diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/common/Cryptography/Authentication/SRP6.cpp | 4 | ||||
-rw-r--r-- | src/server/authserver/Main.cpp | 2 | ||||
-rw-r--r-- | src/server/authserver/Server/AuthSession.cpp | 64 | ||||
-rw-r--r-- | src/server/authserver/Server/AuthSession.h | 1 | ||||
-rw-r--r-- | src/server/database/Database/Field.cpp | 7 | ||||
-rw-r--r-- | src/server/database/Database/Field.h | 11 | ||||
-rw-r--r-- | src/server/database/Database/Implementation/LoginDatabase.cpp | 24 | ||||
-rw-r--r-- | src/server/database/Database/Implementation/LoginDatabase.h | 4 | ||||
-rw-r--r-- | src/server/database/Database/PreparedStatement.h | 6 | ||||
-rw-r--r-- | src/server/game/Accounts/AccountMgr.cpp | 71 | ||||
-rw-r--r-- | src/server/game/Accounts/AccountMgr.h | 1 | ||||
-rw-r--r-- | src/server/game/Server/WorldSocket.cpp | 2 | ||||
-rw-r--r-- | src/server/worldserver/RemoteAccess/RASession.cpp | 20 |
13 files changed, 153 insertions, 64 deletions
diff --git a/src/common/Cryptography/Authentication/SRP6.cpp b/src/common/Cryptography/Authentication/SRP6.cpp index de05ffd1693..11125a3211b 100644 --- a/src/common/Cryptography/Authentication/SRP6.cpp +++ b/src/common/Cryptography/Authentication/SRP6.cpp @@ -54,7 +54,7 @@ using SRP6 = Trinity::Crypto::SRP6; // merge this into CalculateVerifier once the sha_pass hack finally gets nuked from orbit /*static*/ SRP6::Verifier SRP6::CalculateVerifierFromHash(SHA1::Digest const& hash, SRP6::Salt const& salt) { - return _g.ModExp(SHA1::GetDigestOf(salt, hash), _N).ToByteArray<32>(false); + return _g.ModExp(SHA1::GetDigestOf(salt, hash), _N).ToByteArray<32>(); } /*static*/ SessionKey SRP6::SHA1Interleave(SRP6::EphemeralKey const& S) @@ -88,7 +88,7 @@ using SRP6 = Trinity::Crypto::SRP6; } SRP6::SRP6(std::string const& username, Salt const& salt, Verifier const& verifier) - : _I(SHA1::GetDigestOf(username)), _b(Crypto::GetRandomBytes<19>()), _v(verifier, false), s(salt), B(_B(_b, _v)) {} + : _I(SHA1::GetDigestOf(username)), _b(Crypto::GetRandomBytes<32>()), _v(verifier), s(salt), B(_B(_b, _v)) {} std::optional<SessionKey> SRP6::VerifyChallengeResponse(EphemeralKey const& A, SHA1::Digest const& clientM) { diff --git a/src/server/authserver/Main.cpp b/src/server/authserver/Main.cpp index 7a6a5037676..f5566355a3d 100644 --- a/src/server/authserver/Main.cpp +++ b/src/server/authserver/Main.cpp @@ -143,6 +143,8 @@ int main(int argc, char** argv) sSecretMgr->Initialize(); + AuthSession::ServerStartup(); + // Load IP Location Database sIPLocation->Load(); diff --git a/src/server/authserver/Server/AuthSession.cpp b/src/server/authserver/Server/AuthSession.cpp index 1eb64a7830e..0c803798074 100644 --- a/src/server/authserver/Server/AuthSession.cpp +++ b/src/server/authserver/Server/AuthSession.cpp @@ -28,6 +28,7 @@ #include "Log.h" #include "RealmList.h" #include "SecretMgr.h" +#include "Timer.h" #include "TOTP.h" #include "Util.h" #include <boost/lexical_cast.hpp> @@ -120,6 +121,48 @@ std::array<uint8, 16> VersionChallenge = { { 0xBA, 0xA3, 0x1E, 0x99, 0xA0, 0x0B, #define AUTH_LOGON_CHALLENGE_INITIAL_SIZE 4 #define REALM_LIST_PACKET_SIZE 5 +/*static*/ void AuthSession::ServerStartup() +{ + TC_LOG_INFO("server.authserver", "Updating password hashes..."); + uint32 const start = getMSTime(); + // the auth update query nulls salt/verifier if they cannot be converted + // if they are non-null but s/v have been cleared, that means a legacy tool touched our auth DB (otherwise, the core might've done it itself, it used to use those hacks too) + QueryResult result = LoginDatabase.Query("SELECT id, sha_pass_hash, IF((salt IS null) AND (verifier IS null), 0, 1) AS shouldWarn FROM account WHERE s != DEFAULT(s) OR v != DEFAULT(v) OR salt IS NULL OR verifier IS NULL"); + if (!result) + { + TC_LOG_INFO("server.authserver", ">> No password hashes to update - this took us %u ms to realize", GetMSTimeDiffToNow(start)); + return; + } + + bool hadWarning = false; + uint32 c = 0; + LoginDatabaseTransaction tx = LoginDatabase.BeginTransaction(); + do + { + uint32 const id = (*result)[0].GetUInt32(); + auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationDataFromHash_DEPRECATED_DONOTUSE( + HexStrToByteArray<Trinity::Crypto::SHA1::DIGEST_LENGTH>((*result)[1].GetString()) + ); + + if ((*result)[2].GetInt64() && !hadWarning) + { + hadWarning = true; + TC_LOG_WARN("server.authserver", "(!) You appear to be using an outdated external account management tool.\n(!!) This is INSECURE, has been deprecated, and will cease to function entirely in the near future.\n(!) Update your external tool.\n(!!) If no update is available, refer your tool's developer to https://github.com/TrinityCore/TrinityCore/issues/25157."); + } + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON); + stmt->setBinary(0, salt); + stmt->setBinary(1, verifier); + stmt->setUInt32(2, id); + tx->Append(stmt); + + ++c; + } while (result->NextRow()); + LoginDatabase.CommitTransaction(tx); + + TC_LOG_INFO("server.authserver", ">> %u password hashes updated in %u ms", c, GetMSTimeDiffToNow(start)); +} + std::unordered_map<uint8, AuthHandler> AuthSession::InitHandlers() { std::unordered_map<uint8, AuthHandler> handlers; @@ -398,25 +441,24 @@ void AuthSession::LogonChallengeCallback(PreparedQueryResult result) if (!fields[10].IsNull()) { - // if this is reached, s/v are empty and we need to recalculate them + // if this is reached, s/v were reset and we need to recalculate from sha_pass_hash Trinity::Crypto::SHA1::Digest sha_pass_hash; HexStrToByteArray(fields[10].GetString(), sha_pass_hash); - auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationDataFromHash_DEPRECATED_DONOTUSE(sha_pass_hash); - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_SV); - stmt->setString(0, ByteArrayToHexStr(salt, true)); /* this is actually flipped in the DB right now, old core did hexstr (big endian) -> bignum -> byte array (little-endian) */ - stmt->setString(1, ByteArrayToHexStr(verifier)); + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON); + stmt->setBinary(0, salt); + stmt->setBinary(1, verifier); stmt->setUInt32(2, _accountInfo.Id); LoginDatabase.Execute(stmt); + TC_LOG_WARN("server.authserver", "(!) You appear to be using an outdated external account management tool.\n(!!) This is INSECURE, has been deprecated, and will cease to function entirely in the near future.\n(!) Update your external tool.\n(!!) If no update is available, refer your tool's developer to https://github.com/TrinityCore/TrinityCore/issues/25157."); + _srp6.emplace(_accountInfo.Login, salt, verifier); } else { - Trinity::Crypto::SRP6::Salt salt; - Trinity::Crypto::SRP6::Verifier verifier; - HexStrToByteArray(fields[11].GetString(), salt, true); /* this is actually flipped in the DB right now, old core did hexstr (big endian) -> bignum -> byte array (little-endian) */ - HexStrToByteArray(fields[12].GetString(), verifier); + Trinity::Crypto::SRP6::Salt salt = fields[11].GetBinary<Trinity::Crypto::SRP6::SALT_LENGTH>(); + Trinity::Crypto::SRP6::Verifier verifier = fields[12].GetBinary<Trinity::Crypto::SRP6::VERIFIER_LENGTH>(); _srp6.emplace(_accountInfo.Login, salt, verifier); } @@ -525,7 +567,7 @@ bool AuthSession::HandleLogonProof() // No SQL injection (escaped user name) and IP address as received by socket LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGONPROOF); - stmt->setString(0, ByteArrayToHexStr(_sessionKey)); + stmt->setBinary(0, _sessionKey); stmt->setString(1, GetRemoteIpAddress().to_string()); stmt->setUInt32(2, GetLocaleByName(_localizationName)); stmt->setString(3, _os); @@ -675,7 +717,7 @@ void AuthSession::ReconnectChallengeCallback(PreparedQueryResult result) Field* fields = result->Fetch(); _accountInfo.LoadResult(fields); - HexStrToByteArray(fields[9].GetString(), _sessionKey); + _sessionKey = fields[9].GetBinary<SESSION_KEY_LENGTH>(); Trinity::Crypto::GetRandomBytes(_reconnectProof); _status = STATUS_RECONNECT_PROOF; diff --git a/src/server/authserver/Server/AuthSession.h b/src/server/authserver/Server/AuthSession.h index 9982399bcbc..47973d0ff1d 100644 --- a/src/server/authserver/Server/AuthSession.h +++ b/src/server/authserver/Server/AuthSession.h @@ -65,6 +65,7 @@ class AuthSession : public Socket<AuthSession> typedef Socket<AuthSession> AuthSocket; public: + static void ServerStartup(); static std::unordered_map<uint8, AuthHandler> InitHandlers(); AuthSession(tcp::socket&& socket); diff --git a/src/server/database/Database/Field.cpp b/src/server/database/Database/Field.cpp index f891bfd22b9..b9d25b3da1b 100644 --- a/src/server/database/Database/Field.cpp +++ b/src/server/database/Database/Field.cpp @@ -16,6 +16,7 @@ */ #include "Field.h" +#include "Errors.h" #include "Log.h" #include "MySQLHacks.h" @@ -247,6 +248,12 @@ std::vector<uint8> Field::GetBinary() const return result; } +void Field::GetBinarySizeChecked(uint8* buf, size_t length) const +{ + ASSERT(data.value && (data.length == length), "Expected %zu-byte binary blob, got %sdata (%u bytes) instead", length, data.value ? "" : "no ", data.length); + memcpy(buf, data.value, length); +} + void Field::SetByteValue(char const* newValue, uint32 length) { // This value stores raw bytes that have to be explicitly cast later diff --git a/src/server/database/Database/Field.h b/src/server/database/Database/Field.h index ea7c82d1876..dcb03344031 100644 --- a/src/server/database/Database/Field.h +++ b/src/server/database/Database/Field.h @@ -20,6 +20,7 @@ #include "Define.h" #include "DatabaseEnvFwd.h" +#include <array> #include <vector> enum class DatabaseFieldTypes : uint8 @@ -104,6 +105,14 @@ class TC_DATABASE_API Field char const* GetCString() const; std::string GetString() const; std::vector<uint8> GetBinary() const; + template <size_t S> + std::array<uint8, S> GetBinary() const + { + std::array<uint8, S> buf; + GetBinarySizeChecked(buf.data(), S); + return buf; + } + bool IsNull() const { @@ -129,6 +138,8 @@ class TC_DATABASE_API Field QueryResultFieldMetadata const* meta; void LogWrongType(char const* getter) const; void SetMetadata(QueryResultFieldMetadata const* fieldMeta); + + void GetBinarySizeChecked(uint8* buf, size_t size) const; }; #endif diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index 39337f03963..74a291bf3fe 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -35,20 +35,21 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 AND username = ? GROUP BY account.id", CONNECTION_SYNCH); PrepareStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED, "INSERT INTO account_banned (id, bandate, unbandate, bannedby, banreason, active) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity Auth', 'Failed login autoban', 1)", CONNECTION_ASYNC); PrepareStatement(LOGIN_DEL_ACCOUNT_BANNED, "DELETE FROM account_banned WHERE id = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_UPD_SV, "UPDATE account SET s = ?, v = ? WHERE id = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_UPD_LOGONPROOF, "UPDATE account SET sessionkey = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_SYNCH); + PrepareStatement(LOGIN_UPD_LOGON, "UPDATE account SET salt = ?, verifier = ?, s = DEFAULT, v = DEFAULT WHERE id = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_UPD_LOGON_LEGACY, "UPDATE account SET sha_pass_hash = ? WHERE id = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_UPD_LOGONPROOF, "UPDATE account SET session_key = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_LOGONCHALLENGE, "SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, " - "ab.unbandate = ab.bandate, aa.SecurityLevel, a.totp_secret, IF(length(a.s)<2 OR length(a.v)<2, a.sha_pass_hash, NULL), a.s, a.v " + "ab.unbandate = ab.bandate, aa.SecurityLevel, a.totp_secret, IF(a.s != DEFAULT(a.s) OR a.v != DEFAULT(a.v) OR a.salt IS NULL OR a.verifier IS NULL, a.sha_pass_hash, NULL), a.salt, a.verifier " "FROM account a LEFT JOIN account_access aa ON a.id = aa.AccountID LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_RECONNECTCHALLENGE, "SELECT a.id, UPPER(a.username), a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, " - "ab.unbandate = ab.bandate, aa.SecurityLevel, a.sessionKey " - "FROM account a LEFT JOIN account_access aa ON a.id = aa.AccountID LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ?", CONNECTION_ASYNC); + "ab.unbandate = ab.bandate, aa.SecurityLevel, a.session_key " + "FROM account a LEFT JOIN account_access aa ON a.id = aa.AccountID LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ? AND a.session_key IS NOT NULL", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_FAILEDLOGINS, "UPDATE account SET failed_logins = failed_logins + 1 WHERE username = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME, "SELECT id FROM account WHERE username = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?", CONNECTION_SYNCH); - PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.sessionkey, a.last_ip, a.locked, a.lock_country, a.expansion, a.mutetime, a.locale, a.recruiter, a.os, aa.SecurityLevel, " + PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.session_key, a.last_ip, a.locked, a.lock_country, a.expansion, a.mutetime, a.locale, a.recruiter, a.os, aa.SecurityLevel, " "ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id FROM account a LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?) " - "LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account r ON a.id = r.recruiter WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC); + "LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account r ON a.id = r.recruiter WHERE a.username = ? AND a.session_key IS NOT NULL ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL, "SELECT id, username FROM account WHERE email = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS, "SELECT realmid, numchars FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?", CONNECTION_SYNCH); @@ -61,14 +62,13 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_DEL_REALM_CHARACTERS, "DELETE FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_INS_REALM_CHARACTERS, "INSERT INTO realmcharacters (numchars, acctid, realmid) VALUES (?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_SUM_REALM_CHARACTERS, "SELECT SUM(numchars) FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_INS_ACCOUNT, "INSERT INTO account(username, sha_pass_hash, reg_mail, email, joindate) VALUES(?, ?, ?, ?, NOW())", CONNECTION_SYNCH); + PrepareStatement(LOGIN_INS_ACCOUNT, "INSERT INTO account(username, salt, verifier, reg_mail, email, joindate) VALUES(?, ?, ?, ?, ?, NOW())", CONNECTION_SYNCH); PrepareStatement(LOGIN_INS_REALM_CHARACTERS_INIT, "INSERT INTO realmcharacters (realmid, acctid, numchars) SELECT realmlist.id, account.id, 0 FROM realmlist, account LEFT JOIN realmcharacters ON acctid = account.id WHERE acctid IS NULL", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_EXPANSION, "UPDATE account SET expansion = ? WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_ACCOUNT_LOCK, "UPDATE account SET locked = ? WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_ACCOUNT_LOCK_COUNTRY, "UPDATE account SET lock_country = ? WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_INS_LOG, "INSERT INTO logs (time, realm, type, level, string) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC); - PrepareStatement(LOGIN_UPD_USERNAME, "UPDATE account SET v = 0, s = 0, username = ?, sha_pass_hash = ? WHERE id = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_UPD_PASSWORD, "UPDATE account SET v = 0, s = 0, sha_pass_hash = ? WHERE id = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_UPD_USERNAME, "UPDATE account SET username = ? WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_EMAIL, "UPDATE account SET email = ? WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_REG_EMAIL, "UPDATE account SET reg_mail = ? WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_MUTE_TIME, "UPDATE account SET mutetime = ? , mutereason = ? , muteby = ? WHERE id = ?", CONNECTION_ASYNC); @@ -84,8 +84,8 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_GET_ACCOUNT_ID_BY_USERNAME, "SELECT id FROM account WHERE username = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_GET_GMLEVEL_BY_REALMID, "SELECT SecurityLevel FROM account_access WHERE AccountID = ? AND (RealmID = ? OR RealmID = -1) ORDER BY RealmID DESC", CONNECTION_BOTH); PrepareStatement(LOGIN_GET_USERNAME_BY_ID, "SELECT username FROM account WHERE id = ?", CONNECTION_SYNCH); - PrepareStatement(LOGIN_SEL_CHECK_PASSWORD, "SELECT 1 FROM account WHERE id = ? AND sha_pass_hash = ?", CONNECTION_SYNCH); - PrepareStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME, "SELECT 1 FROM account WHERE username = ? AND sha_pass_hash = ?", CONNECTION_SYNCH); + PrepareStatement(LOGIN_SEL_CHECK_PASSWORD, "SELECT salt, verifier FROM account WHERE id = ? AND salt IS NOT NULL AND verifier IS NOT NULL", CONNECTION_SYNCH); + PrepareStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME, "SELECT salt, verifier FROM account WHERE username = ? AND salt IS NOT NULL AND verifier IS NOT NULL", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_PINFO, "SELECT a.username, aa.SecurityLevel, a.email, a.reg_mail, a.last_ip, DATE_FORMAT(a.last_login, '%Y-%m-%d %T'), a.mutetime, a.mutereason, a.muteby, a.failed_logins, a.locked, a.OS FROM account a LEFT JOIN account_access aa ON (a.id = aa.AccountID AND (aa.RealmID = ? OR aa.RealmID = -1)) WHERE a.id = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_PINFO_BANS, "SELECT unbandate, bandate = unbandate, bannedby, banreason FROM account_banned WHERE id = ? AND active ORDER BY bandate ASC LIMIT 1", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_GM_ACCOUNTS, "SELECT a.username, aa.SecurityLevel FROM account a, account_access aa WHERE a.id = aa.AccountID AND aa.SecurityLevel >= ? AND (aa.RealmID = -1 OR aa.RealmID = ?)", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h index eee8302457c..28eae7180ae 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.h +++ b/src/server/database/Database/Implementation/LoginDatabase.h @@ -38,7 +38,8 @@ enum LoginDatabaseStatements : uint32 LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME, LOGIN_INS_ACCOUNT_AUTO_BANNED, LOGIN_DEL_ACCOUNT_BANNED, - LOGIN_UPD_SV, + LOGIN_UPD_LOGON, + LOGIN_UPD_LOGON_LEGACY, LOGIN_UPD_LOGONPROOF, LOGIN_SEL_LOGONCHALLENGE, LOGIN_SEL_RECONNECTCHALLENGE, @@ -67,7 +68,6 @@ enum LoginDatabaseStatements : uint32 LOGIN_UPD_ACCOUNT_LOCK_COUNTRY, LOGIN_INS_LOG, LOGIN_UPD_USERNAME, - LOGIN_UPD_PASSWORD, LOGIN_UPD_EMAIL, LOGIN_UPD_REG_EMAIL, LOGIN_UPD_MUTE_TIME, diff --git a/src/server/database/Database/PreparedStatement.h b/src/server/database/Database/PreparedStatement.h index e7f39156252..b920289623e 100644 --- a/src/server/database/Database/PreparedStatement.h +++ b/src/server/database/Database/PreparedStatement.h @@ -77,6 +77,12 @@ class TC_DATABASE_API PreparedStatementBase void setDouble(const uint8 index, const double value); void setString(const uint8 index, const std::string& value); void setBinary(const uint8 index, const std::vector<uint8>& value); + template <size_t Size> + void setBinary(const uint8 index, std::array<uint8, Size> const& value) + { + std::vector<uint8> vec(value.begin(), value.end()); + setBinary(index, vec); + } uint32 GetIndex() const { return m_index; } std::vector<PreparedStatementData> const& GetParameters() const { return statement_data; } diff --git a/src/server/game/Accounts/AccountMgr.cpp b/src/server/game/Accounts/AccountMgr.cpp index 36c2e7955cb..19876a4e192 100644 --- a/src/server/game/Accounts/AccountMgr.cpp +++ b/src/server/game/Accounts/AccountMgr.cpp @@ -24,6 +24,7 @@ #include "Player.h" #include "Realm.h" #include "ScriptMgr.h" +#include "SRP6.h" #include "Util.h" #include "World.h" #include "WorldSession.h" @@ -59,9 +60,11 @@ AccountOpResult AccountMgr::CreateAccount(std::string username, std::string pass LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT); stmt->setString(0, username); - stmt->setString(1, CalculateShaPassHash(username, password)); - stmt->setString(2, email); + auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData(username, password); + stmt->setBinary(1, salt); + stmt->setBinary(2, verifier); stmt->setString(3, email); + stmt->setString(4, email); LoginDatabase.DirectExecute(stmt); // Enforce saving, otherwise AddGroup can fail @@ -146,6 +149,13 @@ AccountOpResult AccountMgr::DeleteAccount(uint32 accountId) return AccountOpResult::AOR_OK; } +// Do not use this. Use the appropriate methods on Trinity::Crypto::SRP6 to do whatever you are trying to do. +// See issue #25157. +static std::string CalculateShaPassHash_DEPRECATED_DONOTUSE(std::string const& name, std::string const& password) +{ + return ByteArrayToHexStr(Trinity::Crypto::SHA1::GetDigestOf(name, ":", password)); +} + AccountOpResult AccountMgr::ChangeUsername(uint32 accountId, std::string newUsername, std::string newPassword) { // Check if accounts exists @@ -166,13 +176,24 @@ AccountOpResult AccountMgr::ChangeUsername(uint32 accountId, std::string newUser Utf8ToUpperOnlyLatin(newPassword); stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_USERNAME); - stmt->setString(0, newUsername); - stmt->setString(1, CalculateShaPassHash(newUsername, newPassword)); - stmt->setUInt32(2, accountId); + stmt->setUInt32(1, accountId); + LoginDatabase.Execute(stmt); + auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData(newUsername, newPassword); + stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON); + stmt->setBinary(0, salt); + stmt->setBinary(1, verifier); + stmt->setUInt32(2, accountId); LoginDatabase.Execute(stmt); + { + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON_LEGACY); + stmt->setString(0, CalculateShaPassHash_DEPRECATED_DONOTUSE(newUsername, newPassword)); + stmt->setUInt32(1, accountId); + LoginDatabase.Execute(stmt); + } + return AccountOpResult::AOR_OK; } @@ -194,21 +215,20 @@ AccountOpResult AccountMgr::ChangePassword(uint32 accountId, std::string newPass Utf8ToUpperOnlyLatin(username); Utf8ToUpperOnlyLatin(newPassword); + auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData(username, newPassword); - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_PASSWORD); - - stmt->setString(0, CalculateShaPassHash(username, newPassword)); - stmt->setUInt32(1, accountId); - + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON); + stmt->setBinary(0, salt); + stmt->setBinary(1, verifier); + stmt->setUInt32(2, accountId);; LoginDatabase.Execute(stmt); - stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_SV); - - stmt->setString(0, ""); - stmt->setString(1, ""); - stmt->setString(2, username); - - LoginDatabase.Execute(stmt); + { + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON_LEGACY); + stmt->setString(0, CalculateShaPassHash_DEPRECATED_DONOTUSE(username, newPassword)); + stmt->setUInt32(1, accountId); + LoginDatabase.Execute(stmt); + } sScriptMgr->OnPasswordChange(accountId); return AccountOpResult::AOR_OK; @@ -346,10 +366,16 @@ bool AccountMgr::CheckPassword(uint32 accountId, std::string password) LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_CHECK_PASSWORD); stmt->setUInt32(0, accountId); - stmt->setString(1, CalculateShaPassHash(username, password)); - PreparedQueryResult result = LoginDatabase.Query(stmt); - return (result) ? true : false; + if (PreparedQueryResult result = LoginDatabase.Query(stmt)) + { + Trinity::Crypto::SRP6::Salt salt = (*result)[0].GetBinary<Trinity::Crypto::SRP6::SALT_LENGTH>(); + Trinity::Crypto::SRP6::Verifier verifier = (*result)[1].GetBinary<Trinity::Crypto::SRP6::VERIFIER_LENGTH>(); + if (Trinity::Crypto::SRP6::CheckLogin(username, password, salt, verifier)) + return true; + } + + return false; } bool AccountMgr::CheckEmail(uint32 accountId, std::string newEmail) @@ -379,11 +405,6 @@ uint32 AccountMgr::GetCharactersCount(uint32 accountId) return (result) ? (*result)[0].GetUInt64() : 0; } -std::string AccountMgr::CalculateShaPassHash(std::string const& name, std::string const& password) -{ - return ByteArrayToHexStr(Trinity::Crypto::SHA1::GetDigestOf(name, ":", password)); -} - bool AccountMgr::IsBannedAccount(std::string const& name) { LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME); diff --git a/src/server/game/Accounts/AccountMgr.h b/src/server/game/Accounts/AccountMgr.h index 07a3d694492..446ec93f7a3 100644 --- a/src/server/game/Accounts/AccountMgr.h +++ b/src/server/game/Accounts/AccountMgr.h @@ -73,7 +73,6 @@ class TC_GAME_API AccountMgr static bool GetEmail(uint32 accountId, std::string& email); static uint32 GetCharactersCount(uint32 accountId); - static std::string CalculateShaPassHash(std::string const& name, std::string const& password); static bool IsBannedAccount(std::string const& name); static bool IsPlayerAccount(uint32 gmlevel); static bool IsAdminAccount(uint32 gmlevel); diff --git a/src/server/game/Server/WorldSocket.cpp b/src/server/game/Server/WorldSocket.cpp index 35fc14aa034..90b42221598 100644 --- a/src/server/game/Server/WorldSocket.cpp +++ b/src/server/game/Server/WorldSocket.cpp @@ -270,7 +270,7 @@ struct AccountInfo // LEFT JOIN account r ON a.id = r.recruiter // WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1 Id = fields[0].GetUInt32(); - HexStrToByteArray(fields[1].GetCString(), SessionKey); + SessionKey = fields[1].GetBinary<SESSION_KEY_LENGTH>(); LastIP = fields[2].GetString(); IsLockedToIP = fields[3].GetBool(); LockCountry = fields[4].GetString(); diff --git a/src/server/worldserver/RemoteAccess/RASession.cpp b/src/server/worldserver/RemoteAccess/RASession.cpp index 65fb88a2680..6c8cb8b3468 100644 --- a/src/server/worldserver/RemoteAccess/RASession.cpp +++ b/src/server/worldserver/RemoteAccess/RASession.cpp @@ -20,9 +20,10 @@ #include "Config.h" #include "DatabaseEnv.h" #include "Log.h" +#include "ServerMotd.h" +#include "SRP6.h" #include "Util.h" #include "World.h" -#include "ServerMotd.h" #include <boost/asio/buffer.hpp> #include <boost/asio/read_until.hpp> #include <memory> @@ -159,22 +160,21 @@ bool RASession::CheckPassword(const std::string& user, const std::string& pass) Utf8ToUpperOnlyLatin(safe_pass); std::transform(safe_pass.begin(), safe_pass.end(), safe_pass.begin(), ::toupper); - std::string hash = AccountMgr::CalculateShaPassHash(safe_user, safe_pass); - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_CHECK_PASSWORD_BY_NAME); stmt->setString(0, safe_user); - stmt->setString(1, hash); - - PreparedQueryResult result = LoginDatabase.Query(stmt); - if (!result) + if (PreparedQueryResult result = LoginDatabase.Query(stmt)) { - TC_LOG_INFO("commands.ra", "Wrong password for user: %s", user.c_str()); - return false; + Trinity::Crypto::SRP6::Salt salt = (*result)[0].GetBinary<Trinity::Crypto::SRP6::SALT_LENGTH>(); + Trinity::Crypto::SRP6::Verifier verifier = (*result)[1].GetBinary<Trinity::Crypto::SRP6::VERIFIER_LENGTH>(); + + if (Trinity::Crypto::SRP6::CheckLogin(safe_user, safe_pass, salt, verifier)) + return true; } - return true; + TC_LOG_INFO("commands.ra", "Wrong password for user: %s", user.c_str()); + return false; } bool RASession::ProcessCommand(std::string& command) |