From 799f3dcd97c673d157b2883dccea8598e2fe3fa7 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 03:42:36 +0000 Subject: [PATCH 1/7] Fix flaky MainWindow tests by replacing notepad.exe with a deterministic RemoteExecutor helper (#125907) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: danmoseley <6385855+danmoseley@users.noreply.github.com> Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> (cherry picked from commit a4bfa0d8a4fdc513c9a8d3dec57931c901e8ef2b) --- .../tests/ProcessTests.Windows.cs | 60 ++++++++++++++++ .../tests/ProcessTests.cs | 70 ------------------- 2 files changed, 60 insertions(+), 70 deletions(-) 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() From 7766e894ccd23eb599f72c5fdb47a6a9c73a0a92 Mon Sep 17 00:00:00 2001 From: Radek Zikmund <32671551+rzikm@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:06:29 +0200 Subject: [PATCH 2/7] Isolate HttpClient certificate validation tests from TLS resumption (#135273) Resumed initial TLS handshakes now skip certificate revalidation and user validation callbacks by default (#133174). HttpClient tests that require those callbacks, certificate-policy checks, or certificate rejection can therefore fail or bypass their assertions after another connection populates the session cache. This follows the SslStream test-isolation approach in #135083. Add an optional `allowTlsResume = true` option to the existing handler helpers and request full handshakes only in validation-dependent tests. Preserve parameterless helper behavior, version-specific certificate defaults, and unrelated tests. The option reaches `SocketsHttpHandler.SslOptions.AllowTlsResume`; WinHTTP and native mobile handlers retain their native behavior because they do not expose this setting. Custom-policy tests set the option in their replacement SSL options rather than losing it when replacing the bag. Add coverage for default/true/false helper forwarding and repeated callback execution across fresh connections. The repeated test follows the existing .NET Framework loopback certificate-ownership convention. No product code or public API changes. With resumption enabled, the repeated self-signed test reproduced a failure on the second fresh connection: expected callback count 2, actual 1. With resumption disabled, all three callbacks execute. Sampled CI stacks also show callback-not-called and missing-certificate-rejection assertions. Separate badssl handshake resets and connection timeouts are not established resumption failures and are not claimed fixed here. HTTP/3 callback coverage passes, but QUIC does not currently consume `AllowTlsResume`; this is not a claimed QUIC-resumption fix. Validation was on Windows x64 only, with Release CoreCLR and Debug libraries. Other platforms and enterprise tests were not run. All commands ran from a short `subst` path (`W:\`). - `.\build.cmd clr+libs -rc release`: passed, zero warnings/errors. Each project below was built and tested with `.\.dotnet\dotnet.exe build /t:Test /p:RuntimeConfiguration=Release`: | Project | Result | |---|---| | `src\libraries\System.Net.Http\tests\FunctionalTests\System.Net.Http.Functional.Tests.csproj` | Passed: 5,435 passed, 48 skipped, zero failures | | `src\libraries\System.Net.Http\tests\UnitTests\System.Net.Http.Unit.Tests.csproj` | Passed: 2,677 passed, four skipped, zero failures | | `src\libraries\System.Net.Http.WinHttpHandler\tests\FunctionalTests\System.Net.Http.WinHttpHandler.Functional.Tests.csproj` | Passed: net11.0-windows 1,710 passed / 41 skipped; net481 640 passed / 28 skipped; zero failures | | `src\libraries\System.Net.Http.WinHttpHandler\tests\UnitTests\System.Net.Http.WinHttpHandler.Unit.Tests.csproj` | Passed: 173 passed, zero failures | External certificate and SSL-protocol coverage: ```powershell .\.dotnet\dotnet.exe build src\libraries\System.Net.Http\tests\FunctionalTests\System.Net.Http.Functional.Tests.csproj /t:Test /p:RuntimeConfiguration=Release /p:Outerloop=true "/p:XUnitOptions=-class System.Net.Http.Functional.Tests.SocketsHttpHandler_HttpClientHandler_ServerCertificates_Test -class System.Net.Http.Functional.Tests.SocketsHttpHandler_HttpClientHandler_SslProtocols_Test" ``` Passed: 53 passed, zero skipped/failures. Negative control, with `allowTlsResume` temporarily enabled in the repeated self-signed test: ```powershell .\.dotnet\dotnet.exe build src\libraries\System.Net.Http\tests\FunctionalTests\System.Net.Http.Functional.Tests.csproj /t:Test /p:RuntimeConfiguration=Release "/p:XUnitOptions=-method *UseCallback_SelfSignedCertificate_ExpectedPolicyErrors" ``` Expected failure: one passed, one failed due to the skipped second callback. Restored `allowTlsResume: false` before the final successful full-suite and outerloop runs. `git diff --check`: passed. > [!NOTE] > This PR description and code changes were generated with assistance from GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> (cherry picked from commit a4802e5895d8092b2f87068bf82e5123ca2c8af6) --- ...ttpClientHandlerTest.ServerCertificates.cs | 55 ++++++++++++------- .../HttpClientHandlerTest.SslProtocols.cs | 2 +- .../tests/System/Net/Http/LoopbackServer.cs | 2 + ...ttpClientHandlerTestBase.WinHttpHandler.cs | 10 +++- .../HttpClientHandlerTest.Http3.cs | 2 +- ...lientHandlerTestBase.SocketsHttpHandler.cs | 16 +++++- .../FunctionalTests/SocketsHttpHandlerTest.cs | 28 +++++++++- .../tests/ClientWebSocketOptionsTests.cs | 36 +++++++----- 8 files changed, 106 insertions(+), 45 deletions(-) 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..fa880e5498baa2 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs @@ -32,9 +32,11 @@ public HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : bas // 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 +145,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 +188,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 +200,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(); @@ -221,7 +223,8 @@ public async Task UseCallback_CallbackThrowsException_ExceptionPropagatesAsBaseE [MemberData(nameof(CertificateValidationServers))] public async Task NoCallback_BadCertificate_ThrowsException(string url) { - using (HttpClient client = CreateHttpClient()) + using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false)) + using (HttpClient client = CreateHttpClient(handler)) { await Assert.ThrowsAsync(() => client.GetAsync(url)); } @@ -245,7 +248,7 @@ 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)) { @@ -261,7 +264,7 @@ public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails() private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string url, string useHttp2String, SslPolicyErrors expectedErrors) { - HttpClientHandler handler = CreateHttpClientHandler(useHttp2String); + HttpClientHandler handler = CreateHttpClientHandler(useHttp2String, allowTlsResume: false); using (HttpClient client = CreateHttpClient(handler, useHttp2String)) { bool callbackCalled = false; @@ -318,18 +321,20 @@ public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(string url, Ss } } - [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 +346,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 +427,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..ac0a47bdc6de0d 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; } @@ -480,6 +481,7 @@ public static async Task CreateAsync(SocketWrapper socket, Stream st #if !NETFRAMEWORK SslServerAuthenticationOptions sslOptions = new SslServerAuthenticationOptions() { + AllowTlsResume = httpOptions.AllowTlsResume, EnabledSslProtocols = httpOptions.SslProtocols, ServerCertificateContext = httpOptions.CertificateContext ?? SslStreamCertificateContext.Create(Configuration.Certificates.GetServerCertificate(), null), ClientCertificateRequired = true, 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..a180c8b67b9945 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -468,6 +468,28 @@ 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); + } } [SkipOnPlatform(TestPlatforms.Browser, "ResponseDrainTimeout is not supported on Browser")] @@ -4700,7 +4722,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 +4755,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 +4794,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")] From 0df3fa75b99a76b1e1ed49ff140933f0142f693c Mon Sep 17 00:00:00 2001 From: Radek Zikmund <32671551+rzikm@users.noreply.github.com> Date: Tue, 13 Oct 2026 12:48:28 +0200 Subject: [PATCH 3/7] Replace badssl HTTP certificate tests with loopback servers (#135618) HTTP certificate validation tests currently depend on public badssl.com endpoints, making their results sensitive to external availability and certificate configuration. Replace the applicable cases with loopback servers and certificates generated by the existing test helpers. - Cover expired, self-signed, and wrong-host certificates with default validation, custom callbacks, and validation-disabled handlers. These cases now run in the inner loop. - Use scoped `X509ChainPolicy` custom trust to isolate SocketsHttpHandler validation errors, and the existing local PKI/revocation responder to exercise both revocation settings without installing trusted roots. - Dispose provisional TLS streams when loopback authentication fails, with a regression test for deterministic cleanup. The two external, no-callback WinHTTP revocation tests are intentionally retained. WinHttpHandler cannot use scoped custom trust, and callback-enabled replacements would test managed chain validation rather than native WinHTTP revocation enforcement. Windows x64, using the `X:` subst path: - `build.cmd clr+libs -rc release`: passed. - Focused System.Net.Http certificate classes: 27 passed, none skipped. - Focused WinHTTP HTTP/1.1 and HTTP/2 certificate classes on `net11.0-windows`: 44 passed, none skipped. - Shared WinHTTP certificate tests on `net481`: 11 passed, none skipped. - Fourteen certificate/cleanup scenarios passed three consecutive reruns. - The cleanup regression failed before the helper fix and passed afterward. Linux, macOS, mobile, and the retained external WinHTTP revocation cases were not run. No product code or public API changes. > [!NOTE] > This pull request description was generated by GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Miha Zupan Copilot-Session: 63463cf1-4bfe-42e3-ad96-49f35e245dd1 (cherry picked from commit f521600964496a5ff81893f4f35618a8a5976a45) --- .../tests/System/Net/Configuration.Http.cs | 3 - .../HttpClientHandlerTest.AcceptAllCerts.cs | 36 ++-- ...ttpClientHandlerTest.ServerCertificates.cs | 179 +++++++++++++----- .../tests/System/Net/Http/LoopbackServer.cs | 39 ++-- .../tests/System/Net/Http/TestHelper.cs | 9 +- .../FunctionalTests/SocketsHttpHandlerTest.cs | 71 +++++++ 6 files changed, 252 insertions(+), 85 deletions(-) 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 fa880e5498baa2..6efabe381e1892 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs @@ -2,12 +2,17 @@ // 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.X509Certificates; using System.Threading.Tasks; @@ -26,7 +31,9 @@ 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) { } @@ -211,25 +218,86 @@ public async Task UseCallback_CallbackThrowsException_ExceptionPropagatesAsBaseE } } - public static readonly object[][] CertificateValidationServers = + [Theory] + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(false, true)] + public async Task NoCallback_BadCertificate_ThrowsException(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("Uses external servers")] - [ConditionalTheory(nameof(ClientSupportsDHECipherSuites))] - [MemberData(nameof(CertificateValidationServers))] - public async Task NoCallback_BadCertificate_ThrowsException(string url) - { using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false)) using (HttpClient client = CreateHttpClient(handler)) { - await Assert.ThrowsAsync(() => client.GetAsync(url)); + bool customChainPolicy = ConfigureCertificateChainPolicy(handler, certificate, trustCertificate: expired || wrongHostName); + + 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 + { + if (customChainPolicy || IsWinHttpHandler) + { + await server.AcceptConnectionAsync(_ => Task.CompletedTask); + } + else + { + // Native handlers may report generic errors, so acceptance must produce a successful response. + 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))] @@ -255,70 +323,85 @@ public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails() 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)] + 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 certificate = TestHelper.CreateServerSelfSignedCertificate( + name: wrongHostName ? "not-localhost" : "localhost", expired: expired); - private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string url, string useHttp2String, SslPolicyErrors expectedErrors) - { - HttpClientHandler handler = CreateHttpClientHandler(useHttp2String, allowTlsResume: false); - using (HttpClient client = CreateHttpClient(handler, useHttp2String)) + using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false)) + using (HttpClient client = CreateHttpClient(handler)) { + bool customChainPolicy = ConfigureCertificateChainPolicy(handler, certificate, trustCertificate: expired || wrongHostName); + 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; + } + + 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 bool ConfigureCertificateChainPolicy(HttpClientHandler handler, X509Certificate2 certificate, bool trustCertificate) { - const int SEC_E_BUFFER_TOO_SMALL = unchecked((int)0x80090321); - - if (!ClientSupportsDHECipherSuites) +#if !WINHTTPHANDLER_TEST + if (GetUnderlyingSocketsHttpHandler(handler) is SocketsHttpHandler socketsHandler) { - return; - } + var policy = new X509ChainPolicy + { + TrustMode = X509ChainTrustMode.CustomRootTrust, + RevocationMode = X509RevocationMode.NoCheck, + DisableCertificateDownloads = true, + }; - 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) - { - // Testing on old Windows versions can hit https://github.com/dotnet/runtime/issues/17005 - // Ignore SEC_E_BUFFER_TOO_SMALL error on such cases. + if (trustCertificate) + { + policy.CustomTrustStore.Add(certificate); + } + + socketsHandler.SslOptions.CertificateChainPolicy = policy; + return true; } +#endif + return false; } [Theory] diff --git a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs index ac0a47bdc6de0d..883189ef990f91 100644 --- a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs @@ -478,26 +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 { - AllowTlsResume = httpOptions.AllowTlsResume, - 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..929b3b4db42715 100644 --- a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs +++ b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs @@ -109,7 +109,7 @@ 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) { using (RSA root = RSA.Create()) { @@ -136,13 +136,16 @@ 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); 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.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index a180c8b67b9945..a2c482ccfdae74 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -490,6 +490,77 @@ public void CreateHttpClientHandler_AllowTlsResume_ConfiguresSslOptions(bool all 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", forceRsaCertificate: true); + 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")] From 6fb8e9cd2541fb822ba337e6e159ad30f6155d3b Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Sun, 18 Oct 2026 00:44:55 +0200 Subject: [PATCH 4/7] Fix Android HTTP certificate validation tests (#135975) (cherry picked from commit 39ab468a6fa07958d3a5232947af14e0672dfaf3) --- ...ttpClientHandlerTest.ServerCertificates.cs | 67 +++++++++++++------ .../tests/System/Net/Http/TestHelper.cs | 21 ++++-- 2 files changed, 63 insertions(+), 25 deletions(-) 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 6efabe381e1892..6304a74aeb54f6 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs @@ -14,6 +14,7 @@ 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; @@ -218,19 +219,33 @@ public async Task UseCallback_CallbackThrowsException_ExceptionPropagatesAsBaseE } } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CreateServerCertificate_IsEndEntity(bool selfSigned) + { + using X509Certificate2 rootCertificate = CreateCertificateAuthority(); + using X509Certificate2 certificate = TestHelper.CreateServerCertificate(selfSigned ? null : rootCertificate); + + 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 X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate( - name: wrongHostName ? "not-localhost" : "localhost", expired: expired); + using X509Certificate2 rootCertificate = CreateCertificateAuthority(); using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false)) using (HttpClient client = CreateHttpClient(handler)) { - bool customChainPolicy = ConfigureCertificateChainPolicy(handler, certificate, trustCertificate: expired || wrongHostName); + 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 => { @@ -250,15 +265,8 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => { try { - if (customChainPolicy || IsWinHttpHandler) - { - await server.AcceptConnectionAsync(_ => Task.CompletedTask); - } - else - { - // Native handlers may report generic errors, so acceptance must produce a successful response. - await server.HandleRequestAsync(); - } + // 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) { @@ -329,15 +337,18 @@ public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails() [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) { - using X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate( - name: wrongHostName ? "not-localhost" : "localhost", expired: expired); + using X509Certificate2 rootCertificate = CreateCertificateAuthority(); using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false)) using (HttpClient client = CreateHttpClient(handler)) { - bool customChainPolicy = ConfigureCertificateChainPolicy(handler, certificate, trustCertificate: expired || wrongHostName); + 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; @@ -348,6 +359,12 @@ public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(bool expired, 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; @@ -380,7 +397,18 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => } } - private static bool ConfigureCertificateChainPolicy(HttpClientHandler handler, X509Certificate2 certificate, bool trustCertificate) + private static X509Certificate2 CreateCertificateAuthority() + { + 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)); + + DateTimeOffset now = DateTimeOffset.UtcNow; + return request.CreateSelfSigned(now.AddYears(-1), now.AddYears(1)); + } + + private static bool ConfigureCertificateChainPolicy(HttpClientHandler handler, X509Certificate2 rootCertificate) { #if !WINHTTPHANDLER_TEST if (GetUnderlyingSocketsHttpHandler(handler) is SocketsHttpHandler socketsHandler) @@ -390,13 +418,10 @@ private static bool ConfigureCertificateChainPolicy(HttpClientHandler handler, X 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 }, }; - if (trustCertificate) - { - policy.CustomTrustStore.Add(certificate); - } - socketsHandler.SslOptions.CertificateChainPolicy = policy; return true; } diff --git a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs index 929b3b4db42715..ab42ead7ecf533 100644 --- a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs +++ b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs @@ -110,16 +110,19 @@ public static byte[] GenerateRandomContent(int size) } 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( @@ -139,7 +142,17 @@ public static X509Certificate2 CreateServerSelfSignedCertificate(string name = " 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) { using (cert) From 6d19282c57fb9b5d79131a4606aa70593b655e4e Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Mon, 19 Oct 2026 22:46:40 -0500 Subject: [PATCH 5/7] Guard EncryptedXml_DecryptDocument_MaxCount_Default with RemoteExecutor.IsSupported (#136119) `EncryptedXml_DecryptDocument_MaxCount_Default` (added in #135741) calls `RemoteExecutor.Invoke` from a plain `[Fact]`, so it throws `PlatformNotSupportedException` where RemoteExecutor isn't supported, e.g. the `runtime-coreclr crossgen2` outerloop R2R library runs ([build 1639697](https://dev.azure.com/dnceng-public/cbb18261-c48f-4abb-8651-8cdcb5474649/_build/results?buildId=1639697)). This changes it to `[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]`, matching its sibling `_AppContext` tests. > [!NOTE] > This PR was generated with the assistance of GitHub Copilot. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> (cherry picked from commit 677ab53860cec0e06422bc36c54f15e9763c39b9) --- .../System.Security.Cryptography.Xml/tests/EncryptedXmlTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 () => From 72bc0d53d479d830f0aa6ee4790c3ab3ba1e6dc6 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Wed, 11 Nov 2026 11:37:39 -0600 Subject: [PATCH 6/7] Drop main-only forceRsaCertificate argument in backported revocation test release/10.0's Configuration.Certificates.GenerateCertificates has no forceRsaCertificate parameter (added on main by #119265) and always generates RSA-2048 certificates, which is what forceRsaCertificate: true selects on main. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../tests/FunctionalTests/SocketsHttpHandlerTest.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index a2c482ccfdae74..2bf1f3314cdb26 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -497,8 +497,7 @@ public void CreateHttpClientHandler_AllowTlsResume_ConfiguresSslOptions(bool all [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", forceRsaCertificate: true); + using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates("localhost"); pkiHolder.Intermediates[^1].Revoke(pkiHolder.EndEntity, pkiHolder.EndEntity.NotBefore); using HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false); From c685e5e7ab737487be92b12e7160b83090b4caaa Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Wed, 11 Nov 2026 16:41:40 -0600 Subject: [PATCH 7/7] Fix NFloat tolerance selection on 32-bit in tensor test helper Partial backport of #127868: on 32-bit platforms DetermineTolerance cast doubleTolerance instead of floatTolerance for NFloat, so callers that only pass floatTolerance hit 'Nullable object must have a value'. The rest of #127868 (the stricter AssertEqualWithTolerance helper) is not backported because it exposes Hypot special-value differences on release/10.0 x64/x86 that do not reproduce on main. (cherry picked from part of commit 7aa830a03599a8255c2c4abf2947afc5b346cc6f) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/libraries/System.Numerics.Tensors/tests/Helpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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