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

Implement TLS1.3 delayed client cert requests on Linux #64268

Merged
merged 7 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -327,6 +327,14 @@ internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credentia
Crypto.ErrClearError();
}

// relevant to TLS 1.3 only: if user supplied a client cert or cert callback,
// advertise that we are willing to send the certificate post-handshake.
if (sslAuthenticationOptions.ClientCertificates?.Count > 0 ||
sslAuthenticationOptions.CertSelectionDelegate != null)
{
Ssl.SslSetPostHandshakeAuth(sslHandle, 1);
}

// Set client cert callback, this will interrupt the handshake with SecurityStatusPalErrorCode.CredentialsNeeded
// if server actually requests a certificate.
Ssl.SslSetClientCertCallback(sslHandle, 1);
Expand Down Expand Up @@ -504,9 +512,16 @@ internal static int Decrypt(SafeSslHandle context, Span<byte> buffer, out Ssl.Ss

case Ssl.SslErrorCode.SSL_ERROR_WANT_READ:
// update error code to renegotiate if renegotiate is pending, otherwise make it SSL_ERROR_WANT_READ
errorCode = Ssl.IsSslRenegotiatePending(context) ?
Ssl.SslErrorCode.SSL_ERROR_RENEGOTIATE :
Ssl.SslErrorCode.SSL_ERROR_WANT_READ;
errorCode = Ssl.IsSslRenegotiatePending(context)
? Ssl.SslErrorCode.SSL_ERROR_RENEGOTIATE
: Ssl.SslErrorCode.SSL_ERROR_WANT_READ;
break;

case Ssl.SslErrorCode.SSL_ERROR_WANT_X509_LOOKUP:
// This happens in TLS 1.3 when server requests post-handshake authentication
// but no certificate is provided by client. We can process it the same way as
// renegotiation on older TLS versions
errorCode = Ssl.SslErrorCode.SSL_ERROR_RENEGOTIATE;
break;

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,17 @@ internal static partial class Ssl
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetData")]
internal static partial int SslSetData(IntPtr ssl, IntPtr data);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslUseCertificate")]
internal static extern int SslUseCertificate(SafeSslHandle ssl, SafeX509Handle certPtr);
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslUseCertificate")]
internal static partial int SslUseCertificate(SafeSslHandle ssl, SafeX509Handle certPtr);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslUsePrivateKey")]
internal static extern int SslUsePrivateKey(SafeSslHandle ssl, SafeEvpPKeyHandle keyPtr);
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslUsePrivateKey")]
internal static partial int SslUsePrivateKey(SafeSslHandle ssl, SafeEvpPKeyHandle keyPtr);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetClientCertCallback")]
internal static extern unsafe void SslSetClientCertCallback(SafeSslHandle ssl, int set);
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetClientCertCallback")]
internal static unsafe partial void SslSetClientCertCallback(SafeSslHandle ssl, int set);

[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetPostHandshakeAuth")]
internal static partial void SslSetPostHandshakeAuth(SafeSslHandle ssl, int value);

[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_Tls13Supported")]
private static partial int Tls13SupportedImpl();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,10 @@ private ProtocolToken ProcessBlob(int frameSize)
}

frameSize = nextHeader.Length + TlsFrameHelper.HeaderSize;
// Can process more handshake frames in single step, but we should avoid processing too much so as to preserve API boundary between handshake and I/O.
if ((nextHeader.Type != TlsContentType.Handshake && nextHeader.Type != TlsContentType.ChangeCipherSpec) || frameSize > _buffer.EncryptedLength)

// Can process more handshake frames in single step or during TLS1.3 post-handshake auth, but we should
rzikm marked this conversation as resolved.
Show resolved Hide resolved
// avoid processing too much so as to preserve API boundary between handshake and I/O.
if ((nextHeader.Type != TlsContentType.Handshake && nextHeader.Type != TlsContentType.ChangeCipherSpec) && !_isRenego || frameSize > _buffer.EncryptedLength)
{
// We don't have full frame left or we already have app data which needs to be processed by decrypt.
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,9 +475,14 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout(
[ActiveIssue("https://github.com/dotnet/runtime/issues/58927", TestPlatforms.Windows)]
[InlineData(true)]
[InlineData(false)]
[PlatformSpecific(TestPlatforms.Windows)]
public async Task SslStream_NegotiateClientCertificateAsyncTls13_Succeeds(bool sendClientCertificate)
{
if (PlatformDetection.IsWindows10Version22000OrGreater)
{
// [ActiveIssue("https://github.com/dotnet/runtime/issues/58927")]
throw new SkipTestException("Unstable on Windows 11");
}

bool negotiateClientCertificateCalled = false;
using CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TestConfiguration.PassingTestTimeout);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ static const Entry s_cryptoNative[] =
DllImportEntry(CryptoNative_SslSetAlpnProtos)
DllImportEntry(CryptoNative_SslSetBio)
DllImportEntry(CryptoNative_SslSetClientCertCallback)
DllImportEntry(CryptoNative_SslSetPostHandshakeAuth)
DllImportEntry(CryptoNative_SslSetConnectState)
DllImportEntry(CryptoNative_SslSetData)
DllImportEntry(CryptoNative_SslSetQuietShutdown)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,9 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void);
REQUIRED_FUNCTION(SSL_write) \
REQUIRED_FUNCTION(SSL_use_certificate) \
REQUIRED_FUNCTION(SSL_use_PrivateKey) \
LIGHTUP_FUNCTION(SSL_verify_client_post_handshake) \
LIGHTUP_FUNCTION(SSL_set_post_handshake_auth) \
REQUIRED_FUNCTION(SSL_version) \
rzikm marked this conversation as resolved.
Show resolved Hide resolved
FALLBACK_FUNCTION(X509_check_host) \
REQUIRED_FUNCTION(X509_check_purpose) \
REQUIRED_FUNCTION(X509_cmp_current_time) \
Expand Down Expand Up @@ -977,6 +980,9 @@ FOR_ALL_OPENSSL_FUNCTIONS
#define SSL_write SSL_write_ptr
#define SSL_use_certificate SSL_use_certificate_ptr
#define SSL_use_PrivateKey SSL_use_PrivateKey_ptr
#define SSL_verify_client_post_handshake SSL_verify_client_post_handshake_ptr
#define SSL_set_post_handshake_auth SSL_set_post_handshake_auth_ptr
#define SSL_version SSL_version_ptr
#define TLS_method TLS_method_ptr
#define X509_check_host X509_check_host_ptr
#define X509_check_purpose X509_check_purpose_ptr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ unsigned long SSL_CTX_set_options(SSL_CTX* ctx, unsigned long options);
void SSL_CTX_set_security_level(SSL_CTX* ctx, int32_t level);
int32_t SSL_is_init_finished(SSL* ssl);
unsigned long SSL_set_options(SSL* ctx, unsigned long options);
void SSL_set_post_handshake_auth(SSL *s, int val);
int SSL_session_reused(SSL* ssl);
int SSL_verify_client_post_handshake(SSL *s);
const SSL_METHOD* TLS_method(void);
const ASN1_TIME* X509_CRL_get0_nextUpdate(const X509_CRL* crl);
int32_t X509_NAME_get0_der(X509_NAME* x509Name, const uint8_t** pder, size_t* pderlen);
Expand Down
34 changes: 34 additions & 0 deletions src/native/libs/System.Security.Cryptography.Native/pal_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,27 @@ static int verify_callback(int preverify_ok, X509_STORE_CTX* store)

int32_t CryptoNative_SslRenegotiate(SSL* ssl, int32_t* error)
{
#ifdef NEED_OPENSSL_1_1
// TLS1.3 uses different API for renegotiation/delayed client cert request
#ifndef TLS1_3_VERSION
#define TLS1_3_VERSION 0x0304
#endif
if (SSL_version(ssl) == TLS1_3_VERSION)
{
// this is just a sanity check, if TLS 1.3 was negotiated, then the function must be available
if (API_EXISTS(SSL_verify_client_post_handshake))
{
// Post-handshake auth reqires SSL_VERIFY_PEER to be set
CryptoNative_SslSetVerifyPeer(ssl);
return SSL_verify_client_post_handshake(ssl);
}
else
{
return 0;
}
}
#endif

// The openssl context is destroyed so we can't use ticket or session resumption.
SSL_set_options(ssl, SSL_OP_NO_TICKET | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);

Expand Down Expand Up @@ -733,6 +754,19 @@ void CryptoNative_SslSetClientCertCallback(SSL* ssl, int set)
SSL_set_cert_cb(ssl, set ? client_certificate_cb : NULL, NULL);
}

void CryptoNative_SslSetPostHandshakeAuth(SSL* ssl, int32_t val)
{
#ifdef NEED_OPENSSL_1_1
if (API_EXISTS(SSL_set_post_handshake_auth))
{
SSL_set_post_handshake_auth(ssl, val);
}
#else
(void)ssl;
(void)val;
#endif
}

int32_t CryptoNative_SslSetData(SSL* ssl, void *ptr)
{
return SSL_set_ex_data(ssl, 0, ptr);
Expand Down
9 changes: 7 additions & 2 deletions src/native/libs/System.Security.Cryptography.Native/pal_ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ It will unset callback if set is zero.
*/
PALEXPORT void CryptoNative_SslSetClientCertCallback(SSL* ssl, int set);

/*
Requests that client sends Post-Handshake Authentication extension in ClientHello.
*/
PALEXPORT void CryptoNative_SslSetPostHandshakeAuth(SSL* ssl, int32_t val);

/*=======
Sets session caching. 0 is disabled.
*/
Expand Down Expand Up @@ -222,9 +227,9 @@ when an error is encountered.
PALEXPORT int32_t CryptoNative_SslRead(SSL* ssl, void* buf, int32_t num, int32_t* error);

/*
Shims the SSL_renegotiate method.
Shims the SSL_renegotiate method (up to TLS 1.2), or SSL_verify_client_post_handshake (TLS 1.3)

Returns 1 when renegotiation started; 0 on error.
Returns 1 when renegotiation/post-handshake authentication started; 0 on error.
*/
PALEXPORT int32_t CryptoNative_SslRenegotiate(SSL* ssl, int32_t* error);

Expand Down