|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Formats.Asn1;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography.Asn1;
using System.Security.Cryptography.X509Certificates.Asn1;
using System.Text;
using Internal.Cryptography;
using Microsoft.Win32.SafeHandles;
using X509VerifyStatusCodeUniversal = Interop.Crypto.X509VerifyStatusCodeUniversal;
namespace System.Security.Cryptography.X509Certificates
{
internal sealed class OpenSslX509ChainProcessor : IChainPal
{
private delegate X509ChainStatusFlags MapVersionSpecificCode(Interop.Crypto.X509VerifyStatusCode code);
// The average chain is 3 (End-Entity, Intermediate, Root)
// 10 is plenty big.
private const int DefaultChainCapacity = 10;
private static readonly OpenSslCachedDirectoryStoreProvider s_userRootStore =
new OpenSslCachedDirectoryStoreProvider(X509Store.RootStoreName);
private static readonly OpenSslCachedDirectoryStoreProvider s_userIntermediateStore =
new OpenSslCachedDirectoryStoreProvider(X509Store.IntermediateCAStoreName);
private static readonly OpenSslCachedDirectoryStoreProvider s_userPersonalStore =
new OpenSslCachedDirectoryStoreProvider(X509Store.MyStoreName);
// Save the results of GetX509VerifyCertErrorString as we look them up.
// On Windows we preload the entire string table, but on Linux we'll delay-load memoize
// to avoid needing to know the upper bound of error codes for the particular build of OpenSSL.
private static readonly ConcurrentDictionary<int, string> s_errorStrings =
new ConcurrentDictionary<int, string>();
private static readonly MapVersionSpecificCode s_mapVersionSpecificCode = GetVersionLookup();
private SafeX509Handle _leafHandle;
private SafeX509StoreHandle _store;
private readonly SafeX509StackHandle _untrustedLookup;
private readonly SafeX509StoreCtxHandle _storeCtx;
private readonly DateTime _verificationTime;
private readonly TimeSpan _downloadTimeout;
private WorkingChain? _workingChain;
private OpenSslX509ChainProcessor(
SafeX509Handle leafHandle,
SafeX509StoreHandle store,
SafeX509StackHandle untrusted,
SafeX509StoreCtxHandle storeCtx,
DateTime verificationTime,
TimeSpan downloadTimeout)
{
_leafHandle = leafHandle;
_store = store;
_untrustedLookup = untrusted;
_storeCtx = storeCtx;
_verificationTime = verificationTime;
_downloadTimeout = downloadTimeout;
}
public void Dispose()
{
_storeCtx?.Dispose();
_untrustedLookup?.Dispose();
_store?.Dispose();
_workingChain?.Dispose();
// We don't own this one.
_leafHandle = null!;
}
public bool? Verify(X509VerificationFlags flags, out Exception? exception)
{
exception = null;
return UnixChainVerifier.Verify(ChainElements!, flags);
}
public X509ChainElement[]? ChainElements { get; private set; }
public X509ChainStatus[]? ChainStatus { get; private set; }
public SafeX509ChainHandle? SafeHandle
{
get { return null; }
}
internal static OpenSslX509ChainProcessor InitiateChain(
SafeX509Handle leafHandle,
X509Certificate2Collection? customTrustStore,
X509ChainTrustMode trustMode,
DateTime verificationTime,
TimeSpan remainingDownloadTime)
{
OpenSslCachedSystemStoreProvider.GetNativeCollections(
out SafeX509StackHandle systemTrust,
out SafeX509StackHandle systemIntermediate);
SafeX509StoreHandle? store = null;
SafeX509StackHandle? untrusted = null;
SafeX509StoreCtxHandle? storeCtx = null;
try
{
untrusted = Interop.Crypto.NewX509Stack();
Interop.Crypto.X509StackAddMultiple(untrusted, s_userIntermediateStore.GetNativeCollection());
Interop.Crypto.X509StackAddMultiple(untrusted, s_userPersonalStore.GetNativeCollection());
store = GetTrustStore(trustMode, customTrustStore, untrusted, systemTrust);
Interop.Crypto.X509StackAddMultiple(untrusted, systemIntermediate);
Interop.Crypto.X509StoreSetVerifyTime(store, verificationTime);
storeCtx = Interop.Crypto.X509StoreCtxCreate();
if (!Interop.Crypto.X509StoreCtxInit(storeCtx, store, leafHandle, untrusted))
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}
return new OpenSslX509ChainProcessor(
leafHandle,
store,
untrusted,
storeCtx,
verificationTime,
remainingDownloadTime);
}
catch
{
store?.Dispose();
untrusted?.Dispose();
storeCtx?.Dispose();
throw;
}
}
private static SafeX509StoreHandle GetTrustStore(
X509ChainTrustMode trustMode,
X509Certificate2Collection? customTrustStore,
SafeX509StackHandle untrusted,
SafeX509StackHandle systemTrust)
{
if (trustMode == X509ChainTrustMode.CustomRootTrust)
{
using (SafeX509StackHandle customTrust = Interop.Crypto.NewX509Stack())
{
if (customTrustStore != null)
{
foreach (X509Certificate2 cert in customTrustStore)
{
SafeX509StackHandle toAdd =
cert.SubjectName.RawData.ContentsEqual(cert.IssuerName.RawData) ?
customTrust :
untrusted;
AddToStackAndUpRef(((OpenSslX509CertificateReader)cert.Pal!).SafeHandle, toAdd);
}
}
return Interop.Crypto.X509ChainNew(customTrust, SafeX509StackHandle.InvalidHandle);
}
}
return Interop.Crypto.X509ChainNew(systemTrust, s_userRootStore.GetNativeCollection());
}
internal Interop.Crypto.X509VerifyStatusCode FindFirstChain(X509Certificate2Collection? extraCerts)
{
SafeX509StoreCtxHandle storeCtx = _storeCtx;
// While this returns true/false, at this stage we care more about the detailed error code.
Interop.Crypto.X509VerifyCert(storeCtx);
Interop.Crypto.X509VerifyStatusCode statusCode = Interop.Crypto.X509StoreCtxGetError(storeCtx);
if (IsCompleteChain(statusCode))
{
return statusCode;
}
SafeX509StackHandle untrusted = _untrustedLookup;
if (extraCerts?.Count > 0)
{
foreach (X509Certificate2 cert in extraCerts)
{
AddToStackAndUpRef(((OpenSslX509CertificateReader)cert.Pal!).SafeHandle, untrusted);
}
Interop.Crypto.X509StoreCtxRebuildChain(storeCtx);
statusCode = Interop.Crypto.X509StoreCtxGetError(storeCtx);
}
return statusCode;
}
internal static bool IsCompleteChain(Interop.Crypto.X509VerifyStatusCode statusCode)
{
switch (statusCode.UniversalCode)
{
case X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
case X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
return false;
default:
return true;
}
}
internal Interop.Crypto.X509VerifyStatusCode FindChainViaAia(
ref List<X509Certificate2>? downloadedCerts)
{
IntPtr lastCert = IntPtr.Zero;
SafeX509StoreCtxHandle storeCtx = _storeCtx;
Interop.Crypto.X509VerifyStatusCode statusCode =
X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT;
while (!IsCompleteChain(statusCode))
{
using (SafeX509Handle currentCert = Interop.Crypto.X509StoreCtxGetCurrentCert(storeCtx))
{
IntPtr currentHandle = currentCert.DangerousGetHandle();
// No progress was made, give up.
if (currentHandle == lastCert)
{
break;
}
lastCert = currentHandle;
ArraySegment<byte> authorityInformationAccess =
OpenSslX509CertificateReader.FindFirstExtension(
currentCert,
Oids.AuthorityInformationAccess);
if (authorityInformationAccess.Count == 0)
{
if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.NoAiaFound(currentCert);
}
break;
}
X509Certificate2? downloaded = DownloadCertificate(
authorityInformationAccess,
_downloadTimeout);
// The AIA record is contained in a public structure, so no need to clear it.
CryptoPool.Return(authorityInformationAccess.Array!, clearSize: 0);
if (downloaded == null)
{
break;
}
downloadedCerts ??= new List<X509Certificate2>();
AddToStackAndUpRef(downloaded.Handle, _untrustedLookup);
downloadedCerts.Add(downloaded);
Interop.Crypto.X509StoreCtxRebuildChain(storeCtx);
statusCode = Interop.Crypto.X509StoreCtxGetError(storeCtx);
}
}
if (statusCode == Interop.Crypto.X509VerifyStatusCode.X509_V_OK && downloadedCerts != null)
{
using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(_storeCtx))
{
int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack);
Span<IntPtr> tempChain = stackalloc IntPtr[DefaultChainCapacity];
byte[]? tempChainRent = null;
if (chainSize <= tempChain.Length)
{
tempChain = tempChain.Slice(0, chainSize);
}
else
{
int targetSize = checked(chainSize * IntPtr.Size);
tempChainRent = CryptoPool.Rent(targetSize);
tempChain = MemoryMarshal.Cast<byte, IntPtr>(tempChainRent.AsSpan(0, targetSize));
}
for (int i = 0; i < chainSize; i++)
{
tempChain[i] = Interop.Crypto.GetX509StackField(chainStack, i);
}
// In the average case we never made it here.
//
// Given that we made it here, in the average remaining case
// we are doing a one item for which will match in the second position
// of an (on-average) 3 item collection.
//
// The only case where this loop really matters is if downloading the
// certificate made an alternate chain better, which may have resulted in
// an extra download and made the first one not be involved any longer. In
// that case, it's a 2 item for loop matching against a three item set.
//
// So N*M is well contained.
for (int i = downloadedCerts.Count - 1; i >= 0; i--)
{
X509Certificate2 downloadedCert = downloadedCerts[i];
if (!tempChain.Contains(downloadedCert.Handle))
{
downloadedCert.Dispose();
downloadedCerts.RemoveAt(i);
}
}
if (downloadedCerts.Count == 0)
{
downloadedCerts = null;
}
if (tempChainRent != null)
{
CryptoPool.Return(tempChainRent);
}
}
}
return statusCode;
}
internal void CommitToChain()
{
Interop.Crypto.X509StoreCtxCommitToChain(_storeCtx);
}
internal static void FlushStores()
{
if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.FlushStores();
}
s_userRootStore.DoRefresh();
s_userPersonalStore.DoRefresh();
s_userIntermediateStore.DoRefresh();
}
internal void ProcessRevocation(
X509RevocationMode revocationMode,
X509RevocationFlag revocationFlag)
{
int chainSize;
int revocationSize;
using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(_storeCtx))
{
chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack);
if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.RevocationCheckStart(revocationMode, revocationFlag, chainSize);
}
switch (revocationFlag)
{
case X509RevocationFlag.EndCertificateOnly:
revocationSize = 1;
break;
case X509RevocationFlag.ExcludeRoot:
revocationSize = chainSize - 1;
break;
default:
Debug.Assert(revocationFlag == X509RevocationFlag.EntireChain);
revocationSize = chainSize;
break;
}
if (revocationMode != X509RevocationMode.NoCheck)
{
for (int i = 0; i < revocationSize; i++)
{
if (i == 0 && Interop.Crypto.X509ChainHasStapledOcsp(_storeCtx))
{
if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.StapledOcspPresent();
}
}
else
{
using (SafeX509Handle cert =
Interop.Crypto.X509UpRef(Interop.Crypto.GetX509StackField(chainStack, i)))
{
OpenSslCrlCache.AddCrlForCertificate(
cert,
_store,
revocationMode,
_verificationTime,
_downloadTimeout);
}
}
}
}
Interop.Crypto.X509StoreSetRevocationFlag(_store, revocationFlag);
Interop.Crypto.X509StoreCtxRebuildChain(_storeCtx);
}
Interop.Crypto.X509VerifyStatusCode errorCode = Interop.Crypto.X509StoreCtxGetError(_storeCtx);
if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.CrlChainFinished(errorCode);
}
// If anything is wrong, move see if we need to try OCSP,
// or clearing an unwanted root revocation flag.
if (errorCode != Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
{
FinishRevocation(revocationMode, revocationFlag, chainSize);
}
if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.RevocationCheckStop();
}
}
private void FinishRevocation(
X509RevocationMode revocationMode,
X509RevocationFlag revocationFlag,
int chainSize)
{
WorkingChain workingChain = BuildWorkingChain();
// If the chain built and the only error was something we ignore (probably X509_V_ERR_CRL_NOT_YET_VALID)
// then there's nothing to do.
if (workingChain.LastError < 0)
{
if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.AllRevocationErrorsIgnored();
}
return;
}
if (OpenSslX509ChainEventSource.Log.IsEnabled() && OpenSslX509ChainEventSource.Log.ShouldLogElementStatuses())
{
for (int logDepth = 0; logDepth <= workingChain.LastError; logDepth++)
{
OpenSslX509ChainEventSource.Log.RawElementStatus(
logDepth,
workingChain[logDepth],
ErrorCollection.BuildDiagnosticString);
}
}
Interop.Crypto.X509VerifyStatusCode statusCode;
ref ErrorCollection refErrors = ref workingChain[0];
// EndCertificateOnly, not testing a trusted self-signed certificate.
if (revocationFlag == X509RevocationFlag.EndCertificateOnly && chainSize > 1)
{
if (refErrors.HasCorruptRevocation())
{
refErrors.ClearRevoked();
}
if (refErrors.IsRevoked())
{
refErrors.ClearRevocationUnknown();
return;
}
Debug.Assert(refErrors.HasRevocationUnknown());
refErrors.ClearRevocationUnknown();
statusCode = CheckOcsp(0, _leafHandle, revocationMode);
if (statusCode != Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
{
refErrors.Add(statusCode);
}
return;
}
// Either we're self-signed, or we're in EntireChain or ExcludeRoot
using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(_storeCtx))
{
// Root processing is special.
// * OpenSSL doesn't have an ExcludeRoot mode, so if it determines the root
// was revoked it'll assert Revoked, and we need to clear that.
// * In EntireChain we'll process OCSP to clear RevocationStatusUnknown, but
// if OCSP also is inconclusive we report NoError.
int start = chainSize - 1;
bool encounteredRevocation = false;
if (workingChain.LastError >= start)
{
refErrors = ref workingChain[start];
if (refErrors.HasCorruptRevocation())
{
refErrors.ClearRevoked();
}
encounteredRevocation = refErrors.IsRevoked();
Debug.Assert(chainSize == 1 || revocationFlag != X509RevocationFlag.EndCertificateOnly);
// If we're in EntireChain, keep the revoked result.
// If we're in ExcludeRoot, ignore the revoked result.
//
// If we're in EndCertificateOnly the chainSize has to be one, or we already exited...
// since the root IS the end certificate, keep the revoked result.
if (encounteredRevocation && revocationFlag == X509RevocationFlag.ExcludeRoot)
{
refErrors.ClearRevoked();
encounteredRevocation = false;
}
else if (refErrors.HasRevocationUnknown() && revocationFlag != X509RevocationFlag.ExcludeRoot)
{
// If the chain size is 1 we need to copy the root cert into untrusted so
// OCSP_basic_verify can find it.
if (chainSize == 1)
{
using (SafeSharedX509StackHandle untrusted = Interop.Crypto.X509StoreCtxGetSharedUntrusted(_storeCtx))
using (SafeX509Handle upref = Interop.Crypto.X509UpRef(_leafHandle))
{
Interop.Crypto.PushX509StackField(untrusted, upref);
// Ownership moved to the stack
upref.SetHandleAsInvalid();
}
}
IntPtr rootPtr = Interop.Crypto.GetX509StackField(chainStack, start);
using (SafeX509Handle rootHandle = Interop.Crypto.X509UpRef(rootPtr))
{
statusCode = CheckOcsp(start, rootHandle, revocationMode);
}
if (statusCode != Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
{
refErrors.Add(statusCode);
encounteredRevocation = refErrors.IsRevoked();
}
}
// Root is revoked and we care, revoked and we don't care, or it's OK.
// Clear any of the bits that indicate revocation is unknown (for the root).
refErrors.ClearRevocationUnknown();
start--;
}
else
{
Debug.Assert(workingChain.LastError <= start - 1);
start = workingChain.LastError;
}
// For the remaining (issued) levels:
// If any higher level cert is revoked, set RevocationStatusUnknown and clear Revoked.
// If revoked at this level, clear RevocationStatusUnknown (revoked on an expired CRL).
// If revocation status is unknown, ask OCSP
for (int i = start; i >= 0; i--)
{
refErrors = ref workingChain[i];
if (encounteredRevocation)
{
refErrors.ClearRevoked();
refErrors.AddRevocationUnknown();
continue;
}
if (refErrors.HasCorruptRevocation())
{
refErrors.ClearRevoked();
}
if (refErrors.IsRevoked())
{
refErrors.ClearRevocationUnknown();
encounteredRevocation = true;
continue;
}
if (refErrors.HasRevocationUnknown())
{
refErrors.ClearRevocationUnknown();
if (i == 0)
{
statusCode = CheckOcsp(0, _leafHandle, revocationMode);
}
else
{
IntPtr certPtr = Interop.Crypto.GetX509StackField(chainStack, i);
using (SafeX509Handle certHandle = Interop.Crypto.X509UpRef(certPtr))
{
statusCode = CheckOcsp(i, certHandle, revocationMode);
}
}
if (statusCode != Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
{
refErrors.Add(statusCode);
encounteredRevocation = refErrors.IsRevoked();
}
}
}
}
if (OpenSslX509ChainEventSource.Log.IsEnabled() && OpenSslX509ChainEventSource.Log.ShouldLogElementStatuses())
{
for (int logDepth = 0; logDepth <= workingChain.LastError; logDepth++)
{
OpenSslX509ChainEventSource.Log.FinalElementStatus(
logDepth,
workingChain[logDepth],
ErrorCollection.BuildDiagnosticString);
}
}
}
[UnmanagedCallersOnly]
private static unsafe int VerifyCallback(int ok, IntPtr ctx)
{
if (ok != 0)
{
return ok;
}
try
{
using (var storeCtx = new SafeX509StoreCtxHandle(ctx, ownsHandle: false))
{
return ((WorkingChain*)Interop.Crypto.X509StoreCtxGetAppData(storeCtx))->VerifyCallback(storeCtx);
}
}
catch
{
return -1;
}
}
private unsafe WorkingChain BuildWorkingChain()
{
if (_workingChain != null)
{
return _workingChain;
}
WorkingChain workingChain = new WorkingChain();
WorkingChain? extraDispose = null;
Interop.Crypto.X509StoreCtxReset(_storeCtx);
Interop.Crypto.X509StoreCtxSetVerifyCallback(_storeCtx, &VerifyCallback, &workingChain);
bool verify = Interop.Crypto.X509VerifyCert(_storeCtx);
if (workingChain.AbortedForSignatureError)
{
Debug.Assert(!verify, "verify should have returned false for signature error");
CloneChainForSignatureErrors();
// Reset to a WorkingChain that won't fail.
extraDispose = workingChain;
workingChain = new WorkingChain(abortOnSignatureError: false);
Interop.Crypto.X509StoreCtxSetVerifyCallback(_storeCtx, &VerifyCallback, &workingChain);
verify = Interop.Crypto.X509VerifyCert(_storeCtx);
}
// Because our callback tells OpenSSL that every problem is ignorable, it should tell us that the
// chain is just fine (unless it returned a negative code for an exception)
Debug.Assert(verify, "verify should have returned true");
extraDispose?.Dispose();
_workingChain = workingChain;
return workingChain;
}
internal void Finish(OidCollection? applicationPolicy, OidCollection? certificatePolicy)
{
WorkingChain? workingChain = _workingChain;
// If the chain had any errors during the previous build we need to walk it again with
// the error collector running.
if (Interop.Crypto.X509StoreCtxGetError(_storeCtx) != Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
{
workingChain ??= BuildWorkingChain();
}
X509ChainElement[] elements = BuildChainElements(
workingChain,
out List<X509ChainStatus>? overallStatus);
workingChain?.Dispose();
if (applicationPolicy?.Count > 0 || certificatePolicy?.Count > 0)
{
ProcessPolicy(elements, ref overallStatus, applicationPolicy, certificatePolicy);
}
ChainStatus = overallStatus?.ToArray() ?? Array.Empty<X509ChainStatus>();
ChainElements = elements;
// The native resources are not needed any longer.
Dispose();
}
private void CloneChainForSignatureErrors()
{
SafeX509StoreHandle? newStore;
Interop.Crypto.X509StoreCtxResetForSignatureError(_storeCtx, out newStore);
if (newStore != null)
{
_store.Dispose();
_store = newStore;
}
}
private Interop.Crypto.X509VerifyStatusCode CheckOcsp(
int chainDepth,
SafeX509Handle certHandle,
X509RevocationMode revocationMode)
{
if (revocationMode == X509RevocationMode.NoCheck)
{
return X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_CRL;
}
string ocspCache = OpenSslCrlCache.GetCachedOcspResponseDirectory();
Interop.Crypto.X509VerifyStatusCode status =
Interop.Crypto.X509ChainGetCachedOcspStatus(_storeCtx, ocspCache, chainDepth);
if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.OcspResponseFromCache(chainDepth, status);
}
if (status != X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_CRL)
{
return status;
}
if (revocationMode != X509RevocationMode.Online)
{
return X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_CRL;
}
string? baseUri = GetOcspEndpoint(certHandle);
if (baseUri == null)
{
return status;
}
using (SafeOcspRequestHandle req = Interop.Crypto.X509ChainBuildOcspRequest(_storeCtx, chainDepth))
{
ArraySegment<byte> encoded = Interop.Crypto.OpenSslRentEncode(
Interop.Crypto.GetOcspRequestDerSize,
Interop.Crypto.EncodeOcspRequest,
req);
ArraySegment<char> urlEncoded = UrlBase64Encoding.RentEncode(encoded);
string requestUrl = UrlPathAppend(baseUri, urlEncoded);
// Nothing sensitive is in the encoded request (it was sent via HTTP-non-S)
CryptoPool.Return(encoded.Array!, clearSize: 0);
ArrayPool<char>.Shared.Return(urlEncoded.Array!);
// https://tools.ietf.org/html/rfc6960#appendix-A describes both a GET and a POST
// version of an OCSP responder.
//
// Doing POST via the reflection indirection to HttpClient is difficult, and
// CA/Browser Forum Baseline Requirements (version 1.6.3) section 4.9.10
// (On-line Revocation Checking Requirements) says that the GET method must be supported.
//
// So, for now, only try GET.
SafeOcspResponseHandle? resp =
OpenSslCertificateAssetDownloader.DownloadOcspGet(requestUrl, _downloadTimeout);
using (resp)
{
if (resp == null || resp.IsInvalid)
{
return X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_CRL;
}
try
{
System.IO.Directory.CreateDirectory(ocspCache);
}
catch
{
// Opportunistic create, suppress all errors.
}
status = Interop.Crypto.X509ChainVerifyOcsp(_storeCtx, req, resp, ocspCache, chainDepth);
if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.OcspResponseFromDownload(chainDepth, status);
}
return status;
}
}
}
private static string UrlPathAppend(string baseUri, ReadOnlyMemory<char> resource)
{
Debug.Assert(baseUri.Length > 0);
Debug.Assert(resource.Length > 0);
if (baseUri.EndsWith('/'))
{
return string.Concat(baseUri, resource.Span);
}
return string.Concat(baseUri, "/", resource.Span);
}
private X509ChainElement[] BuildChainElements(
WorkingChain? workingChain,
out List<X509ChainStatus>? overallStatus)
{
X509ChainElement[] elements;
overallStatus = null;
List<X509ChainStatus>? statusBuilder = null;
bool overallHasNotSignatureValid = false;
using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(_storeCtx))
{
int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack);
elements = new X509ChainElement[chainSize];
for (int i = 0; i < chainSize; i++)
{
X509ChainStatus[] status = Array.Empty<X509ChainStatus>();
ErrorCollection? elementErrors =
workingChain?.LastError >= i ? (ErrorCollection?)workingChain[i] : null;
if (elementErrors.HasValue && elementErrors.Value.HasErrors)
{
statusBuilder ??= new List<X509ChainStatus>();
overallStatus ??= new List<X509ChainStatus>();
bool hadSignatureNotValid = overallHasNotSignatureValid;
AddElementStatus(elementErrors.Value, statusBuilder, overallStatus, ref overallHasNotSignatureValid);
// Clear NotSignatureValid for the last element when overall chain is not PartialChain or UntrustedRoot.
bool isLastElement = i == chainSize - 1;
if (isLastElement && !hadSignatureNotValid && overallHasNotSignatureValid)
{
if (!ContainsStatus(overallStatus, X509ChainStatusFlags.PartialChain) &&
!ContainsStatus(overallStatus, X509ChainStatusFlags.UntrustedRoot))
{
RemoveStatus(statusBuilder, X509ChainStatusFlags.NotSignatureValid);
RemoveStatus(overallStatus, X509ChainStatusFlags.NotSignatureValid);
}
}
status = statusBuilder.ToArray();
statusBuilder.Clear();
}
IntPtr elementCertPtr = Interop.Crypto.GetX509StackField(chainStack, i);
if (elementCertPtr == IntPtr.Zero)
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}
// Duplicate the certificate handle
X509Certificate2 elementCert = new X509Certificate2(elementCertPtr);
elements[i] = new X509ChainElement(elementCert, status, "");
}
}
return elements;
}
private static void ProcessPolicy(
X509ChainElement[] elements,
ref List<X509ChainStatus>? overallStatus,
OidCollection? applicationPolicy,
OidCollection? certificatePolicy)
{
List<X509Certificate2> certsToRead = new List<X509Certificate2>();
foreach (X509ChainElement element in elements)
{
certsToRead.Add(element.Certificate);
}
CertificatePolicyChain policyChain = new CertificatePolicyChain(certsToRead);
bool failsPolicyChecks = false;
if (certificatePolicy != null)
{
if (!policyChain.MatchesCertificatePolicies(certificatePolicy))
{
failsPolicyChecks = true;
}
}
if (applicationPolicy != null)
{
if (!policyChain.MatchesApplicationPolicies(applicationPolicy))
{
failsPolicyChecks = true;
}
}
if (failsPolicyChecks)
{
overallStatus ??= new List<X509ChainStatus>();
X509ChainStatus chainStatus = new X509ChainStatus
{
Status = X509ChainStatusFlags.NotValidForUsage,
StatusInformation = SR.Chain_NoPolicyMatch,
};
AddUniqueStatus(overallStatus, ref chainStatus);
// No individual element can have seen more errors than the chain overall,
// so avoid regrowth of the list.
var elementStatus = new List<X509ChainStatus>(overallStatus.Count);
for (int i = 0; i < elements.Length; i++)
{
X509ChainElement element = elements[i];
elementStatus.Clear();
elementStatus.AddRange(element.ChainElementStatus);
AddUniqueStatus(elementStatus, ref chainStatus);
elements[i] = new X509ChainElement(
element.Certificate,
elementStatus.ToArray(),
element.Information);
}
}
}
private static void AddElementStatus(
ErrorCollection errorCodes,
List<X509ChainStatus> elementStatus,
List<X509ChainStatus> overallStatus,
ref bool overallHasNotSignatureValid)
{
foreach (Interop.Crypto.X509VerifyStatusCode errorCode in errorCodes)
{
AddElementStatus(errorCode, elementStatus, overallStatus, ref overallHasNotSignatureValid);
}
foreach (X509ChainStatus element in elementStatus)
{
if (element.Status == X509ChainStatusFlags.RevocationStatusUnknown)
{
X509ChainStatus chainStatus = new X509ChainStatus
{
Status = X509ChainStatusFlags.OfflineRevocation,
StatusInformation = GetErrorString(
X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_CRL),
};
elementStatus.Add(chainStatus);
AddUniqueStatus(overallStatus, ref chainStatus);
break;
}
}
}
private static void AddElementStatus(
Interop.Crypto.X509VerifyStatusCode errorCode,
List<X509ChainStatus> elementStatus,
List<X509ChainStatus> overallStatus,
ref bool overallHasNotSignatureValid)
{
X509ChainStatusFlags statusFlag = MapVerifyErrorToChainStatus(errorCode);
Debug.Assert(
(statusFlag & (statusFlag - 1)) == 0,
"Status flag has more than one bit set",
"More than one bit is set in status '{0}' for error code '{1}'",
statusFlag,
errorCode);
foreach (X509ChainStatus currentStatus in elementStatus)
{
if ((currentStatus.Status & statusFlag) != 0)
{
return;
}
}
X509ChainStatus chainStatus = new X509ChainStatus
{
Status = statusFlag,
StatusInformation = GetErrorString(errorCode),
};
elementStatus.Add(chainStatus);
AddUniqueStatus(overallStatus, ref chainStatus);
if (statusFlag == X509ChainStatusFlags.NotSignatureValid)
{
overallHasNotSignatureValid = true;
}
}
private static void AddUniqueStatus(List<X509ChainStatus> list, ref X509ChainStatus status)
{
X509ChainStatusFlags statusCode = status.Status;
for (int i = 0; i < list.Count; i++)
{
if (list[i].Status == statusCode)
{
return;
}
}
list.Add(status);
}
private static bool ContainsStatus(List<X509ChainStatus> list, X509ChainStatusFlags statusCode)
{
for (int i = 0; i < list.Count; i++)
{
if (list[i].Status == statusCode)
{
return true;
}
}
return false;
}
private static void RemoveStatus(List<X509ChainStatus> list, X509ChainStatusFlags statusCode)
{
for (int i = 0; i < list.Count; i++)
{
if (list[i].Status == statusCode)
{
list.RemoveAt(i);
return;
}
}
}
private static X509ChainStatusFlags MapVerifyErrorToChainStatus(Interop.Crypto.X509VerifyStatusCode code)
{
switch (code.UniversalCode)
{
case X509VerifyStatusCodeUniversal.X509_V_OK:
return X509ChainStatusFlags.NoError;
case X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_NOT_YET_VALID:
case X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_HAS_EXPIRED:
case X509VerifyStatusCodeUniversal.X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
case X509VerifyStatusCodeUniversal.X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
return X509ChainStatusFlags.NotTimeValid;
case X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_REVOKED:
return X509ChainStatusFlags.Revoked;
case X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
case X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_SIGNATURE_FAILURE:
return X509ChainStatusFlags.NotSignatureValid;
case X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_UNTRUSTED:
case X509VerifyStatusCodeUniversal.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
case X509VerifyStatusCodeUniversal.X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
return X509ChainStatusFlags.UntrustedRoot;
// When adding to the RevocationStatusUnknown block, ensure these codes are properly
// tested/cleared in the ErrorCollection type.
case X509VerifyStatusCodeUniversal.X509_V_ERR_CRL_HAS_EXPIRED:
case X509VerifyStatusCodeUniversal.X509_V_ERR_CRL_NOT_YET_VALID:
case X509VerifyStatusCodeUniversal.X509_V_ERR_CRL_SIGNATURE_FAILURE:
case X509VerifyStatusCodeUniversal.X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
case X509VerifyStatusCodeUniversal.X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
case X509VerifyStatusCodeUniversal.X509_V_ERR_KEYUSAGE_NO_CRL_SIGN:
case X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
case X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_CRL:
case X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER:
case X509VerifyStatusCodeUniversal.X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION:
return X509ChainStatusFlags.RevocationStatusUnknown;
case X509VerifyStatusCodeUniversal.X509_V_ERR_INVALID_EXTENSION:
return X509ChainStatusFlags.InvalidExtension;
case X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
case X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
case X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
return X509ChainStatusFlags.PartialChain;
case X509VerifyStatusCodeUniversal.X509_V_ERR_INVALID_PURPOSE:
return X509ChainStatusFlags.NotValidForUsage;
case X509VerifyStatusCodeUniversal.X509_V_ERR_INVALID_NON_CA:
case X509VerifyStatusCodeUniversal.X509_V_ERR_PATH_LENGTH_EXCEEDED:
case X509VerifyStatusCodeUniversal.X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
case X509VerifyStatusCodeUniversal.X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE:
return X509ChainStatusFlags.InvalidBasicConstraints;
case X509VerifyStatusCodeUniversal.X509_V_ERR_INVALID_POLICY_EXTENSION:
case X509VerifyStatusCodeUniversal.X509_V_ERR_NO_EXPLICIT_POLICY:
return X509ChainStatusFlags.InvalidPolicyConstraints;
case X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_REJECTED:
return X509ChainStatusFlags.ExplicitDistrust;
case X509VerifyStatusCodeUniversal.X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION:
return X509ChainStatusFlags.HasNotSupportedCriticalExtension;
case X509VerifyStatusCodeUniversal.X509_V_ERR_PERMITTED_VIOLATION:
return X509ChainStatusFlags.HasNotPermittedNameConstraint;
case X509VerifyStatusCodeUniversal.X509_V_ERR_EXCLUDED_VIOLATION:
return X509ChainStatusFlags.HasExcludedNameConstraint;
case X509VerifyStatusCodeUniversal.X509_V_ERR_SUBTREE_MINMAX:
return X509ChainStatusFlags.HasNotSupportedNameConstraint;
case X509VerifyStatusCodeUniversal.X509_V_ERR_UNSUPPORTED_NAME_SYNTAX:
return X509ChainStatusFlags.InvalidNameConstraints;
case X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_CHAIN_TOO_LONG:
throw new CryptographicException();
case X509VerifyStatusCodeUniversal.X509_V_ERR_OUT_OF_MEM:
throw new OutOfMemoryException();
default:
return s_mapVersionSpecificCode(code);
}
}
private static X509ChainStatusFlags MapOpenSsl30Code(Interop.Crypto.X509VerifyStatusCode code)
{
switch (code.Code30)
{
case Interop.Crypto.X509VerifyStatusCode30.X509_V_ERR_INVALID_CA:
return X509ChainStatusFlags.InvalidBasicConstraints;
default:
Debug.Fail("Unrecognized X509VerifyStatusCode:" + code.Code30);
throw new CryptographicException();
}
}
private static X509ChainStatusFlags MapOpenSsl102Code(Interop.Crypto.X509VerifyStatusCode code)
{
switch (code.Code102)
{
case Interop.Crypto.X509VerifyStatusCode102.X509_V_ERR_INVALID_CA:
return X509ChainStatusFlags.InvalidBasicConstraints;
default:
Debug.Fail("Unrecognized X509VerifyStatusCode:" + code.Code102);
throw new CryptographicException();
}
}
private static X509ChainStatusFlags MapOpenSsl111Code(Interop.Crypto.X509VerifyStatusCode code)
{
switch (code.Code111)
{
case Interop.Crypto.X509VerifyStatusCode111.X509_V_ERR_INVALID_CA:
return X509ChainStatusFlags.InvalidBasicConstraints;
default:
Debug.Fail("Unrecognized X509VerifyStatusCode:" + code.Code111);
throw new CryptographicException();
}
}
private static X509Certificate2? DownloadCertificate(
ReadOnlyMemory<byte> authorityInformationAccess,
TimeSpan downloadTimeout)
{
string? uri = FindHttpAiaRecord(authorityInformationAccess, Oids.CertificateAuthorityIssuers);
if (uri == null)
{
return null;
}
return OpenSslCertificateAssetDownloader.DownloadCertificate(uri, downloadTimeout);
}
private static string? GetOcspEndpoint(SafeX509Handle cert)
{
ArraySegment<byte> authorityInformationAccess =
OpenSslX509CertificateReader.FindFirstExtension(
cert,
Oids.AuthorityInformationAccess);
if (authorityInformationAccess.Count == 0)
{
return null;
}
string? baseUrl = FindHttpAiaRecord(authorityInformationAccess, Oids.OcspEndpoint);
CryptoPool.Return(authorityInformationAccess.Array!, clearSize: 0);
return baseUrl;
}
private static string? FindHttpAiaRecord(ReadOnlyMemory<byte> authorityInformationAccess, string recordTypeOid)
{
try
{
AsnValueReader reader = new AsnValueReader(authorityInformationAccess.Span, AsnEncodingRules.DER);
AsnValueReader sequenceReader = reader.ReadSequence();
reader.ThrowIfNotEmpty();
while (sequenceReader.HasData)
{
AccessDescriptionAsn.Decode(ref sequenceReader, authorityInformationAccess, out AccessDescriptionAsn description);
if (StringComparer.Ordinal.Equals(description.AccessMethod, recordTypeOid))
{
GeneralNameAsn name = description.AccessLocation;
if (name.Uri != null)
{
if (Uri.TryCreate(name.Uri, UriKind.Absolute, out Uri? uri) &&
uri.Scheme == "http")
{
return name.Uri;
}
else if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.NonHttpAiaEntry(name.Uri);
}
}
}
}
if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.NoMatchingAiaEntry(recordTypeOid);
}
}
catch (CryptographicException)
{
// Treat any ASN errors as if the extension was missing.
if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.InvalidAia();
}
}
catch (AsnContentException)
{
// Treat any ASN errors as if the extension was missing.
if (OpenSslX509ChainEventSource.Log.IsEnabled())
{
OpenSslX509ChainEventSource.Log.InvalidAia();
}
}
return null;
}
private static void AddToStackAndUpRef(SafeX509Handle cert, SafeX509StackHandle stack)
{
using (SafeX509Handle tmp = Interop.Crypto.X509UpRef(cert))
{
if (!Interop.Crypto.PushX509StackField(stack, tmp))
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}
// Ownership was transferred to the cert stack.
tmp.SetHandleAsInvalid();
}
}
private static void AddToStackAndUpRef(IntPtr cert, SafeX509StackHandle stack)
{
using (SafeX509Handle tmp = Interop.Crypto.X509UpRef(cert))
{
if (!Interop.Crypto.PushX509StackField(stack, tmp))
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}
// Ownership was transferred to the cert stack.
tmp.SetHandleAsInvalid();
}
}
private static string GetErrorString(Interop.Crypto.X509VerifyStatusCode code)
{
return s_errorStrings.GetOrAdd(
code.Code,
Interop.Crypto.GetX509VerifyCertErrorString);
}
private sealed class WorkingChain : IDisposable
{
// OpenSSL 1.0 sets a "signature valid, don't check again" if we OK the signature error
// OpenSSL 1.1 does not.
private const long OpenSSL_1_1_0_RTM = 0x10100000L;
private static readonly bool s_defaultAbort = SafeEvpPKeyHandle.OpenSslVersion < OpenSSL_1_1_0_RTM;
private ErrorCollection[]? _errors;
internal bool AbortOnSignatureError { get; }
internal bool AbortedForSignatureError { get; private set; }
internal int LastError { get; private set; }
internal WorkingChain()
: this(s_defaultAbort)
{
LastError = -1;
}
internal WorkingChain(bool abortOnSignatureError)
{
AbortOnSignatureError = abortOnSignatureError;
}
internal ref ErrorCollection this[int idx] => ref _errors![idx];
public void Dispose()
{
ErrorCollection[]? toReturn = _errors;
_errors = null;
if (toReturn != null)
{
ArrayPool<ErrorCollection>.Shared.Return(toReturn);
}
}
internal int VerifyCallback(SafeX509StoreCtxHandle storeCtx)
{
Interop.Crypto.X509VerifyStatusCode errorCode = Interop.Crypto.X509StoreCtxGetError(storeCtx);
int errorDepth = Interop.Crypto.X509StoreCtxGetErrorDepth(storeCtx);
if (AbortOnSignatureError &&
errorCode == X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_SIGNATURE_FAILURE)
{
AbortedForSignatureError = true;
return 0;
}
// * We don't report "OK" as an error.
// * For compatibility with Windows / .NET Framework, do not report X509_V_CRL_NOT_YET_VALID.
// * X509_V_ERR_DIFFERENT_CRL_SCOPE will result in X509_V_ERR_UNABLE_TO_GET_CRL
// which will trigger OCSP, so is ignorable.
if (errorCode != X509VerifyStatusCodeUniversal.X509_V_OK &&
errorCode != X509VerifyStatusCodeUniversal.X509_V_ERR_CRL_NOT_YET_VALID &&
errorCode != X509VerifyStatusCodeUniversal.X509_V_ERR_DIFFERENT_CRL_SCOPE)
{
if (_errors == null)
{
int size = Math.Max(DefaultChainCapacity, errorDepth + 1);
// Since ErrorCollection is a non-public type, this is a private pool.
_errors = ArrayPool<ErrorCollection>.Shared.Rent(size);
// We only do spares writes.
_errors.AsSpan().Clear();
}
else if (errorDepth >= _errors.Length)
{
ErrorCollection[] toReturn = _errors;
_errors = ArrayPool<ErrorCollection>.Shared.Rent(errorDepth + 1);
toReturn.AsSpan().CopyTo(_errors);
// We only do spares writes, clear the remainder.
_errors.AsSpan(toReturn.Length).Clear();
ArrayPool<ErrorCollection>.Shared.Return(toReturn);
}
LastError = Math.Max(errorDepth, LastError);
_errors[errorDepth].Add(errorCode);
}
return 1;
}
}
private static MapVersionSpecificCode GetVersionLookup()
{
// 3.0+ are M_NN_00_PP_p (Major, Minor, 0, Patch, Preview)
// 1.x.y are 1_XX_YY_PP_p
if (SafeEvpPKeyHandle.OpenSslVersion >= 0x3_00_00_00_0)
{
return MapOpenSsl30Code;
}
if (SafeEvpPKeyHandle.OpenSslVersion >= 0x1_01_01_00_0)
{
return MapOpenSsl111Code;
}
return MapOpenSsl102Code;
}
private unsafe struct ErrorCollection
{
// As of OpenSSL 1.1.1 there are 75 defined X509_V_ERR values,
// therefore it fits in a bitvector backed by 3 ints (96 bits available).
private const int BucketCount = 3;
private const int OverflowValue = BucketCount * sizeof(int) * 8 - 1;
private fixed int _codes[BucketCount];
internal bool HasOverflow => _codes[2] < 0;
internal bool HasErrors =>
_codes[0] != 0 || _codes[1] != 0 || _codes[2] != 0;
internal void Add(Interop.Crypto.X509VerifyStatusCode statusCode)
{
int bucket = FindBucket(statusCode, out int bitValue);
_codes[bucket] |= bitValue;
}
private void ClearError(Interop.Crypto.X509VerifyStatusCode statusCode)
{
int bucket = FindBucket(statusCode, out int bitValue);
_codes[bucket] &= ~bitValue;
}
private bool HasError(Interop.Crypto.X509VerifyStatusCode statusCode)
{
int bucket = FindBucket(statusCode, out int bitValue);
return (_codes[bucket] & bitValue) != 0;
}
internal void ClearRevoked()
{
ClearError(X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_REVOKED);
}
internal bool IsRevoked()
{
return HasError(X509VerifyStatusCodeUniversal.X509_V_ERR_CERT_REVOKED);
}
internal bool HasCorruptRevocation()
{
return
HasError(X509VerifyStatusCodeUniversal.X509_V_ERR_CRL_SIGNATURE_FAILURE) &&
IsRevoked();
}
internal void ClearRevocationUnknown()
{
// When adding codes here, make sure that HasRevocationUnknown and
// MapVerifyErrorToChainStatus both agree that the code maps to RevocationStatusUnknown.
ClearError(X509VerifyStatusCodeUniversal.X509_V_ERR_CRL_HAS_EXPIRED);
ClearError(X509VerifyStatusCodeUniversal.X509_V_ERR_CRL_NOT_YET_VALID);
ClearError(X509VerifyStatusCodeUniversal.X509_V_ERR_CRL_SIGNATURE_FAILURE);
ClearError(X509VerifyStatusCodeUniversal.X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD);
ClearError(X509VerifyStatusCodeUniversal.X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
ClearError(X509VerifyStatusCodeUniversal.X509_V_ERR_KEYUSAGE_NO_CRL_SIGN);
ClearError(X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE);
ClearError(X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_CRL);
ClearError(X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER);
ClearError(X509VerifyStatusCodeUniversal.X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION);
}
internal bool HasRevocationUnknown()
{
if (!HasErrors)
{
return false;
}
// When adding codes here, make sure that ClearRevocationUnknown and
// MapVerifyErrorToChainStatus both agree that the code maps to RevocationStatusUnknown.
// The most common reasons are UNABLE_TO_GET_CRL, then CRL_HAS_EXPIRED.
return
HasError(X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_CRL) ||
HasError(X509VerifyStatusCodeUniversal.X509_V_ERR_CRL_HAS_EXPIRED) ||
// The rest are simply alphabetical.
HasError(X509VerifyStatusCodeUniversal.X509_V_ERR_CRL_NOT_YET_VALID) ||
HasError(X509VerifyStatusCodeUniversal.X509_V_ERR_CRL_SIGNATURE_FAILURE) ||
HasError(X509VerifyStatusCodeUniversal.X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD) ||
HasError(X509VerifyStatusCodeUniversal.X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD) ||
HasError(X509VerifyStatusCodeUniversal.X509_V_ERR_KEYUSAGE_NO_CRL_SIGN) ||
HasError(X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE) ||
HasError(X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER) ||
HasError(X509VerifyStatusCodeUniversal.X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION);
}
internal void AddRevocationUnknown()
{
// Only one of the codes has to be set.
// So set the one we look for first.
Add(X509VerifyStatusCodeUniversal.X509_V_ERR_UNABLE_TO_GET_CRL);
}
public Enumerator GetEnumerator()
{
if (HasOverflow)
{
throw new CryptographicException();
}
return new Enumerator(this);
}
#if DEBUG
public override string ToString()
{
if (!HasErrors)
{
return "{}";
}
StringBuilder builder = new StringBuilder("{ ");
string delim = "";
foreach (Interop.Crypto.X509VerifyStatusCode code in this)
{
builder.Append(delim).Append(code);
delim = " | ";
}
builder.Append(" }");
return builder.ToString();
}
#endif
private static int FindBucket(Interop.Crypto.X509VerifyStatusCode statusCode, out int bitValue)
{
int val = statusCode.Code;
int bucket;
if (val >= OverflowValue)
{
Debug.Fail($"Out of range X509VerifyStatusCode returned {val} >= {OverflowValue}");
bucket = BucketCount - 1;
bitValue = 1 << 31;
}
else
{
bucket = Math.DivRem(val, 32, out int localBitNumber);
bitValue = (1 << localBitNumber);
}
return bucket;
}
internal static string BuildDiagnosticString(object boxedErrorCollection)
{
ErrorCollection coll = (ErrorCollection)boxedErrorCollection;
StringBuilder builder = new StringBuilder();
foreach (Interop.Crypto.X509VerifyStatusCode code in coll)
{
if (builder.Length > 0)
{
builder.Append(", ");
}
builder.Append(code.UniversalCode);
}
if (builder.Length == 0)
{
return "(none)";
}
return builder.ToString();
}
internal struct Enumerator
{
private ErrorCollection _collection;
private int _lastBucket;
private int _lastBit;
internal Enumerator(ErrorCollection coll)
{
_collection = coll;
_lastBucket = -1;
_lastBit = -1;
}
public bool MoveNext()
{
if (_lastBucket >= BucketCount)
{
return false;
}
FindNextBit:
if (_lastBit == -1)
{
_lastBucket++;
while (_lastBucket < BucketCount && _collection._codes[_lastBucket] == 0)
{
_lastBucket++;
}
if (_lastBucket >= BucketCount)
{
return false;
}
}
_lastBit++;
int val = _collection._codes[_lastBucket];
while (_lastBit < 32)
{
if ((val & (1 << _lastBit)) != 0)
{
return true;
}
_lastBit++;
}
_lastBit = -1;
goto FindNextBit;
}
public Interop.Crypto.X509VerifyStatusCode Current =>
_lastBit == -1 ?
Interop.Crypto.X509VerifyStatusCode.X509_V_OK :
(Interop.Crypto.X509VerifyStatusCode)(_lastBit + 32 * _lastBucket);
}
}
}
}
|