Signing and verifying packages¶
A package is signed as a whole when pushed or saved, and verified before
it's pulled or loaded. One signature covers the SBOM and every component,
stored beside the package as an OCI referrer. Vulnerability reports and VEX
documents attached with --vex are signed and verified separately; one
that fails verification is skipped with a warning.
bomify-plugin-sigstore is the first-party signing plugin (bomify
plugin install sigstore). See
the architecture docs
for how it works and
plugins/README.md
for its options.
1. Create a key pair¶
Any PEM key pair works (ECDSA P-256 shown; RSA and Ed25519 work too):
Keep signing.key private; give signing.pub to whoever verifies.
2. Sign while pushing¶
For an encrypted key, set its password without echoing it:
printf 'Key password: '; read -rs SIGSTORE_PASSWORD; echo; export SIGSTORE_PASSWORD
bomify push registry.example.com/team/myapp:1.0 --sign sigstore --sign-option key=signing.key
unset SIGSTORE_PASSWORD
The signature is attached before the tag moves, so the tag never points at an unsigned package. Signing again with another key adds a signature.
3. Verify while pulling¶
The signature is checked before anything is downloaded; if none verifies, nothing is written.
4. Require verification with a trust rule¶
Now every pull and load under that prefix requires a signature that
key verifies. The most specific --match wins; --verify overrides the
rules and --insecure-skip-verify bypasses them with a warning. Add
--require-provenance to require build provenance too (see
step 7).
To stop the rule depending on where the key file lives, store the key under a name. Re-adding the name rotates it for every rule using it. Only public keys and certificates are accepted.
bomify trust key add team signing.pub
bomify trust create sigstore --match registry.example.com/team --key-option key=team
bomify trust key add team signing-2027.pub # rotate
5. Offline tarballs¶
save and load take the same flags, and the signature travels inside
the tarball, so verification needs no network:
bomify save myapp:1.0 --output myapp.tar --sign sigstore --sign-option key=signing.key
bomify load --input myapp.tar --verify sigstore --verify-option key=signing.pub
6. Keyless signing in CI¶
With no key, the plugin exchanges an OIDC token from SIGSTORE_ID_TOKEN
for a short-lived certificate naming the workflow, and logs the
signature publicly. There are no secrets to manage; bomify's own plugins
are signed this way. In GitHub Actions, fetch the token right before
pushing, since it lasts minutes:
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write
packages: write
steps:
# ... install bomify and bomify-plugin-sigstore, log in ...
- id: oidc
uses: actions/github-script@v7
with:
script: core.setOutput('token', await core.getIDToken('sigstore'))
- run: bomify push ghcr.io/my-org/myapp:1.0 --sign sigstore
env:
SIGSTORE_ID_TOKEN: ${{ steps.oidc.outputs.token }}
Verify by naming the workflow and issuer instead of a key (this needs network access to Sigstore). The same options work in a trust rule.
bomify pull ghcr.io/my-org/myapp:1.0 --verify sigstore \
--verify-option certificate-identity=https://github.com/my-org/myapp/.github/workflows/publish.yml@refs/heads/main \
--verify-option certificate-oidc-issuer=https://token.actions.githubusercontent.com
7. Attach and require build provenance¶
Record SLSA provenance when you build, and it's attached to the package
as an in-toto attestation on push or save, signed with --sign:
bomify build sbom.json --tag ghcr.io/my-org/myapp:1.0 --provenance
bomify push ghcr.io/my-org/myapp:1.0 --sign sigstore
With bomify-plugin-sigstore, it's a Sigstore bundle attestation
stored as an OCI referrer, the format cosign's bundle support and gh
attestation verify read.
To require it when pulling or loading, add --verify-provenance. The
provenance must be signed by a signer the same plugin and options trust,
be about this very package and SBOM, and come from a bomify build:
bomify pull ghcr.io/my-org/myapp:1.0 --verify sigstore --verify-option certificate-identity=https://github.com/my-org/myapp/.github/workflows/publish.yml@refs/heads/main --verify-option certificate-oidc-issuer=https://token.actions.githubusercontent.com --verify-provenance
See Build provenance.
8. Require several signers¶
To require, say, both a release team's and a security team's signature, give a trust rule a named signer for each:
bomify trust create sigstore --match registry.example.com/prod --signer release --key-option key=release
bomify trust create sigstore --match registry.example.com/prod --signer security --key-option key=security
A matching package now needs a signature each of them verifies. Name
every signer: if the rule was first created without --signer, that
unnamed signer (- in bomify trust list) stays and is required too,
until bomify trust remove --match registry.example.com/prod --signer ''
drops it. To
accept any k of them instead, add --require k, e.g. two of three
maintainers. bomify trust list shows one row per signer, and bomify
trust remove --signer <name> drops one.
Sign as every signer in the same push or save, so the package's reports and VEX carry each signature too. Store each signer under a name first:
bomify signer create release sigstore --option key=release.key
bomify signer create security sigstore --option key=security.key
bomify push registry.example.com/prod/myapp:1.0 --signer release --signer security
A rule requiring provenance accepts it from any one of its signers.
Signing at different times¶
Co-signers rarely sign in the same place: CI signs on release, and the
security team later, after review. bomify sign adds a signature to a
package that's already published, fetching only its manifests, so each
environment needs only its own key:
# CI
bomify push registry.example.com/prod/myapp:1.0 --signer release
# The security team, later
bomify sign registry.example.com/prod/myapp:1.0 --signer security
It signs the package's vulnerability reports and VEX too. For a tarball,
bomify sign --input myapp.tar --signer security signs every package in
it, in place, or writes a copy with --output.
A signature vouches for the package's digest, whatever the tag points
at when you sign. Verify the package as the earlier signer before adding
yours, e.g. pull it with --verify, or sign by digest
(registry.example.com/prod/myapp@sha256:…) once you have.