File: System\Security\Cryptography\X509Certificates\X509Chain.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Runtime.Versioning;
 
using SafeX509ChainHandle = Microsoft.Win32.SafeHandles.SafeX509ChainHandle;
 
namespace System.Security.Cryptography.X509Certificates
{
    public class X509Chain : IDisposable
    {
        private X509ChainPolicy? _chainPolicy;
        private volatile X509ChainStatus[]? _lazyChainStatus;
        private X509ChainElementCollection? _chainElements;
        private IChainPal? _pal;
        private bool _useMachineContext;
        private readonly object _syncRoot = new object();
 
        public X509Chain() { }
 
        public X509Chain(bool useMachineContext)
        {
            _useMachineContext = useMachineContext;
        }
 
        [SupportedOSPlatform("windows")]
        public X509Chain(IntPtr chainContext)
        {
            _pal = ChainPal.FromHandle(chainContext);
            Debug.Assert(_pal != null);
            _chainElements = new X509ChainElementCollection(_pal.ChainElements!);
        }
 
        public static X509Chain Create()
        {
            return new X509Chain();
        }
 
        public X509ChainElementCollection ChainElements => _chainElements ??= new X509ChainElementCollection();
 
        public X509ChainPolicy ChainPolicy
        {
            get => _chainPolicy ??= new X509ChainPolicy();
            set
            {
                ArgumentNullException.ThrowIfNull(value);
                _chainPolicy = value;
            }
        }
 
        public X509ChainStatus[] ChainStatus
        {
            get
            {
                // We give the user a reference to the array since we'll never access it.
                return _lazyChainStatus ??= (_pal == null ? Array.Empty<X509ChainStatus>() : _pal.ChainStatus!);
            }
        }
 
        public IntPtr ChainContext
        {
            get
            {
                SafeX509ChainHandle? handle = SafeHandle;
                if (handle == null)
                {
                    // This case will only exist for Unix
                    return IntPtr.Zero;
                }
 
                // For .NET Framework compat, we may return an invalid handle here (IntPtr.Zero)
                return handle.DangerousGetHandle();
            }
        }
 
        public SafeX509ChainHandle? SafeHandle
        {
            get
            {
                if (_pal == null)
                    return SafeX509ChainHandle.InvalidHandle;
 
                return _pal.SafeHandle;
            }
        }
 
        [UnsupportedOSPlatform("browser")]
        public bool Build(X509Certificate2 certificate)
        {
            return Build(certificate, true);
        }
 
        internal bool Build(X509Certificate2 certificate, bool throwOnException)
        {
            lock (_syncRoot)
            {
                if (certificate == null || certificate.Pal == null)
                    throw new ArgumentException(SR.Cryptography_InvalidContextHandle, nameof(certificate));
 
                if (_chainPolicy != null)
                {
                    if (_chainPolicy._customTrustStore != null)
                    {
                        if (_chainPolicy.TrustMode == X509ChainTrustMode.System && _chainPolicy.CustomTrustStore.Count > 0)
                            throw new CryptographicException(SR.Cryptography_CustomTrustCertsInSystemMode);
 
                        foreach (X509Certificate2 customCertificate in _chainPolicy.CustomTrustStore)
                        {
                            if (customCertificate == null || customCertificate.Handle == IntPtr.Zero)
                            {
                                throw new CryptographicException(SR.Cryptography_InvalidTrustCertificate);
                            }
                        }
                    }
 
                    if (_chainPolicy.TrustMode == X509ChainTrustMode.CustomRootTrust && _chainPolicy._customTrustStore == null)
                    {
                        _chainPolicy._customTrustStore = new X509Certificate2Collection();
                    }
                }
 
                Reset();
 
                X509ChainPolicy chainPolicy = ChainPolicy;
                _pal = ChainPal.BuildChain(
                    _useMachineContext,
                    certificate.Pal,
                    chainPolicy._extraStore,
                    chainPolicy._applicationPolicy!,
                    chainPolicy._certificatePolicy!,
                    chainPolicy.RevocationMode,
                    chainPolicy.RevocationFlag,
                    chainPolicy._customTrustStore,
                    chainPolicy.TrustMode,
                    chainPolicy.VerificationTimeIgnored ? DateTime.Now : chainPolicy.VerificationTime,
                    chainPolicy.UrlRetrievalTimeout,
                    chainPolicy.DisableCertificateDownloads);
 
                bool success = false;
                if (_pal is not null)
                {
                    _chainElements = new X509ChainElementCollection(_pal.ChainElements!);
 
                    Exception? verificationException;
                    bool? verified = _pal.Verify(chainPolicy.VerificationFlags, out verificationException);
                    if (!verified.HasValue)
                    {
                        if (throwOnException)
                        {
                            throw verificationException!;
                        }
                        else
                        {
                            verified = false;
                        }
                    }
 
                    success = verified.Value;
                }
 
                // There are two reasons success might be false here.
                //
                // The most common reason is that we built the chain but the chain appears to run
                // afoul of policy. This is represented by BuildChain returning a non-null object
                // and storing potential policy violations in the chain structure. The public Build
                // method returns false to the caller, and the caller can inspect the ChainStatus
                // and ChainElements properties and evaluate the failure reason against app-level
                // policies. If the caller does not care about these policy violations, they can
                // choose to ignore them and to treat chain building as successful.
                //
                // The other type of failure is that BuildChain simply can't build the chain at all.
                // Perhaps something within the certificate is not valid or is unsupported, or perhaps
                // there's an internal failure within the OS layer we're invoking, etc. Whatever the
                // reason, we're not meaningfully able to initialize the ChainStatus property, which
                // means callers may observe a non-empty list of policy violations. Depending on the
                // caller's logic, they might incorrectly interpret this as there being no policy
                // violations at all, which means they might treat this condition as success.
                //
                // To avoid callers misinterpreting this latter condition as success, we'll throw an
                // exception, which matches general .NET API behavior when a method cannot complete
                // its objective. If throwOnException is false, it means the caller explicitly wants
                // to suppress exceptions and normalize them to a false return value.
 
                if (!success && throwOnException && _pal?.ChainStatus is not { Length: > 0 })
                {
                    throw new CryptographicException(SR.Cryptography_X509_ChainBuildingFailed);
                }
 
                return success;
            }
        }
 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                Reset();
            }
        }
 
        public void Reset()
        {
            // _chainPolicy is not reset for .NET Framework compat
            _lazyChainStatus = null;
            _chainElements = null;
            _useMachineContext = false;
 
            IChainPal? pal = _pal;
            if (pal != null)
            {
                _pal = null;
                pal.Dispose();
            }
        }
    }
}