File: System\IdentityModel\Selectors\X509CertificateValidator.cs
Web Access
Project: src\src\System.ServiceModel.Primitives\src\System.ServiceModel.Primitives.csproj (System.ServiceModel.Primitives)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
 
using System.Diagnostics.Contracts;
using System.IdentityModel.Tokens;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.Text;
 
namespace System.IdentityModel.Selectors
{
    public abstract class X509CertificateValidator
    {
        private static X509CertificateValidator s_peerTrust;
        private static X509CertificateValidator s_chainTrust;
        private static X509CertificateValidator s_peerOrChainTrust;
        private static X509CertificateValidator s_none;
 
        public static X509CertificateValidator None
        {
            get
            {
                if (s_none == null)
                {
                    s_none = new NoneX509CertificateValidator();
                }
 
                return s_none;
            }
        }
 
        public static X509CertificateValidator PeerTrust
        {
            get
            {
                if (s_peerTrust == null)
                {
                    s_peerTrust = new PeerTrustValidator();
                }
 
                return s_peerTrust;
            }
        }
 
        public static X509CertificateValidator ChainTrust
        {
            get
            {
                if (s_chainTrust == null)
                {
                    s_chainTrust = new ChainTrustValidator();
                }
 
                return s_chainTrust;
            }
        }
 
        public static X509CertificateValidator PeerOrChainTrust
        {
            get
            {
                if (s_peerOrChainTrust == null)
                {
                    s_peerOrChainTrust = new PeerOrChainTrustValidator();
                }
 
                return s_peerOrChainTrust;
            }
        }
 
        public static X509CertificateValidator CreateChainTrustValidator(bool useMachineContext, X509ChainPolicy chainPolicy)
        {
            if (chainPolicy == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(chainPolicy));
            }
 
            return new ChainTrustValidator(useMachineContext, chainPolicy, X509CertificateChain.DefaultChainPolicyOID);
        }
 
        public static X509CertificateValidator CreatePeerOrChainTrustValidator(bool useMachineContext, X509ChainPolicy chainPolicy)
        {
            if (chainPolicy == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(chainPolicy));
            }
 
            return new PeerOrChainTrustValidator(useMachineContext, chainPolicy);
        }
 
        public abstract void Validate(X509Certificate2 certificate);
 
        private class NoneX509CertificateValidator : X509CertificateValidator
        {
            public override void Validate(X509Certificate2 certificate)
            {
                if (certificate == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(certificate));
                }
            }
        }
 
        private class PeerTrustValidator : X509CertificateValidator
        {
            public override void Validate(X509Certificate2 certificate)
            {
                if (certificate == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(certificate));
                }
 
                Exception exception;
                if (!TryValidate(certificate, out exception))
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(exception);
                }
            }
 
            private static bool StoreContainsCertificate(StoreName storeName, X509Certificate2 certificate)
            {
                X509Store store = new X509Store(storeName, StoreLocation.CurrentUser);
                X509Certificate2Collection certificates = null;
                try
                {
                    store.Open(OpenFlags.ReadOnly);
                    certificates = store.Certificates.Find(X509FindType.FindByThumbprint, certificate.Thumbprint, false);
                    return certificates.Count > 0;
                }
                finally
                {
                    SecurityUtils.ResetAllCertificates(certificates);
                    store.Dispose();
                }
            }
 
            internal bool TryValidate(X509Certificate2 certificate, out Exception exception)
            {
                // Checklist
                // 1) time validity of cert
                // 2) in trusted people store
                // 3) not in disallowed store
 
                // The following code could be written as:
                // DateTime now = DateTime.UtcNow;
                // if (now > certificate.NotAfter.ToUniversalTime() || now < certificate.NotBefore.ToUniversalTime())
                //
                // this is because X509Certificate2.xxx doesn't return UT.  However this would be a SMALL perf hit.
                // I put an Assert so that this will ensure that the we are compatible with the CLR we shipped with
 
                DateTime now = DateTime.Now;
                Contract.Assert(now.Kind == certificate.NotAfter.Kind && now.Kind == certificate.NotBefore.Kind, "");
 
                if (now > certificate.NotAfter || now < certificate.NotBefore)
                {
                    exception = new SecurityTokenValidationException(SRP.Format(SRP.X509InvalidUsageTime,
                        SecurityUtils.GetCertificateId(certificate), now, certificate.NotBefore, certificate.NotAfter));
                    return false;
                }
 
                if (!StoreContainsCertificate(StoreName.TrustedPeople, certificate))
                {
                    exception = new SecurityTokenValidationException(SRP.Format(SRP.X509IsNotInTrustedStore,
                        SecurityUtils.GetCertificateId(certificate)));
                    return false;
                }
 
                if (StoreContainsCertificate(StoreName.Disallowed, certificate))
                {
                    exception = new SecurityTokenValidationException(SRP.Format(SRP.X509IsInUntrustedStore,
                        SecurityUtils.GetCertificateId(certificate)));
                    return false;
                }
                exception = null;
                return true;
            }
        }
 
        private class ChainTrustValidator : X509CertificateValidator
        {
            private bool _useMachineContext;
            private X509ChainPolicy _chainPolicy;
            private uint _chainPolicyOID = X509CertificateChain.DefaultChainPolicyOID;
 
            public ChainTrustValidator()
            {
                _chainPolicy = null;
            }
 
            public ChainTrustValidator(bool useMachineContext, X509ChainPolicy chainPolicy, uint chainPolicyOID)
            {
                _useMachineContext = useMachineContext;
                _chainPolicy = chainPolicy;
                _chainPolicyOID = chainPolicyOID;
            }
 
            public override void Validate(X509Certificate2 certificate)
            {
                if (certificate == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(certificate));
                }
 
                // implies _useMachineContext = false
                // ctor for X509Chain(_useMachineContext, _chainPolicyOID) not present in CoreCLR
                X509Chain chain = new X509Chain();
 
                if (_chainPolicy != null)
                {
                    _chainPolicy.VerificationTime = DateTime.Now;
                    chain.ChainPolicy = _chainPolicy;
                }
 
                if (!chain.Build(certificate))
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SRP.Format(SRP.X509ChainBuildFail,
                        SecurityUtils.GetCertificateId(certificate), GetChainStatusInformation(chain.ChainStatus))));
                }
            }
 
            private static string GetChainStatusInformation(X509ChainStatus[] chainStatus)
            {
                if (chainStatus != null)
                {
                    StringBuilder error = new StringBuilder(128);
                    for (int i = 0; i < chainStatus.Length; ++i)
                    {
                        error.Append(chainStatus[i].StatusInformation);
                        error.Append(" ");
                    }
                    return error.ToString();
                }
                return String.Empty;
            }
        }
 
 
        private class PeerOrChainTrustValidator : X509CertificateValidator
        {
            private X509CertificateValidator _chain;
            private PeerTrustValidator _peer;
 
            public PeerOrChainTrustValidator()
            {
                _chain = X509CertificateValidator.ChainTrust;
                _peer = (PeerTrustValidator)X509CertificateValidator.PeerTrust;
            }
 
            public PeerOrChainTrustValidator(bool useMachineContext, X509ChainPolicy chainPolicy)
            {
                _chain = X509CertificateValidator.CreateChainTrustValidator(useMachineContext, chainPolicy);
                _peer = (PeerTrustValidator)X509CertificateValidator.PeerTrust;
            }
 
            public override void Validate(X509Certificate2 certificate)
            {
                if (certificate == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(certificate));
                }
 
                Exception exception;
                if (_peer.TryValidate(certificate, out exception))
                {
                    return;
                }
 
                try
                {
                    _chain.Validate(certificate);
                }
                catch (SecurityTokenValidationException ex)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(exception.Message + " " + ex.Message));
                }
            }
        }
    }
}