Skip to content

Revert changes to HttpClient/SslStream certificate revocation check mode #118456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 11, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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}";
Expand All @@ -56,9 +78,7 @@ public static void CleanupCertificates([CallerMemberName] string? testName = nul
}
}
}
catch
{
}
catch { };

try
{
Expand All @@ -75,9 +95,7 @@ public static void CleanupCertificates([CallerMemberName] string? testName = nul
}
}
}
catch
{
}
catch { };
}

internal static X509ExtensionCollection BuildTlsServerCertExtensions(string serverName)
Expand All @@ -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)
Expand All @@ -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,
Expand All @@ -185,15 +142,34 @@ 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;
endEntity = X509CertificateLoader.LoadPkcs12(endEntity.Export(X509ContentType.Pfx), (string?)null, X509KeyStorageFlags.Exportable);
ephemeral.Dispose();
}

return new PkiHolder(testName, root, intermediates, endEntity, responder);
return (endEntity, chain);
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void Ctor_ExpectedDefaultValues()
using (HttpClientHandler handler = CreateHttpClientHandler())
{
Assert.Null(handler.ServerCertificateCustomValidationCallback);
Assert.True(handler.CheckCertificateRevocationList);
Assert.False(handler.CheckCertificateRevocationList);
}
}

Expand Down Expand Up @@ -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);
Expand All @@ -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<HttpRequestException>(() => client.GetAsync(Configuration.Http.RevokedCertRemoteServer));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ System.Net.Http.WinHttpHandler</PackageDescription>
<Compile Include="System\Net\Http\WinHttpTraceHelper.cs" />
<Compile Include="System\Net\Http\WinHttpTrailersHelper.cs" />
<Compile Include="System\Net\Http\WinHttpTransportContext.cs" />
<Compile Include="$(CommonPath)System\AppContextSwitchHelper.cs"
Link="Common\System\AppContextSwitchHelper.cs" />
<Compile Include="$(CommonPath)System\IO\StreamHelpers.CopyValidation.cs"
Link="Common\System\IO\StreamHelpers.CopyValidation.cs" />
<Compile Include="$(CommonPath)System\Net\Logging\NetEventSource.Common.cs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ 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 CertificateCachingAppContextSwitchEnabled { get; } = AppContext.TryGetSwitch("System.Net.Http.UseWinHttpCertificateCaching", out bool enabled) && enabled;
private static readonly TimeSpan s_maxTimeout = TimeSpan.FromMilliseconds(int.MaxValue);

Expand All @@ -73,7 +68,7 @@ private Func<
X509Chain,
SslPolicyErrors,
bool>? _serverCertificateValidationCallback;
private bool _checkCertificateRevocationList = DefaultCertificateRevocationCheck;
private bool _checkCertificateRevocationList;
private ClientCertificateOption _clientCertificateOption = ClientCertificateOption.Manual;
private X509Certificate2Collection? _clientCertificates; // Only create collection when required.
private ICredentials? _serverCredentials;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
Link="Common\Interop\Windows\WinHttp\Interop.SafeWinHttpHandle.cs" />
<Compile Include="$(CommonPath)Interop\Windows\WinHttp\Interop.winhttp_types.cs"
Link="Common\Interop\Windows\WinHttp\Interop.winhttp_types.cs" />
<Compile Include="$(CommonPath)System\AppContextSwitchHelper.cs"
Link="Common\System\AppContextSwitchHelper.cs" />
<Compile Include="$(CommonPath)System\CharArrayHelpers.cs"
Link="Common\System\CharArrayHelpers.cs" />
<Compile Include="$(CommonPath)System\Obsoletions.cs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading