-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
COSE Signature Envelope #139
Changes from 8 commits
ea6b80e
0cea973
4ab7918
7f12961
38e2392
f079232
207f56b
644c8cd
4f3ec4f
ebe6972
d05db32
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
|
@@ -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 `org.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. | ||
|
||
```json | ||
**ProtectedHeaders**: Notary v2 supports only the following protected headers: | ||
shizhMSFT marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
``` | ||
{ | ||
"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', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason to use headers of type string instead of int? Do we plan to followup and include these in IANA? https://www.iana.org/assignments/cose/cose.xhtml#header-parameters |
||
'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`** (*integer*): The REQUIRED property identifies the time at which the signature was generated. | ||
- **`expiry`** (*integer*): This OPTIONAL property contains the expiration time on or after which the signature must not be considered valid. | ||
gokarnm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
To leverage JWS claims validation functionality already provided by libraries, we have defined `iat`, `exp` as top-level nodes. | ||
**UnprotectedHeaders**: Notary v2 supports only 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": "<Base64(TimeStampToken)>", | ||
"x5c": ["<Base64(DER(leafCert))>", "<Base64(DER(intermediateCACert))>", "<Base64(DER(rootCert))>"] | ||
'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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If x5chain is not protected, should require that x5t/34 is. There may also be value in simply requiring that x5chain is protected. This guards against a potential cert substitution attack, at the expense of requiring a larger signature envelope size because the header can't be stripped in environments where the value is known externally. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @priteshbandi Should we move There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @briankr-ms , @shizhMSFT what's the call here? I can make changes if we can conclude on a decision. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @briankr-ms could you provide more details for cert substitution attack. Notation will use x5chain in conjunction with trust store configured by the user. As part of verification, the cert chain is checked to terminate in one of the certs in trust store. @shizhMSFT are we using x5t/thumbprint in any other context, other than signature manifest attributes, for matching signature to policy for evaluation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @shizhMSFT The certs in chain are already signed by the next cert in the chain, and is expected to be authenticated against the trust store. Open to discussing if moving it to signed attributes protects us against certain attacks. |
||
|
||
**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": "<Base64Url(JWSPayload)>", | ||
"protected": "<Base64Url(ProtectedHeaders)>", | ||
"header": { | ||
"timestamp": "<Base64(TimeStampToken)>", | ||
"x5c": ["<Base64(DER(leafCert))>", "<Base64(DER(intermediateCACert))>", "<Base64(DER(rootCert))>"] | ||
``` | ||
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( <Base64Url(ProtectedHeader)>.<Base64Url(JWSPayload)> )))" | ||
} | ||
/ payload / << descriptor >>, | ||
shizhMSFT marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/ 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the intent to disregard any optional parameter e.g.
application/cose; cose-type="cose-sign1"
? Notation should probably includecode-type
param to be more specific.