Skip to content

Commit e1b3a97

Browse files
committed
refactor: simplify P12 to PEM conversion to use OpenSSL only
Remove node-forge fallback - OpenSSL is more robust and available on all macOS runners. Version detection for -legacy flag is preserved.
1 parent 8a54e2d commit e1b3a97

1 file changed

Lines changed: 7 additions & 95 deletions

File tree

‎src/certificate.ts‎

Lines changed: 7 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)