NuGet Package Signing: Trust Boundaries from Build to Deployment
Establish verifiable trust boundaries for NuGet packages using author signatures and repository countersignatures. Covers minimal design, allowlist configuration, pipeline enforcement, and failure-mode mitigations.
15 Sept 2025, 02:26 UTC

Requirements: What Problem Are We Solving
Supply chain attacks targeting package managers have made signature verification a baseline requirement for production .NET workloads. The goal is not merely to sign packages but to establish verifiable trust boundaries: who authored this package, has it been modified since signing, and did it pass through an approved feed. NuGet's X.509-based signing (author signature) and repository countersigning address these questions, but only when configured with explicit trust policies rather than default behaviors.
Smallest Suitable Design
The minimal viable implementation requires three components working together:
- Build-time author signing — The package producer signs at
dotnet packusing a certificate stored in an HSM or Azure Key Vault. File-based.pfxis acceptable only for local development. - Feed-side repository countersigning — The internal feed (Azure Artifacts, NuGet.org, MyGet, BaGet) applies a second signature with its own short-lived certificate, proving the package passed through an approved distribution point.
- Consumer enforcement — Downstream projects configure
nuget.configtorequireboth signatures and maintain an allowlist of trusted certificate fingerprints.
No external timestamp service is strictly required if you use an RFC 3161 timestamp from a trusted TSA (e.g., DigiCert, Sectigo). The timestamp proves the signature existed at a specific time, surviving certificate expiration.
Build Pipeline Integration
# In your build script (PowerShell/Bash)
dotnet pack \
-p:PackageCertificateThumbprint="$AUTHOR_CERT_THUMBPRINT" \
-p:PackageCertificateTimestamper="http://timestamp.digicert.com" \
-o ./artifacts
The thumbprint references a certificate whose private key never leaves the HSM/Key Vault. The timestamper URL adds ~200-800ms network latency per package; plan for this in your build SLA.
Trust and Data Boundaries
NuGet does not validate certificate chains to a root CA by default. You must explicitly configure trusted signers in nuget.config. This is a deliberate design choice: it shifts trust decisions to the consumer organization rather than relying on public CA hierarchies.
Author vs. Repository Certificates
| Property | Author Certificate | Repository Certificate |
|---|---|---|
| Lifetime | Long-lived (1-3 years) | Short-lived (30-90 days) |
| Storage | HSM / Azure Key Vault (offline root preferred) | Managed by feed operator |
| Rotation | Annual with 30-day overlap | Automated by feed |
| Compromise blast radius | All packages signed by that key | Packages published during key validity |
Separating these certificates limits blast radius. If the repository key is compromised, existing author signatures remain valid. If the author key is compromised, the repository countersignature on new packages still requires feed operator cooperation.
Allowlist Configuration
<configuration>
<packageSignatureRequirement>require</packageSignatureRequirement>
<trustedSigners>
<author name="MyOrg">
<certificate fingerprint="SHA256_HEX" hashAlgorithm="SHA256" allowUntrustedRoot="false" />
</author>
<repository name="InternalFeed">
<certificate fingerprint="SHA256_HEX" hashAlgorithm="SHA256" allowUntrustedRoot="false" />
</repository>
</trustedSigners>
</configuration>
Use allowUntrustedRoot="false" to require the certificate chain to anchor in a trusted root store. Set to true only for self-signed certificates in controlled environments, and document the exception.
Operational Checks
Enforce verification in CI/CD pipeline gates, not just at restore time. The dotnet nuget verify command provides scriptable validation:
dotnet nuget verify ./artifacts/MyPackage.1.2.3.nupkg \
--certificate-fingerprint SHA256_HEX \
--certificate-fingerprint-algorithm sha256
Critical automation caveat: dotnet nuget verify exits 0 on success and non-zero on failure, but does not distinguish missing signature from invalid signature in the exit code. Parse stdout/stderr for actionable alerts:
# Example parsing in Bash
if ! dotnet nuget verify $PKG --certificate-fingerprint $FP --certificate-fingerprint-algorithm sha256 2>&1 | tee verify.log; then
if grep -q "No signature found" verify.log; then
echo "::error::Package missing signature"
elif grep -q "Signature validation failed" verify.log; then
echo "::error::Package signature invalid"
fi
exit 1
fi
Monitoring Validation Failures
- Track NuGet CLI exit codes in pipeline telemetry.
- Log signature validation failures with package ID, version, and fingerprint mismatch details.
- Alert on any
NU3037(signature requirement not met) during restore — this indicates either an unsigned package slipped through or the allowlist is stale.
Failure Modes and Mitigations
| Failure Mode | Impact | Mitigation |
|---|---|---|
| Expired timestamp token | Valid signature becomes unverifiable after TSA token expiry (typically 10+ years) | Use TSA with long-term tokens; re-sign and re-timestamp packages before token expiry |
| Revoked author certificate | All packages signed with that certificate fail verification | Allowlist by subject + key usage (not thumbprint alone); maintain 30-day overlap during rotation |
| Feed compromise without repository key compromise | Attacker publishes malicious package with valid author signature but no repository countersignature | Require both signatures in consumer config; repository key stays with feed operator |
| Repository key compromise | Attacker can countersign arbitrary packages | Short certificate lifetime (30-90 days); automated rotation; monitor for unexpected countersignatures |
| Missing feed countersignature support | File shares, simple HTTP feeds cannot apply repository signature | Use only supported feeds (Azure Artifacts, NuGet.org, MyGet, BaGet) for production |
Conditions That Would Change This Design
The current X.509 model is stable but not final. Plan for migration when:
- Sigstore/cosign keyless signing reaches GA in NuGet (experimental in 6.8+). This replaces certificate management with identity-based signing (OIDC + transparency log).
- SLSA provenance attestations become a regulatory or customer requirement. Signatures prove integrity; SLSA proves build process integrity. They complement, not replace, each other.
- Feed operator mandates repository countersigning on all packages. NuGet.org already does this; internal feeds should follow.
- FIPS 140-2 validated HSM becomes mandatory for key storage. Azure Key Vault Managed HSM and AWS CloudHSM meet this; software keystores do not.
Verification Checklist for New Pipelines
- Create test package:
dotnet pack -p:PackageCertificateThumbprint=<thumb> -p:PackageCertificateTimestamper=http://timestamp.digicert.com -o ./testpkg - Verify signature:
dotnet nuget verify ./testpkg/<package>.nupkg --certificate-fingerprint <sha256> --certificate-fingerprint-algorithm sha256 - Inspect signature details:
nuget verify -signatures ./testpkg/<package>.nupkg(NuGet CLI 5.x+) - Test enforcement: add
<packageSignatureRequirement>require</packageSignatureRequirement>tonuget.config, attempt restore of unsigned package — expectNU3037. - Validate trusted signer config: add allowlist entry, verify restore succeeds only for matching certificate.
- Simulate key rotation: generate new certificate, add to allowlist with overlap, sign new package version, verify restore works for both old and new packages during overlap window.
Limitations to Accept
- Signature verification adds 2-5 seconds per package at build plus timestamping latency. This is non-negotiable for production.
- SemVer 2.0 version ranges in
PackageReferencedo not interact with signature verification. A signed package with a known vulnerability will still restore if the version constraint allows it. Use separate vulnerability scanning (e.g.,dotnet list package --vulnerable). - Repository countersignature requires feed support. Simple file shares or custom HTTP endpoints without signing capability break the trust chain.
Diagram Labels
- Author signs package (HSM/Key Vault)
- Feed applies repository countersignature
- Consumer verifies both signatures against allowlist
- Pipeline gate runs dotnet nuget verify
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.