Show fwlink on HTTPS certificate errors (#83).
This commit is contained in:
parent
4d5e1076c9
commit
012d9990ef
|
|
@ -13,7 +13,7 @@ namespace AppSettings
|
|||
{
|
||||
using (WebHost.Start(context => context.Response.WriteAsync("Hello, World!")))
|
||||
{
|
||||
Console.WriteLine("Running application: Press any key to shutdown...");
|
||||
Console.WriteLine("Running application: Press any key to shutdown.");
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore
|
||||
{
|
||||
|
|
@ -15,14 +16,17 @@ namespace Microsoft.AspNetCore
|
|||
public class CertificateLoader
|
||||
{
|
||||
private readonly IConfiguration _certificatesConfiguration;
|
||||
private readonly string _environmentName;
|
||||
private readonly ICertificateFileLoader _certificateFileLoader;
|
||||
private readonly ICertificateStoreLoader _certificateStoreLoader;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="CertificateLoader"/>.
|
||||
/// Creates a new instance of <see cref="CertificateLoader"/> that can load certificate references from configuration.
|
||||
/// </summary>
|
||||
public CertificateLoader()
|
||||
: this(null)
|
||||
/// <param name="certificatesConfiguration">An <see cref="IConfiguration"/> with information about certificates.</param>
|
||||
public CertificateLoader(IConfiguration certificatesConfiguration)
|
||||
: this(certificatesConfiguration, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -30,17 +34,35 @@ namespace Microsoft.AspNetCore
|
|||
/// Creates a new instance of <see cref="CertificateLoader"/> that can load certificate references from configuration.
|
||||
/// </summary>
|
||||
/// <param name="certificatesConfiguration">An <see cref="IConfiguration"/> with information about certificates.</param>
|
||||
public CertificateLoader(IConfiguration certificatesConfiguration)
|
||||
: this(certificatesConfiguration, new CertificateFileLoader(), new CertificateStoreLoader())
|
||||
/// <param name="loggerFactory">An <see cref="ILoggerFactory"/> instance.</param>
|
||||
public CertificateLoader(IConfiguration certificatesConfiguration, ILoggerFactory loggerFactory)
|
||||
: this(certificatesConfiguration, loggerFactory, null)
|
||||
{
|
||||
_certificatesConfiguration = certificatesConfiguration;
|
||||
}
|
||||
|
||||
internal CertificateLoader(IConfiguration certificatesConfiguration, ICertificateFileLoader certificateFileLoader, ICertificateStoreLoader certificateStoreLoader)
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="CertificateLoader"/> that can load certificate references from configuration.
|
||||
/// </summary>
|
||||
/// <param name="certificatesConfiguration">An <see cref="IConfiguration"/> with information about certificates.</param>
|
||||
/// <param name="loggerFactory">An <see cref="ILoggerFactory"/> instance.</param>
|
||||
/// <param name="environmentName">The name of the environment the application is running in.</param>
|
||||
public CertificateLoader(IConfiguration certificatesConfiguration, ILoggerFactory loggerFactory, string environmentName)
|
||||
: this(certificatesConfiguration, loggerFactory, environmentName, new CertificateFileLoader(), new CertificateStoreLoader())
|
||||
{
|
||||
}
|
||||
|
||||
internal CertificateLoader(
|
||||
IConfiguration certificatesConfiguration,
|
||||
ILoggerFactory loggerFactory,
|
||||
string environmentName,
|
||||
ICertificateFileLoader certificateFileLoader,
|
||||
ICertificateStoreLoader certificateStoreLoader)
|
||||
{
|
||||
_environmentName = environmentName;
|
||||
_certificatesConfiguration = certificatesConfiguration;
|
||||
_certificateFileLoader = certificateFileLoader;
|
||||
_certificateStoreLoader = certificateStoreLoader;
|
||||
_logger = loggerFactory?.CreateLogger("Microsoft.AspNetCore.CertificateLoader");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -99,7 +121,8 @@ namespace Microsoft.AspNetCore
|
|||
|
||||
if (!certificateConfiguration.Exists())
|
||||
{
|
||||
throw new InvalidOperationException($"No certificate named {certificateName} found in configuration");
|
||||
var environmentName = _environmentName != null ? $" ({_environmentName})" : "";
|
||||
throw new KeyNotFoundException($"No certificate named '{certificateName}' found in configuration for the current environment{environmentName}.");
|
||||
}
|
||||
|
||||
return LoadSingle(certificateConfiguration);
|
||||
|
|
@ -116,10 +139,10 @@ namespace Microsoft.AspNetCore
|
|||
certificateSource = new CertificateFileSource(_certificateFileLoader);
|
||||
break;
|
||||
case "store":
|
||||
certificateSource = new CertificateStoreSource(_certificateStoreLoader);
|
||||
certificateSource = new CertificateStoreSource(_certificateStoreLoader, _logger);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid certificate source kind: {sourceKind}");
|
||||
throw new InvalidOperationException($"Invalid certificate source kind '{sourceKind}'.");
|
||||
}
|
||||
|
||||
certificateConfiguration.Bind(certificateSource);
|
||||
|
|
@ -163,7 +186,7 @@ namespace Microsoft.AspNetCore
|
|||
|
||||
if (error != null)
|
||||
{
|
||||
throw error;
|
||||
throw new InvalidOperationException($"Unable to load certificate from file '{Path}'. Error details: '{error.Message}'.", error);
|
||||
}
|
||||
|
||||
return certificate;
|
||||
|
|
@ -188,10 +211,12 @@ namespace Microsoft.AspNetCore
|
|||
private class CertificateStoreSource : CertificateSource
|
||||
{
|
||||
private readonly ICertificateStoreLoader _certificateStoreLoader;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CertificateStoreSource(ICertificateStoreLoader certificateStoreLoader)
|
||||
public CertificateStoreSource(ICertificateStoreLoader certificateStoreLoader, ILogger logger)
|
||||
{
|
||||
_certificateStoreLoader = certificateStoreLoader;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string Subject { get; set; }
|
||||
|
|
@ -203,10 +228,17 @@ namespace Microsoft.AspNetCore
|
|||
{
|
||||
if (!Enum.TryParse(StoreLocation, ignoreCase: true, result: out StoreLocation storeLocation))
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid store location: {StoreLocation}");
|
||||
throw new InvalidOperationException($"The certificate store location '{StoreLocation}' is invalid.");
|
||||
}
|
||||
|
||||
return _certificateStoreLoader.Load(Subject, StoreName, storeLocation, !AllowInvalid);
|
||||
var certificate = _certificateStoreLoader.Load(Subject, StoreName, storeLocation, !AllowInvalid);
|
||||
|
||||
if (certificate == null)
|
||||
{
|
||||
_logger?.LogWarning($"Unable to find a matching certificate for subject '{Subject}' in store '{StoreName}' in '{StoreLocation}'.");
|
||||
}
|
||||
|
||||
return certificate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,20 +4,29 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore
|
||||
{
|
||||
internal class KestrelServerOptionsSetup : IConfigureOptions<KestrelServerOptions>
|
||||
{
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IConfiguration _configurationRoot;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
public KestrelServerOptionsSetup(IConfiguration configurationRoot)
|
||||
public KestrelServerOptionsSetup(
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IConfiguration configurationRoot,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_configurationRoot = configurationRoot;
|
||||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
public void Configure(KestrelServerOptions options)
|
||||
|
|
@ -27,7 +36,7 @@ namespace Microsoft.AspNetCore
|
|||
|
||||
private void BindConfiguration(KestrelServerOptions options)
|
||||
{
|
||||
var certificateLoader = new CertificateLoader(_configurationRoot.GetSection("Certificates"));
|
||||
var certificateLoader = new CertificateLoader(_configurationRoot.GetSection("Certificates"), _loggerFactory, _hostingEnvironment.EnvironmentName);
|
||||
|
||||
foreach (var endPoint in _configurationRoot.GetSection("Kestrel:EndPoints").GetChildren())
|
||||
{
|
||||
|
|
@ -56,14 +65,22 @@ namespace Microsoft.AspNetCore
|
|||
options.Listen(address, port, listenOptions =>
|
||||
{
|
||||
var certificateConfig = endPoint.GetSection("Certificate");
|
||||
X509Certificate2 certificate;
|
||||
|
||||
if (certificateConfig.Exists())
|
||||
{
|
||||
var certificate = certificateLoader.Load(certificateConfig).FirstOrDefault();
|
||||
|
||||
if (certificate == null)
|
||||
try
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to load certificate for endpoint '{endPoint.Key}'");
|
||||
certificate = certificateLoader.Load(certificateConfig).FirstOrDefault();
|
||||
|
||||
if (certificate == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No certificate found for endpoint '{endPoint.Key}'.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to configure HTTPS endpoint. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.", ex);
|
||||
}
|
||||
|
||||
listenOptions.UseHttps(certificate);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Xunit;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.FunctionalTests
|
||||
{
|
||||
|
|
@ -35,6 +36,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
||||
|
|
@ -45,31 +48,28 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void Throws_SingleCertificateName_File_KeyNotFound()
|
||||
public void Throws_SingleCertificateName_KeyNotFound()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
{
|
||||
["Certificates:Certificate1:Source"] = "File",
|
||||
["Certificates:Certificate1:Path"] = "Certificate1.pfx",
|
||||
["Certificates:Certificate1:Password"] = "Password1",
|
||||
["TestConfig:Certificate"] = "Certificate2"
|
||||
["TestConfig:Certificate"] = "Certificate1"
|
||||
})
|
||||
.Build();
|
||||
|
||||
var certificate = new X509Certificate2();
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate")));
|
||||
Assert.Equal("No certificate named Certificate2 found in configuration", exception.Message);
|
||||
var exception = Assert.Throws<KeyNotFoundException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate")));
|
||||
Assert.Equal("No certificate named 'Certificate1' found in configuration for the current environment.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Throws_SingleCertificateName_File_FileNotFound()
|
||||
public void Throws_SingleCertificateName_File_FileLoadError()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
|
|
@ -81,19 +81,20 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
})
|
||||
.Build();
|
||||
|
||||
var exception = new Exception();
|
||||
|
||||
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||
certificateFileLoader
|
||||
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||
.Callback(() => throw exception);
|
||||
.Callback(() => throw new Exception(nameof(Throws_SingleCertificateName_File_FileLoadError)));
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
||||
Assert.Same(exception, Assert.Throws<Exception>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"))));
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate")));
|
||||
Assert.Equal($"Unable to load certificate from file 'Certificate1.pfx'. Error details: '{nameof(Throws_SingleCertificateName_File_FileLoadError)}'.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -119,6 +120,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
certificateStoreLoader.Object);
|
||||
|
||||
|
|
@ -127,30 +130,7 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
Assert.Same(certificate, loadedCertificates.ElementAt(0));
|
||||
certificateStoreLoader.VerifyAll();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Throws_SingleCertificateName_Store_KeyNotFound()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
{
|
||||
["Certificates:Certificate1:Source"] = "Store",
|
||||
["Certificates:Certificate1:Subject"] = "localhost",
|
||||
["Certificates:Certificate1:StoreName"] = "My",
|
||||
["Certificates:Certificate1:StoreLocation"] = "CurrentUser",
|
||||
["TestConfig:Certificate"] = "Certificate2"
|
||||
})
|
||||
.Build();
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate")));
|
||||
Assert.Equal("No certificate named Certificate2 found in configuration", exception.Message);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void ReturnsNull_SingleCertificateName_Store_NotFoundInStore()
|
||||
{
|
||||
|
|
@ -172,6 +152,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
certificateStoreLoader.Object);
|
||||
|
||||
|
|
@ -209,6 +191,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
||||
|
|
@ -220,12 +204,9 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Certificate1;NotFound")]
|
||||
[InlineData("Certificate1;Certificate2;NotFound")]
|
||||
[InlineData("NotFound;Certificate1")]
|
||||
[InlineData("NotFound;Certificate1;Certificate2")]
|
||||
[InlineData("Certificate1;NotFound;Certificate2")]
|
||||
public void Throws_MultipleCertificateNames_File_KeyNotFound(string certificateNames)
|
||||
[InlineData("Certificate1;Certificate2")]
|
||||
[InlineData("Certificate2;Certificate1")]
|
||||
public void Throws_MultipleCertificateNames_File_FileLoadError(string certificateNames)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
|
|
@ -241,7 +222,6 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
.Build();
|
||||
|
||||
var certificate1 = new X509Certificate2();
|
||||
var certificate2 = new X509Certificate2();
|
||||
|
||||
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||
certificateFileLoader
|
||||
|
|
@ -249,52 +229,17 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
.Returns(certificate1);
|
||||
certificateFileLoader
|
||||
.Setup(loader => loader.Load("Certificate2.pfx", "Password2", It.IsAny<X509KeyStorageFlags>()))
|
||||
.Returns(certificate2);
|
||||
.Throws(new Exception(nameof(Throws_MultipleCertificateNames_File_FileLoadError)));
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate")));
|
||||
Assert.Equal("No certificate named NotFound found in configuration", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Certificate1;Certificate2")]
|
||||
[InlineData("Certificate2;Certificate1")]
|
||||
public void Throws_MultipleCertificateNames_File_FileNotFound(string certificateNames)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
{
|
||||
["Certificates:Certificate1:Source"] = "File",
|
||||
["Certificates:Certificate1:Path"] = "Certificate1.pfx",
|
||||
["Certificates:Certificate1:Password"] = "Password1",
|
||||
["Certificates:Certificate2:Source"] = "File",
|
||||
["Certificates:Certificate2:Path"] = "Certificate2.pfx",
|
||||
["Certificates:Certificate2:Password"] = "Password2",
|
||||
["TestConfig:Certificate"] = certificateNames
|
||||
})
|
||||
.Build();
|
||||
|
||||
var certificate1 = new X509Certificate2();
|
||||
var exception = new Exception();
|
||||
|
||||
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||
certificateFileLoader
|
||||
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||
.Returns(certificate1);
|
||||
certificateFileLoader
|
||||
.Setup(loader => loader.Load("Certificate2.pfx", "Password2", It.IsAny<X509KeyStorageFlags>()))
|
||||
.Throws(exception);
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
certificateFileLoader.Object,
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
||||
Assert.Same(exception, Assert.Throws<Exception>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"))));
|
||||
Assert.Equal($"Unable to load certificate from file 'Certificate2.pfx'. Error details: '{nameof(Throws_MultipleCertificateNames_File_FileLoadError)}'.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -328,6 +273,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
certificateStoreLoader.Object);
|
||||
|
||||
|
|
@ -338,49 +285,6 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
certificateStoreLoader.VerifyAll();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Certificate1;NotFound")]
|
||||
[InlineData("Certificate1;Certificate2;NotFound")]
|
||||
[InlineData("NotFound;Certificate1")]
|
||||
[InlineData("NotFound;Certificate1;Certificate2")]
|
||||
[InlineData("Certificate1;NotFound;Certificate2")]
|
||||
public void Throws_MultipleCertificateNames_Store_KeyNotFound(string certificateNames)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
{
|
||||
["Certificates:Certificate1:Source"] = "Store",
|
||||
["Certificates:Certificate1:Subject"] = "localhost",
|
||||
["Certificates:Certificate1:StoreName"] = "My",
|
||||
["Certificates:Certificate1:StoreLocation"] = "CurrentUser",
|
||||
["Certificates:Certificate2:Source"] = "Store",
|
||||
["Certificates:Certificate2:Subject"] = "example.com",
|
||||
["Certificates:Certificate2:StoreName"] = "Root",
|
||||
["Certificates:Certificate2:StoreLocation"] = "LocalMachine",
|
||||
["TestConfig:Certificate"] = certificateNames
|
||||
})
|
||||
.Build();
|
||||
|
||||
var certificate1 = new X509Certificate2();
|
||||
var certificate2 = new X509Certificate2();
|
||||
|
||||
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||
certificateStoreLoader
|
||||
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||
.Returns(certificate1);
|
||||
certificateStoreLoader
|
||||
.Setup(loader => loader.Load("example.com", "Root", StoreLocation.LocalMachine, It.IsAny<bool>()))
|
||||
.Returns(certificate2);
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
certificateStoreLoader.Object);
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate")));
|
||||
Assert.Equal("No certificate named NotFound found in configuration", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Certificate1;Certificate2", 1)]
|
||||
[InlineData("Certificate2;Certificate1", 1)]
|
||||
|
|
@ -421,6 +325,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
certificateStoreLoader.Object);
|
||||
|
||||
|
|
@ -460,6 +366,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
certificateStoreLoader.Object);
|
||||
|
||||
|
|
@ -475,7 +383,7 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
[InlineData("Certificate1;Certificate2;NotFound")]
|
||||
[InlineData("Certificate1;NotFound;Certificate2")]
|
||||
[InlineData("NotFound;Certificate1;Certificate2")]
|
||||
public void Throws_MultipleCertificateNames_FileAndStore_KeyNotFound(string certificateNames)
|
||||
public void Throws_MultipleCertificateNames_KeyNotFound(string certificateNames)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
|
|
@ -506,17 +414,19 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
certificateStoreLoader.Object);
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate")));
|
||||
Assert.Equal("No certificate named NotFound found in configuration", exception.Message);
|
||||
var exception = Assert.Throws<KeyNotFoundException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate")));
|
||||
Assert.Equal("No certificate named 'NotFound' found in configuration for the current environment.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Certificate1;Certificate2")]
|
||||
[InlineData("Certificate2;Certificate1")]
|
||||
public void Throws_MultipleCertificateNames_FileAndStore_FileNotFound(string certificateNames)
|
||||
public void Throws_MultipleCertificateNames_FileAndStore_FileLoadError(string certificateNames)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
|
|
@ -532,13 +442,12 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
})
|
||||
.Build();
|
||||
|
||||
var exception = new Exception();
|
||||
var storeCertificate = new X509Certificate2();
|
||||
|
||||
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||
certificateFileLoader
|
||||
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||
.Throws(exception);
|
||||
.Throws(new Exception(nameof(Throws_MultipleCertificateNames_FileAndStore_FileLoadError)));
|
||||
|
||||
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||
certificateStoreLoader
|
||||
|
|
@ -547,10 +456,13 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
certificateStoreLoader.Object);
|
||||
|
||||
Assert.Same(exception, Assert.Throws<Exception>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"))));
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate")));
|
||||
Assert.Equal($"Unable to load certificate from file 'Certificate1.pfx'. Error details: '{nameof(Throws_MultipleCertificateNames_FileAndStore_FileLoadError)}'.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -586,6 +498,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
certificateStoreLoader.Object);
|
||||
|
||||
|
|
@ -616,6 +530,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
.Returns(certificate);
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
|
@ -627,7 +543,7 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void Throws_SingleCertificateInline_FileNotFound()
|
||||
public void Throws_SingleCertificateInline_FileLoadError()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
|
|
@ -638,19 +554,20 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
})
|
||||
.Build();
|
||||
|
||||
var exception = new Exception();
|
||||
|
||||
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||
certificateFileLoader
|
||||
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||
.Throws(exception);
|
||||
.Throws(new Exception(nameof(Throws_SingleCertificateInline_FileLoadError)));
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
||||
Assert.Same(exception, Assert.Throws<Exception>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"))));
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate")));
|
||||
Assert.Equal($"Unable to load certificate from file 'Certificate1.pfx'. Error details: '{nameof(Throws_SingleCertificateInline_FileLoadError)}'.", exception.Message);
|
||||
certificateFileLoader.VerifyAll();
|
||||
}
|
||||
|
||||
|
|
@ -675,6 +592,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
.Returns(certificate);
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
certificateStoreLoader.Object);
|
||||
|
|
@ -701,6 +620,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
certificateStoreLoader.Object);
|
||||
|
|
@ -737,6 +658,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
.Returns(certificate2);
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
|
@ -749,7 +672,7 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void Throws_MultipleCertificatesInline_File_FileNotFound()
|
||||
public void Throws_MultipleCertificatesInline_File_FileLoadError()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
|
|
@ -764,7 +687,6 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
.Build();
|
||||
|
||||
var certificate1 = new X509Certificate2();
|
||||
var exception = new Exception();
|
||||
|
||||
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||
certificateFileLoader
|
||||
|
|
@ -772,14 +694,17 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
.Returns(certificate1);
|
||||
certificateFileLoader
|
||||
.Setup(loader => loader.Load("Certificate2.pfx", "Password2", It.IsAny<X509KeyStorageFlags>()))
|
||||
.Throws(exception);
|
||||
.Throws(new Exception(nameof(Throws_MultipleCertificatesInline_File_FileLoadError)));
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
||||
Assert.Same(exception, Assert.Throws<Exception>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificates"))));
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificates")));
|
||||
Assert.Equal($"Unable to load certificate from file 'Certificate2.pfx'. Error details: '{nameof(Throws_MultipleCertificatesInline_File_FileLoadError)}'.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -811,6 +736,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
.Returns(certificate2);
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
certificateStoreLoader.Object);
|
||||
|
|
@ -851,6 +778,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
.Returns(certificate);
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
certificateStoreLoader.Object);
|
||||
|
|
@ -908,6 +837,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
.Returns(storeCertificate2);
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
certificateStoreLoader.Object);
|
||||
|
|
@ -922,7 +853,7 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void Throws_MultipleCertificatesInline_FileAndStore_FileNotFound()
|
||||
public void Throws_MultipleCertificatesInline_FileAndStore_FileLoadError()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
|
|
@ -937,13 +868,12 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
})
|
||||
.Build();
|
||||
|
||||
var exception = new Exception();
|
||||
var certificate = new X509Certificate2();
|
||||
|
||||
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||
certificateFileLoader
|
||||
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||
.Throws(exception);
|
||||
.Throws(new Exception(nameof(Throws_MultipleCertificatesInline_FileAndStore_FileLoadError)));
|
||||
|
||||
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||
certificateStoreLoader
|
||||
|
|
@ -951,11 +881,14 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
.Returns(certificate);
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
certificateStoreLoader.Object);
|
||||
|
||||
Assert.Same(exception, Assert.Throws<Exception>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificates"))));
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificates")));
|
||||
Assert.Equal($"Unable to load certificate from file 'Certificate1.pfx'. Error details: '{nameof(Throws_MultipleCertificatesInline_FileAndStore_FileLoadError)}'.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -987,6 +920,8 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
.Returns<X509Certificate>(null);
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
certificateFileLoader.Object,
|
||||
certificateStoreLoader.Object);
|
||||
|
|
@ -995,5 +930,115 @@ namespace Microsoft.AspNetCore.FunctionalTests
|
|||
Assert.Equal(1, loadedCertificates.Count());
|
||||
Assert.Same(certificate, loadedCertificates.ElementAt(0));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Development")]
|
||||
[InlineData("Production")]
|
||||
public void IncludesEnvironmentNameInExceptionWhenAvailable(string environmentName)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
{
|
||||
["TestConfig:Certificate"] = "Certificate1"
|
||||
})
|
||||
.Build();
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
environmentName,
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
||||
var exception = Assert.Throws<KeyNotFoundException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate")));
|
||||
Assert.Equal($"No certificate named 'Certificate1' found in configuration for the current environment ({environmentName}).", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoesNotIncludeEnvironmentNameInExceptionWhenNotAvailable()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
{
|
||||
["TestConfig:Certificate"] = "Certificate1"
|
||||
})
|
||||
.Build();
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
configuration.GetSection("Certificates"),
|
||||
null,
|
||||
null,
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
||||
var exception = Assert.Throws<KeyNotFoundException>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate")));
|
||||
Assert.Equal("No certificate named 'Certificate1' found in configuration for the current environment.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WarningLoggedWhenCertificateNotFoundInStore()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string>
|
||||
{
|
||||
["TestConfig:Certificates:Certificate1:Source"] = "Store",
|
||||
["TestConfig:Certificates:Certificate1:Subject"] = "localhost",
|
||||
["TestConfig:Certificates:Certificate1:StoreName"] = "My",
|
||||
["TestConfig:Certificates:Certificate1:StoreLocation"] = "CurrentUser",
|
||||
})
|
||||
.Build();
|
||||
|
||||
var loggerFactory = new Mock<ILoggerFactory>();
|
||||
var logger = new MockLogger();
|
||||
|
||||
loggerFactory
|
||||
.Setup(factory => factory.CreateLogger("Microsoft.AspNetCore.CertificateLoader"))
|
||||
.Returns(logger);
|
||||
|
||||
var certificateLoader = new CertificateLoader(
|
||||
null,
|
||||
loggerFactory.Object,
|
||||
null,
|
||||
Mock.Of<ICertificateFileLoader>(),
|
||||
Mock.Of<ICertificateStoreLoader>());
|
||||
|
||||
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificates"));
|
||||
Assert.Equal(0, loadedCertificates.Count());
|
||||
Assert.Single(logger.LogMessages, logMessage =>
|
||||
logMessage.LogLevel == LogLevel.Warning &&
|
||||
logMessage.Message == "Unable to find a matching certificate for subject 'localhost' in store 'My' in 'CurrentUser'.");
|
||||
}
|
||||
|
||||
private class MockLogger : ILogger
|
||||
{
|
||||
private readonly List<LogMessage> _logMessages = new List<LogMessage>();
|
||||
|
||||
public IEnumerable<LogMessage> LogMessages => _logMessages;
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
_logMessages.Add(new LogMessage
|
||||
{
|
||||
LogLevel = logLevel,
|
||||
Message = formatter(state, exception)
|
||||
});
|
||||
}
|
||||
|
||||
public class LogMessage
|
||||
{
|
||||
public LogLevel LogLevel { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue