verifier

package
v0.13.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 15, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

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:

  1. 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.
  2. 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.
  3. Predicate parse — use the verified predicate when the signature step produced one; otherwise fall back to the unsigned statement.intoto.json (self-consistency only).
  4. 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.
  5. 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

View Source
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

View Source
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.

const (
	InputFormDir     InputForm = "dir"
	InputFormPointer InputForm = "pointer"
	InputFormOCI     InputForm = "oci"
)

func DetectInputForm

func DetectInputForm(input string) (InputForm, error)

DetectInputForm classifies a user-supplied input string into one of the three supported transport forms. Detection precedence:

  1. URL prefix: oci:// → OCI; http(s):// is rejected.
  2. Filesystem: directory → dir; .yaml/.yml file → pointer.
  3. Bare OCI ref shape ("registry/repo[:tag][@digest]") → OCI.

type KV

type KV struct {
	Key   string `json:"key" yaml:"key"`
	Value string `json:"value" yaml:"value"`
}

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:

  1. 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.
  2. Every file the manifest names exists, has the expected size, and hashes to the recorded sha256.
  3. 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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL