Signing Packages
|
This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The C# Reference bibliography lists the reference material consulted while preparing these pages. |
A signature proves who produced a package and that it was not modified afterwards. nuget.org adds its own
repository signature to every package; the publisher can add an author signature with a code-signing
certificate. Author signing is optional on nuget.org until an account registers a certificate — then it becomes
mandatory for that account’s packages. This page signs Ledgerline with a self-signed test certificate and
verifies it against the local feed, with .NET SDK 10.0.402 (NuGet 7.9.0). Signing with a real certificate and
registering it on nuget.org are "not run here". The consumer side — trust policies and signatureValidationMode — is on Signed Packages and Trust Policies.
|
Never commit a signing certificate’s private key ( |
Certificate requirements
-
A code-signing certificate (extended key usage
id-kp-codeSigning) with an RSA key of 2048 bits or more. -
An RFC 3161 timestamp from a timestamping certificate (
id-kp-timeStamping, RSA 2048+), so the signature stays valid after the certificate expires. -
For nuget.org: an author primary signature with exactly one valid timestamp, both certificates within validity, not revoked, and chaining to a root trusted by default on Windows — self-issued certificates are rejected. Public CAs include Certum, DigiCert, GlobalSign and SSL.com.
dotnet nuget sign
dotnet nuget sign (SDK 6.0.100+) takes the certificate in one of three ways:
-
--certificate-path— a PKCS#12 (.pfx) file with the private key, plus--certificate-password; -
--certificate-subject-nameor--certificate-fingerprint— a certificate in a store (--certificate-store-name,--certificate-store-location), the way to use a hardware token without exporting the key.
Fingerprints: SDKs before .NET 9 accept only SHA-1; .NET 9 added SHA-256/384/512 and warns about SHA-1 (NU3043); since
.NET 10 SHA-1 is an error. --timestamper names the CA’s RFC 3161 server, -o writes the signed copy elsewhere
(the default overwrites the input) and --overwrite replaces an existing signature. The other clients: nuget sign
(nuget.exe 4.6+, Windows) and Microsoft’s sign tool (dotnet/sign, in beta:
0.9.1-beta.26475.3, installed with dotnet tool install --global sign --prerelease), which signs files and packages
recursively with a key that never leaves Azure Key Vault (its README: "limited to RSA and Azure Key Vault"). Its package ID is easy to get wrong: versions 1.0.0 to 1.1.5 of
sign on nuget.org are an unrelated, unlisted "Assembly signing tool" by another author.
A test certificate round trip
The guide creates a test certificate with PowerShell’s New-SelfSignedCertificate. On Linux, OpenSSL does the same — but the first attempt, a plain code-signing certificate, failed:
openssl req -x509 -newkey rsa:3072 -sha256 -days 30 -nodes -keyout test.key -out test.crt \
-subj "/CN=Ledgerline Test Signing" -addext "extendedKeyUsage=codeSigning" -addext "keyUsage=critical,digitalSignature"
openssl pkcs12 -export -out test.pfx -inkey test.key -in test.crt -passout env:TEST_CERT_PASSWORD
dotnet nuget sign Ledgerline.1.0.0.nupkg --certificate-path test.pfx --certificate-password "$TEST_CERT_PASSWORD" \
--timestamper http://timestamp.digicert.com -o signed
warn : NU3018: UntrustedRoot: self-signed certificate
error: NU3018: InvalidBasicConstraints: key usage does not include certificate signing
error: NU3018: Certificate chain validation failed.
A self-signed certificate is its own root, so the chain builder requires it to be allowed to sign certificates. With
-addext "keyUsage=critical,digitalSignature,keyCertSign" -addext "basicConstraints=critical,CA:true" signing
succeeded with only warnings (the full PEM is printed too):
warn : NU3042: The following X.509 root certificate is untrusted because it is not present in the certificate bundle at .../sdk/10.0.402/trustedroots/codesignctl.pem. For more information, see documentation for NU3042.
warn : Subject: CN=Ledgerline Test Signing
warn : Fingerprint (SHA-256): 0D4D6C6862C740A180A9D5570A33A12E08FDB758BF659FDD4856032DD510F604
warn : NU3018: UntrustedRoot: self-signed certificate
On Linux and macOS the SDK uses its own root bundle (trustedroots/codesignctl.pem and timestampctl.pem), hence
NU3042. The signed package gained a 9,449-byte .signature.p7s. Verifying it fails until the certificate is
trusted:
$ dotnet nuget verify signed/Ledgerline.1.0.0.nupkg
Signature type: Author
Subject Name: CN=Ledgerline Test Signing
SHA256 hash: 0D4D6C6862C740A180A9D5570A33A12E08FDB758BF659FDD4856032DD510F604
Valid from: 10/23/2026 20:25:50 to 11/22/2026 20:25:50
error: NU3018: The author primary signature's signing certificate is not trusted by the trust provider.
Package signature validation failed.
Trusting the author — dotnet nuget trust author takes the package, reads its certificate and writes the
fingerprint into the given nuget.config — then verifying with that file:
dotnet nuget trust author LedgerlineTest signed/Ledgerline.1.0.0.nupkg --allow-untrusted-root --configfile trust.config
dotnet nuget verify signed/Ledgerline.1.0.0.nupkg --configfile trust.config -v n
<trustedSigners>
<author name="LedgerlineTest">
<certificate fingerprint="0D4D6C6862C740A180A9D5570A33A12E08FDB758BF659FDD4856032DD510F604" hashAlgorithm="SHA256" allowUntrustedRoot="true" />
</author>
</trustedSigners>
Timestamp: 10/23/2026 20:25:56
Verifying author primary signature's timestamp with timestamping service certificate:
Subject Name: CN=DigiCert SHA256 RSA4096 Timestamp Responder 2026 1, O="DigiCert, Inc.", C=US
...
Successfully verified package 'Ledgerline.1.0.0'.
Signing without --timestamper warns at sign time and again at verify time:
warn : NU3002: The '--timestamper' option was not provided. The signed package will not be timestamped. To learn more about this option, visit https://docs.microsoft.com/dotnet/core/tools
warn : NU3027: The signature should be timestamped to enable long-term signature validity after the certificate has expired.
With signatureValidationMode=require and no trusted signers in the file, verification fails with NU3034. A
restore with that configuration accepts only packages signed by a listed signer — including every nuget.org package,
which then needs a <repository> trust entry.
Registering the certificate on nuget.org (not run here)
-
Export the public certificate as a DER-encoded
.cer(never the.pfx). -
On nuget.org, Account settings (or Manage Organization > Edit Organization) > Certificates > Register new.
-
Push signed packages as usual.
Once an account has a registered certificate, every new version of the packages it solely owns must be author-signed with a registered certificate. nuget.org checks this during asynchronous validation after the upload: an unsigned or wrongly signed version is accepted by the push, then fails validation and is never published. Existing versions are unaffected. With several owners, signing is allowed when any owner has a certificate and required only when every owner has one, unless a required signer is set on Manage Packages. Removing a certificate makes new packages signed with it fail; one user may register several certificates, and several users the same one.
Repository signatures
nuget.org countersigns every package it accepts with its repository signature, author-signed or not, so consumers
can trust "anything from nuget.org" with a single <repository> entry in trustedSigners. A package’s author
signature is optional for consumers; the repository signature is what signatureValidationMode=require usually relies
on. The signed-packages reference still says that author signing "is only supported by nuget.exe on Windows at this
time" — outdated since dotnet nuget sign arrived in the .NET 6 SDK, as run above on Linux.
Common mistakes
-
Signing without a timestamp — the signature dies with the certificate (NU3027).
-
Registering a certificate on nuget.org before CI can sign — every later push fails validation.
-
A SHA-1 fingerprint — an error since .NET 10; use the SHA-256 one.
-
Re-signing an already signed package without
--overwrite— the command fails. -
Installing
signwithout--prerelease— the unrelated old package, not Microsoft’s tool.