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 7bcbbedd614354..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,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 84ccf92379e050..af0f0a60f59cf6 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamRemoteExecutorTests.cs @@ -159,5 +159,64 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( } } } + + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [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) + { + var psi = new ProcessStartInfo + { + 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", value); + } + + 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) => + { + Assert.NotNull(chain); + 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(shouldDisableDownloadsString); + Assert.Equal(expectDisabled, disableCertificateDownloadsObserved); + } + }, appCtxSwitchValue.ToString(), shouldDisableDownloads.ToString(), new RemoteInvokeOptions { StartInfo = psi }).DisposeAsync(); + } } }