Skip to content

Support NuGet package signing using Azure Artifact Signing on Linux#10

Draft
Bradley Grainger (bgrainger) wants to merge 2 commits into
Devolutions:masterfrom
bgrainger:fix-linux-azure-nuget-signing
Draft

Support NuGet package signing using Azure Artifact Signing on Linux#10
Bradley Grainger (bgrainger) wants to merge 2 commits into
Devolutions:masterfrom
bgrainger:fix-linux-azure-nuget-signing

Conversation

@bgrainger
Copy link
Copy Markdown

@bgrainger Bradley Grainger (bgrainger) commented May 22, 2026

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 verify accepts. 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:

  • The signingCertificate field was assumed to be a PEM/DER X.509 certificate. In practice it can be base64 text wrapping a PKCS#7 certificate bag, so both portable sign-pe and code --mode portable failed while parsing the returned certificate payload.
  • Some certificate bags have no 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.
  • Once package signing reached the ZIP-writing stage, NuGet rejected the package with NU3005 because the central-directory version made by / external attributes carried Unix file metadata.
  • After clearing ZIP metadata, NuGet rejected the package with NU3000 because the package .signature.p7s used detached CMS content. NuGet package signatures need the id-data content embedded.
  • Timestamping used the Microsoft Authenticode RFC3161 unsigned attribute OID. NuGet did not treat that as a package-signature timestamp; it expects id-aa-timeStampToken (1.2.840.113549.1.9.16.2.14).

Changes

  • Add a shared Artifact Signing certificate parser in psign-sip-digest that accepts PEM, DER, base64-wrapped data, and PKCS#7 certificate bags.
  • Resolve signer certificates from SignerInfo when present, and for certificate-only bags select an unambiguous leaf/code-signing candidate instead of assuming certificate-set order.
  • Reuse the shared parser from both the portable digest CLI and the code command.
  • Normalize NuGet ZIP central-directory host OS and external attributes when removing or embedding .signature.p7s.
  • Let sign_pkcs7_id_data choose detached vs attached CMS content per caller: NuGet uses attached id-data, while App Installer/AuthentiCode-style companion signatures remain detached.
  • Add a CMS/PKCS#9 RFC3161 timestamp helper and use id-aa-timeStampToken for NuGet package signatures, while preserving the Microsoft Authenticode timestamp OID for Authenticode paths.
  • Add regression coverage for PKCS#7 certificate bags, nested/base64-wrapped Artifact Signing certificate payloads, no-SignerInfo certificate bags, NuGet ZIP metadata normalization, and NuGet timestamp OID behavior.

Testing

Automated:

cargo test -p psign-digest-cli --features artifact-signing-rest --locked --quiet
cargo test -p psign --test code_command --locked --quiet
cargo test -p psign-sip-digest --locked --quiet
cargo test -p psign-opc-sign --locked --quiet

Manual/live, with private Azure Artifact Signing configuration redacted and no service URLs included:

  • Built psign-tool on Linux from this branch.
  • Signed a PE/DLL payload using an Azure Artifact Signing account/profile; the signature used the expected short-lived organization code-signing leaf certificate rather than a Microsoft root/intermediate certificate.
  • Signed a NuGet package containing an inner DLL using psign-tool code --mode portable with Azure Artifact Signing and RFC3161 timestamping.
  • Verified the signed package with dotnet nuget verify -v detailed; verification succeeded and NuGet recognized the timestamp.
  • Extracted and verified the inner DLL signature with psign-tool portable verify-pe.

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>
@bgrainger Bradley Grainger (bgrainger) marked this pull request as draft May 22, 2026 22:18
@bgrainger Bradley Grainger (bgrainger) changed the title Support Linux Azure NuGet package signing Support NuGet package signing using Azure Artifact Signing on Linux May 22, 2026
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>
@bgrainger
Copy link
Copy Markdown
Author

Bradley Grainger (bgrainger) commented May 22, 2026

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.

image

AI Text Below

Follow-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 dotnet nuget verify --all reports as Signature type: Author with the expected publisher subject, which is the metadata path NuGetPackageExplorer uses to show the Publisher UI.

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