/// <summary>
        /// Create a X509Certificate2 with a private key by combining
        /// the new certificate with a private key from an existing certificate
        /// </summary>
        public static X509Certificate2 CreateCertificateWithPrivateKey(
            X509Certificate2 certificate,
            X509Certificate2 certificateWithPrivateKey)
        {
            if (!certificateWithPrivateKey.HasPrivateKey)
            {
                throw new NotSupportedException("Need a certificate with a private key.");
            }

            if (!X509Utils.VerifyRSAKeyPair(certificate, certificateWithPrivateKey))
            {
                throw new NotSupportedException("The public and the private key pair doesn't match.");
            }

            string passcode = X509Utils.GeneratePasscode();

            using (RSA rsaPrivateKey = certificateWithPrivateKey.GetRSAPrivateKey())
            {
                byte[] pfxData = CertificateBuilder.CreatePfxWithRSAPrivateKey(
                    certificate, certificate.FriendlyName, rsaPrivateKey, passcode);
                return(X509Utils.CreateCertificateFromPKCS12(pfxData, passcode));
            }
        }
Example #2
0
        /// <summary>
        /// Returns the file name to use for the certificate.
        /// </summary>
        private string GetFileName(X509Certificate2 certificate)
        {
            // build file name.
            string commonName = certificate.FriendlyName;

            List <string> names = X509Utils.ParseDistinguishedName(certificate.Subject);

            for (int ii = 0; ii < names.Count; ii++)
            {
                if (names[ii].StartsWith("CN="))
                {
                    commonName = names[ii].Substring(3).Trim();
                    break;
                }
            }

            StringBuilder fileName = new StringBuilder();

            // remove any special characters.
            for (int ii = 0; ii < commonName.Length; ii++)
            {
                char ch = commonName[ii];

                if ("<>:\"/\\|?*".IndexOf(ch) != -1)
                {
                    ch = '+';
                }

                fileName.Append(ch);
            }

            fileName.Append(" [");
            fileName.Append(certificate.Thumbprint);
            fileName.Append(']');

            return(fileName.ToString());
        }
Example #3
0
        /// <summary>
        /// Called before the server starts.
        /// </summary>
        /// <param name="configuration">The object that stores the configurable configuration information for a UA application.</param>
        protected virtual void OnServerStarting(ApplicationConfiguration configuration)
        {
            // fetch properties and configuration.
            Configuration    = configuration;
            ServerProperties = LoadServerProperties();

            // ensure at least one security policy exists.
            if (configuration.ServerConfiguration != null)
            {
                if (configuration.ServerConfiguration.SecurityPolicies.Count == 0)
                {
                    configuration.ServerConfiguration.SecurityPolicies.Add(new ServerSecurityPolicy());
                }

                // ensure at least one user token policy exists.
                if (configuration.ServerConfiguration.UserTokenPolicies.Count == 0)
                {
                    UserTokenPolicy userTokenPolicy = new UserTokenPolicy();

                    userTokenPolicy.TokenType = UserTokenType.Anonymous;
                    userTokenPolicy.PolicyId  = userTokenPolicy.TokenType.ToString();

                    configuration.ServerConfiguration.UserTokenPolicies.Add(userTokenPolicy);
                }
            }

            // load the instance certificate.
            if (configuration.SecurityConfiguration.ApplicationCertificate != null)
            {
                InstanceCertificate = configuration.SecurityConfiguration.ApplicationCertificate.Find(true).Result;
            }

            if (InstanceCertificate == null)
            {
                throw new ServiceResultException(
                          StatusCodes.BadConfigurationError,
                          "Server does not have an instance certificate assigned.");
            }

            if (!InstanceCertificate.HasPrivateKey)
            {
                throw new ServiceResultException(
                          StatusCodes.BadConfigurationError,
                          "Server does not have access to the private key for the instance certificate.");
            }

            // load certificate chain.
            InstanceCertificateChain = new X509Certificate2Collection(InstanceCertificate);
            List <CertificateIdentifier> issuers = new List <CertificateIdentifier>();

            configuration.CertificateValidator.GetIssuers(InstanceCertificateChain, issuers).Wait();

            for (int i = 0; i < issuers.Count; i++)
            {
                InstanceCertificateChain.Add(issuers[i].Certificate);
            }

            // use the message context from the configuration to ensure the channels are using the same one.
            MessageContext = configuration.CreateMessageContext();

            // assign a unique identifier if none specified.
            if (String.IsNullOrEmpty(configuration.ApplicationUri))
            {
                configuration.ApplicationUri = X509Utils.GetApplicationUriFromCertificate(InstanceCertificate);

                if (String.IsNullOrEmpty(configuration.ApplicationUri))
                {
                    configuration.ApplicationUri = Utils.Format(
                        "http://{0}/{1}/{2}",
                        Utils.GetHostName(),
                        configuration.ApplicationName,
                        Guid.NewGuid());
                }
            }

            // initialize namespace table.
            MessageContext.NamespaceUris = new NamespaceTable();
            MessageContext.NamespaceUris.Append(configuration.ApplicationUri);

            // assign an instance name.
            if (String.IsNullOrEmpty(configuration.ApplicationName) && InstanceCertificate != null)
            {
                configuration.ApplicationName = InstanceCertificate.GetNameInfo(X509NameType.DnsName, false);
            }

            // save the certificate validator.
            CertificateValidator = configuration.CertificateValidator;
        }
        /// <summary>
        /// Sets the parameters to suitable defaults.
        /// </summary>
        private static void SetSuitableDefaults(
            ref string applicationUri,
            ref string applicationName,
            ref string subjectName,
            ref IList <String> domainNames)
        {
            // parse the subject name if specified.
            List <string> subjectNameEntries = null;

            if (!String.IsNullOrEmpty(subjectName))
            {
                subjectNameEntries = X509Utils.ParseDistinguishedName(subjectName);
            }

            // check the application name.
            if (String.IsNullOrEmpty(applicationName))
            {
                if (subjectNameEntries == null)
                {
                    throw new ArgumentNullException(nameof(applicationName), "Must specify a applicationName or a subjectName.");
                }

                // use the common name as the application name.
                for (int ii = 0; ii < subjectNameEntries.Count; ii++)
                {
                    if (subjectNameEntries[ii].StartsWith("CN=", StringComparison.Ordinal))
                    {
                        applicationName = subjectNameEntries[ii].Substring(3).Trim();
                        break;
                    }
                }
            }

            if (String.IsNullOrEmpty(applicationName))
            {
                throw new ArgumentNullException(nameof(applicationName), "Must specify a applicationName or a subjectName.");
            }

            // remove special characters from name.
            StringBuilder buffer = new StringBuilder();

            for (int ii = 0; ii < applicationName.Length; ii++)
            {
                char ch = applicationName[ii];

                if (Char.IsControl(ch) || ch == '/' || ch == ',' || ch == ';')
                {
                    ch = '+';
                }

                buffer.Append(ch);
            }

            applicationName = buffer.ToString();

            // ensure at least one host name.
            if (domainNames == null || domainNames.Count == 0)
            {
                domainNames = new List <string>();
                domainNames.Add(Utils.GetHostName());
            }

            // create the application uri.
            if (String.IsNullOrEmpty(applicationUri))
            {
                StringBuilder builder = new StringBuilder();

                builder.Append("urn:");
                builder.Append(domainNames[0]);
                builder.Append(':');
                builder.Append(applicationName);

                applicationUri = builder.ToString();
            }

            Uri uri = Utils.ParseUri(applicationUri);

            if (uri == null)
            {
                throw new ArgumentNullException(nameof(applicationUri), "Must specify a valid URL.");
            }

            // create the subject name,
            if (String.IsNullOrEmpty(subjectName))
            {
                subjectName = Utils.Format("CN={0}", applicationName);
            }

            if (!subjectName.Contains("CN="))
            {
                subjectName = Utils.Format("CN={0}", subjectName);
            }

            if (domainNames != null && domainNames.Count > 0)
            {
                if (!subjectName.Contains("DC=") && !subjectName.Contains('='))
                {
                    subjectName += Utils.Format(", DC={0}", domainNames[0]);
                }
                else
                {
                    subjectName = Utils.ReplaceDCLocalhost(subjectName, domainNames[0]);
                }
            }
        }
Example #5
0
        /// <summary>
        /// Extracts the DNS names specified in the certificate.
        /// </summary>
        /// <param name="certificate">The certificate.</param>
        /// <returns>The DNS names.</returns>
        public static IList <string> GetDomainsFromCertficate(X509Certificate2 certificate)
        {
            List <string> dnsNames = new List <string>();

            // extracts the domain from the subject name.
            List <string> fields = X509Utils.ParseDistinguishedName(certificate.Subject);

            StringBuilder builder = new StringBuilder();

            for (int ii = 0; ii < fields.Count; ii++)
            {
                if (fields[ii].StartsWith("DC="))
                {
                    if (builder.Length > 0)
                    {
                        builder.Append('.');
                    }

                    builder.Append(fields[ii].Substring(3));
                }
            }

            if (builder.Length > 0)
            {
                dnsNames.Add(builder.ToString().ToUpperInvariant());
            }

            // extract the alternate domains from the subject alternate name extension.
            X509SubjectAltNameExtension alternateName = X509Extensions.FindExtension <X509SubjectAltNameExtension>(certificate);

            if (alternateName != null)
            {
                for (int ii = 0; ii < alternateName.DomainNames.Count; ii++)
                {
                    string hostname = alternateName.DomainNames[ii];

                    // do not add duplicates to the list.
                    bool found = false;

                    for (int jj = 0; jj < dnsNames.Count; jj++)
                    {
                        if (String.Compare(dnsNames[jj], hostname, StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            found = true;
                            break;
                        }
                    }

                    if (!found)
                    {
                        dnsNames.Add(hostname.ToUpperInvariant());
                    }
                }

                for (int ii = 0; ii < alternateName.IPAddresses.Count; ii++)
                {
                    string ipAddress = alternateName.IPAddresses[ii];

                    if (!dnsNames.Contains(ipAddress))
                    {
                        dnsNames.Add(ipAddress);
                    }
                }
            }

            // return the list.
            return(dnsNames);
        }
Example #6
0
        /// <summary>
        /// Checks if issuer has revoked the certificate.
        /// </summary>
        public StatusCode IsRevoked(X509Certificate2 issuer, X509Certificate2 certificate)
        {
            if (issuer == null)
            {
                throw new ArgumentNullException(nameof(issuer));
            }

            if (certificate == null)
            {
                throw new ArgumentNullException(nameof(certificate));
            }

            // check for CRL.
            DirectoryInfo info = new DirectoryInfo(this.Directory.FullName + Path.DirectorySeparatorChar + "crl");

            if (info.Exists)
            {
                bool crlExpired = true;

                foreach (FileInfo file in info.GetFiles("*.crl"))
                {
                    X509CRL crl = null;

                    try
                    {
                        crl = new X509CRL(file.FullName);
                    }
                    catch (Exception e)
                    {
                        Utils.LogError(e, "Could not parse CRL file.");
                        continue;
                    }

                    if (!X509Utils.CompareDistinguishedName(crl.Issuer, issuer.Subject))
                    {
                        continue;
                    }

                    if (!crl.VerifySignature(issuer, false))
                    {
                        continue;
                    }

                    if (crl.IsRevoked(certificate))
                    {
                        return(StatusCodes.BadCertificateRevoked);
                    }

                    if (crl.ThisUpdate <= DateTime.UtcNow && (crl.NextUpdate == DateTime.MinValue || crl.NextUpdate >= DateTime.UtcNow))
                    {
                        crlExpired = false;
                    }
                }

                // certificate is fine.
                if (!crlExpired)
                {
                    return(StatusCodes.Good);
                }
            }

            // can't find a valid CRL.
            return(StatusCodes.BadCertificateRevocationUnknown);
        }
 /// <summary>
 /// Check for self signed certificate if there is match of the Subject/Issuer.
 /// </summary>
 /// <param name="certificate">The certificate to test.</param>
 /// <returns>True if self signed.</returns>
 public static bool IsSelfSigned(X509Certificate2 certificate)
 {
     return(X509Utils.CompareDistinguishedName(certificate.SubjectName, certificate.IssuerName));
 }
Example #8
0
        /// <summary>
        /// Loads the private key from a PFX file in the certificate store.
        /// </summary>
        public X509Certificate2 LoadPrivateKey(string thumbprint, string subjectName, string password)
        {
            if (m_certificateSubdir == null || !m_certificateSubdir.Exists)
            {
                return(null);
            }

            if (string.IsNullOrEmpty(thumbprint) && string.IsNullOrEmpty(subjectName))
            {
                return(null);
            }

            foreach (FileInfo file in m_certificateSubdir.GetFiles("*.der"))
            {
                try
                {
                    X509Certificate2 certificate = new X509Certificate2(file.FullName);

                    if (!String.IsNullOrEmpty(thumbprint))
                    {
                        if (!string.Equals(certificate.Thumbprint, thumbprint, StringComparison.CurrentCultureIgnoreCase))
                        {
                            continue;
                        }
                    }

                    if (!String.IsNullOrEmpty(subjectName))
                    {
                        if (!X509Utils.CompareDistinguishedName(subjectName, certificate.Subject))
                        {
                            if (subjectName.Contains('='))
                            {
                                continue;
                            }

                            if (!X509Utils.ParseDistinguishedName(certificate.Subject).Any(s => s.Equals("CN=" + subjectName, StringComparison.OrdinalIgnoreCase)))
                            {
                                continue;
                            }
                        }
                    }

                    // skip if not RSA certificate
                    if (X509Utils.GetRSAPublicKeySize(certificate) < 0)
                    {
                        continue;
                    }

                    string fileRoot = file.Name.Substring(0, file.Name.Length - file.Extension.Length);

                    StringBuilder filePath = new StringBuilder()
                                             .Append(m_privateKeySubdir.FullName)
                                             .Append(Path.DirectorySeparatorChar)
                                             .Append(fileRoot);

                    X509KeyStorageFlags[] storageFlags =
                    {
                        X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet,
                        X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet
                    };

                    FileInfo privateKeyFile = new FileInfo(filePath.ToString() + ".pfx");
                    password = password ?? String.Empty;
                    foreach (var flag in storageFlags)
                    {
                        try
                        {
                            certificate = new X509Certificate2(
                                privateKeyFile.FullName,
                                password,
                                flag);
                            if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true))
                            {
                                return(certificate);
                            }
                        }
                        catch (Exception)
                        {
                            certificate?.Dispose();
                            certificate = null;
                        }
                    }
                }
                catch (Exception e)
                {
                    Utils.LogError(e, "Could not load private key for certificate " + subjectName);
                }
            }

            return(null);
        }
        private static ServiceResult CheckChainStatus(X509ChainStatus status, CertificateIdentifier id, CertificateIdentifier issuer, bool isIssuer)
        {
            switch (status.Status)
            {
            case X509ChainStatusFlags.NotValidForUsage:
            {
                return(ServiceResult.Create(
                           (isIssuer) ? StatusCodes.BadCertificateUseNotAllowed : StatusCodes.BadCertificateIssuerUseNotAllowed,
                           "Certificate may not be used as an application instance certificate. {0}: {1}",
                           status.Status,
                           status.StatusInformation));
            }

            case X509ChainStatusFlags.NoError:
            case X509ChainStatusFlags.OfflineRevocation:
            case X509ChainStatusFlags.InvalidBasicConstraints:
            {
                break;
            }

            case X509ChainStatusFlags.PartialChain:
            case X509ChainStatusFlags.UntrustedRoot:
            {
                // self signed cert signature validation
                // .Net Core ChainStatus returns NotSignatureValid only on Windows,
                // so we have to do the extra cert signature check on all platforms
                if (issuer == null && !isIssuer &&
                    id.Certificate != null && X509Utils.CompareDistinguishedName(id.Certificate.Subject, id.Certificate.Issuer))
                {
                    if (!IsSignatureValid(id.Certificate))
                    {
                        goto case X509ChainStatusFlags.NotSignatureValid;
                    }
                }

                // ignore this error because the root check is done
                // by looking the certificate up in the trusted issuer stores passed to the validator.
                // the ChainStatus uses the trusted issuer stores.
                break;
            }

            case X509ChainStatusFlags.RevocationStatusUnknown:
            {
                if (issuer != null)
                {
                    if ((issuer.ValidationOptions & CertificateValidationOptions.SuppressRevocationStatusUnknown) != 0)
                    {
                        break;
                    }
                }

                // check for meaning less errors for self-signed certificates.
                if (id.Certificate != null && X509Utils.CompareDistinguishedName(id.Certificate.Subject, id.Certificate.Subject))
                {
                    break;
                }

                return(ServiceResult.Create(
                           (isIssuer) ? StatusCodes.BadCertificateIssuerRevocationUnknown : StatusCodes.BadCertificateRevocationUnknown,
                           "Certificate revocation status cannot be verified. {0}: {1}",
                           status.Status,
                           status.StatusInformation));
            }

            case X509ChainStatusFlags.Revoked:
            {
                return(ServiceResult.Create(
                           (isIssuer) ? StatusCodes.BadCertificateIssuerRevoked : StatusCodes.BadCertificateRevoked,
                           "Certificate has been revoked. {0}: {1}",
                           status.Status,
                           status.StatusInformation));
            }

            case X509ChainStatusFlags.NotTimeNested:
            {
                if (id != null && ((id.ValidationOptions & CertificateValidationOptions.SuppressCertificateExpired) != 0))
                {
                    // TODO: add logging
                    break;
                }

                return(ServiceResult.Create(
                           StatusCodes.BadCertificateIssuerTimeInvalid,
                           "Issuer Certificate has expired or is not yet valid. {0}: {1}",
                           status.Status,
                           status.StatusInformation));
            }

            case X509ChainStatusFlags.NotTimeValid:
            {
                if (id != null && ((id.ValidationOptions & CertificateValidationOptions.SuppressCertificateExpired) != 0))
                {
                    // TODO: add logging
                    break;
                }

                return(ServiceResult.Create(
                           (isIssuer) ? StatusCodes.BadCertificateIssuerTimeInvalid : StatusCodes.BadCertificateTimeInvalid,
                           "Certificate has expired or is not yet valid. {0}: {1}",
                           status.Status,
                           status.StatusInformation));
            }

            case X509ChainStatusFlags.NotSignatureValid:
            default:
            {
                return(ServiceResult.Create(
                           StatusCodes.BadCertificateInvalid,
                           "Certificate validation failed. {0}: {1}",
                           status.Status,
                           status.StatusInformation));
            }
            }

            return(null);
        }
        /// <summary>
        /// Throws an exception if validation fails.
        /// </summary>
        /// <param name="certificates">The certificates to be checked.</param>
        /// <exception cref="ServiceResultException">If certificate[0] cannot be accepted</exception>
        protected virtual async Task InternalValidate(X509Certificate2Collection certificates)
        {
            X509Certificate2 certificate = certificates[0];

            // check for previously validated certificate.
            X509Certificate2 certificate2 = null;

            if (m_validatedCertificates.TryGetValue(certificate.Thumbprint, out certificate2))
            {
                if (Utils.IsEqual(certificate2.RawData, certificate.RawData))
                {
                    return;
                }
            }

            CertificateIdentifier trustedCertificate = await GetTrustedCertificate(certificate);

            // get the issuers (checks the revocation lists if using directory stores).
            List <CertificateIdentifier> issuers = new List <CertificateIdentifier>();
            bool isIssuerTrusted = await GetIssuers(certificates, issuers);

            // setup policy chain
            X509ChainPolicy policy = new X509ChainPolicy();

            policy.RevocationFlag    = X509RevocationFlag.EntireChain;
            policy.RevocationMode    = X509RevocationMode.NoCheck;
            policy.VerificationFlags = X509VerificationFlags.NoFlag;

            foreach (CertificateIdentifier issuer in issuers)
            {
                if ((issuer.ValidationOptions & CertificateValidationOptions.SuppressRevocationStatusUnknown) != 0)
                {
                    policy.VerificationFlags |= X509VerificationFlags.IgnoreCertificateAuthorityRevocationUnknown;
                    policy.VerificationFlags |= X509VerificationFlags.IgnoreCtlSignerRevocationUnknown;
                    policy.VerificationFlags |= X509VerificationFlags.IgnoreEndRevocationUnknown;
                    policy.VerificationFlags |= X509VerificationFlags.IgnoreRootRevocationUnknown;
                }

                // we did the revocation check in the GetIssuers call. No need here.
                policy.RevocationMode = X509RevocationMode.NoCheck;
                policy.ExtraStore.Add(issuer.Certificate);
            }

            // build chain.
            X509Chain chain = new X509Chain();

            chain.ChainPolicy = policy;
            chain.Build(certificate);

            // check the chain results.
            CertificateIdentifier target = trustedCertificate;

            if (target == null)
            {
                target = new CertificateIdentifier(certificate);
            }

            ServiceResult sresult = null;

            for (int ii = 0; ii < chain.ChainElements.Count; ii++)
            {
                X509ChainElement element = chain.ChainElements[ii];

                CertificateIdentifier issuer = null;

                if (ii < issuers.Count)
                {
                    issuer = issuers[ii];
                }
                // check for chain status errors.
                if (element.ChainElementStatus.Length > 0)
                {
                    foreach (X509ChainStatus status in element.ChainElementStatus)
                    {
                        ServiceResult result = CheckChainStatus(status, target, issuer, (ii != 0));
                        if (ServiceResult.IsBad(result))
                        {
                            sresult = new ServiceResult(result, sresult);
                        }
                    }
                }

                if (issuer != null)
                {
                    target = issuer;
                }
            }

            // check whether the chain is complete (if there is a chain)
            bool issuedByCA      = !X509Utils.CompareDistinguishedName(certificate.Subject, certificate.Issuer);
            bool chainIncomplete = false;

            if (issuers.Count > 0)
            {
                var rootCertificate = issuers[issuers.Count - 1].Certificate;
                if (!X509Utils.CompareDistinguishedName(rootCertificate.Subject, rootCertificate.Issuer))
                {
                    chainIncomplete = true;
                }
            }
            else
            {
                if (issuedByCA)
                {
                    // no issuer found at all
                    chainIncomplete = true;
                }
            }

            // check if certificate issuer is trusted.
            if (issuedByCA && !isIssuerTrusted && trustedCertificate == null)
            {
                var message = CertificateMessage("Certificate issuer is not trusted.", certificate);
                sresult = new ServiceResult(StatusCodes.BadCertificateUntrusted,
                                            null,
                                            null,
                                            message,
                                            null,
                                            sresult
                                            );
            }

            // check if certificate is trusted.
            if (trustedCertificate == null && !isIssuerTrusted)
            {
                if (m_applicationCertificate == null || !Utils.IsEqual(m_applicationCertificate.RawData, certificate.RawData))
                {
                    var message = CertificateMessage("Certificate is not trusted.", certificate);
                    sresult = new ServiceResult(StatusCodes.BadCertificateUntrusted,
                                                null,
                                                null,
                                                message,
                                                null,
                                                sresult
                                                );
                }
            }

            // check if certificate is valid for use as app/sw or user cert
            X509KeyUsageFlags certificateKeyUsage = X509Utils.GetKeyUsage(certificate);

            if ((certificateKeyUsage & X509KeyUsageFlags.DataEncipherment) == 0)
            {
                sresult = new ServiceResult(StatusCodes.BadCertificateUseNotAllowed,
                                            null,
                                            null,
                                            "Usage of certificate is not allowed.",
                                            null,
                                            sresult
                                            );
            }

            // check if minimum requirements are met
            if (m_rejectSHA1SignedCertificates && IsSHA1SignatureAlgorithm(certificate.SignatureAlgorithm))
            {
                sresult = new ServiceResult(StatusCodes.BadCertificatePolicyCheckFailed,
                                            null,
                                            null,
                                            "SHA1 signed certificates are not trusted.",
                                            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
                                            );
            }

            if (issuedByCA && chainIncomplete)
            {
                var message = CertificateMessage("Certificate chain validation incomplete.", certificate);
                sresult = new ServiceResult(StatusCodes.BadCertificateChainIncomplete,
                                            null, null, message, null, sresult);
            }
            if (sresult != null)
            {
                throw new ServiceResultException(sresult);
            }
        }
        /// <summary>
        /// Returns the certificate information for a trusted issuer certificate.
        /// </summary>
        private async Task <CertificateIdentifier> GetIssuer(
            X509Certificate2 certificate,
            CertificateIdentifierCollection explicitList,
            CertificateStoreIdentifier certificateStore,
            bool checkRecovationStatus)
        {
            // check if self-signed.
            if (X509Utils.CompareDistinguishedName(certificate.Subject, certificate.Issuer))
            {
                return(null);
            }

            string subjectName  = certificate.IssuerName.Name;
            string keyId        = null;
            string serialNumber = null;

            // find the authority key identifier.
            X509AuthorityKeyIdentifierExtension authority = X509Extensions.FindExtension <X509AuthorityKeyIdentifierExtension>(certificate);

            if (authority != null)
            {
                keyId        = authority.KeyIdentifier;
                serialNumber = authority.SerialNumber;
            }

            // check in explicit list.
            if (explicitList != null)
            {
                for (int ii = 0; ii < explicitList.Count; ii++)
                {
                    X509Certificate2 issuer = await explicitList[ii].Find(false);

                    if (issuer != null)
                    {
                        if (!X509Utils.IsIssuerAllowed(issuer))
                        {
                            continue;
                        }

                        if (Match(issuer, subjectName, serialNumber, keyId))
                        {
                            // can't check revocation.
                            return(new CertificateIdentifier(issuer, CertificateValidationOptions.SuppressRevocationStatusUnknown));
                        }
                    }
                }
            }

            // check in certificate store.
            if (certificateStore != null)
            {
                ICertificateStore store = certificateStore.OpenStore();

                try
                {
                    X509Certificate2Collection certificates = await store.Enumerate();

                    for (int ii = 0; ii < certificates.Count; ii++)
                    {
                        X509Certificate2 issuer = certificates[ii];

                        if (issuer != null)
                        {
                            if (!X509Utils.IsIssuerAllowed(issuer))
                            {
                                continue;
                            }

                            if (Match(issuer, subjectName, serialNumber, keyId))
                            {
                                CertificateValidationOptions options = certificateStore.ValidationOptions;

                                // already checked revocation for file based stores. windows based stores always suppress.
                                options |= CertificateValidationOptions.SuppressRevocationStatusUnknown;

                                if (checkRecovationStatus)
                                {
                                    StatusCode status = store.IsRevoked(issuer, certificate);

                                    if (StatusCode.IsBad(status) && status != StatusCodes.BadNotSupported)
                                    {
                                        if (status == StatusCodes.BadCertificateRevocationUnknown)
                                        {
                                            if (X509Utils.IsCertificateAuthority(certificate))
                                            {
                                                status.Code = StatusCodes.BadCertificateIssuerRevocationUnknown;
                                            }

                                            if (m_rejectUnknownRevocationStatus)
                                            {
                                                throw new ServiceResultException(status);
                                            }
                                        }
                                        else
                                        {
                                            throw new ServiceResultException(status);
                                        }
                                    }
                                }

                                return(new CertificateIdentifier(certificates[ii], options));
                            }
                        }
                    }
                }
                finally
                {
                    store.Close();
                }
            }

            // not a trusted issuer.
            return(null);
        }
 /// <summary>
 /// Returns if a self signed certificate is properly signed.
 /// </summary>
 private static bool IsSignatureValid(X509Certificate2 cert)
 {
     return(X509Utils.VerifySelfSigned(cert));
 }
        /// <summary>
        /// Loads the private key from a PFX/PEM file in the certificate store.
        /// </summary>
        public async Task <X509Certificate2> LoadPrivateKey(string thumbprint, string subjectName, string password)
        {
            if (NoPrivateKeys || m_certificateSubdir == null || !m_certificateSubdir.Exists)
            {
                return(null);
            }

            if (string.IsNullOrEmpty(thumbprint) && string.IsNullOrEmpty(subjectName))
            {
                return(null);
            }

            // on some platforms, specifically in virtualized environments,
            // reloading a previously created and saved private key may fail on the first attempt.
            const int retryDelay   = 100;
            int       retryCounter = 3;

            while (retryCounter-- > 0)
            {
                bool      certificateFound = false;
                Exception importException  = null;
                foreach (FileInfo file in m_certificateSubdir.GetFiles("*.der"))
                {
                    try
                    {
                        X509Certificate2 certificate = new X509Certificate2(file.FullName);

                        if (!String.IsNullOrEmpty(thumbprint))
                        {
                            if (!string.Equals(certificate.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase))
                            {
                                continue;
                            }
                        }

                        if (!String.IsNullOrEmpty(subjectName))
                        {
                            if (!X509Utils.CompareDistinguishedName(subjectName, certificate.Subject))
                            {
                                if (subjectName.Contains('='))
                                {
                                    continue;
                                }

                                if (!X509Utils.ParseDistinguishedName(certificate.Subject).Any(s => s.Equals("CN=" + subjectName, StringComparison.Ordinal)))
                                {
                                    continue;
                                }
                            }
                        }

                        // skip if not RSA certificate
                        if (X509Utils.GetRSAPublicKeySize(certificate) < 0)
                        {
                            continue;
                        }

                        string fileRoot = file.Name.Substring(0, file.Name.Length - file.Extension.Length);

                        StringBuilder filePath = new StringBuilder()
                                                 .Append(m_privateKeySubdir.FullName)
                                                 .Append(Path.DirectorySeparatorChar)
                                                 .Append(fileRoot);

                        X509KeyStorageFlags[] storageFlags =
                        {
                            X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet,
                            X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet
                        };

                        FileInfo privateKeyFilePfx = new FileInfo(filePath + ".pfx");
                        FileInfo privateKeyFilePem = new FileInfo(filePath + ".pem");
                        password = password ?? String.Empty;
                        if (privateKeyFilePfx.Exists)
                        {
                            certificateFound = true;
                            foreach (var flag in storageFlags)
                            {
                                try
                                {
                                    certificate = new X509Certificate2(
                                        privateKeyFilePfx.FullName,
                                        password,
                                        flag);
                                    if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true))
                                    {
                                        Utils.LogInfo(Utils.TraceMasks.Security, "Imported the PFX private key for [{0}].", certificate.Thumbprint);
                                        return(certificate);
                                    }
                                }
                                catch (Exception ex)
                                {
                                    importException = ex;
                                    certificate?.Dispose();
                                }
                            }
                        }
                        // if PFX file doesn't exist, check for PEM file.
                        else if (privateKeyFilePem.Exists)
                        {
                            certificateFound = true;
                            try
                            {
                                byte[] pemDataBlob = File.ReadAllBytes(privateKeyFilePem.FullName);
                                certificate = CertificateFactory.CreateCertificateWithPEMPrivateKey(certificate, pemDataBlob, password);
                                if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true))
                                {
                                    Utils.LogInfo(Utils.TraceMasks.Security, "Imported the PEM private key for [{0}].", certificate.Thumbprint);
                                    return(certificate);
                                }
                            }
                            catch (Exception exception)
                            {
                                certificate?.Dispose();
                                importException = exception;
                            }
                        }
                        else
                        {
                            Utils.LogError(Utils.TraceMasks.Security, "A private key for the certificate with thumbprint [{0}] does not exist.", certificate.Thumbprint);
                            continue;
                        }
                    }
                    catch (Exception e)
                    {
                        Utils.LogError(e, "Could not load private key for certificate {0}", subjectName);
                    }
                }

                // found a certificate, but some error occurred
                if (certificateFound)
                {
                    Utils.LogError(Utils.TraceMasks.Security, "The private key for the certificate with subject {0} failed to import.", subjectName);
                    if (importException != null)
                    {
                        Utils.LogError(importException, "Certificate import failed.");
                    }
                }
                else
                {
                    if (!String.IsNullOrEmpty(thumbprint))
                    {
                        Utils.LogError(Utils.TraceMasks.Security, "A Private key for the certificate with thumbpint {0} was not found.", thumbprint);
                    }
                    // if no private key was found, no need to retry
                    break;
                }

                // retry within a few ms
                if (retryCounter > 0)
                {
                    Utils.LogInfo(Utils.TraceMasks.Security, "Retry to import private key after {0} ms.", retryDelay);
                    await Task.Delay(retryDelay).ConfigureAwait(false);
                }
            }

            return(null);
        }