05/22 Update documents
This commit is contained in:
@@ -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)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user