@overwolf/ow-app-builder-lib
Version:
ow-electron-builder lib
380 lines • 18 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.WindowsSignToolManager = void 0;
exports.getSignVendorPath = getSignVendorPath;
exports.isOldWin6 = isOldWin6;
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 os = require("os");
const path = require("path");
const binDownload_1 = require("../binDownload");
const AppxTarget_1 = require("../targets/AppxTarget");
const appBuilder_1 = require("../util/appBuilder");
const bundledTool_1 = require("../util/bundledTool");
const flags_1 = require("../util/flags");
const resolve_1 = require("../util/resolve");
const vm_1 = require("../vm/vm");
const codesign_1 = require("./codesign");
function getSignVendorPath() {
return (0, binDownload_1.getBin)("winCodeSign");
}
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()) {
var _a, _b, _c;
const inputFile = vm.toVmFile(options.path);
const outputPath = isWin ? inputFile : this.getOutputPath(inputFile, options.hash);
if (!isWin) {
options.resultOutputPath = outputPath;
}
const args = isWin ? ["sign"] : ["-in", inputFile, "-out", outputPath];
if (process.env.ELECTRON_BUILDER_OFFLINE !== "true") {
const timestampingServiceUrl = ((_a = options.options.signtoolOptions) === null || _a === void 0 ? void 0 : _a.timeStampServer) || "http://timestamp.digicert.com";
if (isWin) {
args.push(options.isNest || options.hash === "sha256" ? "/tr" : "/t", options.isNest || options.hash === "sha256" ? ((_b = options.options.signtoolOptions) === null || _b === void 0 ? void 0 : _b.rfc3161TimeStampServer) || "http://timestamp.digicert.com" : timestampingServiceUrl);
}
else {
args.push("-t", timestampingServiceUrl);
}
}
const certificateFile = options.cscInfo.file;
if (certificateFile == null) {
const cscInfo = options.cscInfo;
const subjectName = cscInfo.thumbprint;
if (!isWin) {
throw new Error(`${subjectName == null ? "certificateSha1" : "certificateSubjectName"} supported only on Windows`);
}
args.push("/sha1", cscInfo.thumbprint);
args.push("/s", cscInfo.store);
if (cscInfo.isLocalMachineStore) {
args.push("/sm");
}
}
else {
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`);
}
}
if (!isWin || options.hash !== "sha1") {
args.push(isWin ? "/fd" : "-h", options.hash);
if (isWin && process.env.ELECTRON_BUILDER_OFFLINE !== "true") {
args.push("/td", "sha256");
}
}
if (options.name) {
args.push(isWin ? "/d" : "-n", options.name);
}
if (options.site) {
args.push(isWin ? "/du" : "-i", options.site);
}
// msi does not support dual-signing
if (options.isNest) {
args.push(isWin ? "/as" : "-nest");
}
const password = options.cscInfo == null ? null : options.cscInfo.password;
if (password) {
args.push(isWin ? "/p" : "-pass", password);
}
const additionalCert = (_c = options.options.signtoolOptions) === null || _c === void 0 ? void 0 : _c.additionalCertificateFile;
if (additionalCert) {
args.push(isWin ? "/ac" : "-ac", vm.toVmFile(additionalCert));
}
const httpsProxyFromEnv = process.env.HTTPS_PROXY;
if (!isWin && httpsProxyFromEnv != null && httpsProxyFromEnv.length) {
args.push("-p", httpsProxyFromEnv);
}
if (isWin) {
// https://github.com/electron-userland/electron-builder/issues/2875#issuecomment-387233610
args.push("/debug");
// must be last argument
args.push(inputFile);
}
return args;
}
getOutputPath(inputPath, hash) {
const extension = path.extname(inputPath);
return path.join(path.dirname(inputPath), `${path.basename(inputPath, extension)}-signed-${hash}${extension}`);
}
getWinSignTool(vendorPath) {
// use modern signtool on Windows Server 2012 R2 to be able to sign AppX
if (isOldWin6()) {
return path.join(vendorPath, "windows-6", "signtool.exe");
}
else {
return path.join(vendorPath, "windows-10", process.arch, "signtool.exe");
}
}
async getToolPath(isWin = process.platform === "win32") {
if ((0, flags_1.isUseSystemSigncode)()) {
return { path: "osslsigncode" };
}
const result = process.env.SIGNTOOL_PATH;
if (result) {
return { path: result };
}
const vendorPath = await getSignVendorPath();
if (isWin) {
// use modern signtool on Windows Server 2012 R2 to be able to sign AppX
return { path: this.getWinSignTool(vendorPath) };
}
else if (process.platform === "darwin") {
const toolDirPath = path.join(vendorPath, process.platform, "10.12");
return {
path: path.join(toolDirPath, "osslsigncode"),
env: (0, bundledTool_1.computeToolEnv)([path.join(toolDirPath, "lib")]),
};
}
else {
return { path: path.join(vendorPath, process.platform, "osslsigncode") };
}
}
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) {
// 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 env = process.env;
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 this.getToolPath(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);
if (toolInfo.env != null) {
env = toolInfo.env;
}
}
await (0, builder_util_1.retry)(() => vm.exec(tool, args, { timeout, env }), 2, 15000, 10000, 0, (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;
function isOldWin6() {
const winVersion = os.release();
return winVersion.startsWith("6.") && !winVersion.startsWith("6.3");
}
//# sourceMappingURL=windowsSignToolManager.js.map
;