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.
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;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
@ -24,33 +18,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public void Configure(KestrelServerOptions options)
{
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
httpsOptions.ServerCertificate = LoadCertificate(endpoint.Certificate, endpoint.Name)
?? httpsOptions.ServerCertificate;
// Fallback
Options.ApplyDefaultCert(httpsOptions);
}
if (EndpointConfigurations.TryGetValue(endpoint.Name, out var configureEndpoint))

View File

@ -3,12 +3,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Certificates.Generation;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
@ -75,10 +80,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
private Action<HttpsConnectionAdapterOptions> HttpsDefaults { get; set; } = _ => { };
/// <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>
internal X509Certificate2 DefaultCertificate { get; set; }
/// <summary>
/// Has the default dev certificate load been attempted?
/// </summary>
internal bool IsDevCertLoaded { get; set; }
/// <summary>
/// Specifies a configuration Action to run for each newly created endpoint. Calling this again will replace
/// the prior action.
@ -105,10 +115,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal void ApplyHttpsDefaults(HttpsConnectionAdapterOptions httpsOptions)
{
httpsOptions.ServerCertificate = DefaultCertificate;
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>
/// Creates a configuration loader for setting up Kestrel.
/// </summary>

View File

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

View File

@ -65,7 +65,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
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");

View File

@ -47,25 +47,56 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
options.UseHttps();
});
Assert.False(serverOptions.IsDevCertLoaded);
serverOptions.ListenLocalhost(5001, options =>
{
options.UseHttps(opt =>
{
Assert.Equal(defaultCert, opt.ServerCertificate);
// The default cert is applied after UseHttps.
Assert.Null(opt.ServerCertificate);
});
});
Assert.False(serverOptions.IsDevCertLoaded);
}
[Fact]
public void ConfigureHttpsDefaultsOverridesDefaultCert()
public void ConfigureHttpsDefaultsNeverLoadsDefaultCert()
{
var serverOptions = CreateServerOptions();
var defaultCert = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
serverOptions.DefaultCertificate = defaultCert;
var testCert = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
serverOptions.ConfigureHttpsDefaults(options =>
{
Assert.Equal(defaultCert, options.ServerCertificate);
options.ServerCertificate = null;
Assert.Null(options.ServerCertificate);
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;
});
serverOptions.ListenLocalhost(5000, options =>
@ -73,12 +104,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
options.UseHttps(opt =>
{
Assert.Null(opt.ServerCertificate);
Assert.NotNull(opt.ServerCertificateSelector);
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]