-
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
Signature spec updates #158
Conversation
* cbor payload * cose envelope * finalize payload * remove JWS * refine notations * refine datetime Signed-off-by: Shiwei Zhang <[email protected]> Merging into the remote-siging branch for further iteration, addressing the PR feedback with additional PRs into this branch.
Signed-off-by: Steve Lasker <[email protected]>
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 <[email protected]>
Signed-off-by: Milind Gokarn <[email protected]>
Signed-off-by: Milind Gokarn <[email protected]>
"protected": "<Base64Url(ProtectedHeaders)>", | ||
"header": { | ||
"timestamp": "<Base64(TimeStampToken)>", | ||
"x5c": ["<Base64(DER(leafCert))>", "<Base64(DER(intermediateCACert))>", "<Base64(DER(rootCert))>"] |
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 cert ordering in the chain suggested or required?
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.
It's in order from leaf to root, will clarify in description.
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.
Why do we need the root in the envelope? Isn't it redundant?
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.
The root cert isn't generally required for verification. The intent is for signature producers to always include full chain, which will be validated by signing tool (notation), so that consumers can set trusted root as root cert (our recommendation). We want to avoid partial chains in the envelope as tooling cannot determine if the partial chain was only one level below the root. We can relax this requirement if there is a pressing reason in future.
@shizhMSFT would like to get your feedback before finalizing this spec. |
signature-specification.md
Outdated
- **`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.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. |
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.
How about io.cncf.notary.x5t#S256
in RFC7515 4.1.8?
x5t#S256
looks more common and shorter and it also defines the encoding (i.e. a base64url-encoded SHA-256 thumbprint). However, it seems only defines the cert for the signing key and is not a list.
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.
Renamed to io.cncf.notary.x509chain.thumbprint#S256
, let me know if you have another suggestion.
signature-specification.md
Outdated
|
||
### Unsigned Attributes | ||
- 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. |
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.
This sentence is ambiguous for me. The intention seems to be "the claim set MUST NOT be tampered once verified" but it is hard for me to get from it. Request for rewording.
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.
I've reworded to
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.
signature-specification.md
Outdated
### Unsigned Attributes | ||
- 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. |
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.
This sentence is ambiguous for me. Adding a pair of brackets enables me understand it faster:
Claims (that if present, MUST be processed by a verifier) MUST be marked as critical.
Request for rewording.
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.
How about
Claims that are present and MUST be processed by a verifier MUST be marked as critical.
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.
Updated to
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.
signature-specification.md
Outdated
- **Signing time**: 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). | ||
- **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 clain that indicates the content type of the payload. Notary currently a payload that contains the OCI descriptor of a subject manifest as the payload, the supported value is `application/vnd.cncf.notary.payload.v1+json`, other payload types MAY be supported in future. | ||
- **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. |
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.
Curious if v
is allowed in {version}
like notation/v1.0.0
?
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.
Should this be a signed attribute? Since it is for debugging purposes only, can it be unsigned?
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.
Curious if
v
is allowed in{version}
likenotation/v1.0.0
?
Yes, I'll update this section that its an opaque string, and only make a suggestion about the format to be used (formatting is not validated).
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.
Should this be a signed attribute? Since it is for debugging purposes only, can it be unsigned?
Changed this to an unsigned attribute, as this attribute is informational, and cannot be a signed attribute when a envelope-generator
plugin is used.
signature-specification.md
Outdated
- **Signing time**: 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). | ||
- **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 clain that indicates the content type of the payload. Notary currently a payload that contains the OCI descriptor of a subject manifest as the payload, the supported value is `application/vnd.cncf.notary.payload.v1+json`, other payload types MAY be supported in future. | ||
- **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. |
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.
Should this be a signed attribute? Since it is for debugging purposes only, can it be unsigned?
"protected": "<Base64Url(ProtectedHeaders)>", | ||
"header": { | ||
"timestamp": "<Base64(TimeStampToken)>", | ||
"x5c": ["<Base64(DER(leafCert))>", "<Base64(DER(intermediateCACert))>", "<Base64(DER(rootCert))>"] |
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.
Why do we need the root in the envelope? Isn't it redundant?
signature-envelope-jws.md
Outdated
``` | ||
|
||
- **`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. |
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.
I'm assuming ASN.1 DER encoded, then base64 encoded. Definitely has to be spelled out.
Also, RFC3161 signs an opaque datum, typically a hash of something. So this parameter definition also has to define what is actually being sent to the TSA for signing.
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.
That's a very good point. I'll add a TODO here as this requires some more work.
Signed-off-by: Milind Gokarn <[email protected]>
|
@gokarnm this PR changes some pieces of the signature spec that affect the wording of the plugin spec. The plugin spec should also be updated either in this PR or in a follow up. Examples:
|
@qmuntal, totally agree, I want to merge this PR to unblock implementation, and address plugin spec updates as separate one. I was aware of the keyspec updates, thanks for pointing the payload type inconsistency. |
To be addressed in a subsequent PR |
@shizhMSFT @roywill @letmaik @qmuntal @rgnote @priteshbandi @jondonas thanks for all the great feedback! I've addressed most of it. I've resolved ones which are straightforward, and kept more contentious conversations open if you have more feedback. Would appreciate your review as this is blocking implementation. |
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.
LGTM
LGTM |
signature-envelope-jws.md
Outdated
{ | ||
"alg": "PS384", | ||
"cty": "application/vnd.cncf.notary.payload.v1+json", | ||
"io.cncf.notary.signingTime": "2022-04-06 07:01:20Z", |
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.
Not RFC 3339, missing the T
.
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.
RFC 3339 allows a space separator [1]. I'll update the example though to commonly used representation.
https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
NOTE: ISO 8601 defines date and time separated by "T".
Applications using this syntax may choose, for the sake of
readability, to specify a full-date and full-time separated by
(say) a space character.
signature-envelope-jws.md
Outdated
```jsonc | ||
{ | ||
"x5c": ["<Base64(DER(leafCert))>", "<Base64(DER(intermediateCACert))>", "<Base64(DER(rootCert))>"], | ||
"io.cncf.notary.timestamptoken": "<Base64(TimeStampToken)>", |
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.
"io.cncf.notary.timestamptoken": "<Base64(TimeStampToken)>", | |
"io.cncf.notary.timestamp": "<Base64(DER(TimeStampToken))>", |
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.
Got some more feedback on this, plan to rename to io.cncf.notary.timestampSignature
to indicate it's a countersignature.
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.
Isn't the value of RFC3161 TimeStampToken already DER encoded, do we need to specify it additionally here?
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.
I guess it's confusing because we mention DER encoding for the cert chain (x5c), which could originally be in PEM or DER format.
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.
TimeStampToken is defined in terms of ASN.1, and section 3 specifies encodings for multiple transports like email, files, HTTP. All of them use DER encoding, and then some of them base64 encode after that. So yes, it's very likely DER already, but given that we define a new context I think we need to specify the ASN.1 encoding as well.
"payload": "<Base64Url(JWSPayload)>", | ||
"protected": "<Base64Url(ProtectedHeaders)>", | ||
"header": { | ||
"io.cncf.notary.timestamp": "<Base64(TimeStampToken)>", |
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.
"io.cncf.notary.timestamp": "<Base64(TimeStampToken)>", | |
"io.cncf.notary.timestamp": "<Base64(DER(TimeStampToken))>", |
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.
Time stamp token is a CMS signed-data and is BER encoded. If we require DER here, the client needs to convert the BER encoded timestamp token to DER encoded. Is it intended?
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.
You may be right, I was looking at RFC 3161 which only says DER, not BER. Let's postpone any timestamp-related changes to a follow-up PR.
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.
Time-Stamp Protocol via HTTP in RFC 3161 specifies that the TSA response is ASN.1 DER-encoded. Validated with a freetsa.org example here.
So the current content "io.cncf.notary.timestamp": "<Base64(TimeStampToken)>"
is accurate, we don't specifically require DER, it's whichever format TSA servers respond with. I'm going to defer changes to timestamp related details, we can refine when we do the implementation. From the signature format's perspective it's an opaque token that is separately validated.
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. | ||
- `targetArtifact` : Required property whose value is the descriptor of the target artifact manifest that is being signed. Both [OCI descriptor][oci-descriptor] and [ORAS artifact descriptors][artifact-descriptor] are supported. |
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.
How do you know which descriptor type is used? If the descriptor was signed directly, then cty
of the envelope would indicate it, but now with the additional wrapping it seems this information is gone. Maybe I'm missing something though.
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.
Line 87-89 provide the requirements for the descriptor. Notary only depends on attributes common to both OCI and ORAS descriptor - digest
, size
, mediaType
and annotations
.
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.
Got it.
…ignature` Signed-off-by: Milind Gokarn <[email protected]>
Signed-off-by: Milind Gokarn <[email protected]>
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))` |
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.
The Base64Url
here includes paddings or not? It seems Base64Url
is never defined in this documentation.
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.
Good point, clarified that JWS uses base64url without padding as defined in Base64url Encoding in RFC 7515 section 2.
"payload": "<Base64Url(JWSPayload)>", | ||
"protected": "<Base64Url(ProtectedHeaders)>", | ||
"header": { | ||
"io.cncf.notary.timestamp": "<Base64(TimeStampToken)>", |
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.
Time stamp token is a CMS signed-data and is BER encoded. If we require DER here, the client needs to convert the BER encoded timestamp token to DER encoded. Is it intended?
- **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 [trusted 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”. |
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.
Should this part be signed?
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.
Signing Agent could be signed, which makes it tamper proof. But it does not play a part in signature verification, only diagnostics, so it's not required to be signed.
Notation's default signing and Signature Generator plugins can support this as a signed attribute, but Envelope generator plugin can't. I went with unsigned for simplicity, but we can change it such that it can be a signed attribute where possible if there is a strong case for it.
Signed-off-by: Milind Gokarn <[email protected]>
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.
LGTM
Resolved merge conflicts. |
|
||
```jsonc | ||
{ | ||
"artifactType": "application/vnd.cncf.notary.signature", |
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.
need version here: application/vnd.cncf.notary.signature.v2
main
, based subset of updates incose-envelope
branch.Signed-off-by: Milind Gokarn [email protected]
cc: @shizhMSFT @roywill @letmaik @priteshbandi @rgnote for review