Add optional Artifact Signing for Windows builds#2995
Add optional Artifact Signing for Windows builds#2995
Conversation
📊 Performance Test ResultsComparing 6b940af vs trunk app-size
site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff) |
| # The ~> modifier is not currently used, but we check for it just in case | ||
| XCODE_VERSION=$(sed -E -n 's/^(~> )?(.*)/xcode-\2/p' .xcode-version) | ||
| CI_TOOLKIT_VERSION='6.0.1' | ||
| CI_TOOLKIT_VERSION='4411dd924c08dea251702db5760e741c9d81eff2' |
There was a problem hiding this comment.
Waiting for confirmation on the tooling from a few more clients before shipping a new version of the plugin. A commit sha doesn't look as neat as a tag, but the pinning result is the same.
Shared config and signing hook for Azure Trusted Signing, ported from the `test-artifact-signing` branch. `azure-signing.cjs` builds signtool args and validates env vars. `azure-sign-hook.js` is the custom `@electron/windows-sign` hook that calls signtool with SHA256-only params (Azure doesn't support the default SHA1+SHA256 dual-sign). --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Returns Azure signing hook config when `USE_AZURE_TRUSTED_SIGNING` is set, `undefined` otherwise so Forge falls back to PFX certs. Throws if the toggle is on but Azure env vars are missing. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Adds `windowsSign` to `packagerConfig` and `MakerSquirrel`. When defined (Azure mode), uses the signing hook. When undefined (PFX mode), uses `certificateFile`/`certificatePassword`. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Conditionally calls `setup_azure_trusted_signing.ps1` or `setup_windows_code_signing.ps1` based on `USE_AZURE_TRUSTED_SIGNING`. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
When `USE_AZURE_TRUSTED_SIGNING` is set, builds an unsigned sideload AppX then signs it with Azure signtool. Otherwise uses the existing PFX certificate path. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Points to the `add-azure-trusted-signing` branch which includes `setup_azure_trusted_signing.ps1`. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
The `add-azure-trusted-signing` branch no longer exists. Use the commit SHA directly. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Gio Lodi <giovanni.lodi42@gmail.com>
Set `USE_AZURE_TRUSTED_SIGNING=1` in both dev and release Windows build jobs so Azure signing is active by default. The toggle in `build-for-windows.ps1` still allows falling back to PFX by unsetting the env var. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
`electron2appx` names the file "unsigned" when `devCert: 'nil'` is passed. The Azure signing path builds unsigned then signs externally, so the file kept the misleading name. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
f1360a5 to
82cf136
Compare
| const renamedPath = path.join( appxOutputPathSigned, renamedFile ); | ||
| await fs.rename( appxPath, renamedPath ); | ||
| console.log( `Renamed to ${ renamedFile }` ); | ||
| } |
| // Azure mode: use the custom signing hook that calls signtool | ||
| // with Azure Trusted Signing parameters. | ||
| // PFX mode: use the local certificate file and password. | ||
| ...( windowsSign | ||
| ? { windowsSign } | ||
| : { | ||
| certificateFile: path.join( repoRoot, 'certificate.pfx' ), | ||
| certificatePassword: process.env.WINDOWS_CODE_SIGNING_CERT_PASSWORD, | ||
| } | ||
| ), |
There was a problem hiding this comment.
Once we'll have established that the new code signing is good, we'll be able to merge the checks done inside windowsSign.ts in this file, before defining this ForgeConfig instance.
As it stands, this code looks a bit ugly, but it's a temporary compromise.
There was a problem hiding this comment.
Pull request overview
This PR adds an opt-in Azure Trusted Signing path for Windows artifacts (Forge packaging, Squirrel installer, and sideload AppX), while retaining the existing PFX-based signing as a fallback controlled by USE_AZURE_TRUSTED_SIGNING.
Changes:
- Introduces Azure Trusted Signing helpers (
azure-signing.cjs,azure-sign-hook.js) and awindowsSignForge config helper gated byUSE_AZURE_TRUSTED_SIGNING. - Updates Windows AppX packaging to support Azure signing for the sideload (local testing) AppX path.
- Updates Buildkite pipelines and Windows build script to set up Azure signing when the toggle is enabled and bumps CI toolkit ref.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/package-appx.mjs | Adds Azure-vs-PFX branching for env validation and sideload AppX signing via signtool. |
| scripts/azure-signing.cjs | Adds shared Azure signing config/env validation and signtool arg builder. |
| scripts/azure-sign-hook.js | Adds a custom @electron/windows-sign hook to do SHA256-only signing via Azure. |
| apps/studio/windowsSign.ts | Adds an env-gated Forge windowsSign hook configuration for Azure signing. |
| apps/studio/forge.config.ts | Wires windowsSign into packager config and conditionally into MakerSquirrel config. |
| .buildkite/shared-pipeline-vars | Updates CI toolkit ref to include Azure signing setup script. |
| .buildkite/release-build-and-distribute.yml | Enables Azure signing for Windows release builds via USE_AZURE_TRUSTED_SIGNING=1. |
| .buildkite/pipeline.yml | Enables Azure signing for Windows dev builds via USE_AZURE_TRUSTED_SIGNING=1. |
| .buildkite/commands/build-for-windows.ps1 | Conditionally runs Azure or PFX signing setup based on USE_AZURE_TRUSTED_SIGNING. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if ( ! process.env.AZURE_CODE_SIGNING_DLIB || ! process.env.AZURE_METADATA_JSON ) { | ||
| throw new Error( | ||
| 'USE_AZURE_TRUSTED_SIGNING is set but Azure signing env vars ' + | ||
| '(AZURE_CODE_SIGNING_DLIB, AZURE_METADATA_JSON) are missing. ' + |
There was a problem hiding this comment.
getWindowsSign() validates AZURE_CODE_SIGNING_DLIB and AZURE_METADATA_JSON but not SIGNTOOL_PATH, even though the Azure signing hook requires it. This can lead to a later failure during signing rather than failing fast when loading Forge config; consider validating SIGNTOOL_PATH here and including it in the error message to keep the required env var list consistent.
| if ( ! process.env.AZURE_CODE_SIGNING_DLIB || ! process.env.AZURE_METADATA_JSON ) { | |
| throw new Error( | |
| 'USE_AZURE_TRUSTED_SIGNING is set but Azure signing env vars ' + | |
| '(AZURE_CODE_SIGNING_DLIB, AZURE_METADATA_JSON) are missing. ' + | |
| if ( ! process.env.AZURE_CODE_SIGNING_DLIB || ! process.env.AZURE_METADATA_JSON || ! process.env.SIGNTOOL_PATH ) { | |
| throw new Error( | |
| 'USE_AZURE_TRUSTED_SIGNING is set but Azure signing env vars ' + | |
| '(AZURE_CODE_SIGNING_DLIB, AZURE_METADATA_JSON, SIGNTOOL_PATH) are missing. ' + |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@p-jackson @wojtekn can I ask you to take the artifacts from the Windows test build, in particular the Artifacts Signing ones for a spin to verify they work as expected? Thanks! |
|
@mokagio both Azure and PFX build artifacts install fine, and show as properly signed on my test machine. I noticed one difference, though - the PFX doesn't show dates.
|
|
Thanks @wojtekn ! I don't know why the PFX doesn't show the date in the signature. Guess: Artifact Signing requires a more precise system. Anyway, given Artifacts Signing is the way we want to go forward, I think it's okay to leave the question unanswered for the PFX in the interest of moving ahead. |
|
@mokagio I saw the same things as Wojtek, but it makes sense that if we have to move forward with PFX then the dates can be figured out later. The usual stuff (installing, installing on top of existing installation, uninstalling) all work as expected. |
|
Thanks @p-jackson |




Related issues
How AI was used in this PR
Implementation planned and written with Claude Code (Opus 4.6).
All code reviewed by @mokagio before committing.
Proposed Changes
Takes the work from #2709 and puts it behind a toggle env var.
The idea being that we can switch back to the previous signing implementation if something turns out to be wrong once released in the wild.
azure-signing.cjs,azure-sign-hook.js) for SHA256-only signing via signtoolwindowsSign.tsmodule gated onUSE_AZURE_TRUSTED_SIGNINGenv var — returns Azure hook config when set,undefined(PFX fallback) otherwisewindowsSignintoforge.config.tsfor bothpackagerConfigandMakerSquirrelbuild-for-windows.ps1(Azure or PFX based on toggle)package-appx.mjsfor sideload AppXsetup_azure_trusted_signing.ps1USE_AZURE_TRUSTED_SIGNING=1in both dev and release Windows build jobs in the pipelineTo revert to PFX signing, remove the
USE_AZURE_TRUSTED_SIGNINGenv var from the pipeline YAML files.Testing Instructions
I created #2996 to test the changes here and verify that both build paths work.
Pre-merge Checklist
Posted by Claude Code (Opus 4.6) on behalf of @mokagio with approval.