diff options
Diffstat (limited to 'src')
30 files changed, 1536 insertions, 172 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 2ba4db69cd9..44875737957 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -63,6 +63,7 @@ target_link_libraries(common process trinity-core-interface PUBLIC + argon2 boost fmt g3dlib diff --git a/src/common/Cryptography/AES.cpp b/src/common/Cryptography/AES.cpp new file mode 100644 index 00000000000..31e02485aff --- /dev/null +++ b/src/common/Cryptography/AES.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "AES.h" +#include "Errors.h" +#include <limits> + +Trinity::Crypto::AES::AES(bool encrypting) : _ctx(EVP_CIPHER_CTX_new()), _encrypting(encrypting) +{ + EVP_CIPHER_CTX_init(_ctx); + int status = EVP_CipherInit_ex(_ctx, EVP_aes_128_gcm(), nullptr, nullptr, nullptr, _encrypting ? 1 : 0); + ASSERT(status); +} + +Trinity::Crypto::AES::~AES() +{ + EVP_CIPHER_CTX_free(_ctx); +} + +void Trinity::Crypto::AES::Init(Key const& key) +{ + int status = EVP_CipherInit_ex(_ctx, nullptr, nullptr, key.data(), nullptr, -1); + ASSERT(status); +} + +bool Trinity::Crypto::AES::Process(IV const& iv, uint8* data, std::size_t length, Tag& tag) +{ + ASSERT(length <= std::numeric_limits<int>::max()); + int len = static_cast<int>(length); + if (!EVP_CipherInit_ex(_ctx, nullptr, nullptr, nullptr, iv.data(), -1)) + return false; + + int outLen; + if (!EVP_CipherUpdate(_ctx, data, &outLen, data, len)) + return false; + + len -= outLen; + + if (!_encrypting && !EVP_CIPHER_CTX_ctrl(_ctx, EVP_CTRL_GCM_SET_TAG, sizeof(tag), tag)) + return false; + + if (!EVP_CipherFinal_ex(_ctx, data + outLen, &outLen)) + return false; + + ASSERT(len == outLen); + + if (_encrypting && !EVP_CIPHER_CTX_ctrl(_ctx, EVP_CTRL_GCM_GET_TAG, sizeof(tag), tag)) + return false; + + return true; +} diff --git a/src/common/Cryptography/AES.h b/src/common/Cryptography/AES.h new file mode 100644 index 00000000000..9306fc31980 --- /dev/null +++ b/src/common/Cryptography/AES.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef Trinity_AES_h__ +#define Trinity_AES_h__ + +#include "Define.h" +#include <array> +#include <openssl/evp.h> + +namespace Trinity +{ +namespace Crypto +{ +class TC_COMMON_API AES +{ +public: + static constexpr std::size_t IV_SIZE_BYTES = 12; + static constexpr std::size_t KEY_SIZE_BYTES = 16; + static constexpr std::size_t TAG_SIZE_BYTES = 12; + + using IV = std::array<uint8, IV_SIZE_BYTES>; + using Key = std::array<uint8, KEY_SIZE_BYTES>; + using Tag = uint8[TAG_SIZE_BYTES]; + + AES(bool encrypting); + ~AES(); + + void Init(Key const& key); + + bool Process(IV const& iv, uint8* data, std::size_t length, Tag& tag); + +private: + EVP_CIPHER_CTX* _ctx; + bool _encrypting; +}; +} +} + +#endif // Trinity_AES_h__ diff --git a/src/common/Cryptography/Argon2.cpp b/src/common/Cryptography/Argon2.cpp new file mode 100644 index 00000000000..fb849261e57 --- /dev/null +++ b/src/common/Cryptography/Argon2.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Argon2.h" +#include <argon2/argon2.h> + +/*static*/ Optional<std::string> Trinity::Crypto::Argon2::Hash(std::string const& password, BigNumber const& salt, uint32 nIterations, uint32 kibMemoryCost) +{ + char buf[ENCODED_HASH_LEN]; + int status = argon2id_hash_encoded( + nIterations, + kibMemoryCost, + PARALLELISM, + password.c_str(), password.length(), + salt.AsByteArray().get(), salt.GetNumBytes(), + HASH_LEN, buf, ENCODED_HASH_LEN + ); + + if (status == ARGON2_OK) + return std::string(buf); + + return {}; +} + +/*static*/ bool Trinity::Crypto::Argon2::Verify(std::string const& password, std::string const& hash) +{ + int status = argon2id_verify(hash.c_str(), password.c_str(), password.length()); + return (status == ARGON2_OK); +} diff --git a/src/common/Cryptography/Argon2.h b/src/common/Cryptography/Argon2.h new file mode 100644 index 00000000000..afd37794854 --- /dev/null +++ b/src/common/Cryptography/Argon2.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_ARGON2_H +#define TRINITY_ARGON2_H + +#include "BigNumber.h" +#include "Define.h" +#include "Optional.h" +#include <string> + +namespace Trinity +{ +namespace Crypto +{ +struct TC_COMMON_API Argon2 +{ + static constexpr uint32 HASH_LEN = 16; // 128 bits, in bytes + static constexpr uint32 ENCODED_HASH_LEN = 100; // in chars + static constexpr uint32 DEFAULT_ITERATIONS = 10; // determined by dice roll, guaranteed to be secure (not really) + static constexpr uint32 DEFAULT_MEMORY_COST = (1u << 17); // 2^17 kibibytes is 2^7 mebibytes is ~100MB + static constexpr uint32 PARALLELISM = 1; // we don't support threaded hashing + + static Optional<std::string> Hash(std::string const& password, BigNumber const& salt, uint32 nIterations = DEFAULT_ITERATIONS, uint32 kibMemoryCost = DEFAULT_MEMORY_COST); + static bool Verify(std::string const& password, std::string const& hash); +}; +} +} + +#endif diff --git a/src/common/Cryptography/BigNumber.cpp b/src/common/Cryptography/BigNumber.cpp index 96f9144181e..19de13572bf 100644 --- a/src/common/Cryptography/BigNumber.cpp +++ b/src/common/Cryptography/BigNumber.cpp @@ -17,6 +17,7 @@ */ #include "Cryptography/BigNumber.h" +#include "Errors.h" #include <openssl/bn.h> #include <cstring> #include <algorithm> @@ -65,9 +66,10 @@ void BigNumber::SetBinary(uint8 const* bytes, int32 len) delete[] array; } -void BigNumber::SetHexStr(char const* str) +bool BigNumber::SetHexStr(char const* str) { - BN_hex2bn(&_bn, str); + int n = BN_hex2bn(&_bn, str); + return (n > 0); } void BigNumber::SetRand(int32 numbits) @@ -84,19 +86,19 @@ BigNumber& BigNumber::operator=(BigNumber const& bn) return *this; } -BigNumber BigNumber::operator+=(BigNumber const& bn) +BigNumber& BigNumber::operator+=(BigNumber const& bn) { BN_add(_bn, _bn, bn._bn); return *this; } -BigNumber BigNumber::operator-=(BigNumber const& bn) +BigNumber& BigNumber::operator-=(BigNumber const& bn) { BN_sub(_bn, _bn, bn._bn); return *this; } -BigNumber BigNumber::operator*=(BigNumber const& bn) +BigNumber& BigNumber::operator*=(BigNumber const& bn) { BN_CTX *bnctx; @@ -107,7 +109,7 @@ BigNumber BigNumber::operator*=(BigNumber const& bn) return *this; } -BigNumber BigNumber::operator/=(BigNumber const& bn) +BigNumber& BigNumber::operator/=(BigNumber const& bn) { BN_CTX *bnctx; @@ -118,7 +120,7 @@ BigNumber BigNumber::operator/=(BigNumber const& bn) return *this; } -BigNumber BigNumber::operator%=(BigNumber const& bn) +BigNumber& BigNumber::operator%=(BigNumber const& bn) { BN_CTX *bnctx; @@ -129,7 +131,18 @@ BigNumber BigNumber::operator%=(BigNumber const& bn) return *this; } -BigNumber BigNumber::Exp(BigNumber const& bn) +BigNumber& BigNumber::operator<<=(int n) +{ + BN_lshift(_bn, _bn, n); + return *this; +} + +int BigNumber::CompareTo(BigNumber const& bn) const +{ + return BN_cmp(_bn, bn._bn); +} + +BigNumber BigNumber::Exp(BigNumber const& bn) const { BigNumber ret; BN_CTX *bnctx; @@ -141,7 +154,7 @@ BigNumber BigNumber::Exp(BigNumber const& bn) return ret; } -BigNumber BigNumber::ModExp(BigNumber const& bn1, BigNumber const& bn2) +BigNumber BigNumber::ModExp(BigNumber const& bn1, BigNumber const& bn2) const { BigNumber ret; BN_CTX *bnctx; @@ -153,12 +166,12 @@ BigNumber BigNumber::ModExp(BigNumber const& bn1, BigNumber const& bn2) return ret; } -int32 BigNumber::GetNumBytes(void) +int32 BigNumber::GetNumBytes() const { return BN_num_bytes(_bn); } -uint32 BigNumber::AsDword() +uint32 BigNumber::AsDword() const { return (uint32)BN_get_word(_bn); } @@ -173,25 +186,37 @@ bool BigNumber::IsNegative() const return BN_is_negative(_bn); } -std::unique_ptr<uint8[]> BigNumber::AsByteArray(int32 minSize, bool littleEndian) +bool BigNumber::AsByteArray(uint8* buf, std::size_t bufsize, bool littleEndian) const { - int numBytes = GetNumBytes(); - int length = (minSize >= numBytes) ? minSize : numBytes; + int nBytes = GetNumBytes(); + ASSERT(!(nBytes < 0)); + std::size_t numBytes = static_cast<std::size_t>(nBytes); - uint8* array = new uint8[length]; + // too large to store + if (bufsize < numBytes) + return false; // If we need more bytes than length of BigNumber set the rest to 0 - if (length > numBytes) - memset((void*)array, 0, length); + if (numBytes < bufsize) + memset((void*)buf, 0, bufsize); - BN_bn2bin(_bn, array + (length-numBytes)); + BN_bn2bin(_bn, buf + (bufsize - numBytes)); // openssl's BN stores data internally in big endian format, reverse if little endian desired if (littleEndian) - std::reverse(array, array + length); + std::reverse(buf, buf + bufsize); - std::unique_ptr<uint8[]> ret(array); - return ret; + return true; +} + +std::unique_ptr<uint8[]> BigNumber::AsByteArray(int32 minSize, bool littleEndian) const +{ + std::size_t length = std::max(GetNumBytes(), minSize); + uint8* array = new uint8[length]; + bool success = AsByteArray(array, length, littleEndian); + ASSERT(success); + + return std::unique_ptr<uint8[]>(array); } std::string BigNumber::AsHexStr() const diff --git a/src/common/Cryptography/BigNumber.h b/src/common/Cryptography/BigNumber.h index 0f53d1b7160..e2c70fe0dd8 100644 --- a/src/common/Cryptography/BigNumber.h +++ b/src/common/Cryptography/BigNumber.h @@ -20,6 +20,7 @@ #define _AUTH_BIGNUMBER_H #include "Define.h" +#include <array> #include <memory> #include <string> @@ -36,60 +37,81 @@ class TC_COMMON_API BigNumber void SetDword(uint32); void SetQword(uint64); void SetBinary(uint8 const* bytes, int32 len); - void SetHexStr(char const* str); + bool SetHexStr(char const* str); void SetRand(int32 numbits); BigNumber& operator=(BigNumber const& bn); - BigNumber operator+=(BigNumber const& bn); - BigNumber operator+(BigNumber const& bn) + BigNumber& operator+=(BigNumber const& bn); + BigNumber operator+(BigNumber const& bn) const { BigNumber t(*this); return t += bn; } - BigNumber operator-=(BigNumber const& bn); - BigNumber operator-(BigNumber const& bn) + BigNumber& operator-=(BigNumber const& bn); + BigNumber operator-(BigNumber const& bn) const { BigNumber t(*this); return t -= bn; } - BigNumber operator*=(BigNumber const& bn); - BigNumber operator*(BigNumber const& bn) + BigNumber& operator*=(BigNumber const& bn); + BigNumber operator*(BigNumber const& bn) const { BigNumber t(*this); return t *= bn; } - BigNumber operator/=(BigNumber const& bn); - BigNumber operator/(BigNumber const& bn) + BigNumber& operator/=(BigNumber const& bn); + BigNumber operator/(BigNumber const& bn) const { BigNumber t(*this); return t /= bn; } - BigNumber operator%=(BigNumber const& bn); - BigNumber operator%(BigNumber const& bn) + BigNumber& operator%=(BigNumber const& bn); + BigNumber operator%(BigNumber const& bn) const { BigNumber t(*this); return t %= bn; } + BigNumber& operator<<=(int n); + BigNumber operator<<(int n) const + { + BigNumber t(*this); + return t <<= n; + } + + int CompareTo(BigNumber const& bn) const; + bool operator<=(BigNumber const& bn) const { return (CompareTo(bn) <= 0); } + bool operator==(BigNumber const& bn) const { return (CompareTo(bn) == 0); } + bool operator>=(BigNumber const& bn) const { return (CompareTo(bn) >= 0); } + bool operator<(BigNumber const& bn) const { return (CompareTo(bn) < 0); } + bool operator>(BigNumber const& bn) const { return (CompareTo(bn) > 0); } + bool IsZero() const; bool IsNegative() const; - BigNumber ModExp(BigNumber const& bn1, BigNumber const& bn2); - BigNumber Exp(BigNumber const&); + BigNumber ModExp(BigNumber const& bn1, BigNumber const& bn2) const; + BigNumber Exp(BigNumber const&) const; - int32 GetNumBytes(void); + int32 GetNumBytes() const; struct bignum_st *BN() { return _bn; } - uint32 AsDword(); + uint32 AsDword() const; - std::unique_ptr<uint8[]> AsByteArray(int32 minSize = 0, bool littleEndian = true); + bool AsByteArray(uint8* buf, std::size_t bufsize, bool littleEndian = true) const; + std::unique_ptr<uint8[]> AsByteArray(int32 minSize = 0, bool littleEndian = true) const; + template <std::size_t N> std::array<uint8, N> AsByteArray(bool littleEndian = true) const + { + std::array<uint8, N> buf; + AsByteArray(buf.data(), N, littleEndian); + return buf; + } std::string AsHexStr() const; std::string AsDecStr() const; diff --git a/src/common/Cryptography/CryptoGenerics.h b/src/common/Cryptography/CryptoGenerics.h new file mode 100644 index 00000000000..4ecdb109692 --- /dev/null +++ b/src/common/Cryptography/CryptoGenerics.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_CRYPTO_GENERICS_HPP +#define TRINITY_CRYPTO_GENERICS_HPP + +#include "advstd.h" +#include "BigNumber.h" +#include "Define.h" +#include "Errors.h" +#include <vector> +#include <openssl/rand.h> + +namespace Trinity +{ + namespace Impl + { + struct CryptoGenericsImpl + { + template <typename Cipher> + static typename Cipher::IV GenerateRandomIV() + { + typename Cipher::IV iv; + int status = RAND_bytes(advstd::data(iv), advstd::size(iv)); + ASSERT(status); + return iv; + } + + template <typename C> + static void AppendToBack(std::vector<uint8>& data, C const& tail) + { + data.insert(data.end(), std::begin(tail), std::end(tail)); + } + + template <typename C> + static void SplitFromBack(std::vector<uint8>& data, C& tail) + { + ASSERT(data.size() >= advstd::size(tail)); + for (size_t i = 1, N = advstd::size(tail); i <= N; ++i) + { + tail[N - i] = data.back(); + data.pop_back(); + } + } + }; + } + + namespace Crypto + { + template <typename Cipher> + void AEEncryptWithRandomIV(std::vector<uint8>& data, typename Cipher::Key const& key) + { + using IV = typename Cipher::IV; + using Tag = typename Cipher::Tag; + // select random IV + IV iv = Trinity::Impl::CryptoGenericsImpl::GenerateRandomIV<Cipher>(); + Tag tag; + + // encrypt data + Cipher cipher(true); + cipher.Init(key); + bool success = cipher.Process(iv, data.data(), data.size(), tag); + ASSERT(success); + + // append trailing IV and tag + Trinity::Impl::CryptoGenericsImpl::AppendToBack(data, iv); + Trinity::Impl::CryptoGenericsImpl::AppendToBack(data, tag); + } + + template <typename Cipher> + void AEEncryptWithRandomIV(std::vector<uint8>& data, BigNumber const& key) + { + AEEncryptWithRandomIV<Cipher>(data, key.AsByteArray<Cipher::KEY_SIZE_BYTES>()); + } + + template <typename Cipher> + bool AEDecrypt(std::vector<uint8>& data, typename Cipher::Key const& key) + { + using IV = typename Cipher::IV; + using Tag = typename Cipher::Tag; + // extract trailing IV and tag + IV iv; + Tag tag; + Trinity::Impl::CryptoGenericsImpl::SplitFromBack(data, tag); + Trinity::Impl::CryptoGenericsImpl::SplitFromBack(data, iv); + + // decrypt data + Cipher cipher(false); + cipher.Init(key); + return cipher.Process(iv, data.data(), data.size(), tag); + } + + template <typename Cipher> + bool AEDecrypt(std::vector<uint8>& data, BigNumber const& key) + { + return AEDecrypt<Cipher>(data, key.AsByteArray<Cipher::KEY_SIZE_BYTES>()); + } + } +} + +#endif diff --git a/src/common/Cryptography/TOTP.cpp b/src/common/Cryptography/TOTP.cpp new file mode 100644 index 00000000000..1b720ab082a --- /dev/null +++ b/src/common/Cryptography/TOTP.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "TOTP.h" +#include <cstring> +#include <openssl/evp.h> +#include <openssl/hmac.h> + +using namespace Trinity::Crypto; + +static constexpr uint32 TOTP_INTERVAL = 30; +static constexpr uint32 HMAC_RESULT_SIZE = 20; +/*static*/ uint32 TOTP::GenerateToken(TOTP::Secret const& secret, time_t timestamp) +{ + timestamp /= TOTP_INTERVAL; + unsigned char challenge[8]; + for (int i = 8; i--; timestamp >>= 8) + challenge[i] = timestamp; + + unsigned char digest[HMAC_RESULT_SIZE]; + uint32 digestSize = HMAC_RESULT_SIZE; + HMAC(EVP_sha1(), secret.data(), secret.size(), challenge, 8, digest, &digestSize); + + uint32 offset = digest[19] & 0xF; + uint32 truncated = (digest[offset] << 24) | (digest[offset + 1] << 16) | (digest[offset + 2] << 8) | (digest[offset + 3]); + truncated &= 0x7FFFFFFF; + return (truncated % 1000000); +} + +/*static*/ bool TOTP::ValidateToken(TOTP::Secret const& secret, uint32 token) +{ + time_t now = time(nullptr); + return ( + (token == GenerateToken(secret, now - TOTP_INTERVAL)) || + (token == GenerateToken(secret, now)) || + (token == GenerateToken(secret, now + TOTP_INTERVAL)) + ); +} diff --git a/src/common/Cryptography/TOTP.h b/src/common/Cryptography/TOTP.h new file mode 100644 index 00000000000..d90988757c6 --- /dev/null +++ b/src/common/Cryptography/TOTP.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_TOTP_H +#define TRINITY_TOTP_H + +#include "Define.h" +#include <ctime> +#include <vector> + +namespace Trinity +{ +namespace Crypto +{ +struct TC_COMMON_API TOTP +{ + static constexpr std::size_t RECOMMENDED_SECRET_LENGTH = 20; + using Secret = std::vector<uint8>; + + static uint32 GenerateToken(Secret const& key, time_t timestamp); + static bool ValidateToken(Secret const& key, uint32 token); +}; +} +} + +#endif diff --git a/src/common/Encoding/Base32.cpp b/src/common/Encoding/Base32.cpp new file mode 100644 index 00000000000..70460b4c86c --- /dev/null +++ b/src/common/Encoding/Base32.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Base32.h" +#include "BaseEncoding.h" +#include "Errors.h" + +struct B32Impl +{ + static constexpr std::size_t BITS_PER_CHAR = 5; + + static constexpr char PADDING = '='; + static constexpr char Encode(uint8 v) + { + ASSERT(v < 0x20); + if (v < 26) return 'A'+v; + else return '2' + (v-26); + } + + static constexpr uint8 DECODE_ERROR = 0xff; + static constexpr uint8 Decode(uint8 v) + { + if (v == '0') return Decode('O'); + if (v == '1') return Decode('l'); + if (('A' <= v) && (v <= 'Z')) return (v-'A'); + if (('a' <= v) && (v <= 'z')) return (v-'a'); + if (('2' <= v) && (v <= '8')) return (v-'2')+26; + return DECODE_ERROR; + } +}; + +/*static*/ std::string Trinity::Encoding::Base32::Encode(std::vector<uint8> const& data) +{ + return Trinity::Impl::GenericBaseEncoding<B32Impl>::Encode(data); +} + +/*static*/ Optional<std::vector<uint8>> Trinity::Encoding::Base32::Decode(std::string const& data) +{ + return Trinity::Impl::GenericBaseEncoding<B32Impl>::Decode(data); +} diff --git a/src/common/Encoding/Base32.h b/src/common/Encoding/Base32.h new file mode 100644 index 00000000000..8b4dda2a9fd --- /dev/null +++ b/src/common/Encoding/Base32.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_BASE32_H +#define TRINITY_BASE32_H + +#include "Define.h" +#include "Optional.h" +#include <string> +#include <vector> + +namespace Trinity +{ +namespace Encoding +{ +struct TC_COMMON_API Base32 +{ + static std::string Encode(std::vector<uint8> const& data); + static Optional<std::vector<uint8>> Decode(std::string const& data); +}; +} +} + +#endif diff --git a/src/common/Encoding/Base64.cpp b/src/common/Encoding/Base64.cpp new file mode 100644 index 00000000000..21e02ade85d --- /dev/null +++ b/src/common/Encoding/Base64.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Base64.h" +#include "BaseEncoding.h" +#include "Errors.h" + +struct B64Impl +{ + static constexpr std::size_t BITS_PER_CHAR = 6; + + static constexpr char PADDING = '='; + static constexpr char Encode(uint8 v) + { + ASSERT(v < 0x40); + if (v < 26) return 'A' + v; + if (v < 52) return 'a' + (v - 26); + if (v < 62) return '0' + (v - 52); + if (v == 62) return '+'; + else return '/'; + } + + static constexpr uint8 DECODE_ERROR = 0xff; + static constexpr uint8 Decode(uint8 v) + { + if (('A' <= v) && (v <= 'Z')) return (v - 'A'); + if (('a' <= v) && (v <= 'z')) return (v - 'a') + 26; + if (('0' <= v) && (v <= '9')) return (v - '0') + 52; + if (v == '+') return 62; + if (v == '/') return 63; + return DECODE_ERROR; + } +}; + +/*static*/ std::string Trinity::Encoding::Base64::Encode(std::vector<uint8> const& data) +{ + return Trinity::Impl::GenericBaseEncoding<B64Impl>::Encode(data); +} + +/*static*/ Optional<std::vector<uint8>> Trinity::Encoding::Base64::Decode(std::string const& data) +{ + return Trinity::Impl::GenericBaseEncoding<B64Impl>::Decode(data); +} diff --git a/src/common/Encoding/Base64.h b/src/common/Encoding/Base64.h new file mode 100644 index 00000000000..d421118711d --- /dev/null +++ b/src/common/Encoding/Base64.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_BASE64_H +#define TRINITY_BASE64_H + +#include "Define.h" +#include "Optional.h" +#include <string> +#include <vector> + +namespace Trinity +{ +namespace Encoding +{ +struct TC_COMMON_API Base64 +{ + static std::string Encode(std::vector<uint8> const& data); + static Optional<std::vector<uint8>> Decode(std::string const& data); +}; +} +} + +#endif diff --git a/src/common/Encoding/BaseEncoding.h b/src/common/Encoding/BaseEncoding.h new file mode 100644 index 00000000000..d8b1453f271 --- /dev/null +++ b/src/common/Encoding/BaseEncoding.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_BASE_ENCODING_HPP +#define TRINITY_BASE_ENCODING_HPP + +#include "advstd.h" +#include "Define.h" +#include "Optional.h" +#include <string> +#include <vector> + +namespace Trinity +{ +namespace Impl +{ +template <typename Encoding> +struct GenericBaseEncoding +{ + static constexpr std::size_t BITS_PER_CHAR = Encoding::BITS_PER_CHAR; + static constexpr std::size_t PAD_TO = advstd::lcm(8u, BITS_PER_CHAR); + + static_assert(BITS_PER_CHAR < 8, "Encoding parameters are invalid"); + + static constexpr uint8 DECODE_ERROR = Encoding::DECODE_ERROR; + static constexpr char PADDING = Encoding::PADDING; + + static constexpr std::size_t EncodedSize(std::size_t size) + { + size *= 8; // bits in input + if (size % PAD_TO) // pad to boundary + size += (PAD_TO - (size % PAD_TO)); + return (size / BITS_PER_CHAR); + } + + static constexpr std::size_t DecodedSize(std::size_t size) + { + size *= BITS_PER_CHAR; // bits in input + if (size % PAD_TO) // pad to boundary + size += (PAD_TO - (size % PAD_TO)); + return (size / 8); + } + + static std::string Encode(std::vector<uint8> const& data) + { + auto it = data.begin(), end = data.end(); + if (it == end) + return ""; + + std::string s; + s.reserve(EncodedSize(data.size())); + + uint8 bitsLeft = 8; // in current byte + do + { + uint8 thisC = 0; + if (bitsLeft >= BITS_PER_CHAR) + { + bitsLeft -= BITS_PER_CHAR; + thisC = ((*it >> bitsLeft) & ((1 << BITS_PER_CHAR)-1)); + if (!bitsLeft) + { + ++it; + bitsLeft = 8; + } + } + else + { + thisC = (*it & ((1 << bitsLeft) - 1)) << (BITS_PER_CHAR - bitsLeft); + bitsLeft += (8 - BITS_PER_CHAR); + if ((++it) != end) + thisC |= (*it >> bitsLeft); + } + s.append(1, Encoding::Encode(thisC)); + } while (it != end); + + while (bitsLeft != 8) + { + if (bitsLeft > BITS_PER_CHAR) + bitsLeft -= BITS_PER_CHAR; + else + bitsLeft += (8 - BITS_PER_CHAR); + s.append(1, PADDING); + } + + return s; + } + + static Optional<std::vector<uint8>> Decode(std::string const& data) + { + auto it = data.begin(), end = data.end(); + if (it == end) + return std::vector<uint8>(); + + std::vector<uint8> v; + v.reserve(DecodedSize(data.size())); + + uint8 currentByte = 0; + uint8 bitsLeft = 8; // in current byte + while ((it != end) && (*it != PADDING)) + { + uint8 cur = Encoding::Decode(*(it++)); + if (cur == DECODE_ERROR) + return {}; + + if (bitsLeft > BITS_PER_CHAR) + { + bitsLeft -= BITS_PER_CHAR; + currentByte |= (cur << bitsLeft); + } + else + { + bitsLeft = BITS_PER_CHAR - bitsLeft; // in encoded char + currentByte |= (cur >> bitsLeft); + v.push_back(currentByte); + currentByte = (cur & ((1 << bitsLeft) - 1)); + bitsLeft = 8 - bitsLeft; // in byte again + currentByte <<= bitsLeft; + } + } + + if (currentByte) + return {}; // decode error, trailing non-zero bits + + // process padding + while ((it != end) && (*it == PADDING) && (bitsLeft != 8)) + { + if (bitsLeft > BITS_PER_CHAR) + bitsLeft -= BITS_PER_CHAR; + else + bitsLeft += (8 - BITS_PER_CHAR); + ++it; + } + + // ok, all padding should be consumed, and we should be at end of string + if (it == end) + return v; + + // anything else is an error + return {}; + } +}; +} +} + +#endif diff --git a/src/common/Utilities/advstd.h b/src/common/Utilities/advstd.h index 5606b84cfa2..86edcd4445d 100644 --- a/src/common/Utilities/advstd.h +++ b/src/common/Utilities/advstd.h @@ -86,12 +86,46 @@ namespace advstd #undef forward_2v // C++17 std::size - template <class C> - constexpr auto size(const C& c) -> decltype(c.size()) { return c.size(); } + template <typename C> + constexpr auto size(const C& c) { return c.size(); } - template <class T, std::size_t N> + template <typename T, std::size_t N> constexpr std::size_t size(const T(&)[N]) noexcept { return N; } + // C++17 std::data + template <typename C> + constexpr auto data(C& c) { return c.data(); } + + template <typename C> + constexpr auto data(C const& c) { return c.data(); } + + template <typename T, std::size_t N> + constexpr T* data(T(&a)[N]) noexcept { return a; } + + template <typename T, std::size_t N> + constexpr T const* data(const T(&a)[N]) noexcept { return a; } + + template <typename T> + constexpr T const* data(std::initializer_list<T> l) noexcept { return l.begin(); } + + // C++17 std::gcd + template <typename T1, typename T2> + constexpr std::enable_if_t<advstd::is_unsigned_v<T1> && advstd::is_unsigned_v<T2>, std::common_type_t<T1, T2>> gcd(T1 m, T2 n) + { + if (m < n) + return gcd(n, m); + if (!n) + return m; + return gcd(n, m%n); + } + + // C++17 std::lcm + template <typename T1, typename T2> + constexpr std::enable_if_t<advstd::is_unsigned_v<T1> && advstd::is_unsigned_v<T2>, std::common_type_t<T1, T2>> lcm(T1 m, T2 n) + { + return (m/gcd(m, n))*n; + } + // C++20 std::remove_cvref_t template <class T> using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>; diff --git a/src/server/authserver/Authentication/TOTP.cpp b/src/server/authserver/Authentication/TOTP.cpp deleted file mode 100644 index 3f7062d6b7f..00000000000 --- a/src/server/authserver/Authentication/TOTP.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "TOTP.h" -#include <cstring> - -int base32_decode(char const* encoded, char* result, int bufSize) -{ - // Base32 implementation - // Copyright 2010 Google Inc. - // Author: Markus Gutschke - // Licensed under the Apache License, Version 2.0 - int buffer = 0; - int bitsLeft = 0; - int count = 0; - for (const char *ptr = encoded; count < bufSize && *ptr; ++ptr) - { - char ch = *ptr; - if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') - continue; - buffer <<= 5; - - // Deal with commonly mistyped characters - if (ch == '0') - ch = 'O'; - else if (ch == '1') - ch = 'L'; - else if (ch == '8') - ch = 'B'; - - // Look up one base32 digit - if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) - ch = (ch & 0x1F) - 1; - else if (ch >= '2' && ch <= '7') - ch -= '2' - 26; - else - return -1; - - buffer |= ch; - bitsLeft += 5; - if (bitsLeft >= 8) - { - result[count++] = buffer >> (bitsLeft - 8); - bitsLeft -= 8; - } - } - - if (count < bufSize) - result[count] = '\000'; - return count; -} - -#define HMAC_RES_SIZE 20 - -namespace TOTP -{ - unsigned int GenerateToken(char const* b32key) - { - size_t keySize = strlen(b32key); - int bufsize = (keySize + 7)/8*5; - char* encoded = new char[bufsize]; - memset(encoded, 0, bufsize); - unsigned int hmacResSize = HMAC_RES_SIZE; - unsigned char hmacRes[HMAC_RES_SIZE]; - unsigned long timestamp = time(nullptr)/30; - unsigned char challenge[8]; - - for (int i = 8; i--;timestamp >>= 8) - challenge[i] = timestamp; - - base32_decode(b32key, encoded, bufsize); - HMAC(EVP_sha1(), encoded, bufsize, challenge, 8, hmacRes, &hmacResSize); - unsigned int offset = hmacRes[19] & 0xF; - unsigned int truncHash = (hmacRes[offset] << 24) | (hmacRes[offset+1] << 16 )| (hmacRes[offset+2] << 8) | (hmacRes[offset+3]); - truncHash &= 0x7FFFFFFF; - - delete[] encoded; - - return truncHash % 1000000; - } -} diff --git a/src/server/authserver/Main.cpp b/src/server/authserver/Main.cpp index 1b58aa35155..fe9ff9f9b3f 100644 --- a/src/server/authserver/Main.cpp +++ b/src/server/authserver/Main.cpp @@ -37,6 +37,8 @@ #include "MySQLThreading.h" #include "ProcessPriority.h" #include "RealmList.h" +#include "SecretMgr.h" +#include "SharedDefines.h" #include "Util.h" #include <boost/asio/signal_set.hpp> #include <boost/program_options.hpp> @@ -79,6 +81,7 @@ variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, s int main(int argc, char** argv) { + Trinity::Impl::CurrentServerProcessHolder::_type = SERVER_PROCESS_AUTHSERVER; signal(SIGABRT, &Trinity::AbortHandler); auto configFile = fs::absolute(_TRINITY_REALM_CONFIG); @@ -139,6 +142,8 @@ int main(int argc, char** argv) if (!StartDB()) return 1; + sSecretMgr->Initialize(); + // Load IP Location Database sIPLocation->Load(); diff --git a/src/server/authserver/Server/AuthSession.cpp b/src/server/authserver/Server/AuthSession.cpp index 78aa977ab92..5c0628334fd 100644 --- a/src/server/authserver/Server/AuthSession.cpp +++ b/src/server/authserver/Server/AuthSession.cpp @@ -17,13 +17,16 @@ */ #include "AuthSession.h" +#include "AES.h" #include "AuthCodes.h" #include "Config.h" +#include "CryptoGenerics.h" +#include "DatabaseEnv.h" #include "Errors.h" #include "IPLocation.h" #include "Log.h" -#include "DatabaseEnv.h" #include "RealmList.h" +#include "SecretMgr.h" #include "SHA1.h" #include "TOTP.h" #include "Util.h" @@ -139,7 +142,7 @@ void AccountInfo::LoadResult(Field* fields) // 0 1 2 3 4 5 6 //SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, // 7 8 9 10 11 12 - // ab.unbandate = ab.bandate, aa.gmlevel, a.token_key, a.sha_pass_hash, a.v, a.s + // ab.unbandate = ab.bandate, aa.gmlevel, a.totp_secret, a.sha_pass_hash, a.v, a.s //FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ? Id = fields[0].GetUInt32(); @@ -380,6 +383,25 @@ void AuthSession::LogonChallengeCallback(PreparedQueryResult result) } } + uint8 securityFlags = 0; + // Check if a TOTP token is needed + if (!fields[9].IsNull()) + { + securityFlags = 4; + _totpSecret = fields[9].GetBinary(); + if (auto const& secret = sSecretMgr->GetSecret(SECRET_TOTP_MASTER_KEY)) + { + bool success = Trinity::Crypto::AEDecrypt<Trinity::Crypto::AES>(*_totpSecret, *secret); + if (!success) + { + pkt << uint8(WOW_FAIL_DB_BUSY); + TC_LOG_ERROR("server.authserver", "[AuthChallenge] Account '%s' has invalid ciphertext for TOTP token key stored", _accountInfo.Login.c_str()); + SendPacket(pkt); + return; + } + } + } + // Get the password from the account table, upper it, and make the SRP6 calculation std::string rI = fields[10].GetString(); @@ -421,13 +443,6 @@ void AuthSession::LogonChallengeCallback(PreparedQueryResult result) pkt.append(N.AsByteArray(32).get(), 32); pkt.append(s.AsByteArray(int32(BufferSizes::SRP_6_S)).get(), size_t(BufferSizes::SRP_6_S)); // 32 bytes pkt.append(VersionChallenge.data(), VersionChallenge.size()); - uint8 securityFlags = 0; - - // Check if token is used - _tokenKey = fields[9].GetString(); - if (!_tokenKey.empty()) - securityFlags = 4; - pkt << uint8(securityFlags); // security flags (0x0...0x04) if (securityFlags & 0x01) // PIN input @@ -548,23 +563,29 @@ bool AuthSession::HandleLogonProof() if (!memcmp(M.AsByteArray(sha.GetLength()).get(), logonProof->M1, 20)) { // Check auth token - if ((logonProof->securityFlags & 0x04) || !_tokenKey.empty()) + bool tokenSuccess = false; + bool sentToken = (logonProof->securityFlags & 0x04); + if (sentToken && _totpSecret) { uint8 size = *(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C)); std::string token(reinterpret_cast<char*>(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C) + sizeof(size)), size); GetReadBuffer().ReadCompleted(sizeof(size) + size); - uint32 validToken = TOTP::GenerateToken(_tokenKey.c_str()); - _tokenKey.clear(); + uint32 incomingToken = atoi(token.c_str()); - if (validToken != incomingToken) - { - ByteBuffer packet; - packet << uint8(AUTH_LOGON_PROOF); - packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); - packet << uint16(0); // LoginFlags, 1 has account message - SendPacket(packet); - return true; - } + tokenSuccess = Trinity::Crypto::TOTP::ValidateToken(*_totpSecret, incomingToken); + memset(_totpSecret->data(), 0, _totpSecret->size()); + } + else if (!sentToken && !_totpSecret) + tokenSuccess = true; + + if (!tokenSuccess) + { + ByteBuffer packet; + packet << uint8(AUTH_LOGON_PROOF); + packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); + packet << uint16(0); // LoginFlags, 1 has account message + SendPacket(packet); + return true; } if (!VerifyVersion(logonProof->A, sizeof(logonProof->A), logonProof->crc_hash, false)) diff --git a/src/server/authserver/Server/AuthSession.h b/src/server/authserver/Server/AuthSession.h index 9c603616225..cac1450b401 100644 --- a/src/server/authserver/Server/AuthSession.h +++ b/src/server/authserver/Server/AuthSession.h @@ -19,10 +19,11 @@ #ifndef __AUTHSESSION_H__ #define __AUTHSESSION_H__ -#include "Common.h" +#include "BigNumber.h" #include "ByteBuffer.h" +#include "Common.h" +#include "Optional.h" #include "Socket.h" -#include "BigNumber.h" #include "QueryResult.h" #include "QueryCallbackProcessor.h" #include <memory> @@ -56,7 +57,6 @@ struct AccountInfo bool IsBanned = false; bool IsPermanenetlyBanned = false; AccountTypes SecurityLevel = SEC_PLAYER; - std::string TokenKey; }; class AuthSession : public Socket<AuthSession> @@ -99,7 +99,7 @@ private: AuthStatus _status; AccountInfo _accountInfo; - std::string _tokenKey; + Optional<std::vector<uint8>> _totpSecret; std::string _localizationName; std::string _os; std::string _ipCountry; diff --git a/src/server/authserver/authserver.conf.dist b/src/server/authserver/authserver.conf.dist index f3aa6f6e67e..480a49eca8e 100644 --- a/src/server/authserver/authserver.conf.dist +++ b/src/server/authserver/authserver.conf.dist @@ -9,6 +9,7 @@ # EXAMPLE CONFIG # AUTH SERVER SETTINGS # MYSQL SETTINGS +# CRYPTOGRAPHY # UPDATE SETTINGS # LOGGING SYSTEM SETTINGS # @@ -213,6 +214,24 @@ LoginDatabase.SynchThreads = 1 ################################################################################################### ################################################################################################### +# CRYPTOGRAPHY +# +# TOTPMasterSecret +# Description: The master key used to encrypt TOTP secrets for database storage. +# If you want to change this, uncomment TOTPOldMasterSecret, then copy +# your old secret there and startup authserver once. Afterwards, you can re- +# comment that line and get rid of your old secret. +# +# Default: <blank> - (Store TOTP secrets unencrypted) +# Example: 000102030405060708090A0B0C0D0E0F + +TOTPMasterSecret = +# TOTPOldMasterSecret = + +# +################################################################################################### + +################################################################################################### # UPDATE SETTINGS # # Updates.EnableDatabases diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index cc3f7d4f17f..c098a3ceb80 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -38,7 +38,7 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_UPD_VS, "UPDATE account SET v = ?, s = ? WHERE username = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_LOGONPROOF, "UPDATE account SET sessionkey = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_LOGONCHALLENGE, "SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, " - "ab.unbandate = ab.bandate, aa.gmlevel, a.token_key, a.sha_pass_hash, a.v, a.s " + "ab.unbandate = ab.bandate, aa.gmlevel, a.totp_secret, a.sha_pass_hash, a.v, a.s " "FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_RECONNECTCHALLENGE, "SELECT a.id, UPPER(a.username), a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, " "ab.unbandate = ab.bandate, aa.gmlevel, a.sessionKey " @@ -116,6 +116,13 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_INS_ACCOUNT_MUTE, "INSERT INTO account_muted VALUES (?, UNIX_TIMESTAMP(), ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_ACCOUNT_MUTE_INFO, "SELECT mutedate, mutetime, mutereason, mutedby FROM account_muted WHERE guid = ? ORDER BY mutedate ASC", CONNECTION_SYNCH); PrepareStatement(LOGIN_DEL_ACCOUNT_MUTED, "DELETE FROM account_muted WHERE guid = ?", CONNECTION_ASYNC); + + PrepareStatement(LOGIN_SEL_SECRET_DIGEST, "SELECT digest FROM secret_digest WHERE id = ?", CONNECTION_SYNCH); + PrepareStatement(LOGIN_INS_SECRET_DIGEST, "INSERT INTO secret_digest (id, digest) VALUES (?,?)", CONNECTION_ASYNC); + PrepareStatement(LOGIN_DEL_SECRET_DIGEST, "DELETE FROM secret_digest WHERE id = ?", CONNECTION_ASYNC); + + PrepareStatement(LOGIN_SEL_ACCOUNT_TOTP_SECRET, "SELECT totp_secret FROM account WHERE id = ?", CONNECTION_SYNCH); + PrepareStatement(LOGIN_UPD_ACCOUNT_TOTP_SECRET, "UPDATE account SET totp_secret = ? WHERE id = ?", CONNECTION_ASYNC); } LoginDatabaseConnection::LoginDatabaseConnection(MySQLConnectionInfo& connInfo) : MySQLConnection(connInfo) diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h index 63b510ef29c..bcc985f5eb0 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.h +++ b/src/server/database/Database/Implementation/LoginDatabase.h @@ -112,6 +112,14 @@ enum LoginDatabaseStatements : uint32 LOGIN_INS_ACCOUNT_MUTE, LOGIN_SEL_ACCOUNT_MUTE_INFO, LOGIN_DEL_ACCOUNT_MUTED, + + LOGIN_SEL_SECRET_DIGEST, + LOGIN_INS_SECRET_DIGEST, + LOGIN_DEL_SECRET_DIGEST, + + LOGIN_SEL_ACCOUNT_TOTP_SECRET, + LOGIN_UPD_ACCOUNT_TOTP_SECRET, + MAX_LOGINDATABASE_STATEMENTS }; diff --git a/src/server/scripts/Commands/cs_account.cpp b/src/server/scripts/Commands/cs_account.cpp index a70a0f376ce..0917cd2a021 100644 --- a/src/server/scripts/Commands/cs_account.cpp +++ b/src/server/scripts/Commands/cs_account.cpp @@ -23,7 +23,10 @@ Category: commandscripts EndScriptData */ #include "AccountMgr.h" +#include "AES.h" +#include "Base32.h" #include "Chat.h" +#include "CryptoGenerics.h" #include "DatabaseEnv.h" #include "IpAddress.h" #include "IPLocation.h" @@ -31,8 +34,12 @@ EndScriptData */ #include "Log.h" #include "Player.h" #include "ScriptMgr.h" +#include "SecretMgr.h" +#include "TOTP.h" #include "World.h" #include "WorldSession.h" +#include <unordered_map> +#include <openssl/rand.h> using namespace Trinity::ChatCommands; @@ -54,6 +61,12 @@ public: { "sec", rbac::RBAC_PERM_COMMAND_ACCOUNT_SET_SEC, true, nullptr, "", accountSetSecTable }, { "gmlevel", rbac::RBAC_PERM_COMMAND_ACCOUNT_SET_GMLEVEL, true, &HandleAccountSetGmLevelCommand, "" }, { "password", rbac::RBAC_PERM_COMMAND_ACCOUNT_SET_PASSWORD, true, &HandleAccountSetPasswordCommand, "" }, + { "2fa", rbac::RBAC_PERM_COMMAND_ACCOUNT_SET_2FA, true, &HandleAccountSet2FACommand, "" }, + }; + static std::vector<ChatCommand> account2FACommandTable = + { + { "setup", rbac::RBAC_PERM_COMMAND_ACCOUNT_2FA_SETUP, false, &HandleAccount2FASetupCommand, "" }, + { "remove", rbac::RBAC_PERM_COMMAND_ACCOUNT_2FA_REMOVE, false, &HandleAccount2FARemoveCommand, "" }, }; static std::vector<ChatCommand> accountLockCommandTable = { @@ -62,6 +75,7 @@ public: }; static std::vector<ChatCommand> accountCommandTable = { + { "2fa", rbac::RBAC_PERM_COMMAND_ACCOUNT_2FA, false, nullptr, "", account2FACommandTable }, { "addon", rbac::RBAC_PERM_COMMAND_ACCOUNT_ADDON, false, &HandleAccountAddonCommand, "" }, { "create", rbac::RBAC_PERM_COMMAND_ACCOUNT_CREATE, true, &HandleAccountCreateCommand, "" }, { "delete", rbac::RBAC_PERM_COMMAND_ACCOUNT_DELETE, true, &HandleAccountDeleteCommand, "" }, @@ -79,6 +93,138 @@ public: return commandTable; } + static bool HandleAccount2FASetupCommand(ChatHandler* handler, Optional<uint32> token) + { + auto const& masterKey = sSecretMgr->GetSecret(SECRET_TOTP_MASTER_KEY); + if (!masterKey.IsAvailable()) + { + handler->SendSysMessage(LANG_2FA_COMMANDS_NOT_SETUP); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 const accountId = handler->GetSession()->GetAccountId(); + + { // check if 2FA already enabled + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_TOTP_SECRET); + stmt->setUInt32(0, accountId); + PreparedQueryResult result = LoginDatabase.Query(stmt); + + if (!result) + { + TC_LOG_ERROR("misc", "Account %u not found in login database when processing .account 2fa setup command.", accountId); + handler->SendSysMessage(LANG_UNKNOWN_ERROR); + handler->SetSentErrorMessage(true); + return false; + } + + if (!result->Fetch()->IsNull()) + { + handler->SendSysMessage(LANG_2FA_ALREADY_SETUP); + handler->SetSentErrorMessage(true); + return false; + } + } + + // store random suggested secrets + static std::unordered_map<uint32, Trinity::Crypto::TOTP::Secret> suggestions; + auto pair = suggestions.emplace(std::piecewise_construct, std::make_tuple(accountId), std::make_tuple(Trinity::Crypto::TOTP::RECOMMENDED_SECRET_LENGTH)); // std::vector 1-argument size_t constructor invokes resize + if (pair.second) // no suggestion yet, generate random secret + RAND_bytes(pair.first->second.data(), pair.first->second.size()); + + if (!pair.second && token) // suggestion already existed and token specified - validate + { + if (Trinity::Crypto::TOTP::ValidateToken(pair.first->second, *token)) + { + if (masterKey) + Trinity::Crypto::AEEncryptWithRandomIV<Trinity::Crypto::AES>(pair.first->second, *masterKey); + + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_TOTP_SECRET); + stmt->setBinary(0, pair.first->second); + stmt->setUInt32(1, accountId); + LoginDatabase.Execute(stmt); + suggestions.erase(pair.first); + handler->SendSysMessage(LANG_2FA_SETUP_COMPLETE); + return true; + } + else + handler->SendSysMessage(LANG_2FA_INVALID_TOKEN); + } + + // new suggestion, or no token specified, output TOTP parameters + handler->PSendSysMessage(LANG_2FA_SECRET_SUGGESTION, Trinity::Encoding::Base32::Encode(pair.first->second)); + handler->SetSentErrorMessage(true); + return false; + } + + static bool HandleAccount2FARemoveCommand(ChatHandler* handler, Optional<uint32> token) + { + auto const& masterKey = sSecretMgr->GetSecret(SECRET_TOTP_MASTER_KEY); + if (!masterKey.IsAvailable()) + { + handler->SendSysMessage(LANG_2FA_COMMANDS_NOT_SETUP); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 const accountId = handler->GetSession()->GetAccountId(); + Trinity::Crypto::TOTP::Secret secret; + { // get current TOTP secret + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_TOTP_SECRET); + stmt->setUInt32(0, accountId); + PreparedQueryResult result = LoginDatabase.Query(stmt); + + if (!result) + { + TC_LOG_ERROR("misc", "Account %u not found in login database when processing .account 2fa setup command.", accountId); + handler->SendSysMessage(LANG_UNKNOWN_ERROR); + handler->SetSentErrorMessage(true); + return false; + } + + Field* field = result->Fetch(); + if (field->IsNull()) + { // 2FA not enabled + handler->SendSysMessage(LANG_2FA_NOT_SETUP); + handler->SetSentErrorMessage(true); + return false; + } + + secret = field->GetBinary(); + } + + if (token) + { + if (masterKey) + { + bool success = Trinity::Crypto::AEDecrypt<Trinity::Crypto::AES>(secret, *masterKey); + if (!success) + { + TC_LOG_ERROR("misc", "Account %u has invalid ciphertext in TOTP token.", accountId); + handler->SendSysMessage(LANG_UNKNOWN_ERROR); + handler->SetSentErrorMessage(true); + return false; + } + } + + if (Trinity::Crypto::TOTP::ValidateToken(secret, *token)) + { + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_TOTP_SECRET); + stmt->setNull(0); + stmt->setUInt32(1, accountId); + LoginDatabase.Execute(stmt); + handler->SendSysMessage(LANG_2FA_REMOVE_COMPLETE); + return true; + } + else + handler->SendSysMessage(LANG_2FA_INVALID_TOKEN); + } + + handler->SendSysMessage(LANG_2FA_REMOVE_NEED_TOKEN); + handler->SetSentErrorMessage(true); + return false; + } + static bool HandleAccountAddonCommand(ChatHandler* handler, uint8 expansion) { if (expansion > sWorld->getIntConfig(CONFIG_EXPANSION)) @@ -659,6 +805,69 @@ public: return true; } + static bool HandleAccountSet2FACommand(ChatHandler* handler, std::string accountName, std::string secret) + { + if (!Utf8ToUpperOnlyLatin(accountName)) + { + handler->PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, accountName.c_str()); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 targetAccountId = AccountMgr::GetId(accountName); + if (!targetAccountId) + { + handler->PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, accountName.c_str()); + handler->SetSentErrorMessage(true); + return false; + } + + if (handler->HasLowerSecurityAccount(nullptr, targetAccountId, true)) + return false; + + if (secret == "off") + { + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_TOTP_SECRET); + stmt->setNull(0); + stmt->setUInt32(1, targetAccountId); + LoginDatabase.Execute(stmt); + handler->PSendSysMessage(LANG_2FA_REMOVE_COMPLETE); + return true; + } + + auto const& masterKey = sSecretMgr->GetSecret(SECRET_TOTP_MASTER_KEY); + if (!masterKey.IsAvailable()) + { + handler->SendSysMessage(LANG_2FA_COMMANDS_NOT_SETUP); + handler->SetSentErrorMessage(true); + return false; + } + + Optional<std::vector<uint8>> decoded = Trinity::Encoding::Base32::Decode(secret); + if (!decoded) + { + handler->SendSysMessage(LANG_2FA_SECRET_INVALID); + handler->SetSentErrorMessage(true); + return false; + } + if (128 < (decoded->size() + Trinity::Crypto::AES::IV_SIZE_BYTES + Trinity::Crypto::AES::TAG_SIZE_BYTES)) + { + handler->SendSysMessage(LANG_2FA_SECRET_TOO_LONG); + handler->SetSentErrorMessage(true); + return false; + } + + if (masterKey) + Trinity::Crypto::AEEncryptWithRandomIV<Trinity::Crypto::AES>(*decoded, *masterKey); + + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_TOTP_SECRET); + stmt->setBinary(0, *decoded); + stmt->setUInt32(1, targetAccountId); + LoginDatabase.Execute(stmt); + handler->PSendSysMessage(LANG_2FA_SECRET_SET_COMPLETE, accountName.c_str()); + return true; + } + /// Set normal email for account static bool HandleAccountSetEmailCommand(ChatHandler* handler, std::string accountName, std::string const& email, std::string const& confirmEmail) { diff --git a/src/server/shared/Secrets/SecretMgr.cpp b/src/server/shared/Secrets/SecretMgr.cpp new file mode 100644 index 00000000000..2f476005f8a --- /dev/null +++ b/src/server/shared/Secrets/SecretMgr.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "SecretMgr.h" +#include "AES.h" +#include "Argon2.h" +#include "Config.h" +#include "CryptoGenerics.h" +#include "DatabaseEnv.h" +#include "Errors.h" +#include "Log.h" +#include "SharedDefines.h" +#include <functional> +#include <unordered_map> + +#define SECRET_FLAG_FOR(key, val, server) server ## _ ## key = (val ## ull << (16*SERVER_PROCESS_ ## server)) +#define SECRET_FLAG(key, val) SECRET_FLAG_ ## key = val, SECRET_FLAG_FOR(key, val, AUTHSERVER), SECRET_FLAG_FOR(key, val, WORLDSERVER) +enum SecretFlags : uint64 +{ + SECRET_FLAG(DEFER_LOAD, 0x1) +}; +#undef SECRET_FLAG_FOR +#undef SECRET_FLAG + +struct SecretInfo +{ + char const* configKey; + char const* oldKey; + int bits; + ServerProcessTypes owner; + uint64 _flags; + uint16 flags() const { return static_cast<uint16>(_flags >> (16*THIS_SERVER_PROCESS)); } +}; + +static constexpr SecretInfo secret_info[NUM_SECRETS] = +{ + { "TOTPMasterSecret", "TOTPOldMasterSecret", 128, SERVER_PROCESS_AUTHSERVER, WORLDSERVER_DEFER_LOAD } +}; + +/*static*/ SecretMgr* SecretMgr::instance() +{ + static SecretMgr instance; + return &instance; +} + +static Optional<BigNumber> GetHexFromConfig(char const* configKey, int bits) +{ + ASSERT(bits > 0); + std::string str = sConfigMgr->GetStringDefault(configKey, ""); + if (str.empty()) + return {}; + + BigNumber secret; + if (!secret.SetHexStr(str.c_str())) + { + TC_LOG_FATAL("server.loading", "Invalid value for '%s' - specify a hexadecimal integer of up to %d bits with no prefix.", configKey, bits); + ABORT(); + } + + BigNumber threshold(2); + threshold <<= bits; + if (!((BigNumber(0) <= secret) && (secret < threshold))) + { + TC_LOG_ERROR("server.loading", "Value for '%s' is out of bounds (should be an integer of up to %d bits with no prefix). Truncated to %d bits.", configKey, bits, bits); + secret %= threshold; + } + ASSERT(((BigNumber(0) <= secret) && (secret < threshold))); + + return secret; +} + +void SecretMgr::Initialize() +{ + for (uint32 i = 0; i < NUM_SECRETS; ++i) + { + if (secret_info[i].flags() & SECRET_FLAG_DEFER_LOAD) + continue; + std::unique_lock<std::mutex> lock(_secrets[i].lock); + AttemptLoad(Secrets(i), LOG_LEVEL_FATAL, lock); + if (!_secrets[i].IsAvailable()) + ABORT(); // load failed + } +} + +SecretMgr::Secret const& SecretMgr::GetSecret(Secrets i) +{ + std::unique_lock<std::mutex> lock(_secrets[i].lock); + + if (_secrets[i].state == Secret::NOT_LOADED_YET) + AttemptLoad(i, LOG_LEVEL_ERROR, lock); + return _secrets[i]; +} + +void SecretMgr::AttemptLoad(Secrets i, LogLevel errorLevel, std::unique_lock<std::mutex> const&) +{ + auto const& info = secret_info[i]; + Optional<std::string> oldDigest; + { + PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_SECRET_DIGEST); + stmt->setUInt32(0, i); + PreparedQueryResult result = LoginDatabase.Query(stmt); + if (result) + oldDigest = result->Fetch()->GetString(); + } + Optional<BigNumber> currentValue = GetHexFromConfig(info.configKey, info.bits); + + // verify digest + if ( + ((!oldDigest) != (!currentValue)) || // there is an old digest, but no current secret (or vice versa) + (oldDigest && !Trinity::Crypto::Argon2::Verify(currentValue->AsHexStr(), *oldDigest)) // there is an old digest, and the current secret does not match it + ) + { + if (info.owner != THIS_SERVER_PROCESS) + { + if (currentValue) + TC_LOG_MESSAGE_BODY("server.loading", errorLevel, "Invalid value for '%s' specified - this is not actually the secret being used in your auth DB.", info.configKey); + else + TC_LOG_MESSAGE_BODY("server.loading", errorLevel, "No value for '%s' specified - please specify the secret currently being used in your auth DB.", info.configKey); + _secrets[i].state = Secret::LOAD_FAILED; + return; + } + + Optional<BigNumber> oldSecret; + if (oldDigest && info.oldKey) // there is an old digest, so there might be an old secret (if possible) + { + oldSecret = GetHexFromConfig(info.oldKey, info.bits); + if (oldSecret && !Trinity::Crypto::Argon2::Verify(oldSecret->AsHexStr(), *oldDigest)) + { + TC_LOG_MESSAGE_BODY("server.loading", errorLevel, "Invalid value for '%s' specified - this is not actually the secret previously used in your auth DB.", info.oldKey); + _secrets[i].state = Secret::LOAD_FAILED; + return; + } + } + + // attempt to transition us to the new key, if possible + Optional<std::string> error = AttemptTransition(Secrets(i), currentValue, oldSecret, !!oldDigest); + if (error) + { + TC_LOG_MESSAGE_BODY("server.loading", errorLevel, "Your value of '%s' changed, but we cannot transition your database to the new value:\n%s", info.configKey, error->c_str()); + _secrets[i].state = Secret::LOAD_FAILED; + return; + } + + TC_LOG_INFO("server.loading", "Successfully transitioned database to new '%s' value.", info.configKey); + } + + if (currentValue) + { + _secrets[i].state = Secret::PRESENT; + _secrets[i].value = *currentValue; + } + else + _secrets[i].state = Secret::NOT_PRESENT; +} + +Optional<std::string> SecretMgr::AttemptTransition(Secrets i, Optional<BigNumber> const& newSecret, Optional<BigNumber> const& oldSecret, bool hadOldSecret) const +{ + SQLTransaction trans = LoginDatabase.BeginTransaction(); + + switch (i) + { + case SECRET_TOTP_MASTER_KEY: + { + QueryResult result = LoginDatabase.Query("SELECT id, totp_secret FROM account"); + if (result) do + { + Field* fields = result->Fetch(); + if (fields[1].IsNull()) + continue; + + uint32 id = fields[0].GetUInt32(); + std::vector<uint8> totpSecret = fields[1].GetBinary(); + + if (hadOldSecret) + { + if (!oldSecret) + return Trinity::StringFormat("Cannot decrypt old TOTP tokens - add config key '%s' to authserver.conf!", secret_info[i].oldKey); + + bool success = Trinity::Crypto::AEDecrypt<Trinity::Crypto::AES>(totpSecret, oldSecret->AsByteArray<Trinity::Crypto::AES::KEY_SIZE_BYTES>()); + if (!success) + return Trinity::StringFormat("Cannot decrypt old TOTP tokens - value of '%s' is incorrect for some users!", secret_info[i].oldKey); + } + + if (newSecret) + Trinity::Crypto::AEEncryptWithRandomIV<Trinity::Crypto::AES>(totpSecret, newSecret->AsByteArray<Trinity::Crypto::AES::KEY_SIZE_BYTES>()); + + PreparedStatement* updateStmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_TOTP_SECRET); + updateStmt->setBinary(0, totpSecret); + updateStmt->setUInt32(1, id); + trans->Append(updateStmt); + } while (result->NextRow()); + + break; + } + default: + return std::string("Unknown secret index - huh?"); + } + + if (hadOldSecret) + { + PreparedStatement* deleteStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_SECRET_DIGEST); + deleteStmt->setUInt32(0, i); + trans->Append(deleteStmt); + } + + if (newSecret) + { + BigNumber salt; + salt.SetRand(128); + Optional<std::string> hash = Trinity::Crypto::Argon2::Hash(newSecret->AsHexStr(), salt); + if (!hash) + return std::string("Failed to hash new secret"); + + PreparedStatement* insertStmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_SECRET_DIGEST); + insertStmt->setUInt32(0, i); + insertStmt->setString(1, *hash); + trans->Append(insertStmt); + } + + LoginDatabase.CommitTransaction(trans); + return {}; +} diff --git a/src/server/shared/Secrets/SecretMgr.h b/src/server/shared/Secrets/SecretMgr.h new file mode 100644 index 00000000000..faaefab98a9 --- /dev/null +++ b/src/server/shared/Secrets/SecretMgr.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2008-2019 TrinityCore <https://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __TRINITY_SECRETMGR_H__ +#define __TRINITY_SECRETMGR_H__ + +#include "BigNumber.h" +#include "Common.h" +#include "LogCommon.h" +#include "Optional.h" +#include <array> +#include <mutex> +#include <string> + +enum Secrets : uint32 +{ + SECRET_TOTP_MASTER_KEY = 0, + + // only add new indices right above this line + NUM_SECRETS +}; + +class TC_SHARED_API SecretMgr +{ + private: + SecretMgr() {} + ~SecretMgr() {} + + public: + SecretMgr(SecretMgr const&) = delete; + static SecretMgr* instance(); + + struct Secret + { + public: + explicit operator bool() const { return (state == PRESENT); } + BigNumber const& operator*() const { return value; } + BigNumber const* operator->() const { return &value; } + bool IsAvailable() const { return (state != NOT_LOADED_YET) && (state != LOAD_FAILED); } + + private: + std::mutex lock; + enum { NOT_LOADED_YET, LOAD_FAILED, NOT_PRESENT, PRESENT } state = NOT_LOADED_YET; + BigNumber value; + + friend class SecretMgr; + }; + + void Initialize(); + Secret const& GetSecret(Secrets i); + + private: + void AttemptLoad(Secrets i, LogLevel errorLevel, std::unique_lock<std::mutex> const&); + Optional<std::string> AttemptTransition(Secrets i, Optional<BigNumber> const& newSecret, Optional<BigNumber> const& oldSecret, bool hadOldSecret) const; + + std::array<Secret, NUM_SECRETS> _secrets; +}; + +#define sSecretMgr SecretMgr::instance() + +#endif diff --git a/src/server/authserver/Authentication/TOTP.h b/src/server/shared/SharedDefines.cpp index 0c66820a879..f5f9ad98b7d 100644 --- a/src/server/authserver/Authentication/TOTP.h +++ b/src/server/shared/SharedDefines.cpp @@ -15,15 +15,6 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef _TOTP_H -#define _TOTP_H +#include "SharedDefines.h" -#include "openssl/hmac.h" -#include "openssl/evp.h" - -namespace TOTP -{ - unsigned int GenerateToken(char const* b32key); -} - -#endif +ServerProcessTypes Trinity::Impl::CurrentServerProcessHolder::_type = NUM_SERVER_PROCESS_TYPES; diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h index b829a984925..3c6505c7ac2 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -3787,6 +3787,27 @@ enum LineOfSightChecks LINEOFSIGHT_ALL_CHECKS = (LINEOFSIGHT_CHECK_VMAP | LINEOFSIGHT_CHECK_GOBJECT) }; +enum ServerProcessTypes +{ + SERVER_PROCESS_AUTHSERVER = 0, + SERVER_PROCESS_WORLDSERVER = 1, + + NUM_SERVER_PROCESS_TYPES +}; + +namespace Trinity +{ +namespace Impl +{ + struct TC_SHARED_API CurrentServerProcessHolder + { + static ServerProcessTypes type() { return _type; } + static ServerProcessTypes _type; + }; +} +} +#define THIS_SERVER_PROCESS (Trinity::Impl::CurrentServerProcessHolder::type()) + #define MAX_CREATURE_SPELL_DATA_SLOT 4 #endif diff --git a/src/server/worldserver/Main.cpp b/src/server/worldserver/Main.cpp index e163dd2566d..2accd7297c2 100644 --- a/src/server/worldserver/Main.cpp +++ b/src/server/worldserver/Main.cpp @@ -47,6 +47,8 @@ #include "ScriptLoader.h" #include "ScriptMgr.h" #include "ScriptReloadMgr.h" +#include "SecretMgr.h" +#include "SharedDefines.h" #include "TCSoap.h" #include "World.h" #include "WorldSocket.h" @@ -116,6 +118,7 @@ variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, s /// Launch the Trinity server extern int main(int argc, char** argv) { + Trinity::Impl::CurrentServerProcessHolder::_type = SERVER_PROCESS_WORLDSERVER; signal(SIGABRT, &Trinity::AbortHandler); auto configFile = fs::absolute(_TRINITY_CORE_CONFIG); @@ -247,6 +250,7 @@ extern int main(int argc, char** argv) }); // Initialize the World + sSecretMgr->Initialize(); sWorld->SetInitialWorldSettings(); std::shared_ptr<void> mapManagementHandle(nullptr, [](void*) diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index c34a49a9e8d..c308bc9d03d 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -11,6 +11,7 @@ # PERFORMANCE SETTINGS # SERVER LOGGING # SERVER SETTINGS +# CRYPTOGRAPHY # UPDATE SETTINGS # HOTSWAP SETTINGS # WARDEN SETTINGS @@ -1296,6 +1297,25 @@ CacheDataQueries = 1 ################################################################################################### ################################################################################################### +# CRYPTOGRAPHY +# +# TOTPMasterSecret +# Description: The key used by authserver to decrypt TOTP secrets from database storage. +# You only need to set this here if you plan to use the in-game 2FA +# management commands (.account 2fa), otherwise this can be left blank. +# +# The server will auto-detect if this does not match your authserver setting, +# in which case any commands reliant on the secret will be disabled. +# +# Default: <blank> +# + +TOTPMasterSecret = + +# +################################################################################################### + +################################################################################################### # UPDATE SETTINGS # # Updates.EnableDatabases |