Skip to content

ARCHITECTURE · 05

The chain of trust.

Package parts travel over mirrors and get shared between machines. How do we know they were not messed with? The answer is a chain: one root key, one signature mechanism, and verification at every hand-off — with honesty about what a checksum can and cannot prove.

STATED PLAINLY

crc32 in the tpkg trailer is not authenticity. It is an accident-integrity check — it catches a truncated download or a flipped byte, and nothing more. Likewise, HTTPS + a sha256 from the same channel as the download protects the transport, not the object: whoever swaps the artifact swaps the manifest too. Authenticity starts at the OpenPGP signature — anything short of it is a corruption check, and these docs call it that.

The flow, from release page to run.

Verification happens at fetch and install — never per run. What lands in the store carries its proof with it; what runs has already been believed, exactly once.

the release pagepayload-1.2.3.tfs+ .sha256 — the anchor+ .asc — opt-in signatureon the publisher's OWN hostfetchverify at installsha256 of the bytes vs theanchor; signature if presentmismatch → named error (70/71/72),nothing enters the storerenamethe store entry~/.tebako/payloads/…/1.2.3.tfs+ .sha256 sidecar = "verified"read-only 0444 · byte-identicalwith the published artifactevery runno re-verification —that is what makesstartup fastUNSIGNED IS FIRST-CLASS — AND LOUDan unsigned artifact installs and runs — with a loud stderr warningand a line in the audit journal, every time. Signing is per-packageopt-in; verification of anything signed is always strict.TEBAKO_REQUIRE_SIGNED=1 — unsigned fails closed (exit 71). Never the default.Trust survives redistribution: the anchor travels with the object, so a mirror, a CDN, or a USB stickis untrusted infrastructure — the verification never touches the network again.

The chain, link by link.

1

Root of trust

PLANNED

One signature mechanism for the whole project: OpenPGP via rnp/rnp-rs over librnp. The tamatebako release keypair’s root fingerprint is to be published on tebako.org and embedded in the artifacts; rotation happens via a signed successor-key statement, with a documented revocation procedure.

2

Production (releases)

PLANNED

Every released part — runtime packages, bootstrap, src tarballs, libtfs packages — ships a detached .asc per artifact plus a signed release manifest: one verify path for all. This is the release-side phase; the format and verifier below are ready for it.

3

tpkg trailer v2

SHIPPED

The trailer gains a per-slot sha256 array, the signer keyid, and an OpenPGP signature block over the canonical trailer bytes. crc32 stays as the cheap corruption check — documented as non-authentic. v1 trailers still parse; the version field stays 1.

4

Press-time verification

SHIPPED

tebako-cli verifies release signatures before using any part — fail closed. The trusted keyring lives in $TEBAKO_HOME/keyring/trusted.pgp; additional signer keys are TOFU-registered with a named prompt.

5

Bundle-time signing — OPT-IN

SHIPPED

tebako-pkg bundle --sign[=keyid] signs; without it the package carries no v2 extension at all — no key, no prompt, no ceremony. A press-local Ed25519 key is generated under $TEBAKO_HOME/keys only on the first explicit --sign, and auto-registered locally so dev iteration never produces or accepts unsigned artifacts.

6

First-run verification

SHIPPED

The bootstrap verifies the trailer signature against the keyring, then each slot’s sha256 before mounting or extracting it — one streaming pass at install time, with a trusted-cache marker avoiding re-hashing every run. Failures are named exit codes, never crashes.

7

Fail closed everywhere

SHIPPED

No insecure-skip flag exists. Verification of signed packages is always strict; only the presence of a signature is optional.

8

The v1-legacy rule

SHIPPED

Unsigned v1 packages exist in the wild and must not become un-runnable: a v2-verify bootstrap accepts them as LEGACY with a loud stderr warning (recorded in its audit journal), while v2-signed packages get full verification. Rejecting legacy is a separate explicit opt-in — TEBAKO_REQUIRE_SIGNED=1 — for hardened environments, never the default during transition.

"Shipped" here means implemented and tested in tebako-rs — the workspace's own release pipeline (the distribution channel) is still planned, so these binaries are not yet downloadable.

tpkg v2: the wire layout.

SHIPPED · crates/tpkg

v2 puts the integrity material between the slot table and the header, keeping the header exactly where v1 readers expect it — at EOF, version field still 1. Signing is flagged by TPKG_FLAG_SIGNED_V2 (bit 1 of package_flags), which old readers pass through untouched: v1-era runtimes keep reading v2 packages exactly as before; they just don't verify. The authoritative reference is crates/tpkg/src/lib.rs.

[payload][slot records][v2 extension][trailer header (166 B, at EOF)]

v2 extension — 268 B fixed + variable signature (all-new numerics big-endian):
  offset        size  field
       0        256   slot sha256 digests: 8 × 32 B — digest of slot i's bytes
                      at i*32; entries beyond slot_count are zeroed
     256          8   signer keyid (low 64 bits of the OpenPGP fingerprint, BE)
     264    sig_len   OpenPGP detached signature (binary packets)
264+sig_len      4    u32be signature length (1..TPKG_SIG_MAX)

canonical signed bytes = slot table ‖ digest array ‖ keyid ‖ trailer header
(everything except the signature and its length field)

Detection: parse the v1 header at EOF as always; if the signed flag is set, an extension of exactly 256 + 8 + sig_len + 4 bytes must fill the gap between the slot table and the header, with the digest tail zeroed. A flag-clear trailer has no extension and parses exactly as v1 — the legacy-unsigned case below.

The signer.

SHIPPED · crates/tebako-signer

crates/tebako-signer is the one OpenPGP half of the chain — rnp-rs over librnp, #![forbid(unsafe_code)]. It owns the press-local Ed25519 key (generated once per machine under $TEBAKO_HOME/keys), the trusted keyring (trusted.pgp, TOFU registration with named outcomes), and detached sign/verify that classifies into Trusted / Untrusted / Invalid — the outcomes the named trust errors map onto. Rewrite operations (insert-image / remove-image / set-runtime / reassemble) preserve the input's signing state.

Named failure exits.

SHIPPED · tebako-bootstrap (Rust)

Verification failures are named exit codes with full message bodies — never silent, never a crash. The v2 codes join the v1 set unchanged:

CODEMEANINGSINCE
65manifest missing, corrupt, or structurally invalidv1
66launcher ABI mismatch (package requires newer bootstrap)v1
67runtime_ref missing or unparsablev1
69runtime unavailable: not cached and unreachable / offline / lock timeoutv1
70SHA256 mismatch — downloaded or embedded payload deleted, cache untouchedv1
71signature verification failed — the package’s OpenPGP signature does not verifyv2
72trust error — the signer key is not in the trusted keyringv2
74local I/O failure (cache not writable, exec failed, …)v1

End-to-end behavior: a tampered runtime download is refused with the sha error; a tampered image slot names its sha256 mismatch at first run; an unregistered signer key is exit 72 — a trust error, not a crash.

What is still planned.

PLANNED · phase 2 — release side

The package-side machinery is shipped in tebako-rs; the release-side wrap is not: publication of the tamatebako root fingerprint on this site, detached .asc signatures per released artifact plus a signed release manifest, key rotation and revocation procedures, and signing of the runtime download manifest itself. Until then, the chain's release link rests on the sha256-verified release index — transport-checked, not yet object-signed.