Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/wide-wolves-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"electron-builder": minor
"app-builder-lib": minor
---

feat(win): updating win-codesign tooling to latest from `electron-builder-binaries`
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ jobs:
testFiles:
- oneClickInstallerTest,assistedInstallerTest,webInstallerTest,packageManagerTest,HoistedNodeModuleTest,rebuilderTest
- winPackagerTest,winCodeSignTest,BuildTest,blackboxUpdateTest,debTest
- masTest,filesTest,macPackagerTest,macArchiveTest
- masTest,dmgTest,filesTest,macPackagerTest,macArchiveTest
- concurrentBuildsTest,macIconTest
steps:
- name: Checkout code repository
Expand Down
16 changes: 16 additions & 0 deletions packages/app-builder-lib/scheme.json
Original file line number Diff line number Diff line change
Expand Up @@ -6879,6 +6879,22 @@
"default": true,
"description": "Whether to verify the signature of an available update before installation.\nThe [publisher name](#publisherName) will be used for the signature verification.",
"type": "boolean"
},
"winCodeSign": {
"anyOf": [
{
"enum": [
"0.0.0",
"1.0.0"
],
"type": "string"
},
{
"type": "null"
}
],
"default": "0.0.0",
"description": "`win-codesign` version to use for signing Windows artifacts.\nLocated at https://github.com/electron-userland/electron-builder-binaries/releases?q=win-codesign&expanded=true\n0.0.0 - legacy toolset (winCodeSign)\n1.0.0 - windows-kits-bundle-10_0_26100_0"
}
},
"type": "object"
Expand Down
165 changes: 73 additions & 92 deletions packages/app-builder-lib/src/codeSign/windowsSignToolManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,18 @@ import { asArray, InvalidConfigurationError, log, retry } from "builder-util"
import { MemoLazy, parseDn } from "builder-util-runtime"
import { rename } from "fs-extra"
import { Lazy } from "lazy-val"
import * as os from "os"
import * as path from "path"
import { getBin } from "../binDownload"
import { Target } from "../core"
import { WindowsConfiguration } from "../options/winOptions"
import AppXTarget from "../targets/AppxTarget"
import { executeAppBuilderAsJson } from "../util/appBuilder"
import { computeToolEnv, ToolInfo } from "../util/bundledTool"
import { isUseSystemSigncode } from "../util/flags"
import { resolveFunction } from "../util/resolve"
import { VmManager } from "../vm/vm"
import { WinPackager } from "../winPackager"
import { importCertificate } from "./codesign"
import { SignManager } from "./signManager"
import { WindowsSignOptions } from "./windowsCodeSign"

export function getSignVendorPath() {
return getBin("winCodeSign")
}
import { getSignToolPath } from "../targets/tools"

export type CustomWindowsSign = (configuration: CustomWindowsSignTaskConfiguration, packager?: WinPackager) => Promise<any>

Expand Down Expand Up @@ -264,55 +257,101 @@ export class WindowsSignToolManager implements SignManager {

// on windows be aware of http://stackoverflow.com/a/32640183/1910191
computeSignToolArgs(options: WindowsSignTaskConfiguration, isWin: boolean, vm: VmManager = new VmManager()): Array<string> {
return isWin ? this.computeWindowsSignArgs(options, vm) : this.computeOsslsigncodeArgs(options, vm)
}

private computeWindowsSignArgs(options: WindowsSignTaskConfiguration, vm: VmManager): Array<string> {
const inputFile = vm.toVmFile(options.path)
const outputPath = isWin ? inputFile : this.getOutputPath(inputFile, options.hash)
if (!isWin) {
options.resultOutputPath = outputPath
const args = ["sign"]

// Timestamping
if (process.env.ELECTRON_BUILDER_OFFLINE !== "true") {
const isRfc3161 = options.isNest || options.hash === "sha256"
args.push(isRfc3161 ? "/tr" : "/t")

const timestampUrl = isRfc3161
? options.options.signtoolOptions?.rfc3161TimeStampServer || "http://timestamp.digicert.com"
: options.options.signtoolOptions?.timeStampServer || "http://timestamp.digicert.com"
args.push(timestampUrl)
}

const args = isWin ? ["sign"] : ["-in", inputFile, "-out", outputPath]
// Certificate
this.addCertificateArgs(args, options, vm, true)

// Hash algorithm
args.push("/fd", options.hash.toLowerCase())
if (process.env.ELECTRON_BUILDER_OFFLINE !== "true" && !args.includes("/t")) {
args.push("/td", "sha256")
}

// Optional parameters
this.addCommonSigningArgs(args, options, vm, true)

// Windows-specific
args.push("/debug")
args.push(inputFile) // Must be last

return args
}

private computeOsslsigncodeArgs(options: WindowsSignTaskConfiguration, vm: VmManager): Array<string> {
const inputFile = vm.toVmFile(options.path)
const outputPath = this.getOutputPath(inputFile, options.hash)
options.resultOutputPath = outputPath

const args = ["sign", "-in", inputFile, "-out", outputPath]

// Timestamping
if (process.env.ELECTRON_BUILDER_OFFLINE !== "true") {
const timestampingServiceUrl = options.options.signtoolOptions?.timeStampServer || "http://timestamp.digicert.com"
if (isWin) {
args.push(
options.isNest || options.hash === "sha256" ? "/tr" : "/t",
options.isNest || options.hash === "sha256" ? options.options.signtoolOptions?.rfc3161TimeStampServer || "http://timestamp.digicert.com" : timestampingServiceUrl
)
} else {
args.push("-t", timestampingServiceUrl)
}
const timestampUrl = options.options.signtoolOptions?.timeStampServer || "http://timestamp.digicert.com"
args.push("-t", timestampUrl)
}

// Certificate
this.addCertificateArgs(args, options, vm, false)

// Hash algorithm
args.push("-h", options.hash.toLowerCase())

// Optional parameters
this.addCommonSigningArgs(args, options, vm, false)

// Proxy support
const httpsProxy = process.env.HTTPS_PROXY
if (httpsProxy?.length) {
args.push("-p", httpsProxy)
}

return args
}

private addCertificateArgs(args: Array<string>, options: WindowsSignTaskConfiguration, vm: VmManager, isWin: boolean): void {
const certificateFile = (options.cscInfo as FileCodeSigningInfo).file

if (certificateFile == null) {
const cscInfo = options.cscInfo as CertificateFromStoreInfo
const subjectName = cscInfo.thumbprint
// Certificate from store (Windows only)
if (!isWin) {
throw new Error(`${subjectName == null ? "certificateSha1" : "certificateSubjectName"} supported only on Windows`)
throw new Error("certificateSha1/certificateSubjectName supported only on Windows")
}

const cscInfo = options.cscInfo as CertificateFromStoreInfo
args.push("/sha1", cscInfo.thumbprint)
args.push("/s", cscInfo.store)
if (cscInfo.isLocalMachineStore) {
args.push("/sm")
}
} else {
// Certificate file
const certExtension = path.extname(certificateFile)
if (certExtension === ".p12" || certExtension === ".pfx") {
args.push(isWin ? "/f" : "-pkcs12", vm.toVmFile(certificateFile))
} else {
throw new Error(`Please specify pkcs12 (.p12/.pfx) file, ${certificateFile} is not correct`)
}
}
}

if (!isWin || options.hash !== "sha1") {
args.push(isWin ? "/fd" : "-h", options.hash)
if (isWin && process.env.ELECTRON_BUILDER_OFFLINE !== "true") {
args.push("/td", "sha256")
}
}

private addCommonSigningArgs(args: Array<string>, options: WindowsSignTaskConfiguration, vm: VmManager, isWin: boolean): void {
if (options.name) {
args.push(isWin ? "/d" : "-n", options.name)
}
Expand All @@ -321,12 +360,11 @@ export class WindowsSignToolManager implements SignManager {
args.push(isWin ? "/du" : "-i", options.site)
}

// msi does not support dual-signing
if (options.isNest) {
args.push(isWin ? "/as" : "-nest")
}

const password = options.cscInfo == null ? null : (options.cscInfo as FileCodeSigningInfo).password
const password = (options.cscInfo as FileCodeSigningInfo)?.password
if (password) {
args.push(isWin ? "/p" : "-pass", password)
}
Expand All @@ -335,61 +373,13 @@ export class WindowsSignToolManager implements SignManager {
if (additionalCert) {
args.push(isWin ? "/ac" : "-ac", vm.toVmFile(additionalCert))
}

const httpsProxyFromEnv = process.env.HTTPS_PROXY
if (!isWin && httpsProxyFromEnv != null && httpsProxyFromEnv.length) {
args.push("-p", httpsProxyFromEnv)
}

if (isWin) {
// https://github.com/electron-userland/electron-builder/issues/2875#issuecomment-387233610
args.push("/debug")
// must be last argument
args.push(inputFile)
}

return args
}

getOutputPath(inputPath: string, hash: string) {
const extension = path.extname(inputPath)
return path.join(path.dirname(inputPath), `${path.basename(inputPath, extension)}-signed-${hash}${extension}`)
}

getWinSignTool(vendorPath: string): string {
// use modern signtool on Windows Server 2012 R2 to be able to sign AppX
if (isOldWin6()) {
return path.join(vendorPath, "windows-6", "signtool.exe")
} else {
return path.join(vendorPath, "windows-10", process.arch, "signtool.exe")
}
}

async getToolPath(isWin = process.platform === "win32"): Promise<ToolInfo> {
if (isUseSystemSigncode()) {
return { path: "osslsigncode" }
}

const result = process.env.SIGNTOOL_PATH
if (result) {
return { path: result }
}

const vendorPath = await getSignVendorPath()
if (isWin) {
// use modern signtool on Windows Server 2012 R2 to be able to sign AppX
return { path: this.getWinSignTool(vendorPath) }
} else if (process.platform === "darwin") {
const toolDirPath = path.join(vendorPath, process.platform, "10.12")
return {
path: path.join(toolDirPath, "osslsigncode"),
env: computeToolEnv([path.join(toolDirPath, "lib")]),
}
} else {
return { path: path.join(vendorPath, process.platform, "osslsigncode") }
}
}

async getCertificateFromStoreInfo(options: WindowsConfiguration, vm: VmManager): Promise<CertificateFromStoreInfo> {
const certificateSubjectName = options.signtoolOptions?.certificateSubjectName
const certificateSha1 = options.signtoolOptions?.certificateSha1?.toUpperCase()
Expand Down Expand Up @@ -432,24 +422,20 @@ export class WindowsSignToolManager implements SignManager {
const timeout = parseInt(process.env.SIGNTOOL_TIMEOUT as any, 10) || 10 * 60 * 1000
// decide runtime argument by cases
let args: Array<string>
let env = process.env
let vm: VmManager
const useVmIfNotOnWin = configuration.path.endsWith(".appx") || !("file" in configuration.cscInfo!) /* certificateSubjectName and other such options */
const isWin = process.platform === "win32" || useVmIfNotOnWin
const toolInfo = await this.getToolPath(isWin)
const toolInfo = await getSignToolPath(this.packager.config.win?.winCodeSign, isWin)
const tool = toolInfo.path
if (useVmIfNotOnWin) {
vm = await packager.vm.value
args = this.computeSignToolArgs(configuration, isWin, vm)
} else {
vm = new VmManager()
args = configuration.computeSignToolArgs(isWin)
if (toolInfo.env != null) {
env = toolInfo.env
}
}

await retry(() => vm.exec(tool, args, { timeout, env }), {
await retry(() => vm.exec(tool, args, { timeout, env: { ...process.env, ...(toolInfo.env || {}) } }), {
retries: 2,
interval: 15000,
backoff: 10000,
Expand All @@ -467,8 +453,3 @@ export class WindowsSignToolManager implements SignManager {
})
}
}

export function isOldWin6() {
const winVersion = os.release()
return winVersion.startsWith("6.") && !winVersion.startsWith("6.3")
}
10 changes: 10 additions & 0 deletions packages/app-builder-lib/src/options/winOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ export interface WindowsConfiguration extends PlatformSpecificBuildOptions {
*/
readonly legalTrademarks?: string | null

/**
* `win-codesign` version to use for signing Windows artifacts.
* Located at https://github.com/electron-userland/electron-builder-binaries/releases?q=win-codesign&expanded=true
* 0.0.0 - legacy toolset (winCodeSign)
* 1.0.0 - windows-kits-bundle-10_0_26100_0
* @default "0.0.0"
*
*/
readonly winCodeSign?: "0.0.0" | "1.0.0" | null

/**
* Options for usage with signtool.exe
* Cannot be used in conjunction with `azureSignOptions`, signing will default to Azure Trusted Signing
Expand Down
12 changes: 6 additions & 6 deletions packages/app-builder-lib/src/targets/AppxTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { Nullish } from "builder-util-runtime"
import { emptyDir, readdir, readFile, writeFile } from "fs-extra"
import * as path from "path"
import { AppXOptions } from "../"
import { getSignVendorPath, isOldWin6 } from "../codeSign/windowsSignToolManager"
import { getWindowsKitsBundle } from "./tools"
import { Target } from "../core"
import { getTemplatePath } from "../util/pathManager"
import { VmManager } from "../vm/vm"
import { WinPackager } from "../winPackager"
import { createStageDir } from "./targetUtil"
import { isOldWin6 } from "../targets/tools"

const APPX_ASSETS_DIR_NAME = "appx"

Expand Down Expand Up @@ -73,7 +74,7 @@ export default class AppXTarget extends Target {
arch,
})

const vendorPath = await getSignVendorPath()
const vendorPath = await getWindowsKitsBundle({ winCodeSign: this.packager.config.win?.winCodeSign, arch: process.arch })
const vm = await packager.vm.value

const stageDir = await createStageDir(this, packager, arch)
Expand All @@ -98,7 +99,7 @@ export default class AppXTarget extends Target {
)

const userAssetDir = await this.packager.getResource(undefined, APPX_ASSETS_DIR_NAME)
const assetInfo = await AppXTarget.computeUserAssets(vm, vendorPath, userAssetDir)
const assetInfo = await AppXTarget.computeUserAssets(vm, vendorPath.appxAssets, userAssetDir)
const userAssets = assetInfo.userAssets

const manifestFile = stageDir.getTempFile("AppxManifest.xml")
Expand All @@ -107,11 +108,10 @@ export default class AppXTarget extends Target {
await packager.info.emitAppxManifestCreated(manifestFile)
mappingList.push(assetInfo.mappings)
mappingList.push([`"${vm.toVmFile(manifestFile)}" "AppxManifest.xml"`])
const signToolArch = arch === Arch.arm64 ? "x64" : Arch[arch]

if (isScaledAssetsProvided(userAssets)) {
const outFile = vm.toVmFile(stageDir.getTempFile("resources.pri"))
const makePriPath = vm.toVmFile(path.join(vendorPath, "windows-10", signToolArch, "makepri.exe"))
const makePriPath = vm.toVmFile(path.join(vendorPath.kit, "makepri.exe"))

const assetRoot = stageDir.getTempFile("appx/assets")
await emptyDir(assetRoot)
Expand Down Expand Up @@ -148,7 +148,7 @@ export default class AppXTarget extends Target {
makeAppXArgs.push(...this.options.makeappxArgs)
}
this.buildQueueManager.add(async () => {
await vm.exec(vm.toVmFile(path.join(vendorPath, "windows-10", signToolArch, "makeappx.exe")), makeAppXArgs)
await vm.exec(vm.toVmFile(path.join(vendorPath.kit, "makeappx.exe")), makeAppXArgs)
await packager.signIf(artifactPath)

await stageDir.cleanup()
Expand Down
Loading
Loading