How to Sign an SBOM with Cosign

Signing software bills of materials with Cosign

An earlier version of this material was published in the Cosign chapter of the Linux Foundation Sigstore course.

Cosign, developed as part of the Sigstore project, is a command line utility for signing, verifying, storing, and retrieving software artifacts through interface with an OCI (Open Container Initiative) registry. Cosign can be used to sign attestations, or a verifiable assertion or statement about a software artifact.

What is an Attestation?

An attestation is a cryptographically verifiable statement about a software artifact. Attestations include a subject, a software artifact or artifacts to which the attestation applies, and a predicate, a claim or proposition about the subject. For example, an attestation might assert that a specific container image was built on a specific date using a specific configuration, and that assertion could be cryptographically verified as issuing from a specific organization or entity.

Attestations are commonly used for associating useful metadata such as SBOMs or SLSA provenance with a specific artifact such as an OCI container image. In the case of an SBOM (Software Bill of Materials), the attestation proposes that the software artifact contains a specific set of packages. In the case of SLSA provenance, the attestation claims that a specific build environment was used to create the artifact.

Attestations verify that a specific trusted entity issued specific metadata about a generated software artifact, and can also indicate that software artifacts and associated documents have not been tampered with. The SLSA project page hosts further detailed information on the attestation model.

One common use case for attestations is associating a software artifact, such as an OCI container image, with a software bill of materials (SBOM), an inventory of the components that make up a given software artifact. Increasingly, SBOMs are considered an essential component in maintaining a secure software supply chain.

What is a Software Bill of Materials (SBOM?

A Software Bill of Materials (SBOM) is a formally structured list of libraries, modules, licenses, and version information that make up any given piece of software. An SBOM provides a comprehensive view of the components contained in a software artifact, allowing for systematic analysis, investigation, and automation. SBOMs are most useful in identifying known vulnerabilities, surveying licenses for compliance, and making decisions based on project status or supplier trust.

Read more about SBOMs on Chainguard Academy.

Including an SBOM with software you ship can help others trust the provenance and contents of the software. Including your SBOM as an attestation verifies that the SBOM was generated by the same trusted organization that created the software artifact, and that neither the artifact nor its associated metadata and documents have been tampered with.

In the following, we’ll generate an SBOM and associate it with a specific OCI container using an attestation generated by Cosign.

Creating a Demonstration Image

Since we’ll be attaching an SBOM to a container image, we’ll first need to create an example image. We’ll base this image on Chainguard’s wolfi-base, and add a single additional package, the venerable cowsay utility that prints a message along with some ASCII art. We then set the entrypoint so that, when the image is run, a message will be displayed.

Create a new folder for our Dockerfile build and change your working directory to that folder:

mkdir -p ~/example-image && cd ~/example-image

Create a new Dockerfile in the folder using Nano or your preferred text editor:

nano Dockerfile

Paste the following commands into the file:

FROM cgr.dev/chainguard/wolfi-base
RUN apk add cowsay
ENTRYPOINT ["cowsay", "-f", "tux", "I love FOSS!"]

Save the file and close Nano by pressing CTRL + X, y, and ENTER in succession.

Since we’ll be pushing to a repository on your Docker Hub account, let’s set a variable to your Docker Hub username that we can use in further commands.

DH_USERNAME=<your-username>

You can find your username on your Docker Hub account page. If you’re logged in on the command line, you can also run the following command to find your username:

docker info | sed '/Username:/!d;s/.* //' 2> /dev/null

Finally, let’s build and tag the image with:

docker build . -t $DH_USERNAME/example-image

You can test out the image by running it:

docker run $DH_USERNAME/example-image

This should display the “I love FOSS!” message along with some ASCII art.

Generating an SBOM with Syft

Syft is a tool that allows us to create SBOMs. If you don’t already have Syft, use the following instructions to install this utility.

How to Install Syft

Syft is a CLI tool for generating a Software Bill of Materials (SBOM). It is created and maintained by Anchore.

The recommended way to install Syft is by running a provided installation script. We recommend inspecting this script before running it on your local machine.

The following command will download the installation script and install Syft to your /usr/local/bin folder. Depending on your machine’s configuration, you may need to add sudo at the beginning of the second line of the below command or otherwise use elevated permissions to complete the installation.

curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | \
 sh -s -- -b /usr/local/bin

Alternately, your can install Syft with Homebrew:

brew install syft

You can also use Syft directly by pulling and running the official image from Docker Hub. The following command will pull the Syft image and use it to scan the offical Python image from Dockerhub:

docker run -it anchore/syft:latest python

For more on installing Syft, review the project’s installation instructions on GitHub.

Once you have Syft installed, you can generate an SBOM with the syft command, passing in the target image as an argument. For example, the following will use Syft to generate a list of packages present in the official python image on Docker Hub:

syft python

Now let’s generate an SBOM for example-image, the image we built in the previous section. Run the following Syft command to generate an SBOM in SPDX format:

syft $DH_USERNAME/example-image:latest -o spdx-json > example-image.spdx.json
What is SPDX?

System Package Data Exchange (SPDX) is a file format for representing Software Bills of Materials (SBOMs), or itemized metadata on packages contained in a software artifact.

The SPDX specification is based on an open standard and is maintained by the SPDX Project, part of the Linux Foundation.

If you take a look at the contents of the file, you will find our installed package, cowsay, represented alongside the other packages already in our base image, wolfi-base. You can also check that cowsay is detected by Syft with the following:

syft --quiet $DH_USERNAME/example-image:latest | grep cowsay
cowsay                  3.04-r0       apk

In the next section, we’ll associate this SBOM with our container image using Cosign.

Attesting to the SBOM

Now that we have our SBOM file, let’s associate it with our image using an attestation. In this attestation, the image will be our subject, and the generated SBOM will serve as our predicate (an assertion about the subject). In this case, we will be attesting that the image contains the packages listed in our SBOM file.

Before proceeding, let’s push our image to Docker Hub, since the following commands will refer to the image on the OCI repository.

docker push $DH_USERNAME/example-image

Our example-image still has attestations derived from our base image, since all Chainguard Images come with SBOM and SLSA provenance attestations. Let’s remove these attestations with the cosign clean command:

cosign clean $DH_USERNAME/example-image

We’re now ready to add our attestation. Sigstore recommends referring to the image by digest and not by tag to avoid attesting to the wrong image, and attesting by tag will be removed in a future version of Cosign. The following command will set a variable to the digest of our newly pushed image:

DIGEST=$(docker inspect $DH_USERNAME/example-image |jq -c 'first'| jq .RepoDigests | jq -c 'first' | tr -d '"')

Alternatively, you can find the digest manually by visiting your repository on Docker Hub.

We can now attest using our image as the subject and our generated SBOM as the predicate:

cosign attest --type spdxjson \
 --predicate example-image.spdx.json \
 $DIGEST

You will receive the following prompt:

Generating ephemeral keys...
Retrieving signed certificate...

	The sigstore service, hosted by sigstore a Series of LF Projects, LLC, is provided pursuant to the Hosted Project Tools Terms of Use, available at https://lfprojects.org/policies/hosted-project-tools-terms-of-use/.
	Note that if your submission includes personal data associated with this signed artifact, it will be part of an immutable record.
	This may include the email address associated with the account with which you authenticate your contractual Agreement.
	This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later, and is subject to the Immutable Record notice at https://lfprojects.org/policies/hosted-project-tools-immutable-records/.

By typing 'y', you attest that (1) you are not submitting the personal data of any other person; and (2) you understand and agree to the statement and the Agreement terms at the URLs listed above.
Are you sure you would like to continue? [y/N] 

Note the warnings — a record of the attestation will be recorded to an immutable log maintained by the Sigstore project. When you’re ready, press y to agree and attest.

Retrieving the Signed SBOM

Our example-image in our Docker Hub repository should now bear an attached SBOM as an attestation. Let’s confirm that this is the case by accessing the image’s SBOM from the repository using the cosign download attestation command:

cosign download attestation \
 $DIGEST | \
 jq -r .payload | base64 -d \
 | jq .predicate

This command will download the in-toto attestation envelope using the cosign download attestation command. This envelope contains information on the attestation’s subject (the image) and predicate (a statement about the subject, in this case the generated SBOM). We decode this envelope from base64, an encoding used to reduce information loss while transferring data, then extract the predicate. The result should be our SBOM in SPDX-JSON:

{
  "SPDXID": "SPDXRef-DOCUMENT",
  "creationInfo": {
    "created": "2024-10-09T19:16:20Z",
    "creators": [
      "Organization: Anchore, Inc",
      "Tool: syft-1.14.0"
    ],
    "licenseListVersion": "3.25"
  },
  "dataLicense": "CC0-1.0",
  "documentNamespace": "https://anchore.com/syft/image/$DH_USERNAME/example-image-15096a2c-e277-40f6-bc6e-93c5a4aff24e",
  "files": [
    {
      "SPDXID": "SPDXRef-File-bin-busybox-b93a85ec4cd132fa",
      "checksums": [
[...]

If we choose, we can further parse this output so we can examine the cowsay package we added in our Dockerfile:

cosign download attestation \
 $DIGEST | \
 jq -r .payload | base64 -d |\
 jq .predicate | jq .packages | \
 jq '.[] | select(.name == "cowsay")'
{
  "SPDXID": "SPDXRef-Package-apk-cowsay-f6f3edc220b6705a",
  "copyrightText": "NOASSERTION",
  "description": "Configurable talking cow (and a few other creatures)",
  "downloadLocation": "NONE",
  "externalRefs": [
    {
      "referenceCategory": "SECURITY",
      "referenceLocator": "cpe:2.3:a:cowsay:cowsay:3.04-r0:*:*:*:*:*:*:*",
      "referenceType": "cpe23Type"
    },
    {
      "referenceCategory": "PACKAGE-MANAGER",
      "referenceLocator": "pkg:apk/wolfi/cowsay@3.04-r0?arch=x86_64&distro=wolfi-20230201",
      "referenceType": "purl"
    }
  ],
  "filesAnalyzed": true,
  "licenseConcluded": "NOASSERTION",
  "licenseDeclared": "GPL-2.0-or-later",
  "name": "cowsay",
  "packageVerificationCode": {
    "packageVerificationCodeValue": "4f82bdba8e1217f8af0abee5cadc9c2387bf4720"
  },
  "sourceInfo": "acquired package info from APK DB: /lib/apk/db/installed",
  "supplier": "NOASSERTION",
  "versionInfo": "3.04-r0"
}

At this point, we’ve attested to the contents of our image with an SBOM and confirmed that the attestation is attached to the image in our Docker Hub repository. In the next section, we’ll learn how to verify the identity of the entity issuing an attestation.

Verifying an Attestation

Cosign can also be used to verify the identity of the person or entity issuing an attestation. The following assumes you used GitHub to authenticate when attesting in the previous step.

To verify that an attestation was issued by a specific entity, we use the cosign verify-attestation command, specifying the email address of the issuer:

cosign verify-attestation \
 --certificate-oidc-issuer=https://github.com/login/oauth \
 --type https://spdx.dev/Document \
 --certificate-identity=emailaddress@emailprovider.com \
 $DIGEST

If the identity is successfully verified, an initial message similar to the following is printed to stderr:

Verification for $DH_USERNAME/example-image@sha256:545a731e803b917daf44e292b03b427427f8090c4e6c4a704e4c18d56c38539f --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - The code-signing certificate was verified using trusted certificate authority certificates
Certificate subject: <you@domain.com>
Certificate issuer URL: https://github.com/login/oauth

The remainder of the message consists of an in-toto attestation envelope encoded in base64. If you wish, you can retrieve the predicate from this response:

cosign verify-attestation \
 --certificate-oidc-issuer=https://github.com/login/oauth \
 --type https://spdx.dev/Document \
 --certificate-identity=emailaddress@emailprovider.com \
 $DIGEST | \
 jq -r .payload | \
 base64 -d | jq .predicate

At this point, you have successfully created an image, generated an SBOM for that image, associated the SBOM with the image as an attestation, and verified the identity of the issuer of the attestation. Using this workflow, you can attest to the contents of images you create, allowing others to understand the provenance of software you ship and enabling others to verify that software artifacts and associated documents originate from you.

Last updated: 2024-10-10 15:12