Skip to content

set-key-partition-list uses the .p12 import password instead of the generated keychain password, breaking CSC_LINK-based macOS signing #10066

Description

@deadsoftie

electron-builder version: 26.15.3

Environment:

  • macOS 27.0 (build 26A5388g) - reported via sw_vers; this is a very new/beta build, worth noting in case it's version-specific, though the root cause below looks like a straightforward logic bug independent of macOS version.
  • Darwin ... 27.0.0 ... arm64 (Apple Silicon)
  • Node v26.5.0, pnpm 11.17.0
  • Package manager: pnpm workspace (monorepo)

Description:

When signing a macOS app via CSC_LINK/CSC_KEY_PASSWORD (pointing at a local .p12 file), the build fails during keychain setup with:

⨯ /usr/bin/security process failed 1
Exit code:
1
Output:

Exit code: 1. Command failed: /usr/bin/security set-key-partition-list -S apple-tool:,apple: -s -k <REDACTED> /var/folders/.../<hash>.keychain
security: SecKeychainUnlock: The user name or passphrase you entered is not correct.

This reproduces identically with both an empty CSC_KEY_PASSWORD and a real, non-empty one - same failure, same error, just a different (still wrong) string after -k. That rules out "just set/fix the .p12 password" as a workaround, since the password being passed to set-key-partition-list -k is never the correct one regardless of what it is.

Root cause:

In packages/app-builder-lib/src/codeSign/macCodeSign.ts (compiled: app-builder-lib/out/codeSign/macCodeSign.js), createKeychain() generates a random password to create and unlock the temporary keychain it builds for signing:

// macCodeSign.js, line 137
const keychainPassword = (0, crypto_1.randomBytes)(32).toString("base64");
const securityCommands = [
    ["create-keychain", "-p", keychainPassword, keychainFile],
    ["unlock-keychain", "-p", keychainPassword, keychainFile],
    ["set-keychain-settings", keychainFile],
];

But keychainPassword is never passed down into importCerts() - only the .p12's own import password(s) are (cscPasswords, line 155/159). importCerts() then reuses that .p12 import password for the -k flag of set-key-partition-list, which is documented (and behaves, per this bug) as needing the keychain's own password, not the imported item's password:

// macCodeSign.js, importCerts(), lines 161-169
async function importCerts(keychainFile, paths, keyPasswords) {
    for (let i = 0; i < paths.length; i++) {
        const password = keyPasswords[i] ?? "";
        await exec("/usr/bin/security", ["import", paths[i], "-k", keychainFile, "-T", "/usr/bin/codesign", "-T", "/usr/bin/productbuild", "-P", password]);
        // BUG: `password` here is the .p12's own import password (correct for `security import -P`
        // just above), but set-key-partition-list's `-k` flag needs the *keychain's* unlock
        // password (`keychainPassword` from createKeychain(), never passed through here) instead.
        await exec("/usr/bin/security", ["set-key-partition-list", "-S", "apple-tool:,apple:", "-s", "-k", password, keychainFile]);
    }
    return { keychainFile };
}

security import -k -P is correct - that -P is genuinely the item's import password. But security set-key-partition-list -k needs the keychain's unlock password to succeed, per security's own man page and every other caller of this command I could find (including electron-builder's own create-keychain/unlock-keychain calls two lines above, which correctly use keychainPassword).

Steps to reproduce:

  1. Create a Developer ID Application (or any) signing certificate, export as .p12 (with or without a password - both fail).
  2. Set CSC_LINK=/path/to/cert.p12 and CSC_KEY_PASSWORD=.
  3. Run electron-builder --mac with any mac.identity unset (so it goes through the normal CSC_LINK import flow).
  4. Observe the SecKeychainUnlock failure above.

Suggested fix:

Pass keychainPassword from createKeychain() into importCerts() and use it for the set-key-partition-list -k argument, keeping the existing password (import password) only for the security import -P call:

await exec("/usr/bin/security", ["set-key-partition-list", "-S", "apple-tool:,apple:", "-s", "-k", keychainPassword, keychainFile]);
Workaround I used:

don't set CSC_LINK/CSC_KEY_PASSWORD at all. If the certificate is already present in an existing keychain (e.g. the login keychain, from generating the CSR via Keychain Access), electron-builder's identity auto-discovery (CSC_IDENTITY_AUTO_DISCOVERY, on by default) finds and uses it via security find-identity - a code path that never calls createKeychain/importCerts, so the bug never triggers. This isn't viable for CI, though, where there's no pre-populated keychain to discover from - that's presumably the exact scenario CSC_LINK exists for.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions