aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/Cryptography/Authentication/SRP6.cpp203
-rw-r--r--src/common/Cryptography/Authentication/SRP6.h258
-rw-r--r--src/common/Cryptography/BigNumber.cpp7
-rw-r--r--src/common/Cryptography/BigNumber.h2
-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
14 files changed, 618 insertions, 193 deletions
diff --git a/src/common/Cryptography/Authentication/SRP6.cpp b/src/common/Cryptography/Authentication/SRP6.cpp
index c172bbc1d3d..e015b8c33f6 100644
--- a/src/common/Cryptography/Authentication/SRP6.cpp
+++ b/src/common/Cryptography/Authentication/SRP6.cpp
@@ -17,42 +17,113 @@
#include "SRP6.h"
#include "CryptoRandom.h"
-#include "Util.h"
#include <algorithm>
#include <functional>
-using SHA1 = Trinity::Crypto::SHA1;
-using SRP6 = Trinity::Crypto::SRP6;
+namespace Trinity::Crypto::SRP
+{
+using namespace std::string_literals;
+
+BigNumber const GruntSRP6::N = "894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7"s;
+BigNumber const GruntSRP6::g = 7;
+
+BigNumber const BnetSRP6v1Base::N = "86A7F6DEEB306CE519770FE37D556F29944132554DED0BD68205E27F3231FEF5A10108238A3150C59CAF7B0B6478691C13A6ACF5E1B5ADAFD4A943D4A21A142B800E8A55F8BFBAC700EB77A7235EE5A609E350EA9FC19F10D921C2FA832E4461B7125D38D254A0BE873DFC27858ACB3F8B9F258461E4373BC3A6C2A9634324AB"s;
+BigNumber const BnetSRP6v1Base::g = 2;
+
+BigNumber const BnetSRP6v2Base::N = "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73"s;
+BigNumber const BnetSRP6v2Base::g = 2;
+
+SRP6::SRP6(BigNumber const& i, Salt const& salt, Verifier const& verifier, BigNumber const& N, BigNumber const& g, BigNumber const& k)
+ : s(salt), I(i), b(CalculatePrivateB(N)), v(verifier), B(CalculatePublicB(N, g, k))
+{
+}
+
+SRP6::SRP6(ForRegistrationTag)
+ : s(GetRandomBytes<SALT_LENGTH>()), _used(true)
+{
+}
-/*static*/ std::array<uint8, 1> const SRP6::g = { 7 };
-/*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);
+Optional<BigNumber> SRP6::VerifyClientEvidence(BigNumber const& A, BigNumber const& clientM1)
+{
+ ASSERT(!_used, "A single SRP6 object must only ever be used to verify ONCE!");
+ _used = true;
+
+ return DoVerifyClientEvidence(A, clientM1);
+}
-/*static*/ std::pair<SRP6::Salt, SRP6::Verifier> SRP6::MakeRegistrationData(std::string const& username, std::string const& password)
+bool SRP6::CheckCredentials(std::string const& username, std::string const& password) const
{
- std::pair<SRP6::Salt, SRP6::Verifier> res;
- Crypto::GetRandomBytes(res.first); // random salt
- res.second = CalculateVerifier(username, password, res.first);
- return res;
+ return v == CalculateVerifier(username, password, s);
}
-/*static*/ SRP6::Verifier SRP6::CalculateVerifier(std::string const& username, std::string const& password, SRP6::Salt const& salt)
+BigNumber SRP6::CalculatePrivateB(BigNumber const& N)
+{
+ BigNumber b;
+ b.SetRand(N.GetNumBits());
+ b %= N - 1;
+ return b;
+}
+
+BigNumber SRP6::CalculatePublicB(BigNumber const& N, BigNumber const& g, BigNumber const& k) const
+{
+ return (g.ModExp(b, N) + (v * k)) % N;
+}
+
+Verifier SRP6::CalculateVerifier(std::string const& username, std::string const& password, Salt const& salt) const
{
// v = g ^ H(s || H(u || ':' || p)) mod N
- return _g.ModExp(
- SHA1::GetDigestOf(
- salt,
- SHA1::GetDigestOf(username, ":", password)
- )
- ,_N).ToByteArray<32>();
+ return Getg().ModExp(
+ CalculateX(username, password, salt),
+ GetN()
+ ).ToByteVector();
+}
+
+GruntSRP6::GruntSRP6(std::string const& username, Salt const& salt, Verifier const& verifier)
+ : SRP6(SHA1::GetDigestOf(username), salt, verifier, N, g, 3)
+{
+}
+
+BigNumber GruntSRP6::CalculateServerEvidence(BigNumber const& A, BigNumber const& clientM1, BigNumber const& K) const
+{
+ return SHA1::GetDigestOf(A.ToByteArray<EPHEMERAL_KEY_LENGTH>(), clientM1.ToByteArray<SHA1::DIGEST_LENGTH>(), K.ToByteArray<SHA1::DIGEST_LENGTH * 2>());
+}
+
+BigNumber GruntSRP6::CalculateX(std::string const& username, std::string const& password, Salt const& salt) const
+{
+ return SHA1::GetDigestOf(
+ salt,
+ SHA1::GetDigestOf(username, ":", password)
+ );
}
-/*static*/ SessionKey SRP6::SHA1Interleave(SRP6::EphemeralKey const& S)
+Optional<BigNumber> GruntSRP6::DoVerifyClientEvidence(BigNumber const& A, BigNumber const& clientM1)
+{
+ if ((A % N).IsZero())
+ return {};
+
+ BigNumber const u(SHA1::GetDigestOf(A.ToByteArray<EPHEMERAL_KEY_LENGTH>(), B.ToByteArray<EPHEMERAL_KEY_LENGTH>()));
+ EphemeralKey const S = (A * (v.ModExp(u, N))).ModExp(b, N).ToByteArray<EPHEMERAL_KEY_LENGTH>();
+
+ SessionKey K = SHA1Interleave(S);
+
+ // NgHash = H(N) xor H(g)
+ SHA1::Digest const NHash = SHA1::GetDigestOf(N.ToByteArray<32>());
+ SHA1::Digest const gHash = SHA1::GetDigestOf(g.ToByteArray<1>());
+ SHA1::Digest NgHash;
+ std::transform(NHash.begin(), NHash.end(), gHash.begin(), NgHash.begin(), std::bit_xor<>());
+
+ BigNumber const ourM = SHA1::GetDigestOf(NgHash, I.ToByteArray<SHA1::DIGEST_LENGTH>(), s, A.ToByteArray<EPHEMERAL_KEY_LENGTH>(), B.ToByteArray<EPHEMERAL_KEY_LENGTH>(), K);
+ if (ourM == clientM1)
+ return K;
+
+ return {};
+}
+
+GruntSRP6::SessionKey GruntSRP6::SHA1Interleave(EphemeralKey const& S)
{
// split S into two buffers
- std::array<uint8, EPHEMERAL_KEY_LENGTH/2> buf0, buf1;
- for (size_t i = 0; i < EPHEMERAL_KEY_LENGTH/2; ++i)
+ std::array<uint8, EPHEMERAL_KEY_LENGTH / 2> buf0, buf1;
+ for (size_t i = 0; i < S.size() / 2; ++i)
{
buf0[i] = S[2 * i + 0];
buf1[i] = S[2 * i + 1];
@@ -60,13 +131,13 @@ using SRP6 = Trinity::Crypto::SRP6;
// find position of first nonzero byte
size_t p = 0;
- while (p < EPHEMERAL_KEY_LENGTH && !S[p]) ++p;
+ while (p < S.size() && !S[p]) ++p;
if (p & 1) ++p; // skip one extra byte if p is odd
p /= 2; // offset into buffers
// hash each of the halves, starting at the first nonzero byte
- SHA1::Digest const hash0 = SHA1::GetDigestOf(buf0.data() + p, EPHEMERAL_KEY_LENGTH/2 - p);
- SHA1::Digest const hash1 = SHA1::GetDigestOf(buf1.data() + p, EPHEMERAL_KEY_LENGTH/2 - p);
+ SHA1::Digest const hash0 = SHA1::GetDigestOf(buf0.data() + p, S.size() / 2 - p);
+ SHA1::Digest const hash1 = SHA1::GetDigestOf(buf1.data() + p, S.size() / 2 - p);
// stick the two hashes back together
SessionKey K;
@@ -78,32 +149,74 @@ using SRP6 = Trinity::Crypto::SRP6;
return K;
}
-SRP6::SRP6(std::string const& username, Salt const& salt, Verifier const& verifier)
- : _I(SHA1::GetDigestOf(username)), _b(Crypto::GetRandomBytes<32>()), _v(verifier), s(salt), B(_B(_b, _v)) {}
+BnetSRP6Base::BnetSRP6Base(BigNumber const& i, Salt const& salt, Verifier const& verifier, BigNumber const& N, BigNumber const& g, BigNumber const& k)
+ : SRP6(i, salt, verifier, N, g, k)
+{
+}
-Optional<SessionKey> SRP6::VerifyChallengeResponse(EphemeralKey const& A, SHA1::Digest const& clientM)
+BigNumber BnetSRP6Base::CalculateServerEvidence(BigNumber const& A, BigNumber const& clientM1, BigNumber const& K) const
{
- ASSERT(!_used, "A single SRP6 object must only ever be used to verify ONCE!");
- _used = true;
+ std::array evidenceBns{ &A, &clientM1, &K };
+ return DoCalculateEvidence(evidenceBns);
+}
- BigNumber const _A(A);
- if ((_A % _N).IsZero())
+Optional<BigNumber> BnetSRP6Base::DoVerifyClientEvidence(BigNumber const& A, BigNumber const& clientM1)
+{
+ BigNumber N = GetN();
+ if ((A % N).IsZero())
return {};
- BigNumber const u(SHA1::GetDigestOf(A, B));
- EphemeralKey const S = (_A * (_v.ModExp(u, _N))).ModExp(_b, N).ToByteArray<32>();
+ BigNumber const u = CalculateU(A);
+ if ((u % N).IsZero())
+ return {};
- SessionKey K = SHA1Interleave(S);
+ BigNumber const S = (A * v.ModExp(u, N)).ModExp(b, N);
- // NgHash = H(N) xor H(g)
- SHA1::Digest const NHash = SHA1::GetDigestOf(N);
- SHA1::Digest const gHash = SHA1::GetDigestOf(g);
- SHA1::Digest NgHash;
- std::transform(NHash.begin(), NHash.end(), gHash.begin(), NgHash.begin(), std::bit_xor<>());
-
- SHA1::Digest const ourM = SHA1::GetDigestOf(NgHash, _I, s, A, B, K);
- if (ourM == clientM)
- return K;
- else
+ std::array evidenceBns{ &A, &B, &S };
+ BigNumber const ourM = DoCalculateEvidence(evidenceBns);
+ if (ourM != clientM1)
return {};
+
+ return S;
+}
+
+std::vector<uint8> BnetSRP6Base::GetBrokenEvidenceVector(BigNumber const& bn)
+{
+ int32 bytes = (bn.GetNumBits() + 8) >> 3;
+ return bn.ToByteVector(bytes, false);
+}
+
+BnetSRP6v1Base::BnetSRP6v1Base(std::string const& username, Salt const& salt, Verifier const& verifier, BigNumber const& k)
+ : BnetSRP6Base(SHA256::GetDigestOf(username), salt, verifier, N, g, k)
+{
+}
+
+BigNumber BnetSRP6v1Base::CalculateX(std::string const& username, std::string const& password, Salt const& salt) const
+{
+ return SHA256::GetDigestOf(
+ salt,
+ SHA256::GetDigestOf(username, ":", password)
+ );
+}
+
+BnetSRP6v2Base::BnetSRP6v2Base(std::string const& username, Salt const& salt, Verifier const& verifier, BigNumber const& k)
+ : BnetSRP6Base(SHA256::GetDigestOf(username), salt, verifier, N, g, k)
+{
+}
+
+BigNumber BnetSRP6v2Base::CalculateX(std::string const& username, std::string const& password, Salt const& salt) const
+{
+ SHA512::Digest xBytes = {};
+ std::string tmp = username + ":" + password;
+ PKCS5_PBKDF2_HMAC(tmp.c_str(), tmp.length(), salt.data(), salt.size(), GetXIterations(), EVP_sha512(), xBytes.size(), xBytes.data());
+ BigNumber x(xBytes, false);
+ if (xBytes[0] & 0x80)
+ {
+ std::array<uint8, 65> fix = {};
+ fix[64] = 1;
+ x -= fix;
+ }
+
+ return x % (N - 1);
+}
}
diff --git a/src/common/Cryptography/Authentication/SRP6.h b/src/common/Cryptography/Authentication/SRP6.h
index 8b1a0aafd59..b29f9bff1a4 100644
--- a/src/common/Cryptography/Authentication/SRP6.h
+++ b/src/common/Cryptography/Authentication/SRP6.h
@@ -18,68 +18,224 @@
#ifndef TRINITY_SRP6_H
#define TRINITY_SRP6_H
-#include "AuthDefines.h"
#include "BigNumber.h"
-#include "Define.h"
#include "CryptoHash.h"
+#include "Define.h"
#include "Optional.h"
#include <array>
+#include <span>
-namespace Trinity
+namespace Trinity::Crypto
{
-namespace Crypto
+namespace SRP
{
+ static constexpr size_t SALT_LENGTH = 32;
+ using Salt = std::array<uint8, SALT_LENGTH>;
+
+ using Verifier = std::vector<uint8>;
+
class TC_COMMON_API SRP6
{
- public:
- static constexpr size_t SALT_LENGTH = 32;
- using Salt = std::array<uint8, SALT_LENGTH>;
- static constexpr size_t VERIFIER_LENGTH = 32;
- using Verifier = std::array<uint8, VERIFIER_LENGTH>;
- static constexpr size_t EPHEMERAL_KEY_LENGTH = 32;
- using EphemeralKey = std::array<uint8, EPHEMERAL_KEY_LENGTH>;
-
- static std::array<uint8, 1> const g;
- static std::array<uint8, 32> const N;
-
- // username + password must be passed through Utf8ToUpperOnlyLatin FIRST!
- static std::pair<Salt, Verifier> MakeRegistrationData(std::string const& username, std::string const& password);
- // username + password must be passed through Utf8ToUpperOnlyLatin FIRST!
- static bool CheckLogin(std::string const& username, std::string const& password, Salt const& salt, Verifier const& verifier)
- {
- return (verifier == CalculateVerifier(username, password, salt));
- }
-
- static SHA1::Digest GetSessionVerifier(EphemeralKey const& A, SHA1::Digest const& clientM, SessionKey const& K)
- {
- return SHA1::GetDigestOf(A, clientM, K);
- }
-
- SRP6(std::string const& username, Salt const& salt, Verifier const& verifier);
- Optional<SessionKey> VerifyChallengeResponse(EphemeralKey const& A, SHA1::Digest const& clientM);
-
- private:
- bool _used = false; // a single instance can only be used to verify once
-
- static Verifier CalculateVerifier(std::string const& username, std::string const& password, Salt const& salt);
- static SessionKey SHA1Interleave(EphemeralKey const& S);
-
- /* global algorithm parameters */
- static BigNumber const _g; // a [g]enerator for the ring of integers mod N, algorithm parameter
- static BigNumber const _N; // the modulus, an algorithm parameter; all operations are mod this
-
- static EphemeralKey _B(BigNumber const& b, BigNumber const& v) { return ((_g.ModExp(b,_N) + (v * 3)) % N).ToByteArray<EPHEMERAL_KEY_LENGTH>(); }
-
- /* per-instantiation parameters, set on construction */
- SHA1::Digest const _I; // H(I) - the username, all uppercase
- BigNumber const _b; // b - randomly chosen by the server, 19 bytes, never given out
- BigNumber const _v; // v - the user's password verifier, derived from s + H(USERNAME || ":" || PASSWORD)
-
- public:
- Salt const s; // s - the user's password salt, random, used to calculate v on registration
- EphemeralKey const B; // B = 3v + g^b
+ protected:
+ struct ForRegistrationTag { };
+
+ public:
+ explicit SRP6(BigNumber const& i, Salt const& salt, Verifier const& verifier, BigNumber const& N, BigNumber const& g, BigNumber const& k);
+ explicit SRP6(ForRegistrationTag);
+
+ SRP6(SRP6 const&) = delete;
+ SRP6(SRP6&&) = delete;
+ SRP6& operator=(SRP6 const&) = delete;
+ SRP6& operator=(SRP6&&) = delete;
+
+ virtual ~SRP6() = default;
+
+ virtual BigNumber const& GetN() const = 0;
+ virtual BigNumber const& Getg() const = 0;
+
+ Optional<BigNumber> VerifyClientEvidence(BigNumber const& A, BigNumber const& clientM1);
+
+ virtual BigNumber CalculateServerEvidence(BigNumber const& A, BigNumber const& clientM1, BigNumber const& K) const = 0;
+
+ template<typename Impl>
+ static std::pair<Salt, Verifier> MakeRegistrationData(std::string const& username, std::string const& password)
+ {
+ Impl impl(ForRegistrationTag{});
+ return { impl.s, impl.CalculateVerifier(username, password, impl.s) };
+ }
+
+ bool CheckCredentials(std::string const& username, std::string const& password) const;
+
+ Salt const s; // s - the user's password salt, random, used to calculate v on registration
+
+ protected:
+ static BigNumber CalculatePrivateB(BigNumber const& N);
+
+ BigNumber CalculatePublicB(BigNumber const& N, BigNumber const& g, BigNumber const& k) const;
+
+ virtual BigNumber CalculateX(std::string const& username, std::string const& password, Salt const& salt) const = 0;
+
+ Verifier CalculateVerifier(std::string const& username, std::string const& password, Salt const& salt) const;
+
+ virtual Optional<BigNumber> DoVerifyClientEvidence(BigNumber const& A, BigNumber const& clientM1) = 0;
+
+ BigNumber const I; // H(I) - the username, all uppercase
+ BigNumber const b; // b - randomly chosen by the server, same length as N, never given out
+ BigNumber const v; // v - the user's password verifier, derived from s + H(USERNAME || ":" || PASSWORD)
+
+ public:
+ BigNumber const B; // B = k*v + g^b
+
+ private:
+ bool _used = false; // a single instance can only be used to verify once
+ };
+
+ class TC_COMMON_API GruntSRP6 final : public SRP6
+ {
+ public:
+ static constexpr size_t EPHEMERAL_KEY_LENGTH = 32;
+
+ using EphemeralKey = std::array<uint8, EPHEMERAL_KEY_LENGTH>;
+
+ using SessionKey = std::array<uint8, SHA1::DIGEST_LENGTH * 2>;
+
+ static BigNumber const N; // the modulus, an algorithm parameter; all operations are mod this
+ static BigNumber const g; // a [g]enerator for the ring of integers mod N, algorithm parameter
+
+ explicit GruntSRP6(std::string const& username, Salt const& salt, Verifier const& verifier);
+ explicit GruntSRP6(ForRegistrationTag t) : SRP6(t) { }
+
+ BigNumber const& GetN() const override { return N; }
+ BigNumber const& Getg() const override { return g; }
+
+ BigNumber CalculateServerEvidence(BigNumber const& A, BigNumber const& clientM1, BigNumber const& K) const override;
+
+ protected:
+ BigNumber CalculateX(std::string const& username, std::string const& password, Salt const& salt) const override;
+
+ Optional<BigNumber> DoVerifyClientEvidence(BigNumber const& A, BigNumber const& clientM1) override;
+
+ static SessionKey SHA1Interleave(EphemeralKey const& S);
+ };
+
+ class TC_COMMON_API BnetSRP6Base : public SRP6
+ {
+ public:
+ explicit BnetSRP6Base(BigNumber const& i, Salt const& salt, Verifier const& verifier, BigNumber const& N, BigNumber const& g, BigNumber const& k);
+ explicit BnetSRP6Base(ForRegistrationTag t) : SRP6(t) { }
+
+ BigNumber CalculateServerEvidence(BigNumber const& A, BigNumber const& clientM1, BigNumber const& K) const final;
+
+ virtual uint8 GetVersion() const = 0;
+ virtual uint32 GetXIterations() const = 0;
+
+ protected:
+ Optional<BigNumber> DoVerifyClientEvidence(BigNumber const& A, BigNumber const& clientM1) final;
+
+ virtual BigNumber CalculateU(BigNumber const& A) const = 0;
+
+ virtual BigNumber DoCalculateEvidence(std::span<BigNumber const*> bns) const = 0;
+
+ template<typename CryptoHash>
+ BigNumber DoCalculateEvidence(std::span<BigNumber const*> bns) const
+ {
+ CryptoHash hash;
+ for (BigNumber const* bn : bns)
+ hash.UpdateData(GetBrokenEvidenceVector(*bn));
+
+ hash.Finalize();
+ return BigNumber(hash.GetDigest(), false);
+ }
+
+ static std::vector<uint8> GetBrokenEvidenceVector(BigNumber const& bn);
+ };
+
+ class TC_COMMON_API BnetSRP6v1Base : public BnetSRP6Base
+ {
+ public:
+ static BigNumber const N; // the modulus, an algorithm parameter; all operations are mod this
+ static BigNumber const g; // a [g]enerator for the ring of integers mod N, algorithm parameter
+
+ explicit BnetSRP6v1Base(std::string const& username, Salt const& salt, Verifier const& verifier, BigNumber const& k);
+ explicit BnetSRP6v1Base(ForRegistrationTag t) : BnetSRP6Base(t) { }
+
+ BigNumber const& GetN() const final { return N; }
+ BigNumber const& Getg() const final { return g; }
+
+ uint8 GetVersion() const final { return 1; }
+ uint32 GetXIterations() const final { return 1; }
+
+ protected:
+ BigNumber CalculateX(std::string const& username, std::string const& password, Salt const& salt) const final;
+ };
+
+ class TC_COMMON_API BnetSRP6v2Base : public BnetSRP6Base
+ {
+ public:
+ static BigNumber const N; // the modulus, an algorithm parameter; all operations are mod this
+ static BigNumber const g; // a [g]enerator for the ring of integers mod N, algorithm parameter
+
+ explicit BnetSRP6v2Base(std::string const& username, Salt const& salt, Verifier const& verifier, BigNumber const& k);
+ explicit BnetSRP6v2Base(ForRegistrationTag t) : BnetSRP6Base(t) { }
+
+ BigNumber const& GetN() const final { return N; }
+ BigNumber const& Getg() const final { return g; }
+
+ uint8 GetVersion() const final { return 2; }
+ uint32 GetXIterations() const final { return 15000; }
+
+ protected:
+ BigNumber CalculateX(std::string const& username, std::string const& password, Salt const& salt) const final;
+ };
+
+ template<typename CryptoHash>
+ class BnetSRP6v1 final : public BnetSRP6v1Base
+ {
+ public:
+ BnetSRP6v1(std::string const& username, Salt const& salt, Verifier const& verifier)
+ : BnetSRP6v1Base(username, salt, verifier, BigNumber(CryptoHash::GetDigestOf(N.ToByteArray<128>(false), g.ToByteArray<128>(false)), false))
+ {
+ }
+
+ explicit BnetSRP6v1(ForRegistrationTag t) : BnetSRP6v1Base(t) { }
+
+ protected:
+ BigNumber CalculateU(BigNumber const& A) const override
+ {
+ return BigNumber(CryptoHash::GetDigestOf(A.ToByteArray<128>(false), B.ToByteArray<128>(false)), false);
+ }
+
+ BigNumber DoCalculateEvidence(std::span<BigNumber const*> bns) const override
+ {
+ return BnetSRP6Base::DoCalculateEvidence<CryptoHash>(bns);
+ }
+ };
+
+ template<typename CryptoHash>
+ class BnetSRP6v2 final : public BnetSRP6v2Base
+ {
+ public:
+ BnetSRP6v2(std::string const& username, Salt const& salt, Verifier const& verifier)
+ : BnetSRP6v2Base(username, salt, verifier, BigNumber(CryptoHash::GetDigestOf(N.ToByteArray<256>(false), g.ToByteArray<256>(false)), false))
+ {
+ }
+
+ explicit BnetSRP6v2(ForRegistrationTag t) : BnetSRP6v2Base(t) { }
+
+ protected:
+ BigNumber CalculateU(BigNumber const& A) const override
+ {
+ return BigNumber(CryptoHash::GetDigestOf(A.ToByteArray<256>(false), B.ToByteArray<256>(false)), false);
+ }
+
+ BigNumber DoCalculateEvidence(std::span<BigNumber const*> bns) const override
+ {
+ return BnetSRP6Base::DoCalculateEvidence<CryptoHash>(bns);
+ }
};
}
+
+using SRP::SRP6;
}
#endif
diff --git a/src/common/Cryptography/BigNumber.cpp b/src/common/Cryptography/BigNumber.cpp
index b9f6a1c947c..f220ca319be 100644
--- a/src/common/Cryptography/BigNumber.cpp
+++ b/src/common/Cryptography/BigNumber.cpp
@@ -120,7 +120,7 @@ BigNumber& BigNumber::operator%=(BigNumber const& bn)
BN_CTX *bnctx;
bnctx = BN_CTX_new();
- BN_mod(_bn, _bn, bn._bn, bnctx);
+ BN_nnmod(_bn, _bn, bn._bn, bnctx);
BN_CTX_free(bnctx);
return *this;
@@ -166,6 +166,11 @@ int32 BigNumber::GetNumBytes() const
return BN_num_bytes(_bn);
}
+int32 BigNumber::GetNumBits() const
+{
+ return BN_num_bits(_bn);
+}
+
uint32 BigNumber::AsDword() const
{
return (uint32)BN_get_word(_bn);
diff --git a/src/common/Cryptography/BigNumber.h b/src/common/Cryptography/BigNumber.h
index d36c8a9f718..3ec6cc65590 100644
--- a/src/common/Cryptography/BigNumber.h
+++ b/src/common/Cryptography/BigNumber.h
@@ -34,6 +34,7 @@ class TC_COMMON_API BigNumber
BigNumber(uint32 v) : BigNumber() { SetDword(v); }
BigNumber(int32 v) : BigNumber() { SetDword(v); }
BigNumber(std::string const& v) : BigNumber() { SetHexStr(v); }
+ BigNumber(std::vector<uint8> const& v, bool littleEndian = true) : BigNumber() { SetBinary(v.data(), v.size(), littleEndian); }
template <size_t Size>
BigNumber(std::array<uint8, Size> const& v, bool littleEndian = true) : BigNumber() { SetBinary(v.data(), Size, littleEndian); }
@@ -113,6 +114,7 @@ class TC_COMMON_API BigNumber
BigNumber Exp(BigNumber const&) const;
int32 GetNumBytes() const;
+ int32 GetNumBits() const;
struct bignum_st* BN() { return _bn; }
struct bignum_st const* BN() const { return _bn; }
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;
};