Add command line settings.
This commit is contained in:
Родитель
77183f413c
Коммит
d52fc83e39
47
Dockerfile
47
Dockerfile
|
@ -2,52 +2,9 @@ FROM microsoft/dotnet:1.1-sdk
|
|||
|
||||
COPY /src /build
|
||||
|
||||
RUN \
|
||||
set -ex \
|
||||
&& \
|
||||
apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
git cmake \
|
||||
libcurl4-openssl-dev libssl-dev \
|
||||
libavahi-compat-libdnssd-dev \
|
||||
dbus rsyslog avahi-daemon avahi-utils \
|
||||
&& \
|
||||
git clone https://github.com/marcschier/UA-LDS.git /lds \
|
||||
&& cd /lds \
|
||||
&& git submodule init \
|
||||
&& git submodule update \
|
||||
&& \
|
||||
rm -rf /lds/build && mkdir /lds/build && cd /lds/build \
|
||||
&& sed 's/LogSystem[[:blank:]]*=.*/LogSystem=file/' /lds/etc/ualds.conf > /lds/etc/ualds.conf \
|
||||
&& sed 's/LogFile[[:blank:]]*=.*/LogFile=\/app\/Logs\/opcualds.log/' /lds/etc/ualds.conf > /lds/etc/ualds.conf \
|
||||
&& sed 's/LogFileSize[[:blank:]]*=.*/LogFileSize=10/' /lds/etc/ualds.conf > /lds/etc/ualds.conf \
|
||||
&& sed 's/CertificateStorePath[[:blank:]]*=.*/CertificateStorePath=\/app\/Shared\/ualds/' /lds/etc/ualds.conf > /lds/etc/ualds.conf \
|
||||
&& cmake .. && cmake --build . \
|
||||
&& \
|
||||
cp /lds/docker-initd.sh /etc/init.d/lds \
|
||||
&& echo "service rsyslog start" >> /etc/init.d/lds \
|
||||
&& echo "service dbus start" >> /etc/init.d/lds \
|
||||
&& echo "service avahi-daemon restart --no-drop-root --daemonize --syslog" >> /etc/init.d/lds \
|
||||
&& echo "./lds/build/bin/ualds -c /lds/etc/ualds.conf " >> /etc/init.d/lds \
|
||||
&& chmod +x /etc/init.d/lds \
|
||||
&& \
|
||||
echo "#!/bin/bash" > /lds/start.sh \
|
||||
&& echo "service lds start" >> /lds/start.sh \
|
||||
&& echo "until [ -e /app/Shared/ualds/own/certs/ualdscert.der ]; do" >> /lds/start.sh \
|
||||
&& echo " sleep 3 " >> /lds/start.sh \
|
||||
&& echo "done" >> /lds/start.sh \
|
||||
&& echo 'cp /app/Shared/ualds/own/certs/ualdscert.der "/app/Shared/CertificateStores/UA Applications/certs"' >> /lds/start.sh \
|
||||
&& echo 'chmod u+x "/app/Shared/CertificateStores/UA Applications/certs/ualdscert.der"' >> /lds/start.sh \
|
||||
&& echo 'rm -rf /app/Shared/ualds/trusted/certs' >> /lds/start.sh \
|
||||
&& echo 'ln -s "/app/Shared/CertificateStores/UA Applications/certs" /app/Shared/ualds/trusted/certs' >> /lds/start.sh \
|
||||
&& echo 'exec dotnet $@' >> /lds/start.sh \
|
||||
&& chmod +x /lds/start.sh
|
||||
|
||||
EXPOSE 5353
|
||||
|
||||
WORKDIR /build
|
||||
RUN dotnet restore
|
||||
RUN dotnet publish -c Release -o out
|
||||
WORKDIR /build/out
|
||||
ENTRYPOINT ["/lds/start.sh"]
|
||||
|
||||
WORKDIR /build/out
|
||||
ENTRYPOINT ["dotnet", "Opc.Ua.Publisher.dll"]
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace Opc.Ua.Publisher
|
||||
{
|
||||
using System.IO;
|
||||
using static Opc.Ua.Workarounds.TraceWorkaround;
|
||||
|
||||
public class ModuleConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -14,89 +17,95 @@ namespace Opc.Ua.Publisher
|
|||
public ModuleConfiguration(string applicationName)
|
||||
{
|
||||
// set reasonable defaults
|
||||
Configuration = new ApplicationConfiguration();
|
||||
Configuration.ApplicationName = applicationName;
|
||||
Configuration = new ApplicationConfiguration()
|
||||
{
|
||||
ApplicationName = applicationName
|
||||
};
|
||||
Configuration.ApplicationUri = "urn:" + Utils.GetHostName() + ":microsoft:" + Configuration.ApplicationName;
|
||||
Configuration.ApplicationType = ApplicationType.ClientAndServer;
|
||||
Configuration.TransportQuotas = new TransportQuotas { OperationTimeout = 15000 };
|
||||
Configuration.ClientConfiguration = new ClientConfiguration();
|
||||
Configuration.ServerConfiguration = new ServerConfiguration();
|
||||
|
||||
// enable logging
|
||||
Configuration.TraceConfiguration = new TraceConfiguration();
|
||||
Configuration.TraceConfiguration.TraceMasks = Utils.TraceMasks.Error | Utils.TraceMasks.Security | Utils.TraceMasks.StackTrace | Utils.TraceMasks.StartStop;
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_LOGP")))
|
||||
// enable logging and enforce information flag
|
||||
Program.OpcStackTraceMask |= Utils.TraceMasks.Information;
|
||||
Configuration.TraceConfiguration = new TraceConfiguration()
|
||||
{
|
||||
Configuration.TraceConfiguration.OutputFilePath = Environment.GetEnvironmentVariable("_GW_LOGP");
|
||||
TraceMasks = Program.OpcStackTraceMask
|
||||
};
|
||||
// StdOutAndFile is not working correct, due to a bug in the stack. Need to workaround with own Trace for now.
|
||||
Utils.SetTraceOutput(Utils.TraceOutput.FileOnly);
|
||||
if (string.IsNullOrEmpty(Program.LogFileName))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_LOGP")))
|
||||
{
|
||||
Configuration.TraceConfiguration.OutputFilePath = Environment.GetEnvironmentVariable("_GW_LOGP");
|
||||
}
|
||||
else
|
||||
{
|
||||
Configuration.TraceConfiguration.OutputFilePath = "./Logs/" + Configuration.ApplicationName + ".log.txt";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Configuration.TraceConfiguration.OutputFilePath = "./Logs/" + Configuration.ApplicationName + ".log.txt";
|
||||
Configuration.TraceConfiguration.OutputFilePath = Program.LogFileName;
|
||||
}
|
||||
Configuration.TraceConfiguration.ApplySettings();
|
||||
Trace($"Current directory is: {Directory.GetCurrentDirectory()}");
|
||||
Trace($"Log file is: {Utils.GetAbsoluteFilePath(Configuration.TraceConfiguration.OutputFilePath, true, false, false, true)}");
|
||||
Trace($"Trace mask set to: 0x{Program.OpcStackTraceMask:X}");
|
||||
|
||||
if (Configuration.SecurityConfiguration == null)
|
||||
{
|
||||
Configuration.SecurityConfiguration = new SecurityConfiguration();
|
||||
}
|
||||
Configuration.SecurityConfiguration = new SecurityConfiguration();
|
||||
|
||||
if (Configuration.SecurityConfiguration.TrustedPeerCertificates == null)
|
||||
// Trusted cert store configuration.
|
||||
Configuration.SecurityConfiguration.TrustedPeerCertificates = new CertificateTrustList();
|
||||
Configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType = Program.OpcTrustedCertStoreType;
|
||||
if (string.IsNullOrEmpty(Program.OpcTrustedCertStorePath))
|
||||
{
|
||||
Configuration.SecurityConfiguration.TrustedPeerCertificates = new CertificateTrustList();
|
||||
}
|
||||
|
||||
if (Configuration.SecurityConfiguration.TrustedIssuerCertificates == null)
|
||||
{
|
||||
Configuration.SecurityConfiguration.TrustedIssuerCertificates = new CertificateTrustList();
|
||||
}
|
||||
|
||||
if (Configuration.SecurityConfiguration.RejectedCertificateStore == null)
|
||||
{
|
||||
Configuration.SecurityConfiguration.RejectedCertificateStore = new CertificateTrustList();
|
||||
}
|
||||
|
||||
if (Configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType == null)
|
||||
{
|
||||
Configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType = "Directory";
|
||||
}
|
||||
|
||||
if (Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath == null)
|
||||
{
|
||||
Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = "CertificateStores/UA Applications";
|
||||
// Set default.
|
||||
Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = Program.OpcTrustedCertStoreType == CertificateStoreType.X509Store ? Program.OpcTrustedCertX509StorePathDefault : Program.OpcTrustedCertDirectoryStorePathDefault;
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_TPC_SP")))
|
||||
{
|
||||
// Use environment variable.
|
||||
Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = Environment.GetEnvironmentVariable("_TPC_SP");
|
||||
}
|
||||
}
|
||||
|
||||
if (Configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType == null)
|
||||
else
|
||||
{
|
||||
Configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType = "Directory";
|
||||
Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = Program.OpcTrustedCertStorePath;
|
||||
}
|
||||
Trace($"Trusted Peer Certificate store type is: {Configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType}");
|
||||
Trace($"Trusted Peer Certificate store path is: {Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}");
|
||||
|
||||
if (Configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath == null)
|
||||
// Trusted issuer cert store configuration.
|
||||
Configuration.SecurityConfiguration.TrustedIssuerCertificates = new CertificateTrustList();
|
||||
Configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType = Program.OpcIssuerCertStoreType;
|
||||
Configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath = Program.OpcIssuerCertStorePath;
|
||||
Trace($"Trusted Issuer store type is: {Configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType}");
|
||||
Trace($"Trusted Issuer Certificate store path is: {Configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath}");
|
||||
|
||||
// Rejected cert store configuration.
|
||||
Configuration.SecurityConfiguration.RejectedCertificateStore = new CertificateTrustList();
|
||||
Configuration.SecurityConfiguration.RejectedCertificateStore.StoreType = Program.OpcRejectedCertStoreType;
|
||||
Configuration.SecurityConfiguration.RejectedCertificateStore.StorePath = Program.OpcRejectedCertStorePath;
|
||||
Trace($"Rejected certificate store type is: {Configuration.SecurityConfiguration.RejectedCertificateStore.StoreType}");
|
||||
Trace($"Rejected Certificate store path is: {Configuration.SecurityConfiguration.RejectedCertificateStore.StorePath}");
|
||||
|
||||
Configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier()
|
||||
{
|
||||
Configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath = "CertificateStores/UA Certificate Authorities";
|
||||
}
|
||||
|
||||
if (Configuration.SecurityConfiguration.RejectedCertificateStore.StoreType == null)
|
||||
{
|
||||
Configuration.SecurityConfiguration.RejectedCertificateStore.StoreType = "Directory";
|
||||
}
|
||||
|
||||
if (Configuration.SecurityConfiguration.RejectedCertificateStore.StorePath == null)
|
||||
{
|
||||
Configuration.SecurityConfiguration.RejectedCertificateStore.StorePath = "CertificateStores/Rejected Certificates";
|
||||
}
|
||||
|
||||
Configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier();
|
||||
Configuration.SecurityConfiguration.ApplicationCertificate.StoreType = "X509Store";
|
||||
Configuration.SecurityConfiguration.ApplicationCertificate.StorePath = "CurrentUser\\UA_MachineDefault";
|
||||
Configuration.SecurityConfiguration.ApplicationCertificate.SubjectName = Configuration.ApplicationName;
|
||||
StoreType = Program.OpcOwnCertStoreType,
|
||||
StorePath = Program.OpcOwnCertStorePath,
|
||||
SubjectName = Configuration.ApplicationName
|
||||
};
|
||||
Trace($"Application Certificate store type is: {Configuration.SecurityConfiguration.ApplicationCertificate.StoreType}");
|
||||
Trace($"Application Certificate store path is: {Configuration.SecurityConfiguration.ApplicationCertificate.StorePath}");
|
||||
|
||||
// Use existing certificate, if it is there.
|
||||
X509Certificate2 certificate = Configuration.SecurityConfiguration.ApplicationCertificate.Find(true).Result;
|
||||
if (certificate == null)
|
||||
{
|
||||
Trace($"Create a self-signed Application certificate valid from yesterday for {CertificateFactory.defaultLifeTime} months,");
|
||||
Trace($"with a {CertificateFactory.defaultKeySize} bit key and {CertificateFactory.defaultHashSize} bit hash.");
|
||||
certificate = CertificateFactory.CreateCertificate(
|
||||
Configuration.SecurityConfiguration.ApplicationCertificate.StoreType,
|
||||
Configuration.SecurityConfiguration.ApplicationCertificate.StorePath,
|
||||
|
@ -113,75 +122,84 @@ namespace Opc.Ua.Publisher
|
|||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
if (certificate == null)
|
||||
{
|
||||
throw new Exception("OPC UA application certificate could not be created, cannot continue without it!");
|
||||
}
|
||||
Configuration.SecurityConfiguration.ApplicationCertificate.Certificate = certificate ?? throw new Exception("OPC UA application certificate could not be created! Cannot continue without it!");
|
||||
|
||||
Configuration.SecurityConfiguration.ApplicationCertificate.Certificate = certificate;
|
||||
Configuration.ApplicationUri = Utils.GetApplicationUriFromCertificate(certificate);
|
||||
|
||||
// Ensure it is trusted
|
||||
try
|
||||
{
|
||||
ICertificateStore store = Configuration.SecurityConfiguration.TrustedPeerCertificates.OpenStore();
|
||||
if (store == null)
|
||||
// Trust myself if requested.
|
||||
if (Program.TrustMyself)
|
||||
{
|
||||
Program.Trace("Could not open trusted peer store. StorePath={0}", Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath);
|
||||
// Ensure it is trusted
|
||||
try
|
||||
{
|
||||
ICertificateStore store = Configuration.SecurityConfiguration.TrustedPeerCertificates.OpenStore();
|
||||
if (store == null)
|
||||
{
|
||||
Trace($"Could not open trusted peer store. StorePath={Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Trace($"Adding publisher certificate to trusted peer store. StorePath={Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}");
|
||||
X509Certificate2 publicKey = new X509Certificate2(certificate.RawData);
|
||||
store.Add(publicKey).Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
store.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace(e, $"Could not add publisher certificate to trusted peer store. StorePath={Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Program.Trace(Utils.TraceMasks.Information, "Adding certificate to trusted peer store. StorePath={0}", Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath);
|
||||
X509Certificate2 publicKey = new X509Certificate2(certificate.RawData);
|
||||
store.Add(publicKey).Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
store.Close();
|
||||
}
|
||||
Trace("Publisher certificate is not added to trusted peer store.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
else
|
||||
{
|
||||
Program.Trace(e, "Could not add certificate to trusted peer store. StorePath={0}", Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath);
|
||||
Trace("Application certificate found in Application Certificate Store");
|
||||
}
|
||||
|
||||
Configuration.ApplicationUri = Utils.GetApplicationUriFromCertificate(certificate);
|
||||
Trace($"Application certificate is for Application URI: {Configuration.ApplicationUri}");
|
||||
|
||||
// patch our base address
|
||||
if (Configuration.ServerConfiguration.BaseAddresses.Count == 0)
|
||||
{
|
||||
Configuration.ServerConfiguration.BaseAddresses.Add("opc.tcp://" + Configuration.ApplicationName.ToLowerInvariant() + ":62222/UA/Publisher");
|
||||
Configuration.ServerConfiguration.BaseAddresses.Add($"opc.tcp://{Configuration.ApplicationName.ToLowerInvariant()}:{Program.PublisherServerPort}{Program.PublisherServerPath}");
|
||||
}
|
||||
|
||||
// tighten security policy by removing security policy "none"
|
||||
foreach (ServerSecurityPolicy policy in Configuration.ServerConfiguration.SecurityPolicies)
|
||||
foreach (var endpoint in Configuration.ServerConfiguration.BaseAddresses)
|
||||
{
|
||||
if (policy.SecurityMode == MessageSecurityMode.None)
|
||||
{
|
||||
Configuration.ServerConfiguration.SecurityPolicies.Remove(policy);
|
||||
break;
|
||||
}
|
||||
Trace($"Publisher server Endpoint URL: {endpoint}");
|
||||
}
|
||||
|
||||
// turn off LDS registration
|
||||
Configuration.ServerConfiguration.MaxRegistrationInterval = 0;
|
||||
// Set LDS registration interval
|
||||
Configuration.ServerConfiguration.MaxRegistrationInterval = Program.LdsRegistrationInterval;
|
||||
Trace($"LDS(-ME) registration intervall set to {Program.LdsRegistrationInterval} ms (0 means no registration)");
|
||||
|
||||
// add sign & encrypt policy
|
||||
ServerSecurityPolicy newPolicy = new ServerSecurityPolicy();
|
||||
newPolicy.SecurityMode = MessageSecurityMode.SignAndEncrypt;
|
||||
newPolicy.SecurityPolicyUri = SecurityPolicies.Basic128Rsa15;
|
||||
ServerSecurityPolicy newPolicy = new ServerSecurityPolicy()
|
||||
{
|
||||
SecurityMode = MessageSecurityMode.SignAndEncrypt,
|
||||
SecurityPolicyUri = SecurityPolicies.Basic256Sha256
|
||||
};
|
||||
Configuration.ServerConfiguration.SecurityPolicies.Add(newPolicy);
|
||||
Trace($"Security policy {newPolicy.SecurityPolicyUri} with mode {newPolicy.SecurityMode} added");
|
||||
|
||||
// the OperationTimeout should be twice the minimum value for PublishingInterval * KeepAliveCount, so set to 120s
|
||||
Configuration.TransportQuotas.OperationTimeout = 120000;
|
||||
Configuration.TransportQuotas.OperationTimeout = Program.OpcOperationTimeout;
|
||||
Trace($"OperationTimeout set to {Configuration.TransportQuotas.OperationTimeout}");
|
||||
|
||||
// allow SHA1 certificates for now as many OPC Servers still use them
|
||||
Configuration.SecurityConfiguration.RejectSHA1SignedCertificates = false;
|
||||
Trace($"Rejection of SHA1 signed certificates is {(Configuration.SecurityConfiguration.RejectSHA1SignedCertificates ? "enabled" : "disabled")}");
|
||||
|
||||
// allow 1024 minimum key size as many OPC Servers still use them
|
||||
Configuration.SecurityConfiguration.MinimumCertificateKeySize = 1024;
|
||||
Trace($"Minimum certificate key size set to {Configuration.SecurityConfiguration.MinimumCertificateKeySize}");
|
||||
|
||||
// validate the configuration now
|
||||
Configuration.Validate(Configuration.ApplicationType).Wait();
|
||||
|
|
|
@ -15,9 +15,11 @@ namespace Publisher
|
|||
public PublisherNodeManager(Opc.Ua.Server.IServerInternal server, ApplicationConfiguration configuration)
|
||||
: base(server)
|
||||
{
|
||||
List<string> namespaceUris = new List<string>();
|
||||
namespaceUris.Add(Namespaces.Publisher);
|
||||
namespaceUris.Add(Namespaces.Publisher + "/Instance");
|
||||
List<string> namespaceUris = new List<string>
|
||||
{
|
||||
Namespaces.Publisher,
|
||||
Namespaces.Publisher + "/Instance"
|
||||
};
|
||||
NamespaceUris = namespaceUris;
|
||||
|
||||
m_typeNamespaceIndex = Server.NamespaceUris.GetIndexOrAppend(namespaceUris[0]);
|
||||
|
|
|
@ -37,9 +37,11 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Devices" Version="1.3.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.4.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Devices" Version="1.3.1" />
|
||||
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.4.1" />
|
||||
<PackageReference Include="Mono.Options" Version="5.3.0.1" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua" Version="0.4.1" />
|
||||
<PackageReference Include="System.CommandLine" Version="0.1.0-e170819-1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
397
src/Program.cs
397
src/Program.cs
|
@ -2,159 +2,368 @@
|
|||
using IoTHubCredentialTools;
|
||||
using Microsoft.Azure.Devices;
|
||||
using Microsoft.Azure.Devices.Client;
|
||||
using Mono.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Opc.Ua.Client;
|
||||
using Publisher;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Opc.Ua.Publisher
|
||||
{
|
||||
using static Opc.Ua.CertificateStoreType;
|
||||
using static Opc.Ua.Workarounds.TraceWorkaround;
|
||||
using static System.Console;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static ApplicationConfiguration m_configuration = null;
|
||||
public static List<Session> m_sessions = new List<Session>();
|
||||
public static PublishedNodesCollection m_nodesLookups = new PublishedNodesCollection();
|
||||
public static List<Uri> m_endpointUrls = new List<Uri>();
|
||||
public static string m_applicationName = string.Empty;
|
||||
public static string ApplicationName { get; set; }
|
||||
public static DeviceClient m_deviceClient = null;
|
||||
public static string IoTHubOwnerConnectionString { get; set; }
|
||||
public static string LogFileName { get; set; }
|
||||
public static ushort PublisherServerPort { get; set; } = 62222;
|
||||
public static string PublisherServerPath { get; set; } = "/UA/Publisher";
|
||||
public static int LdsRegistrationInterval { get; set; } = 0;
|
||||
public static int OpcOperationTimeout { get; set; } = 120000;
|
||||
public static bool TrustMyself { get; set; } = true;
|
||||
public static int OpcStackTraceMask { get; set; } = Utils.TraceMasks.Error | Utils.TraceMasks.Security | Utils.TraceMasks.StackTrace | Utils.TraceMasks.StartStop | Utils.TraceMasks.Information;
|
||||
public static string PublisherServerSecurityPolicy { get; set; } = SecurityPolicies.Basic128Rsa15;
|
||||
|
||||
private static PublisherServer m_server = new PublisherServer();
|
||||
public static string OpcOwnCertStoreType { get; set; } = X509Store;
|
||||
private const string _opcOwnCertDirectoryStorePathDefault = "CertificateStores/own";
|
||||
private const string _opcOwnCertX509StorePathDefault = "CurrentUser\\UA_MachineDefault";
|
||||
public static string OpcOwnCertStorePath { get; set; } = _opcOwnCertX509StorePathDefault;
|
||||
|
||||
/// <summary>
|
||||
/// Trace message helper
|
||||
/// </summary>
|
||||
public static void Trace(string message, params object[] args)
|
||||
public static string OpcTrustedCertStoreType { get; set; } = Directory;
|
||||
public static string OpcTrustedCertDirectoryStorePathDefault = "CertificateStores/UA Applications";
|
||||
public static string OpcTrustedCertX509StorePathDefault = "CurrentUser\\UA_MachineDefault";
|
||||
public static string OpcTrustedCertStorePath { get; set; } = null;
|
||||
|
||||
public static string OpcRejectedCertStoreType { get; set; } = Directory;
|
||||
private const string _opcRejectedCertDirectoryStorePathDefault = "CertificateStores/Rejected Certificates";
|
||||
private const string _opcRejectedCertX509StorePathDefault = "CurrentUser\\UA_MachineDefault";
|
||||
public static string OpcRejectedCertStorePath { get; set; } = _opcRejectedCertDirectoryStorePathDefault;
|
||||
|
||||
public static string OpcIssuerCertStoreType { get; set; } = Directory;
|
||||
private const string _opcIssuerCertDirectoryStorePathDefault = "CertificateStores/UA Certificate Authorities";
|
||||
private const string _opcIssuerCertX509StorePathDefault = "CurrentUser\\UA_MachineDefault";
|
||||
public static string OpcIssuerCertStorePath { get; set; } = _opcIssuerCertDirectoryStorePathDefault;
|
||||
|
||||
public static string IotDeviceCertStoreType { get; set; } = X509Store;
|
||||
private const string _iotDeviceCertDirectoryStorePathDefault = "CertificateStores/IoTHub";
|
||||
private const string _iotDeviceCertX509StorePathDefault = "IoTHub";
|
||||
public static string IotDeviceCertStorePath { get; set; } = _iotDeviceCertX509StorePathDefault;
|
||||
|
||||
public static string PublishedNodesAbsFilenameDefault = $"{System.IO.Directory.GetCurrentDirectory()}{Path.DirectorySeparatorChar}publishednodes.json";
|
||||
public static string PublishedNodesAbsFilename { get; set; }
|
||||
|
||||
|
||||
private static PublisherServer _publishingServer = new PublisherServer();
|
||||
public static Microsoft.Azure.Devices.Client.TransportType IotHubProtocol { get; set; } = Microsoft.Azure.Devices.Client.TransportType.Mqtt;
|
||||
|
||||
private static void Usage(Mono.Options.OptionSet options)
|
||||
{
|
||||
Utils.Trace(Utils.TraceMasks.Error, message, args);
|
||||
Console.WriteLine(DateTime.Now.ToString() + ": " + message, args);
|
||||
}
|
||||
|
||||
public static void Trace(int traceMask, string format, params object[] args)
|
||||
{
|
||||
Utils.Trace(traceMask, format, args);
|
||||
Console.WriteLine(DateTime.Now.ToString() + ": " + format, args);
|
||||
}
|
||||
|
||||
public static void Trace(Exception e, string format, params object[] args)
|
||||
{
|
||||
Utils.Trace(e, format, args);
|
||||
Console.WriteLine(DateTime.Now.ToString() + ": " + e.Message.ToString());
|
||||
Console.WriteLine(DateTime.Now.ToString() + ": " + format, args);
|
||||
// show usage
|
||||
WriteLine();
|
||||
WriteLine("Usage: {0}.exe applicationname [iothubconnectionstring] [options]", Assembly.GetEntryAssembly().GetName().Name);
|
||||
WriteLine();
|
||||
WriteLine("OPC Edge Publisher to subscribe to configured OPC UA servers and send telemetry to Azure IoTHub.");
|
||||
WriteLine();
|
||||
WriteLine("applicationname: the OPC UA application name to use, required");
|
||||
WriteLine(" The application name is also used to register the publisher under this name in the");
|
||||
WriteLine(" IoTHub device registry.");
|
||||
WriteLine();
|
||||
WriteLine("iothubconnectionstring: the IoTHub owner connectionstring, optional");
|
||||
WriteLine();
|
||||
WriteLine("There are a couple of environemnt variables which could be used to control the application:");
|
||||
WriteLine("_HUB_CS: sets the IoTHub owner connectionstring");
|
||||
WriteLine("_GW_LOGP: sets the filename of the log file to use");
|
||||
WriteLine("_TPC_SP: sets the path to store certificates of trusted stations");
|
||||
WriteLine("_GW_PNFP: sets the filename of the publishing configuration file");
|
||||
WriteLine();
|
||||
WriteLine("Notes:");
|
||||
WriteLine("If an environment variable is controlling the OPC UA stack configuration, they are only taken into account");
|
||||
WriteLine("if they are not set in the OPC UA configuration file.");
|
||||
WriteLine("Command line arguments overrule OPC UA configuration file settings and environement variable settings.");
|
||||
WriteLine();
|
||||
|
||||
// output the options
|
||||
WriteLine("Options:");
|
||||
options.WriteOptionDescriptions(Console.Out);
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var opcTraceInitialized = false;
|
||||
try
|
||||
{
|
||||
if ((args.Length == 0) || string.IsNullOrEmpty(args[0]) || args[0].Equals("localhost", StringComparison.OrdinalIgnoreCase))
|
||||
var shouldShowHelp = false;
|
||||
|
||||
// these are the available options, not that they set the variables
|
||||
Mono.Options.OptionSet options = new Mono.Options.OptionSet {
|
||||
// Publishing configuration options
|
||||
{ "pf|publishfile=", $"the filename to configure the nodes to publish.\nDefault: '{PublishedNodesAbsFilenameDefault}'", (string p) => PublishedNodesAbsFilename = p },
|
||||
|
||||
// IoTHub specific options
|
||||
{ "ih|iothubprotocol=", $"the protocol to use for communication with Azure IoTHub (allowed values: {string.Join(", ", Enum.GetNames(IotHubProtocol.GetType()))}).\nDefault: {Enum.GetName(IotHubProtocol.GetType(), IotHubProtocol)}",
|
||||
(Microsoft.Azure.Devices.Client.TransportType p) => IotHubProtocol = p
|
||||
},
|
||||
|
||||
// opc server configuration options
|
||||
{ "lf|logfile=", $"the filename of the logfile to use.\nDefault: './logs/<applicationname>.log.txt'", (string l) => LogFileName = l },
|
||||
{ "pn|portnum=", $"the server port of the publisher OPC server endpoint.\nDefault: {PublisherServerPort}", (ushort p) => PublisherServerPort = p },
|
||||
{ "pa|path=", $"the enpoint URL path part of the publisher OPC server endpoint.\nDefault: '{PublisherServerPath}'", (string a) => PublisherServerPath = a },
|
||||
{ "lr|ldsreginterval=", $"the LDS(-ME) registration interval in ms.\nDefault: {LdsRegistrationInterval}", (int i) => LdsRegistrationInterval = i },
|
||||
{ "ot|operationtimeout=", $"the operation timeout of the publisher OPC UA client in ms.\nDefault: {OpcOperationTimeout}", (int i) => OpcOperationTimeout = i },
|
||||
{ "st|opcstacktracemask=", $"the trace mask for the OPC stack. See github OPC .NET stack for definitions.\n(Information is enforced)\nDefault: 0x{OpcStackTraceMask:X}", (int i) => OpcStackTraceMask = i },
|
||||
|
||||
// trust own public cert option
|
||||
{ "tm|trustmyself=", $"the publisher certificate is put into the trusted certificate store automatically.\nDefault: {TrustMyself}", (bool b) => TrustMyself = b },
|
||||
|
||||
// own cert store options
|
||||
{ "at|appcertstoretype=", $"the own application cert store type. \n(allowed values: Directory, X509Store)\nDefault: '{OpcOwnCertStoreType}'", (string s) => {
|
||||
if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(Directory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
OpcOwnCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : Directory;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException();
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "ap|appcertstorepath=", $"the path where the own application cert should be stored\nDefault (depends on store type):\n" +
|
||||
$"X509Store: '{_opcOwnCertX509StorePathDefault}'\n" +
|
||||
$"Directory: '{_opcOwnCertDirectoryStorePathDefault}'", (string s) => OpcOwnCertStorePath = s
|
||||
},
|
||||
|
||||
// trusted cert store options
|
||||
{
|
||||
"tt|trustedcertstoretype=", $"the trusted cert store type. \n(allowed values: Directory, X509Store)\nDefault: {OpcTrustedCertStoreType}", (string s) => {
|
||||
if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(Directory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
OpcTrustedCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : Directory;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException();
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "tp|trustedcertstorepath=", $"the path of the trusted cert store\nDefault (depends on store type):\n" +
|
||||
$"X509Store: '{OpcTrustedCertX509StorePathDefault}'\n" +
|
||||
$"Directory: '{OpcTrustedCertDirectoryStorePathDefault}'", (string s) => OpcTrustedCertStorePath = s
|
||||
},
|
||||
|
||||
// rejected cert store options
|
||||
{ "rt|rejectedcertstoretype=", $"the rejected cert store type. \n(allowed values: Directory, X509Store)\nDefault: {OpcRejectedCertStoreType}", (string s) => {
|
||||
if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(Directory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
OpcRejectedCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : Directory;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException();
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "rp|rejectedcertstorepath=", $"the path of the rejected cert store\nDefault (depends on store type):\n" +
|
||||
$"X509Store: '{_opcRejectedCertX509StorePathDefault}'\n" +
|
||||
$"Directory: '{_opcRejectedCertDirectoryStorePathDefault}'", (string s) => OpcRejectedCertStorePath = s
|
||||
},
|
||||
|
||||
// issuer cert store options
|
||||
{
|
||||
"it|issuercertstoretype=", $"the trusted issuer cert store type. \n(allowed values: Directory, X509Store)\nDefault: {OpcIssuerCertStoreType}", (string s) => {
|
||||
if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(Directory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
OpcIssuerCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : Directory;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException();
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "p|issuercertstorepath=", $"the path of the trusted issuer cert store\nDefault (depends on store type):\n" +
|
||||
$"X509Store: '{_opcIssuerCertX509StorePathDefault}'\n" +
|
||||
$"Directory: '{_opcIssuerCertDirectoryStorePathDefault}'", (string s) => OpcIssuerCertStorePath = s
|
||||
},
|
||||
|
||||
// device connection string cert store options
|
||||
{ "dt|devicecertstoretype=", $"the iothub device cert store type. \n(allowed values: Directory, X509Store)\nDefault: {IotDeviceCertStoreType}", (string s) => {
|
||||
if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(Directory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
IotDeviceCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : Directory;
|
||||
IotDeviceCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? _iotDeviceCertX509StorePathDefault : _iotDeviceCertDirectoryStorePathDefault;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException();
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "dp|devicecertstorepath=", $"the path of the iot device cert store\nDefault Default (depends on store type):\n" +
|
||||
$"X509Store: '{_iotDeviceCertX509StorePathDefault}'\n" +
|
||||
$"Directory: '{_iotDeviceCertDirectoryStorePathDefault}'", (string s) => IotDeviceCertStorePath = s
|
||||
},
|
||||
|
||||
// misc
|
||||
{ "h|help", "show this message and exit", h => shouldShowHelp = h != null },
|
||||
};
|
||||
|
||||
List<string> arguments;
|
||||
try
|
||||
{
|
||||
m_applicationName = Utils.GetHostName();
|
||||
// parse the command line
|
||||
arguments = options.Parse(args);
|
||||
}
|
||||
else
|
||||
catch (OptionException e)
|
||||
{
|
||||
m_applicationName = args[0];
|
||||
// show usage
|
||||
Usage(options);
|
||||
return;
|
||||
}
|
||||
|
||||
Trace("Publisher is starting up...");
|
||||
ModuleConfiguration moduleConfiguration = new ModuleConfiguration(m_applicationName);
|
||||
// Validate and parse arguments.
|
||||
if (arguments.Count > 2 || shouldShowHelp)
|
||||
{
|
||||
Usage(options);
|
||||
return;
|
||||
}
|
||||
else if (arguments.Count == 2)
|
||||
{
|
||||
ApplicationName = arguments[0];
|
||||
IoTHubOwnerConnectionString = arguments[1];
|
||||
}
|
||||
else if (arguments.Count == 1)
|
||||
{
|
||||
ApplicationName = arguments[0];
|
||||
}
|
||||
else {
|
||||
ApplicationName = Utils.GetHostName();
|
||||
}
|
||||
|
||||
WriteLine("Publisher is starting up...");
|
||||
ModuleConfiguration moduleConfiguration = new ModuleConfiguration(ApplicationName);
|
||||
opcTraceInitialized = true;
|
||||
m_configuration = moduleConfiguration.Configuration;
|
||||
m_configuration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_CertificateValidation);
|
||||
|
||||
// start our server interface
|
||||
try
|
||||
{
|
||||
Trace("Starting server on endpoint " + m_configuration.ServerConfiguration.BaseAddresses[0].ToString() + "...");
|
||||
m_server.Start(m_configuration);
|
||||
Trace($"Starting server on endpoint {m_configuration.ServerConfiguration.BaseAddresses[0].ToString()} ...");
|
||||
_publishingServer.Start(m_configuration);
|
||||
Trace("Server started.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace("Starting server failed with: " + ex.Message);
|
||||
Trace($"Starting server failed with: {ex.Message}");
|
||||
Trace("exiting...");
|
||||
return;
|
||||
}
|
||||
|
||||
// check if we also received an owner connection string
|
||||
string ownerConnectionString = string.Empty;
|
||||
if ((args.Length > 1) && !string.IsNullOrEmpty(args[1]))
|
||||
{
|
||||
ownerConnectionString = args[1];
|
||||
}
|
||||
else
|
||||
if (string.IsNullOrEmpty(IoTHubOwnerConnectionString))
|
||||
{
|
||||
Trace("IoT Hub owner connection string not passed as argument.");
|
||||
|
||||
// check if we have an environment variable to register ourselves with IoT Hub
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_HUB_CS")))
|
||||
{
|
||||
ownerConnectionString = Environment.GetEnvironmentVariable("_HUB_CS");
|
||||
IoTHubOwnerConnectionString = Environment.GetEnvironmentVariable("_HUB_CS");
|
||||
Trace("IoT Hub owner connection string read from environment.");
|
||||
}
|
||||
}
|
||||
|
||||
// register ourselves with IoT Hub
|
||||
if (ownerConnectionString != string.Empty)
|
||||
string deviceConnectionString;
|
||||
Trace($"IoTHub device cert store type is: {IotDeviceCertStoreType}");
|
||||
Trace($"IoTHub device cert path is: {IotDeviceCertStorePath}");
|
||||
if (IoTHubOwnerConnectionString != string.Empty)
|
||||
{
|
||||
Trace("Attemping to register ourselves with IoT Hub using owner connection string: " + ownerConnectionString);
|
||||
RegistryManager manager = RegistryManager.CreateFromConnectionString(ownerConnectionString);
|
||||
Trace($"Attemping to register ourselves with IoT Hub using owner connection string: {IoTHubOwnerConnectionString}");
|
||||
RegistryManager manager = RegistryManager.CreateFromConnectionString(IoTHubOwnerConnectionString);
|
||||
|
||||
// remove any existing device
|
||||
Device existingDevice = manager.GetDeviceAsync(m_applicationName).Result;
|
||||
Device existingDevice = manager.GetDeviceAsync(ApplicationName).Result;
|
||||
if (existingDevice != null)
|
||||
{
|
||||
manager.RemoveDeviceAsync(m_applicationName).Wait();
|
||||
Trace($"Device '{ApplicationName}' found in IoTHub registry. Remove it.");
|
||||
manager.RemoveDeviceAsync(ApplicationName).Wait();
|
||||
}
|
||||
|
||||
Device newDevice = manager.AddDeviceAsync(new Device(m_applicationName)).Result;
|
||||
Trace($"Adding device '{ApplicationName}' to IoTHub registry.");
|
||||
Device newDevice = manager.AddDeviceAsync(new Device(ApplicationName)).Result;
|
||||
if (newDevice != null)
|
||||
{
|
||||
string hostname = ownerConnectionString.Substring(0, ownerConnectionString.IndexOf(";"));
|
||||
string deviceConnectionString = hostname + ";DeviceId=" + m_applicationName + ";SharedAccessKey=" + newDevice.Authentication.SymmetricKey.PrimaryKey;
|
||||
SecureIoTHubToken.Write(m_applicationName, deviceConnectionString);
|
||||
string hostname = IoTHubOwnerConnectionString.Substring(0, IoTHubOwnerConnectionString.IndexOf(";"));
|
||||
deviceConnectionString = hostname + ";DeviceId=" + ApplicationName + ";SharedAccessKey=" + newDevice.Authentication.SymmetricKey.PrimaryKey;
|
||||
Trace($"Device connection string is: {deviceConnectionString}");
|
||||
Trace($"Adding it to device cert store.");
|
||||
SecureIoTHubToken.Write(ApplicationName, deviceConnectionString, IotDeviceCertStoreType, IotDeviceCertStoreType);
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace("Could not register ourselves with IoT Hub using owner connection string: " + ownerConnectionString);
|
||||
Trace($"Could not register ourselves with IoT Hub using owner connection string: {IoTHubOwnerConnectionString}");
|
||||
Trace("exiting...");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace("IoT Hub owner connection string not found, registration with IoT Hub abandoned.");
|
||||
Trace("IoT Hub owner connection string not specified. Assume device connection string already in cert store.");
|
||||
}
|
||||
|
||||
// try to read connection string from secure store and open IoTHub client
|
||||
Trace("Attemping to read connection string from secure store with certificate name: " + m_applicationName);
|
||||
string connectionString = SecureIoTHubToken.Read(m_applicationName);
|
||||
if (!string.IsNullOrEmpty(connectionString))
|
||||
Trace($"Attemping to read device connection string from cert store using subject name: {ApplicationName}");
|
||||
deviceConnectionString = SecureIoTHubToken.Read(ApplicationName, IotDeviceCertStoreType, IotDeviceCertStorePath);
|
||||
if (!string.IsNullOrEmpty(deviceConnectionString))
|
||||
{
|
||||
Trace("Attemping to configure publisher with connection string: " + connectionString);
|
||||
m_deviceClient = DeviceClient.CreateFromConnectionString(connectionString, Microsoft.Azure.Devices.Client.TransportType.Mqtt);
|
||||
Trace($"Create Publisher IoTHub client with device connection string: '{deviceConnectionString}' using '{IotHubProtocol}' for communication.");
|
||||
m_deviceClient = DeviceClient.CreateFromConnectionString(deviceConnectionString, IotHubProtocol);
|
||||
m_deviceClient.RetryPolicy = RetryPolicyType.Exponential_Backoff_With_Jitter;
|
||||
m_deviceClient.OpenAsync().Wait();
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace("Device connection string not found in secure store.");
|
||||
Trace("Device connection string not found in secure store. Could not connect to IoTHub.");
|
||||
Trace("exiting...");
|
||||
return;
|
||||
}
|
||||
|
||||
// get a list of persisted endpoint URLs and create a session for each.
|
||||
try
|
||||
{
|
||||
// check if we have an env variable specifying the published nodes path, otherwise use current directory
|
||||
string publishedNodesFilePath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "publishednodes.json";
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_PNFP")))
|
||||
if (string.IsNullOrEmpty(PublishedNodesAbsFilename))
|
||||
{
|
||||
publishedNodesFilePath = Environment.GetEnvironmentVariable("_GW_PNFP");
|
||||
// check if we have an env variable specifying the published nodes path, otherwise use the default
|
||||
PublishedNodesAbsFilename = PublishedNodesAbsFilenameDefault;
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_PNFP")))
|
||||
{
|
||||
Trace("Publishing node configuration file path read from environment.");
|
||||
PublishedNodesAbsFilename = Environment.GetEnvironmentVariable("_GW_PNFP");
|
||||
}
|
||||
}
|
||||
|
||||
Trace("Attemping to load nodes file from: " + publishedNodesFilePath);
|
||||
m_nodesLookups = JsonConvert.DeserializeObject<PublishedNodesCollection>(File.ReadAllText(publishedNodesFilePath));
|
||||
Trace("Loaded " + m_nodesLookups.Count.ToString() + " nodes.");
|
||||
Trace($"Attemping to load nodes file from: {PublishedNodesAbsFilename}");
|
||||
m_nodesLookups = JsonConvert.DeserializeObject<PublishedNodesCollection>(File.ReadAllText(PublishedNodesAbsFilename));
|
||||
Trace($"Loaded {m_nodesLookups.Count.ToString()} nodes.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace("Nodes file loading failed with: " + ex.Message);
|
||||
Trace($"Nodes file loading failed with: {ex.Message}");
|
||||
Trace("exiting...");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (NodeLookup nodeLookup in m_nodesLookups)
|
||||
|
@ -172,7 +381,7 @@ namespace Opc.Ua.Publisher
|
|||
List<Task> connectionAttempts = new List<Task>();
|
||||
foreach (Uri endpointUrl in m_endpointUrls)
|
||||
{
|
||||
Trace("Connecting to server: " + endpointUrl);
|
||||
Trace($"Connecting to server: {endpointUrl}");
|
||||
connectionAttempts.Add(EndpointConnect(endpointUrl));
|
||||
}
|
||||
|
||||
|
@ -181,7 +390,7 @@ namespace Opc.Ua.Publisher
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace("Exception: " + ex.ToString() + "\r\n" + ex.InnerException != null ? ex.InnerException.ToString() : null);
|
||||
Trace($"Exception: {ex.ToString()}\r\n{ ex.InnerException?.ToString()}");
|
||||
}
|
||||
|
||||
// subscribe to preconfigured nodes
|
||||
|
@ -196,7 +405,7 @@ namespace Opc.Ua.Publisher
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace("Unexpected error publishing node: " + ex.Message + "\r\nIgnoring node: " + nodeLookup.EndPointURL.AbsoluteUri + ", " + nodeLookup.NodeID.ToString());
|
||||
Trace($"Unexpected error publishing node: {ex.Message}\r\nIgnoring node: {nodeLookup.EndPointURL.AbsoluteUri}, {nodeLookup.NodeID.ToString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +425,16 @@ namespace Opc.Ua.Publisher
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace(e, "Unhandled exception in Publisher, exiting!");
|
||||
if (opcTraceInitialized)
|
||||
{
|
||||
Trace(e, "Unhandled exception in Publisher. Exiting... ");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine($"{DateTime.Now.ToString()}: Unhandled exception in Publisher:");
|
||||
WriteLine($"{DateTime.Now.ToString()}: {e.Message.ToString()}");
|
||||
WriteLine($"{DateTime.Now.ToString()}: exiting...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,7 +459,7 @@ namespace Opc.Ua.Publisher
|
|||
|
||||
if (newSession != null)
|
||||
{
|
||||
Trace("Created session with updated endpoint " + selectedEndpoint.EndpointUrl + " from server!");
|
||||
Trace($"Created session with updated endpoint '{selectedEndpoint.EndpointUrl}' from server!");
|
||||
newSession.KeepAlive += new KeepAliveEventHandler((sender, e) => StandardClient_KeepAlive(sender, e, newSession));
|
||||
m_sessions.Add(newSession);
|
||||
}
|
||||
|
@ -281,24 +499,24 @@ namespace Opc.Ua.Publisher
|
|||
}
|
||||
|
||||
// add the new monitored item.
|
||||
MonitoredItem monitoredItem = new MonitoredItem(subscription.DefaultItem);
|
||||
|
||||
monitoredItem.StartNodeId = nodeLookup.NodeID;
|
||||
monitoredItem.AttributeId = Attributes.Value;
|
||||
monitoredItem.DisplayName = nodeDisplayName;
|
||||
monitoredItem.MonitoringMode = MonitoringMode.Reporting;
|
||||
monitoredItem.SamplingInterval = 1000;
|
||||
monitoredItem.QueueSize = 0;
|
||||
monitoredItem.DiscardOldest = true;
|
||||
|
||||
MonitoredItem monitoredItem = new MonitoredItem(subscription.DefaultItem)
|
||||
{
|
||||
StartNodeId = nodeLookup.NodeID,
|
||||
AttributeId = Attributes.Value,
|
||||
DisplayName = nodeDisplayName,
|
||||
MonitoringMode = MonitoringMode.Reporting,
|
||||
SamplingInterval = 1000,
|
||||
QueueSize = 0,
|
||||
DiscardOldest = true
|
||||
};
|
||||
monitoredItem.Notification += new MonitoredItemNotificationEventHandler(MonitoredItem_Notification);
|
||||
subscription.AddItem(monitoredItem);
|
||||
subscription.ApplyChanges();
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace("ERROR: Could not find endpoint URL " + nodeLookup.EndPointURL.ToString() + " in active server sessions, NodeID " + nodeLookup.NodeID.Identifier.ToString() + " NOT published!");
|
||||
Trace("To fix this, please update your publishednodes.json file with the updated endpoint URL!");
|
||||
Trace($"ERROR: Could not find endpoint URL '{nodeLookup.EndPointURL.ToString()}' in active server sessions, NodeID '{nodeLookup.NodeID.Identifier.ToString()}' NOT published!");
|
||||
Trace($"To fix this, please update '{PublishedNodesAbsFilename}' with the updated endpoint URL!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -344,7 +562,7 @@ namespace Opc.Ua.Publisher
|
|||
|
||||
// publish
|
||||
eventMessage.Properties.Add("content-type", "application/opcua+uajson");
|
||||
eventMessage.Properties.Add("deviceName", m_applicationName);
|
||||
eventMessage.Properties.Add("deviceName", ApplicationName);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -361,7 +579,7 @@ namespace Opc.Ua.Publisher
|
|||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Trace("Error processing monitored item notification: " + exception.ToString());
|
||||
Trace($"Error processing monitored item notification: {exception.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,11 +592,7 @@ namespace Opc.Ua.Publisher
|
|||
{
|
||||
if (!ServiceResult.IsGood(e.Status))
|
||||
{
|
||||
Trace(String.Format(
|
||||
"Status: {0}/t/tOutstanding requests: {1}/t/tDefunct requests: {2}",
|
||||
e.Status,
|
||||
session.OutstandingRequestCount,
|
||||
session.DefunctRequestCount));
|
||||
Trace($"Status: {e.Status}/t/tOutstanding requests: {session.OutstandingRequestCount}/t/tDefunct requests: {session.DefunctRequestCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -390,13 +604,10 @@ namespace Opc.Ua.Publisher
|
|||
{
|
||||
if (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted)
|
||||
{
|
||||
Trace("Certificate \""
|
||||
+ e.Certificate.Subject
|
||||
+ "\" not trusted. If you want to trust this certificate, please copy it from the \""
|
||||
+ m_configuration.SecurityConfiguration.RejectedCertificateStore.StorePath + "/certs"
|
||||
+ "\" to the \""
|
||||
+ m_configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath + "/certs"
|
||||
+ "\" folder. A restart of the gateway is NOT required.");
|
||||
Trace($"Certificate '{e.Certificate.Subject}' not trusted. If you want to trust this certificate, please copy it from the/r/n" +
|
||||
$"'{m_configuration.SecurityConfiguration.RejectedCertificateStore.StorePath}/certs' to the " +
|
||||
$"'{m_configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}/certs' folder./r/n" +
|
||||
"A restart of the gateway is NOT required.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace Publisher
|
|||
{
|
||||
ServerProperties properties = new ServerProperties();
|
||||
properties.ManufacturerName = "Contoso";
|
||||
properties.ProductName = "OPC UA Factory Publisher";
|
||||
properties.ProductName = "IoT Edge OPC Publisher";
|
||||
properties.ProductUri = "";
|
||||
properties.SoftwareVersion = Utils.GetAssemblySoftwareVersion();
|
||||
properties.BuildNumber = Utils.GetAssemblyBuildNumber();
|
||||
|
|
|
@ -12,6 +12,8 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Publisher
|
||||
{
|
||||
using static Opc.Ua.Utils;
|
||||
|
||||
public partial class PublisherState
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -34,7 +36,7 @@ namespace Publisher
|
|||
{
|
||||
if (inputArguments[0] == null || inputArguments[1] == null)
|
||||
{
|
||||
Program.Trace("PublishNodeMethod: Invalid Arguments!");
|
||||
Trace("PublishNodeMethod: Invalid Arguments!");
|
||||
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments!");
|
||||
}
|
||||
|
||||
|
@ -42,19 +44,21 @@ namespace Publisher
|
|||
string uri = inputArguments[1] as string;
|
||||
if (string.IsNullOrEmpty(nodeID) || string.IsNullOrEmpty(uri))
|
||||
{
|
||||
Program.Trace("PublishNodeMethod: Arguments are not valid strings!");
|
||||
Trace("PublishNodeMethod: Arguments are not valid strings!");
|
||||
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments as strings!");
|
||||
}
|
||||
|
||||
NodeLookup lookup = new NodeLookup();
|
||||
lookup.NodeID = new NodeId(nodeID);
|
||||
NodeLookup lookup = new NodeLookup()
|
||||
{
|
||||
NodeID = new NodeId(nodeID)
|
||||
};
|
||||
try
|
||||
{
|
||||
lookup.EndPointURL = new Uri(uri);
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
Program.Trace("PublishNodeMethod: Invalid endpoint URL!");
|
||||
Trace("PublishNodeMethod: Invalid endpoint URL!");
|
||||
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!");
|
||||
}
|
||||
|
||||
|
@ -65,9 +69,9 @@ namespace Publisher
|
|||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Program.Trace("PublishNodeMethod: Session not found, creating one for " + lookup.EndPointURL);
|
||||
Trace($"PublishNodeMethod: Session not found, creating one for endpoint '{lookup.EndPointURL}'");
|
||||
Program.EndpointConnect(lookup.EndPointURL).Wait();
|
||||
Program.Trace("PublishNodeMethod: Session created.");
|
||||
Trace("PublishNodeMethod: Session created.");
|
||||
|
||||
return DoPublish(lookup);
|
||||
});
|
||||
|
@ -76,8 +80,8 @@ namespace Publisher
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Trace("PublishNodeMethod: Exception: " + ex.ToString());
|
||||
return ServiceResult.Create(ex, StatusCodes.BadUnexpectedError, "Unexpected error publishing node: " + ex.Message);
|
||||
Trace(ex, "PublishNodeMethod: Exception while trying to setup publishing");
|
||||
return ServiceResult.Create(ex, StatusCodes.BadUnexpectedError, $"Unexpected error publishing node: {ex.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -109,10 +113,10 @@ namespace Publisher
|
|||
|
||||
if (matchingSession == null)
|
||||
{
|
||||
Program.Trace("PublishNodeMethod: No matching session found for " + lookup.EndPointURL.ToString());
|
||||
Trace($"DoPublish: No matching session found for endpoint '{lookup.EndPointURL.ToString()}'");
|
||||
return ServiceResult.Create(StatusCodes.BadSessionIdInvalid, "Session for published node not found!");
|
||||
}
|
||||
Program.Trace("PublishNodeMethod: Session found.");
|
||||
Trace("DoPublish: Session found.");
|
||||
|
||||
|
||||
// check if the node has already been published
|
||||
|
@ -120,14 +124,14 @@ namespace Publisher
|
|||
{
|
||||
if (item.StartNodeId == lookup.NodeID)
|
||||
{
|
||||
Program.Trace("PublishNodeMethod: Node ID has already been published " + lookup.NodeID.ToString());
|
||||
return ServiceResult.Create(StatusCodes.BadNodeIdExists, "Node has already been published!");
|
||||
Trace($"DoPublish: Node ID '{lookup.NodeID.ToString()}' is already published!");
|
||||
return ServiceResult.Create(StatusCodes.BadNodeIdExists, $"Node ID '{lookup.NodeID.ToString()}' is already published!");
|
||||
}
|
||||
}
|
||||
|
||||
// subscribe to the node
|
||||
Program.CreateMonitoredItem(lookup);
|
||||
Program.Trace("PublishNodeMethod: Monitored item created.");
|
||||
Trace("DoPublish: Monitored item created.");
|
||||
|
||||
// update our data
|
||||
Program.m_nodesLookups.Add(lookup);
|
||||
|
@ -137,20 +141,15 @@ namespace Publisher
|
|||
}
|
||||
|
||||
//serialize Program.m_nodesLookups to disk
|
||||
string publishedNodesFilePath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "publishednodes.json";
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_PNFP")))
|
||||
{
|
||||
publishedNodesFilePath = Environment.GetEnvironmentVariable("_GW_PNFP");
|
||||
}
|
||||
File.WriteAllText(publishedNodesFilePath, JsonConvert.SerializeObject(Program.m_nodesLookups));
|
||||
File.WriteAllText(Program.PublishedNodesAbsFilename, JsonConvert.SerializeObject(Program.m_nodesLookups));
|
||||
|
||||
Program.Trace("PublishNodeMethod: Successful publish: " + lookup.ToString());
|
||||
Trace($"DoPublish: Now publishing: {lookup.ToString()}");
|
||||
return ServiceResult.Good;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Trace("PublishNodeMethod: Exception: " + ex.ToString());
|
||||
return ServiceResult.Create(ex, StatusCodes.BadUnexpectedError, "Unexpected error publishing node: " + ex.Message);
|
||||
Trace(ex, $"DoPublish: Exception while trying to configure publishing node '{lookup.ToString()}'");
|
||||
return ServiceResult.Create(ex, StatusCodes.BadUnexpectedError, $"Unexpected error publishing node: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,7 +160,7 @@ namespace Publisher
|
|||
{
|
||||
if (inputArguments[0] == null || inputArguments[1] == null)
|
||||
{
|
||||
Program.Trace("UnPublishNodeMethod: Invalid arguments!");
|
||||
Trace("UnPublishNodeMethod: Invalid arguments!");
|
||||
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments!");
|
||||
}
|
||||
|
||||
|
@ -169,19 +168,21 @@ namespace Publisher
|
|||
string uri = inputArguments[1] as string;
|
||||
if (string.IsNullOrEmpty(nodeID) || string.IsNullOrEmpty(uri))
|
||||
{
|
||||
Program.Trace("UnPublishNodeMethod: Arguments are not valid strings!");
|
||||
Trace("UnPublishNodeMethod: Arguments are not valid strings!");
|
||||
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments as strings!");
|
||||
}
|
||||
|
||||
NodeLookup lookup = new NodeLookup();
|
||||
lookup.NodeID = new NodeId(nodeID);
|
||||
NodeLookup lookup = new NodeLookup()
|
||||
{
|
||||
NodeID = new NodeId(nodeID)
|
||||
};
|
||||
try
|
||||
{
|
||||
lookup.EndPointURL = new Uri(uri);
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
Program.Trace("UnPublishNodeMethod: Invalid endpoint URL!");
|
||||
Trace("UnPublishNodeMethod: Invalid endpoint URL!");
|
||||
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!");
|
||||
}
|
||||
|
||||
|
@ -199,7 +200,7 @@ namespace Publisher
|
|||
|
||||
if (matchingSession == null)
|
||||
{
|
||||
Program.Trace("UnPublishNodeMethod: Session for published node not found: " + lookup.EndPointURL.ToString());
|
||||
Trace("UnPublishNodeMethod: Session for published node not found: " + lookup.EndPointURL.ToString());
|
||||
return ServiceResult.Create(StatusCodes.BadSessionIdInvalid, "Session for published node not found!");
|
||||
}
|
||||
|
||||
|
@ -209,7 +210,7 @@ namespace Publisher
|
|||
if (item.StartNodeId == lookup.NodeID)
|
||||
{
|
||||
matchingSession.DefaultSubscription.RemoveItem(item);
|
||||
Program.Trace("UnPublishNodeMethod: Successful unpublish: " + lookup.NodeID.ToString());
|
||||
Trace($"UnPublishNodeMethod: Stopping publishing of '{lookup.NodeID.ToString()}'");
|
||||
|
||||
// update our data on success only
|
||||
// we keep the session to the server, as there may be other nodes still published on it
|
||||
|
@ -217,19 +218,14 @@ namespace Publisher
|
|||
Program.m_nodesLookups.Remove(itemToRemove);
|
||||
|
||||
//serialize Program.m_nodesLookups to disk
|
||||
string publishedNodesFilePath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "publishednodes.json";
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_PNFP")))
|
||||
{
|
||||
publishedNodesFilePath = Environment.GetEnvironmentVariable("_GW_PNFP");
|
||||
}
|
||||
File.WriteAllText(publishedNodesFilePath, JsonConvert.SerializeObject(Program.m_nodesLookups));
|
||||
File.WriteAllText(Program.PublishedNodesAbsFilename, JsonConvert.SerializeObject(Program.m_nodesLookups));
|
||||
|
||||
return ServiceResult.Good;
|
||||
}
|
||||
}
|
||||
|
||||
Program.Trace("UnPublishNodeMethod: Monitored item for node ID not found " + lookup.NodeID.ToString());
|
||||
return ServiceResult.Create(StatusCodes.BadNodeIdInvalid, "Monitored item for node ID not found!");
|
||||
Trace($"UnPublishNodeMethod: Monitored item for NodeID '{lookup.NodeID.ToString()}' not found ");
|
||||
return ServiceResult.Create(StatusCodes.BadNodeIdInvalid, $"Monitored item for NodeID '{lookup.NodeID.ToString()}' not found!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -238,7 +234,7 @@ namespace Publisher
|
|||
private ServiceResult GetListOfPublishedNodesMethod(ISystemContext context, MethodState method, IList<object> inputArguments, IList<object> outputArguments)
|
||||
{
|
||||
outputArguments[0] = JsonConvert.SerializeObject(Program.m_nodesLookups);
|
||||
Program.Trace("GetListOfPublishedNodesMethod: Success!");
|
||||
Trace("GetListOfPublishedNodesMethod: Success!");
|
||||
|
||||
return ServiceResult.Good;
|
||||
}
|
||||
|
@ -251,7 +247,7 @@ namespace Publisher
|
|||
var connectionString = value as string;
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
Program.Trace("ConnectionStringWrite: Invalid Argument!");
|
||||
Trace("ConnectionStringWrite: Invalid Argument!");
|
||||
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments as strings!");
|
||||
}
|
||||
|
||||
|
@ -259,33 +255,33 @@ namespace Publisher
|
|||
timestamp = DateTime.Now;
|
||||
|
||||
// read current connection string and compare to the one passed in
|
||||
string currentConnectionString = SecureIoTHubToken.Read(Program.m_configuration.ApplicationName);
|
||||
string currentConnectionString = SecureIoTHubToken.Read(Program.m_configuration.ApplicationName, Program.IotDeviceCertStoreType, Program.IotDeviceCertStorePath);
|
||||
if (string.Equals(connectionString, currentConnectionString, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Program.Trace("ConnectionStringWrite: Connection string up to date!");
|
||||
Trace("ConnectionStringWrite: Connection string up to date!");
|
||||
return ServiceResult.Create(StatusCodes.Bad, "Connection string already up-to-date!");
|
||||
}
|
||||
|
||||
Program.Trace("Attemping to configure publisher with connection string: " + connectionString);
|
||||
Trace($"ConnectionStringWrite: Attemping to configure publisher with connection string: {connectionString}");
|
||||
|
||||
// configure publisher and write connection string
|
||||
try
|
||||
{
|
||||
DeviceClient newClient = DeviceClient.CreateFromConnectionString(connectionString, Microsoft.Azure.Devices.Client.TransportType.Mqtt);
|
||||
DeviceClient newClient = DeviceClient.CreateFromConnectionString(connectionString, Program.IotHubProtocol);
|
||||
newClient.RetryPolicy = RetryPolicyType.Exponential_Backoff_With_Jitter;
|
||||
newClient.OpenAsync().Wait();
|
||||
SecureIoTHubToken.Write(Program.m_configuration.ApplicationName, connectionString);
|
||||
SecureIoTHubToken.Write(Program.m_configuration.ApplicationName, connectionString, Program.IotDeviceCertStoreType, Program.IotDeviceCertStorePath);
|
||||
Program.m_deviceClient = newClient;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
statusCode = StatusCodes.Bad;
|
||||
Program.Trace("ConnectionStringWrite: Exception: " + ex.ToString());
|
||||
Trace(ex, $"ConnectionStringWrite: Exception while trying to create IoTHub client and store device connection string in cert store");
|
||||
return ServiceResult.Create(StatusCodes.Bad, "Publisher registration failed: " + ex.Message);
|
||||
}
|
||||
|
||||
statusCode = StatusCodes.Good;
|
||||
Program.Trace("ConnectionStringWrite: Success!");
|
||||
Trace("ConnectionStringWrite: Success!");
|
||||
|
||||
return statusCode;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Opc.Ua;
|
||||
using Org.BouncyCastle.Asn1.X509;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Generators;
|
||||
|
@ -14,48 +10,99 @@ using Org.BouncyCastle.Pkcs;
|
|||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.Utilities;
|
||||
using Org.BouncyCastle.X509;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
|
||||
namespace IoTHubCredentialTools
|
||||
{
|
||||
public class SecureIoTHubToken
|
||||
{
|
||||
public static string Read(string name)
|
||||
public static string Read(string name, string storeType, string storePath)
|
||||
{
|
||||
// load an existing key from a no-expired cert with the subject name passed in from the OS-provided X509Store
|
||||
using (X509Store store = new X509Store("IoTHub", StoreLocation.CurrentUser))
|
||||
// handle each store type differently
|
||||
switch (storeType)
|
||||
{
|
||||
store.Open(OpenFlags.ReadOnly);
|
||||
foreach (X509Certificate2 cert in store.Certificates)
|
||||
{
|
||||
if ((cert.SubjectName.Decode(X500DistinguishedNameFlags.None | X500DistinguishedNameFlags.DoNotUseQuotes).Equals("CN=" + name, StringComparison.OrdinalIgnoreCase)) &&
|
||||
(DateTime.Now < cert.NotAfter))
|
||||
case CertificateStoreType.Directory:
|
||||
{
|
||||
using (RSA rsa = cert.GetRSAPrivateKey())
|
||||
// load an existing key from a no-expired cert with the subject name passed in from the OS-provided X509Store
|
||||
using (DirectoryCertificateStore store = new DirectoryCertificateStore())
|
||||
{
|
||||
if (rsa != null)
|
||||
store.Open(storePath);
|
||||
X509CertificateCollection certificates = store.Enumerate().Result;
|
||||
|
||||
foreach (X509Certificate2 cert in certificates)
|
||||
{
|
||||
foreach (System.Security.Cryptography.X509Certificates.X509Extension extension in cert.Extensions)
|
||||
if ((cert.SubjectName.Decode(X500DistinguishedNameFlags.None | X500DistinguishedNameFlags.DoNotUseQuotes).Equals("CN=" + name, StringComparison.OrdinalIgnoreCase)) &&
|
||||
(DateTime.Now < cert.NotAfter))
|
||||
{
|
||||
// check for instruction code extension
|
||||
if ((extension.Oid.Value == "2.5.29.23") && (extension.RawData.Length >= 4))
|
||||
using (RSA rsa = cert.GetRSAPrivateKey())
|
||||
{
|
||||
byte[] bytes = new byte[extension.RawData.Length - 4];
|
||||
Array.Copy(extension.RawData, 4, bytes, 0, bytes.Length);
|
||||
byte[] token = rsa.Decrypt(bytes, RSAEncryptionPadding.OaepSHA1);
|
||||
return Encoding.ASCII.GetString(token);
|
||||
if (rsa != null)
|
||||
{
|
||||
foreach (System.Security.Cryptography.X509Certificates.X509Extension extension in cert.Extensions)
|
||||
{
|
||||
// check for instruction code extension
|
||||
if ((extension.Oid.Value == "2.5.29.23") && (extension.RawData.Length >= 4))
|
||||
{
|
||||
byte[] bytes = new byte[extension.RawData.Length - 4];
|
||||
Array.Copy(extension.RawData, 4, bytes, 0, bytes.Length);
|
||||
byte[] token = rsa.Decrypt(bytes, RSAEncryptionPadding.OaepSHA1);
|
||||
return Encoding.ASCII.GetString(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CertificateStoreType.X509Store:
|
||||
{
|
||||
// load an existing key from a no-expired cert with the subject name passed in from the OS-provided X509Store
|
||||
using (X509Store store = new X509Store(storePath, StoreLocation.CurrentUser))
|
||||
{
|
||||
store.Open(OpenFlags.ReadOnly);
|
||||
foreach (X509Certificate2 cert in store.Certificates)
|
||||
{
|
||||
if ((cert.SubjectName.Decode(X500DistinguishedNameFlags.None | X500DistinguishedNameFlags.DoNotUseQuotes).Equals("CN=" + name, StringComparison.OrdinalIgnoreCase)) &&
|
||||
(DateTime.Now < cert.NotAfter))
|
||||
{
|
||||
using (RSA rsa = cert.GetRSAPrivateKey())
|
||||
{
|
||||
if (rsa != null)
|
||||
{
|
||||
foreach (System.Security.Cryptography.X509Certificates.X509Extension extension in cert.Extensions)
|
||||
{
|
||||
// check for instruction code extension
|
||||
if ((extension.Oid.Value == "2.5.29.23") && (extension.RawData.Length >= 4))
|
||||
{
|
||||
byte[] bytes = new byte[extension.RawData.Length - 4];
|
||||
Array.Copy(extension.RawData, 4, bytes, 0, bytes.Length);
|
||||
byte[] token = rsa.Decrypt(bytes, RSAEncryptionPadding.OaepSHA1);
|
||||
return Encoding.ASCII.GetString(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new Exception($"The requested store type '{storeType}' is not supported. Please change.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void Write(string name, string connectionString)
|
||||
public static void Write(string name, string connectionString, string storeType, string storePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
|
@ -110,7 +157,7 @@ namespace IoTHubCredentialTools
|
|||
}
|
||||
}
|
||||
rsa.Dispose();
|
||||
|
||||
|
||||
// sign the cert with the private key
|
||||
ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA256WITHRSA", keys.Private, random);
|
||||
Org.BouncyCastle.X509.X509Certificate x509 = cg.Generate(signatureFactory);
|
||||
|
@ -129,23 +176,58 @@ namespace IoTHubCredentialTools
|
|||
// create X509Certificate2 object from PKCS12 file
|
||||
certificate = CreateCertificateFromPKCS12(pfxData.ToArray(), passcode);
|
||||
|
||||
// Add to X509Store
|
||||
using (X509Store store = new X509Store("IoTHub", StoreLocation.CurrentUser))
|
||||
// handle each store type differently
|
||||
switch (storeType)
|
||||
{
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
|
||||
// remove any existing cert with our name from the store
|
||||
foreach (X509Certificate2 cert in store.Certificates)
|
||||
{
|
||||
if (cert.SubjectName.Decode(X500DistinguishedNameFlags.None | X500DistinguishedNameFlags.DoNotUseQuotes).Equals("CN=" + name, StringComparison.OrdinalIgnoreCase))
|
||||
case CertificateStoreType.Directory:
|
||||
{
|
||||
store.Remove(cert);
|
||||
}
|
||||
}
|
||||
// Add to DirectoryStore
|
||||
using (DirectoryCertificateStore store = new DirectoryCertificateStore())
|
||||
{
|
||||
store.Open(storePath);
|
||||
X509CertificateCollection certificates = store.Enumerate().Result;
|
||||
|
||||
// add new one
|
||||
store.Add(certificate);
|
||||
// remove any existing cert with our name from the store
|
||||
foreach (X509Certificate2 cert in certificates)
|
||||
{
|
||||
if (cert.SubjectName.Decode(X500DistinguishedNameFlags.None | X500DistinguishedNameFlags.DoNotUseQuotes).Equals("CN=" + name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
store.Delete(cert.Thumbprint);
|
||||
}
|
||||
}
|
||||
|
||||
// add new one
|
||||
store.Add(certificate);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CertificateStoreType.X509Store:
|
||||
{
|
||||
// Add to X509Store
|
||||
using (X509Store store = new X509Store("IoTHub", StoreLocation.CurrentUser))
|
||||
{
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
|
||||
// remove any existing cert with our name from the store
|
||||
foreach (X509Certificate2 cert in store.Certificates)
|
||||
{
|
||||
if (cert.SubjectName.Decode(X500DistinguishedNameFlags.None | X500DistinguishedNameFlags.DoNotUseQuotes).Equals("CN=" + name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
store.Remove(cert);
|
||||
}
|
||||
}
|
||||
|
||||
// add new one
|
||||
store.Add(certificate);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new Exception($"The requested store type '{storeType}' is not supported. Please change.");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
using System;
|
||||
using static System.Console;
|
||||
|
||||
namespace Opc.Ua.Workarounds
|
||||
{
|
||||
public static class TraceWorkaround
|
||||
{
|
||||
/// <summary>
|
||||
/// Trace message helper
|
||||
/// </summary>
|
||||
public static void Trace(string message, params object[] args)
|
||||
{
|
||||
Utils.Trace(Utils.TraceMasks.Error, message, args);
|
||||
WriteLine(DateTime.Now.ToString() + ": " + message, args);
|
||||
}
|
||||
|
||||
public static void Trace(int traceMask, string format, params object[] args)
|
||||
{
|
||||
Utils.Trace(traceMask, format, args);
|
||||
WriteLine(DateTime.Now.ToString() + ": " + format, args);
|
||||
}
|
||||
|
||||
public static void Trace(Exception e, string format, params object[] args)
|
||||
{
|
||||
Utils.Trace(e, format, args);
|
||||
WriteLine(DateTime.Now.ToString() + ": " + e.Message.ToString());
|
||||
WriteLine(DateTime.Now.ToString() + ": " + format, args);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче