Skip to content

Commit

Permalink
feat(windows): specification of signing algorithms (#435)
Browse files Browse the repository at this point in the history
Under the hood a lot of changes:
* electron-packages fork use promises, option added to avoid move on electron-builder side (generated app out dir customized)
* windows bootstrap loadingGif delay removed
* windows bootstrap zip uses default compression level to speedup decompression (package is still compressed using -9)
* app out dir changed — **osx** for OS X instead of appName-darwin-arch, **linux** for Linux instead of appName-linux-arch
* **Windows Code Signing on Linux**

@heinzbeinz added `signingHashAlgorithms` and code signing on windows 2008 r2, thanks for #434.

Closes #374, #416
  • Loading branch information
develar committed May 25, 2016
1 parent bffbbf1 commit 73e7c14
Show file tree
Hide file tree
Showing 19 changed files with 109 additions and 107 deletions.
7 changes: 3 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,16 @@ before_install:

install:
- nvm install $NODE_VERSION
- npm install npm -g
- npm install
- npm prune
- if [[ "$TRAVIS_OS_NAME" == "osx" && "$NODE_VERSION" == "4" ]]; then npm install npm -g ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" || "$NODE_VERSION" == "6" ]]; then npm install && npm prune ; fi

script:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then npm run test ; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run --rm -v ${PWD}:/project -v ~/.electron:/root/.electron electronuserland/electron-builder:wine /test.sh ; fi

after_success:
- node out/cleanup.js
- if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" && "$AUTO_PUBLISH" != "false" ]]; then npm run semantic-release ; fi
- if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" && "$AUTO_PUBLISH" != "false" && "$NODE_VERSION" == "6" ]]; then npm run semantic-release ; fi

branches:
except:
Expand Down
10 changes: 7 additions & 3 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
FROM buildpack-deps:xenial-curl

# rpm is required for FPM to build rpm package
# yasm is required to build p7zip
# osslsigncode to sign windows on Linux

RUN apt-get update -y && \
apt-get install --no-install-recommends -y bsdtar build-essential autoconf libssl-dev icnsutils graphicsmagick gcc-multilib g++-multilib libgnome-keyring-dev zip lzip rpm && \
apt-get install --no-install-recommends -y bsdtar build-essential autoconf libssl-dev icnsutils graphicsmagick gcc-multilib g++-multilib libgnome-keyring-dev lzip rpm osslsigncode yasm && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

Expand All @@ -23,12 +25,15 @@ RUN set -ex \

ENV NODE_VERSION 6.2.0
ENV XZ_VERSION 5.2.2
# we don't use our bundled 7za because it is better to build for specific platform - not generic
ENV USE_SYSTEM_7ZA true

# https://github.com/npm/npm/issues/4531

# install modern multi-thread xz
# ldconfig - see 4.6. liblzma.so (or similar) not found when running xz
RUN curl -L http://tukaani.org/xz/xz-$XZ_VERSION.tar.xz | tar -xJ && cd xz-$XZ_VERSION && ./configure && make && make install && cd .. && rm -rf xz-$XZ_VERSION && ldconfig && \
mkdir -p /tmp/7z && curl -L http://downloads.sourceforge.net/project/p7zip/p7zip/15.14.1/p7zip_15.14.1_src_all.tar.bz2 | tar -xj -C /tmp/7z --strip-components 1 && cd /tmp/7z && cp makefile.linux_amd64_asm makefile.machine && make && make install && cd .. && rm -rf /tmp/7z && \
curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
Expand Down Expand Up @@ -98,14 +103,13 @@ ENV BUNDLE_PATH="$GEM_HOME" \
ENV PATH $BUNDLE_BIN:$PATH
RUN mkdir -p "$GEM_HOME" "$BUNDLE_BIN" \
&& chmod 777 "$GEM_HOME" "$BUNDLE_BIN" \
&& mkdir /fpm && curl -L https://github.com/jordansissel/fpm/archive/6e2514df27664912826b4fcd89affa19df0e713b.tar.gz | tar -xz -C /fpm --strip-components 1 && cd /fpm && bundle install && make install && cd ..
&& mkdir -p /tmp/fpm && curl -L https://github.com/jordansissel/fpm/archive/6e2514df27664912826b4fcd89affa19df0e713b.tar.gz | tar -xz -C /tmp/fpm --strip-components 1 && cd /tmp/fpm && bundle install && make install && cd .. && rm -rf /tmp/fpm

# use fpm commit https://github.com/jordansissel/fpm/commit/6e2514df27664912826b4fcd89affa19df0e713b because of some important unreleased fixes:
# https://github.com/jordansissel/fpm/commit/94be82c0a23c8cd641ab9e60f3eb4a8db445fff0
# https://github.com/jordansissel/fpm/commit/77b95747b9cc01ca420ee24084a449b3ac19e6d5

# we don't use our bundled fpm because it is better to build ruby & tools for specific platform - not generic. And easy to maintain (update ruby and so on).

ENV USE_SYSTEM_FPM true

# fix error /usr/local/bundle/gems/fpm-1.5.0/lib/fpm/package/freebsd.rb:72:in `encode': "\xE2" from ASCII-8BIT to UTF-8 (Encoding::UndefinedConversionError)
Expand Down
4 changes: 2 additions & 2 deletions docs/Multi Platform Build.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ To build app in distributable format for Windows on Linux:
sudo apt-get install --no-install-recommends -y mono-devel ca-certificates-mono
```

* Install zip.
* Install osslsigncode.
```
apt-get install --no-install-recommends -y zip
apt-get install --no-install-recommends -y osslsigncode
```

To build app in 32 bit from a machine with 64 bit:
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
"debug": "^2.2.0",
"deep-assign": "^2.0.0",
"electron-osx-sign-tf": "0.4.0-beta.0",
"electron-packager-tf": "~7.1.0",
"electron-winstaller-fixed": "~2.9.0",
"electron-packager-tf": "~7.2.0",
"electron-winstaller-fixed": "~2.9.3",
"fs-extra-p": "^1.0.1",
"globby": "^4.1.0",
"hosted-git-info": "^2.1.5",
Expand All @@ -77,7 +77,7 @@
"progress": "^1.1.8",
"progress-stream": "^1.2.0",
"read-package-json": "^2.0.4",
"signcode-tf": "^0.6.3",
"signcode-tf": "~0.7.3",
"source-map-support": "^0.4.0"
},
"optionalDependencies": {
Expand All @@ -92,7 +92,7 @@
]
},
"devDependencies": {
"ava-tf": "^0.12.4-beta.6",
"ava-tf": "^0.15",
"babel-plugin-array-includes": "^2.0.3",
"babel-plugin-transform-es2015-parameters": "^6.9.0",
"babel-plugin-transform-es2015-spread": "^6.8.0",
Expand Down
3 changes: 1 addition & 2 deletions src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export interface BuildOptions extends PackagerOptions, PublishOptions {
export async function build(originalOptions?: BuildOptions): Promise<void> {
const options: BuildOptions = Object.assign({
cscLink: process.env.CSC_LINK,
csaLink: process.env.CSA_LINK,
cscKeyPassword: process.env.CSC_KEY_PASSWORD,
githubToken: process.env.GH_TOKEN || process.env.GH_TEST_TOKEN,
}, originalOptions)
Expand Down Expand Up @@ -93,4 +92,4 @@ export async function build(originalOptions?: BuildOptions): Promise<void> {
return BluebirdPromise.all(publishTasks)
}
})
}
}
31 changes: 15 additions & 16 deletions src/codeSign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function downloadUrlOrBase64(urlOrBase64: string, destination: string): Bluebird

let bundledCertKeychainAdded = false

export async function createKeychain(keychainName: string, cscLink: string, cscKeyPassword: string, cscILink?: string | null, cscIKeyPassword?: string | null, csaLink?: string | null): Promise<CodeSigningInfo> {
export async function createKeychain(keychainName: string, cscLink: string, cscKeyPassword: string, cscILink?: string | null, cscIKeyPassword?: string | null): Promise<CodeSigningInfo> {
if (!bundledCertKeychainAdded) {
// "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."
// 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
Expand All @@ -63,27 +63,30 @@ export async function createKeychain(keychainName: string, cscLink: string, cscK
.filter(it => it.length > 0)

if (!list.includes(keychainPath)) {
await exec("security", ["list-keychains", "-d", "user", "-s", keychainPath])
await exec("security", ["list-keychains", "-d", "user", "-s", keychainPath].concat(list))
}
}

const certLinks = csaLink == null ? [] : [csaLink]
certLinks.push(cscLink)
const certLinks = [cscLink]
if (cscILink != null) {
certLinks.push(cscILink)
}

const certPaths = certLinks.map(it => path.join(tmpdir(), randomString() + (it.endsWith(".cer") ? ".cer" : ".p12")))
const certPaths = new Array(certLinks.length)
const keychainPassword = randomString()
return await executeFinally(BluebirdPromise.all([
BluebirdPromise.map(certPaths, (p, i) => downloadUrlOrBase64(certLinks[i], p)),
BluebirdPromise.map(certLinks, (link, i) => {
const tempFile = path.join(tmpdir(), `${randomString()}.p12`)
certPaths[i] = tempFile
return downloadUrlOrBase64(link, tempFile)
}),
BluebirdPromise.mapSeries([
["create-keychain", "-p", keychainPassword, keychainName],
["unlock-keychain", "-p", keychainPassword, keychainName],
["set-keychain-settings", "-t", "3600", "-u", keychainName]
], it => exec("security", it))
])
.then(() => importCerts(keychainName, certPaths, [cscKeyPassword, cscIKeyPassword].filter(it => it != null))),
.then(() => importCerts(keychainName, certPaths, <Array<string>>[cscKeyPassword, cscIKeyPassword].filter(it => it != null))),
errorOccurred => {
const tasks = certPaths.map(it => deleteFile(it, true))
if (errorOccurred) {
Expand All @@ -93,15 +96,10 @@ export async function createKeychain(keychainName: string, cscLink: string, cscK
})
}

async function importCerts(keychainName: string, paths: Array<string>, keyPasswords: Array<string | null | undefined>): Promise<CodeSigningInfo> {
const certFiles = paths.slice(0, -keyPasswords.length)
for (let file of certFiles) {
await exec("security", ["import", file, "-k", keychainName, "-T", "/usr/bin/codesign"])
}

async function importCerts(keychainName: string, paths: Array<string>, keyPasswords: Array<string>): Promise<CodeSigningInfo> {
const namePromises: Array<Promise<string>> = []
for (let i = paths.length - keyPasswords.length, j = 0; i < paths.length; i++, j++) {
const password = keyPasswords[j]!
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])

Expand Down Expand Up @@ -138,7 +136,8 @@ export function sign(path: string, options: CodeSigningInfo): BluebirdPromise<an
}

export function deleteKeychain(keychainName: string, ignoreNotFound: boolean = true): BluebirdPromise<any> {
const result = exec("security", ["delete-keychain", keychainName])
// exec("security", ["delete-keychain", keychainName])
const result = BluebirdPromise.resolve()
if (ignoreNotFound) {
return result.catch(error => {
if (!error.message.includes("The specified keychain could not be found.")) {
Expand Down
2 changes: 1 addition & 1 deletion src/linuxPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class LinuxPackager extends PlatformPackager<LinuxBuildOptions> {

async pack(outDir: string, arch: string, postAsyncTasks: Array<Promise<any>>): Promise<any> {
const appOutDir = this.computeAppOutDir(outDir, arch)
await this.doPack(this.computePackOptions(outDir, arch), outDir, appOutDir, arch, this.customBuildOptions)
await this.doPack(this.computePackOptions(outDir, appOutDir, arch), outDir, appOutDir, arch, this.customBuildOptions)

if (this.options.dist) {
for (let target of this.targets) {
Expand Down
6 changes: 5 additions & 1 deletion src/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ElectronPackagerOptions = ElectronPackager.ElectronPackagerOptions
import { ElectronPackagerOptions } from "electron-packager-tf"

export interface Metadata {
readonly repository?: string | RepositoryInfo | null
}
Expand Down Expand Up @@ -256,6 +257,9 @@ export interface WinBuildOptions extends PlatformSpecificBuildOptions {
Authentication token for remote updates
*/
readonly remoteToken?: string | null

readonly signingHashAlgorithms?: Array<string> | null
readonly signcodePath?: string | null
}

/*
Expand Down
34 changes: 24 additions & 10 deletions src/osxPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PlatformPackager, BuildInfo } from "./platformPackager"
import { Platform, OsXBuildOptions, MasBuildOptions } from "./metadata"
import * as path from "path"
import { Promise as BluebirdPromise } from "bluebird"
import { log, debug, statOrNull, warn } from "./util"
import { log, debug, warn } from "./util"
import { createKeychain, deleteKeychain, CodeSigningInfo, generateKeychainName } from "./codeSign"
import deepAssign = require("deep-assign")
import { sign, flat, BaseSignOptions, SignOptions, FlatOptions } from "electron-osx-sign-tf"
Expand All @@ -22,13 +22,19 @@ export default class OsXPackager extends PlatformPackager<OsXBuildOptions> {
if (this.options.cscLink != null && this.options.cscKeyPassword != null) {
const keychainName = generateKeychainName()
cleanupTasks.push(() => deleteKeychain(keychainName))
this.codeSigningInfo = createKeychain(keychainName, this.options.cscLink, this.options.cscKeyPassword, this.options.cscInstallerLink, this.options.cscInstallerKeyPassword, this.options.csaLink)
this.codeSigningInfo = createKeychain(keychainName, this.options.cscLink, this.options.cscKeyPassword, this.options.cscInstallerLink, this.options.cscInstallerKeyPassword)
}
else {
this.codeSigningInfo = BluebirdPromise.resolve(null)
}

this.resourceList = readdir(this.buildResourcesDir)
.catch(e => {
if (e.code !== "ENOENT") {
throw e
}
return []
})
}

get platform() {
Expand All @@ -40,7 +46,7 @@ export default class OsXPackager extends PlatformPackager<OsXBuildOptions> {
}

async pack(outDir: string, arch: string, postAsyncTasks: Array<Promise<any>>): Promise<any> {
const packOptions = this.computePackOptions(outDir, arch)
const packOptions = this.computePackOptions(outDir, this.computeAppOutDir(outDir, arch), arch)
let nonMasPromise: Promise<any> | null = null
if (this.targets.length > 1 || this.targets[0] !== "mas") {
const appOutDir = this.computeAppOutDir(outDir, arch)
Expand All @@ -51,9 +57,9 @@ export default class OsXPackager extends PlatformPackager<OsXBuildOptions> {

if (this.targets.includes("mas")) {
// osx-sign - disable warning
const appOutDir = path.join(outDir, `${this.appName}-mas-${arch}`)
const appOutDir = path.join(outDir, "mas")
const masBuildOptions = deepAssign({}, this.customBuildOptions, (<any>this.devMetadata.build)["mas"])
await this.doPack(Object.assign({}, packOptions, {platform: "mas", "osx-sign": false}), outDir, appOutDir, arch, masBuildOptions)
await this.doPack(Object.assign({}, packOptions, {platform: "mas", "osx-sign": false, generateFinalBasename: function () { return "mas" }}), outDir, appOutDir, arch, masBuildOptions)
await this.sign(appOutDir, masBuildOptions)
}

Expand Down Expand Up @@ -146,7 +152,6 @@ export default class OsXPackager extends PlatformPackager<OsXBuildOptions> {
protected async computeEffectiveDistOptions(appOutDir: string): Promise<appdmg.Specification> {
const specification: appdmg.Specification = deepAssign({
title: this.appName,
icon: path.join(this.buildResourcesDir, "icon.icns"),
"icon-size": 80,
contents: [
{
Expand All @@ -159,11 +164,20 @@ export default class OsXPackager extends PlatformPackager<OsXBuildOptions> {
format: this.devMetadata.build.compression === "store" ? "UDRO" : "UDBZ",
}, this.customBuildOptions)

if (!("icon" in this.customBuildOptions)) {
const resourceList = await this.resourceList
if (resourceList.includes("icon.icns")) {
specification.icon = path.join(this.buildResourcesDir, "icon.icns")
}
else {
warn("Application icon is not set, default Electron icon will be used")
}
}

if (!("background" in this.customBuildOptions)) {
const background = path.join(this.buildResourcesDir, "background.png")
const info = await statOrNull(background)
if (info != null && info.isFile()) {
specification.background = background
const resourceList = await this.resourceList
if (resourceList.includes("background.png")) {
specification.background = path.join(this.buildResourcesDir, "background.png")
}
}

Expand Down
13 changes: 5 additions & 8 deletions src/platformPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,18 @@ import { AppMetadata, DevMetadata, Platform, PlatformSpecificBuildOptions, getPr
import EventEmitter = NodeJS.EventEmitter
import { Promise as BluebirdPromise } from "bluebird"
import * as path from "path"
import packager = require("electron-packager-tf")
import { pack, ElectronPackagerOptions } from "electron-packager-tf"
import globby = require("globby")
import { copy, unlink } from "fs-extra-p"
import { statOrNull, use, spawn, debug7zArgs, debug } from "./util"
import { Packager } from "./packager"
import deepAssign = require("deep-assign")
import { listPackage, statFile } from "asar"
import ElectronPackagerOptions = ElectronPackager.ElectronPackagerOptions
import { path7za } from "7zip-bin"

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

const pack = BluebirdPromise.promisify(packager)

class CompressionDescriptor {
constructor(public flag: string, public env: string, public minLevel: string, public maxLevel: string = "-9") {
}
Expand Down Expand Up @@ -46,7 +43,6 @@ export interface PackagerOptions {
projectDir?: string | null

cscLink?: string | null
csaLink?: string | null
cscKeyPassword?: string | null

cscInstallerLink?: string | null
Expand Down Expand Up @@ -122,7 +118,7 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
protected abstract get supportedTargets(): Array<string>

protected computeAppOutDir(outDir: string, arch: string): string {
return path.join(outDir, `${this.appName}-${this.platform.nodeName}-${arch}`)
return path.join(outDir, `${this.platform.buildConfigurationKey}${arch === "x64" ? "" : `-${arch}`}`)
}

protected dispatchArtifactCreated(file: string, artifactName?: string) {
Expand All @@ -140,7 +136,7 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
await this.copyExtraResources(appOutDir, arch, customBuildOptions)
}

protected computePackOptions(outDir: string, arch: string): ElectronPackagerOptions {
protected computePackOptions(outDir: string, appOutDir: string, arch: string): ElectronPackagerOptions {
const version = this.metadata.version
let buildVersion = version
const buildNumber = this.computeBuildNumber()
Expand All @@ -163,6 +159,7 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
"app-copyright": `Copyright © ${new Date().getFullYear()} ${this.metadata.author.name || this.appName}`,
"build-version": buildVersion,
tmpdir: false,
generateFinalBasename: () => path.basename(appOutDir),
"version-string": {
CompanyName: this.metadata.author.name,
FileDescription: smarten(this.metadata.description),
Expand Down Expand Up @@ -398,4 +395,4 @@ export function smarten(s: string): string {
// closing doubles
s = s.replace(/"/g, "\u201d")
return s
}
}
Loading

0 comments on commit 73e7c14

Please sign in to comment.