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 6980c28aff6c93..b18bb2c15f8393 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 @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -21,7 +22,9 @@ internal static partial class OpenSsl { private const string DisableTlsResumeCtxSwitch = "System.Net.Security.DisableTlsResume"; private const string DisableTlsResumeEnvironmentVariable = "DOTNET_SYSTEM_NET_SECURITY_DISABLETLSRESUME"; + private const SslProtocols FakeAlpnSslProtocol = (SslProtocols)1; // used to distinguish server sessions with ALPN private static readonly IdnMapping s_idnMapping = new IdnMapping(); + private static readonly ConcurrentDictionary s_clientSslContexts = new ConcurrentDictionary(); #region internal methods internal static SafeChannelBindingHandle? QueryChannelBinding(SafeSslHandle context, ChannelBindingKind bindingType) @@ -80,7 +83,7 @@ private static bool DisableTlsResume private static SslProtocols CalculateEffectiveProtocols(SslAuthenticationOptions sslAuthenticationOptions) { // make sure low bit is not set since we use it in context dictionary to distinguish use with ALPN - Debug.Assert(((int)sslAuthenticationOptions.EnabledSslProtocols & 1) == 0); + Debug.Assert((sslAuthenticationOptions.EnabledSslProtocols & FakeAlpnSslProtocol) == 0); SslProtocols protocols = sslAuthenticationOptions.EnabledSslProtocols & ~((SslProtocols)1); if (!Interop.Ssl.Capabilities.Tls13Supported) @@ -127,7 +130,7 @@ private static SslProtocols CalculateEffectiveProtocols(SslAuthenticationOptions } // This essentially wraps SSL_CTX* aka SSL_CTX_new + setting - internal static SafeSslContextHandle AllocateSslContext(SafeFreeSslCredentials credential, SslAuthenticationOptions sslAuthenticationOptions, SslProtocols protocols, bool enableResume) + internal static unsafe SafeSslContextHandle AllocateSslContext(SafeFreeSslCredentials credential, SslAuthenticationOptions sslAuthenticationOptions, SslProtocols protocols, bool enableResume) { SafeX509Handle? certHandle = credential.CertHandle; SafeEvpPKeyHandle? certKeyHandle = credential.CertKeyHandle; @@ -164,16 +167,13 @@ internal static SafeSslContextHandle AllocateSslContext(SafeFreeSslCredentials c Debug.Assert(cipherSuites == null || (cipherSuites.Length >= 1 && cipherSuites[cipherSuites.Length - 1] == 0)); - unsafe + fixed (byte* cipherListStr = cipherList) + fixed (byte* cipherSuitesStr = cipherSuites) { - fixed (byte* cipherListStr = cipherList) - fixed (byte* cipherSuitesStr = cipherSuites) + if (!Ssl.SslCtxSetCiphers(sslCtx, cipherListStr, cipherSuitesStr)) { - if (!Ssl.SslCtxSetCiphers(sslCtx, cipherListStr, cipherSuitesStr)) - { - Crypto.ErrClearError(); - throw new PlatformNotSupportedException(SR.Format(SR.net_ssl_encryptionpolicy_notsupported, sslAuthenticationOptions.EncryptionPolicy)); - } + Crypto.ErrClearError(); + throw new PlatformNotSupportedException(SR.Format(SR.net_ssl_encryptionpolicy_notsupported, sslAuthenticationOptions.EncryptionPolicy)); } } @@ -186,15 +186,28 @@ internal static SafeSslContextHandle AllocateSslContext(SafeFreeSslCredentials c // https://www.openssl.org/docs/manmaster/ssl/SSL_shutdown.html Ssl.SslCtxSetQuietShutdown(sslCtx); - Ssl.SslCtxSetCaching(sslCtx, enableResume ? 1 : 0); - - if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0) + if (enableResume) { - unsafe + if (sslAuthenticationOptions.IsServer) + { + Ssl.SslCtxSetCaching(sslCtx, 1, null, null); + } + else { - Interop.Ssl.SslCtxSetAlpnSelectCb(sslCtx, &AlpnServerSelectCallback, IntPtr.Zero); + int result = Ssl.SslCtxSetCaching(sslCtx, 1, &NewSessionCallback, &RemoveSessionCallback); + Debug.Assert(result == 1); + sslCtx.EnableSessionCache(); } } + else + { + Ssl.SslCtxSetCaching(sslCtx, 0, null, null); + } + + if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0) + { + Interop.Ssl.SslCtxSetAlpnSelectCb(sslCtx, &AlpnServerSelectCallback, IntPtr.Zero); + } bool hasCertificateAndKey = certHandle != null && !certHandle.IsInvalid @@ -269,15 +282,47 @@ internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credentia SafeSslContextHandle? newCtxHandle = null; SslProtocols protocols = CalculateEffectiveProtocols(sslAuthenticationOptions); bool hasAlpn = sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0; - bool cacheSslContext = !DisableTlsResume && sslAuthenticationOptions.EncryptionPolicy == EncryptionPolicy.RequireEncryption && - sslAuthenticationOptions.IsServer && - sslAuthenticationOptions.CertificateContext != null && - sslAuthenticationOptions.CertificateContext.SslContexts != null && - sslAuthenticationOptions.CipherSuitesPolicy == null; + bool cacheSslContext = !DisableTlsResume && sslAuthenticationOptions.EncryptionPolicy == EncryptionPolicy.RequireEncryption && sslAuthenticationOptions.CipherSuitesPolicy == null; if (cacheSslContext) { - sslAuthenticationOptions.CertificateContext!.SslContexts!.TryGetValue(protocols | (SslProtocols)(hasAlpn ? 1 : 0), out sslCtxHandle); + if (sslAuthenticationOptions.IsClient) + { + // We don't support client resume on old OpenSSL versions. + // We don't want to try on empty TargetName since that is our key. + // And we don't want to mess up with client authentication. It may be possible + // but it seems safe to get full new session. + if (!Interop.Ssl.Capabilities.Tls13Supported || + string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) || + sslAuthenticationOptions.CertificateContext != null || + sslAuthenticationOptions.CertSelectionDelegate != null) + { + cacheSslContext = false; + } + } + else + { + // Server should always have certificate + Debug.Assert(sslAuthenticationOptions.CertificateContext != null); + if (sslAuthenticationOptions.CertificateContext == null || + sslAuthenticationOptions.CertificateContext.SslContexts == null) + { + cacheSslContext = false; + } + } + } + + if (cacheSslContext) + { + if (sslAuthenticationOptions.IsServer) + { + sslAuthenticationOptions.CertificateContext!.SslContexts!.TryGetValue(protocols | (hasAlpn ? FakeAlpnSslProtocol : SslProtocols.None), out sslCtxHandle); + } + else + { + + s_clientSslContexts.TryGetValue(protocols, out sslCtxHandle); + } } if (sslCtxHandle == null) @@ -285,9 +330,15 @@ internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credentia // We did not get SslContext from cache sslCtxHandle = newCtxHandle = AllocateSslContext(credential, sslAuthenticationOptions, protocols, cacheSslContext); - if (cacheSslContext && sslAuthenticationOptions.CertificateContext!.SslContexts!.TryAdd(protocols | (SslProtocols)(hasAlpn ? 1 : 0), newCtxHandle)) + if (cacheSslContext) { - newCtxHandle = null; + bool added = sslAuthenticationOptions.IsServer ? + sslAuthenticationOptions.CertificateContext!.SslContexts!.TryAdd(protocols | (SslProtocols)(hasAlpn ? 1 : 0), newCtxHandle) : + s_clientSslContexts.TryAdd(protocols, newCtxHandle); + if (added) + { + newCtxHandle = null; + } } } @@ -306,6 +357,7 @@ internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credentia { if (sslAuthenticationOptions.IsServer) { + Debug.Assert(Interop.Ssl.SslGetData(sslHandle) == IntPtr.Zero); alpnHandle = GCHandle.Alloc(sslAuthenticationOptions.ApplicationProtocols); Interop.Ssl.SslSetData(sslHandle, GCHandle.ToIntPtr(alpnHandle)); sslHandle.AlpnHandle = alpnHandle; @@ -319,7 +371,7 @@ internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credentia } } - if (!sslAuthenticationOptions.IsServer) + if (sslAuthenticationOptions.IsClient) { // The IdnMapping converts unicode input into the IDNA punycode sequence. string punyCode = string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) ? string.Empty : s_idnMapping.GetAscii(sslAuthenticationOptions.TargetHost!); @@ -330,6 +382,11 @@ internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credentia Crypto.ErrClearError(); } + if (cacheSslContext && !string.IsNullOrEmpty(punyCode)) + { + sslCtxHandle.TrySetSession(sslHandle, punyCode); + } + // 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 || @@ -644,6 +701,58 @@ private static unsafe int AlpnServerSelectCallback(IntPtr ssl, byte** outp, byte return Ssl.SSL_TLSEXT_ERR_ALERT_FATAL; } + [UnmanagedCallersOnly] + // Invoked from OpenSSL when new session is created. + // We attached GCHandle to the SSL so we can find back SafeSslContextHandle holding the cache. + // New session has refCount of 1. + // If this function returns 0, OpenSSL will drop the refCount and discard the session. + // If we return 1, the ownership is transfered to us and we will need to call SessionFree(). + private static unsafe int NewSessionCallback(IntPtr ssl, IntPtr session) + { + Debug.Assert(ssl != IntPtr.Zero); + Debug.Assert(session != IntPtr.Zero); + + IntPtr ptr = Ssl.SslGetData(ssl); + Debug.Assert(ptr != IntPtr.Zero); + GCHandle gch = GCHandle.FromIntPtr(ptr); + + SafeSslContextHandle? ctxHandle = gch.Target as SafeSslContextHandle; + // There is no relation between SafeSslContextHandle and SafeSslHandle so the handle + // may be released while the ssl session is still active. + if (ctxHandle != null && ctxHandle.TryAddSession(Ssl.SslGetServerName(ssl), session)) + { + // offered session was stored in our cache. + return 1; + } + + // OpenSSL will destroy session. + return 0; + } + + [UnmanagedCallersOnly] + private static unsafe void RemoveSessionCallback(IntPtr ctx, IntPtr session) + { + Debug.Assert(ctx != IntPtr.Zero && session != IntPtr.Zero); + + IntPtr ptr = Ssl.SslCtxGetData(ctx); + if (ptr == IntPtr.Zero) + { + // Same as above, SafeSslContextHandle could be released while OpenSSL still holds reference. + return; + } + + GCHandle gch = GCHandle.FromIntPtr(ptr); + SafeSslContextHandle? ctxHandle = gch.Target as SafeSslContextHandle; + if (ctxHandle == null) + { + return; + } + + IntPtr name = Ssl.SessionGetHostname(session); + Debug.Assert(name != IntPtr.Zero); + ctxHandle.RemoveSession(name, session); + } + private static int BioRead(SafeBioHandle bio, byte[] buffer, int count) { Debug.Assert(buffer != null); 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 605a85d2736cf8..38e3df8876d62b 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 @@ -57,6 +57,12 @@ internal static partial class Ssl [return: MarshalAs(UnmanagedType.Bool)] internal static partial bool SslSetTlsExtHostName(SafeSslHandle ssl, string host); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGetServerName")] + internal static unsafe partial IntPtr SslGetServerName(IntPtr ssl); + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetSession")] + internal static unsafe partial int SslSetSession(SafeSslHandle ssl, IntPtr session); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGet0AlpnSelected")] internal static partial void SslGetAlpnSelected(SafeSslHandle ssl, out IntPtr protocol, out int len); @@ -146,6 +152,9 @@ internal static partial class Ssl [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGetData")] internal static partial IntPtr SslGetData(IntPtr ssl); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGetData")] + internal static partial IntPtr SslGetData(SafeSslHandle ssl); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetData")] internal static partial int SslSetData(SafeSslHandle ssl, IntPtr data); @@ -167,6 +176,15 @@ internal static partial class Ssl [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_Tls13Supported")] private static partial int Tls13SupportedImpl(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSessionGetHostname")] + internal static partial IntPtr SessionGetHostname(IntPtr session); + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSessionFree")] + internal static partial void SessionFree(IntPtr session); + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSessionSetHostname")] + internal static partial int SessionSetHostname(IntPtr session, IntPtr name); + internal static class Capabilities { // needs separate type (separate static cctor) to be sure OpenSSL is initialized. diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs index e735e68a1cb238..1c36f62bbbe9a1 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtx.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Net.Security; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; @@ -19,11 +20,20 @@ internal static partial class Ssl [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxDestroy")] internal static partial void SslCtxDestroy(IntPtr ctx); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxGetData")] + internal static partial IntPtr SslCtxGetData(IntPtr ctx); + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetData")] + internal static partial int SslCtxSetData(SafeSslContextHandle ctx, IntPtr data); + + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetData")] + internal static partial int SslCtxSetData(IntPtr ctx, IntPtr data); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetAlpnSelectCb")] internal static unsafe partial void SslCtxSetAlpnSelectCb(SafeSslContextHandle ctx, delegate* unmanaged callback, IntPtr arg); [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetCaching")] - internal static unsafe partial void SslCtxSetCaching(SafeSslContextHandle ctx, int mode); + internal static unsafe partial int SslCtxSetCaching(SafeSslContextHandle ctx, int mode, delegate* unmanaged neewSessionCallback, delegate* unmanaged removeSessionCallback); internal static bool AddExtraChainCertificates(SafeSslContextHandle ctx, X509Certificate2[] chain) { @@ -50,6 +60,10 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeSslContextHandle : SafeHandle { + // This is session cache keyed by SNI e.g. TargetHost + private Dictionary? _sslSessions; + private GCHandle _gch; + public SafeSslContextHandle() : base(IntPtr.Zero, true) { @@ -67,9 +81,136 @@ public override bool IsInvalid protected override bool ReleaseHandle() { + if (_sslSessions != null) + { + // The SSL_CTX is ref counted and may not immediately die when we call SslCtxDestroy() + // Since there is no relation between SafeSslContextHandle and SafeSslHandle `this` can be release + // while we still have SSL session using it. + Interop.Ssl.SslCtxSetData(handle, IntPtr.Zero); + + lock (_sslSessions) + { + foreach (IntPtr session in _sslSessions.Values) + { + Interop.Ssl.SessionFree(session); + } + + _sslSessions.Clear(); + } + + Debug.Assert(_gch.IsAllocated); + _gch.Free(); + } + Interop.Ssl.SslCtxDestroy(handle); SetHandle(IntPtr.Zero); + return true; } + + internal void EnableSessionCache() + { + Debug.Assert(_sslSessions == null); + + _sslSessions = new Dictionary(); + _gch = GCHandle.Alloc(this); + Debug.Assert(_gch.IsAllocated); + // This is needed so we can find the handle from session in SessionRemove callback. + Interop.Ssl.SslCtxSetData(this, (IntPtr)_gch); + } + + internal bool TryAddSession(IntPtr namePtr, IntPtr session) + { + Debug.Assert(_sslSessions != null && session != IntPtr.Zero); + + if (_sslSessions == null || namePtr == IntPtr.Zero) + { + return false; + } + + string? targetName = Marshal.PtrToStringAnsi(namePtr); + Debug.Assert(targetName != null); + + if (!string.IsNullOrEmpty(targetName)) + { + // We do this only for lookup in RemoveSession. + // Since this is part of chache manipulation and no function impact it is done here. + // This will use strdup() so it is safe to pass in raw pointer. + Interop.Ssl.SessionSetHostname(session, namePtr); + + lock (_sslSessions) + { + if (!_sslSessions.TryAdd(targetName, session)) + { + if (_sslSessions.Remove(targetName, out IntPtr oldSession)) + { + Interop.Ssl.SessionFree(oldSession); + } + + bool added = _sslSessions.TryAdd(targetName, session); + Debug.Assert(added); + } + } + + return true; + } + + return false; + } + + internal void RemoveSession(IntPtr namePtr, IntPtr session) + { + Debug.Assert(_sslSessions != null); + + string? targetName = Marshal.PtrToStringAnsi(namePtr); + Debug.Assert(targetName != null); + + if (_sslSessions != null && targetName != null) + { + IntPtr oldSession; + bool removed; + lock (_sslSessions) + { + removed = _sslSessions.Remove(targetName, out oldSession); + } + + if (removed) + { + // It seems like we may be called more than once. Since we grabbed only one refference + // when added to Dictionary, we will also drop exactly one when removed. + Interop.Ssl.SessionFree(oldSession); + } + + } + } + + internal bool TrySetSession(SafeSslHandle sslHandle, string name) + { + Debug.Assert(_sslSessions != null); + + if (_sslSessions == null || string.IsNullOrEmpty(name)) + { + return false; + } + + // even if we don't have matching session, we can get new one and we need + // way how to link SSL back to `this`. + Debug.Assert(Interop.Ssl.SslGetData(sslHandle) == IntPtr.Zero); + Interop.Ssl.SslSetData(sslHandle, (IntPtr)_gch); + + lock (_sslSessions) + { + if (_sslSessions.TryGetValue(name, out IntPtr session)) + { + // This will increase reference count on the session as needed. + // We need to hold lock here to prevent session being deleted before the call is done. + Interop.Ssl.SslSetSession(sslHandle, session); + + return true; + } + } + + return false; + } } } diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj index e284895fe234a2..0271d7b3f12d6e 100644 --- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj @@ -311,6 +311,8 @@ Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs" /> + ? ApplicationProtocols { get; set; } internal bool IsServer { get; set; } + internal bool IsClient => !IsServer; internal SslStreamCertificateContext? CertificateContext { get; set; } internal SslProtocols EnabledSslProtocols { get; set; } internal X509RevocationMode CertificateRevocationCheckMode { get; set; } diff --git a/src/native/libs/System.Security.Cryptography.Native/apibridge.c b/src/native/libs/System.Security.Cryptography.Native/apibridge.c index 0a3705321eec4c..73ba3e6cef5d67 100644 --- a/src/native/libs/System.Security.Cryptography.Native/apibridge.c +++ b/src/native/libs/System.Security.Cryptography.Native/apibridge.c @@ -889,5 +889,4 @@ int local_EVP_PKEY_public_check(EVP_PKEY_CTX* ctx) return -1; } } - #endif diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index 1951011467a0b9..10080d25b316c6 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -293,7 +293,9 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_SslCtxCheckPrivateKey) DllImportEntry(CryptoNative_SslCtxCreate) DllImportEntry(CryptoNative_SslCtxDestroy) + DllImportEntry(CryptoNative_SslCtxGetData) DllImportEntry(CryptoNative_SslCtxSetAlpnSelectCb) + DllImportEntry(CryptoNative_SslCtxSetData) DllImportEntry(CryptoNative_SslCtxSetProtocolOptions) DllImportEntry(CryptoNative_SslCtxSetQuietShutdown) DllImportEntry(CryptoNative_SslCtxUseCertificate) @@ -310,8 +312,12 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_SslGetPeerCertChain) DllImportEntry(CryptoNative_SslGetPeerCertificate) DllImportEntry(CryptoNative_SslGetPeerFinished) + DllImportEntry(CryptoNative_SslGetServerName) DllImportEntry(CryptoNative_SslGetVersion) DllImportEntry(CryptoNative_SslRead) + DllImportEntry(CryptoNative_SslSessionFree) + DllImportEntry(CryptoNative_SslSessionGetHostname) + DllImportEntry(CryptoNative_SslSessionSetHostname) DllImportEntry(CryptoNative_SslSessionReused) DllImportEntry(CryptoNative_SslSetAcceptState) DllImportEntry(CryptoNative_SslSetAlpnProtos) @@ -321,6 +327,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_SslSetConnectState) DllImportEntry(CryptoNative_SslSetData) DllImportEntry(CryptoNative_SslSetQuietShutdown) + DllImportEntry(CryptoNative_SslSetSession) DllImportEntry(CryptoNative_SslSetTlsExtHostName) DllImportEntry(CryptoNative_SslSetVerifyPeer) DllImportEntry(CryptoNative_SslShutdown) diff --git a/src/native/libs/System.Security.Cryptography.Native/openssl_1_0_structs.h b/src/native/libs/System.Security.Cryptography.Native/openssl_1_0_structs.h index f730dd39387ffa..83942449bb3af6 100644 --- a/src/native/libs/System.Security.Cryptography.Native/openssl_1_0_structs.h +++ b/src/native/libs/System.Security.Cryptography.Native/openssl_1_0_structs.h @@ -169,7 +169,8 @@ struct x509_store_st X509_VERIFY_PARAM* param; }; -struct bio_st { +struct bio_st +{ const void* _ignored1; const void* _ignored2; const void* _ignored3; diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index 6f60a3c78900eb..a6cfc259623bb1 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -467,13 +467,17 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); FALLBACK_FUNCTION(SSL_CTX_config) \ REQUIRED_FUNCTION(SSL_CTX_ctrl) \ REQUIRED_FUNCTION(SSL_CTX_free) \ + REQUIRED_FUNCTION(SSL_CTX_get_ex_data) \ FALLBACK_FUNCTION(SSL_is_init_finished) \ REQUIRED_FUNCTION(SSL_CTX_new) \ + REQUIRED_FUNCTION(SSL_CTX_sess_set_new_cb) \ + REQUIRED_FUNCTION(SSL_CTX_sess_set_remove_cb) \ LIGHTUP_FUNCTION(SSL_CTX_set_alpn_protos) \ LIGHTUP_FUNCTION(SSL_CTX_set_alpn_select_cb) \ REQUIRED_FUNCTION(SSL_CTX_set_cipher_list) \ LIGHTUP_FUNCTION(SSL_CTX_set_ciphersuites) \ REQUIRED_FUNCTION(SSL_CTX_set_client_cert_cb) \ + REQUIRED_FUNCTION(SSL_CTX_set_ex_data) \ REQUIRED_FUNCTION(SSL_CTX_set_quiet_shutdown) \ FALLBACK_FUNCTION(SSL_CTX_set_options) \ FALLBACK_FUNCTION(SSL_CTX_set_security_level) \ @@ -490,6 +494,7 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(SSL_get_finished) \ REQUIRED_FUNCTION(SSL_get_peer_cert_chain) \ REQUIRED_FUNCTION(SSL_get_peer_finished) \ + REQUIRED_FUNCTION(SSL_get_servername) \ REQUIRED_FUNCTION(SSL_get_SSL_CTX) \ REQUIRED_FUNCTION(SSL_get_version) \ LIGHTUP_FUNCTION(SSL_get0_alpn_selected) \ @@ -501,6 +506,9 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(SSL_read) \ REQUIRED_FUNCTION(SSL_renegotiate) \ REQUIRED_FUNCTION(SSL_renegotiate_pending) \ + REQUIRED_FUNCTION(SSL_SESSION_free) \ + LIGHTUP_FUNCTION(SSL_SESSION_get0_hostname) \ + LIGHTUP_FUNCTION(SSL_SESSION_set1_hostname) \ FALLBACK_FUNCTION(SSL_session_reused) \ REQUIRED_FUNCTION(SSL_set_accept_state) \ REQUIRED_FUNCTION(SSL_set_bio) \ @@ -510,6 +518,7 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(SSL_set_connect_state) \ REQUIRED_FUNCTION(SSL_set_ex_data) \ FALLBACK_FUNCTION(SSL_set_options) \ + REQUIRED_FUNCTION(SSL_set_session) \ REQUIRED_FUNCTION(SSL_set_verify) \ REQUIRED_FUNCTION(SSL_shutdown) \ LEGACY_FUNCTION(SSL_state) \ @@ -931,12 +940,16 @@ FOR_ALL_OPENSSL_FUNCTIONS #define SSL_CTX_config SSL_CTX_config_ptr #define SSL_CTX_ctrl SSL_CTX_ctrl_ptr #define SSL_CTX_free SSL_CTX_free_ptr +#define SSL_CTX_get_ex_data SSL_CTX_get_ex_data_ptr #define SSL_CTX_new SSL_CTX_new_ptr +#define SSL_CTX_sess_set_new_cb SSL_CTX_sess_set_new_cb_ptr +#define SSL_CTX_sess_set_remove_cb SSL_CTX_sess_set_remove_cb_ptr #define SSL_CTX_set_alpn_protos SSL_CTX_set_alpn_protos_ptr #define SSL_CTX_set_alpn_select_cb SSL_CTX_set_alpn_select_cb_ptr #define SSL_CTX_set_cipher_list SSL_CTX_set_cipher_list_ptr #define SSL_CTX_set_ciphersuites SSL_CTX_set_ciphersuites_ptr #define SSL_CTX_set_client_cert_cb SSL_CTX_set_client_cert_cb_ptr +#define SSL_CTX_set_ex_data SSL_CTX_set_ex_data_ptr #define SSL_CTX_set_options SSL_CTX_set_options_ptr #define SSL_CTX_set_quiet_shutdown SSL_CTX_set_quiet_shutdown_ptr #define SSL_CTX_set_security_level SSL_CTX_set_security_level_ptr @@ -953,6 +966,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #define SSL_get_finished SSL_get_finished_ptr #define SSL_get_peer_cert_chain SSL_get_peer_cert_chain_ptr #define SSL_get_peer_finished SSL_get_peer_finished_ptr +#define SSL_get_servername SSL_get_servername_ptr #define SSL_get_SSL_CTX SSL_get_SSL_CTX_ptr #define SSL_get_version SSL_get_version_ptr #define SSL_get0_alpn_selected SSL_get0_alpn_selected_ptr @@ -966,6 +980,9 @@ FOR_ALL_OPENSSL_FUNCTIONS #define SSL_read SSL_read_ptr #define SSL_renegotiate SSL_renegotiate_ptr #define SSL_renegotiate_pending SSL_renegotiate_pending_ptr +#define SSL_SESSION_free SSL_SESSION_free_ptr +#define SSL_SESSION_get0_hostname SSL_SESSION_get0_hostname_ptr +#define SSL_SESSION_set1_hostname SSL_SESSION_set1_hostname_ptr #define SSL_session_reused SSL_session_reused_ptr #define SSL_set_accept_state SSL_set_accept_state_ptr #define SSL_set_bio SSL_set_bio_ptr @@ -975,6 +992,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #define SSL_set_connect_state SSL_set_connect_state_ptr #define SSL_set_ex_data SSL_set_ex_data_ptr #define SSL_set_options SSL_set_options_ptr +#define SSL_set_session SSL_set_session_ptr #define SSL_set_verify SSL_set_verify_ptr #define SSL_shutdown SSL_shutdown_ptr #define SSL_state SSL_state_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 4ffd26220abb7c..831a0a98caf363 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_111.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_111.h @@ -81,6 +81,8 @@ int32_t X509_get_version(const X509* x509); int X509_set1_notAfter(X509* x509, const ASN1_TIME*); int X509_set1_notBefore(X509* x509, const ASN1_TIME*); int32_t X509_up_ref(X509* x509); +const char *SSL_SESSION_get0_hostname(const SSL_SESSION *s); +int SSL_SESSION_set1_hostname(SSL_SESSION *s, const char *hostname); #if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_0_2_RTM int32_t X509_check_host(X509* x509, const char* name, size_t namelen, unsigned int flags, char** peername); 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 ead2acea2bb6a9..6b849919df9ffd 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c @@ -576,8 +576,21 @@ void CryptoNative_SslSetVerifyPeer(SSL* ssl) SSL_set_verify(ssl, SSL_VERIFY_PEER, verify_callback); } -void CryptoNative_SslCtxSetCaching(SSL_CTX* ctx, int mode) +int CryptoNative_SslCtxSetCaching(SSL_CTX* ctx, int mode, SslCtxNewSessionCallback newSessionCb, SslCtxRemoveSessionCallback removeSessionCb) { + int retValue = 1; + if (mode && !API_EXISTS(SSL_SESSION_get0_hostname)) + { + // Disable caching on old OpenSSL. + // While TLS resume is optional, none of this is critical. + mode = 0; + + if (newSessionCb != NULL || removeSessionCb != NULL) + { + // Indicate unwillingness to restore sessions + retValue = 0; + } + } // void shim functions don't lead to exceptions, so skip the unconditional error clearing. // We never reuse same CTX for both client and server @@ -586,6 +599,60 @@ void CryptoNative_SslCtxSetCaching(SSL_CTX* ctx, int mode) { SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET); } + + if (newSessionCb != NULL) + { + SSL_CTX_sess_set_new_cb(ctx, newSessionCb); + } + + if (removeSessionCb != NULL) + { + SSL_CTX_sess_set_remove_cb(ctx, removeSessionCb); + } + + return retValue; +} + +const char* CryptoNative_SslGetServerName(SSL* ssl) +{ + return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); +} + +int32_t CryptoNative_SslSetSession(SSL* ssl, SSL_SESSION* session) +{ + return SSL_set_session(ssl, session); +} + +void CryptoNative_SslSessionFree(SSL_SESSION* session) +{ + SSL_SESSION_free(session); +} + +const char* CryptoNative_SslSessionGetHostname(SSL_SESSION* session) +{ +#if defined NEED_OPENSSL_1_1 || defined NEED_OPENSSL_3_0 + if (API_EXISTS(SSL_SESSION_get0_hostname)) + { + return SSL_SESSION_get0_hostname(session); + } +#else + (void*)session; +#endif + return NULL; +} + +int CryptoNative_SslSessionSetHostname(SSL_SESSION* session, const char* hostname) +{ +#if defined NEED_OPENSSL_1_1 || defined NEED_OPENSSL_3_0 + if (API_EXISTS(SSL_SESSION_set1_hostname)) + { + SSL_SESSION_set1_hostname(session, hostname); + } +#else + (void*)session; + (const void*)hostname; +#endif + return 0; } int32_t CryptoNative_SslCtxSetEncryptionPolicy(SSL_CTX* ctx, EncryptionPolicy policy) @@ -808,7 +875,7 @@ void CryptoNative_SslCtxSetAlpnSelectCb(SSL_CTX* ctx, SslCtxSetAlpnCallback cb, #endif } -static int client_certificate_cb(SSL *ssl, void* state) +static int client_certificate_cb(SSL* ssl, void* state) { (void*)ssl; (void*)state; @@ -836,7 +903,7 @@ void CryptoNative_SslSetPostHandshakeAuth(SSL* ssl, int32_t val) #endif } -int32_t CryptoNative_SslSetData(SSL* ssl, void *ptr) +int32_t CryptoNative_SslSetData(SSL* ssl, void* ptr) { ERR_clear_error(); return SSL_set_ex_data(ssl, 0, ptr); @@ -848,6 +915,16 @@ void* CryptoNative_SslGetData(SSL* ssl) return SSL_get_ex_data(ssl, 0); } +int32_t CryptoNative_SslCtxSetData(SSL_CTX* ctx, void* ptr) +{ + return SSL_CTX_set_ex_data(ctx, 0, ptr); +} + +void* CryptoNative_SslCtxGetData(SSL_CTX* ctx) +{ + return SSL_CTX_get_ex_data(ctx, 0); +} + int32_t CryptoNative_SslSetAlpnProtos(SSL* ssl, const uint8_t* protos, uint32_t protos_len) { ERR_clear_error(); @@ -912,7 +989,7 @@ int32_t CryptoNative_SslGetCurrentCipherId(SSL* ssl, int32_t* cipherId) } // This function generates key pair and creates simple certificate. -static int MakeSelfSignedCertificate(X509 * cert, EVP_PKEY* evp) +static int MakeSelfSignedCertificate(X509* cert, EVP_PKEY* evp) { RSA* rsa = NULL; ASN1_TIME* time = ASN1_TIME_new(); 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 522c170e7cf073..e88eb18444854e 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.h @@ -117,6 +117,13 @@ typedef int32_t (*SslCtxSetAlpnCallback)(SSL* ssl, const uint8_t* in, uint32_t inlen, void* arg); + +// the function pointer used for new session +typedef int32_t (*SslCtxNewSessionCallback)(SSL* ssl, SSL_SESSION* sesssion); + +// the function pointer used for new session +typedef void (*SslCtxRemoveSessionCallback)(SSL_CTX* ctx, SSL_SESSION* sesssion); + /* Ensures that libssl is correctly initialized and ready to use. */ @@ -152,10 +159,36 @@ Requests that client sends Post-Handshake Authentication extension in ClientHell */ PALEXPORT void CryptoNative_SslSetPostHandshakeAuth(SSL* ssl, int32_t val); -/*======= +/* Sets session caching. 0 is disabled. */ -PALEXPORT void CryptoNative_SslCtxSetCaching(SSL_CTX* ctx, int mode); +PALEXPORT int CryptoNative_SslCtxSetCaching(SSL_CTX* ctx, int mode, SslCtxNewSessionCallback newCb, SslCtxRemoveSessionCallback removeCb); + +/* +Returns name associated with given ssl session. +OpenSSL holds reference to it and it must not be freed. +*/ +PALEXPORT const char* CryptoNative_SslGetServerName(SSL* ssl); + +/* +This function will attach existing ssl session for possible TLS resume. +*/ +PALEXPORT int32_t CryptoNative_SslSetSession(SSL* ssl, SSL_SESSION* session); + +/* + * Frees SSL session. + */ +PALEXPORT void CryptoNative_SslSessionFree(SSL_SESSION* session); + +/* + * Get name associated with given SSL_SESSION. + */ +PALEXPORT const char* CryptoNative_SslSessionGetHostname(SSL_SESSION* session); + +/* + * Associate name with given SSL_SESSION. + */ +PALEXPORT int CryptoNative_SslSessionSetHostname(SSL_SESSION* session, const char* hostname); /* Shims the SSL_new method. @@ -332,7 +365,7 @@ PALEXPORT void CryptoNative_SslCtxSetQuietShutdown(SSL_CTX* ctx); /* Shims the SSL_set_quiet_shutdown method. */ -PALEXPORT void CryptoNative_SslSetQuietShutdown(SSL* ctx, int mode); +PALEXPORT void CryptoNative_SslSetQuietShutdown(SSL* ssl, int mode); /* Shims the SSL_get_client_CA_list method. @@ -349,13 +382,23 @@ PALEXPORT void CryptoNative_SslSetVerifyPeer(SSL* ssl); /* Shims SSL_set_ex_data to attach application context. */ -PALEXPORT int32_t CryptoNative_SslSetData(SSL* ssl, void *ptr); +PALEXPORT int32_t CryptoNative_SslSetData(SSL* ssl, void* ptr); /* Shims SSL_get_ex_data to retrieve application context. */ PALEXPORT void* CryptoNative_SslGetData(SSL* ssl); +/* +Shims SSL_CTX_set_ex_data to attach application context. +*/ +PALEXPORT int32_t CryptoNative_SslCtxSetData(SSL_CTX* ctx, void* ptr); + +/* +Shims SSL_CTX_get_ex_data to retrieve application context. +*/ +PALEXPORT void* CryptoNative_SslCtxGetData(SSL_CTX* ctx); + /* Sets the specified encryption policy on the SSL_CTX. @@ -417,7 +460,7 @@ PALEXPORT int32_t CryptoNative_SslAddClientCAs(SSL* ssl, X509** x509s, uint32_t /* Shims the ssl_ctx_set_alpn_select_cb method. */ -PALEXPORT void CryptoNative_SslCtxSetAlpnSelectCb(SSL_CTX* ctx, SslCtxSetAlpnCallback cb, void *arg); +PALEXPORT void CryptoNative_SslCtxSetAlpnSelectCb(SSL_CTX* ctx, SslCtxSetAlpnCallback cb, void* arg); /* Shims the ssl_set_alpn_protos method.