Skip to content
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 specification #214

Merged
merged 1 commit into from
Apr 14, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
355 changes: 355 additions & 0 deletions SPEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,355 @@
# Cosign Signature Specifications

This document aims to describe how `cosign` signs containers.
The goal is to specify the behavior well enough to promote other implementations and enable interoperability.
Container signatures generated with `cosign` should be verifiable in other tools, and vice-versa.

This document is broken up into a few parts:
* [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.
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](#payloads).

* `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`](#signature) section below.

Example `signature`:
`MEYCIQDXmXWj59naoPFlLnCADIPLKgLG3LyFtKrbjpnkYiGNGgIhAJ/eNx5zr/l1MJKSFpFMjPKKr4fjh5RHEtT2DhMamZuT`

* `certificate` string

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`:

```
-----BEGIN CERTIFICATE-----
MIICrjCCAjSgAwIBAgIUAM4mURWUSkg06fmHmFfTmerYKaUwCgYIKoZIzj0EAwMw
KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
MTA0MDExNTU5MDZaFw0yMTA0MDExNjE4NTlaMDoxGzAZBgNVBAoMEmRsb3JlbmNA
Z29vZ2xlLmNvbTEbMBkGA1UEAwwSZGxvcmVuY0Bnb29nbGUuY29tMFkwEwYHKoZI
zj0CAQYIKoZIzj0DAQcDQgAE3R0ZtpfBd3Y8DaXuB1gM8JPlhsDIEfXO/WsMJEN1
4hEn8wajX2HklqL7igZPFICv6tBUGylIHp2mTH2Nhv38mqOCASYwggEiMA4GA1Ud
DwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAMBgNVHRMBAf8EAjAAMB0G
A1UdDgQWBBTy3UWIop0bNrdNgSrVHHD10qSASTAfBgNVHSMEGDAWgBTIxR0AQZok
KTJRJOsNrkrtSgbT7DCBjQYIKwYBBQUHAQEEgYAwfjB8BggrBgEFBQcwAoZwaHR0
cDovL3ByaXZhdGVjYS1jb250ZW50LTYwM2ZlN2U3LTAwMDAtMjIyNy1iZjc1LWY0
ZjVlODBkMjk1NC5zdG9yYWdlLmdvb2dsZWFwaXMuY29tL2NhMzZhMWU5NjI0MmI5
ZmNiMTQ2L2NhLmNydDAdBgNVHREEFjAUgRJkbG9yZW5jQGdvb2dsZS5jb20wCgYI
KoZIzj0EAwMDaAAwZQIwC15Gtd9F6W9lmJuoXMym9DfWlBpK5HEPak38WPXqowRp
6p+2/3jSLkFT5Nn5fuISAjEAouVlX4zH2rlkfg45HnDJax7o6ZV+E0/6BdAms44D
Ej6T/GLK6XJSB28haSPRWB7k
-----END CERTIFICATE-----
```

* `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 chain MAY be used by implementations to verify the `certificate` property.

Example `chain`:

```
----BEGIN CERTIFICATE-----
MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq
MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx
MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu
ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy
A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas
taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm
MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE
FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u
Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx
Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup
Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICVTCCAT2gAwIBAgIQAUrAOaxMcCVQ6AwcDagmRzANBgkqhkiG9w0BAQsFADAh
MR8wHQYDVQQDDBZZdWJpY28gUElWIEF0dGVzdGF0aW9uMCAXDTE2MDMxNDAwMDAw
MFoYDzIwNTIwNDE3MDAwMDAwWjAlMSMwIQYDVQQDDBpZdWJpS2V5IFBJViBBdHRl
c3RhdGlvbiA5YzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABFfnSOVAZLOTOYRs
n4BeD3cMYHFvtwBsK8X0yJ21NKUwJ3fvnqdq0qGeIT92zstNLEWCqP3qMkhs9sh4
wP1tHTGjTjBMMBEGCisGAQQBgsQKAwMEAwUCBjAUBgorBgEEAYLECgMHBAYCBADH
kP4wEAYKKwYBBAGCxAoDCAQCAwIwDwYKKwYBBAGCxAoDCQQBAzANBgkqhkiG9w0B
AQsFAAOCAQEAVRtRFpmgFD+rQqBG92HArMQ+j1FMX23QL9Z76IhaSElmN6cjgsv3
8pJM8GL+ih6vVyCHeU6GoE9Bgj2XB02ZgkmWihnaJX2WG4VOm2dN3SqDmWFp4KLJ
vuzVXEHWuGevwMAOsvMkmXP8HI2npaCPBmprirExbv6bxSyng4ZNHmgdzqmjYyt+
d+ELe3xEeYYqQYx+IswHPRE5mGk/PO4hysk79mhwRNuvmygDbI8Emwvp3Pgzlgr1
Gyp4apdU7AXEwysEQIb034aPrTlpmxh90SnTZFs2DHOvCjCPPAmoWfuQUwPhSPRb
92pXqODWYqpW8+IRED5e42Ncu9XtDgS5Pw==
-----END CERTIFICATE-----
```

* `bundle` TODO

## Storage

`cosign` image signatures are stored in an OCI registry and are designed to make use of the existing specifications.
The full specificatons for the OCI formats and specifications used are available [here](https://github.com/opencontainers).

### Discovery

Signature object are placed in specific location in an OCI registry to enable consistent, interoperable discovery.

Multiple discovery mechanisms MAY be used.
Implementations MUST support at least the following mechanisms:

* Tag-based Discovery

#### Tag-based Discovery

In this scheme, signatures are stored in an OCI registry in a predictable location, addressable by tag.
The location of signatures corresponding to a specific object can be computed using the digest of the object.

If the object is referenced by tag, the tag must first be resolved to a digest.
Then the digest of the object (of the form `sha256:abcdef...`) is encoded into a tag name using the following rules:

* Replace the `:` character with a `-`
* Append the `.cosign` suffix

Example digest->tag mapping:

1. Start with `gcr.io/dlorenc-vmtest2/demo:latest`
2. Resolve this to a digest: `sha256:97fc222cee7991b5b061d4d4afdb5f3428fcb0c9054e1690313786befa1e4e36`
3. Follow the encoding rules: `sha256-97fc222cee7991b5b061d4d4afdb5f3428fcb0c9054e1690313786befa1e4e36.cosign`
dlorenc marked this conversation as resolved.
Show resolved Hide resolved
4. Signature can be found at `gcr.io/dlorenc-vmtest2/demo:sha256-97fc222cee7991b5b061d4d4afdb5f3428fcb0c9054e1690313786befa1e4e36.cosign`

Implementations MAY store signatures objects in the same OCI repository as the target image or a different one.

### 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 in at least the following object types:

* [Image Manifest V2 Schema 2](https://docs.docker.com/registry/spec/manifest-v2-2/)

#### Image Manifest V2 Schema 2

This section describes the way the mandatory and optional signature properties are embedded into an Image Manifest V2 Schema 2 object.

Only one image manifest is created for every signed object.
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`](https://github.com/opencontainers/image-spec/blob/master/descriptor.md).

The `mediaType` property for the `payload` is included in the same descriptor.

Example `payload`:

```
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
<omitted for brevity>
},
"layers": [
{
"mediaType": "application/vnd.dev.cosign.simplesigning.v1+json",
"size": 210,
"digest": "sha256:1119abab63e605dcc281019bad0424744178b6f61ba57378701fe7391994c999",
},
<other layers here>
]
)
```

##### Signature

The `signature` is base64-encoded and stored as an `annotation` on the layer, in the same descriptor.
The `annotation` key is `dev.cosignproject.cosign/signature`.

Example `signature`:

```
"annotations": {
"dev.cosignproject.cosign/signature": "MEUCIBKI9FIC+YD3m/lWViyPxsJsbnIHj86sSbb7L3qvpEFoAiEA2ZChO/67CuAPQKJLBVsAc7bs9hBK8RpsdfjBsByGKJM="
}
```

##### Certificate

The `certificate` is stored as an `annotation` on the layer, in the same descriptor.
The `annotation` key is `dev.cosignproject.cosign/certificate`.

Example `certificate`:

```
"annotations": {
"dev.sigstore.cosign/certificate": "-----BEGIN CERTIFICATE-----\nMIICrjCCAjSgAwIBAgIUAM4mURWUSkg06fmHmFfTmerYKaUwCgYIKoZIzj0EAwMw\nKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y\nMTA0MDExNTU5MDZaFw0yMTA0MDExNjE4NTlaMDoxGzAZBgNVBAoMEmRsb3JlbmNA\nZ29vZ2xlLmNvbTEbMBkGA1UEAwwSZGxvcmVuY0Bnb29nbGUuY29tMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAE3R0ZtpfBd3Y8DaXuB1gM8JPlhsDIEfXO/WsMJEN1\n4hEn8wajX2HklqL7igZPFICv6tBUGylIHp2mTH2Nhv38mqOCASYwggEiMA4GA1Ud\nDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAMBgNVHRMBAf8EAjAAMB0G\nA1UdDgQWBBTy3UWIop0bNrdNgSrVHHD10qSASTAfBgNVHSMEGDAWgBTIxR0AQZok\nKTJRJOsNrkrtSgbT7DCBjQYIKwYBBQUHAQEEgYAwfjB8BggrBgEFBQcwAoZwaHR0\ncDovL3ByaXZhdGVjYS1jb250ZW50LTYwM2ZlN2U3LTAwMDAtMjIyNy1iZjc1LWY0\nZjVlODBkMjk1NC5zdG9yYWdlLmdvb2dsZWFwaXMuY29tL2NhMzZhMWU5NjI0MmI5\nZmNiMTQ2L2NhLmNydDAdBgNVHREEFjAUgRJkbG9yZW5jQGdvb2dsZS5jb20wCgYI\nKoZIzj0EAwMDaAAwZQIwC15Gtd9F6W9lmJuoXMym9DfWlBpK5HEPak38WPXqowRp\n6p+2/3jSLkFT5Nn5fuISAjEAouVlX4zH2rlkfg45HnDJax7o6ZV+E0/6BdAms44D\nEj6T/GLK6XJSB28haSPRWB7k\n-----END CERTIFICATE-----\n",
}
```

##### Chain

The `chain` is stored as an `annotation` on the layer, in the same descriptor.
The `annotation` key is `dev.cosignproject.cosign/chain`.

Example `chain`:

```
"annotations": {
"dev.sigstore.cosign/chain": "-----BEGIN CERTIFICATE-----\nMIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq\nMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx\nMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu\nZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy\nA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas\ntaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm\nMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE\nFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u\nSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx\nVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup\nHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==\n-----END CERTIFICATE-----"
}
```

## Payloads

Implementations MUST support at least the following payload types:

* Simple Signing

### Simple Signing

The Simple Signing payload format is [specified here](https://github.com/containers/image/blob/a5061e5a5f00333ea3a92e7103effd11c6e2f51d/docs/containers-signature.5.md#json-data-format)

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 image signature`.
* The `critical.identity.docker-reference` field is ignored.
* Optional user-specified claims may be included in the `Optional` section.

## Signature Schemes

Implementations must support at least the following schemes:

* ECDSA-P256

No information about the signature scheme is included in the object.
Clients must determine the signature scheme out-of-band during the verification process.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, how are they going to actually do that?

(In a single public key root of trust, I agree that supported schemes/algorithms are a property of that root of trust along with the raw public key, and should be a part of the same root-of-trust configuration set up by users. But with certificate chains that gets more difficult; x.509 certificates AFAICS include a public key signature algorithm, but not really a specific signature form/encoding scheme.)

I don’t have a better proposal, just noting that this is difficult.

Essentially this hinges on the hash algorithm choice; given that, things like the OpenPGP design include the algorithms inside the signed data, so maliciously modifying that would require breaking the signer-chosen public key algorithm or hash (both trusted by assumption), substituting the public key algorithm for a weaker one (fixed in client configuration for simple roots of trust, or included in X.509 certificates), or substituting the hash algorithm for a weaker one (no defense?).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice the scheme is as you described - they're either stored a property of the public key itself or somewhere adjacent to it.


### 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s practical short-term but problematic longer-term if we ever need to transition from SHA256:

  • This makes it impossible to use signatures with a stronger scheme to mitigate SHA256 weaknesses
  • If we copy an image from a SHA256 registry to a post-SHA256 registry, or vice versa, even if the manifest+layers+config were exactly identical (I guess with the manifest listing multiple hash algorithms during the transition period), a signature is only valid in one of the registries.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I agree - we should think through and document what the migration from sha256 to something else might look like.

My understanding though is that copying an image from a sha256 registry to a post-sha256 one would result in a new image. Since there are unique images for each registry, we would need unique signatures for each registry. But exactly what that copy/migration process would look like is really a guess at this point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My hypothesis is that, in order to avoid a flag day and breaking digest references entirely, there will be some mechanism to represent manifests that contain both the SHA256 digest and the new digest of each blob (e.g. by using SHA256 in the designated descriptor field, and the new digest in an annotation). So it should in principle be possible to have a byte-for-byte identical image in two different registries, accessible using the same @sha256:… digest on both, and using some @new-digest:… on the updated registry — and it would be nice to keep signatures working when copying the image across.

OTOH it might be possible for the signer to create two separate signatures using the two hash algorithms, and publish both along with the image, so this would not be a deal-breaker.


Another concern in such a situation is that the hash algorithm is essentially registry-chosen: given a repo:tag reference, I understand “digest be calculated using the SAME algorithm as the OCI registry” to mean “use the algorithm returned by the registry in the Docker-Content-Digest header of https://github.com/distribution/distribution/blob/main/docs/spec/api.md#existing-manifests ”, or something like that. The signature scheme should protect against malicious registries, and giving registries this point of influence feels risky.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another concern in such a situation is that the hash algorithm is essentially registry-chosen: given a repo:tag reference, I understand “digest be calculated using the SAME algorithm as the OCI registry” to mean “use the algorithm returned by the registry in the Docker-Content-Digest header of https://github.com/distribution/distribution/blob/main/docs/spec/api.md#existing-manifests ”, or something like that. The signature scheme should protect against malicious registries, and giving registries this point of influence feels risky.

This is a very very good point. I was imagining the algorithm would actually be user controlled in this scenario, but this reinforces that we need to think through it with the OCI folks a bit more. The responsibility on digest calculation is sort of split between users and registries today - the Header from the registry is one place that is registry controlled, I was imagining it would be the one set by clients on upload though: https://docs.docker.com/registry/spec/api/#pushing-an-image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quickly skimming docker/distribution , it seems that the implementation can’t look up by arbitrary digests (that would be hard), it essentially chooses its own digest algorithm during blob upload, the clients then must use that one for references.

But that’s not very relevant to the future migration scenarios; many implementations probably hard-code SHA256 in various places, and we don’t know for sure which components will be responsible for which choices in the future.

More to the point, even if the process will end up as uploader-controlled, a malicious registry can just lie and return a different digest (or an attacker with unwanted write access to the repo can upload an image with the uploader-chosen different digest).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you think of any monkey business that a registry could get up do that wouldn't be transparent to clients with a simple pull-after-push verification?

Note that a malicious registry can return different content to different victims.

Copy link
Member

@dekkagaijin dekkagaijin Apr 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that a malicious registry can return different content to different victims.

This is mitigated by pulling by digest, which is an independent UX issue. Even pulling by tag, assuming clients verify signatures, we'd only need to ensure that the content being pushed to the registry is what we're actually signing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither the signatures nor their metadata are protected by the image digest. (And any end-user of the signed image doesn’t know the digest already, otherwise signatures wouldn’t be necessary.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(And any end-user of the signed image doesn’t know the digest already, otherwise signatures wouldn’t be necessary.)

I don't think I agree with this part - signatures are useful even when pulling by digest.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake, the non-repudiation property of the signature (and perhaps any associated metadata) could still be useful, apart from the minimal (signer, name/purpose/identity, binding) essential to the signature.

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.

This means that our signatures is "linked" to a "container image" happens via two hops:

`Sign(sha256(SimpleSigningPayload(sha256(Image Manifest))))`

Allowing flexibility in hashing algorithms of the digital signature would only allow manipulation of the "outer" one - the image manifest
itself is always referenced by `sha256` today.
This means using a different hashing algorithm as part of the final signature, even one perceived as "stronger", would result in limited cryptographic benefits.

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 as part of the OCI specification.

# 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

We chose Simple Signing because it has the most existing usage and meets our requirements:

* Critical/Optional sections
* Extensible with more attestations
* Cross-language serialization support

### Alternatives

* OCI Descriptor.

This has the fields we need, with a few problems.
* The annotations section is limiting with map[string]string.
* The `URLs` field is not meant for this use-case.
* No real benefit, since it's stored as a `blob` (not parsed by the registry).

* Plain digest

This doesn't have any attestation/annotation support.

* Something new

See above, we've tried to avoid making any new types where possible.

## OCI Type - Docker Manifest/OCI Manifest

We're currently using Docker but will switch to OCI.
The format support across registries is a toss-up, but likely to improve for OCI.
OCI supports custom media-types and has other "correctness" benefits.

## Discovery - Tag Based

We use the tag based mechanism because it's the only option we can think of.
It meets the two hard requirements: works **everywhere** today and requires no extra services.
It also does not mutate the signed object (like an attached signature or index might).

Support for multiple signatures works but is racy.

### Alternatives Considered

Notary v1/Grafeas both require another database.
A few other proprietary APIs exist, but they're not cross-registry.

## 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 - the 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.

## Compatibility

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.