UNPKG

electron-updater

Version:
136 lines 7.43 kB
"use strict"; 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