Skip to content

feat(build): Authenticode code signing via Azure Trusted Signing (all 7 installers) - #1363

Merged
accesswatch merged 1 commit into
mainfrom
feature/authenticode-signing
Aug 9, 2026
Merged

feat(build): Authenticode code signing via Azure Trusted Signing (all 7 installers)#1363
accesswatch merged 1 commit into
mainfrom
feature/authenticode-signing

Conversation

@accesswatch

Copy link
Copy Markdown
Contributor

Summary

Adds Windows Authenticode code signing for QUILL and all six companion apps via Azure Trusted Signing (no PFX/private key on disk; auth is the ambient az login / workload-identity credential). This is distinct from the existing Ed25519 provenance signing in quill/tools/signing.py and the update-feed key — see the comparison table in docs/code-signing.md.

Signs three things per app: each shipped .exe/.dll, the Setup.exe, and the Inno-generated uninstaller.

What's included

  • scripts/code_signing.py — one reusable signer + CLI (doctor, ensure-dlib, sign, sign-tree, sign-build, verify). Locates signtool, downloads + SHA-256-verifies the pinned Microsoft.Trusted.Signing.Client dlib into gitignored build/deps/trusted-signing/, and invokes signtool via an argv list (never an MSYS shell, which mangles /fd switches into paths).
  • Opt-in / fail-open contract: signs only under QUILL_SIGN=1; aborts only under QUILL_SIGN_REQUIRED=1; QUILL_SIGN_PATTERNS tunes the fileset (default *.exe,*.dll). Plain builds — CI, offline, contributor clones — are byte-for-byte unchanged.
  • All 7 installers wired. Payload signed before packaging; Setup.exe + the embedded uninstaller signed by Inno's native SignTool + SignedUninstaller during compile, gated behind an #ifdef Sign block (ISCC gets /DSign + a /Squilltrusted mapping). The embedded uninstaller can only be signed this way.
    • Main app: build_inno_setup_script emits the block; compile_inno_setup_installer adds the sign args; installer/quill.iss regenerated.
    • Standalones (radio, weather, cast, beacon, social, studio): block added to each .iss; each build_release.ps1 gains a -Sign switch.
  • Docs: docs/code-signing.md (runbook), cross-refs from docs/signing.md, PRD §10.6, and CLAUDE.md.
  • metadata.json: Trusted Signing account/profile config (endpoint + names only, no secret) so CI and other machines can sign.

Verification

  • Signed + verified a real PE with the QUILL cert (CN=Jeffrey Bishop, timestamped).
  • Compiled a minimal installer through ISCC with the plumbing: log showed Inno signing both uninst.e32.tmp (uninstaller) and MiniSetup.exe (setup), both "Verification successful".
  • tests/unit/scripts/test_code_signing.py (10, cert-free) + installer sync/compile tests pass; ruff clean; docs parity gate passes; all six build_release.ps1 parse.

Follow-ups (not in this PR)

  • CI signing needs an Azure workload identity (service principal / GitHub OIDC) with the Trusted Signing Certificate Profile Signer role.
  • The -lite/-shared .iss variants aren't compiled by the current build scripts, so they're not wired.

🤖 Generated with Claude Code

… Azure Trusted Signing

Add Windows code signing for QUILL and all six companion apps using Azure
Trusted Signing (no PFX on disk; auth is the ambient az login / workload
credential). Distinct from the Ed25519 provenance signing in quill/tools/signing.py.

- scripts/code_signing.py: one reusable signer + CLI (doctor, ensure-dlib,
  sign, sign-tree, sign-build, verify). Locates signtool, downloads and
  SHA-256-verifies the pinned Microsoft.Trusted.Signing.Client dlib into
  gitignored build/deps/trusted-signing/, and invokes signtool via an argv list
  (never an MSYS shell, which mangles /fd switches). Opt-in via QUILL_SIGN=1;
  fail-open unless QUILL_SIGN_REQUIRED=1; QUILL_SIGN_PATTERNS tunes the fileset.

- Wired into all seven installers. Payload .exe/.dll are signed before
  packaging; each Setup.exe and its embedded uninstaller are signed by Inno's
  native SignTool + SignedUninstaller during compile, gated behind an
  "#ifdef Sign" block (ISCC gets /DSign + a /Squilltrusted mapping). A plain
  build passes neither, so unsigned builds compile unchanged.

- Main app: build_inno_setup_script emits the block; compile_inno_setup_installer
  adds the sign args when QUILL_SIGN is set; installer/quill.iss regenerated.
- Standalones: block added to each .iss; each build_release.ps1 gains a -Sign
  switch and passes the sign args to ISCC.

- Docs: docs/code-signing.md, with cross-refs from docs/signing.md, the PRD, and
  CLAUDE.md. Tests: tests/unit/scripts/test_code_signing.py (10, cert-free).
- metadata.json: Trusted Signing account/profile config (endpoint + names only,
  no secret) so CI and other machines can sign.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@accesswatch
accesswatch merged commit bc2410b into main Aug 9, 2026
14 checks passed
@accesswatch
accesswatch deleted the feature/authenticode-signing branch August 9, 2026 16:47
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