2121
2222using Microsoft . Extensions . Logging ;
2323
24+ using Org . BouncyCastle . Math ;
2425using Org . BouncyCastle . Crypto ;
2526using Org . BouncyCastle . Pkcs ;
2627using Org . BouncyCastle . X509 ;
2728using System . Security . Cryptography ;
2829using Org . BouncyCastle . OpenSsl ;
30+ using Org . BouncyCastle . Crypto . Parameters ;
31+ using Org . BouncyCastle . Asn1 . X9 ;
2932
3033namespace Keyfactor . Extensions . Orchestrator . RemoteFile . PEM
3134{
3235 class PEMCertificateStoreSerializer : ICertificateStoreSerializer
3336 {
3437 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-----" } ;
3640 string CertDelimBeg = "-----BEGIN CERTIFICATE-----" ;
3741 string CertDelimEnd = "-----END CERTIFICATE-----" ;
3842
43+ private enum PrivateKeyTypeEnum
44+ {
45+ EC ,
46+ RSA ,
47+ PKCS8
48+ }
49+
3950 private bool IsTrustStore { get ; set ; }
4051 private bool IncludesChain { get ; set ; }
4152 private string SeparatePrivateKeyFilePath { get ; set ; }
@@ -68,11 +79,8 @@ public Pkcs12Store DeserializeRemoteCertificateStore(byte[] storeContentBytes, s
6879 }
6980 else
7081 {
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 ) ;
7684
7785 store . SetKeyEntry ( CertificateConverterFactory . FromBouncyCastleCertificate ( certificates [ 0 ] . Certificate ) . ToX509Certificate2 ( ) . Thumbprint , keyEntry , certificates ) ;
7886 }
@@ -112,16 +120,14 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
112120 else
113121 {
114122 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 ) { }
121123
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 ) ;
124127
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+
125131 bool keyEntryProcessed = false ;
126132 foreach ( string alias in certificateStore . Aliases )
127133 {
@@ -137,10 +143,16 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
137143 CertificateConverter certConverter = CertificateConverterFactory . FromBouncyCastleCertificate ( chainEntries [ 0 ] . Certificate ) ;
138144
139145 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 ( ) ;
142147
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
144156 {
145157 TextWriter textWriter = new StringWriter ( ) ;
146158 PemWriter pemWriter = new PemWriter ( textWriter ) ;
@@ -149,13 +161,6 @@ public List<SerializedStoreInfo> SerializeRemoteCertificateStore(Pkcs12Store cer
149161
150162 keyString = textWriter . ToString ( ) ;
151163 }
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- }
159164
160165 pemString = certConverter . ToPEM ( true ) ;
161166 if ( string . IsNullOrEmpty ( SeparatePrivateKeyFilePath ) )
@@ -230,61 +235,120 @@ private X509CertificateEntry[] GetCertificates(string certificates)
230235 return certificateEntries . ToArray ( ) ;
231236 }
232237
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 )
234239 {
235240 logger . MethodEntry ( LogLevel . Debug ) ;
236241
242+ AsymmetricKeyEntry keyEntry = null ;
243+
237244 if ( ! String . IsNullOrEmpty ( SeparatePrivateKeyFilePath ) )
238245 {
239246 storeContents = Encoding . ASCII . GetString ( remoteHandler . DownloadCertificateFile ( SeparatePrivateKeyFilePath ) ) ;
240247 }
241248
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 )
244305 {
245306 if ( storeContents . Contains ( begDelim ) )
246307 {
247- isRSA = true ;
248- break ;
308+ privateKeyBegDelim = begDelim ;
309+ return PrivateKeyTypeEnum . RSA ;
249310 }
250311 }
251312
252- string privateKey = string . Empty ;
253- foreach ( string begDelim in isRSA ? PrivateKeyDelimetersPkcs1 : PrivateKeyDelimetersPkcs8 )
313+ foreach ( string begDelim in PrivateKeyDelimetersEC )
254314 {
255- string endDelim = begDelim . Replace ( "BEGIN" , "END" ) ;
315+ if ( storeContents . Contains ( begDelim ) )
316+ {
317+ privateKeyBegDelim = begDelim ;
318+ return PrivateKeyTypeEnum . EC ;
319+ }
320+ }
256321
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+ }
263324
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 ) ;
265329
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" ) ;
268335
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 ) ) ;
271340
272- PrivateKeyConverter c ;
273- if ( isRSA )
341+ if ( parameters . D != null )
274342 {
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 ) ) ;
279347 }
280348 else
281349 {
282- c = PrivateKeyConverterFactory . FromPkcs8Blob ( Convert . FromBase64String ( privateKey ) , storePassword ) ;
350+ throw new RemoteFileException ( "Error converting to BouncyCastle private key - Invalid parameter." ) ;
283351 }
284-
285- logger . MethodExit ( LogLevel . Debug ) ;
286-
287- return new AsymmetricKeyEntry ( c . ToBCPrivateKey ( ) ) ;
288352 }
289353 }
290354}
0 commit comments