[Security] Harden credential format acceptance and challenge nonce validation

This commit is contained in:
Lukas Brübach 2026-09-02 20:12:44 +02:00 committed by GitHub
parent 709233d977
commit 46be02fbcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 43 additions and 16 deletions

View file

@ -1,5 +1,8 @@
#include "server_protocolhandler.h"
// Challenge-response nonces are valid for at most one minute from issuance.
static constexpr qint64 kAuthNonceLifetimeSeconds = 60;
#include "game/game_config.h"
#include "game/server_game.h"
#include "game/server_player.h"
@ -44,20 +47,26 @@ Server_ProtocolHandler::~Server_ProtocolHandler()
{
}
void Server_ProtocolHandler::setAuthNonce(const QByteArray &nonce)
void Server_ProtocolHandler::setAuthNonce(const QByteArray &nonce, const QString &userName)
{
authNonce = nonce;
authNonceUser = userName;
authNonceCreated = QDateTime::currentDateTimeUtc();
}
bool Server_ProtocolHandler::isAuthNonceValid(const QByteArray &nonce) const
bool Server_ProtocolHandler::isAuthNonceValid(const QByteArray &nonce, const QString &userName) const
{
return !authNonce.isEmpty() && authNonce == nonce && authNonceCreated.secsTo(QDateTime::currentDateTimeUtc()) < 60;
// secsTo is signed, so a wall-clock step backwards (NTP correction, VM resume)
// must not make the elapsed time negative and re-validate an old nonce.
const qint64 elapsed = authNonceCreated.secsTo(QDateTime::currentDateTimeUtc());
return !authNonce.isEmpty() && authNonce == nonce && authNonceUser == userName && authNonceCreated.isValid() &&
elapsed >= 0 && elapsed < kAuthNonceLifetimeSeconds;
}
void Server_ProtocolHandler::clearAuthNonce()
{
authNonce.clear();
authNonceUser.clear();
}
// This function must only be called from the thread this object lives in.

View file

@ -58,6 +58,7 @@ protected:
bool acceptsRoomListChanges;
bool idleClientWarningSent;
QByteArray authNonce;
QString authNonceUser;
QDateTime authNonceCreated;
virtual void logDebugMessage(const QString & /* message */)
{
@ -128,10 +129,10 @@ public:
return databaseInterface;
}
/** @brief Store a fresh challenge nonce for the next challenge-response login attempt. */
void setAuthNonce(const QByteArray &nonce);
/** @brief True if nonce matches the pending one and was issued less than 60 seconds ago. */
bool isAuthNonceValid(const QByteArray &nonce) const;
/** @brief Store a fresh challenge nonce bound to @p userName for the next challenge-response login attempt. */
void setAuthNonce(const QByteArray &nonce, const QString &userName);
/** @brief True if nonce matches the pending one, was issued for @p userName, and is less than 60 seconds old. */
bool isAuthNonceValid(const QByteArray &nonce, const QString &userName) const;
/** @brief Invalidate the pending nonce (single-use). */
void clearAuthNonce();