From de01c6dd6432c5a23278d3da873f1c01143fc800 Mon Sep 17 00:00:00 2001 From: develar Date: Mon, 4 Jul 2016 12:34:01 +0200 Subject: [PATCH] feat: multi-cert p12 Closes #560 --- .idea/dictionaries/develar.xml | 2 + .idea/runConfigurations/CodeSignTest.xml | 2 +- docs/Code Signing.md | 24 +++-- docs/Options.md | 6 +- package.json | 3 +- src/codeSign.ts | 118 ++++++++++++---------- src/macPackager.ts | 121 +++++++++-------------- src/metadata.ts | 6 +- src/packager.ts | 2 +- src/platformPackager.ts | 8 +- src/targets/dmg.ts | 2 +- src/util/deepAssign.ts | 38 +++++++ test/src/CodeSignTest.ts | 13 +-- test/src/helpers/codeSignData.ts | 8 +- test/src/helpers/packTester.ts | 14 +-- test/src/macPackagerTest.ts | 57 +++++++---- test/tsconfig.json | 5 +- tsconfig.json | 6 +- typings/deep-assign.d.ts | 7 -- 19 files changed, 237 insertions(+), 205 deletions(-) create mode 100644 src/util/deepAssign.ts delete mode 100644 typings/deep-assign.d.ts diff --git a/.idea/dictionaries/develar.xml b/.idea/dictionaries/develar.xml index 63aed6f8c70..fe554bc3971 100644 --- a/.idea/dictionaries/develar.xml +++ b/.idea/dictionaries/develar.xml @@ -3,6 +3,7 @@ actperepo appimage + appleid appveyor archs aspx @@ -38,6 +39,7 @@ hicolor hrtime icnsutils + idms inno installmode instdir diff --git a/.idea/runConfigurations/CodeSignTest.xml b/.idea/runConfigurations/CodeSignTest.xml index e1dd0167fee..f0946aeda3c 100644 --- a/.idea/runConfigurations/CodeSignTest.xml +++ b/.idea/runConfigurations/CodeSignTest.xml @@ -1,5 +1,5 @@ - + diff --git a/docs/Code Signing.md b/docs/Code Signing.md index 738fe37549b..2d07c4b21b6 100644 --- a/docs/Code Signing.md +++ b/docs/Code Signing.md @@ -1,14 +1,12 @@ -MacOS and Windows code signing is supported. Windows is dual code-signed (SHA1 & SHA256 hashing algorithms). +macOS and Windows code signing is supported. Windows is dual code-signed (SHA1 & SHA256 hashing algorithms). -On a MacOS development machine valid and appropriate identity from your keychain will be automatically used. +On a macOS development machine valid and appropriate identity from your keychain will be automatically used. | Env name | Description | -------------- | ----------- -| `CSC_LINK` | The HTTPS link (or base64-encoded data) to certificate (`*.p12` file). +| `CSC_LINK` | The HTTPS link (or base64-encoded data, or `file://` link) to certificate (`*.p12` file). | `CSC_KEY_PASSWORD` | The password to decrypt the certificate given in `CSC_LINK`. -| `CSC_INSTALLER_LINK` | *osx-only* The HTTPS link (or base64-encoded data) to certificate to sign Mac App Store build (`*.p12` file). -| `CSC_INSTALLER_KEY_PASSWORD` | *osx-only* The password to decrypt the certificate given in `CSC_INSTALLER_LINK`. -| `CSC_NAME` | *osx-only* Name of certificate (to retrieve from login.keychain). Useful on a development machine (not on CI) if you have several identities (otherwise don't specify it). +| `CSC_NAME` | *macOS-only* Name of certificate (to retrieve from login.keychain). Useful on a development machine (not on CI) if you have several identities (otherwise don't specify it). ## Travis, AppVeyor and other CI Servers To sign app on build server you need to set `CSC_LINK`, `CSC_KEY_PASSWORD` (and `CSC_INSTALLER_LINK`, `CSC_INSTALLER_KEY_PASSWORD` if you build for Mac App Store): @@ -26,4 +24,16 @@ To sign app on build server you need to set `CSC_LINK`, `CSC_KEY_PASSWORD` (and # Where to Buy Code Signing Certificate [StartSSL](https://startssl.com/Support?v=34) is recommended. -Please note — Gatekeeper only recognises [Apple digital certificates](http://stackoverflow.com/questions/11833481/non-apple-issued-code-signing-certificate-can-it-work-with-mac-os-10-8-gatekeep). \ No newline at end of file +Please note — Gatekeeper only recognises [Apple digital certificates](http://stackoverflow.com/questions/11833481/non-apple-issued-code-signing-certificate-can-it-work-with-mac-os-10-8-gatekeep). + +# How to Export Certificate on macOS + +1. Open Keychain. +2. Select `login` keychain, and `My Certificates` category. +3. Select all required certificates (hint: use cmd-click to select several): + * `Developer ID Application:` to sign app for macOS. + * `3rd Party Mac Developer Application:` and `3rd Party Mac Developer Installer:` to sign app for MAS (Mac App Store). + + Please note – you can select as many certificates, as need. No restrictions on electron-builder side. + All selected certificates will be imported into temporary keychain on CI server. +4. Open context menu and `Export`. \ No newline at end of file diff --git a/docs/Options.md b/docs/Options.md index 1660b6c1427..52beb1322a8 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -51,7 +51,7 @@ Here documented only `electron-builder` specific options: | Name | Description | --- | --- | appId |

The application id. Used as [CFBundleIdentifier](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070) for MacOS and as [Application User Model ID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx) for Windows.

For windows only NSIS target supports it. Squirrel.Windows is not fixed yet.

Defaults to com.electron.${name}. It is strongly recommended that an explicit ID be set.

-| app-category-type |

*MacOS-only.* The application category type, as shown in the Finder via *View -> Arrange by Application Category* when viewing the Applications directory.

For example, app-category-type=public.app-category.developer-tools will set the application category to *Developer Tools*.

Valid values are listed in [Apple’s documentation](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8).

+| app-category-type |

*macOS-only.* The application category type, as shown in the Finder via *View -> Arrange by Application Category* when viewing the Applications directory.

For example, app-category-type=public.app-category.developer-tools will set the application category to *Developer Tools*.

Valid values are listed in [Apple’s documentation](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8).

| asar |

Whether to package the application’s source code into an archive, using [Electron’s archive format](https://github.com/electron/asar). Defaults to true. Reasons why you may want to disable this feature are described in [an application packaging tutorial in Electron’s documentation](http://electron.atom.io/docs/latest/tutorial/application-packaging/#limitations-on-node-api/).

Or you can pass object of any asar options.

| productName | See [AppMetadata.productName](#AppMetadata-productName). | files |

A [glob patterns](https://www.npmjs.com/package/glob#glob-primer) relative to the [app directory](#MetadataDirectories-app), which specifies which files to include when copying files to create the package. Defaults to \*\*\/\* (i.e. [hidden files are ignored by default](https://www.npmjs.com/package/glob#dots)).

Development dependencies are never copied in any case. You don’t need to ignore it explicitly.

[Multiple patterns](#multiple-glob-patterns) are supported. You can use ${os} (expanded to mac, linux or win according to current platform) and ${arch} in the pattern. If directory matched, all contents are copied. So, you can just specify foo to copy foo directory.

Remember that default pattern \*\*\/\* is not added to your custom, so, you have to add it explicitly — e.g. ["\*\*\/\*", "!ignoreMe${/\*}"].

May be specified in the platform options (e.g. in the build.mac).

@@ -76,8 +76,8 @@ MacOS specific build options. | target | Target package type: list of `default`, `dmg`, `mas`, `7z`, `zip`, `tar.xz`, `tar.lz`, `tar.gz`, `tar.bz2`. Defaults to `default` (dmg and zip for Squirrel.Mac). | identity |

The name of certificate to use when signing. Consider using environment variables [CSC_LINK or CSC_NAME](https://github.com/electron-userland/electron-builder/wiki/Code-Signing). MAS installer identity is specified in the [.build.mas](#MasBuildOptions-identity).

| icon | The path to application icon. Defaults to `build/icon.icns` (consider using this convention instead of complicating your configuration). -| entitlements |

The path to entitlements file for signing the app. build/entitlements.osx.plist will be used if exists (it is a recommended way to set). MAS entitlements is specified in the [.build.mas](#MasBuildOptions-entitlements).

-| entitlementsInherit |

The path to child entitlements which inherit the security settings for signing frameworks and bundles of a distribution. build/entitlements.osx.inherit.plist will be used if exists (it is a recommended way to set). Otherwise [default](https://github.com/electron-userland/electron-osx-sign/blob/master/default.entitlements.darwin.inherit.plist).

This option only applies when signing with entitlements provided.

+| entitlements |

The path to entitlements file for signing the app. build/entitlements.mac.plist will be used if exists (it is a recommended way to set). MAS entitlements is specified in the [.build.mas](#MasBuildOptions-entitlements).

+| entitlementsInherit |

The path to child entitlements which inherit the security settings for signing frameworks and bundles of a distribution. build/entitlements.mac.inherit.plist will be used if exists (it is a recommended way to set). Otherwise [default](https://github.com/electron-userland/electron-osx-sign/blob/master/default.entitlements.darwin.inherit.plist).

This option only applies when signing with entitlements provided.

### `.build.dmg` diff --git a/package.json b/package.json index 86ae0b00821..126c147404b 100644 --- a/package.json +++ b/package.json @@ -65,8 +65,7 @@ "chalk": "^1.1.3", "cli-cursor": "^1.0.2", "debug": "^2.2.0", - "deep-assign": "^2.0.0", - "electron-osx-sign-tf": "0.6.0", + "electron-osx-sign": "^0.4.0-beta4", "electron-packager-tf": "~7.5.2", "electron-winstaller-fixed": "~2.11.6", "fs-extra-p": "^1.0.5", diff --git a/src/codeSign.ts b/src/codeSign.ts index 76147a46f02..dd8a5f0ede5 100644 --- a/src/codeSign.ts +++ b/src/codeSign.ts @@ -1,4 +1,4 @@ -import { exec, getTempName } from "./util/util" +import { exec, getTempName, isEmptyOrSpaces } from "./util/util" import { deleteFile, outputFile, copy, rename } from "fs-extra-p" import { download } from "./util/httpRequest" import { tmpdir } from "os" @@ -11,27 +11,30 @@ import { homedir } from "os" //noinspection JSUnusedLocalSymbols const __awaiter = require("./util/awaiter") -export const appleCertificatePrefixes = ["Developer ID Application:", "3rd Party Mac Developer Application:", "Developer ID Installer:", "3rd Party Mac Developer Installer:"] +const appleCertificatePrefixes = ["Developer ID Application:", "3rd Party Mac Developer Application:", "Developer ID Installer:", "3rd Party Mac Developer Installer:"] export type CertType = "Developer ID Application" | "3rd Party Mac Developer Application" | "Developer ID Installer" | "3rd Party Mac Developer Installer" export interface CodeSigningInfo { - name: string keychainName?: string | null - - installerName?: string | null } export function generateKeychainName(): string { return path.join(tmpdir(), getTempName("csc") + ".keychain") } -function downloadUrlOrBase64(urlOrBase64: string, destination: string): BluebirdPromise { +export function downloadCertificate(urlOrBase64: string): BluebirdPromise { + const tempFile = path.join(tmpdir(), `${getTempName()}.p12`) if (urlOrBase64.startsWith("https://")) { - return download(urlOrBase64, destination) + return download(urlOrBase64, tempFile) + .thenReturn(tempFile) + } + else if (urlOrBase64.startsWith("file://")) { + return BluebirdPromise.resolve(urlOrBase64.substring("file://".length)) } else { - return outputFile(destination, new Buffer(urlOrBase64, "base64")) + return outputFile(tempFile, new Buffer(urlOrBase64, "base64")) + .thenReturn(tempFile) } } @@ -77,11 +80,7 @@ export async function createKeychain(keychainName: string, cscLink: string, cscK const certPaths = new Array(certLinks.length) const keychainPassword = randomBytes(8).toString("hex") return await executeFinally(BluebirdPromise.all([ - BluebirdPromise.map(certLinks, (link, i) => { - const tempFile = path.join(tmpdir(), `${getTempName()}.p12`) - certPaths[i] = tempFile - return downloadUrlOrBase64(link, tempFile) - }), + BluebirdPromise.map(certLinks, (link, i) => downloadCertificate(link).then(it => certPaths[i] = it)), BluebirdPromise.mapSeries([ ["create-keychain", "-p", keychainPassword, keychainName], ["unlock-keychain", "-p", keychainPassword, keychainName], @@ -90,7 +89,7 @@ export async function createKeychain(keychainName: string, cscLink: string, cscK ]) .then(() => importCerts(keychainName, certPaths, >[cscKeyPassword, cscIKeyPassword].filter(it => it != null))), errorOccurred => { - const tasks = certPaths.map(it => deleteFile(it, true)) + const tasks = certPaths.map((it, index) => certLinks[index].startsWith("file://") ? BluebirdPromise.resolve() : deleteFile(it, true)) if (errorOccurred) { tasks.push(deleteKeychain(keychainName)) } @@ -99,40 +98,19 @@ export async function createKeychain(keychainName: string, cscLink: string, cscK } async function importCerts(keychainName: string, paths: Array, keyPasswords: Array): Promise { - const namePromises: Array> = [] for (let i = 0; i < paths.length; i++) { - const password = keyPasswords[i] - const certPath = paths[i] - await exec("security", ["import", certPath, "-k", keychainName, "-T", "/usr/bin/codesign", "-T", "/usr/bin/productbuild", "-P", password]) - - namePromises.push(extractCommonName(password, certPath)) + await exec("security", ["import", paths[i], "-k", keychainName, "-T", "/usr/bin/codesign", "-T", "/usr/bin/productbuild", "-P", keyPasswords[i]]) } - const names = await BluebirdPromise.all(namePromises) return { - name: names[0], - installerName: names.length > 1 ? names[1] : null, keychainName: keychainName, } } -function extractCommonName(password: string, certPath: string): BluebirdPromise { - return exec("openssl", ["pkcs12", "-nokeys", "-nodes", "-passin", "pass:" + password, "-nomacver", "-clcerts", "-in", certPath]) - .then(result => { - const match = | null>(result.match(/^subject.*\/CN=([^\/\n]+)/m)) - if (match == null || match[1] == null) { - throw new Error("Cannot extract common name from p12") - } - else { - return match[1]! - } - }) -} - -export function sign(path: string, options: CodeSigningInfo): BluebirdPromise { - const args = ["--deep", "--force", "--sign", options.name, path] - if (options.keychainName != null) { - args.push("--keychain", options.keychainName) +export function sign(path: string, name: string, keychain: string): BluebirdPromise { + const args = ["--deep", "--force", "--sign", name, path] + if (keychain != null) { + args.push("--keychain", keychain) } return exec("codesign", args) } @@ -151,20 +129,22 @@ export function deleteKeychain(keychainName: string, ignoreNotFound: boolean = t } } -export function downloadCertificate(cscLink: string): Promise { - const certPath = path.join(tmpdir(), `${getTempName()}.p12`) - return downloadUrlOrBase64(cscLink, certPath) - .thenReturn(certPath) -} - export let findIdentityRawResult: Promise> | null = null -export async function findIdentity(namePrefix: CertType, qualifier?: string): Promise { - if (findIdentityRawResult == null) { +async function getValidIdentities(keychain?: string | null): Promise> { + function addKeychain(args: Array) { + if (keychain != null) { + args.push(keychain) + } + return args + } + + let result = findIdentityRawResult + if (result == null || keychain != null) { // https://github.com/electron-userland/electron-builder/issues/481 // https://github.com/electron-userland/electron-builder/issues/535 - findIdentityRawResult = BluebirdPromise.all>([ - exec("security", ["find-identity", "-v"]) + result = BluebirdPromise.all>([ + exec("security", addKeychain(["find-identity", "-v"])) .then(it => it.trim().split("\n").filter(it => { for (let prefix of appleCertificatePrefixes) { if (it.includes(prefix)) { @@ -173,7 +153,7 @@ export async function findIdentity(namePrefix: CertType, qualifier?: string): Pr } return false })), - exec("security", ["find-identity", "-v", "-p", "codesigning"]) + exec("security", addKeychain(["find-identity", "-v", "-p", "codesigning"])) .then(it => it.trim().split(("\n"))), ]) .then(it => { @@ -183,11 +163,18 @@ export async function findIdentity(namePrefix: CertType, qualifier?: string): Pr .map(it => it.substring(it.indexOf(")") + 1).trim()) return Array.from(new Set(array)) }) + + if (keychain == null) { + findIdentityRawResult = result + } } + return result +} +async function _findIdentity(namePrefix: CertType, qualifier?: string | null, keychain?: string | null): Promise { // https://github.com/electron-userland/electron-builder/issues/484 //noinspection SpellCheckingInspection - const lines = await findIdentityRawResult + const lines = await getValidIdentities(keychain) for (let line of lines) { if (qualifier != null && !line.includes(qualifier)) { continue @@ -216,4 +203,31 @@ export async function findIdentity(namePrefix: CertType, qualifier?: string): Pr } } return null +} + +export async function findIdentity(certType: CertType, qualifier?: string | null, keychain?: string | null): Promise { + let identity = process.env.CSC_NAME || qualifier + if (isEmptyOrSpaces(identity)) { + if (keychain == null && process.env.CI == null && process.env.CSC_IDENTITY_AUTO_DISCOVERY === "false") { + return null + } + return await _findIdentity(certType, null, keychain) + } + else { + identity = identity.trim() + for (let prefix of appleCertificatePrefixes) { + checkPrefix(identity, prefix) + } + const result = await _findIdentity(certType, identity, keychain) + if (result == null) { + throw new Error(`Identity name "${identity}" is specified, but no valid identity with this name in the keychain`) + } + return result + } +} + +function checkPrefix(name: string, prefix: string) { + if (name.startsWith(prefix)) { + throw new Error(`Please remove prefix "${prefix}" from the specified name — appropriate certificate will be chosen automatically`) + } } \ No newline at end of file diff --git a/src/macPackager.ts b/src/macPackager.ts index 817775f4ff5..6f1a3fe9767 100644 --- a/src/macPackager.ts +++ b/src/macPackager.ts @@ -2,11 +2,10 @@ import { PlatformPackager, BuildInfo, Target } from "./platformPackager" import { Platform, MasBuildOptions, Arch, MacOptions } from "./metadata" import * as path from "path" import { Promise as BluebirdPromise } from "bluebird" -import { isEmptyOrSpaces } from "./util/util" import { log, warn, task } from "./util/log" -import { createKeychain, deleteKeychain, CodeSigningInfo, generateKeychainName, findIdentity, appleCertificatePrefixes, CertType } from "./codeSign" -import deepAssign = require("deep-assign") -import { signAsync, flatAsync, BaseSignOptions, SignOptions, FlatOptions } from "electron-osx-sign-tf" +import { createKeychain, deleteKeychain, CodeSigningInfo, generateKeychainName, findIdentity } from "./codeSign" +import { deepAssign } from "./util/deepAssign" +import { signAsync, flatAsync, BaseSignOptions, SignOptions, FlatOptions } from "electron-osx-sign" import { DmgTarget } from "./targets/dmg" import { createCommonTarget, DEFAULT_TARGET } from "./targets/targetFactory" @@ -14,13 +13,13 @@ import { createCommonTarget, DEFAULT_TARGET } from "./targets/targetFactory" const __awaiter = require("./util/awaiter") export default class MacPackager extends PlatformPackager { - codeSigningInfo: Promise + codeSigningInfo: Promise constructor(info: BuildInfo, cleanupTasks: Array<() => Promise>) { super(info) if (this.options.cscLink == null) { - this.codeSigningInfo = BluebirdPromise.resolve(null) + this.codeSigningInfo = BluebirdPromise.resolve({}) } else { const keychainName = generateKeychainName() @@ -97,83 +96,43 @@ export default class MacPackager extends PlatformPackager { } } - private static async findIdentity(certType: CertType, name?: string | null): Promise { - let identity = process.env.CSC_NAME || name - if (isEmptyOrSpaces(identity)) { - if (process.env.CSC_IDENTITY_AUTO_DISCOVERY === "false") { - return null - } - return await findIdentity(certType) - } - else { - identity = identity.trim() - for (let prefix of appleCertificatePrefixes) { - checkPrefix(identity, prefix) - } - const result = await findIdentity(certType, identity) - if (result == null) { - throw new Error(`Identity name "${identity}" is specified, but no valid identity with this name in the keychain`) - } - return result + private async sign(appOutDir: string, masOptions: MasBuildOptions | null): Promise { + let keychainName = (await this.codeSigningInfo).keychainName + if (process.env.CSC_LINK != null) { + throw new Error("keychainName is null, but CSC_LINK defined") } - } - private async sign(appOutDir: string, masOptions: MasBuildOptions | null): Promise { - let codeSigningInfo = await this.codeSigningInfo - if (codeSigningInfo == null) { - if (process.env.CSC_LINK != null) { - throw new Error("codeSigningInfo is null, but CSC_LINK defined") - } + const masQualifier = masOptions == null ? null : (masOptions.identity || this.platformSpecificBuildOptions.identity) - const identity = await MacPackager.findIdentity(masOptions == null ? "Developer ID Application" : "3rd Party Mac Developer Application", this.platformSpecificBuildOptions.identity) - if (identity == null) { - const message = "App is not signed: CSC_LINK or CSC_NAME are not specified, and no valid identity in the keychain, see https://github.com/electron-userland/electron-builder/wiki/Code-Signing" - if (masOptions == null) { - warn(message) - return - } - else { - throw new Error(message) - } - } - - if (masOptions != null) { - const installerName = masOptions == null ? null : (await MacPackager.findIdentity("3rd Party Mac Developer Installer", this.platformSpecificBuildOptions.identity)) - if (installerName == null) { - throw new Error("Cannot find valid installer certificate: CSC_LINK or CSC_NAME are not specified, and no valid identity in the keychain, see https://github.com/electron-userland/electron-builder/wiki/Code-Signing") - } - - codeSigningInfo = { - name: identity, - installerName: installerName, - } + let name = await findIdentity(masOptions == null ? "Developer ID Application" : "3rd Party Mac Developer Application", masOptions == null ? this.platformSpecificBuildOptions.identity : masQualifier, keychainName) + if (name == null) { + const message = "App is not signed: CSC_LINK is not specified, and no valid identity in the keychain, see https://github.com/electron-userland/electron-builder/wiki/Code-Signing" + if (masOptions == null) { + warn(message) + return } else { - codeSigningInfo = { - name: identity, - } + throw new Error(message) } } - else { - if (codeSigningInfo.name == null && masOptions == null) { - throw new Error("codeSigningInfo.name is null, but CSC_LINK defined") - } - if (masOptions != null && codeSigningInfo.installerName == null) { - throw new Error("Signing is required for mas builds but CSC_INSTALLER_LINK is not specified") + + let installerName: string | null = null + if (masOptions != null) { + installerName = await findIdentity("3rd Party Mac Developer Installer", masQualifier, keychainName) + if (installerName == null) { + throw new Error("Cannot find valid installer certificate: CSC_LINK is not specified, and no valid identity in the keychain, see https://github.com/electron-userland/electron-builder/wiki/Code-Signing") } } - const identity = codeSigningInfo.name - const baseSignOptions: BaseSignOptions = { app: path.join(appOutDir, `${this.appInfo.productFilename}.app`), platform: masOptions == null ? "darwin" : "mas", - keychain: codeSigningInfo.keychainName, + keychain: keychainName || undefined, version: this.info.electronVersion } const signOptions = Object.assign({ - identity: identity, + identity: name, }, (this.devMetadata.build)["osx-sign"], baseSignOptions) const resourceList = await this.resourceList @@ -183,29 +142,45 @@ export default class MacPackager extends PlatformPackager { signOptions.entitlements = customSignOptions.entitlements } else { - const p = `entitlements.${masOptions == null ? "osx" : "mas"}.plist` + let p = `entitlements.${masOptions == null ? "osx" : "mas"}.plist` if (resourceList.includes(p)) { + warn("entitlements.osx.plist is deprecated name, please use entitlements.mac.plist") signOptions.entitlements = path.join(this.buildResourcesDir, p) } + + if (masOptions == null) { + p = `entitlements.mac.plist` + if (resourceList.includes(p)) { + signOptions.entitlements = path.join(this.buildResourcesDir, p) + } + } } if (customSignOptions.entitlementsInherit != null) { signOptions["entitlements-inherit"] = customSignOptions.entitlementsInherit } else { - const p = `entitlements.${masOptions == null ? "osx" : "mas"}.inherit.plist` + let p = `entitlements.${masOptions == null ? "osx" : "mas"}.inherit.plist` if (resourceList.includes(p)) { + warn("entitlements.osx.inherit.plist is deprecated name, please use entitlements.mac.inherit.plist") signOptions["entitlements-inherit"] = path.join(this.buildResourcesDir, p) } + if (masOptions == null) { + p = `entitlements.mac.inherit.plist` + if (resourceList.includes(p)) { + signOptions["entitlements-inherit"] = path.join(this.buildResourcesDir, p) + } + } } - await task(`Signing app (identity: ${identity})`, this.doSign(signOptions)) + await task(`Signing app (identity: ${name})`, this.doSign(signOptions)) if (masOptions != null) { + await task(`Signing app (identity: ${name})`, this.doSign(signOptions)) const pkg = path.join(appOutDir, `${this.appInfo.productFilename}-${this.appInfo.version}.pkg`) await this.doFlat(Object.assign({ pkg: pkg, - identity: codeSigningInfo.installerName, + identity: installerName, }, baseSignOptions)) this.dispatchArtifactCreated(pkg, `${this.appInfo.name}-${this.appInfo.version}.pkg`) } @@ -236,10 +211,4 @@ export default class MacPackager extends PlatformPackager { } } } -} - -function checkPrefix(name: string, prefix: string) { - if (name.startsWith(prefix)) { - throw new Error(`Please remove prefix "${prefix}" from the specified name — appropriate certificate will be chosen automatically`) - } } \ No newline at end of file diff --git a/src/metadata.ts b/src/metadata.ts index 3bf3bd89250..cc903ebdb65 100755 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -95,7 +95,7 @@ export interface BuildMetadata { readonly "app-bundle-id"?: string | null /* - *MacOS-only.* The application category type, as shown in the Finder via *View -> Arrange by Application Category* when viewing the Applications directory. + *macOS-only.* The application category type, as shown in the Finder via *View -> Arrange by Application Category* when viewing the Applications directory. For example, `app-category-type=public.app-category.developer-tools` will set the application category to *Developer Tools*. @@ -234,13 +234,13 @@ export interface MacOptions extends PlatformSpecificBuildOptions { readonly icon?: string | null /* - The path to entitlements file for signing the app. `build/entitlements.osx.plist` will be used if exists (it is a recommended way to set). + The path to entitlements file for signing the app. `build/entitlements.mac.plist` will be used if exists (it is a recommended way to set). MAS entitlements is specified in the [.build.mas](#MasBuildOptions-entitlements). */ readonly entitlements?: string | null /* - The path to child entitlements which inherit the security settings for signing frameworks and bundles of a distribution. `build/entitlements.osx.inherit.plist` will be used if exists (it is a recommended way to set). + The path to child entitlements which inherit the security settings for signing frameworks and bundles of a distribution. `build/entitlements.mac.inherit.plist` will be used if exists (it is a recommended way to set). Otherwise [default](https://github.com/electron-userland/electron-osx-sign/blob/master/default.entitlements.darwin.inherit.plist). This option only applies when signing with `entitlements` provided. diff --git a/src/packager.ts b/src/packager.ts index 9dffc249d21..5440bb24d73 100644 --- a/src/packager.ts +++ b/src/packager.ts @@ -11,7 +11,7 @@ import { PackagerOptions, PlatformPackager, BuildInfo, ArtifactCreated, Target } import { WinPackager } from "./winPackager" import * as errorMessages from "./errorMessages" import * as util from "util" -import deepAssign = require("deep-assign") +import { deepAssign } from "./util/deepAssign" import semver = require("semver") import { warn, log } from "./util/log" import { AppInfo } from "./appInfo" diff --git a/src/platformPackager.ts b/src/platformPackager.ts index 090d83f70f8..4276cd85bac 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -4,13 +4,13 @@ import { Promise as BluebirdPromise } from "bluebird" import * as path from "path" import { pack, ElectronPackagerOptions, userIgnoreFilter } from "electron-packager-tf" import { readdir, remove, realpath } from "fs-extra-p" -import { statOrNull, use, unlinkIfExists } from "./util/util" +import { statOrNull, use, unlinkIfExists, isEmptyOrSpaces } from "./util/util" import { Packager } from "./packager" import { AsarOptions } from "asar" import { archiveApp } from "./targets/archive" import { Minimatch } from "minimatch" import { checkFileInPackage, createAsarArchive } from "./asarUtil" -import deepAssign = require("deep-assign") +import { deepAssign } from "./util/deepAssign" import { warn, log, task } from "./util/log" import { AppInfo } from "./appInfo" import { listDependencies, createFilter, copyFiltered, hasMagic } from "./util/filter" @@ -123,8 +123,8 @@ export abstract class PlatformPackager } protected getCscPassword(): string { - const password = this.options.cscKeyPassword - if (password == null) { + const password = this.options.cscKeyPassword || process.env.CSC_KEY_PASSWORD + if (isEmptyOrSpaces(password)) { log("CSC_KEY_PASSWORD is not defined, empty password will be used") return "" } diff --git a/src/targets/dmg.ts b/src/targets/dmg.ts index 2b48deb21a2..fbfae33b613 100644 --- a/src/targets/dmg.ts +++ b/src/targets/dmg.ts @@ -1,4 +1,4 @@ -import deepAssign = require("deep-assign") +import { deepAssign } from "../util/deepAssign" import * as path from "path" import { log } from "../util/log" import { Target, PlatformPackager } from "../platformPackager" diff --git a/src/util/deepAssign.ts b/src/util/deepAssign.ts new file mode 100644 index 00000000000..56c50a4aef4 --- /dev/null +++ b/src/util/deepAssign.ts @@ -0,0 +1,38 @@ +function isObject(x: any) { + const type = typeof x + return type === 'object' || type === 'function' +} + +function assignKey(to: any, from: any, key: string) { + const value = from[key] + // https://github.com/electron-userland/electron-builder/pull/562 + if (value === undefined) { + return + } + + const prevValue = to[key] + if (prevValue == null || value === null || typeof prevValue !== 'object' || !isObject(value)) { + to[key] = value + } + else { + to[key] = assign(prevValue, value) + } +} + +function assign(to: any, from: any) { + if (to !== from) { + for (let key of Object.getOwnPropertyNames(from)) { + assignKey(to, from, key) + } + } + return to +} + +export function deepAssign(target: any, ...objects: Array) { + for (let o of objects) { + if (o != null) { + assign(target, o) + } + } + return target +} \ No newline at end of file diff --git a/test/src/CodeSignTest.ts b/test/src/CodeSignTest.ts index b0b6f742461..8acf113f58f 100644 --- a/test/src/CodeSignTest.ts +++ b/test/src/CodeSignTest.ts @@ -1,12 +1,7 @@ import { createKeychain, deleteKeychain, generateKeychainName } from "out/codeSign" import * as assertThat from "should/as-function" import test from "./helpers/avaEx" -import { - CSC_NAME, CSC_LINK, - CSC_KEY_PASSWORD, - CSC_INSTALLER_KEY_PASSWORD, - CSC_INSTALLER_LINK -} from "./helpers/codeSignData" +import { CSC_LINK } from "./helpers/codeSignData" import { executeFinally, all } from "out/util/promise" import { removePassword } from "out/util/util" @@ -15,19 +10,17 @@ const __awaiter = require("out/util/awaiter") test.ifOsx("create keychain", async () => { const keychainName = generateKeychainName() - await executeFinally(createKeychain(keychainName, CSC_LINK, CSC_KEY_PASSWORD) + await executeFinally(createKeychain(keychainName, CSC_LINK, process.env.CSC_KEY_PASSWORD) .then(result => { assertThat(result.keychainName).not.empty() - assertThat(result.name).equal(CSC_NAME) }), () => all([deleteKeychain(keychainName)])) }) test.ifOsx("create keychain with installers", async () => { const keychainName = generateKeychainName() - await executeFinally(createKeychain(keychainName, CSC_LINK, CSC_KEY_PASSWORD, CSC_INSTALLER_LINK, CSC_INSTALLER_KEY_PASSWORD) + await executeFinally(createKeychain(keychainName, CSC_LINK, process.env.CSC_KEY_PASSWORD) .then(result => { assertThat(result.keychainName).not.empty() - assertThat(result.name).equal(CSC_NAME) }), () => all([deleteKeychain(keychainName)])) }) diff --git a/test/src/helpers/codeSignData.ts b/test/src/helpers/codeSignData.ts index e7473f7b1e5..72f4b3c6d51 100644 --- a/test/src/helpers/codeSignData.ts +++ b/test/src/helpers/codeSignData.ts @@ -1,7 +1 @@ -export const CSC_LINK = "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" -export const CSC_KEY_PASSWORD = "password" - -export const CSC_INSTALLER_LINK = "https://drive.google.com/uc?export=download&id=0Bz3JwZ-jqfROYkRYblpfOXA4QVU" -export const CSC_INSTALLER_KEY_PASSWORD = "password" - -export const CSC_NAME = "Test Test" \ No newline at end of file +export const CSC_LINK = "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" \ No newline at end of file diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index 39e83fa6be1..1aa913f3ca0 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -3,11 +3,11 @@ import * as assertThat2 from "should/as-function" import { assertThat } from "./fileAssert" import * as path from "path" import { parse as parsePlist } from "plist" -import { CSC_LINK, CSC_KEY_PASSWORD, CSC_INSTALLER_LINK, CSC_INSTALLER_KEY_PASSWORD } from "./codeSignData" +import { CSC_LINK } from "./codeSignData" import { expectedLinuxContents, expectedWinContents } from "./expectedContents" import { Packager, PackagerOptions, Platform, ArtifactCreated, Arch, DIR_TARGET } from "out" import { exec, getTempName } from "out/util/util" -import { log } from "out/util/log" +import { log, warn } from "out/util/log" import { createTargets } from "out" import { tmpdir } from "os" import { getArchSuffix, Target } from "out/platformPackager" @@ -336,10 +336,12 @@ export function platform(platform: Platform): PackagerOptions { } export function signed(packagerOptions: PackagerOptions): PackagerOptions { - packagerOptions.cscLink = CSC_LINK - packagerOptions.cscKeyPassword = CSC_KEY_PASSWORD - packagerOptions.cscInstallerLink = CSC_INSTALLER_LINK - packagerOptions.cscInstallerKeyPassword = CSC_INSTALLER_KEY_PASSWORD + if (process.env.CSC_KEY_PASSWORD == null) { + warn("macOS code sign is not tested — CSC_KEY_PASSWORD is not defined") + } + else { + packagerOptions.cscLink = CSC_LINK + } return packagerOptions } diff --git a/test/src/macPackagerTest.ts b/test/src/macPackagerTest.ts index fe292b3d510..23a64a7d794 100644 --- a/test/src/macPackagerTest.ts +++ b/test/src/macPackagerTest.ts @@ -8,7 +8,7 @@ import { Promise as BluebirdPromise } from "bluebird" import * as assertThat from "should/as-function" import { ElectronPackagerOptions } from "electron-packager-tf" import { Platform, MacOptions, createTargets } from "out" -import { SignOptions, FlatOptions } from "electron-osx-sign-tf" +import { SignOptions, FlatOptions } from "electron-osx-sign" import { Arch } from "out" import { Target } from "out/platformPackager" import { DmgTarget } from "out/targets/dmg" @@ -27,7 +27,7 @@ function createTargetTest(target: Array, expectedContents: Array targets: Platform.MAC.createTarget(), devMetadata: { build: { - osx: { + mac: { target: target } } @@ -50,15 +50,14 @@ test.ifOsx("mas", createTargetTest(["mas"], ["Test App ßW-1.1.0.pkg"])) test.ifOsx("mas and 7z", createTargetTest(["mas", "7z"], ["Test App ßW-1.1.0-mac.7z", "Test App ßW-1.1.0.pkg"])) test.ifOsx("custom mas", () => { - let platformPackager: CheckingOsXPackager = null + let platformPackager: CheckingMacPackager = null return assertPack("test-app-one", signed({ targets: Platform.MAC.createTarget(), - platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingOsXPackager(packager, cleanupTasks), + platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingMacPackager(packager, cleanupTasks), devMetadata: { build: { mac: { target: ["mas"], - identity: "Test Test", }, mas: { entitlements: "mas-entitlements file path", @@ -69,7 +68,6 @@ test.ifOsx("custom mas", () => { }), { packed: () => { assertThat(platformPackager.effectiveSignOptions).has.properties({ - identity: "Test Test", entitlements: "mas-entitlements file path", "entitlements-inherit": "mas-entitlementsInherit file path", }) @@ -79,10 +77,10 @@ test.ifOsx("custom mas", () => { }) test.ifOsx("entitlements in the package.json", () => { - let platformPackager: CheckingOsXPackager = null + let platformPackager: CheckingMacPackager = null return assertPack("test-app-one", signed({ targets: Platform.MAC.createTarget(), - platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingOsXPackager(packager, cleanupTasks), + platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingMacPackager(packager, cleanupTasks), devMetadata: { build: { mac: { @@ -103,19 +101,19 @@ test.ifOsx("entitlements in the package.json", () => { }) test.ifOsx("entitlements in build dir", () => { - let platformPackager: CheckingOsXPackager = null + let platformPackager: CheckingMacPackager = null return assertPack("test-app-one", signed({ targets: Platform.MAC.createTarget(), - platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingOsXPackager(packager, cleanupTasks), + platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingMacPackager(packager, cleanupTasks), }), { tempDirCreated: projectDir => BluebirdPromise.all([ - writeFile(path.join(projectDir, "build", "entitlements.osx.plist"), ""), - writeFile(path.join(projectDir, "build", "entitlements.osx.inherit.plist"), ""), + writeFile(path.join(projectDir, "build", "entitlements.mac.plist"), ""), + writeFile(path.join(projectDir, "build", "entitlements.mac.inherit.plist"), ""), ]), packed: projectDir => { assertThat(platformPackager.effectiveSignOptions).has.properties({ - entitlements: path.join(projectDir, "build", "entitlements.osx.plist"), - "entitlements-inherit": path.join(projectDir, "build", "entitlements.osx.inherit.plist"), + entitlements: path.join(projectDir, "build", "entitlements.mac.plist"), + "entitlements-inherit": path.join(projectDir, "build", "entitlements.mac.inherit.plist"), }) return BluebirdPromise.resolve(null) } @@ -131,11 +129,11 @@ test.ifOsx("no build directory", (t: any) => assertPack("test-app-one", platform })) test.ifOsx("custom background - old way", () => { - let platformPackager: CheckingOsXPackager = null + let platformPackager: CheckingMacPackager = null const customBackground = "customBackground.png" return assertPack("test-app-one", { targets: Platform.MAC.createTarget(), - platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingOsXPackager(packager, cleanupTasks) + platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingMacPackager(packager, cleanupTasks) }, { tempDirCreated: projectDir => BluebirdPromise.all([ move(path.join(projectDir, "build", "background.png"), path.join(projectDir, customBackground)), @@ -155,11 +153,11 @@ test.ifOsx("custom background - old way", () => { }) test.ifOsx("custom background - new way", () => { - let platformPackager: CheckingOsXPackager = null + let platformPackager: CheckingMacPackager = null const customBackground = "customBackground.png" return assertPack("test-app-one", { targets: Platform.MAC.createTarget(), - platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingOsXPackager(packager, cleanupTasks) + platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingMacPackager(packager, cleanupTasks) }, { tempDirCreated: projectDir => BluebirdPromise.all([ move(path.join(projectDir, "build", "background.png"), path.join(projectDir, customBackground)), @@ -188,7 +186,28 @@ test.ifOsx("custom background - new way", () => { }) }) -class CheckingOsXPackager extends OsXPackager { +test.ifOsx("disable dmg icon", () => { + let platformPackager: CheckingMacPackager = null + return assertPack("test-app-one", { + targets: Platform.MAC.createTarget(), + platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingMacPackager(packager, cleanupTasks), + devMetadata: { + build: { + dmg: { + icon: null, + } + }, + } + }, { + packed: () => { + assertThat(platformPackager.effectiveDistOptions.icon).equal(null) + assertThat(platformPackager.effectivePackOptions.icon).not.equal(null) + return BluebirdPromise.resolve(null) + }, + }) +}) + +class CheckingMacPackager extends OsXPackager { effectiveDistOptions: any effectivePackOptions: ElectronPackagerOptions effectiveSignOptions: SignOptions diff --git a/test/tsconfig.json b/test/tsconfig.json index 3611bd28fe4..0a4bee2bcc2 100755 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -22,7 +22,7 @@ "../node_modules/fs-extra-p/index.d.ts", "../node_modules/fs-extra-p/bluebird.d.ts", "out/electron-builder.d.ts", - "../node_modules/electron-osx-sign-tf/index.d.ts", + "../node_modules/electron-osx-sign/index.d.ts", "../node_modules/@types/**/*.d.ts", "!../node_modules/@types/node/node-*.d.ts" ], @@ -31,7 +31,6 @@ "../typings/appdmg.d.ts", "../typings/asar.d.ts", "../typings/chalk.d.ts", - "../typings/deep-assign.d.ts", "../typings/electron-packager.d.ts", "../typings/electron-winstaller-fixed.d.ts", "../typings/gh-api.d.ts", @@ -56,7 +55,7 @@ "../node_modules/fs-extra-p/index.d.ts", "../node_modules/fs-extra-p/bluebird.d.ts", "out/electron-builder.d.ts", - "../node_modules/electron-osx-sign-tf/index.d.ts", + "../node_modules/electron-osx-sign/index.d.ts", "../node_modules/@types/debug/index.d.ts", "../node_modules/@types/mime/index.d.ts", "../node_modules/@types/node/index.d.ts", diff --git a/tsconfig.json b/tsconfig.json index 0865b5c0b8e..85b6b45441d 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,7 @@ "node_modules/fs-extra-p/index.d.ts", "node_modules/7zip-bin/index.d.ts", "node_modules/fs-extra-p/bluebird.d.ts", - "node_modules/electron-osx-sign-tf/index.d.ts", + "node_modules/electron-osx-sign/index.d.ts", "node_modules/@types/**/*.d.ts", "!node_modules/@types/node/node-*.d.ts" ], @@ -38,7 +38,6 @@ "typings/appdmg.d.ts", "typings/asar.d.ts", "typings/chalk.d.ts", - "typings/deep-assign.d.ts", "typings/electron-packager.d.ts", "typings/electron-winstaller-fixed.d.ts", "typings/gh-api.d.ts", @@ -57,7 +56,7 @@ "node_modules/fs-extra-p/index.d.ts", "node_modules/7zip-bin/index.d.ts", "node_modules/fs-extra-p/bluebird.d.ts", - "node_modules/electron-osx-sign-tf/index.d.ts", + "node_modules/electron-osx-sign/index.d.ts", "node_modules/@types/debug/index.d.ts", "node_modules/@types/mime/index.d.ts", "node_modules/@types/node/index.d.ts", @@ -92,6 +91,7 @@ "src/targets/targetFactory.ts", "src/util/awaiter.ts", "src/util/binDownload.ts", + "src/util/deepAssign.ts", "src/util/filter.ts", "src/util/httpRequest.ts", "src/util/log.ts", diff --git a/typings/deep-assign.d.ts b/typings/deep-assign.d.ts deleted file mode 100644 index 2dcd705c21e..00000000000 --- a/typings/deep-assign.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare module "deep-assign" { - function deepAssign(target: T, source: U): T & U - - function deepAssign(target: T, source: U, source2: U): T & U - - export = deepAssign -} \ No newline at end of file