Delay loading the dev cert #2422

This commit is contained in:
Chris Ross (ASP.NET) 2018-04-04 12:22:42 -07:00
parent 953496a970
commit 2ee0d6e337
6 changed files with 100 additions and 45 deletions

View File

@ -2,12 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Certificates.Generation;
using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
@ -24,33 +18,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public void Configure(KestrelServerOptions options) public void Configure(KestrelServerOptions options)
{ {
options.ApplicationServices = _services; options.ApplicationServices = _services;
UseDefaultDeveloperCertificate(options);
}
private void UseDefaultDeveloperCertificate(KestrelServerOptions options)
{
var logger = options.ApplicationServices.GetRequiredService<ILogger<KestrelServer>>();
X509Certificate2 certificate = null;
try
{
var certificateManager = new CertificateManager();
certificate = certificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true)
.FirstOrDefault();
}
catch
{
logger.UnableToLocateDevelopmentCertificate();
}
if (certificate != null)
{
logger.LocatedDevelopmentCertificate(certificate);
options.DefaultCertificate = certificate;
}
else
{
logger.UnableToLocateDevelopmentCertificate();
}
} }
} }
} }

View File

@ -225,6 +225,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel
// Specified // Specified
httpsOptions.ServerCertificate = LoadCertificate(endpoint.Certificate, endpoint.Name) httpsOptions.ServerCertificate = LoadCertificate(endpoint.Certificate, endpoint.Name)
?? httpsOptions.ServerCertificate; ?? httpsOptions.ServerCertificate;
// Fallback
Options.ApplyDefaultCert(httpsOptions);
} }
if (EndpointConfigurations.TryGetValue(endpoint.Name, out var configureEndpoint)) if (EndpointConfigurations.TryGetValue(endpoint.Name, out var configureEndpoint))

View File

@ -3,12 +3,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Certificates.Generation;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core namespace Microsoft.AspNetCore.Server.Kestrel.Core
{ {
@ -75,10 +80,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
private Action<HttpsConnectionAdapterOptions> HttpsDefaults { get; set; } = _ => { }; private Action<HttpsConnectionAdapterOptions> HttpsDefaults { get; set; } = _ => { };
/// <summary> /// <summary>
/// The default server certificate for https endpoints. This is applied before HttpsDefaults. /// The default server certificate for https endpoints. This is applied lazily after HttpsDefaults and user options.
/// </summary> /// </summary>
internal X509Certificate2 DefaultCertificate { get; set; } internal X509Certificate2 DefaultCertificate { get; set; }
/// <summary>
/// Has the default dev certificate load been attempted?
/// </summary>
internal bool IsDevCertLoaded { get; set; }
/// <summary> /// <summary>
/// Specifies a configuration Action to run for each newly created endpoint. Calling this again will replace /// Specifies a configuration Action to run for each newly created endpoint. Calling this again will replace
/// the prior action. /// the prior action.
@ -105,10 +115,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal void ApplyHttpsDefaults(HttpsConnectionAdapterOptions httpsOptions) internal void ApplyHttpsDefaults(HttpsConnectionAdapterOptions httpsOptions)
{ {
httpsOptions.ServerCertificate = DefaultCertificate;
HttpsDefaults(httpsOptions); HttpsDefaults(httpsOptions);
} }
internal void ApplyDefaultCert(HttpsConnectionAdapterOptions httpsOptions)
{
if (httpsOptions.ServerCertificate != null || httpsOptions.ServerCertificateSelector != null)
{
return;
}
EnsureDefaultCert();
httpsOptions.ServerCertificate = DefaultCertificate;
}
private void EnsureDefaultCert()
{
if (DefaultCertificate == null && !IsDevCertLoaded)
{
IsDevCertLoaded = true; // Only try once
var logger = ApplicationServices.GetRequiredService<ILogger<KestrelServer>>();
try
{
var certificateManager = new CertificateManager();
DefaultCertificate = certificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true)
.FirstOrDefault();
if (DefaultCertificate != null)
{
logger.LocatedDevelopmentCertificate(DefaultCertificate);
}
else
{
logger.UnableToLocateDevelopmentCertificate();
}
}
catch
{
logger.UnableToLocateDevelopmentCertificate();
}
}
}
/// <summary> /// <summary>
/// Creates a configuration loader for setting up Kestrel. /// Creates a configuration loader for setting up Kestrel.
/// </summary> /// </summary>

View File

@ -178,6 +178,7 @@ namespace Microsoft.AspNetCore.Hosting
var options = new HttpsConnectionAdapterOptions(); var options = new HttpsConnectionAdapterOptions();
listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options); listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options);
configureOptions(options); configureOptions(options);
listenOptions.KestrelServerOptions.ApplyDefaultCert(options);
if (options.ServerCertificate == null && options.ServerCertificateSelector == null) if (options.ServerCertificate == null && options.ServerCertificateSelector == null)
{ {
@ -191,6 +192,7 @@ namespace Microsoft.AspNetCore.Hosting
{ {
var options = new HttpsConnectionAdapterOptions(); var options = new HttpsConnectionAdapterOptions();
listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options); listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options);
listenOptions.KestrelServerOptions.ApplyDefaultCert(options);
if (options.ServerCertificate == null && options.ServerCertificateSelector == null) if (options.ServerCertificate == null && options.ServerCertificateSelector == null)
{ {

View File

@ -65,7 +65,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void KestrelServerThrowsUsefulExceptionIfDefaultHttpsProviderNotAdded() public void KestrelServerThrowsUsefulExceptionIfDefaultHttpsProviderNotAdded()
{ {
using (var server = CreateServer(CreateServerOptions(), throwOnCriticalErrors: false)) var options = CreateServerOptions();
options.IsDevCertLoaded = true; // Prevent the system default from being loaded
using (var server = CreateServer(options, throwOnCriticalErrors: false))
{ {
server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://127.0.0.1:0"); server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://127.0.0.1:0");

View File

@ -47,25 +47,56 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
options.UseHttps(); options.UseHttps();
}); });
Assert.False(serverOptions.IsDevCertLoaded);
serverOptions.ListenLocalhost(5001, options => serverOptions.ListenLocalhost(5001, options =>
{ {
options.UseHttps(opt => options.UseHttps(opt =>
{ {
Assert.Equal(defaultCert, opt.ServerCertificate); // The default cert is applied after UseHttps.
Assert.Null(opt.ServerCertificate);
}); });
}); });
Assert.False(serverOptions.IsDevCertLoaded);
} }
[Fact] [Fact]
public void ConfigureHttpsDefaultsOverridesDefaultCert() public void ConfigureHttpsDefaultsNeverLoadsDefaultCert()
{ {
var serverOptions = CreateServerOptions(); var serverOptions = CreateServerOptions();
var defaultCert = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"); var testCert = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
serverOptions.DefaultCertificate = defaultCert;
serverOptions.ConfigureHttpsDefaults(options => serverOptions.ConfigureHttpsDefaults(options =>
{ {
Assert.Equal(defaultCert, options.ServerCertificate); Assert.Null(options.ServerCertificate);
options.ServerCertificate = null; options.ServerCertificate = testCert;
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
});
serverOptions.ListenLocalhost(5000, options =>
{
options.UseHttps(opt =>
{
Assert.Equal(testCert, opt.ServerCertificate);
Assert.Equal(ClientCertificateMode.RequireCertificate, opt.ClientCertificateMode);
});
});
// Never lazy loaded
Assert.False(serverOptions.IsDevCertLoaded);
Assert.Null(serverOptions.DefaultCertificate);
}
[Fact]
public void ConfigureCertSelectorNeverLoadsDefaultCert()
{
var serverOptions = CreateServerOptions();
var testCert = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
serverOptions.ConfigureHttpsDefaults(options =>
{
Assert.Null(options.ServerCertificate);
Assert.Null(options.ServerCertificateSelector);
options.ServerCertificateSelector = (features, name) =>
{
return testCert;
};
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate; options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
}); });
serverOptions.ListenLocalhost(5000, options => serverOptions.ListenLocalhost(5000, options =>
@ -73,12 +104,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
options.UseHttps(opt => options.UseHttps(opt =>
{ {
Assert.Null(opt.ServerCertificate); Assert.Null(opt.ServerCertificate);
Assert.NotNull(opt.ServerCertificateSelector);
Assert.Equal(ClientCertificateMode.RequireCertificate, opt.ClientCertificateMode); Assert.Equal(ClientCertificateMode.RequireCertificate, opt.ClientCertificateMode);
// So UseHttps won't throw
opt.ServerCertificate = defaultCert;
}); });
}); });
// Never lazy loaded
Assert.False(serverOptions.IsDevCertLoaded);
Assert.Null(serverOptions.DefaultCertificate);
} }
[Fact] [Fact]