feat(crypto): encrypt username at rest (CODE_AUDIT §1.3)
username is no longer stored cleartext. New columns username_enc/username_iv (AES-GCM under the vault key, same as encrypted_password). Search/sort/render stay client-side, so the field is decrypted at loadEntries into e.username in memory — everything downstream is unchanged. Full-strength random-IV AES-GCM (no searchable/deterministic encryption) precisely because search is client-side. Server (PM.Handler.Entries / .Auth / PM.Database): - Schema: vault_entries.username_enc, username_iv. - GET returns them; POST/PUT/bulk-import read + persist them; master-pw rotation re-encrypts them under the new key (UPDATE + loop). - ?q= server search drops `username LIKE` (ciphertext won't match; frontend searches client-side anyway). Client (app.js / app.import.js): - loadEntries/loadTrash decrypt username_enc → e.username (fallback to cleartext for un-migrated rows). - withEncryptedUsername(obj): write choke point — encrypts obj.username into username_enc/username_iv and blanks the cleartext. Wraps every POST/PUT body: saveEntry, soSave, duplicateEntry, moveEntryToFolder, addTagToEntry, batchMove/AddTag, encryptImportEntry (import + sync-apply). - doChangeMasterPassword re-encrypts username under the new key. - migrateUsernamesAtRest(): one-time sweep at enterApp, PUT-re-ships rows that still carry cleartext username so the DB gets scrubbed (bumps updated_at once; plaintext unchanged so devices converge). site/title/tags stay cleartext (same pattern later — see memory note). +1 merge test (username encrypted on import). 65/65. NOT compiled/tested at runtime (Delphi) — large multi-handler change; rebuild BuildAssets + PMServer and test create/edit/rotate/import/sync + verify the DB shows no cleartext username. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -429,11 +429,27 @@ Résout le cas "j'ai ajouté un mot de passe avec tri A-Z, où se loge-t-il ?"
|
||||
|
||||
Une `vault_entries` row porte **plusieurs blobs chiffrés indépendants** :
|
||||
`encrypted_password/iv`, `totp_secret/totp_iv`, `custom_fields/custom_fields_iv`,
|
||||
**`username_enc/username_iv`** (métadonnée-at-rest, cf. plus bas),
|
||||
plus le champ-icône `icon_b64` et les méta non chiffrées (`site, title,
|
||||
username, folder, tags, kind, template`). `template` est le sous-type
|
||||
folder, tags, kind, template`). `template` est le sous-type
|
||||
(ex: `credit-card`, `ssh-key`, `server`, `recovery-codes`) qui drive le
|
||||
label de card/table — vide pour login/note génériques.
|
||||
|
||||
**`username` chiffré (§1.3)** : la colonne `username` en clair est en voie
|
||||
d'extinction — les nouvelles écritures y mettent `''` et rangent le chiffré
|
||||
dans `username_enc/username_iv` (AES-GCM sous la clé du vault, comme
|
||||
`encrypted_password`). `loadEntries`/`loadTrash` déchiffrent → `e.username`
|
||||
en mémoire, donc **recherche/tri/render/autofill-match marchent inchangés**
|
||||
(tout est déjà côté client). Choke-point d'écriture : `withEncryptedUsername(obj)`
|
||||
(chiffre `obj.username`, blanchit le clair) — enveloppe **chaque** body
|
||||
POST/PUT `/entries` (saveEntry, soSave, duplicateEntry, moveEntryToFolder,
|
||||
addTagToEntry, batchMove/AddTag, encryptImportEntry, migration). Anciennes
|
||||
lignes migrées au unlock par `migrateUsernamesAtRest` (PUT re-ship, bump
|
||||
`updated_at` assumé une fois). Rotation master-pw re-chiffre `username_enc`
|
||||
sous la nouvelle clé (JS loop + UPDATE serveur). Le `?q=` serveur ne LIKE
|
||||
plus que `site`. `site`/`title`/`tags` restent en clair (à chiffrer plus
|
||||
tard, même patron — cf. [[encrypt-metadata-plan]]).
|
||||
|
||||
Quand tu ajoutes un nouveau champ (chiffré ou non), il faut **toujours**
|
||||
mettre à jour ces 6 endroits sous peine de perdre la donnée silencieusement
|
||||
sur certaines actions :
|
||||
|
||||
Reference in New Issue
Block a user