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:
330
docs/src/getting-started/demo-walkthrough.md
Normal file
330
docs/src/getting-started/demo-walkthrough.md
Normal file
@@ -0,0 +1,330 @@
|
||||
# Demo Walkthrough: Alice and Bob
|
||||
|
||||
This page walks through a complete end-to-end encrypted conversation between two participants -- Alice and Bob -- using the persistent group CLI. By the end, you will have started a server, registered two identities, created an MLS group, exchanged a Welcome, and sent encrypted messages in both directions.
|
||||
|
||||
You will need **three terminal windows**: one for the server, one for Alice, and one for Bob.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
┌─────────┐ ┌──────────────────┐ ┌─────────┐
|
||||
│ Alice │ │ Server │ │ Bob │
|
||||
│ (client) │──── QUIC ────│ AS + DS (:7000) │──── QUIC ────│ (client)│
|
||||
└─────────┘ └──────────────────┘ └─────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sequence diagram
|
||||
|
||||
```
|
||||
Alice Server (AS+DS) Bob
|
||||
│ │ │
|
||||
│ 1. register-state │ │
|
||||
│ ─── uploadKeyPackage ─────> │ │
|
||||
│ <── fingerprint ─────────── │ │
|
||||
│ │ │
|
||||
│ │ 2. register-state │
|
||||
│ │ <── uploadKeyPackage ───────── │
|
||||
│ │ ─── fingerprint ────────────> │
|
||||
│ │ │
|
||||
│ 3. create-group │ │
|
||||
│ (local: epoch 0) │ │
|
||||
│ │ │
|
||||
│ 4. invite --peer-key <bob> │ │
|
||||
│ ─── fetchKeyPackage ──────> │ (Bob's KP removed from AS) │
|
||||
│ <── package ────────────── │ │
|
||||
│ (local: add_member → Commit + Welcome) │
|
||||
│ ─── enqueue(Welcome) ─────> │ (queued for Bob) │
|
||||
│ │ │
|
||||
│ │ 5. join │
|
||||
│ │ <── fetch ──────────────────── │
|
||||
│ │ ─── Welcome ────────────────> │
|
||||
│ │ (local: new_from_welcome) │
|
||||
│ │ │
|
||||
│ 6. send --msg "Hi Bob" │ │
|
||||
│ (local: create_message → PrivateMessage) │
|
||||
│ ─── enqueue(ciphertext) ──> │ (queued for Bob) │
|
||||
│ │ │
|
||||
│ │ 7. recv │
|
||||
│ │ <── fetch ──────────────────── │
|
||||
│ │ ─── ciphertext ─────────────> │
|
||||
│ │ (local: process_message) │
|
||||
│ │ plaintext: "Hi Bob" │
|
||||
│ │ │
|
||||
│ │ 8. send --msg "Hi Alice" │
|
||||
│ │ (local: create_message) │
|
||||
│ │ <── enqueue(ciphertext) ────── │
|
||||
│ │ │
|
||||
│ 9. recv │ │
|
||||
│ ─── fetch ────────────────> │ │
|
||||
│ <── ciphertext ─────────── │ │
|
||||
│ (local: process_message) │ │
|
||||
│ plaintext: "Hi Alice" │ │
|
||||
│ │ │
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step-by-step instructions
|
||||
|
||||
### Step 1: Start the server
|
||||
|
||||
In **Terminal 1** (Server):
|
||||
|
||||
```bash
|
||||
cargo run -p quicnprotochat-server
|
||||
```
|
||||
|
||||
Wait for the log line confirming it is accepting connections:
|
||||
|
||||
```
|
||||
INFO quicnprotochat_server: accepting QUIC connections addr="0.0.0.0:7000"
|
||||
```
|
||||
|
||||
If this is the first run, you will also see a log line about generating the self-signed TLS certificate. The certificate is written to `data/server-cert.der`, which the client will use for TLS verification.
|
||||
|
||||
### Step 2: Alice registers her identity
|
||||
|
||||
In **Terminal 2** (Alice):
|
||||
|
||||
```bash
|
||||
cargo run -p quicnprotochat-client -- register-state \
|
||||
--state alice.bin \
|
||||
--server 127.0.0.1:7000
|
||||
```
|
||||
|
||||
This command:
|
||||
- Generates a fresh Ed25519 identity keypair (or loads one from `alice.bin` if it already exists).
|
||||
- Creates an MLS KeyPackage signed with that identity.
|
||||
- Uploads the KeyPackage to the server's Authentication Service.
|
||||
- Saves the identity seed and key store to `alice.bin` and `alice.ks`.
|
||||
|
||||
**Output:**
|
||||
```
|
||||
identity_key : <ALICE_KEY> (64 hex chars)
|
||||
fingerprint : <fingerprint>
|
||||
KeyPackage uploaded successfully.
|
||||
```
|
||||
|
||||
**Copy the `identity_key` value** -- Bob will need it in Step 5.
|
||||
|
||||
### Step 3: Bob registers his identity
|
||||
|
||||
In **Terminal 3** (Bob):
|
||||
|
||||
```bash
|
||||
cargo run -p quicnprotochat-client -- register-state \
|
||||
--state bob.bin \
|
||||
--server 127.0.0.1:7000
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```
|
||||
identity_key : <BOB_KEY> (64 hex chars)
|
||||
fingerprint : <fingerprint>
|
||||
KeyPackage uploaded successfully.
|
||||
```
|
||||
|
||||
**Copy the `identity_key` value** -- Alice will need it in Step 4.
|
||||
|
||||
### Step 4: Alice creates a group and invites Bob
|
||||
|
||||
In **Terminal 2** (Alice):
|
||||
|
||||
First, create the group:
|
||||
|
||||
```bash
|
||||
cargo run -p quicnprotochat-client -- create-group \
|
||||
--state alice.bin \
|
||||
--group-id "demo-chat"
|
||||
```
|
||||
|
||||
```
|
||||
group created: demo-chat
|
||||
```
|
||||
|
||||
Alice is now the sole member of the group at epoch 0.
|
||||
|
||||
Next, invite Bob using his identity key from Step 3:
|
||||
|
||||
```bash
|
||||
cargo run -p quicnprotochat-client -- invite \
|
||||
--state alice.bin \
|
||||
--peer-key <BOB_KEY> \
|
||||
--server 127.0.0.1:7000
|
||||
```
|
||||
|
||||
This command:
|
||||
1. Fetches Bob's KeyPackage from the AS (this atomically removes it -- single-use).
|
||||
2. Calls `add_member()` on Alice's local MLS group, producing a Commit (applied locally) and a Welcome.
|
||||
3. Enqueues the Welcome message to the DS, addressed to Bob's identity key.
|
||||
|
||||
```
|
||||
invited peer (welcome queued)
|
||||
```
|
||||
|
||||
Alice's group state has now advanced to epoch 1.
|
||||
|
||||
### Step 5: Bob joins the group
|
||||
|
||||
In **Terminal 3** (Bob):
|
||||
|
||||
```bash
|
||||
cargo run -p quicnprotochat-client -- join \
|
||||
--state bob.bin \
|
||||
--server 127.0.0.1:7000
|
||||
```
|
||||
|
||||
This command:
|
||||
1. Fetches all pending messages for Bob's identity key from the DS.
|
||||
2. Finds the Welcome message that Alice enqueued.
|
||||
3. Calls `MlsGroup::new_from_welcome()`, which decrypts the Welcome using the HPKE init private key from Bob's key store (`bob.ks`).
|
||||
4. Saves the joined group state to `bob.bin`.
|
||||
|
||||
```
|
||||
joined group successfully
|
||||
```
|
||||
|
||||
Bob is now a member of the group at epoch 1, sharing the same group secret as Alice.
|
||||
|
||||
### Step 6: Alice sends an encrypted message
|
||||
|
||||
In **Terminal 2** (Alice):
|
||||
|
||||
```bash
|
||||
cargo run -p quicnprotochat-client -- send \
|
||||
--state alice.bin \
|
||||
--peer-key <BOB_KEY> \
|
||||
--msg "Hello Bob, this is encrypted with MLS!" \
|
||||
--server 127.0.0.1:7000
|
||||
```
|
||||
|
||||
This command:
|
||||
1. Calls `create_message()` on Alice's MLS group, encrypting the plaintext as an MLS `PrivateMessage`.
|
||||
2. Enqueues the ciphertext to the DS for Bob's identity key.
|
||||
|
||||
```
|
||||
message sent
|
||||
```
|
||||
|
||||
### Step 7: Bob receives and decrypts
|
||||
|
||||
In **Terminal 3** (Bob):
|
||||
|
||||
```bash
|
||||
cargo run -p quicnprotochat-client -- recv \
|
||||
--state bob.bin \
|
||||
--server 127.0.0.1:7000
|
||||
```
|
||||
|
||||
This command:
|
||||
1. Fetches all pending messages from the DS.
|
||||
2. For each message, calls `process_message()` on Bob's MLS group, which decrypts the `PrivateMessage` and returns the plaintext.
|
||||
|
||||
```
|
||||
[0] plaintext: Hello Bob, this is encrypted with MLS!
|
||||
```
|
||||
|
||||
### Step 8: Bob replies
|
||||
|
||||
In **Terminal 3** (Bob):
|
||||
|
||||
```bash
|
||||
cargo run -p quicnprotochat-client -- send \
|
||||
--state bob.bin \
|
||||
--peer-key <ALICE_KEY> \
|
||||
--msg "Hi Alice, received loud and clear!" \
|
||||
--server 127.0.0.1:7000
|
||||
```
|
||||
|
||||
```
|
||||
message sent
|
||||
```
|
||||
|
||||
### Step 9: Alice receives Bob's reply
|
||||
|
||||
In **Terminal 2** (Alice):
|
||||
|
||||
```bash
|
||||
cargo run -p quicnprotochat-client -- recv \
|
||||
--state alice.bin \
|
||||
--server 127.0.0.1:7000
|
||||
```
|
||||
|
||||
```
|
||||
[0] plaintext: Hi Alice, received loud and clear!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automated demo (single command)
|
||||
|
||||
If you want to see the entire flow in a single command without managing three terminals, use the `demo-group` subcommand. This creates both Alice and Bob in-process with ephemeral identities and runs the full round-trip:
|
||||
|
||||
```bash
|
||||
# Ensure the server is running, then:
|
||||
cargo run -p quicnprotochat-client -- demo-group --server 127.0.0.1:7000
|
||||
```
|
||||
|
||||
```
|
||||
Alice -> Bob plaintext: hello bob
|
||||
Bob -> Alice plaintext: hello alice
|
||||
demo-group complete
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What happened under the hood
|
||||
|
||||
Here is a summary of the cryptographic operations and network calls that occurred during this walkthrough:
|
||||
|
||||
| Step | Client | Crypto operation | Network RPC |
|
||||
|---|---|---|---|
|
||||
| 2 | Alice | Ed25519 keygen, MLS KeyPackage creation | `uploadKeyPackage` |
|
||||
| 3 | Bob | Ed25519 keygen, MLS KeyPackage creation | `uploadKeyPackage` |
|
||||
| 4a | Alice | `MlsGroup::new_with_group_id` (epoch 0) | -- |
|
||||
| 4b | Alice | `MlsGroup::add_members` (Commit + Welcome, epoch 0 -> 1) | `fetchKeyPackage`, `enqueue` |
|
||||
| 5 | Bob | `MlsGroup::new_from_welcome` (HPKE decrypt, epoch 1) | `fetch` |
|
||||
| 6 | Alice | `MlsGroup::create_message` (AES-128-GCM encrypt) | `enqueue` |
|
||||
| 7 | Bob | `MlsGroup::process_message` (AES-128-GCM decrypt) | `fetch` |
|
||||
| 8 | Bob | `MlsGroup::create_message` (AES-128-GCM encrypt) | `enqueue` |
|
||||
| 9 | Alice | `MlsGroup::process_message` (AES-128-GCM decrypt) | `fetch` |
|
||||
|
||||
The MLS ciphersuite used throughout is `MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519`:
|
||||
|
||||
- **DHKEM(X25519, HKDF-SHA256)** for the HPKE key encapsulation in KeyPackages
|
||||
- **AES-128-GCM** for symmetric encryption of application messages
|
||||
- **SHA-256** for the key schedule hash function
|
||||
- **Ed25519** for signing KeyPackages, Commits, and leaf nodes
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `join` fails with "HPKE init key not found"
|
||||
|
||||
This happens when the key store file (`.ks`) was deleted or when `join` is run with a different `--state` path than `register-state`. The HPKE init private key generated during KeyPackage creation must be available at join time. Solution: use the same `--state` path for both `register-state` and `join`, and do not delete the `.ks` file between them.
|
||||
|
||||
### `invite` fails with "server returned empty KeyPackage for peer"
|
||||
|
||||
The peer has not registered yet, or their KeyPackage was already consumed by a previous `invite` or `fetch-key`. Ask the peer to run `register-state` again to upload a fresh KeyPackage.
|
||||
|
||||
### `join` fails with "no Welcome found in DS for this identity"
|
||||
|
||||
The Welcome message has not been enqueued yet (the inviter has not run `invite`), or it was already consumed by a previous `join`. Check that `invite` completed successfully before running `join`.
|
||||
|
||||
### TLS verification fails
|
||||
|
||||
Ensure the client has access to the server's TLS certificate. By default, both server and client use `data/server-cert.der`. If the server regenerated its certificate (e.g., after deleting the `data/` directory), clients must pick up the new certificate.
|
||||
|
||||
---
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Running the Client](running-the-client.md) -- full CLI reference
|
||||
- [MLS (RFC 9420)](../protocol-layers/mls.md) -- how the MLS group operations work
|
||||
- [GroupMember Lifecycle](../internals/group-member-lifecycle.md) -- internal state machine details
|
||||
- [Delivery Service Internals](../internals/delivery-service.md) -- how the DS queues and delivers messages
|
||||
Reference in New Issue
Block a user