@@ -214,17 +214,18 @@ export async function isOpenSSL3OrHigher(): Promise<boolean> {
214214
215215/**
216216 * Convert P12 format to PEM format using OpenSSL.
217- * This method is more robust than node-forge for certain p12 files,
218- * especially those using RC2-40-CBC encryption which requires the legacy provider in OpenSSL 3.x.
217+ * This is necessary because importing P12 directly into macOS keychain
218+ * can trigger interactive prompts, while PEM import is non-interactive.
219+ * Detects OpenSSL version and adds -legacy flag for OpenSSL 3.x to support
220+ * RC2-40-CBC and other legacy algorithms commonly found in Apple certificates.
219221 * @param p12Data The P12 certificate data as a Buffer.
220222 * @param password The password for the P12 file (empty string if no password).
221223 * @returns A string containing the PEM format certificate and private key.
222224 */
223- export async function convertP12ToPemWithOpenSSL (
225+ export async function convertP12ToPem (
224226 p12Data : Buffer ,
225227 password = ''
226228) : Promise < string > {
227- // Create temporary files for input and output
228229 const tempDir = os . tmpdir ( )
229230 const p12Path = path . join (
230231 tempDir ,
@@ -236,17 +237,15 @@ export async function convertP12ToPemWithOpenSSL(
236237 )
237238
238239 try {
239- // Write p12 data to temporary file
240240 fs . writeFileSync ( p12Path , p12Data as Uint8Array )
241241
242- // Build OpenSSL command arguments
243242 const args = [
244243 'pkcs12' ,
245244 '-in' ,
246245 p12Path ,
247246 '-out' ,
248247 pemPath ,
249- '-nodes' , // Don't encrypt private key in output
248+ '-nodes' ,
250249 '-passin' ,
251250 `pass:${ password } `
252251 ]
@@ -257,20 +256,18 @@ export async function convertP12ToPemWithOpenSSL(
257256 args . push ( '-legacy' )
258257 }
259258
260- // Run OpenSSL conversion
261259 const result = await spawn ( 'openssl' , args )
262260 if ( result . Code !== 0 ) {
263261 throw new Error (
264262 `OpenSSL pkcs12 command failed with code ${ result . Code } : ${ result . Stderr } `
265263 )
266264 }
267265
268- // Read the generated PEM file
269266 const pemData = fs . readFileSync ( pemPath , 'utf-8' )
270267 return pemData
271268 } catch ( error ) {
272269 throw new Error (
273- `Failed to convert P12 to PEM using OpenSSL : ${ error instanceof Error ? error . message : String ( error ) } `
270+ `Failed to convert P12 to PEM: ${ error instanceof Error ? error . message : String ( error ) } `
274271 )
275272 } finally {
276273 // Clean up temporary files
@@ -287,91 +284,6 @@ export async function convertP12ToPemWithOpenSSL(
287284 }
288285}
289286
290- /**
291- * Convert P12 format to PEM format using node-forge.
292- * This is kept as a fallback method.
293- * @param p12Data The P12 certificate data as a Buffer.
294- * @param password The password for the P12 file (empty string if no password).
295- * @returns A string containing the PEM format certificate and private key.
296- */
297- export async function convertP12ToPemWithForge (
298- p12Data : Buffer ,
299- password = ''
300- ) : Promise < string > {
301- try {
302- // Convert Buffer to node-forge compatible format
303- const p12Der = forge . util . decode64 ( p12Data . toString ( 'base64' ) )
304- const p12Asn1 = forge . asn1 . fromDer ( p12Der )
305-
306- // Parse the PKCS#12 structure
307- const p12 = forge . pkcs12 . pkcs12FromAsn1 ( p12Asn1 , password )
308-
309- // Extract bags
310- const certBags = p12 . getBags ( { bagType : forge . pki . oids . certBag } )
311- const keyBags = p12 . getBags ( { bagType : forge . pki . oids . pkcs8ShroudedKeyBag } )
312-
313- // Build PEM output
314- const pemParts : string [ ] = [ ]
315-
316- // Add private key if found
317- const keyBagArray = keyBags [ forge . pki . oids . pkcs8ShroudedKeyBag ]
318- if ( keyBagArray && keyBagArray . length > 0 ) {
319- const keyBag = keyBagArray [ 0 ]
320- if ( keyBag . key ) {
321- pemParts . push ( forge . pki . privateKeyToPem ( keyBag . key ) )
322- }
323- }
324-
325- // Add certificates if found
326- const certBagArray = certBags [ forge . pki . oids . certBag ]
327- if ( certBagArray && certBagArray . length > 0 ) {
328- for ( const certBag of certBagArray ) {
329- if ( certBag . cert ) {
330- pemParts . push ( forge . pki . certificateToPem ( certBag . cert ) )
331- }
332- }
333- }
334-
335- if ( pemParts . length === 0 ) {
336- throw new Error ( 'No certificates or keys found in P12 file' )
337- }
338-
339- return pemParts . join ( '\n' )
340- } catch ( error ) {
341- throw new Error (
342- `Failed to convert P12 to PEM using node-forge: ${ error instanceof Error ? error . message : String ( error ) } `
343- )
344- }
345- }
346-
347- /**
348- * Convert P12 format to PEM format.
349- * Tries OpenSSL first (more robust), falls back to node-forge if OpenSSL fails.
350- * @param p12Data The P12 certificate data as a Buffer.
351- * @param password The password for the P12 file (empty string if no password).
352- * @returns A string containing the PEM format certificate and private key.
353- */
354- export async function convertP12ToPem (
355- p12Data : Buffer ,
356- password = ''
357- ) : Promise < string > {
358- try {
359- // Try OpenSSL first - it's more robust and handles RC2-encrypted p12 files
360- return await convertP12ToPemWithOpenSSL ( p12Data , password )
361- } catch ( opensslError ) {
362- // Fall back to node-forge if OpenSSL fails
363- try {
364- return await convertP12ToPemWithForge ( p12Data , password )
365- } catch ( forgeError ) {
366- // If both fail, throw a combined error message
367- throw new Error (
368- `Failed to convert P12 to PEM. OpenSSL error: ${ opensslError instanceof Error ? opensslError . message : String ( opensslError ) } . ` +
369- `node-forge error: ${ forgeError instanceof Error ? forgeError . message : String ( forgeError ) } `
370- )
371- }
372- }
373- }
374-
375287/**
376288 * Import the provided secret value into a keychain.
377289 * Supports both PEM and P12 formats, with or without base64 encoding.
0 commit comments