From defd136bae2b98daab06ac259d2dea7d67cd5874 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Wed, 6 Aug 2025 18:16:00 +0200 Subject: [PATCH 1/7] Revert "Change HttpClient/SslStream default certificate revocation check mode to Online (#116098)" This reverts commit 6a4b7e3b1eb06d7c4782f692e617555d3fe944a6. --- .../Net/Configuration.Certificates.Dynamic.cs | 118 ++--- ...ttpClientHandlerTest.ServerCertificates.cs | 7 +- .../System/Net/Http/HttpClientHandlerTest.cs | 2 +- .../src/System.Net.Http.WinHttpHandler.csproj | 2 - .../src/System/Net/Http/WinHttpHandler.cs | 7 +- ....Net.Http.WinHttpHandler.Unit.Tests.csproj | 2 - .../tests/UnitTests/WinHttpHandlerTest.cs | 2 +- .../FunctionalTests/SocketsHttpHandlerTest.cs | 71 +-- .../tests/Functional/SmtpClientTlsTest.cs | 24 +- .../tests/FunctionalTests/MsQuicTests.cs | 403 ++++++++++-------- .../src/System.Net.Security.csproj | 2 - .../Net/Security/SslAuthenticationOptions.cs | 7 - .../SslClientAuthenticationOptions.cs | 2 +- .../SslServerAuthenticationOptions.cs | 2 +- .../CertificateValidationClientServer.cs | 5 +- .../SslStreamCertificateTrustTests.cs | 16 +- .../FunctionalTests/SslStreamFramingTest.cs | 4 +- .../SslStreamNetworkStreamTest.cs | 73 ++-- .../SslStreamRemoteExecutorTests.cs | 26 -- .../tests/FunctionalTests/SslStreamSniTest.cs | 69 +-- .../SslStreamStreamToStreamTest.cs | 23 +- .../SslAuthenticationOptionsTests.cs | 8 +- .../System.Net.Security.Unit.Tests.csproj | 2 - 23 files changed, 427 insertions(+), 450 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Configuration.Certificates.Dynamic.cs b/src/libraries/Common/tests/System/Net/Configuration.Certificates.Dynamic.cs index 185416759e725b..fa20d1f374ce36 100644 --- a/src/libraries/Common/tests/System/Net/Configuration.Certificates.Dynamic.cs +++ b/src/libraries/Common/tests/System/Net/Configuration.Certificates.Dynamic.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Net.Security; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -38,6 +37,29 @@ public static partial class Certificates private static readonly X509BasicConstraintsExtension s_eeConstraints = new X509BasicConstraintsExtension(false, false, 0, false); + private static X509Certificate2 s_dynamicServerCertificate; + private static X509Certificate2Collection s_dynamicCaCertificates; + private static object certLock = new object(); + + + // These Get* methods make a copy of the certificates so that consumers own the lifetime of the + // certificates handed back. Consumers are expected to dispose of their certs when done with them. + + public static X509Certificate2 GetDynamicServerCerttificate(X509Certificate2Collection? chainCertificates) + { + lock (certLock) + { + if (s_dynamicServerCertificate == null) + { + CleanupCertificates(); + (s_dynamicServerCertificate, s_dynamicCaCertificates) = GenerateCertificates("localhost", nameof(Configuration) + nameof(Certificates)); + } + + chainCertificates?.AddRange(s_dynamicCaCertificates); + return new X509Certificate2(s_dynamicServerCertificate); + } + } + public static void CleanupCertificates([CallerMemberName] string? testName = null, StoreName storeName = StoreName.CertificateAuthority) { string caName = $"O={testName}"; @@ -56,9 +78,7 @@ public static void CleanupCertificates([CallerMemberName] string? testName = nul } } } - catch - { - } + catch { }; try { @@ -75,9 +95,7 @@ public static void CleanupCertificates([CallerMemberName] string? testName = nul } } } - catch - { - } + catch { }; } internal static X509ExtensionCollection BuildTlsServerCertExtensions(string serverName) @@ -101,68 +119,7 @@ private static X509ExtensionCollection BuildTlsCertExtensions(string targetName, return extensions; } - internal class PkiHolder : IDisposable - { - internal CertificateAuthority Root { get; } - internal CertificateAuthority[] Intermediates { get; } - public X509Certificate2 EndEntity { get; } - public X509Certificate2Collection IssuerChain { get; } - internal RevocationResponder Responder { get; } - - private readonly string? _testName; - - public PkiHolder(string? testName, CertificateAuthority root, CertificateAuthority[] intermediates, X509Certificate2 endEntity, RevocationResponder responder) - { - _testName = testName; - Root = root; - Intermediates = intermediates; - EndEntity = endEntity; - Responder = responder; - - // Walk the intermediates backwards so we build the chain collection as - // Issuer3 - // Issuer2 - // Issuer1 - // Root - IssuerChain = new X509Certificate2Collection(); - for (int i = intermediates.Length - 1; i >= 0; i--) - { - CertificateAuthority authority = intermediates[i]; - - IssuerChain.Add(authority.CloneIssuerCert()); - } - - IssuerChain.Add(root.CloneIssuerCert()); - } - - public SslStreamCertificateContext CreateSslStreamCertificateContext() - { - return SslStreamCertificateContext.Create(EndEntity, IssuerChain); - } - - public void Dispose() - { - foreach (CertificateAuthority authority in Intermediates) - { - authority.Dispose(); - } - Root.Dispose(); - EndEntity.Dispose(); - Responder.Dispose(); - - foreach (X509Certificate2 authority in IssuerChain) - { - authority.Dispose(); - } - - if (PlatformDetection.IsWindows && _testName != null) - { - CleanupCertificates(_testName); - } - } - } - - internal static PkiHolder GenerateCertificates(string targetName, [CallerMemberName] string? testName = null, bool longChain = false, bool serverCertificate = true, bool ephemeralKey = false) + public static (X509Certificate2 certificate, X509Certificate2Collection) GenerateCertificates(string targetName, [CallerMemberName] string? testName = null, bool longChain = false, bool serverCertificate = true, bool ephemeralKey = false) { const int keySize = 2048; if (PlatformDetection.IsWindows && testName != null) @@ -174,7 +131,7 @@ internal static PkiHolder GenerateCertificates(string targetName, [CallerMemberN X509ExtensionCollection extensions = BuildTlsCertExtensions(targetName, serverCertificate); CertificateAuthority.BuildPrivatePki( - PkiOptions.AllRevocation, + PkiOptions.IssuerRevocationViaCrl, out RevocationResponder responder, out CertificateAuthority root, out CertificateAuthority[] intermediates, @@ -185,6 +142,24 @@ internal static PkiHolder GenerateCertificates(string targetName, [CallerMemberN keyFactory: CertificateAuthority.KeyFactory.RSASize(keySize), extensions: extensions); + // Walk the intermediates backwards so we build the chain collection as + // Issuer3 + // Issuer2 + // Issuer1 + // Root + for (int i = intermediates.Length - 1; i >= 0; i--) + { + CertificateAuthority authority = intermediates[i]; + + chain.Add(authority.CloneIssuerCert()); + authority.Dispose(); + } + + chain.Add(root.CloneIssuerCert()); + + responder.Dispose(); + root.Dispose(); + if (!ephemeralKey && PlatformDetection.IsWindows) { X509Certificate2 ephemeral = endEntity; @@ -192,8 +167,9 @@ internal static PkiHolder GenerateCertificates(string targetName, [CallerMemberN ephemeral.Dispose(); } - return new PkiHolder(testName, root, intermediates, endEntity, responder); + return (endEntity, chain); } + } } } 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 d027b87f0d86df..05149276a1b158 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs @@ -48,7 +48,7 @@ public void Ctor_ExpectedDefaultValues() using (HttpClientHandler handler = CreateHttpClientHandler()) { Assert.Null(handler.ServerCertificateCustomValidationCallback); - Assert.True(handler.CheckCertificateRevocationList); + Assert.False(handler.CheckCertificateRevocationList); } } @@ -232,9 +232,7 @@ public async Task NoCallback_BadCertificate_ThrowsException(string url) [ActiveIssue("https://github.com/dotnet/runtime/issues/106634", typeof(PlatformDetection), nameof(PlatformDetection.IsAlpine))] public async Task NoCallback_RevokedCertificate_NoRevocationChecking_Succeeds() { - HttpClientHandler handler = CreateHttpClientHandler(); - handler.CheckCertificateRevocationList = false; - using (HttpClient client = CreateHttpClient(handler)) + using (HttpClient client = CreateHttpClient()) using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RevokedCertRemoteServer)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -246,6 +244,7 @@ public async Task NoCallback_RevokedCertificate_NoRevocationChecking_Succeeds() public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails() { HttpClientHandler handler = CreateHttpClientHandler(); + handler.CheckCertificateRevocationList = true; using (HttpClient client = CreateHttpClient(handler)) { await Assert.ThrowsAsync(() => client.GetAsync(Configuration.Http.RevokedCertRemoteServer)); diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index 450edaab12e377..4fc25003717f2f 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -78,7 +78,7 @@ public void Ctor_ExpectedDefaultPropertyValues() Assert.True(handler.SupportsRedirectConfiguration); // Changes from .NET Framework. - Assert.True(handler.CheckCertificateRevocationList); + Assert.False(handler.CheckCertificateRevocationList); Assert.Equal(0, handler.MaxRequestContentBufferSize); Assert.Equal(SslProtocols.None, handler.SslProtocols); } diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj b/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj index 2cba1a88684d98..7e6af9f6b954f9 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj @@ -96,8 +96,6 @@ System.Net.Http.WinHttpHandler - ? _serverCertificateValidationCallback; - private bool _checkCertificateRevocationList = DefaultCertificateRevocationCheck; + private bool _checkCertificateRevocationList; private ClientCertificateOption _clientCertificateOption = ClientCertificateOption.Manual; private X509Certificate2Collection? _clientCertificates; // Only create collection when required. private ICredentials? _serverCredentials; diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/UnitTests/System.Net.Http.WinHttpHandler.Unit.Tests.csproj b/src/libraries/System.Net.Http.WinHttpHandler/tests/UnitTests/System.Net.Http.WinHttpHandler.Unit.Tests.csproj index e2553e9b42e680..edabede4649558 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/UnitTests/System.Net.Http.WinHttpHandler.Unit.Tests.csproj +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/UnitTests/System.Net.Http.WinHttpHandler.Unit.Tests.csproj @@ -24,8 +24,6 @@ Link="Common\Interop\Windows\WinHttp\Interop.SafeWinHttpHandle.cs" /> - _pkiHolder.EndEntity; - public X509Certificate2Collection ServerChain => _pkiHolder.IssuerChain; - - private readonly Configuration.Certificates.PkiHolder _pkiHolder; - - public CertificateSetup() - { - _pkiHolder = Configuration.Certificates.GenerateCertificates("localhost", nameof(HttpClientHandlerTestBase), longChain: true); - } - - public SslStreamCertificateContext CreateSslStreamCertificateContext() => _pkiHolder.CreateSslStreamCertificateContext(); - - public void Dispose() - { - _pkiHolder.Dispose(); - } - } - public sealed class SocketsHttpHandler_HttpClientHandler_Asynchrony_Test_Http11 : SocketsHttpHandler_HttpClientHandler_Asynchrony_Test { public SocketsHttpHandler_HttpClientHandler_Asynchrony_Test_Http11(ITestOutputHelper output) : base(output) { } @@ -2540,7 +2520,7 @@ public void SslOptions_GetSet_Roundtrips() Assert.True(options.AllowRenegotiation); Assert.Null(options.ApplicationProtocols); - Assert.Equal(X509RevocationMode.Online, options.CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.NoCheck, options.CertificateRevocationCheckMode); Assert.Null(options.ClientCertificates); Assert.Equal(SslProtocols.None, options.EnabledSslProtocols); Assert.Equal(EncryptionPolicy.RequireEncryption, options.EncryptionPolicy); @@ -4404,17 +4384,15 @@ public SocketsHttpHandler_RequestContentLengthMismatchTest_Http3(ITestOutputHelp [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] public abstract class SocketsHttpHandler_SecurityTest : HttpClientHandlerTestBase { - private readonly CertificateSetup _certificateSetup; - - public SocketsHttpHandler_SecurityTest(ITestOutputHelper output, CertificateSetup certificateSetup) : base(output) - { - _certificateSetup = certificateSetup; - } + public SocketsHttpHandler_SecurityTest(ITestOutputHelper output) : base(output) { } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] public async Task SslOptions_CustomTrust_Ok() { - GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = new X509Certificate2(_certificateSetup.ServerCert) }; + X509Certificate2Collection caCerts = new X509Certificate2Collection(); + X509Certificate2 certificate = Configuration.Certificates.GetDynamicServerCerttificate(caCerts); + + GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = certificate }; await LoopbackServerFactory.CreateClientAndServerAsync( async uri => { @@ -4427,8 +4405,8 @@ await LoopbackServerFactory.CreateClientAndServerAsync( TrustMode = X509ChainTrustMode.CustomRootTrust, }; - policy.ExtraStore.AddRange(_certificateSetup.ServerChain); - policy.CustomTrustStore.Add(_certificateSetup.ServerChain[^1]); + policy.ExtraStore.AddRange(caCerts); + policy.CustomTrustStore.Add(caCerts[caCerts.Count - 1]); socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy }; using HttpClient client = CreateHttpClient(handler); client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact; @@ -4447,22 +4425,15 @@ await LoopbackServerFactory.CreateClientAndServerAsync( [Fact] public async Task SslOptions_InvalidName_Throws() { - GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = new X509Certificate2(_certificateSetup.ServerCert) }; + X509Certificate2Collection caCerts = new X509Certificate2Collection(); + using X509Certificate2 certificate = Configuration.Certificates.GetDynamicServerCerttificate(caCerts); + + GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = certificate }; await LoopbackServerFactory.CreateClientAndServerAsync( async uri => { using HttpClientHandler handler = CreateHttpClientHandler(allowAllCertificates: false); var socketsHandler = (SocketsHttpHandler)GetUnderlyingSocketsHttpHandler(handler); - - var policy = new X509ChainPolicy() - { - RevocationMode = X509RevocationMode.NoCheck, - TrustMode = X509ChainTrustMode.CustomRootTrust, - }; - - policy.ExtraStore.AddRange(_certificateSetup.ServerChain); - policy.CustomTrustStore.Add(_certificateSetup.ServerChain[^1]); - socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy }; using HttpClient client = CreateHttpClient(handler); client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact; HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion, VersionPolicy = HttpVersionPolicy.RequestVersionExact }; @@ -4485,7 +4456,10 @@ await LoopbackServerFactory.CreateClientAndServerAsync( [Fact] public async Task SslOptions_CustomPolicy_IgnoresNameMismatch() { - GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = new X509Certificate2(_certificateSetup.ServerCert) }; + X509Certificate2Collection caCerts = new X509Certificate2Collection(); + X509Certificate2 certificate = Configuration.Certificates.GetDynamicServerCerttificate(caCerts); + + GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = certificate }; await LoopbackServerFactory.CreateClientAndServerAsync( async uri => { @@ -4499,8 +4473,8 @@ await LoopbackServerFactory.CreateClientAndServerAsync( VerificationFlags = X509VerificationFlags.IgnoreInvalidName, }; - policy.ExtraStore.AddRange(_certificateSetup.ServerChain); - policy.CustomTrustStore.Add(_certificateSetup.ServerChain[^1]); + policy.ExtraStore.AddRange(caCerts); + policy.CustomTrustStore.Add(caCerts[caCerts.Count - 1]); socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy }; using HttpClient client = CreateHttpClient(handler); @@ -4519,9 +4493,9 @@ await LoopbackServerFactory.CreateClientAndServerAsync( } } - public sealed class SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http11 : SocketsHttpHandler_SecurityTest, IClassFixture + public sealed class SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http11 : SocketsHttpHandler_SecurityTest { - public SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http11(ITestOutputHelper output, CertificateSetup certificateSetup) : base(output, certificateSetup) { } + public SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http11(ITestOutputHelper output) : base(output) { } protected override Version UseVersion => HttpVersion.Version11; #if DEBUG @@ -4571,10 +4545,9 @@ await server.AcceptConnectionAsync(async connection => } [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))] - public sealed class SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http2 : SocketsHttpHandler_SecurityTest, IClassFixture + public sealed class SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http2 : SocketsHttpHandler_SecurityTest { - public SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http2(ITestOutputHelper output, CertificateSetup certificateSetup) : base(output, certificateSetup) { } - + public SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http2(ITestOutputHelper output) : base(output) { } protected override Version UseVersion => HttpVersion.Version20; } diff --git a/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTlsTest.cs b/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTlsTest.cs index 2f46b2a0e3babf..794fc2e7bf45f5 100644 --- a/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTlsTest.cs +++ b/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTlsTest.cs @@ -13,26 +13,26 @@ namespace System.Net.Mail.Tests { - using Configuration = System.Net.Test.Common.Configuration; - // Common test setup to share across test cases. public class CertificateSetup : IDisposable { - public X509Certificate2 ServerCert => _pkiHolder.EndEntity; - public X509Certificate2Collection ServerChain => _pkiHolder.IssuerChain; - - private readonly Configuration.Certificates.PkiHolder _pkiHolder; + public readonly X509Certificate2 serverCert; + public readonly X509Certificate2Collection serverChain; + public readonly SslStreamCertificateContext serverCertContext; public CertificateSetup() { - _pkiHolder = Configuration.Certificates.GenerateCertificates("localhost", nameof(SmtpClientTlsTest<>), longChain: true); + (serverCert, serverChain) = System.Net.Test.Common.Configuration.Certificates.GenerateCertificates("localhost", nameof(SmtpClientTlsTest<>)); + serverCertContext = SslStreamCertificateContext.Create(serverCert, serverChain); } - public SslStreamCertificateContext CreateSslStreamCertificateContext() => _pkiHolder.CreateSslStreamCertificateContext(); - public void Dispose() { - _pkiHolder.Dispose(); + serverCert.Dispose(); + foreach (var c in serverChain) + { + c.Dispose(); + } } } @@ -47,7 +47,7 @@ public SmtpClientTlsTest(ITestOutputHelper output, CertificateSetup certificateS _certificateSetup = certificateSetup; Server.SslOptions = new SslServerAuthenticationOptions { - ServerCertificateContext = _certificateSetup.CreateSslStreamCertificateContext(), + ServerCertificateContext = _certificateSetup.serverCertContext, ClientCertificateRequired = false, }; @@ -158,7 +158,7 @@ public async Task AuthenticationException_Propagates() public async Task ClientCertificateRequired_Sent() { Server.SslOptions.ClientCertificateRequired = true; - X509Certificate2 clientCert = _certificateSetup.ServerCert; // use the server cert as a client cert for testing + X509Certificate2 clientCert = _certificateSetup.serverCert; // use the server cert as a client cert for testing X509Certificate2? receivedClientCert = null; Server.SslOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => { diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs index 49464fe44514e7..3fdf1a8a35cf82 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs @@ -27,21 +27,22 @@ namespace System.Net.Quic.Tests public class CertificateSetup : IDisposable { - public X509Certificate2 ServerCert => _pkiHolder.EndEntity; - public X509Certificate2Collection ServerChain => _pkiHolder.IssuerChain; - - Configuration.Certificates.PkiHolder _pkiHolder; + public readonly X509Certificate2 serverCert; + public readonly X509Certificate2Collection serverChain; public CertificateSetup() { - _pkiHolder = Configuration.Certificates.GenerateCertificates("localhost", nameof(MsQuicTests), longChain: true); + Configuration.Certificates.CleanupCertificates(nameof(MsQuicTests)); + (serverCert, serverChain) = Configuration.Certificates.GenerateCertificates("localhost", nameof(MsQuicTests), longChain: true); } - public SslStreamCertificateContext CreateSslStreamCertificateContext() => _pkiHolder.CreateSslStreamCertificateContext(); - public void Dispose() { - _pkiHolder.Dispose(); + serverCert.Dispose(); + foreach (var c in serverChain) + { + c.Dispose(); + } } } @@ -180,63 +181,74 @@ bool TestWeakReferences() [Fact] public async Task ConnectWithCertificateChain() { - X509Certificate2 certificate = _certificates.ServerCert; - X509Certificate2Collection chain = _certificates.ServerChain; - - X509Certificate2 rootCA = chain[chain.Count - 1]; - - var listenerOptions = new QuicListenerOptions() + (X509Certificate2 certificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates("localhost", longChain: true); + try { - ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), - ApplicationProtocols = new List() { ApplicationProtocol }, - ConnectionOptionsCallback = (_, _, _) => + X509Certificate2 rootCA = chain[chain.Count - 1]; + + var listenerOptions = new QuicListenerOptions() { - var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificate, chain); - serverOptions.ServerAuthenticationOptions.ServerCertificate = null; - return ValueTask.FromResult(serverOptions); - } - }; + ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), + ApplicationProtocols = new List() { ApplicationProtocol }, + ConnectionOptionsCallback = (_, _, _) => + { + var serverOptions = CreateQuicServerOptions(); + serverOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificate, chain); + serverOptions.ServerAuthenticationOptions.ServerCertificate = null; + return ValueTask.FromResult(serverOptions); + } + }; - // Use whatever endpoint, it'll get overwritten in CreateConnectedQuicConnection. - QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listenerOptions.ListenEndPoint); - clientOptions.ClientAuthenticationOptions.CertificateChainPolicy = new X509ChainPolicy() - { - // We should get full chain without root CA. - // With trusted root, we should be able to build chain. - TrustMode = X509ChainTrustMode.CustomRootTrust, - CustomTrustStore = { rootCA }, - }; + // Use whatever endpoint, it'll get overwritten in CreateConnectedQuicConnection. + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listenerOptions.ListenEndPoint); + clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => + { + Assert.Equal(certificate.Subject, cert.Subject); + Assert.Equal(certificate.Issuer, cert.Issuer); - clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => - { - Assert.Equal(certificate.Subject, cert.Subject); - Assert.Equal(certificate.Issuer, cert.Issuer); + // Dispose of the chain's elements before calling Build, which will overwrite them. + for (int i = 0; i < chain.ChainElements.Count; i++) + { + chain.ChainElements[i].Certificate.Dispose(); + } - if (errors != SslPolicyErrors.None) - { - _output.WriteLine("Certificate validation failed with {0}", errors); - _output.WriteLine("Chain has {0} elements", chain.ChainElements.Count); - foreach (X509ChainElement element in chain.ChainElements) + // We should get full chain without root CA. + // With trusted root, we should be able to build chain. + chain.ChainPolicy.CustomTrustStore.Add(rootCA); + chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; + bool ret = chain.Build(certificate); + if (!ret) { - _output.WriteLine("Element subject {0} and issuer {1}", element.Certificate.Subject, element.Certificate.Issuer); - _output.WriteLine("Element status len {0}", element.ChainElementStatus.Length); - foreach (X509ChainStatus status in element.ChainElementStatus) + _output.WriteLine("Chain build failed with {0} elements", chain.ChainElements); + foreach (X509ChainElement element in chain.ChainElements) { - _output.WriteLine($"Status: {status.Status}: {status.StatusInformation}"); + _output.WriteLine("Element subject {0} and issuer {1}", element.Certificate.Subject, element.Certificate.Issuer); + _output.WriteLine("Element status len {0}", element.ChainElementStatus.Length); + foreach (X509ChainStatus status in element.ChainElementStatus) + { + _output.WriteLine($"Status: {status.Status}: {status.StatusInformation}"); + } } } - } - return errors == SslPolicyErrors.None; - }; + return ret; + }; - (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listenerOptions); - using X509Certificate clientRemoteCertificate = clientConnection.RemoteCertificate; - Assert.Equal(certificate, clientRemoteCertificate); - Assert.Null(serverConnection.RemoteCertificate); - await serverConnection.DisposeAsync(); - await clientConnection.DisposeAsync(); + (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listenerOptions); + using X509Certificate clientRemoteCertificate = clientConnection.RemoteCertificate; + Assert.Equal(certificate, clientRemoteCertificate); + Assert.Null(serverConnection.RemoteCertificate); + await serverConnection.DisposeAsync(); + await clientConnection.DisposeAsync(); + } + finally + { + foreach (X509Certificate2 cert in chain) + { + cert.Dispose(); + } + certificate.Dispose(); + } } [Theory] @@ -244,7 +256,7 @@ public async Task ConnectWithCertificateChain() [InlineData(false)] public async Task ConnectWithUntrustedCaWithCustomTrust_OK(bool usePartialChain) { - int split = Random.Shared.Next(0, _certificates.ServerChain.Count - 1); + int split = Random.Shared.Next(0, _certificates.serverChain.Count - 1); X509Certificate2Collection serverChain; if (usePartialChain) @@ -253,19 +265,19 @@ public async Task ConnectWithUntrustedCaWithCustomTrust_OK(bool usePartialChain) serverChain = new X509Certificate2Collection(); for (int i = 0; i < split; i++) { - serverChain.Add(_certificates.ServerChain[i]); + serverChain.Add(_certificates.serverChain[i]); } } else { - serverChain = _certificates.ServerChain; + serverChain = _certificates.serverChain; } var listenerOptions = CreateQuicListenerOptions(); listenerOptions.ConnectionOptionsCallback = (_, _, _) => { var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.ServerCert, serverChain); + serverOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.serverCert, serverChain); serverOptions.ServerAuthenticationOptions.RemoteCertificateValidationCallback = null; return ValueTask.FromResult(serverOptions); }; @@ -281,14 +293,14 @@ public async Task ConnectWithUntrustedCaWithCustomTrust_OK(bool usePartialChain) RevocationMode = X509RevocationMode.NoCheck, TrustMode = X509ChainTrustMode.CustomRootTrust }; - clientSslOptions.CertificateChainPolicy.CustomTrustStore.Add(_certificates.ServerChain[_certificates.ServerChain.Count - 1]); + clientSslOptions.CertificateChainPolicy.CustomTrustStore.Add(_certificates.serverChain[_certificates.serverChain.Count - 1]); // Add only one CA to verify that peer did send intermediate CA cert. // In case of partial chain, we need to make missing certs available. if (usePartialChain) { - for (int i = split; i < _certificates.ServerChain.Count - 1; i++) + for (int i = split; i < _certificates.serverChain.Count - 1; i++) { - clientSslOptions.CertificateChainPolicy.ExtraStore.Add(_certificates.ServerChain[i]); + clientSslOptions.CertificateChainPolicy.ExtraStore.Add(_certificates.serverChain[i]); } } @@ -496,66 +508,86 @@ public async Task ConnectWithIpSetsSni(string destination) [Fact] public async Task ConnectWithCertificateForDifferentName_Throws() { - X509Certificate2 certificate = _certificates.ServerCert; - - var quicOptions = new QuicListenerOptions() + (X509Certificate2 certificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates("localhost"); + try { - ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), - ApplicationProtocols = new List() { ApplicationProtocol }, - ConnectionOptionsCallback = (_, _, _) => + var quicOptions = new QuicListenerOptions() { - var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ServerCertificate = certificate; - return ValueTask.FromResult(serverOptions); - } - }; - await using QuicListener listener = await CreateQuicListener(quicOptions); + ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), + ApplicationProtocols = new List() { ApplicationProtocol }, + ConnectionOptionsCallback = (_, _, _) => + { + var serverOptions = CreateQuicServerOptions(); + serverOptions.ServerAuthenticationOptions.ServerCertificate = certificate; + return ValueTask.FromResult(serverOptions); + } + }; + await using QuicListener listener = await CreateQuicListener(quicOptions); - QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); - // Use different target host on purpose to get RemoteCertificateNameMismatch ssl error. - clientOptions.ClientAuthenticationOptions.TargetHost = "loopback"; - clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => - { - Assert.Equal(certificate.Subject, cert.Subject); - Assert.Equal(certificate.Issuer, cert.Issuer); - Assert.Equal(SslPolicyErrors.RemoteCertificateNameMismatch, errors & SslPolicyErrors.RemoteCertificateNameMismatch); - return SslPolicyErrors.None == errors; - }; + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); + // Use different target host on purpose to get RemoteCertificateNameMismatch ssl error. + clientOptions.ClientAuthenticationOptions.TargetHost = "loopback"; + clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => + { + Assert.Equal(certificate.Subject, cert.Subject); + Assert.Equal(certificate.Issuer, cert.Issuer); + Assert.Equal(SslPolicyErrors.RemoteCertificateNameMismatch, errors & SslPolicyErrors.RemoteCertificateNameMismatch); + return SslPolicyErrors.None == errors; + }; - await Assert.ThrowsAsync(async () => await CreateQuicConnection(clientOptions)); + await Assert.ThrowsAsync(async () => await CreateQuicConnection(clientOptions)); + } + finally + { + foreach (X509Certificate2 cert in chain) + { + cert.Dispose(); + } + certificate.Dispose(); + } } [Fact] public async Task ConnectWithCertificate_MissingTargetHost_Succeeds() { - X509Certificate2 certificate = _certificates.ServerCert; - - var quicOptions = new QuicListenerOptions() + (X509Certificate2 certificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates("localhost"); + try { - // loopback may resolve to IPv6 - ListenEndPoint = new IPEndPoint(IPAddress.IPv6Any, 0), - ApplicationProtocols = new List() { ApplicationProtocol }, - ConnectionOptionsCallback = (_, _, _) => + var quicOptions = new QuicListenerOptions() { - var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ServerCertificate = certificate; - return ValueTask.FromResult(serverOptions); - } - }; - await using QuicListener listener = await CreateQuicListener(quicOptions); + // loopback may resolve to IPv6 + ListenEndPoint = new IPEndPoint(IPAddress.IPv6Any, 0), + ApplicationProtocols = new List() { ApplicationProtocol }, + ConnectionOptionsCallback = (_, _, _) => + { + var serverOptions = CreateQuicServerOptions(); + serverOptions.ServerAuthenticationOptions.ServerCertificate = certificate; + return ValueTask.FromResult(serverOptions); + } + }; + await using QuicListener listener = await CreateQuicListener(quicOptions); - QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(new DnsEndPoint("localhost", listener.LocalEndPoint.Port)); - // Do not set target host on client options, it should be taken from remote endpoint and used for both ClientHello SNI and Server cert validation - clientOptions.ClientAuthenticationOptions.TargetHost = null; - clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => - { - Assert.Equal(certificate.Subject, cert.Subject); - Assert.Equal(certificate.Issuer, cert.Issuer); - Assert.Equal(SslPolicyErrors.None, errors & SslPolicyErrors.RemoteCertificateNameMismatch); - return true; - }; + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(new DnsEndPoint("localhost", listener.LocalEndPoint.Port)); + // Do not set target host on client options, it should be taken from remote endpoint and used for both ClientHello SNI and Server cert validation + clientOptions.ClientAuthenticationOptions.TargetHost = null; + clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => + { + Assert.Equal(certificate.Subject, cert.Subject); + Assert.Equal(certificate.Issuer, cert.Issuer); + Assert.Equal(SslPolicyErrors.None, errors & SslPolicyErrors.RemoteCertificateNameMismatch); + return true; + }; - await using QuicConnection connection = await CreateQuicConnection(clientOptions); + await using QuicConnection connection = await CreateQuicConnection(clientOptions); + } + finally + { + foreach (X509Certificate2 cert in chain) + { + cert.Dispose(); + } + certificate.Dispose(); + } } [ConditionalTheory] @@ -571,34 +603,43 @@ public async Task ConnectWithCertificateForLoopbackIP_IndicatesExpectedError(str throw new SkipTestException("IPv6 is not available on this platform"); } - using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(expectsError ? "badhost" : "localhost"); - X509Certificate2 certificate = pkiHolder.EndEntity; - - var listenerOptions = new QuicListenerOptions() + (X509Certificate2 certificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates(expectsError ? "badhost" : "localhost"); + try { - ListenEndPoint = new IPEndPoint(ipAddress, 0), - ApplicationProtocols = new List() { ApplicationProtocol }, - ConnectionOptionsCallback = (_, _, _) => + var listenerOptions = new QuicListenerOptions() { - var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ServerCertificate = certificate; - return ValueTask.FromResult(serverOptions); - } - }; + ListenEndPoint = new IPEndPoint(ipAddress, 0), + ApplicationProtocols = new List() { ApplicationProtocol }, + ConnectionOptionsCallback = (_, _, _) => + { + var serverOptions = CreateQuicServerOptions(); + serverOptions.ServerAuthenticationOptions.ServerCertificate = certificate; + return ValueTask.FromResult(serverOptions); + } + }; - // Use whatever endpoint, it'll get overwritten in CreateConnectedQuicConnection. - QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listenerOptions.ListenEndPoint); - clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => - { - Assert.Equal(certificate.Subject, cert.Subject); - Assert.Equal(certificate.Issuer, cert.Issuer); - Assert.Equal(expectsError ? SslPolicyErrors.RemoteCertificateNameMismatch : SslPolicyErrors.None, errors & SslPolicyErrors.RemoteCertificateNameMismatch); - return true; - }; + // Use whatever endpoint, it'll get overwritten in CreateConnectedQuicConnection. + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listenerOptions.ListenEndPoint); + clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => + { + Assert.Equal(certificate.Subject, cert.Subject); + Assert.Equal(certificate.Issuer, cert.Issuer); + Assert.Equal(expectsError ? SslPolicyErrors.RemoteCertificateNameMismatch : SslPolicyErrors.None, errors & SslPolicyErrors.RemoteCertificateNameMismatch); + return true; + }; - (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listenerOptions); - await clientConnection.DisposeAsync(); - await serverConnection.DisposeAsync(); + (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listenerOptions); + await clientConnection.DisposeAsync(); + await serverConnection.DisposeAsync(); + } + finally + { + foreach (X509Certificate2 cert in chain) + { + cert.Dispose(); + } + certificate.Dispose(); + } } public enum ClientCertSource @@ -683,59 +724,85 @@ public async Task ConnectWithClientCertificate(bool sendCertificate, ClientCertS [PlatformSpecific(TestPlatforms.Windows)] public async Task Server_CertificateWithEphemeralKey_Throws() { - using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(Server_CertificateWithEphemeralKey_Throws), ephemeralKey: true); + (X509Certificate2 serverCertificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates(nameof(Server_CertificateWithEphemeralKey_Throws), ephemeralKey: true); + Configuration.Certificates.CleanupCertificates(nameof(Server_CertificateWithEphemeralKey_Throws)); - QuicListenerOptions listenerOptions = new QuicListenerOptions() + try { - ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), - ApplicationProtocols = new List() { ApplicationProtocol }, - ConnectionOptionsCallback = (_, _, _) => + QuicListenerOptions listenerOptions = new QuicListenerOptions() { - var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ServerCertificate = null; - serverOptions.ServerAuthenticationOptions.ServerCertificateContext = pkiHolder.CreateSslStreamCertificateContext(); - return ValueTask.FromResult(serverOptions); - } - }; - await using QuicListener listener = await CreateQuicListener(listenerOptions); + ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), + ApplicationProtocols = new List() { ApplicationProtocol }, + ConnectionOptionsCallback = (_, _, _) => + { + var serverOptions = CreateQuicServerOptions(); + serverOptions.ServerAuthenticationOptions.ServerCertificate = null; + serverOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(serverCertificate, chain); + return ValueTask.FromResult(serverOptions); + } + }; + await using QuicListener listener = await CreateQuicListener(listenerOptions); - QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); - clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; }; + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); + clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; }; - // client connection attempt will fail - await Assert.ThrowsAsync(async () => await CreateQuicConnection(clientOptions)); + // client connection attempt will fail + await Assert.ThrowsAsync(async () => await CreateQuicConnection(clientOptions)); - // server-side failure will be reported from AcceptConnectionAsync - AuthenticationException e = await Assert.ThrowsAsync(async () => await listener.AcceptConnectionAsync()); - Assert.Contains("ephemeral", e.Message); + // server-side failure will be reported from AcceptConnectionAsync + AuthenticationException e = await Assert.ThrowsAsync(async () => await listener.AcceptConnectionAsync()); + Assert.Contains("ephemeral", e.Message); + } + finally + { + Configuration.Certificates.CleanupCertificates(nameof(Server_CertificateWithEphemeralKey_Throws)); + serverCertificate.Dispose(); + foreach (X509Certificate c in chain) + { + c.Dispose(); + } + } } [ConditionalFact(typeof(QuicTestCollection), nameof(QuicTestCollection.IsUsingSchannelBackend))] [PlatformSpecific(TestPlatforms.Windows)] public async Task Client_CertificateWithEphemeralKey_Throws() { - using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(Client_CertificateWithEphemeralKey_Throws), ephemeralKey: true); + (X509Certificate2 clientCertificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates(nameof(Client_CertificateWithEphemeralKey_Throws), ephemeralKey: true); + Configuration.Certificates.CleanupCertificates(nameof(Client_CertificateWithEphemeralKey_Throws)); - QuicListenerOptions listenerOptions = new QuicListenerOptions() + try { - ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), - ApplicationProtocols = new List() { ApplicationProtocol }, - ConnectionOptionsCallback = (_, _, _) => + QuicListenerOptions listenerOptions = new QuicListenerOptions() { - var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ClientCertificateRequired = true; - serverOptions.ServerAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; }; - return ValueTask.FromResult(serverOptions); - } - }; - await using QuicListener listener = await CreateQuicListener(listenerOptions); + ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), + ApplicationProtocols = new List() { ApplicationProtocol }, + ConnectionOptionsCallback = (_, _, _) => + { + var serverOptions = CreateQuicServerOptions(); + serverOptions.ServerAuthenticationOptions.ClientCertificateRequired = true; + serverOptions.ServerAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; }; + return ValueTask.FromResult(serverOptions); + } + }; + await using QuicListener listener = await CreateQuicListener(listenerOptions); - QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); - clientOptions.ClientAuthenticationOptions.ClientCertificates = new X509CertificateCollection() { pkiHolder.EndEntity }; - clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; }; + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); + clientOptions.ClientAuthenticationOptions.ClientCertificates = new X509CertificateCollection() { clientCertificate }; + clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; }; - AuthenticationException e = await Assert.ThrowsAsync(async () => await CreateQuicConnection(clientOptions)); - Assert.Contains("ephemeral", e.Message); + AuthenticationException e = await Assert.ThrowsAsync(async () => await CreateQuicConnection(clientOptions)); + Assert.Contains("ephemeral", e.Message); + } + finally + { + Configuration.Certificates.CleanupCertificates(nameof(Client_CertificateWithEphemeralKey_Throws)); + clientCertificate.Dispose(); + foreach (X509Certificate c in chain) + { + c.Dispose(); + } + } } [Theory] diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj index fe26b4a0a12905..1f4e9278633817 100644 --- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj @@ -32,8 +32,6 @@ Link="Common\System\Obsoletions.cs" /> - diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs index c70cbb3bb4e8ae..603a3b24c24af6 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs @@ -12,13 +12,6 @@ namespace System.Net.Security internal sealed class SslAuthenticationOptions : IDisposable { private const string EnableOcspStaplingContextSwitchName = "System.Net.Security.EnableServerOcspStaplingFromOnlyCertificateOnLinux"; - - internal static readonly X509RevocationMode DefaultRevocationMode = - AppContextSwitchHelper.GetBooleanConfig( - "System.Net.Security.NoRevocationCheckByDefault", - "DOTNET_SYSTEM_NET_SECURITY_NOREVOCATIONCHECKBYDEFAULT") - ? X509RevocationMode.NoCheck : X509RevocationMode.Online; - internal SslAuthenticationOptions() { TargetHost = string.Empty; diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs index 16700e0598dad1..9f159207f93351 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs @@ -12,7 +12,7 @@ namespace System.Net.Security public class SslClientAuthenticationOptions { private EncryptionPolicy _encryptionPolicy = EncryptionPolicy.RequireEncryption; - private X509RevocationMode _checkCertificateRevocation = SslAuthenticationOptions.DefaultRevocationMode; + private X509RevocationMode _checkCertificateRevocation = X509RevocationMode.NoCheck; private SslProtocols _enabledSslProtocols = SslProtocols.None; private bool _allowRenegotiation = true; private bool _allowTlsResume = true; diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs index 940461b2632b2b..6ca64677652d50 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs @@ -10,7 +10,7 @@ namespace System.Net.Security { public class SslServerAuthenticationOptions { - private X509RevocationMode _checkCertificateRevocation = SslAuthenticationOptions.DefaultRevocationMode; + private X509RevocationMode _checkCertificateRevocation = X509RevocationMode.NoCheck; private SslProtocols _enabledSslProtocols = SslProtocols.None; private EncryptionPolicy _encryptionPolicy = EncryptionPolicy.RequireEncryption; private bool _allowRenegotiation; diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs index eae09611396bca..6844e0d47189c7 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs @@ -185,10 +185,7 @@ public async Task CertificateValidationClientServer_EndToEnd_Ok(ClientCertSource TargetHost = Guid.NewGuid().ToString("N"), ClientCertificates = clientCerts, EnabledSslProtocols = SslProtocolSupport.DefaultSslProtocols, - CertificateChainPolicy = new X509ChainPolicy() - { - RevocationMode = X509RevocationMode.NoCheck, - } + CertificateChainPolicy = new X509ChainPolicy(), }; if (clientCertSource == ClientCertSource.CertificateContext) diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs index ea3809f33f2e88..e9b78bbc344862 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs @@ -22,13 +22,13 @@ public class SslStreamCertificateTrustTest [SkipOnPlatform(TestPlatforms.Windows, "CertificateCollection-based SslCertificateTrust is not Supported on Windows")] public async Task SslStream_SendCertificateTrust_CertificateCollection() { - using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); + (X509Certificate2 certificate, X509Certificate2Collection caCerts) = Configuration.Certificates.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); - SslCertificateTrust trust = SslCertificateTrust.CreateForX509Collection(pkiHolder.IssuerChain, sendTrustInHandshake: true); + SslCertificateTrust trust = SslCertificateTrust.CreateForX509Collection(caCerts, sendTrustInHandshake: true); string[] acceptableIssuers = await ConnectAndGatherAcceptableIssuers(trust); - Assert.Equal(pkiHolder.IssuerChain.Count, acceptableIssuers.Length); - Assert.Equal(pkiHolder.IssuerChain.Select(c => c.Subject), acceptableIssuers); + Assert.Equal(caCerts.Count, acceptableIssuers.Length); + Assert.Equal(caCerts.Select(c => c.Subject), acceptableIssuers); } [ConditionalFact(nameof(SupportsSendingCustomCANamesInTls))] @@ -94,20 +94,20 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( [PlatformSpecific(TestPlatforms.Windows)] public void SslStream_SendCertificateTrust_CertificateCollection_ThrowsOnWindows() { - using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); + (X509Certificate2 certificate, X509Certificate2Collection caCerts) = Configuration.Certificates.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); - Assert.Throws(() => SslCertificateTrust.CreateForX509Collection(pkiHolder.IssuerChain, sendTrustInHandshake: true)); + Assert.Throws(() => SslCertificateTrust.CreateForX509Collection(caCerts, sendTrustInHandshake: true)); } [ConditionalFact(nameof(DoesNotSupportSendingCustomCANamesInTls))] [SkipOnPlatform(TestPlatforms.Windows, "Windows tested separately")] public void SslStream_SendCertificateTrust_ThrowsOnUnsupportedPlatform() { - using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); + (X509Certificate2 certificate, X509Certificate2Collection caCerts) = Configuration.Certificates.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); using X509Store store = new X509Store("Root", StoreLocation.LocalMachine); - Assert.Throws(() => SslCertificateTrust.CreateForX509Collection(pkiHolder.IssuerChain, sendTrustInHandshake: true)); + Assert.Throws(() => SslCertificateTrust.CreateForX509Collection(caCerts, sendTrustInHandshake: true)); Assert.Throws(() => SslCertificateTrust.CreateForX509Store(store, sendTrustInHandshake: true)); } } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamFramingTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamFramingTest.cs index e6946670f76904..f83e1b7027ebba 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamFramingTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamFramingTest.cs @@ -91,7 +91,7 @@ public async Task Handshake_Success(FramingType framingType, SslProtocols sslPro { EnabledSslProtocols = sslProtocol, CertificateRevocationCheckMode = X509RevocationMode.NoCheck, - ServerCertificateContext = _certificates.CreateSslStreamCertificateContext(), + ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.serverCert, _certificates.serverChain), RemoteCertificateValidationCallback = (sender, cert, chain, errors) => true, ClientCertificateRequired = clientCertScenario == ClientCertScenario.InHandshake, }; @@ -102,7 +102,7 @@ public async Task Handshake_Success(FramingType framingType, SslProtocols sslPro EnabledSslProtocols = sslProtocol, CertificateRevocationCheckMode = X509RevocationMode.NoCheck, ClientCertificates = clientCertScenario != ClientCertScenario.None - ? new X509CertificateCollection { _certificates.ServerCert } + ? new X509CertificateCollection { _certificates.serverCert } : new X509CertificateCollection(), RemoteCertificateValidationCallback = (sender, cert, chain, errors) => true, }; diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs index 811b6932e896de..5c2f101d922e85 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs @@ -22,21 +22,22 @@ namespace System.Net.Security.Tests public class CertificateSetup : IDisposable { - public X509Certificate2 ServerCert => _pkiHolder.EndEntity; - public X509Certificate2Collection ServerChain => _pkiHolder.IssuerChain; - - private readonly Configuration.Certificates.PkiHolder _pkiHolder; + public readonly X509Certificate2 serverCert; + public readonly X509Certificate2Collection serverChain; public CertificateSetup() { - _pkiHolder = Configuration.Certificates.GenerateCertificates("localhost", nameof(SslStreamNetworkStreamTest), longChain: true); + TestHelper.CleanupCertificates(nameof(SslStreamNetworkStreamTest)); + (serverCert, serverChain) = Configuration.Certificates.GenerateCertificates("localhost", nameof(SslStreamNetworkStreamTest), longChain: true); } - public SslStreamCertificateContext CreateSslStreamCertificateContext() => _pkiHolder.CreateSslStreamCertificateContext(); - public void Dispose() { - _pkiHolder.Dispose(); + serverCert.Dispose(); + foreach (var c in serverChain) + { + c.Dispose(); + } } } @@ -759,7 +760,7 @@ public async Task SslStream_ServerUntrustedCaWithCustomTrust_OK(bool usePartialC throw new SkipTestException("Android does not support partial chain validation."); } - int split = Random.Shared.Next(0, _certificates.ServerChain.Count - 1); + int split = Random.Shared.Next(0, _certificates.serverChain.Count - 1); var clientOptions = new SslClientAuthenticationOptions() { TargetHost = "localhost" }; clientOptions.CertificateChainPolicy = new X509ChainPolicy() @@ -767,14 +768,14 @@ public async Task SslStream_ServerUntrustedCaWithCustomTrust_OK(bool usePartialC RevocationMode = X509RevocationMode.NoCheck, TrustMode = X509ChainTrustMode.CustomRootTrust }; - clientOptions.CertificateChainPolicy.CustomTrustStore.Add(_certificates.ServerChain[_certificates.ServerChain.Count - 1]); + clientOptions.CertificateChainPolicy.CustomTrustStore.Add(_certificates.serverChain[_certificates.serverChain.Count - 1]); // Add only one CA to verify that peer did send intermediate CA cert. // In case of partial chain, we need to make missing certs available. if (usePartialChain) { - for (int i = split; i < _certificates.ServerChain.Count - 1; i++) + for (int i = split; i < _certificates.serverChain.Count - 1; i++) { - clientOptions.CertificateChainPolicy.ExtraStore.Add(_certificates.ServerChain[i]); + clientOptions.CertificateChainPolicy.ExtraStore.Add(_certificates.serverChain[i]); } } @@ -786,15 +787,15 @@ public async Task SslStream_ServerUntrustedCaWithCustomTrust_OK(bool usePartialC serverChain = new X509Certificate2Collection(); for (int i = 0; i < split; i++) { - serverChain.Add(_certificates.ServerChain[i]); + serverChain.Add(_certificates.serverChain[i]); } } else { - serverChain = _certificates.ServerChain; + serverChain = _certificates.serverChain; } - serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.ServerCert, serverChain); + serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.serverCert, serverChain); (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams(); using (clientStream) @@ -877,15 +878,13 @@ public async Task SslStream_ClientCertificate_SendsChain() StoreName storeName = OperatingSystem.IsMacOS() ? StoreName.My : StoreName.CertificateAuthority; List streams = new List(); TestHelper.CleanupCertificates(nameof(SslStream_ClientCertificate_SendsChain), storeName); - using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(SslStream_ClientCertificate_SendsChain), serverCertificate: false); - X509Certificate2 clientCertificate = pkiHolder.EndEntity; - X509Certificate2Collection clientChain = pkiHolder.IssuerChain; + (X509Certificate2 clientCertificate, X509Certificate2Collection clientChain) = Configuration.Certificates.GenerateCertificates(nameof(SslStream_ClientCertificate_SendsChain), serverCertificate: false); using (X509Store store = new X509Store(storeName, StoreLocation.CurrentUser)) { // add chain certificate so we can construct chain since there is no way how to pass intermediates directly. store.Open(OpenFlags.ReadWrite); - store.AddRange(pkiHolder.IssuerChain); + store.AddRange(clientChain); store.Close(); } @@ -921,6 +920,13 @@ public async Task SslStream_ClientCertificate_SendsChain() clientOptions.LocalCertificateSelectionCallback = (sender, target, certificates, remoteCertificate, issuers) => clientCertificate; await SslStream_ClientSendsChain_Core(clientOptions, clientChain); + + TestHelper.CleanupCertificates(nameof(SslStream_ClientCertificate_SendsChain), storeName); + clientCertificate.Dispose(); + foreach (X509Certificate c in clientChain) + { + c.Dispose(); + } } [Theory] @@ -928,7 +934,7 @@ public async Task SslStream_ClientCertificate_SendsChain() [InlineData(false)] public async Task SslStream_ClientCertificateContext_SendsChain(bool useTrust) { - using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(SslStream_ClientCertificateContext_SendsChain), serverCertificate: false); + (X509Certificate2 clientCertificate, X509Certificate2Collection clientChain) = Configuration.Certificates.GenerateCertificates(nameof(SslStream_ClientCertificateContext_SendsChain), serverCertificate: false); TestHelper.CleanupCertificates(nameof(SslStream_ClientCertificateContext_SendsChain)); SslCertificateTrust? trust = null; @@ -936,24 +942,32 @@ public async Task SslStream_ClientCertificateContext_SendsChain(bool useTrust) { // This is simplification. We make all the intermediates trusted, // normally just the root would go here. - trust = SslCertificateTrust.CreateForX509Collection(pkiHolder.IssuerChain, false); + trust = SslCertificateTrust.CreateForX509Collection(clientChain, false); } var clientOptions = new SslClientAuthenticationOptions() { TargetHost = "localhost", - RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true, - ClientCertificateContext = SslStreamCertificateContext.Create(pkiHolder.EndEntity, useTrust ? null : pkiHolder.IssuerChain, offline: true, trust), }; + clientOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; + clientOptions.ClientCertificateContext = SslStreamCertificateContext.Create(clientCertificate, useTrust ? null : clientChain, offline: true, trust); - await SslStream_ClientSendsChain_Core(clientOptions, pkiHolder.IssuerChain); + await SslStream_ClientSendsChain_Core(clientOptions, clientChain); + + TestHelper.CleanupCertificates(nameof(SslStream_ClientCertificateContext_SendsChain)); + clientCertificate.Dispose(); + foreach (X509Certificate c in clientChain) + { + c.Dispose(); + } } [Fact] [PlatformSpecific(TestPlatforms.Windows)] public async Task SslStream_EphemeralKey_Throws() { - using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(SslStream_EphemeralKey_Throws), ephemeralKey: true); + (X509Certificate2 serverCertificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates(nameof(SslStream_EphemeralKey_Throws), ephemeralKey: true); + TestHelper.CleanupCertificates(nameof(SslStream_EphemeralKey_Throws)); var clientOptions = new SslClientAuthenticationOptions() { @@ -963,7 +977,7 @@ public async Task SslStream_EphemeralKey_Throws() var serverOptions = new SslServerAuthenticationOptions() { - ServerCertificate = pkiHolder.EndEntity + ServerCertificate = serverCertificate }; (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); @@ -976,6 +990,13 @@ public async Task SslStream_EphemeralKey_Throws() server.Dispose(); await Assert.ThrowsAsync(() => t1); client.Dispose(); + + TestHelper.CleanupCertificates(nameof(SslStream_EphemeralKey_Throws)); + serverCertificate.Dispose(); + foreach (X509Certificate c in chain) + { + c.Dispose(); + } } [Theory] diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs index 4ac1691eb7f1fb..46a4707b43cf01 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs @@ -83,31 +83,5 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( Assert.True(File.ReadAllText(tempFile).Length == 0); } } - - [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [InlineData(true)] - [InlineData(false)] - public void DefaultRevocationMode_OfflineRevocationByDefault_True_UsesNoCheck(bool useEnvVar) - { - var psi = new ProcessStartInfo(); - if (useEnvVar) - { - psi.Environment.Add("DOTNET_SYSTEM_NET_SECURITY_NOREVOCATIONCHECKBYDEFAULT", "true"); - } - - Assert.Equal(X509RevocationMode.Online, new SslClientAuthenticationOptions().CertificateRevocationCheckMode); - Assert.Equal(X509RevocationMode.Online, new SslServerAuthenticationOptions().CertificateRevocationCheckMode); - - RemoteExecutor.Invoke(useEnvVar => - { - if (!bool.Parse(useEnvVar)) - { - AppContext.SetSwitch("System.Net.Security.NoRevocationCheckByDefault", true); - } - - Assert.Equal(X509RevocationMode.NoCheck, new SslClientAuthenticationOptions().CertificateRevocationCheckMode); - Assert.Equal(X509RevocationMode.NoCheck, new SslServerAuthenticationOptions().CertificateRevocationCheckMode); - }, useEnvVar.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose(); - } } } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs index 520db2e7000b56..8c9ecd96c6caad 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs @@ -27,8 +27,7 @@ public async Task SslStream_ClientSendsSNIServerReceives_Ok(string hostName) await WithVirtualConnection(async (server, client) => { - Task clientJob = Task.Run(() => - { + Task clientJob = Task.Run(() => { client.AuthenticateAsClient(hostName); }); @@ -80,8 +79,7 @@ public async Task SslStream_ServerCallbackAndLocalCertificateSelectionSet_Throws using (SslStream server = new SslStream(stream1, false, null, selectionCallback), client = new SslStream(stream2, leaveInnerStreamOpen: false, validationCallback)) { - Task clientJob = Task.Run(() => - { + Task clientJob = Task.Run(() => { client.AuthenticateAsClient(hostName); Assert.Fail("RemoteCertificateValidationCallback called when AuthenticateAsServerAsync was expected to fail."); }); @@ -129,8 +127,7 @@ public async Task SslStream_ServerCallbackNotSet_UsesLocalCertificateSelection(s using (SslStream server = new SslStream(stream1, false, null, selectionCallback), client = new SslStream(stream2, leaveInnerStreamOpen: false, validationCallback)) { - Task clientJob = Task.Run(() => - { + Task clientJob = Task.Run(() => { client.AuthenticateAsClient(hostName); }); @@ -150,8 +147,7 @@ public async Task SslStream_NoSniFromClient_CallbackReturnsNull() { await WithVirtualConnection(async (server, client) => { - Task clientJob = Task.Run(() => - { + Task clientJob = Task.Run(() => { Assert.Throws(() => client.AuthenticateAsClient("test") ); @@ -193,8 +189,8 @@ public async Task SslStream_IpLiteral_NotSend(string target) (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions() { - TargetHost = target, - RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true, + TargetHost = target, + RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true, }; SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() { @@ -245,33 +241,41 @@ public async Task UnencodedHostName_ValidatesCertificate() string rawHostname = "räksmörgås.josefsson.org"; string punycodeHostname = "xn--rksmrgs-5wao1o.josefsson.org"; - using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(punycodeHostname); - - SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() + var (serverCert, serverChain) = Configuration.Certificates.GenerateCertificates(punycodeHostname); + try { - ServerCertificateContext = pkiHolder.CreateSslStreamCertificateContext(), - }; + SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() + { + ServerCertificateContext = SslStreamCertificateContext.Create(serverCert, serverChain), + }; - SslClientAuthenticationOptions clientOptions = new() - { - TargetHost = rawHostname, - CertificateChainPolicy = new X509ChainPolicy() + SslClientAuthenticationOptions clientOptions = new () { - RevocationMode = X509RevocationMode.NoCheck, - TrustMode = X509ChainTrustMode.CustomRootTrust, - CustomTrustStore = { pkiHolder.IssuerChain[^1] } - } - }; + TargetHost = rawHostname, + CertificateChainPolicy = new X509ChainPolicy() + { + RevocationMode = X509RevocationMode.NoCheck, + TrustMode = X509ChainTrustMode.CustomRootTrust, + CustomTrustStore = { serverChain[serverChain.Count - 1] } + } + }; - (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); - await TestConfiguration.WhenAllOrAnyFailedWithTimeout( - client.AuthenticateAsClientAsync(clientOptions, default), - server.AuthenticateAsServerAsync(serverOptions, default)); + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions, default), + server.AuthenticateAsServerAsync(serverOptions, default)); - await TestHelper.PingPong(client, server, default); - Assert.Equal(rawHostname, server.TargetHostName); - Assert.Equal(rawHostname, client.TargetHostName); + await TestHelper.PingPong(client, server, default); + Assert.Equal(rawHostname, server.TargetHostName); + Assert.Equal(rawHostname, client.TargetHostName); + } + finally + { + serverCert.Dispose(); + foreach (var c in serverChain) c.Dispose(); + TestHelper.CleanupCertificates(rawHostname); + } } [ConditionalTheory] @@ -334,8 +338,7 @@ public async Task SslStream_UnsafeInvalidIdn_Throws(string name) private static Func WithAggregateExceptionUnwrapping(Func a) { - return async () => - { + return async () => { try { await a(); diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs index 494080f0c794a0..c0cdc6ccd4e143 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs @@ -471,7 +471,7 @@ public sealed class SslStreamStreamToStreamTest_Async : SslStreamStreamToStreamT protected override async Task DoHandshake(SslStream clientSslStream, SslStream serverSslStream, X509Certificate serverCertificate = null, X509Certificate clientCertificate = null) { X509CertificateCollection clientCerts = clientCertificate != null ? new X509CertificateCollection() { clientCertificate } : null; - await WithServerCertificate(serverCertificate, async (certificate, name) => + await WithServerCertificate(serverCertificate, async(certificate, name) => { Task t1 = clientSslStream.AuthenticateAsClientAsync(name, clientCerts, SslProtocols.None, checkCertificateRevocation: false); Task t2 = serverSslStream.AuthenticateAsServerAsync(certificate, clientCertificateRequired: clientCertificate != null, checkCertificateRevocation: false); @@ -636,13 +636,10 @@ await WithServerCertificate(serverCertificate, async (certificate, name) => TargetHost = name, ClientCertificates = clientCerts, EnabledSslProtocols = SslProtocols.None, - CertificateRevocationCheckMode = X509RevocationMode.NoCheck }; SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() { - ServerCertificate = certificate, - ClientCertificateRequired = clientCertificate != null, - CertificateRevocationCheckMode = X509RevocationMode.NoCheck + ServerCertificate = certificate, ClientCertificateRequired = clientCertificate != null, }; Task t1 = Task.Run(() => clientSslStream.AuthenticateAsClient(clientOptions)); Task t2 = Task.Run(() => serverSslStream.AuthenticateAsServer(serverOptions)); @@ -656,20 +653,10 @@ public sealed class SslStreamStreamToStreamTest_MemoryAsync : SslStreamStreamToS protected override async Task DoHandshake(SslStream clientSslStream, SslStream serverSslStream, X509Certificate serverCertificate = null, X509Certificate clientCertificate = null) { X509CertificateCollection clientCerts = clientCertificate != null ? new X509CertificateCollection() { clientCertificate } : null; - await WithServerCertificate(serverCertificate, async (certificate, name) => + await WithServerCertificate(serverCertificate, async(certificate, name) => { - Task t1 = clientSslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions() - { - TargetHost = name, - ClientCertificates = clientCerts, - CertificateRevocationCheckMode = X509RevocationMode.NoCheck - }, CancellationToken.None); - Task t2 = serverSslStream.AuthenticateAsServerAsync(new SslServerAuthenticationOptions() - { - ServerCertificate = certificate, - ClientCertificateRequired = clientCertificate != null, - CertificateRevocationCheckMode = X509RevocationMode.NoCheck - }, CancellationToken.None); + Task t1 = clientSslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions() { TargetHost = name, ClientCertificates = clientCerts }, CancellationToken.None); + Task t2 = serverSslStream.AuthenticateAsServerAsync(new SslServerAuthenticationOptions() { ServerCertificate = certificate, ClientCertificateRequired = clientCertificate != null }, CancellationToken.None); await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2); }); } diff --git a/src/libraries/System.Net.Security/tests/UnitTests/SslAuthenticationOptionsTests.cs b/src/libraries/System.Net.Security/tests/UnitTests/SslAuthenticationOptionsTests.cs index 22c8bcfffae93d..b7e9d29d9dda98 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/SslAuthenticationOptionsTests.cs +++ b/src/libraries/System.Net.Security/tests/UnitTests/SslAuthenticationOptionsTests.cs @@ -133,13 +133,13 @@ public void EnabledSslProtocols_Get_Set_Succeeds() [Fact] public void CheckCertificateRevocation_Get_Set_Succeeds() { - Assert.Equal(X509RevocationMode.Online, _clientOptions.CertificateRevocationCheckMode); - Assert.Equal(X509RevocationMode.Online, _serverOptions.CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.NoCheck, _clientOptions.CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.NoCheck, _serverOptions.CertificateRevocationCheckMode); - _clientOptions.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; + _clientOptions.CertificateRevocationCheckMode = X509RevocationMode.Online; _serverOptions.CertificateRevocationCheckMode = X509RevocationMode.Offline; - Assert.Equal(X509RevocationMode.NoCheck, _clientOptions.CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.Online, _clientOptions.CertificateRevocationCheckMode); Assert.Equal(X509RevocationMode.Offline, _serverOptions.CertificateRevocationCheckMode); Assert.Throws(() => _clientOptions.CertificateRevocationCheckMode = (X509RevocationMode)3); diff --git a/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj index 42381dbab6c6b8..efa3cd671c53dd 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj +++ b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj @@ -34,8 +34,6 @@ - Date: Thu, 7 Aug 2025 13:30:26 +0200 Subject: [PATCH 2/7] Reapply "Change HttpClient/SslStream default certificate revocation check mode to Online (#116098)" This reverts commit defd136bae2b98daab06ac259d2dea7d67cd5874. --- .../Net/Configuration.Certificates.Dynamic.cs | 118 +++-- ...ttpClientHandlerTest.ServerCertificates.cs | 7 +- .../System/Net/Http/HttpClientHandlerTest.cs | 2 +- .../src/System.Net.Http.WinHttpHandler.csproj | 2 + .../src/System/Net/Http/WinHttpHandler.cs | 7 +- ....Net.Http.WinHttpHandler.Unit.Tests.csproj | 2 + .../tests/UnitTests/WinHttpHandlerTest.cs | 2 +- .../FunctionalTests/SocketsHttpHandlerTest.cs | 71 ++- .../tests/Functional/SmtpClientTlsTest.cs | 24 +- .../tests/FunctionalTests/MsQuicTests.cs | 403 ++++++++---------- .../src/System.Net.Security.csproj | 2 + .../Net/Security/SslAuthenticationOptions.cs | 7 + .../SslClientAuthenticationOptions.cs | 2 +- .../SslServerAuthenticationOptions.cs | 2 +- .../CertificateValidationClientServer.cs | 5 +- .../SslStreamCertificateTrustTests.cs | 16 +- .../FunctionalTests/SslStreamFramingTest.cs | 4 +- .../SslStreamNetworkStreamTest.cs | 73 ++-- .../SslStreamRemoteExecutorTests.cs | 26 ++ .../tests/FunctionalTests/SslStreamSniTest.cs | 69 ++- .../SslStreamStreamToStreamTest.cs | 23 +- .../SslAuthenticationOptionsTests.cs | 8 +- .../System.Net.Security.Unit.Tests.csproj | 2 + 23 files changed, 450 insertions(+), 427 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Configuration.Certificates.Dynamic.cs b/src/libraries/Common/tests/System/Net/Configuration.Certificates.Dynamic.cs index fa20d1f374ce36..185416759e725b 100644 --- a/src/libraries/Common/tests/System/Net/Configuration.Certificates.Dynamic.cs +++ b/src/libraries/Common/tests/System/Net/Configuration.Certificates.Dynamic.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Net.Security; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -37,29 +38,6 @@ public static partial class Certificates private static readonly X509BasicConstraintsExtension s_eeConstraints = new X509BasicConstraintsExtension(false, false, 0, false); - private static X509Certificate2 s_dynamicServerCertificate; - private static X509Certificate2Collection s_dynamicCaCertificates; - private static object certLock = new object(); - - - // These Get* methods make a copy of the certificates so that consumers own the lifetime of the - // certificates handed back. Consumers are expected to dispose of their certs when done with them. - - public static X509Certificate2 GetDynamicServerCerttificate(X509Certificate2Collection? chainCertificates) - { - lock (certLock) - { - if (s_dynamicServerCertificate == null) - { - CleanupCertificates(); - (s_dynamicServerCertificate, s_dynamicCaCertificates) = GenerateCertificates("localhost", nameof(Configuration) + nameof(Certificates)); - } - - chainCertificates?.AddRange(s_dynamicCaCertificates); - return new X509Certificate2(s_dynamicServerCertificate); - } - } - public static void CleanupCertificates([CallerMemberName] string? testName = null, StoreName storeName = StoreName.CertificateAuthority) { string caName = $"O={testName}"; @@ -78,7 +56,9 @@ public static void CleanupCertificates([CallerMemberName] string? testName = nul } } } - catch { }; + catch + { + } try { @@ -95,7 +75,9 @@ public static void CleanupCertificates([CallerMemberName] string? testName = nul } } } - catch { }; + catch + { + } } internal static X509ExtensionCollection BuildTlsServerCertExtensions(string serverName) @@ -119,7 +101,68 @@ private static X509ExtensionCollection BuildTlsCertExtensions(string targetName, return extensions; } - public static (X509Certificate2 certificate, X509Certificate2Collection) GenerateCertificates(string targetName, [CallerMemberName] string? testName = null, bool longChain = false, bool serverCertificate = true, bool ephemeralKey = false) + internal class PkiHolder : IDisposable + { + internal CertificateAuthority Root { get; } + internal CertificateAuthority[] Intermediates { get; } + public X509Certificate2 EndEntity { get; } + public X509Certificate2Collection IssuerChain { get; } + internal RevocationResponder Responder { get; } + + private readonly string? _testName; + + public PkiHolder(string? testName, CertificateAuthority root, CertificateAuthority[] intermediates, X509Certificate2 endEntity, RevocationResponder responder) + { + _testName = testName; + Root = root; + Intermediates = intermediates; + EndEntity = endEntity; + Responder = responder; + + // Walk the intermediates backwards so we build the chain collection as + // Issuer3 + // Issuer2 + // Issuer1 + // Root + IssuerChain = new X509Certificate2Collection(); + for (int i = intermediates.Length - 1; i >= 0; i--) + { + CertificateAuthority authority = intermediates[i]; + + IssuerChain.Add(authority.CloneIssuerCert()); + } + + IssuerChain.Add(root.CloneIssuerCert()); + } + + public SslStreamCertificateContext CreateSslStreamCertificateContext() + { + return SslStreamCertificateContext.Create(EndEntity, IssuerChain); + } + + public void Dispose() + { + foreach (CertificateAuthority authority in Intermediates) + { + authority.Dispose(); + } + Root.Dispose(); + EndEntity.Dispose(); + Responder.Dispose(); + + foreach (X509Certificate2 authority in IssuerChain) + { + authority.Dispose(); + } + + if (PlatformDetection.IsWindows && _testName != null) + { + CleanupCertificates(_testName); + } + } + } + + internal static PkiHolder GenerateCertificates(string targetName, [CallerMemberName] string? testName = null, bool longChain = false, bool serverCertificate = true, bool ephemeralKey = false) { const int keySize = 2048; if (PlatformDetection.IsWindows && testName != null) @@ -131,7 +174,7 @@ public static (X509Certificate2 certificate, X509Certificate2Collection) Generat X509ExtensionCollection extensions = BuildTlsCertExtensions(targetName, serverCertificate); CertificateAuthority.BuildPrivatePki( - PkiOptions.IssuerRevocationViaCrl, + PkiOptions.AllRevocation, out RevocationResponder responder, out CertificateAuthority root, out CertificateAuthority[] intermediates, @@ -142,24 +185,6 @@ public static (X509Certificate2 certificate, X509Certificate2Collection) Generat keyFactory: CertificateAuthority.KeyFactory.RSASize(keySize), extensions: extensions); - // Walk the intermediates backwards so we build the chain collection as - // Issuer3 - // Issuer2 - // Issuer1 - // Root - for (int i = intermediates.Length - 1; i >= 0; i--) - { - CertificateAuthority authority = intermediates[i]; - - chain.Add(authority.CloneIssuerCert()); - authority.Dispose(); - } - - chain.Add(root.CloneIssuerCert()); - - responder.Dispose(); - root.Dispose(); - if (!ephemeralKey && PlatformDetection.IsWindows) { X509Certificate2 ephemeral = endEntity; @@ -167,9 +192,8 @@ public static (X509Certificate2 certificate, X509Certificate2Collection) Generat ephemeral.Dispose(); } - return (endEntity, chain); + return new PkiHolder(testName, root, intermediates, endEntity, responder); } - } } } 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 05149276a1b158..d027b87f0d86df 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs @@ -48,7 +48,7 @@ public void Ctor_ExpectedDefaultValues() using (HttpClientHandler handler = CreateHttpClientHandler()) { Assert.Null(handler.ServerCertificateCustomValidationCallback); - Assert.False(handler.CheckCertificateRevocationList); + Assert.True(handler.CheckCertificateRevocationList); } } @@ -232,7 +232,9 @@ public async Task NoCallback_BadCertificate_ThrowsException(string url) [ActiveIssue("https://github.com/dotnet/runtime/issues/106634", typeof(PlatformDetection), nameof(PlatformDetection.IsAlpine))] public async Task NoCallback_RevokedCertificate_NoRevocationChecking_Succeeds() { - using (HttpClient client = CreateHttpClient()) + HttpClientHandler handler = CreateHttpClientHandler(); + handler.CheckCertificateRevocationList = false; + using (HttpClient client = CreateHttpClient(handler)) using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RevokedCertRemoteServer)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -244,7 +246,6 @@ public async Task NoCallback_RevokedCertificate_NoRevocationChecking_Succeeds() public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails() { HttpClientHandler handler = CreateHttpClientHandler(); - handler.CheckCertificateRevocationList = true; using (HttpClient client = CreateHttpClient(handler)) { await Assert.ThrowsAsync(() => client.GetAsync(Configuration.Http.RevokedCertRemoteServer)); diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index 4fc25003717f2f..450edaab12e377 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -78,7 +78,7 @@ public void Ctor_ExpectedDefaultPropertyValues() Assert.True(handler.SupportsRedirectConfiguration); // Changes from .NET Framework. - Assert.False(handler.CheckCertificateRevocationList); + Assert.True(handler.CheckCertificateRevocationList); Assert.Equal(0, handler.MaxRequestContentBufferSize); Assert.Equal(SslProtocols.None, handler.SslProtocols); } diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj b/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj index 7e6af9f6b954f9..2cba1a88684d98 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System.Net.Http.WinHttpHandler.csproj @@ -96,6 +96,8 @@ System.Net.Http.WinHttpHandler + ? _serverCertificateValidationCallback; - private bool _checkCertificateRevocationList; + private bool _checkCertificateRevocationList = DefaultCertificateRevocationCheck; private ClientCertificateOption _clientCertificateOption = ClientCertificateOption.Manual; private X509Certificate2Collection? _clientCertificates; // Only create collection when required. private ICredentials? _serverCredentials; diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/UnitTests/System.Net.Http.WinHttpHandler.Unit.Tests.csproj b/src/libraries/System.Net.Http.WinHttpHandler/tests/UnitTests/System.Net.Http.WinHttpHandler.Unit.Tests.csproj index edabede4649558..e2553e9b42e680 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/UnitTests/System.Net.Http.WinHttpHandler.Unit.Tests.csproj +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/UnitTests/System.Net.Http.WinHttpHandler.Unit.Tests.csproj @@ -24,6 +24,8 @@ Link="Common\Interop\Windows\WinHttp\Interop.SafeWinHttpHandle.cs" /> + _pkiHolder.EndEntity; + public X509Certificate2Collection ServerChain => _pkiHolder.IssuerChain; + + private readonly Configuration.Certificates.PkiHolder _pkiHolder; + + public CertificateSetup() + { + _pkiHolder = Configuration.Certificates.GenerateCertificates("localhost", nameof(HttpClientHandlerTestBase), longChain: true); + } + + public SslStreamCertificateContext CreateSslStreamCertificateContext() => _pkiHolder.CreateSslStreamCertificateContext(); + + public void Dispose() + { + _pkiHolder.Dispose(); + } + } + public sealed class SocketsHttpHandler_HttpClientHandler_Asynchrony_Test_Http11 : SocketsHttpHandler_HttpClientHandler_Asynchrony_Test { public SocketsHttpHandler_HttpClientHandler_Asynchrony_Test_Http11(ITestOutputHelper output) : base(output) { } @@ -2520,7 +2540,7 @@ public void SslOptions_GetSet_Roundtrips() Assert.True(options.AllowRenegotiation); Assert.Null(options.ApplicationProtocols); - Assert.Equal(X509RevocationMode.NoCheck, options.CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.Online, options.CertificateRevocationCheckMode); Assert.Null(options.ClientCertificates); Assert.Equal(SslProtocols.None, options.EnabledSslProtocols); Assert.Equal(EncryptionPolicy.RequireEncryption, options.EncryptionPolicy); @@ -4384,15 +4404,17 @@ public SocketsHttpHandler_RequestContentLengthMismatchTest_Http3(ITestOutputHelp [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] public abstract class SocketsHttpHandler_SecurityTest : HttpClientHandlerTestBase { - public SocketsHttpHandler_SecurityTest(ITestOutputHelper output) : base(output) { } + private readonly CertificateSetup _certificateSetup; + + public SocketsHttpHandler_SecurityTest(ITestOutputHelper output, CertificateSetup certificateSetup) : base(output) + { + _certificateSetup = certificateSetup; + } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] public async Task SslOptions_CustomTrust_Ok() { - X509Certificate2Collection caCerts = new X509Certificate2Collection(); - X509Certificate2 certificate = Configuration.Certificates.GetDynamicServerCerttificate(caCerts); - - GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = certificate }; + GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = new X509Certificate2(_certificateSetup.ServerCert) }; await LoopbackServerFactory.CreateClientAndServerAsync( async uri => { @@ -4405,8 +4427,8 @@ await LoopbackServerFactory.CreateClientAndServerAsync( TrustMode = X509ChainTrustMode.CustomRootTrust, }; - policy.ExtraStore.AddRange(caCerts); - policy.CustomTrustStore.Add(caCerts[caCerts.Count - 1]); + policy.ExtraStore.AddRange(_certificateSetup.ServerChain); + policy.CustomTrustStore.Add(_certificateSetup.ServerChain[^1]); socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy }; using HttpClient client = CreateHttpClient(handler); client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact; @@ -4425,15 +4447,22 @@ await LoopbackServerFactory.CreateClientAndServerAsync( [Fact] public async Task SslOptions_InvalidName_Throws() { - X509Certificate2Collection caCerts = new X509Certificate2Collection(); - using X509Certificate2 certificate = Configuration.Certificates.GetDynamicServerCerttificate(caCerts); - - GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = certificate }; + GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = new X509Certificate2(_certificateSetup.ServerCert) }; await LoopbackServerFactory.CreateClientAndServerAsync( async uri => { using HttpClientHandler handler = CreateHttpClientHandler(allowAllCertificates: false); var socketsHandler = (SocketsHttpHandler)GetUnderlyingSocketsHttpHandler(handler); + + var policy = new X509ChainPolicy() + { + RevocationMode = X509RevocationMode.NoCheck, + TrustMode = X509ChainTrustMode.CustomRootTrust, + }; + + policy.ExtraStore.AddRange(_certificateSetup.ServerChain); + policy.CustomTrustStore.Add(_certificateSetup.ServerChain[^1]); + socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy }; using HttpClient client = CreateHttpClient(handler); client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact; HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion, VersionPolicy = HttpVersionPolicy.RequestVersionExact }; @@ -4456,10 +4485,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync( [Fact] public async Task SslOptions_CustomPolicy_IgnoresNameMismatch() { - X509Certificate2Collection caCerts = new X509Certificate2Collection(); - X509Certificate2 certificate = Configuration.Certificates.GetDynamicServerCerttificate(caCerts); - - GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = certificate }; + GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = new X509Certificate2(_certificateSetup.ServerCert) }; await LoopbackServerFactory.CreateClientAndServerAsync( async uri => { @@ -4473,8 +4499,8 @@ await LoopbackServerFactory.CreateClientAndServerAsync( VerificationFlags = X509VerificationFlags.IgnoreInvalidName, }; - policy.ExtraStore.AddRange(caCerts); - policy.CustomTrustStore.Add(caCerts[caCerts.Count - 1]); + policy.ExtraStore.AddRange(_certificateSetup.ServerChain); + policy.CustomTrustStore.Add(_certificateSetup.ServerChain[^1]); socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy }; using HttpClient client = CreateHttpClient(handler); @@ -4493,9 +4519,9 @@ await LoopbackServerFactory.CreateClientAndServerAsync( } } - public sealed class SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http11 : SocketsHttpHandler_SecurityTest + public sealed class SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http11 : SocketsHttpHandler_SecurityTest, IClassFixture { - public SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http11(ITestOutputHelper output) : base(output) { } + public SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http11(ITestOutputHelper output, CertificateSetup certificateSetup) : base(output, certificateSetup) { } protected override Version UseVersion => HttpVersion.Version11; #if DEBUG @@ -4545,9 +4571,10 @@ await server.AcceptConnectionAsync(async connection => } [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))] - public sealed class SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http2 : SocketsHttpHandler_SecurityTest + public sealed class SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http2 : SocketsHttpHandler_SecurityTest, IClassFixture { - public SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http2(ITestOutputHelper output) : base(output) { } + public SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http2(ITestOutputHelper output, CertificateSetup certificateSetup) : base(output, certificateSetup) { } + protected override Version UseVersion => HttpVersion.Version20; } diff --git a/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTlsTest.cs b/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTlsTest.cs index 794fc2e7bf45f5..2f46b2a0e3babf 100644 --- a/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTlsTest.cs +++ b/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTlsTest.cs @@ -13,26 +13,26 @@ namespace System.Net.Mail.Tests { + using Configuration = System.Net.Test.Common.Configuration; + // Common test setup to share across test cases. public class CertificateSetup : IDisposable { - public readonly X509Certificate2 serverCert; - public readonly X509Certificate2Collection serverChain; - public readonly SslStreamCertificateContext serverCertContext; + public X509Certificate2 ServerCert => _pkiHolder.EndEntity; + public X509Certificate2Collection ServerChain => _pkiHolder.IssuerChain; + + private readonly Configuration.Certificates.PkiHolder _pkiHolder; public CertificateSetup() { - (serverCert, serverChain) = System.Net.Test.Common.Configuration.Certificates.GenerateCertificates("localhost", nameof(SmtpClientTlsTest<>)); - serverCertContext = SslStreamCertificateContext.Create(serverCert, serverChain); + _pkiHolder = Configuration.Certificates.GenerateCertificates("localhost", nameof(SmtpClientTlsTest<>), longChain: true); } + public SslStreamCertificateContext CreateSslStreamCertificateContext() => _pkiHolder.CreateSslStreamCertificateContext(); + public void Dispose() { - serverCert.Dispose(); - foreach (var c in serverChain) - { - c.Dispose(); - } + _pkiHolder.Dispose(); } } @@ -47,7 +47,7 @@ public SmtpClientTlsTest(ITestOutputHelper output, CertificateSetup certificateS _certificateSetup = certificateSetup; Server.SslOptions = new SslServerAuthenticationOptions { - ServerCertificateContext = _certificateSetup.serverCertContext, + ServerCertificateContext = _certificateSetup.CreateSslStreamCertificateContext(), ClientCertificateRequired = false, }; @@ -158,7 +158,7 @@ public async Task AuthenticationException_Propagates() public async Task ClientCertificateRequired_Sent() { Server.SslOptions.ClientCertificateRequired = true; - X509Certificate2 clientCert = _certificateSetup.serverCert; // use the server cert as a client cert for testing + X509Certificate2 clientCert = _certificateSetup.ServerCert; // use the server cert as a client cert for testing X509Certificate2? receivedClientCert = null; Server.SslOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => { diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs index 3fdf1a8a35cf82..49464fe44514e7 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs @@ -27,22 +27,21 @@ namespace System.Net.Quic.Tests public class CertificateSetup : IDisposable { - public readonly X509Certificate2 serverCert; - public readonly X509Certificate2Collection serverChain; + public X509Certificate2 ServerCert => _pkiHolder.EndEntity; + public X509Certificate2Collection ServerChain => _pkiHolder.IssuerChain; + + Configuration.Certificates.PkiHolder _pkiHolder; public CertificateSetup() { - Configuration.Certificates.CleanupCertificates(nameof(MsQuicTests)); - (serverCert, serverChain) = Configuration.Certificates.GenerateCertificates("localhost", nameof(MsQuicTests), longChain: true); + _pkiHolder = Configuration.Certificates.GenerateCertificates("localhost", nameof(MsQuicTests), longChain: true); } + public SslStreamCertificateContext CreateSslStreamCertificateContext() => _pkiHolder.CreateSslStreamCertificateContext(); + public void Dispose() { - serverCert.Dispose(); - foreach (var c in serverChain) - { - c.Dispose(); - } + _pkiHolder.Dispose(); } } @@ -181,74 +180,63 @@ bool TestWeakReferences() [Fact] public async Task ConnectWithCertificateChain() { - (X509Certificate2 certificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates("localhost", longChain: true); - try - { - X509Certificate2 rootCA = chain[chain.Count - 1]; + X509Certificate2 certificate = _certificates.ServerCert; + X509Certificate2Collection chain = _certificates.ServerChain; - var listenerOptions = new QuicListenerOptions() - { - ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), - ApplicationProtocols = new List() { ApplicationProtocol }, - ConnectionOptionsCallback = (_, _, _) => - { - var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificate, chain); - serverOptions.ServerAuthenticationOptions.ServerCertificate = null; - return ValueTask.FromResult(serverOptions); - } - }; + X509Certificate2 rootCA = chain[chain.Count - 1]; - // Use whatever endpoint, it'll get overwritten in CreateConnectedQuicConnection. - QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listenerOptions.ListenEndPoint); - clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => + var listenerOptions = new QuicListenerOptions() + { + ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), + ApplicationProtocols = new List() { ApplicationProtocol }, + ConnectionOptionsCallback = (_, _, _) => { - Assert.Equal(certificate.Subject, cert.Subject); - Assert.Equal(certificate.Issuer, cert.Issuer); + var serverOptions = CreateQuicServerOptions(); + serverOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificate, chain); + serverOptions.ServerAuthenticationOptions.ServerCertificate = null; + return ValueTask.FromResult(serverOptions); + } + }; - // Dispose of the chain's elements before calling Build, which will overwrite them. - for (int i = 0; i < chain.ChainElements.Count; i++) - { - chain.ChainElements[i].Certificate.Dispose(); - } + // Use whatever endpoint, it'll get overwritten in CreateConnectedQuicConnection. + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listenerOptions.ListenEndPoint); + clientOptions.ClientAuthenticationOptions.CertificateChainPolicy = new X509ChainPolicy() + { + // We should get full chain without root CA. + // With trusted root, we should be able to build chain. + TrustMode = X509ChainTrustMode.CustomRootTrust, + CustomTrustStore = { rootCA }, + }; - // We should get full chain without root CA. - // With trusted root, we should be able to build chain. - chain.ChainPolicy.CustomTrustStore.Add(rootCA); - chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; - bool ret = chain.Build(certificate); - if (!ret) + clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => + { + Assert.Equal(certificate.Subject, cert.Subject); + Assert.Equal(certificate.Issuer, cert.Issuer); + + if (errors != SslPolicyErrors.None) + { + _output.WriteLine("Certificate validation failed with {0}", errors); + _output.WriteLine("Chain has {0} elements", chain.ChainElements.Count); + foreach (X509ChainElement element in chain.ChainElements) { - _output.WriteLine("Chain build failed with {0} elements", chain.ChainElements); - foreach (X509ChainElement element in chain.ChainElements) + _output.WriteLine("Element subject {0} and issuer {1}", element.Certificate.Subject, element.Certificate.Issuer); + _output.WriteLine("Element status len {0}", element.ChainElementStatus.Length); + foreach (X509ChainStatus status in element.ChainElementStatus) { - _output.WriteLine("Element subject {0} and issuer {1}", element.Certificate.Subject, element.Certificate.Issuer); - _output.WriteLine("Element status len {0}", element.ChainElementStatus.Length); - foreach (X509ChainStatus status in element.ChainElementStatus) - { - _output.WriteLine($"Status: {status.Status}: {status.StatusInformation}"); - } + _output.WriteLine($"Status: {status.Status}: {status.StatusInformation}"); } } + } - return ret; - }; + return errors == SslPolicyErrors.None; + }; - (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listenerOptions); - using X509Certificate clientRemoteCertificate = clientConnection.RemoteCertificate; - Assert.Equal(certificate, clientRemoteCertificate); - Assert.Null(serverConnection.RemoteCertificate); - await serverConnection.DisposeAsync(); - await clientConnection.DisposeAsync(); - } - finally - { - foreach (X509Certificate2 cert in chain) - { - cert.Dispose(); - } - certificate.Dispose(); - } + (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listenerOptions); + using X509Certificate clientRemoteCertificate = clientConnection.RemoteCertificate; + Assert.Equal(certificate, clientRemoteCertificate); + Assert.Null(serverConnection.RemoteCertificate); + await serverConnection.DisposeAsync(); + await clientConnection.DisposeAsync(); } [Theory] @@ -256,7 +244,7 @@ public async Task ConnectWithCertificateChain() [InlineData(false)] public async Task ConnectWithUntrustedCaWithCustomTrust_OK(bool usePartialChain) { - int split = Random.Shared.Next(0, _certificates.serverChain.Count - 1); + int split = Random.Shared.Next(0, _certificates.ServerChain.Count - 1); X509Certificate2Collection serverChain; if (usePartialChain) @@ -265,19 +253,19 @@ public async Task ConnectWithUntrustedCaWithCustomTrust_OK(bool usePartialChain) serverChain = new X509Certificate2Collection(); for (int i = 0; i < split; i++) { - serverChain.Add(_certificates.serverChain[i]); + serverChain.Add(_certificates.ServerChain[i]); } } else { - serverChain = _certificates.serverChain; + serverChain = _certificates.ServerChain; } var listenerOptions = CreateQuicListenerOptions(); listenerOptions.ConnectionOptionsCallback = (_, _, _) => { var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.serverCert, serverChain); + serverOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.ServerCert, serverChain); serverOptions.ServerAuthenticationOptions.RemoteCertificateValidationCallback = null; return ValueTask.FromResult(serverOptions); }; @@ -293,14 +281,14 @@ public async Task ConnectWithUntrustedCaWithCustomTrust_OK(bool usePartialChain) RevocationMode = X509RevocationMode.NoCheck, TrustMode = X509ChainTrustMode.CustomRootTrust }; - clientSslOptions.CertificateChainPolicy.CustomTrustStore.Add(_certificates.serverChain[_certificates.serverChain.Count - 1]); + clientSslOptions.CertificateChainPolicy.CustomTrustStore.Add(_certificates.ServerChain[_certificates.ServerChain.Count - 1]); // Add only one CA to verify that peer did send intermediate CA cert. // In case of partial chain, we need to make missing certs available. if (usePartialChain) { - for (int i = split; i < _certificates.serverChain.Count - 1; i++) + for (int i = split; i < _certificates.ServerChain.Count - 1; i++) { - clientSslOptions.CertificateChainPolicy.ExtraStore.Add(_certificates.serverChain[i]); + clientSslOptions.CertificateChainPolicy.ExtraStore.Add(_certificates.ServerChain[i]); } } @@ -508,86 +496,66 @@ public async Task ConnectWithIpSetsSni(string destination) [Fact] public async Task ConnectWithCertificateForDifferentName_Throws() { - (X509Certificate2 certificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates("localhost"); - try - { - var quicOptions = new QuicListenerOptions() - { - ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), - ApplicationProtocols = new List() { ApplicationProtocol }, - ConnectionOptionsCallback = (_, _, _) => - { - var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ServerCertificate = certificate; - return ValueTask.FromResult(serverOptions); - } - }; - await using QuicListener listener = await CreateQuicListener(quicOptions); - - QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); - // Use different target host on purpose to get RemoteCertificateNameMismatch ssl error. - clientOptions.ClientAuthenticationOptions.TargetHost = "loopback"; - clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => - { - Assert.Equal(certificate.Subject, cert.Subject); - Assert.Equal(certificate.Issuer, cert.Issuer); - Assert.Equal(SslPolicyErrors.RemoteCertificateNameMismatch, errors & SslPolicyErrors.RemoteCertificateNameMismatch); - return SslPolicyErrors.None == errors; - }; + X509Certificate2 certificate = _certificates.ServerCert; - await Assert.ThrowsAsync(async () => await CreateQuicConnection(clientOptions)); - } - finally + var quicOptions = new QuicListenerOptions() { - foreach (X509Certificate2 cert in chain) + ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), + ApplicationProtocols = new List() { ApplicationProtocol }, + ConnectionOptionsCallback = (_, _, _) => { - cert.Dispose(); + var serverOptions = CreateQuicServerOptions(); + serverOptions.ServerAuthenticationOptions.ServerCertificate = certificate; + return ValueTask.FromResult(serverOptions); } - certificate.Dispose(); - } + }; + await using QuicListener listener = await CreateQuicListener(quicOptions); + + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); + // Use different target host on purpose to get RemoteCertificateNameMismatch ssl error. + clientOptions.ClientAuthenticationOptions.TargetHost = "loopback"; + clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => + { + Assert.Equal(certificate.Subject, cert.Subject); + Assert.Equal(certificate.Issuer, cert.Issuer); + Assert.Equal(SslPolicyErrors.RemoteCertificateNameMismatch, errors & SslPolicyErrors.RemoteCertificateNameMismatch); + return SslPolicyErrors.None == errors; + }; + + await Assert.ThrowsAsync(async () => await CreateQuicConnection(clientOptions)); } [Fact] public async Task ConnectWithCertificate_MissingTargetHost_Succeeds() { - (X509Certificate2 certificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates("localhost"); - try - { - var quicOptions = new QuicListenerOptions() - { - // loopback may resolve to IPv6 - ListenEndPoint = new IPEndPoint(IPAddress.IPv6Any, 0), - ApplicationProtocols = new List() { ApplicationProtocol }, - ConnectionOptionsCallback = (_, _, _) => - { - var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ServerCertificate = certificate; - return ValueTask.FromResult(serverOptions); - } - }; - await using QuicListener listener = await CreateQuicListener(quicOptions); + X509Certificate2 certificate = _certificates.ServerCert; - QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(new DnsEndPoint("localhost", listener.LocalEndPoint.Port)); - // Do not set target host on client options, it should be taken from remote endpoint and used for both ClientHello SNI and Server cert validation - clientOptions.ClientAuthenticationOptions.TargetHost = null; - clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => - { - Assert.Equal(certificate.Subject, cert.Subject); - Assert.Equal(certificate.Issuer, cert.Issuer); - Assert.Equal(SslPolicyErrors.None, errors & SslPolicyErrors.RemoteCertificateNameMismatch); - return true; - }; - - await using QuicConnection connection = await CreateQuicConnection(clientOptions); - } - finally + var quicOptions = new QuicListenerOptions() { - foreach (X509Certificate2 cert in chain) + // loopback may resolve to IPv6 + ListenEndPoint = new IPEndPoint(IPAddress.IPv6Any, 0), + ApplicationProtocols = new List() { ApplicationProtocol }, + ConnectionOptionsCallback = (_, _, _) => { - cert.Dispose(); + var serverOptions = CreateQuicServerOptions(); + serverOptions.ServerAuthenticationOptions.ServerCertificate = certificate; + return ValueTask.FromResult(serverOptions); } - certificate.Dispose(); - } + }; + await using QuicListener listener = await CreateQuicListener(quicOptions); + + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(new DnsEndPoint("localhost", listener.LocalEndPoint.Port)); + // Do not set target host on client options, it should be taken from remote endpoint and used for both ClientHello SNI and Server cert validation + clientOptions.ClientAuthenticationOptions.TargetHost = null; + clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => + { + Assert.Equal(certificate.Subject, cert.Subject); + Assert.Equal(certificate.Issuer, cert.Issuer); + Assert.Equal(SslPolicyErrors.None, errors & SslPolicyErrors.RemoteCertificateNameMismatch); + return true; + }; + + await using QuicConnection connection = await CreateQuicConnection(clientOptions); } [ConditionalTheory] @@ -603,43 +571,34 @@ public async Task ConnectWithCertificateForLoopbackIP_IndicatesExpectedError(str throw new SkipTestException("IPv6 is not available on this platform"); } - (X509Certificate2 certificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates(expectsError ? "badhost" : "localhost"); - try - { - var listenerOptions = new QuicListenerOptions() - { - ListenEndPoint = new IPEndPoint(ipAddress, 0), - ApplicationProtocols = new List() { ApplicationProtocol }, - ConnectionOptionsCallback = (_, _, _) => - { - var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ServerCertificate = certificate; - return ValueTask.FromResult(serverOptions); - } - }; - - // Use whatever endpoint, it'll get overwritten in CreateConnectedQuicConnection. - QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listenerOptions.ListenEndPoint); - clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => - { - Assert.Equal(certificate.Subject, cert.Subject); - Assert.Equal(certificate.Issuer, cert.Issuer); - Assert.Equal(expectsError ? SslPolicyErrors.RemoteCertificateNameMismatch : SslPolicyErrors.None, errors & SslPolicyErrors.RemoteCertificateNameMismatch); - return true; - }; + using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(expectsError ? "badhost" : "localhost"); + X509Certificate2 certificate = pkiHolder.EndEntity; - (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listenerOptions); - await clientConnection.DisposeAsync(); - await serverConnection.DisposeAsync(); - } - finally + var listenerOptions = new QuicListenerOptions() { - foreach (X509Certificate2 cert in chain) + ListenEndPoint = new IPEndPoint(ipAddress, 0), + ApplicationProtocols = new List() { ApplicationProtocol }, + ConnectionOptionsCallback = (_, _, _) => { - cert.Dispose(); + var serverOptions = CreateQuicServerOptions(); + serverOptions.ServerAuthenticationOptions.ServerCertificate = certificate; + return ValueTask.FromResult(serverOptions); } - certificate.Dispose(); - } + }; + + // Use whatever endpoint, it'll get overwritten in CreateConnectedQuicConnection. + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listenerOptions.ListenEndPoint); + clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => + { + Assert.Equal(certificate.Subject, cert.Subject); + Assert.Equal(certificate.Issuer, cert.Issuer); + Assert.Equal(expectsError ? SslPolicyErrors.RemoteCertificateNameMismatch : SslPolicyErrors.None, errors & SslPolicyErrors.RemoteCertificateNameMismatch); + return true; + }; + + (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listenerOptions); + await clientConnection.DisposeAsync(); + await serverConnection.DisposeAsync(); } public enum ClientCertSource @@ -724,85 +683,59 @@ public async Task ConnectWithClientCertificate(bool sendCertificate, ClientCertS [PlatformSpecific(TestPlatforms.Windows)] public async Task Server_CertificateWithEphemeralKey_Throws() { - (X509Certificate2 serverCertificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates(nameof(Server_CertificateWithEphemeralKey_Throws), ephemeralKey: true); - Configuration.Certificates.CleanupCertificates(nameof(Server_CertificateWithEphemeralKey_Throws)); + using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(Server_CertificateWithEphemeralKey_Throws), ephemeralKey: true); - try + QuicListenerOptions listenerOptions = new QuicListenerOptions() { - QuicListenerOptions listenerOptions = new QuicListenerOptions() + ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), + ApplicationProtocols = new List() { ApplicationProtocol }, + ConnectionOptionsCallback = (_, _, _) => { - ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), - ApplicationProtocols = new List() { ApplicationProtocol }, - ConnectionOptionsCallback = (_, _, _) => - { - var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ServerCertificate = null; - serverOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(serverCertificate, chain); - return ValueTask.FromResult(serverOptions); - } - }; - await using QuicListener listener = await CreateQuicListener(listenerOptions); + var serverOptions = CreateQuicServerOptions(); + serverOptions.ServerAuthenticationOptions.ServerCertificate = null; + serverOptions.ServerAuthenticationOptions.ServerCertificateContext = pkiHolder.CreateSslStreamCertificateContext(); + return ValueTask.FromResult(serverOptions); + } + }; + await using QuicListener listener = await CreateQuicListener(listenerOptions); - QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); - clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; }; + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); + clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; }; - // client connection attempt will fail - await Assert.ThrowsAsync(async () => await CreateQuicConnection(clientOptions)); + // client connection attempt will fail + await Assert.ThrowsAsync(async () => await CreateQuicConnection(clientOptions)); - // server-side failure will be reported from AcceptConnectionAsync - AuthenticationException e = await Assert.ThrowsAsync(async () => await listener.AcceptConnectionAsync()); - Assert.Contains("ephemeral", e.Message); - } - finally - { - Configuration.Certificates.CleanupCertificates(nameof(Server_CertificateWithEphemeralKey_Throws)); - serverCertificate.Dispose(); - foreach (X509Certificate c in chain) - { - c.Dispose(); - } - } + // server-side failure will be reported from AcceptConnectionAsync + AuthenticationException e = await Assert.ThrowsAsync(async () => await listener.AcceptConnectionAsync()); + Assert.Contains("ephemeral", e.Message); } [ConditionalFact(typeof(QuicTestCollection), nameof(QuicTestCollection.IsUsingSchannelBackend))] [PlatformSpecific(TestPlatforms.Windows)] public async Task Client_CertificateWithEphemeralKey_Throws() { - (X509Certificate2 clientCertificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates(nameof(Client_CertificateWithEphemeralKey_Throws), ephemeralKey: true); - Configuration.Certificates.CleanupCertificates(nameof(Client_CertificateWithEphemeralKey_Throws)); + using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(Client_CertificateWithEphemeralKey_Throws), ephemeralKey: true); - try + QuicListenerOptions listenerOptions = new QuicListenerOptions() { - QuicListenerOptions listenerOptions = new QuicListenerOptions() + ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), + ApplicationProtocols = new List() { ApplicationProtocol }, + ConnectionOptionsCallback = (_, _, _) => { - ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), - ApplicationProtocols = new List() { ApplicationProtocol }, - ConnectionOptionsCallback = (_, _, _) => - { - var serverOptions = CreateQuicServerOptions(); - serverOptions.ServerAuthenticationOptions.ClientCertificateRequired = true; - serverOptions.ServerAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; }; - return ValueTask.FromResult(serverOptions); - } - }; - await using QuicListener listener = await CreateQuicListener(listenerOptions); + var serverOptions = CreateQuicServerOptions(); + serverOptions.ServerAuthenticationOptions.ClientCertificateRequired = true; + serverOptions.ServerAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; }; + return ValueTask.FromResult(serverOptions); + } + }; + await using QuicListener listener = await CreateQuicListener(listenerOptions); - QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); - clientOptions.ClientAuthenticationOptions.ClientCertificates = new X509CertificateCollection() { clientCertificate }; - clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; }; + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint); + clientOptions.ClientAuthenticationOptions.ClientCertificates = new X509CertificateCollection() { pkiHolder.EndEntity }; + clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; }; - AuthenticationException e = await Assert.ThrowsAsync(async () => await CreateQuicConnection(clientOptions)); - Assert.Contains("ephemeral", e.Message); - } - finally - { - Configuration.Certificates.CleanupCertificates(nameof(Client_CertificateWithEphemeralKey_Throws)); - clientCertificate.Dispose(); - foreach (X509Certificate c in chain) - { - c.Dispose(); - } - } + AuthenticationException e = await Assert.ThrowsAsync(async () => await CreateQuicConnection(clientOptions)); + Assert.Contains("ephemeral", e.Message); } [Theory] diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj index 1f4e9278633817..fe26b4a0a12905 100644 --- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj @@ -32,6 +32,8 @@ Link="Common\System\Obsoletions.cs" /> + diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs index 603a3b24c24af6..c70cbb3bb4e8ae 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs @@ -12,6 +12,13 @@ namespace System.Net.Security internal sealed class SslAuthenticationOptions : IDisposable { private const string EnableOcspStaplingContextSwitchName = "System.Net.Security.EnableServerOcspStaplingFromOnlyCertificateOnLinux"; + + internal static readonly X509RevocationMode DefaultRevocationMode = + AppContextSwitchHelper.GetBooleanConfig( + "System.Net.Security.NoRevocationCheckByDefault", + "DOTNET_SYSTEM_NET_SECURITY_NOREVOCATIONCHECKBYDEFAULT") + ? X509RevocationMode.NoCheck : X509RevocationMode.Online; + internal SslAuthenticationOptions() { TargetHost = string.Empty; diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs index 9f159207f93351..16700e0598dad1 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs @@ -12,7 +12,7 @@ namespace System.Net.Security public class SslClientAuthenticationOptions { private EncryptionPolicy _encryptionPolicy = EncryptionPolicy.RequireEncryption; - private X509RevocationMode _checkCertificateRevocation = X509RevocationMode.NoCheck; + private X509RevocationMode _checkCertificateRevocation = SslAuthenticationOptions.DefaultRevocationMode; private SslProtocols _enabledSslProtocols = SslProtocols.None; private bool _allowRenegotiation = true; private bool _allowTlsResume = true; diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs index 6ca64677652d50..940461b2632b2b 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs @@ -10,7 +10,7 @@ namespace System.Net.Security { public class SslServerAuthenticationOptions { - private X509RevocationMode _checkCertificateRevocation = X509RevocationMode.NoCheck; + private X509RevocationMode _checkCertificateRevocation = SslAuthenticationOptions.DefaultRevocationMode; private SslProtocols _enabledSslProtocols = SslProtocols.None; private EncryptionPolicy _encryptionPolicy = EncryptionPolicy.RequireEncryption; private bool _allowRenegotiation; diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs index 6844e0d47189c7..eae09611396bca 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs @@ -185,7 +185,10 @@ public async Task CertificateValidationClientServer_EndToEnd_Ok(ClientCertSource TargetHost = Guid.NewGuid().ToString("N"), ClientCertificates = clientCerts, EnabledSslProtocols = SslProtocolSupport.DefaultSslProtocols, - CertificateChainPolicy = new X509ChainPolicy(), + CertificateChainPolicy = new X509ChainPolicy() + { + RevocationMode = X509RevocationMode.NoCheck, + } }; if (clientCertSource == ClientCertSource.CertificateContext) diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs index e9b78bbc344862..ea3809f33f2e88 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs @@ -22,13 +22,13 @@ public class SslStreamCertificateTrustTest [SkipOnPlatform(TestPlatforms.Windows, "CertificateCollection-based SslCertificateTrust is not Supported on Windows")] public async Task SslStream_SendCertificateTrust_CertificateCollection() { - (X509Certificate2 certificate, X509Certificate2Collection caCerts) = Configuration.Certificates.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); + using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); - SslCertificateTrust trust = SslCertificateTrust.CreateForX509Collection(caCerts, sendTrustInHandshake: true); + SslCertificateTrust trust = SslCertificateTrust.CreateForX509Collection(pkiHolder.IssuerChain, sendTrustInHandshake: true); string[] acceptableIssuers = await ConnectAndGatherAcceptableIssuers(trust); - Assert.Equal(caCerts.Count, acceptableIssuers.Length); - Assert.Equal(caCerts.Select(c => c.Subject), acceptableIssuers); + Assert.Equal(pkiHolder.IssuerChain.Count, acceptableIssuers.Length); + Assert.Equal(pkiHolder.IssuerChain.Select(c => c.Subject), acceptableIssuers); } [ConditionalFact(nameof(SupportsSendingCustomCANamesInTls))] @@ -94,20 +94,20 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( [PlatformSpecific(TestPlatforms.Windows)] public void SslStream_SendCertificateTrust_CertificateCollection_ThrowsOnWindows() { - (X509Certificate2 certificate, X509Certificate2Collection caCerts) = Configuration.Certificates.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); + using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); - Assert.Throws(() => SslCertificateTrust.CreateForX509Collection(caCerts, sendTrustInHandshake: true)); + Assert.Throws(() => SslCertificateTrust.CreateForX509Collection(pkiHolder.IssuerChain, sendTrustInHandshake: true)); } [ConditionalFact(nameof(DoesNotSupportSendingCustomCANamesInTls))] [SkipOnPlatform(TestPlatforms.Windows, "Windows tested separately")] public void SslStream_SendCertificateTrust_ThrowsOnUnsupportedPlatform() { - (X509Certificate2 certificate, X509Certificate2Collection caCerts) = Configuration.Certificates.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); + using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); using X509Store store = new X509Store("Root", StoreLocation.LocalMachine); - Assert.Throws(() => SslCertificateTrust.CreateForX509Collection(caCerts, sendTrustInHandshake: true)); + Assert.Throws(() => SslCertificateTrust.CreateForX509Collection(pkiHolder.IssuerChain, sendTrustInHandshake: true)); Assert.Throws(() => SslCertificateTrust.CreateForX509Store(store, sendTrustInHandshake: true)); } } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamFramingTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamFramingTest.cs index f83e1b7027ebba..e6946670f76904 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamFramingTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamFramingTest.cs @@ -91,7 +91,7 @@ public async Task Handshake_Success(FramingType framingType, SslProtocols sslPro { EnabledSslProtocols = sslProtocol, CertificateRevocationCheckMode = X509RevocationMode.NoCheck, - ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.serverCert, _certificates.serverChain), + ServerCertificateContext = _certificates.CreateSslStreamCertificateContext(), RemoteCertificateValidationCallback = (sender, cert, chain, errors) => true, ClientCertificateRequired = clientCertScenario == ClientCertScenario.InHandshake, }; @@ -102,7 +102,7 @@ public async Task Handshake_Success(FramingType framingType, SslProtocols sslPro EnabledSslProtocols = sslProtocol, CertificateRevocationCheckMode = X509RevocationMode.NoCheck, ClientCertificates = clientCertScenario != ClientCertScenario.None - ? new X509CertificateCollection { _certificates.serverCert } + ? new X509CertificateCollection { _certificates.ServerCert } : new X509CertificateCollection(), RemoteCertificateValidationCallback = (sender, cert, chain, errors) => true, }; diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs index 5c2f101d922e85..811b6932e896de 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamNetworkStreamTest.cs @@ -22,22 +22,21 @@ namespace System.Net.Security.Tests public class CertificateSetup : IDisposable { - public readonly X509Certificate2 serverCert; - public readonly X509Certificate2Collection serverChain; + public X509Certificate2 ServerCert => _pkiHolder.EndEntity; + public X509Certificate2Collection ServerChain => _pkiHolder.IssuerChain; + + private readonly Configuration.Certificates.PkiHolder _pkiHolder; public CertificateSetup() { - TestHelper.CleanupCertificates(nameof(SslStreamNetworkStreamTest)); - (serverCert, serverChain) = Configuration.Certificates.GenerateCertificates("localhost", nameof(SslStreamNetworkStreamTest), longChain: true); + _pkiHolder = Configuration.Certificates.GenerateCertificates("localhost", nameof(SslStreamNetworkStreamTest), longChain: true); } + public SslStreamCertificateContext CreateSslStreamCertificateContext() => _pkiHolder.CreateSslStreamCertificateContext(); + public void Dispose() { - serverCert.Dispose(); - foreach (var c in serverChain) - { - c.Dispose(); - } + _pkiHolder.Dispose(); } } @@ -760,7 +759,7 @@ public async Task SslStream_ServerUntrustedCaWithCustomTrust_OK(bool usePartialC throw new SkipTestException("Android does not support partial chain validation."); } - int split = Random.Shared.Next(0, _certificates.serverChain.Count - 1); + int split = Random.Shared.Next(0, _certificates.ServerChain.Count - 1); var clientOptions = new SslClientAuthenticationOptions() { TargetHost = "localhost" }; clientOptions.CertificateChainPolicy = new X509ChainPolicy() @@ -768,14 +767,14 @@ public async Task SslStream_ServerUntrustedCaWithCustomTrust_OK(bool usePartialC RevocationMode = X509RevocationMode.NoCheck, TrustMode = X509ChainTrustMode.CustomRootTrust }; - clientOptions.CertificateChainPolicy.CustomTrustStore.Add(_certificates.serverChain[_certificates.serverChain.Count - 1]); + clientOptions.CertificateChainPolicy.CustomTrustStore.Add(_certificates.ServerChain[_certificates.ServerChain.Count - 1]); // Add only one CA to verify that peer did send intermediate CA cert. // In case of partial chain, we need to make missing certs available. if (usePartialChain) { - for (int i = split; i < _certificates.serverChain.Count - 1; i++) + for (int i = split; i < _certificates.ServerChain.Count - 1; i++) { - clientOptions.CertificateChainPolicy.ExtraStore.Add(_certificates.serverChain[i]); + clientOptions.CertificateChainPolicy.ExtraStore.Add(_certificates.ServerChain[i]); } } @@ -787,15 +786,15 @@ public async Task SslStream_ServerUntrustedCaWithCustomTrust_OK(bool usePartialC serverChain = new X509Certificate2Collection(); for (int i = 0; i < split; i++) { - serverChain.Add(_certificates.serverChain[i]); + serverChain.Add(_certificates.ServerChain[i]); } } else { - serverChain = _certificates.serverChain; + serverChain = _certificates.ServerChain; } - serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.serverCert, serverChain); + serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_certificates.ServerCert, serverChain); (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams(); using (clientStream) @@ -878,13 +877,15 @@ public async Task SslStream_ClientCertificate_SendsChain() StoreName storeName = OperatingSystem.IsMacOS() ? StoreName.My : StoreName.CertificateAuthority; List streams = new List(); TestHelper.CleanupCertificates(nameof(SslStream_ClientCertificate_SendsChain), storeName); - (X509Certificate2 clientCertificate, X509Certificate2Collection clientChain) = Configuration.Certificates.GenerateCertificates(nameof(SslStream_ClientCertificate_SendsChain), serverCertificate: false); + using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(SslStream_ClientCertificate_SendsChain), serverCertificate: false); + X509Certificate2 clientCertificate = pkiHolder.EndEntity; + X509Certificate2Collection clientChain = pkiHolder.IssuerChain; using (X509Store store = new X509Store(storeName, StoreLocation.CurrentUser)) { // add chain certificate so we can construct chain since there is no way how to pass intermediates directly. store.Open(OpenFlags.ReadWrite); - store.AddRange(clientChain); + store.AddRange(pkiHolder.IssuerChain); store.Close(); } @@ -920,13 +921,6 @@ public async Task SslStream_ClientCertificate_SendsChain() clientOptions.LocalCertificateSelectionCallback = (sender, target, certificates, remoteCertificate, issuers) => clientCertificate; await SslStream_ClientSendsChain_Core(clientOptions, clientChain); - - TestHelper.CleanupCertificates(nameof(SslStream_ClientCertificate_SendsChain), storeName); - clientCertificate.Dispose(); - foreach (X509Certificate c in clientChain) - { - c.Dispose(); - } } [Theory] @@ -934,7 +928,7 @@ public async Task SslStream_ClientCertificate_SendsChain() [InlineData(false)] public async Task SslStream_ClientCertificateContext_SendsChain(bool useTrust) { - (X509Certificate2 clientCertificate, X509Certificate2Collection clientChain) = Configuration.Certificates.GenerateCertificates(nameof(SslStream_ClientCertificateContext_SendsChain), serverCertificate: false); + using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(SslStream_ClientCertificateContext_SendsChain), serverCertificate: false); TestHelper.CleanupCertificates(nameof(SslStream_ClientCertificateContext_SendsChain)); SslCertificateTrust? trust = null; @@ -942,32 +936,24 @@ public async Task SslStream_ClientCertificateContext_SendsChain(bool useTrust) { // This is simplification. We make all the intermediates trusted, // normally just the root would go here. - trust = SslCertificateTrust.CreateForX509Collection(clientChain, false); + trust = SslCertificateTrust.CreateForX509Collection(pkiHolder.IssuerChain, false); } var clientOptions = new SslClientAuthenticationOptions() { TargetHost = "localhost", + RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true, + ClientCertificateContext = SslStreamCertificateContext.Create(pkiHolder.EndEntity, useTrust ? null : pkiHolder.IssuerChain, offline: true, trust), }; - clientOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; - clientOptions.ClientCertificateContext = SslStreamCertificateContext.Create(clientCertificate, useTrust ? null : clientChain, offline: true, trust); - await SslStream_ClientSendsChain_Core(clientOptions, clientChain); - - TestHelper.CleanupCertificates(nameof(SslStream_ClientCertificateContext_SendsChain)); - clientCertificate.Dispose(); - foreach (X509Certificate c in clientChain) - { - c.Dispose(); - } + await SslStream_ClientSendsChain_Core(clientOptions, pkiHolder.IssuerChain); } [Fact] [PlatformSpecific(TestPlatforms.Windows)] public async Task SslStream_EphemeralKey_Throws() { - (X509Certificate2 serverCertificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates(nameof(SslStream_EphemeralKey_Throws), ephemeralKey: true); - TestHelper.CleanupCertificates(nameof(SslStream_EphemeralKey_Throws)); + using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(nameof(SslStream_EphemeralKey_Throws), ephemeralKey: true); var clientOptions = new SslClientAuthenticationOptions() { @@ -977,7 +963,7 @@ public async Task SslStream_EphemeralKey_Throws() var serverOptions = new SslServerAuthenticationOptions() { - ServerCertificate = serverCertificate + ServerCertificate = pkiHolder.EndEntity }; (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); @@ -990,13 +976,6 @@ public async Task SslStream_EphemeralKey_Throws() server.Dispose(); await Assert.ThrowsAsync(() => t1); client.Dispose(); - - TestHelper.CleanupCertificates(nameof(SslStream_EphemeralKey_Throws)); - serverCertificate.Dispose(); - foreach (X509Certificate c in chain) - { - c.Dispose(); - } } [Theory] diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs index 46a4707b43cf01..4ac1691eb7f1fb 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs @@ -83,5 +83,31 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( Assert.True(File.ReadAllText(tempFile).Length == 0); } } + + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [InlineData(true)] + [InlineData(false)] + public void DefaultRevocationMode_OfflineRevocationByDefault_True_UsesNoCheck(bool useEnvVar) + { + var psi = new ProcessStartInfo(); + if (useEnvVar) + { + psi.Environment.Add("DOTNET_SYSTEM_NET_SECURITY_NOREVOCATIONCHECKBYDEFAULT", "true"); + } + + Assert.Equal(X509RevocationMode.Online, new SslClientAuthenticationOptions().CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.Online, new SslServerAuthenticationOptions().CertificateRevocationCheckMode); + + RemoteExecutor.Invoke(useEnvVar => + { + if (!bool.Parse(useEnvVar)) + { + AppContext.SetSwitch("System.Net.Security.NoRevocationCheckByDefault", true); + } + + Assert.Equal(X509RevocationMode.NoCheck, new SslClientAuthenticationOptions().CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.NoCheck, new SslServerAuthenticationOptions().CertificateRevocationCheckMode); + }, useEnvVar.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose(); + } } } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs index 8c9ecd96c6caad..520db2e7000b56 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs @@ -27,7 +27,8 @@ public async Task SslStream_ClientSendsSNIServerReceives_Ok(string hostName) await WithVirtualConnection(async (server, client) => { - Task clientJob = Task.Run(() => { + Task clientJob = Task.Run(() => + { client.AuthenticateAsClient(hostName); }); @@ -79,7 +80,8 @@ public async Task SslStream_ServerCallbackAndLocalCertificateSelectionSet_Throws using (SslStream server = new SslStream(stream1, false, null, selectionCallback), client = new SslStream(stream2, leaveInnerStreamOpen: false, validationCallback)) { - Task clientJob = Task.Run(() => { + Task clientJob = Task.Run(() => + { client.AuthenticateAsClient(hostName); Assert.Fail("RemoteCertificateValidationCallback called when AuthenticateAsServerAsync was expected to fail."); }); @@ -127,7 +129,8 @@ public async Task SslStream_ServerCallbackNotSet_UsesLocalCertificateSelection(s using (SslStream server = new SslStream(stream1, false, null, selectionCallback), client = new SslStream(stream2, leaveInnerStreamOpen: false, validationCallback)) { - Task clientJob = Task.Run(() => { + Task clientJob = Task.Run(() => + { client.AuthenticateAsClient(hostName); }); @@ -147,7 +150,8 @@ public async Task SslStream_NoSniFromClient_CallbackReturnsNull() { await WithVirtualConnection(async (server, client) => { - Task clientJob = Task.Run(() => { + Task clientJob = Task.Run(() => + { Assert.Throws(() => client.AuthenticateAsClient("test") ); @@ -189,8 +193,8 @@ public async Task SslStream_IpLiteral_NotSend(string target) (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions() { - TargetHost = target, - RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true, + TargetHost = target, + RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true, }; SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() { @@ -241,41 +245,33 @@ public async Task UnencodedHostName_ValidatesCertificate() string rawHostname = "räksmörgås.josefsson.org"; string punycodeHostname = "xn--rksmrgs-5wao1o.josefsson.org"; - var (serverCert, serverChain) = Configuration.Certificates.GenerateCertificates(punycodeHostname); - try + using Configuration.Certificates.PkiHolder pkiHolder = Configuration.Certificates.GenerateCertificates(punycodeHostname); + + SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() { - SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() - { - ServerCertificateContext = SslStreamCertificateContext.Create(serverCert, serverChain), - }; + ServerCertificateContext = pkiHolder.CreateSslStreamCertificateContext(), + }; - SslClientAuthenticationOptions clientOptions = new () + SslClientAuthenticationOptions clientOptions = new() + { + TargetHost = rawHostname, + CertificateChainPolicy = new X509ChainPolicy() { - TargetHost = rawHostname, - CertificateChainPolicy = new X509ChainPolicy() - { - RevocationMode = X509RevocationMode.NoCheck, - TrustMode = X509ChainTrustMode.CustomRootTrust, - CustomTrustStore = { serverChain[serverChain.Count - 1] } - } - }; + RevocationMode = X509RevocationMode.NoCheck, + TrustMode = X509ChainTrustMode.CustomRootTrust, + CustomTrustStore = { pkiHolder.IssuerChain[^1] } + } + }; - (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); - await TestConfiguration.WhenAllOrAnyFailedWithTimeout( - client.AuthenticateAsClientAsync(clientOptions, default), - server.AuthenticateAsServerAsync(serverOptions, default)); + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions, default), + server.AuthenticateAsServerAsync(serverOptions, default)); - await TestHelper.PingPong(client, server, default); - Assert.Equal(rawHostname, server.TargetHostName); - Assert.Equal(rawHostname, client.TargetHostName); - } - finally - { - serverCert.Dispose(); - foreach (var c in serverChain) c.Dispose(); - TestHelper.CleanupCertificates(rawHostname); - } + await TestHelper.PingPong(client, server, default); + Assert.Equal(rawHostname, server.TargetHostName); + Assert.Equal(rawHostname, client.TargetHostName); } [ConditionalTheory] @@ -338,7 +334,8 @@ public async Task SslStream_UnsafeInvalidIdn_Throws(string name) private static Func WithAggregateExceptionUnwrapping(Func a) { - return async () => { + return async () => + { try { await a(); diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs index c0cdc6ccd4e143..494080f0c794a0 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs @@ -471,7 +471,7 @@ public sealed class SslStreamStreamToStreamTest_Async : SslStreamStreamToStreamT protected override async Task DoHandshake(SslStream clientSslStream, SslStream serverSslStream, X509Certificate serverCertificate = null, X509Certificate clientCertificate = null) { X509CertificateCollection clientCerts = clientCertificate != null ? new X509CertificateCollection() { clientCertificate } : null; - await WithServerCertificate(serverCertificate, async(certificate, name) => + await WithServerCertificate(serverCertificate, async (certificate, name) => { Task t1 = clientSslStream.AuthenticateAsClientAsync(name, clientCerts, SslProtocols.None, checkCertificateRevocation: false); Task t2 = serverSslStream.AuthenticateAsServerAsync(certificate, clientCertificateRequired: clientCertificate != null, checkCertificateRevocation: false); @@ -636,10 +636,13 @@ await WithServerCertificate(serverCertificate, async (certificate, name) => TargetHost = name, ClientCertificates = clientCerts, EnabledSslProtocols = SslProtocols.None, + CertificateRevocationCheckMode = X509RevocationMode.NoCheck }; SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() { - ServerCertificate = certificate, ClientCertificateRequired = clientCertificate != null, + ServerCertificate = certificate, + ClientCertificateRequired = clientCertificate != null, + CertificateRevocationCheckMode = X509RevocationMode.NoCheck }; Task t1 = Task.Run(() => clientSslStream.AuthenticateAsClient(clientOptions)); Task t2 = Task.Run(() => serverSslStream.AuthenticateAsServer(serverOptions)); @@ -653,10 +656,20 @@ public sealed class SslStreamStreamToStreamTest_MemoryAsync : SslStreamStreamToS protected override async Task DoHandshake(SslStream clientSslStream, SslStream serverSslStream, X509Certificate serverCertificate = null, X509Certificate clientCertificate = null) { X509CertificateCollection clientCerts = clientCertificate != null ? new X509CertificateCollection() { clientCertificate } : null; - await WithServerCertificate(serverCertificate, async(certificate, name) => + await WithServerCertificate(serverCertificate, async (certificate, name) => { - Task t1 = clientSslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions() { TargetHost = name, ClientCertificates = clientCerts }, CancellationToken.None); - Task t2 = serverSslStream.AuthenticateAsServerAsync(new SslServerAuthenticationOptions() { ServerCertificate = certificate, ClientCertificateRequired = clientCertificate != null }, CancellationToken.None); + Task t1 = clientSslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions() + { + TargetHost = name, + ClientCertificates = clientCerts, + CertificateRevocationCheckMode = X509RevocationMode.NoCheck + }, CancellationToken.None); + Task t2 = serverSslStream.AuthenticateAsServerAsync(new SslServerAuthenticationOptions() + { + ServerCertificate = certificate, + ClientCertificateRequired = clientCertificate != null, + CertificateRevocationCheckMode = X509RevocationMode.NoCheck + }, CancellationToken.None); await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2); }); } diff --git a/src/libraries/System.Net.Security/tests/UnitTests/SslAuthenticationOptionsTests.cs b/src/libraries/System.Net.Security/tests/UnitTests/SslAuthenticationOptionsTests.cs index b7e9d29d9dda98..22c8bcfffae93d 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/SslAuthenticationOptionsTests.cs +++ b/src/libraries/System.Net.Security/tests/UnitTests/SslAuthenticationOptionsTests.cs @@ -133,13 +133,13 @@ public void EnabledSslProtocols_Get_Set_Succeeds() [Fact] public void CheckCertificateRevocation_Get_Set_Succeeds() { - Assert.Equal(X509RevocationMode.NoCheck, _clientOptions.CertificateRevocationCheckMode); - Assert.Equal(X509RevocationMode.NoCheck, _serverOptions.CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.Online, _clientOptions.CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.Online, _serverOptions.CertificateRevocationCheckMode); - _clientOptions.CertificateRevocationCheckMode = X509RevocationMode.Online; + _clientOptions.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; _serverOptions.CertificateRevocationCheckMode = X509RevocationMode.Offline; - Assert.Equal(X509RevocationMode.Online, _clientOptions.CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.NoCheck, _clientOptions.CertificateRevocationCheckMode); Assert.Equal(X509RevocationMode.Offline, _serverOptions.CertificateRevocationCheckMode); Assert.Throws(() => _clientOptions.CertificateRevocationCheckMode = (X509RevocationMode)3); diff --git a/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj index efa3cd671c53dd..42381dbab6c6b8 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj +++ b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj @@ -34,6 +34,8 @@ + Date: Thu, 7 Aug 2025 13:32:10 +0200 Subject: [PATCH 3/7] 2nd try --- .../src/System/Net/Http/WinHttpHandler.cs | 5 +--- .../Net/Security/SslAuthenticationOptions.cs | 6 +---- .../SslStreamRemoteExecutorTests.cs | 26 ------------------- 3 files changed, 2 insertions(+), 35 deletions(-) diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs index 110fc6a7ca29ca..3eb6c8da177231 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs @@ -43,10 +43,7 @@ public class WinHttpHandler : HttpMessageHandler internal static readonly Version HttpVersion20 = new Version(2, 0); internal static readonly Version HttpVersion30 = new Version(3, 0); internal static readonly Version HttpVersionUnknown = new Version(0, 0); - internal static bool DefaultCertificateRevocationCheck { get; } = - AppContextSwitchHelper.GetBooleanConfig( - "System.Net.Security.NoRevocationCheckByDefault", - "DOTNET_SYSTEM_NET_SECURITY_NOREVOCATIONCHECKBYDEFAULT") ? false : true; + internal static bool DefaultCertificateRevocationCheck { get; } = false; internal static bool CertificateCachingAppContextSwitchEnabled { get; } = AppContext.TryGetSwitch("System.Net.Http.UseWinHttpCertificateCaching", out bool enabled) && enabled; private static readonly TimeSpan s_maxTimeout = TimeSpan.FromMilliseconds(int.MaxValue); diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs index c70cbb3bb4e8ae..fce59ffaef0d74 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs @@ -13,11 +13,7 @@ internal sealed class SslAuthenticationOptions : IDisposable { private const string EnableOcspStaplingContextSwitchName = "System.Net.Security.EnableServerOcspStaplingFromOnlyCertificateOnLinux"; - internal static readonly X509RevocationMode DefaultRevocationMode = - AppContextSwitchHelper.GetBooleanConfig( - "System.Net.Security.NoRevocationCheckByDefault", - "DOTNET_SYSTEM_NET_SECURITY_NOREVOCATIONCHECKBYDEFAULT") - ? X509RevocationMode.NoCheck : X509RevocationMode.Online; + internal const X509RevocationMode DefaultRevocationMode = X509RevocationMode.NoCheck; internal SslAuthenticationOptions() { diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs index 4ac1691eb7f1fb..46a4707b43cf01 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs @@ -83,31 +83,5 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( Assert.True(File.ReadAllText(tempFile).Length == 0); } } - - [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [InlineData(true)] - [InlineData(false)] - public void DefaultRevocationMode_OfflineRevocationByDefault_True_UsesNoCheck(bool useEnvVar) - { - var psi = new ProcessStartInfo(); - if (useEnvVar) - { - psi.Environment.Add("DOTNET_SYSTEM_NET_SECURITY_NOREVOCATIONCHECKBYDEFAULT", "true"); - } - - Assert.Equal(X509RevocationMode.Online, new SslClientAuthenticationOptions().CertificateRevocationCheckMode); - Assert.Equal(X509RevocationMode.Online, new SslServerAuthenticationOptions().CertificateRevocationCheckMode); - - RemoteExecutor.Invoke(useEnvVar => - { - if (!bool.Parse(useEnvVar)) - { - AppContext.SetSwitch("System.Net.Security.NoRevocationCheckByDefault", true); - } - - Assert.Equal(X509RevocationMode.NoCheck, new SslClientAuthenticationOptions().CertificateRevocationCheckMode); - Assert.Equal(X509RevocationMode.NoCheck, new SslServerAuthenticationOptions().CertificateRevocationCheckMode); - }, useEnvVar.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose(); - } } } From 316d7add33724cbf630b7633369b60ff9df99540 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Fri, 8 Aug 2025 09:09:12 +0200 Subject: [PATCH 4/7] Revert test expectations --- ...ttpClientHandlerTest.ServerCertificates.cs | 2 +- .../System/Net/Http/HttpClientHandlerTest.cs | 38 +++++++++---------- .../FunctionalTests/SocketsHttpHandlerTest.cs | 2 +- .../SslAuthenticationOptionsTests.cs | 8 ++-- 4 files changed, 25 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 d027b87f0d86df..b2d0179378eca8 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs @@ -48,7 +48,7 @@ public void Ctor_ExpectedDefaultValues() using (HttpClientHandler handler = CreateHttpClientHandler()) { Assert.Null(handler.ServerCertificateCustomValidationCallback); - Assert.True(handler.CheckCertificateRevocationList); + Assert.False(handler.CheckCertificateRevocationList); } } diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index 450edaab12e377..aebe3c8b4511df 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -76,9 +76,9 @@ public void Ctor_ExpectedDefaultPropertyValues() Assert.False(handler.PreAuthenticate); Assert.True(handler.SupportsProxy); Assert.True(handler.SupportsRedirectConfiguration); + Assert.False(handler.CheckCertificateRevocationList); // Changes from .NET Framework. - Assert.True(handler.CheckCertificateRevocationList); Assert.Equal(0, handler.MaxRequestContentBufferSize); Assert.Equal(SslProtocols.None, handler.SslProtocols); } @@ -2300,26 +2300,26 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => switch (headerType) { case HeaderType.Request: - { - var headerLine = DecodeHeaderValue("Custom-Header"); - var receivedHeaderValue = headerLine.Substring(headerLine.IndexOf("HeaderValue")); - Assert.Equal(headerValue, receivedHeaderValue); - break; - } + { + var headerLine = DecodeHeaderValue("Custom-Header"); + var receivedHeaderValue = headerLine.Substring(headerLine.IndexOf("HeaderValue")); + Assert.Equal(headerValue, receivedHeaderValue); + break; + } case HeaderType.Content: - { - var headerLine = DecodeHeaderValue("Custom-Content-Header"); - var receivedHeaderValue = headerLine.Substring(headerLine.IndexOf("HeaderValue")); - Assert.Equal(headerValue, receivedHeaderValue); - break; - } + { + var headerLine = DecodeHeaderValue("Custom-Content-Header"); + var receivedHeaderValue = headerLine.Substring(headerLine.IndexOf("HeaderValue")); + Assert.Equal(headerValue, receivedHeaderValue); + break; + } case HeaderType.Cookie: - { - var headerLine = DecodeHeaderValue("cookie"); - var receivedHeaderValue = headerLine.Substring(headerLine.IndexOf("HeaderValue")); - Assert.Equal(headerValue, receivedHeaderValue); - break; - } + { + var headerLine = DecodeHeaderValue("cookie"); + var receivedHeaderValue = headerLine.Substring(headerLine.IndexOf("HeaderValue")); + Assert.Equal(headerValue, receivedHeaderValue); + break; + } } string DecodeHeaderValue(string headerName) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index 332e31489c0f93..f5877a3bd2e286 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -2540,7 +2540,7 @@ public void SslOptions_GetSet_Roundtrips() Assert.True(options.AllowRenegotiation); Assert.Null(options.ApplicationProtocols); - Assert.Equal(X509RevocationMode.Online, options.CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.NoCheck, options.CertificateRevocationCheckMode); Assert.Null(options.ClientCertificates); Assert.Equal(SslProtocols.None, options.EnabledSslProtocols); Assert.Equal(EncryptionPolicy.RequireEncryption, options.EncryptionPolicy); diff --git a/src/libraries/System.Net.Security/tests/UnitTests/SslAuthenticationOptionsTests.cs b/src/libraries/System.Net.Security/tests/UnitTests/SslAuthenticationOptionsTests.cs index 22c8bcfffae93d..b7e9d29d9dda98 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/SslAuthenticationOptionsTests.cs +++ b/src/libraries/System.Net.Security/tests/UnitTests/SslAuthenticationOptionsTests.cs @@ -133,13 +133,13 @@ public void EnabledSslProtocols_Get_Set_Succeeds() [Fact] public void CheckCertificateRevocation_Get_Set_Succeeds() { - Assert.Equal(X509RevocationMode.Online, _clientOptions.CertificateRevocationCheckMode); - Assert.Equal(X509RevocationMode.Online, _serverOptions.CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.NoCheck, _clientOptions.CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.NoCheck, _serverOptions.CertificateRevocationCheckMode); - _clientOptions.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; + _clientOptions.CertificateRevocationCheckMode = X509RevocationMode.Online; _serverOptions.CertificateRevocationCheckMode = X509RevocationMode.Offline; - Assert.Equal(X509RevocationMode.NoCheck, _clientOptions.CertificateRevocationCheckMode); + Assert.Equal(X509RevocationMode.Online, _clientOptions.CertificateRevocationCheckMode); Assert.Equal(X509RevocationMode.Offline, _serverOptions.CertificateRevocationCheckMode); Assert.Throws(() => _clientOptions.CertificateRevocationCheckMode = (X509RevocationMode)3); From 1674a1d8adfd6dbd0df9fe8b196f3994b6674e24 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Fri, 8 Aug 2025 09:42:07 +0200 Subject: [PATCH 5/7] revert whitespace changes --- .../System/Net/Http/HttpClientHandlerTest.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index aebe3c8b4511df..b18dd83dcbeb71 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -2300,26 +2300,26 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => switch (headerType) { case HeaderType.Request: - { - var headerLine = DecodeHeaderValue("Custom-Header"); - var receivedHeaderValue = headerLine.Substring(headerLine.IndexOf("HeaderValue")); - Assert.Equal(headerValue, receivedHeaderValue); - break; - } + { + var headerLine = DecodeHeaderValue("Custom-Header"); + var receivedHeaderValue = headerLine.Substring(headerLine.IndexOf("HeaderValue")); + Assert.Equal(headerValue, receivedHeaderValue); + break; + } case HeaderType.Content: - { - var headerLine = DecodeHeaderValue("Custom-Content-Header"); - var receivedHeaderValue = headerLine.Substring(headerLine.IndexOf("HeaderValue")); - Assert.Equal(headerValue, receivedHeaderValue); - break; - } + { + var headerLine = DecodeHeaderValue("Custom-Content-Header"); + var receivedHeaderValue = headerLine.Substring(headerLine.IndexOf("HeaderValue")); + Assert.Equal(headerValue, receivedHeaderValue); + break; + } case HeaderType.Cookie: - { - var headerLine = DecodeHeaderValue("cookie"); - var receivedHeaderValue = headerLine.Substring(headerLine.IndexOf("HeaderValue")); - Assert.Equal(headerValue, receivedHeaderValue); - break; - } + { + var headerLine = DecodeHeaderValue("cookie"); + var receivedHeaderValue = headerLine.Substring(headerLine.IndexOf("HeaderValue")); + Assert.Equal(headerValue, receivedHeaderValue); + break; + } } string DecodeHeaderValue(string headerName) From 37edd2e2b5bccf93f738d86e9ca4dacc6221cf3e Mon Sep 17 00:00:00 2001 From: Radek Zikmund <32671551+rzikm@users.noreply.github.com> Date: Fri, 8 Aug 2025 15:22:02 +0200 Subject: [PATCH 6/7] Update src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs --- .../src/System/Net/Http/WinHttpHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs index 3eb6c8da177231..1edeb191cc7ab7 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs @@ -43,7 +43,7 @@ public class WinHttpHandler : HttpMessageHandler internal static readonly Version HttpVersion20 = new Version(2, 0); internal static readonly Version HttpVersion30 = new Version(3, 0); internal static readonly Version HttpVersionUnknown = new Version(0, 0); - internal static bool DefaultCertificateRevocationCheck { get; } = false; + internal static bool DefaultCertificateRevocationCheck { get; } internal static bool CertificateCachingAppContextSwitchEnabled { get; } = AppContext.TryGetSwitch("System.Net.Http.UseWinHttpCertificateCaching", out bool enabled) && enabled; private static readonly TimeSpan s_maxTimeout = TimeSpan.FromMilliseconds(int.MaxValue); From 910600398d79e436d56956a189e388cc71732e23 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Sat, 9 Aug 2025 22:26:33 +0200 Subject: [PATCH 7/7] Fix WinHttphandlerTest --- .../tests/UnitTests/WinHttpHandlerTest.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/UnitTests/WinHttpHandlerTest.cs b/src/libraries/System.Net.Http.WinHttpHandler/tests/UnitTests/WinHttpHandlerTest.cs index 2505480230e59e..d257f38d382d6f 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/UnitTests/WinHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/UnitTests/WinHttpHandlerTest.cs @@ -45,7 +45,7 @@ public void Ctor_ExpectedDefaultPropertyValues() Assert.Equal(CookieUsePolicy.UseInternalCookieStoreOnly, handler.CookieUsePolicy); Assert.Null(handler.CookieContainer); Assert.Null(handler.ServerCertificateValidationCallback); - Assert.True(handler.CheckCertificateRevocationList); + Assert.False(handler.CheckCertificateRevocationList); Assert.Equal(ClientCertificateOption.Manual, handler.ClientCertificateOption); X509Certificate2Collection certs = handler.ClientCertificates; Assert.True(certs.Count == 0); @@ -130,7 +130,8 @@ public void TcpKeepalive_WhenEnabled_ForwardsCorrectNativeOptions() { using var handler = new WinHttpHandler(); - SendRequestHelper.Send(handler, () => { + SendRequestHelper.Send(handler, () => + { handler.TcpKeepAliveEnabled = true; handler.TcpKeepAliveTime = TimeSpan.FromMinutes(13); handler.TcpKeepAliveInterval = TimeSpan.FromSeconds(42); @@ -148,7 +149,8 @@ public void TcpKeepalive_InfiniteTimeSpan_TranslatesToUInt32MaxValue() { using var handler = new WinHttpHandler(); - SendRequestHelper.Send(handler, () => { + SendRequestHelper.Send(handler, () => + { handler.TcpKeepAliveEnabled = true; handler.TcpKeepAliveTime = Timeout.InfiniteTimeSpan; handler.TcpKeepAliveInterval = Timeout.InfiniteTimeSpan; @@ -312,7 +314,8 @@ public void CookieUsePolicy_SetUseSpecifiedCookieContainerAndContainer_ExpectedW SendRequestHelper.Send( handler, - delegate { + delegate + { handler.CookieUsePolicy = CookieUsePolicy.UseSpecifiedCookieContainer; handler.CookieContainer = new CookieContainer(); });