From 3d8617b32aca594e48aeda7ac7d7bc6fff5646bc Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Sat, 26 Mar 2022 02:36:11 +0800 Subject: [PATCH 01/11] COSE Signature Envelope (#139) * cbor payload * cose envelope * finalize payload * remove JWS * refine notations * refine datetime Signed-off-by: Shiwei Zhang Merging into the remote-siging branch for further iteration, addressing the PR feedback with additional PRs into this branch. --- signature-specification.md | 174 ++++++++++++++++--------------------- 1 file changed, 77 insertions(+), 97 deletions(-) diff --git a/signature-specification.md b/signature-specification.md index cd90df4e..2144cbba 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -17,7 +17,7 @@ The signature manifest has an artifact type that specifies it's a Notary V2 sign - **`artifactType`** (*string*): This REQUIRED property references the Notary version of the signature: `application/vnd.cncf.notary.v2.signature`. - **`blobs`** (*array of objects*): This REQUIRED property contains collection of only one [artifact descriptor](https://github.com/oras-project/artifacts-spec/blob/main/descriptor.md) referencing signature envelope. - - **`mediaType`** (*string*): This REQUIRED property contains media type of signature envelope blob. The supported value is `application/jose+json` + - **`mediaType`** (*string*): This REQUIRED property contains media type of signature envelope blob. The supported value is `application/cose`. - **`subject`** (*descriptor*): A REQUIRED artifact descriptor referencing the signed manifest, including, but not limited to image manifest, image index, oras-artifact manifest. - **`annotations`** (*string-string map*): This REQUIRED property contains metadata for the artifact manifest. It is being used to store information about the signature. @@ -30,7 +30,7 @@ The signature manifest has an artifact type that specifies it's a Notary V2 sign "artifactType": "application/vnd.cncf.notary.v2.signature", "blobs": [ { - "mediaType": "application/jose+json", + "mediaType": "application/cose", "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", "size": 32654 } @@ -70,7 +70,7 @@ A signature envelope consists of the following components: A signature envelope is `e = {m, v, u, s}` where `s` is signature. -Notary v2 supports [JWS JSON Serialization](https://datatracker.ietf.org/doc/html/rfc7515) as signature envelope format with some additional constraints but makes provisions to support additional signature envelope format. +Notary v2 supports [COSE_Sign1_Tagged](https://datatracker.ietf.org/doc/html/rfc8152#section-4) as signature envelope format with some additional constraints but makes provisions to support additional signature envelope format. ### Payload @@ -80,7 +80,7 @@ Notary v2 requires Payload to be the content **descriptor** of the subject manif 1. Descriptor MAY contain `annotations` and if present it MUST follow the [annotation rules](https://github.com/opencontainers/image-spec/blob/main/annotations.md#rules). Notary v2 uses annotations for storing both Notary specific and user defined signed attributes. The prefix `io.cncf.notary` in annotation keys is reserved for use in Notary v2 and MUST NOT be used outside this specification. 1. Descriptor MAY contain `artifactType` field for artifact manifests, or the `config.mediaType` for `oci.image` based manifests. -Examples: +Examples: The media type of the content descriptor is `application/vnd.cncf.oras.artifact.descriptor.v1+json`. ```jsonc { @@ -120,125 +120,105 @@ Notary v2 requires the signature envelope to support the following signed attrib ### Supported Signature Envelopes -#### JWS JSON Serialization +#### COSE_Sign1_Tagged -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. +In COSE ([rfc8152](https://datatracker.ietf.org/doc/html/rfc8152)), data is stored as either payload or headers (protected and unprotected). +Notary v2 uses [COSE_Sign1_Tagged](https://datatracker.ietf.org/doc/html/rfc8152#section-4.2) object as the signature envelope with some additional constraints on the header fields. 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 +**Payload**: COSE signs the payload as defined in the [Payload](#payload) section. Detached payloads are not supported. -```json +**ProtectedHeaders**: Notary v2 supports the following protected headers. Other header fields can be included but will be ignored. + +``` { - "subject": { - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", - "size": 16724, - "annotations": { - "key1": "value1", - "key2": "value2", - ... - } - }, - "iat": 1234567891000, - "exp": 1234567891011 + / crit / 2: [ + / cty / 3, + 'signingtime', + 'expiry' + ], + / cty / 3: 'application/vnd.cncf.oras.artifact.descriptor.v1+json', + 'signingtime': 1234567891000, + 'expiry': 1234567891011 } ``` -The payload contains the subject manifest and other attributes that have to be integrity protected. +Note: The above example is represented using the [extended CBOR diagnostic notation](https://datatracker.ietf.org/doc/html/rfc8152#appendix-C). -- **`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. +- **`crit`** (*array of integers or strings*): This REQUIRED property (label: `2`) lists the headers that implementation MUST understand and process. + The array MUST contain `3` (`cty`), and `signingtime`. If `expiry` is presented, the array MUST also contain `expiry`. +- **`cty`** (*string*): The REQUIRED property content-type (label: `3`) is used to declare the media type of the secured content (the payload). +- **`signingtime`** (*datetime*): The REQUIRED property identifies the time at which the signature was generated. +- **`expiry`** (*datetime*): 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. +**UnprotectedHeaders**: Notary v2 supports two unprotected headers: `timestamp` and `x5chain`. -**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': << TimeStampToken >>, + / x5chain / 33: [ + << DER(leafCert) >>, + << DER(intermediate CACert) >>, + << DER(rootCert) >> + ] } ``` -- **`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. +Note: `<<` and `>>` are used to notate the byte string resulting from encoding the data item. -- **`timestamp`** (*string*): This OPTIONAL property is used to store time stamp token. +- **`timestamp`** (*byte 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. +- **`x5chain`** (*array of byte strings*): This REQUIRED property (label: `33` by [IANA](https://www.iana.org/assignments/cose/cose.xhtml#header-parameters)) 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 COSE. + The certificate containing the public key corresponding to the key used to digitally sign the COSE MUST be the first certificate. + Optionally, this header can be presented in the protected header. -**Signature**: In JWS signature is calculated by combining JWSPayload and protected headers. +**Signature**: In COSE, signature is calculated by constructing the `Sig_structure` for `COSE_Sign1`. 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. +1. Encode the protected header into a CBOR object as a byte string named `body_protected`. +2. Construct the `Sig_structure` for `COSE_Sign1`. + ``` + Sig_structure = [ + / context / 'Signature1', + / body_protected / << ProtectedHeaders >>, + / external_aad / h'', + / payload / << Payload >>, + ] + ``` +3. Encode `Sig_structure` into a CBOR object as a byte string named `ToBeSigned`. +4. Compute the signature on the `ToBeSigned` constructed in the previous step by using the signature algorithm defined in the corresponding header element: `alg`. 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. +**Signature Envelope**: The final signature envelope is a `COSE_Sign1_Tagged` object, consisting of Payload, 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": ["", "", ""] +``` +18( + [ + / protected / << { + / crit / 2: [ + / cty / 3, + 'signingtime', + 'expiry' + ], + / cty / 3: 'application/vnd.cncf.oras.artifact.descriptor.v1+json', + 'signingtime': 1234567891000, + 'expiry': 1234567891011 + } >>, + / unprotected / { + 'timestamp': << TimeStampToken >>, + / x5chain / 33: [ + << DER(leafCert) >>, + << DER(intermediate CACert) >>, + << DER(rootCert) >> + ] }, - "signature": "Base64Url( sign( ASCII( . )))" -} + / payload / << descriptor >>, + / signature / << sign( << Sig_structure >> ) >> + ] +) ``` -**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: @@ -292,10 +272,10 @@ The **timestamping certificate** MUST meet the following minimum requirements: **Q: How will Notary v2 support multiple signature envelope format?** -**A:** The idea is to use `mediaType` of artifact manifest's blob to identify the signature envelope type (like JWS, CMS, DSSE, etc). +**A:** The idea is to use `mediaType` of artifact manifest's blob to identify the signature envelope type (like COSE, JWS, CMS, DSSE, etc). The client implementation can use the aforementioned `mediaType` to parse the signature envelope. **Q: How will Notary v2 handle non-backward compatible changes to signature format?** **A:** The Signature envelope MUST have a versioning mechanism to support non-backward compatible changes. -For [JWS JSON serialization](#jws-json-serialization) signature envelope it is achieved by `cty` field in ProtectedHeaders. +For [COSE_Sign1_Tagged](#cose_sign1_tagged) signature envelope it is achieved by `cty` field in ProtectedHeaders. From 33d1ee9e3f45f1075833c5e1498902c792771109 Mon Sep 17 00:00:00 2001 From: Steve Lasker Date: Wed, 6 Apr 2022 13:37:34 -0700 Subject: [PATCH 02/11] Support for JWT and COSE envelopes (#145) Signed-off-by: Steve Lasker --- signature-envelope-cose.md | 134 +++++++++++++++++++++++++++ signature-envelope-jwt.md | 142 ++++++++++++++++++++++++++++ signature-specification.md | 184 +++++++------------------------------ 3 files changed, 307 insertions(+), 153 deletions(-) create mode 100644 signature-envelope-cose.md create mode 100644 signature-envelope-jwt.md diff --git a/signature-envelope-cose.md b/signature-envelope-cose.md new file mode 100644 index 00000000..ff21d5a8 --- /dev/null +++ b/signature-envelope-cose.md @@ -0,0 +1,134 @@ +# COSE Sign1 Envelope Serialization + +In COSE ([rfc8152](https://datatracker.ietf.org/doc/html/rfc8152)), data is stored as either payload or headers (protected and unprotected). +Notary v2 supports [COSE_Sign1_Tagged](https://datatracker.ietf.org/doc/html/rfc8152#section-4.2) as a signature envelope with some additional constraints on the header fields. + +A JWS based signature will be persisted with a blob `mediaType` of `"application/cose"` + +```jsonc +{ + "artifactType": "application/vnd.cncf.notary.v2.signature", + "blobs": [ + { + "mediaType": "application/cose", + "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", + "size": 32654 + } + ], + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", + "size": 16724 + }, + "annotations": { + "io.cncf.notary.x509certs.fingerprint.sha256": "[\"B7A69A70992AE4F9FF103EBE04A2C3BA6C777E439253CE36562E6E98375068C3\" \"932EB6F5598435D4EF23F97B0B5ACB515FAE2B8D8FAC046AB813DDC419DD5E89\"]" + } +} +``` + +Unless explicitly specified as OPTIONAL, all fields are required. + +**Payload**: COSE signs the payload as defined in the [Payload](#payload) section. Detached payloads are not supported. + +**ProtectedHeaders**: Notary v2 supports the following protected headers. Other header fields can be included but will be ignored. + +``` +{ + / crit / 2: [ + / cty / 3, + 'signingtime', + 'expiry' + ], + / cty / 3: 'application/vnd.cncf.oras.artifact.descriptor.v1+json', + 'signingtime': 1234567891000, + 'expiry': 1234567891011 +} +``` + +Note: The above example is represented using the [extended CBOR diagnostic notation](https://datatracker.ietf.org/doc/html/rfc8152#appendix-C). + +- **`crit`** (*array of integers or strings*): This REQUIRED property (label: `2`) lists the headers that implementation MUST understand and process. + The array MUST contain `3` (`cty`), and `signingtime`. If `expiry` is presented, the array MUST also contain `expiry`. +- **`cty`** (*string*): The REQUIRED property content-type (label: `3`) is used to declare the media type of the secured content (the payload). +- **`signingtime`** (*datetime*): The REQUIRED property identifies the time at which the signature was generated. +- **`expiry`** (*datetime*): This OPTIONAL property contains the expiration time on or after which the signature must not be considered valid. + +**UnprotectedHeaders**: Notary v2 supports two unprotected headers: `timestamp` and `x5chain`. + +``` +{ + 'timestamp': << TimeStampToken >>, + / x5chain / 33: [ + << DER(leafCert) >>, + << DER(intermediate CACert) >>, + << DER(rootCert) >> + ] +} +``` + +Note: `<<` and `>>` are used to notate the byte string resulting from encoding the data item. + +- **`timestamp`** (*byte 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. +- **`x5chain`** (*array of byte strings*): This REQUIRED property (label: `33` by [IANA](https://www.iana.org/assignments/cose/cose.xhtml#header-parameters)) 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 COSE. + The certificate containing the public key corresponding to the key used to digitally sign the COSE MUST be the first certificate. + Optionally, this header can be presented in the protected header. + +**Signature**: In COSE, signature is calculated by constructing the `Sig_structure` for `COSE_Sign1`. +The process is described below: + +1. Encode the protected header into a CBOR object as a byte string named `body_protected`. +2. Construct the `Sig_structure` for `COSE_Sign1`. + ``` + Sig_structure = [ + / context / 'Signature1', + / body_protected / << ProtectedHeaders >>, + / external_aad / h'', + / payload / << Payload >>, + ] + ``` +3. Encode `Sig_structure` into a CBOR object as a byte string named `ToBeSigned`. +4. Compute the signature on the `ToBeSigned` constructed in the previous step by using the signature algorithm defined in the corresponding header element: `alg`. + This is the value of the signature property used in the signature envelope. + +**Signature Envelope**: The final signature envelope is a `COSE_Sign1_Tagged` object, consisting of Payload, ProtectedHeaders, UnprotectedHeaders, and Signature. + +``` +18( + [ + / protected / << { + / crit / 2: [ + / cty / 3, + 'signingtime', + 'expiry' + ], + / cty / 3: 'application/vnd.cncf.oras.artifact.descriptor.v1+json', + 'signingtime': 1234567891000, + 'expiry': 1234567891011 + } >>, + / unprotected / { + 'timestamp': << TimeStampToken >>, + / x5chain / 33: [ + << DER(leafCert) >>, + << DER(intermediate CACert) >>, + << DER(rootCert) >> + ] + }, + / payload / << descriptor >>, + / signature / << sign( << Sig_structure >> ) >> + ] +) +``` + +## Signature Algorithm Requirements + +The implementation MUST support the following set of algorithms: + +1. RSASSA-PSS with SHA-256 +1. RSASSA-PSS with SHA-384 +1. RSASSA-PSS with SHA-512 +1. ECDSA on secp256r1 with SHA-256 +1. ECDSA on secp384r1 with SHA-384 +1. ECDSA on secp521r1 with SHA-512 + +For ECDSA equivalent NIST curves and ANSI curves can be found at [RFC4492 Appendix A](https://tools.ietf.org/search/rfc4492#appendix-A). diff --git a/signature-envelope-jwt.md b/signature-envelope-jwt.md new file mode 100644 index 00000000..43604747 --- /dev/null +++ b/signature-envelope-jwt.md @@ -0,0 +1,142 @@ +# JWS Signature Envelope Serialization + +JWS JSON Serialization ([RFC7515](https://datatracker.ietf.org/doc/html/rfc7515)) data is stored as either claims or headers (protected and unprotected). +Notary v2 supports JWS JSON Serialization as a signature envelope with some additional constraints on the structure of claims and headers. + +A JWS based signature will be persisted with a blob `mediaType` of `"application/jose+json"` + +```jsonc +{ + "artifactType": "application/vnd.cncf.notary.v2.signature", + "blobs": [ + { + "mediaType": "application/jose+json", + "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", + "size": 32654 + } + ], + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", + "size": 16724 + }, + "annotations": { + "io.cncf.notary.x509certs.fingerprint.sha256": "[\"B7A69A70992AE4F9FF103EBE04A2C3BA6C777E439253CE36562E6E98375068C3\" \"932EB6F5598435D4EF23F97B0B5ACB515FAE2B8D8FAC046AB813DDC419DD5E89\"]" + } +} +``` + +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 + +```jsonc +{ + "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`. + +```jsonc +{ + "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. + +```jsonc +{ + "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. +2. Compute the Base64Url value of JWSPayload. +3. 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. + +```jsonc +{ + "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. Only JWS JSON flattened format is supported. + See 'Signature Envelope' section. \ No newline at end of file diff --git a/signature-specification.md b/signature-specification.md index 2144cbba..da329acc 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -9,15 +9,15 @@ The document has the following sections: ## Storage This section describes how Notary v2 signatures are stored in the OCI Distribution conformant registry. -Notary v2 uses [ORAS artifact manifest](https://github.com/oras-project/artifacts-spec/blob/main/artifact-manifest.md) to store the signature in the repository. +Notary v2 uses [ORAS artifact manifest][oras-artifact-manifest] to store the signature in the repository. The media type of the signature manifest is `application/vnd.cncf.oras.artifact.manifest.v1+json`. The signature manifest has an artifact type that specifies it's a Notary V2 signature, a reference to the manifest of the artifact being signed, a blob referencing the signature, and a collection of annotations. ![Signature storage inside registry](media/signature-specification.svg) - **`artifactType`** (*string*): This REQUIRED property references the Notary version of the signature: `application/vnd.cncf.notary.v2.signature`. -- **`blobs`** (*array of objects*): This REQUIRED property contains collection of only one [artifact descriptor](https://github.com/oras-project/artifacts-spec/blob/main/descriptor.md) referencing signature envelope. - - **`mediaType`** (*string*): This REQUIRED property contains media type of signature envelope blob. The supported value is `application/cose`. +- **`blobs`** (*array of objects*): This REQUIRED property contains collection of only one [artifact descriptor][artifact-descriptor] referencing signature envelope. + - **`mediaType`** (*string*): This REQUIRED property contains media type of signature envelope blob. The currently supported values are `application/cose` and `application/jose+json`. - **`subject`** (*descriptor*): A REQUIRED artifact descriptor referencing the signed manifest, including, but not limited to image manifest, image index, oras-artifact manifest. - **`annotations`** (*string-string map*): This REQUIRED property contains metadata for the artifact manifest. It is being used to store information about the signature. @@ -25,30 +25,9 @@ The signature manifest has an artifact type that specifies it's a Notary V2 sign - **`io.cncf.notary.x509certs.fingerprint.sha256`**: A REQUIRED annotation whose value contains the list of SHA-256 fingerprint of signing certificate and certificate chain used for signature generation. The list of fingerprints is present as a JSON array string. -```json -{ - "artifactType": "application/vnd.cncf.notary.v2.signature", - "blobs": [ - { - "mediaType": "application/cose", - "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", - "size": 32654 - } - ], - "subject": { - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", - "size": 16724 - }, - "annotations": { - "io.cncf.notary.x509certs.fingerprint.sha256": "[\"B7A69A70992AE4F9FF103EBE04A2C3BA6C777E439253CE36562E6E98375068C3\" \"932EB6F5598435D4EF23F97B0B5ACB515FAE2B8D8FAC046AB813DDC419DD5E89\"]" - } -} -``` - ### Signature Discovery -The client should be able to discover all the signatures belonging to an artifact (such as image manifest) by using [ORAS Manifest Referrers API](https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md). +The client should be able to discover all the signatures belonging to an artifact (such as image manifest) by using [ORAS Manifest Referrers API][oras-artifacts-referrers]. ORAS Manifest Referrers API returns a paginated list of all artifacts belonging to a target artifact (such as container images, SBoMs). The implementation can filter Notary signature artifacts by either using ORAS Manifest Referrers API or using custom logic on the client. Each Notary signature artifact refers to a signature envelope blob. @@ -64,23 +43,27 @@ The Signature Envelope is a standard data structure for creating a signed messag A signature envelope consists of the following components: - Payload `m`: The data that is integrity protected - e.g. descriptor of the artifact being signed. -- Signed attributes `v`: The signature metadata that is integrity protected - e.g. signature expiration time, signing time, etc. -- Unsigned attributes `u`: This OPTIONAL property represents signature metadata that is not integrity protected - e.g. timestamp, certificates, etc. +- Signed attributes `v`: The signature metadata that is integrity protected - e.g. signature expiration time, creation time, etc. +- Unsigned attributes `u`: This OPTIONAL property represents signature metadata that is not integrity protected by this envelope- e.g. timestamp, certificates, etc. + We anticipate all unsigned attributes contain signed content. - Cryptographic signatures `s`: The digital signatures computed on payload and signed attributes. A signature envelope is `e = {m, v, u, s}` where `s` is signature. -Notary v2 supports [COSE_Sign1_Tagged](https://datatracker.ietf.org/doc/html/rfc8152#section-4) as signature envelope format with some additional constraints but makes provisions to support additional signature envelope format. +Notary v2 supports multiple envelope formats, including: + +- [COSE Sign1](./signature-envelope-cose.md) +- [JWT](./signature-envelope-jwt.md) ### Payload Notary v2 requires Payload to be the content **descriptor** of the subject manifest that is being signed. 1. Descriptor MUST contain `mediaType`, `digest`, `size` fields. -1. Descriptor MAY contain `annotations` and if present it MUST follow the [annotation rules](https://github.com/opencontainers/image-spec/blob/main/annotations.md#rules). Notary v2 uses annotations for storing both Notary specific and user defined signed attributes. The prefix `io.cncf.notary` in annotation keys is reserved for use in Notary v2 and MUST NOT be used outside this specification. -1. Descriptor MAY contain `artifactType` field for artifact manifests, or the `config.mediaType` for `oci.image` based manifests. +2. 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 signed attributes. The prefix `io.cncf.notary` in annotation keys is reserved for use in Notary v2 and MUST NOT be used outside this specification. +3. Descriptor MAY contain `artifactType` field for artifact manifests, or the `config.mediaType` for `oci.image` based manifests. -Examples: The media type of the content descriptor is `application/vnd.cncf.oras.artifact.descriptor.v1+json`. +**Examples**: The media type of the content descriptor is `application/vnd.cncf.oras.artifact.descriptor.v1+json`. ```jsonc { @@ -93,7 +76,7 @@ Examples: The media type of the content descriptor is `application/vnd.cncf.oras } ``` -```json +```jsonc { "mediaType": "sbom/example", "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", @@ -109,128 +92,12 @@ Notary v2 requires the signature envelope to support the following signed attrib Its value should be numeric representing the number of seconds (not milliseconds) since Epoch. - **Expiration time**: The time after which signature shouldn't be considered valid. Its value should be numeric representing the number of seconds since Epoch. + The value MUST be equal or greater than the `Creation time` This is an OPTIONAL attribute. ### Unsigned Attributes - **Certificates**: The ordered collection of X.509 certificates with a signing certificate as the first entry. -- **Timestamp**: The time stamp token generated for a given signature. - Only [RFC3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2) compliant *TimeStampToken* are supported. - This is an OPTIONAL attribute. - -### Supported Signature Envelopes - -#### COSE_Sign1_Tagged - -In COSE ([rfc8152](https://datatracker.ietf.org/doc/html/rfc8152)), data is stored as either payload or headers (protected and unprotected). -Notary v2 uses [COSE_Sign1_Tagged](https://datatracker.ietf.org/doc/html/rfc8152#section-4.2) object as the signature envelope with some additional constraints on the header fields. - -Unless explicitly specified as OPTIONAL, all fields are required. - -**Payload**: COSE signs the payload as defined in the [Payload](#payload) section. Detached payloads are not supported. - -**ProtectedHeaders**: Notary v2 supports the following protected headers. Other header fields can be included but will be ignored. - -``` -{ - / crit / 2: [ - / cty / 3, - 'signingtime', - 'expiry' - ], - / cty / 3: 'application/vnd.cncf.oras.artifact.descriptor.v1+json', - 'signingtime': 1234567891000, - 'expiry': 1234567891011 -} -``` - -Note: The above example is represented using the [extended CBOR diagnostic notation](https://datatracker.ietf.org/doc/html/rfc8152#appendix-C). - -- **`crit`** (*array of integers or strings*): This REQUIRED property (label: `2`) lists the headers that implementation MUST understand and process. - The array MUST contain `3` (`cty`), and `signingtime`. If `expiry` is presented, the array MUST also contain `expiry`. -- **`cty`** (*string*): The REQUIRED property content-type (label: `3`) is used to declare the media type of the secured content (the payload). -- **`signingtime`** (*datetime*): The REQUIRED property identifies the time at which the signature was generated. -- **`expiry`** (*datetime*): This OPTIONAL property contains the expiration time on or after which the signature must not be considered valid. - -**UnprotectedHeaders**: Notary v2 supports two unprotected headers: `timestamp` and `x5chain`. - -``` -{ - 'timestamp': << TimeStampToken >>, - / x5chain / 33: [ - << DER(leafCert) >>, - << DER(intermediate CACert) >>, - << DER(rootCert) >> - ] -} -``` - -Note: `<<` and `>>` are used to notate the byte string resulting from encoding the data item. - -- **`timestamp`** (*byte 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. -- **`x5chain`** (*array of byte strings*): This REQUIRED property (label: `33` by [IANA](https://www.iana.org/assignments/cose/cose.xhtml#header-parameters)) 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 COSE. - The certificate containing the public key corresponding to the key used to digitally sign the COSE MUST be the first certificate. - Optionally, this header can be presented in the protected header. - -**Signature**: In COSE, signature is calculated by constructing the `Sig_structure` for `COSE_Sign1`. -The process is described below: - -1. Encode the protected header into a CBOR object as a byte string named `body_protected`. -2. Construct the `Sig_structure` for `COSE_Sign1`. - ``` - Sig_structure = [ - / context / 'Signature1', - / body_protected / << ProtectedHeaders >>, - / external_aad / h'', - / payload / << Payload >>, - ] - ``` -3. Encode `Sig_structure` into a CBOR object as a byte string named `ToBeSigned`. -4. Compute the signature on the `ToBeSigned` constructed in the previous step by using the signature algorithm defined in the corresponding header element: `alg`. - This is the value of the signature property used in the signature envelope. - -**Signature Envelope**: The final signature envelope is a `COSE_Sign1_Tagged` object, consisting of Payload, ProtectedHeaders, UnprotectedHeaders, and Signature. - -``` -18( - [ - / protected / << { - / crit / 2: [ - / cty / 3, - 'signingtime', - 'expiry' - ], - / cty / 3: 'application/vnd.cncf.oras.artifact.descriptor.v1+json', - 'signingtime': 1234567891000, - 'expiry': 1234567891011 - } >>, - / unprotected / { - 'timestamp': << TimeStampToken >>, - / x5chain / 33: [ - << DER(leafCert) >>, - << DER(intermediate CACert) >>, - << DER(rootCert) >> - ] - }, - / payload / << descriptor >>, - / signature / << sign( << Sig_structure >> ) >> - ] -) -``` - -## Signature Algorithm Requirements - -The implementation MUST support the following set of algorithms: - -1. RSASSA-PSS with SHA-256 -1. RSASSA-PSS with SHA-384 -1. RSASSA-PSS with SHA-512 -1. ECDSA on secp256r1 with SHA-256 -1. ECDSA on secp384r1 with SHA-384 -1. ECDSA on secp521r1 with SHA-512 - -For ECDSA equivalent NIST curves and ANSI curves can be found at [RFC4492 Appendix A](https://tools.ietf.org/search/rfc4492#appendix-A). ### Algorithm Selection @@ -270,12 +137,23 @@ The **timestamping certificate** MUST meet the following minimum requirements: ## FAQ -**Q: How will Notary v2 support multiple signature envelope format?** +**Q: How will Notary v2 support multiple signature envelope formats?** -**A:** The idea is to use `mediaType` of artifact manifest's blob to identify the signature envelope type (like COSE, JWS, CMS, DSSE, etc). +**A:** The `mediaType` of artifact manifest's blob identifies the signature envelope type. The client implementation can use the aforementioned `mediaType` to parse the signature envelope. **Q: How will Notary v2 handle non-backward compatible changes to signature format?** -**A:** The Signature envelope MUST have a versioning mechanism to support non-backward compatible changes. -For [COSE_Sign1_Tagged](#cose_sign1_tagged) signature envelope it is achieved by `cty` field in ProtectedHeaders. +**A:** The Signature envelope MUST have a versioning mechanism to support non-backward compatible changes. +[COSE_Sign1_Tagged](./signature-envelope-cose.md) signature envelope versioning is achieved by the `cty` field in ProtectedHeaders. +[JWS JSON serialization](./signature-envelope-jwt.md) signature envelope versioning is achieved by the `cty` field in ProtectedHeaders. + +[annotation-rules]: https://github.com/opencontainers/image-spec/blob/main/annotations.md#rules +[artifact-descriptor]: https://github.com/oras-project/artifacts-spec/blob/main/descriptor.md +[ietf-cose]: https://datatracker.ietf.org/doc/html/rfc8152 +[ietf-cose-section-4]: https://datatracker.ietf.org/doc/html/rfc8152#section-4 +[ietf-cose-section-4.2]: https://datatracker.ietf.org/doc/html/rfc8152#section-4.2 +[ietf-cose-appendix-c]: https://datatracker.ietf.org/doc/html/rfc8152#appendix-C +[ietf-rfc3161]: https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2 +[oras-artifact-manifest]: https://github.com/oras-project/artifacts-spec/blob/main/artifact-manifest.md +[oras-artifacts-referrers]: https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md From 3e93835f6c55d6b77c901bdf0ff63342e3ebb61a Mon Sep 17 00:00:00 2001 From: Milind Gokarn Date: Tue, 19 Apr 2022 13:13:00 -0700 Subject: [PATCH 03/11] Updates to signature specification (#147) Updates to signature specification * Moves signed attributes out of payload * Defines new signed attributes and marks them as critical/informational * Introduces signature scheme to support different trust models Signed-off-by: Milind Gokarn --- ...velope-jwt.md => signature-envelope-jws.md | 0 signature-specification.md | 68 +++++++++++++------ 2 files changed, 48 insertions(+), 20 deletions(-) rename signature-envelope-jwt.md => signature-envelope-jws.md (100%) diff --git a/signature-envelope-jwt.md b/signature-envelope-jws.md similarity index 100% rename from signature-envelope-jwt.md rename to signature-envelope-jws.md diff --git a/signature-specification.md b/signature-specification.md index da329acc..1f93e0e3 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -1,10 +1,9 @@ # Signature Specification -This document describes how Notary v2 signatures are created and stored. -The document has the following sections: +This document provides the following details for Notary v2 signatures: -- **[Storage](#storage)**: Describes how signatures are stored in OCI registry. -- **[Signature Envelope](#signature-envelope)**: Describes how signatures are created. +- **[Storage](#storage)**: Describes how signatures are stored and retrieved from an OCI registry. +- **[Signature Envelope](#signature-envelope)**: Describes the structure of a Notary v2 signature. ## Storage @@ -22,7 +21,7 @@ The signature manifest has an artifact type that specifies it's a Notary V2 sign - **`annotations`** (*string-string map*): This REQUIRED property contains metadata for the artifact manifest. It is being used to store information about the signature. Keys using the `io.cncf.notary` namespace are reserved for use in Notary and MUST NOT be used by other specifications. - - **`io.cncf.notary.x509certs.fingerprint.sha256`**: A REQUIRED annotation whose value contains the list of SHA-256 fingerprint of signing certificate and certificate chain used for signature generation. + - **`io.cncf.notary.x509.fingerprint.sha256`**: A REQUIRED annotation whose value contains the list of SHA-256 fingerprint of signing certificate and certificate chain used for signature generation. The list of fingerprints is present as a JSON array string. ### Signature Discovery @@ -35,25 +34,26 @@ Each Notary signature artifact refers to a signature envelope blob. ### Signature Filtering An OCI artifact can have multiple signatures, Notary v2 uses annotations of the signature artifact to filter relevant signatures based on the applicable trust policy. -The Notary v2 signature artifact's `io.cncf.notary.x509certs.fingerprint.sha256` annotations key MUST contain the list of SHA-256 fingerprints of certificate and certificate chain used for signing. +The Notary v2 signature artifact's `io.cncf.notary.x509.fingerprint.sha256` annotations key MUST contain the list of SHA-256 fingerprints of certificate and certificate chain used for signing. ## Signature Envelope The Signature Envelope is a standard data structure for creating a signed message. A signature envelope consists of the following components: -- Payload `m`: The data that is integrity protected - e.g. descriptor of the artifact being signed. +- Payload/Message `m`: The data that is integrity protected - e.g. descriptor of the artifact being signed. - Signed attributes `v`: The signature metadata that is integrity protected - e.g. signature expiration time, creation time, etc. -- Unsigned attributes `u`: This OPTIONAL property represents signature metadata that is not integrity protected by this envelope- e.g. timestamp, certificates, etc. - We anticipate all unsigned attributes contain signed content. +- Unsigned attributes `u`: Unsigned attributes `u`: These attributes are not signed by the signing key that generates the signature. We anticipate unsigned attributes contain content that may be signed by an different party e.g. Certificate chain signed by a CA, or TSA countersignature signed by the TSA. - Cryptographic signatures `s`: The digital signatures computed on payload and signed attributes. A signature envelope is `e = {m, v, u, s}` where `s` is signature. -Notary v2 supports multiple envelope formats, including: +This specification defines the set of signed and unsigned attributes that make up a valid The Notary v2 signature. This specification aims to be be agnostic of signature envelope format (e.g. COSE, JWS), details of encoding the envelope in a specific signature envelope format are covered in in separate specs. + +Notary v2 supports the following envelope formats: - [COSE Sign1](./signature-envelope-cose.md) -- [JWT](./signature-envelope-jwt.md) +- [JWS](./signature-envelope-jws.md) ### Payload @@ -71,7 +71,7 @@ Notary v2 requires Payload to be the content **descriptor** of the subject manif "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", "size": 16724, "annotations": { - "io.wabbit-networks.buildId": "123" // user defined signed attribute. + "io.wabbit-networks.buildId": "123" // user defined metadata } } ``` @@ -84,20 +84,42 @@ Notary v2 requires Payload to be the content **descriptor** of the subject manif } ``` +### Signature Scheme + +Notary will initially support X509 PKI based identities, which has well established standards for establishing and trusting signing identities, managing key lifetimes and rotation, and revocation. There is interest in the community to support additional trust models based on other standards and techniques (Notary TUF, ledger based). Notary addresses future support for additional trust models through an abstraction called Signature Scheme. A Signature Scheme defines the the specific trust model used for generation and verification of signatures. It includes the supported identity type (e.g. X509 certificates), set of signed and unsigned attributes in the signature to support the trust model, which attributes are required/optional and critical/informational, the signature verification logic, and verification policy switches that are supported (e.g. revocation checks can be skipped, audited, enforced) and how they are processed during signature verification. Notary v2 defines the following signature schemes. + +`notary.signer.x509` - This signature scheme defines the trust model that uses traditional X509 based PKI. Users owns signing keys and use CA issued certificates to represent identity. + +`notary.signingservice.x509` - This signature scheme defines the trust model for a signing service that uses X509 based PKI. In this model, a trusted signing service manages the keys of behalf of the user, and generates signatures. The end user does not have direct access to signing keys. + +When Notary supports additional signature schemes, Notary verification policy may have breaking changes to support newer concepts introduced by the signature scheme, and existing signed artifacts will need to be resigned if they are required to be validated using the new trust system introduced by the signature scheme. + ### Signed Attributes -Notary v2 requires the signature envelope to support the following signed attributes: +Signed attributes/claims are additional metadata apart from the payload, which are required to support the signature verification process. + +- Any claims MUST NOT be stored/appended in the payload itself, as the payload is only parsed and processed once the signature has been verified (signature is valid, and from a trusted key) and trust is established. +- Specific claims can be either required or optional. +- Claims that if present, MUST be processed by a verifier MUST be marked as critical. +- Claims which are informational and do not influence signature verification MUST NOT be marked critical. -- **Creation time**: The time at which the signature was generated. - Its value should be numeric representing the number of seconds (not milliseconds) since Epoch. -- **Expiration time**: The time after which signature shouldn't be considered valid. - Its value should be numeric representing the number of seconds since Epoch. - The value MUST be equal or greater than the `Creation time` - This is an OPTIONAL attribute. +Notary v2 requires the signature envelope to support the following signed attributes/claims. + +#### Standard attributes + +- **Notary Signature scheme** (critical): The trust model used by Notary currently supports the following values for this claim `notary.signer.x509` and `notary.signingservice.x509`. Both signature schemes use the same set of claims defined in this document. One difference is that `notary.signer.x509` uses a TSA signature to determine authenticated signing time (timestamp), and `notary.signingservice.x509` uses the trusted signing time claim instead, with optional support for TSA signatures. Signature verification MUST consider the signature to be invalid if any other value is used. +- **Signing time**: The time at which the signature was generated. This is a REQUIRED claim for signature scheme `notary.signer.x509`. Though this claim is signed by the signing key, it’s considered unauthenticated as a signer can modify local time and manipulate this claim. More details [here](#signing-time). +- **Trusted signing time** (critical) : The time at which the signature was generated. This is an authenticated signing time which can be generated by a trusted time-stamper, like a signing service. This is a REQUIRED claim for signature scheme `notary.signingservice.x509` . More details [here](#signing-time). +- **Signature Expiry** (critical): The time when a signature is considered to be expired. This is an OPTIONAL claim. [Open issue](https://github.com/notaryproject/notaryproject/issues/141) tracking if this attribute should be removed. +- **Content type** (critical): The content type of the payload. Notary currently supports OCI descriptor of a subject manifest as the payload, supported value is `application/vnd.cncf.oras.artifact.descriptor.v1+json`, other payload types MAY be supported in future. This is a REQUIRED claim. +- **Client identifier**: The version of a client (e.g. Notation) that produced the signature. This is an OPTIONAL claim. It uses the following format `{client}/{version}` e.g. “notation/1.0.0”. This claim in intended to be used for diagnostic and troubleshooting purposes. ### Unsigned Attributes -- **Certificates**: The ordered collection of X.509 certificates with a signing certificate as the first entry. +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). + +- **Certificate Chain**: This property contains the list of X.509 certificate or certificate chain. This is a REQUIRED attribute. The certificate chain is authenticated using against a trust store as part of signature validation. +- **TSA counter signature** : The time stamp token generated for a given signature. Only [RFC3161](ietf-rfc3161) compliant TimeStampToken are supported. This is an OPTIONAL attribute. ### Algorithm Selection @@ -148,6 +170,12 @@ The client implementation can use the aforementioned `mediaType` to parse the si [COSE_Sign1_Tagged](./signature-envelope-cose.md) signature envelope versioning is achieved by the `cty` field in ProtectedHeaders. [JWS JSON serialization](./signature-envelope-jwt.md) signature envelope versioning is achieved by the `cty` field in ProtectedHeaders. +## Appendix + +### Signing time + +The signing time denotes the time at which the signature was generated. A X509 certificate has a defined [validity](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5) during which it can be used to generate signatures. The signing time must be greater than or equal to certificate's `notBefore` attribute, and signing time must be less than or equal to certificate's `notAfter` attribute. Signatures generated after the certificate expires are considered invalid. A trusted timestamp allows a verifier to determine if the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature be treated as valid when a certificate is revoked, if the certificate was revoked after the signature was generated. In the absence of a trusted timestamp, signatures are considered invalid after certificate expires, and all signatures are considered revoked when a certificate is revoked. + [annotation-rules]: https://github.com/opencontainers/image-spec/blob/main/annotations.md#rules [artifact-descriptor]: https://github.com/oras-project/artifacts-spec/blob/main/descriptor.md [ietf-cose]: https://datatracker.ietf.org/doc/html/rfc8152 From e7cf5dfa165139708d4dddb566170d2bda2be001 Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Wed, 3 Aug 2022 06:19:13 +0800 Subject: [PATCH 04/11] Update COSE spec (#179) * Update signature-envelope-cose.md (#153) The `crit` header should be optional, not required. The spec already defines `cty` and `signingtime` as required, so a compliant verifier should reject any signature that does not include them, irrespective of whether or not the envelope also declares them as critical. An appropriate use of `crit` would be if a signer intends to include a new header not defined in the spec, to instruct verifiers that they must understand and process that header in order for the signature to be successfully validated. Another scenario is if a signer intends to mandate processing of an otherwise optional header (e.g., `expiry`). In other words, `crit` can be used by a signer to add requirements to the verification flow, but it is unnecessary and redundant if the verifier already has the same requirements. Signed-off-by: Brian Krell * `cose-envelope`: merge changes from `main` (#177) * Align COSE spec with JWS Signed-off-by: letmaik Co-authored-by: Steve Lasker Co-authored-by: Quim Muntal Co-authored-by: Quim Muntal Co-authored-by: Milind Gokarn Co-authored-by: Milind Gokarn Co-authored-by: Shiwei Zhang Co-authored-by: Pritesh Bandi Co-authored-by: Sajay Antony <1821104+sajayantony@users.noreply.github.com> * update CBOR spec Co-authored-by: Steve Lasker Co-authored-by: qmuntal Co-authored-by: Milind Gokarn Co-authored-by: Pritesh Bandi Co-authored-by: Sajay Antony Signed-off-by: Shiwei Zhang Co-authored-by: Brian Krell <57008892+briankr-ms@users.noreply.github.com> Co-authored-by: Maik Riechert Co-authored-by: Steve Lasker Co-authored-by: Quim Muntal Co-authored-by: Quim Muntal Co-authored-by: Milind Gokarn Co-authored-by: Milind Gokarn Co-authored-by: Pritesh Bandi Co-authored-by: Sajay Antony <1821104+sajayantony@users.noreply.github.com> Co-authored-by: Pritesh Bandi Co-authored-by: Sajay Antony --- signature-envelope-cose.md | 165 +++++--- signature-envelope-jws.md | 189 ++++++--- signature-specification.md | 261 +++++++++--- signing-scheme.md | 99 +++++ specs/plugin-extensibility.md | 493 +++++++++++++++++----- trust-store-trust-policy-specification.md | 483 +++++++++++---------- 6 files changed, 1194 insertions(+), 496 deletions(-) create mode 100644 signing-scheme.md diff --git a/signature-envelope-cose.md b/signature-envelope-cose.md index ff21d5a8..1cb36c9a 100644 --- a/signature-envelope-cose.md +++ b/signature-envelope-cose.md @@ -1,13 +1,19 @@ -# COSE Sign1 Envelope Serialization +# COSE Sign1 Signature Envelope -In COSE ([rfc8152](https://datatracker.ietf.org/doc/html/rfc8152)), data is stored as either payload or headers (protected and unprotected). -Notary v2 supports [COSE_Sign1_Tagged](https://datatracker.ietf.org/doc/html/rfc8152#section-4.2) as a signature envelope with some additional constraints on the header fields. +This specification implements the [Notary v2 Signature specification](signature-specification.md) using +CBOR Object Signing and Encryption (COSE). COSE ([RFC8152](https://datatracker.ietf.org/doc/html/rfc8152)) is a CBOR based envelope format for digital signatures over any type of payload (e.g. CBOR, JSON, binary). +Notary v2 specifically supports [COSE_Sign1_Tagged](https://datatracker.ietf.org/doc/html/rfc8152#section-4.2) as a signature envelope. -A JWS based signature will be persisted with a blob `mediaType` of `"application/cose"` +## Storage + +A COSE signature envelope will be stored in an OCI registry as a blob, and referenced in the signature manifest as a blob with `mediaType` of `"application/cose"`. + +Signature Manifest Example ```jsonc { - "artifactType": "application/vnd.cncf.notary.v2.signature", + "mediaType": "application/vnd.cncf.oras.artifact.manifest.v1+json", + "artifactType": "application/vnd.cncf.notary.signature", "blobs": [ { "mediaType": "application/cose", @@ -21,65 +27,109 @@ A JWS based signature will be persisted with a blob `mediaType` of `"application "size": 16724 }, "annotations": { - "io.cncf.notary.x509certs.fingerprint.sha256": "[\"B7A69A70992AE4F9FF103EBE04A2C3BA6C777E439253CE36562E6E98375068C3\" \"932EB6F5598435D4EF23F97B0B5ACB515FAE2B8D8FAC046AB813DDC419DD5E89\"]" + "io.cncf.notary.x509chain.thumbprint#S256": + "[\"B7A69A70992AE4F9FF103EBE04A2C3BA6C777E439253CE36562E6E98375068C3\",\"932EB6F5598435D4EF23F97B0B5ACB515FAE2B8D8FAC046AB813DDC419DD5E89\"]" } } ``` -Unless explicitly specified as OPTIONAL, all fields are required. +## COSE Payload + +The COSE envelope contains a [Notary v2 Payload](./signature-specification.md#payload). -**Payload**: COSE signs the payload as defined in the [Payload](#payload) section. Detached payloads are not supported. +## Protected Header -**ProtectedHeaders**: Notary v2 supports the following protected headers. Other header fields can be included but will be ignored. +The COSE envelope for Notary v2 uses the following header parameters: +- [Common parameters](https://www.iana.org/assignments/cose/cose.xhtml#header-parameters) + - Label `1`: `alg` + - Label `2`: `crit` + - Label `3`: `content type` +- Additional parameters with collision resistant names. + - `io.cncf.notary.signingScheme` + - `io.cncf.notary.signingTime` + - `io.cncf.notary.authenticSigningTime` + - `io.cncf.notary.expiry` + +Example with Signing Scheme `notary.x509` + +```yaml +{ + / alg / 1: / PS384 / -38, + / crit / 2: [ + 'io.cncf.notary.signingScheme', + 'io.cncf.notary.expiry' + ], + / cty / 3: 'application/vnd.cncf.notary.payload.v1+json', + 'io.cncf.notary.signingScheme': 'notary.x509', + 'io.cncf.notary.signingTime': 1234567890, + 'io.cncf.notary.expiry': 1234567891 +} ``` + +Example with Signing Scheme `notary.x509.signingAuthority` + +```yaml { + / alg / 1: / PS384 / -38, / crit / 2: [ - / cty / 3, - 'signingtime', - 'expiry' + 'io.cncf.notary.signingScheme', + 'io.cncf.notary.authenticSigningTime', + 'io.cncf.notary.expiry' ], - / cty / 3: 'application/vnd.cncf.oras.artifact.descriptor.v1+json', - 'signingtime': 1234567891000, - 'expiry': 1234567891011 + / cty / 3: 'application/vnd.cncf.notary.payload.v1+json', + 'io.cncf.notary.signingScheme': 'notary.x509.signingAuthority', + 'io.cncf.notary.authenticSigningTime': 1234567890, + 'io.cncf.notary.expiry': 1234567891 } ``` -Note: The above example is represented using the [extended CBOR diagnostic notation](https://datatracker.ietf.org/doc/html/rfc8152#appendix-C). +Note: The above examples are represented using the [extended CBOR diagnostic notation](https://datatracker.ietf.org/doc/html/rfc8152#appendix-C). -- **`crit`** (*array of integers or strings*): This REQUIRED property (label: `2`) lists the headers that implementation MUST understand and process. - The array MUST contain `3` (`cty`), and `signingtime`. If `expiry` is presented, the array MUST also contain `expiry`. -- **`cty`** (*string*): The REQUIRED property content-type (label: `3`) is used to declare the media type of the secured content (the payload). -- **`signingtime`** (*datetime*): The REQUIRED property identifies the time at which the signature was generated. -- **`expiry`** (*datetime*): This OPTIONAL property contains the expiration time on or after which the signature must not be considered valid. +- **[`alg`](https://datatracker.ietf.org/doc/html/rfc8152#section-3.1)** (*int*): This REQUIRED parameter (label `1`) defines which signing algorithm was used to generate the signature. The signature algorithm of the signing key (first certificate in `x5chain`) is the source of truth, and during signing the value of `alg` MUST be set corresponding to signature algorithm of the signing key using [this mapping](#supported-alg-header-values) that lists the Notary v2 allowed subset of `alg` values supported by COSE. Similarly verifier of the signature MUST match `alg` with signature algorithm of the signing key to mitigate algorithm substitution attacks. +- **[`crit`](https://datatracker.ietf.org/doc/html/rfc8152#section-3.1)** (*array of int/tstr*): This REQUIRED parameter (label `2`) lists the header parameters that implementations MUST understand and process. It MUST only contain parameters apart from integer labels in the range of 0 to 8. This header MUST contain `io.cncf.notary.signingScheme` which is a required critical header, and optionally contain `io.cncf.notary.authenticSigningTime` and `io.cncf.notary.expiry` if these critical headers are present in the signature. +- **[`content type`](https://datatracker.ietf.org/doc/html/rfc8152#section-3.1)** (*tstr*): The REQUIRED parameter content type (label `3`) is used to declare the media type of the secured content (the payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. +- **`io.cncf.notary.signingScheme`** (*tstr*, critical): This REQUIRED header specifies the [Notary v2 Signing Scheme](./signing-scheme.md) used by the signature. Supported values are `notary.x509` and `notary.x509.signingAuthority`. +- **`io.cncf.notary.signingTime`** (*uint*): This header specifies the time at which the signature was generated. This is an untrusted timestamp, and therefore not used in trust decisions. Its value is the number of seconds from `1970-01-01T00:00Z` in UTC time, commonly known as UNIX timestamp. This claim is REQUIRED and only valid when signing scheme is `notary.x509`. +- **`io.cncf.notary.authenticSigningTime`** (*uint*, critical): This header specifies the authenticated time at which the signature was generated. Its value is the number of seconds from `1970-01-01T00:00Z` in UTC time, commonly known as UNIX timestamp. This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . +- **`io.cncf.notary.expiry`** (*uint*, critical): This OPTIONAL header provides a "best by use" time for the artifact, as defined by the signer. Its value is the number of seconds from `1970-01-01T00:00Z` in UTC time, commonly known as UNIX timestamp. -**UnprotectedHeaders**: Notary v2 supports two unprotected headers: `timestamp` and `x5chain`. +## Unprotected Headers -``` +Notary v2 supports the following unprotected header parameters: + +- `io.cncf.notary.timestampSignature` +- Label `33`: `x5chain` +- `io.cncf.notary.signingAgent` + +```yaml { - 'timestamp': << TimeStampToken >>, / x5chain / 33: [ << DER(leafCert) >>, << DER(intermediate CACert) >>, << DER(rootCert) >> - ] + ], + 'io.cncf.notary.timestampSignature': << TimeStampToken >>, + 'io.cncf.notary.signingAgent': 'notation/1.0.0' } ``` Note: `<<` and `>>` are used to notate the byte string resulting from encoding the data item. -- **`timestamp`** (*byte 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. -- **`x5chain`** (*array of byte strings*): This REQUIRED property (label: `33` by [IANA](https://www.iana.org/assignments/cose/cose.xhtml#header-parameters)) 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 COSE. - The certificate containing the public key corresponding to the key used to digitally sign the COSE MUST be the first certificate. - Optionally, this header can be presented in the protected header. +- **[`x5chain`](https://datatracker.ietf.org/doc/html/draft-ietf-cose-x509-08#section-2)** (*array of bstr*): This REQUIRED parameter (label `33` by [IANA](https://www.iana.org/assignments/cose/cose.xhtml#header-parameters)) contains the ordered 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 COSE. The certificate chain is represented as an array of certificate value byte string, each certificate in the array is DER encoded. The certificate containing the public key corresponding to the key used to digitally sign the COSE MUST be the first certificate, followed by the intermediate and root certificates in the correct order. Refer [*Certificate Chain* unsigned attribute](signature-specification.md#unsigned-attributes) for more details. Optionally, this header can be presented in the protected header. + - **TODO** update signature specification to allow chains in protected header +- **`io.cncf.notary.timestampSignature`** (*bstr*): This OPTIONAL header is used to store countersignature that provides authentic signing time. Only [RFC3161]([rfc3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2)) compliant `TimeStampToken` are supported. + - **TODO** Define the opaque datum (hash of envelope) that is sent to TSA, and how TSA response (time stamp token) is represented in this header. +- **`io.cncf.notary.signingAgent`** (*tstr*): This OPTIONAL header provides the identifier of a client (e.g. Notation) that produced the signature. E.g. `notation/1.0.0`. Refer [*Signing Agent* unsigned attribute](signature-specification.md#unsigned-attributes) for more details. + +## Signature -**Signature**: In COSE, signature is calculated by constructing the `Sig_structure` for `COSE_Sign1`. +In COSE, signature is calculated by constructing the `Sig_structure` for `COSE_Sign1`. The process is described below: 1. Encode the protected header into a CBOR object as a byte string named `body_protected`. 2. Construct the `Sig_structure` for `COSE_Sign1`. - ``` + ```yaml Sig_structure = [ / context / 'Signature1', / body_protected / << ProtectedHeaders >>, @@ -91,28 +141,33 @@ The process is described below: 4. Compute the signature on the `ToBeSigned` constructed in the previous step by using the signature algorithm defined in the corresponding header element: `alg`. This is the value of the signature property used in the signature envelope. -**Signature Envelope**: The final signature envelope is a `COSE_Sign1_Tagged` object, consisting of Payload, ProtectedHeaders, UnprotectedHeaders, and Signature. +## Signature Envelope -``` +The final signature envelope is a `COSE_Sign1_Tagged` object, consisting of Payload, ProtectedHeaders, UnprotectedHeaders, and Signature. + +```yaml 18( [ / protected / << { + / alg / 1: / PS384 / -38, / crit / 2: [ - / cty / 3, - 'signingtime', - 'expiry' + 'io.cncf.notary.signingScheme', + 'io.cncf.notary.authenticSigningTime', + 'io.cncf.notary.expiry' ], - / cty / 3: 'application/vnd.cncf.oras.artifact.descriptor.v1+json', - 'signingtime': 1234567891000, - 'expiry': 1234567891011 + / cty / 3: 'application/vnd.cncf.notary.payload.v1+json', + 'io.cncf.notary.signingScheme': 'notary.x509.signingAuthority', + 'io.cncf.notary.authenticSigningTime': 1234567890, + 'io.cncf.notary.expiry': 1234567891 } >>, / unprotected / { - 'timestamp': << TimeStampToken >>, / x5chain / 33: [ << DER(leafCert) >>, << DER(intermediate CACert) >>, << DER(rootCert) >> - ] + ], + 'io.cncf.notary.timestampSignature': << TimeStampToken >>, + 'io.cncf.notary.signingAgent': 'notation/1.0.0' }, / payload / << descriptor >>, / signature / << sign( << Sig_structure >> ) >> @@ -120,15 +175,23 @@ The process is described below: ) ``` -## Signature Algorithm Requirements +## Implementation Constraints + +### Supported `alg` header values + +Notary v2 implementation MUST enforce the following constraints on signature generation and verification: -The implementation MUST support the following set of algorithms: +1. `alg` parameter value MUST NOT be a symmetric-key algorithm such as `HMAC`. +1. `alg` parameter value MUST be same as that of signature algorithm identified using signing certificate's public key algorithm and size. +1. `alg` parameter values for various signature algorithms is a subset of values supported by [COSE](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). -1. RSASSA-PSS with SHA-256 -1. RSASSA-PSS with SHA-384 -1. RSASSA-PSS with SHA-512 -1. ECDSA on secp256r1 with SHA-256 -1. ECDSA on secp384r1 with SHA-384 -1. ECDSA on secp521r1 with SHA-512 +**Mapping of Notary v2 approved algorithms to COSE `alg` header parameter values** -For ECDSA equivalent NIST curves and ANSI curves can be found at [RFC4492 Appendix A](https://tools.ietf.org/search/rfc4492#appendix-A). + | Signature Algorithm | `alg` Label | + | ------------------------------- | ----------------- | + | RSASSA-PSS with SHA-256 | -37 | + | RSASSA-PSS with SHA-384 | -38 | + | RSASSA-PSS with SHA-512 | -39 | + | ECDSA on secp256r1 with SHA-256 | -7 | + | ECDSA on secp384r1 with SHA-384 | -35 | + | ECDSA on secp521r1 with SHA-512 | -36 | diff --git a/signature-envelope-jws.md b/signature-envelope-jws.md index 43604747..f2eb4824 100644 --- a/signature-envelope-jws.md +++ b/signature-envelope-jws.md @@ -1,13 +1,17 @@ -# JWS Signature Envelope Serialization +# JWS Signature Envelope -JWS JSON Serialization ([RFC7515](https://datatracker.ietf.org/doc/html/rfc7515)) data is stored as either claims or headers (protected and unprotected). -Notary v2 supports JWS JSON Serialization as a signature envelope with some additional constraints on the structure of claims and headers. +This specification implements the [Notary v2 Signature specification](signature-specification.md) using JSON Web Signature (JWS). JWS ([RFC7515](https://datatracker.ietf.org/doc/html/rfc7515)) is a JSON based envelope format for digital signatures over any type of payload (e.g. JSON, binary). JWS is a Notary v2 supported signature format and specifically uses the *JWS JSON Serialization* representation. -A JWS based signature will be persisted with a blob `mediaType` of `"application/jose+json"` +## Storage + +A JWS signature envelope will be stored in an OCI registry as a blob, and referenced in the signature manifest as a blob with `mediaType` of `"application/jose+json"`. + +Signature Manifest Example ```jsonc { - "artifactType": "application/vnd.cncf.notary.v2.signature", + "mediaType": "application/vnd.cncf.oras.artifact.manifest.v1+json", + "artifactType": "application/vnd.cncf.notary.signature", "blobs": [ { "mediaType": "application/jose+json", @@ -21,93 +25,129 @@ A JWS based signature will be persisted with a blob `mediaType` of `"application "size": 16724 }, "annotations": { - "io.cncf.notary.x509certs.fingerprint.sha256": "[\"B7A69A70992AE4F9FF103EBE04A2C3BA6C777E439253CE36562E6E98375068C3\" \"932EB6F5598435D4EF23F97B0B5ACB515FAE2B8D8FAC046AB813DDC419DD5E89\"]" + "io.cncf.notary.x509chain.thumbprint#S256": + "[\"B7A69A70992AE4F9FF103EBE04A2C3BA6C777E439253CE36562E6E98375068C3\",\"932EB6F5598435D4EF23F97B0B5ACB515FAE2B8D8FAC046AB813DDC419DD5E89\"]" } } ``` -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. +## JWS Payload -**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 +The JWS envelope contains a [Notary v2 Payload](./signature-specification.md#payload). + +Example of Notary v2 payload ```jsonc { - "subject": { - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", - "size": 16724, - "annotations": { - "key1": "value1", - "key2": "value2", - ... - } - }, - "iat": 1234567891000, - "exp": 1234567891011 + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", + "size": 16724, + "annotations": { + "io.wabbit-networks.buildId": "123" // user defined metadata + } + } } ``` -The payload contains the subject manifest and other attributes that have to be integrity protected. +## Protected Headers -- **`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. +The JWS envelope for Notary v2 uses following headers -To leverage JWS claims validation functionality already provided by libraries, we have defined `iat`, `exp` as top-level nodes. +- Registered headers - `alg`, `cty`, and `crit` +- [Public headers](https://datatracker.ietf.org/doc/html/rfc7515#section-4.2) with collision resistant names. + - `io.cncf.notary.signingScheme` + - `io.cncf.notary.signingTime` + - `io.cncf.notary.authenticSigningTime` + - `io.cncf.notary.expiry` -**ProtectedHeaders**: Notary v2 supports only three protected headers: `alg`, `cty`, and `crit`. +Example with Signing Scheme `notary.x509` ```jsonc { - "alg": "RS256", - "cty": "application/vnd.cncf.notary.v2.jws.v1", - "crit":["cty"] + "alg": "PS384", + "cty": "application/vnd.cncf.notary.payload.v1+json", + "io.cncf.notary.signingScheme": "notary.x509", + "io.cncf.notary.signingTime": "2022-04-06T07:01:20Z", + "io.cncf.notary.expiry": "2022-10-06T07:01:20Z", + "crit":[ + "io.cncf.notary.signingScheme", + "io.cncf.notary.expiry" + ] } ``` -- **`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"]`. +Example with Signing Scheme `notary.x509.signingAuthority` + +```jsonc +{ + "alg": "PS384", + "cty": "application/vnd.cncf.notary.payload.v1+json", + "io.cncf.notary.signingScheme": "notary.x509.signingAuthority", + "io.cncf.notary.authenticSigningTime": "2022-04-06T07:01:20Z", + "io.cncf.notary.expiry": "2022-10-06T07:01:20Z", + "crit":[ + "io.cncf.notary.signingScheme", + "io.cncf.notary.authenticSigningTime", + "io.cncf.notary.expiry"] +} +``` + +- **[`alg`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.1)**(*string*): This REQUIRED header defines which signing algorithm was used to generate the signature. JWS specification defines `alg` as a required header, that MUST be present and MUST be understood and processed by verifier. The signature algorithm of the signing key (first certificate in `x5c`) is the source of truth, and during signing the value of `alg` MUST be set corresponding to signature algorithm of the signing key using [this mapping](#supported-alg-header-values) that lists the Notary v2 allowed subset of `alg` values supported by JWS. Similarly verifier of the signature MUST match `alg` with signature algorithm of the signing key to mitigate algorithm substitution attacks. +- **[`cty`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.10)**(*string*): The REQUIRED header content-type is used to declare the media type of the secured content (the payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. +- **`io.cncf.notary.signingScheme`**(*string*)(critical): This REQUIRED header specifies the [Notary v2 Signing Scheme](./signing-scheme.md) used by the signature. Supported values are `notary.x509` and `notary.x509.signingAuthority`. +- **`io.cncf.notary.signingTime`**(*string*): This header specifies the time at which the signature was generated. This is an untrusted timestamp, and therefore not used in trust decisions. Its value is a [RFC 3339][rfc3339] formatted date time, the optional fractional second ([time-secfrac][rfc3339][[1](https://datatracker.ietf.org/doc/html/rfc3339#section-5.3)]) SHOULD NOT be used. This claim is REQUIRED and only valid when signing scheme is `notary.x509`. +- **`io.cncf.notary.authenticSigningTime`**(*string*)(critical): This header specifies the authenticated time at which the signature was generated. Its value is a [RFC 3339][rfc3339] formatted date time, the optional fractional second ([time-secfrac][rfc3339][[1](https://datatracker.ietf.org/doc/html/rfc3339#section-5.3)]) SHOULD NOT be used. This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . +- **`io.cncf.notary.expiry`**(*string*)(critical): This OPTIONAL header provides a “best by use” time for the artifact, as defined by the signer. Its value is a [RFC 3339][rfc3339] formatted date time, the optional fractional second ([time-secfrac][rfc3339][[1](https://datatracker.ietf.org/doc/html/rfc3339#section-5.3)]) SHOULD NOT be used. +- **[`crit`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.11)**(*array of strings*): This REQUIRED (optional as per JWS spec, but required in Notary v2 JWS signature) header lists the headers that implementation MUST understand and process. It MUST only contain headers apart from registered headers (e.g. `alg`, `cty`) in JWS specification. This header MUST contain `io.cncf.notary.signingScheme` which is a required critical header, and optionally contain `io.cncf.notary.authenticSigningTime` and `io.cncf.notary.expiry` if these critical headers are present in the signature. + +## Extended Protected Headers for *Notation* Plugins + +See [Extended attributes for *Notation* Plugins](./signature-specification.md#extended-attributes-for-notation-plugins) for detailed description of these headers. + +- **`io.cncf.notary.verificationPlugin`**(*string*)(critical): An OPTIONAL header that specifies the name of the verification plugin that MAY be used to verify the signature. +- **`io.cncf.notary.verificationPluginMinVersion`**(*string*)(critical): An OPTIONAL header that specifies the minimum version of the verification plugin that MUST be used to verify the signature. + +## Unprotected Headers -**UnprotectedHeaders**: Notary v2 supports only two unprotected headers: timestamp and x5c. +Notary v2 supports following unprotected headers: `timestamp`, `x5c` and `io.cncf.notary.signingAgent` ```jsonc { - "timestamp": "", - "x5c": ["", "", ""] + "x5c": ["", "", ""], + "io.cncf.notary.timestampSignature": "", + "io.cncf.notary.signingAgent": "notation/1.0.0" } ``` -- **`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. +- **[`x5c`](https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6)** (*array of strings*): This REQUIRED header contains the ordered 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 chain is represented as a JSON array of certificate value strings, each string in the array is a base64-encoded DER certificate value. The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate, followed by the intermediate and root certificates in the correct order. Refer [*Certificate Chain* unsigned attribute](signature-specification.md#unsigned-attributes) for more details. +- **`io.cncf.notary.timestampSignature`** (*string*): This OPTIONAL header is used to store countersignature that provides authentic signing time. Only [RFC3161]([rfc3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2)) compliant `TimeStampToken` are supported. + - **TODO** Define the opaque datum (hash of envelope) that is sent to TSA, and how TSA response (time stamp token) is represented in this header. +- **`io.cncf.notary.signingAgent`**(*string*): This OPTIONAL header provides the identifier of a client (e.g. Notation) that produced the signature. E.g. “notation/1.0.0”. Refer [*Signing Agent* unsigned attribute](signature-specification.md#unsigned-attributes) for more details. -- **`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 -**Signature**: In JWS signature is calculated by combining JWSPayload and protected headers. +In JWS signature is calculated by combining JWSPayload and protected headers. The process is described below: -1. Compute the Base64Url value of ProtectedHeaders. -2. Compute the Base64Url value of JWSPayload. -3. Build message to be signed by concatenating the values generated in step 1 and step 2 using '.' +### Create the *JWS Signing Input* + +1. Compute the Base64Url value of ProtectedHeaders, this is the value of `protected` property in the signature envelope. +1. Compute the Base64Url value of JWSPayload, this is the value of `payload` property in the signature envelope. +1. Build *JWS Signing Input* 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. +Base64Url encoding used by JWS (*Base64url Encoding* in [RFC 7515 section 2][jws-terminology]) is a URL safe Base64 encoding as defined in [RFC 4648][rfc4648], with all trailing '=' characters omitted and without the inclusion of any additional characters. + +### Generate the signature + +1. Compute the signature on the *JWS Signing Input* constructed in the previous step by using the signature algorithm of the signing key, which MUST match the corresponding protected header `alg`. +2. Compute the Base64Url value of the signature produced in the previous step. + This is the value of the `signature` property in the signature envelope. + +## Signature Envelope + +The final signature envelope comprises of Payload, ProtectedHeaders, UnprotectedHeaders, and Signature, no additional top level fields are supported. Since Notary v2 restricts one signature per signature envelope, the compliant signature envelope MUST be in flattened JWS JSON format. @@ -116,20 +156,26 @@ Since Notary v2 restricts one signature per signature envelope, the compliant si "payload": "", "protected": "", "header": { - "timestamp": "", + "io.cncf.notary.timestamp": "", "x5c": ["", "", ""] }, "signature": "Base64Url( sign( ASCII( . )))" } ``` -**Implementation Constraints**: Notary v2 implementation MUST enforce the following constraints on signature generation and verification: +## Implementation Constraints + +### Supported `alg` header values + +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: +1. `alg` header values for various signature algorithms is a subset of values supported by [JWS][jws-alg-values]. - | Signature Algorithm | `alg` Param Value | +**Mapping of Notary v2 approved algorithms to JWS `alg` header values** + + | Signature Algorithm | `alg` Header Value| | ------------------------------- | ----------------- | | RSASSA-PSS with SHA-256 | PS256 | | RSASSA-PSS with SHA-384 | PS384 | @@ -138,5 +184,20 @@ Since Notary v2 restricts one signature per signature envelope, the compliant si | 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. \ No newline at end of file + +## FAQ + +**Q:** Why JWT is not used as the signature envelope format? + +**A:** JWT uses JWS compact serialization which do not support unsigned attributes. Notary v2 signature requires support for unsigned attributes. Instead we use the *JWS JSON Serialization* representation, which supports unsigned attributes. + +**Q:** Why JWT `exp` and `iat` claims are not used? + +**A:** Unlike JWT which always contains a JSON payload, Notary v2 envelope can support payloads other than JSON, like binary. Reusing the JWT payload structure and claims, limits the Notary v2 JWS envelope to only support JSON payload, which is undesirable. Also, reusing JWT claims requires following same claim semantics as defined in JWT specifications. The [`exp`](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4) claim requires that verifier MUST reject the signature if current time equals or is greater than `exp`, where as Notary v2 allows verification policy to define how expiry is handled. + +[jws-alg-values]: https://datatracker.ietf.org/doc/html/rfc7518#section-3.1 +[rfc3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 +[rfc4648]: https://datatracker.ietf.org/doc/html/rfc4648#section-5 +[jws-terminology]: https://datatracker.ietf.org/doc/html/rfc7515#section-2 diff --git a/signature-specification.md b/signature-specification.md index 1f93e0e3..611f7b7c 100644 --- a/signature-specification.md +++ b/signature-specification.md @@ -14,15 +14,42 @@ The signature manifest has an artifact type that specifies it's a Notary V2 sign ![Signature storage inside registry](media/signature-specification.svg) -- **`artifactType`** (*string*): This REQUIRED property references the Notary version of the signature: `application/vnd.cncf.notary.v2.signature`. +Signature Manifest Example + +```jsonc +{ + "mediaType": "application/vnd.cncf.oras.artifact.manifest.v1+json", + "artifactType": "application/vnd.cncf.notary.signature", + "blobs": [ + { + "mediaType": "application/jose+json", + "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", + "size": 32654 + } + ], + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", + "size": 16724 + }, + "annotations": { + "io.cncf.notary.x509chain.thumbprint#S256": + "[\"B7A69A70992AE4F9FF103EBE04A2C3BA6C777E439253CE36562E6E98375068C3\",\"932EB6F5598435D4EF23F97B0B5ACB515FAE2B8D8FAC046AB813DDC419DD5E89\"]" + } +} +``` + +- **`artifactType`** (*string*): This REQUIRED property references the Notary version of the signature: `application/vnd.cncf.notary.signature`. - **`blobs`** (*array of objects*): This REQUIRED property contains collection of only one [artifact descriptor][artifact-descriptor] referencing signature envelope. - - **`mediaType`** (*string*): This REQUIRED property contains media type of signature envelope blob. The currently supported values are `application/cose` and `application/jose+json`. + - **`mediaType`** (*string*): This REQUIRED property contains media type of signature envelope blob. Following values are supported + - `application/jose+json` + - `application/cose` - **`subject`** (*descriptor*): A REQUIRED artifact descriptor referencing the signed manifest, including, but not limited to image manifest, image index, oras-artifact manifest. - **`annotations`** (*string-string map*): This REQUIRED property contains metadata for the artifact manifest. It is being used to store information about the signature. Keys using the `io.cncf.notary` namespace are reserved for use in Notary and MUST NOT be used by other specifications. - - **`io.cncf.notary.x509.fingerprint.sha256`**: A REQUIRED annotation whose value contains the list of SHA-256 fingerprint of signing certificate and certificate chain used for signature generation. - The list of fingerprints is present as a JSON array string. + - **`io.cncf.notary.x509chain.thumbprint#S256`**: A REQUIRED annotation whose value contains the list of SHA-256 fingerprint of signing certificate and certificate chain (including root) used for signature generation. The annotation name contains the hash algorithm as a suffix (`#S256`) and can be extended to support other hashing algorithms in future. + The list of fingerprints is present as a JSON array string, corresponding to ordered certificates in [*Certificate Chain* unsigned attribute](#unsigned-attributes) in the signature envelope. ### Signature Discovery @@ -34,7 +61,7 @@ Each Notary signature artifact refers to a signature envelope blob. ### Signature Filtering An OCI artifact can have multiple signatures, Notary v2 uses annotations of the signature artifact to filter relevant signatures based on the applicable trust policy. -The Notary v2 signature artifact's `io.cncf.notary.x509.fingerprint.sha256` annotations key MUST contain the list of SHA-256 fingerprints of certificate and certificate chain used for signing. +The Notary v2 signature artifact's `io.cncf.notary.x509chain.thumbprint#S256` annotations key MUST contain the list of SHA-256 fingerprints of certificate and certificate chain used for signing. ## Signature Envelope @@ -43,7 +70,7 @@ A signature envelope consists of the following components: - Payload/Message `m`: The data that is integrity protected - e.g. descriptor of the artifact being signed. - Signed attributes `v`: The signature metadata that is integrity protected - e.g. signature expiration time, creation time, etc. -- Unsigned attributes `u`: Unsigned attributes `u`: These attributes are not signed by the signing key that generates the signature. We anticipate unsigned attributes contain content that may be signed by an different party e.g. Certificate chain signed by a CA, or TSA countersignature signed by the TSA. +- Unsigned attributes `u`: These attributes are not signed by the signing key that generates the signature. We anticipate unsigned attributes contain content that may be signed by an different party e.g. Certificate chain signed by a CA, or TSA countersignature signed by the TSA. - Cryptographic signatures `s`: The digital signatures computed on payload and signed attributes. A signature envelope is `e = {m, v, u, s}` where `s` is signature. @@ -52,74 +79,106 @@ This specification defines the set of signed and unsigned attributes that make u Notary v2 supports the following envelope formats: -- [COSE Sign1](./signature-envelope-cose.md) - [JWS](./signature-envelope-jws.md) +- [COSE Sign1](./signature-envelope-cose.md) ### Payload -Notary v2 requires Payload to be the content **descriptor** of the subject manifest that is being signed. +Notary v2 payload is a JSON document with media type `application/vnd.cncf.notary.payload.v1+json` and has following properties. -1. Descriptor MUST contain `mediaType`, `digest`, `size` fields. -2. 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 signed attributes. The prefix `io.cncf.notary` in annotation keys is reserved for use in Notary v2 and MUST NOT be used outside this specification. -3. Descriptor MAY contain `artifactType` field for artifact manifests, or the `config.mediaType` for `oci.image` based manifests. +- `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. + - 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. -**Examples**: The media type of the content descriptor is `application/vnd.cncf.oras.artifact.descriptor.v1+json`. +#### Examples ```jsonc { - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", - "size": 16724, - "annotations": { - "io.wabbit-networks.buildId": "123" // user defined metadata - } + "subject": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", + "size": 16724, + "annotations": { + "io.wabbit-networks.buildId": "123" // user defined metadata + } + } } ``` ```jsonc { - "mediaType": "sbom/example", - "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", - "size": 32654 + "subject": { + "mediaType": "sbom/example", + "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", + "size": 32654 + } } ``` -### Signature Scheme - -Notary will initially support X509 PKI based identities, which has well established standards for establishing and trusting signing identities, managing key lifetimes and rotation, and revocation. There is interest in the community to support additional trust models based on other standards and techniques (Notary TUF, ledger based). Notary addresses future support for additional trust models through an abstraction called Signature Scheme. A Signature Scheme defines the the specific trust model used for generation and verification of signatures. It includes the supported identity type (e.g. X509 certificates), set of signed and unsigned attributes in the signature to support the trust model, which attributes are required/optional and critical/informational, the signature verification logic, and verification policy switches that are supported (e.g. revocation checks can be skipped, audited, enforced) and how they are processed during signature verification. Notary v2 defines the following signature schemes. - -`notary.signer.x509` - This signature scheme defines the trust model that uses traditional X509 based PKI. Users owns signing keys and use CA issued certificates to represent identity. - -`notary.signingservice.x509` - This signature scheme defines the trust model for a signing service that uses X509 based PKI. In this model, a trusted signing service manages the keys of behalf of the user, and generates signatures. The end user does not have direct access to signing keys. - -When Notary supports additional signature schemes, Notary verification policy may have breaking changes to support newer concepts introduced by the signature scheme, and existing signed artifacts will need to be resigned if they are required to be validated using the new trust system introduced by the signature scheme. - ### Signed Attributes Signed attributes/claims are additional metadata apart from the payload, which are required to support the signature verification process. -- Any claims MUST NOT be stored/appended in the payload itself, as the payload is only parsed and processed once the signature has been verified (signature is valid, and from a trusted key) and trust is established. +- Any metadata that is used to verify the payload itself, and establish trust MUST be stored separately from the payload itself, as signed attributes or claims. Such metadata MUST NOT be stored/appended in the payload, as the payload is only parsed and processed once the signature has been verified and trust is established. - Specific claims can be either required or optional. -- Claims that if present, MUST be processed by a verifier MUST be marked as critical. +- Claims that MUST be processed by a verifier MUST be marked as critical. Some claims may be optional and critical, i.e. they MUST be processed by a verifier only if they are present. - Claims which are informational and do not influence signature verification MUST NOT be marked critical. Notary v2 requires the signature envelope to support the following signed attributes/claims. #### Standard attributes -- **Notary Signature scheme** (critical): The trust model used by Notary currently supports the following values for this claim `notary.signer.x509` and `notary.signingservice.x509`. Both signature schemes use the same set of claims defined in this document. One difference is that `notary.signer.x509` uses a TSA signature to determine authenticated signing time (timestamp), and `notary.signingservice.x509` uses the trusted signing time claim instead, with optional support for TSA signatures. Signature verification MUST consider the signature to be invalid if any other value is used. -- **Signing time**: The time at which the signature was generated. This is a REQUIRED claim for signature scheme `notary.signer.x509`. Though this claim is signed by the signing key, it’s considered unauthenticated as a signer can modify local time and manipulate this claim. More details [here](#signing-time). -- **Trusted signing time** (critical) : The time at which the signature was generated. This is an authenticated signing time which can be generated by a trusted time-stamper, like a signing service. This is a REQUIRED claim for signature scheme `notary.signingservice.x509` . More details [here](#signing-time). -- **Signature Expiry** (critical): The time when a signature is considered to be expired. This is an OPTIONAL claim. [Open issue](https://github.com/notaryproject/notaryproject/issues/141) tracking if this attribute should be removed. -- **Content type** (critical): The content type of the payload. Notary currently supports OCI descriptor of a subject manifest as the payload, supported value is `application/vnd.cncf.oras.artifact.descriptor.v1+json`, other payload types MAY be supported in future. This is a REQUIRED claim. -- **Client identifier**: The version of a client (e.g. Notation) that produced the signature. This is an OPTIONAL claim. It uses the following format `{client}/{version}` e.g. “notation/1.0.0”. This claim in intended to be used for diagnostic and troubleshooting purposes. +- **Signing Scheme** (critical): A REQUIRED claim that defines the [Notary v2 Signing Scheme](./signing-scheme.md) used by the signature. This attribute dictates the rest of signature schema - the set of signed and unsigned attributes to be included in the signature. Supported values are `notary.x509` and `notary.x509.signingAuthority`. +- **Signing Time**: A claim that indicates the time at which the signature was generated. Though this claim is signed by the signing key, it’s considered unauthenticated as a signer can modify local time and manipulate this claim. More details [here](#signing-time). This claim is REQUIRED and only valid when signing scheme is `notary.x509` . +- **Authentic Signing Time** (critical): The authenticated time at which the signature was generated. This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . More details [here](#signing-time). +- **Expiry** (critical): An OPTIONAL claim that provides a “best by use” time for the artifact, as defined by the signer. More details [here](#expiry). +- **Content Type** (critical): A REQUIRED claim that indicates the content type of the [payload](#payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. Other payload types MAY be supported in future. + +#### Extended attributes + +Implementations of Notary v2 signature spec MAY include additional signed attributes in the signature envelope. +These attributes MAY be marked critical, i.e. the attribute MUST be understood and processed by a verifier, unknown critical attributes MUST cause signature verification to fail. +Usage of extended signed attributes which are marked critical in signature will have implications on portability of the signature, these are discussed in [Signature Portability](#signature-portability) section. + +#### Extended attributes for *Notation* Plugins + +This section documents extended attributes used by Notary v2 reference implementation *Notation* to support plugins. +Plugins is a *Notation* concept that allows parts of signing and verification logic to be performed by an external provider. +*Signing plugins* allow *Notation* to be extended for integration with remote keys remote key management services and signing services, where as *verification plugins* allow for customization of verification logic. +Detailed specification for plugins can be found [here](https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#notation-extensibility-for-signing-and-verification). +These extended attributes are documented in this spec, as other Notary V2 implementations may encounter these attributes if they verify a signature that indicated it required a verification plugin for complete signature verification. + +- **Verification Plugin** (critical): An OPTIONAL attribute that specifies the name of the verification plugin that MAY be used to verify the signature e.g. “com.example.nv2plugin”. +[Notation plugin](https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#plugin-contract) aware implementations use this attribute to load and execute a *Notation* compliant plugin. +The plugin participates in the overall signature verification workflow and performs specific steps in it. +- **Verification Plugin Minimum Version** (critical): An OPTIONAL attribute that specifies the minimum version of the verification plugin that MUST be used to verify the signature. +A Notation plugin aware implementations MUST use this attribute to verify the signature with a plugin with matching or higher plugin version. +The plugin MUST use [Semantic Versioning](https://semver.org/) (SemVer) to use this feature i.e the `get-plugin-metadata` plugin command MUST return a SemVer compliant version in the response. +A use case for this feature is for a plugin publisher to address security bug in older plugin version, by setting the minimum version to the plugin version with fixes. + +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). -- **Certificate Chain**: This property contains the list of X.509 certificate or certificate chain. This is a REQUIRED attribute. The certificate chain is authenticated using against a trust store as part of signature validation. -- **TSA counter signature** : The time stamp token generated for a given signature. Only [RFC3161](ietf-rfc3161) compliant TimeStampToken are supported. This is an OPTIONAL attribute. +- **Certificate Chain**: This is a REQUIRED attribute that contains the ordered list of X.509 public certificates associated with the signing key used to generate the signature. The ordered list starts with the signing certificates, any intermediate certificates and ends with the root certificate. The certificate chain MUST be authenticated against a trust store as part of signature validation. Specific requirements for the certificates in the chain are provided [here](#certificate-requirements). +- **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”. + +## Signature Algorithm Requirements + +The implementation MUST support the following set of algorithms: + +1. RSASSA-PSS with SHA-256 +1. RSASSA-PSS with SHA-384 +1. RSASSA-PSS with SHA-512 +1. ECDSA on secp256r1 with SHA-256 +1. ECDSA on secp384r1 with SHA-384 +1. ECDSA on secp521r1 with SHA-512 + +For ECDSA equivalent NIST curves and ANSI curves can be found at [RFC4492 Appendix A](https://tools.ietf.org/search/rfc4492#appendix-A). ### Algorithm Selection @@ -132,30 +191,46 @@ The signing certificate's public key algorithm and size MUST be used to determin | RSA | 4096 | RSASSA-PSS with SHA-512 | | EC | 256 | ECDSA on secp256r1 with SHA-256 | | EC | 384 | ECDSA on secp384r1 with SHA-384 | -| EC | 512 | ECDSA on secp521r1 with SHA-512 | +| EC | 521 | ECDSA on secp521r1 with SHA-512 | ### Certificate Requirements -The **signing certificate** MUST meet the following minimum requirements: +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. -- 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. +#### Root and Intermediate CA Certificates -The **timestamping certificate** MUST meet the following minimum requirements: +The CA certificates MUST meet the following 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 MUST be marked critical. - The value of extension MUST be id-kp-timeStamping ([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. +1. **[Basic Constraints:](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9)** +The `basicConstraints` extension MUST be present and MUST be marked as critical. The `cA` field MUST be set `true`. +The [`pathLenConstraint`](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9) field is OPTIONAL. If present, it MUST be verified against the depth of the chain below that CA certificate. (If value is null consider it as not present) +1. **[Key Usage:](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3)** +The `keyUsage` extension MUST be present and MUST be marked critical. Bit positions for `keyCertSign` MUST be set. + +#### Leaf Certificates + +The leaf or end certificates MUST meet the following requirements + +1. **[Basic Constraints:](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9)** +The `basicConstraints` extension is OPTIONAL and can OPTIONALLY be marked as critical. If present, the `cA` field MUST be set to `false`. +1. **[Key Usage:](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3)** +The `keyUsage` extension MUST be present and MUST be marked critical. Bit positions for `digitalSignature` MUST be set. The Bit positions for `keyCertSign` and `cRLSign` MUST NOT be set. +1. **[Extended Key Usage:](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.12)** The `extendedKeyUsage` extension is OPTIONAL and can OPTIONALLY be marked as critical. + - **For codesigning certificate:** If present, the value MUST contain `id-kp-codeSigning` and MUST NOT contain `anyExtendedKeyUsage`, `serverAuth`, `emailProtection` and `timeStamping`. + - **For timestamping certificate:** If present, the value MUST contain `id-kp-timeStamping` and MUST NOT contain `anyExtendedKeyUsage`, `serverAuth`, `emailProtection` and `codeSigning`. +1. **Key Length** 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. + +#### Other requirements + +1. The certificates in the signature MUST be ordered list of X.509 certificate or certificate chain i.e. the certificate containing the public key used to digitally sign the payload must be the first certificate, followed by the intermediate and root certificates in the correct order. This also means + - The certificate MUST NOT chain to multiple parents/roots. + - The certificate chain MUST NOT contain a certificate that is unrelated to the certificate chain. +1. A valid certificate chain MUST contain a minimum of two certificates - a leaf and a root certificate. +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. ## FAQ @@ -164,24 +239,74 @@ The **timestamping certificate** MUST meet the following minimum requirements: **A:** The `mediaType` of artifact manifest's blob identifies the signature envelope type. The client implementation can use the aforementioned `mediaType` to parse the signature envelope. -**Q: How will Notary v2 handle non-backward compatible changes to signature format?** +**Q: How will Notary v2 support multiple payload formats?** + +**A:** The Signature envelope MUST have a versioning mechanism to support multiple payload formats. -**A:** The Signature envelope MUST have a versioning mechanism to support non-backward compatible changes. -[COSE_Sign1_Tagged](./signature-envelope-cose.md) signature envelope versioning is achieved by the `cty` field in ProtectedHeaders. -[JWS JSON serialization](./signature-envelope-jwt.md) signature envelope versioning is achieved by the `cty` field in ProtectedHeaders. +- For [JWS JSON serialization](./signature-envelope-jwt.md) signature envelope, versioning is achieved by the `cty` field in ProtectedHeaders. +- For [COSE_Sign1_Tagged](./signature-envelope-cose.md) signature envelope, versioning is achieved by the `content type` (label: `3`) field in ProtectedHeaders. ## Appendix ### Signing time -The signing time denotes the time at which the signature was generated. A X509 certificate has a defined [validity](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5) during which it can be used to generate signatures. The signing time must be greater than or equal to certificate's `notBefore` attribute, and signing time must be less than or equal to certificate's `notAfter` attribute. Signatures generated after the certificate expires are considered invalid. A trusted timestamp allows a verifier to determine if the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature be treated as valid when a certificate is revoked, if the certificate was revoked after the signature was generated. In the absence of a trusted timestamp, signatures are considered invalid after certificate expires, and all signatures are considered revoked when a certificate is revoked. +The signing time denotes the time at which the signature was generated. A X509 certificate has a defined [validity](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5) during which it can be used to generate signatures. The signing time must be greater than or equal to certificate's `notBefore` attribute, and signing time must be less than or equal to certificate's `notAfter` attribute. Signatures generated after the certificate expires are considered invalid. An authentic timestamp, like TSA countersignature, allows a verifier to determine if the signature was generated when the certificate was valid. It also allows a verifier to determine if a signature be treated as valid when a certificate is revoked, if the certificate was revoked after the signature was generated. In the absence of an authentic timestamp, signatures are considered invalid after certificate expires, and all signatures are considered revoked when a certificate is revoked. + +### Expiry + +This is an optional feature that provides a “best by use” time for the artifact, as defined by the signer. Notary v2 allows users to include an optional expiry time when they generate a signature. The expiry time is not set by default and requires explicit configuration by users at the time of signature generation. The artifact is considered expired when the current time is greater than or equal to expiry time, users performing verification can either configure their trust policies to fail the verification or even accept the artifact with expiry date in the past using policy. This is an advanced feature that allows implementing controls for user defined semantics like deprecation for older artifacts, or block older artifacts in a production environment. Users should only include an expiry time in the signed artifact after considering the behavior they expect for consumers of the artifact after it expires. Users can choose to consume an artifact even after the expiry time based on their specific needs. + +### Signature Portability + +Portability of signatures is associated with the portability of associated artifacts which are being signed. +OCI artifacts are inherently location agnostic, artifacts can be pulled from and pushed to any OCI compliant registry to which a user has access. +The artifacts themselves can be classified as follow. + +1. *Public Artifacts* - Artifacts that are distributed publicly for broad consumption. +Artifacts distributed via public registries fall in this category. +E.g. Public images for software distributed by software vendors, and open source projects. +Signatures associated with these artifacts require broad portability. +1. *Private Artifacts* - Artifacts that are private to a user or organization, and may be shared with limited parties. +E.g. Images for containerized applications and services used within an organization, or shared with limited authorized parties. +Based on user requirements a private artifact can have different levels of portability, the signature’s portability should at least match the the artifact’s portability. + +*Notary v2 signature portability* is based on the following + +**Signature discovery** + +Notary v2 addressed signature discovery by storing signatures in the same registry (location) where an artifact is present. +This is supported through [ORAS artifact spec](https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md) which allows reference artifacts such as signatures, SBOMs to be associated with existing artifacts like Images. +Notary v2 allows multiple signatures to be associated with an artifact, and clients may automatically push signatures for an artifact to a destination registry when a signed artifact moves from one registry to other. + +**Verification requirements** + +Notary v2 supports a range of signed artifacts intended for public and private distribution. +Signatures generated by Notary v2 without extended signature attributes marked critical can be verified in any environment where Notation client or another Notary v2 standards compliant verification tool is available, without any additional dependencies. +It should be noted that revocations checks, which usually relies on an external mechanism such as CRL/OCSP may require the verification environment to have access to local network or public internet, to have access to the CRL/OCSP endpoint. + +Notary v2 also supports signatures generated using compliant signing plugins, which allow vendors to optionally provide additional features on top of Notary v2 standard features. +Verification of these signatures may require additional dependencies like Notary v2 compliant verification plugin, making these signatures more appropriate to use where broad portability may not be required for the associated signed artifact. +This allows users to implement security controls required for their organizations, that are not broadly applicable and may take time to standardize. +E.g. Integration with a signature transparency log as part of signature verification. + +Based on user’s requirements, a user can select appropriate signing mechanism that produces signatures with desired portability. +Notation signatures without any critical extended attributes do not impose any additional dependency requirements for verifiers as these can be validated with just the Notation client. +Whereas, Notation signatures that contain critical extended attributes will require additional dependencies for signature validation, either on Notary v2 compliant plugins or equivalent tooling which may not be available in all environments. +Similarly, Notary v2 compliant plugin vendors should be aware that usage of extended signed attributes which are marked critical in signature will have implications on portability of the signature. + +### Guidelines for Notary v2 Implementors + +Implementations of Notary v2, can choose to be [Notation plugin protocol](./specs/plugin-extensibility.md#plugin-contract) aware or not. If an implementation chooses to be plugin protocol aware, and it encounters the Verification Plugin and Verification Plugin minimum version attributes during signature verification, it MUST process these attributes. This involves finding the appropriate plugin and the version to use, and executing `verify-signature` plugin command with correct inputs and processing the plugin response, as per the [Verification Plugin interface](../specs/../notaryproject-specs/specs/plugin-extensibility.md#verification-extensibility). + +Alternatively, an implementation of Notary v2 can choose not to implement plugin protocol. + +- The implementation MUST itself perform equivalent verification logic that is usually performed by plugin specified in the signature. +- An implementation MUST fail signature verification if it cannot perform the equivalent verification logic, as skipping the plugin equivalent verification logic will cause incorrect and inconsistent signature verification behavior. +- An implementation MAY choose to support a set of known plugin’s verification logic and fail others. [annotation-rules]: https://github.com/opencontainers/image-spec/blob/main/annotations.md#rules +[oci-descriptor]: https://github.com/opencontainers/image-spec/blob/main/descriptor.md [artifact-descriptor]: https://github.com/oras-project/artifacts-spec/blob/main/descriptor.md -[ietf-cose]: https://datatracker.ietf.org/doc/html/rfc8152 -[ietf-cose-section-4]: https://datatracker.ietf.org/doc/html/rfc8152#section-4 -[ietf-cose-section-4.2]: https://datatracker.ietf.org/doc/html/rfc8152#section-4.2 -[ietf-cose-appendix-c]: https://datatracker.ietf.org/doc/html/rfc8152#appendix-C [ietf-rfc3161]: https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2 [oras-artifact-manifest]: https://github.com/oras-project/artifacts-spec/blob/main/artifact-manifest.md [oras-artifacts-referrers]: https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md diff --git a/signing-scheme.md b/signing-scheme.md new file mode 100644 index 00000000..b7db5c64 --- /dev/null +++ b/signing-scheme.md @@ -0,0 +1,99 @@ +# Signing Scheme + +Signatures are primarily used to provide a consumer of an artifact the following guarantees - integrity (the signed artifact was not tampered after signing it), and authenticity (the artifact was indeed signed by the entity who claims to have signed it). +X.509 PKI based identities are commonly used to for signing artifacts. +It has well established standards for issuing and representing identities (CAs and certificates) which sign artifacts, mechanism to establish authenticity using trust stores, and support for managing key lifetimes and rotation, and revocation of identities. +There is interest in the community to support other ways to establish integrity and authenticity, based on other systems and techniques (Notary TUF, ledger based). +These approaches also can provide different feature sets than ones traditionally provided by standard X.509 PKI based signing. +E.g. TUF is geared for software update systems and provides a freshness guarantee. + +Notary v2 will initially support X.509 PKI identity based signing, but provide the flexibility for additional systems through an abstraction called Signing Scheme. +The Signing Scheme covers aspects of signature generation and verification, and formalizes the feature set (guarantees) provided by the signature produced using a signing scheme. +Generally it covers the following aspects, but can be extended to other aspects as required by newer systems. + +*Signature creation* + +1. Supported entities that can generate signatures - typically an end user generates the signature, other models can be supported using signature scheme. +1. Representation of identities - e.g. X.509 certificates are used to represent end users and entities which verify the end user’s authenticity (CAs). +1. Signature schema - Defines the signed and unsigned attributes to be included in a signature envelope, and which attributes are required, optional and critical. + +*Signature verification* + +1. Mechanism to establish trust in end users and other entities (like CAs). +1. Set of guarantees available to the verifier apart from integrity and authenticity. + +## Signing Scheme + +Notary v2 currently defines the following Signing Schemes. + +`notary.x509` - Defines a signing scheme that uses the traditional signing workflow in which an end user generates signatures using X.509 certificates. + +`notary.x509.signingAuthority` - Defines a signing scheme in which a `signing authority` generates signatures on behalf of an end user (the signature requestor) using X.509 certificates. +A trusted signing authority is defined as a third party service that is trusted both by the end user (the signature requestor) and verifying entity to generate signatures. +Authorities trusted by users should have mechanisms for validating, logging, monitoring, and auditing access to their systems and publish incidence response plans. +A certificate authority (CA) will need to demonstrate only validated entities were issued an X.509 certificate that chains to their root, and a time stamp authority (TSA) will need to demonstrate the time-stamp signing keys were only used within their service. +Similarly, a signing authority (SA) will need to demonstrate signing keys were only used within their service and only validated entities were allowed to generate signatures using the service. + +* A signature envelope can only specify one Signing Scheme +* When Notary supports an additional Signing Scheme + * Existing signed artifacts MUST be resigned if they need to be verified using the new signing scheme defined verification process. + * Existing clients used by verifying entity MUST be updated to newer versions that support verifying signatures that use the new signing scheme, otherwise the signatures with newer signing schemes which are unknown to existing clients will fail signature verification. Signatures that use older signing schemes which are known to existing clients will continue to verify correctly. + * The Notary verification policy language in *trustpolicy.json* MAY have breaking changes to support newer concepts/configuration elements introduced by the new signing scheme. + The breaking changes are addressed by introducing new major version in the versioned *trustpolicy.json* . + +## Signature Creation + +The signature envelope contains `Signing Scheme` as an required and critical attribute. +This attribute dictates the rest of signature schema - the set of signed and unsigned attributes to be included in the signature. + +Both `notary.x509` and `notary.x509.signingAuthority` signing schemes use the similar signature schema (set of signed and unsigned attributes) with the following differences. + +* `notary.x509` MUST use a countersignature from trusted source to determine authentic signing time (timestamp). +This is supported through the the Timestamp signature unsigned attribute in the signature envelope. Currently Notary V2 uses a [RFC3161](ietf-rfc3161) compliant TSA signature for this purpose. + +* `notary.x509.signingAuthority` MUST use a timestamp attribute that is generated by a signing service as part of the original signature itself to determine authenticated signing time (timestamp). +This is supported through the *Authentic Signing time* attribute in the signature envelope. + +## Signature Verification + +Signature verification requires that a `Signing Scheme` attribute is present in the signature and it’s treated as critical i.e. the attribute MUST be understood and processed by the verifier. +For the JWS signature format, the attribute name is `io.cncf.notary.signingScheme` , with supported values `notary.x509` and `notary.x509.signingAuthority`. +Any other value will fail signature verification i.e when Notary supports an additional Signing Scheme, clients (like *notation*) MUST be updated to a version that supports the new signing scheme. + +### Trust Stores + +Each *Signing Scheme* defines the set of trust store types (e.g. CA) that it uses for signature verification. + +`notary.x509` + +* Uses trusts store types Certificate Authority (CA) and Timestamping Authority (TSA) during signature verification. +The signature is verified against the trust store of type CA, and the *Timestamp signature* is verified against the trust store of type TSA (to determine the signing time). +* For signature verification to be successful + * The verifying entity’s trust store MUST contain the trusted root certificates under named trust stores of type CA (`{CONFIG}/notation/truststore/x509/ca`) and TSA(`{CONFIG}/notation/truststore/x509/tsa`) + * The named trust stores MUST be specified in *trustpolicy.json*. E.g. *trustPolicy.trustStores* with value of `ca:acme-rockets,tsa:acme-tsa`. + +`notary.x509.signingAuthority` + +* Uses trusts store type Signing Authority during signature verification. +The signature is verified against the trust store of type Signing Authority. +The signing time is determined using the *Authentic Signing time* attribute in the signature envelope, and does not rely on a seperate TSA generated Timestamp signature. +* For signature verification to be successful + * The verifying entity’s trust store MUST contain the trusted root certificates under named trust stores of type CA (`{CONFIG}/notation/truststore/x509/signingAuthority`). + * The named trust stores MUST be specified in *trustpolicy.json*. E.g. *trustPolicy.trustStores* with value of `signingAuthority:foobar` . + +## FAQ + +*Q:* What is the relationship of Signing Scheme with Signature Envelope format? + +*A:* Signing Scheme aims to be agnostic of the Signature Envelope format. +A given signing scheme can be implemented through any signature envelope format (such as JWS or COSE) as long as it can support the required signature schema used by the signing scheme. + +*Q:* Why is the trust store used for Signing Authority (`x509/signingAuthority`) distinct from trust store for Certificate Authority (`x509/ca`), why can’t they share the same trust store? + +*A:* Signing Authority is a different type of trusted entity as compared to Certificate Authority (CA) or Timestamping Authority (TSA). +A CA is trusted for verifying the identity of a signing entity (end user) and issuing it a certificate, whereas a TSA is trusted to generate authentic timestamp. +In contrast, an SA is trusted to generate signatures on behalf of an end user (signature requestor) and also to generate authentic timestamp as part of the signature. +If we use a shared trust store for CA and SA, a verifying entity does not have the ability to differentiate between CA and SA when the verifying entity configures trusted roots in the trust store. +This has implication such as an end user with CA issued certificate can masquerade themselves as an SA by generating a signature with signing scheme `notary.x509.signingAuthority`. + +[ietf-rfc3161]: https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2 diff --git a/specs/plugin-extensibility.md b/specs/plugin-extensibility.md index 087056d7..c2d2ed38 100644 --- a/specs/plugin-extensibility.md +++ b/specs/plugin-extensibility.md @@ -1,6 +1,6 @@ # Notation Extensibility for Signing and Verification -Keys and associated certificates used for signing artifacts using Notary could be available to users through varied solutions that provide secure key generation, storage and cryptographic operations. Some are well established with standards like [PIV](https://csrc.nist.gov/projects/piv/piv-standards-and-supporting-documentation) and [PKCS #11](https://docs.oasis-open.org/pkcs11/pkcs11-base/v3.0/os/pkcs11-base-v3.0-os.html) implemented by hardware tokens, smart cards. More recent options which use varied authentication and API protocols are remote key management services and signing services by third party vendors and cloud service providers. Notation will support a few built-in integrations with standard providers, and will provide plugin interfaces for users, and vendors to implement their own integrations with the solutions they use. This allows a plugin publisher to implement, test, release and patch their solutions independent of Notation’s development and release cycle. This document provides specification for the plugin model, and interfaces to implement. This specification aims to work both for [existing](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md) and future signature formats adopted by Notary. +Keys and associated certificates used for signing artifacts using Notary could be available to users through varied solutions that provide secure key generation, storage and cryptographic operations. Some are well established with standards like [PIV](https://csrc.nist.gov/projects/piv/piv-standards-and-supporting-documentation) and [PKCS #11](https://docs.oasis-open.org/pkcs11/pkcs11-base/v3.0/os/pkcs11-base-v3.0-os.html) implemented by hardware tokens, smart cards. More recent options which use varied authentication and API protocols are remote key management services and signing services by third party vendors and cloud service providers. Notation will support a few built-in integrations with standard providers, and will provide plugin interfaces for users, and vendors to implement their own integrations with the solutions they use. This allows a plugin publisher to implement, test, release and patch their solutions independent of Notation’s development and release cycle. This document provides specification for the plugin model, and interfaces to implement. This specification aims to work both for [existing](../signature-specification.md) and future signature formats adopted by Notary. ## Terminology @@ -14,53 +14,106 @@ Keys and associated certificates used for signing artifacts using Notary could b * A plugin publisher MUST be able to distribute and patch a Notation plugin independently of Notation’s release cycle. * The plugin mechanism MUST work across commonly used OS platforms like Linux, Windows and macOS. -* The plugin interface contract MUST be versioned. This version is different from Notation's release version. It will be used to accomodate for additional capabilities and contract changes post initial release of Notation. +* The plugin interface contract MUST be versioned. This version is different from Notation's release version. It will be used to accommodate for additional capabilities and contract changes post initial release of Notation. * A plugin MAY implement a subset of capabilities (features) available in plugin contract. E.g A plugin may implement signing feature, but not verification. * Notation and plugins MAY be updated independently in an environment. * Notation MUST work with a plugin that implements a matching or lower minor version of the plugin contract. Notation SHALL NOT support using a plugin with higher version of plugin contract. * A plugin MUST support a single plugin contract version, per major version. -Notation will invoke plugins as executable, pass parameters using command line arguments, and use standard IO streams to pass request/response payloads. This mechanism is used as Go language (used to develop [Notation library](https://github.com/notaryproject/notation-go-lib)) does not have a [in built support](https://github.com/golang/go/issues/19282) to load and execute plugins that works across OS platforms. Other mechanisms like gRPC require every plugin to be implemented as a service/daemon. +Notation will invoke plugins as executable, pass parameters using command line arguments, and use standard IO streams to pass request/response payloads. This mechanism is used as Go language (used to develop [Notation library](https://github.com/notaryproject/notation-go)) does not have a [in built support](https://github.com/golang/go/issues/19282) to load and execute plugins that works across OS platforms. Other mechanisms like gRPC require every plugin to be implemented as a service/daemon. -### Plugin installation and config +### Plugin lifecycle management + +#### Installation + +Plugin publisher will provide instructions to download and install the plugin. Plugins intended for public distribution should also include instructions for users to verify the authenticity of the plugin. + +**Open Item** : [Plugin install paths](https://github.com/notaryproject/notation/issues/167) + +To enumerate all available plugins the following paths are scanned: +* Unix-like OSes: + * `$HOME/notation/plugins` +* On Windows: + * `%USERPROFILE%\notation\plugins` + +Each plugin executable and dependencies are installed under directory `~/notation/plugins/{plugin-name}` with an executable under that directory `~/notation/plugins/{plugin-name}/notation-{plugin-name}`. + +Any directory found inside `~/notation/plugins` is considered potential plugin "candidates". Anything found which is not a directory is ignored and is not considered as a plugin candidate. + +To be considered a valid plugin a candidate must pass each of these "plugin candidate tests": + +* The directory must contain an executable named `notation-{plugin-name}`. +* The executable MUST be a regular file, symlinks are not supported. Implementation MUST validate that the executable is a regular file, before executing it, and fail it it does not meet this condition. +* On Windows, executables must have a `.exe` suffix. +* Must, where relevant, have appropriate OS "execute" permissions (e.g. Unix x bit set) for the current user. +* Must actually be executed successfully and when executed with the subcommand `get-plugin-metadata` must produce a valid JSON metadata (and nothing else) on its standard output (schema to be discussed later). + +#### Commands + +* List + +`notation plugin list` + +List all valid plugins. + +### Using a plugin for signing -* Plugin publisher will provide instructions to download and install the plugin. Plugins intended for public distribution should also include instructions for users to verify the authenticity of the plugin. -* Each plugin executable and dependencies are installed under directory `~/.notation/plugins/{plugin-name}` with an executable under that directory `~/.notation/plugins/{plugin-name}/notation-{plugin-name}`. The executable can be a shim which calls plugin dependecies installed elsewhere on the file system. * To use a plugin for signing, the user associates the plugin as part of registering a signing key. E.g. * `notation key add --name "mysigningkey" --id "keyid" --plugin "com.example.nv2plugin"` - * In the example, the command registers a signing key in `/notation/config.json`, where `mysigningkey` is a friendly key name to refer during signing operation from the CLI, `id` is an key identifier known to plugin that is used for signing, and the value of `plugin` specifies it's using a plugin located at `~/.notation/plugins/com.example.nv2plugin/notation-com.example.nv2plugin`. + * In the example, the command registers a signing key in `/notation/config.json`, where `mysigningkey` is a friendly key name to refer during signing operation from the CLI, `id` is an key identifier known to plugin that is used for signing, and the value of `plugin` specifies it's using a plugin located at `~/notation/plugins/com.example.nv2plugin/notation-com.example.nv2plugin`. ```jsonc { "signingKeys": { "default": "", "keys": [ - { - "name": "mysigningkey", - "id" : "keyid", - "plugin": "com.example.nv2plugin" - } + { + "name": "mysigningkey", + "id" : "keyid", + "plugin": "com.example.nv2plugin", + // Optional configuration as required by the plugin + "pluginConfig" : { + "key1" : "value1", + "key 2" : "value 2" + } + } ] } } ``` +### Plugin configuration + +Plugins may require additional configuration to work correctly, that could be set out of band, or provided by notation when it invokes a plugin. To support plugin configuration through notation, the key configuration provides an optional `pluginConfig` map of of key value pairs, that is passed as-is by notation to the plugin. + +* To use this feature, plugin authors MUST define and document the set of plugin configuration keys-values, which is set by users when they associate a plugin with signing key. +* Plugin authors SHOULD NOT use plugin configuration to store sensitive configuration in plaintext, such as authentication keys used by the plugin etc. + +For the previous example, plugin config can be set using command line arguments as part of registering a key with the `notation key add` command. E.g. + +* `notation key add --name "mysigningkey" --id "keyid" --plugin "com.example.nv2plugin" --pluginConfig key1=value1,"key 2"="value 2"` + +Plugin config can be also set/overriden during signing with the `notation sign` command. Following example overrides value for `key 2` already set in `config.json` by previous command. + +* `notation sign $IMAGE --key "mysigningkey" --pluginConfig "key 2"=newValue2` + ### Plugin contract * Notation will invoke the plugin executable for each command (e.g. sign, verify), pass inputs through `stdin` and get output through `stdout` and `stderr`. * The command will be passed as the first argument to the plugin e.g. `notary-{plugin-name} `. A JSON request is passed using `stdin`. The plugin is expected to return a JSON response through `stdout` with a `0` exit code for successful response, and a non-zero exit code with a JSON error response in `stderr` for error response. Each command defines its request, response and error contract. To avoid any additional content like debug or info level logging from dependencies and inbuilt libraries, the plugin implementation should redirect any output to `stdout` on initialization, and only send the JSON response away from `stdout` when the command execution completes. E.g. For golang, set [`os.Stdout`](https://pkg.go.dev/os#pkg-variables) to point to a log file. -* Every request JSON will contain a `contract-version` top level attribute whose value will indicate the plugin contract version. Contract version is revised when there are changes to command request/response, new plugin commands are introduced, and supported through Notation. -* For an error response, every command returns a non-zero exit code 1, with an OPTIONAL JSON error response in `stderr`. It is recommended to return an error response to help user troubleshoot the error. There is no need to send different exit codes for different error conditions, as Notation (which will call the plugin and parse the response) will use `error-code` in the error response to interpret different error conditions if needed. Notation will attempt to parse the error response in `stderr` when exit code is 1, else treat it as a general error for any other non-zero exit codes. An implementation can +* Every request JSON will contain a `contractVersion` top level attribute whose value will indicate the plugin contract version. Contract version is revised when there are changes to command request/response, new plugin commands are introduced, and supported through Notation. +* To maintain forward compatibility plugin implementors MUST ignore unrecognized attributes in command request which are introduced in minor version updates of the plugin contract. +* For an error response, every command returns a non-zero exit code 1, with an OPTIONAL JSON error response in `stderr`. It is recommended to return an error response to help user troubleshoot the error. There is no need to send different exit codes for different error conditions, as Notation (which will call the plugin and parse the response) will use `error-code` in the error response to interpret different error conditions if needed. Notation will attempt to parse the error response in `stderr` when exit code is 1, else treat it as a general error for any other non-zero exit codes. ```jsonc { // Each command defines expected error codes - "error-code" : "", + "errorCode" : "", // Plugin defined error message. - "error-message" : "User friendly error message" + "errorMessage" : "User friendly error message", // Optional plugin defined additional key value pairs related to the error. - "error-metadata" : { + "errorMetadata" : { "key1" : "value1", "key2" : "value2" } @@ -69,12 +122,19 @@ Notation will invoke plugins as executable, pass parameters using command line a ### Plugin metadata -* Every plugin MUST implement a metadata discovery command called `discover`. -* Notation will invoke this command as part of signing and verification workflows to discover the capabilities of the plugin and subsequently invoke commands corresponding to the capabilities. Notation will invoke the `discover` command every time a signing or verification workflow is invoked to discover metadata. Notation will not cache the response of `discover` command as it involves invalidating cache when a plugin is updated, detecting a plugin update can be non trivial. The `discover` command is expected to return only static data, and plugin implementors should avoid making remote calls in this command. +* Every plugin MUST implement a metadata discovery command called `get-plugin-metadata`. +* Notation will invoke this command as part of signing and verification workflows to discover the capabilities of the plugin and subsequently invoke commands corresponding to the capabilities. Notation will invoke the `get-plugin-metadata` command every time a signing or verification workflow is invoked to discover metadata. Notation will not cache the response of `get-plugin-metadata` command as it involves invalidating cache when a plugin is updated, detecting a plugin update can be non trivial. The `get-plugin-metadata` command is expected to return only static data, and plugin implementors should avoid making remote calls in this command. -***discover*** +***get-plugin-metadata*** -*Request* - None +*Request* + +```jsonc +{ + // Optional plugin configuration, map of string-string + "pluginConfig" : { } +} +``` *Response* All response attributes are required. @@ -83,20 +143,29 @@ All response attributes are required. { // Plugin name that matches its install dir // e.g. "com.example.nv2plugin". - "plugin-name" : "", - // Plugin friendly name. - "plugin-friendly-name" : "", + "name" : "", + // Plugin description. + "description" : "description>", // Plugin publisher controlled version. - "plugin-version" : "", + "version" : "", // Plugin webpage for support or documentation. - "plugin-url" : "", + "url" : "", // List of contract versions supported by the plugin, one per major version - "supported-contract-versions" : [ ], + "supportedContractVersions" : [ ], - // Currently one of - // SIGNATURE_GENERATOR or - // SIGNATURE_ENVELOPE_GENERATOR + // List of one or more capabilities supported by plugin. + // Valid values are + // SIGNATURE_GENERATOR.RAW + // SIGNATURE_GENERATOR.ENVELOPE + // SIGNATURE_VERIFIER.TRUSTED_IDENTITY + // SIGNATURE_VERIFIER.REVOCATION_CHECK + // + // A signing plugin implements either SIGNATURE_GENERATOR.RAW + // or SIGNATURE_GENERATOR.ENVELOPE capability. + // + // A verification plugin implements + // one or more of SIGNATURE_VERIFIER capabilities. "capabilities" : [ ] @@ -105,9 +174,9 @@ All response attributes are required. *plugin-name* - Plugin name uses reverse domain name notation to avoid plugin name collisions. -*supported-contract-versions* - The list of contract versions supported by the plugin. Currently this list must include only one version, per major version. Post initial release, Notation may add new features through plugins, in the form of new commands (e.g. tsa-sign for timestamping), or additional request and response parameters. Notation will publish updates to plugin interface along with appropriate contract version update. Backwards compatible changes (changes for which older version of plugin continue to work with versions of Notation using newer contract version) like new optional parameters on existing contracts, and new commands will be supported through minor version contract updates, breaking changes through major version updates. Plugin `discover` command returns the contract version a plugin supports. Notation will evaluate the minimum plugin version required to satisfy a user's request, and reject the request if the plugin does not support the required version. +*supported-contract-versions* - The list of contract versions supported by the plugin. Currently this list must include only one version, per major version. Post initial release, Notation may add new features through plugins, in the form of new commands (e.g. tsa-sign for timestamping), or additional request and response parameters. Notation will publish updates to plugin interface along with appropriate contract version update. Backwards compatible changes (changes for which older version of plugin continue to work with versions of Notation using newer contract version) like new optional parameters on existing contracts, and new commands will be supported through minor version contract updates, breaking changes through major version updates. To maintain forward compatibility plugin implementors MUST ignore unrecognized attributes in command request which are introduced in minor version updates of the plugin contract. Plugin `get-plugin-metadata` command returns the contract version a plugin supports. Notation will evaluate the minimum plugin version required to satisfy a user's request, and reject the request if the plugin does not support the required version. -*capabilities* - Non empty list of features supported by a plugin. Each capability such as `SIGNATURE_ENVELOPE_GENERATOR` requires one of more commands to be implemented by the plugin. When new features are available for plugins to implement, an implementation may choose to not implement it, and therefore will not include the feature in capabililies. Notation will evaluate the capability required to satisfy a user’s request, and reject the request if the plugin does not support the required capability. +*capabilities* - Non empty list of features supported by a plugin. Each capability such as `SIGNATURE_GENERATOR.RAW` requires one of more commands to be implemented by the plugin. When new features are available for plugins to implement, an implementation may choose to not implement it, and therefore will not include the feature in capabilities. Notation will evaluate the capability required to satisfy a user’s request, and reject the request if the plugin does not support the required capability. ## Signing interfaces @@ -123,70 +192,136 @@ This interface targets plugins that integrate with providers of basic cryptograp #### Signing workflow using plugin -1. Given a user request to sign `oci-artifact`, with `keyName` (the friendly key name) -1. Pull the image manifest using `oci-artifact` url, and construct a descriptor -1. Append any user provided metadata and Notary metadata as descriptor annotations. -1. Determine if the registered key uses a plugin -1. Execute the plugin with `discover` command - 1. If plugin supports capability `SIGNATURE_GENERATOR` - 1. Generate the payload to be signed. For JWS this includes [JWS](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#supported-signature-envelopes) payload with `subject` claim as descriptor, additional JWS claims, and protected headers. - 2. Execute the plugin with `generate-signature` command, set `request.keyDefinition` to key definition corresponding to `keyName`, `request.payload` to the generated payload. - 3. Validate the generated signature, return an error if any of the checks fails. - 1. Check if `response.signingAlgorithm` is one of [supported signing algorithms](https://github.com/notaryproject/notaryproject/blob/main/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), or revocation check. - 3. Check that the `response.certificateChain` conforms to [Certificate Requirements](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#certificate-requirements). - 4. Assemble the JWS Signature envelope using `response.signature`, `response.signingAlgorithm` and `response.certificateChain`. Notation may also generate and include timestamp signature in this step. - 5. Generate a signature manifest for the given signature envelope. - 2. Else if plugin supports capability `SIGNATURE_ENVELOPE_GENERATOR` *(covered in next section)* +1. Given a user request to sign `oci-artifact`, with signing key `keyName` (the friendly key name) +2. Pull the image manifest using `oci-artifact` url, and construct a descriptor +3. Append any user provided metadata and Notary metadata as descriptor annotations. +4. Determine if the registered key uses a plugin +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). + 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. + 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. + 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 -***generate-signature*** +#### describe-key + +This command is used to get metadata for a given key. *Request* ```jsonc { - "contract-version" : , + "contractVersion" : "", - // Complete key definition from - // /notation/config.json /signingKeys/keys with matching key name - "keyDefinition" : , + // Key id associated with signing key (keyName) + // in config.json /signingKeys/keys + "keyId": "", - "payload" : + // Optional plugin configuration, map of string-string + "pluginConfig" : { } } ``` -*keyDefinition* : Required field that contains the complete key definition, which includes friendly name (`name`), key identifier (`id`) and any additional custom fields required by the plugin. -For example, after registering a key `mysigningkey` and plugin, the command `notation sign $IMAGE --key "mysigningkey"` will subsequently invoke plugin with `request.keyDefinition` set to following value. +*keyId* : Required field that has the key identifier (`keyId`) associated with signing key `keyName` in `config.json`. + +*pluginConfig* : Optional field for plugin configuration. For details, see [Plugin Configuration](#plugin-configuration) section. + +*Response* ```jsonc -{ - "name": "mysigningkey", - "id" : "keyid", - "plugin": "com.example.nv2plugin" +{ + // The same key id as passed in the request. + "keyId" : "", + "keySpec" : "" } ``` -*payload* : Required field that contains payload to be signed. This is currently the JWSPayload that includes the subject descriptor and other JWS claims. +*keySpec* : One of following [supported key types](../signature-specification.md#algorithm-selection) - `RSA_2048`, `RSA_3072`, `RSA_4096`, `EC_256`, `EC_384`, `EC_512`. + +NOTE: This command can also be used as part of `notation key describe {key-name}` which will include the following output + +* Summary of key definition from `config.json` +* If the key has an associated plugin + * Output of plugin `discover` command + * Output of `describe-key` command for the specific key + +#### generate-signature + +This command is used to generate the raw signature for a given payload. + +*Request* + +```jsonc +{ + "contractVersion" : "", + + // Key id associated with signing key (keyName) + // in config.json /signingKeys/keys + "keyId": "", + + // Optional plugin configuration, map of string-string + "pluginConfig" : { }, + + // The key spec for the given key id + "keySpec" : "", + + // Hash algorithm associated with the key spec, plugin must + // hash the payload using this hash algorithm + "hashAlgorithm" : "SHA_256" | "SHA_384" | "SHA_512", + + // Payload to sign, this is base64 encoded + "payload" : "" +} +``` + +*keyId* : Required field that has the key identifier (`keyId`) associated with signing key `keyName` in `config.json`. + +*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. + +*hashAlgorithm* : Required field that specifies Hash algorithm corresponding to the signature algorithm determined by `keySpec` for the key. + +*payload* : Required field that contains base64 encoded payload to be signed. For JWS, the *payload to sign* is base64 encoded (a second time) before sending to the plugin. *Response* + All response attributes are required. ```jsonc { // The same key id as passed in the request. - "keyId" : , - "signature" : , - "signingAlgorithm" : + "keyId" : "", + "signature" : "", + "signingAlgorithm" : "", "certificateChain": ["Base64(DER(leafCert))","Base64(DER(intermediateCACert))","Base64(DER(rootCert))"] } ``` -*signingAlgorithm* : One of following [supported signing algorithms](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection), Notation uses this validate the signature, and to set the appropriate attribute in signature envelope (e.g. JWS `alg`). `RSASSA_PSS_SHA_256`, `RSASSA_PSS_SHA_384`, `RSASSA_PSS_SHA_512`, `ECDSA_SHA_256`, `ECDSA_SHA_384`, `ECDSA_SHA_512`. +*signingAlgorithm* : One of following [supported signing algorithms](../signature-specification.md#algorithm-selection), Notation uses this validate the signature, and to set the appropriate attribute in signature envelope (e.g. JWS `alg`). `RSASSA_PSS_SHA_256`, `RSASSA_PSS_SHA_384`, `RSASSA_PSS_SHA_512`, `ECDSA_SHA_256`, `ECDSA_SHA_384`, `ECDSA_SHA_512`. *certificateChain* : Ordered list of certificates starting with leaf certificate and ending with root certificate. -*Error codes* +#### Error codes for describe-key and generate-signature * VALIDATION_ERROR - Any of the required request fields was empty, or a value was malformed/invalid. Includes condition where the key referenced by `keyId` was not found. * UNSUPPORTED_CONTRACT_VERSION - The contract version used in the request is unsupported. @@ -205,53 +340,65 @@ This interface targets plugins that in addition to signature generation want to 1. Pull the image manifest using `image` url, and construct a descriptor 1. Append any user provided metadata and Notary metadata as descriptor annotations. 1. Determine if the registered key uses a plugin -1. Execute the plugin with `discover` command - 1. If plugin supports capability `SIGNATURE_ENVELOPE_GENERATOR` - 1. Execute the plugin with `generate-envelope` command, set `request.keyDefinition` to key definition (from `/notation/config.json`) corresponding to `keyName`, `request.payload` to base64 encoded descriptor, `request.payloadType` to `application/vnd.oci.descriptor.v1+json` and `request.signatureEnvelopeType` to a pre-defined type (default to `application/vnd.cncf.notary.v2.jws.v1`). - 1. `response.signatureEnvelope` contains the base64 encoded signature envelope, value of `response.signatureEnvelopeType` MUST match request.signatureEnvelopeType. - 1. 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`. - 1. Check if the signing algorithm in the signature envelope is one of [supported signing algorithms](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection). - 1. Check that the [`subject` descriptor](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#supported-signature-envelopes) in JWSPayload in `response.signatureEnvelope` matches `request.payload`. Plugins MAY append additional annotations but MUST NOT replace/override existing descriptor attributes and annotations. - 1. 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. - 1. Check that the certificate chain in `response.signatureEnvelope` confirm to [Certificate Requirements]. - 1. Generate a signature manifest for the given signature envelope, and append `response.annotations` to manifest annotations. - 1. Else if plugin supports capability `SIGNATURE_GENERATOR` *(covered in previous section)* - 1. Return an error - -**generate-envelope** +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` + 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. + 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)* + 3. Return an error + +#### generate-envelope + +This command is used to generate the complete signature envelope for a given payload. *Request* All request attributes are required. ```jsonc { - "contract-version" : , + "contractVersion" : "", - // Complete key definition from /notation/config.JSON /signingKeys/keys with matching key name - "keyDefinition" : , + // Key id associated with signing key (keyName) + // in config.json /signingKeys/keys + "keyId": "", - "payload" : , + // Optional plugin configuration, map of string-string + "pluginConfig" : { }, + + "payload" : "", // The type of payload - currently a descriptor - "payloadType" : "application/vnd.oci.descriptor.v1+json", + "payloadType" : "application/vnd.cncf.notary.payload.v1+json", // The expected response signature envelope "signatureEnvelopeType" : "application/vnd.cncf.notary.v2.jws.v1" } ``` -*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. +*keyId* : Required field that has the key identifier (`keyId`) associated with signing key `keyName` in `config.json`. + +*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. e.g. `notation sign $IMAGE --key {key-name} --signatureFormat {some-new-format}` *Response* All response attributes are required. - ```jsonc { - "signatureEnvelope": , - "signatureEnvelopeType" : "application/vnd.cncf.notary.v2.jws.v1" + "signatureEnvelope": "", + "signatureEnvelopeType" : "application/vnd.cncf.notary.v2.jws.v1", // Annotations to be appended to Signature Manifest annotations "annotations" : { @@ -261,7 +408,7 @@ All response attributes are required. } ``` -*Error codes* +#### Error codes for generate-envelope * VALIDATION_ERROR - Any of the required request fields was empty, or a value was malformed/invalid. Includes condition where the key referenced by `keyId` was not found, payload type or signature envelope type in the request is unsupported by the plugin. * UNSUPPORTED_CONTRACT_VERSION - The contract version used in the request is unsupported. @@ -272,15 +419,173 @@ All response attributes are required. ## Verification extensibility -TBD [notaryproject/roadmap#27](https://github.com/notaryproject/roadmap/issues/27) +[notaryproject/roadmap#27](https://github.com/notaryproject/roadmap/issues/27) + +### Requirements + +* The interface MUST NOT be tied to a specific signature envelope format. +* The interface MUST NOT allow a plugin to customize the complete [signature verification workflow](../trust-store-trust-policy-specification.md#signature-verification). Certain steps like identifying the applicable trust policy for the artifact, signature envelope schema validation, integrity checks, certificate chain validation against trust store will be performed by Notation, and cannot be customized by a plugin. +* The interface MUST allow plugins to customize verification logic for specific supported steps + * Trusted Identity validation + * Revocation check validation +* The interface MAY be extended in future to support additional steps which can be customized through a plugin. +* The interface MUST be agnostic of sequencing of steps in signature verification workflow as implemented in Notation or other implementations. +* The interface MUST allow processing of [extended attributes](../signature-specification.md#extended-attributes) that are not part of Notary v2 [standard attributes](../signature-specification.md#standard-attributes). + +### Guidelines for Verification plugin publishers + +* Usage of extended signed attributes which are marked critical in signature will have implications on portability of the signature. +The environment where verification occurs will require dependencies on either a compatible verification plugin in addition to Notation, or a compliant verification tool that understands the extended signed attributes. +Therefore, signatures intended for public distribution which require broad signature portability SHOULD avoid extended signed attributes which are marked critical. + +* Signatures which require a plugin for verification may be distributed privately (e.g. within an organization) or publicly (e.g. via a public registry). +If the plugin publisher wants their plugin used publicly they SHOULD publish specifications for the verification logic the plugin performs and test vectors. +This allows Notary v2 implementations to perform the same logic themselves, if they choose to. + +### Signature Verifier + +#### Verification workflow using plugin + +1. If signature envelope contains *Verification Plugin* attribute, check if the plugin with the given name exists, else fail signature verification. + 1. For the resolved plugin, call the `get-plugin-metadata` plugin command to get plugin version and capabilities. + 2. If signature envelope contains *Verification plugin minimum version* attribute. + 1. Validate that *Verification plugin minimum version* and plugin version are in SemVer format + 2. Validate that plugin version is greater than or equal to *Verification plugin minimum version* + 3. Fail signature verification if these validations fail + 3. Validate if plugin capabilities contains any `SIGNATURE_VERIFIER` capabilities + 1. Fail signature verification if a matching plugin with `SIGNATURE_VERIFIER` capability cannot be found. Include *Verification Plugin Name* attribute as a hint in error response. +2. Complete steps *Identify applicable trust policy* and *Proceed based on signature verification level* from [signature verification workflow](../trust-store-trust-policy-specification.md#steps). +3. Complete steps *Validate Integrity, Validate Expiry* and *Validate Trust Store* from [signature verification workflow](../trust-store-trust-policy-specification.md#steps). +4. Based on the signature verification level, each validation may be enforced, logged or skipped. +5. Populate `verify-signature` request. NOTE: The processing order of remaining known attributes does not matter as long as they are processed before the end of signature verification workflow. + 1. Set `request.signature.criticalAttributes` to the set of [standard Notary v2 attributes](../signature-specification.md#standard-attributes) that are marked critical, from the signature envelope. + 2. Set `request.signature.criticalAttributes.extendedAttributes` to extended attributes that Notation does not recognize. Notation only supports JSON primitive types for critical extended attributes. If a signature producer required complex types, it MUST set the value to a primitive type by encoding it (e.g. Base64), and MUST be decoded by the plugin which processed the attribute. + 3. Set `request.signature.unprocessedAttributes` to set of critical attribute names that are marked critical, but unknown to Notation, and therefore cannot be processed by Notation. + 4. Set `request.signature.certificateChain` to ordered array of certificates from signing envelope. + 5. Set `request.trustPolicy.trustedIdentities` to corresponding values from user configured trust policy that is applicable for given artifact and was identified in step 2. + 6. Set `request.trustPolicy.signatureVerification` to the set of verification checks that are supported by the plugin, and are required to be performed as per step 2. Notation may not populate some checks in this array, if it determined them as “skipped” as per user’s trust policy. +6. Process `verify-signature.response` + 1. Validate `response.verificationResults` map keys match the set of verifications requested in `request.trustPolicy.signatureVerification` . Fail signature verification if they don't match. + 2. For each element in `response.verificationResults` map, process each result as follows + 1. If `verificationResult.success` is true, proceed to next element. + 2. Else if `verificationResult.success` is `false`, check if the failure should be enforced or logged based on value of `signatureVerification` level in Trust Policy. Proceed to next element if failure is logged, else fail signature verification with `verificationResult.reason`. + 3. Validate values in `response.processedAttributes` match the set of values in `request.signature.unprocessedAttributes`. If they don't, fail signature verification, as the plugin did not process a critical attribute that is unknown to the caller. +7. Perform any remaining steps in [signature verification workflow](../trust-store-trust-policy-specification.md#signature-verification) which are not covered by plugin's verification capabilities. These steps MUST process any remaining critical attributes. Fail signature verification if any critical attributes are unprocessed at the end of this step. + +#### *verify-signature* command + +*Request* + +```jsonc +{ + "contractVersion" : "", + + "signature" : { + // Array of all Notary V2 defined critical attributes and their values + // in the signature envelope. Agnostic of header names and value serialization + // in specific envelope formats like JWS or COSE. + "criticalAttributes" : + { + "contentType" : "application/vnd.cncf.notary.payload.v1+json", + // One of notary.default.x509 or notary.signingAuthority.x509 + "signingScheme" : "notary.default.x509", + // Value is always RFC 3339 formatted date time string + "expiry": "2022-10-06T07:01:20Z", + "extendedAttributes" : { + // Map of extended attributes + // Extended attributes values support JSON primitive values string, number and null. + "name" : primitive-value + } + }, + + // Array of names of critical attributes that plugin + // caller does not understand and does not intend to process. + "unprocessedAttributes" : [ ], + + // Certificate chain from signature envelope. + "certificateChain" : [ ] + }, + + "trustPolicy" : { + // Array of trusted identities as specified in + // trust policy. + "trustedIdentities": [], + // Array of verification checks to be performed by the plugin + "signatureVerification" : [ + "SIGNATURE_VERIFIER.TRUSTED_IDENTITY", + "SIGNATURE_VERIFIER.REVOCATION_CHECK" + ] + }, + + // Optional plugin configuration, map of string-string + "pluginConfig" : { } +} +``` + +The request can be extended in future by including other elements of signature envelope and trust policy as required to customize additional steps in verification. + +*Response* +All response attributes are required. + +```jsonc +{ + "verificationResults" : { + // Map of results. Key must match the set of + // verification capabilities + // in verify-signature request's trustPolicy.signatureVerification attribute. + // SIGNATURE_VERIFIER.TRUSTED_IDENTITY + // SIGNATURE_VERIFIER.REVOCATION_CHECK + "SIGNATURE_VERIFIER.TRUSTED_IDENTITY" : + { + // The result of a verification check + "success" : true | false, + // Reason for check being successful or not, + // required if value of success attribute is false. + "reason" : "" + }, + "SIGNATURE_VERIFIER.REVOCATION_CHECK" : + { + // The result of a verification check + "success" : true | false, + // Reason for check being successful or not, + // required if value of success attribute is false. + "reason" : "" + } + }, + // Array of strings containing critical attributes processed by the plugin. + "processedAttributes" : [ + ] +} +``` + +*verificationResults* : Verifications performed by the plugin. This is a map where the keys MUST match set of verify capabilities in `verify-signature` request's `trustPolicy.signatureVerification` attribute, and values are objects with following attributes + +* *success* (required): The `boolean` verification result. +* *reason* (optional): Reason associated with verification being successful or not, REQUIRED if value of success field is `false`. + +*processedAttributes* (required): Array of strings containing critical attributes processed by the plugin. Values must be one or more of attribute names in `verify-signature` request's `signature.unprocessedAttributes`. + +#### Error codes for *verify-signature* + +* VALIDATION_ERROR - Any of the required request fields was empty, or a value was malformed/invalid. +* UNSUPPORTED_CONTRACT_VERSION - The contract version used in the request is unsupported. +* ACCESS_DENIED - Authentication/authorization during signature verification, this may be due to external calls made by the plugin. +* TIMEOUT - The verification process timed out and can be retried by Notation. +* THROTTLED - The verification process was throttled and can be retried by Notation. +* ERROR - Any general error that does not fall into previous error categories. ## Threat Model TBD [#135](https://github.com/notaryproject/notaryproject/issues/135) +## FAQ + +**Q: Will Notation generate timestamp signature for Signature Envelope Generator plugin or its responsibility of plugin publisher?** + +**A :** If the envelope generated by a Signature Envelope Generator plugin contains timestamp signature, Notation will not append additional timestamp signature, else it will generate the timestamp signature and append it to the envelope as an unsigned attribute. + ## Open Items -* Will Notation generate timestamp signature for Signature Envelope Generator plugin or its responsibility of plugin publisher? + * [Issue #151](https://github.com/notaryproject/notation/issues/151) - Add Notation command line options to pass raw signature generated by existing crypto tools. * What standard providers should be supported? -* Do we need to support file based plugin configuration, or pass-through plugin configuration passed as is from Notation CLI to the plugin? -* Suport for chaining plugins. It allows us to seperate out and compose things like signing, TSA integration, push to transparency log. \ No newline at end of file +* Support for chaining plugins. It allows us to separate out and compose things like signing, TSA integration, push to transparency log. diff --git a/trust-store-trust-policy-specification.md b/trust-store-trust-policy-specification.md index 83b92845..af8625f5 100644 --- a/trust-store-trust-policy-specification.md +++ b/trust-store-trust-policy-specification.md @@ -1,194 +1,236 @@ # Trust Store and Trust Policy Specification -This document describes how Notary v2 signatures are evaluated for trust. +Notary v2 currently supports X.509 based PKI and identities, and uses a trust store and trust policy to determine if a signed artifact is considered authentic. + The document consists of the following sections: -- **[Trust Store](#trust-store)**: Defines set of signing identity that user trusts. -- **[Trust Policy](#trust-policy)**: Defines how the artifact is evaluated for trust. +- **[Trust Store](#trust-store)**: Contains a set of trusted identities through which trust is derived for the rest of the system. For X.509 PKI, the trust store typically contains a set of root certificates. +- **[Trust Policy](#trust-policy)**: A policy language which indicates which identities are trusted to produce artifacts. Both trust store and trust policy need to be configured by users/administrators before artifact signature can be evaluated. +- **[Signature Verification](#signature-verification)**: Describes how signatures are evaluated using the policy, to determine if a signed artifact is authentic. This section is meant for implementors of Notary v2 standards. -## Trust Store +Other types of identities and trust models may be supported in future, which may introduce other constructs/policy elements to support signature evaluation. -Users who consume and execute the signed artifact from a registry need a mechanism to specify the trusted producers. -This is where Trust Store is used. +## Scenario -Trust store allows users to specify two kinds of identities: +All examples use the actors defined in Notary v2 [scenario](https://github.com/notaryproject/notaryproject/blob/main/scenarios.md#scenario-0-build-publish-consume-enforce-policy-deploy) -- **Certificates**: These are the signing certificates or certificates in the certificate chain of the signing certificate. -- **Timestamping Certificates**: These are the timestamping certificates or certificates in the certificate chain of the timestamping certificate. +- Wabbit Networks company builds, signs and distributes their `net-monitor` software though public registries. +- ACME Rockets consumes the `net-monitor` software from a public registry importing the artifacts and reference artifacts (signatures, SBoMs) into their private registry. The private registry also contains additional artifacts that ACME Rockets themselves sign. -The trust store is represented as JSON data structure as shown below: +## Trust Store -```json -{ - "version": "1.0", - "trustStores": { - "trust-store-name-1": { - "identities": { - "x509Certs": [ - "-----BEGIN CERTIFICATE-----\ncertificate1\n-----END CERTIFICATE-----\n", - "-----BEGIN CERTIFICATE-----\ncertificate2\n----END CERTIFICATE-----\n" - ], - "tsaX509Certs": [ - "-----BEGIN CERTIFICATE-----\ntsaCertificate1\n-----END CERTIFICATE-----\n", - "-----BEGIN CERTIFICATE-----\ntsaCertificate2\n-----END CERTIFICATE-----\n" - ] - } - }, - "trust-store-name-2": { - "identities": { - "x509Certs": [ - "-----BEGIN CERTIFICATE-----\ncertificate1\n-----END CERTIFICATE-----\n", - "-----BEGIN CERTIFICATE-----\ncertificate2\n----END CERTIFICATE-----\n" - ], - "tsaX509Certs": [ - "-----BEGIN CERTIFICATE-----\ntsaCertificate2\n-----END CERTIFICATE-----\n", - "-----BEGIN CERTIFICATE-----\ntsaCertificate3\n----END CERTIFICATE-----\n" - ] - } - } - } -} +Contains a set of trusted identities through which trust is derived for the rest of the system. For X.509 PKI, the trust store typically contains root certificates. + +- The Notary v2 trust store consists of multiple named collections of certificates, called named stores. +- Following certificate formats are supported - Files with extension .pem, .crt and .cer, the files are expected to contain certificate(s) in DER (binary) format or PEM format (base-64 encoded DER). +- The trust store is a directory location, under which each sub directory is considered a named store, that contains zero or more certificates. The name of this sub directory is used to reference the specific store in trust policy. +- Symlinks are not supported for the named store directories or certificate files. Implementation MUST validate that the named store directory or certificate files are not symlinks, and fail if it does not meet this condition. +- Certificates in a trust store are root certificates. Placing intermediate certificates in the trust store is not recommended this is a form of certificate pinning that can break signature verification unexpectedly anytime the intermediate certificate is rotated. + +Notary v2 uses following directory structure to represent the trust store. The example shows named stores `acme-rockets` and `wabbit-networks`, which are subseqently references in the trust policy. Without this reference, presence of a named store and certificates in it does not confer trust automatically to the named store. The trust store is configured ahead of verification time, by an out of band mechanism that is beyond the scope of this document. Different entities and organizations have their own processes and policies to configure and distribute trust stores. + +```text +$XDG_CONFIG_HOME/notation/trust-store + /x509 + /ca + /acme-rockets + cert1.pem + cert2.pem + /sub-dir # sub directory is ignored + cert-3.pem # certs under sub directory is ignored + /acme-rockets-ca2 + cert1.pem + /wabbit-networks + cert3.crt + /tsa + /publicly-trusted-tsa + tsa-cert1.pem ``` -Property Description: +The Trust store currently supports two kinds of identities, additional identities may be supported in future : -- **`version`**(*string*): This REQUIRED property is the version of the trust store. - The supported value is `1.0` -- **`trustStores`**(*object*): This REQUIRED property represents the parent node containing multiple trust stores. - Each trust store is identified by the key associated with it like 'trust-store-name-1', 'trust-store-name-2'. - - **`identities`**(*object*): This REQUIRED property represents the collection of different types of identities. - There are two types of identifies that Notary v2 supports: x509 certificates and x509 timestamping certificates. - - **`x509Certs`**(*array of strings*): This REQUIRED property specifies a list of x509 certificates in PEM format. - The collection MUST contain at least one certificate. - - **`tsaX509Certs`**(*array of strings*): This OPTIONAL property specifies a list of x509 timestamping certificates in PEM format. - If the `tsaX509Certs` key is present then collection MUST contain at least one timestamping certificate. +- **Certificates**: The `x509/ca` trust store contains named stores that contain Certificate Authority (CA) root certificates. +- **Timestamping Certificates**: The `x509/tsa` trust store contains named stores with Time Stamping Authority (TSA) root certificates. **NOTE** TSA based timestamping will not be available in `notation` RC1. -## Trust Policy - -Users who consume and execute the signed artifact from a registry need a mechanism to specify how the artifacts should be evaluated for trust, this is where a trust policy is used. -Trust policy allows users to control the artifact's integrity, expiry, and revocation aspect of signature evaluation. - -### Artifact Integrity - -The artifact MUST be signed and has not been altered. - -### Artifact Expiry +Any additional sub directories under names stores and certificates in it are ignored. **NOTE**: Implementation SHOULD warn if it finds sub directories with certificates under a named store, to help diagnose misconfigured store. -Trust policy allows users to define how the system should behave when the artifact's signature is expired or signing identity is expired or timestamping identity is expired. - -If artifact expiry validations are enforced the implementation MUST perform the following validations: - -1. If signature expiry is present then signature MUST NOT be expired. -1. If signing identity is certificate and - 1. The timestamp signature is not present then the signing certificate and the certificate chain MUST NOT be expired. - 1. The timestamp signature is present then the timestamp signature MUST be valid. - At the time of timestamping, signing certificate (including certificate chain) MUST NOT be expired. - Also, the timestamping certificate and certificate chain MUST NOT be expired. - -### Artifact Revocation +## Trust Policy -Trust policy also allows users to control how the system should behave when signing identity or timestamping identity is revoked +Users who consume signed artifact from a registry use the trust policy to specify trusted identities which will sign the artifacts, and level of signature verification to use. -If revocation validations are enforced implementation MUST perform the following validations: +### Trust Policy Schema -1. If signing identity is a certificate then signing certificate and certificate chain MUST NOT be revoked. -1. If the timestamp signature is present then the timestamping certificate and certificate chain MUST NOT be revoked. +The trust policy is a JSON document, here are some examples. -The implementation MUST support both [OCSP](https://datatracker.ietf.org/doc/html/rfc6960) and [CRL](https://datatracker.ietf.org/doc/html/rfc5280) based revocations. -Since revocation check requires network call and network call can fail because of a variety of reasons such as revocation endpoint is unavailable, network connectivity issue, DDoS attack, etc the implementation MUST support both `fail-open` or `fail-close` use cases. +Trust policy for a simple scenario where ACME Rockets uses only artifacts signed by their CI/CD. Any third party artifacts also are vetted and signed by ACME Rockets. -- `fail-open`: If revocation endpoint is not reachable, consider artifact as not revoked. -- `fail-close`: If revocation endpoint is not reachable, consider artifact as revoked. +```jsonc +{ + "version": "1.0", + "trustPolicies": [ + { + // Policy for all artifacts, from any registry location. + "name": "wabbit-networks-images", // Name of the policy. + "registryScopes": [ "*" ], // The registry artifacts to which the policy applies. + "signatureVerification": { // The level of verification - strict, permissive, audit, skip. + "level" : "audit" + }, + "trustStores": ["ca:acme-rockets"], // The trust stores that contains the X.509 trusted roots. + "trustedIdentities": [ // Identities that are trusted to sign the artifact. + "x509.subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io, OU=Finance, CN=SecureBuilder" + ] + } + ] +} +``` -The trust policy is represented as JSON data structure as shown below: +Trust policy for the scenario where ACME Rockets uses some artifacts signed by Wabbit Networks and some signed by ACME Rockets. -```json +```jsonc { "version": "1.0", "trustPolicies": [ { - "name": "verify-signature", - "scopes": [ - "wabbit-networks.io/software/product1" - "wabbit-networks.io/software/product2" ], - "trustStores": [ "trust-store-name-1", "trust-store-name-2" ], - "trustAnchors": [ - "subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io" + // Policy for set of artifacts signed by Wabbit Networks + // that are pulled from ACME Rockets repository + "name": "wabbit-networks-images", + "registryScopes": [ + "registry.acme-rockets.io/software/net-monitor", + "registry.acme-rockets.io/software/net-logger" ], - "expiryValidations": { - "signatureExpiry": "enforce | warn", - "signingIdentityExpiry": "enforce | warn", - "timestampExpiry": "enforce | warn" + "signatureVerification": { + "level" : "strict" }, - "revocationValidations": { - "signingIdentityRevocation": "enforceWithFailOpen | enforceWithFailClose | warn | skip", - "timestampRevocation": "enforceWithFailOpen | enforceWithFailClose | warn | skip" + "trustStores": ["wabbit-networks"], + "trustedIdentities": [ + "x509.subject: C=US, ST=WA, L=Seattle, O=wabbit-networks.io, OU=Security Tools" + ] + }, + { + // Exception policy for a single unsigned artifact pulled from + // Wabbit Networks repository + "name": "unsigned-image", + "registryScopes": [ "registry.wabbit-networks.io/software/unsigned/net-utils" ], + "signatureVerification": { + "level" : "skip" } }, { - "name": "skip-signature-verification", - "scopes": [ "wabbit-networks.io/software/unsigned/productA" ], - "skipSignatureVerification": true, + // Policy that uses custom verification level to relax the strict verification. + // It logs expiry and skips recovocation check for a specific artifact. + "name": "use-expired-image", + "registryScopes": [ "registry.acme-rockets.io/software/legacy/metrics" ], + "signatureVerification": { + "level" : "strict", + "override" : { + "expiry" : "log", + "revocation" : "skip" + } + }, + "trustStores": ["ca:acme-rockets"], + "trustedIdentities": ["*"] }, { - "name": "global-trust-policy", - "scopes": [ "*" ], - "trustStores": [ "trust-store-name-1", "trust-store-name-2" ], - "expiryValidations": { - "signatureExpiry": "enforce | warn", - "signingIdentityExpiry": "enforce | warn", - "timestampExpiry": "enforce | warn" + // Policy for all other artifacts signed by ACME Rockets + // from any registry location. The policy also specified multiple trust stores. + "name": "global-policy-for-all-other-images", + "registryScopes": [ "*" ], + "signatureVerification": { + "level" : "audit" }, - "revocationValidations": { - "signingIdentityRevocation": "enforceWithFailOpen | enforceWithFailClose | warn | skip", - "timestampRevocation": "enforceWithFailOpen | enforceWithFailClose | warn | skip" - } + "trustStores": ["ca:acme-rockets", "ca:acme-rockets-ca2"], + "trustedIdentities": [ + "x509.subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io, OU=Finance, CN=SecureBuilder" + ] } - ] } ``` -Property descriptions +### Trust Policy Properties - **`version`**(*string*): This REQUIRED property is the version of the trust policy. The supported value is `1.0`. - **`trustPolicies`**(*string-array of objects map*): This REQUIRED property represents a collection of trust policies. - - **`name`**(*string*): This REQUIRED propert represents the name of the trust policy. - - **`scopes`**(*array of strings*): This REQUIRED property determines which trust policy is applicable for the given artifact. + - **`name`**(*string*): This REQUIRED property represents the name of the trust policy. + - **`registryScopes`**(*array of strings*): This REQUIRED property determines which trust policy is applicable for the given artifact. The scope field supports filtering based on fully qualified repository URI `${registry-name}/${namespace}/${repository-name}`. - For more information, see [scopes constraints](#scope-constraints) section. - - **`skipSignatureVerification`**(*boolean*): This OPTIONAL property dictates whether Notary v2 should skip signature verification or not. - If set to `true` Notary v2 MUST NOT perform any signature validations including the custom validations performed using plugins. - This is required to support the gradual rollout of signature validation i.e the case when the user application has a mix of signed and unsigned artifacts. - When set to `false`, the following properties MUST be present `trustStores`, `expiryValidations`, `revocationValidations`. - The default value is `false`. - - **`trustStores`**(*array of strings*): This OPTIONAL property specifies a list of names of trust stores that the user trusts. - - **`trustAnchors`**(*array of strings*): This OPTIONAL property specifies a list of elements/attributes of the signing certificate's subject. - If present, the collection MUST contain at least one value. - For more information, see [trust anchors constraints](#trust-anchors-constraints) section. - - **`expiryValidations`**(*object*): This OPTIONAL property represents a collection of artifact expiry-related validations. - - **`signatureExpiry`**(*string*): This REQUIRED property specifies what implementation must do if the signature is expired. - Supported values are `enforce` and `warn`. - - **`signingIdentityExpiry`**(*string*): This REQUIRED property specifies what implementation must do if signing identity(certificate and certificate-chain) is expired. - Supported values are `enforce` and `warn`. - - **`timestampExpiry`**(*string*): This REQUIRED property specifies what implementation must do if timestamping certificate and certificate-chain are expired. - Supported values are `enforce` and `warn`. - - **`revocationValidations`**(*object*): This OPTIONAL property represents collection of artifact revocation related validations. - - **`signingIdentityRevocation`**(*string*): This REQUIRED property specifies whether implementation should check for signing identity(certificate and certificate-chain) revocation status or not and what implementation must do if this revocation check fails. - Supported values are `enforceWithFailOpen`, `enforceWithFailClose`, `warn` and `skip`. - - **`timestampRevocation`**(*string*): This REQUIRED property specifies whether implementation should check for timestamping certificate and certificate-chain revocation status or not and what implementation must do if this revocation check fails. - Supported values are `enforceWithFailOpen`, `enforceWithFailClose`, `warn` and `skip`. - -Value descriptions - -- **`enforce`**: This means implementation MUST perform validation and throw an error if validation fails. -- **`enforceWithFailOpen`**: This means implementation MUST perform validation and if validation fails because the endpoint is not reachable, the implementation MUST log an error and MUST NOT fail the validation. -- **`enforceWithFailClose`**: This means implementation MUST perform validation and if validation fails because the endpoint is not reachable, the implementation MUST throw an error and MUST fail the validation. -- **`warn`**: This means implementation MUST perform the validation and if validation fails(because of any reason) the implementation MUST log an error and MUST NOT fail validation. -- **`skip`**: This means implementation MUST NOT perform the validation. - -#### Scopes Constraints + For more information, see [registry scopes constraints](#registry-scopes-constraints) section. + - **`signatureVerification`**(*object*): This REQUIRED property dictates how signature verification is performed. + An *object* that specifies a predefined verification level, with an option to override Notary v2 defined verification level if user wants to specify a [custom verification level](#custom-verification-level). + - **`level` **(*string*): A REQUIRED property that specifies the verification level, supported values are `strict`, `permissive`, `audit` and `skip`. Detailed explaination of each level is present [here](#signatureverification-details). + - **`override` **(*map of string-string*): This OPTIONAL map is used to specify a [custom verification level](#custom-verification-level). + - **`trustStores`**(*array of string*): This REQUIRED property specifies a set of one or more named trust stores, each of which contain the trusted roots against which signatures are verified. Each named trust store uses the format `{trust-store-type}:{named-store}`. Currently supported values for `trust-store-type` are `ca` and `tsa`. + - **NOTE**: When support for publicly trusted TSA is available, `tsa:publicly-trusted-tsa` is the default value, and implied without explictly specifying it. If a custom TSA is used the format `ca:acme-rockets,tsa:acme-tsa` is supported to specify it. + - **`trustedIdentities`**(*array of strings*): This REQUIRED property specifies a set of identities that the user trusts. For X.509 PKI, it supports list of elements/attributes of the signing certificate's subject. For more information, see [identities constraints](#trusted-identities-constraints) section. A value `*` is supported if user trusts any identity (signing certificate) issued by the CA(s) in `trustStore`. + +#### Signature Verification details + + - Signature verification is a multi step process performs the following validations + - integrity (artifact is unaltered, signature is not corrupted) + - authenticity (the signature is really from the identity that claims to have signed it) + - trusted timestamping (the signature was generated when the key/certificate were unexpired) + - expiry (an optional check if the artifact specifies an expiry time) + - revocation check (is the signing identity still trusted at the present time). + - Based on the signature verification level, each of these validations is *enforced* or *logged*. + - If a validation is *enforced*, a failure is treated as critical failure, and causes the overall signature verification to fail. + - If a validation is *logged*, a failure causes details to be logged, and the next validation is evaluated till all validations succeed or a critical failure is encountered. +- Implementations may change the ordering of these validations based on efficiency, but all validation MUST be performed till the first critical failure is encountered, or all validation succeed, for the overall signature verification process to be considered complete. + + Notary v2 defines the following signature verification levels to provide different levels of enforcement for different scenarios. + +- `strict` : Signature verification is performed at `strict` level, which enforces all validations. If any of these validations fail, the signature verification fails. This is the recommended level in environments where a signature verification failure does not have high impact to other concerns (like application availability). It is recommended that build and development environments where images are initially injested, or for high assurance at deploy time use `strict` level. +- `permissive` : The `permissive` level enforces most validations, but will only logs failures for revocation and expiry. The `permissive` level is recommended to be used if signature verification is done at deploy time or runtime, and the user only needs integrity and authenticity guarantees. +- `audit` : The `audit` level only enforces signature integrity if a signature is present. Failure of all other validations are only logged. +- `skip` : The `skip` level does not fetch signatures for artifacts and does not perform any signature verification. This is useful when an application uses multiple artifacts, and has a mix of signed and unsigned artifacts. Note that `skip` cannot be used with a global scope (`*`), the value of `registryScopes` MUST contain fully qualified registry URL(s). + +The following table shows the resultant validation action, either *enforced* (verification fails), or *logged* for each of the checks, based on signature verification level. + +|Signature Verification Level|Recommended Usage|||Validations||| +|----------------------------|-----------------|---------|------------|-----------------|------|----------------| +|||*Integrity*|*Authenticity*|*Authentic timestamp*|*Expiry*|*Revocation check*| +|*strict* |Use at development, build and deploy time|enforced|enforced|enforced|enforced|enforced| +|*permissive*|Use at deploy time or runtime|enforced|enforced|logged|logged|logged| +|*audit* |Use when adopting signed images, without breaking existing workflows|enforced|logged|logged|logged|logged| +|*skip* |Use to exclude verification for unsigned images|skipped|skipped|skipped|skipped|skipped| + +**Integrity** : Guarantees that the artifact wasn't altered after it was signed, or the signature isn't corrupted. All signature verification levels always enforce integrity. + +**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. + +- **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). + +**Expiry** : This is an optional feature that guarantees that artifact is within “best by use” date indicated in the signature. Notary v2 allows users to include an optional expiry time when they generate a signature. The expiry time is not set by default and requires explicit configuration by users at the time of signature generation. The artifact is considered expired when the current time is greater than or equal to expiry time, users performing verification can either configure their trust policies to fail the verification or even accept the artifact with expiry date in the past using policy. This is an advanced feature that allows implementing controls for user defined semantics like deprecation for older artifacts, or block older artifacts in a production environment. Users should only include an expiry time in the signed artifact after considering the behavior they expect for consumers of the artifact after it expires. Users can choose to consume an artifact even after the expiry time based on their specific needs. + +**Revocation check** : Guarantees that the signing identity is still trusted at signature verification time. Events such as key or system compromise can make a signing identity that was previously trusted, to be subsequently untrusted. This guarantee typically requires a verification-time call to an external system, which may not be consistently reliable. The `permissive` verification level only logs failures of revocation check and does not enforce it. If a particular revocation mechanism is reliable, use `strict` verification level instead. + +- **NOTE** : `notation` RC1 will not have in built support for Revocation Check feature, see [Key Management - Rescinding Signatures (CRL)](https://github.com/notaryproject/notaryproject/issues/72) for details. When this feature is subsequently implemented, the `strict` verification level will **enforce** this validation, and will fail signatures which would previously pass signature verification when revocation checks were not supported. This is considered a **breaking change**, but is the appropriate behavior for a `strict` verification level. + +#### Custom Verification Level + +Signature verification levels provide Notary v2 defined behavior for each validation e.g. `strict` will always *enforce* authenticity validation. For fine grained control over validations that occur during signature verification, users can define a custom level which overrides the behavior of an existing verification level. + +- To use this feature, the `level` property MUST be specified along with an OPTIONAL `override` map. +- Supported values for `level` are - `strict`, `permissive` and `audit`. A `skip` level cannot be customized. +- Supported keys for `override` map and their supported values are as follows. + - `integrity` validation cannot be overriden, and therefore cannot be specified as a key. + - `authenticity` - Supported values are `enforce` and `log`. + - `authenticTimestamp` - Supported values are `enforce` and `log`. + - `expiry` - Supported values are `enforce` and `log`. + - `revocation` - Supported values are `enforce`, `log`, and `skip`. + +```jsonc + "signatureVerification": { + "level" : "strict", + "override" : { + "expiry" : "log" + } + } +``` + +#### Registry Scopes Constraints - Each trust policy MUST contain scope property and the scope collection MUST contain at least one value. - The scope MUST contain one of the following: @@ -198,6 +240,9 @@ Value descriptions The scope with `*` value is called global scope. The trust policy with global scope applies to all the artifacts. There can only be one trust policy that uses a global scope. + +#### Selecting a trust policy based on artifact URI + - For a given artifact there MUST be only one applicable trust policy, except for trust policy with global scope. - For a given artifact, if there is no applicable trust policy then Notary v2 MUST consider the artifact as untrusted and fail signature verification. - The scope MUST NOT support reference expansion i.e. URIs must be fully qualified. @@ -208,26 +253,27 @@ Value descriptions 1. *Global*: If there exists a trust policy with global scope then use that policy for signature evaluation. Otherwise, fail the signature verification. -### Trust Anchors Constraints +### Trusted Identities Constraints -A distinguished name (usually just shortened to "DN") uniquely identifies an entry and in the case of the certificate's subject, DN uniquely identifies the requestor/holder of the certificate. +This section defines how to specify `trustedIdentities` for X.509 certificates using its distinguished name. A distinguished name (usually just shortened to "DN") uniquely identifies the requestor/holder of the certificate. The DN is comprised of zero or more comma-separated components called relative distinguished names, or RDNs. -For example, the DN `C=US, ST=WA, O=wabbit-network.io, OU=org1`"` has four RDNs. +For example, the DN `C=US, ST=WA, O=wabbit-network.io, OU=org1` has four RDNs. The RDN consists of an attribute type name followed by an equal sign and the string representation of the corresponding attribute value. -- Trust anchor MUST support a full and partial list of all the attribute types present in [subject DN](https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1.2.6) of x509 certificate. +- The `trustedIdentities` list items MUST support a full and partial list of all the attribute types present in [subject DN](https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1.2.6) of x509 certificate. Alternatively, it supports a single item with value `*`, to indicate that any certificate that chains to the associated trust store (`trustStore`) is allowed. - If the subject DN of the signing certificate is used in the trust anchor, then it MUST meet the following requirements: - - The value of `trustAnchors` MUST begin with `subject:` followed by comma-separated one or more RDNs. - For example, `subject: C=${country}, ST=${state}, L=${locallity}, O={organization}, OU=${organization-unit}, CN=${common-name}`. - - Trust anchor MUST contain country (CN), state Or province (ST), and organization (O) RDNs. + - The value of each `trustedIdentities` list item, if it begins with `x509.subject:`, MUST be followed by comma-separated one or more RDNs. + Other types of trusted identities may be supported, by using an alternate prefix, or a different format. + For example, `x509.subject: C=${country}, ST=${state}, L=${locallity}, O={organization}, OU=${organization-unit}, CN=${common-name}`. + - Each identity in `identities` list MUST contain country (CN), state Or province (ST), and organization (O) RDNs. All other RDNs are optional. - The minimal possible trust anchor is `subject: C=${country}, ST=${state}, O={organization}`, - - Trust anchor MUST NOT have overlapping values. - Trust anchors are considered overlapping if there exists a certificate for which multiple trust anchors evaluate true. - For example, the following two trust anchors are overlapping: - - `subject: C=US, ST=WA, O=wabbit-network.io, OU=org1` - - `subject: C=US, ST=WA, O=wabbit-network.io` - - In some special cases trust anchor MUST escape one or more characters in an RDN. + The minimal possible value is `x509.subject: C=${country}, ST=${state}, O={organization}`, + - `trustedIdentities` list items MUST NOT have overlapping values, + they are considered overlapping if there exists a certificate for which multiple DNs evaluate true. In such case the policy is considered invalid, and will fail at signature verification time when the policy is validated. + For example, the following two identity values are overlapping: + - `x509.subject: C=US, ST=WA, O=wabbit-network.io, OU=org1` + - `x509.subject: C=US, ST=WA, O=wabbit-network.io` + - In some special cases values in `trustedIdentities` list MUST escape one or more characters in an RDN. Those cases are: - If a value starts or ends with a space, then that space character MUST be escaped as `\`. - All occurrences of the comma character (`,`) MUST be escaped as `\,`. @@ -236,76 +282,74 @@ The RDN consists of an attribute type name followed by an equal sign and the str ### Extended Validation -The implementation must allow the user to execute custom validations. -These custom validation MUST have access to all the information available in the signature envelope like payload, signed attributes, unsigned attributes, and signature. +The implementation must allow the user to execute custom validations. These custom validation MUST have access to all the information available in the signature envelope like payload, signed attributes, unsigned attributes, and signature. -## Signature Evaluation +TODO: Update this section after [verification plugin spec](https://github.com/notaryproject/notaryproject/blob/main/specs/plugin-extensibility.md#verification-extensibility) is ready. + +## Signature Verification ### Prerequisites -- User has configured valid [trust store](#trust-store) and [trust policy](#trust-policy). +- User has configured a valid [trust store](#trust-store) and [trust policy](#trust-policy). +- The artifact's fully qualified repository URI, and associated signature envelope is available. ### Steps -1. **Validate that the signature envelope format is supported.** - 1. Parse the signature envelope content based on the signature envelope type specified in the `[descriptors].descriptor.mediaType` attribute of the signature artifact manifest. - 1. Validate that the content type indicated by the `cty` property value of protected headers in the signature envelope is supported. -1. **Validate the signature envelope integrity.** +1. **Identify applicable trust policy** + 1. For the given artifact URI [determine the applicable trust policy](#selecting-a-trust-policy-based-on-artifact-uri) using `registryScopes`. + 1. If an applicable trust policy for the artifact URI cannot be found, fail signature verification. +1. **Proceed based on signature verification level** + 1. If `signatureVerification` level is set to `skip` in the trust policy, return success. + 1. For all other `signatureVerification` levels, `strict`, `permissive` and `audit`, perform each of the validation defined in the next sections - `integrity`, `authenticity`, `trusted timestamp`, `expiry` and `revocation`. + 1. The `signatureVerification` level defines if each validation is `enforced` or `logged` + 1. `enforced` - validation failures are treated as critical, causes the overall signature verification to fail and exit. Subsequent validations are not processed. + 1. `logged` - validation failure is logged and the next validation step is processed. + 1. A signature verification is considered successful when all validation steps are completed without critical failure. +1. **Validate Integrity.** + 1. Validate that signature envelope can be parsed sucessfully based on the signature envelope type specified in the `blobs[0].mediaType` attribute of the signature artifact manifest. + 1. Validate that the content type indicated by the `content type` signed attribute in the signature envelope is supported. 1. Get the signing certificate from the parsed [signature envelope](https://github.com/notaryproject/notaryproject/blob/7b7d283038/signature-specification.md#signature-envelope). - 1. Get the signing algorithm(hash+encryption) from the signing certificate and validate that the signing algorithm satisfies [algorithm requirements](./signature-specification.md#signature-algorithm-requirements) + 1. Determine the signing algorithm(hash+encryption) from the signing certificate and validate that the signing algorithm satisfies [algorithm requirements](./signature-specification.md#signature-algorithm-requirements) 1. Using the public key of the signing certificate and signing algorithm identified in the previous step, validate the integrity of the signature envelope. -1. **Validate the signature against trust policy and trust store.** - 1. Using the `scope` configured in trust policies, get the applicable trust policy. - (Implementations might have this value precomputed, added it for completeness) - 1. For the applicable trust policy, **validate trust-store:** - 1. Validate that the signature envelope contains a complete certificate chain that starts from a code signing certificate and terminates with the root certificate. - Also, validate that code signing certificate satisfies [certificate requirements](./signature-specification.md#certificate-requirements). - 1. For each of the trust-stores configured in applicable trust-policy perform the following steps. - 1. Validate that certificate and certificate-chain lead to a trusted certificate configured in the `x509Certs` field of trust-store. - 1. If the above verification succeeds then continue to the next step else iterate over the next trust store. - If all of the trust stores have been evaluated then fail the signature validation and exit. - 1. For the applicable trust policy, **validate trust anchors** (if present): - 1. If trust anchors are present, validate that the value of subject attributes configured in `trustAnchors` matches with the value of corresponding attributes in the signing certificate’s subject. - If trust anchors are not present continue to step 4. - 1. If the above verification succeeds then continue to the next step. - Otherwise, fail the signature validation and exit. - 1. **Validate trust policy:** - 1. If signature expiry is present in the signature envelope, using the local machine’s current time(in UTC) check whether the signature is expired or not. - If the signature is not expired, continue to the next step. - Otherwise, if `signatureExpiry` is set to `Enforce` then fail the signature validation and exit else log a warning and continue to the next step. - 1. Check for the timestamp signature in the signature envelope. - 1. If the timestamp exists, continue with the next step. - Otherwise, store the local machine's current time(in UTC) in variables `timeStampLowerLimit` and `timeStampUpperLimit` and continue with step 3.3.c. - 1. Validate that the timestamp hash in `TSTInfo.messageImprint` matches the hash of the signature to which the timestamp was applied. - 1. Validate that the timestamp signing certificate satisfies [certificate requirements](./signature-specification.md#certificate-requirements). - 1. Validate that the timestamp signing algorithm satisfies [algorithm requirements](./signature-specification.md#signature-algorithm-requirements). - 1. Validate the `signing-certificate`([RFC-2634](https://tools.ietf.org/html/rfc2634)) or `signing-certificate-v2`([RFC-5126](https://tools.ietf.org/html/rfc5126#section-5.7.3.2)) attribute of timestamp CMS. - 1. Check whether timestamping certificate and certificate chain are valid (not expired) or not. - If timestamping certificate and certificate-chain are not expired, continue to the next step. - Otherwise, if `timestampExpiry` in trust-policy is configured to `Enforce` then fail the signature validation and exit, else log a warning and continue to the next step. - 1. Validate that timestamp certificate and certificate chain leads to a trusted TSA certificate configured in trust policy. - 1. Validate timestamp certificate and certificate chain revocation status using [certificate revocation evaluation](#certificate-revocation-evaluation) section as per `timestampRevocation` setting in trust-policy - 1. Retrieve the timestamp's time from `TSTInfo.genTime`. - 1. 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](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. - Otherwise, If `signingIdentityExpiry` in trust-policy is configured to `Enforce` then fail the signature validation and exit else log a warning and continue to the next step. - 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. - 1. If you have reached this step then treat the OCI artifact signature as a valid signature. +1. **Validate Authenticity.** + 1. For the applicable trust policy, **validate trust store and identities:** + 1. Validate that the signature envelope contains a complete certificate chain that starts from a code signing certificate and terminates with a root certificate. Also, validate that code signing certificate satisfies [certificate requirements](./signature-specification.md#certificate-requirements). + 1. For the `trustStore` configured in applicable trust-policy perform the following steps. + 1. Validate that certificate and certificate-chain lead to a trusted certificate present in the named store. + 1. If all the certificates in the named store have been evaluated without a match, then fail this step. + 1. If `trustedIdentities` is `*`, any signing certificate issued by a CA in `trustStore` is allowed, skip to the next validation (Validate Expiry). + 1. Else validate if the X.509 subject (`x509.subject`) in the `trustedIdentities` list matches with the value of corresponding attributes in the signing certificate’s subject, refer [this section](#trusted-identities-constraints) for details. If a match is not found, fail this step. +1. **Validate Expiry:** + 1. If an `expiry time` signed attribute is present in the signature envelope, check if the local machine’s current time(in UTC) is greater than `expiry time`. If yes, fail this step. +1. **Validate Trusted Timestamp:** + 1. Check for the timestamp signature in the signature envelope. + 1. If the timestamp exists, continue with the next step. + Otherwise, store the local machine's current time(in UTC) in variables `timeStampLowerLimit` and `timeStampUpperLimit` and continue with step 6.2. + 1. Validate that the timestamp hash in `TSTInfo.messageImprint` matches the hash of the signature to which the timestamp was applied. + 1. Validate that the timestamp signing certificate satisfies [certificate requirements](./signature-specification.md#certificate-requirements). + 1. Validate that the timestamp signing algorithm satisfies [algorithm requirements](./signature-specification.md#signature-algorithm-requirements). + 1. Validate the `signing-certificate`([RFC-2634](https://tools.ietf.org/html/rfc2634)) or `signing-certificate-v2`([RFC-5126](https://tools.ietf.org/html/rfc5126#section-5.7.3.2)) attribute of timestamp CMS. + 1. Validate that timestamp certificate and certificate chain leads to a trusted TSA certificate as per value configured in `trustStore`. + 1. Validate timestamp certificate and certificate chain revocation status using [certificate revocation evaluation](#certificate-revocation-evaluation) section. + 1. Retrieve the timestamp's time from `TSTInfo.genTime`. + 1. 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](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 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. +1. If all the steps are completed without critical failures then the signatures is successfully verified. ### Certificate Revocation Evaluation -If the certificate revocation trust-store setting is set to `skip`, skip the below steps. -therwise, check for revocation status for certificate and certificate chain. +This section should be considered as **DRAFT** and will be updated after RC1 release. + +If the certificate revocation validation is set to `skip` using policy, skip the below steps, otherwise check for revocation status for certificate and certificate chain. -1. If the revocation status of any of the certificates cannot be determined (revocation unavailable) and `signingIdentityRevocation` is set to either `enforceWithFailOpen` or `warn` then log a warning and skip the below steps. - Otherwise, fail the signature validation and exit. -1. If any of the certificates are revoked and `signingIdentityRevocation` is set to either `enforceWithFailOpen` or `enforceWithFailClose` then fail signature validation and exit else log a warning. +- If the revocation status of any of the certificates is revoked or cannot be determined (revocation status unavailable), fail this step. This will fail the overall signature verification or log the error, depending on if revocations status check is `enforced` or `logged`, as per the signature verification level. Starting from Root to leaf certificate, for each certificate in the certificate chain, perform the following steps to check its revocation status: @@ -399,7 +443,8 @@ Signature verification workflow succeeds if verification succeeds for at least o **Q: Does Notary v2 support overriding of revocation endpoints to support signature verification in disconnected environments?** -**A:** Not natively supported but a user can configure `revocationValidations` to `skip` and then use extended validations to check for revocation. +**A:** TODO: Update after verification extensibility spec is ready. +Not natively supported but a user can configure `revocationValidations` to `skip` and then use extended validations to check for revocation. **Q: Why user needs to include a complete certificate chain (leading to root) in the signature?** From 379e642ab76eface1ce9e02f94d97968cf08f3b3 Mon Sep 17 00:00:00 2001 From: David Tesar Date: Wed, 14 Sep 2022 10:57:06 -0700 Subject: [PATCH 05/11] Update signature-envelope-cose.md Co-authored-by: Thomas Fossati Signed-off-by: David Tesar --- signature-envelope-cose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signature-envelope-cose.md b/signature-envelope-cose.md index 1cb36c9a..1c9dd0c0 100644 --- a/signature-envelope-cose.md +++ b/signature-envelope-cose.md @@ -114,7 +114,7 @@ Notary v2 supports the following unprotected header parameters: } ``` -Note: `<<` and `>>` are used to notate the byte string resulting from encoding the data item. +Note: `<<` and `>>` are used to notate the CBOR byte string resulting from encoding the data item. - **[`x5chain`](https://datatracker.ietf.org/doc/html/draft-ietf-cose-x509-08#section-2)** (*array of bstr*): This REQUIRED parameter (label `33` by [IANA](https://www.iana.org/assignments/cose/cose.xhtml#header-parameters)) contains the ordered 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 COSE. The certificate chain is represented as an array of certificate value byte string, each certificate in the array is DER encoded. The certificate containing the public key corresponding to the key used to digitally sign the COSE MUST be the first certificate, followed by the intermediate and root certificates in the correct order. Refer [*Certificate Chain* unsigned attribute](signature-specification.md#unsigned-attributes) for more details. Optionally, this header can be presented in the protected header. - **TODO** update signature specification to allow chains in protected header From 32507a57ff1d6341efa65cfeba443e8ce29fb676 Mon Sep 17 00:00:00 2001 From: David Tesar Date: Wed, 14 Sep 2022 10:57:25 -0700 Subject: [PATCH 06/11] Update signature-envelope-cose.md Co-authored-by: Thomas Fossati Signed-off-by: David Tesar --- signature-envelope-cose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signature-envelope-cose.md b/signature-envelope-cose.md index 1c9dd0c0..1c6839d1 100644 --- a/signature-envelope-cose.md +++ b/signature-envelope-cose.md @@ -116,7 +116,7 @@ Notary v2 supports the following unprotected header parameters: Note: `<<` and `>>` are used to notate the CBOR byte string resulting from encoding the data item. -- **[`x5chain`](https://datatracker.ietf.org/doc/html/draft-ietf-cose-x509-08#section-2)** (*array of bstr*): This REQUIRED parameter (label `33` by [IANA](https://www.iana.org/assignments/cose/cose.xhtml#header-parameters)) contains the ordered 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 COSE. The certificate chain is represented as an array of certificate value byte string, each certificate in the array is DER encoded. The certificate containing the public key corresponding to the key used to digitally sign the COSE MUST be the first certificate, followed by the intermediate and root certificates in the correct order. Refer [*Certificate Chain* unsigned attribute](signature-specification.md#unsigned-attributes) for more details. Optionally, this header can be presented in the protected header. +- **[`x5chain`](https://datatracker.ietf.org/doc/html/draft-ietf-cose-x509-08#section-2)** (*array of bstr*): This REQUIRED parameter (label `33` by [IANA](https://www.iana.org/assignments/cose/cose.xhtml#header-parameters)) contains the ordered 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 COSE. The certificate chain is represented as an array of certificate, each certificate in the array is DER encoded and then wrapped in a CBOR byte string. The certificate containing the public key corresponding to the key used to digitally sign the COSE MUST be the first certificate, followed by the intermediate and root certificates in the correct order. Refer [*Certificate Chain* unsigned attribute](signature-specification.md#unsigned-attributes) for more details. Optionally, this header can be presented in the protected header. - **TODO** update signature specification to allow chains in protected header - **`io.cncf.notary.timestampSignature`** (*bstr*): This OPTIONAL header is used to store countersignature that provides authentic signing time. Only [RFC3161]([rfc3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2)) compliant `TimeStampToken` are supported. - **TODO** Define the opaque datum (hash of envelope) that is sent to TSA, and how TSA response (time stamp token) is represented in this header. From b5b27d463633b31848b4668c527ff5f6062eeb42 Mon Sep 17 00:00:00 2001 From: Yi Zha Date: Fri, 4 Nov 2022 14:22:47 +0800 Subject: [PATCH 07/11] spec: update data type of signingTime to data/time Signed-off-by: Yi Zha --- signature-envelope-cose.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/signature-envelope-cose.md b/signature-envelope-cose.md index 1c6839d1..5612247b 100644 --- a/signature-envelope-cose.md +++ b/signature-envelope-cose.md @@ -90,9 +90,9 @@ Note: The above examples are represented using the [extended CBOR diagnostic not - **[`crit`](https://datatracker.ietf.org/doc/html/rfc8152#section-3.1)** (*array of int/tstr*): This REQUIRED parameter (label `2`) lists the header parameters that implementations MUST understand and process. It MUST only contain parameters apart from integer labels in the range of 0 to 8. This header MUST contain `io.cncf.notary.signingScheme` which is a required critical header, and optionally contain `io.cncf.notary.authenticSigningTime` and `io.cncf.notary.expiry` if these critical headers are present in the signature. - **[`content type`](https://datatracker.ietf.org/doc/html/rfc8152#section-3.1)** (*tstr*): The REQUIRED parameter content type (label `3`) is used to declare the media type of the secured content (the payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. - **`io.cncf.notary.signingScheme`** (*tstr*, critical): This REQUIRED header specifies the [Notary v2 Signing Scheme](./signing-scheme.md) used by the signature. Supported values are `notary.x509` and `notary.x509.signingAuthority`. -- **`io.cncf.notary.signingTime`** (*uint*): This header specifies the time at which the signature was generated. This is an untrusted timestamp, and therefore not used in trust decisions. Its value is the number of seconds from `1970-01-01T00:00Z` in UTC time, commonly known as UNIX timestamp. This claim is REQUIRED and only valid when signing scheme is `notary.x509`. -- **`io.cncf.notary.authenticSigningTime`** (*uint*, critical): This header specifies the authenticated time at which the signature was generated. Its value is the number of seconds from `1970-01-01T00:00Z` in UTC time, commonly known as UNIX timestamp. This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . -- **`io.cncf.notary.expiry`** (*uint*, critical): This OPTIONAL header provides a "best by use" time for the artifact, as defined by the signer. Its value is the number of seconds from `1970-01-01T00:00Z` in UTC time, commonly known as UNIX timestamp. +- **`io.cncf.notary.signingTime`** (*date/time*): This header specifies the time at which the signature was generated. This is an untrusted date/time, and therefore not used in trust decisions. Its value is a [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6) formatted date time. This claim is REQUIRED and only valid when signing scheme is `notary.x509`. +- **`io.cncf.notary.authenticSigningTime`** (*date/time*, critical): This header specifies the authenticated time at which the signature was generated. Its value is a [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6) formatted date time. This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . +- **`io.cncf.notary.expiry`** (*date/time*, critical): This OPTIONAL header provides a "best by use" time for the artifact, as defined by the signer. Its value is a [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6) formatted date time. ## Unprotected Headers From 26f2ccb3f2b8e5e1a664c4b4601f1e69a186aa4c Mon Sep 17 00:00:00 2001 From: Yi Zha Date: Fri, 4 Nov 2022 14:39:27 +0800 Subject: [PATCH 08/11] Revert "spec: update data type of signingTime to data/time" This reverts commit b5b27d463633b31848b4668c527ff5f6062eeb42. Signed-off-by: Yi Zha --- signature-envelope-cose.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/signature-envelope-cose.md b/signature-envelope-cose.md index 5612247b..1c6839d1 100644 --- a/signature-envelope-cose.md +++ b/signature-envelope-cose.md @@ -90,9 +90,9 @@ Note: The above examples are represented using the [extended CBOR diagnostic not - **[`crit`](https://datatracker.ietf.org/doc/html/rfc8152#section-3.1)** (*array of int/tstr*): This REQUIRED parameter (label `2`) lists the header parameters that implementations MUST understand and process. It MUST only contain parameters apart from integer labels in the range of 0 to 8. This header MUST contain `io.cncf.notary.signingScheme` which is a required critical header, and optionally contain `io.cncf.notary.authenticSigningTime` and `io.cncf.notary.expiry` if these critical headers are present in the signature. - **[`content type`](https://datatracker.ietf.org/doc/html/rfc8152#section-3.1)** (*tstr*): The REQUIRED parameter content type (label `3`) is used to declare the media type of the secured content (the payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. - **`io.cncf.notary.signingScheme`** (*tstr*, critical): This REQUIRED header specifies the [Notary v2 Signing Scheme](./signing-scheme.md) used by the signature. Supported values are `notary.x509` and `notary.x509.signingAuthority`. -- **`io.cncf.notary.signingTime`** (*date/time*): This header specifies the time at which the signature was generated. This is an untrusted date/time, and therefore not used in trust decisions. Its value is a [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6) formatted date time. This claim is REQUIRED and only valid when signing scheme is `notary.x509`. -- **`io.cncf.notary.authenticSigningTime`** (*date/time*, critical): This header specifies the authenticated time at which the signature was generated. Its value is a [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6) formatted date time. This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . -- **`io.cncf.notary.expiry`** (*date/time*, critical): This OPTIONAL header provides a "best by use" time for the artifact, as defined by the signer. Its value is a [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6) formatted date time. +- **`io.cncf.notary.signingTime`** (*uint*): This header specifies the time at which the signature was generated. This is an untrusted timestamp, and therefore not used in trust decisions. Its value is the number of seconds from `1970-01-01T00:00Z` in UTC time, commonly known as UNIX timestamp. This claim is REQUIRED and only valid when signing scheme is `notary.x509`. +- **`io.cncf.notary.authenticSigningTime`** (*uint*, critical): This header specifies the authenticated time at which the signature was generated. Its value is the number of seconds from `1970-01-01T00:00Z` in UTC time, commonly known as UNIX timestamp. This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . +- **`io.cncf.notary.expiry`** (*uint*, critical): This OPTIONAL header provides a "best by use" time for the artifact, as defined by the signer. Its value is the number of seconds from `1970-01-01T00:00Z` in UTC time, commonly known as UNIX timestamp. ## Unprotected Headers From e7c8b558d733f8ef2a2a4e373efc38e676466d94 Mon Sep 17 00:00:00 2001 From: Yi Zha <107919912+yizha1@users.noreply.github.com> Date: Thu, 10 Nov 2022 00:21:59 +0800 Subject: [PATCH 09/11] spec: update data type of signingTime to date/time (#199) Signed-off-by: David Tesar --- signature-envelope-cose.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/signature-envelope-cose.md b/signature-envelope-cose.md index 1c6839d1..f304aab5 100644 --- a/signature-envelope-cose.md +++ b/signature-envelope-cose.md @@ -90,9 +90,9 @@ Note: The above examples are represented using the [extended CBOR diagnostic not - **[`crit`](https://datatracker.ietf.org/doc/html/rfc8152#section-3.1)** (*array of int/tstr*): This REQUIRED parameter (label `2`) lists the header parameters that implementations MUST understand and process. It MUST only contain parameters apart from integer labels in the range of 0 to 8. This header MUST contain `io.cncf.notary.signingScheme` which is a required critical header, and optionally contain `io.cncf.notary.authenticSigningTime` and `io.cncf.notary.expiry` if these critical headers are present in the signature. - **[`content type`](https://datatracker.ietf.org/doc/html/rfc8152#section-3.1)** (*tstr*): The REQUIRED parameter content type (label `3`) is used to declare the media type of the secured content (the payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. - **`io.cncf.notary.signingScheme`** (*tstr*, critical): This REQUIRED header specifies the [Notary v2 Signing Scheme](./signing-scheme.md) used by the signature. Supported values are `notary.x509` and `notary.x509.signingAuthority`. -- **`io.cncf.notary.signingTime`** (*uint*): This header specifies the time at which the signature was generated. This is an untrusted timestamp, and therefore not used in trust decisions. Its value is the number of seconds from `1970-01-01T00:00Z` in UTC time, commonly known as UNIX timestamp. This claim is REQUIRED and only valid when signing scheme is `notary.x509`. -- **`io.cncf.notary.authenticSigningTime`** (*uint*, critical): This header specifies the authenticated time at which the signature was generated. Its value is the number of seconds from `1970-01-01T00:00Z` in UTC time, commonly known as UNIX timestamp. This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . -- **`io.cncf.notary.expiry`** (*uint*, critical): This OPTIONAL header provides a "best by use" time for the artifact, as defined by the signer. Its value is the number of seconds from `1970-01-01T00:00Z` in UTC time, commonly known as UNIX timestamp. +- **`io.cncf.notary.signingTime`** (*date/time*): This header specifies the time at which the signature was generated. This is an untrusted date/time, and therefore not used in trust decisions. Its value is an Epoch-Based Date/Time defined in [RFC 8949](https://datatracker.ietf.org/doc/html/rfc8949#section-3.4.2). This claim is REQUIRED and only valid when signing scheme is `notary.x509`. +- **`io.cncf.notary.authenticSigningTime`** (*date/time*, critical): This header specifies the authenticated time at which the signature was generated. Its value is an Epoch-Based Date/Time defined in [RFC 8949](https://datatracker.ietf.org/doc/html/rfc8949#section-3.4.2). This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . +- **`io.cncf.notary.expiry`** (*date/time*, critical): This OPTIONAL header provides a "best by use" time for the artifact, as defined by the signer. Its value is an Epoch-Based Date/Time defined in [RFC 8949](https://datatracker.ietf.org/doc/html/rfc8949#section-3.4.2). ## Unprotected Headers From cf533b86e4a8f24db3d576b4fd768146fcefd078 Mon Sep 17 00:00:00 2001 From: Yi Zha Date: Thu, 17 Nov 2022 19:33:28 +0800 Subject: [PATCH 10/11] spec: update cose and signature spec according to review comments (#204) * spec: update according to review comments * update to RFC for the fields of the sig_structure * update description for field context Signed-off-by: Yi Zha --- .../signature-envelope-cose.md | 25 +++- specs/signature-specification.md | 132 ------------------ 2 files changed, 20 insertions(+), 137 deletions(-) rename signature-envelope-cose.md => specs/signature-envelope-cose.md (90%) diff --git a/signature-envelope-cose.md b/specs/signature-envelope-cose.md similarity index 90% rename from signature-envelope-cose.md rename to specs/signature-envelope-cose.md index f304aab5..79b3b22d 100644 --- a/signature-envelope-cose.md +++ b/specs/signature-envelope-cose.md @@ -37,6 +37,21 @@ Signature Manifest Example The COSE envelope contains a [Notary v2 Payload](./signature-specification.md#payload). +Example of Notary v2 payload: + +```jsonc +{ + "targetArtifact": { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333", + "size": 16724, + "annotations": { + "io.wabbit-networks.buildId": "123" // user defined metadata + } + } +} +``` + ## Protected Header The COSE envelope for Notary v2 uses the following header parameters: @@ -90,9 +105,9 @@ Note: The above examples are represented using the [extended CBOR diagnostic not - **[`crit`](https://datatracker.ietf.org/doc/html/rfc8152#section-3.1)** (*array of int/tstr*): This REQUIRED parameter (label `2`) lists the header parameters that implementations MUST understand and process. It MUST only contain parameters apart from integer labels in the range of 0 to 8. This header MUST contain `io.cncf.notary.signingScheme` which is a required critical header, and optionally contain `io.cncf.notary.authenticSigningTime` and `io.cncf.notary.expiry` if these critical headers are present in the signature. - **[`content type`](https://datatracker.ietf.org/doc/html/rfc8152#section-3.1)** (*tstr*): The REQUIRED parameter content type (label `3`) is used to declare the media type of the secured content (the payload). The supported value is `application/vnd.cncf.notary.payload.v1+json`. - **`io.cncf.notary.signingScheme`** (*tstr*, critical): This REQUIRED header specifies the [Notary v2 Signing Scheme](./signing-scheme.md) used by the signature. Supported values are `notary.x509` and `notary.x509.signingAuthority`. -- **`io.cncf.notary.signingTime`** (*date/time*): This header specifies the time at which the signature was generated. This is an untrusted date/time, and therefore not used in trust decisions. Its value is an Epoch-Based Date/Time defined in [RFC 8949](https://datatracker.ietf.org/doc/html/rfc8949#section-3.4.2). This claim is REQUIRED and only valid when signing scheme is `notary.x509`. -- **`io.cncf.notary.authenticSigningTime`** (*date/time*, critical): This header specifies the authenticated time at which the signature was generated. Its value is an Epoch-Based Date/Time defined in [RFC 8949](https://datatracker.ietf.org/doc/html/rfc8949#section-3.4.2). This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . -- **`io.cncf.notary.expiry`** (*date/time*, critical): This OPTIONAL header provides a "best by use" time for the artifact, as defined by the signer. Its value is an Epoch-Based Date/Time defined in [RFC 8949](https://datatracker.ietf.org/doc/html/rfc8949#section-3.4.2). +- **`io.cncf.notary.signingTime`** (*date/time*): This header specifies the time at which the signature was generated. This is an untrusted date/time, and therefore not used in trust decisions. Its value is an Epoch-Based Date/Time defined in [RFC 8949](https://datatracker.ietf.org/doc/html/rfc8949#section-3.4.2). The optional fractional seconds SHOULD NOT be used. This claim is REQUIRED and only valid when signing scheme is `notary.x509`. +- **`io.cncf.notary.authenticSigningTime`** (*date/time*, critical): This header specifies the authenticated time at which the signature was generated. Its value is an Epoch-Based Date/Time defined in [RFC 8949](https://datatracker.ietf.org/doc/html/rfc8949#section-3.4.2). The optional fractional seconds SHOULD NOT be used. This claim is REQUIRED and only valid when signing scheme is `notary.x509.signingAuthority` . +- **`io.cncf.notary.expiry`** (*date/time*, critical): This OPTIONAL header provides a "best by use" time for the artifact, as defined by the signer. Its value is an Epoch-Based Date/Time defined in [RFC 8949](https://datatracker.ietf.org/doc/html/rfc8949#section-3.4.2). The optional fractional seconds SHOULD NOT be used. ## Unprotected Headers @@ -128,7 +143,7 @@ In COSE, signature is calculated by constructing the `Sig_structure` for `COSE_S The process is described below: 1. Encode the protected header into a CBOR object as a byte string named `body_protected`. -2. Construct the `Sig_structure` for `COSE_Sign1`. +2. Construct the `Sig_structure` for `COSE_Sign1`. The field `context` is set to `Signature1` for `COSE_Sign1` as specified by [RFC9052](https://www.rfc-editor.org/rfc/rfc9052.html#section-4.4). ```yaml Sig_structure = [ / context / 'Signature1', @@ -138,7 +153,7 @@ The process is described below: ] ``` 3. Encode `Sig_structure` into a CBOR object as a byte string named `ToBeSigned`. -4. Compute the signature on the `ToBeSigned` constructed in the previous step by using the signature algorithm defined in the corresponding header element: `alg`. +4. Compute the signature on the `ToBeSigned` constructed in the previous step by using the signature algorithm of the signing key, which MUST match the corresponding protected header element `alg`. This is the value of the signature property used in the signature envelope. ## Signature Envelope diff --git a/specs/signature-specification.md b/specs/signature-specification.md index e3b1cf00..0802238e 100644 --- a/specs/signature-specification.md +++ b/specs/signature-specification.md @@ -159,7 +159,6 @@ 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). @@ -168,127 +167,6 @@ 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: @@ -317,16 +195,6 @@ 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 From fa0d6aafb4e05b4afe352dd37d1c5a37f0eeb69c Mon Sep 17 00:00:00 2001 From: Yi Zha Date: Tue, 22 Nov 2022 10:40:32 +0800 Subject: [PATCH 11/11] spec: move certificate chain discussion to a new issue (#209) * move certificate chain discussion to a new issue Signed-off-by: Yi Zha --- specs/signature-envelope-cose.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/signature-envelope-cose.md b/specs/signature-envelope-cose.md index 79b3b22d..fa1f4ebb 100644 --- a/specs/signature-envelope-cose.md +++ b/specs/signature-envelope-cose.md @@ -132,7 +132,6 @@ Notary v2 supports the following unprotected header parameters: Note: `<<` and `>>` are used to notate the CBOR byte string resulting from encoding the data item. - **[`x5chain`](https://datatracker.ietf.org/doc/html/draft-ietf-cose-x509-08#section-2)** (*array of bstr*): This REQUIRED parameter (label `33` by [IANA](https://www.iana.org/assignments/cose/cose.xhtml#header-parameters)) contains the ordered 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 COSE. The certificate chain is represented as an array of certificate, each certificate in the array is DER encoded and then wrapped in a CBOR byte string. The certificate containing the public key corresponding to the key used to digitally sign the COSE MUST be the first certificate, followed by the intermediate and root certificates in the correct order. Refer [*Certificate Chain* unsigned attribute](signature-specification.md#unsigned-attributes) for more details. Optionally, this header can be presented in the protected header. - - **TODO** update signature specification to allow chains in protected header - **`io.cncf.notary.timestampSignature`** (*bstr*): This OPTIONAL header is used to store countersignature that provides authentic signing time. Only [RFC3161]([rfc3161](https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2)) compliant `TimeStampToken` are supported. - **TODO** Define the opaque datum (hash of envelope) that is sent to TSA, and how TSA response (time stamp token) is represented in this header. - **`io.cncf.notary.signingAgent`** (*tstr*): This OPTIONAL header provides the identifier of a client (e.g. Notation) that produced the signature. E.g. `notation/1.0.0`. Refer [*Signing Agent* unsigned attribute](signature-specification.md#unsigned-attributes) for more details.