Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable server-side OCSP stapling on Linux #69833

Merged
merged 7 commits into from
Jun 2, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
@@ -0,0 +1,94 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

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
{
[LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)]
private static partial int CryptoNative_X509ChainGetCachedOcspStatus(
SafeX509StoreCtxHandle ctx,
string cachePath,
int chainDepth);

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

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

return response;
}

[LibraryImport(Libraries.CryptoNative)]
private static partial int CryptoNative_X509ChainHasStapledOcsp(SafeX509StoreCtxHandle storeCtx);

internal static bool X509ChainHasStapledOcsp(SafeX509StoreCtxHandle storeCtx)
{
int resp = CryptoNative_X509ChainHasStapledOcsp(storeCtx);

if (resp == 1)
{
return true;
}

Debug.Assert(resp == 0, $"Unexpected response from X509ChainHasStapledOcsp: {resp}");
return false;
}

[LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)]
private static partial int CryptoNative_X509ChainVerifyOcsp(
SafeX509StoreCtxHandle ctx,
SafeOcspRequestHandle req,
SafeOcspResponseHandle resp,
string cachePath,
int chainDepth);

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

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

return response;
}

[LibraryImport(Libraries.CryptoNative)]
private static partial SafeOcspRequestHandle CryptoNative_X509ChainBuildOcspRequest(
SafeX509StoreCtxHandle storeCtx,
int chainDepth);

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

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

return req;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Win32.SafeHandles;

Expand All @@ -21,97 +20,73 @@ internal static partial class Crypto
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EncodeOcspRequest")]
internal static partial int EncodeOcspRequest(SafeOcspRequestHandle req, byte[] buf);

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

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

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

[LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)]
private static partial int CryptoNative_X509ChainGetCachedOcspStatus(
SafeX509StoreCtxHandle ctx,
string cachePath,
int chainDepth);
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_X509BuildOcspRequest")]
internal static partial SafeOcspRequestHandle X509BuildOcspRequest(IntPtr subject, IntPtr issuer);

internal static X509VerifyStatusCode X509ChainGetCachedOcspStatus(SafeX509StoreCtxHandle ctx, string cachePath, int chainDepth)
[LibraryImport(Libraries.CryptoNative)]
private static unsafe partial int CryptoNative_X509DecodeOcspToExpiration(
byte* buf,
int len,
SafeOcspRequestHandle req,
IntPtr subject,
IntPtr issuer,
ref long expiration);

internal static unsafe bool X509DecodeOcspToExpiration(
ReadOnlySpan<byte> buf,
SafeOcspRequestHandle request,
IntPtr x509Subject,
IntPtr x509Issuer,
out DateTimeOffset expiration)
{
X509VerifyStatusCode response = (X509VerifyStatusCode)CryptoNative_X509ChainGetCachedOcspStatus(ctx, cachePath, chainDepth);
long timeT = 0;
int ret;

if (response.Code < 0)
fixed (byte* pBuf = buf)
{
Debug.Fail($"Unexpected response from X509ChainGetCachedOcspSuccess: {response}");
throw new CryptographicException();
ret = CryptoNative_X509DecodeOcspToExpiration(
pBuf,
buf.Length,
request,
x509Subject,
x509Issuer,
ref timeT);
}

return response;
}

[LibraryImport(Libraries.CryptoNative)]
private static partial int CryptoNative_X509ChainHasStapledOcsp(SafeX509StoreCtxHandle storeCtx);

internal static bool X509ChainHasStapledOcsp(SafeX509StoreCtxHandle storeCtx)
{
int resp = CryptoNative_X509ChainHasStapledOcsp(storeCtx);

if (resp == 1)
if (ret == 1)
{
if (timeT != 0)
{
expiration = DateTimeOffset.FromUnixTimeSeconds(timeT);
}
else
{
// Something went wrong during the determination of when the response
// should not be used any longer.
// Half an hour sounds fair?
expiration = DateTimeOffset.UtcNow.AddMinutes(30);
}

return true;
}

Debug.Assert(resp == 0, $"Unexpected response from X509ChainHasStapledOcsp: {resp}");
Debug.Assert(ret == 0, $"Unexpected response from X509DecodeOcspToExpiration: {ret}");
expiration = DateTimeOffset.MinValue;
return false;
}

[LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)]
private static partial int CryptoNative_X509ChainVerifyOcsp(
SafeX509StoreCtxHandle ctx,
SafeOcspRequestHandle req,
SafeOcspResponseHandle resp,
string cachePath,
int chainDepth);

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

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

return response;
}

[LibraryImport(Libraries.CryptoNative)]
private static partial SafeOcspRequestHandle CryptoNative_X509ChainBuildOcspRequest(
SafeX509StoreCtxHandle storeCtx,
int chainDepth);
private static partial SafeOcspResponseHandle CryptoNative_DecodeOcspResponse(ref byte buf, int len);

internal static SafeOcspRequestHandle X509ChainBuildOcspRequest(SafeX509StoreCtxHandle storeCtx, int chainDepth)
internal static SafeOcspResponseHandle DecodeOcspResponse(ReadOnlySpan<byte> buf)
{
SafeOcspRequestHandle req = CryptoNative_X509ChainBuildOcspRequest(storeCtx, chainDepth);

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

return req;
return CryptoNative_DecodeOcspResponse(
ref MemoryMarshal.GetReference(buf),
buf.Length);
}

[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_OcspResponseDestroy")]
internal static partial void OcspResponseDestroy(IntPtr ocspReq);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,19 @@ internal static unsafe SafeSslContextHandle AllocateSslContext(SafeFreeSslCreden
SetSslCertificate(sslCtx, certHandle!, certKeyHandle!);
}

if (sslAuthenticationOptions.CertificateContext != null && sslAuthenticationOptions.CertificateContext.IntermediateCertificates.Length > 0)
if (sslAuthenticationOptions.CertificateContext != null)
{
if (!Ssl.AddExtraChainCertificates(sslCtx, sslAuthenticationOptions.CertificateContext.IntermediateCertificates))
if (sslAuthenticationOptions.CertificateContext.IntermediateCertificates.Length > 0)
{
throw CreateSslException(SR.net_ssl_use_cert_failed);
if (!Ssl.AddExtraChainCertificates(sslCtx, sslAuthenticationOptions.CertificateContext.IntermediateCertificates))
{
throw CreateSslException(SR.net_ssl_use_cert_failed);
}
}

if (sslAuthenticationOptions.CertificateContext.OcspStaplingAvailable)
{
Ssl.SslCtxSetDefaultOcspCallback(sslCtx);
}
}
}
Expand Down Expand Up @@ -422,27 +430,37 @@ internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credentia
Ssl.SslSetVerifyPeer(sslHandle);
}

if (sslAuthenticationOptions.CertificateContext?.Trust?._sendTrustInHandshake == true)
if (sslAuthenticationOptions.CertificateContext != null)
{
SslCertificateTrust trust = sslAuthenticationOptions.CertificateContext!.Trust!;
X509Certificate2Collection certList = (trust._trustList ?? trust._store!.Certificates);

Debug.Assert(certList != null, "certList != null");
Span<IntPtr> handles = certList.Count <= 256
? stackalloc IntPtr[256]
: new IntPtr[certList.Count];

for (int i = 0; i < certList.Count; i++)
if (sslAuthenticationOptions.CertificateContext.Trust?._sendTrustInHandshake == true)
{
handles[i] = certList[i].Handle;
SslCertificateTrust trust = sslAuthenticationOptions.CertificateContext!.Trust!;
X509Certificate2Collection certList = (trust._trustList ?? trust._store!.Certificates);

Debug.Assert(certList != null, "certList != null");
Span<IntPtr> handles = certList.Count <= 256 ?
stackalloc IntPtr[256] :
new IntPtr[certList.Count];

for (int i = 0; i < certList.Count; i++)
{
handles[i] = certList[i].Handle;
}

if (!Ssl.SslAddClientCAs(sslHandle, handles.Slice(0, certList.Count)))
{
// The method can fail only when the number of cert names exceeds the maximum capacity
// supported by STACK_OF(X509_NAME) structure, which should not happen under normal
// operation.
Debug.Fail("Failed to add issuer to trusted CA list.");
}
}

if (!Ssl.SslAddClientCAs(sslHandle, handles.Slice(0, certList.Count)))
byte[]? ocspResponse = sslAuthenticationOptions.CertificateContext.GetOcspResponseNoWaiting();

if (ocspResponse != null)
{
// The method can fail only when the number of cert names exceeds the maximum capacity
// supported by STACK_OF(X509_NAME) structure, which should not happen under normal
// operation.
Debug.Fail("Failed to add issuer to trusted CA list.");
Ssl.SslStapleOcsp(sslHandle, ocspResponse);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,19 @@ internal static unsafe bool SslAddClientCAs(SafeSslHandle ssl, Span<IntPtr> x509
}
}

[LibraryImport(Libraries.CryptoNative)]
private static unsafe partial void CryptoNative_SslStapleOcsp(SafeSslHandle ssl, byte* buf, int len);

internal static unsafe void SslStapleOcsp(SafeSslHandle ssl, ReadOnlySpan<byte> stapledResponse)
{
Debug.Assert(stapledResponse.Length > 0);

fixed (byte* ptr = stapledResponse)
{
CryptoNative_SslStapleOcsp(ssl, ptr, stapledResponse.Length);
}
}

internal static bool AddExtraChainCertificates(SafeSslHandle ssl, X509Certificate2[] chain)
{
// send pre-computed list of intermediates.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,8 @@ internal static partial class Ssl
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetEncryptionPolicy")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool SetEncryptionPolicy(SafeSslContextHandle ctx, EncryptionPolicy policy);

[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetDefaultOcspCallback")]
internal static partial void SslCtxSetDefaultOcspCallback(SafeSslContextHandle ctx);
}
}
Loading