Rename all project references from quicproquo/qpq to quicprochat/qpc across documentation, Docker configuration, CI workflows, packaging scripts, operational configs, and build tooling. - Docker: crate paths, binary names, user/group, data dirs, env vars - CI: workflow crate references, binary names, artifact names - Docs: all markdown files under docs/, SDK READMEs, book.toml - Packaging: OpenWrt Makefile, init script, UCI config (file renames) - Scripts: justfile, dev-shell, screenshot, cross-compile, ai_team - Operations: Prometheus config, alert rules, Grafana dashboard - Config: .env.example (QPQ_* → QPC_*), CODEOWNERS paths - Top-level: README, CONTRIBUTING, ROADMAP, CLAUDE.md
9.5 KiB
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 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 quicprochat'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.
[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:
[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 quicprochat
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:
// 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:
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:
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).
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 quicprochat:
Signal Sender Keys
In Signal's group protocol:
- Each member generates a Sender Key -- a symmetric key used to encrypt all messages they send to the group.
- The Sender Key is distributed to all group members via pairwise Signal sessions.
- Each message from a sender is encrypted with their Sender Key.
- 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 (quicprochat)
In contrast, MLS's ratchet tree provides PCS because:
- Any member can issue an Update that re-randomizes their leaf.
- The Commit propagates new key material up the tree, affecting the group secret.
- The attacker, who holds old state, cannot predict the new randomness.
- 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):
-
Current epoch: The attacker can decrypt all messages in the current epoch from all members (because epoch keys are shared group secrets).
-
Past epochs: Protected by forward secrecy. Old epoch keys have been deleted by openmls.
-
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 quicprochat 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 for the full analysis of a compromised server.
Related Pages
- Cryptography Overview -- algorithm inventory
- Forward Secrecy -- the complementary property
- Key Lifecycle and Zeroization -- how key deletion enables FS and PCS
- Threat Model -- attacker models including compromised clients
- Post-Quantum Readiness -- PQ protection for PCS mechanisms
- MLS (RFC 9420) -- protocol deep dive