-
Notifications
You must be signed in to change notification settings - Fork 252
Package Signatures Technical Details
Status: In review
The discussion around this spec is tracked here - Package Signatures Technical Details #6250
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.
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.
- Package reader: anything that attempts to read input as a NuGet package.
- Package writer: anything that attempts to create or modify a NuGet package.
- CAdES: CMS Advanced Electronic Signatures [RFC 5126]
- CMS: cryptographic message syntax [RFC 5652]
- EKU: extended key usage [RFC 5280]
- OID: object identifier [X.660]
The general requirements are:
- A signed package MUST be consumable by package readers and writers that do not support package signing.
- A package signature MUST be embedded in a package file.
- A signed package MUST have exactly 1 primary signature. (Cosigning is not permitted.)
- The primary signature SHOULD be either an author signature or a repository signature.
- 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.)
The package signing feature roadmap consists of multiple rollout stages. Repository signatures and repository countersignatures, which are planned after the first rollout stage, will be detailed in a later specification. They are minimally covered in this specification to make reservations for them. Package writers SHOULD NOT generate repository signatures or repository countersignatures until their specification has been finalized.
NuGet package signing will not support:
- 64-bit ZIP files
- ZIP files signed with other techniques
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.
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.
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 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).
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 be1
.
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 include2.16.840.1.101.3.4.2.1-Hash
,2.16.840.1.101.3.4.2.2-Hash
, and2.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.
- The actual property name is not
An example properties document is:
Version:1
2.16.840.1.101.3.4.2.1-Hash:l76DnQSKN9AKihpevTkEaojKaLaZqmcmj9q76TwdZthfChmtVPLtC5ijl6ydsk1sMBMdF2Lcg8FKj6y1U4VMKg==
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.
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 theid-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.
The full requirements for repository signatures and countersignatures will be detailed in a separate specification.
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 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. 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.
A timestamp signing certificate MUST satisfy minimum requirements and the timestamp MUST use a supported hash algorithm.
A NuGet package signing certificate MUST meet the following minimum requirements:
- The certificate MUST be valid for the
id-kp-codeSigning
purpose [RFC 5280 section 4.2.1.12]. - The certificate MUST have an RSA public key length of 2048 bits or higher.
A timestamping certificate MUST meet the following minimum requirements:
- The certificate MUST be valid for the
id-kp-timeStamping
purpose [RFC 5280 section 4.2.1.12]. - 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 not 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).
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.)
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.
-
Determine if the package is signed.
- 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.
- If the package is signed and the sign operation should overwrite an existing signature, remove the existing signature and continue with step 2.
- If the package is not signed, continue with step 2.
- Verify that the signing certificate satisfies minimum requirements.
- Verify that the hash, signature, and timestamp hash algorithms are supported.
- Generate a package signature file.
- Create an author signature.
- Obtain a timestamp for the author signature.
- Verify that the timestamp signing certificate satisfies minimum requirements.
- Verify that the timestamp signature and hash algorithms are supported.
- Extend the author signature to CAdES-T [RFC 5126].
- Encode the author signature CMS
SignedData
. - Write the encoded author signature to file.
- Add the package signature file as an uncompressed (stored) file to the package being signed.
If the package contains a package signature file, remove it. The package is no longer signed.
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.
- Determine if the package is signed. If it is not signed, stop further validation.
- Verify that the package signature file is uncompressed and a regular file.
- Verify that the package signature format is supported.
- Decode the contents of the package signature file as a signed CMS.
- Parse the signed CMS content as a properties document.
- Verify that the package signature format version as indicated by the
Version
property value is supported.
- Verify package integrity.
- 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. - Compute a new hash using the same hash algorithm from step 4.i.
- Open a read-only binary stream for the package file.
- Compute a hash over the stream while accounting for package changes introduced by adding the package signature file to the package. For example:
- Exclude the package signature file's local file and central directory headers from hashing.
- 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
- 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
- Verify that the expected and actual package hashes are identical.
- Verify that the hash algorithm indicated by the Hash-Algorithm-Oid
- Verify primary signature validity and trust.
- Verify that the signed CMS has exactly one
SignerInfo
. - Check a
signature-time-stamp
attribute on the primary signature.- If the timestamp exists, continue with the next step. Otherwise, store the local machine's current UTC time in variables
TimeStampLowerLimit
andTimeStampUpperLimit
and continue with step 6. - Verify that the timestamp hash in
TSTInfo.messageImprint
matches the hash of the signature to which the timestamp applies. - Verify that the timestamp hash and signature algorithms are supported.
- Verify that the timestamp certificate satisfies minimum requirements.
- Using certificates in timestamp's
SignedData.certificates
collection, build a chain for the timestamp signing certificate with theid-kp-timeStamping
EKU. - Verify the
signing-certificate
[RFC 2634] orsigning-certificate-v2
[RFC 5126] attribute. - Retrieve the timestamp's generalized time from
TSTInfo.genTime
. - Retrieve the timestamp's accuracy. If the accuracy is explicitly specified in
TSTInfo.accuracy
, use that value. If the accuracy is not explicitly specified andTSTInfo.policy
is the baseline time-stamp policy [RFC 3628], use an accuracy of 1 second. Otherwise, use an accuracy of 0. - Calculate the timestamp range using the lower and upper limits per RFC 3161 section 2.4.2 and store the limits in variables
TimeStampLowerLimit
andTimeStampUpperLimit
, respectively.
- If the timestamp exists, continue with the next step. Otherwise, store the local machine's current UTC time in variables
- Verify that the signed CMS has exactly one
- Verify primary signature validity.
- Verify that the signature algorithm is supported.
- Verify that the signing certificate satisfies minimum requirements.
- Verify signature validity (e.g.:
SignerInfo.CheckSignature(verifySignatureOnly: true)
). - Verify that the time range from
TimeStampLowerLimit
toTimeStampUpperLimit
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. - Using certificates in the signer's
SignedData.certificates
collection, build a chain for the signing certificate with theid-kp-codeSigning
EKU. - Verify the
signing-certificate-v2
[RFC 5126] attribute, if present.
- If no failures have been encountered, treat the package as a valid signed package.
- 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
Check out the proposals in the accepted
& proposed
folders on the repository, and active PRs for proposals being discussed today.