Skip to content

Root CA persisted before trust-store install — failed install leaves devcert in a broken non-retrying state #118

Description

@ccstr

Bug

installCertificateAuthority in src/certificate-authority.ts persists the root CA cert and key to their canonical paths before attempting to add the CA to the OS trust store. If addToTrustStores fails or is declined (e.g. the user clicks "No" on the Windows Security Warning), the on-disk state remains, and certificateFor in src/index.ts then skips re-installation on every subsequent run because its guard is !existsSync(rootCAKeyPath). The browser stays broken with NET::ERR_CERT_AUTHORITY_INVALID, and devcert never re-prompts.

Current flow

In src/certificate-authority.ts:

generateKey(rootKeyPath);                                                    // tmp key
openssl(['req', '-new', '-x509', /*...*/, '-out', rootCACertPath, /*...*/]); // ← PERSISTS rootCACertPath
await saveCertificateAuthorityCredentials(rootKeyPath);                      // ← PERSISTS rootCAKeyPath
await currentPlatform.addToTrustStores(rootCACertPath, options);             // ← may fail/be declined silently

In src/platforms/win32.ts addToTrustStores:

try {
  run('certutil', ['-addstore', '-user', 'root', certificatePath]);
} catch (e) {
  e.output.map((buffer) => { if (buffer) console.log(buffer.toString()); });
  // error swallowed; no rethrow
}

In src/index.ts certificateFor:

if (!existsSync(rootCAKeyPath)) {
  await installCertificateAuthority(options);  // never reached again once the file exists
}

Reproduction (Windows)

  1. Fresh machine, no prior devcert state.
  2. Call devcert.certificateFor('localcdn.example.com') from a Node process.
  3. Windows shows the Security Warning dialog ("You are about to install a certificate from a certification authority claiming to represent: devcert ..."). Click No.
  4. devcert's win32 addToTrustStores swallows the certutil error and installCertificateAuthority returns successfully. Files at %LOCALAPPDATA%\devcert\certificate-authority\{certificate.cert,private-key.key} exist on disk.
  5. Open https://localcdn.example.com:<port>/ in the browser → NET::ERR_CERT_AUTHORITY_INVALID.
  6. Re-run devcert.certificateFor(...). devcert sees rootCAKeyPath exists, skips installCertificateAuthority entirely, never re-prompts. Browser remains broken.

The same shape applies on macOS/Linux if their sudo security add-trusted-cert / sudo update-ca-certificates call fails after the canonical files have already been written — those platforms do propagate errors, but the next run still skips install because the files are on disk.

Proposed fix

Generate the cert+key into temp paths, install into the trust store first, and persist to canonical paths only after the trust install succeeds. Also propagate certutil failures from win32.addToTrustStores (keeping the Firefox open-failure tolerance — most users don't have Firefox).

PR: #117 ← will update with the new PR number after opening

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions