stacklok / stacklok/toolhive-studio

[Task] Migrate Windows code signing from DigiCert KeyLocker to Azure Trusted Signing

Open
#2,034 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Task
Dominant language
TypeScript
Stars
167
Forks
24
Avg merge
11h 32m
Merged PRs (30d)
91

Description

Description

Migrate the Windows code signing pipeline from DigiCert KeyLocker to Azure Trusted Signing, authenticated via GitHub OIDC workload identity federation, and decommission the DigiCert path entirely.

Today Windows artifacts are signed by shelling out to DigiCert's smctl.exe from a custom @electron/windows-sign hook, which requires storing a client certificate (.p12), its password, an API key, a host URL, and a SHA-1 fingerprint as long-lived repository secrets. The same flow is duplicated in both pr-build-test.yml (opt-in via /build-test --sign-windows) and on-release.yml (stable + prerelease tags).

Azure Trusted Signing replaces all of that with OIDC (no client secret stored anywhere), uses the standard signtool.exe + Azure Code Signing DLib, and is the path Electron Forge now documents as canonical for Windows signing. The migration also lets us scope signing credentials to a dedicated artifact-signing GitHub environment so unsigned PR builds and non-Windows matrix rows can never request them.

Technical Details

Target architecture
  • Signing backend: Azure Trusted Signing account + Certificate Profile, with a federated credential trusting repo:stacklok/toolhive-studio:environment:artifact-signing.
  • Auth: azure/login@v2 with id-token: write; no client secret.
  • Signer: signtool.exe (from the Windows SDK on the runner) + Azure.CodeSigning.Dlib (installed via nuget) + a generated metadata.json — wired through @electron/windows-sign's signWithParams option, following the official Electron Forge Trusted Signing guide.
  • GitHub environment: artifact-signing, holding six AZURE_ARTIFACT_SIGNING_* secrets (tenant id, client id, subscription id, endpoint, account name, certificate profile name). Activated only for the Windows matrix row when signing is requested.
  • Reusable composite action: .github/actions/setup-azure-trusted-signing encapsulates azure/login, nuget install, locating the latest x64 signtool.exe, writing metadata.json, and exporting AZURE_CODE_SIGNING_DLIB / AZURE_METADATA_JSON / SIGNTOOL_PATH to GITHUB_ENV.
  • Forge integration: a single getWindowsSignConfig() helper in forge.config.ts returns the Azure config when env vars are present; both packagerConfig.windowsSign and maker-squirrel's windowsSign consume it.
Rollout plan (staged to keep releases shipping)

The migration is broken into three PRs so we can burn in the new flow progressively without risking a broken stable release:

  1. PR builds (opt-in): migrate pr-build-test.yml / /build-test --sign-windows to Azure Trusted Signing. Keep DigiCert as a runtime fallback in forge.config.ts so on-release.yml is unaffected. Add utils/windows-sign-azure.ts, the composite action, and the artifact-signing environment.
  2. Prerelease tags: extend Azure signing to *-alpha, *-beta, *-rc tags in on-release.yml via a conditional step. Stable releases still sign via DigiCert until prereleases are proven.
  3. Stable releases + cleanup: flip stable Windows releases to Azure, then remove the entire DigiCert path — .github/actions/setup-windows-codesign, utils/digicert-hook.js, the DigiCert fallback in forge.config.ts, the eslint.config.mjs ignore entry, the DigiCert section in docs/README.md, and the SM_* repository/environment secrets.
AWS OIDC compatibility

The build-and-release job also assumes an AWS IAM role via OIDC (for S3 sync / CloudFront invalidation). Entering the artifact-signing environment changes the token subject from repo:.../ref:refs/tags/vX.Y.Z to repo:.../environment:artifact-signing. The existing AWS trust policy uses the wildcard repo:stacklok/toolhive-studio:*, so both subjects are accepted and no AWS-side changes are required.

Relevant files
  • .github/workflows/pr-build-test.yml
  • .github/workflows/on-release.yml
  • .github/actions/setup-azure-trusted-signing/action.yml (new)
  • .github/actions/setup-windows-codesign/action.yml (to be removed at the end)
  • utils/windows-sign-azure.ts (new)
  • utils/digicert-hook.js (to be removed at the end)
  • forge.config.ts (getWindowsSignConfig())
  • eslint.config.mjs
  • docs/README.md

Acceptance Criteria

  • A dedicated artifact-signing GitHub environment holds the six AZURE_ARTIFACT_SIGNING_* secrets and has a federated credential trusting the repo's environment subject
  • .github/actions/setup-azure-trusted-signing composite action is in place and used by both pr-build-test.yml and on-release.yml
  • utils/windows-sign-azure.ts produces a valid @electron/windows-sign config and is consumed by forge.config.ts for both packagerConfig.windowsSign and maker-squirrel's windowsSign
  • /build-test --sign-windows produces Azure-signed artifacts on PRs
  • Prerelease tags (*-alpha, *-beta, *-rc) are signed via Azure Trusted Signing from on-release.yml
  • Stable release tags are signed via Azure Trusted Signing from on-release.yml
  • Signed artifacts pass signtool verify /pa /v, install cleanly on a fresh Windows VM without SmartScreen blocking after reputation builds, and the Squirrel auto-updater handshake succeeds on an existing installed build
  • AWS S3 sync + CloudFront invalidation on the Windows release runner continue to work after switching the OIDC token subject to environment:artifact-signing
  • .github/actions/setup-windows-codesign/, utils/digicert-hook.js, the DigiCert fallback branch in forge.config.ts, and the utils/digicert-hook.js entry in eslint.config.mjs are removed
  • docs/README.md documents Azure Trusted Signing as the sole Windows signing path
  • The SM_HOST, SM_API_KEY, SM_CLIENT_CERT_FILE_B64, SM_CLIENT_CERT_PASSWORD, and SM_CODE_SIGNING_CERT_SHA1_HASH secrets are removed from the repo and any environments
  • Lint, type-check, and unit tests pass

Out of Scope

  • macOS / Flatpak signing (unchanged — Apple certificates and Flatpak remain as-is)
  • Reworking the AWS OIDC trust policy (the existing wildcard already covers both tag and environment subjects)
  • Changes to the auto-updater feed itself — only the signing identity on the artifacts changes
  • Cancelling the DigiCert KeyLocker subscription is tracked under this task but handled outside the repo

Priority

High — removes long-lived client certs + API keys from repository secrets, aligns with the Electron Forge recommended path, and unblocks decommissioning DigiCert KeyLocker.

Task Type

Chore / Infrastructure

References

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading .github/workflows/pr-build-test.yml, .github/workflows/on-release.yml, and forge.config.ts, then review the Electron Forge Trusted Signing guide and the existing DigiCert action. Implement the staged workflow and Forge integration across the listed files, validate signing and release behavior on Windows, and finish with lint, type-check, and unit tests passing and the DigiCert path removed.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, electron, github-actions, typescript
Domain
build-system, ci-cd, devops, release, security
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.