Skip to content

Commit df49c9d

Browse files
authored
Merge pull request #109 from syncpoint/feature/sync-gated-content
Sync-gated content loading after join
2 parents afaabf4 + 15179fc commit df49c9d

File tree

19 files changed

+1089
-36
lines changed

19 files changed

+1089
-36
lines changed

docs/e2ee-device-verification.md

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
# E2EE Device Verification – Proposal
2+
3+
## Problem
4+
5+
Currently ODIN uses TOFU (Trust on First Use) for device trust. This means any device claiming to be a project participant is trusted without verification. An attacker who compromises the homeserver or performs a MITM attack could inject a rogue device and intercept encrypted content.
6+
7+
For military/government use cases, this is insufficient. Users need to verify that they're communicating with the genuine devices of their collaborators.
8+
9+
## Matrix SAS Verification
10+
11+
Matrix specifies [Short Authentication String (SAS)](https://spec.matrix.org/v1.12/client-server-api/#short-authentication-string-sas-verification) verification, where two users compare a set of 7 emojis displayed on their screens. If the emojis match, the devices are mutually verified.
12+
13+
The `@matrix-org/matrix-sdk-crypto-wasm` SDK fully supports this:
14+
15+
- `VerificationRequest` — initiates/receives a verification flow
16+
- `Sas` — the SAS verification state machine
17+
- `Sas.emoji()` — returns 7 `Emoji` objects (symbol + description)
18+
- `Sas.confirm()` — confirms match, sends `m.key.verification.done`
19+
- `Sas.cancel()` — cancels if emojis don't match
20+
21+
## Proposed Flow for ODIN
22+
23+
### When: Verification happens at **project join** time
24+
25+
1. **Alice** shares an E2EE project with **Bob** (invite).
26+
2. **Bob** accepts the invitation and joins.
27+
3. ODIN detects Bob's new device (via `m.room.member` join + `device_lists.changed` in sync).
28+
4. ODIN shows a **verification prompt** to Alice: _"Bob has joined. Verify Bob's device?"_
29+
5. Alice initiates verification.
30+
6. Both Alice and Bob see **7 emojis** in a modal dialog.
31+
7. They compare emojis out-of-band (voice call, in person, secure messenger).
32+
8. Both confirm → devices are marked as verified.
33+
34+
### Where in the UI
35+
36+
- **Verification prompt** appears in the project's sharing panel or as a notification bar at the top of the map view.
37+
- **Emoji comparison dialog** is a modal overlay showing the 7 emojis in a grid, with "They match" and "They don't match" buttons.
38+
- **Verification status** is shown per-member in the sharing properties panel (✅ verified / ⚠️ unverified).
39+
40+
### What changes if a device is unverified?
41+
42+
Two possible strategies:
43+
44+
**Option A: Warn but allow (recommended for V1)**
45+
- Unverified devices get a ⚠️ warning in the sharing panel.
46+
- All operations work normally.
47+
- Users can verify at any time.
48+
- Pragmatic for field use where verification might be deferred.
49+
50+
**Option B: Block until verified**
51+
- Unverified devices cannot decrypt content.
52+
- Keys are only shared with verified devices.
53+
- More secure, but may break workflows if verification is delayed.
54+
55+
**Recommendation:** Start with Option A. The WASM SDK already has `TrustRequirement` settings that can switch behavior later.
56+
57+
## Implementation: matrix-client-api
58+
59+
### New CryptoManager Methods
60+
61+
```javascript
62+
/**
63+
* Request verification of another user's device.
64+
* @param {string} userId
65+
* @returns {VerificationRequest} the request object to track the flow
66+
*/
67+
async requestVerification(userId)
68+
69+
/**
70+
* Accept an incoming verification request.
71+
* @param {string} userId
72+
* @param {string} flowId
73+
* @returns {VerificationRequest}
74+
*/
75+
async acceptVerification(userId, flowId)
76+
77+
/**
78+
* Start SAS verification on an accepted request.
79+
* @param {VerificationRequest} request
80+
* @returns {Sas} the SAS state machine
81+
*/
82+
async startSas(request)
83+
84+
/**
85+
* Get the 7 emojis for comparison.
86+
* @param {Sas} sas
87+
* @returns {Array<{symbol: string, description: string}>}
88+
*/
89+
getEmojis(sas)
90+
91+
/**
92+
* Confirm that emojis match.
93+
* @param {Sas} sas
94+
* @returns {OutgoingRequest[]} requests to send
95+
*/
96+
async confirmSas(sas)
97+
98+
/**
99+
* Cancel the verification.
100+
* @param {Sas} sas
101+
* @returns {OutgoingRequest|undefined}
102+
*/
103+
cancelSas(sas)
104+
105+
/**
106+
* Check if a user's device is verified.
107+
* @param {string} userId
108+
* @param {string} deviceId
109+
* @returns {boolean}
110+
*/
111+
async isDeviceVerified(userId, deviceId)
112+
113+
/**
114+
* Get verification status for all devices of a user.
115+
* @param {string} userId
116+
* @returns {Array<{deviceId: string, verified: boolean}>}
117+
*/
118+
async getDeviceVerificationStatus(userId)
119+
```
120+
121+
### Verification Event Handling
122+
123+
The SAS flow uses `to_device` events:
124+
- `m.key.verification.request`
125+
- `m.key.verification.ready`
126+
- `m.key.verification.start`
127+
- `m.key.verification.accept`
128+
- `m.key.verification.key`
129+
- `m.key.verification.mac`
130+
- `m.key.verification.done`
131+
- `m.key.verification.cancel`
132+
133+
These are already routed through `receiveSyncChanges()` and handled by the OlmMachine internally. We need to:
134+
135+
1. **Detect incoming requests** — poll `getVerificationRequests(userId)` after sync.
136+
2. **Surface them to ODIN** — emit events that ODIN can listen to (e.g., `verificationRequested`, `verificationReady`, `emojisAvailable`, `verificationDone`).
137+
3. **Send outgoing requests**`accept()`, `confirm()`, `cancel()` return `OutgoingRequest` objects that need to be sent via HTTP.
138+
139+
### Event Emission
140+
141+
Add verification events to the existing stream handler pattern:
142+
143+
```javascript
144+
// In project.mjs or a new verification-handler.mjs
145+
streamHandler.verificationRequested({ userId, flowId, request })
146+
streamHandler.emojisAvailable({ userId, flowId, emojis })
147+
streamHandler.verificationDone({ userId, deviceId })
148+
streamHandler.verificationCancelled({ userId, flowId, reason })
149+
```
150+
151+
## Implementation: ODIN (Electron)
152+
153+
### UI Components
154+
155+
1. **VerificationPrompt** — Notification bar or toast: _"New device detected for Bob. [Verify]"_
156+
2. **EmojiComparisonDialog** — Modal showing 7 emojis in a grid with confirm/cancel buttons.
157+
3. **MemberVerificationBadge** — ✅/⚠️ icon next to member names in sharing properties.
158+
159+
### Electron-specific
160+
161+
- The verification flow involves multiple async steps (request → accept → emojis → confirm).
162+
- Use a React state machine or reducer to track the verification phase.
163+
- Emojis are Unicode — no custom graphics needed.
164+
165+
## Protocol Sequence
166+
167+
```
168+
Alice Homeserver Bob
169+
│ │ │
170+
│ m.key.verification.request │ │
171+
├───────────────────────────────►│ to_device │
172+
│ ├──────────────────────────────►│
173+
│ │ │
174+
│ │ m.key.verification.ready │
175+
│ to_device │◄──────────────────────────────┤
176+
│◄───────────────────────────────┤ │
177+
│ │ │
178+
│ m.key.verification.start │ │
179+
├───────────────────────────────►│ to_device │
180+
│ ├──────────────────────────────►│
181+
│ │ │
182+
│ m.key.verification.accept │ │
183+
│ to_device │◄──────────────────────────────┤
184+
│◄───────────────────────────────┤ │
185+
│ │ │
186+
│ m.key.verification.key │ (both exchange DH keys) │
187+
│◄──────────────────────────────►│◄─────────────────────────────►│
188+
│ │ │
189+
│ ┌─────────────────────┐ │ ┌─────────────────────┐ │
190+
│ │ 🐶 🔑 🎵 🌍 🎩 ☂️ 🌻 │ │ │ 🐶 🔑 🎵 🌍 🎩 ☂️ 🌻 │ │
191+
│ │ "Do these match?" │ │ │ "Do these match?" │ │
192+
│ └────────┬────────────┘ │ └────────┬────────────┘ │
193+
│ │ [Yes!] │ │ [Yes!] │
194+
│ │ │
195+
│ m.key.verification.mac │ (both send MACs) │
196+
│◄──────────────────────────────►│◄─────────────────────────────►│
197+
│ │ │
198+
│ m.key.verification.done │ │
199+
│◄──────────────────────────────►│◄─────────────────────────────►│
200+
│ │ │
201+
│ ✅ Bob's device verified │ ✅ Alice verified │
202+
```
203+
204+
## Scope & Phases
205+
206+
### Phase 1 (minimal viable)
207+
- CryptoManager methods for SAS verification
208+
- Verification event emission in stream handler
209+
- Basic ODIN UI: prompt + emoji dialog + status badge
210+
- Manual verification (user clicks "Verify" in sharing properties)
211+
212+
### Phase 2 (polish)
213+
- Auto-prompt on new device detection
214+
- Verification status persists across restarts (already in OlmMachine store)
215+
- Block key sharing to unverified devices (Option B)
216+
- QR code verification as alternative to emoji
217+
218+
### Phase 3 (advanced)
219+
- Cross-signing (verify user, not individual devices)
220+
- Verification via room events (instead of to_device) for audit trail
221+
222+
## Open Questions
223+
224+
1. **When to block?** Should we ever refuse to share keys with unverified devices, or always warn-only?
225+
2. **Verification UI location** — Modal? Side panel? Notification?
226+
3. **Re-verification** — What happens when a user gets a new device? Auto-detect and re-prompt?
227+
4. **Offline verification** — If Bob is offline when Alice initiates, the request waits. Timeout?

0 commit comments

Comments
 (0)