diff --git a/src/libraries/Common/tests/System/Net/Configuration.Http.cs b/src/libraries/Common/tests/System/Net/Configuration.Http.cs index 15d426ce9bab42..23e7c7a011f179 100644 --- a/src/libraries/Common/tests/System/Net/Configuration.Http.cs +++ b/src/libraries/Common/tests/System/Net/Configuration.Http.cs @@ -37,9 +37,6 @@ public static partial class Http public static string TLSv11RemoteServer => GetValue("DOTNET_TEST_HTTPHOST_TLS11", "https://www.ssllabs.com:10302/"); public static string TLSv12RemoteServer => GetValue("DOTNET_TEST_HTTPHOST_TLS12", "https://www.ssllabs.com:10303/"); - public static string ExpiredCertRemoteServer => GetValue("DOTNET_TEST_HTTPHOST_EXPIREDCERT", "https://expired.badssl.com/"); - public static string WrongHostNameCertRemoteServer => GetValue("DOTNET_TEST_HTTPHOST_WRONGHOSTNAME", "https://wrong.host.badssl.com/"); - public static string SelfSignedCertRemoteServer => GetValue("DOTNET_TEST_HTTPHOST_SELFSIGNEDCERT", "https://self-signed.badssl.com/"); public static string RevokedCertRemoteServer => GetValue("DOTNET_TEST_HTTPHOST_REVOKEDCERT", "https://revoked.badssl.com/"); public static string EchoClientCertificateRemoteServer => GetValue("DOTNET_TEST_HTTPHOST_ECHOCLIENTCERT", "https://corefx-net-tls.azurewebsites.net/EchoClientCertificate.ashx"); diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs index b449f5025276dc..eacbc846604d82 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs @@ -4,23 +4,19 @@ using System.Net.Security; using System.Net.Test.Common; using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; -using Microsoft.DotNet.XUnitExtensions; using Xunit; using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { - using Configuration = System.Net.Test.Common.Configuration; - #if WINHTTPHANDLER_TEST using HttpClientHandler = System.Net.Http.WinHttpClientHandler; #endif public abstract class HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test : HttpClientHandlerTestBase { - private static bool ClientSupportsDHECipherSuites => (!PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1607OrGreater); - public HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test(ITestOutputHelper output) : base(output) { } #if !WINHTTPHANDLER_TEST @@ -86,22 +82,30 @@ await TestHelper.WhenAllCompletedOrAnyFailed( } } - public static readonly object[][] InvalidCertificateServers = + [Theory] + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task InvalidCertificates_CertificateValidationDisabled_Succeeds(bool expired, bool wrongHostName) { - new object[] { Configuration.Http.ExpiredCertRemoteServer }, - new object[] { Configuration.Http.SelfSignedCertRemoteServer }, - new object[] { Configuration.Http.WrongHostNameCertRemoteServer }, - }; + using X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate( + name: wrongHostName ? "not-localhost" : "localhost", expired: expired); - [OuterLoop] - [ConditionalTheory(nameof(ClientSupportsDHECipherSuites))] - [MemberData(nameof(InvalidCertificateServers))] - public async Task InvalidCertificateServers_CertificateValidationDisabled_Succeeds(string url) - { using (HttpClientHandler handler = CreateHttpClientHandler(allowAllCertificates: true)) using (HttpClient client = CreateHttpClient(handler)) { - (await client.GetAsync(url)).Dispose(); + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + using HttpResponseMessage response = await client.GetAsync(new UriBuilder(uri) { Host = "localhost" }.Uri); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + }, server => server.HandleRequestAsync(), + options: new GenericLoopbackOptions + { + UseSsl = true, + Certificate = certificate, + SslProtocols = SslProtocols.None, + }); } } } diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs index 0ed517ae168426..6304a74aeb54f6 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs @@ -2,13 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +#if WINHTTPHANDLER_TEST +using System.ComponentModel; +#endif using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Security; +using System.Net.Sockets; using System.Net.Test.Common; using System.Runtime.InteropServices; +using System.Security.Authentication; using System.Security.Authentication.ExtendedProtection; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; @@ -26,15 +32,19 @@ namespace System.Net.Http.Functional.Tests public abstract partial class HttpClientHandler_ServerCertificates_Test : HttpClientHandlerTestBase { +#if WINHTTPHANDLER_TEST private static bool ClientSupportsDHECipherSuites => (!PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1607OrGreater); +#endif public HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { } // This enables customizing ServerCertificateCustomValidationCallback in WinHttpHandler variants: protected bool AllowAllCertificates { get; set; } = true; - protected new HttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler( + protected new HttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(allowTlsResume: true); + protected new HttpClientHandler CreateHttpClientHandler(bool allowTlsResume) => CreateHttpClientHandler( useVersion: UseVersion, - allowAllCertificates: UseVersion >= HttpVersion20.Value && AllowAllCertificates); + allowAllCertificates: UseVersion >= HttpVersion20.Value && AllowAllCertificates, + allowTlsResume: allowTlsResume); protected override HttpClient CreateHttpClient() => CreateHttpClient(CreateHttpClientHandler()); [Fact] @@ -143,7 +153,7 @@ public static IEnumerable UseCallback_ValidCertificate_ExpectedValuesD [MemberData(nameof(UseCallback_ValidCertificate_ExpectedValuesDuringCallback_Urls))] public async Task UseCallback_ValidCertificate_ExpectedValuesDuringCallback(Configuration.Http.RemoteServer remoteServer, Uri url, bool checkRevocation) { - HttpClientHandler handler = CreateHttpClientHandler(); + HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false); using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler)) { bool callbackCalled = false; @@ -186,7 +196,7 @@ public async Task UseCallback_ValidCertificate_ExpectedValuesDuringCallback(Conf [Fact] public async Task UseCallback_CallbackReturnsFailure_ThrowsException() { - HttpClientHandler handler = CreateHttpClientHandler(); + HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false); using (HttpClient client = CreateHttpClient(handler)) { handler.ServerCertificateCustomValidationCallback = delegate { return false; }; @@ -198,7 +208,7 @@ public async Task UseCallback_CallbackReturnsFailure_ThrowsException() [Fact] public async Task UseCallback_CallbackThrowsException_ExceptionPropagatesAsBaseException() { - HttpClientHandler handler = CreateHttpClientHandler(); + HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false); using (HttpClient client = CreateHttpClient(handler)) { var e = new DivideByZeroException(); @@ -209,24 +219,93 @@ public async Task UseCallback_CallbackThrowsException_ExceptionPropagatesAsBaseE } } - public static readonly object[][] CertificateValidationServers = + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CreateServerCertificate_IsEndEntity(bool selfSigned) { - new object[] { Configuration.Http.ExpiredCertRemoteServer }, - new object[] { Configuration.Http.SelfSignedCertRemoteServer }, - new object[] { Configuration.Http.WrongHostNameCertRemoteServer }, - }; + using X509Certificate2 rootCertificate = CreateCertificateAuthority(); + using X509Certificate2 certificate = TestHelper.CreateServerCertificate(selfSigned ? null : rootCertificate); - [OuterLoop("Uses external servers")] - [ConditionalTheory(nameof(ClientSupportsDHECipherSuites))] - [MemberData(nameof(CertificateValidationServers))] - public async Task NoCallback_BadCertificate_ThrowsException(string url) + Assert.False(Assert.Single(certificate.Extensions.OfType()).CertificateAuthority); + } + + [Theory] + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task NoCallback_BadCertificate_ThrowsException(bool expired, bool wrongHostName) { - using (HttpClient client = CreateHttpClient()) + using X509Certificate2 rootCertificate = CreateCertificateAuthority(); + + using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false)) + using (HttpClient client = CreateHttpClient(handler)) { - await Assert.ThrowsAsync(() => client.GetAsync(url)); + bool customChainPolicy = ConfigureCertificateChainPolicy(handler, rootCertificate); + using X509Certificate2 certificate = TestHelper.CreateServerCertificate( + customChainPolicy && (expired || wrongHostName) ? rootCertificate : null, + name: wrongHostName ? "not-localhost" : "localhost", expired: expired); + + await LoopbackServer.CreateClientAndServerAsync(async uri => + { + HttpRequestException exception = await Assert.ThrowsAsync( + () => client.GetAsync(new UriBuilder(uri) { Host = "localhost" }.Uri)); +#if WINHTTPHANDLER_TEST + const int ErrorWinHttpSecureFailure = 12175; + Assert.Equal(ErrorWinHttpSecureFailure, Assert.IsAssignableFrom(exception.InnerException).NativeErrorCode); +#else + if (customChainPolicy) + { + Assert.Equal(HttpRequestError.SecureConnectionError, exception.HttpRequestError); + Assert.IsType(exception.InnerException); + } +#endif + }, async server => + { + try + { + // Unexpected certificate acceptance must fail the assertion, not trigger a retry against an idle listener. + await server.HandleRequestAsync(); + } + catch (Exception e) when (e is AuthenticationException or IOException) + { + _output.WriteLine($"Expected certificate validation failure: {e}"); + } + }, + options: new LoopbackServer.Options + { + UseSsl = true, + Certificate = certificate, + SslProtocols = SslProtocols.None, + }); } } + [Fact] + public async Task LoopbackServer_AuthenticationFailure_DisposesStream() + { + bool streamDisposed = false; + var expectedException = new IOException("Simulated TLS transport failure."); + using var stream = new DelegateStream( + canReadFunc: () => true, + canWriteFunc: () => true, + readAsyncFunc: (_, _, _, _) => Task.FromException(expectedException), + writeAsyncFunc: (_, _, _, _) => Task.CompletedTask, + disposeFunc: disposing => streamDisposed |= disposing); + using var socket = new SocketWrapper(new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)); + using X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate(); + var options = new LoopbackServer.Options { UseSsl = true, Certificate = certificate }; +#if !NETFRAMEWORK + options.CertificateContext = SslStreamCertificateContext.Create(certificate, null); +#endif + + IOException exception = await Assert.ThrowsAsync(() => LoopbackServer.Connection.CreateAsync(socket, stream, options)); + Assert.Same(expectedException, exception.GetBaseException()); + Assert.True(streamDisposed); + } + +#if WINHTTPHANDLER_TEST [OuterLoop("Uses external servers")] [ConditionalFact(nameof(ClientSupportsDHECipherSuites))] [ActiveIssue("https://github.com/dotnet/runtime/issues/106634", typeof(PlatformDetection), nameof(PlatformDetection.IsAlpine))] @@ -245,91 +324,125 @@ public async Task NoCallback_RevokedCertificate_NoRevocationChecking_Succeeds() [Fact] public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails() { - HttpClientHandler handler = CreateHttpClientHandler(); + HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false); handler.CheckCertificateRevocationList = true; using (HttpClient client = CreateHttpClient(handler)) { await Assert.ThrowsAsync(() => client.GetAsync(Configuration.Http.RevokedCertRemoteServer)); } } +#endif - public static readonly object[][] CertificateValidationServersAndExpectedPolicies = + [Theory] + [InlineData(true, false, SslPolicyErrors.RemoteCertificateChainErrors)] + [InlineData(false, false, SslPolicyErrors.RemoteCertificateChainErrors)] + [InlineData(false, true, SslPolicyErrors.RemoteCertificateNameMismatch)] + [InlineData(true, true, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch)] + public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(bool expired, bool wrongHostName, SslPolicyErrors expectedErrors) { - new object[] { Configuration.Http.ExpiredCertRemoteServer, SslPolicyErrors.RemoteCertificateChainErrors }, - new object[] { Configuration.Http.WrongHostNameCertRemoteServer , SslPolicyErrors.RemoteCertificateNameMismatch}, - }; + using X509Certificate2 rootCertificate = CreateCertificateAuthority(); - private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string url, string useHttp2String, SslPolicyErrors expectedErrors) - { - HttpClientHandler handler = CreateHttpClientHandler(useHttp2String); - using (HttpClient client = CreateHttpClient(handler, useHttp2String)) + using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false)) + using (HttpClient client = CreateHttpClient(handler)) { + bool customChainPolicy = ConfigureCertificateChainPolicy(handler, rootCertificate); + using X509Certificate2 certificate = TestHelper.CreateServerCertificate( + customChainPolicy && (expired || wrongHostName) ? rootCertificate : null, + name: wrongHostName ? "not-localhost" : "localhost", expired: expired); + X509ChainStatusFlags expectedChainStatus = expired ? X509ChainStatusFlags.NotTimeValid : + wrongHostName ? X509ChainStatusFlags.NoError : X509ChainStatusFlags.UntrustedRoot; + + if (!customChainPolicy) + { + // Native handlers cannot trust the generated root without changing the OS certificate store. + expectedErrors |= SslPolicyErrors.RemoteCertificateChainErrors; + expectedChainStatus |= X509ChainStatusFlags.UntrustedRoot; + } + + if (PlatformDetection.IsAndroid && expectedChainStatus != X509ChainStatusFlags.NoError) + { + // Android cannot build a path through an expired or untrusted certificate. + expectedChainStatus = X509ChainStatusFlags.PartialChain; + } + + string expectedThumbprint = certificate.Thumbprint; bool callbackCalled = false; handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => { - // https://github.com/dotnet/corefx/issues/21922#issuecomment-315555237 - X509ChainStatusFlags flags = chain.ChainStatus.Aggregate(X509ChainStatusFlags.NoError, (cur, status) => cur | status.Status); - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && - flags == X509ChainStatusFlags.RevocationStatusUnknown) - { - expectedErrors |= SslPolicyErrors.RemoteCertificateChainErrors; - } - callbackCalled = true; Assert.NotNull(request); Assert.NotNull(cert); Assert.NotNull(chain); + Assert.Equal(expectedThumbprint, cert.Thumbprint); Assert.Equal(expectedErrors, errors); + Assert.Equal(expectedChainStatus, chain.ChainStatus.Aggregate( + X509ChainStatusFlags.NoError, (flags, status) => flags | status.Status)); return true; }; - using (HttpResponseMessage response = await client.GetAsync(url)) + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { + using HttpResponseMessage response = await client.GetAsync(new UriBuilder(uri) { Host = "localhost" }.Uri); Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } + }, server => server.HandleRequestAsync(), + options: new GenericLoopbackOptions + { + UseSsl = true, + Certificate = certificate, + SslProtocols = SslProtocols.None, + }); Assert.True(callbackCalled); } } - [OuterLoop("Uses external servers")] - [Theory] - [MemberData(nameof(CertificateValidationServersAndExpectedPolicies))] - public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(string url, SslPolicyErrors expectedErrors) + private static X509Certificate2 CreateCertificateAuthority() { - const int SEC_E_BUFFER_TOO_SMALL = unchecked((int)0x80090321); + using RSA key = RSA.Create(); + var request = new CertificateRequest("CN=Test Certificate Authority", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + request.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true)); + request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign, true)); - if (!ClientSupportsDHECipherSuites) - { - return; - } + DateTimeOffset now = DateTimeOffset.UtcNow; + return request.CreateSelfSigned(now.AddYears(-1), now.AddYears(1)); + } - try - { - await UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(url, UseVersion.ToString(), expectedErrors); - } - catch (HttpRequestException e) when (e.InnerException?.GetType().Name == "WinHttpException" && - e.InnerException.HResult == SEC_E_BUFFER_TOO_SMALL && - !PlatformDetection.IsWindows10Version1607OrGreater) + private static bool ConfigureCertificateChainPolicy(HttpClientHandler handler, X509Certificate2 rootCertificate) + { +#if !WINHTTPHANDLER_TEST + if (GetUnderlyingSocketsHttpHandler(handler) is SocketsHttpHandler socketsHandler) { - // Testing on old Windows versions can hit https://github.com/dotnet/runtime/issues/17005 - // Ignore SEC_E_BUFFER_TOO_SMALL error on such cases. + var policy = new X509ChainPolicy + { + TrustMode = X509ChainTrustMode.CustomRootTrust, + RevocationMode = X509RevocationMode.NoCheck, + DisableCertificateDownloads = true, + // Android requires a nonempty trust store and does not check a trust anchor's expiration. + CustomTrustStore = { rootCertificate }, + }; + + socketsHandler.SslOptions.CertificateChainPolicy = policy; + return true; } +#endif + return false; } - [Fact] - public async Task UseCallback_SelfSignedCertificate_ExpectedPolicyErrors() + [Theory] + [InlineData(1)] + [InlineData(3)] + public async Task UseCallback_SelfSignedCertificate_ExpectedPolicyErrors(int requestCount) { - using (HttpClientHandler handler = CreateHttpClientHandler()) + using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false)) using (HttpClient client = CreateHttpClient(handler)) { - bool callbackCalled = false; - X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate(); + int callbackCount = 0; + using X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate(); handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => { - callbackCalled = true; + callbackCount++; Assert.NotNull(request); Assert.NotNull(cert); Assert.NotNull(chain); @@ -341,12 +454,23 @@ public async Task UseCallback_SelfSignedCertificate_ExpectedPolicyErrors() await LoopbackServer.CreateServerAsync(async (server, url) => { - await TestHelper.WhenAllCompletedOrAnyFailed( - server.AcceptConnectionSendResponseAndCloseAsync(), - client.GetAsync($"https://{certificate.GetNameInfo(X509NameType.SimpleName, false)}:{url.Port}/")); + for (int i = 0; i < requestCount; i++) + { +#if NETFRAMEWORK + // The loopback authentication overload disposes its certificate after each handshake. + options.Certificate = new X509Certificate2(certificate); +#endif + Task responseTask = client.GetAsync($"https://{certificate.GetNameInfo(X509NameType.SimpleName, false)}:{url.Port}/"); + await TestHelper.WhenAllCompletedOrAnyFailed( + server.AcceptConnectionSendResponseAndCloseAsync(), + responseTask); + using HttpResponseMessage response = await responseTask; + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(i + 1, callbackCount); + } }, options); - Assert.True(callbackCalled); + Assert.Equal(requestCount, callbackCount); } } @@ -411,7 +535,8 @@ await RemoteExecutor.Invoke(async (useVersionString, allowAllCertificatesString) var version = Version.Parse(useVersionString); using HttpClientHandler handler = CreateHttpClientHandler( useVersion: version, - allowAllCertificates: version >= HttpVersion20.Value && bool.Parse(allowAllCertificatesString)); + allowAllCertificates: version >= HttpVersion20.Value && bool.Parse(allowAllCertificatesString), + allowTlsResume: false); using HttpClient client = CreateHttpClient(handler, useVersionString); await Assert.ThrowsAsync(() => client.GetAsync(Url)); diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.SslProtocols.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.SslProtocols.cs index 1bbe73dd0a3a04..48e8fb9901463e 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.SslProtocols.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.SslProtocols.cs @@ -103,7 +103,7 @@ public static IEnumerable GetAsync_AllowedSSLVersion_Succeeds_MemberDa public async Task GetAsync_AllowedSSLVersion_Succeeds(SslProtocols acceptedProtocol, bool requestOnlyThisProtocol) { int count = 0; - using (HttpClientHandler handler = CreateHttpClientHandler()) + using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false)) using (HttpClient client = CreateHttpClient(handler)) { handler.ServerCertificateCustomValidationCallback = diff --git a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs index 0ac084afc4e3e2..883189ef990f91 100644 --- a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs @@ -428,6 +428,7 @@ public static string GetConnectionCloseResponse(HttpStatusCode statusCode = Http public class Options : GenericLoopbackOptions { + public bool AllowTlsResume { get; set; } = true; public bool WebSocketEndpoint { get; set; } = false; public Func StreamWrapper { get; set; } public string Username { get; set; } @@ -477,25 +478,35 @@ public static async Task CreateAsync(SocketWrapper socket, Stream st if (httpOptions.UseSsl) { var sslStream = new SslStream(stream, false, delegate { return true; }); -#if !NETFRAMEWORK - SslServerAuthenticationOptions sslOptions = new SslServerAuthenticationOptions() + try { - EnabledSslProtocols = httpOptions.SslProtocols, - ServerCertificateContext = httpOptions.CertificateContext ?? SslStreamCertificateContext.Create(Configuration.Certificates.GetServerCertificate(), null), - ClientCertificateRequired = true, - }; +#if !NETFRAMEWORK + SslServerAuthenticationOptions sslOptions = new SslServerAuthenticationOptions() + { + AllowTlsResume = httpOptions.AllowTlsResume, + EnabledSslProtocols = httpOptions.SslProtocols, + ServerCertificateContext = httpOptions.CertificateContext ?? SslStreamCertificateContext.Create(Configuration.Certificates.GetServerCertificate(), null), + ClientCertificateRequired = true, + }; - await sslStream.AuthenticateAsServerAsync(sslOptions, default).ConfigureAwait(false); + await sslStream.AuthenticateAsServerAsync(sslOptions, default).ConfigureAwait(false); #else - using (X509Certificate2 cert = httpOptions.Certificate ?? Configuration.Certificates.GetServerCertificate()) + using (X509Certificate2 cert = httpOptions.Certificate ?? Configuration.Certificates.GetServerCertificate()) + { + await sslStream.AuthenticateAsServerAsync( + cert, + clientCertificateRequired: true, // allowed but not required + enabledSslProtocols: httpOptions.SslProtocols, + checkCertificateRevocation: false).ConfigureAwait(false); + } +#endif + } + catch { - await sslStream.AuthenticateAsServerAsync( - cert, - clientCertificateRequired: true, // allowed but not required - enabledSslProtocols: httpOptions.SslProtocols, - checkCertificateRevocation: false).ConfigureAwait(false); + sslStream.Dispose(); + throw; } -#endif + stream = sslStream; } diff --git a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs index 8ff0e437526bac..ab42ead7ecf533 100644 --- a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs +++ b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs @@ -109,17 +109,20 @@ public static byte[] GenerateRandomContent(int size) return data; } - public static X509Certificate2 CreateServerSelfSignedCertificate(string name = "localhost") + public static X509Certificate2 CreateServerSelfSignedCertificate(string name = "localhost", bool expired = false) + => CreateServerCertificate(null, name, expired); + + public static X509Certificate2 CreateServerCertificate(X509Certificate2 issuerCertificate, string name = "localhost", bool expired = false) { - using (RSA root = RSA.Create()) + using (RSA key = RSA.Create()) { CertificateRequest req = new CertificateRequest( $"CN={name}", - root, + key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - req.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true)); + req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true)); req.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(req.PublicKey, false)); req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DataEncipherment, false)); req.CertificateExtensions.Add( @@ -136,13 +139,26 @@ public static X509Certificate2 CreateServerSelfSignedCertificate(string name = " builder.AddIpAddress(IPAddress.IPv6Loopback); req.CertificateExtensions.Add(builder.Build()); - DateTimeOffset start = DateTimeOffset.UtcNow.AddMinutes(-5); + DateTimeOffset start = expired ? DateTimeOffset.UtcNow.AddMonths(-2) : DateTimeOffset.UtcNow.AddMinutes(-5); DateTimeOffset end = start.AddMonths(1); - X509Certificate2 cert = req.CreateSelfSigned(start, end); + X509Certificate2 cert; + if (issuerCertificate is null) + { + cert = req.CreateSelfSigned(start, end); + } + else + { + using X509Certificate2 publicCertificate = req.Create(issuerCertificate, start, end, Guid.NewGuid().ToByteArray()); + cert = publicCertificate.CopyWithPrivateKey(key); + } + if (PlatformDetection.IsWindows) { - cert = X509CertificateLoader.LoadPkcs12(cert.Export(X509ContentType.Pfx), (string?)null); + using (cert) + { + return X509CertificateLoader.LoadPkcs12(cert.Export(X509ContentType.Pfx), (string?)null); + } } return cert; diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs index 7850ca8afcbd7d..1eb512e40877f5 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Windows.cs @@ -5,6 +5,9 @@ using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; using Microsoft.DotNet.XUnitExtensions; using Xunit; @@ -12,6 +15,63 @@ namespace System.Diagnostics.Tests { public partial class ProcessTests { + private static bool IsNotNanoServerNotServerCoreAndRemoteExecutorSupported => + PlatformDetection.IsNotWindowsNanoServer && + PlatformDetection.IsNotWindowsServerCore && + RemoteExecutor.IsSupported; + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern int MessageBoxW(IntPtr hWnd, string lpText, string lpCaption, uint uType); + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern IntPtr FindWindowW(string? lpClassName, string? lpWindowName); + + // Creates a visible top-level window with a known title. + // Shows a MessageBox on a background thread, waits until the window is visible via FindWindowW, + // then signals the parent via stdout and blocks until killed by the parent. + private static int CreateMainWindowWithTitle() + { + // MessageBoxW blocks the current thread until the user closes the message box + Thread t = new Thread(() => MessageBoxW(IntPtr.Zero, string.Empty, "Test Main Window", 0 /* MB_OK */)); + t.IsBackground = true; + t.Start(); + + // Spin until the MessageBox window is actually visible before signaling the parent. + SpinWait.SpinUntil(() => FindWindowW(null, "Test Main Window") != IntPtr.Zero); + + // Signal the parent that the window is ready. + Console.WriteLine("ready"); + + // Block until the parent kills this process. + Thread.Sleep(Timeout.Infinite); + return RemoteExecutor.SuccessExitCode; + } + + [ConditionalFact(typeof(ProcessTests), nameof(IsNotNanoServerNotServerCoreAndRemoteExecutorSupported))] + [OuterLoop("Pops UI")] + [PlatformSpecific(TestPlatforms.Windows)] + public async Task MainWindowHandle_And_Title_GetWithGui_ShouldRefresh_Windows() + { + using Process process = CreateProcess(CreateMainWindowWithTitle); + process.StartInfo.RedirectStandardOutput = true; + process.Start(); + + try + { + // Wait for child to signal that its window is ready. + Assert.Equal("ready", await process.StandardOutput.ReadLineAsync()); + + process.Refresh(); + Assert.NotEqual(IntPtr.Zero, process.MainWindowHandle); + Assert.Equal("Test Main Window", process.MainWindowTitle); + } + finally + { + process.Kill(); + process.WaitForExit(); + } + } + private string WriteScriptFile(string directory, string name, int returnValue) { string filename = Path.Combine(directory, name); diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs index 88d8c78073ebb8..867648bfdfabb7 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs @@ -1817,76 +1817,6 @@ public void MainWindowHandle_GetNotStarted_ThrowsInvalidOperationException() Assert.Throws(() => process.MainWindowHandle); } - [ConditionalFact(typeof(PlatformDetection), - nameof(PlatformDetection.IsNotWindowsNanoServer), // it needs Notepad - nameof(PlatformDetection.IsNotWindowsServerCore))] // explained in https://github.com/dotnet/runtime/pull/44972 - [OuterLoop("Pops UI")] - [PlatformSpecific(TestPlatforms.Windows)] - public void MainWindowHandle_GetWithGui_ShouldRefresh_Windows() - { - const string ExePath = "notepad.exe"; - Assert.True(IsProgramInstalled(ExePath), "Notepad is not installed"); - - using (Process process = Process.Start(ExePath)) - { - try - { - for (int attempt = 0; attempt < 50; ++attempt) - { - process.Refresh(); - if (process.MainWindowHandle != IntPtr.Zero) - { - break; - } - - Thread.Sleep(100); - } - - Assert.NotEqual(IntPtr.Zero, process.MainWindowHandle); - } - finally - { - process.Kill(); - Assert.True(process.WaitForExit(WaitInMS)); - } - } - } - - [ConditionalFact(typeof(PlatformDetection), - nameof(PlatformDetection.IsNotWindowsNanoServer), // it needs Notepad - nameof(PlatformDetection.IsNotWindowsServerCore))] // explained in https://github.com/dotnet/runtime/pull/44972 - [OuterLoop("Pops UI")] - [PlatformSpecific(TestPlatforms.Windows)] - public void MainWindowTitle_GetWithGui_ShouldRefresh_Windows() - { - const string ExePath = "notepad.exe"; - Assert.True(IsProgramInstalled(ExePath), "Notepad is not installed"); - - using (Process process = Process.Start(new ProcessStartInfo(ExePath))) - { - try - { - for (int attempt = 0; attempt < 50; ++attempt) - { - process.Refresh(); - if (process.MainWindowTitle != string.Empty) - { - break; - } - - Thread.Sleep(100); - } - - Assert.NotEqual(string.Empty, process.MainWindowTitle); - } - finally - { - process.Kill(); - Assert.True(process.WaitForExit(WaitInMS)); - } - } - } - [Fact] [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "libproc is not supported on iOS/tvOS")] public void RefreshResetsAllRefreshableFields() diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/HttpClientHandlerTestBase.WinHttpHandler.cs b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/HttpClientHandlerTestBase.WinHttpHandler.cs index 81773638085d93..ff841013d1e199 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/HttpClientHandlerTestBase.WinHttpHandler.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/HttpClientHandlerTestBase.WinHttpHandler.cs @@ -10,7 +10,8 @@ public abstract partial class HttpClientHandlerTestBase : FileCleanupTestBase { protected static bool IsWinHttpHandler => true; - protected static WinHttpClientHandler CreateHttpClientHandler(Version useVersion = null, bool allowAllCertificates = true) + // WinHTTP owns TLS and does not expose SslStream's resumption setting. + protected static WinHttpClientHandler CreateHttpClientHandler(Version useVersion = null, bool allowAllCertificates = true, bool allowTlsResume = true) { useVersion ??= HttpVersion.Version11; @@ -26,8 +27,11 @@ protected static WinHttpClientHandler CreateHttpClientHandler(Version useVersion protected WinHttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(UseVersion); - protected static WinHttpClientHandler CreateHttpClientHandler(string useVersionString) => - CreateHttpClientHandler(Version.Parse(useVersionString)); + protected WinHttpClientHandler CreateHttpClientHandler(bool allowTlsResume) => + CreateHttpClientHandler(UseVersion, allowTlsResume: allowTlsResume); + + protected static WinHttpClientHandler CreateHttpClientHandler(string useVersionString, bool allowTlsResume = true) => + CreateHttpClientHandler(Version.Parse(useVersionString), allowTlsResume: allowTlsResume); protected static HttpRequestMessage CreateRequest(HttpMethod method, Uri uri, Version version, bool exactVersion = false) => new HttpRequestMessage(method, uri) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs index 76e10d1628df5d..ea327a3cdc10fe 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs @@ -667,7 +667,7 @@ public async Task ServerCertificateCustomValidationCallback_Succeeds() HttpRequestMessage? callbackRequest = null; int invocationCount = 0; - var httpClientHandler = CreateHttpClientHandler(); + var httpClientHandler = CreateHttpClientHandler(allowTlsResume: false); httpClientHandler.ServerCertificateCustomValidationCallback = (request, _, _, _) => { callbackRequest = request; diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs index 662fd3479ce1a3..28350a5dff902e 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs @@ -46,7 +46,7 @@ public static bool IsHttp3Supported } } - protected static HttpClientHandler CreateHttpClientHandler(Version useVersion = null, bool allowAllCertificates = true) + protected static HttpClientHandler CreateHttpClientHandler(Version useVersion = null, bool allowAllCertificates = true, bool allowTlsResume = true) { useVersion ??= HttpVersion.Version11; @@ -58,6 +58,13 @@ protected static HttpClientHandler CreateHttpClientHandler(Version useVersion = handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates; } + // Native mobile handlers do not use SslStream or expose its resumption setting. + if (!allowTlsResume && PlatformDetection.IsNotBrowser && PlatformDetection.IsNotWasi && + GetUnderlyingSocketsHttpHandler(handler) is SocketsHttpHandler socketsHandler) + { + socketsHandler.SslOptions.AllowTlsResume = false; + } + return handler; } @@ -71,8 +78,11 @@ protected static Http3LoopbackServer CreateHttp3LoopbackServer(Http3Options opti protected HttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(UseVersion); - protected static HttpClientHandler CreateHttpClientHandler(string useVersionString) => - CreateHttpClientHandler(Version.Parse(useVersionString)); + protected HttpClientHandler CreateHttpClientHandler(bool allowTlsResume) => + CreateHttpClientHandler(UseVersion, allowTlsResume: allowTlsResume); + + protected static HttpClientHandler CreateHttpClientHandler(string useVersionString, bool allowTlsResume = true) => + CreateHttpClientHandler(Version.Parse(useVersionString), allowTlsResume: allowTlsResume); protected static SocketsHttpHandler GetUnderlyingSocketsHttpHandler(HttpClientHandler handler) { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index 1efa1d9e47201f..2bf1f3314cdb26 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -468,6 +468,98 @@ await server.AcceptConnectionAsync(async connection => public sealed class SocketsHttpHandler_HttpClientHandler_ServerCertificates_Test : HttpClientHandler_ServerCertificates_Test { public SocketsHttpHandler_HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { } + + [Theory] + [InlineData(true)] + [InlineData(false)] + [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.Linux | TestPlatforms.OSX)] + public void CreateHttpClientHandler_AllowTlsResume_ConfiguresSslOptions(bool allowTlsResume) + { + using HttpClientHandler defaultHandler = CreateHttpClientHandler(); + Assert.True(GetUnderlyingSocketsHttpHandler(defaultHandler).SslOptions.AllowTlsResume); + + using HttpClientHandler instanceHandler = CreateHttpClientHandler(allowTlsResume: allowTlsResume); + Assert.Equal(allowTlsResume, GetUnderlyingSocketsHttpHandler(instanceHandler).SslOptions.AllowTlsResume); + Assert.Null(instanceHandler.ServerCertificateCustomValidationCallback); + + using HttpClientHandler versionHandler = CreateHttpClientHandler(UseVersion, allowAllCertificates: false, allowTlsResume: allowTlsResume); + Assert.Equal(allowTlsResume, GetUnderlyingSocketsHttpHandler(versionHandler).SslOptions.AllowTlsResume); + Assert.Null(versionHandler.ServerCertificateCustomValidationCallback); + + using HttpClientHandler stringHandler = CreateHttpClientHandler(UseVersion.ToString(), allowTlsResume: allowTlsResume); + Assert.Equal(allowTlsResume, GetUnderlyingSocketsHttpHandler(stringHandler).SslOptions.AllowTlsResume); + Assert.NotNull(stringHandler.ServerCertificateCustomValidationCallback); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.Linux | TestPlatforms.OSX)] + public async Task NoCallback_RevokedCertificate_RevocationChecking_MatchesExpected(bool checkRevocation) + { + using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates("localhost"); + pkiHolder.Intermediates[^1].Revoke(pkiHolder.EndEntity, pkiHolder.EndEntity.NotBefore); + + using HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false); + handler.CheckCertificateRevocationList = checkRevocation; + SocketsHttpHandler socketsHandler = GetUnderlyingSocketsHttpHandler(handler); + + var policy = new X509ChainPolicy + { + TrustMode = X509ChainTrustMode.CustomRootTrust, + RevocationMode = socketsHandler.SslOptions.CertificateRevocationCheckMode, + RevocationFlag = X509RevocationFlag.EndCertificateOnly, + DisableCertificateDownloads = true, + CustomTrustStore = { pkiHolder.IssuerChain[^1] }, + }; + policy.ExtraStore.AddRange(pkiHolder.IssuerChain); + socketsHandler.SslOptions.CertificateChainPolicy = policy; + + using HttpClient client = CreateHttpClient(handler); + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + if (checkRevocation) + { + HttpRequestException exception = await Assert.ThrowsAsync(() => client.GetAsync(uri)); + Assert.Equal(HttpRequestError.SecureConnectionError, exception.HttpRequestError); + Assert.IsType(exception.InnerException); + } + else + { + using HttpResponseMessage response = await client.GetAsync(uri); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + }, async server => + { + try + { + if (checkRevocation) + { + await server.AcceptConnectionAsync(_ => Task.CompletedTask); + } + else + { + await server.HandleRequestAsync(); + } + } + catch (Exception e) when (checkRevocation && (e is AuthenticationException or IOException)) + { + _output.WriteLine($"Expected certificate validation failure: {e}"); + } + }, + options: new GenericLoopbackOptions + { + UseSsl = true, + Certificate = pkiHolder.EndEntity, + SslProtocols = SslProtocols.None, + }); + + using X509Chain chain = new X509Chain(); + chain.ChainPolicy = policy; + Assert.Equal(!checkRevocation, chain.Build(pkiHolder.EndEntity)); + Assert.Equal(checkRevocation ? X509ChainStatusFlags.Revoked : X509ChainStatusFlags.NoError, + chain.ChainStatus.Aggregate(X509ChainStatusFlags.NoError, (flags, status) => flags | status.Status)); + } } [SkipOnPlatform(TestPlatforms.Browser, "ResponseDrainTimeout is not supported on Browser")] @@ -4700,7 +4792,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync( policy.ExtraStore.AddRange(_certificateSetup.ServerChain); policy.CustomTrustStore.Add(_certificateSetup.ServerChain[^1]); - socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy }; + socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy, AllowTlsResume = false }; using HttpClient client = CreateHttpClient(handler); client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact; HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion, VersionPolicy = HttpVersionPolicy.RequestVersionExact }; @@ -4733,7 +4825,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync( policy.ExtraStore.AddRange(_certificateSetup.ServerChain); policy.CustomTrustStore.Add(_certificateSetup.ServerChain[^1]); - socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy }; + socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy, AllowTlsResume = false }; using HttpClient client = CreateHttpClient(handler); client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact; HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion, VersionPolicy = HttpVersionPolicy.RequestVersionExact }; @@ -4772,7 +4864,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync( policy.ExtraStore.AddRange(_certificateSetup.ServerChain); policy.CustomTrustStore.Add(_certificateSetup.ServerChain[^1]); - socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy }; + socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy, AllowTlsResume = false }; using HttpClient client = CreateHttpClient(handler); client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact; diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketOptionsTests.cs b/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketOptionsTests.cs index 7e64f6baa61246..c74afce3dd4245 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketOptionsTests.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketOptionsTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Net.Http; +using System.Net.Http.Functional.Tests; using System.Net.Security; using System.Net.Test.Common; using System.Security.Cryptography.X509Certificates; @@ -172,41 +173,46 @@ public void RemoteCertificateValidationCallback_Roundtrips() [OuterLoop("Connects to remote service")] [ConditionalTheory(nameof(WebSocketsSupported))] - [InlineData(false)] - [InlineData(true)] + [InlineData(false, 1)] + [InlineData(true, 1)] + [InlineData(true, 3)] [SkipOnPlatform(TestPlatforms.Browser, "Certificates not supported on browser")] - public async Task RemoteCertificateValidationCallback_PassedRemoteCertificateInfo(bool secure) + public async Task RemoteCertificateValidationCallback_PassedRemoteCertificateInfo(bool secure, int connectionCount) { if (PlatformDetection.IsWindows7) { return; // see https://github.com/dotnet/runtime/issues/1491#issuecomment-376392057 for more details } - bool callbackInvoked = false; + int callbackCount = 0; - await LoopbackServer.CreateClientAndServerAsync(async uri => + // Disable resumption on the server because ClientWebSocketOptions has no such setting. + await LoopbackServer.CreateServerAsync(async (server, uri) => { - using (var cws = new ClientWebSocket()) - using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) + for (int i = 0; i < connectionCount; i++) { + using var cws = new ClientWebSocket(); + using var cts = new CancellationTokenSource(TimeOutMilliseconds); cws.Options.RemoteCertificateValidationCallback = (source, cert, chain, errors) => { Assert.NotNull(source); Assert.NotNull(cert); Assert.NotNull(chain); Assert.NotEqual(SslPolicyErrors.None, errors); - callbackInvoked = true; + callbackCount++; return true; }; - await cws.ConnectAsync(uri, cts.Token); + await TestHelper.WhenAllCompletedOrAnyFailed( + cws.ConnectAsync(uri, cts.Token), + server.AcceptConnectionAsync(async connection => + { + Assert.NotNull(await LoopbackHelper.WebSocketHandshakeAsync(connection)); + })); + Assert.Equal(secure ? i + 1 : 0, callbackCount); } - }, server => server.AcceptConnectionAsync(async connection => - { - Assert.NotNull(await LoopbackHelper.WebSocketHandshakeAsync(connection)); - }), - new LoopbackServer.Options { UseSsl = secure, WebSocketEndpoint = true }); + }, new LoopbackServer.Options { UseSsl = secure, WebSocketEndpoint = true, AllowTlsResume = false }); - Assert.Equal(secure, callbackInvoked); + Assert.Equal(secure ? connectionCount : 0, callbackCount); } [OuterLoop("Connects to remote service")] diff --git a/src/libraries/System.Numerics.Tensors/tests/Helpers.cs b/src/libraries/System.Numerics.Tensors/tests/Helpers.cs index 4e5e89bbb3cad8..cfd89731635c14 100644 --- a/src/libraries/System.Numerics.Tensors/tests/Helpers.cs +++ b/src/libraries/System.Numerics.Tensors/tests/Helpers.cs @@ -85,7 +85,7 @@ public static bool IsEqualWithTolerance(float expected, float actual, float? tol } else if (IntPtr.Size == 4 && floatTolerance != null) { - return (T?)(object)(NFloat)doubleTolerance; + return (T?)(object)(NFloat)floatTolerance; } } #endif diff --git a/src/libraries/System.Security.Cryptography.Xml/tests/EncryptedXmlTests.cs b/src/libraries/System.Security.Cryptography.Xml/tests/EncryptedXmlTests.cs index 5818d835ee473e..51f93bc584a972 100644 --- a/src/libraries/System.Security.Cryptography.Xml/tests/EncryptedXmlTests.cs +++ b/src/libraries/System.Security.Cryptography.Xml/tests/EncryptedXmlTests.cs @@ -610,7 +610,7 @@ public static void EncryptedXml_DecryptEncryptedKey_MaxCount_AppContext() }).Dispose(); } - [Fact] + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public static void EncryptedXml_DecryptDocument_MaxCount_Default() { RemoteExecutor.Invoke(static () =>