This document defines the requirements for signature format and discusses the candidates for Notary V2.
SignatureEnvelope(
A signature
mediaType
, digest
, size
fields.artifactType
field for artifact manifests, or the config.mediaType
for oci.image
based manifests.annotations
field.Notary v2 prototype2 shows JWS compact serialization.
alg=none
.alg
is a required header, implementing a format which instead gets the signing algorithm from the certificate, key store or application mapping, requires workarounds.Payload: aka claims
{
"notary.v2": {
"subjectManifest": {...},
"signedAttrs": {
"reserved": {...},
"custom" : {...}
}
},
"iat": "1624377760337",
"exp": "1632153760337"
}
ProtectedHeader:
{
"alg": "RS256",
"cty": "application/<TBD>",
"crit":["cty"]
}
SignatureEnvelope:
<Base64Url(ProtectedHeader)>.<Base64Url(Payload)>.Base64Url( sign( ASCII(<Base64Url(ProtectedHeader)>.<Base64Url(Payload)> )))
alg=none
. Some of the main concerns raised for JWS, can they be addressed with additional layer of validation on top of core libraries.Payload: aka claims
{
"notary.v2": {
"subjectManifest": {
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333",
"size": 16724,
"annotations": {
"org.acme-rockets.importDate": "2021-04-23T18:25:43.511Z"
}
},
"signedAttrs": {
"reserved": {
"arl": "http://registry.wabbit.net/arl",
"identity": "acme-rockets.io/net-monitor:v1"
},
"custom" : {
"buildId": "0001",
"imageScanned": "true"
}
}
},
"iat": "1624377760337",
"exp": "1632153760337"
}
Payload contain the subject manifest and other attributes that has to be integrity protected.
notary.v2
: Top level node and private claim, encapsulating the notary v2 data. This claim MUST be present.subjectManifest
: The image manifest that needs to be integrity protected.signedAttrs
: Contains additional attributes that needs to be integrity protected.
reserved
: Collection of attributes reserved for notary v2 use such as artifact revocation list(arl),identity, etc.custom
: Collection of user defined attributes such as buildId, imageScanned, etc. Use of this field is OPTIONAL.iat
: Issued at identifies the time at which the JWT was issued. This claim MUST be present.exp
: Expiration time identifies the expiration time on or after which the JWT must not be accepted for processing. This claim MUST be present.iat
, exp
as top-level nodes.ProtectedHeader:
{
"alg": "RS256",
"cty": "application/<TBD>",
"crit":["cty"]
}
alg
: JWS needs algorithm(alg) to be present in header so we have added it as protected header. This header MUST be present.cty
: Content type(cty) used to declare the media type of the secured content(the payload). This header MUST be present.crit
: Indicates the list of headers that implementation MUST understood and process. This header MUST be present.SignatureEnvelope:
{
"payload": "<Base64Url(Payload)>",
"signatures": [
{
"protected": "<Base64Url(ProtectedHeader)>",
"header": {
"timestamp": "<Base64Url(TimeStampToken)>",
"kid": "906ade40b96cff95e5b60f7e96f2cda7979c8ad5",
"x5c": ["<Base64(DER(leafCert))>", "<Base64(DER(intermediateCACert))>", "<Base64(DER(rootCert))>"]
},
"signature": "Base64Url( sign( ASCII( <Base64Url(Payload)>.<Base64Url(ProtectedHeader)> )))"
}
]
}
If a JWS contains only one signature as above, the JWS can be flattened as
{
"payload": "<Base64Url(Payload)>",
"protected": "<Base64Url(ProtectedHeader)>"
"header": {
"timestamp": "<Base64Url(TimeStampToken)>",
"kid": "906ade40b96cff95e5b60f7e96f2cda7979c8ad5",
"x5c": ["<Base64(DER(leafCert))>", "<Base64(DER(intermediateCACert))>", "<Base64(DER(rootCert))>"]
},
"signature": "Base64Url( sign( ASCII( <Base64Url(ProtectedHeader)>.<Base64Url(Payload)> )))"
}
protected
: Base64Url encoded JSON object that contains the header parameters that are integrity protected by the JWS Signature digital signatureheader
: JOSE Header containing the parameters describing the cryptographic operations and parameters employed. header is not integrity protected by signature. To start with we will only support reserved set of headers.
timestamp
: Base64Url encoded timestamp token generated by TSA. Use of this is OPTIONAL.kid
Hint indicating which key was used to generate the signature. Use of this is OPTIONAL.x5c
Contains the X.509 public key certificate or certificate chain corresponding to the key used to generate the signature. Use of this is OPTIONAL. If signature was generated by x509 certificate signature envelop MUST contain x5c.header
node MUST contain either kid
or x5c
but not both.x5c
, the leaf certificate's public key algorithm(with some additional conventions) will be used for signature generation and this algorithm must match with alg
header value. The verifier will make sure that the value of alg
header is same as that of leaf certificate's signing algorithm.
alg
will always be from a predefined set of values.Payload:
{
"subjectManifest": {
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:73c803930ea3ba1e54bc25c2bdc53edd0284c62ed651fe7b00369da519a3c333",
"size": 16724,
"annotations": {
"org.acme-rockets.importDate": "2021-04-23T18:25:43.511Z"
}
},
"signedAttrs": {
"reserved": {
"iat": "2021-06-22T16:02:40.3375379Z",
"exp": "2021-09-20T16:02:40.3375379Z",
"arl": "http://registry.wabbit.net/arl",
"identity": "acme-rockets.io/net-monitor:v1"
},
"custom" : {
"BuildId": "0001",
"imageScanned": "true"
}
}
}
SignatureEnvelope:
{
"payload": "<Base64(Payload)>",
"payloadType": "application/<TBD>",
"signatures": [
{
"keyid": "906ade40b96cff95e5b60f7e96f2cda7979c8ad5",
"x5c": ["<Base64(DER(leafCert))>", "<Base64(DER(intermediateCACert))>", "<Base64(DER(rootCert))>"], // field not supported
"timestamp": "<Base64(TimeStampToken)>", // field under consideration
"sig": "Base64( sign( <Base64(Payload)> ))"
}
]
}
In SignatureEnvelope's signatures
node, MUST contain either keyid or x5c but not both.
The signature is stored in ASN.1 DER binary format and optionally wrapped in PEM blocks. Payload aka eContent
{
"notary.v2": {
"subjectManifest": {...},
"signedAttrs": {
"reserved": {...},
"custom" : {...}
}
}
}
signedAttrs:
"signingTime": "2021-06-22T16:02:40.3375379Z",
"expirationTime": "2021-09-20T16:02:40.3375379Z",
"cmsAlgorithmProtection": "..."
unsignedAttrs:
"timestamp": "...."
Signature Envelope:
ContentInfo ::= SEQUENCE {
contentType ContentType,
content [0] SignedData }
ContentType ::= OBJECT IDENTIFIER
SignedData ::= SEQUENCE {
version CMSVersion,
digestAlgorithms DigestAlgorithmIdentifiers,
encapContentInfo EncapsulatedContentInfo,
certificates [0] IMPLICIT CertificateSet OPTIONAL,
crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
signerInfos SignerInfos }
CMSVersion ::= INTEGER{ v0(0), v1(1), v2(2), v3(3), v4(4), v5(5) }
DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
EncapsulatedContentInfo ::= SEQUENCE {
eContentType ContentType,
eContent [0] EXPLICIT OCTET STRING OPTIONAL }
SignerInfos ::= SET OF SignerInfo
SignerInfo ::= SEQUENCE {
version CMSVersion,
sid SignerIdentifier,
digestAlgorithm DigestAlgorithmIdentifier,
signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
signatureAlgorithm SignatureAlgorithmIdentifier,
signature SignatureValue,
unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }
SignerIdentifier ::= CHOICE {
issuerAndSerialNumber IssuerAndSerialNumber,
subjectKeyIdentifier [0] SubjectKeyIdentifier }
SignedAttributes ::= SET SIZE (1..MAX) OF Attribute
UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute
Attribute ::= SEQUENCE {
attrType OBJECT IDENTIFIER,
attrValues SET OF AttributeValue }
AttributeValue ::= ANY
SignatureValue ::= OCTET STRING
payload
{
"subjectManifest": {...},
"signedAttrs": {
"reserved": {
"iat": "2021-06-22T16:02:40.3375379Z",
"exp": "2021-09-20T16:02:40.3375379Z",
...
},
"custom" : {...}
}
}
footer
{
"x5c": ["<Base64(DER(leafCert))>", "<Base64(DER(intermediateCACert))>", "<Base64(DER(rootCert))>"],
"timestamp": "<Base64(TimeStampToken)>"
}
Signature Envelope:
<version>.<purpose>.Base64Url(<payload><Sign(PAE(<version><purpose><payload>))>).Base64Url(<footer>)
version
: v2
, v2 is recommended version.Purpose
: public
, public is used for signing.PAE
: Pre-Authentication EncodingIt’s used by Red Hat for signing container image. Simple signing is not a signature format instead defines the payload format which is then signed using PGP to create detached signature. The payload format is adapted by atomic and later by cosign.
payload:
{
"critical": {
"identity": {
"docker-reference": "testing/manifest"
},
"image": {
"Docker-manifest-digest": "sha256:20be...fe55"
},
"type": "atomic container signature"
},
"optional": {
"creator": "atomic",
"timestamp": 1458239713
}
}
Recommendation is to use JWS JSON Serialization because of following reasons:
Concerns with DSSE:
Example of payload in recommended format.<TBD>
notary