Documentation
¶
Overview ¶
Package verifier implements `aicr evidence verify`: offline verification of a recipe-evidence v1 bundle produced by `aicr validate --emit-attestation`. Five steps run:
- Materialize — resolve the input (directory / pointer file / OCI reference) to a bundle root on disk. Pointer and OCI forms pull the artifact via ORAS, then discover the Sigstore Bundle attached as an OCI Referrer and stage it as attestation.intoto.jsonl so the signature step finds it on disk the same way it does for directory input.
- Signature verify — when attestation.intoto.jsonl is present, sigstore-go verifies the DSSE-wrapped in-toto Statement against the Sigstore trusted root (Fulcio cert chain, optional Rekor entry). The cryptographically anchored predicate body is extracted from the verified payload.
- Predicate parse — use the verified predicate when the signature step produced one; otherwise fall back to the unsigned statement.intoto.json (self-consistency only).
- Manifest hash check — sha256(manifest.json) must match predicate.Manifest.Digest, and every file the manifest names must match its recorded sha256. Together these transitively bind every bundled file to the (now signature-anchored) predicate.
- Render — Markdown / JSON; surfaces signer identity, fingerprint, phase counts, and BOM info.
The trust chain when a signature is present:
Sigstore trusted root → Fulcio cert (Rekor-logged)
→ DSSE-signed Statement
→ predicate.Manifest.Digest
→ manifest.json
→ every bundled file's sha256
Tampering anywhere below the signature breaks the chain. The OCI input form adds a freshness check: the signed Statement's subject digest is locked to the pulled artifact's OCI manifest digest, so a substituted artifact paired with a stale signature fails too.
See docs/design/007-recipe-evidence.md for the full trust model.
Index ¶
- Constants
- Variables
- func CrossCheckPointerSigner(claimed *attestation.PointerSigner, actual *SignerClaims) error
- func LoadAndValidatePointer(path string) (*attestation.Pointer, error)
- func RenderJSON(r *VerifyResult) ([]byte, error)
- func RenderMarkdown(r *VerifyResult) string
- type InputForm
- type KV
- type MaterializedBundle
- type SignatureResult
- type SignerClaims
- type StepResult
- type StepStatus
- type VerifyOptions
- type VerifyResult
Constants ¶
const ( ExitValidPassed = 0 ExitValidPhaseFailures = 1 ExitInvalid = 2 )
Exit codes returned by Verify in VerifyResult.Exit. The CLI maps these to OS exit codes via pkg/errors error codes.
Variables ¶
var ErrUnsignedBundle = errors.New(errors.ErrCodeNotFound, "no signature attached (unsigned bundle)")
ErrUnsignedBundle is returned by VerifySignature when no Sigstore Bundle file is present in the bundle directory. Callers translate this to a Skipped step row.
Functions ¶
func CrossCheckPointerSigner ¶
func CrossCheckPointerSigner(claimed *attestation.PointerSigner, actual *SignerClaims) error
CrossCheckPointerSigner compares the verified signer claims against what the pointer file claimed. Returns nil when the pointer makes no signer claim, when there's no actual signer to compare against, or when every claimed field matches. A mismatch produces an error that names the specific field and both sides — the verifier surfaces it so a malicious pointer that names a different signer than the actual bundle fails loudly.
func LoadAndValidatePointer ¶
func LoadAndValidatePointer(path string) (*attestation.Pointer, error)
LoadAndValidatePointer reads and validates the pointer file at path. V1 enforces schema 1.0.x with exactly one attestation entry — schema 2.0 (multi-instance pointers) is reserved.
func RenderJSON ¶
func RenderJSON(r *VerifyResult) ([]byte, error)
RenderJSON serializes the VerifyResult deterministically.
func RenderMarkdown ¶
func RenderMarkdown(r *VerifyResult) string
RenderMarkdown produces the PR-comment-shaped summary. Signed predicate fields (fingerprint, phase counts, BOM info) are surfaced directly — when the signature step passed, the predicate body is cryptographically anchored to the Fulcio cert claims shown on the Signer line.
Types ¶
type InputForm ¶
type InputForm string
InputForm enumerates supported bundle transport shapes.
func DetectInputForm ¶
DetectInputForm classifies a user-supplied input string into one of the three supported transport forms. Detection precedence:
- URL prefix: oci:// → OCI; http(s):// is rejected.
- Filesystem: directory → dir; .yaml/.yml file → pointer.
- Bare OCI ref shape ("registry/repo[:tag][@digest]") → OCI.
type KV ¶
KV is a flat key-value pair for StepResult.SubRows.
func CheckInventory ¶
func CheckInventory(ctx context.Context, mat *MaterializedBundle, expectedManifestDigest string) ([]KV, error)
CheckInventory verifies the bundle's integrity chain:
- sha256(manifest.json) matches expectedManifestDigest (the predicate's Manifest.Digest field). This is what binds the unsigned manifest to the predicate — without it, a tampered bundle could rewrite manifest.json to match its own contents and pass file-by-file hash checks.
- Every file the manifest names exists, has the expected size, and hashes to the recorded sha256.
- No file in the bundle is unmanaged (i.e., not in the manifest).
expectedManifestDigest must be the "sha256:<hex>" form from pred.Manifest.Digest. An empty value is rejected — the verifier refuses to operate without a predicate-side digest to compare against.
ctx is honored between files (large bundles, hostile manifests with many entries) and during the bundle walk for stray-file detection.
Returns per-file mismatch rows and an error summarizing the failure; both nil on success.
type MaterializedBundle ¶
type MaterializedBundle struct {
// BundleDir is the local directory containing recipe.yaml,
// manifest.json, ctrf/*, etc. Always populated.
BundleDir string
// Reference and Digest are populated when the bundle came from an
// OCI source. Reference is the canonical registry/repo:tag string;
// Digest is the resolved OCI manifest digest ("sha256:...").
Reference string
Digest string
// contains filtered or unexported fields
}
MaterializedBundle is the verifier's view of a bundle on local disk.
func MaterializeBundle ¶
func MaterializeBundle( ctx context.Context, opts VerifyOptions, form InputForm, pointer *attestation.Pointer, ) (*MaterializedBundle, error)
MaterializeBundle dispatches on InputForm. Returns a directory the rest of the verifier reads from, plus optional OCI provenance.
func (*MaterializedBundle) Cleanup ¶
func (m *MaterializedBundle) Cleanup()
Cleanup releases any temporary directories the verifier created.
type SignatureResult ¶
type SignatureResult struct {
// Signer holds OIDC claims extracted from the verifying cert.
Signer *SignerClaims
// Predicate is the cryptographically anchored predicate body
// extracted from the verified DSSE payload. Callers should prefer
// this over the unsigned statement.intoto.json when present —
// THIS is the value the signer attested to.
Predicate *attestation.Predicate
}
SignatureResult is what a successful VerifySignature returns.
func VerifySignature ¶
func VerifySignature(ctx context.Context, mat *MaterializedBundle, opts VerifyOptions) (*SignatureResult, error)
VerifySignature performs sigstore-go verification of the bundle's in-toto Statement signature. Returns ErrUnsignedBundle when no attestation.intoto.jsonl is present.
For OCI inputs the subject digest in the signed Statement is locked to the actual pulled artifact digest — a mismatch means someone substituted the bundle and re-pointed at a different signature.
type SignerClaims ¶
type SignerClaims struct {
Identity string `json:"identity" yaml:"identity"`
Issuer string `json:"issuer" yaml:"issuer"`
RekorLogIndex *int64 `json:"rekorLogIndex,omitempty" yaml:"rekorLogIndex,omitempty"`
}
SignerClaims records the OIDC identity from the signing certificate. nil on unsigned bundles.
type StepResult ¶
type StepResult struct {
Step int `json:"step" yaml:"step"`
Name string `json:"name" yaml:"name"`
Status StepStatus `json:"status" yaml:"status"`
Detail string `json:"detail,omitempty" yaml:"detail,omitempty"`
SubRows []KV `json:"subRows,omitempty" yaml:"subRows,omitempty"`
}
StepResult is the recorded outcome of one verification step.
type StepStatus ¶
type StepStatus string
StepStatus is the per-step verdict.
const ( StepPassed StepStatus = "passed" StepFailed StepStatus = "failed" StepSkipped StepStatus = "skipped" StepInformational StepStatus = "informational" )
type VerifyOptions ¶
type VerifyOptions struct {
// Input is the user-supplied positional argument: pointer path,
// OCI reference (with or without oci:// prefix), or unpacked
// bundle directory. Required.
Input string
// BundleRef overrides the OCI reference when the input does not
// embed one — e.g., a pointer file whose bundle.oci is empty.
BundleRef string
// ExpectedIssuer pins the OIDC issuer URL recorded on the signing
// certificate. Empty allows any issuer.
ExpectedIssuer string
// ExpectedIdentityRegexp pins the signer's SubjectAlternativeName
// via regex. Empty allows any identity.
ExpectedIdentityRegexp string
// PlainHTTP forces HTTP for registry traffic (local-registry tests
// only).
PlainHTTP bool
// InsecureTLS disables TLS verification for the registry
// (self-signed certificates).
InsecureTLS bool
// AllowUnpinnedTag opts into accepting OCI references that resolve
// to a tag rather than a digest. By default the verifier refuses
// unpinned refs because tags can be rewritten by the registry, so
// "verify this artifact at this tag" is not content-addressable.
// Pointer-driven flows ignore this flag when the pointer carries a
// non-empty bundle.digest (the pointer's digest claim becomes the
// pin and is cross-checked against the actual pulled digest).
AllowUnpinnedTag bool
}
VerifyOptions configures one Verify run.
type VerifyResult ¶
type VerifyResult struct {
Input InputForm `json:"input" yaml:"input"`
Pointer *attestation.Pointer `json:"pointer,omitempty" yaml:"pointer,omitempty"`
Predicate *attestation.Predicate `json:"predicate,omitempty" yaml:"predicate,omitempty"`
Signer *SignerClaims `json:"signer,omitempty" yaml:"signer,omitempty"`
RecipeName string `json:"recipeName,omitempty" yaml:"recipeName,omitempty"`
BundleDigest string `json:"bundleDigest,omitempty" yaml:"bundleDigest,omitempty"`
Steps []StepResult `json:"steps" yaml:"steps"`
Exit int `json:"exit" yaml:"exit"`
}
VerifyResult is what Verify returns to its caller.
func Verify ¶
func Verify(ctx context.Context, opts VerifyOptions) (*VerifyResult, error)
Verify runs the verification pipeline. Returns a non-nil error only when verification could not begin (bad input, etc.); step-level failures are recorded in VerifyResult.Steps and reflected in Exit.