File: System\Security\Cryptography\X509Certificates\ChainPal.OpenSsl.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.Collections.Generic;
using System.Diagnostics;
 
namespace System.Security.Cryptography.X509Certificates
{
    internal sealed partial class ChainPal
    {
        private static readonly TimeSpan s_maxUrlRetrievalTimeout = TimeSpan.FromMinutes(1);
 
#pragma warning disable IDE0060
        internal static partial IChainPal FromHandle(IntPtr chainContext)
        {
            throw new PlatformNotSupportedException();
        }
 
        internal static partial bool ReleaseSafeX509ChainHandle(IntPtr handle)
        {
            return true;
        }
#pragma warning restore IDE0060
 
        public static void FlushStores()
        {
            OpenSslX509ChainProcessor.FlushStores();
        }
 
        internal static partial IChainPal? BuildChain(
            bool useMachineContext,
            ICertificatePal cert,
            X509Certificate2Collection? extraStore,
            OidCollection? applicationPolicy,
            OidCollection? certificatePolicy,
            X509RevocationMode revocationMode,
            X509RevocationFlag revocationFlag,
            X509Certificate2Collection? customTrustStore,
            X509ChainTrustMode trustMode,
            DateTime verificationTime,
            TimeSpan timeout,
            bool disableAia)
        {
            if (OpenSslX509ChainEventSource.Log.IsEnabled())
            {
                OpenSslX509ChainEventSource.Log.ChainStart();
            }
 
            try
            {
                return BuildChainCore(
                    useMachineContext,
                    cert,
                    extraStore,
                    applicationPolicy,
                    certificatePolicy,
                    revocationMode,
                    revocationFlag,
                    customTrustStore,
                    trustMode,
                    verificationTime,
                    timeout,
                    disableAia);
            }
            finally
            {
                if (OpenSslX509ChainEventSource.Log.IsEnabled())
                {
                    OpenSslX509ChainEventSource.Log.ChainStop();
                }
            }
        }
 
        private static OpenSslX509ChainProcessor? BuildChainCore(
            bool useMachineContext,
            ICertificatePal cert,
            X509Certificate2Collection? extraStore,
            OidCollection? applicationPolicy,
            OidCollection? certificatePolicy,
            X509RevocationMode revocationMode,
            X509RevocationFlag revocationFlag,
            X509Certificate2Collection? customTrustStore,
            X509ChainTrustMode trustMode,
            DateTime verificationTime,
            TimeSpan timeout,
            bool disableAia)
        {
            if (timeout == TimeSpan.Zero)
            {
                // An input value of 0 on the timeout is treated as 15 seconds, to match Windows.
                timeout = TimeSpan.FromSeconds(15);
            }
            else if (timeout > s_maxUrlRetrievalTimeout || timeout < TimeSpan.Zero)
            {
                // Windows has a max timeout of 1 minute, so we'll match. Windows also treats
                // the timeout as unsigned, so a negative value gets treated as a large positive
                // value that is also clamped.
                timeout = s_maxUrlRetrievalTimeout;
            }
 
            // Let Unspecified mean Local, so only convert if the source was UTC.
            //
            // Converge on Local instead of UTC because OpenSSL is going to assume we gave it
            // local time.
            if (verificationTime.Kind == DateTimeKind.Utc)
            {
                verificationTime = verificationTime.ToLocalTime();
            }
 
            // Until we support the Disallowed store, ensure it's empty (which is done by the ctor)
            using (new X509Store(StoreName.Disallowed, StoreLocation.CurrentUser, OpenFlags.ReadOnly))
            {
            }
 
            TimeSpan downloadTimeout = timeout;
 
            OpenSslX509ChainProcessor chainPal = OpenSslX509ChainProcessor.InitiateChain(
                ((OpenSslX509CertificateReader)cert).SafeHandle,
                customTrustStore,
                trustMode,
                verificationTime,
                downloadTimeout);
 
            Interop.Crypto.X509VerifyStatusCode status = chainPal.FindFirstChain(extraStore);
 
            if (OpenSslX509ChainEventSource.Log.IsEnabled())
            {
                OpenSslX509ChainEventSource.Log.FindFirstChainFinished(status);
            }
 
            if (!OpenSslX509ChainProcessor.IsCompleteChain(status))
            {
                if (disableAia)
                {
                    if (OpenSslX509ChainEventSource.Log.IsEnabled())
                    {
                        OpenSslX509ChainEventSource.Log.AiaDisabled();
                    }
                }
                else
                {
                    List<X509Certificate2>? tmp = null;
                    status = chainPal.FindChainViaAia(ref tmp);
 
                    if (OpenSslX509ChainEventSource.Log.IsEnabled())
                    {
                        OpenSslX509ChainEventSource.Log.FindChainViaAiaFinished(status, tmp?.Count ?? 0);
                    }
 
                    if (tmp != null)
                    {
                        if (status == Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
                        {
                            SaveIntermediateCertificates(tmp);
                        }
 
                        foreach (X509Certificate2 downloaded in tmp)
                        {
                            downloaded.Dispose();
                        }
                    }
                }
            }
 
            chainPal.CommitToChain();
 
            if (revocationMode != X509RevocationMode.NoCheck)
            {
                if (OpenSslX509ChainProcessor.IsCompleteChain(status))
                {
                    // Checking the validity period for the certificates in the chain is done after the
                    // check for a trusted root, so accept expired (or not yet valid) as acceptable for
                    // processing revocation.
                    if (status != Interop.Crypto.X509VerifyStatusCode.X509_V_OK &&
                        status != Interop.Crypto.X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_NOT_YET_VALID &&
                        status != Interop.Crypto.X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_HAS_EXPIRED)
                    {
                        if (OpenSslX509ChainEventSource.Log.IsEnabled())
                        {
                            OpenSslX509ChainEventSource.Log.UntrustedChainWithRevocation();
                        }
 
                        revocationMode = X509RevocationMode.NoCheck;
                    }
 
                    chainPal.ProcessRevocation(revocationMode, revocationFlag);
                }
            }
 
            chainPal.Finish(applicationPolicy, certificatePolicy);
 
#if DEBUG
            if (chainPal.ChainElements!.Length > 0)
            {
                X509Certificate2 reportedLeaf = chainPal.ChainElements[0].Certificate;
                Debug.Assert(reportedLeaf != null);
                Debug.Assert(!ReferenceEquals(cert, reportedLeaf.Pal));
            }
#endif
            return chainPal;
        }
 
        private static void SaveIntermediateCertificates(List<X509Certificate2> downloadedCerts)
        {
            using (var userIntermediate = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
            {
                try
                {
                    userIntermediate.Open(OpenFlags.ReadWrite);
                }
                catch (CryptographicException)
                {
                    // Saving is opportunistic, just ignore failures
 
                    if (OpenSslX509ChainEventSource.Log.IsEnabled())
                    {
                        OpenSslX509ChainEventSource.Log.CouldNotOpenCAStore();
                    }
 
                    return;
                }
 
                foreach (X509Certificate2 cert in downloadedCerts)
                {
                    try
                    {
                        if (OpenSslX509ChainEventSource.Log.IsEnabled())
                        {
                            OpenSslX509ChainEventSource.Log.CachingIntermediate(cert);
                        }
 
                        userIntermediate.Add(cert);
                    }
                    catch
                    {
                        // Saving is opportunistic, just ignore failures
 
                        if (OpenSslX509ChainEventSource.Log.IsEnabled())
                        {
                            OpenSslX509ChainEventSource.Log.CachingIntermediateFailedMessage();
                        }
                    }
                }
            }
        }
    }
}