Initial commit

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
dinlo
2026-05-31 18:44:04 +08:00
commit 436a9631fc
8616 changed files with 1389957 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
import { TmpDir } from "temp-file";
/** @private */
export declare function importCertificate(cscLink: string, tmpDir: TmpDir, currentDir: string): Promise<string>;
+49
View File
@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.importCertificate = importCertificate;
const builder_util_1 = require("builder-util");
const fs_extra_1 = require("fs-extra");
const os_1 = require("os");
const path = require("path");
const binDownload_1 = require("../binDownload");
/** @private */
async function importCertificate(cscLink, tmpDir, currentDir) {
var _a, _b;
cscLink = cscLink.trim();
let file = null;
if ((cscLink.length > 3 && cscLink[1] === ":") || cscLink.startsWith("/") || cscLink.startsWith(".")) {
file = cscLink;
}
else if (cscLink.startsWith("file://")) {
file = cscLink.substring("file://".length);
}
else if (cscLink.startsWith("~/")) {
file = path.join((0, os_1.homedir)(), cscLink.substring("~/".length));
}
else if (cscLink.startsWith("https://")) {
const tempFile = await tmpDir.getTempFile({ suffix: ".p12" });
await (0, binDownload_1.download)(cscLink, tempFile);
return tempFile;
}
else {
const mimeType = (_a = /data:.*;base64,/.exec(cscLink)) === null || _a === void 0 ? void 0 : _a[0];
if (mimeType || cscLink.length > 2048 || cscLink.endsWith("=")) {
const tempFile = await tmpDir.getTempFile({ suffix: ".p12" });
await (0, fs_extra_1.outputFile)(tempFile, Buffer.from(cscLink.substring((_b = mimeType === null || mimeType === void 0 ? void 0 : mimeType.length) !== null && _b !== void 0 ? _b : 0), "base64"));
return tempFile;
}
file = cscLink;
}
file = path.resolve(currentDir, file);
const stat = await (0, builder_util_1.statOrNull)(file);
if (stat == null) {
throw new builder_util_1.InvalidConfigurationError(`${file} doesn't exist`);
}
else if (!stat.isFile()) {
throw new builder_util_1.InvalidConfigurationError(`${file} not a file`);
}
else {
return file;
}
}
//# sourceMappingURL=codesign.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"codesign.js","sourceRoot":"","sources":["../../src/codeSign/codesign.ts"],"names":[],"mappings":";;AAQA,8CAiCC;AAzCD,+CAAoE;AACpE,uCAAqC;AACrC,2BAA4B;AAC5B,6BAA4B;AAE5B,gDAAyC;AAEzC,eAAe;AACR,KAAK,UAAU,iBAAiB,CAAC,OAAe,EAAE,MAAc,EAAE,UAAkB;;IACzF,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAA;IAExB,IAAI,IAAI,GAAkB,IAAI,CAAA;IAC9B,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrG,IAAI,GAAG,OAAO,CAAA;IAChB,CAAC;SAAM,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACzC,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC5C,CAAC;SAAM,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAA,YAAO,GAAE,EAAE,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;IAC7D,CAAC;SAAM,IAAI,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;QAC7D,MAAM,IAAA,sBAAQ,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;QACjC,OAAO,QAAQ,CAAA;IACjB,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,GAAG,MAAA,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,0CAAG,CAAC,CAAC,CAAA;QACrD,IAAI,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;YAC7D,MAAM,IAAA,qBAAU,EAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,MAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,MAAM,mCAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAA;YAC3F,OAAO,QAAQ,CAAA;QACjB,CAAC;QACD,IAAI,GAAG,OAAO,CAAA;IAChB,CAAC;IAED,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;IACrC,MAAM,IAAI,GAAG,MAAM,IAAA,yBAAU,EAAC,IAAI,CAAC,CAAA;IACnC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,wCAAyB,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAA;IAC9D,CAAC;SAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAC1B,MAAM,IAAI,wCAAyB,CAAC,GAAG,IAAI,aAAa,CAAC,CAAA;IAC3D,CAAC;SAAM,CAAC;QACN,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC","sourcesContent":["import { InvalidConfigurationError, statOrNull } from \"builder-util\"\nimport { outputFile } from \"fs-extra\"\nimport { homedir } from \"os\"\nimport * as path from \"path\"\nimport { TmpDir } from \"temp-file\"\nimport { download } from \"../binDownload\"\n\n/** @private */\nexport async function importCertificate(cscLink: string, tmpDir: TmpDir, currentDir: string): Promise<string> {\n cscLink = cscLink.trim()\n\n let file: string | null = null\n if ((cscLink.length > 3 && cscLink[1] === \":\") || cscLink.startsWith(\"/\") || cscLink.startsWith(\".\")) {\n file = cscLink\n } else if (cscLink.startsWith(\"file://\")) {\n file = cscLink.substring(\"file://\".length)\n } else if (cscLink.startsWith(\"~/\")) {\n file = path.join(homedir(), cscLink.substring(\"~/\".length))\n } else if (cscLink.startsWith(\"https://\")) {\n const tempFile = await tmpDir.getTempFile({ suffix: \".p12\" })\n await download(cscLink, tempFile)\n return tempFile\n } else {\n const mimeType = /data:.*;base64,/.exec(cscLink)?.[0]\n if (mimeType || cscLink.length > 2048 || cscLink.endsWith(\"=\")) {\n const tempFile = await tmpDir.getTempFile({ suffix: \".p12\" })\n await outputFile(tempFile, Buffer.from(cscLink.substring(mimeType?.length ?? 0), \"base64\"))\n return tempFile\n }\n file = cscLink\n }\n\n file = path.resolve(currentDir, file)\n const stat = await statOrNull(file)\n if (stat == null) {\n throw new InvalidConfigurationError(`${file} doesn't exist`)\n } else if (!stat.isFile()) {\n throw new InvalidConfigurationError(`${file} not a file`)\n } else {\n return file\n }\n}\n"]}
+28
View File
@@ -0,0 +1,28 @@
import { SignOptions } from "@electron/osx-sign/dist/cjs/types";
import { TmpDir } from "builder-util";
import { Nullish } from "builder-util-runtime";
export declare const appleCertificatePrefixes: string[];
export type CertType = "Developer ID Application" | "Developer ID Installer" | "3rd Party Mac Developer Application" | "3rd Party Mac Developer Installer" | "Mac Developer" | "Apple Development" | "Apple Distribution";
export interface CodeSigningInfo {
keychainFile?: string | null;
}
export declare function isSignAllowed(isPrintWarn?: boolean): boolean;
export declare function reportError(isMas: boolean, certificateTypes: CertType[], qualifier: string | Nullish, keychainFile: string | Nullish, isForceCodeSigning: boolean): Promise<void>;
export interface CreateKeychainOptions {
tmpDir: TmpDir;
cscLink: string;
cscKeyPassword: string;
cscILink?: string | null;
cscIKeyPassword?: string | null;
currentDir: string;
}
export declare function removeKeychain(keychainFile: string, printWarn?: boolean): Promise<any>;
export declare function createKeychain({ tmpDir, cscLink, cscKeyPassword, cscILink, cscIKeyPassword, currentDir }: CreateKeychainOptions): Promise<CodeSigningInfo>;
export declare function sign(opts: SignOptions): Promise<void>;
export declare let findIdentityRawResult: Promise<Array<string>> | null;
export declare class Identity {
readonly name: string;
readonly hash?: string;
constructor(name: string, hash?: string);
}
export declare function findIdentity(certType: CertType, qualifier?: string | null, keychain?: string | null): Promise<Identity | null>;
+283
View File
@@ -0,0 +1,283 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findIdentityRawResult = exports.appleCertificatePrefixes = void 0;
exports.isSignAllowed = isSignAllowed;
exports.reportError = reportError;
exports.removeKeychain = removeKeychain;
exports.createKeychain = createKeychain;
exports.sign = sign;
exports.findIdentity = findIdentity;
const osx_sign_1 = require("@electron/osx-sign");
const util_identities_1 = require("@electron/osx-sign/dist/cjs/util-identities");
const builder_util_1 = require("builder-util");
const crypto_1 = require("crypto");
const promises_1 = require("fs/promises");
const lazy_val_1 = require("lazy-val");
const os_1 = require("os");
const path = require("path");
const temp_file_1 = require("temp-file");
const flags_1 = require("../util/flags");
const codesign_1 = require("./codesign");
exports.appleCertificatePrefixes = ["Developer ID Application:", "Developer ID Installer:", "3rd Party Mac Developer Application:", "3rd Party Mac Developer Installer:"];
function isSignAllowed(isPrintWarn = true) {
if (process.platform !== "darwin") {
if (isPrintWarn) {
builder_util_1.log.warn({ reason: "supported only on macOS" }, "skipped macOS application code signing");
}
return false;
}
const buildForPrWarning = "There are serious security concerns with CSC_FOR_PULL_REQUEST=true (see the CircleCI documentation (https://circleci.com/docs/1.0/fork-pr-builds/) for details)" +
"\nIf you have SSH keys, sensitive env vars or AWS credentials stored in your project settings and untrusted forks can make pull requests against your repo, then this option isn't for you.";
if ((0, builder_util_1.isPullRequest)()) {
if ((0, builder_util_1.isEnvTrue)(process.env.CSC_FOR_PULL_REQUEST)) {
if (isPrintWarn) {
builder_util_1.log.warn(buildForPrWarning);
}
}
else {
if (isPrintWarn) {
// https://github.com/electron-userland/electron-builder/issues/1524
builder_util_1.log.warn("Current build is a part of pull request, code signing will be skipped." + "\nSet env CSC_FOR_PULL_REQUEST to true to force code signing." + `\n${buildForPrWarning}`);
}
return false;
}
}
return true;
}
async function reportError(isMas, certificateTypes, qualifier, keychainFile, isForceCodeSigning) {
const logFields = {};
if (qualifier == null) {
logFields.reason = "";
if ((0, flags_1.isAutoDiscoveryCodeSignIdentity)()) {
logFields.reason += `cannot find valid "${certificateTypes.join(", ")}" identity${isMas ? "" : ` or custom non-Apple code signing certificate, it could cause some undefined behaviour, e.g. macOS localized description not visible`}`;
}
logFields.reason += ", see https://electron.build/code-signing";
if (!(0, flags_1.isAutoDiscoveryCodeSignIdentity)()) {
logFields.CSC_IDENTITY_AUTO_DISCOVERY = false;
}
}
else {
logFields.reason = "Identity name is specified, but no valid identity with this name in the keychain";
logFields.identity = qualifier;
}
const args = ["find-identity"];
if (keychainFile != null) {
args.push(keychainFile);
}
if (qualifier != null || (0, flags_1.isAutoDiscoveryCodeSignIdentity)()) {
logFields.allIdentities = (await (0, builder_util_1.exec)("/usr/bin/security", args))
.trim()
.split("\n")
.filter(it => !(it.includes("Policy: X.509 Basic") || it.includes("Matching identities")))
.join("\n");
}
const skipMessage = "skipped macOS application code signing";
if (isMas || isForceCodeSigning) {
throw new Error(builder_util_1.Logger.createMessage(skipMessage, logFields, "error", it => it));
}
else {
builder_util_1.log.warn(logFields, skipMessage);
}
}
// "Note that filename will not be searched to resolve the signing identity's certificate chain unless it is also on the user's keychain search list."
// but "security list-keychains" doesn't support add - we should 1) get current list 2) set new list - it is very bad http://stackoverflow.com/questions/10538942/add-a-keychain-to-search-list
// "overly complicated and introduces a race condition."
// https://github.com/electron-userland/electron-builder/issues/398
const bundledCertKeychainAdded = new lazy_val_1.Lazy(async () => {
// copy to temp and then atomic rename to final path
const cacheDir = getCacheDirectory();
const tmpKeychainPath = path.join(cacheDir, (0, temp_file_1.getTempName)("electron-builder-root-certs"));
const keychainPath = path.join(cacheDir, "electron-builder-root-certs.keychain");
const results = await Promise.all([
listUserKeychains(),
(0, builder_util_1.copyFile)(path.join(__dirname, "..", "..", "certs", "root_certs.keychain"), tmpKeychainPath).then(() => (0, promises_1.rename)(tmpKeychainPath, keychainPath)),
]);
const list = results[0];
if (!list.includes(keychainPath)) {
await (0, builder_util_1.exec)("/usr/bin/security", ["list-keychains", "-d", "user", "-s", keychainPath].concat(list));
}
});
function getCacheDirectory() {
const env = process.env.ELECTRON_BUILDER_CACHE;
return (0, builder_util_1.isEmptyOrSpaces)(env) ? path.join((0, os_1.homedir)(), "Library", "Caches", "electron-builder") : path.resolve(env);
}
function listUserKeychains() {
return (0, builder_util_1.exec)("/usr/bin/security", ["list-keychains", "-d", "user"]).then(it => it
.split("\n")
.map(it => {
const r = it.trim();
return r.substring(1, r.length - 1);
})
.filter(it => it.length > 0));
}
function removeKeychain(keychainFile, printWarn = true) {
return (0, builder_util_1.exec)("/usr/bin/security", ["delete-keychain", keychainFile]).catch((e) => {
if (printWarn) {
builder_util_1.log.warn({ file: keychainFile, error: e.stack || e }, "cannot delete keychain");
}
return (0, builder_util_1.unlinkIfExists)(keychainFile);
});
}
async function createKeychain({ tmpDir, cscLink, cscKeyPassword, cscILink, cscIKeyPassword, currentDir }) {
// travis has correct AppleWWDRCA cert
if (process.env.TRAVIS !== "true") {
await bundledCertKeychainAdded.value;
}
// https://github.com/electron-userland/electron-builder/issues/3685
// use constant file
const keychainFile = path.join(process.env.APP_BUILDER_TMP_DIR || (0, os_1.tmpdir)(), `${(0, crypto_1.createHash)("sha256").update(currentDir).update("app-builder").digest("hex")}.keychain`);
// noinspection JSUnusedLocalSymbols
await removeKeychain(keychainFile, false).catch(_ => {
/* ignore*/
});
const certLinks = [cscLink];
if (cscILink != null) {
certLinks.push(cscILink);
}
const certPaths = new Array(certLinks.length);
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],
];
// https://stackoverflow.com/questions/42484678/codesign-keychain-gets-ignored
// https://github.com/electron-userland/electron-builder/issues/1457
const list = await listUserKeychains();
if (!list.includes(keychainFile)) {
securityCommands.push(["list-keychains", "-d", "user", "-s", keychainFile].concat(list));
}
await Promise.all([
// we do not clear downloaded files - will be removed on tmpDir cleanup automatically. not a security issue since in any case data is available as env variables and protected by password.
...certLinks.map((link, i) => (0, codesign_1.importCertificate)(link, tmpDir, currentDir).then(it => (certPaths[i] = it))),
// queue each security command
securityCommands.reduce((promise, cmd) => promise.then(() => (0, builder_util_1.exec)("/usr/bin/security", cmd)), new Promise(resolve => resolve(null))),
]);
const cscPasswords = [cscKeyPassword];
if (cscIKeyPassword != null) {
cscPasswords.push(cscIKeyPassword);
}
return await importCerts(keychainFile, certPaths, cscPasswords);
}
async function importCerts(keychainFile, paths, keyPasswords) {
var _a;
for (let i = 0; i < paths.length; i++) {
const password = (_a = keyPasswords[i]) !== null && _a !== void 0 ? _a : "";
await (0, builder_util_1.exec)("/usr/bin/security", ["import", paths[i], "-k", keychainFile, "-T", "/usr/bin/codesign", "-T", "/usr/bin/productbuild", "-P", password]);
// https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p
// https://github.com/electron-userland/electron-packager/issues/701#issuecomment-322315996
await (0, builder_util_1.exec)("/usr/bin/security", ["set-key-partition-list", "-S", "apple-tool:,apple:", "-s", "-k", password, keychainFile]);
}
return {
keychainFile,
};
}
async function sign(opts) {
return (0, builder_util_1.retry)(() => (0, osx_sign_1.signAsync)(opts), {
retries: 3,
interval: 5000,
backoff: 5000,
});
}
exports.findIdentityRawResult = null;
async function getValidIdentities(keychain) {
function addKeychain(args) {
if (keychain != null) {
args.push(keychain);
}
return args;
}
let result = exports.findIdentityRawResult;
if (result == null || keychain != null) {
// https://github.com/electron-userland/electron-builder/issues/481
// https://github.com/electron-userland/electron-builder/issues/535
result = Promise.all([
(0, builder_util_1.exec)("/usr/bin/security", addKeychain(["find-identity", "-v"])).then(it => it
.trim()
.split("\n")
.filter(it => {
for (const prefix of exports.appleCertificatePrefixes) {
if (it.includes(prefix)) {
return true;
}
}
return false;
})),
(0, builder_util_1.exec)("/usr/bin/security", addKeychain(["find-identity", "-v", "-p", "codesigning"])).then(it => it.trim().split("\n")),
]).then(it => {
const array = it[0]
.concat(it[1])
.filter(it => !it.includes("(Missing required extension)") && !it.includes("valid identities found") && !it.includes("iPhone ") && !it.includes("com.apple.idms.appleid.prd."))
// remove 1)
.map(it => it.substring(it.indexOf(")") + 1).trim());
return Array.from(new Set(array));
});
if (keychain == null) {
exports.findIdentityRawResult = result;
}
}
return result;
}
async function _findIdentity(type, qualifier, keychain) {
// https://github.com/electron-userland/electron-builder/issues/484
//noinspection SpellCheckingInspection
const lines = await getValidIdentities(keychain);
const namePrefix = `${type}:`;
for (const line of lines) {
if (qualifier != null && !line.includes(qualifier)) {
continue;
}
if (line.includes(namePrefix)) {
return parseIdentity(line);
}
}
if (type === "Developer ID Application") {
// find non-Apple certificate
// https://github.com/electron-userland/electron-builder/issues/458
l: for (const line of lines) {
if (qualifier != null && !line.includes(qualifier)) {
continue;
}
if (line.includes("Mac Developer:")) {
continue;
}
for (const prefix of exports.appleCertificatePrefixes) {
if (line.includes(prefix)) {
continue l;
}
}
return parseIdentity(line);
}
}
return null;
}
function parseIdentity(line) {
const firstQuoteIndex = line.indexOf('"');
const name = line.substring(firstQuoteIndex + 1, line.lastIndexOf('"'));
const hash = line.substring(0, firstQuoteIndex - 1);
return new util_identities_1.Identity(name, hash);
}
function findIdentity(certType, qualifier, keychain) {
let identity = qualifier || process.env.CSC_NAME;
if ((0, builder_util_1.isEmptyOrSpaces)(identity)) {
if ((0, flags_1.isAutoDiscoveryCodeSignIdentity)()) {
return _findIdentity(certType, null, keychain);
}
else {
return Promise.resolve(null);
}
}
else {
identity = identity.trim();
for (const prefix of exports.appleCertificatePrefixes) {
checkPrefix(identity, prefix);
}
return _findIdentity(certType, identity, keychain);
}
}
function checkPrefix(name, prefix) {
if (name.startsWith(prefix)) {
throw new builder_util_1.InvalidConfigurationError(`Please remove prefix "${prefix}" from the specified name — appropriate certificate will be chosen automatically`);
}
}
//# sourceMappingURL=macCodeSign.js.map
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
import { MemoLazy, Nullish } from "builder-util-runtime";
import { Lazy } from "lazy-val";
import { Target } from "../core";
import { WindowsConfiguration } from "../options/winOptions";
import { WindowsSignOptions } from "./windowsCodeSign";
import { CertificateFromStoreInfo, FileCodeSigningInfo } from "./windowsSignToolManager";
export interface SignManager {
readonly computedPublisherName: Lazy<Array<string> | null>;
readonly cscInfo: MemoLazy<WindowsConfiguration, FileCodeSigningInfo | CertificateFromStoreInfo | null>;
computePublisherName(target: Target, publisherName: string | Nullish): Promise<string>;
initialize(): Promise<void>;
signFile(options: WindowsSignOptions): Promise<boolean>;
}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=signManager.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"signManager.js","sourceRoot":"","sources":["../../src/codeSign/signManager.ts"],"names":[],"mappings":"","sourcesContent":["import { MemoLazy, Nullish } from \"builder-util-runtime\"\nimport { Lazy } from \"lazy-val\"\nimport { Target } from \"../core\"\nimport { WindowsConfiguration } from \"../options/winOptions\"\nimport { WindowsSignOptions } from \"./windowsCodeSign\"\nimport { CertificateFromStoreInfo, FileCodeSigningInfo } from \"./windowsSignToolManager\"\n\nexport interface SignManager {\n readonly computedPublisherName: Lazy<Array<string> | null>\n readonly cscInfo: MemoLazy<WindowsConfiguration, FileCodeSigningInfo | CertificateFromStoreInfo | null>\n computePublisherName(target: Target, publisherName: string | Nullish): Promise<string>\n initialize(): Promise<void>\n signFile(options: WindowsSignOptions): Promise<boolean>\n}\n"]}
+7
View File
@@ -0,0 +1,7 @@
import { WindowsConfiguration } from "../options/winOptions";
import { WinPackager } from "../winPackager";
export interface WindowsSignOptions {
readonly path: string;
readonly options: WindowsConfiguration;
}
export declare function signWindows(options: WindowsSignOptions, packager: WinPackager): Promise<boolean>;
+38
View File
@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.signWindows = signWindows;
const builder_util_1 = require("builder-util");
async function signWindows(options, packager) {
if (options.options.azureSignOptions) {
if (options.options.signtoolOptions) {
builder_util_1.log.warn(null, "ignoring signtool options, using Azure Trusted Signing; please only configure one");
}
builder_util_1.log.info({ path: builder_util_1.log.filePath(options.path) }, "signing with Azure Trusted Signing");
}
else {
builder_util_1.log.info({ path: builder_util_1.log.filePath(options.path) }, "signing with signtool.exe");
}
const packageManager = await packager.signingManager.value;
return signWithRetry(async () => packageManager.signFile(options));
}
function signWithRetry(signer) {
return (0, builder_util_1.retry)(signer, {
retries: 3,
interval: 1000,
backoff: 1000,
shouldRetry: (e) => {
const message = e.message;
if (
// https://github.com/electron-userland/electron-builder/issues/1414
(message === null || message === void 0 ? void 0 : message.includes("Couldn't resolve host name")) ||
(
// https://github.com/electron-userland/electron-builder/issues/8615
message === null || message === void 0 ? void 0 : message.includes("being used by another process."))) {
builder_util_1.log.warn({ error: message }, "attempt to sign failed, another attempt will be made");
return true;
}
return false;
},
});
}
//# sourceMappingURL=windowsCodeSign.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"windowsCodeSign.js","sourceRoot":"","sources":["../../src/codeSign/windowsCodeSign.ts"],"names":[],"mappings":";;AASA,kCAWC;AApBD,+CAAyC;AASlC,KAAK,UAAU,WAAW,CAAC,OAA2B,EAAE,QAAqB;IAClF,IAAI,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;YACpC,kBAAG,CAAC,IAAI,CAAC,IAAI,EAAE,mFAAmF,CAAC,CAAA;QACrG,CAAC;QACD,kBAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,kBAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,oCAAoC,CAAC,CAAA;IACtF,CAAC;SAAM,CAAC;QACN,kBAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,kBAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,2BAA2B,CAAC,CAAA;IAC7E,CAAC;IACD,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAA;IAC1D,OAAO,aAAa,CAAC,KAAK,IAAI,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAA;AACpE,CAAC;AAED,SAAS,aAAa,CAAC,MAA8B;IACnD,OAAO,IAAA,oBAAK,EAAC,MAAM,EAAE;QACnB,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,IAAI;QACb,WAAW,EAAE,CAAC,CAAM,EAAE,EAAE;YACtB,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,CAAA;YACzB;YACE,oEAAoE;YACpE,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,CAAC,4BAA4B,CAAC;;gBAC/C,oEAAoE;gBACpE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,CAAC,gCAAgC,CAAC,CAAA,EACnD,CAAC;gBACD,kBAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,sDAAsD,CAAC,CAAA;gBACpF,OAAO,IAAI,CAAA;YACb,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC;KACF,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { log, retry } from \"builder-util\"\nimport { WindowsConfiguration } from \"../options/winOptions\"\nimport { WinPackager } from \"../winPackager\"\n\nexport interface WindowsSignOptions {\n readonly path: string\n readonly options: WindowsConfiguration\n}\n\nexport async function signWindows(options: WindowsSignOptions, packager: WinPackager): Promise<boolean> {\n if (options.options.azureSignOptions) {\n if (options.options.signtoolOptions) {\n log.warn(null, \"ignoring signtool options, using Azure Trusted Signing; please only configure one\")\n }\n log.info({ path: log.filePath(options.path) }, \"signing with Azure Trusted Signing\")\n } else {\n log.info({ path: log.filePath(options.path) }, \"signing with signtool.exe\")\n }\n const packageManager = await packager.signingManager.value\n return signWithRetry(async () => packageManager.signFile(options))\n}\n\nfunction signWithRetry(signer: () => Promise<boolean>): Promise<boolean> {\n return retry(signer, {\n retries: 3,\n interval: 1000,\n backoff: 1000,\n shouldRetry: (e: any) => {\n const message = e.message\n if (\n // https://github.com/electron-userland/electron-builder/issues/1414\n message?.includes(\"Couldn't resolve host name\") ||\n // https://github.com/electron-userland/electron-builder/issues/8615\n message?.includes(\"being used by another process.\")\n ) {\n log.warn({ error: message }, \"attempt to sign failed, another attempt will be made\")\n return true\n }\n return false\n },\n })\n}\n"]}
+21
View File
@@ -0,0 +1,21 @@
import { MemoLazy } from "builder-util-runtime";
import { Lazy } from "lazy-val";
import { WindowsConfiguration } from "../options/winOptions";
import { WinPackager } from "../winPackager";
import { SignManager } from "./signManager";
import { WindowsSignOptions } from "./windowsCodeSign";
import { CertificateFromStoreInfo, FileCodeSigningInfo } from "./windowsSignToolManager";
export declare class WindowsSignAzureManager implements SignManager {
private readonly packager;
private readonly platformSpecificBuildOptions;
readonly computedPublisherName: Lazy<string[] | null>;
constructor(packager: WinPackager);
initialize(): Promise<void>;
verifyRequiredEnvVars(): void;
verifyPrincipleSecretEnv(): boolean;
verifyPrincipleCertificateEnv(): boolean;
verifyUsernamePasswordEnv(): boolean;
computePublisherName(): Promise<string>;
readonly cscInfo: MemoLazy<WindowsConfiguration, FileCodeSigningInfo | CertificateFromStoreInfo | null>;
signFile(options: WindowsSignOptions): Promise<boolean>;
}
+116
View File
@@ -0,0 +1,116 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WindowsSignAzureManager = void 0;
const builder_util_1 = require("builder-util");
const builder_util_runtime_1 = require("builder-util-runtime");
const lazy_val_1 = require("lazy-val");
class WindowsSignAzureManager {
constructor(packager) {
this.packager = packager;
this.computedPublisherName = new lazy_val_1.Lazy(() => {
var _a;
const publisherName = (_a = this.platformSpecificBuildOptions.azureSignOptions) === null || _a === void 0 ? void 0 : _a.publisherName;
if (publisherName === null) {
return Promise.resolve(null);
}
else if (publisherName != null) {
return Promise.resolve((0, builder_util_1.asArray)(publisherName));
}
// TODO: Is there another way to automatically pull Publisher Name from AzureTrusted service?
// For now return null.
return Promise.resolve(null);
});
this.cscInfo = new builder_util_runtime_1.MemoLazy(() => this.packager.platformSpecificBuildOptions, _selected => Promise.resolve(null));
this.platformSpecificBuildOptions = packager.platformSpecificBuildOptions;
}
async initialize() {
const vm = await this.packager.vm.value;
const ps = await vm.powershellCommand.value;
builder_util_1.log.info(null, "installing required module (TrustedSigning) with scope CurrentUser");
try {
await vm.exec(ps, ["-NoProfile", "-NonInteractive", "-Command", "Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser"]);
}
catch (error) {
// Might not be needed, seems GH runners already have NuGet set up.
// Logging to debug just in case users run into this. If NuGet isn't present, Install-Module -Name TrustedSigning will fail, so we'll get the logs at that point
builder_util_1.log.debug({ message: error.message || error.stack }, "unable to install PackageProvider Nuget. Might be a false alarm though as some systems already have it installed");
}
await vm.exec(ps, ["-NoProfile", "-NonInteractive", "-Command", "Install-Module -Name TrustedSigning -MinimumVersion 0.5.0 -Force -Repository PSGallery -Scope CurrentUser"]);
// Preemptively check env vars once during initialization
// Options: https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition
builder_util_1.log.info(null, "verifying env vars for authenticating to Microsoft Entra ID");
this.verifyRequiredEnvVars();
if (!(this.verifyPrincipleSecretEnv() || this.verifyPrincipleCertificateEnv() || this.verifyUsernamePasswordEnv())) {
throw new builder_util_1.InvalidConfigurationError(`Unable to find valid azure env configuration for signing. Missing field(s) can be debugged via "DEBUG=electron-builder". Please refer to: https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition`);
}
}
verifyRequiredEnvVars() {
;
["AZURE_TENANT_ID", "AZURE_CLIENT_ID"].forEach(field => {
if (!process.env[field]) {
throw new builder_util_1.InvalidConfigurationError(`Unable to find valid azure env field ${field} for signing. Please refer to: https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition`);
}
});
}
verifyPrincipleSecretEnv() {
if (!process.env.AZURE_CLIENT_SECRET) {
builder_util_1.log.debug({ envVar: "AZURE_CLIENT_SECRET" }, "no secret found for authenticating to Microsoft Entra ID");
return false;
}
return true;
}
verifyPrincipleCertificateEnv() {
if (!process.env.AZURE_CLIENT_CERTIFICATE_PATH) {
builder_util_1.log.debug({ envVar: "AZURE_CLIENT_CERTIFICATE_PATH" }, "no path found for signing certificate for authenticating to Microsoft Entra ID");
return false;
}
if (!process.env.AZURE_CLIENT_CERTIFICATE_PASSWORD) {
builder_util_1.log.debug({ envVar: "AZURE_CLIENT_CERTIFICATE_PASSWORD" }, "(optional) certificate password not found, assuming no password");
}
if (!process.env.AZURE_CLIENT_SEND_CERTIFICATE_CHAIN) {
builder_util_1.log.debug({ envVar: "AZURE_CLIENT_SEND_CERTIFICATE_CHAIN" }, "(optional) certificate chain not found");
}
return true;
}
verifyUsernamePasswordEnv() {
if (!process.env.AZURE_USERNAME) {
builder_util_1.log.debug({ envVar: "AZURE_USERNAME" }, "no username found for authenticating to Microsoft Entra ID");
if (!process.env.AZURE_PASSWORD) {
builder_util_1.log.debug({ envVar: "AZURE_PASSWORD" }, "no password found for authenticating to Microsoft Entra ID");
}
return false;
}
return true;
}
computePublisherName() {
return Promise.resolve(this.packager.platformSpecificBuildOptions.azureSignOptions.publisherName);
}
// prerequisite: requires `initializeProviderModules` to already have been executed
async signFile(options) {
const vm = await this.packager.vm.value;
const ps = await vm.powershellCommand.value;
const { publisherName: _publisher, // extract from `extraSigningArgs`
endpoint, certificateProfileName, codeSigningAccountName, fileDigest, timestampRfc3161, timestampDigest, ...extraSigningArgs } = options.options.azureSignOptions;
const params = {
...extraSigningArgs,
Endpoint: endpoint,
CertificateProfileName: certificateProfileName,
CodeSigningAccountName: codeSigningAccountName,
TimestampRfc3161: timestampRfc3161 || "http://timestamp.acs.microsoft.com",
TimestampDigest: timestampDigest || "SHA256",
FileDigest: fileDigest || "SHA256",
Files: options.path,
};
const paramsString = Object.entries(params)
.filter(([_, value]) => value != null)
.reduce((res, [field, value]) => {
const escapedValue = String(value).replace(/'/g, "''");
return [...res, `-${field}`, `'${escapedValue}'`];
}, [])
.join(" ");
await vm.exec(ps, ["-NoProfile", "-NonInteractive", "-Command", `Invoke-TrustedSigning ${paramsString}`]);
return true;
}
}
exports.WindowsSignAzureManager = WindowsSignAzureManager;
//# sourceMappingURL=windowsSignAzureManager.js.map
File diff suppressed because one or more lines are too long
+56
View File
@@ -0,0 +1,56 @@
import { MemoLazy } from "builder-util-runtime";
import { Lazy } from "lazy-val";
import { Target } from "../core";
import { WindowsConfiguration } from "../options/winOptions";
import { VmManager } from "../vm/vm";
import { WinPackager } from "../winPackager";
import { SignManager } from "./signManager";
import { WindowsSignOptions } from "./windowsCodeSign";
export type CustomWindowsSign = (configuration: CustomWindowsSignTaskConfiguration, packager?: WinPackager) => Promise<any>;
export interface WindowsSignToolOptions extends WindowsSignOptions {
readonly name: string;
readonly site: string | null;
}
export interface FileCodeSigningInfo {
readonly file: string;
readonly password: string | null;
}
export interface WindowsSignTaskConfiguration extends WindowsSignToolOptions {
readonly cscInfo: FileCodeSigningInfo | CertificateFromStoreInfo | null;
resultOutputPath?: string;
hash: string;
isNest: boolean;
}
export interface CustomWindowsSignTaskConfiguration extends WindowsSignTaskConfiguration {
computeSignToolArgs(isWin: boolean): Array<string>;
}
export interface CertificateInfo {
readonly commonName: string;
readonly bloodyMicrosoftSubjectDn: string;
}
export interface CertificateFromStoreInfo {
thumbprint: string;
subject: string;
store: string;
isLocalMachineStore: boolean;
}
export declare class WindowsSignToolManager implements SignManager {
private readonly packager;
private readonly platformSpecificBuildOptions;
constructor(packager: WinPackager);
readonly computedPublisherName: Lazy<string[] | null>;
readonly lazyCertInfo: MemoLazy<MemoLazy<WindowsConfiguration, FileCodeSigningInfo | CertificateFromStoreInfo | null>, CertificateInfo | null>;
readonly cscInfo: MemoLazy<WindowsConfiguration, FileCodeSigningInfo | CertificateFromStoreInfo | null>;
initialize(): Promise<void>;
computePublisherName(target: Target, publisherName: string): Promise<string>;
signFile(options: WindowsSignOptions): Promise<boolean>;
getCertInfo(file: string, password: string): Promise<CertificateInfo>;
computeSignToolArgs(options: WindowsSignTaskConfiguration, isWin: boolean, vm?: VmManager): Array<string>;
private computeWindowsSignArgs;
private computeOsslsigncodeArgs;
private addCertificateArgs;
private addCommonSigningArgs;
getOutputPath(inputPath: string, hash: string): string;
getCertificateFromStoreInfo(options: WindowsConfiguration, vm: VmManager): Promise<CertificateFromStoreInfo>;
doSign(configuration: CustomWindowsSignTaskConfiguration, packager: WinPackager): Promise<void>;
}
+375
View File
@@ -0,0 +1,375 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WindowsSignToolManager = void 0;
const builder_util_1 = require("builder-util");
const builder_util_runtime_1 = require("builder-util-runtime");
const fs_extra_1 = require("fs-extra");
const lazy_val_1 = require("lazy-val");
const path = require("path");
const AppxTarget_1 = require("../targets/AppxTarget");
const windows_1 = require("../toolsets/windows");
const appBuilder_1 = require("../util/appBuilder");
const resolve_1 = require("../util/resolve");
const vm_1 = require("../vm/vm");
const codesign_1 = require("./codesign");
class WindowsSignToolManager {
constructor(packager) {
this.packager = packager;
this.computedPublisherName = new lazy_val_1.Lazy(async () => {
var _a;
const publisherName = (_a = this.platformSpecificBuildOptions.signtoolOptions) === null || _a === void 0 ? void 0 : _a.publisherName;
if (publisherName === null) {
return null;
}
else if (publisherName != null) {
return (0, builder_util_1.asArray)(publisherName);
}
const certInfo = await this.lazyCertInfo.value;
return certInfo == null ? null : [certInfo.commonName];
});
this.lazyCertInfo = new builder_util_runtime_1.MemoLazy(() => this.cscInfo, async (csc) => {
const cscInfo = await csc.value;
if (cscInfo == null) {
return null;
}
if ("subject" in cscInfo) {
const bloodyMicrosoftSubjectDn = cscInfo.subject;
return {
commonName: (0, builder_util_runtime_1.parseDn)(bloodyMicrosoftSubjectDn).get("CN"),
bloodyMicrosoftSubjectDn,
};
}
const cscFile = cscInfo.file;
if (cscFile == null) {
return null;
}
return await this.getCertInfo(cscFile, cscInfo.password || "");
});
this.cscInfo = new builder_util_runtime_1.MemoLazy(() => this.platformSpecificBuildOptions, platformSpecificBuildOptions => {
var _a, _b, _c;
const subjectName = (_a = platformSpecificBuildOptions.signtoolOptions) === null || _a === void 0 ? void 0 : _a.certificateSubjectName;
const shaType = (_b = platformSpecificBuildOptions.signtoolOptions) === null || _b === void 0 ? void 0 : _b.certificateSha1;
if (subjectName != null || shaType != null) {
return this.packager.vm.value
.then(vm => this.getCertificateFromStoreInfo(platformSpecificBuildOptions, vm))
.catch((e) => {
var _a;
// https://github.com/electron-userland/electron-builder/pull/2397
if (((_a = platformSpecificBuildOptions.signtoolOptions) === null || _a === void 0 ? void 0 : _a.sign) == null) {
throw e;
}
else {
builder_util_1.log.debug({ error: e }, "getCertificateFromStoreInfo error");
return null;
}
});
}
const certificateFile = (_c = platformSpecificBuildOptions.signtoolOptions) === null || _c === void 0 ? void 0 : _c.certificateFile;
if (certificateFile != null) {
const certificatePassword = this.packager.getCscPassword();
return Promise.resolve({
file: certificateFile,
password: certificatePassword == null ? null : certificatePassword.trim(),
});
}
const cscLink = this.packager.getCscLink("WIN_CSC_LINK");
if (cscLink == null || cscLink === "") {
return Promise.resolve(null);
}
return ((0, codesign_1.importCertificate)(cscLink, this.packager.info.tempDirManager, this.packager.projectDir)
// before then
.catch((e) => {
if (e instanceof builder_util_1.InvalidConfigurationError) {
throw new builder_util_1.InvalidConfigurationError(`Env WIN_CSC_LINK is not correct, cannot resolve: ${e.message}`);
}
else {
throw e;
}
})
.then(path => {
return {
file: path,
password: this.packager.getCscPassword(),
};
}));
});
this.platformSpecificBuildOptions = packager.platformSpecificBuildOptions;
}
initialize() {
return Promise.resolve();
}
// https://github.com/electron-userland/electron-builder/issues/2108#issuecomment-333200711
async computePublisherName(target, publisherName) {
if (target instanceof AppxTarget_1.default && (await this.cscInfo.value) == null) {
builder_util_1.log.info({ reason: "Windows Store only build" }, "AppX is not signed");
return publisherName || "CN=ms";
}
const certInfo = await this.lazyCertInfo.value;
const publisher = publisherName || (certInfo == null ? null : certInfo.bloodyMicrosoftSubjectDn);
if (publisher == null) {
throw new Error("Internal error: cannot compute subject using certificate info");
}
return publisher;
}
async signFile(options) {
var _a, _b;
let hashes = (_a = options.options.signtoolOptions) === null || _a === void 0 ? void 0 : _a.signingHashAlgorithms;
// msi does not support dual-signing
if (options.path.endsWith(".msi")) {
hashes = [hashes != null && !hashes.includes("sha1") ? "sha256" : "sha1"];
}
else if (options.path.endsWith(".appx")) {
hashes = ["sha256"];
}
else if (hashes == null) {
hashes = ["sha1", "sha256"];
}
else {
hashes = Array.isArray(hashes) ? hashes : [hashes];
}
const name = this.packager.appInfo.productName;
const site = await this.packager.appInfo.computePackageUrl();
const customSign = await (0, resolve_1.resolveFunction)(this.packager.appInfo.type, (_b = options.options.signtoolOptions) === null || _b === void 0 ? void 0 : _b.sign, "sign");
const cscInfo = await this.cscInfo.value;
if (cscInfo) {
let logInfo = {
file: builder_util_1.log.filePath(options.path),
};
if ("file" in cscInfo) {
logInfo = {
...logInfo,
certificateFile: cscInfo.file,
};
}
else {
logInfo = {
...logInfo,
subject: cscInfo.subject,
thumbprint: cscInfo.thumbprint,
store: cscInfo.store,
user: cscInfo.isLocalMachineStore ? "local machine" : "current user",
};
}
builder_util_1.log.info(logInfo, "signing");
}
else if (!customSign) {
builder_util_1.log.debug({ signHook: !!customSign, cscInfo }, "no signing info identified, signing is skipped");
return false;
}
const executor = customSign || ((config, packager) => this.doSign(config, packager));
let isNest = false;
for (const hash of hashes) {
const taskConfiguration = { ...options, name, site, cscInfo, hash, isNest };
await Promise.resolve(executor({
...taskConfiguration,
computeSignToolArgs: isWin => this.computeSignToolArgs(taskConfiguration, isWin),
}, this.packager));
isNest = true;
if (taskConfiguration.resultOutputPath != null) {
await (0, fs_extra_1.rename)(taskConfiguration.resultOutputPath, options.path);
}
}
return true;
}
async getCertInfo(file, password) {
let result = null;
const errorMessagePrefix = "Cannot extract publisher name from code signing certificate. As workaround, set win.publisherName. Error: ";
try {
result = await (0, appBuilder_1.executeAppBuilderAsJson)(["certificate-info", "--input", file, "--password", password]);
}
catch (e) {
throw new Error(`${errorMessagePrefix}${e.stack || e}`);
}
if (result.error != null) {
// noinspection ExceptionCaughtLocallyJS
throw new builder_util_1.InvalidConfigurationError(`${errorMessagePrefix}${result.error}`);
}
return result;
}
// on windows be aware of http://stackoverflow.com/a/32640183/1910191
computeSignToolArgs(options, isWin, vm = new vm_1.VmManager()) {
return isWin ? this.computeWindowsSignArgs(options, vm) : this.computeOsslsigncodeArgs(options, vm);
}
computeWindowsSignArgs(options, vm) {
var _a, _b, _c, _d;
const inputFile = vm.toVmFile(options.path);
const args = ["sign"];
// Timestamping
if (process.env.ELECTRON_BUILDER_OFFLINE !== "true") {
const isRfc3161 = options.isNest || options.hash === "sha256";
args.push(isRfc3161 ? "/tr" : "/t");
const timestampUrl = isRfc3161
? ((_a = options.options.signtoolOptions) === null || _a === void 0 ? void 0 : _a.rfc3161TimeStampServer) || "http://timestamp.digicert.com"
: ((_b = options.options.signtoolOptions) === null || _b === void 0 ? void 0 : _b.timeStampServer) || "http://timestamp.digicert.com";
args.push(timestampUrl);
}
// Certificate
this.addCertificateArgs(args, options, vm, true);
// Hash algorithm
const isLegacyToolset = ((_c = this.packager.config.toolsets) === null || _c === void 0 ? void 0 : _c.winCodeSign) === "0.0.0" || ((_d = this.packager.config.toolsets) === null || _d === void 0 ? void 0 : _d.winCodeSign) == null;
if (isLegacyToolset) {
// Legacy || v0.0.0: Only add /fd for non-SHA1 (original behavior)
if (options.hash !== "sha1") {
args.push("/fd", options.hash);
if (process.env.ELECTRON_BUILDER_OFFLINE !== "true") {
args.push("/td", "sha256");
}
}
}
else {
// Modern: Always add /fd (required by new Windows Kits)
args.push("/fd", options.hash.toLowerCase());
// Only add /td for RFC3161 timestamps (incompatible with /t)
if (process.env.ELECTRON_BUILDER_OFFLINE !== "true" && (options.isNest || options.hash === "sha256")) {
args.push("/td", "sha256");
}
}
// Optional parameters
this.addCommonSigningArgs(args, options, vm, true);
// Windows-specific
args.push("/debug");
args.push(inputFile); // Must be last
return args;
}
computeOsslsigncodeArgs(options, vm) {
var _a;
const inputFile = vm.toVmFile(options.path);
const outputPath = this.getOutputPath(inputFile, options.hash);
options.resultOutputPath = outputPath;
const args = ["sign", "-in", inputFile, "-out", outputPath];
// Timestamping
if (process.env.ELECTRON_BUILDER_OFFLINE !== "true") {
const timestampUrl = ((_a = options.options.signtoolOptions) === null || _a === void 0 ? void 0 : _a.timeStampServer) || "http://timestamp.digicert.com";
args.push("-t", timestampUrl);
}
// Certificate
this.addCertificateArgs(args, options, vm, false);
// Hash algorithm
args.push("-h", options.hash.toLowerCase());
// Optional parameters
this.addCommonSigningArgs(args, options, vm, false);
// Proxy support
const httpsProxy = process.env.HTTPS_PROXY;
if (httpsProxy === null || httpsProxy === void 0 ? void 0 : httpsProxy.length) {
args.push("-p", httpsProxy);
}
return args;
}
addCertificateArgs(args, options, vm, isWin) {
const certificateFile = options.cscInfo.file;
if (certificateFile == null) {
// Certificate from store (Windows only)
if (!isWin) {
throw new Error("certificateSha1/certificateSubjectName supported only on Windows");
}
const cscInfo = options.cscInfo;
args.push("/sha1", cscInfo.thumbprint);
args.push("/s", cscInfo.store);
if (cscInfo.isLocalMachineStore) {
args.push("/sm");
}
}
else {
// Certificate file
const certExtension = path.extname(certificateFile);
if (certExtension === ".p12" || certExtension === ".pfx") {
args.push(isWin ? "/f" : "-pkcs12", vm.toVmFile(certificateFile));
}
else {
throw new Error(`Please specify pkcs12 (.p12/.pfx) file, ${certificateFile} is not correct`);
}
}
}
addCommonSigningArgs(args, options, vm, isWin) {
var _a, _b;
if (options.name) {
args.push(isWin ? "/d" : "-n", options.name);
}
if (options.site) {
args.push(isWin ? "/du" : "-i", options.site);
}
if (options.isNest) {
args.push(isWin ? "/as" : "-nest");
}
const password = (_a = options.cscInfo) === null || _a === void 0 ? void 0 : _a.password;
if (password) {
args.push(isWin ? "/p" : "-pass", password);
}
const additionalCert = (_b = options.options.signtoolOptions) === null || _b === void 0 ? void 0 : _b.additionalCertificateFile;
if (additionalCert) {
args.push(isWin ? "/ac" : "-ac", vm.toVmFile(additionalCert));
}
}
getOutputPath(inputPath, hash) {
const extension = path.extname(inputPath);
return path.join(path.dirname(inputPath), `${path.basename(inputPath, extension)}-signed-${hash}${extension}`);
}
async getCertificateFromStoreInfo(options, vm) {
var _a, _b, _c;
const certificateSubjectName = (_a = options.signtoolOptions) === null || _a === void 0 ? void 0 : _a.certificateSubjectName;
const certificateSha1 = (_c = (_b = options.signtoolOptions) === null || _b === void 0 ? void 0 : _b.certificateSha1) === null || _c === void 0 ? void 0 : _c.toUpperCase();
const ps = await vm.powershellCommand.value;
const rawResult = await vm.exec(ps, [
"-NoProfile",
"-NonInteractive",
"-Command",
"Get-ChildItem -Recurse Cert: -CodeSigningCert | Select-Object -Property Subject,PSParentPath,Thumbprint | ConvertTo-Json -Compress",
]);
const certList = rawResult.length === 0 ? [] : (0, builder_util_1.asArray)(JSON.parse(rawResult));
for (const certInfo of certList) {
if ((certificateSubjectName != null && !certInfo.Subject.includes(certificateSubjectName)) ||
(certificateSha1 != null && certInfo.Thumbprint.toUpperCase() !== certificateSha1)) {
continue;
}
const parentPath = certInfo.PSParentPath;
const store = parentPath.substring(parentPath.lastIndexOf("\\") + 1);
builder_util_1.log.debug({ store, PSParentPath: parentPath }, "auto-detect certificate store");
// https://github.com/electron-userland/electron-builder/issues/1717
const isLocalMachineStore = parentPath.includes("Certificate::LocalMachine");
builder_util_1.log.debug(null, "auto-detect using of LocalMachine store");
return {
thumbprint: certInfo.Thumbprint,
subject: certInfo.Subject,
store,
isLocalMachineStore,
};
}
throw new Error(`Cannot find certificate ${certificateSubjectName || certificateSha1}, all certs: ${rawResult}`);
}
async doSign(configuration, packager) {
var _a;
// https://github.com/electron-userland/electron-builder/pull/1944
const timeout = parseInt(process.env.SIGNTOOL_TIMEOUT, 10) || 10 * 60 * 1000;
// decide runtime argument by cases
let args;
let vm;
const useVmIfNotOnWin = configuration.path.endsWith(".appx") || !("file" in configuration.cscInfo); /* certificateSubjectName and other such options */
const isWin = process.platform === "win32" || useVmIfNotOnWin;
const toolInfo = await (0, windows_1.getSignToolPath)((_a = this.packager.config.toolsets) === null || _a === void 0 ? void 0 : _a.winCodeSign, isWin);
const tool = toolInfo.path;
if (useVmIfNotOnWin) {
vm = await packager.vm.value;
args = this.computeSignToolArgs(configuration, isWin, vm);
}
else {
vm = new vm_1.VmManager();
args = configuration.computeSignToolArgs(isWin);
}
await (0, builder_util_1.retry)(() => vm.exec(tool, args, { timeout, env: { ...process.env, ...(toolInfo.env || {}) } }), {
retries: 2,
interval: 15000,
backoff: 10000,
shouldRetry: (e) => {
if (e.message.includes("The file is being used by another process") ||
e.message.includes("The specified timestamp server either could not be reached") ||
e.message.includes("No certificates were found that met all the given criteria.")) {
builder_util_1.log.warn(`Attempt to code sign failed, another attempt will be made in 15 seconds: ${e.message}`);
return true;
}
return false;
},
});
}
}
exports.WindowsSignToolManager = WindowsSignToolManager;
//# sourceMappingURL=windowsSignToolManager.js.map
File diff suppressed because one or more lines are too long