diff --git a/src/certificate-authority.ts b/src/certificate-authority.ts index 2587cf3..43d2a50 100644 --- a/src/certificate-authority.ts +++ b/src/certificate-authority.ts @@ -33,23 +33,43 @@ export default async function installCertificateAuthority(options: Options = {}) uninstall(); ensureConfigDirs(); - debug(`Making a temp working directory for files to copied in`); - let rootKeyPath = mktmp(); - debug(`Generating the OpenSSL configuration needed to setup the certificate authority`); seedConfigFiles(); + // Generate the root key + CA cert into TEMP paths first, so a failed/declined + // trust-store install does not leave behind canonical files that make + // certificateFor's `!existsSync(rootCAKeyPath)` guard skip re-installation + // on subsequent runs. + debug(`Making a temp working directory for files to be copied in`); + let tmpRootKeyPath = mktmp(); + let tmpRootCertPath = mktmp(); + debug(`Generating a private key`); - generateKey(rootKeyPath); + generateKey(tmpRootKeyPath); debug(`Generating a CA certificate`); - openssl(['req', '-new', '-x509', '-config', caSelfSignConfig, '-key', rootKeyPath, '-out', rootCACertPath, '-days', '825']); - - debug('Saving certificate authority credentials'); - await saveCertificateAuthorityCredentials(rootKeyPath); + openssl(['req', '-new', '-x509', '-config', caSelfSignConfig, '-key', tmpRootKeyPath, '-out', tmpRootCertPath, '-days', '825']); + // Install into the OS trust store BEFORE persisting to the canonical paths. + // If addToTrustStores throws (e.g. user declines the Windows Security Warning, + // or `sudo security add-trusted-cert` fails on macOS), the canonical files + // are never written and the next certificateFor() call will retry the whole + // install — including the user-facing prompt. debug(`Adding the root certificate authority to trust stores`); - await currentPlatform.addToTrustStores(rootCACertPath, options); + try { + await currentPlatform.addToTrustStores(tmpRootCertPath, options); + } catch (e) { + rm(tmpRootKeyPath); + rm(tmpRootCertPath); + throw e; + } + + debug(`Persisting root CA certificate and key to their canonical paths`); + writeFile(rootCACertPath, readFile(tmpRootCertPath)); + await saveCertificateAuthorityCredentials(tmpRootKeyPath); + + rm(tmpRootKeyPath); + rm(tmpRootCertPath); } /** diff --git a/src/platforms/win32.ts b/src/platforms/win32.ts index 94a78d7..9d02ba3 100644 --- a/src/platforms/win32.ts +++ b/src/platforms/win32.ts @@ -30,11 +30,17 @@ export default class WindowsPlatform implements Platform { try { run('certutil', ['-addstore', '-user', 'root', certificatePath]); } catch (e) { - e.output.map((buffer: Buffer) => { - if (buffer) { - console.log(buffer.toString()); - } - }); + if (e.output) { + e.output.map((buffer: Buffer) => { + if (buffer) { + console.log(buffer.toString()); + } + }); + } + // certutil non-zero exit means the user declined the Windows Security + // Warning, or the store is otherwise unwritable. Propagate so the caller + // can avoid persisting on-disk state that would block future retries. + throw e; } debug('adding devcert root to Firefox trust store') // Firefox (don't even try NSS certutil, no easy install for Windows)