diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs index 22e7d9bd1e2b62..ed807385c9b574 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs @@ -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); @@ -504,9 +512,16 @@ internal static int Decrypt(SafeSslHandle context, Span 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: diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs index 6ccb1307886f71..18668ca9c76595 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs @@ -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(); diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs index fc083ad5d4b91e..57f0718b20f8b9 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs @@ -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 + // 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; diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs index 21953c34cfd5e3..44dbc87d84b265 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs @@ -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); diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index bcca0693f63fbc..3ee4300de95d18 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -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) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index bad52f66bde0c6..f1b39f2f5020d4 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -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) \ FALLBACK_FUNCTION(X509_check_host) \ REQUIRED_FUNCTION(X509_check_purpose) \ REQUIRED_FUNCTION(X509_cmp_current_time) \ @@ -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 diff --git a/src/native/libs/System.Security.Cryptography.Native/osslcompat_111.h b/src/native/libs/System.Security.Cryptography.Native/osslcompat_111.h index 59f0fc5f59d5df..4ffd26220abb7c 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_111.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_111.h @@ -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); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c index 698e3282c45096..5b5e2028c0203d 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c @@ -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); @@ -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); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.h b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.h index bdbfed127af998..5bb758eeff16ee 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.h @@ -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. */ @@ -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);