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
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.
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 |
|---|---|---|
|
X25519 + Ed25519 + AES-256-GCM + SHA-256 |
Classical; shipped. |
|
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. |
|
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.
See also: Declarative overlays · The chain of trust · Image formats.