diff --git a/src/Servers/Kestrel/Core/src/CoreStrings.resx b/src/Servers/Kestrel/Core/src/CoreStrings.resx index 46f2e263a326..49c2f5fb482c 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 31e4270131bd..2843bb014c5e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs +++ b/src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs @@ -205,6 +205,8 @@ public CertificateConfig(IConfigurationSection configSection) 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 1fedf58ae5cd..a28c74ae872e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/LoggerExtensions.cs +++ b/src/Servers/Kestrel/Core/src/Internal/LoggerExtensions.cs @@ -46,6 +46,18 @@ internal static class LoggerExtensions 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 @@ internal static class LoggerExtensions 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 ad7210b6d655..04349031e2d1 100644 --- a/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs +++ b/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs @@ -6,6 +6,7 @@ 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 @@ private bool TryGetCertificatePath(out string path) 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 108edc514d13..8786fa6969ee 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 fb0b182cdd28..5053b3cffb12 100644 --- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs @@ -6,6 +6,7 @@ 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 @@ public class KestrelConfigurationLoaderTests 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 @@ public void ConfigureEndpointDevelopmentCertificateGetsLoadedWhenPresent() } } + [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 571f2cda3028..f4106e104099 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 000000000000..911dbdca5c4b --- /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 000000000000..0421f377734d Binary files /dev/null and b/src/Servers/Kestrel/shared/test/TestCertificates/https-aspnet.crt differ 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 000000000000..34fba0f776de --- /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 000000000000..196ea3217f94 --- /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 000000000000..2f087db08aa3 Binary files /dev/null and b/src/Servers/Kestrel/shared/test/TestCertificates/https-dsa.crt differ diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-dsa.key b/src/Servers/Kestrel/shared/test/TestCertificates/https-dsa.key new file mode 100644 index 000000000000..7034a7b5a487 --- /dev/null +++ b/src/Servers/Kestrel/shared/test/TestCertificates/https-dsa.key @@ -0,0 +1,9 @@ +-----BEGIN PRIVATE KEY----- +MIIBSwIBADCCASsGByqGSM44BAEwggEeAoGBAJyiyioeXx1O98gRCMEjlPKMpr79 +KrcDkoroghtuXO1U6Cx34pBRjOQmQLDPqSOriEo5VuG6SJc/ppfZx9TrSrzqB26h +KTUmiaOKmwpfIfzpi72wgsZeMOtU7JQ+FThfGyS8VxGh6G0h7xw26B/9ALxRw25z +O1cy9ZJs0EY3hsHzAhUA/4dpclsck8K+SkWBTcPfU+x7wTUCgYB4LP6UvrvIiiFP +xhk7AEGMMr0MhcJ3hhsgKWukUqIYsJKBM5MpKCnej5BHvnLXdKodIxygcKR4dJX7 +BRv69L+2RJk+UrYL1qBco5HpUslumA0e3gNdwRLoOoGD14dn1LD1LdESsyMgwfHH +J0RRkYwacgCVXsvHv/eAkA8qq136dwQXAhUA216Tqp4OvdUBNv8QLv8Z5QPopGQ= +-----END PRIVATE KEY----- diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-dsa.pem b/src/Servers/Kestrel/shared/test/TestCertificates/https-dsa.pem new file mode 100644 index 000000000000..4f64dac50996 --- /dev/null +++ b/src/Servers/Kestrel/shared/test/TestCertificates/https-dsa.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWTCCAxWgAwIBAgIUFRQGA90GHC74cNK/hNzQDi7XJFYwCwYJYIZIAWUDBAMC +MF0xCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhWaXJnaW5pYTETMBEGA1UEBwwKQWxl +eGFuZHJpYTEQMA4GA1UECgwHQ29udG9zbzEUMBIGA1UECwwLRGV2ZWxvcG1lbnQw +HhcNMjAwNjE5MTkyODIwWhcNMjAwNzE5MTkyODIwWjBdMQswCQYDVQQGEwJVUzER +MA8GA1UECAwIVmlyZ2luaWExEzARBgNVBAcMCkFsZXhhbmRyaWExEDAOBgNVBAoM +B0NvbnRvc28xFDASBgNVBAsMC0RldmVsb3BtZW50MIIBtjCCASsGByqGSM44BAEw +ggEeAoGBAJyiyioeXx1O98gRCMEjlPKMpr79KrcDkoroghtuXO1U6Cx34pBRjOQm +QLDPqSOriEo5VuG6SJc/ppfZx9TrSrzqB26hKTUmiaOKmwpfIfzpi72wgsZeMOtU +7JQ+FThfGyS8VxGh6G0h7xw26B/9ALxRw25zO1cy9ZJs0EY3hsHzAhUA/4dpclsc +k8K+SkWBTcPfU+x7wTUCgYB4LP6UvrvIiiFPxhk7AEGMMr0MhcJ3hhsgKWukUqIY +sJKBM5MpKCnej5BHvnLXdKodIxygcKR4dJX7BRv69L+2RJk+UrYL1qBco5HpUslu +mA0e3gNdwRLoOoGD14dn1LD1LdESsyMgwfHHJ0RRkYwacgCVXsvHv/eAkA8qq136 +dwOBhAACgYAHltgzkK3zD8yGdcGY0YgvN5l3lna1voLmcK+XtmehjMVy7OSSFICN +KybLBOvO8paydhCb1J0klkLPAoAjgP2cEd+KueeRyJpx+jD1MsjIEXIn5jtjXdUH +d0JJmHWAyHdNzmhXrXC7JLnj4ri7xMAV3GZGDpAnYvvL0LiXzFyomqNTMFEwHQYD +VR0OBBYEFF1l4ZrF3ND05CjGd//ev0dJLCB7MB8GA1UdIwQYMBaAFF1l4ZrF3ND0 +5CjGd//ev0dJLCB7MA8GA1UdEwEB/wQFMAMBAf8wCwYJYIZIAWUDBAMCAzEAMC4C +FQD6plYf60MDCvMjf1yQ8SBaFX3YYwIVAKqRQklh2b0Qhv+US222hb8xySJV +-----END CERTIFICATE----- diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa-protected.key b/src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa-protected.key new file mode 100644 index 000000000000..bc4741ec91ec --- /dev/null +++ b/src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa-protected.key @@ -0,0 +1,7 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAjGHXmGVX2JFwICCAAw +DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEN5v5Ytosv3NT48Qg8C/Zh0EgZDK +aWxzYPTIuIA4Tb1kCTd7BVGTsdeBxb5clCbEehF3yIZskalbRfjDso4n2HtVY9eq ++UZilRDSAt/XUhJqqViwbGg3pc+IHTdM2kTosG9vZK7WILRPvPphBFyn1NGwYPak +zHeW1T2Y0PXdjxloqccS6xv4ySwHwv8Hp1pPbpUfRakH3KTrGzOlKouktkcrhfw= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa.crt b/src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa.crt new file mode 100644 index 000000000000..42a18cbb4144 Binary files /dev/null and b/src/Servers/Kestrel/shared/test/TestCertificates/https-ecdsa.crt differ 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 000000000000..e6ca224e8a22 --- /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 000000000000..73066f6b49a6 --- /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 000000000000..34fba0f776de --- /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 000000000000..0421f377734d Binary files /dev/null and b/src/Servers/Kestrel/shared/test/TestCertificates/https-rsa.crt differ 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 000000000000..3462b060b3d5 --- /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 000000000000..2a2d0312f342 --- /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 fe0b8dae14fd..70b216e149e7 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.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 @@ void ConfigureListenOptions(ListenOptions listenOptions) } } + [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 952a0a9f50c9..9b2925609d38 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 @@ + + +