diff --git a/sql/migrations/20210519212944_logon.sql b/sql/migrations/20210519212944_logon.sql new file mode 100644 index 00000000000..25656e9fcd5 --- /dev/null +++ b/sql/migrations/20210519212944_logon.sql @@ -0,0 +1,21 @@ +DROP PROCEDURE IF EXISTS add_migration; +delimiter ?? +CREATE PROCEDURE `add_migration`() +BEGIN +DECLARE v INT DEFAULT 1; +SET v = (SELECT COUNT(*) FROM `migrations` WHERE `id`='20210519212944'); +IF v=0 THEN +INSERT INTO `migrations` VALUES ('20210519212944'); +-- Add your query below. + + +ALTER TABLE `account` + DROP COLUMN `sha_pass_hash`; + + +-- End of migration. +END IF; +END?? +delimiter ; +CALL add_migration(); +DROP PROCEDURE IF EXISTS add_migration; diff --git a/src/game/AccountMgr.cpp b/src/game/AccountMgr.cpp index f3d81f5e067..9db7a2eacbe 100644 --- a/src/game/AccountMgr.cpp +++ b/src/game/AccountMgr.cpp @@ -31,7 +31,7 @@ #include "WorldSession.h" #include "MasterPlayer.h" #include "Anticheat.h" - +#include "SRP6/SRP6.h" INSTANTIATE_SINGLETON_1(AccountMgr); @@ -54,11 +54,24 @@ AccountOpResult AccountMgr::CreateAccount(std::string username, std::string pass return AOR_NAME_ALREDY_EXIST; // username does already exist } - if (!LoginDatabase.PExecute("INSERT INTO `account` (`username`, `sha_pass_hash`, `joindate`) VALUES('%s','%s',NOW())", username.c_str(), CalculateShaPassHash(username, password).c_str())) + SRP6 srp; + srp.CalculateVerifier(CalculateShaPassHash(username, password)); + const char* s_hex = srp.GetSalt().AsHexStr(); + const char* v_hex = srp.GetVerifier().AsHexStr(); + + bool update_sv = LoginDatabase.PExecute( + "INSERT INTO account(`username`, `v`, `s`, `joindate`) VALUES('%s','%s','%s',NOW())", + username.c_str(), v_hex, s_hex); + + OPENSSL_free((void*)s_hex); + OPENSSL_free((void*)v_hex); + + if (!update_sv) return AOR_DB_INTERNAL_ERROR; // unexpected error - LoginDatabase.Execute("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"); + LoginDatabase.Execute( + "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"); - return AOR_OK; // everything's fine + return AOR_OK; // everything's fine // everything's fine } AccountOpResult AccountMgr::DeleteAccount(uint32 accid) @@ -121,11 +134,24 @@ AccountOpResult AccountMgr::ChangeUsername(uint32 accid, std::string new_uname, normalizeString(new_uname); normalizeString(new_passwd); + SRP6 srp; + + srp.CalculateVerifier(CalculateShaPassHash(new_uname, new_passwd)); + std::string safe_new_uname = new_uname; LoginDatabase.escape_string(safe_new_uname); - if (!LoginDatabase.PExecute("UPDATE `account` SET `v`='0', `s`='0', `username`='%s', `sha_pass_hash`='%s' WHERE `id`='%u'", safe_new_uname.c_str(), - CalculateShaPassHash(new_uname, new_passwd).c_str(), accid)) + const char* s_hex = srp.GetSalt().AsHexStr(); + const char* v_hex = srp.GetVerifier().AsHexStr(); + + bool update_sv = LoginDatabase.PExecute( + "UPDATE `account` SET `v`='%s', `s`='%s', `username`='%s' WHERE `id`='%u'", + v_hex, s_hex, safe_new_uname.c_str(), accid); + + OPENSSL_free((void*)s_hex); + OPENSSL_free((void*)v_hex); + + if (!update_sv) return AOR_DB_INTERNAL_ERROR; // unexpected error return AOR_OK; @@ -146,9 +172,22 @@ AccountOpResult AccountMgr::ChangePassword(uint32 accid, std::string new_passwd, normalizeString(new_passwd); + SRP6 srp; + + srp.CalculateVerifier(CalculateShaPassHash(username, new_passwd)); + + const char* s_hex = srp.GetSalt().AsHexStr(); + const char* v_hex = srp.GetVerifier().AsHexStr(); + + bool update_sv = LoginDatabase.PExecute( + "UPDATE `account` SET `v`='%s', `s`='%s' WHERE `id`='%u'", + v_hex, s_hex, accid); + + OPENSSL_free((void*)s_hex); + OPENSSL_free((void*)v_hex); + // also reset s and v to force update at next realmd login - if (!LoginDatabase.PExecute("UPDATE `account` SET `v`='0', `s`='0', `sha_pass_hash`='%s' WHERE `id`='%u'", - CalculateShaPassHash(username, new_passwd).c_str(), accid)) + if (!update_sv) return AOR_DB_INTERNAL_ERROR; // unexpected error return AOR_OK; @@ -273,11 +312,22 @@ bool AccountMgr::CheckPassword(uint32 accid, std::string passwd, std::string use normalizeString(passwd); - QueryResult* result = LoginDatabase.PQuery("SELECT 1 FROM `account` WHERE `id`='%u' AND `sha_pass_hash`='%s'", accid, CalculateShaPassHash(username, passwd).c_str()); + QueryResult* result = LoginDatabase.PQuery("SELECT `s`, `v` FROM `account` WHERE `id`='%u'", accid); if (result) { + Field* fields = result->Fetch(); + SRP6 srp; + + bool calcv = srp.CalculateVerifier( + CalculateShaPassHash(username, passwd), fields[0].GetCppString().c_str()); + + if (calcv && srp.ProofVerifier(fields[1].GetCppString())) + { + delete result; + return true; + } + delete result; - return true; } return false; diff --git a/src/realmd/AuthSocket.cpp b/src/realmd/AuthSocket.cpp index 496abec74b3..3ae83f3d970 100644 --- a/src/realmd/AuthSocket.cpp +++ b/src/realmd/AuthSocket.cpp @@ -186,8 +186,6 @@ std::array VersionChallenge = { { 0xBA, 0xA3, 0x1E, 0x99, 0xA0, 0x0B, /// Constructor - set the N and g values for SRP6 AuthSocket::AuthSocket() : promptPin(false), gridSeed(0), _geoUnlockPIN(0), _accountId(0), _lastRealmListRequest(0) { - N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7"); - g.SetDword(7); _status = STATUS_CHALLENGE; _accountDefaultSecurityLevel = SEC_PLAYER; @@ -277,38 +275,6 @@ void AuthSocket::OnRead() } } -/// Make the SRP6 calculation from hash in dB -void AuthSocket::_SetVSFields(const std::string& rI) -{ - s.SetRand(s_BYTE_SIZE * 8); - - BigNumber I; - I.SetHexStr(rI.c_str()); - - // In case of leading zeros in the rI hash, restore them - uint8 mDigest[SHA_DIGEST_LENGTH]; - memset(mDigest, 0, SHA_DIGEST_LENGTH); - if (I.GetNumBytes() <= SHA_DIGEST_LENGTH) - memcpy(mDigest, I.AsByteArray().data(), I.GetNumBytes()); - - std::reverse(mDigest, mDigest + SHA_DIGEST_LENGTH); - - Sha1Hash sha; - sha.UpdateData(s.AsByteArray()); - sha.UpdateData(mDigest, SHA_DIGEST_LENGTH); - sha.Finalize(); - BigNumber x; - x.SetBinary(sha.GetDigest(), sha.GetLength()); - v = g.ModExp(x, N); - // No SQL injection (username escaped) - const char *v_hex, *s_hex; - v_hex = v.AsHexStr(); - s_hex = s.AsHexStr(); - LoginDatabase.PExecute("UPDATE `account` SET `v` = '%s', `s` = '%s' WHERE `username` = '%s'", v_hex, s_hex, _safelogin.c_str() ); - OPENSSL_free((void*)v_hex); - OPENSSL_free((void*)s_hex); -} - void AuthSocket::SendProof(Sha1Hash sha) { if (_build < 6299) // before version 2.0.3 (exclusive) @@ -424,7 +390,8 @@ bool AuthSocket::_HandleLogonChallenge() { ///- Get the account details from the account table // No SQL injection (escaped user name) - result = LoginDatabase.PQuery("SELECT `sha_pass_hash`, `id`, `locked`, `last_ip`, `v`, `s`, `security`, `email_verif`, `geolock_pin`, `email`, UNIX_TIMESTAMP(`joindate`) FROM `account` WHERE `username` = '%s'",_safelogin.c_str ()); + // 0 1 2 3 4 5 6 7 8 9 + result = LoginDatabase.PQuery("SELECT `id`, `locked`, `last_ip`, `v`, `s`, `security`, `email_verif`, `geolock_pin`, `email`, UNIX_TIMESTAMP(`joindate`) FROM `account` WHERE `username` = '%s'",_safelogin.c_str ()); if (result) { Field* fields = result->Fetch(); @@ -432,12 +399,12 @@ bool AuthSocket::_HandleLogonChallenge() // Prevent login if the user's email address has not been verified bool requireVerification = sConfig.GetBoolDefault("ReqEmailVerification", false); int32 requireEmailSince = sConfig.GetIntDefault("ReqEmailSince", 0); - bool verified = (*result)[7].GetBool(); + bool verified = (*result)[6].GetBool(); // Prevent login if the user's join date is bigger than the timestamp in configuration if (requireEmailSince > 0) { - uint32 t = (*result)[10].GetUInt32(); + uint32 t = (*result)[9].GetUInt32(); requireVerification = requireVerification && (t >= uint32(requireEmailSince)); } @@ -451,11 +418,11 @@ bool AuthSocket::_HandleLogonChallenge() ///- If the IP is 'locked', check that the player comes indeed from the correct IP address bool locked = false; - lockFlags = (LockFlag)(*result)[2].GetUInt32(); - securityInfo = (*result)[6].GetCppString(); - _lastIP = fields[3].GetString(); - _geoUnlockPIN = fields[8].GetUInt32(); - _email = fields[9].GetCppString(); + lockFlags = (LockFlag)(*result)[1].GetUInt32(); + securityInfo = (*result)[5].GetCppString(); + _lastIP = fields[2].GetString(); + _geoUnlockPIN = fields[7].GetUInt32(); + _email = fields[8].GetCppString(); if (lockFlags & IP_LOCK) { @@ -482,9 +449,20 @@ bool AuthSocket::_HandleLogonChallenge() DEBUG_LOG("[AuthChallenge] Account '%s' is not locked to ip", _login.c_str()); } - if (!locked || (locked && (lockFlags & FIXED_PIN || lockFlags & TOTP))) + std::string databaseV = fields[3].GetCppString(); + std::string databaseS = fields[4].GetCppString(); + bool broken = false; + + if (!srp.SetVerifier(databaseV.c_str()) || !srp.SetSalt(databaseS.c_str())) + { + pkt << (uint8)WOW_FAIL_FAIL_NOACCESS; + BASIC_LOG("[AuthChallenge] Broken v/s values in database for account %s!", _login.c_str()); + broken = true; + } + + if ((!locked || (locked && (lockFlags & FIXED_PIN || lockFlags & TOTP))) && !broken) { - uint32 account_id = fields[1].GetUInt32(); + uint32 account_id = fields[0].GetUInt32(); ///- If the account is banned, reject the logon attempt QueryResult *banresult = LoginDatabase.PQuery("SELECT `bandate`, `unbandate` FROM `account_banned` WHERE " "`id` = %u AND `active` = 1 AND (`unbandate` > UNIX_TIMESTAMP() OR `unbandate` = `bandate`) LIMIT 1", account_id); @@ -505,39 +483,22 @@ bool AuthSocket::_HandleLogonChallenge() } else { - ///- Get the password from the account table, upper it, and make the SRP6 calculation - std::string rI = fields[0].GetCppString(); - - ///- Don't calculate (v, s) if there are already some in the database - std::string databaseV = fields[4].GetCppString(); - std::string databaseS = fields[5].GetCppString(); - DEBUG_LOG("database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); - // multiply with 2, bytes are stored as hexstring - if(databaseV.size() != s_BYTE_SIZE*2 || databaseS.size() != s_BYTE_SIZE*2) - _SetVSFields(rI); - else - { - s.SetHexStr(databaseS.c_str()); - v.SetHexStr(databaseV.c_str()); - } - - b.SetRand(19 * 8); - BigNumber gmod = g.ModExp(b, N); - B = ((v * 3) + gmod) % N; + BigNumber s; + s.SetHexStr(databaseS.c_str()); - MANGOS_ASSERT(gmod.GetNumBytes() <= 32); + srp.CalculateHostPublicEphemeral(); ///- Fill the response packet with the result pkt << uint8(WOW_SUCCESS); // B may be calculated < 32B so we force minimal length to 32B - pkt.append(B.AsByteArray(32)); // 32 bytes + pkt.append(srp.GetHostPublicEphemeral().AsByteArray(32).data(), 32); // 32 bytes pkt << uint8(1); - pkt.append(g.AsByteArray()); + pkt.append(srp.GetGeneratorModulo().AsByteArray().data(), 1); pkt << uint8(32); - pkt.append(N.AsByteArray(32)); + pkt.append(srp.GetPrime().AsByteArray(32).data(), 32); pkt.append(s.AsByteArray()); // 32 bytes pkt.append(VersionChallenge.data(), VersionChallenge.size()); @@ -690,81 +651,11 @@ bool AuthSocket::_HandleLogonProof() /// ///- Continue the SRP6 calculation based on data received from the client - BigNumber A; - - A.SetBinary(lp.A, 32); - - // SRP safeguard: abort if A==0 - if (A.isZero()) - return false; - - if ((A % N).isZero()) + if (!srp.CalculateSessionKey(lp.A, 32)) return false; - Sha1Hash sha; - sha.UpdateBigNumbers(&A, &B, nullptr); - sha.Finalize(); - BigNumber u; - u.SetBinary(sha.GetDigest(), 20); - BigNumber S = (A * (v.ModExp(u, N))).ModExp(b, N); - - uint8 t[32]; - uint8 t1[16]; - uint8 vK[40]; - memcpy(t, S.AsByteArray(32).data(), 32); - for (int i = 0; i < 16; ++i) - { - t1[i] = t[i * 2]; - } - sha.Initialize(); - sha.UpdateData(t1, 16); - sha.Finalize(); - for (int i = 0; i < 20; ++i) - { - vK[i * 2] = sha.GetDigest()[i]; - } - for (int i = 0; i < 16; ++i) - { - t1[i] = t[i * 2 + 1]; - } - sha.Initialize(); - sha.UpdateData(t1, 16); - sha.Finalize(); - for (int i = 0; i < 20; ++i) - { - vK[i * 2 + 1] = sha.GetDigest()[i]; - } - K.SetBinary(vK, 40); - - uint8 hash[20]; - - sha.Initialize(); - sha.UpdateBigNumbers(&N, nullptr); - sha.Finalize(); - memcpy(hash, sha.GetDigest(), 20); - sha.Initialize(); - sha.UpdateBigNumbers(&g, nullptr); - sha.Finalize(); - for (int i = 0; i < 20; ++i) - { - hash[i] ^= sha.GetDigest()[i]; - } - BigNumber t3; - t3.SetBinary(hash, 20); - - sha.Initialize(); - sha.UpdateData(_login); - sha.Finalize(); - uint8 t4[SHA_DIGEST_LENGTH]; - memcpy(t4, sha.GetDigest(), SHA_DIGEST_LENGTH); - - sha.Initialize(); - sha.UpdateBigNumbers(&t3, nullptr); - sha.UpdateData(t4, SHA_DIGEST_LENGTH); - sha.UpdateBigNumbers(&s, &A, &B, &K, nullptr); - sha.Finalize(); - BigNumber M; - M.SetBinary(sha.GetDigest(), 20); + srp.HashSessionKey(); + srp.CalculateProof(_login); ///- Check PIN data is correct bool pinResult = true; @@ -804,7 +695,7 @@ bool AuthSocket::_HandleLogonProof() } ///- Check if SRP6 results match (password is correct), else send an error - if (!memcmp(M.AsByteArray().data(), lp.M1, 20) && pinResult) + if (!srp.Proof(lp.M1, 20) && pinResult) { if (!VerifyVersion(lp.A, sizeof(lp.A), lp.crc_hash, false)) { @@ -875,7 +766,7 @@ bool AuthSocket::_HandleLogonProof() ///- Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account // No SQL injection (escaped user name) and IP address as received by socket - const char* K_hex = K.AsHexStr(); + const char* K_hex = srp.GetStrongSessionKey().AsHexStr(); const char *os = reinterpret_cast(&_os); // no injection as there are only two possible values auto result = LoginDatabase.PQuery("UPDATE `account` SET `sessionkey` = '%s', `last_ip` = '%s', `last_login` = NOW(), `locale` = '%u', `failed_logins` = 0, `os` = '%s' WHERE `username` = '%s'", K_hex, get_remote_address().c_str(), GetLocaleByName(_localizationName), os, _safelogin.c_str() ); @@ -883,9 +774,8 @@ bool AuthSocket::_HandleLogonProof() OPENSSL_free((void*)K_hex); ///- Finish SRP6 and send the final result to the client - sha.Initialize(); - sha.UpdateBigNumbers(&A, &M, &K, nullptr); - sha.Finalize(); + Sha1Hash sha; + srp.Finalize(sha); SendProof(sha); @@ -1001,7 +891,7 @@ bool AuthSocket::_HandleReconnectChallenge() } Field* fields = result->Fetch (); - K.SetHexStr (fields[0].GetString ()); + srp.SetStrongSessionKey(fields[0].GetString()); _accountId = fields[1].GetUInt32(); delete result; @@ -1031,6 +921,7 @@ bool AuthSocket::_HandleReconnectProof() ///- Session is closed unless overriden _status = STATUS_CLOSED; + BigNumber K = srp.GetStrongSessionKey(); if (_login.empty() || !_reconnectProof.GetNumBytes() || !K.GetNumBytes()) return false; diff --git a/src/realmd/AuthSocket.h b/src/realmd/AuthSocket.h index 1b688359a9e..81e6606349e 100644 --- a/src/realmd/AuthSocket.h +++ b/src/realmd/AuthSocket.h @@ -29,6 +29,7 @@ #include "Common.h" #include "Auth/BigNumber.h" #include "Auth/Sha1.h" +#include "SRP6/SRP6.h" #include "ByteBuffer.h" #include "BufferedSocket.h" @@ -77,8 +78,6 @@ class AuthSocket: public BufferedSocket bool _HandleXferCancel(); bool _HandleXferAccept(); - void _SetVSFields(const std::string& rI); - private: enum eStatus { @@ -92,9 +91,7 @@ class AuthSocket: public BufferedSocket bool VerifyVersion(uint8 const* a, int32 aLength, uint8 const* versionProof, bool isReconnect); - BigNumber N, s, g, v; - BigNumber b, B; - BigNumber K; + SRP6 srp; BigNumber _reconnectProof; bool promptPin; diff --git a/src/shared/Auth/BigNumber.cpp b/src/shared/Auth/BigNumber.cpp index cb117398e53..44160d38c48 100644 --- a/src/shared/Auth/BigNumber.cpp +++ b/src/shared/Auth/BigNumber.cpp @@ -61,9 +61,9 @@ void BigNumber::SetBinary(uint8 const* bytes, int len) BN_bin2bn(t, len, _bn); } -void BigNumber::SetHexStr(char const* str) +int BigNumber::SetHexStr(const char* str) { - BN_hex2bn(&_bn, str); + return BN_hex2bn(&_bn, str); } void BigNumber::SetRand(int numbits) diff --git a/src/shared/Auth/BigNumber.h b/src/shared/Auth/BigNumber.h index 71170c4cd52..eebd7fc785a 100644 --- a/src/shared/Auth/BigNumber.h +++ b/src/shared/Auth/BigNumber.h @@ -35,7 +35,7 @@ class BigNumber void SetDword(uint32); void SetQword(uint64); void SetBinary(uint8 const* bytes, int len); - void SetHexStr(char const* str); + int SetHexStr(char const* str); void SetRand(int numbits); diff --git a/src/shared/Auth/Sha1.h b/src/shared/Auth/Sha1.h index 49d4fcebfe7..607e6807e4e 100644 --- a/src/shared/Auth/Sha1.h +++ b/src/shared/Auth/Sha1.h @@ -41,7 +41,7 @@ class Sha1Hash void Finalize(); uint8* GetDigest(void) { return mDigest; }; - int GetLength(void) { return SHA_DIGEST_LENGTH; }; + static int GetLength(void) { return SHA_DIGEST_LENGTH; }; private: SHA_CTX mC; diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt index 4079b92badc..a4b7cd3c6b5 100644 --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -63,6 +63,7 @@ set (shared_SRCS Database/SqlPreparedStatement.h Database/SQLStorage.h Database/SQLStorageImpl.h + SRP6/SRP6.h nonstd/optional.hpp Common.cpp DelayExecutor.cpp @@ -93,6 +94,7 @@ set (shared_SRCS Database/SqlOperations.cpp Database/SqlPreparedStatement.cpp Database/SQLStorage.cpp + SRP6/SRP6.cpp ) if(USE_LIBCURL) diff --git a/src/shared/SRP6/SRP6.cpp b/src/shared/SRP6/SRP6.cpp new file mode 100644 index 00000000000..b59b10efb6f --- /dev/null +++ b/src/shared/SRP6/SRP6.cpp @@ -0,0 +1,202 @@ +/* + * This file is part of the CMaNGOS Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "Common.h" +#include "Auth/HMACSHA1.h" +#include "Auth/base32.h" +#include "SRP6.h" + +SRP6::SRP6() +{ + N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7"); + g.SetDword(7); +} + +void SRP6::CalculateHostPublicEphemeral(void) +{ + b.SetRand(19 * 8); + BigNumber gmod = g.ModExp(b, N); + B = ((v * 3) + gmod) % N; + + MANGOS_ASSERT(gmod.GetNumBytes() <= 32); +} + +void SRP6::CalculateProof(std::string username) +{ + uint8 hash[20]; + + Sha1Hash sha; + sha.Initialize(); + sha.UpdateBigNumbers(&N, nullptr); + sha.Finalize(); + memcpy(hash, sha.GetDigest(), 20); + sha.Initialize(); + sha.UpdateBigNumbers(&g, nullptr); + sha.Finalize(); + for (int i = 0; i < 20; ++i) + { + hash[i] ^= sha.GetDigest()[i]; + } + BigNumber t3; + t3.SetBinary(hash, 20); + + sha.Initialize(); + sha.UpdateData(username); + sha.Finalize(); + uint8 t4[SHA_DIGEST_LENGTH]; + memcpy(t4, sha.GetDigest(), SHA_DIGEST_LENGTH); + + sha.Initialize(); + sha.UpdateBigNumbers(&t3, nullptr); + sha.UpdateData(t4, SHA_DIGEST_LENGTH); + sha.UpdateBigNumbers(&s, &A, &B, &K, nullptr); + sha.Finalize(); + M.SetBinary(sha.GetDigest(), 20); +} + +bool SRP6::CalculateSessionKey(uint8* lp_A, int l) +{ + A.SetBinary(lp_A, l); + + // SRP safeguard: abort if A==0 + if (A.isZero()) + return false; + + if ((A % N).isZero()) + return false; + + Sha1Hash sha; + sha.UpdateBigNumbers(&A, &B, nullptr); + sha.Finalize(); + u.SetBinary(sha.GetDigest(), 20); + S = (A * (v.ModExp(u, N))).ModExp(b, N); + + return true; +} + +bool SRP6::CalculateVerifier(const std::string& rI) +{ + BigNumber salt; + salt.SetRand(s_BYTE_SIZE * 8); + const char* _salt = salt.AsHexStr(); + bool ret = CalculateVerifier(rI, _salt); + OPENSSL_free((void*)_salt); + return ret; +} + +bool SRP6::CalculateVerifier(const std::string& rI, const char* salt) +{ + if (s.SetHexStr(salt) == 0 || s.isZero()) + return false; + + BigNumber I; + I.SetHexStr(rI.c_str()); + + // in case of leading zeros in the rI hash, restore them + uint8 mDigest[SHA_DIGEST_LENGTH]; + memset(mDigest, 0, SHA_DIGEST_LENGTH); + if (I.GetNumBytes() <= SHA_DIGEST_LENGTH) + memcpy(mDigest, I.AsByteArray().data(), I.GetNumBytes()); + + std::reverse(mDigest, mDigest + SHA_DIGEST_LENGTH); + + Sha1Hash sha; + sha.UpdateData(s.AsByteArray().data(), s.GetNumBytes()); + sha.UpdateData(mDigest, SHA_DIGEST_LENGTH); + sha.Finalize(); + BigNumber x; + x.SetBinary(sha.GetDigest(), Sha1Hash::GetLength()); + v = g.ModExp(x, N); + + return true; +} + +void SRP6::HashSessionKey(void) +{ + uint8 t[32]; + uint8 t1[16]; + uint8 vK[40]; + memcpy(t, S.AsByteArray(32).data(), 32); + for (int i = 0; i < 16; ++i) + { + t1[i] = t[i * 2]; + } + Sha1Hash sha; + sha.Initialize(); + sha.UpdateData(t1, 16); + sha.Finalize(); + for (int i = 0; i < 20; ++i) + { + vK[i * 2] = sha.GetDigest()[i]; + } + for (int i = 0; i < 16; ++i) + { + t1[i] = t[i * 2 + 1]; + } + sha.Initialize(); + sha.UpdateData(t1, 16); + sha.Finalize(); + for (int i = 0; i < 20; ++i) + { + vK[i * 2 + 1] = sha.GetDigest()[i]; + } + K.SetBinary(vK, 40); +} + +bool SRP6::Proof(uint8* lp_M, int l) +{ + if (!memcmp(M.AsByteArray().data(), lp_M, l)) + return false; + + return true; +} + +bool SRP6::ProofVerifier(std::string vC) +{ + const char* vC_hex = vC.c_str(); + const char* v_hex = v.AsHexStr(); + + if (memcmp(vC_hex, v_hex, strlen(vC_hex)) == 0) + { + OPENSSL_free((void*)v_hex); + return true; + } + + OPENSSL_free((void*)v_hex); + return false; +} + +void SRP6::Finalize(Sha1Hash& sha) +{ + sha.Initialize(); + sha.UpdateBigNumbers(&A, &M, &K, nullptr); + sha.Finalize(); +} + +bool SRP6::SetSalt(const char* new_s) +{ + if (s.SetHexStr(new_s) == 0 || s.isZero()) + return false; + return true; +} +bool SRP6::SetVerifier(const char* new_v) +{ + if (v.SetHexStr(new_v) == 0 || v.isZero()) + return false; + return true; +} diff --git a/src/shared/SRP6/SRP6.h b/src/shared/SRP6/SRP6.h new file mode 100644 index 00000000000..3f9de3c09f5 --- /dev/null +++ b/src/shared/SRP6/SRP6.h @@ -0,0 +1,122 @@ +/* + * This file is part of the CMaNGOS Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _SRP6_H +#define _SRP6_H + +#include "Common.h" +#include "Auth/BigNumber.h" +#include "Auth/Sha1.h" +#include "ByteBuffer.h" + +#define HMAC_RES_SIZE 20 + +/*! Secure Remote Password protocol */ +class SRP6 +{ + public: + const static int s_BYTE_SIZE = 32; /*!< length of salt */ + + //! Initializes SRP with a predefined prime (N) and generator module (g) + SRP6(void); + + //! calculates the host public ephemeral (B) + /*! + generates also a random number as host private ephemeral (b) + */ + void CalculateHostPublicEphemeral(void); + + //! calculates proof (M) of the strong session key (K) + /*! + \param username the unique identity of the account to authenticate + */ + void CalculateProof(std::string username); + + //! calculates a session key (S) based on client public ephemeral (A) + /*! + safeguard conditions are (A != 0) and (A % N != 0) + \param lp_A the client public ephemeral (A) + \param l the length of client public ephemeral (A) + \return true on valid safeguard conditions otherwise false + */ + bool CalculateSessionKey(uint8* lp_A, int l); + + //! calculates the password verifier (v) + /*! + \param rI a sha1 hash of USERNAME:PASSWORD + \return true on success otherwise false if s is faulty + */ + bool CalculateVerifier(const std::string& rI); + + //! calculates the password verifier (v) based on a predefined salt (s) + /*! + \param rI a sha1 hash of USERNAME:PASSWORD + \param salt a predefined salt (s) + \return true on success otherwise false if s is faulty + */ + bool CalculateVerifier(const std::string& rI, const char* salt); + + //! generates a strong session key (K) of session key (S) + void HashSessionKey(void); + + //! compares proof (M) of strong session key (K) + /*! + \param lp_M client proof (M) of the strong session key (K) + \param l the length of client proof (M) + \return true if client and server proof matches otherwise false + */ + bool Proof(uint8* lp_M, int l); + + //! compare password verifier (v) + /*! + verifies if provided password matches the password verifier (v) + requires to use the same salt (s) which was initially used to compute v. + \param vC predefined password verifier (v) read from database + \return true if password verifier matches otherwise false + */ + bool ProofVerifier(std::string vC); + + //! generate hash for proof of strong session key (K) + /*! + this hash has to be send to the client for client-side proof. + client has to show it's proof first. If the server detects an incorrect proof + it must abort without showing it's proof. + \param sha reference to an empty Sha1Hash object + */ + void Finalize(Sha1Hash& sha); + + BigNumber GetHostPublicEphemeral(void) { return B; }; + BigNumber GetGeneratorModulo(void) { return g; }; + BigNumber GetPrime(void) { return N; }; + BigNumber GetProof(void) { return M; }; + BigNumber GetSalt(void) { return s; }; + BigNumber GetStrongSessionKey(void) { return K; }; + BigNumber GetVerifier(void) { return v; }; + + bool SetSalt(const char* new_s); + void SetStrongSessionKey(const char* new_K) { K.SetHexStr(new_K); }; + bool SetVerifier(const char* new_v); + + private: + BigNumber A, u, S; + BigNumber N, s, g, v; + BigNumber b, B; + BigNumber K; + BigNumber M; +}; +#endif