Skip to content

feat(desktop): sign Windows installers via Azure Trusted Signing - #559

Open
Weegy wants to merge 1 commit into
mainfrom
feat/windows-azure-trusted-signing
Open

feat(desktop): sign Windows installers via Azure Trusted Signing#559
Weegy wants to merge 1 commit into
mainfrom
feat/windows-azure-trusted-signing

Conversation

@Weegy

@Weegy Weegy commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Problem

Windows installers currently ship unsigned — SmartScreen shows an "unknown publisher" warning. Unlike the macOS defect in #558 the installer still runs, so this is a product-quality issue rather than an outage, but it hurts conversion on every download.

The existing path in the workflow expects a .p12 in WINDOWS_CSC_LINK_BASE64, and that is a dead end for any new certificate: since 2023-06-01 the CA/Browser Forum requires every code-signing signing key — OV and EV — to be generated and held in a FIPS 140-2 Level 2 / EAL4+ HSM. No CA will issue an exportable file any more, so that secret can only ever be filled from a pre-2023 certificate.

Approach: Azure Trusted Signing

Azure Trusted Signing (rebranded Azure Artifact Signing in 2026) keeps the signing material in Microsoft's HSM and signs over an API, so it needs no hardware token and works on GitHub-hosted runners. ~$9.99/month, available to verified EU businesses.

Everything below was verified against the pinned app-builder-lib@25.1.8, not assumed:

Checked Result
win.azureSignOptions in 25.1.8 schema present — no electron-builder upgrade needed
Required fields endpoint, codeSigningAccountName, certificateProfileName
PowerShell module electron-builder installs it itself (Install-Module -Name TrustedSigning -RequiredVersion 0.4.1)
Credentials Azure EnvironmentCredential → the three AZURE_* names are fixed by Azure, not chosen here

Changes

File Change
.github/workflows/desktop-apps.yml Azure signing step building --config.win.azureSignOptions.* flags; fails fast if AZURE_CLIENT_ID is set but any sibling is empty
.github/workflows/desktop-apps.yml Always-on Windows verification gate (see below)
.github/workflows/desktop-apps.yml Legacy .p12 path kept, but only used when the Azure secrets are absent; header documents why it is legacy
desktop/README.md Documents both paths and the HSM requirement

The three account coordinates are passed on the CLI rather than written into electron-builder.yml, so the committed config stays generic — a fork without these secrets builds unsigned instead of failing against byte5's Azure account.

Verification gate

Mirrors the macOS gate added in #558. It runs whenever signing was configured, so "secrets are present but the installer came out unsigned" fails the build instead of going green and reaching users. It also requires a trusted timestamp — without a countersignature the signature stops validating once the certificate expires. Uses Get-AuthenticodeSignature rather than signtool.exe, so there is no Windows SDK path to locate.

That failure mode is not hypothetical: it is exactly how v0.56.0 and v0.57.0 shipped a macOS app that could not be opened at all.

Verified so far

  • Schema and runtime requirements confirmed against the actual pinned package
  • Flag construction tested locally: produces exactly the 3 expected argv entries, and the missing-variable guard triggers correctly
  • actionlint clean (the two info-level SC2086 hits are the intentional word-splitting on ${EB_NOTARIZE:-} / ${EB_WIN_SIGN:-})

Not yet validated end-to-end

This cannot be tested until the Azure Trusted Signing account exists. Required setup on the byte5 Azure tenant:

  1. Trusted Signing Account + Certificate Profile, with identity validation (takes a few days)
  2. App Registration + client secret, granted the Trusted Signing Certificate Profile Signer role
  3. Six repo secrets: AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SIGN_ENDPOINT, AZURE_SIGN_ACCOUNT, AZURE_SIGN_CERT_PROFILE

Until then this branch is a no-op for Windows: no Azure secrets → no EB_WIN_SIGN flags → unsigned build, exactly as today. Merging it early is safe; it simply activates once the secrets land.

Test plan

  • azureSignOptions support confirmed in the pinned electron-builder
  • Flag construction + guard tested
  • Azure Trusted Signing account provisioned and secrets added
  • Desktop apps dispatched; Windows job signs and the gate passes
  • Downloaded .exe shows a valid, timestamped signature with the byte5 publisher
  • Confirm SmartScreen behaviour on a clean Windows machine (reputation builds over downloads; only EV certificates grant it instantly)

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

The existing Windows path expects a .p12 in WINDOWS_CSC_LINK_BASE64, which is
a dead end for any new certificate: since 2023-06-01 the CA/Browser Forum
requires every code-signing private key (OV *and* EV) to be generated and kept
in a FIPS 140-2 L2 / EAL4+ HSM, so no CA will issue an exportable file. As a
result Windows installers currently ship unsigned and SmartScreen shows an
'unknown publisher' warning.

Azure Trusted Signing (rebranded Azure Artifact Signing) solves this without a
hardware token, so it works on GitHub-hosted runners. Verified against the
pinned app-builder-lib@25.1.8 schema rather than assumed:

  - win.azureSignOptions already exists in 25.1.8 — no electron-builder
    upgrade needed
  - required fields are endpoint, codeSigningAccountName, certificateProfileName
  - electron-builder installs the TrustedSigning PowerShell module itself
    (Install-Module -Name TrustedSigning -RequiredVersion 0.4.1) and reads
    credentials via Azure EnvironmentCredential, which is why the three
    AZURE_* credential names are fixed by Azure rather than chosen here

The three account coordinates are passed on the CLI instead of being written
into electron-builder.yml, so the committed config stays generic — a fork
without these secrets builds unsigned rather than failing against someone
else's Azure account.

Also adds an always-on Windows verification gate, mirroring the macOS one: it
runs whenever signing was configured, so 'secrets present but the installer
came out unsigned' fails the build instead of reaching users. It additionally
requires a trusted timestamp, without which the signature would expire with
the certificate. Uses Get-AuthenticodeSignature rather than signtool.exe to
avoid depending on a Windows SDK path.

The legacy .p12 path is kept for pre-2023 certificates but is now only used
when the Azure secrets are absent.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant