Support more certificate loading scenarios (#69).
This commit is contained in:
parent
a5a9b6adab
commit
605aeddc22
|
|
@ -40,11 +40,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartRequestDelegateUrlApp"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateDefaultBuilderApp", "test\TestSites\CreateDefaultBuilderApp\CreateDefaultBuilderApp.csproj", "{79CF58CE-B020-45D8-BDB5-2D8036BEAD14}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateDefaultBuilderApp", "test\TestSites\CreateDefaultBuilderApp\CreateDefaultBuilderApp.csproj", "{79CF58CE-B020-45D8-BDB5-2D8036BEAD14}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestArtifacts", "TestArtifacts", "{9BBA7A0A-109A-4AC8-B6EF-A52EA7CF1D90}"
|
|
||||||
ProjectSection(SolutionItems) = preProject
|
|
||||||
test\TestArtifacts\testCert.pfx = test\TestArtifacts\testCert.pfx
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-archive", "src\dotnet-archive\dotnet-archive.csproj", "{AE4216BF-D471-471B-82F3-6B6D004F7D17}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-archive", "src\dotnet-archive\dotnet-archive.csproj", "{AE4216BF-D471-471B-82F3-6B6D004F7D17}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Archive", "src\Microsoft.DotNet.Archive\Microsoft.DotNet.Archive.csproj", "{302400A0-98BB-4C04-88D4-C32DC2D4B945}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Archive", "src\Microsoft.DotNet.Archive\Microsoft.DotNet.Archive.csproj", "{302400A0-98BB-4C04-88D4-C32DC2D4B945}"
|
||||||
|
|
@ -121,7 +116,6 @@ Global
|
||||||
{3A85FA52-F601-422E-A42E-9F187DB28492} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
|
{3A85FA52-F601-422E-A42E-9F187DB28492} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
|
||||||
{401C741B-6C7C-4E08-9F09-C3D43D22C0DE} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
|
{401C741B-6C7C-4E08-9F09-C3D43D22C0DE} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
|
||||||
{79CF58CE-B020-45D8-BDB5-2D8036BEAD14} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
|
{79CF58CE-B020-45D8-BDB5-2D8036BEAD14} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
|
||||||
{9BBA7A0A-109A-4AC8-B6EF-A52EA7CF1D90} = {9E49B5B9-9E72-42FB-B684-90CA1B1BCF9C}
|
|
||||||
{AE4216BF-D471-471B-82F3-6B6D004F7D17} = {ED834E68-51C3-4ADE-ACC8-6BA6D4207C09}
|
{AE4216BF-D471-471B-82F3-6B6D004F7D17} = {ED834E68-51C3-4ADE-ACC8-6BA6D4207C09}
|
||||||
{302400A0-98BB-4C04-88D4-C32DC2D4B945} = {ED834E68-51C3-4ADE-ACC8-6BA6D4207C09}
|
{302400A0-98BB-4C04-88D4-C32DC2D4B945} = {ED834E68-51C3-4ADE-ACC8-6BA6D4207C09}
|
||||||
{67E4C92F-6D12-4C52-BB79-B8D11BFC6B82} = {ED834E68-51C3-4ADE-ACC8-6BA6D4207C09}
|
{67E4C92F-6D12-4C52-BB79-B8D11BFC6B82} = {ED834E68-51C3-4ADE-ACC8-6BA6D4207C09}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
<AspNetCoreIdentityServiceVersion>1.0.0-*</AspNetCoreIdentityServiceVersion>
|
<AspNetCoreIdentityServiceVersion>1.0.0-*</AspNetCoreIdentityServiceVersion>
|
||||||
<CoreFxVersion>4.3.0</CoreFxVersion>
|
<CoreFxVersion>4.3.0</CoreFxVersion>
|
||||||
<InternalAspNetCoreSdkVersion>2.0.0-*</InternalAspNetCoreSdkVersion>
|
<InternalAspNetCoreSdkVersion>2.0.0-*</InternalAspNetCoreSdkVersion>
|
||||||
|
<MoqVersion>4.7.1</MoqVersion>
|
||||||
<NewtonsoftJsonVersion>10.0.1</NewtonsoftJsonVersion>
|
<NewtonsoftJsonVersion>10.0.1</NewtonsoftJsonVersion>
|
||||||
<TestSdkVersion>15.0.0</TestSdkVersion>
|
<TestSdkVersion>15.0.0</TestSdkVersion>
|
||||||
<XunitVersion>2.2.0</XunitVersion>
|
<XunitVersion>2.2.0</XunitVersion>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
"Source": "File",
|
"Source": "File",
|
||||||
"Path": "testCert.pfx",
|
"Path": "testCert.pfx",
|
||||||
// TODO: remove when dotnet user-secrets is working again
|
// TODO: remove when dotnet user-secrets is working again
|
||||||
"Password": "testPassword",
|
"Password": "testPassword"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Add testCert.pfx to the current user's certificate store to enable this scenario.
|
// Add testCert.pfx to the current user's certificate store to enable this scenario.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
internal class CertificateFileLoader : ICertificateFileLoader
|
||||||
|
{
|
||||||
|
public X509Certificate2 Load(string path, string password, X509KeyStorageFlags flags)
|
||||||
|
{
|
||||||
|
return new X509Certificate2(path, password, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,49 +12,125 @@ namespace Microsoft.AspNetCore
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A helper class to load certificates from files and certificate stores based on <seealso cref="IConfiguration"/> data.
|
/// A helper class to load certificates from files and certificate stores based on <seealso cref="IConfiguration"/> data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CertificateLoader
|
public class CertificateLoader
|
||||||
{
|
{
|
||||||
|
private readonly IConfiguration _certificatesConfiguration;
|
||||||
|
private readonly ICertificateFileLoader _certificateFileLoader;
|
||||||
|
private readonly ICertificateStoreLoader _certificateStoreLoader;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads one or more certificates from a single source.
|
/// Creates a new instance of <see cref="CertificateLoader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="certificateConfiguration">An <seealso cref="IConfiguration"/> with information about a certificate source.</param>
|
public CertificateLoader()
|
||||||
/// <param name="password">The certificate password, in case it's being loaded from a file.</param>
|
: this(null)
|
||||||
/// <returns>The loaded certificates.</returns>
|
|
||||||
public static X509Certificate2 Load(IConfiguration certificateConfiguration, string password)
|
|
||||||
{
|
{
|
||||||
var sourceKind = certificateConfiguration.GetValue<string>("Source");
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
public CertificateLoader(IConfiguration certificatesConfiguration)
|
||||||
|
: this(certificatesConfiguration, new CertificateFileLoader(), new CertificateStoreLoader())
|
||||||
|
{
|
||||||
|
_certificatesConfiguration = certificatesConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal CertificateLoader(IConfiguration certificatesConfiguration, ICertificateFileLoader certificateFileLoader, ICertificateStoreLoader certificateStoreLoader)
|
||||||
|
{
|
||||||
|
_certificatesConfiguration = certificatesConfiguration;
|
||||||
|
_certificateFileLoader = certificateFileLoader;
|
||||||
|
_certificateStoreLoader = certificateStoreLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads one or more certificates based on the information found in a configuration section.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="certificateConfiguration">A configuration section containing either a string value referencing certificates
|
||||||
|
/// by name, or one or more inline certificate specifications.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>One or more loaded certificates.</returns>
|
||||||
|
public IEnumerable<X509Certificate2> Load(IConfigurationSection certificateConfiguration)
|
||||||
|
{
|
||||||
|
var certificateNames = certificateConfiguration.Value;
|
||||||
|
var certificates = new List<X509Certificate2>();
|
||||||
|
|
||||||
|
if (certificateNames != null)
|
||||||
|
{
|
||||||
|
foreach (var certificateName in certificateNames.Split(';'))
|
||||||
|
{
|
||||||
|
var certificate = LoadSingle(certificateName);
|
||||||
|
if (certificate != null)
|
||||||
|
{
|
||||||
|
certificates.Add(certificate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (certificateConfiguration["Source"] != null)
|
||||||
|
{
|
||||||
|
var certificate = LoadSingle(certificateConfiguration);
|
||||||
|
if (certificate != null)
|
||||||
|
{
|
||||||
|
certificates.Add(certificate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
certificates.AddRange(LoadMultiple(certificateConfiguration));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a certificate by name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="certificateName">The certificate name.</param>
|
||||||
|
/// <returns>The loaded certificate</returns>
|
||||||
|
/// <remarks>This method only works if the <see cref="CertificateLoader"/> instance was constructed with
|
||||||
|
/// a reference to an <see cref="IConfiguration"/> instance containing named certificates.
|
||||||
|
/// </remarks>
|
||||||
|
private X509Certificate2 LoadSingle(string certificateName)
|
||||||
|
{
|
||||||
|
var certificateConfiguration = _certificatesConfiguration?.GetSection(certificateName);
|
||||||
|
|
||||||
|
if (!certificateConfiguration.Exists())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"No certificate named {certificateName} found in configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadSingle(certificateConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private X509Certificate2 LoadSingle(IConfigurationSection certificateConfiguration)
|
||||||
|
{
|
||||||
|
var sourceKind = certificateConfiguration["Source"];
|
||||||
|
|
||||||
CertificateSource certificateSource;
|
CertificateSource certificateSource;
|
||||||
switch (sourceKind.ToLowerInvariant())
|
switch (sourceKind.ToLowerInvariant())
|
||||||
{
|
{
|
||||||
case "file":
|
case "file":
|
||||||
certificateSource = new CertificateFileSource(password);
|
certificateSource = new CertificateFileSource(_certificateFileLoader);
|
||||||
break;
|
break;
|
||||||
case "store":
|
case "store":
|
||||||
certificateSource = new CertificateStoreSource();
|
certificateSource = new CertificateStoreSource(_certificateStoreLoader);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException($"Invalid certificate source kind: {sourceKind}");
|
throw new InvalidOperationException($"Invalid certificate source kind: {sourceKind}");
|
||||||
}
|
}
|
||||||
|
|
||||||
certificateConfiguration.Bind(certificateSource);
|
certificateConfiguration.Bind(certificateSource);
|
||||||
|
|
||||||
return certificateSource.Load();
|
return certificateSource.Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private IEnumerable<X509Certificate2> LoadMultiple(IConfigurationSection certificatesConfiguration)
|
||||||
/// Loads all certificates specified in an <seealso cref="IConfiguration"/>.
|
=> certificatesConfiguration.GetChildren()
|
||||||
/// </summary>
|
.Select(LoadSingle)
|
||||||
/// <param name="configurationRoot">The root <seealso cref="IConfiguration"/>.</param>
|
.Where(c => c != null);
|
||||||
/// <returns>
|
|
||||||
/// A dictionary mapping certificate names to loaded certificates.
|
|
||||||
/// </returns>
|
|
||||||
public static Dictionary<string, X509Certificate2> LoadAll(IConfiguration configurationRoot)
|
|
||||||
{
|
|
||||||
return configurationRoot.GetSection("Certificates").GetChildren()
|
|
||||||
.ToDictionary(
|
|
||||||
certificateSource => certificateSource.Key,
|
|
||||||
certificateSource => Load(certificateSource, certificateSource["Password"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class CertificateSource
|
private abstract class CertificateSource
|
||||||
{
|
{
|
||||||
|
|
@ -65,22 +141,24 @@ namespace Microsoft.AspNetCore
|
||||||
|
|
||||||
private class CertificateFileSource : CertificateSource
|
private class CertificateFileSource : CertificateSource
|
||||||
{
|
{
|
||||||
private readonly string _password;
|
private ICertificateFileLoader _certificateFileLoader;
|
||||||
|
|
||||||
public CertificateFileSource(string password)
|
public CertificateFileSource(ICertificateFileLoader certificateFileLoader)
|
||||||
{
|
{
|
||||||
_password = password;
|
_certificateFileLoader = certificateFileLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
public override X509Certificate2 Load()
|
public override X509Certificate2 Load()
|
||||||
{
|
{
|
||||||
var certificate = TryLoad(X509KeyStorageFlags.DefaultKeySet, out var error)
|
var certificate = TryLoad(X509KeyStorageFlags.DefaultKeySet, out var error)
|
||||||
?? TryLoad(X509KeyStorageFlags.UserKeySet, out error)
|
?? TryLoad(X509KeyStorageFlags.UserKeySet, out error)
|
||||||
#if NETCOREAPP2_0
|
#if NETCOREAPP2_0
|
||||||
?? TryLoad(X509KeyStorageFlags.EphemeralKeySet, out error)
|
?? TryLoad(X509KeyStorageFlags.EphemeralKeySet, out error)
|
||||||
#endif
|
#endif
|
||||||
;
|
;
|
||||||
|
|
||||||
if (error != null)
|
if (error != null)
|
||||||
|
|
@ -95,7 +173,7 @@ namespace Microsoft.AspNetCore
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var loadedCertificate = new X509Certificate2(Path, _password, flags);
|
var loadedCertificate = _certificateFileLoader.Load(Path, Password, flags);
|
||||||
exception = null;
|
exception = null;
|
||||||
return loadedCertificate;
|
return loadedCertificate;
|
||||||
}
|
}
|
||||||
|
|
@ -109,6 +187,13 @@ namespace Microsoft.AspNetCore
|
||||||
|
|
||||||
private class CertificateStoreSource : CertificateSource
|
private class CertificateStoreSource : CertificateSource
|
||||||
{
|
{
|
||||||
|
private readonly ICertificateStoreLoader _certificateStoreLoader;
|
||||||
|
|
||||||
|
public CertificateStoreSource(ICertificateStoreLoader certificateStoreLoader)
|
||||||
|
{
|
||||||
|
_certificateStoreLoader = certificateStoreLoader;
|
||||||
|
}
|
||||||
|
|
||||||
public string Subject { get; set; }
|
public string Subject { get; set; }
|
||||||
public string StoreName { get; set; }
|
public string StoreName { get; set; }
|
||||||
public string StoreLocation { get; set; }
|
public string StoreLocation { get; set; }
|
||||||
|
|
@ -121,52 +206,7 @@ namespace Microsoft.AspNetCore
|
||||||
throw new InvalidOperationException($"Invalid store location: {StoreLocation}");
|
throw new InvalidOperationException($"Invalid store location: {StoreLocation}");
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var store = new X509Store(StoreName, storeLocation))
|
return _certificateStoreLoader.Load(Subject, StoreName, storeLocation, !AllowInvalid);
|
||||||
{
|
|
||||||
X509Certificate2Collection storeCertificates = null;
|
|
||||||
X509Certificate2Collection foundCertificates = null;
|
|
||||||
X509Certificate2 foundCertificate = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
store.Open(OpenFlags.ReadOnly);
|
|
||||||
storeCertificates = store.Certificates;
|
|
||||||
foundCertificates = storeCertificates.Find(X509FindType.FindBySubjectDistinguishedName, Subject, validOnly: !AllowInvalid);
|
|
||||||
foundCertificate = foundCertificates
|
|
||||||
.OfType<X509Certificate2>()
|
|
||||||
.OrderByDescending(certificate => certificate.NotAfter)
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (foundCertificate == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"No certificate found for {Subject} in store {StoreName} in {StoreLocation}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return foundCertificate;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (foundCertificate != null)
|
|
||||||
{
|
|
||||||
storeCertificates.Remove(foundCertificate);
|
|
||||||
foundCertificates.Remove(foundCertificate);
|
|
||||||
}
|
|
||||||
|
|
||||||
DisposeCertificates(storeCertificates);
|
|
||||||
DisposeCertificates(foundCertificates);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisposeCertificates(X509Certificate2Collection certificates)
|
|
||||||
{
|
|
||||||
if (certificates != null)
|
|
||||||
{
|
|
||||||
foreach (var certificate in certificates)
|
|
||||||
{
|
|
||||||
certificate.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore
|
||||||
|
{
|
||||||
|
internal class CertificateStoreLoader : ICertificateStoreLoader
|
||||||
|
{
|
||||||
|
public X509Certificate2 Load(string subject, string storeName, StoreLocation storeLocation, bool validOnly)
|
||||||
|
{
|
||||||
|
using (var store = new X509Store(storeName, storeLocation))
|
||||||
|
{
|
||||||
|
X509Certificate2Collection storeCertificates = null;
|
||||||
|
X509Certificate2Collection foundCertificates = null;
|
||||||
|
X509Certificate2 foundCertificate = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
store.Open(OpenFlags.ReadOnly);
|
||||||
|
storeCertificates = store.Certificates;
|
||||||
|
foundCertificates = storeCertificates.Find(X509FindType.FindBySubjectDistinguishedName, subject, validOnly);
|
||||||
|
foundCertificate = foundCertificates
|
||||||
|
.OfType<X509Certificate2>()
|
||||||
|
.OrderByDescending(certificate => certificate.NotAfter)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
return foundCertificate;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (foundCertificate != null)
|
||||||
|
{
|
||||||
|
storeCertificates.Remove(foundCertificate);
|
||||||
|
foundCertificates.Remove(foundCertificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposeCertificates(storeCertificates);
|
||||||
|
DisposeCertificates(foundCertificates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeCertificates(X509Certificate2Collection certificates)
|
||||||
|
{
|
||||||
|
if (certificates != null)
|
||||||
|
{
|
||||||
|
foreach (var certificate in certificates)
|
||||||
|
{
|
||||||
|
certificate.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
internal interface ICertificateFileLoader
|
||||||
|
{
|
||||||
|
X509Certificate2 Load(string path, string password, X509KeyStorageFlags flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
internal interface ICertificateStoreLoader
|
||||||
|
{
|
||||||
|
X509Certificate2 Load(string subject, string storeName, StoreLocation storeLocation, bool validOnly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,39 +2,24 @@
|
||||||
// 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.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore
|
namespace Microsoft.AspNetCore
|
||||||
{
|
{
|
||||||
/// <summary>
|
internal class KestrelServerOptionsSetup : IConfigureOptions<KestrelServerOptions>
|
||||||
/// Binds Kestrel configuration.
|
|
||||||
/// </summary>
|
|
||||||
public class KestrelServerOptionsSetup : IConfigureOptions<KestrelServerOptions>
|
|
||||||
{
|
{
|
||||||
private readonly IConfiguration _configurationRoot;
|
private readonly IConfiguration _configurationRoot;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of <see cref="KestrelServerOptionsSetup"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="configurationRoot">The root <seealso cref="IConfiguration"/>.</param>
|
|
||||||
public KestrelServerOptionsSetup(IConfiguration configurationRoot)
|
public KestrelServerOptionsSetup(IConfiguration configurationRoot)
|
||||||
{
|
{
|
||||||
_configurationRoot = configurationRoot;
|
_configurationRoot = configurationRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configures a <seealso cref="KestrelServerOptions"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="options">The <seealso cref="KestrelServerOptions"/> to configure.</param>
|
|
||||||
public void Configure(KestrelServerOptions options)
|
public void Configure(KestrelServerOptions options)
|
||||||
{
|
{
|
||||||
BindConfiguration(options);
|
BindConfiguration(options);
|
||||||
|
|
@ -42,60 +27,46 @@ namespace Microsoft.AspNetCore
|
||||||
|
|
||||||
private void BindConfiguration(KestrelServerOptions options)
|
private void BindConfiguration(KestrelServerOptions options)
|
||||||
{
|
{
|
||||||
var certificates = CertificateLoader.LoadAll(_configurationRoot);
|
var certificateLoader = new CertificateLoader(_configurationRoot.GetSection("Certificates"));
|
||||||
var endPoints = _configurationRoot.GetSection("Kestrel:EndPoints");
|
|
||||||
|
|
||||||
foreach (var endPoint in endPoints.GetChildren())
|
foreach (var endPoint in _configurationRoot.GetSection("Kestrel:EndPoints").GetChildren())
|
||||||
{
|
{
|
||||||
BindEndPoint(options, endPoint, certificates);
|
BindEndPoint(options, endPoint, certificateLoader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BindEndPoint(
|
private void BindEndPoint(
|
||||||
KestrelServerOptions options,
|
KestrelServerOptions options,
|
||||||
IConfigurationSection endPoint,
|
IConfigurationSection endPoint,
|
||||||
Dictionary<string, X509Certificate2> certificates)
|
CertificateLoader certificateLoader)
|
||||||
{
|
{
|
||||||
var addressValue = endPoint.GetValue<string>("Address");
|
var configAddress = endPoint.GetValue<string>("Address");
|
||||||
var portValue = endPoint.GetValue<string>("Port");
|
var configPort = endPoint.GetValue<string>("Port");
|
||||||
|
|
||||||
IPAddress address;
|
if (!IPAddress.TryParse(configAddress, out var address))
|
||||||
if (!IPAddress.TryParse(addressValue, out address))
|
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Invalid IP address: {addressValue}");
|
throw new InvalidOperationException($"Invalid IP address in configuration: {configAddress}");
|
||||||
}
|
}
|
||||||
|
|
||||||
int port;
|
if (!int.TryParse(configPort, out var port))
|
||||||
if (!int.TryParse(portValue, out port))
|
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Invalid port: {portValue}");
|
throw new InvalidOperationException($"Invalid port in configuration: {configPort}");
|
||||||
}
|
}
|
||||||
|
|
||||||
options.Listen(address, port, listenOptions =>
|
options.Listen(address, port, listenOptions =>
|
||||||
{
|
{
|
||||||
var certificateName = endPoint.GetValue<string>("Certificate");
|
var certificateConfig = endPoint.GetSection("Certificate");
|
||||||
|
|
||||||
X509Certificate2 endPointCertificate = null;
|
if (certificateConfig.Exists())
|
||||||
if (certificateName != null)
|
|
||||||
{
|
{
|
||||||
if (!certificates.TryGetValue(certificateName, out endPointCertificate))
|
var certificate = certificateLoader.Load(certificateConfig).FirstOrDefault();
|
||||||
|
|
||||||
|
if (certificate == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"No certificate named {certificateName} found in configuration");
|
throw new InvalidOperationException($"Unable to load certificate for endpoint '{endPoint.Key}'");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var certificate = endPoint.GetSection("Certificate");
|
|
||||||
|
|
||||||
if (certificate.GetChildren().Any())
|
listenOptions.UseHttps(certificate);
|
||||||
{
|
|
||||||
endPointCertificate = CertificateLoader.Load(certificate, certificate["Password"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endPointCertificate != null)
|
|
||||||
{
|
|
||||||
listenOptions.UseHttps(endPointCertificate);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<Import Project="..\..\build\common.props" />
|
<Import Project="..\..\build\common.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard1.3</TargetFramework>
|
<TargetFrameworks>netstandard1.3;netcoreapp2.0</TargetFrameworks>
|
||||||
<PackageTags>aspnetcore</PackageTags>
|
<PackageTags>aspnetcore</PackageTags>
|
||||||
<Description>Microsoft.AspNetCore</Description>
|
<Description>Microsoft.AspNetCore</Description>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
// 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.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||||
|
|
@ -0,0 +1,999 @@
|
||||||
|
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Xunit;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.FunctionalTests
|
||||||
|
{
|
||||||
|
public class CertificateLoaderTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Loads_SingleCertificateName_File()
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["Certificates:Certificate1:Source"] = "File",
|
||||||
|
["Certificates:Certificate1:Path"] = "Certificate1.pfx",
|
||||||
|
["Certificates:Certificate1:Password"] = "Password1",
|
||||||
|
["TestConfig:Certificate"] = "Certificate1"
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificate = new X509Certificate2();
|
||||||
|
|
||||||
|
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||||
|
certificateFileLoader
|
||||||
|
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||||
|
.Returns(certificate);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
configuration.GetSection("Certificates"),
|
||||||
|
certificateFileLoader.Object,
|
||||||
|
Mock.Of<ICertificateStoreLoader>());
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"));
|
||||||
|
Assert.Equal(1, loadedCertificates.Count());
|
||||||
|
Assert.Same(certificate, loadedCertificates.ElementAt(0));
|
||||||
|
certificateFileLoader.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Throws_SingleCertificateName_File_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"
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificate = new X509Certificate2();
|
||||||
|
|
||||||
|
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 Throws_SingleCertificateName_File_FileNotFound()
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["Certificates:Certificate1:Source"] = "File",
|
||||||
|
["Certificates:Certificate1:Path"] = "Certificate1.pfx",
|
||||||
|
["Certificates:Certificate1:Password"] = "Password1",
|
||||||
|
["TestConfig:Certificate"] = "Certificate1"
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var exception = new Exception();
|
||||||
|
|
||||||
|
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||||
|
certificateFileLoader
|
||||||
|
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||||
|
.Callback(() => throw 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"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Loads_SingleCertificateName_Store()
|
||||||
|
{
|
||||||
|
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"] = "Certificate1"
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificate = new X509Certificate2();
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||||
|
.Returns(certificate);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
configuration.GetSection("Certificates"),
|
||||||
|
Mock.Of<ICertificateFileLoader>(),
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"));
|
||||||
|
Assert.Equal(1, loadedCertificates.Count());
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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"] = "Certificate1"
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||||
|
.Returns<X509Certificate2>(null);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
configuration.GetSection("Certificates"),
|
||||||
|
Mock.Of<ICertificateFileLoader>(),
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"));
|
||||||
|
Assert.Equal(0, loadedCertificates.Count());
|
||||||
|
certificateStoreLoader.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Loads_MultipleCertificateNames_File()
|
||||||
|
{
|
||||||
|
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"] = "Certificate1;Certificate2"
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificate1 = new X509Certificate2();
|
||||||
|
var certificate2 = new X509Certificate2();
|
||||||
|
|
||||||
|
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>()))
|
||||||
|
.Returns(certificate2);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
configuration.GetSection("Certificates"),
|
||||||
|
certificateFileLoader.Object,
|
||||||
|
Mock.Of<ICertificateStoreLoader>());
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"));
|
||||||
|
Assert.Equal(2, loadedCertificates.Count());
|
||||||
|
Assert.Same(certificate1, loadedCertificates.ElementAt(0));
|
||||||
|
Assert.Same(certificate2, loadedCertificates.ElementAt(1));
|
||||||
|
certificateFileLoader.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[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)
|
||||||
|
{
|
||||||
|
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 certificate2 = new X509Certificate2();
|
||||||
|
|
||||||
|
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>()))
|
||||||
|
.Returns(certificate2);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
configuration.GetSection("Certificates"),
|
||||||
|
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"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Loads_MultipleCertificateNames_Store()
|
||||||
|
{
|
||||||
|
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"] = "Certificate1;Certificate2"
|
||||||
|
})
|
||||||
|
.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 loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"));
|
||||||
|
Assert.Equal(2, loadedCertificates.Count());
|
||||||
|
Assert.Same(certificate1, loadedCertificates.ElementAt(0));
|
||||||
|
Assert.Same(certificate2, loadedCertificates.ElementAt(1));
|
||||||
|
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)]
|
||||||
|
[InlineData("Certificate1;Certificate2;Certificate3", 1)]
|
||||||
|
[InlineData("Certificate2;Certificate3", 0)]
|
||||||
|
[InlineData("Certificate2;Certificate3;Certificate1", 1)]
|
||||||
|
public void ReturnsNull_MultipleCertificateNames_Store_NotFoundInStore(string certificateNames, int expectedFoundCertificates)
|
||||||
|
{
|
||||||
|
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",
|
||||||
|
["Certificates:Certificate3:Source"] = "Store",
|
||||||
|
["Certificates:Certificate3:Subject"] = "notfound.com",
|
||||||
|
["Certificates:Certificate3:StoreName"] = "Root",
|
||||||
|
["Certificates:Certificate3:StoreLocation"] = "LocalMachine",
|
||||||
|
["TestConfig:Certificate"] = certificateNames
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||||
|
.Returns(new X509Certificate2());
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("example.com", "Root", StoreLocation.LocalMachine, It.IsAny<bool>()))
|
||||||
|
.Returns<X509Certificate2>(null);
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("notfound.com", "Root", StoreLocation.LocalMachine, It.IsAny<bool>()))
|
||||||
|
.Returns<X509Certificate2>(null);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
configuration.GetSection("Certificates"),
|
||||||
|
Mock.Of<ICertificateFileLoader>(),
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"));
|
||||||
|
Assert.Equal(expectedFoundCertificates, loadedCertificates.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Loads_MultipleCertificateNames_FileAndStore()
|
||||||
|
{
|
||||||
|
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"] = "Store",
|
||||||
|
["Certificates:Certificate2:Subject"] = "localhost",
|
||||||
|
["Certificates:Certificate2:StoreName"] = "My",
|
||||||
|
["Certificates:Certificate2:StoreLocation"] = "CurrentUser",
|
||||||
|
["TestConfig:Certificate"] = "Certificate1;Certificate2"
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var fileCertificate = new X509Certificate2();
|
||||||
|
var storeCertificate = new X509Certificate2();
|
||||||
|
|
||||||
|
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||||
|
certificateFileLoader
|
||||||
|
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||||
|
.Returns(fileCertificate);
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||||
|
.Returns(storeCertificate);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
configuration.GetSection("Certificates"),
|
||||||
|
certificateFileLoader.Object,
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"));
|
||||||
|
Assert.Equal(2, loadedCertificates.Count());
|
||||||
|
Assert.Same(fileCertificate, loadedCertificates.ElementAt(0));
|
||||||
|
Assert.Same(storeCertificate, loadedCertificates.ElementAt(1));
|
||||||
|
certificateFileLoader.VerifyAll();
|
||||||
|
certificateStoreLoader.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Certificate1;Certificate2;NotFound")]
|
||||||
|
[InlineData("Certificate1;NotFound;Certificate2")]
|
||||||
|
[InlineData("NotFound;Certificate1;Certificate2")]
|
||||||
|
public void Throws_MultipleCertificateNames_FileAndStore_KeyNotFound(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"] = "Store",
|
||||||
|
["Certificates:Certificate2:Subject"] = "localhost",
|
||||||
|
["Certificates:Certificate2:StoreName"] = "My",
|
||||||
|
["Certificates:Certificate2:StoreLocation"] = "CurrentUser",
|
||||||
|
["TestConfig:Certificate"] = certificateNames
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var fileCertificate = new X509Certificate2();
|
||||||
|
var storeCertificate = new X509Certificate2();
|
||||||
|
|
||||||
|
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||||
|
certificateFileLoader
|
||||||
|
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||||
|
.Returns(fileCertificate);
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||||
|
.Returns(storeCertificate);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
configuration.GetSection("Certificates"),
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Certificate1;Certificate2")]
|
||||||
|
[InlineData("Certificate2;Certificate1")]
|
||||||
|
public void Throws_MultipleCertificateNames_FileAndStore_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"] = "Store",
|
||||||
|
["Certificates:Certificate2:Subject"] = "localhost",
|
||||||
|
["Certificates:Certificate2:StoreName"] = "My",
|
||||||
|
["Certificates:Certificate2:StoreLocation"] = "CurrentUser",
|
||||||
|
["TestConfig:Certificate"] = certificateNames
|
||||||
|
})
|
||||||
|
.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);
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||||
|
.Returns(storeCertificate);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
configuration.GetSection("Certificates"),
|
||||||
|
certificateFileLoader.Object,
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
Assert.Same(exception, Assert.Throws<Exception>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Certificate1;Certificate2")]
|
||||||
|
[InlineData("Certificate2;Certificate1")]
|
||||||
|
public void ReturnsNull_MultipleCertificateNames_FileAndStore_NotFoundInStore(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"] = "Store",
|
||||||
|
["Certificates:Certificate2:Subject"] = "localhost",
|
||||||
|
["Certificates:Certificate2:StoreName"] = "My",
|
||||||
|
["Certificates:Certificate2:StoreLocation"] = "CurrentUser",
|
||||||
|
["TestConfig:Certificate"] = "Certificate1;Certificate2"
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificate = new X509Certificate2();
|
||||||
|
|
||||||
|
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||||
|
certificateFileLoader
|
||||||
|
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||||
|
.Returns(certificate);
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||||
|
.Returns<X509Certificate2>(null);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
configuration.GetSection("Certificates"),
|
||||||
|
certificateFileLoader.Object,
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"));
|
||||||
|
Assert.Equal(1, loadedCertificates.Count());
|
||||||
|
Assert.Same(certificate, loadedCertificates.ElementAt(0));
|
||||||
|
certificateFileLoader.VerifyAll();
|
||||||
|
certificateStoreLoader.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Loads_SingleCertificateInline_File()
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["TestConfig:Certificate:Source"] = "File",
|
||||||
|
["TestConfig:Certificate:Path"] = "Certificate1.pfx",
|
||||||
|
["TestConfig:Certificate:Password"] = "Password1"
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificate = new X509Certificate2();
|
||||||
|
|
||||||
|
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||||
|
certificateFileLoader
|
||||||
|
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||||
|
.Returns(certificate);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
null,
|
||||||
|
certificateFileLoader.Object,
|
||||||
|
Mock.Of<ICertificateStoreLoader>());
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"));
|
||||||
|
Assert.Equal(1, loadedCertificates.Count());
|
||||||
|
Assert.Same(certificate, loadedCertificates.ElementAt(0));
|
||||||
|
certificateFileLoader.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Throws_SingleCertificateInline_FileNotFound()
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["TestConfig:Certificate:Source"] = "File",
|
||||||
|
["TestConfig:Certificate:Path"] = "Certificate1.pfx",
|
||||||
|
["TestConfig:Certificate:Password"] = "Password1"
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var exception = new Exception();
|
||||||
|
|
||||||
|
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||||
|
certificateFileLoader
|
||||||
|
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||||
|
.Throws(exception);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
null,
|
||||||
|
certificateFileLoader.Object,
|
||||||
|
Mock.Of<ICertificateStoreLoader>());
|
||||||
|
|
||||||
|
Assert.Same(exception, Assert.Throws<Exception>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"))));
|
||||||
|
certificateFileLoader.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Loads_SingleCertificateInline_Store()
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["TestConfig:Certificate:Source"] = "Store",
|
||||||
|
["TestConfig:Certificate:Subject"] = "localhost",
|
||||||
|
["TestConfig:Certificate:StoreName"] = "My",
|
||||||
|
["TestConfig:Certificate:StoreLocation"] = "CurrentUser",
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificate = new X509Certificate2();
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||||
|
.Returns(certificate);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
null,
|
||||||
|
Mock.Of<ICertificateFileLoader>(),
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"));
|
||||||
|
Assert.Equal(1, loadedCertificates.Count());
|
||||||
|
Assert.Same(certificate, loadedCertificates.ElementAt(0));
|
||||||
|
certificateStoreLoader.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReturnsNull_SingleCertificateInline_Store_NotFoundInStore()
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["TestConfig:Certificate:Source"] = "Store",
|
||||||
|
["TestConfig:Certificate:Subject"] = "localhost",
|
||||||
|
["TestConfig:Certificate:StoreName"] = "My",
|
||||||
|
["TestConfig:Certificate:StoreLocation"] = "CurrentUser",
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
null,
|
||||||
|
Mock.Of<ICertificateFileLoader>(),
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificate"));
|
||||||
|
Assert.Equal(0, loadedCertificates.Count());
|
||||||
|
certificateStoreLoader.Verify(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Loads_MultipleCertificatesInline_File()
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["TestConfig:Certificates:Certificate1:Source"] = "File",
|
||||||
|
["TestConfig:Certificates:Certificate1:Path"] = "Certificate1.pfx",
|
||||||
|
["TestConfig:Certificates:Certificate1:Password"] = "Password1",
|
||||||
|
["TestConfig:Certificates:Certificate2:Source"] = "File",
|
||||||
|
["TestConfig:Certificates:Certificate2:Path"] = "Certificate2.pfx",
|
||||||
|
["TestConfig:Certificates:Certificate2:Password"] = "Password2",
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificate1 = new X509Certificate2();
|
||||||
|
var certificate2 = new X509Certificate2();
|
||||||
|
|
||||||
|
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>()))
|
||||||
|
.Returns(certificate2);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
null,
|
||||||
|
certificateFileLoader.Object,
|
||||||
|
Mock.Of<ICertificateStoreLoader>());
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificates"));
|
||||||
|
Assert.Equal(2, loadedCertificates.Count());
|
||||||
|
Assert.Same(certificate1, loadedCertificates.ElementAt(0));
|
||||||
|
Assert.Same(certificate2, loadedCertificates.ElementAt(1));
|
||||||
|
certificateFileLoader.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Throws_MultipleCertificatesInline_File_FileNotFound()
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["TestConfig:Certificates:Certificate1:Source"] = "File",
|
||||||
|
["TestConfig:Certificates:Certificate1:Path"] = "Certificate1.pfx",
|
||||||
|
["TestConfig:Certificates:Certificate1:Password"] = "Password1",
|
||||||
|
["TestConfig:Certificates:Certificate2:Source"] = "File",
|
||||||
|
["TestConfig:Certificates:Certificate2:Path"] = "Certificate2.pfx",
|
||||||
|
["TestConfig:Certificates:Certificate2:Password"] = "Password2",
|
||||||
|
})
|
||||||
|
.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(
|
||||||
|
null,
|
||||||
|
certificateFileLoader.Object,
|
||||||
|
Mock.Of<ICertificateStoreLoader>());
|
||||||
|
|
||||||
|
Assert.Same(exception, Assert.Throws<Exception>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificates"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Loads_MultipleCertificatesInline_Store()
|
||||||
|
{
|
||||||
|
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",
|
||||||
|
["TestConfig:Certificates:Certificate2:Source"] = "Store",
|
||||||
|
["TestConfig:Certificates:Certificate2:Subject"] = "example.com",
|
||||||
|
["TestConfig:Certificates:Certificate2:StoreName"] = "Root",
|
||||||
|
["TestConfig:Certificates:Certificate2:StoreLocation"] = "LocalMachine"
|
||||||
|
})
|
||||||
|
.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(
|
||||||
|
null,
|
||||||
|
Mock.Of<ICertificateFileLoader>(),
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificates"));
|
||||||
|
Assert.Equal(2, loadedCertificates.Count());
|
||||||
|
Assert.Same(certificate1, loadedCertificates.ElementAt(0));
|
||||||
|
Assert.Same(certificate2, loadedCertificates.ElementAt(1));
|
||||||
|
certificateStoreLoader.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReturnsNull_MultipleCertificatesInline_Store_NotFoundInStore()
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["TestConfig:Certificates:Certificate1:Source"] = "Store",
|
||||||
|
["TestConfig:Certificates:Certificate1:Subject"] = "notfound.com",
|
||||||
|
["TestConfig:Certificates:Certificate1:StoreName"] = "Root",
|
||||||
|
["TestConfig:Certificates:Certificate1:StoreLocation"] = "LocalMachine",
|
||||||
|
["TestConfig:Certificates:Certificate2:Source"] = "Store",
|
||||||
|
["TestConfig:Certificates:Certificate2:Subject"] = "localhost",
|
||||||
|
["TestConfig:Certificates:Certificate2:StoreName"] = "My",
|
||||||
|
["TestConfig:Certificates:Certificate2:StoreLocation"] = "CurrentUser",
|
||||||
|
["TestConfig:Certificates:Certificate3:Source"] = "Store",
|
||||||
|
["TestConfig:Certificates:Certificate3:Subject"] = "example.com",
|
||||||
|
["TestConfig:Certificates:Certificate3:StoreName"] = "Root",
|
||||||
|
["TestConfig:Certificates:Certificate3:StoreLocation"] = "LocalMachine"
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificate = new X509Certificate2();
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||||
|
.Returns(certificate);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
null,
|
||||||
|
Mock.Of<ICertificateFileLoader>(),
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificates"));
|
||||||
|
Assert.Equal(1, loadedCertificates.Count());
|
||||||
|
Assert.Same(certificate, loadedCertificates.ElementAt(0));
|
||||||
|
certificateStoreLoader.Verify(loader => loader.Load("notfound.com", "Root", StoreLocation.LocalMachine, It.IsAny<bool>()));
|
||||||
|
certificateStoreLoader.Verify(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()));
|
||||||
|
certificateStoreLoader.Verify(loader => loader.Load("example.com", "Root", StoreLocation.LocalMachine, It.IsAny<bool>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Loads_MultipleCertificatesInline_FileAndStore()
|
||||||
|
{
|
||||||
|
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",
|
||||||
|
["TestConfig:Certificates:Certificate2:Source"] = "File",
|
||||||
|
["TestConfig:Certificates:Certificate2:Path"] = "Certificate1.pfx",
|
||||||
|
["TestConfig:Certificates:Certificate2:Password"] = "Password1",
|
||||||
|
["TestConfig:Certificates:Certificate3:Source"] = "Store",
|
||||||
|
["TestConfig:Certificates:Certificate3:Subject"] = "example.com",
|
||||||
|
["TestConfig:Certificates:Certificate3:StoreName"] = "Root",
|
||||||
|
["TestConfig:Certificates:Certificate3:StoreLocation"] = "LocalMachine",
|
||||||
|
["TestConfig:Certificates:Certificate4:Source"] = "File",
|
||||||
|
["TestConfig:Certificates:Certificate4:Path"] = "Certificate2.pfx",
|
||||||
|
["TestConfig:Certificates:Certificate4:Password"] = "Password2",
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var fileCertificate1 = new X509Certificate2();
|
||||||
|
var fileCertificate2 = new X509Certificate2();
|
||||||
|
var storeCertificate1 = new X509Certificate2();
|
||||||
|
var storeCertificate2 = new X509Certificate2();
|
||||||
|
|
||||||
|
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||||
|
certificateFileLoader
|
||||||
|
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||||
|
.Returns(fileCertificate1);
|
||||||
|
certificateFileLoader
|
||||||
|
.Setup(loader => loader.Load("Certificate2.pfx", "Password2", It.IsAny<X509KeyStorageFlags>()))
|
||||||
|
.Returns(fileCertificate2);
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||||
|
.Returns(storeCertificate1);
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("example.com", "Root", StoreLocation.LocalMachine, It.IsAny<bool>()))
|
||||||
|
.Returns(storeCertificate2);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
null,
|
||||||
|
certificateFileLoader.Object,
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificates"));
|
||||||
|
Assert.Equal(4, loadedCertificates.Count());
|
||||||
|
Assert.Same(storeCertificate1, loadedCertificates.ElementAt(0));
|
||||||
|
Assert.Same(fileCertificate1, loadedCertificates.ElementAt(1));
|
||||||
|
Assert.Same(storeCertificate2, loadedCertificates.ElementAt(2));
|
||||||
|
Assert.Same(fileCertificate2, loadedCertificates.ElementAt(3));
|
||||||
|
certificateStoreLoader.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Throws_MultipleCertificatesInline_FileAndStore_FileNotFound()
|
||||||
|
{
|
||||||
|
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",
|
||||||
|
["TestConfig:Certificates:Certificate2:Source"] = "File",
|
||||||
|
["TestConfig:Certificates:Certificate2:Path"] = "Certificate1.pfx",
|
||||||
|
["TestConfig:Certificates:Certificate2:Password"] = "Password1",
|
||||||
|
})
|
||||||
|
.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);
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||||
|
.Returns(certificate);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
null,
|
||||||
|
certificateFileLoader.Object,
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
Assert.Same(exception, Assert.Throws<Exception>(() => certificateLoader.Load(configuration.GetSection("TestConfig:Certificates"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReturnsNull_MultipleCertificatesInline_FileAndStore_NotFoundInStore()
|
||||||
|
{
|
||||||
|
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",
|
||||||
|
["TestConfig:Certificates:Certificate2:Source"] = "File",
|
||||||
|
["TestConfig:Certificates:Certificate2:Path"] = "Certificate1.pfx",
|
||||||
|
["TestConfig:Certificates:Certificate2:Password"] = "Password1",
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var certificate = new X509Certificate2();
|
||||||
|
|
||||||
|
var certificateFileLoader = new Mock<ICertificateFileLoader>();
|
||||||
|
certificateFileLoader
|
||||||
|
.Setup(loader => loader.Load("Certificate1.pfx", "Password1", It.IsAny<X509KeyStorageFlags>()))
|
||||||
|
.Returns(certificate);
|
||||||
|
|
||||||
|
var certificateStoreLoader = new Mock<ICertificateStoreLoader>();
|
||||||
|
certificateStoreLoader
|
||||||
|
.Setup(loader => loader.Load("localhost", "My", StoreLocation.CurrentUser, It.IsAny<bool>()))
|
||||||
|
.Returns<X509Certificate>(null);
|
||||||
|
|
||||||
|
var certificateLoader = new CertificateLoader(
|
||||||
|
null,
|
||||||
|
certificateFileLoader.Object,
|
||||||
|
certificateStoreLoader.Object);
|
||||||
|
|
||||||
|
var loadedCertificates = certificateLoader.Load(configuration.GetSection("TestConfig:Certificates"));
|
||||||
|
Assert.Equal(1, loadedCertificates.Count());
|
||||||
|
Assert.Same(certificate, loadedCertificates.ElementAt(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="..\TestArtifacts\testCert.pfx" CopyToOutputDirectory="PreserveNewest" />
|
<Content Include="TestArtifacts\Certificate.pfx" CopyToOutputDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.IntegrationTesting" Version="$(AspNetCoreIntegrationTestingVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.IntegrationTesting" Version="$(AspNetCoreIntegrationTestingVersion)" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
|
||||||
|
<PackageReference Include="Moq" Version="$(MoqVersion)" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
|
||||||
<PackageReference Include="xunit" Version="$(XunitVersion)" />
|
<PackageReference Include="xunit" Version="$(XunitVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -134,8 +134,7 @@ namespace Microsoft.AspNetCore.Tests
|
||||||
""Certificates"": {
|
""Certificates"": {
|
||||||
""TestCert"": {
|
""TestCert"": {
|
||||||
""Source"": ""File"",
|
""Source"": ""File"",
|
||||||
""Path"": ""testCert.pfx"",
|
""Path"": ""TestArtifacts/Certificate.pfx""
|
||||||
""Password"": ""testPassword""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -173,8 +172,7 @@ namespace Microsoft.AspNetCore.Tests
|
||||||
""Port"": 0,
|
""Port"": 0,
|
||||||
""Certificate"": {
|
""Certificate"": {
|
||||||
""Source"": ""File"",
|
""Source"": ""File"",
|
||||||
""Path"": ""testCert.pfx"",
|
""Path"": ""TestArtifacts/Certificate.pfx"",
|
||||||
""Password"": ""testPassword""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue