Skip to content

Commit aaaa2a7

Browse files
authored
Implement TLS1.3 delayed client cert requests on Linux
1 parent 4f2cd55 commit aaaa2a7

File tree

9 files changed

+87
-14
lines changed

9 files changed

+87
-14
lines changed

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,14 @@ internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credentia
327327
Crypto.ErrClearError();
328328
}
329329

330+
// relevant to TLS 1.3 only: if user supplied a client cert or cert callback,
331+
// advertise that we are willing to send the certificate post-handshake.
332+
if (sslAuthenticationOptions.ClientCertificates?.Count > 0 ||
333+
sslAuthenticationOptions.CertSelectionDelegate != null)
334+
{
335+
Ssl.SslSetPostHandshakeAuth(sslHandle, 1);
336+
}
337+
330338
// Set client cert callback, this will interrupt the handshake with SecurityStatusPalErrorCode.CredentialsNeeded
331339
// if server actually requests a certificate.
332340
Ssl.SslSetClientCertCallback(sslHandle, 1);
@@ -504,9 +512,16 @@ internal static int Decrypt(SafeSslHandle context, Span<byte> buffer, out Ssl.Ss
504512

505513
case Ssl.SslErrorCode.SSL_ERROR_WANT_READ:
506514
// update error code to renegotiate if renegotiate is pending, otherwise make it SSL_ERROR_WANT_READ
507-
errorCode = Ssl.IsSslRenegotiatePending(context) ?
508-
Ssl.SslErrorCode.SSL_ERROR_RENEGOTIATE :
509-
Ssl.SslErrorCode.SSL_ERROR_WANT_READ;
515+
errorCode = Ssl.IsSslRenegotiatePending(context)
516+
? Ssl.SslErrorCode.SSL_ERROR_RENEGOTIATE
517+
: Ssl.SslErrorCode.SSL_ERROR_WANT_READ;
518+
break;
519+
520+
case Ssl.SslErrorCode.SSL_ERROR_WANT_X509_LOOKUP:
521+
// This happens in TLS 1.3 when server requests post-handshake authentication
522+
// but no certificate is provided by client. We can process it the same way as
523+
// renegotiation on older TLS versions
524+
errorCode = Ssl.SslErrorCode.SSL_ERROR_RENEGOTIATE;
510525
break;
511526

512527
default:

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,17 @@ internal static partial class Ssl
151151
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetData")]
152152
internal static partial int SslSetData(IntPtr ssl, IntPtr data);
153153

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

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

160-
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetClientCertCallback")]
161-
internal static extern unsafe void SslSetClientCertCallback(SafeSslHandle ssl, int set);
160+
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetClientCertCallback")]
161+
internal static unsafe partial void SslSetClientCertCallback(SafeSslHandle ssl, int set);
162+
163+
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetPostHandshakeAuth")]
164+
internal static partial void SslSetPostHandshakeAuth(SafeSslHandle ssl, int value);
162165

163166
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_Tls13Supported")]
164167
private static partial int Tls13SupportedImpl();

src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,8 +507,10 @@ private ProtocolToken ProcessBlob(int frameSize)
507507
}
508508

509509
frameSize = nextHeader.Length + TlsFrameHelper.HeaderSize;
510-
// 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.
511-
if ((nextHeader.Type != TlsContentType.Handshake && nextHeader.Type != TlsContentType.ChangeCipherSpec) || frameSize > _buffer.EncryptedLength)
510+
511+
// Can process more handshake frames in single step or during TLS1.3 post-handshake auth, but we should
512+
// avoid processing too much so as to preserve API boundary between handshake and I/O.
513+
if ((nextHeader.Type != TlsContentType.Handshake && nextHeader.Type != TlsContentType.ChangeCipherSpec) && !_isRenego || frameSize > _buffer.EncryptedLength)
512514
{
513515
// We don't have full frame left or we already have app data which needs to be processed by decrypt.
514516
break;

src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,9 +475,14 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout(
475475
[ActiveIssue("https://github.com/dotnet/runtime/issues/58927", TestPlatforms.Windows)]
476476
[InlineData(true)]
477477
[InlineData(false)]
478-
[PlatformSpecific(TestPlatforms.Windows)]
479478
public async Task SslStream_NegotiateClientCertificateAsyncTls13_Succeeds(bool sendClientCertificate)
480479
{
480+
if (PlatformDetection.IsWindows10Version22000OrGreater)
481+
{
482+
// [ActiveIssue("https://github.com/dotnet/runtime/issues/58927")]
483+
throw new SkipTestException("Unstable on Windows 11");
484+
}
485+
481486
bool negotiateClientCertificateCalled = false;
482487
using CancellationTokenSource cts = new CancellationTokenSource();
483488
cts.CancelAfter(TestConfiguration.PassingTestTimeout);

src/native/libs/System.Security.Cryptography.Native/entrypoints.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ static const Entry s_cryptoNative[] =
316316
DllImportEntry(CryptoNative_SslSetAlpnProtos)
317317
DllImportEntry(CryptoNative_SslSetBio)
318318
DllImportEntry(CryptoNative_SslSetClientCertCallback)
319+
DllImportEntry(CryptoNative_SslSetPostHandshakeAuth)
319320
DllImportEntry(CryptoNative_SslSetConnectState)
320321
DllImportEntry(CryptoNative_SslSetData)
321322
DllImportEntry(CryptoNative_SslSetQuietShutdown)

src/native/libs/System.Security.Cryptography.Native/opensslshim.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,9 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void);
517517
REQUIRED_FUNCTION(SSL_write) \
518518
REQUIRED_FUNCTION(SSL_use_certificate) \
519519
REQUIRED_FUNCTION(SSL_use_PrivateKey) \
520+
LIGHTUP_FUNCTION(SSL_verify_client_post_handshake) \
521+
LIGHTUP_FUNCTION(SSL_set_post_handshake_auth) \
522+
REQUIRED_FUNCTION(SSL_version) \
520523
FALLBACK_FUNCTION(X509_check_host) \
521524
REQUIRED_FUNCTION(X509_check_purpose) \
522525
REQUIRED_FUNCTION(X509_cmp_current_time) \
@@ -977,6 +980,9 @@ FOR_ALL_OPENSSL_FUNCTIONS
977980
#define SSL_write SSL_write_ptr
978981
#define SSL_use_certificate SSL_use_certificate_ptr
979982
#define SSL_use_PrivateKey SSL_use_PrivateKey_ptr
983+
#define SSL_verify_client_post_handshake SSL_verify_client_post_handshake_ptr
984+
#define SSL_set_post_handshake_auth SSL_set_post_handshake_auth_ptr
985+
#define SSL_version SSL_version_ptr
980986
#define TLS_method TLS_method_ptr
981987
#define X509_check_host X509_check_host_ptr
982988
#define X509_check_purpose X509_check_purpose_ptr

src/native/libs/System.Security.Cryptography.Native/osslcompat_111.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ unsigned long SSL_CTX_set_options(SSL_CTX* ctx, unsigned long options);
6060
void SSL_CTX_set_security_level(SSL_CTX* ctx, int32_t level);
6161
int32_t SSL_is_init_finished(SSL* ssl);
6262
unsigned long SSL_set_options(SSL* ctx, unsigned long options);
63+
void SSL_set_post_handshake_auth(SSL *s, int val);
6364
int SSL_session_reused(SSL* ssl);
65+
int SSL_verify_client_post_handshake(SSL *s);
6466
const SSL_METHOD* TLS_method(void);
6567
const ASN1_TIME* X509_CRL_get0_nextUpdate(const X509_CRL* crl);
6668
int32_t X509_NAME_get0_der(X509_NAME* x509Name, const uint8_t** pder, size_t* pderlen);

src/native/libs/System.Security.Cryptography.Native/pal_ssl.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,27 @@ static int verify_callback(int preverify_ok, X509_STORE_CTX* store)
411411

412412
int32_t CryptoNative_SslRenegotiate(SSL* ssl, int32_t* error)
413413
{
414+
#ifdef NEED_OPENSSL_1_1
415+
// TLS1.3 uses different API for renegotiation/delayed client cert request
416+
#ifndef TLS1_3_VERSION
417+
#define TLS1_3_VERSION 0x0304
418+
#endif
419+
if (SSL_version(ssl) == TLS1_3_VERSION)
420+
{
421+
// this is just a sanity check, if TLS 1.3 was negotiated, then the function must be available
422+
if (API_EXISTS(SSL_verify_client_post_handshake))
423+
{
424+
// Post-handshake auth reqires SSL_VERIFY_PEER to be set
425+
CryptoNative_SslSetVerifyPeer(ssl);
426+
return SSL_verify_client_post_handshake(ssl);
427+
}
428+
else
429+
{
430+
return 0;
431+
}
432+
}
433+
#endif
434+
414435
// The openssl context is destroyed so we can't use ticket or session resumption.
415436
SSL_set_options(ssl, SSL_OP_NO_TICKET | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
416437

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

757+
void CryptoNative_SslSetPostHandshakeAuth(SSL* ssl, int32_t val)
758+
{
759+
#ifdef NEED_OPENSSL_1_1
760+
if (API_EXISTS(SSL_set_post_handshake_auth))
761+
{
762+
SSL_set_post_handshake_auth(ssl, val);
763+
}
764+
#else
765+
(void)ssl;
766+
(void)val;
767+
#endif
768+
}
769+
736770
int32_t CryptoNative_SslSetData(SSL* ssl, void *ptr)
737771
{
738772
return SSL_set_ex_data(ssl, 0, ptr);

src/native/libs/System.Security.Cryptography.Native/pal_ssl.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ It will unset callback if set is zero.
147147
*/
148148
PALEXPORT void CryptoNative_SslSetClientCertCallback(SSL* ssl, int set);
149149

150+
/*
151+
Requests that client sends Post-Handshake Authentication extension in ClientHello.
152+
*/
153+
PALEXPORT void CryptoNative_SslSetPostHandshakeAuth(SSL* ssl, int32_t val);
154+
150155
/*=======
151156
Sets session caching. 0 is disabled.
152157
*/
@@ -222,9 +227,9 @@ when an error is encountered.
222227
PALEXPORT int32_t CryptoNative_SslRead(SSL* ssl, void* buf, int32_t num, int32_t* error);
223228

224229
/*
225-
Shims the SSL_renegotiate method.
230+
Shims the SSL_renegotiate method (up to TLS 1.2), or SSL_verify_client_post_handshake (TLS 1.3)
226231
227-
Returns 1 when renegotiation started; 0 on error.
232+
Returns 1 when renegotiation/post-handshake authentication started; 0 on error.
228233
*/
229234
PALEXPORT int32_t CryptoNative_SslRenegotiate(SSL* ssl, int32_t* error);
230235

0 commit comments

Comments
 (0)