diff options
author | Treeston <treeston.mmoc@gmail.com> | 2020-08-02 22:52:21 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2020-08-03 22:37:47 +0200 |
commit | 73922d2a857614d27ddb9dfa517687b8018e5d39 (patch) | |
tree | 2d1d2babf54728e4e784138b00105eaf042f35df | |
parent | 77380f032b772ff180b6b663d241079a12eb608b (diff) |
Core/Authserver: Re-organize the `accounts` table (PR #25135)
- no longer use sha_pass_hash for anything else core-side (.account, SOAP, RA)
- salt/verifier/session_key are now binary
- old s/v/sha_pass_hash fields kept around for backwards compatibility
- sha_pass_hash is still updated (for now), s/v are not
- sha_pass_hash is only read if s/v have been manually changed
- SRP6 b now uses the full 32 bytes of randomness (instead of randomly only using 19)
(cherry picked from commit 3164b58c7d170810b69378950c0891e5f5b8678b)
-rw-r--r-- | sql/base/auth_database.sql | 11 | ||||
-rw-r--r-- | sql/updates/auth/master/2020_08_02_00_auth.sql | 13 | ||||
-rw-r--r-- | src/common/Cryptography/Authentication/SRP6.cpp | 11 | ||||
-rw-r--r-- | src/server/bnetserver/Main.cpp | 2 | ||||
-rw-r--r-- | src/server/bnetserver/Server/SessionManager.cpp | 45 | ||||
-rw-r--r-- | src/server/bnetserver/Server/SessionManager.h | 2 | ||||
-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 | 78 | ||||
-rw-r--r-- | src/server/game/Accounts/AccountMgr.h | 1 | ||||
-rw-r--r-- | src/server/game/Server/WorldSocket.cpp | 12 | ||||
-rw-r--r-- | src/server/shared/Realm/RealmList.cpp | 2 | ||||
-rw-r--r-- | src/server/worldserver/RemoteAccess/RASession.cpp | 18 |
16 files changed, 181 insertions, 66 deletions
diff --git a/sql/base/auth_database.sql b/sql/base/auth_database.sql index 831129f828a..312ce07c731 100644 --- a/sql/base/auth_database.sql +++ b/sql/base/auth_database.sql @@ -25,10 +25,12 @@ DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Identifier', `username` varchar(32) NOT NULL DEFAULT '', + `salt` BINARY(32), + `verifier` BINARY(32), + `session_key` BINARY(40) DEFAULT NULL, `sha_pass_hash` varchar(40) NOT NULL DEFAULT '', - `sessionkey` varchar(128) NOT NULL DEFAULT '', - `v` varchar(64) NOT NULL DEFAULT '', - `s` varchar(64) NOT NULL DEFAULT '', + `v` varchar(64) NOT NULL DEFAULT 'dummy value, use `verifier` instead', + `s` varchar(64) NOT NULL DEFAULT 'dummy value, use `salt` instead', `token_key` varchar(100) NOT NULL DEFAULT '', `email` varchar(255) NOT NULL DEFAULT '', `reg_mail` varchar(255) NOT NULL DEFAULT '', @@ -2363,7 +2365,8 @@ INSERT INTO `updates` VALUES ('2020_07_03_00_auth.sql','ED7175E51F248ADC5EF60E7CEA9627CC3191ED4C','RELEASED','2020-07-03 20:09:39',0), ('2020_07_23_00_auth.sql','5F47E1CEECA9F837C85C2DAC7ECD47AED321F502','RELEASED','2020-07-23 19:54:42',0), ('2020_07_24_00_auth.sql','06598162E9C84DDF8AA1F83D0410D056C3F7F69E','RELEASED','2020-07-24 00:44:34',0), -('2020_07_25_00_auth.sql','BE376B619ECB6FE827270D5022F311E20AD6E177','RELEASED','2020-07-25 00:00:49',0); +('2020_07_25_00_auth.sql','BE376B619ECB6FE827270D5022F311E20AD6E177','RELEASED','2020-07-25 00:00:49',0), +('2020_08_02_00_auth.sql','B0290F6558C59262D9DDD8071060A8803DD56930','ARCHIVED','2020-08-02 00:00:00',0); /*!40000 ALTER TABLE `updates` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/updates/auth/master/2020_08_02_00_auth.sql b/sql/updates/auth/master/2020_08_02_00_auth.sql new file mode 100644 index 00000000000..035ca4c4367 --- /dev/null +++ b/sql/updates/auth/master/2020_08_02_00_auth.sql @@ -0,0 +1,13 @@ +-- AUTH CLEANUP PHASE 3 +-- update `account` structure +-- sha_pass_hash/s/v kept around for now, for backwards compatibility +ALTER TABLE `account` + DROP COLUMN `sessionkey`, + ADD COLUMN `salt` BINARY(32) AFTER `username`, + ADD COLUMN `verifier` BINARY(32) AFTER `salt`, + ADD COLUMN `session_key` BINARY(40) AFTER `verifier`, + MODIFY COLUMN `s` VARCHAR(64) NOT NULL DEFAULT 'dummy value, use `salt` instead', + MODIFY COLUMN `v` VARCHAR(64) NOT NULL DEFAULT 'dummy value, use `verifier` instead'; + +UPDATE `account` SET `salt`=REVERSE(UNHEX(`s`)), `s`=DEFAULT WHERE LENGTH(`s`)=64; +UPDATE `account` SET `verifier`=REVERSE(UNHEX(`v`)), `v`=DEFAULT WHERE LENGTH(`v`)=64; diff --git a/src/common/Cryptography/Authentication/SRP6.cpp b/src/common/Cryptography/Authentication/SRP6.cpp index c8b80d58f9b..4d96a28960d 100644 --- a/src/common/Cryptography/Authentication/SRP6.cpp +++ b/src/common/Cryptography/Authentication/SRP6.cpp @@ -24,7 +24,12 @@ using SHA1 = Trinity::Crypto::SHA1; using SRP6 = Trinity::Crypto::SRP6; -/*static*/ std::array<uint8, 1> const SRP6::g = { 7 }; +/*static*/ std::array<uint8, 1> const SRP6::g = []() +{ + std::array<uint8, 1> g_temp; + g_temp[0] = 7; + return g_temp; +}(); /*static*/ std::array<uint8, 32> const SRP6::N = HexStrToByteArray<32>("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", true); /*static*/ BigNumber const SRP6::_g(SRP6::g); /*static*/ BigNumber const SRP6::_N(N); @@ -54,7 +59,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 +93,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)) {} Optional<SessionKey> SRP6::VerifyChallengeResponse(EphemeralKey const& A, SHA1::Digest const& clientM) { diff --git a/src/server/bnetserver/Main.cpp b/src/server/bnetserver/Main.cpp index c2590271b98..7ffc504eeab 100644 --- a/src/server/bnetserver/Main.cpp +++ b/src/server/bnetserver/Main.cpp @@ -154,6 +154,8 @@ int main(int argc, char** argv) if (!StartDB()) return 1; + sSessionMgr.FixLegacyAuthHashes(); + // Load IP Location Database sIPLocation->Load(); diff --git a/src/server/bnetserver/Server/SessionManager.cpp b/src/server/bnetserver/Server/SessionManager.cpp index 3336df4892a..e503bba4219 100644 --- a/src/server/bnetserver/Server/SessionManager.cpp +++ b/src/server/bnetserver/Server/SessionManager.cpp @@ -16,6 +16,9 @@ */ #include "SessionManager.h" +#include "DatabaseEnv.h" +#include "SRP6.h" +#include "Util.h" bool Battlenet::SessionManager::StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount) { @@ -27,6 +30,48 @@ bool Battlenet::SessionManager::StartNetwork(Trinity::Asio::IoContext& ioContext return true; } +void Battlenet::SessionManager::FixLegacyAuthHashes() +{ + TC_LOG_INFO("server.bnetserver", "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.bnetserver", ">> 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(); + std::pair<Trinity::Crypto::SRP6::Salt, Trinity::Crypto::SRP6::Verifier> registrationData = 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.bnetserver", "(!) 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, registrationData.first); + stmt->setBinary(1, registrationData.second); + stmt->setUInt32(2, id); + tx->Append(stmt); + + ++c; + } while (result->NextRow()); + LoginDatabase.CommitTransaction(tx); + + TC_LOG_INFO("server.bnetserver", ">> %u password hashes updated in %u ms", c, GetMSTimeDiffToNow(start)); +} + NetworkThread<Battlenet::Session>* Battlenet::SessionManager::CreateThreads() const { return new NetworkThread<Session>[GetNetworkThreadCount()]; diff --git a/src/server/bnetserver/Server/SessionManager.h b/src/server/bnetserver/Server/SessionManager.h index f2c3c19fa64..ae0e714971a 100644 --- a/src/server/bnetserver/Server/SessionManager.h +++ b/src/server/bnetserver/Server/SessionManager.h @@ -32,6 +32,8 @@ namespace Battlenet bool StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount = 1) override; + void FixLegacyAuthHashes(); + protected: NetworkThread<Session>* CreateThreads() const override; diff --git a/src/server/database/Database/Field.cpp b/src/server/database/Database/Field.cpp index 1a5f74348c0..657cc062409 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 5a2217acf49..91667c2b483 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -33,16 +33,17 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_ALL, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 GROUP BY account.id", CONNECTION_SYNCH); 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 LIKE CONCAT('%%', ?, '%%') GROUP BY account.id", CONNECTION_SYNCH); PrepareStatement(LOGIN_DEL_ACCOUNT_BANNED, "DELETE FROM account_banned WHERE id = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_UPD_ACCOUNT_INFO_CONTINUED_SESSION, "UPDATE account SET sessionkey = ? WHERE id = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_CONTINUED_SESSION, "SELECT username, sessionkey FROM account WHERE id = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_UPD_SV, "UPDATE account SET s = ?, v = ? WHERE id = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_UPD_ACCOUNT_INFO_CONTINUED_SESSION, "UPDATE account SET session_key = ? WHERE id = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_CONTINUED_SESSION, "SELECT username, session_key FROM account WHERE id = ? AND LENGTH(session_key) = 40", CONNECTION_ASYNC); + 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_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, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.SecurityLevel, " + PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.session_key, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.SecurityLevel, " "bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id " "FROM account a LEFT JOIN account r ON a.id = r.recruiter LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id " "LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?) LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 " - "WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC); + "WHERE a.username = ? AND LENGTH(a.session_key) = 64 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_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?", CONNECTION_SYNCH); @@ -55,14 +56,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, battlenet_account, battlenet_index) 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_INS_ACCOUNT, "INSERT INTO account(username, salt, verifier, reg_mail, email, joindate, battlenet_account, battlenet_index) 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); @@ -79,8 +79,8 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_GET_ACCOUNT_ACCESS_GMLEVEL, "SELECT SecurityLevel FROM account_access WHERE AccountID = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_GET_GMLEVEL_BY_REALMID, "SELECT SecurityLevel FROM account_access WHERE AccountID = ? AND (RealmID = ? OR RealmID = -1)", CONNECTION_SYNCH); 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); @@ -123,7 +123,7 @@ void LoginDatabaseConnection::DoPrepareStatements() " FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account a ON ba.id = a.battlenet_account" " LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID = -1 WHERE ba.LoginTicket = ? ORDER BY a.id", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_BNET_LAST_LOGIN_INFO, "UPDATE battlenet_accounts SET last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE id = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_UPD_BNET_GAME_ACCOUNT_LOGIN_INFO, "UPDATE account SET sessionkey = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_SYNCH); + PrepareStatement(LOGIN_UPD_BNET_GAME_ACCOUNT_LOGIN_INFO, "UPDATE account SET session_key = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_ACCOUNT_ID, "SELECT rc.acctid, rc.numchars, r.id, r.Region, r.Battlegroup FROM realmcharacters rc INNER JOIN realmlist r ON rc.realmid = r.id WHERE rc.acctid = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID, "SELECT rc.acctid, rc.numchars, r.id, r.Region, r.Battlegroup FROM realmcharacters rc INNER JOIN realmlist r ON rc.realmid = r.id LEFT JOIN account a ON rc.acctid = a.id WHERE a.battlenet_account = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_BNET_LAST_PLAYER_CHARACTERS, "SELECT lpc.accountId, lpc.region, lpc.battlegroup, lpc.realmId, lpc.characterName, lpc.characterGUID, lpc.lastPlayedTime FROM account_last_played_character lpc LEFT JOIN account a ON lpc.accountId = a.id WHERE a.battlenet_account = ?", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h index 66c856e002e..2373265c5df 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_DEL_ACCOUNT_BANNED, LOGIN_UPD_ACCOUNT_INFO_CONTINUED_SESSION, LOGIN_SEL_ACCOUNT_INFO_CONTINUED_SESSION, - LOGIN_UPD_SV, + LOGIN_UPD_LOGON, + LOGIN_UPD_LOGON_LEGACY, LOGIN_SEL_ACCOUNT_ID_BY_NAME, LOGIN_SEL_ACCOUNT_LIST_BY_NAME, LOGIN_SEL_ACCOUNT_INFO_BY_NAME, @@ -62,7 +63,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 d3789feca75..64f2bf89cc0 100644 --- a/src/server/database/Database/PreparedStatement.h +++ b/src/server/database/Database/PreparedStatement.h @@ -96,6 +96,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); + } void setNull(const uint8 index); uint32 GetIndex() const { return m_index; } diff --git a/src/server/game/Accounts/AccountMgr.cpp b/src/server/game/Accounts/AccountMgr.cpp index d29b93e859b..7e5528136f5 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,18 +60,21 @@ 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); + std::pair<Trinity::Crypto::SRP6::Salt, Trinity::Crypto::SRP6::Verifier> registrationData = Trinity::Crypto::SRP6::MakeRegistrationData(username, password); + stmt->setBinary(1, registrationData.first); + stmt->setBinary(2, registrationData.second); stmt->setString(3, email); + stmt->setString(4, email); + if (bnetAccountId && bnetIndex) { - stmt->setUInt32(4, bnetAccountId); - stmt->setUInt8(5, bnetIndex); + stmt->setUInt32(5, bnetAccountId); + stmt->setUInt8(6, bnetIndex); } else { - stmt->setNull(4); stmt->setNull(5); + stmt->setNull(6); } LoginDatabase.DirectExecute(stmt); // Enforce saving, otherwise AddGroup can fail @@ -156,6 +160,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 @@ -176,13 +187,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); + std::pair<Trinity::Crypto::SRP6::Salt, Trinity::Crypto::SRP6::Verifier> registrationData = Trinity::Crypto::SRP6::MakeRegistrationData(newUsername, newPassword); + stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON); + stmt->setBinary(0, registrationData.first); + stmt->setBinary(1, registrationData.second); + 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; } @@ -204,22 +226,21 @@ AccountOpResult AccountMgr::ChangePassword(uint32 accountId, std::string newPass Utf8ToUpperOnlyLatin(username); Utf8ToUpperOnlyLatin(newPassword); + std::pair<Trinity::Crypto::SRP6::Salt, Trinity::Crypto::SRP6::Verifier> registrationData = Trinity::Crypto::SRP6::MakeRegistrationData(username, newPassword); - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_PASSWORD); - - stmt->setString(0, CalculateShaPassHash(username, newPassword)); - stmt->setUInt32(1, accountId); - - LoginDatabase.Execute(stmt); - - stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_SV); - - stmt->setString(0, ""); - stmt->setString(1, ""); + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON); + stmt->setBinary(0, registrationData.first); + stmt->setBinary(1, registrationData.second); stmt->setUInt32(2, accountId); - 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; } @@ -354,10 +375,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) @@ -387,11 +414,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 2b2400a9164..45d8b8cf7ee 100644 --- a/src/server/game/Accounts/AccountMgr.h +++ b/src/server/game/Accounts/AccountMgr.h @@ -74,7 +74,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 b05afa02883..549d68e7d78 100644 --- a/src/server/game/Server/WorldSocket.cpp +++ b/src/server/game/Server/WorldSocket.cpp @@ -618,15 +618,15 @@ struct AccountInfo explicit AccountInfo(Field* fields) { - // 0 1 2 3 4 5 6 7 8 9 10 11 - // SELECT a.id, a.sessionkey, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.SecurityLevel, + // 0 1 2 3 4 5 6 7 8 9 10 11 + // SELECT a.id, a.session_key, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.SecurityLevel, // 12 13 14 // bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id // FROM account a LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?) // LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account_banned ab ON a.id = ab.id LEFT JOIN account r ON a.id = r.recruiter - // WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1 + // WHERE a.username = ? AND LENGTH(a.session_key) = 40 ORDER BY aa.RealmID DESC LIMIT 1 Game.Id = fields[0].GetUInt32(); - HexStrToByteArray(fields[1].GetString(), Game.KeyData.data()); + Game.KeyData = fields[1].GetBinary<64>(); BattleNet.LastIP = fields[2].GetString(); BattleNet.IsLockedToIP = fields[3].GetBool(); BattleNet.LockCountry = fields[4].GetString(); @@ -734,7 +734,7 @@ void WorldSocket::HandleAuthSessionCallback(std::shared_ptr<WorldPackets::Auth:: // This also allows to check for possible "hack" attempts on account stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_INFO_CONTINUED_SESSION); - stmt->setString(0, ByteArrayToHexStr(_sessionKey)); + stmt->setBinary(0, _sessionKey); stmt->setUInt32(1, account.Game.Id); LoginDatabase.Execute(stmt); @@ -896,7 +896,7 @@ void WorldSocket::HandleAuthContinuedSessionCallback(std::shared_ptr<WorldPacket uint32 accountId = uint32(key.Fields.AccountId); Field* fields = result->Fetch(); std::string login = fields[0].GetString(); - HexStrToByteArray(fields[1].GetString(), _sessionKey.data()); + _sessionKey = fields[1].GetBinary<SESSION_KEY_LENGTH>(); Trinity::Crypto::HMAC_SHA256 hmac(_sessionKey); hmac.UpdateData(reinterpret_cast<uint8 const*>(&authSession->Key), sizeof(authSession->Key)); diff --git a/src/server/shared/Realm/RealmList.cpp b/src/server/shared/Realm/RealmList.cpp index 5673636eb28..a77bb7f7dc2 100644 --- a/src/server/shared/Realm/RealmList.cpp +++ b/src/server/shared/Realm/RealmList.cpp @@ -411,7 +411,7 @@ uint32 RealmList::JoinRealm(uint32 realmAddress, uint32 build, boost::asio::ip:: memcpy(&keyData[32], serverSecret.data(), 32); LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_GAME_ACCOUNT_LOGIN_INFO); - stmt->setString(0, ByteArrayToHexStr(keyData)); + stmt->setBinary(0, keyData); stmt->setString(1, clientAddress.to_string()); stmt->setUInt8(2, locale); stmt->setString(3, os); diff --git a/src/server/worldserver/RemoteAccess/RASession.cpp b/src/server/worldserver/RemoteAccess/RASession.cpp index 88e9b07f759..c49863d353c 100644 --- a/src/server/worldserver/RemoteAccess/RASession.cpp +++ b/src/server/worldserver/RemoteAccess/RASession.cpp @@ -20,6 +20,7 @@ #include "Config.h" #include "DatabaseEnv.h" #include "Log.h" +#include "SRP6.h" #include "Util.h" #include "World.h" #include <boost/asio/buffer.hpp> @@ -159,22 +160,21 @@ bool RASession::CheckPassword(const std::string& user, const std::string& pass) std::string safe_pass = pass; Utf8ToUpperOnlyLatin(safe_pass); - 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) |