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 chain, link by link.
Root of trust
PLANNEDOne 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.
Production (releases)
PLANNEDEvery 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.
tpkg trailer v2
SHIPPEDThe 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.
Press-time verification
SHIPPEDtebako-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.
Bundle-time signing — OPT-IN
SHIPPEDtebako-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.
First-run verification
SHIPPEDThe 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.
Fail closed everywhere
SHIPPEDNo insecure-skip flag exists. Verification of signed packages is always strict; only the presence of a signature is optional.
The v1-legacy rule
SHIPPEDUnsigned 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/tpkgv2 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-signercrates/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:
| CODE | MEANING | SINCE |
|---|---|---|
| 65 | manifest missing, corrupt, or structurally invalid | v1 |
| 66 | launcher ABI mismatch (package requires newer bootstrap) | v1 |
| 67 | runtime_ref missing or unparsable | v1 |
| 69 | runtime unavailable: not cached and unreachable / offline / lock timeout | v1 |
| 70 | SHA256 mismatch — downloaded or embedded payload deleted, cache untouched | v1 |
| 71 | signature verification failed — the package’s OpenPGP signature does not verify | v2 |
| 72 | trust error — the signer key is not in the trusted keyring | v2 |
| 74 | local 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 sideThe 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.