diff --git a/SPEC.md b/SPEC.md index 469202430a9b..294c4cdb5afa 100644 --- a/SPEC.md +++ b/SPEC.md @@ -5,10 +5,10 @@ The goal is to specify the behavior well enough to promote other implementations Container signatures generated with `cosign` should be verifiable in other tools, and vice-versa. This document is broken up into a few parts: -* The properties section details the individual components that are used to create and verify signatures. -* The storage section details how signatures are stored and discovered in an OCI registry. -* The payload section details the format of payloads generated and signed by `cosign`. -* The signature section details the signature schemes supported by `cosign`. +* [Properties](#properties) details the individual components that are used to create and verify signatures. +* [Storage](#storage) details how signatures are stored and discovered in an OCI registry. +* [Payload](#payload) details the format of to-be-signed payloads. +* [Signature](#signature) details the signature schemes supported. ## Properties @@ -16,24 +16,27 @@ This section describes the REQUIRED and OPTIONAL properties used to sign and ver Their layout in an OCI object is described below. * `payload` bytes + This REQUIRED property contains the contents of signed data in byte-form. - Depending on the signature scheme, the algorithm may sign the raw payload contents or the hash of this payload, but the entire payload must be present. Because signatures are __detached__, the payload MUST contain the digest of the image it references, in a well-known location. - This location is dependent on the payload format - see that section below for more details. + This location is dependent on the [payload format](#payloads). -* payload `mediaType` string +* `mediaType` string + This REQUIRED property contains the media type of the payload. * `signature` string + This REQUIRED property contains the base64-encoded signature. This signature MUST be generated by a supported scheme. - For more details on supported schemes, see the `Signature` section below. + For more details on supported schemes, see the [`Signature`](#signature) section below. Example `signature`: `MEYCIQDXmXWj59naoPFlLnCADIPLKgLG3LyFtKrbjpnkYiGNGgIhAJ/eNx5zr/l1MJKSFpFMjPKKr4fjh5RHEtT2DhMamZuT` * `certificate` string - This OPTIONAL property contains a PEM-encoded, DER-formatted, ASN.1 x509 certificate. + + This OPTIONAL property contains a [PEM-encoded](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) x509 certificate. If present, this certificate MUST embed the public key that can be used to verify the signature. Example `certificate`: @@ -61,7 +64,7 @@ Ej6T/GLK6XJSB28haSPRWB7k * `chain` string This OPTIONAL property contains a PEM-encoded, DER-formatted, ASN.1 x509 certificate chain. The `certificate` property MUST be present if this property is present. - This chian MAY be used by implementations to verify the `certificate` property. + This chain MAY be used by implementations to verify the `certificate` property. Example `chain`: @@ -134,7 +137,7 @@ Implementations MAY store signatures objects in the same OCI repository as the t ### Object Types This section describes the way the properties from above are embedded into OCI objects that can be stored in a registry. -Implementations MUST support storing signatures inat least the following object types: +Implementations MUST support storing signatures in at least the following object types: * [Image Manifest V2 Schema 2](https://docs.docker.com/registry/spec/manifest-v2-2/) @@ -148,7 +151,7 @@ Multiple signatures can be embedded in one image manifest. ##### Payload and mediaType The `payload` bytes are uploaded to an OCI registry as a `blob`, and are referenced by `digest`, `size` and `mediaType.` -The digest is embedded into the `Image` manifest as a `layer`, via a `Descriptor`. +The digest is embedded into the `Image` manifest as a `layer`, via a [`Descriptor`](https://github.com/opencontainers/image-spec/blob/master/descriptor.md). The `mediaType` property for the `payload` is included in the same descriptor. @@ -159,7 +162,7 @@ Example `payload`: "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { - + }, "layers": [ { @@ -225,15 +228,112 @@ The following additional semantics are applied: * The `mediaType` used to identify this payload format is: `application/vnd.dev.cosign.simplesigning.v1+json`. * The `critical.type` value used to identify `cosign` signatures is: `cosign container signature`. -* The `critical.identity.docker-reference` field is not supported. +* The `critical.identity.docker-reference` field is not ignored. * Optional user-specified claims may be included in the `Optional` section. ## Signature Schemes Implementations must support at least the following schemes: -* ECDSA-P256 with the SHA256 Hash Algorithm +* ECDSA-P256 No information about the signature scheme is included in the object. -Clients must determine the hash algorithm and signature scheme out-of-band during the verification process. -This is an intentional decision to reduce the risk of [algorithm-confusion attacks](https://news.ycombinator.com/item?id=24346317). +Clients must determine the signature scheme out-of-band during the verification process. + +### Hashing Algorithms + +Signers and verifiers must know the hash algorithm used in addition to the signature scheme. +In an attempt to avoid specifying a particular hashing algorithm, we require that digest be calculated using the SAME algorithm as the OCI registry. +In practice, this means [sha256](https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests). + +When the payload is stored as a `blob` in the OCI registry, it is exposed and referenced via a [Content Addressable](https://en.wikipedia.org/wiki/Content-addressable_storage) API. + +Example referenced payload: + +``` +{ + "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json", + "size": 210, + "digest": "sha256:1119abab63e605dcc281019bad0424744178b6f61ba57378701fe7391994c999", +} +``` + +Here, `1119abab63e605dcc281019bad0424744178b6f61ba57378701fe7391994c999` is the hex-encoded digest, and the `sha256:` prefix specifies the algorithm. +This value is already calculated and verified by both the registry and client-tooling. + +Put simply: implementations MUST use the same hash algorithm used by the underlying registry to reference the payload. +Any future algorithmic-agility will come from the storage layer. + +# Rationales and Commentary + +This document, while labeled a `Specification`, aims to specify as few things as possible. +Instead, we prescribe the usage of other specifications. + +This section contains a rationale for each choice, as well as comparisons to alternatives considered. + +## Payload/Attestation Format: Simple Signing + +Existing usage +Support for critical and optional claims +Simple to parse + +### Alternatives + +Descriptor +Custom thing + +## OCI Type - Docker Manifest + +This is a toss up, will probably switch. + +Docker Works everywhere +OCI works almost everywhere +OCI is more "correct", supports custom media types. + +## Discovery - Tag Based + +Works everywhere +No extra service +Does not mutate +Supports multiple (kind of) + +### Alternatives Considered + +Notary v1/Grafeas + None meet first two requirements +Nothing cross-registry + + +* OCI Descriptor +* Custom Format +* Plain digest + +## Hash Algorithm - None! + +Most common signature schemes support customizable hash algorithms. +These are typically stored with the signature for convenience, presenting a possible attack/confusion vector. + +We decided to pin to the registry hash algorithm. +This removes an entire moving part, without sacrificing agility. + +The registry/client-tooling already perform hash validation as part of the CAS. +While their spec does not completely pin to specific algorithm, SHA256 is ubiquitous in practice. +This means that our signed payload object references the actual image "target" by a sha-256 digest - he entire signature already relies on the strength of this algorithm. + +Trading off this perceived agility for the reduction in attack surface is a win. + +** Note **: This is only possible if we store the `payload` in a blob by itself. +Serializing the payload and signature together in something like a JWT for storage would mean the payload is no longer directly hashed into the CAS. +There is also a performance benefit: we can validate the signature (stored in the manifest) against the payload (stored as a blob) without fetching the blob, because the blob's digest is also present in the manifest. + +## Algorithms + +We only require ECDSA-P256 (with the SHA256 hash algorithm, see above), but will allow for other schemes. +We will bias toward algorithms well-supported in the Go ecosystem, but will accept others. +We will bias toward support for algorithms with wide industry adoption, API support, and hardware availability. + +## Compatility + +We are compatible with the In-Toto [Metablock](https://in-toto.readthedocs.io/en/latest/model.html) and JWS [rfc7515](https://tools.ietf.org/html/rfc7515) formats. +This means we can convert to these formats during verification, and from these formats during upload/signature. +You can think of this spec as an "on-registry serialization format" for either of these specififed formats.