diff --git a/src/Servers/Kestrel/Core/src/IHttpsConfigurationService.cs b/src/Servers/Kestrel/Core/src/IHttpsConfigurationService.cs index 7e5a955719fd..171b6a3a86fd 100644 --- a/src/Servers/Kestrel/Core/src/IHttpsConfigurationService.cs +++ b/src/Servers/Kestrel/Core/src/IHttpsConfigurationService.cs @@ -90,11 +90,21 @@ void ApplyHttpsConfiguration( internal readonly struct CertificateAndConfig { public readonly X509Certificate2 Certificate; + public readonly X509Certificate2Collection CertificateChain; public readonly CertificateConfig CertificateConfig; public CertificateAndConfig(X509Certificate2 certificate, CertificateConfig certificateConfig) + : this( + certificate, + certificateConfig, + []) + { + } + + public CertificateAndConfig(X509Certificate2 certificate, CertificateConfig certificateConfig, X509Certificate2Collection certificateChain) { Certificate = certificate; CertificateConfig = certificateConfig; + CertificateChain = certificateChain; } } diff --git a/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs b/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs index 49842d4c3389..e7e67c67c6d4 100644 --- a/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs +++ b/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs @@ -77,6 +77,8 @@ internal KestrelConfigurationLoader( private CertificateConfig? DefaultCertificateConfig { get; set; } internal X509Certificate2? DefaultCertificate { get; set; } + internal X509Certificate2Collection? DefaultCertificateChain { get; set; } + /// /// Specifies a configuration Action to run when an endpoint with the given name is loaded from configuration. /// @@ -345,12 +347,14 @@ internal void ProcessEndpointsToAdd() DefaultCertificateConfig = null; DefaultCertificate = null; + DefaultCertificateChain = null; ConfigurationReader = new ConfigurationReader(Configuration); if (_httpsConfigurationService.IsInitialized && _httpsConfigurationService.LoadDefaultCertificate(ConfigurationReader) is CertificateAndConfig certPair) { DefaultCertificate = certPair.Certificate; + DefaultCertificateChain = certPair.CertificateChain; DefaultCertificateConfig = certPair.CertificateConfig; } diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index 63efe1767cad..30c703d9d87f 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -303,6 +303,10 @@ internal void ApplyDefaultCertificate(HttpsConnectionAdapterOptions httpsOptions if (ConfigurationLoader?.DefaultCertificate is X509Certificate2 certificateFromLoader) { httpsOptions.ServerCertificate = certificateFromLoader; + if (ConfigurationLoader?.DefaultCertificateChain is X509Certificate2Collection certificateChainFromLoader) + { + httpsOptions.ServerCertificateChain = certificateChainFromLoader; + } return; } diff --git a/src/Servers/Kestrel/Core/src/TlsConfigurationLoader.cs b/src/Servers/Kestrel/Core/src/TlsConfigurationLoader.cs index 2f19ea9acd26..abeb9df5c392 100644 --- a/src/Servers/Kestrel/Core/src/TlsConfigurationLoader.cs +++ b/src/Servers/Kestrel/Core/src/TlsConfigurationLoader.cs @@ -128,9 +128,13 @@ public ListenOptions UseHttpsWithSni( { if (configurationReader.Certificates.TryGetValue("Default", out var defaultCertConfig)) { - var (defaultCert, _ /* cert chain */) = _certificateConfigLoader.LoadCertificate(defaultCertConfig, "Default"); + var (defaultCert, defaultCertChain) = _certificateConfigLoader.LoadCertificate(defaultCertConfig, "Default"); if (defaultCert != null) { + if (defaultCertChain != null) + { + return new CertificateAndConfig(defaultCert, defaultCertConfig, defaultCertChain); + } return new CertificateAndConfig(defaultCert, defaultCertConfig); } } diff --git a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs index f361e01c903d..b4a55423038b 100644 --- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs @@ -196,6 +196,33 @@ public void ConfigureDefaultsAppliesToNewConfigureEndpoints() Assert.False(serverOptions.CodeBackedListenOptions[0].IsTls); } + [Fact] + public void ConfigureDefaultCertificatePathLoadsChain() + { + var serverOptions = CreateServerOptions(); + var testCertPath = TestResources.GetCertPath("leaf.com.crt"); + var ran1 = false; + var config = new ConfigurationBuilder().AddInMemoryCollection(new[] + { + new KeyValuePair("Endpoints:End1:Url", "https://*:5001"), + new KeyValuePair("Certificates:Default:Path", testCertPath) + }).Build(); + + serverOptions.Configure(config) + .Endpoint("End1", opt => + { + ran1 = true; + Assert.True(opt.IsHttps); + Assert.NotNull(opt.HttpsOptions.ServerCertificate); + Assert.NotNull(opt.HttpsOptions.ServerCertificateChain); + Assert.Equal(2, opt.HttpsOptions.ServerCertificateChain.Count); + }).Load(); + + Assert.True(ran1); + + Assert.True(serverOptions.ConfigurationBackedListenOptions[0].IsTls); + } + [Fact] public void ConfigureEndpointDefaultCanEnableHttps() {