Skip to content

Azure Artifact Signing validation with publisher ID pinning - #44

Merged
Marc-André Moreau (mamoreau-devolutions) merged 6 commits into
masterfrom
claude/azure-artifact-signing-validation-ad783f
Oct 26, 2026
Merged

Marc-André Moreau (mamoreau-devolutions) merged 6 commits into
masterfrom
claude/azure-artifact-signing-validation-ad783f

Conversation

@mamoreau-devolutions

@mamoreau-devolutions Marc-André Moreau (mamoreau-devolutions) commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds Azure Artifact Signing validation: extract the publisher ID (the certificate profile identity EKU suffix, 1.3.6.1.4.1.311.97.<id>), and verify signatures against the pinned Microsoft Identity Verification Root Certificate Authority 2020 (signer and RFC 3161 timestamp chains), optionally pinned to specific publisher IDs. The same check runs in psign-tool Windows and portable modes and in the PowerShell module, across PE/WinMD, CAB, MSI, WIM/ESD, catalogs, ZIP, PowerShell and WSH scripts, MSIX/AppX, NuGet, VSIX, and detached signatures.

Full design: docs/artifact-signing-validation.md.

Commits

  1. Publisher ID extraction and pinned-root validation. New artifact_signing module in psign-authenticode-trust: identity EKU decoding (hand-decoded; some arcs exceed const-oid limits), embedded pinned root checked against SHA-1/SHA-256, validity at the trusted timestamp (certificates live 72 h). The policy is a TrustVerifyPeOptions field applied by every trust_verify_* format. psign-tool verify --artifact-signing[-publisher-id|-private-trust-root|-allow-missing-timestamp] (Windows mode runs it after WinVerifyTrust, forcing /pa), psign-tool portable artifact-signing-publisher-id, and the PowerShell cmdlets Get-PsignArtifactSigningPublisherId, Test-PsignArtifactSigning, Get-PsignSignature -ArtifactSigning*. CRL/OCSP signatures now accept RSA SHA-384/512, so --revocation-mode require works on Microsoft's SHA-384 CRLs.
  2. WSH, MSIX/AppX, NuGet, and VSIX trust.
    • New trust-verify-{script,msix,nupkg,vsix} commands.
    • NuGet package hash is computed over the exact unsigned bytes, matching dotnet nuget sign.
    • A standards-based OPC XMLDSig verifier for VSIX: C14N, manifest coverage and content types, and <EncodedTime> timestamps.
  3. Standards-compliant VSIX and NuGet signers.
    • psign's VSIX signatures now use the System.IO.Packaging / dotnet/sign layout, verified Success by .NET PackageDigitalSignatureManager.VerifySignatures, including timestamped ones.
    • VSIX RFC 3161 timestamping is now available.
    • Fixes a bug that corrupted [Content_Types].xml in every psign-signed VSIX.
    • Set-PsignSignature NuGet signatures embed their content and are classified as Author signatures by dotnet nuget verify.

Behavior changes to review

  • VSIX trust verification now enforces the code-signing EKU like other formats (existing tests pass --allow-loose-signing-cert).
  • Tampered VSIX/NuGet packages are reported with the new verifier's errors (e.g. "not covered by the signature", ".signature.p7s must be the single, last central directory record").
  • VSIX/NuGet packages signed by earlier psign releases are handled for compatibility only (legacy VSIX layout inspects but fails trust; detached NuGet CMS still verifies).
  • psign-tool code now timestamps VSIX instead of rejecting --timestamp-url.

Test plan

  • cargo fmt --all, cargo clippy --workspace --all-targets --all-features --locked
  • cargo test --workspace --locked --no-fail-fast: all pass except 5 corpus_sign_verify tests that fail locally with WinVerifyTrust 0x800B0109 (untrusted root) because the Devolutions test CA isn't trusted on this machine; not checked against master
  • Pester PowerShell/tests: 157 passed
  • Real Artifact Signing sample (Devolutions psign-tool.exe) validates with its publisher ID in both modes (PSIGN_ARTIFACT_SIGNING_SAMPLE gated test), wrong IDs rejected; strict CRL revocation passes
  • Synthetic Private Trust fixtures (JS, MSIX, NuGet, VSIX) validate end to end; forged Artifact Signing EKUs rejected
  • psign-signed VSIX verified by .NET PackageDigitalSignatureManager; psign-signed NuGet classified as Author by dotnet nuget verify
  • CI (ci-unix, Windows)

🤖 Generated with Claude Code

…lidation

Artifact Signing certificates are renewed daily (72-hour validity), so the
durable identity is the certificate-profile EKU under 1.3.6.1.4.1.311.97.
Decode it into a publisher ID and add a validation policy that pins the
signer and RFC 3161 timestamp chains to Microsoft Identity Verification Root
Certificate Authority 2020 instead of trusting AuthRoot.

- psign-authenticode-trust: new artifact_signing module (hand-decoded EKU
  arcs, embedded pinned root checked against SHA-1/SHA-256, Public Trust,
  Public Trust Test, Private Trust and CI Policy kinds). The policy is a
  TrustVerifyPeOptions field applied by every trust_verify_* format through
  one shared per-PKCS#7 helper; trust_verify_path dispatches by extension.
- CRL/OCSP: accept RSA PKCS#1 v1.5 with SHA-384/SHA-512 (Microsoft PKI CRLs
  are sha384WithRSAEncryption) and require the CRL outer signature algorithm
  to match TBSCertList.signature, so --revocation-mode require works.
- psign-tool verify --artifact-signing[-publisher-id|-private-trust-root|
  -allow-missing-timestamp]: same check in Windows mode (after WinVerifyTrust,
  forced to /pa) and portable mode; portable trust-verify-* take the same
  flags; portable artifact-signing-publisher-id extracts the ID.
- Portable core / FFI: artifact_signing request options, decoded identity and
  verification result in signature responses, and
  psign_core_get_artifact_signing_identity.
- PowerShell: Get-PsignArtifactSigningPublisherId, Test-PsignArtifactSigning,
  and Get-PsignSignature -ArtifactSigning* parameters.
- Docs (docs/artifact-signing-validation.md and related), CLI matrix, Rust
  CLI tests (both modes; real-sample test gated on
  PSIGN_ARTIFACT_SIGNING_SAMPLE), Pester tests, and public fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Extend portable trust verification, and with it the Azure Artifact Signing
policy, to the formats that previously had no trust check. The policy runs
the same way in psign-tool Windows and portable modes and in the PowerShell
module.

- WSH (.js/.jse/.vbs/.vbe/.wsf): wsh_digest_report feeds the shared script
  trust path.
- MSIX/AppX and bundles: msix_verified_signature returns the PKCS#7 and the
  verified APPX blob; AXCI presence must now match on both sides.
- NuGet: package hash over the exact unsigned bytes (the last-entry
  .signature.p7s is cut out byte for byte, as NuGet does), replacing the ZIP
  rewrite that never matched dotnet nuget sign packages. Embedded signature
  content is required to match; psign's detached CMS is still accepted.
- VSIX: new standards-based OPC XMLDSig verifier (quick-xml DOM with
  inclusive/exclusive C14N): SignedInfo SignatureValue, #id object and
  Manifest part digests (raw, C14N, RelationshipTransform), full part
  coverage, [Content_Types].xml content types, and the dotnet/sign
  <EncodedTime> RFC 3161 timestamp over the SignatureValue octets.
- Artifact Signing policy split into identity, timestamp-instant, and anchor
  helpers with a certificate-based entry point for XMLDSig; standalone
  RFC 3161 token verification factored out of the CMS path.
- psign-tool: portable trust-verify-{script,msix,nupkg,vsix}; verify routes
  explicit trust and --artifact-signing for these formats; Windows mode skips
  WinVerifyTrust only for NuGet/VSIX (no Windows SIP).
- Portable core / PowerShell: trust and Artifact Signing for these formats,
  VSIX inspection with the standard verifier (legacy psign layout still
  reported), NuGet hash binding in inspection, WSH signer extraction.
- Synthetic Private Trust fixtures (script-generated, keys discarded) and
  Rust, CLI, and Pester tests for both modes; docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VSIX: replace the legacy layout (direct part references in SignedInfo,
SignedInfo signed without its inherited namespace, _rels/.rels unsigned) with
the OPC XMLDSig layout that System.IO.Packaging / PackageDigitalSignatureManager
and dotnet/sign produce: an idPackageObject Manifest referencing every part
(as it will exist after embedding, with ContentType from [Content_Types].xml)
plus SignatureTime, a SignedInfo reference to it, and inclusive C14N. The
template is written directly in canonical form, so all signing providers and
the split prehash/from-signature flow keep signing VsixSignatureTemplate's
signed_info bytes. Output is verified Success by .NET VerifySignatures.

- RFC3161 <EncodedTime> timestamps (dotnet/sign format) in vsix-sign
  (--timestamp-url/--timestamp-digest), psign-tool code, and
  Set-PsignSignature -TimestampServer.
- Fix expand_self_closing_xml_root, which turned the first self-closing child
  of [Content_Types].xml into a bogus </Types> and corrupted every VSIX psign
  signed; add a rels content-type default for the origin relationships part.
- vsix-verify-signature(-xml) use the standard OPC verifier; the old reference
  checker is kept as verify_legacy_signature_reference_xml for inspecting
  packages signed by earlier releases.

NuGet: Set-PsignSignature now embeds the signature content in the CMS (it was
detached) and, for local keys, adds the NuGet author attributes, so
`dotnet nuget verify` classifies it as an Author signature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Print only the Azure Artifact Signing publisher ID (one line per signature)
so it can be pinned without JSON parsing; fails when the signer has no
identity EKU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without the azure-kv-sign / artifact-signing-rest features SigningProvider
has only the Local variant, so `if let SigningProvider::Local` was
irrefutable and failed portable clippy (-D warnings). Use a match whose
remote-provider arm is feature-gated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit d454298 into master Oct 26, 2026
36 checks passed
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) deleted the claude/azure-artifact-signing-validation-ad783f branch October 26, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant