Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Expand Up @@ -14,6 +14,7 @@
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.DotNet.RemoteExecutor;
Expand Down Expand Up @@ -218,19 +219,33 @@ public async Task UseCallback_CallbackThrowsException_ExceptionPropagatesAsBaseE
}
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void CreateServerCertificate_IsEndEntity(bool selfSigned)
{
using X509Certificate2 rootCertificate = CreateCertificateAuthority();
using X509Certificate2 certificate = TestHelper.CreateServerCertificate(selfSigned ? null : rootCertificate);

Assert.False(Assert.Single(certificate.Extensions.OfType<X509BasicConstraintsExtension>()).CertificateAuthority);
}

[Theory]
[InlineData(true, false)]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, true)]
public async Task NoCallback_BadCertificate_ThrowsException(bool expired, bool wrongHostName)
{
using X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate(
name: wrongHostName ? "not-localhost" : "localhost", expired: expired);
using X509Certificate2 rootCertificate = CreateCertificateAuthority();

using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false))
using (HttpClient client = CreateHttpClient(handler))
{
bool customChainPolicy = ConfigureCertificateChainPolicy(handler, certificate, trustCertificate: expired || wrongHostName);
bool customChainPolicy = ConfigureCertificateChainPolicy(handler, rootCertificate);
using X509Certificate2 certificate = TestHelper.CreateServerCertificate(
customChainPolicy && (expired || wrongHostName) ? rootCertificate : null,
name: wrongHostName ? "not-localhost" : "localhost", expired: expired);

await LoopbackServer.CreateClientAndServerAsync(async uri =>
{
Expand All @@ -250,15 +265,8 @@ await LoopbackServer.CreateClientAndServerAsync(async uri =>
{
try
{
if (customChainPolicy || IsWinHttpHandler)
{
await server.AcceptConnectionAsync(_ => Task.CompletedTask);
}
else
{
// Native handlers may report generic errors, so acceptance must produce a successful response.
await server.HandleRequestAsync();
}
// Unexpected certificate acceptance must fail the assertion, not trigger a retry against an idle listener.
await server.HandleRequestAsync();
}
catch (Exception e) when (e is AuthenticationException or IOException)
{
Expand Down Expand Up @@ -329,15 +337,18 @@ public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails()
[InlineData(true, false, SslPolicyErrors.RemoteCertificateChainErrors)]
[InlineData(false, false, SslPolicyErrors.RemoteCertificateChainErrors)]
[InlineData(false, true, SslPolicyErrors.RemoteCertificateNameMismatch)]
[InlineData(true, true, SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch)]
public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(bool expired, bool wrongHostName, SslPolicyErrors expectedErrors)
{
using X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate(
name: wrongHostName ? "not-localhost" : "localhost", expired: expired);
using X509Certificate2 rootCertificate = CreateCertificateAuthority();

using (HttpClientHandler handler = CreateHttpClientHandler(allowTlsResume: false))
using (HttpClient client = CreateHttpClient(handler))
{
bool customChainPolicy = ConfigureCertificateChainPolicy(handler, certificate, trustCertificate: expired || wrongHostName);
bool customChainPolicy = ConfigureCertificateChainPolicy(handler, rootCertificate);
using X509Certificate2 certificate = TestHelper.CreateServerCertificate(
customChainPolicy && (expired || wrongHostName) ? rootCertificate : null,
name: wrongHostName ? "not-localhost" : "localhost", expired: expired);
X509ChainStatusFlags expectedChainStatus = expired ? X509ChainStatusFlags.NotTimeValid :
wrongHostName ? X509ChainStatusFlags.NoError : X509ChainStatusFlags.UntrustedRoot;

Expand All @@ -348,6 +359,12 @@ public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(bool expired,
expectedChainStatus |= X509ChainStatusFlags.UntrustedRoot;
}

if (PlatformDetection.IsAndroid && expectedChainStatus != X509ChainStatusFlags.NoError)
{
// Android cannot build a path through an expired or untrusted certificate.
expectedChainStatus = X509ChainStatusFlags.PartialChain;
}

string expectedThumbprint = certificate.Thumbprint;
bool callbackCalled = false;

Expand Down Expand Up @@ -380,7 +397,18 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
}
}

private static bool ConfigureCertificateChainPolicy(HttpClientHandler handler, X509Certificate2 certificate, bool trustCertificate)
private static X509Certificate2 CreateCertificateAuthority()
{
using RSA key = RSA.Create();
var request = new CertificateRequest("CN=Test Certificate Authority", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign, true));

DateTimeOffset now = DateTimeOffset.UtcNow;
return request.CreateSelfSigned(now.AddYears(-1), now.AddYears(1));
}

private static bool ConfigureCertificateChainPolicy(HttpClientHandler handler, X509Certificate2 rootCertificate)
{
#if !WINHTTPHANDLER_TEST
if (GetUnderlyingSocketsHttpHandler(handler) is SocketsHttpHandler socketsHandler)
Expand All @@ -390,13 +418,10 @@ private static bool ConfigureCertificateChainPolicy(HttpClientHandler handler, X
TrustMode = X509ChainTrustMode.CustomRootTrust,
RevocationMode = X509RevocationMode.NoCheck,
DisableCertificateDownloads = true,
// Android requires a nonempty trust store and does not check a trust anchor's expiration.
CustomTrustStore = { rootCertificate },
};

if (trustCertificate)
{
policy.CustomTrustStore.Add(certificate);
}

socketsHandler.SslOptions.CertificateChainPolicy = policy;
return true;
}
Expand Down
21 changes: 17 additions & 4 deletions src/libraries/Common/tests/System/Net/Http/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,19 @@ public static byte[] GenerateRandomContent(int size)
}

public static X509Certificate2 CreateServerSelfSignedCertificate(string name = "localhost", bool expired = false)
=> CreateServerCertificate(null, name, expired);

public static X509Certificate2 CreateServerCertificate(X509Certificate2 issuerCertificate, string name = "localhost", bool expired = false)
{
using (RSA root = RSA.Create())
using (RSA key = RSA.Create())
{
CertificateRequest req = new CertificateRequest(
$"CN={name}",
root,
key,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);

req.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
req.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(req.PublicKey, false));
req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DataEncipherment, false));
req.CertificateExtensions.Add(
Expand All @@ -139,7 +142,17 @@ public static X509Certificate2 CreateServerSelfSignedCertificate(string name = "
DateTimeOffset start = expired ? DateTimeOffset.UtcNow.AddMonths(-2) : DateTimeOffset.UtcNow.AddMinutes(-5);
DateTimeOffset end = start.AddMonths(1);

X509Certificate2 cert = req.CreateSelfSigned(start, end);
X509Certificate2 cert;
if (issuerCertificate is null)
{
cert = req.CreateSelfSigned(start, end);
}
else
{
using X509Certificate2 publicCertificate = req.Create(issuerCertificate, start, end, Guid.NewGuid().ToByteArray());
cert = publicCertificate.CopyWithPrivateKey(key);
}

if (PlatformDetection.IsWindows)
{
using (cert)
Expand Down
Loading