electron-updater
Version:
Cross platform updater for electron applications
136 lines • 7.43 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifySignature = verifySignature;
const builder_util_runtime_1 = require("builder-util-runtime");
const child_process_1 = require("child_process");
const os = require("os");
const path = require("path");
// $certificateInfo = (Get-AuthenticodeSignature 'xxx\yyy.exe'
// | where {$_.Status.Equals([System.Management.Automation.SignatureStatus]::Valid) -and $_.SignerCertificate.Subject.Contains("CN=siemens.com")})
// | Out-String ; if ($certificateInfo) { exit 0 } else { exit 1 }
function verifySignature(publisherNames, unescapedTempUpdateFile, logger) {
return new Promise((resolve, reject) => {
// Escape quotes and backticks in filenames to prevent user from breaking the
// arguments and perform a remote command injection.
//
// Consider example powershell command:
// ```powershell
// Get-AuthenticodeSignature 'C:\\path\\my-bad-';calc;'filename.exe'
// ```
// The above would work expected and find the file name, however, it will also execute `;calc;`
// command and start the calculator app.
//
// From Powershell quoting rules:
// https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7
// * Double quotes `"` are treated literally within single-quoted strings;
// * Single quotes can be escaped by doubling them: 'don''t' -> don't;
//
// Also note that at this point the file has already been written to the disk, thus we are
// guaranteed that the path will not contain any illegal characters like <>:"/\|?*
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
const tempUpdateFile = unescapedTempUpdateFile.replace(/'/g, "''");
logger.info(`Verifying signature ${tempUpdateFile}`);
// https://github.com/electron-userland/electron-builder/issues/2421
// https://github.com/electron-userland/electron-builder/issues/2535
// Resetting PSModulePath is necessary https://github.com/electron-userland/electron-builder/issues/7127
// semicolon wont terminate the set command and run chcp thus leading to verification errors on certificats with special chars like german umlauts, so rather
// join commands using & https://github.com/electron-userland/electron-builder/issues/8162
(0, child_process_1.execFile)(`set "PSModulePath=" & chcp 65001 >NUL & powershell.exe`, ["-NoProfile", "-NonInteractive", "-InputFormat", "None", "-Command", `"Get-AuthenticodeSignature -LiteralPath '${tempUpdateFile}' | ConvertTo-Json -Compress"`], {
shell: true,
timeout: 20 * 1000,
}, (error, stdout, stderr) => {
var _a;
try {
if (error != null || stderr) {
handleError(logger, error, stderr, reject);
resolve(null);
return;
}
const data = parseOut(stdout);
if (data.Status === 0) {
try {
const normlaizedUpdateFilePath = path.normalize(data.Path);
const normalizedTempUpdateFile = path.normalize(unescapedTempUpdateFile);
logger.info(`LiteralPath: ${normlaizedUpdateFilePath}. Update Path: ${normalizedTempUpdateFile}`);
if (normlaizedUpdateFilePath !== normalizedTempUpdateFile) {
handleError(logger, new Error(`LiteralPath of ${normlaizedUpdateFilePath} is different than ${normalizedTempUpdateFile}`), stderr, reject);
resolve(null);
return;
}
}
catch (error) {
logger.warn(`Unable to verify LiteralPath of update asset due to missing data.Path. Skipping this step of validation. Message: ${(_a = error.message) !== null && _a !== void 0 ? _a : error.stack}`);
}
const subject = (0, builder_util_runtime_1.parseDn)(data.SignerCertificate.Subject);
let match = false;
for (const name of publisherNames) {
const dn = (0, builder_util_runtime_1.parseDn)(name);
if (dn.size) {
// if we have a full DN, compare all values
const allKeys = Array.from(dn.keys());
match = allKeys.every(key => {
return dn.get(key) === subject.get(key);
});
}
else if (name === subject.get("CN")) {
logger.warn(`Signature validated using only CN ${name}. Please add your full Distinguished Name (DN) to publisherNames configuration`);
match = true;
}
if (match) {
resolve(null);
return;
}
}
}
const result = `publisherNames: ${publisherNames.join(" | ")}, raw info: ` + JSON.stringify(data, (name, value) => (name === "RawData" ? undefined : value), 2);
logger.warn(`Sign verification failed, installer signed with incorrect certificate: ${result}`);
resolve(result);
}
catch (e) {
handleError(logger, e, null, reject);
resolve(null);
return;
}
});
});
}
function parseOut(out) {
const data = JSON.parse(out);
delete data.PrivateKey;
delete data.IsOSBinary;
delete data.SignatureType;
const signerCertificate = data.SignerCertificate;
if (signerCertificate != null) {
delete signerCertificate.Archived;
delete signerCertificate.Extensions;
delete signerCertificate.Handle;
delete signerCertificate.HasPrivateKey;
// duplicates data.SignerCertificate (contains RawData)
delete signerCertificate.SubjectName;
}
return data;
}
function handleError(logger, error, stderr, reject) {
if (isOldWin6()) {
logger.warn(`Cannot execute Get-AuthenticodeSignature: ${error || stderr}. Ignoring signature validation due to unsupported powershell version. Please upgrade to powershell 3 or higher.`);
return;
}
try {
(0, child_process_1.execFileSync)("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", "ConvertTo-Json test"], { timeout: 10 * 1000 });
}
catch (testError) {
logger.warn(`Cannot execute ConvertTo-Json: ${testError.message}. Ignoring signature validation due to unsupported powershell version. Please upgrade to powershell 3 or higher.`);
return;
}
if (error != null) {
reject(error);
}
if (stderr) {
reject(new Error(`Cannot execute Get-AuthenticodeSignature, stderr: ${stderr}. Failing signature validation due to unknown stderr.`));
}
}
function isOldWin6() {
const winVersion = os.release();
return winVersion.startsWith("6.") && !winVersion.startsWith("6.3");
}
//# sourceMappingURL=windowsExecutableCodeSignatureVerifier.js.map
;