Skip to content

Package Signatures Technical Details

Claire Novotny edited this page Feb 7, 2022 · 24 revisions

Status: Implemented

Issue

The discussion around this spec is tracked here - Package Signatures Technical Details #6250

NuGet Package Signatures Technical Specification

Introduction

This specification defines a standard for signing NuGet packages, describes how package signatures are embedded inside the NuGet package to which they apply, and outlines steps for generating and validating package signatures.

Conformance keywords

The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Definitions

  • Package reader: anything that attempts to read input as a NuGet package.
  • Package writer: anything that attempts to create or modify a NuGet package.

Abbreviations

  • CAdES: CMS Advanced Electronic Signatures [RFC 5126]
  • CMS: cryptographic message syntax [RFC 5652]
  • EKU: extended key usage [RFC 5280]
  • OID: object identifier [X.660]

Requirements

The general requirements are:

  1. A signed package MUST be consumable by package readers and writers that do not support package signing.
  2. A package signature MUST be embedded in a package file.
  3. A signed package MUST have exactly 1 primary signature. (Cosigning is not permitted.)
  4. The primary signature SHOULD be either an author signature or a repository signature.
  5. An author signature MUST be the primary signature. (To apply an author signature on an already signed package, the existing primary signature must first be removed.)

Repository signatures and repository countersignatures, are detailed in their own specification.

Out of scope

NuGet package signing will not support:

  • 64-bit ZIP files
  • ZIP files signed with other techniques

Description of a signed NuGet package

A NuGet package is based on the widely used ZIP file format.

A signed NuGet package MUST contain a package signature file, which is described in a later section.

A package reader that does not support signed package validation SHOULD NOT attach special significance to this file and MAY choose to ignore it.

Determination if a package is signed

An efficient test is required to determine if a package should be validated as a signed package. The test MUST NOT produce false negatives. The test MAY produce false positives; however, it is important that the test minimize them.

A package SHOULD be considered signed if and only if the package has a package signature file.

Passing of this test MUST NOT imply package integrity or signature validity.

The properties document

A properties document is a collection of properties (name-value pairs) and has the following ABNF format:

properties-document = header-section 1*section
header-section      = 1*property EOL
section             = 1*property EOL
EOL                 = CRLF / LF
property            = name ":" value EOL
name                = 1*namechar
namechar            = ALPHA / DIGIT / . / - / "/"
value               = 1*valuechar

valuechar is defined as any UTF-8 character excluding NUL, CR, and LF. See RFC 5234 section B.1 for additional definitions. A properties document MUST be UTF-8 encoded.

The package signature file

The package signature file is a file in a package with the following full, case-sensitive file name (e.g.: as returned by the System.IO.Compression.ZipArchiveEntry.FullName property):

.signature.p7s

The package signature file name MUST be encoded with the default ZIP character encoding set IBM code page 437. Within the package ZIP file the package signature file's file type MUST be a regular file and MUST NOT be anything other than a regular file (e.g.: a directory, symbolic link, etc.). The package signature file MUST NOT be compressed; it MUST be uncompressed (stored).

Package signature file content

The package signature file content MUST be a single CMS SignedData as encoded by System.Security.Cryptography.Pkcs.SignedCms.Encode(). The CMS MUST have exactly 1 SignerInfo in its SignedData.signerInfos collection. This SignerInfo is termed the primary signature or primary signer. The CMS content (SignedData.encapContentInfo.eContent) MUST be a properties document. Property names (name) and values (value) MUST be case-sensitive.

The properties document's header-section MUST contain the following property:

  • Version: This property defines the package signature format version. The value MUST be 1.

Package readers and writers compliant with this specification MUST fail a signature verification or signing operation if the version is anything other than this value.

The properties document's first section MUST contain the following property:

  • Hash-Algorithm-Oid-Hash:
    • The actual property name is not Hash-Algorithm-Oid-Hash. The actual name substitutes Hash-Algorithm-Oid with the OID of a supported hash algorithm. Actual property names include 2.16.840.1.101.3.4.2.1-Hash, 2.16.840.1.101.3.4.2.2-Hash, and 2.16.840.1.101.3.4.2.3-Hash.
    • The property value MUST be a Base64-encoded hash of the entire sequence of bytes of the unsigned package using the hash algorithm specified by Hash-Algorithm-Oid.

An example properties document is:

Version:1

2.16.840.1.101.3.4.2.1-Hash:l76DnQSKN9AKihpevTkEaojKaLaZqmcmj9q76TwdZthfChmtVPLtC5ijl6ydsk1sMBMdF2Lcg8FKj6y1U4VMKg==

Signatures and countersignatures

Signatures and countersignatures are classified through use of the commitment-type-indication attribute [RFC 5126].

  • The id-cti-ets-proofOfOrigin commitment type is reserved for author signatures. An author signature MUST include this commitment type.
  • The id-cti-ets-proofOfReceipt commitment type is reserved for repository signatures and repository countersignatures. Repository signatures and repository countersignatures MUST include this commitment type.

A signature or countersignature MUST NOT specify both the author and repository commitment types. A signature or countersignature which is neither author nor repository MUST NOT use the aforementioned commitment types.

Author signatures

If an author signature is present it MUST be the primary signature. A signature or countersignature in any position other than the primary signature MUST NOT use the author signature commitment type. An author signature MAY satisfy the requirements of any CAdES [RFC 5126] signature but MUST satisfy CAdES-BES [RFC 5126] requirements with the following additional requirements:

  • The commitment-type-indication attribute [RFC 5126] MUST be present. The attribute MUST include the id-cti-ets-proofOfOrigin commitment type.
  • The signing-certificate-v2 attribute [RFC 5126] MUST be present. The hash algorithm used in this attribute MUST be a supported hash algorithm.
  • The signing-time attribute [RFC 5652] MUST be present.

Repository signatures and countersignatures

The full requirements for repository signatures and countersignatures will be detailed in a separate specification.

Timestamps

Signatures and countersignatures SHOULD include the signature-time-stamp attribute [RFC 5126] to provide long-term validity even after the signing certificate's validity period has expired. If a signature or countersignature lacks a timestamp, then that signature or countersignature SHOULD be considered expired and subsequently ignored if the current time according to the package reader is outside of the signing certificate's validity period. If specifically the primary signature's signing certificate has expired and the primary signature either lacks a timestamp or has a timestamp which fails to satisfy policy requirements (e.g.: trust) not defined here, then a package reader MAY still use information in the signed CMS to verify package integrity but MUST otherwise treat the signature as expired and the package as unsigned. An exception to signature expiration is that package readers MAY choose to treat timestamp signatures as non-expiring.

A timestamp signing certificate MUST satisfy minimum requirements and the timestamp MUST use a supported hash algorithm.

Certificates

A signed CMS certificates collection (SignedData.certificates) MUST contain all certificates, including the root certificate, from the complete chain that was successfully built at signing time for a signer's signing certificate. Note that a timestamp signature is also a signed CMS, and this requirement applies to timestamp signatures as well. If a timestamp signature does not already satisfy this requirement, the timestamp requestor MUST build the timestamp signing certificate's chain and add the certificates to the timestamp's signed CMS before adding the timestamp to the signature or countersignature being timestamped.

Certificate minimum requirements

A NuGet package signing certificate MUST meet the following minimum requirements:

  1. The certificate MUST be valid for the id-kp-codeSigning purpose [RFC 5280 section 4.2.1.12].
  2. The certificate MUST have an RSA public key length of 2048 bits or higher.

A timestamping certificate MUST meet the following minimum requirements:

  1. The certificate MUST be valid for the id-kp-timeStamping purpose [RFC 5280 section 4.2.1.12].
  2. The certificate MUST have an RSA public key length of 2048 bits or higher.

At signing time, a certificate MUST be within its validity period according to the package writer and MUST NOT be revoked. At validation time, the certificate's revocation status SHOULD be rechecked; however, package readers MAY fail open if revocation status is unavailable (e.g.: a CRL is inaccessible).

Certificates MUST NOT have the lifetime signing EKU (1.3.6.1.4.1.311.10.3.13).

Supported algorithms

The following hash algorithms MUST be supported:

Hash Algorithm Hash-Algorithm-Oid
SHA-2-256 2.16.840.1.101.3.4.2.1
SHA-2-384 2.16.840.1.101.3.4.2.2
SHA-2-512 2.16.840.1.101.3.4.2.3

The following RSA PKCS #1 v1.5 signature algorithms MUST be supported:

Signature Algorithm OID
sha256WithRSAEncryption 1.2.840.113549.1.1.11
sha384WithRSAEncryption 1.2.840.113549.1.1.12
sha512WithRSAEncryption 1.2.840.113549.1.1.13

The RSASSA-PKCS1-v1_5 padding mode [RFC 8017] is used to help ensure compatibility across all platforms (https://github.com/dotnet/corefx/blob/master/Documentation/architecture/cross-platform-cryptography.md#rsa).

Over time algorithms may be deprecated and replaced with newer, more secure algorithms. An example is SHA-1's deprecation in favor of SHA-2. Package readers and writers that support package signing MAY block acceptance and creation, respectively, of new packages signed with a deprecated algorithm. Older package readers and writers that support package signing SHOULD treat packages signed with a newer, unsupported algorithm as:

  • unsigned for read operations. (Do not block installation of such a package, but also do not attempt to validate the signature.)
  • signed for write operations. (An older writer can still remove an existing signature.)

Signing a package

The following is a recommended outline for author signing a package. Unless otherwise indicated, package writers SHOULD err on the side of caution and treat unexpected failures as fatal.

  1. Determine if the package is signed.
    1. If the package is signed and the sign operation should not overwrite an existing signature, fail the sign operation with a message indicating that the package is already signed.
    2. If the package is signed and the sign operation should overwrite an existing signature, remove the existing signature and continue with step 2.
    3. If the package is not signed, continue with step 2.
  2. Verify that the signing certificate satisfies minimum requirements.
  3. Verify that the hash, signature, and timestamp hash algorithms are supported.
  4. Generate a package signature file.
    1. Create an author signature.
    2. Obtain a timestamp for the author signature.
      1. Verify that the timestamp signing certificate satisfies minimum requirements.
      2. Verify that the timestamp signature and hash algorithms are supported.
    3. Extend the author signature to CAdES-T [RFC 5126].
    4. Encode the author signature CMS SignedData.
    5. Write the encoded author signature to file.
  5. Add the package signature file as an uncompressed (stored) file to the package being signed.

Unsigning a package

If the package contains a package signature file, remove it. The package is no longer signed.

Validating a signed package

The following is a recommended outline for verifying a signed package. Unless otherwise indicated, package readers SHOULD err on the side of caution and treat unexpected validation failures as fatal and block restoration of the package as a signed package. A client policy MAY permit continued restoration of such a package as an unsigned package; however, client policies are outside the scope of this specification. Examples of such failures include:

  • failure to decode the package signature file contents as a signed CMS fails
  • failure to read expected ZIP structures
  • failure parse a properties document
  • failure to validate package integrity

Package readers MAY impose more stringent requirements during signature validation. In the case of signed packages downloaded by a plugin, some steps below MAY be delegated to the plugin to fully or partially implement.

  1. Determine if the package is signed. If it is not signed, stop further validation.
  2. Verify that the package signature file is uncompressed and a regular file.
  3. Verify that the package signature format is supported.
    1. Decode the contents of the package signature file as a signed CMS.
    2. Parse the signed CMS content as a properties document.
    3. Verify that the package signature format version as indicated by the Version property value is supported.
  4. Verify package integrity.
    1. Verify that the hash algorithm indicated by the Hash-Algorithm-Oid-Hash property is supported. If it is not supported, stop further validation and treat the package as unsigned.
    2. Compute a new hash using the same hash algorithm from step 4.i.
      1. Open a read-only binary stream for the package file.
      2. Compute a hash over the stream while accounting for package changes introduced by adding the package signature file to the package. For example:
        1. Exclude the package signature file's local file and central directory headers from hashing.
        2. For each local file header after the package signature file's local file header, instead of hashing the value of the central directory header field in the signed package, compute and hash the value for the unsigned package.
          • relative offset of local header
        3. In the end of central directory record, instead of hashing the values of the following fields in the signed package, compute and hash their values for the unsigned package.
          • size of the central directory
          • offset of start of central directory with respect to the starting disk number
    3. Verify that the expected and actual package hashes are identical.
  5. Verify primary signature validity and trust.
    1. Verify that the signed CMS has exactly one SignerInfo.
    2. Check a signature-time-stamp attribute on the primary signature.
      1. If the timestamp exists, continue with the next step. Otherwise, store the local machine's current UTC time in variables TimeStampLowerLimit and TimeStampUpperLimit and continue with step 6.
      2. Verify that the timestamp hash in TSTInfo.messageImprint matches the hash of the signature to which the timestamp applies.
      3. Verify that the timestamp hash and signature algorithms are supported.
      4. Verify that the timestamp certificate satisfies minimum requirements.
      5. Using certificates in timestamp's SignedData.certificates collection, build a chain for the timestamp signing certificate with the id-kp-timeStamping EKU.
      6. Verify the signing-certificate [RFC 2634] or signing-certificate-v2 [RFC 5126] attribute.
      7. Retrieve the timestamp's generalized time from TSTInfo.genTime.
      8. Retrieve the timestamp's accuracy. If the accuracy is explicitly specified in TSTInfo.accuracy, use that value. If the accuracy is not explicitly specified and TSTInfo.policy is the baseline time-stamp policy [RFC 3628], use an accuracy of 1 second. Otherwise, use an accuracy of 0.
      9. Calculate the timestamp range using the lower and upper limits per RFC 3161 section 2.4.2 and store the limits in variables TimeStampLowerLimit and TimeStampUpperLimit, respectively.
  6. Verify primary signature validity.
    1. Verify that the signature algorithm is supported.
    2. Verify that the signing certificate satisfies minimum requirements.
    3. Verify signature validity (e.g.: SignerInfo.CheckSignature(verifySignatureOnly: true)).
    4. Verify that the time range from TimeStampLowerLimit to TimeStampUpperLimit timestamp is entirely within the certificate's validity period. If the time range is entirely within the certificate's validity period, continue to the next step. Otherwise, the signature is invalid and package readers MUST ignore the signature. Package readers MAY allow subsequent use of the package as an unsigned package. For example, if a signed package has only an author signature and this step places the time range after the certificate's validity period, then the author signature validity has expired, and the package may be still be installed but only as though the package had no signature.
    5. Using certificates in the signer's SignedData.certificates collection, build a chain for the signing certificate with the id-kp-codeSigning EKU.
    6. Verify the signing-certificate-v2 [RFC 5126] attribute, if present.
  7. If no failures have been encountered, treat the package as a valid signed package.

References

  • RFC 2119: Key words for use in RFCs to Indicate Requirement Levels
  • RFC 2634: Enhanced Security Services for S/MIME
  • RFC 3161: Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP)
  • RFC 3628: Policy Requirements for Time-Stamping Authorities (TSAs)
  • RFC 5035: Enhanced Security Services (ESS) Update: Adding CertID Algorithm Agility
  • RFC 5126: CMS Advanced Electronic Signatures (CAdES)
  • RFC 5234: Augmented BNF for Syntax Specifications: ABNF
  • RFC 5280: Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
  • RFC 5652: Cryptographic Message Syntax (CMS)
  • RFC 8174: Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words
  • APPNOTE.TXT: ZIP File Format Specification from PKWARE, Inc., version 6.3.4 (2014)
  • X.660: Information technology – Procedures for the operation of object identifier registration authorities: General procedures and top arcs of the international object identifier tree

Contributing

What's Being Worked On?

Check out the proposals in the accepted & proposed folders on the repository, and active PRs for proposals being discussed today.

Common Problems

Clone this wiki locally