Documentation
¶
Overview ¶
Package signer signs OCI artifacts with Sigstore, following the cosign convention: a "simple signing" payload binding the artifact's manifest digest is signed, attached to the registry as a cosign signature manifest (the "sha256-<hex>.sig" tag), and returned as a serialized Sigstore bundle for durable storage and offline re-verification.
Both cosign signing methods are supported, selected by Options: a file-based key pair (Options.Key) or keyless signing against Fulcio and Rekor with an OIDC identity token (Options.IdentityToken). Acquiring that token — ambient CI credentials, an interactive browser flow, a device flow — is entirely the caller's concern; this package never performs OAuth and only ever forwards the token it is handed to Fulcio.
It is the signing counterpart to github.com/stacklok/toolhive-core/container/verifier: a key-signed bundle produced here verifies through that package's verifier.VerifyBundleWithKey and a keyless one through verifier.VerifyBundle, and the attached manifest is the layout verifier.RetrieveBundles reconstructs from.
Index ¶
Constants ¶
const ( // DefaultFulcioURL is the public-good Fulcio certificate authority. DefaultFulcioURL = "https://fulcio.sigstore.dev" // DefaultRekorURL is the public-good Rekor transparency log. DefaultRekorURL = "https://rekor.sigstore.dev" )
Public-good Sigstore endpoints, used when Options leaves the corresponding URL empty.
These are hardcoded rather than derived from a trusted root, deliberately. The trusted root does carry the deployment's URIs, but reading them means fetching a root (a TUF network round trip, or the point-in-time snapshot embedded in container/verifier) purely to learn a URL, and the URI is only reachable by type-asserting root.CertificateAuthority — a single-method Verify interface — to the concrete *root.FulcioCertificateAuthority. More importantly, the trusted root is verification material: keying signing endpoints off it would let a trust-root refresh silently redirect where signing requests, and the identity token in them, are sent. Signing against a different deployment is instead an explicit choice, made through Options.
Variables ¶
var ErrAmbiguousSigningMethod = errors.New(
"conflicting signing methods: set exactly one of Options.Key or Options.IdentityToken")
ErrAmbiguousSigningMethod indicates both Options.Key and Options.IdentityToken were set. Rather than pick one, signing fails: the two produce materially different trust material — a bundle verifiers check against a bare public key, versus one they check against a Fulcio identity and the transparency log — so silently choosing would attach a signature the caller cannot verify the way it expects.
var ErrKeyRequired = errors.New(
"no signing method provided: set Options.Key to a cosign private key, " +
"or Options.IdentityToken to an OIDC identity token for keyless signing")
ErrKeyRequired indicates no signing method was provided: signing needs either a cosign private key or an OIDC identity token for the keyless flow. Callers exposing a CLI should wrap this with the flags the user is expected to pass.
Functions ¶
func PayloadDigest ¶
PayloadDigest returns the digest of the simple-signing payload that a signature over the artifact at ref pinned to digestStr covers.
It is informational, for inspecting or cross-referencing the blob inside an attached cosign signature manifest. Verification does not need it and must not be given it: every verifier entry point — including verifier.VerifyBundleOffline and verifier.VerifyBundleOfflineWithKey — takes the ARTIFACT's own manifest digest, the same digestStr passed here, and recovers the payload from the stored bundle itself. Passing this value where an artifact digest is expected fails with verifier.ErrSignatureArtifactMismatch.
func SimpleSigningPayload ¶
SimpleSigningPayload builds the canonical simple-signing payload for the artifact at ref pinned to digestStr. This payload — not the manifest digest — is what gets signed, per the cosign convention: a verifier recovers the payload from the signature manifest's layer, checks the signature over it, and reads the bound manifest digest out of it. Exported so callers can reproduce and inspect the exact bytes a signature covers; re-verifying a stored Result.Bundle does not need it, since the stored form carries the payload itself.
Types ¶
type Default ¶
type Default struct {
// contains filtered or unexported fields
}
Default implements Signer for both cosign signing methods: file-based key pairs and keyless signing via Fulcio and Rekor.
func NewDefault ¶
NewDefault creates a signer using the given registry auth keychain for pushing the signature manifest. A nil keychain falls back to the default keychain.
func (*Default) SignOCI ¶
func (d *Default) SignOCI(ctx context.Context, ref, digestStr string, opts Options) (*Result, error)
SignOCI signs the artifact following the cosign convention: the signature is computed over the simple-signing payload (which embeds the manifest digest, binding the signature to the artifact), the SAME signature is attached to the registry as a cosign signature manifest, and the returned bundle carries it with the payload's digest as the signed message. A verifier reconstructing the bundle from the registry manifest (or re-verifying the stored bundle offline) therefore checks exactly the signature that was attached — one signature, two representations.
The signing method comes from opts: a file key, or keyless via Fulcio and Rekor. The two differ only in where the signing key and its trust material come from — everything downstream of sign.Bundle, including the attached manifest layout, is shared.
type Options ¶
type Options struct {
// Key is the path to a cosign PEM-encoded private key file. An
// encrypted key is decrypted with the COSIGN_PASSWORD environment
// variable, matching the cosign CLI.
Key string
// IdentityToken is a raw OIDC ID token (a JWT, as a plain string)
// identifying the signer to Fulcio. Setting it selects the keyless
// flow: a single-use ephemeral key pair is minted, Fulcio issues a
// short-lived certificate binding it to the token's identity, and the
// signature is submitted to Rekor. Obtaining the token is the caller's
// responsibility — see the package doc.
//
// This token is sent as a bearer credential to FulcioURL, which is why
// FulcioURL and RekorURL are validated to be HTTPS (loopback excepted,
// for tests) before either is contacted — see keylessBundleOptions.
IdentityToken string
// FulcioURL overrides the certificate authority for keyless signing.
// Empty means DefaultFulcioURL. Defaulting happens at signing time and
// only when IdentityToken selects the keyless flow — a zero Options
// value alone still errors with ErrKeyRequired, it does not sign
// against the public-good deployment by default. Once IdentityToken IS
// set, though, an empty FulcioURL/RekorURL is a deliberate posture
// change from "no key ⇒ always error": keyless signing then performs
// outbound network egress to public-good Sigstore services by default.
FulcioURL string
// RekorURL overrides the transparency log for keyless signing. Empty
// means DefaultRekorURL, applied at signing time as with FulcioURL.
// Only the Rekor v1 API is supported — see rekorAPIVersionV1.
//
// A Rekor entry is not optional: the verification policy this project's
// container/verifier applies to keyless bundles requires a
// transparency-log entry, so a bundle signed without one would not
// verify.
RekorURL string
}
Options configures OCI signing. Exactly one signing method must be set: Key for the cosign key-pair flow, or IdentityToken for keyless signing. FulcioURL and RekorURL apply to keyless signing only and are ignored for the key-pair flow.
type Result ¶
type Result struct {
// Bundle is the serialized Sigstore bundle, for durable storage and
// later offline re-verification.
//
// Re-verify it against the ARTIFACT digest — the same digest passed to
// SignOCI — with [verifier.VerifyBundleOffline] or
// [verifier.VerifyBundleOfflineWithKey]. Bundle is not bare Sigstore
// bundle JSON: it wraps the bundle together with the simple-signing
// payload the signature covers, because that payload is the only thing
// tying a cosign signature to an artifact. See
// [verifier.StoredBundleMediaType] for the shape, and
// [verifier.DecodeStoredBundle] to unwrap it.
//
// Its JSON shape is not stable across calls for the same identity: a
// freshly attached signature wraps sign.Bundle's own bundle media type
// (v0.3), while a signature this call deduped against (see
// SignOCI's "one signature, two representations" doc) is reconstructed
// by verifier.RetrieveBundles from the classic cosign annotations —
// the v0.1 shape, carrying an inclusion promise rather than a proof.
// Both verify identically; a caller comparing stored bundles byte-for-
// byte across calls, or inspecting mediaType, will see this difference.
Bundle []byte
// PayloadDigest is the "<algorithm>:<hex>" digest of the simple-signing
// payload the signature covers.
//
// It is NOT the artifact digest passed to SignOCI: following the cosign
// convention, the signature covers a payload that *embeds* the artifact
// digest rather than the digest itself. Verifying Bundle does not
// require it — Bundle carries the payload, and the verifier entry points
// take the artifact digest — so this is informational: it identifies the
// blob in the attached signature manifest that this signature signs. See
// [PayloadDigest] to recompute it from a reference and digest alone.
PayloadDigest string
}
Result is the outcome of a signing operation.
type Signer ¶
type Signer interface {
// SignOCI signs the artifact at ref pinned to the given manifest digest
// ("sha256:..."), attaches the signature as a cosign signature manifest
// next to the artifact, and returns the bundle together with the digest
// it signs.
SignOCI(ctx context.Context, ref, digest string, opts Options) (*Result, error)
}
Signer signs OCI artifacts and attaches the signature to the registry.