Skip to content

First-party signing (Path A)

Sign images your own CI builds and publishes, at publish time.

Flow

$ gapsign sign -key ~/.config/gapsign/cosign.key registry.internal/app:v1
signed registry.internal/app@sha256:...
  signature: registry.internal/app@sha256:...

gapsign sign:

  1. Resolves the reference to an immutable digest (a tag is looked up against the registry; a digest ref is used as-is).
  2. Builds the cosign "simple signing" payload committing to that digest and signs it with the enterprise key (ECDSA P-256 / SHA-256).
  3. Pushes the signature as a cosign-layout OCI artifact at repo:sha256-<hex>.sig, next to the image.

Verify

$ gapsign verify -pub ~/.config/gapsign/cosign.pub registry.internal/app:v1
verified registry.internal/app@sha256:...

Verification is fail-closed: it exits non-zero for an unsigned image, a key that didn't sign it, or a signature whose payload binds a different digest or repository. Because the signature commits to the digest and the repository, a signature cannot be transplanted to a different image or repo.

Keys

$ gapsign keygen                 # writes cosign.key (0600) + cosign.pub
$ gapsign keygen -name ci-kms    # a named pair
  • The private key is written mode 0600 under ~/.config/gapsign/ (or -out DIR) and is never logged or committed.
  • The public key is standard PKIX PEM — interchangeable with cosign generate-key-pair output — and is the trust anchor you distribute to verifiers via GitOps.

cosign interoperability

gapsign signatures are byte-format cosign signatures. A stock cosign verifies them offline:

$ cosign verify --key cosign.pub --insecure-ignore-tlog registry.internal/app@sha256:...

In CI

Run gapsign sign after the image is pushed. Resolve the key from a file under ~/.config/gapsign/ or from a KMS (see Configuration); never bake a private key into the pipeline definition.