Skip to content

Commit

Permalink
feat(windows): Support passing the sha1 of a certificate in Windows…
Browse files Browse the repository at this point in the history
… codesign

Close electron-userland#1297
  • Loading branch information
develar committed Feb 24, 2017
1 parent 7bd11e1 commit 99730ab
Show file tree
Hide file tree
Showing 27 changed files with 438 additions and 206 deletions.
7 changes: 0 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,8 @@ dist/

/docs/.idea/

/typings/browser/
/typings/browser.d.ts
/typings/main.d.ts

.DS_Store

/test/typings/electron-builder.d.ts
/test/typings/electron-updater.d.ts

/packages/*/out/
/out/
# to not exclude .js.snap (jest snapshots)
Expand Down
1 change: 1 addition & 0 deletions docs/Options.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ To use Squirrel.Windows please install `electron-builder-squirrel-windows` depen
| certificateFile | <a name="WinBuildOptions-certificateFile"></a><p>The path to the *.pfx certificate you want to sign with. Please use it only if you cannot use env variable <code>CSC_LINK</code> (<code>WIN_CSC_LINK</code>) for some reason. Please see [Code Signing](https://github.com/electron-userland/electron-builder/wiki/Code-Signing).</p>
| certificatePassword | <a name="WinBuildOptions-certificatePassword"></a><p>The password to the certificate provided in <code>certificateFile</code>. Please use it only if you cannot use env variable <code>CSC_KEY_PASSWORD</code> (<code>WIN_CSC_KEY_PASSWORD</code>) for some reason. Please see [Code Signing](https://github.com/electron-userland/electron-builder/wiki/Code-Signing).</p>
| certificateSubjectName | <a name="WinBuildOptions-certificateSubjectName"></a>The name of the subject of the signing certificate. Required only for EV Code Signing and works only on Windows.
| certificateSha1 | <a name="WinBuildOptions-certificateSha1"></a>* The SHA1 hash of the signing certificate. The SHA1 hash is commonly specified when multiple certificates satisfy the criteria specified by the remaining switches. Works only on Windows.
| rfc3161TimeStampServer | <a name="WinBuildOptions-rfc3161TimeStampServer"></a>The URL of the RFC 3161 time stamp server. Defaults to `http://timestamp.comodoca.com/rfc3161`.
| timeStampServer | <a name="WinBuildOptions-timeStampServer"></a>The URL of the time stamp server. Defaults to `http://timestamp.verisign.com/scripts/timstamp.dll`.
| publisherName | <a name="WinBuildOptions-publisherName"></a><p>[The publisher name](https://github.com/electron-userland/electron-builder/issues/1187#issuecomment-278972073), exactly as in your code signed certificate. Several names can be provided. Defaults to common name from your code signing certificate.</p>
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"pretest": "node ./test/vendor/yarn.js compile && node ./test/vendor/yarn.js lint && node ./test/vendor/yarn.js lint-deps",
"lint-deps": "node ./test/out/helpers/checkDeps.js",
"///": "Please see https://github.com/electron-userland/electron-builder/blob/master/CONTRIBUTING.md#run-test-using-cli how to run particular test instead full (and very slow) run",
"test": "node ./test/out/helpers/runTests.js skipFpm skipSw skipArtifactPublisher",
"test": "node ./test/out/helpers/runTests.js skipFpm skipSw skipArtifactPublisher ALL_TESTS=false",
"test-all": "node ./test/vendor/yarn.js pretest && node ./test/out/helpers/runTests.js",
"test-linux": "docker run --rm -ti -v ${PWD}: /project -v ${PWD##*/}-node-modules:/project/node_modules -v ~/.electron:/root/.electron electronuserland/electron-builder:wine /bin/bash -c \"node ./test/vendor/yarn.js && node ./test/vendor/yarn.js test\"",
"//": "Update wiki if docs changed. Update only if functionalily are generally available (latest release, not next)",
Expand All @@ -28,8 +28,8 @@
"ajv": "^5.0.2-beta",
"ajv-keywords": "^2.0.1-beta.0",
"archiver": "^1.3.0",
"asar-electron-builder": "~0.14.2",
"aws-sdk": "^2.17.0",
"asar": "~0.13.0",
"aws-sdk": "^2.19.0",
"bluebird-lst": "^1.0.1",
"chalk": "^1.1.3",
"chromium-pickle-js": "^0.2.0",
Expand Down Expand Up @@ -79,7 +79,7 @@
"depcheck": "^0.6.7",
"docdash": "https://github.com/develar/docdash.git",
"electron-download-tf": "3.2.0",
"jest-cli": "^19.0.1",
"jest-cli": "^19.0.2",
"jest-environment-node-debug": "^2.0.0",
"jest-junit-reporter": "^1.0.1",
"jsdoc": "^3.4.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/electron-builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"ajv": "^5.0.2-beta",
"ajv-keywords": "^2.0.1-beta.0",
"7zip-bin": "^2.0.4",
"asar-electron-builder": "~0.14.2",
"asar": "~0.13.0",
"bluebird-lst": "^1.0.1",
"chalk": "^1.1.3",
"chromium-pickle-js": "^0.2.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/electron-builder/src/asarUtil.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AsarFileInfo, listPackage, statFile } from "asar-electron-builder"
import { AsarFileInfo, listPackage, statFile } from "asar"
import BluebirdPromise from "bluebird-lst"
import { debug } from "electron-builder-util"
import { deepAssign } from "electron-builder-util/out/deepAssign"
Expand All @@ -10,7 +10,7 @@ import { AsarOptions } from "./metadata"

const isBinaryFile: any = BluebirdPromise.promisify(require("isbinaryfile"))
const pickle = require ("chromium-pickle-js")
const Filesystem = require("asar-electron-builder/lib/filesystem")
const Filesystem = require("asar/lib/filesystem")
const UINT64 = require("cuint").UINT64

const NODE_MODULES_PATTERN = `${path.sep}node_modules${path.sep}`
Expand Down
5 changes: 5 additions & 0 deletions packages/electron-builder/src/options/winOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ export interface WinBuildOptions extends PlatformSpecificBuildOptions {
*/
readonly certificateSubjectName?: string

/**
* The SHA1 hash of the signing certificate. The SHA1 hash is commonly specified when multiple certificates satisfy the criteria specified by the remaining switches. Works only on Windows.
*/
readonly certificateSha1?: string

/**
The URL of the RFC 3161 time stamp server. Defaults to `http://timestamp.comodoca.com/rfc3161`.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/electron-builder/src/packager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extractFile } from "asar-electron-builder"
import { extractFile } from "asar"
import BluebirdPromise from "bluebird-lst"
import { Arch, Platform, Target } from "electron-builder-core"
import { CancellationToken } from "electron-builder-http/out/CancellationToken"
Expand Down
3 changes: 1 addition & 2 deletions packages/electron-builder/src/targets/appx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ export default class AppXTarget extends Target {
async build(appOutDir: string, arch: Arch): Promise<any> {
const packager = this.packager

const cscInfo = await packager.cscInfo
if (cscInfo == null) {
if ((await packager.cscInfo) == null) {
throw new Error("AppX package must be signed, but certificate is not set, please see https://github.com/electron-userland/electron-builder/wiki/Code-Signing")
}

Expand Down
2 changes: 1 addition & 1 deletion packages/electron-builder/src/util/readPackageJson.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Ajv from "ajv"
import { extractFile } from "asar-electron-builder"
import { extractFile } from "asar"
import { log, warn } from "electron-builder-util/out/log"
import { readFile, readJson } from "fs-extra-p"
import { safeLoad } from "js-yaml"
Expand Down
79 changes: 41 additions & 38 deletions packages/electron-builder/src/winPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,44 @@ import { PlatformPackager } from "./platformPackager"
import AppXTarget from "./targets/appx"
import NsisTarget from "./targets/nsis"
import { createCommonTarget, DIR_TARGET } from "./targets/targetFactory"
import { getSignVendorPath, sign, SignOptions } from "./windowsCodeSign"

export interface FileCodeSigningInfo {
readonly file?: string | null
readonly password?: string | null

readonly subjectName?: string | null
}
import { FileCodeSigningInfo, getSignVendorPath, sign, SignOptions } from "./windowsCodeSign"

export class WinPackager extends PlatformPackager<WinBuildOptions> {
readonly cscInfo = new Lazy<FileCodeSigningInfo | null>(() => {
const subjectName = this.platformSpecificBuildOptions.certificateSubjectName
if (subjectName == null) {
const certificateFile = this.platformSpecificBuildOptions.certificateFile
if (certificateFile != null) {
const certificatePassword = this.getCscPassword()
return BluebirdPromise.resolve({
file: certificateFile,
password: certificatePassword == null ? null : certificatePassword.trim(),
})
}
else {
const cscLink = process.env.WIN_CSC_LINK || this.packagerOptions.cscLink
if (cscLink != null) {
return downloadCertificate(cscLink, this.info.tempDirManager)
.then(path => {
return {
file: path,
password: this.getCscPassword(),
}
})
}
else {
return BluebirdPromise.resolve(null)
}
}
const platformSpecificBuildOptions = this.platformSpecificBuildOptions
const subjectName = platformSpecificBuildOptions.certificateSubjectName
if (subjectName != null) {
return BluebirdPromise.resolve({subjectName})
}
else {

const certificateSha1 = platformSpecificBuildOptions.certificateSha1
if (certificateSha1 != null) {
return BluebirdPromise.resolve({certificateSha1})
}

const certificateFile = platformSpecificBuildOptions.certificateFile
if (certificateFile != null) {
const certificatePassword = this.getCscPassword()
return BluebirdPromise.resolve({
subjectName: subjectName
file: certificateFile,
password: certificatePassword == null ? null : certificatePassword.trim(),
})
}
else {
const cscLink = process.env.WIN_CSC_LINK || this.packagerOptions.cscLink
if (cscLink != null) {
return downloadCertificate(cscLink, this.info.tempDirManager)
.then(path => {
return {
file: path,
password: this.getCscPassword(),
}
})
}
else {
return BluebirdPromise.resolve(null)
}
}
})

private iconPath: Promise<string> | null
Expand Down Expand Up @@ -171,7 +167,12 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
logMessagePrefix = `Signing ${path.basename(file)}`
}
if (certFile == null) {
log(`${logMessagePrefix} (subject name: "${cscInfo.subjectName}")`)
if (cscInfo.subjectName == null) {
log(`${logMessagePrefix} (certificate SHA1: "${cscInfo.certificateSha1}")`)
}
else {
log(`${logMessagePrefix} (certificate subject name: "${cscInfo.subjectName}")`)
}
}
else {
log(`${logMessagePrefix} (certificate file: "${certFile}")`)
Expand All @@ -181,12 +182,14 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
path: file,

cert: certFile,
subjectName: cscInfo.subjectName,

password: cscInfo.password,
name: this.appInfo.productName,
site: await this.appInfo.computePackageUrl(),
options: this.platformSpecificBuildOptions,
options: Object.assign({}, this.platformSpecificBuildOptions, {
certificateSubjectName: cscInfo.subjectName,
certificateSha1: cscInfo.certificateSha1,
}),
})
}

Expand Down
26 changes: 20 additions & 6 deletions packages/electron-builder/src/windowsCodeSign.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { exec } from "electron-builder-util"
import { rename } from "fs-extra-p"
import * as path from "path"
import { release } from "os"
import { getBinFromBintray } from "electron-builder-util/out/binDownload"
import { rename } from "fs-extra-p"
import isCi from "is-ci"
import { release } from "os"
import * as path from "path"
import { WinBuildOptions } from "./options/winOptions"

const TOOLS_VERSION = "1.7.0"
Expand All @@ -13,11 +13,18 @@ export function getSignVendorPath() {
return getBinFromBintray("winCodeSign", TOOLS_VERSION, "a34a60e74d02b81d0303e498f03c70ce0133f908b671f62ec32896db5cd0a716")
}

export interface FileCodeSigningInfo {
readonly file?: string | null
readonly password?: string | null

readonly subjectName?: string | null
readonly certificateSha1?: string | null
}

export interface SignOptions {
readonly path: string

readonly cert?: string | null
readonly subjectName?: string | null

readonly name?: string | null
readonly password?: string | null
Expand Down Expand Up @@ -75,10 +82,17 @@ async function spawnSign(options: SignOptions, inputPath: string, outputPath: st

const certificateFile = options.cert
if (certificateFile == null) {
const subjectName = options.options.certificateSubjectName
if (process.platform !== "win32") {
throw new Error("certificateSubjectName supported only on Windows")
throw new Error(`${subjectName == null ? "certificateSha1" : "certificateSubjectName"} supported only on Windows`)
}

if (subjectName == null) {
args.push("/sha1", options.options.certificateSha1!)
}
else {
args.push("/n", subjectName)
}
args.push("/n", options.subjectName!)
}
else {
const certExtension = path.extname(certificateFile)
Expand Down
2 changes: 1 addition & 1 deletion packages/electron-publisher-s3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
],
"dependencies": {
"fs-extra-p": "^4.0.2",
"aws-sdk": "^2.17.0",
"aws-sdk": "^2.19.0",
"mime": "^1.3.4",
"electron-publish": "~0.0.0-semantic-release",
"electron-builder-util": "~0.0.0-semantic-release"
Expand Down
5 changes: 5 additions & 0 deletions test/jestSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ const isWindows = process.platform === "win32"
jasmine.DEFAULT_TIMEOUT_INTERVAL = (isWindows ? 30 : 10) * 1000 * 60

const skip = test.skip
const skipSuite = describe.skip

const isAllTests = process.env.ALL_TESTS === undefined || process.env.ALL_TESTS === "true"
describe.ifAll = isAllTests ? describe : skipSuite
test.ifAll = isAllTests ? test : skip

const isMac = process.platform === "darwin"
test.ifMac = isMac ? test : skip
Expand Down
10 changes: 5 additions & 5 deletions test/out/__snapshots__/BuildTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -238,25 +238,25 @@ Object {
}
`;
exports[`custom buildResources and output dirs: linux 1`] = `
exports[`sign custom buildResources and output dirs: linux 1`] = `
Array [
"TestApp-1.1.0-x86_64.AppImage",
]
`;
exports[`custom buildResources and output dirs: win 1`] = `
exports[`sign custom buildResources and output dirs: win 1`] = `
Array [
"Test App ßW Setup 1.1.0.exe",
]
`;
exports[`custom buildResources and output dirs: win 2`] = `
exports[`sign custom buildResources and output dirs: win 2`] = `
Array [
"TestApp-Setup-1.1.0.exe",
]
`;
exports[`scheme validation 1`] = `
exports[`sign scheme validation 1`] = `
"Config is invalid:
{
\\"foo\\": \\"Unknown option\\",
Expand Down Expand Up @@ -306,7 +306,7 @@ Raw validation errors: [
]"
`;
exports[`scheme validation 2 1`] = `
exports[`sign scheme validation 2 1`] = `
"Config is invalid:
{
\\"appId\\": \\"Should be null,string\\"
Expand Down
11 changes: 11 additions & 0 deletions test/out/windows/__snapshots__/winPackagerTest.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`icon < 256 1`] = `"Windows icon size must be at least 256x256, please fix \\"<path>/icon.ico\\""`;
exports[`icon not an image 1`] = `"Windows icon is not valid ico file, please fix \\"<path>/icon.ico\\""`;
exports[`sign certificateSha1 1`] = `"certificateSha1 supported only on Windows"`;
exports[`sign ev 1`] = `"certificateSubjectName supported only on Windows"`;
exports[`sign forceCodeSigning 1`] = `"App is not signed and \\"forceCodeSigning\\" is set to true, please ensure that code signing configuration is correct, please see https://github.com/electron-userland/electron-builder/wiki/Code-Signing"`;
Loading

0 comments on commit 99730ab

Please sign in to comment.