File: System\Security\Cryptography\X509Certificates\UnixChainVerifier.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;
using Internal.Cryptography;
 
namespace System.Security.Cryptography.X509Certificates
{
    internal static class UnixChainVerifier
    {
        public static bool Verify(X509ChainElement[] chainElements, X509VerificationFlags flags)
        {
            bool isEndEntity = true;
 
            foreach (X509ChainElement element in chainElements)
            {
                if (HasUnsuppressedError(flags, element, isEndEntity))
                {
                    return false;
                }
 
                isEndEntity = false;
            }
 
            return true;
        }
 
        private static bool HasUnsuppressedError(X509VerificationFlags flags, X509ChainElement element, bool isEndEntity)
        {
            foreach (X509ChainStatus status in element.ChainElementStatus)
            {
                if (status.Status == X509ChainStatusFlags.NoError)
                {
                    return false;
                }
 
                Debug.Assert(
                    (status.Status & (status.Status - 1)) == 0,
                    $"Only one bit should be set in status.Status ({status})");
 
                // The Windows certificate store API only checks the time error for a "peer trust" certificate,
                // but we don't have a concept for that in Unix.  If we did, we'd need to do that logic that here.
                // Note also that the logic is skipped if CERT_CHAIN_POLICY_IGNORE_PEER_TRUST_FLAG is set.
 
                X509VerificationFlags? suppressionFlag;
 
                if (status.Status == X509ChainStatusFlags.RevocationStatusUnknown)
                {
                    if (isEndEntity)
                    {
                        suppressionFlag = X509VerificationFlags.IgnoreEndRevocationUnknown;
                    }
                    else if (IsSelfSigned(element.Certificate))
                    {
                        suppressionFlag = X509VerificationFlags.IgnoreRootRevocationUnknown;
                    }
                    else
                    {
                        suppressionFlag = X509VerificationFlags.IgnoreCertificateAuthorityRevocationUnknown;
                    }
                }
                else if (status.Status == X509ChainStatusFlags.OfflineRevocation)
                {
                    // This is mainly a warning code, it's redundant to RevocationStatusUnknown
                    continue;
                }
                else
                {
                    suppressionFlag = GetSuppressionFlag(status.Status);
                }
 
                // If an error was found, and we do NOT have the suppression flag for it enabled,
                // we have an unsuppressed error, so return true. (If there's no suppression for a given code,
                // we (by definition) don't have that flag set.
                if (!suppressionFlag.HasValue ||
                    (flags & suppressionFlag) == 0)
                {
                    return true;
                }
            }
 
            return false;
        }
 
        private static bool IsSelfSigned(X509Certificate2 cert)
        {
            return cert.SubjectName.RawData.ContentsEqual(cert.IssuerName.RawData);
        }
 
        private static X509VerificationFlags? GetSuppressionFlag(X509ChainStatusFlags status)
        {
            switch (status)
            {
                case X509ChainStatusFlags.UntrustedRoot:
                case X509ChainStatusFlags.PartialChain:
                    return X509VerificationFlags.AllowUnknownCertificateAuthority;
 
                case X509ChainStatusFlags.NotValidForUsage:
                case X509ChainStatusFlags.CtlNotValidForUsage:
                    return X509VerificationFlags.IgnoreWrongUsage;
 
                case X509ChainStatusFlags.NotTimeValid:
                    return X509VerificationFlags.IgnoreNotTimeValid;
 
                case X509ChainStatusFlags.CtlNotTimeValid:
                    return X509VerificationFlags.IgnoreCtlNotTimeValid;
 
                case X509ChainStatusFlags.InvalidNameConstraints:
                case X509ChainStatusFlags.HasNotSupportedNameConstraint:
                case X509ChainStatusFlags.HasNotDefinedNameConstraint:
                case X509ChainStatusFlags.HasNotPermittedNameConstraint:
                case X509ChainStatusFlags.HasExcludedNameConstraint:
                    return X509VerificationFlags.IgnoreInvalidName;
 
                case X509ChainStatusFlags.InvalidPolicyConstraints:
                case X509ChainStatusFlags.NoIssuanceChainPolicy:
                    return X509VerificationFlags.IgnoreInvalidPolicy;
 
                case X509ChainStatusFlags.InvalidBasicConstraints:
                    return X509VerificationFlags.IgnoreInvalidBasicConstraints;
 
                case X509ChainStatusFlags.HasNotSupportedCriticalExtension:
                    // This field would be mapped in by AllFlags, but we don't have a name for it currently.
                    return (X509VerificationFlags)0x00002000;
 
                case X509ChainStatusFlags.NotTimeNested:
                    return X509VerificationFlags.IgnoreNotTimeNested;
            }
 
            return null;
        }
    }
}