Rename the entire workspace:
- Crate packages: quicnprotochat-{core,proto,server,client,gui,p2p,mobile} -> quicproquo-*
- Binary names: quicnprotochat -> qpq, quicnprotochat-server -> qpq-server,
quicnprotochat-gui -> qpq-gui
- Default files: *-state.bin -> qpq-state.bin, *-server.toml -> qpq-server.toml,
*.db -> qpq.db
- Environment variable prefix: QUICNPROTOCHAT_* -> QPQ_*
- App identifier: chat.quicnproto.gui -> chat.quicproquo.gui
- Proto package: quicnprotochat.bench -> quicproquo.bench
- All documentation, Docker, CI, and script references updated
HKDF domain-separation strings and P2P ALPN remain unchanged for
backward compatibility with existing encrypted state and wire protocol.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
240 lines
9.5 KiB
Markdown
240 lines
9.5 KiB
Markdown
# 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 quicproquo'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 quicproquo
|
|
|
|
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 quicproquo:
|
|
|
|
### 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 (quicproquo)
|
|
|
|
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 quicproquo 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
|