diff --git a/SampleApplications/Samples/NetCoreConsoleClient/Program.cs b/SampleApplications/Samples/NetCoreConsoleClient/Program.cs index fa856a0595..87473f4709 100644 --- a/SampleApplications/Samples/NetCoreConsoleClient/Program.cs +++ b/SampleApplications/Samples/NetCoreConsoleClient/Program.cs @@ -70,6 +70,11 @@ namespace NetCoreConsoleClient StoreType = "Directory", StorePath = "./OPC Foundation/CertificateStores/UA Certificate Authorities", }, + RejectedCertificateStore = new CertificateTrustList + { + StoreType = "Directory", + StorePath = "./OPC Foundation/CertificateStores/RejectedCertificates", + }, NonceLength = 32, AutoAcceptUntrustedCertificates = true }, diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs index e75fda64c5..753521bebd 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs @@ -371,7 +371,7 @@ namespace Opc.Ua { if (certificates == null || certificates.Count == 0) { - throw new Exception("Primary certificate has not been provided."); + throw new CryptographicException("Primary certificate has not been provided."); } // copy the primary certificate. @@ -425,7 +425,7 @@ namespace Opc.Ua { if (!IsValidCertificateBlob(encodedData)) { - throw new Exception("Primary certificate in blob is not valid."); + throw new CryptographicException("Primary certificate in blob is not valid."); } X509Certificate2Collection collection = new X509Certificate2Collection(); @@ -447,7 +447,7 @@ namespace Opc.Ua if (!IsValidCertificateBlob(buffer)) { - throw new Exception("Supporting certificate in blob is not valid."); + throw new CryptographicException("Supporting certificate in blob is not valid."); } X509Certificate2 issuerCertificate = CertificateFactory.Create(buffer, true); @@ -468,8 +468,7 @@ namespace Opc.Ua public ICertificateStore OpenStore() { ICertificateStore store = CertificateStoreIdentifier.CreateStore(this.StoreType); - Task t = Task.Run(() => store.Open(this.StorePath)); - t.Wait(); + store.Open(this.StorePath); return store; } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateStoreIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateStoreIdentifier.cs index 916d68a0be..eebb1bd112 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateStoreIdentifier.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateStoreIdentifier.cs @@ -148,8 +148,7 @@ namespace Opc.Ua public ICertificateStore OpenStore() { ICertificateStore store = CreateStore(this.StoreType); - Task t = Task.Run(() => store.Open(this.StorePath)); - t.Wait(); + store.Open(this.StorePath); return store; } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 978c42c083..7ea961d71b 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -150,12 +150,9 @@ namespace Opc.Ua /// Validates the specified certificate against the trust list. /// /// The certificate. - public virtual void Validate(X509Certificate2Collection chain) + public virtual void Validate(X509Certificate2 certificate) { - foreach (X509Certificate2 certificate in chain) - { - Validate(certificate); - } + Validate(new X509Certificate2Collection() { certificate }); } /// @@ -177,18 +174,20 @@ namespace Opc.Ua /// /// The validator may be configured to ignore the application trust list and/or trust chain. /// - public virtual void Validate(X509Certificate2 certificate) + public virtual void Validate(X509Certificate2Collection chain) { + X509Certificate2 certificate = chain[0]; + try { - Task.Run(async () => - { - await InternalValidate(certificate); - }).Wait(); - - // add to list of validated certificates. lock (m_lock) - { + { + Task.Run(async () => + { + await InternalValidate(chain); + }).Wait(); + + // add to list of validated certificates. m_validatedCertificates[certificate.Thumbprint] = certificate; } } @@ -335,6 +334,9 @@ namespace Opc.Ua return null; } + /// + /// Returns the authority key identifier in the certificate. + /// private X509AuthorityKeyIdentifierExtension FindAuthorityKeyIdentifier(X509Certificate2 certificate) { for (int ii = 0; ii < certificate.Extensions.Count; ii++) @@ -469,13 +471,11 @@ namespace Opc.Ua issuer = await GetIssuer(certificate, collection, null, true); } } - else - { - isTrusted = true; - } if (issuer != null) { + isTrusted = true; + issuers.Add(issuer); certificate = await issuer.Find(false); @@ -485,6 +485,10 @@ namespace Opc.Ua break; } } + else + { + isTrusted = false; + } } while (issuer != null); @@ -541,7 +545,7 @@ namespace Opc.Ua CertificateStoreIdentifier certificateStore, bool checkRecovationStatus) { - string subjectName = certificate.IssuerName.ToString(); + string subjectName = certificate.IssuerName.Name; string keyId = null; string serialNumber = null; @@ -635,10 +639,12 @@ namespace Opc.Ua /// /// Throws an exception if validation fails. /// - /// The certificate to be checked. - /// If certificate cannot be accepted - protected async virtual Task InternalValidate(X509Certificate2 certificate) + /// The certificates to be checked. + /// If certificate[0] cannot be accepted + protected virtual async Task InternalValidate(X509Certificate2Collection certificates) { + X509Certificate2 certificate = certificates[0]; + // check for previously validated certificate. X509Certificate2 certificate2 = null; @@ -654,7 +660,7 @@ namespace Opc.Ua // get the issuers (checks the revocation lists if using directory stores). List issuers = new List(); - bool isIssuerTrusted = await GetIssuers(certificate, issuers); + bool isIssuerTrusted = await GetIssuers(certificates, issuers); // setup policy chain X509ChainPolicy policy = new X509ChainPolicy(); @@ -709,6 +715,12 @@ namespace Opc.Ua if (ServiceResult.IsBad(result)) { + // check untrusted certificates. + if (trustedCertificate == null) + { + throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed); + } + throw new ServiceResultException(result); } } @@ -719,6 +731,21 @@ namespace Opc.Ua } } + bool issuedByCA = !Utils.CompareDistinguishedName(certificate.Subject, certificate.Issuer); + + // check if certificate issuer is trusted. + if (issuedByCA && !isIssuerTrusted) + { + if (m_applicationCertificate == null || !Utils.IsEqual(m_applicationCertificate.RawData, certificate.RawData)) + { + throw ServiceResultException.Create( + StatusCodes.BadCertificateUntrusted, + "Certificate issuer is not trusted.\r\nSubjectName: {0}\r\nIssuerName: {1}", + certificate.SubjectName.Name, + certificate.IssuerName.Name); + } + } + // check if certificate is trusted. if (trustedCertificate == null && !isIssuerTrusted) { @@ -726,13 +753,14 @@ namespace Opc.Ua { throw ServiceResultException.Create( StatusCodes.BadCertificateUntrusted, - "Certificate is not trusted.\r\nSubjectName: {0}\r\nIssuerName: {1}", - certificate.SubjectName.Name, - certificate.IssuerName); + "Certificate is not trusted.\r\nSubjectName: {0}\r\nIssuerName: {1}", + certificate.SubjectName.Name, + certificate.IssuerName.Name); } } } + /// /// Returns an object that can be used with WCF channel. /// diff --git a/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs b/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs index 0cc9fa0846..983739df0a 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs @@ -395,6 +395,11 @@ namespace Opc.Ua return null; } + if (string.IsNullOrEmpty(thumbprint) && string.IsNullOrEmpty(subjectName)) + { + return null; + } + foreach (FileInfo file in m_certificateSubdir.GetFiles("*.der")) { try @@ -403,7 +408,7 @@ namespace Opc.Ua if (!String.IsNullOrEmpty(thumbprint)) { - if (certificate.Thumbprint != thumbprint) + if (!string.Equals(certificate.Thumbprint, thumbprint, StringComparison.CurrentCultureIgnoreCase)) { continue; } @@ -442,6 +447,7 @@ namespace Opc.Ua byte[] bytes2 = rsa.Decrypt(bytes1, RSAEncryptionPadding.OaepSHA1); if (bytes2 != null) { + // Utils.Trace(1, "RSA: {0}", certificate.Thumbprint); return certificate; } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509AuthorityKeyIdentifierExtension.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509AuthorityKeyIdentifierExtension.cs index 1ebfa3b14b..8816980465 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509AuthorityKeyIdentifierExtension.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509AuthorityKeyIdentifierExtension.cs @@ -160,7 +160,6 @@ namespace Opc.Ua public string KeyId { get { return m_keyId; } - set { m_keyId = value; } } /// @@ -169,7 +168,6 @@ namespace Opc.Ua public string[] AuthorityNames { get { return m_authorityNames; } - set { m_authorityNames = value; } } /// @@ -178,7 +176,6 @@ namespace Opc.Ua public string SerialNumber { get { return m_serialNumber; } - set { m_serialNumber = value; } } #endregion diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509CRL.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509CRL.cs index c8909a100d..830dfe4188 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509CRL.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509CRL.cs @@ -29,8 +29,11 @@ using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.IO; +using Org.BouncyCastle.X509; + namespace Opc.Ua { + /// /// Provides access to an X509 CRL object. /// @@ -110,9 +113,9 @@ namespace Opc.Ua /// public bool VerifySignature(X509Certificate2 issuer, bool throwOnError) { - //TODO - m_issuer = issuer; + Org.BouncyCastle.X509.X509Certificate bccert = new X509CertificateParser().ReadCertificate(issuer.RawData); + m_crl.Verify(bccert.GetPublicKey()); return true; } @@ -127,22 +130,25 @@ namespace Opc.Ua throw new ServiceResultException(StatusCodes.BadCertificateInvalid, "Certificate was not created by the CRL issuer."); } - // TODO: get the cert info for the target certificate and check revocation. - - // not revoked. - return false; + Org.BouncyCastle.X509.X509Certificate bccert = new X509CertificateParser().ReadCertificate(certificate.RawData); + return m_crl.IsRevoked(bccert); } #endregion #region Private Methods private void Initialize(byte[] crl) { - - } + X509CrlParser parser = new X509CrlParser(); + m_crl = parser.ReadCrl(crl); + UpdateTime = (m_crl.ThisUpdate == null) ? DateTime.MinValue : m_crl.ThisUpdate; + NextUpdateTime = (m_crl.NextUpdate == null) ? DateTime.MinValue : m_crl.NextUpdate.Value; + Issuer = m_crl.IssuerDN.ToString(); + } #endregion #region Private Fields - private X509Certificate2 m_issuer; - #endregion + X509Certificate2 m_issuer; + private X509Crl m_crl; + #endregion } } diff --git a/Stack/Opc.Ua.Core/project.json b/Stack/Opc.Ua.Core/project.json index e8147fd6fe..be6eb77546 100644 --- a/Stack/Opc.Ua.Core/project.json +++ b/Stack/Opc.Ua.Core/project.json @@ -1,13 +1,14 @@ { "version": "1.0.0-*", - "dependencies": { - "NETStandard.Library": "1.6.0", - "Newtonsoft.Json": "9.0.1", - "System.Data.Common": "4.1.0", - "System.Private.ServiceModel": "4.1.0", - "System.ServiceModel.Primitives": "4.1.0" - }, + "dependencies": { + "NETStandard.Library": "1.6.0", + "Newtonsoft.Json": "9.0.1", + "Portable.BouncyCastle": "1.8.1.1", + "System.Data.Common": "4.1.0", + "System.Private.ServiceModel": "4.1.0", + "System.ServiceModel.Primitives": "4.1.0" + }, "frameworks": { "net46": {