diff --git a/docs/Options.md b/docs/Options.md
index 871ac1defe1..73cd30c4bb8 100644
--- a/docs/Options.md
+++ b/docs/Options.md
@@ -121,6 +121,8 @@ Windows specific build options.
| signingHashAlgorithms | Array of signing algorithms used. Defaults to `['sha1', 'sha256']`
| icon | The path to application icon. Defaults to `build/icon.ico` (consider using this convention instead of complicating your configuration).
| legalTrademarks | The trademarks and registered trademarks.
+| certificateSubjectName | The name of the subject of the signing certificate. Required only for EV Code Signing and works only on Windows.
+| rfc3161TimeStampServer | The URL of the RFC 3161 time stamp server. Defaults to `http://timestamp.comodoca.com/rfc3161`.
### `.build.nsis`
diff --git a/src/metadata.ts b/src/metadata.ts
index 08ac327770b..1e004f6e9a2 100755
--- a/src/metadata.ts
+++ b/src/metadata.ts
@@ -297,9 +297,6 @@ export interface MasBuildOptions extends MacOptions {
Windows specific build options.
*/
export interface WinBuildOptions extends PlatformSpecificBuildOptions {
- readonly certificateFile?: string
- readonly certificatePassword?: string
-
/*
Target package type: list of `squirrel`, `nsis`, `7z`, `zip`, `tar.xz`, `tar.lz`, `tar.gz`, `tar.bz2`. Defaults to `squirrel`.
*/
@@ -352,6 +349,19 @@ export interface WinBuildOptions extends PlatformSpecificBuildOptions {
The trademarks and registered trademarks.
*/
readonly legalTrademarks?: string | null
+
+ readonly certificateFile?: string
+ readonly certificatePassword?: string
+
+ /*
+ The name of the subject of the signing certificate. Required only for EV Code Signing and works only on Windows.
+ */
+ readonly certificateSubjectName?: string
+
+ /*
+ The URL of the RFC 3161 time stamp server. Defaults to `http://timestamp.comodoca.com/rfc3161`.
+ */
+ readonly rfc3161TimeStampServer?: string
}
/*
diff --git a/src/winPackager.ts b/src/winPackager.ts
index 0bcdf57ca44..168e282a8e7 100644
--- a/src/winPackager.ts
+++ b/src/winPackager.ts
@@ -16,41 +16,51 @@ import { rename } from "fs-extra-p"
const __awaiter = require("./util/awaiter")
export interface FileCodeSigningInfo {
- readonly file: string
+ readonly file?: string | null
readonly password?: string | null
+
+ readonly subjectName?: string | null
}
export class WinPackager extends PlatformPackager {
- readonly cscInfo: Promise
+ readonly cscInfo: Promise | null
private readonly iconPath: Promise
constructor(info: BuildInfo, cleanupTasks: Array<() => Promise>) {
super(info)
- const certificateFile = this.platformSpecificBuildOptions.certificateFile
- const cscLink = this.options.cscLink
- if (certificateFile != null) {
- const certificatePassword = this.platformSpecificBuildOptions.certificatePassword || this.getCscPassword()
- this.cscInfo = BluebirdPromise.resolve({
- file: certificateFile,
- password: certificatePassword == null ? null : certificatePassword.trim(),
- })
- }
- else if (cscLink != null) {
- this.cscInfo = downloadCertificate(cscLink)
- .then(path => {
- if (cscLink.startsWith("https://")) {
- cleanupTasks.push(() => deleteFile(path, true))
- }
- return {
- file: path,
- password: this.getCscPassword(),
- }
+ const subjectName = this.platformSpecificBuildOptions.certificateSubjectName
+ if (subjectName == null) {
+ const certificateFile = this.platformSpecificBuildOptions.certificateFile
+ const cscLink = this.options.cscLink
+ if (certificateFile != null) {
+ const certificatePassword = this.platformSpecificBuildOptions.certificatePassword || this.getCscPassword()
+ this.cscInfo = BluebirdPromise.resolve({
+ file: certificateFile,
+ password: certificatePassword == null ? null : certificatePassword.trim(),
})
+ }
+ else if (cscLink != null) {
+ this.cscInfo = downloadCertificate(cscLink)
+ .then(path => {
+ if (cscLink.startsWith("https://")) {
+ cleanupTasks.push(() => deleteFile(path, true))
+ }
+ return {
+ file: path,
+ password: this.getCscPassword(),
+ }
+ })
+ }
+ else {
+ this.cscInfo = BluebirdPromise.resolve(null)
+ }
}
else {
- this.cscInfo = BluebirdPromise.resolve(null)
+ this.cscInfo = BluebirdPromise.resolve({
+ subjectName: subjectName
+ })
}
this.iconPath = this.getValidIconPath()
@@ -121,18 +131,22 @@ export class WinPackager extends PlatformPackager {
log(`Signing ${path.basename(file)} (certificate file "${cscInfo.file}")`)
await this.doSign({
path: file,
+
cert: cscInfo.file,
- password: cscInfo.password!,
+ subjectName: cscInfo.subjectName,
+
+ password: cscInfo.password,
name: this.appInfo.productName,
site: await this.appInfo.computePackageUrl(),
hash: this.platformSpecificBuildOptions.signingHashAlgorithms,
+ tr: this.platformSpecificBuildOptions.rfc3161TimeStampServer,
})
}
}
//noinspection JSMethodCanBeStatic
- protected async doSign(opts: SignOptions): Promise {
- return sign(opts)
+ protected async doSign(options: SignOptions): Promise {
+ return sign(options)
}
async signAndEditResources(file: string) {
diff --git a/src/windowsCodeSign.ts b/src/windowsCodeSign.ts
index b0fd87b98b2..2a1dd4c63f9 100644
--- a/src/windowsCodeSign.ts
+++ b/src/windowsCodeSign.ts
@@ -13,12 +13,17 @@ export function getSignVendorPath() {
}
export interface SignOptions {
- path: string
- cert: string
- name?: string | null
- password: string
- site?: string | null
- hash?: Array | null
+ readonly path: string
+
+ readonly cert?: string | null
+ readonly subjectName?: string | null
+
+ readonly name?: string | null
+ readonly password?: string | null
+ readonly site?: string | null
+ readonly hash?: Array | null
+
+ readonly tr?: string | null
}
export async function sign(options: SignOptions) {
@@ -45,26 +50,30 @@ export async function sign(options: SignOptions) {
}
// on windows be aware of http://stackoverflow.com/a/32640183/1910191
-async function spawnSign(options: any, inputPath: string, outputPath: string, hash: string, nest: boolean) {
+async function spawnSign(options: SignOptions, inputPath: string, outputPath: string, hash: string, nest: boolean) {
const timestampingServiceUrl = "http://timestamp.verisign.com/scripts/timstamp.dll"
const isWin = process.platform === "win32"
const args = isWin ? [
"sign",
- nest || hash === "sha256" ? "/tr" : "/t", nest || hash === "sha256" ? "http://timestamp.comodoca.com/rfc3161" : timestampingServiceUrl
+ nest || hash === "sha256" ? "/tr" : "/t", nest || hash === "sha256" ? (options.tr || "http://timestamp.comodoca.com/rfc3161") : timestampingServiceUrl
] : [
"-in", inputPath,
"-out", outputPath,
"-t", timestampingServiceUrl
]
- const certExtension = path.extname(options.cert)
- if (certExtension === ".p12" || certExtension === ".pfx") {
- args.push(isWin ? "/f" : "-pkcs12", options.cert)
+ const certificateFile = options.cert
+ if (certificateFile == null) {
+ if (process.platform !== "win32") {
+ throw new Error("certificateSubjectName supported only on Windows")
+ }
+ args.push("/n", options.subjectName!)
}
else {
- args.push(isWin ? "/f" : "-certs", options.cert)
- // todo win maybe incorrect
- args.push(isWin ? "/csp" : "-key", options.key)
+ const certExtension = path.extname(certificateFile)
+ if (certExtension === ".p12" || certExtension === ".pfx") {
+ args.push(isWin ? "/f" : "-pkcs12", certificateFile)
+ }
}
if (!isWin || hash !== "sha1") {
@@ -90,19 +99,12 @@ async function spawnSign(options: any, inputPath: string, outputPath: string, ha
args.push(isWin ? "/p" : "-pass", options.password)
}
- if (options.passwordPath) {
- if (isWin) {
- throw new Error("-readpass is not supported on Windows")
- }
- args.push("-readpass", options.passwordPath)
- }
-
if (isWin) {
// must be last argument
args.push(inputPath)
}
- return await spawn(await getToolPath(options), args)
+ return await spawn(await getToolPath(), args)
}
// async function verify(options: any) {
@@ -124,7 +126,7 @@ function getOutputPath(inputPath: string, hash: string) {
return path.join(path.dirname(inputPath), `${path.basename(inputPath, extension)}-signed-${hash}${extension}`)
}
-async function getToolPath(options: any) {
+async function getToolPath() {
let result = process.env.SIGNTOOL_PATH
if (result) {
return result
diff --git a/test/src/winPackagerTest.ts b/test/src/winPackagerTest.ts
index 427114a3a1b..aecd893edad 100755
--- a/test/src/winPackagerTest.ts
+++ b/test/src/winPackagerTest.ts
@@ -93,11 +93,11 @@ test("detect install-spinner, certificateFile/password", () => {
})
})
-test.ifNotCiOsx("icon < 256", (t: any) => t.throws(assertPack("test-app-one", platform(Platform.WINDOWS), {
+test.ifNotCiOsx("icon < 256", t => t.throws(assertPack("test-app-one", platform(Platform.WINDOWS), {
tempDirCreated: projectDir => rename(path.join(projectDir, "build", "incorrect.ico"), path.join(projectDir, "build", "icon.ico"))
}), /Windows icon size must be at least 256x256, please fix ".+/))
-test.ifNotCiOsx("icon not an image", (t: any) => t.throws(assertPack("test-app-one", platform(Platform.WINDOWS), {
+test.ifNotCiOsx("icon not an image", t => t.throws(assertPack("test-app-one", platform(Platform.WINDOWS), {
tempDirCreated: projectDir => outputFile(path.join(projectDir, "build", "icon.ico"), "foo")
}), /Windows icon is not valid ico file, please fix ".+/))
@@ -122,6 +122,17 @@ test.ifOsx("custom icon", () => {
})
})
+test.ifNotWindows("ev", t => t.throws(assertPack("test-app-one", {
+ targets: Platform.WINDOWS.createTarget(["dir"]),
+ devMetadata: {
+ build: {
+ win: {
+ certificateSubjectName: "ev",
+ }
+ }
+ }
+}), /certificateSubjectName supported only on Windows/))
+
class CheckingWinPackager extends WinPackager {
effectiveDistOptions: any
signOptions: SignOptions | null