Core/Bnet: Implemented new SRP6 variants, and migrate old sha_pass_hash in battlenet_accounts to separate salt and verifier columns

* passwords can now be case sensitive and up to 128 characters long
This commit is contained in:
Shauren
2023-12-26 14:55:15 +01:00
parent 4a61675191
commit 623202d68e
16 changed files with 626 additions and 194 deletions

View File

@@ -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;
};

View File

@@ -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);
if (sentPasswordHash != pass_hash)
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 (!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));
}
}

View File

@@ -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);
RequestHandlerResult HandlePostRefreshLoginTicket(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
RequestHandlerResult HandlePostLogin(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) const;
RequestHandlerResult HandlePostRefreshLoginTicket(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) const;
static std::string CalculateShaPassHash(std::string const& name, std::string const& password);
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);
void MigrateLegacyPasswordHashes() const;
JSON::Login::FormInputs _formInputs;
std::string _bindIP;

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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));
}

View File

@@ -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);
}
}

View File

@@ -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)

View File

@@ -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;
};