[Server] Store a scrypt verifier when an admin resets a password (#7679)

The temporary password was written as a legacy salt+hash row, which makes the
next login look like an authentication downgrade. A client holding a saved
verifier refuses that downgrade, so the reset locked the account out of every
modern client.

Store the verifier and, where security/legacy_backup_key is configured, keep a
sealed legacy hash of the same temporary password as the downgrade backup, so
pre-challenge clients keep the credential path the reset used to provide.

Without the key no row shape serves both: the verifier and the legacy hash
cannot share one column, so those clients have no path at all. Warn at reset
time that the new password has no pre-challenge path, and say so in
servatrice.ini.example instead of promising that a reset gets them back in.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-11-02 07:00:48 +01:00 • committed by GitHub
parent e08402ac94
commit ef4574d2b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 91 additions and 16 deletions

View file

@ -2641,7 +2641,15 @@ Response::ResponseCode AbstractServerSocketInterface::cmdResetUserPassword(const
}
const QString tempPassword = PasswordHasher::generateRandomSalt();
if (!sqlInterface->changeUserPassword(userName, tempPassword, true)) {
// Store a scrypt verifier rather than a legacy salt+hash: a legacy row makes the next
// login look like an authentication downgrade, which modern clients refuse when they
// still hold a verifier for the account. Where security/legacy_backup_key is configured,
// keep a sealed legacy hash of the same temporary password as the downgrade backup so
// pre-challenge clients keep the path the reset used to provide; without the key they
// have none, which changeUserPasswordWithLegacyBackup warns about.
const QString verifier = PasswordHasher::generatePasswordVerifier(tempPassword);
const QString legacyCredential = PasswordHasher::computeHash(tempPassword, PasswordHasher::generateRandomSalt());
if (!sqlInterface->changeUserPasswordWithLegacyBackup(userName, verifier, legacyCredential)) {
return Response::RespInternalError;
}
sqlInterface->setForcePasswordChange(userName, true);