File: System\ServiceModel\Security\X509ClientCertificateAuthentication.cs
Project: ndp\cdf\src\WCF\ServiceModel\System.ServiceModel.csproj (System.ServiceModel)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
 
namespace System.ServiceModel.Security
{
    using System.IdentityModel.Selectors;
    using System.ServiceModel;
    using System.Security.Cryptography;
    using System.Security.Cryptography.X509Certificates;
    using Text;
    using IdentityModel.Tokens;
 
    public class X509ClientCertificateAuthentication
    {
        internal const X509CertificateValidationMode DefaultCertificateValidationMode = X509CertificateValidationMode.ChainTrust;
        internal const X509RevocationMode DefaultRevocationMode = X509RevocationMode.Online;
        internal const StoreLocation DefaultTrustedStoreLocation = StoreLocation.LocalMachine;
        internal const bool DefaultMapCertificateToWindowsAccount = false;
        static X509CertificateValidator defaultCertificateValidator;
 
        X509CertificateValidationMode certificateValidationMode = DefaultCertificateValidationMode;
        X509RevocationMode revocationMode = DefaultRevocationMode;
        StoreLocation trustedStoreLocation = DefaultTrustedStoreLocation;
        X509CertificateValidator customCertificateValidator = null;
        bool mapClientCertificateToWindowsAccount = DefaultMapCertificateToWindowsAccount;
        bool includeWindowsGroups = SspiSecurityTokenProvider.DefaultExtractWindowsGroupClaims;
        bool isReadOnly;
 
        internal X509ClientCertificateAuthentication()
        {
        }
 
        internal X509ClientCertificateAuthentication(X509ClientCertificateAuthentication other)
        {
            this.certificateValidationMode = other.certificateValidationMode;
            this.customCertificateValidator = other.customCertificateValidator;
            this.includeWindowsGroups = other.includeWindowsGroups;
            this.mapClientCertificateToWindowsAccount = other.mapClientCertificateToWindowsAccount;
            this.trustedStoreLocation = other.trustedStoreLocation;
            this.revocationMode = other.revocationMode;
            this.isReadOnly = other.isReadOnly;
        }
 
        internal static X509CertificateValidator DefaultCertificateValidator
        {
            get
            {
                if (defaultCertificateValidator == null)
                {
                    bool useMachineContext = DefaultTrustedStoreLocation == StoreLocation.LocalMachine;
                    X509ChainPolicy chainPolicy = new X509ChainPolicy();
                    chainPolicy.RevocationMode = DefaultRevocationMode;
 
                    if (!ServiceModelAppSettings.UseLegacyCertificateUsagePolicy)
                    {
                        defaultCertificateValidator = new ClientChainTrustValidator(useMachineContext, chainPolicy);
                    }
                    else
                    {
                        defaultCertificateValidator = X509CertificateValidator.CreateChainTrustValidator(useMachineContext, chainPolicy);
                    }
                }
 
                return defaultCertificateValidator;
            }
        }
 
        public X509CertificateValidationMode CertificateValidationMode 
        { 
            get 
            { 
                return this.certificateValidationMode; 
            }
            set 
            {
                X509CertificateValidationModeHelper.Validate(value);
                ThrowIfImmutable();
                this.certificateValidationMode = value; 
            }
        }
 
        public X509RevocationMode RevocationMode 
        {
            get 
            { 
                return this.revocationMode; 
            }
            set 
            {
                ThrowIfImmutable();
                this.revocationMode = value; 
            }
        }
 
        public StoreLocation TrustedStoreLocation
        {
            get 
            { 
                return this.trustedStoreLocation; 
            }
            set 
            {
                ThrowIfImmutable();
                this.trustedStoreLocation = value; 
            }
        }
 
        public X509CertificateValidator CustomCertificateValidator
        {
            get
            {
                return this.customCertificateValidator;
            }
            set
            {
                ThrowIfImmutable();
                this.customCertificateValidator = value;
            }
        }
 
        public bool MapClientCertificateToWindowsAccount
        {
            get
            {
                return this.mapClientCertificateToWindowsAccount;
            }
            set
            {
                ThrowIfImmutable();
                this.mapClientCertificateToWindowsAccount = value;
            }
        }
 
        public bool IncludeWindowsGroups
        {
            get
            {
                return this.includeWindowsGroups;
            }
            set
            {
                ThrowIfImmutable();
                this.includeWindowsGroups = value;
            }
        }
 
        internal X509CertificateValidator GetCertificateValidator()
        {
            if (this.certificateValidationMode == X509CertificateValidationMode.None)
            {
                return X509CertificateValidator.None;
            }
            else if (this.certificateValidationMode == X509CertificateValidationMode.PeerTrust)
            {
                return X509CertificateValidator.PeerTrust;
            }
            else if (this.certificateValidationMode == X509CertificateValidationMode.Custom)
            {
                if (this.customCertificateValidator == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.MissingCustomCertificateValidator)));
                }
                return this.customCertificateValidator;
            }
            else
            {
                bool useMachineContext = this.trustedStoreLocation == StoreLocation.LocalMachine;
                X509ChainPolicy chainPolicy = new X509ChainPolicy();
                chainPolicy.RevocationMode = this.revocationMode;
 
                if (!ServiceModelAppSettings.UseLegacyCertificateUsagePolicy)
                {
                    if (this.certificateValidationMode == X509CertificateValidationMode.ChainTrust)
                    {
                        return new ClientChainTrustValidator(useMachineContext, chainPolicy);
                    }
 
                    return new ClientPeerOrChainTrustValidator(useMachineContext, chainPolicy);
                }
                else
                {
                    if (this.certificateValidationMode == X509CertificateValidationMode.ChainTrust)
                    {
                        return X509CertificateValidator.CreateChainTrustValidator(useMachineContext, chainPolicy);
                    }
 
                    return X509CertificateValidator.CreatePeerOrChainTrustValidator(useMachineContext, chainPolicy);
                }
            }
        }
 
        internal void MakeReadOnly()
        {
            this.isReadOnly = true;
        }
 
        void ThrowIfImmutable()
        {
            if (this.isReadOnly)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ObjectIsReadOnly)));
            }
        }
 
        // Code based off from IdentityModel's X509CertificateValidator.CreateChainTrustValidator
        private class ClientChainTrustValidator : X509CertificateValidator
        {
            bool useMachineContext;
            X509ChainPolicy chainPolicy;
            static readonly X509ChainPolicy OidChainPolicy;
 
            static ClientChainTrustValidator()
            {
                // ASN.1 description: {iso(1) identified-organization(3) dod(6) internet(1) security(5) mechanisms(5) pkix(7) kp(3) clientAuth(2)}
                Oid clientAuthOid = new Oid("1.3.6.1.5.5.7.3.2", "1.3.6.1.5.5.7.3.2");
 
                X509ChainPolicy policy = new X509ChainPolicy();
                policy.ApplicationPolicy.Add(clientAuthOid);
                policy.RevocationMode = X509RevocationMode.NoCheck;
                OidChainPolicy = policy;
            }
 
            public ClientChainTrustValidator(bool useMachineContext, X509ChainPolicy chainPolicy)
            {
                if (chainPolicy == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("chainPolicy");
                }
 
                this.useMachineContext = useMachineContext;
                this.chainPolicy = chainPolicy;
            }
 
            public override void Validate(X509Certificate2 certificate)
            {
                if (certificate == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("certificate");
                }
 
                Exception exception;
 
                if (!TryValidate(certificate, out exception))
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(exception);
                }
            }
 
            internal bool TryValidate(X509Certificate2 certificate, out Exception exception)
            {
                using (X509Chain chain = new X509Chain(this.useMachineContext))
                {                    
                    chain.ChainPolicy = this.chainPolicy;
                    chain.ChainPolicy.VerificationTime = DateTime.Now;
 
                    if (!chain.Build(certificate))
                    {
                        exception = new SecurityTokenValidationException(SR.GetString(SR.X509ChainBuildFail,
                            SecurityUtils.GetCertificateId(certificate), GetChainStatusInformation(chain.ChainStatus)));
                        return false;
                    }
 
                    if (chain.ChainElements.Count > 1)  //is not self-signed
                    {
                        chain.ChainPolicy = OidChainPolicy;
                        chain.ChainPolicy.VerificationTime = DateTime.Now;
 
                        X509Certificate2 cert = chain.ChainElements[1].Certificate;
 
                        if (!chain.Build(cert))
                        {
                            exception = new SecurityTokenValidationException(SR.GetString(SR.X509ChainBuildFail,
                                SecurityUtils.GetCertificateId(certificate), GetChainStatusInformation(chain.ChainStatus)));
                            return false;
                        }
                    }
 
                    exception = null;
                    return true;
                }
            }
 
            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;
            }
        }
 
        // Code based off from IdentityModel's X509CertificateValidator.CreatePeerOrChainTrustValidator
        private class ClientPeerOrChainTrustValidator : X509CertificateValidator
        {
            ClientChainTrustValidator chain;
            X509CertificateValidator peer;
 
            public ClientPeerOrChainTrustValidator(bool useMachineContext, X509ChainPolicy chainPolicy)
            {
                this.chain = new ClientChainTrustValidator(useMachineContext, chainPolicy);
                this.peer = X509CertificateValidator.PeerTrust;
            }
 
            public override void Validate(X509Certificate2 certificate)
            {
                if (certificate == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("certificate");
                }
 
                Exception exception;
                if (this.chain.TryValidate(certificate, out exception))
                {
                    return;
                }
 
                try
                {
                    this.peer.Validate(certificate);
                }
                catch (SecurityTokenValidationException ex)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(ex.Message + " " + exception.Message));
                }
            }
        }
    }
}