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:
208
docs/src/architecture/protocol-stack.md
Normal file
208
docs/src/architecture/protocol-stack.md
Normal 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
|
||||
Reference in New Issue
Block a user