// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Collections.Concurrent;
+using System.Diagnostics;
using System.Runtime.InteropServices;
+using System.Security.Cryptography;
internal static partial class Interop
{
internal static partial class Crypto
{
+ private static readonly ConcurrentDictionary<string, int> s_nidLookup =
+ new ConcurrentDictionary<string, int>();
+
internal const int NID_undef = 0;
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ObjSn2Nid", CharSet = CharSet.Ansi)]
internal static extern int ObjSn2Nid(string sn);
+
+ [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ObjTxt2Nid", CharSet = CharSet.Ansi)]
+ private static extern int ObjTxt2Nid(string oid);
+
+ internal static int ResolveRequiredNid(string oid)
+ {
+ return s_nidLookup.GetOrAdd(oid, s => LookupNid(s));
+ }
+
+ private static int LookupNid(string oid)
+ {
+ int nid = ObjTxt2Nid(oid);
+
+ if (nid == NID_undef)
+ {
+ Debug.Fail($"NID Lookup for {oid} failed, only well-known types should be queried.");
+ throw new CryptographicException();
+ }
+
+ return nid;
+ }
}
}
// 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;
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);
return GetDynamicBuffer((ptr, buf, i) => GetAsn1StringBytes(ptr, buf, i), asn1);
}
+ internal static ArraySegment<byte> RentAsn1StringBytes(IntPtr asn1)
+ {
+ return RentDynamicBuffer((ptr, buf, i) => GetAsn1StringBytes(ptr, buf, i), asn1);
+ }
+
internal static byte[] GetX509Thumbprint(SafeX509Handle x509)
{
return GetDynamicBuffer((handle, buf, i) => GetX509Thumbprint(handle, buf, i), x509);
}
}
+ internal static void X509StoreSetVerifyTime(SafeX509StoreHandle ctx, DateTime verifyTime)
+ {
+ // OpenSSL is going to convert our input time to universal, so we should be in Local or
+ // Unspecified (local-assumed).
+ Debug.Assert(verifyTime.Kind != DateTimeKind.Utc, "UTC verifyTime should have been normalized to Local");
+
+ int succeeded = CryptoNative_X509StoreSetVerifyTime(
+ ctx,
+ verifyTime.Year,
+ verifyTime.Month,
+ verifyTime.Day,
+ verifyTime.Hour,
+ verifyTime.Minute,
+ verifyTime.Second,
+ verifyTime.IsDaylightSavingTime());
+
+ if (succeeded != 1)
+ {
+ throw Interop.Crypto.CreateOpenSslCryptographicException();
+ }
+ }
+
private static byte[] GetDynamicBuffer<THandle>(NegativeSizeReadMethod<THandle> method, THandle handle)
{
int negativeSize = method(handle, null, 0);
return bytes;
}
+
+ private static ArraySegment<byte> RentDynamicBuffer<THandle>(NegativeSizeReadMethod<THandle> method, THandle handle)
+ {
+ int negativeSize = method(handle, null, 0);
+
+ if (negativeSize > 0)
+ {
+ throw Interop.Crypto.CreateOpenSslCryptographicException();
+ }
+
+ int targetSize = -negativeSize;
+ byte[] bytes = ArrayPool<byte>.Shared.Rent(targetSize);
+
+ int ret = method(handle, bytes, targetSize);
+
+ if (ret != 1)
+ {
+ ArrayPool<byte>.Shared.Return(bytes);
+ throw Interop.Crypto.CreateOpenSslCryptographicException();
+ }
+
+ return new ArraySegment<byte>(bytes, 0, targetSize);
+ }
}
}
// 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
{
internal delegate int EncodeFunc<in THandle>(THandle handle, byte[] buf);
- internal static byte[] OpenSslEncode<THandle>(GetEncodedSizeFunc<THandle> getSize, EncodeFunc<THandle> encode, THandle handle)
+ internal static byte[] OpenSslEncode<THandle>(
+ GetEncodedSizeFunc<THandle> getSize,
+ EncodeFunc<THandle> encode,
+ THandle handle)
where THandle : SafeHandle
{
int size = getSize(handle);
if (size < 1)
{
- throw Crypto.CreateOpenSslCryptographicException();
+ throw CreateOpenSslCryptographicException();
}
byte[] data = new byte[size];
return data;
}
+
+ internal static ArraySegment<byte> OpenSslRentEncode<THandle>(
+ GetEncodedSizeFunc<THandle> getSize,
+ EncodeFunc<THandle> encode,
+ THandle handle)
+ where THandle : SafeHandle
+ {
+ int size = getSize(handle);
+
+ if (size < 1)
+ {
+ throw CreateOpenSslCryptographicException();
+ }
+
+ byte[] data = ArrayPool<byte>.Shared.Rent(size);
+
+ int size2 = encode(handle, data);
+ if (size2 < 1)
+ {
+ Debug.Fail(
+ $"{nameof(OpenSslEncode)}: {nameof(getSize)} succeeded ({size}) and {nameof(encode)} failed ({size2})");
+
+ // Since we don't know what was written, assume it was secret and clear the value.
+ // (It doesn't matter much, since we're behind Debug.Fail)
+ ArrayPool<byte>.Shared.Return(data, clearArray: true);
+
+ // If it ever happens, ensure the error queue gets cleared.
+ // And since it didn't write the data, reporting an exception is good too.
+ throw CreateOpenSslCryptographicException();
+ }
+
+ Debug.Assert(size == size2);
+
+ return new ArraySegment<byte>(data, 0, size2);
+ }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Crypto
+ {
+ [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_OcspRequestDestroy")]
+ internal static extern void OcspRequestDestroy(IntPtr ocspReq);
+
+ [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetOcspRequestDerSize")]
+ internal static extern int GetOcspRequestDerSize(SafeOcspRequestHandle req);
+
+ [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EncodeOcspRequest")]
+ internal static extern int EncodeOcspRequest(SafeOcspRequestHandle req, byte[] buf);
+
+ [DllImport(Libraries.CryptoNative)]
+ private static extern SafeOcspResponseHandle CryptoNative_DecodeOcspResponse(ref byte buf, int len);
+
+ internal static SafeOcspResponseHandle DecodeOcspResponse(ReadOnlySpan<byte> buf)
+ {
+ return CryptoNative_DecodeOcspResponse(
+ ref MemoryMarshal.GetReference(buf),
+ buf.Length);
+ }
+
+ [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_OcspResponseDestroy")]
+ internal static extern void OcspResponseDestroy(IntPtr ocspReq);
+
+
+ [DllImport(Libraries.CryptoNative)]
+ private static extern X509VerifyStatusCode CryptoNative_X509ChainGetCachedOcspStatus(SafeX509StoreCtxHandle ctx, string cachePath);
+
+ internal static X509VerifyStatusCode X509ChainGetCachedOcspStatus(SafeX509StoreCtxHandle ctx, string cachePath)
+ {
+ X509VerifyStatusCode response = CryptoNative_X509ChainGetCachedOcspStatus(ctx, cachePath);
+
+ if (response < 0)
+ {
+ Debug.Fail($"Unexpected response from X509ChainGetCachedOcspSuccess: {response}");
+ throw new CryptographicException();
+ }
+
+ return response;
+ }
+
+ [DllImport(Libraries.CryptoNative)]
+ private static extern X509VerifyStatusCode CryptoNative_X509ChainVerifyOcsp(
+ SafeX509StoreCtxHandle ctx,
+ SafeOcspRequestHandle req,
+ SafeOcspResponseHandle resp,
+ string cachePath);
+
+ internal static X509VerifyStatusCode X509ChainVerifyOcsp(
+ SafeX509StoreCtxHandle ctx,
+ SafeOcspRequestHandle req,
+ SafeOcspResponseHandle resp,
+ string cachePath)
+ {
+ X509VerifyStatusCode response = CryptoNative_X509ChainVerifyOcsp(ctx, req, resp, cachePath);
+
+ if (response < 0)
+ {
+ Debug.Fail($"Unexpected response from X509ChainGetCachedOcspSuccess: {response}");
+ throw new CryptographicException();
+ }
+
+ return response;
+ }
+
+ [DllImport(Libraries.CryptoNative)]
+ private static extern SafeOcspRequestHandle CryptoNative_X509ChainBuildOcspRequest(SafeX509StoreCtxHandle storeCtx);
+
+ internal static SafeOcspRequestHandle X509ChainBuildOcspRequest(SafeX509StoreCtxHandle storeCtx)
+ {
+ SafeOcspRequestHandle req = CryptoNative_X509ChainBuildOcspRequest(storeCtx);
+
+ if (req.IsInvalid)
+ {
+ req.Dispose();
+ throw CreateOpenSslCryptographicException();
+ }
+
+ return req;
+ }
+ }
+}
+
+namespace System.Security.Cryptography.X509Certificates
+{
+ internal class SafeOcspRequestHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ public SafeOcspRequestHandle()
+ : base(true)
+ {
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ Interop.Crypto.OcspRequestDestroy(handle);
+ handle = IntPtr.Zero;
+ return true;
+ }
+ }
+
+ internal class SafeOcspResponseHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ public SafeOcspResponseHandle()
+ : base(true)
+ {
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ Interop.Crypto.OcspResponseDestroy(handle);
+ handle = IntPtr.Zero;
+ return true;
+ }
+ }
+}
[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);
[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);
[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)]
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);
/// </summary>
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetX509StackField")]
internal static extern IntPtr GetX509StackField(SafeSharedX509StackHandle stack, int loc);
+
+ [DllImport(Libraries.CryptoNative)]
+ private static extern int CryptoNative_X509StackAddDirectoryStore(SafeX509StackHandle stack, string storePath);
+
+ internal static void X509StackAddDirectoryStore(SafeX509StackHandle stack, string storePath)
+ {
+ if (CryptoNative_X509StackAddDirectoryStore(stack, storePath) != 1)
+ {
+ throw CreateOpenSslCryptographicException();
+ }
+ }
+
+ [DllImport(Libraries.CryptoNative)]
+ private static extern int CryptoNative_X509StackAddMultiple(SafeX509StackHandle dest, SafeX509StackHandle src);
+
+ internal static void X509StackAddMultiple(SafeX509StackHandle dest, SafeX509StackHandle src)
+ {
+ if (CryptoNative_X509StackAddMultiple(dest, src) != 1)
+ {
+ throw CreateOpenSslCryptographicException();
+ }
+ }
}
}
[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);
using System;
using System.Runtime.InteropServices;
-using System.Security;
namespace Microsoft.Win32.SafeHandles
{
{
}
}
+
+ internal sealed class SafeSharedAsn1OctetStringHandle : SafeInteriorHandle
+ {
+ private SafeSharedAsn1OctetStringHandle() :
+ base(IntPtr.Zero, ownsHandle: true)
+ {
+ }
+ }
}
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";
pal_evp_pkey_rsa.c
pal_evp_cipher.c
pal_hmac.c
+ pal_ocsp.c
pal_pkcs12.c
pal_pkcs7.c
pal_rsa.c
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;
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)
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);
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
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);
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;
};
#include <openssl/hmac.h>
#include <openssl/md5.h>
#include <openssl/objects.h>
+#include <openssl/ocsp.h>
#include <openssl/pem.h>
#include <openssl/pkcs12.h>
#include <openssl/pkcs7.h>
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);
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);
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
#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) \
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) \
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) \
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) \
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) \
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) \
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) \
// 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
#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
#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
#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
#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
#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
#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
#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
#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
#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))
// 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))
#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
#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
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);
DLLEXPORT int32_t CryptoNative_ObjSn2Nid(const char* sn);
/*
+Direct shim to OBJ_txt2nid.
+*/
+DLLEXPORT int32_t CryptoNative_ObjTxt2Nid(const char* sn);
+
+/*
Direct shim to OBJ_nid2obj.
*/
DLLEXPORT const ASN1_OBJECT* CryptoNative_ObjNid2Obj(int32_t nid);
--- /dev/null
+// 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);
+ }
+}
--- /dev/null
+// 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);
#include <stdbool.h>
#include <assert.h>
+#include <dirent.h>
+#include <string.h>
+#include <unistd.h>
+#include "../Common/pal_safecrt.h"
c_static_assert(PAL_X509_V_OK == X509_V_OK);
c_static_assert(PAL_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT);
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();
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)
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);
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;
+}
DLLEXPORT int32_t CryptoNative_X509ExtensionGetCritical(X509_EXTENSION* x);
/*
+Returns the data portion of the first matched extension.
+*/
+DLLEXPORT ASN1_OCTET_STRING* CryptoNative_X509FindExtensionData(X509* x, int32_t nid);
+
+/*
Shims the X509_STORE_new method.
*/
DLLEXPORT X509_STORE* CryptoNative_X509StoreCreate(void);
DLLEXPORT X509Stack* CryptoNative_X509StoreCtxGetChain(X509_STORE_CTX* ctx);
/*
+Shims the X509_STORE_CTX_get_current_cert function.
+*/
+DLLEXPORT X509* CryptoNative_X509StoreCtxGetCurrentCert(X509_STORE_CTX* ctx);
+
+/*
Returns the interior pointer to the "untrusted" certificates collection for this X509_STORE_CTX
*/
DLLEXPORT X509Stack* CryptoNative_X509StoreCtxGetSharedUntrusted(X509_STORE_CTX* ctx);
DLLEXPORT X509VerifyStatusCode CryptoNative_X509StoreCtxGetError(X509_STORE_CTX* ctx);
/*
+Resets ctx to before the chain was built, preserving the target cert, trust store, extra cert context,
+and verify parameters.
+*/
+DLLEXPORT int32_t CryptoNative_X509StoreCtxReset(X509_STORE_CTX* ctx);
+
+/*
+Reset ctx and rebuild the chain.
+Returns -1 if CryptoNative_X509StoreCtxReset failed, otherwise returns the result of
+X509_verify_cert.
+*/
+DLLEXPORT int32_t CryptoNative_X509StoreCtxRebuildChain(X509_STORE_CTX* ctx);
+
+/*
Shims the X509_STORE_CTX_get_error_depth method.
*/
DLLEXPORT int32_t CryptoNative_X509StoreCtxGetErrorDepth(X509_STORE_CTX* ctx);
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);
// 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
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)
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
TimeSpan remainingDownloadTime = timeout;
- using (var leaf = new X509Certificate2(cert.Handle))
- {
- GC.KeepAlive(cert); // ensure cert's safe handle isn't finalized while raw handle is in use
-
- var downloaded = new HashSet<X509Certificate2>();
- var systemTrusted = new HashSet<X509Certificate2>();
-
- HashSet<X509Certificate2> candidates = OpenSslX509ChainProcessor.FindCandidates(
- leaf,
- extraStore,
- downloaded,
- systemTrusted,
- ref remainingDownloadTime);
-
- IChainPal chain = OpenSslX509ChainProcessor.BuildChain(
- leaf,
- candidates,
- systemTrusted,
- applicationPolicy,
- certificatePolicy,
- revocationMode,
- revocationFlag,
- verificationTime,
- ref remainingDownloadTime);
+ OpenSslX509ChainProcessor chainPal = OpenSslX509ChainProcessor.InitiateChain(
+ ((OpenSslX509CertificateReader)cert).SafeHandle,
+ verificationTime,
+ remainingDownloadTime);
-#if DEBUG
- if (chain.ChainElements.Length > 0)
- {
- X509Certificate2 reportedLeaf = chain.ChainElements[0].Certificate;
- Debug.Assert(reportedLeaf != null, "reportedLeaf != null");
- Debug.Assert(reportedLeaf.Equals(leaf), "reportedLeaf.Equals(leaf)");
- Debug.Assert(!ReferenceEquals(reportedLeaf, leaf), "!ReferenceEquals(reportedLeaf, leaf)");
- }
-#endif
-
- if (chain.ChainStatus.Length == 0 && downloaded.Count > 0)
- {
- SaveIntermediateCertificates(chain.ChainElements, downloaded);
- }
+ Interop.Crypto.X509VerifyStatusCode status = chainPal.FindFirstChain(extraStore);
- // Everything we put into the chain has been cloned, dispose all the originals.
- systemTrusted.DisposeAll();
- downloaded.DisposeAll();
+ if (!OpenSslX509ChainProcessor.IsCompleteChain(status))
+ {
+ List<X509Certificate2> tmp = null;
+ status = chainPal.FindChainViaAia(ref tmp);
- if (extraStore == null || extraStore.Count == 0)
+ if (tmp != null)
{
- // There were no extraStore certs, so everything can be disposed.
- foreach (X509Certificate2 candidate in candidates)
- {
- candidate.Dispose();
- }
- }
- else
- {
- // Candidate certs which came from extraStore should NOT be disposed, since they came
- // from outside.
- var extraStoreByReference = new HashSet<X509Certificate2>(
- ReferenceEqualityComparer<X509Certificate2>.Instance);
-
- foreach (X509Certificate2 extraCert in extraStore)
+ if (status == Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
{
- extraStoreByReference.Add(extraCert);
+ SaveIntermediateCertificates(tmp);
}
- foreach (X509Certificate2 candidate in candidates)
+ foreach (X509Certificate2 downloaded in tmp)
{
- if (!extraStoreByReference.Contains(candidate))
- {
- candidate.Dispose();
- }
+ downloaded.Dispose();
}
}
-
- return chain;
}
- }
-
- private static void SaveIntermediateCertificates(
- X509ChainElement[] chainElements,
- HashSet<X509Certificate2> downloaded)
- {
- List<X509Certificate2> chainDownloaded = new List<X509Certificate2>(chainElements.Length);
- // It should be very unlikely that we would have downloaded something, the chain succeed,
- // and the thing we downloaded not being a part of the chain, but safer is better.
- for (int i = 0; i < chainElements.Length; i++)
+ if (revocationMode == X509RevocationMode.Online &&
+ status != Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
{
- X509Certificate2 elementCert = chainElements[i].Certificate;
+ revocationMode = X509RevocationMode.Offline;
+ }
- if (downloaded.Contains(elementCert))
- {
- chainDownloaded.Add(elementCert);
- }
+ // In NoCheck+OK then we don't need to build the chain any more, we already
+ // know it's error-free. So skip straight to finish.
+ if (status != Interop.Crypto.X509VerifyStatusCode.X509_V_OK ||
+ revocationMode != X509RevocationMode.NoCheck)
+ {
+ chainPal.CommitToChain();
+ chainPal.ProcessRevocation(revocationMode, revocationFlag);
}
- if (chainDownloaded.Count == 0)
+ chainPal.Finish(applicationPolicy, certificatePolicy);
+
+#if DEBUG
+ if (chainPal.ChainElements.Length > 0)
{
- return;
+ X509Certificate2 reportedLeaf = chainPal.ChainElements[0].Certificate;
+ Debug.Assert(reportedLeaf != null, "reportedLeaf != null");
+ Debug.Assert(!ReferenceEquals(cert, reportedLeaf.Pal), "!ReferenceEquals(cert, reportedLeaf.Pal)");
}
+#endif
+ return chainPal;
+ }
+ private static void SaveIntermediateCertificates(List<X509Certificate2> downloadedCerts)
+ {
using (var userIntermediate = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
{
try
return;
}
- foreach (X509Certificate2 cert in chainDownloaded)
+ foreach (X509Certificate2 cert in downloadedCerts)
{
try
{
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
+using Microsoft.Win32.SafeHandles;
namespace Internal.Cryptography.Pal
{
internal sealed class CollectionBackedStoreProvider : IStorePal
{
private readonly List<X509Certificate2> _certs;
+ private SafeX509StackHandle _nativeCollection;
internal CollectionBackedStoreProvider(List<X509Certificate2> certs)
{
{
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;
+ }
}
}
// 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;
{
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,
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);
}
private static void DownloadAndAddCrl(
- X509Certificate2 cert,
+ SafeX509Handle cert,
SafeX509StoreHandle store,
ref TimeSpan remainingDownloadTime)
{
}
}
}
-
- 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();
if (mkDir)
{
- Directory.CreateDirectory(crlDir);
+ Directory.CreateDirectory(s_crlDir);
}
- return Path.Combine(crlDir, localFileName);
+ return Path.Combine(s_crlDir, localFileName);
}
- private static string GetCdpUrl(X509Certificate2 cert)
+ private static string GetCdpUrl(SafeX509Handle cert)
{
- byte[] crlDistributionPoints = null;
+ ArraySegment<byte> crlDistributionPoints =
+ OpenSslX509CertificateReader.FindFirstExtension(cert, Oids.CrlDistributionPoints);
- foreach (X509Extension extension in cert.Extensions)
- {
- if (StringComparer.Ordinal.Equals(extension.Oid.Value, Oids.CrlDistributionPoints))
- {
- // If there's an Authority Information Access extension, it might be used for
- // looking up additional certificates for the chain.
- crlDistributionPoints = extension.RawData;
- break;
- }
- }
-
- if (crlDistributionPoints == null)
+ if (crlDistributionPoints.Array == null)
{
return null;
}
- AsnReader reader = new AsnReader(crlDistributionPoints, AsnEncodingRules.DER);
- AsnReader sequenceReader = reader.ReadSequence();
- reader.ThrowIfNotEmpty();
-
- while (sequenceReader.HasData)
+ try
{
- DistributionPointAsn.Decode(sequenceReader, out DistributionPointAsn distributionPoint);
+ AsnReader reader = new AsnReader(crlDistributionPoints, AsnEncodingRules.DER);
+ AsnReader sequenceReader = reader.ReadSequence();
+ reader.ThrowIfNotEmpty();
- // Only distributionPoint is supported
- // Only fullName is supported, nameRelativeToCRLIssuer is for LDAP-based lookup.
- if (distributionPoint.DistributionPoint.HasValue &&
- distributionPoint.DistributionPoint.Value.FullName != null)
+ while (sequenceReader.HasData)
{
- foreach (GeneralNameAsn name in distributionPoint.DistributionPoint.Value.FullName)
+ DistributionPointAsn.Decode(sequenceReader, out DistributionPointAsn distributionPoint);
+
+ // Only distributionPoint is supported
+ // Only fullName is supported, nameRelativeToCRLIssuer is for LDAP-based lookup.
+ if (distributionPoint.DistributionPoint.HasValue &&
+ distributionPoint.DistributionPoint.Value.FullName != null)
{
- if (name.Uri != null &&
- Uri.TryCreate(name.Uri, UriKind.Absolute, out Uri uri) &&
- uri.Scheme == "http")
+ foreach (GeneralNameAsn name in distributionPoint.DistributionPoint.Value.FullName)
{
- return name.Uri;
+ if (name.Uri != null &&
+ Uri.TryCreate(name.Uri, UriKind.Absolute, out Uri uri) &&
+ uri.Scheme == "http")
+ {
+ return name.Uri;
+ }
}
}
}
- }
- return null;
+ return null;
+ }
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(crlDistributionPoints.Array);
+ }
}
}
}
throw new CryptographicException(SR.Cryptography_X509_StoreNoFileAvailable);
}
- private static string GetStorePath(string storeName)
+ internal static string GetStorePath(string storeName)
{
string directoryName = GetDirectoryName(storeName);
}
}
+ internal static ArraySegment<byte> FindFirstExtension(SafeX509Handle cert, string oidValue)
+ {
+ int nid = Interop.Crypto.ResolveRequiredNid(oidValue);
+
+ using (SafeSharedAsn1OctetStringHandle data = Interop.Crypto.X509FindExtensionData(cert, nid))
+ {
+ if (data.IsInvalid)
+ {
+ return default;
+ }
+
+ return Interop.Crypto.RentAsn1StringBytes(data.DangerousGetHandle());
+ }
+ }
+
internal void SetPrivateKey(SafeEvpPKeyHandle privateKey)
{
_privateKey = privateKey;
// 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;
{
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)
get { return null; }
}
- public static IChainPal BuildChain(
- X509Certificate2 leaf,
- HashSet<X509Certificate2> candidates,
- HashSet<X509Certificate2> systemTrusted,
- OidCollection applicationPolicy,
- OidCollection certificatePolicy,
- X509RevocationMode revocationMode,
- X509RevocationFlag revocationFlag,
+ internal static OpenSslX509ChainProcessor InitiateChain(
+ SafeX509Handle leafHandle,
DateTime verificationTime,
- ref TimeSpan remainingDownloadTime)
+ TimeSpan remainingDownloadTime)
{
- X509ChainElement[] elements;
- List<X509ChainStatus> overallStatus = new List<X509ChainStatus>();
- WorkingChain workingChain = new WorkingChain();
- Interop.Crypto.X509StoreVerifyCallback workingCallback = workingChain.VerifyCallback;
+ SafeX509StackHandle systemTrust = StorePal.GetMachineRoot().GetNativeCollection();
+ SafeX509StackHandle systemIntermediate = StorePal.GetMachineIntermediate().GetNativeCollection();
- // An X509_STORE is more comparable to Cryptography.X509Certificate2Collection than to
- // Cryptography.X509Store. So read this with OpenSSL eyes, not CAPI/CNG eyes.
- //
- // (If you need to think of it as an X509Store, it's a volatile memory store)
- using (SafeX509StoreHandle store = Interop.Crypto.X509StoreCreate())
- using (SafeX509StoreCtxHandle storeCtx = Interop.Crypto.X509StoreCtxCreate())
- using (SafeX509StackHandle extraCerts = Interop.Crypto.NewX509Stack())
+ SafeX509StoreHandle store = null;
+ SafeX509StackHandle untrusted = null;
+ SafeX509StoreCtxHandle storeCtx = null;
+
+ try
{
- Interop.Crypto.CheckValidOpenSslHandle(store);
- Interop.Crypto.CheckValidOpenSslHandle(storeCtx);
- Interop.Crypto.CheckValidOpenSslHandle(extraCerts);
+ store = Interop.Crypto.X509ChainNew(systemTrust, s_userRootPath);
+
+ untrusted = Interop.Crypto.NewX509Stack();
+ Interop.Crypto.X509StackAddDirectoryStore(untrusted, s_userIntermediatePath);
+ Interop.Crypto.X509StackAddDirectoryStore(untrusted, s_userPersonalPath);
+ Interop.Crypto.X509StackAddMultiple(untrusted, systemIntermediate);
+ Interop.Crypto.X509StoreSetVerifyTime(store, verificationTime);
- bool lookupCrl = revocationMode != X509RevocationMode.NoCheck;
+ storeCtx = Interop.Crypto.X509StoreCtxCreate();
- foreach (X509Certificate2 cert in candidates)
+ if (!Interop.Crypto.X509StoreCtxInit(storeCtx, store, leafHandle, untrusted))
{
- OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)cert.Pal;
+ throw Interop.Crypto.CreateOpenSslCryptographicException();
+ }
- using (SafeX509Handle handle = Interop.Crypto.X509UpRef(pal.SafeHandle))
+ return new OpenSslX509ChainProcessor(
+ leafHandle,
+ store,
+ untrusted,
+ storeCtx,
+ verificationTime,
+ remainingDownloadTime);
+ }
+ catch
+ {
+ store?.Dispose();
+ untrusted?.Dispose();
+ storeCtx?.Dispose();
+ throw;
+ }
+ }
+
+ internal Interop.Crypto.X509VerifyStatusCode FindFirstChain(X509Certificate2Collection extraCerts)
+ {
+ SafeX509StoreCtxHandle storeCtx = _storeCtx;
+
+ // While this returns true/false, at this stage we care more about the detailed error code.
+ Interop.Crypto.X509VerifyCert(storeCtx);
+ Interop.Crypto.X509VerifyStatusCode statusCode = Interop.Crypto.X509StoreCtxGetError(storeCtx);
+
+ if (IsCompleteChain(statusCode))
+ {
+ return statusCode;
+ }
+
+ SafeX509StackHandle untrusted = _untrustedLookup;
+
+ if (extraCerts?.Count > 0)
+ {
+ foreach(X509Certificate2 cert in extraCerts)
+ {
+ AddToStackAndUpRef(((OpenSslX509CertificateReader)cert.Pal).SafeHandle, untrusted);
+ }
+
+ Interop.Crypto.X509StoreCtxRebuildChain(storeCtx);
+ statusCode = Interop.Crypto.X509StoreCtxGetError(storeCtx);
+ }
+
+ return statusCode;
+ }
+
+ internal static bool IsCompleteChain(Interop.Crypto.X509VerifyStatusCode statusCode)
+ {
+ switch (statusCode)
+ {
+ case Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+ case Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ internal Interop.Crypto.X509VerifyStatusCode FindChainViaAia(
+ ref List<X509Certificate2> downloadedCerts)
+ {
+ IntPtr lastCert = IntPtr.Zero;
+ SafeX509StoreCtxHandle storeCtx = _storeCtx;
+
+ Interop.Crypto.X509VerifyStatusCode statusCode =
+ Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT;
+
+ while (!IsCompleteChain(statusCode))
+ {
+ using (SafeX509Handle currentCert = Interop.Crypto.X509StoreCtxGetCurrentCert(storeCtx))
+ {
+ IntPtr currentHandle = currentCert.DangerousGetHandle();
+
+ // No progress was made, give up.
+ if (currentHandle == lastCert)
{
- if (!Interop.Crypto.PushX509StackField(extraCerts, handle))
- {
- throw Interop.Crypto.CreateOpenSslCryptographicException();
- }
+ break;
+ }
+
+ lastCert = currentHandle;
- // Ownership was transferred to the cert stack.
- handle.SetHandleAsInvalid();
+ ArraySegment<byte> authorityInformationAccess =
+ OpenSslX509CertificateReader.FindFirstExtension(
+ currentCert,
+ Oids.AuthorityInformationAccess);
+
+ if (authorityInformationAccess.Count == 0)
+ {
+ break;
}
- if (lookupCrl)
+ X509Certificate2 downloaded = DownloadCertificate(
+ authorityInformationAccess,
+ ref _remainingDownloadTime);
+
+ // The AIA record is contained in a public structure, so no need to clear it.
+ ArrayPool<byte>.Shared.Return(authorityInformationAccess.Array);
+
+ if (downloaded == null)
{
- CrlCache.AddCrlForCertificate(
- cert,
- store,
- revocationMode,
- verificationTime,
- ref remainingDownloadTime);
+ break;
+ }
- // If we only wanted the end-entity certificate CRL then don't look up
- // any more of them.
- lookupCrl = revocationFlag != X509RevocationFlag.EndCertificateOnly;
+ if (downloadedCerts == null)
+ {
+ downloadedCerts = new List<X509Certificate2>();
}
+
+ AddToStackAndUpRef(downloaded.Handle, _untrustedLookup);
+ downloadedCerts.Add(downloaded);
+
+ Interop.Crypto.X509StoreCtxRebuildChain(storeCtx);
+ statusCode = Interop.Crypto.X509StoreCtxGetError(storeCtx);
}
+ }
- if (revocationMode != X509RevocationMode.NoCheck)
+ if (statusCode == Interop.Crypto.X509VerifyStatusCode.X509_V_OK && downloadedCerts != null)
+ {
+ using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(_storeCtx))
{
- if (!Interop.Crypto.X509StoreSetRevocationFlag(store, revocationFlag))
+ int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack);
+ Span<IntPtr> tempChain = stackalloc IntPtr[DefaultChainCapacity];
+ IntPtr[] tempChainRent = null;
+
+ if (chainSize <= tempChain.Length)
{
- throw Interop.Crypto.CreateOpenSslCryptographicException();
+ tempChain = tempChain.Slice(0, chainSize);
+ }
+ else
+ {
+ tempChainRent = ArrayPool<IntPtr>.Shared.Rent(chainSize);
+ tempChain = tempChainRent.AsSpan(0, chainSize);
}
- }
- foreach (X509Certificate2 trustedCert in systemTrusted)
- {
- OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)trustedCert.Pal;
+ for (int i = 0; i < chainSize; i++)
+ {
+ tempChain[i] = Interop.Crypto.GetX509StackField(chainStack, i);
+ }
- if (!Interop.Crypto.X509StoreAddCert(store, pal.SafeHandle))
+ // In the average case we never made it here.
+ //
+ // Given that we made it here, in the average remaining case
+ // we are doing a one item for which will match in the second position
+ // of an (on-average) 3 item collection.
+ //
+ // The only case where this loop really matters is if downloading the
+ // certificate made an alternate chain better, which may have resulted in
+ // an extra download and made the first one not be involved any longer. In
+ // that case, it's a 2 item for loop matching against a three item set.
+ //
+ // So N*M is well contained.
+ for (int i = downloadedCerts.Count - 1; i >= 0; i--)
{
- throw Interop.Crypto.CreateOpenSslCryptographicException();
+ X509Certificate2 downloadedCert = downloadedCerts[i];
+
+ if (!tempChain.Contains(downloadedCert.Handle))
+ {
+ downloadedCert.Dispose();
+ downloadedCerts.RemoveAt(i);
+ }
}
- }
- SafeX509Handle leafHandle = ((OpenSslX509CertificateReader)leaf.Pal).SafeHandle;
+ if (downloadedCerts.Count == 0)
+ {
+ downloadedCerts = null;
+ }
- if (!Interop.Crypto.X509StoreCtxInit(storeCtx, store, leafHandle, extraCerts))
- {
- throw Interop.Crypto.CreateOpenSslCryptographicException();
+ if (tempChainRent != null)
+ {
+ // While the IntPtrs aren't secret, clearing them helps prevent
+ // accidental use-after-free because of pooling.
+ tempChain.Clear();
+ ArrayPool<IntPtr>.Shared.Return(tempChainRent);
+ }
}
+ }
+
+ return statusCode;
+ }
- Interop.Crypto.X509StoreCtxSetVerifyCallback(storeCtx, workingCallback);
- Interop.Crypto.SetX509ChainVerifyTime(storeCtx, verificationTime);
+ internal void CommitToChain()
+ {
+ Interop.Crypto.X509StoreCtxCommitToChain(_storeCtx);
+ }
+
+ internal void ProcessRevocation(
+ X509RevocationMode revocationMode,
+ X509RevocationFlag revocationFlag)
+ {
+ _revocationMode = revocationMode;
+
+ if (revocationMode == X509RevocationMode.NoCheck)
+ {
+ return;
+ }
- int verify = Interop.Crypto.X509VerifyCert(storeCtx);
+ using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(_storeCtx))
+ {
+ int chainSize =
+ revocationFlag == X509RevocationFlag.EndCertificateOnly ?
+ 1 :
+ Interop.Crypto.GetX509StackFieldCount(chainStack);
- if (verify < 0)
+ for (int i = 0; i < chainSize; i++)
{
- throw Interop.Crypto.CreateOpenSslCryptographicException();
+ using (SafeX509Handle cert =
+ Interop.Crypto.X509UpRef(Interop.Crypto.GetX509StackField(chainStack, i)))
+ {
+ CrlCache.AddCrlForCertificate(
+ cert,
+ _store,
+ revocationMode,
+ _verificationTime,
+ ref _remainingDownloadTime);
+ }
}
+ }
+
+ Interop.Crypto.X509StoreSetRevocationFlag(_store, revocationFlag);
+ Interop.Crypto.X509StoreCtxRebuildChain(_storeCtx);
+ }
+
+ internal void Finish(OidCollection applicationPolicy, OidCollection certificatePolicy)
+ {
+ WorkingChain workingChain = null;
+
+ // If the chain had any errors during the previous build we need to walk it again with
+ // the error collector running.
+ if (Interop.Crypto.X509StoreCtxGetError(_storeCtx) !=
+ Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
+ {
+ Interop.Crypto.X509StoreCtxReset(_storeCtx);
+
+ workingChain = new WorkingChain();
+ Interop.Crypto.X509StoreVerifyCallback workingCallback = workingChain.VerifyCallback;
+ Interop.Crypto.X509StoreCtxSetVerifyCallback(_storeCtx, workingCallback);
+
+ bool verify = Interop.Crypto.X509VerifyCert(_storeCtx);
+ GC.KeepAlive(workingCallback);
// Because our callback tells OpenSSL that every problem is ignorable, it should tell us that the
// chain is just fine (unless it returned a negative code for an exception)
- Debug.Assert(verify == 1, "verify == 1");
+ Debug.Assert(verify, "verify should have returned true");
+
+ const Interop.Crypto.X509VerifyStatusCode NoCrl =
+ Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_UNABLE_TO_GET_CRL;
- using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(storeCtx))
+ ErrorCollection? errors =
+ workingChain.LastError > 0 ? (ErrorCollection?)workingChain[0] : null;
+
+ if (_revocationMode == X509RevocationMode.Online &&
+ _remainingDownloadTime > TimeSpan.Zero &&
+ errors?.HasError(NoCrl) == true)
{
- int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack);
- elements = new X509ChainElement[chainSize];
- int maybeRootDepth = chainSize - 1;
+ Interop.Crypto.X509VerifyStatusCode ocspStatus = CheckOcsp();
- // The leaf cert is 0, up to (maybe) the root at chainSize - 1
- for (int i = 0; i < chainSize; i++)
+ ref ErrorCollection refErrors = ref workingChain[0];
+
+ if (ocspStatus == Interop.Crypto.X509VerifyStatusCode.X509_V_OK)
+ {
+ refErrors.ClearError(NoCrl);
+ }
+ else if (ocspStatus != NoCrl)
{
- List<X509ChainStatus> status = new List<X509ChainStatus>();
+ refErrors.ClearError(NoCrl);
+ refErrors.Add(ocspStatus);
+ }
+ }
+ }
- List<Interop.Crypto.X509VerifyStatusCode> elementErrors =
- i < workingChain.Errors.Count ? workingChain.Errors[i] : null;
+ X509ChainElement[] elements = BuildChainElements(
+ workingChain,
+ out List<X509ChainStatus> overallStatus);
- if (elementErrors != null)
- {
- AddElementStatus(elementErrors, status, overallStatus);
- }
+ workingChain?.Dispose();
- IntPtr elementCertPtr = Interop.Crypto.GetX509StackField(chainStack, i);
+ if (applicationPolicy?.Count > 0 || certificatePolicy?.Count > 0)
+ {
+ ProcessPolicy(elements, overallStatus, applicationPolicy, certificatePolicy);
+ }
- if (elementCertPtr == IntPtr.Zero)
- {
- throw Interop.Crypto.CreateOpenSslCryptographicException();
- }
+ ChainStatus = overallStatus?.ToArray() ?? Array.Empty<X509ChainStatus>();
+ ChainElements = elements;
+
+ // The native resources are not needed any longer.
+ Dispose();
+ }
+
+ private Interop.Crypto.X509VerifyStatusCode CheckOcsp()
+ {
+ string ocspCache = CrlCache.GetCachedOcspResponseDirectory();
+ Interop.Crypto.X509VerifyStatusCode status =
+ Interop.Crypto.X509ChainGetCachedOcspStatus(_storeCtx, ocspCache);
+
+ if (status != Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_UNABLE_TO_GET_CRL)
+ {
+ return status;
+ }
+
+ string baseUri = GetOcspEndpoint(_leafHandle);
+
+ if (baseUri == null)
+ {
+ return status;
+ }
+
+ using (SafeOcspRequestHandle req = Interop.Crypto.X509ChainBuildOcspRequest(_storeCtx))
+ {
+ ArraySegment<byte> encoded = Interop.Crypto.OpenSslRentEncode(
+ handle => Interop.Crypto.GetOcspRequestDerSize(handle),
+ (handle, buf) => Interop.Crypto.EncodeOcspRequest(handle, buf),
+ req);
+
+ ArraySegment<char> urlEncoded = Base64UrlEncode(encoded);
+ string requestUrl = UrlPathAppend(baseUri, urlEncoded);
+
+ // Nothing sensitive is in the encoded request (it was sent via HTTP-non-S)
+ ArrayPool<byte>.Shared.Return(encoded.Array);
+ ArrayPool<char>.Shared.Return(urlEncoded.Array);
+
+ // https://tools.ietf.org/html/rfc6960#appendix-A describes both a GET and a POST
+ // version of an OCSP responder.
+ //
+ // Doing POST via the reflection indirection to HttpClient is difficult, and
+ // CA/Browser Forum Baseline Requirements (version 1.6.3) section 4.9.10
+ // (On-line REvocation Checking Requirements) says that the GET method must be supported.
+ //
+ // So, for now, only try GET.
+
+ SafeOcspResponseHandle resp =
+ CertificateAssetDownloader.DownloadOcspGet(requestUrl, ref _remainingDownloadTime);
+
+ using (resp)
+ {
+ if (resp == null || resp.IsInvalid)
+ {
+ return Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_UNABLE_TO_GET_CRL;
+ }
- // Duplicate the certificate handle
- X509Certificate2 elementCert = new X509Certificate2(elementCertPtr);
- elements[i] = new X509ChainElement(elementCert, status.ToArray(), "");
+ try
+ {
+ System.IO.Directory.CreateDirectory(ocspCache);
+ }
+ catch
+ {
+ // Opportunistic create, suppress all errors.
}
+
+ return Interop.Crypto.X509ChainVerifyOcsp(_storeCtx, req, resp, ocspCache);
}
}
+ }
+
+ private static string UrlPathAppend(string baseUri, ReadOnlyMemory<char> resource)
+ {
+ Debug.Assert(baseUri.Length > 0);
+ Debug.Assert(resource.Length > 0);
+
+ int count = baseUri.Length + resource.Length;
+
+ if (baseUri[baseUri.Length - 1] == '/')
+ {
+ return string.Create(
+ count,
+ (baseUri, resource),
+ (buf, st) =>
+ {
+ st.baseUri.AsSpan().CopyTo(buf);
+ st.resource.Span.CopyTo(buf.Slice(st.Item1.Length));
+ });
+ }
- GC.KeepAlive(workingCallback);
+ return string.Create(
+ count + 1,
+ (baseUri, resource),
+ (buf, st) =>
+ {
+ st.baseUri.AsSpan().CopyTo(buf);
+ buf[st.Item1.Length] = '/';
+ st.resource.Span.CopyTo(buf.Slice(st.Item1.Length + 1));
+ });
+ }
+
+ private static ArraySegment<char> Base64UrlEncode(ReadOnlySpan<byte> input)
+ {
+ // Every 3 bytes turns into 4 chars for the Base64 operation
+ int base64Len = ((input.Length + 2) / 3) * 4;
+ char[] base64 = ArrayPool<char>.Shared.Rent(base64Len);
- if ((certificatePolicy != null && certificatePolicy.Count > 0) ||
- (applicationPolicy != null && applicationPolicy.Count > 0))
+ if (!Convert.TryToBase64Chars(input, base64, out int charsWritten))
{
- List<X509Certificate2> certsToRead = new List<X509Certificate2>();
+ Debug.Fail($"Convert.TryToBase64 failed with {input.Length} bytes to a {base64.Length} buffer");
+ throw new CryptographicException();
+ }
+
+ Debug.Assert(charsWritten == base64Len);
+
+ // In the degenerate case every char will turn into 3 chars.
+ int urlEncodedLen = charsWritten * 3;
+ char[] urlEncoded = ArrayPool<char>.Shared.Rent(urlEncodedLen);
+ int writeIdx = 0;
- foreach (X509ChainElement element in elements)
+ for (int readIdx = 0; readIdx < charsWritten; readIdx++)
+ {
+ char cur = base64[readIdx];
+
+ if ((cur >= 'A' && cur <= 'Z') ||
+ (cur >= 'a' && cur <= 'z') ||
+ (cur >= '0' && cur <= '9'))
+ {
+ urlEncoded[writeIdx++] = cur;
+ }
+ else if (cur == '+')
+ {
+ urlEncoded[writeIdx++] = '%';
+ urlEncoded[writeIdx++] = '2';
+ urlEncoded[writeIdx++] = 'B';
+ }
+ else if (cur == '/')
{
- certsToRead.Add(element.Certificate);
+ urlEncoded[writeIdx++] = '%';
+ urlEncoded[writeIdx++] = '2';
+ urlEncoded[writeIdx++] = 'F';
+ }
+ else if (cur == '=')
+ {
+ urlEncoded[writeIdx++] = '%';
+ urlEncoded[writeIdx++] = '3';
+ urlEncoded[writeIdx++] = 'D';
+ }
+ else
+ {
+ Debug.Fail($"'{cur}' is not a valid Base64 character");
+ throw new CryptographicException();
}
+ }
+
+ ArrayPool<char>.Shared.Return(base64);
+ return new ArraySegment<char>(urlEncoded, 0, writeIdx);
+ }
- CertificatePolicyChain policyChain = new CertificatePolicyChain(certsToRead);
+ private X509ChainElement[] BuildChainElements(
+ WorkingChain workingChain,
+ out List<X509ChainStatus> overallStatus)
+ {
+ X509ChainElement[] elements;
+ overallStatus = null;
- bool failsPolicyChecks = false;
+ using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(_storeCtx))
+ {
+ int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack);
+ elements = new X509ChainElement[chainSize];
- if (certificatePolicy != null)
+ for (int i = 0; i < chainSize; i++)
{
- if (!policyChain.MatchesCertificatePolicies(certificatePolicy))
+ X509ChainStatus[] status = Array.Empty<X509ChainStatus>();
+ ErrorCollection? elementErrors =
+ workingChain?.LastError > i ? (ErrorCollection?)workingChain[i] : null;
+
+ if (elementErrors.HasValue && elementErrors.Value.HasErrors)
{
- failsPolicyChecks = true;
+ List<X509ChainStatus> statusBuilder = new List<X509ChainStatus>();
+ overallStatus = new List<X509ChainStatus>();
+
+ AddElementStatus(elementErrors.Value, statusBuilder, overallStatus);
+ status = statusBuilder.ToArray();
}
- }
- if (applicationPolicy != null)
- {
- if (!policyChain.MatchesApplicationPolicies(applicationPolicy))
+ IntPtr elementCertPtr = Interop.Crypto.GetX509StackField(chainStack, i);
+
+ if (elementCertPtr == IntPtr.Zero)
{
- failsPolicyChecks = true;
+ throw Interop.Crypto.CreateOpenSslCryptographicException();
}
+
+ // Duplicate the certificate handle
+ X509Certificate2 elementCert = new X509Certificate2(elementCertPtr);
+ elements[i] = new X509ChainElement(elementCert, status, "");
}
+ }
- if (failsPolicyChecks)
- {
- X509ChainElement leafElement = elements[0];
+ return elements;
+ }
- X509ChainStatus chainStatus = new X509ChainStatus
- {
- Status = X509ChainStatusFlags.NotValidForUsage,
- StatusInformation = SR.Chain_NoPolicyMatch,
- };
+ private static void ProcessPolicy(
+ X509ChainElement[] elements,
+ List<X509ChainStatus> overallStatus,
+ OidCollection applicationPolicy,
+ OidCollection certificatePolicy)
+ {
+ List<X509Certificate2> certsToRead = new List<X509Certificate2>();
+
+ foreach (X509ChainElement element in elements)
+ {
+ certsToRead.Add(element.Certificate);
+ }
- var elementStatus = new List<X509ChainStatus>(leafElement.ChainElementStatus.Length + 1);
- elementStatus.AddRange(leafElement.ChainElementStatus);
+ CertificatePolicyChain policyChain = new CertificatePolicyChain(certsToRead);
- AddUniqueStatus(elementStatus, ref chainStatus);
- AddUniqueStatus(overallStatus, ref chainStatus);
+ bool failsPolicyChecks = false;
- elements[0] = new X509ChainElement(
- leafElement.Certificate,
- elementStatus.ToArray(),
- leafElement.Information);
+ if (certificatePolicy != null)
+ {
+ if (!policyChain.MatchesCertificatePolicies(certificatePolicy))
+ {
+ failsPolicyChecks = true;
}
}
- return new OpenSslX509ChainProcessor
+ if (applicationPolicy != null)
{
- ChainStatus = overallStatus.ToArray(),
- ChainElements = elements,
- };
+ if (!policyChain.MatchesApplicationPolicies(applicationPolicy))
+ {
+ failsPolicyChecks = true;
+ }
+ }
+
+ if (failsPolicyChecks)
+ {
+ X509ChainElement leafElement = elements[0];
+
+ X509ChainStatus chainStatus = new X509ChainStatus
+ {
+ Status = X509ChainStatusFlags.NotValidForUsage,
+ StatusInformation = SR.Chain_NoPolicyMatch,
+ };
+
+ var elementStatus = new List<X509ChainStatus>(leafElement.ChainElementStatus.Length + 1);
+ elementStatus.AddRange(leafElement.ChainElementStatus);
+
+ AddUniqueStatus(elementStatus, ref chainStatus);
+ AddUniqueStatus(overallStatus, ref chainStatus);
+
+ elements[0] = new X509ChainElement(
+ leafElement.Certificate,
+ elementStatus.ToArray(),
+ leafElement.Information);
+ }
}
private static void AddElementStatus(
- List<Interop.Crypto.X509VerifyStatusCode> errorCodes,
+ ErrorCollection errorCodes,
List<X509ChainStatus> elementStatus,
List<X509ChainStatus> overallStatus)
{
}
}
- internal static HashSet<X509Certificate2> FindCandidates(
- X509Certificate2 leaf,
- X509Certificate2Collection extraStore,
- HashSet<X509Certificate2> downloaded,
- HashSet<X509Certificate2> systemTrusted,
- ref TimeSpan remainingDownloadTime)
- {
- var candidates = new HashSet<X509Certificate2>();
- var toProcess = new Queue<X509Certificate2>();
- toProcess.Enqueue(leaf);
-
- using (var systemRootStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
- using (var systemIntermediateStore = new X509Store(StoreName.CertificateAuthority, StoreLocation.LocalMachine))
- using (var userRootStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser))
- using (var userIntermediateStore = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
- using (var userMyStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
- {
- systemRootStore.Open(OpenFlags.ReadOnly);
- systemIntermediateStore.Open(OpenFlags.ReadOnly);
- userRootStore.Open(OpenFlags.ReadOnly);
- userIntermediateStore.Open(OpenFlags.ReadOnly);
- userMyStore.Open(OpenFlags.ReadOnly);
-
- X509Certificate2Collection systemRootCerts = systemRootStore.Certificates;
- X509Certificate2Collection systemIntermediateCerts = systemIntermediateStore.Certificates;
- X509Certificate2Collection userRootCerts = userRootStore.Certificates;
- X509Certificate2Collection userIntermediateCerts = userIntermediateStore.Certificates;
- X509Certificate2Collection userMyCerts = userMyStore.Certificates;
-
- // fill the system trusted collection
- foreach (X509Certificate2 userRootCert in userRootCerts)
- {
- if (!systemTrusted.Add(userRootCert))
- {
- // If we have already (effectively) added another instance of this certificate,
- // then this one provides no value. A Disposed cert won't harm the matching logic.
- userRootCert.Dispose();
- }
- }
-
- foreach (X509Certificate2 systemRootCert in systemRootCerts)
- {
- if (!systemTrusted.Add(systemRootCert))
- {
- // If we have already (effectively) added another instance of this certificate,
- // (for example, because another copy of it was in the user store)
- // then this one provides no value. A Disposed cert won't harm the matching logic.
- systemRootCert.Dispose();
- }
- }
-
- X509Certificate2Collection[] storesToCheck;
- if (extraStore != null && extraStore.Count > 0)
- {
- storesToCheck = new[]
- {
- extraStore,
- userMyCerts,
- userIntermediateCerts,
- systemIntermediateCerts,
- userRootCerts,
- systemRootCerts,
- };
- }
- else
- {
- storesToCheck = new[]
- {
- userMyCerts,
- userIntermediateCerts,
- systemIntermediateCerts,
- userRootCerts,
- systemRootCerts,
- };
- }
-
- while (toProcess.Count > 0)
- {
- X509Certificate2 current = toProcess.Dequeue();
-
- candidates.Add(current);
-
- HashSet<X509Certificate2> results = FindIssuer(
- current,
- storesToCheck,
- downloaded,
- ref remainingDownloadTime);
-
- if (results != null)
- {
- foreach (X509Certificate2 result in results)
- {
- if (!candidates.Contains(result))
- {
- toProcess.Enqueue(result);
- }
- }
- }
- }
-
- // Avoid sending unused certs into the finalizer queue by doing only a ref check
-
- var candidatesByReference = new HashSet<X509Certificate2>(
- candidates,
- ReferenceEqualityComparer<X509Certificate2>.Instance);
-
- // Certificates come from 6 sources:
- // 1) extraStore.
- // These are cert objects that are provided by the user, we shouldn't dispose them.
- // 2) the machine root store
- // These certs are moving on to the "was I a system trust?" test, and we shouldn't dispose them.
- // 3) the user root store
- // These certs are moving on to the "was I a system trust?" test, and we shouldn't dispose them.
- // 4) the machine intermediate store
- // These certs were either path candidates, or not. If they were, don't dispose them. Otherwise do.
- // 5) the user intermediate store
- // These certs were either path candidates, or not. If they were, don't dispose them. Otherwise do.
- // 6) the user my store
- // These certs were either path candidates, or not. If they were, don't dispose them. Otherwise do.
- DisposeUnreferenced(candidatesByReference, systemIntermediateCerts);
- DisposeUnreferenced(candidatesByReference, userIntermediateCerts);
- DisposeUnreferenced(candidatesByReference, userMyCerts);
- }
-
- return candidates;
- }
-
- private static void DisposeUnreferenced(
- ISet<X509Certificate2> referencedSet,
- X509Certificate2Collection storeCerts)
- {
- foreach (X509Certificate2 cert in storeCerts)
- {
- if (!referencedSet.Contains(cert))
- {
- cert.Dispose();
- }
- }
- }
-
- private static HashSet<X509Certificate2> FindIssuer(
- X509Certificate2 cert,
- X509Certificate2Collection[] stores,
- HashSet<X509Certificate2> downloadedCerts,
+ private static X509Certificate2 DownloadCertificate(
+ ReadOnlyMemory<byte> authorityInformationAccess,
ref TimeSpan remainingDownloadTime)
{
- if (IsSelfSigned(cert))
+ // Don't do any work if we're over limit.
+ if (remainingDownloadTime <= TimeSpan.Zero)
{
- // It's a root cert, we won't make any progress.
return null;
}
- SafeX509Handle certHandle = ((OpenSslX509CertificateReader)cert.Pal).SafeHandle;
-
- foreach (X509Certificate2Collection store in stores)
- {
- HashSet<X509Certificate2> fromStore = null;
-
- foreach (X509Certificate2 candidate in store)
- {
- var certPal = (OpenSslX509CertificateReader)candidate.Pal;
-
- if (certPal == null)
- {
- continue;
- }
-
- SafeX509Handle candidateHandle = certPal.SafeHandle;
-
- int issuerError = Interop.Crypto.X509CheckIssued(candidateHandle, certHandle);
-
- if (issuerError == 0)
- {
- if (fromStore == null)
- {
- fromStore = new HashSet<X509Certificate2>();
- }
-
- fromStore.Add(candidate);
- }
- }
-
- if (fromStore != null)
- {
- return fromStore;
- }
- }
-
- byte[] authorityInformationAccess = null;
-
- foreach (X509Extension extension in cert.Extensions)
- {
- if (StringComparer.Ordinal.Equals(extension.Oid.Value, Oids.AuthorityInformationAccess))
- {
- // If there's an Authority Information Access extension, it might be used for
- // looking up additional certificates for the chain.
- authorityInformationAccess = extension.RawData;
- break;
- }
- }
+ string uri = FindHttpAiaRecord(authorityInformationAccess, Oids.CertificateAuthorityIssuers);
- if (authorityInformationAccess != null)
+ if (uri == null)
{
- X509Certificate2 downloaded = DownloadCertificate(
- authorityInformationAccess,
- ref remainingDownloadTime);
-
- if (downloaded != null)
- {
- downloadedCerts.Add(downloaded);
-
- return new HashSet<X509Certificate2>() { downloaded };
- }
+ return null;
}
- return null;
+ return CertificateAssetDownloader.DownloadCertificate(uri, ref remainingDownloadTime);
}
- private static bool IsSelfSigned(X509Certificate2 cert)
+ private static string GetOcspEndpoint(SafeX509Handle cert)
{
- return StringComparer.Ordinal.Equals(cert.Subject, cert.Issuer);
- }
+ ArraySegment<byte> authorityInformationAccess =
+ OpenSslX509CertificateReader.FindFirstExtension(
+ cert,
+ Oids.AuthorityInformationAccess);
- private static X509Certificate2 DownloadCertificate(
- byte[] authorityInformationAccess,
- ref TimeSpan remainingDownloadTime)
- {
- // Don't do any work if we're over limit.
- if (remainingDownloadTime <= TimeSpan.Zero)
+ if (authorityInformationAccess.Count == 0)
{
return null;
}
- string uri = FindHttpAiaRecord(authorityInformationAccess, Oids.CertificateAuthorityIssuers);
-
- if (uri == null)
- {
- return null;
- }
-
- return CertificateAssetDownloader.DownloadCertificate(uri, ref remainingDownloadTime);
+ string baseUrl = FindHttpAiaRecord(authorityInformationAccess, Oids.OcspEndpoint);
+ ArrayPool<byte>.Shared.Return(authorityInformationAccess.Array);
+ return baseUrl;
}
- internal static string FindHttpAiaRecord(byte[] authorityInformationAccess, string recordTypeOid)
+ private static string FindHttpAiaRecord(ReadOnlyMemory<byte> authorityInformationAccess, string recordTypeOid)
{
AsnReader reader = new AsnReader(authorityInformationAccess, AsnEncodingRules.DER);
AsnReader sequenceReader = reader.ReadSequence();
return null;
}
- private class WorkingChain
+ private static void AddToStackAndUpRef(SafeX509Handle cert, SafeX509StackHandle stack)
{
- internal readonly List<List<Interop.Crypto.X509VerifyStatusCode>> Errors =
- new List<List<Interop.Crypto.X509VerifyStatusCode>>();
+ using (SafeX509Handle tmp = Interop.Crypto.X509UpRef(cert))
+ {
+ if (!Interop.Crypto.PushX509StackField(stack, tmp))
+ {
+ throw Interop.Crypto.CreateOpenSslCryptographicException();
+ }
+
+ // Ownership was transferred to the cert stack.
+ tmp.SetHandleAsInvalid();
+ }
+ }
+
+ private static void AddToStackAndUpRef(IntPtr cert, SafeX509StackHandle stack)
+ {
+ using (SafeX509Handle tmp = Interop.Crypto.X509UpRef(cert))
+ {
+ if (!Interop.Crypto.PushX509StackField(stack, tmp))
+ {
+ throw Interop.Crypto.CreateOpenSslCryptographicException();
+ }
+
+ // Ownership was transferred to the cert stack.
+ tmp.SetHandleAsInvalid();
+ }
+ }
+
+ private sealed class WorkingChain : IDisposable
+ {
+ private ErrorCollection[] _errors;
+
+ internal int LastError => _errors?.Length ?? 0;
+
+ internal ref ErrorCollection this[int idx] => ref _errors[idx];
+
+ public void Dispose()
+ {
+ ErrorCollection[] toReturn = _errors;
+ _errors = null;
+
+ if (toReturn != null)
+ {
+ ArrayPool<ErrorCollection>.Shared.Return(toReturn);
+ }
+ }
internal int VerifyCallback(int ok, IntPtr ctx)
{
if (errorCode != Interop.Crypto.X509VerifyStatusCode.X509_V_OK &&
errorCode != Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_CRL_NOT_YET_VALID)
{
- while (Errors.Count <= errorDepth)
+ if (_errors == null)
{
- Errors.Add(null);
- }
+ int size = Math.Max(DefaultChainCapacity, errorDepth + 1);
+ _errors = ArrayPool<ErrorCollection>.Shared.Rent(size);
- if (Errors[errorDepth] == null)
+ // We only do spares writes.
+ _errors.AsSpan().Clear();
+ }
+ else if (errorDepth >= _errors.Length)
{
- Errors[errorDepth] = new List<Interop.Crypto.X509VerifyStatusCode>();
+ ErrorCollection[] toReturn = _errors;
+ _errors = ArrayPool<ErrorCollection>.Shared.Rent(errorDepth + 1);
+ toReturn.AsSpan().CopyTo(_errors);
+
+ // We only do spares writes, clear the remainder.
+ _errors.AsSpan(toReturn.Length).Clear();
+ ArrayPool<ErrorCollection>.Shared.Return(toReturn);
}
- Errors[errorDepth].Add(errorCode);
+ _errors[errorDepth].Add(errorCode);
}
}
}
}
}
+
+ 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);
+ }
+ }
}
}
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)
internal const string CryptographyFeatureName = "cryptography";
internal const string X509StoresSubFeatureName = "x509stores";
internal const string CrlsSubFeatureName = "crls";
+ internal const string OcspSubFeatureName = "ocsp";
}
}
<Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.GetIntegerBytes.cs">
<Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.GetIntegerBytes.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.Nid.cs">
+ <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.Nid.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.Bignum.cs">
<Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Bignum.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs">
<Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.OCSP.cs">
+ <Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.OCSP.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Security.Cryptography.Native\Interop.Pkcs12.cs">
<Link>Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Pkcs12.cs</Link>
</Compile>
internal const string RootStoreName = "Root";
internal const string IntermediateCAStoreName = "CA";
internal const string DisallowedStoreName = "Disallowed";
+ internal const string MyStoreName = "My";
private IStorePal _storePal;
Name = DisallowedStoreName;
break;
case StoreName.My:
- Name = "My";
+ Name = MyStoreName;
break;
case StoreName.Root:
Name = RootStoreName;
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