diff --git a/README.md b/README.md
index b1a49ac..161acd9 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,372 @@
-# attest
-library to create, verify, and evaluate policy for attestations on container images
+# `attest`
+
+Library to create attestation signatures on container images, and verify images against policy.
+
+[](https://pkg.go.dev/github.com/docker/attest)
+[](https://github.com/docker/attest/actions/workflows/test.yml)
[](https://codecov.io/gh/docker/attest)
-# usage
-## signing and verifying attestations
-See [example_sign_test.go](./pkg/attest/example_sign_test.go)
+
-See [example_verify_test.go](./pkg/attest/example_verify_test.go)
+# Table of Contents
-## mirroring TUF repositories to OCI
-See [example_mirror_test.go](./pkg/mirror/example_mirror_test.go)
+- [`attest`](#attest)
+- [Table of Contents](#table-of-contents)
+- [What is this?](#what-is-this)
+- [Features](#features)
+- [Installation](#installation)
+- [Usage](#usage)
+ - [Verifying Image Attestations](#verifying-image-attestations)
+ - [Signing Attestations](#signing-attestations)
+- [Rego Policy](#rego-policy)
+ - [Writing Policy](#writing-policy)
+ - [Input](#input)
+ - [Builtin Functions](#builtin-functions)
+ - [Policy Mapping](#policy-mapping)
+- [Public Key IDs](#public-key-ids)
+- [Transparency Logging](#transparency-logging)
+- [Verification Summary Attestation (VSA)](#verification-summary-attestation-vsa)
+ - [Example VSA](#example-vsa)
+- [API Reference](#api-reference)
+- [Project Layout](#project-layout)
+- [Versioning](#versioning)
-### using `go-tuf` OCI registry client
-See [example_registry_test.go](./pkg/tuf/example_registry_test.go)
+# What is this?
+
+`attest` is a library for signing and verifying [in-toto](https://in-toto.io/) attestations on container images.
+Examples of attestations include statements about the provenance and SBOM of an image.
+
+This library can be used to verify these attestations using Rego policy.
+Policy can be used to check whether an attestation is correctly signed, and that the contents of the attestation are correct.
+
+Our overall goal with this project is adoption of the ideas into other open-source projects, rather than to create another standalone tool.
+It would be a great outcome if this library was no longer needed because the functionality was built into other tools.
+
+# Features
+
+- Sign in-toto attestations
+- Push attestations to container registries using OCI 1.1 compatible artifacts
+- Verify attestations on container images using Rego policy and attestations fetched using OCI 1.1 referrers
+
+# Installation
+
+```shell
+$ go get github.com/docker/attest
+```
+
+# Usage
+
+## Verifying Image Attestations
+
+An image's attestations can be verified against a policy using the `attest.Verify` function.
+This function takes an [oci.ImageSpec](https://github.com/docker/attest/blob/781a738b54b9549c1dabfd7ea3f7ea582514ddec/pkg/oci/types.go#L35-L41) for the image to verify, and a set of options for policy resolution.
+By default, the policy is resolved from the [the Docker TUF repository](https://github.com/docker/tuf), but the options can be used to specify an alternative TUF repository, a local policy directory, and/or a policy ID to use.
+See [Policy Mapping](#policy-mapping) for more details.
+
+The `attest.Verify` function returns a `VerificationSummary` object, which contains the results of the policy evaluation.
+
+See [example_verify_test.go](./pkg/attest/example_verify_test.go) for an example of how to verify an image against a policy.
+
+## Signing Attestations
+
+in-toto statements can be signed directly using the `attestation.SignInTotoStatement` function.
+This function takes a statement and DSSE signer, and returns a signed DSSE envelope containing a copy of the original statement.
+
+For the common use case of signing a statement and adding it to a manifest, e.g. for pushing to a registry as a referrer to the image being attested, the `attestation.AttestationManifest` type can be used.
+See [example_attestation_manifest_test.go](./pkg/attestation/example_attestation_manifest_test.go)
+
+See also [example_sign_test.go](./pkg/attest/example_sign_test.go) for an example of how to sign all attached in-toto statements on an image, e.g. those produced by buildkit.
+
+# Rego Policy
+
+An image policy consists of one or more `rego` files and, optionally, `json` or `yaml` data files.
+
+The policies for trusted namespaces `docker.io/docker` and `docker.io/library` are stored in [the Docker TUF root](https://github.com/docker/tuf) under the `docker` and `doi` target sub-directories respectively.
+
+## Writing Policy
+
+`attest` uses [Open Policy Agent](https://www.openpolicyagent.org/) (OPA) for policy evaluation, and policies are written in Rego.
+A full guide to writing Rego policies is available in the [Rego documentation](https://www.openpolicyagent.org/docs/latest/policy-language/).
+
+For attest, a policy must contain at a minimum a `result` rule in a package called `attest` that returns an object matching the schema defined by the [`policy.Result`](https://github.com/docker/attest/blob/bd2c4d7d8aa497754b674412b09628be8d02fab5/pkg/policy/types.go#L23-L27) struct.
+For example:
+
+```rego
+package attest
+
+import rego.v1
+
+result := {
+ "success": true,
+ "violations": set(),
+ "summary": {
+ "subjects": subjects,
+ "slsa_levels": ["SLSA_BUILD_LEVEL_3"],
+ "verifier": "docker-official-images",
+ "policy_uri": "https://docker.com/official/policy/v0.1",
+ },
+}
+```
+
+The meanings of the fields in the `result` object are as follows:
+
+- `success` (bool): whether the policy passes
+- `violations` (set): a set of strings describing any policy violations
+- `summary` (object): a summary of the policy evaluation, used to construct a Verification Summary Attestation (VSA)
+ - `subjects` (set): a set of strings representing the subjects of each attestation that was evaluated
+ - `slsa_levels` (list): a list of strings representing the SLSA levels that the policy complies with
+ - `verifier` (string): the entity that verified the policy
+ - `policy_uri` (string): the URI of the policy
+
+The `violations` set may contain policy violations even if `success` is `true`.
+This can be useful if there are attestations that are invalid, but are not required by the policy.
+
+### Input
+
+The input to the policy is an object with the following fields:
+
+- `digest` (string): the digest of the image being verified
+- `purl` (string): the package URL of the image being verified
+- `is_canonical` (bool): whether the image being verified was referenced by a 'canonical' name, i.e. one that contains a digest
+
+### Builtin Functions
+
+There are two builtin functions provided by `attest` that can be used to help with policy evaluation:
+
+- `attest.fetch(predicate_type)`: fetches all attestations for the input image with the given predicate type.
+ For example, `attest.fetch("https://spdx.dev/Document")` will fetch all SPDX SBOM attestations for the input image.
+- `attest.verify(attestation, options)`: verifies the DSSE envelope of the given attestation, and returns the statement.
+ The options object can contain the following fields:
+ - `keys` (array): keys to use for signature verification. Each key contains the following fields:
+ - `id` (string): the key ID as specified in [Public Key IDs](#public-key-ids)
+ - `key` (string): the PEM-encoded public key
+ - `from` (string): the time from which the key is valid, or `null` if the key was always valid (default: `null`)
+ - `status` (string): `active` if the key is active, otherwise the reason the key is inactive.
+ This is only used in error messages if the `from` date is in the past
+ - `distrust` (bool): whether the key should be distrusted (default: `false`).
+ If `true`, the key will be considered invalid
+ - `signing-format` (string): the format of the signing key, must be `dssev1`
+ - `skip_tl` (bool): whether to skip transparency log entry verification (see [Transparency Logging](#transparency-logging)) (default: `false`)
+
+Both `attest.fetch` and `attest.verify` return an object with the following fields:
+
+- `value`: the return value of the function if successful
+- `error`: an error message if the function failed
+
+This is to allow the policy to easily construct a violation if an error occurs, which isn't usually possible with custom functions in Rego.
+
+The return value of `attest.fetch` is an attestation which can be passed to `attest.verify`.
+
+## Policy Mapping
+
+A `mapping.yaml` file is stored at the root of TUF targets and contains the mapping from repository name to files containing the corresponding policy.
+
+A simple mapping file might look like this:
+
+```yaml
+version: v1
+kind: policy-mapping
+policies:
+ - id: docker-official-images
+ description: Docker Official Images
+ files:
+ - path: doi/policy.rego
+rules:
+ - pattern: "^docker[.]io/library/(.*)$"
+ policy-id: docker-official-images
+```
+
+The `policies` section contains a list of policies, each with an `id` and a `description`, and a list of `files` containing the policy.
+The `rules` section contains a list of rules that map regex expressions to policies.
+If the `pattern` regex matches the repository name, the policy with the `policy-id` is used to evaluate the image.
+In the above example, any repository in the `docker.io/library` namespace will be evaluated against the policy in `doi/policy.rego`.
+
+Sometimes it is necessary to rewrite the repository name before evaluating the policy.
+This can be useful when the repository name which is used to reference the image is different from the repository name in the attestations.
+For example, when mirroring images from a public registry to a private registry, the repository name in the attestations will be the public registry, but the image will be referenced by the name of the private registry.
+
+An example of a mapping file with rewrite rules might look like this:
+
+```yaml
+version: v1
+kind: policy-mapping
+policies:
+ - id: docker-official-images
+ description: Docker Official Images
+ files:
+ - path: doi/policy.rego
+rules:
+ - pattern: "^docker[.]io/library/(.*)$"
+ policy-id: docker-official-images
+ - pattern: "^public[.]ecr[.]aws/docker/library/(.*)$"
+ rewrite: docker.io/library/$1
+```
+
+As before, any repository in the `docker.io/library` namespace will be evaluated against the policy in `doi/policy.rego`.
+The second rule will rewrite any repository in the `public.ecr.aws/docker/library` namespace to `docker.io/library`.
+This means two things:
+
+1. The rules are evaluated again using the rewritten repository name until a policy is found (in this case the first rule will match); and
+2. The rewritten name is passed into the actual policy when it is evaluated.
+
+The `rewrite` field is not a simple string replacement, but a regex replacement.
+This means that the `rewrite` field can contain capture groups that are referenced in the `pattern` field.
+For example, the `rewrite` field in the example above contains `$1`, which is a reference to the first capture group in the `pattern` field.
+
+> [!IMPORTANT]
+> It's important to remember to escape the `.` character in the `pattern` field, as it is a special character in regex.
+> This is why the `.` character is surrounded by `[]` in the example above.
+>
+> It's also important to make use of the `^` and `$` characters in the `pattern` field to ensure that the regex matches the entire repository name.
+> This is to prevent the regex from matching a subset of the repository name, e.g. `docker.io/library` matching `notdocker.io/library`.
+
+Local policy can also be specified via a local `mapping.yaml`, which can be used to create new mirrors of policies described in the Docker TUF root, as well as describing entirely independent policies. For example:
+
+```golang
+// configure policy options
+opts := &policy.PolicyOptions{
+ TufClient: tufClient,
+ LocalPolicyDir: "", // overrides TUF policy for local policy files if set
+ PolicyId: "", // set to ignore policy mapping and select a policy by id
+}
+
+src, err := oci.ParseImageSpec(image, oci.WithPlatform(platform))
+if err != nil {
+ panic(err)
+}
+// verify attestations
+result, err := attest.Verify(context.Background(), src, opts)
+if err != nil {
+ panic(err)
+}
+```
+
+where `` is a directory containing a `mapping.yaml` file, and any policy files referenced in the `mapping.yaml`. For example:
+
+```
+├── myimages
+│ ├── data.yaml
+| ├── keys.yaml
+│ └── policy.rego
+└── mapping.yaml
+```
+
+> [!NOTE]
+>
+> `PolicyId` can also be set to select a policy by ID, completely ignoring the `rules` section of the mapping file.
+
+The rules section of a local `mapping.yaml` can refer to the policies described in the `mapping.yaml` file in the Docker TUF root to specify additional mirrors to which the referenced policy can be applied.
+
+For example, it might be desirable to mirror `docker.io/library` to a local registry for testing:
+
+```yaml
+version: v1
+kind: policy-mapping
+rules:
+ - pattern: "^localhost:5001/(.*)$"
+ rewrite: docker.io/library/$1
+```
+
+The rewritten repository name will match the `docker-official-images` polict in the TUF managed `mapping.yaml`.
+
+> [!WARNING]
+> Local `mapping.yaml` policies take precendence over TUF managed policies, so for example, it's possible to apply a custom policy to `docker.io/library` namespace:
+>
+> ```yaml
+> version: v1
+> kind: policy-mapping
+> policies:
+> - id: mydoi
+> description: my doi policy
+> files:
+> - path: "mypolicy.rego"
+>
+> rules:
+> - pattern: "^docker[.]io/library/(.*)$"
+> policy-id: mydoi
+> ```
+
+# Public Key IDs
+
+When signing attestations, a key-id is generated from the public key and added to envelope.
+This is used at verification time to look up the public key.
+
+To generate a key-id from a public key, use `openssl` as follows:
+
+```shell
+openssl pkey -in -pubin -outform DER | openssl dgst -sha256
+```
+
+# Transparency Logging
+
+`attest` supports transparency logging for attestation signatures.
+This serves two purposes:
+
+1. the transparency log is a mechanism to ensure that all attestations are logged in a tamper-evident way, and that the logs are publicly auditable; and
+2. the transparency log is a trusted source of timestamps for attestations, which allows signatures to be verified even if the key used to sign the attestation has expired.
+
+By default, transparency logging is enabled and the logs are stored in the [public-good Rekor](https://docs.sigstore.dev/logging/overview/) instance.
+Another transparency log can be used by creating an implementation of the [tl.TL](https://github.com/docker/attest/blob/781a738b54b9549c1dabfd7ea3f7ea582514ddec/pkg/tlog/tl.go#L57-L62) interface and using [`tl.WithTL`](https://github.com/docker/attest/blob/781a738b54b9549c1dabfd7ea3f7ea582514ddec/pkg/tlog/tl.go#L37) to set in on a context.
+Alternatively, transparency logging can be disabled when signing by using `SkipTL` in the `SigningOptions`, and when verifying by using `skip_tl` in the options to `attest.verify` in the Rego policy.
+
+# Verification Summary Attestation (VSA)
+
+Verification of attestations can be expensive, especially when the attestations are large.
+For example, an SBOM attestation can be several megabytes in size.
+An alternative to consumers verifying the full attestation is to have a trusted entity verify the attestation and publish a [SLSA Verification Summary Attestation](https://slsa.dev/spec/v1.0/verification_summary) (VSA) to the registry.
+The VSA can then be verified by the consumer without needing to verify the full attestation, as long as the consumer trusts the entity that signed the VSA.
+This is useful when the consumer only needs to know that the attestation was verified by a trusted entity, and does not need to know the details of the attestation.
+
+A useful pattern is to have apply a policy to a third-party image at initial ingress, then publish a VSA when publishing the image to an internal registry to attest that the image complies with the policy.
+The VSA can be verified very quickly, for example in a Kubernetes admission controller.
+
+`attest` always generates a [SLSA VSA](https://slsa.dev/spec/v1.0/verification_summary) when verifying attestations on an image.
+The VSA can be signed and published to the registry using the signing functions mentioned in [Signing Attestations](#signing-attestations).
+
+## Example VSA
+
+```json
+{
+ "_type": "https://in-toto.io/Statement/v1",
+ "subject": [
+ {
+ "name": "pkg:docker/example.org/example-image@1.0?platform=linux%2Famd64",
+ "digest": {
+ "sha256": "49f717386e5462e945232569a97a05831cb83bef8c3369be3bb7ea1793686960"
+ }
+ }
+ ],
+ "predicateType": "https://slsa.dev/verification_summary/v1",
+ "predicate": {
+ "verifier": {
+ "id": "https://example.org/internal-verifier"
+ },
+ "timeVerified": "2024-04-19T08:00:00.01Z",
+ "resourceUri": "pkg:docker/example.org/example-image@1.0?platform=linux%2Famd64&digest=sha256%3A49f717386e5462e945232569a97a05831cb83bef8c3369be3bb7ea1793686960",
+ "policy": {
+ "uri": "https://example.org/internal-policy/v1"
+ },
+ "verificationResult": "PASSED",
+ "verifiedLevels": ["SLSA_BUILD_LEVEL_3"]
+ }
+}
+```
+
+# API Reference
+
+Full API reference can be found at [pkg.go.dev/github.com/docker/attest](https://pkg.go.dev/github.com/docker/attest).
+
+# Project Layout
+
+- [pkg/](https://pkg.go.dev/github.com/docker/image-signer-verifier/pkg) => packages that are okay to import for other projects
+- [internal/](https://pkg.go.dev/github.com/docker/image-signer-verifier/pkg) => packages that are only for project internal purposes
+- [scripts/](scripts/) => build scripts
+- [test/](test/) => data for use in tests
+
+# Versioning
+
+`attest` uses [Semantic Versioning](https://semver.org/).
+As such, until `attest` reaches version 1.0.0, breaking changes may be introduced in minor versions.
+
+> Anything MAY change at any time. The public API SHOULD NOT be considered stable.
diff --git a/pkg/attest/example_verify_test.go b/pkg/attest/example_verify_test.go
index d13e589..3517ab6 100644
--- a/pkg/attest/example_verify_test.go
+++ b/pkg/attest/example_verify_test.go
@@ -48,11 +48,11 @@ func ExampleVerify_remote() {
PolicyId: "", // set to ignore policy mapping and select a policy by id
}
- // verify attestations
src, err := oci.ParseImageSpec(image, oci.WithPlatform(platform))
if err != nil {
panic(err)
}
+ // verify attestations
result, err := attest.Verify(context.Background(), src, opts)
if err != nil {
panic(err)
diff --git a/pkg/attestation/example_attestation_manifest_test.go b/pkg/attestation/example_attestation_manifest_test.go
new file mode 100644
index 0000000..45a6d4d
--- /dev/null
+++ b/pkg/attestation/example_attestation_manifest_test.go
@@ -0,0 +1,87 @@
+package attestation_test
+
+import (
+ "context"
+ "time"
+
+ "github.com/docker/attest/pkg/attest"
+ "github.com/docker/attest/pkg/attestation"
+ "github.com/docker/attest/pkg/mirror"
+ "github.com/docker/attest/pkg/oci"
+ "github.com/docker/attest/pkg/signerverifier"
+ v1 "github.com/google/go-containerregistry/pkg/v1"
+ intoto "github.com/in-toto/in-toto-golang/in_toto"
+ "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
+)
+
+func ExampleAttestationManifest() {
+ // configure signerverifier
+ // local signer (unsafe for production)
+ signer, err := signerverifier.GenKeyPair()
+ if err != nil {
+ panic(err)
+ }
+ // example using AWS KMS signer
+ // aws_arn := "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012"
+ // aws_region := "us-west-2"
+ // signer, err := signerverifier.GetAWSSigner(cmd.Context(), aws_arn, aws_region)
+
+ // configure signing options
+ opts := &attestation.SigningOptions{
+ SkipTL: true, // skip trust logging to a transparency log
+ }
+
+ ref := "docker/image-signer-verifier:latest"
+
+ digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
+ if err != nil {
+ panic(err)
+ }
+ desc := &v1.Descriptor{
+ Digest: digest,
+ Size: 1234,
+ MediaType: "application/vnd.oci.image.manifest.v1+json",
+ }
+
+ // the in-toto statement to be signed
+ statement := &intoto.Statement{
+ StatementHeader: intoto.StatementHeader{
+ PredicateType: attestation.VSAPredicateType,
+ Subject: []intoto.Subject{{Name: ref, Digest: common.DigestSet{digest.Algorithm: digest.Hex}}},
+ Type: intoto.StatementInTotoV01,
+ },
+ Predicate: attestation.VSAPredicate{
+ Verifier: attestation.VSAVerifier{
+ ID: "test-verifier",
+ },
+ TimeVerified: time.Now().UTC().Format(time.RFC3339),
+ ResourceUri: "some-uri",
+ Policy: attestation.VSAPolicy{URI: "some-uri"},
+ VerificationResult: "PASSED",
+ VerifiedLevels: []string{"SLSA_BUILD_LEVEL_1"},
+ },
+ }
+
+ // create a new manifest to hold the attestation
+ manifest, err := attest.NewAttestationManifest(desc)
+ if err != nil {
+ panic(err)
+ }
+
+ // sign and add the attestation to the manifest
+ err = manifest.AddAttestation(context.Background(), signer, statement, opts)
+ if err != nil {
+ panic(err)
+ }
+
+ output, err := oci.ParseImageSpecs("docker/image-signer-verifier-referrers:latest")
+ if err != nil {
+ panic(err)
+ }
+
+ // save the manifest to the registry as a referrers artifact
+ err = mirror.SaveReferrers(manifest, output)
+ if err != nil {
+ panic(err)
+ }
+}