Repository navigation
Azure Artifact Signing validation with publisher ID pinning - #44
Merged
Marc-André Moreau (mamoreau-devolutions) merged 6 commits intoOct 26, 2026
Conversation
…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>
Marc-André Moreau (mamoreau-devolutions)
merged commit Oct 26, 2026
d454298
into
master
36 checks passed
Marc-André Moreau (mamoreau-devolutions)
deleted the
claude/azure-artifact-signing-validation-ad783f
branch
October 26, 2026 19:02
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 inpsign-toolWindows 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
artifact_signingmodule inpsign-authenticode-trust: identity EKU decoding (hand-decoded; some arcs exceedconst-oidlimits), embedded pinned root checked against SHA-1/SHA-256, validity at the trusted timestamp (certificates live 72 h). The policy is aTrustVerifyPeOptionsfield applied by everytrust_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 cmdletsGet-PsignArtifactSigningPublisherId,Test-PsignArtifactSigning,Get-PsignSignature -ArtifactSigning*. CRL/OCSP signatures now accept RSA SHA-384/512, so--revocation-mode requireworks on Microsoft's SHA-384 CRLs.trust-verify-{script,msix,nupkg,vsix}commands.dotnet nuget sign.<EncodedTime>timestamps.System.IO.Packaging/ dotnet/sign layout, verified Success by .NETPackageDigitalSignatureManager.VerifySignatures, including timestamped ones.[Content_Types].xmlin every psign-signed VSIX.Set-PsignSignatureNuGet signatures embed their content and are classified as Author signatures bydotnet nuget verify.Behavior changes to review
--allow-loose-signing-cert).psign-tool codenow timestamps VSIX instead of rejecting--timestamp-url.Test plan
cargo fmt --all,cargo clippy --workspace --all-targets --all-features --lockedcargo test --workspace --locked --no-fail-fast: all pass except 5corpus_sign_verifytests that fail locally with WinVerifyTrust0x800B0109(untrusted root) because the Devolutions test CA isn't trusted on this machine; not checked againstmasterPowerShell/tests: 157 passedpsign-tool.exe) validates with its publisher ID in both modes (PSIGN_ARTIFACT_SIGNING_SAMPLEgated test), wrong IDs rejected; strict CRL revocation passesPackageDigitalSignatureManager; psign-signed NuGet classified as Author bydotnet nuget verifyci-unix, Windows)🤖 Generated with Claude Code