@@ -12,49 +12,125 @@ namespace Microsoft.AspNetCore
12
12
/// <summary>
13
13
/// A helper class to load certificates from files and certificate stores based on <seealso cref="IConfiguration"/> data.
14
14
/// </summary>
15
- public static class CertificateLoader
15
+ public class CertificateLoader
16
16
{
17
+ private readonly IConfiguration _certificatesConfiguration ;
18
+ private readonly ICertificateFileLoader _certificateFileLoader ;
19
+ private readonly ICertificateStoreLoader _certificateStoreLoader ;
20
+
21
+ /// <summary>
22
+ /// Creates a new instance of <see cref="CertificateLoader"/>.
23
+ /// </summary>
24
+ public CertificateLoader ( )
25
+ : this ( null )
26
+ {
27
+ }
28
+
29
+ /// <summary>
30
+ /// Creates a new instance of <see cref="CertificateLoader"/> that can load certificate references from configuration.
31
+ /// </summary>
32
+ /// <param name="certificatesConfiguration">An <see cref="IConfiguration"/> with information about certificates.</param>
33
+ public CertificateLoader ( IConfiguration certificatesConfiguration )
34
+ : this ( certificatesConfiguration , new CertificateFileLoader ( ) , new CertificateStoreLoader ( ) )
35
+ {
36
+ _certificatesConfiguration = certificatesConfiguration ;
37
+ }
38
+
39
+ internal CertificateLoader ( IConfiguration certificatesConfiguration , ICertificateFileLoader certificateFileLoader , ICertificateStoreLoader certificateStoreLoader )
40
+ {
41
+ _certificatesConfiguration = certificatesConfiguration ;
42
+ _certificateFileLoader = certificateFileLoader ;
43
+ _certificateStoreLoader = certificateStoreLoader ;
44
+ }
45
+
46
+ /// <summary>
47
+ /// Loads one or more certificates based on the information found in a configuration section.
48
+ /// </summary>
49
+ /// <param name="certificateConfiguration">A configuration section containing either a string value referencing certificates
50
+ /// by name, or one or more inline certificate specifications.
51
+ /// </param>
52
+ /// <returns>One or more loaded certificates.</returns>
53
+ public IEnumerable < X509Certificate2 > Load ( IConfigurationSection certificateConfiguration )
54
+ {
55
+ var certificateNames = certificateConfiguration . Value ;
56
+ var certificates = new List < X509Certificate2 > ( ) ;
57
+
58
+ if ( certificateNames != null )
59
+ {
60
+ foreach ( var certificateName in certificateNames . Split ( ';' ) )
61
+ {
62
+ var certificate = LoadSingle ( certificateName ) ;
63
+ if ( certificate != null )
64
+ {
65
+ certificates . Add ( certificate ) ;
66
+ }
67
+ }
68
+ }
69
+ else
70
+ {
71
+ if ( certificateConfiguration [ "Source" ] != null )
72
+ {
73
+ var certificate = LoadSingle ( certificateConfiguration ) ;
74
+ if ( certificate != null )
75
+ {
76
+ certificates . Add ( certificate ) ;
77
+ }
78
+ }
79
+ else
80
+ {
81
+ certificates . AddRange ( LoadMultiple ( certificateConfiguration ) ) ;
82
+ }
83
+ }
84
+
85
+ return certificates ;
86
+ }
87
+
17
88
/// <summary>
18
- /// Loads one or more certificates from a single source .
89
+ /// Loads a certificate by name .
19
90
/// </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 )
91
+ /// <param name="certificateName">The certificate name.</param>
92
+ /// <returns>The loaded certificate</returns>
93
+ /// <remarks>This method only works if the <see cref="CertificateLoader"/> instance was constructed with
94
+ /// a reference to an <see cref="IConfiguration"/> instance containing named certificates.
95
+ /// </remarks>
96
+ private X509Certificate2 LoadSingle ( string certificateName )
97
+ {
98
+ var certificateConfiguration = _certificatesConfiguration ? . GetSection ( certificateName ) ;
99
+
100
+ if ( ! certificateConfiguration . Exists ( ) )
101
+ {
102
+ throw new InvalidOperationException ( $ "No certificate named { certificateName } found in configuration") ;
103
+ }
104
+
105
+ return LoadSingle ( certificateConfiguration ) ;
106
+ }
107
+
108
+ private X509Certificate2 LoadSingle ( IConfigurationSection certificateConfiguration )
24
109
{
25
- var sourceKind = certificateConfiguration . GetValue < string > ( "Source" ) ;
110
+ var sourceKind = certificateConfiguration [ "Source" ] ;
26
111
27
112
CertificateSource certificateSource ;
28
113
switch ( sourceKind . ToLowerInvariant ( ) )
29
114
{
30
115
case "file" :
31
- certificateSource = new CertificateFileSource ( password ) ;
116
+ certificateSource = new CertificateFileSource ( _certificateFileLoader ) ;
32
117
break ;
33
118
case "store" :
34
- certificateSource = new CertificateStoreSource ( ) ;
119
+ certificateSource = new CertificateStoreSource ( _certificateStoreLoader ) ;
35
120
break ;
36
121
default :
37
122
throw new InvalidOperationException ( $ "Invalid certificate source kind: { sourceKind } ") ;
38
123
}
39
124
40
125
certificateConfiguration . Bind ( certificateSource ) ;
126
+
41
127
return certificateSource . Load ( ) ;
42
128
}
43
129
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
- }
130
+ private IEnumerable < X509Certificate2 > LoadMultiple ( IConfigurationSection certificatesConfiguration )
131
+ => certificatesConfiguration . GetChildren ( )
132
+ . Select ( LoadSingle )
133
+ . Where ( c => c != null ) ;
58
134
59
135
private abstract class CertificateSource
60
136
{
@@ -65,22 +141,24 @@ private abstract class CertificateSource
65
141
66
142
private class CertificateFileSource : CertificateSource
67
143
{
68
- private readonly string _password ;
144
+ private ICertificateFileLoader _certificateFileLoader ;
69
145
70
- public CertificateFileSource ( string password )
146
+ public CertificateFileSource ( ICertificateFileLoader certificateFileLoader )
71
147
{
72
- _password = password ;
148
+ _certificateFileLoader = certificateFileLoader ;
73
149
}
74
150
75
151
public string Path { get ; set ; }
76
152
153
+ public string Password { get ; set ; }
154
+
77
155
public override X509Certificate2 Load ( )
78
156
{
79
157
var certificate = TryLoad ( X509KeyStorageFlags . DefaultKeySet , out var error )
80
158
?? TryLoad ( X509KeyStorageFlags . UserKeySet , out error )
81
- #if NETCOREAPP2_0
159
+ #if NETCOREAPP2_0
82
160
?? TryLoad ( X509KeyStorageFlags . EphemeralKeySet , out error )
83
- #endif
161
+ #endif
84
162
;
85
163
86
164
if ( error != null )
@@ -95,7 +173,7 @@ private X509Certificate2 TryLoad(X509KeyStorageFlags flags, out Exception except
95
173
{
96
174
try
97
175
{
98
- var loadedCertificate = new X509Certificate2 ( Path , _password , flags ) ;
176
+ var loadedCertificate = _certificateFileLoader . Load ( Path , Password , flags ) ;
99
177
exception = null ;
100
178
return loadedCertificate ;
101
179
}
@@ -109,6 +187,13 @@ private X509Certificate2 TryLoad(X509KeyStorageFlags flags, out Exception except
109
187
110
188
private class CertificateStoreSource : CertificateSource
111
189
{
190
+ private readonly ICertificateStoreLoader _certificateStoreLoader ;
191
+
192
+ public CertificateStoreSource ( ICertificateStoreLoader certificateStoreLoader )
193
+ {
194
+ _certificateStoreLoader = certificateStoreLoader ;
195
+ }
196
+
112
197
public string Subject { get ; set ; }
113
198
public string StoreName { get ; set ; }
114
199
public string StoreLocation { get ; set ; }
@@ -121,52 +206,7 @@ public override X509Certificate2 Load()
121
206
throw new InvalidOperationException ( $ "Invalid store location: { StoreLocation } ") ;
122
207
}
123
208
124
- using ( var store = new X509Store ( StoreName , storeLocation ) )
125
- {
126
- X509Certificate2Collection storeCertificates = null ;
127
- X509Certificate2Collection foundCertificates = null ;
128
- X509Certificate2 foundCertificate = null ;
129
-
130
- try
131
- {
132
- store . Open ( OpenFlags . ReadOnly ) ;
133
- storeCertificates = store . Certificates ;
134
- foundCertificates = storeCertificates . Find ( X509FindType . FindBySubjectDistinguishedName , Subject , validOnly : ! AllowInvalid ) ;
135
- foundCertificate = foundCertificates
136
- . OfType < X509Certificate2 > ( )
137
- . OrderByDescending ( certificate => certificate . NotAfter )
138
- . FirstOrDefault ( ) ;
139
-
140
- if ( foundCertificate == null )
141
- {
142
- throw new InvalidOperationException ( $ "No certificate found for { Subject } in store { StoreName } in { StoreLocation } ") ;
143
- }
144
-
145
- return foundCertificate ;
146
- }
147
- finally
148
- {
149
- if ( foundCertificate != null )
150
- {
151
- storeCertificates . Remove ( foundCertificate ) ;
152
- foundCertificates . Remove ( foundCertificate ) ;
153
- }
154
-
155
- DisposeCertificates ( storeCertificates ) ;
156
- DisposeCertificates ( foundCertificates ) ;
157
- }
158
- }
159
- }
160
-
161
- private void DisposeCertificates ( X509Certificate2Collection certificates )
162
- {
163
- if ( certificates != null )
164
- {
165
- foreach ( var certificate in certificates )
166
- {
167
- certificate . Dispose ( ) ;
168
- }
169
- }
209
+ return _certificateStoreLoader . Load ( Subject , StoreName , storeLocation , ! AllowInvalid ) ;
170
210
}
171
211
}
172
212
}
0 commit comments