Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Add support for OCSP on Linux, overhaul Linux X509Chain processing
Browse files Browse the repository at this point in the history
This change moves a lot of the chain building work from managed code into the native shim, largely to cut down on the number of P/Invokes required to set up the chain builder.

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

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

"Conforming" OCSP requests are opportunistically cached on the basis that local filesystem re-reads are more reliable (and faster) than doing a live request to the CA.
  • Loading branch information
bartonjs authored Feb 22, 2019
1 parent cbf4620 commit 0fbbb68
Show file tree
Hide file tree
Showing 35 changed files with 2,373 additions and 504 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,42 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;

internal static partial class Interop
{
internal static partial class Crypto
{
private static readonly ConcurrentDictionary<string, int> s_nidLookup =
new ConcurrentDictionary<string, int>();

internal const int NID_undef = 0;

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ObjSn2Nid", CharSet = CharSet.Ansi)]
internal static extern int ObjSn2Nid(string sn);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ObjTxt2Nid", CharSet = CharSet.Ansi)]
private static extern int ObjTxt2Nid(string oid);

internal static int ResolveRequiredNid(string oid)
{
return s_nidLookup.GetOrAdd(oid, s => LookupNid(s));
}

private static int LookupNid(string oid)
{
int nid = ObjTxt2Nid(oid);

if (nid == NID_undef)
{
Debug.Fail($"NID Lookup for {oid} failed, only well-known types should be queried.");
throw new CryptographicException();
}

return nid;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
Expand Down Expand Up @@ -108,6 +109,17 @@ private static extern int SetX509ChainVerifyTime(
int second,
[MarshalAs(UnmanagedType.Bool)] bool isDst);

[DllImport(Libraries.CryptoNative)]
private static extern int CryptoNative_X509StoreSetVerifyTime(
SafeX509StoreHandle ctx,
int year,
int month,
int day,
int hour,
int minute,
int second,
[MarshalAs(UnmanagedType.Bool)] bool isDst);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_CheckX509IpAddress")]
internal static extern int CheckX509IpAddress(SafeX509Handle x509, [In]byte[] addressBytes, int addressLen, string hostname, int cchHostname);

Expand All @@ -119,6 +131,11 @@ internal static byte[] GetAsn1StringBytes(IntPtr asn1)
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);
Expand Down Expand Up @@ -159,6 +176,28 @@ internal static void SetX509ChainVerifyTime(SafeX509StoreCtxHandle ctx, DateTime
}
}

internal static void X509StoreSetVerifyTime(SafeX509StoreHandle ctx, DateTime verifyTime)
{
// OpenSSL is going to convert our input time to universal, so we should be in Local or
// Unspecified (local-assumed).
Debug.Assert(verifyTime.Kind != DateTimeKind.Utc, "UTC verifyTime should have been normalized to Local");

int succeeded = CryptoNative_X509StoreSetVerifyTime(
ctx,
verifyTime.Year,
verifyTime.Month,
verifyTime.Day,
verifyTime.Hour,
verifyTime.Minute,
verifyTime.Second,
verifyTime.IsDaylightSavingTime());

if (succeeded != 1)
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}
}

private static byte[] GetDynamicBuffer<THandle>(NegativeSizeReadMethod<THandle> method, THandle handle)
{
int negativeSize = method(handle, null, 0);
Expand All @@ -179,5 +218,28 @@ private static byte[] GetDynamicBuffer<THandle>(NegativeSizeReadMethod<THandle>

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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;

internal static partial class Interop
{
Expand All @@ -15,14 +15,17 @@ internal static partial class Crypto

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];
Expand All @@ -42,5 +45,40 @@ internal static byte[] OpenSslEncode<THandle>(GetEncodedSizeFunc<THandle> getSiz

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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class Crypto
{
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_OcspRequestDestroy")]
internal static extern void OcspRequestDestroy(IntPtr ocspReq);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetOcspRequestDerSize")]
internal static extern int GetOcspRequestDerSize(SafeOcspRequestHandle req);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EncodeOcspRequest")]
internal static extern int EncodeOcspRequest(SafeOcspRequestHandle req, byte[] buf);

[DllImport(Libraries.CryptoNative)]
private static extern SafeOcspResponseHandle CryptoNative_DecodeOcspResponse(ref byte buf, int len);

internal static SafeOcspResponseHandle DecodeOcspResponse(ReadOnlySpan<byte> buf)
{
return CryptoNative_DecodeOcspResponse(
ref MemoryMarshal.GetReference(buf),
buf.Length);
}

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_OcspResponseDestroy")]
internal static extern void OcspResponseDestroy(IntPtr ocspReq);


[DllImport(Libraries.CryptoNative)]
private static extern X509VerifyStatusCode CryptoNative_X509ChainGetCachedOcspStatus(SafeX509StoreCtxHandle ctx, string cachePath);

internal static X509VerifyStatusCode X509ChainGetCachedOcspStatus(SafeX509StoreCtxHandle ctx, string cachePath)
{
X509VerifyStatusCode response = CryptoNative_X509ChainGetCachedOcspStatus(ctx, cachePath);

if (response < 0)
{
Debug.Fail($"Unexpected response from X509ChainGetCachedOcspSuccess: {response}");
throw new CryptographicException();
}

return response;
}

[DllImport(Libraries.CryptoNative)]
private static extern X509VerifyStatusCode CryptoNative_X509ChainVerifyOcsp(
SafeX509StoreCtxHandle ctx,
SafeOcspRequestHandle req,
SafeOcspResponseHandle resp,
string cachePath);

internal static X509VerifyStatusCode X509ChainVerifyOcsp(
SafeX509StoreCtxHandle ctx,
SafeOcspRequestHandle req,
SafeOcspResponseHandle resp,
string cachePath)
{
X509VerifyStatusCode response = CryptoNative_X509ChainVerifyOcsp(ctx, req, resp, cachePath);

if (response < 0)
{
Debug.Fail($"Unexpected response from X509ChainGetCachedOcspSuccess: {response}");
throw new CryptographicException();
}

return response;
}

[DllImport(Libraries.CryptoNative)]
private static extern SafeOcspRequestHandle CryptoNative_X509ChainBuildOcspRequest(SafeX509StoreCtxHandle storeCtx);

internal static SafeOcspRequestHandle X509ChainBuildOcspRequest(SafeX509StoreCtxHandle storeCtx)
{
SafeOcspRequestHandle req = CryptoNative_X509ChainBuildOcspRequest(storeCtx);

if (req.IsInvalid)
{
req.Dispose();
throw CreateOpenSslCryptographicException();
}

return req;
}
}
}

namespace System.Security.Cryptography.X509Certificates
{
internal class SafeOcspRequestHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeOcspRequestHandle()
: base(true)
{
}

protected override bool ReleaseHandle()
{
Interop.Crypto.OcspRequestDestroy(handle);
handle = IntPtr.Zero;
return true;
}
}

internal class SafeOcspResponseHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeOcspResponseHandle()
: base(true)
{
}

protected override bool ReleaseHandle()
{
Interop.Crypto.OcspResponseDestroy(handle);
handle = IntPtr.Zero;
return true;
}
}
}
Loading

0 comments on commit 0fbbb68

Please sign in to comment.