/// <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);
            }

            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.
                foreach (X509ChainStatus status in element.ChainElementStatus)
                {
                    ServiceResult result = CheckChainStatus(status, target, issuer, (ii != 0));

                    if (ServiceResult.IsBad(result))
                    {
                        // check untrusted certificates.
                        if (trustedCertificate == null)
                        {
                            throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed);
                        }

                        throw new ServiceResultException(result);
                    }
                }

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

            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)
            {
                if (m_applicationCertificate == null || !Utils.IsEqual(m_applicationCertificate.RawData, certificate.RawData))
                {
                    throw ServiceResultException.Create(
                              StatusCodes.BadCertificateUntrusted,
                              "Certificate is not trusted.\r\nSubjectName: {0}\r\nIssuerName: {1}",
                              certificate.SubjectName.Name,
                              certificate.IssuerName.Name);
                }
            }
        }
        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:
            case X509ChainStatusFlags.PartialChain:
            {
                break;
            }

            case X509ChainStatusFlags.UntrustedRoot:
            {
                // 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 Windows 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 && Utils.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))
                {
                    break;
                }

                return(ServiceResult.Create(
                           StatusCodes.BadCertificateIssuerTimeInvalid,
                           "Certificate issuer validatity time does not overhas is expired or not yet valid. {0}: {1}",
                           status.Status,
                           status.StatusInformation));
            }


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

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

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

            return(null);
        }
        /// <summary>
        /// Validates a certificate.
        /// </summary>
        /// <remarks>
        /// Each UA application may have a list of trusted certificates that is different from
        /// all other UA applications that may be running on the same machine. As a result, the
        /// certificate validator cannot rely completely on the Windows certificate store and
        /// user or machine specific CTLs (certificate trust lists).
        ///
        /// The validator constructs the trust chain for the certificate and follows the chain
        /// until it finds a certification that is in the application trust list. Non-fatal trust
        /// chain errors (i.e. certificate expired) are ignored if the certificate is in the
        /// application trust list.
        ///
        /// If no certificate in the chain is trusted then the validator will still accept the
        /// certification if there are no trust chain errors.
        ///
        /// The validator may be configured to ignore the application trust list and/or trust chain.
        /// </remarks>
        public virtual void Validate(X509Certificate2Collection chain)
        {
            X509Certificate2 certificate = chain[0];

            try
            {
                lock (m_lock)
                {
                    Task.Run(async() =>
                    {
                        await InternalValidate(chain);
                    }).Wait();

                    // add to list of validated certificates.
                    m_validatedCertificates[certificate.Thumbprint] = certificate;
                }
            }
            catch (AggregateException ae)
            {
                foreach (ServiceResultException e in ae.InnerExceptions)
                {
                    // check for errors that may be suppressed.
                    switch (e.StatusCode)
                    {
                    case StatusCodes.BadCertificateHostNameInvalid:
                    case StatusCodes.BadCertificateIssuerRevocationUnknown:
                    case StatusCodes.BadCertificateIssuerTimeInvalid:
                    case StatusCodes.BadCertificateIssuerUseNotAllowed:
                    case StatusCodes.BadCertificateRevocationUnknown:
                    case StatusCodes.BadCertificateTimeInvalid:
                    case StatusCodes.BadCertificateUriInvalid:
                    case StatusCodes.BadCertificateUseNotAllowed:
                    case StatusCodes.BadCertificateUntrusted:
                    {
                        Utils.Trace("Cert Validate failed: {0}", (StatusCode)e.StatusCode);
                        break;
                    }

                    default:
                    {
                        throw new ServiceResultException(e, StatusCodes.BadCertificateInvalid);
                    }
                    }

                    // invoke callback.
                    bool accept = false;

                    lock (m_callbackLock)
                    {
                        if (m_CertificateValidation != null)
                        {
                            CertificateValidationEventArgs args = new CertificateValidationEventArgs(new ServiceResult(e), certificate);
                            m_CertificateValidation(this, args);
                            accept = args.Accept;
                        }
                    }

                    // throw if rejected.
                    if (!accept)
                    {
                        // write the invalid certificate to a directory if specified.
                        lock (m_lock)
                        {
                            Utils.Trace((int)Utils.TraceMasks.Error, "Certificate '{0}' rejected. Reason={1}", certificate.Subject, (StatusCode)e.StatusCode);

                            if (m_rejectedCertificateStore != null)
                            {
                                Utils.Trace((int)Utils.TraceMasks.Error, "Writing rejected certificate to directory: {0}", m_rejectedCertificateStore);
                                SaveCertificate(certificate);
                            }
                        }

                        throw new ServiceResultException(e, StatusCodes.BadCertificateInvalid);
                    }

                    // add to list of peers.
                    lock (m_lock)
                    {
                        m_validatedCertificates[certificate.Thumbprint] = certificate;
                    }
                }
            }
        }