Info
Cosign
is developed as part of the sigstore
project.
We also use a slack channel!
Click here for the invite link.
Dockerfile
Here is how to install and use cosign inside a Dockerfile through the gcr.io/projectsigstore/cosign image:
FROM gcr.io/projectsigstore/cosign:v1.13.0 as cosign-bin
# Source: https://github.com/chainguard-images/static
FROM cgr.dev/chainguard/static:latest
COPY --from=cosign-bin /ko-app/cosign /usr/local/bin/cosign
ENTRYPOINT [ "cosign" ]
Quick Start
This shows how to:
- sign a container image with the default identity-based “keyless signing” method (see the documentation for more information)
- verify the container image
Sign a container and store the signature in the registry
Note that you should always sign images based on their digest (@sha256:...
)
rather than a tag (:latest
) because otherwise you might sign something you
didn’t intend to!
cosign sign $IMAGE
Generating ephemeral keys...
Retrieving signed certificate...
Note that there may be personally identifiable information associated with this signed artifact.
This may include the email address associated with the account with which you authenticate.
This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later.
By typing 'y', you attest that you grant (or have permission to grant) and agree to have this information stored permanently in transparency logs.
Are you sure you would like to continue? [y/N] y
Your browser will now be opened to:
https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&code_challenge=OrXitVKUZm2lEWHVt1oQWR4HZvn0rSlKhLcltglYxCY&code_challenge_method=S256&nonce=2KvOWeTFxYfxyzHtssvlIXmY6Jk&redirect_uri=http%3A%2F%2Flocalhost%3A57102%2Fauth%2Fcallback&response_type=code&scope=openid+email&state=2KvOWfbQJ1caqScgjwibzK2qJmb
Successfully verified SCT...
tlog entry created with index: 12086900
Pushing signature to: $IMAGE
Cosign will prompt you to authenticate via OIDC, where you’ll sign in with your email address. Under the hood, cosign will request a code signing certificate from the Fulcio certificate authority. The subject of the certificate will match the email address you logged in with. Cosign will then store the signature and certificate in the Rekor transparency log, and upload the signature to the OCI registry alongside the image you’re signing.
Verify a container
To verify the image, you’ll need to pass in the expected certificate subject and certificate issuer via the --certificate-identity
and --certificate-oidc-issuer
flags:
cosign verify $IMAGE --certificate-identity=$IDENTITY --certificate-oidc-issuer=$OIDC_ISSUER
You can also pass in a regex for the certificate identity and issuer flags, --certificate-identity-regexp
and --certificate-oidc-issuer-regexp
.
Verify a container in an air-gapped environment
Cosign can do completely offline verification by verifying a bundle which is typically distributed as an annotation on the image manifest.
As long as this annotation is present, then offline verification can be done.
This bundle annotation is always included by default for keyless signing, so the default cosign sign
functionality will include all materials needed for offline verification.
To verify an image in an air-gapped environment, the image and signatures must be available locally on the filesystem.
An image can be saved locally using cosign save
(note, this step must be done with a network connection):
cosign initialize # This will pull in the latest TUF root
cosign save $IMAGE_NAME --dir ./path/to/dir
Now, in an air-gapped environment, this local image can be verified:
cosign verify --certificate-identity $CERT_IDENTITY --certificate-oidc-issuer $CERT_OIDC_ISSUER --offline --local-image ./path/to/dir
You’ll need to pass in expected values for $CERT_IDENTITY
and $CERT_OIDC_ISSUER
to correctly verify this image.
If you signed with a keypair, the same command will work, assuming the public key material is present locally:
cosign verify --key cosign.pub --offline --local-image ./path/to/dir
What ** is not ** production ready?
While parts of cosign
are stable, we are continuing to experiment and add new features.
The following feature set is not considered stable yet, but we are committed to stabilizing it over time!
Formats/Specifications
While the cosign
code for uploading, signing, retrieving, and verifying several artifact types is stable,
the format specifications for some of those types may not be considered stable yet.
Some of these are developed outside of the cosign
project, so we are waiting for them to stabilize first.
These include:
- The SBOM specification for storing SBOMs in a container registry
- The In-Toto attestation format
Working with Other Artifacts
OCI registries are useful for storing more than just container images!
Cosign
also includes some utilities for publishing generic artifacts, including binaries, scripts, and configuration files using the OCI protocol.
This section shows how to leverage these for an easy-to-use, backwards-compatible artifact distribution system that integrates well with the rest of Sigstore.
See the documentation for more information.
Blobs
You can publish an artifact with cosign upload blob
:
$ echo "my first artifact" > artifact
$ BLOB_SUM=$(shasum -a 256 artifact | cut -d' ' -f 1) && echo "$BLOB_SUM"
c69d72c98b55258f9026f984e4656f0e9fd3ef024ea3fac1d7e5c7e6249f1626
$ BLOB_NAME=my-artifact-$(uuidgen | head -c 8 | tr 'A-Z' 'a-z')
$ BLOB_URI=ttl.sh/$BLOB_NAME:1h
$ BLOB_URI_DIGEST=$(cosign upload blob -f artifact $BLOB_URI) && echo "$BLOB_URI_DIGEST"
Uploading file from [artifact] to [ttl.sh/my-artifact-f42c22e0:5m] with media type [text/plain]
File [artifact] is available directly at [ttl.sh/v2/my-artifact-f42c22e0/blobs/sha256:c69d72c98b55258f9026f984e4656f0e9fd3ef024ea3fac1d7e5c7e6249f1626]
Uploaded image to:
ttl.sh/my-artifact-f42c22e0@sha256:790d47850411e902aabebc3a684eeb78fcae853d4dd6e1cc554d70db7f05f99f
Your users can download it from the “direct” url with standard tools like curl or wget:
$ curl -L ttl.sh/v2/$BLOB_NAME/blobs/sha256:$BLOB_SUM > artifact-fetched
The digest is baked right into the URL, so they can check that as well:
$ cat artifact-fetched | shasum -a 256
c69d72c98b55258f9026f984e4656f0e9fd3ef024ea3fac1d7e5c7e6249f1626 -
You can sign it with the normal cosign sign
command and flags:
$ cosign sign --key cosign.key $BLOB_URI_DIGEST
Enter password for private key:
Pushing signature to: ttl.sh/my-artifact-f42c22e0
As usual, make sure to reference any images you sign by their digest to make sure you don’t sign the wrong thing!
WASM
Web Assembly Modules can also be stored in an OCI registry, using this specification.
Cosign can upload these using the cosign wasm upload
command:
$ cosign upload wasm -f hello.wasm us.gcr.io/dlorenc-vmtest2/wasm
$ cosign sign --key cosign.key us.gcr.io/dlorenc-vmtest2/wasm@sha256:9e7a511fb3130ee4641baf1adc0400bed674d4afc3f1b81bb581c3c8f613f812
Enter password for private key:
tlog entry created with index: 5198
Pushing signature to: us.gcr.io/dlorenc-vmtest2/wasm:sha256-9e7a511fb3130ee4641baf1adc0400bed674d4afc3f1b81bb581c3c8f613f812.sig
eBPF
eBPF modules can also be stored in an OCI registry, using this specification.
The image below was built using the bee
tool. More information can be found here
Cosign can then sign these images as they can any other OCI image.
$ bee build ./examples/tcpconnect/tcpconnect.c localhost:5000/tcpconnect:test
$ bee push localhost:5000/tcpconnect:test
$ cosign sign --key cosign.key localhost:5000/tcpconnect@sha256:7a91c50d922925f152fec96ed1d84b7bc6b2079c169d68826f6cf307f22d40e6
Enter password for private key:
Pushing signature to: localhost:5000/tcpconnect
$ cosign verify --key cosign.pub localhost:5000/tcpconnect:test
Verification for localhost:5000/tcpconnect:test --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key
[{"critical":{"identity":{"docker-reference":"localhost:5000/tcpconnect"},"image":{"docker-manifest-digest":"sha256:7a91c50d922925f152fec96ed1d84b7bc6b2079c169d68826f6cf307f22d40e6"},"type":"cosign container image signature"},"optional":null}]
Hardware-based Tokens
See the Hardware Tokens documentation for information on how to use cosign
with hardware.
Caveats
Intentionally Missing Features
cosign
only generates ECDSA-P256 keys and uses SHA256 hashes, for both ephemeral keyless signing and managed key signing.
Keys are stored in PEM-encoded PKCS8 format.
However, you can use cosign
to store and retrieve signatures in any format, from any algorithm.
Things That Should Probably Change
Registry Details
cosign
signatures are stored as separate objects in the OCI registry, with only a weak
reference back to the object they “sign”.
This means this relationship is opaque to the registry, and signatures will not be deleted
or garbage-collected when the image is deleted.
Similarly, they can easily be copied from one environment to another, but this is not
automatic.
Multiple signatures are stored in a list which is unfortunately a race condition today. To add a signature, clients orchestrate a “read-append-write” operation, so the last write will win in the case of contention.
Specifying Registry
cosign
will default to storing signatures in the same repo as the image it is signing.
To specify a different repo for signatures, you can set the COSIGN_REPOSITORY
environment variable.
This will replace the repo in the provided image like this:
$ export COSIGN_REPOSITORY=gcr.io/my-new-repo
$ cosign sign --key cosign.key $IMAGE_URI_DIGEST
So the signature for gcr.io/dlorenc-vmtest2/demo
will be stored in gcr.io/my-new-repo/demo:sha256-DIGEST.sig
.
Note: different registries might expect different formats for the “repository.”
- To use GCR, a registry name
like
gcr.io/$REPO
is sufficient, as in the example above. - To use Artifact Registry,
specify a full image name like
$LOCATION-docker.pkg.dev/$PROJECT/$REPO/$STORAGE_IMAGE
, not just a repository. For example,
$ export COSIGN_REPOSITORY=us-docker.pkg.dev/my-new-repo/demo
$ cosign sign --key cosign.key $IMAGE_URI_DIGEST
where the sha256-DIGEST
will match the digest for
gcr.io/dlorenc-vmtest2/demo
. Specifying just a repo like
$LOCATION-docker.pkg.dev/$PROJECT/$REPO
will not work in Artifact Registry.
Signature Specification
cosign
is inspired by tools like minisign and
signify.
Generated private keys are stored in PEM format. The keys encrypted under a password using scrypt as a KDF and nacl/secretbox for encryption.
They have a PEM header of ENCRYPTED SIGSTORE PRIVATE KEY
:
-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY-----
...
-----END ENCRYPTED SIGSTORE PRIVATE KEY-----
Public keys are stored on disk in PEM-encoded standard PKIX format with a header of PUBLIC KEY
.
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELigCnlLNKgOglRTx1D7JhI7eRw99
QolE9Jo4QUxnbMy5nUuBL+UZF9qqfm/Dg1BNeHRThHzWh2ki9vAEgWEDOw==
-----END PUBLIC KEY-----
Storage Specification
cosign
stores signatures in an OCI registry, and uses a naming convention (tag based
on the sha256 of what we’re signing) for locating the signature index.
reg.example.com/ubuntu@sha256:703218c0465075f4425e58fac086e09e1de5c340b12976ab9eb8ad26615c3715
has signatures located at reg.example.com/ubuntu:sha256-703218c0465075f4425e58fac086e09e1de5c340b12976ab9eb8ad26615c3715.sig
Roughly (ignoring ports in the hostname): s/:/-/g
and s/@/:/g
to find the signature index.
See Race conditions for some caveats around this strategy.
Alternative implementations could use transparency logs, local filesystem, a separate repository registry, an explicit reference to a signature index, a new registry API, grafeas, etc.
OCI Artifacts
Push an artifact to a registry using oras (in this case, cosign
itself!):
$ oras push us-central1-docker.pkg.dev/dlorenc-vmtest2/test/artifact ./cosign
Uploading f53604826795 cosign
Pushed us-central1-docker.pkg.dev/dlorenc-vmtest2/test/artifact
Digest: sha256:551e6cce7ed2e5c914998f931b277bc879e675b74843e6f29bc17f3b5f692bef
Now sign it! Using cosign
of course:
$ cosign sign --key cosign.key us-central1-docker.pkg.dev/dlorenc-vmtest2/test/artifact@sha256:551e6cce7ed2e5c914998f931b277bc879e675b74843e6f29bc17f3b5f692bef
Enter password for private key:
Pushing signature to: us-central1-docker.pkg.dev/dlorenc-vmtest2/test/artifact:sha256-551e6cce7ed2e5c914998f931b277bc879e675b74843e6f29bc17f3b5f692bef.sig
Finally, verify cosign
with cosign
again:
$ cosign verify --key cosign.pub us-central1-docker.pkg.dev/dlorenc-vmtest2/test/artifact@sha256:551e6cce7ed2e5c914998f931b277bc879e675b74843e6f29bc17f3b5f692bef
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The claims were present in the transparency log
- The signatures were integrated into the transparency log when the certificate was valid
- The signatures were verified against the specified public key
- The code-signing certificate was verified using trusted certificate authority certificates
{"Critical":{"Identity":{"docker-reference":""},"Image":{"Docker-manifest-digest":"sha256:551e6cce7ed2e5c914998f931b277bc879e675b74843e6f29bc17f3b5f692bef"},"Type":"cosign container image signature"},"Optional":null}
FAQ
Why not use Notary v2
It’s hard to answer this briefly. This post contains some comparisons:
If you find other comparison posts, please send a PR here and we’ll link them all.
Why not use containers/image signing
containers/image
signing is close to cosign
, and we reuse payload formats.
cosign
differs in that it signs with ECDSA-P256 keys instead of PGP, and stores
signatures in the registry.
Why not use TUF?
I believe this tool is complementary to TUF, and they can be used together. I haven’t tried yet, but think we can also reuse a registry for TUF storage.
Future Ideas
Registry API Changes
The naming convention and read-modify-write update patterns we use to store things in a registry are a bit, well, “hacky”. I think they’re the best (only) real option available today, but if the registry API changes we can improve these.
Other Types
cosign
can sign anything in a registry.
These examples show signing a single image, but you could also sign a multi-platform Index
,
or any other type of artifact.
This includes Helm Charts, Tekton Pipelines, and anything else currently using OCI registries
for distribution.
This also means new artifact types can be uploaded to a registry and signed. One interesting type to store and sign would be TUF repositories. I haven’t tried yet, but I’m fairly certain TUF could be implemented on top of this.
Base Image/Layer Signing
Again, cosign
can sign anything in a registry.
You could use cosign
to sign an image that is intended to be used as a base image,
and include that provenance metadata in resulting derived images.
This could be used to enforce that an image was built from an authorized base image.
Rough Idea:
- OCI manifests have an ordered list of
layer
Descriptors
, which can contain annotations. See here for the specification. - A base image is an ordered list of layers to which other layers are appended, as well as an
initial configuration object that is mutated.
- A derived image is free to completely delete/destroy/recreate the config from its base image, so signing the config would provided limited value.
- We can sign the full set of ordered base layers, and attach that signature as an annotation to the last layer in the resulting child image.
This example manifest manifest represents an image that has been built from a base image with two layers. One additional layer is added, forming the final image.
{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"size": 7023,
"digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"size": 32654,
"digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0"
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"size": 16724,
"digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b",
"annotations": {
"dev.cosign.signature.baseimage": "Ejy6ipGJjUzMDoQFePWixqPBYF0iSnIvpMWps3mlcYNSEcRRZelL7GzimKXaMjxfhy5bshNGvDT5QoUJ0tqUAg=="
}
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"size": 73109,
"digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
}
],
}
Note that this could be applied recursively, for multiple intermediate base images.
Counter-Signing
Cosign signatures (and their protected payloads) are stored as artifacts in a registry. These signature objects can also be signed, resulting in a new, “counter-signature” artifact. This “counter-signature” protects the signature (or set of signatures) and the referenced artifact, which allows it to act as an attestation to the signature(s) themselves.
Before we sign the signature artifact, we first give it a memorable name so we can find it later.
$ cosign sign --key cosign.key -a sig=original $IMAGE_URI_DIGEST
Enter password for private key:
Pushing signature to: dlorenc/demo:sha256-97fc222cee7991b5b061d4d4afdb5f3428fcb0c9054e1690313786befa1e4e36.sig
$ cosign verify --key cosign.pub dlorenc/demo | jq .
{
"Critical": {
"Identity": {
"docker-reference": ""
},
"Image": {
"Docker-manifest-digest": "97fc222cee7991b5b061d4d4afdb5f3428fcb0c9054e1690313786befa1e4e36"
},
"Type": "cosign container image signature"
},
"Optional": {
"sig": "original"
}
}
Now give that signature a memorable name, then sign that:
$ crane tag $(cosign triangulate $IMAGE_URI) mysignature
2021/02/15 20:22:55 dlorenc/demo:mysignature: digest: sha256:71f70e5d29bde87f988740665257c35b1c6f52dafa20fab4ba16b3b1f4c6ba0e size: 556
$ cosign sign --key cosign.key -a sig=counter dlorenc/demo:mysignature
Enter password for private key:
Pushing signature to: dlorenc/demo:sha256-71f70e5d29bde87f988740665257c35b1c6f52dafa20fab4ba16b3b1f4c6ba0e.sig
$ cosign verify --key cosign.pub dlorenc/demo:mysignature
{"Critical":{"Identity":{"docker-reference":""},"Image":{"Docker-manifest-digest":"71f70e5d29bde87f988740665257c35b1c6f52dafa20fab4ba16b3b1f4c6ba0e"},"Type":"cosign container image signature"},"Optional":{"sig":"counter"}}
Finally, check the original signature:
$ crane manifest dlorenc/demo@sha256:71f70e5d29bde87f988740665257c35b1c6f52dafa20fab4ba16b3b1f4c6ba0e
{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"size": 233,
"digest": "sha256:3b25a088710d03f39be26629d22eb68cd277a01673b9cb461c4c24fbf8c81c89"
},
"layers": [
{
"mediaType": "application/vnd.oci.descriptor.v1+json",
"size": 217,
"digest": "sha256:0e79a356609f038089088ec46fd95f4649d04de989487220b1a0adbcc63fadae",
"annotations": {
"dev.sigstore.cosign/signature": "5uNZKEP9rm8zxAL0VVX7McMmyArzLqtxMTNPjPO2ns+5GJpBeXg+i9ILU+WjmGAKBCqiexTxzLC1/nkOzD4cDA=="
}
}
]
}
Release Cadence
We cut releases as needed. Patch releases are cut to fix small bugs. Minor releases are cut periodically when there are multiple bugs fixed or features added. Major releases will be released when there are breaking features.
Security
Should you discover any security issues, please refer to sigstore’s security process
PEM files in GitHub Release Assets
The GitHub release assets for cosign contain a PEM file produced by GoReleaser while signing the cosign blob that is used to verify the integrity of the release binaries. This file is not used by cosign itself, but is provided for users who wish to verify the integrity of the release binaries.
By default, cosign output these PEM files in base64 encoded format, this approach might be good for air-gapped environments where the PEM file is stored in a file system. So, you should decode these PEM files before using them to verify the blobs.