Skip to content

Commit

Permalink
initial signature spec
Browse files Browse the repository at this point in the history
  • Loading branch information
aweiteka committed Aug 25, 2016
1 parent 8de80f2 commit 3067e64
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 0 deletions.
1 change: 1 addition & 0 deletions signature/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See [specification](spec/README.md)
118 changes: 118 additions & 0 deletions signature/spec/README.md
Original file line number Diff line number Diff line change
@@ -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
---|:---:|---
<a name="critical"></a>critical | [ [CriticalObject](#criticalObject) ] | **Required.**
<a name="optional"></a>optional | [ [OptionalObject](#optionalObject) ] | **Optional.**

#### <a name="criticalObject"></a>Critical Object

Field Name | Type | Description
---|:---:|---
<a name="identity"></a>identity | [ [IdentityObject](#identityObject) ] | **Required.**
<a name="image"></a>image | [ [ImageObject](#imageObject) ] | **Required.**
<a name="type"></a>type | `string` | **Required.** Valid values: "atomic container signature"

#### <a name="identityObject"></a>Identity Object

Field Name | Type | Description
---|:---:|---
<a name="docker-reference"></a>docker-reference | `string` | **Required.** Image name

#### <a name="imageObject"></a>Image Object

Field Name | Type | Description
---|:---:|---
<a name="docker-manifest-digest"></a>docker-manifest-digest | `digest` | **Required.** Manifest digest in the form of `<algorithm>:<hashValue>`

#### <a name="optionalObject"></a>Optional Object

Field Name | Type | Description
---|:---:|---
<a name="creator"></a>creator | `string` | **Optional.** Creator ID
<a name="timestamp"></a>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:

[/<pathPrefix>]/<registryUri>/<repository>/<image@sha256:hash>/<pubkeyID>

## 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. <security@example.com>" \
pubkey-fingerprint="657F 347A D004 4ADE 55BA 8A5F 199E 2F91 FD43 1D51" \
pubkey-download-url="https://www.example.com/security/ef431d51.txt"
```
127 changes: 127 additions & 0 deletions signature/spec/policy.go
Original file line number Diff line number Diff line change
@@ -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"`
}
15 changes: 15 additions & 0 deletions signature/spec/signature.json
Original file line number Diff line number Diff line change
@@ -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
}
}

0 comments on commit 3067e64

Please sign in to comment.