Signing and verification¶
A package is signed as a whole: the signature covers its OCI manifest,
which pins the SBOM and every layer by digest, and pull verifies each
blob against those digests. The report referrer and VEX referrers are
signed and verified the same way, separately. Signatures never go in the SBOM, whose hash is
the build's identity.
internal/signature implements
transfer.Options.Sign and Verify, so save/load get both for free.
- Sign (
push/save --sign <kind>,--signer <name>): after packing and before tagging, bomify writes the payload (the manifest'smediaType,digest, andsizeas JSON), runs the plugin'ssignature sign, and pushes the envelope as an OCI 1.1 referrer of the plugin's artifact type.--signer(repeatable) names a stored signer (conf/signers.json, frombomify signer create): a plugin and its options.signature.NewSignerssigns the manifest and every other referrer the push attaches with each in turn, so a co-signed package's reports and VEX carry every signature too. A failed sign never leaves a tag on an unsigned package. The payload omitsartifactType, which registries don't return when resolving a tag, so it's identical at pull time. On registries without the Referrers API (ghcr.io), bomify setsSkipReferrersGC, so the superseded tag-schema index is left untagged rather than deleted, which those registries refuse. - Sign later (
bomify sign <reference>, or--input <tarball>):internal/oci/signresolves the reference, checks its manifest is a bomify package's, and signs it and each of its vulnerability report and VEX referrers with the same signer push uses, fetching only manifests. Each signature is one more referrer, so co-signers can sign at different times, in different environments. A tarball is extracted, every tag signed, and re-archived. Provenance isn't attested again: one trusted attestation is enough. - Verify (
pull/load): before fetching anything,signature.Policypicks the signers the reference must satisfy:--verify(one, with only its own--verify-options), else the most specificconf/trust.jsonrule's, else none.--insecure-skip-verifyoverrides a rule, with a warning. Key options are resolved tooption=<stored copy path>as rules are read (signature.ReadResolved), which also refuses a rule that doesn't validate, such as one with no signers. For each signer (signature.VerifySigners), bomify lists the manifest's referrers, keeps those whose artifact type its plugin'ssignature supported-typeslists, and askssignature verify, with that signer's options, about each until one passes. The pull goes ahead once the rule'srequiresigners have passed (every signer when it's unset), and otherwise fails with nothing written, naming each signer that didn't. Everything afterwards is fetched by that verified descriptor. - Verify provenance (
--verify-provenance, or a rule's--require-provenance): the same signers then verify the package's build provenance attestation, one of them sufficing; see Build provenance.
Since each signer is checked with its own options, one envelope counts for two signers only if both trust whoever made it.
Source: docs/diagrams/signing.mmd
Source: docs/diagrams/verification.mmd
Plugins never talk to a registry; bomify moves every envelope itself, which is why one plugin works for registries and tarballs alike.
What verification guarantees, and what it doesn't, is in Trust model.
Rule files¶
Both files are JSON arrays, covered by the data directory version and read strictly: an unknown field fails the command rather than being ignored, so a misspelling can't drop a requirement.
conf/trust.json¶
One rule per match, written by bomify trust create; the most
specific match applies.
| Field | Type | Meaning |
|---|---|---|
match |
string | /-separated prefix of a package's repository, matched at segment boundaries, e.g. registry.example.com/team. Omitted or empty matches every package. |
signers |
array | At least one signer, below. |
require |
integer | How many of signers must verify. Omitted or 0 means all of them. |
provenance |
boolean | Also require build provenance attested by one of signers (see Build provenance). |
Each signer:
| Field | Type | Meaning |
|---|---|---|
name |
string | Tells a rule's signers apart. At most one per rule may omit it, as trust create does without --signer. |
plugin |
string | The signing plugin, bomify-plugin-<plugin>, that verifies this signer's signature. |
options |
array of strings | key=value options passed unparsed to the plugin's signature verify. |
keyOptions |
object | Plugin option name to the name of a key in keys/, passed as <option>=<path of the stored copy>. An option can't also appear in options. A key can't be removed while a rule names it. |
conf/signers.json¶
One entry per name, written by bomify signer create, for push,
save, and sign --signer.
| Field | Type | Meaning |
|---|---|---|
name |
string | The name --signer takes. |
plugin |
string | The signing plugin, bomify-plugin-<plugin>. |
options |
array of strings | key=value options passed unparsed to its signature sign and signature attest, such as a private key's path. Never key material. |