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 d403daac342ad1..12b75443f74723 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,12 +327,9 @@ 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); - } + // Set client cert callback, this will interrupt the handshake with SecurityStatusPalErrorCode.CredentialsNeeded + // if server actually requests a certificate. + Ssl.SslSetClientCertCallback(sslHandle, 1); } if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.RemoteCertRequired) 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 3690837e33f679..9e1a445d9dc750 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 @@ -130,6 +130,7 @@ private static SafeSslHandle CreateSslContext(SafeFreeSslCredentials credential, SetCertificate(sslContext, credential.CertificateContext); } + Interop.AppleCrypto.SslBreakOnCertRequested(sslContext, true); Interop.AppleCrypto.SslBreakOnServerAuth(sslContext, true); Interop.AppleCrypto.SslBreakOnClientAuth(sslContext, true); } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs index 6a2135616f1d55..e3363ddc1c34a2 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs @@ -295,62 +295,24 @@ private string[] GetRequestCertificateAuthorities() return issuers; } - /*++ - AcquireCredentials - Attempts to find Client Credential - Information, that can be sent to the server. In our case, - this is only Client Certificates, that we have Credential Info. - - How it works: - case 0: Cert Selection delegate is present - Always use its result as the client cert answer. - Try to use cached credential handle whenever feasible. - Do not use cached anonymous creds if the delegate has returned null - and the collection is not empty (allow responding with the cert later). - - case 1: Certs collection is empty - Always use the same statically acquired anonymous SSL Credential - - case 2: Before our Connection with the Server - If we have a cached credential handle keyed by first X509Certificate - **content** in the passed collection, then we use that cached - credential and hoping to restart a session. - - Otherwise create a new anonymous (allow responding with the cert later). - - case 3: After our Connection with the Server (i.e. during handshake or re-handshake) - The server has requested that we send it a Certificate then - we Enumerate a list of server sent Issuers trying to match against - our list of Certificates, the first match is sent to the server. - - Once we got a cert we again try to match cached credential handle if possible. - This will not restart a session but helps minimizing the number of handles we create. - - In the case of an error getting a Certificate or checking its private Key we fall back - to the behavior of having no certs, case 1. - - Returns: True if cached creds were used, false otherwise. - - --*/ - - private bool AcquireClientCredentials(ref byte[]? thumbPrint) + internal X509Certificate2? SelectClientCertificate(out bool sessionRestartAttempt) { - // Acquire possible Client Certificate information and set it on the handle. - X509Certificate? clientCertificate = null; // This is a candidate that can come from the user callback or be guessed when targeting a session restart. - List? filteredCerts = null; // This is an intermediate client certs collection that try to use if no selectedCert is available yet. - string[] issuers; // This is a list of issuers sent by the server, only valid is we do know what the server cert is. + sessionRestartAttempt = false; - bool sessionRestartAttempt = false; // If true and no cached creds we will use anonymous creds. + X509Certificate? clientCertificate = null; // candidate certificate that can come from the user callback or be guessed when targeting a session restart. + X509Certificate2? selectedCert = null; // final selected cert (ensured that it does have private key with it). + List? filteredCerts = null; // This is an intermediate client certs collection that try to use if no selectedCert is available yet. + string[] issuers; // This is a list of issuers sent by the server, only valid if we do know what the server cert is. if (_sslAuthenticationOptions.CertSelectionDelegate != null) { - issuers = GetRequestCertificateAuthorities(); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "Calling CertificateSelectionCallback"); X509Certificate2? remoteCert = null; try { + issuers = GetRequestCertificateAuthorities(); remoteCert = CertificateValidationPal.GetRemoteCertificate(_securityContext!); if (_sslAuthenticationOptions.ClientCertificates == null) { @@ -363,7 +325,6 @@ private bool AcquireClientCredentials(ref byte[]? thumbPrint) remoteCert?.Dispose(); } - if (clientCertificate != null) { if (_credentialsHandle == null) @@ -505,9 +466,6 @@ private bool AcquireClientCredentials(ref byte[]? thumbPrint) } } - bool cachedCred = false; // This is a return result from this method. - X509Certificate2? selectedCert = null; // This is a final selected cert (ensured that it does have private key with it). - clientCertificate = null; if (NetEventSource.Log.IsEnabled()) @@ -550,6 +508,56 @@ private bool AcquireClientCredentials(ref byte[]? thumbPrint) if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Selected cert = {selectedCert}"); + _selectedClientCertificate = clientCertificate; + return selectedCert; + } + + /*++ + AcquireCredentials - Attempts to find Client Credential + Information, that can be sent to the server. In our case, + this is only Client Certificates, that we have Credential Info. + + How it works: + case 0: Cert Selection delegate is present + Always use its result as the client cert answer. + Try to use cached credential handle whenever feasible. + Do not use cached anonymous creds if the delegate has returned null + and the collection is not empty (allow responding with the cert later). + + case 1: Certs collection is empty + Always use the same statically acquired anonymous SSL Credential + + case 2: Before our Connection with the Server + If we have a cached credential handle keyed by first X509Certificate + **content** in the passed collection, then we use that cached + credential and hoping to restart a session. + + Otherwise create a new anonymous (allow responding with the cert later). + + case 3: After our Connection with the Server (i.e. during handshake or re-handshake) + The server has requested that we send it a Certificate then + we Enumerate a list of server sent Issuers trying to match against + our list of Certificates, the first match is sent to the server. + + Once we got a cert we again try to match cached credential handle if possible. + This will not restart a session but helps minimizing the number of handles we create. + + In the case of an error getting a Certificate or checking its private Key we fall back + to the behavior of having no certs, case 1. + + Returns: True if cached creds were used, false otherwise. + + --*/ + + private bool AcquireClientCredentials(ref byte[]? thumbPrint) + { + // Acquire possible Client Certificate information and set it on the handle. + + bool sessionRestartAttempt; // If true and no cached creds we will use anonymous creds. + bool cachedCred = false; // this is a return result from this method. + + X509Certificate2? selectedCert = SelectClientCertificate(out sessionRestartAttempt); + try { // Try to locate cached creds first. @@ -574,14 +582,14 @@ private bool AcquireClientCredentials(ref byte[]? thumbPrint) // So we don't want to reuse **anonymous** cached credential for a new SSL connection if the client has passed some certificate. // The following block happens if client did specify a certificate but no cached creds were found in the cache. // Since we don't restart a session the server side can still challenge for a client cert. - if ((object?)clientCertificate != (object?)selectedCert) + if ((object?)_selectedClientCertificate != (object?)selectedCert) { selectedCert.Dispose(); } guessedThumbPrint = null; selectedCert = null; - clientCertificate = null; + _selectedClientCertificate = null; } if (cachedCredentialHandle != null) @@ -589,7 +597,6 @@ private bool AcquireClientCredentials(ref byte[]? thumbPrint) if (NetEventSource.Log.IsEnabled()) NetEventSource.Log.UsingCachedCredential(this); _credentialsHandle = cachedCredentialHandle; - _selectedClientCertificate = clientCertificate; cachedCred = true; if (selectedCert != null) { @@ -607,7 +614,6 @@ private bool AcquireClientCredentials(ref byte[]? thumbPrint) _sslAuthenticationOptions.EnabledSslProtocols, _sslAuthenticationOptions.EncryptionPolicy, _sslAuthenticationOptions.IsServer); thumbPrint = guessedThumbPrint; // Delay until here in case something above threw. - _selectedClientCertificate = clientCertificate; } } finally @@ -792,6 +798,7 @@ private SecurityStatusPal GenerateToken(ReadOnlySpan inputBuffer, ref byte if (_sslAuthenticationOptions.IsServer) { status = SslStreamPal.AcceptSecurityContext( + this, ref _credentialsHandle!, ref _securityContext, inputBuffer, @@ -801,6 +808,7 @@ private SecurityStatusPal GenerateToken(ReadOnlySpan inputBuffer, ref byte else { status = SslStreamPal.InitializeSecurityContext( + this, ref _credentialsHandle!, ref _securityContext, _sslAuthenticationOptions.TargetHost, @@ -841,6 +849,7 @@ private SecurityStatusPal GenerateToken(ReadOnlySpan inputBuffer, ref byte internal SecurityStatusPal Renegotiate(out byte[]? output) { return SslStreamPal.Renegotiate( + this, ref _credentialsHandle!, ref _securityContext, _sslAuthenticationOptions, diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs index 27c854c99168e7..54187a60c6c293 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs @@ -25,6 +25,7 @@ public static void VerifyPackageInfo() } public static SecurityStatusPal AcceptSecurityContext( + SecureChannel secureChannel, ref SafeFreeCredentials credential, ref SafeDeleteSslContext? context, ReadOnlySpan inputBuffer, @@ -35,6 +36,7 @@ public static SecurityStatusPal AcceptSecurityContext( } public static SecurityStatusPal InitializeSecurityContext( + SecureChannel secureChannel, ref SafeFreeCredentials credential, ref SafeDeleteSslContext? context, string? targetName, @@ -45,7 +47,12 @@ public static SecurityStatusPal InitializeSecurityContext( return HandshakeInternal(credential, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions); } - public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentialsHandle, ref SafeDeleteSslContext? context, SslAuthenticationOptions sslAuthenticationOptions, out byte[]? outputBuffer) + public static SecurityStatusPal Renegotiate( + SecureChannel secureChannel, + ref SafeFreeCredentials? credentialsHandle, + ref SafeDeleteSslContext? context, + SslAuthenticationOptions sslAuthenticationOptions, + out byte[]? outputBuffer) { throw new PlatformNotSupportedException(); } 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 7043b7afe19f4d..6b1b13ca9f2a70 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 @@ -20,7 +20,7 @@ public static Exception GetException(SecurityStatusPal status) return status.Exception ?? new Win32Exception((int)status.ErrorCode); } - internal const bool StartMutualAuthAsAnonymous = false; + internal const bool StartMutualAuthAsAnonymous = true; // SecureTransport is okay with a 0 byte input, but it produces a 0 byte output. // Since ST is not producing the framed empty message just call this false and avoid the @@ -32,16 +32,18 @@ public static void VerifyPackageInfo() } public static SecurityStatusPal AcceptSecurityContext( + SecureChannel secureChannel, ref SafeFreeCredentials credential, ref SafeDeleteSslContext? context, ReadOnlySpan inputBuffer, ref byte[]? outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) { - return HandshakeInternal(credential, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions); + return HandshakeInternal(secureChannel, credential, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions); } public static SecurityStatusPal InitializeSecurityContext( + SecureChannel secureChannel, ref SafeFreeCredentials credential, ref SafeDeleteSslContext? context, string? targetName, @@ -49,10 +51,15 @@ public static SecurityStatusPal InitializeSecurityContext( ref byte[]? outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) { - return HandshakeInternal(credential, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions); + return HandshakeInternal(secureChannel, credential, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions); } - public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentialsHandle, ref SafeDeleteSslContext? context, SslAuthenticationOptions sslAuthenticationOptions, out byte[]? outputBuffer) + public static SecurityStatusPal Renegotiate( + SecureChannel secureChannel, + ref SafeFreeCredentials? credentialsHandle, + ref SafeDeleteSslContext? context, + SslAuthenticationOptions sslAuthenticationOptions, + out byte[]? outputBuffer) { throw new PlatformNotSupportedException(); } @@ -224,6 +231,7 @@ public static void QueryContextConnectionInfo( } private static SecurityStatusPal HandshakeInternal( + SecureChannel secureChannel, SafeFreeCredentials credential, ref SafeDeleteSslContext? context, ReadOnlySpan inputBuffer, @@ -268,28 +276,11 @@ private static SecurityStatusPal HandshakeInternal( 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? clientCertificate = secureChannel.SelectClientCertificate(out _); + if (clientCertificate != 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(); - } + sslAuthenticationOptions.CertificateContext = SslStreamCertificateContext.Create(clientCertificate); + SafeDeleteSslContext.SetCertificate(sslContext.SslContext, sslAuthenticationOptions.CertificateContext); } // We either got certificate or we can proceed without it. It is up to the server to decide if either is OK. 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 63c4efc1edb655..a9192681444bef 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 @@ -17,23 +17,34 @@ public static Exception GetException(SecurityStatusPal status) return status.Exception ?? new Interop.OpenSsl.SslException((int)status.ErrorCode); } - internal const bool StartMutualAuthAsAnonymous = false; + internal const bool StartMutualAuthAsAnonymous = true; internal const bool CanEncryptEmptyMessage = false; public static void VerifyPackageInfo() { } - public static SecurityStatusPal AcceptSecurityContext(ref SafeFreeCredentials? credential, ref SafeDeleteSslContext? context, - ReadOnlySpan inputBuffer, ref byte[]? outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) + public static SecurityStatusPal AcceptSecurityContext( + SecureChannel secureChannel, + ref SafeFreeCredentials? credential, + ref SafeDeleteSslContext? context, + ReadOnlySpan inputBuffer, + ref byte[]? outputBuffer, + SslAuthenticationOptions sslAuthenticationOptions) { - return HandshakeInternal(credential!, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions); + return HandshakeInternal(secureChannel, credential!, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions); } - public static SecurityStatusPal InitializeSecurityContext(ref SafeFreeCredentials? credential, ref SafeDeleteSslContext? context, string? targetName, - ReadOnlySpan inputBuffer, ref byte[]? outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) + public static SecurityStatusPal InitializeSecurityContext( + SecureChannel secureChannel, + ref SafeFreeCredentials? credential, + ref SafeDeleteSslContext? context, + string? targetName, + ReadOnlySpan inputBuffer, + ref byte[]? outputBuffer, + SslAuthenticationOptions sslAuthenticationOptions) { - return HandshakeInternal(credential!, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions); + return HandshakeInternal(secureChannel, credential!, ref context, inputBuffer, ref outputBuffer, sslAuthenticationOptions); } public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext, @@ -116,7 +127,12 @@ Interop.Ssl.SslErrorCode.SSL_ERROR_NONE or return bindingHandle; } - public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentialsHandle, ref SafeDeleteSslContext? securityContext, SslAuthenticationOptions sslAuthenticationOptions, out byte[]? outputBuffer) + public static SecurityStatusPal Renegotiate( + SecureChannel secureChannel, + ref SafeFreeCredentials? credentialsHandle, + ref SafeDeleteSslContext? securityContext, + SslAuthenticationOptions sslAuthenticationOptions, + out byte[]? outputBuffer) { var sslContext = ((SafeDeleteSslContext)securityContext!).SslContext; SecurityStatusPal status = Interop.OpenSsl.SslRenegotiate(sslContext, out _); @@ -126,7 +142,7 @@ public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentials { return status; } - return HandshakeInternal(credentialsHandle!, ref securityContext, null, ref outputBuffer, sslAuthenticationOptions); + return HandshakeInternal(secureChannel, credentialsHandle!, ref securityContext, null, ref outputBuffer, sslAuthenticationOptions); } public static void QueryContextStreamSizes(SafeDeleteContext? securityContext, out StreamSizes streamSizes) @@ -139,7 +155,7 @@ public static void QueryContextConnectionInfo(SafeDeleteSslContext securityConte connectionInfo = new SslConnectionInfo(securityContext.SslContext); } - private static SecurityStatusPal HandshakeInternal(SafeFreeCredentials credential, ref SafeDeleteSslContext? context, + private static SecurityStatusPal HandshakeInternal(SecureChannel secureChannel, SafeFreeCredentials credential, ref SafeDeleteSslContext? context, ReadOnlySpan inputBuffer, ref byte[]? outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) { Debug.Assert(!credential.IsInvalid); @@ -158,34 +174,17 @@ private static SecurityStatusPal HandshakeInternal(SafeFreeCredentials credentia if (errorCode == SecurityStatusPalErrorCode.CredentialsNeeded) { - if (sslAuthenticationOptions.CertSelectionDelegate != null) + X509Certificate2? clientCertificate = secureChannel.SelectClientCertificate(out _); + if (clientCertificate != 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(); - } + sslAuthenticationOptions.CertificateContext = SslStreamCertificateContext.Create(clientCertificate); } 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. + // sometimes during renegotiation processing message 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)) { diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs index 2fb36e595b20da..2cb0f2ec14e66c 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs @@ -52,7 +52,13 @@ public static byte[] ConvertAlpnProtocolListToByteArray(List inputBuffer, ref byte[]? outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) + public static SecurityStatusPal AcceptSecurityContext( + SecureChannel secureChannel, + ref SafeFreeCredentials? credentialsHandle, + ref SafeDeleteSslContext? context, + ReadOnlySpan inputBuffer, + ref byte[]? outputBuffer, + SslAuthenticationOptions sslAuthenticationOptions) { Interop.SspiCli.ContextFlags unusedAttributes = default; @@ -82,7 +88,14 @@ public static SecurityStatusPal AcceptSecurityContext(ref SafeFreeCredentials? c return SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode); } - public static SecurityStatusPal InitializeSecurityContext(ref SafeFreeCredentials? credentialsHandle, ref SafeDeleteSslContext? context, string? targetName, ReadOnlySpan inputBuffer, ref byte[]? outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) + public static SecurityStatusPal InitializeSecurityContext( + SecureChannel secureChannel, + ref SafeFreeCredentials? credentialsHandle, + ref SafeDeleteSslContext? context, + string? targetName, + ReadOnlySpan inputBuffer, + ref byte[]? outputBuffer, + SslAuthenticationOptions sslAuthenticationOptions) { Interop.SspiCli.ContextFlags unusedAttributes = default; @@ -112,10 +125,15 @@ public static SecurityStatusPal InitializeSecurityContext(ref SafeFreeCredential return SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode); } - public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentialsHandle, ref SafeDeleteSslContext? context, SslAuthenticationOptions sslAuthenticationOptions, out byte[]? outputBuffer ) + public static SecurityStatusPal Renegotiate( + SecureChannel secureChannel, + ref SafeFreeCredentials? credentialsHandle, + ref SafeDeleteSslContext? context, + SslAuthenticationOptions sslAuthenticationOptions, + out byte[]? outputBuffer ) { byte[]? output = Array.Empty(); - SecurityStatusPal status = AcceptSecurityContext(ref credentialsHandle, ref context, Span.Empty, ref output, sslAuthenticationOptions); + SecurityStatusPal status = AcceptSecurityContext(secureChannel, ref credentialsHandle, ref context, Span.Empty, ref output, sslAuthenticationOptions); outputBuffer = output; return status; } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamMutualAuthenticationTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamMutualAuthenticationTest.cs new file mode 100644 index 00000000000000..adf161fabd49f1 --- /dev/null +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamMutualAuthenticationTest.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Threading.Tasks; +using System.Net.Test.Common; +using System.Security.Cryptography.X509Certificates; + +using Xunit; + +namespace System.Net.Security.Tests +{ + using Configuration = System.Net.Test.Common.Configuration; + + public class SslStreamMutualAuthenticationTest + { + private readonly X509Certificate2 _clientCertificate; + private readonly X509Certificate2 _serverCertificate; + + public SslStreamMutualAuthenticationTest() + { + _serverCertificate = Configuration.Certificates.GetServerCertificate(); + _clientCertificate = Configuration.Certificates.GetClientCertificate(); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public async Task SslStream_RequireClientCert_IsMutuallyAuthenticated_ReturnsTrue(bool clientCertificateRequired, bool useClientSelectionCallback) + { + (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); + using (var client = new SslStream(stream1, false, AllowAnyCertificate)) + using (var server = new SslStream(stream2, false, AllowAnyCertificate)) + { + Task t2 = client.AuthenticateAsClientAsync(new SslClientAuthenticationOptions + { + ClientCertificates = useClientSelectionCallback ? null : new X509CertificateCollection() { _clientCertificate }, + LocalCertificateSelectionCallback = useClientSelectionCallback ? ClientCertSelectionCallback : null, + TargetHost = _clientCertificate.GetNameInfo(X509NameType.SimpleName, false) + }); + Task t1 = server.AuthenticateAsServerAsync(new SslServerAuthenticationOptions + { + ServerCertificate = _serverCertificate, + ClientCertificateRequired = clientCertificateRequired + }); + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2); + + if (Capability.IsTrustedRootCertificateInstalled()) + { + // https://technet.microsoft.com/en-us/library/hh831771.aspx#BKMK_Changes2012R2 + // Starting with Windows 8, the "Management of trusted issuers for client authentication" has changed: + // The behavior to send the Trusted Issuers List by default is off. + // + // In Windows 7 the Trusted Issuers List is sent within the Server Hello TLS record. This list is built + // by the server using certificates from the Trusted Root Authorities certificate store. + // The client side will use the Trusted Issuers List, if not empty, to filter proposed certificates. + + if (clientCertificateRequired) + { + Assert.True(client.IsMutuallyAuthenticated, "client.IsMutuallyAuthenticated"); + Assert.True(server.IsMutuallyAuthenticated, "server.IsMutuallyAuthenticated"); + } + else + { + // Even though the certificate was provided, it was not requested by the server and thus the client + // was not authenticated. + Assert.False(client.IsMutuallyAuthenticated, "client.IsMutuallyAuthenticated"); + Assert.False(server.IsMutuallyAuthenticated, "server.IsMutuallyAuthenticated"); + } + } + } + } + + private static bool AllowAnyCertificate( + object sender, + X509Certificate certificate, + X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { + return true; + } + private X509Certificate ClientCertSelectionCallback( + object sender, + string targetHost, + X509CertificateCollection localCertificates, + X509Certificate remoteCertificate, + string[] acceptableIssuers) + { + return _clientCertificate; + } + } +} diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj index a6a21cc91e155b..ea97df3db286a6 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj @@ -28,6 +28,7 @@ +