Skip to content

Commit

Permalink
fix: Squirrel-packed executable not signed
Browse files Browse the repository at this point in the history
Closes #449
  • Loading branch information
develar committed Jun 6, 2016
1 parent cec4b3d commit eb10afb
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 43 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"deep-assign": "^2.0.0",
"electron-osx-sign-tf": "0.6.0",
"electron-packager-tf": "~7.3.0",
"electron-winstaller-fixed": "~2.9.5",
"electron-winstaller-fixed": "~2.9.6",
"fs-extra-p": "^1.0.1",
"glob": "^7.0.3",
"hosted-git-info": "^2.1.5",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { Packager } from "./packager"
export { PackagerOptions, ArtifactCreated, DIR_TARGET } from "./platformPackager"
export { PackagerOptions, ArtifactCreated, DIR_TARGET, BuildInfo } from "./platformPackager"
export { BuildOptions, build, createPublisher, CliOptions, createTargets } from "./builder"
export { PublishOptions, Publisher } from "./gitHubPublisher"
export { AppMetadata, DevMetadata, Platform, Arch, archFromString, getProductName, BuildMetadata, OsXBuildOptions, WinBuildOptions, LinuxBuildOptions } from "./metadata"
6 changes: 1 addition & 5 deletions src/osxPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,9 @@ export default class OsXPackager extends PlatformPackager<OsXBuildOptions> {
this.codeSigningInfo = BluebirdPromise.resolve(null)
}
else {
if (this.options.cscKeyPassword == null) {
throw new Error("cscLink is set, but cscKeyPassword not")
}

const keychainName = generateKeychainName()
cleanupTasks.push(() => deleteKeychain(keychainName))
this.codeSigningInfo = createKeychain(keychainName, this.options.cscLink, this.options.cscKeyPassword, this.options.cscInstallerLink, this.options.cscInstallerKeyPassword)
this.codeSigningInfo = createKeychain(keychainName, this.options.cscLink, this.getCscPassword(), this.options.cscInstallerLink, this.options.cscInstallerKeyPassword)
}
}

Expand Down
13 changes: 12 additions & 1 deletion src/platformPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as path from "path"
import { pack, ElectronPackagerOptions } from "electron-packager-tf"
import { globby } from "./globby"
import { readdir, copy, unlink, lstat, remove } from "fs-extra-p"
import { statOrNull, use, spawn, debug7zArgs, debug, warn } from "./util"
import { statOrNull, use, spawn, debug7zArgs, debug, warn, log } from "./util"
import { Packager } from "./packager"
import { listPackage, statFile, AsarFileMetadata, createPackageFromFiles, AsarOptions } from "asar"
import { path7za } from "7zip-bin"
Expand Down Expand Up @@ -105,6 +105,17 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
})
}

protected getCscPassword(): string {
const password = this.options.cscKeyPassword
if (password == null) {
log("CSC_KEY_PASSWORD is not defined, empty password will be used")
return ""
}
else {
return password.trim()
}
}

public computeEffectiveTargets(rawList: Array<string>): Array<string> {
let targets = normalizeTargets(rawList.length === 0 ? this.customBuildOptions.target : rawList)
if (targets != null) {
Expand Down
5 changes: 3 additions & 2 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,11 @@ export function debug7zArgs(command: "a" | "x"): Array<string> {
}

let tmpDirCounter = 0
const pidAsString = process.pid.toString(36)
// add date to avoid use stale temp dir
const tempDirPrefix = `${process.pid.toString(36)}-${Date.now().toString(36)}`

export function getTempName(prefix?: string | n): string {
return `${prefix == null ? "" : prefix + "-"}${pidAsString}-${tmpDirCounter++}-${Date.now().toString(36)}`
return `${prefix == null ? "" : prefix + "-"}${tempDirPrefix}-${(tmpDirCounter++).toString(36)}`
}

export function isEmptyOrSpaces(s: string | n) {
Expand Down
54 changes: 39 additions & 15 deletions src/winPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,45 @@ import { Platform, WinBuildOptions, Arch } from "./metadata"
import * as path from "path"
import { log, warn } from "./util"
import { deleteFile, emptyDir, open, close, read } from "fs-extra-p"
import { sign } from "signcode-tf"
import { sign, SignOptions } from "signcode-tf"
import { ElectronPackagerOptions } from "electron-packager-tf"

//noinspection JSUnusedLocalSymbols
const __awaiter = require("./awaiter")

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

export class WinPackager extends PlatformPackager<WinBuildOptions> {
certFilePromise: Promise<string | null>
private readonly cscInfo: Promise<FileCodeSigningInfo | null>

readonly iconPath: Promise<string>
private readonly iconPath: Promise<string>

constructor(info: BuildInfo, cleanupTasks: Array<() => Promise<any>>) {
super(info)

if (this.options.cscLink != null && this.options.cscKeyPassword != null) {
this.certFilePromise = downloadCertificate(this.options.cscLink)
const certificateFile = this.customBuildOptions.certificateFile
if (certificateFile != null) {
const certificatePassword = this.customBuildOptions.certificatePassword
this.cscInfo = BluebirdPromise.resolve({
file: certificateFile,
password: certificatePassword == null ? null : certificatePassword.trim(),
})
}
else if (this.options.cscLink != null) {
this.cscInfo = downloadCertificate(this.options.cscLink)
.then(path => {
cleanupTasks.push(() => deleteFile(path, true))
return path
return {
file: path,
password: this.getCscPassword(),
}
})
}
else {
this.certFilePromise = BluebirdPromise.resolve(null)
this.cscInfo = BluebirdPromise.resolve(null)
}

this.iconPath = this.getValidIconPath()
Expand Down Expand Up @@ -78,15 +94,18 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {

protected async doPack(options: ElectronPackagerOptions, outDir: string, appOutDir: string, arch: Arch, customBuildOptions: WinBuildOptions) {
await super.doPack(options, outDir, appOutDir, arch, customBuildOptions)
await this.sign(appOutDir)
}

const cert = await this.certFilePromise
if (cert != null) {
protected async sign(appOutDir: string) {
const cscInfo = await this.cscInfo
if (cscInfo != null) {
const filename = `${this.appName}.exe`
log(`Signing ${filename}`)
await BluebirdPromise.promisify(sign)({
log(`Signing ${filename} (certificate file "${cscInfo.file}")`)
await this.doSign({
path: path.join(appOutDir, filename),
cert: cert,
password: this.options.cscKeyPassword!,
cert: cscInfo.file,
password: cscInfo.password!,
name: this.appName,
site: await this.computePackageUrl(),
overwrite: true,
Expand All @@ -95,6 +114,10 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
}
}

protected async doSign(opts: SignOptions): Promise<any> {
return BluebirdPromise.promisify(sign)(opts)
}

protected async computeEffectiveDistOptions(appOutDir: string, installerOutDir: string, packOptions: ElectronPackagerOptions, setupExeName: string): Promise<WinBuildOptions> {
let iconUrl = this.customBuildOptions.iconUrl || this.devMetadata.build.iconUrl
if (iconUrl == null) {
Expand All @@ -120,6 +143,7 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
}
rceditOptions["version-string"]!.LegalCopyright = packOptions["app-copyright"]

const cscInfo = await this.cscInfo
const options: any = Object.assign({
name: this.metadata.name,
productName: this.appName,
Expand All @@ -133,8 +157,8 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
authors: this.metadata.author.name,
iconUrl: iconUrl,
setupIcon: await this.iconPath,
certificateFile: await this.certFilePromise,
certificatePassword: this.options.cscKeyPassword,
certificateFile: cscInfo == null ? null : cscInfo.file,
certificatePassword: cscInfo == null ? null : cscInfo.password,
fixUpPaths: false,
skipUpdateIcon: true,
usePackageJson: false,
Expand Down
2 changes: 1 addition & 1 deletion test/src/globTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ test.ifDevOrLinuxCi("ignore build resources", () => {
return outputFile(path.join(projectDir, "one/build/foo.txt"), "data")
},
packed: projectDir => {
return assertThat(path.join(projectDir, "dist", "linux", "resources", "app", "one", "build", "foo.txt")).isFile()
return assertThat(path.join(projectDir, outDirName, "linux", "resources", "app", "one", "build", "foo.txt")).isFile()
},
})
})
Expand Down
2 changes: 1 addition & 1 deletion test/src/helpers/packTester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export async function assertPack(fixtureName: string, packagerOptions: PackagerO
const customTmpDir = process.env.TEST_APP_TMP_DIR
if (useTempDir) {
// non-osx test uses the same dir as osx test, but we cannot share node_modules (because tests executed in parallel)
const dir = customTmpDir == null ? path.join(tmpdir(), `${getTempName("electron-builder-test")}-${fixtureName}}`) : path.resolve(customTmpDir)
const dir = customTmpDir == null ? path.join(tmpdir(), `${getTempName("electron-builder-test")}`) : path.resolve(customTmpDir)
if (customTmpDir != null) {
console.log("Custom temp dir used: %s", customTmpDir)
}
Expand Down
38 changes: 22 additions & 16 deletions test/src/winPackagerTest.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Platform } from "out"
import { Platform, Arch, BuildInfo } from "out"
import test from "./helpers/avaEx"
import { assertPack, platform, modifyPackageJson, signed } from "./helpers/packTester"
import { move, outputFile } from "fs-extra-p"
import * as path from "path"
import { WinPackager, computeDistOut } from "out/winPackager"
import { BuildInfo } from "out/platformPackager"
import { Promise as BluebirdPromise } from "bluebird"
import * as assertThat from "should/as-function"
import { ElectronPackagerOptions } from "electron-packager-tf"
import { Arch } from "out"
import { assertThat } from "./helpers/fileAssert"
import { SignOptions } from "signcode-tf"

//noinspection JSUnusedLocalSymbols
const __awaiter = require("out/awaiter")
Expand Down Expand Up @@ -63,22 +62,20 @@ test.ifNotCiOsx("msi as string", t => t.throws(assertPack("test-app-one", platfo
}), `msi expected to be boolean value, but string '"false"' was specified`)
)

test("detect install-spinner", () => {
test("detect install-spinner, certificateFile/password", () => {
let platformPackager: CheckingWinPackager = null
let loadingGifPath: string = null

// todo all PackagerOptions should be optional otherwise it is not possible to pass only several to override dev package.json
const devMetadata: any = {
build: {
win: {
certificatePassword: "pass",
}
}
}
return assertPack("test-app-one", {
targets: Platform.WINDOWS.createTarget(),
platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingWinPackager(packager, cleanupTasks),
devMetadata: devMetadata
devMetadata: {
build: {
win: {
certificatePassword: "pass",
}
}
}
}, {
tempDirCreated: it => {
loadingGifPath = path.join(it, "build", "install-spinner.gif")
Expand All @@ -92,8 +89,9 @@ test("detect install-spinner", () => {
})])
},
packed: () => {
assertThat(platformPackager.effectiveDistOptions.loadingGif).equal(loadingGifPath)
assertThat(platformPackager.effectiveDistOptions.certificateFile).equal("secretFile")
assertThat(platformPackager.effectiveDistOptions.loadingGif).isEqualTo(loadingGifPath)
assertThat(platformPackager.signOptions.cert).isEqualTo("secretFile")
assertThat(platformPackager.signOptions.password).isEqualTo("pass")
return BluebirdPromise.resolve(null)
},
})
Expand All @@ -109,6 +107,7 @@ test.ifNotCiOsx("icon not an image", (t: any) => t.throws(assertPack("test-app-o

class CheckingWinPackager extends WinPackager {
effectiveDistOptions: any
signOptions: SignOptions | null

constructor(info: BuildInfo, cleanupTasks: Array<() => Promise<any>>) {
super(info, cleanupTasks)
Expand All @@ -120,9 +119,16 @@ class CheckingWinPackager extends WinPackager {
const appOutDir = this.computeAppOutDir(outDir, arch)
const packOptions = this.computePackOptions(outDir, appOutDir, arch)
this.effectiveDistOptions = await this.computeEffectiveDistOptions(appOutDir, installerOutDir, packOptions, "Foo.exe")

await this.sign(appOutDir)
}

async packageInDistributableFormat(appOutDir: string, installerOutDir: string, arch: Arch, packOptions: ElectronPackagerOptions): Promise<any> {
// skip
}

protected doSign(opts: SignOptions): Promise<any> {
this.signOptions = opts
return BluebirdPromise.resolve(null)
}
}

0 comments on commit eb10afb

Please sign in to comment.