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

@@ -419,7 +419,9 @@ DROP TABLE IF EXISTS `battlenet_accounts`;
CREATE TABLE `battlenet_accounts` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Identifier',
`email` varchar(320) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`sha_pass_hash` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`srp_version` tinyint(3) NOT NULL DEFAULT '1',
`salt` binary(32) NOT NULL,
`verifier` blob NOT NULL,
`joindate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_ip` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '127.0.0.1',
`failed_logins` int unsigned NOT NULL DEFAULT '0',
@@ -2778,7 +2780,8 @@ INSERT INTO `updates` VALUES
('2023_12_13_00_auth.sql','C3D6AA45BECD5A7F8A420FE0022AAF6A349C5E3F','RELEASED','2023-12-13 06:42:48',0),
('2023_12_19_00_auth.sql','6761E7111F613E57A7D684E18E38FCA3F5CD66A6','RELEASED','2023-12-19 08:54:36',0),
('2023_12_21_00_auth.sql','DB294EF35C00AA92C79786F7A0BFBCE739D4E193','RELEASED','2023-12-21 09:08:30',0),
('2023_12_24_00_auth.sql','F59B3A895750FD83177324B89BFCEBD8A43DD577','RELEASED','2023-12-24 06:24:58',0);
('2023_12_24_00_auth.sql','F59B3A895750FD83177324B89BFCEBD8A43DD577','RELEASED','2023-12-24 06:24:58',0),
('2023_12_26_00_auth.sql','5C8716F7F6E2792E15A42BDA8F2D855A7DE95FC5','RELEASED','2023-12-26 13:38:58',0);
/*!40000 ALTER TABLE `updates` ENABLE KEYS */;
UNLOCK TABLES;

View File

@@ -0,0 +1,4 @@
ALTER TABLE `battlenet_accounts`
ADD `srp_version` tinyint(3) NOT NULL DEFAULT '1' AFTER `email`,
ADD `salt` binary(32) AFTER `srp_version`,
ADD `verifier` blob AFTER `salt`;

View File

@@ -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;
/*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);
/*static*/ std::pair<SRP6::Salt, SRP6::Verifier> SRP6::MakeRegistrationData(std::string const& username, std::string const& password)
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))
{
std::pair<SRP6::Salt, SRP6::Verifier> res;
Crypto::GetRandomBytes(res.first); // random salt
res.second = CalculateVerifier(username, password, res.first);
return res;
}
/*static*/ SRP6::Verifier SRP6::CalculateVerifier(std::string const& username, std::string const& password, SRP6::Salt const& salt)
SRP6::SRP6(ForRegistrationTag)
: s(GetRandomBytes<SALT_LENGTH>()), _used(true)
{
}
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);
}
bool SRP6::CheckCredentials(std::string const& username, std::string const& password) const
{
return v == CalculateVerifier(username, password, s);
}
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();
}
/*static*/ SessionKey SRP6::SHA1Interleave(SRP6::EphemeralKey const& S)
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)
);
}
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)) {}
Optional<SessionKey> SRP6::VerifyChallengeResponse(EphemeralKey const& A, SHA1::Digest const& clientM)
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)
{
ASSERT(!_used, "A single SRP6 object must only ever be used to verify ONCE!");
_used = true;
BigNumber const _A(A);
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>();
SessionKey K = SHA1Interleave(S);
// 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
return {};
}
BigNumber BnetSRP6Base::CalculateServerEvidence(BigNumber const& A, BigNumber const& clientM1, BigNumber const& K) const
{
std::array evidenceBns{ &A, &clientM1, &K };
return DoCalculateEvidence(evidenceBns);
}
Optional<BigNumber> BnetSRP6Base::DoVerifyClientEvidence(BigNumber const& A, BigNumber const& clientM1)
{
BigNumber N = GetN();
if ((A % N).IsZero())
return {};
BigNumber const u = CalculateU(A);
if ((u % N).IsZero())
return {};
BigNumber const S = (A * v.ModExp(u, N)).ModExp(b, N);
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);
}
}

View File

@@ -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>;
protected:
struct ForRegistrationTag { };
static std::array<uint8, 1> const g;
static std::array<uint8, 32> const N;
public:
explicit SRP6(BigNumber const& i, Salt const& salt, Verifier const& verifier, BigNumber const& N, BigNumber const& g, BigNumber const& k);
explicit SRP6(ForRegistrationTag);
// 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));
}
SRP6(SRP6 const&) = delete;
SRP6(SRP6&&) = delete;
SRP6& operator=(SRP6 const&) = delete;
SRP6& operator=(SRP6&&) = delete;
static SHA1::Digest GetSessionVerifier(EphemeralKey const& A, SHA1::Digest const& clientM, SessionKey const& K)
{
return SHA1::GetDigestOf(A, clientM, K);
}
virtual ~SRP6() = default;
SRP6(std::string const& username, Salt const& salt, Verifier const& verifier);
Optional<SessionKey> VerifyChallengeResponse(EphemeralKey const& A, SHA1::Digest const& clientM);
virtual BigNumber const& GetN() const = 0;
virtual BigNumber const& Getg() const = 0;
private:
bool _used = false; // a single instance can only be used to verify once
Optional<BigNumber> VerifyClientEvidence(BigNumber const& A, BigNumber const& clientM1);
static Verifier CalculateVerifier(std::string const& username, std::string const& password, Salt const& salt);
static SessionKey SHA1Interleave(EphemeralKey const& S);
virtual BigNumber CalculateServerEvidence(BigNumber const& A, BigNumber const& clientM1, BigNumber const& K) const = 0;
/* 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
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) };
}
static EphemeralKey _B(BigNumber const& b, BigNumber const& v) { return ((_g.ModExp(b,_N) + (v * 3)) % N).ToByteArray<EPHEMERAL_KEY_LENGTH>(); }
bool CheckCredentials(std::string const& username, std::string const& password) const;
/* 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)
Salt const s; // s - the user's password salt, random, used to calculate v on registration
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:
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

View File

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

View File

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

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