Skip to content

Verify keyless cosign signatures with sigstore-verify (spike) - #179

Merged
jonasz-lasut merged 5 commits into
mainfrom
spike/sigstore-verify
Oct 27, 2026
Merged

jonasz-lasut merged 5 commits into
mainfrom
spike/sigstore-verify

Conversation

@jonasz-lasut

@jonasz-lasut jonasz-lasut commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Spike for #116: keyless cosign signatures verified through the sigstore org's second Rust implementation, sigstore-rust (sigstore-verify 0.15.1), in place of the sigstore 0.14 crate.

What changes

  • --cosign-trusted-root <trusted_root.json> (COSIGN_TRUSTED_ROOT) on serve and validate: a Sigstore trusted root (Fulcio authorities, Rekor and certificate-transparency keys, timestamp authorities). A bundle carrying a certificate is verified offline against it: the signing time from the bundle's RFC 3161 timestamps and its Rekor entry's signed entry timestamp (a source that is present but fails is fatal; there is no fallback to the clock), the inclusion proof and checkpoint, the certificate chain at that time, then the signature. cosign.rs then requires the cosign predicate over the pinned digest.
  • trustSigner, the operator policy's eighth action: permit (principal, action == Action::"trustSigner", resource) when { resource in Repository::"…" && context.issuer == "…" && context.identity like "…" } decides, per repository and default-deny, which signer a verified keyless signature may come from. A trusted root alone admits nothing; requireSignature keeps deciding which repositories must be signed.
  • Key-based bundles stay on the key path, now on sigstore-crypto's VerificationKey: the keys remain the trust root and no transparency-log entry is read. sigstore-verify's public-key policy refuses a bundle with neither a log entry nor a timestamp, which is what cosign sign --key --tlog-upload=false writes (the e2e scenario), so that path is not an option for them.
  • The operator policy is shared with the verifier as an Arc. reqwest's query and json features are declared: the removed crate had enabled them transitively.
  • Docs: the signatures page (a keyless section, how to obtain the root with cosign initialize), the flags table, the Cedar reference, troubleshooting, the trust-model and use-case one-pagers, AGENTS.md.

Verification

  • cargo test -p function-wasm --lib (103), --test conformance (7, goldens unchanged), --test raw_client, cargo test -p guestfn, cargo test -p function-wasm-engine; cargo clippy --workspace --all-targets -- -D warnings; cargo fmt --all --check; mdbook build. The guest behavioural suite (tests/guests.rs) was not run locally.
  • The fixture under crates/function/testdata/cosign/ is the real keyless signature the release workflow attached to the published pdb-addon example; its certificate expired on 2026-10-07 at 11:25 UTC. The tests verify it offline against the trusted root embedded in sigstore-trust-root, and refuse it for another digest, under the staging root, tampered, and when no rule trusts the signer.
  • Live, against ghcr.io/jonasz-lasut/wasmfn/examples/policy-gate@sha256:89ed3c52…: function validate composition.yaml --sandbox-policy-file policy.cedar --cosign-trusted-root trusted_root.json --resolve admitted the module under a rule trusting publish-pkg.yml@refs/tags/v*; under a rule trusting *@refs/heads/main it refused with keyless signature by https://github.com/jonasz-lasut/function-wasm/.github/workflows/publish-pkg.yml@refs/tags/v0.6.0 (issuer https://token.actions.githubusercontent.com) is not trusted for ghcr.io/jonasz-lasut/wasmfn/examples/policy-gate: no trustSigner rule in the operator policy permits it; without the flag it fell back to the existing no-key refusal.

Open before this leaves spike status

  • The trusted root is a file read at startup. Fetching it over TUF and refreshing it is Fetch and refresh the Sigstore trusted root over TUF #183 (an air-gapped static file, or a dynamic pull of public-good, staging or github), with the design and its decisions written up there.
  • sigstore-rust is pre-1.0: four breaking minors landed between September and October 2026, so its Renovate bumps will need code.
  • /e2e has not run on this branch.

Since the first push

  • The refusal for a required module met by a runtime with neither flag now names both: the operator policy requires a cosign signature, but the runtime has neither --cosign-key nor --cosign-trusted-root to verify it, and function validate's egress warning likewise. Three conformance goldens were re-recorded deliberately for those two sentences and nothing else.
  • testsigstore::Instance (feature testutil) is an in-memory Sigstore instance: a Fulcio CA, a CT log and a Rekor log from fixed seeds that issue keyless bundles at a fixed past time (ten-minute certificate with an embedded SCT, Rekor dsse/0.0.1 entry with its signed entry timestamp, inclusion proof and checkpoint) and publish the trusted root that verifies them. Five new conformance cases run function validate --resolve against it under testdata/validate/signer-policy*.cedar: a trusted signer admitted, an untrusted one refused, another instance's root refused, a keyless module met by --cosign-key alone, a key-signed module met by --cosign-trusted-root alone. The whole keyless path is now covered offline and deterministically; the real GHCR fixture stays as the cross-check against production material.

Fixes #116. The trusted root's fetch and refresh over TUF continues in #183.

Keyless cosign signatures (a Fulcio certificate, a Rekor entry and an
RFC 3161 timestamp in a v0.3 Sigstore bundle) are verified through the
sigstore org's sigstore-verify crate against a Sigstore trusted root the
new --cosign-trusted-root flag names. The crate establishes the signing
time from the bundle's RFC 3161 timestamps and its Rekor entry's signed
entry timestamp, verifies the inclusion proof and checkpoint, chains the
certificate at that time and checks the signature; the runtime then
requires the statement to be a cosign signature of the pinned digest and
the signer to be trusted for the module's repository by the operator
policy's new trustSigner rules (context.issuer, context.identity over
the Repository hierarchy, default-deny).

The sigstore 0.14 crate is replaced: key-based signatures keep their
contract (the keys are the trust root, no transparency-log entry is
required or read) on sigstore-crypto's VerificationKey, because
sigstore-verify's public-key policy refuses a bundle with neither a log
entry nor a timestamp, which is what cosign writes with
--tlog-upload=false. The operator policy is shared with the verifier as
an Arc. reqwest's query and json features, which the removed crate had
enabled transitively, are declared.

The fixture under testdata/cosign is the real keyless signature this
repository's release workflow attached to the published pdb-addon
example; the tests verify it offline against the trusted root embedded
in sigstore-trust-root and refuse it for another digest, under another
trusted root, tampered, and when no trustSigner rule trusts the signer.

Spike for #116.
The signatures page gains a keyless section (--cosign-trusted-root, how
to obtain a trusted root, what is verified and the trustSigner rule), the
flags table its row, the Cedar reference the eighth action, the
troubleshooting page the four new refusals, and the trust-model and
use-case one-pagers move their revisions. AGENTS.md follows the code: the
cosign.rs entry, the Signatures section and the sigstore-rust crates in
Key Dependencies in place of sigstore-rs.

Part of #116.
A required module met by a runtime that can verify nothing is refused
with "the operator policy requires a cosign signature, but the runtime
has neither --cosign-key nor --cosign-trusted-root to verify it", and
function validate's egress warning reads "neither --cosign-key nor
--cosign-trusted-root was given"; the all-or-nothing refusal of a
non-OCI source names both flags too. Both sentences named --cosign-key
alone, which since the keyless path landed understated the operator's
options.

Three conformance goldens are re-recorded deliberately for those two
sentences and nothing else: SignatureRequired, ManifestNoPolicy-egress
and ManifestPermissive-egress. The wording is pinned contract from 1.0.0
on; this lands before it.

Part of #116.
testsigstore::Instance (feature testutil) is an in-memory Sigstore
instance: a Fulcio certificate authority, a certificate-transparency log
and a Rekor log, P-256 keys from fixed seeds, that issues keyless cosign
bundles at a fixed past time and publishes the trusted root that
verifies them. A bundle carries a ten-minute certificate with the
signer's identity, Fulcio's issuer extension and an embedded SCT over
the precertificate, and a Rekor dsse 0.0.1 entry with its signed entry
timestamp, a one-leaf inclusion proof and a signed checkpoint; no
RFC 3161 timestamp. Every certificate it issues has expired, so a
signature verifies only because its signing time is authenticated.

Unit tests verify and refuse against it beside the real GHCR fixture,
and five conformance cases run function validate --resolve against a
registry it signed into, under the new signer-policy fixtures: a
trusted signer admitted, an untrusted one refused, another instance's
root refused (that golden quotes sigstore-verify's wording), a keyless
module met by --cosign-key alone, a key-signed module met by
--cosign-trusted-root alone. rcgen, x509-cert and time join the
testutil feature and the dev-dependencies.

Part of #116.
The signatures page, the trust-model one-pager and AGENTS.md said the
trusted root's TUF refresh was the open half of #116; it is now #183,
split out so #116 closes with this branch on the file-based root.
@jonasz-lasut

Copy link
Copy Markdown
Owner Author

/e2e

@jonasz-lasut
jonasz-lasut marked this pull request as ready for review October 27, 2026 07:40
@jonasz-lasut jonasz-lasut mentioned this pull request Oct 27, 2026
13 of 18 tasks
@jonasz-lasut
jonasz-lasut merged commit 4104095 into main Oct 27, 2026
6 checks passed
@jonasz-lasut
jonasz-lasut deleted the spike/sigstore-verify branch October 27, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Verify keyless cosign signatures once sigstore-rs authenticates the signing time

1 participant