diff --git a/package-lock.json b/package-lock.json index 179dd78..586f602 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ "@typescript-eslint/parser": "^8.68.0", "concurrently": "^10.0.5", "electron": "^44.4.1", - "electron-builder": "^26.15.3", + "electron-builder": "26.16.1", "eslint": "^9.39.5", "eslint-import-resolver-typescript": "^4.4.5", "eslint-plugin-import": "^2.32.0", @@ -754,9 +754,9 @@ } }, "node_modules/@electron/rebuild": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.0.tgz", - "integrity": "sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.1.tgz", + "integrity": "sha512-IS4pG3BysRrCLJ8xTAfvg+IQDbiUoAzdJy2SfjcYKnLDy5DpCdjnPdhS5Zf5UMfrkB24xu0KC+TqcOxg+eG+6w==", "dev": true, "license": "MIT", "dependencies": { @@ -811,9 +811,9 @@ } }, "node_modules/@electron/universal/node_modules/fs-extra": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", - "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", "dev": true, "license": "MIT", "dependencies": { @@ -864,9 +864,9 @@ } }, "node_modules/@electron/windows-sign/node_modules/fs-extra": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", - "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", "dev": true, "license": "MIT", "optional": true, @@ -2769,13 +2769,13 @@ } }, "node_modules/@noble/hashes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", - "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", "engines": { - "node": ">= 20.19.0" + "node": "^14.21.3 || >=16" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -2820,9 +2820,9 @@ } }, "node_modules/@peculiar/asn1-schema": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.4.tgz", - "integrity": "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.11.0.tgz", + "integrity": "sha512-8xh4avo53/vXO6UEv9yaoJ83hY581geaLTygf61NPIXP5AeIZCPjJhpTaYU23IZkG+NpLN/C9FYzWp7hphIT9A==", "dev": true, "license": "MIT", "dependencies": { @@ -6465,9 +6465,9 @@ } }, "node_modules/app-builder-lib": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", - "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "version": "26.16.1", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.16.1.tgz", + "integrity": "sha512-FhaO6YOup01ZfQW0Z6gt3AyukJjv1gW4uFK47jTgwcHZKqyN/fSlK2LqPf9tAeZYLP2bRJLDzeOkRImsw2X4Pg==", "dev": true, "license": "MIT", "dependencies": { @@ -6479,13 +6479,13 @@ "@electron/rebuild": "^4.0.4", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", - "@noble/hashes": "^2.2.0", + "@noble/hashes": "^1.8.0", "@peculiar/webcrypto": "^1.7.1", "@types/fs-extra": "9.0.13", "ajv": "^8.18.0", "asn1js": "^3.0.10", "async-exit-hook": "^2.0.1", - "builder-util": "26.15.3", + "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", @@ -6493,7 +6493,7 @@ "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", - "electron-publish": "26.15.3", + "electron-publish": "26.16.0", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", @@ -6517,8 +6517,8 @@ "node": ">=14.0.0" }, "peerDependencies": { - "dmg-builder": "26.15.3", - "electron-builder-squirrel-windows": "26.15.3" + "dmg-builder": "26.16.1", + "electron-builder-squirrel-windows": "26.16.1" } }, "node_modules/app-builder-lib/node_modules/@electron/get": { @@ -6831,14 +6831,14 @@ } }, "node_modules/asn1js": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", - "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.1.0.tgz", + "integrity": "sha512-HWYi1THkOqguCiO+vwj09ZkQFD2kKEsejYHAYCtX0D80WVdoDnvRc9IukXX/jeb0T9NRWJnRtqwdbHltnFjwXA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "pvtsutils": "^1.3.6", - "pvutils": "^1.1.5", + "pvutils": "^1.2.0", "tslib": "^2.8.1" }, "engines": { @@ -7048,9 +7048,9 @@ "license": "MIT" }, "node_modules/builder-util": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.3.tgz", - "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", + "version": "26.16.0", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.16.0.tgz", + "integrity": "sha512-RLyJhB7Si3YkzKR9ubQslWuXW3Vhs3CGe1i+SeixBZ0qTd1mk3XBmssvY22TlB6CS5blyko8Gu1JzpYk8UkYAg==", "dev": true, "license": "MIT", "dependencies": { @@ -8493,14 +8493,14 @@ } }, "node_modules/dmg-builder": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz", - "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "version": "26.16.1", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.16.1.tgz", + "integrity": "sha512-pnI/3Qb24Uk+rMTgIUrsVUKosVgwmBUdF8Zeb8TexOSbpq8MWc7v6l+n+FrEqVkjNZwzBN+XpDS9ENgZ/rkWAw==", "dev": true, "license": "MIT", "dependencies": { - "app-builder-lib": "26.15.3", - "builder-util": "26.15.3", + "app-builder-lib": "26.16.1", + "builder-util": "26.16.0", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0" } @@ -8705,18 +8705,18 @@ } }, "node_modules/electron-builder": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz", - "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "version": "26.16.1", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.16.1.tgz", + "integrity": "sha512-LrLK65QX5PUYYODXqp23FKrV7CILTtVY7mrJckNknO9jLNSMiqFkKbSMiDRw4CjOADMPVDdWLxY4mezOZWswxg==", "dev": true, "license": "MIT", "dependencies": { - "app-builder-lib": "26.15.3", - "builder-util": "26.15.3", + "app-builder-lib": "26.16.1", + "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", - "dmg-builder": "26.15.3", + "dmg-builder": "26.16.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", @@ -8731,15 +8731,15 @@ } }, "node_modules/electron-builder-squirrel-windows": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", - "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "version": "26.16.1", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.16.1.tgz", + "integrity": "sha512-w0y44wSaT1l6R7CAGmeHn4nHPfvzDyCAU1xJyi1w9SbPYJpYn76SmHDzqHf8Y7l91cPWTdPYBpGQtB2T5mJ08A==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "app-builder-lib": "26.15.3", - "builder-util": "26.15.3", + "app-builder-lib": "26.16.1", + "builder-util": "26.16.0", "electron-winstaller": "5.4.0" } }, @@ -8867,15 +8867,15 @@ } }, "node_modules/electron-publish": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", - "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", + "version": "26.16.0", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.16.0.tgz", + "integrity": "sha512-Vt3KzQIiw9BImvNOYtndg9Mjki+tl4+1sQiC/+G5j8khWaENOJFWodiB+sUl6yyHwtd37avehskdtPw7f8y/+Q==", "dev": true, "license": "MIT", "dependencies": { "@types/fs-extra": "^9.0.11", "aws4": "^1.13.2", - "builder-util": "26.15.3", + "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "form-data": "^4.0.5", @@ -10961,9 +10961,9 @@ } }, "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", "dev": true, "license": "BSD-2-Clause" }, @@ -13674,9 +13674,9 @@ } }, "node_modules/node-abi": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.35.0.tgz", - "integrity": "sha512-ymk4aIzxdPopw2giv8Fs1Ec6vybGkjmyxUwVqhkI4MCy2tVfXdkOGGWieWVjL0THgH+7a8lRdevyupoYj3Js/Q==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.38.0.tgz", + "integrity": "sha512-suSdznUPfabdaeLiNERA0LHod14RhcEn8wMPUZEQ6bKvajUCZ+aizY4pIvlLU+gImeLKp2bFDHhOc9yonISsXw==", "dev": true, "license": "MIT", "dependencies": { @@ -13809,9 +13809,9 @@ } }, "node_modules/node-gyp/node_modules/undici": { - "version": "6.28.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.1.tgz", - "integrity": "sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==", + "version": "6.29.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.1.tgz", + "integrity": "sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==", "dev": true, "license": "MIT", "engines": { @@ -14307,13 +14307,16 @@ } }, "node_modules/pkijs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", - "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.1.tgz", + "integrity": "sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA==", "dev": true, "license": "BSD-3-Clause", + "workspaces": [ + "website" + ], "dependencies": { - "@noble/hashes": "1.4.0", + "@noble/hashes": "1.8.0", "asn1js": "^3.0.6", "bytestreamjs": "^2.0.1", "pvtsutils": "^1.3.6", @@ -14324,19 +14327,6 @@ "node": ">=16.0.0" } }, - "node_modules/pkijs/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", diff --git a/package.json b/package.json index 90fb498..6b60829 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "@typescript-eslint/parser": "^8.68.0", "concurrently": "^10.0.5", "electron": "^44.4.1", - "electron-builder": "^26.15.3", + "electron-builder": "26.16.1", "eslint": "^9.39.5", "eslint-import-resolver-typescript": "^4.4.5", "eslint-plugin-import": "^2.32.0", diff --git a/tests/mac-signing-toolchain.test.ts b/tests/mac-signing-toolchain.test.ts new file mode 100644 index 0000000..f1ec03e --- /dev/null +++ b/tests/mac-signing-toolchain.test.ts @@ -0,0 +1,124 @@ +/** Execute the installed signing helper with fake security commands and fake + * certificates. Never touch a real keychain, credential, or certificate file. + * Regression: electron-builder #10066 used the p12 password to unlock a + * temporary keychain that had been created with a different random password. */ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import fs from "node:fs"; +import path from "node:path"; +import * as crypto from "node:crypto"; +import { createRequire } from "node:module"; +import vm from "node:vm"; + +const require = createRequire(import.meta.url); +const sourcePath = require.resolve("app-builder-lib/out/codeSign/macCodeSign.js"); +const source = fs.readFileSync(sourcePath, "utf8"); +const generatedPassword = Buffer.alloc(32, 42).toString("base64"); +const applicationPassword = "fixture-application-export-password"; +const installerPassword = "fixture-installer-export-password"; +type Command = { executable: string; args: string[] }; +type Options = { installer?: boolean; source?: string }; + +async function simulate({ installer = false, source: code = source }: Options = {}) { + const commands: Command[] = []; + const imports: string[] = []; + const fakeModules: Record = { + "builder-util": { + exec: async (executable: string, args: string[]) => { + assert.equal(executable, "/usr/bin/security"); + commands.push({ executable, args: [...args] }); + return ""; + }, + }, + "../util/dynamicImport": {}, + crypto: { ...crypto, randomBytes: () => Buffer.alloc(32, 42) }, + "fs/promises": {}, + "lazy-val": { Lazy: class { get value() { throw new Error("Unexpected real certificate-cache initialization"); } } }, + os: { tmpdir: () => "/fixture/tmp" }, + path, + "temp-file": {}, + "../util/flags": {}, + "./codesign": { + importCertificate: async (link: string) => { + assert.ok(["fixture-application", "fixture-installer"].includes(link)); + imports.push(link); + return `/fixture/${link}.p12`; + }, + }, + }; + const exports: { createKeychain?: (options: unknown) => Promise<{ keychainFile: string }> } = {}; + vm.runInNewContext(code, { + exports, + __dirname: path.dirname(sourcePath), + // Skip the unrelated bundled-root-certificate cache in this sandbox. + process: { env: { TRAVIS: "true" }, platform: "darwin" }, + require: (id: string) => { + assert.ok(Object.hasOwn(fakeModules, id), `Unexpected dependency: ${id}`); + return fakeModules[id]; + }, + }, { filename: sourcePath }); + assert.ok(exports.createKeychain); + const result = await exports.createKeychain({ + tmpDir: {}, + currentDir: "/fixture/project", + cscLink: "fixture-application", + cscKeyPassword: applicationPassword, + ...(installer ? { cscILink: "fixture-installer", cscIKeyPassword: installerPassword } : {}), + }); + return { commands, imports, result }; +} + +function argument(command: Command, flag: string) { + const index = command.args.indexOf(flag); + assert.ok(index >= 0, `Missing ${flag} in ${command.args[0]}`); + return command.args[index + 1]; +} + +function verifyKeychainPassword(commands: Command[]) { + const created = commands.find(c => c.args[0] === "create-keychain")!; + assert.equal(argument(created, "-p"), generatedPassword); + const unlocked = commands.find(c => c.args[0] === "unlock-keychain")!; + assert.equal(argument(unlocked, "-p"), generatedPassword); + const partitions = commands.filter(c => c.args[0] === "set-key-partition-list"); + assert.ok(partitions.length > 0); + for (const command of partitions) { + assert.equal(argument(command, "-k"), generatedPassword, "partition-list access requires the generated keychain password, not an export password"); + assert.equal(command.args.at(-1), created.args.at(-1)); + } +} + +describe("macOS signing toolchain password separation", () => { + it("pins the tested packaging tool rather than allowing an implicit upgrade", () => { + const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")); + const version = pkg.devDependencies["electron-builder"]; + assert.match(version, /^\d+\.\d+\.\d+$/); + assert.equal(require("electron-builder/package.json").version, version); + assert.equal(require("app-builder-lib/package.json").version, version); + }); + + it("uses the export password for import and the generated password for keychain access", async () => { + const { commands, imports, result } = await simulate(); + assert.deepEqual(imports, ["fixture-application"]); + const imported = commands.filter(c => c.args[0] === "import"); + assert.equal(imported.length, 1); + assert.equal(argument(imported[0]!, "-P"), applicationPassword); + assert.equal(argument(imported[0]!, "-k"), result.keychainFile); + verifyKeychainPassword(commands); + }); + + it("keeps both certificate export passwords distinct from their shared keychain password", async () => { + const { commands, imports } = await simulate({ installer: true }); + assert.deepEqual(imports, ["fixture-application", "fixture-installer"]); + const imported = commands.filter(c => c.args[0] === "import"); + assert.deepEqual(imported.map(c => argument(c, "-P")), [applicationPassword, installerPassword]); + assert.equal(commands.filter(c => c.args[0] === "set-key-partition-list").length, 2); + verifyKeychainPassword(commands); + }); + + it("the regression check rejects the historical password mix-up", async () => { + const broken = source.replace('"-k", keychainPassword, keychainFile', '"-k", password, keychainFile'); + assert.notEqual(broken, source, "the negative control must actually recreate the bug"); + const { commands } = await simulate({ source: broken }); + assert.throws(() => verifyKeychainPassword(commands), /partition-list access requires/); + }); +});