From 4bee42cef678e297e00c971fdcfa6539aff621bd Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Wed, 23 Oct 2024 16:57:42 +0200 Subject: [PATCH 01/13] ValidateRolePermissions for MIs montioring the Value of a Node --- Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs b/Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs index 640052e78..6c4676904 100644 --- a/Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs +++ b/Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs @@ -294,6 +294,15 @@ public void OnMonitoredNodeChanged(ISystemContext context, NodeState node, NodeS if (monitoredItem.AttributeId == Attributes.Value && (changes & NodeStateChangeMasks.Value) != 0) { + // validate if the monitored item has the required role permissions to read the value + ServiceResult validationResult = NodeManager.ValidateRolePermissions(new OperationContext(monitoredItem), node.NodeId, PermissionType.Read); + + if (ServiceResult.IsBad(validationResult)) + { + // skip reading the value MonitoredItem without permissions + continue; + } + QueueValue(context, node, monitoredItem); continue; } From dfa4c9a122e84c13cfd1f6793f54db592f430547 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Wed, 23 Oct 2024 17:47:11 +0200 Subject: [PATCH 02/13] fix typo --- Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs b/Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs index 6c4676904..53b451107 100644 --- a/Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs +++ b/Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs @@ -299,7 +299,7 @@ public void OnMonitoredNodeChanged(ISystemContext context, NodeState node, NodeS if (ServiceResult.IsBad(validationResult)) { - // skip reading the value MonitoredItem without permissions + // skip if the monitored item does not have permission to read continue; } From 16b9aeff4d23efc01f7a80cfbd8ae0004d7dad3b Mon Sep 17 00:00:00 2001 From: romanett Date: Thu, 24 Oct 2024 05:49:26 +0200 Subject: [PATCH 03/13] Add ReturnDiagnostics to Session Constructor (#2810) returndiagnostics was not propagated to recreated and reconnected sessions --- Libraries/Opc.Ua.Client/Session/Session.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/Opc.Ua.Client/Session/Session.cs b/Libraries/Opc.Ua.Client/Session/Session.cs index 3de3e6573..caf4cda25 100644 --- a/Libraries/Opc.Ua.Client/Session/Session.cs +++ b/Libraries/Opc.Ua.Client/Session/Session.cs @@ -136,6 +136,7 @@ public Session(ITransportChannel channel, Session template, bool copyEventHandle m_keepAliveInterval = template.KeepAliveInterval; m_checkDomain = template.m_checkDomain; m_continuationPointPolicy = template.m_continuationPointPolicy; + ReturnDiagnostics = template.ReturnDiagnostics; if (template.OperationTimeout > 0) { OperationTimeout = template.OperationTimeout; From d60adc197a522bde36d7f81f83639472d5cd303e Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Thu, 24 Oct 2024 12:55:40 +0200 Subject: [PATCH 04/13] IOP: Fix FetchOperationLimits for some use cases (#2807) * an exception in FetchOperationLimits may have skipped applying the configured operation limits on the server --- Libraries/Opc.Ua.Client/Session/Session.cs | 16 ++++++++-------- .../Opc.Ua.Client/Session/SessionAsync.cs | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session/Session.cs b/Libraries/Opc.Ua.Client/Session/Session.cs index caf4cda25..e9f7052bf 100644 --- a/Libraries/Opc.Ua.Client/Session/Session.cs +++ b/Libraries/Opc.Ua.Client/Session/Session.cs @@ -1687,19 +1687,19 @@ public void FetchOperationLimits() } property.SetValue(operationLimits, value); } - OperationLimits = operationLimits; - if (values[maxBrowseContinuationPointIndex] != null - && ServiceResult.IsNotBad(errors[maxBrowseContinuationPointIndex])) + + if (values[maxBrowseContinuationPointIndex] is UInt16 serverMaxContinuationPointsPerBrowse && + ServiceResult.IsNotBad(errors[maxBrowseContinuationPointIndex])) { - ServerMaxContinuationPointsPerBrowse = (UInt16)values[maxBrowseContinuationPointIndex]; + ServerMaxContinuationPointsPerBrowse = serverMaxContinuationPointsPerBrowse; } - if (values[maxByteStringLengthIndex] != null - && ServiceResult.IsNotBad(errors[maxByteStringLengthIndex])) + + if (values[maxByteStringLengthIndex] is UInt32 serverMaxByteStringLength && + ServiceResult.IsNotBad(errors[maxByteStringLengthIndex])) { - ServerMaxByteStringLength = (UInt32)values[maxByteStringLengthIndex]; + ServerMaxByteStringLength = serverMaxByteStringLength; } - } catch (Exception ex) { diff --git a/Libraries/Opc.Ua.Client/Session/SessionAsync.cs b/Libraries/Opc.Ua.Client/Session/SessionAsync.cs index 2c38ffcfb..d569a8aa7 100644 --- a/Libraries/Opc.Ua.Client/Session/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/Session/SessionAsync.cs @@ -639,8 +639,7 @@ public async Task FetchOperationLimitsAsync(CancellationToken ct) .GetValue(null)) ); - // add the server capability MaxContinuationPointPerBrowse. Add further capabilities - // later (when support form them will be implemented and in a more generic fashion) + // add the server capability MaxContinuationPointPerBrowse and MaxByteStringLength nodeIds.Add(VariableIds.Server_ServerCapabilities_MaxBrowseContinuationPoints); int maxBrowseContinuationPointIndex = nodeIds.Count - 1; @@ -670,17 +669,18 @@ public async Task FetchOperationLimitsAsync(CancellationToken ct) } property.SetValue(operationLimits, value); } - OperationLimits = operationLimits; - if (values[maxBrowseContinuationPointIndex].Value != null - && ServiceResult.IsNotBad(errors[maxBrowseContinuationPointIndex])) + + if (values[maxBrowseContinuationPointIndex].Value is UInt16 serverMaxContinuationPointsPerBrowse && + ServiceResult.IsNotBad(errors[maxBrowseContinuationPointIndex])) { - ServerMaxContinuationPointsPerBrowse = (UInt16)values[maxBrowseContinuationPointIndex].Value; + ServerMaxContinuationPointsPerBrowse = serverMaxContinuationPointsPerBrowse; } - if (values[maxByteStringLengthIndex] != null - && ServiceResult.IsNotBad(errors[maxByteStringLengthIndex])) + + if (values[maxByteStringLengthIndex].Value is UInt32 serverMaxByteStringLength && + ServiceResult.IsNotBad(errors[maxByteStringLengthIndex])) { - ServerMaxByteStringLength = (UInt32)values[maxByteStringLengthIndex].Value; + ServerMaxByteStringLength = serverMaxByteStringLength; } } catch (Exception ex) From b3b2f9154e7bcb8383620aeacd437284381f2df6 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Sat, 26 Oct 2024 09:39:04 +0200 Subject: [PATCH 05/13] Update version.json to allow preview builds from develop (#2813) --- version.json | 1 + 1 file changed, 1 insertion(+) diff --git a/version.json b/version.json index 2868ff5f7..e8b1cc07d 100644 --- a/version.json +++ b/version.json @@ -8,6 +8,7 @@ "publicReleaseRefSpec": [ "^refs/heads/main$", "^refs/heads/master$", + "^refs/heads/develop/", "^refs/heads/release/\\d+\\.\\d+\\.\\d+" ], "cloudBuild": { From ce9850c491b9f4093bcd505e5c5709840448ee8e Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Thu, 31 Oct 2024 08:02:32 +0100 Subject: [PATCH 06/13] Fix bugs in JSON decoder (#2828) JSON ReadEnumeratedString does not decode the number in e.g. "Red_0". JSON reencode of JSON content in an extension object runs into an encoder error. --- Fuzzing/Encoders/Fuzz.Tests/EncoderTests.cs | 6 ++++++ Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Fuzzing/Encoders/Fuzz.Tests/EncoderTests.cs b/Fuzzing/Encoders/Fuzz.Tests/EncoderTests.cs index 6b76fe466..f92d9ff0e 100644 --- a/Fuzzing/Encoders/Fuzz.Tests/EncoderTests.cs +++ b/Fuzzing/Encoders/Fuzz.Tests/EncoderTests.cs @@ -65,6 +65,12 @@ public void FuzzGoodTestcases( FuzzTarget(fuzzableCode, messageEncoder.Testcase); } + [Theory] + public void FuzzEmptyByteArray(FuzzTargetFunction fuzzableCode) + { + FuzzTarget(fuzzableCode, Array.Empty()); + } + [Theory] public void FuzzCrashAssets(FuzzTargetFunction fuzzableCode) { diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs index 61a86f439..54068ac40 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs @@ -82,7 +82,7 @@ public JsonDecoder(string json, IServiceMessageContext context) /// /// Create a JSON decoder to decode a from a . /// - /// The system type of the encoded JSON stram. + /// The system type of the encoded JSON stream. /// The text reader. /// The service message context to use. public JsonDecoder(Type systemType, JsonTextReader reader, IServiceMessageContext context) @@ -2847,7 +2847,7 @@ private T ReadEnumeratedString(object token, TryParseHandler handler) wher if (handler?.Invoke(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out number) == false) { int lastIndex = text.LastIndexOf('_'); - if (lastIndex == -1) + if (lastIndex != -1) { text = text.Substring(lastIndex + 1); retry = true; @@ -3280,7 +3280,7 @@ private void EncodeAsJson(JsonTextWriter writer, object value) EncodeAsJson(writer, element); } - writer.WriteStartArray(); + writer.WriteEndArray(); return; } From 787844c011d0b109c3e7a51f40be49e3f1183cf2 Mon Sep 17 00:00:00 2001 From: Suciu Mircea Adrian Date: Thu, 31 Oct 2024 22:03:55 +0200 Subject: [PATCH 07/13] Update brokerHostName before MqttClientOptionsBuilder uses it's value (#2830) --- .../Transport/MqttPubSubConnection.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs b/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs index 937c751dc..61fcaf46c 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs @@ -717,6 +717,17 @@ private MqttClientOptions GetMqttClientOptions() return null; } + // Setup data needed also in mqttClientOptionsBuilder + if ((connectionUri.Scheme == Utils.UriSchemeMqtt) || (connectionUri.Scheme == Utils.UriSchemeMqtts)) + { + if (!String.IsNullOrEmpty(connectionUri.Host)) + { + m_brokerHostName = connectionUri.Host; + m_brokerPort = (connectionUri.Port > 0) ? connectionUri.Port : ((connectionUri.Scheme == Utils.UriSchemeMqtt) ? 1883 : 8883); + m_urlScheme = connectionUri.Scheme; + } + } + ITransportProtocolConfiguration transportProtocolConfiguration = new MqttClientProtocolConfiguration(PubSubConnectionConfiguration.ConnectionProperties); @@ -728,6 +739,7 @@ private MqttClientOptions GetMqttClientOptions() .ProtocolVersion; // create uniques client id string clientId = $"ClientId_{new Random().Next():D10}"; + // MQTTS mqttConnection. if (connectionUri.Scheme == Utils.UriSchemeMqtts) { @@ -794,6 +806,8 @@ private MqttClientOptions GetMqttClientOptions() // Set user credentials. if (mqttProtocolConfiguration.UseCredentials) { + // Following Password usage in both cases is correct since it is the Password position + // to be taken into account for the UserName to be read properly mqttClientOptionsBuilder.WithCredentials( new System.Net.NetworkCredential(string.Empty, mqttProtocolConfiguration.UserName) .Password, From ff3674fe58112afb1999a8f564abe2c3a45aa5c4 Mon Sep 17 00:00:00 2001 From: romanett Date: Sat, 2 Nov 2024 11:44:51 +0100 Subject: [PATCH 08/13] Improve crl handling in certificate stores (#2829) * improve crl handling in certificate stores by not loading CRL with invalid or unsupported content. Hence the revocation check for such certificates may fail. --- .../X509Crl/X509Crl.cs | 9 +++------ .../Opc.Ua.Server/Configuration/TrustList.cs | 4 ++-- .../Certificates/DirectoryCertificateStore.cs | 15 +++++++++++---- .../X509CertificateStore/X509CertificateStore.cs | 13 +++++++++---- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs b/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs index a60a421a3..9473aa6b4 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs @@ -50,6 +50,7 @@ public class X509CRL : IX509CRL public X509CRL(string filePath) : this() { RawData = File.ReadAllBytes(filePath); + EnsureDecoded(); } /// @@ -58,6 +59,7 @@ public X509CRL(string filePath) : this() public X509CRL(byte[] crl) : this() { RawData = crl; + EnsureDecoded(); } /// @@ -78,6 +80,7 @@ public X509CRL(IX509CRL crl) m_crlExtensions.Add(extension); } RawData = crl.RawData; + EnsureDecoded(); } /// @@ -99,7 +102,6 @@ public X500DistinguishedName IssuerName { get { - EnsureDecoded(); return m_issuerName; } } @@ -112,7 +114,6 @@ public DateTime ThisUpdate { get { - EnsureDecoded(); return m_thisUpdate; } } @@ -122,7 +123,6 @@ public DateTime NextUpdate { get { - EnsureDecoded(); return m_nextUpdate; } } @@ -132,7 +132,6 @@ public HashAlgorithmName HashAlgorithmName { get { - EnsureDecoded(); return m_hashAlgorithmName; } } @@ -142,7 +141,6 @@ public IList RevokedCertificates { get { - EnsureDecoded(); return m_revokedCertificates.AsReadOnly(); } } @@ -152,7 +150,6 @@ public X509ExtensionCollection CrlExtensions { get { - EnsureDecoded(); return m_crlExtensions; } } diff --git a/Libraries/Opc.Ua.Server/Configuration/TrustList.cs b/Libraries/Opc.Ua.Server/Configuration/TrustList.cs index c33e77a21..685b395b0 100644 --- a/Libraries/Opc.Ua.Server/Configuration/TrustList.cs +++ b/Libraries/Opc.Ua.Server/Configuration/TrustList.cs @@ -471,7 +471,7 @@ private ServiceResult AddCertificate( result = StatusCodes.BadCertificateInvalid; } - var storeIdentifier = isTrustedCertificate? m_trustedStore : m_issuerStore; + var storeIdentifier = isTrustedCertificate ? m_trustedStore : m_issuerStore; ICertificateStore store = storeIdentifier.OpenStore(); try { @@ -539,7 +539,7 @@ private ServiceResult RemoveCertificate( foreach (var cert in certCollection) { if (X509Utils.CompareDistinguishedName(cert.SubjectName, crl.IssuerName) && - crl.VerifySignature(cert, false)) + crl.VerifySignature(cert, false)) { crlsToDelete.Add(crl); break; diff --git a/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs b/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs index 5d61a0d9a..abea61dde 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs @@ -20,7 +20,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Threading.Tasks; using Opc.Ua.Security.Certificates; using Opc.Ua.Redaction; -using System.Threading; namespace Opc.Ua { @@ -620,7 +619,7 @@ public Task IsRevoked(X509Certificate2 issuer, X509Certificate2 cert } catch (Exception e) { - Utils.LogError(e, "Could not parse CRL file."); + Utils.LogError(e, "Failed to parse CRL {0} in store {1}.", file.FullName, StorePath); continue; } @@ -670,8 +669,16 @@ public Task EnumerateCRLs() { foreach (FileInfo file in m_crlSubdir.GetFiles("*" + kCrlExtension)) { - var crl = new X509CRL(file.FullName); - crls.Add(crl); + try + { + var crl = new X509CRL(file.FullName); + crls.Add(crl); + } + catch (Exception e) + { + Utils.LogError(e, "Failed to parse CRL {0} in store {1}.", file.FullName, StorePath); + } + } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/X509CertificateStore.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/X509CertificateStore.cs index 8cb54b9aa..9be4daf45 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/X509CertificateStore.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/X509CertificateStore.cs @@ -15,7 +15,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ using System; -using System.IO; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; @@ -251,7 +250,6 @@ public async Task IsRevoked(X509Certificate2 issuer, X509Certificate foreach (X509CRL crl in crls) { - if (!X509Utils.CompareDistinguishedName(crl.IssuerName, issuer.SubjectName)) { continue; @@ -299,8 +297,15 @@ public Task EnumerateCRLs() byte[][] rawCrls = store.EnumerateCrls(); foreach (byte[] rawCrl in rawCrls) { - var crl = new X509CRL(rawCrl); - crls.Add(crl); + try + { + var crl = new X509CRL(rawCrl); + crls.Add(crl); + } + catch (Exception e) + { + Utils.LogError(e, "Failed to parse CRL in store {0}.", store.Name); + } } } return Task.FromResult(crls); From 56dd06d97fc282f6d4a64b6e6b8ed344677ce8ee Mon Sep 17 00:00:00 2001 From: KircMax Date: Thu, 7 Nov 2024 10:31:18 +0100 Subject: [PATCH 09/13] Using Uri.TryCreate causes regression with namespace uri that use mixed lower/uppercase letters in the of the Uri.(#2837) Uri.TryCreate lower cases the hostnames. Switch always back to the legacy implementation which maintains the casing. --- Stack/Opc.Ua.Core/Types/Utils/Utils.cs | 61 ++++++++++++-------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs index e6236c51e..9cb66fda6 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs @@ -13,21 +13,21 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System; using System.Collections; using System.Collections.Generic; -using System.Text; -using System.Xml; +using System.Collections.ObjectModel; +using System.Diagnostics; using System.Globalization; -using System.Security.Cryptography.X509Certificates; -using System.Reflection; -using System.Runtime.Serialization; using System.IO; using System.Linq; -using System.Threading.Tasks; -using System.Diagnostics; -using System.Security.Cryptography; using System.Net; -using System.Collections.ObjectModel; -using Opc.Ua.Security.Certificates; +using System.Reflection; using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using Opc.Ua.Security.Certificates; namespace Opc.Ua { @@ -1267,37 +1267,30 @@ public static string EscapeUri(string uri) { if (!string.IsNullOrWhiteSpace(uri)) { - // back compat: for not well formed Uri, fall back to legacy formatting behavior - see #2793 - if (!Uri.IsWellFormedUriString(uri, UriKind.Absolute) || - !Uri.TryCreate(uri.Replace(";", "%3b"), UriKind.Absolute, out Uri validUri)) + // always use back compat: for not well formed Uri, fall back to legacy formatting behavior - see #2793, #2826 + // problem with Uri.TryCreate(uri.Replace(";", "%3b"), UriKind.Absolute, out Uri validUri); + // -> uppercase letters will later be lowercase (and therefore the uri will later be non-matching) + var buffer = new StringBuilder(); + foreach (char ch in uri) { - var buffer = new StringBuilder(); - foreach (char ch in uri) + switch (ch) { - switch (ch) + case ';': + case '%': { - case ';': - case '%': - { - buffer.AppendFormat(CultureInfo.InvariantCulture, "%{0:X2}", Convert.ToInt16(ch)); - break; - } + buffer.AppendFormat(CultureInfo.InvariantCulture, "%{0:X2}", Convert.ToInt16(ch)); + break; + } - default: - { - buffer.Append(ch); - break; - } + default: + { + buffer.Append(ch); + break; } } - return buffer.ToString(); - } - else - { - return validUri.AbsoluteUri; } + return buffer.ToString(); } - return String.Empty; } @@ -2604,7 +2597,7 @@ public static void UpdateExtension(ref XmlElementCollection extensions, XmlQu extensions.Add(document.DocumentElement); } } -#endregion + #endregion #region Reflection Helper Functions /// From 76b53184de0a34016db1c223834899d4a3576425 Mon Sep 17 00:00:00 2001 From: Martin Regen <7962757+mregen@users.noreply.github.com> Date: Thu, 21 Nov 2024 10:16:13 +0100 Subject: [PATCH 10/13] ChannelToken: Dispose HMAC and improve lifetime calculations. (#2846) - Channels are not disposed (client and server) - During long running tests an HMAC object leak was observed. - The HMAC and SymmetricSign objects were not disposed after use, leading to a small incremental memory leak per key renewal. - The channel token lifetime is calculated by the system clock, which can change. Instead use the continous HiResClock.TickCount to calculate the lifetimes. - Token renewal and connection requests are not removed from the request dictionary Improvements: - Make ChannelToken disposable. Dispose channels. - Dispose HMAC and SymmetricSign objects after use. - Use TickCount to calculate key renewal and key expiry. - Introduce a 5% jitter to the token renewal timer, to avoid token renewal storms. - Reduce the allocation per use of the HMAC objects by using ReadOnlySpan and avoid MemoryStream. --- .editorconfig | 3 + .../Common/AsnUtils.cs | 37 ++- Stack/Opc.Ua.Core/Stack/Tcp/ChannelToken.cs | 74 ++++- Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs | 5 + .../Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs | 22 +- .../Stack/Tcp/TcpTransportListener.cs | 17 +- .../Stack/Tcp/UaSCBinaryChannel.Symmetric.cs | 294 +++++++++++------- .../Stack/Tcp/UaSCBinaryChannel.cs | 2 +- .../Stack/Tcp/UaSCBinaryClientChannel.cs | 100 +++--- .../Types/Encoders/BinaryEncoder.cs | 10 + Stack/Opc.Ua.Core/Types/Utils/HiResClock.cs | 10 + Stack/Opc.Ua.Core/Types/Utils/Utils.cs | 55 +++- 12 files changed, 438 insertions(+), 191 deletions(-) diff --git a/.editorconfig b/.editorconfig index 1db323f25..5b57ac1d0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -342,6 +342,9 @@ dotnet_diagnostic.CA1819.severity = # CA1721: The property name is confusing given the existence of another method with the same name. dotnet_diagnostic.CA1721.severity = silent +# CA2014: Do not use stackalloc in loops +dotnet_diagnostic.CA2014.severity = error + # exclude generated code [**/Generated/*.cs] generated_code = true diff --git a/Libraries/Opc.Ua.Security.Certificates/Common/AsnUtils.cs b/Libraries/Opc.Ua.Security.Certificates/Common/AsnUtils.cs index f4e3ff0e0..c2a4d0204 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Common/AsnUtils.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Common/AsnUtils.cs @@ -50,24 +50,35 @@ internal static string ToHexString(this byte[] buffer, bool invertEndian = false return String.Empty; } - var builder = new StringBuilder(buffer.Length * 2); - - if (invertEndian) +#if NET6_0_OR_GREATER + if (!invertEndian) { - for (int ii = buffer.Length - 1; ii >= 0; ii--) - { - builder.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}", buffer[ii]); - } + return Convert.ToHexString(buffer); } else +#endif { - for (int ii = 0; ii < buffer.Length; ii++) + StringBuilder builder = new StringBuilder(buffer.Length * 2); + +#if !NET6_0_OR_GREATER + if (!invertEndian) + { + for (int ii = 0; ii < buffer.Length; ii++) + { + builder.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}", buffer[ii]); + } + } + else +#endif { - builder.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}", buffer[ii]); + for (int ii = buffer.Length - 1; ii >= 0; ii--) + { + builder.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}", buffer[ii]); + } } - } - return builder.ToString(); + return builder.ToString(); + } } /// @@ -85,6 +96,9 @@ internal static byte[] FromHexString(this string buffer) return Array.Empty(); } +#if NET6_0_OR_GREATER + return Convert.FromHexString(buffer); +#else const string digits = "0123456789ABCDEF"; byte[] bytes = new byte[(buffer.Length / 2) + (buffer.Length % 2)]; @@ -120,6 +134,7 @@ internal static byte[] FromHexString(this string buffer) } return bytes; +#endif } /// diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelToken.cs b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelToken.cs index 1cdb5b4f7..a6cada4bb 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelToken.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelToken.cs @@ -11,6 +11,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ using System; +using System.Diagnostics; using System.Security.Cryptography; namespace Opc.Ua.Bindings @@ -18,7 +19,7 @@ namespace Opc.Ua.Bindings /// /// Represents a security token associate with a channel. /// - public class ChannelToken + public sealed class ChannelToken : IDisposable { #region Constructors /// @@ -29,6 +30,49 @@ public ChannelToken() } #endregion + #region IDisposable + /// + /// The private version of the Dispose. + /// + private void Dispose(bool disposing) + { + if (!m_disposed) + { + if (disposing) + { + Utils.SilentDispose(m_clientHmac); + Utils.SilentDispose(m_serverHmac); + Utils.SilentDispose(m_clientEncryptor); + Utils.SilentDispose(m_serverEncryptor); + } + m_clientHmac = null; + m_serverHmac = null; + m_clientEncryptor = null; + m_serverEncryptor = null; + m_disposed = true; + } + } + +#if DEBUG + /// + /// The finalizer is used to catch issues with the dispose. + /// + ~ChannelToken() + { + Dispose(disposing: false); + } +#endif + + /// + /// Disposes the channel tokens. + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + #endregion + #region Public Properties /// /// The id assigned to the channel that the token belongs to. @@ -57,6 +101,16 @@ public DateTime CreatedAt set { m_createdAt = value; } } + /// + /// When the token was created (refers to the local tick count). + /// Used for calculation of renewals. Uses . + /// + public int CreatedAtTickCount + { + get { return m_createdAtTickCount; } + set { m_createdAtTickCount = value; } + } + /// /// The lifetime of the token in milliseconds. /// @@ -73,12 +127,7 @@ public bool Expired { get { - if (DateTime.UtcNow > m_createdAt.AddMilliseconds(m_lifetime)) - { - return true; - } - - return false; + return (HiResClock.TickCount - m_createdAtTickCount) > m_lifetime; } } @@ -89,12 +138,7 @@ public bool ActivationRequired { get { - if (DateTime.UtcNow > m_createdAt.AddMilliseconds(m_lifetime * TcpMessageLimits.TokenActivationPeriod)) - { - return true; - } - - return false; + return (HiResClock.TickCount - m_createdAtTickCount) > (int)Math.Round(m_lifetime * TcpMessageLimits.TokenActivationPeriod); } } @@ -213,12 +257,13 @@ public HMAC ServerHmac get { return m_serverHmac; } set { m_serverHmac = value; } } - #endregion + #endregion #region Private Fields private uint m_channelId; private uint m_tokenId; private DateTime m_createdAt; + private int m_createdAtTickCount; private int m_lifetime; private byte[] m_clientNonce; private byte[] m_serverNonce; @@ -232,6 +277,7 @@ public HMAC ServerHmac private HMAC m_serverHmac; private SymmetricAlgorithm m_clientEncryptor; private SymmetricAlgorithm m_serverEncryptor; + private bool m_disposed; #endregion } } diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs index 8ac08c03d..8fd4b0f23 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs @@ -298,6 +298,11 @@ public static class TcpMessageLimits /// public const double TokenRenewalPeriod = 0.75; + /// + /// The fraction of the lifetime to jitter renewing a token. + /// + public const double TokenRenewalJitterPeriod = 0.05; + /// /// The fraction of the lifetime to wait before forcing the activation of the renewed token. /// diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs index 8ce1377c4..869a907f6 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs @@ -67,7 +67,10 @@ public TcpServerChannel( /// protected override void Dispose(bool disposing) { - base.Dispose(disposing); + lock (DataLock) + { + base.Dispose(disposing); + } } #endregion @@ -549,6 +552,7 @@ private bool ProcessOpenSecureChannelRequest(uint messageType, ArraySegment messageBody, out BufferCollection chunksToProcess) { chunksToProcess = null; - using (var decoder = new BinaryDecoder(messageBody.AsMemory().ToArray(), Quotas.MessageContext)) + using (var decoder = new BinaryDecoder(messageBody, Quotas.MessageContext)) { // read the type of the message before more chunks are processed. NodeId typeId = decoder.ReadNodeId(null); @@ -1134,7 +1143,6 @@ private bool ValidateDiscoveryServiceCall(ChannelToken token, uint requestId, Ar return true; } } - #endregion #region Private Fields diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs index 38bcd7616..2753bdd72 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs @@ -241,8 +241,10 @@ public bool ReconnectToExistingChannel( /// public void ChannelClosed(uint channelId) { - if (m_channels?.TryRemove(channelId, out _) == true) + TcpListenerChannel channel = null; + if (m_channels?.TryRemove(channelId, out channel) == true) { + Utils.SilentDispose(channel); Utils.LogInfo("ChannelId {0}: closed", channelId); } else @@ -307,11 +309,17 @@ private void OnReverseHelloComplete(IAsyncResult result) channel.SetReportCloseSecureChannelAuditCallback(new ReportAuditCloseSecureChannelEventHandler(OnReportAuditCloseSecureChannelEvent)); channel.SetReportCertificateAuditCallback(new ReportAuditCertificateEventHandler(OnReportAuditCertificateEvent)); } + + channel = null; } catch (Exception e) { ConnectionStatusChanged?.Invoke(this, new ConnectionStatusEventArgs(channel.ReverseConnectionUrl, new ServiceResult(e), true)); } + finally + { + Utils.SilentDispose(channel); + } } #endregion @@ -533,6 +541,7 @@ private void OnAccept(object sender, SocketAsyncEventArgs e) // check if the accept socket has been created. if (serveChannel && e.AcceptSocket != null && e.SocketError == SocketError.Success) { + channel = null; try { if (m_reverseConnectListener) @@ -578,11 +587,17 @@ private void OnAccept(object sender, SocketAsyncEventArgs e) // start accepting messages on the channel. channel.Attach(channelId, e.AcceptSocket); + + channel = null; } catch (Exception ex) { Utils.LogError(ex, "Unexpected error accepting a new connection."); } + finally + { + Utils.SilentDispose(channel); + } } } diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs index 8d91e253a..c94ec114d 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs @@ -15,6 +15,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Security.Cryptography; using System.Text; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; namespace Opc.Ua.Bindings { @@ -41,15 +42,16 @@ public partial class UaSCUaBinaryChannel /// protected ChannelToken CreateToken() { - ChannelToken token = new ChannelToken(); + ChannelToken token = new ChannelToken { + ChannelId = m_channelId, + TokenId = 0, + CreatedAt = DateTime.UtcNow, + CreatedAtTickCount = HiResClock.TickCount, + Lifetime = Quotas.SecurityTokenLifetime + }; - token.ChannelId = m_channelId; - token.TokenId = 0; - token.CreatedAt = DateTime.UtcNow; - token.Lifetime = (int)Quotas.SecurityTokenLifetime; - - Utils.LogInfo("ChannelId {0}: Token #{1} created. CreatedAt={2:HH:mm:ss.fff}. Lifetime={3}.", - Id, token.TokenId, token.CreatedAt, token.Lifetime); + Utils.LogInfo("ChannelId {0}: New Token created. CreatedAt={1:HH:mm:ss.fff}-{2}. Lifetime={3}.", + Id, token.CreatedAt, token.CreatedAtTickCount, token.Lifetime); return token; } @@ -62,20 +64,25 @@ protected void ActivateToken(ChannelToken token) // compute the keys for the token. ComputeKeys(token); + Utils.SilentDispose(m_previousToken); m_previousToken = m_currentToken; m_currentToken = token; + Utils.SilentDispose(m_renewedToken); m_renewedToken = null; - Utils.LogInfo("ChannelId {0}: Token #{1} activated. CreatedAt={2:HH:mm:ss.fff}. Lifetime={3}.", Id, token.TokenId, token.CreatedAt, token.Lifetime); + Utils.LogInfo("ChannelId {0}: Token #{1} activated. CreatedAt={2:HH:mm:ss.fff}-{3}. Lifetime={4}.", + Id, token.TokenId, token.CreatedAt, token.CreatedAtTickCount, token.Lifetime); } /// - /// Sets the renewed token + /// Sets the renewed token. /// protected void SetRenewedToken(ChannelToken token) { + Utils.SilentDispose(m_renewedToken); m_renewedToken = token; - Utils.LogInfo("ChannelId {0}: Renewed Token #{1} set. CreatedAt={2:HH:mm:ss.fff}. Lifetime ={3}.", Id, token.TokenId, token.CreatedAt, token.Lifetime); + Utils.LogInfo("ChannelId {0}: Renewed Token #{1} set. CreatedAt={2:HH:mm:ss.fff}-{3}. Lifetime={4}.", + Id, token.TokenId, token.CreatedAt, token.CreatedAtTickCount, token.Lifetime); } /// @@ -83,8 +90,12 @@ protected void SetRenewedToken(ChannelToken token) /// protected void DiscardTokens() { + Utils.SilentDispose(m_previousToken); m_previousToken = null; + Utils.SilentDispose(m_currentToken); m_currentToken = null; + Utils.SilentDispose(m_renewedToken); + m_renewedToken = null; } #endregion @@ -176,25 +187,37 @@ protected void ComputeKeys(ChannelToken token) return; } - if (SecurityPolicyUri == SecurityPolicies.Basic256Sha256 || - SecurityPolicyUri == SecurityPolicies.Aes128_Sha256_RsaOaep || - SecurityPolicyUri == SecurityPolicies.Aes256_Sha256_RsaPss) + bool useSHA256 = SecurityPolicyUri != SecurityPolicies.Basic128Rsa15 && SecurityPolicyUri != SecurityPolicies.Basic256; + + if (useSHA256) { - token.ClientSigningKey = Utils.PSHA256(token.ServerNonce, null, token.ClientNonce, 0, m_signatureKeySize); - token.ClientEncryptingKey = Utils.PSHA256(token.ServerNonce, null, token.ClientNonce, m_signatureKeySize, m_encryptionKeySize); - token.ClientInitializationVector = Utils.PSHA256(token.ServerNonce, null, token.ClientNonce, m_signatureKeySize + m_encryptionKeySize, m_encryptionBlockSize); - token.ServerSigningKey = Utils.PSHA256(token.ClientNonce, null, token.ServerNonce, 0, m_signatureKeySize); - token.ServerEncryptingKey = Utils.PSHA256(token.ClientNonce, null, token.ServerNonce, m_signatureKeySize, m_encryptionKeySize); - token.ServerInitializationVector = Utils.PSHA256(token.ClientNonce, null, token.ServerNonce, m_signatureKeySize + m_encryptionKeySize, m_encryptionBlockSize); + using (HMACSHA256 hmac = new HMACSHA256(token.ServerNonce)) + { + token.ClientSigningKey = Utils.PSHA256(hmac, null, token.ClientNonce, 0, m_signatureKeySize); + token.ClientEncryptingKey = Utils.PSHA256(hmac, null, token.ClientNonce, m_signatureKeySize, m_encryptionKeySize); + token.ClientInitializationVector = Utils.PSHA256(hmac, null, token.ClientNonce, m_signatureKeySize + m_encryptionKeySize, m_encryptionBlockSize); + } + using (HMACSHA256 hmac = new HMACSHA256(token.ClientNonce)) + { + token.ServerSigningKey = Utils.PSHA256(hmac, null, token.ServerNonce, 0, m_signatureKeySize); + token.ServerEncryptingKey = Utils.PSHA256(hmac, null, token.ServerNonce, m_signatureKeySize, m_encryptionKeySize); + token.ServerInitializationVector = Utils.PSHA256(hmac, null, token.ServerNonce, m_signatureKeySize + m_encryptionKeySize, m_encryptionBlockSize); + } } else { - token.ClientSigningKey = Utils.PSHA1(token.ServerNonce, null, token.ClientNonce, 0, m_signatureKeySize); - token.ClientEncryptingKey = Utils.PSHA1(token.ServerNonce, null, token.ClientNonce, m_signatureKeySize, m_encryptionKeySize); - token.ClientInitializationVector = Utils.PSHA1(token.ServerNonce, null, token.ClientNonce, m_signatureKeySize + m_encryptionKeySize, m_encryptionBlockSize); - token.ServerSigningKey = Utils.PSHA1(token.ClientNonce, null, token.ServerNonce, 0, m_signatureKeySize); - token.ServerEncryptingKey = Utils.PSHA1(token.ClientNonce, null, token.ServerNonce, m_signatureKeySize, m_encryptionKeySize); - token.ServerInitializationVector = Utils.PSHA1(token.ClientNonce, null, token.ServerNonce, m_signatureKeySize + m_encryptionKeySize, m_encryptionBlockSize); + using (HMACSHA1 hmac = new HMACSHA1(token.ServerNonce)) + { + token.ClientSigningKey = Utils.PSHA1(hmac, null, token.ClientNonce, 0, m_signatureKeySize); + token.ClientEncryptingKey = Utils.PSHA1(hmac, null, token.ClientNonce, m_signatureKeySize, m_encryptionKeySize); + token.ClientInitializationVector = Utils.PSHA1(hmac, null, token.ClientNonce, m_signatureKeySize + m_encryptionKeySize, m_encryptionBlockSize); + } + using (HMACSHA1 hmac = new HMACSHA1(token.ClientNonce)) + { + token.ServerSigningKey = Utils.PSHA1(hmac, null, token.ServerNonce, 0, m_signatureKeySize); + token.ServerEncryptingKey = Utils.PSHA1(hmac, null, token.ServerNonce, m_signatureKeySize, m_encryptionKeySize); + token.ServerInitializationVector = Utils.PSHA1(hmac, null, token.ServerNonce, m_signatureKeySize + m_encryptionKeySize, m_encryptionBlockSize); + } } switch (SecurityPolicyUri) @@ -206,24 +229,22 @@ protected void ComputeKeys(ChannelToken token) case SecurityPolicies.Aes256_Sha256_RsaPss: { // create encryptors. - SymmetricAlgorithm AesCbcEncryptorProvider = Aes.Create(); - AesCbcEncryptorProvider.Mode = CipherMode.CBC; - AesCbcEncryptorProvider.Padding = PaddingMode.None; - AesCbcEncryptorProvider.Key = token.ClientEncryptingKey; - AesCbcEncryptorProvider.IV = token.ClientInitializationVector; - token.ClientEncryptor = AesCbcEncryptorProvider; - - SymmetricAlgorithm AesCbcDecryptorProvider = Aes.Create(); - AesCbcDecryptorProvider.Mode = CipherMode.CBC; - AesCbcDecryptorProvider.Padding = PaddingMode.None; - AesCbcDecryptorProvider.Key = token.ServerEncryptingKey; - AesCbcDecryptorProvider.IV = token.ServerInitializationVector; - token.ServerEncryptor = AesCbcDecryptorProvider; - - // create HMACs. - if (SecurityPolicyUri == SecurityPolicies.Basic256Sha256 || - SecurityPolicyUri == SecurityPolicies.Aes128_Sha256_RsaOaep || - SecurityPolicyUri == SecurityPolicies.Aes256_Sha256_RsaPss) + SymmetricAlgorithm aesCbcEncryptorProvider = Aes.Create(); + aesCbcEncryptorProvider.Mode = CipherMode.CBC; + aesCbcEncryptorProvider.Padding = PaddingMode.None; + aesCbcEncryptorProvider.Key = token.ClientEncryptingKey; + aesCbcEncryptorProvider.IV = token.ClientInitializationVector; + token.ClientEncryptor = aesCbcEncryptorProvider; + + SymmetricAlgorithm aesCbcDecryptorProvider = Aes.Create(); + aesCbcDecryptorProvider.Mode = CipherMode.CBC; + aesCbcDecryptorProvider.Padding = PaddingMode.None; + aesCbcDecryptorProvider.Key = token.ServerEncryptingKey; + aesCbcDecryptorProvider.IV = token.ServerInitializationVector; + token.ServerEncryptor = aesCbcDecryptorProvider; + + // create HMACs. Must be disposed after use. + if (useSHA256) { // SHA256 token.ServerHmac = new HMACSHA256(token.ServerSigningKey); @@ -277,7 +298,6 @@ protected BufferCollection WriteSymmetricMessage( maxPayloadSize); // check for encodeable body. - if (messageBody is IEncodeable encodeable) { // debug code used to verify that message aborts are handled correctly. @@ -311,6 +331,9 @@ protected BufferCollection WriteSymmetricMessage( BufferCollection chunksToSend = new BufferCollection(chunksToProcess.Capacity); +#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER + Span paddingBuffer = stackalloc byte[EncryptionBlockSize]; +#endif int messageSize = 0; for (int ii = 0; ii < chunksToProcess.Count; ii++) @@ -325,28 +348,24 @@ protected BufferCollection WriteSymmetricMessage( } MemoryStream strm = new MemoryStream(chunkToProcess.Array, 0, SendBufferSize); - BinaryEncoder encoder = new BinaryEncoder(strm, Quotas.MessageContext, false); - - try + using (BinaryEncoder encoder = new BinaryEncoder(strm, Quotas.MessageContext, false)) { - // check if the message needs to be aborted. if (MessageLimitsExceeded(isRequest, messageSize + chunkToProcess.Count - headerSize, ii + 1)) { encoder.WriteUInt32(null, messageType | TcpMessageType.Abort); // replace the body in the chunk with an error message. - BinaryEncoder errorEncoder = new BinaryEncoder( + using (BinaryEncoder errorEncoder = new BinaryEncoder( chunkToProcess.Array, chunkToProcess.Offset, chunkToProcess.Count, - Quotas.MessageContext); - - WriteErrorMessageBody(errorEncoder, (isRequest) ? StatusCodes.BadRequestTooLarge : StatusCodes.BadResponseTooLarge); - - int size = errorEncoder.Close(); - errorEncoder.Dispose(); - chunkToProcess = new ArraySegment(chunkToProcess.Array, chunkToProcess.Offset, size); + Quotas.MessageContext)) + { + WriteErrorMessageBody(errorEncoder, (isRequest) ? StatusCodes.BadRequestTooLarge : StatusCodes.BadResponseTooLarge); + int size = errorEncoder.Close(); + chunkToProcess = new ArraySegment(chunkToProcess.Array, chunkToProcess.Offset, size); + } limitsExceeded = true; } @@ -377,9 +396,11 @@ protected BufferCollection WriteSymmetricMessage( // reserve one byte for the padding size. count++; - if (count % EncryptionBlockSize != 0) + // use padding as helper to calc the real padding + padding = count % EncryptionBlockSize; + if (padding != 0) { - padding = EncryptionBlockSize - (count % EncryptionBlockSize); + padding = EncryptionBlockSize - padding; } count += padding; @@ -405,9 +426,20 @@ protected BufferCollection WriteSymmetricMessage( // write padding. if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { - for (int jj = 0; jj <= padding; jj++) +#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER + if (padding > 1) { - encoder.WriteByte(null, (byte)padding); + Span buffer = paddingBuffer.Slice(0, padding + 1); + buffer.Fill((byte)padding); + encoder.WriteRawBytes(buffer); + } + else +#endif + { + for (int jj = 0; jj <= padding; jj++) + { + encoder.WriteByte(null, (byte)padding); + } } } @@ -432,10 +464,6 @@ protected BufferCollection WriteSymmetricMessage( // add the header into chunk. chunksToSend.Add(new ArraySegment(chunkToProcess.Array, 0, encoder.Position)); } - finally - { - encoder.Dispose(); - } } // ensure the buffers don't get cleaned up on exit. @@ -486,10 +514,9 @@ protected ArraySegment ReadSymmetricMessage( } // check if activation of the new token should be forced. - if (RenewedToken != null && CurrentToken.ActivationRequired) + else if (RenewedToken != null && CurrentToken.ActivationRequired) { ActivateToken(RenewedToken); - Utils.LogInfo("ChannelId {0}: Token #{1} activated forced.", Id, CurrentToken.TokenId); } @@ -502,7 +529,7 @@ protected ArraySegment ReadSymmetricMessage( } // find the token. - if (currentToken.TokenId != tokenId && PreviousToken != null && PreviousToken.TokenId != tokenId) + if (currentToken.TokenId != tokenId && (PreviousToken == null || PreviousToken.TokenId != tokenId)) { throw ServiceResultException.Create( StatusCodes.BadTcpSecureChannelUnknown, @@ -524,8 +551,8 @@ protected ArraySegment ReadSymmetricMessage( if (token.Expired) { throw ServiceResultException.Create(StatusCodes.BadTcpSecureChannelUnknown, - "Channel{0}: Token #{1} has expired. Lifetime={2:HH:mm:ss.fff}", - Id, token.TokenId, token.CreatedAt); + "Channel{0}: Token #{1} has expired. Lifetime={2:HH:mm:ss.fff}-{3}", + Id, token.TokenId, token.CreatedAt, token.CreatedAtTickCount); } int headerSize = decoder.Position; @@ -536,15 +563,14 @@ protected ArraySegment ReadSymmetricMessage( Decrypt(token, new ArraySegment(buffer.Array, buffer.Offset + headerSize, buffer.Count - headerSize), isRequest); } + int paddingCount = 0; if (SecurityMode != MessageSecurityMode.None) { + int signatureStart = buffer.Offset + buffer.Count - SymmetricSignatureSize; + // extract signature. byte[] signature = new byte[SymmetricSignatureSize]; - - for (int ii = 0; ii < SymmetricSignatureSize; ii++) - { - signature[ii] = buffer.Array[buffer.Offset + buffer.Count - SymmetricSignatureSize + ii]; - } + Array.Copy(buffer.Array, signatureStart, signature, 0, signature.Length); // verify the signature. if (!Verify(token, signature, new ArraySegment(buffer.Array, buffer.Offset, buffer.Count - SymmetricSignatureSize), isRequest)) @@ -552,26 +578,24 @@ protected ArraySegment ReadSymmetricMessage( Utils.LogError("ChannelId {0}: Could not verify signature on message.", Id); throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Could not verify the signature on the message."); } - } - - int paddingCount = 0; - if (SecurityMode == MessageSecurityMode.SignAndEncrypt) - { - // verify padding. - int paddingStart = buffer.Offset + buffer.Count - SymmetricSignatureSize - 1; - paddingCount = buffer.Array[paddingStart]; - - for (int ii = paddingStart - paddingCount; ii < paddingStart; ii++) + if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { - if (buffer.Array[ii] != paddingCount) + // verify padding. + int paddingStart = signatureStart - 1; + paddingCount = buffer.Array[paddingStart]; + + for (int ii = paddingStart - paddingCount; ii <= paddingStart; ii++) { - throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Could not verify the padding in the message."); + if (buffer.Array[ii] != paddingCount) + { + throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Could not verify the padding in the message."); + } } - } - // add byte for size. - paddingCount++; + // add byte for size. + paddingCount++; + } } // extract request id and sequence number. @@ -693,15 +717,37 @@ protected void Decrypt(ChannelToken token, ArraySegment dataToDecrypt, boo } } +#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER + /// + /// Signs the message using HMAC. + /// + private static byte[] SymmetricSign(ChannelToken token, ReadOnlySpan dataToSign, bool useClientKeys) + { + // get HMAC object. + HMAC hmac = (useClientKeys) ? token.ClientHmac : token.ServerHmac; + + // compute hash. + int hashSizeInBytes = hmac.HashSize >> 3; + byte[] signature = new byte[hashSizeInBytes]; + bool result = hmac.TryComputeHash(dataToSign, signature, out int bytesWritten); + // check result + if (!result || bytesWritten != hashSizeInBytes) + { + ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "The computed hash doesn't match the expected size."); + } + + // return signature. + return signature; + } +#else /// - /// Signs the message using SHA1 HMAC + /// Signs the message using HMAC. /// private static byte[] SymmetricSign(ChannelToken token, ArraySegment dataToSign, bool useClientKeys) { // get HMAC object. HMAC hmac = (useClientKeys) ? token.ClientHmac : token.ServerHmac; - // compute hash. MemoryStream istrm = new MemoryStream(dataToSign.Array, dataToSign.Offset, dataToSign.Count, false); byte[] signature = hmac.ComputeHash(istrm); @@ -710,7 +756,34 @@ private static byte[] SymmetricSign(ChannelToken token, ArraySegment dataT // return signature. return signature; } +#endif +#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER + /// + /// Verifies a HMAC for a message. + /// + private bool SymmetricVerify( + ChannelToken token, + ReadOnlySpan signature, + ReadOnlySpan dataToVerify, + bool useClientKeys) + { + // get HMAC object. + HMAC hmac = (useClientKeys) ? token.ClientHmac : token.ServerHmac; + + // compute hash. + int hashSizeInBytes = hmac.HashSize >> 3; + Span computedSignature = stackalloc byte[hashSizeInBytes]; + bool result = hmac.TryComputeHash(dataToVerify, computedSignature, out int bytesWritten); + Debug.Assert(bytesWritten == hashSizeInBytes); + // compare signatures. + if (!result || !computedSignature.SequenceEqual(signature)) + { + string expectedSignature = Utils.ToHexString(computedSignature.ToArray()); + string messageType = Encoding.UTF8.GetString(dataToVerify.Slice(0, 4)); + int messageLength = BitConverter.ToInt32(dataToVerify.Slice(4)); + string actualSignature = Utils.ToHexString(signature); +#else /// /// Verifies a HMAC for a message. /// @@ -723,31 +796,27 @@ private bool SymmetricVerify( // get HMAC object. HMAC hmac = (useClientKeys) ? token.ClientHmac : token.ServerHmac; - // compute hash. MemoryStream istrm = new MemoryStream(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count, false); byte[] computedSignature = hmac.ComputeHash(istrm); istrm.Dispose(); - // compare signatures. - for (int ii = 0; ii < signature.Length; ii++) + if (!Utils.IsEqual(computedSignature, signature)) { - if (computedSignature[ii] != signature[ii]) - { - string messageType = Encoding.UTF8.GetString(dataToVerify.Array, dataToVerify.Offset, 4); - int messageLength = BitConverter.ToInt32(dataToVerify.Array, dataToVerify.Offset + 4); - string expectedSignature = Utils.ToHexString(computedSignature); - string actualSignature = Utils.ToHexString(signature); - - var message = new StringBuilder(); - message.AppendLine("Channel{0}: Could not validate signature."); - message.AppendLine("ChannelId={1}, TokenId={2}, MessageType={3}, Length={4}"); - message.AppendLine("ExpectedSignature={5}"); - message.AppendLine("ActualSignature={6}"); - Utils.LogError(message.ToString(), Id, token.ChannelId, token.TokenId, - messageType, messageLength, expectedSignature, actualSignature); - - return false; - } + string expectedSignature = Utils.ToHexString(computedSignature); + string messageType = Encoding.UTF8.GetString(dataToVerify.Array, dataToVerify.Offset, 4); + int messageLength = BitConverter.ToInt32(dataToVerify.Array, dataToVerify.Offset + 4); + string actualSignature = Utils.ToHexString(signature); +#endif + + var message = new StringBuilder(); + message.AppendLine("Channel{0}: Could not validate signature."); + message.AppendLine("ChannelId={1}, TokenId={2}, MessageType={3}, Length={4}"); + message.AppendLine("ExpectedSignature={5}"); + message.AppendLine("ActualSignature={6}"); + Utils.LogError(message.ToString(), Id, token.ChannelId, token.TokenId, + messageType, messageLength, expectedSignature, actualSignature); + + return false; } return true; @@ -779,7 +848,6 @@ private static void SymmetricEncrypt( { throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Input data is not an even number of encryption blocks."); } - encryptor.TransformBlock(blockToEncrypt, start, count, blockToEncrypt, start); } } diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs index f69fc55d9..0bb3d2fc6 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs @@ -150,7 +150,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - // nothing to do. + DiscardTokens(); } } #endregion diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs index 9c6915cec..84508c514 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs @@ -81,6 +81,7 @@ public UaSCUaBinaryClientChannel( } m_requests = new ConcurrentDictionary(); + m_random = new Random(); m_lastRequestId = 0; m_ConnectCallback = new EventHandler(OnConnectComplete); m_startHandshake = new TimerCallback(OnScheduledHandshake); @@ -106,6 +107,10 @@ protected override void Dispose(bool disposing) { Utils.SilentDispose(m_handshakeTimer); m_handshakeTimer = null; + Utils.SilentDispose(m_requestedToken); + m_requestedToken = null; + m_requests?.Clear(); + m_handshakeOperation = null; } base.Dispose(disposing); @@ -161,8 +166,9 @@ public IAsyncResult BeginConnect(Uri url, int timeout, AsyncCallback callback, o Socket = m_socketFactory.Create(this, BufferManager, Quotas.MaxBufferSize); Socket.BeginConnect(m_via, m_ConnectCallback, operation); } + + return operation; } - return m_handshakeOperation; } /// @@ -504,7 +510,7 @@ private bool ProcessAcknowledgeMessage(ArraySegment messageChunk) decoder.Close(); } - + // ready to open the channel. State = TcpChannelState.Opening; @@ -958,11 +964,17 @@ private void OnScheduledHandshake(object state) // close the socket and reconnect. State = TcpChannelState.Closed; - if (Socket != null) + // dispose of the tokens. + uint channelId = ChannelId; + ChannelId = 0; + DiscardTokens(); + + var socket = Socket; + if (socket != null) { - Utils.LogInfo("ChannelId {0}: CLIENTCHANNEL SOCKET CLOSED: {1:X8}", ChannelId, Socket.Handle); - Socket.Close(); Socket = null; + Utils.LogInfo("ChannelId {0}: CLIENTCHANNEL SOCKET CLOSED ON SCHEDULED HANDSHAKE: {1:X8}", channelId, socket.Handle); + socket.Close(); } // set the state. @@ -997,6 +1009,7 @@ private void OnHandshakeComplete(IAsyncResult result) { lock (DataLock) { + ServiceResult error = null; try { if (m_handshakeOperation == null) @@ -1007,17 +1020,14 @@ private void OnHandshakeComplete(IAsyncResult result) Utils.LogTrace("ChannelId {0}: OnHandshakeComplete", ChannelId); m_handshakeOperation.End(Int32.MaxValue); - m_handshakeOperation = null; - m_reconnecting = false; + + return; } catch (Exception e) { Utils.LogError(e, "ChannelId {0}: Handshake Failed {1}", ChannelId, e.Message); - m_handshakeOperation = null; - m_reconnecting = false; - - ServiceResult error = ServiceResult.Create(e, StatusCodes.BadUnexpectedError, "Unexpected error reconnecting or renewing a token."); + error = ServiceResult.Create(e, StatusCodes.BadUnexpectedError, "Unexpected error reconnecting or renewing a token."); // check for expired channel or token. if (error.Code == StatusCodes.BadTcpSecureChannelUnknown || error.Code == StatusCodes.BadSecurityChecksFailed) @@ -1026,9 +1036,14 @@ private void OnHandshakeComplete(IAsyncResult result) Shutdown(error); return; } - - ForceReconnect(ServiceResult.Create(e, StatusCodes.BadUnexpectedError, "Unexpected error reconnecting or renewing a token.")); } + finally + { + OperationCompleted(m_handshakeOperation); + m_reconnecting = false; + } + + ForceReconnect(error); } } @@ -1134,13 +1149,10 @@ private void Shutdown(ServiceResult reason) } // cancel all requests. - List operations = new List(m_requests.Values); - - foreach (WriteOperation operation in operations) + foreach (var operation in m_requests.ToArray()) { - operation.Fault(new ServiceResult(StatusCodes.BadSecureChannelClosed, reason)); + operation.Value.Fault(new ServiceResult(StatusCodes.BadSecureChannelClosed, reason)); } - m_requests.Clear(); uint channelId = ChannelId; @@ -1154,14 +1166,16 @@ private void Shutdown(ServiceResult reason) // clear the handshake state. m_handshakeOperation = null; + Utils.SilentDispose(m_requestedToken); m_requestedToken = null; m_reconnecting = false; - if (Socket != null) + var socket = Socket; + if (socket != null) { - Utils.LogInfo("ChannelId {0}: CLIENTCHANNEL SOCKET CLOSED: {1:X8}", channelId, Socket.Handle); - Socket.Close(); Socket = null; + Utils.LogInfo("ChannelId {0}: CLIENTCHANNEL SOCKET CLOSED SHUTDOWN: {1:X8}", channelId, socket.Handle); + socket.Close(); } // set the state. @@ -1192,17 +1206,14 @@ private void ForceReconnect(ServiceResult reason) Utils.LogWarning("ChannelId {0}: Force reconnect reason={1}", Id, reason); // cancel all requests. - List operations = new List(m_requests.Values); - - foreach (WriteOperation operation in operations) + foreach (var operation in m_requests.ToArray()) { - operation.Fault(new ServiceResult(StatusCodes.BadSecureChannelClosed, reason)); + operation.Value.Fault(new ServiceResult(StatusCodes.BadSecureChannelClosed, reason)); } - m_requests.Clear(); // halt any existing handshake. - if (m_handshakeOperation != null && !m_handshakeOperation.IsCompleted) + if (m_handshakeOperation?.IsCompleted == false) { m_handshakeOperation.Fault(reason); return; @@ -1220,6 +1231,7 @@ private void ForceReconnect(ServiceResult reason) // clear the handshake state. m_handshakeOperation = null; + Utils.SilentDispose(m_requestedToken); m_requestedToken = null; m_reconnecting = true; @@ -1265,19 +1277,20 @@ private void ScheduleTokenRenewal(ChannelToken token) m_handshakeTimer = null; } - // calculate renewal timing based on token lifetime. - DateTime expiryTime = token.CreatedAt.AddMilliseconds(token.Lifetime); - - double timeToRenewal = ((expiryTime.Ticks - DateTime.UtcNow.Ticks) / TimeSpan.TicksPerMillisecond) * TcpMessageLimits.TokenRenewalPeriod; - + // calculate renewal timing based on token lifetime + jitter. Do not rely on the server time! + int jitterResolution = (int)Math.Round(token.Lifetime * TcpMessageLimits.TokenRenewalJitterPeriod); + int jitter = m_random.Next(-jitterResolution, jitterResolution); + int timeToRenewal = (int)Math.Round(token.Lifetime * TcpMessageLimits.TokenRenewalPeriod) + + jitter - (HiResClock.TickCount - token.CreatedAtTickCount); if (timeToRenewal < 0) { timeToRenewal = 0; } - Utils.LogInfo("ChannelId {0}: Token Expiry {1}, renewal scheduled in {2} ms.", ChannelId, expiryTime, (int)timeToRenewal); + Utils.LogInfo("ChannelId {0}: Token Expiry {1:HH:mm:ss.fff}, renewal scheduled at {2:HH:mm:ss.fff} in {3} ms.", + ChannelId, token.CreatedAt.AddMilliseconds(token.Lifetime), HiResClock.UtcTickCount(token.CreatedAtTickCount + timeToRenewal), timeToRenewal); - m_handshakeTimer = new Timer(m_startHandshake, token, (int)timeToRenewal, Timeout.Infinite); + m_handshakeTimer = new Timer(m_startHandshake, token, timeToRenewal, Timeout.Infinite); } /// @@ -1289,7 +1302,7 @@ private WriteOperation BeginOperation(int timeout, AsyncCallback callback, objec operation.RequestId = Utils.IncrementIdentifier(ref m_lastRequestId); if (!m_requests.TryAdd(operation.RequestId, operation)) { - throw new ServiceResultException(StatusCodes.BadUnexpectedError, "Could not add operation to list of pending operations."); + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Could not add request {0} to list of pending operations.", operation.RequestId); } return operation; } @@ -1304,12 +1317,15 @@ private void OperationCompleted(WriteOperation operation) return; } - if (m_handshakeOperation == operation) + if (Object.ReferenceEquals(m_handshakeOperation, operation)) { m_handshakeOperation = null; } - m_requests.TryRemove(operation.RequestId, out _); + if (!m_requests.TryRemove(operation.RequestId, out _)) + { + Utils.LogError("Could not remove requestId {0} from list of pending operations.", operation.RequestId); + } } /// @@ -1356,6 +1372,10 @@ private void OnConnectOnDemandComplete(object state) request.Operation.Fault(StatusCodes.BadNoCommunication, "Error establishing a connection: " + e.Message); continue; } + finally + { + OperationCompleted(operation); + } } if (this.CurrentToken == null) @@ -1390,9 +1410,10 @@ private WriteOperation InternalClose(int timeout) } // check if a handshake is in progress. - if (m_handshakeOperation != null && !m_handshakeOperation.IsCompleted) + if (m_handshakeOperation?.IsCompleted == false) { m_handshakeOperation.Fault(ServiceResult.Create(StatusCodes.BadConnectionClosed, "Channel was closed by the user.")); + OperationCompleted(m_handshakeOperation); } Utils.LogTrace("ChannelId {0}: Close", ChannelId); @@ -1437,6 +1458,7 @@ protected bool ProcessErrorMessage(uint messageType, ArraySegment messageC if (m_handshakeOperation != null) { m_handshakeOperation.Fault(error); + OperationCompleted(m_handshakeOperation); return false; } @@ -1517,7 +1539,6 @@ private bool ProcessResponseMessage(uint messageType, ArraySegment message // check if operation is still available. WriteOperation operation = null; - if (!m_requests.TryGetValue(requestId, out operation)) { return false; @@ -1605,6 +1626,7 @@ private bool ProcessResponseMessage(uint messageType, ArraySegment message private TimerCallback m_startHandshake; private AsyncCallback m_handshakeComplete; private List m_queuedOperations; + private Random m_random; private readonly string g_ImplementationString = "UA.NETStandard ClientChannel {0} " + Utils.GetAssemblyBuildNumber(); #endregion } diff --git a/Stack/Opc.Ua.Core/Types/Encoders/BinaryEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/BinaryEncoder.cs index 48522da10..91e0a81f4 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/BinaryEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/BinaryEncoder.cs @@ -193,6 +193,16 @@ public void WriteRawBytes(byte[] buffer, int offset, int count) m_writer.Write(buffer, offset, count); } +#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER + /// + /// Writes raw bytes to the stream. + /// + public void WriteRawBytes(ReadOnlySpan buffer) + { + m_writer.Write(buffer); + } +#endif + /// /// Encodes a message in a buffer. /// diff --git a/Stack/Opc.Ua.Core/Types/Utils/HiResClock.cs b/Stack/Opc.Ua.Core/Types/Utils/HiResClock.cs index d4cf87edc..b2a0b1c2d 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/HiResClock.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/HiResClock.cs @@ -81,6 +81,16 @@ public static long TickCount64 /// public static int TickCount => Environment.TickCount; + /// + /// Returns the Utc time with respect to the current tick count. + /// + public static DateTime UtcTickCount(int tickCount) + { + DateTime utcNow = DateTime.UtcNow; + int delta = tickCount - TickCount; + return utcNow.AddMilliseconds(delta); + } + /// /// Disables the hires clock. /// diff --git a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs index 9cb66fda6..54269fe17 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs @@ -1544,6 +1544,7 @@ public static Array FlattenArray(Array array) /// /// Converts a buffer to a hexadecimal string. /// +#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER public static string ToHexString(byte[] buffer, bool invertEndian = false) { if (buffer == null || buffer.Length == 0) @@ -1551,6 +1552,27 @@ public static string ToHexString(byte[] buffer, bool invertEndian = false) return String.Empty; } + return ToHexString(new ReadOnlySpan(buffer), invertEndian); + } + + /// + /// Converts a buffer to a hexadecimal string. + /// + public static string ToHexString(ReadOnlySpan buffer, bool invertEndian = false) + { + if (buffer.Length == 0) + { + return String.Empty; + } +#else + public static string ToHexString(byte[] buffer, bool invertEndian = false) + { + if (buffer == null || buffer.Length == 0) + { + return String.Empty; + } +#endif + #if NET6_0_OR_GREATER if (!invertEndian) { @@ -2597,7 +2619,7 @@ public static void UpdateExtension(ref XmlElementCollection extensions, XmlQu extensions.Add(document.DocumentElement); } } - #endregion +#endregion #region Reflection Helper Functions /// @@ -3011,8 +3033,10 @@ public static byte[] PSHA1(byte[] secret, string label, byte[] data, int offset, { if (secret == null) throw new ArgumentNullException(nameof(secret)); // create the hmac. - HMACSHA1 hmac = new HMACSHA1(secret); - return PSHA(hmac, label, data, offset, length); + using (HMACSHA1 hmac = new HMACSHA1(secret)) + { + return PSHA(hmac, label, data, offset, length); + } } /// @@ -3022,10 +3046,32 @@ public static byte[] PSHA256(byte[] secret, string label, byte[] data, int offse { if (secret == null) throw new ArgumentNullException(nameof(secret)); // create the hmac. - HMACSHA256 hmac = new HMACSHA256(secret); + using (HMACSHA256 hmac = new HMACSHA256(secret)) + { + return PSHA(hmac, label, data, offset, length); + } + } + + /// + /// Generates a Pseudo random sequence of bits using the P_SHA1 alhorithm. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Security", "CA5350:Do Not Use Weak Cryptographic Algorithms", + Justification = "SHA1 is needed for deprecated security profiles.")] + public static byte[] PSHA1(HMACSHA1 hmac, string label, byte[] data, int offset, int length) + { + return PSHA(hmac, label, data, offset, length); + } + + /// + /// Generates a Pseudo random sequence of bits using the P_SHA256 alhorithm. + /// + public static byte[] PSHA256(HMACSHA256 hmac, string label, byte[] data, int offset, int length) + { return PSHA(hmac, label, data, offset, length); } + /// /// Generates a Pseudo random sequence of bits using the HMAC algorithm. /// @@ -3074,7 +3120,6 @@ private static byte[] PSHA(HMAC hmac, string label, byte[] data, int offset, int byte[] output = new byte[length]; int position = 0; - do { byte[] hash = hmac.ComputeHash(prfSeed); From 3543d0292556691f681e39145e2de4526b90487d Mon Sep 17 00:00:00 2001 From: Suciu Mircea Adrian Date: Fri, 22 Nov 2024 11:01:30 +0200 Subject: [PATCH 11/13] Added a minimal rogue client detection mechanism at the transport level (#2850) Clients that are failing too often to pass the security validationin a certain interval of time with the Basic128 security profile are now tracked and blocked. --- .../Stack/Tcp/TcpListenerChannel.cs | 13 + .../Stack/Tcp/TcpTransportListener.cs | 266 +++++++++++++++++- 2 files changed, 278 insertions(+), 1 deletion(-) diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs index 763b71f1a..2aeb52f1c 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs @@ -11,6 +11,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ using System; +using System.Net; using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.Logging; @@ -267,6 +268,18 @@ protected void ForceChannelFault(ServiceResult reason) if (close) { + // mark the RemoteAddress as potential problematic if Basic128Rsa15 + if ((SecurityPolicyUri == SecurityPolicies.Basic128Rsa15) && + (reason.StatusCode == StatusCodes.BadSecurityChecksFailed || reason.StatusCode == StatusCodes.BadTcpMessageTypeInvalid)) + { + var tcpTransportListener = m_listener as TcpTransportListener; + if (tcpTransportListener != null) + { + tcpTransportListener.MarkAsPotentialProblematic + (((IPEndPoint)Socket.RemoteEndpoint).Address); + } + } + // close channel immediately. ChannelFaulted(); } diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs index 2753bdd72..27c51eb71 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs @@ -13,6 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; @@ -37,6 +38,233 @@ public override ITransportListener Create() } } + /// + /// Represents a potential problematic ActiveClient + /// + public class ActiveClient + { + #region Properties + /// + /// Time of the last recorded problematic action + /// + public int LastActionTicks + { + get + { + return m_lastActionTicks; + } + set + { + m_lastActionTicks = value; + } + } + + /// + /// Counter for number of recorded potential problematic actions + /// + public int ActiveActionCount + { + get + { + return m_actionCount; + } + set + { + m_actionCount = value; + } + } + + /// + /// Ticks until the client is Blocked + /// + public int BlockedUntilTicks + { + get + { + return m_blockedUntilTicks; + } + set + { + m_blockedUntilTicks = value; + } + } + #endregion + + #region Private members + int m_lastActionTicks; + int m_actionCount; + int m_blockedUntilTicks; + #endregion + } + + /// + /// Manages clients with potential problematic activities + /// + public class ActiveClientTracker : IDisposable + { + #region Public + /// + /// Constructor + /// + public ActiveClientTracker() + { + m_cleanupTimer = new Timer(CleanupExpiredEntries, null, m_kCleanupIntervalMs, m_kCleanupIntervalMs); + } + + /// + /// Checks if an IP address is currently blocked + /// + /// + /// + public bool IsBlocked(IPAddress ipAddress) + { + if (m_activeClients.TryGetValue(ipAddress, out ActiveClient client)) + { + int currentTicks = HiResClock.TickCount; + return IsBlockedTicks(client.BlockedUntilTicks, currentTicks); + } + return false; + } + + /// + /// Adds a potential problematic action entry for a client + /// + /// + public void AddClientAction(IPAddress ipAddress) + { + int currentTicks = HiResClock.TickCount; + + m_activeClients.AddOrUpdate(ipAddress, + // If client is new , create a new entry + key => new ActiveClient { + LastActionTicks = currentTicks, + ActiveActionCount = 1, + BlockedUntilTicks = 0 + }, + // If the client exists, update its entry + (key, existingEntry) => { + // If IP currently blocked simply do nothing + if (IsBlockedTicks(existingEntry.BlockedUntilTicks, currentTicks)) + { + return existingEntry; + } + + // Elapsed time since last recorded action + int elapsedSinceLastRecAction = currentTicks - existingEntry.LastActionTicks; + + if (elapsedSinceLastRecAction <= m_kActionsIntervalMs) + { + existingEntry.ActiveActionCount++; + + if (existingEntry.ActiveActionCount > m_kNrActionsTillBlock) + { + // Block the IP + existingEntry.BlockedUntilTicks = currentTicks + m_kBlockDurationMs; + Utils.LogError("RemoteClient IPAddress: {0} blocked for {1} ms due to exceeding {2} actions under {3} ms ", + ipAddress.ToString(), + m_kBlockDurationMs, + m_kNrActionsTillBlock, + m_kActionsIntervalMs); + + } + } + else + { + // Reset the count as the last action was outside the interval + existingEntry.ActiveActionCount = 1; + } + + existingEntry.LastActionTicks = currentTicks; + + return existingEntry; + } + ); + } + + /// + /// Dispose the cleanup timer + /// + public void Dispose() + { + m_cleanupTimer?.Dispose(); + } + + #endregion + #region Private methods + + /// + /// Periodically cleans up expired active client entries to avoid memory leak and unblock clients whose duration has expired. + /// + /// + private void CleanupExpiredEntries(object state) + { + int currentTicks = HiResClock.TickCount; + + foreach (var entry in m_activeClients) + { + IPAddress clientIp = entry.Key; + ActiveClient rClient = entry.Value; + + // Unblock client if blocking duration has been exceeded + if (rClient.BlockedUntilTicks != 0 && !IsBlockedTicks(rClient.BlockedUntilTicks, currentTicks)) + { + rClient.BlockedUntilTicks = 0; + rClient.ActiveActionCount = 0; + Utils.LogDebug("Active Client with IP {0} is now unblocked, blocking duration of {1} ms has been exceeded", + clientIp.ToString(), + m_kBlockDurationMs); + } + + // Remove clients that haven't had any potential problematic actions in the last m_kEntryExpirationMs interval + int elapsedSinceBadActionTicks = currentTicks - rClient.LastActionTicks; + if (elapsedSinceBadActionTicks > m_kEntryExpirationMs) + { + // Even if TryRemove fails it will most probably succeed at the next execution + if (m_activeClients.TryRemove(clientIp, out _)) + { + Utils.LogDebug("Active Client with IP {0} is not tracked any longer, hasn't had actions for more than {1} ms", + clientIp.ToString(), + m_kEntryExpirationMs); + } + } + } + } + + /// + /// Determines if the IP is currently blocked based on the block expiration ticks and current ticks + /// + /// + /// + /// + private bool IsBlockedTicks(int blockedUntilTicks, int currentTicks) + { + if (blockedUntilTicks == 0) + { + return false; + } + // C# signed arithmetic + int diff = blockedUntilTicks - currentTicks; + // If currentTicks < blockedUntilTicks then it is still blocked + // Works even if TickCount has wrapped around due to C# signed integer arithmetic + return diff > 0; + } + + + #endregion + #region Private members + private ConcurrentDictionary m_activeClients = new ConcurrentDictionary(); + + private const int m_kActionsIntervalMs = 10_000; + private const int m_kNrActionsTillBlock = 3; + + private const int m_kBlockDurationMs = 30_000; // 30 seconds + private const int m_kCleanupIntervalMs = 15_000; + private const int m_kEntryExpirationMs = 600_000; // 10 minutes + + private Timer m_cleanupTimer; + #endregion + } + /// /// Manages the transport for a UA TCP server. /// @@ -331,6 +559,12 @@ public void Start() { lock (m_lock) { + // Track potential problematic client behavior only if Basic128Rsa15 security policy is offered + if (m_descriptions != null && m_descriptions.Any(d => d.SecurityPolicyUri == SecurityPolicies.Basic128Rsa15)) + { + m_activeClientTracker = new ActiveClientTracker(); + } + // ensure a valid port. int port = m_uri.Port; @@ -505,16 +739,44 @@ public void CertificateUpdate( } #endregion + #region Internal + /// + /// Mark a remote endpoint as potential problematic + /// + /// + internal void MarkAsPotentialProblematic(IPAddress remoteEndpoint) + { + Utils.LogDebug("MarkClientAsPotentialProblematic address: {0} ", remoteEndpoint.ToString()); + m_activeClientTracker?.AddClientAction(remoteEndpoint); + } + #endregion + #region Socket Event Handler /// /// Handles a new connection. /// private void OnAccept(object sender, SocketAsyncEventArgs e) { + TcpListenerChannel channel = null; bool repeatAccept = false; do { + bool isBlocked = false; + + // Track potential problematic client behavior only if Basic128Rsa15 security policy is offered + if (m_activeClientTracker != null) + { + // Filter out the Remote IP addresses which are detected with potential problematic behavior + IPAddress ipAddress = ((IPEndPoint)e?.AcceptSocket?.RemoteEndPoint)?.Address; + if (ipAddress != null && m_activeClientTracker.IsBlocked(ipAddress)) + { + Utils.LogDebug("OnAccept: RemoteEndpoint address: {0} refused access for behaving as potential problematic ", + ((IPEndPoint)e.AcceptSocket.RemoteEndPoint).Address.ToString()); + isBlocked = true; + } + } + repeatAccept = false; lock (m_lock) { @@ -526,7 +788,7 @@ private void OnAccept(object sender, SocketAsyncEventArgs e) } var channels = m_channels; - if (channels != null) + if (channels != null && !isBlocked) { // TODO: .Count is flagged as hotpath, implement separate counter int channelCount = channels.Count; @@ -821,6 +1083,8 @@ private void SetUri(Uri baseAddress, string relativeAddress) private int m_inactivityDetectPeriod; private Timer m_inactivityDetectionTimer; private int m_maxChannelCount; + + private ActiveClientTracker m_activeClientTracker; #endregion } From 2f21ca885356e775aec06d81a768eac0cc5c6375 Mon Sep 17 00:00:00 2001 From: romanett Date: Thu, 28 Nov 2024 09:52:02 +0100 Subject: [PATCH 12/13] [Server] ValidateRolePermissions of MonitoredItems based of the saved user identity to allow validation when no session is present (#2832) * ValidateRolePermissions for MIs montioring the Value of a Node * allow validation of user identity also in case of disconnected session --- .../DataChangeMonitoredItem.cs | 12 +++++++++ .../Diagnostics/CustomNodeManager.cs | 2 +- .../NodeManager/MasterNodeManager.cs | 4 +-- .../Opc.Ua.Server/Server/OperationContext.cs | 1 + .../Subscription/IMonitoredItem.cs | 5 ++++ .../Subscription/MonitoredItem.cs | 13 ++++++++++ .../Subscription/Subscription.cs | 25 +++++++++++-------- .../Subscription/SubscriptionManager.cs | 4 +-- 8 files changed, 51 insertions(+), 15 deletions(-) diff --git a/Applications/Quickstarts.Servers/SampleNodeManager/DataChangeMonitoredItem.cs b/Applications/Quickstarts.Servers/SampleNodeManager/DataChangeMonitoredItem.cs index c03661639..ab52ac6ff 100644 --- a/Applications/Quickstarts.Servers/SampleNodeManager/DataChangeMonitoredItem.cs +++ b/Applications/Quickstarts.Servers/SampleNodeManager/DataChangeMonitoredItem.cs @@ -340,6 +340,18 @@ public Session Session } } + /// + /// The monitored items owner identity. + /// + public IUserIdentity EffectiveIdentity + { + get + { + ISubscription subscription = m_subscription; + return subscription?.EffectiveIdentity; + } + } + /// /// The identifier for the subscription that the monitored item belongs to. /// diff --git a/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs index 5581456b3..fa25395da 100644 --- a/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs @@ -3752,7 +3752,7 @@ protected virtual void OnMonitoredItemCreated( /// public ServiceResult ValidateRolePermissions(OperationContext operationContext, NodeId nodeId, PermissionType requestedPermission) { - if (operationContext.Session == null || requestedPermission == PermissionType.None) + if (requestedPermission == PermissionType.None) { // no permission is required hence the validation passes. return StatusCodes.Good; diff --git a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs index 90046cefc..6792c19c8 100644 --- a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs +++ b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs @@ -3233,7 +3233,7 @@ protected static ServiceResult ValidateAccessRestrictions(OperationContext conte /// protected internal static ServiceResult ValidateRolePermissions(OperationContext context, NodeMetadata nodeMetadata, PermissionType requestedPermission) { - if (context.Session == null || nodeMetadata == null || requestedPermission == PermissionType.None) + if (nodeMetadata == null || requestedPermission == PermissionType.None) { // no permission is required hence the validation passes return StatusCodes.Good; @@ -3323,7 +3323,7 @@ protected internal static ServiceResult ValidateRolePermissions(OperationContext } } - var currentRoleIds = context.Session.Identity.GrantedRoleIds; + var currentRoleIds = context.UserIdentity.GrantedRoleIds; if (currentRoleIds == null || currentRoleIds.Count == 0) { return ServiceResult.Create(StatusCodes.BadUserAccessDenied, "Current user has no granted role."); diff --git a/Libraries/Opc.Ua.Server/Server/OperationContext.cs b/Libraries/Opc.Ua.Server/Server/OperationContext.cs index d31a996cc..7d7ec7f41 100644 --- a/Libraries/Opc.Ua.Server/Server/OperationContext.cs +++ b/Libraries/Opc.Ua.Server/Server/OperationContext.cs @@ -127,6 +127,7 @@ public OperationContext(IMonitoredItem monitoredItem) if (monitoredItem == null) throw new ArgumentNullException(nameof(monitoredItem)); m_channelContext = null; + m_identity = monitoredItem.EffectiveIdentity; m_session = monitoredItem.Session; if (m_session != null) diff --git a/Libraries/Opc.Ua.Server/Subscription/IMonitoredItem.cs b/Libraries/Opc.Ua.Server/Subscription/IMonitoredItem.cs index 6f8730294..7b05a0d64 100644 --- a/Libraries/Opc.Ua.Server/Subscription/IMonitoredItem.cs +++ b/Libraries/Opc.Ua.Server/Subscription/IMonitoredItem.cs @@ -52,6 +52,11 @@ public interface IMonitoredItem /// Session Session { get; } + /// + /// The monitored items owner identity. + /// + IUserIdentity EffectiveIdentity { get; } + /// /// The identifier for the item that is unique within the server. /// diff --git a/Libraries/Opc.Ua.Server/Subscription/MonitoredItem.cs b/Libraries/Opc.Ua.Server/Subscription/MonitoredItem.cs index cefd14eef..1c74cdf50 100644 --- a/Libraries/Opc.Ua.Server/Subscription/MonitoredItem.cs +++ b/Libraries/Opc.Ua.Server/Subscription/MonitoredItem.cs @@ -398,6 +398,19 @@ public Session Session } } } + /// + /// The monitored items owner identity. + /// + public IUserIdentity EffectiveIdentity + { + get + { + lock (m_lock) + { + return m_subscription?.EffectiveIdentity; + } + } + } /// /// The identifier for the item that is unique within the server. diff --git a/Libraries/Opc.Ua.Server/Subscription/Subscription.cs b/Libraries/Opc.Ua.Server/Subscription/Subscription.cs index 34557b237..3ace961e9 100644 --- a/Libraries/Opc.Ua.Server/Subscription/Subscription.cs +++ b/Libraries/Opc.Ua.Server/Subscription/Subscription.cs @@ -49,6 +49,11 @@ public interface ISubscription /// Session Session { get; } + /// + /// The subscriptions owner identity. + /// + IUserIdentity EffectiveIdentity { get; } + /// /// The identifier for the item that is unique within the server. /// @@ -208,6 +213,14 @@ public uint Id get { return m_id; } } + /// + /// The subscriptions owner identity. + /// + public IUserIdentity EffectiveIdentity + { + get { return (m_session != null) ? m_session.EffectiveIdentity : m_savedOwnerIdentity; } + } + /// /// Queues an item that is ready to publish. /// @@ -255,14 +268,6 @@ public NodeId SessionId } } - /// - /// The owner identity. - /// - public UserIdentityToken OwnerIdentity - { - get { return (m_session != null) ? m_session.IdentityToken : m_savedOwnerIdentity; } - } - /// /// Gets the lock that must be acquired before accessing the contents of the Diagnostics property. /// @@ -594,7 +599,7 @@ public void SessionClosed() { if (m_session != null) { - m_savedOwnerIdentity = m_session.IdentityToken; + m_savedOwnerIdentity = m_session.EffectiveIdentity; m_session = null; } } @@ -2414,7 +2419,7 @@ private void TraceState(LogLevel logLevel, TraceStateId id, string context) private IServerInternal m_server; private Session m_session; private uint m_id; - private UserIdentityToken m_savedOwnerIdentity; + private IUserIdentity m_savedOwnerIdentity; private double m_publishingInterval; private uint m_maxLifetimeCount; private uint m_maxKeepAliveCount; diff --git a/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs b/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs index 5e0275441..ff01fe178 100644 --- a/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs +++ b/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs @@ -1223,11 +1223,11 @@ public void TransferSubscriptions( } // get the identity of the current or last owner - UserIdentityToken ownerIdentity = subscription.OwnerIdentity; + UserIdentityToken ownerIdentity = subscription.EffectiveIdentity.GetIdentityToken(); // Validate the identity of the user who owns/owned the subscription // is the same as the new owner. - bool validIdentity = Utils.IsEqualUserIdentity(ownerIdentity, context.Session.IdentityToken); + bool validIdentity = Utils.IsEqualUserIdentity(ownerIdentity, context.Session.EffectiveIdentity.GetIdentityToken()); // Test if anonymous user is using a // secure session using Sign or SignAndEncrypt From f76c457c72af1b3a3772a82f9bb9ea49d7f86f07 Mon Sep 17 00:00:00 2001 From: Martin Regen <7962757+mregen@users.noreply.github.com> Date: Thu, 28 Nov 2024 12:57:10 +0100 Subject: [PATCH 13/13] Support .NET 9.0 build (#2865) * support .NET 9.0 build * provide a helper to use the X509CertificateLoader also on older .NET versions --- .azurepipelines/ci.yml | 5 + .azurepipelines/preview.yml | 4 +- .azurepipelines/signlistDebug.txt | 10 ++ .azurepipelines/signlistRelease.txt | 10 ++ .azurepipelines/test.yml | 4 +- .azurepipelines/testcc.yml | 4 +- .github/workflows/buildandtest.yml | 8 +- .../ConsoleReferenceClient.csproj | 6 +- .../ConsoleReferenceServer.csproj | 6 +- Libraries/Opc.Ua.Client/Session/Session.cs | 4 +- .../ApplicationInstance.cs | 7 +- .../ServerPushConfigurationClient.cs | 2 +- .../ApplicationsNodeManager.cs | 8 +- .../CertificateGroup.cs | 8 +- .../ICertificateGroup.cs | 2 +- .../MqttClientProtocolConfiguration.cs | 12 +-- .../Transport/MqttPubSubConnection.cs | 6 +- .../X509Certificate/X509CertificateLoader.cs | 91 +++++++++++++++++++ .../X509Certificate/X509PfxUtils.cs | 3 +- .../Configuration/ConfigurationNodeManager.cs | 6 +- .../Opc.Ua.Server/Configuration/TrustList.cs | 6 +- Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 10 +- .../Certificates/CertificateFactory.cs | 31 ++++--- .../Certificates/CertificateValidator.cs | 8 +- .../Certificates/DirectoryCertificateStore.cs | 7 +- .../X509CertificateStore.cs | 2 +- .../Security/Certificates/X509Utils.cs | 2 +- Tests/Opc.Ua.Client.Tests/ClientTest.cs | 2 +- .../ApplicationInstanceTests.cs | 8 +- .../Certificates/CertificateFactoryTest.cs | 10 +- .../Certificates/CertificateStoreTest.cs | 4 +- .../Certificates/CertificateValidatorTest.cs | 60 ++++++------ Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 4 +- .../GlobalDiscoveryTestClient.cs | 2 +- Tests/Opc.Ua.Gds.Tests/PushTest.cs | 14 +-- Tests/Opc.Ua.Gds.Tests/X509TestUtils.cs | 8 +- .../CRLTests.cs | 4 +- .../CertificateTestUtils.cs | 2 +- .../CertificateTestsForECDsa.cs | 6 +- .../CertificateTestsForRSA.cs | 10 +- .../ExtensionTests.cs | 2 +- Tests/customtest.bat | 8 +- azure-pipelines.yml | 10 ++ targets.props | 64 +++++++++---- 44 files changed, 326 insertions(+), 164 deletions(-) create mode 100644 Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509CertificateLoader.cs diff --git a/.azurepipelines/ci.yml b/.azurepipelines/ci.yml index d6dccac88..7229e8aac 100644 --- a/.azurepipelines/ci.yml +++ b/.azurepipelines/ci.yml @@ -33,6 +33,11 @@ jobs: - task: NuGetToolInstaller@1 inputs: versionSpec: '>=5.8.x' + - task: UseDotNet@2 + displayName: 'Install .NET 9.0' + inputs: + packageType: 'sdk' + version: '9.0.x' - task: PowerShell@2 displayName: Versioning inputs: diff --git a/.azurepipelines/preview.yml b/.azurepipelines/preview.yml index ac06f4d70..cb31bbb9b 100644 --- a/.azurepipelines/preview.yml +++ b/.azurepipelines/preview.yml @@ -34,10 +34,10 @@ jobs: value: '.azurepipelines/signlist${{parameters.config}}.txt' steps: - task: UseDotNet@2 - displayName: 'Install .NET 8.0' + displayName: 'Install .NET 9.0' inputs: packageType: 'sdk' - version: '8.0.x' + version: '9.0.x' includePreviewVersions: false - task: DownloadSecureFile@1 name: strongnamefile diff --git a/.azurepipelines/signlistDebug.txt b/.azurepipelines/signlistDebug.txt index 077e6c470..9e75cc3a4 100644 --- a/.azurepipelines/signlistDebug.txt +++ b/.azurepipelines/signlistDebug.txt @@ -4,56 +4,66 @@ Stack\Opc.Ua.Core\bin\Debug\net472\Opc.Ua.Core.dll Stack\Opc.Ua.Core\bin\Debug\net48\Opc.Ua.Core.dll Stack\Opc.Ua.Core\bin\Debug\net6.0\Opc.Ua.Core.dll Stack\Opc.Ua.Core\bin\Debug\net8.0\Opc.Ua.Core.dll +Stack\Opc.Ua.Core\bin\Debug\net9.0\Opc.Ua.Core.dll Stack\Opc.Ua.Bindings.Https\bin\Debug\netstandard2.0\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Debug\netstandard2.1\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Debug\net472\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Debug\net48\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Debug\net6.0\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Debug\net8.0\Opc.Ua.Bindings.Https.dll +Stack\Opc.Ua.Bindings.Https\bin\Debug\net9.0\Opc.Ua.Bindings.Https.dll Libraries\Opc.Ua.Server\bin\Debug\netstandard2.0\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Debug\netstandard2.1\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Debug\net472\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Debug\net48\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Debug\net6.0\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Debug\net8.0\Opc.Ua.Server.dll +Libraries\Opc.Ua.Server\bin\Debug\net9.0\Opc.Ua.Server.dll Libraries\Opc.Ua.Client\bin\Debug\netstandard2.0\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Debug\netstandard2.1\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Debug\net472\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Debug\net48\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Debug\net6.0\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Debug\net8.0\Opc.Ua.Client.dll +Libraries\Opc.Ua.Client\bin\Debug\net9.0\Opc.Ua.Client.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\netstandard2.1\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net462\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net472\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net48\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net6.0\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net8.0\Opc.Ua.Client.ComplexTypes.dll +Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net9.0\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Configuration\bin\Debug\netstandard2.0\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Debug\netstandard2.1\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Debug\net472\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Debug\net48\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Debug\net6.0\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Debug\net8.0\Opc.Ua.Configuration.dll +Libraries\Opc.Ua.Configuration\bin\Debug\net9.0\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Debug\netstandard2.0\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Debug\netstandard2.1\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Debug\net472\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Debug\net48\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Debug\net6.0\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Debug\net8.0\Opc.Ua.Gds.Client.Common.dll +Libraries\Opc.Ua.Gds.Client.Common\bin\Debug\net9.0\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Debug\netstandard2.0\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Debug\netstandard2.1\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Debug\net472\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Debug\net48\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Debug\net6.0\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Debug\net8.0\Opc.Ua.Gds.Server.Common.dll +Libraries\Opc.Ua.Gds.Server.Common\bin\Debug\net9.0\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Security.Certificates\bin\Debug\netstandard2.0\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Debug\netstandard2.1\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Debug\net472\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Debug\net48\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Debug\net6.0\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Debug\net8.0\Opc.Ua.Security.Certificates.dll +Libraries\Opc.Ua.Security.Certificates\bin\Debug\net9.0\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.PubSub\bin\Debug\netstandard2.1\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Debug\net472\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Debug\net48\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Debug\net6.0\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Debug\net8.0\Opc.Ua.PubSub.dll +Libraries\Opc.Ua.PubSub\bin\Debug\net9.0\Opc.Ua.PubSub.dll diff --git a/.azurepipelines/signlistRelease.txt b/.azurepipelines/signlistRelease.txt index c42668f93..2766d2698 100644 --- a/.azurepipelines/signlistRelease.txt +++ b/.azurepipelines/signlistRelease.txt @@ -4,56 +4,66 @@ Stack\Opc.Ua.Core\bin\Release\net472\Opc.Ua.Core.dll Stack\Opc.Ua.Core\bin\Release\net48\Opc.Ua.Core.dll Stack\Opc.Ua.Core\bin\Release\net6.0\Opc.Ua.Core.dll Stack\Opc.Ua.Core\bin\Release\net8.0\Opc.Ua.Core.dll +Stack\Opc.Ua.Core\bin\Release\net9.0\Opc.Ua.Core.dll Stack\Opc.Ua.Bindings.Https\bin\Release\netstandard2.0\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Release\netstandard2.1\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Release\net472\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Release\net48\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Release\net6.0\Opc.Ua.Bindings.Https.dll Stack\Opc.Ua.Bindings.Https\bin\Release\net8.0\Opc.Ua.Bindings.Https.dll +Stack\Opc.Ua.Bindings.Https\bin\Release\net9.0\Opc.Ua.Bindings.Https.dll Libraries\Opc.Ua.Server\bin\Release\netstandard2.0\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Release\netstandard2.1\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Release\net472\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Release\net48\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Release\net6.0\Opc.Ua.Server.dll Libraries\Opc.Ua.Server\bin\Release\net8.0\Opc.Ua.Server.dll +Libraries\Opc.Ua.Server\bin\Release\net9.0\Opc.Ua.Server.dll Libraries\Opc.Ua.Client\bin\Release\netstandard2.0\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Release\netstandard2.1\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Release\net472\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Release\net48\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Release\net6.0\Opc.Ua.Client.dll Libraries\Opc.Ua.Client\bin\Release\net8.0\Opc.Ua.Client.dll +Libraries\Opc.Ua.Client\bin\Release\net9.0\Opc.Ua.Client.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\netstandard2.1\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net462\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net472\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net48\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net6.0\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net8.0\Opc.Ua.Client.ComplexTypes.dll +Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net9.0\Opc.Ua.Client.ComplexTypes.dll Libraries\Opc.Ua.Configuration\bin\Release\netstandard2.0\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Release\netstandard2.1\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Release\net472\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Release\net48\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Release\net6.0\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Configuration\bin\Release\net8.0\Opc.Ua.Configuration.dll +Libraries\Opc.Ua.Configuration\bin\Release\net9.0\Opc.Ua.Configuration.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Release\netstandard2.0\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Release\netstandard2.1\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Release\net472\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Release\net48\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Release\net6.0\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Client.Common\bin\Release\net8.0\Opc.Ua.Gds.Client.Common.dll +Libraries\Opc.Ua.Gds.Client.Common\bin\Release\net9.0\Opc.Ua.Gds.Client.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Release\netstandard2.0\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Release\netstandard2.1\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Release\net472\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Release\net48\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Release\net6.0\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Gds.Server.Common\bin\Release\net8.0\Opc.Ua.Gds.Server.Common.dll +Libraries\Opc.Ua.Gds.Server.Common\bin\Release\net9.0\Opc.Ua.Gds.Server.Common.dll Libraries\Opc.Ua.Security.Certificates\bin\Release\netstandard2.0\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Release\netstandard2.1\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Release\net472\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Release\net48\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Release\net6.0\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.Security.Certificates\bin\Release\net8.0\Opc.Ua.Security.Certificates.dll +Libraries\Opc.Ua.Security.Certificates\bin\Release\net9.0\Opc.Ua.Security.Certificates.dll Libraries\Opc.Ua.PubSub\bin\Release\netstandard2.1\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Release\net472\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Release\net48\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Release\net6.0\Opc.Ua.PubSub.dll Libraries\Opc.Ua.PubSub\bin\Release\net8.0\Opc.Ua.PubSub.dll +Libraries\Opc.Ua.PubSub\bin\Release\net9.0\Opc.Ua.PubSub.dll diff --git a/.azurepipelines/test.yml b/.azurepipelines/test.yml index 12f7449ad..7d926e294 100644 --- a/.azurepipelines/test.yml +++ b/.azurepipelines/test.yml @@ -47,10 +47,10 @@ jobs: packageType: 'sdk' version: '6.0.x' - task: UseDotNet@2 - displayName: 'Install .NET 8.0' + displayName: 'Install .NET 9.0' inputs: packageType: 'sdk' - version: '8.0.x' + version: '9.0.x' - task: NuGetToolInstaller@1 inputs: versionSpec: '>=5.8.x' diff --git a/.azurepipelines/testcc.yml b/.azurepipelines/testcc.yml index 4f2fdb8ad..67d7861e6 100644 --- a/.azurepipelines/testcc.yml +++ b/.azurepipelines/testcc.yml @@ -30,10 +30,10 @@ jobs: packageType: 'sdk' version: '6.0.x' - task: UseDotNet@2 - displayName: 'Install .NET 8.0' + displayName: 'Install .NET 9.0' inputs: packageType: 'sdk' - version: '8.0.x' + version: '9.0.x' - task: NuGetToolInstaller@1 inputs: versionSpec: '>=5.8.x' diff --git a/.github/workflows/buildandtest.yml b/.github/workflows/buildandtest.yml index 00533e258..001359d2b 100644 --- a/.github/workflows/buildandtest.yml +++ b/.github/workflows/buildandtest.yml @@ -1,4 +1,4 @@ -name: Build and Test .NET 8.0 +name: Build and Test .NET 9.0 on: push: @@ -22,10 +22,10 @@ jobs: os: [ubuntu-latest, windows-latest, macOS-latest] csproj: [Security.Certificates, Core, Server, Client, Client.ComplexTypes, PubSub, Configuration, Gds] include: - - framework: 'net8.0' - dotnet-version: '8.0.x' + - framework: 'net9.0' + dotnet-version: '9.0.x' configuration: 'Release' - customtesttarget: net8.0 + customtesttarget: net9.0 env: OS: ${{ matrix.os }} diff --git a/Applications/ConsoleReferenceClient/ConsoleReferenceClient.csproj b/Applications/ConsoleReferenceClient/ConsoleReferenceClient.csproj index bdfd44f97..04bf1da45 100644 --- a/Applications/ConsoleReferenceClient/ConsoleReferenceClient.csproj +++ b/Applications/ConsoleReferenceClient/ConsoleReferenceClient.csproj @@ -20,9 +20,9 @@ - - - + + + diff --git a/Applications/ConsoleReferenceServer/ConsoleReferenceServer.csproj b/Applications/ConsoleReferenceServer/ConsoleReferenceServer.csproj index 0fb89a57e..0c9ea93ce 100644 --- a/Applications/ConsoleReferenceServer/ConsoleReferenceServer.csproj +++ b/Applications/ConsoleReferenceServer/ConsoleReferenceServer.csproj @@ -29,9 +29,9 @@ - - - + + + diff --git a/Libraries/Opc.Ua.Client/Session/Session.cs b/Libraries/Opc.Ua.Client/Session/Session.cs index e9f7052bf..6efed4ab8 100644 --- a/Libraries/Opc.Ua.Client/Session/Session.cs +++ b/Libraries/Opc.Ua.Client/Session/Session.cs @@ -1076,7 +1076,7 @@ public static async Task CreateChannelAsync( endpoint.Description.ServerCertificate.Length > 0) { configuration.CertificateValidator?.ValidateDomains( - new X509Certificate2(endpoint.Description.ServerCertificate), + X509CertificateLoader.LoadCertificate(endpoint.Description.ServerCertificate), endpoint); checkDomain = false; } @@ -1440,7 +1440,7 @@ public bool ApplySessionConfiguration(SessionConfiguration sessionConfiguration) byte[] serverCertificate = m_endpoint.Description?.ServerCertificate; m_sessionName = sessionConfiguration.SessionName; - m_serverCertificate = serverCertificate != null ? new X509Certificate2(serverCertificate) : null; + m_serverCertificate = serverCertificate != null ? X509CertificateLoader.LoadCertificate(serverCertificate) : null; m_identity = sessionConfiguration.Identity; m_checkDomain = sessionConfiguration.CheckDomain; m_serverNonce = sessionConfiguration.ServerNonce; diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index 1734790b1..6ee1b5497 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -631,7 +631,9 @@ private async Task CheckApplicationInstanceCertificateAsync( { // validate certificate. configuration.CertificateValidator.CertificateValidation += certValidator.OnCertificateValidation; - await configuration.CertificateValidator.ValidateAsync(certificate.HasPrivateKey ? new X509Certificate2(certificate.RawData) : certificate, ct).ConfigureAwait(false); + await configuration.CertificateValidator.ValidateAsync( + certificate.HasPrivateKey ? + X509CertificateLoader.LoadCertificate(certificate.RawData) : certificate, ct).ConfigureAwait(false); } catch (Exception ex) { @@ -995,7 +997,8 @@ private static async Task AddToTrustedStoreAsync(ApplicationConfiguration config } // add new certificate. - X509Certificate2 publicKey = new X509Certificate2(certificate.RawData); + X509Certificate2 publicKey = X509CertificateLoader.LoadCertificate(certificate.RawData); + await store.Add(publicKey).ConfigureAwait(false); Utils.LogInfo("Added application certificate to trusted peer store."); diff --git a/Libraries/Opc.Ua.Gds.Client.Common/ServerPushConfigurationClient.cs b/Libraries/Opc.Ua.Gds.Client.Common/ServerPushConfigurationClient.cs index 40940834f..11e3d973b 100644 --- a/Libraries/Opc.Ua.Gds.Client.Common/ServerPushConfigurationClient.cs +++ b/Libraries/Opc.Ua.Gds.Client.Common/ServerPushConfigurationClient.cs @@ -721,7 +721,7 @@ public X509Certificate2Collection GetRejectedList() X509Certificate2Collection collection = new X509Certificate2Collection(); foreach (var rawCertificate in rawCertificates) { - collection.Add(new X509Certificate2(rawCertificate)); + collection.Add(X509CertificateLoader.LoadCertificate(rawCertificate)); } return collection; } diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index bd3ca1332..fb4238de9 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -234,7 +234,7 @@ private ICertificateGroup GetGroupForCertificate(byte[] certificate) { if (certificate != null && certificate.Length > 0) { - using (var x509 = new X509Certificate2(certificate)) + using (var x509 = X509CertificateLoader.LoadCertificate(certificate)) { foreach (var certificateGroup in m_certificateGroups.Values) { @@ -258,7 +258,7 @@ private async Task RevokeCertificateAsync(byte[] certificate) if (certificateGroup != null) { - using (X509Certificate2 x509 = new X509Certificate2(certificate)) + using (X509Certificate2 x509 = X509CertificateLoader.LoadCertificate(certificate)) { try { @@ -689,7 +689,7 @@ private ServiceResult OnCheckRevocationStatus( } } - using (var x509 = new X509Certificate2(certificate)) + using (var x509 = X509CertificateLoader.LoadCertificate(certificate)) { if (chain.Build(x509)) { @@ -1305,7 +1305,7 @@ out privateKeyPassword } else { - certificate = new X509Certificate2(signedCertificate); + certificate = X509CertificateLoader.LoadCertificate(signedCertificate); } // TODO: return chain, verify issuer chain cert is up to date, otherwise update local chain diff --git a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs index 1f1b6553c..8ce0249f2 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs @@ -119,7 +119,7 @@ public virtual async Task Init() Configuration.CACertificateLifetime ); X509Certificate2 newCertificate = await CreateCACertificateAsync(SubjectName).ConfigureAwait(false); - Certificate = new X509Certificate2(newCertificate.RawData); + Certificate = X509CertificateLoader.LoadCertificate(newCertificate.RawData); Utils.LogCertificate(Utils.TraceMasks.Security, "Created CA certificate: ", Certificate); } } @@ -173,7 +173,7 @@ public virtual async Task NewKeyPairRequestAsync( { throw new ServiceResultException(StatusCodes.BadInvalidArgument, "Invalid private key format"); } - return new X509Certificate2KeyPair(new X509Certificate2(certificate.RawData), privateKeyFormat, privateKey); + return new X509Certificate2KeyPair(X509CertificateLoader.LoadCertificate(certificate.RawData), privateKeyFormat, privateKey); } } @@ -336,7 +336,7 @@ string subjectName { // save only public key - Certificate = new X509Certificate2(newCertificate.RawData); + Certificate = X509CertificateLoader.LoadCertificate(newCertificate.RawData); // initialize revocation list X509CRL crl = await RevokeCertificateAsync(AuthoritiesStore, newCertificate, null).ConfigureAwait(false); @@ -494,7 +494,7 @@ protected async Task UpdateAuthorityCertInCertificateStore(CertificateStoreIdent X509Certificate2Collection certs = await trustedOrIssuerStore.FindByThumbprint(certificate.Thumbprint).ConfigureAwait(false); if (certs.Count == 0) { - using (var x509 = new X509Certificate2(certificate.RawData)) + using (var x509 = X509CertificateLoader.LoadCertificate(certificate.RawData)) { await trustedOrIssuerStore.Add(x509).ConfigureAwait(false); } diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ICertificateGroup.cs b/Libraries/Opc.Ua.Gds.Server.Common/ICertificateGroup.cs index d1eac1926..787906ad6 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ICertificateGroup.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ICertificateGroup.cs @@ -44,7 +44,7 @@ public X509Certificate2KeyPair(X509Certificate2 certificate, string privateKeyFo { if (certificate.HasPrivateKey) { - certificate = new X509Certificate2(certificate.RawData); + certificate = X509CertificateLoader.LoadCertificate(certificate.RawData); } Certificate = certificate; PrivateKeyFormat = privateKeyFormat; diff --git a/Libraries/Opc.Ua.PubSub/Transport/MqttClientProtocolConfiguration.cs b/Libraries/Opc.Ua.PubSub/Transport/MqttClientProtocolConfiguration.cs index a5ea6a729..ba75b7b04 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/MqttClientProtocolConfiguration.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/MqttClientProtocolConfiguration.cs @@ -43,8 +43,8 @@ public class MqttTlsCertificates { #region Private menbers - private X509Certificate m_caCertificate; - private X509Certificate m_clientCertificate; + private X509Certificate2 m_caCertificate; + private X509Certificate2 m_clientCertificate; #endregion Private menbers @@ -64,11 +64,11 @@ public MqttTlsCertificates(string caCertificatePath = null, if (!string.IsNullOrEmpty(CaCertificatePath)) { - m_caCertificate = X509Certificate.CreateFromCertFile(CaCertificatePath); + m_caCertificate = X509CertificateLoader.LoadCertificateFromFile(CaCertificatePath); } if (!string.IsNullOrEmpty(clientCertificatePath)) { - m_clientCertificate = new X509Certificate2(clientCertificatePath, ClientCertificatePassword); + m_clientCertificate = X509CertificateLoader.LoadPkcs12FromFile(clientCertificatePath, ClientCertificatePassword); } KeyValuePairs = new KeyValuePairCollection(); @@ -105,11 +105,11 @@ public MqttTlsCertificates(KeyValuePairCollection keyValuePairs) if (!string.IsNullOrEmpty(CaCertificatePath)) { - m_caCertificate = X509Certificate.CreateFromCertFile(CaCertificatePath); + m_caCertificate = X509CertificateLoader.LoadCertificateFromFile(CaCertificatePath); } if (!string.IsNullOrEmpty(ClientCertificatePath)) { - m_clientCertificate = new X509Certificate2(ClientCertificatePath, ClientCertificatePassword); + m_clientCertificate = X509CertificateLoader.LoadPkcs12FromFile(ClientCertificatePath, ClientCertificatePassword); } } diff --git a/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs b/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs index 61fcaf46c..bde6ae01a 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs @@ -749,9 +749,9 @@ private MqttClientOptions GetMqttClientOptions() var x509Certificate2s = new List(); if (mqttTlsOptions?.Certificates != null) { - foreach (X509Certificate x509cert in mqttTlsOptions?.Certificates.X509Certificates) + foreach (X509Certificate2 x509cert in mqttTlsOptions?.Certificates.X509Certificates) { - x509Certificate2s.Add(new X509Certificate2(x509cert.Handle)); + x509Certificate2s.Add(X509CertificateLoader.LoadCertificate(x509cert.RawData)); } } @@ -852,7 +852,7 @@ private static CertificateValidator CreateCertificateValidator(MqttTlsOptions mq /// The context of the validation private bool ValidateBrokerCertificate(MqttClientCertificateValidationEventArgs context) { - var brokerCertificate = new X509Certificate2(context.Certificate.GetRawCertData()); + var brokerCertificate = X509CertificateLoader.LoadCertificate(context.Certificate.GetRawCertData()); try { diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509CertificateLoader.cs b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509CertificateLoader.cs new file mode 100644 index 000000000..84c163ca3 --- /dev/null +++ b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509CertificateLoader.cs @@ -0,0 +1,91 @@ +/* ======================================================================== + * Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +#if !NET9_0_OR_GREATER + +using System; +using System.Security.Cryptography.X509Certificates; + +namespace System.Security.Cryptography.X509Certificates +{ + /// + /// A helper to support the .NET 9 certificate loader primitives on older .NET versions. + /// + public static class X509CertificateLoader + { + /// + /// Initializes a new instance of the class from certificate data. + /// + public static X509Certificate2 LoadCertificate(byte[] data) + { + return new X509Certificate2(data); + } + +#if NET6_0_OR_GREATER + /// + /// Initializes a new instance of the class from certificate data. + /// + public static X509Certificate2 LoadCertificate(ReadOnlySpan rawData) + { + return new X509Certificate2(rawData); + } +#endif + + /// + /// Initializes a new instance of the class from certificate file. + /// + public static X509Certificate2 LoadCertificateFromFile(string path) + { + return new X509Certificate2(path); + } + + /// + /// Initializes a new instance of the class from Pfx data. + /// + public static X509Certificate2 LoadPkcs12( + byte[] data, + string password, + X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet) + { + return new X509Certificate2(data, password, keyStorageFlags); + } + + /// + /// Initializes a new instance of the class from Pfx file. + /// + public static X509Certificate2 LoadPkcs12FromFile( + string filename, + string password, + X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet) + { + return new X509Certificate2(filename, password, keyStorageFlags); + } + } +} +#endif diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs index 03ad62b1f..19909507f 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs @@ -153,10 +153,11 @@ public static X509Certificate2 CreateCertificateFromPKCS12( try { // merge first cert with private key into X509Certificate2 - certificate = new X509Certificate2( + certificate = X509CertificateLoader.LoadPkcs12( rawData, password ?? string.Empty, flag); + // can we really access the private key? if (VerifyRSAKeyPair(certificate, certificate, true)) { diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index b63675147..a41431b66 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -385,12 +385,12 @@ private ServiceResult UpdateCertificate( { foreach (byte[] issuerRawCert in issuerCertificates) { - var newIssuerCert = new X509Certificate2(issuerRawCert); + var newIssuerCert = X509CertificateLoader.LoadCertificate(issuerRawCert); newIssuerCollection.Add(newIssuerCert); } } - newCert = new X509Certificate2(certificate); + newCert = X509CertificateLoader.LoadCertificate(certificate); } catch { @@ -499,7 +499,7 @@ private ServiceResult UpdateCertificate( var passwordProvider = m_configuration.SecurityConfiguration.CertificatePasswordProvider; appStore.Add(updateCertificate.CertificateWithPrivateKey, passwordProvider?.GetPassword(certificateGroup.ApplicationCertificate)).Wait(); // keep only track of cert without private key - var certOnly = new X509Certificate2(updateCertificate.CertificateWithPrivateKey.RawData); + var certOnly = X509CertificateLoader.LoadCertificate(updateCertificate.CertificateWithPrivateKey.RawData); updateCertificate.CertificateWithPrivateKey.Dispose(); updateCertificate.CertificateWithPrivateKey = certOnly; } diff --git a/Libraries/Opc.Ua.Server/Configuration/TrustList.cs b/Libraries/Opc.Ua.Server/Configuration/TrustList.cs index 685b395b0..1930c6b56 100644 --- a/Libraries/Opc.Ua.Server/Configuration/TrustList.cs +++ b/Libraries/Opc.Ua.Server/Configuration/TrustList.cs @@ -347,7 +347,7 @@ private ServiceResult CloseAndUpdate( issuerCertificates = new X509Certificate2Collection(); foreach (var cert in trustList.IssuerCertificates) { - issuerCertificates.Add(new X509Certificate2(cert)); + issuerCertificates.Add(X509CertificateLoader.LoadCertificate(cert)); } } if ((masks & TrustListMasks.IssuerCrls) != 0) @@ -363,7 +363,7 @@ private ServiceResult CloseAndUpdate( trustedCertificates = new X509Certificate2Collection(); foreach (var cert in trustList.TrustedCertificates) { - trustedCertificates.Add(new X509Certificate2(cert)); + trustedCertificates.Add(X509CertificateLoader.LoadCertificate(cert)); } } if ((masks & TrustListMasks.TrustedCrls) != 0) @@ -461,7 +461,7 @@ private ServiceResult AddCertificate( X509Certificate2 cert = null; try { - cert = new X509Certificate2(certificate); + cert = X509CertificateLoader.LoadCertificate(certificate); } catch { diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index 280460251..64ef8e948 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -42,9 +42,10 @@ - + - + + @@ -60,8 +61,7 @@ - - + @@ -71,7 +71,7 @@ - + diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs index 2f3acaf63..9ae545707 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs @@ -58,10 +58,11 @@ public static class CertificateFactory public static X509Certificate2 Create(ReadOnlyMemory encodedData, bool useCache) { #if NET6_0_OR_GREATER - var certificate = new X509Certificate2(encodedData.Span); + var certificate = X509CertificateLoader.LoadCertificate(encodedData.Span); #else - var certificate = new X509Certificate2(encodedData.ToArray()); + var certificate = X509CertificateLoader.LoadCertificate(encodedData.ToArray()); #endif + if (useCache) { return Load(certificate, false); @@ -368,7 +369,7 @@ public static X509Certificate2 CreateCertificateWithPEMPrivateKey( string password = null) { RSA rsaPrivateKey = PEMReader.ImportPrivateKeyFromPEM(pemDataBlob, password); - return new X509Certificate2(certificate.RawData).CopyWithPrivateKey(rsaPrivateKey); + return X509CertificateLoader.LoadCertificate(certificate.RawData).CopyWithPrivateKey(rsaPrivateKey); } #else /// @@ -453,18 +454,18 @@ public static X509Certificate2 CreateCertificateWithPEMPrivateKey( /// The certificate with a private key. [Obsolete("Use the new CreateCertificate methods with CertificateBuilder.")] internal static X509Certificate2 CreateCertificate( - string applicationUri, - string applicationName, - string subjectName, - IList domainNames, - ushort keySize, - DateTime startTime, - ushort lifetimeInMonths, - ushort hashSizeInBits, - bool isCA = false, - X509Certificate2 issuerCAKeyCert = null, - byte[] publicKey = null, - int pathLengthConstraint = 0) + string applicationUri, + string applicationName, + string subjectName, + IList domainNames, + ushort keySize, + DateTime startTime, + ushort lifetimeInMonths, + ushort hashSizeInBits, + bool isCA = false, + X509Certificate2 issuerCAKeyCert = null, + byte[] publicKey = null, + int pathLengthConstraint = 0) { ICertificateBuilder builder = null; if (isCA) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 4e30f5d1a..2c13adf88 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -510,7 +510,7 @@ public virtual async Task ValidateAsync(X509Certificate2Collection chain, Config await InternalValidateAsync(chain, endpoint, ct).ConfigureAwait(false); // add to list of validated certificates. - m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData); + m_validatedCertificates[certificate.Thumbprint] = X509CertificateLoader.LoadCertificate(certificate.RawData); return; } @@ -529,7 +529,7 @@ public virtual async Task ValidateAsync(X509Certificate2Collection chain, Config try { Utils.LogCertificate(LogLevel.Warning, "Validation errors suppressed: ", certificate); - m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData); + m_validatedCertificates[certificate.Thumbprint] = X509CertificateLoader.LoadCertificate(certificate.RawData); } finally { @@ -554,7 +554,7 @@ public virtual void Validate(X509Certificate2Collection chain, ConfiguredEndpoin InternalValidateAsync(chain, endpoint).GetAwaiter().GetResult(); // add to list of validated certificates. - m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData); + m_validatedCertificates[certificate.Thumbprint] = X509CertificateLoader.LoadCertificate(certificate.RawData); return; } @@ -574,7 +574,7 @@ public virtual void Validate(X509Certificate2Collection chain, ConfiguredEndpoin try { Utils.LogCertificate(LogLevel.Warning, "Validation errors suppressed: ", certificate); - m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData); + m_validatedCertificates[certificate.Thumbprint] = X509CertificateLoader.LoadCertificate(certificate.RawData); } finally { diff --git a/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs b/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs index abea61dde..3aec185c3 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs @@ -449,7 +449,7 @@ public async Task LoadPrivateKey(string thumbprint, string sub { try { - var certificate = new X509Certificate2(file.FullName); + var certificate = X509CertificateLoader.LoadCertificateFromFile(file.FullName); if (!String.IsNullOrEmpty(thumbprint)) { @@ -512,10 +512,11 @@ public async Task LoadPrivateKey(string thumbprint, string sub { try { - certificate = new X509Certificate2( + certificate = X509CertificateLoader.LoadPkcs12FromFile( privateKeyFilePfx.FullName, password, flag); + if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true)) { Utils.LogInfo(Utils.TraceMasks.Security, "Imported the PFX private key for [{0}].", certificate.Thumbprint); @@ -831,7 +832,7 @@ private IDictionary Load(string thumbprint) try { var entry = new Entry { - Certificate = new X509Certificate2(file.FullName), + Certificate = X509CertificateLoader.LoadCertificateFromFile(file.FullName), CertificateFile = file, PrivateKeyFile = null, CertificateWithPrivateKey = null, diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/X509CertificateStore.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/X509CertificateStore.cs index 9be4daf45..a65fd0c20 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/X509CertificateStore.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/X509CertificateStore.cs @@ -153,7 +153,7 @@ public Task Add(X509Certificate2 certificate, string password = null) else if (certificate.HasPrivateKey && m_noPrivateKeys) { // ensure no private key is added to store - using (var publicKey = new X509Certificate2(certificate.RawData)) + using (var publicKey = X509CertificateLoader.LoadCertificate(certificate.RawData)) { store.Add(publicKey); } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs index e0698029f..6188d59d9 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs @@ -511,7 +511,7 @@ public static X509Certificate2 CreateCopyWithPrivateKey(X509Certificate2 certifi // see https://github.com/dotnet/runtime/issues/29144 string passcode = GeneratePasscode(); X509KeyStorageFlags storageFlags = persisted ? X509KeyStorageFlags.PersistKeySet : X509KeyStorageFlags.Exportable; - return new X509Certificate2(certificate.Export(X509ContentType.Pfx, passcode), passcode, storageFlags); + return X509CertificateLoader.LoadPkcs12(certificate.Export(X509ContentType.Pfx, passcode), passcode, storageFlags); } return certificate; } diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs index 36463b881..1e3c060a3 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs @@ -156,7 +156,7 @@ public async Task GetEndpointsAsync() if (endpoint.ServerCertificate != null) { - using (var cert = new X509Certificate2(endpoint.ServerCertificate)) + using (var cert = X509CertificateLoader.LoadCertificate(endpoint.ServerCertificate)) { TestContext.Out.WriteLine(" [{0}]", cert.Thumbprint); } diff --git a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs index 95f0130fe..b75631815 100644 --- a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs +++ b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs @@ -330,7 +330,7 @@ public async Task TestNoFileConfigAsServerX509Store() { // store public key in trusted store var rawData = applicationCertificate.Certificate.RawData; - await store.Add(new X509Certificate2(rawData)).ConfigureAwait(false); + await store.Add(X509CertificateLoader.LoadCertificate(rawData)).ConfigureAwait(false); } if (deleteAfterUse) @@ -427,7 +427,7 @@ public async Task TestInvalidAppCertDoNotRecreate(InvalidCertType certType, bool applicationCertificate.StoreType, applicationCertificate.StorePath ); - publicKey = new X509Certificate2(testCert.RawData); + publicKey = X509CertificateLoader.LoadCertificate(testCert.RawData); } using (publicKey) @@ -514,7 +514,7 @@ public async Task TestInvalidAppCertChainDoNotRecreate(InvalidCertType certType, applicationCertificate.StoreType, applicationCertificate.StorePath ); - publicKey = new X509Certificate2(testCert.RawData); + publicKey = X509CertificateLoader.LoadCertificate(testCert.RawData); } using (publicKey) @@ -715,7 +715,7 @@ private X509Certificate2Collection CreateInvalidCertChain(InvalidCertType certTy var result = new X509Certificate2Collection { appCert, - new X509Certificate2(rootCA.RawData) + X509CertificateLoader.LoadCertificate(rootCA.RawData) }; return result; diff --git a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateFactoryTest.cs b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateFactoryTest.cs index 93637490d..37f36749b 100644 --- a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateFactoryTest.cs +++ b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateFactoryTest.cs @@ -111,7 +111,7 @@ KeyHashPair keyHashPair { rsa.ExportParameters(false); } - var plainCert = new X509Certificate2(cert.RawData); + var plainCert = X509CertificateLoader.LoadCertificate(cert.RawData); Assert.NotNull(plainCert); VerifyApplicationCert(app, plainCert); X509Utils.VerifyRSAKeyPair(cert, cert, true); @@ -143,7 +143,7 @@ KeyHashPair keyHashPair Assert.NotNull(cert); Assert.NotNull(cert.RawData); Assert.True(cert.HasPrivateKey); - using (var plainCert = new X509Certificate2(cert.RawData)) + using (var plainCert = X509CertificateLoader.LoadCertificate(cert.RawData)) { Assert.NotNull(plainCert); VerifyApplicationCert(app, plainCert, issuerCertificate); @@ -171,7 +171,7 @@ KeyHashPair keyHashPair Assert.NotNull(cert); Assert.NotNull(cert.RawData); Assert.True(cert.HasPrivateKey); - var plainCert = new X509Certificate2(cert.RawData); + var plainCert = X509CertificateLoader.LoadCertificate(cert.RawData); Assert.NotNull(plainCert); VerifyCACert(plainCert, subject, pathLengthConstraint); X509Utils.VerifyRSAKeyPair(cert, cert, true); @@ -220,7 +220,7 @@ KeyHashPair keyHashPair Assert.NotNull(rsa); } - using (var plainCert = new X509Certificate2(issuerCertificate.RawData)) + using (var plainCert = X509CertificateLoader.LoadCertificate(issuerCertificate.RawData)) { Assert.NotNull(plainCert); VerifyCACert(plainCert, issuerCertificate.Subject, pathLengthConstraint); @@ -285,7 +285,7 @@ public void ParseCertificateBlob() byte[] singleBlob = AsnUtils.ParseX509Blob(certBlob).ToArray(); Assert.NotNull(singleBlob); - var certX = new X509Certificate2(singleBlob); + var certX = X509CertificateLoader.LoadCertificate(singleBlob); Assert.NotNull(certX); Assert.AreEqual(certArray[0].RawData, singleBlob); Assert.AreEqual(singleBlob, certX.RawData); diff --git a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateStoreTest.cs b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateStoreTest.cs index d77170d59..012ba43a0 100644 --- a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateStoreTest.cs +++ b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateStoreTest.cs @@ -118,7 +118,7 @@ public async Task VerifyAppCertX509Store(string storePath) CertificateStoreType.X509Store, storePath ); - using (var publicKey = new X509Certificate2(appCertificate.RawData)) + using (var publicKey = X509CertificateLoader.LoadCertificate(appCertificate.RawData)) { Assert.NotNull(publicKey); Assert.False(publicKey.HasPrivateKey); @@ -163,7 +163,7 @@ public async Task VerifyAppCertDirectoryStore() certificateStoreIdentifier, password ); - using (var publicKey = new X509Certificate2(appCertificate.RawData)) + using (var publicKey = X509CertificateLoader.LoadCertificate(appCertificate.RawData)) { Assert.NotNull(publicKey); Assert.False(publicKey.HasPrivateKey); diff --git a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorTest.cs b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorTest.cs index 71466fbce..23a3f03f5 100644 --- a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorTest.cs +++ b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateValidatorTest.cs @@ -479,7 +479,7 @@ public async Task VerifyAppChainsOneTrusted() TestContext.Out.WriteLine($"InitValidator: {stopWatch.ElapsedMilliseconds - start}"); foreach (var app in m_goodApplicationTestSet) { - certValidator.Validate(new X509Certificate2(app.Certificate)); + certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate)); } TestContext.Out.WriteLine($"Validation: {stopWatch.ElapsedMilliseconds - start}"); } @@ -507,7 +507,7 @@ public async Task VerifyAppChainsAllButOneTrusted() var certValidator = validator.Update(); foreach (var app in m_goodApplicationTestSet) { - certValidator.Validate(new X509Certificate2(app.Certificate)); + certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate)); } } } @@ -535,7 +535,7 @@ public async Task VerifyAppChainsIncompleteChain() var certValidator = validator.Update(); foreach (var app in m_goodApplicationTestSet) { - var serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + var serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual((StatusCode)StatusCodes.BadCertificateChainIncomplete, (StatusCode)serviceResultException.StatusCode, serviceResultException.Message); } } @@ -569,7 +569,7 @@ public async Task VerifyAppChainsInvalidChain() var certValidator = validator.Update(); foreach (var app in m_goodApplicationTestSet) { - var serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + var serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual((StatusCode)StatusCodes.BadCertificateChainIncomplete, (StatusCode)serviceResultException.StatusCode, serviceResultException.Message); } } @@ -598,7 +598,7 @@ public async Task VerifyAppChainsWithGoodAndInvalidChain() var certValidator = validator.Update(); foreach (var app in m_goodApplicationTestSet) { - certValidator.Validate(new X509Certificate2(app.Certificate)); + certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate)); } } } @@ -631,7 +631,7 @@ public async Task VerifyRevokedTrustedStoreAppChains() var certValidator = validator.Update(); foreach (var app in m_goodApplicationTestSet) { - ServiceResultException serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + ServiceResultException serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual(v == kCaChainCount - 1 ? StatusCodes.BadCertificateRevoked : StatusCodes.BadCertificateIssuerRevoked, serviceResultException.StatusCode, serviceResultException.Message); } @@ -665,7 +665,7 @@ public async Task VerifyRevokedIssuerStoreAppChains() var certValidator = validator.Update(); foreach (var app in m_goodApplicationTestSet) { - var serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + var serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual(v == kCaChainCount - 1 ? StatusCodes.BadCertificateRevoked : StatusCodes.BadCertificateIssuerRevoked, serviceResultException.StatusCode, serviceResultException.Message); } @@ -698,12 +698,12 @@ public async Task VerifyRevokedIssuerStoreTrustedAppChains() } foreach (var app in m_goodApplicationTestSet) { - await validator.TrustedStore.Add(new X509Certificate2(app.Certificate)).ConfigureAwait(false); + await validator.TrustedStore.Add(X509CertificateLoader.LoadCertificate(app.Certificate)).ConfigureAwait(false); } var certValidator = validator.Update(); foreach (var app in m_goodApplicationTestSet) { - var serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + var serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual(v == kCaChainCount - 1 ? StatusCodes.BadCertificateRevoked : StatusCodes.BadCertificateIssuerRevoked, serviceResultException.StatusCode, serviceResultException.Message); } @@ -736,7 +736,7 @@ public async Task VerifyRevokedTrustedStoreNotTrustedAppChains() var certValidator = validator.Update(); foreach (var app in m_goodApplicationTestSet) { - var serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + var serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual(v == kCaChainCount - 1 ? StatusCodes.BadCertificateRevoked : StatusCodes.BadCertificateIssuerRevoked, serviceResultException.StatusCode, serviceResultException.Message); } @@ -768,12 +768,12 @@ public async Task VerifyRevokedTrustedStoreTrustedAppChains() } foreach (var app in m_goodApplicationTestSet) { - await validator.TrustedStore.Add(new X509Certificate2(app.Certificate)).ConfigureAwait(false); + await validator.TrustedStore.Add(X509CertificateLoader.LoadCertificate(app.Certificate)).ConfigureAwait(false); } var certValidator = validator.Update(); foreach (var app in m_goodApplicationTestSet) { - var serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + var serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual(v == kCaChainCount - 1 ? StatusCodes.BadCertificateRevoked : StatusCodes.BadCertificateIssuerRevoked, serviceResultException.StatusCode, serviceResultException.Message); } @@ -799,13 +799,13 @@ public async Task VerifyIssuerChainIncompleteTrustedAppCerts() // all app certs are trusted foreach (var app in m_goodApplicationTestSet) { - await validator.TrustedStore.Add(new X509Certificate2(app.Certificate)).ConfigureAwait(false); + await validator.TrustedStore.Add(X509CertificateLoader.LoadCertificate(app.Certificate)).ConfigureAwait(false); } var certValidator = validator.Update(); foreach (var app in m_goodApplicationTestSet) { - certValidator.Validate(new X509Certificate2(app.Certificate)); + certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate)); } } } @@ -834,13 +834,13 @@ public async Task VerifyIssuerChainTrustedAppCerts() // all app certs are trusted foreach (var app in m_goodApplicationTestSet) { - await validator.TrustedStore.Add(new X509Certificate2(app.Certificate)).ConfigureAwait(false); + await validator.TrustedStore.Add(X509CertificateLoader.LoadCertificate(app.Certificate)).ConfigureAwait(false); } var certValidator = validator.Update(); foreach (var app in m_goodApplicationTestSet) { - var serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + var serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual((StatusCode)StatusCodes.BadCertificateChainIncomplete, (StatusCode)serviceResultException.StatusCode, serviceResultException.Message); } } @@ -859,9 +859,9 @@ public void VerifyPemWriterPrivateKeys() var pemDataBlob = PEMWriter.ExportPrivateKeyAsPEM(appCert); var pemString = Encoding.UTF8.GetString(pemDataBlob); TestContext.Out.WriteLine(pemString); - CertificateFactory.CreateCertificateWithPEMPrivateKey(new X509Certificate2(appCert.RawData), pemDataBlob); + CertificateFactory.CreateCertificateWithPEMPrivateKey(X509CertificateLoader.LoadCertificate(appCert.RawData), pemDataBlob); // note: password is ignored - var newCert = CertificateFactory.CreateCertificateWithPEMPrivateKey(new X509Certificate2(appCert.RawData), pemDataBlob, "password"); + var newCert = CertificateFactory.CreateCertificateWithPEMPrivateKey(X509CertificateLoader.LoadCertificate(appCert.RawData), pemDataBlob, "password"); X509Utils.VerifyRSAKeyPair(newCert, newCert, true); } } @@ -897,10 +897,10 @@ public void VerifyPemWriterRSAPrivateKeys() var pemDataBlob = PEMWriter.ExportRSAPrivateKeyAsPEM(appCert); var pemString = Encoding.UTF8.GetString(pemDataBlob); TestContext.Out.WriteLine(pemString); - var cert = CertificateFactory.CreateCertificateWithPEMPrivateKey(new X509Certificate2(appCert.RawData), pemDataBlob); + var cert = CertificateFactory.CreateCertificateWithPEMPrivateKey(X509CertificateLoader.LoadCertificate(appCert.RawData), pemDataBlob); Assert.NotNull(cert); // note: password is ignored - var newCert = CertificateFactory.CreateCertificateWithPEMPrivateKey(new X509Certificate2(appCert.RawData), pemDataBlob, "password"); + var newCert = CertificateFactory.CreateCertificateWithPEMPrivateKey(X509CertificateLoader.LoadCertificate(appCert.RawData), pemDataBlob, "password"); X509Utils.VerifyRSAKeyPair(newCert, newCert, true); } } @@ -1378,7 +1378,7 @@ public async Task VerifySomeMissingCRLRevokedTrustedStoreAppChains(bool rejectUn foreach (var app in m_goodApplicationTestSet) { - ServiceResultException serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + ServiceResultException serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual(v == kCaChainCount - 1 ? StatusCodes.BadCertificateRevoked : StatusCodes.BadCertificateIssuerRevoked, serviceResultException.StatusCode, serviceResultException.Message); @@ -1421,7 +1421,7 @@ public async Task VerifyAllMissingCRLRevokedTrustedStoreAppChains() foreach (var app in m_goodApplicationTestSet) { - ServiceResultException serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + ServiceResultException serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.IsTrue(StatusCodes.BadCertificateRevocationUnknown == serviceResultException.StatusCode, serviceResultException.Message); @@ -1483,7 +1483,7 @@ public async Task VerifySomeMissingCRLTrustedStoreAppChains(bool rejectUnknownRe { if (rejectUnknownRevocationStatus) { - ServiceResultException serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + ServiceResultException serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual(v == kCaChainCount - 1 ? StatusCodes.BadCertificateRevocationUnknown : StatusCodes.BadCertificateIssuerRevocationUnknown, serviceResultException.StatusCode, @@ -1491,7 +1491,7 @@ public async Task VerifySomeMissingCRLTrustedStoreAppChains(bool rejectUnknownRe } else { - certValidator.Validate(new X509Certificate2(app.Certificate)); + certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate)); } } } @@ -1523,7 +1523,7 @@ public async Task VerifyMissingCRLANDAppChainsIncompleteChain(bool rejectUnknown foreach (var app in m_goodApplicationTestSet) { - var serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + var serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual((StatusCode)StatusCodes.BadCertificateChainIncomplete, (StatusCode)serviceResultException.StatusCode, serviceResultException.Message); // no need to check for inner exceptions, since an incomplete chain error cannot be suppressed. } @@ -1547,12 +1547,12 @@ public async Task VerifyExistingCRLAppChainsExpiredCertificates(bool rejectUnkno { if (i != v || kCaChainCount == 1) { - await validator.TrustedStore.Add(new X509Certificate2(m_caChain[i].RawData)).ConfigureAwait(false); + await validator.TrustedStore.Add(X509CertificateLoader.LoadCertificate(m_caChain[i].RawData)).ConfigureAwait(false); await validator.TrustedStore.AddCRL(m_crlChain[i]).ConfigureAwait(false); } else { - await validator.IssuerStore.Add(new X509Certificate2(m_caChain[i].RawData)).ConfigureAwait(false); + await validator.IssuerStore.Add(X509CertificateLoader.LoadCertificate(m_caChain[i].RawData)).ConfigureAwait(false); await validator.IssuerStore.AddCRL(m_crlChain[i]).ConfigureAwait(false); } } @@ -1563,7 +1563,7 @@ public async Task VerifyExistingCRLAppChainsExpiredCertificates(bool rejectUnkno foreach (var app in m_notYetValidCertsApplicationTestSet) { - var serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + var serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual((StatusCode)StatusCodes.BadCertificateTimeInvalid, (StatusCode)serviceResultException.StatusCode, serviceResultException.Message); } } @@ -1602,7 +1602,7 @@ public async Task VerifyMissingCRLAppChainsExpiredCertificates(bool rejectUnknow foreach (var app in m_notYetValidCertsApplicationTestSet) { - var serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + var serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual((StatusCode)StatusCodes.BadCertificateTimeInvalid, (StatusCode)serviceResultException.StatusCode, serviceResultException.Message); // BadCertificateTimeInvalid can be suppressed. Ensure the other issues are caught, as well: @@ -1663,7 +1663,7 @@ public async Task VerifyMissingCRLNoTrust(bool rejectUnknownRevocationStatus) foreach (var app in m_goodApplicationTestSet) { - var serviceResultException = Assert.Throws(() => certValidator.Validate(new X509Certificate2(app.Certificate))); + var serviceResultException = Assert.Throws(() => certValidator.Validate(X509CertificateLoader.LoadCertificate(app.Certificate))); Assert.AreEqual((StatusCode)StatusCodes.BadCertificateUntrusted, (StatusCode)serviceResultException.StatusCode, serviceResultException.Message); } } diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index bff7f8ddd..462440233 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -794,7 +794,7 @@ public void StartGoodSigningRequests() } else { - csrCertificate = CertificateFactory.CreateCertificateWithPEMPrivateKey(new X509Certificate2(application.Certificate), application.PrivateKey, application.PrivateKeyPassword); + csrCertificate = CertificateFactory.CreateCertificateWithPEMPrivateKey(X509CertificateLoader.LoadCertificate(application.Certificate), application.PrivateKey, application.PrivateKeyPassword); } byte[] certificateRequest = CertificateFactory.CreateSigningRequest(csrCertificate, application.DomainNames); csrCertificate.Dispose(); @@ -1089,7 +1089,7 @@ public void GoodSigningRequestAsSelfAdmin() } else { - csrCertificate = CertificateFactory.CreateCertificateWithPEMPrivateKey(new X509Certificate2(application.Certificate), application.PrivateKey, application.PrivateKeyPassword); + csrCertificate = CertificateFactory.CreateCertificateWithPEMPrivateKey(X509CertificateLoader.LoadCertificate(application.Certificate), application.PrivateKey, application.PrivateKeyPassword); } byte[] certificateRequest = CertificateFactory.CreateSigningRequest(csrCertificate, application.DomainNames); csrCertificate.Dispose(); diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs index e7ea4a97e..316312c47 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs @@ -206,7 +206,7 @@ public string ReadLogFile() #region Private Methods private async Task ApplyNewApplicationInstanceCertificateAsync(byte[] certificate, byte[] privateKey) { - using (var x509 = new X509Certificate2(certificate)) + using (var x509 = X509CertificateLoader.LoadCertificate(certificate)) { var certWithPrivateKey = CertificateFactory.CreateCertificateWithPEMPrivateKey(x509, privateKey); m_client.Configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier(certWithPrivateKey); diff --git a/Tests/Opc.Ua.Gds.Tests/PushTest.cs b/Tests/Opc.Ua.Gds.Tests/PushTest.cs index 6b9cfb236..4c9f27210 100644 --- a/Tests/Opc.Ua.Gds.Tests/PushTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/PushTest.cs @@ -83,7 +83,7 @@ protected async Task OneTimeSetUp() ConnectGDSClient(true); RegisterPushServerApplication(m_pushClient.PushClient.EndpointUrl); - m_selfSignedServerCert = new X509Certificate2(m_pushClient.PushClient.Session.ConfiguredEndpoint.Description.ServerCertificate); + m_selfSignedServerCert = X509CertificateLoader.LoadCertificate(m_pushClient.PushClient.Session.ConfiguredEndpoint.Description.ServerCertificate); m_domainNames = X509Utils.GetDomainsFromCertificate(m_selfSignedServerCert).ToArray(); await CreateCATestCerts(m_pushClient.TempStorePath).ConfigureAwait(false); @@ -328,7 +328,7 @@ public void UpdateCertificateSelfSignedNoPrivateKeyAsserts() { ConnectPushClient(true); using (X509Certificate2 invalidCert = CertificateFactory.CreateCertificate("uri:x:y:z", "TestApp", "CN=Push Server Test", null).CreateForRSA()) - using (X509Certificate2 serverCert = new X509Certificate2(m_pushClient.PushClient.Session.ConfiguredEndpoint.Description.ServerCertificate)) + using (X509Certificate2 serverCert = X509CertificateLoader.LoadCertificate(m_pushClient.PushClient.Session.ConfiguredEndpoint.Description.ServerCertificate)) { if (!X509Utils.CompareDistinguishedName(serverCert.Subject, serverCert.Issuer)) { @@ -358,7 +358,7 @@ public void UpdateCertificateSelfSignedNoPrivateKeyAsserts() public void UpdateCertificateSelfSignedNoPrivateKey() { ConnectPushClient(true); - using (X509Certificate2 serverCert = new X509Certificate2(m_pushClient.PushClient.Session.ConfiguredEndpoint.Description.ServerCertificate)) + using (X509Certificate2 serverCert = X509CertificateLoader.LoadCertificate(m_pushClient.PushClient.Session.ConfiguredEndpoint.Description.ServerCertificate)) { if (!X509Utils.CompareDistinguishedName(serverCert.Subject, serverCert.Issuer)) { @@ -627,7 +627,7 @@ public void GetCertificates() Assert.That(certificateTypeIds.Length == 1); Assert.NotNull(certificates[0]); - using (var x509 = new X509Certificate2(certificates[0])) + using (var x509 = X509CertificateLoader.LoadCertificate(certificates[0])) { Assert.NotNull(x509); } @@ -687,7 +687,7 @@ private X509Certificate2Collection CreateCertCollection(ByteStringCollection cer var result = new X509Certificate2Collection(); foreach (var rawCert in certList) { - result.Add(new X509Certificate2(rawCert)); + result.Add(X509CertificateLoader.LoadCertificate(rawCert)); } return result; } @@ -757,7 +757,7 @@ private async Task AddTrustListToStore(SecurityConfiguration config, Trust issuerCertificates = new X509Certificate2Collection(); foreach (var cert in trustList.IssuerCertificates) { - issuerCertificates.Add(new X509Certificate2(cert)); + issuerCertificates.Add(X509CertificateLoader.LoadCertificate(cert)); } } if ((masks & TrustListMasks.IssuerCrls) != 0) @@ -773,7 +773,7 @@ private async Task AddTrustListToStore(SecurityConfiguration config, Trust trustedCertificates = new X509Certificate2Collection(); foreach (var cert in trustList.TrustedCertificates) { - trustedCertificates.Add(new X509Certificate2(cert)); + trustedCertificates.Add(X509CertificateLoader.LoadCertificate(cert)); } } if ((masks & TrustListMasks.TrustedCrls) != 0) diff --git a/Tests/Opc.Ua.Gds.Tests/X509TestUtils.cs b/Tests/Opc.Ua.Gds.Tests/X509TestUtils.cs index e61ad47a1..8bfa910de 100644 --- a/Tests/Opc.Ua.Gds.Tests/X509TestUtils.cs +++ b/Tests/Opc.Ua.Gds.Tests/X509TestUtils.cs @@ -42,7 +42,7 @@ public static class X509TestUtils { public static void VerifyApplicationCertIntegrity(byte[] certificate, byte[] privateKey, string privateKeyPassword, string privateKeyFormat, byte[][] issuerCertificates) { - X509Certificate2 newCert = new X509Certificate2(certificate); + X509Certificate2 newCert = X509CertificateLoader.LoadCertificate(certificate); Assert.IsNotNull(newCert); X509Certificate2 newPrivateKeyCert = null; if (privateKeyFormat == "PFX") @@ -64,7 +64,7 @@ public static void VerifyApplicationCertIntegrity(byte[] certificate, byte[] pri CertificateIdentifierCollection issuerCertIdCollection = new CertificateIdentifierCollection(); foreach (var issuer in issuerCertificates) { - var issuerCert = new X509Certificate2(issuer); + var issuerCert = X509CertificateLoader.LoadCertificate(issuer); Assert.IsNotNull(issuerCert); issuerCertIdCollection.Add(new CertificateIdentifier(issuerCert)); } @@ -85,8 +85,8 @@ public static void VerifyApplicationCertIntegrity(byte[] certificate, byte[] pri public static void VerifySignedApplicationCert(ApplicationTestData testApp, byte[] rawSignedCert, byte[][] rawIssuerCerts) { - X509Certificate2 signedCert = new X509Certificate2(rawSignedCert); - X509Certificate2 issuerCert = new X509Certificate2(rawIssuerCerts[0]); + X509Certificate2 signedCert = X509CertificateLoader.LoadCertificate(rawSignedCert); + X509Certificate2 issuerCert = X509CertificateLoader.LoadCertificate(rawIssuerCerts[0]); TestContext.Out.WriteLine($"Signed cert: {signedCert}"); TestContext.Out.WriteLine($"Issuer cert: {issuerCert}"); diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CRLTests.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CRLTests.cs index 536f78fd0..a6302d905 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/CRLTests.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/CRLTests.cs @@ -174,7 +174,7 @@ public void CrlBuilderTest(bool empty, bool noExtensions, KeyHashPair keyHashPai Assert.AreEqual(2, x509Crl.CrlExtensions.Count); } - using (var issuerPubKey = new X509Certificate2(m_issuerCert.RawData)) + using (var issuerPubKey = X509CertificateLoader.LoadCertificate(m_issuerCert.RawData)) { Assert.True(x509Crl.VerifySignature(issuerPubKey, true)); } @@ -219,7 +219,7 @@ public void CrlBuilderTestWithSignatureGenerator(KeyHashPair keyHashPair) Assert.AreEqual(serial, x509Crl.RevokedCertificates[0].UserCertificate); Assert.AreEqual(serstring, x509Crl.RevokedCertificates[1].SerialNumber); Assert.AreEqual(2, x509Crl.CrlExtensions.Count); - using (var issuerPubKey = new X509Certificate2(m_issuerCert.RawData)) + using (var issuerPubKey = X509CertificateLoader.LoadCertificate(m_issuerCert.RawData)) { Assert.True(x509Crl.VerifySignature(issuerPubKey, true)); } diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestUtils.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestUtils.cs index 2f2cc4a85..feb037b27 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestUtils.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestUtils.cs @@ -193,7 +193,7 @@ public void Initialize(byte[] blob, string path) Cert = blob; try { - X509Certificate = new X509Certificate2(path); + X509Certificate = X509CertificateLoader.LoadCertificateFromFile(path); } catch { } diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs index 2c2d24ab7..767f96a23 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs @@ -304,7 +304,7 @@ ECCurveHashPair ecCurveHashPair { var generator = X509SignatureGenerator.CreateForECDsa(ecdsaPrivateKey); var cert = CertificateBuilder.Create("CN=App Cert") - .SetIssuer(new X509Certificate2(signingCert.RawData)) + .SetIssuer(X509CertificateLoader.LoadCertificate(signingCert.RawData)) .CreateForRSA(generator); Assert.NotNull(cert); WriteCertificate(cert, "Default signed ECDsa cert"); @@ -316,7 +316,7 @@ ECCurveHashPair ecCurveHashPair var generator = X509SignatureGenerator.CreateForECDsa(ecdsaPrivateKey); var cert = CertificateBuilder.Create("CN=App Cert") .SetHashAlgorithm(ecCurveHashPair.HashAlgorithmName) - .SetIssuer(new X509Certificate2(signingCert.RawData)) + .SetIssuer(X509CertificateLoader.LoadCertificate(signingCert.RawData)) .SetECDsaPublicKey(ecdsaPublicKey) .CreateForECDsa(generator); Assert.NotNull(cert); @@ -328,7 +328,7 @@ ECCurveHashPair ecCurveHashPair var generator = X509SignatureGenerator.CreateForECDsa(ecdsaPrivateKey); var cert = CertificateBuilder.Create("CN=App Cert") .SetHashAlgorithm(ecCurveHashPair.HashAlgorithmName) - .SetIssuer(new X509Certificate2(signingCert.RawData)) + .SetIssuer(X509CertificateLoader.LoadCertificate(signingCert.RawData)) .SetECCurve(ecCurveHashPair.Curve) .CreateForECDsa(generator); Assert.NotNull(cert); diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForRSA.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForRSA.cs index e9563031e..7abef4c8f 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForRSA.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForRSA.cs @@ -350,7 +350,7 @@ public void CreateIssuerRSAWithSuppliedKeyPair() .CreateForRSA(generator)) { Assert.NotNull(cert); - issuer = new X509Certificate2(cert.RawData); + issuer = X509CertificateLoader.LoadCertificate(cert.RawData); WriteCertificate(cert, "Default root cert with supplied RSA cert"); CheckPEMWriter(cert); } @@ -388,7 +388,7 @@ public void CreateIssuerRSACngWithSuppliedKeyPair() .CreateForRSA(generator)) { Assert.NotNull(cert); - issuer = new X509Certificate2(cert.RawData); + issuer = X509CertificateLoader.LoadCertificate(cert.RawData); WriteCertificate(cert, "Default root cert with supplied RSA cert"); CheckPEMWriter(cert); } @@ -425,7 +425,7 @@ KeyHashPair keyHashPair using (RSA rsaPrivateKey = signingCert.GetRSAPrivateKey()) { var generator = X509SignatureGenerator.CreateForRSA(rsaPrivateKey, RSASignaturePadding.Pkcs1); - using (var issuer = new X509Certificate2(signingCert.RawData)) + using (var issuer = X509CertificateLoader.LoadCertificate(signingCert.RawData)) using (var cert = CertificateBuilder.Create("CN=App Cert") .SetIssuer(issuer) .CreateForRSA(generator)) @@ -442,7 +442,7 @@ KeyHashPair keyHashPair using (RSA rsaPublicKey = signingCert.GetRSAPublicKey()) { var generator = X509SignatureGenerator.CreateForRSA(rsaPrivateKey, RSASignaturePadding.Pkcs1); - using (var issuer = new X509Certificate2(signingCert.RawData)) + using (var issuer = X509CertificateLoader.LoadCertificate(signingCert.RawData)) using (var cert = CertificateBuilder.Create("CN=App Cert") .SetHashAlgorithm(keyHashPair.HashAlgorithmName) .SetIssuer(issuer) @@ -457,7 +457,7 @@ KeyHashPair keyHashPair using (RSA rsaPrivateKey = signingCert.GetRSAPrivateKey()) { var generator = X509SignatureGenerator.CreateForRSA(rsaPrivateKey, RSASignaturePadding.Pkcs1); - using (var issuer = new X509Certificate2(signingCert.RawData)) + using (var issuer = X509CertificateLoader.LoadCertificate(signingCert.RawData)) using (var cert = CertificateBuilder.Create("CN=App Cert") .SetHashAlgorithm(keyHashPair.HashAlgorithmName) .SetIssuer(issuer) diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/ExtensionTests.cs b/Tests/Opc.Ua.Security.Certificates.Tests/ExtensionTests.cs index 5078a0264..a574e1282 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/ExtensionTests.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/ExtensionTests.cs @@ -56,7 +56,7 @@ public void DecodeExtensions( CertificateAsset certAsset ) { - using (var x509Cert = new X509Certificate2(certAsset.Cert)) + using (var x509Cert = X509CertificateLoader.LoadCertificate(certAsset.Cert)) { Assert.NotNull(x509Cert); TestContext.Out.WriteLine("CertificateAsset:"); diff --git a/Tests/customtest.bat b/Tests/customtest.bat index 7ae1bbecb..fef271ed2 100644 --- a/Tests/customtest.bat +++ b/Tests/customtest.bat @@ -2,19 +2,19 @@ setlocal enabledelayedexpansion echo This script is used to run custom platform tests for the UA Core Library -echo Supported parameters: net462, netstandard2.0, netstandard2.1, net472, net48, net6.0, net8.0 +echo Supported parameters: net462, netstandard2.0, netstandard2.1, net472, net48, net6.0, net8.0, net 9.0 REM Check if the target framework parameter is provided if "%1"=="" ( echo Usage: %0 [TargetFramework] - echo Allowed values for TargetFramework: net462, netstandard2.0, netstandard2.1, net472, net48, net6.0, net8.0, default + echo Allowed values for TargetFramework: net462, netstandard2.0, netstandard2.1, net472, net48, net6.0, net8.0, net9.0, default goto :eof ) REM Check if the provided TargetFramework is valid -set "validFrameworks= default net462 net472 netstandard2.0 netstandard2.1 net48 net6.0 net8.0 " +set "validFrameworks= default net462 net472 netstandard2.0 netstandard2.1 net48 net6.0 net8.0 net9.0 " if "!validFrameworks: %1 =!"=="%validFrameworks%" ( - echo Invalid TargetFramework specified. Allowed values are: default, net462, net472 netstandard2.0, netstandard2.1, net48, net6.0, net8.0 + echo Invalid TargetFramework specified. Allowed values are: default, net462, net472 netstandard2.0, netstandard2.1, net48, net6.0, net8.0, net9.0 goto :eof ) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1d2ede8b5..0be491de2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -102,6 +102,16 @@ stages: framework: net8.0 configuration: Release jobnamesuffix: net80 +- stage: testnet90 + dependsOn: [build] + displayName: 'Test .NET 9.0' + condition: and(succeeded(), ne(variables.ScheduledBuild, 'False')) + jobs: + - template: .azurepipelines/test.yml + parameters: + framework: net9.0 + configuration: Release + jobnamesuffix: net90 - stage: testnet462 dependsOn: [build] displayName: 'Test .NET 4.6.2' diff --git a/targets.props b/targets.props index 17b539d7c..b1cf79a21 100644 --- a/targets.props +++ b/targets.props @@ -12,7 +12,7 @@ A build with all custom targets which are not part of a regular build is scheduled once a week in the DevOps build pipeline. Uncomment the following lines to test a custom test target - supported values: net462, netstandard2.0, netstandard2.1, net472, net48, net6.0, net8.0 + supported values: net462, netstandard2.0, netstandard2.1, net472, net48, net6.0, net8.0, net9.0 --> - + - preview-all - net8.0;net6.0;net48 - net8.0 - net48;net8.0 - net472;net48;netstandard2.0;netstandard2.1;net6.0;net8.0 - net472;net48;netstandard2.0;netstandard2.1;net6.0;net8.0 - net462;net472;net48;netstandard2.1;net6.0;net8.0 - net472;net48;netstandard2.0;netstandard2.1;net6.0;net8.0 + preview + all + default + net9.0;net6.0;net48 + net9.0 + net48;net9.0 + net472;net48;netstandard2.0;netstandard2.1;net6.0;net8.0;net9.0 + net472;net48;netstandard2.0;netstandard2.1;net6.0;net8.0;net9.0 + net462;net472;net48;netstandard2.1;net6.0;net8.0;net9.0 + net472;net48;netstandard2.0;netstandard2.1;net6.0;net8.0;net9.0