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

Add support for OCSP on Linux, overhaul X509Chain processing #35367

Merged
merged 31 commits into from
Feb 22, 2019
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
318893d
New native targets to start simplifying chain processing
bartonjs Feb 8, 2019
439e021
Move a lot more chain processing to native code
bartonjs Feb 9, 2019
ae3187e
Code cleanup
bartonjs Feb 12, 2019
055dfce
Reduce some redundant processing and allocations
bartonjs Feb 12, 2019
03a7026
Reduce GC footprint of chain building
bartonjs Feb 12, 2019
c431e7e
Native changes required for OCSP processing
bartonjs Feb 14, 2019
84545a9
Managed side for OCSP
bartonjs Feb 14, 2019
e8ca875
Remove redundant casting
bartonjs Feb 15, 2019
168df27
Fix compilation error with newer clang
bartonjs Feb 15, 2019
d3f0937
Add field definitions needed for building portable with 1.1 headers
bartonjs Feb 15, 2019
0b0f7a6
Add missing function prototypes for building portable with 1.0 headers
bartonjs Feb 15, 2019
02c8547
Fix some edge case handling with X509_cmp_time and simplify file ops
bartonjs Feb 15, 2019
55e1807
Make portable build succeed with 1.0 headers
bartonjs Feb 15, 2019
00fe0a0
Make portable build work with 1.1 headers
bartonjs Feb 15, 2019
d088b25
Reduce the number of P/Invokes and string-allocs to find extensions
bartonjs Feb 15, 2019
1f7fadf
Native functions to accelerate extension lookup
bartonjs Feb 15, 2019
a4ea1c3
Revert unnecessary field definition
bartonjs Feb 15, 2019
1e92716
Remember to check the response nonce
bartonjs Feb 15, 2019
b18894c
Fix some whitespace
bartonjs Feb 15, 2019
3683f34
Move verification time to X509_STORE
bartonjs Feb 15, 2019
6ca95fa
Fix a null-deref
bartonjs Feb 15, 2019
8de68b6
Add comments to new exports
bartonjs Feb 16, 2019
6c439b6
Use const void* instead of void* for unused fields
bartonjs Feb 16, 2019
b0a0700
Native changes for moving verifytime to X509_STORE
bartonjs Feb 16, 2019
95a7c29
Fix compilation with 1.1 headers
bartonjs Feb 16, 2019
03470d2
Reduce the number of P/Invokes in rebuilding a chain
bartonjs Feb 16, 2019
5ff7fc8
Native changes to reduce the P/Invokes on rebuild
bartonjs Feb 16, 2019
c756f23
Review/CI feedback
bartonjs Feb 16, 2019
6befdeb
Fix compilation with 1.0.1 headers
bartonjs Feb 17, 2019
367f281
Fix ErrorCollection to set overflow and delay a throw on a too-large …
bartonjs Feb 20, 2019
cee85dd
Apply review feedback
bartonjs Feb 20, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 =
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
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);
bartonjs marked this conversation as resolved.
Show resolved Hide resolved

if (negativeSize > 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be >=? (0 is not negative)

{
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