-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Kestrel SNI from config #24286
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Kestrel SNI from config #24286
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
5b737d6
Kestrel SNI from config
halter73 b66dbc4
ConfigurationReaderTests
halter73 3d98e44
Cache SslServerAuthenticationOptions and certs
halter73 14b20e3
Validate cached options on load
halter73 246de72
WIP
halter73 5e49ce1
Solidify fallback logic
halter73 79d221b
Support default handshake timeout
halter73 b01b5ce
Flow SNI HttpProtocols to middleware
halter73 2f0dd94
Use default CertificateRevocationCheckMode for SNI endpoints
halter73 b66f0ed
SNI -> Sni
halter73 f8b0f56
Add CertificateConfigLoader for easier testing
halter73 40a33b8
Add SniOptionsSelectorTests
halter73 55a2ff2
Add more SniOptionsSelectorTests
halter73 68489f9
Address PR feedback
halter73 0c65226
Support SNI config in EndpointDefaults
halter73 a2f708c
Test new internal UseHttps() overload
halter73 6237f5a
Add CloneSslOptionsClonesAllProperties test
halter73 4869022
More tests
halter73 14b4d3b
Add functional test
halter73 24a4023
SkipValidation -> IsTestMock
halter73 da3aa14
Remove EndpointDefaults.Sni
halter73 25d354b
Validate that HTTPS config is not applied to non-HTTPS endpoints
halter73 052bd57
Fix CloneSslOptionsClonesAllProperties test on Ubuntu 16.04
halter73 a538c2f
Address PR feedback
halter73 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
178 changes: 178 additions & 0 deletions
178
src/Servers/Kestrel/Core/src/Internal/Certificates/CertificateConfigLoader.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.IO; | ||
using System.Runtime.InteropServices; | ||
using System.Security.Cryptography; | ||
using System.Security.Cryptography.X509Certificates; | ||
using Microsoft.AspNetCore.Server.Kestrel.Https; | ||
using Microsoft.Extensions.Hosting; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Certificates | ||
{ | ||
internal class CertificateConfigLoader : ICertificateConfigLoader | ||
{ | ||
public CertificateConfigLoader(IHostEnvironment hostEnvironment, ILogger<KestrelServer> logger) | ||
{ | ||
HostEnvironment = hostEnvironment; | ||
Logger = logger; | ||
} | ||
|
||
public IHostEnvironment HostEnvironment { get; } | ||
public ILogger<KestrelServer> Logger { get; } | ||
|
||
public bool IsTestMock => false; | ||
|
||
public X509Certificate2 LoadCertificate(CertificateConfig certInfo, string endpointName) | ||
{ | ||
if (certInfo is null) | ||
{ | ||
return null; | ||
} | ||
|
||
if (certInfo.IsFileCert && certInfo.IsStoreCert) | ||
{ | ||
throw new InvalidOperationException(CoreStrings.FormatMultipleCertificateSources(endpointName)); | ||
} | ||
else if (certInfo.IsFileCert) | ||
{ | ||
var certificatePath = Path.Combine(HostEnvironment.ContentRootPath, certInfo.Path); | ||
if (certInfo.KeyPath != null) | ||
{ | ||
var certificateKeyPath = Path.Combine(HostEnvironment.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(HostEnvironment.ContentRootPath, certInfo.Path), certInfo.Password); | ||
} | ||
else if (certInfo.IsStoreCert) | ||
{ | ||
return LoadFromStoreCert(certInfo); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private 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); | ||
} | ||
|
||
private 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)) | ||
}; | ||
} | ||
|
||
private 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) | ||
{ | ||
var subject = certInfo.Subject; | ||
var storeName = string.IsNullOrEmpty(certInfo.Store) ? StoreName.My.ToString() : certInfo.Store; | ||
var location = certInfo.Location; | ||
var storeLocation = StoreLocation.CurrentUser; | ||
if (!string.IsNullOrEmpty(location)) | ||
{ | ||
storeLocation = (StoreLocation)Enum.Parse(typeof(StoreLocation), location, ignoreCase: true); | ||
} | ||
var allowInvalid = certInfo.AllowInvalid ?? false; | ||
|
||
return CertificateLoader.LoadFromStoreCert(subject, storeName, storeLocation, allowInvalid); | ||
} | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
src/Servers/Kestrel/Core/src/Internal/Certificates/ICertificateConfigLoader.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.Security.Cryptography.X509Certificates; | ||
|
||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Certificates | ||
{ | ||
internal interface ICertificateConfigLoader | ||
{ | ||
bool IsTestMock { get; } | ||
|
||
X509Certificate2 LoadCertificate(CertificateConfig certInfo, string endpointName); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.