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:
239
docs/src/cryptography/post-compromise-security.md
Normal file
239
docs/src/cryptography/post-compromise-security.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Post-Compromise Security
|
||||
|
||||
Post-compromise security (PCS) is a property of a cryptographic protocol that
|
||||
guarantees: **after an attacker compromises a participant's state, the protocol
|
||||
automatically heals so that future messages are protected.** The attacker's
|
||||
window of access is limited to the current epoch; once the compromised member (or
|
||||
any other member) issues an Update or Commit, the group state is re-randomized
|
||||
and the attacker is locked out.
|
||||
|
||||
PCS is the complement of [forward secrecy](forward-secrecy.md):
|
||||
|
||||
- **Forward secrecy** protects the **past** from a future compromise.
|
||||
- **Post-compromise security** protects the **future** from a past compromise.
|
||||
|
||||
MLS (RFC 9420) is specifically designed to provide both properties simultaneously
|
||||
for group messaging. This is a key differentiator of quicnprotochat's design.
|
||||
|
||||
## How MLS Provides PCS
|
||||
|
||||
### The Ratchet Tree
|
||||
|
||||
At the heart of MLS's PCS mechanism is the **ratchet tree**, a binary tree where:
|
||||
|
||||
- Each **leaf** represents a group member and contains their public key material.
|
||||
- Each **internal node** holds derived key material computed from its children.
|
||||
- The **root** of the tree determines the epoch's group key material.
|
||||
|
||||
```text
|
||||
[Root]
|
||||
/ \
|
||||
[A,B] [C,D]
|
||||
/ \ / \
|
||||
[A] [B] [C] [D]
|
||||
```
|
||||
|
||||
When a member updates their leaf (by generating fresh key material and issuing an
|
||||
Update proposal or Commit), the change propagates up the tree path from the leaf
|
||||
to the root:
|
||||
|
||||
```text
|
||||
[Root]* <- re-derived
|
||||
/ \
|
||||
[A,B]* [C,D] <- re-derived
|
||||
/ \ / \
|
||||
[A]* [B] [C] [D] <- A updated leaf
|
||||
```
|
||||
|
||||
Every node marked with `*` receives new key material. This means the new epoch's
|
||||
group secrets depend on A's freshly generated randomness. An attacker who
|
||||
previously compromised A's state must now also compromise the new key material --
|
||||
which they cannot do because it was generated after the compromise was detected
|
||||
(or automatically healed by the protocol).
|
||||
|
||||
### Cost
|
||||
|
||||
The path from a leaf to the root has length O(log n) for a group of n members.
|
||||
This means:
|
||||
|
||||
- An Update/Commit produces O(log n) encrypted path secrets.
|
||||
- Each group member processes the Update in O(log n) time.
|
||||
- This is dramatically more efficient than pairwise rekeying (O(n)) or
|
||||
broadcast encryption.
|
||||
|
||||
For a group of 1,000 members, the path length is approximately 10 nodes --
|
||||
making PCS practical even for large groups.
|
||||
|
||||
## Epoch Advancement in quicnprotochat
|
||||
|
||||
In the current implementation, epoch advancement occurs through the `GroupMember`
|
||||
methods in `group.rs`:
|
||||
|
||||
### Adding a Member (Commit)
|
||||
|
||||
When `add_member()` is called, openmls creates a Commit that adds the new member
|
||||
and updates the ratchet tree:
|
||||
|
||||
```rust
|
||||
// Alice adds Bob -- this creates a Commit + Welcome
|
||||
let (commit_bytes, welcome_bytes) = alice.add_member(&bob_kp)?;
|
||||
|
||||
// Alice merges the pending Commit, advancing her epoch
|
||||
// Internally, openmls re-derives the ratchet tree with Bob's leaf
|
||||
group.merge_pending_commit(&self.backend)?;
|
||||
```
|
||||
|
||||
After `merge_pending_commit()`, Alice's group is in a new epoch with fresh key
|
||||
material. Any attacker who had compromised Alice's state before this Commit
|
||||
must now also compromise the new epoch's keys.
|
||||
|
||||
### Receiving a Commit
|
||||
|
||||
When a member receives and processes a Commit from another member:
|
||||
|
||||
```rust
|
||||
ProcessedMessageContent::StagedCommitMessage(staged) => {
|
||||
// Merge the Commit into local state -- epoch advances
|
||||
group.merge_staged_commit(&self.backend, *staged)?;
|
||||
Ok(None)
|
||||
}
|
||||
```
|
||||
|
||||
This advances the receiver's epoch, incorporating the committer's fresh key
|
||||
material into the ratchet tree.
|
||||
|
||||
### Future: Periodic Updates
|
||||
|
||||
The current implementation only advances epochs when members are added. A more
|
||||
robust PCS strategy involves periodic Update proposals, where members
|
||||
re-randomize their leaf key material on a regular schedule (e.g., every hour, or
|
||||
every N messages). This is planned for future milestones and will look like:
|
||||
|
||||
```text
|
||||
1. Member generates fresh leaf key material
|
||||
2. Member creates an Update proposal
|
||||
3. Any member (or the updater) creates a Commit including the Update
|
||||
4. All members process the Commit and advance to the new epoch
|
||||
5. The attacker's compromised state is now stale
|
||||
```
|
||||
|
||||
## PCS vs Forward Secrecy
|
||||
|
||||
These two properties are often confused but protect against different attack
|
||||
scenarios:
|
||||
|
||||
| Property | Protects | Mechanism | Threat |
|
||||
|----------|----------|-----------|--------|
|
||||
| Forward Secrecy | Past messages | Delete old epoch keys | Attacker compromises state **now**, tries to read **past** |
|
||||
| Post-Compromise Security | Future messages | Re-randomize ratchet tree | Attacker compromised state **before**, tries to read **future** |
|
||||
|
||||
Together, they provide a strong guarantee: the attacker's window of access is
|
||||
limited to the **current epoch only**. Past epochs are protected by FS (old keys
|
||||
deleted), and future epochs are protected by PCS (new key material generated).
|
||||
|
||||
```text
|
||||
Past epochs Current epoch Future epochs
|
||||
+-----------------+ +-----------------+ +-----------------+
|
||||
| Protected by | | Attacker has | | Protected by |
|
||||
| Forward | | access if they | | Post-Compromise|
|
||||
| Secrecy | | hold current | | Security |
|
||||
| (keys deleted) | | epoch keys | | (tree updated) |
|
||||
+-----------------+ +-----------------+ +-----------------+
|
||||
```
|
||||
|
||||
## Comparison with Signal Groups
|
||||
|
||||
Signal's group messaging uses **Sender Keys**, a fundamentally different
|
||||
mechanism from MLS's ratchet tree. The comparison is instructive because it
|
||||
highlights why MLS was chosen for quicnprotochat:
|
||||
|
||||
### Signal Sender Keys
|
||||
|
||||
In Signal's group protocol:
|
||||
|
||||
1. Each member generates a Sender Key -- a symmetric key used to encrypt all
|
||||
messages they send to the group.
|
||||
2. The Sender Key is distributed to all group members via pairwise Signal
|
||||
sessions.
|
||||
3. Each message from a sender is encrypted with their Sender Key.
|
||||
4. The Sender Key includes a symmetric ratchet (hash ratchet) that advances per
|
||||
message, providing forward secrecy within a sender's chain.
|
||||
|
||||
**The critical limitation:** Sender Keys do **not** provide post-compromise
|
||||
security. If an attacker compromises a member's Sender Key:
|
||||
|
||||
- The attacker can derive all future message keys from that sender (the hash
|
||||
ratchet is one-way, but the attacker has the current state).
|
||||
- The key is only rotated when the member manually refreshes it or when group
|
||||
membership changes.
|
||||
- There is no automatic healing mechanism analogous to MLS's ratchet tree.
|
||||
|
||||
### MLS Ratchet Tree (quicnprotochat)
|
||||
|
||||
In contrast, MLS's ratchet tree provides PCS because:
|
||||
|
||||
1. Any member can issue an Update that re-randomizes their leaf.
|
||||
2. The Commit propagates new key material up the tree, affecting the group
|
||||
secret.
|
||||
3. The attacker, who holds old state, cannot predict the new randomness.
|
||||
4. The group automatically heals after at most one epoch advance.
|
||||
|
||||
| Property | Signal Sender Keys | MLS Ratchet Tree |
|
||||
|----------|-------------------|-----------------|
|
||||
| Forward secrecy (group) | Per-sender hash ratchet | Per-epoch (Commit) |
|
||||
| Post-compromise security | **No** -- compromised Sender Key reveals all future messages from that sender | **Yes** -- any Commit/Update heals the tree |
|
||||
| Key rotation | Manual or on membership change | Any Commit (add/remove/update) |
|
||||
| Healing time | Until manual rotation | Next epoch (automatic) |
|
||||
| Cost per update | O(n) pairwise re-encryption | O(log n) tree path |
|
||||
|
||||
## Practical Implications
|
||||
|
||||
### What happens during a compromise?
|
||||
|
||||
Suppose an attacker compromises Member A's MLS state (including the current epoch
|
||||
keys):
|
||||
|
||||
1. **Current epoch:** The attacker can decrypt all messages in the current epoch
|
||||
from all members (because epoch keys are shared group secrets).
|
||||
|
||||
2. **Past epochs:** Protected by forward secrecy. Old epoch keys have been
|
||||
deleted by openmls.
|
||||
|
||||
3. **After the next Commit:** Any member (including A, after recovering) can
|
||||
issue a Commit. The ratchet tree is updated with fresh key material. The
|
||||
attacker's stale state cannot derive the new epoch keys. The attacker is
|
||||
locked out.
|
||||
|
||||
### How quickly does healing occur?
|
||||
|
||||
In the current implementation, healing occurs whenever:
|
||||
|
||||
- A new member is added (`add_member()` issues a Commit).
|
||||
- A member is removed (not yet implemented, but will issue a Commit).
|
||||
- In the future: periodic Update proposals are issued on a timer.
|
||||
|
||||
The practical healing window is the time between Commits. For active groups
|
||||
with frequent membership changes, this window is small. For static groups,
|
||||
periodic Updates (planned) will bound the healing window.
|
||||
|
||||
### Server compromise does not prevent PCS
|
||||
|
||||
The quicnprotochat server is MLS-unaware -- it stores and forwards encrypted
|
||||
MLS messages without access to the group state. A compromised server cannot:
|
||||
|
||||
- Prevent PCS by blocking Commits (it could perform denial-of-service, but
|
||||
cannot selectively suppress Update proposals without being detected, because
|
||||
MLS epoch numbers must be sequential).
|
||||
- Inject fraudulent Commits (it lacks the signing key of any group member).
|
||||
|
||||
See [Threat Model](threat-model.md) for the full analysis of a compromised
|
||||
server.
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [Cryptography Overview](overview.md) -- algorithm inventory
|
||||
- [Forward Secrecy](forward-secrecy.md) -- the complementary property
|
||||
- [Key Lifecycle and Zeroization](key-lifecycle.md) -- how key deletion enables FS and PCS
|
||||
- [Threat Model](threat-model.md) -- attacker models including compromised clients
|
||||
- [Post-Quantum Readiness](post-quantum-readiness.md) -- PQ protection for PCS mechanisms
|
||||
- [MLS (RFC 9420)](../protocol-layers/mls.md) -- protocol deep dive
|
||||
Reference in New Issue
Block a user