From 9d3bf572b3ec53eb00f4066750ea931a9116bf30 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Mon, 6 Jul 2020 15:48:46 +0200 Subject: [PATCH] [HTTPS] Adds PEM support for Kestrel (#23584) * Adds support for loading PEM certificates and keys in Kestrel. * You can load PEM Certificate + PKCS8 encoded PEM Keys. * Certificates in DER format + PKCS8 encoded PEM Keys. * Supported key types are: * RSA * ECSA * DSA --- src/Servers/Kestrel/Core/src/CoreStrings.resx | 6 + .../Core/src/Internal/ConfigurationReader.cs | 2 + .../Core/src/Internal/LoggerExtensions.cs | 15 +++ .../Core/src/KestrelConfigurationLoader.cs | 118 +++++++++++++++++- ...spNetCore.Server.Kestrel.Core.Tests.csproj | 2 + .../test/KestrelConfigurationLoaderTests.cs | 118 +++++++++++++++++- ...oft.AspNetCore.Server.Kestrel.Tests.csproj | 3 + .../test/TestCertificates/.gitattributes | 2 + .../test/TestCertificates/https-aspnet.crt | Bin 0 -> 784 bytes .../test/TestCertificates/https-aspnet.key | 30 +++++ .../TestCertificates/https-dsa-protected.key | 11 ++ .../test/TestCertificates/https-dsa.crt | Bin 0 -> 861 bytes .../test/TestCertificates/https-dsa.key | 9 ++ .../test/TestCertificates/https-dsa.pem | 20 +++ .../https-ecdsa-protected.key | 7 ++ .../test/TestCertificates/https-ecdsa.crt | Bin 0 -> 522 bytes .../test/TestCertificates/https-ecdsa.key | 8 ++ .../test/TestCertificates/https-ecdsa.pem | 13 ++ .../TestCertificates/https-rsa-protected.key | 30 +++++ .../test/TestCertificates/https-rsa.crt | Bin 0 -> 784 bytes .../test/TestCertificates/https-rsa.key | 28 +++++ .../test/TestCertificates/https-rsa.pem | 19 +++ .../HttpsConnectionMiddlewareTests.cs | 39 ++++++ .../InMemory.FunctionalTests.csproj | 3 + 24 files changed, 480 insertions(+), 3 deletions(-) create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/.gitattributes create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-aspnet.crt create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-aspnet.key create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-dsa-protected.key create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-dsa.crt create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-dsa.key create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-dsa.pem create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa-protected.key create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa.crt create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa.key create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa.pem create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-rsa-protected.key create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-rsa.crt create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-rsa.key create mode 100644 src/Servers/Kestrel/shared/test/TestCertificates/https-rsa.pem diff --git a/src/Servers/Kestrel/Core/src/CoreStrings.resx b/src/Servers/Kestrel/Core/src/CoreStrings.resx index 46f2e263a3..49c2f5fb48 100644 --- a/src/Servers/Kestrel/Core/src/CoreStrings.resx +++ b/src/Servers/Kestrel/Core/src/CoreStrings.resx @@ -614,4 +614,10 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l A TimeSpan value greater than or equal to {value} is required. + + The provided key file is missing or invalid. + + + Unknown algorithm for certificate with public key type '{0}'. + \ No newline at end of file diff --git a/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs b/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs index 31e4270131..2843bb014c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs @@ -205,6 +205,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public string Path { get; set; } + public string KeyPath { get; set; } + public string Password { get; set; } // Cert store diff --git a/src/Servers/Kestrel/Core/src/Internal/LoggerExtensions.cs b/src/Servers/Kestrel/Core/src/Internal/LoggerExtensions.cs index 1fedf58ae5..a28c74ae87 100644 --- a/src/Servers/Kestrel/Core/src/Internal/LoggerExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Internal/LoggerExtensions.cs @@ -46,6 +46,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal new EventId(5, "DeveloperCertificateFirstRun"), "{Message}"); + private static readonly Action _failedToLoadCertificate = + LoggerMessage.Define( + LogLevel.Error, + new EventId(6, "MissingOrInvalidCertificateFile"), + "The certificate file at '{CertificateFilePath}' can not be found, contains malformed data or does not contain a certificate."); + + private static readonly Action _failedToLoadCertificateKey = + LoggerMessage.Define( + LogLevel.Error, + new EventId(7, "MissingOrInvalidCertificateKeyFile"), + "The certificate key file at '{CertificateKeyFilePath}' can not be found, contains malformed data or does not contain a PEM encoded key in PKCS8 format."); + public static void LocatedDevelopmentCertificate(this ILogger logger, X509Certificate2 certificate) => _locatedDevelopmentCertificate(logger, certificate.Subject, certificate.Thumbprint, null); public static void UnableToLocateDevelopmentCertificate(this ILogger logger) => _unableToLocateDevelopmentCertificate(logger, null); @@ -57,5 +69,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public static void BadDeveloperCertificateState(this ILogger logger) => _badDeveloperCertificateState(logger, null); public static void DeveloperCertificateFirstRun(this ILogger logger, string message) => _developerCertificateFirstRun(logger, message, null); + + public static void FailedToLoadCertificate(this ILogger logger, string certificatePath) => _failedToLoadCertificate(logger, certificatePath, null); + public static void FailedToLoadCertificateKey(this ILogger logger, string certificateKeyPath) => _failedToLoadCertificateKey(logger, certificateKeyPath, null); } } diff --git a/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs b/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs index ad7210b6d6..04349031e2 100644 --- a/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs +++ b/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; +using System.Runtime.InteropServices; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; @@ -429,20 +430,133 @@ namespace Microsoft.AspNetCore.Server.Kestrel private X509Certificate2 LoadCertificate(CertificateConfig certInfo, string endpointName) { + var logger = Options.ApplicationServices.GetRequiredService>(); if (certInfo.IsFileCert && certInfo.IsStoreCert) { throw new InvalidOperationException(CoreStrings.FormatMultipleCertificateSources(endpointName)); } else if (certInfo.IsFileCert) { - var env = Options.ApplicationServices.GetRequiredService(); - return new X509Certificate2(Path.Combine(env.ContentRootPath, certInfo.Path), certInfo.Password); + var environment = Options.ApplicationServices.GetRequiredService(); + var certificatePath = Path.Combine(environment.ContentRootPath, certInfo.Path); + if (certInfo.KeyPath != null) + { + var certificateKeyPath = Path.Combine(environment.ContentRootPath, certInfo.KeyPath); + var certificate = GetCertificate(certificatePath); + + if (certificate != null) + { + certificate = LoadCertificateKey(certificate, certificateKeyPath, certInfo.Password); + } + else + { + logger.FailedToLoadCertificate(certificateKeyPath); + } + + if (certificate != null) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return PersistKey(certificate); + } + + return certificate; + } + else + { + logger.FailedToLoadCertificateKey(certificateKeyPath); + } + + throw new InvalidOperationException(CoreStrings.InvalidPemKey); + } + + return new X509Certificate2(Path.Combine(environment.ContentRootPath, certInfo.Path), certInfo.Password); } else if (certInfo.IsStoreCert) { return LoadFromStoreCert(certInfo); } return null; + + static X509Certificate2 PersistKey(X509Certificate2 fullCertificate) + { + // We need to force the key to be persisted. + // See https://github.com/dotnet/runtime/issues/23749 + var certificateBytes = fullCertificate.Export(X509ContentType.Pkcs12, ""); + return new X509Certificate2(certificateBytes, "", X509KeyStorageFlags.DefaultKeySet); + } + + static X509Certificate2 LoadCertificateKey(X509Certificate2 certificate, string keyPath, string password) + { + // OIDs for the certificate key types. + const string RSAOid = "1.2.840.113549.1.1.1"; + const string DSAOid = "1.2.840.10040.4.1"; + const string ECDsaOid = "1.2.840.10045.2.1"; + + var keyText = File.ReadAllText(keyPath); + return certificate.PublicKey.Oid.Value switch + { + RSAOid => AttachPemRSAKey(certificate, keyText, password), + ECDsaOid => AttachPemECDSAKey(certificate, keyText, password), + DSAOid => AttachPemDSAKey(certificate, keyText, password), + _ => throw new InvalidOperationException(string.Format(CoreStrings.UnrecognizedCertificateKeyOid, certificate.PublicKey.Oid.Value)) + }; + } + + static X509Certificate2 GetCertificate(string certificatePath) + { + if (X509Certificate2.GetCertContentType(certificatePath) == X509ContentType.Cert) + { + return new X509Certificate2(certificatePath); + } + + return null; + } + } + + private static X509Certificate2 AttachPemRSAKey(X509Certificate2 certificate, string keyText, string password) + { + using var rsa = RSA.Create(); + if (password == null) + { + rsa.ImportFromPem(keyText); + } + else + { + rsa.ImportFromEncryptedPem(keyText, password); + } + + return certificate.CopyWithPrivateKey(rsa); + } + + private static X509Certificate2 AttachPemDSAKey(X509Certificate2 certificate, string keyText, string password) + { + using var dsa = DSA.Create(); + if (password == null) + { + dsa.ImportFromPem(keyText); + } + else + { + dsa.ImportFromEncryptedPem(keyText, password); + } + + return certificate.CopyWithPrivateKey(dsa); + } + + private static X509Certificate2 AttachPemECDSAKey(X509Certificate2 certificate, string keyText, string password) + { + using var ecdsa = ECDsa.Create(); + if (password == null) + { + ecdsa.ImportFromPem(keyText); + } + else + { + ecdsa.ImportFromEncryptedPem(keyText, password); + } + + return certificate.CopyWithPrivateKey(ecdsa); } private static X509Certificate2 LoadFromStoreCert(CertificateConfig certInfo) diff --git a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj index 108edc514d..8786fa6969 100644 --- a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj +++ b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj @@ -13,6 +13,8 @@ + + diff --git a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs index fb0b182cdd..5053b3cffb 100644 --- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Authentication; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using Microsoft.AspNetCore.Hosting; @@ -25,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests private KestrelServerOptions CreateServerOptions() { var serverOptions = new KestrelServerOptions(); - var env = new MockHostingEnvironment { ApplicationName = "TestApplication" }; + var env = new MockHostingEnvironment { ApplicationName = "TestApplication", ContentRootPath = Directory.GetCurrentDirectory() }; serverOptions.ApplicationServices = new ServiceCollection() .AddLogging() .AddSingleton(env) @@ -254,6 +255,121 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests } } + [Fact] + public void ConfigureEndpoint_ThrowsWhen_The_PasswordIsMissing() + { + var serverOptions = CreateServerOptions(); + var certificate = new X509Certificate2(TestResources.GetCertPath("https-aspnet.crt")); + + var config = new ConfigurationBuilder().AddInMemoryCollection(new[] + { + new KeyValuePair("Endpoints:End1:Url", "https://*:5001"), + new KeyValuePair("Certificates:Default:Path", Path.Combine("shared", "TestCertificates", "https-aspnet.crt")), + new KeyValuePair("Certificates:Default:KeyPath", Path.Combine("shared", "TestCertificates", "https-aspnet.key")) + }).Build(); + + var ex = Assert.Throws(() => + { + serverOptions + .Configure(config) + .Endpoint("End1", opt => + { + Assert.True(opt.IsHttps); + }).Load(); + }); + } + + [Fact] + public void ConfigureEndpoint_ThrowsWhen_TheKeyDoesntMatchTheCertificateKey() + { + var serverOptions = CreateServerOptions(); + var certificate = new X509Certificate2(TestResources.GetCertPath("https-aspnet.crt")); + + var config = new ConfigurationBuilder().AddInMemoryCollection(new[] + { + new KeyValuePair("Endpoints:End1:Url", "https://*:5001"), + new KeyValuePair("Certificates:Default:Path", Path.Combine("shared", "TestCertificates", "https-aspnet.crt")), + new KeyValuePair("Certificates:Default:KeyPath", Path.Combine("shared", "TestCertificates", "https-ecdsa.key")), + new KeyValuePair("Certificates:Default:Password", "aspnetcore") + }).Build(); + + var ex = Assert.Throws(() => + { + serverOptions + .Configure(config) + .Endpoint("End1", opt => + { + Assert.True(opt.IsHttps); + }).Load(); + }); + } + + [Fact] + public void ConfigureEndpoint_ThrowsWhen_The_PasswordIsIncorrect() + { + var serverOptions = CreateServerOptions(); + var certificate = new X509Certificate2(TestResources.GetCertPath("https-aspnet.crt")); + + var config = new ConfigurationBuilder().AddInMemoryCollection(new[] + { + new KeyValuePair("Endpoints:End1:Url", "https://*:5001"), + new KeyValuePair("Certificates:Default:Path", Path.Combine("shared", "TestCertificates", "https-aspnet.crt")), + new KeyValuePair("Certificates:Default:KeyPath", Path.Combine("shared", "TestCertificates", "https-aspnet.key")), + new KeyValuePair("Certificates:Default:Password", "abcde"), + }).Build(); + + var ex = Assert.Throws(() => + { + serverOptions + .Configure(config) + .Endpoint("End1", opt => + { + Assert.True(opt.IsHttps); + }).Load(); + }); + } + + [Theory] + [InlineData("https-rsa.pem", "https-rsa.key", null)] + [InlineData("https-rsa.pem", "https-rsa-protected.key", "aspnetcore")] + [InlineData("https-rsa.crt", "https-rsa.key", null)] + [InlineData("https-rsa.crt", "https-rsa-protected.key", "aspnetcore")] + [InlineData("https-ecdsa.pem", "https-ecdsa.key", null)] + [InlineData("https-ecdsa.pem", "https-ecdsa-protected.key", "aspnetcore")] + [InlineData("https-ecdsa.crt", "https-ecdsa.key", null)] + [InlineData("https-ecdsa.crt", "https-ecdsa-protected.key", "aspnetcore")] + [InlineData("https-dsa.pem", "https-dsa.key", null)] + [InlineData("https-dsa.pem", "https-dsa-protected.key", "test")] + [InlineData("https-dsa.crt", "https-dsa.key", null)] + [InlineData("https-dsa.crt", "https-dsa-protected.key", "test")] + public void ConfigureEndpoint_CanLoadPemCertificates(string certificateFile, string certificateKey, string password) + { + var serverOptions = CreateServerOptions(); + var certificate = new X509Certificate2(TestResources.GetCertPath(Path.ChangeExtension(certificateFile, "crt"))); + + var ran1 = false; + var config = new ConfigurationBuilder().AddInMemoryCollection(new[] + { + new KeyValuePair("Endpoints:End1:Url", "https://*:5001"), + new KeyValuePair("Certificates:Default:Path", Path.Combine("shared", "TestCertificates", certificateFile)), + new KeyValuePair("Certificates:Default:KeyPath", Path.Combine("shared", "TestCertificates", certificateKey)), + } + .Concat(password != null ? new[] { new KeyValuePair("Certificates:Default:Password", password) } : Array.Empty>())) + .Build(); + + serverOptions + .Configure(config) + .Endpoint("End1", opt => + { + ran1 = true; + Assert.True(opt.IsHttps); + Assert.Equal(opt.HttpsOptions.ServerCertificate.SerialNumber, certificate.SerialNumber); + }).Load(); + + Assert.True(ran1); + Assert.NotNull(serverOptions.DefaultCertificate); + } + [Fact] public void ConfigureEndpointDevelopmentCertificateGetsIgnoredIfPasswordIsNotCorrect() { diff --git a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj index 571f2cda30..f4106e1040 100644 --- a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj +++ b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj @@ -8,6 +8,9 @@ + + + diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/.gitattributes b/src/Servers/Kestrel/shared/test/TestCertificates/.gitattributes new file mode 100644 index 0000000000..911dbdca5c --- /dev/null +++ b/src/Servers/Kestrel/shared/test/TestCertificates/.gitattributes @@ -0,0 +1,2 @@ +*.key binary +*.pem binary diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-aspnet.crt b/src/Servers/Kestrel/shared/test/TestCertificates/https-aspnet.crt new file mode 100644 index 0000000000000000000000000000000000000000..0421f377734d035790e8cd737bdfbad614e72667 GIT binary patch literal 784 zcmXqLV&*YuV*IjznTe5!iNpJ`;q5=d21g8d**LY@JlekVGBR?rG8l*$3Kq1NMvk$A^D^^IA)Wq<$ntc*Wl4v@rY+C=FuVlmf z%bc?qF1|2rys>S6*u3_4@+|^q`j_`??GP?=nSQS>vDoHP<@q1`6dLTT=Dlq$pLCJ+ zl&k&16s{Ytzm)3~on=F6mTr~^wCz3I|Hh^8@H0lYe9jYLu^DWKj&2FoU=`5nZp~h~ z-A$+b)G0}kkFtxb&;JX$dR}y@hsr~~qwZ3>l0kw24=}`Kg&7(DvoIMj81R93{2(3+GZX6q12GU^ z6~yN;;9}#@W@BV!WoKqI5C@41g2Z_ZxSNooi=T}PsECEJ$vlJ+NHHSE0x*(+vB1cX zV(-cPey-AkleJ|DGGop$EPvMVonoxfNv zH=Vx7Ci4&1HQi~in*=N#-EVl9lo_q4DX?hWvkCupe%w`Bx5AF+_es&0la&hAMSi<} zJ6VEZ*{!Lk8;$ozvd3*)wrbbE^*0VmKQoFs65jRn2=CvOH^f#XtNl*An)OALN7}kQ zdwZnw&$VCHFHP3}&7u_4b>qaYPa7|sefX~V-L5-Hm$&oIPS-xL;Ed#ADT{EP?bQ>j P%Xzmis+z!lw__my11T}7 literal 0 HcmV?d00001 diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-aspnet.key b/src/Servers/Kestrel/shared/test/TestCertificates/https-aspnet.key new file mode 100644 index 0000000000..34fba0f776 --- /dev/null +++ b/src/Servers/Kestrel/shared/test/TestCertificates/https-aspnet.key @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNjBgBgkqhkiG9w0BBQ0wUzAyBgkqhkiG9w0BBQwwJQQQ93oRxzJ5UoNOb/zN +x5cdsAIDAYagMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBAuHsE18X/Z9ZVe +aBl7C55nBIIE0AABqjc9ERcLYNpCRpA6c/TFG62m4Mr9J4dU4g1WD07t7uLxZiRi +Pl0YOjCulljMsevAW5PlLxi4ffJ+I0/UB1WOfzEMhcj7o1qG0Uv55B7WRuWKw1Zr +jo0bDY5Man48ZjpqMMBdnWhyHdIDm+WD0OyN98mpsN6SCQMjvx91M+klrsp7LOMM +88HHS0RFVKGF9hYSy6rCwMJWf+7QGO2wXfq+MKvJ/bBgPGDLwN4phUCyocnR0swD +/XZNiiw0xIC8OxAKhc6BV4AJkjNs32THdBOCGY6B4P/9Zo5W29S3ja/hGsMQAA27 +QtIDg74HpX7TgIyqoc1oiLNIWW/+jUHSEYJsTPlg5VYWsXUfSHZpz8EJvKt2tyvt +vBGOCLDDZD4GVXhPigKG6zJSJeTe94/VlwPhNSEucKeaALdax5t3HvPNzWKFX57E +aC82/IxRrjgHmgsGSZdMi08HY6K9GAVBFpIGvXOGtRq7w8zO/KagAvSwAOLLtOs7 +iEuAQxD+cKLRT59c4E7r5W7BT+faq85ovqdXe5Edtl3cT81zsl27pZvQrcrTPbZe +4OeIdWxOmOnC/bXvRHNd9XuYadXXazBoFbe9yPwjqnflEh39CyvlOZXeaQXSdsEM +1IBhddRTorO/I8M/znu9glqIa5ya1NA+4ujmf4OnJLtsrlKQa65VPVTrFdeYuMr0 +VfOuuIye2OdyJ6jS0a1PYQm4bEEz6UR88dnmnhDx6i8/l2wW5+CArA/x8IBYboBM +NJpJY9bHpic1AhjnjnTtFz2s4uYPi5g9peBizarZn+6OJvgYqs4a8SI92dA3E2o4 +a/1j7xlLlgXnVRLBMibxqzjMt4Zt7Nj+BaN1owrB/q04AWS2M4TSQz+NYOZwNFxB +dzb+fysTLK5XNEYq6rSg+0i+EKZl8Jb/t4d8SLPVr/tdfDt9BtZ0nTgjvy1HWy1p +kQdm13XfK1/9KsePH/Jb6dvN/u6ubV+ZqI7Bc7VyTi0bKMdpH2K8/dtopNyDZ/P+ +/IsyyDYTorgJB/klSih/W0hqpSBbEAmlSBfBxP1/ozBEGR2oF20JOCFyD6UXQR/1 +V7r2KtplpyfXaIWh4fABitAMHz7VgmEIQ2H9cB4Ey9jdRPQ/1p+OgGjfaFJQ0uYM +987TDtjkuukJYnPZNIIx0Yv3iAX16XmhzJixWSMUIJiWfSiz0aTjBxsPQVPTQV+M +6BgFf3riBApZYlVVJsGIie2XTvu/tHRhfQrxccl63HN7yAeJheQnoscin6Z5TKN/ +U8Ouy/QGiATatKUEUjr4lN+BYySf8F6e3cAAeAx/ZnFvGw5z8fwNYBjVWg/83bTw +9rS+tSk8VsvTdkcKoNbbDtw+SwYfZSbMUBFm0B13190iJZoyWI+5ZKPnZ2CvOZhX +PjGTOnh6Diq907l2Q7S/v8SLe0bCHCHVBy+CcPWVDZ6Z7V5cJ/W8TvFPcSGw1UCl +tKPp862uDaPKvGxqGDq0vGouEUrtJKZ279Lnrtz1n8raUj0Gxa+KXqLACh8dXCzK +ZgCTPhfAjZcYgA73edW0whNNH9MNInDGulT/arCK3HTkFPczD+7wA8Ojw/LxKFJs +0d8vtILbmLv46CO+wvIdWrW1c7PCrGJDf9Zuw06vIH7hpW9swSM55k9/ +-----END ENCRYPTED PRIVATE KEY----- \ No newline at end of file diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-dsa-protected.key b/src/Servers/Kestrel/shared/test/TestCertificates/https-dsa-protected.key new file mode 100644 index 0000000000..196ea3217f --- /dev/null +++ b/src/Servers/Kestrel/shared/test/TestCertificates/https-dsa-protected.key @@ -0,0 +1,11 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIBoTBLBgkqhkiG9w0BBQ0wPjApBgkqhkiG9w0BBQwwHAQI+PhdT1Kk/SkCAggA +MAwGCCqGSIb3DQIJBQAwEQYFKw4DAgcECGV1ZmaiQtz2BIIBUA/6pNqTkXpkOLlI +22Lh0cm5+/foDRh3qTrAOSHHHV0Dz1xYvYMa9MFzONatLf55Rpb2ZPji3hXwUQfn +gOJeTBRTaMNz5LaKJiOIWj0qDckhgKt9cmgiBzVTvXO4pERp1uz5zcvaUOKj2TSv +ljxishj76MYQftIGMMkJQKf4OsHubCopuKUbzTPgJt0FuF4eT37+tiEMgbYrmA6p +REPE0vT1aY+LYdJLV/Dax/l4lMvYmQYOWs9TCLPlI5RZQxxte6zbcA13ESg/qLE3 +4Mx8xgXrPvCxp3h8KBKNMaJR1xzpr7UQOpkI9qja++3cJAl6O/0mdeqZct0V9Z8P +a3+wyUWo58z5sOPNdJHIMV6qw6m3w+IQoCJC7EbV0+Pyo5eSU5zbgm7YWZ9Yx6l8 +g1mCP4Q6Tqe6LjKfBsZAmYWSfKqoTKRjC3ocJMt53tIDpB5jFw== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-dsa.crt b/src/Servers/Kestrel/shared/test/TestCertificates/https-dsa.crt new file mode 100644 index 0000000000000000000000000000000000000000..2f087db08aa35ad61ed028cd16d1c4273304f97c GIT binary patch literal 861 zcmXqLVvaOuVisM%%*4pVBqA!p#(bAeM(;<#rTr~;F7WAHR|zxVX5&m~^I%M6W?^PB zh&ALk;ACSCWnmL$3Jo?CG~fquICwb1GKHxk8>6*j6jYYQ)45;oJFU!zV zPwINnB%K%YHspm)`J)MeJx|meHk@Cnyt>27GVI|lkLmWyrr$h%<+azISL}HUHBHqz z7kAC(idX#evU~4_rekpiuS4EUu@kk3msZ&mF1YYTuHt(cvlsGz8TJGo&MUSKH~Km$ z=YpGg+riIFq747rGmD~SCLh}8<=W_b_Vu|CjSUq#|EBEQeWFXz|CppTgJX}; zUY^!N_>RuGutgLYG<8gJDz==IlMGRBp zP9NX@ye)#$_t z!6Nl%*2%F~*~^_gXOuRaDEB>=5x%xyx601Pk9O=nazOM>nj7B)^`zgYFYK6pCT7K~ z#lZ%F2C~2eCCkSm#v&4%`f%3KI~Tq@(KuHA|K5IgPaTD719_0NGK++PSOaziprkA- z%*gnkh1Gx=NTH;BW?;Nc3+&rQ`0E=ep&HB>Z^2MKcVNOw@3pPQ3=WpS`Upn)vVud;kBVk{!> zmVfWeeiQNJg|}Ud{{JY)D;~VI;Lws)W|1%uYrw96AEaNHk?}tZs{u2RLXHt;cLoC& zCIul2mY18lR#?q%Fjf;{pSU$yJ-__KM3Lj=;_iBF56&!RQqbwVUbM?aqFhJCmg~-% XJsSfp8T`yY9KJ0f?sD(r`kQV5s_CPh literal 0 HcmV?d00001 diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa.key b/src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa.key new file mode 100644 index 0000000000..e6ca224e8a --- /dev/null +++ b/src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa.key @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIHteKGH6xLqUHhvsgtz9jIBuj+YCwAPHRg2C47rzX0L6oAoGCCqGSM49 +AwEHoUQDQgAES8Hf3hm1ygXcZ+4NLuNgrlY9mmyiQTA4bq+aW4s56IrorHy1Se0j +WtOOngaYvYA7qvVV778h8DTKJcefzpxt1A== +-----END EC PRIVATE KEY----- diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa.pem b/src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa.pem new file mode 100644 index 0000000000..73066f6b49 --- /dev/null +++ b/src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICBjCCAa2gAwIBAgIUUHh7FUSJoSgSmhI5lnio2/R++gUwCgYIKoZIzj0EAwIw +WTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu +dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIw +MDcwMjEyMTIzOFoXDTIxMDcwMjEyMTIzOFowWTELMAkGA1UEBhMCQVUxEzARBgNV +BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 +ZDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +S8Hf3hm1ygXcZ+4NLuNgrlY9mmyiQTA4bq+aW4s56IrorHy1Se0jWtOOngaYvYA7 +qvVV778h8DTKJcefzpxt1KNTMFEwHQYDVR0OBBYEFO6n94lr7Fjk6Es+XC//WkHU +SA09MB8GA1UdIwQYMBaAFO6n94lr7Fjk6Es+XC//WkHUSA09MA8GA1UdEwEB/wQF +MAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgEjgE6bKKqDqfgDMmEgeRtWMnb3fIkRTH +dxdHLobgzKMCICyJ13K6RBh3LCQ9CtysvLFROQBON/DD2xgXRN7xr9lG +-----END CERTIFICATE----- diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-rsa-protected.key b/src/Servers/Kestrel/shared/test/TestCertificates/https-rsa-protected.key new file mode 100644 index 0000000000..34fba0f776 --- /dev/null +++ b/src/Servers/Kestrel/shared/test/TestCertificates/https-rsa-protected.key @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNjBgBgkqhkiG9w0BBQ0wUzAyBgkqhkiG9w0BBQwwJQQQ93oRxzJ5UoNOb/zN +x5cdsAIDAYagMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBAuHsE18X/Z9ZVe +aBl7C55nBIIE0AABqjc9ERcLYNpCRpA6c/TFG62m4Mr9J4dU4g1WD07t7uLxZiRi +Pl0YOjCulljMsevAW5PlLxi4ffJ+I0/UB1WOfzEMhcj7o1qG0Uv55B7WRuWKw1Zr +jo0bDY5Man48ZjpqMMBdnWhyHdIDm+WD0OyN98mpsN6SCQMjvx91M+klrsp7LOMM +88HHS0RFVKGF9hYSy6rCwMJWf+7QGO2wXfq+MKvJ/bBgPGDLwN4phUCyocnR0swD +/XZNiiw0xIC8OxAKhc6BV4AJkjNs32THdBOCGY6B4P/9Zo5W29S3ja/hGsMQAA27 +QtIDg74HpX7TgIyqoc1oiLNIWW/+jUHSEYJsTPlg5VYWsXUfSHZpz8EJvKt2tyvt +vBGOCLDDZD4GVXhPigKG6zJSJeTe94/VlwPhNSEucKeaALdax5t3HvPNzWKFX57E +aC82/IxRrjgHmgsGSZdMi08HY6K9GAVBFpIGvXOGtRq7w8zO/KagAvSwAOLLtOs7 +iEuAQxD+cKLRT59c4E7r5W7BT+faq85ovqdXe5Edtl3cT81zsl27pZvQrcrTPbZe +4OeIdWxOmOnC/bXvRHNd9XuYadXXazBoFbe9yPwjqnflEh39CyvlOZXeaQXSdsEM +1IBhddRTorO/I8M/znu9glqIa5ya1NA+4ujmf4OnJLtsrlKQa65VPVTrFdeYuMr0 +VfOuuIye2OdyJ6jS0a1PYQm4bEEz6UR88dnmnhDx6i8/l2wW5+CArA/x8IBYboBM +NJpJY9bHpic1AhjnjnTtFz2s4uYPi5g9peBizarZn+6OJvgYqs4a8SI92dA3E2o4 +a/1j7xlLlgXnVRLBMibxqzjMt4Zt7Nj+BaN1owrB/q04AWS2M4TSQz+NYOZwNFxB +dzb+fysTLK5XNEYq6rSg+0i+EKZl8Jb/t4d8SLPVr/tdfDt9BtZ0nTgjvy1HWy1p +kQdm13XfK1/9KsePH/Jb6dvN/u6ubV+ZqI7Bc7VyTi0bKMdpH2K8/dtopNyDZ/P+ +/IsyyDYTorgJB/klSih/W0hqpSBbEAmlSBfBxP1/ozBEGR2oF20JOCFyD6UXQR/1 +V7r2KtplpyfXaIWh4fABitAMHz7VgmEIQ2H9cB4Ey9jdRPQ/1p+OgGjfaFJQ0uYM +987TDtjkuukJYnPZNIIx0Yv3iAX16XmhzJixWSMUIJiWfSiz0aTjBxsPQVPTQV+M +6BgFf3riBApZYlVVJsGIie2XTvu/tHRhfQrxccl63HN7yAeJheQnoscin6Z5TKN/ +U8Ouy/QGiATatKUEUjr4lN+BYySf8F6e3cAAeAx/ZnFvGw5z8fwNYBjVWg/83bTw +9rS+tSk8VsvTdkcKoNbbDtw+SwYfZSbMUBFm0B13190iJZoyWI+5ZKPnZ2CvOZhX +PjGTOnh6Diq907l2Q7S/v8SLe0bCHCHVBy+CcPWVDZ6Z7V5cJ/W8TvFPcSGw1UCl +tKPp862uDaPKvGxqGDq0vGouEUrtJKZ279Lnrtz1n8raUj0Gxa+KXqLACh8dXCzK +ZgCTPhfAjZcYgA73edW0whNNH9MNInDGulT/arCK3HTkFPczD+7wA8Ojw/LxKFJs +0d8vtILbmLv46CO+wvIdWrW1c7PCrGJDf9Zuw06vIH7hpW9swSM55k9/ +-----END ENCRYPTED PRIVATE KEY----- \ No newline at end of file diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-rsa.crt b/src/Servers/Kestrel/shared/test/TestCertificates/https-rsa.crt new file mode 100644 index 0000000000000000000000000000000000000000..0421f377734d035790e8cd737bdfbad614e72667 GIT binary patch literal 784 zcmXqLV&*YuV*IjznTe5!iNpJ`;q5=d21g8d**LY@JlekVGBR?rG8l*$3Kq1NMvk$A^D^^IA)Wq<$ntc*Wl4v@rY+C=FuVlmf z%bc?qF1|2rys>S6*u3_4@+|^q`j_`??GP?=nSQS>vDoHP<@q1`6dLTT=Dlq$pLCJ+ zl&k&16s{Ytzm)3~on=F6mTr~^wCz3I|Hh^8@H0lYe9jYLu^DWKj&2FoU=`5nZp~h~ z-A$+b)G0}kkFtxb&;JX$dR}y@hsr~~qwZ3>l0kw24=}`Kg&7(DvoIMj81R93{2(3+GZX6q12GU^ z6~yN;;9}#@W@BV!WoKqI5C@41g2Z_ZxSNooi=T}PsECEJ$vlJ+NHHSE0x*(+vB1cX zV(-cPey-AkleJ|DGGop$EPvMVonoxfNv zH=Vx7Ci4&1HQi~in*=N#-EVl9lo_q4DX?hWvkCupe%w`Bx5AF+_es&0la&hAMSi<} zJ6VEZ*{!Lk8;$ozvd3*)wrbbE^*0VmKQoFs65jRn2=CvOH^f#XtNl*An)OALN7}kQ zdwZnw&$VCHFHP3}&7u_4b>qaYPa7|sefX~V-L5-Hm$&oIPS-xL;Ed#ADT{EP?bQ>j P%Xzmis+z!lw__my11T}7 literal 0 HcmV?d00001 diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-rsa.key b/src/Servers/Kestrel/shared/test/TestCertificates/https-rsa.key new file mode 100644 index 0000000000..3462b060b3 --- /dev/null +++ b/src/Servers/Kestrel/shared/test/TestCertificates/https-rsa.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwK6M1tIuVqrKn +JDT/uBvEz+dMR5dExiWh9Df0aHVL6ZIQs2upwiqoViJSduE3rvSdjZMpvOzE1YUO +Mh8dAsHrcTkXzeF77yF4GnR8XA6FNvIWYhiCxSM1f/cOdLDv0wmaANHoNYHYtr9W +nofuH4QQzI+njLWIE3ZEl95+YXM80nnP+L4ggD46nu2Dd5LRBcpFP6FkCthF+iN+ +IUMdVHylsxhRPY3Dj+xEccPmAUZvCchWXWgGwsW0UygFECqLhWupt0Ysd8rKGRTx +HaI7z/5S1c8VlUgk4Q7FRxq7HiiNdbkrViJkES3ghJ2LbKYMwkGXmfbnam1qRagO +BWnYnnZpAgMBAAECggEBAKlBs7Pke3tXHf/RnI3XX+6OZMX3vlDYIs3f6maKea9u +f+RFzXmyz/MdliouhyFNmT1KCQq/tadDEWvbIeNog9Fl3ZmON0YwMLLIkAPvGhBJ +AvwYUT5KkxJSmJWt7VTtKDtq8EEuL0t8AIcDFsvkQak2MAqk+L/9GtK6Koy3qdTT +Nx0to6jcSvAz6cBC/WY4u2fmGJb6RoQR0K7KWdovkhb60/5PF+tI5afDF4zO6UzD +c7qxb9/rPqhsqomC1isS+MdRWl6edW28RYzhZNxtux0KKy+HYfbrlya41HIYOdCN +h9IEc46tOuEZ4aARHq0eNkLr8oBARcjHUlfPnBpmDGUCgYEA1DYAd45N13DTAITd +MIwiKLHDH5XLmKdCGPk/LNd0asyw7Yw0ffJ2LUtCKt1HyvVKaRsCjzmC7pN1d/JG +Sni+zDgdip9f9vERVF/F/sA9h+zH+Qwx5DUDhu6a4naey3t3t2hyRSbH+e+M7+1b +4/i0nlsD35/lmwwfM7zgSZSmCIcCgYEA1IXOATUURWPilI9HoHt7yhGEvgXvquXC +KF4K+1XNnC7AXO2jwz62xg6rnkFBxiPtvnN+fCVajMusyxCwarc/QyuctcQEm8jK ++vOI1dJM4Qgy+MNzcat7MjJCBpi6oFXAKDu3CGzfUw0SKNepc9cST8x9FsMgcC8K +OKbLWLK2dY8CgYAIgMlwAPG5ijnKMYizY0oTG1xYLaZkzX7mhUY0w8VUajNEsXOB +AHAfzH4wPYGc7ks2/vARURqf+KSiU8DhRwlOIYl9fnlX6bzqBpRmasmMYr54ijaN +kFo909286Uffm2jmnnbFspIcv66EBpzB+7sxBTCYi02l8sxlRFIwYJZujQKBgAO5 +2NPCl3lj9+v82xegMppnVjlypzIK1y2YAH9JkNJFK5A1hmJ87f1o8m9S25FavedR +5QzOJtlDFON2hnFIhy5pTFUPe7kzewONU3/UMQ7c8u/TlWmPxRgrM2ckNFltR3It +Idde+UdeekwHA+yI/8QwZJ0KjL4KxRYbLoN+lp5XAoGAbVNfX57Hjc/TxrC7nTyQ +0Mqi8S1t7Yo/Je/5Ow/8W3zVrzMYTipwJyBrAMhhhOTuvWc6lnJFW6XkyftQPwI6 +RDD3i9DELPhIPhh4kz7ID5OPtRnf3hrvXDOyucSV66RpxSSb5l7i92a8wTFWeGlN +nLbTYpaPuX1fCIbRnigXI3A= +-----END PRIVATE KEY----- diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-rsa.pem b/src/Servers/Kestrel/shared/test/TestCertificates/https-rsa.pem new file mode 100644 index 0000000000..2a2d0312f3 --- /dev/null +++ b/src/Servers/Kestrel/shared/test/TestCertificates/https-rsa.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIIS+Mx2/wTMMQwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UE +AxMJbG9jYWxob3N0MB4XDTIwMDcwMTE5MjcwOVoXDTIxMDcwMTE5MjcwOVowFDES +MBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAsCujNbSLlaqypyQ0/7gbxM/nTEeXRMYlofQ39Gh1S+mSELNrqcIqqFYiUnbh +N670nY2TKbzsxNWFDjIfHQLB63E5F83he+8heBp0fFwOhTbyFmIYgsUjNX/3DnSw +79MJmgDR6DWB2La/Vp6H7h+EEMyPp4y1iBN2RJfefmFzPNJ5z/i+IIA+Op7tg3eS +0QXKRT+hZArYRfojfiFDHVR8pbMYUT2Nw4/sRHHD5gFGbwnIVl1oBsLFtFMoBRAq +i4VrqbdGLHfKyhkU8R2iO8/+UtXPFZVIJOEOxUcaux4ojXW5K1YiZBEt4ISdi2ym +DMJBl5n252ptakWoDgVp2J52aQIDAQABo2IwYDAMBgNVHRMBAf8EAjAAMA4GA1Ud +DwEB/wQEAwIFoDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDATAXBgNVHREBAf8EDTAL +gglsb2NhbGhvc3QwDwYKKwYBBAGCN1QBAQQBATANBgkqhkiG9w0BAQsFAAOCAQEA +ZD9JA++dIuBke7GYYlJuKTMHJB0Sm1Ug2idNi1JiocXYsCVzY05sd4Qh+34PcED7 +B6592o1h47bgOh1ISolrOkt/23VjJweWGsa9rqt1zMdmmCulmPPFOiWWzMSm1OkN +Q/Q5pzWXojxp/ArWLZbrghA44t+A4WJpWyEpEKKu5pD+ufG6dX6oPgz7yRXpkyJw +rln219tjGACm2pXLgTO/WQdesaaquv6v2MEb5jJcxFeK5cQN/anYFqhjJvth1Wr0 +FQwbO4drt1lD+a30r6VjL/sEIlKK2Mi68rHQzeHug+663GLTtw2bZyvAoMwZoxo4 +Vwy3e5F7dw23onqQB92IoQ== +-----END CERTIFICATE----- diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs index fe0b8dae14..70b216e149 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsConnectionMiddlewareTests.cs @@ -21,7 +21,11 @@ using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport; using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging.Testing; +using Moq; using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests @@ -51,6 +55,41 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests } } + [Fact] + public async Task CanReadAndWriteWithHttpsConnectionMiddlewareWithPemCertificate() + { + var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary + { + ["Certificates:Default:Path"] = Path.Combine("shared", "TestCertificates", "https-aspnet.crt"), + ["Certificates:Default:KeyPath"] = Path.Combine("shared", "TestCertificates", "https-aspnet.key"), + ["Certificates:Default:Password"] = "aspnetcore", + }).Build(); + + var options = new KestrelServerOptions(); + var env = new Mock(); + env.SetupGet(e => e.ContentRootPath).Returns(Directory.GetCurrentDirectory()); + + options.ApplicationServices = new ServiceCollection().AddSingleton(env.Object).AddLogging().BuildServiceProvider(); + var loader = new KestrelConfigurationLoader(options, configuration, reloadOnChange: false); + loader.Load(); + void ConfigureListenOptions(ListenOptions listenOptions) + { + listenOptions.KestrelServerOptions = options; + listenOptions.UseHttps(); + }; + + await using (var server = new TestServer(App, new TestServiceContext(LoggerFactory), ConfigureListenOptions)) + { + var result = await server.HttpClientSlim.PostAsync($"https://localhost:{server.Port}/", + new FormUrlEncodedContent(new[] { + new KeyValuePair("content", "Hello World?") + }), + validateCertificate: false); + + Assert.Equal("content=Hello+World%3F", result); + } + } + [Fact] public async Task HandshakeDetailsAreAvailable() { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj index 952a0a9f50..9b2925609d 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj @@ -11,6 +11,9 @@ + + +