aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/bnetserver/REST/LoginHttpSession.h8
-rw-r--r--src/server/bnetserver/REST/LoginRESTService.cpp165
-rw-r--r--src/server/bnetserver/REST/LoginRESTService.h29
-rw-r--r--src/server/database/Database/Implementation/LoginDatabase.cpp9
-rw-r--r--src/server/database/Database/Implementation/LoginDatabase.h3
-rw-r--r--src/server/game/Accounts/AccountMgr.cpp32
-rw-r--r--src/server/game/Accounts/BattlenetAccountMgr.cpp76
-rw-r--r--src/server/game/Accounts/BattlenetAccountMgr.h3
-rw-r--r--src/server/shared/Networking/Http/HttpService.cpp10
-rw-r--r--src/server/shared/Networking/Http/HttpService.h6
10 files changed, 245 insertions, 96 deletions
diff --git a/src/server/bnetserver/REST/LoginHttpSession.h b/src/server/bnetserver/REST/LoginHttpSession.h
index 17c94b55bda..2ee70d5afa5 100644
--- a/src/server/bnetserver/REST/LoginHttpSession.h
+++ b/src/server/bnetserver/REST/LoginHttpSession.h
@@ -19,9 +19,15 @@
#define TRINITYCORE_LOGIN_HTTP_SESSION_H
#include "HttpSslSocket.h"
+#include "SRP6.h"
namespace Battlenet
{
+struct LoginSessionState : public Trinity::Net::Http::SessionState
+{
+ std::unique_ptr<Trinity::Crypto::SRP::BnetSRP6Base> Srp;
+};
+
class LoginHttpSession : public Trinity::Net::Http::SslSocket<LoginHttpSession>
{
public:
@@ -36,6 +42,8 @@ public:
Trinity::Net::Http::RequestHandlerResult RequestHandler(Trinity::Net::Http::RequestContext& context) override;
+ LoginSessionState* GetSessionState() const { return static_cast<LoginSessionState*>(_state.get()); }
+
protected:
std::shared_ptr<Trinity::Net::Http::SessionState> ObtainSessionState(Trinity::Net::Http::RequestContext& context) const override;
};
diff --git a/src/server/bnetserver/REST/LoginRESTService.cpp b/src/server/bnetserver/REST/LoginRESTService.cpp
index 7636cbe7bf3..b591d248134 100644
--- a/src/server/bnetserver/REST/LoginRESTService.cpp
+++ b/src/server/bnetserver/REST/LoginRESTService.cpp
@@ -25,6 +25,7 @@
#include "IteratorPair.h"
#include "ProtobufJSON.h"
#include "Resolver.h"
+#include "Timer.h"
#include "Util.h"
namespace Battlenet
@@ -47,7 +48,7 @@ bool LoginRESTService::StartNetwork(Trinity::Asio::IoContext& ioContext, std::st
return HandleGetForm(std::move(session), context);
});
- RegisterHandler(boost::beast::http::verb::get, "/bnetserver/gameAccounts/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
+ RegisterHandler(boost::beast::http::verb::get, "/bnetserver/gameAccounts/", [](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
{
return HandleGetGameAccounts(std::move(session), context);
});
@@ -105,7 +106,7 @@ bool LoginRESTService::StartNetwork(Trinity::Asio::IoContext& ioContext, std::st
input->set_input_id("password");
input->set_type("password");
input->set_label("Password");
- input->set_max_length(16);
+ input->set_max_length(128);
input = _formInputs.add_inputs();
input->set_input_id("log_in_submit");
@@ -114,6 +115,8 @@ bool LoginRESTService::StartNetwork(Trinity::Asio::IoContext& ioContext, std::st
_loginTicketDuration = sConfigMgr->GetIntDefault("LoginREST.TicketDuration", 3600);
+ MigrateLegacyPasswordHashes();
+
_acceptor->AsyncAcceptWithCallback<&LoginRESTService::OnSocketAccept>();
return true;
}
@@ -213,17 +216,17 @@ LoginRESTService::RequestHandlerResult LoginRESTService::HandleGetGameAccounts(s
return RequestHandlerResult::Async;
}
-LoginRESTService::RequestHandlerResult LoginRESTService::HandleGetPortal(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
+LoginRESTService::RequestHandlerResult LoginRESTService::HandleGetPortal(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) const
{
context.response.set(boost::beast::http::field::content_type, "text/plain");
context.response.body() = Trinity::StringFormat("{}:{}", GetHostnameForClient(session->GetRemoteIpAddress()), sConfigMgr->GetIntDefault("BattlenetPort", 1119));
return RequestHandlerResult::Handled;
}
-LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostLogin(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
+LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostLogin(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) const
{
- JSON::Login::LoginForm loginForm;
- if (!::JSON::Deserialize(context.request.body(), &loginForm))
+ std::shared_ptr<JSON::Login::LoginForm> loginForm = std::make_shared<JSON::Login::LoginForm>();
+ if (!::JSON::Deserialize(context.request.body(), loginForm.get()))
{
JSON::Login::LoginResult loginResult;
loginResult.set_authentication_state(JSON::Login::LOGIN);
@@ -238,27 +241,22 @@ LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostLogin(std::sh
return RequestHandlerResult::Handled;
}
- std::string login;
- std::string password;
-
- for (int32 i = 0; i < loginForm.inputs_size(); ++i)
+ auto getInputValue = [](JSON::Login::LoginForm const* loginForm, std::string_view inputId) -> std::string
{
- if (loginForm.inputs(i).input_id() == "account_name")
- login = loginForm.inputs(i).value();
- else if (loginForm.inputs(i).input_id() == "password")
- password = loginForm.inputs(i).value();
- }
+ for (int32 i = 0; i < loginForm->inputs_size(); ++i)
+ if (loginForm->inputs(i).input_id() == inputId)
+ return loginForm->inputs(i).value();
+ return "";
+ };
+ std::string login(getInputValue(loginForm.get(), "account_name"));
Utf8ToUpperOnlyLatin(login);
- Utf8ToUpperOnlyLatin(password);
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_AUTHENTICATION);
stmt->setString(0, login);
- std::string sentPasswordHash = CalculateShaPassHash(login, password);
-
session->QueueQuery(LoginDatabase.AsyncQuery(stmt)
- .WithChainingPreparedCallback([this, session, context = std::move(context), login = std::move(login), sentPasswordHash = std::move(sentPasswordHash)](QueryCallback& callback, PreparedQueryResult result) mutable
+ .WithChainingPreparedCallback([this, session, context = std::move(context), loginForm = std::move(loginForm), getInputValue](QueryCallback& callback, PreparedQueryResult result) mutable
{
if (!result)
{
@@ -270,15 +268,33 @@ LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostLogin(std::sh
return;
}
+ std::string login(getInputValue(loginForm.get(), "account_name"));
+ Utf8ToUpperOnlyLatin(login);
+ bool passwordCorrect = false;
+
Field* fields = result->Fetch();
uint32 accountId = fields[0].GetUInt32();
- std::string pass_hash = fields[1].GetString();
- uint32 failedLogins = fields[2].GetUInt32();
- std::string loginTicket = fields[3].GetString();
- uint32 loginTicketExpiry = fields[4].GetUInt32();
- bool isBanned = fields[5].GetUInt64() != 0;
+ if (!session->GetSessionState()->Srp)
+ {
+ SrpVersion version = SrpVersion(fields[1].GetInt8());
+ std::string srpUsername = ByteArrayToHexStr(Trinity::Crypto::SHA256::GetDigestOf(login));
+ Trinity::Crypto::SRP::Salt s = fields[2].GetBinary<Trinity::Crypto::SRP::SALT_LENGTH>();
+ Trinity::Crypto::SRP::Verifier v = fields[3].GetBinary();
+ session->GetSessionState()->Srp = CreateSrpImplementation(version, SrpHashFunction::Sha256, srpUsername, s, v);
+
+ std::string password(getInputValue(loginForm.get(), "password"));
+ if (version == SrpVersion::v1)
+ Utf8ToUpperOnlyLatin(password);
+
+ passwordCorrect = session->GetSessionState()->Srp->CheckCredentials(srpUsername, password);
+ }
+
+ uint32 failedLogins = fields[4].GetUInt32();
+ std::string loginTicket = fields[5].GetString();
+ uint32 loginTicketExpiry = fields[6].GetUInt32();
+ bool isBanned = fields[7].GetUInt64() != 0;
- if (sentPasswordHash != pass_hash)
+ if (!passwordCorrect)
{
if (!isBanned)
{
@@ -358,7 +374,7 @@ LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostLogin(std::sh
return RequestHandlerResult::Async;
}
-LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
+LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) const
{
std::string ticket = ExtractAuthorization(context.request);
if (ticket.empty())
@@ -397,23 +413,100 @@ LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostRefreshLoginT
return RequestHandlerResult::Async;
}
-std::string LoginRESTService::CalculateShaPassHash(std::string const& name, std::string const& password)
+std::unique_ptr<Trinity::Crypto::SRP::BnetSRP6Base> LoginRESTService::CreateSrpImplementation(SrpVersion version, SrpHashFunction hashFunction,
+ std::string const& username, Trinity::Crypto::SRP::Salt const& salt, Trinity::Crypto::SRP::Verifier const& verifier)
{
- Trinity::Crypto::SHA256 email;
- email.UpdateData(name);
- email.Finalize();
+ if (version == SrpVersion::v2)
+ {
+ if (hashFunction == SrpHashFunction::Sha256)
+ return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v2<Trinity::Crypto::SHA256>>(username, salt, verifier);
+ if (hashFunction == SrpHashFunction::Sha512)
+ return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v2<Trinity::Crypto::SHA512>>(username, salt, verifier);
+ }
- Trinity::Crypto::SHA256 sha;
- sha.UpdateData(ByteArrayToHexStr(email.GetDigest()));
- sha.UpdateData(":");
- sha.UpdateData(password);
- sha.Finalize();
+ if (version == SrpVersion::v1)
+ {
+ if (hashFunction == SrpHashFunction::Sha256)
+ return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v1<Trinity::Crypto::SHA256>>(username, salt, verifier);
+ if (hashFunction == SrpHashFunction::Sha512)
+ return std::make_unique<Trinity::Crypto::SRP::BnetSRP6v1<Trinity::Crypto::SHA512>>(username, salt, verifier);
+ }
- return ByteArrayToHexStr(sha.GetDigest(), true);
+ return nullptr;
+}
+
+std::shared_ptr<Trinity::Net::Http::SessionState> LoginRESTService::CreateNewSessionState(boost::asio::ip::address const& address)
+{
+ std::shared_ptr<LoginSessionState> state = std::make_shared<LoginSessionState>();
+ InitAndStoreSessionState(state, address);
+ return state;
}
void LoginRESTService::OnSocketAccept(boost::asio::ip::tcp::socket&& sock, uint32 threadIndex)
{
sLoginService.OnSocketOpen(std::move(sock), threadIndex);
}
+
+void LoginRESTService::MigrateLegacyPasswordHashes() const
+{
+ if (!LoginDatabase.Query("SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = SCHEMA() AND TABLE_NAME = 'battlenet_accounts' AND COLUMN_NAME = 'sha_pass_hash'"))
+ return;
+
+ TC_LOG_INFO(_logger, "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) OR (verifier IS null), 0, 1) AS shouldWarn FROM battlenet_accounts WHERE sha_pass_hash != DEFAULT(sha_pass_hash) OR salt IS NULL OR verifier IS NULL");
+ if (!result)
+ {
+ TC_LOG_INFO(_logger, ">> No password hashes to update - this took us {} ms to realize", GetMSTimeDiffToNow(start));
+ return;
+ }
+
+ bool hadWarning = false;
+ uint32 c = 0;
+ LoginDatabaseTransaction tx = LoginDatabase.BeginTransaction();
+ do
+ {
+ uint32 const id = (*result)[0].GetUInt32();
+
+ Trinity::Crypto::SRP::Salt salt = Trinity::Crypto::GetRandomBytes<Trinity::Crypto::SRP::SALT_LENGTH>();
+ BigNumber x = Trinity::Crypto::SHA256::GetDigestOf(salt, HexStrToByteArray<Trinity::Crypto::SHA256::DIGEST_LENGTH>((*result)[1].GetString(), true));
+ Trinity::Crypto::SRP::Verifier verifier = Trinity::Crypto::SRP::BnetSRP6v1Base::g.ModExp(x, Trinity::Crypto::SRP::BnetSRP6v1Base::N).ToByteVector();
+
+ if ((*result)[2].GetInt64())
+ {
+ if (!hadWarning)
+ {
+ hadWarning = true;
+ TC_LOG_WARN(_logger,
+ " ========\n"
+ "(!) You appear to be using an outdated external account management tool.\n"
+ "(!) Update your external tool.\n"
+ "(!!) If no update is available, refer your tool's developer to https://github.com/TrinityCore/TrinityCore/issues/25157.\n"
+ " ========");
+ }
+ }
+
+ LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_LOGON);
+ stmt->setInt8(0, AsUnderlyingType(SrpVersion::v1));
+ stmt->setBinary(1, salt);
+ stmt->setBinary(2, verifier);
+ stmt->setUInt32(3, id);
+ tx->Append(stmt);
+
+ tx->Append(Trinity::StringFormat("UPDATE battlenet_accounts SET sha_pass_hash = DEFAULT(sha_pass_hash) WHERE id = {}", id).c_str());
+
+ if (tx->GetSize() >= 10000)
+ {
+ LoginDatabase.CommitTransaction(tx);
+ tx = LoginDatabase.BeginTransaction();
+ }
+
+ ++c;
+ } while (result->NextRow());
+ LoginDatabase.CommitTransaction(tx);
+
+ TC_LOG_INFO(_logger, ">> {} password hashes updated in {} ms", c, GetMSTimeDiffToNow(start));
+}
}
diff --git a/src/server/bnetserver/REST/LoginRESTService.h b/src/server/bnetserver/REST/LoginRESTService.h
index 1313493e023..9d48f1d2710 100644
--- a/src/server/bnetserver/REST/LoginRESTService.h
+++ b/src/server/bnetserver/REST/LoginRESTService.h
@@ -24,6 +24,18 @@
namespace Battlenet
{
+enum class SrpVersion : int8
+{
+ v1 = 1,
+ v2 = 2
+};
+
+enum class SrpHashFunction
+{
+ Sha256 = 0,
+ Sha512 = 1
+};
+
enum class BanMode
{
BAN_IP = 0,
@@ -48,19 +60,24 @@ public:
std::string const& GetHostnameForClient(boost::asio::ip::address const& address) const;
uint16 GetPort() const { return _port; }
+ std::shared_ptr<Trinity::Net::Http::SessionState> CreateNewSessionState(boost::asio::ip::address const& address) override;
+
private:
static void OnSocketAccept(boost::asio::ip::tcp::socket&& sock, uint32 threadIndex);
static std::string ExtractAuthorization(HttpRequest const& request);
- RequestHandlerResult HandleGetForm(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
- RequestHandlerResult HandleGetGameAccounts(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
- RequestHandlerResult HandleGetPortal(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
+ RequestHandlerResult HandleGetForm(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) const;
+ static RequestHandlerResult HandleGetGameAccounts(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
+ RequestHandlerResult HandleGetPortal(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) const;
+
+ RequestHandlerResult HandlePostLogin(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) const;
+ RequestHandlerResult HandlePostRefreshLoginTicket(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) const;
- RequestHandlerResult HandlePostLogin(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
- RequestHandlerResult HandlePostRefreshLoginTicket(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
+ static std::unique_ptr<Trinity::Crypto::SRP::BnetSRP6Base> CreateSrpImplementation(SrpVersion version, SrpHashFunction hashFunction,
+ std::string const& username, Trinity::Crypto::SRP::Salt const& salt, Trinity::Crypto::SRP::Verifier const& verifier);
- static std::string CalculateShaPassHash(std::string const& name, std::string const& password);
+ void MigrateLegacyPasswordHashes() const;
JSON::Login::FormInputs _formInputs;
std::string _bindIP;
diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp
index d646194ae8c..27aeaa91535 100644
--- a/src/server/database/Database/Implementation/LoginDatabase.cpp
+++ b/src/server/database/Database/Implementation/LoginDatabase.cpp
@@ -120,7 +120,7 @@ void LoginDatabaseConnection::DoPrepareStatements()
#define BnetAccountInfo "ba.id, UPPER(ba.email), ba.locked, ba.lock_country, ba.last_ip, ba.LoginTicketExpiry, bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, bab.unbandate = bab.bandate"
#define BnetGameAccountInfo "a.id, a.username, ab.unbandate, ab.unbandate = ab.bandate, aa.SecurityLevel"
- PrepareStatement(LOGIN_SEL_BNET_AUTHENTICATION, "SELECT ba.id, ba.sha_pass_hash, ba.failed_logins, ba.LoginTicket, ba.LoginTicketExpiry, bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id WHERE email = ?", CONNECTION_ASYNC);
+ PrepareStatement(LOGIN_SEL_BNET_AUTHENTICATION, "SELECT ba.id, ba.srp_version, ba.salt, ba.verifier, ba.failed_logins, ba.LoginTicket, ba.LoginTicketExpiry, bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id WHERE email = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_BNET_AUTHENTICATION, "UPDATE battlenet_accounts SET LoginTicket = ?, LoginTicketExpiry = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_BNET_EXISTING_AUTHENTICATION, "SELECT LoginTicketExpiry FROM battlenet_accounts WHERE LoginTicket = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_BNET_EXISTING_AUTHENTICATION_BY_ID, "SELECT LoginTicket FROM battlenet_accounts WHERE id = ?", CONNECTION_ASYNC);
@@ -135,11 +135,12 @@ void LoginDatabaseConnection::DoPrepareStatements()
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);
PrepareStatement(LOGIN_DEL_BNET_LAST_PLAYER_CHARACTERS, "DELETE FROM account_last_played_character WHERE accountId = ? AND region = ? AND battlegroup = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_BNET_LAST_PLAYER_CHARACTERS, "INSERT INTO account_last_played_character (accountId, region, battlegroup, realmId, characterName, characterGUID, lastPlayedTime) VALUES (?,?,?,?,?,?,?)", CONNECTION_ASYNC);
- PrepareStatement(LOGIN_INS_BNET_ACCOUNT, "INSERT INTO battlenet_accounts (`email`,`sha_pass_hash`) VALUES (?, ?)", CONNECTION_SYNCH);
+ PrepareStatement(LOGIN_INS_BNET_ACCOUNT, "INSERT INTO battlenet_accounts (`email`,`srp_version`,`salt`,`verifier`) VALUES (?, ?, ?, ?)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_BNET_ACCOUNT_EMAIL_BY_ID, "SELECT email FROM battlenet_accounts WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_BNET_ACCOUNT_ID_BY_EMAIL, "SELECT id FROM battlenet_accounts WHERE email = ?", CONNECTION_SYNCH);
- PrepareStatement(LOGIN_UPD_BNET_PASSWORD, "UPDATE battlenet_accounts SET sha_pass_hash = ? WHERE id = ?", CONNECTION_ASYNC);
- PrepareStatement(LOGIN_SEL_BNET_CHECK_PASSWORD, "SELECT 1 FROM battlenet_accounts WHERE id = ? AND sha_pass_hash = ?", CONNECTION_SYNCH);
+ PrepareStatement(LOGIN_UPD_BNET_LOGON, "UPDATE battlenet_accounts SET srp_version = ?, salt = ?, verifier = ? WHERE id = ?", CONNECTION_ASYNC);
+ PrepareStatement(LOGIN_SEL_BNET_CHECK_PASSWORD, "SELECT srp_version, salt, verifier FROM battlenet_accounts WHERE id = ?", CONNECTION_SYNCH);
+ PrepareStatement(LOGIN_SEL_BNET_CHECK_PASSWORD_BY_EMAIL, "SELECT srp_version, salt, verifier FROM battlenet_accounts WHERE email = ?", CONNECTION_BOTH);
PrepareStatement(LOGIN_UPD_BNET_ACCOUNT_LOCK, "UPDATE battlenet_accounts SET locked = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_BNET_ACCOUNT_LOCK_CONTRY, "UPDATE battlenet_accounts SET lock_country = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_BNET_ACCOUNT_ID_BY_GAME_ACCOUNT, "SELECT battlenet_account FROM account WHERE id = ?", CONNECTION_BOTH);
diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h
index d70241e2dca..9fa77877cbd 100644
--- a/src/server/database/Database/Implementation/LoginDatabase.h
+++ b/src/server/database/Database/Implementation/LoginDatabase.h
@@ -129,8 +129,9 @@ enum LoginDatabaseStatements : uint32
LOGIN_INS_BNET_ACCOUNT,
LOGIN_SEL_BNET_ACCOUNT_EMAIL_BY_ID,
LOGIN_SEL_BNET_ACCOUNT_ID_BY_EMAIL,
- LOGIN_UPD_BNET_PASSWORD,
+ LOGIN_UPD_BNET_LOGON,
LOGIN_SEL_BNET_CHECK_PASSWORD,
+ LOGIN_SEL_BNET_CHECK_PASSWORD_BY_EMAIL,
LOGIN_UPD_BNET_ACCOUNT_LOCK,
LOGIN_UPD_BNET_ACCOUNT_LOCK_CONTRY,
LOGIN_SEL_BNET_ACCOUNT_ID_BY_GAME_ACCOUNT,
diff --git a/src/server/game/Accounts/AccountMgr.cpp b/src/server/game/Accounts/AccountMgr.cpp
index 0229dd686b4..113e81153ba 100644
--- a/src/server/game/Accounts/AccountMgr.cpp
+++ b/src/server/game/Accounts/AccountMgr.cpp
@@ -29,6 +29,8 @@
#include "World.h"
#include "WorldSession.h"
+using AccountSRP6 = Trinity::Crypto::SRP::GruntSRP6;
+
AccountMgr::AccountMgr() { }
AccountMgr::~AccountMgr()
@@ -60,9 +62,9 @@ AccountOpResult AccountMgr::CreateAccount(std::string username, std::string pass
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT);
stmt->setString(0, username);
- 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);
+ auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData<AccountSRP6>(username, password);
+ stmt->setBinary(1, salt);
+ stmt->setBinary(2, verifier);
stmt->setString(3, email);
stmt->setString(4, email);
@@ -184,10 +186,10 @@ AccountOpResult AccountMgr::ChangeUsername(uint32 accountId, std::string newUser
stmt->setUInt32(1, accountId);
LoginDatabase.Execute(stmt);
- std::pair<Trinity::Crypto::SRP6::Salt, Trinity::Crypto::SRP6::Verifier> registrationData = Trinity::Crypto::SRP6::MakeRegistrationData(newUsername, newPassword);
+ auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData<AccountSRP6>(newUsername, newPassword);
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON);
- stmt->setBinary(0, registrationData.first);
- stmt->setBinary(1, registrationData.second);
+ stmt->setBinary(0, salt);
+ stmt->setBinary(1, verifier);
stmt->setUInt32(2, accountId);
LoginDatabase.Execute(stmt);
@@ -212,11 +214,11 @@ 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);
+ auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData<AccountSRP6>(username, newPassword);
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGON);
- stmt->setBinary(0, registrationData.first);
- stmt->setBinary(1, registrationData.second);
+ stmt->setBinary(0, salt);
+ stmt->setBinary(1, verifier);
stmt->setUInt32(2, accountId);
LoginDatabase.Execute(stmt);
@@ -354,9 +356,9 @@ bool AccountMgr::CheckPassword(std::string username, std::string password)
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))
+ Trinity::Crypto::SRP::Salt salt = (*result)[0].GetBinary<Trinity::Crypto::SRP::SALT_LENGTH>();
+ Trinity::Crypto::SRP::Verifier verifier = (*result)[1].GetBinary();
+ if (AccountSRP6(username, salt, verifier).CheckCredentials(username, password))
return true;
}
@@ -378,9 +380,9 @@ bool AccountMgr::CheckPassword(uint32 accountId, std::string password)
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))
+ Trinity::Crypto::SRP::Salt salt = (*result)[0].GetBinary<Trinity::Crypto::SRP::SALT_LENGTH>();
+ Trinity::Crypto::SRP::Verifier verifier = (*result)[1].GetBinary();
+ if (AccountSRP6(username, salt, verifier).CheckCredentials(username, password))
return true;
}
diff --git a/src/server/game/Accounts/BattlenetAccountMgr.cpp b/src/server/game/Accounts/BattlenetAccountMgr.cpp
index 08a807b8db6..8b57ac71093 100644
--- a/src/server/game/Accounts/BattlenetAccountMgr.cpp
+++ b/src/server/game/Accounts/BattlenetAccountMgr.cpp
@@ -17,29 +17,41 @@
#include "BattlenetAccountMgr.h"
#include "AccountMgr.h"
-#include "CryptoHash.h"
#include "DatabaseEnv.h"
+#include "SRP6.h"
#include "Util.h"
using GameAccountMgr = AccountMgr;
+using BnetSRP6_OLD = Trinity::Crypto::SRP::BnetSRP6v1<Trinity::Crypto::SHA256>;
+using BnetSRP6 = Trinity::Crypto::SRP::BnetSRP6v2<Trinity::Crypto::SHA256>;
+
+enum class SrpVersion : int8
+{
+ v1 = 1, // password length limit 16 characters, case-insensitive, uses SHA256 to generate verifier
+ v2 = 2 // password length limit 128 characters, case-sensitive, uses PBKDF2 with SHA512 to generate verifier
+};
AccountOpResult Battlenet::AccountMgr::CreateBattlenetAccount(std::string email, std::string password, bool withGameAccount, std::string* gameAccountName)
{
if (utf8length(email) > MAX_BNET_EMAIL_STR)
return AccountOpResult::AOR_NAME_TOO_LONG;
- if (utf8length(password) > MAX_PASS_STR)
+ if (utf8length(password) > MAX_BNET_PASS_STR)
return AccountOpResult::AOR_PASS_TOO_LONG;
Utf8ToUpperOnlyLatin(email);
- Utf8ToUpperOnlyLatin(password);
if (GetId(email))
return AccountOpResult::AOR_NAME_ALREADY_EXIST;
+ std::string srpUsername = GetSrpUsername(email);
+ auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData<BnetSRP6>(srpUsername, password);
+
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BNET_ACCOUNT);
stmt->setString(0, email);
- stmt->setString(1, CalculateShaPassHash(email, password));
+ stmt->setInt8(1, AsUnderlyingType(SrpVersion::v2));
+ stmt->setBinary(2, salt);
+ stmt->setBinary(3, verifier);
LoginDatabase.DirectExecute(stmt);
uint32 newAccountId = GetId(email);
@@ -48,7 +60,9 @@ AccountOpResult Battlenet::AccountMgr::CreateBattlenetAccount(std::string email,
if (withGameAccount)
{
*gameAccountName = std::to_string(newAccountId) + "#1";
- GameAccountMgr::instance()->CreateAccount(*gameAccountName, password, email, newAccountId, 1);
+ std::string gameAccountPassword = password.substr(0, MAX_PASS_STR);
+ Utf8ToUpperOnlyLatin(gameAccountPassword);
+ GameAccountMgr::instance()->CreateAccount(*gameAccountName, gameAccountPassword, email, newAccountId, 1);
}
return AccountOpResult::AOR_OK;
@@ -60,14 +74,17 @@ AccountOpResult Battlenet::AccountMgr::ChangePassword(uint32 accountId, std::str
if (!GetName(accountId, username))
return AccountOpResult::AOR_NAME_NOT_EXIST; // account doesn't exist
- Utf8ToUpperOnlyLatin(username);
- Utf8ToUpperOnlyLatin(newPassword);
- if (utf8length(newPassword) > MAX_PASS_STR)
+ if (utf8length(newPassword) > MAX_BNET_PASS_STR)
return AccountOpResult::AOR_PASS_TOO_LONG;
- LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_PASSWORD);
- stmt->setString(0, CalculateShaPassHash(username, newPassword));
- stmt->setUInt32(1, accountId);
+ std::string srpUsername = GetSrpUsername(username);
+ auto [salt, verifier] = Trinity::Crypto::SRP6::MakeRegistrationData<BnetSRP6>(srpUsername, newPassword);
+
+ LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_LOGON);
+ stmt->setInt8(0, AsUnderlyingType(SrpVersion::v2));
+ stmt->setBinary(1, salt);
+ stmt->setBinary(2, verifier);
+ stmt->setUInt32(3, accountId);
LoginDatabase.Execute(stmt);
return AccountOpResult::AOR_OK;
@@ -80,14 +97,26 @@ bool Battlenet::AccountMgr::CheckPassword(uint32 accountId, std::string password
if (!GetName(accountId, username))
return false;
- Utf8ToUpperOnlyLatin(username);
- Utf8ToUpperOnlyLatin(password);
-
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_CHECK_PASSWORD);
stmt->setUInt32(0, accountId);
- stmt->setString(1, CalculateShaPassHash(username, password));
- return LoginDatabase.Query(stmt) != nullptr;
+ if (PreparedQueryResult result = LoginDatabase.Query(stmt))
+ {
+ Trinity::Crypto::SRP::Salt salt = (*result)[1].GetBinary<Trinity::Crypto::SRP::SALT_LENGTH>();
+ Trinity::Crypto::SRP::Verifier verifier = (*result)[2].GetBinary();
+ switch (SrpVersion((*result)[0].GetInt8()))
+ {
+ case SrpVersion::v1:
+ Utf8ToUpperOnlyLatin(password);
+ return BnetSRP6_OLD(username, salt, verifier).CheckCredentials(username, password);
+ case SrpVersion::v2:
+ return BnetSRP6(username, salt, verifier).CheckCredentials(username, password);
+ default:
+ break;
+ }
+ }
+
+ return false;
}
AccountOpResult Battlenet::AccountMgr::LinkWithGameAccount(std::string_view email, std::string_view gameAccountName)
@@ -179,17 +208,8 @@ uint8 Battlenet::AccountMgr::GetMaxIndex(uint32 accountId)
return 0;
}
-std::string Battlenet::AccountMgr::CalculateShaPassHash(std::string_view name, std::string_view password)
+std::string Battlenet::AccountMgr::GetSrpUsername(std::string name)
{
- Trinity::Crypto::SHA256 email;
- email.UpdateData(name);
- email.Finalize();
-
- Trinity::Crypto::SHA256 sha;
- sha.UpdateData(ByteArrayToHexStr(email.GetDigest()));
- sha.UpdateData(":");
- sha.UpdateData(password);
- sha.Finalize();
-
- return ByteArrayToHexStr(sha.GetDigest(), true);
+ Utf8ToUpperOnlyLatin(name);
+ return ByteArrayToHexStr(Trinity::Crypto::SHA256::GetDigestOf(name));
}
diff --git a/src/server/game/Accounts/BattlenetAccountMgr.h b/src/server/game/Accounts/BattlenetAccountMgr.h
index bfeb02302f7..b4fe7626b42 100644
--- a/src/server/game/Accounts/BattlenetAccountMgr.h
+++ b/src/server/game/Accounts/BattlenetAccountMgr.h
@@ -25,6 +25,7 @@ class QueryCallback;
enum class AccountOpResult : uint8;
#define MAX_BNET_EMAIL_STR 320
+#define MAX_BNET_PASS_STR 128
namespace Battlenet
{
@@ -42,7 +43,7 @@ namespace Battlenet
[[nodiscard]] TC_GAME_API QueryCallback GetIdByGameAccountAsync(uint32 gameAccountId);
TC_GAME_API uint8 GetMaxIndex(uint32 accountId);
- TC_GAME_API std::string CalculateShaPassHash(std::string_view name, std::string_view password);
+ TC_GAME_API std::string GetSrpUsername(std::string name);
}
}
diff --git a/src/server/shared/Networking/Http/HttpService.cpp b/src/server/shared/Networking/Http/HttpService.cpp
index 3e80d08959c..8bb533d73c0 100644
--- a/src/server/shared/Networking/Http/HttpService.cpp
+++ b/src/server/shared/Networking/Http/HttpService.cpp
@@ -81,9 +81,9 @@ RequestHandlerResult DispatcherService::HandleRequest(std::shared_ptr<AbstractSo
return context.handler->Func(std::move(session), context);
}
-RequestHandlerResult DispatcherService::HandlePathNotFound(std::shared_ptr<AbstractSocket> /*session*/, RequestContext& context)
+RequestHandlerResult DispatcherService::HandleBadRequest(std::shared_ptr<AbstractSocket> /*session*/, RequestContext& context)
{
- context.response.result(boost::beast::http::status::not_found);
+ context.response.result(boost::beast::http::status::bad_request);
return RequestHandlerResult::Handled;
}
@@ -93,6 +93,12 @@ RequestHandlerResult DispatcherService::HandleUnauthorized(std::shared_ptr<Abstr
return RequestHandlerResult::Handled;
}
+RequestHandlerResult DispatcherService::HandlePathNotFound(std::shared_ptr<AbstractSocket> /*session*/, RequestContext& context)
+{
+ context.response.result(boost::beast::http::status::not_found);
+ return RequestHandlerResult::Handled;
+}
+
void DispatcherService::RegisterHandler(boost::beast::http::verb method, std::string_view path,
std::function<RequestHandlerResult(std::shared_ptr<AbstractSocket> session, RequestContext& context)> handler,
RequestHandlerFlag flags)
diff --git a/src/server/shared/Networking/Http/HttpService.h b/src/server/shared/Networking/Http/HttpService.h
index 01c66146ae3..e68f8d2795f 100644
--- a/src/server/shared/Networking/Http/HttpService.h
+++ b/src/server/shared/Networking/Http/HttpService.h
@@ -61,8 +61,9 @@ public:
RequestHandlerResult HandleRequest(std::shared_ptr<AbstractSocket> session, RequestContext& context);
- RequestHandlerResult HandlePathNotFound(std::shared_ptr<AbstractSocket> session, RequestContext& context);
- RequestHandlerResult HandleUnauthorized(std::shared_ptr<AbstractSocket> session, RequestContext& context);
+ static RequestHandlerResult HandleBadRequest(std::shared_ptr<AbstractSocket> session, RequestContext& context);
+ static RequestHandlerResult HandleUnauthorized(std::shared_ptr<AbstractSocket> session, RequestContext& context);
+ static RequestHandlerResult HandlePathNotFound(std::shared_ptr<AbstractSocket> session, RequestContext& context);
protected:
void RegisterHandler(boost::beast::http::verb method, std::string_view path,
@@ -179,7 +180,6 @@ protected:
return threads;
}
-private:
Asio::IoContext* _ioContext;
std::string _logger;
};