Skip to content

fix(release): the Authenticode gate pins Artifact Signing's issuer and EKU, so a trusted self-signed CN=Matt Gradwohl certificate no longer passes (#2990) - #3000

Merged
mgradwohl merged 3 commits into
mainfrom
fix/2990-signer-issuer-pin
Oct 26, 2026
Merged

mgradwohl merged 3 commits into
mainfrom
fix/2990-signer-issuer-pin

Conversation

@mgradwohl

Copy link
Copy Markdown
Owner

Description

cmake/VerifyWindowsSignatures.cmake accepted any Valid, timestamped signature whose signer CN was TASKSMACK_EXPECTED_SIGNER (Matt Gradwohl), with the same Subject on both exes. A self-signed or internal code-signing certificate with CN=Matt Gradwohl that the build machine trusts passed. The check now also pins two things about the signer certificate that stay the same across Azure Artifact Signing's few-day rotation:

  • Issuer: -like TASKSMACK_EXPECTED_ISSUER, default CN=Microsoft ID Verified CS [AE]OC CA [0-9][0-9], O=Microsoft Corporation, C=US. Any two-digit CA number matches, so a rollover from CA 04 to 05 still passes. The pattern takes AOC as well as EOC because both are Microsoft ID Verified code-signing issuing CAs; drop [AE] to E if you'd rather pin EOC only.
  • EKU: at least one extended key usage -like TASKSMACK_EXPECTED_EKU, default 1.3.6.1.4.1.311.97.* (Artifact Signing's).

Both checks are required. The CN, Valid, timestamp and same-subject checks stay as they were.

The real certificate (main's dry run 40245228873, release-assets-dry-run, both TaskSmack-0.9.0-win64.zip and -windows-arm64.zip; read with Get-AuthenticodeSignature):

Subject CN=Matt Gradwohl, O=Matt Gradwohl, L=REDMOND, S=wa, C=US
Issuer CN=Microsoft ID Verified CS EOC CA 04, O=Microsoft Corporation, C=US
Chain → Microsoft ID Verified Code Signing PCA 2021 → Microsoft Identity Verification Root Certificate Authority 2020
EKUs 1.3.6.1.4.1.311.97.1.0, 1.3.6.1.5.5.7.3.3 (Code Signing), 1.3.6.1.4.1.311.97.703777433.107619563.194876341.588498515 (the identity validation's own)
Validity 2026-10-25 15:08 to 2026-10-28 15:08 (three days)
Timestamp CN=Microsoft Public RSA Time Stamping Authority, ...

The pin doesn't use the identity-validation-specific EKU, because it changes whenever the identity is validated again. 311.97.* covers it and 311.97.1.0.

Plumbing.

  • Both values are CMake cache options in cmake/Options.cmake, documented next to TASKSMACK_EXPECTED_SIGNER.
  • They reach CPack's pre-build check through CPackProjectConfig.cmake.in.
  • In -P mode the script falls back to the same defaults. An empty value also means the default, so a pin can't be dropped by mistake.
  • tools/verify-windows-signatures.ps1 takes optional -ExpectedIssuer/-ExpectedEku and logs each signer's EKUs. Without them it uses the defaults.
  • release.yml is not changed: validate-windows-package and verify-published-release pick up the pins through the defaults. sign-windows' "quick look" before repacking still checks only the CN. That step isn't the gate, and editing a workflow needs your confirmation.

Testability.

  • The decision is now one PowerShell function, Test-TaskSmackSignatures, kept in a bracket argument in the .cmake file.
  • -DTASKSMACK_WRITE_SIGNATURE_CHECK=<file> writes the function out and returns without checking anything.
  • No new shipped file was added, so release.yml's sparse checkouts need no change.

Fixes #2990

PR Size

Medium: about 290 lines. About 160 of them are the new test.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Build/CI improvement

Checklist

  • I have read the CONTRIBUTING guidelines
  • My code follows the project's coding standards
  • I have run clang-tidy and addressed any warnings (no C++ changed)
  • I have run clang-format on my changes (no C++ changed)
  • I have run pre-commit run --all-files or installed pre-commit hooks (pre-commit run --files on every touched file)
  • I have added tests that prove my fix/feature works
  • All new and existing tests pass
  • I have updated documentation as needed

Testing

1. New test: tools/test-verify-windows-signatures.ps1, run by CTest as VerifyWindowsSignaturesScript (label pwsh-script, RESOURCE_LOCK pwsh-script-tests, 120 s timeout).

  • It writes the check out through cmake and calls it with Get-AuthenticodeSignature-shaped objects.
  • The certificates are made in memory with CertificateRequest. Nothing is signed, no store is opened, and nothing is trusted.
  • It also asserts that the script's defaults and Options.cmake's cache defaults are identical.

Pass:

  • the Azure-shaped chain (EOC CA 04 with the real EKUs);
  • a rotation between the two files onto CA 05;
  • an AOC CA 01;
  • only the Public Trust EKU;
  • a configured issuer.

Fail:

  • self-signed CN=Matt Gradwohl with Code Signing (the issue's repro);
  • self-signed with the 311.97 EKUs;
  • an internal CA;
  • a lookalike CN=Microsoft ID Verified CS EOC CA 04, O=Contoso;
  • Code Signing only;
  • no EKU extension;
  • another CN, no timestamp, HashMismatch, NotSigned;
  • different subjects;
  • a configured EKU that isn't present.
pwsh -NoProfile -File tools/test-verify-windows-signatures.ps1
  Artifact Signing, one certificate: OK
  A rotation between the files, onto CA 05: OK
  Self-signed CN=Matt Gradwohl: FAIL TaskSmack.exe's signer certificate is issued by CN=Matt Gradwohl, not by CN=Microsoft ID Verified CS [AE]OC CA [0-9][0-9], O=Microsoft Corporation, C=US
  No Artifact Signing usage: FAIL TaskSmack.exe's signer certificate has no 1.3.6.1.4.1.311.97.* extended key usage (it has 1.3.6.1.5.5.7.3.3)
  ...
VerifyWindowsSignatures.cmake check tests passed

2. The real signed exes from dry run 40245228873, extracted x64 and ARM64 .zips. The default pins pass:

cmake -DTASKSMACK_VERIFY_DIR=<extracted> -P cmake/VerifyWindowsSignatures.cmake
-- VerifyWindowsSignatures: TaskSmack.exe and TaskSmackHelper.exe are signed and timestamped by Matt Gradwohl, with certificates issued by CN=Microsoft ID Verified CS [AE]OC CA [0-9][0-9], O=Microsoft Corporation, C=US that carry 1.3.6.1.4.1.311.97.*

Other runs:

  • tools/verify-windows-signatures.ps1 -PackageRoot ... -ExpectedSigner 'Matt Gradwohl' -VerifyScript cmake/VerifyWindowsSignatures.cmake, release.yml's call: exit 0.
  • -DTASKSMACK_EXPECTED_ISSUER=CN=Contoso*: fails with "issued by CN=Microsoft ID Verified CS EOC CA 04, ...".
  • -DTASKSMACK_EXPECTED_EKU=1.2.3.*: fails and lists the three real EKUs.
  • The function also gives OK under Windows PowerShell 5.1, which the script falls back to when pwsh is missing.
  • An unsigned folder still fails with "isn't Authenticode-signed".
  • CPackProjectConfig.cmake.in configured with @ONLY sets all three values for the NSIS pre-build check.

Additional Notes

  • Backport: backport:0.9 is on this PR. The commit cherry-picks onto origin/release/0.9 with one modify/delete conflict, tools/verify-windows-signatures.ps1, because release/0.9 has no such file. Resolve it with git rm tools/verify-windows-signatures.ps1; everything else applies cleanly. release/0.9 keeps the check inline in release.yml: cmake "-DTASKSMACK_VERIFY_DIR=$pkg" "-DTASKSMACK_EXPECTED_SIGNER=..." -P repo/cmake/VerifyWindowsSignatures.cmake, with a sparse checkout of just that file. It gets the pins from the defaults with no workflow change. On the cherry-picked tree, the new test passes and that inline-style call prints OK on the real exes.
  • What the pin doesn't protect against: it is a name-and-EKU pin, like the CN check. It refuses a stray trusted test or internal certificate, which is the case the issue names. It doesn't refuse someone who can add a CA named Microsoft ID Verified CS EOC CA 04, O=Microsoft Corporation to the machine's trusted roots.

🤖 Generated with Claude Code

…er and extended key usage, so a trusted self-signed CN=Matt Gradwohl certificate no longer passes (#2990)

cmake/VerifyWindowsSignatures.cmake accepted any Valid, timestamped signature whose signer CN was
TASKSMACK_EXPECTED_SIGNER. It now also requires the signer certificate's issuer to be like
TASKSMACK_EXPECTED_ISSUER (default "CN=Microsoft ID Verified CS [AE]OC CA [0-9][0-9], O=Microsoft
Corporation, C=US"; the dry run's certificate is issued by "... EOC CA 04 ...") and one of its extended
key usages to be like TASKSMACK_EXPECTED_EKU (default 1.3.6.1.4.1.311.97.*; the certificate carries
1.3.6.1.4.1.311.97.1.0 and its identity validation's own 1.3.6.1.4.1.311.97.<id>). Both are stable
across the certificates' few-day rotation, and the issuer pattern survives a CA rollover. The CN and
same-subject checks stay.

The decision is now one PowerShell function, Test-TaskSmackSignatures, which the script can write out
(TASKSMACK_WRITE_SIGNATURE_CHECK) without checking anything. tools/test-verify-windows-signatures.ps1
(CTest VerifyWindowsSignaturesScript, pwsh-script) calls it with certificates made in memory with
CertificateRequest: a self-signed CN=Matt Gradwohl one, an internal CA's and a lookalike CA name fail,
the Azure-shaped chain passes, also across a rotation onto CA 05. Nothing is signed, stored or trusted.

Both new options are CMake cache options beside TASKSMACK_EXPECTED_SIGNER and reach CPack's pre-build
check through CPackProjectConfig.cmake.in; tools/verify-windows-signatures.ps1 takes optional
-ExpectedIssuer/-ExpectedEku and otherwise uses the script's defaults, so release.yml needs no change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mgradwohl mgradwohl added this to the v0.9.1 milestone Oct 26, 2026
@mgradwohl mgradwohl added bug Something isn't working release security agent:Windows Owned by the Windows Claude session (bug-fix conveyor) backport:0.9 Cherry-pick to release/0.9 after merge labels Oct 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions github-actions Bot added documentation Improvements or additions to documentation testing tooling Developer tooling, CI, editor config, and automation labels Oct 26, 2026
@mgradwohl
mgradwohl enabled auto-merge (squash) October 26, 2026 03:05
@mgradwohl

Copy link
Copy Markdown
Owner Author

Lane review: the issuer pattern CN=Microsoft ID Verified CS [AE]OC CA [0-9][0-9], O=Microsoft Corporation, C=US stays as it is. AOC and EOC are both Microsoft ID Verified code-signing issuing CAs that Artifact Signing uses, and the Artifact Signing EKU pin (1.3.6.1.4.1.311.97.*) has to match too. So a CA rollover inside the family can't break a release, and a stray trusted test or internal certificate still fails. Auto-merge on. The release/0.9 backport is cherry-picked by hand after the merge (one modify/delete conflict on tools/verify-windows-signatures.ps1, resolved with git rm).

…a bracket pair the link audit reads as a reference (#2990)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mgradwohl
mgradwohl merged commit 2bc934d into main Oct 26, 2026
24 checks passed
@mgradwohl
mgradwohl deleted the fix/2990-signer-issuer-pin branch October 26, 2026 05:16
@mgradwohl

Copy link
Copy Markdown
Owner Author

Backport failed for release/0.9, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release/0.9
git worktree add -d .worktree/backport-3000-to-release/0.9 origin/release/0.9
cd .worktree/backport-3000-to-release/0.9
git switch --create backport-3000-to-release/0.9
git cherry-pick -x 2bc934d75431324451290f84b55e64dde79a8693

mgradwohl added a commit that referenced this pull request Oct 26, 2026
…d EKU, so a trusted self-signed CN=Matt Gradwohl certificate no longer passes (#2990) (#3000) (#3021)

* fix(release): the Authenticode gate also pins Artifact Signing's issuer and extended key usage, so a trusted self-signed CN=Matt Gradwohl certificate no longer passes (#2990)

cmake/VerifyWindowsSignatures.cmake accepted any Valid, timestamped signature whose signer CN was
TASKSMACK_EXPECTED_SIGNER. It now also requires the signer certificate's issuer to be like
TASKSMACK_EXPECTED_ISSUER (default "CN=Microsoft ID Verified CS [AE]OC CA [0-9][0-9], O=Microsoft
Corporation, C=US"; the dry run's certificate is issued by "... EOC CA 04 ...") and one of its extended
key usages to be like TASKSMACK_EXPECTED_EKU (default 1.3.6.1.4.1.311.97.*; the certificate carries
1.3.6.1.4.1.311.97.1.0 and its identity validation's own 1.3.6.1.4.1.311.97.<id>). Both are stable
across the certificates' few-day rotation, and the issuer pattern survives a CA rollover. The CN and
same-subject checks stay.

The decision is now one PowerShell function, Test-TaskSmackSignatures, which the script can write out
(TASKSMACK_WRITE_SIGNATURE_CHECK) without checking anything. tools/test-verify-windows-signatures.ps1
(CTest VerifyWindowsSignaturesScript, pwsh-script) calls it with certificates made in memory with
CertificateRequest: a self-signed CN=Matt Gradwohl one, an internal CA's and a lookalike CA name fail,
the Azure-shaped chain passes, also across a rotation onto CA 05. Nothing is signed, stored or trusted.

Both new options are CMake cache options beside TASKSMACK_EXPECTED_SIGNER and reach CPack's pre-build
check through CPackProjectConfig.cmake.in; tools/verify-windows-signatures.ps1 takes optional
-ExpectedIssuer/-ExpectedEku and otherwise uses the script's defaults, so release.yml needs no change.



* docs(release): the expected-issuer row describes the pattern without a bracket pair the link audit reads as a reference (#2990)



---------


(cherry picked from commit 2bc934d)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent:Windows Owned by the Windows Claude session (bug-fix conveyor) backport:0.9 Cherry-pick to release/0.9 after merge bug Something isn't working documentation Improvements or additions to documentation release security testing tooling Developer tooling, CI, editor config, and automation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] VerifyWindowsSignatures accepts any trusted certificate whose CN is Matt Gradwohl

1 participant