aboutsummaryrefslogtreecommitdiffstats
path: root/docs/devel-phases-next.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/devel-phases-next.md')
-rw-r--r--docs/devel-phases-next.md88
1 files changed, 55 insertions, 33 deletions
diff --git a/docs/devel-phases-next.md b/docs/devel-phases-next.md
index 6730ca0..48620ca 100644
--- a/docs/devel-phases-next.md
+++ b/docs/devel-phases-next.md
@@ -1001,17 +1001,33 @@ in production imports it — `grep` finds it only in its own tests. The node cha
(`_do_chat_message`) stores raw payloads. The module provides per-sender chain key
derivation, symmetric message encryption, and a distribution format.
-### 15.0 — Decide the distribution channel FIRST (blocking sub-milestone)
+### 15.0 — Distribution channel ✅ DECIDED 2026-09-03
`draft-v4` §6.6 says sender keys are distributed "via pairwise channels (GEK-wrapped or
-direct)". **GEK-wrapped is the wrong choice** and must not be implemented: it makes every
-sender key a function of the GEK, so anyone who holds the GEK — including an attacker who
-obtained it via H3 key substitution, or a former member who kept it — recovers every sender
-key. The encryption would then be decorative.
+direct)". An earlier version of this document rejected GEK-wrapped distribution on the
+grounds that anyone holding the GEK recovers every sender key.
-Distribution must be **pairwise to identity keys**: wrap each sender key with ECIES to the
-recipient's `pk_x25519` (the existing `wrap_gek_aes` primitive), or run the existing
-`ratchet.py` Double Ratchet per member pair. Decide and record before writing 15.1.
+**Revised 2026-09-03 by operator decision: GEK-wrapped distribution is the right choice
+for this platform.** The reasoning that led to pairwise was sound in isolation but wrong
+for the actual threat model:
+
+- The GEK already gives access to **all files** in the group. Wrapping sender keys
+ under it means "anyone who can read the files can read the chat" — which is exactly
+ the semantics of a group chat. There is no scenario where a member should read files
+ but not chat, or vice versa.
+- The node operator is always a group member and therefore a legitimate sender-key
+ recipient. Sender Keys does not protect chat from the operator regardless of the
+ distribution channel (see threat delta below).
+- H3 (key substitution at invite) is **closed** (2026-08-14, `invite-pairing-v1.md`).
+ The GEK is no longer obtainable through the hub. A former member who kept the old
+ GEK is handled by GEK rotation on removal, which is already implemented.
+- Pairwise distribution would add O(devices × members) ECIES wraps per sender key
+ change, for a marginal security gain: separating "file access" from "chat access"
+ on a platform where both are gated by the same group membership.
+
+**Distribution is GEK-wrapped:** each sender key distribution message is encrypted
+with `wrap_gek_aes` under the group's current GEK. Every member who has the GEK can
+unwrap it. Simple, no fan-out, no new crypto primitive.
### 15.0b — A sender key is per DEVICE, never per person (added 2026-08-17)
@@ -1037,37 +1053,40 @@ per device.
**What follows from per-device chains:**
-- **Fan-out is O(devices), not O(members)** — bounded by the per-user device cap (5 by
- default), so up to 5× the distribution messages. Acceptable, but size the distribution
- path for it rather than discovering it.
-- **A new device cannot read history until every sender redistributes.** Nobody but the
- senders holds their chain keys — that is the point — so a freshly linked device sees an
- unreadable backlog until each sender is next online. Either accept and surface it
- ("history before this device was added is unavailable"), or have the **linking device
- hand over its own accumulated state as a blob sealed to the new device's key**, relayed
- by the node, which cannot read it. Decide in 15.0.
+- **A new device receives all current sender keys via the GEK it already holds** (revised
+ 2026-09-03). Since distribution is GEK-wrapped, a device that has completed the
+ handshake and received the GEK can unwrap every sender key distribution message. No
+ redistribution by every sender is needed; the node replays the latest distribution
+ message for each active chain. History encrypted under older chain keys remains
+ unreadable only if the chain has ratcheted forward since — which is the expected
+ forward-secrecy property, not a gap.
- **Revoking a device must rotate**, exactly like revoking a member: a lost laptop holds
every sender key it ever received. 15.4 only knows about members today and must cover
`device revoke` and `member unpin`.
### Honest threat delta (state this in the docs, not just here)
-Sender Keys protects chat against **someone who holds the node's disk but is not a group
-member** — a seized machine, a hosting provider, a compromised node. It does **not** protect
-chat from the node operator, because on this platform the operator is a group member and
-therefore a legitimate sender-key recipient. Claiming more than that would repeat the
-overstatement pattern `second-review.md` §7 flags.
+Sender Keys protects chat against **someone who holds the node's disk but not the GEK** —
+a hosting provider imaging the machine, a backup that leaks, a law-enforcement seizure
+where the keystore password is not surrendered. It does **not** protect chat from anyone
+who holds the GEK, which includes every current group member and the node operator.
+This is the same boundary as file access, by design (operator decision 2026-09-03):
+the GEK is the group secret, and both files and chat are gated by it.
-Three additions once devices exist, all of which belong in the user-facing docs:
+Claiming more than that would repeat the overstatement pattern `second-review.md` §7
+flags. Additions that belong in the user-facing docs:
- **It does not protect against anyone holding any one device of any member.** With
several devices per person, that surface is larger than it was.
+- **It does not protect against a former member who kept the GEK before rotation.**
+ GEK rotation on member removal is implemented, but messages encrypted under the
+ old GEK remain readable to anyone who held it. This is the same property as files.
- **C4's blast radius reaches chat history.** A browser recovers its identity key from the
- keypair bundle on the node; cracking that bundle yields every sender key ever wrapped to
- it, because the distribution channel has no forward secrecy. Not a regression — chat is
- plaintext at rest today — but it means Sender Keys is worth measurably less to a
- browser-using account than to a native one, which is the same asymmetry as everywhere
- else in `desktop-client-v1.md` §5.1.
+ keypair bundle on the node; cracking that bundle yields the GEK, and therefore every
+ sender key distributed under it. Not a regression — chat is plaintext at rest today —
+ but it means Sender Keys is worth measurably less to a browser-using account than to a
+ native one, which is the same asymmetry as everywhere else in `desktop-client-v1.md`
+ §5.1.
- **Sender authentication is now a requirement, not an accepted limitation**
(operator decision, 2026-08-17). A sender key proves *a device*; it does not prove which
account that device belongs to, and NS6's enforcement of `sender_id` from the session is
@@ -1083,9 +1102,9 @@ Three additions once devices exist, all of which belong in the user-facing docs:
| # | Component | Description |
|---|---|---|
-| 15.0 | **Distribution decision** | Pairwise-to-identity-key, never GEK-derived. Blocking |
-| 15.0b | **Per-device chains** | `sender_id` becomes a device identifier; fix `GroupSenderKeyStore`'s silent overwrite; decide the history-handover question. **Blocking, and depends on device linking (Stage C) landing first** |
-| 15.1 | Node: sender key init | Generate a sender key **per device** on group join, distribute to **every device of every member** |
+| 15.0 | **Distribution decision** | ✅ **DECIDED 2026-09-03** — GEK-wrapped. The GEK is the group secret; files and chat share the same access boundary |
+| 15.0b | **Per-device chains** | `sender_id` becomes a device identifier; fix `GroupSenderKeyStore`'s silent overwrite. **Blocking, and depends on device linking (Stage C) landing first** |
+| 15.1 | Node: sender key init | Generate a sender key **per device** on group join, distribute GEK-wrapped to the group |
| 15.2 | Node: encrypt chat on send | Encrypt payload with that device's chain key before broadcast |
| 15.3 | Node: decrypt chat on receive | Decrypt incoming chat messages, handle out-of-order |
| 15.4 | Key rotation on removal | Member removed **or device revoked or unpinned** → all remaining devices rotate |
@@ -1337,7 +1356,10 @@ silent overwrite that becomes a hole the moment more than one key per person is
destination (C4). ✅ (2026-08-13). **Qualified 2026-08-17:** this holds for native
devices. A browser has no durable storage of its own and still needs a bundle on each
node, so C4 closes for an account only when it opts out of browser use.
-20. **Sender keys are distributed pairwise to identity keys, never derived from or wrapped
- under the GEK.** ✅ (2026-08-13)
+20. ~~Sender keys are distributed pairwise to identity keys, never derived from or wrapped
+ under the GEK.~~ **Reversed 2026-09-03:** sender keys are distributed **GEK-wrapped**.
+ The GEK is the group secret; both files and chat are gated by it. Pairwise distribution
+ would add complexity for a separation (files vs chat) that has no meaning in this
+ platform's group model. Per-device chains (15.0b) remain required for correctness
21. **Hub minimization is enforced by an acceptance test (12.1), not by policy.** The hub
must be *unable* to see keys, content, or file listings. ✅ (2026-08-13)