From 294192f03f9ffd5dcc3bcf3e35d943941bcbfb56 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Fri, 18 Nov 2022 13:25:29 +0100 Subject: [PATCH 01/80] Squash proto ecc (#1998) --- .../UA Client Controls.csproj | 2 +- .../Quickstarts.ReferenceClient.Config.xml | 4 +- .../Quickstarts.ReferenceServer.Config.xml | 50 +- .../UA Server Controls.csproj | 2 +- Libraries/Opc.Ua.Client/CoreClientUtils.cs | 42 +- Libraries/Opc.Ua.Client/Session.cs | 47 +- .../ApplicationConfigurationBuilder.cs | 56 +- .../ApplicationInstance.cs | 177 ++- .../IApplicationConfigurationBuilder.cs | 26 + .../Opc.Ua.Configuration.csproj | 16 + .../Org.BouncyCastle/CertificateBuilder.cs | 18 +- .../Org.BouncyCastle/PEMWriter.cs | 2 +- .../Org.BouncyCastle/X509Utils.cs | 35 +- .../PEM/PEMReader.cs | 8 +- .../X509Certificate/CertificateBuilder.cs | 14 + .../X509Certificate/X509PfxUtils.cs | 41 +- .../Configuration/ConfigurationNodeManager.cs | 5 + .../Server/ServerInternalData.cs | 7 +- .../Opc.Ua.Server/Server/StandardServer.cs | 36 +- .../Opc.Ua.Bindings.Https.csproj | 2 +- .../Stack/Https/HttpsServiceHost.cs | 29 +- .../Stack/Https/HttpsTransportListener.cs | 26 +- Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 16 + .../Schema/ApplicationConfiguration.cs | 134 ++- .../Schema/SecuredApplicationHelpers.cs | 8 + .../Certificates/CertificateFactory.cs | 30 +- .../Certificates/CertificateIdentifier.cs | 291 ++++- .../Certificates/CertificateValidator.cs | 99 +- .../Certificates/DirectoryCertificateStore.cs | 28 +- .../Security/Certificates/EccUtils.cs | 1034 +++++++++++++++++ .../Certificates/ICertificateStore.cs | 12 + .../Security/Certificates/Nonce.cs | 360 ++++++ .../Certificates/SecurityConfiguration.cs | 256 +++- .../Certificates/X509CertificateStore.cs | 7 + .../Security/Certificates/X509Utils.cs | 44 + .../Security/Constants/SecurityConstants.cs | 22 +- .../Security/Constants/SecurityPolicies.cs | 205 +++- .../Stack/Bindings/ITransportBindings.cs | 6 +- .../Stack/Client/ReverseConnectHost.cs | 2 - .../Configuration/ApplicationConfiguration.cs | 12 +- .../SecurityConfigurationManager.cs | 4 + Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs | 121 +- .../Stack/Tcp/TcpListenerChannel.cs | 20 +- .../Stack/Tcp/TcpReverseConnectChannel.cs | 2 +- .../Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs | 25 +- Stack/Opc.Ua.Core/Stack/Tcp/TcpServiceHost.cs | 21 +- .../Stack/Tcp/TcpTransportListener.cs | 19 +- .../Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs | 507 +++++--- .../Stack/Tcp/UaSCBinaryChannel.Symmetric.cs | 743 +++++++++++- .../Stack/Tcp/UaSCBinaryChannel.cs | 56 +- .../Stack/Tcp/UaSCBinaryClientChannel.cs | 23 +- .../Stack/Transport/ITransportListener.cs | 4 +- .../Transport/TransportListenerSettings.cs | 22 +- Stack/Opc.Ua.Core/Types/Utils/Utils.cs | 57 +- Tests/Opc.Ua.Client.Tests/ClientFixture.cs | 13 +- Tests/Opc.Ua.Client.Tests/ClientTest.cs | 31 +- .../Certificates/CertificateStoreTypeTest.cs | 4 + .../GlobalDiscoveryTestServer.cs | 2 +- Tests/Opc.Ua.Gds.Tests/PushTest.cs | 7 - .../CertificateTestsForECDsa.cs | 2 + .../CertificateTestsForRSA.cs | 19 +- Tests/Opc.Ua.Server.Tests/ServerFixture.cs | 12 +- azure-pipelines.yml | 6 + targets.props | 2 - 64 files changed, 4251 insertions(+), 682 deletions(-) create mode 100644 Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs create mode 100644 Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs diff --git a/Applications/ClientControls.Net4/UA Client Controls.csproj b/Applications/ClientControls.Net4/UA Client Controls.csproj index 74b09e9e3..4e1a9d133 100644 --- a/Applications/ClientControls.Net4/UA Client Controls.csproj +++ b/Applications/ClientControls.Net4/UA Client Controls.csproj @@ -1054,4 +1054,4 @@ - + \ No newline at end of file diff --git a/Applications/ConsoleReferenceClient/Quickstarts.ReferenceClient.Config.xml b/Applications/ConsoleReferenceClient/Quickstarts.ReferenceClient.Config.xml index 97d4db30c..7010762c7 100644 --- a/Applications/ConsoleReferenceClient/Quickstarts.ReferenceClient.Config.xml +++ b/Applications/ConsoleReferenceClient/Quickstarts.ReferenceClient.Config.xml @@ -17,8 +17,10 @@ %LocalApplicationData%/OPC Foundation/pki/own CN=Console Reference Client, C=US, S=Arizona, O=OPC Foundation, DC=localhost + + Rsa,NistP256,NistP384,BrainpoolP256r1,BrainpoolP384r1 - + Directory %LocalApplicationData%/OPC Foundation/pki/issuer diff --git a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml index 5a1bd5019..5dea23621 100644 --- a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml +++ b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml @@ -18,6 +18,11 @@ CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + + Rsa,NistP256,NistP384,BrainpoolP256r1,BrainpoolP384r1 + + + Directory @@ -96,14 +101,11 @@ --> + SignAndEncrypt_3 http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256 - - None_1 - http://opcfoundation.org/UA/SecurityPolicy#None - Sign_2 @@ -112,7 +114,43 @@ SignAndEncrypt_3 - Sign_2 http://opcfoundation.org/UA/SecurityPolicy#Basic256 @@ -129,7 +167,7 @@ SignAndEncrypt_3 http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15 - --> + 5 diff --git a/Applications/ServerControls.Net4/UA Server Controls.csproj b/Applications/ServerControls.Net4/UA Server Controls.csproj index 32c75d96e..15ab1b898 100644 --- a/Applications/ServerControls.Net4/UA Server Controls.csproj +++ b/Applications/ServerControls.Net4/UA Server Controls.csproj @@ -170,4 +170,4 @@ - + \ No newline at end of file diff --git a/Libraries/Opc.Ua.Client/CoreClientUtils.cs b/Libraries/Opc.Ua.Client/CoreClientUtils.cs index faef26c49..93aa7aab3 100644 --- a/Libraries/Opc.Ua.Client/CoreClientUtils.cs +++ b/Libraries/Opc.Ua.Client/CoreClientUtils.cs @@ -113,6 +113,7 @@ int discoverTimeout /// The discovery URL. /// if set to true select an endpoint that uses security. /// The best available endpoint. + [Obsolete("Use the SelectEndpoint with ApplicationConfiguration instead.")] public static EndpointDescription SelectEndpoint(string discoveryUrl, bool useSecurity) { return SelectEndpoint(discoveryUrl, useSecurity, DefaultDiscoverTimeout); @@ -125,6 +126,7 @@ public static EndpointDescription SelectEndpoint(string discoveryUrl, bool useSe /// if set to true select an endpoint that uses security. /// Operation timeout in milliseconds. /// The best available endpoint. + [Obsolete("Use the SelectEndpoint with ApplicationConfiguration instead.")] public static EndpointDescription SelectEndpoint( string discoveryUrl, bool useSecurity, @@ -172,7 +174,7 @@ int discoverTimeout { var url = new Uri(client.Endpoint.EndpointUrl); var endpoints = client.GetEndpoints(null); - return SelectEndpoint(url, endpoints, useSecurity); + return SelectEndpoint(application, url, endpoints, useSecurity); } } @@ -215,7 +217,7 @@ int discoverTimeout // Connect to the server's discovery endpoint and find the available configuration. Uri url = new Uri(client.Endpoint.EndpointUrl); var endpoints = client.GetEndpoints(null); - var selectedEndpoint = SelectEndpoint(url, endpoints, useSecurity); + var selectedEndpoint = SelectEndpoint(application, url, endpoints, useSecurity); Uri endpointUrl = Utils.ParseUri(selectedEndpoint.EndpointUrl); if (endpointUrl != null && endpointUrl.Scheme == uri.Scheme) @@ -234,10 +236,28 @@ int discoverTimeout /// Select the best supported endpoint from an /// EndpointDescriptionCollection, with or without security. /// + /// The discovery Url of the server. + /// + /// + [Obsolete("Use the SelectEndpoint with ApplicationConfiguration instead.")] + public static EndpointDescription SelectEndpoint( + Uri url, + EndpointDescriptionCollection endpoints, + bool useSecurity) + { + return SelectEndpoint(null, url, endpoints, useSecurity); + } + + /// + /// Select the best supported endpoint from an + /// EndpointDescriptionCollection, with or without security. + /// + /// /// /// /// public static EndpointDescription SelectEndpoint( + ApplicationConfiguration configuration, Uri url, EndpointDescriptionCollection endpoints, bool useSecurity) @@ -260,10 +280,22 @@ public static EndpointDescription SelectEndpoint( continue; } - // skip unsupported security policies - if (SecurityPolicies.GetDisplayName(endpoint.SecurityPolicyUri) == null) + if (configuration != null) { - continue; + // skip unsupported security policies + if (!configuration.SecurityConfiguration.SupportedSecurityPolicies.Contains(endpoint.SecurityPolicyUri)) + { + continue; + } + } + else + { + // skip unsupported security policies, for backward compatibility only + // may contain policies for which no certificate is available + if (SecurityPolicies.GetDisplayName(endpoint.SecurityPolicyUri) == null) + { + continue; + } } } else diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index 1967d102d..9b37916fb 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -165,26 +165,28 @@ private void Initialize( if (m_endpoint.Description.SecurityPolicyUri != SecurityPolicies.None) { - // update client certificate. - m_instanceCertificate = clientCertificate; - if (clientCertificate == null) { - // load the application instance certificate. - if (m_configuration.SecurityConfiguration.ApplicationCertificate == null) + m_instanceCertificate = LoadCertificate(configuration, m_endpoint.Description.SecurityPolicyUri).GetAwaiter().GetResult(); + if (m_instanceCertificate == null) { throw new ServiceResultException( StatusCodes.BadConfigurationError, "The client configuration does not specify an application instance certificate."); } - - m_instanceCertificate = m_configuration.SecurityConfiguration.ApplicationCertificate.Find(true).Result; + } + else + { + // update client certificate. + m_instanceCertificate = clientCertificate; } // check for valid certificate. if (m_instanceCertificate == null) { +#pragma warning disable CS0618 // Type or member is obsolete var cert = m_configuration.SecurityConfiguration.ApplicationCertificate; +#pragma warning restore CS0618 // Type or member is obsolete throw ServiceResultException.Create( StatusCodes.BadConfigurationError, "Cannot find the application instance certificate. Store={0}, SubjectName={1}, Thumbprint={2}.", @@ -197,19 +199,12 @@ private void Initialize( throw ServiceResultException.Create( StatusCodes.BadConfigurationError, "No private key for the application instance certificate. Subject={0}, Thumbprint={1}.", - m_instanceCertificate.Subject, + m_instanceCertificate.Subject, m_instanceCertificate.Thumbprint); } // load certificate chain. - m_instanceCertificateChain = new X509Certificate2Collection(m_instanceCertificate); - List issuers = new List(); - configuration.CertificateValidator.GetIssuers(m_instanceCertificate, issuers).Wait(); - - for (int i = 0; i < issuers.Count; i++) - { - m_instanceCertificateChain.Add(issuers[i].Certificate); - } + m_instanceCertificateChain = LoadCertificateChain(configuration, m_instanceCertificate).GetAwaiter().GetResult(); } // initialize the message context. @@ -991,7 +986,7 @@ public static async Task CreateChannelAsync( X509Certificate2Collection clientCertificateChain = null; if (endpointDescription.SecurityPolicyUri != SecurityPolicies.None) { - clientCertificate = await LoadCertificate(configuration).ConfigureAwait(false); + clientCertificate = await LoadCertificate(configuration, endpointDescription.SecurityPolicyUri).ConfigureAwait(false); clientCertificateChain = await LoadCertificateChain(configuration, clientCertificate).ConfigureAwait(false); } @@ -5798,20 +5793,18 @@ private void DeleteSubscription(uint subscriptionId) /// /// Load certificate for connection. /// - private static async Task LoadCertificate(ApplicationConfiguration configuration) + private static async Task LoadCertificate(ApplicationConfiguration configuration, string securityProfile) { - X509Certificate2 clientCertificate; - if (configuration.SecurityConfiguration.ApplicationCertificate == null) - { - throw ServiceResultException.Create(StatusCodes.BadConfigurationError, "ApplicationCertificate must be specified."); - } - - clientCertificate = await configuration.SecurityConfiguration.ApplicationCertificate.Find(true).ConfigureAwait(false); + X509Certificate2 clientCertificate = + await configuration.SecurityConfiguration.FindApplicationCertificateAsync(securityProfile, true).ConfigureAwait(false); if (clientCertificate == null) { - throw ServiceResultException.Create(StatusCodes.BadConfigurationError, "ApplicationCertificate cannot be found."); + throw ServiceResultException.Create(StatusCodes.BadConfigurationError, + "ApplicationCertificate for the security profile {0} cannot be found.", + securityProfile); } + return clientCertificate; } @@ -5826,7 +5819,7 @@ private static async Task LoadCertificateChain(Appli { clientCertificateChain = new X509Certificate2Collection(clientCertificate); List issuers = new List(); - await configuration.CertificateValidator.GetIssuers(clientCertificate, issuers).ConfigureAwait(false); + await configuration.CertificateValidator.GetIssuers(clientCertificate, issuers, false).ConfigureAwait(false); for (int i = 0; i < issuers.Count; i++) { diff --git a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs index 6deed9b56..7e42ac1fd 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs @@ -104,7 +104,9 @@ public IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration( var rejectedRootType = CertificateStoreIdentifier.DetermineStoreType(rejectedRoot); ApplicationConfiguration.SecurityConfiguration = new SecurityConfiguration { // app cert store +#pragma warning disable CS0618 // Type or member is obsolete ApplicationCertificate = new CertificateIdentifier() { +#pragma warning restore CS0618 // Type or member is obsolete StoreType = appStoreType, StorePath = DefaultCertificateStorePath(TrustlistType.Application, appRoot), SubjectName = Utils.ReplaceDCLocalhost(subjectName) @@ -349,10 +351,24 @@ public IApplicationConfigurationBuilderServerSelected AddSignAndEncryptPolicies( return this; } + /// + public IApplicationConfigurationBuilderServerSelected AddEccSignPolicies() + { + AddEccSecurityPolicies(true); + return this; + } + + /// + public IApplicationConfigurationBuilderServerSelected AddEccSignAndEncryptPolicies() + { + AddEccSecurityPolicies(false); + return this; + } + /// public IApplicationConfigurationBuilderServerSelected AddPolicy(MessageSecurityMode securityMode, string securityPolicy) { - if (SecurityPolicies.GetDisplayName(securityPolicy) == null) throw new ArgumentException("Unknown security policy", nameof(securityPolicy)); + if (!SecurityPolicies.IsValidSecurityPolicyUri(securityPolicy)) throw new ArgumentException("Unknown security policy", nameof(securityPolicy)); if (securityMode == MessageSecurityMode.None || securityPolicy.Equals(SecurityPolicies.None)) throw new ArgumentException("Use AddUnsecurePolicyNone to add no security policy."); InternalAddPolicy(ApplicationConfiguration.ServerConfiguration.SecurityPolicies, securityMode, securityPolicy); return this; @@ -373,6 +389,13 @@ public IApplicationConfigurationBuilderServerSelected AddUserTokenPolicy(UserTok return this; } + /// + public IApplicationConfigurationBuilderSecurityOptions SetApplicationCertificateTypes(string types) + { + ApplicationConfiguration.SecurityConfiguration.ApplicationCertificateTypes = types; + return this; + } + /// public IApplicationConfigurationBuilderSecurityOptions SetAutoAcceptUntrustedCertificates(bool autoAccept) { @@ -949,20 +972,10 @@ private string DefaultCertificateStorePath(TrustlistType trustListType, string p private void AddSecurityPolicies(bool includeSign = false, bool deprecated = false, bool policyNone = false) { // create list of supported policies - string[] defaultPolicyUris = SecurityPolicies.GetDefaultUris(); + var defaultPolicyUris = SecurityPolicies.GetDefaultUris().ToList(); if (deprecated) { - string[] names = SecurityPolicies.GetDisplayNames(); - var deprecatedPolicyList = new List(); - foreach (string name in names) - { - string uri = SecurityPolicies.GetUri(name); - if (uri != null) - { - deprecatedPolicyList.Add(uri); - } - } - defaultPolicyUris = deprecatedPolicyList.ToArray(); + defaultPolicyUris.AddRange(SecurityPolicies.GetDefaultDeprecatedUris()); } foreach (MessageSecurityMode securityMode in typeof(MessageSecurityMode).GetEnumValues()) @@ -983,6 +996,23 @@ private void AddSecurityPolicies(bool includeSign = false, bool deprecated = fal } } + /// + /// Add default Ecc policies. + /// + private void AddEccSecurityPolicies(bool sign = false) + { + // create list of supported policies + var defaultPolicyUris = SecurityPolicies.GetDefaultEccUris(); + MessageSecurityMode securityMode = sign ? MessageSecurityMode.Sign : MessageSecurityMode.SignAndEncrypt; + { + var policies = ApplicationConfiguration.ServerConfiguration.SecurityPolicies; + foreach (var policyUri in defaultPolicyUris) + { + InternalAddPolicy(policies, securityMode, policyUri); + } + } + } + /// /// Set secure defaults for flags. /// diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index 56bc06e0a..ce75d740e 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -32,6 +32,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; @@ -401,12 +402,16 @@ public Task CheckApplicationInstanceCertificate( } /// - /// Delete the application certificate. + /// Deletes all application certificates. /// - public async Task DeleteApplicationInstanceCertificate() + public async Task DeleteApplicationInstanceCertificate(string[] profileIds = null) { + // TODO: delete only selected profiles if (m_applicationConfiguration == null) throw new ArgumentException("Missing configuration."); - await DeleteApplicationInstanceCertificate(m_applicationConfiguration).ConfigureAwait(false); + foreach (var id in m_applicationConfiguration.SecurityConfiguration.ApplicationCertificates) + { + await DeleteApplicationInstanceCertificate(m_applicationConfiguration, id).ConfigureAwait(false); + } } /// @@ -427,10 +432,42 @@ public async Task CheckApplicationInstanceCertificate( await LoadApplicationConfiguration(silent).ConfigureAwait(false); } - ApplicationConfiguration configuration = m_applicationConfiguration; + // find the existing certificates. + SecurityConfiguration securityConfiguration = m_applicationConfiguration.SecurityConfiguration; + + if (securityConfiguration.ApplicationCertificates.Count == 0) + { + throw new ServiceResultException(StatusCodes.BadConfigurationError, "Need at least one Application Certificate."); + } + + bool result = true; + foreach (var certId in securityConfiguration.ApplicationCertificates) + { + bool nextResult = await CheckCertificateTypeAsync(certId, silent, minimumKeySize, lifeTimeInMonths).ConfigureAwait(false); + result = result && nextResult; + } - // find the existing certificate. - CertificateIdentifier id = configuration.SecurityConfiguration.ApplicationCertificate; + return result; + } + #endregion + + #region Private Methods + /// + /// + /// + /// + /// + /// + /// + /// + private async Task CheckCertificateTypeAsync( + CertificateIdentifier id, + bool silent, + ushort minimumKeySize, + ushort lifeTimeInMonths + ) + { + ApplicationConfiguration configuration = m_applicationConfiguration; if (id == null) { @@ -440,7 +477,7 @@ public async Task CheckApplicationInstanceCertificate( // reload the certificate from disk in the cache. var passwordProvider = configuration.SecurityConfiguration.CertificatePasswordProvider; - await configuration.SecurityConfiguration.ApplicationCertificate.LoadPrivateKeyEx(passwordProvider).ConfigureAwait(false); + await id.LoadPrivateKeyEx(passwordProvider).ConfigureAwait(false); // load the certificate X509Certificate2 certificate = await id.Find(true).ConfigureAwait(false); @@ -449,7 +486,7 @@ public async Task CheckApplicationInstanceCertificate( if (certificate != null) { Utils.LogCertificate("Check certificate:", certificate); - bool certificateValid = await CheckApplicationInstanceCertificate(configuration, certificate, silent, minimumKeySize).ConfigureAwait(false); + bool certificateValid = await CheckApplicationInstanceCertificate(configuration, id, certificate, silent, minimumKeySize).ConfigureAwait(false); if (!certificateValid) { @@ -512,8 +549,7 @@ public async Task CheckApplicationInstanceCertificate( if (certificate == null) { - certificate = await CreateApplicationInstanceCertificate(configuration, - minimumKeySize, lifeTimeInMonths).ConfigureAwait(false); + certificate = await CreateApplicationInstanceCertificate(configuration, id, minimumKeySize, lifeTimeInMonths).ConfigureAwait(false); if (certificate == null) { @@ -566,6 +602,7 @@ public void OnCertificateValidation(object sender, CertificateValidationEventArg /// private async Task CheckApplicationInstanceCertificate( ApplicationConfiguration configuration, + CertificateIdentifier id, X509Certificate2 certificate, bool silent, ushort minimumKeySize) @@ -608,18 +645,21 @@ private async Task CheckApplicationInstanceCertificate( configuration.CertificateValidator.CertificateValidation -= certValidator.OnCertificateValidation; } - // check key size. - int keySize = X509Utils.GetRSAPublicKeySize(certificate); - if (minimumKeySize > keySize) + if (!X509Utils.IsECDsaSignature(certificate)) { - string message = Utils.Format( - "The key size ({0}) in the certificate is less than the minimum allowed ({1}). Use certificate anyway?", - keySize, - minimumKeySize); - - if (!await ApproveMessage(message, silent).ConfigureAwait(false)) + // check key size. + int keySize = X509Utils.GetRSAPublicKeySize(certificate); + if (minimumKeySize > keySize) { - return false; + string message = Utils.Format( + "The key size ({0}) in the certificate is less than the minimum provided ({1}). Use certificate anyway?", + keySize, + minimumKeySize); + + if (!await ApproveMessage(message, silent).ConfigureAwait(false)) + { + return false; + } } } @@ -652,7 +692,7 @@ private async Task CheckApplicationInstanceCertificate( Utils.LogInfo("Using the ApplicationUri: {0}", applicationUri); // update configuration. - configuration.SecurityConfiguration.ApplicationCertificate.Certificate = certificate; + id.Certificate = certificate; return true; } @@ -750,22 +790,22 @@ private async Task CheckDomainsInCertificate( /// Creates the application instance certificate. /// /// The configuration. + /// The certificate identifier. /// Size of the key. /// The lifetime in months. /// The new certificate private static async Task CreateApplicationInstanceCertificate( ApplicationConfiguration configuration, + CertificateIdentifier id, ushort keySize, ushort lifeTimeInMonths ) { // delete any existing certificate. - await DeleteApplicationInstanceCertificate(configuration).ConfigureAwait(false); + await DeleteApplicationInstanceCertificate(configuration, id).ConfigureAwait(false); Utils.LogInfo("Creating application instance certificate."); - CertificateIdentifier id = configuration.SecurityConfiguration.ApplicationCertificate; - // get the domains from the configuration file. IList serverDomainNames = configuration.GetServerDomainNames(); @@ -780,19 +820,72 @@ ushort lifeTimeInMonths Utils.GetAbsoluteDirectoryPath(id.StorePath, true, true, true); } + var builder = CertificateFactory.CreateCertificate( + configuration.ApplicationUri, + configuration.ApplicationName, + id.SubjectName, + serverDomainNames) + .SetLifeTime(lifeTimeInMonths); + + if (id.CertificateType == null || + id.CertificateType == ObjectTypeIds.ApplicationCertificateType || + id.CertificateType == ObjectTypeIds.RsaMinApplicationCertificateType || + id.CertificateType == ObjectTypeIds.RsaSha256ApplicationCertificateType) + { + id.Certificate = builder + .SetRSAKeySize(keySize) + .CreateForRSA(); + + Utils.LogCertificate("Certificate created for RSA.", id.Certificate); + } + else + { +#if !ECC_SUPPORT + throw new ServiceResultException(StatusCodes.BadConfigurationError, "The Ecc certificate type is not supported."); +#else + ECCurve curve = default(ECCurve); + if (id.CertificateType == ObjectTypeIds.EccApplicationCertificateType || + id.CertificateType == ObjectTypeIds.EccNistP256ApplicationCertificateType) + { + curve = ECCurve.NamedCurves.nistP256; + } + else if (id.CertificateType == ObjectTypeIds.EccNistP384ApplicationCertificateType) + { + curve = ECCurve.NamedCurves.nistP384; + } + else if (id.CertificateType == ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType) + { + curve = ECCurve.NamedCurves.brainpoolP256r1; + } + else if (id.CertificateType == ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType) + { + curve = ECCurve.NamedCurves.brainpoolP384r1; + } +#if CURVE25519 + else if (id.CertificateType == ObjectTypeIds.EccCurve25519ApplicationCertificateType) + { + curve = default(ECCurve); + } + else if (id.CertificateType == ObjectTypeIds.EccCurve448ApplicationCertificateType) + { + curve = default(ECCurve); + } +#endif + else + { + throw new ServiceResultException(StatusCodes.BadConfigurationError, "The ECC certificate type is not supported."); + } + + id.Certificate = builder + .SetECCurve(curve) + .CreateForECDsa(); + + Utils.LogCertificate("Certificate created for {0}.", id.Certificate, curve.Oid.FriendlyName); +#endif + } + var passwordProvider = configuration.SecurityConfiguration.CertificatePasswordProvider; - X509Certificate2 certificate = CertificateFactory.CreateCertificate( - configuration.ApplicationUri, - configuration.ApplicationName, - id.SubjectName, - serverDomainNames) - .SetLifeTime(lifeTimeInMonths) - .SetRSAKeySize(keySize) - .CreateForRSA(); - - // need id for password provider - id.Certificate = certificate; - certificate.AddToStore( + id.Certificate.AddToStore( id.StoreType, id.StorePath, passwordProvider?.GetPassword(id) @@ -801,15 +894,15 @@ ushort lifeTimeInMonths // ensure the certificate is trusted. if (configuration.SecurityConfiguration.AddAppCertToTrustedStore) { - await AddToTrustedStore(configuration, certificate).ConfigureAwait(false); + await AddToTrustedStore(configuration, id.Certificate).ConfigureAwait(false); } // reload the certificate from disk. - id.Certificate = await configuration.SecurityConfiguration.ApplicationCertificate.LoadPrivateKeyEx(passwordProvider).ConfigureAwait(false); + id.Certificate = await id.LoadPrivateKeyEx(passwordProvider).ConfigureAwait(false); await configuration.CertificateValidator.Update(configuration.SecurityConfiguration).ConfigureAwait(false); - Utils.LogCertificate("Certificate created for {0}.", certificate, configuration.ApplicationUri); + Utils.LogInfo("New Certificate saved, reloaded and activated."); // do not dispose temp cert, or X509Store certs become unusable @@ -820,11 +913,9 @@ ushort lifeTimeInMonths /// Deletes an existing application instance certificate. /// /// The configuration instance that stores the configurable information for a UA application. - private static async Task DeleteApplicationInstanceCertificate(ApplicationConfiguration configuration) + /// The certificate identifier. + private static async Task DeleteApplicationInstanceCertificate(ApplicationConfiguration configuration, CertificateIdentifier id) { - // create a default certificate id none specified. - CertificateIdentifier id = configuration.SecurityConfiguration.ApplicationCertificate; - if (id == null) { return; diff --git a/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs index a4d035150..e6834ce20 100644 --- a/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs @@ -338,6 +338,22 @@ public interface IApplicationConfigurationBuilderServerPolicies /// Add policies if true. IApplicationConfigurationBuilderServerSelected AddSignAndEncryptPolicies(bool addPolicies = true); + /// + /// Add the ECCsign security policies to the server configuration. + /// + /// + /// The policies are only added if the platform supports the ECC policies. + /// + IApplicationConfigurationBuilderServerSelected AddEccSignPolicies(); + + /// + /// Add the ECC sign and encrypt security policies to the server configuration. + /// + /// + /// The policies are only added if the platform supports the ECC policies. + /// + IApplicationConfigurationBuilderServerSelected AddEccSignAndEncryptPolicies(); + /// /// Add the specified security policy with the specified security mode. /// @@ -436,6 +452,16 @@ public interface IApplicationConfigurationBuilderSecurityOptions : IApplicationConfigurationBuilderExtension, IApplicationConfigurationBuilderCreate { + /// + /// The certificate types that should be supported. + /// + /// + /// At this time the following types are supported, if the platform OS and .NET version can handle it. + /// Rsa,nistP256,nistP384,brainpoolP256r1,brainpoolP384r1 + /// + /// A comma seperated list of supported types + IApplicationConfigurationBuilderSecurityOptions SetApplicationCertificateTypes(string types); + /// /// Whether an unknown application certificate should be accepted /// once all other security checks passed. diff --git a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj index 7b16a15e0..d24b37168 100644 --- a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj +++ b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj @@ -12,6 +12,22 @@ true + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + $(PackageId).Debug diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateBuilder.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateBuilder.cs index ff9a39ca1..1d648ebf9 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateBuilder.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateBuilder.cs @@ -149,7 +149,7 @@ public static byte[] CreatePfxWithRSAPrivateKey( { return X509Utils.CreatePfxWithPrivateKey( x509, friendlyName, - X509Utils.GetPrivateKeyParameter(privateKey), + X509Utils.GetRsaPrivateKeyParameter(privateKey), passcode, new SecureRandom(cfrg)); } @@ -170,8 +170,8 @@ public static byte[] CreateSigningRequest( SecureRandom random = new SecureRandom(cfrg); // try to get signing/private key from certificate passed in - AsymmetricKeyParameter signingKey = X509Utils.GetPrivateKeyParameter(certificate); - RsaKeyParameters publicKey = X509Utils.GetPublicKeyParameter(certificate); + AsymmetricKeyParameter signingKey = X509Utils.GetRsaPrivateKeyParameter(certificate); + RsaKeyParameters publicKey = X509Utils.GetRsaPublicKeyParameter(certificate); ISignatureFactory signatureFactory = new Asn1SignatureFactory(X509Utils.GetRSAHashAlgorithm(X509Defaults.HashAlgorithmName), signingKey, random); @@ -326,7 +326,7 @@ private void CreateExtensions(X509V3CertificateGenerator cg, AsymmetricKeyParame BigInteger issuerSerialNumber; if (IssuerCAKeyCert != null) { - issuerPublicKey = X509Utils.GetPublicKeyParameter(IssuerCAKeyCert); + issuerPublicKey = X509Utils.GetRsaPublicKeyParameter(IssuerCAKeyCert); issuerSerialNumber = X509Utils.GetSerialNumber(IssuerCAKeyCert); } else @@ -405,7 +405,7 @@ private X509Certificate2 CreateForRSAWithPublicKey(ISignatureFactory signatureFa CreateMandatoryFields(cg); // set public key - AsymmetricKeyParameter subjectPublicKey = X509Utils.GetPublicKeyParameter(m_rsaPublicKey); + AsymmetricKeyParameter subjectPublicKey = X509Utils.GetRsaPublicKeyParameter(m_rsaPublicKey); cg.SetPublicKey(subjectPublicKey); CreateExtensions(cg, subjectPublicKey); @@ -413,7 +413,7 @@ private X509Certificate2 CreateForRSAWithPublicKey(ISignatureFactory signatureFa // sign certificate by issuer if (signatureFactory == null) { - AsymmetricKeyParameter signingKey = X509Utils.GetPrivateKeyParameter(IssuerCAKeyCert); + AsymmetricKeyParameter signingKey = X509Utils.GetRsaPrivateKeyParameter(IssuerCAKeyCert); signatureFactory = new Asn1SignatureFactory(X509Utils.GetRSAHashAlgorithm(HashAlgorithmName), signingKey); } Org.BouncyCastle.X509.X509Certificate x509 = cg.Generate(signatureFactory); @@ -459,8 +459,8 @@ private byte[] CreatePfxForRSA(string passcode, ISignatureFactory signatureFacto AsymmetricKeyParameter subjectPrivateKey = null; using (var rsa = new RSACryptoServiceProvider(m_keySize == 0 ? X509Defaults.RSAKeySize : m_keySize)) { - subjectPublicKey = X509Utils.GetPublicKeyParameter(rsa); - subjectPrivateKey = X509Utils.GetPrivateKeyParameter(rsa); + subjectPublicKey = X509Utils.GetRsaPublicKeyParameter(rsa); + subjectPrivateKey = X509Utils.GetRsaPrivateKeyParameter(rsa); } cg.SetPublicKey(subjectPublicKey); @@ -473,7 +473,7 @@ private byte[] CreatePfxForRSA(string passcode, ISignatureFactory signatureFacto if (IssuerCAKeyCert != null) { // signed by issuer - signingKey = X509Utils.GetPrivateKeyParameter(IssuerCAKeyCert); + signingKey = X509Utils.GetRsaPrivateKeyParameter(IssuerCAKeyCert); } else { diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMWriter.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMWriter.cs index ebfad7f8e..b31ac967c 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMWriter.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMWriter.cs @@ -53,7 +53,7 @@ public static byte[] ExportPrivateKeyAsPEM( ) { if (!String.IsNullOrEmpty(password)) throw new ArgumentException("Export with password not supported on this platform.", nameof(password)); - RsaPrivateCrtKeyParameters privateKeyParameter = X509Utils.GetPrivateKeyParameter(certificate); + RsaPrivateCrtKeyParameters privateKeyParameter = X509Utils.GetRsaPrivateKeyParameter(certificate); // write private key as PKCS#8 PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKeyParameter); byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetDerEncoded(); diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs index ae896e0e7..ea78ea47d 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs @@ -104,18 +104,18 @@ internal static string GetRSAHashAlgorithm(HashAlgorithmName hashAlgorithmName) /// /// Get public key parameters from a X509Certificate2 /// - internal static RsaKeyParameters GetPublicKeyParameter(X509Certificate2 certificate) + internal static RsaKeyParameters GetRsaPublicKeyParameter(X509Certificate2 certificate) { using (RSA rsa = certificate.GetRSAPublicKey()) { - return GetPublicKeyParameter(rsa); + return GetRsaPublicKeyParameter(rsa); } } /// /// Get public key parameters from a RSA. /// - internal static RsaKeyParameters GetPublicKeyParameter(RSA rsa) + internal static RsaKeyParameters GetRsaPublicKeyParameter(RSA rsa) { RSAParameters rsaParams = rsa.ExportParameters(false); return new RsaKeyParameters( @@ -128,20 +128,24 @@ internal static RsaKeyParameters GetPublicKeyParameter(RSA rsa) /// Get private key parameters from a X509Certificate2. /// The private key must be exportable. /// - internal static RsaPrivateCrtKeyParameters GetPrivateKeyParameter(X509Certificate2 certificate) + internal static RsaPrivateCrtKeyParameters GetRsaPrivateKeyParameter(X509Certificate2 certificate) { // try to get signing/private key from certificate passed in using (RSA rsa = certificate.GetRSAPrivateKey()) { - return GetPrivateKeyParameter(rsa); + if (rsa != null) + { + return GetRsaPrivateKeyParameter(rsa); + } } + return null; } /// /// Get private key parameters from a RSA private key. /// The private key must be exportable. /// - internal static RsaPrivateCrtKeyParameters GetPrivateKeyParameter(RSA rsa) + internal static RsaPrivateCrtKeyParameters GetRsaPrivateKeyParameter(RSA rsa) { RSAParameters rsaParams = rsa.ExportParameters(true); return new RsaPrivateCrtKeyParameters( @@ -155,6 +159,25 @@ internal static RsaPrivateCrtKeyParameters GetPrivateKeyParameter(RSA rsa) new BigInteger(1, rsaParams.InverseQ)); } +#if NET472_OR_GREATER + /// + /// Get private key parameters from a ECDsa private key. + /// The private key must be exportable. + /// + internal static ECPrivateKeyParameters GetECPrivateKeyParameter(ECDsa ec) + { + ECParameters ecParams = ec.ExportParameters(true); + BigInteger d = new BigInteger(1, ecParams.D); + throw new NotImplementedException(nameof(GetECPrivateKeyParameter)); +#if TODO + ECDomainParameters parameters = new ECDomainParameters( + new Org.BouncyCastle.Math.EC.ECCurve( + new BigInteger(1, ecParams.Q)); + + return new ECPrivateKeyParameters(d, parameters); +#endif + } +#endif /// /// Get the serial number from a certificate as BigInteger. diff --git a/Libraries/Opc.Ua.Security.Certificates/PEM/PEMReader.cs b/Libraries/Opc.Ua.Security.Certificates/PEM/PEMReader.cs index 234e8dd84..7f3ccf714 100644 --- a/Libraries/Opc.Ua.Security.Certificates/PEM/PEMReader.cs +++ b/Libraries/Opc.Ua.Security.Certificates/PEM/PEMReader.cs @@ -41,7 +41,7 @@ namespace Opc.Ua.Security.Certificates /// public static class PEMReader { - #region Public Methods +#region Public Methods /// /// Import a PKCS#8 private key or RSA private key from PEM. /// The PKCS#8 private key may be encrypted using a password. @@ -109,10 +109,10 @@ public static RSA ImportPrivateKeyFromPEM( } throw new ArgumentException("No private PEM key found."); } - #endregion +#endregion - #region Private Methods - #endregion +#region Private Methods +#endregion } } #endif diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs index ac50044d0..d10bdd862 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs @@ -274,7 +274,21 @@ public override ICertificateBuilderCreateForECDsaAny SetECDsaPublicKey(byte[] pu try { m_ecdsaPublicKey = ECDsa.Create(); +#if NET472_OR_GREATER + var asymmetricKeyParameter = Org.BouncyCastle.Security.PublicKeyFactory.CreateKey(publicKey); + var ecKeyParameters = asymmetricKeyParameter as Org.BouncyCastle.Crypto.Parameters.ECKeyParameters; + var parameters = new ECParameters { + Curve = default(ECCurve), + Q = new ECPoint() { + X = ecKeyParameters.Parameters.G.XCoord.ToBigInteger().ToByteArrayUnsigned(), + Y = ecKeyParameters.Parameters.G.YCoord.ToBigInteger().ToByteArrayUnsigned(), + } + }; + m_ecdsaPublicKey.ImportParameters(parameters); + bytes = publicKey.Length; +#else m_ecdsaPublicKey.ImportSubjectPublicKeyInfo(publicKey, out bytes); +#endif } catch (Exception e) { diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs index 4a5706cc9..87c7dd72c 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs @@ -57,6 +57,28 @@ private static X509KeyUsageFlags GetKeyUsage(X509Certificate2 cert) return allFlags; } + /// + /// Verify RSA key pair of two certificates. + /// + public static bool VerifyKeyPair( + X509Certificate2 certWithPublicKey, + X509Certificate2 certWithPrivateKey, + bool throwOnError = false) + { + if (IsECDsaSignature(certWithPublicKey)) + { +#if ECC_SUPPORT + return VerifyECDsaKeyPair(certWithPublicKey, certWithPrivateKey, throwOnError); +#else + throw new NotSupportedException("This platform does not support ECC."); +#endif + } + else + { + return VerifyRSAKeyPair(certWithPublicKey, certWithPrivateKey, throwOnError); + } + } + /// /// Verify RSA key pair of two certificates. /// @@ -142,8 +164,7 @@ string password rawData, password ?? String.Empty, flag); - // can we really access the private key? - if (VerifyRSAKeyPair(certificate, certificate, true)) + if (VerifyKeyPair(certificate, certificate, true)) { return certificate; } @@ -197,6 +218,22 @@ internal static bool VerifyRSAKeyPairSign( return rsaPublicKey.VerifyData(testBlock, signature, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); } + /// + /// If the certificate has a ECDsa signature. + /// + public static bool IsECDsaSignature(X509Certificate2 cert) + { + switch (cert.SignatureAlgorithm.Value) + { + case Oids.ECDsaWithSha1: + case Oids.ECDsaWithSha256: + case Oids.ECDsaWithSha384: + case Oids.ECDsaWithSha512: + return true; + } + return false; + } + #if ECC_SUPPORT /// /// Verify ECDsa key pair of two certificates. diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 0c80363e6..c03b94805 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -122,8 +122,12 @@ ApplicationConfiguration configuration ServerCertificateGroup defaultApplicationGroup = new ServerCertificateGroup { NodeId = Opc.Ua.ObjectIds.ServerConfiguration_CertificateGroups_DefaultApplicationGroup, BrowseName = Opc.Ua.BrowseNames.DefaultApplicationGroup, + // TODO CertificateTypes = new NodeId[] { ObjectTypeIds.RsaSha256ApplicationCertificateType }, + // TODO +#pragma warning disable CS0618 // Type or member is obsolete ApplicationCertificate = configuration.SecurityConfiguration.ApplicationCertificate, +#pragma warning restore CS0618 // Type or member is obsolete IssuerStorePath = configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath, TrustedStorePath = configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath }; @@ -499,6 +503,7 @@ private ServiceResult UpdateCertificate( { try { + // TODO using (ICertificateStore appStore = certificateGroup.ApplicationCertificate.OpenStore()) { Utils.LogCertificate(Utils.TraceMasks.Security, "Delete application certificate: ", certificateGroup.ApplicationCertificate.Certificate); diff --git a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs index f95787c7e..e0fb60d70 100644 --- a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs +++ b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs @@ -65,13 +65,13 @@ public class ServerInternalData : IServerInternal, IDisposable /// The configuration. /// The message context. /// The certificate validator. - /// The instance certificate. + /// The certificate type provider. public ServerInternalData( ServerProperties serverDescription, ApplicationConfiguration configuration, IServiceMessageContext messageContext, CertificateValidator certificateValidator, - X509Certificate2 instanceCertificate) + CertificateTypesProvider instanceCertificateProvider) { m_serverDescription = serverDescription; m_configuration = configuration; @@ -448,7 +448,8 @@ public bool IsRunning if (m_serverStatus.Value.State == ServerState.Running) return true; - if (m_serverStatus.Value.State == ServerState.Shutdown && m_serverStatus.Value.SecondsTillShutdown > 0) + if (m_serverStatus.Value.State == ServerState.Shutdown && + m_serverStatus.Value.SecondsTillShutdown > 0) return true; return false; diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs index 7b59e3107..406558045 100644 --- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs +++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs @@ -422,10 +422,17 @@ public override ResponseHeader CreateSession( } } + // load the certificate for the security profile + X509Certificate2 instanceCertificate = null; + if (requireEncryption) + { + instanceCertificate = InstanceCertificateTypesProvider.GetInstanceCertificate(context.SecurityPolicyUri); + } + // create the session. session = ServerInternal.SessionManager.CreateSession( context, - requireEncryption ? InstanceCertificate : null, + instanceCertificate, sessionName, clientNonce, clientDescription, @@ -447,7 +454,7 @@ public override ResponseHeader CreateSession( EndpointUrl = new Uri(endpointUrl) }; - CertificateValidator.ValidateDomains(InstanceCertificate, configuredEndpoint, true); + CertificateValidator.ValidateDomains(instanceCertificate, configuredEndpoint, true); } catch (ServiceResultException sre) when (sre.StatusCode == StatusCodes.BadCertificateHostNameInvalid) { @@ -462,22 +469,13 @@ public override ResponseHeader CreateSession( if (requireEncryption) { // check if complete chain should be sent. - if (Configuration.SecurityConfiguration.SendCertificateChain && - InstanceCertificateChain != null && - InstanceCertificateChain.Count > 1) + if (Configuration.SecurityConfiguration.SendCertificateChain) { - List serverCertificateChain = new List(); - - for (int i = 0; i < InstanceCertificateChain.Count; i++) - { - serverCertificateChain.AddRange(InstanceCertificateChain[i].RawData); - } - - serverCertificate = serverCertificateChain.ToArray(); + serverCertificate = InstanceCertificateTypesProvider.LoadCertificateChainRawAsync(instanceCertificate).GetAwaiter().GetResult(); } else { - serverCertificate = InstanceCertificate.RawData; + serverCertificate = instanceCertificate.RawData; } } @@ -494,7 +492,7 @@ public override ResponseHeader CreateSession( if (parsedClientCertificate != null && clientNonce != null) { byte[] dataToSign = Utils.Append(parsedClientCertificate.RawData, clientNonce); - serverSignature = SecurityPolicies.Sign(InstanceCertificate, context.SecurityPolicyUri, dataToSign); + serverSignature = SecurityPolicies.Sign(instanceCertificate, context.SecurityPolicyUri, dataToSign); } } @@ -2294,11 +2292,12 @@ public bool RegisterWithDiscoveryServer() requestHeader.Timestamp = DateTime.UtcNow; // create the client. + var instanceCertificate = InstanceCertificateTypesProvider.GetInstanceCertificate(null); client = RegistrationClient.Create( configuration, endpoint.Description, endpoint.Configuration, - base.InstanceCertificate); + instanceCertificate); client.OperationTimeout = 10000; @@ -2853,8 +2852,7 @@ protected override IList InitializeServiceHosts( configuration.ServerConfiguration.BaseAddresses, serverDescription, configuration.ServerConfiguration.SecurityPolicies, - InstanceCertificate, - InstanceCertificateChain + InstanceCertificateTypesProvider ); endpoints.AddRange(endpointsForHost); } @@ -2907,7 +2905,7 @@ protected override void StartApplication(ApplicationConfiguration configuration) configuration, MessageContext, new CertificateValidator(), - InstanceCertificate); + InstanceCertificateTypesProvider); // create the manager responsible for providing localized string resources. Utils.LogInfo(TraceMasks.StartStop, "Server - CreateResourceManager."); diff --git a/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj b/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj index d709e861b..f93c62c86 100644 --- a/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj +++ b/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj @@ -41,7 +41,7 @@ - + diff --git a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsServiceHost.cs b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsServiceHost.cs index aad624e8a..7350e4395 100644 --- a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsServiceHost.cs +++ b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsServiceHost.cs @@ -41,8 +41,7 @@ public List CreateServiceHost( IList baseAddresses, ApplicationDescription serverDescription, List securityPolicies, - X509Certificate2 instanceCertificate, - X509Certificate2Collection instanceCertificateChain + CertificateTypesProvider certificateTypesProvider ) { // generate a unique host name. @@ -107,24 +106,17 @@ X509Certificate2Collection instanceCertificateChain description.EndpointUrl = uri.ToString(); description.Server = serverDescription; - if (instanceCertificate != null) - { - description.ServerCertificate = instanceCertificate.RawData; - // check if complete chain should be sent. - if (configuration.SecurityConfiguration.SendCertificateChain && - instanceCertificateChain != null && - instanceCertificateChain.Count > 1) + if (certificateTypesProvider != null) { - List serverCertificateChain = new List(); + var instanceCertificate = certificateTypesProvider.GetInstanceCertificate(bestPolicy.SecurityPolicyUri); + description.ServerCertificate = instanceCertificate.RawData; - for (int i = 0; i < instanceCertificateChain.Count; i++) + // check if complete chain should be sent. + if (configuration.SecurityConfiguration.SendCertificateChain) { - serverCertificateChain.AddRange(instanceCertificateChain[i].RawData); + description.ServerCertificate = certificateTypesProvider.LoadCertificateChainRawAsync(instanceCertificate).GetAwaiter().GetResult(); } - - description.ServerCertificate = serverCertificateChain.ToArray(); } - } description.SecurityMode = bestPolicy.SecurityMode; description.SecurityPolicyUri = bestPolicy.SecurityPolicyUri; @@ -145,10 +137,9 @@ X509Certificate2Collection instanceCertificateChain } } - // create the host. - ServiceHost serviceHost = serverBase.CreateServiceHost(serverBase, uris.ToArray()); - - hosts[hostName] = serviceHost; + // create the host. + hosts[hostName] = serverBase.CreateServiceHost(serverBase, uris.ToArray()); + } return endpoints; } diff --git a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs index 3478ee53b..ec7421135 100644 --- a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs +++ b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs @@ -194,8 +194,7 @@ public void Open( // save the callback to the server. m_callback = callback; - m_serverCertificate = settings.ServerCertificate; - m_serverCertificateChain = settings.ServerCertificateChain; + m_serverCertProvider = settings.ServerCertificateTypesProvider; // start the listener Start(); @@ -249,12 +248,11 @@ public void Start() { Startup.Listener = this; m_hostBuilder = new WebHostBuilder(); - var httpsOptions = new HttpsConnectionAdapterOptions() { - CheckCertificateRevocation = false, - ClientCertificateMode = ClientCertificateMode.NoCertificate, - // note: this is the TLS certificate! - ServerCertificate = m_serverCertificate - }; + HttpsConnectionAdapterOptions httpsOptions = new HttpsConnectionAdapterOptions(); + httpsOptions.CheckCertificateRevocation = false; + httpsOptions.ClientCertificateMode = ClientCertificateMode.NoCertificate; + // note: if there is not a specific Https cert defined, the first App cert is used + httpsOptions.ServerCertificate = m_serverCertProvider.GetInstanceCertificate(SecurityPolicies.Https); #if NET462 // note: although security tools recommend 'None' here, @@ -436,14 +434,12 @@ public async Task SendAsync(HttpContext context) /// public void CertificateUpdate( ICertificateValidator validator, - X509Certificate2 serverCertificate, - X509Certificate2Collection serverCertificateChain) + CertificateTypesProvider certificateTypeProvider) { Stop(); m_quotas.CertificateValidator = validator; - m_serverCertificate = serverCertificate; - m_serverCertificateChain = serverCertificateChain; + m_serverCertProvider = certificateTypeProvider; foreach (var description in m_descriptions) { // check if complete chain should be sent. @@ -461,7 +457,8 @@ public void CertificateUpdate( } else if (description.ServerCertificate != null) { - description.ServerCertificate = serverCertificate.RawData; + description.ServerCertificate = + m_serverCertProvider.GetInstanceCertificate(description.SecurityPolicyUri).RawData; } } @@ -512,8 +509,7 @@ private static async Task ReadBodyAsync(HttpRequest req) private ITransportListenerCallback m_callback; private IWebHostBuilder m_hostBuilder; private IWebHost m_host; - private X509Certificate2 m_serverCertificate; - private X509Certificate2Collection m_serverCertificateChain; + private CertificateTypesProvider m_serverCertProvider; #endregion } } diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index 575e48bc5..758618e31 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -11,6 +11,22 @@ true + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + $(PackageId).Debug diff --git a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs index 8e6b22fb1..6bd6cded3 100644 --- a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs +++ b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs @@ -668,9 +668,15 @@ public static byte CalculateSecurityLevel(MessageSecurityMode mode, string polic switch (policyUri) { case SecurityPolicies.Basic128Rsa15: result = 2; break; + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: case SecurityPolicies.Basic256: result = 4; break; case SecurityPolicies.Basic256Sha256: result = 6; break; + case SecurityPolicies.ECC_nistP256: result = 12; break; + case SecurityPolicies.ECC_brainpoolP256r1: result = 11; break; case SecurityPolicies.Aes128_Sha256_RsaOaep: result = 8; break; + case SecurityPolicies.ECC_nistP384: result = 14; break; + case SecurityPolicies.ECC_brainpoolP384r1: result = 13; break; case SecurityPolicies.Aes256_Sha256_RsaPss: result = 10; break; case SecurityPolicies.None: default: return 0; @@ -764,6 +770,7 @@ public SecurityConfiguration() /// private void Initialize() { + m_applicationCertificates = new CertificateIdentifierCollection(); m_trustedIssuerCertificates = new CertificateTrustList(); m_trustedPeerCertificates = new CertificateTrustList(); m_nonceLength = 32; @@ -774,6 +781,7 @@ private void Initialize() m_addAppCertToTrustedStore = true; m_sendCertificateChain = true; m_suppressNonceValidationErrors = false; + m_supportedSecurityPolicies = new StringCollection(); } /// @@ -792,17 +800,72 @@ private void Initialize() /// This certificate must contain the application uri. /// For servers, URLs for each supported protocol must also be present. /// - [DataMember(IsRequired = true, EmitDefaultValue = false, Order = 0)] + [DataMember(IsRequired = true, EmitDefaultValue = false, Order = 10)] public CertificateIdentifier ApplicationCertificate { - get { return m_applicationCertificate; } - set { m_applicationCertificate = value; } + get + { + if (m_applicationCertificates.Count > 0) + { + return m_applicationCertificates[0]; + } + return null; + } + set + { + if (m_applicationCertificates.Count > 0) + { + if (value == null) + { + m_applicationCertificates.RemoveAt(0); + } + else + { + m_applicationCertificates[0] = value; + } + } + else + { + m_applicationCertificates.Add(value); + } + SupportedSecurityPolicies = BuildSupportedSecurityPolicies(); + } + } + + /// + /// The application instance certificates in use for the application. + /// + public CertificateIdentifierCollection ApplicationCertificates + { + get + { + return m_applicationCertificates; + } + } + + /// + /// The supported application certificate types. + /// + /// + /// The application by default only supports RSA certificates. + /// To use more certificate types add the types to the list, + /// e.g. NistP384, BrainpoolP384r1. + /// + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 15)] + public string ApplicationCertificateTypes + { + get { return EncodeApplicationCertificateTypes(); } + set + { + DecodeApplicationCertificateTypes(value); + SupportedSecurityPolicies = BuildSupportedSecurityPolicies(); + } } /// /// The store containing any additional issuer certificates. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 2)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 20)] public CertificateTrustList TrustedIssuerCertificates { get @@ -819,7 +882,7 @@ public CertificateTrustList TrustedIssuerCertificates /// /// The trusted certificate store. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 4)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 40)] public CertificateTrustList TrustedPeerCertificates { get @@ -839,7 +902,7 @@ public CertificateTrustList TrustedPeerCertificates /// /// The length of nonce in the CreateSession service. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 6)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 60)] public int NonceLength { get { return m_nonceLength; } @@ -852,7 +915,7 @@ public int NonceLength /// /// A store where invalid certificates can be placed for later review by the administrator. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 7)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 70)] public CertificateStoreIdentifier RejectedCertificateStore { get { return m_rejectedCertificateStore; } @@ -866,7 +929,7 @@ public CertificateStoreIdentifier RejectedCertificateStore /// This flag can be set to by servers that allow anonymous clients or use user credentials for authentication. /// It can be set by clients that connect to URLs specified in configuration rather than with user entry. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 8)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 80)] public bool AutoAcceptUntrustedCertificates { get { return m_autoAcceptUntrustedCertificates; } @@ -889,7 +952,7 @@ public string UserRoleDirectory /// /// This flag can be set to false by servers that accept SHA-1 signed certificates. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 10)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 100)] public bool RejectSHA1SignedCertificates { get { return m_rejectSHA1SignedCertificates; } @@ -902,7 +965,7 @@ public bool RejectSHA1SignedCertificates /// /// This flag can be set to true by servers that must have a revocation list for each CA (even if empty). /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 11)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 110)] public bool RejectUnknownRevocationStatus { get { return m_rejectUnknownRevocationStatus; } @@ -911,11 +974,12 @@ public bool RejectUnknownRevocationStatus /// /// Gets or sets a value indicating which minimum certificate key strength is accepted. + /// The value is ignored for certificates with a ECDSA signature. /// /// /// This value can be set to 1024, 2048 or 4096 by servers /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 12)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 120)] public ushort MinimumCertificateKeySize { get { return m_minCertificateKeySize; } @@ -929,7 +993,7 @@ public ushort MinimumCertificateKeySize /// /// This flag can be set to true by applications. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 13)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 130)] public bool UseValidatedCertificates { get { return m_useValidatedCertificates; } @@ -942,7 +1006,7 @@ public bool UseValidatedCertificates /// /// It is useful for client/server applications running on the same host and sharing the cert store to autotrust. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 14)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 140)] public bool AddAppCertToTrustedStore { get { return m_addAppCertToTrustedStore; } @@ -955,7 +1019,7 @@ public bool AddAppCertToTrustedStore /// /// If set to true the complete certificate chain will be sent for CA signed certificates. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 15)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 150)] public bool SendCertificateChain { get { return m_sendCertificateChain; } @@ -965,7 +1029,7 @@ public bool SendCertificateChain /// /// The store containing additional user issuer certificates. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 16)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 160)] public CertificateTrustList UserIssuerCertificates { get @@ -987,7 +1051,7 @@ public CertificateTrustList UserIssuerCertificates /// /// The store containing trusted user certificates. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 17)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 170)] public CertificateTrustList TrustedUserCertificates { get @@ -1009,7 +1073,7 @@ public CertificateTrustList TrustedUserCertificates /// /// The store containing additional Https issuer certificates. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 18)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 180)] public CertificateTrustList HttpsIssuerCertificates { get @@ -1031,7 +1095,7 @@ public CertificateTrustList HttpsIssuerCertificates /// /// The store containing trusted Https certificates. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 19)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 190)] public CertificateTrustList TrustedHttpsCertificates { get @@ -1058,7 +1122,7 @@ public CertificateTrustList TrustedHttpsCertificates /// If set to true the server nonce validation errors are suppressed. /// Please set this flag to true only in close and secured networks since it can cause security vulnerabilities. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 20)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 200)] public bool SuppressNonceValidationErrors { get { return m_suppressNonceValidationErrors; } @@ -1067,13 +1131,14 @@ public bool SuppressNonceValidationErrors #endregion #region Private Fields - private CertificateIdentifier m_applicationCertificate; + private CertificateIdentifierCollection m_applicationCertificates; private CertificateTrustList m_trustedIssuerCertificates; private CertificateTrustList m_trustedPeerCertificates; private CertificateTrustList m_httpsIssuerCertificates; private CertificateTrustList m_trustedHttpsCertificates; private CertificateTrustList m_userIssuerCertificates; private CertificateTrustList m_trustedUserCertificates; + // TODO: is this really necessary? private int m_nonceLength; private CertificateStoreIdentifier m_rejectedCertificateStore; private bool m_autoAcceptUntrustedCertificates; @@ -1085,6 +1150,7 @@ public bool SuppressNonceValidationErrors private bool m_addAppCertToTrustedStore; private bool m_sendCertificateChain; private bool m_suppressNonceValidationErrors; + private StringCollection m_supportedSecurityPolicies; #endregion } #endregion @@ -2967,7 +3033,7 @@ private void Initialize() /// The type of certificate store. /// /// The type of the store - defined in the . - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 0)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 10)] public string StoreType { get @@ -2990,7 +3056,7 @@ public string StoreType /// The path that identifies the certificate store. /// /// The store path in the form StoreName\\Store Location . - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 1)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 15)] public string StorePath { get @@ -3027,7 +3093,7 @@ public string StorePath /// /// The name of the store. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 2)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 20)] [Obsolete("Use StoreType/StorePath instead")] public string StoreName { @@ -3040,7 +3106,7 @@ public string StoreName /// /// The store location. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 3)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 30)] [Obsolete("Use StoreType/StorePath instead")] public string StoreLocation { @@ -3095,7 +3161,7 @@ public string StoreLocation /// /// /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 4)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 40)] public string SubjectName { get @@ -3127,7 +3193,7 @@ public string SubjectName /// /// The thumbprint of a certificate.. /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 5)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 50)] public string Thumbprint { get @@ -3158,7 +3224,7 @@ public string Thumbprint /// Gets the DER encoded certificate data or create emebeded in this instance certifcate using the DER encoded certificate data. /// /// A byte array containing the X.509 certificate data. - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 6)] + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 60)] public byte[] RawData { get @@ -3189,12 +3255,23 @@ public byte[] RawData /// Gets or sets the XML encoded validation options - use to serialize the validation options. /// /// The XML encoded validation options. - [DataMember(Name = "ValidationOptions", IsRequired = false, EmitDefaultValue = false, Order = 7)] + [DataMember(Name = "ValidationOptions", IsRequired = false, EmitDefaultValue = false, Order = 70)] private int XmlEncodedValidationOptions { get { return (int)m_validationOptions; } set { m_validationOptions = (CertificateValidationOptions)value; } } + + /// + /// Gets or sets the certificate type. + /// + /// The NodeId of the certificate type, e.g. EccNistP256ApplicationCertificateType. + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 80)] + public NodeId CertificateType + { + get { return m_certificateType; } + set { m_certificateType = value; } + } #endregion #region Private Fields @@ -3205,6 +3282,7 @@ private int XmlEncodedValidationOptions private string m_subjectName; private string m_thumbprint; private X509Certificate2 m_certificate; + private NodeId m_certificateType; private CertificateValidationOptions m_validationOptions; #endregion } diff --git a/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs b/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs index ca51be734..f1823b20d 100644 --- a/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs +++ b/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs @@ -395,11 +395,19 @@ private static ServerSecurityPolicy CreatePolicy(string profileUri) case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Aes256_Sha256_RsaPss: + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: { policy.SecurityMode = MessageSecurityMode.SignAndEncrypt; break; } + default: + break; } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs index fa9d8a3dc..4b109fb0d 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs @@ -100,7 +100,7 @@ public static X509Certificate2 Load(X509Certificate2 certificate, bool ensurePri if (ensurePrivateKeyAccessible) { - if (!X509Utils.VerifyRSAKeyPair(certificate, certificate)) + if (!X509Utils.VerifyKeyPair(certificate, certificate)) { Utils.LogWarning("Trying to add certificate to cache with invalid private key."); return null; @@ -201,7 +201,7 @@ public static X509Certificate2 CreateCertificate( } /// - /// Revoke the certificate. + /// Revoke the certificate. /// The CRL number is increased by one and the new CRL is returned. /// public static X509CRL RevokeCertificate( @@ -334,7 +334,7 @@ public static byte[] CreateSigningRequest( /// - /// Create a X509Certificate2 with a private key by combining + /// Create a X509Certificate2 with a private key by combining /// the new certificate with a private key from an existing certificate /// public static X509Certificate2 CreateCertificateWithPrivateKey( @@ -346,12 +346,28 @@ public static X509Certificate2 CreateCertificateWithPrivateKey( throw new NotSupportedException("Need a certificate with a private key."); } - if (!X509Utils.VerifyRSAKeyPair(certificate, certificateWithPrivateKey)) + if (X509Utils.IsECDsaSignature(certificate)) { - throw new NotSupportedException("The public and the private key pair doesn't match."); + if (!X509Utils.VerifyECDsaKeyPair(certificate, certificateWithPrivateKey)) + { + throw new NotSupportedException("The public and the private key pair doesn't match."); + } + using (ECDsa privateKey = certificateWithPrivateKey.GetECDsaPrivateKey()) + { + return certificate.CopyWithPrivateKey(privateKey); + } + } + else + { + if (!X509Utils.VerifyRSAKeyPair(certificate, certificateWithPrivateKey)) + { + throw new NotSupportedException("The public and the private key pair doesn't match."); + } + using (RSA privateKey = certificateWithPrivateKey.GetRSAPrivateKey()) + { + return certificate.CopyWithPrivateKey(privateKey); + } } - - return certificate.CopyWithPrivateKey(certificateWithPrivateKey.GetRSAPrivateKey()); } /// diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index 999d09d35..500a6098a 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -13,6 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -114,6 +115,11 @@ public override bool Equals(object obj) return false; } + if (CertificateType != id.CertificateType) + { + return false; + } + return true; } @@ -167,7 +173,7 @@ public Task LoadPrivateKey(string password) => LoadPrivateKeyEx(password != null ? new CertificatePasswordProvider(password) : null); /// - /// Loads the private key for the certificate with an optional password. + /// Loads the private key for the certificate with an optional password provider. /// public async Task LoadPrivateKeyEx(ICertificatePasswordProvider passwordProvider) { @@ -179,7 +185,7 @@ public async Task LoadPrivateKeyEx(ICertificatePasswordProvide { store.Open(this.StorePath, false); string password = passwordProvider?.GetPassword(this); - m_certificate = await store.LoadPrivateKey(this.Thumbprint, this.SubjectName, password).ConfigureAwait(false); + m_certificate = await store.LoadPrivateKey(this.Thumbprint, this.SubjectName, this.CertificateType, password).ConfigureAwait(false); return m_certificate; } } @@ -190,6 +196,7 @@ public async Task LoadPrivateKeyEx(ICertificatePasswordProvide /// /// Finds a certificate in a store. /// + /// The certificate type is used to match the signature and public key type. /// if set to true the returned certificate must contain the private key. /// An instance of the that is embedded by this instance or find it in /// the selected store pointed out by the using selected . @@ -211,7 +218,7 @@ public async Task Find(bool needPrivateKey) X509Certificate2Collection collection = await store.Enumerate().ConfigureAwait(false); - certificate = Find(collection, m_thumbprint, m_subjectName, needPrivateKey); + certificate = Find(collection, m_thumbprint, m_subjectName, m_certificateType, needPrivateKey); if (certificate != null) { @@ -248,6 +255,7 @@ private void Paste(CertificateIdentifier certificate) this.RawData = certificate.RawData; this.ValidationOptions = certificate.ValidationOptions; this.Certificate = certificate.Certificate; + this.CertificateType = certificate.CertificateType; } /// @@ -317,9 +325,15 @@ private static string GetDisplayName(X509Certificate2 certificate) /// The collection. /// The thumbprint of the certificate. /// Subject name of the certificate. + /// The certificate type. /// if set to true [need private key]. /// - public static X509Certificate2 Find(X509Certificate2Collection collection, string thumbprint, string subjectName, bool needPrivateKey) + public static X509Certificate2 Find( + X509Certificate2Collection collection, + string thumbprint, + string subjectName, + NodeId certificateType, + bool needPrivateKey) { // find by thumbprint. if (!String.IsNullOrEmpty(thumbprint)) @@ -353,9 +367,10 @@ public static X509Certificate2 Find(X509Certificate2Collection collection, strin foreach (X509Certificate2 certificate in collection) { - if (X509Utils.CompareDistinguishedName(certificate, subjectName2)) + if (ValidateCertificateType(certificate, certificateType) && + X509Utils.CompareDistinguishedName(certificate, subjectName2)) { - if ((!needPrivateKey || certificate.HasPrivateKey) && X509Utils.GetRSAPublicKeySize(certificate) >= 0) + if (!needPrivateKey || certificate.HasPrivateKey) { return certificate; } @@ -366,7 +381,8 @@ public static X509Certificate2 Find(X509Certificate2Collection collection, strin foreach (X509Certificate2 certificate in collection) { - if ((!needPrivateKey || certificate.HasPrivateKey) && X509Utils.GetRSAPublicKeySize(certificate) >= 0) + if (ValidateCertificateType(certificate, certificateType) && + (!needPrivateKey || certificate.HasPrivateKey)) { return certificate; } @@ -492,6 +508,138 @@ public ICertificateStore OpenStore() store.Open(this.StorePath, false); return store; } + + /// + /// Get the OPC UA CertificateType. + /// + /// The certificate with a signature. + public static NodeId GetCertificateType(X509Certificate2 certificate) + { + switch (certificate.SignatureAlgorithm.Value) + { + case Oids.ECDsaWithSha1: + case Oids.ECDsaWithSha384: + case Oids.ECDsaWithSha256: + case Oids.ECDsaWithSha512: + return EccUtils.GetEccCertificateTypeId(certificate); + + case Oids.RsaPkcs1Sha256: + case Oids.RsaPkcs1Sha384: + case Oids.RsaPkcs1Sha512: + return ObjectTypeIds.RsaSha256ApplicationCertificateType; + case Oids.RsaPkcs1Sha1: + return ObjectTypeIds.RsaMinApplicationCertificateType; + } + return NodeId.Null; + } + + /// + /// Validate if the certificate matches the CertificateType. + /// + /// The certificate with a signature. + /// The NodeId of the certificate type. + public static bool ValidateCertificateType(X509Certificate2 certificate, NodeId certificateType) + { + switch (certificate.SignatureAlgorithm.Value) + { + case Oids.ECDsaWithSha1: + case Oids.ECDsaWithSha384: + case Oids.ECDsaWithSha256: + case Oids.ECDsaWithSha512: + var certType = EccUtils.GetEccCertificateTypeId(certificate); + if (certType.IsNullNodeId) + { + return false; + } + else if (certType == certificateType) + { + return true; + } + + // special cases + if (certType == ObjectTypeIds.EccNistP384ApplicationCertificateType && + certificateType == ObjectTypeIds.EccNistP256ApplicationCertificateType) + { + return true; + } + + if (certType == ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType && + certificateType == ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType) + { + return true; + } + break; + + default: + // TODO: check SHA1/key size + if (certificateType == null || + certificateType == ObjectTypeIds.RsaSha256ApplicationCertificateType || + certificateType == ObjectTypeIds.RsaMinApplicationCertificateType || + certificateType == ObjectTypeIds.ApplicationCertificateType) + { + return true; + } + break; + } + return false; + } + + /// + /// Map a security policy to a list of supported certificate types. + /// + /// + public static IList MapSecurityPolicyToCertificateTypes(string securityPolicy) + { + var result = new List(); + switch (securityPolicy) + { + case SecurityPolicies.Basic128Rsa15: + case SecurityPolicies.Basic256: + result.Add(ObjectTypeIds.RsaMinApplicationCertificateType); + goto case SecurityPolicies.Basic256Sha256; + case SecurityPolicies.Basic256Sha256: + case SecurityPolicies.Aes128_Sha256_RsaOaep: + case SecurityPolicies.Aes256_Sha256_RsaPss: + result.Add(ObjectTypeIds.RsaSha256ApplicationCertificateType); + break; + case SecurityPolicies.ECC_nistP256: + result.Add(ObjectTypeIds.EccNistP256ApplicationCertificateType); + goto case SecurityPolicies.ECC_nistP384; + case SecurityPolicies.ECC_nistP384: + result.Add(ObjectTypeIds.EccNistP384ApplicationCertificateType); + break; + case SecurityPolicies.ECC_brainpoolP256r1: + result.Add(ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType); + goto case SecurityPolicies.ECC_brainpoolP384r1; + case SecurityPolicies.ECC_brainpoolP384r1: + result.Add(ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType); + break; + case SecurityPolicies.ECC_curve25519: + result.Add(ObjectTypeIds.EccCurve25519ApplicationCertificateType); + break; + case SecurityPolicies.ECC_curve448: + result.Add(ObjectTypeIds.EccCurve448ApplicationCertificateType); + break; + case SecurityPolicies.Https: + result.Add(ObjectTypeIds.HttpsCertificateType); + result.Add(ObjectTypeIds.ApplicationCertificateType); + break; + default: + break; + } + return result; + } + + + /// + /// Disposes and deletes the reference to the certificate. + /// + public void DisposeCertificate() + { + var certificate = m_certificate; + m_certificate = null; + Utils.SilentDispose(certificate); + } #endregion #region Private Methods @@ -736,6 +884,12 @@ public Task LoadPrivateKey(string thumbprint, string subjectNa return Task.FromResult(null); } + /// + public Task LoadPrivateKey(string thumbprint, string subjectName, NodeId certificateType, string password) + { + return Task.FromResult(null); + } + /// public bool SupportsCRLs => false; @@ -815,4 +969,127 @@ public enum CertificateValidationOptions TreatAsInvalid = 0x40 } #endregion + + #region CertificateTypesProvider + /// + /// The identifier for an X509 certificate. + /// + public class CertificateTypesProvider + { + /// + /// + /// + public CertificateTypesProvider(ApplicationConfiguration config) + { + m_securityConfiguration = config.SecurityConfiguration; + m_certificateValidator = config.CertificateValidator; + } + + /// + /// Return the instance certificate for a security policy. + /// + /// The security policy Uri + public X509Certificate2 GetInstanceCertificate(string securityPolicyUri) + { + if (securityPolicyUri == SecurityPolicies.None) + { + // return the default certificate for None + return m_securityConfiguration.ApplicationCertificate.Certificate; + } + var certificateTypes = CertificateIdentifier.MapSecurityPolicyToCertificateTypes(securityPolicyUri); + foreach (var certType in certificateTypes) + { + var instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == certType); + if (instanceCertificate == null && + certType == ObjectTypeIds.RsaSha256ApplicationCertificateType) + { + instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == null); + } + if (instanceCertificate == null && + certType == ObjectTypeIds.ApplicationCertificateType) + { + instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(); + } + if (instanceCertificate != null) + { + return instanceCertificate.Certificate; + } + } + return null; + } + + /// + /// Load the instance certificate with a private key. + /// + /// + /// + public Task GetInstanceCertificateAsync(IList certificateTypes, bool privateKey) + { + foreach (var certType in certificateTypes) + { + var instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == certType); + if (instanceCertificate != null) + { + return instanceCertificate.Find(privateKey); + } + } + return Task.FromResult(null); + } + + /// + /// Loads the certificate chain of a certificate for use in a secure channel as raw byte array. + /// + /// The application certificate. + public async Task LoadCertificateChainRawAsync(X509Certificate2 certificate) + { + var instanceCertificateChain = await LoadCertificateChainAsync(certificate).ConfigureAwait(false); + if (instanceCertificateChain != null) + { + List serverCertificateChain = new List(); + for (int i = 0; i < instanceCertificateChain.Count; i++) + { + serverCertificateChain.AddRange(instanceCertificateChain[i].RawData); + } + return serverCertificateChain.ToArray(); + } + return null; + } + + /// + /// Loads the certificate chain for an application certificate. + /// + /// The application certificate. + public async Task LoadCertificateChainAsync(X509Certificate2 certificate) + { + if (certificate == null) + { + return null; + } + + // load certificate chain. + var certificateChain = new X509Certificate2Collection(certificate); + List issuers = new List(); + if (await m_certificateValidator.GetIssuers(certificate, issuers, false).ConfigureAwait(false)) + { + for (int i = 0; i < issuers.Count; i++) + { + certificateChain.Add(issuers[i].Certificate); + } + } + return certificateChain; + } + + /// + /// Update the security configuration of the cert type provider. + /// + /// The new security configuration. + public void Update(SecurityConfiguration securityConfiguration) + { + m_securityConfiguration = securityConfiguration; + } + + CertificateValidator m_certificateValidator; + SecurityConfiguration m_securityConfiguration; + } + #endregion } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index a0365ca18..f37198f60 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -180,7 +180,7 @@ private void InternalUpdate( /// /// Updates the validator with the current state of the configuration. /// - public virtual async Task Update(SecurityConfiguration configuration) + public virtual Task Update(SecurityConfiguration configuration) { if (configuration == null) { @@ -238,10 +238,16 @@ public virtual async Task UpdateCertificate(SecurityConfiguration securityConfig { await m_semaphore.WaitAsync().ConfigureAwait(false); - securityConfiguration.ApplicationCertificate.Certificate = null; + foreach (var applicationCertificate in securityConfiguration.ApplicationCertificates) + { + applicationCertificate.DisposeCertificate(); + } - await securityConfiguration.ApplicationCertificate.LoadPrivateKeyEx( - securityConfiguration.CertificatePasswordProvider).ConfigureAwait(false); + foreach (var applicationCertificate in securityConfiguration.ApplicationCertificates) + { + await applicationCertificate.LoadPrivateKeyEx( + securityConfiguration.CertificatePasswordProvider).ConfigureAwait(false); + } } finally { @@ -833,11 +839,22 @@ private bool Match( return check; } + /// + /// Returns the issuers for the certificates. + /// + public Task GetIssuers(X509Certificate2Collection certificates, List issuers, bool checkRevocationStatus) + { + return GetIssuersNoExceptionsOnGetIssuer(certificates, issuers, + null, // ensures legacy behavior is respected + checkRevocationStatus + ); + } + /// /// Returns the issuers for the certificates. /// public async Task GetIssuersNoExceptionsOnGetIssuer(X509Certificate2Collection certificates, - List issuers, Dictionary validationErrors) + List issuers, Dictionary validationErrors, bool checkRevocationStatus) { bool isTrusted = false; CertificateIdentifier issuer = null; @@ -860,22 +877,22 @@ public async Task GetIssuersNoExceptionsOnGetIssuer(X509Certificate2Collec if (validationErrors != null) { - (issuer, revocationStatus) = await GetIssuerNoException(certificate, m_trustedCertificateList, m_trustedCertificateStore, true).ConfigureAwait(false); + (issuer, revocationStatus) = await GetIssuerNoException(certificate, m_trustedCertificateList, m_trustedCertificateStore, checkRevocationStatus).ConfigureAwait(false); } else { - issuer = await GetIssuer(certificate, m_trustedCertificateList, m_trustedCertificateStore, true).ConfigureAwait(false); + issuer = await GetIssuer(certificate, m_trustedCertificateList, m_trustedCertificateStore, checkRevocationStatus).ConfigureAwait(false); } if (issuer == null) { if (validationErrors != null) { - (issuer, revocationStatus) = await GetIssuerNoException(certificate, m_issuerCertificateList, m_issuerCertificateStore, true).ConfigureAwait(false); + (issuer, revocationStatus) = await GetIssuerNoException(certificate, m_issuerCertificateList, m_issuerCertificateStore, checkRevocationStatus).ConfigureAwait(false); } else { - issuer = await GetIssuer(certificate, m_issuerCertificateList, m_issuerCertificateStore, true).ConfigureAwait(false); + issuer = await GetIssuer(certificate, m_issuerCertificateList, m_issuerCertificateStore, checkRevocationStatus).ConfigureAwait(false); } if (issuer == null) @@ -923,7 +940,7 @@ public async Task GetIssuersNoExceptionsOnGetIssuer(X509Certificate2Collec public Task GetIssuers(X509Certificate2Collection certificates, List issuers) { return GetIssuersNoExceptionsOnGetIssuer( - certificates, issuers, null // ensures legacy behavior is respected + certificates, issuers, null, true // ensures legacy behavior is respected ); } @@ -932,9 +949,21 @@ public Task GetIssuers(X509Certificate2Collection certificates, List /// The certificate. /// The issuers. + /// If the revocation status of the issuers should be checked. + public Task GetIssuers(X509Certificate2 certificate, List issuers, bool checkRevocationStatus) + { + return GetIssuers(new X509Certificate2Collection { certificate }, issuers, checkRevocationStatus); + } + + /// + /// Returns the issuers for the certificate. + /// + /// The certificate. + /// The issuers. + /// public Task GetIssuers(X509Certificate2 certificate, List issuers) { - return GetIssuers(new X509Certificate2Collection { certificate }, issuers); + return GetIssuers(new X509Certificate2Collection { certificate }, issuers, true); } /// @@ -1113,7 +1142,7 @@ protected virtual async Task InternalValidate(X509Certificate2Collection certifi List issuers = new List(); Dictionary validationErrors = new Dictionary(); - bool isIssuerTrusted = await GetIssuersNoExceptionsOnGetIssuer(certificates, issuers, validationErrors).ConfigureAwait(false); + bool isIssuerTrusted = await GetIssuersNoExceptionsOnGetIssuer(certificates, issuers, validationErrors, true).ConfigureAwait(false); ServiceResult sresult = PopulateSresultWithValidationErrors(validationErrors); @@ -1282,7 +1311,10 @@ protected virtual async Task InternalValidate(X509Certificate2Collection certifi // check if certificate is trusted. if (trustedCertificate == null && !isIssuerTrusted) { - if (m_applicationCertificate == null || !Utils.IsEqual(m_applicationCertificate.RawData, certificate.RawData)) + // TODO ECC cert + bool isApplicationCertificate = true; + if (isApplicationCertificate) + //if (m_applicationCertificate == null || !Utils.IsEqual(m_applicationCertificate.RawData, certificate.RawData)) { var message = "Certificate is not trusted."; sresult = new ServiceResult(StatusCodes.BadCertificateUntrusted, @@ -1300,13 +1332,25 @@ protected virtual async Task InternalValidate(X509Certificate2Collection certifi ); } + bool isECDsaSignature = X509PfxUtils.IsECDsaSignature(certificate); + // check if certificate is valid for use as app/sw or user cert X509KeyUsageFlags certificateKeyUsage = X509Utils.GetKeyUsage(certificate); - - if ((certificateKeyUsage & X509KeyUsageFlags.DataEncipherment) == 0) + if (isECDsaSignature) { - sresult = new ServiceResult(StatusCodes.BadCertificateUseNotAllowed, - null, null, "Usage of certificate is not allowed.", null, sresult); + if ((certificateKeyUsage & X509KeyUsageFlags.DigitalSignature) == 0) + { + sresult = new ServiceResult(StatusCodes.BadCertificateUseNotAllowed, + null, null, "Usage of ECDSA certificate is not allowed.", null, sresult); + } + } + else + { + if ((certificateKeyUsage & X509KeyUsageFlags.DataEncipherment) == 0) + { + sresult = new ServiceResult(StatusCodes.BadCertificateUseNotAllowed, + null, null, "Usage of RSA certificate is not allowed.", null, sresult); + } } // check if minimum requirements are met @@ -1316,13 +1360,18 @@ protected virtual async Task InternalValidate(X509Certificate2Collection certifi null, null, "SHA1 signed certificates are not trusted.", null, sresult); } - int keySize = X509Utils.GetRSAPublicKeySize(certificate); - if (keySize < m_minimumCertificateKeySize) + if (!isECDsaSignature) { - sresult = new ServiceResult(StatusCodes.BadCertificatePolicyCheckFailed, - null, null, - $"Certificate doesn't meet minimum key length requirement. ({keySize}<{m_minimumCertificateKeySize})", - null, sresult); + int keySize = X509Utils.GetRSAPublicKeySize(certificate); + if (keySize < m_minimumCertificateKeySize) + { + sresult = new ServiceResult(StatusCodes.BadCertificatePolicyCheckFailed, + null, null, "Certificate doesn't meet minimum key length requirement.", null, sresult); + } + } + else + { + // TODO: check if curve type is secure enough for profile } if (issuedByCA && chainIncomplete) @@ -1623,7 +1672,7 @@ private static bool IsSHA1SignatureAlgorithm(Oid oid) { return oid.Value == "1.3.14.3.2.29" || // sha1RSA oid.Value == "1.2.840.10040.4.3" || // sha1DSA - oid.Value == "1.2.840.10045.4.1" || // sha1ECDSA + oid.Value == Oids.ECDsaWithSha1 || // sha1ECDSA oid.Value == "1.2.840.113549.1.1.5" || // sha1RSA oid.Value == "1.3.14.3.2.13" || // sha1DSA oid.Value == "1.3.14.3.2.27"; // dsaSHA1 @@ -1760,7 +1809,7 @@ private enum ProtectFlags private CertificateStoreIdentifier m_rejectedCertificateStore; private event CertificateValidationEventHandler m_CertificateValidation; private event CertificateUpdateEventHandler m_CertificateUpdate; - private X509Certificate2 m_applicationCertificate; + //private X509Certificate2 m_applicationCertificate; private ProtectFlags m_protectFlags; private bool m_autoAcceptUntrustedCertificates; private bool m_rejectSHA1SignedCertificates; diff --git a/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs b/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs index e96a23988..e422fef88 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs @@ -336,9 +336,18 @@ public string GetPrivateKeyFilePath(string thumbprint) public bool SupportsLoadPrivateKey => true; /// - /// Loads the private key from a PFX/PEM file in the certificate store. + /// Loads the private key certificate with RSA signature from a PFX file in the certificate store. /// - public async Task LoadPrivateKey(string thumbprint, string subjectName, string password) + [Obsolete("Use LoadPrivateKey with certificateType.")] + public Task LoadPrivateKey(string thumbprint, string subjectName, string password) + { + return LoadPrivateKey(thumbprint, subjectName, null, password); + } + + /// + /// Loads the private key from a PFX file in the certificate store. + /// + public async Task LoadPrivateKey(string thumbprint, string subjectName, NodeId certificateType, string password) { if (NoPrivateKeys || m_privateKeySubdir == null || m_certificateSubdir == null || !m_certificateSubdir.Exists) @@ -389,8 +398,7 @@ public async Task LoadPrivateKey(string thumbprint, string sub } } - // skip if not RSA certificate - if (X509Utils.GetRSAPublicKeySize(certificate) < 0) + if (!CertificateIdentifier.ValidateCertificateType(certificate, certificateType)) { continue; } @@ -421,7 +429,7 @@ public async Task LoadPrivateKey(string thumbprint, string sub privateKeyFilePfx.FullName, password, flag); - if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true)) + if (X509Utils.VerifyKeyPair(certificate, certificate, true)) { Utils.LogInfo(Utils.TraceMasks.Security, "Imported the PFX private key for [{0}].", certificate.Thumbprint); return certificate; @@ -442,7 +450,7 @@ public async Task LoadPrivateKey(string thumbprint, string sub { byte[] pemDataBlob = File.ReadAllBytes(privateKeyFilePem.FullName); certificate = CertificateFactory.CreateCertificateWithPEMPrivateKey(certificate, pemDataBlob, password); - if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true)) + if (X509Utils.VerifyKeyPair(certificate, certificate, true)) { Utils.LogInfo(Utils.TraceMasks.Security, "Imported the PEM private key for [{0}].", certificate.Thumbprint); return certificate; @@ -851,6 +859,14 @@ private string GetFileName(X509Certificate2 certificate) fileName.Append(ch); } + var signatureQualifier = X509Utils.GetECDsaQualifier(certificate); + if (!string.IsNullOrEmpty(signatureQualifier)) + { + fileName.Append(" ["); + fileName.Append(signatureQualifier); + fileName.Append(']'); + } + fileName.Append(" ["); fileName.Append(certificate.Thumbprint); fileName.Append(']'); diff --git a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs new file mode 100644 index 000000000..2b89a1e79 --- /dev/null +++ b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs @@ -0,0 +1,1034 @@ +/* Copyright (c) 1996-2016, OPC Foundation. All rights reserved. + The source code in this file is covered under a dual-license scenario: + - RCL: for OPC Foundation members in good-standing + - GPL V2: everybody else + RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/ + GNU General Public License as published by the Free Software Foundation; + version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2 + This source code is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +*/ + + +using System; +using System.Text; +using System.IO; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Opc.Ua.Security.Certificates; + +#if CURVE25519 +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Digests; +#endif + +namespace Opc.Ua +{ + /// + /// Defines functions to implement ECC cryptography. + /// + public static class EccUtils + { + public const string NistP256 = nameof(NistP256); + public const string NistP384 = nameof(NistP384); + public const string BrainpoolP256r1 = nameof(BrainpoolP256r1); + public const string BrainpoolP384r1 = nameof(BrainpoolP384r1); + + private const string NistP256KeyParameters = "06-08-2A-86-48-CE-3D-03-01-07"; + private const string NistP384KeyParameters = "06-05-2B-81-04-00-22"; + private const string BrainpoolP256r1KeyParameters = "06-09-2B-24-03-03-02-08-01-01-07"; + private const string BrainpoolP384r1KeyParameters = "06-09-2B-24-03-03-02-08-01-01-0B"; + + public static bool IsEccPolicy(string securityPolicyUri) + { + if (securityPolicyUri != null) + { + switch (securityPolicyUri) + { + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + return true; + } + } + } + + return false; + } + + public static NodeId GetEccCertificateTypeId(X509Certificate2 certificate) + { + var keyAlgorithm = certificate.GetKeyAlgorithm(); + if (keyAlgorithm != Oids.ECPublicKey) + { + return NodeId.Null; + } + + PublicKey encodedPublicKey = certificate.PublicKey; + string keyParameters = BitConverter.ToString(encodedPublicKey.EncodedParameters.RawData); + switch (keyParameters) + { + // nistP256 + case NistP256KeyParameters: return ObjectTypeIds.EccNistP256ApplicationCertificateType; + // nistP384 + case NistP384KeyParameters: return ObjectTypeIds.EccNistP384ApplicationCertificateType; + // brainpoolP256r1 + case BrainpoolP256r1KeyParameters: return ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType; + // brainpoolP384r1 + case BrainpoolP384r1KeyParameters: return ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType; + default: return NodeId.Null; + } + } + + public static string GetECDsaQualifier(X509Certificate2 certificate) + { + if (X509Utils.IsECDsaSignature(certificate)) + { + string signatureQualifier = "ECDsa"; + PublicKey encodedPublicKey = certificate.PublicKey; + string keyParameters = BitConverter.ToString(encodedPublicKey.EncodedParameters.RawData); + + // New values can be determined by running the dotted-decimal OID value + // through BitConverter.ToString(CryptoConfig.EncodeOID(dottedDecimal)); + + switch (keyParameters) + { + case NistP256KeyParameters: + { + signatureQualifier = NistP256; + break; + } + + case NistP384KeyParameters: + { + signatureQualifier = NistP384; + break; + } + + case BrainpoolP256r1KeyParameters: + { + signatureQualifier = BrainpoolP256r1; + break; + } + + case BrainpoolP384r1KeyParameters: + { + signatureQualifier = BrainpoolP384r1; + break; + } + } + return signatureQualifier; + } + return string.Empty; + } + +#if ECC_SUPPORT + public static ECDsa GetPublicKey(X509Certificate2 certificate) + { + string[] securityPolicyUris; + return GetPublicKey(certificate, out securityPolicyUris); + } + + public static ECDsa GetPublicKey(X509Certificate2 certificate, out string[] securityPolicyUris) + { + securityPolicyUris = null; + + var keyAlgorithm = certificate.GetKeyAlgorithm(); + + if (certificate == null || keyAlgorithm != Oids.ECPublicKey) + { + return null; + } + + const X509KeyUsageFlags SufficientFlags = + X509KeyUsageFlags.KeyAgreement | + X509KeyUsageFlags.DigitalSignature | + X509KeyUsageFlags.NonRepudiation | + X509KeyUsageFlags.CrlSign | + X509KeyUsageFlags.KeyCertSign; + + foreach (X509Extension extension in certificate.Extensions) + { + if (extension.Oid.Value == "2.5.29.15") + { + X509KeyUsageExtension kuExt = (X509KeyUsageExtension)extension; + + if ((kuExt.KeyUsages & SufficientFlags) == 0) + { + return null; + } + } + } + + PublicKey encodedPublicKey = certificate.PublicKey; + string keyParameters = BitConverter.ToString(encodedPublicKey.EncodedParameters.RawData); + byte[] keyValue = encodedPublicKey.EncodedKeyValue.RawData; + + ECParameters ecParameters = default(ECParameters); + + if (keyValue[0] != 0x04) + { + throw new InvalidOperationException("Only uncompressed points are supported"); + } + + byte[] x = new byte[(keyValue.Length - 1) / 2]; + byte[] y = new byte[x.Length]; + + Buffer.BlockCopy(keyValue, 1, x, 0, x.Length); + Buffer.BlockCopy(keyValue, 1 + x.Length, y, 0, y.Length); + + ecParameters.Q.X = x; + ecParameters.Q.Y = y; + + // New values can be determined by running the dotted-decimal OID value + // through BitConverter.ToString(CryptoConfig.EncodeOID(dottedDecimal)); + + switch (keyParameters) + { + case NistP256KeyParameters: + { + ecParameters.Curve = ECCurve.NamedCurves.nistP256; + securityPolicyUris = new string[] { SecurityPolicies.ECC_nistP256 }; + break; + } + + case NistP384KeyParameters: + { + ecParameters.Curve = ECCurve.NamedCurves.nistP384; + securityPolicyUris = new string[] { SecurityPolicies.ECC_nistP384, SecurityPolicies.ECC_nistP256 }; + break; + } + + case BrainpoolP256r1KeyParameters: + { + ecParameters.Curve = ECCurve.NamedCurves.brainpoolP256r1; + securityPolicyUris = new string[] { SecurityPolicies.ECC_brainpoolP256r1 }; + break; + } + + case BrainpoolP384r1KeyParameters: + { + ecParameters.Curve = ECCurve.NamedCurves.brainpoolP384r1; + securityPolicyUris = new string[] { SecurityPolicies.ECC_brainpoolP384r1, SecurityPolicies.ECC_brainpoolP256r1 }; + break; + } + + default: + { + throw new NotImplementedException(keyParameters); + } + } + + return ECDsa.Create(ecParameters); + } + + /// + /// Returns the length of a ECDsa signature of a digest. + /// + public static int GetSignatureLength(X509Certificate2 signingCertificate) + { + if (signingCertificate == null) + { + throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "No public key for certificate."); + } + using (var publicKey = GetPublicKey(signingCertificate)) + { + if (publicKey == null) + { + throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "No public key for certificate."); + } + + return publicKey.KeySize / 4; + } + + throw new NotImplementedException(); + } + + /// + /// + /// + /// + public static HashAlgorithmName GetSignatureAlgorithmName(string securityPolicyUri) + { + if (securityPolicyUri == null) + { + throw new ArgumentNullException(nameof(securityPolicyUri)); + } + + switch (securityPolicyUri) + { + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + { + return HashAlgorithmName.SHA256; + } + + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + return HashAlgorithmName.SHA384; + } + + case SecurityPolicies.None: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + default: + { + return HashAlgorithmName.SHA256; + } + } + } + + /// + /// Computes an ECDSA signature. + /// + public static byte[] Sign( + ArraySegment dataToSign, + X509Certificate2 signingCertificate, + string securityPolicyUri) + { + var algorithm = GetSignatureAlgorithmName(securityPolicyUri); + return Sign(dataToSign, signingCertificate, algorithm); + } + + /// + /// Computes an ECDSA signature. + /// + public static byte[] Sign( + ArraySegment dataToSign, + X509Certificate2 signingCertificate, + HashAlgorithmName algorithm) + { +#if CURVE25519 + var publicKey = signingCertificate.BcCertificate.GetPublicKey(); + + if (publicKey is Ed25519PublicKeyParameters) + { + var signer = new Ed25519Signer(); + + signer.Init(true, signingCertificate.BcPrivateKey); + signer.BlockUpdate(dataToSign.Array, dataToSign.Offset, dataToSign.Count); + byte[] signature = signer.GenerateSignature(); +#if DEBUG + var verifier = new Ed25519Signer(); + + verifier.Init(false, signingCertificate.BcCertificate.GetPublicKey()); + verifier.BlockUpdate(dataToSign.Array, dataToSign.Offset, dataToSign.Count); + + if (!verifier.VerifySignature(signature)) + { + throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed, "Could not verify signature."); + } +#endif + return signature; + } + + if (publicKey is Ed448PublicKeyParameters) + { + var signer = new Ed448Signer(new byte[32]); + + signer.Init(true, signingCertificate.BcPrivateKey); + signer.BlockUpdate(dataToSign.Array, dataToSign.Offset, dataToSign.Count); + byte[] signature = signer.GenerateSignature(); +#if DEBUG + var verifier = new Ed448Signer(new byte[32]); + + verifier.Init(false, signingCertificate.BcCertificate.GetPublicKey()); + verifier.BlockUpdate(dataToSign.Array, dataToSign.Offset, dataToSign.Count); + + if (!verifier.VerifySignature(signature)) + { + throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed, "Could not verify signature."); + } +#endif + return signature; + } +#endif + var senderPrivateKey = signingCertificate.GetECDsaPrivateKey() as ECDsa; + + if (senderPrivateKey == null) + { + throw new ServiceResultException(StatusCodes.BadCertificateInvalid, "Missing private key needed for create a signature."); + } + + using (senderPrivateKey) + { + var signature = senderPrivateKey.SignData(dataToSign.Array, dataToSign.Offset, dataToSign.Count, algorithm); + +#if DEBUGxxx + using (ECDsa ecdsa = EccUtils.GetPublicKey(new X509Certificate2(signingCertificate.RawData))) + { + if (!ecdsa.VerifyData(dataToSign.Array, dataToSign.Offset, dataToSign.Count, signature, algorithm)) + { + throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed, "Could not verify signature."); + } + } +#endif + + return signature; + } + } + + /// + /// Verifies a ECDsa signature. + /// + public static bool Verify( + ArraySegment dataToVerify, + byte[] signature, + X509Certificate2 signingCertificate, + string securityPolicyUri) + { + return Verify(dataToVerify, signature, signingCertificate, GetSignatureAlgorithmName(securityPolicyUri)); + } + + /// + /// Verifies a ECDsa signature. + /// + public static bool Verify( + ArraySegment dataToVerify, + byte[] signature, + X509Certificate2 signingCertificate, + HashAlgorithmName algorithm) + { +#if CURVE25519 + var publicKey = signingCertificate.BcCertificate.GetPublicKey(); + + if (publicKey is Ed25519PublicKeyParameters) + { + var verifier = new Ed25519Signer(); + + verifier.Init(false, signingCertificate.BcCertificate.GetPublicKey()); + verifier.BlockUpdate(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count); + + if (!verifier.VerifySignature(signature)) + { + return false; + } + + return true; + } + + if (publicKey is Ed448PublicKeyParameters) + { + var verifier = new Ed448Signer(new byte[32]); + + verifier.Init(false, signingCertificate.BcCertificate.GetPublicKey()); + verifier.BlockUpdate(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count); + + if (!verifier.VerifySignature(signature)) + { + return false; + } + + return true; + } +#endif + using (ECDsa ecdsa = EccUtils.GetPublicKey(signingCertificate)) + { + return ecdsa.VerifyData(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count, signature, algorithm); + } + } + } + + public class EncryptedSecret + { + public X509Certificate2 SenderCertificate { get; set; } + + public X509Certificate2Collection SenderIssuerCertificates { get; set; } + + public bool DoNotEncodeSenderCertificate { get; set; } + + public Nonce SenderNonce { get; set; } + + public Nonce ReceiverNonce { get; set; } + + public X509Certificate2 ReceiverCertificate { get; set; } + + public CertificateValidator Validator { get; set; } + + public string SecurityPolicyUri { get; set; } + + private static byte[] EncryptSecret( + byte[] secret, + byte[] nonce, + byte[] encryptingKey, + byte[] iv) + { +#if CURVE25519 + bool useAuthenticatedEncryption = false; + if (SenderCertificate.BcCertificate.GetPublicKey() is Ed25519PublicKeyParameters || + SenderCertificate.BcCertificate.GetPublicKey() is Ed448PublicKeyParameters) + { + useAuthenticatedEncryption = true; + } +#endif + byte[] dataToEncrypt = null; + + using (var encoder = new BinaryEncoder(ServiceMessageContext.GlobalContext)) + { + encoder.WriteByteString(null, nonce); + encoder.WriteByteString(null, secret); + + // add padding. + int paddingSize = (iv.Length - ((encoder.Position + 2) % iv.Length)); + paddingSize %= iv.Length; + + if (secret.Length + paddingSize < iv.Length) + { + paddingSize += iv.Length; + } + + for (int ii = 0; ii < paddingSize; ii++) + { + encoder.WriteByte(null, (byte)(paddingSize & 0xFF)); + } + + encoder.WriteUInt16(null, (ushort)paddingSize); + + dataToEncrypt = encoder.CloseAndReturnBuffer(); + } +#if CURVE25519 + if (useAuthenticatedEncryption) + { + return EncryptWithChaCha20Poly1305(encryptingKey, iv, dataToEncrypt); + } +#endif + using (Aes aes = Aes.Create()) + { + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.None; + aes.Key = encryptingKey; + aes.IV = iv; + + using (ICryptoTransform encryptor = aes.CreateEncryptor()) + { + if (dataToEncrypt.Length % encryptor.InputBlockSize != 0) + { + throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Input data is not an even number of encryption blocks."); + } + + encryptor.TransformBlock(dataToEncrypt, 0, dataToEncrypt.Length, dataToEncrypt, 0); + } + } + + return dataToEncrypt; + } + +#if CURVE25519 + private static byte[] EncryptWithChaCha20Poly1305( + byte[] encryptingKey, + byte[] iv, + byte[] dataToEncrypt) + { + Utils.Trace($"EncryptKey={Utils.ToHexString(encryptingKey)}"); + Utils.Trace($"EncryptIV={Utils.ToHexString(iv)}"); + + int signatureLength = 16; + + AeadParameters parameters = new AeadParameters( + new KeyParameter(encryptingKey), + signatureLength * 8, + iv, + null); + + ChaCha20Poly1305 encryptor = new ChaCha20Poly1305(); + encryptor.Init(true, parameters); + + byte[] ciphertext = new byte[encryptor.GetOutputSize(dataToEncrypt.Length)]; + int length = encryptor.ProcessBytes(dataToEncrypt, 0, dataToEncrypt.Length, ciphertext, 0); + length += encryptor.DoFinal(ciphertext, length); + + if (ciphertext.Length != length) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + $"CipherText not the expected size. [{ciphertext.Length} != {length}]"); + } + + return ciphertext; + } + + private ArraySegment DecryptWithChaCha20Poly1305( + byte[] encryptingKey, + byte[] iv, + byte[] dataToDecrypt, + int offset, + int count) + { + Utils.Trace($"EncryptKey={Utils.ToHexString(encryptingKey)}"); + Utils.Trace($"EncryptIV={Utils.ToHexString(iv)}"); + + int signatureLength = 16; + + AeadParameters parameters = new AeadParameters( + new KeyParameter(encryptingKey), + signatureLength * 8, + iv, + null); + + ChaCha20Poly1305 decryptor = new ChaCha20Poly1305(); + decryptor.Init(false, parameters); + + byte[] plaintext = new byte[decryptor.GetOutputSize(count)]; + int length = decryptor.ProcessBytes(dataToDecrypt, offset, count, plaintext, 0); + length += decryptor.DoFinal(plaintext, length); + + if (plaintext.Length != length || plaintext.Length < iv.Length) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + $"PlainText not the expected size or too short. [{count} != {length}]"); + } + + ushort paddingSize = plaintext[length-1]; + paddingSize <<= 8; + paddingSize += plaintext[length - 2]; + + int notvalid = (paddingSize < length) ? 0 : 1; + int start = length - paddingSize - 2; + + for (int ii = 0; ii < length - 2 && ii < paddingSize; ii++) + { + if (start < 0 || start + ii >= plaintext.Length) + { + notvalid |= 1; + continue; + } + + notvalid |= plaintext[start + ii] ^ (paddingSize & 0xFF); + } + + if (notvalid != 0) + { + throw new ServiceResultException(StatusCodes.BadNonceInvalid); + } + + return new ArraySegment(plaintext, 0, start); + } +#endif + + /// + /// + /// + private static ArraySegment DecryptSecret( + byte[] dataToDecrypt, + int offset, + int count, + byte[] encryptingKey, + byte[] iv) + { +#if CURVE25519 + bool useAuthenticatedEncryption = false; + if (SenderCertificate.BcCertificate.GetPublicKey() is Ed25519PublicKeyParameters || + SenderCertificate.BcCertificate.GetPublicKey() is Ed448PublicKeyParameters) + { + useAuthenticatedEncryption = true; + } + if (useAuthenticatedEncryption) + { + return DecryptWithChaCha20Poly1305(encryptingKey, iv, dataToDecrypt, offset, count); + } +#endif + using (Aes aes = Aes.Create()) + { + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.None; + aes.Key = encryptingKey; + aes.IV = iv; + + using (ICryptoTransform decryptor = aes.CreateDecryptor()) + { + if (count % decryptor.InputBlockSize != 0) + { + throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Input data is not an even number of encryption blocks."); + } + + decryptor.TransformBlock(dataToDecrypt, offset, count, dataToDecrypt, offset); + } + } + + ushort paddingSize = dataToDecrypt[offset + count - 1]; + paddingSize <<= 8; + paddingSize += dataToDecrypt[offset + count - 2]; + + int notvalid = (paddingSize < count) ? 0 : 1; + int start = offset + count - paddingSize - 2; + + for (int ii = 0; ii < count - 2 && ii < paddingSize; ii++) + { + if (start < 0 || start + ii >= dataToDecrypt.Length) + { + notvalid |= 1; + continue; + } + + notvalid |= dataToDecrypt[start + ii] ^ (paddingSize & 0xFF); + } + + if (notvalid != 0) + { + throw new ServiceResultException(StatusCodes.BadNonceInvalid); + } + + return new ArraySegment(dataToDecrypt, offset, count - paddingSize); + } + + + private static readonly byte[] s_Label = new UTF8Encoding().GetBytes("opcua-secret"); + + + private static void CreateKeysForEcc( + string securityPolicyUri, + Nonce senderNonce, + Nonce receiverNonce, + bool forDecryption, + out byte[] encryptingKey, + out byte[] iv) + { + int encryptingKeySize = 32; + int blockSize = 16; + HashAlgorithmName algorithmName = HashAlgorithmName.SHA256; + + switch (securityPolicyUri) + { + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + { + encryptingKeySize = 16; + break; + } + + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + encryptingKeySize = 32; + algorithmName = HashAlgorithmName.SHA384; + break; + } + + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + encryptingKeySize = 32; + blockSize = 12; + algorithmName = HashAlgorithmName.SHA256; + break; + } + } + + encryptingKey = new byte[encryptingKeySize]; + iv = new byte[blockSize]; + + var keyLength = BitConverter.GetBytes((ushort)(encryptingKeySize + blockSize)); + var salt = Utils.Append(keyLength, s_Label, senderNonce.Data, receiverNonce.Data); + + byte[] keyData = null; + + if (forDecryption) + { + keyData = receiverNonce.DeriveKey(senderNonce, salt, algorithmName, encryptingKeySize + blockSize); + } + else + { + keyData = senderNonce.DeriveKey(receiverNonce, salt, algorithmName, encryptingKeySize + blockSize); + } + + Buffer.BlockCopy(keyData, 0, encryptingKey, 0, encryptingKey.Length); + Buffer.BlockCopy(keyData, encryptingKeySize, iv, 0, iv.Length); + } + + public byte[] Encrypt(byte[] secret, byte[] nonce) + { + byte[] encryptingKey = null; + byte[] iv = null; + byte[] message = null; + int lengthPosition = 0; + + var signatureLength = EccUtils.GetSignatureLength(SenderCertificate); + + using (BinaryEncoder encoder = new BinaryEncoder(ServiceMessageContext.GlobalContext)) + { + // write header. + encoder.WriteNodeId(null, DataTypeIds.EccEncryptedSecret); + encoder.WriteByte(null, (byte)ExtensionObjectEncoding.Binary); + + lengthPosition = encoder.Position; + encoder.WriteUInt32(null, 0); + + encoder.WriteString(null, SecurityPolicyUri); + + byte[] senderCertificate = null; + + if (!DoNotEncodeSenderCertificate) + { + senderCertificate = SenderCertificate.RawData; + + if (SenderIssuerCertificates != null && SenderIssuerCertificates.Count > 0) + { + int blobSize = senderCertificate.Length; + + foreach (var issuer in SenderIssuerCertificates) + { + blobSize += issuer.RawData.Length; + } + + var blob = new byte[blobSize]; + Buffer.BlockCopy(senderCertificate, 0, blob, 0, senderCertificate.Length); + + int pos = senderCertificate.Length; + + foreach (var issuer in SenderIssuerCertificates) + { + var data = issuer.RawData; + Buffer.BlockCopy(data, 0, blob, pos, data.Length); + pos += data.Length; + } + + senderCertificate = blob; + } + } + + encoder.WriteByteString(null, senderCertificate); + encoder.WriteDateTime(null, DateTime.UtcNow); + + var senderNonce = SenderNonce.Data; + var receiverNonce = ReceiverNonce.Data; + + encoder.WriteUInt16(null, (ushort)(senderNonce.Length + receiverNonce.Length + 8)); + encoder.WriteByteString(null, senderNonce); + encoder.WriteByteString(null, receiverNonce); + + // create keys. + if (EccUtils.IsEccPolicy(SecurityPolicyUri)) + { + CreateKeysForEcc(SecurityPolicyUri, SenderNonce, ReceiverNonce, false, out encryptingKey, out iv); + } + + // encrypt secret, + var encryptedData = EncryptSecret(secret, nonce, encryptingKey, iv); + + // append encrypted secret. + for (int ii = 0; ii < encryptedData.Length; ii++) + { + encoder.WriteByte(null, encryptedData[ii]); + } + + // save space for signature. + for (int ii = 0; ii < signatureLength; ii++) + { + encoder.WriteByte(null, 0); + } + + message = encoder.CloseAndReturnBuffer(); + } + + var length = message.Length - lengthPosition - 4; + + message[lengthPosition++] = (byte)((length & 0xFF)); + message[lengthPosition++] = (byte)((length & 0xFF00) >> 8); + message[lengthPosition++] = (byte)((length & 0xFF0000) >> 16); + message[lengthPosition++] = (byte)((length & 0xFF000000) >> 24); + + // get the algorithm used for the signature. + HashAlgorithmName signatureAlgorithm = HashAlgorithmName.SHA256; + + switch (SecurityPolicyUri) + { + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + signatureAlgorithm = HashAlgorithmName.SHA384; + break; + } + } + + ArraySegment dataToSign = new ArraySegment(message, 0, message.Length - signatureLength); + var signature = EccUtils.Sign(dataToSign, SenderCertificate, signatureAlgorithm); + Buffer.BlockCopy(signature, 0, message, message.Length - signatureLength, signatureLength); + return message; + } + + private ArraySegment VerifyHeaderForEcc( + ArraySegment dataToDecrypt, + DateTime earliestTime) + { + using (BinaryDecoder decoder = new BinaryDecoder(dataToDecrypt.Array, dataToDecrypt.Offset, dataToDecrypt.Count, ServiceMessageContext.GlobalContext)) + { + var typeId = decoder.ReadNodeId(null); + + if (typeId != DataTypeIds.EccEncryptedSecret) + { + throw new ServiceResultException(StatusCodes.BadDataTypeIdUnknown); + } + + var encoding = (ExtensionObjectEncoding)decoder.ReadByte(null); + + if (encoding != ExtensionObjectEncoding.Binary) + { + throw new ServiceResultException(StatusCodes.BadDataEncodingUnsupported); + } + + var length = decoder.ReadUInt32(null); + + // get the start of data. + int startOfData = decoder.Position + dataToDecrypt.Offset; + + SecurityPolicyUri = decoder.ReadString(null); + + if (!EccUtils.IsEccPolicy(SecurityPolicyUri)) + { + throw new ServiceResultException(StatusCodes.BadSecurityPolicyRejected); + } + + // get the algorithm used for the signature. + HashAlgorithmName signatureAlgorithm = HashAlgorithmName.SHA256; + + switch (SecurityPolicyUri) + { + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + signatureAlgorithm = HashAlgorithmName.SHA384; + break; + } + } + + // extract the send certificate and any chain. + var senderCertificate = decoder.ReadByteString(null); + + if (senderCertificate == null || senderCertificate.Length == 0) + { + if (SenderCertificate == null) + { + throw new ServiceResultException(StatusCodes.BadCertificateInvalid); + } + } + else + { + var senderCertificateChain = Utils.ParseCertificateChainBlob(senderCertificate); + + SenderCertificate = senderCertificateChain[0]; + SenderIssuerCertificates = new X509Certificate2Collection(); + + for (int ii = 1; ii < senderCertificateChain.Count; ii++) + { + SenderIssuerCertificates.Add(senderCertificateChain[ii]); + } + + // validate the sender. + if (Validator != null) + { + Validator.Validate(senderCertificateChain); + } + } + + // extract the send certificate and any chain. + var signingTime = decoder.ReadDateTime(null); + + if (signingTime < earliestTime) + { + throw new ServiceResultException(StatusCodes.BadInvalidTimestamp); + } + + // extract the policy header. + var headerLength = decoder.ReadUInt16(null); + + if (headerLength == 0 || headerLength > length) + { + throw new ServiceResultException(StatusCodes.BadDecodingError); + } + + // read the policy header. + var senderPublicKey = decoder.ReadByteString(null); + var receiverPublicKey = decoder.ReadByteString(null); + + if (headerLength != senderPublicKey.Length + receiverPublicKey.Length + 8) + { + throw new ServiceResultException(StatusCodes.BadDecodingError, "Unexpected policy header length"); + } + + var startOfEncryption = decoder.Position; + + SenderNonce = Nonce.CreateNonce(SecurityPolicyUri, senderPublicKey); + + if (!Utils.IsEqual(receiverPublicKey, ReceiverNonce.Data)) + { + throw new ServiceResultException(StatusCodes.BadDecodingError, "Unexpected receiver nonce."); + } + + // check the signature. + int signatureLength = EccUtils.GetSignatureLength(SenderCertificate); + + if (signatureLength >= length) + { + throw new ServiceResultException(StatusCodes.BadDecodingError); + } + + byte[] signature = new byte[signatureLength]; + Buffer.BlockCopy(dataToDecrypt.Array, startOfData + (int)length - signatureLength, signature, 0, signatureLength); + + ArraySegment dataToSign = new ArraySegment(dataToDecrypt.Array, 0, startOfData + (int)length - signatureLength); + + if (!EccUtils.Verify(dataToSign, signature, SenderCertificate, signatureAlgorithm)) + { + throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed, "Could not verify signature."); + } + + // extract the encrypted data. + return new ArraySegment(dataToDecrypt.Array, startOfEncryption, (int)length - (startOfEncryption - startOfData + signatureLength)); + } + } + + public byte[] Decrypt(DateTime earliestTime, byte[] expectedNonce, byte[] data, int offset, int count) + { + byte[] encryptingKey = null; + byte[] iv = null; + byte[] secret = null; + + var dataToDecrypt = VerifyHeaderForEcc(new ArraySegment(data, offset, count), earliestTime); + + CreateKeysForEcc(SecurityPolicyUri, SenderNonce, ReceiverNonce, true, out encryptingKey, out iv); + + var plainText = DecryptSecret(dataToDecrypt.Array, dataToDecrypt.Offset, dataToDecrypt.Count, encryptingKey, iv); + + using (BinaryDecoder decoder = new BinaryDecoder(plainText.Array, plainText.Offset, plainText.Count, ServiceMessageContext.GlobalContext)) + { + var actualNonce = decoder.ReadByteString(null); + + if (expectedNonce != null && expectedNonce.Length > 0) + { + int notvalid = (expectedNonce.Length == actualNonce.Length) ? 0 : 1; + + for (int ii = 0; ii < expectedNonce.Length && ii < actualNonce.Length; ii++) + { + notvalid |= expectedNonce[ii] ^ actualNonce[ii]; + } + + if (notvalid != 0) + { + throw new ServiceResultException(StatusCodes.BadNonceInvalid); + } + } + + secret = decoder.ReadByteString(null); + } + + return secret; + } +#endif + } +} diff --git a/Stack/Opc.Ua.Core/Security/Certificates/ICertificateStore.cs b/Stack/Opc.Ua.Core/Security/Certificates/ICertificateStore.cs index 1d7932109..689fe5e5c 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/ICertificateStore.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/ICertificateStore.cs @@ -86,8 +86,20 @@ public interface ICertificateStore : IDisposable /// The certificate password. /// Returns always null if SupportsLoadPrivateKey returns false. /// The matching certificate with private key + [Obsolete("Must specify certificate type.")] Task LoadPrivateKey(string thumbprint, string subjectName, string password); + /// + /// Finds the certificate with the specified thumprint. + /// + /// The thumbprint. + /// The certificate subject. + /// The certificate type to load. + /// The certificate password. + /// Returns always null if SupportsLoadPrivateKey returns false. + /// The matching certificate with private key + Task LoadPrivateKey(string thumbprint, string subjectName, NodeId certificateType, string password); + /// /// Checks if issuer has revoked the certificate. /// diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs new file mode 100644 index 000000000..ea5972653 --- /dev/null +++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs @@ -0,0 +1,360 @@ +/* Copyright (c) 1996-2016, OPC Foundation. All rights reserved. + The source code in this file is covered under a dual-license scenario: + - RCL: for OPC Foundation members in good-standing + - GPL V2: everybody else + RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/ + GNU General Public License as published by the Free Software Foundation; + version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2 + This source code is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +*/ + +#if ECC_SUPPORT +using System; +using System.Text; +using System.IO; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Opc.Ua.Security.Certificates; + +#if CURVE25519 +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Digests; +#endif + +namespace Opc.Ua +{ + /// + /// + /// + public class Nonce : IDisposable + { + private ECDiffieHellman m_ecdh; +#if CURVE25519 + private AsymmetricCipherKeyPair m_bcKeyPair; +#endif + + enum Algorithm + { + Unknown, + RSA, + nistP256, + nistP384, + brainpoolP256r1, + brainpoolP384r1, + Ed25519 + } + + private Nonce() + { + m_ecdh = null; +#if CURVE25519 + m_bcKeyPair = null; +#endif + } + +#region IDisposable Members + /// + /// Frees any unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// An overrideable version of the Dispose. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (m_ecdh != null) + { + m_ecdh.Dispose(); + m_ecdh = null; + } + } + } +#endregion + + public byte[] Data { get; private set; } + + public byte[] DeriveKey(Nonce remoteNonce, byte[] salt, HashAlgorithmName algorithm, int length) + { +#if CURVE25519 + if (m_bcKeyPair != null) + { + var localPublicKey = m_bcKeyPair.Public; + + if (localPublicKey is X25519PublicKeyParameters) + { + X25519Agreement agreement = new X25519Agreement(); + agreement.Init(m_bcKeyPair.Private); + + var key = new X25519PublicKeyParameters(remoteNonce.Data, 0); + byte[] secret = new byte[agreement.AgreementSize]; + agreement.CalculateAgreement(key, secret, 0); + + HkdfBytesGenerator generator = new HkdfBytesGenerator(new Sha256Digest()); + generator.Init(new HkdfParameters(secret, salt, salt)); + + byte[] output = new byte[length]; + generator.GenerateBytes(output, 0, output.Length); + return output; + } + + if (localPublicKey is X448PublicKeyParameters) + { + X448Agreement agreement = new X448Agreement(); + agreement.Init(m_bcKeyPair.Private); + + var key = new X448PublicKeyParameters(remoteNonce.Data, 0); + byte[] secret = new byte[agreement.AgreementSize]; + agreement.CalculateAgreement(key, secret, 0); + + HkdfBytesGenerator generator = new HkdfBytesGenerator(new Sha256Digest()); + generator.Init(new HkdfParameters(secret, salt, salt)); + + byte[] output = new byte[length]; + generator.GenerateBytes(output, 0, output.Length); + return output; + } + + throw new NotSupportedException(); + } +#endif + if (m_ecdh != null) + { + var secret = m_ecdh.DeriveKeyFromHmac(remoteNonce.m_ecdh.PublicKey, algorithm, salt, null, null); + + byte[] output = new byte[length]; + + HMACSHA256 hmac = new HMACSHA256(secret); + + byte counter = 1; + + byte[] info = new byte[hmac.HashSize / 8 + salt.Length + 1]; + Buffer.BlockCopy(salt, 0, info, 0, salt.Length); + info[salt.Length] = counter++; + + byte[] hash = hmac.ComputeHash(info, 0, salt.Length + 1); + + int pos = 0; + + for (int ii = 0; ii < hash.Length && pos < length; ii++) + { + output[pos++] = hash[ii]; + } + + while (pos < length) + { + Buffer.BlockCopy(hash, 0, info, 0, hash.Length); + Buffer.BlockCopy(salt, 0, info, hash.Length, salt.Length); + info[info.Length - 1] = counter++; + + hash = hmac.ComputeHash(info, 0, info.Length); + + for (int ii = 0; ii < hash.Length && pos < length; ii++) + { + output[pos++] = hash[ii]; + } + } + + return output; + } + + return Data; + } + + public static Nonce CreateNonce(string securityPolicyUri, uint nonceLength) + { + if (securityPolicyUri == null) + { + throw new ArgumentNullException(nameof(securityPolicyUri)); + } + + Nonce nonce = null; + + switch (securityPolicyUri) + { + case SecurityPolicies.ECC_nistP256: { return CreateNonce(ECCurve.NamedCurves.nistP256); } + case SecurityPolicies.ECC_nistP384: { return CreateNonce(ECCurve.NamedCurves.nistP384); } + case SecurityPolicies.ECC_brainpoolP256r1: { return CreateNonce(ECCurve.NamedCurves.brainpoolP256r1); } + case SecurityPolicies.ECC_brainpoolP384r1: { return CreateNonce(ECCurve.NamedCurves.brainpoolP384r1); } +#if CURVE25519 + case SecurityPolicies.ECC_curve25519: + { + return CreateNonceForCurve25519(); + } + + case SecurityPolicies.ECC_curve448: + { + return CreateNonceForCurve448(); + } +#endif + default: + { + nonce = new Nonce() { + Data = Utils.Nonce.CreateNonce(nonceLength) + }; + + return nonce; + } + } + } +#if CURVE25519 + private static Nonce CreateNonceForCurve25519() + { + SecureRandom random = new SecureRandom(); + IAsymmetricCipherKeyPairGenerator generator = new X25519KeyPairGenerator(); + generator.Init(new X25519KeyGenerationParameters(random)); + + var keyPair = generator.GenerateKeyPair(); + + byte[] senderNonce = new byte[X25519PublicKeyParameters.KeySize]; + ((X25519PublicKeyParameters)(keyPair.Public)).Encode(senderNonce, 0); + + var nonce = new Nonce() { + Data = senderNonce, + m_bcKeyPair = keyPair + }; + + return nonce; + } + + private static Nonce CreateNonceForCurve448() + { + SecureRandom random = new SecureRandom(); + IAsymmetricCipherKeyPairGenerator generator = new X448KeyPairGenerator(); + generator.Init(new X448KeyGenerationParameters(random)); + + var keyPair = generator.GenerateKeyPair(); + + byte[] senderNonce = new byte[X448PublicKeyParameters.KeySize]; + ((X448PublicKeyParameters)(keyPair.Public)).Encode(senderNonce, 0); + + var nonce = new Nonce() { + Data = senderNonce, + m_bcKeyPair = keyPair + }; + + return nonce; + } +#endif + + private static Nonce CreateNonce(ECCurve curve) + { + var ecdh = (ECDiffieHellman)ECDiffieHellman.Create(curve); + var ecdhParameters = ecdh.ExportParameters(false); + int xLen = ecdhParameters.Q.X.Length; + int yLen = ecdhParameters.Q.Y.Length; + byte[] senderNonce = new byte[xLen + yLen]; + Array.Copy(ecdhParameters.Q.X, senderNonce, xLen); + Array.Copy(ecdhParameters.Q.Y, 0, senderNonce, xLen, yLen); + + var nonce = new Nonce() { + Data = senderNonce, + m_ecdh = ecdh + }; + + return nonce; + } + + public static Nonce CreateNonce(string securityPolicyUri, byte[] nonceData) + { + if (securityPolicyUri == null) + { + throw new ArgumentNullException(nameof(securityPolicyUri)); + } + + if (nonceData == null) + { + throw new ArgumentNullException(nameof(nonceData)); + } + + Nonce nonce = new Nonce() { + Data = nonceData + }; + + switch (securityPolicyUri) + { + case SecurityPolicies.ECC_nistP256: { return CreateNonce(ECCurve.NamedCurves.nistP256, nonceData); } + case SecurityPolicies.ECC_nistP384: { return CreateNonce(ECCurve.NamedCurves.nistP384, nonceData); } + case SecurityPolicies.ECC_brainpoolP256r1: { return CreateNonce(ECCurve.NamedCurves.brainpoolP256r1, nonceData); } + case SecurityPolicies.ECC_brainpoolP384r1: { return CreateNonce(ECCurve.NamedCurves.brainpoolP384r1, nonceData); } + + case SecurityPolicies.ECC_curve25519: + { + return CreateNonceForCurve25519(nonceData); + } + + case SecurityPolicies.ECC_curve448: + { + return CreateNonceForCurve448(nonceData); + } + + default: + { + break; + } + } + + return nonce; + } + + + private static Nonce CreateNonceForCurve25519(byte[] nonceData) + { + var nonce = new Nonce() { + Data = nonceData, + }; + + return nonce; + } + + private static Nonce CreateNonceForCurve448(byte[] nonceData) + { + var nonce = new Nonce() { + Data = nonceData, + }; + + return nonce; + } + + private static Nonce CreateNonce(ECCurve curve, byte[] nonceData) + { + Nonce nonce = new Nonce() { + Data = nonceData + }; + + int keyLength = nonceData.Length; + + byte[] qx = new byte[keyLength / 2]; + byte[] qy = new byte[keyLength / 2]; + Buffer.BlockCopy(nonceData, 0, qx, 0, keyLength / 2); + Buffer.BlockCopy(nonceData, keyLength / 2, qy, 0, keyLength / 2); + + var ecdhParameters = new ECParameters { + Curve = curve, + Q = { X = qx, Y = qy } + }; + + nonce.m_ecdh = ECDiffieHellman.Create(ecdhParameters); + + return nonce; + } + } + +} +#endif diff --git a/Stack/Opc.Ua.Core/Security/Certificates/SecurityConfiguration.cs b/Stack/Opc.Ua.Core/Security/Certificates/SecurityConfiguration.cs index cc0cc34e5..d184a8d07 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/SecurityConfiguration.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/SecurityConfiguration.cs @@ -10,7 +10,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ +using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; namespace Opc.Ua { @@ -20,13 +26,27 @@ namespace Opc.Ua /// public partial class SecurityConfiguration { + #region Public Properties + /// + /// The security profiles which are supported for this configuration. + /// + public StringCollection SupportedSecurityPolicies { get; private set; } + + /// + /// Get the provider which is invoked when a password + /// for a private key is requested. + /// + public ICertificatePasswordProvider CertificatePasswordProvider { get; set; } + #endregion + + #region Public Methods /// /// Adds a certificate as a trusted peer. /// public void AddTrustedPeer(byte[] certificate) { - this.TrustedPeerCertificates.TrustedCertificates.Add(new CertificateIdentifier(certificate)); + TrustedPeerCertificates.TrustedCertificates.Add(new CertificateIdentifier(certificate)); } /// @@ -34,7 +54,8 @@ public void AddTrustedPeer(byte[] certificate) /// public void Validate() { - if (m_applicationCertificate == null) + if (m_applicationCertificates == null || + m_applicationCertificates.Count == 0) { throw ServiceResultException.Create(StatusCodes.BadConfigurationError, "ApplicationCertificate must be specified."); } @@ -51,8 +72,117 @@ public void Validate() } // replace subjectName DC=localhost with DC=hostname - ApplicationCertificate.SubjectName = Utils.ReplaceDCLocalhost(ApplicationCertificate.SubjectName); + foreach (var applicationCertificate in m_applicationCertificates) + { + applicationCertificate.SubjectName = Utils.ReplaceDCLocalhost(applicationCertificate.SubjectName); + } + } + /// + /// Find application certificate for a security policy. + /// + /// + /// + public async Task FindApplicationCertificateAsync(string securityPolicy, bool privateKey) + { + var certificateTypes = CertificateIdentifier.MapSecurityPolicyToCertificateTypes(securityPolicy); + foreach (var certType in certificateTypes) + { + CertificateIdentifier id = ApplicationCertificates.FirstOrDefault(certId => certId.CertificateType == certType); + if (id == null) + { + if (certType == ObjectTypeIds.RsaSha256ApplicationCertificateType) + { + // undefined certificate type as RsaSha256 + id = ApplicationCertificates.FirstOrDefault(certId => certId.CertificateType == null); + } + else if (certType == ObjectTypeIds.ApplicationCertificateType) + { + // first certificate + id = ApplicationCertificates.FirstOrDefault(); + } + else if (certType == ObjectTypeIds.EccApplicationCertificateType) + { + // first Ecc certificate + id = ApplicationCertificates.FirstOrDefault(certId => X509Utils.IsECDsaSignature(certId.Certificate)); + } + } + + if (id != null) + { + return await id.Find(privateKey).ConfigureAwait(false); + } + } + + return null; + } + #endregion + + #region Private Methods + /// + /// Use the list of application certificates to build a list + /// of supported security policies. + /// + private StringCollection BuildSupportedSecurityPolicies() + { + var securityPolicies = new StringCollection(); + securityPolicies.Add(SecurityPolicies.None); + foreach (var applicationCertificate in m_applicationCertificates) + { + if (applicationCertificate.CertificateType == null) + { + securityPolicies.Add(SecurityPolicies.Basic256Sha256); + securityPolicies.Add(SecurityPolicies.Aes128_Sha256_RsaOaep); + securityPolicies.Add(SecurityPolicies.Aes256_Sha256_RsaPss); + continue; + } + if (applicationCertificate.CertificateType.Identifier is uint identifier) + { + switch (identifier) + { + case ObjectTypes.EccNistP256ApplicationCertificateType: + securityPolicies.Add(SecurityPolicies.ECC_nistP256); + break; + case ObjectTypes.EccNistP384ApplicationCertificateType: + securityPolicies.Add(SecurityPolicies.ECC_nistP256); + securityPolicies.Add(SecurityPolicies.ECC_nistP384); + break; + case ObjectTypes.EccBrainpoolP256r1ApplicationCertificateType: + securityPolicies.Add(SecurityPolicies.ECC_brainpoolP256r1); + break; + case ObjectTypes.EccBrainpoolP384r1ApplicationCertificateType: + securityPolicies.Add(SecurityPolicies.ECC_brainpoolP256r1); + securityPolicies.Add(SecurityPolicies.ECC_brainpoolP384r1); + break; + case ObjectTypes.EccCurve25519ApplicationCertificateType: + securityPolicies.Add(SecurityPolicies.ECC_curve25519); + break; + case ObjectTypes.EccCurve448ApplicationCertificateType: + securityPolicies.Add(SecurityPolicies.ECC_curve448); + break; + case ObjectTypes.RsaMinApplicationCertificateType: + securityPolicies.Add(SecurityPolicies.Basic128Rsa15); + securityPolicies.Add(SecurityPolicies.Basic256); + break; + case ObjectTypes.ApplicationCertificateType: + case ObjectTypes.RsaSha256ApplicationCertificateType: + securityPolicies.Add(SecurityPolicies.Basic256Sha256); + securityPolicies.Add(SecurityPolicies.Aes128_Sha256_RsaOaep); + securityPolicies.Add(SecurityPolicies.Aes256_Sha256_RsaPss); + goto case ObjectTypes.RsaMinApplicationCertificateType; + } + } + } + // filter based on platform support + var result = new StringCollection(); + foreach (var securityPolicyUri in securityPolicies.Distinct()) + { + if (SecurityPolicies.GetDisplayName(securityPolicyUri) != null) + { + result.Add(securityPolicyUri); + } + } + return result; } /// @@ -72,10 +202,124 @@ private CertificateTrustList CreateDefaultTrustList(CertificateTrustList trustLi } /// - /// Get the provider which is invoked when a password - /// for a private key is requested. + /// The tags of the supported certificate types. /// - public ICertificatePasswordProvider CertificatePasswordProvider { get; set; } + private static Dictionary m_supportedCertificateTypes = new Dictionary() { + { ObjectTypes.EccNistP256ApplicationCertificateType, "NistP256"}, + { ObjectTypes.EccNistP384ApplicationCertificateType, "NistP384"}, + { ObjectTypes.EccBrainpoolP256r1ApplicationCertificateType, "BrainpoolP256r1"}, + { ObjectTypes.EccBrainpoolP384r1ApplicationCertificateType, "BrainpoolP384r1"}, + { ObjectTypes.EccCurve25519ApplicationCertificateType, "Curve25519"}, + { ObjectTypes.EccCurve448ApplicationCertificateType, "Curve448"}, + { ObjectTypes.RsaSha256ApplicationCertificateType, "RsaSha256"}, + { ObjectTypes.RsaMinApplicationCertificateType, "RsaMin"}, + { ObjectTypes.ApplicationCertificateType, "Rsa"}, + }; + + /// + /// Encode certificate types as comma separated string. + /// + private string EncodeApplicationCertificateTypes() + { + if (m_applicationCertificates != null) + { + var result = new StringBuilder(); + bool commaRequired = false; + foreach (var applicationCertificate in m_applicationCertificates) + { + string idName = null; + if (applicationCertificate.CertificateType == null) + { + idName = "Rsa"; + } + else if (applicationCertificate.CertificateType.Identifier is uint identifier) + { + if (m_supportedCertificateTypes.TryGetValue(identifier, out idName)) + { + idName = idName.Substring(0, idName.IndexOf(nameof(ObjectTypes.ApplicationCertificateType), StringComparison.OrdinalIgnoreCase)); + } + } + + if (result.ToString().IndexOf(idName, StringComparison.OrdinalIgnoreCase) >= 0) + { + continue; + } + + if (commaRequired) + { + result.Append(','); + } + result.Append(idName); + commaRequired = true; + } + if (commaRequired) + { + return result.ToString(); + } + } + return null; + } + + /// + /// Clones the default application certificate with + /// certificate types specified in the configuration. + /// + /// + /// A comma separated string of certificate + /// types to clone from the default certificate. + /// + private void DecodeApplicationCertificateTypes(string certificateTypes) + { + if (m_applicationCertificates.Count > 0) + { + // fix null certType + var idNull = m_applicationCertificates.FirstOrDefault(id => id.CertificateType == null); + if (idNull != null) + { + idNull.CertificateType = ObjectTypeIds.RsaSha256ApplicationCertificateType; + } + CertificateIdentifier template = m_applicationCertificates[0]; + if (!String.IsNullOrWhiteSpace(certificateTypes)) + { + var result = new NodeIdDictionary(); + var certificateTypesArray = certificateTypes.Trim().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var certType in certificateTypesArray) + { + foreach (var profile in m_supportedCertificateTypes) + { + if (profile.Value.IndexOf(certType.Trim(), StringComparison.OrdinalIgnoreCase) >= 0) + { + var certificateType = new NodeId(profile.Key); + if (Utils.IsSupportedCertificateType(certificateType)) + { + // no duplicates + if (m_applicationCertificates.Any(id => id.CertificateType == certificateType)) + { + break; + } + m_applicationCertificates.Add(new CertificateIdentifier() { + StoreType = template.StoreType, + StorePath = template.StorePath, + SubjectName = template.SubjectName, + CertificateType = certificateType + }); + } + else + { + Utils.LogWarning("Ignoring certificateType {0} because the platform doesn't support it.", + profile.Value); + } + break; + } + } + } + } + } + else + { + throw new ServiceResultException(StatusCodes.BadConfigurationError, "Need application certificate to clone certificate types."); + } + } #endregion } #endregion diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore.cs index 9d0d35b0c..d28ceb84e 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore.cs @@ -221,6 +221,13 @@ public Task LoadPrivateKey(string thumbprint, string subjectNa return Task.FromResult(null); } + /// + /// The LoadPrivateKey special handling is not necessary in this store. + public Task LoadPrivateKey(string thumbprint, string subjectName, NodeId certificateType, string password) + { + return Task.FromResult(null); + } + /// /// CRLs are not supported here. public bool SupportsCRLs => false; diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs index 7feb23fcf..937e11c36 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs @@ -462,6 +462,50 @@ public static List ParseDistinguishedName(string name) return fields; } + /// + /// Return if a certificate has a ECDsa signature. + /// + /// The certificate to test. + public static bool IsECDsaSignature(X509Certificate2 cert) + { + return X509PfxUtils.IsECDsaSignature(cert); + } + + /// + /// Return a qualifier string if a ECDsa signature algorithm used. + /// + /// The certificate. + public static string GetECDsaQualifier(X509Certificate2 certificate) + { + return EccUtils.GetECDsaQualifier(certificate); + } + + /// + /// Verify RSA key pair of two certificates. + /// + public static bool VerifyKeyPair( + X509Certificate2 certWithPublicKey, + X509Certificate2 certWithPrivateKey, + bool throwOnError = false) + { + return X509PfxUtils.VerifyKeyPair(certWithPublicKey, certWithPrivateKey, throwOnError); + } + + /// + /// Verify ECDsa key pair of two certificates. + /// + public static bool VerifyECDsaKeyPair( + X509Certificate2 certWithPublicKey, + X509Certificate2 certWithPrivateKey, + bool throwOnError = false) + { +#if ECC_SUPPORT + return X509PfxUtils.VerifyECDsaKeyPair(certWithPublicKey, certWithPrivateKey, throwOnError); +#else + throw new NotSupportedException(); +#endif + } + /// /// Verify RSA key pair of two certificates. /// diff --git a/Stack/Opc.Ua.Core/Security/Constants/SecurityConstants.cs b/Stack/Opc.Ua.Core/Security/Constants/SecurityConstants.cs index 21e3de76f..8c2b130a0 100644 --- a/Stack/Opc.Ua.Core/Security/Constants/SecurityConstants.cs +++ b/Stack/Opc.Ua.Core/Security/Constants/SecurityConstants.cs @@ -11,7 +11,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ namespace Opc.Ua -{ +{ /// /// Defines constants for key security policies. /// @@ -39,47 +39,47 @@ public static class SecurityAlgorithms /// /// The RSA-PSS-SHA256 algorithm used to create asymmetric key signatures. - /// + /// public const string RsaPssSha256 = "http://opcfoundation.org/UA/security/rsa-pss-sha2-256"; /// /// The AES128 algorithm used to encrypt data. /// public const string Aes128 = "http://www.w3.org/2001/04/xmlenc#aes128-cbc"; - + /// /// The AES256 algorithm used to encrypt data. /// public const string Aes256 = "http://www.w3.org/2001/04/xmlenc#aes256-cbc"; - + /// /// The RSA-OAEP algorithm used to encrypt data. - /// + /// public const string RsaOaep = "http://www.w3.org/2001/04/xmlenc#rsa-oaep"; /// /// The RSA-OAEP-SHA256 algorithm used to encrypt data. - /// + /// public const string RsaOaepSha256 = "http://opcfoundation.org/UA/security/rsa-oaep-sha2-256"; /// /// The RSA-PKCSv1.5 algorithm used to encrypt data. - /// + /// public const string Rsa15 = "http://www.w3.org/2001/04/xmlenc#rsa-1_5"; /// /// The RSA-PKCSv1.5 SHA256 algorithm used to encrypt data. - /// + /// public const string Rsa15Sha256 = "http://www.w3.org/2001/04/xmlenc#rsa-1_5-sha2-256"; /// /// The RSA-OAEP algorithm used to encrypt keys. - /// + /// public const string KwRsaOaep = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"; - + /// /// The RSA-PKCSv1.5 algorithm used to encrypt keys. - /// + /// public const string KwRsa15 = "http://www.w3.org/2001/04/xmlenc#rsa-1_5"; /// diff --git a/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs b/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs index 4b673890f..08cd3fff3 100644 --- a/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs +++ b/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs @@ -13,6 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System; using System.Collections.Generic; using System.Reflection; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; @@ -59,6 +60,36 @@ public static class SecurityPolicies /// public const string Aes256_Sha256_RsaPss = BaseUri + "Aes256_Sha256_RsaPss"; + /// + /// The URI for the ECC_nistP256 security policy. + /// + public const string ECC_nistP256 = BaseUri + "ECC_nistP256"; + + /// + /// The URI for the ECC_nistP384 security policy. + /// + public const string ECC_nistP384 = BaseUri + "ECC_nistP384"; + + /// + /// The URI for the ECC_brainpoolP256r1 security policy. + /// + public const string ECC_brainpoolP256r1 = BaseUri + "ECC_brainpoolP256r1"; + + /// + /// The URI for the ECC_brainpoolP384r1 security policy. + /// + public const string ECC_brainpoolP384r1 = BaseUri + "ECC_brainpoolP384r1"; + + /// + /// The URI for the ECC_curve25519 security policy. + /// + public const string ECC_curve25519 = BaseUri + "ECC_curve25519"; + + /// + /// The URI for the ECC_curve448 security policy. + /// + public const string ECC_curve448 = BaseUri + "ECC_curve448"; + /// /// The URI for the Https security policy. /// @@ -66,14 +97,52 @@ public static class SecurityPolicies #endregion #region Static Methods - private static bool IsPlatformSupportedUri(string name) + private static bool IsPlatformSupportedName(string name) { + // all RSA + if (name.Equals(nameof(None)) || + name.Equals(nameof(Basic256)) || + name.Equals(nameof(Basic128Rsa15)) || + name.Equals(nameof(Basic256Sha256)) || + name.Equals(nameof(Aes128_Sha256_RsaOaep))) + { + return true; + } + if (name.Equals(nameof(Aes256_Sha256_RsaPss)) && - !RsaUtils.IsSupportingRSAPssSign.Value) + RsaUtils.IsSupportingRSAPssSign.Value) + { + return true; + } + +#if ECC_SUPPORT + // ECC policy + if (name.Equals(nameof(ECC_nistP256)) || + name.Equals(nameof(ECC_nistP384))) { - return false; + return true; } - return true; + + if (name.Equals(nameof(ECC_brainpoolP256r1)) || + name.Equals(nameof(ECC_brainpoolP384r1))) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return false; + } + return true; + } + + // ECC policy + if (name.Equals(nameof(ECC_curve25519)) || + name.Equals(nameof(ECC_curve448))) + { +#if CURVE25519 + return true; +#endif + } +#endif + return false; } /// @@ -84,7 +153,7 @@ public static string GetUri(string displayName) FieldInfo[] fields = typeof(SecurityPolicies).GetFields(BindingFlags.Public | BindingFlags.Static); foreach (FieldInfo field in fields) { - if (field.Name == displayName && IsPlatformSupportedUri(field.Name)) + if (field.Name == displayName && IsPlatformSupportedName(field.Name)) { return (string)field.GetValue(typeof(SecurityPolicies)); } @@ -103,7 +172,7 @@ public static string GetDisplayName(string policyUri) foreach (FieldInfo field in fields) { if (policyUri == (string)field.GetValue(typeof(SecurityPolicies)) && - IsPlatformSupportedUri(field.Name)) + IsPlatformSupportedName(field.Name)) { return field.Name; } @@ -112,6 +181,30 @@ public static string GetDisplayName(string policyUri) return null; } + /// + /// If a security policy is known and spelled according to the spec. + /// + /// + /// This functions returns only information if a security policy Uri is + /// valid and existing according to the spec. + /// It does not provide the information if the policy is supported + /// by the application or by the platform. + /// + public static bool IsValidSecurityPolicyUri(string policyUri) + { + FieldInfo[] fields = typeof(SecurityPolicies).GetFields(BindingFlags.Public | BindingFlags.Static); + + foreach (FieldInfo field in fields) + { + if (policyUri == (string)field.GetValue(typeof(SecurityPolicies))) + { + return true; + } + } + + return false; + } + /// /// Returns the display names for all security policy uris. /// @@ -120,10 +213,10 @@ public static string[] GetDisplayNames() FieldInfo[] fields = typeof(SecurityPolicies).GetFields(BindingFlags.Public | BindingFlags.Static); var names = new List(); - // skip base Uri + // skip base Uri, ignore Https for (int ii = 1; ii < fields.Length - 1; ii++) { - if (IsPlatformSupportedUri(fields[ii].Name)) + if (IsPlatformSupportedName(fields[ii].Name)) { names.Add(fields[ii].Name); } @@ -133,7 +226,28 @@ public static string[] GetDisplayNames() } /// - /// Returns the default security policy uri. + /// Returns the deprecated RSA security policy uri. + /// + public static string[] GetDefaultDeprecatedUris() + { + string[] defaultNames = { + nameof(Basic128Rsa15), + nameof(Basic256) + }; + var defaultUris = new List(); + foreach (var name in defaultNames) + { + var uri = GetUri(name); + if (uri != null) + { + defaultUris.Add(uri); + } + } + return defaultUris.ToArray(); + } + + /// + /// Returns the default RSA security policy uri. /// public static string[] GetDefaultUris() { @@ -153,6 +267,29 @@ public static string[] GetDefaultUris() return defaultUris.ToArray(); } + /// + /// Returns the default ECC security policy uri. + /// + public static string[] GetDefaultEccUris() + { + string[] defaultNames = { + nameof(ECC_nistP256), + nameof(ECC_nistP384), + nameof(ECC_brainpoolP256r1), + nameof(ECC_brainpoolP384r1) + }; + var defaultUris = new List(); + foreach (var name in defaultNames) + { + var uri = GetUri(name); + if (uri != null) + { + defaultUris.Add(uri); + } + } + return defaultUris.ToArray(); + } + /// /// Encrypts the text using the SecurityPolicyUri and returns the result. /// @@ -201,11 +338,21 @@ public static EncryptedData Encrypt(X509Certificate2 certificate, string securit break; } + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: + { + return encryptedData; + } + case SecurityPolicies.None: { break; } + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: default: { throw ServiceResultException.Create( @@ -267,6 +414,10 @@ public static byte[] Decrypt(X509Certificate2 certificate, string securityPolicy break; } + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: case SecurityPolicies.None: { if (String.IsNullOrEmpty(dataToDecrypt.Algorithm)) @@ -276,6 +427,8 @@ public static byte[] Decrypt(X509Certificate2 certificate, string securityPolicy break; } + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: default: { throw ServiceResultException.Create( @@ -336,6 +489,24 @@ public static SignatureData Sign(X509Certificate2 certificate, string securityPo break; } +#if ECC_SUPPORT + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + { + signatureData.Algorithm = null; + signatureData.Signature = EccUtils.Sign(new ArraySegment(dataToSign), certificate, HashAlgorithmName.SHA256); + break; + } + + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + signatureData.Algorithm = null; + signatureData.Signature = EccUtils.Sign(new ArraySegment(dataToSign), certificate, HashAlgorithmName.SHA384); + break; + } +#endif + case SecurityPolicies.None: { signatureData.Algorithm = null; @@ -343,6 +514,8 @@ public static SignatureData Sign(X509Certificate2 certificate, string securityPo break; } + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: default: { throw ServiceResultException.Create( @@ -418,13 +591,27 @@ public static bool Verify(X509Certificate2 certificate, string securityPolicyUri signature.Algorithm, SecurityAlgorithms.RsaPssSha256); } +#if ECC_SUPPORT + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + { + return EccUtils.Verify(new ArraySegment(dataToVerify), signature.Signature, certificate, HashAlgorithmName.SHA256); + } + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + return EccUtils.Verify(new ArraySegment(dataToVerify), signature.Signature, certificate, HashAlgorithmName.SHA384); + } +#endif // always accept signatures if security is not used. case SecurityPolicies.None: { return true; } + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: default: { throw ServiceResultException.Create( diff --git a/Stack/Opc.Ua.Core/Stack/Bindings/ITransportBindings.cs b/Stack/Opc.Ua.Core/Stack/Bindings/ITransportBindings.cs index 9390e5b01..5adf836b6 100644 --- a/Stack/Opc.Ua.Core/Stack/Bindings/ITransportBindings.cs +++ b/Stack/Opc.Ua.Core/Stack/Bindings/ITransportBindings.cs @@ -93,8 +93,7 @@ public interface ITransportListenerFactory : /// The base addreses for the service host. /// The server description. /// The list of supported security policies. - /// The server certificate. - /// The cert cahin of the server certificate. + /// The provider for application certificates. List CreateServiceHost( ServerBase serverBase, IDictionary hosts, @@ -102,8 +101,7 @@ List CreateServiceHost( IList baseAddresses, ApplicationDescription serverDescription, List securityPolicies, - X509Certificate2 instanceCertificate, - X509Certificate2Collection instanceCertificateChain + CertificateTypesProvider instanceCertificateTypesProvider ); } diff --git a/Stack/Opc.Ua.Core/Stack/Client/ReverseConnectHost.cs b/Stack/Opc.Ua.Core/Stack/Client/ReverseConnectHost.cs index d820dd31f..c0849c6cd 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/ReverseConnectHost.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/ReverseConnectHost.cs @@ -67,8 +67,6 @@ public void Open() CertificateValidator = null, NamespaceUris = null, Factory = null, - ServerCertificate = null, - ServerCertificateChain = null, ReverseConnectListener = true }; diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs index 423bf99e7..38f3e20ea 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs @@ -436,11 +436,13 @@ public virtual async Task Validate(ApplicationType applicationType) SecurityConfiguration.Validate(); - // load private key - await SecurityConfiguration.ApplicationCertificate.LoadPrivateKeyEx(SecurityConfiguration.CertificatePasswordProvider).ConfigureAwait(false); - - Func generateDefaultUri = () => + // load private keys + foreach (var applicationCertificate in SecurityConfiguration.ApplicationCertificates) { + await applicationCertificate.LoadPrivateKeyEx(SecurityConfiguration.CertificatePasswordProvider).ConfigureAwait(false); + } + + Func generateDefaultUri = () => { var sb = new StringBuilder(); sb.Append("urn:"); sb.Append(Utils.GetHostName()); @@ -547,7 +549,7 @@ public ConfiguredEndpointCollection LoadCachedEndpoints(bool createAlways, bool } catch (Exception e) { - Utils.Trace(e, "Could not load configuration from file: {0}", filePath); + Utils.LogError(e, "Could not load configuration from file: {0}", filePath); } finally { diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs b/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs index 230791f77..13f742720 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs @@ -148,7 +148,9 @@ public SecuredApplication ReadConfiguration(string filePath) // copy the security settings. if (applicationConfiguration.SecurityConfiguration != null) { +#pragma warning disable CS0618 // Type or member is obsolete application.ApplicationCertificate = SecuredApplication.ToCertificateIdentifier(applicationConfiguration.SecurityConfiguration.ApplicationCertificate); +#pragma warning restore CS0618 // Type or member is obsolete if (applicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates != null) { @@ -318,7 +320,9 @@ private static void UpdateDocument(XmlElement element, SecuredApplication applic if (application.ApplicationCertificate != null) { +#pragma warning disable CS0618 // Type or member is obsolete security.ApplicationCertificate = SecuredApplication.FromCertificateIdentifier(application.ApplicationCertificate); +#pragma warning restore CS0618 // Type or member is obsolete } security.TrustedIssuerCertificates = SecuredApplication.FromCertificateStoreIdentifierToTrustList(application.IssuerCertificateStore); diff --git a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs index e8d0dfc00..caebcb824 100644 --- a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs @@ -628,35 +628,19 @@ private set } /// - /// The server's application instance certificate. + /// The server's application instance certificate types provider. /// - /// The instance X.509 certificate. - protected X509Certificate2 InstanceCertificate + /// The provider for the X.509 certificates. + public CertificateTypesProvider InstanceCertificateTypesProvider { get { - return (X509Certificate2)m_instanceCertificate; + return m_InstanceCertificateTypesProvider; } private set { - m_instanceCertificate = value; - } - } - - /// - /// Gets the instance certificate chain. - /// - protected X509Certificate2Collection InstanceCertificateChain - { - get - { - return m_instanceCertificateChain; - } - - private set - { - m_instanceCertificateChain = value; + m_InstanceCertificateTypesProvider = value; } } @@ -782,30 +766,10 @@ public static bool RequireEncryption(EndpointDescription description) protected virtual void OnCertificateUpdate(object sender, CertificateUpdateEventArgs e) { // disconnect all sessions - InstanceCertificateChain = null; - InstanceCertificate = e.SecurityConfiguration.ApplicationCertificate.Certificate; - if (Configuration.SecurityConfiguration.SendCertificateChain) - { - InstanceCertificateChain = new X509Certificate2Collection(InstanceCertificate); - var issuers = new List(); - var validationErrors = new Dictionary(); - Configuration.CertificateValidator.GetIssuersNoExceptionsOnGetIssuer(InstanceCertificateChain, issuers, validationErrors).GetAwaiter().GetResult(); - foreach (var error in validationErrors) - { - if (error.Value != null) - { - Utils.LogCertificate("OnCertificateUpdate: GetIssuers Validation Error: {0}", error.Key, error.Value.Result); - } - } - for (int i = 0; i < issuers.Count; i++) - { - InstanceCertificateChain.Add(issuers[i].Certificate); - } - } - + InstanceCertificateTypesProvider.Update(e.SecurityConfiguration); foreach (var listener in TransportListeners) { - listener.CertificateUpdate(e.CertificateValidator, InstanceCertificate, InstanceCertificateChain); + listener.CertificateUpdate(e.CertificateValidator, InstanceCertificateTypesProvider); } } @@ -832,7 +796,7 @@ ICertificateValidator certificateValidator settings.Descriptions = endpoints; settings.Configuration = endpointConfiguration; - settings.ServerCertificate = InstanceCertificate; + settings.ServerCertificateTypesProvider = InstanceCertificateTypesProvider; settings.CertificateValidator = certificateValidator; settings.NamespaceUris = MessageContext.NamespaceUris; settings.Factory = MessageContext.Factory; @@ -1301,47 +1265,38 @@ protected virtual void OnServerStarting(ApplicationConfiguration configuration) } // load the instance certificate. - if (configuration.SecurityConfiguration.ApplicationCertificate != null) + X509Certificate2 defaultInstanceCertificate = null; + InstanceCertificateTypesProvider = new CertificateTypesProvider(configuration); + foreach (var securityPolicy in configuration.ServerConfiguration.SecurityPolicies) { - InstanceCertificate = configuration.SecurityConfiguration.ApplicationCertificate.Find(true).GetAwaiter().GetResult(); - } + if (securityPolicy.SecurityMode == MessageSecurityMode.None) + { + continue; + } - if (InstanceCertificate == null) - { - throw new ServiceResultException( - StatusCodes.BadConfigurationError, - "Server does not have an instance certificate assigned."); - } + var instanceCertificate = InstanceCertificateTypesProvider.GetInstanceCertificate(securityPolicy.SecurityPolicyUri); - if (!InstanceCertificate.HasPrivateKey) - { - throw new ServiceResultException( - StatusCodes.BadConfigurationError, - "Server does not have access to the private key for the instance certificate."); - } + if (instanceCertificate == null) + { + throw new ServiceResultException( + StatusCodes.BadConfigurationError, + "Server does not have an instance certificate assigned."); + } - // load certificate chain. - InstanceCertificateChain = new X509Certificate2Collection(InstanceCertificate); - var issuers = new List(); - var validationErrors = new Dictionary(); - configuration.CertificateValidator.GetIssuersNoExceptionsOnGetIssuer(InstanceCertificateChain, issuers, validationErrors).Wait(); + if (!instanceCertificate.HasPrivateKey) + { + throw new ServiceResultException( + StatusCodes.BadConfigurationError, + "Server does not have access to the private key for the instance certificate."); + } - if (validationErrors.Count > 0) - { - Utils.LogWarning("Issuer validation errors ignored on startup:"); - // only list warning for errors to avoid that the server can not start - foreach (var error in validationErrors) + if (defaultInstanceCertificate == null) { - if (error.Value != null) - { - Utils.LogCertificate(LogLevel.Warning, "- " + error.Value.Message, error.Key); - } + defaultInstanceCertificate = instanceCertificate; } - } - for (int i = 0; i < issuers.Count; i++) - { - InstanceCertificateChain.Add(issuers[i].Certificate); + // preload chain + InstanceCertificateTypesProvider.LoadCertificateChainAsync(instanceCertificate).GetAwaiter().GetResult(); } // use the message context from the configuration to ensure the channels are using the same one. @@ -1352,7 +1307,10 @@ protected virtual void OnServerStarting(ApplicationConfiguration configuration) // assign a unique identifier if none specified. if (String.IsNullOrEmpty(configuration.ApplicationUri)) { - configuration.ApplicationUri = X509Utils.GetApplicationUriFromCertificate(InstanceCertificate); + var instanceCertificate = InstanceCertificateTypesProvider.GetInstanceCertificate( + configuration.ServerConfiguration.SecurityPolicies[0].SecurityPolicyUri); + + configuration.ApplicationUri = X509Utils.GetApplicationUriFromCertificate(instanceCertificate); if (String.IsNullOrEmpty(configuration.ApplicationUri)) { @@ -1368,9 +1326,9 @@ protected virtual void OnServerStarting(ApplicationConfiguration configuration) MessageContext.NamespaceUris.Append(configuration.ApplicationUri); // assign an instance name. - if (String.IsNullOrEmpty(configuration.ApplicationName) && InstanceCertificate != null) + if (String.IsNullOrEmpty(configuration.ApplicationName) && defaultInstanceCertificate != null) { - configuration.ApplicationName = InstanceCertificate.GetNameInfo(X509NameType.DnsName, false); + configuration.ApplicationName = defaultInstanceCertificate.GetNameInfo(X509NameType.DnsName, false); } // save the certificate validator. @@ -1684,8 +1642,7 @@ private void OnProcessRequestQueue(object state) private object m_messageContext; private object m_serverError; private object m_certificateValidator; - private object m_instanceCertificate; - private X509Certificate2Collection m_instanceCertificateChain; + private CertificateTypesProvider m_InstanceCertificateTypesProvider; private object m_serverProperties; private object m_configuration; private object m_serverDescription; diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs index e980666c3..a635c437b 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs @@ -33,26 +33,10 @@ public TcpListenerChannel( ITcpChannelListener listener, BufferManager bufferManager, ChannelQuotas quotas, - X509Certificate2 serverCertificate, + CertificateTypesProvider serverCertificateTypeProvider, EndpointDescriptionCollection endpoints) : - this(contextId, listener, bufferManager, quotas, serverCertificate, null, endpoints) - { - } - - /// - /// Attaches the object to an existing socket. - /// - public TcpListenerChannel( - string contextId, - ITcpChannelListener listener, - BufferManager bufferManager, - ChannelQuotas quotas, - X509Certificate2 serverCertificate, - X509Certificate2Collection serverCertificateChain, - EndpointDescriptionCollection endpoints) - : - base(contextId, bufferManager, quotas, serverCertificate, serverCertificateChain, endpoints, MessageSecurityMode.None, SecurityPolicies.None) + base(contextId, bufferManager, quotas, serverCertificateTypeProvider, endpoints, MessageSecurityMode.None, SecurityPolicies.None) { m_listener = listener; } diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpReverseConnectChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpReverseConnectChannel.cs index 75e85cdb4..ef49d2a2b 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpReverseConnectChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpReverseConnectChannel.cs @@ -32,7 +32,7 @@ public TcpReverseConnectChannel( ChannelQuotas quotas, EndpointDescriptionCollection endpoints) : - base(contextId, listener, bufferManager, quotas, null, null, endpoints) + base(contextId, listener, bufferManager, quotas, null, endpoints) { } #endregion diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs index fc8930a47..a8ce8c6b5 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs @@ -35,27 +35,10 @@ public TcpServerChannel( ITcpChannelListener listener, BufferManager bufferManager, ChannelQuotas quotas, - X509Certificate2 serverCertificate, + CertificateTypesProvider serverCertificateTypesProvider, EndpointDescriptionCollection endpoints) : - this(contextId, listener, bufferManager, quotas, serverCertificate, null, endpoints) - { - m_queuedResponses = new SortedDictionary(); - } - - /// - /// Attaches the object to an existing socket. - /// - public TcpServerChannel( - string contextId, - ITcpChannelListener listener, - BufferManager bufferManager, - ChannelQuotas quotas, - X509Certificate2 serverCertificate, - X509Certificate2Collection serverCertificateChain, - EndpointDescriptionCollection endpoints) - : - base(contextId, listener, bufferManager, quotas, serverCertificate, serverCertificateChain, endpoints) + base(contextId, listener, bufferManager, quotas, serverCertificateTypesProvider, endpoints) { m_queuedResponses = new SortedDictionary(); } @@ -602,11 +585,11 @@ private bool ProcessOpenSecureChannelRequest(uint messageType, ArraySegment CreateServiceHost( IList baseAddresses, ApplicationDescription serverDescription, List securityPolicies, - X509Certificate2 instanceCertificate, - X509Certificate2Collection instanceCertificateChain) + CertificateTypesProvider instanceCertificateTypesProvider) { // generate a unique host name. string hostName = "/Tcp"; @@ -100,21 +99,17 @@ public List CreateServiceHost( if (requireEncryption) { - description.ServerCertificate = instanceCertificate.RawData; - - // check if complete chain should be sent. - if (configuration.SecurityConfiguration.SendCertificateChain && - instanceCertificateChain != null && - instanceCertificateChain.Count > 1) + if (instanceCertificateTypesProvider != null) { - List serverCertificateChain = new List(); + var instanceCertificate = instanceCertificateTypesProvider.GetInstanceCertificate(description.SecurityPolicyUri); + description.ServerCertificate = instanceCertificate?.RawData; - for (int i = 0; i < instanceCertificateChain.Count; i++) + // check if complete chain should be sent. + if (instanceCertificate != null && + configuration.SecurityConfiguration.SendCertificateChain) { - serverCertificateChain.AddRange(instanceCertificateChain[i].RawData); + description.ServerCertificate = instanceCertificateTypesProvider.LoadCertificateChainRawAsync(instanceCertificate).GetAwaiter().GetResult(); } - - description.ServerCertificate = serverCertificateChain.ToArray(); } } diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs index b6127f59c..a898794b8 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs @@ -133,8 +133,7 @@ public void Open( m_quotas.CertificateValidator = settings.CertificateValidator; // save the server certificate. - m_serverCertificate = settings.ServerCertificate; - m_serverCertificateChain = settings.ServerCertificateChain; + m_serverCertificateTypesProvider = settings.ServerCertificateTypesProvider; m_bufferManager = new BufferManager("Server", m_quotas.MaxBufferSize); m_channels = new Dictionary(); @@ -226,7 +225,7 @@ public void CreateReverseConnection(Uri url, int timeout) this, m_bufferManager, m_quotas, - m_serverCertificate, + m_serverCertificateTypesProvider, m_descriptions); uint channelId = GetNextChannelId(); @@ -432,12 +431,11 @@ public async Task TransferListenerChannel( /// public void CertificateUpdate( ICertificateValidator validator, - X509Certificate2 serverCertificate, - X509Certificate2Collection serverCertificateChain) + CertificateTypesProvider certificateTypesProvider + ) { m_quotas.CertificateValidator = validator; - m_serverCertificate = serverCertificate; - m_serverCertificateChain = serverCertificateChain; + m_serverCertificateTypesProvider = certificateTypesProvider; foreach (var description in m_descriptions) { // check if complete chain should be sent. @@ -455,6 +453,7 @@ public void CertificateUpdate( } else if (description.ServerCertificate != null) { + var serverCertificate = certificateTypesProvider.GetInstanceCertificate(description.SecurityPolicyUri); description.ServerCertificate = serverCertificate.RawData; } } @@ -506,8 +505,7 @@ private void OnAccept(object sender, SocketAsyncEventArgs e) this, m_bufferManager, m_quotas, - m_serverCertificate, - m_serverCertificateChain, + m_serverCertificateTypesProvider, m_descriptions); } @@ -716,8 +714,7 @@ private void SetUri(Uri baseAddress, string relativeAddress) private EndpointDescriptionCollection m_descriptions; private BufferManager m_bufferManager; private ChannelQuotas m_quotas; - private X509Certificate2 m_serverCertificate; - private X509Certificate2Collection m_serverCertificateChain; + private CertificateTypesProvider m_serverCertificateTypesProvider; private uint m_lastChannelId; private Socket m_listeningSocket; private Socket m_listeningSocketIPv6; diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs index 273dc0196..f41cd3d2e 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs @@ -97,19 +97,6 @@ internal X509Certificate2Collection ClientCertificateChain set { m_clientCertificateChain = value; } } - /// - /// Creates a new nonce. - /// - protected byte[] CreateNonce() - { - uint length = GetNonceLength(); - if (length > 0) - { - return Utils.Nonce.CreateNonce(length); - } - return null; - } - /// /// Returns the thumbprint as a uppercase string. /// @@ -195,46 +182,188 @@ protected static void CompareCertificates(X509Certificate2 expected, X509Certifi /// protected uint GetNonceLength() { - return Utils.Nonce.GetNonceLength(SecurityPolicyUri); + switch (SecurityPolicyUri) + { + case SecurityPolicies.Basic128Rsa15: + { + return 16; + } + + case SecurityPolicies.Basic256: + case SecurityPolicies.Basic256Sha256: + case SecurityPolicies.Aes128_Sha256_RsaOaep: + case SecurityPolicies.Aes256_Sha256_RsaPss: + { + return 32; + } + + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + { + return 64; + } + + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + return 96; + } + + case SecurityPolicies.ECC_curve25519: + { + return 32; + } + + case SecurityPolicies.ECC_curve448: + { + return 56; + } + + default: + case SecurityPolicies.None: + { + return 0; + } + } } /// /// Validates the nonce. /// - protected bool ValidateNonce(byte[] nonce) + protected byte[] CreateNonce(X509Certificate2 certificate) { - return Utils.Nonce.ValidateNonce(nonce, SecurityMode, SecurityPolicyUri); + switch (SecurityPolicyUri) + { + case SecurityPolicies.Basic128Rsa15: + case SecurityPolicies.Basic256: + case SecurityPolicies.Basic256Sha256: + case SecurityPolicies.Aes128_Sha256_RsaOaep: + case SecurityPolicies.Aes256_Sha256_RsaPss: + { + uint length = GetNonceLength(); + + if (length > 0) + { + return Utils.Nonce.CreateNonce(length); + } + + break; + } +#if ECC_SUPPORT + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + m_localNonce = Nonce.CreateNonce(SecurityPolicyUri, GetNonceLength()); + return m_localNonce.Data; + } +#endif + default: + case SecurityPolicies.None: + { + return null; + } + } + + return null; } /// - /// Returns the plain text block size for key in the specified certificate. + /// Validates the nonce. /// - protected int GetPlainTextBlockSize(X509Certificate2 receiverCertificate) + protected bool ValidateNonce(X509Certificate2 certificate, byte[] nonce) { + // no nonce needed for no security. + if (SecurityMode == MessageSecurityMode.None) + { + return true; + } + + // check the length. + if (nonce == null || nonce.Length < GetNonceLength()) + { + return false; + } + switch (SecurityPolicyUri) { + case SecurityPolicies.Basic128Rsa15: case SecurityPolicies.Basic256: case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Aes128_Sha256_RsaOaep: + case SecurityPolicies.Aes256_Sha256_RsaPss: + { + // try to catch programming errors by rejecting nonces with all zeros. + for (int ii = 0; ii < nonce.Length; ii++) { - return RsaUtils.GetPlainTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA1); + if (nonce[ii] != 0) + { + return true; + } } + break; + } +#if ECC_SUPPORT + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + m_remoteNonce = Nonce.CreateNonce(SecurityPolicyUri, nonce); + return true; + } +#endif + } + + return false; + } + + + /// + /// Returns the plain text block size for key in the specified certificate. + /// + protected int GetPlainTextBlockSize(X509Certificate2 receiverCertificate) + { + switch (SecurityPolicyUri) + { + case SecurityPolicies.Basic256: + case SecurityPolicies.Basic256Sha256: + case SecurityPolicies.Aes128_Sha256_RsaOaep: + { + return RsaUtils.GetPlainTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA1); + } + case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return RsaUtils.GetPlainTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA256); - } + { + return RsaUtils.GetPlainTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA256); + } case SecurityPolicies.Basic128Rsa15: - { - return RsaUtils.GetPlainTextBlockSize(receiverCertificate, RsaUtils.Padding.Pkcs1); - } + { + return RsaUtils.GetPlainTextBlockSize(receiverCertificate, RsaUtils.Padding.Pkcs1); + } + + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + return 1; + } default: case SecurityPolicies.None: - { - return 1; - } + { + return 1; + } } } @@ -248,25 +377,35 @@ protected int GetCipherTextBlockSize(X509Certificate2 receiverCertificate) case SecurityPolicies.Basic256: case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Aes128_Sha256_RsaOaep: - { - return RsaUtils.GetCipherTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA1); - } + { + return RsaUtils.GetCipherTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA1); + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return RsaUtils.GetCipherTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA256); - } + { + return RsaUtils.GetCipherTextBlockSize(receiverCertificate, RsaUtils.Padding.OaepSHA256); + } case SecurityPolicies.Basic128Rsa15: - { - return RsaUtils.GetCipherTextBlockSize(receiverCertificate, RsaUtils.Padding.Pkcs1); - } + { + return RsaUtils.GetCipherTextBlockSize(receiverCertificate, RsaUtils.Padding.Pkcs1); + } + + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + return 1; + } default: case SecurityPolicies.None: - { - return 1; - } + { + return 1; + } } } @@ -359,15 +498,25 @@ protected int GetAsymmetricSignatureSize(X509Certificate2 senderCertificate) case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return RsaUtils.GetSignatureLength(senderCertificate); - } - + { + return RsaUtils.GetSignatureLength(senderCertificate); + } +#if ECC_SUPPORT + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + return EccUtils.GetSignatureLength(senderCertificate); + } +#endif default: case SecurityPolicies.None: - { - return 0; - } + { + return 0; + } } } @@ -596,43 +745,46 @@ protected BufferCollection WriteAsymmetricMessage( if (SecurityMode != MessageSecurityMode.None) { - if (X509Utils.GetRSAPublicKeySize(receiverCertificate) <= TcpMessageLimits.KeySizeExtraPadding) + if (receiverCertificate.GetRSAPublicKey() != null) { - // need to reserve one byte for the padding. - plainTextSize++; - - if (plainTextSize % plainTextBlockSize != 0) + if (X509Utils.GetRSAPublicKeySize(receiverCertificate) <= TcpMessageLimits.KeySizeExtraPadding) { - padding = plainTextBlockSize - (plainTextSize % plainTextBlockSize); - } + // need to reserve one byte for the padding. + plainTextSize++; - encoder.WriteByte(null, (byte)padding); - for (int ii = 0; ii < padding; ii++) - { - encoder.WriteByte(null, (byte)padding); - } - } - else - { - // need to reserve one byte for the padding. - plainTextSize++; - // need to reserve one byte for the extrapadding. - plainTextSize++; + if (plainTextSize % plainTextBlockSize != 0) + { + padding = plainTextBlockSize - (plainTextSize % plainTextBlockSize); + } - if (plainTextSize % plainTextBlockSize != 0) - { - padding = plainTextBlockSize - (plainTextSize % plainTextBlockSize); + encoder.WriteByte(null, (byte)padding); + for (int ii = 0; ii < padding; ii++) + { + encoder.WriteByte(null, (byte)padding); + } } - - byte paddingSize = (byte)(padding & 0xff); - byte extraPaddingByte = (byte)((padding >> 8) & 0xff); - - encoder.WriteByte(null, paddingSize); - for (int ii = 0; ii < padding; ii++) + else { - encoder.WriteByte(null, (byte)paddingSize); + // need to reserve one byte for the padding. + plainTextSize++; + // need to reserve one byte for the extrapadding. + plainTextSize++; + + if (plainTextSize % plainTextBlockSize != 0) + { + padding = plainTextBlockSize - (plainTextSize % plainTextBlockSize); + } + + byte paddingSize = (byte)(padding & 0xff); + byte extraPaddingByte = (byte)((padding >> 8) & 0xff); + + encoder.WriteByte(null, paddingSize); + for (int ii = 0; ii < padding; ii++) + { + encoder.WriteByte(null, (byte)paddingSize); + } + encoder.WriteByte(null, extraPaddingByte); } - encoder.WriteByte(null, extraPaddingByte); } // update the plaintext size with the padding size. @@ -714,7 +866,7 @@ protected BufferCollection WriteAsymmetricMessage( [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "messageType"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "messageSize")] protected void ReadAsymmetricMessageHeader( BinaryDecoder decoder, - X509Certificate2 receiverCertificate, + ref X509Certificate2 receiverCertificate, out uint secureChannelId, out X509Certificate2Collection senderCertificateChain, out string securityPolicyUri) @@ -773,10 +925,29 @@ protected void ReadAsymmetricMessageHeader( // verify receiver thumbprint. if (thumbprintData != null && thumbprintData.Length > 0) { + bool loadChain = false; + // TODO: client should use the proider too! + if (m_serverCertificateTypesProvider != null) + { + receiverCertificate = m_serverCertificateTypesProvider.GetInstanceCertificate(securityPolicyUri); + m_serverCertificate = receiverCertificate; + loadChain = true; + } + + if (receiverCertificate == null) + { + throw ServiceResultException.Create(StatusCodes.BadCertificateInvalid, "The receiver has no matching certificate for the selected profile."); + } + if (receiverCertificate.Thumbprint.ToUpperInvariant() != GetThumbprintString(thumbprintData)) { throw ServiceResultException.Create(StatusCodes.BadCertificateInvalid, "The receiver's certificate thumbprint is not valid."); } + + if (loadChain) + { + m_serverCertificateChain = m_serverCertificateTypesProvider?.LoadCertificateChainAsync(receiverCertificate).GetAwaiter().GetResult(); + } } else { @@ -806,6 +977,8 @@ protected void ReviseSecurityMode(bool firstCall, MessageSecurityMode requestedM { m_securityMode = endpoint.SecurityMode; m_selectedEndpoint = endpoint; + m_serverCertificate = m_serverCertificateTypesProvider.GetInstanceCertificate(m_securityPolicyUri); + m_serverCertificateChain = m_serverCertificateTypesProvider.LoadCertificateChainAsync(m_serverCertificate).GetAwaiter().GetResult(); supported = true; break; } @@ -847,6 +1020,8 @@ protected virtual bool SetEndpointUrl(string endpointUrl) m_securityMode = endpoint.SecurityMode; m_securityPolicyUri = endpoint.SecurityPolicyUri; + m_serverCertificate = m_serverCertificateTypesProvider.GetInstanceCertificate(m_securityPolicyUri); + m_serverCertificateChain = m_serverCertificateTypesProvider.LoadCertificateChainAsync(m_serverCertificate).GetAwaiter().GetResult(); m_selectedEndpoint = endpoint; return true; } @@ -860,6 +1035,7 @@ protected virtual bool SetEndpointUrl(string endpointUrl) protected ArraySegment ReadAsymmetricMessage( ArraySegment buffer, X509Certificate2 receiverCertificate, + out uint channelId, out X509Certificate2 senderCertificate, out uint requestId, @@ -873,7 +1049,7 @@ protected ArraySegment ReadAsymmetricMessage( // parse the security header. ReadAsymmetricMessageHeader( decoder, - receiverCertificate, + ref receiverCertificate, out channelId, out senderCertificateChain, out securityPolicyUri); @@ -924,6 +1100,15 @@ protected ArraySegment ReadAsymmetricMessage( { m_securityMode = endpoint.SecurityMode; m_securityPolicyUri = securityPolicyUri; + m_serverCertificate = m_serverCertificateTypesProvider?.GetInstanceCertificate(m_securityPolicyUri); + if (m_serverCertificate != null) + { + m_serverCertificateChain = m_serverCertificateTypesProvider.LoadCertificateChainAsync(m_serverCertificate).GetAwaiter().GetResult(); + } + else + { + m_serverCertificateChain = null; + } m_discoveryOnly = false; m_uninitialized = false; m_selectedEndpoint = endpoint; @@ -945,6 +1130,8 @@ protected ArraySegment ReadAsymmetricMessage( m_securityMode = MessageSecurityMode.None; m_securityPolicyUri = SecurityPolicies.None; + m_serverCertificate = null; + m_serverCertificateChain = null; m_discoveryOnly = true; m_uninitialized = false; m_selectedEndpoint = null; @@ -981,7 +1168,7 @@ protected ArraySegment ReadAsymmetricMessage( // verify padding. int paddingCount = 0; - if (SecurityMode != MessageSecurityMode.None) + if (SecurityMode != MessageSecurityMode.None && receiverCertificate.GetRSAPublicKey() != null) { int paddingEnd = -1; if (X509Utils.GetRSAPublicKeySize(receiverCertificate) > TcpMessageLimits.KeySizeExtraPadding) @@ -1053,26 +1240,41 @@ protected byte[] Sign( { default: case SecurityPolicies.None: - { - return null; - } + { + return null; + } case SecurityPolicies.Basic256: case SecurityPolicies.Basic128Rsa15: - { - return Rsa_Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); - } + { + return Rsa_Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); + } case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Basic256Sha256: - { - return Rsa_Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - } + { + return Rsa_Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return Rsa_Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); - } + { + return Rsa_Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + } +#if ECC_SUPPORT + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + return EccUtils.Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA256); + } + + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + return EccUtils.Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA384); + } +#endif } } @@ -1093,31 +1295,45 @@ protected bool Verify( switch (SecurityPolicyUri) { case SecurityPolicies.None: - { - return true; - } + { + return true; + } case SecurityPolicies.Basic128Rsa15: case SecurityPolicies.Basic256: - { - return Rsa_Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); - } + { + return Rsa_Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); + } case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Basic256Sha256: - { - return Rsa_Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - } + { + return Rsa_Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return Rsa_Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); - } + { + return Rsa_Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + } +#if ECC_SUPPORT + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + return EccUtils.Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA256); + } + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + return EccUtils.Verify(dataToVerify, signature, senderCertificate, HashAlgorithmName.SHA384); + } +#endif default: - { - return false; - } + { + return false; + } } } @@ -1137,32 +1353,38 @@ protected ArraySegment Encrypt( switch (SecurityPolicyUri) { default: + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: case SecurityPolicies.None: - { - byte[] encryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Encrypt"); + { + byte[] encryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Encrypt"); - Array.Copy(headerToCopy.Array, headerToCopy.Offset, encryptedBuffer, 0, headerToCopy.Count); - Array.Copy(dataToEncrypt.Array, dataToEncrypt.Offset, encryptedBuffer, headerToCopy.Count, dataToEncrypt.Count); + Array.Copy(headerToCopy.Array, headerToCopy.Offset, encryptedBuffer, 0, headerToCopy.Count); + Array.Copy(dataToEncrypt.Array, dataToEncrypt.Offset, encryptedBuffer, headerToCopy.Count, dataToEncrypt.Count); - return new ArraySegment(encryptedBuffer, 0, dataToEncrypt.Count + headerToCopy.Count); - } + return new ArraySegment(encryptedBuffer, 0, dataToEncrypt.Count + headerToCopy.Count); + } case SecurityPolicies.Basic256: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Basic256Sha256: - { - return Rsa_Encrypt(dataToEncrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA1); - } + { + return Rsa_Encrypt(dataToEncrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA1); + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return Rsa_Encrypt(dataToEncrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA256); - } + { + return Rsa_Encrypt(dataToEncrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA256); + } case SecurityPolicies.Basic128Rsa15: - { - return Rsa_Encrypt(dataToEncrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.Pkcs1); - } + { + return Rsa_Encrypt(dataToEncrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.Pkcs1); + } } } @@ -1181,32 +1403,38 @@ protected ArraySegment Decrypt( switch (SecurityPolicyUri) { default: + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: case SecurityPolicies.None: - { - byte[] decryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Decrypt"); + { + byte[] decryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Decrypt"); - Array.Copy(headerToCopy.Array, headerToCopy.Offset, decryptedBuffer, 0, headerToCopy.Count); - Array.Copy(dataToDecrypt.Array, dataToDecrypt.Offset, decryptedBuffer, headerToCopy.Count, dataToDecrypt.Count); + Array.Copy(headerToCopy.Array, headerToCopy.Offset, decryptedBuffer, 0, headerToCopy.Count); + Array.Copy(dataToDecrypt.Array, dataToDecrypt.Offset, decryptedBuffer, headerToCopy.Count, dataToDecrypt.Count); - return new ArraySegment(decryptedBuffer, 0, dataToDecrypt.Count + headerToCopy.Count); - } + return new ArraySegment(decryptedBuffer, 0, dataToDecrypt.Count + headerToCopy.Count); + } case SecurityPolicies.Basic256: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Basic256Sha256: - { - return Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA1); - } + { + return Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA1); + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA256); - } + { + return Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.OaepSHA256); + } case SecurityPolicies.Basic128Rsa15: - { - return Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.Pkcs1); - } + { + return Rsa_Decrypt(dataToDecrypt, headerToCopy, receiverCertificate, RsaUtils.Padding.Pkcs1); + } } } #endregion @@ -1217,11 +1445,16 @@ protected ArraySegment Decrypt( private string m_securityPolicyUri; private bool m_discoveryOnly; private EndpointDescription m_selectedEndpoint; + private CertificateTypesProvider m_serverCertificateTypesProvider; private X509Certificate2 m_serverCertificate; private X509Certificate2Collection m_serverCertificateChain; private X509Certificate2 m_clientCertificate; private X509Certificate2Collection m_clientCertificateChain; private bool m_uninitialized; +#if ECC_SUPPORT + private Nonce m_localNonce; + private Nonce m_remoteNonce; +#endif #endregion } } diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs index 5335ab0e6..e847776b8 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs @@ -89,6 +89,14 @@ protected void DiscardTokens() #endregion #region Symmetric Cryptography Functions + /// + /// Indicates that an explicit signature is not present. + /// + private bool UseAuthenticatedEncryption + { + get; set; + } + /// /// The byte length of the MAC (a.k.a signature) attached to each message. /// @@ -104,6 +112,8 @@ protected void DiscardTokens() /// protected void CalculateSymmetricKeySizes() { + UseAuthenticatedEncryption = false; + switch (SecurityPolicyUri) { case SecurityPolicies.Basic128Rsa15: @@ -151,6 +161,37 @@ protected void CalculateSymmetricKeySizes() break; } + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + { + m_hmacHashSize = 32; + m_signatureKeySize = 32; + m_encryptionKeySize = 16; + m_encryptionBlockSize = 16; + break; + } + + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + UseAuthenticatedEncryption = true; + m_hmacHashSize = 16; + m_signatureKeySize = 32; + m_encryptionKeySize = 32; + m_encryptionBlockSize = 12; + break; + } + + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + m_hmacHashSize = 48; + m_signatureKeySize = 48; + m_encryptionKeySize = 32; + m_encryptionBlockSize = 16; + break; + } + default: case SecurityPolicies.None: { @@ -162,7 +203,74 @@ protected void CalculateSymmetricKeySizes() } } } + private void DeriveKeysWithPSHA(HashAlgorithmName algorithmName, byte[] secret, byte[] seed, ChannelToken token, bool isServer) + { + int length = m_signatureKeySize + m_encryptionKeySize + m_encryptionBlockSize; + + using (var hmac = Utils.CreateHMAC(algorithmName, secret)) + { + var output = Utils.PSHA(hmac, null, seed, 0, length); + + var signingKey = new byte[m_signatureKeySize]; + var encryptingKey = new byte[m_encryptionKeySize]; + var iv = new byte[m_encryptionBlockSize]; + + Buffer.BlockCopy(output, 0, signingKey, 0, signingKey.Length); + Buffer.BlockCopy(output, m_signatureKeySize, encryptingKey, 0, encryptingKey.Length); + Buffer.BlockCopy(output, m_signatureKeySize + m_encryptionKeySize, iv, 0, iv.Length); + + if (isServer) + { + token.ServerSigningKey = signingKey; + token.ServerEncryptingKey = encryptingKey; + token.ServerInitializationVector = iv; + } + else + { + token.ClientSigningKey = signingKey; + token.ClientEncryptingKey = encryptingKey; + token.ClientInitializationVector = iv; + } + } + } + +#if ECC_SUPPORT + private void DeriveKeysWithHKDF(HashAlgorithmName algorithmName, byte[] salt, ChannelToken token, bool isServer) + { + int length = m_signatureKeySize + m_encryptionKeySize + m_encryptionBlockSize; + + var output = m_localNonce.DeriveKey(m_remoteNonce, salt, algorithmName, length); + var signingKey = new byte[m_signatureKeySize]; + var encryptingKey = new byte[m_encryptionKeySize]; + var iv = new byte[m_encryptionBlockSize]; + + Buffer.BlockCopy(output, 0, signingKey, 0, signingKey.Length); + Buffer.BlockCopy(output, m_signatureKeySize, encryptingKey, 0, encryptingKey.Length); + Buffer.BlockCopy(output, m_signatureKeySize + m_encryptionKeySize, iv, 0, iv.Length); + + if (isServer) + { + token.ServerSigningKey = signingKey; + token.ServerEncryptingKey = encryptingKey; + token.ServerInitializationVector = iv; + } + else + { + token.ClientSigningKey = signingKey; + token.ClientEncryptingKey = encryptingKey; + token.ClientInitializationVector = iv; + } + } + + static readonly byte[] s_HkdfClientLabel = new UTF8Encoding().GetBytes("opcua-client"); + static readonly byte[] s_HkdfServerLabel = new UTF8Encoding().GetBytes("opcua-server"); + static readonly byte[] s_HkdfAes128SignOnlyKeyLength = BitConverter.GetBytes((ushort)32); + static readonly byte[] s_HkdfAes256SignOnlyKeyLength = BitConverter.GetBytes((ushort)48); + static readonly byte[] s_HkdfAes128SignAndEncryptKeyLength = BitConverter.GetBytes((ushort)64); + static readonly byte[] s_HkdfAes256SignAndEncryptKeyLength = BitConverter.GetBytes((ushort)96); + static readonly byte[] s_HkdfChaCha20Poly1305KeyLength = BitConverter.GetBytes((ushort)76); +#endif /// /// Computes the keys for a token. @@ -176,25 +284,84 @@ protected void ComputeKeys(ChannelToken token) return; } - if (SecurityPolicyUri == SecurityPolicies.Basic256Sha256 || - SecurityPolicyUri == SecurityPolicies.Aes128_Sha256_RsaOaep || - SecurityPolicyUri == SecurityPolicies.Aes256_Sha256_RsaPss) - { - 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); - } - else + byte[] serverSecret = token.ServerNonce; + byte[] clientSecret = token.ClientNonce; + HashAlgorithmName algorithmName = HashAlgorithmName.SHA256; + + switch (SecurityPolicyUri) { - 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); + default: + { + DeriveKeysWithPSHA(algorithmName, serverSecret, clientSecret, token, false); + DeriveKeysWithPSHA(algorithmName, clientSecret, serverSecret, token, true); + break; + } +#if ECC_SUPPORT + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + { + algorithmName = HashAlgorithmName.SHA256; + var length = (SecurityMode == MessageSecurityMode.Sign) ? s_HkdfAes128SignOnlyKeyLength : s_HkdfAes128SignAndEncryptKeyLength; + var serverSalt = Utils.Append(length, s_HkdfServerLabel, serverSecret, clientSecret); + var clientSalt = Utils.Append(length, s_HkdfClientLabel, clientSecret, serverSecret); + + Utils.LogTrace("Length={0}", Utils.ToHexString(length)); + Utils.LogTrace("ClientSecret={0}", Utils.ToHexString(clientSecret)); + Utils.LogTrace("ServerSecret={0}", Utils.ToHexString(clientSecret)); + Utils.LogTrace("ServerSalt={0}", Utils.ToHexString(serverSalt)); + Utils.LogTrace("ClientSalt={0}", Utils.ToHexString(clientSalt)); + + DeriveKeysWithHKDF(algorithmName, serverSalt, token, true); + DeriveKeysWithHKDF(algorithmName, clientSalt, token, false); + break; + } + + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + algorithmName = HashAlgorithmName.SHA384; + var length = (SecurityMode == MessageSecurityMode.Sign) ? s_HkdfAes256SignOnlyKeyLength : s_HkdfAes256SignAndEncryptKeyLength; + var serverSalt = Utils.Append(length, s_HkdfServerLabel, serverSecret, clientSecret); + var clientSalt = Utils.Append(length, s_HkdfClientLabel, clientSecret, serverSecret); + + Utils.LogTrace("Length={0}", Utils.ToHexString(length)); + Utils.LogTrace("ClientSecret={0}", Utils.ToHexString(clientSecret)); + Utils.LogTrace("ServerSecret={0}", Utils.ToHexString(clientSecret)); + Utils.LogTrace("ServerSalt={0}", Utils.ToHexString(serverSalt)); + Utils.LogTrace("ClientSalt={0}", Utils.ToHexString(clientSalt)); + + DeriveKeysWithHKDF(algorithmName, serverSalt, token, true); + DeriveKeysWithHKDF(algorithmName, clientSalt, token, false); + break; + } + + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + algorithmName = HashAlgorithmName.SHA256; + var length = s_HkdfChaCha20Poly1305KeyLength; + var serverSalt = Utils.Append(length, s_HkdfServerLabel, serverSecret, clientSecret); + var clientSalt = Utils.Append(length, s_HkdfClientLabel, clientSecret, serverSecret); + + Utils.LogTrace("Length={0}", Utils.ToHexString(length)); + Utils.LogTrace("ClientSecret={0}", Utils.ToHexString(clientSecret)); + Utils.LogTrace("ServerSecret={0}", Utils.ToHexString(clientSecret)); + Utils.LogTrace("ServerSalt={0}", Utils.ToHexString(serverSalt)); + Utils.LogTrace("ClientSalt={0}", Utils.ToHexString(clientSalt)); + + DeriveKeysWithHKDF(algorithmName, serverSalt, token, true); + DeriveKeysWithHKDF(algorithmName, clientSalt, token, false); + break; + } +#endif + case SecurityPolicies.Basic128Rsa15: + case SecurityPolicies.Basic256: + { + algorithmName = HashAlgorithmName.SHA1; + DeriveKeysWithPSHA(algorithmName, serverSecret, clientSecret, token, false); + DeriveKeysWithPSHA(algorithmName, clientSecret, serverSecret, token, true); + break; + } } switch (SecurityPolicyUri) @@ -204,8 +371,12 @@ protected void ComputeKeys(ChannelToken token) case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Aes256_Sha256_RsaPss: + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: { - // create encryptors. + // create encryptors. SymmetricAlgorithm AesCbcEncryptorProvider = Aes.Create(); AesCbcEncryptorProvider.Mode = CipherMode.CBC; AesCbcEncryptorProvider.Padding = PaddingMode.None; @@ -219,21 +390,54 @@ protected void ComputeKeys(ChannelToken token) AesCbcDecryptorProvider.Key = token.ServerEncryptingKey; AesCbcDecryptorProvider.IV = token.ServerInitializationVector; token.ServerEncryptor = AesCbcDecryptorProvider; + break; + } - // create HMACs. - if (SecurityPolicyUri == SecurityPolicies.Basic256Sha256 || - SecurityPolicyUri == SecurityPolicies.Aes128_Sha256_RsaOaep || - SecurityPolicyUri == SecurityPolicies.Aes256_Sha256_RsaPss) - { - // SHA256 - token.ServerHmac = new HMACSHA256(token.ServerSigningKey); - token.ClientHmac = new HMACSHA256(token.ClientSigningKey); - } - else - { // SHA1 - token.ServerHmac = new HMACSHA1(token.ServerSigningKey); - token.ClientHmac = new HMACSHA1(token.ClientSigningKey); - } + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + break; + } + + default: + case SecurityPolicies.None: + { + break; + } + } + + switch (SecurityPolicyUri) + { + case SecurityPolicies.Basic128Rsa15: + case SecurityPolicies.Basic256: + { + token.ServerHmac = new HMACSHA1(token.ServerSigningKey); + token.ClientHmac = new HMACSHA1(token.ClientSigningKey); + break; + } + + case SecurityPolicies.Basic256Sha256: + case SecurityPolicies.Aes128_Sha256_RsaOaep: + case SecurityPolicies.Aes256_Sha256_RsaPss: + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + { + token.ServerHmac = new HMACSHA256(token.ServerSigningKey); + token.ClientHmac = new HMACSHA256(token.ClientSigningKey); + break; + } + + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + token.ServerHmac = new HMACSHA384(token.ServerSigningKey); + token.ClientHmac = new HMACSHA384(token.ClientSigningKey); + break; + } + + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { break; } @@ -243,6 +447,8 @@ protected void ComputeKeys(ChannelToken token) break; } } + + } /// @@ -269,6 +475,12 @@ protected BufferCollection WriteSymmetricMessage( int maxPayloadSize = maxPlainTextSize - SymmetricSignatureSize - 1 - TcpMessageLimits.SequenceHeaderSize; int headerSize = TcpMessageLimits.SymmetricHeaderSize + TcpMessageLimits.SequenceHeaderSize; + // no padding byte. + if (UseAuthenticatedEncryption) + { + maxPayloadSize++; + } + // write the body to stream. ArraySegmentStream ostrm = new ArraySegmentStream( BufferManager, @@ -373,7 +585,7 @@ protected BufferCollection WriteSymmetricMessage( // calculate the padding. int padding = 0; - if (SecurityMode == MessageSecurityMode.SignAndEncrypt) + if (SecurityMode == MessageSecurityMode.SignAndEncrypt && !UseAuthenticatedEncryption) { // reserve one byte for the padding size. count++; @@ -404,7 +616,7 @@ protected BufferCollection WriteSymmetricMessage( messageSize += chunkToProcess.Count; // write padding. - if (SecurityMode == MessageSecurityMode.SignAndEncrypt) + if (SecurityMode == MessageSecurityMode.SignAndEncrypt && !UseAuthenticatedEncryption) { for (int jj = 0; jj <= padding; jj++) { @@ -412,18 +624,26 @@ protected BufferCollection WriteSymmetricMessage( } } + // calculate and write signature. if (SecurityMode != MessageSecurityMode.None) { - // calculate and write signature. - byte[] signature = Sign(token, new ArraySegment(chunkToProcess.Array, 0, encoder.Position), isRequest); - - if (signature != null) + if (UseAuthenticatedEncryption) { - encoder.WriteRawBytes(signature, 0, signature.Length); + strm.Seek(SymmetricSignatureSize, SeekOrigin.Current); + } + else + { + byte[] signature = Sign(token, new ArraySegment(chunkToProcess.Array, 0, encoder.Position), isRequest); + + if (signature != null) + { + encoder.WriteRawBytes(signature, 0, signature.Length); + } } } - if (SecurityMode == MessageSecurityMode.SignAndEncrypt) + if ((SecurityMode == MessageSecurityMode.SignAndEncrypt && !UseAuthenticatedEncryption) || + (SecurityMode != MessageSecurityMode.None && UseAuthenticatedEncryption)) { // encrypt the data. ArraySegment dataToEncrypt = new ArraySegment(chunkToProcess.Array, TcpMessageLimits.SymmetricHeaderSize, encoder.Position - TcpMessageLimits.SymmetricHeaderSize); @@ -534,13 +754,14 @@ protected ArraySegment ReadSymmetricMessage( int headerSize = decoder.Position; - if (SecurityMode == MessageSecurityMode.SignAndEncrypt) + if ((SecurityMode == MessageSecurityMode.SignAndEncrypt && !UseAuthenticatedEncryption) || + (SecurityMode != MessageSecurityMode.None && UseAuthenticatedEncryption)) { // decrypt the message. Decrypt(token, new ArraySegment(buffer.Array, buffer.Offset + headerSize, buffer.Count - headerSize), isRequest); } - if (SecurityMode != MessageSecurityMode.None) + if (SecurityMode != MessageSecurityMode.None && !UseAuthenticatedEncryption) { // extract signature. byte[] signature = new byte[SymmetricSignatureSize]; @@ -560,7 +781,7 @@ protected ArraySegment ReadSymmetricMessage( int paddingCount = 0; - if (SecurityMode == MessageSecurityMode.SignAndEncrypt) + if (SecurityMode == MessageSecurityMode.SignAndEncrypt && !UseAuthenticatedEncryption) { // verify padding. int paddingStart = buffer.Offset + buffer.Count - SymmetricSignatureSize - 1; @@ -607,9 +828,21 @@ protected byte[] Sign(ChannelToken token, ArraySegment dataToSign, bool us case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Aes256_Sha256_RsaPss: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: + case SecurityPolicies.ECC_nistP256: { return SymmetricSign(token, dataToSign, useClientKeys); } + +#if GCMMODE + case SecurityPolicies.ECC_nistP256: + //case SecurityPolicies.Aes128_Gcm256_RsaOaep: + { + return SymmetricSignWithAESGCM(token, dataToSign, useClientKeys); + } +#endif } } @@ -633,12 +866,22 @@ protected bool Verify( case SecurityPolicies.Basic128Rsa15: case SecurityPolicies.Basic256: case SecurityPolicies.Basic256Sha256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Aes256_Sha256_RsaPss: + case SecurityPolicies.ECC_nistP256: { return SymmetricVerify(token, signature, dataToVerify, useClientKeys); } - +#if GCMMODE + case SecurityPolicies.ECC_nistP256: + //case SecurityPolicies.Aes128_Gcm256_RsaOaep: + { + return SymmetricVerifyWithAESGCM(token, signature, dataToVerify, useClientKeys); + } +#endif default: { return false; @@ -653,7 +896,6 @@ protected void Encrypt(ChannelToken token, ArraySegment dataToEncrypt, boo { switch (SecurityPolicyUri) { - default: case SecurityPolicies.None: { break; @@ -662,12 +904,35 @@ protected void Encrypt(ChannelToken token, ArraySegment dataToEncrypt, boo case SecurityPolicies.Basic256: case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Basic128Rsa15: + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Aes256_Sha256_RsaPss: { SymmetricEncrypt(token, dataToEncrypt, useClientKeys); break; } +#if CURVE25519 + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + if (SecurityMode == MessageSecurityMode.SignAndEncrypt) + { + SymmetricEncryptWithChaCha20Poly1305(token, m_localSequenceNumber, dataToEncrypt, useClientKeys); + break; + } + + SymmetricSignWithPoly1305(token, m_localSequenceNumber, dataToEncrypt, useClientKeys); + break; + } +#endif + //case SecurityPolicies.Aes128_Gcm256_RsaOaep: + default: + { + throw new NotSupportedException(SecurityPolicyUri); + } } } @@ -678,7 +943,6 @@ protected void Decrypt(ChannelToken token, ArraySegment dataToDecrypt, boo { switch (SecurityPolicyUri) { - default: case SecurityPolicies.None: { break; @@ -687,12 +951,35 @@ protected void Decrypt(ChannelToken token, ArraySegment dataToDecrypt, boo case SecurityPolicies.Basic256: case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Basic128Rsa15: + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP256r1: + case SecurityPolicies.ECC_brainpoolP384r1: case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Aes256_Sha256_RsaPss: { SymmetricDecrypt(token, dataToDecrypt, useClientKeys); break; } +#if CURVE25519 + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + { + if (SecurityMode == MessageSecurityMode.SignAndEncrypt) + { + SymmetricDecryptWithChaCha20Poly1305(token, m_remoteSequenceNumber, dataToDecrypt, useClientKeys); + break; + } + + SymmetricVerifyWithPoly1305(token, m_remoteSequenceNumber, dataToDecrypt, useClientKeys); + break; + } +#endif + //case SecurityPolicies.Aes128_Gcm256_RsaOaep: + default: + { + throw new NotSupportedException(SecurityPolicyUri); + } } } @@ -713,7 +1000,60 @@ private static byte[] SymmetricSign(ChannelToken token, ArraySegment dataT // return signature. return signature; } +#if GCMMODE + /// + /// Signs the message using SHA1 HMAC + /// + private static byte[] SymmetricSignWithAESGCM(ChannelToken token, ArraySegment dataToSign, bool useClientKeys) + { + try + { + var key = (useClientKeys) ? token.ClientEncryptingKey : token.ServerSigningKey; + var nonce = (useClientKeys) ? token.ClientInitializationVector : token.ServerInitializationVector; + var macSize = (useClientKeys) ? token.ClientSigningKey.Length : token.ServerSigningKey.Length; + + var header = new byte[TcpMessageLimits.SymmetricHeaderSize]; + Buffer.BlockCopy(dataToSign.Array, 0, header, 0, header.Length); + + var cipher = new GcmBlockCipher(new AesEngine()); + var parameters = new AeadParameters(new KeyParameter(key), macSize, nonce, header); + cipher.Init(true, parameters); + + var plainTextLength = dataToSign.Count - TcpMessageLimits.SymmetricHeaderSize; + var cipherLength = cipher.GetOutputSize(plainTextLength); + var cipherText = new byte[cipherLength]; + + var length = cipher.ProcessBytes( + dataToSign.Array, + TcpMessageLimits.SymmetricHeaderSize, + plainTextLength, + cipherText, + 0); + + cipher.DoFinal(cipherText, length); + + Buffer.BlockCopy(cipherText, 0, dataToSign.Array, TcpMessageLimits.SymmetricHeaderSize, cipherText.Length); + + // return signature. + var signature = new byte[cipherLength + header.Length - dataToSign.Count]; + Buffer.BlockCopy(dataToSign.Array, dataToSign.Count, signature, 0, signature.Length); + int eod = dataToSign.Count + signature.Length; + int siz = BitConverter.ToInt32(dataToSign.Array, 4); + + //if (!SymmetricVerifyWithAESGCM(token, signature, dataToSign, useClientKeys)) + //{ + // int x = 0; + //} + + return signature; + } + catch (Exception) + { + throw; + } + } +#endif /// /// Verifies a HMAC for a message. /// @@ -755,7 +1095,58 @@ private bool SymmetricVerify( return true; } +#if GCMMODE + /// + /// Verifies a HMAC for a message. + /// + private static bool SymmetricVerifyWithAESGCM( + ChannelToken token, + byte[] signature, + ArraySegment dataToVerify, + bool useClientKeys) + { + try + { + var key = (useClientKeys) ? token.ClientEncryptingKey : token.ServerSigningKey; + var nonce = (useClientKeys) ? token.ClientInitializationVector : token.ServerInitializationVector; + var macSize = (useClientKeys) ? token.ClientSigningKey.Length : token.ServerSigningKey.Length; + + using (var cipherStream = new MemoryStream(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count + signature.Length)) + { + using (var cipherReader = new BinaryReader(cipherStream)) + { + var header = cipherReader.ReadBytes(TcpMessageLimits.SymmetricHeaderSize); + + var cipher = new GcmBlockCipher(new AesEngine()); + var parameters = new AeadParameters(new KeyParameter(key), macSize, nonce, header); + cipher.Init(false, parameters); + var cipherText = cipherReader.ReadBytes(dataToVerify.Count - header.Length + signature.Length); + var plainTextLength = cipher.GetOutputSize(cipherText.Length); + var plainText = new byte[plainTextLength]; + + try + { + var length = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0); + cipher.DoFinal(plainText, length); + } + catch (InvalidCipherTextException e) + { + // validation failed. + return false; + } + + Buffer.BlockCopy(plainText, 0, dataToVerify.Array, dataToVerify.Offset + header.Length, plainText.Length); + return true; + } + } + } + catch (Exception e) + { + throw; + } + } +#endif /// /// Encrypts a message using a symmetric algorithm. /// @@ -818,6 +1209,264 @@ private static void SymmetricDecrypt( decryptor.TransformBlock(blockToDecrypt, start, count, blockToDecrypt, start); } } + + private static void ApplyChaCha20Poly1305Mask(ChannelToken token, uint lastSequenceNumber, byte[] iv) + { + iv[0] ^= (byte)((token.TokenId & 0x000000FF)); + iv[1] ^= (byte)((token.TokenId & 0x0000FF00) >> 8); + iv[2] ^= (byte)((token.TokenId & 0x00FF0000) >> 16); + iv[3] ^= (byte)((token.TokenId & 0xFF000000) >> 24); + iv[4] ^= (byte)((lastSequenceNumber & 0x000000FF)); + iv[5] ^= (byte)((lastSequenceNumber & 0x0000FF00) >> 8); + iv[6] ^= (byte)((lastSequenceNumber & 0x00FF0000) >> 16); + iv[7] ^= (byte)((lastSequenceNumber & 0xFF000000) >> 24); + } + +#if CURVE25519 + /// + /// Encrypts a message using a symmetric algorithm. + /// + private static void SymmetricEncryptWithChaCha20Poly1305( + ChannelToken token, + uint lastSequenceNumber, + ArraySegment dataToEncrypt, + bool useClientKeys) + { + var signingKey = (useClientKeys) ? token.ClientSigningKey : token.ServerSigningKey; + + if (signingKey == null) + { + throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Token missing symmetric key object."); + } + + var encryptingKey = (useClientKeys) ? token.ClientEncryptingKey : token.ServerEncryptingKey; + + if (encryptingKey == null) + { + throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Token missing symmetric key object."); + } + + var iv = (useClientKeys) ? token.ClientInitializationVector : token.ServerInitializationVector; + + if (iv == null) + { + throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Token missing symmetric key object."); + } + + // Utils.Trace($"EncryptKey={Utils.ToHexString(encryptingKey)}"); + // Utils.Trace($"EncryptIV1={Utils.ToHexString(iv)}"); + ApplyChaCha20Poly1305Mask(token, lastSequenceNumber, iv); + // Utils.Trace($"EncryptIV2={Utils.ToHexString(iv)}"); + + int signatureLength = 16; + + var plaintext = dataToEncrypt.Array; + int headerSize = dataToEncrypt.Offset; + int plainTextLength = dataToEncrypt.Offset + dataToEncrypt.Count - signatureLength; + + // Utils.Trace($"OUT={headerSize}|{plainTextLength}|{signatureLength}|[{plainTextLength + signatureLength}]"); + + AeadParameters parameters = new AeadParameters( + new KeyParameter(encryptingKey), + signatureLength * 8, + iv, + null); + + ChaCha20Poly1305 encryptor = new ChaCha20Poly1305(); + encryptor.Init(true, parameters); + encryptor.ProcessAadBytes(plaintext, 0, headerSize); + + byte[] ciphertext = new byte[encryptor.GetOutputSize(plainTextLength - headerSize) + headerSize]; + Buffer.BlockCopy(plaintext, 0, ciphertext, 0, headerSize); + int length = encryptor.ProcessBytes(plaintext, headerSize, plainTextLength - headerSize, ciphertext, headerSize); + length += encryptor.DoFinal(ciphertext, length + headerSize); + + if (ciphertext.Length - headerSize != length) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + $"Cipher text not the expected size. [{ciphertext.Length - headerSize} != {length}]"); + } + + Buffer.BlockCopy(ciphertext, 0, plaintext, 0, plainTextLength + signatureLength); + + // byte[] mac = new byte[16]; + // Buffer.BlockCopy(plaintext, plainTextLength, mac, 0, signatureLength); + // Utils.Trace($"EncryptMAC1={Utils.ToHexString(encryptor.GetMac())}"); + // Utils.Trace($"EncryptMAC2={Utils.ToHexString(mac)}"); + } + + /// + /// Encrypts a message using a symmetric algorithm. + /// + private static void SymmetricSignWithPoly1305( + ChannelToken token, + uint lastSequenceNumber, + ArraySegment dataToEncrypt, + bool useClientKeys) + { + var signingKey = (useClientKeys) ? token.ClientSigningKey : token.ServerSigningKey; + + if (signingKey == null) + { + throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Token missing symmetric key object."); + } + + ApplyChaCha20Poly1305Mask(token, lastSequenceNumber, signingKey); + + using (var hash = SHA256.Create()) + { + signingKey = hash.ComputeHash(signingKey); + } + + // Utils.Trace($"SigningKey={Utils.ToHexString(signingKey)}"); + + int signatureLength = 16; + + var plaintext = dataToEncrypt.Array; + int headerSize = dataToEncrypt.Offset; + int plainTextLength = dataToEncrypt.Offset + dataToEncrypt.Count - signatureLength; + + // Utils.Trace($"OUT={headerSize}|{plainTextLength}|{signatureLength}|[{plainTextLength + signatureLength}]"); + + Poly1305 poly = new Poly1305(); + + poly.Init(new KeyParameter(signingKey, 0, signingKey.Length)); + poly.BlockUpdate(plaintext, 0, plainTextLength); + int length = poly.DoFinal(plaintext, plainTextLength); + + if (signatureLength != length) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + $"Signed data not the expected size. [{plainTextLength + signatureLength} != {length}]"); + } + } + + /// + /// Decrypts a message using a symmetric algorithm. + /// + private static void SymmetricDecryptWithChaCha20Poly1305( + ChannelToken token, + uint lastSequenceNumber, + ArraySegment dataToDecrypt, + bool useClientKeys) + { + var encryptingKey = (useClientKeys) ? token.ClientEncryptingKey : token.ServerEncryptingKey; + + if (encryptingKey == null) + { + throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Token missing symmetric key object."); + } + + var iv = (useClientKeys) ? token.ClientInitializationVector : token.ServerInitializationVector; + + if (iv == null) + { + throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Token missing symmetric key object."); + } + + // Utils.Trace($"DecryptKey={Utils.ToHexString(encryptingKey)}"); + // Utils.Trace($"DecryptIV1={Utils.ToHexString(iv)}"); + ApplyChaCha20Poly1305Mask(token, lastSequenceNumber, iv); + // Utils.Trace($"DecryptIV2={Utils.ToHexString(iv)}"); + + int signatureLength = 16; + + var ciphertext = dataToDecrypt.Array; + int headerSize = dataToDecrypt.Offset; + int cipherTextLength = dataToDecrypt.Offset + dataToDecrypt.Count - signatureLength; + + // Utils.Trace($"OUT={headerSize}|{cipherTextLength}|{signatureLength}|[{cipherTextLength + signatureLength}]"); + + byte[] mac = new byte[16]; + Buffer.BlockCopy(ciphertext, cipherTextLength, mac, 0, signatureLength); + // Utils.Trace($"DecryptMAC={Utils.ToHexString(mac)}"); + + AeadParameters parameters = new AeadParameters( + new KeyParameter(encryptingKey), + signatureLength * 8, + iv, + null); + + ChaCha20Poly1305 decryptor = new ChaCha20Poly1305(); + decryptor.Init(false, parameters); + decryptor.ProcessAadBytes(ciphertext, 0, headerSize); + + var plaintext = new byte[decryptor.GetOutputSize(cipherTextLength + signatureLength - headerSize) + headerSize]; + Buffer.BlockCopy(ciphertext, headerSize, plaintext, 0, headerSize); + + int length = decryptor.ProcessBytes(ciphertext, headerSize, cipherTextLength + signatureLength - headerSize, plaintext, headerSize); + length += decryptor.DoFinal(plaintext, length + headerSize); + + if (plaintext.Length - headerSize != length) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + $"Plain text not the expected size. [{plaintext.Length - headerSize} != {length}]"); + } + + Buffer.BlockCopy(plaintext, 0, ciphertext, 0, cipherTextLength); + } + + /// + /// Encrypts a message using a symmetric algorithm. + /// + private static void SymmetricVerifyWithPoly1305( + ChannelToken token, + uint lastSequenceNumber, + ArraySegment dataToDecrypt, + bool useClientKeys) + { + var signingKey = (useClientKeys) ? token.ClientSigningKey : token.ServerSigningKey; + + if (signingKey == null) + { + throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Token missing symmetric key object."); + } + + ApplyChaCha20Poly1305Mask(token, lastSequenceNumber, signingKey); + // Utils.Trace($"SigningKey={Utils.ToHexString(signingKey)}"); + + using (var hash = SHA256.Create()) + { + signingKey = hash.ComputeHash(signingKey); + } + + int signatureLength = 16; + + var plaintext = dataToDecrypt.Array; + int headerSize = dataToDecrypt.Offset; + int plainTextLength = dataToDecrypt.Offset + dataToDecrypt.Count - signatureLength; + + // Utils.Trace($"OUT={headerSize}|{plainTextLength}|{signatureLength}|[{plainTextLength + signatureLength}]"); + + Poly1305 poly = new Poly1305(); + + poly.Init(new KeyParameter(signingKey, 0, signingKey.Length)); + poly.BlockUpdate(plaintext, 0, plainTextLength); + + byte[] mac = new byte[poly.GetMacSize()]; + int length = poly.DoFinal(mac, 0); + + if (signatureLength != length) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + $"Signed data not the expected size. [{plainTextLength + signatureLength} != {length}]"); + } + + for (int ii = 0; ii < mac.Length; ii++) + { + if (mac[ii] != plaintext[plainTextLength + ii]) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + $"Invaid MAC on data."); + } + } + } +#endif #endregion #region Private Fields diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs index f5c1c0ae5..b06ddb345 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs @@ -24,7 +24,6 @@ namespace Opc.Ua.Bindings public partial class UaSCUaBinaryChannel : IMessageSink, IDisposable { #region Constructors - /// /// Attaches the object to an existing socket. /// @@ -35,10 +34,10 @@ public UaSCUaBinaryChannel( X509Certificate2 serverCertificate, EndpointDescriptionCollection endpoints, MessageSecurityMode securityMode, - string securityPolicyUri) - : - this(contextId, bufferManager, quotas, serverCertificate, null, endpoints, securityMode, securityPolicyUri) - { } + string securityPolicyUri) : + this(contextId, bufferManager, quotas, null, serverCertificate, endpoints, securityMode, securityPolicyUri) + { + } /// /// Attaches the object to an existing socket. @@ -47,12 +46,28 @@ public UaSCUaBinaryChannel( string contextId, BufferManager bufferManager, ChannelQuotas quotas, + CertificateTypesProvider serverCertificateTypesProvider, + EndpointDescriptionCollection endpoints, + MessageSecurityMode securityMode, + string securityPolicyUri) : + this(contextId, bufferManager, quotas, serverCertificateTypesProvider, null, endpoints, securityMode, securityPolicyUri) + { + } + + /// + /// Attaches the object to an existing socket. + /// + private UaSCUaBinaryChannel( + string contextId, + BufferManager bufferManager, + ChannelQuotas quotas, + CertificateTypesProvider serverCertificateTypesProvider, X509Certificate2 serverCertificate, - X509Certificate2Collection serverCertificateChain, EndpointDescriptionCollection endpoints, MessageSecurityMode securityMode, string securityPolicyUri) { + if (bufferManager == null) throw new ArgumentNullException(nameof(bufferManager)); if (quotas == null) throw new ArgumentNullException(nameof(quotas)); @@ -70,8 +85,12 @@ public UaSCUaBinaryChannel( securityPolicyUri = SecurityPolicies.None; } - if (securityMode != MessageSecurityMode.None) + X509Certificate2Collection serverCertificateChain = null; + if (serverCertificateTypesProvider != null && + securityMode != MessageSecurityMode.None) { + serverCertificate = serverCertificateTypesProvider.GetInstanceCertificate(securityPolicyUri); + if (serverCertificate == null) throw new ArgumentNullException(nameof(serverCertificate)); if (serverCertificate.RawData.Length > TcpMessageLimits.MaxCertificateSize) @@ -80,6 +99,8 @@ public UaSCUaBinaryChannel( Utils.Format("The DER encoded certificate may not be more than {0} bytes.", TcpMessageLimits.MaxCertificateSize), nameof(serverCertificate)); } + + serverCertificateChain = serverCertificateTypesProvider.LoadCertificateChainAsync(serverCertificate).GetAwaiter().GetResult(); } if (new UTF8Encoding().GetByteCount(securityPolicyUri) > TcpMessageLimits.MaxSecurityPolicyUriSize) @@ -91,6 +112,7 @@ public UaSCUaBinaryChannel( m_bufferManager = bufferManager; m_quotas = quotas; + m_serverCertificateTypesProvider = serverCertificateTypesProvider; m_serverCertificate = serverCertificate; m_serverCertificateChain = serverCertificateChain; m_endpoints = endpoints; @@ -150,7 +172,19 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - // nothing to do. +#if ECC_SUPPORT + if (m_localNonce != null) + { + m_localNonce.Dispose(); + m_localNonce = null; + } + + if (m_remoteNonce != null) + { + m_remoteNonce.Dispose(); + m_remoteNonce = null; + } +#endif } } #endregion @@ -204,7 +238,8 @@ protected void ChannelStateChanged(TcpChannelState state, ServiceResult reason) { if (m_StateChanged != null) { - Task.Run(() => { + Task.Run(() => + { m_StateChanged?.Invoke(this, state, reason); }); } @@ -215,6 +250,8 @@ protected void ChannelStateChanged(TcpChannelState state, ServiceResult reason) /// protected uint GetNewSequenceNumber() { + // TODO transaction safe + m_localSequenceNumber = (uint)m_sequenceNumber; return Utils.IncrementIdentifier(ref m_sequenceNumber); } @@ -844,6 +881,7 @@ protected static int CalculateChunkCount(int messageSize, int bufferSize) private uint m_channelId; private string m_globalChannelId; private long m_sequenceNumber; + private uint m_localSequenceNumber; private uint m_remoteSequenceNumber; private bool m_sequenceRollover; private uint m_partialRequestId; diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs index 7c0beb364..de5ce3864 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs @@ -26,22 +26,6 @@ namespace Opc.Ua.Bindings public class UaSCUaBinaryClientChannel : UaSCUaBinaryChannel { #region Constructors - /// - /// Creates a channel for for a client. - /// - public UaSCUaBinaryClientChannel( - string contextId, - BufferManager bufferManager, - IMessageSocketFactory socketFactory, - ChannelQuotas quotas, - X509Certificate2 clientCertificate, - X509Certificate2 serverCertificate, - EndpointDescription endpoint) - : - this(contextId, bufferManager, socketFactory, quotas, clientCertificate, null, serverCertificate, endpoint) - { - } - /// /// Creates a channel for for a client. /// @@ -559,7 +543,7 @@ private void SendOpenSecureChannelRequest(bool renew) { // create a new token. ChannelToken token = CreateToken(); - token.ClientNonce = CreateNonce(); + token.ClientNonce = CreateNonce(ClientCertificate); // construct the request. OpenSecureChannelRequest request = new OpenSecureChannelRequest(); @@ -681,6 +665,11 @@ private bool ProcessOpenSecureChannelResponse(uint messageType, ArraySegment void CertificateUpdate( ICertificateValidator validator, - X509Certificate2 serverCertificate, - X509Certificate2Collection serverCertificateChain); + CertificateTypesProvider serverCertificateTypes); /// /// Raised when a new connection is waiting for a client. diff --git a/Stack/Opc.Ua.Core/Stack/Transport/TransportListenerSettings.cs b/Stack/Opc.Ua.Core/Stack/Transport/TransportListenerSettings.cs index 40d90f43f..1bba3acdd 100644 --- a/Stack/Opc.Ua.Core/Stack/Transport/TransportListenerSettings.cs +++ b/Stack/Opc.Ua.Core/Stack/Transport/TransportListenerSettings.cs @@ -39,25 +39,14 @@ public EndpointConfiguration Configuration } /// - /// Gets or sets the server certificate. + /// Gets or sets the server certificate type provider. /// - public X509Certificate2 ServerCertificate + public CertificateTypesProvider ServerCertificateTypesProvider { - get { return m_serverCertificate; } - set { m_serverCertificate = value; } + get { return m_serverCertificateTypes; } + set { m_serverCertificateTypes = value; } } - /// - /// Gets or sets the server certificate chain. - /// - /// - /// The server certificate chain. - /// - public X509Certificate2Collection ServerCertificateChain - { - get { return m_serverCertificateChain; } - set { m_serverCertificateChain = value; } - } /// /// Gets or Sets the certificate validator. @@ -122,8 +111,7 @@ public bool ReverseConnectListener #region Private Fields private EndpointDescriptionCollection m_descriptions; private EndpointConfiguration m_configuration; - private X509Certificate2 m_serverCertificate; - private X509Certificate2Collection m_serverCertificateChain; + private CertificateTypesProvider m_serverCertificateTypes; private ICertificateValidator m_certificateValidator; private NamespaceTable m_namespaceUris; private IEncodeableFactory m_channelFactory; diff --git a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs index 91b406d75..c5d7c4d69 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs @@ -2823,7 +2823,7 @@ public static byte[] PSHA256(byte[] secret, string label, byte[] data, int offse /// /// Generates a Pseudo random sequence of bits using the HMAC algorithm. /// - private static byte[] PSHA(HMAC hmac, string label, byte[] data, int offset, int length) + public static byte[] PSHA(HMAC hmac, string label, byte[] data, int offset, int length) { if (hmac == null) throw new ArgumentNullException(nameof(hmac)); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); @@ -2899,6 +2899,32 @@ private static byte[] PSHA(HMAC hmac, string label, byte[] data, int offset, int return output; } + + /// + /// Creates an HMAC. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", + "CA5350:Do Not Use Weak Cryptographic Algorithms", Justification = "")] + public static HMAC CreateHMAC(HashAlgorithmName algorithmName, byte[] secret) + { + if (algorithmName == HashAlgorithmName.SHA256) + { + return new HMACSHA256(secret); + } + + if (algorithmName == HashAlgorithmName.SHA384) + { + return new HMACSHA384(secret); + } + + if (algorithmName == HashAlgorithmName.SHA1) + { + return new HMACSHA1(secret); + } + + throw new NotImplementedException(); + } + /// /// Checks if the target is in the list. Comparisons ignore case. /// @@ -2920,6 +2946,35 @@ public static bool FindStringIgnoreCase(IList strings, string target) return false; } + /// + /// Returns if the certificate type is supported on the platform OS. + /// + /// The certificate type to check. + public static bool IsSupportedCertificateType(NodeId certificateType) + { + if (certificateType.Identifier is uint identifier) + { + switch (identifier) + { +#if ECC_SUPPORT + case ObjectTypes.EccApplicationCertificateType: + case ObjectTypes.EccBrainpoolP256r1ApplicationCertificateType: + case ObjectTypes.EccBrainpoolP384r1ApplicationCertificateType: + case ObjectTypes.EccNistP256ApplicationCertificateType: + case ObjectTypes.EccNistP384ApplicationCertificateType: + //case ObjectTypes.EccCurve25519ApplicationCertificateType: + //case ObjectTypes.EccCurve448ApplicationCertificateType: +#endif + case ObjectTypes.ApplicationCertificateType: + case ObjectTypes.RsaMinApplicationCertificateType: + case ObjectTypes.RsaSha256ApplicationCertificateType: + case ObjectTypes.HttpsCertificateType: + return true; + } + } + return false; + } + /// /// Lazy helper to allow runtime check for Mono. /// diff --git a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs index 417c9d3a8..af06536af 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs @@ -30,6 +30,8 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; +using System.Runtime.InteropServices; using System.Threading.Tasks; using NUnit.Framework; using Opc.Ua.Configuration; @@ -73,6 +75,12 @@ public async Task LoadClientConfiguration(string pkiRoot = null, string clientNa pkiRoot = pkiRoot ?? Path.Combine("%LocalApplicationData%", "OPC", "pki"); + string eccCertTypes = "RSA,nistP256,nistP384"; + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + eccCertTypes += ",brainpoolP256r1,brainpoolP384r1"; + } + // build the application configuration. Config = await application .Build( @@ -88,6 +96,8 @@ public async Task LoadClientConfiguration(string pkiRoot = null, string clientNa .AddSecurityConfiguration( "CN=" + clientName + ", O=OPC Foundation, DC=localhost", pkiRoot) + + .SetApplicationCertificateTypes(eccCertTypes) .SetAutoAcceptUntrustedCertificates(true) .SetRejectSHA1SignedCertificates(false) .SetMinimumCertificateKeySize(1024) @@ -284,7 +294,8 @@ public static EndpointDescription SelectEndpoint( if (endpoint.EndpointUrl.StartsWith(url.Scheme)) { // skip unsupported security policies - if (SecurityPolicies.GetDisplayName(endpoint.SecurityPolicyUri) == null) + if (!configuration.SecurityConfiguration.SupportedSecurityPolicies. + Contains(endpoint.SecurityPolicyUri)) { continue; } diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs index 35e036d45..44a776d50 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs @@ -856,7 +856,36 @@ private void ValidateDataTypeDefinition(INode node) StructureDefinition structureDefinition = dataTypeDefinition.Body as StructureDefinition; Assert.AreEqual(ObjectIds.ProgramDiagnosticDataType_Encoding_DefaultBinary, structureDefinition.DefaultEncodingId); } - +#if mist + [Test] + public async Task ReadWriteDataTypeDefinition() + { + // Test Read a DataType Node + var typeId = DataTypeIds.PubSubGroupDataType; + var node = m_session.ReadNode(typeId); + Assert.NotNull(node); + var dataTypeNode = (DataTypeNode)node; + Assert.NotNull(dataTypeNode); + var dataTypeDefinition = dataTypeNode.DataTypeDefinition; + Assert.NotNull(dataTypeDefinition); + Assert.True(dataTypeDefinition is ExtensionObject); + Assert.NotNull(dataTypeDefinition.Body); + Assert.True(dataTypeDefinition.Body is StructureDefinition); + StructureDefinition structureDefinition = dataTypeDefinition.Body as StructureDefinition; + Assert.AreEqual(ObjectIds.PubSubGroupDataType_Encoding_DefaultBinary, structureDefinition.DefaultEncodingId); + structureDefinition.DefaultEncodingId = ObjectIds.PubSubGroupDataType_Encoding_DefaultJson; + + var writeValueCollection = new WriteValueCollection(); + writeValueCollection.Add(new WriteValue() { + AttributeId = Attributes.DataTypeDefinition, + NodeId = typeId, + Value = new DataValue(new Variant(dataTypeDefinition)) + }); + var response = await m_session.WriteAsync(null, writeValueCollection, CancellationToken.None); + Assert.AreEqual(StatusCodes.BadNotWritable, response.Results[0].Code); + Assert.NotNull(response); + } +#endif [Theory, Order(400)] public async Task BrowseFullAddressSpace(string securityPolicy, bool operationLimits = false) { diff --git a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateStoreTypeTest.cs b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateStoreTypeTest.cs index d0c22279f..adfb22e59 100644 --- a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateStoreTypeTest.cs +++ b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateStoreTypeTest.cs @@ -147,6 +147,10 @@ public Task IsRevoked(X509Certificate2 issuer, X509Certificate2 cert public Task LoadPrivateKey(string thumbprint, string subjectName, string password) => m_innerStore.LoadPrivateKey(thumbprint, subjectName, password); + /// + public Task LoadPrivateKey(string thumbprint, string subjectName, NodeId certificateType, string password) + => m_innerStore.LoadPrivateKey(thumbprint, subjectName, certificateType, password); + public static int InstancesCreated => s_instancesCreated; #region data members diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs index eb1995812..73e051afd 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs @@ -183,7 +183,7 @@ private static void CertificateValidator_CertificateValidation(CertificateValida private static async Task Load(ApplicationInstance application, int basePort) { -#if !USE_FILE_CONFIG +#if USE_FILE_CONFIG // load the application configuration. ApplicationConfiguration config = await application.LoadApplicationConfiguration(true).ConfigureAwait(false); #else diff --git a/Tests/Opc.Ua.Gds.Tests/PushTest.cs b/Tests/Opc.Ua.Gds.Tests/PushTest.cs index 3858c9b25..dff644996 100644 --- a/Tests/Opc.Ua.Gds.Tests/PushTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/PushTest.cs @@ -381,13 +381,6 @@ public void UpdateCertificateSelfSignedNoPrivateKey() [Test, Order(510)] public void UpdateCertificateCASigned() { -#if NETCOREAPP3_1_OR_GREATER - // this test fails on macOS, ignore - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - Assert.Ignore("Update CA signed certificate fails on mac OS."); - } -#endif ConnectPushClient(true); ConnectGDSClient(true); TestContext.Out.WriteLine("Create Signing Request"); diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs index e7132e38b..200eeb00a 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs @@ -379,7 +379,9 @@ private void CheckPEMWriter(X509Certificate2 certificate, string password = null if (certificate.HasPrivateKey) { PEMWriter.ExportPrivateKeyAsPEM(certificate, password); +#if NETCOREAPP3_1 || NET5_0_OR_GREATER PEMWriter.ExportECDsaPrivateKeyAsPEM(certificate); +#endif } } #endregion diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForRSA.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForRSA.cs index 93f89a4ef..f1c3153fa 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForRSA.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForRSA.cs @@ -247,14 +247,16 @@ public void CreateRSADefaultWithSerialTest() { // default cert Assert.Throws( - () => { + () => + { CertificateBuilder.Create(Subject) .SetSerialNumberLength(0) .CreateForRSA(); } ); Assert.Throws( - () => { + () => + { CertificateBuilder.Create(Subject) .SetSerialNumberLength(X509Defaults.SerialNumberLengthMax + 1) .CreateForRSA(); @@ -278,14 +280,16 @@ public void CreateRSAManualSerialTest() { // default cert Assert.Throws( - () => { + () => + { CertificateBuilder.Create(Subject) .SetSerialNumber(Array.Empty()) .CreateForRSA(); } ); Assert.Throws( - () => { + () => + { CertificateBuilder.Create(Subject) .SetSerialNumber(new byte[X509Defaults.SerialNumberLengthMax + 1]) .CreateForRSA(); @@ -432,6 +436,9 @@ KeyHashPair keyHashPair { Assert.NotNull(cert); WriteCertificate(cert, "Default signed RSA cert with Public Key"); + Assert.AreEqual(issuer.SubjectName.Name, cert.IssuerName.Name); + Assert.AreEqual(issuer.SubjectName.RawData, cert.IssuerName.RawData); + CheckPEMWriter(cert); } } @@ -454,7 +461,8 @@ KeyHashPair keyHashPair } // ensure invalid path throws argument exception - Assert.Throws(() => { + Assert.Throws(() => + { using (RSA rsaPrivateKey = signingCert.GetRSAPrivateKey()) { var generator = X509SignatureGenerator.CreateForRSA(rsaPrivateKey, RSASignaturePadding.Pkcs1); @@ -501,5 +509,4 @@ private void CheckPEMWriter(X509Certificate2 certificate, string password = null #region Private Fields #endregion } - } diff --git a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs index 92fb1afee..fa410e8e5 100644 --- a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs +++ b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs @@ -29,6 +29,7 @@ using System; using System.IO; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Opc.Ua.Configuration; @@ -88,7 +89,9 @@ public async Task LoadConfiguration(string pkiRoot = null) .AddPolicy(MessageSecurityMode.SignAndEncrypt, SecurityPolicies.Basic128Rsa15) .AddPolicy(MessageSecurityMode.SignAndEncrypt, SecurityPolicies.Basic256) .AddSignPolicies() - .AddSignAndEncryptPolicies(); + .AddSignAndEncryptPolicies() + .AddEccSignPolicies() + .AddEccSignAndEncryptPolicies(); } if (OperationLimits) @@ -116,9 +119,16 @@ public async Task LoadConfiguration(string pkiRoot = null) }); } + string eccCertTypes = "RSA,nistP256,nistP384"; + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + eccCertTypes += ",brainpoolP256r1,brainpoolP384r1"; + } + Config = await serverConfig.AddSecurityConfiguration( "CN=" + typeof(T).Name + ", C=US, S=Arizona, O=OPC Foundation, DC=localhost", pkiRoot) + .SetApplicationCertificateTypes(eccCertTypes) .SetAutoAcceptUntrustedCertificates(AutoAccept) .Create().ConfigureAwait(false); } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 66c077e40..2e3f37346 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -100,6 +100,12 @@ stages: parameters: framework: netcoreapp3.1 configuration: Debug + - template: .azurepipelines/test.yml + parameters: + configuration: Debug + framework: net6.0 + agents: '@{ windows = "windows-2022"; linux = "ubuntu-18.04"; mac = "macOS-10.15" }' + jobnamesuffix: net60debug - stage: coverage dependsOn: [testdebug,testrelease] displayName: 'Code Coverage' diff --git a/targets.props b/targets.props index 7ab73c1d7..600bf568b 100644 --- a/targets.props +++ b/targets.props @@ -1,10 +1,8 @@ - From fd3fcd19fdbfe29accaae5c47e2dab7f33f38098 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Fri, 18 Nov 2022 22:42:22 +0100 Subject: [PATCH 02/80] clean a few warning --- azure-pipelines.yml | 8 +++++++- common.props | 2 +- targets.props | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2e3f37346..0f2e68e9f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -64,6 +64,12 @@ stages: framework: net48 agents: '@{ windows = "windows-2019" }' jobnamesuffix: net48 + - template: .azurepipelines/test.yml + parameters: + configuration: Release + framework: net48 + agents: '@{ windows = "windows-2022" }' + jobnamesuffix: net48 - template: .azurepipelines/test.yml parameters: configuration: Release @@ -104,7 +110,7 @@ stages: parameters: configuration: Debug framework: net6.0 - agents: '@{ windows = "windows-2022"; linux = "ubuntu-18.04"; mac = "macOS-10.15" }' + agents: '@{ windows = "windows-2022"; linux = "ubuntu-22.04"; mac = "macOS-11" }' jobnamesuffix: net60debug - stage: coverage dependsOn: [testdebug,testrelease] diff --git a/common.props b/common.props index c8b0c5425..792d7165d 100644 --- a/common.props +++ b/common.props @@ -2,7 +2,7 @@ OPC UA .NET Standard Library https://github.com/OPCFoundation/UA-.NETStandard - 1.04.368 + 1.04.372 preview-$([System.DateTime]::Now.ToString("yyyyMMdd")) Copyright © 2004-2023 OPC Foundation, Inc OPC Foundation diff --git a/targets.props b/targets.props index 600bf568b..7ab73c1d7 100644 --- a/targets.props +++ b/targets.props @@ -1,8 +1,10 @@ + From dee608895c422d060600f3b923c7789722d7c25b Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Mon, 13 Feb 2023 10:52:22 +0200 Subject: [PATCH 03/80] Instantiate HMAC instance depending on provided algorithm. --- .../Opc.Ua.Core/Security/Certificates/Nonce.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs index ea5972653..dc05df80c 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs @@ -63,6 +63,19 @@ private Nonce() #endif } + private HMAC returnHMACInstance(byte[] secret, HashAlgorithmName algorithm) + { + switch (algorithm.Name) + { + case "SHA256": + return new HMACSHA256(secret); + case "SHA384": + return new HMACSHA384(secret); + default: + return new HMACSHA256(secret); + } + } + #region IDisposable Members /// /// Frees any unmanaged resources. @@ -140,8 +153,8 @@ public byte[] DeriveKey(Nonce remoteNonce, byte[] salt, HashAlgorithmName algori byte[] output = new byte[length]; - HMACSHA256 hmac = new HMACSHA256(secret); - + HMAC hmac = returnHMACInstance(secret, algorithm); + byte counter = 1; byte[] info = new byte[hmac.HashSize / 8 + salt.Length + 1]; From b17271ce0901ac82f44ce377af221b86b423b755 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Mon, 13 Mar 2023 08:03:19 -0700 Subject: [PATCH 04/80] fix build --- .../Stack/Https/HttpsTransportListener.cs | 23 +++++++----------- .../Certificates/CertificateIdentifier.cs | 8 +++++++ .../Stack/Tcp/TcpTransportListener.cs | 24 ++++++++----------- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs index ec7421135..8bde70a00 100644 --- a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs +++ b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs @@ -442,23 +442,18 @@ public void CertificateUpdate( m_serverCertProvider = certificateTypeProvider; foreach (var description in m_descriptions) { - // check if complete chain should be sent. - if (m_serverCertificateChain != null && - m_serverCertificateChain.Count > 1) + if (description.ServerCertificate != null) { - var byteServerCertificateChain = new List(); - - for (int i = 0; i < m_serverCertificateChain.Count; i++) + X509Certificate2 serverCertificate = m_serverCertProvider.GetInstanceCertificate(description.SecurityPolicyUri); + if (m_serverCertProvider.SendCertificateChain) { - byteServerCertificateChain.AddRange(m_serverCertificateChain[i].RawData); + byte[] serverCertificateChainRaw = m_serverCertProvider.LoadCertificateChainRawAsync(serverCertificate).Result; + description.ServerCertificate = serverCertificateChainRaw; + } + else + { + description.ServerCertificate = serverCertificate.RawData; } - - description.ServerCertificate = byteServerCertificateChain.ToArray(); - } - else if (description.ServerCertificate != null) - { - description.ServerCertificate = - m_serverCertProvider.GetInstanceCertificate(description.SecurityPolicyUri).RawData; } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index 500a6098a..95da04169 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -985,6 +985,14 @@ public CertificateTypesProvider(ApplicationConfiguration config) m_certificateValidator = config.CertificateValidator; } + /// + /// Gets or sets a value indicating whether the application should send the complete certificate chain. + /// + /// + /// If set to true the complete certificate chain will be sent for CA signed certificates. + /// + public bool SendCertificateChain => m_securityConfiguration.SendCertificateChain; + /// /// Return the instance certificate for a security policy. /// diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs index a898794b8..551d04652 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs @@ -438,23 +438,19 @@ CertificateTypesProvider certificateTypesProvider m_serverCertificateTypesProvider = certificateTypesProvider; foreach (var description in m_descriptions) { - // check if complete chain should be sent. - if (m_serverCertificateChain != null && - m_serverCertificateChain.Count > 1) + // TODO: why only if SERVERCERT != null + if (description.ServerCertificate != null) { - var byteServerCertificateChain = new List(); - - for (int i = 0; i < m_serverCertificateChain.Count; i++) + X509Certificate2 serverCertificate = certificateTypesProvider.GetInstanceCertificate(description.SecurityPolicyUri); + if (certificateTypesProvider.SendCertificateChain) { - byteServerCertificateChain.AddRange(m_serverCertificateChain[i].RawData); + byte[] serverCertificateChainRaw = certificateTypesProvider.LoadCertificateChainRawAsync(serverCertificate).Result; + description.ServerCertificate = serverCertificateChainRaw; + } + else + { + description.ServerCertificate = serverCertificate.RawData; } - - description.ServerCertificate = byteServerCertificateChain.ToArray(); - } - else if (description.ServerCertificate != null) - { - var serverCertificate = certificateTypesProvider.GetInstanceCertificate(description.SecurityPolicyUri); - description.ServerCertificate = serverCertificate.RawData; } } } From d63925f075cd9fd8a6324073d8e7bd1c102faaea Mon Sep 17 00:00:00 2001 From: mirceasu Date: Tue, 27 Jun 2023 14:56:02 +0300 Subject: [PATCH 05/80] Added ApplicationCertificates XML tag (bcw compat) --- .../Quickstarts.ReferenceServer.Config.xml | 58 ++++++++++++++++--- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml index 5dea23621..1a8e78ba8 100644 --- a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml +++ b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml @@ -11,15 +11,57 @@ - - - Directory - %LocalApplicationData%/OPC Foundation/pki/own - CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost - - - Rsa,NistP256,NistP384,BrainpoolP256r1,BrainpoolP384r1 + + + Rsa + CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + + + + NistP256 + CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + + + + NistP384 + CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + + + + BrainpoolP256r1 + CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + + + + BrainpoolP384r1 + CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + + + + Rsa4096 + CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + + + From 5b1fe70c3aca98c61882e9beeb099107b99073a7 Mon Sep 17 00:00:00 2001 From: mirceasu Date: Thu, 3 Aug 2023 15:32:43 +0300 Subject: [PATCH 06/80] Use ListOfCertificateIdentifier for configuration --- .../Quickstarts.ReferenceServer.Config.xml | 83 +++++------- .../ApplicationConfigurationBuilder.cs | 14 +- .../ApplicationInstance.cs | 6 +- .../IApplicationConfigurationBuilder.cs | 11 +- .../CertificateWrapper.cs | 3 +- .../Schema/ApplicationConfiguration.cs | 97 ++++++++++---- .../Certificates/CertificateIdentifier.cs | 74 ++++++++++- .../Certificates/CertificateValidator.cs | 91 ++++++++++--- .../Certificates/SecurityConfiguration.cs | 124 +----------------- .../Security/Certificates/X509Utils.cs | 25 ++++ .../Configuration/ApplicationConfiguration.cs | 2 +- 11 files changed, 305 insertions(+), 225 deletions(-) diff --git a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml index 1a8e78ba8..cf1d77977 100644 --- a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml +++ b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml @@ -4,6 +4,7 @@ xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd" > + Quickstart Reference Server urn:localhost:UA:Quickstarts:ReferenceServer uri:opcfoundation.org:Quickstarts:ReferenceServer @@ -12,56 +13,44 @@ - - - Rsa + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost - - Directory - %LocalApplicationData%/OPC Foundation/pki/own - - - - NistP256 + + i=12560 + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost - - Directory - %LocalApplicationData%/OPC Foundation/pki/own - - - - NistP384 + + i=23538 + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost - - Directory - %LocalApplicationData%/OPC Foundation/pki/own - - - - BrainpoolP256r1 + i=23539 + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost - - Directory - %LocalApplicationData%/OPC Foundation/pki/own - - - - BrainpoolP384r1 + i=23540 + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost - - Directory - %LocalApplicationData%/OPC Foundation/pki/own - - - - Rsa4096 - CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost - - Directory - %LocalApplicationData%/OPC Foundation/pki/own - - - + i=23541 + + @@ -143,7 +132,7 @@ --> - + SignAndEncrypt_3 http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256 @@ -319,7 +308,7 @@ 0 false - + - + Directory %LocalApplicationData%/OPC Foundation/pki/own @@ -50,7 +50,7 @@ CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost i=23541 - + diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index 9b37916fb..0dee6f0bb 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -181,18 +181,6 @@ private void Initialize( m_instanceCertificate = clientCertificate; } - // check for valid certificate. - if (m_instanceCertificate == null) - { -#pragma warning disable CS0618 // Type or member is obsolete - var cert = m_configuration.SecurityConfiguration.ApplicationCertificate; -#pragma warning restore CS0618 // Type or member is obsolete - throw ServiceResultException.Create( - StatusCodes.BadConfigurationError, - "Cannot find the application instance certificate. Store={0}, SubjectName={1}, Thumbprint={2}.", - cert.StorePath, cert.SubjectName, cert.Thumbprint); - } - // check for private key. if (!m_instanceCertificate.HasPrivateKey) { diff --git a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs index aa85b85bb..b07360142 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs @@ -104,13 +104,7 @@ public IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration( var rejectedRootType = CertificateStoreIdentifier.DetermineStoreType(rejectedRoot); ApplicationConfiguration.SecurityConfiguration = new SecurityConfiguration { // app cert store -#pragma warning disable CS0618 // Type or member is obsolete - ApplicationCertificate = new CertificateIdentifier() { -#pragma warning restore CS0618 // Type or member is obsolete - StoreType = appStoreType, - StorePath = DefaultCertificateStorePath(TrustlistType.Application, appRoot), - SubjectName = Utils.ReplaceDCLocalhost(subjectName) - }, + ApplicationCertificates = CreateDefaultApplicationCertificates(appRoot), // App trusted & issuer TrustedPeerCertificates = new CertificateTrustList() { StoreType = pkiRootType, @@ -143,70 +137,15 @@ public IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration( StoreType = rejectedRootType, StorePath = DefaultCertificateStorePath(TrustlistType.Rejected, rejectedRoot) }, - }; - SetSecureDefaults(ApplicationConfiguration.SecurityConfiguration); - - return this; - } - - /// - public IApplicationConfigurationBuilderSecurityOptionStores AddSecurityConfigurationStores( - string subjectName, - string appRoot, - string trustedRoot, - string issuerRoot, - string rejectedRoot = null - ) - { - string appStoreType = CertificateStoreIdentifier.DetermineStoreType(appRoot); - string issuerRootType = CertificateStoreIdentifier.DetermineStoreType(issuerRoot); - string trustedRootType = CertificateStoreIdentifier.DetermineStoreType(trustedRoot); - rejectedRoot = rejectedRoot ?? DefaultPKIRoot(null); - string rejectedRootType = CertificateStoreIdentifier.DetermineStoreType(rejectedRoot); - ApplicationConfiguration.SecurityConfiguration = new SecurityConfiguration { - // app cert store - ApplicationCertificate = new CertificateIdentifier() { - StoreType = appStoreType, - StorePath = DefaultCertificateStorePath(TrustlistType.Application, appRoot), - SubjectName = Utils.ReplaceDCLocalhost(subjectName) - }, - // App trusted & issuer - TrustedPeerCertificates = new CertificateTrustList() { - StoreType = trustedRootType, - StorePath = DefaultCertificateStorePath(TrustlistType.Trusted, trustedRoot) - }, - TrustedIssuerCertificates = new CertificateTrustList() { - StoreType = issuerRootType, - StorePath = DefaultCertificateStorePath(TrustlistType.Issuer, issuerRoot) - }, - // rejected store - RejectedCertificateStore = new CertificateTrustList() { - StoreType = rejectedRootType, - StorePath = DefaultCertificateStorePath(TrustlistType.Rejected, rejectedRoot) - }, - }; - SetSecureDefaults(ApplicationConfiguration.SecurityConfiguration); - - return this; - } - - /// - public IApplicationConfigurationBuilderSecurityOptionStores AddSecurityConfigurationUserStore( - string trustedRoot, - string issuerRoot - ) - { - string trustedRootType = CertificateStoreIdentifier.DetermineStoreType(trustedRoot); - string issuerRootType = CertificateStoreIdentifier.DetermineStoreType(issuerRoot); - - // User trusted & issuer - ApplicationConfiguration.SecurityConfiguration.TrustedUserCertificates = new CertificateTrustList() { - StoreType = trustedRootType, - StorePath = DefaultCertificateStorePath(TrustlistType.TrustedUser, trustedRoot) - }; - ApplicationConfiguration.SecurityConfiguration.UserIssuerCertificates = new CertificateTrustList() { - StoreType = issuerRootType, - StorePath = DefaultCertificateStorePath(TrustlistType.IssuerUser, issuerRoot) + // ensure secure default settings + AutoAcceptUntrustedCertificates = false, + AddAppCertToTrustedStore = false, + RejectSHA1SignedCertificates = true, + RejectUnknownRevocationStatus = true, + SuppressNonceValidationErrors = false, + SendCertificateChain = true, + MinimumCertificateKeySize = CertificateFactory.DefaultKeySize, + MinimumECCertificateKeySize = CertificateFactory.DefaultECCKeySize, }; return this; } @@ -390,9 +329,9 @@ public IApplicationConfigurationBuilderServerSelected AddUserTokenPolicy(UserTok } /// - public IApplicationConfigurationBuilderSecurityOptions SetListOfCertificateIdentifier(CertificateIdentifierCollection certIdList) + public IApplicationConfigurationBuilderSecurityOptions SetApplicationCertificates(CertificateIdentifierCollection certIdList) { - ApplicationConfiguration.SecurityConfiguration.ListOfCertificateIdentifier = certIdList; + ApplicationConfiguration.SecurityConfiguration.ApplicationCertificates = certIdList; return this; } @@ -860,6 +799,62 @@ public IApplicationConfigurationBuilderExtension AddExtension(XmlQualifiedNam } #endregion + #region Public Static Methods + + /// + /// Create ApplicationCertificates from a PKI root. + /// + /// The PKI root. + /// The application certificates. + + public static CertificateIdentifierCollection CreateDefaultApplicationCertificates(string pkiRoot) + { + CertificateIdentifierCollection certificateIdentifiers = new CertificateIdentifierCollection{ + new CertificateIdentifier { + StoreType = "Directory", + StorePath = pkiRoot, + SubjectName = "N=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost", + CertificateType = new NodeId ("i=12560") // RSA + }, + new CertificateIdentifier { + StoreType = "Directory", + StorePath = pkiRoot, + SubjectName = "CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost", + CertificateType = new NodeId ("i=23538") // Nistp256 + }, + new CertificateIdentifier { + StoreType = "Directory", + StorePath = pkiRoot, + SubjectName = "CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost", + CertificateType = new NodeId ("i=23539") // Nistp384 + } + }; + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + certificateIdentifiers.AddRange( + new CertificateIdentifierCollection + { + new CertificateIdentifier { + StoreType = "Directory", + StorePath = pkiRoot, + SubjectName = "CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost", + CertificateType = new NodeId ("i=23540") // BrainpoolP256r1 + }, + new CertificateIdentifier { + StoreType = "Directory", + StorePath = pkiRoot, + SubjectName = "CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost", + CertificateType = new NodeId ("i=23541") // BrainpoolP384r1 + } + }); + } + + return certificateIdentifiers; + + } + #endregion + #region Private Methods /// /// Internal enumeration of supported trust lists. @@ -1064,5 +1059,6 @@ private bool InternalAddPolicy(ServerSecurityPolicyCollection policies, MessageS #region Private Fields private bool m_typeSelected; #endregion + } } diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index 96f94f91d..ce75d740e 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -408,7 +408,7 @@ public async Task DeleteApplicationInstanceCertificate(string[] profileIds = nul { // TODO: delete only selected profiles if (m_applicationConfiguration == null) throw new ArgumentException("Missing configuration."); - foreach (var id in m_applicationConfiguration.SecurityConfiguration.ListOfCertificateIdentifier) + foreach (var id in m_applicationConfiguration.SecurityConfiguration.ApplicationCertificates) { await DeleteApplicationInstanceCertificate(m_applicationConfiguration, id).ConfigureAwait(false); } @@ -435,13 +435,13 @@ public async Task CheckApplicationInstanceCertificate( // find the existing certificates. SecurityConfiguration securityConfiguration = m_applicationConfiguration.SecurityConfiguration; - if (securityConfiguration.ListOfCertificateIdentifier.Count == 0) + if (securityConfiguration.ApplicationCertificates.Count == 0) { throw new ServiceResultException(StatusCodes.BadConfigurationError, "Need at least one Application Certificate."); } bool result = true; - foreach (var certId in securityConfiguration.ListOfCertificateIdentifier) + foreach (var certId in securityConfiguration.ApplicationCertificates) { bool nextResult = await CheckCertificateTypeAsync(certId, silent, minimumKeySize, lifeTimeInMonths).ConfigureAwait(false); result = result && nextResult; diff --git a/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs index 75bc76ea6..2f1f9680e 100644 --- a/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs @@ -460,7 +460,7 @@ public interface IApplicationConfigurationBuilderSecurityOptions : /// Rsa,nistP256,nistP384,brainpoolP256r1,brainpoolP384r1 /// /// A list of Certificate identifiers - IApplicationConfigurationBuilderSecurityOptions SetListOfCertificateIdentifier(CertificateIdentifierCollection certIdList); + IApplicationConfigurationBuilderSecurityOptions SetApplicationCertificates(CertificateIdentifierCollection certIdList); /// /// Whether an unknown application certificate should be accepted diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index c03b94805..8d32278b1 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -33,6 +33,8 @@ using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.Xml; +using Opc.Ua.Security; + namespace Opc.Ua.Server { @@ -122,15 +124,20 @@ ApplicationConfiguration configuration ServerCertificateGroup defaultApplicationGroup = new ServerCertificateGroup { NodeId = Opc.Ua.ObjectIds.ServerConfiguration_CertificateGroups_DefaultApplicationGroup, BrowseName = Opc.Ua.BrowseNames.DefaultApplicationGroup, - // TODO - CertificateTypes = new NodeId[] { ObjectTypeIds.RsaSha256ApplicationCertificateType }, - // TODO -#pragma warning disable CS0618 // Type or member is obsolete - ApplicationCertificate = configuration.SecurityConfiguration.ApplicationCertificate, -#pragma warning restore CS0618 // Type or member is obsolete + CertificateTypes = new NodeId[]{}, + ApplicationCertificates = new CertificateIdentifierCollection(), IssuerStorePath = configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath, TrustedStorePath = configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath }; + + // For each certificate in ApplicationCertificates, add the certificate type to ServerConfiguration_CertificateGroups_DefaultApplicationGroup + // under the CertificateTypes field. + foreach (var cert in configuration.SecurityConfiguration.ApplicationCertificates) + { + defaultApplicationGroup.CertificateTypes = defaultApplicationGroup.CertificateTypes.Concat(new NodeId[] { cert.CertificateType }).ToArray(); + defaultApplicationGroup.ApplicationCertificates.Add(cert); + } + m_certificateGroups.Add(defaultApplicationGroup); } #endregion @@ -403,6 +410,19 @@ private ServiceResult UpdateCertificate( ServerCertificateGroup certificateGroup = VerifyGroupAndTypeId(certificateGroupId, certificateTypeId); certificateGroup.UpdateCertificate = null; + // identify the existing certificate to be updated + // it should be of the same type and same subject name as the new certificate + CertificateIdentifier existingCertIdentifier = certificateGroup.ApplicationCertificates.FirstOrDefault(cert => + X509Utils.CompareDistinguishedName(cert.Certificate.Subject, newCert.Subject) && + cert.CertificateType == certificateTypeId); + + // if there is no such existing certificate then this is an error + if (existingCertIdentifier == null) + { + throw new ServiceResultException(StatusCodes.BadInvalidArgument, "No existing certificate found for the specified certificate type and subject name."); + } + + X509Certificate2Collection newIssuerCollection = new X509Certificate2Collection(); try @@ -429,7 +449,7 @@ private ServiceResult UpdateCertificate( // TODO: An issuer may modify the subject of an issued certificate, // but then the configuration must be updated too! // NOTE: not a strict requirement here for ASN.1 byte compare - if (!X509Utils.CompareDistinguishedName(certificateGroup.ApplicationCertificate.Certificate.Subject, newCert.Subject)) + if (!X509Utils.CompareDistinguishedName(existingCertIdentifier.Certificate.Subject, newCert.Subject)) { throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed, "Subject Name of new certificate doesn't match the application."); } @@ -472,19 +492,19 @@ private ServiceResult UpdateCertificate( case null: case "": { - X509Certificate2 certWithPrivateKey = certificateGroup.ApplicationCertificate.LoadPrivateKeyEx(passwordProvider).Result; + X509Certificate2 certWithPrivateKey = existingCertIdentifier.LoadPrivateKeyEx(passwordProvider).Result; updateCertificate.CertificateWithPrivateKey = CertificateFactory.CreateCertificateWithPrivateKey(newCert, certWithPrivateKey); break; } case "PFX": { - X509Certificate2 certWithPrivateKey = X509Utils.CreateCertificateFromPKCS12(privateKey, passwordProvider?.GetPassword(certificateGroup.ApplicationCertificate)); + X509Certificate2 certWithPrivateKey = X509Utils.CreateCertificateFromPKCS12(privateKey, passwordProvider?.GetPassword(existingCertIdentifier)); updateCertificate.CertificateWithPrivateKey = CertificateFactory.CreateCertificateWithPrivateKey(newCert, certWithPrivateKey); break; } case "PEM": { - updateCertificate.CertificateWithPrivateKey = CertificateFactory.CreateCertificateWithPEMPrivateKey(newCert, privateKey, passwordProvider?.GetPassword(certificateGroup.ApplicationCertificate)); + updateCertificate.CertificateWithPrivateKey = CertificateFactory.CreateCertificateWithPEMPrivateKey(newCert, privateKey, passwordProvider?.GetPassword(existingCertIdentifier)); break; } } @@ -504,13 +524,13 @@ private ServiceResult UpdateCertificate( try { // TODO - using (ICertificateStore appStore = certificateGroup.ApplicationCertificate.OpenStore()) + using (ICertificateStore appStore = existingCertIdentifier.OpenStore()) { - Utils.LogCertificate(Utils.TraceMasks.Security, "Delete application certificate: ", certificateGroup.ApplicationCertificate.Certificate); - appStore.Delete(certificateGroup.ApplicationCertificate.Thumbprint).Wait(); + Utils.LogCertificate(Utils.TraceMasks.Security, "Delete application certificate: ", existingCertIdentifier.Certificate); + appStore.Delete(existingCertIdentifier.Thumbprint).Wait(); Utils.LogCertificate(Utils.TraceMasks.Security, "Add new application certificate: ", updateCertificate.CertificateWithPrivateKey); var passwordProvider = m_configuration.SecurityConfiguration.CertificatePasswordProvider; - appStore.Add(updateCertificate.CertificateWithPrivateKey, passwordProvider?.GetPassword(certificateGroup.ApplicationCertificate)).Wait(); + appStore.Add(updateCertificate.CertificateWithPrivateKey, passwordProvider?.GetPassword(existingCertIdentifier)).Wait(); // keep only track of cert without private key var certOnly = new X509Certificate2(updateCertificate.CertificateWithPrivateKey.RawData); updateCertificate.CertificateWithPrivateKey.Dispose(); @@ -568,6 +588,11 @@ private ServiceResult CreateSigningRequest( ServerCertificateGroup certificateGroup = VerifyGroupAndTypeId(certificateGroupId, certificateTypeId); + // identify the existing certificate for which to CreateSigningRequest + // it should be of the same type + CertificateIdentifier existingCertIdentifier = certificateGroup.ApplicationCertificates.FirstOrDefault(cert => + cert.CertificateType == certificateTypeId); + if (!String.IsNullOrEmpty(subjectName)) { throw new ArgumentNullException(nameof(subjectName)); @@ -577,7 +602,7 @@ private ServiceResult CreateSigningRequest( // TODO: use nonce for generating the private key var passwordProvider = m_configuration.SecurityConfiguration.CertificatePasswordProvider; - X509Certificate2 certWithPrivateKey = certificateGroup.ApplicationCertificate.LoadPrivateKeyEx(passwordProvider).Result; + X509Certificate2 certWithPrivateKey = existingCertIdentifier.LoadPrivateKeyEx(passwordProvider).Result; Utils.LogCertificate(Utils.TraceMasks.Security, "Create signing request: ", certWithPrivateKey); certificateRequest = CertificateFactory.CreateSigningRequest(certWithPrivateKey, X509Utils.GetDomainsFromCertficate(certWithPrivateKey)); return ServiceResult.Good; @@ -789,7 +814,7 @@ private class ServerCertificateGroup public NodeId NodeId; public CertificateGroupState Node; public NodeId[] CertificateTypes; - public CertificateIdentifier ApplicationCertificate; + public CertificateIdentifierCollection ApplicationCertificates; public string IssuerStorePath; public string TrustedStorePath; public UpdateCertificateData UpdateCertificate; diff --git a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs index a21ead76c..4a5409787 100644 --- a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs +++ b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs @@ -841,7 +841,7 @@ public CertificateIdentifier ApplicationCertificate /// The application instance certificates in use for the application. /// [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 15)] - public CertificateIdentifierCollection ListOfCertificateIdentifier + public CertificateIdentifierCollection ApplicationCertificates { get => m_applicationCertificates; set @@ -1166,6 +1166,7 @@ public bool SuppressNonceValidationErrors public bool IsDeprecatedConfiguration { get { return m_isDeprecatedConfiguration; } + set { m_isDeprecatedConfiguration = value; } } #endregion @@ -2992,7 +2993,7 @@ public CertificateIdentifierCollection TrustedCertificates #endregion #region CertificateIdentifierCollection Class - [CollectionDataContract(Name = "ListOfCertificateIdentifier", Namespace = Namespaces.OpcUaConfig, ItemName = "CertificateIdentifier")] + [CollectionDataContract(Name = "ApplicationCertificates", Namespace = Namespaces.OpcUaConfig, ItemName = "CertificateIdentifier")] public partial class CertificateIdentifierCollection : List { /// diff --git a/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs b/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs index f1823b20d..0b714774b 100644 --- a/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs +++ b/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs @@ -424,6 +424,15 @@ private static Opc.Ua.Security.SecurityProfile CreateProfile(string profileUri) policy.Enabled = false; return policy; } + + /// + /// TODO: Holds the application certificates but should be generated and the Opc.Ua.Security namespace automatically + /// TODO: Should replace ApplicationCertificateField in the generated Opc.Ua.Security.SecuredApplication class + /// + public CertificateList ApplicationCertificates + { + get; set; + } } /// diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs index 4b109fb0d..5514f628e 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs @@ -35,6 +35,15 @@ public static class CertificateFactory /// Supported values are 1024(deprecated), 2048, 3072 or 4096. /// public static readonly ushort DefaultKeySize = 2048; + + /// + /// The default key size for ECC certificates in bits. + /// + /// + /// Supported values are 256, 384 or 521. + /// + public static readonly ushort DefaultECCKeySize = 256; + /// /// The default hash size for RSA certificates in bits. /// diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index 892d1c8d6..ec898dd7f 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -1066,21 +1066,21 @@ public X509Certificate2 GetInstanceCertificate(string securityPolicyUri) if (securityPolicyUri == SecurityPolicies.None) { // return the default certificate for None - return m_securityConfiguration.ApplicationCertificate.Certificate; + return m_securityConfiguration.ApplicationCertificates.FirstOrDefault().Certificate; } var certificateTypes = CertificateIdentifier.MapSecurityPolicyToCertificateTypes(securityPolicyUri); foreach (var certType in certificateTypes) { - var instanceCertificate = m_securityConfiguration.ListOfCertificateIdentifier.FirstOrDefault(id => id.CertificateType == certType); + var instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == certType); if (instanceCertificate == null && certType == ObjectTypeIds.RsaSha256ApplicationCertificateType) { - instanceCertificate = m_securityConfiguration.ListOfCertificateIdentifier.FirstOrDefault(id => id.CertificateType == null); + instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == null); } if (instanceCertificate == null && certType == ObjectTypeIds.ApplicationCertificateType) { - instanceCertificate = m_securityConfiguration.ListOfCertificateIdentifier.FirstOrDefault(); + instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(); } if (instanceCertificate != null) { @@ -1099,7 +1099,7 @@ public Task GetInstanceCertificateAsync(IList certific { foreach (var certType in certificateTypes) { - var instanceCertificate = m_securityConfiguration.ListOfCertificateIdentifier.FirstOrDefault(id => id.CertificateType == certType); + var instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == certType); if (instanceCertificate != null) { return instanceCertificate.Find(privateKey); diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index f5c863a2d..ffbebc21a 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -1748,6 +1748,21 @@ private static bool IsSignatureValid(X509Certificate2 cert) StatusCodes.BadCertificateUntrusted }); + /// + /// Dictionary of named curves and their bit sizes. + /// + private static readonly Dictionary NamedCurveBitSizes = new Dictionary + { + // NIST Curves + { "1.2.840.10045.3.1.7", 256 }, // secp256r1 or prime256v1 or NIST P-256 + { "1.3.132.0.34", 384 }, // secp384r1 or NIST P-384 + { "1.3.132.0.35", 521 }, // secp521r1 or NIST P-521 + + // Brainpool Curves + { "1.3.36.3.3.2.8.1.1.7", 256 }, // BrainpoolP256r1 + { "1.3.36.3.3.2.8.1.1.11", 384 }, // BrainpoolP384r1 + }; + /// /// Find the domain in a certificate in the /// endpoint that was used to connect a session. @@ -1823,18 +1838,43 @@ public static bool IsECSecureForProfile(X509Certificate2 certificate, int requir ECCurve curve = ecdsa.ExportParameters(false).Curve; - int curveKeySizeInBits = curve.Order.Length * 8; - - // Check if the curve key size is at least as large as the required key size - if (curveKeySizeInBits < requiredKeySizeInBits) + if (curve.IsNamed) { - return false; + if (NamedCurveBitSizes.TryGetValue(curve.Oid.Value, out int curveSize)) + { + return curveSize >= requiredKeySizeInBits; + } + throw new NotSupportedException($"Unknown named curve: {curve.Oid.Value}"); + } + else + { + throw new NotSupportedException("Unsupported curve type."); } - - // The curve is secure enough for the profile - return true; } } + // public static bool IsECSecureForProfile(X509Certificate2 certificate, int requiredKeySizeInBits) + // { + // using (ECDsa ecdsa = certificate.GetECDsaPublicKey()) + // { + // if (ecdsa == null) + // { + // throw new ArgumentException("Certificate does not contain an ECC public key"); + // } + + // ECCurve curve = ecdsa.ExportParameters(false).Curve; + + // int curveKeySizeInBits = curve.Order.Length * 8; + + // // Check if the curve key size is at least as large as the required key size + // if (curveKeySizeInBits < requiredKeySizeInBits) + // { + // return false; + // } + + // // The curve is secure enough for the profile + // return true; + // } + // } #endregion #region Private Enum diff --git a/Stack/Opc.Ua.Core/Security/Certificates/SecurityConfiguration.cs b/Stack/Opc.Ua.Core/Security/Certificates/SecurityConfiguration.cs index 072646875..b88c4042c 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/SecurityConfiguration.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/SecurityConfiguration.cs @@ -88,23 +88,23 @@ public async Task FindApplicationCertificateAsync(string secur var certificateTypes = CertificateIdentifier.MapSecurityPolicyToCertificateTypes(securityPolicy); foreach (var certType in certificateTypes) { - CertificateIdentifier id = ListOfCertificateIdentifier.FirstOrDefault(certId => certId.CertificateType == certType); + CertificateIdentifier id = ApplicationCertificates.FirstOrDefault(certId => certId.CertificateType == certType); if (id == null) { if (certType == ObjectTypeIds.RsaSha256ApplicationCertificateType) { // undefined certificate type as RsaSha256 - id = ListOfCertificateIdentifier.FirstOrDefault(certId => certId.CertificateType == null); + id = ApplicationCertificates.FirstOrDefault(certId => certId.CertificateType == null); } else if (certType == ObjectTypeIds.ApplicationCertificateType) { // first certificate - id = ListOfCertificateIdentifier.FirstOrDefault(); + id = ApplicationCertificates.FirstOrDefault(); } else if (certType == ObjectTypeIds.EccApplicationCertificateType) { // first Ecc certificate - id = ListOfCertificateIdentifier.FirstOrDefault(certId => X509Utils.IsECDsaSignature(certId.Certificate)); + id = ApplicationCertificates.FirstOrDefault(certId => X509Utils.IsECDsaSignature(certId.Certificate)); } } diff --git a/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs b/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs index 1cc658329..96980ef26 100644 --- a/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs +++ b/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs @@ -109,7 +109,7 @@ public static DiscoveryClient Create( } /// - /// Creates a binding for to use for discovering servers. + /// Creates a binding to use for discovering servers. /// /// The discovery URL. /// The endpoint configuration. @@ -130,6 +130,7 @@ public static DiscoveryClient Create( try { + // Will always use the first certificate clientCertificate = applicationConfiguration?.SecurityConfiguration?.ApplicationCertificate?.Find(true).Result; } catch diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs index 10c6ab0f0..38f3e20ea 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/ApplicationConfiguration.cs @@ -437,7 +437,7 @@ public virtual async Task Validate(ApplicationType applicationType) SecurityConfiguration.Validate(); // load private keys - foreach (var applicationCertificate in SecurityConfiguration.ListOfCertificateIdentifier) + foreach (var applicationCertificate in SecurityConfiguration.ApplicationCertificates) { await applicationCertificate.LoadPrivateKeyEx(SecurityConfiguration.CertificatePasswordProvider).ConfigureAwait(false); } diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs b/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs index 13f742720..c6cd64a3d 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs @@ -148,9 +148,17 @@ public SecuredApplication ReadConfiguration(string filePath) // copy the security settings. if (applicationConfiguration.SecurityConfiguration != null) { + + if (applicationConfiguration.SecurityConfiguration.IsDeprecatedConfiguration) + { #pragma warning disable CS0618 // Type or member is obsolete - application.ApplicationCertificate = SecuredApplication.ToCertificateIdentifier(applicationConfiguration.SecurityConfiguration.ApplicationCertificate); -#pragma warning restore CS0618 // Type or member is obsolete + application.ApplicationCertificate = SecuredApplication.ToCertificateIdentifier(applicationConfiguration.SecurityConfiguration.ApplicationCertificate); +#pragma warning disable CS0618 // Type or member is obsolete + } + else + { + application.ApplicationCertificates = SecuredApplication.ToCertificateList(applicationConfiguration.SecurityConfiguration.ApplicationCertificates); + } if (applicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates != null) { @@ -323,6 +331,12 @@ private static void UpdateDocument(XmlElement element, SecuredApplication applic #pragma warning disable CS0618 // Type or member is obsolete security.ApplicationCertificate = SecuredApplication.FromCertificateIdentifier(application.ApplicationCertificate); #pragma warning restore CS0618 // Type or member is obsolete + security.IsDeprecatedConfiguration = true; + } + + if (application.ApplicationCertificates != null) + { + security.ApplicationCertificates = SecuredApplication.FromCertificateList(application.ApplicationCertificates); } security.TrustedIssuerCertificates = SecuredApplication.FromCertificateStoreIdentifierToTrustList(application.IssuerCertificateStore); diff --git a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs index fa410e8e5..14baf4bda 100644 --- a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs +++ b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs @@ -118,17 +118,13 @@ public async Task LoadConfiguration(string pkiRoot = null) RejectTimeout = ReverseConnectTimeout / 4 }); } - - string eccCertTypes = "RSA,nistP256,nistP384"; - if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - eccCertTypes += ",brainpoolP256r1,brainpoolP384r1"; - } + + CertificateIdentifierCollection eccSecurityCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates(pkiRoot); Config = await serverConfig.AddSecurityConfiguration( "CN=" + typeof(T).Name + ", C=US, S=Arizona, O=OPC Foundation, DC=localhost", pkiRoot) - .SetApplicationCertificateTypes(eccCertTypes) + .SetApplicationCertificates(eccSecurityCerts) .SetAutoAcceptUntrustedCertificates(AutoAccept) .Create().ConfigureAwait(false); } From d23450d189b0a21a2e6a04d8455e9507ac09f163 Mon Sep 17 00:00:00 2001 From: mirceasu Date: Mon, 18 Sep 2023 12:20:43 +0300 Subject: [PATCH 08/80] Added new Interface AddSecurityConfiguration( CertificateIdentifierCollection applicationCertificates, string pkiRoot = null, string rejectedRoot = null ) --- .../ApplicationConfigurationBuilder.cs | 123 ++++++++++++++---- .../IApplicationConfigurationBuilder.cs | 20 +++ .../Schema/ApplicationConfiguration.cs | 1 + .../Certificates/CertificateIdentifier.cs | 25 ++-- .../Certificates/CertificateValidator.cs | 71 +++++----- .../SecurityConfigurationManager.cs | 2 +- Tests/Opc.Ua.Client.Tests/ClientFixture.cs | 14 +- Tests/Opc.Ua.Client.Tests/ClientTest.cs | 7 +- .../ApplicationInstanceTests.cs | 90 ++++++++++--- .../Certificates/TemporaryCertValidator.cs | 6 +- .../GlobalDiscoveryTestClient.cs | 7 +- .../GlobalDiscoveryTestServer.cs | 8 +- .../ServerConfigurationPushTestClient.cs | 9 +- Tests/Opc.Ua.Server.Tests/ServerFixture.cs | 9 +- 14 files changed, 288 insertions(+), 104 deletions(-) diff --git a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs index b07360142..fd588b33a 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs @@ -104,7 +104,73 @@ public IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration( var rejectedRootType = CertificateStoreIdentifier.DetermineStoreType(rejectedRoot); ApplicationConfiguration.SecurityConfiguration = new SecurityConfiguration { // app cert store - ApplicationCertificates = CreateDefaultApplicationCertificates(appRoot), +#pragma warning disable CS0618 // Type or member is obsolete + ApplicationCertificate = new CertificateIdentifier() { +#pragma warning restore CS0618 // Type or member is obsolete + StoreType = appStoreType, + StorePath = DefaultCertificateStorePath(TrustlistType.Application, appRoot), + SubjectName = Utils.ReplaceDCLocalhost(subjectName) + }, + // App trusted & issuer + TrustedPeerCertificates = new CertificateTrustList() { + StoreType = pkiRootType, + StorePath = DefaultCertificateStorePath(TrustlistType.Trusted, pkiRoot) + }, + TrustedIssuerCertificates = new CertificateTrustList() { + StoreType = pkiRootType, + StorePath = DefaultCertificateStorePath(TrustlistType.Issuer, pkiRoot) + }, + // Https trusted & issuer + TrustedHttpsCertificates = new CertificateTrustList() { + StoreType = pkiRootType, + StorePath = DefaultCertificateStorePath(TrustlistType.TrustedHttps, pkiRoot) + }, + HttpsIssuerCertificates = new CertificateTrustList() { + StoreType = pkiRootType, + StorePath = DefaultCertificateStorePath(TrustlistType.IssuerHttps, pkiRoot) + }, + // User trusted & issuer + TrustedUserCertificates = new CertificateTrustList() { + StoreType = pkiRootType, + StorePath = DefaultCertificateStorePath(TrustlistType.TrustedUser, pkiRoot) + }, + UserIssuerCertificates = new CertificateTrustList() { + StoreType = pkiRootType, + StorePath = DefaultCertificateStorePath(TrustlistType.IssuerUser, pkiRoot) + }, + // rejected store + RejectedCertificateStore = new CertificateTrustList() { + StoreType = rejectedRootType, + StorePath = DefaultCertificateStorePath(TrustlistType.Rejected, rejectedRoot) + }, + // ensure secure default settings + AutoAcceptUntrustedCertificates = false, + AddAppCertToTrustedStore = false, + RejectSHA1SignedCertificates = true, + RejectUnknownRevocationStatus = true, + SuppressNonceValidationErrors = false, + SendCertificateChain = true, + MinimumCertificateKeySize = CertificateFactory.DefaultKeySize, + MinimumECCertificateKeySize = CertificateFactory.DefaultECCKeySize, + }; + + return this; + } + + /// + public IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration( + CertificateIdentifierCollection applicationCertificates, + string pkiRoot = null, + string rejectedRoot = null + ) + { + pkiRoot = DefaultPKIRoot(pkiRoot); + rejectedRoot = rejectedRoot == null ? pkiRoot : DefaultPKIRoot(rejectedRoot); + var pkiRootType = CertificateStoreIdentifier.DetermineStoreType(pkiRoot); + var rejectedRootType = CertificateStoreIdentifier.DetermineStoreType(rejectedRoot); + ApplicationConfiguration.SecurityConfiguration = new SecurityConfiguration { + // app cert store + ApplicationCertificates = applicationCertificates, // App trusted & issuer TrustedPeerCertificates = new CertificateTrustList() { StoreType = pkiRootType, @@ -804,29 +870,34 @@ public IApplicationConfigurationBuilderExtension AddExtension(XmlQualifiedNam /// /// Create ApplicationCertificates from a PKI root. /// - /// The PKI root. + /// The cert store type: ex: "Directory" + /// The PKI root. + /// The subject name. /// The application certificates. - public static CertificateIdentifierCollection CreateDefaultApplicationCertificates(string pkiRoot) + public static CertificateIdentifierCollection CreateDefaultApplicationCertificates( + string subjectName, + string storeType = null, + string storePath = null) { CertificateIdentifierCollection certificateIdentifiers = new CertificateIdentifierCollection{ new CertificateIdentifier { - StoreType = "Directory", - StorePath = pkiRoot, - SubjectName = "N=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost", - CertificateType = new NodeId ("i=12560") // RSA + StoreType = storeType, + StorePath = storePath, + SubjectName = subjectName, + CertificateType = ObjectTypeIds.RsaSha256ApplicationCertificateType }, new CertificateIdentifier { - StoreType = "Directory", - StorePath = pkiRoot, - SubjectName = "CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost", - CertificateType = new NodeId ("i=23538") // Nistp256 + StoreType = storeType, + StorePath = storePath, + SubjectName = subjectName, + CertificateType = ObjectTypeIds.EccNistP256ApplicationCertificateType }, new CertificateIdentifier { - StoreType = "Directory", - StorePath = pkiRoot, - SubjectName = "CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost", - CertificateType = new NodeId ("i=23539") // Nistp384 + StoreType = storeType, + StorePath = storePath, + SubjectName = subjectName, + CertificateType = ObjectTypeIds.EccNistP384ApplicationCertificateType } }; @@ -835,17 +906,19 @@ public static CertificateIdentifierCollection CreateDefaultApplicationCertificat certificateIdentifiers.AddRange( new CertificateIdentifierCollection { - new CertificateIdentifier { - StoreType = "Directory", - StorePath = pkiRoot, - SubjectName = "CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost", - CertificateType = new NodeId ("i=23540") // BrainpoolP256r1 + new CertificateIdentifier + { + StoreType = storeType, + StorePath = storePath, + SubjectName = subjectName, + CertificateType = ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType }, - new CertificateIdentifier { - StoreType = "Directory", - StorePath = pkiRoot, - SubjectName = "CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost", - CertificateType = new NodeId ("i=23541") // BrainpoolP384r1 + new CertificateIdentifier + { + StoreType = storeType, + StorePath = storePath, + SubjectName = subjectName, + CertificateType = ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType } }); } diff --git a/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs index 2f1f9680e..dd0a9f48d 100644 --- a/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs @@ -29,6 +29,7 @@ using System.Threading.Tasks; using System.Xml; +using System; namespace Opc.Ua.Configuration { @@ -392,12 +393,31 @@ public interface IApplicationConfigurationBuilderSecurity /// The path to the pki root. By default all cert stores use the pki root. /// The path to the app cert store, if different than the pki root. /// The path to the rejected certificate store. + [Obsolete("Use AddSecurityConfiguration(CertificateIdentifierCollection certIdList, string pkiRoot = null, string rejectedRoot = null) instead.")] IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration( string subjectName, string pkiRoot = null, string appRoot = null, string rejectedRoot = null ); + + /// + /// Add the security configuration. + /// + /// + /// The pki root path default to the certificate store + /// location as defined in + /// A defaults to the corresponding default store location. + /// + /// A list of Certificate identifiers + /// The path to the pki root. By default all cert stores use the pki root. + /// The path to the rejected certificate store. + + IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration( + CertificateIdentifierCollection certIdList, + string pkiRoot = null, + string rejectedRoot = null + ); /// /// Add the security configuration for mandatory application, issuer and trusted stores. diff --git a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs index 4a5409787..cc1d64d77 100644 --- a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs +++ b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs @@ -795,6 +795,7 @@ private void Initialize() #region Persistent Properties /// /// The application instance certificate. + /// Kept for backward compatibility with configuration files which only support RSA certificates. /// /// The application certificate. /// diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index ec898dd7f..9bfd6a12c 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -556,18 +556,21 @@ public static bool ValidateCertificateType(X509Certificate2 certificate, NodeId return true; } - // special cases - if (certType == ObjectTypeIds.EccNistP384ApplicationCertificateType && - certificateType == ObjectTypeIds.EccNistP256ApplicationCertificateType) - { - return true; - } + /* // Why where these special cases needed? + // special cases + if (certType == ObjectTypeIds.EccNistP384ApplicationCertificateType && + certificateType == ObjectTypeIds.EccNistP256ApplicationCertificateType) + { + return true; + } + + if (certType == ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType && + certificateType == ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType) + { + return true; + } + */ - if (certType == ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType && - certificateType == ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType) - { - return true; - } break; default: diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index ffbebc21a..f21c8e92c 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -35,6 +35,7 @@ public class CertificateValidator : ICertificateValidator public CertificateValidator() { m_validatedCertificates = new Dictionary(); + m_applicationCertificates = new List(); m_protectFlags = 0; m_autoAcceptUntrustedCertificates = false; m_rejectSHA1SignedCertificates = CertificateFactory.DefaultHashSize >= 256; @@ -180,7 +181,7 @@ private void InternalUpdate( /// /// Updates the validator with the current state of the configuration. /// - public virtual Task Update(SecurityConfiguration configuration) + public virtual async Task Update(SecurityConfiguration configuration) { if (configuration == null) { @@ -227,7 +228,25 @@ public virtual Task Update(SecurityConfiguration configuration) m_semaphore.Release(); } - return Task.CompletedTask; + if (configuration.ApplicationCertificates != null) + { + foreach (var applicationCertificate in configuration.ApplicationCertificates) + { + X509Certificate2 certificate = await applicationCertificate.Find(true).ConfigureAwait(false); + if (certificate == null) + { + Utils.Trace(Utils.TraceMasks.Security, "Could not find application certificate: {0}", applicationCertificate); + continue; + } + // Add to list of application certificates only if not allready in list + // necessary since the application certificates may be updated multiple times + if (!m_applicationCertificates.Exists(cert => Utils.IsEqual(cert.RawData, certificate.RawData))) + { + m_applicationCertificates.Add(certificate); + } + } + } + } /// @@ -1332,12 +1351,24 @@ protected virtual async Task InternalValidate(X509Certificate2Collection certifi // check if certificate is trusted. if (trustedCertificate == null && !isIssuerTrusted) { - // TODO ECC cert - bool isApplicationCertificate = true; - if (isApplicationCertificate) - //if (m_applicationCertificate == null || !Utils.IsEqual(m_applicationCertificate.RawData, certificate.RawData)) + // If the certificate is not trusted, check if the certificate is amongst the application certificates + bool isApplicationCertificate = false; + if (m_applicationCertificates != null) { - var message = "Certificate is not trusted."; + foreach (var appCert in m_applicationCertificates) + { + if (Utils.IsEqual(appCert.RawData, certificate.RawData)) + { + // certificate is the application certificate + isApplicationCertificate = true; + break; + } + } + } + + if (m_applicationCertificates == null || !isApplicationCertificate) + { + string message = "Certificate is not trusted."; sresult = new ServiceResult(StatusCodes.BadCertificateUntrusted, null, null, message, null, sresult); } @@ -1852,29 +1883,6 @@ public static bool IsECSecureForProfile(X509Certificate2 certificate, int requir } } } - // public static bool IsECSecureForProfile(X509Certificate2 certificate, int requiredKeySizeInBits) - // { - // using (ECDsa ecdsa = certificate.GetECDsaPublicKey()) - // { - // if (ecdsa == null) - // { - // throw new ArgumentException("Certificate does not contain an ECC public key"); - // } - - // ECCurve curve = ecdsa.ExportParameters(false).Curve; - - // int curveKeySizeInBits = curve.Order.Length * 8; - - // // Check if the curve key size is at least as large as the required key size - // if (curveKeySizeInBits < requiredKeySizeInBits) - // { - // return false; - // } - - // // The curve is secure enough for the profile - // return true; - // } - // } #endregion #region Private Enum @@ -1905,7 +1913,7 @@ private enum ProtectFlags private CertificateStoreIdentifier m_rejectedCertificateStore; private event CertificateValidationEventHandler m_CertificateValidation; private event CertificateUpdateEventHandler m_CertificateUpdate; - //private X509Certificate2 m_applicationCertificate; + private List m_applicationCertificates; private ProtectFlags m_protectFlags; private bool m_autoAcceptUntrustedCertificates; private bool m_rejectSHA1SignedCertificates; @@ -1913,6 +1921,7 @@ private enum ProtectFlags private ushort m_minimumCertificateKeySize; private ushort m_minimumECCertificateKeySize; private bool m_useValidatedCertificates; + #endregion } diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs b/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs index c6cd64a3d..b90b2602a 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs @@ -153,7 +153,7 @@ public SecuredApplication ReadConfiguration(string filePath) { #pragma warning disable CS0618 // Type or member is obsolete application.ApplicationCertificate = SecuredApplication.ToCertificateIdentifier(applicationConfiguration.SecurityConfiguration.ApplicationCertificate); -#pragma warning disable CS0618 // Type or member is obsolete +#pragma warning restore CS0618 // Type or member is obsolete } else { diff --git a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs index af06536af..067df4e00 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs @@ -75,11 +75,11 @@ public async Task LoadClientConfiguration(string pkiRoot = null, string clientNa pkiRoot = pkiRoot ?? Path.Combine("%LocalApplicationData%", "OPC", "pki"); - string eccCertTypes = "RSA,nistP256,nistP384"; - if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - eccCertTypes += ",brainpoolP256r1,brainpoolP384r1"; - } + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + "CN=" + clientName + ", O=OPC Foundation, DC=localhost", + CertificateStoreType.Directory, + pkiRoot + ); // build the application configuration. Config = await application @@ -94,10 +94,10 @@ public async Task LoadClientConfiguration(string pkiRoot = null, string clientNa MaxNodesPerWrite = kDefaultOperationLimits }) .AddSecurityConfiguration( - "CN=" + clientName + ", O=OPC Foundation, DC=localhost", + applicationCerts, pkiRoot) - .SetApplicationCertificateTypes(eccCertTypes) + // .SetApplicationCertificates(applicationCerts) .SetAutoAcceptUntrustedCertificates(true) .SetRejectSHA1SignedCertificates(false) .SetMinimumCertificateKeySize(1024) diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs index 44a776d50..d5eba8fef 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs @@ -311,9 +311,14 @@ public async Task InvalidConfiguration() ApplicationName = ClientFixture.Config.ApplicationName }; Assert.NotNull(applicationInstance); + + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + ClientFixture.Config.SecurityConfiguration.ApplicationCertificate.SubjectName + ); + ApplicationConfiguration config = await applicationInstance.Build(ClientFixture.Config.ApplicationUri, ClientFixture.Config.ProductUri) .AsClient() - .AddSecurityConfiguration(ClientFixture.Config.SecurityConfiguration.ApplicationCertificate.SubjectName) + .AddSecurityConfiguration(applicationCerts) .Create().ConfigureAwait(false); } diff --git a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs index 1e42986c7..d74cbc5c0 100644 --- a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs +++ b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs @@ -106,9 +106,15 @@ public async Task TestNoFileConfigAsClient() ApplicationName = ApplicationName }; Assert.NotNull(applicationInstance); + + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + SubjectName, + CertificateStoreType.Directory, + m_pkiRoot); + ApplicationConfiguration config = await applicationInstance.Build(ApplicationUri, ProductUri) .AsClient() - .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .AddSecurityConfiguration(applicationCerts,m_pkiRoot) .Create().ConfigureAwait(false); Assert.NotNull(config); bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); @@ -121,10 +127,16 @@ public async Task TestBadApplicationInstance() // no app name var applicationInstance = new ApplicationInstance(); Assert.NotNull(applicationInstance); + + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + SubjectName, + CertificateStoreType.Directory, + m_pkiRoot); + Assert.ThrowsAsync(async () => await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { EndpointUrl }) - .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .AddSecurityConfiguration(applicationCerts,m_pkiRoot) .Create() .ConfigureAwait(false) ); @@ -136,14 +148,14 @@ await applicationInstance.Build(ApplicationUri, ProductUri) Assert.ThrowsAsync(async () => await applicationInstance.Build(ApplicationUri, ProductUri) .AsClient() - .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .AddSecurityConfiguration(applicationCerts,m_pkiRoot) .Create() .ConfigureAwait(false) ); Assert.ThrowsAsync(async () => await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { EndpointUrl }) - .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .AddSecurityConfiguration(applicationCerts,m_pkiRoot) .Create() .ConfigureAwait(false) ); @@ -155,7 +167,7 @@ await applicationInstance.Build(ApplicationUri, ProductUri) var config = await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { EndpointUrl }) - .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .AddSecurityConfiguration(applicationCerts,m_pkiRoot) .Create() .ConfigureAwait(false); Assert.AreEqual(ApplicationType.Server, applicationInstance.ApplicationType); @@ -168,7 +180,7 @@ await applicationInstance.Build(ApplicationUri, ProductUri) await applicationInstance.Build(ApplicationUri, ProductUri) .AsClient() - .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .AddSecurityConfiguration(applicationCerts,m_pkiRoot) .Create() .ConfigureAwait(false); Assert.AreEqual(ApplicationType.Client, applicationInstance.ApplicationType); @@ -182,7 +194,7 @@ await applicationInstance.Build(ApplicationUri, ProductUri) await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { EndpointUrl }) .AddPolicy(MessageSecurityMode.None, SecurityPolicies.None) - .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .AddSecurityConfiguration(applicationCerts,m_pkiRoot) .Create() .ConfigureAwait(false) ); @@ -191,7 +203,7 @@ await applicationInstance.Build(ApplicationUri, ProductUri) await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { EndpointUrl }) .AddPolicy(MessageSecurityMode.Sign, SecurityPolicies.None) - .AddSecurityConfiguration(SubjectName) + .AddSecurityConfiguration(applicationCerts) .Create() .ConfigureAwait(false) ); @@ -200,7 +212,7 @@ await applicationInstance.Build(ApplicationUri, ProductUri) await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { EndpointUrl }) .AddPolicy(MessageSecurityMode.Sign, "123") - .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .AddSecurityConfiguration(applicationCerts,m_pkiRoot) .Create() .ConfigureAwait(false) ); @@ -209,7 +221,7 @@ await applicationInstance.Build(ApplicationUri, ProductUri) await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { EndpointUrl }) .AddUserTokenPolicy(null) - .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .AddSecurityConfiguration(applicationCerts,m_pkiRoot) .Create() .ConfigureAwait(false) ); @@ -222,10 +234,16 @@ public async Task TestNoFileConfigAsServerMinimal() ApplicationName = ApplicationName }; Assert.NotNull(applicationInstance); + + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + SubjectName, + CertificateStoreType.Directory, + m_pkiRoot); + ApplicationConfiguration config = await applicationInstance.Build(ApplicationUri, ProductUri) .SetOperationTimeout(10000) .AsServer(new string[] { EndpointUrl }) - .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .AddSecurityConfiguration(applicationCerts, m_pkiRoot) .Create().ConfigureAwait(false); Assert.NotNull(config); bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); @@ -239,6 +257,12 @@ public async Task TestNoFileConfigAsServerMaximal() ApplicationName = ApplicationName }; Assert.NotNull(applicationInstance); + + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + SubjectName, + CertificateStoreType.Directory, + m_pkiRoot); + ApplicationConfiguration config = await applicationInstance.Build(ApplicationUri, ProductUri) .SetTransportQuotas(new TransportQuotas() { OperationTimeout = 10000 }) .AsServer(new string[] { EndpointUrl }) @@ -254,7 +278,7 @@ public async Task TestNoFileConfigAsServerMaximal() .AddUserTokenPolicy(new UserTokenPolicy(UserTokenType.Certificate) { SecurityPolicyUri = SecurityPolicies.Basic256Sha256 }) .SetDiagnosticsEnabled(true) .SetPublishingResolution(100) - .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .AddSecurityConfiguration(applicationCerts, m_pkiRoot) .SetAddAppCertToTrustedStore(true) .SetAutoAcceptUntrustedCertificates(true) .SetMinimumCertificateKeySize(1024) @@ -275,6 +299,12 @@ public async Task TestNoFileConfigAsClientAndServer() ApplicationName = ApplicationName }; Assert.NotNull(applicationInstance); + + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + SubjectName, + CertificateStoreType.Directory, + m_pkiRoot); + ApplicationConfiguration config = await applicationInstance.Build(ApplicationUri, ProductUri) .SetMaxBufferSize(32768) .AsServer(new string[] { EndpointUrl }) @@ -284,7 +314,7 @@ public async Task TestNoFileConfigAsClientAndServer() .AddPolicy(MessageSecurityMode.Sign, SecurityPolicies.Basic256) .SetDiagnosticsEnabled(true) .AsClient() - .AddSecurityConfiguration(SubjectName, CertificateStoreType.Directory, CertificateStoreType.X509Store) + .AddSecurityConfiguration(applicationCerts, CertificateStoreType.Directory, CertificateStoreType.X509Store) .Create().ConfigureAwait(false); Assert.NotNull(config); bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); @@ -309,6 +339,12 @@ public async Task TestNoFileConfigAsServerX509Store() ApplicationName = ApplicationName }; Assert.NotNull(applicationInstance); + + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + SubjectName, + CertificateStoreType.Directory, + m_pkiRoot); + ApplicationConfiguration config = await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { EndpointUrl }) .AddUnsecurePolicyNone() @@ -316,7 +352,7 @@ public async Task TestNoFileConfigAsServerX509Store() .AddUserTokenPolicy(UserTokenType.UserName) .AsClient() .SetDefaultSessionTimeout(10000) - .AddSecurityConfiguration(SubjectName, CertificateStoreType.X509Store) + .AddSecurityConfiguration(applicationCerts, CertificateStoreType.X509Store) .Create().ConfigureAwait(false); Assert.NotNull(config); var applicationCertificate = applicationInstance.ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate; @@ -354,6 +390,12 @@ public async Task TestNoFileConfigAsServerCustom() ApplicationName = ApplicationName }; Assert.NotNull(applicationInstance); + + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + SubjectName, + CertificateStoreType.Directory, + m_pkiRoot); + ApplicationConfiguration config = await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { EndpointUrl, "opc.https://localhost:51001" }, new string[] { "opc.tcp://192.168.1.100:51000" }) .AddSecurityConfiguration(SubjectName, m_pkiRoot) @@ -394,19 +436,25 @@ public async Task TestInvalidAppCertDoNotRecreate(InvalidCertType certType, bool var applicationInstance = new ApplicationInstance() { ApplicationName = ApplicationName }; Assert.NotNull(applicationInstance); + + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + SubjectName, + CertificateStoreType.Directory, + pkiRoot); + ApplicationConfiguration config; if (server) { config = await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { "opc.tcp://localhost:12345/Configuration" }) - .AddSecurityConfiguration(SubjectName, pkiRoot) + .AddSecurityConfiguration(applicationCerts, pkiRoot) .Create().ConfigureAwait(false); } else { config = await applicationInstance.Build(ApplicationUri, ProductUri) .AsClient() - .AddSecurityConfiguration(SubjectName, pkiRoot) + .AddSecurityConfiguration(applicationCerts, pkiRoot) .Create().ConfigureAwait(false); } @@ -469,19 +517,25 @@ public async Task TestInvalidAppCertChainDoNotRecreate(InvalidCertType certType, ApplicationName = ApplicationName }; Assert.NotNull(applicationInstance); + + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + SubjectName, + CertificateStoreType.Directory, + pkiRoot); + ApplicationConfiguration config; if (server) { config = await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { "opc.tcp://localhost:12345/Configuration" }) - .AddSecurityConfiguration(SubjectName, pkiRoot) + .AddSecurityConfiguration(applicationCerts, pkiRoot) .Create().ConfigureAwait(false); } else { config = await applicationInstance.Build(ApplicationUri, ProductUri) .AsClient() - .AddSecurityConfiguration(SubjectName, pkiRoot) + .AddSecurityConfiguration(applicationCerts, pkiRoot) .Create().ConfigureAwait(false); } Assert.NotNull(config); diff --git a/Tests/Opc.Ua.Core.Tests/Security/Certificates/TemporaryCertValidator.cs b/Tests/Opc.Ua.Core.Tests/Security/Certificates/TemporaryCertValidator.cs index 4caa0b1f8..c746fa633 100644 --- a/Tests/Opc.Ua.Core.Tests/Security/Certificates/TemporaryCertValidator.cs +++ b/Tests/Opc.Ua.Core.Tests/Security/Certificates/TemporaryCertValidator.cs @@ -115,18 +115,18 @@ public CertificateValidator Update() { var certValidator = new CertificateValidator(); var issuerTrustList = new CertificateTrustList { - StoreType = "Directory", + StoreType = CertificateStoreType.Directory, StorePath = m_issuerStore.Directory.FullName }; var trustedTrustList = new CertificateTrustList { - StoreType = "Directory", + StoreType = CertificateStoreType.Directory, StorePath = m_trustedStore.Directory.FullName }; CertificateStoreIdentifier rejectedList = null; if (m_rejectedStore != null) { rejectedList = new CertificateStoreIdentifier { - StoreType = "Directory", + StoreType = CertificateStoreType.Directory, StorePath = m_rejectedStore.Directory.FullName }; } diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs index 218d624ae..4eb9268b2 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs @@ -73,6 +73,11 @@ public async Task LoadClientConfiguration(int port = -1) AdminPassword = "demo" }; + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + "CN=Global Discovery Test Client, O=OPC Foundation, DC=localhost", + CertificateStoreType.Directory, + pkiRoot); + // build the application configuration. Configuration = await application .Build( @@ -82,7 +87,7 @@ public async Task LoadClientConfiguration(int port = -1) .SetDefaultSessionTimeout(600000) .SetMinSubscriptionLifetime(10000) .AddSecurityConfiguration( - "CN=Global Discovery Test Client, O=OPC Foundation, DC=localhost", + applicationCerts, pkiRoot) .SetAutoAcceptUntrustedCertificates(true) .SetRejectSHA1SignedCertificates(false) diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs index 73e051afd..fe8110956 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs @@ -211,6 +211,12 @@ private static async Task Load(ApplicationInstance app DatabaseStorePath = Path.Combine(gdsRoot, "gdsdb.json") }; + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + "CN=Global Discovery Test Client, O=OPC Foundation, DC=localhost", + CertificateStoreType.Directory, + gdsRoot + ); + // build the application configuration. ApplicationConfiguration config = await application .Build( @@ -224,7 +230,7 @@ private static async Task Load(ApplicationInstance app .AddServerProfile("http://opcfoundation.org/UA-Profile/Server/GlobalDiscoveryAndCertificateManagement2017") .SetShutdownDelay(0) .AddSecurityConfiguration( - "CN=Global Discovery Test Server, O=OPC Foundation, DC=localhost", + applicationCerts, gdsRoot) .SetAutoAcceptUntrustedCertificates(true) .SetRejectSHA1SignedCertificates(false) diff --git a/Tests/Opc.Ua.Gds.Tests/ServerConfigurationPushTestClient.cs b/Tests/Opc.Ua.Gds.Tests/ServerConfigurationPushTestClient.cs index 11e45a9e4..52c100a00 100644 --- a/Tests/Opc.Ua.Gds.Tests/ServerConfigurationPushTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/ServerConfigurationPushTestClient.cs @@ -86,6 +86,11 @@ public async Task LoadClientConfiguration(int port = -1) SecurityTokenLifetime = 3600000, }; + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + "CN=Server Configuration Push Test Client, O=OPC Foundation", + CertificateStoreType.Directory, + pkiRoot); + // build the application configuration. Config = await application .Build( @@ -94,8 +99,8 @@ public async Task LoadClientConfiguration(int port = -1) .SetTransportQuotas(transportQuotas) .AsClient() .AddSecurityConfiguration( - "CN=Server Configuration Push Test Client, O=OPC Foundation", - pkiRoot, pkiRoot, pkiRoot) + applicationCerts, + pkiRoot, pkiRoot) .SetAutoAcceptUntrustedCertificates(true) .SetRejectSHA1SignedCertificates(false) .SetRejectUnknownRevocationStatus(true) diff --git a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs index 14baf4bda..fa4c2e3ed 100644 --- a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs +++ b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs @@ -119,12 +119,15 @@ public async Task LoadConfiguration(string pkiRoot = null) }); } - CertificateIdentifierCollection eccSecurityCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates(pkiRoot); + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates( + "CN=" + typeof(T).Name + ", C=US, S=Arizona, O=OPC Foundation, DC=localhost", + CertificateStoreType.Directory, + pkiRoot); Config = await serverConfig.AddSecurityConfiguration( - "CN=" + typeof(T).Name + ", C=US, S=Arizona, O=OPC Foundation, DC=localhost", + applicationCerts, pkiRoot) - .SetApplicationCertificates(eccSecurityCerts) + //.SetApplicationCertificates(applicationCerts) .SetAutoAcceptUntrustedCertificates(AutoAccept) .Create().ConfigureAwait(false); } From 8fc81d3ccbc1ed81c1756f4429c5a5d282d81967 Mon Sep 17 00:00:00 2001 From: mirceasu Date: Wed, 27 Sep 2023 17:08:39 +0300 Subject: [PATCH 09/80] Reenabled special cases in ValidateCertificateType --- .../Certificates/CertificateIdentifier.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index 9bfd6a12c..26e47865d 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -556,20 +556,19 @@ public static bool ValidateCertificateType(X509Certificate2 certificate, NodeId return true; } - /* // Why where these special cases needed? - // special cases - if (certType == ObjectTypeIds.EccNistP384ApplicationCertificateType && - certificateType == ObjectTypeIds.EccNistP256ApplicationCertificateType) - { - return true; - } - - if (certType == ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType && - certificateType == ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType) - { - return true; - } - */ + + // special cases + if (certType == ObjectTypeIds.EccNistP384ApplicationCertificateType && + certificateType == ObjectTypeIds.EccNistP256ApplicationCertificateType) + { + return true; + } + + if (certType == ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType && + certificateType == ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType) + { + return true; + } break; From b1cd86ec491d7f991215040ee0657209c61157ca Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Wed, 27 Sep 2023 18:01:38 +0300 Subject: [PATCH 10/80] Use KeySize property value if set under IsECSecureForProfile method --- .../Certificates/CertificateValidator.cs | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index f21c8e92c..cba8511bf 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -1867,19 +1867,27 @@ public static bool IsECSecureForProfile(X509Certificate2 certificate, int requir throw new ArgumentException("Certificate does not contain an ECC public key"); } - ECCurve curve = ecdsa.ExportParameters(false).Curve; - - if (curve.IsNamed) + if (ecdsa.KeySize != 0) { - if (NamedCurveBitSizes.TryGetValue(curve.Oid.Value, out int curveSize)) - { - return curveSize >= requiredKeySizeInBits; - } - throw new NotSupportedException($"Unknown named curve: {curve.Oid.Value}"); + return ecdsa.KeySize >= requiredKeySizeInBits; } else { - throw new NotSupportedException("Unsupported curve type."); + ECCurve curve = ecdsa.ExportParameters(false).Curve; + + if (curve.IsNamed) + { + if (NamedCurveBitSizes.TryGetValue(curve.Oid.Value, out int curveSize)) + { + return curveSize >= requiredKeySizeInBits; + } + throw new NotSupportedException($"Unknown named curve: {curve.Oid.Value}"); + } + else + { + throw new NotSupportedException("Unsupported curve type."); + } + } } } From 108f7ed5543bc7b798fb742419a1e6fe0cacbe96 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Tue, 3 Oct 2023 17:53:16 +0300 Subject: [PATCH 11/80] Check minimKeySize for ApplicationCertificates --- .../ConsoleReferenceClient/Program.cs | 2 +- .../Quickstarts.ReferenceServer.Config.xml | 1 + .../ConsoleReferenceServer/UAServer.cs | 2 +- .../ApplicationInstance.cs | 32 ++++++--------- .../Opc.Ua.Server/Server/StandardServer.cs | 6 +-- .../Certificates/CertificateIdentifier.cs | 29 +++++++++++++ .../Certificates/CertificateValidator.cs | 10 ++--- Stack/Opc.Ua.Core/Types/Utils/Utils.cs | 41 ++++++++++++++++++- 8 files changed, 91 insertions(+), 32 deletions(-) diff --git a/Applications/ConsoleReferenceClient/Program.cs b/Applications/ConsoleReferenceClient/Program.cs index e586a373f..b5dc945ee 100644 --- a/Applications/ConsoleReferenceClient/Program.cs +++ b/Applications/ConsoleReferenceClient/Program.cs @@ -160,7 +160,7 @@ public static async Task Main(string[] args) } // check the application certificate. - bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(false, minimumKeySize: 0).ConfigureAwait(false); + bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(false).ConfigureAwait(false); if (!haveAppCertificate) { throw new ErrorExitException("Application instance certificate invalid!", ExitCode.ErrorCertificate); diff --git a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml index f84a6805b..913de49f3 100644 --- a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml +++ b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml @@ -81,6 +81,7 @@ true true 2048 + 256 false true diff --git a/Applications/ConsoleReferenceServer/UAServer.cs b/Applications/ConsoleReferenceServer/UAServer.cs index 4c9c5dbf9..3ca1aba62 100644 --- a/Applications/ConsoleReferenceServer/UAServer.cs +++ b/Applications/ConsoleReferenceServer/UAServer.cs @@ -101,7 +101,7 @@ public async Task CheckCertificateAsync(bool renewCertificate) } // check the application certificate. - bool haveAppCertificate = await m_application.CheckApplicationInstanceCertificate(false, minimumKeySize: 0).ConfigureAwait(false); + bool haveAppCertificate = await m_application.CheckApplicationInstanceCertificate(false).ConfigureAwait(false); if (!haveAppCertificate) { throw new ErrorExitException("Application instance certificate invalid!"); diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index ce75d740e..c6c6d31c8 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -393,12 +393,10 @@ string productUri /// Checks for a valid application instance certificate. /// /// if set to true no dialogs will be displayed. - /// Minimum size of the key. public Task CheckApplicationInstanceCertificate( - bool silent, - ushort minimumKeySize) + bool silent) { - return CheckApplicationInstanceCertificate(silent, minimumKeySize, CertificateFactory.DefaultLifeTime); + return CheckApplicationInstanceCertificate(silent, CertificateFactory.DefaultLifeTime); } /// @@ -418,11 +416,9 @@ public async Task DeleteApplicationInstanceCertificate(string[] profileIds = nul /// Checks for a valid application instance certificate. /// /// if set to true no dialogs will be displayed. - /// Minimum size of the key. /// The lifetime in months. public async Task CheckApplicationInstanceCertificate( bool silent, - ushort minimumKeySize, ushort lifeTimeInMonths) { Utils.LogInfo("Checking application instance certificate."); @@ -443,6 +439,7 @@ public async Task CheckApplicationInstanceCertificate( bool result = true; foreach (var certId in securityConfiguration.ApplicationCertificates) { + ushort minimumKeySize = certId.GetMinKeySize(securityConfiguration); bool nextResult = await CheckCertificateTypeAsync(certId, silent, minimumKeySize, lifeTimeInMonths).ConfigureAwait(false); result = result && nextResult; } @@ -645,21 +642,18 @@ private async Task CheckApplicationInstanceCertificate( configuration.CertificateValidator.CertificateValidation -= certValidator.OnCertificateValidation; } - if (!X509Utils.IsECDsaSignature(certificate)) + // check key size + int keySize = X509Utils.GetPublicKeySize(certificate); + if (minimumKeySize > keySize) { - // check key size. - int keySize = X509Utils.GetRSAPublicKeySize(certificate); - if (minimumKeySize > keySize) - { - string message = Utils.Format( - "The key size ({0}) in the certificate is less than the minimum provided ({1}). Use certificate anyway?", - keySize, - minimumKeySize); + string message = Utils.Format( + "The key size ({0}) in the certificate is less than the minimum provided ({1}). Use certificate anyway?", + keySize, + minimumKeySize); - if (!await ApproveMessage(message, silent).ConfigureAwait(false)) - { - return false; - } + if (!await ApproveMessage(message, silent).ConfigureAwait(false)) + { + return false; } } diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs index 406558045..6ce37d967 100644 --- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs +++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs @@ -423,11 +423,7 @@ public override ResponseHeader CreateSession( } // load the certificate for the security profile - X509Certificate2 instanceCertificate = null; - if (requireEncryption) - { - instanceCertificate = InstanceCertificateTypesProvider.GetInstanceCertificate(context.SecurityPolicyUri); - } + X509Certificate2 instanceCertificate = InstanceCertificateTypesProvider.GetInstanceCertificate(context.SecurityPolicyUri); // create the session. session = ServerInternal.SessionManager.CreateSession( diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index 26e47865d..02e9d3645 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -509,6 +509,35 @@ public ICertificateStore OpenStore() return store; } + /// + /// Retrieves the minimum accepted key size given the security configuration + /// + /// + /// + public ushort GetMinKeySize(SecurityConfiguration securityConfiguration) + { + if (CertificateType == ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType || + CertificateType == ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType || + CertificateType == ObjectTypeIds.EccCurve25519ApplicationCertificateType || + CertificateType == ObjectTypeIds.EccCurve448ApplicationCertificateType || + CertificateType == ObjectTypeIds.EccNistP256ApplicationCertificateType || + CertificateType == ObjectTypeIds.EccNistP384ApplicationCertificateType) + { + return securityConfiguration.MinimumECCertificateKeySize; + } + else if ( + CertificateType == ObjectTypeIds.RsaMinApplicationCertificateType || + CertificateType == ObjectTypeIds.RsaSha256ApplicationCertificateType || + CertificateType == ObjectTypeIds.ApplicationCertificateType) + { + return securityConfiguration.MinimumCertificateKeySize; + } + + throw new ArgumentException("Certificate type is unknown"); + + } + + /// /// Get the OPC UA CertificateType. /// diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index cba8511bf..24e12788c 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -1785,13 +1785,13 @@ private static bool IsSignatureValid(X509Certificate2 cert) private static readonly Dictionary NamedCurveBitSizes = new Dictionary { // NIST Curves - { "1.2.840.10045.3.1.7", 256 }, // secp256r1 or prime256v1 or NIST P-256 - { "1.3.132.0.34", 384 }, // secp384r1 or NIST P-384 - { "1.3.132.0.35", 521 }, // secp521r1 or NIST P-521 + { ECCurve.NamedCurves.nistP256.Oid.Value, 256 }, // NIST P-256 + { ECCurve.NamedCurves.nistP384.Oid.Value, 384 }, // NIST P-384 + { ECCurve.NamedCurves.nistP521.Oid.Value, 521 }, // NIST P-521 // Brainpool Curves - { "1.3.36.3.3.2.8.1.1.7", 256 }, // BrainpoolP256r1 - { "1.3.36.3.3.2.8.1.1.11", 384 }, // BrainpoolP384r1 + { ECCurve.NamedCurves.brainpoolP256r1.Oid.Value, 256 }, // BrainpoolP256r1 + { ECCurve.NamedCurves.brainpoolP384r1.Oid.Value, 384 }, // BrainpoolP384r1 }; /// diff --git a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs index c5d7c4d69..a2549c017 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs @@ -2958,10 +2958,15 @@ public static bool IsSupportedCertificateType(NodeId certificateType) { #if ECC_SUPPORT case ObjectTypes.EccApplicationCertificateType: + return true; case ObjectTypes.EccBrainpoolP256r1ApplicationCertificateType: + return s_eccCurveSupportCache[ECCurve.NamedCurves.brainpoolP256r1.Oid.FriendlyName].Value; case ObjectTypes.EccBrainpoolP384r1ApplicationCertificateType: + return s_eccCurveSupportCache[ECCurve.NamedCurves.brainpoolP384r1.Oid.FriendlyName].Value; case ObjectTypes.EccNistP256ApplicationCertificateType: + return s_eccCurveSupportCache[ECCurve.NamedCurves.nistP256.Oid.FriendlyName].Value; case ObjectTypes.EccNistP384ApplicationCertificateType: + return s_eccCurveSupportCache[ECCurve.NamedCurves.nistP384.Oid.FriendlyName].Value; //case ObjectTypes.EccCurve25519ApplicationCertificateType: //case ObjectTypes.EccCurve448ApplicationCertificateType: #endif @@ -2975,6 +2980,40 @@ public static bool IsSupportedCertificateType(NodeId certificateType) return false; } + /// + /// Check if known curve is supported by platform + /// + /// + private static bool IsCurveSupported(ECCurve eCCurve) + { + try + { + // Create a ECDsa object and generate a new keypair on the given curve + using (ECDsa eCDsa = ECDsa.Create(eCCurve)) + { + ECParameters parameters = eCDsa.ExportParameters(false); + return parameters.Q.X != null && parameters.Q.Y != null; + } + } + catch (Exception ex) when ( + ex is PlatformNotSupportedException || + ex is ArgumentException || + ex is CryptographicException) + { + return false; + } + } + + /// + /// Lazy helper for checking ECC eliptic curve support for running OS + /// + private static readonly Dictionary> s_eccCurveSupportCache = new Dictionary>{ + { ECCurve.NamedCurves.nistP256.Oid.FriendlyName, new Lazy(() => IsCurveSupported(ECCurve.NamedCurves.nistP256)) }, + { ECCurve.NamedCurves.nistP384.Oid.FriendlyName, new Lazy(() => IsCurveSupported(ECCurve.NamedCurves.nistP384)) }, + { ECCurve.NamedCurves.brainpoolP256r1.Oid.FriendlyName, new Lazy(() => IsCurveSupported(ECCurve.NamedCurves.brainpoolP256r1)) }, + { ECCurve.NamedCurves.brainpoolP384r1.Oid.FriendlyName, new Lazy(() => IsCurveSupported(ECCurve.NamedCurves.brainpoolP384r1)) }, + }; + /// /// Lazy helper to allow runtime check for Mono. /// @@ -2990,6 +3029,6 @@ public static bool IsRunningOnMono() { return s_isRunningOnMonoValue.Value; } - #endregion +#endregion } } From 9a25264d186eb200fb07f1a859c8f4a39042da0b Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Wed, 4 Oct 2023 15:03:39 +0300 Subject: [PATCH 12/80] Keep and mark as obsolete ApplicationInstance methods for bacword compat --- .../ConsoleReferenceClient/Program.cs | 2 +- .../ConsoleReferenceServer/UAServer.cs | 2 +- Applications/ReferenceClient/Program.cs | 2 +- .../ApplicationInstance.cs | 30 ++++++++++++++++++- .../Certificates/CertificateIdentifier.cs | 10 +++---- .../Security/Constants/SecurityPolicies.cs | 25 ++++++++-------- Tests/Opc.Ua.Client.Tests/ClientFixture.cs | 2 +- .../ApplicationInstanceTests.cs | 22 +++++++------- .../GlobalDiscoveryTestClient.cs | 2 +- .../GlobalDiscoveryTestServer.cs | 2 +- .../ServerConfigurationPushTestClient.cs | 2 +- Tests/Opc.Ua.Server.Tests/ServerFixture.cs | 6 ++-- 12 files changed, 67 insertions(+), 40 deletions(-) diff --git a/Applications/ConsoleReferenceClient/Program.cs b/Applications/ConsoleReferenceClient/Program.cs index b5dc945ee..013f2fe94 100644 --- a/Applications/ConsoleReferenceClient/Program.cs +++ b/Applications/ConsoleReferenceClient/Program.cs @@ -160,7 +160,7 @@ public static async Task Main(string[] args) } // check the application certificate. - bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(false).ConfigureAwait(false); + bool haveAppCertificate = await application.CheckApplicationInstanceCertificates(false).ConfigureAwait(false); if (!haveAppCertificate) { throw new ErrorExitException("Application instance certificate invalid!", ExitCode.ErrorCertificate); diff --git a/Applications/ConsoleReferenceServer/UAServer.cs b/Applications/ConsoleReferenceServer/UAServer.cs index 3ca1aba62..9f8760989 100644 --- a/Applications/ConsoleReferenceServer/UAServer.cs +++ b/Applications/ConsoleReferenceServer/UAServer.cs @@ -101,7 +101,7 @@ public async Task CheckCertificateAsync(bool renewCertificate) } // check the application certificate. - bool haveAppCertificate = await m_application.CheckApplicationInstanceCertificate(false).ConfigureAwait(false); + bool haveAppCertificate = await m_application.CheckApplicationInstanceCertificates(false).ConfigureAwait(false); if (!haveAppCertificate) { throw new ErrorExitException("Application instance certificate invalid!"); diff --git a/Applications/ReferenceClient/Program.cs b/Applications/ReferenceClient/Program.cs index 6ad71ee39..8627ef9c4 100644 --- a/Applications/ReferenceClient/Program.cs +++ b/Applications/ReferenceClient/Program.cs @@ -60,7 +60,7 @@ static void Main() application.LoadApplicationConfiguration(false).Wait(); // check the application certificate. - var certOK = application.CheckApplicationInstanceCertificate(false, 0).Result; + var certOK = application.CheckApplicationInstanceCertificates(false).Result; if (!certOK) { throw new Exception("Application instance certificate invalid!"); diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index c6c6d31c8..a7f7f3fcc 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -393,10 +393,23 @@ string productUri /// Checks for a valid application instance certificate. /// /// if set to true no dialogs will be displayed. + /// Minimum size of the key. + [Obsolete("This method is obsolete since an application now supports different minKey sizes depending on certificate type")] public Task CheckApplicationInstanceCertificate( + bool silent, + ushort minimumKeySize) + { + return CheckApplicationInstanceCertificate(silent, minimumKeySize, CertificateFactory.DefaultLifeTime); + } + + /// + /// Checks for a valid application instance certificate. + /// + /// if set to true no dialogs will be displayed. + public Task CheckApplicationInstanceCertificates( bool silent) { - return CheckApplicationInstanceCertificate(silent, CertificateFactory.DefaultLifeTime); + return CheckApplicationInstanceCertificates(silent, CertificateFactory.DefaultLifeTime); } /// @@ -416,8 +429,23 @@ public async Task DeleteApplicationInstanceCertificate(string[] profileIds = nul /// Checks for a valid application instance certificate. /// /// if set to true no dialogs will be displayed. + /// Minimum size of the key. /// The lifetime in months. + [Obsolete("This method is obsolete since an application now supports different minKey sizes depending on certificate type")] public async Task CheckApplicationInstanceCertificate( + bool silent, + ushort minimumKeySize, + ushort lifeTimeInMonths) + { + return await CheckApplicationInstanceCertificates(silent, lifeTimeInMonths).ConfigureAwait(false); + } + + /// + /// Checks for a valid application instance certificate. + /// + /// if set to true no dialogs will be displayed. + /// The lifetime in months. + public async Task CheckApplicationInstanceCertificates( bool silent, ushort lifeTimeInMonths) { diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index 02e9d3645..2cda54b26 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -528,11 +528,12 @@ public ushort GetMinKeySize(SecurityConfiguration securityConfiguration) else if ( CertificateType == ObjectTypeIds.RsaMinApplicationCertificateType || CertificateType == ObjectTypeIds.RsaSha256ApplicationCertificateType || - CertificateType == ObjectTypeIds.ApplicationCertificateType) + CertificateType == ObjectTypeIds.ApplicationCertificateType || + securityConfiguration.IsDeprecatedConfiguration) // Deprecated configurations are implicitly RSA { return securityConfiguration.MinimumCertificateKeySize; } - + throw new ArgumentException("Certificate type is unknown"); } @@ -608,10 +609,7 @@ public static bool ValidateCertificateType(X509Certificate2 certificate, NodeId certificateType == ObjectTypeIds.RsaMinApplicationCertificateType || certificateType == ObjectTypeIds.ApplicationCertificateType) { - if (X509Utils.GetRSAPublicKeySize(certificate) >= CertificateFactory.DefaultKeySize) - { - return true; - } + return true; } break; } diff --git a/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs b/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs index 08cd3fff3..3cc526020 100644 --- a/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs +++ b/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs @@ -117,20 +117,21 @@ private static bool IsPlatformSupportedName(string name) #if ECC_SUPPORT // ECC policy - if (name.Equals(nameof(ECC_nistP256)) || - name.Equals(nameof(ECC_nistP384))) + if (name.Equals(nameof(ECC_nistP256))) { - return true; + return Utils.IsSupportedCertificateType(ObjectTypeIds.EccNistP256ApplicationCertificateType); } - - if (name.Equals(nameof(ECC_brainpoolP256r1)) || - name.Equals(nameof(ECC_brainpoolP384r1))) + if (name.Equals(nameof(ECC_nistP384))) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return false; - } - return true; + return Utils.IsSupportedCertificateType(ObjectTypeIds.EccNistP384ApplicationCertificateType); + } + if (name.Equals(nameof(ECC_brainpoolP256r1))) + { + return Utils.IsSupportedCertificateType(ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType); + } + if (name.Equals(nameof(ECC_brainpoolP384r1))) + { + return Utils.IsSupportedCertificateType(ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType); } // ECC policy @@ -626,6 +627,6 @@ public static bool Verify(X509Certificate2 certificate, string securityPolicyUri "Unexpected security policy Uri: {0}", securityPolicyUri); } - #endregion +#endregion } } diff --git a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs index 067df4e00..819b72d4f 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs @@ -106,7 +106,7 @@ public async Task LoadClientConfiguration(string pkiRoot = null, string clientNa .Create().ConfigureAwait(false); // check the application certificate. - bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool haveAppCertificate = await application.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); if (!haveAppCertificate) { throw new Exception("Application instance certificate invalid!"); diff --git a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs index d74cbc5c0..f2c3e2393 100644 --- a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs +++ b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs @@ -95,7 +95,7 @@ public async Task TestFileConfig() Assert.NotNull(configPath); ApplicationConfiguration applicationConfiguration = await applicationInstance.LoadApplicationConfiguration(configPath, true).ConfigureAwait(false); Assert.NotNull(applicationConfiguration); - bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool certOK = await applicationInstance.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); Assert.True(certOK); } @@ -117,7 +117,7 @@ public async Task TestNoFileConfigAsClient() .AddSecurityConfiguration(applicationCerts,m_pkiRoot) .Create().ConfigureAwait(false); Assert.NotNull(config); - bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool certOK = await applicationInstance.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); Assert.True(certOK); } @@ -246,7 +246,7 @@ public async Task TestNoFileConfigAsServerMinimal() .AddSecurityConfiguration(applicationCerts, m_pkiRoot) .Create().ConfigureAwait(false); Assert.NotNull(config); - bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool certOK = await applicationInstance.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); Assert.True(certOK); } @@ -288,7 +288,7 @@ public async Task TestNoFileConfigAsServerMaximal() .SetRejectUnknownRevocationStatus(true) .Create().ConfigureAwait(false); Assert.NotNull(config); - bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool certOK = await applicationInstance.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); Assert.True(certOK); } @@ -317,7 +317,7 @@ public async Task TestNoFileConfigAsClientAndServer() .AddSecurityConfiguration(applicationCerts, CertificateStoreType.Directory, CertificateStoreType.X509Store) .Create().ConfigureAwait(false); Assert.NotNull(config); - bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool certOK = await applicationInstance.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); Assert.True(certOK); } @@ -358,7 +358,7 @@ public async Task TestNoFileConfigAsServerX509Store() var applicationCertificate = applicationInstance.ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate; bool deleteAfterUse = applicationCertificate.Certificate != null; - bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool certOK = await applicationInstance.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); Assert.True(certOK); using (ICertificateStore store = applicationInstance.ApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.OpenStore()) { @@ -402,7 +402,7 @@ public async Task TestNoFileConfigAsServerCustom() .SetAddAppCertToTrustedStore(true) .Create().ConfigureAwait(false); Assert.NotNull(config); - bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool certOK = await applicationInstance.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); Assert.True(certOK); } @@ -480,7 +480,7 @@ public async Task TestInvalidAppCertDoNotRecreate(InvalidCertType certType, bool { if (suppress) { - bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0) + bool certOK = await applicationInstance.CheckApplicationInstanceCertificates(true) .ConfigureAwait(false); Assert.True(certOK); @@ -489,7 +489,7 @@ public async Task TestInvalidAppCertDoNotRecreate(InvalidCertType certType, bool else { var sre = Assert.ThrowsAsync(async () => - await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false)); + await applicationInstance.CheckApplicationInstanceCertificates(true).ConfigureAwait(false)); Assert.AreEqual(StatusCodes.BadConfigurationError, sre.StatusCode); } } @@ -573,7 +573,7 @@ public async Task TestInvalidAppCertChainDoNotRecreate(InvalidCertType certType, { if (suppress) { - bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0) + bool certOK = await applicationInstance.CheckApplicationInstanceCertificates(true) .ConfigureAwait(false); Assert.True(certOK); @@ -582,7 +582,7 @@ public async Task TestInvalidAppCertChainDoNotRecreate(InvalidCertType certType, else { var sre = Assert.ThrowsAsync(async () => - await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false)); + await applicationInstance.CheckApplicationInstanceCertificates(true).ConfigureAwait(false)); Assert.AreEqual(StatusCodes.BadConfigurationError, sre.StatusCode); } } diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs index 4eb9268b2..9b115e67d 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs @@ -99,7 +99,7 @@ public async Task LoadClientConfiguration(int port = -1) .Create().ConfigureAwait(false); #endif // check the application certificate. - bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool haveAppCertificate = await application.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); if (!haveAppCertificate) { throw new Exception("Application instance certificate invalid!"); diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs index fe8110956..f9b4f3bf1 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs @@ -81,7 +81,7 @@ public async Task StartServer(bool clean, int basePort = -1) } // check the application certificate. - bool haveAppCertificate = await Application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool haveAppCertificate = await Application.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); if (!haveAppCertificate) { throw new Exception("Application instance certificate invalid!"); diff --git a/Tests/Opc.Ua.Gds.Tests/ServerConfigurationPushTestClient.cs b/Tests/Opc.Ua.Gds.Tests/ServerConfigurationPushTestClient.cs index 52c100a00..fb9d937d0 100644 --- a/Tests/Opc.Ua.Gds.Tests/ServerConfigurationPushTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/ServerConfigurationPushTestClient.cs @@ -111,7 +111,7 @@ public async Task LoadClientConfiguration(int port = -1) .Create().ConfigureAwait(false); #endif // check the application certificate. - bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool haveAppCertificate = await application.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); if (!haveAppCertificate) { throw new Exception("Application instance certificate invalid!"); diff --git a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs index fa4c2e3ed..f1bfbd7b7 100644 --- a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs +++ b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs @@ -127,7 +127,7 @@ public async Task LoadConfiguration(string pkiRoot = null) Config = await serverConfig.AddSecurityConfiguration( applicationCerts, pkiRoot) - //.SetApplicationCertificates(applicationCerts) + .SetMinimumCertificateKeySize(1024) .SetAutoAcceptUntrustedCertificates(AutoAccept) .Create().ConfigureAwait(false); } @@ -199,8 +199,8 @@ private async Task InternalStartServerAsync(TextWriter writer, int port) } // check the application certificate. - bool haveAppCertificate = await Application.CheckApplicationInstanceCertificate( - true, CertificateFactory.DefaultKeySize, CertificateFactory.DefaultLifeTime).ConfigureAwait(false); + bool haveAppCertificate = await Application.CheckApplicationInstanceCertificates( + true, CertificateFactory.DefaultLifeTime).ConfigureAwait(false); if (!haveAppCertificate) { throw new Exception("Application instance certificate invalid!"); From a2e0a4b2b74ac598af503d428832213021de3e93 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Wed, 4 Oct 2023 17:31:31 +0300 Subject: [PATCH 13/80] Fix CfgManager UpdateCertificate() --- .../Configuration/ConfigurationNodeManager.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 8d32278b1..95356d19a 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -410,6 +410,15 @@ private ServiceResult UpdateCertificate( ServerCertificateGroup certificateGroup = VerifyGroupAndTypeId(certificateGroupId, certificateTypeId); certificateGroup.UpdateCertificate = null; + try + { + newCert = new X509Certificate2(certificate); + } + catch + { + throw new ServiceResultException(StatusCodes.BadCertificateInvalid, "Certificate data is invalid."); + } + // identify the existing certificate to be updated // it should be of the same type and same subject name as the new certificate CertificateIdentifier existingCertIdentifier = certificateGroup.ApplicationCertificates.FirstOrDefault(cert => @@ -437,7 +446,6 @@ private ServiceResult UpdateCertificate( } } - newCert = new X509Certificate2(certificate); } catch { @@ -467,6 +475,7 @@ private ServiceResult UpdateCertificate( { // verify cert with issuer chain CertificateValidator certValidator = new CertificateValidator(); + certValidator.MinimumCertificateKeySize = 1024; CertificateTrustList issuerStore = new CertificateTrustList(); CertificateIdentifierCollection issuerCollection = new CertificateIdentifierCollection(); foreach (var issuerCert in newIssuerCollection) From a0e4b0f69630191cb3b53dd9d63a1ae72896a076 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Mon, 9 Oct 2023 13:13:18 +0300 Subject: [PATCH 14/80] Added missing interface implementation --- Applications/ReferenceServer/Program.cs | 2 +- .../ApplicationConfigurationBuilder.cs | 63 +++++++++++++++++++ .../Stack/Https/HttpsServiceHost.cs | 29 ++++----- .../Certificates/CertificateValidator.cs | 46 ++++++++------ common.props | 2 +- 5 files changed, 107 insertions(+), 35 deletions(-) diff --git a/Applications/ReferenceServer/Program.cs b/Applications/ReferenceServer/Program.cs index 644cab145..a511d9007 100644 --- a/Applications/ReferenceServer/Program.cs +++ b/Applications/ReferenceServer/Program.cs @@ -66,7 +66,7 @@ static void Main() SerilogTraceLogger.Create(loggerConfiguration, config); // check the application certificate. - bool certOk = application.CheckApplicationInstanceCertificate(false, 0).Result; + bool certOk = application.CheckApplicationInstanceCertificates(false).Result; if (!certOk) { throw new Exception("Application instance certificate invalid!"); diff --git a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs index fd588b33a..0f6d225e5 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs @@ -216,6 +216,69 @@ public IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration( return this; } + /// + public IApplicationConfigurationBuilderSecurityOptionStores AddSecurityConfigurationStores( + string subjectName, + string appRoot, + string trustedRoot, + string issuerRoot, + string rejectedRoot = null + ) + { + string appStoreType = CertificateStoreIdentifier.DetermineStoreType(appRoot); + string issuerRootType = CertificateStoreIdentifier.DetermineStoreType(issuerRoot); + string trustedRootType = CertificateStoreIdentifier.DetermineStoreType(trustedRoot); + rejectedRoot = rejectedRoot ?? DefaultPKIRoot(null); + string rejectedRootType = CertificateStoreIdentifier.DetermineStoreType(rejectedRoot); + ApplicationConfiguration.SecurityConfiguration = new SecurityConfiguration { + // app cert store + #pragma warning disable CS0618 // Type or member is obsolete + ApplicationCertificate = new CertificateIdentifier() { + #pragma warning restore CS0618 // Type or member is obsolete + StoreType = appStoreType, + StorePath = DefaultCertificateStorePath(TrustlistType.Application, appRoot), + SubjectName = Utils.ReplaceDCLocalhost(subjectName) + }, + // App trusted & issuer + TrustedPeerCertificates = new CertificateTrustList() { + StoreType = trustedRootType, + StorePath = DefaultCertificateStorePath(TrustlistType.Trusted, trustedRoot) + }, + TrustedIssuerCertificates = new CertificateTrustList() { + StoreType = issuerRootType, + StorePath = DefaultCertificateStorePath(TrustlistType.Issuer, issuerRoot) + }, + // rejected store + RejectedCertificateStore = new CertificateTrustList() { + StoreType = rejectedRootType, + StorePath = DefaultCertificateStorePath(TrustlistType.Rejected, rejectedRoot) + }, + }; + SetSecureDefaults(ApplicationConfiguration.SecurityConfiguration); + + return this; + } + + /// + public IApplicationConfigurationBuilderSecurityOptionStores AddSecurityConfigurationUserStore( + string trustedRoot, + string issuerRoot + ) + { + string trustedRootType = CertificateStoreIdentifier.DetermineStoreType(trustedRoot); + string issuerRootType = CertificateStoreIdentifier.DetermineStoreType(issuerRoot); + + // User trusted & issuer + ApplicationConfiguration.SecurityConfiguration.TrustedUserCertificates = new CertificateTrustList() { + StoreType = trustedRootType, + StorePath = DefaultCertificateStorePath(TrustlistType.TrustedUser, trustedRoot) + }; + ApplicationConfiguration.SecurityConfiguration.UserIssuerCertificates = new CertificateTrustList() { + StoreType = issuerRootType, + StorePath = DefaultCertificateStorePath(TrustlistType.IssuerUser, issuerRoot) + }; + return this; + } /// public IApplicationConfigurationBuilderSecurityOptionStores AddSecurityConfigurationHttpsStore( string trustedRoot, diff --git a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsServiceHost.cs b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsServiceHost.cs index 7350e4395..9ed4976fc 100644 --- a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsServiceHost.cs +++ b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsServiceHost.cs @@ -106,17 +106,17 @@ CertificateTypesProvider certificateTypesProvider description.EndpointUrl = uri.ToString(); description.Server = serverDescription; - if (certificateTypesProvider != null) + if (certificateTypesProvider != null) + { + var instanceCertificate = certificateTypesProvider.GetInstanceCertificate(bestPolicy.SecurityPolicyUri); + description.ServerCertificate = instanceCertificate.RawData; + + // check if complete chain should be sent. + if (configuration.SecurityConfiguration.SendCertificateChain) { - var instanceCertificate = certificateTypesProvider.GetInstanceCertificate(bestPolicy.SecurityPolicyUri); - description.ServerCertificate = instanceCertificate.RawData; - - // check if complete chain should be sent. - if (configuration.SecurityConfiguration.SendCertificateChain) - { - description.ServerCertificate = certificateTypesProvider.LoadCertificateChainRawAsync(instanceCertificate).GetAwaiter().GetResult(); - } + description.ServerCertificate = certificateTypesProvider.LoadCertificateChainRawAsync(instanceCertificate).GetAwaiter().GetResult(); } + } description.SecurityMode = bestPolicy.SecurityMode; description.SecurityPolicyUri = bestPolicy.SecurityPolicyUri; @@ -137,11 +137,12 @@ CertificateTypesProvider certificateTypesProvider } } - // create the host. - hosts[hostName] = serverBase.CreateServiceHost(serverBase, uris.ToArray()); - } + // create the host. + hosts[hostName] = serverBase.CreateServiceHost(serverBase, uris.ToArray()); + return endpoints; - } - } + + } + } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 24e12788c..a70e88d3b 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -222,29 +222,30 @@ public virtual async Task Update(SecurityConfiguration configuration) { m_useValidatedCertificates = configuration.UseValidatedCertificates; } - } - finally - { - m_semaphore.Release(); - } - if (configuration.ApplicationCertificates != null) - { - foreach (var applicationCertificate in configuration.ApplicationCertificates) + if (configuration.ApplicationCertificates != null) { - X509Certificate2 certificate = await applicationCertificate.Find(true).ConfigureAwait(false); - if (certificate == null) + foreach (var applicationCertificate in configuration.ApplicationCertificates) { - Utils.Trace(Utils.TraceMasks.Security, "Could not find application certificate: {0}", applicationCertificate); - continue; - } - // Add to list of application certificates only if not allready in list - // necessary since the application certificates may be updated multiple times - if (!m_applicationCertificates.Exists(cert => Utils.IsEqual(cert.RawData, certificate.RawData))) - { - m_applicationCertificates.Add(certificate); + X509Certificate2 certificate = await applicationCertificate.Find(true).ConfigureAwait(false); + if (certificate == null) + { + Utils.Trace(Utils.TraceMasks.Security, "Could not find application certificate: {0}", applicationCertificate); + continue; + } + // Add to list of application certificates only if not allready in list + // necessary since the application certificates may be updated multiple times + if (!m_applicationCertificates.Exists(cert => Utils.IsEqual(cert.RawData, certificate.RawData))) + { + m_applicationCertificates.Add(certificate); + } } } + + } + finally + { + m_semaphore.Release(); } } @@ -428,14 +429,21 @@ public ushort MinimumECCertificateKeySize get => m_minimumECCertificateKeySize; set { - lock (m_lock) + try { + m_semaphore.Wait(); + m_protectFlags |= ProtectFlags.MinimumECCertificateKeySize; if (m_minimumECCertificateKeySize != value) { m_minimumECCertificateKeySize = value; ResetValidatedCertificates(); } + + } + finally + { + m_semaphore.Release(); } } } diff --git a/common.props b/common.props index 792d7165d..65a8a5a64 100644 --- a/common.props +++ b/common.props @@ -7,7 +7,7 @@ Copyright © 2004-2023 OPC Foundation, Inc OPC Foundation OPC Foundation - NU5125;CA2254;CA1307 + NU5125;CS1594;CS1591;NETSDK1138;CA2254;CA1307 en-US true false From 97b4c878edc2c328315f9db4a90241f6f08d7e01 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Mon, 9 Oct 2023 15:28:49 +0300 Subject: [PATCH 15/80] Added CertificateTypeString to improve visual appearance --- .../Quickstarts.ReferenceServer.Config.xml | 13 +++++-------- .../Opc.Ua.Core/Schema/ApplicationConfiguration.cs | 13 ++++++++++--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml index 913de49f3..2c4d6831d 100644 --- a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml +++ b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml @@ -11,44 +11,41 @@ Server_0 - Directory %LocalApplicationData%/OPC Foundation/pki/own CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost - - i=12560 + RsaSha256 Directory %LocalApplicationData%/OPC Foundation/pki/own CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost - - i=23538 + NistP256 Directory %LocalApplicationData%/OPC Foundation/pki/own CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost - i=23539 + NistP384 Directory %LocalApplicationData%/OPC Foundation/pki/own CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost - i=23540 + BrainpoolP256r1 Directory %LocalApplicationData%/OPC Foundation/pki/own CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost - i=23541 + BrainpoolP384r1 diff --git a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs index cc1d64d77..a91f13397 100644 --- a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs +++ b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs @@ -3314,10 +3314,17 @@ public NodeId CertificateType { get => m_certificateType; set => m_certificateType = value; + } - // get { return EncodeCertificateType(m_certificateType); } - // set { m_certificateType = DecodeCertificateType (value); } - + /// + /// The string representation of the certificate + /// + /// Rsa, RsaMin, RsaSha256, NistP256, NistP384, BrainpoolP256r1, BrainpoolP384r1, Curve25519, Curve448 + [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 90)] + public string CertificateTypeString + { + get => EncodeCertificateType(m_certificateType); + set => m_certificateType = DecodeCertificateType(value); } #endregion From 9efe4a72630f5ea7e81dd4dd1a962d8cd837bc09 Mon Sep 17 00:00:00 2001 From: mirceasu Date: Tue, 10 Oct 2023 16:07:02 +0300 Subject: [PATCH 16/80] Commented ECCUtils code --- .../Security/Certificates/EccUtils.cs | 146 +++++++++++++++++- .../Security/Certificates/Nonce.cs | 55 ++++++- 2 files changed, 198 insertions(+), 3 deletions(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs index 2b89a1e79..f350caf60 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs @@ -39,16 +39,45 @@ namespace Opc.Ua /// public static class EccUtils { + #region Public constants + + + /// + /// The name of the NIST P-256 curve. + /// public const string NistP256 = nameof(NistP256); + /// + /// The name of the NIST P-384 curve. + /// public const string NistP384 = nameof(NistP384); + /// + /// The name of the BrainpoolP256r1 curve. + /// public const string BrainpoolP256r1 = nameof(BrainpoolP256r1); + /// + /// The name of the BrainpoolP384r1 curve. + /// public const string BrainpoolP384r1 = nameof(BrainpoolP384r1); + #endregion + + #region Private constants + + private const string NistP256KeyParameters = "06-08-2A-86-48-CE-3D-03-01-07"; private const string NistP384KeyParameters = "06-05-2B-81-04-00-22"; private const string BrainpoolP256r1KeyParameters = "06-09-2B-24-03-03-02-08-01-01-07"; private const string BrainpoolP384r1KeyParameters = "06-09-2B-24-03-03-02-08-01-01-0B"; + #endregion + + + + /// + /// Returns true if the certificate is an ECC certificate. + /// + /// + /// public static bool IsEccPolicy(string securityPolicyUri) { if (securityPolicyUri != null) @@ -70,6 +99,11 @@ public static bool IsEccPolicy(string securityPolicyUri) return false; } + /// + /// Returns the NodeId for the certificate type for the specified certificate. + /// + /// + /// public static NodeId GetEccCertificateTypeId(X509Certificate2 certificate) { var keyAlgorithm = certificate.GetKeyAlgorithm(); @@ -94,6 +128,11 @@ public static NodeId GetEccCertificateTypeId(X509Certificate2 certificate) } } + /// + /// Returns the signature algorithm for the specified certificate. + /// + /// + /// public static string GetECDsaQualifier(X509Certificate2 certificate) { if (X509Utils.IsECDsaSignature(certificate)) @@ -137,12 +176,23 @@ public static string GetECDsaQualifier(X509Certificate2 certificate) } #if ECC_SUPPORT + /// + /// Returns the public key for the specified certificate. + /// + /// + /// public static ECDsa GetPublicKey(X509Certificate2 certificate) { string[] securityPolicyUris; return GetPublicKey(certificate, out securityPolicyUris); } + /// + /// Returns the public key for the specified certificate and ouputs the security policy uris. + /// + /// + /// + /// public static ECDsa GetPublicKey(X509Certificate2 certificate, out string[] securityPolicyUris) { securityPolicyUris = null; @@ -259,9 +309,10 @@ public static int GetSignatureLength(X509Certificate2 signingCertificate) } /// - /// + /// Returns the hash algorithm for the specified security policy. /// /// + /// public static HashAlgorithmName GetSignatureAlgorithmName(string securityPolicyUri) { if (securityPolicyUri == null) @@ -444,24 +495,60 @@ public static bool Verify( } } + /// + /// Utility class for encrypting and decrypting secrets using Elliptic Curve Cryptography (ECC). + /// public class EncryptedSecret { + /// + /// Gets or sets the X.509 certificate of the sender. + /// public X509Certificate2 SenderCertificate { get; set; } + /// + /// Gets or sets the collection of X.509 certificates of the sender's issuer. + /// public X509Certificate2Collection SenderIssuerCertificates { get; set; } + /// + /// Gets or sets a value indicating whether the sender's certificate should not be encoded. + /// public bool DoNotEncodeSenderCertificate { get; set; } + /// + /// Gets or sets the nonce of the sender. + /// public Nonce SenderNonce { get; set; } + /// + /// Gets or sets the nonce of the receiver. + /// public Nonce ReceiverNonce { get; set; } + /// + /// Gets or sets the X.509 certificate of the receiver. + /// public X509Certificate2 ReceiverCertificate { get; set; } + /// + /// Gets or sets the certificate validator. + /// public CertificateValidator Validator { get; set; } + /// + /// Gets or sets the security policy URI. + /// public string SecurityPolicyUri { get; set; } + + /// + /// Encrypts a secret using the specified nonce, encrypting key, and initialization vector (IV). + /// + /// The secret to encrypt. + /// The nonce to use for encryption. + /// The key to use for encryption. + /// The initialization vector to use for encryption. + /// The encrypted secret. private static byte[] EncryptSecret( byte[] secret, byte[] nonce, @@ -529,6 +616,13 @@ private static byte[] EncryptSecret( } #if CURVE25519 + /// + /// Encrypts the given data using the ChaCha20Poly1305 algorithm with the provided key and initialization vector (IV). + /// + /// The key used for encryption. + /// The initialization vector used for encryption. + /// The data to be encrypted. + /// The encrypted data. private static byte[] EncryptWithChaCha20Poly1305( byte[] encryptingKey, byte[] iv, @@ -562,6 +656,16 @@ private static byte[] EncryptWithChaCha20Poly1305( return ciphertext; } + /// + /// Decrypts the given data using the ChaCha20Poly1305 algorithm with the provided key and initialization vector (IV). + /// + /// The key used for encryption. + /// The initialization vector used for encryption. + /// The data to be decrypted. + /// The offset in the data to start decrypting from. + /// The number of bytes to decrypt. + /// An containing the decrypted data. + /// Thrown if the plaintext is not the expected size or too short, or if the nonce is invalid. private ArraySegment DecryptWithChaCha20Poly1305( byte[] encryptingKey, byte[] iv, @@ -621,9 +725,17 @@ private ArraySegment DecryptWithChaCha20Poly1305( } #endif + /// - /// + /// Decrypts the specified data using the provided encrypting key and initialization vector (IV). /// + /// The data to decrypt. + /// The offset in the data to start decrypting from. + /// The number of bytes to decrypt. + /// The key to use for decryption. + /// The initialization vector to use for decryption. + /// The decrypted data. + /// Thrown if the input data is not an even number of encryption blocks or if the nonce is invalid. private static ArraySegment DecryptSecret( byte[] dataToDecrypt, int offset, @@ -691,6 +803,15 @@ private static ArraySegment DecryptSecret( private static readonly byte[] s_Label = new UTF8Encoding().GetBytes("opcua-secret"); + /// + /// Creates the encrypting key and initialization vector (IV) for Elliptic Curve Cryptography (ECC) encryption or decryption. + /// + /// The security policy URI. + /// The sender nonce. + /// The receiver nonce. + /// if set to true, creates the keys for decryption; otherwise, creates the keys for encryption. + /// The encrypting key. + /// The initialization vector (IV). private static void CreateKeysForEcc( string securityPolicyUri, Nonce senderNonce, @@ -751,6 +872,12 @@ private static void CreateKeysForEcc( Buffer.BlockCopy(keyData, encryptingKeySize, iv, 0, iv.Length); } + /// + /// Encrypts a secret using the specified nonce. + /// + /// The secret to encrypt. + /// The nonce to use for encryption. + /// The encrypted secret. public byte[] Encrypt(byte[] secret, byte[] nonce) { byte[] encryptingKey = null; @@ -862,6 +989,12 @@ public byte[] Encrypt(byte[] secret, byte[] nonce) return message; } + /// + /// Verifies the header for an ECC encrypted message and returns the encrypted data. + /// + /// The data to decrypt. + /// The earliest time allowed for the message signing time. + /// The encrypted data. private ArraySegment VerifyHeaderForEcc( ArraySegment dataToDecrypt, DateTime earliestTime) @@ -993,6 +1126,15 @@ private ArraySegment VerifyHeaderForEcc( } } + /// + /// Decrypts the specified data using the ECC algorithm. + /// + /// The earliest time allowed for the message. + /// The expected nonce value. + /// The data to decrypt. + /// The offset of the data to decrypt. + /// The number of bytes to decrypt. + /// The decrypted data. public byte[] Decrypt(DateTime earliestTime, byte[] expectedNonce, byte[] data, int offset, int count) { byte[] encryptingKey = null; diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs index dc05df80c..0c125ec6c 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs @@ -34,8 +34,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { + /// - /// + /// Represents a cryptographic nonce used for secure communication. /// public class Nonce : IDisposable { @@ -101,8 +102,19 @@ protected virtual void Dispose(bool disposing) } #endregion + /// + /// Gets the nonce data. + /// public byte[] Data { get; private set; } + /// + /// Derives a key from the remote nonce, using the specified salt, hash algorithm, and length. + /// + /// The remote nonce to use in key derivation. + /// The salt to use in key derivation. + /// The hash algorithm to use in key derivation. + /// The length of the derived key. + /// The derived key. public byte[] DeriveKey(Nonce remoteNonce, byte[] salt, HashAlgorithmName algorithm, int length) { #if CURVE25519 @@ -190,6 +202,12 @@ public byte[] DeriveKey(Nonce remoteNonce, byte[] salt, HashAlgorithmName algori return Data; } + /// + /// Creates a nonce for the specified security policy URI and nonce length. + /// + /// The security policy URI. + /// The length of the nonce. + /// A object containing the generated nonce. public static Nonce CreateNonce(string securityPolicyUri, uint nonceLength) { if (securityPolicyUri == null) @@ -227,6 +245,10 @@ public static Nonce CreateNonce(string securityPolicyUri, uint nonceLength) } } #if CURVE25519 + /// + /// Creates a new Nonce object to be used in Curve25519 cryptography. + /// + /// A new Nonce object. private static Nonce CreateNonceForCurve25519() { SecureRandom random = new SecureRandom(); @@ -246,6 +268,10 @@ private static Nonce CreateNonceForCurve25519() return nonce; } + /// + /// Creates a Nonce object using the X448 elliptic curve algorithm. + /// + /// A Nonce object containing the generated nonce data and key pair. private static Nonce CreateNonceForCurve448() { SecureRandom random = new SecureRandom(); @@ -266,6 +292,11 @@ private static Nonce CreateNonceForCurve448() } #endif + /// + /// Creates a new Nonce instance using the specified elliptic curve. + /// + /// The elliptic curve to use for the ECDH key exchange. + /// A new Nonce instance. private static Nonce CreateNonce(ECCurve curve) { var ecdh = (ECDiffieHellman)ECDiffieHellman.Create(curve); @@ -284,6 +315,12 @@ private static Nonce CreateNonce(ECCurve curve) return nonce; } + /// + /// Creates a new Nonce object for the specified security policy URI and nonce data. + /// + /// The security policy URI. + /// The nonce data. + /// A new Nonce object. public static Nonce CreateNonce(string securityPolicyUri, byte[] nonceData) { if (securityPolicyUri == null) @@ -327,6 +364,11 @@ public static Nonce CreateNonce(string securityPolicyUri, byte[] nonceData) } + /// + /// Creates a new Nonce object for use with Curve25519. + /// + /// The nonce data to use. + /// A new Nonce object. private static Nonce CreateNonceForCurve25519(byte[] nonceData) { var nonce = new Nonce() { @@ -336,6 +378,11 @@ private static Nonce CreateNonceForCurve25519(byte[] nonceData) return nonce; } + /// + /// Creates a new Nonce instance for Curve448. + /// + /// The nonce data. + /// A new Nonce instance. private static Nonce CreateNonceForCurve448(byte[] nonceData) { var nonce = new Nonce() { @@ -345,6 +392,12 @@ private static Nonce CreateNonceForCurve448(byte[] nonceData) return nonce; } + /// + /// Creates a new Nonce instance with the specified curve and nonce data. + /// + /// The elliptic curve to use for the ECDH key exchange. + /// The nonce data to use for the ECDH key exchange. + /// A new Nonce instance with the specified curve and nonce data. private static Nonce CreateNonce(ECCurve curve, byte[] nonceData) { Nonce nonce = new Nonce() { From a01d27681a06aab59039958404cbd2b6479d05a9 Mon Sep 17 00:00:00 2001 From: mirceasu Date: Tue, 10 Oct 2023 17:29:39 +0300 Subject: [PATCH 17/80] Corrected ConsoleRefClient Configuration --- .../Quickstarts.ReferenceClient.Config.xml | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/Applications/ConsoleReferenceClient/Quickstarts.ReferenceClient.Config.xml b/Applications/ConsoleReferenceClient/Quickstarts.ReferenceClient.Config.xml index 7010762c7..758fdbf62 100644 --- a/Applications/ConsoleReferenceClient/Quickstarts.ReferenceClient.Config.xml +++ b/Applications/ConsoleReferenceClient/Quickstarts.ReferenceClient.Config.xml @@ -12,14 +12,43 @@ - - Directory - %LocalApplicationData%/OPC Foundation/pki/own - CN=Console Reference Client, C=US, S=Arizona, O=OPC Foundation, DC=localhost - + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + CN=Quickstart Reference Client, C=US, S=Arizona, O=OPC Foundation, DC=localhost + RsaSha256 + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + CN=Quickstart Reference Client, C=US, S=Arizona, O=OPC Foundation, DC=localhost + NistP256 + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + CN=Quickstart Reference client, C=US, S=Arizona, O=OPC Foundation, DC=localhost + NistP384 + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + CN=Quickstart Reference Client, C=US, S=Arizona, O=OPC Foundation, DC=localhost + BrainpoolP256r1 + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + CN=Quickstart Reference Client, C=US, S=Arizona, O=OPC Foundation, DC=localhost + BrainpoolP384r1 + + - Rsa,NistP256,NistP384,BrainpoolP256r1,BrainpoolP384r1 - Directory From f165dee077395b00979389cac1784d8df3464bd5 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Wed, 11 Oct 2023 15:13:21 +0300 Subject: [PATCH 18/80] net48 has null Oid values for Brainpool curves --- .../Security/Certificates/CertificateValidator.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index a70e88d3b..89b5dc5ca 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -1793,13 +1793,13 @@ private static bool IsSignatureValid(X509Certificate2 cert) private static readonly Dictionary NamedCurveBitSizes = new Dictionary { // NIST Curves - { ECCurve.NamedCurves.nistP256.Oid.Value, 256 }, // NIST P-256 - { ECCurve.NamedCurves.nistP384.Oid.Value, 384 }, // NIST P-384 - { ECCurve.NamedCurves.nistP521.Oid.Value, 521 }, // NIST P-521 + { ECCurve.NamedCurves.nistP256.Oid.Value ?? "1.2.840.10045.3.1.7", 256 }, // NIST P-256 + { ECCurve.NamedCurves.nistP384.Oid.Value ?? "1.3.132.0.34" , 384 }, // NIST P-384 + { ECCurve.NamedCurves.nistP521.Oid.Value ?? "1.3.132.0.35" , 521 }, // NIST P-521 // Brainpool Curves - { ECCurve.NamedCurves.brainpoolP256r1.Oid.Value, 256 }, // BrainpoolP256r1 - { ECCurve.NamedCurves.brainpoolP384r1.Oid.Value, 384 }, // BrainpoolP384r1 + { ECCurve.NamedCurves.brainpoolP256r1.Oid.Value ?? "1.3.36.3.3.2.8.1.1.7", 256 }, // BrainpoolP256r1 + { ECCurve.NamedCurves.brainpoolP384r1.Oid.Value ?? "1.3.36.3.3.2.8.1.1.11", 384 }, // BrainpoolP384r1 }; /// From 58f583b40ec2f171cdc3ef3c36b68fde5ed48a9f Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Wed, 11 Oct 2023 15:27:32 +0300 Subject: [PATCH 19/80] Updated tests to new API --- .../Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs | 2 +- .../Opc.Ua.Configuration.Tests/CertificateStoreTypeTest.cs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs index f2c3e2393..31443b47c 100644 --- a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs +++ b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs @@ -398,7 +398,7 @@ public async Task TestNoFileConfigAsServerCustom() ApplicationConfiguration config = await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { EndpointUrl, "opc.https://localhost:51001" }, new string[] { "opc.tcp://192.168.1.100:51000" }) - .AddSecurityConfiguration(SubjectName, m_pkiRoot) + .AddSecurityConfiguration(applicationCerts, m_pkiRoot) .SetAddAppCertToTrustedStore(true) .Create().ConfigureAwait(false); Assert.NotNull(config); diff --git a/Tests/Opc.Ua.Configuration.Tests/CertificateStoreTypeTest.cs b/Tests/Opc.Ua.Configuration.Tests/CertificateStoreTypeTest.cs index f58f13b39..d9a2612c1 100644 --- a/Tests/Opc.Ua.Configuration.Tests/CertificateStoreTypeTest.cs +++ b/Tests/Opc.Ua.Configuration.Tests/CertificateStoreTypeTest.cs @@ -95,7 +95,7 @@ public async Task CertifcateStoreTypeNoConfigTest() // patch custom stores before creating the config ApplicationConfiguration appConfig = await appConfigBuilder.Create().ConfigureAwait(false); - bool certOK = await application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool certOK = await application.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); Assert.True(certOK); int instancesCreatedWhileLoadingConfig = TestCertStore.InstancesCreated; @@ -237,9 +237,14 @@ public Task IsRevoked(X509Certificate2 issuer, X509Certificate2 cert public bool SupportsLoadPrivateKey => m_innerStore.SupportsLoadPrivateKey; /// + public Task LoadPrivateKey(string thumbprint, string subjectName, NodeId certificateType, string password) + => m_innerStore.LoadPrivateKey(thumbprint, subjectName, certificateType, password); + + [Obsolete] public Task LoadPrivateKey(string thumbprint, string subjectName, string password) => m_innerStore.LoadPrivateKey(thumbprint, subjectName, password); + public static int InstancesCreated => s_instancesCreated; #region Private Members From 453ad95fa8c9fb4dee2c754f00a72628eebb698d Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 12 Oct 2023 17:37:54 +0300 Subject: [PATCH 20/80] Default certificate type for "old" style configuration is RsaSha256ApplicationCertificateType --- Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs index a91f13397..5a72e96d3 100644 --- a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs +++ b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs @@ -832,9 +832,8 @@ public CertificateIdentifier ApplicationCertificate } SupportedSecurityPolicies = BuildSupportedSecurityPolicies(); - + m_applicationCertificates[0].CertificateType = ObjectTypeIds.RsaSha256ApplicationCertificateType; m_isDeprecatedConfiguration = true; - } } From 11f30857ac2f2ba202f2d8d31dfb2b56135ec087 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 12 Oct 2023 17:39:40 +0300 Subject: [PATCH 21/80] SemaphoreSlim is not reentrant => removed from ResetValidatedCertificates --- .../Security/Certificates/CertificateValidator.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 89b5dc5ca..e673ae0e5 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -261,6 +261,7 @@ public virtual async Task UpdateCertificate(SecurityConfiguration securityConfig foreach (var applicationCertificate in securityConfiguration.ApplicationCertificates) { + m_applicationCertificates.RemoveAll(cert => Utils.IsEqual(cert.RawData, applicationCertificate.RawData)); applicationCertificate.DisposeCertificate(); } @@ -290,17 +291,14 @@ await applicationCertificate.LoadPrivateKeyEx( /// /// Reset the list of validated certificates. /// - public void ResetValidatedCertificates() + private void ResetValidatedCertificates() { try { - m_semaphore.Wait(); - InternalResetValidatedCertificates(); } finally { - m_semaphore.Release(); } } From 48cce5cb4551f29a052c63608533cb913c931fa7 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Mon, 16 Oct 2023 18:40:58 +0300 Subject: [PATCH 22/80] Fixed loading of disposed cached certificates --- .../Opc.Ua.Core/Security/Certificates/CertificateFactory.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs index 5514f628e..e378b9811 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs @@ -98,7 +98,11 @@ public static X509Certificate2 Load(X509Certificate2 certificate, bool ensurePri // check for existing cached certificate. if (m_certificates.TryGetValue(certificate.Thumbprint, out cachedCertificate)) { - return cachedCertificate; + // cached certificate might be disposed, if so do not return but try to update value in the cache + if (cachedCertificate.Handle != IntPtr.Zero) + { + return cachedCertificate; + } } // nothing more to do if no private key or dont care about accessibility. From 1ada020511e993383865e1d4c329befd684fe115 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 20 Oct 2023 15:32:21 +0300 Subject: [PATCH 23/80] Modified projects to correctly use ECC_SUPPORT flag --- .../Opc.Ua.Configuration.csproj | 2 +- .../Opc.Ua.Security.Certificates.csproj | 4 ---- Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj | 16 ++++++++++++++++ Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 2 +- .../Opc.Ua.Security.Certificates.Tests.csproj | 4 ++-- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj index d24b37168..baeb2f8cc 100644 --- a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj +++ b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj @@ -20,7 +20,7 @@ $(DefineConstants);ECC_SUPPORT - + $(DefineConstants);ECC_SUPPORT diff --git a/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj b/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj index aa00dd167..816926d20 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj +++ b/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj @@ -19,10 +19,6 @@ $(DefineConstants);ECC_SUPPORT - - $(DefineConstants);ECC_SUPPORT - - $(DefineConstants);ECC_SUPPORT diff --git a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj index a2cc79d83..9e8854235 100644 --- a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj +++ b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj @@ -20,7 +20,23 @@ $(DefineConstants);SIGNASSEMBLY + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index 758618e31..291915058 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -15,7 +15,7 @@ $(DefineConstants);ECC_SUPPORT - + $(DefineConstants);ECC_SUPPORT diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj b/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj index 45f0d6d3b..35ea5bda2 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj +++ b/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj @@ -1,4 +1,4 @@ - + Exe @@ -11,7 +11,7 @@ $(DefineConstants);ECC_SUPPORT - + $(DefineConstants);ECC_SUPPORT From c7ebfe14e8c7927b0db560844ab8f35b156d9b72 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 20 Oct 2023 16:26:37 +0300 Subject: [PATCH 24/80] Handle EphemerousKey from Server side --- .../Opc.Ua.Server/Server/StandardServer.cs | 71 +++++++++++++++---- Libraries/Opc.Ua.Server/Session/Session.cs | 13 ++++ .../Security/Certificates/EccUtils.cs | 22 +++++- 3 files changed, 92 insertions(+), 14 deletions(-) diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs index 6ce37d967..ac940ed95 100644 --- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs +++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs @@ -502,7 +502,13 @@ public override ResponseHeader CreateSession( // report audit for successful create session ServerInternal.ReportAuditCreateSessionEvent(context?.AuditEntryId, session, revisedSessionTimeout); - return CreateResponse(requestHeader, StatusCodes.Good); + ResponseHeader responseHeader = CreateResponse(requestHeader, StatusCodes.Good); + +#if ECC_SUPPORT + GenerateEphemeralKey(context?.SecurityPolicyUri, session, instanceCertificate, responseHeader); +#endif + + return responseHeader; } catch (ServiceResultException e) { @@ -536,6 +542,37 @@ public override ResponseHeader CreateSession( } } +#if ECC_SUPPORT + /// + /// Generate a new ephemeral key to be used in UserIdentityToken encryption, + /// add it to the ResponseHeader.AdditionalHeader field and save it to the server session + /// This is to be used only within encryption using ECC security policies + /// + /// + /// + /// + /// + private static void GenerateEphemeralKey(string securityPolicyUri, Session session, X509Certificate2 instanceCertificate, ResponseHeader responseHeader) + { + if (EccUtils.IsEccPolicy(securityPolicyUri)) + { + EphemeralKeyType ephemeralKey = EccUtils.GenerateEphemeralKey(securityPolicyUri, instanceCertificate); + + // Add the ephemeralKey to the AdditionalHeader of the response header + KeyValuePairCollection additionalHeaderKeyValues = new KeyValuePairCollection(); + additionalHeaderKeyValues.Add(new KeyValuePair() { Key = securityPolicyUri, Value = new Variant(ephemeralKey) }); + + AdditionalParametersType additionalParams = new AdditionalParametersType(); + additionalParams.Parameters = additionalHeaderKeyValues; + + responseHeader.AdditionalHeader = new ExtensionObject(additionalParams); + + session.EphemeralKey = ephemeralKey; + } + } +#endif + + /// /// Invokes the ActivateSession service. /// @@ -649,7 +686,15 @@ public override ResponseHeader ActivateSession( Session session = ServerInternal.SessionManager.GetSession(requestHeader.AuthenticationToken); ServerInternal.ReportAuditActivateSessionEvent(context?.AuditEntryId, session, softwareCertificates); - return CreateResponse(requestHeader, StatusCodes.Good); + ResponseHeader responseHeader = CreateResponse(requestHeader, StatusCodes.Good); + +#if ECC_SUPPORT + // load the certificate for the security profile + X509Certificate2 instanceCertificate = InstanceCertificateTypesProvider.GetInstanceCertificate(context.SecurityPolicyUri); + + GenerateEphemeralKey(context?.SecurityPolicyUri, session, instanceCertificate, responseHeader); +#endif + return responseHeader; } catch (ServiceResultException e) { @@ -2197,9 +2242,9 @@ public override ResponseHeader Call( OnRequestComplete(context); } } - #endregion +#endregion - #region Public Methods used by the Host Process +#region Public Methods used by the Host Process /// /// The state object associated with the server. /// It provides the shared components for the Server. @@ -2449,9 +2494,9 @@ private void OnRegisterServer(object state) Utils.LogError(e, "Unexpected exception handling registration timer."); } } - #endregion +#endregion - #region Protected Members used for Request Processing +#region Protected Members used for Request Processing /// /// The synchronization object. /// @@ -2707,9 +2752,9 @@ protected virtual void OnRequestComplete(OperationContext context) m_serverInternal.RequestManager.RequestCompleted(context); } } - #endregion +#endregion - #region Protected Members used for Initialization +#region Protected Members used for Initialization /// /// Raised when the configuration changes. /// @@ -3330,13 +3375,13 @@ public virtual void RemoveNodeManager(INodeManagerFactory nodeManagerFactory) { m_nodeManagerFactories.Remove(nodeManagerFactory); } - #endregion +#endregion - #region Private Properties +#region Private Properties private OperationLimitsState OperationLimits => ServerInternal.ServerObject.ServerCapabilities.OperationLimits; - #endregion +#endregion - #region Private Fields +#region Private Fields private readonly object m_lock = new object(); private readonly object m_registrationLock = new object(); private ServerInternalData m_serverInternal; @@ -3350,6 +3395,6 @@ public virtual void RemoveNodeManager(INodeManagerFactory nodeManagerFactory) private int m_minNonceLength; private bool m_useRegisterServer2; private IList m_nodeManagerFactories; - #endregion +#endregion } } diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs index 541d0b886..4dc77e9db 100644 --- a/Libraries/Opc.Ua.Server/Session/Session.cs +++ b/Libraries/Opc.Ua.Server/Session/Session.cs @@ -379,6 +379,17 @@ public string SecureChannelId } } + /// + /// The Server generated ephemeral key + /// + public EphemeralKeyType EphemeralKey + { + set + { + m_ephemeralKey = value; + } + } + /// /// Validates the request. /// @@ -1176,6 +1187,8 @@ private void UpdateDiagnosticCounters(RequestType requestType, bool error, bool private SessionSecurityDiagnosticsDataType m_securityDiagnostics; private List m_browseContinuationPoints; private List m_historyContinuationPoints; + + private EphemeralKeyType m_ephemeralKey; #endregion } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs index f350caf60..649561e19 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs @@ -493,6 +493,27 @@ public static bool Verify( return ecdsa.VerifyData(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count, signature, algorithm); } } + + /// + /// Generates an EphemeralKey and signes it with the provided certificate + /// + /// + /// + /// The ephemeral key + public static EphemeralKeyType GenerateEphemeralKey(string securityPolicyUri, X509Certificate2 certificate) + { + EphemeralKeyType ephemeralKey = new EphemeralKeyType(); + + // Create the ephemeralKey + using (ECDiffieHellman ephemeralKeyCreator = ECDiffieHellman.Create()) + { + byte[] publicKeyBytes = ephemeralKeyCreator.PublicKey.ToByteArray(); + ephemeralKey.PublicKey = publicKeyBytes; + ephemeralKey.Signature = Sign(new ArraySegment(publicKeyBytes), certificate, securityPolicyUri); + } + + return ephemeralKey; + } } /// @@ -540,7 +561,6 @@ public class EncryptedSecret /// public string SecurityPolicyUri { get; set; } - /// /// Encrypts a secret using the specified nonce, encrypting key, and initialization vector (IV). /// From e62ad975ae1e537e1046b3cd8e33815da3bed135 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 20 Oct 2023 16:29:39 +0300 Subject: [PATCH 25/80] Added minimal test --- Tests/Opc.Ua.Client.Tests/ClientTest.cs | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs index d5eba8fef..9019c0e2b 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs @@ -1337,6 +1337,41 @@ public void ReadBuildInfo() Assert.AreEqual(buildInfo.BuildNumber, values[5].Value); Assert.AreEqual(buildInfo.BuildDate, values[6].Value); } + + /// + /// Open a session on a channel using ECC encrypted UserIdentityToken + /// + [Test, Combinatorial, Order(10100)] + public async Task OpenSessionECCUserIdentityToken( + [Values(SecurityPolicies.ECC_nistP256, + SecurityPolicies.ECC_nistP384, + SecurityPolicies.ECC_brainpoolP256r1, + SecurityPolicies.ECC_brainpoolP384r1)] string securityPolicy, + [Values(true, false)] bool anonymous) + { + + IUserIdentity userIdentity = anonymous ? new UserIdentity() : new UserIdentity("user1", "password"); + + // the first channel determines the endpoint + ConfiguredEndpoint endpoint = await ClientFixture.GetEndpointAsync(ServerUrl, securityPolicy, Endpoints).ConfigureAwait(false); + Assert.NotNull(endpoint); + + UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, userIdentity.IssuedTokenType); + if (identityPolicy == null) + { + Assert.Ignore($"No UserTokenPolicy found for {userIdentity.TokenType} / {userIdentity.IssuedTokenType}"); + } + + // the active channel + ISession session1 = await ClientFixture.ConnectAsync(endpoint, userIdentity).ConfigureAwait(false); + Assert.NotNull(session1); + + ServerStatusDataType value1 = (ServerStatusDataType)session1.ReadValue(VariableIds.Server_ServerStatus, typeof(ServerStatusDataType)); + Assert.NotNull(value1); + + } + + #endregion #region Benchmarks From 1a8ab0708abdb96bc90e58f895d3a0bd54de7f96 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 9 Nov 2023 14:22:20 +0200 Subject: [PATCH 26/80] Added UserIdentityToken Encypt/Decrypt functionality --- Libraries/Opc.Ua.Client/Session.cs | 116 ++++++- Libraries/Opc.Ua.Client/SessionAsync.cs | 35 ++- Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj | 4 + .../Opc.Ua.Server/Server/StandardServer.cs | 88 ++++-- Libraries/Opc.Ua.Server/Session/Session.cs | 47 ++- Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 18 +- .../Security/Certificates/Certificate.cs | 161 ++++++++++ .../Security/Certificates/EccUtils.cs | 21 -- .../Security/Certificates/Nonce.cs | 235 ++++++++++++++- .../Security/Certificates/X509Utils.cs | 2 +- .../Stack/Types/UserIdentityToken.cs | 284 +++++++++++++++++- 11 files changed, 940 insertions(+), 71 deletions(-) create mode 100644 Stack/Opc.Ua.Core/Security/Certificates/Certificate.cs diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index 0dee6f0bb..fed0a2181 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -1383,7 +1383,16 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. - identityToken.Encrypt(m_serverCertificate, m_serverNonce, securityPolicyUri); + // identityToken.Encrypt(m_serverCertificate, m_serverNonce, securityPolicyUri); + // encrypt token. + identityToken.Encrypt( + m_serverCertificate, + m_serverNonce, + m_userTokenSecurityPolicyUri, + m_eccServerEphermalKey, + m_instanceCertificate, + m_instanceCertificateChain, + m_endpoint.Description.SecurityMode != MessageSecurityMode.None); // send the software certificates assigned to the client. SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); @@ -1463,12 +1472,14 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel Utils.LogWarning("WARNING: ACTIVATE SESSION timed out. {0}/{1}", GoodPublishRequestCount, OutstandingRequestCount); } - EndActivateSession( + var responseHeader = EndActivateSession( result, out serverNonce, out certificateResults, out certificateDiagnosticInfos); + ProcessResponseAdditionalHeader(responseHeader); + int publishCount = 0; lock (SyncRoot) @@ -2386,14 +2397,40 @@ public void Open( sessionTimeout = (uint)m_configuration.ClientConfiguration.DefaultSessionTimeout; } + // select the security policy for the user token. + var userTokenSecurityPolicyUri = identityPolicy.SecurityPolicyUri; + + if (String.IsNullOrEmpty(userTokenSecurityPolicyUri)) + { + userTokenSecurityPolicyUri = m_endpoint.Description.SecurityPolicyUri; + } + + RequestHeader requestHeader = new RequestHeader(); + + if (EccUtils.IsEccPolicy(userTokenSecurityPolicyUri)) + { + AdditionalParametersType parameters = new AdditionalParametersType(); + + parameters.Parameters.Add(new KeyValuePair() { + Key = "ECDHPolicyUri", + Value = userTokenSecurityPolicyUri + }); + + requestHeader.AdditionalHeader = new ExtensionObject(parameters); + m_userTokenSecurityPolicyUri = userTokenSecurityPolicyUri; + } + bool successCreateSession = false; + + ResponseHeader responseHeader = null; + //if security none, first try to connect without certificate if (m_endpoint.Description.SecurityPolicyUri == SecurityPolicies.None) { //first try to connect with client certificate NULL try { - base.CreateSession( + responseHeader = base.CreateSession( null, clientDescription, m_endpoint.Description.Server.ApplicationUri, @@ -2424,7 +2461,7 @@ public void Open( if (!successCreateSession) { - base.CreateSession( + responseHeader = base.CreateSession( null, clientDescription, m_endpoint.Description.Server.ApplicationUri, @@ -2467,6 +2504,9 @@ public void Open( HandleSignedSoftwareCertificates(serverSoftwareCertificates); + // process additional header + ProcessResponseAdditionalHeader(responseHeader); + // create the client signature. byte[] dataToSign = Utils.Append(serverCertificate != null ? serverCertificate.RawData : null, serverNonce); SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, securityPolicyUri, dataToSign); @@ -2498,7 +2538,14 @@ public void Open( SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. - identityToken.Encrypt(serverCertificate, serverNonce, securityPolicyUri); + identityToken.Encrypt( + serverCertificate, + serverNonce, + userTokenSecurityPolicyUri, + m_eccServerEphermalKey, + m_instanceCertificate, + m_instanceCertificateChain, + m_endpoint.Description.SecurityMode != MessageSecurityMode.None); // send the software certificates assigned to the client. SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); @@ -2668,7 +2715,14 @@ public void UpdateSession(IUserIdentity identity, StringCollection preferredLoca userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. - identityToken.Encrypt(m_serverCertificate, serverNonce, securityPolicyUri); + identityToken.Encrypt( + m_serverCertificate, + serverNonce, + m_userTokenSecurityPolicyUri, + m_eccServerEphermalKey, + m_instanceCertificate, + m_instanceCertificateChain, + m_endpoint.Description.SecurityMode != MessageSecurityMode.None); // send the software certificates assigned to the client. SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); @@ -5971,6 +6025,54 @@ private void IndicateSessionConfigurationChanged() } #endregion + #region Protected Methods + /// + /// Process the AdditionalHeader field of a ResponseHeader + /// + /// + /// + protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHeader) + { + AdditionalParametersType parameters = ExtensionObject.ToEncodeable(responseHeader?.AdditionalHeader) as AdditionalParametersType; + + if (parameters != null) + { + foreach (var ii in parameters.Parameters) + { + if (ii.Key == "ECDHKey") + { + if (ii.Value.TypeInfo == TypeInfo.Scalars.StatusCode) + { + throw new ServiceResultException( + (uint)(StatusCode)ii.Value.Value, + "Server could not provide an ECDHKey. User authentication not possible."); + } + + var key = ExtensionObject.ToEncodeable(ii.Value.Value as ExtensionObject) as EphemeralKeyType; + + if (key == null) + { + throw new ServiceResultException( + StatusCodes.BadDecodingError, + "Server did not provide a valid ECDHKey. User authentication not possible."); + } + + if (!EccUtils.Verify(new ArraySegment(key.PublicKey), key.Signature, m_serverCertificate, m_userTokenSecurityPolicyUri)) + { + throw new ServiceResultException( + StatusCodes.BadDecodingError, + "Could not verify signature on ECDHKey. User authentication not possible."); + } + + m_eccServerEphermalKey = Nonce.CreateNonce(m_userTokenSecurityPolicyUri, key.PublicKey); + } + } + } + } + + + #endregion + #region Protected Fields /// /// The period for which the server will maintain the session if there is no communication from the client. @@ -6052,6 +6154,8 @@ private void IndicateSessionConfigurationChanged() private const int kDefaultPublishRequestCount = 1; private int m_minPublishRequestCount; private LinkedList m_outstandingRequests; + private string m_userTokenSecurityPolicyUri; + private Nonce m_eccServerEphermalKey; private readonly EndpointDescriptionCollection m_discoveryServerEndpoints; private readonly StringCollection m_discoveryProfileUris; diff --git a/Libraries/Opc.Ua.Client/SessionAsync.cs b/Libraries/Opc.Ua.Client/SessionAsync.cs index f7ede5eee..75e22b0b2 100644 --- a/Libraries/Opc.Ua.Client/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/SessionAsync.cs @@ -124,6 +124,28 @@ public async Task OpenAsync( sessionTimeout = (uint)m_configuration.ClientConfiguration.DefaultSessionTimeout; } + // select the security policy for the user token. + var userTokenSecurityPolicyUri = identityPolicy.SecurityPolicyUri; + + if (String.IsNullOrEmpty(userTokenSecurityPolicyUri)) + { + userTokenSecurityPolicyUri = m_endpoint.Description.SecurityPolicyUri; + } + + RequestHeader requestHeader = new RequestHeader(); + + if (EccUtils.IsEccPolicy(userTokenSecurityPolicyUri)) + { + AdditionalParametersType parameters = new AdditionalParametersType(); + + parameters.Parameters.Add(new KeyValuePair() { + Key = "ECDHPolicyUri", + Value = userTokenSecurityPolicyUri + }); + + requestHeader.AdditionalHeader = new ExtensionObject(parameters); + m_userTokenSecurityPolicyUri = userTokenSecurityPolicyUri; + } bool successCreateSession = false; CreateSessionResponse response = null; @@ -202,6 +224,9 @@ public async Task OpenAsync( HandleSignedSoftwareCertificates(serverSoftwareCertificates); + // process additional header + ProcessResponseAdditionalHeader(response.ResponseHeader); + // create the client signature. byte[] dataToSign = Utils.Append(serverCertificate != null ? serverCertificate.RawData : null, serverNonce); SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, securityPolicyUri, dataToSign); @@ -233,7 +258,15 @@ public async Task OpenAsync( SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. - identityToken.Encrypt(serverCertificate, serverNonce, securityPolicyUri); + //identityToken.Encrypt(serverCertificate, serverNonce, securityPolicyUri); + identityToken.Encrypt( + serverCertificate, + serverNonce, + userTokenSecurityPolicyUri, + m_eccServerEphermalKey, + m_instanceCertificate, + m_instanceCertificateChain, + m_endpoint.Description.SecurityMode != MessageSecurityMode.None); // send the software certificates assigned to the client. SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); diff --git a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj index 9e8854235..491fc2d9e 100644 --- a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj +++ b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj @@ -21,6 +21,10 @@ $(DefineConstants);SIGNASSEMBLY + + $(DefineConstants);ECC_SUPPORT + + $(DefineConstants);ECC_SUPPORT diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs index ac940ed95..f78015dbd 100644 --- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs +++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs @@ -459,6 +459,14 @@ public override ResponseHeader CreateSession( } } +#if ECC_SUPPORT + var parameters = ExtensionObject.ToEncodeable(requestHeader.AdditionalHeader) as AdditionalParametersType; + + if (parameters != null) + { + parameters = CreateSessionProcessAdditionalParameters(session, parameters); + } +#endif lock (m_lock) { // return the application instance certificate for the server. @@ -504,8 +512,11 @@ public override ResponseHeader CreateSession( ResponseHeader responseHeader = CreateResponse(requestHeader, StatusCodes.Good); -#if ECC_SUPPORT - GenerateEphemeralKey(context?.SecurityPolicyUri, session, instanceCertificate, responseHeader); +#if ECC_SUPPORT + if (parameters != null) + { + responseHeader.AdditionalHeader = new ExtensionObject(parameters); + } #endif return responseHeader; @@ -542,34 +553,52 @@ public override ResponseHeader CreateSession( } } -#if ECC_SUPPORT - /// - /// Generate a new ephemeral key to be used in UserIdentityToken encryption, - /// add it to the ResponseHeader.AdditionalHeader field and save it to the server session - /// This is to be used only within encryption using ECC security policies - /// - /// - /// - /// - /// - private static void GenerateEphemeralKey(string securityPolicyUri, Session session, X509Certificate2 instanceCertificate, ResponseHeader responseHeader) +#if ECC_SUPPORT + protected virtual AdditionalParametersType CreateSessionProcessAdditionalParameters(Session session, AdditionalParametersType parameters) { - if (EccUtils.IsEccPolicy(securityPolicyUri)) + AdditionalParametersType response = null; + + if (parameters != null && parameters.Parameters != null) { - EphemeralKeyType ephemeralKey = EccUtils.GenerateEphemeralKey(securityPolicyUri, instanceCertificate); + response = new AdditionalParametersType(); - // Add the ephemeralKey to the AdditionalHeader of the response header - KeyValuePairCollection additionalHeaderKeyValues = new KeyValuePairCollection(); - additionalHeaderKeyValues.Add(new KeyValuePair() { Key = securityPolicyUri, Value = new Variant(ephemeralKey) }); + foreach (var ii in parameters.Parameters) + { + if (ii.Key == "ECDHPolicyUri") + { + var policyUri = ii.Value.ToString(); - AdditionalParametersType additionalParams = new AdditionalParametersType(); - additionalParams.Parameters = additionalHeaderKeyValues; + if (EccUtils.IsEccPolicy(policyUri)) + { + session.SetEccUserTokenSecurityPolicy(policyUri); + var key = session.GetNewEccKey(); + response.Parameters.Add(new KeyValuePair() { Key = "ECDHKey", Value = new ExtensionObject(key) }); + } + else + { + response.Parameters.Add(new KeyValuePair() { Key = "ECDHKey", Value = StatusCodes.BadSecurityPolicyRejected }); + } + } + } + } + + return response; + } + protected virtual AdditionalParametersType ActivateSessionProcessAdditionalParameters(Session session, AdditionalParametersType parameters) + { + AdditionalParametersType response = null; - responseHeader.AdditionalHeader = new ExtensionObject(additionalParams); + var key = session.GetNewEccKey(); - session.EphemeralKey = ephemeralKey; + if (key != null) + { + response = new AdditionalParametersType(); + response.Parameters.Add(new KeyValuePair() { Key = "ECDHKey", Value = new ExtensionObject(key) }); } + + return response; } + #endif @@ -680,19 +709,22 @@ public override ResponseHeader ActivateSession( // TBD - call Node Manager and Subscription Manager. } + var parameters = ExtensionObject.ToEncodeable(requestHeader.AdditionalHeader) as AdditionalParametersType; + Session session = ServerInternal.SessionManager.GetSession(requestHeader.AuthenticationToken); + parameters = ActivateSessionProcessAdditionalParameters(session, parameters); + Utils.LogInfo("Server - SESSION ACTIVATED."); // report the audit event for session activate - Session session = ServerInternal.SessionManager.GetSession(requestHeader.AuthenticationToken); ServerInternal.ReportAuditActivateSessionEvent(context?.AuditEntryId, session, softwareCertificates); ResponseHeader responseHeader = CreateResponse(requestHeader, StatusCodes.Good); -#if ECC_SUPPORT - // load the certificate for the security profile - X509Certificate2 instanceCertificate = InstanceCertificateTypesProvider.GetInstanceCertificate(context.SecurityPolicyUri); - - GenerateEphemeralKey(context?.SecurityPolicyUri, session, instanceCertificate, responseHeader); +#if ECC_SUPPORT + if (parameters != null) + { + responseHeader.AdditionalHeader = new ExtensionObject(parameters); + } #endif return responseHeader; } diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs index 4dc77e9db..92bda510b 100644 --- a/Libraries/Opc.Ua.Server/Session/Session.cs +++ b/Libraries/Opc.Ua.Server/Session/Session.cs @@ -94,7 +94,10 @@ public Session( m_sessionName = sessionName; m_serverCertificate = serverCertificate; m_clientCertificate = clientCertificate; - m_secureChannelId = context.ChannelContext.SecureChannelId; + + m_clientIssuerCertificates = null; + + m_secureChannelId = SecureChannelId; m_maxResponseMessageSize = maxResponseMessageSize; m_maxRequestAge = maxRequestAge; m_maxBrowseContinuationPoints = maxBrowseContinuationPoints; @@ -357,6 +360,37 @@ public bool Activated } } + public virtual void SetEccUserTokenSecurityPolicy(string securityPolicyUri) + { + lock (m_lock) + { + m_eccUserTokenSecurityPolicyUri = securityPolicyUri; + m_eccUserTokenNonce = null; + } + } + + public virtual EphemeralKeyType GetNewEccKey() + { + lock (m_lock) + { + if (m_eccUserTokenSecurityPolicyUri == null) + { + return null; + } + + m_eccUserTokenNonce = Nonce.CreateNonce(m_eccUserTokenSecurityPolicyUri, 0).Data; + + EphemeralKeyType key = new EphemeralKeyType() + { + PublicKey = m_eccUserTokenNonce + }; + + key.Signature = EccUtils.Sign(new ArraySegment(key.PublicKey), m_serverCertificate, m_eccUserTokenSecurityPolicyUri); + + return key; + } + } + /// /// Returns the session's endpoint /// @@ -991,7 +1025,12 @@ private UserIdentityToken ValidateUserIdentityToken( try { - token.Decrypt(m_serverCertificate, m_serverNonce, securityPolicyUri); + token.Decrypt(m_serverCertificate, + Nonce.CreateNonce(securityPolicyUri,m_serverNonce), + securityPolicyUri, + Nonce.CreateNonce(securityPolicyUri,m_eccUserTokenNonce), + m_clientCertificate, + m_clientIssuerCertificates); } catch (Exception e) { @@ -1166,6 +1205,8 @@ private void UpdateDiagnosticCounters(RequestType requestType, bool error, bool private bool m_activated; private X509Certificate2 m_clientCertificate; + private X509Certificate2Collection m_clientIssuerCertificates; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] private List m_softwareCertificates; private byte[] m_clientNonce; @@ -1175,6 +1216,8 @@ private void UpdateDiagnosticCounters(RequestType requestType, bool error, bool private EndpointDescription m_endpoint; private X509Certificate2 m_serverCertificate; private byte[] m_serverCertificateChain; + private string m_eccUserTokenSecurityPolicyUri; + private byte[] m_eccUserTokenNonce; private string[] m_localeIds; [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index 291915058..0e2362985 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -1,4 +1,4 @@ - + $(DefineConstants);NET_STANDARD;NET_STANDARD_ASYNC @@ -11,6 +11,10 @@ true + + $(DefineConstants);ECC_SUPPORT + + $(DefineConstants);ECC_SUPPORT @@ -74,6 +78,18 @@ + + + + + + + + + diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Certificate.cs b/Stack/Opc.Ua.Core/Security/Certificates/Certificate.cs new file mode 100644 index 000000000..38c4907fe --- /dev/null +++ b/Stack/Opc.Ua.Core/Security/Certificates/Certificate.cs @@ -0,0 +1,161 @@ +using System.Collections; +using System.Collections.Generic; +using System.IO; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; + +using Bc = Org.BouncyCastle.X509; +using Ms = System.Security.Cryptography.X509Certificates; + +namespace Opc.Ua.Security.Certificates +{ + /// + /// A X509Certificate2 extended with Bouncy castle enabled functionality + /// + public class MsBcCertificate : Ms.X509Certificate2 + { + + #region Public Properies + /// + /// The certificate + /// + public Bc.X509Certificate BouncyCastleCertificate { get => m_bouncyCastleCertificate; set => m_bouncyCastleCertificate = value; } + + /// + /// The Private key + /// + public AsymmetricKeyParameter BcPrivateKey { get => m_bcPrivateKey; set => m_bcPrivateKey = value; } + #endregion + + #region Constructors + public MsBcCertificate(byte[] data) : base(data) + { + BouncyCastleCertificate = new Bc.X509Certificate(X509CertificateStructure.GetInstance(data)); + } + + public MsBcCertificate(byte[] data, string password, Ms.X509KeyStorageFlags flags) : base(data, password, flags) + { + } + + public MsBcCertificate(string filePath) : base(filePath) + { + BouncyCastleCertificate = new Bc.X509Certificate(X509CertificateStructure.GetInstance(File.ReadAllBytes(filePath))); + } + + public MsBcCertificate(string fullName, string password, Ms.X509KeyStorageFlags flags) : base(fullName, password, flags) + { + } + + public MsBcCertificate(Ms.X509Certificate2 certificate) : base(certificate) + { + BouncyCastleCertificate = new Bc.X509Certificate(X509CertificateStructure.GetInstance(certificate.RawData)); + } + #endregion + + #region Private Members + private Bc.X509Certificate m_bouncyCastleCertificate; + private AsymmetricKeyParameter m_bcPrivateKey; + #endregion + + + } + + /// + /// A collection of MsBcCertificate + /// + public class MsBcCertificateCollection : Ms.X509Certificate2Collection, IEnumerable + { + public MsBcCertificateCollection() + { + } + + public MsBcCertificateCollection(MsBcCertificate certificate) : base(certificate) + { + } + + public MsBcCertificateCollection(IEnumerable certificates) + { + if (certificates != null) + { + foreach (var certificate in certificates) + { + Add(certificate); + } + } + } + + public MsBcCertificateCollection(Ms.X509Certificate2Collection certificates) : base(certificates) + { + } + + public new MsBcCertificate this[int index] + { + get + { + var certificate = base[index]; + + if (certificate is MsBcCertificate) + { + return (MsBcCertificate)certificate; + } + + return new MsBcCertificate(certificate); + } + + set + { + base[index] = value; + } + } + + public new MsBcCertificateCollection Find(Ms.X509FindType findType, object findValue, bool validOnly) + { + return new MsBcCertificateCollection(base.Find(findType, findValue, validOnly)); + } + + private class Enumerator : IEnumerator + { + private Ms.X509Certificate2Enumerator m_enumerator; + + public Enumerator(Ms.X509Certificate2Enumerator enumerator) + { + m_enumerator = enumerator; + } + + public MsBcCertificate Current + { + get + { + return (MsBcCertificate)m_enumerator.Current; + } + } + + object IEnumerator.Current + { + get + { + return (MsBcCertificate)m_enumerator.Current; + } + } + + public void Dispose() + { + } + + public bool MoveNext() + { + return m_enumerator.MoveNext(); + } + + public void Reset() + { + m_enumerator.Reset(); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(base.GetEnumerator()); + } + } +} diff --git a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs index 649561e19..5af74a39c 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs @@ -493,27 +493,6 @@ public static bool Verify( return ecdsa.VerifyData(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count, signature, algorithm); } } - - /// - /// Generates an EphemeralKey and signes it with the provided certificate - /// - /// - /// - /// The ephemeral key - public static EphemeralKeyType GenerateEphemeralKey(string securityPolicyUri, X509Certificate2 certificate) - { - EphemeralKeyType ephemeralKey = new EphemeralKeyType(); - - // Create the ephemeralKey - using (ECDiffieHellman ephemeralKeyCreator = ECDiffieHellman.Create()) - { - byte[] publicKeyBytes = ephemeralKeyCreator.PublicKey.ToByteArray(); - ephemeralKey.PublicKey = publicKeyBytes; - ephemeralKey.Signature = Sign(new ArraySegment(publicKeyBytes), certificate, securityPolicyUri); - } - - return ephemeralKey; - } } /// diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs index 0c125ec6c..8c4bfd2c9 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs @@ -19,6 +19,17 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Security.Cryptography.X509Certificates; using Opc.Ua.Security.Certificates; +#if NETSTANDARD2_0 +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +#endif + #if CURVE25519 using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.X509; @@ -38,9 +49,167 @@ namespace Opc.Ua /// /// Represents a cryptographic nonce used for secure communication. /// + +#if NETSTANDARD2_0 + public class ECDHKeyPairGenerator + { + public static (AsymmetricCipherKeyPair, ECDomainParameters) GenerateKeyPair(X9ECParameters eCParameters) + { + ECDomainParameters domainParameters = new ECDomainParameters(eCParameters.Curve, + eCParameters.G, eCParameters.N, eCParameters.H, eCParameters.GetSeed()); + ECKeyGenerationParameters keyGenParameters = new ECKeyGenerationParameters(domainParameters, new SecureRandom()); + Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator generator = new Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator(); + generator.Init(keyGenParameters); + return (generator.GenerateKeyPair(), domainParameters); + } + + public static (AsymmetricCipherKeyPair, ECDomainParameters) GenerateKeyPair(string curveName) + { + X9ECParameters eCParameters = ECNamedCurveTable.GetByName(curveName); + return GenerateKeyPair(eCParameters); + } + + public static byte[] DeriveKeyFromHmac(AsymmetricCipherKeyPair localKeyPair, ECPublicKeyParameters remotePublicKey, byte[] salt, byte[] info, int derivedKeyLength) + { + // Perform ECDH key agreement + ECDHBasicAgreement agreement = new ECDHBasicAgreement(); + agreement.Init(localKeyPair.Private); + BigInteger sharedSecret = agreement.CalculateAgreement(remotePublicKey); + + byte[] sharedSecretBytes = sharedSecret.ToByteArrayUnsigned(); + + // Setup HMAC-based KDF + HMac hmac = new HMac(new Sha256Digest()); + hmac.Init(new KeyParameter(sharedSecretBytes)); + + // KDF parameters + int hashLength = hmac.GetMacSize(); + int numBlocks = (int)Math.Ceiling((double)derivedKeyLength / hashLength); + byte[] derivedKey = new byte[derivedKeyLength]; + + // HMAC-based KDF as per HKDF + byte[] previousBlock = new byte[0]; + for (int blockIndex = 1; blockIndex <= numBlocks; blockIndex++) + { + hmac.BlockUpdate(previousBlock, 0, previousBlock.Length); + hmac.BlockUpdate(salt, 0, salt.Length); + hmac.BlockUpdate(new byte[] { (byte)blockIndex }, 0, 1); + hmac.BlockUpdate(info, 0, info.Length); + + byte[] block = new byte[hashLength]; + hmac.DoFinal(block, 0); + previousBlock = block; + + Array.Copy(block, 0, derivedKey, (blockIndex - 1) * hashLength, Math.Min(hashLength, derivedKey.Length - (blockIndex - 1) * hashLength)); + } + + return derivedKey; + } + + public static string TranslateMsCurveToBcName(ECCurve eccCurve) + { + if (CurveNameMap.TryGetValue(eccCurve.Oid.FriendlyName, out string bcCurveName)) + { + X9ECParameters ecParameters = ECNamedCurveTable.GetByName(bcCurveName); + if (ecParameters != null) + { + return bcCurveName; + } + } + throw new NotSupportedException($"Curve {eccCurve.Oid.FriendlyName} is not supported"); + } + + public static string TranslateBcCurveNameToMs(string bcCurveName) + { + foreach (var kvp in CurveNameMap) + { + if (kvp.Value.Equals(bcCurveName, StringComparison.OrdinalIgnoreCase)) + { + return kvp.Key; + } + } + + throw new NotSupportedException($"Curve {bcCurveName} is not supported"); + } + + private static readonly Dictionary CurveNameMap = new Dictionary { + { "nistp256", "P-256" }, + { "nistP384", "P-384" }, + { "nistP521", "P-521" }, + { "brainpoolP256r1", "brainpoolp256r1" }, + { "brainpoolP384r1", "brainpoolp384r1" }, + { "brainpoolP521r1", "brainpoolp521r1" } + }; + + } + + public class BCECDiffieHellman + { + public AsymmetricCipherKeyPair PublicKey + { + get + { + return m_asymmetricCipherKeyPair; + } + } + public (AsymmetricCipherKeyPair, ECDomainParameters) Create(string bcCurveName) + { + m_curveName = bcCurveName; + (m_asymmetricCipherKeyPair, m_domainParameters) = ECDHKeyPairGenerator.GenerateKeyPair(bcCurveName); + + return (m_asymmetricCipherKeyPair, m_domainParameters); + } + public byte[] DeriveKeyFromHmac(AsymmetricCipherKeyPair localKeyPair, ECPublicKeyParameters remotePublicKey, byte[] salt, byte[] info, int derivedKeyLength) + { + // Perform ECDH key agreement + ECDHBasicAgreement agreement = new ECDHBasicAgreement(); + agreement.Init(localKeyPair.Private); + BigInteger sharedSecret = agreement.CalculateAgreement(remotePublicKey); + + byte[] sharedSecretBytes = sharedSecret.ToByteArrayUnsigned(); + + // Setup HMAC-based KDF + HMac hmac = new HMac(new Sha256Digest()); + hmac.Init(new KeyParameter(sharedSecretBytes)); + + // KDF parameters + int hashLength = hmac.GetMacSize(); + int numBlocks = (int)Math.Ceiling((double)derivedKeyLength / hashLength); + byte[] derivedKey = new byte[derivedKeyLength]; + + // HMAC-based KDF as per HKDF + byte[] previousBlock = new byte[0]; + for (int blockIndex = 1; blockIndex <= numBlocks; blockIndex++) + { + hmac.BlockUpdate(previousBlock, 0, previousBlock.Length); + hmac.BlockUpdate(salt, 0, salt.Length); + hmac.BlockUpdate(new byte[] { (byte)blockIndex }, 0, 1); + hmac.BlockUpdate(info, 0, info.Length); + + byte[] block = new byte[hashLength]; + hmac.DoFinal(block, 0); + previousBlock = block; + + Array.Copy(block, 0, derivedKey, (blockIndex - 1) * hashLength, Math.Min(hashLength, derivedKey.Length - (blockIndex - 1) * hashLength)); + } + + return derivedKey; + } + + private string m_curveName; + private ECDomainParameters m_domainParameters; + private AsymmetricCipherKeyPair m_asymmetricCipherKeyPair; + } +#endif + public class Nonce : IDisposable { +#if NETSTANDARD2_0 + private AsymmetricCipherKeyPair m_ecdh; +#else private ECDiffieHellman m_ecdh; +#endif + #if CURVE25519 private AsymmetricCipherKeyPair m_bcKeyPair; #endif @@ -77,7 +246,7 @@ private HMAC returnHMACInstance(byte[] secret, HashAlgorithmName algorithm) } } -#region IDisposable Members + #region IDisposable Members /// /// Frees any unmanaged resources. /// @@ -95,12 +264,14 @@ protected virtual void Dispose(bool disposing) { if (m_ecdh != null) { +#if !NETSTANDARD2_0 m_ecdh.Dispose(); +#endif m_ecdh = null; } } } -#endregion + #endregion /// /// Gets the nonce data. @@ -161,12 +332,21 @@ public byte[] DeriveKey(Nonce remoteNonce, byte[] salt, HashAlgorithmName algori #endif if (m_ecdh != null) { +#if NETSTANDARD2_0 + var derivedKey = ECDHKeyPairGenerator.DeriveKeyFromHmac(m_ecdh, + (ECPublicKeyParameters)remoteNonce.m_ecdh.Public, + salt, + null, + length); + + return derivedKey; +#else var secret = m_ecdh.DeriveKeyFromHmac(remoteNonce.m_ecdh.PublicKey, algorithm, salt, null, null); byte[] output = new byte[length]; HMAC hmac = returnHMACInstance(secret, algorithm); - + byte counter = 1; byte[] info = new byte[hmac.HashSize / 8 + salt.Length + 1]; @@ -197,6 +377,8 @@ public byte[] DeriveKey(Nonce remoteNonce, byte[] salt, HashAlgorithmName algori } return output; + +#endif } return Data; @@ -299,6 +481,30 @@ private static Nonce CreateNonceForCurve448() /// A new Nonce instance. private static Nonce CreateNonce(ECCurve curve) { +#if NETSTANDARD2_0 + (var keyPair, _) = ECDHKeyPairGenerator.GenerateKeyPair(ECDHKeyPairGenerator.TranslateMsCurveToBcName(curve)); + + // Get the public key parameters + var ecPublicKeyParameters = (ECPublicKeyParameters)keyPair.Public; + var q = ecPublicKeyParameters.Q.Normalize(); + + // Get the byte arrays for X and Y coordinates of the ppublic key + byte[] qx = q.XCoord.GetEncoded(); + byte[] qy = q.YCoord.GetEncoded(); + + // Combine the X and Y coordinate byte arrays into a singe byte array + byte[] senderNonce = new byte[qx.Length + qy.Length]; + Array.Copy(qx, 0, senderNonce, 0, qx.Length); + Array.Copy(qy, 0, senderNonce, qx.Length, qy.Length); + + // Create and return the Nonce object + var nonce = new Nonce { + Data = senderNonce, + m_ecdh = keyPair + }; + + return nonce; +#else var ecdh = (ECDiffieHellman)ECDiffieHellman.Create(curve); var ecdhParameters = ecdh.ExportParameters(false); int xLen = ecdhParameters.Q.X.Length; @@ -313,6 +519,9 @@ private static Nonce CreateNonce(ECCurve curve) }; return nonce; +#endif + + } /// @@ -411,6 +620,24 @@ private static Nonce CreateNonce(ECCurve curve, byte[] nonceData) Buffer.BlockCopy(nonceData, 0, qx, 0, keyLength / 2); Buffer.BlockCopy(nonceData, keyLength / 2, qy, 0, keyLength / 2); +#if NETSTANDARD2_0 + string bcCurveName = ECDHKeyPairGenerator.TranslateMsCurveToBcName(curve); + var bcCurve = X9ECParameters.GetInstance( + ECNamedCurveTable.GetByName(bcCurveName) + ).Curve; + + var q = bcCurve.CreatePoint( + new BigInteger(1, qx), + new BigInteger(1, qy) + ); + + ECPublicKeyParameters eCPublicKeyParameters = new ECPublicKeyParameters(q, new ECDomainParameters(bcCurve, null, null)); + ECPrivateKeyParameters eCPrivateKeyParameters = new ECPrivateKeyParameters(new BigInteger(1, nonceData), new ECDomainParameters(bcCurve, null, null)); + nonce.m_ecdh = new AsymmetricCipherKeyPair(eCPublicKeyParameters, eCPrivateKeyParameters); + + return nonce; + +#else var ecdhParameters = new ECParameters { Curve = curve, Q = { X = qx, Y = qy } @@ -419,8 +646,8 @@ private static Nonce CreateNonce(ECCurve curve, byte[] nonceData) nonce.m_ecdh = ECDiffieHellman.Create(ecdhParameters); return nonce; +#endif } } - } #endif diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs index a23c01cb9..600d59a50 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs @@ -524,7 +524,7 @@ public static bool VerifyECDsaKeyPair( X509Certificate2 certWithPrivateKey, bool throwOnError = false) { -#if ECC_SUPPORT +#if ECC_SUPPORT && !NETSTANDARD2_0 return X509PfxUtils.VerifyECDsaKeyPair(certWithPublicKey, certWithPrivateKey, throwOnError); #else throw new NotSupportedException(); diff --git a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs index cb2409e30..5a25d498c 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs @@ -13,6 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System; using System.Text; using System.Security.Cryptography.X509Certificates; +using Opc.Ua.Security.Certificates; namespace Opc.Ua { @@ -20,22 +21,52 @@ namespace Opc.Ua /// The UserIdentityToken class. /// public partial class UserIdentityToken - { + { #region Public Methods /// /// Encrypts the token (implemented by the subclass). /// + [Obsolete("Use Encrypt(X509Certificate2, byte[], string securityPolicyUri, Nonce, X509Certificate2, X509Certificate2Collection, bool) ")] public virtual void Encrypt(X509Certificate2 certificate, byte[] receiverNonce, string securityPolicyUri) { } - + /// /// Decrypts the token (implemented by the subclass). /// + [Obsolete("Use Decrypt(X509Certificate2, Nonce, string, Nonce, X509Certificate2, X509Certificate2Collection, CertificateValidator) ")] public virtual void Decrypt(X509Certificate2 certificate, byte[] receiverNonce, string securityPolicyUri) { } - + + /// + /// Encrypts the token (implemented by the subclass). + /// + public virtual void Encrypt( + X509Certificate2 receiverCertificate, + byte[] receiverNonce, + string securityPolicyUri, + Nonce receiverEphemeralKey = null, + X509Certificate2 senderCertificate = null, + X509Certificate2Collection senderIssuerCertificates = null, + bool doNotEncodeSenderCertificate = false) + { + } + + /// + /// Decrypts the token (implemented by the subclass). + /// + public virtual void Decrypt( + X509Certificate2 certificate, + Nonce receiverNonce, + string securityPolicyUri, + Nonce ephemeralKey = null, + X509Certificate2 senderCertificate = null, + X509Certificate2Collection senderIssuerCertificates = null, + CertificateValidator validator = null) + { + } + /// /// Creates a signature with the token (implemented by the subclass). /// @@ -69,11 +100,12 @@ public string DecryptedPassword set { m_decryptedPassword = value; } } #endregion - + #region Public Methods /// /// Encrypts the DecryptedPassword using the EncryptionAlgorithm and places the result in Password /// + [Obsolete] public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { if (m_decryptedPassword == null) @@ -105,6 +137,7 @@ public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, s /// /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword /// + [Obsolete] public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { // handle no encryption. @@ -152,6 +185,160 @@ public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, s // convert to UTF-8. m_decryptedPassword = new UTF8Encoding().GetString(decryptedPassword, 0, startOfNonce); } + + /// + /// Encrypts the DecryptedPassword using the EncryptionAlgorithm and places the result in Password + /// + public override void Encrypt( + X509Certificate2 receiverCertificate, + byte[] receiverNonce, + string securityPolicyUri, + Nonce receiverEphemeralKey = null, + X509Certificate2 senderCertificate = null, + X509Certificate2Collection senderIssuerCertificates = null, + bool doNotEncodeSenderCertificate = false) + { + if (m_decryptedPassword == null) + { + m_password = null; + return; + } + + // handle no encryption. + if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None) + { + m_password = new UTF8Encoding().GetBytes(m_decryptedPassword); + m_encryptionAlgorithm = null; + return; + } + + // handle RSA encryption. + if (!EccUtils.IsEccPolicy(securityPolicyUri)) + { + byte[] dataToEncrypt = Utils.Append(new UTF8Encoding().GetBytes(m_decryptedPassword), receiverNonce); + + EncryptedData encryptedData = SecurityPolicies.Encrypt( + receiverCertificate, + securityPolicyUri, + dataToEncrypt); + + m_password = encryptedData.Data; + m_encryptionAlgorithm = encryptedData.Algorithm; + } + + // handle ECC encryption. + else + { + EncryptedSecret secret = new EncryptedSecret(); + + secret.ReceiverCertificate = receiverCertificate; + secret.SecurityPolicyUri = securityPolicyUri; + secret.ReceiverNonce = receiverEphemeralKey; + secret.SenderCertificate = senderCertificate; + secret.SenderIssuerCertificates = senderIssuerCertificates; + secret.DoNotEncodeSenderCertificate = doNotEncodeSenderCertificate; + + // check if the complete chain is included in the sender issuers. + if (senderIssuerCertificates != null && senderIssuerCertificates.Count > 0) + { + if (senderIssuerCertificates[0].Thumbprint == senderCertificate.Thumbprint) + { + var issuers = new MsBcCertificateCollection(); + + for (int ii = 1; ii < senderIssuerCertificates.Count; ii++) + { + issuers.Add(senderIssuerCertificates[ii]); + } + + senderIssuerCertificates = issuers; + } + } + + secret.SenderIssuerCertificates = senderIssuerCertificates; + secret.SenderNonce = Nonce.CreateNonce(securityPolicyUri, 0); + + var utf8 = new UTF8Encoding(false).GetBytes(m_decryptedPassword); + m_password = secret.Encrypt(utf8, receiverNonce); + m_encryptionAlgorithm = null; + } + } + + /// + /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword + /// + public override void Decrypt( + X509Certificate2 certificate, + Nonce receiverNonce, + string securityPolicyUri, + Nonce ephemeralKey = null, + X509Certificate2 senderCertificate = null, + X509Certificate2Collection senderIssuerCertificates = null, + CertificateValidator validator = null) + { + // handle no encryption. + if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None) + { + m_decryptedPassword = new UTF8Encoding().GetString(m_password, 0, m_password.Length); + return; + } + + // handle RSA encryption. + if (!EccUtils.IsEccPolicy(securityPolicyUri)) + { + EncryptedData encryptedData = new EncryptedData(); + encryptedData.Data = m_password; + encryptedData.Algorithm = m_encryptionAlgorithm; + + byte[] decryptedPassword = SecurityPolicies.Decrypt( + certificate, + securityPolicyUri, + encryptedData); + + if (decryptedPassword == null) + { + m_decryptedPassword = null; + return; + } + + // verify the sender's nonce. + int startOfNonce = decryptedPassword.Length; + + if (receiverNonce != null) + { + startOfNonce -= receiverNonce.Data.Length; + + int result = 0; + for (int ii = 0; ii < receiverNonce.Data.Length; ii++) + { + result |= receiverNonce.Data[ii] ^ decryptedPassword[ii + startOfNonce]; + } + + if (result != 0) + { + throw new ServiceResultException(StatusCodes.BadIdentityTokenRejected); + } + } + + // convert to UTF-8. + m_decryptedPassword = new UTF8Encoding().GetString(decryptedPassword, 0, startOfNonce); + } + + // handle ECC encryption. + else + { + EncryptedSecret secret = new EncryptedSecret(); + + secret.SenderCertificate = senderCertificate; + secret.SenderIssuerCertificates = senderIssuerCertificates; + secret.Validator = validator; + secret.ReceiverCertificate = certificate; + secret.ReceiverNonce = ephemeralKey; + secret.SecurityPolicyUri = securityPolicyUri; + + var plainText = secret.Decrypt(DateTime.UtcNow.AddHours(-1), receiverNonce.Data, m_password, 0, m_password.Length); + m_decryptedPassword = new UTF8Encoding().GetString(plainText); + } + } #endregion #region Private Fields @@ -268,7 +455,7 @@ public enum IssuedTokenType /// The IssuedIdentityToken class. /// public partial class IssuedIdentityToken - { + { #region Public Properties /// /// The type of issued token. @@ -288,11 +475,12 @@ public byte[] DecryptedTokenData set { m_decryptedTokenData = value; } } #endregion - + #region Public Methods /// /// Encrypts the DecryptedTokenData using the EncryptionAlgorithm and places the result in Password /// + [Obsolete] public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { // handle no encryption. @@ -313,10 +501,11 @@ public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, s m_tokenData = encryptedData.Data; m_encryptionAlgorithm = encryptedData.Algorithm; } - + /// /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword /// + [Obsolete] public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { // handle no encryption. @@ -357,6 +546,87 @@ public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, s Array.Copy(decryptedTokenData, m_decryptedTokenData, startOfNonce); } + /// + /// Encrypts the DecryptedTokenData using the EncryptionAlgorithm and places the result in Password + /// + public override void Encrypt( + X509Certificate2 receiverCertificate, + byte[] receiverNonce, + string securityPolicyUri, + Nonce receiverEphemeralKey = null, + X509Certificate2 senderCertificate = null, + X509Certificate2Collection senderIssuerCertificates = null, + bool doNotEncodeSenderCertificate = false) + { + // handle no encryption. + if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None) + { + m_tokenData = m_decryptedTokenData; + m_encryptionAlgorithm = String.Empty; + return; + } + + byte[] dataToEncrypt = Utils.Append(m_decryptedTokenData, receiverNonce); + + EncryptedData encryptedData = SecurityPolicies.Encrypt( + receiverCertificate, + securityPolicyUri, + dataToEncrypt); + + m_tokenData = encryptedData.Data; + m_encryptionAlgorithm = encryptedData.Algorithm; + } + + /// + /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword + /// + public override void Decrypt( + X509Certificate2 certificate, + Nonce receiverNonce, + string securityPolicyUri, + Nonce ephemeralKey = null, + X509Certificate2 senderCertificate = null, + X509Certificate2Collection senderIssuerCertificates = null, + CertificateValidator validator = null) + { + // handle no encryption. + if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None) + { + m_decryptedTokenData = m_tokenData; + return; + } + + EncryptedData encryptedData = new EncryptedData(); + + encryptedData.Data = m_tokenData; + encryptedData.Algorithm = m_encryptionAlgorithm; + + byte[] decryptedTokenData = SecurityPolicies.Decrypt( + certificate, + securityPolicyUri, + encryptedData); + + // verify the sender's nonce. + int startOfNonce = decryptedTokenData.Length; + + if (receiverNonce != null) + { + startOfNonce -= receiverNonce.Data.Length; + + for (int ii = 0; ii < receiverNonce.Data.Length; ii++) + { + if (receiverNonce.Data[ii] != decryptedTokenData[ii + startOfNonce]) + { + throw new ServiceResultException(StatusCodes.BadIdentityTokenRejected); + } + } + } + + // copy results. + m_decryptedTokenData = new byte[startOfNonce]; + Array.Copy(decryptedTokenData, m_decryptedTokenData, startOfNonce); + } + /// /// Creates a signature with the token. /// From 0ed43b9c7e6982267c93cb075ec09bb790e21cbf Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 10 Nov 2023 16:42:00 +0200 Subject: [PATCH 27/80] Preserve key material in UserIdentityToken encryption/decription --- Libraries/Opc.Ua.Client/Session.cs | 11 +- Libraries/Opc.Ua.Client/SessionAsync.cs | 4 +- Libraries/Opc.Ua.Server/Session/Session.cs | 31 ++-- .../Opc.Ua.Server/Session/SessionManager.cs | 18 +- .../Security/Certificates/Certificate.cs | 161 ------------------ .../Stack/Types/UserIdentityToken.cs | 2 +- Tests/Opc.Ua.Client.Tests/ClientTest.cs | 2 +- 7 files changed, 34 insertions(+), 195 deletions(-) delete mode 100644 Stack/Opc.Ua.Core/Security/Certificates/Certificate.cs diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index fed0a2181..984040a97 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -1382,8 +1382,6 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel identityToken.PolicyId = identityPolicy.PolicyId; SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); - // encrypt token. - // identityToken.Encrypt(m_serverCertificate, m_serverNonce, securityPolicyUri); // encrypt token. identityToken.Encrypt( m_serverCertificate, @@ -1478,7 +1476,7 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel out certificateResults, out certificateDiagnosticInfos); - ProcessResponseAdditionalHeader(responseHeader); + ProcessResponseAdditionalHeader(responseHeader, m_serverCertificate); int publishCount = 0; @@ -2505,7 +2503,7 @@ public void Open( HandleSignedSoftwareCertificates(serverSoftwareCertificates); // process additional header - ProcessResponseAdditionalHeader(responseHeader); + ProcessResponseAdditionalHeader(responseHeader, serverCertificate); // create the client signature. byte[] dataToSign = Utils.Append(serverCertificate != null ? serverCertificate.RawData : null, serverNonce); @@ -6030,8 +6028,9 @@ private void IndicateSessionConfigurationChanged() /// Process the AdditionalHeader field of a ResponseHeader /// /// + /// /// - protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHeader) + protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHeader, X509Certificate2 serverCertificate) { AdditionalParametersType parameters = ExtensionObject.ToEncodeable(responseHeader?.AdditionalHeader) as AdditionalParametersType; @@ -6057,7 +6056,7 @@ protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHe "Server did not provide a valid ECDHKey. User authentication not possible."); } - if (!EccUtils.Verify(new ArraySegment(key.PublicKey), key.Signature, m_serverCertificate, m_userTokenSecurityPolicyUri)) + if (!EccUtils.Verify(new ArraySegment(key.PublicKey), key.Signature, serverCertificate, m_userTokenSecurityPolicyUri)) { throw new ServiceResultException( StatusCodes.BadDecodingError, diff --git a/Libraries/Opc.Ua.Client/SessionAsync.cs b/Libraries/Opc.Ua.Client/SessionAsync.cs index 75e22b0b2..0ffc4feae 100644 --- a/Libraries/Opc.Ua.Client/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/SessionAsync.cs @@ -179,7 +179,7 @@ public async Task OpenAsync( if (!successCreateSession) { response = await base.CreateSessionAsync( - null, + requestHeader, clientDescription, m_endpoint.Description.Server.ApplicationUri, m_endpoint.EndpointUrl.ToString(), @@ -225,7 +225,7 @@ public async Task OpenAsync( HandleSignedSoftwareCertificates(serverSoftwareCertificates); // process additional header - ProcessResponseAdditionalHeader(response.ResponseHeader); + ProcessResponseAdditionalHeader(response.ResponseHeader, serverCertificate); // create the client signature. byte[] dataToSign = Utils.Append(serverCertificate != null ? serverCertificate.RawData : null, serverNonce); diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs index 92bda510b..dc9749a41 100644 --- a/Libraries/Opc.Ua.Server/Session/Session.cs +++ b/Libraries/Opc.Ua.Server/Session/Session.cs @@ -67,7 +67,7 @@ public Session( X509Certificate2 serverCertificate, NodeId authenticationToken, byte[] clientNonce, - byte[] serverNonce, + Nonce serverNonce, string sessionName, ApplicationDescription clientDescription, string endpointUrl, @@ -97,7 +97,7 @@ public Session( m_clientIssuerCertificates = null; - m_secureChannelId = SecureChannelId; + m_secureChannelId = context.ChannelContext.SecureChannelId; m_maxResponseMessageSize = maxResponseMessageSize; m_maxRequestAge = maxRequestAge; m_maxBrowseContinuationPoints = maxBrowseContinuationPoints; @@ -378,11 +378,10 @@ public virtual EphemeralKeyType GetNewEccKey() return null; } - m_eccUserTokenNonce = Nonce.CreateNonce(m_eccUserTokenSecurityPolicyUri, 0).Data; + m_eccUserTokenNonce = Nonce.CreateNonce(m_eccUserTokenSecurityPolicyUri, 0); - EphemeralKeyType key = new EphemeralKeyType() - { - PublicKey = m_eccUserTokenNonce + EphemeralKeyType key = new EphemeralKeyType() { + PublicKey = m_eccUserTokenNonce.Data }; key.Signature = EccUtils.Sign(new ArraySegment(key.PublicKey), m_serverCertificate, m_eccUserTokenSecurityPolicyUri); @@ -530,8 +529,6 @@ public void ValidateBeforeActivate( List clientSoftwareCertificates, ExtensionObject userIdentityToken, SignatureData userTokenSignature, - StringCollection localeIds, - byte[] serverNonce, out UserIdentityToken identityToken, out UserTokenPolicy userTokenPolicy) { @@ -559,7 +556,7 @@ public void ValidateBeforeActivate( throw new ServiceResultException(StatusCodes.BadApplicationSignatureInvalid); } - byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce); + byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce.Data); if (!SecurityPolicies.Verify(m_clientCertificate, m_endpoint.SecurityPolicyUri, dataToSign, clientSignature)) { @@ -577,7 +574,7 @@ public void ValidateBeforeActivate( } byte[] serverCertificateChainData = serverCertificateChainList.ToArray(); - dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce); + dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce.Data); if (!SecurityPolicies.Verify(m_clientCertificate, m_endpoint.SecurityPolicyUri, dataToSign, clientSignature)) { @@ -625,7 +622,7 @@ public bool Activate( IUserIdentity identity, IUserIdentity effectiveIdentity, StringCollection localeIds, - byte[] serverNonce) + Nonce serverNonce) { lock (m_lock) { @@ -1026,9 +1023,9 @@ private UserIdentityToken ValidateUserIdentityToken( try { token.Decrypt(m_serverCertificate, - Nonce.CreateNonce(securityPolicyUri,m_serverNonce), + m_serverNonce, securityPolicyUri, - Nonce.CreateNonce(securityPolicyUri,m_eccUserTokenNonce), + m_eccUserTokenNonce, m_clientCertificate, m_clientIssuerCertificates); } @@ -1045,7 +1042,7 @@ private UserIdentityToken ValidateUserIdentityToken( // verify the signature. if (securityPolicyUri != SecurityPolicies.None) { - byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce); + byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce.Data); if (!token.Verify(dataToSign, userTokenSignature, securityPolicyUri)) { @@ -1063,7 +1060,7 @@ private UserIdentityToken ValidateUserIdentityToken( } byte[] serverCertificateChainData = serverCertificateChainList.ToArray(); - dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce); + dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce.Data); if (!token.Verify(dataToSign, userTokenSignature, securityPolicyUri)) { @@ -1210,14 +1207,14 @@ private void UpdateDiagnosticCounters(RequestType requestType, bool error, bool [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] private List m_softwareCertificates; private byte[] m_clientNonce; - private byte[] m_serverNonce; + private Nonce m_serverNonce; private string m_sessionName; private string m_secureChannelId; private EndpointDescription m_endpoint; private X509Certificate2 m_serverCertificate; private byte[] m_serverCertificateChain; private string m_eccUserTokenSecurityPolicyUri; - private byte[] m_eccUserTokenNonce; + private Nonce m_eccUserTokenNonce; private string[] m_localeIds; [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] diff --git a/Libraries/Opc.Ua.Server/Session/SessionManager.cs b/Libraries/Opc.Ua.Server/Session/SessionManager.cs index de3d760c8..89c5986cb 100644 --- a/Libraries/Opc.Ua.Server/Session/SessionManager.cs +++ b/Libraries/Opc.Ua.Server/Session/SessionManager.cs @@ -161,6 +161,7 @@ public virtual Session CreateSession( out double revisedSessionTimeout) { sessionId = 0; + serverNonce = null; revisedSessionTimeout = requestedSessionTimeout; Session session = null; @@ -214,7 +215,8 @@ public virtual Session CreateSession( } // create server nonce. - serverNonce = Utils.Nonce.CreateNonce((uint)m_minNonceLength); + var serverNonceObject = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri, + (uint)m_minNonceLength); // assign client name. if (String.IsNullOrEmpty(sessionName)) @@ -229,7 +231,7 @@ public virtual Session CreateSession( serverCertificate, authenticationToken, clientNonce, - serverNonce, + serverNonceObject, sessionName, clientDescription, endpointUrl, @@ -241,6 +243,7 @@ public virtual Session CreateSession( // get the session id. sessionId = session.Id; + serverNonce = serverNonceObject.Data; // save session. m_sessions.Add(authenticationToken, session); @@ -267,6 +270,7 @@ public virtual bool ActivateSession( out byte[] serverNonce) { serverNonce = null; + Nonce serverNonceObject = null; Session session = null; UserIdentityToken newIdentity = null; @@ -292,7 +296,7 @@ public virtual bool ActivateSession( } // create new server nonce. - serverNonce = Utils.Nonce.CreateNonce((uint)m_minNonceLength); + serverNonceObject = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri, (uint)m_minNonceLength); // validate before activation. session.ValidateBeforeActivate( @@ -301,10 +305,10 @@ public virtual bool ActivateSession( clientSoftwareCertificates, userIdentityToken, userTokenSignature, - localeIds, - serverNonce, out newIdentity, out userTokenPolicy); + + serverNonce = serverNonceObject.Data; } IUserIdentity identity = null; @@ -373,7 +377,7 @@ public virtual bool ActivateSession( identity, effectiveIdentity, localeIds, - serverNonce); + serverNonceObject); // raise session related event. if (contextChanged) @@ -508,7 +512,7 @@ protected virtual Session CreateSession( X509Certificate2 serverCertificate, NodeId sessionCookie, byte[] clientNonce, - byte[] serverNonce, + Nonce serverNonce, string sessionName, ApplicationDescription clientDescription, string endpointUrl, diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Certificate.cs b/Stack/Opc.Ua.Core/Security/Certificates/Certificate.cs deleted file mode 100644 index 38c4907fe..000000000 --- a/Stack/Opc.Ua.Core/Security/Certificates/Certificate.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.IO; -using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.Crypto; - -using Bc = Org.BouncyCastle.X509; -using Ms = System.Security.Cryptography.X509Certificates; - -namespace Opc.Ua.Security.Certificates -{ - /// - /// A X509Certificate2 extended with Bouncy castle enabled functionality - /// - public class MsBcCertificate : Ms.X509Certificate2 - { - - #region Public Properies - /// - /// The certificate - /// - public Bc.X509Certificate BouncyCastleCertificate { get => m_bouncyCastleCertificate; set => m_bouncyCastleCertificate = value; } - - /// - /// The Private key - /// - public AsymmetricKeyParameter BcPrivateKey { get => m_bcPrivateKey; set => m_bcPrivateKey = value; } - #endregion - - #region Constructors - public MsBcCertificate(byte[] data) : base(data) - { - BouncyCastleCertificate = new Bc.X509Certificate(X509CertificateStructure.GetInstance(data)); - } - - public MsBcCertificate(byte[] data, string password, Ms.X509KeyStorageFlags flags) : base(data, password, flags) - { - } - - public MsBcCertificate(string filePath) : base(filePath) - { - BouncyCastleCertificate = new Bc.X509Certificate(X509CertificateStructure.GetInstance(File.ReadAllBytes(filePath))); - } - - public MsBcCertificate(string fullName, string password, Ms.X509KeyStorageFlags flags) : base(fullName, password, flags) - { - } - - public MsBcCertificate(Ms.X509Certificate2 certificate) : base(certificate) - { - BouncyCastleCertificate = new Bc.X509Certificate(X509CertificateStructure.GetInstance(certificate.RawData)); - } - #endregion - - #region Private Members - private Bc.X509Certificate m_bouncyCastleCertificate; - private AsymmetricKeyParameter m_bcPrivateKey; - #endregion - - - } - - /// - /// A collection of MsBcCertificate - /// - public class MsBcCertificateCollection : Ms.X509Certificate2Collection, IEnumerable - { - public MsBcCertificateCollection() - { - } - - public MsBcCertificateCollection(MsBcCertificate certificate) : base(certificate) - { - } - - public MsBcCertificateCollection(IEnumerable certificates) - { - if (certificates != null) - { - foreach (var certificate in certificates) - { - Add(certificate); - } - } - } - - public MsBcCertificateCollection(Ms.X509Certificate2Collection certificates) : base(certificates) - { - } - - public new MsBcCertificate this[int index] - { - get - { - var certificate = base[index]; - - if (certificate is MsBcCertificate) - { - return (MsBcCertificate)certificate; - } - - return new MsBcCertificate(certificate); - } - - set - { - base[index] = value; - } - } - - public new MsBcCertificateCollection Find(Ms.X509FindType findType, object findValue, bool validOnly) - { - return new MsBcCertificateCollection(base.Find(findType, findValue, validOnly)); - } - - private class Enumerator : IEnumerator - { - private Ms.X509Certificate2Enumerator m_enumerator; - - public Enumerator(Ms.X509Certificate2Enumerator enumerator) - { - m_enumerator = enumerator; - } - - public MsBcCertificate Current - { - get - { - return (MsBcCertificate)m_enumerator.Current; - } - } - - object IEnumerator.Current - { - get - { - return (MsBcCertificate)m_enumerator.Current; - } - } - - public void Dispose() - { - } - - public bool MoveNext() - { - return m_enumerator.MoveNext(); - } - - public void Reset() - { - m_enumerator.Reset(); - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(base.GetEnumerator()); - } - } -} diff --git a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs index 5a25d498c..bb980bfc9 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs @@ -243,7 +243,7 @@ public override void Encrypt( { if (senderIssuerCertificates[0].Thumbprint == senderCertificate.Thumbprint) { - var issuers = new MsBcCertificateCollection(); + var issuers = new X509Certificate2Collection(); for (int ii = 1; ii < senderIssuerCertificates.Count; ii++) { diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs index 9019c0e2b..5e2fd4b66 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs @@ -1347,7 +1347,7 @@ public async Task OpenSessionECCUserIdentityToken( SecurityPolicies.ECC_nistP384, SecurityPolicies.ECC_brainpoolP256r1, SecurityPolicies.ECC_brainpoolP384r1)] string securityPolicy, - [Values(true, false)] bool anonymous) + [Values(false)] bool anonymous) { IUserIdentity userIdentity = anonymous ? new UserIdentity() : new UserIdentity("user1", "password"); From f41f10a676176dabd767c46a524e771809f85bf8 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Wed, 15 Nov 2023 14:31:46 +0200 Subject: [PATCH 28/80] Removed ECC support from NETSTANDARD2_0 (introduced bu UserIdentityEncryption) --- Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj | 20 ++ Libraries/Opc.Ua.Client/Session.cs | 129 +++++----- Libraries/Opc.Ua.Client/SessionAsync.cs | 50 ++-- Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj | 2 +- .../Opc.Ua.Server/Server/StandardServer.cs | 6 +- Libraries/Opc.Ua.Server/Session/Session.cs | 86 ++++++- .../Opc.Ua.Server/Session/SessionManager.cs | 114 ++++++--- Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 2 +- .../Security/Certificates/EccUtils.cs | 207 ++++++++++++++++ .../Security/Certificates/Nonce.cs | 226 ------------------ .../Security/Certificates/X509Utils.cs | 2 +- .../Stack/Types/UserIdentityToken.cs | 62 +++-- 12 files changed, 532 insertions(+), 374 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj index 10cf86589..174603309 100644 --- a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj +++ b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj @@ -20,6 +20,26 @@ $(PackageId).Debug + + + $(DefineConstants); + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index 984040a97..e0ec7c792 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -1383,6 +1383,7 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. +#if ECC_SUPPORT identityToken.Encrypt( m_serverCertificate, m_serverNonce, @@ -1391,6 +1392,9 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel m_instanceCertificate, m_instanceCertificateChain, m_endpoint.Description.SecurityMode != MessageSecurityMode.None); +#else + identityToken.Encrypt(m_serverCertificate, m_serverNonce, securityPolicyUri); +#endif // send the software certificates assigned to the client. SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); @@ -2536,6 +2540,7 @@ public void Open( SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. +#if ECC_SUPPORT identityToken.Encrypt( serverCertificate, serverNonce, @@ -2544,7 +2549,9 @@ public void Open( m_instanceCertificate, m_instanceCertificateChain, m_endpoint.Description.SecurityMode != MessageSecurityMode.None); - +#else + identityToken.Encrypt(serverCertificate, serverNonce, securityPolicyUri); +#endif // send the software certificates assigned to the client. SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); @@ -2713,6 +2720,7 @@ public void UpdateSession(IUserIdentity identity, StringCollection preferredLoca userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. +#if ECC_SUPPORT identityToken.Encrypt( m_serverCertificate, serverNonce, @@ -2721,7 +2729,9 @@ public void UpdateSession(IUserIdentity identity, StringCollection preferredLoca m_instanceCertificate, m_instanceCertificateChain, m_endpoint.Description.SecurityMode != MessageSecurityMode.None); - +#else + identityToken.Encrypt(m_serverCertificate, serverNonce, securityPolicyUri); +#endif // send the software certificates assigned to the client. SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); @@ -3012,9 +3022,9 @@ public void ReadDisplayName( } } } - #endregion +#endregion - #region Close Methods +#region Close Methods /// public override StatusCode Close() { @@ -3110,9 +3120,9 @@ public virtual StatusCode Close(int timeout, bool closeChannel) return result; } - #endregion +#endregion - #region Subscription Methods +#region Subscription Methods /// public bool AddSubscription(Subscription subscription) { @@ -3358,9 +3368,9 @@ public bool TransferSubscriptions( return failedSubscriptions == 0; } - #endregion +#endregion - #region Browse Methods +#region Browse Methods /// public virtual ResponseHeader Browse( RequestHeader requestHeader, @@ -3537,9 +3547,9 @@ public ResponseHeader EndBrowse( return responseHeader; } - #endregion +#endregion - #region BrowseNext Methods +#region BrowseNext Methods /// public virtual ResponseHeader BrowseNext( RequestHeader requestHeader, @@ -3667,9 +3677,9 @@ public ResponseHeader EndBrowseNext( return responseHeader; } - #endregion +#endregion - #region Call Methods +#region Call Methods /// public IList Call(NodeId objectId, NodeId methodId, params object[] args) { @@ -3718,9 +3728,9 @@ public IList Call(NodeId objectId, NodeId methodId, params object[] args return outputArguments; } - #endregion +#endregion - #region Protected Methods +#region Protected Methods /// /// Returns the software certificates assigned to the application. /// @@ -4850,9 +4860,9 @@ private IDictionary CreateAttributes(NodeClass nodeclass = Node return attributes; } - #endregion +#endregion - #region Publish Methods +#region Publish Methods /// /// Sends an additional publish request. /// @@ -5253,9 +5263,9 @@ public bool ResendData(IEnumerable subscriptions, out IList /// Validates the identity for an open call. /// @@ -6021,9 +6031,9 @@ private void IndicateSessionConfigurationChanged() } } } - #endregion +#endregion - #region Protected Methods +#region Protected Methods /// /// Process the AdditionalHeader field of a ResponseHeader /// @@ -6062,17 +6072,18 @@ protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHe StatusCodes.BadDecodingError, "Could not verify signature on ECDHKey. User authentication not possible."); } - +#if ECC_SUPPORT m_eccServerEphermalKey = Nonce.CreateNonce(m_userTokenSecurityPolicyUri, key.PublicKey); +#endif } } } } - #endregion +#endregion - #region Protected Fields +#region Protected Fields /// /// The period for which the server will maintain the session if there is no communication from the client. /// @@ -6117,9 +6128,9 @@ protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHe /// The user identity currently used for the session. /// protected IUserIdentity m_identity; - #endregion +#endregion - #region Private Fields +#region Private Fields private ISessionFactory m_sessionFactory; private SubscriptionAcknowledgementCollection m_acknowledgementsToSend; private Dictionary m_latestAcknowledgementsSent; @@ -6154,7 +6165,9 @@ protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHe private int m_minPublishRequestCount; private LinkedList m_outstandingRequests; private string m_userTokenSecurityPolicyUri; +#if ECC_SUPPORT private Nonce m_eccServerEphermalKey; +#endif private readonly EndpointDescriptionCollection m_discoveryServerEndpoints; private readonly StringCollection m_discoveryProfileUris; @@ -6175,16 +6188,16 @@ private class AsyncRequestState private event EventHandler m_SubscriptionsChanged; private event EventHandler m_SessionClosing; private event EventHandler m_SessionConfigurationChanged; - #endregion +#endregion } - #region KeepAliveEventArgs Class +#region KeepAliveEventArgs Class /// /// The event arguments provided when a keep alive response arrives. /// public class KeepAliveEventArgs : EventArgs { - #region Constructors +#region Constructors /// /// Creates a new instance. /// @@ -6197,9 +6210,9 @@ internal KeepAliveEventArgs( m_currentState = currentState; m_currentTime = currentTime; } - #endregion +#endregion - #region Public Properties +#region Public Properties /// /// Gets the status associated with the keep alive operation. /// @@ -6223,24 +6236,24 @@ public bool CancelKeepAlive get { return m_cancelKeepAlive; } set { m_cancelKeepAlive = value; } } - #endregion +#endregion - #region Private Fields +#region Private Fields private readonly ServiceResult m_status; private readonly ServerState m_currentState; private readonly DateTime m_currentTime; private bool m_cancelKeepAlive; - #endregion +#endregion } - #endregion +#endregion - #region NotificationEventArgs Class +#region NotificationEventArgs Class /// /// Represents the event arguments provided when a new notification message arrives. /// public class NotificationEventArgs : EventArgs { - #region Constructors +#region Constructors /// /// Creates a new instance. /// @@ -6253,9 +6266,9 @@ internal NotificationEventArgs( m_notificationMessage = notificationMessage; m_stringTable = stringTable; } - #endregion +#endregion - #region Public Properties +#region Public Properties /// /// Gets the subscription that the notification applies to. /// @@ -6270,23 +6283,23 @@ internal NotificationEventArgs( /// Gets the string table returned with the notification message. /// public IList StringTable => m_stringTable; - #endregion +#endregion - #region Private Fields +#region Private Fields private readonly Subscription m_subscription; private readonly NotificationMessage m_notificationMessage; private readonly IList m_stringTable; - #endregion +#endregion } - #endregion +#endregion - #region PublishErrorEventArgs Class +#region PublishErrorEventArgs Class /// /// Represents the event arguments provided when a publish error occurs. /// public class PublishErrorEventArgs : EventArgs { - #region Constructors +#region Constructors /// /// Creates a new instance. /// @@ -6304,9 +6317,9 @@ internal PublishErrorEventArgs(ServiceResult status, uint subscriptionId, uint s m_subscriptionId = subscriptionId; m_sequenceNumber = sequenceNumber; } - #endregion +#endregion - #region Public Properties +#region Public Properties /// /// Gets the status associated with the keep alive operation. /// @@ -6321,17 +6334,17 @@ internal PublishErrorEventArgs(ServiceResult status, uint subscriptionId, uint s /// Gets the sequence number for the message that could not be republished. /// public uint SequenceNumber => m_sequenceNumber; - #endregion +#endregion - #region Private Fields +#region Private Fields private readonly uint m_subscriptionId; private readonly uint m_sequenceNumber; private readonly ServiceResult m_status; - #endregion +#endregion } - #endregion +#endregion - #region PublishSequenceNumbersToAcknowledgeEventArgs Class +#region PublishSequenceNumbersToAcknowledgeEventArgs Class /// /// Represents the event arguments provided when publish response /// sequence numbers are about to be achknoledged with a publish request. @@ -6344,7 +6357,7 @@ internal PublishErrorEventArgs(ServiceResult status, uint subscriptionId, uint s /// public class PublishSequenceNumbersToAcknowledgeEventArgs : EventArgs { - #region Constructors +#region Constructors /// /// Creates a new instance. /// @@ -6355,9 +6368,9 @@ internal PublishSequenceNumbersToAcknowledgeEventArgs( m_acknowledgementsToSend = acknowledgementsToSend; m_deferredAcknowledgementsToSend = deferredAcknowledgementsToSend; } - #endregion +#endregion - #region Public Properties +#region Public Properties /// /// The acknowledgements which are sent with the next publish request. /// @@ -6375,12 +6388,12 @@ internal PublishSequenceNumbersToAcknowledgeEventArgs( /// to this list to defer the acknowledge of a sequence number to the next publish request. /// public SubscriptionAcknowledgementCollection DeferredAcknowledgementsToSend => m_deferredAcknowledgementsToSend; - #endregion +#endregion - #region Private Fields +#region Private Fields private readonly SubscriptionAcknowledgementCollection m_acknowledgementsToSend; private readonly SubscriptionAcknowledgementCollection m_deferredAcknowledgementsToSend; - #endregion +#endregion } - #endregion +#endregion } diff --git a/Libraries/Opc.Ua.Client/SessionAsync.cs b/Libraries/Opc.Ua.Client/SessionAsync.cs index 0ffc4feae..e4466ea14 100644 --- a/Libraries/Opc.Ua.Client/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/SessionAsync.cs @@ -259,6 +259,7 @@ public async Task OpenAsync( // encrypt token. //identityToken.Encrypt(serverCertificate, serverNonce, securityPolicyUri); +#if ECC_SUPPORT identityToken.Encrypt( serverCertificate, serverNonce, @@ -267,6 +268,9 @@ public async Task OpenAsync( m_instanceCertificate, m_instanceCertificateChain, m_endpoint.Description.SecurityMode != MessageSecurityMode.None); +#else + identityToken.Encrypt(serverCertificate, serverNonce, securityPolicyUri); +#endif // send the software certificates assigned to the client. SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); @@ -350,9 +354,9 @@ public async Task OpenAsync( throw; } } - #endregion +#endregion - #region Subscription Async Methods +#region Subscription Async Methods /// public async Task RemoveSubscriptionAsync(Subscription subscription, CancellationToken ct = default) { @@ -585,9 +589,9 @@ public async Task TransferSubscriptionsAsync( return failedSubscriptions == 0; } - #endregion +#endregion - #region FetchNamespaceTables Async Methods +#region FetchNamespaceTables Async Methods /// public async Task FetchNamespaceTablesAsync(CancellationToken ct = default) { @@ -610,9 +614,9 @@ public async Task FetchNamespaceTablesAsync(CancellationToken ct = default) UpdateNamespaceTable(values, diagnosticInfos, responseHeader); } - #endregion +#endregion - #region FetchTypeTree Async Methods +#region FetchTypeTree Async Methods /// public async Task FetchTypeTreeAsync(ExpandedNodeId typeId, CancellationToken ct = default) { @@ -656,9 +660,9 @@ public async Task FetchTypeTreeAsync(ExpandedNodeIdCollection typeIds, Cancellat await FetchTypeTreeAsync(subTypes, ct).ConfigureAwait(false); } } - #endregion +#endregion - #region FetchOperationLimits Async Methods +#region FetchOperationLimits Async Methods /// /// Fetch the operation limits of the server. /// @@ -711,9 +715,9 @@ public async Task FetchOperationLimitsAsync(CancellationToken ct) } } } - #endregion +#endregion - #region ReadNode Async Methods +#region ReadNode Async Methods /// public async Task<(IList, IList)> ReadNodesAsync( IList nodeIds, @@ -964,9 +968,9 @@ public async Task ReadValueAsync( return (values, errors); } - #endregion +#endregion - #region Browse Methods +#region Browse Methods /// public async Task<( ResponseHeader responseHeader, @@ -1035,9 +1039,9 @@ IList errors return (browseResponse.ResponseHeader, continuationPoints, referencesList, errors); } - #endregion +#endregion - #region BrowseNext Methods +#region BrowseNext Methods /// public async Task<( @@ -1086,9 +1090,9 @@ List errors return (response.ResponseHeader, revisedContinuationPoints, referencesList, errors); } - #endregion +#endregion - #region Call Methods +#region Call Methods /// public async Task> CallAsync(NodeId objectId, NodeId methodId, CancellationToken ct = default, params object[] args) { @@ -1136,9 +1140,9 @@ public async Task> CallAsync(NodeId objectId, NodeId methodId, Can return outputArguments; } - #endregion +#endregion - #region FetchReferences Async Methods +#region FetchReferences Async Methods /// public async Task FetchReferencesAsync( NodeId nodeId, @@ -1263,9 +1267,9 @@ IList browseNextErrors return (result, errors); } - #endregion +#endregion - #region Recreate Async Methods +#region Recreate Async Methods /// /// Recreates a session based on a specified template. /// @@ -1400,9 +1404,9 @@ await session.OpenAsync( return session; } - #endregion +#endregion - #region Close Async Methods +#region Close Async Methods /// public override Task CloseAsync(CancellationToken ct = default) { @@ -1502,7 +1506,7 @@ public virtual async Task CloseAsync(int timeout, bool closeChannel, return result; } - #endregion +#endregion /// /// Recreate the subscriptions in a reconnected session. diff --git a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj index 491fc2d9e..77fba1eee 100644 --- a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj +++ b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj @@ -22,7 +22,7 @@ - $(DefineConstants);ECC_SUPPORT + $(DefineConstants); diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs index f78015dbd..48542eeee 100644 --- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs +++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs @@ -709,9 +709,11 @@ public override ResponseHeader ActivateSession( // TBD - call Node Manager and Subscription Manager. } - var parameters = ExtensionObject.ToEncodeable(requestHeader.AdditionalHeader) as AdditionalParametersType; Session session = ServerInternal.SessionManager.GetSession(requestHeader.AuthenticationToken); +#if ECC_SUPPORT + var parameters = ExtensionObject.ToEncodeable(requestHeader.AdditionalHeader) as AdditionalParametersType; parameters = ActivateSessionProcessAdditionalParameters(session, parameters); +#endif Utils.LogInfo("Server - SESSION ACTIVATED."); @@ -720,7 +722,7 @@ public override ResponseHeader ActivateSession( ResponseHeader responseHeader = CreateResponse(requestHeader, StatusCodes.Good); -#if ECC_SUPPORT +#if ECC_SUPPORT if (parameters != null) { responseHeader.AdditionalHeader = new ExtensionObject(parameters); diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs index dc9749a41..31dcf9478 100644 --- a/Libraries/Opc.Ua.Server/Session/Session.cs +++ b/Libraries/Opc.Ua.Server/Session/Session.cs @@ -61,6 +61,7 @@ public class Session : IDisposable /// The maximum number of browse continuation points. /// The maximum number of history continuation points. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] + #if ECC_SUPPORT public Session( OperationContext context, IServerInternal server, @@ -77,6 +78,24 @@ public Session( double maxRequestAge, int maxBrowseContinuationPoints, int maxHistoryContinuationPoints) +#else + public Session( + OperationContext context, + IServerInternal server, + X509Certificate2 serverCertificate, + NodeId authenticationToken, + byte[] clientNonce, + byte[] serverNonce, + string sessionName, + ApplicationDescription clientDescription, + string endpointUrl, + X509Certificate2 clientCertificate, + double sessionTimeout, + uint maxResponseMessageSize, + double maxRequestAge, + int maxBrowseContinuationPoints, + int maxHistoryContinuationPoints) +#endif { if (context == null) throw new ArgumentNullException(nameof(context)); if (server == null) throw new ArgumentNullException(nameof(server)); @@ -95,7 +114,9 @@ public Session( m_serverCertificate = serverCertificate; m_clientCertificate = clientCertificate; +#if ECC_SUPPORT m_clientIssuerCertificates = null; +#endif m_secureChannelId = context.ChannelContext.SecureChannelId; m_maxResponseMessageSize = maxResponseMessageSize; @@ -190,9 +211,9 @@ public Session( TraceState("CREATED"); } - #endregion +#endregion - #region IDisposable Members +#region IDisposable Members /// /// Frees any unmanaged resources. /// @@ -242,9 +263,9 @@ protected virtual void Dispose(bool disposing) } } } - #endregion +#endregion - #region Public Interface +#region Public Interface /// /// Gets the identifier assigned to the session when it was created. /// @@ -360,6 +381,7 @@ public bool Activated } } +#if ECC_SUPPORT public virtual void SetEccUserTokenSecurityPolicy(string securityPolicyUri) { lock (m_lock) @@ -389,6 +411,7 @@ public virtual EphemeralKeyType GetNewEccKey() return key; } } +#endif /// /// Returns the session's endpoint @@ -556,7 +579,11 @@ public void ValidateBeforeActivate( throw new ServiceResultException(StatusCodes.BadApplicationSignatureInvalid); } +#if ECC_SUPPORT byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce.Data); +#else + byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce); +#endif if (!SecurityPolicies.Verify(m_clientCertificate, m_endpoint.SecurityPolicyUri, dataToSign, clientSignature)) { @@ -574,7 +601,11 @@ public void ValidateBeforeActivate( } byte[] serverCertificateChainData = serverCertificateChainList.ToArray(); +#if ECC_SUPPORT dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce.Data); +#else + dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce); +#endif if (!SecurityPolicies.Verify(m_clientCertificate, m_endpoint.SecurityPolicyUri, dataToSign, clientSignature)) { @@ -615,6 +646,7 @@ public void ValidateBeforeActivate( /// /// Activates the session and binds it to the current secure channel. /// +#if ECC_SUPPORT public bool Activate( OperationContext context, List clientSoftwareCertificates, @@ -622,7 +654,17 @@ public bool Activate( IUserIdentity identity, IUserIdentity effectiveIdentity, StringCollection localeIds, - Nonce serverNonce) + Nonce serverNonce) +#else + public bool Activate( + OperationContext context, + List clientSoftwareCertificates, + UserIdentityToken identityToken, + IUserIdentity identity, + IUserIdentity effectiveIdentity, + StringCollection localeIds, + byte[] serverNonce) +#endif { lock (m_lock) { @@ -850,9 +892,9 @@ private class HistoryContinuationPoint public object Value; public DateTime Timestamp; } - #endregion +#endregion - #region Private Methods +#region Private Methods /// /// Dumps the current state of the session queue. /// @@ -1022,12 +1064,17 @@ private UserIdentityToken ValidateUserIdentityToken( try { +#if ECC_SUPPORT token.Decrypt(m_serverCertificate, m_serverNonce, securityPolicyUri, m_eccUserTokenNonce, m_clientCertificate, m_clientIssuerCertificates); +#else + + token.Decrypt(m_serverCertificate, m_serverNonce, securityPolicyUri); +#endif } catch (Exception e) { @@ -1042,7 +1089,11 @@ private UserIdentityToken ValidateUserIdentityToken( // verify the signature. if (securityPolicyUri != SecurityPolicies.None) { +#if ECC_SUPPORT byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce.Data); +#else + byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce); +#endif if (!token.Verify(dataToSign, userTokenSignature, securityPolicyUri)) { @@ -1060,8 +1111,12 @@ private UserIdentityToken ValidateUserIdentityToken( } byte[] serverCertificateChainData = serverCertificateChainList.ToArray(); - dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce.Data); +#if ECC_SUPPORT + dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce.Data); +#else + dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce); +#endif if (!token.Verify(dataToSign, userTokenSignature, securityPolicyUri)) { throw new ServiceResultException(StatusCodes.BadIdentityTokenRejected, "Invalid user signature!"); @@ -1187,9 +1242,9 @@ private void UpdateDiagnosticCounters(RequestType requestType, bool error, bool } } } - #endregion +#endregion - #region Private Fields +#region Private Fields private readonly object m_lock = new object(); private NodeId m_sessionId; [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] @@ -1202,19 +1257,24 @@ private void UpdateDiagnosticCounters(RequestType requestType, bool error, bool private bool m_activated; private X509Certificate2 m_clientCertificate; - private X509Certificate2Collection m_clientIssuerCertificates; [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] private List m_softwareCertificates; private byte[] m_clientNonce; - private Nonce m_serverNonce; private string m_sessionName; private string m_secureChannelId; private EndpointDescription m_endpoint; private X509Certificate2 m_serverCertificate; private byte[] m_serverCertificateChain; + +#if ECC_SUPPORT + private Nonce m_serverNonce; private string m_eccUserTokenSecurityPolicyUri; private Nonce m_eccUserTokenNonce; + private X509Certificate2Collection m_clientIssuerCertificates; +#else + private byte[] m_serverNonce; +#endif private string[] m_localeIds; [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] @@ -1229,6 +1289,6 @@ private void UpdateDiagnosticCounters(RequestType requestType, bool error, bool private List m_historyContinuationPoints; private EphemeralKeyType m_ephemeralKey; - #endregion +#endregion } } diff --git a/Libraries/Opc.Ua.Server/Session/SessionManager.cs b/Libraries/Opc.Ua.Server/Session/SessionManager.cs index 89c5986cb..3ad88fbe6 100644 --- a/Libraries/Opc.Ua.Server/Session/SessionManager.cs +++ b/Libraries/Opc.Ua.Server/Session/SessionManager.cs @@ -215,8 +215,12 @@ public virtual Session CreateSession( } // create server nonce. +#if ECC_SUPPORT var serverNonceObject = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri, (uint)m_minNonceLength); +#else + serverNonce = Utils.Nonce.CreateNonce((uint)m_minNonceLength); +#endif // assign client name. if (String.IsNullOrEmpty(sessionName)) @@ -225,6 +229,7 @@ public virtual Session CreateSession( } // create instance of session. +#if ECC_SUPPORT session = CreateSession( context, m_server, @@ -240,10 +245,29 @@ public virtual Session CreateSession( maxResponseMessageSize, m_maxRequestAge, m_maxBrowseContinuationPoints); +#else + session = CreateSession( + context, + m_server, + serverCertificate, + authenticationToken, + clientNonce, + serverNonce, + sessionName, + clientDescription, + endpointUrl, + clientCertificate, + revisedSessionTimeout, + maxResponseMessageSize, + m_maxRequestAge, + m_maxBrowseContinuationPoints); +#endif // get the session id. sessionId = session.Id; +#if ECC_SUPPORT serverNonce = serverNonceObject.Data; +#endif // save session. m_sessions.Add(authenticationToken, session); @@ -270,7 +294,9 @@ public virtual bool ActivateSession( out byte[] serverNonce) { serverNonce = null; +#if ECC_SUPPORT Nonce serverNonceObject = null; +#endif Session session = null; UserIdentityToken newIdentity = null; @@ -296,7 +322,11 @@ public virtual bool ActivateSession( } // create new server nonce. +#if ECC_SUPPORT serverNonceObject = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri, (uint)m_minNonceLength); +#else + serverNonce = Utils.Nonce.CreateNonce((uint)m_minNonceLength); +#endif // validate before activation. session.ValidateBeforeActivate( @@ -307,8 +337,9 @@ public virtual bool ActivateSession( userTokenSignature, out newIdentity, out userTokenPolicy); - +#if ECC_SUPPORT serverNonce = serverNonceObject.Data; +#endif } IUserIdentity identity = null; @@ -370,6 +401,7 @@ public virtual bool ActivateSession( } // activate session. +#if ECC_SUPPORT bool contextChanged = session.Activate( context, clientSoftwareCertificates, @@ -378,6 +410,16 @@ public virtual bool ActivateSession( effectiveIdentity, localeIds, serverNonceObject); +#else + bool contextChanged = session.Activate( + context, + clientSoftwareCertificates, + newIdentity, + identity, + effectiveIdentity, + localeIds, + serverNonce); +#endif // raise session related event. if (contextChanged) @@ -500,12 +542,13 @@ public virtual OperationContext ValidateRequest(RequestHeader requestHeader, Req throw new ServiceResultException(e, StatusCodes.BadUnexpectedError); } } - #endregion +#endregion - #region Protected Methods - /// - /// Creates a new instance of a session. - /// +#region Protected Methods +/// +/// Creates a new instance of a session. +/// +#if ECC_SUPPORT protected virtual Session CreateSession( OperationContext context, IServerInternal server, @@ -521,6 +564,23 @@ protected virtual Session CreateSession( uint maxResponseMessageSize, int maxRequestAge, // TBD - Remove unused parameter. int maxContinuationPoints) // TBD - Remove unused parameter. +#else + protected virtual Session CreateSession( + OperationContext context, + IServerInternal server, + X509Certificate2 serverCertificate, + NodeId sessionCookie, + byte[] clientNonce, + byte[] serverNonce, + string sessionName, + ApplicationDescription clientDescription, + string endpointUrl, + X509Certificate2 clientCertificate, + double sessionTimeout, + uint maxResponseMessageSize, + int maxRequestAge, // TBD - Remove unused parameter. + int maxContinuationPoints) // TBD - Remove unused parameter. +#endif { Session session = new Session( context, @@ -571,9 +631,9 @@ protected virtual void RaiseSessionEvent(Session session, SessionEventReason rea } } } - #endregion +#endregion - #region Private Methods +#region Private Methods /// /// Periodically checks if the sessions have timed out. /// @@ -625,9 +685,9 @@ private void MonitorSessions(object data) Utils.LogError(e, "Server - Session Monitor Thread Exited Unexpectedly"); } } - #endregion +#endregion - #region Private Fields +#region Private Fields private object m_lock = new object(); private IServerInternal m_server; private Dictionary m_sessions; @@ -648,9 +708,9 @@ private void MonitorSessions(object data) private event SessionEventHandler m_sessionClosing; private event ImpersonateEventHandler m_impersonateUser; private event EventHandler m_validateSessionLessRequest; - #endregion +#endregion - #region ISessionManager Members +#region ISessionManager Members /// public event SessionEventHandler SessionCreated { @@ -773,7 +833,7 @@ public Session GetSession(NodeId authenticationToken) } return session; } - #endregion +#endregion } /// @@ -853,13 +913,13 @@ public enum SessionEventReason /// public delegate void SessionEventHandler(Session session, SessionEventReason reason); - #region ImpersonateEventArgs Class +#region ImpersonateEventArgs Class /// /// A class which provides the event arguments for session related event. /// public class ImpersonateEventArgs : EventArgs { - #region Constructors +#region Constructors /// /// Creates a new instance. /// @@ -869,9 +929,9 @@ public ImpersonateEventArgs(UserIdentityToken newIdentity, UserTokenPolicy userT m_userTokenPolicy = userTokenPolicy; m_endpointDescription = endpointDescription; } - #endregion +#endregion - #region Public Properties +#region Public Properties /// /// The new user identity for the session. /// @@ -922,31 +982,31 @@ public EndpointDescription EndpointDescription { get { return m_endpointDescription; } } - #endregion +#endregion - #region Private Fields +#region Private Fields private UserIdentityToken m_newIdentity; private UserTokenPolicy m_userTokenPolicy; private ServiceResult m_identityValidationError; private IUserIdentity m_identity; private IUserIdentity m_effectiveIdentity; private EndpointDescription m_endpointDescription; - #endregion +#endregion } /// /// The delegate for functions used to receive impersonation events. /// public delegate void ImpersonateEventHandler(Session session, ImpersonateEventArgs args); - #endregion +#endregion - #region ImpersonateEventArgs Class +#region ImpersonateEventArgs Class /// /// A class which provides the event arguments for session related event. /// public class ValidateSessionLessRequestEventArgs : EventArgs { - #region Constructors +#region Constructors /// /// Creates a new instance. /// @@ -955,9 +1015,9 @@ public ValidateSessionLessRequestEventArgs(NodeId authenticationToken, RequestTy AuthenticationToken = authenticationToken; RequestType = requestType; } - #endregion +#endregion - #region Public Properties +#region Public Properties /// /// The request type for the request. /// @@ -977,7 +1037,7 @@ public ValidateSessionLessRequestEventArgs(NodeId authenticationToken, RequestTy /// Set to indicate that an error occurred validating the session-less request and that it should be rejected. /// public ServiceResult Error { get; set; } - #endregion +#endregion } - #endregion +#endregion } diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index 0e2362985..6535e0b0e 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -12,7 +12,7 @@ - $(DefineConstants);ECC_SUPPORT + $(DefineConstants); diff --git a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs index 5af74a39c..52ada2d4e 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs @@ -1170,6 +1170,213 @@ public byte[] Decrypt(DateTime earliestTime, byte[] expectedNonce, byte[] data, return secret; } +#else + /// + /// Verifies a ECDsa signature. + /// + public static bool Verify( + ArraySegment dataToVerify, + byte[] signature, + X509Certificate2 signingCertificate, + string securityPolicyUri) + { + return Verify(dataToVerify, signature, signingCertificate, GetSignatureAlgorithmName(securityPolicyUri)); + } + + /// + /// Verifies a ECDsa signature. + /// + public static bool Verify( + ArraySegment dataToVerify, + byte[] signature, + X509Certificate2 signingCertificate, + HashAlgorithmName algorithm) + { +#if CURVE25519 + var publicKey = signingCertificate.BcCertificate.GetPublicKey(); + + if (publicKey is Ed25519PublicKeyParameters) + { + var verifier = new Ed25519Signer(); + + verifier.Init(false, signingCertificate.BcCertificate.GetPublicKey()); + verifier.BlockUpdate(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count); + + if (!verifier.VerifySignature(signature)) + { + return false; + } + + return true; + } + + if (publicKey is Ed448PublicKeyParameters) + { + var verifier = new Ed448Signer(new byte[32]); + + verifier.Init(false, signingCertificate.BcCertificate.GetPublicKey()); + verifier.BlockUpdate(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count); + + if (!verifier.VerifySignature(signature)) + { + return false; + } + + return true; + } +#endif + using (ECDsa ecdsa = EccUtils.GetPublicKey(signingCertificate)) + { + return ecdsa.VerifyData(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count, signature, algorithm); + } + } + + /// + /// Returns the public key for the specified certificate and ouputs the security policy uris. + /// + /// + /// + /// + public static ECDsa GetPublicKey(X509Certificate2 certificate, out string[] securityPolicyUris) + { + securityPolicyUris = null; + + var keyAlgorithm = certificate.GetKeyAlgorithm(); + + if (certificate == null || keyAlgorithm != Oids.ECPublicKey) + { + return null; + } + + const X509KeyUsageFlags SufficientFlags = + X509KeyUsageFlags.KeyAgreement | + X509KeyUsageFlags.DigitalSignature | + X509KeyUsageFlags.NonRepudiation | + X509KeyUsageFlags.CrlSign | + X509KeyUsageFlags.KeyCertSign; + + foreach (X509Extension extension in certificate.Extensions) + { + if (extension.Oid.Value == "2.5.29.15") + { + X509KeyUsageExtension kuExt = (X509KeyUsageExtension)extension; + + if ((kuExt.KeyUsages & SufficientFlags) == 0) + { + return null; + } + } + } + + PublicKey encodedPublicKey = certificate.PublicKey; + string keyParameters = BitConverter.ToString(encodedPublicKey.EncodedParameters.RawData); + byte[] keyValue = encodedPublicKey.EncodedKeyValue.RawData; + + ECParameters ecParameters = default(ECParameters); + + if (keyValue[0] != 0x04) + { + throw new InvalidOperationException("Only uncompressed points are supported"); + } + + byte[] x = new byte[(keyValue.Length - 1) / 2]; + byte[] y = new byte[x.Length]; + + Buffer.BlockCopy(keyValue, 1, x, 0, x.Length); + Buffer.BlockCopy(keyValue, 1 + x.Length, y, 0, y.Length); + + ecParameters.Q.X = x; + ecParameters.Q.Y = y; + + // New values can be determined by running the dotted-decimal OID value + // through BitConverter.ToString(CryptoConfig.EncodeOID(dottedDecimal)); + + switch (keyParameters) + { + case NistP256KeyParameters: + { + ecParameters.Curve = ECCurve.NamedCurves.nistP256; + securityPolicyUris = new string[] { SecurityPolicies.ECC_nistP256 }; + break; + } + + case NistP384KeyParameters: + { + ecParameters.Curve = ECCurve.NamedCurves.nistP384; + securityPolicyUris = new string[] { SecurityPolicies.ECC_nistP384, SecurityPolicies.ECC_nistP256 }; + break; + } + + case BrainpoolP256r1KeyParameters: + { + ecParameters.Curve = ECCurve.NamedCurves.brainpoolP256r1; + securityPolicyUris = new string[] { SecurityPolicies.ECC_brainpoolP256r1 }; + break; + } + + case BrainpoolP384r1KeyParameters: + { + ecParameters.Curve = ECCurve.NamedCurves.brainpoolP384r1; + securityPolicyUris = new string[] { SecurityPolicies.ECC_brainpoolP384r1, SecurityPolicies.ECC_brainpoolP256r1 }; + break; + } + + default: + { + throw new NotImplementedException(keyParameters); + } + } + + return ECDsa.Create(ecParameters); + } + + /// + /// Returns the public key for the specified certificate. + /// + /// + /// + public static ECDsa GetPublicKey(X509Certificate2 certificate) + { + string[] securityPolicyUris; + return GetPublicKey(certificate, out securityPolicyUris); + } + + /// + /// Returns the hash algorithm for the specified security policy. + /// + /// + /// + public static HashAlgorithmName GetSignatureAlgorithmName(string securityPolicyUri) + { + if (securityPolicyUri == null) + { + throw new ArgumentNullException(nameof(securityPolicyUri)); + } + + switch (securityPolicyUri) + { + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + { + return HashAlgorithmName.SHA256; + } + + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + return HashAlgorithmName.SHA384; + } + + case SecurityPolicies.None: + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: + default: + { + return HashAlgorithmName.SHA256; + } + } + } + #endif } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs index 8c4bfd2c9..4b9e9bd8d 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs @@ -19,17 +19,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Security.Cryptography.X509Certificates; using Opc.Ua.Security.Certificates; -#if NETSTANDARD2_0 -using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Agreement; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Crypto.Macs; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; -#endif - #if CURVE25519 using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.X509; @@ -50,165 +39,9 @@ namespace Opc.Ua /// Represents a cryptographic nonce used for secure communication. /// -#if NETSTANDARD2_0 - public class ECDHKeyPairGenerator - { - public static (AsymmetricCipherKeyPair, ECDomainParameters) GenerateKeyPair(X9ECParameters eCParameters) - { - ECDomainParameters domainParameters = new ECDomainParameters(eCParameters.Curve, - eCParameters.G, eCParameters.N, eCParameters.H, eCParameters.GetSeed()); - ECKeyGenerationParameters keyGenParameters = new ECKeyGenerationParameters(domainParameters, new SecureRandom()); - Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator generator = new Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator(); - generator.Init(keyGenParameters); - return (generator.GenerateKeyPair(), domainParameters); - } - - public static (AsymmetricCipherKeyPair, ECDomainParameters) GenerateKeyPair(string curveName) - { - X9ECParameters eCParameters = ECNamedCurveTable.GetByName(curveName); - return GenerateKeyPair(eCParameters); - } - - public static byte[] DeriveKeyFromHmac(AsymmetricCipherKeyPair localKeyPair, ECPublicKeyParameters remotePublicKey, byte[] salt, byte[] info, int derivedKeyLength) - { - // Perform ECDH key agreement - ECDHBasicAgreement agreement = new ECDHBasicAgreement(); - agreement.Init(localKeyPair.Private); - BigInteger sharedSecret = agreement.CalculateAgreement(remotePublicKey); - - byte[] sharedSecretBytes = sharedSecret.ToByteArrayUnsigned(); - - // Setup HMAC-based KDF - HMac hmac = new HMac(new Sha256Digest()); - hmac.Init(new KeyParameter(sharedSecretBytes)); - - // KDF parameters - int hashLength = hmac.GetMacSize(); - int numBlocks = (int)Math.Ceiling((double)derivedKeyLength / hashLength); - byte[] derivedKey = new byte[derivedKeyLength]; - - // HMAC-based KDF as per HKDF - byte[] previousBlock = new byte[0]; - for (int blockIndex = 1; blockIndex <= numBlocks; blockIndex++) - { - hmac.BlockUpdate(previousBlock, 0, previousBlock.Length); - hmac.BlockUpdate(salt, 0, salt.Length); - hmac.BlockUpdate(new byte[] { (byte)blockIndex }, 0, 1); - hmac.BlockUpdate(info, 0, info.Length); - - byte[] block = new byte[hashLength]; - hmac.DoFinal(block, 0); - previousBlock = block; - - Array.Copy(block, 0, derivedKey, (blockIndex - 1) * hashLength, Math.Min(hashLength, derivedKey.Length - (blockIndex - 1) * hashLength)); - } - - return derivedKey; - } - - public static string TranslateMsCurveToBcName(ECCurve eccCurve) - { - if (CurveNameMap.TryGetValue(eccCurve.Oid.FriendlyName, out string bcCurveName)) - { - X9ECParameters ecParameters = ECNamedCurveTable.GetByName(bcCurveName); - if (ecParameters != null) - { - return bcCurveName; - } - } - throw new NotSupportedException($"Curve {eccCurve.Oid.FriendlyName} is not supported"); - } - - public static string TranslateBcCurveNameToMs(string bcCurveName) - { - foreach (var kvp in CurveNameMap) - { - if (kvp.Value.Equals(bcCurveName, StringComparison.OrdinalIgnoreCase)) - { - return kvp.Key; - } - } - - throw new NotSupportedException($"Curve {bcCurveName} is not supported"); - } - - private static readonly Dictionary CurveNameMap = new Dictionary { - { "nistp256", "P-256" }, - { "nistP384", "P-384" }, - { "nistP521", "P-521" }, - { "brainpoolP256r1", "brainpoolp256r1" }, - { "brainpoolP384r1", "brainpoolp384r1" }, - { "brainpoolP521r1", "brainpoolp521r1" } - }; - - } - - public class BCECDiffieHellman - { - public AsymmetricCipherKeyPair PublicKey - { - get - { - return m_asymmetricCipherKeyPair; - } - } - public (AsymmetricCipherKeyPair, ECDomainParameters) Create(string bcCurveName) - { - m_curveName = bcCurveName; - (m_asymmetricCipherKeyPair, m_domainParameters) = ECDHKeyPairGenerator.GenerateKeyPair(bcCurveName); - - return (m_asymmetricCipherKeyPair, m_domainParameters); - } - public byte[] DeriveKeyFromHmac(AsymmetricCipherKeyPair localKeyPair, ECPublicKeyParameters remotePublicKey, byte[] salt, byte[] info, int derivedKeyLength) - { - // Perform ECDH key agreement - ECDHBasicAgreement agreement = new ECDHBasicAgreement(); - agreement.Init(localKeyPair.Private); - BigInteger sharedSecret = agreement.CalculateAgreement(remotePublicKey); - - byte[] sharedSecretBytes = sharedSecret.ToByteArrayUnsigned(); - - // Setup HMAC-based KDF - HMac hmac = new HMac(new Sha256Digest()); - hmac.Init(new KeyParameter(sharedSecretBytes)); - - // KDF parameters - int hashLength = hmac.GetMacSize(); - int numBlocks = (int)Math.Ceiling((double)derivedKeyLength / hashLength); - byte[] derivedKey = new byte[derivedKeyLength]; - - // HMAC-based KDF as per HKDF - byte[] previousBlock = new byte[0]; - for (int blockIndex = 1; blockIndex <= numBlocks; blockIndex++) - { - hmac.BlockUpdate(previousBlock, 0, previousBlock.Length); - hmac.BlockUpdate(salt, 0, salt.Length); - hmac.BlockUpdate(new byte[] { (byte)blockIndex }, 0, 1); - hmac.BlockUpdate(info, 0, info.Length); - - byte[] block = new byte[hashLength]; - hmac.DoFinal(block, 0); - previousBlock = block; - - Array.Copy(block, 0, derivedKey, (blockIndex - 1) * hashLength, Math.Min(hashLength, derivedKey.Length - (blockIndex - 1) * hashLength)); - } - - return derivedKey; - } - - private string m_curveName; - private ECDomainParameters m_domainParameters; - private AsymmetricCipherKeyPair m_asymmetricCipherKeyPair; - } -#endif - public class Nonce : IDisposable { -#if NETSTANDARD2_0 - private AsymmetricCipherKeyPair m_ecdh; -#else private ECDiffieHellman m_ecdh; -#endif #if CURVE25519 private AsymmetricCipherKeyPair m_bcKeyPair; @@ -264,9 +97,7 @@ protected virtual void Dispose(bool disposing) { if (m_ecdh != null) { -#if !NETSTANDARD2_0 m_ecdh.Dispose(); -#endif m_ecdh = null; } } @@ -332,15 +163,6 @@ public byte[] DeriveKey(Nonce remoteNonce, byte[] salt, HashAlgorithmName algori #endif if (m_ecdh != null) { -#if NETSTANDARD2_0 - var derivedKey = ECDHKeyPairGenerator.DeriveKeyFromHmac(m_ecdh, - (ECPublicKeyParameters)remoteNonce.m_ecdh.Public, - salt, - null, - length); - - return derivedKey; -#else var secret = m_ecdh.DeriveKeyFromHmac(remoteNonce.m_ecdh.PublicKey, algorithm, salt, null, null); byte[] output = new byte[length]; @@ -377,8 +199,6 @@ public byte[] DeriveKey(Nonce remoteNonce, byte[] salt, HashAlgorithmName algori } return output; - -#endif } return Data; @@ -481,30 +301,6 @@ private static Nonce CreateNonceForCurve448() /// A new Nonce instance. private static Nonce CreateNonce(ECCurve curve) { -#if NETSTANDARD2_0 - (var keyPair, _) = ECDHKeyPairGenerator.GenerateKeyPair(ECDHKeyPairGenerator.TranslateMsCurveToBcName(curve)); - - // Get the public key parameters - var ecPublicKeyParameters = (ECPublicKeyParameters)keyPair.Public; - var q = ecPublicKeyParameters.Q.Normalize(); - - // Get the byte arrays for X and Y coordinates of the ppublic key - byte[] qx = q.XCoord.GetEncoded(); - byte[] qy = q.YCoord.GetEncoded(); - - // Combine the X and Y coordinate byte arrays into a singe byte array - byte[] senderNonce = new byte[qx.Length + qy.Length]; - Array.Copy(qx, 0, senderNonce, 0, qx.Length); - Array.Copy(qy, 0, senderNonce, qx.Length, qy.Length); - - // Create and return the Nonce object - var nonce = new Nonce { - Data = senderNonce, - m_ecdh = keyPair - }; - - return nonce; -#else var ecdh = (ECDiffieHellman)ECDiffieHellman.Create(curve); var ecdhParameters = ecdh.ExportParameters(false); int xLen = ecdhParameters.Q.X.Length; @@ -519,9 +315,6 @@ private static Nonce CreateNonce(ECCurve curve) }; return nonce; -#endif - - } /// @@ -620,24 +413,6 @@ private static Nonce CreateNonce(ECCurve curve, byte[] nonceData) Buffer.BlockCopy(nonceData, 0, qx, 0, keyLength / 2); Buffer.BlockCopy(nonceData, keyLength / 2, qy, 0, keyLength / 2); -#if NETSTANDARD2_0 - string bcCurveName = ECDHKeyPairGenerator.TranslateMsCurveToBcName(curve); - var bcCurve = X9ECParameters.GetInstance( - ECNamedCurveTable.GetByName(bcCurveName) - ).Curve; - - var q = bcCurve.CreatePoint( - new BigInteger(1, qx), - new BigInteger(1, qy) - ); - - ECPublicKeyParameters eCPublicKeyParameters = new ECPublicKeyParameters(q, new ECDomainParameters(bcCurve, null, null)); - ECPrivateKeyParameters eCPrivateKeyParameters = new ECPrivateKeyParameters(new BigInteger(1, nonceData), new ECDomainParameters(bcCurve, null, null)); - nonce.m_ecdh = new AsymmetricCipherKeyPair(eCPublicKeyParameters, eCPrivateKeyParameters); - - return nonce; - -#else var ecdhParameters = new ECParameters { Curve = curve, Q = { X = qx, Y = qy } @@ -646,7 +421,6 @@ private static Nonce CreateNonce(ECCurve curve, byte[] nonceData) nonce.m_ecdh = ECDiffieHellman.Create(ecdhParameters); return nonce; -#endif } } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs index 600d59a50..a8d616981 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs @@ -524,7 +524,7 @@ public static bool VerifyECDsaKeyPair( X509Certificate2 certWithPrivateKey, bool throwOnError = false) { -#if ECC_SUPPORT && !NETSTANDARD2_0 +#if ECC_SUPPORT return X509PfxUtils.VerifyECDsaKeyPair(certWithPublicKey, certWithPrivateKey, throwOnError); #else throw new NotSupportedException(); diff --git a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs index bb980bfc9..1eb4e07a8 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs @@ -21,12 +21,14 @@ namespace Opc.Ua /// The UserIdentityToken class. /// public partial class UserIdentityToken - { + { #region Public Methods /// /// Encrypts the token (implemented by the subclass). /// - [Obsolete("Use Encrypt(X509Certificate2, byte[], string securityPolicyUri, Nonce, X509Certificate2, X509Certificate2Collection, bool) ")] +#if ECC_SUPPORT + [Obsolete("Use Encrypt(X509Certificate2, byte[], string securityPolicyUri, Nonce, X509Certificate2, X509Certificate2Collection, bool) ")] +#endif public virtual void Encrypt(X509Certificate2 certificate, byte[] receiverNonce, string securityPolicyUri) { } @@ -34,17 +36,21 @@ public virtual void Encrypt(X509Certificate2 certificate, byte[] receiverNonce, /// /// Decrypts the token (implemented by the subclass). /// +#if ECC_SUPPORT [Obsolete("Use Decrypt(X509Certificate2, Nonce, string, Nonce, X509Certificate2, X509Certificate2Collection, CertificateValidator) ")] +#endif public virtual void Decrypt(X509Certificate2 certificate, byte[] receiverNonce, string securityPolicyUri) { } + +#if ECC_SUPPORT /// /// Encrypts the token (implemented by the subclass). /// public virtual void Encrypt( X509Certificate2 receiverCertificate, - byte[] receiverNonce, + byte[] receiverNonce, string securityPolicyUri, Nonce receiverEphemeralKey = null, X509Certificate2 senderCertificate = null, @@ -57,8 +63,8 @@ public virtual void Encrypt( /// Decrypts the token (implemented by the subclass). /// public virtual void Decrypt( - X509Certificate2 certificate, - Nonce receiverNonce, + X509Certificate2 certificate, + Nonce receiverNonce, string securityPolicyUri, Nonce ephemeralKey = null, X509Certificate2 senderCertificate = null, @@ -66,6 +72,7 @@ public virtual void Decrypt( CertificateValidator validator = null) { } +#endif /// /// Creates a signature with the token (implemented by the subclass). @@ -82,7 +89,7 @@ public virtual bool Verify(byte[] dataToVerify, SignatureData signatureData, str { return true; } - #endregion +#endregion } /// @@ -90,7 +97,7 @@ public virtual bool Verify(byte[] dataToVerify, SignatureData signatureData, str /// public partial class UserNameIdentityToken { - #region Public Properties +#region Public Properties /// /// The decrypted password associated with the token. /// @@ -105,7 +112,9 @@ public string DecryptedPassword /// /// Encrypts the DecryptedPassword using the EncryptionAlgorithm and places the result in Password /// +#if ECC_SUPPORT [Obsolete] +#endif public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { if (m_decryptedPassword == null) @@ -137,7 +146,9 @@ public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, s /// /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword /// +#if ECC_SUPPORT [Obsolete] +#endif public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { // handle no encryption. @@ -189,6 +200,8 @@ public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, s /// /// Encrypts the DecryptedPassword using the EncryptionAlgorithm and places the result in Password /// + /// +#if ECC_SUPPORT public override void Encrypt( X509Certificate2 receiverCertificate, byte[] receiverNonce, @@ -225,7 +238,6 @@ public override void Encrypt( m_password = encryptedData.Data; m_encryptionAlgorithm = encryptedData.Algorithm; } - // handle ECC encryption. else { @@ -302,7 +314,6 @@ public override void Decrypt( // verify the sender's nonce. int startOfNonce = decryptedPassword.Length; - if (receiverNonce != null) { startOfNonce -= receiverNonce.Data.Length; @@ -318,11 +329,10 @@ public override void Decrypt( throw new ServiceResultException(StatusCodes.BadIdentityTokenRejected); } } - + // convert to UTF-8. m_decryptedPassword = new UTF8Encoding().GetString(decryptedPassword, 0, startOfNonce); } - // handle ECC encryption. else { @@ -339,11 +349,13 @@ public override void Decrypt( m_decryptedPassword = new UTF8Encoding().GetString(plainText); } } +#endif + #endregion #region Private Fields private string m_decryptedPassword; - #endregion +#endregion } /// @@ -351,7 +363,7 @@ public override void Decrypt( /// public partial class X509IdentityToken { - #region Public Properties +#region Public Properties /// /// The certificate associated with the token. /// @@ -367,9 +379,9 @@ public X509Certificate2 Certificate } set { m_certificate = value; } } - #endregion +#endregion - #region Public Methods +#region Public Methods /// /// Creates a signature with the token. /// @@ -421,11 +433,11 @@ public override bool Verify(byte[] dataToVerify, SignatureData signatureData, st throw ServiceResultException.Create(StatusCodes.BadIdentityTokenInvalid, e, "Could not verify user signature!"); } } - #endregion +#endregion - #region Private Fields +#region Private Fields private X509Certificate2 m_certificate; - #endregion +#endregion } /// @@ -456,7 +468,7 @@ public enum IssuedTokenType /// public partial class IssuedIdentityToken { - #region Public Properties +#region Public Properties /// /// The type of issued token. /// @@ -480,7 +492,9 @@ public byte[] DecryptedTokenData /// /// Encrypts the DecryptedTokenData using the EncryptionAlgorithm and places the result in Password /// +#if ECC_SUPPORT [Obsolete] +#endif public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { // handle no encryption. @@ -505,7 +519,9 @@ public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, s /// /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword /// +#if ECC_SUPPORT [Obsolete] +#endif public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { // handle no encryption. @@ -546,6 +562,7 @@ public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, s Array.Copy(decryptedTokenData, m_decryptedTokenData, startOfNonce); } +#if ECC_SUPPORT /// /// Encrypts the DecryptedTokenData using the EncryptionAlgorithm and places the result in Password /// @@ -626,6 +643,7 @@ public override void Decrypt( m_decryptedTokenData = new byte[startOfNonce]; Array.Copy(decryptedTokenData, m_decryptedTokenData, startOfNonce); } +#endif /// /// Creates a signature with the token. @@ -642,10 +660,10 @@ public override bool Verify(byte[] dataToVerify, SignatureData signatureData, st { return true; } - #endregion +#endregion - #region Private Fields +#region Private Fields private byte[] m_decryptedTokenData; - #endregion +#endregion } } From c6a10d7185b238cefbc34cb610a965319a6c1f52 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Wed, 15 Nov 2023 16:58:01 +0200 Subject: [PATCH 29/80] Added UserIdentity encryption positive roundtrip unit tests --- .../Stack/Types/UserIdentityToken.cs | 8 +- Tests/Opc.Ua.Client.Tests/ClientTest.cs | 143 +++++++++++++++--- .../Opc.Ua.Client.Tests.csproj | 1 + 3 files changed, 131 insertions(+), 21 deletions(-) diff --git a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs index 1eb4e07a8..302ce4891 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs @@ -22,7 +22,7 @@ namespace Opc.Ua /// public partial class UserIdentityToken { - #region Public Methods +#region Public Methods /// /// Encrypts the token (implemented by the subclass). /// @@ -108,7 +108,7 @@ public string DecryptedPassword } #endregion - #region Public Methods +#region Public Methods /// /// Encrypts the DecryptedPassword using the EncryptionAlgorithm and places the result in Password /// @@ -353,7 +353,7 @@ public override void Decrypt( #endregion - #region Private Fields +#region Private Fields private string m_decryptedPassword; #endregion } @@ -488,7 +488,7 @@ public byte[] DecryptedTokenData } #endregion - #region Public Methods +#region Public Methods /// /// Encrypts the DecryptedTokenData using the EncryptionAlgorithm and places the result in Password /// diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs index 5e2fd4b66..06cfb99fd 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs @@ -40,6 +40,11 @@ using Opc.Ua.Configuration; using Opc.Ua.Server.Tests; +using Opc.Ua.Security.Certificates.Tests; +using System.Security.Cryptography; +using System.Runtime.InteropServices; +using Opc.Ua.Security.Certificates; + namespace Opc.Ua.Client.Tests { /// @@ -1342,36 +1347,140 @@ public void ReadBuildInfo() /// Open a session on a channel using ECC encrypted UserIdentityToken /// [Test, Combinatorial, Order(10100)] - public async Task OpenSessionECCUserIdentityToken( + public async Task OpenSessionECCUserNamePwdIdentityToken( [Values(SecurityPolicies.ECC_nistP256, SecurityPolicies.ECC_nistP384, SecurityPolicies.ECC_brainpoolP256r1, - SecurityPolicies.ECC_brainpoolP384r1)] string securityPolicy, - [Values(false)] bool anonymous) + SecurityPolicies.ECC_brainpoolP384r1)] string securityPolicy) { - IUserIdentity userIdentity = anonymous ? new UserIdentity() : new UserIdentity("user1", "password"); + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || + (securityPolicy != SecurityPolicies.ECC_brainpoolP256r1) || + (securityPolicy != SecurityPolicies.ECC_brainpoolP384r1)) + { + IUserIdentity userIdentity = new UserIdentity("user1", "password"); - // the first channel determines the endpoint - ConfiguredEndpoint endpoint = await ClientFixture.GetEndpointAsync(ServerUrl, securityPolicy, Endpoints).ConfigureAwait(false); - Assert.NotNull(endpoint); + // the first channel determines the endpoint + ConfiguredEndpoint endpoint = await ClientFixture.GetEndpointAsync(ServerUrl, securityPolicy, Endpoints).ConfigureAwait(false); + Assert.NotNull(endpoint); - UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, userIdentity.IssuedTokenType); - if (identityPolicy == null) - { - Assert.Ignore($"No UserTokenPolicy found for {userIdentity.TokenType} / {userIdentity.IssuedTokenType}"); + UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, userIdentity.IssuedTokenType); + if (identityPolicy == null) + { + Assert.Ignore($"No UserTokenPolicy found for {userIdentity.TokenType} / {userIdentity.IssuedTokenType}"); + } + + // the active channel + ISession session1 = await ClientFixture.ConnectAsync(endpoint, userIdentity).ConfigureAwait(false); + Assert.NotNull(session1); + + ServerStatusDataType value1 = (ServerStatusDataType)session1.ReadValue(VariableIds.Server_ServerStatus, typeof(ServerStatusDataType)); + Assert.NotNull(value1); } - // the active channel - ISession session1 = await ClientFixture.ConnectAsync(endpoint, userIdentity).ConfigureAwait(false); - Assert.NotNull(session1); + } - ServerStatusDataType value1 = (ServerStatusDataType)session1.ReadValue(VariableIds.Server_ServerStatus, typeof(ServerStatusDataType)); - Assert.NotNull(value1); - + /// + /// Open a session on a channel using ECC encrypted IssuedIdentityToken + /// + [Test, Combinatorial, Order(10200)] + public async Task OpenSessionECCIssuedIdentityToken( + [Values(SecurityPolicies.ECC_nistP256, + SecurityPolicies.ECC_nistP384, + SecurityPolicies.ECC_brainpoolP256r1, + SecurityPolicies.ECC_brainpoolP384r1)] string securityPolicy) + { + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || + (securityPolicy != SecurityPolicies.ECC_brainpoolP256r1) || + (securityPolicy != SecurityPolicies.ECC_brainpoolP384r1)) + + { + var identityToken = "fakeTokenString"; + + var issuedToken = new IssuedIdentityToken() { + IssuedTokenType = IssuedTokenType.JWT, + PolicyId = Profiles.JwtUserToken, + DecryptedTokenData = Encoding.UTF8.GetBytes(identityToken) + }; + + IUserIdentity userIdentity = new UserIdentity(issuedToken); + + // the first channel determines the endpoint + ConfiguredEndpoint endpoint = await ClientFixture.GetEndpointAsync(ServerUrl, securityPolicy, Endpoints).ConfigureAwait(false); + Assert.NotNull(endpoint); + + UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, userIdentity.IssuedTokenType); + if (identityPolicy == null) + { + Assert.Ignore($"No UserTokenPolicy found for {userIdentity.TokenType} / {userIdentity.IssuedTokenType}"); + } + + // the active channel + ISession session1 = await ClientFixture.ConnectAsync(endpoint, userIdentity).ConfigureAwait(false); + Assert.NotNull(session1); + + ServerStatusDataType value1 = (ServerStatusDataType)session1.ReadValue(VariableIds.Server_ServerStatus, typeof(ServerStatusDataType)); + Assert.NotNull(value1); + } } + /// + /// Open a session on a channel using ECC encrypted UserCertificateIdentityToken + /// + [Test, Combinatorial, Order(10300)] + public async Task OpenSessionECCUserCertIdentityToken( + [Values(SecurityPolicies.ECC_nistP256, + SecurityPolicies.ECC_nistP384, + SecurityPolicies.ECC_brainpoolP256r1, + SecurityPolicies.ECC_brainpoolP384r1)] string securityPolicy) + { + + var eccCurveHashPairs = new ECCurveHashPairCollection { + { ECCurve.NamedCurves.nistP256, HashAlgorithmName.SHA256 }, + { ECCurve.NamedCurves.nistP384, HashAlgorithmName.SHA384 } }; + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + eccCurveHashPairs.AddRange(new ECCurveHashPairCollection { + { ECCurve.NamedCurves.brainpoolP256r1, HashAlgorithmName.SHA256 }, + { ECCurve.NamedCurves.brainpoolP384r1, HashAlgorithmName.SHA384 }}); + } + + foreach (var eccurveHashPair in eccCurveHashPairs) + { + string extractedFriendlyNamae = null; + string[] friendlyNameContext = securityPolicy.Split('_'); + if (friendlyNameContext.Length > 1) + { + extractedFriendlyNamae = friendlyNameContext[1]; + } + if (eccurveHashPair.Curve.Oid.FriendlyName.Contains(extractedFriendlyNamae)) + { + X509Certificate2 cert = CertificateBuilder.Create("CN=Client Test ECC Subject, O=OPC Foundation") + .SetECCurve(eccurveHashPair.Curve) + .CreateForECDsa(); + + IUserIdentity userIdentity = new UserIdentity(cert); + // the first channel determines the endpoint + ConfiguredEndpoint endpoint = await ClientFixture.GetEndpointAsync(ServerUrl, securityPolicy, Endpoints).ConfigureAwait(false); + Assert.NotNull(endpoint); + + UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, userIdentity.IssuedTokenType); + if (identityPolicy == null) + { + Assert.Ignore($"No UserTokenPolicy found for {userIdentity.TokenType} / {userIdentity.IssuedTokenType}"); + } + + // the active channel + ISession session1 = await ClientFixture.ConnectAsync(endpoint, userIdentity).ConfigureAwait(false); + Assert.NotNull(session1); + + ServerStatusDataType value1 = (ServerStatusDataType)session1.ReadValue(VariableIds.Server_ServerStatus, typeof(ServerStatusDataType)); + Assert.NotNull(value1); + } + } + } #endregion #region Benchmarks diff --git a/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj b/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj index b47a447cc..989b57fc4 100644 --- a/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj +++ b/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj @@ -29,6 +29,7 @@ + From 57a79fb17febe14bf2713f596f82ea80fd442da8 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 16 Nov 2023 13:33:23 +0200 Subject: [PATCH 30/80] Propagated clientIssuerCertificates to UserIdentityToken encryption --- .../Opc.Ua.Server/Server/StandardServer.cs | 12 +++ Libraries/Opc.Ua.Server/Session/Session.cs | 29 +++++- .../Opc.Ua.Server/Session/SessionManager.cs | 89 +++++++++++++++---- 3 files changed, 108 insertions(+), 22 deletions(-) diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs index 48542eeee..b1c9dd13c 100644 --- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs +++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs @@ -367,6 +367,8 @@ public override ResponseHeader CreateSession( requireEncryption = true; } + X509Certificate2Collection clientIssuerCertifficates = null; + // validate client application instance certificate. X509Certificate2 parsedClientCertificate = null; @@ -377,6 +379,15 @@ public override ResponseHeader CreateSession( X509Certificate2Collection clientCertificateChain = Utils.ParseCertificateChainBlob(clientCertificate); parsedClientCertificate = clientCertificateChain[0]; + if (clientCertificateChain.Count > 1) + { + clientIssuerCertifficates = new X509Certificate2Collection(); + for (int i = 1; i < clientCertificateChain.Count; i++) + { + clientIssuerCertifficates.Add(clientCertificateChain[i]); + } + } + if (context.SecurityPolicyUri != SecurityPolicies.None) { string certificateApplicationUri = X509Utils.GetApplicationUriFromCertificate(parsedClientCertificate); @@ -434,6 +445,7 @@ public override ResponseHeader CreateSession( clientDescription, endpointUrl, parsedClientCertificate, + clientIssuerCertifficates, requestedSessionTimeout, maxResponseMessageSize, out sessionId, diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs index 31dcf9478..e979be92b 100644 --- a/Libraries/Opc.Ua.Server/Session/Session.cs +++ b/Libraries/Opc.Ua.Server/Session/Session.cs @@ -42,6 +42,7 @@ public class Session : IDisposable { #region Constructors +#if ECC_SUPPORT /// /// Initializes a new instance of the class. /// @@ -55,30 +56,50 @@ public class Session : IDisposable /// Application description for the client application. /// The endpoint URL. /// The client certificate. + /// The client certifiate chain /// The session timeout. /// The maximum size of a response message /// The max request age. /// The maximum number of browse continuation points. /// The maximum number of history continuation points. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] - #if ECC_SUPPORT public Session( OperationContext context, IServerInternal server, X509Certificate2 serverCertificate, NodeId authenticationToken, byte[] clientNonce, - Nonce serverNonce, + Nonce serverNonce, string sessionName, ApplicationDescription clientDescription, string endpointUrl, X509Certificate2 clientCertificate, + X509Certificate2Collection clientCertificateChain, double sessionTimeout, uint maxResponseMessageSize, double maxRequestAge, int maxBrowseContinuationPoints, int maxHistoryContinuationPoints) #else + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The Server object. + /// The server certificate. + /// The unique private identifier assigned to the Session. + /// The client nonce. + /// The server nonce. + /// The name assigned to the Session. + /// Application description for the client application. + /// The endpoint URL. + /// The client certificate. + /// The session timeout. + /// The maximum size of a response message + /// The max request age. + /// The maximum number of browse continuation points. + /// The maximum number of history continuation points. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] public Session( OperationContext context, IServerInternal server, @@ -115,9 +136,9 @@ public Session( m_clientCertificate = clientCertificate; #if ECC_SUPPORT - m_clientIssuerCertificates = null; + m_clientIssuerCertificates = clientCertificateChain; #endif - + m_secureChannelId = context.ChannelContext.SecureChannelId; m_maxResponseMessageSize = maxResponseMessageSize; m_maxRequestAge = maxRequestAge; diff --git a/Libraries/Opc.Ua.Server/Session/SessionManager.cs b/Libraries/Opc.Ua.Server/Session/SessionManager.cs index 3ad88fbe6..0c65d77de 100644 --- a/Libraries/Opc.Ua.Server/Session/SessionManager.cs +++ b/Libraries/Opc.Ua.Server/Session/SessionManager.cs @@ -153,6 +153,7 @@ public virtual Session CreateSession( ApplicationDescription clientDescription, string endpointUrl, X509Certificate2 clientCertificate, + X509Certificate2Collection clientCertificateChain, double requestedSessionTimeout, uint maxResponseMessageSize, out NodeId sessionId, @@ -241,10 +242,9 @@ public virtual Session CreateSession( clientDescription, endpointUrl, clientCertificate, + clientCertificateChain, revisedSessionTimeout, - maxResponseMessageSize, - m_maxRequestAge, - m_maxBrowseContinuationPoints); + maxResponseMessageSize); #else session = CreateSession( context, @@ -258,9 +258,7 @@ public virtual Session CreateSession( endpointUrl, clientCertificate, revisedSessionTimeout, - maxResponseMessageSize, - m_maxRequestAge, - m_maxBrowseContinuationPoints); + maxResponseMessageSize); #endif // get the session id. @@ -280,6 +278,42 @@ public virtual Session CreateSession( return session; } + /// + /// Creates a new session. + /// + [Obsolete("Use CreateSession that passes X509Certificate2Collection)")] + public virtual Session CreateSession( + OperationContext context, + X509Certificate2 serverCertificate, + string sessionName, + byte[] clientNonce, + ApplicationDescription clientDescription, + string endpointUrl, + X509Certificate2 clientCertificate, + double requestedSessionTimeout, + uint maxResponseMessageSize, + out NodeId sessionId, + out NodeId authenticationToken, + out byte[] serverNonce, + out double revisedSessionTimeout) + { + return CreateSession( + context, + serverCertificate, + sessionName, + clientNonce, + clientDescription, + endpointUrl, + clientCertificate, + null, + requestedSessionTimeout, + maxResponseMessageSize, + out sessionId, + out authenticationToken, + out serverNonce, + out revisedSessionTimeout); + } + /// /// Activates an existing session /// @@ -542,12 +576,12 @@ public virtual OperationContext ValidateRequest(RequestHeader requestHeader, Req throw new ServiceResultException(e, StatusCodes.BadUnexpectedError); } } -#endregion + #endregion -#region Protected Methods -/// -/// Creates a new instance of a session. -/// + #region Protected Methods + /// + /// Creates a new instance of a session. + /// #if ECC_SUPPORT protected virtual Session CreateSession( OperationContext context, @@ -560,10 +594,30 @@ protected virtual Session CreateSession( ApplicationDescription clientDescription, string endpointUrl, X509Certificate2 clientCertificate, + X509Certificate2Collection clientCertificateChain, double sessionTimeout, - uint maxResponseMessageSize, - int maxRequestAge, // TBD - Remove unused parameter. - int maxContinuationPoints) // TBD - Remove unused parameter. + uint maxResponseMessageSize) + { + Session session = new Session( + context, + m_server, + serverCertificate, + sessionCookie, + clientNonce, + serverNonce, + sessionName, + clientDescription, + endpointUrl, + clientCertificate, + clientCertificateChain, + sessionTimeout, + maxResponseMessageSize, + m_maxRequestAge, + m_maxBrowseContinuationPoints, + m_maxHistoryContinuationPoints); + + return session; + } #else protected virtual Session CreateSession( OperationContext context, @@ -577,10 +631,8 @@ protected virtual Session CreateSession( string endpointUrl, X509Certificate2 clientCertificate, double sessionTimeout, - uint maxResponseMessageSize, - int maxRequestAge, // TBD - Remove unused parameter. - int maxContinuationPoints) // TBD - Remove unused parameter. -#endif + uint maxResponseMessageSize) + { Session session = new Session( context, @@ -601,6 +653,7 @@ protected virtual Session CreateSession( return session; } +#endif /// /// Raises an event related to a session. From f37a392b8219eb1a7e8dbbda9ec9b96ff52e8b8d Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 16 Nov 2023 13:34:34 +0200 Subject: [PATCH 31/80] Save the userTokenSecurityPolicyUri per Session (needed in reconnect) --- Libraries/Opc.Ua.Client/Session.cs | 3 +-- Libraries/Opc.Ua.Client/SessionAsync.cs | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index e0ec7c792..494c592c1 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -2406,6 +2406,7 @@ public void Open( { userTokenSecurityPolicyUri = m_endpoint.Description.SecurityPolicyUri; } + m_userTokenSecurityPolicyUri = userTokenSecurityPolicyUri; RequestHeader requestHeader = new RequestHeader(); @@ -2419,11 +2420,9 @@ public void Open( }); requestHeader.AdditionalHeader = new ExtensionObject(parameters); - m_userTokenSecurityPolicyUri = userTokenSecurityPolicyUri; } bool successCreateSession = false; - ResponseHeader responseHeader = null; //if security none, first try to connect without certificate diff --git a/Libraries/Opc.Ua.Client/SessionAsync.cs b/Libraries/Opc.Ua.Client/SessionAsync.cs index e4466ea14..db0d94a64 100644 --- a/Libraries/Opc.Ua.Client/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/SessionAsync.cs @@ -131,6 +131,7 @@ public async Task OpenAsync( { userTokenSecurityPolicyUri = m_endpoint.Description.SecurityPolicyUri; } + m_userTokenSecurityPolicyUri = userTokenSecurityPolicyUri; RequestHeader requestHeader = new RequestHeader(); @@ -144,8 +145,8 @@ public async Task OpenAsync( }); requestHeader.AdditionalHeader = new ExtensionObject(parameters); - m_userTokenSecurityPolicyUri = userTokenSecurityPolicyUri; } + bool successCreateSession = false; CreateSessionResponse response = null; From 2c4d446fb859230265237552c2b0e4d2c8144a6f Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Tue, 21 Nov 2023 17:34:28 +0200 Subject: [PATCH 32/80] Added FindUserTokenPolicy methods which support providing tokenSecurityPolicy --- Libraries/Opc.Ua.Client/Session.cs | 13 +- .../Org.BouncyCastle/PEMWriter.cs | 32 +++-- .../Org.BouncyCastle/X509Utils.cs | 40 +++++-- Libraries/Opc.Ua.Server/Session/Session.cs | 4 +- .../Configuration/EndpointDescription.cs | 111 ++++++++++++++++++ Tests/Opc.Ua.Client.Tests/ClientTest.cs | 19 ++- .../ClientTestFramework.cs | 52 ++++++++ Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs | 4 +- Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 1 + 9 files changed, 250 insertions(+), 26 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index 494c592c1..32b00dd85 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -1344,7 +1344,9 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, endpoint.SecurityPolicyUri, dataToSign); // check that the user identity is supported by the endpoint. - UserTokenPolicy identityPolicy = endpoint.FindUserTokenPolicy(m_identity.TokenType, m_identity.IssuedTokenType); + UserTokenPolicy identityPolicy = endpoint.FindUserTokenPolicy(m_identity.TokenType, + m_identity.IssuedTokenType, + endpoint.SecurityPolicyUri); if (identityPolicy == null) { @@ -1362,6 +1364,7 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel { securityPolicyUri = endpoint.SecurityPolicyUri; } + m_userTokenSecurityPolicyUri = securityPolicyUri; // need to refresh the identity (reprompt for password, refresh token). if (m_RenewUserIdentity != null) @@ -2680,7 +2683,9 @@ public void UpdateSession(IUserIdentity identity, StringCollection preferredLoca } // check that the user identity is supported by the endpoint. - UserTokenPolicy identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identity.TokenType, identity.IssuedTokenType); + UserTokenPolicy identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identity.TokenType, + identity.IssuedTokenType, + securityPolicyUri); if (identityPolicy == null) { @@ -5304,12 +5309,12 @@ private void OpenValidateIdentity( identityToken = identity.GetIdentityToken(); // check that the user identity is supported by the endpoint. - identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identityToken.PolicyId); + identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identityToken.PolicyId, securityPolicyUri); if (identityPolicy == null) { // try looking up by TokenType if the policy id was not found. - identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identity.TokenType, identity.IssuedTokenType); + identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identity.TokenType, identity.IssuedTokenType, securityPolicyUri); if (identityPolicy == null) { diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMWriter.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMWriter.cs index b31ac967c..58facaa4d 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMWriter.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMWriter.cs @@ -52,14 +52,32 @@ public static byte[] ExportPrivateKeyAsPEM( string password = null ) { - if (!String.IsNullOrEmpty(password)) throw new ArgumentException("Export with password not supported on this platform.", nameof(password)); - RsaPrivateCrtKeyParameters privateKeyParameter = X509Utils.GetRsaPrivateKeyParameter(certificate); - // write private key as PKCS#8 - PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKeyParameter); - byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetDerEncoded(); - return EncodeAsPEM(serializedPrivateBytes, "PRIVATE KEY"); + bool isECDsaSignature = X509PfxUtils.IsECDsaSignature(certificate); + // check if certificate is valid for use as app/sw or user cert + if (!isECDsaSignature) + { + if (!String.IsNullOrEmpty(password)) throw new ArgumentException("Export with password not supported on this platform.", nameof(password)); + RsaPrivateCrtKeyParameters privateKeyParameter = X509Utils.GetRsaPrivateKeyParameter(certificate); + // write private key as PKCS#8 + PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKeyParameter); + byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetDerEncoded(); + return EncodeAsPEM(serializedPrivateBytes, "PRIVATE KEY"); + } +#if ECC_SUPPORT + else + { + if (!String.IsNullOrEmpty(password)) throw new ArgumentException("Export with password not supported on this platform.", nameof(password)); + ECPrivateKeyParameters privateKeyParameter = X509Utils.GetECPrivateKeyParameter(certificate.GetECDsaPrivateKey()); + // write private key as PKCS#8 + PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKeyParameter); + byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetDerEncoded(); + return EncodeAsPEM(serializedPrivateBytes, "PRIVATE KEY"); + } +#else + return null; // Only on NETSTANDARD2_0 +#endif } - #endregion +#endregion } } #endif diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs index ea78ea47d..afe9af25e 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs @@ -33,7 +33,10 @@ using System.Linq; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +using System.Text.RegularExpressions; +using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; @@ -168,14 +171,37 @@ internal static ECPrivateKeyParameters GetECPrivateKeyParameter(ECDsa ec) { ECParameters ecParams = ec.ExportParameters(true); BigInteger d = new BigInteger(1, ecParams.D); - throw new NotImplementedException(nameof(GetECPrivateKeyParameter)); -#if TODO - ECDomainParameters parameters = new ECDomainParameters( - new Org.BouncyCastle.Math.EC.ECCurve( - new BigInteger(1, ecParams.Q)); - return new ECPrivateKeyParameters(d, parameters); -#endif + X9ECParameters curve = null; + if (!string.IsNullOrEmpty(ecParams.Curve.Oid.Value)) + { + var oid = new DerObjectIdentifier(ecParams.Curve.Oid.Value); + curve = ECNamedCurveTable.GetByOid(oid); + } + else if (!string.IsNullOrEmpty(ecParams.Curve.Oid.FriendlyName)) + { + // nist curve names do not contain "nist" in the bouncy castle ECNamedCurveTable + // for ex: the form is "P-256" while the microsoft is "nistP256" + // brainpool bouncy castle curve names are identic to the microsoft ones + string msFriendlyName = ecParams.Curve.Oid.FriendlyName; + string bcFriendlyName = msFriendlyName; + string nistCurveName = "nist"; + if (msFriendlyName.StartsWith(nistCurveName)) + { + string patternMatch = @"(.*?)(\d+)$"; // divide string in two capture groups (string & numeric) + bcFriendlyName = Regex.Replace(msFriendlyName, patternMatch, m => { + string lastChar = m.Groups[1].Value.Length > 0 ? m.Groups[1].Value.Last().ToString() : ""; + string number = m.Groups[2].Value; + return lastChar + "-" + number; + }); + } + curve = ECNamedCurveTable.GetByName(bcFriendlyName); + } + + if (curve == null) throw new ArgumentException("Curve OID is not recognized ", ecParams.Curve.Oid.ToString()); + ECDomainParameters domainParameters = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H); + return new ECPrivateKeyParameters(d, domainParameters); + } #endif diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs index e979be92b..cb47c5129 100644 --- a/Libraries/Opc.Ua.Server/Session/Session.cs +++ b/Libraries/Opc.Ua.Server/Session/Session.cs @@ -1011,7 +1011,7 @@ private UserIdentityToken ValidateUserIdentityToken( throw ServiceResultException.Create(StatusCodes.BadUserAccessDenied, "Invalid user identity token provided."); } - policy = m_endpoint.FindUserTokenPolicy(newToken.PolicyId); + policy = m_endpoint.FindUserTokenPolicy(newToken.PolicyId, m_endpoint.SecurityPolicyUri); if (policy == null) { throw ServiceResultException.Create(StatusCodes.BadUserAccessDenied, "User token policy not supported.", "Opc.Ua.Server.Session.ValidateUserIdentityToken"); @@ -1046,7 +1046,7 @@ private UserIdentityToken ValidateUserIdentityToken( } // find the user token policy. - policy = m_endpoint.FindUserTokenPolicy(token.PolicyId); + policy = m_endpoint.FindUserTokenPolicy(token.PolicyId, m_endpoint.SecurityPolicyUri); if (policy == null) { diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/EndpointDescription.cs b/Stack/Opc.Ua.Core/Stack/Configuration/EndpointDescription.cs index 9cee8b280..e1a78fa69 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/EndpointDescription.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/EndpointDescription.cs @@ -90,6 +90,7 @@ public Uri ProxyUrl /// /// Finds the user token policy with the specified id. /// + [Obsolete] public UserTokenPolicy FindUserTokenPolicy(string policyId) { foreach (UserTokenPolicy policy in m_userIdentityTokens) @@ -103,9 +104,52 @@ public UserTokenPolicy FindUserTokenPolicy(string policyId) return null; } + /// + /// Finds the user token policy with the specified id and securtyPolicyUri + /// + public UserTokenPolicy FindUserTokenPolicy(string policyId, string tokenSecurityPolicyUri) + { + UserTokenPolicy sameEncryptionAlgorithm = null; + UserTokenPolicy unspecifiedSecPolicy = null; + // The specified security policies take precedence + foreach (UserTokenPolicy policy in m_userIdentityTokens) + { + if (policy.PolicyId == policyId) + { + if (policy.SecurityPolicyUri == tokenSecurityPolicyUri) + { + return policy; + } + else if ( + policy.SecurityPolicyUri != null && tokenSecurityPolicyUri != null && + (EccUtils.IsEccPolicy(policy.SecurityPolicyUri) && EccUtils.IsEccPolicy(tokenSecurityPolicyUri)) || + (!EccUtils.IsEccPolicy(policy.SecurityPolicyUri) && !EccUtils.IsEccPolicy(tokenSecurityPolicyUri)) + ) + { + if (sameEncryptionAlgorithm == null) + { + sameEncryptionAlgorithm = policy; + } + } + else if (policy.SecurityPolicyUri == null) + { + unspecifiedSecPolicy = policy; + } + } + } + // The first token with the same encryption algorithm (RSA/ECC) follows + if (sameEncryptionAlgorithm != null) + { + return sameEncryptionAlgorithm; + } + // The first token with unspecified security policy follows / no policy + return unspecifiedSecPolicy; + } + /// /// Finds a token policy that matches the user identity specified. /// + [Obsolete] public UserTokenPolicy FindUserTokenPolicy(UserTokenType tokenType, XmlQualifiedName issuedTokenType) { if (issuedTokenType == null) @@ -119,6 +163,22 @@ public UserTokenPolicy FindUserTokenPolicy(UserTokenType tokenType, XmlQualified /// /// Finds a token policy that matches the user identity specified. /// + public UserTokenPolicy FindUserTokenPolicy(UserTokenType tokenType, + XmlQualifiedName issuedTokenType, + string tokenSecurityPolicyUri) + { + if (issuedTokenType == null) + { + return FindUserTokenPolicy(tokenType, (string)null, tokenSecurityPolicyUri); + } + + return FindUserTokenPolicy(tokenType, issuedTokenType.Namespace, tokenSecurityPolicyUri); + } + + /// + /// Finds a token policy that matches the user identity specified. + /// + [Obsolete] public UserTokenPolicy FindUserTokenPolicy(UserTokenType tokenType, string issuedTokenType) { // construct issuer type. @@ -145,6 +205,57 @@ public UserTokenPolicy FindUserTokenPolicy(UserTokenType tokenType, string issue // no policy found return null; } + + /// + /// Finds a token policy that matches the user identity specified. + /// + public UserTokenPolicy FindUserTokenPolicy(UserTokenType tokenType, + string issuedTokenType, + string tokenSecurityPolicyUri) + { + // construct issuer type. + string issuedTokenTypeText = issuedTokenType; + + UserTokenPolicy sameEncryptionAlgorithm = null; + UserTokenPolicy unspecifiedSecPolicy = null; + // The specified security policies take precedence + foreach (UserTokenPolicy policy in m_userIdentityTokens) + { + if ((policy.TokenType == tokenType) && (issuedTokenTypeText == policy.IssuedTokenType)) + { + if ((policy.SecurityPolicyUri == tokenSecurityPolicyUri) || (tokenType == UserTokenType.Anonymous)) + { + return policy; + } + else if ( + policy.SecurityPolicyUri != null && tokenSecurityPolicyUri != null && + (EccUtils.IsEccPolicy(policy.SecurityPolicyUri) && EccUtils.IsEccPolicy(tokenSecurityPolicyUri)) || + (!EccUtils.IsEccPolicy(policy.SecurityPolicyUri) && !EccUtils.IsEccPolicy(tokenSecurityPolicyUri)) + ) + { + if (sameEncryptionAlgorithm == null) + { + sameEncryptionAlgorithm = policy; + } + } + else if (policy.SecurityPolicyUri == null) + { + if (sameEncryptionAlgorithm == null) + { + unspecifiedSecPolicy = policy; + } + } + } + } + // The first token with the same encryption algorithm (RSA/ECC) follows + if (sameEncryptionAlgorithm != null) + { + return sameEncryptionAlgorithm; + } + // The first token with unspecified security policy follows / no policy + return unspecifiedSecPolicy; + + } #endregion #region Private Fields diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs index 06cfb99fd..72249f054 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs @@ -615,7 +615,9 @@ public async Task ReconnectSessionOnAlternateChannelWithSavedSessionSecrets(stri ConfiguredEndpoint endpoint = await ClientFixture.GetEndpointAsync(ServerUrl, securityPolicy, Endpoints).ConfigureAwait(false); Assert.NotNull(endpoint); - UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, userIdentity.IssuedTokenType); + UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, + userIdentity.IssuedTokenType, + endpoint.Description.SecurityPolicyUri); if (identityPolicy == null) { Assert.Ignore($"No UserTokenPolicy found for {userIdentity.TokenType} / {userIdentity.IssuedTokenType}"); @@ -1364,7 +1366,9 @@ public async Task OpenSessionECCUserNamePwdIdentityToken( ConfiguredEndpoint endpoint = await ClientFixture.GetEndpointAsync(ServerUrl, securityPolicy, Endpoints).ConfigureAwait(false); Assert.NotNull(endpoint); - UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, userIdentity.IssuedTokenType); + UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, + userIdentity.IssuedTokenType, + endpoint.Description.SecurityPolicyUri); if (identityPolicy == null) { Assert.Ignore($"No UserTokenPolicy found for {userIdentity.TokenType} / {userIdentity.IssuedTokenType}"); @@ -1401,7 +1405,7 @@ public async Task OpenSessionECCIssuedIdentityToken( var issuedToken = new IssuedIdentityToken() { IssuedTokenType = IssuedTokenType.JWT, PolicyId = Profiles.JwtUserToken, - DecryptedTokenData = Encoding.UTF8.GetBytes(identityToken) + DecryptedTokenData = Encoding.UTF8.GetBytes(identityToken), }; IUserIdentity userIdentity = new UserIdentity(issuedToken); @@ -1410,7 +1414,10 @@ public async Task OpenSessionECCIssuedIdentityToken( ConfiguredEndpoint endpoint = await ClientFixture.GetEndpointAsync(ServerUrl, securityPolicy, Endpoints).ConfigureAwait(false); Assert.NotNull(endpoint); - UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, userIdentity.IssuedTokenType); + UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, + userIdentity.IssuedTokenType, + securityPolicy); + if (identityPolicy == null) { Assert.Ignore($"No UserTokenPolicy found for {userIdentity.TokenType} / {userIdentity.IssuedTokenType}"); @@ -1466,7 +1473,9 @@ public async Task OpenSessionECCUserCertIdentityToken( ConfiguredEndpoint endpoint = await ClientFixture.GetEndpointAsync(ServerUrl, securityPolicy, Endpoints).ConfigureAwait(false); Assert.NotNull(endpoint); - UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, userIdentity.IssuedTokenType); + UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, + userIdentity.IssuedTokenType, + endpoint.Description.SecurityPolicyUri); if (identityPolicy == null) { Assert.Ignore($"No UserTokenPolicy found for {userIdentity.TokenType} / {userIdentity.IssuedTokenType}"); diff --git a/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs b/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs index cc33e6ff1..f25b62677 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs @@ -152,6 +152,58 @@ public async Task OneTimeSetUpAsync(TextWriter writer = null, bool securityNone ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add( new UserTokenPolicy(UserTokenType.IssuedToken) { IssuedTokenType = Opc.Ua.Profiles.JwtUserToken }); + + ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy(UserTokenType.UserName) { + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1" + }); + ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy(UserTokenType.UserName) { + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1" + }); + ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy(UserTokenType.UserName) { + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256" + }); + ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy(UserTokenType.UserName) { + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384" + }); + + ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy(UserTokenType.Certificate) { + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1" + }); + ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy(UserTokenType.Certificate) { + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1" + }); + ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy(UserTokenType.Certificate) { + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256" + }); + ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy(UserTokenType.Certificate) { + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384" + }); + + ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy(UserTokenType.IssuedToken) { + IssuedTokenType = Opc.Ua.Profiles.JwtUserToken, + PolicyId = Profiles.JwtUserToken, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1" + }); + ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy(UserTokenType.IssuedToken) + { + IssuedTokenType = Opc.Ua.Profiles.JwtUserToken, + PolicyId = Profiles.JwtUserToken, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1" + }); + ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy(UserTokenType.IssuedToken) + { + IssuedTokenType = Opc.Ua.Profiles.JwtUserToken, + PolicyId = Profiles.JwtUserToken, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256" + }); + ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy(UserTokenType.IssuedToken) + { + IssuedTokenType = Opc.Ua.Profiles.JwtUserToken, + PolicyId = Profiles.JwtUserToken, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384" + }); + + ReferenceServer = await ServerFixture.StartAsync(writer ?? TestContext.Out).ConfigureAwait(false); ReferenceServer.TokenValidator = this.TokenValidator; } diff --git a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs index 393fa0f50..634446570 100644 --- a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs +++ b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs @@ -470,7 +470,9 @@ public async Task ReconnectWithSavedSessionSecrets( ConfiguredEndpoint endpoint = await ClientFixture.GetEndpointAsync(ServerUrl, securityPolicy, Endpoints).ConfigureAwait(false); Assert.NotNull(endpoint); - UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, userIdentity.IssuedTokenType); + UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(userIdentity.TokenType, + userIdentity.IssuedTokenType, + endpoint.Description.SecurityPolicyUri); if (identityPolicy == null) { Assert.Ignore($"No UserTokenPolicy found for {userIdentity.TokenType} / {userIdentity.IssuedTokenType}"); diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index 7898f5c5c..849d521e3 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -1009,6 +1009,7 @@ public void VerifyUnregisterGoodApplications() [Test, Order(920)] public void UnregisterUnregisteredGoodApplications() { + AssertIgnoreTestWithoutGoodRegistration(); ConnectGDS(true); foreach (var application in m_goodApplicationTestSet) { From 7f00e7aa5256bb7c35b41926aeb1c503e858edf1 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 23 Nov 2023 13:09:50 +0200 Subject: [PATCH 33/80] Added eccServerEphemeralKeu to saved session secrets --- Libraries/Opc.Ua.Client/Session.cs | 48 +++++++++++++++---- Libraries/Opc.Ua.Client/SessionAsync.cs | 5 +- .../Opc.Ua.Client/SessionConfiguration.cs | 38 +++++++++++++++ .../Security/Certificates/Nonce.cs | 45 ++++++++++++++++- Tests/Opc.Ua.Client.Tests/ClientTest.cs | 9 ++++ Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs | 8 +++- 6 files changed, 140 insertions(+), 13 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index 32b00dd85..2b47972f4 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -34,6 +34,7 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -1269,6 +1270,10 @@ public bool ApplySessionConfiguration(SessionConfiguration sessionConfiguration) m_identity = sessionConfiguration.Identity; m_checkDomain = sessionConfiguration.CheckDomain; m_serverNonce = sessionConfiguration.ServerNonce; +# if ECC_SUPPORT + m_userTokenSecurityPolicyUri = sessionConfiguration.UserIdentityTokenPolicy; + m_eccServerEphemeralKey = sessionConfiguration.ServerEccEphemeralKey; +# endif SessionCreated(sessionConfiguration.SessionId, sessionConfiguration.AuthenticationToken); return true; @@ -1277,7 +1282,25 @@ public bool ApplySessionConfiguration(SessionConfiguration sessionConfiguration) /// public SessionConfiguration SaveSessionConfiguration(Stream stream = null) { +#if ECC_SUPPORT + var sessionConfiguration = new SessionConfiguration(this, m_serverNonce, m_userTokenSecurityPolicyUri, m_eccServerEphemeralKey, AuthenticationToken); +#else var sessionConfiguration = new SessionConfiguration(this, m_serverNonce, AuthenticationToken); +# endif + +#if ECC_SUPPORT + if (stream != null) + { + XmlWriterSettings settings = Utils.DefaultXmlWriterSettings(); + using (XmlWriter writer = XmlWriter.Create(stream, settings)) + { + DataContractSerializer serializer = new DataContractSerializer(typeof(SessionConfiguration), + new[] { typeof(UserIdentityToken), typeof(AnonymousIdentityToken), typeof(X509IdentityToken), + typeof(IssuedIdentityToken), typeof(UserIdentity), typeof(ECParameters) }); + serializer.WriteObject(writer, sessionConfiguration); + } + } +else if (stream != null) { XmlWriterSettings settings = Utils.DefaultXmlWriterSettings(); @@ -1289,6 +1312,7 @@ public SessionConfiguration SaveSessionConfiguration(Stream stream = null) serializer.WriteObject(writer, sessionConfiguration); } } +#endif return sessionConfiguration; } @@ -1386,12 +1410,12 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. -#if ECC_SUPPORT +#if ECC_SUPPORT identityToken.Encrypt( m_serverCertificate, m_serverNonce, m_userTokenSecurityPolicyUri, - m_eccServerEphermalKey, + m_eccServerEphemeralKey, m_instanceCertificate, m_instanceCertificateChain, m_endpoint.Description.SecurityMode != MessageSecurityMode.None); @@ -2542,12 +2566,12 @@ public void Open( SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. -#if ECC_SUPPORT +#if ECC_SUPPORT identityToken.Encrypt( serverCertificate, serverNonce, userTokenSecurityPolicyUri, - m_eccServerEphermalKey, + m_eccServerEphemeralKey, m_instanceCertificate, m_instanceCertificateChain, m_endpoint.Description.SecurityMode != MessageSecurityMode.None); @@ -2567,7 +2591,7 @@ public void Open( DiagnosticInfoCollection certificateDiagnosticInfos = null; // activate session. - ActivateSession( + responseHeader = ActivateSession( null, clientSignature, clientSoftwareCertificates, @@ -2578,6 +2602,8 @@ public void Open( out certificateResults, out certificateDiagnosticInfos); + ProcessResponseAdditionalHeader(responseHeader, serverCertificate); + if (certificateResults != null) { for (int i = 0; i < certificateResults.Count; i++) @@ -2724,12 +2750,12 @@ public void UpdateSession(IUserIdentity identity, StringCollection preferredLoca userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. -#if ECC_SUPPORT +#if ECC_SUPPORT identityToken.Encrypt( m_serverCertificate, serverNonce, m_userTokenSecurityPolicyUri, - m_eccServerEphermalKey, + m_eccServerEphemeralKey, m_instanceCertificate, m_instanceCertificateChain, m_endpoint.Description.SecurityMode != MessageSecurityMode.None); @@ -2743,7 +2769,7 @@ public void UpdateSession(IUserIdentity identity, StringCollection preferredLoca DiagnosticInfoCollection certificateDiagnosticInfos = null; // activate session. - ActivateSession( + ResponseHeader responseHeader = ActivateSession( null, clientSignature, clientSoftwareCertificates, @@ -2754,6 +2780,8 @@ public void UpdateSession(IUserIdentity identity, StringCollection preferredLoca out certificateResults, out certificateDiagnosticInfos); + ProcessResponseAdditionalHeader(responseHeader, m_serverCertificate); + // save nonce and new values. lock (SyncRoot) { @@ -6077,7 +6105,7 @@ protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHe "Could not verify signature on ECDHKey. User authentication not possible."); } #if ECC_SUPPORT - m_eccServerEphermalKey = Nonce.CreateNonce(m_userTokenSecurityPolicyUri, key.PublicKey); + m_eccServerEphemeralKey = Nonce.CreateNonce(m_userTokenSecurityPolicyUri, key.PublicKey); #endif } } @@ -6170,7 +6198,7 @@ protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHe private LinkedList m_outstandingRequests; private string m_userTokenSecurityPolicyUri; #if ECC_SUPPORT - private Nonce m_eccServerEphermalKey; + private Nonce m_eccServerEphemeralKey; #endif private readonly EndpointDescriptionCollection m_discoveryServerEndpoints; private readonly StringCollection m_discoveryProfileUris; diff --git a/Libraries/Opc.Ua.Client/SessionAsync.cs b/Libraries/Opc.Ua.Client/SessionAsync.cs index db0d94a64..213965b64 100644 --- a/Libraries/Opc.Ua.Client/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/SessionAsync.cs @@ -265,7 +265,7 @@ public async Task OpenAsync( serverCertificate, serverNonce, userTokenSecurityPolicyUri, - m_eccServerEphermalKey, + m_eccServerEphemeralKey, m_instanceCertificate, m_instanceCertificateChain, m_endpoint.Description.SecurityMode != MessageSecurityMode.None); @@ -292,6 +292,9 @@ public async Task OpenAsync( userTokenSignature, ct).ConfigureAwait(false); + // process additional header + ProcessResponseAdditionalHeader(activateResponse.ResponseHeader, serverCertificate); + serverNonce = activateResponse.ServerNonce; StatusCodeCollection certificateResults = activateResponse.Results; DiagnosticInfoCollection certificateDiagnosticInfos = activateResponse.DiagnosticInfos; diff --git a/Libraries/Opc.Ua.Client/SessionConfiguration.cs b/Libraries/Opc.Ua.Client/SessionConfiguration.cs index 3d67b80a7..7f422a976 100644 --- a/Libraries/Opc.Ua.Client/SessionConfiguration.cs +++ b/Libraries/Opc.Ua.Client/SessionConfiguration.cs @@ -29,6 +29,7 @@ using System.IO; using System.Runtime.Serialization; +using System.Security.Cryptography; using System.Xml; namespace Opc.Ua.Client @@ -40,6 +41,27 @@ namespace Opc.Ua.Client [DataContract(Namespace = Namespaces.OpcUaXsd)] public class SessionConfiguration { +#if ECC_SUPPORT + /// + /// Creates a session configuration + /// + internal SessionConfiguration(ISession session, + byte[] serverNonce, + string userIdentityTokenPolicy, + Nonce eccServerEphemeralKey, + NodeId authenthicationToken) + { + SessionName = session.SessionName; + SessionId = session.SessionId; + AuthenticationToken = authenthicationToken; + Identity = session.Identity; + ConfiguredEndpoint = session.ConfiguredEndpoint; + CheckDomain = session.CheckDomain; + ServerNonce = serverNonce; + ServerEccEphemeralKey = eccServerEphemeralKey; + UserIdentityTokenPolicy = userIdentityTokenPolicy; + } +#else /// /// Creates a session configuration /// @@ -53,6 +75,7 @@ internal SessionConfiguration(ISession session, byte[] serverNonce, NodeId authe CheckDomain = session.CheckDomain; ServerNonce = serverNonce; } +#endif /// /// Creates the session configuration from a stream. @@ -112,5 +135,20 @@ public static SessionConfiguration Create(Stream stream) /// [DataMember(IsRequired = true, Order = 70)] public byte[] ServerNonce { get; set; } + +#if ECC_SUPPORT + /// + /// The last server ecc ephemeral key received. + /// + [DataMember(IsRequired = true, Order = 80)] + public string UserIdentityTokenPolicy { get; set; } + + /// + /// The last server ecc ephemeral key received. + /// + [DataMember(IsRequired = true, Order = 90)] + public Nonce ServerEccEphemeralKey { get; set; } +#endif + } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs index 4b9e9bd8d..993868d37 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs @@ -18,6 +18,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Opc.Ua.Security.Certificates; +using System.Runtime.Serialization; #if CURVE25519 using Org.BouncyCastle.Pkcs; @@ -39,9 +40,11 @@ namespace Opc.Ua /// Represents a cryptographic nonce used for secure communication. /// - public class Nonce : IDisposable + [Serializable] + public class Nonce : IDisposable, ISerializable { private ECDiffieHellman m_ecdh; + #if CURVE25519 private AsymmetricCipherKeyPair m_bcKeyPair; @@ -422,6 +425,46 @@ private static Nonce CreateNonce(ECCurve curve, byte[] nonceData) return nonce; } + + /// + /// Custom deserialization + /// + /// + /// + protected Nonce(SerializationInfo info, StreamingContext context) + { + var curveName = info.GetString("CurveName"); + var ecParams = new ECParameters { + Curve = ECCurve.CreateFromFriendlyName(curveName), + Q = new ECPoint { + X = (byte[])info.GetValue("QX", typeof(byte[])), + Y = (byte[])info.GetValue("QY", typeof(byte[])), + } + }; + m_ecdh = ECDiffieHellman.Create(ecParams); + + Data = (byte[])info.GetValue("Data", typeof (byte[])); + } + + /// + /// Custom serializarion + /// + /// + /// + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (m_ecdh != null) + { + var ecParams = m_ecdh.ExportParameters(false); + info.AddValue("CurveName", ecParams.Curve.Oid.FriendlyName); + info.AddValue("QX", ecParams.Q.X); + info.AddValue("QY", ecParams.Q.Y); + } + if (Data != null) + { + info.AddValue("Data", Data); + } + } } } #endif diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs index 72249f054..23dad49d2 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs @@ -605,6 +605,14 @@ public async Task ReconnectSessionOnAlternateChannel(bool closeChannel) [TestCase(SecurityPolicies.None, false)] [TestCase(SecurityPolicies.Basic256Sha256, true)] [TestCase(SecurityPolicies.Basic256Sha256, false)] + [TestCase(SecurityPolicies.ECC_brainpoolP256r1, true)] + [TestCase(SecurityPolicies.ECC_brainpoolP256r1, false)] + [TestCase(SecurityPolicies.ECC_brainpoolP384r1, true)] + [TestCase(SecurityPolicies.ECC_brainpoolP384r1, false)] + [TestCase(SecurityPolicies.ECC_nistP256, true)] + [TestCase(SecurityPolicies.ECC_nistP256, false)] + [TestCase(SecurityPolicies.ECC_nistP384, true)] + [TestCase(SecurityPolicies.ECC_nistP384, false)] public async Task ReconnectSessionOnAlternateChannelWithSavedSessionSecrets(string securityPolicy, bool anonymous) { ServiceResultException sre; @@ -632,6 +640,7 @@ public async Task ReconnectSessionOnAlternateChannelWithSavedSessionSecrets(stri // save the session configuration var stream = new MemoryStream(); + session1.SaveSessionConfiguration(stream); var streamArray = stream.ToArray(); diff --git a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs index 634446570..c766deec2 100644 --- a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs +++ b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs @@ -452,7 +452,13 @@ public void SequentialPublishingSubscription(bool enabled) /// [Test, Combinatorial, Order(350)] public async Task ReconnectWithSavedSessionSecrets( - [Values(SecurityPolicies.None, SecurityPolicies.Basic256Sha256)] string securityPolicy, + [Values(SecurityPolicies.None, + SecurityPolicies.Basic256Sha256, + SecurityPolicies.ECC_brainpoolP256r1, + SecurityPolicies.ECC_brainpoolP384r1, + SecurityPolicies.ECC_brainpoolP256r1, + SecurityPolicies.ECC_nistP384 + )] string securityPolicy, [Values(true, false)] bool anonymous, [Values(true, false)] bool sequentialPublishing, [Values(true, false)] bool sendInitialValues, From 89be8d67b9bd0ae9343707bc33990c9e6708a10c Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 23 Nov 2023 17:24:54 +0200 Subject: [PATCH 34/80] Addapt code to merge changes --- Libraries/Opc.Ua.Client/Session.cs | 2 +- .../ApplicationInstance.cs | 5 +++- .../Certificates/CertificateIdentifier.cs | 2 +- .../Certificates/CertificateValidator.cs | 24 +------------------ .../ApplicationInstanceTests.cs | 13 ++++++---- 5 files changed, 16 insertions(+), 30 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index 45955f394..3691e8080 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -5901,7 +5901,7 @@ private static async Task LoadCertificateChain(Appli { clientCertificateChain = new X509Certificate2Collection(clientCertificate); List issuers = new List(); - await configuration.CertificateValidator.GetIssuers(clientCertificate, issuers, false).ConfigureAwait(false); + await configuration.CertificateValidator.GetIssuers(clientCertificate, issuers).ConfigureAwait(false); for (int i = 0; i < issuers.Count; i++) { diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index 5fb0f347f..c91d3ea65 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -448,6 +448,7 @@ public async Task DeleteApplicationInstanceCertificate(string[] profileIds = nul /// if set to true no dialogs will be displayed. /// Minimum size of the key. /// The lifetime in months. + /// [Obsolete("This method is obsolete since an application now supports different minKey sizes depending on certificate type")] public async Task CheckApplicationInstanceCertificate( bool silent, @@ -463,6 +464,7 @@ public async Task CheckApplicationInstanceCertificate( /// /// if set to true no dialogs will be displayed. /// The lifetime in months. + /// public async Task CheckApplicationInstanceCertificates( bool silent, ushort lifeTimeInMonths, @@ -503,6 +505,7 @@ public async Task CheckApplicationInstanceCertificates( /// /// /// + /// /// private async Task CheckCertificateTypeAsync( CertificateIdentifier id, @@ -955,7 +958,7 @@ await id.Certificate.AddToStoreAsync( await configuration.CertificateValidator.Update(configuration.SecurityConfiguration).ConfigureAwait(false); - Utils.LogCertificate("Certificate created for {0}.", certificate, configuration.ApplicationUri); + Utils.LogCertificate("Certificate created for {0}.", id.Certificate, configuration.ApplicationUri); // do not dispose temp cert, or X509Store certs become unusable diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index a6a1b1491..39d7591a5 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -1169,7 +1169,7 @@ public async Task LoadCertificateChainAsync(X509Cert // load certificate chain. var certificateChain = new X509Certificate2Collection(certificate); List issuers = new List(); - if (await m_certificateValidator.GetIssuers(certificate, issuers, false).ConfigureAwait(false)) + if (await m_certificateValidator.GetIssuers(certificate, issuers).ConfigureAwait(false)) { for (int i = 0; i < issuers.Count; i++) { diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 7463b196b..f59540c34 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -885,17 +885,6 @@ private bool Match( return check; } - /// - /// Returns the issuers for the certificates. - /// - public Task GetIssuers(X509Certificate2Collection certificates, List issuers) - { - return GetIssuersNoExceptionsOnGetIssuer(certificates, issuers, - null, // ensures legacy behavior is respected - true - ); - } - /// /// Returns the issuers for the certificates. /// @@ -986,21 +975,10 @@ public async Task GetIssuersNoExceptionsOnGetIssuer(X509Certificate2Collec public Task GetIssuers(X509Certificate2Collection certificates, List issuers) { return GetIssuersNoExceptionsOnGetIssuer( - certificates, issuers, null, true // ensures legacy behavior is respected + certificates, issuers, null // ensures legacy behavior is respected ); } - /// - /// Returns the issuers for the certificate. - /// - /// The certificate. - /// The issuers. - /// If the revocation status of the issuers should be checked. - public Task GetIssuers(X509Certificate2 certificate, List issuers) - { - return GetIssuers(new X509Certificate2Collection { certificate }, issuers, checkRevocationStatus); - } - /// /// Returns the issuers for the certificate. /// diff --git a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs index b6e1cdf3d..c439b75e7 100644 --- a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs +++ b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs @@ -603,18 +603,23 @@ public async Task TestDisableCertificateAutoCreationAsync(bool server, bool disa }; Assert.NotNull(applicationInstance); ApplicationConfiguration config; + + CertificateIdentifierCollection applicationCerts = ApplicationConfigurationBuilder.CreateDefaultApplicationCertificates(SubjectName, + CertificateStoreType.Directory, + m_pkiRoot); + if (server) { config = await applicationInstance.Build(ApplicationUri, ProductUri) .AsServer(new string[] { "opc.tcp://localhost:12345/Configuration" }) - .AddSecurityConfiguration(SubjectName, pkiRoot) + .AddSecurityConfiguration(applicationCerts, pkiRoot) .Create().ConfigureAwait(false); } else { config = await applicationInstance.Build(ApplicationUri, ProductUri) .AsClient() - .AddSecurityConfiguration(SubjectName, pkiRoot) + .AddSecurityConfiguration(applicationCerts, pkiRoot) .Create().ConfigureAwait(false); } Assert.NotNull(config); @@ -625,12 +630,12 @@ public async Task TestDisableCertificateAutoCreationAsync(bool server, bool disa if (disableCertificateAutoCreation) { var sre = Assert.ThrowsAsync(async () => - await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false)); + await applicationInstance.CheckApplicationInstanceCertificates(true).ConfigureAwait(false)); Assert.AreEqual(StatusCodes.BadConfigurationError, sre.StatusCode); } else { - bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool certOK = await applicationInstance.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); Assert.True(certOK); } } From 8beb5958d444ac4f83a55abc15a02cd2075b4a3f Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 24 Nov 2023 22:00:26 +0200 Subject: [PATCH 35/80] Added ReentrantSlimSemaphore --- .../Certificates/CertificateValidator.cs | 4 +- .../Certificates/ReentrantSemaphoreSlim.cs | 87 +++++++++++++++++++ .../GlobalDiscoveryTestServer.cs | 2 +- 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 Stack/Opc.Ua.Core/Security/Certificates/ReentrantSemaphoreSlim.cs diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index f59540c34..7527b1501 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -295,10 +295,12 @@ private void ResetValidatedCertificates() { try { + m_semaphore.Wait(); InternalResetValidatedCertificates(); } finally { + m_semaphore.Release(); } } @@ -1912,7 +1914,7 @@ private enum ProtectFlags #endregion #region Private Fields - private readonly SemaphoreSlim m_semaphore = new SemaphoreSlim(1, 1); + private readonly ReentrantSemaphoreSlim m_semaphore = new ReentrantSemaphoreSlim(1, 1); private readonly object m_callbackLock = new object(); private readonly Dictionary m_validatedCertificates; private CertificateStoreIdentifier m_trustedCertificateStore; diff --git a/Stack/Opc.Ua.Core/Security/Certificates/ReentrantSemaphoreSlim.cs b/Stack/Opc.Ua.Core/Security/Certificates/ReentrantSemaphoreSlim.cs new file mode 100644 index 000000000..30e6a5703 --- /dev/null +++ b/Stack/Opc.Ua.Core/Security/Certificates/ReentrantSemaphoreSlim.cs @@ -0,0 +1,87 @@ +/* Copyright (c) 1996-2022 The OPC Foundation. All rights reserved. + The source code in this file is covered under a dual-license scenario: + - RCL: for OPC Foundation Corporate Members in good-standing + - GPL V2: everybody else + RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/ + GNU General Public License as published by the Free Software Foundation; + version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2 + This source code is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +*/ + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Opc.Ua +{ + /// + /// Defines a reentrant SemaphoreSlim Lock. + /// + internal class ReentrantSemaphoreSlim + { + #region Constructors + public ReentrantSemaphoreSlim() + { + m_semaphore = new SemaphoreSlim(1, 1); + m_reentrantCounter = 0; + } + + public ReentrantSemaphoreSlim(int initialCount, int maxCount) + { + m_semaphore = new SemaphoreSlim(initialCount, maxCount); + m_reentrantCounter = 0; + } + #endregion + + #region Public Members + /// + /// Wait method for synchronous usage + /// + public void Wait() + { + int counter = m_reentrantCounter; + if (counter == 0) + { + m_semaphore.Wait(); + } + m_reentrantCounter++; + } + + /// + /// Wait method for async usage + /// + public async Task WaitAsync(CancellationToken ct = default) + { + int counter = m_reentrantCounter; + if (counter == 0) + { + await m_semaphore.WaitAsync(ct).ConfigureAwait(false); + } + m_reentrantCounter++; + } + + public void Release() + { + int counter = m_reentrantCounter; + if (counter <= 0) + { + throw new InvalidOperationException("Release called without a corresponding Wait"); + } + if (--counter == 0) + { + m_reentrantCounter = 0; + m_semaphore.Release(); + } + } + + #endregion + + #region Private members + private readonly SemaphoreSlim m_semaphore; + private int m_reentrantCounter; + #endregion + + } +} diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs index f9b4f3bf1..c723a6a49 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs @@ -234,7 +234,7 @@ private static async Task Load(ApplicationInstance app gdsRoot) .SetAutoAcceptUntrustedCertificates(true) .SetRejectSHA1SignedCertificates(false) - .SetRejectUnknownRevocationStatus(true) + .SetRejectUnknownRevocationStatus(false) .SetMinimumCertificateKeySize(1024) .AddExtension(null, gdsConfig) .SetDeleteOnLoad(true) From 1e579b199b007d356e7c736873c056f6b305e2be Mon Sep 17 00:00:00 2001 From: mirceasu Date: Mon, 27 Nov 2023 16:17:44 +0200 Subject: [PATCH 36/80] Added EccProfiles.md --- Docs/EccProfiles.md | 252 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 Docs/EccProfiles.md diff --git a/Docs/EccProfiles.md b/Docs/EccProfiles.md new file mode 100644 index 000000000..3512cf4a3 --- /dev/null +++ b/Docs/EccProfiles.md @@ -0,0 +1,252 @@ +# Support for Elliptic Curve Cryptography (ECC) Certificates in Server and Client Applications + +The server and client applications now support encrypted communication using both the RSA and the ECC encryption algorithms. +The following document tries to explain the changes in the configuration of the server and client applications needed to support ECC certificates as well as the well consacrated RSA certificates. + +The means by which client and server application security related configuration has been configured upto introducing the ECC certificates, is described in the section [Previous Client and Server application configuration supports only RSA certificates](###previous-client-and-server-application-configuration-supports-only-rsa-certificates). + +The new security related configuration of client and server applications is described in the section [New Client and Server application configuration support both RSA and ECC certificates](###new-client-and-server-application-configuration-support-both-rsa-and-ecc-certificates). + +The compatibility between the old and the new configuration of client and server applications is described in the section [Old Client and Server application configuration VS New Client and Server spplication configuration](###old-client-and-server-application-configuration-vs-new-client-and-server-spplication-configuration). + +The limitations of the support for ECC certificates are described in the section [Known Limitations](###known-limitations). + + +## Previous Client and Server application configuration supports only RSA certificates + +Up to now, the configuration supported encrypted communication using only the RSA encryption algorithm. That means that the server and client certificates were RSA certificates and there was just one RSA certificate per application needed to be configured. +The XML tag which contained the RSA certificate to be configured was ``. This tag is still used for the RSA certificate configuration in backward compatibility mode as further described below: + + +```xml + + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + + .... + +``` + +## Previous Server application configuration supports only RSA certificates + +For Server applications the configuration of the RSA certificate involves also specifying the SecurityPolicies to be supported by the server: + +```xml + + + + + + SignAndEncrypt_3 + http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256 + + + None_1 + http://opcfoundation.org/UA/SecurityPolicy#None + + + Sign_2 + + + + SignAndEncrypt_3 + + + .... + +``` + +## New Client and Server application configuration support both RSA and ECC certificates + +With the newly introduced support for ECC certificates, the configuration of the server application has been extended to support both RSA and ECC certificates. The XML tag under which both the RSA and ECC certificates are configured is ``. + +The `` tag is still used for the RSA certificate configuration in backward compatibility mode, meaning that old server configurations are still supported, but they cannot simultaneously coexist. + +The new configuration of the server application is described below: + +```xml + + + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + RsaSha256 + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + NistP256 + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + NistP384 + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + BrainpoolP256r1 + + + + Directory + %LocalApplicationData%/OPC Foundation/pki/own + CN=Quickstart Reference Server, C=US, S=Arizona, O=OPC Foundation, DC=localhost + BrainpoolP384r1 + + +.... + +``` + +This layout of the configuration file allows the server to support both RSA and ECC certificates and allows the server to generate certificates of different types. The `` tag is used to specify the type of the certificate. + +The supported types are: + - `RsaSha256` for RSA certificates + - `NistP256` for ECC certificates with NIST P256 curve + - `NistP384` for ECC certificates with NIST P384 curve + - `BrainpoolP256r1` for ECC certificates with Brainpool P256r1 curve + - `BrainpoolP384r1` for ECC certificates with Brainpool P384r1 curve + +Additionally, this layout enables the user to select a specific `` for each certificate. This is useful when the user wants to generate certificates with different ``s. + + +## New Server application configuration related to ECC certificates + +The Server applications can configure the supported SecurityPolicies in the same way as before, but now the SecurityPolicies can be configured for each ECC specific SecurityPolicyUri which is to be supported by the server: + +```xml + + + + + + SignAndEncrypt_3 + http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256 + + + Sign_2 + + + + SignAndEncrypt_3 + + + + Sign_2 + http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256 + + + Sign_2 + http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384 + + + Sign_2 + http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1 + + + Sign_2 + http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1 + + + SignAndEncrypt_3 + http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256 + + + SignAndEncrypt_3 + http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384 + + + SignAndEncrypt_3 + http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1 + + + SignAndEncrypt_3 + http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1 + + + .... + +``` + +With the introduction of ECC certificates, the `` section of the configuration file has been extended to support ECC specific SecurityPolicies. The ECC specific SecurityPolicies are the following: + + - `http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256` + - `http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384` + - `http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1` + - `http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1` + +If ECC specific SecurityPolicies are specified in the `` section of the configuration file, then the server will need to support the corresponding ECC certificates configured in the `` section of the configuration file with the corresponding ``s, not having them configured will result in the server not being able to start. + +Since the UserIdentityToken is also encrypted using the RSA or ECC certificate depending on the active security policy or the specified `` of the ``, servers which intend to explicitly state which ECC encryption (if any) of the UserIdentity tokens is supported, should specify the supported `` in the `` section of the configuration file (as it has always been). The supported ``s are the same as the ones specified in the `` section of the configuration file. + +The following example shows how the server can specify the supported ``s for the UserIdentityTokens: + +```xml + + + + + Anonymous_0 + + + + + UserName_1 + + + + + + + UserName_1 + + + http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256 + + +``` + +The `` tag can be ommited in the `` tag, in which case the active security policy is used for the encryption of the UserIdentityToken. That implies that there is a dependency between the `` and the `` sections of the configuration file, meaning that the ``s specified in the `` section should be a subset of the ``s specified in the `` section of the configuration file. + +A situation in which a `` is specified in the `` section of the configuration file, but it is not specified in the `` section of the configuration file is logically incorrect and produces an invalid configuration. + + +## "Old" Client and Server application configuration format VS "New" Client and Server application configuration format + +Client and server applications which use the "old" configuration, will continue to work as before, meaning that the server and client certificates will be RSA certificates and the `` tag will be used to configure the RSA certificate. Server applications will have no ECC security policies specified in the `` section of the configuration file. +You should be aware that such applications will not be able to support ECC certificates therefore they will not be able to communicate with clients and servers which use ECC certificates. + +Client and server applications which use the "new" configuration, are be able to support both RSA and ECC certificates. +Server and Client applications are be able to support both RSA and ECC security policies by using the `` tag to configure the RSA and ECC certificates. The `` section of the configuration file are still used to configure the supported security policies which include the new ECC policies. +Additionally the `` section of the configuration file can be used to configure the supported security policies for the UserIdentityTokens which also include the new ECC policies. + +Combining the "old" and the "new" configuration formats is not supported. That means that the `` tag cannot be used in the same configuration file with the `` tag. + + +## Known Limitations + +Not all curves are supported by all OS platforms and not all .NET implementations offer cryptographic API support for all curve types. +Due to these limitations, the support for ECC profiles is available starting with the following target platforms: .NET 4.8, .NET standard 2.1 and .NET 5 and above. +The supported ECC curve types are the following: + + - `NistP256` for ECC certificates with NIST P256 curve + - `NistP384` for ECC certificates with NIST P384 curve + - `BrainpoolP256r1` for ECC certificates with Brainpool P256r1 curve + - `BrainpoolP384r1` for ECC certificates with Brainpool P384r1 curve + + + \ No newline at end of file From d1aa4e9fb0514357d97df6657f8f8ffe605eae2e Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Wed, 20 Dec 2023 20:40:05 +0100 Subject: [PATCH 37/80] improve project setup for ECC --- .../Opc.Ua.Client.ComplexTypes.csproj | 1 - Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj | 39 ++++++++---------- .../Opc.Ua.Configuration.csproj | 33 +++++---------- Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj | 29 +++++-------- .../Opc.Ua.Bindings.Https.csproj | 11 +++++ Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 41 +++++-------------- .../Opc.Ua.Client.ComplexTypes.Tests.csproj | 12 ++++++ .../Opc.Ua.Client.Tests.csproj | 12 ++++++ .../Opc.Ua.Configuration.Tests.csproj | 12 ++++++ .../Opc.Ua.Core.Tests.csproj | 2 +- .../Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj | 14 ++++++- .../Opc.Ua.PubSub.Tests.csproj | 12 ++++++ .../Opc.Ua.Security.Certificates.Tests.csproj | 4 +- .../Opc.Ua.Server.Tests.csproj | 16 +++++++- 14 files changed, 135 insertions(+), 103 deletions(-) diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj b/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj index 6be179a1d..1d84393e0 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj +++ b/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj @@ -24,7 +24,6 @@ $(DefineConstants);SIGNASSEMBLY - diff --git a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj index 0b434eafb..21c63e9d0 100644 --- a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj +++ b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj @@ -20,30 +20,23 @@ $(PackageId).Debug - - - $(DefineConstants); - - - - $(DefineConstants);ECC_SUPPORT - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - - + + + + + + + + + + $(DefineConstants);ECC_SUPPORT + + + + + + diff --git a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj index 6110a7e75..eb83baaef 100644 --- a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj +++ b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj @@ -12,29 +12,16 @@ true - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - + + + + + + + $(DefineConstants);ECC_SUPPORT + + + $(PackageId).Debug diff --git a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj index 77fba1eee..0c4e12c59 100644 --- a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj +++ b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj @@ -20,27 +20,18 @@ $(DefineConstants);SIGNASSEMBLY - - - $(DefineConstants); - - - - $(DefineConstants);ECC_SUPPORT - - - $(DefineConstants);ECC_SUPPORT - + + + + + + + $(DefineConstants);ECC_SUPPORT + + + - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - diff --git a/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj b/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj index 9b7868adb..53dccbad9 100644 --- a/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj +++ b/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj @@ -18,6 +18,17 @@ $(DefineConstants);SIGNASSEMBLY + + + + + + + $(DefineConstants);ECC_SUPPORT + + + + diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index fa7aa9fe5..a6a5724d6 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -11,25 +11,16 @@ true - - $(DefineConstants); - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - + + + + + + + $(DefineConstants);ECC_SUPPORT + + + $(PackageId).Debug @@ -79,18 +70,6 @@ - - - - - - - - - diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj index 47661d118..84269d6da 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj @@ -20,6 +20,18 @@ + + + + + + + + $(DefineConstants);ECC_SUPPORT + + + + diff --git a/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj b/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj index d922960ef..509a7aed4 100644 --- a/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj +++ b/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj @@ -8,6 +8,18 @@ false + + + + + + + + $(DefineConstants);ECC_SUPPORT + + + + diff --git a/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj b/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj index 8c43827d4..823724155 100644 --- a/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj +++ b/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj @@ -8,6 +8,18 @@ CS0618 + + + + + + + + $(DefineConstants);ECC_SUPPORT + + + + diff --git a/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj b/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj index aba14b86a..0d21616c9 100644 --- a/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj +++ b/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj @@ -27,7 +27,7 @@ - + diff --git a/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj b/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj index 71ea65fb6..548dd82cb 100644 --- a/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj +++ b/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj @@ -1,4 +1,4 @@ - + Exe @@ -11,6 +11,18 @@ $(DefineConstants);USE_FILE_CONFIG + + + + + + + + $(DefineConstants);ECC_SUPPORT + + + + diff --git a/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj b/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj index a29903ade..b8809b498 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj +++ b/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj @@ -7,6 +7,18 @@ false + + + + + + + + $(DefineConstants);ECC_SUPPORT + + + + diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj b/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj index 7a492fa0a..38763312b 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj +++ b/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj @@ -10,13 +10,13 @@ - + $(DefineConstants);ECC_SUPPORT - + diff --git a/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj b/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj index 44d051d09..8eaabd909 100644 --- a/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj +++ b/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj @@ -4,9 +4,21 @@ Exe $(TestsTargetFrameworks) Opc.Ua.Server.Tests - false + false + + + + + + + + $(DefineConstants);ECC_SUPPORT + + + + @@ -22,7 +34,7 @@ - + From 870dbc7e4a12782731ab8ecf2ca42f2961974f6d Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Tue, 27 Feb 2024 11:12:37 +0100 Subject: [PATCH 38/80] fix build --- Libraries/Opc.Ua.Client/Session.cs | 3 ++- Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index 0c0c48059..09feb9294 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -5594,8 +5594,9 @@ ITransportChannel transportChannel EndpointDescription endpoint = m_endpoint.Description; SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, endpoint.SecurityPolicyUri, dataToSign); +#if TODO // new code inside, please check if the update is valid UserTokenPolicy identityPolicy = m_endpoint.Description.FindUserTokenPolicy(m_identity.PolicyId); -#if TODO was +#else // check that the user identity is supported by the endpoint. UserTokenPolicy identityPolicy = endpoint.FindUserTokenPolicy(m_identity.TokenType, m_identity.IssuedTokenType, diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs index 896b18417..5f15fe43f 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs @@ -103,7 +103,7 @@ public async Task LoadClientConfiguration(int port = -1) .Create().ConfigureAwait(false); #endif // check the application certificate. - bool haveAppCertificate = await application.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); + bool haveAppCertificate = await m_application.CheckApplicationInstanceCertificates(true).ConfigureAwait(false); if (!haveAppCertificate) { throw new Exception("Application instance certificate invalid!"); From 1447154fd54a6e4fba5e88ce53eb03357e44d434 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 15 Mar 2024 17:11:08 +0200 Subject: [PATCH 39/80] Removed unused code and corrected some --- .../Certificates/CertificateFactory.cs | 4 + .../Certificates/CertificateIdentifier.cs | 134 +--------- .../Certificates/CertificateTypesProvider.cs | 165 ++++++++++++ .../Certificates/CertificateValidator.cs | 1 - .../Certificates/DirectoryCertificateStore.cs | 2 +- .../Security/Certificates/EccUtils.cs | 6 +- .../Certificates/ICertificateStore.cs | 2 +- .../Security/Certificates/Nonce.cs | 4 +- .../Certificates/ReentrantSemaphoreSlim.cs | 22 +- .../Certificates/X509CertificateStore.cs | 1 + .../Security/Constants/SecurityPolicies.cs | 236 +++++++++--------- Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs | 6 +- .../Stack/Tcp/UaSCBinaryChannel.Symmetric.cs | 119 --------- Tests/Opc.Ua.Client.Tests/ClientTest.cs | 31 +-- .../CertificateStoreTypeTest.cs | 2 +- .../Certificates/CertificateStoreTypeTest.cs | 1 + 16 files changed, 310 insertions(+), 426 deletions(-) create mode 100644 Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs index f8c2da913..cb2bee5f6 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs @@ -103,6 +103,10 @@ public static X509Certificate2 Load(X509Certificate2 certificate, bool ensurePri { return cachedCertificate; } + else + { + m_certificates.Remove(certificate.Thumbprint); + } } // nothing more to do if no private key or dont care about accessibility. diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index 9d95cd499..ef34d9f07 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -124,7 +124,7 @@ public override bool Equals(object obj) /// public override int GetHashCode() { - return HashCode.Combine(Thumbprint, m_storeLocation, m_storeName, SubjectName); + return HashCode.Combine(Thumbprint, m_storeLocation, m_storeName, SubjectName, CertificateType); } #endregion @@ -523,7 +523,6 @@ public ushort GetMinKeySize(SecurityConfiguration securityConfiguration) else if ( CertificateType == ObjectTypeIds.RsaMinApplicationCertificateType || CertificateType == ObjectTypeIds.RsaSha256ApplicationCertificateType || - CertificateType == ObjectTypeIds.ApplicationCertificateType || securityConfiguration.IsDeprecatedConfiguration) // Deprecated configurations are implicitly RSA { return securityConfiguration.MinimumCertificateKeySize; @@ -649,7 +648,6 @@ public static IList MapSecurityPolicyToCertificateTypes(string securityP break; case SecurityPolicies.Https: result.Add(ObjectTypeIds.HttpsCertificateType); - result.Add(ObjectTypeIds.ApplicationCertificateType); break; default: break; @@ -1058,134 +1056,4 @@ public enum CertificateValidationOptions } #endregion - #region CertificateTypesProvider - /// - /// The identifier for an X509 certificate. - /// - public class CertificateTypesProvider - { - /// - /// - /// - public CertificateTypesProvider(ApplicationConfiguration config) - { - m_securityConfiguration = config.SecurityConfiguration; - m_certificateValidator = config.CertificateValidator; - } - - /// - /// Gets or sets a value indicating whether the application should send the complete certificate chain. - /// - /// - /// If set to true the complete certificate chain will be sent for CA signed certificates. - /// - public bool SendCertificateChain => m_securityConfiguration.SendCertificateChain; - - /// - /// Return the instance certificate for a security policy. - /// - /// The security policy Uri - public X509Certificate2 GetInstanceCertificate(string securityPolicyUri) - { - if (securityPolicyUri == SecurityPolicies.None) - { - // return the default certificate for None - return m_securityConfiguration.ApplicationCertificates.FirstOrDefault().Certificate; - } - var certificateTypes = CertificateIdentifier.MapSecurityPolicyToCertificateTypes(securityPolicyUri); - foreach (var certType in certificateTypes) - { - var instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == certType); - if (instanceCertificate == null && - certType == ObjectTypeIds.RsaSha256ApplicationCertificateType) - { - instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == null); - } - if (instanceCertificate == null && - certType == ObjectTypeIds.ApplicationCertificateType) - { - instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(); - } - if (instanceCertificate != null) - { - return instanceCertificate.Certificate; - } - } - return null; - } - - /// - /// Load the instance certificate with a private key. - /// - /// - /// - public Task GetInstanceCertificateAsync(IList certificateTypes, bool privateKey) - { - foreach (var certType in certificateTypes) - { - var instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == certType); - if (instanceCertificate != null) - { - return instanceCertificate.Find(privateKey); - } - } - return Task.FromResult(null); - } - - /// - /// Loads the certificate chain of a certificate for use in a secure channel as raw byte array. - /// - /// The application certificate. - public async Task LoadCertificateChainRawAsync(X509Certificate2 certificate) - { - var instanceCertificateChain = await LoadCertificateChainAsync(certificate).ConfigureAwait(false); - if (instanceCertificateChain != null) - { - List serverCertificateChain = new List(); - for (int i = 0; i < instanceCertificateChain.Count; i++) - { - serverCertificateChain.AddRange(instanceCertificateChain[i].RawData); - } - return serverCertificateChain.ToArray(); - } - return null; - } - - /// - /// Loads the certificate chain for an application certificate. - /// - /// The application certificate. - public async Task LoadCertificateChainAsync(X509Certificate2 certificate) - { - if (certificate == null) - { - return null; - } - - // load certificate chain. - var certificateChain = new X509Certificate2Collection(certificate); - List issuers = new List(); - if (await m_certificateValidator.GetIssuers(certificate, issuers).ConfigureAwait(false)) - { - for (int i = 0; i < issuers.Count; i++) - { - certificateChain.Add(issuers[i].Certificate); - } - } - return certificateChain; - } - - /// - /// Update the security configuration of the cert type provider. - /// - /// The new security configuration. - public void Update(SecurityConfiguration securityConfiguration) - { - m_securityConfiguration = securityConfiguration; - } - - CertificateValidator m_certificateValidator; - SecurityConfiguration m_securityConfiguration; - } - #endregion } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs new file mode 100644 index 000000000..71d687fcc --- /dev/null +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs @@ -0,0 +1,165 @@ +/* ======================================================================== + * Copyright (c) 2005-2021 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/ + * ======================================================================*/ + +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; + +namespace Opc.Ua +{ + /// + /// The identifier for an X509 certificate. + /// + public class CertificateTypesProvider + { + /// + /// Constructor + /// + public CertificateTypesProvider(ApplicationConfiguration config) + { + m_securityConfiguration = config.SecurityConfiguration; + m_certificateValidator = config.CertificateValidator; + } + + /// + /// Gets or sets a value indicating whether the application should send the complete certificate chain. + /// + /// + /// If set to true the complete certificate chain will be sent for CA signed certificates. + /// + public bool SendCertificateChain => m_securityConfiguration.SendCertificateChain; + + /// + /// Return the instance certificate for a security policy. + /// + /// The security policy Uri + public X509Certificate2 GetInstanceCertificate(string securityPolicyUri) + { + if (securityPolicyUri == SecurityPolicies.None) + { + // return the default certificate for None + return m_securityConfiguration.ApplicationCertificates.FirstOrDefault().Certificate; + } + var certificateTypes = CertificateIdentifier.MapSecurityPolicyToCertificateTypes(securityPolicyUri); + foreach (var certType in certificateTypes) + { + var instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == certType); + if (instanceCertificate == null && + certType == ObjectTypeIds.RsaSha256ApplicationCertificateType) + { + instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == null); + } + if (instanceCertificate == null && + certType == ObjectTypeIds.ApplicationCertificateType) + { + instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(); + } + if (instanceCertificate != null) + { + return instanceCertificate.Certificate; + } + } + return null; + } + + /// + /// Load the instance certificate with a private key. + /// + /// + /// + public Task GetInstanceCertificateAsync(IList certificateTypes, bool privateKey) + { + foreach (var certType in certificateTypes) + { + var instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == certType); + if (instanceCertificate != null) + { + return instanceCertificate.Find(privateKey); + } + } + return Task.FromResult(null); + } + + /// + /// Loads the certificate chain of a certificate for use in a secure channel as raw byte array. + /// + /// The application certificate. + public async Task LoadCertificateChainRawAsync(X509Certificate2 certificate) + { + var instanceCertificateChain = await LoadCertificateChainAsync(certificate).ConfigureAwait(false); + if (instanceCertificateChain != null) + { + List serverCertificateChain = new List(); + for (int i = 0; i < instanceCertificateChain.Count; i++) + { + serverCertificateChain.AddRange(instanceCertificateChain[i].RawData); + } + return serverCertificateChain.ToArray(); + } + return null; + } + + /// + /// Loads the certificate chain for an application certificate. + /// + /// The application certificate. + public async Task LoadCertificateChainAsync(X509Certificate2 certificate) + { + if (certificate == null) + { + return null; + } + + // load certificate chain. + var certificateChain = new X509Certificate2Collection(certificate); + List issuers = new List(); + if (await m_certificateValidator.GetIssuers(certificate, issuers).ConfigureAwait(false)) + { + for (int i = 0; i < issuers.Count; i++) + { + certificateChain.Add(issuers[i].Certificate); + } + } + return certificateChain; + } + + /// + /// Update the security configuration of the cert type provider. + /// + /// The new security configuration. + public void Update(SecurityConfiguration securityConfiguration) + { + m_securityConfiguration = securityConfiguration; + } + + CertificateValidator m_certificateValidator; + SecurityConfiguration m_securityConfiguration; + } +} diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 46b2d47ec..652dcf4c5 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -981,7 +981,6 @@ public Task GetIssuers(X509Certificate2Collection certificates, List /// The certificate. /// The issuers. - /// public Task GetIssuers(X509Certificate2 certificate, List issuers) { return GetIssuers(new X509Certificate2Collection { certificate }, issuers); diff --git a/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs b/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs index 018f98cbf..696cfc571 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs @@ -339,7 +339,7 @@ public string GetPrivateKeyFilePath(string thumbprint) /// /// Loads the private key certificate with RSA signature from a PFX file in the certificate store. /// - [Obsolete("Use LoadPrivateKey with certificateType.")] + [Obsolete("Method is deprecated. Use only for RSA certificates, the replacing LoadPrivateKey with certificateType parameter should be used.")] public Task LoadPrivateKey(string thumbprint, string subjectName, string password) { return LoadPrivateKey(thumbprint, subjectName, null, password); diff --git a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs index 52ada2d4e..4b5f18890 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs @@ -1,6 +1,6 @@ -/* Copyright (c) 1996-2016, OPC Foundation. All rights reserved. +/* Copyright (c) 1996-2022 The OPC Foundation. All rights reserved. The source code in this file is covered under a dual-license scenario: - - RCL: for OPC Foundation members in good-standing + - RCL: for OPC Foundation Corporate Members in good-standing - GPL V2: everybody else RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/ GNU General Public License as published by the Free Software Foundation; @@ -13,8 +13,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System; using System.Text; -using System.IO; -using System.Collections.Generic; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Opc.Ua.Security.Certificates; diff --git a/Stack/Opc.Ua.Core/Security/Certificates/ICertificateStore.cs b/Stack/Opc.Ua.Core/Security/Certificates/ICertificateStore.cs index 7c29f2e93..fcade8897 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/ICertificateStore.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/ICertificateStore.cs @@ -86,7 +86,7 @@ public interface ICertificateStore : IDisposable /// The certificate password. /// Returns always null if SupportsLoadPrivateKey returns false. /// The matching certificate with private key - [Obsolete("Must specify certificate type.")] + [Obsolete("Method is deprecated. Use only for RSA certificates, the replacing LoadPrivateKey with certificateType parameter should be used.")] Task LoadPrivateKey(string thumbprint, string subjectName, string password); /// diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs index 993868d37..596b95b9d 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs @@ -1,6 +1,6 @@ -/* Copyright (c) 1996-2016, OPC Foundation. All rights reserved. +/* Copyright (c) 1996-2022 The OPC Foundation. All rights reserved. The source code in this file is covered under a dual-license scenario: - - RCL: for OPC Foundation members in good-standing + - RCL: for OPC Foundation Corporate Members in good-standing - GPL V2: everybody else RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/ GNU General Public License as published by the Free Software Foundation; diff --git a/Stack/Opc.Ua.Core/Security/Certificates/ReentrantSemaphoreSlim.cs b/Stack/Opc.Ua.Core/Security/Certificates/ReentrantSemaphoreSlim.cs index 30e6a5703..1cedd66ee 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/ReentrantSemaphoreSlim.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/ReentrantSemaphoreSlim.cs @@ -25,13 +25,13 @@ internal class ReentrantSemaphoreSlim public ReentrantSemaphoreSlim() { m_semaphore = new SemaphoreSlim(1, 1); - m_reentrantCounter = 0; + m_reentrantCounter = new ThreadLocal(() => 0); } public ReentrantSemaphoreSlim(int initialCount, int maxCount) { m_semaphore = new SemaphoreSlim(initialCount, maxCount); - m_reentrantCounter = 0; + m_reentrantCounter = new ThreadLocal(() => 0); } #endregion @@ -41,12 +41,11 @@ public ReentrantSemaphoreSlim(int initialCount, int maxCount) /// public void Wait() { - int counter = m_reentrantCounter; - if (counter == 0) + if (m_reentrantCounter.Value == 0) { m_semaphore.Wait(); } - m_reentrantCounter++; + m_reentrantCounter.Value++; } /// @@ -54,24 +53,21 @@ public void Wait() /// public async Task WaitAsync(CancellationToken ct = default) { - int counter = m_reentrantCounter; - if (counter == 0) + if (m_reentrantCounter.Value == 0) { await m_semaphore.WaitAsync(ct).ConfigureAwait(false); } - m_reentrantCounter++; + m_reentrantCounter.Value++; } public void Release() { - int counter = m_reentrantCounter; - if (counter <= 0) + if (m_reentrantCounter.Value <= 0) { throw new InvalidOperationException("Release called without a corresponding Wait"); } - if (--counter == 0) + if (--m_reentrantCounter.Value == 0) { - m_reentrantCounter = 0; m_semaphore.Release(); } } @@ -80,7 +76,7 @@ public void Release() #region Private members private readonly SemaphoreSlim m_semaphore; - private int m_reentrantCounter; + private ThreadLocal m_reentrantCounter; #endregion } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore.cs index 4d4c894d7..62fb35ad1 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore.cs @@ -211,6 +211,7 @@ public Task FindByThumbprint(string thumbprint) /// /// The LoadPrivateKey special handling is not necessary in this store. + [Obsolete("Method is deprecated. Use only for RSA certificates, the replacing LoadPrivateKey with certificateType parameter should be used.")] public Task LoadPrivateKey(string thumbprint, string subjectName, string password) { return Task.FromResult(null); diff --git a/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs b/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs index 7ff27b52c..f3a55fa0d 100644 --- a/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs +++ b/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs @@ -319,48 +319,48 @@ public static EncryptedData Encrypt(X509Certificate2 certificate, string securit case SecurityPolicies.Basic256: case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Aes128_Sha256_RsaOaep: - { - encryptedData.Algorithm = SecurityAlgorithms.RsaOaep; - encryptedData.Data = RsaUtils.Encrypt(plainText, certificate, RsaUtils.Padding.OaepSHA1); - break; - } + { + encryptedData.Algorithm = SecurityAlgorithms.RsaOaep; + encryptedData.Data = RsaUtils.Encrypt(plainText, certificate, RsaUtils.Padding.OaepSHA1); + break; + } case SecurityPolicies.Basic128Rsa15: - { - encryptedData.Algorithm = SecurityAlgorithms.Rsa15; - encryptedData.Data = RsaUtils.Encrypt(plainText, certificate, RsaUtils.Padding.Pkcs1); - break; - } + { + encryptedData.Algorithm = SecurityAlgorithms.Rsa15; + encryptedData.Data = RsaUtils.Encrypt(plainText, certificate, RsaUtils.Padding.Pkcs1); + break; + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - encryptedData.Algorithm = SecurityAlgorithms.RsaOaepSha256; - encryptedData.Data = RsaUtils.Encrypt(plainText, certificate, RsaUtils.Padding.OaepSHA256); - break; - } + { + encryptedData.Algorithm = SecurityAlgorithms.RsaOaepSha256; + encryptedData.Data = RsaUtils.Encrypt(plainText, certificate, RsaUtils.Padding.OaepSHA256); + break; + } case SecurityPolicies.ECC_nistP256: case SecurityPolicies.ECC_nistP384: case SecurityPolicies.ECC_brainpoolP256r1: case SecurityPolicies.ECC_brainpoolP384r1: - { - return encryptedData; - } + { + return encryptedData; + } case SecurityPolicies.None: - { - break; - } + { + break; + } case SecurityPolicies.ECC_curve25519: case SecurityPolicies.ECC_curve448: default: - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityPolicyRejected, - "Unsupported security policy: {0}", - securityPolicyUri); - } + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityPolicyRejected, + "Unsupported security policy: {0}", + securityPolicyUri); + } } return encryptedData; @@ -389,54 +389,54 @@ public static byte[] Decrypt(X509Certificate2 certificate, string securityPolicy case SecurityPolicies.Basic256: case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Aes128_Sha256_RsaOaep: + { + if (dataToDecrypt.Algorithm == SecurityAlgorithms.RsaOaep) { - if (dataToDecrypt.Algorithm == SecurityAlgorithms.RsaOaep) - { - return RsaUtils.Decrypt(new ArraySegment(dataToDecrypt.Data), certificate, RsaUtils.Padding.OaepSHA1); - } - break; + return RsaUtils.Decrypt(new ArraySegment(dataToDecrypt.Data), certificate, RsaUtils.Padding.OaepSHA1); } + break; + } case SecurityPolicies.Basic128Rsa15: + { + if (dataToDecrypt.Algorithm == SecurityAlgorithms.Rsa15) { - if (dataToDecrypt.Algorithm == SecurityAlgorithms.Rsa15) - { - return RsaUtils.Decrypt(new ArraySegment(dataToDecrypt.Data), certificate, RsaUtils.Padding.Pkcs1); - } - break; + return RsaUtils.Decrypt(new ArraySegment(dataToDecrypt.Data), certificate, RsaUtils.Padding.Pkcs1); } + break; + } case SecurityPolicies.Aes256_Sha256_RsaPss: + { + if (dataToDecrypt.Algorithm == SecurityAlgorithms.RsaOaepSha256) { - if (dataToDecrypt.Algorithm == SecurityAlgorithms.RsaOaepSha256) - { - return RsaUtils.Decrypt(new ArraySegment(dataToDecrypt.Data), certificate, RsaUtils.Padding.OaepSHA256); - } - break; + return RsaUtils.Decrypt(new ArraySegment(dataToDecrypt.Data), certificate, RsaUtils.Padding.OaepSHA256); } + break; + } case SecurityPolicies.ECC_nistP256: case SecurityPolicies.ECC_nistP384: case SecurityPolicies.ECC_brainpoolP256r1: case SecurityPolicies.ECC_brainpoolP384r1: case SecurityPolicies.None: + { + if (String.IsNullOrEmpty(dataToDecrypt.Algorithm)) { - if (String.IsNullOrEmpty(dataToDecrypt.Algorithm)) - { - return dataToDecrypt.Data; - } - break; + return dataToDecrypt.Data; } + break; + } case SecurityPolicies.ECC_curve25519: case SecurityPolicies.ECC_curve448: default: - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityPolicyRejected, - "Unsupported security policy: {0}", - securityPolicyUri); - } + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityPolicyRejected, + "Unsupported security policy: {0}", + securityPolicyUri); + } } throw ServiceResultException.Create( @@ -469,26 +469,26 @@ public static SignatureData Sign(X509Certificate2 certificate, string securityPo { case SecurityPolicies.Basic256: case SecurityPolicies.Basic128Rsa15: - { - signatureData.Algorithm = SecurityAlgorithms.RsaSha1; - signatureData.Signature = RsaUtils.Rsa_Sign(new ArraySegment(dataToSign), certificate, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); - break; - } + { + signatureData.Algorithm = SecurityAlgorithms.RsaSha1; + signatureData.Signature = RsaUtils.Rsa_Sign(new ArraySegment(dataToSign), certificate, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); + break; + } case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Basic256Sha256: - { - signatureData.Algorithm = SecurityAlgorithms.RsaSha256; - signatureData.Signature = RsaUtils.Rsa_Sign(new ArraySegment(dataToSign), certificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - break; - } + { + signatureData.Algorithm = SecurityAlgorithms.RsaSha256; + signatureData.Signature = RsaUtils.Rsa_Sign(new ArraySegment(dataToSign), certificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + break; + } case SecurityPolicies.Aes256_Sha256_RsaPss: - { - signatureData.Algorithm = SecurityAlgorithms.RsaPssSha256; - signatureData.Signature = RsaUtils.Rsa_Sign(new ArraySegment(dataToSign), certificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); - break; - } + { + signatureData.Algorithm = SecurityAlgorithms.RsaPssSha256; + signatureData.Signature = RsaUtils.Rsa_Sign(new ArraySegment(dataToSign), certificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + break; + } #if ECC_SUPPORT case SecurityPolicies.ECC_nistP256: @@ -509,21 +509,21 @@ public static SignatureData Sign(X509Certificate2 certificate, string securityPo #endif case SecurityPolicies.None: - { - signatureData.Algorithm = null; - signatureData.Signature = null; - break; - } + { + signatureData.Algorithm = null; + signatureData.Signature = null; + break; + } case SecurityPolicies.ECC_curve25519: case SecurityPolicies.ECC_curve448: default: - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityPolicyRejected, - "Unsupported security policy: {0}", - securityPolicyUri); - } + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityPolicyRejected, + "Unsupported security policy: {0}", + securityPolicyUri); + } } return signatureData; @@ -551,47 +551,47 @@ public static bool Verify(X509Certificate2 certificate, string securityPolicyUri { case SecurityPolicies.Basic256: case SecurityPolicies.Basic128Rsa15: + { + if (signature.Algorithm == SecurityAlgorithms.RsaSha1) { - if (signature.Algorithm == SecurityAlgorithms.RsaSha1) - { - return RsaUtils.Rsa_Verify(new ArraySegment(dataToVerify), signature.Signature, certificate, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); - } - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "Unexpected signature algorithm for Basic256/Basic128Rsa15: {0}\n" + - "Expected signature algorithm: {1}", - signature.Algorithm, - SecurityAlgorithms.RsaSha1); + return RsaUtils.Rsa_Verify(new ArraySegment(dataToVerify), signature.Signature, certificate, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); } + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "Unexpected signature algorithm for Basic256/Basic128Rsa15: {0}\n" + + "Expected signature algorithm: {1}", + signature.Algorithm, + SecurityAlgorithms.RsaSha1); + } case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Basic256Sha256: + { + if (signature.Algorithm == SecurityAlgorithms.RsaSha256) { - if (signature.Algorithm == SecurityAlgorithms.RsaSha256) - { - return RsaUtils.Rsa_Verify(new ArraySegment(dataToVerify), signature.Signature, certificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - } - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "Unexpected signature algorithm for Basic256Sha256/Aes128_Sha256_RsaOaep: {0}\n" + - "Expected signature algorithm: {1}", - signature.Algorithm, - SecurityAlgorithms.RsaSha256); + return RsaUtils.Rsa_Verify(new ArraySegment(dataToVerify), signature.Signature, certificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); } + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "Unexpected signature algorithm for Basic256Sha256/Aes128_Sha256_RsaOaep: {0}\n" + + "Expected signature algorithm: {1}", + signature.Algorithm, + SecurityAlgorithms.RsaSha256); + } case SecurityPolicies.Aes256_Sha256_RsaPss: + { + if (signature.Algorithm == SecurityAlgorithms.RsaPssSha256) { - if (signature.Algorithm == SecurityAlgorithms.RsaPssSha256) - { - return RsaUtils.Rsa_Verify(new ArraySegment(dataToVerify), signature.Signature, certificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); - } - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "Unexpected signature algorithm for Aes256_Sha256_RsaPss: {0}\n" + - "Expected signature algorithm : {1}", - signature.Algorithm, - SecurityAlgorithms.RsaPssSha256); + return RsaUtils.Rsa_Verify(new ArraySegment(dataToVerify), signature.Signature, certificate, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); } + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "Unexpected signature algorithm for Aes256_Sha256_RsaPss: {0}\n" + + "Expected signature algorithm : {1}", + signature.Algorithm, + SecurityAlgorithms.RsaPssSha256); + } #if ECC_SUPPORT case SecurityPolicies.ECC_nistP256: case SecurityPolicies.ECC_brainpoolP256r1: @@ -607,19 +607,19 @@ public static bool Verify(X509Certificate2 certificate, string securityPolicyUri #endif // always accept signatures if security is not used. case SecurityPolicies.None: - { - return true; - } + { + return true; + } case SecurityPolicies.ECC_curve25519: case SecurityPolicies.ECC_curve448: default: - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityPolicyRejected, - "Unsupported security policy: {0}", - securityPolicyUri); - } + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityPolicyRejected, + "Unsupported security policy: {0}", + securityPolicyUri); + } } throw ServiceResultException.Create( diff --git a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs index 63da1a9a3..59be4e9aa 100644 --- a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs @@ -637,12 +637,12 @@ public CertificateTypesProvider InstanceCertificateTypesProvider { get { - return m_InstanceCertificateTypesProvider; + return m_instanceCertificateTypesProvider; } private set { - m_InstanceCertificateTypesProvider = value; + m_instanceCertificateTypesProvider = value; } } @@ -1689,7 +1689,7 @@ private void OnProcessRequestQueue(object state) private object m_messageContext; private object m_serverError; private object m_certificateValidator; - private CertificateTypesProvider m_InstanceCertificateTypesProvider; + private CertificateTypesProvider m_instanceCertificateTypesProvider; private object m_serverProperties; private object m_configuration; private object m_serverDescription; diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs index 12f4a494e..ac2a2242f 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs @@ -834,14 +834,6 @@ protected byte[] Sign(ChannelToken token, ArraySegment dataToSign, bool us { return SymmetricSign(token, dataToSign, useClientKeys); } - -#if GCMMODE - case SecurityPolicies.ECC_nistP256: - //case SecurityPolicies.Aes128_Gcm256_RsaOaep: - { - return SymmetricSignWithAESGCM(token, dataToSign, useClientKeys); - } -#endif } } @@ -874,13 +866,6 @@ protected bool Verify( { return SymmetricVerify(token, signature, dataToVerify, useClientKeys); } -#if GCMMODE - case SecurityPolicies.ECC_nistP256: - //case SecurityPolicies.Aes128_Gcm256_RsaOaep: - { - return SymmetricVerifyWithAESGCM(token, signature, dataToVerify, useClientKeys); - } -#endif default: { return false; @@ -999,60 +984,7 @@ private static byte[] SymmetricSign(ChannelToken token, ArraySegment dataT // return signature. return signature; } -#if GCMMODE - /// - /// Signs the message using SHA1 HMAC - /// - private static byte[] SymmetricSignWithAESGCM(ChannelToken token, ArraySegment dataToSign, bool useClientKeys) - { - try - { - var key = (useClientKeys) ? token.ClientEncryptingKey : token.ServerSigningKey; - var nonce = (useClientKeys) ? token.ClientInitializationVector : token.ServerInitializationVector; - var macSize = (useClientKeys) ? token.ClientSigningKey.Length : token.ServerSigningKey.Length; - - var header = new byte[TcpMessageLimits.SymmetricHeaderSize]; - Buffer.BlockCopy(dataToSign.Array, 0, header, 0, header.Length); - - var cipher = new GcmBlockCipher(new AesEngine()); - var parameters = new AeadParameters(new KeyParameter(key), macSize, nonce, header); - cipher.Init(true, parameters); - - var plainTextLength = dataToSign.Count - TcpMessageLimits.SymmetricHeaderSize; - var cipherLength = cipher.GetOutputSize(plainTextLength); - var cipherText = new byte[cipherLength]; - - var length = cipher.ProcessBytes( - dataToSign.Array, - TcpMessageLimits.SymmetricHeaderSize, - plainTextLength, - cipherText, - 0); - - cipher.DoFinal(cipherText, length); - Buffer.BlockCopy(cipherText, 0, dataToSign.Array, TcpMessageLimits.SymmetricHeaderSize, cipherText.Length); - - // return signature. - var signature = new byte[cipherLength + header.Length - dataToSign.Count]; - Buffer.BlockCopy(dataToSign.Array, dataToSign.Count, signature, 0, signature.Length); - - int eod = dataToSign.Count + signature.Length; - int siz = BitConverter.ToInt32(dataToSign.Array, 4); - - //if (!SymmetricVerifyWithAESGCM(token, signature, dataToSign, useClientKeys)) - //{ - // int x = 0; - //} - - return signature; - } - catch (Exception) - { - throw; - } - } -#endif /// /// Verifies a HMAC for a message. /// @@ -1094,58 +1026,7 @@ private bool SymmetricVerify( return true; } -#if GCMMODE - /// - /// Verifies a HMAC for a message. - /// - private static bool SymmetricVerifyWithAESGCM( - ChannelToken token, - byte[] signature, - ArraySegment dataToVerify, - bool useClientKeys) - { - try - { - var key = (useClientKeys) ? token.ClientEncryptingKey : token.ServerSigningKey; - var nonce = (useClientKeys) ? token.ClientInitializationVector : token.ServerInitializationVector; - var macSize = (useClientKeys) ? token.ClientSigningKey.Length : token.ServerSigningKey.Length; - - using (var cipherStream = new MemoryStream(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count + signature.Length)) - { - using (var cipherReader = new BinaryReader(cipherStream)) - { - var header = cipherReader.ReadBytes(TcpMessageLimits.SymmetricHeaderSize); - var cipher = new GcmBlockCipher(new AesEngine()); - var parameters = new AeadParameters(new KeyParameter(key), macSize, nonce, header); - cipher.Init(false, parameters); - - var cipherText = cipherReader.ReadBytes(dataToVerify.Count - header.Length + signature.Length); - var plainTextLength = cipher.GetOutputSize(cipherText.Length); - var plainText = new byte[plainTextLength]; - - try - { - var length = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0); - cipher.DoFinal(plainText, length); - } - catch (InvalidCipherTextException e) - { - // validation failed. - return false; - } - - Buffer.BlockCopy(plainText, 0, dataToVerify.Array, dataToVerify.Offset + header.Length, plainText.Length); - return true; - } - } - } - catch (Exception e) - { - throw; - } - } -#endif /// /// Encrypts a message using a symmetric algorithm. /// diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs index a7061485b..cfcd77f72 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs @@ -915,36 +915,7 @@ private void ValidateDataTypeDefinition(INode node) StructureDefinition structureDefinition = dataTypeDefinition.Body as StructureDefinition; Assert.AreEqual(ObjectIds.ProgramDiagnosticDataType_Encoding_DefaultBinary, structureDefinition.DefaultEncodingId); } -#if mist - [Test] - public async Task ReadWriteDataTypeDefinition() - { - // Test Read a DataType Node - var typeId = DataTypeIds.PubSubGroupDataType; - var node = m_session.ReadNode(typeId); - Assert.NotNull(node); - var dataTypeNode = (DataTypeNode)node; - Assert.NotNull(dataTypeNode); - var dataTypeDefinition = dataTypeNode.DataTypeDefinition; - Assert.NotNull(dataTypeDefinition); - Assert.True(dataTypeDefinition is ExtensionObject); - Assert.NotNull(dataTypeDefinition.Body); - Assert.True(dataTypeDefinition.Body is StructureDefinition); - StructureDefinition structureDefinition = dataTypeDefinition.Body as StructureDefinition; - Assert.AreEqual(ObjectIds.PubSubGroupDataType_Encoding_DefaultBinary, structureDefinition.DefaultEncodingId); - structureDefinition.DefaultEncodingId = ObjectIds.PubSubGroupDataType_Encoding_DefaultJson; - - var writeValueCollection = new WriteValueCollection(); - writeValueCollection.Add(new WriteValue() { - AttributeId = Attributes.DataTypeDefinition, - NodeId = typeId, - Value = new DataValue(new Variant(dataTypeDefinition)) - }); - var response = await m_session.WriteAsync(null, writeValueCollection, CancellationToken.None); - Assert.AreEqual(StatusCodes.BadNotWritable, response.Results[0].Code); - Assert.NotNull(response); - } -#endif + [Theory, Order(400)] public async Task BrowseFullAddressSpace(string securityPolicy, bool operationLimits = false) { diff --git a/Tests/Opc.Ua.Configuration.Tests/CertificateStoreTypeTest.cs b/Tests/Opc.Ua.Configuration.Tests/CertificateStoreTypeTest.cs index ea2e42322..9ee82de42 100644 --- a/Tests/Opc.Ua.Configuration.Tests/CertificateStoreTypeTest.cs +++ b/Tests/Opc.Ua.Configuration.Tests/CertificateStoreTypeTest.cs @@ -240,7 +240,7 @@ public Task IsRevoked(X509Certificate2 issuer, X509Certificate2 cert public Task LoadPrivateKey(string thumbprint, string subjectName, NodeId certificateType, string password) => m_innerStore.LoadPrivateKey(thumbprint, subjectName, certificateType, password); - [Obsolete] + [Obsolete("Method is deprecated. Use only for RSA certificates, the replacing LoadPrivateKey with certificateType parameter should be used.")] public Task LoadPrivateKey(string thumbprint, string subjectName, string password) => m_innerStore.LoadPrivateKey(thumbprint, subjectName, password); diff --git a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateStoreTypeTest.cs b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateStoreTypeTest.cs index 199366a48..a9d06be84 100644 --- a/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateStoreTypeTest.cs +++ b/Tests/Opc.Ua.Core.Tests/Security/Certificates/CertificateStoreTypeTest.cs @@ -144,6 +144,7 @@ public Task IsRevoked(X509Certificate2 issuer, X509Certificate2 cert public bool SupportsLoadPrivateKey => m_innerStore.SupportsLoadPrivateKey; /// + [Obsolete("Method is deprecated. Use only for RSA certificates, the replacing LoadPrivateKey with certificateType parameter should be used.")] public Task LoadPrivateKey(string thumbprint, string subjectName, string password) => m_innerStore.LoadPrivateKey(thumbprint, subjectName, password); From 550626978e3e37746834463b3ca787dadc3854f7 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Fri, 15 Mar 2024 12:55:58 -0700 Subject: [PATCH 40/80] ECC cert fixes --- .../X509Certificate/CertificateBuilder.cs | 2 +- .../Security/Certificates/CertificateFactory.cs | 1 + .../Security/Certificates/CertificateIdentifier.cs | 11 ++++++++--- .../Security/Certificates/CertificateValidator.cs | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs index 37614a6ce..b0fca59a2 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs @@ -417,7 +417,7 @@ private void CreateX509Extensions(CertificateRequest request, bool forECDsa) true)); } - if (!m_isCA) + if (!m_isCA && !forECDsa) { if (X509Extensions.FindExtension(m_extensions) == null) { diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs index f8c2da913..e02403def 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs @@ -300,6 +300,7 @@ DateTime nextUpdate /// public static byte[] CreateSigningRequest( X509Certificate2 certificate, + // TODO: provide CertificateType to return CSR per certificate type IList domainNames = null ) { diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index 9d95cd499..213e2ed1a 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -1060,17 +1060,22 @@ public enum CertificateValidationOptions #region CertificateTypesProvider /// - /// The identifier for an X509 certificate. + /// The provider for the X509 application certificates. /// public class CertificateTypesProvider { /// - /// + /// Create an instance of the certificate provider. /// public CertificateTypesProvider(ApplicationConfiguration config) { m_securityConfiguration = config.SecurityConfiguration; - m_certificateValidator = config.CertificateValidator; + m_certificateValidator = new CertificateValidator(); + m_certificateValidator.Update(m_securityConfiguration).GetAwaiter().GetResult(); + // for application certificates, allow untrusted and revocation status unknown, cache the known certs + m_certificateValidator.RejectUnknownRevocationStatus = false; + m_certificateValidator.AutoAcceptUntrustedCertificates = true; + m_certificateValidator.UseValidatedCertificates = true; } /// diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 46b2d47ec..00fac9a05 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -234,7 +234,7 @@ public virtual async Task Update(SecurityConfiguration configuration) Utils.Trace(Utils.TraceMasks.Security, "Could not find application certificate: {0}", applicationCertificate); continue; } - // Add to list of application certificates only if not allready in list + // Add to list of application certificates only if not already in list // necessary since the application certificates may be updated multiple times if (!m_applicationCertificates.Exists(cert => Utils.IsEqual(cert.RawData, certificate.RawData))) { From 0d03fa0a88e35bdc62688ec475bc0cd824b489c0 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Fri, 15 Mar 2024 15:11:49 -0700 Subject: [PATCH 41/80] fix a merge conflict (includes releaxed validator for cert loading) --- .../Certificates/CertificateIdentifier.cs | 136 ------------------ .../Certificates/CertificateTypesProvider.cs | 11 +- 2 files changed, 8 insertions(+), 139 deletions(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index fdc99787f..529bf9336 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -1055,140 +1055,4 @@ public enum CertificateValidationOptions TreatAsInvalid = 0x40 } #endregion - - #region CertificateTypesProvider - /// - /// The provider for the X509 application certificates. - /// - public class CertificateTypesProvider - { - /// - /// Create an instance of the certificate provider. - /// - public CertificateTypesProvider(ApplicationConfiguration config) - { - m_securityConfiguration = config.SecurityConfiguration; - m_certificateValidator = new CertificateValidator(); - m_certificateValidator.Update(m_securityConfiguration).GetAwaiter().GetResult(); - // for application certificates, allow untrusted and revocation status unknown, cache the known certs - m_certificateValidator.RejectUnknownRevocationStatus = false; - m_certificateValidator.AutoAcceptUntrustedCertificates = true; - m_certificateValidator.UseValidatedCertificates = true; - } - - /// - /// Gets or sets a value indicating whether the application should send the complete certificate chain. - /// - /// - /// If set to true the complete certificate chain will be sent for CA signed certificates. - /// - public bool SendCertificateChain => m_securityConfiguration.SendCertificateChain; - - /// - /// Return the instance certificate for a security policy. - /// - /// The security policy Uri - public X509Certificate2 GetInstanceCertificate(string securityPolicyUri) - { - if (securityPolicyUri == SecurityPolicies.None) - { - // return the default certificate for None - return m_securityConfiguration.ApplicationCertificates.FirstOrDefault().Certificate; - } - var certificateTypes = CertificateIdentifier.MapSecurityPolicyToCertificateTypes(securityPolicyUri); - foreach (var certType in certificateTypes) - { - var instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == certType); - if (instanceCertificate == null && - certType == ObjectTypeIds.RsaSha256ApplicationCertificateType) - { - instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == null); - } - if (instanceCertificate == null && - certType == ObjectTypeIds.ApplicationCertificateType) - { - instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(); - } - if (instanceCertificate != null) - { - return instanceCertificate.Certificate; - } - } - return null; - } - - /// - /// Load the instance certificate with a private key. - /// - /// - /// - public Task GetInstanceCertificateAsync(IList certificateTypes, bool privateKey) - { - foreach (var certType in certificateTypes) - { - var instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(id => id.CertificateType == certType); - if (instanceCertificate != null) - { - return instanceCertificate.Find(privateKey); - } - } - return Task.FromResult(null); - } - - /// - /// Loads the certificate chain of a certificate for use in a secure channel as raw byte array. - /// - /// The application certificate. - public async Task LoadCertificateChainRawAsync(X509Certificate2 certificate) - { - var instanceCertificateChain = await LoadCertificateChainAsync(certificate).ConfigureAwait(false); - if (instanceCertificateChain != null) - { - List serverCertificateChain = new List(); - for (int i = 0; i < instanceCertificateChain.Count; i++) - { - serverCertificateChain.AddRange(instanceCertificateChain[i].RawData); - } - return serverCertificateChain.ToArray(); - } - return null; - } - - /// - /// Loads the certificate chain for an application certificate. - /// - /// The application certificate. - public async Task LoadCertificateChainAsync(X509Certificate2 certificate) - { - if (certificate == null) - { - return null; - } - - // load certificate chain. - var certificateChain = new X509Certificate2Collection(certificate); - List issuers = new List(); - if (await m_certificateValidator.GetIssuers(certificate, issuers).ConfigureAwait(false)) - { - for (int i = 0; i < issuers.Count; i++) - { - certificateChain.Add(issuers[i].Certificate); - } - } - return certificateChain; - } - - /// - /// Update the security configuration of the cert type provider. - /// - /// The new security configuration. - public void Update(SecurityConfiguration securityConfiguration) - { - m_securityConfiguration = securityConfiguration; - } - - CertificateValidator m_certificateValidator; - SecurityConfiguration m_securityConfiguration; - } - #endregion } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs index 71d687fcc..658f42672 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs @@ -35,17 +35,22 @@ namespace Opc.Ua { /// - /// The identifier for an X509 certificate. + /// The provider for the X509 application certificates. /// public class CertificateTypesProvider { /// - /// Constructor + /// Create an instance of the certificate provider. /// public CertificateTypesProvider(ApplicationConfiguration config) { m_securityConfiguration = config.SecurityConfiguration; - m_certificateValidator = config.CertificateValidator; + m_certificateValidator = new CertificateValidator(); + m_certificateValidator.Update(m_securityConfiguration).GetAwaiter().GetResult(); + // for application certificates, allow untrusted and revocation status unknown, cache the known certs + m_certificateValidator.RejectUnknownRevocationStatus = false; + m_certificateValidator.AutoAcceptUntrustedCertificates = true; + m_certificateValidator.UseValidatedCertificates = true; } /// From ba26d64879c9143d4e58611df5b0bd2ccbeab7fb Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Fri, 15 Mar 2024 15:16:31 -0700 Subject: [PATCH 42/80] fix semaphore hang --- .../Security/Certificates/CertificateValidator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 16b3ebc9f..3c5efb96e 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -412,7 +412,7 @@ public ushort MinimumCertificateKeySize if (m_minimumCertificateKeySize != value) { m_minimumCertificateKeySize = value; - ResetValidatedCertificates(); + InternalResetValidatedCertificates(); } } finally @@ -438,7 +438,7 @@ public ushort MinimumECCertificateKeySize if (m_minimumECCertificateKeySize != value) { m_minimumECCertificateKeySize = value; - ResetValidatedCertificates(); + InternalResetValidatedCertificates(); } } @@ -465,7 +465,7 @@ public bool UseValidatedCertificates if (m_useValidatedCertificates != value) { m_useValidatedCertificates = value; - ResetValidatedCertificates(); + InternalResetValidatedCertificates(); } } finally From 3306aa91e8664fb4ee447f05baf8085f63ff59d2 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sun, 31 Mar 2024 10:03:16 +0200 Subject: [PATCH 43/80] fix CertificateTypes Provider & CertificateFactory --- .../Certificates/CertificateFactory.cs | 58 +++++++++++++------ .../Certificates/CertificateTypesProvider.cs | 5 ++ 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs index ade44646f..ed9f7a501 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs @@ -313,10 +313,20 @@ public static byte[] CreateSigningRequest( throw new NotSupportedException("Need a certificate with a private key."); } - RSA rsaPublicKey = certificate.GetRSAPublicKey(); - var request = new CertificateRequest(certificate.SubjectName, rsaPublicKey, - Oids.GetHashAlgorithmName(certificate.SignatureAlgorithm.Value), RSASignaturePadding.Pkcs1); + CertificateRequest request = null; + bool isECDsaSignature = X509PfxUtils.IsECDsaSignature(certificate); + if (!isECDsaSignature) + { + RSA rsaPublicKey = certificate.GetRSAPublicKey(); + request = new CertificateRequest(certificate.SubjectName, rsaPublicKey, + Oids.GetHashAlgorithmName(certificate.SignatureAlgorithm.Value), RSASignaturePadding.Pkcs1); + } + else + { + var eCDsaPublicKey = certificate.GetECDsaPublicKey(); + request = new CertificateRequest(certificate.SubjectName, eCDsaPublicKey, Oids.GetHashAlgorithmName(certificate.SignatureAlgorithm.Value)); + } var alternateName = X509Extensions.FindExtension(certificate); domainNames = domainNames ?? new List(); if (alternateName != null) @@ -342,11 +352,21 @@ public static byte[] CreateSigningRequest( // Subject Alternative Name var subjectAltName = new X509SubjectAltNameExtension(applicationUri, domainNames); request.CertificateExtensions.Add(new X509Extension(subjectAltName, false)); - - using (RSA rsa = certificate.GetRSAPrivateKey()) + if (!isECDsaSignature) { - var x509SignatureGenerator = X509SignatureGenerator.CreateForRSA(rsa, RSASignaturePadding.Pkcs1); - return request.CreateSigningRequest(x509SignatureGenerator); + using (RSA rsa = certificate.GetRSAPrivateKey()) + { + var x509SignatureGenerator = X509SignatureGenerator.CreateForRSA(rsa, RSASignaturePadding.Pkcs1); + return request.CreateSigningRequest(x509SignatureGenerator); + } + } + else + { + using (ECDsa key = certificate.GetECDsaPrivateKey()) + { + var x509SignatureGenerator = X509SignatureGenerator.CreateForECDsa(key); + return request.CreateSigningRequest(x509SignatureGenerator); + } } } @@ -483,18 +503,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/CertificateTypesProvider.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs index 658f42672..48be51fca 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs @@ -86,6 +86,11 @@ public X509Certificate2 GetInstanceCertificate(string securityPolicyUri) { instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(); } + if (instanceCertificate == null && + certType == ObjectTypeIds.HttpsCertificateType) + { + instanceCertificate = m_securityConfiguration.ApplicationCertificates.FirstOrDefault(); + } if (instanceCertificate != null) { return instanceCertificate.Certificate; From 84873bf01b470db35377f319fb9f48a7b4013f82 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Tue, 2 Apr 2024 10:34:06 +0300 Subject: [PATCH 44/80] client session assync initialize --- Libraries/Opc.Ua.Client/Session.cs | 18 +++++++++--------- .../ApplicationInstance.cs | 4 ++-- Libraries/Opc.Ua.Server/Session/Session.cs | 2 -- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index 09feb9294..bf6bd6822 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -99,7 +99,7 @@ public Session( : base(channel) { - Initialize(channel, configuration, endpoint, clientCertificate); + InitializeAsync(channel, configuration, endpoint, clientCertificate); m_discoveryServerEndpoints = availableEndpoints; m_discoveryProfileUris = discoveryProfileUris; } @@ -114,7 +114,7 @@ public Session(ITransportChannel channel, Session template, bool copyEventHandle : base(channel) { - Initialize(channel, template.m_configuration, template.ConfiguredEndpoint, template.m_instanceCertificate); + InitializeAsync(channel, template.m_configuration, template.ConfiguredEndpoint, template.m_instanceCertificate); m_sessionFactory = template.m_sessionFactory; m_defaultSubscription = template.m_defaultSubscription; @@ -156,7 +156,7 @@ public Session(ITransportChannel channel, Session template, bool copyEventHandle /// /// Initializes the channel. /// - private void Initialize( + private async void InitializeAsync( ITransportChannel channel, ApplicationConfiguration configuration, ConfiguredEndpoint endpoint, @@ -177,7 +177,7 @@ private void Initialize( { if (clientCertificate == null) { - m_instanceCertificate = LoadCertificate(configuration, m_endpoint.Description.SecurityPolicyUri).GetAwaiter().GetResult(); + m_instanceCertificate = await LoadCertificateAsync(configuration, m_endpoint.Description.SecurityPolicyUri).ConfigureAwait(false); if (m_instanceCertificate == null) { throw new ServiceResultException( @@ -202,7 +202,7 @@ private void Initialize( } // load certificate chain. - m_instanceCertificateChain = LoadCertificateChain(configuration, m_instanceCertificate).GetAwaiter().GetResult(); + m_instanceCertificateChain = await LoadCertificateChainAsync(configuration, m_instanceCertificate).ConfigureAwait(false); } // initialize the message context. @@ -991,8 +991,8 @@ public static async Task CreateChannelAsync( X509Certificate2Collection clientCertificateChain = null; if (endpointDescription.SecurityPolicyUri != SecurityPolicies.None) { - clientCertificate = await LoadCertificate(configuration, endpointDescription.SecurityPolicyUri).ConfigureAwait(false); - clientCertificateChain = await LoadCertificateChain(configuration, clientCertificate).ConfigureAwait(false); + clientCertificate = await LoadCertificateAsync(configuration, endpointDescription.SecurityPolicyUri).ConfigureAwait(false); + clientCertificateChain = await LoadCertificateChainAsync(configuration, clientCertificate).ConfigureAwait(false); } // initialize the channel which will be created with the server. @@ -6112,7 +6112,7 @@ private void DeleteSubscription(uint subscriptionId) /// /// Load certificate for connection. /// - private static async Task LoadCertificate(ApplicationConfiguration configuration, string securityProfile) + private static async Task LoadCertificateAsync(ApplicationConfiguration configuration, string securityProfile) { X509Certificate2 clientCertificate = await configuration.SecurityConfiguration.FindApplicationCertificateAsync(securityProfile, true).ConfigureAwait(false); @@ -6130,7 +6130,7 @@ private static async Task LoadCertificate(ApplicationConfigura /// /// Load certificate chain for connection. /// - private static async Task LoadCertificateChain(ApplicationConfiguration configuration, X509Certificate2 clientCertificate) + private static async Task LoadCertificateChainAsync(ApplicationConfiguration configuration, X509Certificate2 clientCertificate) { X509Certificate2Collection clientCertificateChain = null; // load certificate chain. diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index 409367397..ea2a75cf4 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -424,10 +424,10 @@ public Task CheckApplicationInstanceCertificate( /// Checks for a valid application instance certificate. /// /// if set to true no dialogs will be displayed. - public Task CheckApplicationInstanceCertificates( + public async Task CheckApplicationInstanceCertificates( bool silent) { - return CheckApplicationInstanceCertificates(silent, CertificateFactory.DefaultLifeTime); + return await CheckApplicationInstanceCertificates(silent, CertificateFactory.DefaultLifeTime).ConfigureAwait(false); } /// diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs index b44bac23a..b904294c2 100644 --- a/Libraries/Opc.Ua.Server/Session/Session.cs +++ b/Libraries/Opc.Ua.Server/Session/Session.cs @@ -29,9 +29,7 @@ using System; using System.Collections.Generic; -using System.Text; using System.Security.Cryptography.X509Certificates; -using System.Reflection; namespace Opc.Ua.Server { From 5fdc79d883c02a99cffc29674bf8c9cc7d16700c Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Tue, 2 Apr 2024 15:06:36 +0300 Subject: [PATCH 45/80] Deprecated Utils.Nonce --- Libraries/Opc.Ua.Client/Session.cs | 6 +- Libraries/Opc.Ua.Client/SessionAsync.cs | 2 +- .../Opc.Ua.Server/Session/SessionManager.cs | 8 +- .../Subscription/SubscriptionManager.cs | 2 +- .../Security/Certificates/Nonce.cs | 417 ++++++++++++------ .../Security/Certificates/X509Utils.cs | 2 +- .../Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs | 2 +- Stack/Opc.Ua.Core/Types/Utils/Utils.cs | 74 +--- .../Types/BuiltIn/BuiltInTests.cs | 4 +- 9 files changed, 315 insertions(+), 202 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index bf6bd6822..e85ffde34 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -334,7 +334,7 @@ private void ValidateServerNonce( if (identity != null && identity.TokenType != UserTokenType.Anonymous) { // the server nonce should be validated if the token includes a secret. - if (!Utils.Nonce.ValidateNonce(serverNonce, MessageSecurityMode.SignAndEncrypt, (uint)m_configuration.SecurityConfiguration.NonceLength)) + if (!Nonce.ValidateNonce(serverNonce, MessageSecurityMode.SignAndEncrypt, (uint)m_configuration.SecurityConfiguration.NonceLength)) { if (channelSecurityMode == MessageSecurityMode.SignAndEncrypt || m_configuration.SecurityConfiguration.SuppressNonceValidationErrors) @@ -348,7 +348,7 @@ private void ValidateServerNonce( } // check that new nonce is different from the previously returned server nonce. - if (previousServerNonce != null && Utils.CompareNonce(serverNonce, previousServerNonce)) + if (previousServerNonce != null && Nonce.CompareNonce(serverNonce, previousServerNonce)) { if (channelSecurityMode == MessageSecurityMode.SignAndEncrypt || m_configuration.SecurityConfiguration.SuppressNonceValidationErrors) @@ -2339,7 +2339,7 @@ public void Open( // create a nonce. uint length = (uint)m_configuration.SecurityConfiguration.NonceLength; - byte[] clientNonce = Utils.Nonce.CreateNonce(length); + byte[] clientNonce = Nonce.CreateRandomNonceData(length); NodeId sessionId = null; NodeId sessionCookie = null; byte[] serverNonce = Array.Empty(); diff --git a/Libraries/Opc.Ua.Client/SessionAsync.cs b/Libraries/Opc.Ua.Client/SessionAsync.cs index dd97637e2..5beffdfba 100644 --- a/Libraries/Opc.Ua.Client/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/SessionAsync.cs @@ -108,7 +108,7 @@ public async Task OpenAsync( // create a nonce. uint length = (uint)m_configuration.SecurityConfiguration.NonceLength; - byte[] clientNonce = Utils.Nonce.CreateNonce(length); + byte[] clientNonce = Nonce.CreateRandomNonceData(length); // send the application instance certificate for the client. BuildCertificateData(out byte[] clientCertificateData, out byte[] clientCertificateChainData); diff --git a/Libraries/Opc.Ua.Server/Session/SessionManager.cs b/Libraries/Opc.Ua.Server/Session/SessionManager.cs index 829774ef0..06c13afd7 100644 --- a/Libraries/Opc.Ua.Server/Session/SessionManager.cs +++ b/Libraries/Opc.Ua.Server/Session/SessionManager.cs @@ -63,7 +63,7 @@ public SessionManager( m_minNonceLength = configuration.SecurityConfiguration.NonceLength; m_sessions = new Dictionary(); - m_lastSessionId = BitConverter.ToInt64(Utils.Nonce.CreateNonce(sizeof(long)), 0); + m_lastSessionId = BitConverter.ToInt64(Nonce.CreateRandomNonceData(sizeof(long)), 0); // create a event to signal shutdown. m_shutdownEvent = new ManualResetEvent(true); @@ -180,7 +180,7 @@ public virtual Session CreateSession( { foreach (Session sessionIterator in m_sessions.Values) { - if (Utils.CompareNonce(sessionIterator.ClientNonce, clientNonce)) + if (Nonce.CompareNonce(sessionIterator.ClientNonce, clientNonce)) { throw new ServiceResultException(StatusCodes.BadNonceInvalid); } @@ -200,7 +200,7 @@ public virtual Session CreateSession( // must assign a hard-to-guess id if not secured. if (authenticationToken == null) { - byte[] token = Utils.Nonce.CreateNonce(32); + byte[] token = Nonce.CreateRandomNonceData(32); authenticationToken = new NodeId(token); } @@ -220,7 +220,7 @@ public virtual Session CreateSession( var serverNonceObject = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri, (uint)m_minNonceLength); #else - serverNonce = Utils.Nonce.CreateNonce((uint)m_minNonceLength); + serverNonce = Nonce.CreateRandomNonceData((uint)m_minNonceLength); #endif // assign client name. diff --git a/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs b/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs index bb2a99350..c2f797ece 100644 --- a/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs +++ b/Libraries/Opc.Ua.Server/Subscription/SubscriptionManager.cs @@ -66,7 +66,7 @@ public SubscriptionManager( m_subscriptions = new Dictionary(); m_publishQueues = new Dictionary(); m_statusMessages = new Dictionary>(); - m_lastSubscriptionId = BitConverter.ToInt64(Utils.Nonce.CreateNonce(sizeof(long)), 0); + m_lastSubscriptionId = BitConverter.ToInt64(Nonce.CreateRandomNonceData(sizeof(long)), 0); // create a event to signal shutdown. m_shutdownEvent = new ManualResetEvent(true); diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs index 596b95b9d..ece6abe70 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs @@ -12,12 +12,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. #if ECC_SUPPORT using System; -using System.Text; -using System.IO; -using System.Collections.Generic; using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using Opc.Ua.Security.Certificates; using System.Runtime.Serialization; #if CURVE25519 @@ -35,32 +30,17 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. namespace Opc.Ua { - /// /// Represents a cryptographic nonce used for secure communication. /// - [Serializable] public class Nonce : IDisposable, ISerializable { - private ECDiffieHellman m_ecdh; - - -#if CURVE25519 - private AsymmetricCipherKeyPair m_bcKeyPair; -#endif - - enum Algorithm - { - Unknown, - RSA, - nistP256, - nistP384, - brainpoolP256r1, - brainpoolP384r1, - Ed25519 - } + #region Constructor + /// + /// Constructor + /// private Nonce() { m_ecdh = null; @@ -68,49 +48,30 @@ private Nonce() m_bcKeyPair = null; #endif } + #endregion - private HMAC returnHMACInstance(byte[] secret, HashAlgorithmName algorithm) - { - switch (algorithm.Name) - { - case "SHA256": - return new HMACSHA256(secret); - case "SHA384": - return new HMACSHA384(secret); - default: - return new HMACSHA256(secret); - } - } - - #region IDisposable Members - /// - /// Frees any unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - } + #region Public Properties /// - /// An overrideable version of the Dispose. + /// Gets the nonce data. /// - protected virtual void Dispose(bool disposing) + public byte[] Data { - if (disposing) + get { - if (m_ecdh != null) - { - m_ecdh.Dispose(); - m_ecdh = null; - } + return m_data; + } + private set + { + m_data = value; } } + #endregion - /// - /// Gets the nonce data. - /// - public byte[] Data { get; private set; } + #region Public Methods + + #region Instance Methods /// /// Derives a key from the remote nonce, using the specified salt, hash algorithm, and length. @@ -207,6 +168,9 @@ public byte[] DeriveKey(Nonce remoteNonce, byte[] salt, HashAlgorithmName algori return Data; } + #endregion + + #region Factory Methods /// /// Creates a nonce for the specified security policy URI and nonce length. /// @@ -242,83 +206,13 @@ public static Nonce CreateNonce(string securityPolicyUri, uint nonceLength) default: { nonce = new Nonce() { - Data = Utils.Nonce.CreateNonce(nonceLength) + Data = CreateRandomNonceData(nonceLength) }; return nonce; } } } -#if CURVE25519 - /// - /// Creates a new Nonce object to be used in Curve25519 cryptography. - /// - /// A new Nonce object. - private static Nonce CreateNonceForCurve25519() - { - SecureRandom random = new SecureRandom(); - IAsymmetricCipherKeyPairGenerator generator = new X25519KeyPairGenerator(); - generator.Init(new X25519KeyGenerationParameters(random)); - - var keyPair = generator.GenerateKeyPair(); - - byte[] senderNonce = new byte[X25519PublicKeyParameters.KeySize]; - ((X25519PublicKeyParameters)(keyPair.Public)).Encode(senderNonce, 0); - - var nonce = new Nonce() { - Data = senderNonce, - m_bcKeyPair = keyPair - }; - - return nonce; - } - - /// - /// Creates a Nonce object using the X448 elliptic curve algorithm. - /// - /// A Nonce object containing the generated nonce data and key pair. - private static Nonce CreateNonceForCurve448() - { - SecureRandom random = new SecureRandom(); - IAsymmetricCipherKeyPairGenerator generator = new X448KeyPairGenerator(); - generator.Init(new X448KeyGenerationParameters(random)); - - var keyPair = generator.GenerateKeyPair(); - - byte[] senderNonce = new byte[X448PublicKeyParameters.KeySize]; - ((X448PublicKeyParameters)(keyPair.Public)).Encode(senderNonce, 0); - - var nonce = new Nonce() { - Data = senderNonce, - m_bcKeyPair = keyPair - }; - - return nonce; - } -#endif - - /// - /// Creates a new Nonce instance using the specified elliptic curve. - /// - /// The elliptic curve to use for the ECDH key exchange. - /// A new Nonce instance. - private static Nonce CreateNonce(ECCurve curve) - { - var ecdh = (ECDiffieHellman)ECDiffieHellman.Create(curve); - var ecdhParameters = ecdh.ExportParameters(false); - int xLen = ecdhParameters.Q.X.Length; - int yLen = ecdhParameters.Q.Y.Length; - byte[] senderNonce = new byte[xLen + yLen]; - Array.Copy(ecdhParameters.Q.X, senderNonce, xLen); - Array.Copy(ecdhParameters.Q.Y, 0, senderNonce, xLen, yLen); - - var nonce = new Nonce() { - Data = senderNonce, - m_ecdh = ecdh - }; - - return nonce; - } /// /// Creates a new Nonce object for the specified security policy URI and nonce data. @@ -367,7 +261,128 @@ public static Nonce CreateNonce(string securityPolicyUri, byte[] nonceData) return nonce; } + #endregion + + #region Utility Methods + + /// + /// Generates a Nonce for cryptographic functions of a given length. + /// + /// + /// The requested Nonce as a + public static byte[] CreateRandomNonceData(uint length) + { + byte[] randomBytes = new byte[length]; + m_rng.GetBytes(randomBytes); + return randomBytes; + } + + /// + /// Validates the nonce for a message security mode and security policy. + /// + public static bool ValidateNonce(byte[] nonce, MessageSecurityMode securityMode, string securityPolicyUri) + { + return ValidateNonce(nonce, securityMode, GetNonceLength(securityPolicyUri)); + } + + /// + /// Validates the nonce for a message security mode and a minimum length. + /// + public static bool ValidateNonce(byte[] nonce, MessageSecurityMode securityMode, uint minNonceLength) + { + // no nonce needed for no security. + if (securityMode == MessageSecurityMode.None) + { + return true; + } + + // check the length. + if (nonce == null || nonce.Length < minNonceLength) + { + return false; + } + + // try to catch programming errors by rejecting nonces with all zeros. + for (int ii = 0; ii < nonce.Length; ii++) + { + if (nonce[ii] != 0) + { + return true; + } + } + return false; + } + + /// + /// Returns the length of the symmetric encryption key for a security policy. + /// + public static uint GetNonceLength(string securityPolicyUri) + { + switch (securityPolicyUri) + { + case SecurityPolicies.Basic128Rsa15: + { + return 16; + } + + case SecurityPolicies.Basic256: + case SecurityPolicies.Basic256Sha256: + case SecurityPolicies.Aes128_Sha256_RsaOaep: + case SecurityPolicies.Aes256_Sha256_RsaPss: + case SecurityPolicies.ECC_curve25519: + { + return 32; + } + + case SecurityPolicies.ECC_nistP256: + case SecurityPolicies.ECC_brainpoolP256r1: + { + // Q.X + Q.Y = 32 + 32 = 64 + return 64; + } + + case SecurityPolicies.ECC_nistP384: + case SecurityPolicies.ECC_brainpoolP384r1: + { + // Q.X + Q.Y = 48 + 48 = 96 + return 96; + } + + case SecurityPolicies.ECC_curve448: + { + // Q.X + return 56; + } + + default: + case SecurityPolicies.None: + { + return 0; + } + } + } + + /// + /// Compare Nonce for equality. + /// + public static bool CompareNonce(byte[] a, byte[] b) + { + if (a == null || b == null) return false; + if (a.Length != b.Length) return false; + + byte result = 0; + for (int i = 0; i < a.Length; i++) + result |= (byte)(a[i] ^ b[i]); + + return result == 0; + } + + #endregion + + #endregion + + #region Private Methods /// /// Creates a new Nonce object for use with Curve25519. @@ -426,6 +441,102 @@ private static Nonce CreateNonce(ECCurve curve, byte[] nonceData) return nonce; } + /// + /// Creates a new Nonce instance using the specified elliptic curve. + /// + /// The elliptic curve to use for the ECDH key exchange. + /// A new Nonce instance. + private static Nonce CreateNonce(ECCurve curve) + { + var ecdh = (ECDiffieHellman)ECDiffieHellman.Create(curve); + var ecdhParameters = ecdh.ExportParameters(false); + int xLen = ecdhParameters.Q.X.Length; + int yLen = ecdhParameters.Q.Y.Length; + byte[] senderNonce = new byte[xLen + yLen]; + Array.Copy(ecdhParameters.Q.X, senderNonce, xLen); + Array.Copy(ecdhParameters.Q.Y, 0, senderNonce, xLen, yLen); + + var nonce = new Nonce() { + Data = senderNonce, + m_ecdh = ecdh + }; + + return nonce; + } + + + + /// + /// Return the HMAC instance depending on secret and algortihm + /// + /// + /// + /// + private HMAC returnHMACInstance(byte[] secret, HashAlgorithmName algorithm) + { + switch (algorithm.Name) + { + case "SHA256": + return new HMACSHA256(secret); + case "SHA384": + return new HMACSHA384(secret); + default: + return new HMACSHA256(secret); + } + } + +#if CURVE25519 + /// + /// Creates a new Nonce object to be used in Curve25519 cryptography. + /// + /// A new Nonce object. + private static Nonce CreateNonceForCurve25519() + { + SecureRandom random = new SecureRandom(); + IAsymmetricCipherKeyPairGenerator generator = new X25519KeyPairGenerator(); + generator.Init(new X25519KeyGenerationParameters(random)); + + var keyPair = generator.GenerateKeyPair(); + + byte[] senderNonce = new byte[X25519PublicKeyParameters.KeySize]; + ((X25519PublicKeyParameters)(keyPair.Public)).Encode(senderNonce, 0); + + var nonce = new Nonce() { + Data = senderNonce, + m_bcKeyPair = keyPair + }; + + return nonce; + } + + /// + /// Creates a Nonce object using the X448 elliptic curve algorithm. + /// + /// A Nonce object containing the generated nonce data and key pair. + private static Nonce CreateNonceForCurve448() + { + SecureRandom random = new SecureRandom(); + IAsymmetricCipherKeyPairGenerator generator = new X448KeyPairGenerator(); + generator.Init(new X448KeyGenerationParameters(random)); + + var keyPair = generator.GenerateKeyPair(); + + byte[] senderNonce = new byte[X448PublicKeyParameters.KeySize]; + ((X448PublicKeyParameters)(keyPair.Public)).Encode(senderNonce, 0); + + var nonce = new Nonce() { + Data = senderNonce, + m_bcKeyPair = keyPair + }; + + return nonce; + } +#endif + + + #endregion + + #region Protected Methods /// /// Custom deserialization /// @@ -443,11 +554,55 @@ protected Nonce(SerializationInfo info, StreamingContext context) }; m_ecdh = ECDiffieHellman.Create(ecParams); - Data = (byte[])info.GetValue("Data", typeof (byte[])); + Data = (byte[])info.GetValue("Data", typeof(byte[])); } + #endregion + #region Private Members + + private ECDiffieHellman m_ecdh; + + private byte[] m_data; + +#if CURVE25519 + private AsymmetricCipherKeyPair m_bcKeyPair; +#endif + + #endregion + + #region Private Static Members + private static readonly RandomNumberGenerator m_rng = RandomNumberGenerator.Create(); + #endregion + + #region IDisposable /// - /// Custom serializarion + /// Frees any unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// An overrideable version of the Dispose. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (m_ecdh != null) + { + m_ecdh.Dispose(); + m_ecdh = null; + } + } + } + #endregion + + #region ISerializable + + /// + /// Custom serialization /// /// /// @@ -465,6 +620,8 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) info.AddValue("Data", Data); } } + + #endregion } } #endif diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs index 2a6b5cd61..0ee355776 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs @@ -726,7 +726,7 @@ public static HashAlgorithmName GetRSAHashAlgorithmName(uint hashSizeInBits) internal static string GeneratePasscode() { const int kLength = 18; - byte[] tokenBuffer = Utils.Nonce.CreateNonce(kLength); + byte[] tokenBuffer = Nonce.CreateRandomNonceData(kLength); return Convert.ToBase64String(tokenBuffer); } diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs index c1286e26f..60644ae9e 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs @@ -244,7 +244,7 @@ protected byte[] CreateNonce(X509Certificate2 certificate) if (length > 0) { - return Utils.Nonce.CreateNonce(length); + return Nonce.CreateRandomNonceData(length); } break; diff --git a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs index facc21651..13bfd7991 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs @@ -29,6 +29,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using Opc.Ua.Security.Certificates; using System.Runtime.InteropServices; +using NewNonceImplementation = Opc.Ua.Nonce; + namespace Opc.Ua { /// @@ -2529,7 +2531,7 @@ public static void UpdateExtension(ref XmlElementCollection extensions, XmlQu extensions.Add(document.DocumentElement); } } -#endregion + #endregion #region Reflection Helper Functions /// @@ -2792,98 +2794,52 @@ public static X509Certificate2Collection ParseCertificateChainBlob(byte[] certif /// /// Compare Nonce for equality. /// + [Obsolete("Use equivalent methods from the Opc.Ua.Nonce class")] public static bool CompareNonce(byte[] a, byte[] b) { - if (a == null || b == null) return false; - if (a.Length != b.Length) return false; - - byte result = 0; - for (int i = 0; i < a.Length; i++) - result |= (byte)(a[i] ^ b[i]); - - return result == 0; + return NewNonceImplementation.CompareNonce(a, b); } /// /// Cryptographic Nonce helper functions. /// + [Obsolete("Use equivalent methods from the Opc.Ua.Nonce class")] public static class Nonce { - static readonly RandomNumberGenerator m_rng = RandomNumberGenerator.Create(); - /// /// Generates a Nonce for cryptographic functions. /// + [Obsolete("Use equivalent CreateRandomNonceData method from the Opc.Ua.Nonce class")] public static byte[] CreateNonce(uint length) { - byte[] randomBytes = new byte[length]; - m_rng.GetBytes(randomBytes); - return randomBytes; + return NewNonceImplementation.CreateRandomNonceData(length); } /// /// Returns the length of the symmetric encryption key for a security policy. /// + [Obsolete("Use equivalent method from the Opc.Ua.Nonce class")] public static uint GetNonceLength(string securityPolicyUri) { - switch (securityPolicyUri) - { - case SecurityPolicies.Basic128Rsa15: - { - return 16; - } - - case SecurityPolicies.Basic256: - case SecurityPolicies.Basic256Sha256: - case SecurityPolicies.Aes128_Sha256_RsaOaep: - case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return 32; - } - - default: - case SecurityPolicies.None: - { - return 0; - } - } + return NewNonceImplementation.GetNonceLength(securityPolicyUri); } /// /// Validates the nonce for a message security mode and security policy. /// + [Obsolete("Use equivalent method from the Opc.Ua.Nonce class")] public static bool ValidateNonce(byte[] nonce, MessageSecurityMode securityMode, string securityPolicyUri) { - return ValidateNonce(nonce, securityMode, GetNonceLength(securityPolicyUri)); + return NewNonceImplementation.ValidateNonce(nonce, securityMode, GetNonceLength(securityPolicyUri)); } /// /// Validates the nonce for a message security mode and a minimum length. /// + [Obsolete("Use equivalent method from the Opc.Ua.Nonce class")] public static bool ValidateNonce(byte[] nonce, MessageSecurityMode securityMode, uint minNonceLength) { - // no nonce needed for no security. - if (securityMode == MessageSecurityMode.None) - { - return true; - } - - // check the length. - if (nonce == null || nonce.Length < minNonceLength) - { - return false; - } - - // try to catch programming errors by rejecting nonces with all zeros. - for (int ii = 0; ii < nonce.Length; ii++) - { - if (nonce[ii] != 0) - { - return true; - } - } - - return false; + return NewNonceImplementation.ValidateNonce(nonce,securityMode, minNonceLength); } } @@ -3121,6 +3077,6 @@ public static bool IsRunningOnMono() { return s_isRunningOnMonoValue.Value; } -#endregion + #endregion } } diff --git a/Tests/Opc.Ua.Core.Tests/Types/BuiltIn/BuiltInTests.cs b/Tests/Opc.Ua.Core.Tests/Types/BuiltIn/BuiltInTests.cs index e1d72ea93..93fb2f971 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/BuiltIn/BuiltInTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/BuiltIn/BuiltInTests.cs @@ -615,7 +615,7 @@ public void NodeIdComparison(Opc.Ua.IdType idType) case Opc.Ua.IdType.Numeric: nodeId = new NodeId(DataGenerator.GetRandomUInt16(), DataGenerator.GetRandomByte()); break; case Opc.Ua.IdType.String: nodeId = new NodeId(DataGenerator.GetRandomString(), 0); break; case Opc.Ua.IdType.Guid: nodeId = new NodeId(DataGenerator.GetRandomGuid()); break; - case Opc.Ua.IdType.Opaque: nodeId = new NodeId(Utils.Nonce.CreateNonce(32)); break; + case Opc.Ua.IdType.Opaque: nodeId = new NodeId(Nonce.CreateRandomNonceData(32)); break; } NodeId nodeIdClone = (NodeId)nodeId.Clone(); Assert.AreEqual(nodeId, nodeIdClone); @@ -643,7 +643,7 @@ public void NodeIdComparison(Opc.Ua.IdType idType) case Opc.Ua.IdType.Numeric: nodeId2 = new NodeId(DataGenerator.GetRandomUInt16(), DataGenerator.GetRandomByte()); break; case Opc.Ua.IdType.String: nodeId2 = new NodeId(DataGenerator.GetRandomString(), 0); break; case Opc.Ua.IdType.Guid: nodeId2 = new NodeId(DataGenerator.GetRandomGuid()); break; - case Opc.Ua.IdType.Opaque: nodeId2 = new NodeId(Utils.Nonce.CreateNonce(32)); break; + case Opc.Ua.IdType.Opaque: nodeId2 = new NodeId(Nonce.CreateRandomNonceData(32)); break; } dictionary.Add(nodeId2, "TestClone"); Assert.AreEqual(2, dictionary.Distinct().ToList().Count); From 058cb960dfbcb08709e5c958431ff079867cb88c Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 4 Apr 2024 11:22:11 +0300 Subject: [PATCH 46/80] Simplify Nonce.CreateNonce method, added NonceTests UnitTest --- .../Opc.Ua.Server/Server/StandardServer.cs | 3 + Libraries/Opc.Ua.Server/Session/Session.cs | 2 +- .../Opc.Ua.Server/Session/SessionManager.cs | 9 +- .../Security/Certificates/Nonce.cs | 19 +- .../Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs | 57 +----- .../Stack/Types/UserIdentityToken.cs | 2 +- .../Types/BuiltIn/BuiltInTests.cs | 4 +- .../Types/Nonce/NonceTests.cs | 163 ++++++++++++++++++ 8 files changed, 192 insertions(+), 67 deletions(-) create mode 100644 Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs index 219ce8dde..ea868d7a6 100644 --- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs +++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs @@ -2986,6 +2986,9 @@ protected override void StartApplication(ApplicationConfiguration configuration) { Utils.LogInfo(TraceMasks.StartStop, "Server - Start application {0}.", configuration.ApplicationName); + // Setup the minimum nonce length + Nonce.SetMinNonceValue((uint)configuration.SecurityConfiguration.NonceLength); + // create the datastore for the instance. m_serverInternal = new ServerInternalData( ServerProperties, diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs index b904294c2..24c6d866f 100644 --- a/Libraries/Opc.Ua.Server/Session/Session.cs +++ b/Libraries/Opc.Ua.Server/Session/Session.cs @@ -419,7 +419,7 @@ public virtual EphemeralKeyType GetNewEccKey() return null; } - m_eccUserTokenNonce = Nonce.CreateNonce(m_eccUserTokenSecurityPolicyUri, 0); + m_eccUserTokenNonce = Nonce.CreateNonce(m_eccUserTokenSecurityPolicyUri); EphemeralKeyType key = new EphemeralKeyType() { PublicKey = m_eccUserTokenNonce.Data diff --git a/Libraries/Opc.Ua.Server/Session/SessionManager.cs b/Libraries/Opc.Ua.Server/Session/SessionManager.cs index 06c13afd7..3f3326033 100644 --- a/Libraries/Opc.Ua.Server/Session/SessionManager.cs +++ b/Libraries/Opc.Ua.Server/Session/SessionManager.cs @@ -60,7 +60,6 @@ public SessionManager( m_maxRequestAge = configuration.ServerConfiguration.MaxRequestAge; m_maxBrowseContinuationPoints = configuration.ServerConfiguration.MaxBrowseContinuationPoints; m_maxHistoryContinuationPoints = configuration.ServerConfiguration.MaxHistoryContinuationPoints; - m_minNonceLength = configuration.SecurityConfiguration.NonceLength; m_sessions = new Dictionary(); m_lastSessionId = BitConverter.ToInt64(Nonce.CreateRandomNonceData(sizeof(long)), 0); @@ -217,8 +216,7 @@ public virtual Session CreateSession( // create server nonce. #if ECC_SUPPORT - var serverNonceObject = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri, - (uint)m_minNonceLength); + var serverNonceObject = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri); #else serverNonce = Nonce.CreateRandomNonceData((uint)m_minNonceLength); #endif @@ -357,9 +355,9 @@ public virtual bool ActivateSession( // create new server nonce. #if ECC_SUPPORT - serverNonceObject = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri, (uint)m_minNonceLength); + serverNonceObject = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri); #else - serverNonce = Utils.Nonce.CreateNonce((uint)m_minNonceLength); + serverNonce = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri).Data; #endif // validate before activation. @@ -753,7 +751,6 @@ private void MonitorSessions(object data) private int m_maxRequestAge; private int m_maxBrowseContinuationPoints; private int m_maxHistoryContinuationPoints; - private int m_minNonceLength; private readonly object m_eventLock = new object(); private event SessionEventHandler m_sessionCreated; diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs index ece6abe70..30d167217 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs @@ -175,9 +175,8 @@ public byte[] DeriveKey(Nonce remoteNonce, byte[] salt, HashAlgorithmName algori /// Creates a nonce for the specified security policy URI and nonce length. /// /// The security policy URI. - /// The length of the nonce. /// A object containing the generated nonce. - public static Nonce CreateNonce(string securityPolicyUri, uint nonceLength) + public static Nonce CreateNonce(string securityPolicyUri) { if (securityPolicyUri == null) { @@ -205,8 +204,9 @@ public static Nonce CreateNonce(string securityPolicyUri, uint nonceLength) #endif default: { + uint rsaNonceLength = GetNonceLength(securityPolicyUri); nonce = new Nonce() { - Data = CreateRandomNonceData(nonceLength) + Data = CreateRandomNonceData(rsaNonceLength) }; return nonce; @@ -358,7 +358,8 @@ public static uint GetNonceLength(string securityPolicyUri) default: case SecurityPolicies.None: { - return 0; + // Minimum nonce length by default + return m_minNonceLength; } } } @@ -378,6 +379,14 @@ public static bool CompareNonce(byte[] a, byte[] b) return result == 0; } + /// + /// Sets the minimum nonce value to be used as default + /// + /// + public static void SetMinNonceValue(uint nonceLength) + { + m_minNonceLength = nonceLength; + } #endregion #endregion @@ -572,6 +581,8 @@ protected Nonce(SerializationInfo info, StreamingContext context) #region Private Static Members private static readonly RandomNumberGenerator m_rng = RandomNumberGenerator.Create(); + + private static uint m_minNonceLength = 0; #endregion #region IDisposable diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs index 60644ae9e..70159de57 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs @@ -177,56 +177,7 @@ protected static void CompareCertificates(X509Certificate2 expected, X509Certifi #endregion #region Asymmetric Cryptography Functions - /// - /// Returns the length of the symmetric encryption key. - /// - protected uint GetNonceLength() - { - switch (SecurityPolicyUri) - { - case SecurityPolicies.Basic128Rsa15: - { - return 16; - } - - case SecurityPolicies.Basic256: - case SecurityPolicies.Basic256Sha256: - case SecurityPolicies.Aes128_Sha256_RsaOaep: - case SecurityPolicies.Aes256_Sha256_RsaPss: - { - return 32; - } - - case SecurityPolicies.ECC_nistP256: - case SecurityPolicies.ECC_brainpoolP256r1: - { - return 64; - } - - case SecurityPolicies.ECC_nistP384: - case SecurityPolicies.ECC_brainpoolP384r1: - { - return 96; - } - - case SecurityPolicies.ECC_curve25519: - { - return 32; - } - - case SecurityPolicies.ECC_curve448: - { - return 56; - } - - default: - case SecurityPolicies.None: - { - return 0; - } - } - } - + /// /// Validates the nonce. /// @@ -240,7 +191,7 @@ protected byte[] CreateNonce(X509Certificate2 certificate) case SecurityPolicies.Aes128_Sha256_RsaOaep: case SecurityPolicies.Aes256_Sha256_RsaPss: { - uint length = GetNonceLength(); + uint length = Nonce.GetNonceLength(SecurityPolicyUri); if (length > 0) { @@ -257,7 +208,7 @@ protected byte[] CreateNonce(X509Certificate2 certificate) case SecurityPolicies.ECC_curve25519: case SecurityPolicies.ECC_curve448: { - m_localNonce = Nonce.CreateNonce(SecurityPolicyUri, GetNonceLength()); + m_localNonce = Nonce.CreateNonce(SecurityPolicyUri); return m_localNonce.Data; } #endif @@ -283,7 +234,7 @@ protected bool ValidateNonce(X509Certificate2 certificate, byte[] nonce) } // check the length. - if (nonce == null || nonce.Length < GetNonceLength()) + if (nonce == null || nonce.Length < Nonce.GetNonceLength(SecurityPolicyUri)) { return false; } diff --git a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs index 33eeb887b..531e2d2da 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs @@ -267,7 +267,7 @@ public override void Encrypt( } secret.SenderIssuerCertificates = senderIssuerCertificates; - secret.SenderNonce = Nonce.CreateNonce(securityPolicyUri, 0); + secret.SenderNonce = Nonce.CreateNonce(securityPolicyUri); var utf8 = new UTF8Encoding(false).GetBytes(m_decryptedPassword); m_password = secret.Encrypt(utf8, receiverNonce); diff --git a/Tests/Opc.Ua.Core.Tests/Types/BuiltIn/BuiltInTests.cs b/Tests/Opc.Ua.Core.Tests/Types/BuiltIn/BuiltInTests.cs index 93fb2f971..4e55daee6 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/BuiltIn/BuiltInTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/BuiltIn/BuiltInTests.cs @@ -615,7 +615,7 @@ public void NodeIdComparison(Opc.Ua.IdType idType) case Opc.Ua.IdType.Numeric: nodeId = new NodeId(DataGenerator.GetRandomUInt16(), DataGenerator.GetRandomByte()); break; case Opc.Ua.IdType.String: nodeId = new NodeId(DataGenerator.GetRandomString(), 0); break; case Opc.Ua.IdType.Guid: nodeId = new NodeId(DataGenerator.GetRandomGuid()); break; - case Opc.Ua.IdType.Opaque: nodeId = new NodeId(Nonce.CreateRandomNonceData(32)); break; + case Opc.Ua.IdType.Opaque: nodeId = new NodeId(Ua.Nonce.CreateRandomNonceData(32)); break; } NodeId nodeIdClone = (NodeId)nodeId.Clone(); Assert.AreEqual(nodeId, nodeIdClone); @@ -643,7 +643,7 @@ public void NodeIdComparison(Opc.Ua.IdType idType) case Opc.Ua.IdType.Numeric: nodeId2 = new NodeId(DataGenerator.GetRandomUInt16(), DataGenerator.GetRandomByte()); break; case Opc.Ua.IdType.String: nodeId2 = new NodeId(DataGenerator.GetRandomString(), 0); break; case Opc.Ua.IdType.Guid: nodeId2 = new NodeId(DataGenerator.GetRandomGuid()); break; - case Opc.Ua.IdType.Opaque: nodeId2 = new NodeId(Nonce.CreateRandomNonceData(32)); break; + case Opc.Ua.IdType.Opaque: nodeId2 = new NodeId(Ua.Nonce.CreateRandomNonceData(32)); break; } dictionary.Add(nodeId2, "TestClone"); Assert.AreEqual(2, dictionary.Distinct().ToList().Count); diff --git a/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs new file mode 100644 index 000000000..9830e7613 --- /dev/null +++ b/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs @@ -0,0 +1,163 @@ +/* ======================================================================== + * 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/ + * ======================================================================*/ + +using System; +using System.Security.Cryptography; +using NUnit.Framework; + +namespace Opc.Ua.Core.Tests.Types.Nonce +{ + /// + /// Tests for the Binary Schema Validator class. + /// + [TestFixture, Category("NonceTests")] + [Parallelizable] + public class NonceTests + { + #region Test Methods + /// + /// Test the CreateNonce - securitypolicy and valid nonceLength + /// + [Theory] + [TestCase(SecurityPolicies.ECC_nistP256)] + [TestCase(SecurityPolicies.ECC_nistP384)] + [TestCase(SecurityPolicies.ECC_brainpoolP256r1)] + [TestCase(SecurityPolicies.ECC_brainpoolP384r1)] + [TestCase(SecurityPolicies.Basic256)] + [TestCase(SecurityPolicies.Basic256Sha256)] + [TestCase(SecurityPolicies.Aes128_Sha256_RsaOaep)] + [TestCase(SecurityPolicies.Aes256_Sha256_RsaPss)] + public void ValidateCreateNoncePolicyLength(string securityPolicyUri) + { + if (IsSupportedByPlatform(securityPolicyUri)) + { + uint nonceLength = Ua.Nonce.GetNonceLength(securityPolicyUri); + + Ua.Nonce nonce = Ua.Nonce.CreateNonce(securityPolicyUri); + + Assert.IsNotNull(nonce); + Assert.IsNotNull(nonce.Data); + Assert.AreEqual(nonceLength, nonce.Data.Length); + } + } + + /// + /// Test the CreateEccNonce - securitypolicy and nonceData + /// + [Theory] + [TestCase(SecurityPolicies.ECC_nistP256)] + [TestCase(SecurityPolicies.ECC_nistP384)] + [TestCase(SecurityPolicies.ECC_brainpoolP256r1)] + [TestCase(SecurityPolicies.ECC_brainpoolP384r1)] + [TestCase(SecurityPolicies.Basic256)] + [TestCase(SecurityPolicies.Basic256Sha256)] + [TestCase(SecurityPolicies.Aes128_Sha256_RsaOaep)] + [TestCase(SecurityPolicies.Aes256_Sha256_RsaPss)] + public void ValidateCreateNoncePolicyNonceData(string securityPolicyUri) + { + if (IsSupportedByPlatform(securityPolicyUri)) + { + uint nonceLength = Ua.Nonce.GetNonceLength(securityPolicyUri); + Ua.Nonce nonceByLen = Ua.Nonce.CreateNonce(securityPolicyUri); + + Ua.Nonce nonceByData = Ua.Nonce.CreateNonce(securityPolicyUri, nonceByLen.Data); + + Assert.IsNotNull(nonceByData); + Assert.IsNotNull(nonceByData.Data); + Assert.AreEqual(nonceLength, nonceByData.Data.Length); + Assert.AreEqual(nonceByData.Data, nonceByLen.Data); + } + } + + /// + /// Test the CreateEccNonce - securitypolicy and invalid nonceData + /// + [Theory] + [TestCase(SecurityPolicies.ECC_nistP256)] + [TestCase(SecurityPolicies.ECC_nistP384)] + [TestCase(SecurityPolicies.ECC_brainpoolP256r1)] + [TestCase(SecurityPolicies.ECC_brainpoolP384r1)] + [TestCase(SecurityPolicies.Basic256)] + [TestCase(SecurityPolicies.Basic256Sha256)] + [TestCase(SecurityPolicies.Aes128_Sha256_RsaOaep)] + [TestCase(SecurityPolicies.Aes256_Sha256_RsaPss)] + public void ValidateCreateEccNoncePolicyInvalidNonceDataCorrectLength(string securityPolicyUri) + { + if (IsSupportedByPlatform(securityPolicyUri)) + { + uint nonceLength = Ua.Nonce.GetNonceLength(securityPolicyUri); + + byte[] randomValue = Ua.Nonce.CreateRandomNonceData(nonceLength); + + if (securityPolicyUri.Contains("ECC_")) + { + Assert.Throws(typeof(PlatformNotSupportedException), () => Ua.Nonce.CreateNonce(securityPolicyUri, randomValue)); + } + else + { + Ua.Nonce rsaNonce = Ua.Nonce.CreateNonce(securityPolicyUri, randomValue); + Assert.AreEqual(rsaNonce.Data, randomValue); + } + } + } + #endregion + + #region Helper + + /// + /// Determines if security policy is supported by platform + /// + /// + /// + private bool IsSupportedByPlatform(string securityPolicyUri) + { + if (securityPolicyUri.Equals(SecurityPolicies.ECC_nistP256, StringComparison.Ordinal)) + { + return Utils.IsSupportedCertificateType(ObjectTypeIds.EccNistP256ApplicationCertificateType); + } + else if (securityPolicyUri.Equals(SecurityPolicies.ECC_nistP384, StringComparison.Ordinal)) + { + return Utils.IsSupportedCertificateType(ObjectTypeIds.EccNistP384ApplicationCertificateType); + } + else if (securityPolicyUri.Equals(SecurityPolicies.ECC_brainpoolP256r1, StringComparison.Ordinal)) + { + return Utils.IsSupportedCertificateType(ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType); + } + else if (securityPolicyUri.Equals(SecurityPolicies.ECC_brainpoolP384r1, StringComparison.Ordinal)) + { + return Utils.IsSupportedCertificateType(ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType); + } + + return true; + } + + #endregion + } + +} From 55c97b08296d2685ad7f73c20a16a087762e421f Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 4 Apr 2024 11:37:47 +0300 Subject: [PATCH 47/80] Removed #if ECC_SUPPORT conditional compilation statements related to Nonce usage --- Libraries/Opc.Ua.Server/Session/Session.cs | 81 +--------- .../Opc.Ua.Server/Session/SessionManager.cs | 82 +--------- .../Stack/Types/UserIdentityToken.cs | 153 +----------------- 3 files changed, 15 insertions(+), 301 deletions(-) diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs index 24c6d866f..3323a7a39 100644 --- a/Libraries/Opc.Ua.Server/Session/Session.cs +++ b/Libraries/Opc.Ua.Server/Session/Session.cs @@ -40,7 +40,6 @@ public class Session : IDisposable { #region Constructors -#if ECC_SUPPORT /// /// Initializes a new instance of the class. /// @@ -78,43 +77,6 @@ public Session( double maxRequestAge, int maxBrowseContinuationPoints, int maxHistoryContinuationPoints) -#else - /// - /// Initializes a new instance of the class. - /// - /// The context. - /// The Server object. - /// The server certificate. - /// The unique private identifier assigned to the Session. - /// The client nonce. - /// The server nonce. - /// The name assigned to the Session. - /// Application description for the client application. - /// The endpoint URL. - /// The client certificate. - /// The session timeout. - /// The maximum size of a response message - /// The max request age. - /// The maximum number of browse continuation points. - /// The maximum number of history continuation points. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] - public Session( - OperationContext context, - IServerInternal server, - X509Certificate2 serverCertificate, - NodeId authenticationToken, - byte[] clientNonce, - byte[] serverNonce, - string sessionName, - ApplicationDescription clientDescription, - string endpointUrl, - X509Certificate2 clientCertificate, - double sessionTimeout, - uint maxResponseMessageSize, - double maxRequestAge, - int maxBrowseContinuationPoints, - int maxHistoryContinuationPoints) -#endif { if (context == null) throw new ArgumentNullException(nameof(context)); if (server == null) throw new ArgumentNullException(nameof(server)); @@ -133,9 +95,7 @@ public Session( m_serverCertificate = serverCertificate; m_clientCertificate = clientCertificate; -#if ECC_SUPPORT m_clientIssuerCertificates = clientCertificateChain; -#endif m_secureChannelId = context.ChannelContext.SecureChannelId; m_maxResponseMessageSize = maxResponseMessageSize; @@ -400,7 +360,6 @@ public bool Activated } } -#if ECC_SUPPORT public virtual void SetEccUserTokenSecurityPolicy(string securityPolicyUri) { lock (m_lock) @@ -430,7 +389,6 @@ public virtual EphemeralKeyType GetNewEccKey() return key; } } -#endif /// /// Returns the session's endpoint @@ -591,11 +549,7 @@ public void ValidateBeforeActivate( throw new ServiceResultException(StatusCodes.BadApplicationSignatureInvalid); } -#if ECC_SUPPORT byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce.Data); -#else - byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce); -#endif if (!SecurityPolicies.Verify(m_clientCertificate, m_endpoint.SecurityPolicyUri, dataToSign, clientSignature)) { @@ -613,11 +567,8 @@ public void ValidateBeforeActivate( } byte[] serverCertificateChainData = serverCertificateChainList.ToArray(); -#if ECC_SUPPORT + dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce.Data); -#else - dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce); -#endif if (!SecurityPolicies.Verify(m_clientCertificate, m_endpoint.SecurityPolicyUri, dataToSign, clientSignature)) { @@ -658,7 +609,6 @@ public void ValidateBeforeActivate( /// /// Activates the session and binds it to the current secure channel. /// -#if ECC_SUPPORT public bool Activate( OperationContext context, List clientSoftwareCertificates, @@ -667,16 +617,6 @@ public bool Activate( IUserIdentity effectiveIdentity, StringCollection localeIds, Nonce serverNonce) -#else - public bool Activate( - OperationContext context, - List clientSoftwareCertificates, - UserIdentityToken identityToken, - IUserIdentity identity, - IUserIdentity effectiveIdentity, - StringCollection localeIds, - byte[] serverNonce) -#endif { lock (m_lock) { @@ -1076,17 +1016,12 @@ private UserIdentityToken ValidateUserIdentityToken( try { -#if ECC_SUPPORT token.Decrypt(m_serverCertificate, m_serverNonce, securityPolicyUri, m_eccUserTokenNonce, m_clientCertificate, m_clientIssuerCertificates); -#else - - token.Decrypt(m_serverCertificate, m_serverNonce, securityPolicyUri); -#endif } catch (Exception e) { @@ -1101,11 +1036,8 @@ private UserIdentityToken ValidateUserIdentityToken( // verify the signature. if (securityPolicyUri != SecurityPolicies.None) { -#if ECC_SUPPORT + byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce.Data); -#else - byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce); -#endif if (!token.Verify(dataToSign, userTokenSignature, securityPolicyUri)) { @@ -1124,11 +1056,8 @@ private UserIdentityToken ValidateUserIdentityToken( byte[] serverCertificateChainData = serverCertificateChainList.ToArray(); -#if ECC_SUPPORT dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce.Data); -#else - dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce); -#endif + if (!token.Verify(dataToSign, userTokenSignature, securityPolicyUri)) { throw new ServiceResultException(StatusCodes.BadIdentityTokenRejected, "Invalid user signature!"); @@ -1279,14 +1208,10 @@ private void UpdateDiagnosticCounters(RequestType requestType, bool error, bool private X509Certificate2 m_serverCertificate; private byte[] m_serverCertificateChain; -#if ECC_SUPPORT private Nonce m_serverNonce; private string m_eccUserTokenSecurityPolicyUri; private Nonce m_eccUserTokenNonce; private X509Certificate2Collection m_clientIssuerCertificates; -#else - private byte[] m_serverNonce; -#endif private string[] m_localeIds; [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] diff --git a/Libraries/Opc.Ua.Server/Session/SessionManager.cs b/Libraries/Opc.Ua.Server/Session/SessionManager.cs index 3f3326033..fdaffb772 100644 --- a/Libraries/Opc.Ua.Server/Session/SessionManager.cs +++ b/Libraries/Opc.Ua.Server/Session/SessionManager.cs @@ -215,11 +215,8 @@ public virtual Session CreateSession( } // create server nonce. -#if ECC_SUPPORT var serverNonceObject = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri); -#else - serverNonce = Nonce.CreateRandomNonceData((uint)m_minNonceLength); -#endif + // assign client name. if (String.IsNullOrEmpty(sessionName)) @@ -228,7 +225,6 @@ public virtual Session CreateSession( } // create instance of session. -#if ECC_SUPPORT session = CreateSession( context, m_server, @@ -243,27 +239,10 @@ public virtual Session CreateSession( clientCertificateChain, revisedSessionTimeout, maxResponseMessageSize); -#else - session = CreateSession( - context, - m_server, - serverCertificate, - authenticationToken, - clientNonce, - serverNonce, - sessionName, - clientDescription, - endpointUrl, - clientCertificate, - revisedSessionTimeout, - maxResponseMessageSize); -#endif // get the session id. sessionId = session.Id; -#if ECC_SUPPORT serverNonce = serverNonceObject.Data; -#endif // save session. m_sessions.Add(authenticationToken, session); @@ -326,9 +305,8 @@ public virtual bool ActivateSession( out byte[] serverNonce) { serverNonce = null; -#if ECC_SUPPORT + Nonce serverNonceObject = null; -#endif Session session = null; UserIdentityToken newIdentity = null; @@ -354,11 +332,7 @@ public virtual bool ActivateSession( } // create new server nonce. -#if ECC_SUPPORT serverNonceObject = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri); -#else - serverNonce = Nonce.CreateNonce(context.ChannelContext.EndpointDescription.SecurityPolicyUri).Data; -#endif // validate before activation. session.ValidateBeforeActivate( @@ -369,9 +343,8 @@ public virtual bool ActivateSession( userTokenSignature, out newIdentity, out userTokenPolicy); -#if ECC_SUPPORT + serverNonce = serverNonceObject.Data; -#endif } IUserIdentity identity = null; @@ -433,7 +406,7 @@ public virtual bool ActivateSession( } // activate session. -#if ECC_SUPPORT + bool contextChanged = session.Activate( context, clientSoftwareCertificates, @@ -442,16 +415,6 @@ public virtual bool ActivateSession( effectiveIdentity, localeIds, serverNonceObject); -#else - bool contextChanged = session.Activate( - context, - clientSoftwareCertificates, - newIdentity, - identity, - effectiveIdentity, - localeIds, - serverNonce); -#endif // raise session related event. if (contextChanged) @@ -580,7 +543,6 @@ public virtual OperationContext ValidateRequest(RequestHeader requestHeader, Req /// /// Creates a new instance of a session. /// -#if ECC_SUPPORT protected virtual Session CreateSession( OperationContext context, IServerInternal server, @@ -616,42 +578,6 @@ protected virtual Session CreateSession( return session; } -#else - protected virtual Session CreateSession( - OperationContext context, - IServerInternal server, - X509Certificate2 serverCertificate, - NodeId sessionCookie, - byte[] clientNonce, - byte[] serverNonce, - string sessionName, - ApplicationDescription clientDescription, - string endpointUrl, - X509Certificate2 clientCertificate, - double sessionTimeout, - uint maxResponseMessageSize) - - { - Session session = new Session( - context, - m_server, - serverCertificate, - sessionCookie, - clientNonce, - serverNonce, - sessionName, - clientDescription, - endpointUrl, - clientCertificate, - sessionTimeout, - maxResponseMessageSize, - m_maxRequestAge, - m_maxBrowseContinuationPoints, - m_maxHistoryContinuationPoints); - - return session; - } -#endif /// /// Raises an event related to a session. diff --git a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs index 531e2d2da..26e086197 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs @@ -26,9 +26,7 @@ public partial class UserIdentityToken /// /// Encrypts the token (implemented by the subclass). /// -#if ECC_SUPPORT [Obsolete("Use Encrypt(X509Certificate2, byte[], string securityPolicyUri, Nonce, X509Certificate2, X509Certificate2Collection, bool) ")] -#endif public virtual void Encrypt(X509Certificate2 certificate, byte[] receiverNonce, string securityPolicyUri) { } @@ -36,15 +34,11 @@ public virtual void Encrypt(X509Certificate2 certificate, byte[] receiverNonce, /// /// Decrypts the token (implemented by the subclass). /// -#if ECC_SUPPORT [Obsolete("Use Decrypt(X509Certificate2, Nonce, string, Nonce, X509Certificate2, X509Certificate2Collection, CertificateValidator) ")] -#endif public virtual void Decrypt(X509Certificate2 certificate, byte[] receiverNonce, string securityPolicyUri) { } - -#if ECC_SUPPORT /// /// Encrypts the token (implemented by the subclass). /// @@ -72,7 +66,6 @@ public virtual void Decrypt( CertificateValidator validator = null) { } -#endif /// /// Creates a signature with the token (implemented by the subclass). @@ -112,96 +105,24 @@ public string DecryptedPassword /// /// Encrypts the DecryptedPassword using the EncryptionAlgorithm and places the result in Password /// -#if ECC_SUPPORT - [Obsolete] -#endif + [Obsolete("Use Encrypt(X509Certificate2, byte[], string securityPolicyUri, Nonce, X509Certificate2, X509Certificate2Collection, bool)")] public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { - if (m_decryptedPassword == null) - { - m_password = null; - return; - } - - // handle no encryption. - if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None) - { - m_password = Encoding.UTF8.GetBytes(m_decryptedPassword); - m_encryptionAlgorithm = null; - return; - } - - // encrypt the password. - byte[] dataToEncrypt = Utils.Append(Encoding.UTF8.GetBytes(m_decryptedPassword), senderNonce); - - EncryptedData encryptedData = SecurityPolicies.Encrypt( - certificate, - securityPolicyUri, - dataToEncrypt); - - m_password = encryptedData.Data; - m_encryptionAlgorithm = encryptedData.Algorithm; + Encrypt(certificate, senderNonce, securityPolicyUri, null); } /// /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword /// -#if ECC_SUPPORT - [Obsolete] -#endif + [Obsolete("Use Decrypt(X509Certificate2, Nonce, string, Nonce, X509Certificate2, X509Certificate2Collection, CertificateValidator) ")] public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { - // handle no encryption. - if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None) - { - m_decryptedPassword = Encoding.UTF8.GetString(m_password, 0, m_password.Length); - return; - } - - // decrypt. - EncryptedData encryptedData = new EncryptedData(); - encryptedData.Data = m_password; - encryptedData.Algorithm = m_encryptionAlgorithm; - - byte[] decryptedPassword = SecurityPolicies.Decrypt( - certificate, - securityPolicyUri, - encryptedData); - - if (decryptedPassword == null) - { - m_decryptedPassword = null; - return; - } - - // verify the sender's nonce. - int startOfNonce = decryptedPassword.Length; - - if (senderNonce != null) - { - startOfNonce -= senderNonce.Length; - - int result = 0; - for (int ii = 0; ii < senderNonce.Length; ii++) - { - result |= senderNonce[ii] ^ decryptedPassword[ii + startOfNonce]; - } - - if (result != 0) - { - throw new ServiceResultException(StatusCodes.BadIdentityTokenRejected); - } - } - - // convert to UTF-8. - m_decryptedPassword = Encoding.UTF8.GetString(decryptedPassword, 0, startOfNonce); + Decrypt(certificate, Nonce.CreateNonce(securityPolicyUri, senderNonce), securityPolicyUri); } /// /// Encrypts the DecryptedPassword using the EncryptionAlgorithm and places the result in Password /// - /// -#if ECC_SUPPORT public override void Encrypt( X509Certificate2 receiverCertificate, byte[] receiverNonce, @@ -349,7 +270,6 @@ public override void Decrypt( m_decryptedPassword = new UTF8Encoding().GetString(plainText); } } -#endif #endregion @@ -492,77 +412,21 @@ public byte[] DecryptedTokenData /// /// Encrypts the DecryptedTokenData using the EncryptionAlgorithm and places the result in Password /// -#if ECC_SUPPORT - [Obsolete] -#endif + [Obsolete("Use Encrypt(X509Certificate2, byte[], string securityPolicyUri, Nonce, X509Certificate2, X509Certificate2Collection, bool)")] public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { - // handle no encryption. - if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None) - { - m_tokenData = m_decryptedTokenData; - m_encryptionAlgorithm = String.Empty; - return; - } - - byte[] dataToEncrypt = Utils.Append(m_decryptedTokenData, senderNonce); - - EncryptedData encryptedData = SecurityPolicies.Encrypt( - certificate, - securityPolicyUri, - dataToEncrypt); - - m_tokenData = encryptedData.Data; - m_encryptionAlgorithm = encryptedData.Algorithm; + Encrypt(certificate, senderNonce, securityPolicyUri, null); } /// /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword /// -#if ECC_SUPPORT - [Obsolete] -#endif + [Obsolete("Use Decrypt(X509Certificate2, Nonce, string, Nonce, X509Certificate2, X509Certificate2Collection, CertificateValidator) ")] public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { - // handle no encryption. - if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None) - { - m_decryptedTokenData = m_tokenData; - return; - } - - EncryptedData encryptedData = new EncryptedData(); - - encryptedData.Data = m_tokenData; - encryptedData.Algorithm = m_encryptionAlgorithm; - - byte[] decryptedTokenData = SecurityPolicies.Decrypt( - certificate, - securityPolicyUri, - encryptedData); - - // verify the sender's nonce. - int startOfNonce = decryptedTokenData.Length; - - if (senderNonce != null) - { - startOfNonce -= senderNonce.Length; - - for (int ii = 0; ii < senderNonce.Length; ii++) - { - if (senderNonce[ii] != decryptedTokenData[ii + startOfNonce]) - { - throw new ServiceResultException(StatusCodes.BadIdentityTokenRejected); - } - } - } - - // copy results. - m_decryptedTokenData = new byte[startOfNonce]; - Array.Copy(decryptedTokenData, m_decryptedTokenData, startOfNonce); + Decrypt(certificate, Nonce.CreateNonce(securityPolicyUri, senderNonce), securityPolicyUri); } -#if ECC_SUPPORT /// /// Encrypts the DecryptedTokenData using the EncryptionAlgorithm and places the result in Password /// @@ -643,7 +507,6 @@ public override void Decrypt( m_decryptedTokenData = new byte[startOfNonce]; Array.Copy(decryptedTokenData, m_decryptedTokenData, startOfNonce); } -#endif /// /// Creates a signature with the token. From 5760ac7802edea8a6c033e5441e6e19581902158 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 19 Apr 2024 15:55:44 +0300 Subject: [PATCH 48/80] Move async code out of the constructor --- Libraries/Opc.Ua.Client/Session.cs | 84 ++++++++++++++++-------------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index e85ffde34..c62d23e83 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -99,7 +99,8 @@ public Session( : base(channel) { - InitializeAsync(channel, configuration, endpoint, clientCertificate); + Initialize(channel, configuration, endpoint); + LoadInstanceCertificateAsync(clientCertificate).GetAwaiter().GetResult(); m_discoveryServerEndpoints = availableEndpoints; m_discoveryProfileUris = discoveryProfileUris; } @@ -114,8 +115,8 @@ public Session(ITransportChannel channel, Session template, bool copyEventHandle : base(channel) { - InitializeAsync(channel, template.m_configuration, template.ConfiguredEndpoint, template.m_instanceCertificate); - + Initialize(channel, template.m_configuration, template.ConfiguredEndpoint); + LoadInstanceCertificateAsync(template.m_instanceCertificate).GetAwaiter().GetResult(); m_sessionFactory = template.m_sessionFactory; m_defaultSubscription = template.m_defaultSubscription; m_deleteSubscriptionsOnClose = template.m_deleteSubscriptionsOnClose; @@ -156,11 +157,10 @@ public Session(ITransportChannel channel, Session template, bool copyEventHandle /// /// Initializes the channel. /// - private async void InitializeAsync( + private void Initialize( ITransportChannel channel, ApplicationConfiguration configuration, - ConfiguredEndpoint endpoint, - X509Certificate2 clientCertificate) + ConfiguredEndpoint endpoint) { Initialize(); @@ -173,38 +173,6 @@ private async void InitializeAsync( // update the default subscription. m_defaultSubscription.MinLifetimeInterval = (uint)configuration.ClientConfiguration.MinSubscriptionLifetime; - if (m_endpoint.Description.SecurityPolicyUri != SecurityPolicies.None) - { - if (clientCertificate == null) - { - m_instanceCertificate = await LoadCertificateAsync(configuration, m_endpoint.Description.SecurityPolicyUri).ConfigureAwait(false); - if (m_instanceCertificate == null) - { - throw new ServiceResultException( - StatusCodes.BadConfigurationError, - "The client configuration does not specify an application instance certificate."); - } - } - else - { - // update client certificate. - m_instanceCertificate = clientCertificate; - } - - // check for private key. - if (!m_instanceCertificate.HasPrivateKey) - { - throw ServiceResultException.Create( - StatusCodes.BadConfigurationError, - "No private key for the application instance certificate. Subject={0}, Thumbprint={1}.", - m_instanceCertificate.Subject, - m_instanceCertificate.Thumbprint); - } - - // load certificate chain. - m_instanceCertificateChain = await LoadCertificateChainAsync(configuration, m_instanceCertificate).ConfigureAwait(false); - } - // initialize the message context. IServiceMessageContext messageContext = channel.MessageContext; @@ -6109,6 +6077,46 @@ private void DeleteSubscription(uint subscriptionId) } } + /// + /// Asynchronously load instance certificate + /// + /// + /// + private async Task LoadInstanceCertificateAsync(X509Certificate2 clientCertificate) + { + if (m_endpoint.Description.SecurityPolicyUri != SecurityPolicies.None) + { + if (clientCertificate == null) + { + m_instanceCertificate = await LoadCertificateAsync(m_configuration, m_endpoint.Description.SecurityPolicyUri).ConfigureAwait(false); + if (m_instanceCertificate == null) + { + throw new ServiceResultException( + StatusCodes.BadConfigurationError, + "The client configuration does not specify an application instance certificate."); + } + } + else + { + // update client certificate. + m_instanceCertificate = clientCertificate; + } + + // check for private key. + if (!m_instanceCertificate.HasPrivateKey) + { + throw ServiceResultException.Create( + StatusCodes.BadConfigurationError, + "No private key for the application instance certificate. Subject={0}, Thumbprint={1}.", + m_instanceCertificate.Subject, + m_instanceCertificate.Thumbprint); + } + + // load certificate chain. + m_instanceCertificateChain = await LoadCertificateChainAsync(m_configuration, m_instanceCertificate).ConfigureAwait(false); + } + } + /// /// Load certificate for connection. /// From a3d146953ca224009ea46dc07b507d6c85063896 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 17 May 2024 10:35:56 +0300 Subject: [PATCH 49/80] Implemented SetECDsaPublicKey, ReentrantSemaphoreSlim removed --- .../Opc.Ua.Gds.Server.Common.csproj | 2 +- .../Opc.Ua.Security.Certificates.csproj | 8 +- .../Org.BouncyCastle/PEMWriter.cs | 2 +- .../Org.BouncyCastle/X509Utils.cs | 86 +++++++++++++++++-- .../X509Certificate/CertificateBuilder.cs | 75 ++++++++++++---- .../Configuration/ConfigurationNodeManager.cs | 1 - .../Certificates/CertificateValidator.cs | 2 +- .../Certificates/ReentrantSemaphoreSlim.cs | 83 ------------------ .../CertificateTestsForECDsa.cs | 57 +++++++++++- 9 files changed, 200 insertions(+), 116 deletions(-) delete mode 100644 Stack/Opc.Ua.Core/Security/Certificates/ReentrantSemaphoreSlim.cs diff --git a/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj b/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj index 45788da9b..cc26dafd3 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj +++ b/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj @@ -26,7 +26,7 @@ - + diff --git a/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj b/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj index ce5446355..8892e02dc 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj +++ b/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj @@ -18,12 +18,12 @@ - + - + @@ -49,11 +49,11 @@ - + - + diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMWriter.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMWriter.cs index 58facaa4d..5a1e7189d 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMWriter.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/PEMWriter.cs @@ -74,7 +74,7 @@ public static byte[] ExportPrivateKeyAsPEM( return EncodeAsPEM(serializedPrivateBytes, "PRIVATE KEY"); } #else - return null; // Only on NETSTANDARD2_0 + throw new ArgumentException("ExportPrivateKeyAsPEM not supported on this platform."); // Only on NETSTANDARD2_0 #endif } #endregion diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs index d9c852fdc..0c518e0f2 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509Utils.cs @@ -164,7 +164,7 @@ internal static RsaPrivateCrtKeyParameters GetRsaPrivateKeyParameter(RSA rsa) #if NET472_OR_GREATER /// - /// Get private key parameters from a ECDsa private key. + /// Get BouncyCastle format private key parameters from a System.Security.Cryptography.ECDsa. /// The private key must be exportable. /// internal static ECPrivateKeyParameters GetECPrivateKeyParameter(ECDsa ec) @@ -172,11 +172,46 @@ internal static ECPrivateKeyParameters GetECPrivateKeyParameter(ECDsa ec) ECParameters ecParams = ec.ExportParameters(true); BigInteger d = new BigInteger(1, ecParams.D); - X9ECParameters curve = null; + X9ECParameters curve = GetX9ECParameters(ecParams); + + if (curve == null) throw new ArgumentException("Curve OID is not recognized ", ecParams.Curve.Oid.ToString()); + ECDomainParameters domainParameters = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H); + return new ECPrivateKeyParameters(d, domainParameters); + + } + + /// + /// Get BouncyCastle format public key parameters from a System.Security.Cryptography.ECDsa + /// + internal static ECPublicKeyParameters GetECPublicKeyParameters(ECDsa ec) + { + ECParameters ecParams = ec.ExportParameters(false); + + X9ECParameters curve = GetX9ECParameters(ecParams); + + if (curve == null) throw new ArgumentException("Curve OID is not recognized ", ecParams.Curve.Oid.ToString()); + + var q = curve.Curve.CreatePoint( + new BigInteger(1, ecParams.Q.X), + new BigInteger(1, ecParams.Q.Y)); + + ECDomainParameters domainParameters = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed()); + + return new ECPublicKeyParameters(q, domainParameters); + + } + + /// + /// Return Bouncy Castle X9ECParameters value equivalent of System.Security.Cryptography.ECparameters + /// + /// + /// X9ECParameters value equivalent of System.Security.Cryptography.ECparameters if found else null + internal static X9ECParameters GetX9ECParameters(ECParameters ecParams) + { if (!string.IsNullOrEmpty(ecParams.Curve.Oid.Value)) { var oid = new DerObjectIdentifier(ecParams.Curve.Oid.Value); - curve = ECNamedCurveTable.GetByOid(oid); + return ECNamedCurveTable.GetByOid(oid); } else if (!string.IsNullOrEmpty(ecParams.Curve.Oid.FriendlyName)) { @@ -195,12 +230,49 @@ internal static ECPrivateKeyParameters GetECPrivateKeyParameter(ECDsa ec) return lastChar + "-" + number; }); } - curve = ECNamedCurveTable.GetByName(bcFriendlyName); + return ECNamedCurveTable.GetByName(bcFriendlyName); } - if (curve == null) throw new ArgumentException("Curve OID is not recognized ", ecParams.Curve.Oid.ToString()); - ECDomainParameters domainParameters = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H); - return new ECPrivateKeyParameters(d, domainParameters); + return null; + } + + /// + /// Identifies a named curve by the provided coeficients A and B from their first 4 bytes + /// + /// + /// + /// The successfully identified named System.Security.Cryptography.ECCurve curve + /// or throws if no curve is identified + internal static ECCurve IdentifyEccCurveByCoefficients(byte[] a, byte[] b) + { + byte[] brainpoolP256AStart = new byte[] { 0x7D, 0x5A, 0x09, 0x75 }; + byte[] brainpoolP256BStart = new byte[] { 0x26, 0xDC, 0x5C, 0x6C }; + byte[] brainpoolP384AStart = new byte[] { 0x7B, 0xC3, 0x82, 0xC6 }; + byte[] brainpoolP384BStart = new byte[] { 0x04, 0xA8, 0xC7, 0xDD }; + byte[] nistP256AStart = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }; + byte[] nistP256BStart = new byte[] { 0x5A, 0xC6, 0x35, 0xD8 }; + byte[] nistP384AStart = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }; + byte[] nistP384BStart = new byte[] { 0xB3, 0x31, 0x2F, 0xA7 }; + + + if (a.Take(4).SequenceEqual(brainpoolP256AStart) && b.Take(4).SequenceEqual(brainpoolP256BStart)) + { + return ECCurve.NamedCurves.brainpoolP256r1; + } + else if (a.Take(4).SequenceEqual(brainpoolP384AStart) && b.Take(4).SequenceEqual(brainpoolP384BStart)) + { + return ECCurve.NamedCurves.brainpoolP384r1; + } + else if (a.Take(4).SequenceEqual(nistP256AStart) && b.Take(4).SequenceEqual(nistP256BStart)) + { + return ECCurve.NamedCurves.nistP256; + } + else if (a.Take(4).SequenceEqual(nistP384AStart) && b.Take(4).SequenceEqual(nistP384BStart)) + { + return ECCurve.NamedCurves.nistP384; + } + + throw new ArgumentException("EccCurveByCoefficients cannot be identified"); } #endif diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs index b0fca59a2..75c67e8f2 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/CertificateBuilder.cs @@ -30,10 +30,20 @@ #if NETSTANDARD2_1 || NET472_OR_GREATER || NET5_0_OR_GREATER using System; +using System.Collections.Generic; using System.Linq; +using System.Numerics; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; + +#if NET472_OR_GREATER +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Math.EC; +#endif + namespace Opc.Ua.Security.Certificates { /// @@ -188,7 +198,7 @@ public override X509Certificate2 CreateForECDsa() ECDsa publicKey = m_ecdsaPublicKey; if (publicKey == null) { - key = ECDsa.Create((ECCurve)m_curve); + key = ECDsa.Create((System.Security.Cryptography.ECCurve)m_curve); publicKey = key; } @@ -242,7 +252,7 @@ public override X509Certificate2 CreateForECDsa(X509SignatureGenerator generator ECDsa publicKey = m_ecdsaPublicKey; if (publicKey == null) { - key = ECDsa.Create((ECCurve)m_curve); + key = ECDsa.Create((System.Security.Cryptography.ECCurve)m_curve); publicKey = key; } @@ -266,25 +276,57 @@ public override X509Certificate2 CreateForECDsa(X509SignatureGenerator generator public override ICertificateBuilderCreateForECDsaAny SetECDsaPublicKey(byte[] publicKey) { if (publicKey == null) throw new ArgumentNullException(nameof(publicKey)); -#if NET472_OR_GREATER - throw new NotSupportedException("Import a ECDsaPublicKey is not supported on this platform."); -#else + int bytes = 0; try { m_ecdsaPublicKey = ECDsa.Create(); #if NET472_OR_GREATER - var asymmetricKeyParameter = Org.BouncyCastle.Security.PublicKeyFactory.CreateKey(publicKey); - var ecKeyParameters = asymmetricKeyParameter as Org.BouncyCastle.Crypto.Parameters.ECKeyParameters; - var parameters = new ECParameters { - Curve = default(ECCurve), - Q = new ECPoint() { - X = ecKeyParameters.Parameters.G.XCoord.ToBigInteger().ToByteArrayUnsigned(), - Y = ecKeyParameters.Parameters.G.YCoord.ToBigInteger().ToByteArrayUnsigned(), - } + + var asymmetricPubKeyParameters = Org.BouncyCastle.Security.PublicKeyFactory.CreateKey(publicKey) as Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters; + if (asymmetricPubKeyParameters == null) + { + throw new ArgumentException("Invalid public key format or key type."); + } + + var asn1Obj = Asn1Object.FromByteArray(publicKey); + var publicKeyInfo = SubjectPublicKeyInfo.GetInstance(asn1Obj); + var algParams = publicKeyInfo.Algorithm.Parameters; + var x962Params = X962Parameters.GetInstance(algParams); + var ecPoint = publicKeyInfo.PublicKey.GetBytes(); + + ECParameters ecParameters = new ECParameters(); + X9ECParameters ecParametersAsn1 = null; + + if (x962Params.IsNamedCurve) + { + // Named + var namedCurveOid = (DerObjectIdentifier)x962Params.Parameters; + string curveName = namedCurveOid.Id; + + var ecCurve = System.Security.Cryptography.ECCurve.CreateFromOid(new Oid(curveName)); + ecParameters.Curve = ecCurve; + } + else + { + // Explicit but still need to create the curve as named since the platform does not support it's creation + // otherwise + ecParametersAsn1 = X9ECParameters.GetInstance(x962Params.Parameters); + ecParameters.Curve = BouncyCastle.X509Utils.IdentifyEccCurveByCoefficients(ecParametersAsn1.Curve.A.GetEncoded(), + ecParametersAsn1.Curve.B.GetEncoded());// instead of ecParametersAsn1.Curve; + + } + + //Extract the public key coordinates + var publicKeyPoint = new X9ECPoint(ecParametersAsn1.Curve, ecPoint).Point; + ecParameters.Q = new System.Security.Cryptography.ECPoint { + X = publicKeyPoint.AffineXCoord.ToBigInteger().ToByteArrayUnsigned(), + Y = publicKeyPoint.AffineYCoord?.ToBigInteger().ToByteArrayUnsigned() }; - m_ecdsaPublicKey.ImportParameters(parameters); + + m_ecdsaPublicKey.ImportParameters(ecParameters); bytes = publicKey.Length; + #else m_ecdsaPublicKey.ImportSubjectPublicKeyInfo(publicKey, out bytes); #endif @@ -301,7 +343,6 @@ public override ICertificateBuilderCreateForECDsaAny SetECDsaPublicKey(byte[] pu return this; #endif } -#endif /// public override ICertificateBuilderCreateForRSAAny SetRSAPublicKey(byte[] publicKey) @@ -373,7 +414,7 @@ private void CreateX509Extensions(CertificateRequest request, bool forECDsa) // Authority Key Identifier if (X509Extensions.FindExtension(m_extensions) == null) { - X509Extension authorityKeyIdentifier = IssuerCAKeyCert != null + System.Security.Cryptography.X509Certificates.X509Extension authorityKeyIdentifier = IssuerCAKeyCert != null ? X509Extensions.BuildAuthorityKeyIdentifier(IssuerCAKeyCert) : new X509AuthorityKeyIdentifierExtension( ski.SubjectKeyIdentifier.FromHexString(), @@ -431,7 +472,7 @@ private void CreateX509Extensions(CertificateRequest request, bool forECDsa) } } - foreach (X509Extension extension in m_extensions) + foreach (System.Security.Cryptography.X509Certificates.X509Extension extension in m_extensions) { request.CertificateExtensions.Add(extension); } diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 48b7caee2..aaeb5cbc3 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -500,7 +500,6 @@ private ServiceResult UpdateCertificate( { try { - // TODO using (ICertificateStore appStore = existingCertIdentifier.OpenStore()) { Utils.LogCertificate(Utils.TraceMasks.Security, "Delete application certificate: ", existingCertIdentifier.Certificate); diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 3c5efb96e..70162859a 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -1908,7 +1908,7 @@ private enum ProtectFlags #endregion #region Private Fields - private readonly ReentrantSemaphoreSlim m_semaphore = new ReentrantSemaphoreSlim(1, 1); + private readonly SemaphoreSlim m_semaphore = new SemaphoreSlim(1, 1); private readonly object m_callbackLock = new object(); private readonly Dictionary m_validatedCertificates; private CertificateStoreIdentifier m_trustedCertificateStore; diff --git a/Stack/Opc.Ua.Core/Security/Certificates/ReentrantSemaphoreSlim.cs b/Stack/Opc.Ua.Core/Security/Certificates/ReentrantSemaphoreSlim.cs deleted file mode 100644 index 1cedd66ee..000000000 --- a/Stack/Opc.Ua.Core/Security/Certificates/ReentrantSemaphoreSlim.cs +++ /dev/null @@ -1,83 +0,0 @@ -/* Copyright (c) 1996-2022 The OPC Foundation. All rights reserved. - The source code in this file is covered under a dual-license scenario: - - RCL: for OPC Foundation Corporate Members in good-standing - - GPL V2: everybody else - RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/ - GNU General Public License as published by the Free Software Foundation; - version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2 - This source code is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -*/ - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Opc.Ua -{ - /// - /// Defines a reentrant SemaphoreSlim Lock. - /// - internal class ReentrantSemaphoreSlim - { - #region Constructors - public ReentrantSemaphoreSlim() - { - m_semaphore = new SemaphoreSlim(1, 1); - m_reentrantCounter = new ThreadLocal(() => 0); - } - - public ReentrantSemaphoreSlim(int initialCount, int maxCount) - { - m_semaphore = new SemaphoreSlim(initialCount, maxCount); - m_reentrantCounter = new ThreadLocal(() => 0); - } - #endregion - - #region Public Members - /// - /// Wait method for synchronous usage - /// - public void Wait() - { - if (m_reentrantCounter.Value == 0) - { - m_semaphore.Wait(); - } - m_reentrantCounter.Value++; - } - - /// - /// Wait method for async usage - /// - public async Task WaitAsync(CancellationToken ct = default) - { - if (m_reentrantCounter.Value == 0) - { - await m_semaphore.WaitAsync(ct).ConfigureAwait(false); - } - m_reentrantCounter.Value++; - } - - public void Release() - { - if (m_reentrantCounter.Value <= 0) - { - throw new InvalidOperationException("Release called without a corresponding Wait"); - } - if (--m_reentrantCounter.Value == 0) - { - m_semaphore.Release(); - } - } - - #endregion - - #region Private members - private readonly SemaphoreSlim m_semaphore; - private ThreadLocal m_reentrantCounter; - #endregion - - } -} diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs index a02b5b22c..48b5f5d6c 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs @@ -29,11 +29,15 @@ #if ECC_SUPPORT using System; +using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using NUnit.Framework; using Opc.Ua.Tests; +#if NETFRAMEWORK +using Org.BouncyCastle.X509; +#endif namespace Opc.Ua.Security.Certificates.Tests { @@ -346,6 +350,39 @@ ECCurveHashPair ecCurveHashPair } }); } + + [Theory] + public void SetECDsaPublicKeyByteArray( + ECCurveHashPair ecCurveHashPair + ) + { + // default signing cert with custom key + X509Certificate2 signingCert = CertificateBuilder.Create(Subject) + .SetCAConstraint() + .SetHashAlgorithm(HashAlgorithmName.SHA512) + .SetECCurve(ecCurveHashPair.Curve) + .CreateForECDsa(); + + WriteCertificate(signingCert, $"Signing ECDsa {signingCert.GetECDsaPublicKey().KeySize} cert"); + + + using (ECDsa ecdsaPrivateKey = signingCert.GetECDsaPrivateKey()) + using (ECDsa ecdsaPublicKey = signingCert.GetECDsaPublicKey()) + { + byte[] pubKeyBytes = GetPublicKey(ecdsaPublicKey); + + var generator = X509SignatureGenerator.CreateForECDsa(ecdsaPrivateKey); + var cert = CertificateBuilder.Create("CN=App Cert") + .SetHashAlgorithm(ecCurveHashPair.HashAlgorithmName) + .SetIssuer(new X509Certificate2(signingCert.RawData)) + .SetECDsaPublicKey(pubKeyBytes) + .CreateForECDsa(generator); + Assert.NotNull(cert); + WriteCertificate(cert, "Default signed ECDsa cert with Public Key"); + } + + } + #endregion #region Private Methods @@ -386,7 +423,25 @@ private void CheckPEMWriter(X509Certificate2 certificate, string password = null #endif } } - #endregion + + private static byte[] GetPublicKey(ECDsa ecdsa) + { + if (ecdsa is ECDsaCng cng) + { +#if NETFRAMEWORK + + var pubKeyParams = BouncyCastle.X509Utils.GetECPublicKeyParameters(ecdsa); + return SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKeyParams).ToAsn1Object().GetDerEncoded(); +#elif NETCOREAPP3_1 || NET5_0_OR_GREATER + return ecdsa.ExportSubjectPublicKeyInfo(); +#endif + } + else + { + throw new ArgumentException("Unsuported ECDsa format"); + } + } +#endregion #region Private Fields #endregion From c53858ea43ab86bb22befe32864097ab35d48294 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Wed, 22 May 2024 15:37:58 +0300 Subject: [PATCH 50/80] Added MaxChannelCount of 100 to ServerFixture --- Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs b/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs index 9f51b87cc..ba7892c73 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs @@ -53,6 +53,7 @@ public class ClientTestFramework new object [] { Utils.UriSchemeOpcHttps}, }; + public const int MaxChannelCount = 100; public const int MaxReferences = 100; public const int MaxTimeout = 10000; public const int TransportQuotaMaxMessageSize = 4 * 1024 * 1024; @@ -208,7 +209,8 @@ public async Task OneTimeSetUpAsync(TextWriter writer = null, bool securityNone PolicyId = Profiles.JwtUserToken, SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384" }); - + + ServerFixture.Config.ServerConfiguration.MaxChannelCount = MaxChannelCount; ReferenceServer = await ServerFixture.StartAsync(writer ?? TestContext.Out).ConfigureAwait(false); ReferenceServer.TokenValidator = this.TokenValidator; From 303c7b1b4af0ad5aa5b0c5bc7b42fdeeda342787 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Wed, 22 May 2024 16:37:33 +0300 Subject: [PATCH 51/80] Set version 1.5.375-ECC-preview --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 57a6f7362..2604d514e 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.5.375-preview", + "version": "1.5.375-ECC-preview", "versionHeightOffset": 20, "nugetPackageVersion": { "semVer": 2 From 2f4aef01cb2d21f3b9bdecf225eb033d282698f0 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 24 May 2024 15:57:38 +0300 Subject: [PATCH 52/80] Removed ECDsaCng validation for ECDsa (OS platform dependent) --- .../CertificateTestsForECDsa.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs index 7a5a8ac80..72939db30 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs @@ -428,20 +428,12 @@ private void CheckPEMWriter(X509Certificate2 certificate, string password = null private static byte[] GetPublicKey(ECDsa ecdsa) { - if (ecdsa is ECDsaCng cng) - { -#if NETFRAMEWORK - - var pubKeyParams = BouncyCastle.X509Utils.GetECPublicKeyParameters(ecdsa); - return SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKeyParams).ToAsn1Object().GetDerEncoded(); +#if NETFRAMEWORK + var pubKeyParams = BouncyCastle.X509Utils.GetECPublicKeyParameters(ecdsa); + return SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKeyParams).ToAsn1Object().GetDerEncoded(); #elif NETCOREAPP3_1 || NET5_0_OR_GREATER - return ecdsa.ExportSubjectPublicKeyInfo(); + return ecdsa.ExportSubjectPublicKeyInfo(); #endif - } - else - { - throw new ArgumentException("Unsuported ECDsa format"); - } } #endregion From cff04141b52b374d9b7d1ca095b3eae03004a242 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Fri, 24 May 2024 15:57:22 +0200 Subject: [PATCH 53/80] fix CreateNonce function for invalid input data --- .../Opc.Ua.Core/Security/Certificates/Nonce.cs | 17 +++++++++++++++-- .../Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs index 6c81faed6..9bdfa9736 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs @@ -452,8 +452,21 @@ private static Nonce CreateNonce(ECCurve curve, byte[] nonceData) Curve = curve, Q = { X = qx, Y = qy } }; - - nonce.m_ecdh = ECDiffieHellman.Create(ecdhParameters); + //validate curve parameters as ECDiffieHellman.Create expects already validated curve parameters + try + { + ecdhParameters.Validate(); + nonce.m_ecdh = ECDiffieHellman.Create(ecdhParameters); + } + catch (CryptographicException e) + { + throw new ArgumentException("Invalid nonce data provided", nameof(nonceData), e); + } + //On Windows a PlatformNotSupportedException is thrown when invalid parameters are provided + catch (PlatformNotSupportedException e) + { + throw new ArgumentException("Invalid nonce data provided", nameof(nonceData), e); + } return nonce; #else diff --git a/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs index 32de92b2b..7df601ae0 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs @@ -118,7 +118,7 @@ public void ValidateCreateEccNoncePolicyInvalidNonceDataCorrectLength(string sec if (securityPolicyUri.Contains("ECC_")) { - Assert.Throws(typeof(PlatformNotSupportedException), () => Ua.Nonce.CreateNonce(securityPolicyUri, randomValue)); + Assert.Throws(typeof(ArgumentException), () => Ua.Nonce.CreateNonce(securityPolicyUri, randomValue)); } else { From c1a9fd24e107fd96457640d935232112b44c9904 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Fri, 24 May 2024 16:40:07 +0200 Subject: [PATCH 54/80] ignore test on OSX --- Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs index 7df601ae0..bc018e142 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs @@ -28,13 +28,14 @@ * ======================================================================*/ using System; +using System.Runtime.InteropServices; using System.Security.Cryptography; using NUnit.Framework; using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace Opc.Ua.Core.Tests.Types.Nonce { - /// + /// /// Tests for the Binary Schema Validator class. /// [TestFixture, Category("NonceTests")] @@ -118,6 +119,11 @@ public void ValidateCreateEccNoncePolicyInvalidNonceDataCorrectLength(string sec if (securityPolicyUri.Contains("ECC_")) { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && + (securityPolicyUri == SecurityPolicies.ECC_nistP256 || securityPolicyUri == SecurityPolicies.ECC_nistP384)) + { + Assert.Ignore("No exception is thrown on OSX with NIST curves"); + } Assert.Throws(typeof(ArgumentException), () => Ua.Nonce.CreateNonce(securityPolicyUri, randomValue)); } else @@ -157,7 +163,7 @@ private bool IsSupportedByPlatform(string securityPolicyUri) return true; } - + #endregion } From d54fbc4df42156b7b206de77b33472d224f05bbe Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 30 May 2024 18:00:14 +0300 Subject: [PATCH 55/80] Remove NoWarn tag --- Libraries/Opc.Ua.Server/Server/StandardServer.cs | 15 ++++++++++++++- Libraries/Opc.Ua.Server/Session/Session.cs | 8 ++++++++ common.props | 1 - 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs index b8a9b6972..f0dec2cb8 100644 --- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs +++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs @@ -567,7 +567,13 @@ public override ResponseHeader CreateSession( } } -#if ECC_SUPPORT +#if ECC_SUPPORT + /// + /// Process additional parameters during the ECC session creation and set the session's UserToken security policy + /// + /// The session + /// The additional parameters for the session + /// An AdditionalParametersType object containing the processed parameters protected virtual AdditionalParametersType CreateSessionProcessAdditionalParameters(Session session, AdditionalParametersType parameters) { AdditionalParametersType response = null; @@ -598,6 +604,13 @@ protected virtual AdditionalParametersType CreateSessionProcessAdditionalParamet return response; } + + /// + /// Process additional parameters during ECC session activation + /// + /// The session + /// The additional parameters for the session + /// An AdditionalParametersType object containing the processed parameters protected virtual AdditionalParametersType ActivateSessionProcessAdditionalParameters(Session session, AdditionalParametersType parameters) { AdditionalParametersType response = null; diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs index 7b9a9f0f2..8eae3f715 100644 --- a/Libraries/Opc.Ua.Server/Session/Session.cs +++ b/Libraries/Opc.Ua.Server/Session/Session.cs @@ -374,6 +374,10 @@ public bool Activated } } + /// + /// Set the ECC security policy URI + /// + /// public virtual void SetEccUserTokenSecurityPolicy(string securityPolicyUri) { lock (m_lock) @@ -383,6 +387,10 @@ public virtual void SetEccUserTokenSecurityPolicy(string securityPolicyUri) } } + /// + /// Create new ECC ephemeral key + /// + /// A new ephemeral key public virtual EphemeralKeyType GetNewEccKey() { lock (m_lock) diff --git a/common.props b/common.props index 18365e360..b9ca3f867 100644 --- a/common.props +++ b/common.props @@ -7,7 +7,6 @@ Copyright © 2004-2024 OPC Foundation, Inc OPC Foundation OPC Foundation - NU5125;CS1594;CS1591;NETSDK1138;CA2254;CA1307 en-US true false From 58f1ff78fe6977deef0f6d964ae04f2fe3725876 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Tue, 4 Jun 2024 17:10:11 +0300 Subject: [PATCH 56/80] Ignore GetEndpoints call exceptions on platforms other than Windows for opc.https and https url schemes --- Tests/Opc.Ua.Client.Tests/ClientFixture.cs | 24 +++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs index da98c4126..787cb1b69 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs @@ -31,6 +31,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Net.Http; using System.Runtime.InteropServices; using System.Threading.Tasks; using NUnit.Framework; @@ -283,7 +284,28 @@ public async Task GetEndpointAsync( { if (endpoints == null) { - endpoints = await GetEndpoints(url).ConfigureAwait(false); + try + { + endpoints = await GetEndpoints(url).ConfigureAwait(false); + } + catch(Exception) + { + // Don't ignore if platform is Windows + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + throw; + } + else + { + // Don't ignore schemes other than opc.https or https + if (!url.Scheme.Equals("opc.https", StringComparison.OrdinalIgnoreCase) && + !url.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) + { + throw; + } + Assert.Ignore("Failed to get endpoints from discovery server on non Windows platform"); + } + } } var endpointDescription = SelectEndpoint(Config, endpoints, url, securityPolicy); if (endpointDescription == null) From 8fdaf6df88e255e5421e6960b7648ef087efc246 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 13 Jun 2024 10:30:57 +0300 Subject: [PATCH 57/80] Use RSA minimum certificate size of 2048 (OpenSSL on Linux does not accept less for TLS versions greated than 1), revert GetEndpointsAsync --- Tests/Opc.Ua.Client.Tests/ClientFixture.cs | 24 +--------------------- Tests/Opc.Ua.Server.Tests/ServerFixture.cs | 1 - 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs index 787cb1b69..b613acc74 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs @@ -120,7 +120,6 @@ public async Task LoadClientConfiguration(string pkiRoot = null, string clientNa // .SetApplicationCertificates(applicationCerts) .SetAutoAcceptUntrustedCertificates(true) .SetRejectSHA1SignedCertificates(false) - .SetMinimumCertificateKeySize(1024) .SetOutputFilePath(Path.Combine(pkiRoot, "Logs", "Opc.Ua.Client.Tests.log.txt")) .SetTraceMasks(TraceMasks) .Create().ConfigureAwait(false); @@ -284,28 +283,7 @@ public async Task GetEndpointAsync( { if (endpoints == null) { - try - { - endpoints = await GetEndpoints(url).ConfigureAwait(false); - } - catch(Exception) - { - // Don't ignore if platform is Windows - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - throw; - } - else - { - // Don't ignore schemes other than opc.https or https - if (!url.Scheme.Equals("opc.https", StringComparison.OrdinalIgnoreCase) && - !url.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) - { - throw; - } - Assert.Ignore("Failed to get endpoints from discovery server on non Windows platform"); - } - } + endpoints = await GetEndpoints(url).ConfigureAwait(false); } var endpointDescription = SelectEndpoint(Config, endpoints, url, securityPolicy); if (endpointDescription == null) diff --git a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs index eba07f683..e917faa8c 100644 --- a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs +++ b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs @@ -154,7 +154,6 @@ public async Task LoadConfiguration(string pkiRoot = null) Config = await serverConfig.AddSecurityConfiguration( applicationCerts, pkiRoot) - .SetMinimumCertificateKeySize(1024) .SetAutoAcceptUntrustedCertificates(AutoAccept) .Create().ConfigureAwait(false); } From e165873c420b2a2100359140e57f15662573d5aa Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 13 Jun 2024 17:13:03 +0300 Subject: [PATCH 58/80] Increase test timeout to 45 minutes --- .azurepipelines/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azurepipelines/test.yml b/.azurepipelines/test.yml index 9a285c095..3b6a673e6 100644 --- a/.azurepipelines/test.yml +++ b/.azurepipelines/test.yml @@ -67,7 +67,7 @@ jobs: arguments: '${{ variables.DotCliCommandline }} --configuration ${{ parameters.configuration }}' - task: DotNetCoreCLI@2 displayName: Test ${{ parameters.configuration }} - timeoutInMinutes: 30 + timeoutInMinutes: 45 inputs: command: test projects: $(file) From b6ce654fe383401aa0e2d24fdc2cb2224beaf134 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 14 Jun 2024 14:01:03 +0300 Subject: [PATCH 59/80] Fix build errors --- .../Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs | 2 +- Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs | 1 - Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 443928baa..14fa3130d 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -678,7 +678,7 @@ private ServiceResult GetCertificates( { certificateTypeIds = new NodeId[1] {certificateTypeId }; certificates = new byte[1][]; - certificates[0] = certificateGroup.ApplicationCertificate.Certificate.GetRawCertData(); + certificates[0] = certificateGroup.ApplicationCertificates[0].Certificate.GetRawCertData(); } else { diff --git a/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs b/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs index 27954bff3..bafc71fbc 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs @@ -61,7 +61,6 @@ public class ClientTestFramework public TokenValidatorMock TokenValidator { get; set; } = new TokenValidatorMock(); public bool SingleSession { get; set; } = true; - public int MaxChannelCount { get; set; } = 10; public bool SupportsExternalServerUrl { get; set; } = false; public ServerFixture ServerFixture { get; set; } public ClientFixture ClientFixture { get; set; } diff --git a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs index 59e0043fa..fd309275e 100644 --- a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs +++ b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs @@ -62,7 +62,6 @@ public class SubscriptionTest : ClientTestFramework SupportsExternalServerUrl = true; // create a new session for every test SingleSession = false; - MaxChannelCount = 1000; return base.OneTimeSetUpAsync(null, true); } From b5db0fb65d40856934e40f1d350522737e0c9d2b Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 14 Jun 2024 14:02:04 +0300 Subject: [PATCH 60/80] Fix validation of Nonce length --- Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs | 5 ----- Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs index 9bdfa9736..a892c3528 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs @@ -328,11 +328,6 @@ public static uint GetNonceLength(string securityPolicyUri) { switch (securityPolicyUri) { - case SecurityPolicies.Basic128Rsa15: - { - return 16; - } - case SecurityPolicies.Basic256: case SecurityPolicies.Basic256Sha256: case SecurityPolicies.Aes128_Sha256_RsaOaep: diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs index 7b5fcd248..23acb3f84 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs @@ -234,7 +234,7 @@ protected bool ValidateNonce(X509Certificate2 certificate, byte[] nonce) } // check the length. - if (nonce == null || nonce.Length < Nonce.GetNonceLength(SecurityPolicyUri)) + if (nonce == null || nonce.Length != Nonce.GetNonceLength(SecurityPolicyUri)) { return false; } From 4bc277fb073da18cfda6f6fd992f3b4e2c0a6983 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sun, 16 Jun 2024 19:11:09 +0200 Subject: [PATCH 61/80] add ECC polices to Client Security Level calculation --- .../Schema/ApplicationConfiguration.cs | 30 +------------------ .../Schema/SecuredApplicationHelpers.cs | 7 +++++ 2 files changed, 8 insertions(+), 29 deletions(-) diff --git a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs index 9cc7deae3..70b347554 100644 --- a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs +++ b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs @@ -691,35 +691,7 @@ private void Initialize() /// public static byte CalculateSecurityLevel(MessageSecurityMode mode, string policyUri) { - if ((mode == MessageSecurityMode.Invalid) || (mode == MessageSecurityMode.None)) - { - return 0; - } - - byte result = 0; - switch (policyUri) - { - case SecurityPolicies.Basic128Rsa15: result = 2; break; - case SecurityPolicies.ECC_curve25519: - case SecurityPolicies.ECC_curve448: - case SecurityPolicies.Basic256: result = 4; break; - case SecurityPolicies.Basic256Sha256: result = 6; break; - case SecurityPolicies.ECC_nistP256: result = 12; break; - case SecurityPolicies.ECC_brainpoolP256r1: result = 11; break; - case SecurityPolicies.Aes128_Sha256_RsaOaep: result = 8; break; - case SecurityPolicies.ECC_nistP384: result = 14; break; - case SecurityPolicies.ECC_brainpoolP384r1: result = 13; break; - case SecurityPolicies.Aes256_Sha256_RsaPss: result = 10; break; - case SecurityPolicies.None: - default: return 0; - } - - if (mode == MessageSecurityMode.SignAndEncrypt) - { - result += 100; - } - - return result; + return SecuredApplication.CalculateSecurityLevel(mode, policyUri); } /// diff --git a/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs b/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs index b39c21241..4ff1fab7a 100644 --- a/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs +++ b/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs @@ -387,11 +387,18 @@ public static byte CalculateSecurityLevel(MessageSecurityMode mode, string polic switch (policyUri) { case SecurityPolicies.Basic128Rsa15: result = 2; break; + case SecurityPolicies.ECC_curve25519: + case SecurityPolicies.ECC_curve448: case SecurityPolicies.Basic256: result = 4; break; case SecurityPolicies.Basic256Sha256: result = 6; break; case SecurityPolicies.Aes128_Sha256_RsaOaep: result = 8; break; case SecurityPolicies.Aes256_Sha256_RsaPss: result = 10; break; + case SecurityPolicies.ECC_brainpoolP256r1: result = 11; break; + case SecurityPolicies.ECC_nistP256: result = 12; break; + case SecurityPolicies.ECC_brainpoolP384r1: result = 13; break; + case SecurityPolicies.ECC_nistP384: result = 14; break; case SecurityPolicies.None: + return 0; default: Utils.LogWarning("Security level requested for unknown Security Policy {policy}. Returning security level 0", policyUri); return 0; From b7f3ff8646886cd43abad0e747f378913c08c17c Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Mon, 17 Jun 2024 15:59:20 +0300 Subject: [PATCH 62/80] Remove wrong propagation of minKeySize into RSA application certificate creation --- Libraries/Opc.Ua.Configuration/ApplicationInstance.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index f407ac294..ad861f80b 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -29,7 +29,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -601,7 +600,7 @@ private async Task CheckCertificateTypeAsync( if (!DisableCertificateAutoCreation) { certificate = await CreateApplicationInstanceCertificateAsync(configuration, id, - minimumKeySize, lifeTimeInMonths, ct).ConfigureAwait(false); + lifeTimeInMonths, ct).ConfigureAwait(false); } else { @@ -858,14 +857,12 @@ private static async Task CheckDomainsInCertificateAsync( /// /// The configuration. /// The certificate identifier. - /// Size of the key. /// The lifetime in months. /// /// The new certificate private static async Task CreateApplicationInstanceCertificateAsync( ApplicationConfiguration configuration, CertificateIdentifier id, - ushort keySize, ushort lifeTimeInMonths, CancellationToken ct) { @@ -901,7 +898,7 @@ private static async Task CreateApplicationInstanceCertificate id.CertificateType == ObjectTypeIds.RsaSha256ApplicationCertificateType) { id.Certificate = builder - .SetRSAKeySize(keySize) + .SetRSAKeySize(CertificateFactory.DefaultKeySize) .CreateForRSA(); Utils.LogCertificate("Certificate created for RSA.", id.Certificate); From 977680d45a8efa12e89b4dcac0133e5f60a45752 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Wed, 3 Jul 2024 16:49:05 +0300 Subject: [PATCH 63/80] Fix behaviour of flag AddAppCertToTrustedStore --- .../ApplicationInstance.cs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index ad861f80b..91619afb6 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -1094,10 +1094,25 @@ private static async Task AddToTrustedStoreAsync(ApplicationConfiguration config return; } - Utils.LogCertificate("Delete Certificate from trusted store.", certificate); + bool deleteCert = false; + if (X509Utils.IsECDsaSignature(certificates[ii]) && X509Utils.IsECDsaSignature(certificate)) + { + if (X509Utils.GetECDsaQualifier(certificates[ii]).Equals(X509Utils.GetECDsaQualifier(certificate))) + { + deleteCert = true; + } + } + else if (!X509Utils.IsECDsaSignature(certificates[ii]) && !X509Utils.IsECDsaSignature(certificate)) + { + deleteCert = true; + } - await store.Delete(certificates[ii].Thumbprint).ConfigureAwait(false); - break; + if (deleteCert) + { + Utils.LogCertificate("Delete Certificate from trusted store.", certificate); + await store.Delete(certificates[ii].Thumbprint).ConfigureAwait(false); + break; + } } } From a28d901d917781d1314fb94b339c25ea048be1d1 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 4 Jul 2024 10:31:28 +0300 Subject: [PATCH 64/80] Fix netstandard2.0 compilation --- Libraries/Opc.Ua.Server/Session/Session.cs | 26 ++++++++++++------- .../Stack/Types/UserIdentityToken.cs | 18 ++++++++++--- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs index 8eae3f715..511d0bf2b 100644 --- a/Libraries/Opc.Ua.Server/Session/Session.cs +++ b/Libraries/Opc.Ua.Server/Session/Session.cs @@ -387,6 +387,7 @@ public virtual void SetEccUserTokenSecurityPolicy(string securityPolicyUri) } } +#if ECC_SUPPORT /// /// Create new ECC ephemeral key /// @@ -412,6 +413,17 @@ public virtual EphemeralKeyType GetNewEccKey() } } + /// + /// The Server generated ephemeral key + /// + public EphemeralKeyType EphemeralKey + { + set + { + m_ephemeralKey = value; + } + } +#endif /// /// Returns the session's endpoint /// @@ -434,16 +446,7 @@ public string SecureChannelId } } - /// - /// The Server generated ephemeral key - /// - public EphemeralKeyType EphemeralKey - { - set - { - m_ephemeralKey = value; - } - } + /// /// Validates the request. @@ -1247,7 +1250,10 @@ private void UpdateDiagnosticCounters(RequestType requestType, bool error, bool private List m_browseContinuationPoints; private List m_historyContinuationPoints; +#if ECC_SUPPORT private EphemeralKeyType m_ephemeralKey; +#endif + #endregion } } diff --git a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs index dbc762707..455ee5e8a 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs @@ -22,11 +22,13 @@ namespace Opc.Ua /// public partial class UserIdentityToken { -#region Public Methods + #region Public Methods /// /// Encrypts the token (implemented by the subclass). /// +#if ECC_SUPPORT [Obsolete("Use Encrypt(X509Certificate2, byte[], string securityPolicyUri, Nonce, X509Certificate2, X509Certificate2Collection, bool) ")] +#endif public virtual void Encrypt(X509Certificate2 certificate, byte[] receiverNonce, string securityPolicyUri) { } @@ -34,7 +36,9 @@ public virtual void Encrypt(X509Certificate2 certificate, byte[] receiverNonce, /// /// Decrypts the token (implemented by the subclass). /// +#if ECC_SUPPORT [Obsolete("Use Decrypt(X509Certificate2, Nonce, string, Nonce, X509Certificate2, X509Certificate2Collection, CertificateValidator) ")] +#endif public virtual void Decrypt(X509Certificate2 certificate, byte[] receiverNonce, string securityPolicyUri) { } @@ -101,11 +105,13 @@ public string DecryptedPassword } #endregion -#region Public Methods + #region Public Methods /// /// Encrypts the DecryptedPassword using the EncryptionAlgorithm and places the result in Password /// +#if ECC_SUPPORT [Obsolete("Use Encrypt(X509Certificate2, byte[], string securityPolicyUri, Nonce, X509Certificate2, X509Certificate2Collection, bool)")] +#endif public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { Encrypt(certificate, senderNonce, securityPolicyUri, null); @@ -114,7 +120,9 @@ public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, s /// /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword /// +#if ECC_SUPPORT [Obsolete("Use Decrypt(X509Certificate2, Nonce, string, Nonce, X509Certificate2, X509Certificate2Collection, CertificateValidator) ")] +#endif public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { Decrypt(certificate, Nonce.CreateNonce(securityPolicyUri, senderNonce), securityPolicyUri); @@ -416,11 +424,13 @@ public byte[] DecryptedTokenData } #endregion -#region Public Methods + #region Public Methods /// /// Encrypts the DecryptedTokenData using the EncryptionAlgorithm and places the result in Password /// +#if ECC_SUPPORT [Obsolete("Use Encrypt(X509Certificate2, byte[], string securityPolicyUri, Nonce, X509Certificate2, X509Certificate2Collection, bool)")] +#endif public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { Encrypt(certificate, senderNonce, securityPolicyUri, null); @@ -429,7 +439,9 @@ public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, s /// /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword /// +#if ECC_SUPPORT [Obsolete("Use Decrypt(X509Certificate2, Nonce, string, Nonce, X509Certificate2, X509Certificate2Collection, CertificateValidator) ")] +#endif public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { Decrypt(certificate, Nonce.CreateNonce(securityPolicyUri, senderNonce), securityPolicyUri); From 505d325f0f840d81c18b96d7f8bb0481b7698450 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Thu, 4 Jul 2024 13:41:33 +0300 Subject: [PATCH 65/80] Changed versionHeightOffset to 70 --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 2604d514e..2ecb27ee8 100644 --- a/version.json +++ b/version.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", "version": "1.5.375-ECC-preview", - "versionHeightOffset": 20, + "versionHeightOffset": 70, "nugetPackageVersion": { "semVer": 2 }, From 8d28310cf0df4d22e9592053690b08e0337742f7 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 5 Jul 2024 10:17:45 +0300 Subject: [PATCH 66/80] Modirfied Opc.Ua.Client.cproj to generate the APICompat suppression file --- Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj index 21c63e9d0..6776fab61 100644 --- a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj +++ b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj @@ -21,6 +21,12 @@ $(PackageId).Debug + + + true + + From 8b998ad087c7038a920f5a0527ba665d20ac0b54 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Fri, 19 Jul 2024 10:49:49 +0200 Subject: [PATCH 67/80] do not duplicate the code for additional header user ecc secret --- Libraries/Opc.Ua.Client/Session/Session.cs | 77 ++++++++++--------- .../Opc.Ua.Client/Session/SessionAsync.cs | 25 +----- .../Opc.Ua.Configuration.csproj.bak | 59 ++++++++++++++ .../Stack/Types/UserIdentityToken.cs | 30 ++++---- 4 files changed, 115 insertions(+), 76 deletions(-) create mode 100644 Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj.bak diff --git a/Libraries/Opc.Ua.Client/Session/Session.cs b/Libraries/Opc.Ua.Client/Session/Session.cs index 7706c3823..6d26b26a6 100644 --- a/Libraries/Opc.Ua.Client/Session/Session.cs +++ b/Libraries/Opc.Ua.Client/Session/Session.cs @@ -1366,13 +1366,10 @@ public SessionConfiguration SaveSessionConfiguration(Stream stream = null) XmlWriterSettings settings = Utils.DefaultXmlWriterSettings(); using (XmlWriter writer = XmlWriter.Create(stream, settings)) { - DataContractSerializer serializer = new DataContractSerializer(typeof(SessionConfiguration), - new[] { typeof(UserIdentityToken), typeof(AnonymousIdentityToken), typeof(X509IdentityToken), - typeof(IssuedIdentityToken), typeof(UserIdentity), typeof(ECParameters) }); + DataContractSerializer serializer = new DataContractSerializer(typeof(SessionConfiguration)); serializer.WriteObject(writer, sessionConfiguration); } } - return sessionConfiguration; } @@ -1439,7 +1436,6 @@ private void Reconnect(ITransportWaitingConnection connection, ITransportChannel ProcessResponseAdditionalHeader(responseHeader, m_serverCertificate); - Utils.LogInfo("Session RECONNECT {0} completed successfully.", SessionId); lock (SyncRoot) @@ -2362,28 +2358,7 @@ public void Open( } // select the security policy for the user token. - var userTokenSecurityPolicyUri = identityPolicy.SecurityPolicyUri; - - if (String.IsNullOrEmpty(userTokenSecurityPolicyUri)) - { - userTokenSecurityPolicyUri = m_endpoint.Description.SecurityPolicyUri; - } - m_userTokenSecurityPolicyUri = userTokenSecurityPolicyUri; - - RequestHeader requestHeader = new RequestHeader(); - - if (EccUtils.IsEccPolicy(userTokenSecurityPolicyUri)) - { - AdditionalParametersType parameters = new AdditionalParametersType(); - - parameters.Parameters.Add(new KeyValuePair() - { - Key = "ECDHPolicyUri", - Value = userTokenSecurityPolicyUri - }); - - requestHeader.AdditionalHeader = new ExtensionObject(parameters); - } + RequestHeader requestHeader = CreateRequestHeaderPerUserTokenPolicy(identityPolicy.SecurityPolicyUri, m_endpoint.Description.SecurityPolicyUri); bool successCreateSession = false; ResponseHeader responseHeader = null; @@ -2426,7 +2401,7 @@ public void Open( if (!successCreateSession) { responseHeader = base.CreateSession( - null, + requestHeader, clientDescription, m_endpoint.Description.Server.ApplicationUri, m_endpoint.EndpointUrl.ToString(), @@ -2503,7 +2478,7 @@ public void Open( identityToken.Encrypt( serverCertificate, serverNonce, - userTokenSecurityPolicyUri, + m_userTokenSecurityPolicyUri, m_eccServerEphemeralKey, m_instanceCertificate, m_instanceCertificateChain, @@ -6400,16 +6375,44 @@ private static void UpdateLatestSequenceNumberToSend(ref uint latestSequenceNumb latestSequenceNumberToSend = sequenceNumber; } } - #endregion - #region Protected Methods /// - /// Process the AdditionalHeader field of a ResponseHeader + /// Creates a request header with additional parameters for the ecc user token security policy. + /// Returns null if additional header is not needed. /// - /// - /// - /// - protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHeader, X509Certificate2 serverCertificate) + private RequestHeader CreateRequestHeaderPerUserTokenPolicy(string identityTokenSecurityPolicyUri, string endpointSecurityPolicyUri) + { + var requestHeader = new RequestHeader(); + var userTokenSecurityPolicyUri = identityTokenSecurityPolicyUri; + if (String.IsNullOrEmpty(userTokenSecurityPolicyUri)) + { + userTokenSecurityPolicyUri = m_endpoint.Description.SecurityPolicyUri; + } + m_userTokenSecurityPolicyUri = userTokenSecurityPolicyUri; + + if (EccUtils.IsEccPolicy(userTokenSecurityPolicyUri)) + { + AdditionalParametersType parameters = new AdditionalParametersType(); + parameters.Parameters.Add(new KeyValuePair() { + Key = "ECDHPolicyUri", + Value = userTokenSecurityPolicyUri + }); + requestHeader.AdditionalHeader = new ExtensionObject(parameters); + } + + return requestHeader; + } + + #endregion + + #region Protected Methods + /// + /// Process the AdditionalHeader field of a ResponseHeader + /// + /// + /// + /// + protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHeader, X509Certificate2 serverCertificate) { AdditionalParametersType parameters = ExtensionObject.ToEncodeable(responseHeader?.AdditionalHeader) as AdditionalParametersType; @@ -6448,8 +6451,6 @@ protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHe } } } - - #endregion #region Protected Fields diff --git a/Libraries/Opc.Ua.Client/Session/SessionAsync.cs b/Libraries/Opc.Ua.Client/Session/SessionAsync.cs index 437a918f2..d916a19bb 100644 --- a/Libraries/Opc.Ua.Client/Session/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/Session/SessionAsync.cs @@ -126,29 +126,8 @@ public async Task OpenAsync( sessionTimeout = (uint)m_configuration.ClientConfiguration.DefaultSessionTimeout; } -//TODO: helper for user token selection? // select the security policy for the user token. - var userTokenSecurityPolicyUri = identityPolicy.SecurityPolicyUri; - - if (String.IsNullOrEmpty(userTokenSecurityPolicyUri)) - { - userTokenSecurityPolicyUri = m_endpoint.Description.SecurityPolicyUri; - } - m_userTokenSecurityPolicyUri = userTokenSecurityPolicyUri; - - RequestHeader requestHeader = new RequestHeader(); - - if (EccUtils.IsEccPolicy(userTokenSecurityPolicyUri)) - { - AdditionalParametersType parameters = new AdditionalParametersType(); - - parameters.Parameters.Add(new KeyValuePair() { - Key = "ECDHPolicyUri", - Value = userTokenSecurityPolicyUri - }); - - requestHeader.AdditionalHeader = new ExtensionObject(parameters); - } + RequestHeader requestHeader = CreateRequestHeaderPerUserTokenPolicy(identityPolicy.SecurityPolicyUri, m_endpoint.Description.SecurityPolicyUri); bool successCreateSession = false; CreateSessionResponse response = null; @@ -264,7 +243,7 @@ public async Task OpenAsync( identityToken.Encrypt( serverCertificate, serverNonce, - userTokenSecurityPolicyUri, + m_userTokenSecurityPolicyUri, m_eccServerEphemeralKey, m_instanceCertificate, m_instanceCertificateChain, diff --git a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj.bak b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj.bak new file mode 100644 index 000000000..6110a7e75 --- /dev/null +++ b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj.bak @@ -0,0 +1,59 @@ + + + + Opc.Ua.Configuration + $(LibTargetFrameworks) + OPCFoundation.NetStandard.Opc.Ua.Configuration + Opc.Ua.Configuration + OPC UA Configuration Class Library + true + MIT + + true + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + + + $(DefineConstants);ECC_SUPPORT + + + + $(PackageId).Debug + + + + + + + + + + + + + + $(DefineConstants);SIGNASSEMBLY + + + + + diff --git a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs index 455ee5e8a..4eff4b590 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs @@ -86,7 +86,7 @@ public virtual bool Verify(byte[] dataToVerify, SignatureData signatureData, str { return true; } -#endregion + #endregion } /// @@ -94,7 +94,7 @@ public virtual bool Verify(byte[] dataToVerify, SignatureData signatureData, str /// public partial class UserNameIdentityToken { -#region Public Properties + #region Public Properties /// /// The decrypted password associated with the token. /// @@ -287,11 +287,11 @@ public override void Decrypt( } } -#endregion + #endregion -#region Private Fields + #region Private Fields private string m_decryptedPassword; -#endregion + #endregion } /// @@ -299,7 +299,7 @@ public override void Decrypt( /// public partial class X509IdentityToken { -#region Public Properties + #region Public Properties /// /// The certificate associated with the token. /// @@ -315,9 +315,9 @@ public X509Certificate2 Certificate } set { m_certificate = value; } } -#endregion + #endregion -#region Public Methods + #region Public Methods /// /// Creates a signature with the token. /// @@ -369,11 +369,11 @@ public override bool Verify(byte[] dataToVerify, SignatureData signatureData, st throw ServiceResultException.Create(StatusCodes.BadIdentityTokenInvalid, e, "Could not verify user signature!"); } } -#endregion + #endregion -#region Private Fields + #region Private Fields private X509Certificate2 m_certificate; -#endregion + #endregion } /// @@ -404,7 +404,7 @@ public enum IssuedTokenType /// public partial class IssuedIdentityToken { -#region Public Properties + #region Public Properties /// /// The type of issued token. /// @@ -543,10 +543,10 @@ public override bool Verify(byte[] dataToVerify, SignatureData signatureData, st { return true; } -#endregion + #endregion -#region Private Fields + #region Private Fields private byte[] m_decryptedTokenData; -#endregion + #endregion } } From 3fcb5e419217dad02fcc0dcf1689a59607a26101 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Fri, 19 Jul 2024 10:51:25 +0200 Subject: [PATCH 68/80] fix formatting --- Libraries/Opc.Ua.Client/Session/Session.cs | 484 +++++++++--------- .../Opc.Ua.Client/Session/SessionAsync.cs | 46 +- 2 files changed, 254 insertions(+), 276 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session/Session.cs b/Libraries/Opc.Ua.Client/Session/Session.cs index 6d26b26a6..d9eb9842f 100644 --- a/Libraries/Opc.Ua.Client/Session/Session.cs +++ b/Libraries/Opc.Ua.Client/Session/Session.cs @@ -201,8 +201,7 @@ private void Initialize( m_preferredLocales = new string[] { CultureInfo.CurrentCulture.Name }; // create a context to use. - m_systemContext = new SystemContext - { + m_systemContext = new SystemContext { SystemHandle = this, EncodeableFactory = m_factory, NamespaceUris = m_namespaceUris, @@ -245,8 +244,7 @@ private void Initialize() m_reconnecting = false; m_reconnectLock = new SemaphoreSlim(1, 1); - m_defaultSubscription = new Subscription - { + m_defaultSubscription = new Subscription { DisplayName = "Subscription", PublishingInterval = 1000, KeepAliveCount = 10, @@ -1940,8 +1938,7 @@ public void ReadNodes( // first read only nodeclasses for nodes from server. var itemsToRead = new ReadValueIdCollection( nodeIds.Select(nodeId => - new ReadValueId - { + new ReadValueId { NodeId = nodeId, AttributeId = Attributes.NodeClass })); @@ -2023,8 +2020,7 @@ public Node ReadNode( ReadValueIdCollection itemsToRead = new ReadValueIdCollection(); foreach (uint attributeId in attributes.Keys) { - ReadValueId itemToRead = new ReadValueId - { + ReadValueId itemToRead = new ReadValueId { NodeId = nodeId, AttributeId = attributeId }; @@ -2052,8 +2048,7 @@ public Node ReadNode( /// public DataValue ReadValue(NodeId nodeId) { - ReadValueId itemToRead = new ReadValueId - { + ReadValueId itemToRead = new ReadValueId { NodeId = nodeId, AttributeId = Attributes.Value }; @@ -2102,8 +2097,7 @@ public void ReadValues( // read all values from server. var itemsToRead = new ReadValueIdCollection( nodeIds.Select(nodeId => - new ReadValueId - { + new ReadValueId { NodeId = nodeId, AttributeId = Attributes.Value })); @@ -2344,8 +2338,7 @@ public void Open( // send the application instance certificate for the client. BuildCertificateData(out byte[] clientCertificateData, out byte[] clientCertificateChainData); - ApplicationDescription clientDescription = new ApplicationDescription - { + ApplicationDescription clientDescription = new ApplicationDescription { ApplicationUri = m_configuration.ApplicationUri, ApplicationName = m_configuration.ApplicationName, ApplicationType = ApplicationType.Client, @@ -3053,8 +3046,7 @@ public virtual StatusCode Close(int timeout, bool closeChannel) try { // close the session and delete all subscriptions if specified. - var requestHeader = new RequestHeader() - { + var requestHeader = new RequestHeader() { TimeoutHint = timeout > 0 ? (uint)timeout : (uint)(this.OperationTimeout > 0 ? this.OperationTimeout : 0), }; CloseSession(requestHeader, m_deleteSubscriptionsOnClose); @@ -3391,8 +3383,7 @@ public virtual ResponseHeader Browse( BrowseDescriptionCollection browseDescription = new BrowseDescriptionCollection(); foreach (var nodeToBrowse in nodesToBrowse) { - BrowseDescription description = new BrowseDescription - { + BrowseDescription description = new BrowseDescription { NodeId = nodeToBrowse, BrowseDirection = browseDirection, ReferenceTypeId = referenceTypeId, @@ -3912,8 +3903,7 @@ private void OnSendKeepAlive(ReadValueIdCollection nodesToRead) } } - RequestHeader requestHeader = new RequestHeader - { + RequestHeader requestHeader = new RequestHeader { RequestHandle = Utils.IncrementIdentifier(ref m_keepAliveCounter), TimeoutHint = (uint)(KeepAliveInterval * 2), ReturnDiagnostics = 0 @@ -4126,8 +4116,7 @@ private void CreateNodeClassAttributesReadNodesRequest( var attributes = CreateAttributes(node.NodeClass, optionalAttributes); foreach (uint attributeId in attributes.Keys) { - ReadValueId itemToRead = new ReadValueId - { + ReadValueId itemToRead = new ReadValueId { NodeId = node.NodeId, AttributeId = attributeId }; @@ -4147,8 +4136,7 @@ private ReadValueIdCollection PrepareNamespaceTableNodesToRead() var nodesToRead = new ReadValueIdCollection(); // request namespace array. - ReadValueId valueId = new ReadValueId - { + ReadValueId valueId = new ReadValueId { NodeId = Variables.Server_NamespaceArray, AttributeId = Attributes.Value }; @@ -4156,8 +4144,7 @@ private ReadValueIdCollection PrepareNamespaceTableNodesToRead() nodesToRead.Add(valueId); // request server array. - valueId = new ReadValueId - { + valueId = new ReadValueId { NodeId = Variables.Server_ServerArray, AttributeId = Attributes.Value }; @@ -4242,8 +4229,7 @@ bool optionalAttributes var attributes = CreateAttributes(node.NodeClass, optionalAttributes); foreach (uint attributeId in attributes.Keys) { - ReadValueId itemToRead = new ReadValueId - { + ReadValueId itemToRead = new ReadValueId { NodeId = node.NodeId, AttributeId = attributeId }; @@ -4375,294 +4361,294 @@ private Node ProcessReadResponse( switch ((NodeClass)nodeClass.Value) { default: - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not have a valid value for NodeClass: {0}.", nodeClass.Value); - } + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Node does not have a valid value for NodeClass: {0}.", nodeClass.Value); + } case NodeClass.Object: - { - ObjectNode objectNode = new ObjectNode(); - - value = attributes[Attributes.EventNotifier]; + { + ObjectNode objectNode = new ObjectNode(); - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Object does not support the EventNotifier attribute."); - } + value = attributes[Attributes.EventNotifier]; - objectNode.EventNotifier = (byte)value.GetValue(typeof(byte)); - node = objectNode; - break; + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Object does not support the EventNotifier attribute."); } - case NodeClass.ObjectType: - { - ObjectTypeNode objectTypeNode = new ObjectTypeNode(); + objectNode.EventNotifier = (byte)value.GetValue(typeof(byte)); + node = objectNode; + break; + } - value = attributes[Attributes.IsAbstract]; + case NodeClass.ObjectType: + { + ObjectTypeNode objectTypeNode = new ObjectTypeNode(); - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ObjectType does not support the IsAbstract attribute."); - } + value = attributes[Attributes.IsAbstract]; - objectTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool)); - node = objectTypeNode; - break; + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ObjectType does not support the IsAbstract attribute."); } + objectTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool)); + node = objectTypeNode; + break; + } + case NodeClass.Variable: - { - VariableNode variableNode = new VariableNode(); + { + VariableNode variableNode = new VariableNode(); - // DataType Attribute - value = attributes[Attributes.DataType]; + // DataType Attribute + value = attributes[Attributes.DataType]; - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the DataType attribute."); - } + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the DataType attribute."); + } - variableNode.DataType = (NodeId)value.GetValue(typeof(NodeId)); + variableNode.DataType = (NodeId)value.GetValue(typeof(NodeId)); - // ValueRank Attribute - value = attributes[Attributes.ValueRank]; + // ValueRank Attribute + value = attributes[Attributes.ValueRank]; - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the ValueRank attribute."); - } + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the ValueRank attribute."); + } - variableNode.ValueRank = (int)value.GetValue(typeof(int)); + variableNode.ValueRank = (int)value.GetValue(typeof(int)); - // ArrayDimensions Attribute - value = attributes[Attributes.ArrayDimensions]; + // ArrayDimensions Attribute + value = attributes[Attributes.ArrayDimensions]; - if (value != null) + if (value != null) + { + if (value.Value == null) { - if (value.Value == null) - { - variableNode.ArrayDimensions = Array.Empty(); - } - else - { - variableNode.ArrayDimensions = (uint[])value.GetValue(typeof(uint[])); - } + variableNode.ArrayDimensions = Array.Empty(); } - - // AccessLevel Attribute - value = attributes[Attributes.AccessLevel]; - - if (value == null) + else { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the AccessLevel attribute."); + variableNode.ArrayDimensions = (uint[])value.GetValue(typeof(uint[])); } + } - variableNode.AccessLevel = (byte)value.GetValue(typeof(byte)); - - // UserAccessLevel Attribute - value = attributes[Attributes.UserAccessLevel]; + // AccessLevel Attribute + value = attributes[Attributes.AccessLevel]; - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the UserAccessLevel attribute."); - } + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the AccessLevel attribute."); + } - variableNode.UserAccessLevel = (byte)value.GetValue(typeof(byte)); + variableNode.AccessLevel = (byte)value.GetValue(typeof(byte)); - // Historizing Attribute - value = attributes[Attributes.Historizing]; + // UserAccessLevel Attribute + value = attributes[Attributes.UserAccessLevel]; - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the Historizing attribute."); - } + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the UserAccessLevel attribute."); + } - variableNode.Historizing = (bool)value.GetValue(typeof(bool)); + variableNode.UserAccessLevel = (byte)value.GetValue(typeof(byte)); - // MinimumSamplingInterval Attribute - value = attributes[Attributes.MinimumSamplingInterval]; + // Historizing Attribute + value = attributes[Attributes.Historizing]; - if (value != null) - { - variableNode.MinimumSamplingInterval = Convert.ToDouble(attributes[Attributes.MinimumSamplingInterval].Value, CultureInfo.InvariantCulture); - } + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Variable does not support the Historizing attribute."); + } - // AccessLevelEx Attribute - value = attributes[Attributes.AccessLevelEx]; + variableNode.Historizing = (bool)value.GetValue(typeof(bool)); - if (value != null) - { - variableNode.AccessLevelEx = (uint)value.GetValue(typeof(uint)); - } + // MinimumSamplingInterval Attribute + value = attributes[Attributes.MinimumSamplingInterval]; - node = variableNode; - break; + if (value != null) + { + variableNode.MinimumSamplingInterval = Convert.ToDouble(attributes[Attributes.MinimumSamplingInterval].Value, CultureInfo.InvariantCulture); } - case NodeClass.VariableType: + // AccessLevelEx Attribute + value = attributes[Attributes.AccessLevelEx]; + + if (value != null) { - VariableTypeNode variableTypeNode = new VariableTypeNode(); + variableNode.AccessLevelEx = (uint)value.GetValue(typeof(uint)); + } - // IsAbstract Attribute - value = attributes[Attributes.IsAbstract]; + node = variableNode; + break; + } - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the IsAbstract attribute."); - } + case NodeClass.VariableType: + { + VariableTypeNode variableTypeNode = new VariableTypeNode(); - variableTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool)); + // IsAbstract Attribute + value = attributes[Attributes.IsAbstract]; - // DataType Attribute - value = attributes[Attributes.DataType]; + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the IsAbstract attribute."); + } - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the DataType attribute."); - } + variableTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool)); + + // DataType Attribute + value = attributes[Attributes.DataType]; - variableTypeNode.DataType = (NodeId)value.GetValue(typeof(NodeId)); + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the DataType attribute."); + } - // ValueRank Attribute - value = attributes[Attributes.ValueRank]; + variableTypeNode.DataType = (NodeId)value.GetValue(typeof(NodeId)); - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the ValueRank attribute."); - } + // ValueRank Attribute + value = attributes[Attributes.ValueRank]; - variableTypeNode.ValueRank = (int)value.GetValue(typeof(int)); + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "VariableType does not support the ValueRank attribute."); + } - // ArrayDimensions Attribute - value = attributes[Attributes.ArrayDimensions]; + variableTypeNode.ValueRank = (int)value.GetValue(typeof(int)); - if (value != null && value.Value != null) - { - variableTypeNode.ArrayDimensions = (uint[])value.GetValue(typeof(uint[])); - } + // ArrayDimensions Attribute + value = attributes[Attributes.ArrayDimensions]; - node = variableTypeNode; - break; + if (value != null && value.Value != null) + { + variableTypeNode.ArrayDimensions = (uint[])value.GetValue(typeof(uint[])); } + node = variableTypeNode; + break; + } + case NodeClass.Method: - { - MethodNode methodNode = new MethodNode(); + { + MethodNode methodNode = new MethodNode(); - // Executable Attribute - value = attributes[Attributes.Executable]; + // Executable Attribute + value = attributes[Attributes.Executable]; - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Method does not support the Executable attribute."); - } + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Method does not support the Executable attribute."); + } - methodNode.Executable = (bool)value.GetValue(typeof(bool)); + methodNode.Executable = (bool)value.GetValue(typeof(bool)); - // UserExecutable Attribute - value = attributes[Attributes.UserExecutable]; + // UserExecutable Attribute + value = attributes[Attributes.UserExecutable]; - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Method does not support the UserExecutable attribute."); - } + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "Method does not support the UserExecutable attribute."); + } - methodNode.UserExecutable = (bool)value.GetValue(typeof(bool)); + methodNode.UserExecutable = (bool)value.GetValue(typeof(bool)); - node = methodNode; - break; - } + node = methodNode; + break; + } case NodeClass.DataType: - { - DataTypeNode dataTypeNode = new DataTypeNode(); + { + DataTypeNode dataTypeNode = new DataTypeNode(); - // IsAbstract Attribute - value = attributes[Attributes.IsAbstract]; + // IsAbstract Attribute + value = attributes[Attributes.IsAbstract]; - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "DataType does not support the IsAbstract attribute."); - } - - dataTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool)); + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "DataType does not support the IsAbstract attribute."); + } - // DataTypeDefinition Attribute - value = attributes[Attributes.DataTypeDefinition]; + dataTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool)); - if (value != null) - { - dataTypeNode.DataTypeDefinition = value.Value as ExtensionObject; - } + // DataTypeDefinition Attribute + value = attributes[Attributes.DataTypeDefinition]; - node = dataTypeNode; - break; + if (value != null) + { + dataTypeNode.DataTypeDefinition = value.Value as ExtensionObject; } - case NodeClass.ReferenceType: - { - ReferenceTypeNode referenceTypeNode = new ReferenceTypeNode(); + node = dataTypeNode; + break; + } - // IsAbstract Attribute - value = attributes[Attributes.IsAbstract]; + case NodeClass.ReferenceType: + { + ReferenceTypeNode referenceTypeNode = new ReferenceTypeNode(); - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ReferenceType does not support the IsAbstract attribute."); - } + // IsAbstract Attribute + value = attributes[Attributes.IsAbstract]; - referenceTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool)); + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ReferenceType does not support the IsAbstract attribute."); + } - // Symmetric Attribute - value = attributes[Attributes.Symmetric]; + referenceTypeNode.IsAbstract = (bool)value.GetValue(typeof(bool)); - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ReferenceType does not support the Symmetric attribute."); - } + // Symmetric Attribute + value = attributes[Attributes.Symmetric]; - referenceTypeNode.Symmetric = (bool)value.GetValue(typeof(bool)); + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "ReferenceType does not support the Symmetric attribute."); + } - // InverseName Attribute - value = attributes[Attributes.InverseName]; + referenceTypeNode.Symmetric = (bool)value.GetValue(typeof(bool)); - if (value != null && value.Value != null) - { - referenceTypeNode.InverseName = (LocalizedText)value.GetValue(typeof(LocalizedText)); - } + // InverseName Attribute + value = attributes[Attributes.InverseName]; - node = referenceTypeNode; - break; + if (value != null && value.Value != null) + { + referenceTypeNode.InverseName = (LocalizedText)value.GetValue(typeof(LocalizedText)); } + node = referenceTypeNode; + break; + } + case NodeClass.View: - { - ViewNode viewNode = new ViewNode(); + { + ViewNode viewNode = new ViewNode(); - // EventNotifier Attribute - value = attributes[Attributes.EventNotifier]; + // EventNotifier Attribute + value = attributes[Attributes.EventNotifier]; - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "View does not support the EventNotifier attribute."); - } + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "View does not support the EventNotifier attribute."); + } - viewNode.EventNotifier = (byte)value.GetValue(typeof(byte)); + viewNode.EventNotifier = (byte)value.GetValue(typeof(byte)); - // ContainsNoLoops Attribute - value = attributes[Attributes.ContainsNoLoops]; + // ContainsNoLoops Attribute + value = attributes[Attributes.ContainsNoLoops]; - if (value == null) - { - throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "View does not support the ContainsNoLoops attribute."); - } + if (value == null) + { + throw ServiceResultException.Create(StatusCodes.BadUnexpectedError, "View does not support the ContainsNoLoops attribute."); + } - viewNode.ContainsNoLoops = (bool)value.GetValue(typeof(bool)); + viewNode.ContainsNoLoops = (bool)value.GetValue(typeof(bool)); - node = viewNode; - break; - } + node = viewNode; + break; + } } // NodeId Attribute @@ -4925,16 +4911,14 @@ public IAsyncResult BeginPublish(int timeout) timeoutHint = Math.Min((uint)(OperationTimeout / 2), timeoutHint); // send publish request. - var requestHeader = new RequestHeader - { + var requestHeader = new RequestHeader { // ensure the publish request is discarded before the timeout occurs to ensure the channel is dropped. TimeoutHint = timeoutHint, ReturnDiagnostics = (uint)(int)ReturnDiagnostics, RequestHandle = Utils.IncrementIdentifier(ref m_publishCounter) }; - var state = new AsyncRequestState - { + var state = new AsyncRequestState { RequestTypeId = DataTypes.PublishRequest, RequestId = requestHeader.RequestHandle, TickCount = HiResClock.TickCount @@ -5148,8 +5132,7 @@ private void OnPublishComplete(IAsyncResult result) case StatusCodes.BadTcpServerTooBusy: case StatusCodes.BadServerTooBusy: // throttle the next publish to reduce server load - _ = Task.Run(async () => - { + _ = Task.Run(async () => { await Task.Delay(100).ConfigureAwait(false); QueueBeginPublish(); }); @@ -5175,8 +5158,7 @@ public bool Republish(uint subscriptionId, uint sequenceNumber, out ServiceResul error = ServiceResult.Good; // send republish request. - RequestHeader requestHeader = new RequestHeader - { + RequestHeader requestHeader = new RequestHeader { TimeoutHint = (uint)OperationTimeout, ReturnDiagnostics = (uint)(int)ReturnDiagnostics, RequestHandle = Utils.IncrementIdentifier(ref m_publishCounter) @@ -6010,8 +5992,7 @@ private void ProcessPublishResponse( { NotificationEventArgs args = new NotificationEventArgs(subscription, notificationMessage, responseHeader.StringTable); - Task.Run(() => - { + Task.Run(() => { OnRaisePublishNotification(publishEventHandler, args); }); } @@ -6023,8 +6004,7 @@ private void ProcessPublishResponse( // Delete abandoned subscription from server. Utils.LogWarning("Received Publish Response for Unknown SubscriptionId={0}. Deleting abandoned subscription from server.", subscriptionId); - Task.Run(() => - { + Task.Run(() => { DeleteSubscription(subscriptionId); }); } @@ -6235,8 +6215,7 @@ private void AddAcknowledgementToSend(SubscriptionAcknowledgementCollection ackn Debug.Assert(Monitor.IsEntered(m_acknowledgementsToSendLock)); - SubscriptionAcknowledgement acknowledgement = new SubscriptionAcknowledgement - { + SubscriptionAcknowledgement acknowledgement = new SubscriptionAcknowledgement { SubscriptionId = subscriptionId, SequenceNumber = sequenceNumber }; @@ -6308,8 +6287,7 @@ private CallMethodRequestCollection CreateCallRequestsForResendData(IEnumerable< new Variant(subscription.Id) }; - var request = new CallMethodRequest - { + var request = new CallMethodRequest { ObjectId = ObjectIds.Server, MethodId = MethodIds.Server_ResendData, InputArguments = inputArguments @@ -6403,16 +6381,16 @@ private RequestHeader CreateRequestHeaderPerUserTokenPolicy(string identityToken return requestHeader; } - #endregion + #endregion - #region Protected Methods - /// - /// Process the AdditionalHeader field of a ResponseHeader - /// - /// - /// - /// - protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHeader, X509Certificate2 serverCertificate) + #region Protected Methods + /// + /// Process the AdditionalHeader field of a ResponseHeader + /// + /// + /// + /// + protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHeader, X509Certificate2 serverCertificate) { AdditionalParametersType parameters = ExtensionObject.ToEncodeable(responseHeader?.AdditionalHeader) as AdditionalParametersType; diff --git a/Libraries/Opc.Ua.Client/Session/SessionAsync.cs b/Libraries/Opc.Ua.Client/Session/SessionAsync.cs index d916a19bb..5187b2304 100644 --- a/Libraries/Opc.Ua.Client/Session/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/Session/SessionAsync.cs @@ -340,9 +340,9 @@ public async Task OpenAsync( throw; } } -#endregion + #endregion -#region Subscription Async Methods + #region Subscription Async Methods /// public async Task RemoveSubscriptionAsync(Subscription subscription, CancellationToken ct = default) { @@ -569,9 +569,9 @@ public async Task TransferSubscriptionsAsync( return failedSubscriptions == 0; } -#endregion + #endregion -#region FetchNamespaceTables Async Methods + #region FetchNamespaceTables Async Methods /// public async Task FetchNamespaceTablesAsync(CancellationToken ct = default) { @@ -594,9 +594,9 @@ public async Task FetchNamespaceTablesAsync(CancellationToken ct = default) UpdateNamespaceTable(values, diagnosticInfos, responseHeader); } -#endregion + #endregion -#region FetchTypeTree Async Methods + #region FetchTypeTree Async Methods /// public async Task FetchTypeTreeAsync(ExpandedNodeId typeId, CancellationToken ct = default) { @@ -640,9 +640,9 @@ public async Task FetchTypeTreeAsync(ExpandedNodeIdCollection typeIds, Cancellat await FetchTypeTreeAsync(subTypes, ct).ConfigureAwait(false); } } -#endregion + #endregion -#region FetchOperationLimits Async Methods + #region FetchOperationLimits Async Methods /// /// Fetch the operation limits of the server. /// @@ -695,9 +695,9 @@ public async Task FetchOperationLimitsAsync(CancellationToken ct) } } } -#endregion + #endregion -#region ReadNode Async Methods + #region ReadNode Async Methods /// public async Task<(IList, IList)> ReadNodesAsync( IList nodeIds, @@ -948,9 +948,9 @@ public async Task ReadValueAsync( return (values, errors); } -#endregion + #endregion -#region Browse Methods + #region Browse Methods /// public async Task<( ResponseHeader responseHeader, @@ -1019,9 +1019,9 @@ IList errors return (browseResponse.ResponseHeader, continuationPoints, referencesList, errors); } -#endregion + #endregion -#region BrowseNext Methods + #region BrowseNext Methods /// public async Task<( @@ -1070,9 +1070,9 @@ List errors return (response.ResponseHeader, revisedContinuationPoints, referencesList, errors); } -#endregion + #endregion -#region Call Methods + #region Call Methods /// public async Task> CallAsync(NodeId objectId, NodeId methodId, CancellationToken ct = default, params object[] args) { @@ -1120,9 +1120,9 @@ public async Task> CallAsync(NodeId objectId, NodeId methodId, Can return outputArguments; } -#endregion + #endregion -#region FetchReferences Async Methods + #region FetchReferences Async Methods /// public async Task FetchReferencesAsync( NodeId nodeId, @@ -1247,9 +1247,9 @@ IList browseNextErrors return (result, errors); } -#endregion + #endregion -#region Recreate Async Methods + #region Recreate Async Methods /// /// Recreates a session based on a specified template. /// @@ -1389,9 +1389,9 @@ await session.OpenAsync( return session; } -#endregion + #endregion -#region Close Async Methods + #region Close Async Methods /// public override Task CloseAsync(CancellationToken ct = default) { @@ -1480,7 +1480,7 @@ public virtual async Task CloseAsync(int timeout, bool closeChannel, return result; } -#endregion + #endregion #region Reconnect Async Methods /// From f20f2063bd6f6ff9b6f72fea7ff9e78b8f380ab3 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Sat, 20 Jul 2024 08:09:14 +0200 Subject: [PATCH 69/80] simplify code --- .../ApplicationConfigurationBuilder.cs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs index caad50faa..8a1504624 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs @@ -143,16 +143,8 @@ public IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration( StoreType = rejectedRootType, StorePath = DefaultCertificateStorePath(TrustlistType.Rejected, rejectedRoot) }, - // ensure secure default settings - AutoAcceptUntrustedCertificates = false, - AddAppCertToTrustedStore = false, - RejectSHA1SignedCertificates = true, - RejectUnknownRevocationStatus = true, - SuppressNonceValidationErrors = false, - SendCertificateChain = true, - MinimumCertificateKeySize = CertificateFactory.DefaultKeySize, - MinimumECCertificateKeySize = CertificateFactory.DefaultECCKeySize, }; + SetSecureDefaults(ApplicationConfiguration.SecurityConfiguration); return this; } @@ -203,16 +195,9 @@ public IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration( StoreType = rejectedRootType, StorePath = DefaultCertificateStorePath(TrustlistType.Rejected, rejectedRoot) }, - // ensure secure default settings - AutoAcceptUntrustedCertificates = false, - AddAppCertToTrustedStore = false, - RejectSHA1SignedCertificates = true, - RejectUnknownRevocationStatus = true, - SuppressNonceValidationErrors = false, - SendCertificateChain = true, - MinimumCertificateKeySize = CertificateFactory.DefaultKeySize, - MinimumECCertificateKeySize = CertificateFactory.DefaultECCKeySize, }; + SetSecureDefaults(ApplicationConfiguration.SecurityConfiguration); + return this; } @@ -1186,6 +1171,7 @@ private void SetSecureDefaults(SecurityConfiguration securityConfiguration) securityConfiguration.SuppressNonceValidationErrors = false; securityConfiguration.SendCertificateChain = true; securityConfiguration.MinimumCertificateKeySize = CertificateFactory.DefaultKeySize; + securityConfiguration.MinimumECCertificateKeySize = CertificateFactory.DefaultECCKeySize; } /// From 32c2f9e8382741c0d7eee165e2896e9075f99a34 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Sat, 20 Jul 2024 08:28:10 +0200 Subject: [PATCH 70/80] add test and a few missing bits in sessionconfiguration --- .../Session/SessionConfiguration.cs | 8 +++--- Tests/Opc.Ua.Client.Tests/ClientTest.cs | 25 +++++++++++++------ .../ClientTestNoSecurity.cs | 10 ++++++++ 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs b/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs index fc18f3cc2..21231f13f 100644 --- a/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs +++ b/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs @@ -54,12 +54,14 @@ public class SessionConfiguration /// /// Creates a session configuration /// - internal SessionConfiguration(ISession session, + public SessionConfiguration( + ISession session, byte[] serverNonce, string userIdentityTokenPolicy, Nonce eccServerEphemeralKey, NodeId authenthicationToken) { + Timestamp = DateTime.UtcNow; SessionName = session.SessionName; SessionId = session.SessionId; AuthenticationToken = authenthicationToken; @@ -154,13 +156,13 @@ public static SessionConfiguration Create(Stream stream) /// /// The last server ecc ephemeral key received. /// - [DataMember(IsRequired = true, Order = 80)] + [DataMember(IsRequired = true, Order = 90)] public string UserIdentityTokenPolicy { get; set; } /// /// The last server ecc ephemeral key received. /// - [DataMember(IsRequired = true, Order = 90)] + [DataMember(IsRequired = false, Order = 100)] public Nonce ServerEccEphemeralKey { get; set; } #endif diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs index abe927db3..6c6eb9420 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs @@ -299,7 +299,8 @@ public void ReadOnDiscoveryChannel(int readCount) /// but an oversized message should return an error. /// [Test, Order(105)] - public void GetEndpointsOnDiscoveryChannel() + [TestCase(false)] + public void GetEndpointsOnDiscoveryChannel(bool securityNoneEnabled) { var endpointConfiguration = EndpointConfiguration.Create(); endpointConfiguration.OperationTimeout = 10000; @@ -312,14 +313,24 @@ public void GetEndpointsOnDiscoveryChannel() // dummy uri to create a bigger message profileUris.Add($"https://opcfoundation.org/ProfileUri={i}"); } - var sre = Assert.Throws(() => client.GetEndpoints(profileUris)); - // race condition, if socket closed is detected before the error was returned, - // client may report channel closed instead of security policy rejected - if (StatusCodes.BadSecureChannelClosed == sre.StatusCode) + + if (securityNoneEnabled) { - Assert.Inconclusive($"Unexpected Status: {sre}"); + // test can pass, there is no limit for discovery messages + // because the server supports security None + client.GetEndpoints(profileUris); + } + else + { + var sre = Assert.Throws(() => client.GetEndpoints(profileUris)); + // race condition, if socket closed is detected before the error was returned, + // client may report channel closed instead of security policy rejected + if (StatusCodes.BadSecureChannelClosed == sre.StatusCode) + { + Assert.Inconclusive($"Unexpected Status: {sre}"); + } + Assert.AreEqual((StatusCode)StatusCodes.BadSecurityPolicyRejected, (StatusCode)sre.StatusCode, "Unexpected Status: {0}", sre); } - Assert.AreEqual((StatusCode)StatusCodes.BadSecurityPolicyRejected, (StatusCode)sre.StatusCode, "Unexpected Status: {0}", sre); } } diff --git a/Tests/Opc.Ua.Client.Tests/ClientTestNoSecurity.cs b/Tests/Opc.Ua.Client.Tests/ClientTestNoSecurity.cs index f95d1eca9..b483140de 100644 --- a/Tests/Opc.Ua.Client.Tests/ClientTestNoSecurity.cs +++ b/Tests/Opc.Ua.Client.Tests/ClientTestNoSecurity.cs @@ -100,6 +100,16 @@ public Task TearDown() } #endregion + /// + /// GetEndpoints on the discovery channel, + /// the oversized message can pass because security None is enabled. + /// + [Test, Order(105)] + public void GetEndpointsOnDiscoveryChannel() + { + _clientTest.GetEndpointsOnDiscoveryChannel(true); + } + [Test, Order(230)] public Task ReconnectJWTSecurityNone() { From 15574d44345ad46bb826d75894b43640977535fd Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 26 Jul 2024 12:19:44 +0300 Subject: [PATCH 71/80] Sequence Header sequenceNumber calculation depends on Security Policy --- .../Stack/Tcp/UaSCBinaryChannel.cs | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs index c709a3df1..0250dc197 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs @@ -153,6 +153,7 @@ private UaSCUaBinaryChannel( m_maxResponseChunkCount = CalculateChunkCount(m_maxResponseMessageSize, TcpMessageLimits.MinBufferSize); CalculateSymmetricKeySizes(); + } #endregion @@ -255,9 +256,39 @@ protected void ChannelStateChanged(TcpChannelState state, ServiceResult reason) /// protected uint GetNewSequenceNumber() { - // TODO transaction safe - m_localSequenceNumber = (uint)m_sequenceNumber; - return Utils.IncrementIdentifier(ref m_sequenceNumber); + long origValue = 0; + long updatedSeqNumber = 0; + do + { + origValue = updatedSeqNumber = Interlocked.Read(ref m_sequenceNumber); + // LegacySequenceNumbers are TRUE for non ECC profiles + // https://reference.opcfoundation.org/Core/Part6/v105/docs/6.7.2.4 + if (!EccUtils.IsEccPolicy(SecurityPolicyUri)) + { + // First number after wrap around shall be less than 1024 + if (origValue == kMaxValueLegacyTrue) + { + updatedSeqNumber = -1; + } + updatedSeqNumber++; + m_localSequenceNumber = (uint)(origValue == kMaxValueLegacyTrue ? kMaxValueLegacyTrue : updatedSeqNumber - 1); + } + else + { + // First number after wrap around shall be 0 + if (origValue == kMaxValueLegacyFalse) + { + updatedSeqNumber = -1; + } + updatedSeqNumber++; + m_localSequenceNumber = (uint)(origValue == kMaxValueLegacyTrue ? kMaxValueLegacyTrue : updatedSeqNumber - 1); + + } + if (Interlocked.CompareExchange(ref m_sequenceNumber, updatedSeqNumber, origValue) == origValue) + { + return (uint)updatedSeqNumber; + } + }while (true); } /// @@ -928,6 +959,11 @@ public void UpdateLastActiveTime() private int m_lastActiveTickCount; #endregion + + #region Constants + private const uint kMaxValueLegacyTrue = TcpMessageLimits.MinSequenceNumber; + private const uint kMaxValueLegacyFalse = UInt32.MaxValue; + #endregion } /// From 37c44f7b2f326daddc9fb63678c7c6db6a3c4a8d Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Wed, 31 Jul 2024 19:37:40 +0300 Subject: [PATCH 72/80] Removed Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj.bak --- .../Opc.Ua.Configuration.csproj.bak | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj.bak diff --git a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj.bak b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj.bak deleted file mode 100644 index 6110a7e75..000000000 --- a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj.bak +++ /dev/null @@ -1,59 +0,0 @@ - - - - Opc.Ua.Configuration - $(LibTargetFrameworks) - OPCFoundation.NetStandard.Opc.Ua.Configuration - Opc.Ua.Configuration - OPC UA Configuration Class Library - true - MIT - - true - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(DefineConstants);ECC_SUPPORT - - - - $(PackageId).Debug - - - - - - - - - - - - - - $(DefineConstants);SIGNASSEMBLY - - - - - From 9b4ecf62465f40017fd4d635e4a408ce684c587e Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Mon, 5 Aug 2024 06:33:26 +0200 Subject: [PATCH 73/80] improve a comment --- Libraries/Opc.Ua.Client/Session/Session.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session/Session.cs b/Libraries/Opc.Ua.Client/Session/Session.cs index d9eb9842f..81db4e01b 100644 --- a/Libraries/Opc.Ua.Client/Session/Session.cs +++ b/Libraries/Opc.Ua.Client/Session/Session.cs @@ -6355,8 +6355,8 @@ private static void UpdateLatestSequenceNumberToSend(ref uint latestSequenceNumb } /// - /// Creates a request header with additional parameters for the ecc user token security policy. - /// Returns null if additional header is not needed. + /// Creates a request header with additional parameters + /// for the ecc user token security policy, if needed. /// private RequestHeader CreateRequestHeaderPerUserTokenPolicy(string identityTokenSecurityPolicyUri, string endpointSecurityPolicyUri) { From 1a478487335c7f3e9e5343b4bdfed9dc0e02a16b Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Mon, 5 Aug 2024 08:52:26 +0200 Subject: [PATCH 74/80] add a review comment --- .../Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 14fa3130d..eeda79966 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -448,6 +448,7 @@ private ServiceResult UpdateCertificate( { // verify cert with issuer chain CertificateValidator certValidator = new CertificateValidator(); +// TODO: why? certValidator.MinimumCertificateKeySize = 1024; CertificateTrustList issuerStore = new CertificateTrustList(); CertificateIdentifierCollection issuerCollection = new CertificateIdentifierCollection(); From 88e1ac6ab9acfecf404512229b0f11ed72a0cdb3 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Mon, 5 Aug 2024 11:12:46 +0200 Subject: [PATCH 75/80] fix a comment --- Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs b/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs index 21231f13f..882d7f932 100644 --- a/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs +++ b/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs @@ -154,7 +154,7 @@ public static SessionConfiguration Create(Stream stream) #if ECC_SUPPORT /// - /// The last server ecc ephemeral key received. + /// The user identity token policy which was used to create the session. /// [DataMember(IsRequired = true, Order = 90)] public string UserIdentityTokenPolicy { get; set; } From f6f6ca315c01297b676e44daae80010f0b07ac97 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 30 Aug 2024 17:21:46 +0300 Subject: [PATCH 76/80] Unified SessionConfiguration --- Libraries/Opc.Ua.Client/Session/Session.cs | 13 ++++----- .../Session/SessionConfiguration.cs | 23 ++++----------- .../Security/Certificates/Nonce.cs | 29 +++++++++++-------- 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session/Session.cs b/Libraries/Opc.Ua.Client/Session/Session.cs index 91a31d87b..862264662 100644 --- a/Libraries/Opc.Ua.Client/Session/Session.cs +++ b/Libraries/Opc.Ua.Client/Session/Session.cs @@ -1384,11 +1384,9 @@ public bool ApplySessionConfiguration(SessionConfiguration sessionConfiguration) m_serverCertificate = serverCertificate != null ? new X509Certificate2(serverCertificate) : null; m_identity = sessionConfiguration.Identity; m_checkDomain = sessionConfiguration.CheckDomain; - m_serverNonce = sessionConfiguration.ServerNonce; -# if ECC_SUPPORT + m_serverNonce = sessionConfiguration.ServerNonce.Data; m_userTokenSecurityPolicyUri = sessionConfiguration.UserIdentityTokenPolicy; m_eccServerEphemeralKey = sessionConfiguration.ServerEccEphemeralKey; -# endif SessionCreated(sessionConfiguration.SessionId, sessionConfiguration.AuthenticationToken); return true; @@ -1397,11 +1395,10 @@ public bool ApplySessionConfiguration(SessionConfiguration sessionConfiguration) /// public SessionConfiguration SaveSessionConfiguration(Stream stream = null) { -#if ECC_SUPPORT - var sessionConfiguration = new SessionConfiguration(this, m_serverNonce, m_userTokenSecurityPolicyUri, m_eccServerEphemeralKey, AuthenticationToken); -#else - var sessionConfiguration = new SessionConfiguration(this, m_serverNonce, AuthenticationToken); -#endif + + Nonce serverNonce = Nonce.CreateNonce(m_endpoint.Description?.SecurityPolicyUri, m_serverNonce); + + var sessionConfiguration = new SessionConfiguration(this, serverNonce, m_userTokenSecurityPolicyUri, m_eccServerEphemeralKey, AuthenticationToken); if (stream != null) { diff --git a/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs b/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs index 882d7f932..7b045a0fa 100644 --- a/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs +++ b/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs @@ -45,18 +45,14 @@ namespace Opc.Ua.Client [KnownType(typeof(X509IdentityToken))] [KnownType(typeof(IssuedIdentityToken))] [KnownType(typeof(UserIdentity))] -#if ECC_SUPPORT - [KnownType(typeof(ECParameters))] -#endif public class SessionConfiguration { -#if ECC_SUPPORT /// /// Creates a session configuration /// public SessionConfiguration( ISession session, - byte[] serverNonce, + Nonce serverNonce, string userIdentityTokenPolicy, Nonce eccServerEphemeralKey, NodeId authenthicationToken) @@ -72,22 +68,15 @@ public SessionConfiguration( ServerEccEphemeralKey = eccServerEphemeralKey; UserIdentityTokenPolicy = userIdentityTokenPolicy; } -#else + /// /// Creates a session configuration /// + [Obsolete("Use SessionConfiguration(ISession session, Nonce serverNonce, string userIdentityTokenPolicy, Nonce eccServerEphemeralKey, NodeId authenthicationToken)")] public SessionConfiguration(ISession session, byte[] serverNonce, NodeId authenthicationToken) + :this(session, Nonce.CreateNonce("RSA-only", serverNonce), null, null, authenthicationToken) { - Timestamp = DateTime.UtcNow; - SessionName = session.SessionName; - SessionId = session.SessionId; - AuthenticationToken = authenthicationToken; - Identity = session.Identity; - ConfiguredEndpoint = session.ConfiguredEndpoint; - CheckDomain = session.CheckDomain; - ServerNonce = serverNonce; } -#endif /// /// Creates the session configuration from a stream. @@ -150,9 +139,8 @@ public static SessionConfiguration Create(Stream stream) /// The last server nonce received. /// [DataMember(IsRequired = true, Order = 80)] - public byte[] ServerNonce { get; set; } + public Nonce ServerNonce { get; set; } -#if ECC_SUPPORT /// /// The user identity token policy which was used to create the session. /// @@ -164,7 +152,6 @@ public static SessionConfiguration Create(Stream stream) /// [DataMember(IsRequired = false, Order = 100)] public Nonce ServerEccEphemeralKey { get; set; } -#endif } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs index a892c3528..605c71296 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs @@ -581,16 +581,18 @@ protected Nonce(SerializationInfo info, StreamingContext context) var curveName = info.GetString("CurveName"); #if ECC_SUPPORT - var ecParams = new ECParameters { - Curve = ECCurve.CreateFromFriendlyName(curveName), - Q = new ECPoint { - X = (byte[])info.GetValue("QX", typeof(byte[])), - Y = (byte[])info.GetValue("QY", typeof(byte[])), - } - }; - m_ecdh = ECDiffieHellman.Create(ecParams); + if (curveName != null) + { + var ecParams = new ECParameters { + Curve = ECCurve.CreateFromFriendlyName(curveName), + Q = new ECPoint { + X = (byte[])info.GetValue("QX", typeof(byte[])), + Y = (byte[])info.GetValue("QY", typeof(byte[])), + } + }; + m_ecdh = ECDiffieHellman.Create(ecParams); + } #endif - Data = (byte[])info.GetValue("Data", typeof(byte[])); } #endregion @@ -659,11 +661,14 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) info.AddValue("QX", ecParams.Q.X); info.AddValue("QY", ecParams.Q.Y); } -#endif - if (Data != null) + else { - info.AddValue("Data", Data); + info.AddValue("CurveName", null); + info.AddValue("QX", null); + info.AddValue("QY", null); } +#endif + info.AddValue("Data", Data); } #endregion From d79719fcdcd21def0bf64a6bb93c753bc7666b04 Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 30 Aug 2024 17:24:47 +0300 Subject: [PATCH 77/80] Unified UserIdentityToken Encrypt/Decrypt --- Libraries/Opc.Ua.Client/Session/Session.cs | 20 +++---------------- .../Opc.Ua.Client/Session/SessionAsync.cs | 5 ----- .../Stack/Types/UserIdentityToken.cs | 12 ----------- 3 files changed, 3 insertions(+), 34 deletions(-) diff --git a/Libraries/Opc.Ua.Client/Session/Session.cs b/Libraries/Opc.Ua.Client/Session/Session.cs index 862264662..edba90ff2 100644 --- a/Libraries/Opc.Ua.Client/Session/Session.cs +++ b/Libraries/Opc.Ua.Client/Session/Session.cs @@ -2456,7 +2456,6 @@ public void Open( SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. -#if ECC_SUPPORT identityToken.Encrypt( serverCertificate, serverNonce, @@ -2465,9 +2464,7 @@ public void Open( m_instanceCertificate, m_instanceCertificateChain, m_endpoint.Description.SecurityMode != MessageSecurityMode.None); -#else - identityToken.Encrypt(serverCertificate, serverNonce, securityPolicyUri); -#endif + // send the software certificates assigned to the client. SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); @@ -2641,8 +2638,6 @@ public void UpdateSession(IUserIdentity identity, StringCollection preferredLoca userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. -#if ECC_SUPPORT - // TODO: build helper function identityToken.Encrypt( m_serverCertificate, serverNonce, @@ -2651,9 +2646,7 @@ public void UpdateSession(IUserIdentity identity, StringCollection preferredLoca m_instanceCertificate, m_instanceCertificateChain, m_endpoint.Description.SecurityMode != MessageSecurityMode.None); -#else - identityToken.Encrypt(m_serverCertificate, serverNonce, securityPolicyUri); -#endif + // send the software certificates assigned to the client. SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); @@ -5679,7 +5672,6 @@ ITransportChannel transportChannel SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. -#if ECC_SUPPORT identityToken.Encrypt( m_serverCertificate, m_serverNonce, @@ -5688,9 +5680,6 @@ ITransportChannel transportChannel m_instanceCertificate, m_instanceCertificateChain, m_endpoint.Description.SecurityMode != MessageSecurityMode.None); -#else - identityToken.Encrypt(m_serverCertificate, m_serverNonce, securityPolicyUri); -#endif // send the software certificates assigned to the client. SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); @@ -6481,9 +6470,8 @@ protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHe StatusCodes.BadDecodingError, "Could not verify signature on ECDHKey. User authentication not possible."); } -#if ECC_SUPPORT + m_eccServerEphemeralKey = Nonce.CreateNonce(m_userTokenSecurityPolicyUri, key.PublicKey); -#endif } } } @@ -6579,9 +6567,7 @@ protected virtual void ProcessResponseAdditionalHeader(ResponseHeader responseHe private int m_maxPublishRequestCount; private LinkedList m_outstandingRequests; private string m_userTokenSecurityPolicyUri; -#if ECC_SUPPORT private Nonce m_eccServerEphemeralKey; -#endif private readonly EndpointDescriptionCollection m_discoveryServerEndpoints; private readonly StringCollection m_discoveryProfileUris; private uint m_serverMaxContinuationPointsPerBrowse = 0; diff --git a/Libraries/Opc.Ua.Client/Session/SessionAsync.cs b/Libraries/Opc.Ua.Client/Session/SessionAsync.cs index 6290e968d..70743ba1a 100644 --- a/Libraries/Opc.Ua.Client/Session/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/Session/SessionAsync.cs @@ -239,8 +239,6 @@ public async Task OpenAsync( SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); // encrypt token. - //identityToken.Encrypt(serverCertificate, serverNonce, securityPolicyUri); -#if ECC_SUPPORT identityToken.Encrypt( serverCertificate, serverNonce, @@ -249,9 +247,6 @@ public async Task OpenAsync( m_instanceCertificate, m_instanceCertificateChain, m_endpoint.Description.SecurityMode != MessageSecurityMode.None); -#else - identityToken.Encrypt(serverCertificate, serverNonce, securityPolicyUri); -#endif // send the software certificates assigned to the client. SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); diff --git a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs index 4eff4b590..ce9c2fca7 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs @@ -26,9 +26,7 @@ public partial class UserIdentityToken /// /// Encrypts the token (implemented by the subclass). /// -#if ECC_SUPPORT [Obsolete("Use Encrypt(X509Certificate2, byte[], string securityPolicyUri, Nonce, X509Certificate2, X509Certificate2Collection, bool) ")] -#endif public virtual void Encrypt(X509Certificate2 certificate, byte[] receiverNonce, string securityPolicyUri) { } @@ -36,9 +34,7 @@ public virtual void Encrypt(X509Certificate2 certificate, byte[] receiverNonce, /// /// Decrypts the token (implemented by the subclass). /// -#if ECC_SUPPORT [Obsolete("Use Decrypt(X509Certificate2, Nonce, string, Nonce, X509Certificate2, X509Certificate2Collection, CertificateValidator) ")] -#endif public virtual void Decrypt(X509Certificate2 certificate, byte[] receiverNonce, string securityPolicyUri) { } @@ -109,9 +105,7 @@ public string DecryptedPassword /// /// Encrypts the DecryptedPassword using the EncryptionAlgorithm and places the result in Password /// -#if ECC_SUPPORT [Obsolete("Use Encrypt(X509Certificate2, byte[], string securityPolicyUri, Nonce, X509Certificate2, X509Certificate2Collection, bool)")] -#endif public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { Encrypt(certificate, senderNonce, securityPolicyUri, null); @@ -120,9 +114,7 @@ public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, s /// /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword /// -#if ECC_SUPPORT [Obsolete("Use Decrypt(X509Certificate2, Nonce, string, Nonce, X509Certificate2, X509Certificate2Collection, CertificateValidator) ")] -#endif public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { Decrypt(certificate, Nonce.CreateNonce(securityPolicyUri, senderNonce), securityPolicyUri); @@ -428,9 +420,7 @@ public byte[] DecryptedTokenData /// /// Encrypts the DecryptedTokenData using the EncryptionAlgorithm and places the result in Password /// -#if ECC_SUPPORT [Obsolete("Use Encrypt(X509Certificate2, byte[], string securityPolicyUri, Nonce, X509Certificate2, X509Certificate2Collection, bool)")] -#endif public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { Encrypt(certificate, senderNonce, securityPolicyUri, null); @@ -439,9 +429,7 @@ public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, s /// /// Decrypts the Password using the EncryptionAlgorithm and places the result in DecryptedPassword /// -#if ECC_SUPPORT [Obsolete("Use Decrypt(X509Certificate2, Nonce, string, Nonce, X509Certificate2, X509Certificate2Collection, CertificateValidator) ")] -#endif public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, string securityPolicyUri) { Decrypt(certificate, Nonce.CreateNonce(securityPolicyUri, senderNonce), securityPolicyUri); From 7d1da4165cef6c6a9f6ea722478d15736bebe78c Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Tue, 10 Sep 2024 16:15:09 +0300 Subject: [PATCH 78/80] Removed MinimumECCertificateKeySize --- .../ApplicationConfigurationBuilder.cs | 9 ---- .../IApplicationConfigurationBuilder.cs | 7 --- .../Schema/ApplicationConfiguration.cs | 15 ------- .../Certificates/CertificateFactory.cs | 8 ---- .../Certificates/CertificateIdentifier.cs | 19 +++----- .../Certificates/CertificateValidator.cs | 44 +------------------ 6 files changed, 8 insertions(+), 94 deletions(-) diff --git a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs index 037765cdd..056f3c492 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs @@ -505,14 +505,6 @@ public IApplicationConfigurationBuilderSecurityOptions SetMinimumCertificateKeyS return this; } - /// - public IApplicationConfigurationBuilderSecurityOptions SetMinimumECCertificateKeySize(ushort keySize) - { - ApplicationConfiguration.SecurityConfiguration.MinimumECCertificateKeySize = keySize; - return this; - } - - /// public IApplicationConfigurationBuilderSecurityOptions AddCertificatePasswordProvider(ICertificatePasswordProvider certificatePasswordProvider) { @@ -1171,7 +1163,6 @@ private void SetSecureDefaults(SecurityConfiguration securityConfiguration) securityConfiguration.SuppressNonceValidationErrors = false; securityConfiguration.SendCertificateChain = true; securityConfiguration.MinimumCertificateKeySize = CertificateFactory.DefaultKeySize; - securityConfiguration.MinimumECCertificateKeySize = CertificateFactory.DefaultECCKeySize; } /// diff --git a/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs index 43ce0f9e1..19ca5d060 100644 --- a/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs @@ -553,13 +553,6 @@ public interface IApplicationConfigurationBuilderSecurityOptions : /// The minimum RSA key size to accept. IApplicationConfigurationBuilderSecurityOptions SetMinimumCertificateKeySize(ushort keySize); - /// - /// The minimum ECDSA key size to accept. - /// By default the key size is set to . - /// - /// The minimum ECDSA key size to accept. - IApplicationConfigurationBuilderSecurityOptions SetMinimumECCertificateKeySize(ushort keySize); - /// /// Add a certificate password provider. /// diff --git a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs index 70b347554..2995f31eb 100644 --- a/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs +++ b/Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs @@ -1002,20 +1002,6 @@ public ushort MinimumCertificateKeySize set { m_minCertificateKeySize = value; } } - /// - /// Gets or sets a value indicating which minimum certificate key strength is accepted for certificates with a ECDSA signature.. - /// The value is ignored for certificates with a RSA signature. - /// - /// - /// This value can be set to 256 or 448 by servers - /// - [DataMember(IsRequired = false, EmitDefaultValue = false, Order = 125)] - public ushort MinimumECCertificateKeySize - { - get { return m_minECCertificateKeySize; } - set { m_minECCertificateKeySize = value; } - } - /// /// Gets or sets a value indicating whether the Validator skips the full chain validation /// for already validated or accepted certificates. @@ -1190,7 +1176,6 @@ public bool IsDeprecatedConfiguration private bool m_rejectSHA1SignedCertificates; private bool m_rejectUnknownRevocationStatus; private ushort m_minCertificateKeySize; - private ushort m_minECCertificateKeySize; private bool m_useValidatedCertificates; private bool m_addAppCertToTrustedStore; private bool m_sendCertificateChain; diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs index 8cc38d73b..5be0a8afa 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateFactory.cs @@ -36,14 +36,6 @@ public static class CertificateFactory /// public static readonly ushort DefaultKeySize = 2048; - /// - /// The default key size for ECC certificates in bits. - /// - /// - /// Supported values are 256, 384 or 521. - /// - public static readonly ushort DefaultECCKeySize = 256; - /// /// The default hash size for RSA certificates in bits. /// diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index 529bf9336..6c0f63bce 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -511,21 +511,16 @@ public ICertificateStore OpenStore() /// public ushort GetMinKeySize(SecurityConfiguration securityConfiguration) { - if (CertificateType == ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType || - CertificateType == ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType || - CertificateType == ObjectTypeIds.EccCurve25519ApplicationCertificateType || - CertificateType == ObjectTypeIds.EccCurve448ApplicationCertificateType || - CertificateType == ObjectTypeIds.EccNistP256ApplicationCertificateType || - CertificateType == ObjectTypeIds.EccNistP384ApplicationCertificateType) + if (CertificateType == ObjectTypeIds.RsaMinApplicationCertificateType || + CertificateType == ObjectTypeIds.RsaSha256ApplicationCertificateType || + securityConfiguration.IsDeprecatedConfiguration) // Deprecated configurations are implicitly RSA { - return securityConfiguration.MinimumECCertificateKeySize; + return securityConfiguration.MinimumCertificateKeySize; } - else if ( - CertificateType == ObjectTypeIds.RsaMinApplicationCertificateType || - CertificateType == ObjectTypeIds.RsaSha256ApplicationCertificateType || - securityConfiguration.IsDeprecatedConfiguration) // Deprecated configurations are implicitly RSA + else { - return securityConfiguration.MinimumCertificateKeySize; + // non RSA + return 0; } throw new ArgumentException("Certificate type is unknown"); diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 759dd23c9..509d0347c 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -216,10 +216,6 @@ public virtual async Task Update(SecurityConfiguration configuration) { m_minimumCertificateKeySize = configuration.MinimumCertificateKeySize; } - if ((m_protectFlags & ProtectFlags.MinimumECCertificateKeySize) == 0) - { - m_minimumECCertificateKeySize = configuration.MinimumECCertificateKeySize; - } if ((m_protectFlags & ProtectFlags.UseValidatedCertificates) == 0) { m_useValidatedCertificates = configuration.UseValidatedCertificates; @@ -423,33 +419,6 @@ public ushort MinimumCertificateKeySize } } - /// - /// The minimum size of an Eliptic Curve certificate key to be trusted. - /// - public ushort MinimumECCertificateKeySize - { - get => m_minimumECCertificateKeySize; - set - { - try - { - m_semaphore.Wait(); - - m_protectFlags |= ProtectFlags.MinimumECCertificateKeySize; - if (m_minimumECCertificateKeySize != value) - { - m_minimumECCertificateKeySize = value; - InternalResetValidatedCertificates(); - } - - } - finally - { - m_semaphore.Release(); - } - } - } - /// /// Opt-In to use the already validated certificates for validation. /// @@ -1419,15 +1388,6 @@ protected virtual async Task InternalValidateAsync(X509Certificate2Collection ce null, null, "Certificate doesn't meet minimum key length requirement.", null, sresult); } } - else - { - // check if curve type is secure enough for profile - if (!IsECSecureForProfile(certificate, m_minimumECCertificateKeySize)) - { - sresult = new ServiceResult(StatusCodes.BadCertificatePolicyCheckFailed, - null, null, "Certificate doesn't meet minimum security level requirement.", null, sresult); - } - } if (issuedByCA && chainIncomplete) { @@ -1902,8 +1862,7 @@ private enum ProtectFlags RejectSHA1SignedCertificates = 2, RejectUnknownRevocationStatus = 4, MinimumCertificateKeySize = 8, - MinimumECCertificateKeySize = 16, - UseValidatedCertificates = 32 + UseValidatedCertificates = 16 }; #endregion @@ -1924,7 +1883,6 @@ private enum ProtectFlags private bool m_rejectSHA1SignedCertificates; private bool m_rejectUnknownRevocationStatus; private ushort m_minimumCertificateKeySize; - private ushort m_minimumECCertificateKeySize; private bool m_useValidatedCertificates; #endregion From 91f68529cf729cefda321d1272a80109316ea90f Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Mon, 30 Sep 2024 18:31:39 +0300 Subject: [PATCH 79/80] Initial sequenceNumber value depends on policy; VerifySequenceNumber takes policy into account --- .../Stack/Tcp/UaSCBinaryChannel.cs | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs index ff6f3ff36..6da293416 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs @@ -248,7 +248,7 @@ protected void ChannelStateChanged(TcpChannelState state, ServiceResult reason) /// /// Returns a new sequence number. /// - protected uint GetNewSequenceNumber() + protected uint GetNewSequenceNumber() { long origValue = 0; long updatedSeqNumber = 0; @@ -260,23 +260,26 @@ protected uint GetNewSequenceNumber() if (!EccUtils.IsEccPolicy(SecurityPolicyUri)) { // First number after wrap around shall be less than 1024 - if (origValue == kMaxValueLegacyTrue) + // 1 for legaccy reasons + if ((origValue == kMaxValueLegacyTrue) || (origValue == -1)) { - updatedSeqNumber = -1; + updatedSeqNumber = 0; } updatedSeqNumber++; - m_localSequenceNumber = (uint)(origValue == kMaxValueLegacyTrue ? kMaxValueLegacyTrue : updatedSeqNumber - 1); } else { - // First number after wrap around shall be 0 - if (origValue == kMaxValueLegacyFalse) + // First number after wrap around and as initial value shall be 0 + if ((origValue == kMaxValueLegacyFalse) || (origValue == -1)) { - updatedSeqNumber = -1; + updatedSeqNumber = 0; + m_localSequenceNumber = 0; + } + else + { + updatedSeqNumber++; + m_localSequenceNumber = (uint)updatedSeqNumber; } - updatedSeqNumber++; - m_localSequenceNumber = (uint)(origValue == kMaxValueLegacyTrue ? kMaxValueLegacyTrue : updatedSeqNumber - 1); - } if (Interlocked.CompareExchange(ref m_sequenceNumber, updatedSeqNumber, origValue) == origValue) { @@ -298,6 +301,17 @@ protected void ResetSequenceNumber(uint sequenceNumber) /// protected bool VerifySequenceNumber(uint sequenceNumber, string context) { + + // Accept the first sequence number depending on security policy + if (m_firstReceivedSequenceNumber && + (!EccUtils.IsEccPolicy(SecurityPolicyUri) || + (EccUtils.IsEccPolicy(SecurityPolicyUri) && (sequenceNumber == 0) ))) + { + m_remoteSequenceNumber = sequenceNumber; + m_firstReceivedSequenceNumber = false; + return true; + } + // everything ok if new number is greater. if (sequenceNumber > m_remoteSequenceNumber) { @@ -308,8 +322,10 @@ protected bool VerifySequenceNumber(uint sequenceNumber, string context) // check for a valid rollover. if (m_remoteSequenceNumber > TcpMessageLimits.MinSequenceNumber && sequenceNumber < TcpMessageLimits.MaxRolloverSequenceNumber) { - // only one rollover per token is allowed. - if (!m_sequenceRollover) + // only one rollover per token is allowed and with valid values depending on security policy + if (!m_sequenceRollover && + (!EccUtils.IsEccPolicy(SecurityPolicyUri) || + (EccUtils.IsEccPolicy(SecurityPolicyUri) && (sequenceNumber == 0) ))) { m_sequenceRollover = true; m_remoteSequenceNumber = sequenceNumber; @@ -942,10 +958,11 @@ public void UpdateLastActiveTime() private int m_state; private uint m_channelId; private string m_globalChannelId; - private long m_sequenceNumber; + private long m_sequenceNumber = -1; private uint m_localSequenceNumber; private uint m_remoteSequenceNumber; private bool m_sequenceRollover; + private bool m_firstReceivedSequenceNumber = true; private uint m_partialRequestId; private BufferCollection m_partialMessageChunks; From 69ab50aeb7c64636c5e241f1cd1134171351e53c Mon Sep 17 00:00:00 2001 From: mrsuciu Date: Fri, 4 Oct 2024 18:25:59 +0300 Subject: [PATCH 80/80] Simplify GetNewSequenceNumber --- .../Quickstarts.ReferenceServer.Config.xml | 1 - .../X509Certificate/X509PfxUtils.cs | 2 +- .../Stack/Tcp/UaSCBinaryChannel.Symmetric.cs | 7 ++- .../Stack/Tcp/UaSCBinaryChannel.cs | 60 +++++++++---------- 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml index 628148fd9..c6e139f03 100644 --- a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml +++ b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml @@ -79,7 +79,6 @@ true true 2048 - 256 false true diff --git a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs index 6a4e67b6c..4edf156f4 100644 --- a/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs +++ b/Libraries/Opc.Ua.Security.Certificates/X509Certificate/X509PfxUtils.cs @@ -64,7 +64,7 @@ private static X509KeyUsageFlags GetKeyUsage(X509Certificate2 cert) } /// - /// Verify RSA key pair of two certificates. + /// Verify key pair of two certificates. /// public static bool VerifyKeyPair( X509Certificate2 certWithPublicKey, diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs index fc08b2c52..ffa08af7a 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs @@ -901,11 +901,12 @@ protected void Encrypt(ChannelToken token, ArraySegment dataToEncrypt, boo { if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { - SymmetricEncryptWithChaCha20Poly1305(token, m_localSequenceNumber, dataToEncrypt, useClientKeys); + // narowing conversion can safely be done on m_localSequenceNumber + SymmetricEncryptWithChaCha20Poly1305(token, (uint)m_localSequenceNumber, dataToEncrypt, useClientKeys); break; } - - SymmetricSignWithPoly1305(token, m_localSequenceNumber, dataToEncrypt, useClientKeys); + // narowing conversion can safely be done on m_localSequenceNumber + SymmetricSignWithPoly1305(token, (uint)m_localSequenceNumber, dataToEncrypt, useClientKeys); break; } #endif diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs index 6da293416..d6aea6fc4 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs @@ -250,44 +250,40 @@ protected void ChannelStateChanged(TcpChannelState state, ServiceResult reason) /// protected uint GetNewSequenceNumber() { - long origValue = 0; - long updatedSeqNumber = 0; - do - { - origValue = updatedSeqNumber = Interlocked.Read(ref m_sequenceNumber); - // LegacySequenceNumbers are TRUE for non ECC profiles - // https://reference.opcfoundation.org/Core/Part6/v105/docs/6.7.2.4 - if (!EccUtils.IsEccPolicy(SecurityPolicyUri)) + bool isLegacy = !EccUtils.IsEccPolicy(SecurityPolicyUri); + + long newSeqNumber = Interlocked.Increment(ref m_sequenceNumber); + bool maxValueOverflow = isLegacy ? newSeqNumber > kMaxValueLegacyTrue : newSeqNumber > kMaxValueLegacyFalse; + + // LegacySequenceNumbers are TRUE for non ECC profiles + // https://reference.opcfoundation.org/Core/Part6/v105/docs/6.7.2.4 + if (isLegacy) + { + if (maxValueOverflow) { // First number after wrap around shall be less than 1024 // 1 for legaccy reasons - if ((origValue == kMaxValueLegacyTrue) || (origValue == -1)) - { - updatedSeqNumber = 0; - } - updatedSeqNumber++; + Interlocked.Exchange(ref m_sequenceNumber, 1); + return 1; } - else + return (uint)newSeqNumber; + } + else + { + uint retVal = (uint)newSeqNumber - 1; + if (maxValueOverflow) { // First number after wrap around and as initial value shall be 0 - if ((origValue == kMaxValueLegacyFalse) || (origValue == -1)) - { - updatedSeqNumber = 0; - m_localSequenceNumber = 0; - } - else - { - updatedSeqNumber++; - m_localSequenceNumber = (uint)updatedSeqNumber; - } + Interlocked.Exchange(ref m_sequenceNumber, 0); + Interlocked.Exchange(ref m_localSequenceNumber, 0); + return retVal; } - if (Interlocked.CompareExchange(ref m_sequenceNumber, updatedSeqNumber, origValue) == origValue) - { - return (uint)updatedSeqNumber; - } - }while (true); + Interlocked.Exchange(ref m_localSequenceNumber, retVal); + return retVal; + } } - + + /// /// Resets the sequence number after a connect. /// @@ -958,8 +954,8 @@ public void UpdateLastActiveTime() private int m_state; private uint m_channelId; private string m_globalChannelId; - private long m_sequenceNumber = -1; - private uint m_localSequenceNumber; + private long m_sequenceNumber; + private long m_localSequenceNumber; private uint m_remoteSequenceNumber; private bool m_sequenceRollover; private bool m_firstReceivedSequenceNumber = true;