Skip to content

ARCHITECTURE · 20

Encryption.

Encryption is per-image opt-in and never a default; it is a stacking transform like COW rather than a format. Reads decrypt on demand and in memory only, and plaintext blocks never touch disk. One artifact can serve N audiences, with each key opening exactly its declared slice.

Note

EncBackend plus tfs encrypt / decrypt / mount are shipped at SUITE-1, with per-block AES-256-GCM, PKESK envelopes, and the envelopes.yaml grant manifest.

The PQC suites (construction locked), metadata hiding, and merkle verify-on-read are planned.

The model: a transform, not a format

The model is EncBackend { base: dyn Backend, key_source }: it wraps any backend and answers reads by decrypting on demand. Per the transforms law it lives only in the Rust TFS, and backends and dwarfs-t stay ignorant of it. The primitive is OpenPGP via rnp-rs (AES-256/SHA-2, botan backend, vendored), applied after compression, since ciphertext does not compress and individually compressed blocks are individually encryptable. Optional padding blurs per-block sizes.

COW  →  ENC  →  format backend        # the one pinned stack
COW(Enc(base))   # the writable sealed-base mount — the declarative spelling
Enc(COW(base))   # expressible at the mount API, never the declarative spelling —
                 # Enc is a decrypting READ view; no write can pass through it

The key model: session keys are grant envelopes

Data keys (data encryption keys, DEKs) encrypt content blocks, and a DEK is a persisted OpenPGP session key: rnp’s native wrap/unwrap (PKESK packets to N recipients) is the envelope. No custom crypto exists anywhere. Sharing is wrapping the DEK to a recipient, offline, accountless, and auditable. A wrapped DEK plus the manifest digest is a capability: possessing it grants exactly that subtree and nothing else.

identity lives at the PLAINTEXT layer — manifests carry TWO digests:
tree hash (plaintext)   → semantic identity: CAS, dedup, sign/verify,
audit-without-access
ciphertext blob hash    → transport identity
encryption is a per-audience transform; CAS never keys content-identity
to any one wrapping
hierarchical derivation: K(/a/b) = HKDF(root DEK, info="/a/b")
grants /a/b/** but not /a/c or /a (one-way) —
sharing a directory subtree = handing over its derived key
revocation is prospective: re-issue the manifest without the recipient,
or rotate the DEK for FUTURE versions — you cannot un-read what was
read (documented honestly)

The composition rules are the following. A JOIN of layers with different DEKs is either authorized re-encryption under a new joined key or preserved per-layer key domains in the joined manifest (lazy join). Encapsulated images keep independent key domains, so the outer may be readable while the inner stays sealed (transport versus payload audiences). Per-directory recipient mapping in the manifest gives selective disclosure: one artifact serves N audiences, and each key opens only its slice. The in-image /tpkg/envelopes.yaml is the grant manifest against which the mount binds; a read of a sealed path outside every opened grant answers ENOKEY, never garbage. The memory discipline is that decrypted blocks live in mlock’d heap, zeroized on free, and keys come via agent, passphrase, or PKCS#11, never written to disk and never in swap.

PQC readiness: agility from day one

Every manifest, envelope, and signature carries a cipher-suite id from a versioned registry; nothing is hardcoded, and unknown ids fail with a named error, never a guess. Signatures are the migration surface and envelopes the confidentiality surface; both are suite-versioned, so migration is additive.

SUITE CONSTRUCTION NOTE

SUITE-1

X25519 + Ed25519 + AES-256-GCM + SHA-256

Classical; shipped.

SUITE-2

ML-KEM-1024+X25519 composite KEM · ML-DSA-87+Ed25519 composite signatures · AES-256-GCM · SHA-384

Hybrid: secure if either family holds; the transition form.

SUITE-3

SLH-DSA hash-based signatures

The maximal-conservatism tier.

Under the harvest-now-decrypt-later (HNDL) threat, long-lived encrypted images SHOULD use hybrid envelopes now. AES-256 keeps roughly 128-bit post-Grover strength, and the merkle/hash layer is PQC-safe by construction; the asymmetric pair is what Shor’s algorithm threatens, and that is exactly what the suites version. Recipients may hold classical and PQC keys, and multi-recipient envelopes are native.

One crypto home per layer

LimniFS brings format-native cryptography: per-drop AEAD and Ed25519 over the BLAKE3 manifest root. The consolidation decision draws the boundary once: format-native crypto is consumed as evidence, never as anchor.

QUESTION HOME NOTE

who signed it

the trust-and-signing layer — OpenPGP trailer signature + signed release indexes + the trust store

It is the only authenticity decision point, for every backend.

who may read it

the encryption layer — the ENC transform (PKESK DEK envelopes, HKDF subtree keys)

It stacks over any backend, limnifs included.

how the bytes are sealed

the format’s own business (limnifs per-drop AEAD)

It is consumed as evidence, never as anchor — tebako-pressed images ship aead = 0.

Concretely, limnifs Ed25519 keys never enter the key or trust stores, and a slice author manages exactly one keypair, the author’s OpenPGP key. Mounting a self-encrypted limnifs image (aead ≠ 0) fails the named ENOTSUP, which is the v2.x posture rather than a stopgap. An embedded signature bundle may later be verified as non-authoritative integrity evidence, only after the signing chain has decided authenticity and only against a key that the OpenPGP-signed metadata binds to the author identity; absence is never an error, and a mismatch is a loud warning plus a journal entry, never the trust decision. Admission of any format-native envelope into the grant path requires the full PQC gate list (ML-DSA signing, hybrid ML-KEM envelopes, explicit registry ids, and external audit posture), which is recorded and gated but not committed.

The CLI surface and why it matters

The CLI surface comprises tfs encrypt (the root grant --recipient and subtree grants --subtree <path>=<pubkey>; --rewrap rotates grants to a new recipient set with the bulk ciphertext byte-identical), tfs decrypt (plaintext to a tar stream, never a staging tree), and tfs mount --key (the unlock/grant report). A wrong key is a named EKEY-class error, never garbage. The operational algebra holds that object security beats channel security: artifacts self-protect over any transport or mirror, disk snapshots and caches hold ciphertext only, and destroying a data key (or re-issuing the manifest without a recipient) is crypto-shredding, which leaves the content unrecoverable everywhere, provably, with no mirror-purge problem. The honest risks, stated, are access-pattern side channels on serve, key-agent UX as the hard part, and mlock/zeroize as the baseline rather than a panacea.