feat: add post-quantum hybrid KEM + SQLCipher persistence

Feature 1 — Post-Quantum Hybrid KEM (X25519 + ML-KEM-768):
- Create hybrid_kem.rs with keygen, encrypt, decrypt + 11 unit tests
- Wire format: version(1) | x25519_eph_pk(32) | mlkem_ct(1088) | nonce(12) | ct
- Add uploadHybridKey/fetchHybridKey RPCs to node.capnp schema
- Server: hybrid key storage in FileBackedStore + RPC handlers
- Client: hybrid keypair in StoredState, auto-wrap/unwrap in send/recv/invite/join
- demo-group runs full hybrid PQ envelope round-trip

Feature 2 — SQLCipher Persistence:
- Extract Store trait from FileBackedStore API
- Create SqlStore (rusqlite + bundled-sqlcipher) with encrypted-at-rest SQLite
- Schema: key_packages, deliveries, hybrid_keys tables with indexes
- Server CLI: --store-backend=sql, --db-path, --db-key flags
- 5 unit tests for SqlStore (FIFO, round-trip, upsert, channel isolation)

Also includes: client lib.rs refactor, auth config, TOML config file support,
mdBook documentation, and various cleanups by user.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 08:07:48 +01:00
parent d1ddef4cea
commit f334ed3d43
81 changed files with 14502 additions and 2289 deletions

View File

@@ -0,0 +1,208 @@
# Protocol Stack
quicnprotochat layers three protocol stages to move a plaintext message from
sender to recipient with end-to-end encryption, typed RPC framing, and
authenticated transport. This page describes each layer, explains why both the
QUIC and Noise transport stacks exist, and provides a side-by-side comparison.
---
## Primary Stack (M3+): QUIC + TLS 1.3
Starting from milestone M3, the primary transport is QUIC over UDP with TLS 1.3
negotiated by `quinn` and `rustls`. Cap'n Proto RPC rides on a bidirectional
QUIC stream.
```text
┌─────────────────────────────────────────────┐
│ Application / MLS ciphertext │ <- group key ratchet (RFC 9420)
├─────────────────────────────────────────────┤
│ Cap'n Proto RPC │ <- typed, schema-versioned framing
├─────────────────────────────────────────────┤
│ QUIC + TLS 1.3 (quinn / rustls) │ <- mutual auth + transport secrecy
└─────────────────────────────────────────────┘
```
### What each layer provides
**QUIC + TLS 1.3** (`quinn`, `rustls`)
- Encrypted, authenticated transport with 0-RTT connection establishment
(where resumed).
- TLS 1.3 provides perfect forward secrecy per connection via ephemeral ECDHE.
- The server presents a self-signed certificate by default; the client pins
the server certificate via `--ca-cert`.
- ALPN protocol identifier: `capnp`.
- Multiplexed streams over a single UDP socket -- one bidirectional stream
per RPC session.
**Cap'n Proto RPC** (`capnp`, `capnp-rpc`)
- Zero-copy, schema-versioned serialisation.
- Asynchronous RPC with promise pipelining (multiple in-flight calls).
- The `NodeService` interface (defined in `schemas/node.capnp`) multiplexes
Authentication and Delivery operations on a single connection.
- The two-party VatNetwork runs over `tokio::io::compat` adapters wrapping
QUIC send/recv streams.
**MLS (RFC 9420)** (`openmls`, `openmls_rust_crypto`)
- Group key agreement with ratchet-tree-based key schedule.
- Forward secrecy: past messages remain confidential if a member's key is
compromised.
- Post-compromise security (PCS): the group heals after a compromise once an
honest update occurs.
- Identity binding: each member's Ed25519 public key is embedded in the MLS
`BasicCredential`.
- Ciphersuite: `MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519`.
---
## M1 Stack: Noise_XX over TCP
The original milestone-1 transport uses a Noise Protocol Framework handshake
directly over TCP. This stack is retained for environments where QUIC (UDP) is
blocked by middleboxes.
```text
TCP connection
└── Noise_XX handshake (snow)
└── Authenticated encrypted channel (ChaCha20-Poly1305)
└── [u32 frame_len LE][Cap'n Proto encoded message]
└── Cap'n Proto RPC (capnp-rpc)
```
### Layer details
**TCP**
- Reliable, ordered byte stream.
- No built-in encryption or authentication.
**Noise_XX** (`snow`)
- Pattern: `Noise_XX_25519_ChaChaPoly_BLAKE2s`.
- Three-message handshake that mutually authenticates both peers' static
X25519 keys:
```text
XX handshake (3 messages):
-> e (initiator sends ephemeral public key)
<- e, ee, s, es (responder: DH + static key)
-> s, se (initiator: static key + final DH)
```
- After the handshake, every frame is encrypted with ChaCha20-Poly1305 (AEAD)
using session keys derived from the Noise key schedule.
- Maximum Noise message size: 65,535 bytes.
**Length-Prefixed Codec** (`LengthPrefixedCodec` in `quicnprotochat-core`)
- Each frame is prefixed by a 4-byte little-endian `u32` length field.
- Little-endian was chosen for consistency with Cap'n Proto's segment table
encoding.
- Wire format:
```text
┌──────────────────────────┬──────────────────────────────────────┐
│ length (4 bytes, LE u32)│ payload (length bytes) │
└──────────────────────────┴──────────────────────────────────────┘
```
- Maximum payload size is `NOISE_MAX_MSG` (65,535 bytes), enforced on both
encode and decode.
- See [Length-Prefixed Framing Codec](../wire-format/framing-codec.md) for the
full specification.
**Cap'n Proto RPC**
- Same schema and RPC interface as the QUIC stack.
- The `NoiseTransport::into_capnp_io()` method bridges the message-oriented
Noise channel to the byte-stream interface that `capnp-rpc`'s
`twoparty::VatNetwork` expects, using a `tokio::io::duplex` pipe and a
background shuttle task.
---
## Why Both Stacks Exist
| Concern | QUIC + TLS 1.3 | Noise_XX over TCP |
|------------------------|----------------------------------------|----------------------------------------|
| **Milestone** | M3+ (primary) | M1 (original, retained) |
| **UDP availability** | Requires UDP; may be blocked on some networks | TCP-only; works everywhere |
| **Connection setup** | 1-RTT (or 0-RTT on resumption) | 1-RTT TCP + 1.5-RTT Noise handshake |
| **Multiplexing** | Native QUIC stream multiplexing | Single TCP connection, single stream |
| **Authentication** | Server cert (self-signed / CA-issued) | Mutual static-key authentication |
| **PQ gap** | TLS 1.3 key exchange is classical ECDHE | Noise key exchange is classical X25519 |
| **Crate** | `quinn`, `rustls` | `snow` |
Both stacks carry the same Cap'n Proto RPC and MLS layers on top, so
application logic is transport-agnostic. The Noise_XX stack may also serve as a
peer-to-peer transport in future mesh topologies where a QUIC server
certificate model does not apply.
---
## Comparison Table
| Layer | Provides | Crate(s) |
|-------------|------------------------------------------------------------------|-----------------------------------------|
| **Transport: QUIC + TLS 1.3** | Confidentiality, server authentication, forward secrecy, multiplexed streams, congestion control | `quinn`, `rustls` |
| **Transport: Noise_XX** | Confidentiality, mutual authentication, forward secrecy (per-session) | `snow` |
| **Framing: Cap'n Proto** | Zero-copy typed serialisation, schema versioning, async RPC with promise pipelining | `capnp`, `capnp-rpc` |
| **Encryption: MLS** | Group key agreement, forward secrecy, post-compromise security, identity binding | `openmls`, `openmls_rust_crypto` |
| **Encryption: Hybrid KEM** (optional) | Post-quantum confidentiality for individual payloads (X25519 + ML-KEM-768) | `ml-kem`, `x25519-dalek`, `chacha20poly1305`, `hkdf` |
---
## Data Path Summary
A plaintext message traverses the stack as follows:
```text
Sender Recipient
────── ─────────
plaintext bytes
MLS create_message()
│ ── encrypts with group AEAD key (AES-128-GCM) ──
TLS-encoded MlsMessageOut (opaque ciphertext blob)
Cap'n Proto: enqueue(recipientKey, payload)
│ ── serialised into NodeService RPC call ──
QUIC stream (TLS 1.3 encrypted) ─── or ─── Noise frame (ChaCha20-Poly1305)
│ │
▼ ▼
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ network ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
│ │
▼ ▼
Server: NodeService.enqueue() stores payload in FIFO queue
Cap'n Proto: fetch() / fetchWait() returns payload
MLS process_message()
│ ── decrypts with group AEAD key ──
plaintext bytes
```
The server **never** holds the MLS group key. It sees only the encrypted
`MlsMessageOut` blob.
---
## Further Reading
- [Architecture Overview](overview.md) -- high-level system diagram and dual-key model
- [Noise_XX Handshake](../protocol-layers/noise-xx.md) -- deep dive into the three-message handshake
- [QUIC + TLS 1.3](../protocol-layers/quic-tls.md) -- QUIC configuration, ALPN, and certificate handling
- [Cap'n Proto Serialisation and RPC](../protocol-layers/capn-proto.md) -- schema design and VatNetwork wiring
- [MLS (RFC 9420)](../protocol-layers/mls.md) -- ciphersuite selection, key schedule, and ratchet tree
- [Hybrid KEM: X25519 + ML-KEM-768](../protocol-layers/hybrid-kem.md) -- post-quantum envelope encryption