feat(security): decouple the login verifier from the AES vault key
The zero-knowledge verifier sent to /login used to be the raw PBKDF2 output in hex — i.e. the exact bytes of the AES key that encrypts every entry. Intercepting a /login body (loopback, but still) handed over the vault key. This introduces a decoupled scheme where the transmitted verifier is a one-way function of the key. New auth-hash scheme - users.hash_algo 'pbkdf2-sha256-v2': the client sends verifier = SHA256(keyHex + "pmserver/auth-verifier/v2") instead of keyHex. Stored form is still SHA256(verifier) (identical server wrap to 'pbkdf2-sha256'), so only the algo LABEL differs — it tells the client which verifier formula to use. Verification needs no new server branch (VerifierToStoredHash already SHA256-wraps any non-legacy verifier). - The AES key (cryptoKey) stays hex(PBKDF2) for EVERY algo, so entries remain decryptable and switching schemes never re-encrypts data. Adoption: new-registration + master-pw-change only - Register and change-master-password write v2. Existing accounts keep their algo until they rotate — the login/reauth migration signal now fires only for LEGACY 'pbkdf2' (was: anything != CURRENT), so sha256/v2 accounts are never force-migrated (which would have downgraded v2 → sha256 via migrate-kdf). Client (js/app.js): algo-aware everywhere - verifierFromKeyHex(keyHex, algo) central helper; deriveKeyAndVerifier / computeVerifier take an algo arg. state.hashAlgo caches the account scheme, set from /login/challenge, register, change-master, the quick-unlock / PIN cold-start blobs, and the /recovery-key/redeem response. All ~12 verifier sites updated (login, register, reauth ×4, change-master current+new, migrate-kdf, quick-unlock + PIN cold-start, recovery-mode current verifier). Safety invariant: unknown/empty hashAlgo → key hex → byte-identical to the old behaviour, so every pre-decoupling account (and every existing quick-unlock / PIN blob without the new field) keeps working unchanged. Verified: existing account + pre-change quick-unlock still unlocks; a master-pw change now writes 'pbkdf2-sha256-v2' in vault.db. Server: recovery redeem returns hashAlgo; register + change-master store the decoupled algo; login + reauth migration signal narrowed to legacy. Also: BuildAssets.ps1 pipes $null into node --check so the JS syntax gate can't block on stdin in the Delphi pre-build environment. Addresses CODE_AUDIT.md section 1.1. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -273,7 +273,7 @@ procedure HandleRedeem(ARequest: TIdHTTPRequestInfo;
|
||||
var
|
||||
LBody, LObj: TJSONObject;
|
||||
LUser, LCode, LCodeHash, LIP, LStoredHash, LKdfSalt, LWrappedKey, LWrappedIv,
|
||||
LSalt, LToken, LCSRF: string;
|
||||
LSalt, LToken, LCSRF, LAlgo: string;
|
||||
LUserId, LKdfIters, LCurrentUses, LNewUses: Integer;
|
||||
LQ: TFDQuery;
|
||||
begin
|
||||
@@ -309,7 +309,7 @@ begin
|
||||
LQ.Connection := DB.Connection;
|
||||
// Join to users to look up by username + verify the code in one shot.
|
||||
LQ.SQL.Text :=
|
||||
'SELECT u.id, u.salt, u.kdf_iterations, ' +
|
||||
'SELECT u.id, u.salt, u.kdf_iterations, u.hash_algo, ' +
|
||||
' rk.code_hash, rk.kdf_salt, rk.wrapped_key, rk.wrapped_iv, rk.remaining_uses ' +
|
||||
'FROM users u ' +
|
||||
'LEFT JOIN recovery_keys rk ON rk.user_id = u.id ' +
|
||||
@@ -326,6 +326,7 @@ begin
|
||||
LUserId := LQ.FieldByName('id').AsInteger;
|
||||
LSalt := LQ.FieldByName('salt').AsString;
|
||||
LKdfIters := LQ.FieldByName('kdf_iterations').AsInteger;
|
||||
LAlgo := LQ.FieldByName('hash_algo').AsString;
|
||||
LStoredHash := LQ.FieldByName('code_hash').AsString;
|
||||
LKdfSalt := LQ.FieldByName('kdf_salt').AsString;
|
||||
LWrappedKey := LQ.FieldByName('wrapped_key').AsString;
|
||||
@@ -401,6 +402,7 @@ begin
|
||||
LObj.AddPair('csrfToken', LCSRF);
|
||||
LObj.AddPair('salt', LSalt);
|
||||
LObj.AddPair('kdfIterations', TJSONNumber.Create(LKdfIters));
|
||||
LObj.AddPair('hashAlgo', LAlgo);
|
||||
LObj.AddPair('wrappedKey', LWrappedKey);
|
||||
LObj.AddPair('wrappedIv', LWrappedIv);
|
||||
LObj.AddPair('kdfSalt', LKdfSalt);
|
||||
|
||||
Reference in New Issue
Block a user