diff --git a/signature-envelope-jws.md b/signature-envelope-jws.md index f2eb4824..d55895b9 100644 --- a/signature-envelope-jws.md +++ b/signature-envelope-jws.md @@ -39,7 +39,7 @@ Example of Notary v2 payload ```jsonc { - "subject": { + "targetArtifact": { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", "size": 16724, diff --git a/signature-specification.md b/signature-specification.md index 611f7b7c..363e7bf6 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -86,7 +86,7 @@ Notary v2 supports the following envelope formats: Notary v2 payload is a JSON document with media type `application/vnd.cncf.notary.payload.v1+json` and has following properties. -- `subject` : Required property whose value is the descriptor of the target artifact manifest that is being signed. Both [OCI descriptor][oci-descriptor] and [ORAS artifact descriptors][artifact-descriptor] are supported. +- `targetArtifact` : Required property whose value is the descriptor of the target artifact manifest that is being signed. Both [OCI descriptor][oci-descriptor] and [ORAS artifact descriptors][artifact-descriptor] are supported. - Descriptor MUST contain `mediaType`, `digest`, `size` fields. - Descriptor MAY contain `annotations` and if present it MUST follow the [annotation rules][annotation-rules]. Notary v2 uses annotations for storing both Notary specific and user defined metadata. The prefix `io.cncf.notary` in annotation keys is reserved for use in Notary v2 and MUST NOT be used outside this specification. - Descriptor MAY contain `artifactType` field for artifact manifests, or the `config.mediaType` for `oci.image` based manifests. @@ -95,7 +95,7 @@ Notary v2 payload is a JSON document with media type `application/vnd.cncf.notar ```jsonc { - "subject": { + "targetArtifact": { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", "size": 16724, @@ -108,7 +108,7 @@ Notary v2 payload is a JSON document with media type `application/vnd.cncf.notar ```jsonc { - "subject": { + "targetArtifact": { "mediaType": "sbom/example", "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", "size": 32654 @@ -159,6 +159,7 @@ A use case for this feature is for a plugin publisher to address security bug in See [Guidelines for Notary v2 Implementors](#guidelines-for-notary-v2-implementors) for options to handle these attributes during signature verification. + ### Unsigned Attributes These attributes are considered unsigned with respect to the signing key that generates the signature. These attributes are typically signed by a third party (e.g. CA, TSA). @@ -167,6 +168,127 @@ These attributes are considered unsigned with respect to the signing key that ge - **Timestamp signature** : An OPTIONAL counter signature which provides [authentic timestamp](#signing-time)e.g. Time Stamp Authority (TSA) generated timestamp signature. Only [RFC3161](ietf-rfc3161) compliant TimeStampToken are currently supported. - **Signing Agent**: An OPTIONAL claim that provides the identifier of the software (e.g. Notation) that produced the signature on behalf of the user. It is an opaque string set by the software that produces the signature. It's intended primarily for diagnostic and troubleshooting purposes, this attribute is unsigned, the verifier MUST NOT validate formatting, or fail validation based on the content of this claim. The suggested format is one or more tokens of the form `{id}/{version}` containing identifier and version of the software, seperated by spaces. E.g. “notation/1.0.0”, “notation/1.0.0 com.example.nv2plugin/0.8”. +### Supported Signature Envelopes + +#### JWS JSON Serialization + +In JWS JSON Serialization ([RFC7515](https://datatracker.ietf.org/doc/html/rfc7515)), data is stored as either claims or headers (protected and unprotected). +Notary v2 uses JWS JSON Serialization for the signature envelope with some additional constraints on the structure of claims and headers. + +Unless explicitly specified as OPTIONAL, all fields are required. +Also, there shouldn’t be any additional fields other than ones specified in JWSPayload, ProtectedHeader, and UnprotectedHeader. + +**JWSPayload a.k.a. Claims**: +Notary v2 is using one private claim (`notary`) and two public claims (`iat` and `exp`). +An example of the claim is described below + +```json +{ + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", + "size": 16724, + "annotations": { + "key1": "value1", + "key2": "value2", + ... + } + }, + "iat": 1234567891000, + "exp": 1234567891011 +} +``` + +The payload contains the subject manifest and other attributes that have to be integrity protected. + +- **`subject`**(*descriptor*): A REQUIRED top-level node consisting of the manifest that needs to be integrity protected. + Please refer [Payload](#payload) section for more details. +- **`iat`**(*number*): The REQUIRED property Issued-at(`iat`) identifies the time at which the signature was issued. +- **`exp`**(*number*): This OPTIONAL property contains the expiration time on or after which the signature must not be considered valid. + +To leverage JWS claims validation functionality already provided by libraries, we have defined `iat`, `exp` as top-level nodes. + +**ProtectedHeaders**: Notary v2 supports only three protected headers: `alg`, `cty`, and `crit`. + +```json +{ + "alg": "RS256", + "cty": "application/vnd.cncf.notary.v2.jws.v1", + "crit":["cty"] +} +``` + +- **`alg`**(*string*): This REQUIRED property defines which algorithm was used to generate the signature. + JWS needs an algorithm(`alg`) to be present in the header, so we have added it as a protected header. +- **`cty`**(*string*): The REQUIRED property content-type(cty) is used to declare the media type of the secured content(the payload). + This will be used to version different variations of JWS signature. + The supported value is `application/vnd.cncf.notary.v2.jws.v1`. +- **`crit`**(*array of strings*): This REQUIRED property lists the headers that implementation MUST understand and process. + The value MUST be `["cty"]`. + +**UnprotectedHeaders**: Notary v2 supports only two unprotected headers: timestamp and x5c. + +```json +{ + "timestamp": "", + "x5c": ["", "", ""] +} +``` + +- **`timestamp`** (*string*): This OPTIONAL property is used to store time stamp token. + Only [RFC3161]([rfc3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2)) compliant TimeStampToken are supported. +- **`x5c`** (*array of strings*): This REQUIRED property contains the list of X.509 certificate or certificate chain([RFC5280](https://datatracker.ietf.org/doc/html/rfc5280)) corresponding to the key used to digitally sign the JWS. + The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. + +- **`timestamp`** (*string*): This OPTIONAL property is used to store time stamp token. + Only [RFC3161]([rfc3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2)) compliant TimeStampToken are supported. +- **`x5c`** (*array of strings*): This REQUIRED property contains the list of X.509 certificate or certificate chain([RFC5280](https://datatracker.ietf.org/doc/html/rfc5280)) corresponding to the key used to digitally sign the JWS. + The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. + +**Signature**: In JWS signature is calculated by combining JWSPayload and protected headers. +The process is described below: + +1. Compute the Base64Url value of ProtectedHeaders. +1. Compute the Base64Url value of JWSPayload. +1. Build message to be signed by concatenating the values generated in step 1 and step 2 using '.' +`ASCII(BASE64URL(UTF8(ProtectedHeaders)) ‘.’ BASE64URL(JWSPayload))` +1. Compute the signature on the message constructed in the previous step by using the signature algorithm defined in the corresponding header element: `alg`. +1. Compute the Base64Url value of the signature produced in the previous step. + This is the value of the signature property used in the signature envelope. + +**Signature Envelope**: The final signature envelope comprises of Claims, ProtectedHeaders, UnprotectedHeaders, and signature. + +Since Notary v2 restricts one signature per signature envelope, the compliant signature envelope MUST be in flattened JWS JSON format. + +```json +{ + "payload": "", + "protected": "", + "header": { + "timestamp": "", + "x5c": ["", "", ""] + }, + "signature": "Base64Url( sign( ASCII( . )))" +} +``` + +**Implementation Constraints**: Notary v2 implementation MUST enforce the following constraints on signature generation and verification: + +1. `alg` header value MUST NOT be `none` or any symmetric-key algorithm such as `HMAC`. +1. `alg` header value MUST be same as that of signature algorithm identified using signing certificate's public key algorithm and size. +1. `alg` header values for various signature algorithms: + | Signature Algorithm | `alg` Param Value | + | ------------------------------- | ----------------- | + | RSASSA-PSS with SHA-256 | PS256 | + | RSASSA-PSS with SHA-384 | PS384 | + | RSASSA-PSS with SHA-512 | PS512 | + | ECDSA on secp256r1 with SHA-256 | ES256 | + | ECDSA on secp384r1 with SHA-384 | ES384 | + | ECDSA on secp521r1 with SHA-512 | ES512 | +1. Signing certificate MUST be a valid codesigning certificate. +1. Only JWS JSON flattened format is supported. + See 'Signature Envelope' section. + ## Signature Algorithm Requirements The implementation MUST support the following set of algorithms: @@ -195,6 +317,16 @@ The signing certificate's public key algorithm and size MUST be used to determin ### Certificate Requirements +The **signing certificate** MUST meet the following minimum requirements: + +- The keyUsage extension MUST be present and MUST be marked critical. + The bit positions for digitalSignature MUST be set ([RFC-5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3)). +- The extKeyUsage extension MUST be present and its value MUST be id-kp-codeSigning ([RFC-5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.12)). +- If the basicConstraints extension is present, the cA field MUST be set false ([RFC-5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9)). +- The certificate MUST abide by the following key length restrictions: +- For RSA public key, the key length MUST be 2048 bits or higher. + - For ECDSA public key, the key length MUST be 256 bits or higher. + The codesigning and timestamping certificates MUST meet the following requirements. These requirements are validated both at signature generation time and signature verification time, and are applied to the certificate chain in the signature envelope. These validations are independent of certificate chain validation against a trust store. #### Root and Intermediate CA Certificates @@ -231,6 +363,7 @@ The `keyUsage` extension MUST be present and MUST be marked critical. Bit positi 1. Any certificate in the certificate chain MUST NOT use SHA1WithRSA and ECDSAWithSHA1 signatures. 1. Only Basic Constraints, Key Usage, and Extended Key Usage extensions of X.509 certificates are honored. For rest of the extensions, Notary MUST fail open i.e. they MUST NOT be evaluated or honored. 1. The certificates in the certificate chain MUST be valid at signing time. Notary MUST NOT enforce validity period nesting, i.e the validity period for a given certificate may not fall entirely within the validity period of that certificate's issuer certificate. +1. In the absence of an Authentic Timestamp, each and every certificate in the certificate chain i.e. signing certificate, intermediate certificates, and the root certificate must be valid i.e. not expired at the time of signature verification. ## FAQ diff --git a/specs/plugin-extensibility.md b/specs/plugin-extensibility.md index c2d2ed38..c8129ac7 100644 --- a/specs/plugin-extensibility.md +++ b/specs/plugin-extensibility.md @@ -199,25 +199,19 @@ This interface targets plugins that integrate with providers of basic cryptograp 5. Execute the plugin with `get-plugin-metadata` command 1. If plugin supports capability `SIGNATURE_GENERATOR.RAW` 1. Execute the plugin with `describe-key` command, set `request.keyId` and the optional `request.pluginConfig` to corresponding values associated with signing key `keyName` in `config.json`. - 2. Generate the payload to be signed - * For [JWS](../signature-envelope-jws.md) envelope format - 1. Create the JWS protected headers collection and set `alg` to value corresponding to `describe-key.response.keySpec` as per [signature algorithm selection](../signature-specification.md#algorithm-selection). - 2. Create the Notary v2 Payload (JWS Payload) as defined [here](../signature-specification.md#payload). - 3. The *payload to sign* is then created as - `ASCII(BASE64URL(UTF8(ProtectedHeaders)) ‘.’ BASE64URL(JWSPayload))` - * For [COSE](../signature-envelope-cose.md) envelope format - 1. Create the COSE protected headers collection and set `alg` to value corresponding to `describe-key.response.keySpec` as per [signature algorithm selection](../signature-specification.md#algorithm-selection). - 2. Create the Notary v2 Payload as defined [here](../signature-specification.md#payload). - 3. The *payload to sign* is then created as the CBOR object [Sig_structure](../signature-envelope-cose.md#signature). + 2. Generate the payload to be signed for [JWS](../signature-specification.md#supported-signature-envelopes) envelope format. + 1. Create the JWS protected headers collection and set `alg` to value corresponding to `describe-key.response.keySpec` as per [signature algorithm selection](../signature-specification.md#algorithm-selection). + 2. Create the Notary v2 Payload (JWS Payload) as defined [here](../signature-specification.md#payload). + 3. The *payload to sign* is then created as - `ASCII(BASE64URL(UTF8(ProtectedHeaders)) ‘.’ BASE64URL(JWSPayload))` 3. Execute the plugin with `generate-signature` command. 1. Set `request.keyId` and the optional `request.pluginConfig` to corresponding values associated with signing key `keyName` in `config.json`. - 2. Set `request.payload` as base64 encoded *payload to sign* - * The JWS *payload to sign* is double encoded, this is a shortcoming of using plugin contract with JSON encoding. + 2. Set `request.payload` as base64 encoded *payload to sign* (the JWS *payload to sign* is double encoded, this is a shortcoming of using plugin contract with JSON encoding). 3. Set `keySpec` to value returned by `describe-key` command in `response.keySpec`, and `hashAlgorithm` to hash algorithm corresponding to the key spec, as per [signature algorithm selection](../signature-specification.md#algorithm-selection). The algorithm specified in `hashAlgorithm` MUST be used by the plugin to hash the payload (`request.payload`) as part of signature generation. 4. Validate the generated signature, return an error if any of the checks fails. 1. Check if `response.signingAlgorithm` is one of [supported signing algorithms](../signature-specification.md#algorithm-selection). 2. Check that the plugin did not modify `request.payload` before generating the signature, and the signature is valid for the given payload. Verify the hash of the `request.payload` against `response.signature`, using the public key of signing certificate (leaf certificate) in `response.certificateChain` along with the `response.signingAlgorithm`. This step does not include certificate chain validation (certificate chain leads to a trusted root configured in Notation's Trust Store), or revocation check. 3. Check that the `response.certificateChain` conforms to [Certificate Requirements](../signature-specification.md#certificate-requirements). - 5. Assemble the signature envelope using `response.signature`, `response.signingAlgorithm` and `response.certificateChain`. Notation may also generate and include timestamp signature in this step. + 5. Assemble the JWS Signature envelope using `response.signature`, `response.signingAlgorithm` and `response.certificateChain`. Notation may also generate and include timestamp signature in this step. 6. Generate a signature manifest for the given signature envelope. 2. Else if, plugin supports capability `SIGNATURE_GENERATOR.ENVELOPE` *(covered in next section)* 3. Return an error @@ -289,7 +283,8 @@ This command is used to generate the raw signature for a given payload. "hashAlgorithm" : "SHA_256" | "SHA_384" | "SHA_512", // Payload to sign, this is base64 encoded - "payload" : "" + "payload" : "", + } ``` @@ -297,7 +292,7 @@ This command is used to generate the raw signature for a given payload. *pluginConfig* : Optional field for plugin configuration. For details, see [Plugin Configuration section](#plugin-configuration). -*keySpec* : Required field that has one of following [supported key types](../signature-specification.md#algorithm-selection) - `RSA_2048`, `RSA_3072`, `RSA_4096`, `EC_256`, `EC_384`, `EC_521`. Specifies the key type and size for the key. +*keySpec* : Required field that has one of following [supported key types](../signature-specification.md#algorithm-selection) - `RSA_2048`, `RSA_3072`, `RSA_4096`, `EC_256`, `EC_384`, `EC_512`. Specifies the key type and size for the key. *hashAlgorithm* : Required field that specifies Hash algorithm corresponding to the signature algorithm determined by `keySpec` for the key. @@ -342,16 +337,13 @@ This interface targets plugins that in addition to signature generation want to 1. Determine if the registered key uses a plugin 1. Execute the plugin with `get-plugin-metadata` command 1. If plugin supports capability `SIGNATURE_GENERATOR.ENVELOPE` - 1. Execute the plugin with `generate-envelope` command. Set `request.keyId` and the optional `request.pluginConfig` to corresponding values associated with signing key `keyName` in `config.json`. Set `request.payload` to base64 encoded [Notary v2 Payload](../signature-specification.md#payload), `request.payloadType` to `application/vnd.cncf.notary.payload.v1+json` and `request.signatureEnvelopeType` to a pre-defined type. - * Pre-defined types for `request.signatureEnvelopeType`: - * JWS: `application/vnd.cncf.notary.v2.jws.v1` - * COSE: `application/vnd.cncf.notary.v2.cose.v1` + 1. Execute the plugin with `generate-envelope` command. Set `request.keyId` and the optional `request.pluginConfig` to corresponding values associated with signing key `keyName` in `config.json`. Set `request.payload` to base64 encoded [Notary v2 Payload](../signature-specification.md#payload), `request.payloadType` to `application/vnd.cncf.notary.payload.v1+json` and `request.signatureEnvelopeType` to a pre-defined type (`application/vnd.cncf.notary.v2.jws.v1` for JWS). 2. `response.signatureEnvelope` contains the base64 encoded signature envelope, value of `response.signatureEnvelopeType` MUST match `request.signatureEnvelopeType`. 3. Validate the generated signature, return an error if of the checks fails. 1. Check if `response.signatureEnvelopeType` is a supported envelope type and `response.signatureEnvelope`'s format matches `response.signatureEnvelopeType`. 2. Check if the signing algorithm in the signature envelope is one of [supported signing algorithms](../signature-specification.md#algorithm-selection). - 3. Check that the [`targetArtifact` descriptor](../signature-specification.md#payload) in payload of `response.signatureEnvelope` matches `request.payload`. Plugins MAY append additional annotations but MUST NOT replace/override existing descriptor attributes and annotations. - 4. Check that `response.signatureEnvelope` can be verified using the public key and signing algorithm specified in the signing certificate, which is embedded as part of certificate chain in `response.signatureEnvelope`. This step does not include certificate chain validation (certificate chain leads to a trusted root configure in Notation), or revocation check. + 3. Check that the [`targetArtifact` descriptor](../signature-specification.md#payload) in JWSPayload in `response.signatureEnvelope` matches `request.payload`. Plugins MAY append additional annotations but MUST NOT replace/override existing descriptor attributes and annotations. + 4. Check that `response.signatureEnvelope` can be verified using the public key and signing algorithm specified in the signing certificate, which is embedded as part of certificate chain in `response.signatureEnvelope` . This step does not include certificate chain validation (certificate chain leads to a trusted root configure in Notation), or revocation check. 5. Check that the certificate chain in `response.signatureEnvelope` confirm to [Certificate Requirements]. 4. Generate a signature manifest for the given signature envelope, and append `response.annotations` to manifest annotations. 2. Else if plugin supports capability `SIGNATURE_GENERATOR.RAW` *(covered in previous section)* @@ -389,7 +381,7 @@ All request attributes are required. *pluginConfig* : Optional field for plugin configuration. For details, see [Plugin Configuration section](#plugin-configuration). -*signatureEnvelopeType* - defines the type of signature envelope expected from the plugin. As Notation clients need to be updated in order to parse and verify new signature formats, the default signature format can only be changed with new major version releases of Notation. Users however can opt into using an updated signature format supported by Notation, by passing an optional parameter. +*signatureEnvelopeType* - defines the type of signature envelope expected from the plugin. As Notation clients need to be updated in order to parse and verify new signature formats, the default signature format can only be changed with new major version releases of Notation. Users however can opt into using an updated signature format supported by Notation, by passing an optional parameter. e.g. `notation sign $IMAGE --key {key-name} --signatureFormat {some-new-format}` *Response* diff --git a/trust-store-trust-policy-specification.md b/trust-store-trust-policy-specification.md index af8625f5..efbf5f04 100644 --- a/trust-store-trust-policy-specification.md +++ b/trust-store-trust-policy-specification.md @@ -198,7 +198,7 @@ The following table shows the resultant validation action, either *enforced* (ve **Authenticity** : Guarantees that the artifact was signed by an identity trusted by the verifier. Its definition does not include revocation, which is when a trusted identity is subsequently untrusted because of a compromise. -**Authentic timestamp** : Guarantees that the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature must be treated as valid when a certificate is revoked, if the certificate was revoked after the signature was generated. In the absence of a authentic timestamp, signatures are considered invalid after certificate expires, and all signatures are considered revoked when a certificate is revoked. +**Authentic timestamp** : Guarantees that the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature must be treated as valid or invalid based on whether the signature was generated before or after the certificate revocation. In the absence of an Authentic Timestamp, a signature is considered invalid if the signing certificate or chain is either expired or revoked. - **NOTE**: `notation` RC1 will generate trusted timestamp using a TSA when the signature is generated, but will not support verification of TSA countersignatures. Related issue - [#59](https://github.com/notaryproject/roadmap/issues/59). @@ -337,7 +337,7 @@ TODO: Update this section after [verification plugin spec](https://github.com/no If the accuracy is not explicitly specified and `TSTInfo.policy` is the baseline time-stamp policy([RFC-3628](https://tools.ietf.org/html/rfc3628#section-5.2)), use accuracy of 1 second. Otherwise, use an accuracy of 0. 1. Calculate the timestamp range using the lower and upper limits per [RFC-3161 section 2.4.2](https://tools.ietf.org/html/rfc3161#section-2.4.2) and store the limits as `timeStampLowerLimit` and `timeStampUpperLimit` variables respectively. - 1. Check 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 signing certificate and certificate chain's validity period, continue to the next step. Else fail this step. + 1. Validate that the time range from `timeStampLowerLimit` to `timeStampUpperLimit` timestamp is entirely within the signing certificate and certificate chain's validity period. If the validation passes, continue to the next step. Else fail this step. 1. **Validate Revocation Status:** 1. Validate signing identity(certificate and certificate chain) revocation status using [certificate revocation evaluation](#certificate-revocation-evaluation) section as per `signingIdentityRevocation` setting in trust-policy. 1. Perform extended validation using the applicable (if any) plugin.