|
|
0106cf |
% container-signature 5 Container signature format
|
|
|
fa61e0 |
% Miloslav Trmač
|
|
|
fa61e0 |
% March 2017
|
|
|
fa61e0 |
|
|
|
0106cf |
# NAME
|
|
|
0106cf |
container-signature - Container signature format
|
|
|
fa61e0 |
|
|
|
0106cf |
# DESCRIPTION
|
|
|
fa61e0 |
This document describes the format of container signatures,
|
|
|
fa61e0 |
as implemented by the `github.com/containers/image/signature` package.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
Most users should be able to consume these signatures by using the `github.com/containers/image/signature` package
|
|
|
fa61e0 |
(preferably through the higher-level `signature.PolicyContext` interface)
|
|
|
fa61e0 |
without having to care about the details of the format described below.
|
|
|
fa61e0 |
This documentation exists primarily for maintainers of the package
|
|
|
fa61e0 |
and to allow independent reimplementations.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
## High-level overview
|
|
|
fa61e0 |
|
|
|
fa61e0 |
The signature provides an end-to-end authenticated claim that a container image
|
|
|
fa61e0 |
has been approved by a specific party (e.g. the creator of the image as their work,
|
|
|
fa61e0 |
an automated build system as a result of an automated build,
|
|
|
fa61e0 |
a company IT department approving the image for production) under a specified _identity_
|
|
|
fa61e0 |
(e.g. an OS base image / specific application, with a specific version).
|
|
|
fa61e0 |
|
|
|
fa61e0 |
A container signature consists of a cryptographic signature which identifies
|
|
|
fa61e0 |
and authenticates who signed the image, and carries as a signed payload a JSON document.
|
|
|
fa61e0 |
The JSON document identifies the image being signed, claims a specific identity of the
|
|
|
fa61e0 |
image and if applicable, contains other information about the image.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
The signatures do not modify the container image (the layers, configuration, manifest, …);
|
|
|
fa61e0 |
e.g. their presence does not change the manifest digest used to identify the image in
|
|
|
fa61e0 |
docker/distribution servers; rather, the signatures are associated with an immutable image.
|
|
|
fa61e0 |
An image can have any number of signatures so signature distribution systems SHOULD support
|
|
|
fa61e0 |
associating more than one signature with an image.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
## The cryptographic signature
|
|
|
fa61e0 |
|
|
|
fa61e0 |
As distributed, the container signature is a blob which contains a cryptographic signature
|
|
|
fa61e0 |
in an industry-standard format, carrying a signed JSON payload (i.e. the blob contains both the
|
|
|
fa61e0 |
JSON document and a signature of the JSON document; it is not a “detached signature” with
|
|
|
fa61e0 |
independent blobs containing the JSON document and a cryptographic signature).
|
|
|
fa61e0 |
|
|
|
fa61e0 |
Currently the only defined cryptographic signature format is an OpenPGP signature (RFC 4880),
|
|
|
fa61e0 |
but others may be added in the future. (The blob does not contain metadata identifying the
|
|
|
fa61e0 |
cryptographic signature format. It is expected that most formats are sufficiently self-describing
|
|
|
fa61e0 |
that this is not necessary and the configured expected public key provides another indication
|
|
|
fa61e0 |
of the expected cryptographic signature format. Such metadata may be added in the future for
|
|
|
fa61e0 |
newly added cryptographic signature formats, if necessary.)
|
|
|
fa61e0 |
|
|
|
fa61e0 |
Consumers of container signatures SHOULD verify the cryptographic signature
|
|
|
fa61e0 |
against one or more trusted public keys
|
|
|
fa61e0 |
(e.g. defined in a [policy.json signature verification policy file](containers-policy.json.5.md))
|
|
|
fa61e0 |
before parsing or processing the JSON payload in _any_ way,
|
|
|
fa61e0 |
in particular they SHOULD stop processing the container signature
|
|
|
fa61e0 |
if the cryptographic signature verification fails, without even starting to process the JSON payload.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
(Consumers MAY extract identification of the signing key and other metadata from the cryptographic signature,
|
|
|
fa61e0 |
and the JSON payload, without verifying the signature, if the purpose is to allow managing the signature blobs,
|
|
|
fa61e0 |
e.g. to list the authors and image identities of signatures associated with a single container image;
|
|
|
fa61e0 |
if so, they SHOULD design the output of such processing to minimize the risk of users considering the output trusted
|
|
|
fa61e0 |
or in any way usable for making policy decisions about the image.)
|
|
|
fa61e0 |
|
|
|
fa61e0 |
### OpenPGP signature verification
|
|
|
fa61e0 |
|
|
|
fa61e0 |
When verifying a cryptographic signature in the OpenPGP format,
|
|
|
fa61e0 |
the consumer MUST verify at least the following aspects of the signature
|
|
|
fa61e0 |
(like the `github.com/containers/image/signature` package does):
|
|
|
fa61e0 |
|
|
|
fa61e0 |
- The blob MUST be a “Signed Message” as defined RFC 4880 section 11.3.
|
|
|
fa61e0 |
(e.g. it MUST NOT be an unsigned “Literal Message”, or any other non-signature format).
|
|
|
fa61e0 |
- The signature MUST have been made by an expected key trusted for the purpose (and the specific container image).
|
|
|
fa61e0 |
- The signature MUST be correctly formed and pass the cryptographic validation.
|
|
|
fa61e0 |
- The signature MUST correctly authenticate the included JSON payload
|
|
|
fa61e0 |
(in particular, the parsing of the JSON payload MUST NOT start before the complete payload has been cryptographically authenticated).
|
|
|
fa61e0 |
- The signature MUST NOT be expired.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
The consumer SHOULD have tests for its verification code which verify that signatures failing any of the above are rejected.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
## JSON processing and forward compatibility
|
|
|
fa61e0 |
|
|
|
fa61e0 |
The payload of the cryptographic signature is a JSON document (RFC 7159).
|
|
|
fa61e0 |
Consumers SHOULD parse it very strictly,
|
|
|
fa61e0 |
refusing any signature which violates the expected format (e.g. missing members, incorrect member types)
|
|
|
fa61e0 |
or can be interpreted ambiguously (e.g. a duplicated member in a JSON object).
|
|
|
fa61e0 |
|
|
|
fa61e0 |
Any violations of the JSON format or of other requirements in this document MAY be accepted if the JSON document can be recognized
|
|
|
fa61e0 |
to have been created by a known-incorrect implementation (see [`optional.creator`](#optionalcreator) below)
|
|
|
fa61e0 |
and if the semantics of the invalid document, as created by such an implementation, is clear.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
The top-level value of the JSON document MUST be a JSON object with exactly two members, `critical` and `optional`,
|
|
|
fa61e0 |
each a JSON object.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
The `critical` object MUST contain a `type` member identifying the document as a container signature
|
|
|
fa61e0 |
(as defined [below](#criticaltype))
|
|
|
fa61e0 |
and signature consumers MUST reject signatures which do not have this member or in which this member does not have the expected value.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
To ensure forward compatibility (allowing older signature consumers to correctly
|
|
|
fa61e0 |
accept or reject signatures created at a later date, with possible extensions to this format),
|
|
|
fa61e0 |
consumers MUST reject the signature if the `critical` object, or _any_ of its subobjects,
|
|
|
fa61e0 |
contain _any_ member or data value which is unrecognized, unsupported, invalid, or in any other way unexpected.
|
|
|
fa61e0 |
At a minimum, this includes unrecognized members in a JSON object, or incorrect types of expected members.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
For the same reason, consumers SHOULD accept any members with unrecognized names in the `optional` object,
|
|
|
fa61e0 |
and MAY accept signatures where the object member is recognized but unsupported, or the value of the member is unsupported.
|
|
|
fa61e0 |
Consumers still SHOULD reject signatures where a member of an `optional` object is supported but the value is recognized as invalid.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
## JSON data format
|
|
|
fa61e0 |
|
|
|
fa61e0 |
An example of the full format follows, with detailed description below.
|
|
|
fa61e0 |
To reiterate, consumers of the signature SHOULD perform successful cryptographic verification,
|
|
|
fa61e0 |
and MUST reject unexpected data in the `critical` object, or in the top-level object, as described above.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
```json
|
|
|
fa61e0 |
{
|
|
|
fa61e0 |
"critical": {
|
|
|
fa61e0 |
"type": "atomic container signature",
|
|
|
fa61e0 |
"image": {
|
|
|
fa61e0 |
"docker-manifest-digest": "sha256:817a12c32a39bbe394944ba49de563e085f1d3c5266eb8e9723256bc4448680e"
|
|
|
fa61e0 |
},
|
|
|
fa61e0 |
"identity": {
|
|
|
fa61e0 |
"docker-reference": "docker.io/library/busybox:latest"
|
|
|
fa61e0 |
}
|
|
|
fa61e0 |
},
|
|
|
fa61e0 |
"optional": {
|
|
|
fa61e0 |
"creator": "some software package v1.0.1-35",
|
|
|
fa61e0 |
"timestamp": 1483228800,
|
|
|
fa61e0 |
}
|
|
|
fa61e0 |
}
|
|
|
fa61e0 |
```
|
|
|
fa61e0 |
|
|
|
fa61e0 |
### `critical`
|
|
|
fa61e0 |
|
|
|
fa61e0 |
This MUST be a JSON object which contains data critical to correctly evaluating the validity of a signature.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
Consumers MUST reject any signature where the `critical` object contains any unrecognized, unsupported, invalid or in any other way unexpected member or data.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
### `critical.type`
|
|
|
fa61e0 |
|
|
|
fa61e0 |
This MUST be a string with a string value exactly equal to `atomic container signature` (three words, including the spaces).
|
|
|
fa61e0 |
|
|
|
fa61e0 |
Signature consumers MUST reject signatures which do not have this member or this member does not have exactly the expected value.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
(The consumers MAY support signatures with a different value of the `type` member, if any is defined in the future;
|
|
|
fa61e0 |
if so, the rest of the JSON document is interpreted according to rules defining that value of `critical.type`,
|
|
|
fa61e0 |
not by this document.)
|
|
|
fa61e0 |
|
|
|
fa61e0 |
### `critical.image`
|
|
|
fa61e0 |
|
|
|
fa61e0 |
This MUST be a JSON object which identifies the container image this signature applies to.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
Consumers MUST reject any signature where the `critical.image` object contains any unrecognized, unsupported, invalid or in any other way unexpected member or data.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
(Currently only the `docker-manifest-digest` way of identifying a container image is defined;
|
|
|
fa61e0 |
alternatives to this may be defined in the future,
|
|
|
fa61e0 |
but existing consumers are required to reject signatures which use formats they do not support.)
|
|
|
fa61e0 |
|
|
|
fa61e0 |
### `critical.image.docker-manifest-digest`
|
|
|
fa61e0 |
|
|
|
fa61e0 |
This MUST be a JSON string, in the `github.com/opencontainers/go-digest.Digest` string format.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
The value of this member MUST match the manifest of the signed container image, as implemented in the docker/distribution manifest addressing system.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
The consumer of the signature SHOULD verify the manifest digest against a fully verified signature before processing the contents of the image manifest in any other way
|
|
|
fa61e0 |
(e.g. parsing the manifest further or downloading layers of the image).
|
|
|
fa61e0 |
|
|
|
fa61e0 |
Implementation notes:
|
|
|
fa61e0 |
* A single container image manifest may have several valid manifest digest values, using different algorithms.
|
|
|
fa61e0 |
* For “signed” [docker/distribution schema 1](https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-1.md) manifests,
|
|
|
fa61e0 |
the manifest digest applies to the payload of the JSON web signature, not to the raw manifest blob.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
### `critical.identity`
|
|
|
fa61e0 |
|
|
|
fa61e0 |
This MUST be a JSON object which identifies the claimed identity of the image (usually the purpose of the image, or the application, along with a version information),
|
|
|
fa61e0 |
as asserted by the author of the signature.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
Consumers MUST reject any signature where the `critical.identity` object contains any unrecognized, unsupported, invalid or in any other way unexpected member or data.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
(Currently only the `docker-reference` way of claiming an image identity/purpose is defined;
|
|
|
fa61e0 |
alternatives to this may be defined in the future,
|
|
|
fa61e0 |
but existing consumers are required to reject signatures which use formats they do not support.)
|
|
|
fa61e0 |
|
|
|
fa61e0 |
### `critical.identity.docker-reference`
|
|
|
fa61e0 |
|
|
|
fa61e0 |
This MUST be a JSON string, in the `github.com/docker/distribution/reference` string format,
|
|
|
fa61e0 |
and using the same normalization semantics (where e.g. `busybox:latest` is equivalent to `docker.io/library/busybox:latest`).
|
|
|
fa61e0 |
If the normalization semantics allows multiple string representations of the claimed identity with equivalent meaning,
|
|
|
fa61e0 |
the `critical.identity.docker-reference` member SHOULD use the fully explicit form (including the full host name and namespaces).
|
|
|
fa61e0 |
|
|
|
fa61e0 |
The value of this member MUST match the image identity/purpose expected by the consumer of the image signature and the image
|
|
|
fa61e0 |
(again, accounting for the `docker/distribution/reference` normalization semantics).
|
|
|
fa61e0 |
|
|
|
fa61e0 |
In the most common case, this means that the `critical.identity.docker-reference` value must be equal to the docker/distribution reference used to refer to or download the image.
|
|
|
fa61e0 |
However, depending on the specific application, users or system administrators may accept less specific matches
|
|
|
fa61e0 |
(e.g. ignoring the tag value in the signature when pulling the `:latest` tag or when referencing an image by digest),
|
|
|
fa61e0 |
or they may require `critical.identity.docker-reference` values with a completely different namespace to the reference used to refer to/download the image
|
|
|
fa61e0 |
(e.g. requiring a `critical.identity.docker-reference` value which identifies the image as coming from a supplier when fetching it from a company-internal mirror of approved images).
|
|
|
fa61e0 |
The software performing this verification SHOULD allow the users to define such a policy using the [policy.json signature verification policy file format](containers-policy.json.5.md).
|
|
|
fa61e0 |
|
|
|
fa61e0 |
The `critical.identity.docker-reference` value SHOULD contain either a tag or digest;
|
|
|
fa61e0 |
in most cases, it SHOULD use a tag rather than a digest. (See also the default [`matchRepoDigestOrExact` matching semantics in `policy.json`](containers-policy.json.5.md#signedby).)
|
|
|
fa61e0 |
|
|
|
fa61e0 |
### `optional`
|
|
|
fa61e0 |
|
|
|
fa61e0 |
This MUST be a JSON object.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
Consumers SHOULD accept any members with unrecognized names in the `optional` object,
|
|
|
fa61e0 |
and MAY accept a signature where the object member is recognized but unsupported, or the value of the member is valid but unsupported.
|
|
|
fa61e0 |
Consumers still SHOULD reject any signature where a member of an `optional` object is supported but the value is recognized as invalid.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
### `optional.creator`
|
|
|
fa61e0 |
|
|
|
7d010a |
If present, this MUST be a JSON string, identifying the name and version of the software which has created the signature
|
|
|
7d010a |
(identifying the low-level software implementation; not the top-level caller).
|
|
|
fa61e0 |
|
|
|
fa61e0 |
The contents of this string is not defined in detail; however each implementation creating container signatures:
|
|
|
fa61e0 |
|
|
|
fa61e0 |
- SHOULD define the contents to unambiguously define the software in practice (e.g. it SHOULD contain the name of the software, not only the version number)
|
|
|
fa61e0 |
- SHOULD use a build and versioning process which ensures that the contents of this string (e.g. an included version number)
|
|
|
fa61e0 |
changes whenever the format or semantics of the generated signature changes in any way;
|
|
|
fa61e0 |
it SHOULD not be possible for two implementations which use a different format or semantics to have the same `optional.creator` value
|
|
|
fa61e0 |
- SHOULD use a format which is reasonably easy to parse in software (perhaps using a regexp),
|
|
|
fa61e0 |
and which makes it easy enough to recognize a range of versions of a specific implementation
|
|
|
fa61e0 |
(e.g. the version of the implementation SHOULD NOT be only a git hash, because they don’t have an easily defined ordering;
|
|
|
fa61e0 |
the string should contain a version number, or at least a date of the commit).
|
|
|
fa61e0 |
|
|
|
fa61e0 |
Consumers of container signatures MAY recognize specific values or sets of values of `optional.creator`
|
|
|
fa61e0 |
(perhaps augmented with `optional.timestamp`),
|
|
|
fa61e0 |
and MAY change their processing of the signature based on these values
|
|
|
fa61e0 |
(usually to accommodate violations of this specification in past versions of the signing software which cannot be fixed retroactively),
|
|
|
fa61e0 |
as long as the semantics of the invalid document, as created by such an implementation, is clear.
|
|
|
fa61e0 |
|
|
|
fa61e0 |
If consumers of signatures do change their behavior based on the `optional.creator` value,
|
|
|
fa61e0 |
they SHOULD take care that the way they process the signatures is not inconsistent with
|
|
|
fa61e0 |
strictly validating signature consumers.
|
|
|
fa61e0 |
(I.e. it is acceptable for a consumer to accept a signature based on a specific `optional.creator` value
|
|
|
fa61e0 |
if other implementations would completely reject the signature,
|
|
|
fa61e0 |
but it would be very undesirable for the two kinds of implementations to accept the signature in different
|
|
|
fa61e0 |
and inconsistent situations.)
|
|
|
fa61e0 |
|
|
|
fa61e0 |
### `optional.timestamp`
|
|
|
fa61e0 |
|
|
|
fa61e0 |
If present, this MUST be a JSON number, which is representable as a 64-bit integer, and identifies the time when the signature was created
|
|
|
fa61e0 |
as the number of seconds since the UNIX epoch (Jan 1 1970 00:00 UTC).
|