Documentation
¶
Overview ¶
Package gitprov verifies the provenance of git objects and OCI images, fully offline (docs/specs/verification.md): for a git object, a gitsign (sigstore keyless) CMS signature over the raw commit or annotated-tag bytes with — when the caller requires transparency — a Rekor inclusion proof embedded in the signature itself; for an image, a cosign sigstore bundle or simple-signing envelope over the manifest digest with its transparency entry carried (VerifyImage); in both a Fulcio short-lived certificate carrying the signer's OIDC identity matched against caller policy and judged at a signed time, all verified against a caller-pinned trusted root. No service is ever queried.
The label of a git signature's armor header line states its kind — gitsign's CMS, OpenPGP, SSH — and the kind selects the verifier (SignatureKindOf, REQ-verify-signature-kind): Verify takes a sigstore signature and fails on any other kind with ErrSignatureKind; VerifyPinned takes an OpenPGP or an SSH signature and verifies it against exactly the keys the caller pins (ParsePinnedKey), offline and without transparency, the outcome naming the key's kind and fingerprint, a signature the keys do not vouch for ErrUnpinnedKey (REQ-verify-pinned-key). A caller routing by kind reads it first.
Only signatures made in gitsign's offline Rekor mode (`gitsign.rekorMode=offline`) carry the embedded proof. A signature from gitsign's default online mode is unverifiable with transparency required — its legacy Rekor entry is not an offline-verifiable binding of the CMS signature, a dead end established empirically (gitsign's own source plus real-bytes captures) before this library's extraction — and there is deliberately no network fallback.
Mechanism notes:
- The split of an object into the payload a signature covers and the signature is gitprov's own port of git's rules over the raw bytes (object.go: parse_buffer_signed_by_header for a commit, parse_signature and remove_signature for a tag), never a library object re-encode and never a line reader rebuilding the payload — one that drops a carriage return or completes a final line verifies bytes the origin never signed, and refuses what git verifies. Certificate-chain verification uses gitsign's public pkg/git CertVerifier over Fulcio pools built from the pinned trusted root.
- The offline Rekor inclusion check cannot use gitsign's own verifier: pkg/rekor.Client.VerifyInclusion is hard-wired to cosign's TUF/network trusted-root global, and its reconstruction helpers live under gitsign/internal. rekoroid.go is a faithful, attributed port of gitsign v0.16.0 internal/rekor/oid; the proof+SET check is cosign/v3's VerifyTLogEntryOffline with the pinned root as trusted material.
- CMS structural access uses the upstream github.com/github/smimesign/ietf-cms/protocol; sigstore-go supplies the trusted-root material and, for images and for the leaf's judgement at a signed time on both paths, its bundle verifier and its certificate-timestamp verb (its verify API cannot consume CMS, so the git path's chain and Rekor binding stay gitsign's and the port's).
Dependency floor: gitsign v0.15.0, where CVE-2026-44310 (empty-cert PKCS7) was fixed. Re-audit rekoroid.go against upstream on any gitsign bump.
Index ¶
- Variables
- func HasEmbeddedRekor(obj Object) (bool, error)
- func HasImageTime(carrier ImageCarrier) (bool, error)
- func HashedRekordBody(ctx context.Context, message, sig []byte, cert *x509.Certificate) ([]byte, error)
- type Identity
- type ImageCarrier
- type Object
- type ObjectFormat
- type ObjectKind
- type PinnedKey
- type SignatureKind
- type SigstoreBundle
- type SimpleSigningEnvelope
- type TrustedRoot
- type VerifiedIdentity
- type VerifiedKey
Constants ¶
This section is empty.
Variables ¶
var ErrIdentityMismatch = errors.New("gitprov: certificate identity does not match policy")
ErrIdentityMismatch marks a certificate whose extracted identity does not satisfy the policy on some axis (REQ-verify-identity-match) — a cryptographically valid signature by a signer the policy does not accept, as opposed to invalid or unverifiable evidence. Callers whose own policy tolerates unsigned subjects classify on it: an unaccepted signer is a non-acceptance, not proof of tampering.
var ErrSignatureKind = errors.New("gitprov: the signature is not of the kind this verification takes")
ErrSignatureKind marks a signature of a kind other than the one the verification takes: the object is signed, and by a signature its header line states, but this verifier does not read that kind. A caller routing by kind selects the verifier with SignatureKindOf before verifying.
var ErrUnpinnedKey = errors.New("gitprov: the signature is by no pinned key")
ErrUnpinnedKey marks a signature the pinned keys do not vouch for (REQ-verify-pinned-key): none of the signature's kind is pinned, the key that signed is not among them, or it is among them but was not fit to sign at the signature's time — its key flags admitting no signing, the key or signing subkey not yet created, expired, revoked or its self-signature or binding lapsed by then, the primary identity revoked, no self-signature at all, or one of the key's own statements carrying a critical notation this verifier does not know. The cryptography may be sound: the keys pinned simply do not cover it. Callers whose own policy tolerates unsigned subjects classify on it: an unpinned signer is a non-acceptance, not proof of tampering, as ErrIdentityMismatch is for a sigstore signature.
Functions ¶
func HasEmbeddedRekor ¶
HasEmbeddedRekor reports whether the object's signature carries an embedded Rekor TransparencyLogEntry in its CMS unsigned attributes — i.e. the signer used gitsign's offline Rekor mode (REQ-detect-embedded). Present means the proof is offline-verifiable via Verify with transparency required; absent means the signature is from gitsign's default online mode, whose legacy Rekor entry is not offline-verifiable, so a transparency-requiring caller must reject the object — and never query Rekor to recover it.
It establishes NO trust and performs NO verification — it only inspects for the attribute's presence, so consumers can produce a precise unverifiable-versus-invalid diagnosis. An unsigned or structurally malformed object returns an error rather than answering false, so callers fail closed; so does an object signed by a signature of another kind (ErrSignatureKind), which carries no sigstore attribute to inspect.
func HasImageTime ¶
func HasImageTime(carrier ImageCarrier) (bool, error)
HasImageTime reports whether an image carrier holds the material a signed time would come from — a transparency entry with a signed entry timestamp, or an RFC 3161 timestamp — verifying none of it (REQ-detect-image-time): present means VerifyImage can judge the leaf at a time; absent means the carrier is unverifiable by design, which a consumer tells apart from an invalid one. A carrier that is neither shape returns an error rather than answering false.
func HashedRekordBody ¶
func HashedRekordBody(ctx context.Context, message, sig []byte, cert *x509.Certificate) ([]byte, error)
HashedRekordBody is the canonical HashedRekord entry body for (message, sig, cert): the shape both a git object's embedded entry and a simple-signing envelope's Rekor bundle must have logged.
Types ¶
type Identity ¶
type Identity struct {
Issuer string // exact OIDC issuer (xor the other Issuer kinds)
IssuerRegex string // OIDC issuer regex (xor)
IssuerGlob string // OIDC issuer glob (xor)
Subject string // exact cert SAN (xor the other Subject kinds)
SubjectRegex string // cert SAN regex (xor)
SubjectGlob string // cert SAN glob (xor)
}
Identity is the caller's statement of who may sign (REQ-verify-identity-match): each axis carries exactly one pattern kind — an exact string, a full-match regular expression, or a full-input glob (`/`-separated component semantics; the greatliontech/glob pattern language).
type ImageCarrier ¶
type ImageCarrier interface {
// contains filtered or unexported methods
}
ImageCarrier is one of the two carriers cosign produces for a signature over an OCI manifest or index digest (verification.md, Image signatures): a SigstoreBundle or a SimpleSigningEnvelope, as bytes the caller fetched. Where a carrier is found for a digest and how many are judged is the caller's; each VerifyImage call verifies one.
type Object ¶
type Object struct {
Kind ObjectKind
Format ObjectFormat
Raw []byte
}
Object is a git object presented for verification: its kind, the object format its raw bytes are in, and the raw object bytes — the git-core content after the "<type> <len>\0" header, exactly as git hashes it. Any re-encode through an object parser is not raw and would verify bytes the origin never signed (REQ-verify-raw-bytes).
type ObjectFormat ¶
type ObjectFormat string
ObjectFormat names a git object format — the hash algorithm whose form the raw bytes are in. It is caller-stated, never inferred from the bytes (REQ-verify-object-formats).
const ( SHA1 ObjectFormat = "sha1" SHA256 ObjectFormat = "sha256" )
type ObjectKind ¶
type ObjectKind string
ObjectKind names the two signable git object kinds.
const ( Commit ObjectKind = "commit" Tag ObjectKind = "tag" )
type PinnedKey ¶
type PinnedKey struct {
// contains filtered or unexported fields
}
PinnedKey is a public key the caller hands in for a verification — an OpenPGP public key, or an SSH one — verified against directly, never looked up (REQ-verify-pinned-key). It is made by ParsePinnedKey alone: its fingerprint is derived from the parsed key there, so a key and a fingerprint that disagree cannot be built.
func ParsePinnedKey ¶
func ParsePinnedKey(kind SignatureKind, key string) (PinnedKey, error)
ParsePinnedKey reads a public key of the kind. For OpenPGP, one armored public key block — a primary key with its subkeys and signatures, as gpg exports it — and no other: a block holding several keys, or a private key, is refused. For SSH, one OpenSSH public key line — the key type, its base64, an optional comment, nothing before it and no line beside it; an authorized_keys line carrying options is no public key, nor is a certificate, and a DSA key, or an RSA key under 1024 bits, which OpenSSH refuses, is refused.
func (PinnedKey) Fingerprint ¶
Fingerprint is the key's fingerprint as its kind spells it: for an OpenPGP key, the primary key's fingerprint as uppercase hex; for an SSH key, OpenSSH's SHA-256 form ("SHA256:" and the unpadded base64 of the hash of the key's wire form).
func (PinnedKey) Kind ¶
func (k PinnedKey) Kind() SignatureKind
Kind is the signature kind the key verifies: OpenPGP or SSH.
type SignatureKind ¶
type SignatureKind string
SignatureKind is what signed a git object, selected by the label of the signature's armor header line (REQ-verify-signature-kind): gitsign's CMS under SIGNED MESSAGE, an OpenPGP signature under PGP SIGNATURE, an SSH signature under SSH SIGNATURE. The kind selects the verifier and nothing else does: a verification of one kind handed a signature of another fails with ErrSignatureKind rather than reading the bytes as its own.
const ( // Sigstore is gitsign's CMS signature, verified by Verify against a // trusted root. Sigstore SignatureKind = "sigstore" // OpenPGP is an OpenPGP signature, verified against pinned keys. OpenPGP SignatureKind = "openpgp" // SSH is an SSH signature, verified against pinned keys. SSH SignatureKind = "ssh" )
func SignatureKindOf ¶
func SignatureKindOf(obj Object) (SignatureKind, error)
SignatureKindOf reports the kind of the object's signature at its form's location (REQ-verify-signature-kind), establishing no trust: it reads the armor frame alone, never the body — whether the body decodes as the kind states is the verifier's check. An unsigned or malformed object, a signature that is not exactly one armored block, and a label naming no kind each fail closed.
type SigstoreBundle ¶
type SigstoreBundle struct {
JSON []byte // the bundle as serialized
}
SigstoreBundle is the carrier cosign's default sign writes: a sigstore bundle of version 0.3 — a DSSE envelope with one signature over an in-toto v1 statement naming the digest as a subject under the cosign sign predicate type, with the leaf certificate, a transparency-log entry and any RFC 3161 timestamps as verification material — attached to the manifest as an OCI referrer whose artifact type is the bundle's media type.
type SimpleSigningEnvelope ¶
type SimpleSigningEnvelope struct {
// Payload is the layer's content, a simple-signing document
// (application/vnd.dev.cosign.simplesigning.v1+json) naming the
// digest at critical.image.docker-manifest-digest.
Payload []byte
// Signature is the dev.cosignproject.cosign/signature annotation:
// the signature over the payload bytes, base64.
Signature string
// Certificate is the dev.sigstore.cosign/certificate annotation:
// the Fulcio leaf, PEM.
Certificate string
// Chain is the dev.sigstore.cosign/chain annotation, PEM, or empty.
Chain string
// RekorBundle is the dev.sigstore.cosign/bundle annotation: the
// signed entry timestamp with the entry's body, log index, log
// identifier and integrated time, JSON.
RekorBundle string
// RFC3161Timestamp is the dev.sigstore.cosign/rfc3161timestamp
// annotation, JSON holding the DER timestamp response base64, or
// empty.
RFC3161Timestamp string
}
SimpleSigningEnvelope is cosign's legacy carrier, a signature layer under the repository's sha256-<hex>.sig tag or a referrer whose configuration media type is application/vnd.dev.cosign.artifact.sig .v1+json: the layer's payload and its annotations, each as cosign wrote it.
type TrustedRoot ¶
type TrustedRoot struct {
// contains filtered or unexported fields
}
TrustedRoot is a pinned sigstore TUF trusted root plus the digest of the exact bytes it was loaded from. The digest is computed over the raw bytes before parsing — a parse round-trip is not canonical, so only the raw bytes are a stable identity (REQ-root-pinned-bytes) — and rides into every VerifiedIdentity produced against this root.
The fields are unexported so the parsed root and its digest can never diverge: ParseTrustedRoot/LoadTrustedRoot are the only producers. The zero value is unusable and rejected by Verify.
func LoadTrustedRoot ¶
func LoadTrustedRoot(path string) (*TrustedRoot, error)
LoadTrustedRoot reads and pins the trusted root at path.
func ParseTrustedRoot ¶
func ParseTrustedRoot(raw []byte) (*TrustedRoot, error)
ParseTrustedRoot pins and parses trusted-root bytes. Fully offline: the bytes are the trust anchor, with no TUF refresh of any kind.
func (*TrustedRoot) Digest ¶
func (t *TrustedRoot) Digest() string
Digest returns the "sha256:<hex>" digest of the exact raw bytes this root was parsed from (REQ-root-pinned-bytes).
type VerifiedIdentity ¶
type VerifiedIdentity struct {
Subject string // the cert SAN that matched policy
Issuer string // the cert OIDC issuer
CertFingerprint string // "sha256:<hex>" of the leaf cert DER
RekorLogIndex int64 // transparency not required ⇒ 0; with it, a genuine entry MAY still be index 0 (Rekor logIndex minimum is 0) — not an unset sentinel
RekorIntegratedTime int64 // 0 iff transparency was not required (a real entry's integratedTime is wall-clock seconds, never the 1970 epoch — this IS a reliable unset signal); for an image, the signed time the leaf was judged at
TrustedRootDigest string // the pinned-root digest verified against (TrustedRoot.Digest)
Digest string // for an image, the "<algorithm>:<hex>" digest verified; empty for a git object
}
VerifiedIdentity is the proven outcome of a successful verification — everything a consumer needs to record what was verified and against what.
func Verify ¶
func Verify(ctx context.Context, obj Object, id Identity, tr *TrustedRoot, requireTransparency bool) (*VerifiedIdentity, error)
Verify verifies a git object's provenance against the policy and the pinned trusted root, fully offline (REQ-verify-offline): the CMS signature at the object form's location verifies as a detached signature over the split payload against a Fulcio chain ending in the pinned root (REQ-verify-cert-chain); with requireTransparency, the Rekor inclusion proof and signed entry timestamp embedded in the signature's unsigned attributes are bound to this signature and verified against the pinned root's log keys (REQ-verify-embedded-rekor) — a signature with no embedded proof (gitsign's default online mode) is unverifiable and fails, with no network recovery — and the leaf is then judged at the entry's integrated time, its chain and its signed certificate timestamp (REQ-verify-signed-time); and the certificate identity must match policy on both axes (REQ-verify-identity-match). Every failure returns an error and no VerifiedIdentity (REQ-verify-fail-closed).
func VerifyImage ¶
func VerifyImage(ctx context.Context, digest string, carrier ImageCarrier, id Identity, tr *TrustedRoot) (*VerifiedIdentity, error)
VerifyImage verifies an image signature over digest ("<algorithm>: <hex>") against the policy and the pinned trusted root, fully offline (REQ-image-offline-verification): the carrier is one of the two shapes (REQ-image-carriers); the digest in hand is the digest the signed content names, the signature verified over the carried bytes (REQ-image-digest-binding); the leaf's chain verifies against the root's Fulcio authorities and its signed certificate timestamp against the root's certificate-transparency logs; the transparency entry verifies against the root's log keys; every carried RFC 3161 timestamp verifies against a pinned authority; the leaf is judged at a signed time, a carrier with none failing (REQ-image-time-source); and the identity matches policy on both axes (REQ-verify-identity-match). Every failure returns an error and no VerifiedIdentity (REQ-verify-fail-closed); success carries the digest and the signed time the leaf was judged at (REQ-image-verified-identity).
type VerifiedKey ¶
type VerifiedKey struct {
Kind SignatureKind
Fingerprint string
}
VerifiedKey is the proven outcome of a pinned-key verification: the kind and fingerprint of the pinned key that verified the signature, in place of a Fulcio identity. It carries no signed time, no transparency proof binding one to the signature.
func VerifyPinned ¶
func VerifyPinned(obj Object, keys []PinnedKey) (*VerifiedKey, error)
VerifyPinned verifies a git object's OpenPGP or SSH signature against the pinned keys and no other, offline and without transparency — none can be asked for (REQ-verify-pinned-key): the signature at the object form's location (REQ-verify-signature-extraction) is of the kind its label states (REQ-verify-signature-kind), the signing key is found among the pinned keys of that kind by fingerprint, and the pinned key — never the key the signature carries — verifies the signature over the raw payload bytes (REQ-verify-raw-bytes). Every failure — a sigstore signature (ErrSignatureKind), a signature the pinned keys do not vouch for (ErrUnpinnedKey), one that does not verify — returns an error and no VerifiedKey (REQ-verify-fail-closed).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
vulnexcept
command
Command vulnexcept judges govulncheck's JSON stream against a file of standing exceptions, each an advisory id with an expiry date and a reason: an advisory that reaches the module's own code at call level fails the check unless an unexpired exception names it, an exception that has expired fails the check, and an exception naming an advisory the scan no longer reports fails it too, so a standing exception is re-examined when its date comes and removed when its advisory goes; nothing is ever silenced.
|
Command vulnexcept judges govulncheck's JSON stream against a file of standing exceptions, each an advisory id with an expiry date and a reason: an advisory that reaches the module's own code at call level fails the check unless an unexpired exception names it, an exception that has expired fails the check, and an exception naming an advisory the scan no longer reports fails it too, so a standing exception is re-examined when its date comes and removed when its advisory goes; nothing is ever silenced. |
|
Package sigstoretest is a synthetic sigstore for tests, this library's own and its consumers': a timestamp authority (sigstore-go's virtual one), a Rekor log of its own signing entry timestamps and checkpoints, and a Fulcio of its own that issues short-lived leaves carrying a signed certificate timestamp from its own certificate-transparency log — the parts the virtual sigstore does not model, and the parts every verification judged at a signed time needs.
|
Package sigstoretest is a synthetic sigstore for tests, this library's own and its consumers': a timestamp authority (sigstore-go's virtual one), a Rekor log of its own signing entry timestamps and checkpoints, and a Fulcio of its own that issues short-lived leaves carrying a signed certificate timestamp from its own certificate-transparency log — the parts the virtual sigstore does not model, and the parts every verification judged at a signed time needs. |