diff --git a/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs index f4f92c0791c8f5..1ec1835d83a4ea 100644 --- a/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading.Tasks; using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; using System.IO; using System.Net.Sockets; @@ -93,6 +94,7 @@ public class GenericLoopbackOptions { public IPAddress Address { get; set; } = IPAddress.Loopback; public bool UseSsl { get; set; } = PlatformDetection.SupportsAlpn && !Capability.Http2ForceUnencryptedLoopback(); + public X509Certificate2 Certificate { get; set; } public SslProtocols SslProtocols { get; set; } = #if !NETSTANDARD2_0 && !NETFRAMEWORK SslProtocols.Tls13 | 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 d04b1d7aee6860..973a10967a452e 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs @@ -310,7 +310,6 @@ public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails() public static readonly object[][] CertificateValidationServersAndExpectedPolicies = { new object[] { Configuration.Http.ExpiredCertRemoteServer, SslPolicyErrors.RemoteCertificateChainErrors }, - new object[] { Configuration.Http.SelfSignedCertRemoteServer, SslPolicyErrors.RemoteCertificateChainErrors }, new object[] { Configuration.Http.WrongHostNameCertRemoteServer , SslPolicyErrors.RemoteCertificateNameMismatch}, }; @@ -365,6 +364,38 @@ public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(string url, Ss } } + [Fact] + public async Task UseCallback_SelfSignedCertificate_ExpectedPolicyErrors() + { + using (HttpClientHandler handler = CreateHttpClientHandler()) + using (HttpClient client = CreateHttpClient(handler)) + { + bool callbackCalled = false; + X509Certificate2 certificate = TestHelper.CreateServerSelfSignedCertificate(); + + handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => + { + callbackCalled = true; + Assert.NotNull(request); + Assert.NotNull(cert); + Assert.NotNull(chain); + Assert.Equal(SslPolicyErrors.RemoteCertificateChainErrors, errors); + return true; + }; + + var options = new LoopbackServer.Options { UseSsl = true, Certificate = certificate }; + + await LoopbackServer.CreateServerAsync(async (server, url) => + { + await TestHelper.WhenAllCompletedOrAnyFailed( + server.AcceptConnectionSendResponseAndCloseAsync(), + client.GetAsync($"https://{certificate.GetNameInfo(X509NameType.SimpleName, false)}:{url.Port}/")); + }, options); + + Assert.True(callbackCalled); + } + } + [OuterLoop("Uses external server")] [PlatformSpecific(TestPlatforms.Windows)] // CopyToAsync(Stream, TransportContext) isn't used on unix [Fact] diff --git a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs index 47e1ec2540f819..f335a5d598cfe7 100644 --- a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs @@ -424,7 +424,7 @@ public static async Task CreateAsync(Socket socket, Stream stream, O if (httpOptions.UseSsl) { var sslStream = new SslStream(stream, false, delegate { return true; }); - using (X509Certificate2 cert = Configuration.Certificates.GetServerCertificate()) + using (X509Certificate2 cert = httpOptions.Certificate ?? Configuration.Certificates.GetServerCertificate()) { await sslStream.AuthenticateAsServerAsync( cert, diff --git a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs index 041d6efe1e705f..163fdea225976e 100644 --- a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs +++ b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs @@ -118,5 +118,45 @@ public static byte[] GenerateRandomContent(int size) new Random(42).NextBytes(data); return data; } + + public static X509Certificate2 CreateServerSelfSignedCertificate(string name = "localhost") + { + using (RSA root = RSA.Create()) + { + CertificateRequest req = new CertificateRequest( + $"CN={name}", + root, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + req.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, 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( + new X509EnhancedKeyUsageExtension( + new OidCollection() + { + new Oid("1.3.6.1.5.5.7.3.1", null), + }, false)); + + + SubjectAlternativeNameBuilder builder = new SubjectAlternativeNameBuilder(); + builder.AddDnsName(name); + builder.AddIpAddress(IPAddress.Loopback); + builder.AddIpAddress(IPAddress.IPv6Loopback); + req.CertificateExtensions.Add(builder.Build()); + + DateTimeOffset start = DateTimeOffset.UtcNow.AddMinutes(-5); + DateTimeOffset end = start.AddMonths(1); + + X509Certificate2 cert = req.CreateSelfSigned(start, end); + if (PlatformDetection.IsWindows) + { + cert = new X509Certificate2(cert.Export(X509ContentType.Pfx)); + } + + return cert; + } + } } }