@@ -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