Support NuGet package signing using Azure Artifact Signing on Linux#10
Support NuGet package signing using Azure Artifact Signing on Linux#10Bradley Grainger (bgrainger) wants to merge 4 commits into
Conversation
Enable portable Artifact Signing responses to be consumed on Linux by accepting PEM/DER/base64/PKCS#7 signingCertificate payloads and selecting the actual signing cert from PKCS#7 signer info or an unambiguous leaf certificate bag. Make NuGet package signing produce verifiable packages by normalizing ZIP metadata, embedding CMS id-data content, and using the standard id-aa-timeStampToken timestamp attribute while preserving Authenticode timestamp behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add the NuGet author signed-attribute profile needed for publisher metadata: signing-time, commitment-type proof-of-origin, and signing-certificate-v2. Remote signing now obtains the signer certificate before hashing CMS signed attributes so the signing-certificate-v2 ESSCertIDv2 value is part of the signed payload. This makes Linux-generated NuGet signatures classify as Author signatures in NuGet tooling, which enables NuGetPackageExplorer to display the Publisher UI for packages signed through Azure Artifact Signing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
The second commit address this problem: NuGet Package Explorer was not showing "Publisher:" metadata as displayed in this screenshot. Previously, only the "Signature ✅" line was showing; now the other metadata (with a link to a Windows certificate dialog) appears. Reverse-engineered by pointing GPT-5.5 at the https://github.com/NuGetPackageExplorer/NuGetPackageExplorer source code.
AI Text BelowFollow-up: added NuGet author signed attributes required once packages are classified as publisher/author signatures. The final package signature now includes signing-time, id-smime-aa-ets-commitmentType with proofOfOrigin, and signing-certificate-v2/ESSCertIDv2. A redacted live Azure Artifact Signing run produced a package that |
|
Bradley Grainger (@bgrainger) are the changes on this branch making it work for a successful replacement of your current code signing tools? You could remove the draft flag from this PR. There are some code formatting checks to fix for the CI |
I have not tested it E2E but that's the goal. The current signing code is here: https://github.com/Faithlife/FaithlifeBuild/blob/20d69035d606ccc4e346512802f51fb3b0152d27/src/Faithlife.Build/DotNetBuild.cs#L551-L590 My intention is to either detect Linux and install psign, or use psign unconditionally on all platforms in that code, then migrate the signing arguments to be compatible with psign. One point of friction is that signtool integrates directly with Azure.Identity (see https://github.com/dotnet/sign/blob/0ddd0b9a9be5d5be986e94024642f36d0e2c1a31/docs/artifact-signing-integration.md?plain=1#L7) and psign would need me to do Plan:
|
The NuGet author-signature path needs a helper that assembles CMS SignedData from explicit content, digest, signer material, signature bytes, detached-content behavior, and prebuilt signed attributes. Allow Clippy's argument-count lint for this narrowly scoped helper so the portable-clippy CI job passes without changing behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Bradley Grainger (@bgrainger) a way to do the full testing would be to temporarily modify your real workflow to pull your branch, build psign-tool from source and attempt using it to code sign your nuget without actually publishing it. this is how I got Azure Key Vault signing confirmed to be working by code signing psign-tool with it - there were a couple of things to fix that hadn't been caught until I tried code signing for real. |
This was basically what I did. That's how I got the screenshot from NuGet Package Explorer; I iterated on it until it (appeared to) produced the exact same output as a build on Windows. I'm pretty confident that with this PR's changes I can modify Faithlife.Build to sign with psign and get the correct output; I know I need to modify it to run: The only thing that hasn't been tested is running it on |
Allow the shared PKCS#7 id-data helper to exceed clippy's argument-count threshold, matching the earlier targeted suppression on the related CMS helper and unblocking the portable clippy job without changing behavior. Also extend the generated test certificate lifetime in cli_pe_digest so the timestamped verification cases still pass when they intentionally validate at tomorrow-noon UTC. This keeps the CI workflow stable while preserving the same end-to-end coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary This imports the useful work from #10 and cleans it up for a reviewable implementation of portable/Linux NuGet package signing with Azure Artifact Signing. - parses Artifact Signing `signingCertificate` payloads as DER, PEM, nested base64, or PKCS#7 certificate bundles, selecting the actual signer instead of relying on certificate order - produces NuGet-compatible `.signature.p7s` CMS with attached `id-data`, NuGet author attributes, and PKCS#9 RFC3161 timestamp attributes while preserving Authenticode timestamp behavior for companion signatures - normalizes NuGet ZIP central-directory metadata so Unix-created packages do not carry host/external attributes that NuGet rejects - expands deterministic tests for certificate parsing, ZIP normalization, local/package signing, timestamp OID behavior, and Artifact Signing/server-backed flows ## Validation - `cargo fmt --all --check` - `cargo clippy --workspace --all-targets --locked -- -D warnings` - `cargo test --workspace --locked --quiet` --------- Co-authored-by: Bradley Grainger <bradley.grainger@logos.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Excellent 👍 I have cleaned up and fixed your changes to pass the CI checks in #11 I'll make a new release in the coming days |
|
Bradley Grainger (@bgrainger) I just released 0.4.0, let me know if it works for you when you have the chance |
|
Marc-André Moreau (@mamoreau-devolutions) We are so close! I got psign working with this command-line: It was only after doing so that I noticed that the NuGet package itself is timestamped, but the DLLs embedded in it are signed but not timestamped. This is likely an oversight in my last PR so will be repro-ing this E2E and opening a new PR. |

Disclaimer
I understand none of this code; it was produced entirely by GitHub Copilot using GPT-5.5.
The point of this PR is to demonstrate that with these changes, psign-tool could produce a correctly-signed NuGet package using Azure Artifact Signing under Linux (Ubuntu 24.04 on WSL2). I'm opening this PR in case having this information is helpful for producing a new version of psign that has this support built-in; I'm not expecting this to be merged as-is (although it's fine if you do).
I am interested in getting this scenario working, and am willing to produce more targeted fixes (with AI) if required. (Note that I am a C# Windows developer not a Linux Rust developer.)
AI Generated Text Below
Summary
This makes the portable/Linux signing path work with Azure Artifact Signing responses and produce NuGet packages that
dotnet nuget verifyaccepts. It keeps the existing Authenticode timestamp behavior, while using the CMS timestamp attribute NuGet expects for package signatures.Before
A live Azure Artifact Signing response could not be consumed reliably on Linux:
signingCertificatefield was assumed to be a PEM/DER X.509 certificate. In practice it can be base64 text wrapping a PKCS#7 certificate bag, so bothportable sign-peandcode --mode portablefailed while parsing the returned certificate payload.SignerInfo, so choosing the first embedded certificate is not safe. The observed bag order could put a root/intermediate certificate before the short-lived leaf signing certificate, producing signatures attributed to the wrong certificate.NU3005because the central-directoryversion made by/ external attributes carried Unix file metadata.NU3000because the package.signature.p7sused detached CMS content. NuGet package signatures need theid-datacontent embedded.id-aa-timeStampToken(1.2.840.113549.1.9.16.2.14).Changes
psign-sip-digestthat accepts PEM, DER, base64-wrapped data, and PKCS#7 certificate bags.SignerInfowhen present, and for certificate-only bags select an unambiguous leaf/code-signing candidate instead of assuming certificate-set order.codecommand..signature.p7s.sign_pkcs7_id_datachoose detached vs attached CMS content per caller: NuGet uses attachedid-data, while App Installer/AuthentiCode-style companion signatures remain detached.id-aa-timeStampTokenfor NuGet package signatures, while preserving the Microsoft Authenticode timestamp OID for Authenticode paths.SignerInfocertificate bags, NuGet ZIP metadata normalization, and NuGet timestamp OID behavior.Testing
Automated:
Manual/live, with private Azure Artifact Signing configuration redacted and no service URLs included:
psign-toolon Linux from this branch.psign-tool code --mode portablewith Azure Artifact Signing and RFC3161 timestamping.dotnet nuget verify -v detailed; verification succeeded and NuGet recognized the timestamp.psign-tool portable verify-pe.Follow-up: NuGet publisher metadata
A later commit adds the NuGet author signed attributes needed for package tooling to classify the primary package signature as an author/publisher signature:
signingTime,id-smime-aa-ets-commitmentTypewithproofOfOrigin, andsigningCertificateV2/ESSCertIDv2. This is what allows NuGetPackageExplorer to show the Publisher UI instead of only reportingSignature: Valid.Additional validation after that commit: