From 88878e6226b8ae634a3d10ad868ae87b96496d26 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sun, 27 Oct 2024 11:35:21 +0100 Subject: [PATCH 1/3] update ServerConfiguration Node to support multiple Application Certificates. Trust List Support for User Certificates & Https Certificates --- .../ApplicationInstance.cs | 40 ++---- .../Configuration/ConfigurationNodeManager.cs | 118 ++++++++++++------ .../Security/Certificates/EccUtils.cs | 41 ++++++ .../Security/Certificates/X509Utils.cs | 2 +- 4 files changed, 129 insertions(+), 72 deletions(-) diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs index e9cca9400..b6bd78ffc 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs @@ -908,44 +908,18 @@ private static async Task CreateApplicationInstanceCertificate #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 + ECCurve? curve = EccUtils.GetCurveFromCertificateTypeId(id.CertificateType); + + if(curve == null) { - throw new ServiceResultException(StatusCodes.BadConfigurationError, "The ECC certificate type is not supported."); + throw new ServiceResultException(StatusCodes.BadConfigurationError, "The Ecc certificate type is not supported."); } id.Certificate = builder - .SetECCurve(curve) + .SetECCurve(curve.Value) .CreateForECDsa(); - Utils.LogCertificate("Certificate created for {0}.", id.Certificate, curve.Oid.FriendlyName); + Utils.LogCertificate("Certificate created for {0}.", id.Certificate, curve.Value.Oid.FriendlyName); #endif } @@ -1160,7 +1134,7 @@ private static async Task ApproveMessageAsync(string message, bool silent) return false; } } - #endregion +#endregion #region Private Fields private string m_applicationName; diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 860756bb5..0c9339ba3 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -29,9 +29,12 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; +using Opc.Ua.Security.Certificates; namespace Opc.Ua.Server @@ -80,21 +83,47 @@ ApplicationConfiguration configuration ServerCertificateGroup defaultApplicationGroup = new ServerCertificateGroup { NodeId = Opc.Ua.ObjectIds.ServerConfiguration_CertificateGroups_DefaultApplicationGroup, BrowseName = Opc.Ua.BrowseNames.DefaultApplicationGroup, - CertificateTypes = new NodeId[]{}, + CertificateTypes = new NodeId[] { }, ApplicationCertificates = new CertificateIdentifierCollection(), IssuerStore = new CertificateStoreIdentifier(configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath), TrustedStore = new CertificateStoreIdentifier(configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath) }; + ServerCertificateGroup defaultUserGroup = new ServerCertificateGroup { + NodeId = Opc.Ua.ObjectIds.ServerConfiguration_CertificateGroups_DefaultUserTokenGroup, + BrowseName = Opc.Ua.BrowseNames.DefaultUserTokenGroup, + CertificateTypes = new NodeId[] { }, + ApplicationCertificates = new CertificateIdentifierCollection(), + IssuerStore = new CertificateStoreIdentifier(configuration.SecurityConfiguration.UserIssuerCertificates?.StorePath), + TrustedStore = new CertificateStoreIdentifier(configuration.SecurityConfiguration.TrustedUserCertificates?.StorePath) + }; + + ServerCertificateGroup defaultHttpsGroup = new ServerCertificateGroup { + NodeId = Opc.Ua.ObjectIds.ServerConfiguration_CertificateGroups_DefaultHttpsGroup, + BrowseName = Opc.Ua.BrowseNames.DefaultHttpsGroup, + CertificateTypes = new NodeId[] { }, + ApplicationCertificates = new CertificateIdentifierCollection(), + IssuerStore = new CertificateStoreIdentifier(configuration.SecurityConfiguration.HttpsIssuerCertificates?.StorePath), + TrustedStore = new CertificateStoreIdentifier(configuration.SecurityConfiguration.TrustedHttpsCertificates?.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); + + if(cert.CertificateType == ObjectTypeIds.HttpsCertificateType) + { + defaultHttpsGroup.CertificateTypes = defaultHttpsGroup.CertificateTypes.Concat(new NodeId[] { cert.CertificateType }).ToArray(); + defaultHttpsGroup.ApplicationCertificates.Add(cert); + } } m_certificateGroups.Add(defaultApplicationGroup); + m_certificateGroups.Add(defaultHttpsGroup); + m_certificateGroups.Add(defaultUserGroup); } #endregion @@ -397,7 +426,7 @@ private ServiceResult UpdateCertificate( // 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 => + CertificateIdentifier existingCertIdentifier = certificateGroup.ApplicationCertificates.FirstOrDefault(cert => X509Utils.CompareDistinguishedName(cert.Certificate.Subject, newCert.Subject) && cert.CertificateType == certificateTypeId); @@ -451,8 +480,8 @@ private ServiceResult UpdateCertificate( { // verify cert with issuer chain CertificateValidator certValidator = new CertificateValidator(); -// TODO: why? -// certValidator.MinimumCertificateKeySize = 1024; + // TODO: why? + // certValidator.MinimumCertificateKeySize = 1024; CertificateTrustList issuerStore = new CertificateTrustList(); CertificateIdentifierCollection issuerCollection = new CertificateIdentifierCollection(); foreach (var issuerCert in newIssuerCollection) @@ -480,7 +509,7 @@ private ServiceResult UpdateCertificate( { X509Certificate2 exportableKey; //use the new generated private key if one exists and matches the provided public key - if (certificateGroup.TemporaryApplicationCertificate != null && X509Utils.VerifyRSAKeyPair(newCert, certificateGroup.TemporaryApplicationCertificate)) + if (certificateGroup.TemporaryApplicationCertificate != null && X509Utils.VerifyKeyPair(newCert, certificateGroup.TemporaryApplicationCertificate)) { exportableKey = X509Utils.CreateCopyWithPrivateKey(certificateGroup.TemporaryApplicationCertificate, false); } @@ -604,44 +633,69 @@ private ServiceResult CreateSigningRequest( throw new ArgumentNullException(nameof(subjectName)); } - certificateGroup.TemporaryApplicationCertificate?.Dispose(); certificateGroup.TemporaryApplicationCertificate = null; - // TODO: ECC support for regenerative X509Certificate2 certWithPrivateKey; - if (regeneratePrivateKey && certificateTypeId == ObjectTypeIds.RsaSha256ApplicationCertificateType) + if (regeneratePrivateKey) { - ushort keySize = 0; - using (var publicKey = existingCertIdentifier.Certificate.GetRSAPublicKey()) - { - keySize = (ushort)(publicKey?.KeySize ?? 0); - } + certWithPrivateKey = GenerateTemporaryApplicationCertificate(certificateTypeId, certificateGroup, existingCertIdentifier); + } + else + { + ICertificatePasswordProvider passwordProvider = m_configuration.SecurityConfiguration.CertificatePasswordProvider; + certWithPrivateKey = existingCertIdentifier.LoadPrivateKeyEx(passwordProvider).Result; + } + + Utils.LogCertificate(Utils.TraceMasks.Security, "Create signing request: ", certWithPrivateKey); + certificateRequest = CertificateFactory.CreateSigningRequest(certWithPrivateKey, X509Utils.GetDomainsFromCertificate(certWithPrivateKey)); + + return ServiceResult.Good; + } + + + private X509Certificate2 GenerateTemporaryApplicationCertificate(NodeId certificateTypeId, ServerCertificateGroup certificateGroup, CertificateIdentifier existingCertIdentifier) + { + X509Certificate2 certificate; - certWithPrivateKey = CertificateFactory.CreateCertificate( + ICertificateBuilder certificateBuilder = CertificateFactory.CreateCertificate( m_configuration.ApplicationUri, null, existingCertIdentifier.Certificate.Subject, null) .SetNotBefore(DateTime.Today.AddDays(-1)) - .SetNotAfter(DateTime.Today.AddDays(14)) - .SetRSAKeySize(keySize) - .CreateForRSA(); + .SetNotAfter(DateTime.Today.AddDays(14)); - certificateGroup.TemporaryApplicationCertificate = certWithPrivateKey; + if (certificateTypeId == null || + certificateTypeId == ObjectTypeIds.ApplicationCertificateType || + certificateTypeId == ObjectTypeIds.RsaMinApplicationCertificateType || + certificateTypeId == ObjectTypeIds.RsaSha256ApplicationCertificateType) + { + certificate = certificateBuilder + .SetRSAKeySize(CertificateFactory.DefaultKeySize) + .CreateForRSA(); } else { - ICertificatePasswordProvider passwordProvider = m_configuration.SecurityConfiguration.CertificatePasswordProvider; - certWithPrivateKey = existingCertIdentifier.LoadPrivateKeyEx(passwordProvider).Result; +#if !ECC_SUPPORT + throw new ServiceResultException(StatusCodes.BadNotSupported, "The Ecc certificate type is not supported."); +#else + ECCurve? curve = EccUtils.GetCurveFromCertificateTypeId(certificateTypeId); + + if (curve == null) + { + throw new ServiceResultException(StatusCodes.BadNotSupported, "The Ecc certificate type is not supported."); + } + certificate = certificateBuilder + .SetECCurve(curve.Value) + .CreateForECDsa(); +#endif } - Utils.LogCertificate(Utils.TraceMasks.Security, "Create signing request: ", certWithPrivateKey); - certificateRequest = CertificateFactory.CreateSigningRequest(certWithPrivateKey, X509Utils.GetDomainsFromCertificate(certWithPrivateKey)); + certificateGroup.TemporaryApplicationCertificate = certificate; - return ServiceResult.Good; + return certificate; } - private ServiceResult ApplyChanges( ISystemContext context, MethodState method, @@ -735,20 +789,8 @@ private ServiceResult GetCertificates( throw new ServiceResultException(StatusCodes.BadInvalidArgument, "Certificate group invalid."); } - NodeId certificateTypeId = certificateGroup.CertificateTypes.FirstOrDefault(); - - //TODO support multiple Application Instance Certificates - if (certificateTypeId != null) - { - certificateTypeIds = new NodeId[1] { certificateTypeId }; - certificates = new byte[1][]; - certificates[0] = certificateGroup.ApplicationCertificates[0].Certificate.GetRawCertData(); - } - else - { - certificateTypeIds = new NodeId[0]; - certificates = Array.Empty(); - } + certificateTypeIds = certificateGroup.CertificateTypes; + certificates = certificateGroup.ApplicationCertificates.Select(s => s.Certificate.RawData).ToArray(); return ServiceResult.Good; } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs index 17d6e9f67..23006c07c 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs @@ -126,6 +126,47 @@ public static NodeId GetEccCertificateTypeId(X509Certificate2 certificate) } } +#if ECC_SUPPORT + /// + /// returns an ECCCurve if there is a matching supported curve for the provided certificate type id. if no supported ECC curve is found null is returned. + /// + /// the application certificatate type node id + /// the ECCCurve, null if certificatate type id has no matching supported ECC curve + public static ECCurve? GetCurveFromCertificateTypeId(NodeId certificateType) + { + ECCurve? curve = null; + + if (certificateType == ObjectTypeIds.EccApplicationCertificateType || + certificateType == ObjectTypeIds.EccNistP256ApplicationCertificateType) + { + curve = ECCurve.NamedCurves.nistP256; + } + else if (certificateType == ObjectTypeIds.EccNistP384ApplicationCertificateType) + { + curve = ECCurve.NamedCurves.nistP384; + } + else if (certificateType == ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType) + { + curve = ECCurve.NamedCurves.brainpoolP256r1; + } + else if (certificateType == ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType) + { + curve = ECCurve.NamedCurves.brainpoolP384r1; + } +#if CURVE25519 + else if (certificateType == ObjectTypeIds.EccCurve25519ApplicationCertificateType) + { + curve = default(ECCurve); + } + else if (certificateType == ObjectTypeIds.EccCurve448ApplicationCertificateType) + { + curve = default(ECCurve); + } +#endif + return curve; + } +#endif + /// /// Returns the signature algorithm for the specified certificate. /// diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs index 03508118e..21ace552b 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs @@ -510,7 +510,7 @@ public static string GetECDsaQualifier(X509Certificate2 certificate) } /// - /// Verify RSA key pair of two certificates. + /// Verify RSA/ECDsa key pair of two certificates. /// public static bool VerifyKeyPair( X509Certificate2 certWithPublicKey, From 780c78f5a6b0082103d21a99c850ea2ac891542c Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sun, 3 Nov 2024 19:25:40 +0100 Subject: [PATCH 2/3] GDS ready for ECC Step1 --- .../ApplicationsNodeManager.cs | 36 +++- .../CertificateGroup.cs | 161 +++++++++++++----- .../Opc.Ua.Gds.Server.Common.csproj | 11 ++ .../Configuration/ConfigurationNodeManager.cs | 2 +- 4 files changed, 165 insertions(+), 45 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index bd3ca1332..6227c1b4f 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -346,7 +346,19 @@ public override void CreateAddressSpace(IDictionary> e { Ua.ObjectTypeIds.UserCredentialCertificateType, nameof(Ua.ObjectTypeIds.UserCredentialCertificateType) }, { Ua.ObjectTypeIds.ApplicationCertificateType, nameof(Ua.ObjectTypeIds.ApplicationCertificateType) }, { Ua.ObjectTypeIds.RsaMinApplicationCertificateType, nameof(Ua.ObjectTypeIds.RsaMinApplicationCertificateType) }, - { Ua.ObjectTypeIds.RsaSha256ApplicationCertificateType, nameof(Ua.ObjectTypeIds.RsaSha256ApplicationCertificateType) } + { Ua.ObjectTypeIds.RsaSha256ApplicationCertificateType, nameof(Ua.ObjectTypeIds.RsaSha256ApplicationCertificateType) }, + // ECC / V1.05 +#if ECC_SUPPORT + { Ua.ObjectTypeIds.EccApplicationCertificateType, nameof(Ua.ObjectTypeIds.EccApplicationCertificateType) }, + { Ua.ObjectTypeIds.EccNistP256ApplicationCertificateType, nameof(Ua.ObjectTypeIds.EccNistP256ApplicationCertificateType) }, + { Ua.ObjectTypeIds.EccNistP384ApplicationCertificateType, nameof(Ua.ObjectTypeIds.EccNistP384ApplicationCertificateType) }, + { Ua.ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType, nameof(Ua.ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType) }, + { Ua.ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType, nameof(Ua.ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType) }, +#if CURVE25519 + { Ua.ObjectTypeIds.EccCurve25519ApplicationCertificateType, nameof(Ua.ObjectTypeIds.EccCurve25519ApplicationCertificateType) }, + { Ua.ObjectTypeIds.EccCurve448ApplicationCertificateType, nameof(Ua.ObjectTypeIds.EccCurve448ApplicationCertificateType) }, +#endif +#endif }; } @@ -414,7 +426,7 @@ protected override NodeState AddBehaviourToPredefinedNode(ISystemContext context activeNode.CheckRevocationStatus.OnCall = new CheckRevocationStatusMethodStateMethodCallHandler(OnCheckRevocationStatus); activeNode.GetCertificates.OnCall = new GetCertificatesMethodStateMethodCallHandler(OnGetCertificates); - activeNode.CertificateGroups.DefaultApplicationGroup.CertificateTypes.Value = new NodeId[] { Opc.Ua.ObjectTypeIds.RsaSha256ApplicationCertificateType }; + activeNode.CertificateGroups.DefaultApplicationGroup.CertificateTypes.Value = new NodeId[] { Ua.ObjectTypeIds.ApplicationCertificateType }; activeNode.CertificateGroups.DefaultApplicationGroup.TrustList.LastUpdateTime.Value = DateTime.UtcNow; activeNode.CertificateGroups.DefaultApplicationGroup.TrustList.Writable.Value = false; activeNode.CertificateGroups.DefaultApplicationGroup.TrustList.UserWritable.Value = false; @@ -966,7 +978,7 @@ private ServiceResult OnStartNewKeyPairRequest( object[] inputArguments = new object[] { applicationId, certificateGroupId, certificateTypeId, subjectName, domainNames, privateKeyFormat, privateKeyPassword }; Server.ReportCertificateRequestedAuditEvent(context, objectId, method, inputArguments, certificateGroupId, certificateTypeId); - AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); ; + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); var application = m_database.GetApplication(applicationId); @@ -1166,7 +1178,7 @@ private ServiceResult OnFinishRequest( signedCertificate = null; issuerCertificates = null; privateKey = null; - AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); ; + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); var application = m_database.GetApplication(applicationId); if (application == null) @@ -1547,8 +1559,20 @@ protected void SetCertificateGroupNodes(ICertificateGroup certificateGroup) certificateGroup.DefaultTrustList = (TrustListState)FindPredefinedNode(ExpandedNodeId.ToNodeId(Opc.Ua.Gds.ObjectIds.Directory_CertificateGroups_DefaultUserTokenGroup_TrustList, Server.NamespaceUris), typeof(TrustListState)); } else if (Utils.IsEqual(certificateType, Opc.Ua.ObjectTypeIds.ApplicationCertificateType) || - Utils.IsEqual(certificateType, Opc.Ua.ObjectTypeIds.RsaMinApplicationCertificateType) || - Utils.IsEqual(certificateType, Opc.Ua.ObjectTypeIds.RsaSha256ApplicationCertificateType) + Utils.IsEqual(certificateType, Opc.Ua.ObjectTypeIds.RsaMinApplicationCertificateType) || + Utils.IsEqual(certificateType, Opc.Ua.ObjectTypeIds.RsaSha256ApplicationCertificateType) + +#if ECC_SUPPORT + || Utils.IsEqual(certificateType, Ua.ObjectTypeIds.EccApplicationCertificateType) + || Utils.IsEqual(certificateType, Ua.ObjectTypeIds.EccNistP256ApplicationCertificateType) + || Utils.IsEqual(certificateType, Ua.ObjectTypeIds.EccNistP384ApplicationCertificateType) + || Utils.IsEqual(certificateType, Ua.ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType) + || Utils.IsEqual(certificateType, Ua.ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType) +#if CURVE25519 + || Utils.IsEqual(certificateType, Ua.ObjectTypeIds.EccCurve25519ApplicationCertificateType) + || Utils.IsEqual(certificateType, Ua.ObjectTypeIds.EccCurve448ApplicationCertificateType) +#endif +#endif ) { certificateGroup.Id = m_defaultApplicationGroupId; diff --git a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs index 1f1b6553c..1557d95eb 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs @@ -31,10 +31,10 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; -using Opc.Ua.Security; using Opc.Ua.Security.Certificates; namespace Opc.Ua.Gds.Server @@ -152,14 +152,24 @@ public virtual async Task NewKeyPairRequestAsync( if (application.ApplicationNames == null) throw new ArgumentNullException(nameof(application.ApplicationNames)); using (var signingKey = await LoadSigningKeyAsync(Certificate, string.Empty).ConfigureAwait(false)) - using (var certificate = CertificateFactory.CreateCertificate( - application.ApplicationUri, - application.ApplicationNames.Count > 0 ? application.ApplicationNames[0].Text : "ApplicationName", - subjectName, - domainNames) - .SetIssuer(signingKey) - .CreateForRSA()) { + X509Certificate2 certificate; + + ICertificateBuilderIssuer builder = CertificateFactory.CreateCertificate( + application.ApplicationUri, + application.ApplicationNames.Count > 0 ? application.ApplicationNames[0].Text : "ApplicationName", + subjectName, + domainNames) + .SetIssuer(signingKey); +#if ECC_SUPPORT + certificate = TryGetECCCurve(out ECCurve curve) ? + builder.SetECCurve(curve).CreateForECDsa() : + builder.CreateForRSA(); +#else + certificate = builder + .CreateForRSA(); +#endif + byte[] privateKey; if (privateKeyFormat == "PFX") { @@ -173,7 +183,13 @@ public virtual async Task NewKeyPairRequestAsync( { throw new ServiceResultException(StatusCodes.BadInvalidArgument, "Invalid private key format"); } - return new X509Certificate2KeyPair(new X509Certificate2(certificate.RawData), privateKeyFormat, privateKey); + + + var publicKey = new X509Certificate2(certificate.RawData); + + certificate?.Dispose(); + + return new X509Certificate2KeyPair(publicKey, privateKeyFormat, privateKey); } } @@ -289,14 +305,29 @@ public virtual async Task SigningRequestAsync( using (var signingKey = await LoadSigningKeyAsync(Certificate, string.Empty).ConfigureAwait(false)) { X500DistinguishedName subjectName = new X500DistinguishedName(info.Subject.GetEncoded()); - return CertificateBuilder.Create(subjectName) + + X509Certificate2 certificate; + + ICertificateBuilder builder = CertificateBuilder.Create(subjectName) .AddExtension(new X509SubjectAltNameExtension(application.ApplicationUri, domainNames)) .SetNotBefore(yesterday) - .SetLifeTime(Configuration.DefaultCertificateLifetime) - .SetHashAlgorithm(X509Utils.GetRSAHashAlgorithmName(Configuration.DefaultCertificateHashSize)) - .SetIssuer(signingKey) - .SetRSAPublicKey(info.SubjectPublicKeyInfo.GetEncoded()) - .CreateForRSA(); + .SetLifeTime(Configuration.DefaultCertificateLifetime); + +#if ECC_SUPPORT + certificate = TryGetECCCurve(out ECCurve curve) ? + builder.SetIssuer(signingKey).SetECDsaPublicKey(info.SubjectPublicKeyInfo.GetEncoded()).CreateForECDsa() : + builder.SetHashAlgorithm(X509Utils.GetRSAHashAlgorithmName(Configuration.DefaultCertificateHashSize)) + .SetIssuer(signingKey) + .SetRSAPublicKey(info.SubjectPublicKeyInfo.GetEncoded()) + .CreateForRSA(); +#else + certificate = builder.SetHashAlgorithm(X509Utils.GetRSAHashAlgorithmName(Configuration.DefaultCertificateHashSize)) + .SetIssuer(signingKey) + .SetRSAPublicKey(info.SubjectPublicKeyInfo.GetEncoded()) + .CreateForRSA(); +#endif + + return certificate; } } catch (Exception ex) @@ -325,37 +356,51 @@ string subjectName } DateTime yesterday = DateTime.Today.AddDays(-1); - using (X509Certificate2 newCertificate = await CertificateFactory.CreateCertificate(subjectName) + X509Certificate2 certificate; + + ICertificateBuilder builder = CertificateFactory.CreateCertificate(subjectName) .SetNotBefore(yesterday) .SetLifeTime(Configuration.CACertificateLifetime) - .SetHashAlgorithm(X509Utils.GetRSAHashAlgorithmName(Configuration.CACertificateHashSize)) - .SetCAConstraint() - .SetRSAKeySize(Configuration.CACertificateKeySize) - .CreateForRSA() - .AddToStoreAsync(AuthoritiesStore).ConfigureAwait(false)) - { + .SetCAConstraint(); - // save only public key - Certificate = new X509Certificate2(newCertificate.RawData); +#if ECC_SUPPORT + certificate = TryGetECCCurve(out ECCurve curve) ? + builder.SetECCurve(curve).CreateForECDsa() : + builder.SetHashAlgorithm(X509Utils.GetRSAHashAlgorithmName(Configuration.CACertificateHashSize)) + .SetRSAKeySize(Configuration.CACertificateKeySize) + .CreateForRSA(); +#else + certificate = builder.SetHashAlgorithm(X509Utils.GetRSAHashAlgorithmName(Configuration.CACertificateHashSize)) + .SetRSAKeySize(Configuration.CACertificateKeySize) + .CreateForRSA(); +#endif - // initialize revocation list - X509CRL crl = await RevokeCertificateAsync(AuthoritiesStore, newCertificate, null).ConfigureAwait(false); + await certificate.AddToStoreAsync(AuthoritiesStore).ConfigureAwait(false); - //Update TrustedList Store - if (crl != null) - { - // TODO: make CA trust selectable - var certificateStoreIdentifier = new CertificateStoreIdentifier(Configuration.TrustedListPath); - await UpdateAuthorityCertInCertificateStore(certificateStoreIdentifier).ConfigureAwait(false); + // save only public key + Certificate = new X509Certificate2(certificate.RawData); - // Update TrustedIssuerCertificates Store - if (IssuerCertificatesStore != null) - { - await UpdateAuthorityCertInCertificateStore(IssuerCertificatesStore).ConfigureAwait(false); - } + // initialize revocation list + X509CRL crl = await RevokeCertificateAsync(AuthoritiesStore, certificate, null).ConfigureAwait(false); + + //Update TrustedList Store + if (crl != null) + { + // TODO: make CA trust selectable + var certificateStoreIdentifier = new CertificateStoreIdentifier(Configuration.TrustedListPath); + await UpdateAuthorityCertInCertificateStore(certificateStoreIdentifier).ConfigureAwait(false); + + // Update TrustedIssuerCertificates Store + if (IssuerCertificatesStore != null) + { + await UpdateAuthorityCertInCertificateStore(IssuerCertificatesStore).ConfigureAwait(false); } - return Certificate; } + + certificate?.Dispose(); + + return Certificate; + } #endregion @@ -470,6 +515,46 @@ public static async Task RevokeCertificateAsync( #endregion #region Private Methods +#if ECC_SUPPORT + /// + /// GetTheEccCurve of the CertificateGroups CertificateType + /// + /// + /// returns false if RSA CertificateType, true if a ECCurve can be found, else throws Exception + /// + private bool TryGetECCCurve(out ECCurve curve) + { + curve = default; + if (IsRSACertificateType()) + { + return false; + } + ECCurve? tempCurve = EccUtils.GetCurveFromCertificateTypeId(CertificateType); + + if (tempCurve == null) + { + throw new ServiceResultException(StatusCodes.BadNotSupported, $"The certificate type {CertificateType} is not supported."); + } + + curve = tempCurve.Value; + + return true; + } +#endif + /// + /// Checks if the Certificate Group is for RSA Certificates + /// + /// True if the CertificateType of the Certificate Group is an RSA Certificate Type + private bool IsRSACertificateType() + { + return CertificateType == null || + CertificateType == Opc.Ua.ObjectTypeIds.ApplicationCertificateType || + CertificateType == Opc.Ua.ObjectTypeIds.HttpsCertificateType || + CertificateType == Opc.Ua.ObjectTypeIds.UserCredentialCertificateType || + CertificateType == Opc.Ua.ObjectTypeIds.RsaMinApplicationCertificateType || + CertificateType == Opc.Ua.ObjectTypeIds.RsaSha256ApplicationCertificateType; + } + /// /// Updates the certificate authority certificate and CRL in the provided CertificateStore /// 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 7750c96a6..2c9586ae6 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 @@ -21,6 +21,17 @@ $(DefineConstants);SIGNASSEMBLY + + + + + + + $(DefineConstants);ECC_SUPPORT + + + + diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 0c9339ba3..6ccb50f44 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -114,7 +114,7 @@ ApplicationConfiguration configuration defaultApplicationGroup.CertificateTypes = defaultApplicationGroup.CertificateTypes.Concat(new NodeId[] { cert.CertificateType }).ToArray(); defaultApplicationGroup.ApplicationCertificates.Add(cert); - if(cert.CertificateType == ObjectTypeIds.HttpsCertificateType) + if (cert.CertificateType == ObjectTypeIds.HttpsCertificateType) { defaultHttpsGroup.CertificateTypes = defaultHttpsGroup.CertificateTypes.Concat(new NodeId[] { cert.CertificateType }).ToArray(); defaultHttpsGroup.ApplicationCertificates.Add(cert); From 434bcf6642ab64300c4adb57049d5c5b36e3eb24 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sun, 3 Nov 2024 21:02:14 +0100 Subject: [PATCH 3/3] fix test --- Tests/Opc.Ua.Gds.Tests/PushTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Opc.Ua.Gds.Tests/PushTest.cs b/Tests/Opc.Ua.Gds.Tests/PushTest.cs index 8650d8f10..31bfcbf0d 100644 --- a/Tests/Opc.Ua.Gds.Tests/PushTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/PushTest.cs @@ -618,7 +618,7 @@ public void GetCertificates() m_pushClient.PushClient.GetCertificates(m_pushClient.PushClient.DefaultApplicationGroup, out NodeId[] certificateTypeIds, out byte[][] certificates); - Assert.That(certificateTypeIds.Length == 1); + Assert.That(certificateTypeIds.Length == certificates.Length); Assert.NotNull(certificates[0]); using (var x509 = new X509Certificate2(certificates[0])) {