Skip to content

Commit b729340

Browse files
Merge pull request #6 from OneFineStarstuff/design/e2ee-option-a
docs(e2ee): Option A E2EE design spec for sign-off
2 parents d3712b9 + dc7cae4 commit b729340

8 files changed

Lines changed: 294 additions & 0 deletions

File tree

DESIGN-E2EE.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# End-to-End Encryption (E2EE) Design — Option A Sign-off
2+
3+
Status: Ready for sign-off
4+
Owner: Kyaw
5+
6+
## 1. Scope and Goals
7+
- Web-first PWA with future native iOS/Android.
8+
- Protect messages, files/assets (3D/logo/media), docs, and analytics events.
9+
- Privacy-preserving analytics client-side; server-side limited to minimized metadata.
10+
- Retrofit behind feature flags; backward-compatible bridging for non-E2EE content.
11+
12+
## 2. Threat Model
13+
- Adversaries: curious/compromised servers, external attackers (MITM), malicious clients, stolen devices.
14+
- Trust boundaries: Only clients see plaintext/keys. Servers handle ciphertext, minimal metadata, and capability tokens. IdP proves identity only.
15+
- Security goals: Confidentiality & integrity; forward secrecy and post-compromise security; deniable auth for messages; verifiable membership/state.
16+
- Out of scope initial: traffic analysis resistance; plaintext content scanning; hardware tamper beyond platform enclaves.
17+
18+
## 3. Cryptographic Primitives and Libraries
19+
20+
### 3.1 Argon2id Parameters (desktop vs mobile)
21+
- Desktop/Web: m=64 MiB, t=3, p=1 — balances user-perceived latency with robust GPU/ASIC resistance for key wrapping. Typical derivation latency ~200–500 ms on contemporary laptops.
22+
- Mobile: m=32 MiB, t=3, p=1 — reduces memory pressure and thermal impact while keeping meaningful resistance. Target latency ~300–700 ms depending on device class.
23+
- Salt length: 16 bytes (random per vault). Output length: 32 bytes (KEK).
24+
- Rationale: Memory-hardness is the dominant cost lever; t=3 provides reasonable compute amplification without excessive energy draw on battery devices.
25+
26+
- Ed25519 (signing) for identities and devices.
27+
- X25519 (ECDH) for key agreement; sealed boxes for key wrapping (HPKE-ready abstraction).
28+
- Messaging: Signal/Double Ratchet via libsignal-client (WASM) for 1:1/small groups.
29+
- Large groups: Signal sender keys with periodic rotation; MLS on roadmap.
30+
- Files: AES-256-GCM streaming with HKDF-derived per-chunk nonces; BLAKE3 for chunk and whole-file digests.
31+
- KDF: HKDF-SHA256; Password KDF: Argon2id (m=64 MiB, t=3, p=1; mobile fallback m=32 MiB). Salt: 16 bytes. Output: 32 bytes.
32+
- Hashing: BLAKE3 for content addressing and integrity.
33+
34+
## 4. Identity & Device State Machines
35+
### 4.1 Identity Keys
36+
States: uninitialized -> generated -> backed_up (optional) -> compromised(revoked)
37+
Transitions:
38+
- generate: create Ed25519 identity key pair
39+
- backup: wrap private key with Argon2id-derived KEK; store vault in IndexedDB
40+
- revoke: mark identity compromised; re-enroll devices
41+
42+
### 4.2 Device Enrollment
43+
States: new -> pending_attestation -> verified -> revoked
44+
Transitions:
45+
- new: device generates Ed25519 (sign) + X25519 (DH)
46+
- provision: QR shows {device_pubkeys, nonce}; trusted device scans and verifies SAS
47+
- attest: trusted device signs attestation binding device to identity
48+
- verify: server records attestation; device becomes verified
49+
- revoke: immediate revocation; triggers rotations
50+
51+
## 5. Messaging Sessions
52+
- 1:1 and small groups: Double Ratchet with prekeys from libsignal.
53+
- Device revocation: peers refuse messages from revoked devices.
54+
- Group sender keys: per-room sender key rotated on membership change and every 7 days; per-recipient key wraps.
55+
56+
## 6. File Encryption and Sharing
57+
See also Mermaid diagrams in docs/diagrams for provisioning, file-share, and rekey flows.
58+
59+
### 5.1 Canonicalization (signatures & tokens)
60+
- Manifest signing: Canonical JSON per RFC 8785 (JSON Canonicalization Scheme, JCS). Remove the `sig` field before canonicalization; sign the result with device Ed25519. Verification recomputes canonical JSON and verifies the signature.
61+
- PASETO payload normalization: When computing or verifying detached request signatures or audit hashes, canonicalize the JSON payload using the same JCS rules and sort header fields if applicable. Avoid including transient fields (e.g., `iat` skew-adjusted values) in hash commitments.
62+
63+
### 6.1 Streaming Encryption
64+
- Per-file random DEK (256-bit).
65+
- Chunk size 512KB–2MB (adaptive). Max single-object size: 5 GB (resumable uploads).
66+
- Nonce derivation: nonce_i = HKDF(DEK, info="file-chunk" || chunk_index)[0..12]
67+
- AES-256-GCM over each chunk; produce per-chunk BLAKE3 and cumulative whole-file BLAKE3.
68+
69+
### 6.2 Manifest Format (signed by device Ed25519)
70+
```
71+
version: 1
72+
algo: aes-256-gcm
73+
chunk_size: <bytes>
74+
length: <bytes>
75+
blake3_file: <hex>
76+
chunks:
77+
- index: 0
78+
offset: 0
79+
size: <bytes>
80+
blake3: <hex>
81+
- ...
82+
key_wraps: omitted in manifest; stored adjacent by object_id
83+
sig: ed25519(signing_device_pubkey, JCS(manifest_without_sig))
84+
```
85+
86+
### 6.3 DEK Sharing
87+
- For each recipient device X25519 pubkey, create sealed box of DEK.
88+
- Store wraps: key_wraps(object_id, device_id, wrap_ciphertext)
89+
- Rekey on membership change; rewrap to active devices.
90+
91+
## 7. Capability Tokens
92+
- Format: PASETO v4.public (Ed25519-signed by server capability key).
93+
Claims:
94+
- sub: user or device id
95+
- scope: [object:get|put, room:read, room:write, membership:manage]
96+
- resource: URI or prefix (e.g., s3://bucket/path/object-id)
97+
- exp: expiry; iat/nbf
98+
- region: enforced data residency region (controls bucket/prefix routing)
99+
- tid/nonce: unique token id to prevent replay
100+
- TTLs: object GET/PUT 15 minutes; room/event scopes 1 hour.
101+
102+
## 8. APIs (Server)
103+
- POST /devices/attest
104+
- POST /devices/revoke
105+
- POST /rooms
106+
- POST /rooms/:id/members
107+
- POST /rooms/:id/rotate
108+
- POST /capabilities
109+
- PUT /objects/:id (requires capability)
110+
- GET /objects/:id (requires capability)
111+
- WS /events
112+
113+
## 9. Storage Schema (Postgres + S3-compatible)
114+
Residency enforcement: region claim in capabilities is validated and mapped to storage bucket/prefix; mismatches are rejected at capability validation and storage routing.
115+
- users(id, identity_pubkey_hash, oidc_sub, region)
116+
- devices(id, user_id, ed25519_pub, x25519_pub, attestation_sig, status)
117+
- rooms(id, created_by, policy)
118+
- memberships(room_id, device_id, role, since, status)
119+
- sender_keys(room_id, epoch, key_id, wrapped_keys jsonb, created_at)
120+
- objects(id, owner, room_id, bucket, path, blake3_digest, size, manifest_sig, created_at)
121+
- key_wraps(object_id, device_id, wrap_ciphertext)
122+
- audit_events(id, actor, type, target, ts, meta)
123+
124+
## 10. Backup & Recovery
125+
SAS & QR: Use emoji SAS (7-emoji sequence from a 64-emoji table) for human-friendly verification. QR payload schema: base64url(JSON { device_pubkeys, nonce, sig, ts }).
126+
- Key vault: private keys wrapped by Argon2id-derived KEK; IndexedDB on web; Secure Enclave/Keystore on mobile.
127+
- Optional Shamir 2-of-3 recovery (user + admin escrow + HSM) with approvals and audit.
128+
129+
## 11. Metadata Minimization
130+
Audit taxonomy and retention: capture key lifecycle events, membership changes, capability issuance/revocation, and recovery attempts. Retention: 1 year (then purge or anonymize per policy).
131+
- Store hashed identity references; coarse timestamps; encrypted membership maps when feasible.
132+
- Avoid plaintext titles/tags. No plaintext in logs.
133+
134+
## 12. Request Signing & Replay Protection
135+
- Client signs sensitive requests with device Ed25519 over canonical payload + timestamp.
136+
- Server enforces skew window and tid uniqueness.
137+
138+
## 13. Performance Targets
139+
- p95 decrypt < 120 ms for 10 MB on desktop.
140+
- Streaming crypto in Web Workers; backpressure-managed I/O.
141+
142+
## 14. Rollout & Kill Switch
143+
- Feature flags per tenant/room.
144+
- Canary cohorts; schema uses sidecar tables for isolation.
145+
- Instant kill-switch disables capability issuance for E2EE objects/rooms; existing ciphertext remains intact.
146+
147+
## 15. CI/CD and Supply Chain
148+
- Renovate/Dependabot with grouped patch/minor; majors manual.
149+
- GitHub Actions: lint/typecheck/tests, CodeQL, SCA, SBOM (Syft), container scanning.
150+
- Signed commits and releases.
151+
152+
## 16. Test Plan (Acceptance Gates)
153+
- Unit and property tests for: keygen, provisioning, sealed box wraps, AES-GCM streaming (vectors), manifest sign/verify, PASETO claims/validation, rotation flows.
154+
- Integration: 1:1 E2EE chat, file upload/download, membership change triggers rewrap/rotation.
155+
- Data residency pinning tests; GDPR DSR exercises.
156+
157+
## 17. Open Items / Future Work
158+
- Evaluate MLS migration path for large rooms.
159+
- HPKE support behind wrapping abstraction.
160+
- Privacy-preserving analytics with DP budget management per org.

docs/diagrams/file-share.mmd

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
sequenceDiagram
2+
autonumber
3+
participant C as Client
4+
participant O as Object Store
5+
participant S as Server
6+
7+
C->>C: Generate DEK
8+
loop Encrypt chunks
9+
C->>C: nonce_i = HKDF(DEK, "file-chunk"||i)[0..12]
10+
C->>C: c_i = AES-GCM(plaintext_i, nonce_i)
11+
C->>C: blake3_i = BLAKE3(c_i)
12+
end
13+
C->>C: blake3_file = BLAKE3(c_*) ; manifest+sig
14+
C->>O: PUT chunks + manifest (ciphertext only)
15+
par share DEK
16+
C->>S: POST key_wrap(device_pkX, sealed_box(DEK))
17+
and for each recipient
18+
C->>S: POST key_wrap(device_pkY, sealed_box(DEK))
19+
end

docs/diagrams/provisioning.mmd

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
sequenceDiagram
2+
autonumber
3+
participant ND as New Device
4+
participant TD as Trusted Device
5+
participant S as Server
6+
7+
ND->>ND: Generate Ed25519_sign_D, X25519_D, nonce N, ts
8+
ND->>TD: Display QR: base64url(JSON{device_pubkeys, N, ts, sig=Sign_D(N||ts)})
9+
TD->>TD: Compute SAS = hash(identity_pub, device_pubkeys, N, ts) -> 7 emojis
10+
ND-->>TD: Human verifies SAS matches
11+
TD->>S: POST /devices/attest(attestation=Sign_T{identity_pub, device_pubkeys, N, ts})
12+
S-->>TD: 200 OK
13+
S-->>ND: WS event device-verified

docs/diagrams/rekey.mmd

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
sequenceDiagram
2+
autonumber
3+
participant A as Admin
4+
participant S as Server
5+
participant C as Clients (room)
6+
7+
A->>S: Update membership
8+
S-->>C: WS membership-change
9+
C->>C: Rotate sender key; rewrap DEKs
10+
C->>S: POST new wraps
11+
S-->>C: Ack; old wraps invalidated
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"v": 4,
3+
"purpose": "public",
4+
"claims": {
5+
"sub": "device:9b1d2...",
6+
"scope": "object:get",
7+
"resource": "s3://brand-intel-eu/tenant123/objects/abc123",
8+
"exp": 1723939200,
9+
"iat": 1723935600,
10+
"nbf": 1723935600,
11+
"region": "eu",
12+
"tid": "d7b2f0f8-7e2a-4d31-8b4e-7e7c2f0b9d51"
13+
}
14+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://example.com/schemas/capability.schema.json",
4+
"title": "Capability Claims (PASETO v4.public payload)",
5+
"type": "object",
6+
"required": ["sub", "scope", "resource", "exp", "region", "tid"],
7+
"properties": {
8+
"sub": {"type": "string"},
9+
"scope": {"type": "string", "enum": ["object:get", "object:put", "room:read", "room:write", "membership:manage"]},
10+
"resource": {"type": "string"},
11+
"exp": {"type": "integer"},
12+
"iat": {"type": "integer"},
13+
"nbf": {"type": "integer"},
14+
"region": {"type": "string", "description": "enforced data residency region (controls bucket/prefix routing)"},
15+
"tid": {"type": "string"}
16+
}
17+
}

docs/schemas/manifest.schema.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://example.com/schemas/manifest.schema.json",
4+
"title": "E2EE File Manifest",
5+
"type": "object",
6+
"required": ["version", "algo", "chunk_size", "length", "blake3_file", "chunks", "sig"],
7+
"properties": {
8+
"version": {"type": "integer", "enum": [1]},
9+
"algo": {"type": "string", "const": "aes-256-gcm"},
10+
"chunk_size": {"type": "integer", "minimum": 262144, "maximum": 4194304},
11+
"length": {"type": "integer", "minimum": 0},
12+
"blake3_file": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
13+
"chunks": {
14+
"type": "array",
15+
"items": {
16+
"type": "object",
17+
"required": ["index", "offset", "size", "blake3"],
18+
"properties": {
19+
"index": {"type": "integer", "minimum": 0},
20+
"offset": {"type": "integer", "minimum": 0},
21+
"size": {"type": "integer", "minimum": 1},
22+
"blake3": {"type": "string", "pattern": "^[0-9a-f]{64}$"}
23+
}
24+
}
25+
},
26+
"sig": {"type": "string", "description": "Ed25519 signature over canonicalized manifest without sig"}
27+
}
28+
}

docs/sequences/e2ee-sequences.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# E2EE Sequence Diagrams (Textual)
2+
3+
## 1) Device Provisioning (QR + SAS)
4+
1. New device: gen (Ed25519_sign_D, X25519_D), create nonce N and ts
5+
2. New device -> QR payload: base64url(JSON { device_pubkeys, nonce: N, ts, sig: sign_D(N||ts) })
6+
3. Trusted device scans QR
7+
4. Both devices derive SAS from transcript (hash of {identity_pubkey, device_pubkeys, N, ts}) -> 7 emojis
8+
5. Human verifies SAS match
9+
6. Trusted device signs attestation: sign_T({ user_identity_pub, device_pubkeys, N, ts })
10+
7. Trusted device -> Server: POST /devices/attest(attestation)
11+
8. Server records device; broadcasts WS event
12+
13+
## 2) File Share (Encrypt + Upload + Share DEK)
14+
1. Client picks file; gen random DEK
15+
2. For each chunk i:
16+
- nonce_i = HKDF(DEK, info="file-chunk"||i)[0..12]
17+
- c_i = AES-256-GCM(plaintext_i, nonce_i)
18+
- blake3_i = BLAKE3(c_i)
19+
3. blake3_file = BLAKE3(c_0||c_1||...)
20+
4. Manifest = { version, algo, chunk_size, length, blake3_file, chunks[] }
21+
5. sig = Ed25519.sign(manifest_without_sig)
22+
6. Upload chunks and manifest (ciphertext only)
23+
7. For each recipient device pk_X:
24+
- wrap = sealed_box(DEK, pk_X)
25+
- POST key_wraps(object_id, device_id, wrap)
26+
27+
## 3) Membership change -> Rekey
28+
1. Admin changes room members
29+
2. Server emits WS membership-change event
30+
3. Clients rotate sender key and/or rewrap DEKs
31+
4. New wraps stored; old wraps invalidated
32+
5. Capability issuance follows narrow scopes with TTL

0 commit comments

Comments
 (0)