verifier

package
v0.47.0 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package verifier verifies Sigstore signatures on skill artifacts.

It is a thin wrapper over toolhive-core's container/verifier exports. Lock identities are bound into core's Sigstore policy; independently optional catalog constraints are matched against the observed certificate inside each bundle-verification attempt. This package adds the skills-domain vocabulary: lock file provenance conversion, the unsigned/invalid/mismatch error taxonomy, and the trust-on-first-use flow (nil expected identity verifies the chain of trust only; the caller records the observed identity).

It also enforces the two pinned certificate fields core's Identity cannot express — the signing workflow's git ref and runner class — against the certificate a successful policy verification produced.

Verification uses the trusted root embedded in toolhive-core — hermetic, no TUF fetch — so results are reproducible offline at the cost of snapshot freshness (see core's OfflineTrustedMaterial).

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnsigned indicates the artifact carries no Sigstore signature
	// material in any supported layout.
	ErrUnsigned = errors.New("artifact is not signed")
	// ErrSignatureInvalid indicates signature material was found but failed
	// cryptographic verification (or a stored bundle is malformed).
	ErrSignatureInvalid = errors.New("signature verification failed")
	// ErrSignerMismatch indicates the signature verifies, but against an
	// identity other than the expected one.
	ErrSignerMismatch = errors.New("signer identity mismatch")
	// ErrKeySigned indicates the artifact carries only cosign key-pair
	// signatures, so the keyless (Fulcio) trust root has nothing to chain
	// them to. Verifying them needs the signing public key supplied from
	// outside the artifact, because it is recoverable neither from the
	// artifact nor from the attached bundle — cosign's manifest defines no
	// annotation carrying it, and the reconstructed bundle holds a fixed
	// placeholder hint in its place.
	//
	// The text states the situation and prescribes no remedy: this sentinel
	// is shared by the skills and plugins surfaces, which do not offer the
	// same ones, and the right advice depends on what the entry already pins
	// (see keySignedInstallError). Callers wrap it with their own.
	//
	// Deliberately NOT wrapping ErrSignatureInvalid, unlike
	// ErrProvenanceFieldMismatch below: the signature may be perfectly
	// valid, so reporting it as a verification failure is precisely the
	// misclassification this sentinel exists to end. That narrowing cannot
	// fail open, because no caller treats ErrSignatureInvalid as permission
	// to proceed — it only selects a failure reason.
	ErrKeySigned = errors.New("artifact is signed with a cosign key pair, not keylessly")
	// ErrKeylessSigned is the mirror of ErrKeySigned: every signature on the
	// artifact carries a Fulcio certificate, so a cosign public key is the
	// wrong trust anchor to check it with. Reported when a caller supplies a
	// key for an artifact that was signed keylessly — the likeliest way to
	// reach the key path by mistake, and one whose remedy (drop the key) is
	// invisible in a bare "signature verification failed".
	//
	// Deliberately NOT wrapping ErrSignatureInvalid, for the same reason
	// ErrKeySigned does not: the signature is intact, and there is a real
	// verification path for it.
	ErrKeylessSigned = errors.New("artifact is signed keylessly, not with a cosign key pair")
	// ErrProvenanceFieldMismatch indicates the signature verifies against
	// the expected signer identity and issuer, but a certificate field the
	// Sigstore policy cannot itself express — the repository ref or runner
	// environment — differs from what is pinned. This is a NARROWER claim
	// than ErrSignerMismatch: the signer itself did not change, only one of
	// these additional certificate fields did. An error produced for this
	// reason satisfies errors.Is against BOTH sentinels (see
	// pinnedFieldMismatch), so existing callers checking only
	// ErrSignerMismatch keep working — the same --allow-signer-change
	// override remains the correct remediation for either cause — while a
	// caller that wants to tell them apart (e.g. to explain that a version
	// bump's ref changed, rather than its publisher) can check this one
	// specifically.
	ErrProvenanceFieldMismatch = errors.New("certificate provenance field mismatch")
)

Functions

func DecodePublicKey added in v0.47.0

func DecodePublicKey(encoded string) ([]byte, error)

DecodePublicKey converts the base64 DER SPKI form back into the PEM encoding the key verification APIs take. It re-validates rather than trusting its input: the value reaches here from an HTTP request body or a hand-editable lock file, and it is the only trust anchor the artifact will be checked against.

func EncodePublicKey added in v0.47.0

func EncodePublicKey(pemBytes []byte) (string, error)

EncodePublicKey converts a cosign public key file's PEM contents into the single-line base64 DER SPKI form the API and the lock file carry. The lock file's provenance values must be graphic, whitespace-free strings, so PEM's armor and line breaks cannot be stored verbatim.

This runs on the client side of the API deliberately: sending the file's path instead would name nothing on a server that is a different process, or on a different host, a different file entirely.

Types

type Default

type Default struct {
	// contains filtered or unexported fields
}

Default implements Verifier on toolhive-core's Sigstore exports.

func NewDefault

func NewDefault(keychain authn.Keychain) *Default

NewDefault creates a verifier using the given registry auth keychain for bundle retrieval. A nil keychain falls back to the default keychain.

func (*Default) ResultFromBundle

func (*Default) ResultFromBundle(bundleBytes []byte, digest string) (*Result, error)

ResultFromBundle verifies a stored bundle offline (chain of trust only) and returns the observed identity, for back-filling provenance of adopted skills.

A key-signed bundle is reported as ErrKeySigned rather than attempted: there is no identity to observe, so the back-fill this exists for has nothing to record, and the keyless verification below would fail on the missing certificate with a message about the wrong thing entirely.

func (*Default) VerifyBundleOffline

func (*Default) VerifyBundleOffline(bundleBytes []byte, digest string, expected *lockfile.Provenance) error

VerifyBundleOffline re-verifies a stored bundle against the artifact digest without network access. A non-nil expected identity is enforced inside the Sigstore policy; a mismatch is reported as ErrSignerMismatch, any other verification failure as ErrSignatureInvalid.

func (*Default) VerifyBundleOfflineWithKey

func (*Default) VerifyBundleOfflineWithKey(bundleBytes []byte, digest string, pubKeyPEM []byte) error

VerifyBundleOfflineWithKey re-verifies a stored key-signed bundle against the signer's PEM public key — the offline counterpart of VerifyOCIWithKey.

The artifact digest is passed straight through. A key-signed bundle's signature covers the cosign simple-signing payload rather than the artifact, but the stored bundle now carries that payload with it, so core recovers it and checks both that the signature covers those bytes and that those bytes name this artifact. Reconstructing the payload here from the reference — which is what this did before, via signer.PayloadDigest — is now refused as ErrSignatureArtifactMismatch: a digest derived from the payload proves nothing about which artifact it names, so a caller supplying it is exactly the caller who cannot detect a transplanted signature.

func (*Default) VerifyGit

func (*Default) VerifyGit(
	ctx context.Context,
	payload, signature []byte,
	expected *ProvenanceExpectation,
) (*Result, error)

VerifyGit cryptographically verifies a gitsign commit signature: the CMS signature is checked over the commit payload and the signing certificate chain is verified against the Fulcio roots in toolhive-core's embedded trusted material — no network. A non-nil expected identity must match the certificate identity (post-hoc comparison: git signatures have no Sigstore bundle to bind a policy into); nil expected is trust on first use.

The embedded Rekor transparency-log proof is NOT yet validated — signing time is checked against the certificate's own validity window, matching gitsign's certificate verifier. Rekor proof validation is a tracked follow-up (it requires reconstructing the proof from CMS unsigned attributes, which gitsign only exposes internally).

func (*Default) VerifyOCI

func (d *Default) VerifyOCI(
	ctx context.Context,
	imageRef, digest string,
	expected *ProvenanceExpectation,
) (*Result, error)

VerifyOCI discovers and verifies the Sigstore signature for an OCI artifact via the keyless (Fulcio) flow. See the interface documentation for the expected/TOFU semantics.

func (*Default) VerifyOCIWithKey

func (d *Default) VerifyOCIWithKey(
	ctx context.Context,
	imageRef, digest string,
	pubKeyPEM []byte,
) (*Result, error)

VerifyOCIWithKey discovers and verifies the Sigstore signature for an OCI artifact against a PEM public key (the cosign key-pair flow).

type ProvenanceExpectation added in v0.45.0

type ProvenanceExpectation struct {
	// contains filtered or unexported fields
}

ProvenanceExpectation identifies both the provenance constraints and their source. Lock entries use their existing strict identity semantics, while catalog fields constrain independently and ignore empty values.

func NewCatalogExpectation added in v0.45.0

func NewCatalogExpectation(p *regtypes.Provenance) *ProvenanceExpectation

NewCatalogExpectation returns independently optional catalog constraints.

func NewLockExpectation added in v0.45.0

func NewLockExpectation(p *lockfile.Provenance) *ProvenanceExpectation

NewLockExpectation returns the strict expectation recorded in a lock file.

type Result

type Result struct {
	// Signed is true when a signature was found and verified.
	Signed bool
	// SignerIdentity is the certificate's subject identity (workflow path
	// for GitHub-Actions-issued certificates, SAN otherwise). Empty for
	// key-signed artifacts, which carry no certificate.
	SignerIdentity string
	// CertIssuer is the OIDC issuer that authenticated the signer. Empty
	// for key-signed artifacts.
	CertIssuer string
	// RepositoryURI is the source repository from the certificate, if any.
	RepositoryURI string
	// RepositoryRef is the git ref the signing workflow ran on, from the
	// certificate's Fulcio extensions. Empty when the certificate carries no
	// such extension (signers outside CI, or gitsign from a personal OIDC
	// identity).
	RepositoryRef string
	// RunnerEnvironment is the runner class the signing workflow executed in
	// (e.g. "github-hosted"), from the certificate's Fulcio extensions.
	// Empty when the certificate carries no such extension.
	RunnerEnvironment string
	// SigstoreURL is the transparency log instance used for verification.
	SigstoreURL string
	// Provisional marks a verification with a documented assurance gap
	// (git signatures until Rekor proof validation lands).
	Provisional bool
	// Bundle is the signature material for offline re-verification, in
	// core's durable form: bare Sigstore bundle JSON when the signature
	// binds to the artifact structurally, and the
	// coreverifier.StoredBundleMediaType envelope (bundle plus the
	// simple-signing payload) when the payload is what names the artifact.
	// Opaque to callers — pass it back whole, with the ARTIFACT digest.
	Bundle []byte
}

Result contains the outcome of verifying a signed artifact.

func (*Result) ToLockProvenance

func (r *Result) ToLockProvenance() *lockfile.Provenance

ToLockProvenance converts a verification result to a lock file provenance block. Key-signed results have no certificate identity and yield nil — the lock file records provenance only for identity-bearing signatures.

type Verifier

type Verifier interface {
	// VerifyOCI discovers the Sigstore signature material attached to the
	// OCI artifact and verifies it (keyless/Fulcio flow). A non-nil
	// lock expectation is enforced inside the Sigstore verification policy;
	// catalog fields are checked independently against each verified bundle.
	// nil expected is the trust-on-first-use case and verifies the chain of
	// trust only.
	// Returns ErrUnsigned when the artifact carries no signature material.
	VerifyOCI(ctx context.Context, imageRef, digest string, expected *ProvenanceExpectation) (*Result, error)

	// VerifyOCIWithKey discovers the signature material and verifies it
	// against the given PEM public key (the cosign key-pair flow).
	// Key-signed bundles carry no certificate identity: trust is the key.
	VerifyOCIWithKey(ctx context.Context, imageRef, digest string, pubKeyPEM []byte) (*Result, error)

	// VerifyGit cryptographically verifies a gitsign commit signature over
	// the commit payload against the embedded Fulcio roots. A non-nil
	// expectation must match the certificate identity according to its lock
	// or catalog semantics; nil expected is the trust-on-first-use case.
	// Returns ErrUnsigned for an empty signature.
	VerifyGit(ctx context.Context, payload, signature []byte, expected *ProvenanceExpectation) (*Result, error)

	// VerifyBundleOffline re-verifies a stored bundle against the artifact
	// digest ("sha256:<hex>") without network access, enforcing expected
	// like VerifyOCI.
	VerifyBundleOffline(bundle []byte, digest string, expected *lockfile.Provenance) error

	// VerifyBundleOfflineWithKey re-verifies a stored key-signed bundle
	// against the signer's PEM public key without network access — the
	// offline counterpart of VerifyOCIWithKey. digest is the artifact's own
	// manifest digest, as for VerifyBundleOffline: the stored bundle carries
	// whatever the signature actually covers, so no caller reconstructs it.
	VerifyBundleOfflineWithKey(bundle []byte, digest string, pubKeyPEM []byte) error

	// ResultFromBundle verifies a stored bundle offline (chain of trust
	// only) and returns the observed identity — used to back-fill
	// provenance for adopted skills.
	ResultFromBundle(bundle []byte, digest string) (*Result, error)
}

Verifier verifies Sigstore signatures for skill artifacts.

Directories

Path Synopsis
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

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