diff --git a/.changeset/tame-pets-jam.md b/.changeset/tame-pets-jam.md new file mode 100644 index 00000000000..9af2f58e188 --- /dev/null +++ b/.changeset/tame-pets-jam.md @@ -0,0 +1,5 @@ +--- +"app-builder-lib": minor +--- + +feat: Implement Azure Trusted Signing diff --git a/docs/api/electron-builder.md b/docs/api/electron-builder.md index f3ee272823a..c8631996f82 100644 --- a/docs/api/electron-builder.md +++ b/docs/api/electron-builder.md @@ -133,8 +133,11 @@ Developer API only. See [Configuration](../configuration/configuration.md) for u
  • .createTargets(targets, mapper)
  • .artifactPatternConfig(targetSpecificOptions, defaultPattern)module:app-builder-lib/out/platformPackager.__object
  • .computeSafeArtifactName(suggestedName, ext, arch, skipDefaultArch, defaultArch, safePattern)null | String
  • +
  • .getCscLink(extraEnvName)undefined | null | String
  • +
  • .getCscPassword()String
  • .getDefaultFrameworkIcon()null | String
  • .dispatchArtifactCreated(file, target, arch, safeArtifactName)Promise<void>
  • +
  • .doGetCscPassword()undefined | null | String
  • .getElectronDestinationDir(appOutDir)String
  • .getElectronSrcDir(dist)String
  • .expandArtifactBeautyNamePattern(targetSpecificOptions, ext, arch)String
  • @@ -160,8 +163,11 @@ Developer API only. See [Configuration](../configuration/configuration.md) for u
  • .pack(outDir, arch, targets, taskManager)Promise<void>
  • .artifactPatternConfig(targetSpecificOptions, defaultPattern)module:app-builder-lib/out/platformPackager.__object
  • .computeSafeArtifactName(suggestedName, ext, arch, skipDefaultArch, defaultArch, safePattern)null | String
  • +
  • .getCscLink(extraEnvName)undefined | null | String
  • +
  • .getCscPassword()String
  • .getDefaultFrameworkIcon()null | String
  • .dispatchArtifactCreated(file, target, arch, safeArtifactName)Promise<void>
  • +
  • .doGetCscPassword()undefined | null | String
  • .expandArtifactBeautyNamePattern(targetSpecificOptions, ext, arch)String
  • .expandArtifactNamePattern(targetSpecificOptions, ext, arch, defaultPattern, skipDefaultArch, defaultArch)String
  • .expandMacro(pattern, arch, extra, isProductNameSanitized)String
  • @@ -186,7 +192,7 @@ Developer API only. See [Configuration](../configuration/configuration.md) for u
  • .dispatchArtifactCreated(event)
  • .disposeOnBuildFinish(disposer)
  • .installAppDependencies(platform, arch)Promise<any>
  • -
  • .getNodeDependencyInfo(platform)Lazy<Array<module:app-builder-lib/out/util/packageDependencies.NodeModuleDirInfo | module:app-builder-lib/out/util/packageDependencies.NodeModuleInfo>>
  • +
  • .getNodeDependencyInfo(platform, flatten)Lazy<Array<module:app-builder-lib/out/util/packageDependencies.NodeModuleInfo | module:app-builder-lib/out/util/packageDependencies.NodeModuleDirInfo>>
  • .validateConfig()Promise<void>
  • @@ -203,8 +209,11 @@ Developer API only. See [Configuration](../configuration/configuration.md) for u
  • .artifactPatternConfig(targetSpecificOptions, defaultPattern)module:app-builder-lib/out/platformPackager.__object
  • .computeSafeArtifactName(suggestedName, ext, arch, skipDefaultArch, defaultArch, safePattern)null | String
  • .createTargets(targets, mapper)
  • +
  • .getCscLink(extraEnvName)undefined | null | String
  • +
  • .getCscPassword()String
  • .getDefaultFrameworkIcon()null | String
  • .dispatchArtifactCreated(file, target, arch, safeArtifactName)Promise<void>
  • +
  • .doGetCscPassword()undefined | null | String
  • .getElectronDestinationDir(appOutDir)String
  • .getElectronSrcDir(dist)String
  • .expandArtifactBeautyNamePattern(targetSpecificOptions, ext, arch)String
  • @@ -237,11 +246,14 @@ Developer API only. See [Configuration](../configuration/configuration.md) for u
  • .WinPackagerPlatformPackager

    +

    WindowsAzureSigningConfiguration

    +

    Also allows custom fields [k: string: string] passed verbatim (case sensitive) to Invoke-TrustedSigning

    + +

    WindowsSigntoolConfiguration

    +

    undefined

    + +
    + +
    + diff --git a/packages/app-builder-lib/package.json b/packages/app-builder-lib/package.json index d595b6a8f7e..98955d8e611 100644 --- a/packages/app-builder-lib/package.json +++ b/packages/app-builder-lib/package.json @@ -56,7 +56,7 @@ "async-exit-hook": "^2.0.1", "bluebird-lst": "^1.0.9", "builder-util": "workspace:^25", - "builder-util-runtime": "workspace:^9", + "builder-util-runtime": "workspace:*", "chromium-pickle-js": "^0.2.0", "config-file-ts": "0.2.8-rc1", "debug": "^4.3.4", diff --git a/packages/app-builder-lib/scheme.json b/packages/app-builder-lib/scheme.json index bff6304725c..c583e08fedf 100644 --- a/packages/app-builder-lib/scheme.json +++ b/packages/app-builder-lib/scheme.json @@ -5959,6 +5959,26 @@ ], "type": "object" }, + "WindowsAzureSigningConfiguration": { + "additionalProperties": { + "type": "string" + }, + "properties": { + "certificateProfileName": { + "description": "The Certificate Profile name. Translates to field: CertificateProfileName", + "type": "string" + }, + "endpoint": { + "description": "The Trusted Signing Account endpoint. The URI value must have a URI that aligns to the\nregion your Trusted Signing Account and Certificate Profile you are specifying were created\nin during the setup of these resources.\n\nTranslates to field: Endpoint\n\nRequires one of environment variable configurations for authenticating to Microsoft Entra ID per [Microsoft's documentation](https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition)", + "type": "string" + } + }, + "required": [ + "certificateProfileName", + "endpoint" + ], + "type": "object" + }, "WindowsConfiguration": { "additionalProperties": false, "properties": { @@ -6016,6 +6036,17 @@ ], "description": "A [glob patterns](/file-patterns) relative to the [app directory](#MetadataDirectories-app), which specifies which files to unpack when creating the [asar](http://electron.atom.io/docs/tutorial/application-packaging/) archive." }, + "azureSignOptions": { + "anyOf": [ + { + "$ref": "#/definitions/WindowsAzureSigningConfiguration" + }, + { + "type": "null" + } + ], + "description": "Options for usage of Azure Trusted Signing (beta)" + }, "certificateFile": { "description": "The path to the *.pfx certificate you want to sign with. Please use it only if you cannot use env variable `CSC_LINK` (`WIN_CSC_LINK`) for some reason.\nPlease see [Code Signing](/code-signing).", "type": [ @@ -6345,7 +6376,7 @@ ] } ], - "description": "The custom function (or path to file or module id) to sign Windows executable." + "description": "The custom function (or path to file or module id) to sign Windows executables" }, "signAndEditExecutable": { "default": true, @@ -6388,9 +6419,19 @@ "type": "null" } ], - "default": "['sha1', 'sha256']", "description": "Array of signing algorithms used. For AppX `sha256` is always used." }, + "signtoolOptions": { + "anyOf": [ + { + "$ref": "#/definitions/WindowsSigntoolConfiguration" + }, + { + "type": "null" + } + ], + "description": "Options for usage with signtool.exe" + }, "target": { "anyOf": [ { @@ -6434,6 +6475,113 @@ } }, "type": "object" + }, + "WindowsSigntoolConfiguration": { + "additionalProperties": false, + "properties": { + "additionalCertificateFile": { + "description": "The path to an additional certificate file you want to add to the signature block.", + "type": [ + "null", + "string" + ] + }, + "certificateFile": { + "description": "The path to the *.pfx certificate you want to sign with. Please use it only if you cannot use env variable `CSC_LINK` (`WIN_CSC_LINK`) for some reason.\nPlease see [Code Signing](/code-signing).", + "type": [ + "null", + "string" + ] + }, + "certificatePassword": { + "description": "The password to the certificate provided in `certificateFile`. Please use it only if you cannot use env variable `CSC_KEY_PASSWORD` (`WIN_CSC_KEY_PASSWORD`) for some reason.\nPlease see [Code Signing](/code-signing).", + "type": [ + "null", + "string" + ] + }, + "certificateSha1": { + "description": "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 (or on macOS if [Parallels Desktop](https://www.parallels.com/products/desktop/) Windows 10 virtual machines exits).", + "type": [ + "null", + "string" + ] + }, + "certificateSubjectName": { + "description": "The name of the subject of the signing certificate, which is often labeled with the field name `issued to`. Required only for EV Code Signing and works only on Windows (or on macOS if [Parallels Desktop](https://www.parallels.com/products/desktop/) Windows 10 virtual machines exits).", + "type": [ + "null", + "string" + ] + }, + "publisherName": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": [ + "null", + "string" + ] + } + ], + "description": "[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.\nDefaults to common name from your code signing certificate." + }, + "rfc3161TimeStampServer": { + "default": "http://timestamp.digicert.com", + "description": "The URL of the RFC 3161 time stamp server.", + "type": [ + "null", + "string" + ] + }, + "sign": { + "anyOf": [ + { + "typeof": "function" + }, + { + "type": [ + "null", + "string" + ] + } + ], + "description": "The custom function (or path to file or module id) to sign Windows executables" + }, + "signingHashAlgorithms": { + "anyOf": [ + { + "items": { + "enum": [ + "sha1", + "sha256" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": "['sha1', 'sha256']", + "description": "Array of signing algorithms used. For AppX `sha256` is always used." + }, + "timeStampServer": { + "default": "http://timestamp.digicert.com", + "description": "The URL of the time stamp server.", + "type": [ + "null", + "string" + ] + } + }, + "type": "object" } }, "description": "Configuration Options", diff --git a/packages/app-builder-lib/src/codeSign/windowsCodeSign.ts b/packages/app-builder-lib/src/codeSign/windowsCodeSign.ts index 997e439b57e..392e8812fa5 100644 --- a/packages/app-builder-lib/src/codeSign/windowsCodeSign.ts +++ b/packages/app-builder-lib/src/codeSign/windowsCodeSign.ts @@ -1,326 +1,43 @@ -import { InvalidConfigurationError, asArray, log } from "builder-util" -import { getBin } from "../binDownload" +import { log } from "builder-util" import { WindowsConfiguration } from "../options/winOptions" -import { executeAppBuilderAsJson } from "../util/appBuilder" -import { computeToolEnv, ToolInfo } from "../util/bundledTool" -import { rename } from "fs-extra" -import * as os from "os" -import * as path from "path" -import { resolveFunction } from "../util/resolve" -import { isUseSystemSigncode } from "../util/flags" import { VmManager } from "../vm/vm" import { WinPackager } from "../winPackager" -export function getSignVendorPath() { - return getBin("winCodeSign") -} - -export type CustomWindowsSign = (configuration: CustomWindowsSignTaskConfiguration, packager?: WinPackager) => Promise - export interface WindowsSignOptions { readonly path: string - - readonly name?: string | null - readonly cscInfo?: FileCodeSigningInfo | CertificateFromStoreInfo | null - readonly site?: string | null - readonly options: WindowsConfiguration } -export interface WindowsSignTaskConfiguration extends WindowsSignOptions { - // set if output path differs from input (e.g. osslsigncode cannot sign file in-place) - resultOutputPath?: string - - hash: string - isNest: boolean -} - -export interface CustomWindowsSignTaskConfiguration extends WindowsSignTaskConfiguration { - computeSignToolArgs(isWin: boolean): Array -} - -export async function sign(options: WindowsSignOptions, packager: WinPackager): Promise { - let hashes = options.options.signingHashAlgorithms - // msi does not support dual-signing - if (options.path.endsWith(".msi")) { - hashes = [hashes != null && !hashes.includes("sha1") ? "sha256" : "sha1"] - } else if (options.path.endsWith(".appx")) { - hashes = ["sha256"] - } else if (hashes == null) { - hashes = ["sha1", "sha256"] - } else { - hashes = Array.isArray(hashes) ? hashes : [hashes] - } - - const executor = (await resolveFunction(packager.appInfo.type, options.options.sign, "sign")) || doSign - let isNest = false - for (const hash of hashes) { - const taskConfiguration: WindowsSignTaskConfiguration = { ...options, hash, isNest } - await Promise.resolve( - executor( - { - ...taskConfiguration, - computeSignToolArgs: isWin => computeSignToolArgs(taskConfiguration, isWin), - }, - packager - ) - ) - isNest = true - if (taskConfiguration.resultOutputPath != null) { - await rename(taskConfiguration.resultOutputPath, options.path) - } - } - - return true -} - -export interface FileCodeSigningInfo { - readonly file: string - readonly password: string | null -} - -export async function getCertInfo(file: string, password: string): Promise { - let result: any = null - const errorMessagePrefix = "Cannot extract publisher name from code signing certificate. As workaround, set win.publisherName. Error: " - try { - result = await executeAppBuilderAsJson(["certificate-info", "--input", file, "--password", password]) - } catch (e: any) { - throw new Error(`${errorMessagePrefix}${e.stack || e}`) - } - - if (result.error != null) { - // noinspection ExceptionCaughtLocallyJS - throw new InvalidConfigurationError(`${errorMessagePrefix}${result.error}`) - } - return result -} - -export interface CertificateInfo { - readonly commonName: string - readonly bloodyMicrosoftSubjectDn: string -} - -export interface CertificateFromStoreInfo { - thumbprint: string - subject: string - store: string - isLocalMachineStore: boolean -} - -export async function getCertificateFromStoreInfo(options: WindowsConfiguration, vm: VmManager): Promise { - const certificateSubjectName = options.certificateSubjectName - const certificateSha1 = options.certificateSha1 ? options.certificateSha1.toUpperCase() : options.certificateSha1 - - const ps = await getPSCmd(vm) - const rawResult = await vm.exec(ps, [ - "-NoProfile", - "-NonInteractive", - "-Command", - "Get-ChildItem -Recurse Cert: -CodeSigningCert | Select-Object -Property Subject,PSParentPath,Thumbprint | ConvertTo-Json -Compress", - ]) - const certList = rawResult.length === 0 ? [] : asArray(JSON.parse(rawResult)) - for (const certInfo of certList) { - if ( - (certificateSubjectName != null && !certInfo.Subject.includes(certificateSubjectName)) || - (certificateSha1 != null && certInfo.Thumbprint.toUpperCase() !== certificateSha1) - ) { - continue - } - - const parentPath = certInfo.PSParentPath - const store = parentPath.substring(parentPath.lastIndexOf("\\") + 1) - log.debug({ store, PSParentPath: parentPath }, "auto-detect certificate store") - // https://github.com/electron-userland/electron-builder/issues/1717 - const isLocalMachineStore = parentPath.includes("Certificate::LocalMachine") - log.debug(null, "auto-detect using of LocalMachine store") - return { - thumbprint: certInfo.Thumbprint, - subject: certInfo.Subject, - store, - isLocalMachineStore, - } - } - - throw new Error(`Cannot find certificate ${certificateSubjectName || certificateSha1}, all certs: ${rawResult}`) -} - -export async function doSign(configuration: CustomWindowsSignTaskConfiguration, packager: WinPackager) { - // https://github.com/electron-userland/electron-builder/pull/1944 - const timeout = parseInt(process.env.SIGNTOOL_TIMEOUT as any, 10) || 10 * 60 * 1000 - // decide runtime argument by cases - let args: Array - let env = process.env - let vm: VmManager - const vmRequired = configuration.path.endsWith(".appx") || !("file" in configuration.cscInfo!) /* certificateSubjectName and other such options */ - const isWin = process.platform === "win32" || vmRequired - const toolInfo = await getToolPath(isWin) - const tool = toolInfo.path - if (vmRequired) { - vm = await packager.vm.value - args = computeSignToolArgs(configuration, isWin, vm) - } else { - vm = new VmManager() - args = configuration.computeSignToolArgs(isWin) - if (toolInfo.env != null) { - env = toolInfo.env - } - } - - try { - await vm.exec(tool, args, { timeout, env }) - } catch (e: any) { - if (e.message.includes("The file is being used by another process") || e.message.includes("The specified timestamp server either could not be reached")) { - log.warn(`First attempt to code sign failed, another attempt will be made in 15 seconds: ${e.message}`) - await new Promise((resolve, reject) => { - setTimeout(() => { - vm.exec(tool, args, { timeout, env }).then(resolve).catch(reject) - }, 15000) - }) - } - throw e - } -} - -interface CertInfo { - Subject: string - Thumbprint: string - PSParentPath: string -} - -// on windows be aware of http://stackoverflow.com/a/32640183/1910191 -function computeSignToolArgs(options: WindowsSignTaskConfiguration, isWin: boolean, vm: VmManager = new VmManager()): Array { - const inputFile = vm.toVmFile(options.path) - const outputPath = isWin ? inputFile : getOutputPath(inputFile, options.hash) - if (!isWin) { - options.resultOutputPath = outputPath - } - - const args = isWin ? ["sign"] : ["-in", inputFile, "-out", outputPath] - - if (process.env.ELECTRON_BUILDER_OFFLINE !== "true") { - const timestampingServiceUrl = options.options.timeStampServer || "http://timestamp.digicert.com" - if (isWin) { - args.push( - options.isNest || options.hash === "sha256" ? "/tr" : "/t", - options.isNest || options.hash === "sha256" ? options.options.rfc3161TimeStampServer || "http://timestamp.digicert.com" : timestampingServiceUrl - ) - } else { - args.push("-t", timestampingServiceUrl) - } - } - - const certificateFile = (options.cscInfo as FileCodeSigningInfo).file - if (certificateFile == null) { - const cscInfo = options.cscInfo as CertificateFromStoreInfo - const subjectName = cscInfo.thumbprint - if (!isWin) { - throw new Error(`${subjectName == null ? "certificateSha1" : "certificateSubjectName"} supported only on Windows`) - } - - args.push("/sha1", cscInfo.thumbprint) - args.push("/s", cscInfo.store) - if (cscInfo.isLocalMachineStore) { - args.push("/sm") - } - } else { - 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") - } - } - - if (options.name) { - args.push(isWin ? "/d" : "-n", options.name) - } - - if (options.site) { - 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 - if (password) { - args.push(isWin ? "/p" : "-pass", password) +export async function signWindows(options: WindowsSignOptions, packager: WinPackager): Promise { + if (options.options.azureSignOptions) { + log.info({ path: log.filePath(options.path) }, "signing with Azure Trusted Signing (beta)") + return (await packager.azureSignManager.value).signUsingAzureTrustedSigning(options) } - if (options.options.additionalCertificateFile) { - args.push(isWin ? "/ac" : "-ac", vm.toVmFile(options.options.additionalCertificateFile)) + log.info({ path: log.filePath(options.path) }, "signing with signtool.exe") + const deprecatedFields = { + sign: options.options.sign, + signDlls: options.options.signDlls, + signingHashAlgorithms: options.options.signingHashAlgorithms, + certificateFile: options.options.certificateFile, + certificatePassword: options.options.certificatePassword, + certificateSha1: options.options.certificateSha1, + certificateSubjectName: options.options.certificateSubjectName, + additionalCertificateFile: options.options.additionalCertificateFile, + rfc3161TimeStampServer: options.options.rfc3161TimeStampServer, + timeStampServer: options.options.timeStampServer, + publisherName: options.options.publisherName, } - - 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 -} - -function getOutputPath(inputPath: string, hash: string) { - const extension = path.extname(inputPath) - return path.join(path.dirname(inputPath), `${path.basename(inputPath, extension)}-signed-${hash}${extension}`) -} - -/** @internal */ -export function isOldWin6() { - const winVersion = os.release() - return winVersion.startsWith("6.") && !winVersion.startsWith("6.3") -} - -function 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 function getToolPath(isWin = process.platform === "win32"): Promise { - 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: 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") } + const fields = Object.entries(deprecatedFields) + .filter(([, value]) => !!value) + .map(([field]) => field) + if (fields.length) { + log.warn({ fields, reason: "please move to win.signtoolOptions." }, `deprecated field`) } + return (await packager.signtoolManager.value).signUsingSigntool(options) } -async function getPSCmd(vm: VmManager): Promise { +export async function getPSCmd(vm: VmManager): Promise { return await vm .exec("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `Get-Command pwsh.exe`]) .then(() => { diff --git a/packages/app-builder-lib/src/codeSign/windowsSignAzureManager.ts b/packages/app-builder-lib/src/codeSign/windowsSignAzureManager.ts new file mode 100644 index 00000000000..044749d86e2 --- /dev/null +++ b/packages/app-builder-lib/src/codeSign/windowsSignAzureManager.ts @@ -0,0 +1,90 @@ +import { InvalidConfigurationError, log } from "builder-util" +import { WindowsAzureSigningConfiguration } from "../options/winOptions" +import { WinPackager } from "../winPackager" +import { getPSCmd, WindowsSignOptions } from "./windowsCodeSign" + +export class WindowsSignAzureManager { + constructor(private readonly packager: WinPackager) {} + + async initializeProviderModules() { + const vm = await this.packager.vm.value + const ps = await getPSCmd(vm) + + log.debug(null, "installing required package provider (NuGet) and module (TrustedSigning) with scope CurrentUser") + await vm.exec(ps, ["Install-PackageProvider", "-Name", "NuGet", "-MinimumVersion", "2.8.5.201", "-Force", "-Scope", "CurrentUser"]) + await vm.exec(ps, ["Install-Module", "-Name", "TrustedSigning", "-RequiredVersion", "0.4.1", "-Force", "-Repository", "PSGallery", "-Scope", "CurrentUser"]) + + // Preemptively check env vars once during initialization + // Options: https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition + log.info(null, "verifying env vars for authenticating to Microsoft Entra ID") + this.verifyRequiredEnvVars() + if (!(this.verifyPrincipleSecretEnv() || this.verifyPrincipleCertificateEnv() || this.verifyUsernamePasswordEnv())) { + throw new InvalidConfigurationError( + `Unable to find valid azure env configuration for signing. Missing field(s) can be debugged via "DEBUG=electron-builder". Please refer to: https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition` + ) + } + } + + verifyRequiredEnvVars() { + ;["AZURE_TENANT_ID", "AZURE_CLIENT_ID"].forEach(field => { + if (!process.env[field]) { + throw new InvalidConfigurationError( + `Unable to find valid azure env field ${field} for signing. Please refer to: https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition` + ) + } + }) + } + + verifyPrincipleSecretEnv() { + if (!process.env.AZURE_CLIENT_SECRET) { + log.debug({ envVar: "AZURE_CLIENT_SECRET" }, "no secret found for authenticating to Microsoft Entra ID") + return false + } + return true + } + + verifyPrincipleCertificateEnv() { + if (!process.env.AZURE_CLIENT_CERTIFICATE_PATH) { + log.debug({ envVar: "AZURE_CLIENT_CERTIFICATE_PATH" }, "no path found for signing certificate for authenticating to Microsoft Entra ID") + return false + } + if (!process.env.AZURE_CLIENT_CERTIFICATE_PASSWORD) { + log.debug({ envVar: "AZURE_CLIENT_CERTIFICATE_PASSWORD" }, "(optional) certificate password not found, assuming no password") + } + if (!process.env.AZURE_CLIENT_SEND_CERTIFICATE_CHAIN) { + log.debug({ envVar: "AZURE_CLIENT_SEND_CERTIFICATE_CHAIN" }, "(optional) certificate chain not found") + } + return true + } + + verifyUsernamePasswordEnv() { + if (!process.env.AZURE_USERNAME) { + log.debug({ envVar: "AZURE_USERNAME" }, "no username found for authenticating to Microsoft Entra ID") + if (!process.env.AZURE_PASSWORD) { + log.debug({ envVar: "AZURE_PASSWORD" }, "no password found for authenticating to Microsoft Entra ID") + } + return false + } + return true + } + + // prerequisite: requires `initializeProviderModules` to already have been executed + async signUsingAzureTrustedSigning(options: WindowsSignOptions): Promise { + const vm = await this.packager.vm.value + const ps = await getPSCmd(vm) + + const { endpoint, certificateProfileName, ...extraSigningArgs }: WindowsAzureSigningConfiguration = options.options.azureSignOptions! + const params = { + ...extraSigningArgs, + Endpoint: endpoint, + CertificateProfileName: certificateProfileName, + Files: options.path, + } + const paramsString = Object.entries(params).reduce((res, [field, value]) => { + return [...res, `-${field}`, value] + }, [] as string[]) + await vm.exec(ps, ["Invoke-TrustedSigning", ...paramsString]) + + return true + } +} diff --git a/packages/app-builder-lib/src/codeSign/windowsSignToolManager.ts b/packages/app-builder-lib/src/codeSign/windowsSignToolManager.ts new file mode 100644 index 00000000000..8dbfc0708a5 --- /dev/null +++ b/packages/app-builder-lib/src/codeSign/windowsSignToolManager.ts @@ -0,0 +1,432 @@ +import { InvalidConfigurationError, asArray, log, retry } from "builder-util" +import { getBin } from "../binDownload" +import { WindowsConfiguration } from "../options/winOptions" +import { executeAppBuilderAsJson } from "../util/appBuilder" +import { computeToolEnv, ToolInfo } from "../util/bundledTool" +import { rename } from "fs-extra" +import * as os from "os" +import * as path from "path" +import { resolveFunction } from "../util/resolve" +import { isUseSystemSigncode } from "../util/flags" +import { VmManager } from "../vm/vm" +import { WinPackager } from "../winPackager" +import { chooseNotNull } from "../platformPackager" +import { WindowsSignOptions } from "./windowsCodeSign" +import { getPSCmd } from "./windowsCodeSign" +import { MemoLazy, parseDn } from "builder-util-runtime" +import { Lazy } from "lazy-val" +import { importCertificate } from "./codesign" + +export function getSignVendorPath() { + return getBin("winCodeSign") +} + +export type CustomWindowsSign = (configuration: CustomWindowsSignTaskConfiguration, packager?: WinPackager) => Promise + +export interface WindowsSignToolOptions extends WindowsSignOptions { + readonly name: string + readonly site: string | null +} + +export interface FileCodeSigningInfo { + readonly file: string + readonly password: string | null +} + +export interface WindowsSignTaskConfiguration extends WindowsSignToolOptions { + readonly cscInfo: FileCodeSigningInfo | CertificateFromStoreInfo | null + + // set if output path differs from input (e.g. osslsigncode cannot sign file in-place) + resultOutputPath?: string + + hash: string + isNest: boolean +} + +export interface CustomWindowsSignTaskConfiguration extends WindowsSignTaskConfiguration { + computeSignToolArgs(isWin: boolean): Array +} + +export interface CertificateInfo { + readonly commonName: string + readonly bloodyMicrosoftSubjectDn: string +} + +export interface CertificateFromStoreInfo { + thumbprint: string + subject: string + store: string + isLocalMachineStore: boolean +} + +interface CertInfo { + Subject: string + Thumbprint: string + PSParentPath: string +} + +export class WindowsSignToolManager { + private readonly platformSpecificBuildOptions: WindowsConfiguration + + constructor(private readonly packager: WinPackager) { + this.platformSpecificBuildOptions = packager.platformSpecificBuildOptions + } + + readonly computedPublisherName = new Lazy | null>(async () => { + const publisherName = chooseNotNull(this.platformSpecificBuildOptions.signtoolOptions?.publisherName, this.platformSpecificBuildOptions.publisherName) + if (publisherName === null) { + return null + } else if (publisherName != null) { + return asArray(publisherName) + } + + const certInfo = await this.lazyCertInfo.value + return certInfo == null ? null : [certInfo.commonName] + }) + + readonly lazyCertInfo = new MemoLazy, CertificateInfo | null>( + () => this.cscInfo, + async csc => { + const cscInfo = await csc.value + if (cscInfo == null) { + return null + } + + if ("subject" in cscInfo) { + const bloodyMicrosoftSubjectDn = cscInfo.subject + return { + commonName: parseDn(bloodyMicrosoftSubjectDn).get("CN")!, + bloodyMicrosoftSubjectDn, + } + } + + const cscFile = cscInfo.file + if (cscFile == null) { + return null + } + return await this.getCertInfo(cscFile, cscInfo.password || "") + } + ) + + readonly cscInfo = new MemoLazy( + () => this.platformSpecificBuildOptions, + platformSpecificBuildOptions => { + const subjectName = chooseNotNull(platformSpecificBuildOptions.signtoolOptions?.certificateSubjectName, platformSpecificBuildOptions.certificateSubjectName) + const shaType = chooseNotNull(platformSpecificBuildOptions.signtoolOptions?.certificateSha1, platformSpecificBuildOptions.certificateSha1) + if (subjectName != null || shaType != null) { + return this.packager.vm.value + .then(vm => this.getCertificateFromStoreInfo(platformSpecificBuildOptions, vm)) + .catch((e: any) => { + // https://github.com/electron-userland/electron-builder/pull/2397 + if (chooseNotNull(platformSpecificBuildOptions.signtoolOptions?.sign, platformSpecificBuildOptions.sign) == null) { + throw e + } else { + log.debug({ error: e }, "getCertificateFromStoreInfo error") + return null + } + }) + } + + const certificateFile = chooseNotNull(platformSpecificBuildOptions.signtoolOptions?.certificateFile, platformSpecificBuildOptions.certificateFile) + if (certificateFile != null) { + const certificatePassword = this.packager.getCscPassword() + return Promise.resolve({ + file: certificateFile, + password: certificatePassword == null ? null : certificatePassword.trim(), + }) + } + + const cscLink = this.packager.getCscLink("WIN_CSC_LINK") + if (cscLink == null || cscLink === "") { + return Promise.resolve(null) + } + + return ( + importCertificate(cscLink, this.packager.info.tempDirManager, this.packager.projectDir) + // before then + .catch((e: any) => { + if (e instanceof InvalidConfigurationError) { + throw new InvalidConfigurationError(`Env WIN_CSC_LINK is not correct, cannot resolve: ${e.message}`) + } else { + throw e + } + }) + .then(path => { + return { + file: path, + password: this.packager.getCscPassword(), + } + }) + ) + } + ) + + async signUsingSigntool(options: WindowsSignOptions): Promise { + let hashes = chooseNotNull(options.options.signtoolOptions?.signingHashAlgorithms, options.options.signingHashAlgorithms) + // msi does not support dual-signing + if (options.path.endsWith(".msi")) { + hashes = [hashes != null && !hashes.includes("sha1") ? "sha256" : "sha1"] + } else if (options.path.endsWith(".appx")) { + hashes = ["sha256"] + } else if (hashes == null) { + hashes = ["sha1", "sha256"] + } else { + hashes = Array.isArray(hashes) ? hashes : [hashes] + } + + const cscInfo = await this.cscInfo.value + const name = this.packager.appInfo.productName + const site = await this.packager.appInfo.computePackageUrl() + + const customSign = await resolveFunction(this.packager.appInfo.type, chooseNotNull(options.options.signtoolOptions?.sign, options.options.sign), "sign") + const executor = customSign || ((config: CustomWindowsSignTaskConfiguration, packager: WinPackager) => this.doSign(config, packager)) + let isNest = false + for (const hash of hashes) { + const taskConfiguration: WindowsSignTaskConfiguration = { ...options, name, site, cscInfo, hash, isNest } + await Promise.resolve( + executor( + { + ...taskConfiguration, + computeSignToolArgs: isWin => this.computeSignToolArgs(taskConfiguration, isWin), + }, + this.packager + ) + ) + isNest = true + if (taskConfiguration.resultOutputPath != null) { + await rename(taskConfiguration.resultOutputPath, options.path) + } + } + + return true + } + + async getCertInfo(file: string, password: string): Promise { + let result: any = null + const errorMessagePrefix = "Cannot extract publisher name from code signing certificate. As workaround, set win.publisherName. Error: " + try { + result = await executeAppBuilderAsJson(["certificate-info", "--input", file, "--password", password]) + } catch (e: any) { + throw new Error(`${errorMessagePrefix}${e.stack || e}`) + } + + if (result.error != null) { + // noinspection ExceptionCaughtLocallyJS + throw new InvalidConfigurationError(`${errorMessagePrefix}${result.error}`) + } + return result + } + + // on windows be aware of http://stackoverflow.com/a/32640183/1910191 + computeSignToolArgs(options: WindowsSignTaskConfiguration, isWin: boolean, vm: VmManager = new VmManager()): Array { + const inputFile = vm.toVmFile(options.path) + const outputPath = isWin ? inputFile : this.getOutputPath(inputFile, options.hash) + if (!isWin) { + options.resultOutputPath = outputPath + } + + const args = isWin ? ["sign"] : ["-in", inputFile, "-out", outputPath] + + if (process.env.ELECTRON_BUILDER_OFFLINE !== "true") { + const timestampingServiceUrl = chooseNotNull(options.options.signtoolOptions?.timeStampServer, options.options.timeStampServer) || "http://timestamp.digicert.com" + if (isWin) { + args.push( + options.isNest || options.hash === "sha256" ? "/tr" : "/t", + options.isNest || options.hash === "sha256" + ? chooseNotNull(options.options.signtoolOptions?.rfc3161TimeStampServer, options.options.rfc3161TimeStampServer) || "http://timestamp.digicert.com" + : timestampingServiceUrl + ) + } else { + args.push("-t", timestampingServiceUrl) + } + } + + const certificateFile = (options.cscInfo as FileCodeSigningInfo).file + if (certificateFile == null) { + const cscInfo = options.cscInfo as CertificateFromStoreInfo + const subjectName = cscInfo.thumbprint + if (!isWin) { + throw new Error(`${subjectName == null ? "certificateSha1" : "certificateSubjectName"} supported only on Windows`) + } + + args.push("/sha1", cscInfo.thumbprint) + args.push("/s", cscInfo.store) + if (cscInfo.isLocalMachineStore) { + args.push("/sm") + } + } else { + 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") + } + } + + if (options.name) { + args.push(isWin ? "/d" : "-n", options.name) + } + + if (options.site) { + 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 + if (password) { + args.push(isWin ? "/p" : "-pass", password) + } + + const additionalCert = chooseNotNull(options.options.signtoolOptions?.additionalCertificateFile, options.options.additionalCertificateFile) + 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 { + 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 { + const certificateSubjectName = chooseNotNull(options.signtoolOptions?.certificateSubjectName, options.certificateSubjectName) + const certificateSha1 = chooseNotNull(options.signtoolOptions?.certificateSha1, options.certificateSha1)?.toUpperCase() + + const ps = await getPSCmd(vm) + const rawResult = await vm.exec(ps, [ + "-NoProfile", + "-NonInteractive", + "-Command", + "Get-ChildItem -Recurse Cert: -CodeSigningCert | Select-Object -Property Subject,PSParentPath,Thumbprint | ConvertTo-Json -Compress", + ]) + const certList = rawResult.length === 0 ? [] : asArray(JSON.parse(rawResult)) + for (const certInfo of certList) { + if ( + (certificateSubjectName != null && !certInfo.Subject.includes(certificateSubjectName)) || + (certificateSha1 != null && certInfo.Thumbprint.toUpperCase() !== certificateSha1) + ) { + continue + } + + const parentPath = certInfo.PSParentPath + const store = parentPath.substring(parentPath.lastIndexOf("\\") + 1) + log.debug({ store, PSParentPath: parentPath }, "auto-detect certificate store") + // https://github.com/electron-userland/electron-builder/issues/1717 + const isLocalMachineStore = parentPath.includes("Certificate::LocalMachine") + log.debug(null, "auto-detect using of LocalMachine store") + return { + thumbprint: certInfo.Thumbprint, + subject: certInfo.Subject, + store, + isLocalMachineStore, + } + } + + throw new Error(`Cannot find certificate ${certificateSubjectName || certificateSha1}, all certs: ${rawResult}`) + } + + async doSign(configuration: CustomWindowsSignTaskConfiguration, packager: WinPackager) { + // https://github.com/electron-userland/electron-builder/pull/1944 + const timeout = parseInt(process.env.SIGNTOOL_TIMEOUT as any, 10) || 10 * 60 * 1000 + // decide runtime argument by cases + let args: Array + let env = process.env + let vm: VmManager + const vmRequired = configuration.path.endsWith(".appx") || !("file" in configuration.cscInfo!) /* certificateSubjectName and other such options */ + const isWin = process.platform === "win32" || vmRequired + const toolInfo = await this.getToolPath(isWin) + const tool = toolInfo.path + if (vmRequired) { + 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 }), + 2, + 15000, + 10000, + 0, + (e: any) => { + if ( + e.message.includes("The file is being used by another process") || + e.message.includes("The specified timestamp server either could not be reached" || e.message.includes("No certificates were found that met all the given criteria.")) + ) { + log.warn(`Attempt to code sign failed, another attempt will be made in 15 seconds: ${e.message}`) + return true + } + return false + } + ) + } +} + +export function isOldWin6() { + const winVersion = os.release() + return winVersion.startsWith("6.") && !winVersion.startsWith("6.3") +} diff --git a/packages/app-builder-lib/src/index.ts b/packages/app-builder-lib/src/index.ts index 624f224e19d..8e92d88ecde 100644 --- a/packages/app-builder-lib/src/index.ts +++ b/packages/app-builder-lib/src/index.ts @@ -27,7 +27,7 @@ export { PlatformSpecificBuildOptions, AsarOptions, FileSet, Protocol, ReleaseIn export { FileAssociation } from "./options/FileAssociation" export { MacConfiguration, DmgOptions, MasConfiguration, MacOsTargetName, DmgContent, DmgWindow, NotarizeNotaryOptions } from "./options/macOptions" export { PkgOptions, PkgBackgroundOptions, BackgroundAlignment, BackgroundScaling } from "./options/pkgOptions" -export { WindowsConfiguration } from "./options/winOptions" +export { WindowsConfiguration, WindowsAzureSigningConfiguration, WindowsSigntoolConfiguration } from "./options/winOptions" export { AppXOptions } from "./options/AppXOptions" export { MsiOptions } from "./options/MsiOptions" export { MsiWrappedOptions } from "./options/MsiWrappedOptions" @@ -40,14 +40,14 @@ export { AppInfo } from "./appInfo" export { SquirrelWindowsOptions } from "./options/SquirrelWindowsOptions" export { CustomMacSign, CustomMacSignOptions } from "./macPackager" +export { WindowsSignOptions } from "./codeSign/windowsCodeSign" export { - WindowsSignOptions, CustomWindowsSignTaskConfiguration, WindowsSignTaskConfiguration, CustomWindowsSign, FileCodeSigningInfo, CertificateFromStoreInfo, -} from "./codeSign/windowsCodeSign" +} from "./codeSign/windowsSignToolManager" export { CancellationToken, ProgressInfo } from "builder-util-runtime" export { PublishOptions, UploadTask } from "electron-publish" export { PublishManager } from "./publish/PublishManager" diff --git a/packages/app-builder-lib/src/options/winOptions.ts b/packages/app-builder-lib/src/options/winOptions.ts index fc305fe1e44..24dff95a8da 100644 --- a/packages/app-builder-lib/src/options/winOptions.ts +++ b/packages/app-builder-lib/src/options/winOptions.ts @@ -1,5 +1,5 @@ import { PlatformSpecificBuildOptions, TargetConfigType } from "../index" -import { CustomWindowsSign } from "../codeSign/windowsCodeSign" +import { CustomWindowsSign } from "../codeSign/windowsSignToolManager" export interface WindowsConfiguration extends PlatformSpecificBuildOptions { /** @@ -25,52 +25,71 @@ export interface WindowsConfiguration extends PlatformSpecificBuildOptions { /** * Array of signing algorithms used. For AppX `sha256` is always used. - * @default ['sha1', 'sha256'] + * @deprecated Please use {@link signtoolOptions.signingHashAlgorithms} */ readonly signingHashAlgorithms?: Array<"sha1" | "sha256"> | null /** - * The custom function (or path to file or module id) to sign Windows executable. + * The custom function (or path to file or module id) to sign Windows executables + * @deprecated Please use {@link signtoolOptions.sign} */ readonly sign?: CustomWindowsSign | string | null /** * The path to the *.pfx certificate you want to sign with. Please use it only if you cannot use env variable `CSC_LINK` (`WIN_CSC_LINK`) for some reason. * Please see [Code Signing](/code-signing). + * @deprecated Please use {@link signtoolOptions.certificateFile} */ readonly certificateFile?: string | null /** * The password to the certificate provided in `certificateFile`. Please use it only if you cannot use env variable `CSC_KEY_PASSWORD` (`WIN_CSC_KEY_PASSWORD`) for some reason. * Please see [Code Signing](/code-signing). + * @deprecated Please use {@link signtoolOptions.certificatePassword} */ readonly certificatePassword?: string | null /** * The name of the subject of the signing certificate, which is often labeled with the field name `issued to`. Required only for EV Code Signing and works only on Windows (or on macOS if [Parallels Desktop](https://www.parallels.com/products/desktop/) Windows 10 virtual machines exits). + * @deprecated Please use {@link signtoolOptions.certificateSubjectName} */ readonly certificateSubjectName?: string | null /** * 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 (or on macOS if [Parallels Desktop](https://www.parallels.com/products/desktop/) Windows 10 virtual machines exits). + * @deprecated Please use win.signtoolOptions.certificateSha1 */ readonly certificateSha1?: string | null /** * The path to an additional certificate file you want to add to the signature block. + * @deprecated Please use {@link signtoolOptions.additionalCertificateFile} */ readonly additionalCertificateFile?: string | null /** * The URL of the RFC 3161 time stamp server. * @default http://timestamp.digicert.com + * @deprecated Please use {@link signtoolOptions.rfc3161TimeStampServer} */ readonly rfc3161TimeStampServer?: string | null /** * The URL of the time stamp server. * @default http://timestamp.digicert.com + * @deprecated Please use {@link signtoolOptions.timeStampServer} */ readonly timeStampServer?: string | null /** * [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. + * @deprecated Please use {@link signtoolOptions.publisherName} */ readonly publisherName?: string | Array | null + /** + * Options for usage with signtool.exe + */ + readonly signtoolOptions?: WindowsSigntoolConfiguration | null + + /** + * Options for usage of Azure Trusted Signing (beta) + */ + readonly azureSignOptions?: WindowsAzureSigningConfiguration | null + /** * Whether to verify the signature of an available update before installation. * The [publisher name](#publisherName) will be used for the signature verification. @@ -96,7 +115,7 @@ export interface WindowsConfiguration extends PlatformSpecificBuildOptions { * Whether to sign DLL files. Advanced option. * @see https://github.com/electron-userland/electron-builder/issues/3101#issuecomment-404212384 * @default false - * @deprecated Use `signExts` instead for more explicit control + * @deprecated Use {@link signExts} instead for more explicit control */ readonly signDlls?: boolean @@ -109,3 +128,83 @@ export interface WindowsConfiguration extends PlatformSpecificBuildOptions { } export type RequestedExecutionLevel = "asInvoker" | "highestAvailable" | "requireAdministrator" + +export interface WindowsSigntoolConfiguration { + /** + * The custom function (or path to file or module id) to sign Windows executables + */ + readonly sign?: CustomWindowsSign | string | null + + /** + * Array of signing algorithms used. For AppX `sha256` is always used. + * @default ['sha1', 'sha256'] + */ + readonly signingHashAlgorithms?: Array<"sha1" | "sha256"> | null + + /** + * The path to the *.pfx certificate you want to sign with. Please use it only if you cannot use env variable `CSC_LINK` (`WIN_CSC_LINK`) for some reason. + * Please see [Code Signing](/code-signing). + */ + readonly certificateFile?: string | null + + /** + * The password to the certificate provided in `certificateFile`. Please use it only if you cannot use env variable `CSC_KEY_PASSWORD` (`WIN_CSC_KEY_PASSWORD`) for some reason. + * Please see [Code Signing](/code-signing). + */ + readonly certificatePassword?: string | null + + /** + * The name of the subject of the signing certificate, which is often labeled with the field name `issued to`. Required only for EV Code Signing and works only on Windows (or on macOS if [Parallels Desktop](https://www.parallels.com/products/desktop/) Windows 10 virtual machines exits). + */ + readonly certificateSubjectName?: string | null + + /** + * 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 (or on macOS if [Parallels Desktop](https://www.parallels.com/products/desktop/) Windows 10 virtual machines exits). + */ + readonly certificateSha1?: string | null + + /** + * The path to an additional certificate file you want to add to the signature block. + */ + readonly additionalCertificateFile?: string | null + + /** + * The URL of the RFC 3161 time stamp server. + * @default http://timestamp.digicert.com + */ + readonly rfc3161TimeStampServer?: string | null + + /** + * The URL of the time stamp server. + * @default http://timestamp.digicert.com + */ + readonly timeStampServer?: string | null + + /** + * [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. + */ + readonly publisherName?: string | Array | null +} + +// https://learn.microsoft.com/en-us/azure/trusted-signing/how-to-signing-integrations +export interface WindowsAzureSigningConfiguration { + /** + * The Trusted Signing Account endpoint. The URI value must have a URI that aligns to the + * region your Trusted Signing Account and Certificate Profile you are specifying were created + * in during the setup of these resources. + * + * Translates to field: Endpoint + * + * Requires one of environment variable configurations for authenticating to Microsoft Entra ID per [Microsoft's documentation](https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet#definition) + */ + readonly endpoint: string + /** + * The Certificate Profile name. Translates to field: CertificateProfileName + */ + readonly certificateProfileName: string + /** + * Allow other CLI parameters (verbatim case-sensitive) to `Invoke-TrustedSigning` + */ + [k: string]: string +} diff --git a/packages/app-builder-lib/src/platformPackager.ts b/packages/app-builder-lib/src/platformPackager.ts index c262743a766..6f02633eabd 100644 --- a/packages/app-builder-lib/src/platformPackager.ts +++ b/packages/app-builder-lib/src/platformPackager.ts @@ -92,7 +92,7 @@ export abstract class PlatformPackager abstract createTargets(targets: Array, mapper: (name: string, factory: (outDir: string) => Target) => void): void - protected getCscPassword(): string { + getCscPassword(): string { const password = this.doGetCscPassword() if (isEmptyOrSpaces(password)) { log.info({ reason: "CSC_KEY_PASSWORD is not defined" }, "empty password will be used for code signing") @@ -102,13 +102,13 @@ export abstract class PlatformPackager } } - protected getCscLink(extraEnvName?: string | null): string | null | undefined { + getCscLink(extraEnvName?: string | null): string | null | undefined { // allow to specify as empty string const envValue = chooseNotNull(extraEnvName == null ? null : process.env[extraEnvName], process.env.CSC_LINK) return chooseNotNull(chooseNotNull(this.info.config.cscLink, this.platformSpecificBuildOptions.cscLink), envValue) } - protected doGetCscPassword(): string | null | undefined { + doGetCscPassword(): string | null | undefined { // allow to specify as empty string return chooseNotNull(chooseNotNull(this.info.config.cscKeyPassword, this.platformSpecificBuildOptions.cscKeyPassword), process.env.CSC_KEY_PASSWORD) } @@ -770,7 +770,7 @@ export function normalizeExt(ext: string) { return ext.startsWith(".") ? ext.substring(1) : ext } -export function chooseNotNull(v1: string | null | undefined, v2: string | null | undefined): string | null | undefined { +export function chooseNotNull(v1: T | null | undefined, v2: T | null | undefined): T | null | undefined { return v1 == null ? v2 : v1 } diff --git a/packages/app-builder-lib/src/publish/PublishManager.ts b/packages/app-builder-lib/src/publish/PublishManager.ts index 3e63a230ec0..1e39e8aca87 100644 --- a/packages/app-builder-lib/src/publish/PublishManager.ts +++ b/packages/app-builder-lib/src/publish/PublishManager.ts @@ -257,7 +257,7 @@ export async function getAppUpdatePublishConfiguration(packager: PlatformPackage if (packager.platform === Platform.WINDOWS && publishConfig.publisherName == null) { const winPackager = packager as WinPackager - const publisherName = winPackager.isForceCodeSigningVerification ? await winPackager.computedPublisherName.value : undefined + const publisherName = winPackager.isForceCodeSigningVerification ? await (await winPackager.signtoolManager.value).computedPublisherName.value : undefined if (publisherName != null) { publishConfig.publisherName = publisherName } diff --git a/packages/app-builder-lib/src/targets/AppxTarget.ts b/packages/app-builder-lib/src/targets/AppxTarget.ts index d38d7dd5a3d..f4c19f23523 100644 --- a/packages/app-builder-lib/src/targets/AppxTarget.ts +++ b/packages/app-builder-lib/src/targets/AppxTarget.ts @@ -4,7 +4,7 @@ import { copyOrLinkFile, walk } from "builder-util" import { emptyDir, readdir, readFile, writeFile } from "fs-extra" import * as path from "path" import { AppXOptions } from "../" -import { getSignVendorPath, isOldWin6 } from "../codeSign/windowsCodeSign" +import { getSignVendorPath, isOldWin6 } from "../codeSign/windowsSignToolManager" import { Target } from "../core" import { getTemplatePath } from "../util/pathManager" import { VmManager } from "../vm/vm" @@ -186,12 +186,13 @@ export default class AppXTarget extends Target { // https://github.com/electron-userland/electron-builder/issues/2108#issuecomment-333200711 private async computePublisherName() { - if ((await this.packager.cscInfo.value) == null) { + const signtoolManager = await this.packager.signtoolManager.value + if ((await signtoolManager.cscInfo.value) == null) { log.info({ reason: "Windows Store only build" }, "AppX is not signed") return this.options.publisher || "CN=ms" } - const certInfo = await this.packager.lazyCertInfo.value + const certInfo = await signtoolManager.lazyCertInfo.value const publisher = this.options.publisher || (certInfo == null ? null : certInfo.bloodyMicrosoftSubjectDn) if (publisher == null) { throw new Error("Internal error: cannot compute subject using certificate info") diff --git a/packages/app-builder-lib/src/winPackager.ts b/packages/app-builder-lib/src/winPackager.ts index c0cba0cb031..32cadcc3dcf 100644 --- a/packages/app-builder-lib/src/winPackager.ts +++ b/packages/app-builder-lib/src/winPackager.ts @@ -1,22 +1,11 @@ import BluebirdPromise from "bluebird-lst" -import { Arch, asArray, InvalidConfigurationError, log, use, executeAppBuilder, CopyFileTransformer, FileTransformer, walk } from "builder-util" -import { MemoLazy, parseDn } from "builder-util-runtime" +import { Arch, InvalidConfigurationError, log, use, executeAppBuilder, CopyFileTransformer, FileTransformer, walk, retry } from "builder-util" import { createHash } from "crypto" import { readdir } from "fs/promises" import * as isCI from "is-ci" import { Lazy } from "lazy-val" import * as path from "path" -import { importCertificate } from "./codeSign/codesign" -import { - CertificateFromStoreInfo, - CertificateInfo, - FileCodeSigningInfo, - getCertificateFromStoreInfo, - getCertInfo, - getSignVendorPath, - sign, - WindowsSignOptions, -} from "./codeSign/windowsCodeSign" +import { FileCodeSigningInfo, getSignVendorPath, WindowsSignToolManager } from "./codeSign/windowsSignToolManager" import { AfterPackContext } from "./configuration" import { DIR_TARGET, Platform, Target } from "./core" import { RequestedExecutionLevel, WindowsConfiguration } from "./options/winOptions" @@ -34,97 +23,21 @@ import { isBuildCacheEnabled } from "./util/flags" import { time } from "./util/timer" import { getWindowsVm, VmManager } from "./vm/vm" import { execWine } from "./wine" +import { signWindows } from "./codeSign/windowsCodeSign" +import { WindowsSignOptions } from "./codeSign/windowsCodeSign" +import { WindowsSignAzureManager } from "./codeSign/windowsSignAzureManager" export class WinPackager extends PlatformPackager { - readonly cscInfo = new MemoLazy( - () => this.platformSpecificBuildOptions, - platformSpecificBuildOptions => { - if (platformSpecificBuildOptions.certificateSubjectName != null || platformSpecificBuildOptions.certificateSha1 != null) { - return this.vm.value - .then(vm => getCertificateFromStoreInfo(platformSpecificBuildOptions, vm)) - .catch((e: any) => { - // https://github.com/electron-userland/electron-builder/pull/2397 - if (platformSpecificBuildOptions.sign == null) { - throw e - } else { - log.debug({ error: e }, "getCertificateFromStoreInfo error") - return null - } - }) - } - - const certificateFile = platformSpecificBuildOptions.certificateFile - if (certificateFile != null) { - const certificatePassword = this.getCscPassword() - return Promise.resolve({ - file: certificateFile, - password: certificatePassword == null ? null : certificatePassword.trim(), - }) - } - - const cscLink = this.getCscLink("WIN_CSC_LINK") - if (cscLink == null || cscLink === "") { - return Promise.resolve(null) - } - - return ( - importCertificate(cscLink, this.info.tempDirManager, this.projectDir) - // before then - .catch((e: any) => { - if (e instanceof InvalidConfigurationError) { - throw new InvalidConfigurationError(`Env WIN_CSC_LINK is not correct, cannot resolve: ${e.message}`) - } else { - throw e - } - }) - .then(path => { - return { - file: path, - password: this.getCscPassword(), - } - }) - ) - } - ) - - private _iconPath = new Lazy(() => this.getOrConvertIcon("ico")) + _iconPath = new Lazy(() => this.getOrConvertIcon("ico")) readonly vm = new Lazy(() => (process.platform === "win32" ? Promise.resolve(new VmManager()) : getWindowsVm(this.debugLogger))) - readonly computedPublisherName = new Lazy | null>(async () => { - const publisherName = this.platformSpecificBuildOptions.publisherName - if (publisherName === null) { - return null - } else if (publisherName != null) { - return asArray(publisherName) - } - - const certInfo = await this.lazyCertInfo.value - return certInfo == null ? null : [certInfo.commonName] - }) - - readonly lazyCertInfo = new MemoLazy, CertificateInfo | null>( - () => this.cscInfo, - async csc => { - const cscInfo = await csc.value - if (cscInfo == null) { - return null - } - - if ("subject" in cscInfo) { - const bloodyMicrosoftSubjectDn = cscInfo.subject - return { - commonName: parseDn(bloodyMicrosoftSubjectDn).get("CN")!, - bloodyMicrosoftSubjectDn, - } - } - - const cscFile = cscInfo.file - if (cscFile == null) { - return null - } - return await getCertInfo(cscFile, cscInfo.password || "") - } + readonly signtoolManager = new Lazy(() => Promise.resolve(new WindowsSignToolManager(this))) + readonly azureSignManager = new Lazy(() => + Promise.resolve(new WindowsSignAzureManager(this)).then(async manager => { + await manager.initializeProviderModules() + return manager + }) ) get isForceCodeSigningVerification(): boolean { @@ -139,10 +52,6 @@ export class WinPackager extends PlatformPackager { return ["nsis"] } - protected doGetCscPassword(): string | undefined | null { - return chooseNotNull(chooseNotNull(this.platformSpecificBuildOptions.certificatePassword, process.env.WIN_CSC_KEY_PASSWORD), super.doGetCscPassword()) - } - createTargets(targets: Array, mapper: (name: string, factory: (outDir: string) => Target) => void): void { let copyElevateHelper: CopyElevateHelper | null const getCopyElevateHelper = () => { @@ -203,18 +112,26 @@ export class WinPackager extends PlatformPackager { return this._iconPath.value } + doGetCscPassword(): string | undefined | null { + return chooseNotNull( + chooseNotNull( + chooseNotNull(this.platformSpecificBuildOptions.signtoolOptions?.certificatePassword, this.platformSpecificBuildOptions.certificatePassword), + process.env.WIN_CSC_KEY_PASSWORD + ), + super.doGetCscPassword() + ) + } + async sign(file: string, logMessagePrefix?: string): Promise { const signOptions: WindowsSignOptions = { path: file, - name: this.appInfo.productName, - site: await this.appInfo.computePackageUrl(), options: this.platformSpecificBuildOptions, } - const cscInfo = await this.cscInfo.value + const cscInfo = await (await this.signtoolManager.value).cscInfo.value if (cscInfo == null) { - if (this.platformSpecificBuildOptions.sign != null) { - return sign(signOptions, this) + if (chooseNotNull(this.platformSpecificBuildOptions.signtoolOptions?.sign, this.platformSpecificBuildOptions.sign) != null) { + return signWindows(signOptions, this) } else if (this.forceCodeSigning) { throw new InvalidConfigurationError( `App is not signed and "forceCodeSigning" is set to true, please ensure that code signing configuration is correct, please see https://electron.build/code-signing` @@ -251,7 +168,6 @@ export class WinPackager extends PlatformPackager { return this.doSign({ ...signOptions, - cscInfo, options: { ...this.platformSpecificBuildOptions, }, @@ -259,21 +175,22 @@ export class WinPackager extends PlatformPackager { } private async doSign(options: WindowsSignOptions) { - for (let i = 0; i < 3; i++) { - try { - await sign(options, this) - return true - } catch (e: any) { + return retry( + () => signWindows(options, this), + 3, + 500, + 500, + 0, + (e: any) => { // https://github.com/electron-userland/electron-builder/issues/1414 const message = e.message if (message != null && message.includes("Couldn't resolve host name")) { - log.warn({ error: message, attempt: i + 1 }, `cannot sign`) - continue + log.warn({ error: message }, `cannot sign`) + return true } - throw e + return false } - } - return false + ) } async signAndEditResources(file: string, arch: Arch, outDir: string, internalName?: string | null, requestedExecutionLevel?: RequestedExecutionLevel | null) { @@ -315,7 +232,7 @@ export class WinPackager extends PlatformPackager { }) const config = this.config - const cscInfoForCacheDigest = !isBuildCacheEnabled() || isCI || config.electronDist != null ? null : await this.cscInfo.value + const cscInfoForCacheDigest = !isBuildCacheEnabled() || isCI || config.electronDist != null ? null : await (await this.signtoolManager.value).cscInfo.value let buildCacheManager: BuildCacheManager | null = null // resources editing doesn't change executable for the same input and executed quickly - no need to complicate if (cscInfoForCacheDigest != null) { @@ -329,8 +246,10 @@ export class WinPackager extends PlatformPackager { hash.update(config.electronVersion || "no electronVersion") hash.update(JSON.stringify(this.platformSpecificBuildOptions)) hash.update(JSON.stringify(args)) - hash.update(this.platformSpecificBuildOptions.certificateSha1 || "no certificateSha1") - hash.update(this.platformSpecificBuildOptions.certificateSubjectName || "no subjectName") + hash.update(chooseNotNull(this.platformSpecificBuildOptions.signtoolOptions?.certificateSha1, this.platformSpecificBuildOptions.certificateSha1) || "no certificateSha1") + hash.update( + chooseNotNull(this.platformSpecificBuildOptions.signtoolOptions?.certificateSubjectName, this.platformSpecificBuildOptions.certificateSubjectName) || "no subjectName" + ) buildCacheManager = new BuildCacheManager(outDir, file, arch) if (await buildCacheManager.copyIfValid(await digest(hash, files))) { diff --git a/packages/builder-util/package.json b/packages/builder-util/package.json index 50c5703a890..fbe4032fe86 100644 --- a/packages/builder-util/package.json +++ b/packages/builder-util/package.json @@ -19,7 +19,7 @@ "@types/debug": "^4.1.6", "app-builder-bin": "5.0.0-alpha.8", "bluebird-lst": "^1.0.9", - "builder-util-runtime": "workspace:^9", + "builder-util-runtime": "workspace:*", "chalk": "^4.1.2", "cross-spawn": "^7.0.3", "debug": "^4.3.4", diff --git a/packages/dmg-builder/package.json b/packages/dmg-builder/package.json index b1ec2b0b526..5e5ccef7131 100644 --- a/packages/dmg-builder/package.json +++ b/packages/dmg-builder/package.json @@ -17,9 +17,9 @@ "vendor" ], "dependencies": { - "app-builder-lib": "workspace:^25", - "builder-util": "workspace:^25", - "builder-util-runtime": "workspace:^9", + "app-builder-lib": "workspace:*", + "builder-util": "workspace:*", + "builder-util-runtime": "workspace:*", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" diff --git a/packages/electron-builder-squirrel-windows/package.json b/packages/electron-builder-squirrel-windows/package.json index 91a6093f11a..8b3af8870de 100644 --- a/packages/electron-builder-squirrel-windows/package.json +++ b/packages/electron-builder-squirrel-windows/package.json @@ -15,9 +15,9 @@ "out" ], "dependencies": { - "app-builder-lib": "workspace:^25", + "app-builder-lib": "workspace:*", "archiver": "^5.3.1", - "builder-util": "workspace:^25", + "builder-util": "workspace:*", "fs-extra": "^10.1.0" }, "devDependencies": { diff --git a/packages/electron-builder/package.json b/packages/electron-builder/package.json index 7c0ca64885b..9100ff17ada 100644 --- a/packages/electron-builder/package.json +++ b/packages/electron-builder/package.json @@ -50,11 +50,11 @@ "bugs": "https://github.com/electron-userland/electron-builder/issues", "homepage": "https://github.com/electron-userland/electron-builder", "dependencies": { - "app-builder-lib": "workspace:^25", - "builder-util": "workspace:^25", - "builder-util-runtime": "workspace:^9", + "app-builder-lib": "workspace:*", + "builder-util": "workspace:*", + "builder-util-runtime": "workspace:*", "chalk": "^4.1.2", - "dmg-builder": "workspace:^25", + "dmg-builder": "workspace:*", "fs-extra": "^10.1.0", "is-ci": "^3.0.0", "lazy-val": "^1.0.5", diff --git a/packages/electron-builder/src/cli/create-self-signed-cert.ts b/packages/electron-builder/src/cli/create-self-signed-cert.ts index 8ebc579bac6..7d2bdda7d3c 100644 --- a/packages/electron-builder/src/cli/create-self-signed-cert.ts +++ b/packages/electron-builder/src/cli/create-self-signed-cert.ts @@ -1,7 +1,7 @@ import { sanitizeFileName } from "app-builder-lib/out/util/filename" import { exec, log, spawn, TmpDir, unlinkIfExists } from "builder-util" import * as chalk from "chalk" -import { getSignVendorPath } from "app-builder-lib/out/codeSign/windowsCodeSign" +import { getSignVendorPath } from "app-builder-lib/out/codeSign/windowsSignToolManager" import { mkdir } from "fs/promises" import * as path from "path" diff --git a/packages/electron-forge-maker-appimage/package.json b/packages/electron-forge-maker-appimage/package.json index 8434e17ede8..81caa5061ce 100644 --- a/packages/electron-forge-maker-appimage/package.json +++ b/packages/electron-forge-maker-appimage/package.json @@ -15,6 +15,6 @@ "*.js" ], "dependencies": { - "app-builder-lib": "workspace:^25" + "app-builder-lib": "workspace:*" } } diff --git a/packages/electron-forge-maker-nsis-web/package.json b/packages/electron-forge-maker-nsis-web/package.json index f76f10a2bae..378f07c9136 100644 --- a/packages/electron-forge-maker-nsis-web/package.json +++ b/packages/electron-forge-maker-nsis-web/package.json @@ -15,6 +15,6 @@ "*.js" ], "dependencies": { - "app-builder-lib": "workspace:^25" + "app-builder-lib": "workspace:*" } } diff --git a/packages/electron-forge-maker-nsis/package.json b/packages/electron-forge-maker-nsis/package.json index df8da283c6f..c740b6f8ad1 100644 --- a/packages/electron-forge-maker-nsis/package.json +++ b/packages/electron-forge-maker-nsis/package.json @@ -15,6 +15,6 @@ "*.js" ], "dependencies": { - "app-builder-lib": "workspace:^25" + "app-builder-lib": "workspace:*" } } diff --git a/packages/electron-forge-maker-snap/package.json b/packages/electron-forge-maker-snap/package.json index 4cad3b543bb..b86548f30a4 100644 --- a/packages/electron-forge-maker-snap/package.json +++ b/packages/electron-forge-maker-snap/package.json @@ -15,6 +15,6 @@ "*.js" ], "dependencies": { - "app-builder-lib": "workspace:^25" + "app-builder-lib": "workspace:*" } } diff --git a/packages/electron-publish/package.json b/packages/electron-publish/package.json index a59d486f21f..1de5d163b9b 100644 --- a/packages/electron-publish/package.json +++ b/packages/electron-publish/package.json @@ -16,8 +16,8 @@ ], "dependencies": { "@types/fs-extra": "^9.0.11", - "builder-util": "workspace:^25", - "builder-util-runtime": "workspace:^9", + "builder-util": "workspace:*", + "builder-util-runtime": "workspace:*", "chalk": "^4.1.2", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", diff --git a/packages/electron-updater/package.json b/packages/electron-updater/package.json index 60124e9098a..0b45f4183c0 100644 --- a/packages/electron-updater/package.json +++ b/packages/electron-updater/package.json @@ -16,7 +16,7 @@ "out" ], "dependencies": { - "builder-util-runtime": "workspace:^9", + "builder-util-runtime": "workspace:*", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a97e12db35e..52fd0dd2c7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -132,7 +132,7 @@ importers: specifier: workspace:^25 version: link:../builder-util builder-util-runtime: - specifier: workspace:^9 + specifier: workspace:* version: link:../builder-util-runtime chromium-pickle-js: specifier: ^0.2.0 @@ -301,7 +301,7 @@ importers: specifier: ^1.0.9 version: 1.0.9 builder-util-runtime: - specifier: workspace:^9 + specifier: workspace:* version: link:../builder-util-runtime chalk: specifier: ^4.1.2 @@ -372,13 +372,13 @@ importers: packages/dmg-builder: dependencies: app-builder-lib: - specifier: workspace:^25 + specifier: workspace:* version: link:../app-builder-lib builder-util: - specifier: workspace:^25 + specifier: workspace:* version: link:../builder-util builder-util-runtime: - specifier: workspace:^9 + specifier: workspace:* version: link:../builder-util-runtime fs-extra: specifier: ^10.1.0 @@ -407,19 +407,19 @@ importers: packages/electron-builder: dependencies: app-builder-lib: - specifier: workspace:^25 + specifier: workspace:* version: link:../app-builder-lib builder-util: - specifier: workspace:^25 + specifier: workspace:* version: link:../builder-util builder-util-runtime: - specifier: workspace:^9 + specifier: workspace:* version: link:../builder-util-runtime chalk: specifier: ^4.1.2 version: 4.1.2 dmg-builder: - specifier: workspace:^25 + specifier: workspace:* version: link:../dmg-builder fs-extra: specifier: ^10.1.0 @@ -450,13 +450,13 @@ importers: packages/electron-builder-squirrel-windows: dependencies: app-builder-lib: - specifier: workspace:^25 + specifier: workspace:* version: link:../app-builder-lib archiver: specifier: ^5.3.1 version: 5.3.2 builder-util: - specifier: workspace:^25 + specifier: workspace:* version: link:../builder-util fs-extra: specifier: ^10.1.0 @@ -472,25 +472,25 @@ importers: packages/electron-forge-maker-appimage: dependencies: app-builder-lib: - specifier: workspace:^25 + specifier: workspace:* version: link:../app-builder-lib packages/electron-forge-maker-nsis: dependencies: app-builder-lib: - specifier: workspace:^25 + specifier: workspace:* version: link:../app-builder-lib packages/electron-forge-maker-nsis-web: dependencies: app-builder-lib: - specifier: workspace:^25 + specifier: workspace:* version: link:../app-builder-lib packages/electron-forge-maker-snap: dependencies: app-builder-lib: - specifier: workspace:^25 + specifier: workspace:* version: link:../app-builder-lib packages/electron-publish: @@ -499,10 +499,10 @@ importers: specifier: ^9.0.11 version: 9.0.13 builder-util: - specifier: workspace:^25 + specifier: workspace:* version: link:../builder-util builder-util-runtime: - specifier: workspace:^9 + specifier: workspace:* version: link:../builder-util-runtime chalk: specifier: ^4.1.2 @@ -524,7 +524,7 @@ importers: packages/electron-updater: dependencies: builder-util-runtime: - specifier: workspace:^9 + specifier: workspace:* version: link:../builder-util-runtime fs-extra: specifier: ^10.1.0 @@ -576,13 +576,13 @@ importers: specifier: ^27.5.1 version: 27.5.1(ts-node@10.9.2(@types/node@16.18.55)(typescript@5.5.3)) app-builder-lib: - specifier: workspace:^25 + specifier: workspace:* version: link:../packages/app-builder-lib builder-util: - specifier: workspace:^25 + specifier: workspace:* version: link:../packages/builder-util builder-util-runtime: - specifier: workspace:^9 + specifier: workspace:* version: link:../packages/builder-util-runtime chalk: specifier: ^4.1.2 @@ -597,19 +597,19 @@ importers: specifier: 1.4.3 version: 1.4.3 dmg-builder: - specifier: workspace:^25 + specifier: workspace:* version: link:../packages/dmg-builder electron-builder: - specifier: workspace:^25 + specifier: workspace:* version: link:../packages/electron-builder electron-builder-squirrel-windows: - specifier: workspace:^25 + specifier: workspace:* version: link:../packages/electron-builder-squirrel-windows electron-publish: - specifier: workspace:^25 + specifier: workspace:* version: link:../packages/electron-publish electron-updater: - specifier: workspace:^6 + specifier: workspace:* version: link:../packages/electron-updater fs-extra: specifier: ^10.1.0 diff --git a/scripts/jsdoc2md2html.js b/scripts/jsdoc2md2html.js index 5509540e23b..1dc0c23a711 100644 --- a/scripts/jsdoc2md2html.js +++ b/scripts/jsdoc2md2html.js @@ -216,7 +216,10 @@ async function render2(files, jsdoc2MdOptions) { new Page("configuration/mas.md", "MasConfiguration"), new Page("configuration/pkg.md", "PkgOptions"), - new Page("configuration/win.md", "WindowsConfiguration"), + new Page("configuration/win.md", "WindowsConfiguration", { + "WindowsAzureSigningConfiguration" : "Also allows custom fields `[k: string: string]` passed verbatim (case sensitive) to Invoke-TrustedSigning", + "WindowsSigntoolConfiguration": "" + }), new Page("configuration/msi-wrapped.md", "MsiWrappedOptions"), new Page("configuration/msi.md", "MsiOptions"), new Page("configuration/appx.md", "AppXOptions"), diff --git a/test/package.json b/test/package.json index 3005b20fb38..df1a208197a 100644 --- a/test/package.json +++ b/test/package.json @@ -5,18 +5,18 @@ "dependencies": { "@electron/osx-sign": "^1.0.4", "@jest/core": "^27.5.1", - "app-builder-lib": "workspace:^25", - "builder-util": "workspace:^25", - "builder-util-runtime": "workspace:^9", + "app-builder-lib": "workspace:*", + "builder-util": "workspace:*", + "builder-util-runtime": "workspace:*", "chalk": "^4.1.2", "ci-info": "^3.7.0", "decompress-zip": "^0.3.3", "depcheck": "1.4.3", - "dmg-builder": "workspace:^25", - "electron-builder": "workspace:^25", - "electron-builder-squirrel-windows": "workspace:^25", - "electron-publish": "workspace:^25", - "electron-updater": "workspace:^6", + "dmg-builder": "workspace:*", + "electron-builder": "workspace:*", + "electron-builder-squirrel-windows": "workspace:*", + "electron-publish": "workspace:*", + "electron-updater": "workspace:*", "fs-extra": "^10.1.0", "jest": "^27.5.1", "jest-junit": "^12.0.0", diff --git a/test/src/windows/winCodeSignTest.ts b/test/src/windows/winCodeSignTest.ts index d8314deae4f..39166d23620 100644 --- a/test/src/windows/winCodeSignTest.ts +++ b/test/src/windows/winCodeSignTest.ts @@ -40,10 +40,15 @@ function testCustomSign(sign: any) { platformPackagerFactory: (packager, platform) => new CheckingWinPackager(packager), config: { win: { - certificatePassword: "pass", - certificateFile: "secretFile", - sign, - signingHashAlgorithms: ["sha256"], + certificateFile: "deprecated", + certificatePassword: "deprecated", + sign: "deprecated", + signtoolOptions: { + certificatePassword: "pass", + certificateFile: "secretFile", + sign, + signingHashAlgorithms: ["sha256"], + }, // to be sure that sign code will be executed forceCodeSigning: true, },