05/22 Update documents

This commit is contained in:
2026-05-22 11:00:30 -04:00
parent f5dc6660c5
commit 78334982e0
2 changed files with 29 additions and 7 deletions
+26 -5
View File
@@ -246,7 +246,7 @@ CREATE TABLE webauthn_credentials (
- **Decrypted vault data:** `chrome.storage.session` only — never to disk
- **Clipboard auto-clear:** 30 s after any password/username copy (web + extension)
- **Breach detection:** HIBP k-anonymity — only 5-char SHA-1 prefix transmitted
- **Account recovery:** challenge-response via HMAC-SHA256; `enc_key_salt` NOT returned by `/recovery/data` — client must derive it by decrypting the recovery blob (proves possession of recovery code without transmitting it); challenge rotated on each `/recovery/items` call to prevent proof replay
- **Account recovery:** challenge-response via HMAC-SHA256; `enc_key_salt` NOT returned by `/recovery/data` — client must derive it by decrypting the recovery blob (proves possession of recovery code without transmitting it); challenge rotated on each `/recovery/items` call to prevent proof replay; recovery key derived with the user's email as a per-user PBKDF2 salt — legacy fixed salt `'passkeeper-recovery'` accepted transparently for codes created before this change
- **folder_id ownership:** validated server-side on all create/update/import operations — user cannot assign items to another user's folder
- **Audit logs:** never contain plaintext item names, shared item names, or vault data
@@ -404,7 +404,7 @@ Every accepted TOTP code is recorded in `totp_used_codes` (user_id + code, 120s
### Argon2 transparent rehash
`verify_auth_token(auth_hash, stored_hash, user=user)` calls `ph.check_needs_rehash()` on success. If the stored hash uses outdated parameters (e.g. after raising `ARGON2_TIME_COST`), the hash is silently upgraded in the same DB commit as the login success. Pass `user=user` at all login callsites.
`verify_auth_token(auth_hash, stored_hash, user=user)` calls `ph.check_needs_rehash()` on success. If the stored hash uses outdated parameters (e.g. after raising `ARGON2_TIME_COST`), the hash is silently upgraded in the same DB commit as the login success. Pass `user=user` at all verification callsites — both `login` and `change_password`.
### folder_id ownership validation
@@ -416,13 +416,32 @@ Every accepted TOTP code is recorded in `totp_used_codes` (user_id + code, 120s
1. GET /recovery/data → server creates challenge; returns nonce + recovery blob
enc_key_salt NOT returned (client must decrypt blob)
2. Client decrypts blob with recovery code → gets enc_key_salt
Recovery key = PBKDF2(recoveryCode, userEmail, 200k iter)
Legacy fallback: PBKDF2(recoveryCode, "passkeeper-recovery", 200k iter)
3. Client computes proof = HMAC-SHA256(enc_key_salt, nonce)
4. GET /recovery/items → validates proof; consumes challenge; re-issues fresh challenge
with same expected_proof + new nonce (prevents proof replay)
Returns { id, enc_data, iv, enc_name, iv_name } per item
5. POST /recover → validates proof again; consumes rotated challenge; atomically
re-encrypts vault + resets password
re-encrypts vault (enc_data + enc_name) + resets password
```
### Password change key rotation
`handleChangePassword` in `vault.js` performs a fully atomic key rotation in a single `POST /api/auth/change-password` call:
1. Derives `newVaultKey = PBKDF2(newPassword, newEncKeySalt, 600k iter)`
2. Re-encrypts every vault item's `enc_data` + `enc_name` with `newVaultKey`
3. Re-encrypts the ECDH sharing private key at the raw bytes level:
- Fetches `GET /api/sharing/keys` → gets `private_key_enc` / `private_key_iv`
- `AES-GCM.decrypt(vaultKey, ...)` → raw private key bytes
- `AES-GCM.encrypt(newVaultKey, newIv, ...)` → new ciphertext
- Sends `sharing_private_key_enc` / `sharing_private_key_iv` in the payload
4. Server atomically updates `master_hash`, `enc_key_salt`, all vault items,
and `sharing_private_key_enc`/`sharing_private_key_iv` in one transaction
The sharing key uses raw `SubtleCrypto` calls (not `SharingCrypto.decryptPrivateKey`) because `decryptPrivateKey` imports the key with `extractable: false`, making it impossible to re-export the bytes for re-encryption.
### Shared item name encryption
When creating a share, the client encrypts `item.name` with `SharingCrypto.encryptName(sharedKey, name)``enc_name`/`iv_name`. The server receives `item_name = item.item_type` (non-sensitive type label) for the `NOT NULL` column. On the recipient's inbox, `enc_name`/`iv_name` are stored in data attributes on the View button and decrypted client-side with `SharingCrypto.decryptName()` when the user clicks View. Legacy shares (pre-migration, no `enc_name`) fall back to showing `item_name` (the type string).
@@ -555,11 +574,13 @@ Audit log details **never** contain plaintext item names, shared item names, or
- TOTP key generation: `python -c "import secrets; print(secrets.token_hex(32))"`
- `#vault-list` ID must not be renamed — `vault.js` renders into it directly
- `_validate_folder_id` must be called for any user-supplied `folder_id` before DB write
- `verify_auth_token` must receive `user=user` at login to enable Argon2 rehash
- APScheduler cleanup job handles `TokenBlacklist`, `RecoveryChallenge`, AND `TotpUsedCode`
- `verify_auth_token` must receive `user=user` at both `login` and `change_password` to enable Argon2 rehash
- APScheduler cleanup job handles `TokenBlacklist`, `RecoveryChallenge`, AND `TotpUsedCode`; guard with `os.environ.get('WERKZEUG_RUN_MAIN') == 'true'` in Flask debug mode to prevent double-start
- `password_changed_at` lives inside `plain` (encrypted) — never in the server schema
- WebAuthn `attachment`: `"cross-platform"` for security keys; `"platform"` for device biometrics (default)
- `enc_vault_is_legacy` check in `EmergencyAccess.to_dict()` is pure JSON inspection — no decryption
- Never return `str(e)` from exception handlers — log with `_log.exception(...)` and return a generic user-facing message to avoid leaking DB schema details or query fragments
- `SharingCrypto.decryptPrivateKey` imports the key with `extractable: false` — use raw `SubtleCrypto` calls when you need the key bytes (e.g. re-encryption on password change)
---