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>
181 lines
8.0 KiB
Markdown
181 lines
8.0 KiB
Markdown
# Forward Secrecy
|
|
|
|
Forward secrecy (FS), also called perfect forward secrecy (PFS), is a property
|
|
of a cryptographic protocol that guarantees: **if a long-term secret key is
|
|
compromised, past session keys cannot be recovered.** In other words, an
|
|
attacker who obtains today's long-term key cannot use it to decrypt messages
|
|
recorded yesterday.
|
|
|
|
quicproquo provides forward secrecy at two independent layers: the transport
|
|
layer and the application layer. Even if one layer's FS mechanism is defeated,
|
|
the other continues to protect message confidentiality.
|
|
|
|
## Transport Layer Forward Secrecy
|
|
|
|
### TLS 1.3 (QUIC)
|
|
|
|
The QUIC transport (via `quinn 0.11` + `rustls 0.23`) uses TLS 1.3, which
|
|
mandates ephemeral key exchange in every handshake. Unlike TLS 1.2, which
|
|
allowed static RSA key exchange (no FS), TLS 1.3 exclusively uses ephemeral
|
|
ECDHE (Elliptic Curve Diffie-Hellman Ephemeral).
|
|
|
|
In each TLS 1.3 handshake:
|
|
|
|
1. Both client and server generate ephemeral ECDHE key pairs.
|
|
2. They exchange public keys and compute a shared secret via Diffie-Hellman.
|
|
3. Session keys are derived from the shared secret using HKDF.
|
|
4. The ephemeral private keys are discarded after key derivation.
|
|
|
|
Because the ephemeral keys exist only for the duration of the handshake,
|
|
compromising the server's long-term TLS certificate key (currently self-signed
|
|
in quicproquo) does not reveal past session keys.
|
|
|
|
## Application Layer Forward Secrecy
|
|
|
|
### MLS Epoch Ratchet
|
|
|
|
The MLS protocol (RFC 9420) provides forward secrecy at the application layer
|
|
through its epoch ratchet mechanism. This is independent of the transport
|
|
layer's FS and protects message content even if transport session keys are
|
|
leaked.
|
|
|
|
Each MLS group maintains a **ratchet tree** -- a binary tree where each leaf
|
|
represents a group member and internal nodes hold derived key material. The
|
|
tree defines a current **epoch**, which determines the encryption keys for all
|
|
messages in that epoch.
|
|
|
|
When the epoch advances (via a Commit message):
|
|
|
|
1. The ratchet tree is updated with new key material from the committing member.
|
|
2. New epoch keys are derived from the updated tree.
|
|
3. **Old epoch keys are deleted.**
|
|
|
|
This deletion is the mechanism that provides forward secrecy: once old epoch
|
|
keys are erased, messages encrypted under those keys cannot be decrypted, even
|
|
if the current group state is compromised.
|
|
|
|
In quicproquo, epoch advancement occurs when:
|
|
|
|
- `add_member()` is called, which creates a Commit and calls
|
|
`merge_pending_commit()`.
|
|
- A received Commit is processed via `receive_message()`, which calls
|
|
`merge_staged_commit()`.
|
|
|
|
```rust
|
|
// Epoch advances here -- old keys deleted internally by openmls
|
|
group.merge_pending_commit(&self.backend)?; // sender side
|
|
group.merge_staged_commit(&self.backend, *staged)?; // receiver side
|
|
```
|
|
|
|
### Single-Use KeyPackages
|
|
|
|
MLS KeyPackages contain a single-use HPKE init public key. Each init key is
|
|
used exactly once -- to encrypt the Welcome message that bootstraps a new
|
|
member's group state. After the Welcome is processed, the init private key is
|
|
consumed and deleted from the `DiskKeyStore`.
|
|
|
|
This single-use design provides forward secrecy for the initial key exchange:
|
|
|
|
- Even if a member's long-term Ed25519 identity key is later compromised, the
|
|
attacker cannot reconstruct the HPKE init private key that was used to decrypt
|
|
the Welcome.
|
|
- The init key was ephemeral to the join operation and no longer exists.
|
|
|
|
This property is critical because the Welcome message contains the full ratchet
|
|
tree state, including the secrets needed to decrypt messages in the initial
|
|
epoch. If the init key could be reused or recovered, an attacker could
|
|
reconstruct the entire initial group state.
|
|
|
|
See [Key Lifecycle and Zeroization](key-lifecycle.md) for the full lifecycle of
|
|
HPKE init keys.
|
|
|
|
## Layered Forward Secrecy
|
|
|
|
A distinctive property of quicproquo's design is that forward secrecy
|
|
operates at two independent layers:
|
|
|
|
```text
|
|
+------------------------------------------------------+
|
|
| Network Adversary (records ciphertext) |
|
|
+------------------------------------------------------+
|
|
|
|
|
v
|
|
+------------------------------------------------------+
|
|
| TLS 1.3 (QUIC) |
|
|
| Forward secrecy via ephemeral ECDHE |
|
|
| Even if TLS cert is compromised, |
|
|
| past transport sessions are protected. |
|
|
+------------------------------------------------------+
|
|
|
|
|
v
|
|
+------------------------------------------------------+
|
|
| MLS (RFC 9420) |
|
|
| Forward secrecy via epoch ratchet |
|
|
| Even if current MLS state is compromised, |
|
|
| past epochs are protected (keys deleted). |
|
|
+------------------------------------------------------+
|
|
|
|
|
v
|
|
+------------------------------------------------------+
|
|
| Plaintext message content |
|
|
+------------------------------------------------------+
|
|
```
|
|
|
|
**Why this matters:** If the transport layer's forward secrecy is broken (e.g.,
|
|
an attacker obtains a TLS session key through a side channel), the MLS layer
|
|
still protects message content independently. The attacker would see the MLS
|
|
ciphertext but could not decrypt it without the MLS epoch keys.
|
|
|
|
Conversely, if MLS epoch keys are somehow leaked, the transport layer prevents
|
|
a network-level attacker from correlating them with specific network flows
|
|
unless they also break the transport encryption.
|
|
|
|
## Comparison with Signal
|
|
|
|
Signal's Double Ratchet protocol also provides forward secrecy, but the
|
|
mechanisms differ:
|
|
|
|
| Property | Signal Double Ratchet | MLS (quicproquo) |
|
|
|----------|----------------------|---------------------|
|
|
| Scope | Pairwise (1:1 sessions) | Group (n-party) |
|
|
| Ratchet granularity | Per message (symmetric ratchet) + per DH round (DH ratchet) | Per epoch (Commit) |
|
|
| FS granularity | Individual messages | All messages in an epoch |
|
|
| Group support | Sender Keys (no per-message FS in groups) | Native group FS via ratchet tree |
|
|
| Efficiency | O(1) per message | O(log n) per Commit, O(1) per message |
|
|
|
|
Signal achieves finer-grained forward secrecy in 1:1 conversations (per message
|
|
via the symmetric ratchet), but in group settings, Signal uses Sender Keys,
|
|
which do **not** provide per-message forward secrecy. A compromised Sender Key
|
|
reveals all past messages from that sender.
|
|
|
|
MLS provides forward secrecy at the epoch level for the entire group. Within an
|
|
epoch, all messages share the same key material. The trade-off is that FS
|
|
granularity is coarser (per epoch rather than per message), but it applies
|
|
uniformly to all group members.
|
|
|
|
## Practical Implications
|
|
|
|
1. **Epoch advancement frequency:** More frequent Commits provide more
|
|
fine-grained forward secrecy. In the current implementation, epochs advance
|
|
when members are added. Future milestones will add periodic Update proposals
|
|
to advance epochs even without membership changes.
|
|
|
|
2. **Key deletion timing:** Forward secrecy depends on old keys being actually
|
|
deleted from memory and disk. The `DiskKeyStore`'s flush-on-write behavior
|
|
ensures that consumed HPKE init keys are removed from the persistent store.
|
|
MLS epoch key deletion is handled internally by openmls.
|
|
|
|
3. **State file security:** The client state file contains the Ed25519 identity
|
|
seed and potentially the DiskKeyStore contents. If this file is compromised,
|
|
the attacker obtains the current identity key and any stored HPKE init keys
|
|
(for pending Welcome messages). Past epoch keys are not in the state file
|
|
(they have been deleted), so forward secrecy is preserved for past epochs.
|
|
|
|
## Related Pages
|
|
|
|
- [Cryptography Overview](overview.md) -- algorithm inventory
|
|
- [Key Lifecycle and Zeroization](key-lifecycle.md) -- when keys are created and destroyed
|
|
- [Post-Compromise Security](post-compromise-security.md) -- the complementary property (protecting the future)
|
|
- [Threat Model](threat-model.md) -- attacker models and what FS protects against
|
|
- [Ed25519 Identity Keys](identity-keys.md) -- long-term key that FS protects against compromising
|