Skip to content
This repository was archived by the owner on Nov 21, 2018. It is now read-only.

Commit 0b26971

Browse files
author
Cesar Blum Silveira
committed
Support more certificate loading scenarios (#69).
1 parent a6c4df6 commit 0b26971

13 files changed

+224
-87
lines changed

MetaPackages.sln

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartRequestDelegateUrlApp"
4040
EndProject
4141
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateDefaultBuilderApp", "test\TestSites\CreateDefaultBuilderApp\CreateDefaultBuilderApp.csproj", "{79CF58CE-B020-45D8-BDB5-2D8036BEAD14}"
4242
EndProject
43-
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestArtifacts", "TestArtifacts", "{9BBA7A0A-109A-4AC8-B6EF-A52EA7CF1D90}"
44-
ProjectSection(SolutionItems) = preProject
45-
test\TestArtifacts\testCert.pfx = test\TestArtifacts\testCert.pfx
46-
EndProjectSection
47-
EndProject
4843
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-archive", "src\dotnet-archive\dotnet-archive.csproj", "{AE4216BF-D471-471B-82F3-6B6D004F7D17}"
4944
EndProject
5045
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Archive", "src\Microsoft.DotNet.Archive\Microsoft.DotNet.Archive.csproj", "{302400A0-98BB-4C04-88D4-C32DC2D4B945}"
@@ -115,7 +110,6 @@ Global
115110
{3A85FA52-F601-422E-A42E-9F187DB28492} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
116111
{401C741B-6C7C-4E08-9F09-C3D43D22C0DE} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
117112
{79CF58CE-B020-45D8-BDB5-2D8036BEAD14} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
118-
{9BBA7A0A-109A-4AC8-B6EF-A52EA7CF1D90} = {9E49B5B9-9E72-42FB-B684-90CA1B1BCF9C}
119113
{AE4216BF-D471-471B-82F3-6B6D004F7D17} = {ED834E68-51C3-4ADE-ACC8-6BA6D4207C09}
120114
{302400A0-98BB-4C04-88D4-C32DC2D4B945} = {ED834E68-51C3-4ADE-ACC8-6BA6D4207C09}
121115
EndGlobalSection

src/Microsoft.AspNetCore/CertificateLoader.cs

Lines changed: 81 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,90 @@ namespace Microsoft.AspNetCore
1212
/// <summary>
1313
/// A helper class to load certificates from files and certificate stores based on <seealso cref="IConfiguration"/> data.
1414
/// </summary>
15-
public static class CertificateLoader
15+
public class CertificateLoader
1616
{
17+
private readonly IConfiguration _certificatesConfiguration;
18+
19+
/// <summary>
20+
/// Creates a new instance of <see cref="KestrelServerOptionsSetup"/>.
21+
/// </summary>
22+
public CertificateLoader()
23+
{
24+
}
25+
26+
/// <summary>
27+
/// Creates a new instance of <see cref="KestrelServerOptionsSetup"/> that can load certificates by name.
28+
/// </summary>
29+
/// <param name="certificatesConfig">An <see cref="IConfiguration"/> instance with information about certificates.</param>
30+
public CertificateLoader(IConfiguration certificatesConfig)
31+
{
32+
_certificatesConfiguration = certificatesConfig;
33+
}
34+
35+
/// <summary>
36+
/// Loads one or more certificates based on the information found in a configuration section.
37+
/// </summary>
38+
/// <param name="certificateConfiguration">A configuration section containing either a string value referencing certificates
39+
/// by name, or one or more inline certificate specifications.
40+
/// </param>
41+
/// <returns>One or more loaded certificates.</returns>
42+
public IEnumerable<X509Certificate2> Load(IConfigurationSection certificateConfiguration)
43+
{
44+
var certificateNames = certificateConfiguration.Value;
45+
46+
List<X509Certificate2> certificates = new List<X509Certificate2>();
47+
48+
if (certificateNames != null)
49+
{
50+
foreach (var certificateName in certificateNames.Split(' '))
51+
{
52+
certificates.Add(Load(certificateName));
53+
}
54+
}
55+
else
56+
{
57+
if (certificateConfiguration["Source"] != null)
58+
{
59+
certificates.Add(LoadSingle(certificateConfiguration));
60+
}
61+
else
62+
{
63+
certificates.AddRange(LoadMultiple(certificateConfiguration));
64+
}
65+
}
66+
67+
return certificates;
68+
}
69+
1770
/// <summary>
18-
/// Loads one or more certificates from a single source.
71+
/// Loads a certificate by name.
1972
/// </summary>
20-
/// <param name="certificateConfiguration">An <seealso cref="IConfiguration"/> with information about a certificate source.</param>
21-
/// <param name="password">The certificate password, in case it's being loaded from a file.</param>
22-
/// <returns>The loaded certificates.</returns>
23-
public static X509Certificate2 Load(IConfiguration certificateConfiguration, string password)
73+
/// <param name="certificateName">The certificate name.</param>
74+
/// <returns>The loaded certificate</returns>
75+
/// <remarks>This method only works if the <see cref="CertificateLoader"/> instance was constructed with
76+
/// a reference to an <see cref="IConfiguration"/> instance containing named certificates.
77+
/// </remarks>
78+
public X509Certificate2 Load(string certificateName)
2479
{
25-
var sourceKind = certificateConfiguration.GetValue<string>("Source");
80+
var certificateConfiguration = _certificatesConfiguration?.GetSection(certificateName);
81+
82+
if (certificateConfiguration == null)
83+
{
84+
throw new InvalidOperationException($"No certificate named {certificateName} found in configuration");
85+
}
86+
87+
return LoadSingle(certificateConfiguration);
88+
}
89+
90+
private X509Certificate2 LoadSingle(IConfigurationSection certificateConfiguration)
91+
{
92+
var sourceKind = certificateConfiguration["Source"];
2693

2794
CertificateSource certificateSource;
2895
switch (sourceKind.ToLowerInvariant())
2996
{
3097
case "file":
31-
certificateSource = new CertificateFileSource(password);
98+
certificateSource = new CertificateFileSource();
3299
break;
33100
case "store":
34101
certificateSource = new CertificateStoreSource();
@@ -38,23 +105,12 @@ public static X509Certificate2 Load(IConfiguration certificateConfiguration, str
38105
}
39106

40107
certificateConfiguration.Bind(certificateSource);
108+
41109
return certificateSource.Load();
42110
}
43111

44-
/// <summary>
45-
/// Loads all certificates specified in an <seealso cref="IConfiguration"/>.
46-
/// </summary>
47-
/// <param name="configurationRoot">The root <seealso cref="IConfiguration"/>.</param>
48-
/// <returns>
49-
/// A dictionary mapping certificate names to loaded certificates.
50-
/// </returns>
51-
public static Dictionary<string, X509Certificate2> LoadAll(IConfiguration configurationRoot)
52-
{
53-
return configurationRoot.GetSection("Certificates").GetChildren()
54-
.ToDictionary(
55-
certificateSource => certificateSource.Key,
56-
certificateSource => Load(certificateSource, certificateSource["Password"]));
57-
}
112+
private IEnumerable<X509Certificate2> LoadMultiple(IConfigurationSection certificatesConfiguration)
113+
=> certificatesConfiguration.GetChildren().Select(LoadSingle);
58114

59115
private abstract class CertificateSource
60116
{
@@ -65,15 +121,10 @@ private abstract class CertificateSource
65121

66122
private class CertificateFileSource : CertificateSource
67123
{
68-
private readonly string _password;
69-
70-
public CertificateFileSource(string password)
71-
{
72-
_password = password;
73-
}
74-
75124
public string Path { get; set; }
76125

126+
public string Password { get; set; }
127+
77128
public override X509Certificate2 Load()
78129
{
79130
var certificate = TryLoad(X509KeyStorageFlags.DefaultKeySet, out var error)
@@ -95,7 +146,7 @@ private X509Certificate2 TryLoad(X509KeyStorageFlags flags, out Exception except
95146
{
96147
try
97148
{
98-
var loadedCertificate = new X509Certificate2(Path, _password, flags);
149+
var loadedCertificate = new X509Certificate2(Path, Password, flags);
99150
exception = null;
100151
return loadedCertificate;
101152
}

src/Microsoft.AspNetCore/KestrelServerOptionsSetup.cs

Lines changed: 17 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,100 +2,71 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections.Generic;
65
using System.Linq;
76
using System.Net;
8-
using System.Security.Cryptography.X509Certificates;
97
using Microsoft.AspNetCore.Hosting;
108
using Microsoft.AspNetCore.Server.Kestrel.Core;
11-
using Microsoft.AspNetCore.Server.Kestrel.Https;
129
using Microsoft.Extensions.Configuration;
13-
using Microsoft.Extensions.DependencyInjection;
1410
using Microsoft.Extensions.Options;
1511

1612
namespace Microsoft.AspNetCore
1713
{
18-
/// <summary>
19-
/// Binds Kestrel configuration.
20-
/// </summary>
21-
public class KestrelServerOptionsSetup : IConfigureOptions<KestrelServerOptions>
14+
internal class KestrelServerOptionsSetup : IConfigureOptions<KestrelServerOptions>
2215
{
2316
private readonly IConfiguration _configurationRoot;
2417

25-
/// <summary>
26-
/// Creates a new instance of <see cref="KestrelServerOptionsSetup"/>.
27-
/// </summary>
28-
/// <param name="configurationRoot">The root <seealso cref="IConfiguration"/>.</param>
2918
public KestrelServerOptionsSetup(IConfiguration configurationRoot)
3019
{
3120
_configurationRoot = configurationRoot;
3221
}
3322

34-
/// <summary>
35-
/// Configures a <seealso cref="KestrelServerOptions"/> instance.
36-
/// </summary>
37-
/// <param name="options">The <seealso cref="KestrelServerOptions"/> to configure.</param>
3823
public void Configure(KestrelServerOptions options)
3924
{
4025
BindConfiguration(options);
4126
}
4227

4328
private void BindConfiguration(KestrelServerOptions options)
4429
{
45-
var certificates = CertificateLoader.LoadAll(_configurationRoot);
46-
var endPoints = _configurationRoot.GetSection("Kestrel:EndPoints");
30+
var certificateLoader = new CertificateLoader(_configurationRoot.GetSection("Certificates"));
4731

48-
foreach (var endPoint in endPoints.GetChildren())
32+
foreach (var endPoint in _configurationRoot.GetSection("Kestrel:EndPoints").GetChildren())
4933
{
50-
BindEndPoint(options, endPoint, certificates);
34+
BindEndPoint(options, endPoint, certificateLoader);
5135
}
5236
}
5337

5438
private void BindEndPoint(
5539
KestrelServerOptions options,
5640
IConfigurationSection endPoint,
57-
Dictionary<string, X509Certificate2> certificates)
41+
CertificateLoader certificateLoader)
5842
{
59-
var addressValue = endPoint.GetValue<string>("Address");
60-
var portValue = endPoint.GetValue<string>("Port");
43+
var configAddress = endPoint.GetValue<string>("Address");
44+
var configPort = endPoint.GetValue<string>("Port");
6145

62-
IPAddress address;
63-
if (!IPAddress.TryParse(addressValue, out address))
46+
if (!IPAddress.TryParse(configAddress, out var address))
6447
{
65-
throw new InvalidOperationException($"Invalid IP address: {addressValue}");
48+
throw new InvalidOperationException($"Invalid IP address in configuration: {configAddress}");
6649
}
6750

68-
int port;
69-
if (!int.TryParse(portValue, out port))
51+
if (!int.TryParse(configPort, out var port))
7052
{
71-
throw new InvalidOperationException($"Invalid port: {portValue}");
53+
throw new InvalidOperationException($"Invalid port in configuration: {configPort}");
7254
}
7355

7456
options.Listen(address, port, listenOptions =>
7557
{
76-
var certificateName = endPoint.GetValue<string>("Certificate");
58+
var certificateConfig = endPoint.GetSection("Certificate");
7759

78-
X509Certificate2 endPointCertificate = null;
79-
if (certificateName != null)
60+
if (certificateConfig.Exists())
8061
{
81-
if (!certificates.TryGetValue(certificateName, out endPointCertificate))
82-
{
83-
throw new InvalidOperationException($"No certificate named {certificateName} found in configuration");
84-
}
85-
}
86-
else
87-
{
88-
var certificate = endPoint.GetSection("Certificate");
62+
var certificate = certificateLoader.Load(certificateConfig).FirstOrDefault();
8963

90-
if (certificate.GetChildren().Any())
64+
if (certificate == null)
9165
{
92-
endPointCertificate = CertificateLoader.Load(certificate, certificate["Password"]);
66+
throw new InvalidOperationException($"Unable to load certificate for endpoint '{endPoint.Key}'");
9367
}
94-
}
9568

96-
if (endPointCertificate != null)
97-
{
98-
listenOptions.UseHttps(endPointCertificate);
69+
listenOptions.UseHttps(certificate);
9970
}
10071
});
10172
}

0 commit comments

Comments
 (0)