UNPKG

13.2 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.findIdentity = exports.findIdentityRawResult = exports.sign = exports.createKeychain = exports.removeKeychain = exports.reportError = exports.isSignAllowed = exports.appleCertificatePrefixes = void 0;
4const bluebird_lst_1 = require("bluebird-lst");
5const util_1 = require("builder-util/out/util");
6const fs_1 = require("builder-util/out/fs");
7const log_1 = require("builder-util/out/log");
8const crypto_1 = require("crypto");
9const promises_1 = require("fs/promises");
10const lazy_val_1 = require("lazy-val");
11const os_1 = require("os");
12const path = require("path");
13const temp_file_1 = require("temp-file");
14const flags_1 = require("../util/flags");
15const codesign_1 = require("./codesign");
16const util_identities_1 = require("@electron/osx-sign/dist/cjs/util-identities");
17const osx_sign_1 = require("@electron/osx-sign");
18exports.appleCertificatePrefixes = ["Developer ID Application:", "Developer ID Installer:", "3rd Party Mac Developer Application:", "3rd Party Mac Developer Installer:"];
19function isSignAllowed(isPrintWarn = true) {
20 if (process.platform !== "darwin") {
21 if (isPrintWarn) {
22 util_1.log.warn({ reason: "supported only on macOS" }, "skipped macOS application code signing");
23 }
24 return false;
25 }
26 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)" +
27 "\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.";
28 if ((0, util_1.isPullRequest)()) {
29 if ((0, util_1.isEnvTrue)(process.env.CSC_FOR_PULL_REQUEST)) {
30 if (isPrintWarn) {
31 util_1.log.warn(buildForPrWarning);
32 }
33 }
34 else {
35 if (isPrintWarn) {
36 // https://github.com/electron-userland/electron-builder/issues/1524
37 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}`);
38 }
39 return false;
40 }
41 }
42 return true;
43}
44exports.isSignAllowed = isSignAllowed;
45async function reportError(isMas, certificateTypes, qualifier, keychainFile, isForceCodeSigning) {
46 const logFields = {};
47 if (qualifier == null) {
48 logFields.reason = "";
49 if ((0, flags_1.isAutoDiscoveryCodeSignIdentity)()) {
50 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`}`;
51 }
52 logFields.reason += ", see https://electron.build/code-signing";
53 if (!(0, flags_1.isAutoDiscoveryCodeSignIdentity)()) {
54 logFields.CSC_IDENTITY_AUTO_DISCOVERY = false;
55 }
56 }
57 else {
58 logFields.reason = "Identity name is specified, but no valid identity with this name in the keychain";
59 logFields.identity = qualifier;
60 }
61 const args = ["find-identity"];
62 if (keychainFile != null) {
63 args.push(keychainFile);
64 }
65 if (qualifier != null || (0, flags_1.isAutoDiscoveryCodeSignIdentity)()) {
66 logFields.allIdentities = (await (0, util_1.exec)("/usr/bin/security", args))
67 .trim()
68 .split("\n")
69 .filter(it => !(it.includes("Policy: X.509 Basic") || it.includes("Matching identities")))
70 .join("\n");
71 }
72 if (isMas || isForceCodeSigning) {
73 throw new Error(log_1.Logger.createMessage("skipped macOS application code signing", logFields, "error", it => it));
74 }
75 else {
76 util_1.log.warn(logFields, "skipped macOS application code signing");
77 }
78}
79exports.reportError = reportError;
80// "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."
81// 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
82// "overly complicated and introduces a race condition."
83// https://github.com/electron-userland/electron-builder/issues/398
84const bundledCertKeychainAdded = new lazy_val_1.Lazy(async () => {
85 // copy to temp and then atomic rename to final path
86 const cacheDir = getCacheDirectory();
87 const tmpKeychainPath = path.join(cacheDir, (0, temp_file_1.getTempName)("electron-builder-root-certs"));
88 const keychainPath = path.join(cacheDir, "electron-builder-root-certs.keychain");
89 const results = await Promise.all([
90 listUserKeychains(),
91 (0, fs_1.copyFile)(path.join(__dirname, "..", "..", "certs", "root_certs.keychain"), tmpKeychainPath).then(() => (0, promises_1.rename)(tmpKeychainPath, keychainPath)),
92 ]);
93 const list = results[0];
94 if (!list.includes(keychainPath)) {
95 await (0, util_1.exec)("/usr/bin/security", ["list-keychains", "-d", "user", "-s", keychainPath].concat(list));
96 }
97});
98function getCacheDirectory() {
99 const env = process.env.ELECTRON_BUILDER_CACHE;
100 return (0, util_1.isEmptyOrSpaces)(env) ? path.join((0, os_1.homedir)(), "Library", "Caches", "electron-builder") : path.resolve(env);
101}
102function listUserKeychains() {
103 return (0, util_1.exec)("/usr/bin/security", ["list-keychains", "-d", "user"]).then(it => it
104 .split("\n")
105 .map(it => {
106 const r = it.trim();
107 return r.substring(1, r.length - 1);
108 })
109 .filter(it => it.length > 0));
110}
111function removeKeychain(keychainFile, printWarn = true) {
112 return (0, util_1.exec)("/usr/bin/security", ["delete-keychain", keychainFile]).catch((e) => {
113 if (printWarn) {
114 util_1.log.warn({ file: keychainFile, error: e.stack || e }, "cannot delete keychain");
115 }
116 return (0, fs_1.unlinkIfExists)(keychainFile);
117 });
118}
119exports.removeKeychain = removeKeychain;
120async function createKeychain({ tmpDir, cscLink, cscKeyPassword, cscILink, cscIKeyPassword, currentDir }) {
121 // travis has correct AppleWWDRCA cert
122 if (process.env.TRAVIS !== "true") {
123 await bundledCertKeychainAdded.value;
124 }
125 // https://github.com/electron-userland/electron-builder/issues/3685
126 // use constant file
127 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`);
128 // noinspection JSUnusedLocalSymbols
129 // eslint-disable-next-line @typescript-eslint/no-unused-vars
130 await removeKeychain(keychainFile, false).catch(_ => {
131 /* ignore*/
132 });
133 const certLinks = [cscLink];
134 if (cscILink != null) {
135 certLinks.push(cscILink);
136 }
137 const certPaths = new Array(certLinks.length);
138 const keychainPassword = (0, crypto_1.randomBytes)(32).toString("base64");
139 const securityCommands = [
140 ["create-keychain", "-p", keychainPassword, keychainFile],
141 ["unlock-keychain", "-p", keychainPassword, keychainFile],
142 ["set-keychain-settings", keychainFile],
143 ];
144 // https://stackoverflow.com/questions/42484678/codesign-keychain-gets-ignored
145 // https://github.com/electron-userland/electron-builder/issues/1457
146 const list = await listUserKeychains();
147 if (!list.includes(keychainFile)) {
148 securityCommands.push(["list-keychains", "-d", "user", "-s", keychainFile].concat(list));
149 }
150 await Promise.all([
151 // 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.
152 bluebird_lst_1.default.map(certLinks, (link, i) => (0, codesign_1.importCertificate)(link, tmpDir, currentDir).then(it => (certPaths[i] = it))),
153 bluebird_lst_1.default.mapSeries(securityCommands, it => (0, util_1.exec)("/usr/bin/security", it)),
154 ]);
155 return await importCerts(keychainFile, certPaths, [cscKeyPassword, cscIKeyPassword].filter(it => it != null));
156}
157exports.createKeychain = createKeychain;
158async function importCerts(keychainFile, paths, keyPasswords) {
159 for (let i = 0; i < paths.length; i++) {
160 const password = keyPasswords[i];
161 await (0, util_1.exec)("/usr/bin/security", ["import", paths[i], "-k", keychainFile, "-T", "/usr/bin/codesign", "-T", "/usr/bin/productbuild", "-P", password]);
162 // https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p
163 // https://github.com/electron-userland/electron-packager/issues/701#issuecomment-322315996
164 await (0, util_1.exec)("/usr/bin/security", ["set-key-partition-list", "-S", "apple-tool:,apple:", "-s", "-k", password, keychainFile]);
165 }
166 return {
167 keychainFile,
168 };
169}
170async function sign(opts) {
171 return (0, util_1.retry)(() => (0, osx_sign_1.signAsync)(opts), 3, 5000, 5000);
172}
173exports.sign = sign;
174exports.findIdentityRawResult = null;
175async function getValidIdentities(keychain) {
176 function addKeychain(args) {
177 if (keychain != null) {
178 args.push(keychain);
179 }
180 return args;
181 }
182 let result = exports.findIdentityRawResult;
183 if (result == null || keychain != null) {
184 // https://github.com/electron-userland/electron-builder/issues/481
185 // https://github.com/electron-userland/electron-builder/issues/535
186 result = Promise.all([
187 (0, util_1.exec)("/usr/bin/security", addKeychain(["find-identity", "-v"])).then(it => it
188 .trim()
189 .split("\n")
190 .filter(it => {
191 for (const prefix of exports.appleCertificatePrefixes) {
192 if (it.includes(prefix)) {
193 return true;
194 }
195 }
196 return false;
197 })),
198 (0, util_1.exec)("/usr/bin/security", addKeychain(["find-identity", "-v", "-p", "codesigning"])).then(it => it.trim().split("\n")),
199 ]).then(it => {
200 const array = it[0]
201 .concat(it[1])
202 .filter(it => !it.includes("(Missing required extension)") && !it.includes("valid identities found") && !it.includes("iPhone ") && !it.includes("com.apple.idms.appleid.prd."))
203 // remove 1)
204 .map(it => it.substring(it.indexOf(")") + 1).trim());
205 return Array.from(new Set(array));
206 });
207 if (keychain == null) {
208 exports.findIdentityRawResult = result;
209 }
210 }
211 return result;
212}
213async function _findIdentity(type, qualifier, keychain) {
214 // https://github.com/electron-userland/electron-builder/issues/484
215 //noinspection SpellCheckingInspection
216 const lines = await getValidIdentities(keychain);
217 const namePrefix = `${type}:`;
218 for (const line of lines) {
219 if (qualifier != null && !line.includes(qualifier)) {
220 continue;
221 }
222 if (line.includes(namePrefix)) {
223 return parseIdentity(line);
224 }
225 }
226 if (type === "Developer ID Application") {
227 // find non-Apple certificate
228 // https://github.com/electron-userland/electron-builder/issues/458
229 l: for (const line of lines) {
230 if (qualifier != null && !line.includes(qualifier)) {
231 continue;
232 }
233 if (line.includes("Mac Developer:")) {
234 continue;
235 }
236 for (const prefix of exports.appleCertificatePrefixes) {
237 if (line.includes(prefix)) {
238 continue l;
239 }
240 }
241 return parseIdentity(line);
242 }
243 }
244 return null;
245}
246function parseIdentity(line) {
247 const firstQuoteIndex = line.indexOf('"');
248 const name = line.substring(firstQuoteIndex + 1, line.lastIndexOf('"'));
249 const hash = line.substring(0, firstQuoteIndex - 1);
250 return new util_identities_1.Identity(name, hash);
251}
252function findIdentity(certType, qualifier, keychain) {
253 let identity = qualifier || process.env.CSC_NAME;
254 if ((0, util_1.isEmptyOrSpaces)(identity)) {
255 if ((0, flags_1.isAutoDiscoveryCodeSignIdentity)()) {
256 return _findIdentity(certType, null, keychain);
257 }
258 else {
259 return Promise.resolve(null);
260 }
261 }
262 else {
263 identity = identity.trim();
264 for (const prefix of exports.appleCertificatePrefixes) {
265 checkPrefix(identity, prefix);
266 }
267 return _findIdentity(certType, identity, keychain);
268 }
269}
270exports.findIdentity = findIdentity;
271function checkPrefix(name, prefix) {
272 if (name.startsWith(prefix)) {
273 throw new util_1.InvalidConfigurationError(`Please remove prefix "${prefix}" from the specified name — appropriate certificate will be chosen automatically`);
274 }
275}
276//# sourceMappingURL=macCodeSign.js.map
\No newline at end of file