summaryrefslogtreecommitdiffstats
path: root/docs/meshbay-draft-v3.md
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-10 03:07:56 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-10 03:07:56 +0200
commit4b3e8c3b8b9d10c8ac333dd8db614a7569052472 (patch)
tree8828d7d549adc8bb21d8a7f6533c8e9db9c298b9 /docs/meshbay-draft-v3.md
parent8ccbe262ecf4a8f7545cbf1e9f1cc5a485acae67 (diff)
downloadmeshbay-4b3e8c3b8b9d10c8ac333dd8db614a7569052472.tar.gz
feat: Phase 7 — Node v2 (multi-group, Sender Keys, 0-RTT, chat, denylist)
Implements all 8 milestones (7.0-7.7): - 7.0: JWT carries `groups` claim; node verifies group membership at MNP handshake (QUIC + TCP+TLS). Resolves security review C2. - 7.1: QUIC 0-RTT session resumption via stored session tickets (17-21ms reconnect vs 47ms cold). - 7.2: Hub→node WebSocket signaling for NAT punch coordination (`client_incoming`/`punch_ready`) + jti denylist push. Denylist class blocks revoked users/jtis at handshake. - 7.3: Multi-group daemon — one QUIC port serves N groups with per-group GEK, shared_root, and index routing. - 7.4: HLS streaming via QUIC (STREAM_SEGMENT message type, ffmpeg segment extraction). - 7.5: Sender Keys protocol for group chat (Signal Groups approach). Each member has own sending chain key, HKDF chain ratchet, AES-256-GCM encryption, Ed25519 signing. Resolves security review C1. - 7.6: Chat store (SQLite via aiosqlite), CHAT_MESSAGE MNP wire type with peer broadcast, web UI with WebSocket push. - 7.7: Argon2id calibration CLI. First security review included (first-review.md). 109 tests, demo-v3 validated against meshbay.org production hub. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'docs/meshbay-draft-v3.md')
-rw-r--r--docs/meshbay-draft-v3.md58
1 files changed, 41 insertions, 17 deletions
diff --git a/docs/meshbay-draft-v3.md b/docs/meshbay-draft-v3.md
index e11200c..97c391d 100644
--- a/docs/meshbay-draft-v3.md
+++ b/docs/meshbay-draft-v3.md
@@ -97,7 +97,7 @@ Email is kept in full (not hashed) to support:
Phone number: optional, associable after account creation. On Android, both collected at registration. Accounts are fusionable (email + phone pointing to same account).
-Email and phone are stored encrypted at rest in the database.
+Email and phone are stored encrypted at rest in the database, using a server-side key derived from the hub's configuration secret (not the database). **[NOT YET IMPLEMENTED — currently stored in plaintext. Tracked as open question #10.]**
#### 4.1.2 Mandatory IP Logging (Legal Compliance)
@@ -140,7 +140,8 @@ Two tokens issued at login:
**Access token** (JWT, signed Ed25519):
- Validity: 1 hour
-- Payload: `jti` (UUID4, **mandatory** — unique per token, enables individual revocation and prevents replay), `user_id`, `PK_user`, `PK_user_x25519`, `hub_id`, `issued_at`, `expires_at`, hub-signed groups membership claim
+- Payload: `jti` (UUID4, **mandatory** — unique per token, enables individual revocation and prevents replay), `user_id`, `PK_user`, `PK_user_x25519`, `hub_id`, `issued_at`, `expires_at`, `groups` (list of group_ids the user is a member of — hub-signed membership claim)
+- The `groups` claim is **mandatory** for node-side authorization: the node checks that the requested group_id appears in the JWT before serving any content. Without this claim, any authenticated user could access any group on the node.
- Presented to nodes for authentication and group access verification
- Verified locally by nodes using the hub's known public key — no hub roundtrip
- Compromise window: 1 hour maximum
@@ -237,6 +238,11 @@ in the client JWT. The server routes each connection to the appropriate
DirectoryIndexer and GEK after JWT verification.
Rationale: one NAT hole to maintain, one port to forward manually if needed.
+**Authorization invariant:** the node MUST verify that the JWT's `groups` claim
+contains the requested group_id before serving any content. Without this check,
+any authenticated user could access any group on the node. This is enforced at
+the MNP handshake layer, not the transport layer.
+
**Platform:** Linux primary, cross-platform from the start (Windows/macOS). Python ensures portability.
#### 4.2.1 Keystore and Unlock
@@ -512,7 +518,7 @@ Disk (plaintext) → zstd compress → GEK encrypt (per-chunk) → TCP+TLS 1.3 s
**Chunking:**
- Chunk size: 1 MB (amortizes AEAD overhead; enables seeking)
-- Per-chunk key derivation: `chunk_key = HKDF(GEK, salt="file:" || blake3(file) || "chunk:" || index)`
+- Per-chunk key derivation: `chunk_key = HKDF(GEK, salt=None, info="file:" || blake3(file) || ":chunk:" || index)` — salt is omitted because the GEK is a CSPRNG output (already uniform); the file/chunk context goes in `info` for domain separation, which is the correct HKDF usage per RFC 5869
- Each chunk independently decryptable → enables VOD seeking
- Compress before encrypt (compression is ineffective on ciphertext)
@@ -600,11 +606,21 @@ Group chat is a **core feature** (not an extension module).
- **Push/pull:** connected members get real-time push (WebSocket); offline members pull history on reconnect
- **Retention:** managed by the group admin (no automatic expiry)
-**Encryption:** Double Ratchet algorithm (implemented in `meshbay_common.ratchet`):
-- Forward secrecy and break-in recovery per message
-- Each message independently encrypted, out-of-order delivery handled
-- Attachment files: encrypted with GEK-derived key (same as file chunks), hash in message
-- Group scope: all members share the same ratchet state seeded from the group GEK
+**Encryption — Sender Keys protocol (decided in first security review, 2026-08-10):**
+
+The Double Ratchet (implemented in `meshbay_common.ratchet`) is a **pairwise** (1:1) protocol. Using a shared ratchet state for N group members would cause chain key desynchronization and nonce/key reuse — a catastrophic AEAD failure. The architecture uses **Sender Keys** instead (same approach as Signal Groups):
+
+- Each group member generates a **sender key** (random symmetric chain key + signing keypair)
+- On joining a group, the new member's sender key is distributed to all existing members via pairwise channels (GEK-wrapped or direct)
+- Each existing member sends their current sender key to the new member
+- Messages are encrypted with the sender's chain key (symmetric ratchet, one direction)
+- Forward secrecy at **member rotation** granularity: when a member is removed, all remaining members rotate their sender keys
+- O(N) state per member (one chain per group member), not O(N^2)
+- The existing Double Ratchet implementation is kept for future 1:1 direct messaging
+
+Attachment files: encrypted with GEK-derived key (same as file chunks), hash referenced in the message.
+
+> **Why not MLS (RFC 9420)?** MLS provides O(log N) message overhead and per-message forward secrecy via tree-based ratcheting. It is the superior long-term choice, but its complexity is not justified for v1 group sizes (< 50 members). Sender Keys is proven at scale (Signal, WhatsApp) and simpler to implement. Migration to MLS is a v2 option if group sizes grow.
---
@@ -854,16 +870,24 @@ The node loads extension modules (Python) in a sandboxed subprocess. **Chat is a
| R6 | Hub API: which endpoints for GEK distribution? | `GET /v1/users/{username}/pubkeys`, `POST /v1/groups`, `POST /v1/groups/{group_id}/members/{username}/gek`, `GET /v1/groups/{group_id}/gek` | Spike 6 |
| R7 | Package structure? | 3 packages: `meshbay-common`, `meshbay-hub`, `meshbay-node`. uv workspace monorepo. | POC |
+**Resolved by first security review (2026-08-10):**
+
+| # | Question | Resolution | Source |
+|---|---|---|---|
+| R8 | Group chat encryption model? | Sender Keys protocol (Signal Groups approach). Double Ratchet kept for future 1:1 DM only. MLS considered for v2 if groups > 50 members. | Security review C1 |
+| R9 | Token denylist distribution? | Push via existing hub→node WebSocket. Node maintains an in-memory jti set. MNP handshake checks the set before accepting a JWT. No periodic polling needed. | Security review S3 |
+| R10 | Chunk key HKDF: salt or info? | `info` (domain separation), `salt=None`. GEK is CSPRNG output (already uniform), so HKDF extract step doesn't need a random salt. Spec wording corrected to match code (RFC 5869 compliant). | Security review M5 |
+| R11 | AES-GCM keystore IV size? | 96-bit (12 bytes), per NIST SP 800-38D recommendation. Code fixed from 128-bit to 96-bit. | Security review S4 |
+
**Still open:**
1. **Refresh token validity:** 30 or 90 days?
2. **Group address scheme:** final URL format confirmation
-3. **Double Ratchet library:** identify best Python implementation (evaluate `python-doubleratchet`, `axolotl`, or custom)
-4. **GEK bundle location for groups with mixed access** (public-restricted): hub or node?
-5. **MHP federation sync frequency and conflict resolution**
-6. **Hub mirror replication strategy** (when implemented)
-7. **Chat attachment storage:** stored on node like regular files, or separate store?
-8. **Relay registration protocol design** (when implemented)
-9. **Token denylist distribution:** how do nodes fetch and cache the `jti` denylist? Push (hub WebSocket) or pull (periodic poll)? Cache TTL?
-10. **QUIC migration timeline:** when is the application protocol considered stable enough to begin v2 transport implementation?
-11. **Port configuration conflict:** `18000` used for both local web UI and (in some proposals) MNP listener — needs final port allocation decision (proposed split: 18000 for web UI, 18001 for MNP).
+3. **GEK bundle location for groups with mixed access** (public-restricted): hub or node?
+4. **MHP federation sync frequency and conflict resolution**
+5. **Hub mirror replication strategy** (when implemented)
+6. **Chat attachment storage:** stored on node like regular files, or separate store?
+7. **Relay registration protocol design** (when implemented)
+8. **QUIC migration timeline:** when is the application protocol considered stable enough to begin v2 transport implementation?
+9. **Refresh token rotation:** implement one-time-use refresh tokens (rotate on each use, detect reuse as theft indicator). RFC 6819 §5.2.2.3.
+10. **Email encryption at rest:** spec requires encrypted email/phone in DB, implementation stores plaintext. Needs server-side encryption with key from hub config.