07 — The signing map, the röra verdict, and the one chain
Lane D2, 2026-09-20. Answers the owner's question: "du får analysera gunnar igen — det skall finnas DSSE/SLSA där och/eller i tunnr/znippy. Eh, ok, en röra alltså?"
Every cell is marked MEASURED (code read at the cited file:line), READ (a comment or doc in the tree asserts it, code not read), or DESIGNED (a design document, not implemented). Nothing here is inferred from a running system — a new gunnar estate was being stood up while this was written and no gunnar was contacted.
0. Four claims from the previous lane, re-derived
The coordinator carried four claims into this lane. Re-deriving them changed three.
| Claim carried in | Verdict | Evidence |
| "gunnar does NOT use DSSE at all" | RIGHT, and for a stated reason | MEASURED. grep -rniE 'dsse|in-toto|slsa' over every .rs in /home/rickard/git/gunnar hits only doc comments. crates/gunnar-core/src/provenance.rs:12-25 says it outright: the field set is SLSA's so an export is "a rename and not a redesign", but the envelope is sshsig because "SLSA leans on DSSE and, in practice, on Sigstore's public trust root" and gunnar's locked decision is its own issuer and its own log. This is a deliberate choice, not an oversight — see §2. |
"published znippy-common 0.9.14/0.9.15 lacks dsse_verify" |
HALF WRONG — and this is the actionable one | MEASURED. znippy-common 0.9.14 on crates.io ships src/sign.rs with 0 occurrences of dsse_verify. 0.9.15 ships it. Registry index: "vers":"0.9.15" … "pubtime":"2026-09-09T22:55:23Z","yanked":false, with the full sign feature (cms, x509-cert, ed25519-dalek, p256, …). diff of the published 0.9.15/src/sign.rs against /home/rickard/git/znippy/znippy-common/src/sign.rs → IDENTICAL. |
| "holger path-deps znippy-common, so every reuse breaks the standalone-clone rule" | RIGHT in fact, WRONG in cause — and it is a one-line fix | MEASURED. holger/server/lib/Cargo.toml:181 is znippy-common = { version = "0.9.6", path = "../../../znippy/znippy-common", optional = true }. But the workspace-level [patch.crates-io] for znippy was removed 2026-08-23 (holger/Cargo.toml:134) — only facett is still patched. The leaf path= and its comment at server/lib/Cargo.toml:173-178 ("needs znippy-common ≥ 0.9.15 on crates.io") are stale as of 2026-09-09. holger/update-bundle/Cargo.toml:42 and holger/traits/Cargo.toml:30 already take znippy-common from the registry with no path at all. |
"holger re-implements DSSE verification in attest.rs (1 458 lines)" |
WRONG | MEASURED. attest.rs is 1 458 lines, but server/lib/src/attest.rs:817-823 says "Delegates WHOLLY to znippy_common::sign::dsse_verify — the PAE, the algorithm dispatch off the signer certificate's SPKI and the chain walk are all already there", and :828 is use znippy_common::sign::{dsse_verify, CertStore, DsseEnvelope};. The 1 458 lines are the evidence store, the enrolment policy and the in-toto Statement parser, not a second DSSE. There is exactly one DSSE implementation in the house. |
One more correction to the brief's own inventory: the WebAuthn count is five, not four (§2).
1. THE MAP
One row per repo. — means the repo does not do that thing.
| Repo | Signs what | Algorithm | Envelope | Trust keyed on | Verifies? default-on? |
znippy (znippy-common) |
any payload; archive root digest | ed25519 or ECDSA P-256, dispatched off the signer cert's SPKI (sign.rs:1370-1372) |
DSSE v1, byte-exact PAE (sign.rs:1190), payloadType free; and detached CMS SignedData RFC 5652 + M-of-N SignatureSet (sign.rs:1045 sign_multi, :1090 verify_threshold) |
X.509 cert SHA-256 fingerprint — it is literally the DSSE keyid (sign.rs:1360-1361) |
dsse_verify sign.rs:1375, verify_threshold :1090. OFF by default — znippy-common/Cargo.toml default = ["openzl"]; sign is opt-in |
edda (nornir-build-thing) |
nothing — produces the statement + canonical bytes + SHA-256 digest; signing is a trait seam | declares "ed25519"/"p256" as strings (provenance.rs:376,396) |
in-toto Statement v1 provenance.rs:426 + SLSA Provenance v1 :428; DSSE_PAYLOAD_TYPE_INTOTO = "application/vnd.in-toto+json" :430 |
signer_cn + keyid = X.509 fingerprint hex (:378,398,403-405) |
no verifier in this crate. ProvenanceSealer :412 / DsseSealer :422 are the seams; the only impl in edda is a test fake (image_build.rs:1834). Published 0.1.18, matches local (MEASURED against the crates.io index cache) |
edda (nornir-auth-webauthn) |
— (verifier only) | ES256 / P-256 only; RS256 and EdDSA refused (pure.rs:311, :115) |
WebAuthn assertion: authData ‖ SHA256(clientData), DER ECDSA |
per-credential raw P-256 pubkey (x,y) | pure.rs:471 verify_es256, :359 verify_assertion. ON by default (default = ["webauthn-pure"]). NOT on crates.io (MEASURED: absent from the registry index) |
edda (korp-installer) |
(a) app payload's blake3 root; (b) installed layout config; (c) UEFI PEs | (a)(b) ed25519; (c) RSA+SHA-256 PKCS#7 Authenticode; (d) verifies ECDSA P-256 TPM quotes | (a) bespoke KORPSIG1 = 8 magic ‖ 8 BE version ‖ 64 sig (rollback.rs:105-116), domain korp.root-of-trust.v1\0 (:100); (b) detached ed25519, domain korp.layout-config.v1\0 (layout.rs:783); (c) Authenticode SpcIndirectDataContent; (d) TPMS_ATTEST |
(a)(b) raw 32-byte ed25519 pubkey at /boot/korp/root.pub; (c) customer X.509 in UEFI PK/KEK/db; (d) TPM AK |
YES and FAIL-CLOSED. cmdline.rs:78-89 SignaturePolicy::#[default] RequiredByDefault; the only off-switch korp.require_signed=0 is honoured only when baked into the Authenticode-signed first stage (korp-start.rs:302-316). Verified at install (install.rs:784) and every boot (korp-start.rs:288-296). secure-boot feature is OFF by default |
| nornir | the build-provenance record digest, sealed beside the artifact | ed25519 (default) or P-256, via znippy_common::sign::ArchiveSigner |
CMS (ProvenanceMode::#[default] Cms, seal.rs:104-123) or DSSE/in-toto (seal.rs:87-102); archive <artifact>.build-provenance.znippy (seal.rs:238) |
X.509 chain to trusted DER roots → SignerId.common_name |
seal.rs:145 verify, :180 verify_dsse; a CMS verifier refuses a DSSE seal and vice versa (:154,:184). OFF by default — build-provenance not in Cargo.toml:66 default; --seal also opt-in. ⚠ the shipped CLI signs with ZnippySealer::dev() (src/bin/nornir.rs:6003,6031) which mints an in-memory CA and discards the root (seal.rs:58-66) — the signature chains to nothing |
| modgunn | — (verifier only) | reuses znippy-common/sign |
opens <artifact>.build-provenance.znippy, verifies the detached CMS seal |
X.509, trusted DER roots | provenance.rs:700 verify_sealed_provenance. ON by default — the Cargo comment at :54-64 is the lie. Cargo.toml:18 is default = ["scan", "provenance-verify"] and the comment at :11-16 explains why it rides in default. The :54-64 feature doc still says "OFF by default"; it is stale |
holger (server/lib/attest.rs) |
files + verifies in-toto attestations | delegated to znippy (SPKI dispatch) | DSSE v1 payloadType pinned to application/vnd.in-toto+json (:101, gate at :642-649); statement :95; also vulns/v0.1 :113, test-result/v0.1 :116; stored …/<predicate>-<envelope>.dsse.json (:71) |
X.509 cert SHA-256 fingerprint, must be enrolled; empty keyid refused (:670-675), unenrolled keyid refused (:677-681) |
YES. OFF by default — sign is not in server/lib/Cargo.toml:304 default; without it both entry points return VerificationUnavailable (:849-864) |
holger (update-bundle) |
the release manifest, transitively every member | ed25519 only; non-ed25519 signer cert refused (signer.rs:40) |
holger.update-bundle/v1 (schema.rs:4) — signatures/release-manifest.json + detached sigs + approval statements |
X.509 cert SHA-256 fingerprint, never the role string (approval.rs:3-5, policy.rs:225-229) |
YES, fail-closed. All six TUF-without-TUF properties CONFIRMED — see §1a |
| gunnar | commits, push events, tree heads, build records, membership, webhooks, RPC credentials | ed25519 over SSHSIG (ssh-key, HashAlg::Sha512) |
sshsig, seven namespaces — git (gunnar-policy/src/signature.rs:53), gunnar-forge (forge.rs:61), gunnar-transparency (transparency.rs:50), gunnar-provenance (provenance.rs:44), gunnar-membership (membership.rs:47), gunnar-webhook (webhook.rs:47), gunnar-key-attestation (key_attestation.rs:39); plus the RPC credential scheme gunnar-sshsig-v2 (gunnar-grpc/src/auth.rs:164) |
SSH key SHA-256 fingerprint | YES, and one implementation for all of it — gunnar-core/src/lib.rs:133: "One signer and one verifier for every signed event log (one implementation)". BuildRecord carries SLSA's field set (provenance.rs:60+) in an sshsig envelope |
gunnar (gunnar-clock) |
a time reply: nonce ‖ unix_ms |
ed25519 over SSHSIG | SSHSIG PEM, namespace gunnar-time-v1@gunnar.rs (peer.rs:68), magic GUNNAR-TIME/1 (:66) |
SSH pubkey from the kernel command line (GUNNAR_CLOCK_KEY), never the writable key table (peer.rs:50-56) |
peer.rs:193 verify_reply. OFF by default (default = []) |
| monetize | entitlement facts, snapshots, go-aheads, actor tickets | ed25519 (ed25519_dalek) |
signature inside the object — canonical sorted-key JSON with the sig fields dropped (monetize-embed/src/signing.rs:84-111), sig in EntitlementFact.signature (monetize-product/src/lib.rs:453) |
raw 32-byte ed25519 pubkey (monetize-embed/src/lib.rs:123) |
YES. ON by default and no off switch — Entitlements cannot be built without a verifying key; monetize-embed/Cargo.toml has no [features] at all |
| monetize-impl | — | HMAC-SHA256 (Stripe webhooks) | Stripe's own header | Stripe webhook secret | monetize-pay-impl/src/stripe/mod.rs:1052. External protocol, correct to implement |
| Skidbladnir | the airgap bundle's root_sha256 (ASCII bytes) |
ed25519, verify_strict |
detached JSON sidecar <bundle>.sig.json = {root_sha256, signature_hex, signer_pubkey_hex, ca_cert_pem?} (archive.rs:428-438) |
raw 32-byte ed25519 pubkey, pinned out-of-band (TrustRoot.ca_pubkey_hex); the embedded X.509 PEM is carried and explicitly not trusted (pki.rs:352-355) |
fetch.rs:66, pki.rs:359. ON by default (default = ["pki"]). ⚠ caveat: authenticity is only checked if the caller passes a trust_root; None → signature_ok = None, integrity only (fetch.rs:144) |
| dwarves | nothing. The release path publishes to crates.io unsigned |
— | build receipt is an ELF section .nordisk.buildthing, read back by --attest — the type disclaims identity: "an attestation, not a signature — it says two files carry the same program, never who made them" (edda history.rs:267) |
— | verifies hash equality only; --allow-unattested waives it (xtask/src/build_history.rs:88). Gate B (release_doctor/gate_b.rs) is a publish rehearsal, zero cryptography — and a build without --features rehearse now refuses --execute (release_doctor/mod.rs:412) |
| tunnr | nothing. MEASURED: no ed25519, no CMS, no DSSE, no signature verification anywhere | sha256/blake3 pins only (kernel.rs:78 TARBALL_SHA256, :261 TOOLCHAIN_IMAGE_SHA256) |
its build seal is edda's nornir-build-thing Attestation — a digest binding (digest_matches_record(), tunnr-build/tests/vm_boot.rs:101,105), unsigned |
— | the PID-1 reads an UNSIGNED \korp\cmdline off the ESP, said so in four places (payloads/wg-appliance/src/main.rs:41,60,612,626; Cargo.toml:36). Secure Boot for the appliance is DESIGNED (.nornir/boot-chain-secure-boot-design.md) and implemented in korp-installer, not here |
| korp | nothing. KORPSIG1 is not here — it is korp-installer's |
— | — | — | consumes korp-installer's chain only in a dev-dependency e2e test |
| skade | nothing signed. skade-flight's sign() is AEAD sealing, not a signature |
XChaCha20-Poly1305, key = SHA-256 of a domain string + the bearer secret | bespoke: 1 B version ‖ 24 B nonce ‖ AEAD ct+tag | symmetric shared bearer secret | the AEAD tag decides before parsing; always on, no bypass found |
| znippy-zoomies | none found (only cast_sign_loss lints) |
— | — | — | — |
| draupnir | none found (zero hits for ed25519/dsse/cms/sign_/verify_signature) | — | — | — | — |
| facett | none found — sign_in/sign_out is OIDC login UI; facett-core self.sig is a layout hash |
— | — | — | — |
1a. holger.update-bundle/v1 — TUF's semantics without TUF, all six confirmed
Every property the brief listed is MEASURED true. This is the strongest link in the house.
| Property | file:line |
| dual sha256 + blake3 per member | manifest.rs:25-26 fields, :55-56 built, :103-113 both checked |
| rejects unreferenced members | manifest.rs:96 UnreferencedMember; also container.rs:299, verify.rs:437,636 |
| rejects missing members | manifest.rs:119 MissingMember |
| approver roles keyed by cert fingerprint, never the self-asserted role string | approval.rs:3-5, :27, policy.rs:66-71, :225-229 authorizes_role, verify.rs:164-165 |
| two-person rule | verify.rs:300 enforce_two_person, distinct count :308-313, release signer must be distinct :315-322, per-role distinct-cert coverage :325-345 |
| monotonic anti-rollback sequence | core.rs:20-22, rollback.rs:76-82 SequenceRollback, :84-88 SequenceReplay |
| verify before decompress | container.rs:219-222, verify.rs:482-484. ⚠ measured caveat: the control plane itself is decompressed before authentication (container.rs:131-144, said so plainly), bounded by MAX_MEMBERS / MAX_MEMBER_BYTES / MAX_EXPANSION_RATIO 100:1 (schema.rs:27-88) |
2. Är det en röra? — the verdict
Ja, delvis — men mindre än fruktat, och röran sitter inte där man trodde.
The signing primitives are in good shape. The identity vocabularies and the WebAuthn copies are the röra. Counts, all MEASURED:
| Count | Detail | |
| Signature envelopes in use | 13 | Standards adopted (2): DSSE v1, CMS/RFC 5652. House-invented (6): KORPSIG1, korp.layout-config.v1, holger.update-bundle/v1, gunnar-sshsig-v2, monetize's inline-signature-in-canonical-JSON, Skidbladnir's .sig.json. Standards we implement because someone else defined them (4): sshsig, Authenticode PKCS#7, TPMS_ATTEST, Stripe HMAC. Not a signature but often counted (1): skade-flight's AEAD handle. |
| DSSE implementations | 1 | znippy-common/src/sign.rs:1190/1347/1375. holger delegates (attest.rs:817-823), modgunn delegates, nornir delegates, edda declares the seam. This is the house's best-behaved primitive. |
| Independent ed25519 detached sign/verify call sites | 5 | znippy-common/sign.rs, holger/update-bundle/src/signer.rs:149-172, Skidbladnir/src/pki.rs:163/294, korp-installer/src/rootkey.rs:177 + trust.rs:59, monetize-embed/src/signing.rs:123. Each with its own domain separation and its own key format. |
| WebAuthn ES256 verifiers | 5 (the brief said four) | garmr crates/garmr-cli/src/api/passkey.rs:528; gunnar-ui crates/gunnar-ui-server/src/auth/mod.rs:1390; holger-ui crates/holger-ui-server/src/auth/mod.rs:1264; monetize crates/monetize-ui-server/src/auth.rs:431; edda nornir-auth-webauthn/src/pure.rs:471 — the shared one, used by none of the other four (zero hits for nornir-auth-webauthn in all four repos). gunnar-ui's and holger-ui's bodies are byte-identical, doc comment included. And they are on different p256 majors: garmr 0.14, gunnar-ui 0.13, holger-ui 0.13. |
| Identity vocabularies (trust stores a customer must hold) | 5 | 1. X.509 cert SHA-256 fingerprint (znippy, nornir, modgunn, holger×2). 2. SSH key SHA-256 fingerprint (gunnar's seven namespaces, gunnar-clock). 3. Raw 32-byte ed25519 pubkey (Skidbladnir, monetize, korp-installer). 4. Per-credential raw P-256 pubkey (WebAuthn ×5). 5. X.509 in UEFI NVRAM PK/KEK/db (Secure Boot). The brief said three; three is the count for artifact trust, five for everything a customer actually enrols. |
| Whole vendored copies of a nordisk repo | 5, in one repo | garmr/vendor/ contains edda, facett, skade, znippy, znippy-zoomies — including a second znippy-common/src/sign.rs (garmr/vendor/znippy/znippy-common/src/sign.rs:488). That is a sixth crypto copy nobody counted. |
What is ACCIDENTAL (collapse it)
- Five WebAuthn verifiers. No design reason. Two are byte-identical. edda already owns the shared crate. Pure accident of hand-porting. Collapse.
- modgunn's default-on verifier that holger refuses. modgunn
default = ["scan", "provenance-verify"]compiles a real verifier in; holger pinsdefault-features = false, features = ["scan"](server/lib/Cargo.toml:137) and then builds its own filing/enrolment layer inattest.rs. Two gates, one job. Decide which. - Three raw-ed25519-pubkey trust stores (Skidbladnir, monetize, korp-installer) that could all be one cert-fingerprint store, or at minimum one crate. Collapse the crate, keep the stores separate — see below for why keeping them separate is right.
- garmr's five vendored nordisk repos. Collapse to registry deps.
- holger's
path=on znippy-common — obsolete since 2026-09-09. One line. - modgunn's stale
:54-64comment claimingprovenance-verifyis off. Delete it.
What is DELIBERATE and correct (leave it)
- gunnar's sshsig, not DSSE.
provenance.rs:17-25states the reason: DSSE in practice means Sigstore's public trust root, and gunnar's locked decision is its own issuer and its own log so an on-prem install verifies with no third party. A customer can check a gunnar record withssh-keygen -Y verifyand no gunnar at all. Correct. The field set is already SLSA's, so the export is a rename — see §3 link 4. - gunnar's seven sshsig namespaces. That is domain separation, not duplication — one implementation, seven domains, and a signature under one namespace cannot be replayed as another (
provenance.rs:39-43). Correct and exemplary. KORPSIG1and the layout-config domain. Both are 62–80-byte fixed binary records parsed by length-then-magic with no trial verification (rollback.rs:194-226) in a PID-1 that runs before any allocator or parser you would want. A JSON DSSE envelope at that point in the boot is the wrong tool. Correct.holger.update-bundle/v1. It is not a signature envelope competing with DSSE; it is a release policy format (two-person rule, roles, anti-rollback, member map). DSSE has none of that. Correct — and it should become the house's promotion format.- CMS alongside DSSE in znippy. CMS is what an enterprise/government PKI verifier already reads; DSSE is what
cosign/slsa-verifier/Rekor read. Both, from one key, is the point (sign.rs:1345). Correct. - Skidbladnir pinning a raw pubkey and ignoring the bundled X.509. Across an airgap with no OCSP, no CRL and an untrustworthy clock, a pinned key is stronger than a chain. Correct — and it is why §4 proposes sharing the crate, not the store.
- skade's AEAD handle. It is a bearer token, not an attestation. Correct.
So: KORPSIG1 and update-bundle/v1 are not the röra. The röra is five WebAuthn verifiers, five vendored repos, two provenance gates, and a stale path=.
3. THE ONE CHAIN — ax till limpa
What a customer verifies, end to end. ⚠ = a missing or broken link today.
| # | Link | Signed object | By whom | Envelope | Verified by | Crate that owns it |
| 1 | Source: the commit | git commit | developer's ssh key | sshsig, namespace git |
gunnar-server on push | gunnar-policy (src/signature.rs:53) |
| 2 | Push into the forge | push event | the forge | sshsig, gunnar-forge |
gunnar-core | gunnar-core::forge (forge.rs:61) |
| 3 | Transparency | signed tree head (Merkle) | the log | sshsig, gunnar-transparency |
any client, offline | gunnar-core::transparency (transparency.rs:50) |
| 4 | Build → artifact (gunnar's half) | BuildRecord (SLSA field set: builder principal, source, recipe, materials, subjects) |
gunnar-server's runner | sshsig, gunnar-provenance |
⚠ nobody downstream. MEASURED: BuildRecord is signed at gunnar-server/src/runner.rs:378,420 and consumed only inside gunnar and its own tests |
gunnar-core::provenance |
| 4b | Build → artifact (the nordisk half) | in-toto Statement v1 + SLSA Provenance v1 over the produced digests | nornir's --seal |
DSSE or CMS, in <artifact>.build-provenance.znippy |
modgunn (provenance.rs:700, on by default) and holger attest.rs |
edda/nornir-build-thing (statement) + znippy-common/sign (signature) + nornir/src/build/seal.rs (the seal) |
| — | ⚠ link 4 ↔ 4b do not join. gunnar signs SLSA's fields in sshsig; the house verifies SLSA's statement in DSSE. gunnar's own comment calls the export "a rename and not a redesign" — nobody has written the rename. | |||||
| — | ⚠ link 4b signs with a throw-away CA. nornir.rs:6003,6031 call ZnippySealer::dev(), which mints an in-memory P-256 CA + ed25519 leaf and drops the root. A verifier has no root to chain to. This is the single biggest hole in the chain. |
|||||
| — | ⚠ dwarves — the path that actually publishes — signs nothing. A crate reaching crates.io via dwarves release carries no attestation of any kind. Gate B proves it builds, not who built it. |
|||||
| 5 | Artifact filed in the repository | the DSSE envelope, byte-identical to what was signed | — (verifier) | DSSE, payloadType pinned, keyid must be enrolled |
holger | holger-server-lib::attest ⚠ behind the non-default sign feature, and its znippy-common is still a path= |
| 6 | Policy gate | — | — | — | modgunn verify_sealed_provenance |
modgunn ⚠ holger pins features=["scan"] so this never compiles in holger |
| 7 | Promotion (the release decision) | signatures/release-manifest.json, transitively every member |
two distinct approvers + a distinct release signer, all by cert fingerprint | holger.update-bundle/v1 |
holger, fail-closed, before decompression | holger-update-bundle ✅ the healthiest link |
| 8 | On-prem import across the airgap | the bundle's root_sha256 |
the machine CA | .sig.json sidecar |
Skidbladnir verify_on_receipt |
Skidbladnir::pki ⚠ a different trust store from link 7 (raw pubkey vs cert fingerprint); ⚠ trust_root: None silently degrades to integrity-only |
| 9 | Install / boot | app payload blake3 root; layout config; the UEFI PEs | the estate's root key; the customer's PK | KORPSIG1; korp.layout-config.v1; Authenticode |
korp-installer at install and every boot, fail-closed by default | edda/korp-installer ⚠ a third trust store (raw pubkey at /boot/korp/root.pub) |
| 10 | Runtime entitlement | EntitlementFact, Snapshot, ActorTicket |
monetize | signature inside the canonical-JSON object | monetize-embed, no off switch | monetize-embed::signing ⚠ a fourth trust store (raw ed25519 pubkey) |
| 11 | Trusted time under all of it | nonce ‖ unix_ms |
the FRONT, never the internet | sshsig, gunnar-time-v1@gunnar.rs |
gunnar-server | gunnar-clock ⚠ exists, works, and nothing outside gunnar uses it — see §5 |
Links that do not exist today, in the order they hurt:
- A real signing key for link 4b. Until
nornir --sealusesfrom_pkcs8with persistent key material and a published root, every provenance signature in the house verifies against a CA that no longer exists.ZnippySealer::from_pkcs8already exists (seal.rs:33-48) — the CLI just does not call it. - The link-4 → link-4b rename. gunnar's
BuildRecord→nornir_build_thing'sto_slsa_statement(). Both field sets are SLSA's. Nothing joins them. - A signature on the dwarves publish path.
- One trust store, or a stated reason for four. A customer today enrols a cert fingerprint (holger), an ssh fingerprint (gunnar), and three separate raw ed25519 keys (Skidbladnir, korp-installer, monetize).
- Trusted time in the verifiers. holger's own
signer.rs:86-100says it plainly: "on the far side of an airgapnowis the weakest input in the system: a flat RTC battery hands the verifier 1970 … there is no NTP to correct either." And holger still callsUtc::now()(api.rs:31,45;verify.rs:495).
4. THE HOME — what goes in edda, and what must be published
The rule: gunnar and holger both depend on edda; neither depends on the other. edda exists for exactly this. The standalone-clone rule is real — it already forced holger-front to crates.io.
Already solved, nothing to publish
| Crate | Status | Action |
znippy-common (DSSE + CMS + M-of-N + cert fingerprints) |
0.9.15 on crates.io since 2026-09-09, byte-identical to local, sign feature present |
holger/server/lib/Cargo.toml:181 → drop path=, bump to version = "0.9.15". One line. Delete the stale comment at :173-178. This closes the whole "reuse znippy's DSSE breaks the clone rule" objection. |
edda/nornir-build-thing (in-toto + SLSA + the sealer seams) |
0.1.18 published, matches local | nothing. It is already the home for the statement half. |
Must be published for the collapse to happen without path deps
| # | Crate | Where it should live | Why | Publish via |
| 1 | nornir-auth-webauthn 0.1.2 |
already in edda; NOT on crates.io (MEASURED: absent from the registry index) | the single blocker to deleting four hand-rolled ES256 verifiers in garmr, gunnar-ui, holger-ui and monetize-ui-server | dwarves release |
| 2 | modgunn 0.0.6 |
its own repo, fine | holger path-deps it (server/lib/Cargo.toml:137), so holger cannot be cloned standalone today |
dwarves release |
| 3 | facett |
its own repo | the last remaining [patch.crates-io] in holger (holger/Cargo.toml:116) — local 0.1.16 vs registry 0.1.15. While it stands, 19 holger crates are fork-riders |
dwarves release |
| 4 | a new nordisk-clock (from gunnar-clock) |
edda | §5 | dwarves release |
| 5 | (optional, later) split znippy-sign out of znippy-common |
edda or znippy | znippy-common drags arrow + openzl for a consumer that wants only PAE + a cert fingerprint. Not urgent — default-features = false already makes it tolerable |
dwarves release |
Publishing mechanics, measured today: the subcommand is dwarves release, not crate-release. ~/.cargo/bin/dwarves is built without --features rehearse, so Gate B is compiled out; the rehearse build is /home/rickard/cargo-target/release/dwarves. Use that one. Gate B earned its keep today — it caught holger-errcode 0.1.1 being four rows short of its source before upload. A dwarves built without rehearse now refuses --execute outright (release_doctor/mod.rs:412), which is the right behaviour.
What should NOT be collapsed
Do not merge the trust stores. Skidbladnir's pinned raw key is correct for an airgap; korp-installer's /boot/korp/root.pub is correct for a PID-1 with no allocator; holger's cert fingerprints are correct for a two-person release board. Share the crate (one ed25519 detached sign/verify with domain separation, in edda), keep the stores.
5. gunnar-clock — yes, it belongs in the shared home
MEASURED:
- Zero gunnar dependencies. The whole 53-line manifest's
[dependencies]isssh-key,rand_core,ureq,rustix,libc(gunnar/crates/gunnar-clock/Cargo.toml:41-53). The onlygunnar-strings in it are prose and the[[bin]]name. publish = falsepurely by inheritance.Cargo.toml:8ispublish.workspace = true, resolving togunnar/Cargo.toml:117 publish = false— a workspace-wide guard that exists to stopcargo publish -p gunnar-serverfrom putting the appliance's server half on crates.io. gunnar-clock is caught by a rule aimed at something else.gunnar-forgealready shows the override pattern (explicitpublish = true).- It solves the exact problem holger has. Signed
peer://time (peer.rs:180), a 32-byte nonce against replay (:72), a round-trip bound so a stale answer cannot be reused (:83), skew refusal that triggers resync, and the key comes from the kernel command line — baked by the ISO build, changed only by a re-image — never from the network-writable key table (peer.rs:50-56). It syncs from the FRONT, never the internet, so it stays airgap-safe. - holger trusts the RTC for every expiry check —
update-bundle/src/api.rs:31,45,verify.rs:495all default toUtc::now(); certificate windows atsigner.rs:105-118; bundle expiry and max-age atverify.rs:671-682. And holger's own comment (signer.rs:86-100) says the clock is the weakest input in the system. - The verify API already takes
nowas an explicit parameter. The seam is already there. Anordisk-clockin edda drops straight into it.
Recommendation: move gunnar-clock to edda/crates/ as nordisk-clock (edda already hosts gunnar-bitmap, gunnar-delta, gunnar-pack, gunnar-pktline, gunnar-sendpack, gunnar-stage, so a gunnar-born crate in edda is the established pattern), set publish = true, and publish it. gunnar depends on edda; holger depends on edda; neither on the other. Keep default = [] so a consumer that only wants the report pays for no socket and no crypto.
6. What needs the owner
- Ruling: one provenance gate or two? modgunn ships a default-on verifier; holger pins
scanand files attestations itself. Both are real, neither is wrong, and today neither runs in a shipped holger (holger'ssignis not indefault). - Ruling: does gunnar export its
BuildRecordto DSSE/in-toto? gunnar's own comment says it is a rename. Doing it joins gunnar's chain to the house's. Not doing it means gunnar's build provenance stops at gunnar's edge — which may be the correct answer for the airgap product. - Go-ahead to publish four crates (
nornir-auth-webauthn,modgunn,facett,nordisk-clock). The standing go for "publish what a build needs" probably covers this, butfacettand a crate move are more than a version bump. - Decision on the throw-away CA.
nornir --sealsigning withZnippySealer::dev()is either a known placeholder awaiting a key store, or a bug. It reads as a placeholder; it ships as a signature nobody can verify. - Decision on tunnr. tunnr signs and verifies nothing; its appliance PID-1 trusts an unsigned ESP cmdline by design, with Secure Boot designed here but implemented in korp-installer. Is that the settled shape, or a gap?