diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.ASN1.Nid.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.ASN1.Nid.cs index afc79ae504fd..008fcfc4b138 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.ASN1.Nid.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.ASN1.Nid.cs @@ -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 s_nidLookup = + new ConcurrentDictionary(); + 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; + } } } diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs index cfcac7d7dcb1..8671e2e03961 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs @@ -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 @@ private static extern int SetX509ChainVerifyTime( 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 byte[] GetAsn1StringBytes(IntPtr asn1) return GetDynamicBuffer((ptr, buf, i) => GetAsn1StringBytes(ptr, buf, i), asn1); } + internal static ArraySegment 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 void SetX509ChainVerifyTime(SafeX509StoreCtxHandle ctx, DateTime } } + 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(NegativeSizeReadMethod method, THandle handle) { int negativeSize = method(handle, null, 0); @@ -179,5 +218,28 @@ private static byte[] GetDynamicBuffer(NegativeSizeReadMethod return bytes; } + + private static ArraySegment RentDynamicBuffer(NegativeSizeReadMethod method, THandle handle) + { + int negativeSize = method(handle, null, 0); + + if (negativeSize > 0) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + int targetSize = -negativeSize; + byte[] bytes = ArrayPool.Shared.Rent(targetSize); + + int ret = method(handle, bytes, targetSize); + + if (ret != 1) + { + ArrayPool.Shared.Return(bytes); + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + + return new ArraySegment(bytes, 0, targetSize); + } } } diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Encode.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Encode.cs index 27861a263e06..309ab2af19e6 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Encode.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Encode.cs @@ -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 Crypto internal delegate int EncodeFunc(THandle handle, byte[] buf); - internal static byte[] OpenSslEncode(GetEncodedSizeFunc getSize, EncodeFunc encode, THandle handle) + internal static byte[] OpenSslEncode( + GetEncodedSizeFunc getSize, + EncodeFunc 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 byte[] OpenSslEncode(GetEncodedSizeFunc getSiz return data; } + + internal static ArraySegment OpenSslRentEncode( + GetEncodedSizeFunc getSize, + EncodeFunc encode, + THandle handle) + where THandle : SafeHandle + { + int size = getSize(handle); + + if (size < 1) + { + throw CreateOpenSslCryptographicException(); + } + + byte[] data = ArrayPool.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.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(data, 0, size2); + } } } diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OCSP.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OCSP.cs new file mode 100644 index 000000000000..bcf9e2af48ef --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OCSP.cs @@ -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 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; + } + } +} diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509.cs index 17f110270284..b17af46f53fd 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509.cs @@ -92,6 +92,21 @@ internal static SafeSharedAsn1IntegerHandle X509GetSerialNumber(SafeX509Handle x [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 SafeSharedAsn1IntegerHandle X509GetSerialNumber(SafeX509Handle x [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 SafeSharedAsn1IntegerHandle X509GetSerialNumber(SafeX509Handle x [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 extern bool X509StoreCtxInit( 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); diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs index 7853e8e638b1..6619f0c2fdc8 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs @@ -35,6 +35,28 @@ internal static partial class Crypto /// [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(); + } + } } } diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509StoreCtx.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509StoreCtx.cs index 0da65d10ce62..1af339518440 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509StoreCtx.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509StoreCtx.cs @@ -19,6 +19,20 @@ internal static partial class Crypto [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); diff --git a/src/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs b/src/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs index ddf413110c03..72487162b33d 100644 --- a/src/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs +++ b/src/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.InteropServices; -using System.Security; namespace Microsoft.Win32.SafeHandles { @@ -75,4 +74,12 @@ private SafeSharedAsn1IntegerHandle() : { } } + + internal sealed class SafeSharedAsn1OctetStringHandle : SafeInteriorHandle + { + private SafeSharedAsn1OctetStringHandle() : + base(IntPtr.Zero, ownsHandle: true) + { + } + } } diff --git a/src/Common/src/System/Security/Cryptography/Oids.cs b/src/Common/src/System/Security/Cryptography/Oids.cs index 08404bfcdaa4..b24fa7a59f63 100644 --- a/src/Common/src/System/Security/Cryptography/Oids.cs +++ b/src/Common/src/System/Security/Cryptography/Oids.cs @@ -42,6 +42,7 @@ internal static partial class Oids 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"; diff --git a/src/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt b/src/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt index dc4023ccb602..814a06cbf92b 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt +++ b/src/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt @@ -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 diff --git a/src/Native/Unix/System.Security.Cryptography.Native/apibridge.c b/src/Native/Unix/System.Security.Cryptography.Native/apibridge.c index 567d242b227f..0f5ee6dfe7f6 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/apibridge.c +++ b/src/Native/Unix/System.Security.Cryptography.Native/apibridge.c @@ -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) diff --git a/src/Native/Unix/System.Security.Cryptography.Native/apibridge.h b/src/Native/Unix/System.Security.Cryptography.Native/apibridge.h index 2bcdc5656e96..c7f4405b41c8 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/apibridge.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/apibridge.h @@ -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); diff --git a/src/Native/Unix/System.Security.Cryptography.Native/openssl.c b/src/Native/Unix/System.Security.Cryptography.Native/openssl.c index 81c79300ef1f..9bbd92014468 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/openssl.c +++ b/src/Native/Unix/System.Security.Cryptography.Native/openssl.c @@ -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 diff --git a/src/Native/Unix/System.Security.Cryptography.Native/openssl.h b/src/Native/Unix/System.Security.Cryptography.Native/openssl.h index 3de0920ec3ff..3a6d5716c75e 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/openssl.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/openssl.h @@ -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); diff --git a/src/Native/Unix/System.Security.Cryptography.Native/openssl_1_0_structs.h b/src/Native/Unix/System.Security.Cryptography.Native/openssl_1_0_structs.h index 8852c3c1b31e..f777536b1d9a 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/openssl_1_0_structs.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/openssl_1_0_structs.h @@ -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; }; diff --git a/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h b/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h index 56148deac036..62a3397499be 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -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 diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_asn1.c b/src/Native/Unix/System.Security.Cryptography.Native/pal_asn1.c index 10faa926d495..ccfe731a6ed7 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_asn1.c +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_asn1.c @@ -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); diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_asn1.h b/src/Native/Unix/System.Security.Cryptography.Native/pal_asn1.h index 49696de1c517..4659c3949dc4 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_asn1.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_asn1.h @@ -40,6 +40,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. */ diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_ocsp.c b/src/Native/Unix/System.Security.Cryptography.Native/pal_ocsp.c new file mode 100644 index 000000000000..666035eb3d26 --- /dev/null +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_ocsp.c @@ -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/Native/Unix/System.Security.Cryptography.Native/pal_ocsp.h b/src/Native/Unix/System.Security.Cryptography.Native/pal_ocsp.h new file mode 100644 index 000000000000..e720582c420d --- /dev/null +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_ocsp.h @@ -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); diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_x509.c b/src/Native/Unix/System.Security.Cryptography.Native/pal_x509.c index 1aa421a40f09..225fcdd49a25 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_x509.c +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_x509.c @@ -6,6 +6,10 @@ #include #include +#include +#include +#include +#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; +} diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_x509.h b/src/Native/Unix/System.Security.Cryptography.Native/pal_x509.h index 96baa33488ad..51be3455fd82 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_x509.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_x509.h @@ -188,6 +188,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. */ @@ -240,6 +245,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 */ @@ -255,6 +265,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. */ @@ -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); diff --git a/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.Linux.cs b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.Linux.cs index 372ec90fa705..cc2cf2ef6953 100644 --- a/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.Linux.cs +++ b/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.Linux.cs @@ -378,14 +378,10 @@ private static bool VerifyCertChain(SafeX509StoreCtxHandle storeCtx, EasyRequest // 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 diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CertificateAssetDownloader.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CertificateAssetDownloader.cs index cf76ea3f402d..7dc7b91cd3c4 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CertificateAssetDownloader.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CertificateAssetDownloader.cs @@ -73,6 +73,29 @@ internal static SafeX509CrlHandle DownloadCrl(string uri, ref TimeSpan remaining 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) diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ChainPal.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ChainPal.cs index 5bf3d804d9a9..2f5f263a2bc8 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ChainPal.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/ChainPal.cs @@ -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 @@ public static IChainPal BuildChain( 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(); - var systemTrusted = new HashSet(); - - HashSet 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 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( - ReferenceEqualityComparer.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 downloaded) - { - List chainDownloaded = new List(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 downloadedCerts) + { using (var userIntermediate = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser)) { try @@ -168,7 +123,7 @@ private static void SaveIntermediateCertificates( return; } - foreach (X509Certificate2 cert in chainDownloaded) + foreach (X509Certificate2 cert in downloadedCerts) { try { diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CollectionBackedStoreProvider.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CollectionBackedStoreProvider.cs index d81c335f1bda..df6c5076a431 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CollectionBackedStoreProvider.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CollectionBackedStoreProvider.cs @@ -7,12 +7,14 @@ 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 _certs; + private SafeX509StackHandle _nativeCollection; internal CollectionBackedStoreProvider(List certs) { @@ -53,5 +55,39 @@ SafeHandle IStorePal.SafeHandle { 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; + } } } diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CrlCache.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CrlCache.cs index e7eeae34976d..72c240961433 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CrlCache.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/CrlCache.cs @@ -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 @@ public static void AddCrlForCertificate( 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 @@ private static bool AddCachedCrl(X509Certificate2 cert, SafeX509StoreHandle stor } private static void DownloadAndAddCrl( - X509Certificate2 cert, + SafeX509Handle cert, SafeX509StoreHandle store, ref TimeSpan remainingDownloadTime) { @@ -160,18 +171,17 @@ private static void DownloadAndAddCrl( } } } - - 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 @@ private static string GetCachedCrlPath(X509Certificate2 cert, bool mkDir=false) 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 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.Shared.Return(crlDistributionPoints.Array); + } } } } diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/DirectoryBasedStoreProvider.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/DirectoryBasedStoreProvider.cs index fbf228bf2077..341d985913f9 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/DirectoryBasedStoreProvider.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/DirectoryBasedStoreProvider.cs @@ -292,7 +292,7 @@ private string FindOpenSlot(string thumbprint) throw new CryptographicException(SR.Cryptography_X509_StoreNoFileAvailable); } - private static string GetStorePath(string storeName) + internal static string GetStorePath(string storeName) { string directoryName = GetDirectoryName(storeName); diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs index 5c7d944fcab5..9437304aa55f 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509CertificateReader.cs @@ -442,6 +442,21 @@ public IEnumerable Extensions } } + internal static ArraySegment 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; diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs index 03406d1d2f0e..ea7ca81012d8 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs @@ -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 @@ public SafeX509ChainHandle SafeHandle get { return null; } } - public static IChainPal BuildChain( - X509Certificate2 leaf, - HashSet candidates, - HashSet 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 overallStatus = new List(); - 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 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 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.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(); } + + 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 tempChain = stackalloc IntPtr[DefaultChainCapacity]; + IntPtr[] tempChainRent = null; + + if (chainSize <= tempChain.Length) { - throw Interop.Crypto.CreateOpenSslCryptographicException(); + tempChain = tempChain.Slice(0, chainSize); + } + else + { + tempChainRent = ArrayPool.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.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 status = new List(); + refErrors.ClearError(NoCrl); + refErrors.Add(ocspStatus); + } + } + } - List elementErrors = - i < workingChain.Errors.Count ? workingChain.Errors[i] : null; + X509ChainElement[] elements = BuildChainElements( + workingChain, + out List 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(); + 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 encoded = Interop.Crypto.OpenSslRentEncode( + handle => Interop.Crypto.GetOcspRequestDerSize(handle), + (handle, buf) => Interop.Crypto.EncodeOcspRequest(handle, buf), + req); + + ArraySegment urlEncoded = Base64UrlEncode(encoded); + string requestUrl = UrlPathAppend(baseUri, urlEncoded); + + // Nothing sensitive is in the encoded request (it was sent via HTTP-non-S) + ArrayPool.Shared.Return(encoded.Array); + ArrayPool.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 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 Base64UrlEncode(ReadOnlySpan input) + { + // Every 3 bytes turns into 4 chars for the Base64 operation + int base64Len = ((input.Length + 2) / 3) * 4; + char[] base64 = ArrayPool.Shared.Rent(base64Len); - if ((certificatePolicy != null && certificatePolicy.Count > 0) || - (applicationPolicy != null && applicationPolicy.Count > 0)) + if (!Convert.TryToBase64Chars(input, base64, out int charsWritten)) { - List certsToRead = new List(); + 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.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.Shared.Return(base64); + return new ArraySegment(urlEncoded, 0, writeIdx); + } - CertificatePolicyChain policyChain = new CertificatePolicyChain(certsToRead); + private X509ChainElement[] BuildChainElements( + WorkingChain workingChain, + out List 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(); + ErrorCollection? elementErrors = + workingChain?.LastError > i ? (ErrorCollection?)workingChain[i] : null; + + if (elementErrors.HasValue && elementErrors.Value.HasErrors) { - failsPolicyChecks = true; + List statusBuilder = new List(); + overallStatus = new List(); + + 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 overallStatus, + OidCollection applicationPolicy, + OidCollection certificatePolicy) + { + List certsToRead = new List(); + + foreach (X509ChainElement element in elements) + { + certsToRead.Add(element.Certificate); + } - var elementStatus = new List(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(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 errorCodes, + ErrorCollection errorCodes, List elementStatus, List overallStatus) { @@ -367,250 +785,44 @@ private static X509ChainStatusFlags MapVerifyErrorToChainStatus(Interop.Crypto.X } } - internal static HashSet FindCandidates( - X509Certificate2 leaf, - X509Certificate2Collection extraStore, - HashSet downloaded, - HashSet systemTrusted, - ref TimeSpan remainingDownloadTime) - { - var candidates = new HashSet(); - var toProcess = new Queue(); - 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 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( - candidates, - ReferenceEqualityComparer.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 referencedSet, - X509Certificate2Collection storeCerts) - { - foreach (X509Certificate2 cert in storeCerts) - { - if (!referencedSet.Contains(cert)) - { - cert.Dispose(); - } - } - } - - private static HashSet FindIssuer( - X509Certificate2 cert, - X509Certificate2Collection[] stores, - HashSet downloadedCerts, + private static X509Certificate2 DownloadCertificate( + ReadOnlyMemory 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 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(); - } - - 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() { 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 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.Shared.Return(authorityInformationAccess.Array); + return baseUrl; } - internal static string FindHttpAiaRecord(byte[] authorityInformationAccess, string recordTypeOid) + private static string FindHttpAiaRecord(ReadOnlyMemory authorityInformationAccess, string recordTypeOid) { AsnReader reader = new AsnReader(authorityInformationAccess, AsnEncodingRules.DER); AsnReader sequenceReader = reader.ReadSequence(); @@ -634,10 +846,52 @@ internal static string FindHttpAiaRecord(byte[] authorityInformationAccess, stri return null; } - private class WorkingChain + private static void AddToStackAndUpRef(SafeX509Handle cert, SafeX509StackHandle stack) { - internal readonly List> Errors = - new List>(); + 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.Shared.Return(toReturn); + } + } internal int VerifyCallback(int ok, IntPtr ctx) { @@ -658,17 +912,26 @@ internal int VerifyCallback(int ok, IntPtr ctx) 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.Shared.Rent(size); - if (Errors[errorDepth] == null) + // We only do spares writes. + _errors.AsSpan().Clear(); + } + else if (errorDepth >= _errors.Length) { - Errors[errorDepth] = new List(); + ErrorCollection[] toReturn = _errors; + _errors = ArrayPool.Shared.Rent(errorDepth + 1); + toReturn.AsSpan().CopyTo(_errors); + + // We only do spares writes, clear the remainder. + _errors.AsSpan(toReturn.Length).Clear(); + ArrayPool.Shared.Return(toReturn); } - Errors[errorDepth].Add(errorCode); + _errors[errorDepth].Add(errorCode); } } @@ -680,5 +943,127 @@ internal int VerifyCallback(int ok, IntPtr ctx) } } } + + 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); + } + } } } diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs index 40bb27b7e3d0..509c31978c33 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/StorePal.cs @@ -140,6 +140,22 @@ public static IExportPal LinkFromCertificateCollection(X509Certificate2Collectio 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) diff --git a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/X509Persistence.cs b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/X509Persistence.cs index fa29b20f26bd..250c09687776 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/X509Persistence.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/X509Persistence.cs @@ -9,5 +9,6 @@ internal static class X509Persistence internal const string CryptographyFeatureName = "cryptography"; internal const string X509StoresSubFeatureName = "x509stores"; internal const string CrlsSubFeatureName = "crls"; + internal const string OcspSubFeatureName = "ocsp"; } } diff --git a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index a5af472a41a6..e8c2018a36f7 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -348,6 +348,9 @@ Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.GetIntegerBytes.cs + + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.Nid.cs + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Bignum.cs @@ -363,6 +366,9 @@ Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs + + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.OCSP.cs + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Pkcs12.cs diff --git a/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Store.cs b/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Store.cs index f8168656d0e8..f432a2e4293a 100644 --- a/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Store.cs +++ b/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Store.cs @@ -13,6 +13,7 @@ public sealed class X509Store : IDisposable 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 @@ public X509Store(StoreName storeName, StoreLocation storeLocation) Name = DisallowedStoreName; break; case StoreName.My: - Name = "My"; + Name = MyStoreName; break; case StoreName.Root: Name = RootStoreName; diff --git a/src/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs b/src/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs index dc254acf045c..b480b509683a 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs +++ b/src/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs @@ -559,6 +559,19 @@ public static void VerifyWithRevocation() 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