From bb8bd2f8b7dfadd8c9def511941c049a68613c34 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Mon, 2 Mar 2026 13:28:29 +0100 Subject: [PATCH 1/5] Disable AIA certificate downloads for server client-cert validation by default When SslStream operates as a server validating client certificates and no custom X509ChainPolicy has been provided by the user, set DisableCertificateDownloads to true on the chain policy. This prevents the server from making outbound HTTP requests to download intermediate certificates via AIA extensions. Add compat switch System.Net.Security.EnableServerAIADownloads and environment variable DOTNET_SYSTEM_NET_SECURITY_ENABLESERVERAIADOWNLOADS to opt back into the previous behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../System/Net/Security/SslStream.Protocol.cs | 35 ++++++++++++++ .../SslStreamRemoteExecutorTests.cs | 47 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs index 50711bd4eaba8c..fedecad90aa530 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs @@ -18,8 +18,11 @@ public partial class SslStream { private const string DisableTlsResumeCtxSwitch = "System.Net.Security.DisableTlsResume"; private const string DisableTlsResumeEnvironmentVariable = "DOTNET_SYSTEM_NET_SECURITY_DISABLETLSRESUME"; + private const string EnableServerAIADownloadsCtxSwitch = "System.Net.Security.EnableServerAIADownloads"; + private const string EnableServerAIADownloadsEnvironmentVariable = "DOTNET_SYSTEM_NET_SECURITY_ENABLESERVERAIADOWNLOADS"; private static volatile int s_disableTlsResume = -1; + private static volatile int s_enableServerAIADownloads = -1; internal static bool DisableTlsResume { @@ -48,6 +51,33 @@ internal static bool DisableTlsResume } } + internal static bool EnableServerAIADownloads + { + get + { + int enableServerAIADownloads = s_enableServerAIADownloads; + if (enableServerAIADownloads != -1) + { + return enableServerAIADownloads != 0; + } + + // First check for the AppContext switch, giving it priority over the environment variable. + if (AppContext.TryGetSwitch(EnableServerAIADownloadsCtxSwitch, out bool value)) + { + s_enableServerAIADownloads = value ? 1 : 0; + } + else + { + // AppContext switch wasn't used. Check the environment variable. + s_enableServerAIADownloads = + Environment.GetEnvironmentVariable(EnableServerAIADownloadsEnvironmentVariable) is string envVar && + (envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase)) ? 1 : 0; + } + + return s_enableServerAIADownloads != 0; + } + } + private SafeFreeCredentials? _credentialsHandle; @@ -1087,6 +1117,11 @@ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remot chain.ChainPolicy.RevocationMode = _sslAuthenticationOptions.CertificateRevocationCheckMode; chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; + if (_sslAuthenticationOptions.IsServer && !EnableServerAIADownloads) + { + chain.ChainPolicy.DisableCertificateDownloads = true; + } + if (trust != null) { chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs index 46a4707b43cf01..b204886aaaf809 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs @@ -83,5 +83,52 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( Assert.True(File.ReadAllText(tempFile).Length == 0); } } + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [InlineData(true)] + [InlineData(false)] + public async Task SslStream_ServerDisablesCertificateDownloads_DefaultAndCompatSwitch(bool enableAIADownloads) + { + await RemoteExecutor.Invoke(async (enableAIADownloadsStr) => + { + if (bool.Parse(enableAIADownloadsStr)) + { + AppContext.SetSwitch("System.Net.Security.EnableServerAIADownloads", true); + } + + bool disableCertificateDownloadsObserved = false; + (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams(); + using (clientStream) + using (serverStream) + using (var client = new SslStream(clientStream)) + using (var server = new SslStream(serverStream, false, (sender, certificate, chain, sslPolicyErrors) => + { + disableCertificateDownloadsObserved = chain!.ChainPolicy.DisableCertificateDownloads; + return true; + })) + using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate()) + using (X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate()) + { + SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions + { + RemoteCertificateValidationCallback = delegate { return true; }, + ClientCertificates = new X509Certificate2Collection(clientCertificate), + TargetHost = certificate.GetNameInfo(X509NameType.SimpleName, false), + }; + + SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions + { + ServerCertificate = certificate, + ClientCertificateRequired = true, + }; + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions), + server.AuthenticateAsServerAsync(serverOptions)); + + bool expectDisabled = !bool.Parse(enableAIADownloadsStr); + Assert.Equal(expectDisabled, disableCertificateDownloadsObserved); + } + }, enableAIADownloads.ToString()).DisposeAsync(); + } } } From d9454ca6a096f84a6c093fe15f428e6d9e86b32a Mon Sep 17 00:00:00 2001 From: Radek Zikmund <32671551+rzikm@users.noreply.github.com> Date: Mon, 2 Mar 2026 17:11:31 +0100 Subject: [PATCH 2/5] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../tests/FunctionalTests/SslStreamRemoteExecutorTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs index 4bf20bcd340efa..63bfd44ff538f2 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs @@ -179,7 +179,8 @@ await RemoteExecutor.Invoke(async (enableAIADownloadsStr) => using (var client = new SslStream(clientStream)) using (var server = new SslStream(serverStream, false, (sender, certificate, chain, sslPolicyErrors) => { - disableCertificateDownloadsObserved = chain!.ChainPolicy.DisableCertificateDownloads; + Assert.NotNull(chain); + disableCertificateDownloadsObserved = chain.ChainPolicy.DisableCertificateDownloads; return true; })) using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate()) From f7d8a8127dabc6e77de883bb5c6a302c31588eed Mon Sep 17 00:00:00 2001 From: Radek Zikmund <32671551+rzikm@users.noreply.github.com> Date: Fri, 6 Mar 2026 07:44:25 +0100 Subject: [PATCH 3/5] Update src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs Co-authored-by: Jeremy Barton --- .../src/System/Net/Security/SslStream.Protocol.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs index 15ebe3ca626ce0..5c5864a0bdda06 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs @@ -51,7 +51,7 @@ internal static bool DisableTlsResume } } - internal static bool EnableServerAIADownloads + internal static bool EnableServerAiaDownloads { get { From 71c624d8080cb05c623a7491a72c9076fe0f5b96 Mon Sep 17 00:00:00 2001 From: Radek Zikmund <32671551+rzikm@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:19:14 +0100 Subject: [PATCH 4/5] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/System/Net/Security/SslStream.Protocol.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs index 5c5864a0bdda06..8f3f9907c412b8 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs @@ -1117,7 +1117,7 @@ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remot chain.ChainPolicy.RevocationMode = _sslAuthenticationOptions.CertificateRevocationCheckMode; chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; - if (_sslAuthenticationOptions.IsServer && !EnableServerAIADownloads) + if (_sslAuthenticationOptions.IsServer && !EnableServerAiaDownloads) { chain.ChainPolicy.DisableCertificateDownloads = true; } From 6981bb460056dbe5ca25b921ae630f0fc155a6ea Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Fri, 6 Mar 2026 12:35:34 +0100 Subject: [PATCH 5/5] Improvements --- .../System/Net/Security/SslStream.Protocol.cs | 22 +++++++-------- .../SslStreamRemoteExecutorTests.cs | 28 +++++++++++++------ 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs index 8f3f9907c412b8..582c0a3f22c313 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs @@ -18,11 +18,11 @@ public partial class SslStream { private const string DisableTlsResumeCtxSwitch = "System.Net.Security.DisableTlsResume"; private const string DisableTlsResumeEnvironmentVariable = "DOTNET_SYSTEM_NET_SECURITY_DISABLETLSRESUME"; - private const string EnableServerAIADownloadsCtxSwitch = "System.Net.Security.EnableServerAIADownloads"; - private const string EnableServerAIADownloadsEnvironmentVariable = "DOTNET_SYSTEM_NET_SECURITY_ENABLESERVERAIADOWNLOADS"; + private const string EnableServerAiaDownloadsCtxSwitch = "System.Net.Security.EnableServerAiaDownloads"; + private const string EnableServerAiaDownloadsEnvironmentVariable = "DOTNET_SYSTEM_NET_SECURITY_ENABLESERVERAIADOWNLOADS"; private static volatile int s_disableTlsResume = -1; - private static volatile int s_enableServerAIADownloads = -1; + private static volatile int s_enableServerAiaDownloads = -1; internal static bool DisableTlsResume { @@ -55,26 +55,26 @@ internal static bool EnableServerAiaDownloads { get { - int enableServerAIADownloads = s_enableServerAIADownloads; - if (enableServerAIADownloads != -1) + int enableServerAiaDownloads = s_enableServerAiaDownloads; + if (enableServerAiaDownloads != -1) { - return enableServerAIADownloads != 0; + return enableServerAiaDownloads != 0; } // First check for the AppContext switch, giving it priority over the environment variable. - if (AppContext.TryGetSwitch(EnableServerAIADownloadsCtxSwitch, out bool value)) + if (AppContext.TryGetSwitch(EnableServerAiaDownloadsCtxSwitch, out bool value)) { - s_enableServerAIADownloads = value ? 1 : 0; + s_enableServerAiaDownloads = value ? 1 : 0; } else { // AppContext switch wasn't used. Check the environment variable. - s_enableServerAIADownloads = - Environment.GetEnvironmentVariable(EnableServerAIADownloadsEnvironmentVariable) is string envVar && + s_enableServerAiaDownloads = + Environment.GetEnvironmentVariable(EnableServerAiaDownloadsEnvironmentVariable) is string envVar && (envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase)) ? 1 : 0; } - return s_enableServerAIADownloads != 0; + return s_enableServerAiaDownloads != 0; } } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs index 63bfd44ff538f2..af0f0a60f59cf6 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs @@ -161,18 +161,27 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( } [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [InlineData(true)] - [InlineData(false)] - public async Task SslStream_ServerDisablesCertificateDownloads_DefaultAndCompatSwitch(bool enableAIADownloads) + [InlineData(true, true, false)] + [InlineData(true, false, false)] + [InlineData(false, true, true)] + [InlineData(false, false, true)] + [InlineData(null, true, false)] + [InlineData(null, false, true)] + public async Task SslStream_ServerDisablesCertificateDownloads_DefaultAndCompatSwitch(bool? appCtxSwitchValue, bool envVarSet, bool shouldDisableDownloads) { - await RemoteExecutor.Invoke(async (enableAIADownloadsStr) => + var psi = new ProcessStartInfo { - if (bool.Parse(enableAIADownloadsStr)) + Environment = { { "DOTNET_SYSTEM_NET_SECURITY_ENABLESERVERAIADOWNLOADS", envVarSet ? "1" : "0" } } + }; + + await RemoteExecutor.Invoke(async (appCtxSwitchValueString, shouldDisableDownloadsString) => + { + if (bool.TryParse(appCtxSwitchValueString, out bool value)) { - AppContext.SetSwitch("System.Net.Security.EnableServerAIADownloads", true); + AppContext.SetSwitch("System.Net.Security.EnableServerAiaDownloads", value); } - bool disableCertificateDownloadsObserved = false; + bool? disableCertificateDownloadsObserved = false; (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams(); using (clientStream) using (serverStream) @@ -183,6 +192,7 @@ await RemoteExecutor.Invoke(async (enableAIADownloadsStr) => disableCertificateDownloadsObserved = chain.ChainPolicy.DisableCertificateDownloads; return true; })) + using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate()) using (X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate()) { @@ -203,10 +213,10 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( client.AuthenticateAsClientAsync(clientOptions), server.AuthenticateAsServerAsync(serverOptions)); - bool expectDisabled = !bool.Parse(enableAIADownloadsStr); + bool expectDisabled = bool.Parse(shouldDisableDownloadsString); Assert.Equal(expectDisabled, disableCertificateDownloadsObserved); } - }, enableAIADownloads.ToString()).DisposeAsync(); + }, appCtxSwitchValue.ToString(), shouldDisableDownloads.ToString(), new RemoteInvokeOptions { StartInfo = psi }).DisposeAsync(); } } }