ADR-0009: Wovyr-Native Keyless Signing (Sigstore-Shaped, Offline-Verifiable)
Status: Accepted
Date: 2026-07-03
Deciders: Platform Security Team
Supersedes: — (complements the ed25519 TrustStore mode, which remains)
Context
Section titled “Context”Plugin packages are signed with long-lived publisher ed25519 keys verified
against a TrustStore (wovyr-plugin/src/verify.rs).
Long-lived keys are the supply chain’s weakest link: they leak, they outlive the
people who held them, and revocation is manual. The v0.3 security track calls for
keyless (identity-based) signing in the Sigstore style: a certificate authority
(Fulcio’s role) binds an OIDC identity to a short-lived certificate over an
ephemeral key; a transparency log (Rekor’s role) publicly witnesses each signing
event; verifiers trust a pinned root, not per-publisher keys.
Adopting the real Sigstore stack wholesale conflicts with this codebase’s constraints:
- Determinism ([coding-standards §7]): core logic takes no ambient clock or network. Sigstore verification libraries fetch TUF roots and check X.509 validity against wall clock.
- Offline-first development: the public
fulcio/rekor.sigstore.devare not reachable from CI or the dev environment reliably; X.509/Fulcio parsing pulls a heavy dependency tree. - The platform already has the primitives the design needs: ed25519
sign/verify (
ring), a hash-chained tamper-evident audit log (wovyr-audit), and the capability-gated live-test pattern for real infrastructure.
Decision
Section titled “Decision”Implement Wovyr-native keyless signing in
wovyr-plugin/src/keyless.rs: the
Sigstore architecture (ephemeral key → short-lived identity certificate →
transparency-log witness → pinned-root verification) with Wovyr-native encodings
(canonical delimiter-separated byte strings signed by ed25519, JSON on the wire)
instead of X.509/DER.
- Ports, not services.
CertificateAuthorityandTransparencyLogare traits.InMemoryCa/InMemoryTransparencyLogare the deterministic in-process implementations (tests, single-operator dev). A Rekor-backed log (rekor.rs,rekorcargo feature) appendsrekordentries to a real Rekor server;deployment/rekor/runs one locally (pinned release images, no source builds), live-tested behindWOVYR_REKOR_URL(tests/rekor_live.rs). - Verification is fully offline and clock-free. A
KeylessBundle(certificate + manifest signature + log entry) is self-contained; verifiers hold a pinnedKeylessRoot(CA + optional log public keys). The certificate-validity check anchors on the log’sintegrated_time, never a local clock, soverify_keylessis deterministic over its inputs. Certificates are backdated by a 60 s skew allowance (Rekor timestamps are whole seconds). - Identity → namespace binding, fail-closed. An
IdentityPolicymaps identities (issuerexact,subjectprefix-wildcard) to the publisher namespaces they may sign for, checked against the manifest’s declared publisher — an allowed identity cannot sign for someone else’s namespace. Empty policy admits nobody. A transparency-log entry is required unless the operator opts out (require_transparency: false, the registry-witnessed mode). - Bundles ride in the
.wovyrpkgenvelope (optionalkeylessfield), andPackage::verify_keyless(root, policy)is the counterpart to the existingPackage::verify(trust). Both trust modes coexist; consumers choose per policy.
Consequences
Section titled “Consequences”- (+) Verification needs zero network and no new dependencies; the full matrix (tamper, unpinned CA, policy denial, expired window, forged SET, missing entry) is unit-tested offline; the Rekor path is proven live against real infrastructure.
- (+) Publishers need no long-lived signing secret; compromise windows shrink to the certificate TTL (default 10 min).
- (−) Not wire-compatible with Sigstore tooling (
cosigncannot verify an Wovyr bundle). Acceptable: the trust root is operator-pinned either way, and the architecture leaves room to swap encodings later. - (+) Rekor’s signed entry timestamp is verified offline: the bundle
carries Rekor’s canonicalized entry
body, the verifier reproduces the RFC 8785 SET payload ({body, integratedTime, logID, logIndex}), and pinned log keys are accepted as raw/SPKI ed25519 or ECDSA P-256 (Rekor’s memory signer) — proven live against a real Rekor (forged coordinates are rejected). - (−) No real OIDC yet: the CA attests whatever identity the operator of the signing environment asserts. Fine for the dev CA; a Fulcio-compatible or OIDC-validating CA is additive behind the same trait.
Consumers (landed after the core)
Section titled “Consumers (landed after the core)”Keyless is a policy-selectable trust mode at both supply-chain choke points, with identical no-downgrade semantics (a present bundle is verified keylessly or rejected — never falls back to the publisher-key path):
-
Registry publish —
Registry::with_keyless(root, policy)(wovyr-marketplace); a keyless-only package flows publish → discover → download → install → enable with no publisher key anywhere (tests/keyless_supply_chain.rs). -
Engine install —
PluginEngine::with_keyless(root, policy)(wovyr-plugin). -
Server — both are configured from one operator file,
~/.wovyr/plugins/keyless.json({"root": …, "policy": …}); absent ⇒ keyless disabled. -
CLI tooling —
wovyr plugin keyless-init(dev CA + pinned trust config, shared with the server) andwovyr plugin keyless-sign(ephemeral key never touches disk;--rekor <url>witnesses the signing, behind the CLI’skeyless-rekorfeature).plugin.keyless.jsonbeside a manifest rides into packages viapack/install/publish, andplugin.sigbecomes optional for keyless-only packages.
Deferred
Section titled “Deferred”- Merkle inclusion-proof checks against the log’s signed tree head.
- X.509/Fulcio certificate compatibility; OIDC token validation in the CA
(a non-goal while the trust root is operator-pinned — revisit if
cosigninterop is ever required). - Audit-log recording of keyless publish events.