mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 17:15:09 -07:00
[Security] Add challenge-response auth with scrypt verifiers and stop storing plaintext passwords
Challenge-response authentication: the client derives a scrypt verifier (RFC 7914, EVP_PBE_scrypt, N=32768, r=8, p=1) and authenticates with HMAC-SHA256(key, nonce), so neither the password nor its hash is transmitted. The stored format becomes "$scrypt$<n>$<r>$<p>$<salt>$<verifier>" and Response_PasswordSalt now carries the cost parameters. Strict servers only accept scrypt verifiers; legacy accounts are migrated after a successful login. Fix #344 for challenge-response servers: a saved profile stores the derived verifier under the password key instead of the plaintext password. The connect dialog loads it without revealing it, autoconnect passes it through, the change-password dialog no longer prefills the old password field with it, and the client only persists the verifier when "Save password" is checked. Took 3 minutes Took 1 minute Took 10 seconds
This commit is contained in:
parent
5257a8bfa8
commit
088e932882
33 changed files with 675 additions and 23 deletions
|
|
@ -354,6 +354,41 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot
|
|||
qCWarning(DatabaseInterfaceLog) << "Login denied: user not active";
|
||||
return UserIsInactive;
|
||||
}
|
||||
|
||||
if (password.startsWith("$challenge$")) {
|
||||
// Challenge-response login: verify HMAC(stored_key, nonce) without
|
||||
// ever transmitting the stored credential or password hash.
|
||||
const QStringList parts = password.split("$");
|
||||
if (parts.size() != 4) {
|
||||
return NotLoggedIn;
|
||||
}
|
||||
const QByteArray nonce = QByteArray::fromBase64(parts.at(2).toUtf8());
|
||||
const QByteArray response = QByteArray::fromBase64(parts.at(3).toUtf8());
|
||||
if (nonce.isEmpty() || response.isEmpty() || !handler->isAuthNonceValid(nonce)) {
|
||||
return NotLoggedIn;
|
||||
}
|
||||
|
||||
QByteArray key;
|
||||
if (PasswordHasher::isLegacyFormat(correctPasswordSha512)) {
|
||||
key = correctPasswordSha512.toUtf8();
|
||||
} else {
|
||||
const PasswordVerifier verifier = PasswordHasher::parsePasswordVerifier(correctPasswordSha512);
|
||||
if (!verifier.isValid) {
|
||||
return NotLoggedIn;
|
||||
}
|
||||
key = verifier.verifier;
|
||||
}
|
||||
|
||||
const QByteArray expected = PasswordHasher::computeResponse(key, nonce);
|
||||
handler->clearAuthNonce();
|
||||
if (PasswordHasher::constantTimeEquals(expected, response)) {
|
||||
qCDebug(DatabaseInterfaceLog) << "Login accepted: challenge-response password right";
|
||||
return PasswordRight;
|
||||
}
|
||||
qCDebug(DatabaseInterfaceLog) << "Login denied: challenge-response password wrong";
|
||||
return NotLoggedIn;
|
||||
}
|
||||
|
||||
QString hashedPassword;
|
||||
if (passwordNeedsHash) {
|
||||
hashedPassword = PasswordHasher::computeHash(password, correctPasswordSha512.left(16));
|
||||
|
|
@ -552,6 +587,47 @@ QString Servatrice_DatabaseInterface::getUserSalt(const QString &user)
|
|||
return {};
|
||||
}
|
||||
|
||||
QString Servatrice_DatabaseInterface::getUserPasswordData(const QString &user)
|
||||
{
|
||||
if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) {
|
||||
return {};
|
||||
}
|
||||
|
||||
checkSql();
|
||||
|
||||
QSqlQuery *query = prepareQuery("SELECT password_sha512 FROM {prefix}_users WHERE name = :name");
|
||||
query->bindValue(":name", user);
|
||||
if (!execSqlQuery(query)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!query->next()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return query->value(0).toString();
|
||||
}
|
||||
|
||||
bool Servatrice_DatabaseInterface::submitPasswordVerifier(const QString &user, const QString &passwordVerifier)
|
||||
{
|
||||
if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) {
|
||||
return false;
|
||||
}
|
||||
|
||||
checkSql();
|
||||
|
||||
// Only migrate accounts that still use the legacy format; the query is a no-op otherwise.
|
||||
QSqlQuery *query = prepareQuery(
|
||||
"update {prefix}_users set password_sha512 = :verifier where name = :user and password_sha512 not like '$%'");
|
||||
query->bindValue(":verifier", passwordVerifier);
|
||||
query->bindValue(":user", user);
|
||||
if (!execSqlQuery(query)) {
|
||||
qCWarning(DatabaseInterfaceLog) << "Failed to submit password verifier for user" << user << query->lastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int Servatrice_DatabaseInterface::getUserIdInDB(const QString &name)
|
||||
{
|
||||
if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue