From 7429e4906ed822c5a5dea136e1de452988f86257 Mon Sep 17 00:00:00 2001 From: Aaron Weitekamp Date: Thu, 25 Aug 2016 16:58:25 -0400 Subject: [PATCH 1/3] initial signature spec --- signature/README.md | 1 + signature/spec/README.md | 118 +++++++++++++++++++++++++++++++ signature/spec/policy.go | 127 ++++++++++++++++++++++++++++++++++ signature/spec/signature.json | 15 ++++ 4 files changed, 261 insertions(+) create mode 100644 signature/README.md create mode 100644 signature/spec/README.md create mode 100644 signature/spec/policy.go create mode 100644 signature/spec/signature.json diff --git a/signature/README.md b/signature/README.md new file mode 100644 index 0000000000..ac88d5874a --- /dev/null +++ b/signature/README.md @@ -0,0 +1 @@ +See [specification](spec/README.md) diff --git a/signature/spec/README.md b/signature/spec/README.md new file mode 100644 index 0000000000..00f3642339 --- /dev/null +++ b/signature/spec/README.md @@ -0,0 +1,118 @@ +# OCI Image Signature Specification + +**Version 0.1** + +## Introduction + +This document specifies an out-of-band cryptographic verification model for +container images. Detached signatures are associated with images by manifest digest hash value. + +## Signature Format + +### Fields + +Field Name | Type | Description +---|:---:|--- +critical | [ [CriticalObject](#criticalObject) ] | **Required.** +optional | [ [OptionalObject](#optionalObject) ] | **Optional.** + +#### Critical Object + +Field Name | Type | Description +---|:---:|--- +identity | [ [IdentityObject](#identityObject) ] | **Required.** +image | [ [ImageObject](#imageObject) ] | **Required.** +type | `string` | **Required.** Valid values: "atomic container signature" + +#### Identity Object + +Field Name | Type | Description +---|:---:|--- +docker-reference | `string` | **Required.** Image name + +#### Image Object + +Field Name | Type | Description +---|:---:|--- +docker-manifest-digest | `digest` | **Required.** Manifest digest in the form of `:` + +#### Optional Object + +Field Name | Type | Description +---|:---:|--- +creator | `string` | **Optional.** Creator ID +timestamp | `int64` | **Optional.** Timestamp epoch + +### Example + +See [example signature file](signature.json). + +### Encryption and Decryption + +The signature data is written to a file that is encrypted with a private key. The file may be decrypted using the corresponding public key. + +**Example GPG encrypt command** + +TODO + +**Example GPG decrypt command** + +``` +$ gpg --decrypt busybox.sig | python -m json.tool +``` + +## Static File Layout + +When encrypted signature files are stored as static files the following directory layout shall be used: + + [/]//// + +## Validation + +1. Decrypt signature file. +1. Compare image manifest digest with signature digest + +## Policy + +Policy provides a way to describe trust by mapping registries, repositories and images to a list of required public keys. It also describes default fallback behavior when conditions are not met. + +Policy answers the following questions: + +* What should be done when an image does not have specific policy? +* What list of public keys do you trust for a given registry, repository or image? + +See [policy specification file](policy.go). + +**Example Policy File** + +https://github.com/projectatomic/skopeo/blob/master/integration/fixtures/policy.json + +## Signature Server Discovery + +To enable a reasonable user experience, a mechanism is needed to map a given registry with a signature server and associated public keys. Without such a mechanism each user would have to look up this information out-of-band. While this can lead to insecure workflows, Discovery allows tooling to prompt users to make a trust evaluation: Do you want to trust this public key for this registry? + +### Discovery Container Format + +A container image may be used to provide registry metadata. The image uses LABELs with signature server metadata. Four LABELs are specified. + +#### Discovery Container Naming + +Discovery container images shall have the following characteristics: + +* The image shall be named **sigstore**. +* The primary image shall be tagged **:latest** +* When more than one image is served arbitrary tags may be used, such as **:auxilliary** or **:backup**. +* The scope of the signature metadata shall apply to the image being served. In other words, an image served at *registry.example.com/acme/sigstore:latest* provides information about images in the "acme" repository namespace. An image served at *registry.example.com/sigstore:latest* provides information about all images in the registry.example.com registry. + +**Example Dockerfile** + +Consider the following Dockerfile built as registry.example.com/acme/sigstore:latest and pushed to the registry. + +``` +FROM scratch + +LABEL sigstore-url="https://sigstore.example.com:8443" \ + pubkey-id="ef442d51: Example, Inc. " \ + pubkey-fingerprint="657F 347A D004 4ADE 55BA 8A5F 199E 2F91 FD43 1D51" \ + pubkey-download-url="https://www.example.com/security/ef431d51.txt" +``` diff --git a/signature/spec/policy.go b/signature/spec/policy.go new file mode 100644 index 0000000000..c4fcaed1d0 --- /dev/null +++ b/signature/spec/policy.go @@ -0,0 +1,127 @@ +type Policy struct { + // Default applies to any image which does not have a matching policy in Specific. + Default PolicyRequirements `json:"default"` + // Specific applies to images matching scope, the map key. + // Scope is registry/server, namespace in a registry, single repository. + // FIXME: Scope syntax - should it be namespaced docker:something ? Or, in the worst case, a composite object (we couldn't use a JSON map) + // Most specific scope wins, duplication is prohibited (hard failure). + // Defaults to an empty map if not specified. + Specific map[string]PolicyRequirements `json:"specific"` +} + +// PolicyRequirements is a set of requirements applying to a set of images; each of them must be satisfied (though perhaps each by a different signature). +// Must not be empty, frequently will only contain a single element. +type PolicyRequirements []PolicyRequirement + +// PolicyRequirement is a rule which must be satisfied by at least one of the signatures of an image. See prCommon and the various pr* structures below. +type PolicyRequirement interface { +} + +// prCommon is the common type field in a JSON encoding of PolicyRequirement. +type prCommon struct { + Type prTypeIdentifier `json:"type"` +} + +// prTypeIdentifier is string designating a kind of a PolicyRequirement. +type prTypeIdentifier string + +const ( + prTypeInsecureAcceptAnything prTypeIdentifier = "insecureAcceptAnything" + prTypeReject prTypeIdentifier = "reject" + prTypeSignedBy prTypeIdentifier = "signedBy" + prTypeSignedBaseLayer prTypeIdentifier = "signedBaseLayer" +) + +// prInsecureAcceptAnything is a PolicyRequirement with type = prTypeInsecureAcceptAnything: every image is accepted. +// Note that because PolicyRequirements are implicitly ANDed, this is necessary only if it is the only rule (to make the list non-empty and the policy explicit). +type prInsecureAcceptAnything struct { + prCommon +} + +// prReject is a PolicyRequirement with type = prTypeReject: every image is rejected. +type prReject struct { + prCommon +} + +// prSignedBy is a PolicyRequirement with type = prTypeSignedBy: the image is signed by trusted keys for a specified identity. +type prSignedBy struct { + prCommon + + // KeyType specifies what kind of key reference KeyPath/KeyData is. + // Acceptable values are “GPGKeys” | “signedByGPGKeys” “X.509Certificates” | “signedByX.509CAs” + // FIXME: eventually also support GPGTOFU, X.509TOFU, with KeyPath only + KeyType sbKeyType `json:"keyType"` + + // KeyPath is a pathname to a local file containing the trusted key(s). Exactly one of KeyPath and KeyData must be specified. + KeyPath string `json:"keyPath,omitempty"` + // KeyData contains the trusted key(s), base64-encoded. Exactly one of KeyPath and KeyData must be specified. + KeyData []byte `json:"keyData,omitempty"` + + // SignedIdentity specifies what image identity the signature must be claiming about the image. + // Defaults to "match-exact" if not specified. + SignedIdentity PolicyReferenceMatch `json:"signedIdentity"` +} + +// sbKeyType are the allowed values for prSignedBy.KeyType +type sbKeyType string + +const ( + // SBKeyTypeGPGKeys refers to keys contained in a GPG keyring + SBKeyTypeGPGKeys sbKeyType = "GPGKeys" + // SBKeyTypeSignedByGPGKeys refers to keys signed by keys in a GPG keyring + SBKeyTypeSignedByGPGKeys sbKeyType = "signedByGPGKeys" + // SBKeyTypeX509Certificates refers to keys in a set of X.509 certificates + // FIXME: PEM, DER? + SBKeyTypeX509Certificates sbKeyType = "X509Certificates" + // SBKeyTypeSignedByX509CAs refers to keys signed by one of the X.509 CAs + // FIXME: PEM, DER? + SBKeyTypeSignedByX509CAs sbKeyType = "signedByX509CAs" +) + +// prSignedBaseLayer is a PolicyRequirement with type = prSignedBaseLayer: the image has a specified, correctly signed, base image. +type prSignedBaseLayer struct { + prCommon + // BaseLayerIdentity specifies the base image to look for. "match-exact" is rejected, "match-repository" is unlikely to be useful. + BaseLayerIdentity PolicyReferenceMatch `json:"baseLayerIdentity"` +} + +// PolicyReferenceMatch specifies a set of image identities accepted in PolicyRequirement. See prmCommon and the various prm* structures below. +type PolicyReferenceMatch interface { +} + +// prmCommon is the common type field in a JSON encoding of PolicyReferenceMatch. +type prmCommon struct { + Type prmTypeIdentifier `json:"type"` +} + +// prmTypeIdentifier is string designating a kind of a PolicyReferenceMatch. +type prmTypeIdentifier string + +const ( + prmTypeMatchExact prmTypeIdentifier = "matchExact" + prmTypeMatchRepository prmTypeIdentifier = "matchRepository" + prmTypeExactReference prmTypeIdentifier = "exactReference" + prmTypeExactRepository prmTypeIdentifier = "exactRepository" +) + +// prmMatchExact is a PolicyReferenceMatch with type = prmMatchExact: the two references must match exactly. +type prmMatchExact struct { + prmCommon +} + +// prmMatchRepository is a PolicyReferenceMatch with type = prmMatchRepository: the two references must use the same repository, may differ in the tag. +type prmMatchRepository struct { + prmCommon +} + +// prmExactReference is a PolicyReferenceMatch with type = prmExactReference: matches a specified reference exactly. +type prmExactReference struct { + prmCommon + DockerReference string `json:"dockerReference"` +} + +// prmExactRepository is a PolicyReferenceMatch with type = prmExactRepository: matches a specified repository, with any tag. +type prmExactRepository struct { + prmCommon + DockerRepository string `json:"dockerRepository"` +} diff --git a/signature/spec/signature.json b/signature/spec/signature.json new file mode 100644 index 0000000000..49351a4258 --- /dev/null +++ b/signature/spec/signature.json @@ -0,0 +1,15 @@ +{ + "critical": { + "identity": { + "docker-reference": "busybox" + }, + "image": { + "docker-manifest-digest": "sha256:a59906e33509d14c036c8678d687bd4eec81ed7c4b8ce907b888c607f6a1e0e6" + }, + "type": "atomic container signature" + }, + "optional": { + "creator": "atomic 0.1.0-dev", + "timestamp": 1471035347 + } +} From fe5c98cbbdfa99be6bd227dafc78f707a88c6454 Mon Sep 17 00:00:00 2001 From: Aaron Weitekamp Date: Fri, 14 Oct 2016 15:13:36 -0400 Subject: [PATCH 2/3] limit scope of signature format --- docs/signature.md | 93 ++++++++++++++++++++++++++++++ signature/README.md | 1 - signature/spec/README.md | 118 --------------------------------------- 3 files changed, 93 insertions(+), 119 deletions(-) create mode 100644 docs/signature.md delete mode 100644 signature/README.md delete mode 100644 signature/spec/README.md diff --git a/docs/signature.md b/docs/signature.md new file mode 100644 index 0000000000..ead80e79e9 --- /dev/null +++ b/docs/signature.md @@ -0,0 +1,93 @@ +# Image Signature Specification + +**Version 0.1** + +## Introduction + +This document defines a detached container image signature object and signing methods. + +## Signature Format + +```js +{ + "critical": {/* required fields */ + "identity": {/* identity reference */}, + "image": {/* signed object reference */ }, + "type": "..." + }, + "optional": {/* optional metadata fields */} + } +} +``` + +### Fields + +There are two top-level fields, **critical** (required) and **optional** (optional). + +#### `critical` + +**identity** (string): + +```js +{ + "docker-reference": imageName +} +``` + +`imageName` per [V2 API](https://docs.docker.com/registry/spec/api/#/overview) Required. + +**image** (string): + +```js +{ + "docker-manifest-digest": manifestDigest +} +``` + +`manifestDigest` in the form of `:` + +**type** (string): Only supported value is "atomic container signature" + +#### `optional` + +**creator** (string): Creator ID. This refers to the tooling used to generate the signature. + +**timestamp** (int64): timestamp epoch + +### Example + +```js +{ + "critical": { + "identity": { + "docker-reference": "busybox" + }, + "image": { + "docker-manifest-digest": "sha256:a59906e33509d14c036c8678d687bd4eec81ed7c4b8ce907b888c607f6a1e0e6" + }, + "type": "atomic container signature" + }, + "optional": { + "creator": "atomic 0.1.0-dev", + "timestamp": 1471035347 + } +} +``` + +### Encryption and Decryption + +The signature data is written to a file that is encrypted and signed with a private key. The file may be decrypted (verified) using the corresponding public key. + +**Example GPG Sign command** + +Given signature file busybox.sig formatted per above: + +``` +$ gpg2 -r KEYID --encrypt --sign busybox.sig +``` + +**Example GPG Verify command** + +``` +$ gpg2 --decrypt busybox.sig.gpg +``` diff --git a/signature/README.md b/signature/README.md deleted file mode 100644 index ac88d5874a..0000000000 --- a/signature/README.md +++ /dev/null @@ -1 +0,0 @@ -See [specification](spec/README.md) diff --git a/signature/spec/README.md b/signature/spec/README.md deleted file mode 100644 index 00f3642339..0000000000 --- a/signature/spec/README.md +++ /dev/null @@ -1,118 +0,0 @@ -# OCI Image Signature Specification - -**Version 0.1** - -## Introduction - -This document specifies an out-of-band cryptographic verification model for -container images. Detached signatures are associated with images by manifest digest hash value. - -## Signature Format - -### Fields - -Field Name | Type | Description ----|:---:|--- -critical | [ [CriticalObject](#criticalObject) ] | **Required.** -optional | [ [OptionalObject](#optionalObject) ] | **Optional.** - -#### Critical Object - -Field Name | Type | Description ----|:---:|--- -identity | [ [IdentityObject](#identityObject) ] | **Required.** -image | [ [ImageObject](#imageObject) ] | **Required.** -type | `string` | **Required.** Valid values: "atomic container signature" - -#### Identity Object - -Field Name | Type | Description ----|:---:|--- -docker-reference | `string` | **Required.** Image name - -#### Image Object - -Field Name | Type | Description ----|:---:|--- -docker-manifest-digest | `digest` | **Required.** Manifest digest in the form of `:` - -#### Optional Object - -Field Name | Type | Description ----|:---:|--- -creator | `string` | **Optional.** Creator ID -timestamp | `int64` | **Optional.** Timestamp epoch - -### Example - -See [example signature file](signature.json). - -### Encryption and Decryption - -The signature data is written to a file that is encrypted with a private key. The file may be decrypted using the corresponding public key. - -**Example GPG encrypt command** - -TODO - -**Example GPG decrypt command** - -``` -$ gpg --decrypt busybox.sig | python -m json.tool -``` - -## Static File Layout - -When encrypted signature files are stored as static files the following directory layout shall be used: - - [/]//// - -## Validation - -1. Decrypt signature file. -1. Compare image manifest digest with signature digest - -## Policy - -Policy provides a way to describe trust by mapping registries, repositories and images to a list of required public keys. It also describes default fallback behavior when conditions are not met. - -Policy answers the following questions: - -* What should be done when an image does not have specific policy? -* What list of public keys do you trust for a given registry, repository or image? - -See [policy specification file](policy.go). - -**Example Policy File** - -https://github.com/projectatomic/skopeo/blob/master/integration/fixtures/policy.json - -## Signature Server Discovery - -To enable a reasonable user experience, a mechanism is needed to map a given registry with a signature server and associated public keys. Without such a mechanism each user would have to look up this information out-of-band. While this can lead to insecure workflows, Discovery allows tooling to prompt users to make a trust evaluation: Do you want to trust this public key for this registry? - -### Discovery Container Format - -A container image may be used to provide registry metadata. The image uses LABELs with signature server metadata. Four LABELs are specified. - -#### Discovery Container Naming - -Discovery container images shall have the following characteristics: - -* The image shall be named **sigstore**. -* The primary image shall be tagged **:latest** -* When more than one image is served arbitrary tags may be used, such as **:auxilliary** or **:backup**. -* The scope of the signature metadata shall apply to the image being served. In other words, an image served at *registry.example.com/acme/sigstore:latest* provides information about images in the "acme" repository namespace. An image served at *registry.example.com/sigstore:latest* provides information about all images in the registry.example.com registry. - -**Example Dockerfile** - -Consider the following Dockerfile built as registry.example.com/acme/sigstore:latest and pushed to the registry. - -``` -FROM scratch - -LABEL sigstore-url="https://sigstore.example.com:8443" \ - pubkey-id="ef442d51: Example, Inc. " \ - pubkey-fingerprint="657F 347A D004 4ADE 55BA 8A5F 199E 2F91 FD43 1D51" \ - pubkey-download-url="https://www.example.com/security/ef431d51.txt" -``` From e5a20d98fe698732df2b142846d007b45873627f Mon Sep 17 00:00:00 2001 From: Aaron Weitekamp Date: Fri, 14 Oct 2016 15:15:06 -0400 Subject: [PATCH 3/3] remove files from spec dir --- signature/spec/policy.go | 127 ---------------------------------- signature/spec/signature.json | 15 ---- 2 files changed, 142 deletions(-) delete mode 100644 signature/spec/policy.go delete mode 100644 signature/spec/signature.json diff --git a/signature/spec/policy.go b/signature/spec/policy.go deleted file mode 100644 index c4fcaed1d0..0000000000 --- a/signature/spec/policy.go +++ /dev/null @@ -1,127 +0,0 @@ -type Policy struct { - // Default applies to any image which does not have a matching policy in Specific. - Default PolicyRequirements `json:"default"` - // Specific applies to images matching scope, the map key. - // Scope is registry/server, namespace in a registry, single repository. - // FIXME: Scope syntax - should it be namespaced docker:something ? Or, in the worst case, a composite object (we couldn't use a JSON map) - // Most specific scope wins, duplication is prohibited (hard failure). - // Defaults to an empty map if not specified. - Specific map[string]PolicyRequirements `json:"specific"` -} - -// PolicyRequirements is a set of requirements applying to a set of images; each of them must be satisfied (though perhaps each by a different signature). -// Must not be empty, frequently will only contain a single element. -type PolicyRequirements []PolicyRequirement - -// PolicyRequirement is a rule which must be satisfied by at least one of the signatures of an image. See prCommon and the various pr* structures below. -type PolicyRequirement interface { -} - -// prCommon is the common type field in a JSON encoding of PolicyRequirement. -type prCommon struct { - Type prTypeIdentifier `json:"type"` -} - -// prTypeIdentifier is string designating a kind of a PolicyRequirement. -type prTypeIdentifier string - -const ( - prTypeInsecureAcceptAnything prTypeIdentifier = "insecureAcceptAnything" - prTypeReject prTypeIdentifier = "reject" - prTypeSignedBy prTypeIdentifier = "signedBy" - prTypeSignedBaseLayer prTypeIdentifier = "signedBaseLayer" -) - -// prInsecureAcceptAnything is a PolicyRequirement with type = prTypeInsecureAcceptAnything: every image is accepted. -// Note that because PolicyRequirements are implicitly ANDed, this is necessary only if it is the only rule (to make the list non-empty and the policy explicit). -type prInsecureAcceptAnything struct { - prCommon -} - -// prReject is a PolicyRequirement with type = prTypeReject: every image is rejected. -type prReject struct { - prCommon -} - -// prSignedBy is a PolicyRequirement with type = prTypeSignedBy: the image is signed by trusted keys for a specified identity. -type prSignedBy struct { - prCommon - - // KeyType specifies what kind of key reference KeyPath/KeyData is. - // Acceptable values are “GPGKeys” | “signedByGPGKeys” “X.509Certificates” | “signedByX.509CAs” - // FIXME: eventually also support GPGTOFU, X.509TOFU, with KeyPath only - KeyType sbKeyType `json:"keyType"` - - // KeyPath is a pathname to a local file containing the trusted key(s). Exactly one of KeyPath and KeyData must be specified. - KeyPath string `json:"keyPath,omitempty"` - // KeyData contains the trusted key(s), base64-encoded. Exactly one of KeyPath and KeyData must be specified. - KeyData []byte `json:"keyData,omitempty"` - - // SignedIdentity specifies what image identity the signature must be claiming about the image. - // Defaults to "match-exact" if not specified. - SignedIdentity PolicyReferenceMatch `json:"signedIdentity"` -} - -// sbKeyType are the allowed values for prSignedBy.KeyType -type sbKeyType string - -const ( - // SBKeyTypeGPGKeys refers to keys contained in a GPG keyring - SBKeyTypeGPGKeys sbKeyType = "GPGKeys" - // SBKeyTypeSignedByGPGKeys refers to keys signed by keys in a GPG keyring - SBKeyTypeSignedByGPGKeys sbKeyType = "signedByGPGKeys" - // SBKeyTypeX509Certificates refers to keys in a set of X.509 certificates - // FIXME: PEM, DER? - SBKeyTypeX509Certificates sbKeyType = "X509Certificates" - // SBKeyTypeSignedByX509CAs refers to keys signed by one of the X.509 CAs - // FIXME: PEM, DER? - SBKeyTypeSignedByX509CAs sbKeyType = "signedByX509CAs" -) - -// prSignedBaseLayer is a PolicyRequirement with type = prSignedBaseLayer: the image has a specified, correctly signed, base image. -type prSignedBaseLayer struct { - prCommon - // BaseLayerIdentity specifies the base image to look for. "match-exact" is rejected, "match-repository" is unlikely to be useful. - BaseLayerIdentity PolicyReferenceMatch `json:"baseLayerIdentity"` -} - -// PolicyReferenceMatch specifies a set of image identities accepted in PolicyRequirement. See prmCommon and the various prm* structures below. -type PolicyReferenceMatch interface { -} - -// prmCommon is the common type field in a JSON encoding of PolicyReferenceMatch. -type prmCommon struct { - Type prmTypeIdentifier `json:"type"` -} - -// prmTypeIdentifier is string designating a kind of a PolicyReferenceMatch. -type prmTypeIdentifier string - -const ( - prmTypeMatchExact prmTypeIdentifier = "matchExact" - prmTypeMatchRepository prmTypeIdentifier = "matchRepository" - prmTypeExactReference prmTypeIdentifier = "exactReference" - prmTypeExactRepository prmTypeIdentifier = "exactRepository" -) - -// prmMatchExact is a PolicyReferenceMatch with type = prmMatchExact: the two references must match exactly. -type prmMatchExact struct { - prmCommon -} - -// prmMatchRepository is a PolicyReferenceMatch with type = prmMatchRepository: the two references must use the same repository, may differ in the tag. -type prmMatchRepository struct { - prmCommon -} - -// prmExactReference is a PolicyReferenceMatch with type = prmExactReference: matches a specified reference exactly. -type prmExactReference struct { - prmCommon - DockerReference string `json:"dockerReference"` -} - -// prmExactRepository is a PolicyReferenceMatch with type = prmExactRepository: matches a specified repository, with any tag. -type prmExactRepository struct { - prmCommon - DockerRepository string `json:"dockerRepository"` -} diff --git a/signature/spec/signature.json b/signature/spec/signature.json deleted file mode 100644 index 49351a4258..0000000000 --- a/signature/spec/signature.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "critical": { - "identity": { - "docker-reference": "busybox" - }, - "image": { - "docker-manifest-digest": "sha256:a59906e33509d14c036c8678d687bd4eec81ed7c4b8ce907b888c607f6a1e0e6" - }, - "type": "atomic container signature" - }, - "optional": { - "creator": "atomic 0.1.0-dev", - "timestamp": 1471035347 - } -}