-
Notifications
You must be signed in to change notification settings - Fork 0
docs(e2ee): Option A E2EE design spec for sign-off #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
81ea41a
d912579
ed0b181
5c41c00
75bbcb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,160 @@ | ||||||||||||||||||||||||||||||||||||
# End-to-End Encryption (E2EE) Design — Option A Sign-off | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
Status: Ready for sign-off | ||||||||||||||||||||||||||||||||||||
Owner: Kyaw | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 1. Scope and Goals | ||||||||||||||||||||||||||||||||||||
- Web-first PWA with future native iOS/Android. | ||||||||||||||||||||||||||||||||||||
- Protect messages, files/assets (3D/logo/media), docs, and analytics events. | ||||||||||||||||||||||||||||||||||||
- Privacy-preserving analytics client-side; server-side limited to minimized metadata. | ||||||||||||||||||||||||||||||||||||
- Retrofit behind feature flags; backward-compatible bridging for non-E2EE content. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 2. Threat Model | ||||||||||||||||||||||||||||||||||||
- Adversaries: curious/compromised servers, external attackers (MITM), malicious clients, stolen devices. | ||||||||||||||||||||||||||||||||||||
- Trust boundaries: Only clients see plaintext/keys. Servers handle ciphertext, minimal metadata, and capability tokens. IdP proves identity only. | ||||||||||||||||||||||||||||||||||||
- Security goals: Confidentiality & integrity; forward secrecy and post-compromise security; deniable auth for messages; verifiable membership/state. | ||||||||||||||||||||||||||||||||||||
- Out of scope initial: traffic analysis resistance; plaintext content scanning; hardware tamper beyond platform enclaves. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 3. Cryptographic Primitives and Libraries | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
### 3.1 Argon2id Parameters (desktop vs mobile) | ||||||||||||||||||||||||||||||||||||
- 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. | ||||||||||||||||||||||||||||||||||||
- 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. | ||||||||||||||||||||||||||||||||||||
- Salt length: 16 bytes (random per vault). Output length: 32 bytes (KEK). | ||||||||||||||||||||||||||||||||||||
- Rationale: Memory-hardness is the dominant cost lever; t=3 provides reasonable compute amplification without excessive energy draw on battery devices. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
- Ed25519 (signing) for identities and devices. | ||||||||||||||||||||||||||||||||||||
- X25519 (ECDH) for key agreement; sealed boxes for key wrapping (HPKE-ready abstraction). | ||||||||||||||||||||||||||||||||||||
- Messaging: Signal/Double Ratchet via libsignal-client (WASM) for 1:1/small groups. | ||||||||||||||||||||||||||||||||||||
- Large groups: Signal sender keys with periodic rotation; MLS on roadmap. | ||||||||||||||||||||||||||||||||||||
- Files: AES-256-GCM streaming with HKDF-derived per-chunk nonces; BLAKE3 for chunk and whole-file digests. | ||||||||||||||||||||||||||||||||||||
- KDF: HKDF-SHA256; Password KDF: Argon2id (m=64 MiB, t=3, p=1; mobile fallback m=32 MiB). Salt: 16 bytes. Output: 32 bytes. | ||||||||||||||||||||||||||||||||||||
- Hashing: BLAKE3 for content addressing and integrity. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 4. Identity & Device State Machines | ||||||||||||||||||||||||||||||||||||
### 4.1 Identity Keys | ||||||||||||||||||||||||||||||||||||
States: uninitialized -> generated -> backed_up (optional) -> compromised(revoked) | ||||||||||||||||||||||||||||||||||||
Transitions: | ||||||||||||||||||||||||||||||||||||
- generate: create Ed25519 identity key pair | ||||||||||||||||||||||||||||||||||||
- backup: wrap private key with Argon2id-derived KEK; store vault in IndexedDB | ||||||||||||||||||||||||||||||||||||
- revoke: mark identity compromised; re-enroll devices | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
### 4.2 Device Enrollment | ||||||||||||||||||||||||||||||||||||
States: new -> pending_attestation -> verified -> revoked | ||||||||||||||||||||||||||||||||||||
Transitions: | ||||||||||||||||||||||||||||||||||||
- new: device generates Ed25519 (sign) + X25519 (DH) | ||||||||||||||||||||||||||||||||||||
- provision: QR shows {device_pubkeys, nonce}; trusted device scans and verifies SAS | ||||||||||||||||||||||||||||||||||||
- attest: trusted device signs attestation binding device to identity | ||||||||||||||||||||||||||||||||||||
- verify: server records attestation; device becomes verified | ||||||||||||||||||||||||||||||||||||
- revoke: immediate revocation; triggers rotations | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 5. Messaging Sessions | ||||||||||||||||||||||||||||||||||||
- 1:1 and small groups: Double Ratchet with prekeys from libsignal. | ||||||||||||||||||||||||||||||||||||
- Device revocation: peers refuse messages from revoked devices. | ||||||||||||||||||||||||||||||||||||
- Group sender keys: per-room sender key rotated on membership change and every 7 days; per-recipient key wraps. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 6. File Encryption and Sharing | ||||||||||||||||||||||||||||||||||||
See also Mermaid diagrams in docs/diagrams for provisioning, file-share, and rekey flows. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
### 5.1 Canonicalization (signatures & tokens) | ||||||||||||||||||||||||||||||||||||
- 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. | ||||||||||||||||||||||||||||||||||||
- 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. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
### 6.1 Streaming Encryption | ||||||||||||||||||||||||||||||||||||
- Per-file random DEK (256-bit). | ||||||||||||||||||||||||||||||||||||
- Chunk size 512KB–2MB (adaptive). Max single-object size: 5 GB (resumable uploads). | ||||||||||||||||||||||||||||||||||||
- Nonce derivation: nonce_i = HKDF(DEK, info="file-chunk" || chunk_index)[0..12] | ||||||||||||||||||||||||||||||||||||
- AES-256-GCM over each chunk; produce per-chunk BLAKE3 and cumulative whole-file BLAKE3. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
### 6.2 Manifest Format (signed by device Ed25519) | ||||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||||
version: 1 | ||||||||||||||||||||||||||||||||||||
algo: aes-256-gcm | ||||||||||||||||||||||||||||||||||||
chunk_size: <bytes> | ||||||||||||||||||||||||||||||||||||
length: <bytes> | ||||||||||||||||||||||||||||||||||||
blake3_file: <hex> | ||||||||||||||||||||||||||||||||||||
chunks: | ||||||||||||||||||||||||||||||||||||
- index: 0 | ||||||||||||||||||||||||||||||||||||
offset: 0 | ||||||||||||||||||||||||||||||||||||
size: <bytes> | ||||||||||||||||||||||||||||||||||||
blake3: <hex> | ||||||||||||||||||||||||||||||||||||
- ... | ||||||||||||||||||||||||||||||||||||
key_wraps: omitted in manifest; stored adjacent by object_id | ||||||||||||||||||||||||||||||||||||
sig: ed25519(signing_device_pubkey, JCS(manifest_without_sig)) | ||||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
### 6.3 DEK Sharing | ||||||||||||||||||||||||||||||||||||
- For each recipient device X25519 pubkey, create sealed box of DEK. | ||||||||||||||||||||||||||||||||||||
- Store wraps: key_wraps(object_id, device_id, wrap_ciphertext) | ||||||||||||||||||||||||||||||||||||
- Rekey on membership change; rewrap to active devices. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
Comment on lines
+86
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Bind DEK wraps to object and recipient; prefer HPKE-ready structure over bare sealed boxes Sealed boxes don’t carry AAD; pairing a ciphertext with a device_id in the DB without binding risks substitution by a malicious server. Define a wrap payload that includes object_id, alg, and recipient key id, and migrate to HPKE where AAD is first-class. Apply this diff: -### 6.3 DEK Sharing
-- For each recipient device X25519 pubkey, create sealed box of DEK.
-- Store wraps: key_wraps(object_id, device_id, wrap_ciphertext)
-- Rekey on membership change; rewrap to active devices.
+### 6.3 DEK Sharing
+- For each recipient device (recipient_kid, X25519 pubkey), create a wrap over:
+ wrap_payload = CBOR({
+ "v": 1,
+ "object_id": <object_id>,
+ "alg": "aes-256-gcm",
+ "dek": <32 bytes>,
+ "recipient_kid": <recipient_kid>
+ })
+ Prefer HPKE(KEM=X25519, KDF=HKDF-SHA256, AEAD=ChaCha20-Poly1305 or AES-GCM) so that AAD = object_id || "dek-wrap:v1"
+ is authenticated. If using libsodium sealed boxes as a stopgap, include the above fields inside the sealed payload.
+- Store wraps: key_wraps(object_id, recipient_kid, wrap_ciphertext, alg="hpke-v1")
+- Rekey on membership change; rewrap to active devices only. 📝 Committable suggestion
Suggested change
🧰 Tools🪛 LanguageTool[grammar] ~73-~73: There might be a mistake here. (QB_NEW_EN) [grammar] ~74-~74: There might be a mistake here. (QB_NEW_EN) [grammar] ~74-~74: There might be a mistake here. (QB_NEW_EN) [grammar] ~75-~75: There might be a mistake here. (QB_NEW_EN) 🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||
## 7. Capability Tokens | ||||||||||||||||||||||||||||||||||||
- Format: PASETO v4.public (Ed25519-signed by server capability key). | ||||||||||||||||||||||||||||||||||||
Claims: | ||||||||||||||||||||||||||||||||||||
- sub: user or device id | ||||||||||||||||||||||||||||||||||||
- scope: [object:get|put, room:read, room:write, membership:manage] | ||||||||||||||||||||||||||||||||||||
- resource: URI or prefix (e.g., s3://bucket/path/object-id) | ||||||||||||||||||||||||||||||||||||
- exp: expiry; iat/nbf | ||||||||||||||||||||||||||||||||||||
- region: enforced data residency region (controls bucket/prefix routing) | ||||||||||||||||||||||||||||||||||||
- tid/nonce: unique token id to prevent replay | ||||||||||||||||||||||||||||||||||||
- TTLs: object GET/PUT 15 minutes; room/event scopes 1 hour. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 8. APIs (Server) | ||||||||||||||||||||||||||||||||||||
- POST /devices/attest | ||||||||||||||||||||||||||||||||||||
- POST /devices/revoke | ||||||||||||||||||||||||||||||||||||
- POST /rooms | ||||||||||||||||||||||||||||||||||||
- POST /rooms/:id/members | ||||||||||||||||||||||||||||||||||||
- POST /rooms/:id/rotate | ||||||||||||||||||||||||||||||||||||
- POST /capabilities | ||||||||||||||||||||||||||||||||||||
- PUT /objects/:id (requires capability) | ||||||||||||||||||||||||||||||||||||
- GET /objects/:id (requires capability) | ||||||||||||||||||||||||||||||||||||
- WS /events | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 9. Storage Schema (Postgres + S3-compatible) | ||||||||||||||||||||||||||||||||||||
Residency enforcement: region claim in capabilities is validated and mapped to storage bucket/prefix; mismatches are rejected at capability validation and storage routing. | ||||||||||||||||||||||||||||||||||||
- users(id, identity_pubkey_hash, oidc_sub, region) | ||||||||||||||||||||||||||||||||||||
- devices(id, user_id, ed25519_pub, x25519_pub, attestation_sig, status) | ||||||||||||||||||||||||||||||||||||
- rooms(id, created_by, policy) | ||||||||||||||||||||||||||||||||||||
- memberships(room_id, device_id, role, since, status) | ||||||||||||||||||||||||||||||||||||
- sender_keys(room_id, epoch, key_id, wrapped_keys jsonb, created_at) | ||||||||||||||||||||||||||||||||||||
- objects(id, owner, room_id, bucket, path, blake3_digest, size, manifest_sig, created_at) | ||||||||||||||||||||||||||||||||||||
- key_wraps(object_id, device_id, wrap_ciphertext) | ||||||||||||||||||||||||||||||||||||
- audit_events(id, actor, type, target, ts, meta) | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 10. Backup & Recovery | ||||||||||||||||||||||||||||||||||||
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 }). | ||||||||||||||||||||||||||||||||||||
- Key vault: private keys wrapped by Argon2id-derived KEK; IndexedDB on web; Secure Enclave/Keystore on mobile. | ||||||||||||||||||||||||||||||||||||
- Optional Shamir 2-of-3 recovery (user + admin escrow + HSM) with approvals and audit. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 11. Metadata Minimization | ||||||||||||||||||||||||||||||||||||
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). | ||||||||||||||||||||||||||||||||||||
- Store hashed identity references; coarse timestamps; encrypted membership maps when feasible. | ||||||||||||||||||||||||||||||||||||
- Avoid plaintext titles/tags. No plaintext in logs. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 12. Request Signing & Replay Protection | ||||||||||||||||||||||||||||||||||||
- Client signs sensitive requests with device Ed25519 over canonical payload + timestamp. | ||||||||||||||||||||||||||||||||||||
- Server enforces skew window and tid uniqueness. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
Comment on lines
+134
to
+137
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Request signing: specify canonicalization, covered fields, and replay window Define canonical JSON (e.g., JCS), exact covered headers/query params, and the replay/skew windows to ensure interoperability and security. Apply this diff: -## 12. Request Signing & Replay Protection
-- Client signs sensitive requests with device Ed25519 over canonical payload + timestamp.
-- Server enforces skew window and tid uniqueness.
+## 12. Request Signing & Replay Protection
+- Canonicalization: JCS (RFC 8785) over a payload that includes method, path, sorted query, selected headers (host, content-type, content-length), body hash (BLAKE3), timestamp, and tid.
+- Signature: Ed25519 device key over sign_payload; header: X-Signature: base64(sig), X-Signature-KID: <device_kid>.
+- Server enforces clock skew window ≤ 2 minutes and tid uniqueness for 10 minutes (per tenant).
+- GET/HEAD requests sign an empty body hash; all signed fields must be verified before processing. 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||
## 13. Performance Targets | ||||||||||||||||||||||||||||||||||||
- p95 decrypt < 120 ms for 10 MB on desktop. | ||||||||||||||||||||||||||||||||||||
- Streaming crypto in Web Workers; backpressure-managed I/O. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 14. Rollout & Kill Switch | ||||||||||||||||||||||||||||||||||||
- Feature flags per tenant/room. | ||||||||||||||||||||||||||||||||||||
- Canary cohorts; schema uses sidecar tables for isolation. | ||||||||||||||||||||||||||||||||||||
- Instant kill-switch disables capability issuance for E2EE objects/rooms; existing ciphertext remains intact. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 15. CI/CD and Supply Chain | ||||||||||||||||||||||||||||||||||||
- Renovate/Dependabot with grouped patch/minor; majors manual. | ||||||||||||||||||||||||||||||||||||
- GitHub Actions: lint/typecheck/tests, CodeQL, SCA, SBOM (Syft), container scanning. | ||||||||||||||||||||||||||||||||||||
- Signed commits and releases. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 16. Test Plan (Acceptance Gates) | ||||||||||||||||||||||||||||||||||||
- Unit and property tests for: keygen, provisioning, sealed box wraps, AES-GCM streaming (vectors), manifest sign/verify, PASETO claims/validation, rotation flows. | ||||||||||||||||||||||||||||||||||||
- Integration: 1:1 E2EE chat, file upload/download, membership change triggers rewrap/rotation. | ||||||||||||||||||||||||||||||||||||
- Data residency pinning tests; GDPR DSR exercises. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
## 17. Open Items / Future Work | ||||||||||||||||||||||||||||||||||||
- Evaluate MLS migration path for large rooms. | ||||||||||||||||||||||||||||||||||||
- HPKE support behind wrapping abstraction. | ||||||||||||||||||||||||||||||||||||
- Privacy-preserving analytics with DP budget management per org. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
sequenceDiagram | ||
autonumber | ||
participant C as Client | ||
participant O as Object Store | ||
participant S as Server | ||
|
||
C->>C: Generate DEK | ||
loop Encrypt chunks | ||
C->>C: nonce_i = HKDF(DEK, "file-chunk"||i)[0..12] | ||
C->>C: c_i = AES-GCM(plaintext_i, nonce_i) | ||
C->>C: blake3_i = BLAKE3(c_i) | ||
end | ||
C->>C: blake3_file = BLAKE3(c_*) ; manifest+sig | ||
C->>O: PUT chunks + manifest (ciphertext only) | ||
par share DEK | ||
C->>S: POST key_wrap(device_pkX, sealed_box(DEK)) | ||
and for each recipient | ||
C->>S: POST key_wrap(device_pkY, sealed_box(DEK)) | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
sequenceDiagram | ||
autonumber | ||
participant ND as New Device | ||
participant TD as Trusted Device | ||
participant S as Server | ||
|
||
ND->>ND: Generate Ed25519_sign_D, X25519_D, nonce N, ts | ||
ND->>TD: Display QR: base64url(JSON{device_pubkeys, N, ts, sig=Sign_D(N||ts)}) | ||
TD->>TD: Compute SAS = hash(identity_pub, device_pubkeys, N, ts) -> 7 emojis | ||
ND-->>TD: Human verifies SAS matches | ||
TD->>S: POST /devices/attest(attestation=Sign_T{identity_pub, device_pubkeys, N, ts}) | ||
S-->>TD: 200 OK | ||
S-->>ND: WS event device-verified |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
sequenceDiagram | ||
autonumber | ||
participant A as Admin | ||
participant S as Server | ||
participant C as Clients (room) | ||
|
||
A->>S: Update membership | ||
S-->>C: WS membership-change | ||
C->>C: Rotate sender key; rewrap DEKs | ||
C->>S: POST new wraps | ||
S-->>C: Ack; old wraps invalidated |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"v": 4, | ||
"purpose": "public", | ||
"claims": { | ||
"sub": "device:9b1d2...", | ||
"scope": "object:get", | ||
"resource": "s3://brand-intel-eu/tenant123/objects/abc123", | ||
"exp": 1723939200, | ||
"iat": 1723935600, | ||
"nbf": 1723935600, | ||
"region": "eu", | ||
"tid": "d7b2f0f8-7e2a-4d31-8b4e-7e7c2f0b9d51" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"$schema": "https://json-schema.org/draft/2020-12/schema", | ||
"$id": "https://example.com/schemas/capability.schema.json", | ||
"title": "Capability Claims (PASETO v4.public payload)", | ||
"type": "object", | ||
"required": ["sub", "scope", "resource", "exp", "region", "tid"], | ||
"properties": { | ||
"sub": {"type": "string"}, | ||
"scope": {"type": "string", "enum": ["object:get", "object:put", "room:read", "room:write", "membership:manage"]}, | ||
"resource": {"type": "string"}, | ||
"exp": {"type": "integer"}, | ||
"iat": {"type": "integer"}, | ||
"nbf": {"type": "integer"}, | ||
"region": {"type": "string", "description": "enforced data residency region (controls bucket/prefix routing)"}, | ||
"tid": {"type": "string"} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"$schema": "https://json-schema.org/draft/2020-12/schema", | ||
"$id": "https://example.com/schemas/manifest.schema.json", | ||
"title": "E2EE File Manifest", | ||
"type": "object", | ||
"required": ["version", "algo", "chunk_size", "length", "blake3_file", "chunks", "sig"], | ||
"properties": { | ||
"version": {"type": "integer", "enum": [1]}, | ||
"algo": {"type": "string", "const": "aes-256-gcm"}, | ||
"chunk_size": {"type": "integer", "minimum": 262144, "maximum": 4194304}, | ||
"length": {"type": "integer", "minimum": 0}, | ||
"blake3_file": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, | ||
"chunks": { | ||
"type": "array", | ||
"items": { | ||
"type": "object", | ||
"required": ["index", "offset", "size", "blake3"], | ||
"properties": { | ||
"index": {"type": "integer", "minimum": 0}, | ||
"offset": {"type": "integer", "minimum": 0}, | ||
"size": {"type": "integer", "minimum": 1}, | ||
"blake3": {"type": "string", "pattern": "^[0-9a-f]{64}$"} | ||
} | ||
} | ||
}, | ||
"sig": {"type": "string", "description": "Ed25519 signature over canonicalized manifest without sig"} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,32 @@ | ||||||||||||||||||||||||||||||||
# E2EE Sequence Diagrams (Textual) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
## 1) Device Provisioning (QR + SAS) | ||||||||||||||||||||||||||||||||
1. New device: gen (Ed25519_sign_D, X25519_D), create nonce N and ts | ||||||||||||||||||||||||||||||||
2. New device -> QR payload: base64url(JSON { device_pubkeys, nonce: N, ts, sig: sign_D(N||ts) }) | ||||||||||||||||||||||||||||||||
3. Trusted device scans QR | ||||||||||||||||||||||||||||||||
4. Both devices derive SAS from transcript (hash of {identity_pubkey, device_pubkeys, N, ts}) -> 7 emojis | ||||||||||||||||||||||||||||||||
5. Human verifies SAS match | ||||||||||||||||||||||||||||||||
6. Trusted device signs attestation: sign_T({ user_identity_pub, device_pubkeys, N, ts }) | ||||||||||||||||||||||||||||||||
7. Trusted device -> Server: POST /devices/attest(attestation) | ||||||||||||||||||||||||||||||||
8. Server records device; broadcasts WS event | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
## 2) File Share (Encrypt + Upload + Share DEK) | ||||||||||||||||||||||||||||||||
1. Client picks file; gen random DEK | ||||||||||||||||||||||||||||||||
2. For each chunk i: | ||||||||||||||||||||||||||||||||
- nonce_i = HKDF(DEK, info="file-chunk"||i)[0..12] | ||||||||||||||||||||||||||||||||
- c_i = AES-256-GCM(plaintext_i, nonce_i) | ||||||||||||||||||||||||||||||||
- blake3_i = BLAKE3(c_i) | ||||||||||||||||||||||||||||||||
3. blake3_file = BLAKE3(c_0||c_1||...) | ||||||||||||||||||||||||||||||||
4. Manifest = { version, algo, chunk_size, length, blake3_file, chunks[] } | ||||||||||||||||||||||||||||||||
5. sig = Ed25519.sign(manifest_without_sig) | ||||||||||||||||||||||||||||||||
Comment on lines
+16
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Nonce derivation is ambiguous and MD lint flags reversed-link; specify 96-bit HKDF construction and bind AAD For AES-GCM, nonce uniqueness is critical. Also bind context via AAD to prevent cross-object replay. Fix the “reversed link” warning by avoiding bracket slicing syntax. Apply: - - nonce_i = HKDF(DEK, info="file-chunk"||i)[0..12]
- - c_i = AES-256-GCM(plaintext_i, nonce_i)
+ - Derive 96-bit nonce_i deterministically:
+ Let PRK = HKDF-Extract(salt = blake3_file, IKM = DEK)
+ nonce_i = HKDF-Expand(PRK, info = "nonce:file-chunk:v1" || LE64(i), L = 12) # bytes 0..11
+ - c_i = AES-256-GCM(plaintext_i, nonce_i, AAD = concat("e2ee-file:v1", version, chunk_index=i, object_id, algo, chunk_size))
- blake3_i = BLAKE3(c_i)
-3. blake3_file = BLAKE3(c_0||c_1||...)
+3. blake3_file = BLAKE3(c_0 || c_1 || ...)
-4. Manifest = { version, algo, chunk_size, length, blake3_file, chunks[] }
+4. Manifest = { version, algo, chunk_size, length, blake3_file, chunks[] } # signature excludes sig field
-5. sig = Ed25519.sign(manifest_without_sig)
+5. sig = Ed25519.sign(JCS(manifest_without_sig)) # RFC 8785 canonical JSON 📝 Committable suggestion
Suggested change
🧰 Tools🪛 LanguageTool[grammar] ~16-~16: There might be a mistake here. (QB_NEW_EN) [grammar] ~17-~17: There might be a mistake here. (QB_NEW_EN) [grammar] ~18-~18: There might be a mistake here. (QB_NEW_EN) [grammar] ~19-~19: There might be a mistake here. (QB_NEW_EN) [grammar] ~20-~20: There might be a mistake here. (QB_NEW_EN) [grammar] ~21-~21: There might be a mistake here. (QB_NEW_EN) 🪛 markdownlint-cli2 (0.17.2)16-16: Reversed link syntax (MD011, no-reversed-links) 🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||
6. Upload chunks and manifest (ciphertext only) | ||||||||||||||||||||||||||||||||
7. For each recipient device pk_X: | ||||||||||||||||||||||||||||||||
- wrap = sealed_box(DEK, pk_X) | ||||||||||||||||||||||||||||||||
- POST key_wraps(object_id, device_id, wrap) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
## 3) Membership change -> Rekey | ||||||||||||||||||||||||||||||||
1. Admin changes room members | ||||||||||||||||||||||||||||||||
2. Server emits WS membership-change event | ||||||||||||||||||||||||||||||||
3. Clients rotate sender key and/or rewrap DEKs | ||||||||||||||||||||||||||||||||
4. New wraps stored; old wraps invalidated | ||||||||||||||||||||||||||||||||
5. Capability issuance follows narrow scopes with TTL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarify and harden AES-GCM nonce derivation; bind AAD to prevent misuse
The current nonce formula is ambiguous and risks incorrect implementations. For AES-GCM, nonce uniqueness per DEK is critical. Also, include AAD to bind chunk context to the ciphertext.
Apply this diff to specify an unambiguous, misuse-resistant construction and fix the markdown “reversed link” warning:
📝 Committable suggestion
🧰 Tools
🪛 LanguageTool
[grammar] ~53-~53: There might be a mistake here.
Context: ...info="file-chunk" || chunk_index)[0..12] - AES-256-GCM over each chunk; produce per...
(QB_NEW_EN)
🪛 markdownlint-cli2 (0.17.2)
53-53: Reversed link syntax
(DEK, info="file-chunk" || chunk_index)[0..12]
(MD011, no-reversed-links)
🤖 Prompt for AI Agents