| File: System\Security\Cryptography\X509Certificates\X509Chain.cs | Web Access |
| Project: src\runtime\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 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(); } } } }