diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs index b838db0c252b4..81f2c92880b55 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs @@ -914,11 +914,12 @@ private async ValueTask ReadAsyncInternal(TIOAdapter adapter, M // If that happen before EncryptData() runs, _handshakeWaiter will be set to null // and EncryptData() will work normally e.g. no waiting, just exclusion with DecryptData() - if (_sslAuthenticationOptions!.AllowRenegotiation || SslProtocol == SslProtocols.Tls13) + if (_sslAuthenticationOptions!.AllowRenegotiation || SslProtocol == SslProtocols.Tls13 || _nestedAuth != 0) { // create TCS only if we plan to proceed. If not, we will throw in block bellow outside of the lock. // Tls1.3 does not have renegotiation. However on Windows this error code is used // for session management e.g. anything lsass needs to see. + // We also allow it when explicitly requested using RenegotiateAsync(). _handshakeWaiter = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs index 957a40d240375..00fafd6652bf7 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs @@ -243,6 +243,78 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( } } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [InlineData(true)] + [InlineData(false)] + [PlatformSpecific(TestPlatforms.Windows)] + public async Task SslStream_NegotiateClientCertificateAsyncNoRenego_Succeeds(bool sendClientCertificate) + { + bool negotiateClientCertificateCalled = false; + using CancellationTokenSource cts = new CancellationTokenSource(); + cts.CancelAfter(TestConfiguration.PassingTestTimeout); + + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + using (X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate()) + using (X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate()) + { + SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions() + { + TargetHost = Guid.NewGuid().ToString("N"), + EnabledSslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12, + }; + clientOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; + clientOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => + { + return sendClientCertificate ? clientCertificate : null; + }; + + SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() { ServerCertificate = serverCertificate, + AllowRenegotiation = false }; + serverOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => + { + if (negotiateClientCertificateCalled && sendClientCertificate) + { + Assert.Equal(clientCertificate.GetCertHash(), certificate?.GetCertHash()); + } + else + { + Assert.Null(certificate); + } + + return true; + }; + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions, cts.Token), + server.AuthenticateAsServerAsync(serverOptions, cts.Token)); + + Assert.Null(server.RemoteCertificate); + + // Client needs to be reading for renegotiation to happen. + byte[] buffer = new byte[TestHelper.s_ping.Length]; + ValueTask t = client.ReadAsync(buffer, cts.Token); + + negotiateClientCertificateCalled = true; + await server.NegotiateClientCertificateAsync(cts.Token); + if (sendClientCertificate) + { + Assert.NotNull(server.RemoteCertificate); + } + else + { + Assert.Null(server.RemoteCertificate); + } + // Finish the client's read + await server.WriteAsync(TestHelper.s_ping, cts.Token); + await t; + // verify that the session is usable with or without client's certificate + await TestHelper.PingPong(client, server, cts.Token); + await TestHelper.PingPong(server, client, cts.Token); + } + } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.SupportsTls13))] [InlineData(true)] [InlineData(false)]