[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
This commit is contained in:
Javier Calvarro Nelson 2020-07-06 15:48:46 +02:00 committed by GitHub
parent dc477ed2f4
commit 9d3bf572b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 480 additions and 3 deletions

View File

@ -614,4 +614,10 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
<data name="ArgumentTimeSpanGreaterOrEqual" xml:space="preserve">
<value>A TimeSpan value greater than or equal to {value} is required.</value>
</data>
<data name="InvalidPemKey" xml:space="preserve">
<value>The provided key file is missing or invalid.</value>
</data>
<data name="UnrecognizedCertificateKeyOid" xml:space="preserve">
<value>Unknown algorithm for certificate with public key type '{0}'.</value>
</data>
</root>

View File

@ -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

View File

@ -46,6 +46,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
new EventId(5, "DeveloperCertificateFirstRun"),
"{Message}");
private static readonly Action<ILogger, string, Exception> _failedToLoadCertificate =
LoggerMessage.Define<string>(
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<ILogger, string, Exception> _failedToLoadCertificateKey =
LoggerMessage.Define<string>(
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);
}
}

View File

@ -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<ILogger<KestrelConfigurationLoader>>();
if (certInfo.IsFileCert && certInfo.IsStoreCert)
{
throw new InvalidOperationException(CoreStrings.FormatMultipleCertificateSources(endpointName));
}
else if (certInfo.IsFileCert)
{
var env = Options.ApplicationServices.GetRequiredService<IHostEnvironment>();
return new X509Certificate2(Path.Combine(env.ContentRootPath, certInfo.Path), certInfo.Password);
var environment = Options.ApplicationServices.GetRequiredService<IHostEnvironment>();
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)

View File

@ -13,6 +13,8 @@
<Compile Include="$(KestrelSharedSourceRoot)test\*.cs" LinkBase="shared" />
<Compile Include="$(KestrelSharedSourceRoot)KnownHeaders.cs" LinkBase="shared" />
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.crt" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.key" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
<Compile Include="$(KestrelSharedSourceRoot)\CorrelationIdGenerator.cs" Link="Internal\CorrelationIdGenerator.cs" />
<Compile Include="$(SharedSourceRoot)test\Shared.Tests\runtime\**\*.cs" Link="Shared\runtime\%(Filename)%(Extension)" />

View File

@ -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<IHostEnvironment>(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<string, string>("Endpoints:End1:Url", "https://*:5001"),
new KeyValuePair<string, string>("Certificates:Default:Path", Path.Combine("shared", "TestCertificates", "https-aspnet.crt")),
new KeyValuePair<string, string>("Certificates:Default:KeyPath", Path.Combine("shared", "TestCertificates", "https-aspnet.key"))
}).Build();
var ex = Assert.Throws<ArgumentException>(() =>
{
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<string, string>("Endpoints:End1:Url", "https://*:5001"),
new KeyValuePair<string, string>("Certificates:Default:Path", Path.Combine("shared", "TestCertificates", "https-aspnet.crt")),
new KeyValuePair<string, string>("Certificates:Default:KeyPath", Path.Combine("shared", "TestCertificates", "https-ecdsa.key")),
new KeyValuePair<string, string>("Certificates:Default:Password", "aspnetcore")
}).Build();
var ex = Assert.Throws<ArgumentException>(() =>
{
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<string, string>("Endpoints:End1:Url", "https://*:5001"),
new KeyValuePair<string, string>("Certificates:Default:Path", Path.Combine("shared", "TestCertificates", "https-aspnet.crt")),
new KeyValuePair<string, string>("Certificates:Default:KeyPath", Path.Combine("shared", "TestCertificates", "https-aspnet.key")),
new KeyValuePair<string, string>("Certificates:Default:Password", "abcde"),
}).Build();
var ex = Assert.Throws<CryptographicException>(() =>
{
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<string, string>("Endpoints:End1:Url", "https://*:5001"),
new KeyValuePair<string, string>("Certificates:Default:Path", Path.Combine("shared", "TestCertificates", certificateFile)),
new KeyValuePair<string, string>("Certificates:Default:KeyPath", Path.Combine("shared", "TestCertificates", certificateKey)),
}
.Concat(password != null ? new[] { new KeyValuePair<string, string>("Certificates:Default:Password", password) } : Array.Empty<KeyValuePair<string, string>>()))
.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()
{

View File

@ -8,6 +8,9 @@
<Compile Include="$(SharedSourceRoot)NullScope.cs" />
<Compile Include="$(KestrelSharedSourceRoot)test\*.cs" LinkBase="shared" />
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.crt" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.key" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.pem" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelRoot)Core\src\Internal\Http\HttpHeaders.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelRoot)Core\src\Internal\Http\HttpProtocol.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelRoot)Core\src\Internal\Infrastructure\HttpUtilities.Generated.cs" LinkBase="shared\GeneratedContent" CopyToOutputDirectory="PreserveNewest" />

View File

@ -0,0 +1,2 @@
*.key binary
*.pem binary

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -0,0 +1,7 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAjGHXmGVX2JFwICCAAw
DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEN5v5Ytosv3NT48Qg8C/Zh0EgZDK
aWxzYPTIuIA4Tb1kCTd7BVGTsdeBxb5clCbEehF3yIZskalbRfjDso4n2HtVY9eq
+UZilRDSAt/XUhJqqViwbGg3pc+IHTdM2kTosG9vZK7WILRPvPphBFyn1NGwYPak
zHeW1T2Y0PXdjxloqccS6xv4ySwHwv8Hp1pPbpUfRakH3KTrGzOlKouktkcrhfw=
-----END ENCRYPTED PRIVATE KEY-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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<string, string>
{
["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<IHostEnvironment>();
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<string, string>("content", "Hello World?")
}),
validateCertificate: false);
Assert.Equal("content=Hello+World%3F", result);
}
}
[Fact]
public async Task HandshakeDetailsAreAvailable()
{

View File

@ -11,6 +11,9 @@
<Compile Include="$(SharedSourceRoot)NullScope.cs" />
<Compile Include="$(KestrelSharedSourceRoot)test\*.cs" LinkBase="shared" />
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.crt" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.key" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.pem" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
<Compile Include="$(KestrelSharedSourceRoot)\CorrelationIdGenerator.cs" Link="Internal\CorrelationIdGenerator.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.cs" Link="Internal\TransportConnection.cs" />