Add support for OCSP on Linux, overhaul Linux X509Chain processing
authorJeremy Barton <jbarton@microsoft.com>
Fri, 22 Feb 2019 20:51:22 +0000 (12:51 -0800)
committerGitHub <noreply@github.com>
Fri, 22 Feb 2019 20:51:22 +0000 (12:51 -0800)
This change moves a lot of the chain building work from managed code into the native shim, largely to cut down on the number of P/Invokes required to set up the chain builder.

Once a chain has been built to a point where only one issuer will be considered, if revocation was requested and a CRL is not available, attempt an OCSP request if the certificate indicates the CA has an OCSP endpoint.

Based on CA/Browser Forum's requirements this expects CRL for all intermediates and only attempts OCSP for the end-entity certificate.

"Conforming" OCSP requests are opportunistically cached on the basis that local filesystem re-reads are more reliable (and faster) than doing a live request to the CA.

Commit migrated from https://github.com/dotnet/corefx/commit/0fbbb68a3f7be82d26e4b2dff5c25c192e3a2023

35 files changed:
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.ASN1.Nid.cs
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Encode.cs
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OCSP.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509.cs
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509StoreCtx.cs
src/libraries/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs
src/libraries/Common/src/System/Security/Cryptography/Oids.cs
src/libraries/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt
src/libraries/Native/Unix/System.Security.Cryptography.Native/apibridge.c
src/libraries/Native/Unix/System.Security.Cryptography.Native/apibridge.h
src/libraries/Native/Unix/System.Security.Cryptography.Native/openssl.c
src/libraries/Native/Unix/System.Security.Cryptography.Native/openssl.h
src/libraries/Native/Unix/System.Security.Cryptography.Native/openssl_1_0_structs.h
src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_asn1.c
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_asn1.h
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ocsp.c [new file with mode: 0644]
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ocsp.h [new file with mode: 0644]
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_x509.c
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_x509.h
src/libraries/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.Linux.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CertificateAssetDownloader.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ChainPal.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CollectionBackedStoreProvider.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CrlCache.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/DirectoryBasedStoreProvider.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/X509Persistence.cs
src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj
src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Store.cs
src/libraries/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs

index afc79ae..008fcfc 100644 (file)
@@ -2,15 +2,42 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Collections.Concurrent;
+using System.Diagnostics;
 using System.Runtime.InteropServices;
+using System.Security.Cryptography;
 
 internal static partial class Interop
 {
     internal static partial class Crypto
     {
+        private static readonly ConcurrentDictionary<string, int> s_nidLookup =
+            new ConcurrentDictionary<string, int>();
+
         internal const int NID_undef = 0;
 
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ObjSn2Nid", CharSet = CharSet.Ansi)]
         internal static extern int ObjSn2Nid(string sn);
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ObjTxt2Nid", CharSet = CharSet.Ansi)]
+        private static extern int ObjTxt2Nid(string oid);
+
+        internal static int ResolveRequiredNid(string oid)
+        {
+            return s_nidLookup.GetOrAdd(oid, s => LookupNid(s));
+        }
+
+        private static int LookupNid(string oid)
+        {
+            int nid = ObjTxt2Nid(oid);
+
+            if (nid == NID_undef)
+            {
+                Debug.Fail($"NID Lookup for {oid} failed, only well-known types should be queried.");
+                throw new CryptographicException();
+            }
+
+            return nid;
+        }
     }
 }
index cfcac7d..8671e2e 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
+using System.Buffers;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 using System.Security.Cryptography;
@@ -108,6 +109,17 @@ internal static partial class Interop
             int second,
             [MarshalAs(UnmanagedType.Bool)] bool isDst);
 
+        [DllImport(Libraries.CryptoNative)]
+        private static extern int CryptoNative_X509StoreSetVerifyTime(
+            SafeX509StoreHandle ctx,
+            int year,
+            int month,
+            int day,
+            int hour,
+            int minute,
+            int second,
+            [MarshalAs(UnmanagedType.Bool)] bool isDst);
+
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_CheckX509IpAddress")]
         internal static extern int CheckX509IpAddress(SafeX509Handle x509, [In]byte[] addressBytes, int addressLen, string hostname, int cchHostname);
 
@@ -119,6 +131,11 @@ internal static partial class Interop
             return GetDynamicBuffer((ptr, buf, i) => GetAsn1StringBytes(ptr, buf, i), asn1);
         }
 
+        internal static ArraySegment<byte> RentAsn1StringBytes(IntPtr asn1)
+        {
+            return RentDynamicBuffer((ptr, buf, i) => GetAsn1StringBytes(ptr, buf, i), asn1);
+        }
+
         internal static byte[] GetX509Thumbprint(SafeX509Handle x509)
         {
             return GetDynamicBuffer((handle, buf, i) => GetX509Thumbprint(handle, buf, i), x509);
@@ -159,6 +176,28 @@ internal static partial class Interop
             }
         }
 
+        internal static void X509StoreSetVerifyTime(SafeX509StoreHandle ctx, DateTime verifyTime)
+        {
+            // OpenSSL is going to convert our input time to universal, so we should be in Local or
+            // Unspecified (local-assumed).
+            Debug.Assert(verifyTime.Kind != DateTimeKind.Utc, "UTC verifyTime should have been normalized to Local");
+
+            int succeeded = CryptoNative_X509StoreSetVerifyTime(
+                ctx,
+                verifyTime.Year,
+                verifyTime.Month,
+                verifyTime.Day,
+                verifyTime.Hour,
+                verifyTime.Minute,
+                verifyTime.Second,
+                verifyTime.IsDaylightSavingTime());
+
+            if (succeeded != 1)
+            {
+                throw Interop.Crypto.CreateOpenSslCryptographicException();
+            }
+        }
+
         private static byte[] GetDynamicBuffer<THandle>(NegativeSizeReadMethod<THandle> method, THandle handle)
         {
             int negativeSize = method(handle, null, 0);
@@ -179,5 +218,28 @@ internal static partial class Interop
 
             return bytes;
         }
+
+        private static ArraySegment<byte> RentDynamicBuffer<THandle>(NegativeSizeReadMethod<THandle> method, THandle handle)
+        {
+            int negativeSize = method(handle, null, 0);
+
+            if (negativeSize > 0)
+            {
+                throw Interop.Crypto.CreateOpenSslCryptographicException();
+            }
+
+            int targetSize = -negativeSize;
+            byte[] bytes = ArrayPool<byte>.Shared.Rent(targetSize);
+
+            int ret = method(handle, bytes, targetSize);
+
+            if (ret != 1)
+            {
+                ArrayPool<byte>.Shared.Return(bytes);
+                throw Interop.Crypto.CreateOpenSslCryptographicException();
+            }
+
+            return new ArraySegment<byte>(bytes, 0, targetSize);
+        }
     }
 }
index 27861a2..309ab2a 100644 (file)
@@ -3,9 +3,9 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
+using System.Buffers;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
-using System.Security.Cryptography;
 
 internal static partial class Interop
 {
@@ -15,14 +15,17 @@ internal static partial class Interop
 
         internal delegate int EncodeFunc<in THandle>(THandle handle, byte[] buf);
 
-        internal static byte[] OpenSslEncode<THandle>(GetEncodedSizeFunc<THandle> getSize, EncodeFunc<THandle> encode, THandle handle)
+        internal static byte[] OpenSslEncode<THandle>(
+            GetEncodedSizeFunc<THandle> getSize,
+            EncodeFunc<THandle> encode,
+            THandle handle)
             where THandle : SafeHandle
         {
             int size = getSize(handle);
 
             if (size < 1)
             {
-                throw Crypto.CreateOpenSslCryptographicException();
+                throw CreateOpenSslCryptographicException();
             }
 
             byte[] data = new byte[size];
@@ -42,5 +45,40 @@ internal static partial class Interop
             
             return data;
         }
+
+        internal static ArraySegment<byte> OpenSslRentEncode<THandle>(
+            GetEncodedSizeFunc<THandle> getSize,
+            EncodeFunc<THandle> encode,
+            THandle handle)
+            where THandle : SafeHandle
+        {
+            int size = getSize(handle);
+
+            if (size < 1)
+            {
+                throw CreateOpenSslCryptographicException();
+            }
+
+            byte[] data = ArrayPool<byte>.Shared.Rent(size);
+
+            int size2 = encode(handle, data);
+            if (size2 < 1)
+            {
+                Debug.Fail(
+                    $"{nameof(OpenSslEncode)}: {nameof(getSize)} succeeded ({size}) and {nameof(encode)} failed ({size2})");
+
+                // Since we don't know what was written, assume it was secret and clear the value.
+                // (It doesn't matter much, since we're behind Debug.Fail)
+                ArrayPool<byte>.Shared.Return(data, clearArray: true);
+
+                // If it ever happens, ensure the error queue gets cleared.
+                // And since it didn't write the data, reporting an exception is good too.
+                throw CreateOpenSslCryptographicException();
+            }
+
+            Debug.Assert(size == size2);
+
+            return new ArraySegment<byte>(data, 0, size2);
+        }
     }
 }
diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OCSP.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OCSP.cs
new file mode 100644 (file)
index 0000000..bcf9e2a
--- /dev/null
@@ -0,0 +1,128 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+    internal static partial class Crypto
+    {
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_OcspRequestDestroy")]
+        internal static extern void OcspRequestDestroy(IntPtr ocspReq);
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetOcspRequestDerSize")]
+        internal static extern int GetOcspRequestDerSize(SafeOcspRequestHandle req);
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EncodeOcspRequest")]
+        internal static extern int EncodeOcspRequest(SafeOcspRequestHandle req, byte[] buf);
+
+        [DllImport(Libraries.CryptoNative)]
+        private static extern SafeOcspResponseHandle CryptoNative_DecodeOcspResponse(ref byte buf, int len);
+
+        internal static SafeOcspResponseHandle DecodeOcspResponse(ReadOnlySpan<byte> buf)
+        {
+            return CryptoNative_DecodeOcspResponse(
+                ref MemoryMarshal.GetReference(buf),
+                buf.Length);
+        }
+
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_OcspResponseDestroy")]
+        internal static extern void OcspResponseDestroy(IntPtr ocspReq);
+
+
+        [DllImport(Libraries.CryptoNative)]
+        private static extern X509VerifyStatusCode CryptoNative_X509ChainGetCachedOcspStatus(SafeX509StoreCtxHandle ctx, string cachePath);
+
+        internal static X509VerifyStatusCode X509ChainGetCachedOcspStatus(SafeX509StoreCtxHandle ctx, string cachePath)
+        {
+            X509VerifyStatusCode response = CryptoNative_X509ChainGetCachedOcspStatus(ctx, cachePath);
+
+            if (response < 0)
+            {
+                Debug.Fail($"Unexpected response from X509ChainGetCachedOcspSuccess: {response}");
+                throw new CryptographicException();
+            }
+
+            return response;
+        }
+
+        [DllImport(Libraries.CryptoNative)]
+        private static extern X509VerifyStatusCode CryptoNative_X509ChainVerifyOcsp(
+            SafeX509StoreCtxHandle ctx,
+            SafeOcspRequestHandle req,
+            SafeOcspResponseHandle resp,
+            string cachePath);
+
+        internal static X509VerifyStatusCode X509ChainVerifyOcsp(
+            SafeX509StoreCtxHandle ctx,
+            SafeOcspRequestHandle req,
+            SafeOcspResponseHandle resp,
+            string cachePath)
+        {
+            X509VerifyStatusCode response = CryptoNative_X509ChainVerifyOcsp(ctx, req, resp, cachePath);
+
+            if (response < 0)
+            {
+                Debug.Fail($"Unexpected response from X509ChainGetCachedOcspSuccess: {response}");
+                throw new CryptographicException();
+            }
+
+            return response;
+        }
+
+        [DllImport(Libraries.CryptoNative)]
+        private static extern SafeOcspRequestHandle CryptoNative_X509ChainBuildOcspRequest(SafeX509StoreCtxHandle storeCtx);
+
+        internal static SafeOcspRequestHandle X509ChainBuildOcspRequest(SafeX509StoreCtxHandle storeCtx)
+        {
+            SafeOcspRequestHandle req = CryptoNative_X509ChainBuildOcspRequest(storeCtx);
+
+            if (req.IsInvalid)
+            {
+                req.Dispose();
+                throw CreateOpenSslCryptographicException();
+            }
+
+            return req;
+        }
+    }
+}
+
+namespace System.Security.Cryptography.X509Certificates
+{
+    internal class SafeOcspRequestHandle : SafeHandleZeroOrMinusOneIsInvalid
+    {
+        public SafeOcspRequestHandle()
+            : base(true)
+        {
+        }
+
+        protected override bool ReleaseHandle()
+        {
+            Interop.Crypto.OcspRequestDestroy(handle);
+            handle = IntPtr.Zero;
+            return true;
+        }
+    }
+
+    internal class SafeOcspResponseHandle : SafeHandleZeroOrMinusOneIsInvalid
+    {
+        public SafeOcspResponseHandle()
+            : base(true)
+        {
+        }
+
+        protected override bool ReleaseHandle()
+        {
+            Interop.Crypto.OcspResponseDestroy(handle);
+            handle = IntPtr.Zero;
+            return true;
+        }
+    }
+}
index 17f1102..b17af46 100644 (file)
@@ -92,6 +92,21 @@ internal static partial class Interop
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509IssuerNameHash")]
         internal static extern ulong X509IssuerNameHash(SafeX509Handle x);
 
+        [DllImport(Libraries.CryptoNative)]
+        private static extern SafeSharedAsn1OctetStringHandle CryptoNative_X509FindExtensionData(
+            SafeX509Handle x,
+            int extensionNid);
+
+        internal static SafeSharedAsn1OctetStringHandle X509FindExtensionData(SafeX509Handle x, int extensionNid)
+        {
+            CheckValidOpenSslHandle(x);
+
+            return SafeInteriorHandle.OpenInteriorHandle(
+                (handle, arg) => CryptoNative_X509FindExtensionData(handle, arg),
+                x,
+                extensionNid);
+        }
+
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509GetExtCount")]
         internal static extern int X509GetExtCount(SafeX509Handle x);
 
@@ -112,8 +127,8 @@ internal static partial class Interop
         [return: MarshalAs(UnmanagedType.Bool)]
         internal static extern bool X509ExtensionGetCritical(IntPtr ex);
 
-        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509StoreCreate")]
-        internal static extern SafeX509StoreHandle X509StoreCreate();
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509ChainNew")]
+        internal static extern SafeX509StoreHandle X509ChainNew(SafeX509StackHandle systemTrust, string userTrustPath);
 
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509StoreDestory")]
         internal static extern void X509StoreDestory(IntPtr v);
@@ -128,7 +143,15 @@ internal static partial class Interop
 
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509StoreSetRevocationFlag")]
         [return: MarshalAs(UnmanagedType.Bool)]
-        internal static extern bool X509StoreSetRevocationFlag(SafeX509StoreHandle ctx, X509RevocationFlag revocationFlag);
+        private static extern bool CryptoNative_X509StoreSetRevocationFlag(SafeX509StoreHandle ctx, X509RevocationFlag revocationFlag);
+
+        internal static void X509StoreSetRevocationFlag(SafeX509StoreHandle ctx, X509RevocationFlag revocationFlag)
+        {
+            if (!CryptoNative_X509StoreSetRevocationFlag(ctx, revocationFlag))
+            {
+                throw CreateOpenSslCryptographicException();
+            }
+        }
 
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509StoreCtxInit")]
         [return: MarshalAs(UnmanagedType.Bool)]
@@ -138,12 +161,50 @@ internal static partial class Interop
             SafeX509Handle x509,
             SafeX509StackHandle extraCerts);
 
-        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509VerifyCert")]
-        internal static extern int X509VerifyCert(SafeX509StoreCtxHandle ctx);
+        [DllImport(Libraries.CryptoNative)]
+        private static extern int CryptoNative_X509VerifyCert(SafeX509StoreCtxHandle ctx);
+
+        internal static bool X509VerifyCert(SafeX509StoreCtxHandle ctx)
+        {
+            int result = CryptoNative_X509VerifyCert(ctx);
+
+            if (result < 0)
+            {
+                throw CreateOpenSslCryptographicException();
+            }
+
+            return result != 0;
+        }
 
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509StoreCtxGetError")]
         internal static extern X509VerifyStatusCode X509StoreCtxGetError(SafeX509StoreCtxHandle ctx);
 
+        [DllImport(Libraries.CryptoNative)]
+        private static extern int CryptoNative_X509StoreCtxReset(SafeX509StoreCtxHandle ctx);
+
+        internal static void X509StoreCtxReset(SafeX509StoreCtxHandle ctx)
+        {
+            if (CryptoNative_X509StoreCtxReset(ctx) != 1)
+            {
+                throw CreateOpenSslCryptographicException();
+            }
+        }
+
+        [DllImport(Libraries.CryptoNative)]
+        private static extern int CryptoNative_X509StoreCtxRebuildChain(SafeX509StoreCtxHandle ctx);
+
+        internal static bool X509StoreCtxRebuildChain(SafeX509StoreCtxHandle ctx)
+        {
+            int result = CryptoNative_X509StoreCtxRebuildChain(ctx);
+
+            if (result < 0)
+            {
+                throw CreateOpenSslCryptographicException();
+            }
+
+            return result != 0;
+        }
+
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509StoreCtxGetErrorDepth")]
         internal static extern int X509StoreCtxGetErrorDepth(SafeX509StoreCtxHandle ctx);
 
index 7853e8e..6619f0c 100644 (file)
@@ -35,6 +35,28 @@ internal static partial class Interop
         /// </summary>
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetX509StackField")]
         internal static extern IntPtr GetX509StackField(SafeSharedX509StackHandle stack, int loc);
+
+        [DllImport(Libraries.CryptoNative)]
+        private static extern int CryptoNative_X509StackAddDirectoryStore(SafeX509StackHandle stack, string storePath);
+
+        internal static void X509StackAddDirectoryStore(SafeX509StackHandle stack, string storePath)
+        {
+            if (CryptoNative_X509StackAddDirectoryStore(stack, storePath) != 1)
+            {
+                throw CreateOpenSslCryptographicException();
+            }
+        }
+
+        [DllImport(Libraries.CryptoNative)]
+        private static extern int CryptoNative_X509StackAddMultiple(SafeX509StackHandle dest, SafeX509StackHandle src);
+
+        internal static void X509StackAddMultiple(SafeX509StackHandle dest, SafeX509StackHandle src)
+        {
+            if (CryptoNative_X509StackAddMultiple(dest, src) != 1)
+            {
+                throw CreateOpenSslCryptographicException();
+            }
+        }
     }
 }
 
index 0da65d1..1af3395 100644 (file)
@@ -19,6 +19,20 @@ internal static partial class Interop
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509StoreCtxGetChain")]
         internal static extern SafeX509StackHandle X509StoreCtxGetChain(SafeX509StoreCtxHandle ctx);
 
+        [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509StoreCtxGetCurrentCert")]
+        internal static extern SafeX509Handle X509StoreCtxGetCurrentCert(SafeX509StoreCtxHandle ctx);
+
+        [DllImport(Libraries.CryptoNative)]
+        private static extern int CryptoNative_X509StoreCtxCommitToChain(SafeX509StoreCtxHandle ctx);
+
+        internal static void X509StoreCtxCommitToChain(SafeX509StoreCtxHandle ctx)
+        {
+            if (CryptoNative_X509StoreCtxCommitToChain(ctx) != 1)
+            {
+                throw CreateOpenSslCryptographicException();
+            }
+        }
+
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509StoreCtxGetSharedUntrusted")]
         private static extern SafeSharedX509StackHandle X509StoreCtxGetSharedUntrusted_private(SafeX509StoreCtxHandle ctx);
 
index ddf4131..7248716 100644 (file)
@@ -4,7 +4,6 @@
 
 using System;
 using System.Runtime.InteropServices;
-using System.Security;
 
 namespace Microsoft.Win32.SafeHandles
 {
@@ -75,4 +74,12 @@ namespace Microsoft.Win32.SafeHandles
         {
         }
     }
+
+    internal sealed class SafeSharedAsn1OctetStringHandle : SafeInteriorHandle
+    {
+        private SafeSharedAsn1OctetStringHandle() :
+            base(IntPtr.Zero, ownsHandle: true)
+        {
+        }
+    }
 }
index 08404bf..b24fa7a 100644 (file)
@@ -42,6 +42,7 @@ namespace System.Security.Cryptography
         internal const string CertificateTemplate = "1.3.6.1.4.1.311.21.7";
         internal const string ApplicationCertPolicies = "1.3.6.1.4.1.311.21.10";
         internal const string AuthorityInformationAccess = "1.3.6.1.5.5.7.1.1";
+        internal const string OcspEndpoint = "1.3.6.1.5.5.7.48.1";
         internal const string CertificateAuthorityIssuers = "1.3.6.1.5.5.7.48.2";
         internal const string Pkcs9ExtensionRequest = "1.2.840.113549.1.9.14";
 
index dc4023c..814a06c 100644 (file)
@@ -35,6 +35,7 @@ set(NATIVECRYPTO_SOURCES
     pal_evp_pkey_rsa.c
     pal_evp_cipher.c
     pal_hmac.c
+    pal_ocsp.c
     pal_pkcs12.c
     pal_pkcs7.c
     pal_rsa.c
index 567d242..0f5ee6d 100644 (file)
@@ -479,6 +479,16 @@ int32_t local_SSL_is_init_finished(const SSL* ssl)
     return SSL_state(ssl) == SSL_ST_OK;
 }
 
+X509Stack* local_X509_STORE_CTX_get0_chain(X509_STORE_CTX* ctx)
+{
+    return ctx ? ctx->chain : NULL;
+}
+
+X509_STORE* local_X509_STORE_CTX_get0_store(X509_STORE_CTX* ctx)
+{
+    return ctx ? ctx->ctx: NULL;
+}
+
 X509Stack* local_X509_STORE_CTX_get0_untrusted(X509_STORE_CTX* ctx)
 {
     return ctx ? ctx->untrusted : NULL;
@@ -489,6 +499,11 @@ X509* local_X509_STORE_CTX_get0_cert(X509_STORE_CTX* ctx)
     return ctx ? ctx->cert : NULL;
 }
 
+X509_VERIFY_PARAM* local_X509_STORE_get0_param(X509_STORE* ctx)
+{
+    return ctx ? ctx->param: NULL;
+}
+
 int32_t local_X509_up_ref(X509* x509)
 {
     if (x509 != NULL)
index 2bcdc56..c7f4405 100644 (file)
@@ -34,8 +34,11 @@ const ASN1_TIME* local_X509_CRL_get0_nextUpdate(const X509_CRL* crl);
 int32_t local_X509_NAME_get0_der(X509_NAME* x509Name, const uint8_t** pder, size_t* pderlen);
 int32_t local_X509_PUBKEY_get0_param(
     ASN1_OBJECT** palgOid, const uint8_t** pkeyBytes, int* pkeyBytesLen, X509_ALGOR** palg, X509_PUBKEY* pubkey);
+STACK_OF(X509)* local_X509_STORE_CTX_get0_chain(X509_STORE_CTX* ctx);
 X509* local_X509_STORE_CTX_get0_cert(X509_STORE_CTX* ctx);
-STACK_OF(X509) * local_X509_STORE_CTX_get0_untrusted(X509_STORE_CTX* ctx);
+X509_STORE* local_X509_STORE_CTX_get0_store(X509_STORE_CTX* ctx);
+STACK_OF(X509)* local_X509_STORE_CTX_get0_untrusted(X509_STORE_CTX* ctx);
+X509_VERIFY_PARAM* local_X509_STORE_get0_param(X509_STORE* ctx);
 const ASN1_TIME* local_X509_get0_notAfter(const X509* x509);
 const ASN1_TIME* local_X509_get0_notBefore(const X509* x509);
 ASN1_BIT_STRING* local_X509_get0_pubkey_bitstr(const X509* x509);
index 81c7930..9bbd920 100644 (file)
@@ -1082,6 +1082,38 @@ int32_t CryptoNative_SetX509ChainVerifyTime(X509_STORE_CTX* ctx,
     return 1;
 }
 
+int32_t CryptoNative_X509StoreSetVerifyTime(X509_STORE* ctx,
+                                            int32_t year,
+                                            int32_t month,
+                                            int32_t day,
+                                            int32_t hour,
+                                            int32_t minute,
+                                            int32_t second,
+                                            int32_t isDst)
+{
+    if (!ctx)
+    {
+        return 0;
+    }
+
+    time_t verifyTime = MakeTimeT(year, month, day, hour, minute, second, isDst);
+
+    if (verifyTime == (time_t)-1)
+    {
+        return 0;
+    }
+
+    X509_VERIFY_PARAM* verifyParams = X509_STORE_get0_param(ctx);
+
+    if (!verifyParams)
+    {
+        return 0;
+    }
+
+    X509_VERIFY_PARAM_set_time(verifyParams, verifyTime);
+    return 1;
+}
+
 /*
 Function:
 ReadX509AsDerFromBio
index 3de0920..3a6d571 100644 (file)
@@ -56,6 +56,15 @@ DLLEXPORT int32_t CryptoNative_SetX509ChainVerifyTime(X509_STORE_CTX* ctx,
                                                       int32_t second,
                                                       int32_t isDst);
 
+DLLEXPORT int32_t CryptoNative_X509StoreSetVerifyTime(X509_STORE* ctx,
+                                                      int32_t year,
+                                                      int32_t month,
+                                                      int32_t day,
+                                                      int32_t hour,
+                                                      int32_t minute,
+                                                      int32_t second,
+                                                      int32_t isDst);
+
 DLLEXPORT X509* CryptoNative_ReadX509AsDerFromBio(BIO* bio);
 
 DLLEXPORT int32_t CryptoNative_BioTell(BIO* bio);
index 8852c3c..f777536 100644 (file)
@@ -132,8 +132,37 @@ struct x509_st
 
 struct x509_store_ctx_st
 {
-    const void* _ignored0;
+    X509_STORE* ctx;
     int _ignored1;
     X509* cert;
     STACK_OF(X509*) untrusted;
+    const void* _ignored2;
+    const void* _ignored3;
+    const void* _ignored4;
+    // For comparison purposes to the 1.0.x headers:
+    // BEGIN FUNCTION POINTERS
+    const void* _ignored5;
+    const void* _ignored6;
+    const void* _ignored7;
+    const void* _ignored8;
+    const void* _ignored9;
+    const void* _ignored10;
+    const void* _ignored11;
+    const void* _ignored12;
+    const void* _ignored13;
+    const void* _ignored14;
+    const void* _ignored15;
+    const void* _ignored16;
+    // END FUNCTION POINTERS
+    int _ignored17;
+    int _ignored18;
+    STACK_OF(X509*) chain;
+};
+
+struct x509_store_st
+{
+    int _ignored0;
+    const void* _ignored1;
+    const void* _ignored2;
+    X509_VERIFY_PARAM* param;
 };
index 56148de..62a3397 100644 (file)
@@ -23,6 +23,7 @@
 #include <openssl/hmac.h>
 #include <openssl/md5.h>
 #include <openssl/objects.h>
+#include <openssl/ocsp.h>
 #include <openssl/pem.h>
 #include <openssl/pkcs12.h>
 #include <openssl/pkcs7.h>
@@ -118,6 +119,7 @@ int OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS* settings);
 void OPENSSL_sk_free(OPENSSL_STACK*);
 OPENSSL_STACK* OPENSSL_sk_new_null(void);
 int OPENSSL_sk_num(const OPENSSL_STACK*);
+void* OPENSSL_sk_pop(OPENSSL_STACK* st);
 void OPENSSL_sk_pop_free(OPENSSL_STACK* st, void (*func)(void*));
 int OPENSSL_sk_push(OPENSSL_STACK* st, const void* data);
 void* OPENSSL_sk_value(const OPENSSL_STACK*, int);
@@ -141,7 +143,9 @@ int32_t X509_NAME_get0_der(X509_NAME* x509Name, const uint8_t** pder, size_t* pd
 int32_t X509_PUBKEY_get0_param(
     ASN1_OBJECT** palgOid, const uint8_t** pkeyBytes, int* pkeyBytesLen, X509_ALGOR** palg, X509_PUBKEY* pubkey);
 X509* X509_STORE_CTX_get0_cert(X509_STORE_CTX* ctx);
-STACK_OF(X509) * X509_STORE_CTX_get0_untrusted(X509_STORE_CTX* ctx);
+STACK_OF(X509)* X509_STORE_CTX_get0_chain(X509_STORE_CTX* ctx);
+STACK_OF(X509)* X509_STORE_CTX_get0_untrusted(X509_STORE_CTX* ctx);
+X509_VERIFY_PARAM* X509_STORE_get0_param(X509_STORE* ctx);
 const ASN1_TIME* X509_get0_notAfter(const X509* x509);
 const ASN1_TIME* X509_get0_notBefore(const X509* x509);
 ASN1_BIT_STRING* X509_get0_pubkey_bitstr(const X509* x509);
@@ -151,6 +155,10 @@ int32_t X509_get_version(const X509* x509);
 int32_t X509_up_ref(X509* x509);
 #endif
 
+#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_0_2_RTM
+X509_STORE* X509_STORE_CTX_get0_store(X509_STORE_CTX* ctx);
+#endif
+
 #if !HAVE_OPENSSL_ALPN
 #undef HAVE_OPENSSL_ALPN
 #define HAVE_OPENSSL_ALPN 1
@@ -174,11 +182,15 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
 
 #define FOR_ALL_OPENSSL_FUNCTIONS \
     REQUIRED_FUNCTION(ASN1_BIT_STRING_free) \
+    REQUIRED_FUNCTION(ASN1_d2i_bio) \
+    REQUIRED_FUNCTION(ASN1_i2d_bio) \
+    REQUIRED_FUNCTION(ASN1_GENERALIZEDTIME_free) \
     REQUIRED_FUNCTION(ASN1_INTEGER_get) \
     REQUIRED_FUNCTION(ASN1_OBJECT_free) \
     REQUIRED_FUNCTION(ASN1_OCTET_STRING_free) \
     REQUIRED_FUNCTION(ASN1_OCTET_STRING_new) \
     REQUIRED_FUNCTION(ASN1_OCTET_STRING_set) \
+    REQUIRED_FUNCTION(ASN1_STRING_dup) \
     REQUIRED_FUNCTION(ASN1_STRING_free) \
     REQUIRED_FUNCTION(ASN1_STRING_print_ex) \
     REQUIRED_FUNCTION(BASIC_CONSTRAINTS_free) \
@@ -204,8 +216,10 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
     REQUIRED_FUNCTION(d2i_ASN1_OCTET_STRING) \
     REQUIRED_FUNCTION(d2i_BASIC_CONSTRAINTS) \
     REQUIRED_FUNCTION(d2i_EXTENDED_KEY_USAGE) \
+    REQUIRED_FUNCTION(d2i_OCSP_RESPONSE) \
     REQUIRED_FUNCTION(d2i_PKCS12) \
     REQUIRED_FUNCTION(d2i_PKCS12_bio) \
+    REQUIRED_FUNCTION(d2i_PKCS12_fp) \
     REQUIRED_FUNCTION(d2i_PKCS7) \
     REQUIRED_FUNCTION(d2i_PKCS7_bio) \
     REQUIRED_FUNCTION(d2i_RSAPublicKey) \
@@ -338,6 +352,8 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
     REQUIRED_FUNCTION(HMAC_Update) \
     REQUIRED_FUNCTION(i2d_ASN1_INTEGER) \
     REQUIRED_FUNCTION(i2d_ASN1_TYPE) \
+    REQUIRED_FUNCTION(i2d_OCSP_REQUEST) \
+    REQUIRED_FUNCTION(i2d_OCSP_RESPONSE) \
     REQUIRED_FUNCTION(i2d_PKCS12) \
     REQUIRED_FUNCTION(i2d_PKCS7) \
     REQUIRED_FUNCTION(i2d_X509) \
@@ -351,12 +367,26 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
     REQUIRED_FUNCTION(OBJ_sn2nid) \
     REQUIRED_FUNCTION(OBJ_txt2nid) \
     REQUIRED_FUNCTION(OBJ_txt2obj) \
+    REQUIRED_FUNCTION(OCSP_BASICRESP_free) \
+    REQUIRED_FUNCTION(OCSP_basic_verify) \
+    REQUIRED_FUNCTION(OCSP_CERTID_free) \
+    REQUIRED_FUNCTION(OCSP_cert_to_id) \
+    REQUIRED_FUNCTION(OCSP_check_nonce) \
+    REQUIRED_FUNCTION(OCSP_request_add0_id) \
+    REQUIRED_FUNCTION(OCSP_request_add1_nonce) \
+    REQUIRED_FUNCTION(OCSP_REQUEST_free) \
+    REQUIRED_FUNCTION(OCSP_REQUEST_new) \
+    REQUIRED_FUNCTION(OCSP_resp_find_status) \
+    REQUIRED_FUNCTION(OCSP_response_get1_basic) \
+    REQUIRED_FUNCTION(OCSP_RESPONSE_free) \
+    REQUIRED_FUNCTION(OCSP_RESPONSE_new) \
     LEGACY_FUNCTION(OPENSSL_add_all_algorithms_conf) \
     REQUIRED_FUNCTION(OPENSSL_cleanse) \
     NEW_REQUIRED_FUNCTION(OPENSSL_init_ssl) \
     RENAMED_FUNCTION(OPENSSL_sk_free, sk_free) \
     RENAMED_FUNCTION(OPENSSL_sk_new_null, sk_new_null) \
     RENAMED_FUNCTION(OPENSSL_sk_num, sk_num) \
+    RENAMED_FUNCTION(OPENSSL_sk_pop, sk_pop) \
     RENAMED_FUNCTION(OPENSSL_sk_pop_free, sk_pop_free) \
     RENAMED_FUNCTION(OPENSSL_sk_push, sk_push) \
     RENAMED_FUNCTION(OPENSSL_sk_value, sk_value) \
@@ -440,6 +470,8 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
     REQUIRED_FUNCTION(SSL_write) \
     REQUIRED_FUNCTION(X509_check_issued) \
     REQUIRED_FUNCTION(X509_check_purpose) \
+    REQUIRED_FUNCTION(X509_cmp_current_time) \
+    REQUIRED_FUNCTION(X509_cmp_time) \
     REQUIRED_FUNCTION(X509_CRL_free) \
     FALLBACK_FUNCTION(X509_CRL_get0_nextUpdate) \
     REQUIRED_FUNCTION(X509_digest) \
@@ -455,6 +487,7 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
     REQUIRED_FUNCTION(X509_get_default_cert_file) \
     REQUIRED_FUNCTION(X509_get_default_cert_file_env) \
     REQUIRED_FUNCTION(X509_get_ext) \
+    REQUIRED_FUNCTION(X509_get_ext_by_NID) \
     REQUIRED_FUNCTION(X509_get_ext_count) \
     REQUIRED_FUNCTION(X509_get_ext_d2i) \
     REQUIRED_FUNCTION(X509_get_issuer_name) \
@@ -476,20 +509,26 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
     FALLBACK_FUNCTION(X509_NAME_get0_der) \
     REQUIRED_FUNCTION(X509_PUBKEY_get) \
     FALLBACK_FUNCTION(X509_PUBKEY_get0_param) \
+    REQUIRED_FUNCTION(X509_subject_name_hash) \
     REQUIRED_FUNCTION(X509_STORE_add_cert) \
     REQUIRED_FUNCTION(X509_STORE_add_crl) \
+    REQUIRED_FUNCTION(X509_STORE_CTX_cleanup) \
     REQUIRED_FUNCTION(X509_STORE_CTX_free) \
-    REQUIRED_FUNCTION(X509_STORE_CTX_get0_param) \
-    REQUIRED_FUNCTION(X509_STORE_CTX_get1_chain) \
+    REQUIRED_FUNCTION(X509_STORE_CTX_get_current_cert) \
     REQUIRED_FUNCTION(X509_STORE_CTX_get_error) \
     REQUIRED_FUNCTION(X509_STORE_CTX_get_error_depth) \
     FALLBACK_FUNCTION(X509_STORE_CTX_get0_cert) \
+    FALLBACK_FUNCTION(X509_STORE_CTX_get0_chain) \
+    REQUIRED_FUNCTION(X509_STORE_CTX_get0_param) \
+    FALLBACK_FUNCTION(X509_STORE_CTX_get0_store) \
     FALLBACK_FUNCTION(X509_STORE_CTX_get0_untrusted) \
+    REQUIRED_FUNCTION(X509_STORE_CTX_get1_chain) \
     REQUIRED_FUNCTION(X509_STORE_CTX_init) \
     REQUIRED_FUNCTION(X509_STORE_CTX_new) \
     REQUIRED_FUNCTION(X509_STORE_CTX_set_flags) \
     REQUIRED_FUNCTION(X509_STORE_CTX_set_verify_cb) \
     REQUIRED_FUNCTION(X509_STORE_free) \
+    FALLBACK_FUNCTION(X509_STORE_get0_param) \
     REQUIRED_FUNCTION(X509_STORE_new) \
     REQUIRED_FUNCTION(X509_STORE_set_flags) \
     REQUIRED_FUNCTION(X509V3_EXT_print) \
@@ -521,11 +560,15 @@ FOR_ALL_OPENSSL_FUNCTIONS
 // Redefine all calls to OpenSSL functions as calls through pointers that are set
 // to the functions from the libssl.so selected by the shim.
 #define ASN1_BIT_STRING_free ASN1_BIT_STRING_free_ptr
+#define ASN1_GENERALIZEDTIME_free ASN1_GENERALIZEDTIME_free_ptr
+#define ASN1_d2i_bio ASN1_d2i_bio_ptr
+#define ASN1_i2d_bio ASN1_i2d_bio_ptr
 #define ASN1_INTEGER_get ASN1_INTEGER_get_ptr
 #define ASN1_OBJECT_free ASN1_OBJECT_free_ptr
 #define ASN1_OCTET_STRING_free ASN1_OCTET_STRING_free_ptr
 #define ASN1_OCTET_STRING_new ASN1_OCTET_STRING_new_ptr
 #define ASN1_OCTET_STRING_set ASN1_OCTET_STRING_set_ptr
+#define ASN1_STRING_dup ASN1_STRING_dup_ptr
 #define ASN1_STRING_free ASN1_STRING_free_ptr
 #define ASN1_STRING_print_ex ASN1_STRING_print_ex_ptr
 #define BASIC_CONSTRAINTS_free BASIC_CONSTRAINTS_free_ptr
@@ -551,8 +594,10 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define d2i_ASN1_OCTET_STRING d2i_ASN1_OCTET_STRING_ptr
 #define d2i_BASIC_CONSTRAINTS d2i_BASIC_CONSTRAINTS_ptr
 #define d2i_EXTENDED_KEY_USAGE d2i_EXTENDED_KEY_USAGE_ptr
+#define d2i_OCSP_RESPONSE d2i_OCSP_RESPONSE_ptr
 #define d2i_PKCS12 d2i_PKCS12_ptr
 #define d2i_PKCS12_bio d2i_PKCS12_bio_ptr
+#define d2i_PKCS12_fp d2i_PKCS12_fp_ptr
 #define d2i_PKCS7 d2i_PKCS7_ptr
 #define d2i_PKCS7_bio d2i_PKCS7_bio_ptr
 #define d2i_RSAPublicKey d2i_RSAPublicKey_ptr
@@ -685,6 +730,8 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define HMAC_Update HMAC_Update_ptr
 #define i2d_ASN1_INTEGER i2d_ASN1_INTEGER_ptr
 #define i2d_ASN1_TYPE i2d_ASN1_TYPE_ptr
+#define i2d_OCSP_REQUEST i2d_OCSP_REQUEST_ptr
+#define i2d_OCSP_RESPONSE i2d_OCSP_RESPONSE_ptr
 #define i2d_PKCS12 i2d_PKCS12_ptr
 #define i2d_PKCS7 i2d_PKCS7_ptr
 #define i2d_X509 i2d_X509_ptr
@@ -698,12 +745,26 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define OBJ_sn2nid OBJ_sn2nid_ptr
 #define OBJ_txt2nid OBJ_txt2nid_ptr
 #define OBJ_txt2obj OBJ_txt2obj_ptr
+#define OCSP_basic_verify OCSP_basic_verify_ptr
+#define OCSP_BASICRESP_free OCSP_BASICRESP_free_ptr
+#define OCSP_cert_to_id OCSP_cert_to_id_ptr
+#define OCSP_check_nonce OCSP_check_nonce_ptr
+#define OCSP_CERTID_free OCSP_CERTID_free_ptr
+#define OCSP_request_add0_id OCSP_request_add0_id_ptr
+#define OCSP_request_add1_nonce OCSP_request_add1_nonce_ptr
+#define OCSP_REQUEST_free OCSP_REQUEST_free_ptr
+#define OCSP_REQUEST_new OCSP_REQUEST_new_ptr
+#define OCSP_resp_find_status OCSP_resp_find_status_ptr
+#define OCSP_response_get1_basic OCSP_response_get1_basic_ptr
+#define OCSP_RESPONSE_free OCSP_RESPONSE_free_ptr
+#define OCSP_RESPONSE_new OCSP_RESPONSE_new_ptr
 #define OPENSSL_add_all_algorithms_conf OPENSSL_add_all_algorithms_conf_ptr
 #define OPENSSL_cleanse OPENSSL_cleanse_ptr
 #define OPENSSL_init_ssl OPENSSL_init_ssl_ptr
 #define OPENSSL_sk_free OPENSSL_sk_free_ptr
 #define OPENSSL_sk_new_null OPENSSL_sk_new_null_ptr
 #define OPENSSL_sk_num OPENSSL_sk_num_ptr
+#define OPENSSL_sk_pop OPENSSL_sk_pop_ptr
 #define OPENSSL_sk_pop_free OPENSSL_sk_pop_free_ptr
 #define OPENSSL_sk_push OPENSSL_sk_push_ptr
 #define OPENSSL_sk_value OPENSSL_sk_value_ptr
@@ -742,6 +803,7 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define sk_free OPENSSL_sk_free_ptr
 #define sk_new_null OPENSSL_sk_new_null_ptr
 #define sk_num OPENSSL_sk_num_ptr
+#define sk_pop OPENSSL_sk_pop_ptr
 #define sk_pop_free OPENSSL_sk_pop_free_ptr
 #define sk_push OPENSSL_sk_push_ptr
 #define sk_value OPENSSL_sk_value_ptr
@@ -793,6 +855,8 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define TLS_method TLS_method_ptr
 #define X509_check_issued X509_check_issued_ptr
 #define X509_check_purpose X509_check_purpose_ptr
+#define X509_cmp_current_time X509_cmp_current_time_ptr
+#define X509_cmp_time X509_cmp_time_ptr
 #define X509_CRL_free X509_CRL_free_ptr
 #define X509_CRL_get0_nextUpdate X509_CRL_get0_nextUpdate_ptr
 #define X509_digest X509_digest_ptr
@@ -812,6 +876,7 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define X509_get_default_cert_file X509_get_default_cert_file_ptr
 #define X509_get_default_cert_file_env X509_get_default_cert_file_env_ptr
 #define X509_get_ext X509_get_ext_ptr
+#define X509_get_ext_by_NID X509_get_ext_by_NID_ptr
 #define X509_get_ext_count X509_get_ext_count_ptr
 #define X509_get_ext_d2i X509_get_ext_d2i_ptr
 #define X509_get_issuer_name X509_get_issuer_name_ptr
@@ -829,11 +894,16 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define X509_NAME_get_index_by_NID X509_NAME_get_index_by_NID_ptr
 #define X509_PUBKEY_get0_param X509_PUBKEY_get0_param_ptr
 #define X509_PUBKEY_get X509_PUBKEY_get_ptr
+#define X509_subject_name_hash X509_subject_name_hash_ptr
 #define X509_STORE_add_cert X509_STORE_add_cert_ptr
 #define X509_STORE_add_crl X509_STORE_add_crl_ptr
+#define X509_STORE_CTX_cleanup X509_STORE_CTX_cleanup_ptr
 #define X509_STORE_CTX_free X509_STORE_CTX_free_ptr
+#define X509_STORE_CTX_get_current_cert X509_STORE_CTX_get_current_cert_ptr
 #define X509_STORE_CTX_get0_cert X509_STORE_CTX_get0_cert_ptr
+#define X509_STORE_CTX_get0_chain X509_STORE_CTX_get0_chain_ptr
 #define X509_STORE_CTX_get0_param X509_STORE_CTX_get0_param_ptr
+#define X509_STORE_CTX_get0_store X509_STORE_CTX_get0_store_ptr
 #define X509_STORE_CTX_get0_untrusted X509_STORE_CTX_get0_untrusted_ptr
 #define X509_STORE_CTX_get1_chain X509_STORE_CTX_get1_chain_ptr
 #define X509_STORE_CTX_get_error X509_STORE_CTX_get_error_ptr
@@ -843,6 +913,7 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define X509_STORE_CTX_set_flags X509_STORE_CTX_set_flags_ptr
 #define X509_STORE_CTX_set_verify_cb X509_STORE_CTX_set_verify_cb_ptr
 #define X509_STORE_free X509_STORE_free_ptr
+#define X509_STORE_get0_param X509_STORE_get0_param_ptr
 #define X509_STORE_new X509_STORE_new_ptr
 #define X509_STORE_set_flags X509_STORE_set_flags_ptr
 #define X509V3_EXT_print X509V3_EXT_print_ptr
@@ -863,6 +934,7 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_1_0_RTM
 // type-safe OPENSSL_sk_free
 #define sk_GENERAL_NAME_free(stack) OPENSSL_sk_free((OPENSSL_STACK*)(1 ? stack : (STACK_OF(GENERAL_NAME)*)0))
+#define sk_X509_free(stack) OPENSSL_sk_free((OPENSSL_STACK*)(1 ? stack : (STACK_OF(X509)*)0))
 
 // type-safe OPENSSL_sk_num
 #define sk_ASN1_OBJECT_num(stack) OPENSSL_sk_num((const OPENSSL_STACK*)(1 ? stack : (const STACK_OF(ASN1_OBJECT)*)0))
@@ -876,6 +948,9 @@ FOR_ALL_OPENSSL_FUNCTIONS
 // type-safe OPENSSL_sk_push
 #define sk_X509_push(stack,value) OPENSSL_sk_push((OPENSSL_STACK*)(1 ? stack : (STACK_OF(X509)*)0), (const void*)(1 ? value : (X509*)0))
 
+// type-safe OPENSSL_sk_pop
+#define sk_X509_pop(stack) OPENSSL_sk_pop((OPENSSL_STACK*)(1 ? stack : (STACK_OF(X509)*)0))
+
 // type-safe OPENSSL_sk_pop_free
 #define sk_X509_pop_free(stack, freefunc) OPENSSL_sk_pop_free((OPENSSL_STACK*)(1 ? stack : (STACK_OF(X509)*)0), (OPENSSL_sk_freefunc)(1 ? freefunc : (sk_X509_freefunc)0))
 
@@ -921,7 +996,9 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define X509_NAME_get0_der local_X509_NAME_get0_der
 #define X509_PUBKEY_get0_param local_X509_PUBKEY_get0_param
 #define X509_STORE_CTX_get0_cert local_X509_STORE_CTX_get0_cert
+#define X509_STORE_CTX_get0_chain local_X509_STORE_CTX_get0_chain
 #define X509_STORE_CTX_get0_untrusted local_X509_STORE_CTX_get0_untrusted
+#define X509_STORE_get0_param local_X509_STORE_get0_param
 #define X509_get0_notAfter local_X509_get0_notAfter
 #define X509_get0_notBefore local_X509_get0_notBefore
 #define X509_get0_pubkey_bitstr local_X509_get0_pubkey_bitstr
@@ -936,6 +1013,7 @@ FOR_ALL_OPENSSL_FUNCTIONS
 #define OPENSSL_sk_free sk_free
 #define OPENSSL_sk_new_null sk_new_null
 #define OPENSSL_sk_num sk_num
+#define OPENSSL_sk_pop sk_pop
 #define OPENSSL_sk_pop_free sk_pop_free
 #define OPENSSL_sk_push sk_push
 #define OPENSSL_sk_value sk_value
index 10faa92..ccfe731 100644 (file)
@@ -44,6 +44,11 @@ int32_t CryptoNative_ObjSn2Nid(const char* sn)
     return OBJ_sn2nid(sn);
 }
 
+int32_t CryptoNative_ObjTxt2Nid(const char* sn)
+{
+    return OBJ_txt2nid(sn);
+}
+
 const ASN1_OBJECT* CryptoNative_ObjNid2Obj(int32_t nid)
 {
     return OBJ_nid2obj(nid);
index 49696de..4659c39 100644 (file)
@@ -41,6 +41,11 @@ Direct shim to OBJ_sn2nid.
 DLLEXPORT int32_t CryptoNative_ObjSn2Nid(const char* sn);
 
 /*
+Direct shim to OBJ_txt2nid.
+*/
+DLLEXPORT int32_t CryptoNative_ObjTxt2Nid(const char* sn);
+
+/*
 Direct shim to OBJ_nid2obj.
 */
 DLLEXPORT const ASN1_OBJECT* CryptoNative_ObjNid2Obj(int32_t nid);
diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ocsp.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ocsp.c
new file mode 100644 (file)
index 0000000..666035e
--- /dev/null
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "pal_ocsp.h"
+
+
+
+void CryptoNative_OcspRequestDestroy(OCSP_REQUEST* request)
+{
+    if (request != NULL)
+    {
+        OCSP_REQUEST_free(request);
+    }
+}
+
+int32_t CryptoNative_GetOcspRequestDerSize(OCSP_REQUEST* req)
+{
+    return i2d_OCSP_REQUEST(req, NULL);
+}
+
+int32_t CryptoNative_EncodeOcspRequest(OCSP_REQUEST* req, uint8_t* buf)
+{
+    return i2d_OCSP_REQUEST(req, &buf);
+}
+
+OCSP_RESPONSE* CryptoNative_DecodeOcspResponse(const uint8_t* buf, int32_t len)
+{
+    if (buf == NULL || len == 0)
+    {
+        return NULL;
+    }
+
+    return d2i_OCSP_RESPONSE(NULL, &buf, len);
+}
+
+void CryptoNative_OcspResponseDestroy(OCSP_RESPONSE* response)
+{
+    if (response != NULL)
+    {
+        OCSP_RESPONSE_free(response);
+    }
+}
diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ocsp.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ocsp.h
new file mode 100644 (file)
index 0000000..e720582
--- /dev/null
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "pal_crypto_types.h"
+#include "pal_compiler.h"
+#include "opensslshim.h"
+
+/*
+Direct shim to OCSP_REQUEST_free
+*/
+DLLEXPORT void CryptoNative_OcspRequestDestroy(OCSP_REQUEST* request);
+
+/*
+Returns the number of bytes required to encode an OCSP_REQUEST
+*/
+DLLEXPORT int32_t CryptoNative_GetOcspRequestDerSize(OCSP_REQUEST* req);
+
+/*
+Encodes the OCSP_REQUEST req into the destination buffer, returning the number of bytes written.
+*/
+DLLEXPORT int32_t CryptoNative_EncodeOcspRequest(OCSP_REQUEST* req, uint8_t* buf);
+
+/*
+Direct shim to d2i_OCSP_RESPONSE
+*/
+DLLEXPORT OCSP_RESPONSE* CryptoNative_DecodeOcspResponse(const uint8_t* buf, int32_t len);
+
+/*
+Direct shim to OCSP_RESPONSE_free
+*/
+DLLEXPORT void CryptoNative_OcspResponseDestroy(OCSP_RESPONSE* response);
index 1aa421a..225fcdd 100644 (file)
@@ -6,6 +6,10 @@
 
 #include <stdbool.h>
 #include <assert.h>
+#include <dirent.h>
+#include <string.h>
+#include <unistd.h>
+#include "../Common/pal_safecrt.h"
 
 c_static_assert(PAL_X509_V_OK == X509_V_OK);
 c_static_assert(PAL_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT);
@@ -164,6 +168,30 @@ int32_t CryptoNative_X509ExtensionGetCritical(X509_EXTENSION* x)
     return X509_EXTENSION_get_critical(x);
 }
 
+ASN1_OCTET_STRING* CryptoNative_X509FindExtensionData(X509* x, int32_t nid)
+{
+    if (x == NULL || nid == NID_undef)
+    {
+        return NULL;
+    }
+
+    int idx = X509_get_ext_by_NID(x, nid, -1);
+
+    if (idx < 0)
+    {
+        return NULL;
+    }
+
+    X509_EXTENSION* ext = X509_get_ext(x, idx);
+
+    if (ext == NULL)
+    {
+        return NULL;
+    }
+
+    return X509_EXTENSION_get_data(ext);
+}
+
 X509_STORE* CryptoNative_X509StoreCreate()
 {
     return X509_STORE_new();
@@ -234,6 +262,23 @@ X509Stack* CryptoNative_X509StoreCtxGetChain(X509_STORE_CTX* ctx)
     return X509_STORE_CTX_get1_chain(ctx);
 }
 
+X509* CryptoNative_X509StoreCtxGetCurrentCert(X509_STORE_CTX* ctx)
+{
+    if (ctx == NULL)
+    {
+        return NULL;
+    }
+
+    X509* cert = X509_STORE_CTX_get_current_cert(ctx);
+
+    if (cert != NULL)
+    {
+        X509_up_ref(cert);
+    }
+
+    return cert;
+}
+
 X509Stack* CryptoNative_X509StoreCtxGetSharedUntrusted(X509_STORE_CTX* ctx)
 {
     if (ctx)
@@ -259,6 +304,26 @@ X509VerifyStatusCode CryptoNative_X509StoreCtxGetError(X509_STORE_CTX* ctx)
     return (unsigned int)X509_STORE_CTX_get_error(ctx);
 }
 
+int32_t CryptoNative_X509StoreCtxReset(X509_STORE_CTX* ctx)
+{
+    X509* leaf = X509_STORE_CTX_get0_cert(ctx);
+    X509Stack* untrusted = X509_STORE_CTX_get0_untrusted(ctx);
+    X509_STORE* store = X509_STORE_CTX_get0_store(ctx);
+
+    X509_STORE_CTX_cleanup(ctx);
+    return CryptoNative_X509StoreCtxInit(ctx, store, leaf, untrusted);
+}
+
+int32_t CryptoNative_X509StoreCtxRebuildChain(X509_STORE_CTX* ctx)
+{
+    if (!CryptoNative_X509StoreCtxReset(ctx))
+    {
+        return -1;
+    }
+
+    return X509_verify_cert(ctx);
+}
+
 void CryptoNative_X509StoreCtxSetVerifyCallback(X509_STORE_CTX* ctx, X509StoreVerifyCallback callback)
 {
     X509_STORE_CTX_set_verify_cb(ctx, callback);
@@ -323,3 +388,678 @@ X509* CryptoNative_X509UpRef(X509* x509)
 
     return x509;
 }
+
+static DIR* OpenUserStore(const char* storePath, char** pathTmp, size_t* pathTmpSize, char** nextFileWrite)
+{
+    DIR* trustDir = opendir(storePath);
+
+    if (trustDir == NULL)
+    {
+        *pathTmp = NULL;
+        *nextFileWrite = NULL;
+        return NULL;
+    }
+
+    struct dirent* ent = NULL;
+    size_t storePathLen = strlen(storePath);
+
+    // d_name is a fixed length char[], not a char*.
+    // Leave one byte for '\0' and one for '/'
+    size_t allocSize = storePathLen + sizeof(ent->d_name) + 2;
+    char* tmp = (char*)calloc(allocSize, sizeof(char));
+    memcpy_s(tmp, allocSize, storePath, storePathLen);
+    tmp[storePathLen] = '/';
+    *pathTmp = tmp;
+    *pathTmpSize = allocSize;
+    *nextFileWrite = (tmp + storePathLen + 1);
+    return trustDir;
+}
+
+static X509* ReadNextPublicCert(DIR* dir, X509Stack* tmpStack, char* pathTmp, size_t pathTmpSize, char* nextFileWrite)
+{
+    struct dirent* next;
+    ptrdiff_t offset = nextFileWrite - pathTmp;
+    assert(offset > 0);
+    assert((size_t)offset < pathTmpSize);
+    size_t remaining = pathTmpSize - (size_t)offset;
+
+    while ((next = readdir(dir)) != NULL)
+    {
+        size_t len = strnlen(next->d_name, sizeof(next->d_name));
+
+        if (len > 4 && 0 == strncasecmp(".pfx", next->d_name + len - 4, 4))
+        {
+            memcpy_s(nextFileWrite, remaining, next->d_name, len);
+            // if d_name was full-length it might not have a trailing null.
+            nextFileWrite[len] = 0;
+
+            FILE* fp = fopen(pathTmp, "r");
+
+            if (fp != NULL)
+            {
+                PKCS12* p12 = d2i_PKCS12_fp(fp, NULL);
+
+                if (p12 != NULL)
+                {
+                    EVP_PKEY* key;
+                    X509* cert = NULL;
+
+                    if (PKCS12_parse(p12, NULL, &key, &cert, &tmpStack))
+                    {
+                        if (key != NULL)
+                        {
+                            EVP_PKEY_free(key);
+                        }
+
+                        if (cert == NULL && sk_X509_num(tmpStack) > 0)
+                        {
+                            cert = sk_X509_value(tmpStack, 0);
+                            X509_up_ref(cert);
+                        }
+                    }
+
+                    fclose(fp);
+
+                    X509* popTmp;
+                    while ((popTmp = sk_X509_pop(tmpStack)) != NULL)
+                    {
+                        X509_free(popTmp);
+                    }
+
+                    PKCS12_free(p12);
+
+                    if (cert != NULL)
+                    {
+                        return cert;
+                    }
+                }
+            }
+        }
+    }
+
+    return NULL;
+}
+
+X509_STORE* CryptoNative_X509ChainNew(X509Stack* systemTrust, const char* userTrustPath)
+{
+    X509_STORE* store = X509_STORE_new();
+
+    if (store == NULL)
+    {
+        return NULL;
+    }
+
+    if (systemTrust != NULL)
+    {
+        int count = sk_X509_num(systemTrust);
+
+        for (int i = 0; i < count; i++)
+        {
+            if (!X509_STORE_add_cert(store, sk_X509_value(systemTrust, i)))
+            {
+                X509_STORE_free(store);
+                return NULL;
+            }
+        }
+    }
+
+    if (userTrustPath != NULL)
+    {
+        char* pathTmp;
+        size_t pathTmpSize;
+        char* nextFileWrite;
+        DIR* trustDir = OpenUserStore(userTrustPath, &pathTmp, &pathTmpSize, &nextFileWrite);
+
+        if (trustDir != NULL)
+        {
+            X509* cert;
+            X509Stack* tmpStack = sk_X509_new_null();
+
+            while ((cert = ReadNextPublicCert(trustDir, tmpStack, pathTmp, pathTmpSize, nextFileWrite)) != NULL)
+            {
+                // cert refcount is 1
+                if (!X509_STORE_add_cert(store, cert))
+                {
+                    // cert refcount is still 1
+                    if (ERR_get_error() != ERR_PACK(ERR_LIB_X509, X509_F_X509_STORE_ADD_CERT, X509_R_CERT_ALREADY_IN_HASH_TABLE))
+                    {
+                        // cert refcount goes to 0
+                        X509_free(cert);
+                        X509_STORE_free(store);
+                        store = NULL;
+                        break;
+                    }
+                }
+
+                // if add_cert succeeded, reduce refcount to 1
+                // if add_cert failed (duplicate add), reduce refcount to 0
+                X509_free(cert);
+            }
+
+            sk_X509_free(tmpStack);
+            free(pathTmp);
+            closedir(trustDir);
+
+            // store is only NULL if X509_STORE_add_cert failed, in which case we
+            // want to leave the error state intact, so the exception will report
+            // what went wrong (probably out of memory).
+            if (store == NULL)
+            {
+                return NULL;
+            }
+
+            // PKCS12_parse can cause spurious errors.
+            // d2i_PKCS12_fp may have failed for invalid files.
+            // X509_STORE_add_cert may have reported duplicate addition.
+            // Just clear it all.
+            ERR_clear_error();
+        }
+    }
+
+    return store;
+}
+
+int32_t CryptoNative_X509StackAddDirectoryStore(X509Stack* stack, char* storePath)
+{
+    if (stack == NULL || storePath == NULL)
+    {
+        return -1;
+    }
+
+    int clearError = 1;
+    char* pathTmp;
+    size_t pathTmpSize;
+    char* nextFileWrite;
+    DIR* storeDir = OpenUserStore(storePath, &pathTmp, &pathTmpSize, &nextFileWrite);
+
+    if (storeDir != NULL)
+    {
+        X509* cert;
+        X509Stack* tmpStack = sk_X509_new_null();
+
+        while ((cert = ReadNextPublicCert(storeDir, tmpStack, pathTmp, pathTmpSize, nextFileWrite)) != NULL)
+        {
+            if (!sk_X509_push(stack, cert))
+            {
+                clearError = 0;
+                X509_free(cert);
+                break;
+            }
+
+            // Don't free the cert here, it'll get freed by sk_X509_pop_free later (push doesn't call up_ref)
+        }
+
+        sk_X509_free(tmpStack);
+        free(pathTmp);
+        closedir(storeDir);
+
+        if (clearError)
+        {
+            // PKCS12_parse can cause spurious errors.
+            // d2i_PKCS12_fp may have failed for invalid files.
+            // Just clear it all.
+            ERR_clear_error();
+        }
+
+    }
+
+    return clearError;
+}
+
+int32_t CryptoNative_X509StackAddMultiple(X509Stack* dest, X509Stack* src)
+{
+    if (dest == NULL)
+    {
+        return -1;
+    }
+
+    int success = 1;
+
+    if (src != NULL)
+    {
+        int count = sk_X509_num(src);
+
+        for (int i = 0; i < count; i++)
+        {
+            X509* cert = sk_X509_value(src, i);
+            X509_up_ref(cert);
+
+            if (!sk_X509_push(dest, cert))
+            {
+                success = 0;
+                break;
+            }
+        }
+    }
+
+    return success;
+}
+
+int32_t CryptoNative_X509StoreCtxCommitToChain(X509_STORE_CTX* storeCtx)
+{
+    if (storeCtx == NULL)
+    {
+        return -1;
+    }
+
+    X509Stack* chain = X509_STORE_CTX_get1_chain(storeCtx);
+
+    if (chain == NULL)
+    {
+        return 0;
+    }
+
+    X509* cur = NULL;
+    X509Stack* untrusted = X509_STORE_CTX_get0_untrusted(storeCtx);
+    X509* leaf = X509_STORE_CTX_get0_cert(storeCtx);
+
+    while ((cur = sk_X509_pop(untrusted)) != NULL)
+    {
+        X509_free(cur);
+    }
+
+    while ((cur = sk_X509_pop(chain)) != NULL)
+    {
+        if (cur == leaf)
+        {
+            // Undo the up-ref from get1_chain
+            X509_free(cur);
+        }
+        else
+        {
+            // For intermediates which were already in untrusted this puts them back.
+            //
+            // For a fully trusted chain this will add the trust root redundantly to the
+            // untrusted lookup set, but the resulting extra work is small compared to the
+            // risk of being wrong about promoting trust or losing the chain at this point.
+            sk_X509_push(untrusted, cur);
+        }
+    }
+
+    // Since we've already drained out this collection there's no difference between free
+    // and pop_free, other than free saves a bit of work.
+    sk_X509_free(chain);
+    return 1;
+}
+
+static char* BuildOcspCacheFilename(char* cachePath, X509* subject)
+{
+    assert(cachePath != NULL);
+    assert(subject != NULL);
+
+    size_t len = strlen(cachePath);
+    // path plus '/', '.', ".ocsp", '\0' and two 8 character hex strings
+    size_t allocSize = len + 24;
+    char* fullPath = (char*)calloc(allocSize, sizeof(char));
+
+    if (fullPath != NULL)
+    {
+        unsigned long issuerHash = X509_issuer_name_hash(subject);
+        unsigned long subjectHash = X509_subject_name_hash(subject);
+
+        size_t written = (size_t)snprintf(fullPath, allocSize, "%s/%08lx.%08lx.ocsp", cachePath, issuerHash, subjectHash);
+        assert(written == allocSize - 1);
+        (void)written;
+
+        if (issuerHash == 0 || subjectHash == 0)
+        {
+            ERR_clear_error();
+        }
+    }
+
+    return fullPath;
+}
+
+static OCSP_CERTID* MakeCertId(X509* subject, X509* issuer)
+{
+    assert(subject != NULL);
+    assert(issuer != NULL);
+
+    // SHA-1 is being used because that's really the only thing supported by current OCSP responders
+    return OCSP_cert_to_id(EVP_sha1(), subject, issuer);
+}
+
+static X509VerifyStatusCode CheckOcsp(
+    OCSP_REQUEST* req,
+    OCSP_RESPONSE* resp,
+    X509* subject,
+    X509* issuer,
+    X509_STORE_CTX* storeCtx,
+    ASN1_GENERALIZEDTIME** thisUpdate,
+    ASN1_GENERALIZEDTIME** nextUpdate)
+{
+    if (thisUpdate != NULL)
+    {
+        *thisUpdate = NULL;
+    }
+
+    if (nextUpdate != NULL)
+    {
+        *nextUpdate = NULL;
+    }
+
+    assert(resp != NULL);
+    assert(subject != NULL);
+    assert(issuer != NULL);
+
+    OCSP_CERTID* certId = MakeCertId(subject, issuer);
+
+    if (certId == NULL)
+    {
+        return (X509VerifyStatusCode)-1;
+    }
+
+    OCSP_BASICRESP* basicResp = OCSP_response_get1_basic(resp);
+    int status = V_OCSP_CERTSTATUS_UNKNOWN;
+    X509VerifyStatusCode ret = PAL_X509_V_ERR_UNABLE_TO_GET_CRL;
+
+    if (basicResp != NULL)
+    {
+        X509_STORE* store = X509_STORE_CTX_get0_store(storeCtx);
+        X509Stack* untrusted = X509_STORE_CTX_get0_untrusted(storeCtx);
+
+        // From the documentation:
+        // -1: Request has nonce, response does not.
+        // 0: Request and response both have nonce, nonces do not match.
+        // 1: Request and response both have nonce, nonces match.
+        // 2: Neither request nor response have nonce.
+        // 3: Response has a nonce, request does not.
+        //
+        int nonceCheck = req == NULL ? 1 : OCSP_check_nonce(req, basicResp);
+
+        // Treat "response has no nonce" as success, since not all responders set the nonce.
+        if (nonceCheck == -1)
+        {
+            nonceCheck = 1;
+        }
+
+        if (nonceCheck == 1 && OCSP_basic_verify(basicResp, untrusted, store, 0))
+        {
+            ASN1_GENERALIZEDTIME* thisupd = NULL;
+            ASN1_GENERALIZEDTIME* nextupd = NULL;
+
+            if (OCSP_resp_find_status(basicResp, certId, &status, NULL, NULL, &thisupd, &nextupd))
+            {
+                if (thisUpdate != NULL && thisupd != NULL)
+                {
+                    *thisUpdate = ASN1_STRING_dup(thisupd);
+                }
+
+                if (nextUpdate != NULL && nextupd != NULL)
+                {
+                    *nextUpdate = ASN1_STRING_dup(nextupd);
+                }
+
+                if (status == V_OCSP_CERTSTATUS_GOOD)
+                {
+                    ret = PAL_X509_V_OK;
+                }
+                else if (status == V_OCSP_CERTSTATUS_REVOKED)
+                {
+                    ret = PAL_X509_V_ERR_CERT_REVOKED;
+                }
+            }
+        }
+
+        OCSP_BASICRESP_free(basicResp);
+        basicResp = NULL;
+    }
+
+    OCSP_CERTID_free(certId);
+    return ret;
+}
+
+static int Get0CertAndIssuer(X509_STORE_CTX* storeCtx, X509** subject, X509** issuer)
+{
+    assert(storeCtx != NULL);
+    assert(subject != NULL);
+    assert(issuer != NULL);
+
+    // get0 => don't free.
+    X509Stack* chain = X509_STORE_CTX_get0_chain(storeCtx);
+    int chainSize = chain == NULL ? 0 : sk_X509_num(chain);
+
+    if (chainSize < 1)
+    {
+        return 0;
+    }
+
+    *subject = sk_X509_value(chain, 0);
+    *issuer = sk_X509_value(chain, chainSize == 1 ? 0 : 1);
+    return 1;
+}
+
+static time_t GetIssuanceWindowStart()
+{
+    // time_t granularity is seconds, so subtract 4 days worth of seconds.
+    // The 4 day policy is based on the CA/Browser Forum Baseline Requirements
+    // (version 1.6.3) section 4.9.10 (On-Line Revocation Checking Requirements)
+    time_t t = time(NULL);
+    t -= 4 * 24 * 60 * 60;
+    return t;
+}
+
+X509VerifyStatusCode CryptoNative_X509ChainGetCachedOcspStatus(X509_STORE_CTX* storeCtx, char* cachePath)
+{
+    if (storeCtx == NULL || cachePath == NULL)
+    {
+        return (X509VerifyStatusCode)-1;
+    }
+
+    X509* subject;
+    X509* issuer;
+
+    if (!Get0CertAndIssuer(storeCtx, &subject, &issuer))
+    {
+        return (X509VerifyStatusCode)-2;
+    }
+
+    X509VerifyStatusCode ret = PAL_X509_V_ERR_UNABLE_TO_GET_CRL;
+    char* fullPath = BuildOcspCacheFilename(cachePath, subject);
+
+    if (fullPath == NULL)
+    {
+        return ret;
+    }
+
+    BIO* bio = BIO_new_file(fullPath, "rb");
+    OCSP_RESPONSE* resp = NULL;
+
+    if (bio != NULL)
+    {
+        resp = d2i_OCSP_RESPONSE_bio(bio, NULL);
+        BIO_free(bio);
+    }
+
+    if (resp != NULL)
+    {
+        ASN1_GENERALIZEDTIME* thisUpdate = NULL;
+        ASN1_GENERALIZEDTIME* nextUpdate = NULL;
+        ret = CheckOcsp(NULL, resp, subject, issuer, storeCtx, &thisUpdate, &nextUpdate);
+
+        if (ret != PAL_X509_V_ERR_UNABLE_TO_GET_CRL)
+        {
+            time_t oldest = GetIssuanceWindowStart();
+
+            // If either the thisUpdate or nextUpdate is missing we can't determine policy, so reject it.
+            // oldest = now - window;
+            //
+            // if thisUpdate < oldest || nextUpdate < now, reject.
+            //
+            // Since X509_cmp(_current)_time returns 0 on error, do a <= 0 check.
+            if (nextUpdate == NULL ||
+                thisUpdate == NULL ||
+                X509_cmp_current_time(nextUpdate) <= 0 ||
+                X509_cmp_time(thisUpdate, &oldest) <= 0)
+            {
+                ret = PAL_X509_V_ERR_UNABLE_TO_GET_CRL;
+            }
+        }
+
+        if (nextUpdate != NULL)
+        {
+            ASN1_GENERALIZEDTIME_free(nextUpdate);
+        }
+
+        if (thisUpdate != NULL)
+        {
+            ASN1_GENERALIZEDTIME_free(thisUpdate);
+        }
+    }
+
+    // If the file failed to parse, or failed to match the certificate, or was outside of the policy window,
+    // (or any other "this file has no further value" condition), delete the file and clear the errors that
+    // may have been reported while determining we want to delete it and ask again fresh.
+    if (ret == PAL_X509_V_ERR_UNABLE_TO_GET_CRL)
+    {
+        unlink(fullPath);
+        ERR_clear_error();
+    }
+
+    free(fullPath);
+
+    if (resp != NULL)
+    {
+        OCSP_RESPONSE_free(resp);
+    }
+
+    return ret;
+}
+
+OCSP_REQUEST* CryptoNative_X509ChainBuildOcspRequest(X509_STORE_CTX* storeCtx)
+{
+    if (storeCtx == NULL)
+    {
+        return NULL;
+    }
+
+    X509* subject;
+    X509* issuer;
+
+    if (!Get0CertAndIssuer(storeCtx, &subject, &issuer))
+    {
+        return NULL;
+    }
+
+    OCSP_CERTID* certId = MakeCertId(subject, issuer);
+
+    if (certId == NULL)
+    {
+        return NULL;
+    }
+
+    OCSP_REQUEST* req = OCSP_REQUEST_new();
+
+    if (req == NULL)
+    {
+        OCSP_CERTID_free(certId);
+        return NULL;
+    }
+
+    if (!OCSP_request_add0_id(req, certId))
+    {
+        OCSP_CERTID_free(certId);
+        OCSP_REQUEST_free(req);
+        return NULL;
+    }
+
+    // Ownership was successfully transferred to req
+    certId = NULL;
+
+    // Add a random nonce.
+    OCSP_request_add1_nonce(req, NULL, -1);
+    return req;
+}
+
+X509VerifyStatusCode CryptoNative_X509ChainVerifyOcsp(
+    X509_STORE_CTX* storeCtx,
+    OCSP_REQUEST* req,
+    OCSP_RESPONSE* resp,
+    char* cachePath)
+{
+    if (storeCtx == NULL || req == NULL || resp == NULL)
+    {
+        return (X509VerifyStatusCode)-1;
+    }
+
+    X509* subject;
+    X509* issuer;
+
+    if (!Get0CertAndIssuer(storeCtx, &subject, &issuer))
+    {
+        return (X509VerifyStatusCode)-2;
+    }
+
+    X509VerifyStatusCode ret = PAL_X509_V_ERR_UNABLE_TO_GET_CRL;
+    OCSP_CERTID* certId = MakeCertId(subject, issuer);
+
+    if (certId == NULL)
+    {
+        return (X509VerifyStatusCode)-3;
+    }
+
+    ASN1_GENERALIZEDTIME* thisUpdate = NULL;
+    ASN1_GENERALIZEDTIME* nextUpdate = NULL;
+    ret = CheckOcsp(req, resp, subject, issuer, storeCtx, &thisUpdate, &nextUpdate);
+
+    if (ret == PAL_X509_V_OK || ret == PAL_X509_V_ERR_CERT_REVOKED)
+    {
+        // If the nextUpdate time is in the past (or corrupt), report either REVOKED or CRL_EXPIRED
+        if (nextUpdate != NULL && X509_cmp_current_time(nextUpdate) <= 0)
+        {
+            if (ret == PAL_X509_V_OK)
+            {
+                ret = PAL_X509_V_ERR_CRL_HAS_EXPIRED;
+            }
+        }
+        else
+        {
+            time_t oldest = GetIssuanceWindowStart();
+
+            // If the response is within our caching policy (which requires a nextUpdate value)
+            // then try to cache it.
+            if (nextUpdate != NULL &&
+                thisUpdate != NULL &&
+                X509_cmp_time(thisUpdate, &oldest) > 0)
+            {
+                char* fullPath = BuildOcspCacheFilename(cachePath, subject);
+
+                if (fullPath != NULL)
+                {
+                    int clearErr = 1;
+                    BIO* bio = BIO_new_file(fullPath, "wb");
+
+                    if (bio != NULL)
+                    {
+                        if (i2d_OCSP_RESPONSE_bio(bio, resp))
+                        {
+                            clearErr = 0;
+                        }
+
+                        BIO_free(bio);
+                    }
+
+                    if (clearErr)
+                    {
+                        ERR_clear_error();
+                        unlink(fullPath);
+                    }
+
+                    free(fullPath);
+                }
+            }
+        }
+    }
+
+    if (nextUpdate != NULL)
+    {
+        ASN1_GENERALIZEDTIME_free(nextUpdate);
+    }
+
+    if (thisUpdate != NULL)
+    {
+        ASN1_GENERALIZEDTIME_free(thisUpdate);
+    }
+
+    return ret;
+}
index 96baa33..51be345 100644 (file)
@@ -189,6 +189,11 @@ Shims the X509_EXTENSION_get_critical method.
 DLLEXPORT int32_t CryptoNative_X509ExtensionGetCritical(X509_EXTENSION* x);
 
 /*
+Returns the data portion of the first matched extension.
+*/
+DLLEXPORT ASN1_OCTET_STRING* CryptoNative_X509FindExtensionData(X509* x, int32_t nid);
+
+/*
 Shims the X509_STORE_new method.
 */
 DLLEXPORT X509_STORE* CryptoNative_X509StoreCreate(void);
@@ -241,6 +246,11 @@ Shims the X509_STORE_CTX_get1_chain method.
 DLLEXPORT X509Stack* CryptoNative_X509StoreCtxGetChain(X509_STORE_CTX* ctx);
 
 /*
+Shims the X509_STORE_CTX_get_current_cert function.
+*/
+DLLEXPORT X509* CryptoNative_X509StoreCtxGetCurrentCert(X509_STORE_CTX* ctx);
+
+/*
 Returns the interior pointer to the "untrusted" certificates collection for this X509_STORE_CTX
 */
 DLLEXPORT X509Stack* CryptoNative_X509StoreCtxGetSharedUntrusted(X509_STORE_CTX* ctx);
@@ -256,6 +266,19 @@ Shims the X509_STORE_CTX_get_error method.
 DLLEXPORT X509VerifyStatusCode CryptoNative_X509StoreCtxGetError(X509_STORE_CTX* ctx);
 
 /*
+Resets ctx to before the chain was built, preserving the target cert, trust store, extra cert context,
+and verify parameters.
+*/
+DLLEXPORT int32_t CryptoNative_X509StoreCtxReset(X509_STORE_CTX* ctx);
+
+/*
+Reset ctx and rebuild the chain.
+Returns -1 if CryptoNative_X509StoreCtxReset failed, otherwise returns the result of
+X509_verify_cert.
+*/
+DLLEXPORT int32_t CryptoNative_X509StoreCtxRebuildChain(X509_STORE_CTX* ctx);
+
+/*
 Shims the X509_STORE_CTX_get_error_depth method.
 */
 DLLEXPORT int32_t CryptoNative_X509StoreCtxGetErrorDepth(X509_STORE_CTX* ctx);
@@ -311,3 +334,43 @@ Unlike X509Duplicate, this modifies an existing object, so no new memory is allo
 Returns the input value.
 */
 DLLEXPORT X509* CryptoNative_X509UpRef(X509* x509);
+
+/*
+Create a new X509_STORE, considering the certificates from systemTrust and any readable PFX
+in userTrustPath to be trusted
+*/
+DLLEXPORT X509_STORE* CryptoNative_X509ChainNew(X509Stack* systemTrust, const char* userTrustPath);
+
+/*
+Adds all of the simple certificates from null-or-empty-password PFX files in storePath to stack.
+*/
+DLLEXPORT int32_t CryptoNative_X509StackAddDirectoryStore(X509Stack* stack, char* storePath);
+
+/*
+Adds all of the certificates in src to dest and increases their reference count.
+*/
+DLLEXPORT int32_t CryptoNative_X509StackAddMultiple(X509Stack* dest, X509Stack* src);
+
+/*
+Removes any untrusted/extra certificates from the unstrusted collection that are not part of
+the current chain to make chain builds after Reset faster.
+*/
+DLLEXPORT int32_t CryptoNative_X509StoreCtxCommitToChain(X509_STORE_CTX* storeCtx);
+
+/*
+Look for a cached OCSP response appropriate to the end-entity certificate using the issuer as
+determined by the chain in storeCtx.
+*/
+DLLEXPORT X509VerifyStatusCode CryptoNative_X509ChainGetCachedOcspStatus(X509_STORE_CTX* storeCtx, char* cachePath);
+
+/*
+Build an OCSP request appropriate for the end-entity certificate using the issuer (and trust) as
+determined by the chain in storeCtx.
+*/
+DLLEXPORT OCSP_REQUEST* CryptoNative_X509ChainBuildOcspRequest(X509_STORE_CTX* storeCtx);
+
+/*
+Determine if the OCSP response is acceptable, and if acceptable report the status and
+cache the result (if appropriate)
+*/
+DLLEXPORT X509VerifyStatusCode CryptoNative_X509ChainVerifyOcsp(X509_STORE_CTX* storeCtx, OCSP_REQUEST* req, OCSP_RESPONSE* resp, char* cachePath);
index 372ec90..cc2cf2e 100644 (file)
@@ -378,14 +378,10 @@ namespace System.Net.Http
                         // If it succeeds in verifying the cert chain, we're done. Employing this instead of 
                         // our custom implementation will need to be revisited if we ever decide to introduce a 
                         // "disallowed" store that enables users to "untrust" certs the system trusts.
-                        int sslResult = Interop.Crypto.X509VerifyCert(storeCtx);
-                        if (sslResult == 1)
+                        if (Interop.Crypto.X509VerifyCert(storeCtx))
                         {
                             return true;
                         }
-
-                        // X509_verify_cert can return < 0 in the case of programmer error
-                        Debug.Assert(sslResult == 0, "Unexpected error from X509_verify_cert: " + sslResult);
                     }
 
                     // Either OpenSSL verification failed, or there was a server validation callback
index cf76ea3..7dc7b91 100644 (file)
@@ -73,6 +73,29 @@ namespace Internal.Cryptography.Pal
             return null;
         }
 
+        internal static SafeOcspResponseHandle DownloadOcspGet(string uri, ref TimeSpan remainingDownloadTime)
+        {
+            byte[] data = DownloadAsset(uri, ref remainingDownloadTime);
+
+            if (data == null)
+            {
+                return null;
+            }
+
+            // https://tools.ietf.org/html/rfc6960#appendix-A.2 says that the response is the DER-encoded
+            // response, so no rebuffering to interpret PEM is required.
+            SafeOcspResponseHandle resp = Interop.Crypto.DecodeOcspResponse(data);
+
+            if (resp.IsInvalid)
+            {
+                // We're not going to report this error to a user, so clear it
+                // (to avoid tainting future exceptions)
+                Interop.Crypto.ErrClearError();
+            }
+
+            return resp;
+        }
+
         private static byte[] DownloadAsset(string uri, ref TimeSpan remainingDownloadTime)
         {
             if (s_downloadBytes != null && remainingDownloadTime > TimeSpan.Zero)
index 5bf3d80..2f5f263 100644 (file)
@@ -5,7 +5,6 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.IO;
 using System.Security.Cryptography;
 using System.Security.Cryptography.X509Certificates;
 
@@ -56,106 +55,62 @@ namespace Internal.Cryptography.Pal
 
             TimeSpan remainingDownloadTime = timeout;
 
-            using (var leaf = new X509Certificate2(cert.Handle))
-            {
-                GC.KeepAlive(cert); // ensure cert's safe handle isn't finalized while raw handle is in use
-
-                var downloaded = new HashSet<X509Certificate2>();
-                var systemTrusted = new HashSet<X509Certificate2>();
-
-                HashSet<X509Certificate2> candidates = OpenSslX509ChainProcessor.FindCandidates(
-                    leaf,
-                    extraStore,
-                    downloaded,
-                    systemTrusted,
-                    ref remainingDownloadTime);
-
-                IChainPal chain = OpenSslX509ChainProcessor.BuildChain(
-                    leaf,
-                    candidates,
-                    systemTrusted,
-                    applicationPolicy,
-                    certificatePolicy,
-                    revocationMode,
-                    revocationFlag,
-                    verificationTime,
-                    ref remainingDownloadTime);
+            OpenSslX509ChainProcessor chainPal = OpenSslX509ChainProcessor.InitiateChain(
+                ((OpenSslX509CertificateReader)cert).SafeHandle,
+                verificationTime,
+                remainingDownloadTime);
 
-#if DEBUG
-                if (chain.ChainElements.Length > 0)
-                {
-                    X509Certificate2 reportedLeaf = chain.ChainElements[0].Certificate;
-                    Debug.Assert(reportedLeaf != null, "reportedLeaf != null");
-                    Debug.Assert(reportedLeaf.Equals(leaf), "reportedLeaf.Equals(leaf)");
-                    Debug.Assert(!ReferenceEquals(reportedLeaf, leaf), "!ReferenceEquals(reportedLeaf, leaf)");
-                }
-#endif
-
-                if (chain.ChainStatus.Length == 0 && downloaded.Count > 0)
-                {
-                    SaveIntermediateCertificates(chain.ChainElements, downloaded);
-                }
+            Interop.Crypto.X509VerifyStatusCode status = chainPal.FindFirstChain(extraStore);
 
-                // Everything we put into the chain has been cloned, dispose all the originals.
-                systemTrusted.DisposeAll();
-                downloaded.DisposeAll();
+            if (!OpenSslX509ChainProcessor.IsCompleteChain(status))
+            {
+                List<X509Certificate2> tmp = null;
+                status = chainPal.FindChainViaAia(ref tmp);
 
-                if (extraStore == null || extraStore.Count == 0)
+                if (tmp != null)
                 {
-                    // There were no extraStore certs, so everything can be disposed.
-                    foreach (X509Certificate2 candidate in candidates)
-                    {
-                        candidate.Dispose();
-                    }
-                }
-                else
-                {
-                    // Candidate certs which came from extraStore should NOT be disposed, since they came
-                    // from outside.
-                    var extraStoreByReference = new HashSet<X509Certificate2>(
-                        ReferenceEqualityComparer<X509Certificate2>.Instance);
-
-                    foreach (X509Certificate2 extraCert in extraStore)
+                    if (status == Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
                     {
-                        extraStoreByReference.Add(extraCert);
+                        SaveIntermediateCertificates(tmp);
                     }
 
-                    foreach (X509Certificate2 candidate in candidates)
+                    foreach (X509Certificate2 downloaded in tmp)
                     {
-                        if (!extraStoreByReference.Contains(candidate))
-                        {
-                            candidate.Dispose();
-                        }
+                        downloaded.Dispose();
                     }
                 }
-
-                return chain;
             }
-        }
-
-        private static void SaveIntermediateCertificates(
-            X509ChainElement[] chainElements,
-            HashSet<X509Certificate2> downloaded)
-        {
-            List<X509Certificate2> chainDownloaded = new List<X509Certificate2>(chainElements.Length);
 
-            // It should be very unlikely that we would have downloaded something, the chain succeed,
-            // and the thing we downloaded not being a part of the chain, but safer is better.
-            for (int i = 0; i < chainElements.Length; i++)
+            if (revocationMode == X509RevocationMode.Online &&
+                status != Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
             {
-                X509Certificate2 elementCert = chainElements[i].Certificate;
+                revocationMode = X509RevocationMode.Offline;
+            }
 
-                if (downloaded.Contains(elementCert))
-                {
-                    chainDownloaded.Add(elementCert);
-                }
+            // In NoCheck+OK then we don't need to build the chain any more, we already
+            // know it's error-free.  So skip straight to finish.
+            if (status != Interop.Crypto.X509VerifyStatusCode.X509_V_OK ||
+                revocationMode != X509RevocationMode.NoCheck)
+            {
+                chainPal.CommitToChain();
+                chainPal.ProcessRevocation(revocationMode, revocationFlag);
             }
 
-            if (chainDownloaded.Count == 0)
+            chainPal.Finish(applicationPolicy, certificatePolicy);
+
+#if DEBUG
+            if (chainPal.ChainElements.Length > 0)
             {
-                return;
+                X509Certificate2 reportedLeaf = chainPal.ChainElements[0].Certificate;
+                Debug.Assert(reportedLeaf != null, "reportedLeaf != null");
+                Debug.Assert(!ReferenceEquals(cert, reportedLeaf.Pal), "!ReferenceEquals(cert, reportedLeaf.Pal)");
             }
+#endif
+            return chainPal;
+        }
 
+        private static void SaveIntermediateCertificates(List<X509Certificate2> downloadedCerts)
+        {
             using (var userIntermediate = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
             {
                 try
@@ -168,7 +123,7 @@ namespace Internal.Cryptography.Pal
                     return;
                 }
 
-                foreach (X509Certificate2 cert in chainDownloaded)
+                foreach (X509Certificate2 cert in downloadedCerts)
                 {
                     try
                     {
index d81c335..df6c507 100644 (file)
@@ -7,12 +7,14 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 using System.Security.Cryptography.X509Certificates;
+using Microsoft.Win32.SafeHandles;
 
 namespace Internal.Cryptography.Pal
 {
     internal sealed class CollectionBackedStoreProvider : IStorePal
     {
         private readonly List<X509Certificate2> _certs;
+        private SafeX509StackHandle _nativeCollection;
 
         internal CollectionBackedStoreProvider(List<X509Certificate2> certs)
         {
@@ -53,5 +55,39 @@ namespace Internal.Cryptography.Pal
         {
             get { return null; }
         }
+
+        internal SafeX509StackHandle GetNativeCollection()
+        {
+            if (_nativeCollection == null)
+            {
+                lock (_certs)
+                {
+                    if (_nativeCollection == null)
+                    {
+                        SafeX509StackHandle nativeCollection = Interop.Crypto.NewX509Stack();
+
+                        foreach (X509Certificate2 cert in _certs)
+                        {
+                            var certPal = (OpenSslX509CertificateReader)cert.Pal;
+
+                            using (SafeX509Handle tmp = Interop.Crypto.X509UpRef(certPal.SafeHandle))
+                            {
+                                if (!Interop.Crypto.PushX509StackField(nativeCollection, tmp))
+                                {
+                                    throw Interop.Crypto.CreateOpenSslCryptographicException();
+                                }
+
+                                // Ownership was transferred to the cert stack.
+                                tmp.SetHandleAsInvalid();
+                            }
+                        }
+
+                        _nativeCollection = nativeCollection;
+                    }
+                }
+            }
+
+            return _nativeCollection;
+        }
     }
 }
index e7eeae3..72c2409 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
+using System.Buffers;
 using System.Diagnostics;
 using System.IO;
 using System.Security.Cryptography;
@@ -15,10 +16,20 @@ namespace Internal.Cryptography.Pal
 {
     internal static class CrlCache
     {
+        private static readonly string s_crlDir =
+            PersistedFiles.GetUserFeatureDirectory(
+                X509Persistence.CryptographyFeatureName,
+                X509Persistence.CrlsSubFeatureName);
+
+        private static readonly string s_ocspDir =
+            PersistedFiles.GetUserFeatureDirectory(
+                X509Persistence.CryptographyFeatureName,
+                X509Persistence.OcspSubFeatureName);
+
         private const ulong X509_R_CERT_ALREADY_IN_HASH_TABLE = 0x0B07D065;
 
         public static void AddCrlForCertificate(
-            X509Certificate2 cert,
+            SafeX509Handle cert,
             SafeX509StoreHandle store,
             X509RevocationMode revocationMode,
             DateTime verificationTime,
@@ -46,7 +57,7 @@ namespace Internal.Cryptography.Pal
             DownloadAndAddCrl(cert, store, ref remainingDownloadTime);
         }
 
-        private static bool AddCachedCrl(X509Certificate2 cert, SafeX509StoreHandle store, DateTime verificationTime)
+        private static bool AddCachedCrl(SafeX509Handle cert, SafeX509StoreHandle store, DateTime verificationTime)
         {
             string crlFile = GetCachedCrlPath(cert);
 
@@ -108,7 +119,7 @@ namespace Internal.Cryptography.Pal
         }
 
         private static void DownloadAndAddCrl(
-            X509Certificate2 cert,
+            SafeX509Handle cert,
             SafeX509StoreHandle store,
             ref TimeSpan remainingDownloadTime)
         {
@@ -160,18 +171,17 @@ namespace Internal.Cryptography.Pal
                 }
             }
         }
-        
-        private static string GetCachedCrlPath(X509Certificate2 cert, bool mkDir=false)
-        {
-            OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)cert.Pal;
 
-            string crlDir = PersistedFiles.GetUserFeatureDirectory(
-                X509Persistence.CryptographyFeatureName,
-                X509Persistence.CrlsSubFeatureName);
+        internal static string GetCachedOcspResponseDirectory()
+        {
+            return s_ocspDir;
+        }
 
+        private static string GetCachedCrlPath(SafeX509Handle cert, bool mkDir=false)
+        {
             // X509_issuer_name_hash returns "unsigned long", which is marshalled as ulong.
             // But it only sets 32 bits worth of data, so force it down to uint just... in case.
-            ulong persistentHashLong = Interop.Crypto.X509IssuerNameHash(pal.SafeHandle);
+            ulong persistentHashLong = Interop.Crypto.X509IssuerNameHash(cert);
             if (persistentHashLong == 0)
             {
                 Interop.Crypto.ErrClearError();
@@ -185,58 +195,55 @@ namespace Internal.Cryptography.Pal
 
             if (mkDir)
             {
-                Directory.CreateDirectory(crlDir);
+                Directory.CreateDirectory(s_crlDir);
             }
 
-            return Path.Combine(crlDir, localFileName);
+            return Path.Combine(s_crlDir, localFileName);
         }
 
-        private static string GetCdpUrl(X509Certificate2 cert)
+        private static string GetCdpUrl(SafeX509Handle cert)
         {
-            byte[] crlDistributionPoints = null;
+            ArraySegment<byte> crlDistributionPoints =
+                OpenSslX509CertificateReader.FindFirstExtension(cert, Oids.CrlDistributionPoints);
 
-            foreach (X509Extension extension in cert.Extensions)
-            {
-                if (StringComparer.Ordinal.Equals(extension.Oid.Value, Oids.CrlDistributionPoints))
-                {
-                    // If there's an Authority Information Access extension, it might be used for
-                    // looking up additional certificates for the chain.
-                    crlDistributionPoints = extension.RawData;
-                    break;
-                }
-            }
-
-            if (crlDistributionPoints == null)
+            if (crlDistributionPoints.Array == null)
             {
                 return null;
             }
 
-            AsnReader reader = new AsnReader(crlDistributionPoints, AsnEncodingRules.DER);
-            AsnReader sequenceReader = reader.ReadSequence();
-            reader.ThrowIfNotEmpty();
-
-            while (sequenceReader.HasData)
+            try
             {
-                DistributionPointAsn.Decode(sequenceReader, out DistributionPointAsn distributionPoint);
+                AsnReader reader = new AsnReader(crlDistributionPoints, AsnEncodingRules.DER);
+                AsnReader sequenceReader = reader.ReadSequence();
+                reader.ThrowIfNotEmpty();
 
-                // Only distributionPoint is supported
-                // Only fullName is supported, nameRelativeToCRLIssuer is for LDAP-based lookup.
-                if (distributionPoint.DistributionPoint.HasValue &&
-                    distributionPoint.DistributionPoint.Value.FullName != null)
+                while (sequenceReader.HasData)
                 {
-                    foreach (GeneralNameAsn name in distributionPoint.DistributionPoint.Value.FullName)
+                    DistributionPointAsn.Decode(sequenceReader, out DistributionPointAsn distributionPoint);
+
+                    // Only distributionPoint is supported
+                    // Only fullName is supported, nameRelativeToCRLIssuer is for LDAP-based lookup.
+                    if (distributionPoint.DistributionPoint.HasValue &&
+                        distributionPoint.DistributionPoint.Value.FullName != null)
                     {
-                        if (name.Uri != null &&
-                            Uri.TryCreate(name.Uri, UriKind.Absolute, out Uri uri) &&
-                            uri.Scheme == "http")
+                        foreach (GeneralNameAsn name in distributionPoint.DistributionPoint.Value.FullName)
                         {
-                            return name.Uri;
+                            if (name.Uri != null &&
+                                Uri.TryCreate(name.Uri, UriKind.Absolute, out Uri uri) &&
+                                uri.Scheme == "http")
+                            {
+                                return name.Uri;
+                            }
                         }
                     }
                 }
-            }
 
-            return null;
+                return null;
+            }
+            finally
+            {
+                ArrayPool<byte>.Shared.Return(crlDistributionPoints.Array);
+            }
         }
     }
 }
index fbf228b..341d985 100644 (file)
@@ -292,7 +292,7 @@ namespace Internal.Cryptography.Pal
             throw new CryptographicException(SR.Cryptography_X509_StoreNoFileAvailable);
         }
 
-        private static string GetStorePath(string storeName)
+        internal static string GetStorePath(string storeName)
         {
             string directoryName = GetDirectoryName(storeName);
 
index 5c7d944..9437304 100644 (file)
@@ -442,6 +442,21 @@ namespace Internal.Cryptography.Pal
             }
         }
 
+        internal static ArraySegment<byte> FindFirstExtension(SafeX509Handle cert, string oidValue)
+        {
+            int nid = Interop.Crypto.ResolveRequiredNid(oidValue);
+
+            using (SafeSharedAsn1OctetStringHandle data = Interop.Crypto.X509FindExtensionData(cert, nid))
+            {
+                if (data.IsInvalid)
+                {
+                    return default;
+                }
+
+                return Interop.Crypto.RentAsn1StringBytes(data.DangerousGetHandle());
+            }
+        }
+
         internal void SetPrivateKey(SafeEvpPKeyHandle privateKey)
         {
             _privateKey = privateKey;
index 03406d1..ea7ca81 100644 (file)
@@ -3,8 +3,11 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
+using System.Buffers;
+using System.Buffers.Text;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Runtime.InteropServices;
 using System.Security.Cryptography;
 using System.Security.Cryptography.Asn1;
 using System.Security.Cryptography.X509Certificates;
@@ -15,11 +18,51 @@ namespace Internal.Cryptography.Pal
 {
     internal sealed class OpenSslX509ChainProcessor : IChainPal
     {
-        // Constructed (0x20) | Sequence (0x10) => 0x30.
-        private const uint ConstructedSequenceTagId = 0x30;
+        // The average chain is 3 (End-Entity, Intermediate, Root)
+        // 10 is plenty big.
+        private const int DefaultChainCapacity = 10;
+
+        private static readonly string s_userRootPath =
+            DirectoryBasedStoreProvider.GetStorePath(X509Store.RootStoreName);
+
+        private static readonly string s_userIntermediatePath =
+            DirectoryBasedStoreProvider.GetStorePath(X509Store.IntermediateCAStoreName);
+
+        private static readonly string s_userPersonalPath =
+            DirectoryBasedStoreProvider.GetStorePath(X509Store.MyStoreName);
+
+        private SafeX509Handle _leafHandle;
+        private readonly SafeX509StoreHandle _store;
+        private readonly SafeX509StackHandle _untrustedLookup;
+        private readonly SafeX509StoreCtxHandle _storeCtx;
+        private readonly DateTime _verificationTime;
+        private TimeSpan _remainingDownloadTime;
+        private X509RevocationMode _revocationMode;
+
+        private OpenSslX509ChainProcessor(
+            SafeX509Handle leafHandle,
+            SafeX509StoreHandle store,
+            SafeX509StackHandle untrusted,
+            SafeX509StoreCtxHandle storeCtx,
+            DateTime verificationTime,
+            TimeSpan remainingDownloadTime)
+        {
+            _leafHandle = leafHandle;
+            _store = store;
+            _untrustedLookup = untrusted;
+            _storeCtx = storeCtx;
+            _verificationTime = verificationTime;
+            _remainingDownloadTime = remainingDownloadTime;
+        }
 
         public void Dispose()
         {
+            _storeCtx?.Dispose();
+            _untrustedLookup?.Dispose();
+            _store?.Dispose();
+            
+            // We don't own this one.
+            _leafHandle = null;
         }
 
         public bool? Verify(X509VerificationFlags flags, out Exception exception)
@@ -36,202 +79,577 @@ namespace Internal.Cryptography.Pal
             get { return null; }
         }
 
-        public static IChainPal BuildChain(
-            X509Certificate2 leaf,
-            HashSet<X509Certificate2> candidates,
-            HashSet<X509Certificate2> systemTrusted,
-            OidCollection applicationPolicy,
-            OidCollection certificatePolicy,
-            X509RevocationMode revocationMode,
-            X509RevocationFlag revocationFlag,
+        internal static OpenSslX509ChainProcessor InitiateChain(
+            SafeX509Handle leafHandle,
             DateTime verificationTime,
-            ref TimeSpan remainingDownloadTime)
+            TimeSpan remainingDownloadTime)
         {
-            X509ChainElement[] elements;
-            List<X509ChainStatus> overallStatus = new List<X509ChainStatus>();
-            WorkingChain workingChain = new WorkingChain();
-            Interop.Crypto.X509StoreVerifyCallback workingCallback = workingChain.VerifyCallback;
+            SafeX509StackHandle systemTrust = StorePal.GetMachineRoot().GetNativeCollection();
+            SafeX509StackHandle systemIntermediate = StorePal.GetMachineIntermediate().GetNativeCollection();
 
-            // An X509_STORE is more comparable to Cryptography.X509Certificate2Collection than to
-            // Cryptography.X509Store. So read this with OpenSSL eyes, not CAPI/CNG eyes.
-            //
-            // (If you need to think of it as an X509Store, it's a volatile memory store)
-            using (SafeX509StoreHandle store = Interop.Crypto.X509StoreCreate())
-            using (SafeX509StoreCtxHandle storeCtx = Interop.Crypto.X509StoreCtxCreate())
-            using (SafeX509StackHandle extraCerts = Interop.Crypto.NewX509Stack())
+            SafeX509StoreHandle store = null;
+            SafeX509StackHandle untrusted = null;
+            SafeX509StoreCtxHandle storeCtx = null;
+
+            try
             {
-                Interop.Crypto.CheckValidOpenSslHandle(store);
-                Interop.Crypto.CheckValidOpenSslHandle(storeCtx);
-                Interop.Crypto.CheckValidOpenSslHandle(extraCerts);
+                store = Interop.Crypto.X509ChainNew(systemTrust, s_userRootPath);
+
+                untrusted = Interop.Crypto.NewX509Stack();
+                Interop.Crypto.X509StackAddDirectoryStore(untrusted, s_userIntermediatePath);
+                Interop.Crypto.X509StackAddDirectoryStore(untrusted, s_userPersonalPath);
+                Interop.Crypto.X509StackAddMultiple(untrusted, systemIntermediate);
+                Interop.Crypto.X509StoreSetVerifyTime(store, verificationTime);
 
-                bool lookupCrl = revocationMode != X509RevocationMode.NoCheck;
+                storeCtx = Interop.Crypto.X509StoreCtxCreate();
 
-                foreach (X509Certificate2 cert in candidates)
+                if (!Interop.Crypto.X509StoreCtxInit(storeCtx, store, leafHandle, untrusted))
                 {
-                    OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)cert.Pal;
+                    throw Interop.Crypto.CreateOpenSslCryptographicException();
+                }
 
-                    using (SafeX509Handle handle = Interop.Crypto.X509UpRef(pal.SafeHandle))
+                return new OpenSslX509ChainProcessor(
+                    leafHandle,
+                    store,
+                    untrusted,
+                    storeCtx,
+                    verificationTime,
+                    remainingDownloadTime);
+            }
+            catch
+            {
+                store?.Dispose();
+                untrusted?.Dispose();
+                storeCtx?.Dispose();
+                throw;
+            }
+        }
+
+        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)
+            {
+                case Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+                case Interop.Crypto.X509VerifyStatusCode.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 =
+                Interop.Crypto.X509VerifyStatusCode.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)
                     {
-                        if (!Interop.Crypto.PushX509StackField(extraCerts, handle))
-                        {
-                            throw Interop.Crypto.CreateOpenSslCryptographicException();
-                        }
+                        break;
+                    }
+
+                    lastCert = currentHandle;
 
-                        // Ownership was transferred to the cert stack.
-                        handle.SetHandleAsInvalid();
+                    ArraySegment<byte> authorityInformationAccess =
+                        OpenSslX509CertificateReader.FindFirstExtension(
+                            currentCert,
+                            Oids.AuthorityInformationAccess);
+
+                    if (authorityInformationAccess.Count == 0)
+                    {
+                        break;
                     }
 
-                    if (lookupCrl)
+                    X509Certificate2 downloaded = DownloadCertificate(
+                        authorityInformationAccess,
+                        ref _remainingDownloadTime);
+
+                    // The AIA record is contained in a public structure, so no need to clear it.
+                    ArrayPool<byte>.Shared.Return(authorityInformationAccess.Array);
+
+                    if (downloaded == null)
                     {
-                        CrlCache.AddCrlForCertificate(
-                            cert,
-                            store,
-                            revocationMode,
-                            verificationTime,
-                            ref remainingDownloadTime);
+                        break;
+                    }
 
-                        // If we only wanted the end-entity certificate CRL then don't look up
-                        // any more of them.
-                        lookupCrl = revocationFlag != X509RevocationFlag.EndCertificateOnly;
+                    if (downloadedCerts == null)
+                    {
+                        downloadedCerts = new List<X509Certificate2>();
                     }
+
+                    AddToStackAndUpRef(downloaded.Handle, _untrustedLookup);
+                    downloadedCerts.Add(downloaded);
+                    
+                    Interop.Crypto.X509StoreCtxRebuildChain(storeCtx);
+                    statusCode = Interop.Crypto.X509StoreCtxGetError(storeCtx);
                 }
+            }
 
-                if (revocationMode != X509RevocationMode.NoCheck)
+            if (statusCode == Interop.Crypto.X509VerifyStatusCode.X509_V_OK && downloadedCerts != null)
+            {
+                using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(_storeCtx))
                 {
-                    if (!Interop.Crypto.X509StoreSetRevocationFlag(store, revocationFlag))
+                    int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack);
+                    Span<IntPtr> tempChain = stackalloc IntPtr[DefaultChainCapacity];
+                    IntPtr[] tempChainRent = null;
+
+                    if (chainSize <= tempChain.Length)
                     {
-                        throw Interop.Crypto.CreateOpenSslCryptographicException();
+                        tempChain = tempChain.Slice(0, chainSize);
+                    }
+                    else
+                    {
+                        tempChainRent = ArrayPool<IntPtr>.Shared.Rent(chainSize);
+                        tempChain = tempChainRent.AsSpan(0, chainSize);
                     }
-                }
 
-                foreach (X509Certificate2 trustedCert in systemTrusted)
-                {
-                    OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)trustedCert.Pal;
+                    for (int i = 0; i < chainSize; i++)
+                    {
+                        tempChain[i] = Interop.Crypto.GetX509StackField(chainStack, i);
+                    }
 
-                    if (!Interop.Crypto.X509StoreAddCert(store, pal.SafeHandle))
+                    // 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--)
                     {
-                        throw Interop.Crypto.CreateOpenSslCryptographicException();
+                        X509Certificate2 downloadedCert = downloadedCerts[i];
+
+                        if (!tempChain.Contains(downloadedCert.Handle))
+                        {
+                            downloadedCert.Dispose();
+                            downloadedCerts.RemoveAt(i);
+                        }
                     }
-                }
 
-                SafeX509Handle leafHandle = ((OpenSslX509CertificateReader)leaf.Pal).SafeHandle;
+                    if (downloadedCerts.Count == 0)
+                    {
+                        downloadedCerts = null;
+                    }
 
-                if (!Interop.Crypto.X509StoreCtxInit(storeCtx, store, leafHandle, extraCerts))
-                {
-                    throw Interop.Crypto.CreateOpenSslCryptographicException();
+                    if (tempChainRent != null)
+                    {
+                        // While the IntPtrs aren't secret, clearing them helps prevent
+                        // accidental use-after-free because of pooling.
+                        tempChain.Clear();
+                        ArrayPool<IntPtr>.Shared.Return(tempChainRent);
+                    }
                 }
+            }
+
+            return statusCode;
+        }
 
-                Interop.Crypto.X509StoreCtxSetVerifyCallback(storeCtx, workingCallback);
-                Interop.Crypto.SetX509ChainVerifyTime(storeCtx, verificationTime);
+        internal void CommitToChain()
+        {
+            Interop.Crypto.X509StoreCtxCommitToChain(_storeCtx);
+        }
+
+        internal void ProcessRevocation(
+            X509RevocationMode revocationMode,
+            X509RevocationFlag revocationFlag)
+        {
+            _revocationMode = revocationMode;
+
+            if (revocationMode == X509RevocationMode.NoCheck)
+            {
+                return;
+            }
 
-                int verify = Interop.Crypto.X509VerifyCert(storeCtx);
+            using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(_storeCtx))
+            {
+                int chainSize =
+                    revocationFlag == X509RevocationFlag.EndCertificateOnly ?
+                        1 :
+                        Interop.Crypto.GetX509StackFieldCount(chainStack);
 
-                if (verify < 0)
+                for (int i = 0; i < chainSize; i++)
                 {
-                    throw Interop.Crypto.CreateOpenSslCryptographicException();
+                    using (SafeX509Handle cert =
+                        Interop.Crypto.X509UpRef(Interop.Crypto.GetX509StackField(chainStack, i)))
+                    {
+                        CrlCache.AddCrlForCertificate(
+                            cert,
+                            _store,
+                            revocationMode,
+                            _verificationTime,
+                            ref _remainingDownloadTime);
+                    }
                 }
+            }
+
+            Interop.Crypto.X509StoreSetRevocationFlag(_store, revocationFlag);
+            Interop.Crypto.X509StoreCtxRebuildChain(_storeCtx);
+        }
+
+        internal void Finish(OidCollection applicationPolicy, OidCollection certificatePolicy)
+        {
+            WorkingChain workingChain = null;
+
+            // 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)
+            {
+                Interop.Crypto.X509StoreCtxReset(_storeCtx);
+
+                workingChain = new WorkingChain();
+                Interop.Crypto.X509StoreVerifyCallback workingCallback = workingChain.VerifyCallback;
+                Interop.Crypto.X509StoreCtxSetVerifyCallback(_storeCtx, workingCallback);
+
+                bool verify = Interop.Crypto.X509VerifyCert(_storeCtx);
+                GC.KeepAlive(workingCallback);
 
                 // 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 == 1, "verify == 1");
+                Debug.Assert(verify, "verify should have returned true");
+
+                const Interop.Crypto.X509VerifyStatusCode NoCrl =
+                    Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_UNABLE_TO_GET_CRL;
 
-                using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(storeCtx))
+                ErrorCollection? errors =
+                    workingChain.LastError > 0 ? (ErrorCollection?)workingChain[0] : null;
+
+                if (_revocationMode == X509RevocationMode.Online &&
+                    _remainingDownloadTime > TimeSpan.Zero &&
+                    errors?.HasError(NoCrl) == true)
                 {
-                    int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack);
-                    elements = new X509ChainElement[chainSize];
-                    int maybeRootDepth = chainSize - 1;
+                    Interop.Crypto.X509VerifyStatusCode ocspStatus = CheckOcsp();
 
-                    // The leaf cert is 0, up to (maybe) the root at chainSize - 1
-                    for (int i = 0; i < chainSize; i++)
+                    ref ErrorCollection refErrors = ref workingChain[0];
+
+                    if (ocspStatus == Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
+                    {
+                        refErrors.ClearError(NoCrl);
+                    }
+                    else if (ocspStatus != NoCrl)
                     {
-                        List<X509ChainStatus> status = new List<X509ChainStatus>();
+                        refErrors.ClearError(NoCrl);
+                        refErrors.Add(ocspStatus);
+                    }
+                }
+            }
 
-                        List<Interop.Crypto.X509VerifyStatusCode> elementErrors =
-                            i < workingChain.Errors.Count ? workingChain.Errors[i] : null;
+            X509ChainElement[] elements = BuildChainElements(
+                workingChain,
+                out List<X509ChainStatus> overallStatus);
 
-                        if (elementErrors != null)
-                        {
-                            AddElementStatus(elementErrors, status, overallStatus);
-                        }
+            workingChain?.Dispose();
 
-                        IntPtr elementCertPtr = Interop.Crypto.GetX509StackField(chainStack, i);
+            if (applicationPolicy?.Count > 0 || certificatePolicy?.Count > 0)
+            {
+                ProcessPolicy(elements, overallStatus, applicationPolicy, certificatePolicy);
+            }
 
-                        if (elementCertPtr == IntPtr.Zero)
-                        {
-                            throw Interop.Crypto.CreateOpenSslCryptographicException();
-                        }
+            ChainStatus = overallStatus?.ToArray() ?? Array.Empty<X509ChainStatus>();
+            ChainElements = elements;
+
+            // The native resources are not needed any longer.
+            Dispose();
+        }
+
+        private Interop.Crypto.X509VerifyStatusCode CheckOcsp()
+        {
+            string ocspCache = CrlCache.GetCachedOcspResponseDirectory();
+            Interop.Crypto.X509VerifyStatusCode status =
+                Interop.Crypto.X509ChainGetCachedOcspStatus(_storeCtx, ocspCache);
+
+            if (status != Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_UNABLE_TO_GET_CRL)
+            {
+                return status;
+            }
+
+            string baseUri = GetOcspEndpoint(_leafHandle);
+
+            if (baseUri == null)
+            {
+                return status;
+            }
+            
+            using (SafeOcspRequestHandle req = Interop.Crypto.X509ChainBuildOcspRequest(_storeCtx))
+            {
+                ArraySegment<byte> encoded = Interop.Crypto.OpenSslRentEncode(
+                    handle => Interop.Crypto.GetOcspRequestDerSize(handle),
+                    (handle, buf) => Interop.Crypto.EncodeOcspRequest(handle, buf),
+                    req);
+
+                ArraySegment<char> urlEncoded = Base64UrlEncode(encoded);
+                string requestUrl = UrlPathAppend(baseUri, urlEncoded);
+
+                // Nothing sensitive is in the encoded request (it was sent via HTTP-non-S)
+                ArrayPool<byte>.Shared.Return(encoded.Array);
+                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 =
+                    CertificateAssetDownloader.DownloadOcspGet(requestUrl, ref _remainingDownloadTime);
+
+                using (resp)
+                {
+                    if (resp == null || resp.IsInvalid)
+                    {
+                        return Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_UNABLE_TO_GET_CRL;
+                    }
 
-                        // Duplicate the certificate handle
-                        X509Certificate2 elementCert = new X509Certificate2(elementCertPtr);
-                        elements[i] = new X509ChainElement(elementCert, status.ToArray(), "");
+                    try
+                    {
+                        System.IO.Directory.CreateDirectory(ocspCache);
+                    }
+                    catch
+                    {
+                        // Opportunistic create, suppress all errors.
                     }
+
+                    return Interop.Crypto.X509ChainVerifyOcsp(_storeCtx, req, resp, ocspCache);
                 }
             }
+        }
+
+        private static string UrlPathAppend(string baseUri, ReadOnlyMemory<char> resource)
+        {
+            Debug.Assert(baseUri.Length > 0);
+            Debug.Assert(resource.Length > 0);
+
+            int count = baseUri.Length + resource.Length;
+
+            if (baseUri[baseUri.Length - 1] == '/')
+            {
+                return string.Create(
+                    count,
+                    (baseUri, resource),
+                    (buf, st) =>
+                    {
+                        st.baseUri.AsSpan().CopyTo(buf);
+                        st.resource.Span.CopyTo(buf.Slice(st.Item1.Length));
+                    });
+            }
 
-            GC.KeepAlive(workingCallback);
+            return string.Create(
+                count + 1,
+                (baseUri, resource),
+                (buf, st) =>
+                {
+                    st.baseUri.AsSpan().CopyTo(buf);
+                    buf[st.Item1.Length] = '/';
+                    st.resource.Span.CopyTo(buf.Slice(st.Item1.Length + 1));
+                });
+        }
+
+        private static ArraySegment<char> Base64UrlEncode(ReadOnlySpan<byte> input)
+        {
+            // Every 3 bytes turns into 4 chars for the Base64 operation
+            int base64Len = ((input.Length + 2) / 3) * 4;
+            char[] base64 = ArrayPool<char>.Shared.Rent(base64Len);
 
-            if ((certificatePolicy != null && certificatePolicy.Count > 0) ||
-                (applicationPolicy != null && applicationPolicy.Count > 0))
+            if (!Convert.TryToBase64Chars(input, base64, out int charsWritten))
             {
-                List<X509Certificate2> certsToRead = new List<X509Certificate2>();
+                Debug.Fail($"Convert.TryToBase64 failed with {input.Length} bytes to a {base64.Length} buffer");
+                throw new CryptographicException();
+            }
+
+            Debug.Assert(charsWritten == base64Len);
+
+            // In the degenerate case every char will turn into 3 chars.
+            int urlEncodedLen = charsWritten * 3;
+            char[] urlEncoded = ArrayPool<char>.Shared.Rent(urlEncodedLen);
+            int writeIdx = 0;
 
-                foreach (X509ChainElement element in elements)
+            for (int readIdx = 0; readIdx < charsWritten; readIdx++)
+            {
+                char cur = base64[readIdx];
+
+                if ((cur >= 'A' && cur <= 'Z') ||
+                    (cur >= 'a' && cur <= 'z') ||
+                    (cur >= '0' && cur <= '9'))
+                {
+                    urlEncoded[writeIdx++] = cur;
+                }
+                else if (cur == '+')
+                {
+                    urlEncoded[writeIdx++] = '%';
+                    urlEncoded[writeIdx++] = '2';
+                    urlEncoded[writeIdx++] = 'B';
+                }
+                else if (cur == '/')
                 {
-                    certsToRead.Add(element.Certificate);
+                    urlEncoded[writeIdx++] = '%';
+                    urlEncoded[writeIdx++] = '2';
+                    urlEncoded[writeIdx++] = 'F';
+                }
+                else if (cur == '=')
+                {
+                    urlEncoded[writeIdx++] = '%';
+                    urlEncoded[writeIdx++] = '3';
+                    urlEncoded[writeIdx++] = 'D';
+                }
+                else
+                {
+                    Debug.Fail($"'{cur}' is not a valid Base64 character");
+                    throw new CryptographicException();
                 }
+            }
+
+            ArrayPool<char>.Shared.Return(base64);
+            return new ArraySegment<char>(urlEncoded, 0, writeIdx);
+        }
 
-                CertificatePolicyChain policyChain = new CertificatePolicyChain(certsToRead);
+        private X509ChainElement[] BuildChainElements(
+            WorkingChain workingChain,
+            out List<X509ChainStatus> overallStatus)
+        {
+            X509ChainElement[] elements;
+            overallStatus = null;
 
-                bool failsPolicyChecks = false;
+            using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(_storeCtx))
+            {
+                int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack);
+                elements = new X509ChainElement[chainSize];
 
-                if (certificatePolicy != null)
+                for (int i = 0; i < chainSize; i++)
                 {
-                    if (!policyChain.MatchesCertificatePolicies(certificatePolicy))
+                    X509ChainStatus[] status = Array.Empty<X509ChainStatus>();
+                    ErrorCollection? elementErrors =
+                        workingChain?.LastError > i ? (ErrorCollection?)workingChain[i] : null;
+
+                    if (elementErrors.HasValue && elementErrors.Value.HasErrors)
                     {
-                        failsPolicyChecks = true;
+                        List<X509ChainStatus> statusBuilder = new List<X509ChainStatus>();
+                        overallStatus = new List<X509ChainStatus>();
+
+                        AddElementStatus(elementErrors.Value, statusBuilder, overallStatus);
+                        status = statusBuilder.ToArray();
                     }
-                }
 
-                if (applicationPolicy != null)
-                {
-                    if (!policyChain.MatchesApplicationPolicies(applicationPolicy))
+                    IntPtr elementCertPtr = Interop.Crypto.GetX509StackField(chainStack, i);
+
+                    if (elementCertPtr == IntPtr.Zero)
                     {
-                        failsPolicyChecks = true;
+                        throw Interop.Crypto.CreateOpenSslCryptographicException();
                     }
+
+                    // Duplicate the certificate handle
+                    X509Certificate2 elementCert = new X509Certificate2(elementCertPtr);
+                    elements[i] = new X509ChainElement(elementCert, status, "");
                 }
+            }
 
-                if (failsPolicyChecks)
-                {
-                    X509ChainElement leafElement = elements[0];
+            return elements;
+        }
 
-                    X509ChainStatus chainStatus = new X509ChainStatus
-                    {
-                        Status = X509ChainStatusFlags.NotValidForUsage,
-                        StatusInformation = SR.Chain_NoPolicyMatch,
-                    };
+        private static void ProcessPolicy(
+            X509ChainElement[] elements,
+            List<X509ChainStatus> overallStatus,
+            OidCollection applicationPolicy,
+            OidCollection certificatePolicy)
+        {
+            List<X509Certificate2> certsToRead = new List<X509Certificate2>();
+
+            foreach (X509ChainElement element in elements)
+            {
+                certsToRead.Add(element.Certificate);
+            }
 
-                    var elementStatus = new List<X509ChainStatus>(leafElement.ChainElementStatus.Length + 1);
-                    elementStatus.AddRange(leafElement.ChainElementStatus);
+            CertificatePolicyChain policyChain = new CertificatePolicyChain(certsToRead);
 
-                    AddUniqueStatus(elementStatus, ref chainStatus);
-                    AddUniqueStatus(overallStatus, ref chainStatus);
+            bool failsPolicyChecks = false;
 
-                    elements[0] = new X509ChainElement(
-                        leafElement.Certificate,
-                        elementStatus.ToArray(),
-                        leafElement.Information);
+            if (certificatePolicy != null)
+            {
+                if (!policyChain.MatchesCertificatePolicies(certificatePolicy))
+                {
+                    failsPolicyChecks = true;
                 }
             }
 
-            return new OpenSslX509ChainProcessor
+            if (applicationPolicy != null)
             {
-                ChainStatus = overallStatus.ToArray(),
-                ChainElements = elements,
-            };
+                if (!policyChain.MatchesApplicationPolicies(applicationPolicy))
+                {
+                    failsPolicyChecks = true;
+                }
+            }
+
+            if (failsPolicyChecks)
+            {
+                X509ChainElement leafElement = elements[0];
+
+                X509ChainStatus chainStatus = new X509ChainStatus
+                {
+                    Status = X509ChainStatusFlags.NotValidForUsage,
+                    StatusInformation = SR.Chain_NoPolicyMatch,
+                };
+
+                var elementStatus = new List<X509ChainStatus>(leafElement.ChainElementStatus.Length + 1);
+                elementStatus.AddRange(leafElement.ChainElementStatus);
+
+                AddUniqueStatus(elementStatus, ref chainStatus);
+                AddUniqueStatus(overallStatus, ref chainStatus);
+
+                elements[0] = new X509ChainElement(
+                    leafElement.Certificate,
+                    elementStatus.ToArray(),
+                    leafElement.Information);
+            }
         }
 
         private static void AddElementStatus(
-            List<Interop.Crypto.X509VerifyStatusCode> errorCodes,
+            ErrorCollection errorCodes,
             List<X509ChainStatus> elementStatus,
             List<X509ChainStatus> overallStatus)
         {
@@ -367,250 +785,44 @@ namespace Internal.Cryptography.Pal
             }
         }
 
-        internal static HashSet<X509Certificate2> FindCandidates(
-            X509Certificate2 leaf,
-            X509Certificate2Collection extraStore,
-            HashSet<X509Certificate2> downloaded,
-            HashSet<X509Certificate2> systemTrusted,
-            ref TimeSpan remainingDownloadTime)
-        {
-            var candidates = new HashSet<X509Certificate2>();
-            var toProcess = new Queue<X509Certificate2>();
-            toProcess.Enqueue(leaf);
-
-            using (var systemRootStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
-            using (var systemIntermediateStore = new X509Store(StoreName.CertificateAuthority, StoreLocation.LocalMachine))
-            using (var userRootStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser))
-            using (var userIntermediateStore = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
-            using (var userMyStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
-            {
-                systemRootStore.Open(OpenFlags.ReadOnly);
-                systemIntermediateStore.Open(OpenFlags.ReadOnly);
-                userRootStore.Open(OpenFlags.ReadOnly);
-                userIntermediateStore.Open(OpenFlags.ReadOnly);
-                userMyStore.Open(OpenFlags.ReadOnly);
-
-                X509Certificate2Collection systemRootCerts = systemRootStore.Certificates;
-                X509Certificate2Collection systemIntermediateCerts = systemIntermediateStore.Certificates;
-                X509Certificate2Collection userRootCerts = userRootStore.Certificates;
-                X509Certificate2Collection userIntermediateCerts = userIntermediateStore.Certificates;
-                X509Certificate2Collection userMyCerts = userMyStore.Certificates;
-
-                // fill the system trusted collection
-                foreach (X509Certificate2 userRootCert in userRootCerts)
-                {
-                    if (!systemTrusted.Add(userRootCert))
-                    {
-                        // If we have already (effectively) added another instance of this certificate,
-                        // then this one provides no value. A Disposed cert won't harm the matching logic.
-                        userRootCert.Dispose();
-                    }
-                }
-
-                foreach (X509Certificate2 systemRootCert in systemRootCerts)
-                {
-                    if (!systemTrusted.Add(systemRootCert))
-                    {
-                        // If we have already (effectively) added another instance of this certificate,
-                        // (for example, because another copy of it was in the user store)
-                        // then this one provides no value. A Disposed cert won't harm the matching logic.
-                        systemRootCert.Dispose();
-                    }
-                }
-
-                X509Certificate2Collection[] storesToCheck;
-                if (extraStore != null && extraStore.Count > 0)
-                {
-                    storesToCheck = new[]
-                    {
-                        extraStore,
-                        userMyCerts,
-                        userIntermediateCerts,
-                        systemIntermediateCerts,
-                        userRootCerts,
-                        systemRootCerts,
-                    };
-                }
-                else
-                {
-                    storesToCheck = new[]
-                    {
-                        userMyCerts,
-                        userIntermediateCerts,
-                        systemIntermediateCerts,
-                        userRootCerts,
-                        systemRootCerts,
-                    };
-                }
-
-                while (toProcess.Count > 0)
-                {
-                    X509Certificate2 current = toProcess.Dequeue();
-
-                    candidates.Add(current);
-
-                    HashSet<X509Certificate2> results = FindIssuer(
-                        current,
-                        storesToCheck,
-                        downloaded,
-                        ref remainingDownloadTime);
-
-                    if (results != null)
-                    {
-                        foreach (X509Certificate2 result in results)
-                        {
-                            if (!candidates.Contains(result))
-                            {
-                                toProcess.Enqueue(result);
-                            }
-                        }
-                    }
-                }
-
-                // Avoid sending unused certs into the finalizer queue by doing only a ref check
-
-                var candidatesByReference = new HashSet<X509Certificate2>(
-                    candidates,
-                    ReferenceEqualityComparer<X509Certificate2>.Instance);
-
-                // Certificates come from 6 sources:
-                //  1) extraStore.
-                //     These are cert objects that are provided by the user, we shouldn't dispose them.
-                //  2) the machine root store
-                //     These certs are moving on to the "was I a system trust?" test, and we shouldn't dispose them.
-                //  3) the user root store
-                //     These certs are moving on to the "was I a system trust?" test, and we shouldn't dispose them.
-                //  4) the machine intermediate store
-                //     These certs were either path candidates, or not. If they were, don't dispose them. Otherwise do.
-                //  5) the user intermediate store
-                //     These certs were either path candidates, or not. If they were, don't dispose them. Otherwise do.
-                //  6) the user my store
-                //     These certs were either path candidates, or not. If they were, don't dispose them. Otherwise do.
-                DisposeUnreferenced(candidatesByReference, systemIntermediateCerts);
-                DisposeUnreferenced(candidatesByReference, userIntermediateCerts);
-                DisposeUnreferenced(candidatesByReference, userMyCerts);
-            }
-
-            return candidates;
-        }
-
-        private static void DisposeUnreferenced(
-            ISet<X509Certificate2> referencedSet,
-            X509Certificate2Collection storeCerts)
-        {
-            foreach (X509Certificate2 cert in storeCerts)
-            {
-                if (!referencedSet.Contains(cert))
-                {
-                    cert.Dispose();
-                }
-            }
-        }
-
-        private static HashSet<X509Certificate2> FindIssuer(
-            X509Certificate2 cert,
-            X509Certificate2Collection[] stores,
-            HashSet<X509Certificate2> downloadedCerts,
+        private static X509Certificate2 DownloadCertificate(
+            ReadOnlyMemory<byte> authorityInformationAccess,
             ref TimeSpan remainingDownloadTime)
         {
-            if (IsSelfSigned(cert))
+            // Don't do any work if we're over limit.
+            if (remainingDownloadTime <= TimeSpan.Zero)
             {
-                // It's a root cert, we won't make any progress.
                 return null;
             }
 
-            SafeX509Handle certHandle = ((OpenSslX509CertificateReader)cert.Pal).SafeHandle;
-
-            foreach (X509Certificate2Collection store in stores)
-            {
-                HashSet<X509Certificate2> fromStore = null;
-
-                foreach (X509Certificate2 candidate in store)
-                {
-                    var certPal = (OpenSslX509CertificateReader)candidate.Pal;
-
-                    if (certPal == null)
-                    {
-                        continue;
-                    }
-
-                    SafeX509Handle candidateHandle = certPal.SafeHandle;
-
-                    int issuerError = Interop.Crypto.X509CheckIssued(candidateHandle, certHandle);
-
-                    if (issuerError == 0)
-                    {
-                        if (fromStore == null)
-                        {
-                            fromStore = new HashSet<X509Certificate2>();
-                        }
-
-                        fromStore.Add(candidate);
-                    }
-                }
-
-                if (fromStore != null)
-                {
-                    return fromStore;
-                }
-            }
-
-            byte[] authorityInformationAccess = null;
-
-            foreach (X509Extension extension in cert.Extensions)
-            {
-                if (StringComparer.Ordinal.Equals(extension.Oid.Value, Oids.AuthorityInformationAccess))
-                {
-                    // If there's an Authority Information Access extension, it might be used for
-                    // looking up additional certificates for the chain.
-                    authorityInformationAccess = extension.RawData;
-                    break;
-                }
-            }
+            string uri = FindHttpAiaRecord(authorityInformationAccess, Oids.CertificateAuthorityIssuers);
 
-            if (authorityInformationAccess != null)
+            if (uri == null)
             {
-                X509Certificate2 downloaded = DownloadCertificate(
-                    authorityInformationAccess,
-                    ref remainingDownloadTime);
-
-                if (downloaded != null)
-                {
-                    downloadedCerts.Add(downloaded);
-
-                    return new HashSet<X509Certificate2>() { downloaded };
-                }
+                return null;
             }
 
-            return null;
+            return CertificateAssetDownloader.DownloadCertificate(uri, ref remainingDownloadTime);
         }
 
-        private static bool IsSelfSigned(X509Certificate2 cert)
+        private static string GetOcspEndpoint(SafeX509Handle cert)
         {
-            return StringComparer.Ordinal.Equals(cert.Subject, cert.Issuer);
-        }
+            ArraySegment<byte> authorityInformationAccess =
+                OpenSslX509CertificateReader.FindFirstExtension(
+                    cert,
+                    Oids.AuthorityInformationAccess);
 
-        private static X509Certificate2 DownloadCertificate(
-            byte[] authorityInformationAccess,
-            ref TimeSpan remainingDownloadTime)
-        {
-            // Don't do any work if we're over limit.
-            if (remainingDownloadTime <= TimeSpan.Zero)
+            if (authorityInformationAccess.Count == 0)
             {
                 return null;
             }
 
-            string uri = FindHttpAiaRecord(authorityInformationAccess, Oids.CertificateAuthorityIssuers);
-
-            if (uri == null)
-            {
-                return null;
-            }
-
-            return CertificateAssetDownloader.DownloadCertificate(uri, ref remainingDownloadTime);
+            string baseUrl = FindHttpAiaRecord(authorityInformationAccess, Oids.OcspEndpoint);
+            ArrayPool<byte>.Shared.Return(authorityInformationAccess.Array);
+            return baseUrl;
         }
 
-        internal static string FindHttpAiaRecord(byte[] authorityInformationAccess, string recordTypeOid)
+        private static string FindHttpAiaRecord(ReadOnlyMemory<byte> authorityInformationAccess, string recordTypeOid)
         {
             AsnReader reader = new AsnReader(authorityInformationAccess, AsnEncodingRules.DER);
             AsnReader sequenceReader = reader.ReadSequence();
@@ -634,10 +846,52 @@ namespace Internal.Cryptography.Pal
             return null;
         }
 
-        private class WorkingChain
+        private static void AddToStackAndUpRef(SafeX509Handle cert, SafeX509StackHandle stack)
         {
-            internal readonly List<List<Interop.Crypto.X509VerifyStatusCode>> Errors =
-                new List<List<Interop.Crypto.X509VerifyStatusCode>>();
+            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 sealed class WorkingChain : IDisposable
+        {
+            private ErrorCollection[] _errors;
+
+            internal int LastError => _errors?.Length ?? 0;
+
+            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(int ok, IntPtr ctx)
             {
@@ -658,17 +912,26 @@ namespace Internal.Cryptography.Pal
                         if (errorCode != Interop.Crypto.X509VerifyStatusCode.X509_V_OK &&
                             errorCode != Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_CRL_NOT_YET_VALID)
                         {
-                            while (Errors.Count <= errorDepth)
+                            if (_errors == null)
                             {
-                                Errors.Add(null);
-                            }
+                                int size = Math.Max(DefaultChainCapacity, errorDepth + 1);
+                                _errors = ArrayPool<ErrorCollection>.Shared.Rent(size);
 
-                            if (Errors[errorDepth] == null)
+                                // We only do spares writes.
+                                _errors.AsSpan().Clear();
+                            }
+                            else if (errorDepth >= _errors.Length)
                             {
-                                Errors[errorDepth] = new List<Interop.Crypto.X509VerifyStatusCode>();
+                                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);
                             }
 
-                            Errors[errorDepth].Add(errorCode);
+                            _errors[errorDepth].Add(errorCode);
                         }
                     }
 
@@ -680,5 +943,127 @@ namespace Internal.Cryptography.Pal
                 }
             }
         }
+
+        private unsafe struct ErrorCollection
+        {
+            // As of OpenSSL 1.1.1 there are 74 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;
+            }
+
+            public void ClearError(Interop.Crypto.X509VerifyStatusCode statusCode)
+            {
+                int bucket = FindBucket(statusCode, out int bitValue);
+                _codes[bucket] &= ~bitValue;
+            }
+
+            internal bool HasError(Interop.Crypto.X509VerifyStatusCode statusCode)
+            {
+                int bucket = FindBucket(statusCode, out int bitValue);
+                return (_codes[bucket] & bitValue) != 0;
+            }
+
+            public Enumerator GetEnumerator()
+            {
+                if (HasOverflow)
+                {
+                    throw new CryptographicException();
+                }
+
+                return new Enumerator(this);
+            }
+
+            private static int FindBucket(Interop.Crypto.X509VerifyStatusCode statusCode, out int bitValue)
+            {
+                int val = (int)statusCode;
+
+                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 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);
+            }
+        }
     }
 }
index 40bb27b..509c319 100644 (file)
@@ -140,6 +140,22 @@ namespace Internal.Cryptography.Pal
             return new ExportProvider(certificates);
         }
 
+        internal static CollectionBackedStoreProvider GetMachineRoot()
+        {
+            return (CollectionBackedStoreProvider)FromSystemStore(
+                X509Store.RootStoreName,
+                StoreLocation.LocalMachine,
+                OpenFlags.ReadOnly);
+        }
+
+        internal static CollectionBackedStoreProvider GetMachineIntermediate()
+        {
+            return (CollectionBackedStoreProvider)FromSystemStore(
+                X509Store.IntermediateCAStoreName,
+                StoreLocation.LocalMachine,
+                OpenFlags.ReadOnly);
+        }
+
         public static IStorePal FromSystemStore(string storeName, StoreLocation storeLocation, OpenFlags openFlags)
         {
             if (storeLocation == StoreLocation.CurrentUser)
index fa29b20..250c096 100644 (file)
@@ -9,5 +9,6 @@ namespace Internal.Cryptography.Pal
         internal const string CryptographyFeatureName = "cryptography";
         internal const string X509StoresSubFeatureName = "x509stores";
         internal const string CrlsSubFeatureName = "crls";
+        internal const string OcspSubFeatureName = "ocsp";
     }
 }
index a5af472..e8c2018 100644 (file)
     <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.GetIntegerBytes.cs">
       <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.GetIntegerBytes.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.Nid.cs">
+      <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.Nid.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.Bignum.cs">
       <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Bignum.cs</Link>
     </Compile>
     <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs">
       <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.OCSP.cs">
+      <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.OCSP.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.Pkcs12.cs">
       <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Pkcs12.cs</Link>
     </Compile>
index f816865..f432a2e 100644 (file)
@@ -13,6 +13,7 @@ namespace System.Security.Cryptography.X509Certificates
         internal const string RootStoreName = "Root";
         internal const string IntermediateCAStoreName = "CA";
         internal const string DisallowedStoreName = "Disallowed";
+        internal const string MyStoreName = "My";
 
         private IStorePal _storePal;
 
@@ -56,7 +57,7 @@ namespace System.Security.Cryptography.X509Certificates
                     Name = DisallowedStoreName;
                     break;
                 case StoreName.My:
-                    Name = "My";
+                    Name = MyStoreName;
                     break;
                 case StoreName.Root:
                     Name = RootStoreName;
index dc254ac..b480b50 100644 (file)
@@ -559,6 +559,19 @@ namespace System.Security.Cryptography.X509Certificates.Tests
 
                     for (int j = 0; j < onlineChain.ChainElements.Count; j++)
                     {
+                        X509ChainStatusFlags chainFlags = onlineChain.ChainStatus.Aggregate(
+                            X509ChainStatusFlags.NoError,
+                            (cur, status) => cur | status.Status);
+
+                        const X509ChainStatusFlags WontCheck =
+                            X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.UntrustedRoot;
+
+                        if (chainFlags == WontCheck)
+                        {
+                            Console.WriteLine($"{nameof(VerifyWithRevocation)}: online chain failed with {{{chainFlags}}}, skipping");
+                            return;
+                        }
+
                         X509ChainElement chainElement = onlineChain.ChainElements[j];
 
                         // Since `NoError` gets mapped as the empty array, just look for non-empty arrays