@@ -7,6 +7,9 @@ import * as x509 from '@peculiar/x509';
77import  *  as  asn1X509  from  '@peculiar/asn1-x509' ; 
88import  *  as  asn1Schema  from  '@peculiar/asn1-schema' ; 
99
10+ // Import for PKCS#8 structure 
11+ import  {  PrivateKeyInfo  }  from  '@peculiar/asn1-pkcs8' ; 
12+ 
1013const  crypto  =  globalThis . crypto ; 
1114
1215export  type  CAOptions  =  ( CertDataOptions  |  CertPathOptions ) ; 
@@ -71,11 +74,50 @@ function arrayBufferToPem(buffer: ArrayBuffer, label: string): string {
7174    return  `-----BEGIN ${ label }  -----\n${ lines . join ( '\n' ) }  \n-----END ${ label }  -----\n` ; 
7275} 
7376
77+ // OID for rsaEncryption - used to wrap PKCS#1 keys into PKCS#8 below: 
78+ const  rsaEncryptionOid  =  "1.2.840.113549.1.1.1" ; 
79+ 
7480async  function  pemToCryptoKey ( pem : string )  { 
75-     const  derKey  =  x509 . PemConverter . decodeFirst ( pem ) ; 
81+     // The PEM might be PKCS#8 ("BEGIN PRIVATE KEY") or PKCS#1 ("BEGIN 
82+     // RSA PRIVATE KEY"). We want to transparently accept both, but 
83+     // we can only import PKCS#8, so we detect & convert if required. 
84+ 
85+     const  keyData  =  x509 . PemConverter . decodeFirst ( pem ) ; 
86+     let  pkcs8KeyData : ArrayBuffer ; 
87+ 
88+     try  { 
89+         // Try to parse the PEM as PKCS#8 PrivateKeyInfo - if it works, 
90+         // we can just use it directly as-is: 
91+         asn1Schema . AsnConvert . parse ( keyData ,  PrivateKeyInfo ) ; 
92+         pkcs8KeyData  =  keyData ; 
93+     }  catch  ( e : any )  { 
94+         // If parsing as PKCS#8 fails, assume it's PKCS#1 (RSAPrivateKey) 
95+         // and proceed to wrap it as an RSA key in a PrivateKeyInfo structure. 
96+         const  rsaPrivateKeyDer  =  keyData ; 
97+ 
98+         try  { 
99+             const  privateKeyInfo  =  new  PrivateKeyInfo ( { 
100+                 version : 0 , 
101+                 privateKeyAlgorithm : new  asn1X509 . AlgorithmIdentifier ( { 
102+                     algorithm : rsaEncryptionOid 
103+                 } ) , 
104+                 privateKey : new  asn1Schema . OctetString ( rsaPrivateKeyDer ) 
105+             } ) ; 
106+             pkcs8KeyData  =  asn1Schema . AsnConvert . serialize ( privateKeyInfo ) ; 
107+         }  catch  ( conversionError : any )  { 
108+             throw  new  Error ( 
109+                 `Unsupported or malformed key format. Failed to parse as PKCS#8 with ${  
110+                     e . message  ||  e . toString ( )  
111+                 }   and failed to convert to PKCS#1 with ${ 
112+                     conversionError . message  ||  conversionError . toString ( )  
113+                 }  `
114+             ) ; 
115+         } 
116+     } 
117+ 
76118    return  await  crypto . subtle . importKey ( 
77-         "pkcs8" , 
78-         derKey , 
119+         "pkcs8" ,   // N.b, pkcs1 is not supported, which is why we need the above 
120+         pkcs8KeyData , 
79121        {  name : "RSASSA-PKCS1-v1_5" ,  hash : "SHA-256"  } , 
80122        true ,  // Extractable 
81123        [ "sign" ] 
@@ -243,7 +285,10 @@ export async function getCA(options: CAOptions): Promise<CA> {
243285        throw  new  Error ( 'Unrecognized https options: you need to provide either a keyPath & certPath, or a key & cert.' ) 
244286    } 
245287
246-     return  new  CA ( certOptions ) ; 
288+     const  caCert  =  new  x509 . X509Certificate ( certOptions . cert . toString ( ) ) ; 
289+     const  caKey  =  await  pemToCryptoKey ( certOptions . key . toString ( ) ) ; 
290+ 
291+     return  new  CA ( caCert ,  caKey ,  options ) ; 
247292} 
248293
249294// We share a single keypair across all certificates in this process, and 
@@ -261,20 +306,22 @@ const KEY_PAIR_ALGO = {
261306    publicExponent : new  Uint8Array ( [ 1 ,  0 ,  1 ] ) 
262307} ; 
263308
264- export  class   CA  { 
265-      private   caCert :  x509 . X509Certificate ; 
266-      private   caKey :  Promise < CryptoKey > ; 
267-     private  options : CertDataOptions ; 
309+ export  type   {   CA  } ; 
310+ 
311+ class   CA   { 
312+     private  options : BaseCAOptions ; 
268313
269314    private  certCache : {  [ domain : string ] : GeneratedCertificate  } ; 
270315
271-     constructor ( options : CertDataOptions )  { 
272-         this . caKey  =  pemToCryptoKey ( options . key . toString ( ) ) ; 
273-         this . caCert  =  new  x509 . X509Certificate ( options . cert . toString ( ) ) ; 
316+     constructor ( 
317+         private  caCert : x509 . X509Certificate , 
318+         private  caKey : CryptoKey , 
319+         options ?: BaseCAOptions 
320+     )  { 
274321        this . certCache  =  { } ; 
275322        this . options  =  options  ??  { } ; 
276323
277-         const  keyLength  =  options . keyLength  ||  2048 ; 
324+         const  keyLength  =  this . options . keyLength  ||  2048 ; 
278325
279326        if  ( ! KEY_PAIR  ||  KEY_PAIR . length  <  keyLength )  { 
280327            // If we have no key, or not a long enough one, generate one. 
@@ -376,7 +423,7 @@ export class CA {
376423            notAfter, 
377424            signingAlgorithm : KEY_PAIR_ALGO , 
378425            publicKey : leafKeyPair . publicKey , 
379-             signingKey : await   this . caKey , 
426+             signingKey : this . caKey , 
380427            extensions
381428        } ) ; 
382429
0 commit comments