Address review comments.
This commit is contained in:
Родитель
26147c856b
Коммит
880f0dea00
|
@ -34,6 +34,7 @@ namespace Opc.Ua.Publisher
|
||||||
private ConcurrentQueue<string> _sendQueue;
|
private ConcurrentQueue<string> _sendQueue;
|
||||||
private int _currentSizeOfIotHubMessageBytes;
|
private int _currentSizeOfIotHubMessageBytes;
|
||||||
private List<OpcUaMessage> _messageList;
|
private List<OpcUaMessage> _messageList;
|
||||||
|
private SemaphoreSlim _messageListSemaphore;
|
||||||
private uint _maxSizeOfIoTHubMessageBytes;
|
private uint _maxSizeOfIoTHubMessageBytes;
|
||||||
private int _defaultSendIntervalSeconds;
|
private int _defaultSendIntervalSeconds;
|
||||||
private CancellationTokenSource _tokenSource;
|
private CancellationTokenSource _tokenSource;
|
||||||
|
@ -47,6 +48,7 @@ namespace Opc.Ua.Publisher
|
||||||
_sendQueue = new ConcurrentQueue<string>();
|
_sendQueue = new ConcurrentQueue<string>();
|
||||||
_sendQueueEvent = new AutoResetEvent(false);
|
_sendQueueEvent = new AutoResetEvent(false);
|
||||||
_messageList = new List<OpcUaMessage>();
|
_messageList = new List<OpcUaMessage>();
|
||||||
|
_messageListSemaphore = new SemaphoreSlim(1);
|
||||||
_currentSizeOfIotHubMessageBytes = 0;
|
_currentSizeOfIotHubMessageBytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +143,7 @@ namespace Opc.Ua.Publisher
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateConnectionString(string iotHubOwnerConnectionString)
|
public void ConnectionStringWrite(string iotHubOwnerConnectionString)
|
||||||
{
|
{
|
||||||
DeviceClient newClient = DeviceClient.CreateFromConnectionString(iotHubOwnerConnectionString, IotHubProtocol);
|
DeviceClient newClient = DeviceClient.CreateFromConnectionString(iotHubOwnerConnectionString, IotHubProtocol);
|
||||||
newClient.RetryPolicy = RetryPolicyType.Exponential_Backoff_With_Jitter;
|
newClient.RetryPolicy = RetryPolicyType.Exponential_Backoff_With_Jitter;
|
||||||
|
@ -252,7 +254,9 @@ namespace Opc.Ua.Publisher
|
||||||
if (isDequeueSuccessful)
|
if (isDequeueSuccessful)
|
||||||
{
|
{
|
||||||
OpcUaMessage msgPayload = JsonConvert.DeserializeObject<OpcUaMessage>(messageInJson);
|
OpcUaMessage msgPayload = JsonConvert.DeserializeObject<OpcUaMessage>(messageInJson);
|
||||||
|
_messageListSemaphore.Wait();
|
||||||
_messageList.Add(msgPayload);
|
_messageList.Add(msgPayload);
|
||||||
|
_messageListSemaphore.Release();
|
||||||
_currentSizeOfIotHubMessageBytes = _currentSizeOfIotHubMessageBytes + nextMessageSizeBytes;
|
_currentSizeOfIotHubMessageBytes = _currentSizeOfIotHubMessageBytes + nextMessageSizeBytes;
|
||||||
Trace(Utils.TraceMasks.OperationDetail, $"Added new message with size {nextMessageSizeBytes} to IoTHub message (size is now {_currentSizeOfIotHubMessageBytes}). {_sendQueue.Count} message(s) in send queue.");
|
Trace(Utils.TraceMasks.OperationDetail, $"Added new message with size {nextMessageSizeBytes} to IoTHub message (size is now {_currentSizeOfIotHubMessageBytes}). {_sendQueue.Count} message(s) in send queue.");
|
||||||
|
|
||||||
|
@ -283,9 +287,13 @@ namespace Opc.Ua.Publisher
|
||||||
{
|
{
|
||||||
if (_messageList.Count > 0)
|
if (_messageList.Count > 0)
|
||||||
{
|
{
|
||||||
|
// process all queued messages
|
||||||
|
_messageListSemaphore.Wait();
|
||||||
string msgListInJson = JsonConvert.SerializeObject(_messageList);
|
string msgListInJson = JsonConvert.SerializeObject(_messageList);
|
||||||
|
|
||||||
var encodedMessage = new Microsoft.Azure.Devices.Client.Message(Encoding.UTF8.GetBytes(msgListInJson));
|
var encodedMessage = new Microsoft.Azure.Devices.Client.Message(Encoding.UTF8.GetBytes(msgListInJson));
|
||||||
|
_currentSizeOfIotHubMessageBytes = 0;
|
||||||
|
_messageList.Clear();
|
||||||
|
_messageListSemaphore.Release();
|
||||||
|
|
||||||
// publish
|
// publish
|
||||||
encodedMessage.Properties.Add("content-type", "application/opcua+uajson");
|
encodedMessage.Properties.Add("content-type", "application/opcua+uajson");
|
||||||
|
@ -300,17 +308,13 @@ namespace Opc.Ua.Publisher
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Trace("No IoTHub client available ");
|
Trace("No IoTHub client available. Dropping messages...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Trace(e, "Exception while sending message to IoTHub. Dropping...");
|
Trace(e, "Exception while sending message to IoTHub. Dropping messages...");
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset IoTHub message size
|
|
||||||
_currentSizeOfIotHubMessageBytes = 0;
|
|
||||||
_messageList.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart timer
|
// Restart timer
|
||||||
|
|
|
@ -27,13 +27,11 @@ namespace Opc.Ua.Publisher
|
||||||
Configuration.ClientConfiguration = new ClientConfiguration();
|
Configuration.ClientConfiguration = new ClientConfiguration();
|
||||||
Configuration.ServerConfiguration = new ServerConfiguration();
|
Configuration.ServerConfiguration = new ServerConfiguration();
|
||||||
|
|
||||||
// enable logging and enforce information flag
|
// initialize stack tracing
|
||||||
Program.OpcStackTraceMask |= Utils.TraceMasks.Information;
|
|
||||||
Configuration.TraceConfiguration = new TraceConfiguration()
|
Configuration.TraceConfiguration = new TraceConfiguration()
|
||||||
{
|
{
|
||||||
TraceMasks = Program.OpcStackTraceMask
|
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);
|
Utils.SetTraceOutput(Utils.TraceOutput.FileOnly);
|
||||||
if (string.IsNullOrEmpty(Program.LogFileName))
|
if (string.IsNullOrEmpty(Program.LogFileName))
|
||||||
{
|
{
|
||||||
|
|
|
@ -143,7 +143,7 @@ namespace Opc.Ua.Publisher
|
||||||
_namespaceTable = new NamespaceTable();
|
_namespaceTable = new NamespaceTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ConnectAndOrMonitor()
|
public async Task ConnectAndMonitor()
|
||||||
{
|
{
|
||||||
_opcSessionSemaphore.Wait();
|
_opcSessionSemaphore.Wait();
|
||||||
Trace($"Connect and monitor session and nodes on endpoint '{EndpointUri.AbsoluteUri}'.");
|
Trace($"Connect and monitor session and nodes on endpoint '{EndpointUri.AbsoluteUri}'.");
|
||||||
|
|
|
@ -31,6 +31,7 @@ namespace Opc.Ua.Publisher
|
||||||
public static string NodesToPublishAbsFilenameDefault = $"{System.IO.Directory.GetCurrentDirectory()}{Path.DirectorySeparatorChar}publishednodes.json";
|
public static string NodesToPublishAbsFilenameDefault = $"{System.IO.Directory.GetCurrentDirectory()}{Path.DirectorySeparatorChar}publishednodes.json";
|
||||||
public static string NodesToPublishAbsFilename { get; set; }
|
public static string NodesToPublishAbsFilename { get; set; }
|
||||||
public static string ShopfloorDomain { get; set; }
|
public static string ShopfloorDomain { get; set; }
|
||||||
|
public static bool VerboseConsole { get; set; }
|
||||||
|
|
||||||
//
|
//
|
||||||
// IoTHub related
|
// IoTHub related
|
||||||
|
@ -58,8 +59,8 @@ namespace Opc.Ua.Publisher
|
||||||
public static int OpcOperationTimeout = 120000;
|
public static int OpcOperationTimeout = 120000;
|
||||||
public static bool TrustMyself = true;
|
public static bool TrustMyself = true;
|
||||||
// Enable Utils.TraceMasks.OperationDetail to get output for IoTHub telemetry operations. Current: 0x287 (647), with OperationDetail: 0x2C7 (711)
|
// Enable Utils.TraceMasks.OperationDetail to get output for IoTHub telemetry operations. Current: 0x287 (647), with OperationDetail: 0x2C7 (711)
|
||||||
public static int OpcStackTraceMask = Utils.TraceMasks.Error | Utils.TraceMasks.Security | Utils.TraceMasks.StackTrace | Utils.TraceMasks.StartStop | Utils.TraceMasks.Information;
|
public static int OpcStackTraceMask = Utils.TraceMasks.Error | Utils.TraceMasks.Security | Utils.TraceMasks.StackTrace | Utils.TraceMasks.StartStop;
|
||||||
public static bool OpcPublisherAutoAccept = false;
|
public static bool OpcPublisherAutoTrustServerCerts = false;
|
||||||
public static uint OpcSessionCreationTimeout = 10;
|
public static uint OpcSessionCreationTimeout = 10;
|
||||||
public static uint OpcSessionCreationBackoffMax = 5;
|
public static uint OpcSessionCreationBackoffMax = 5;
|
||||||
public static uint OpcKeepAliveDisconnectThreshold = 10;
|
public static uint OpcKeepAliveDisconnectThreshold = 10;
|
||||||
|
@ -132,12 +133,12 @@ namespace Opc.Ua.Publisher
|
||||||
{
|
{
|
||||||
var shouldShowHelp = false;
|
var shouldShowHelp = false;
|
||||||
|
|
||||||
// these are the available options, not that they set the variables
|
// command line options configuration
|
||||||
Mono.Options.OptionSet options = new Mono.Options.OptionSet {
|
Mono.Options.OptionSet options = new Mono.Options.OptionSet {
|
||||||
// Publishing configuration options
|
// Publishing configuration options
|
||||||
{ "pf|publishfile=", $"the filename to configure the nodes to publish.\nDefault: '{NodesToPublishAbsFilenameDefault}'", (string p) => NodesToPublishAbsFilename = p },
|
{ "pf|publishfile=", $"the filename to configure the nodes to publish.\nDefault: '{NodesToPublishAbsFilenameDefault}'", (string p) => NodesToPublishAbsFilename = p },
|
||||||
{ "sd|shopfloordomain=", $"the domain of the shopfloor. if specified this domain is appended (delimited by a ':' to the 'ApplicationURI' property when telemetry is ingested to IoTHub.\n" +
|
{ "sd|shopfloordomain=", $"the domain of the shopfloor. if specified this domain is appended (delimited by a ':' to the 'ApplicationURI' property when telemetry is ingested to IoTHub.\n" +
|
||||||
"The value must follw the syntactical rules of a DNS hostname.\nDefault: not set", (string s) => {
|
"The value must follow the syntactical rules of a DNS hostname.\nDefault: not set", (string s) => {
|
||||||
Regex domainNameRegex = new Regex("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$");
|
Regex domainNameRegex = new Regex("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$");
|
||||||
if (domainNameRegex.IsMatch(s))
|
if (domainNameRegex.IsMatch(s))
|
||||||
{
|
{
|
||||||
|
@ -149,7 +150,7 @@ namespace Opc.Ua.Publisher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ "sw|sessionconnectwait=", $"specify the wait time in seconds publisher is trying to connect to disconnected endpoints\nMin: 10\nDefault: {PublisherSessionConnectWaitSec}", (int i) => {
|
{ "sw|sessionconnectwait=", $"specify the wait time in seconds publisher is trying to connect to disconnected endpoints and starts monitoring unmonitored items\nMin: 10\nDefault: {PublisherSessionConnectWaitSec}", (int i) => {
|
||||||
if (i > 10)
|
if (i > 10)
|
||||||
{
|
{
|
||||||
PublisherSessionConnectWaitSec = i;
|
PublisherSessionConnectWaitSec = i;
|
||||||
|
@ -160,6 +161,7 @@ namespace Opc.Ua.Publisher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{ "vc|verboseconsole=", $"the output of publisher is shown on the console.\nDefault: {VerboseConsole}", (bool b) => VerboseConsole = b },
|
||||||
|
|
||||||
// IoTHub specific options
|
// 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)}",
|
{ "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)}",
|
||||||
|
@ -214,7 +216,8 @@ namespace Opc.Ua.Publisher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ "ds|defaultsamplingrate=", $"the sampling rate in millisecond for which monitored nodes should be queried.\nMin: 100\nDefault: {OpcSamplingRateMillisec}", (int i) => {
|
{ "ds|defaultsamplingrate=", $"the sampling interval in milliseconds, the OPC UA servers should use to sample the values of the nodes to publish.\n" +
|
||||||
|
$"Please check the OPC UA spec for more details.\nMin: 100\nDefault: {OpcSamplingRateMillisec}", (int i) => {
|
||||||
if (i >= 100)
|
if (i >= 100)
|
||||||
{
|
{
|
||||||
OpcSamplingRateMillisec = i;
|
OpcSamplingRateMillisec = i;
|
||||||
|
@ -258,7 +261,7 @@ namespace Opc.Ua.Publisher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ "st|opcstacktracemask=", $"the trace mask for the OPC stack. See github OPC .NET stack for definitions.\nTo enable IoTHub telemetry tracing set it to 711. Information mask 0x2 is enforced.\nDefault: {OpcStackTraceMask:X} ({Program.OpcStackTraceMask})", (int i) => {
|
{ "st|opcstacktracemask=", $"the trace mask for the OPC stack. See github OPC .NET stack for definitions.\nTo enable IoTHub telemetry tracing set it to 711.\nDefault: {OpcStackTraceMask:X} ({Program.OpcStackTraceMask})", (int i) => {
|
||||||
if (i >= 0)
|
if (i >= 0)
|
||||||
{
|
{
|
||||||
OpcStackTraceMask = i;
|
OpcStackTraceMask = i;
|
||||||
|
@ -269,7 +272,7 @@ namespace Opc.Ua.Publisher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ "aa|autoaccept=", $"the publisher accept all servers it is connecting to.\nDefault: {OpcPublisherAutoAccept}", (bool b) => OpcPublisherAutoAccept = b },
|
{ "as|autotrustservercerts=", $"the publisher trusts all servers it is establishing a connection to.\nDefault: {OpcPublisherAutoTrustServerCerts}", (bool b) => OpcPublisherAutoTrustServerCerts = b },
|
||||||
|
|
||||||
// trust own public cert option
|
// trust own public cert option
|
||||||
{ "tm|trustmyself=", $"the publisher certificate is put into the trusted certificate store automatically.\nDefault: {TrustMyself}", (bool b) => TrustMyself = b },
|
{ "tm|trustmyself=", $"the publisher certificate is put into the trusted certificate store automatically.\nDefault: {TrustMyself}", (bool b) => TrustMyself = b },
|
||||||
|
@ -418,14 +421,14 @@ namespace Opc.Ua.Publisher
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set certificate validator.
|
// Set certificate validator.
|
||||||
if (OpcPublisherAutoAccept)
|
if (OpcPublisherAutoTrustServerCerts)
|
||||||
{
|
{
|
||||||
Trace("Publisher configured to auto trust server certificates.");
|
Trace("Publisher configured to auto trust server certificates of the servers it is connecting to.");
|
||||||
OpcConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_AutoAccept);
|
OpcConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_AutoTrustServerCerts);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Trace("Publisher configured to not auto trust server certificates, but use certificate stores.");
|
Trace("Publisher configured to not auto trust server certificates. When connecting to servers, you need to manually copy the rejected server certs to the trusted store to trust them.");
|
||||||
OpcConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_Default);
|
OpcConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -542,11 +545,11 @@ namespace Opc.Ua.Publisher
|
||||||
Task.Run( async () => await SessionConnector(cts.Token));
|
Task.Run( async () => await SessionConnector(cts.Token));
|
||||||
|
|
||||||
// stop on user request
|
// stop on user request
|
||||||
Trace("");
|
WriteLine("");
|
||||||
Trace("");
|
WriteLine("");
|
||||||
Trace("Publisher is running. Press ENTER to quit.");
|
WriteLine("Publisher is running. Press ENTER to quit.");
|
||||||
Trace("");
|
WriteLine("");
|
||||||
Trace("");
|
WriteLine("");
|
||||||
ReadLine();
|
ReadLine();
|
||||||
cts.Cancel();
|
cts.Cancel();
|
||||||
|
|
||||||
|
@ -595,7 +598,7 @@ namespace Opc.Ua.Publisher
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// get tasks for all disconnected sessions and start them
|
// get tasks for all disconnected sessions and start them
|
||||||
var singleSessionHandlerTaskList = OpcSessions.Where(p => p.State == OpcSession.SessionState.Disconnected).Select(s => s.ConnectAndOrMonitor());
|
var singleSessionHandlerTaskList = OpcSessions.Where(p => p.State == OpcSession.SessionState.Disconnected).Select(s => s.ConnectAndMonitor());
|
||||||
await Task.WhenAll(singleSessionHandlerTaskList);
|
await Task.WhenAll(singleSessionHandlerTaskList);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@ -643,13 +646,13 @@ namespace Opc.Ua.Publisher
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default certificate validation callback
|
/// Auto trust server certificate validation callback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void CertificateValidator_AutoAccept(CertificateValidator validator, CertificateValidationEventArgs e)
|
private static void CertificateValidator_AutoTrustServerCerts(CertificateValidator validator, CertificateValidationEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted)
|
if (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted)
|
||||||
{
|
{
|
||||||
Trace($"Certificate '{e.Certificate.Subject}' will be auto accepted.");
|
Trace($"Certificate '{e.Certificate.Subject}' will be trusted, since the autotrustservercerts options was specified.");
|
||||||
e.Accept = true;
|
e.Accept = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,8 +83,8 @@ namespace Publisher
|
||||||
opcSession.AddNodeForMonitoring(nodeToPublish.NodeId);
|
opcSession.AddNodeForMonitoring(nodeToPublish.NodeId);
|
||||||
Trace("DoPublish: Requested to monitor item.");
|
Trace("DoPublish: Requested to monitor item.");
|
||||||
|
|
||||||
// do whatever is needed.
|
// start monitoring the node
|
||||||
Task monitorTask = Task.Run(async () => await opcSession.ConnectAndOrMonitor());
|
Task monitorTask = Task.Run(async () => await opcSession.ConnectAndMonitor());
|
||||||
monitorTask.Wait();
|
monitorTask.Wait();
|
||||||
Trace("DoPublish: Session processing completed.");
|
Trace("DoPublish: Session processing completed.");
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ namespace Publisher
|
||||||
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!");
|
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the session and stop monitoring the item.
|
// Find the session and stop monitoring the node.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// find the session we need to monitor the node
|
// find the session we need to monitor the node
|
||||||
|
@ -154,8 +154,8 @@ namespace Publisher
|
||||||
opcSession.TagNodeForMonitoringStop(nodeToUnpublish.NodeId);
|
opcSession.TagNodeForMonitoringStop(nodeToUnpublish.NodeId);
|
||||||
Trace("UnPublishNodeMethod: Requested to stop monitoring of node.");
|
Trace("UnPublishNodeMethod: Requested to stop monitoring of node.");
|
||||||
|
|
||||||
// do whatever is needed.
|
// stop monitoring the node
|
||||||
Task monitorTask = Task.Run(async () => await opcSession.ConnectAndOrMonitor());
|
Task monitorTask = Task.Run(async () => await opcSession.ConnectAndMonitor());
|
||||||
monitorTask.Wait();
|
monitorTask.Wait();
|
||||||
Trace("UnPublishNodeMethod: Session processing completed.");
|
Trace("UnPublishNodeMethod: Session processing completed.");
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ namespace Publisher
|
||||||
// configure publisher and write connection string
|
// configure publisher and write connection string
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IotHubMessaging.UpdateConnectionString(connectionString);
|
IotHubMessaging.ConnectionStringWrite(connectionString);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,14 +21,42 @@ namespace IoTHubCredentialTools
|
||||||
{
|
{
|
||||||
public class SecureIoTHubToken
|
public class SecureIoTHubToken
|
||||||
{
|
{
|
||||||
|
private static string CheckForToken(X509Certificate2 cert, string name)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static string Read(string name, string storeType, string storePath)
|
public static string Read(string name, string storeType, string storePath)
|
||||||
{
|
{
|
||||||
|
string token = null;
|
||||||
|
|
||||||
// handle each store type differently
|
// handle each store type differently
|
||||||
switch (storeType)
|
switch (storeType)
|
||||||
{
|
{
|
||||||
case CertificateStoreType.Directory:
|
case CertificateStoreType.Directory:
|
||||||
{
|
{
|
||||||
// load an existing key from a no-expired cert with the subject name passed in from the OS-provided X509Store
|
// search a non expired cert with the given subject in the directory cert store and return the token
|
||||||
using (DirectoryCertificateStore store = new DirectoryCertificateStore())
|
using (DirectoryCertificateStore store = new DirectoryCertificateStore())
|
||||||
{
|
{
|
||||||
store.Open(storePath);
|
store.Open(storePath);
|
||||||
|
@ -36,64 +64,32 @@ namespace IoTHubCredentialTools
|
||||||
|
|
||||||
foreach (X509Certificate2 cert in certificates)
|
foreach (X509Certificate2 cert in certificates)
|
||||||
{
|
{
|
||||||
if ((cert.SubjectName.Decode(X500DistinguishedNameFlags.None | X500DistinguishedNameFlags.DoNotUseQuotes).Equals("CN=" + name, StringComparison.OrdinalIgnoreCase)) &&
|
if ((token = CheckForToken(cert, name)) != null)
|
||||||
(DateTime.Now < cert.NotAfter))
|
|
||||||
{
|
{
|
||||||
using (RSA rsa = cert.GetRSAPrivateKey())
|
return 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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case CertificateStoreType.X509Store:
|
case CertificateStoreType.X509Store:
|
||||||
{
|
{
|
||||||
// load an existing key from a no-expired cert with the subject name passed in from the OS-provided X509Store
|
// search a non expired cert with the given subject in the X509 cert store and return the token
|
||||||
using (X509Store store = new X509Store(storePath, StoreLocation.CurrentUser))
|
using (X509Store store = new X509Store(storePath, StoreLocation.CurrentUser))
|
||||||
{
|
{
|
||||||
store.Open(OpenFlags.ReadOnly);
|
store.Open(OpenFlags.ReadOnly);
|
||||||
foreach (X509Certificate2 cert in store.Certificates)
|
foreach (X509Certificate2 cert in store.Certificates)
|
||||||
{
|
{
|
||||||
if ((cert.SubjectName.Decode(X500DistinguishedNameFlags.None | X500DistinguishedNameFlags.DoNotUseQuotes).Equals("CN=" + name, StringComparison.OrdinalIgnoreCase)) &&
|
if ((token = CheckForToken(cert, name)) != null)
|
||||||
(DateTime.Now < cert.NotAfter))
|
|
||||||
{
|
{
|
||||||
using (RSA rsa = cert.GetRSAPrivateKey())
|
return 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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
throw new Exception($"The requested store type '{storeType}' is not supported. Please change.");
|
throw new Exception($"The requested store type '{storeType}' is not supported. Please change.");
|
||||||
|
|
|
@ -13,14 +13,17 @@ namespace Opc.Ua.Workarounds
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Trace(string message, params object[] args)
|
public static void Trace(string message, params object[] args)
|
||||||
{
|
{
|
||||||
Utils.Trace(Utils.TraceMasks.Information, message, args);
|
Utils.Trace(Utils.TraceMasks.Error, message, args);
|
||||||
WriteLine(DateTime.Now.ToString() + ": " + message, args);
|
if (VerboseConsole)
|
||||||
|
{
|
||||||
|
WriteLine(DateTime.Now.ToString() + ": " + message, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Trace(int traceMask, string format, params object[] args)
|
public static void Trace(int traceMask, string format, params object[] args)
|
||||||
{
|
{
|
||||||
Utils.Trace(traceMask, format, args);
|
Utils.Trace(traceMask, format, args);
|
||||||
if ((OpcStackTraceMask & traceMask) != 0)
|
if (VerboseConsole && (OpcStackTraceMask & traceMask) != 0)
|
||||||
{
|
{
|
||||||
WriteLine(DateTime.Now.ToString() + ": " + format, args);
|
WriteLine(DateTime.Now.ToString() + ": " + format, args);
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче