21
21
22
22
using Microsoft . Extensions . Logging ;
23
23
24
+ using Org . BouncyCastle . Math ;
24
25
using Org . BouncyCastle . Crypto ;
25
26
using Org . BouncyCastle . Pkcs ;
26
27
using Org . BouncyCastle . X509 ;
27
28
using System . Security . Cryptography ;
28
29
using Org . BouncyCastle . OpenSsl ;
30
+ using Org . BouncyCastle . Crypto . Parameters ;
31
+ using Org . BouncyCastle . Asn1 . X9 ;
29
32
30
33
namespace Keyfactor . Extensions . Orchestrator . RemoteFile . PEM
31
34
{
32
35
class PEMCertificateStoreSerializer : ICertificateStoreSerializer
33
36
{
34
37
string [ ] PrivateKeyDelimetersPkcs8 = new string [ ] { "-----BEGIN PRIVATE KEY-----" , "-----BEGIN ENCRYPTED PRIVATE KEY-----" } ;
35
- string [ ] PrivateKeyDelimetersPkcs1 = new string [ ] { "-----BEGIN RSA PRIVATE KEY-----" } ;
38
+ string [ ] PrivateKeyDelimetersRSA = new string [ ] { "-----BEGIN RSA PRIVATE KEY-----" } ;
39
+ string [ ] PrivateKeyDelimetersEC = new string [ ] { "-----BEGIN EC PRIVATE KEY-----" } ;
36
40
string CertDelimBeg = "-----BEGIN CERTIFICATE-----" ;
37
41
string CertDelimEnd = "-----END CERTIFICATE-----" ;
38
42
43
+ private enum PrivateKeyTypeEnum
44
+ {
45
+ EC ,
46
+ RSA ,
47
+ PKCS8
48
+ }
49
+
39
50
private bool IsTrustStore { get ; set ; }
40
51
private bool IncludesChain { get ; set ; }
41
52
private string SeparatePrivateKeyFilePath { get ; set ; }
@@ -68,11 +79,8 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, s
68
79
}
69
80
else
70
81
{
71
- bool isRSAPrivateKey = false ;
72
- AsymmetricKeyEntry keyEntry = GetPrivateKey ( storeContents , storePassword ?? string . Empty , remoteHandler , out isRSAPrivateKey ) ;
73
-
74
- if ( isRSAPrivateKey && ! string . IsNullOrEmpty ( storePassword ) )
75
- throw new RemoteFileException ( $ "Certificate store with an RSA Private Key cannot contain a store password. Invalid store format not supported.") ;
82
+ PrivateKeyTypeEnum privateKeyType ;
83
+ AsymmetricKeyEntry keyEntry = GetPrivateKey ( storeContents , storePassword ?? string . Empty , remoteHandler , out privateKeyType ) ;
76
84
77
85
store . SetKeyEntry ( CertificateConverterFactory . FromBouncyCastleCertificate ( certificates [ 0 ] . Certificate ) . ToX509Certificate2 ( ) . Thumbprint , keyEntry , certificates ) ;
78
86
}
@@ -112,16 +120,14 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
112
120
else
113
121
{
114
122
string storeContents = Encoding . ASCII . GetString ( remoteHandler . DownloadCertificateFile ( storePath + storeFileName ) ) ;
115
- bool isRSAPrivateKey = false ;
116
- try
117
- {
118
- GetPrivateKey ( storeContents , storePassword , remoteHandler , out isRSAPrivateKey ) ;
119
- }
120
- catch ( RemoteFileException ) { }
121
123
122
- if ( isRSAPrivateKey && ! string . IsNullOrEmpty ( storePassword ) )
123
- throw new RemoteFileException ( $ "Certificate store with an RSA Private Key cannot contain a store password. Invalid store format not supported.") ;
124
+ string begDelim ;
125
+ string privateKeyContents = String . IsNullOrEmpty ( SeparatePrivateKeyFilePath ) ? storeContents : Encoding . ASCII . GetString ( remoteHandler . DownloadCertificateFile ( SeparatePrivateKeyFilePath ) ) ;
126
+ PrivateKeyTypeEnum privateKeyType = GetPrivateKeyType ( privateKeyContents , out begDelim ) ;
124
127
128
+ if ( ! string . IsNullOrEmpty ( storePassword ) && privateKeyType != PrivateKeyTypeEnum . PKCS8 )
129
+ throw new RemoteFileException ( "Error retrieving private key. Certificate store password cannot have a non empty value if the private key is in PKCS#1 format (BEGIN [RSA|EC] PRIVATE KEY)" ) ;
130
+
125
131
bool keyEntryProcessed = false ;
126
132
foreach ( string alias in certificateStore . Aliases )
127
133
{
@@ -137,10 +143,16 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
137
143
CertificateConverter certConverter = CertificateConverterFactory . FromBouncyCastleCertificate ( chainEntries [ 0 ] . Certificate ) ;
138
144
139
145
AsymmetricKeyParameter privateKey = certificateStore . GetKey ( alias ) . Key ;
140
- X509CertificateEntry [ ] certEntries = certificateStore . GetCertificateChain ( alias ) ;
141
- AsymmetricKeyParameter publicKey = certEntries [ 0 ] . Certificate . GetPublicKey ( ) ;
146
+ AsymmetricKeyParameter publicKey = chainEntries [ 0 ] . Certificate . GetPublicKey ( ) ;
142
147
143
- if ( isRSAPrivateKey )
148
+ if ( privateKeyType == PrivateKeyTypeEnum . PKCS8 )
149
+ {
150
+ PrivateKeyConverter keyConverter = PrivateKeyConverterFactory . FromBCKeyPair ( privateKey , publicKey , false ) ;
151
+
152
+ byte [ ] privateKeyBytes = string . IsNullOrEmpty ( storePassword ) ? keyConverter . ToPkcs8BlobUnencrypted ( ) : keyConverter . ToPkcs8Blob ( storePassword ) ;
153
+ keyString = PemUtilities . DERToPEM ( privateKeyBytes , string . IsNullOrEmpty ( storePassword ) ? PemUtilities . PemObjectType . PrivateKey : PemUtilities . PemObjectType . EncryptedPrivateKey ) ;
154
+ }
155
+ else
144
156
{
145
157
TextWriter textWriter = new StringWriter ( ) ;
146
158
PemWriter pemWriter = new PemWriter ( textWriter ) ;
@@ -149,13 +161,6 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
149
161
150
162
keyString = textWriter . ToString ( ) ;
151
163
}
152
- else
153
- {
154
- PrivateKeyConverter keyConverter = PrivateKeyConverterFactory . FromBCKeyPair ( privateKey , publicKey , false ) ;
155
-
156
- byte [ ] privateKeyBytes = string . IsNullOrEmpty ( storePassword ) ? keyConverter . ToPkcs8BlobUnencrypted ( ) : keyConverter . ToPkcs8Blob ( storePassword ) ;
157
- keyString = PemUtilities . DERToPEM ( privateKeyBytes , string . IsNullOrEmpty ( storePassword ) ? PemUtilities . PemObjectType . PrivateKey : PemUtilities . PemObjectType . EncryptedPrivateKey ) ;
158
- }
159
164
160
165
pemString = certConverter . ToPEM ( true ) ;
161
166
if ( string . IsNullOrEmpty ( SeparatePrivateKeyFilePath ) )
@@ -230,61 +235,120 @@ private X509CertificateEntry[] GetCertificates(string certificates)
230
235
return certificateEntries . ToArray ( ) ;
231
236
}
232
237
233
- private AsymmetricKeyEntry GetPrivateKey ( string storeContents , string storePassword , IRemoteHandler remoteHandler , out bool isRSA )
238
+ private AsymmetricKeyEntry GetPrivateKey ( string storeContents , string storePassword , IRemoteHandler remoteHandler , out PrivateKeyTypeEnum privateKeyType )
234
239
{
235
240
logger . MethodEntry ( LogLevel . Debug ) ;
236
241
242
+ AsymmetricKeyEntry keyEntry = null ;
243
+
237
244
if ( ! String . IsNullOrEmpty ( SeparatePrivateKeyFilePath ) )
238
245
{
239
246
storeContents = Encoding . ASCII . GetString ( remoteHandler . DownloadCertificateFile ( SeparatePrivateKeyFilePath ) ) ;
240
247
}
241
248
242
- isRSA = false ;
243
- foreach ( string begDelim in PrivateKeyDelimetersPkcs1 )
249
+ string begDelim = string . Empty ;
250
+ privateKeyType = GetPrivateKeyType ( storeContents , out begDelim ) ;
251
+
252
+ string privateKey = string . Empty ;
253
+ string endDelim = begDelim . Replace ( "BEGIN" , "END" ) ;
254
+
255
+ int keyStart = storeContents . IndexOf ( begDelim ) ;
256
+ if ( keyStart == - 1 )
257
+ throw new RemoteFileException ( "Invalid private key: No beginning private key delimiter found." ) ;
258
+
259
+ int keyLength = storeContents . IndexOf ( endDelim ) + endDelim . Length - keyStart ;
260
+ if ( keyLength == - 1 )
261
+ throw new RemoteFileException ( "Invalid private key: No ending private key delimiter found." ) ;
262
+
263
+ privateKey = storeContents . Substring ( keyStart , keyLength ) . Replace ( begDelim , string . Empty ) . Replace ( endDelim , string . Empty ) ;
264
+
265
+ if ( string . IsNullOrEmpty ( privateKey ) )
266
+ throw new RemoteFileException ( "Invalid private key: No private key or invalid private key format found." ) ;
267
+
268
+ PrivateKeyConverter c = null ;
269
+ int bytesRead ;
270
+ switch ( privateKeyType )
271
+ {
272
+ case PrivateKeyTypeEnum . PKCS8 :
273
+ c = PrivateKeyConverterFactory . FromPkcs8Blob ( Convert . FromBase64String ( privateKey ) , storePassword ) ;
274
+ keyEntry = new AsymmetricKeyEntry ( c . ToBCPrivateKey ( ) ) ;
275
+ break ;
276
+ case PrivateKeyTypeEnum . RSA :
277
+ RSA rsa = RSA . Create ( ) ;
278
+ rsa . ImportRSAPrivateKey ( Convert . FromBase64String ( privateKey ) , out bytesRead ) ;
279
+ c = PrivateKeyConverterFactory . FromNetPrivateKey ( rsa , false ) ;
280
+ keyEntry = new AsymmetricKeyEntry ( c . ToBCPrivateKey ( ) ) ;
281
+ break ;
282
+ case PrivateKeyTypeEnum . EC :
283
+ ECDiffieHellman ec = ECDiffieHellman . Create ( ) ;
284
+ keyEntry = new AsymmetricKeyEntry ( this . ToBCPrivateKey ( ec ) ) ;
285
+ break ;
286
+ }
287
+
288
+ logger . MethodExit ( LogLevel . Debug ) ;
289
+
290
+ return keyEntry ;
291
+ }
292
+
293
+ private PrivateKeyTypeEnum GetPrivateKeyType ( string storeContents , out string privateKeyBegDelim )
294
+ {
295
+ foreach ( string begDelim in PrivateKeyDelimetersPkcs8 )
296
+ {
297
+ if ( string . IsNullOrEmpty ( storeContents ) || storeContents . Contains ( begDelim ) )
298
+ {
299
+ privateKeyBegDelim = begDelim ;
300
+ return PrivateKeyTypeEnum . PKCS8 ;
301
+ }
302
+ }
303
+
304
+ foreach ( string begDelim in PrivateKeyDelimetersRSA )
244
305
{
245
306
if ( storeContents . Contains ( begDelim ) )
246
307
{
247
- isRSA = true ;
248
- break ;
308
+ privateKeyBegDelim = begDelim ;
309
+ return PrivateKeyTypeEnum . RSA ;
249
310
}
250
311
}
251
312
252
- string privateKey = string . Empty ;
253
- foreach ( string begDelim in isRSA ? PrivateKeyDelimetersPkcs1 : PrivateKeyDelimetersPkcs8 )
313
+ foreach ( string begDelim in PrivateKeyDelimetersEC )
254
314
{
255
- string endDelim = begDelim . Replace ( "BEGIN" , "END" ) ;
315
+ if ( storeContents . Contains ( begDelim ) )
316
+ {
317
+ privateKeyBegDelim = begDelim ;
318
+ return PrivateKeyTypeEnum . EC ;
319
+ }
320
+ }
256
321
257
- int keyStart = storeContents . IndexOf ( begDelim ) ;
258
- if ( keyStart == - 1 )
259
- continue ;
260
- int keyLength = storeContents . IndexOf ( endDelim ) + endDelim . Length - keyStart ;
261
- if ( keyLength == - 1 )
262
- throw new RemoteFileException ( "Invalid private key: No ending private key delimiter found." ) ;
322
+ throw new RemoteFileException ( "Invalid or unsupported Private Key format." ) ;
323
+ }
263
324
264
- privateKey = storeContents . Substring ( keyStart , keyLength ) . Replace ( begDelim , string . Empty ) . Replace ( endDelim , string . Empty ) ;
325
+ private AsymmetricKeyParameter ToBCPrivateKey ( ECDiffieHellman ecdh )
326
+ {
327
+ // Export the key as ECParameters
328
+ ECParameters parameters = ecdh . ExportParameters ( true ) ;
265
329
266
- break ;
267
- }
330
+ // Convert the parameters to a BouncyCastle ECCurve
331
+ var curve2 = ECNamedCurveTable . GetByName ( parameters . Curve . Oid . FriendlyName ) ;
332
+ var curve = ECNamedCurveTable . GetByOid ( new Org . BouncyCastle . Asn1 . DerObjectIdentifier ( parameters . Curve . Oid . Value ) ) ;
333
+ if ( curve == null )
334
+ throw new RemoteFileException ( "Error converting to BouncyCastle private key - Unsupported curve" ) ;
268
335
269
- if ( string . IsNullOrEmpty ( privateKey ) )
270
- throw new RemoteFileException ( "Invalid private key: No private key or invalid private key format found." ) ;
336
+ // Convert the parameters to BigInteger
337
+ var q = curve . Curve . CreatePoint (
338
+ new BigInteger ( 1 , parameters . Q . X ) ,
339
+ new BigInteger ( 1 , parameters . Q . Y ) ) ;
271
340
272
- PrivateKeyConverter c ;
273
- if ( isRSA )
341
+ if ( parameters . D != null )
274
342
{
275
- RSA rsa = RSA . Create ( ) ;
276
- int bytesRead ;
277
- rsa . ImportRSAPrivateKey ( Convert . FromBase64String ( privateKey ) , out bytesRead ) ;
278
- c = PrivateKeyConverterFactory . FromNetPrivateKey ( rsa , false ) ;
343
+ // Create private key parameter
344
+ return new ECPrivateKeyParameters (
345
+ new BigInteger ( 1 , parameters . D ) ,
346
+ new ECDomainParameters ( curve ) ) ;
279
347
}
280
348
else
281
349
{
282
- c = PrivateKeyConverterFactory . FromPkcs8Blob ( Convert . FromBase64String ( privateKey ) , storePassword ) ;
350
+ throw new RemoteFileException ( "Error converting to BouncyCastle private key - Invalid parameter." ) ;
283
351
}
284
-
285
- logger . MethodExit ( LogLevel . Debug ) ;
286
-
287
- return new AsymmetricKeyEntry ( c . ToBCPrivateKey ( ) ) ;
288
352
}
289
353
}
290
354
}
0 commit comments