Skip to content

Commit deba613

Browse files
committed
encryption: X25519, HKDF-SHA384 and signed bundles — the sealed-channel primitives
W0 of the sealed-channel plan: the three pieces both tracks need, none of which commit us to the open decision about where the client key lives. kx.rs — X25519 key agreement. Ephemerals are single-use by TYPE (agreement consumes self), not by convention, because our feature set deliberately excludes the crate's own RNG path: entropy here comes only from crate::fill_random. Low-order peer keys are rejected via was_contributory() — without that check an attacker who can inject a public key forces both sides to derive a shared secret it already knows. RFC 7748 vector included. hkdf_sha384.rs — the key schedule. Separate c2s/s2c keys from one derivation: with a single key in both directions, a reflected record is indistinguishable from a genuine one. RFC 5869 publishes no SHA-384 vectors, so there is a real SHA-256 vector as a wiring check and a marked TODO rather than an invented one — a fabricated vector looks exactly like verification and is worse than none. bundle.rs — Ed25519-signed content bundles, the unit an "app" ships as. The signature covers header AND body, so kind/classid/version cannot be spliced between two legitimately signed bodies. The publisher key travels in the blob but is checked against a caller-supplied allowlist: a self-signed bundle is not valid, and carrying the key exists to make the failure legible ("unknown publisher") rather than silent. sign() overwrites the publisher field from the signing key, so a caller cannot mint a bundle that lies about its own signer. Fixed byte layout rather than signed JSON because JSON canonicalisation is a documented signature-bypass source: two parsers, two readings, one signature. wasm.rs — verify_bundle returns a VerifiedBundle with no constructor. The only route to a body is through the signature check, so "render whatever arrived" is not expressible rather than merely discouraged. An empty allowlist throws instead of quietly accepting anything. 54 tests green, clippy clean, wasm-bindings feature compiles. Generated by [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mwq1QKpw4zRd6oaGRoJhF2
1 parent 8aef362 commit deba613

7 files changed

Lines changed: 1197 additions & 0 deletions

File tree

‎Cargo.lock‎

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/encryption/Cargo.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ argon2 = { version = "0.5", default-features = false, features = ["alloc"] }
1818
chacha20poly1305 = { version = "0.10", default-features = false, features = ["alloc"] }
1919
ed25519-dalek = { version = "2", default-features = false, features = ["alloc", "zeroize"] }
2020
sha2 = { version = "0.10", default-features = false }
21+
# Key agreement + key schedule for the sealed channel (kx.rs / hkdf.rs).
22+
x25519-dalek = { version = "2", default-features = false, features = ["static_secrets", "zeroize"] }
23+
hkdf = { version = "0.12", default-features = false }
2124
zeroize = { version = "1", features = ["derive"] }
2225

2326
# THE ONLY ENTROPY SOURCE. On wasm32 the `js` feature routes this to

0 commit comments

Comments
 (0)