Signing Packages

This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its dotnet CLI) and NuGet 7.9, as published in the MSBuild, .NET and NuGet documentation on learn.microsoft.com, which is the reference these pages are written and verified against. Material about .NET 11 / NuGet 8.0 is still in preview and is always flagged as such — never presented as baseline.

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 (.pfx, .p12, .key) or its password to a repository, an artifact or a log. Keep it in a hardware token, a cloud key vault or a CI secret, and sign in a dedicated release job.

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-name or --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)

  1. Export the public certificate as a DER-encoded .cer (never the .pfx).

  2. On nuget.org, Account settings (or Manage Organization > Edit Organization) > Certificates > Register new.

  3. 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 sign without --prerelease — the unrelated old package, not Microsoft’s tool.