Skip to content

Commit

Permalink
Respond to feedback
Browse files Browse the repository at this point in the history
Add discussion.
  • Loading branch information
Dan Lorenc committed Apr 8, 2021
1 parent 5e3f519 commit 8228dea
Showing 1 changed file with 117 additions and 17 deletions.
134 changes: 117 additions & 17 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,38 @@ 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

This section describes the REQUIRED and OPTIONAL properties used to sign and verify an image.
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`:
Expand Down Expand Up @@ -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`:

Expand Down Expand Up @@ -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/)

Expand All @@ -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.

Expand All @@ -159,7 +162,7 @@ Example `payload`:
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
<ommitted for brevity>
<omitted for brevity>
},
"layers": [
{
Expand Down Expand Up @@ -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.

0 comments on commit 8228dea

Please sign in to comment.