diff --git a/docs/adr/zero-knowledge-encryption.md b/docs/adr/zero-knowledge-encryption.md index 2b6be96..444cc6d 100644 --- a/docs/adr/zero-knowledge-encryption.md +++ b/docs/adr/zero-knowledge-encryption.md @@ -1,7 +1,7 @@ # ADR: Zero-Knowledge Encryption (Phase 1) -**Status**: Implemented (Phase 1) -**Date**: 2026-06-28 +**Status**: Implemented (Phase 1 + Phase 2) +**Date**: 2026-06-28 (Phase 1), 2026-06-29 (Phase 2) ## Context @@ -56,10 +56,31 @@ re-derive it. This is the core ZK trade-off. - No data migration was needed (no real user data existed). ### Phase 2 (future) -- Recovery key-wrapping (wrap the enc key under a recovery-derived key so a - forgotten password doesn't lose data). -- Password-change re-wrapping (re-wrap the enc key under the new password). -- Wrapped-DEK model (separate data-encryption key + key-encryption key). +- ~~Recovery key-wrapping (wrap the enc key under a recovery-derived key so a + forgotten password doesn't lose data).~~ **Done (Phase 2).** +- ~~Password-change re-wrapping (re-wrap the enc key under the new password).~~ **Done (Phase 2).** +- ~~Wrapped-DEK model (separate data-encryption key + key-encryption key).~~ **Done (Phase 2).** + +## Phase 2: Wrapped-DEK Recovery (Implemented) + +Phase 2 introduced the wrapped-DEK model so a forgotten password doesn't lose +all encrypted data: + +- A **random DEK** (data encryption key) encrypts all user data (not a key + derived directly from the password). +- The DEK is **wrapped** (AES-GCM encrypted) under a password-derived KEK and + a recovery-phrase-derived KEK. Both wrapped forms are stored on the server. +- At login, the client derives the password KEK and **unwraps** the DEK. +- At recovery, the client derives the recovery KEK, unwraps the DEK from the + recovery-wrapped form, **re-wraps** it under the new password, and sends the + new wrapped form to the server. +- Password change also re-wraps the DEK under the new password. +- New endpoints: `GET /api/auth/recovery-info` (returns the recovery-wrapped + DEK), updated `POST /api/auth/recover-password` (accepts new wrapped DEK). +- New frontend: `RecoveryPage` (email → recovery phrase → new password flow), + recovery phrase field on `RegisterPage`. + +The server never holds the DEK or any KEK — only the wrapped forms. ## Relationship to the original design doc