diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs index 2e6e72c9418b4..af6596efd51da 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs @@ -46,6 +46,7 @@ internal enum PAL_TlsHandshakeState WouldBlock, ServerAuthCompleted, ClientAuthCompleted, + ClientCertRequested, } internal enum PAL_TlsIo @@ -99,6 +100,12 @@ private static partial int AppleCryptoNative_SslSetBreakOnClientAuth( int setBreak, out int pOSStatus); + [GeneratedDllImport(Interop.Libraries.AppleCryptoNative)] + private static partial int AppleCryptoNative_SslSetBreakOnCertRequested( + SafeSslHandle sslHandle, + int setBreak, + out int pOSStatus); + [GeneratedDllImport(Interop.Libraries.AppleCryptoNative)] private static partial int AppleCryptoNative_SslSetCertificate( SafeSslHandle sslHandle, @@ -266,6 +273,25 @@ internal static void SslBreakOnClientAuth(SafeSslHandle sslHandle, bool setBreak throw new SslException(); } + internal static void SslBreakOnCertRequested(SafeSslHandle sslHandle, bool setBreak) + { + int osStatus; + int result = AppleCryptoNative_SslSetBreakOnCertRequested(sslHandle, setBreak ? 1 : 0, out osStatus); + + if (result == 1) + { + return; + } + + if (result == 0) + { + throw CreateExceptionForOSStatus(osStatus); + } + + Debug.Fail($"AppleCryptoNative_SslSetBreakOnCertRequested returned {result}"); + throw new SslException(); + } + internal static void SslSetCertificate(SafeSslHandle sslHandle, IntPtr[] certChainPtrs) { using (SafeCreateHandle cfCertRefs = CoreFoundation.CFArrayCreate(certChainPtrs, (UIntPtr)certChainPtrs.Length)) 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 6e7701f57ae57..d403daac342ad 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 @@ -219,6 +219,45 @@ internal static SafeSslContextHandle AllocateSslContext(SafeFreeSslCredentials c return sslCtx; } + internal static void UpdateClientCertiticate(SafeSslHandle ssl, SslAuthenticationOptions sslAuthenticationOptions) + { + // Disable certificate selection callback. We either got certificate or we will try to proceed without it. + Interop.Ssl.SslSetClientCertCallback(ssl, 0); + + if (sslAuthenticationOptions.CertificateContext == null) + { + return; + } + + var credential = new SafeFreeSslCredentials(sslAuthenticationOptions.CertificateContext, sslAuthenticationOptions.EnabledSslProtocols, sslAuthenticationOptions.EncryptionPolicy, sslAuthenticationOptions.IsServer); + SafeX509Handle? certHandle = credential.CertHandle; + SafeEvpPKeyHandle? certKeyHandle = credential.CertKeyHandle; + + Debug.Assert(certHandle != null); + Debug.Assert(certKeyHandle != null); + + int retVal = Ssl.SslUseCertificate(ssl, certHandle); + if (1 != retVal) + { + throw CreateSslException(SR.net_ssl_use_cert_failed); + } + + retVal = Ssl.SslUsePrivateKey(ssl, certKeyHandle); + if (1 != retVal) + { + throw CreateSslException(SR.net_ssl_use_private_key_failed); + } + + if (sslAuthenticationOptions.CertificateContext.IntermediateCertificates.Length > 0) + { + if (!Ssl.AddExtraChainCertificates(ssl, sslAuthenticationOptions.CertificateContext.IntermediateCertificates)) + { + throw CreateSslException(SR.net_ssl_use_cert_failed); + } + } + + } + // This essentially wraps SSL* SSL_new() internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credential, SslAuthenticationOptions sslAuthenticationOptions) { @@ -287,6 +326,13 @@ internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credentia { Crypto.ErrClearError(); } + + if (sslAuthenticationOptions.CertSelectionDelegate != null && sslAuthenticationOptions.CertificateContext == null) + { + // We don't have certificate but we have callback. We should wait for remote certificate and + // possible trusted issuer list. + Interop.Ssl.SslSetClientCertCallback(sslHandle, 1); + } } if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.RemoteCertRequired) @@ -324,7 +370,7 @@ internal static SecurityStatusPal SslRenegotiate(SafeSslHandle sslContext, out b return new SecurityStatusPal(SecurityStatusPalErrorCode.OK); } - internal static bool DoSslHandshake(SafeSslHandle context, ReadOnlySpan input, out byte[]? sendBuf, out int sendCount) + internal static SecurityStatusPalErrorCode DoSslHandshake(SafeSslHandle context, ReadOnlySpan input, out byte[]? sendBuf, out int sendCount) { sendBuf = null; sendCount = 0; @@ -345,6 +391,11 @@ internal static bool DoSslHandshake(SafeSslHandle context, ReadOnlySpan in Exception? innerError; Ssl.SslErrorCode error = GetSslError(context, retVal, out innerError); + if (error == Ssl.SslErrorCode.SSL_ERROR_WANT_X509_LOOKUP) + { + return SecurityStatusPalErrorCode.CredentialsNeeded; + } + if ((retVal != -1) || (error != Ssl.SslErrorCode.SSL_ERROR_WANT_READ)) { // Handshake failed, but even if the handshake does not need to read, there may be an Alert going out. @@ -389,7 +440,8 @@ internal static bool DoSslHandshake(SafeSslHandle context, ReadOnlySpan in { context.MarkHandshakeCompleted(); } - return stateOk; + + return stateOk ? SecurityStatusPalErrorCode.OK : SecurityStatusPalErrorCode.ContinueNeeded; } internal static int Encrypt(SafeSslHandle context, ReadOnlySpan input, ref byte[] output, out Ssl.SslErrorCode errorCode) 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 8d1cad3d480fd..c01b829cd189d 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 @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Net.Security; using System.Runtime.InteropServices; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Microsoft.Win32.SafeHandles; @@ -149,6 +150,15 @@ 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); + + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslUsePrivateKey")] + internal static extern 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_Tls13Supported")] private static partial int Tls13SupportedImpl(); @@ -192,6 +202,28 @@ internal static byte[] ConvertAlpnProtocolListToByteArray(List(); + } + long size = Interop.CoreFoundation.CFArrayGetCount(dnArray); if (size == 0) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs index b75ee2bee7475..3690837e33f67 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs @@ -316,7 +316,7 @@ private static void SetProtocols(SafeSslHandle sslContext, SslProtocols protocol Interop.AppleCrypto.SslSetMaxProtocolVersion(sslContext, maxProtocolId); } - private static void SetCertificate(SafeSslHandle sslContext, SslStreamCertificateContext context) + internal static void SetCertificate(SafeSslHandle sslContext, SslStreamCertificateContext context) { Debug.Assert(sslContext != null, "sslContext != null"); diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs index 0414082781c1e..7043b7afe19f4 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs @@ -246,6 +246,13 @@ private static SecurityStatusPal HandshakeInternal( Interop.AppleCrypto.SslSetTargetName(sslContext.SslContext, sslAuthenticationOptions.TargetHost); } + if (sslAuthenticationOptions.CertificateContext == null && sslAuthenticationOptions.CertSelectionDelegate != null) + { + // certificate was not provided but there is user callback. We can break handshake if server asks for certificate + // and we can try to get it based on remote certificate and trusted issuers. + Interop.AppleCrypto.SslBreakOnCertRequested(sslContext.SslContext, true); + } + if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.RemoteCertRequired) { Interop.AppleCrypto.SslSetAcceptClientCert(sslContext.SslContext); @@ -259,6 +266,35 @@ private static SecurityStatusPal HandshakeInternal( SafeSslHandle sslHandle = sslContext!.SslContext; SecurityStatusPal status = PerformHandshake(sslHandle); + if (status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded) + { + // we should not be here if CertSelectionDelegate is null but better check before dereferencing.. + if (sslAuthenticationOptions.CertSelectionDelegate != null) + { + X509Certificate2? remoteCert = null; + try + { + string[] issuers = CertificateValidationPal.GetRequestCertificateAuthorities(context); + remoteCert = CertificateValidationPal.GetRemoteCertificate(context); + if (sslAuthenticationOptions.ClientCertificates == null) + { + sslAuthenticationOptions.ClientCertificates = new X509CertificateCollection(); + } + X509Certificate2 clientCertificate = (X509Certificate2)sslAuthenticationOptions.CertSelectionDelegate(sslAuthenticationOptions.TargetHost!, sslAuthenticationOptions.ClientCertificates, remoteCert, issuers); + if (clientCertificate != null) + { + SafeDeleteSslContext.SetCertificate(sslContext.SslContext, SslStreamCertificateContext.Create(clientCertificate)); + } + } + finally + { + remoteCert?.Dispose(); + } + } + + // We either got certificate or we can proceed without it. It is up to the server to decide if either is OK. + status = PerformHandshake(sslHandle); + } outputBuffer = sslContext.ReadPendingWrites(); return status; @@ -290,6 +326,8 @@ private static SecurityStatusPal PerformHandshake(SafeSslHandle sslHandle) // So, call SslHandshake again to indicate to Secure Transport that we've // accepted this handshake and it should go into the ready state. break; + case PAL_TlsHandshakeState.ClientCertRequested: + return new SecurityStatusPal(SecurityStatusPalErrorCode.CredentialsNeeded); default: return new SecurityStatusPal( SecurityStatusPalErrorCode.InternalError, diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs index 8f8d4221be761..f4c4c5424e18e 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs @@ -87,6 +87,7 @@ private static SecurityStatusPal MapNativeErrorCode(Interop.Ssl.SslErrorCode err { Interop.Ssl.SslErrorCode.SSL_ERROR_RENEGOTIATE => new SecurityStatusPal(SecurityStatusPalErrorCode.Renegotiate), Interop.Ssl.SslErrorCode.SSL_ERROR_ZERO_RETURN => new SecurityStatusPal(SecurityStatusPalErrorCode.ContextExpired), + Interop.Ssl.SslErrorCode.SSL_ERROR_WANT_X509_LOOKUP => new SecurityStatusPal(SecurityStatusPalErrorCode.CredentialsNeeded), Interop.Ssl.SslErrorCode.SSL_ERROR_NONE or Interop.Ssl.SslErrorCode.SSL_ERROR_WANT_READ => new SecurityStatusPal(SecurityStatusPalErrorCode.OK), _ => new SecurityStatusPal(SecurityStatusPalErrorCode.InternalError, new Interop.OpenSsl.SslException((int)errorCode)) @@ -158,12 +159,42 @@ private static SecurityStatusPal HandshakeInternal(SafeFreeCredentials credentia context = new SafeDeleteSslContext((credential as SafeFreeSslCredentials)!, sslAuthenticationOptions); } - bool done = Interop.OpenSsl.DoSslHandshake(((SafeDeleteSslContext)context).SslContext, inputBuffer, out output, out outputSize); + SecurityStatusPalErrorCode errorCode = Interop.OpenSsl.DoSslHandshake(((SafeDeleteSslContext)context).SslContext, inputBuffer, out output, out outputSize); + + if (errorCode == SecurityStatusPalErrorCode.CredentialsNeeded) + { + if (sslAuthenticationOptions.CertSelectionDelegate != null) + { + X509Certificate2? remoteCert = null; + string[] issuers = CertificateValidationPal.GetRequestCertificateAuthorities(context); + try + { + remoteCert = CertificateValidationPal.GetRemoteCertificate(context); + if (sslAuthenticationOptions.ClientCertificates == null) + { + sslAuthenticationOptions.ClientCertificates = new X509CertificateCollection(); + } + X509Certificate2 clientCertificate = (X509Certificate2)sslAuthenticationOptions.CertSelectionDelegate(sslAuthenticationOptions.TargetHost!, sslAuthenticationOptions.ClientCertificates, remoteCert, issuers); + if (clientCertificate != null && clientCertificate.HasPrivateKey) + { + sslAuthenticationOptions.CertificateContext = SslStreamCertificateContext.Create(clientCertificate); + } + } + finally + { + remoteCert?.Dispose(); + } + } + + Interop.OpenSsl.UpdateClientCertiticate(((SafeDeleteSslContext)context).SslContext, sslAuthenticationOptions); + errorCode = Interop.OpenSsl.DoSslHandshake(((SafeDeleteSslContext)context).SslContext, null, out output, out outputSize); + } + // sometimes during renegotiation processing messgae does not yield new output. // That seems to be flaw in OpenSSL state machine and we have workaround to peek it and try it again. if (outputSize == 0 && Interop.Ssl.IsSslRenegotiatePending(((SafeDeleteSslContext)context).SslContext)) { - done = Interop.OpenSsl.DoSslHandshake(((SafeDeleteSslContext)context).SslContext, ReadOnlySpan.Empty, out output, out outputSize); + errorCode = Interop.OpenSsl.DoSslHandshake(((SafeDeleteSslContext)context).SslContext, ReadOnlySpan.Empty, out output, out outputSize); } // When the handshake is done, and the context is server, check if the alpnHandle target was set to null during ALPN. @@ -171,7 +202,7 @@ private static SecurityStatusPal HandshakeInternal(SafeFreeCredentials credentia // We have this workaround, as openssl supports terminating handshake only from version 1.1.0, // whereas ALPN is supported from version 1.0.2. SafeSslHandle sslContext = context.SslContext; - if (done && sslAuthenticationOptions.IsServer + if (errorCode == SecurityStatusPalErrorCode.OK && sslAuthenticationOptions.IsServer && sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0 && sslContext.AlpnHandle.IsAllocated && sslContext.AlpnHandle.Target == null) { @@ -183,7 +214,7 @@ private static SecurityStatusPal HandshakeInternal(SafeFreeCredentials credentia outputSize == output!.Length ? output : new Span(output, 0, outputSize).ToArray(); - return new SecurityStatusPal(done ? SecurityStatusPalErrorCode.OK : SecurityStatusPalErrorCode.ContinueNeeded); + return new SecurityStatusPal(errorCode); } catch (Exception exc) { diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs index a605c859b4051..5ca6a63e0d4ac 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs @@ -3,6 +3,7 @@ using System.Net.Sockets; using System.Net.Test.Common; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; @@ -34,6 +35,80 @@ public void Dispose() _clientCertificate.Dispose(); } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public async Task CertificateSelectionCallback_DelayedCertificate_OK(bool delayCertificate, bool sendClientCertificate) + { + X509Certificate? remoteCertificate = null; + + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + { + int count = 0; + SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions(); + clientOptions.TargetHost = "localhost"; + // Force Tls 1.2 to avoid issues with certain OpenSSL versions and Tls 1.3 + // https://github.com/openssl/openssl/issues/7384 + clientOptions.EnabledSslProtocols = SslProtocols.Tls12; + clientOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; + clientOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, certificate, acceptableIssuers) => + { + count++; + remoteCertificate = certificate; + if (delayCertificate && count == 1) + { + // wait until we get remote certificate from peer e.g. handshake started. + return null; + } + + return sendClientCertificate ? _clientCertificate : null; + }; + + SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions(); + serverOptions.ServerCertificate = _serverCertificate; + serverOptions.ClientCertificateRequired = true; + serverOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => + { + if (sendClientCertificate) + { + Assert.NotNull(certificate); + // The client chain may be incomplete. + Assert.True(sslPolicyErrors == SslPolicyErrors.None || sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors); + } + else + { + Assert.Equal(SslPolicyErrors.RemoteCertificateNotAvailable, sslPolicyErrors); + } + + return true; + }; + + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions), + server.AuthenticateAsServerAsync(serverOptions)); + + // verify that the session is usable with or without client's certificate + await TestHelper.PingPong(client, server); + await TestHelper.PingPong(server, client); + + if (delayCertificate) + { + // LocalCertificateSelectionCallback should be called with real remote certificate. + if (!OperatingSystem.IsWindows()) + { + // remote certificate does not work on windows. + // https://github.com/dotnet/runtime/issues/63321 + Assert.NotNull(remoteCertificate); + } + } + } + } + [Theory] [InlineData(false)] [InlineData(true)] diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c index 78b451a781f53..875ce80316562 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c @@ -56,6 +56,7 @@ static const Entry s_cryptoAppleNative[] = DllImportEntry(AppleCryptoNative_SslCopyCertChain) DllImportEntry(AppleCryptoNative_SslIsHostnameMatch) DllImportEntry(AppleCryptoNative_SslRead) + DllImportEntry(AppleCryptoNative_SslSetBreakOnCertRequested) DllImportEntry(AppleCryptoNative_SslSetBreakOnClientAuth) DllImportEntry(AppleCryptoNative_SslSetBreakOnServerAuth) DllImportEntry(AppleCryptoNative_SslSetIoCallbacks) diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.c b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.c index aff6eb8b25081..7a8a0eebe1317 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.c +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.c @@ -156,6 +156,14 @@ int32_t AppleCryptoNative_SslSetBreakOnServerAuth(SSLContextRef sslContext, int3 #pragma clang diagnostic pop } +int32_t AppleCryptoNative_SslSetBreakOnCertRequested(SSLContextRef sslContext, int32_t setBreak, int32_t* pOSStatus) +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return SslSetSessionOption(sslContext, kSSLSessionOptionBreakOnCertRequested, setBreak, pOSStatus); +#pragma clang diagnostic pop +} + int32_t AppleCryptoNative_SslSetBreakOnClientAuth(SSLContextRef sslContext, int32_t setBreak, int32_t* pOSStatus) { #pragma clang diagnostic push @@ -275,6 +283,8 @@ PAL_TlsHandshakeState AppleCryptoNative_SslHandshake(SSLContextRef sslContext) return PAL_TlsHandshakeState_WouldBlock; case errSSLServerAuthCompleted: return PAL_TlsHandshakeState_ServerAuthCompleted; + case errSSLClientCertRequested: + return PAL_TlsHandshakeState_ClientCertRequested; default: return osStatus; } diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.h b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.h index 8a992bb9a4bc9..2c5961a72ac09 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.h +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.h @@ -15,6 +15,7 @@ enum PAL_TlsHandshakeState_WouldBlock = 2, PAL_TlsHandshakeState_ServerAuthCompleted = 3, PAL_TlsHandshakeState_ClientAuthCompleted = 4, + PAL_TlsHandshakeState_ClientCertRequested = 5, }; typedef int32_t PAL_TlsHandshakeState; @@ -100,6 +101,17 @@ pOSStatus: Receives the value returned by SSLSetSessionOption PALEXPORT int32_t AppleCryptoNative_SslSetBreakOnServerAuth(SSLContextRef sslContext, int32_t setBreak, int32_t* pOSStatus); +/* +Sets the policy of whether or not to break when certificate request was received on client. + +Returns 1 on success, 0 on failure, other values on invalid state. + +Output: +pOSStatus: Receives the value returned by SSLSetSessionOption +*/ +PALEXPORT int32_t +AppleCryptoNative_SslSetBreakOnCertRequested(SSLContextRef sslContext, int32_t setBreak, int32_t* pOSStatus); + /* Sets the policy of whether or not to break when a client identity has been presented. diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index 5b5af80f5205c..bcca0693f63fb 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -298,6 +298,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_SslCtxSetQuietShutdown) DllImportEntry(CryptoNative_SslCtxUseCertificate) DllImportEntry(CryptoNative_SslCtxUsePrivateKey) + DllImportEntry(CryptoNative_SslAddExtraChainCert) DllImportEntry(CryptoNative_SslDestroy) DllImportEntry(CryptoNative_SslDoHandshake) DllImportEntry(CryptoNative_SslGetClientCAList) @@ -314,12 +315,15 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_SslSetAcceptState) DllImportEntry(CryptoNative_SslSetAlpnProtos) DllImportEntry(CryptoNative_SslSetBio) + DllImportEntry(CryptoNative_SslSetClientCertCallback) DllImportEntry(CryptoNative_SslSetConnectState) DllImportEntry(CryptoNative_SslSetData) DllImportEntry(CryptoNative_SslSetQuietShutdown) DllImportEntry(CryptoNative_SslSetTlsExtHostName) DllImportEntry(CryptoNative_SslSetVerifyPeer) DllImportEntry(CryptoNative_SslShutdown) + DllImportEntry(CryptoNative_SslUseCertificate) + DllImportEntry(CryptoNative_SslUsePrivateKey) DllImportEntry(CryptoNative_SslV2_3Method) DllImportEntry(CryptoNative_SslWrite) DllImportEntry(CryptoNative_Tls13Supported) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index daaa49da2869e..0b38944d502e8 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -503,6 +503,7 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); FALLBACK_FUNCTION(SSL_session_reused) \ REQUIRED_FUNCTION(SSL_set_accept_state) \ REQUIRED_FUNCTION(SSL_set_bio) \ + REQUIRED_FUNCTION(SSL_set_cert_cb) \ REQUIRED_FUNCTION(SSL_set_cipher_list) \ LIGHTUP_FUNCTION(SSL_set_ciphersuites) \ REQUIRED_FUNCTION(SSL_set_connect_state) \ @@ -514,6 +515,8 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); LEGACY_FUNCTION(SSLeay) \ RENAMED_FUNCTION(TLS_method, SSLv23_method) \ REQUIRED_FUNCTION(SSL_write) \ + REQUIRED_FUNCTION(SSL_use_certificate) \ + REQUIRED_FUNCTION(SSL_use_PrivateKey) \ FALLBACK_FUNCTION(X509_check_host) \ REQUIRED_FUNCTION(X509_check_purpose) \ REQUIRED_FUNCTION(X509_cmp_current_time) \ @@ -961,6 +964,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #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 +#define SSL_set_cert_cb SSL_set_cert_cb_ptr #define SSL_set_cipher_list SSL_set_cipher_list_ptr #define SSL_set_ciphersuites SSL_set_ciphersuites_ptr #define SSL_set_connect_state SSL_set_connect_state_ptr @@ -971,6 +975,8 @@ FOR_ALL_OPENSSL_FUNCTIONS #define SSL_state SSL_state_ptr #define SSLeay SSLeay_ptr #define SSL_write SSL_write_ptr +#define SSL_use_certificate SSL_use_certificate_ptr +#define SSL_use_PrivateKey SSL_use_PrivateKey_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/pal_ssl.c b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c index d6797af5654a6..b49ca41b1895d 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.c @@ -433,6 +433,16 @@ X509Stack* CryptoNative_SslGetPeerCertChain(SSL* ssl) return SSL_get_peer_cert_chain(ssl); } +int32_t CryptoNative_SslUseCertificate(SSL* ssl, X509* x) +{ + return SSL_use_certificate(ssl, x); +} + +int32_t CryptoNative_SslUsePrivateKey(SSL* ssl, EVP_PKEY* pkey) +{ + return SSL_use_PrivateKey(ssl, pkey); +} + int32_t CryptoNative_SslCtxUseCertificate(SSL_CTX* ctx, X509* x) { return SSL_CTX_use_certificate(ctx, x); @@ -633,6 +643,21 @@ int32_t CryptoNative_SslCtxAddExtraChainCert(SSL_CTX* ctx, X509* x509) return 0; } +int32_t CryptoNative_SslAddExtraChainCert(SSL* ssl, X509* x509) +{ + if (!x509 || !ssl) + { + return 0; + } + + if (SSL_ctrl(ssl, SSL_CTRL_CHAIN_CERT, 1,(void*)x509) == 1) + { + return 1; + } + + return 0; +} + void CryptoNative_SslCtxSetAlpnSelectCb(SSL_CTX* ctx, SslCtxSetAlpnCallback cb, void* arg) { #if HAVE_OPENSSL_ALPN @@ -648,6 +673,19 @@ void CryptoNative_SslCtxSetAlpnSelectCb(SSL_CTX* ctx, SslCtxSetAlpnCallback cb, #endif } +static int client_certificate_cb(SSL *ssl, void* state) +{ + (void*)ssl; + (void*)state; + // if we return negative number handshake will pause with SSL_ERROR_WANT_X509_LOOKUP + return -1; +} + +void CryptoNative_SslSetClientCertCallback(SSL* ssl, int set) +{ + SSL_set_cert_cb(ssl, set ? client_certificate_cb : NULL, NULL); +} + int32_t CryptoNative_SslSetData(SSL* ssl, void *ptr) { return SSL_set_ex_data(ssl, 0, ptr); @@ -655,7 +693,6 @@ int32_t CryptoNative_SslSetData(SSL* ssl, void *ptr) void* CryptoNative_SslGetData(SSL* ssl) { -// void* data = SSL_get_ex_data(ssl, 0, ptr); return SSL_get_ex_data(ssl, 0); } 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 02a8ce973cf30..faf96988b1b50 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_ssl.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_ssl.h @@ -142,6 +142,12 @@ Sets the specified protocols in the SSL_CTX options. PALEXPORT void CryptoNative_SslCtxSetProtocolOptions(SSL_CTX* ctx, SslProtocols protocols); /* +Sets internal callback for client certificate selection is set is positive. +It will unset callback if set is zero. +*/ +PALEXPORT void CryptoNative_SslSetClientCertCallback(SSL* ssl, int set); + +/*======= Sets session caching. 0 is disabled. */ PALEXPORT void CryptoNative_SslCtxSetCaching(SSL_CTX* ctx, int mode); @@ -276,6 +282,22 @@ Returns the certificate chain presented by the peer. */ PALEXPORT X509Stack* CryptoNative_SslGetPeerCertChain(SSL* ssl); +/* +Shims the SSL_use_certificate method. + +Returns 1 upon success, otherwise 0. +*/ +PALEXPORT int32_t CryptoNative_SslUseCertificate(SSL* ssl, X509* x); + +/* +Shims the SSL_use_PrivateKey method. + +Returns 1 upon success, otherwise 0. +*/ +PALEXPORT int32_t CryptoNative_SslUsePrivateKey(SSL* ssl, EVP_PKEY* pkey); + + + /* Shims the SSL_CTX_use_certificate method. @@ -363,13 +385,21 @@ Shims the SSL_session_reused macro. PALEXPORT int32_t CryptoNative_SslSessionReused(SSL* ssl); /* -adds the given certificate to the extra chain certificates associated with ctx. +Adds the given certificate to the extra chain certificates associated with ctx. libssl frees the x509 object. Returns 1 if success and 0 in case of failure */ PALEXPORT int32_t CryptoNative_SslCtxAddExtraChainCert(SSL_CTX* ctx, X509* x509); +/* +Adds the given certificate to the extra chain certificates associated with ssl state. + +libssl frees the x509 object. +Returns 1 if success and 0 in case of failure +*/ +PALEXPORT int32_t CryptoNative_SslAddExtraChainCert(SSL* ssl, X509* x509); + /* Shims the ssl_ctx_set_alpn_select_cb method. */