TrinityCore
SecretMgr.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "SecretMgr.h"
19#include "AES.h"
20#include "Argon2.h"
21#include "Config.h"
22#include "CryptoGenerics.h"
23#include "DatabaseEnv.h"
24#include "Errors.h"
25#include "Log.h"
26
27#define SECRET_FLAG_FOR(key, val, server) server ## _ ## key = (val ## ull << (16*SECRET_OWNER_ ## server))
28#define SECRET_FLAG(key, val) SECRET_FLAG_ ## key = val, SECRET_FLAG_FOR(key, val, BNETSERVER), SECRET_FLAG_FOR(key, val, WORLDSERVER)
30{
31 SECRET_FLAG(DEFER_LOAD, 0x1)
32};
33#undef SECRET_FLAG_FOR
34#undef SECRET_FLAG
35
37{
38 char const* configKey;
39 char const* oldKey;
40 int bits;
43 uint16 flags() const { return static_cast<uint16>(_flags >> (16*SecretMgr::OWNER)); }
44};
45
47{
48 { "TOTPMasterSecret", "TOTPOldMasterSecret", 128, SECRET_OWNER_BNETSERVER, WORLDSERVER_DEFER_LOAD }
49};
50
52
54{
55 static SecretMgr instance;
56 return &instance;
57}
58
59static Optional<BigNumber> GetHexFromConfig(char const* configKey, int bits)
60{
61 ASSERT(bits > 0);
62 std::string str = sConfigMgr->GetStringDefault(configKey, "");
63 if (str.empty())
64 return {};
65
66 BigNumber secret;
67 if (!secret.SetHexStr(str.c_str()))
68 {
69 TC_LOG_FATAL("server.loading", "Invalid value for '{}' - specify a hexadecimal integer of up to {} bits with no prefix.", configKey, bits);
70 ABORT();
71 }
72
73 BigNumber threshold(2);
74 threshold <<= bits;
75 if (!((BigNumber(0) <= secret) && (secret < threshold)))
76 {
77 TC_LOG_ERROR("server.loading", "Value for '{}' is out of bounds (should be an integer of up to {} bits with no prefix). Truncated to {} bits.", configKey, bits, bits);
78 secret %= threshold;
79 }
80 ASSERT(((BigNumber(0) <= secret) && (secret < threshold)));
81
82 return secret;
83}
84
86{
87 OWNER = owner;
88
89 for (uint32 i = 0; i < NUM_SECRETS; ++i)
90 {
91 if (secret_info[i].flags() & SECRET_FLAG_DEFER_LOAD)
92 continue;
93 std::unique_lock<std::mutex> lock(_secrets[i].lock);
95 if (!_secrets[i].IsAvailable())
96 ABORT(); // load failed
97 }
98}
99
101{
102 std::unique_lock<std::mutex> lock(_secrets[i].lock);
103
104 if (_secrets[i].state == Secret::NOT_LOADED_YET)
106 return _secrets[i];
107}
108
109void SecretMgr::AttemptLoad(Secrets i, LogLevel errorLevel, std::unique_lock<std::mutex> const&)
110{
111 auto const& info = secret_info[i];
112 Optional<std::string> oldDigest;
113 {
115 stmt->setUInt32(0, i);
116 PreparedQueryResult result = LoginDatabase.Query(stmt);
117 if (result)
118 oldDigest = result->Fetch()->GetString();
119 }
120 Optional<BigNumber> currentValue = GetHexFromConfig(info.configKey, info.bits);
121
122 // verify digest
123 if (
124 ((!oldDigest) != (!currentValue)) || // there is an old digest, but no current secret (or vice versa)
125 (oldDigest && !Trinity::Crypto::Argon2::Verify(currentValue->AsHexStr(), *oldDigest)) // there is an old digest, and the current secret does not match it
126 )
127 {
128 if (info.owner != OWNER)
129 {
130 if (currentValue)
131 TC_LOG_MESSAGE_BODY("server.loading", errorLevel, "Invalid value for '{}' specified - this is not actually the secret being used in your auth DB.", info.configKey);
132 else
133 TC_LOG_MESSAGE_BODY("server.loading", errorLevel, "No value for '{}' specified - please specify the secret currently being used in your auth DB.", info.configKey);
134 _secrets[i].state = Secret::LOAD_FAILED;
135 return;
136 }
137
138 Optional<BigNumber> oldSecret;
139 if (oldDigest && info.oldKey) // there is an old digest, so there might be an old secret (if possible)
140 {
141 oldSecret = GetHexFromConfig(info.oldKey, info.bits);
142 if (oldSecret && !Trinity::Crypto::Argon2::Verify(oldSecret->AsHexStr(), *oldDigest))
143 {
144 TC_LOG_MESSAGE_BODY("server.loading", errorLevel, "Invalid value for '{}' specified - this is not actually the secret previously used in your auth DB.", info.oldKey);
145 _secrets[i].state = Secret::LOAD_FAILED;
146 return;
147 }
148 }
149
150 // attempt to transition us to the new key, if possible
151 Optional<std::string> error = AttemptTransition(Secrets(i), currentValue, oldSecret, !!oldDigest);
152 if (error)
153 {
154 TC_LOG_MESSAGE_BODY("server.loading", errorLevel, "Your value of '{}' changed, but we cannot transition your database to the new value:\n{}", info.configKey, error->c_str());
155 _secrets[i].state = Secret::LOAD_FAILED;
156 return;
157 }
158
159 TC_LOG_INFO("server.loading", "Successfully transitioned database to new '{}' value.", info.configKey);
160 }
161
162 if (currentValue)
163 {
164 _secrets[i].state = Secret::PRESENT;
165 _secrets[i].value = *currentValue;
166 }
167 else
168 _secrets[i].state = Secret::NOT_PRESENT;
169}
170
171Optional<std::string> SecretMgr::AttemptTransition(Secrets i, Optional<BigNumber> const& newSecret, Optional<BigNumber> const& oldSecret, bool hadOldSecret) const
172{
173 LoginDatabaseTransaction trans = LoginDatabase.BeginTransaction();
174
175 switch (i)
176 {
178 {
179 QueryResult result = LoginDatabase.Query("SELECT id, totp_secret FROM account");
180 if (result) do
181 {
182 Field* fields = result->Fetch();
183 if (fields[1].IsNull())
184 continue;
185
186 uint32 id = fields[0].GetUInt32();
187 std::vector<uint8> totpSecret = fields[1].GetBinary();
188
189 if (hadOldSecret)
190 {
191 if (!oldSecret)
192 return Trinity::StringFormat("Cannot decrypt old TOTP tokens - add config key '{}' to authserver.conf!", secret_info[i].oldKey);
193
194 bool success = Trinity::Crypto::AEDecrypt<Trinity::Crypto::AES>(totpSecret, oldSecret->ToByteArray<Trinity::Crypto::AES::KEY_SIZE_BYTES>());
195 if (!success)
196 return Trinity::StringFormat("Cannot decrypt old TOTP tokens - value of '{}' is incorrect for some users!", secret_info[i].oldKey);
197 }
198
199 if (newSecret)
200 Trinity::Crypto::AEEncryptWithRandomIV<Trinity::Crypto::AES>(totpSecret, newSecret->ToByteArray<Trinity::Crypto::AES::KEY_SIZE_BYTES>());
201
203 updateStmt->setBinary(0, totpSecret);
204 updateStmt->setUInt32(1, id);
205 trans->Append(updateStmt);
206 } while (result->NextRow());
207
208 break;
209 }
210 default:
211 return std::string("Unknown secret index - huh?");
212 }
213
214 if (hadOldSecret)
215 {
216 LoginDatabasePreparedStatement* deleteStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_SECRET_DIGEST);
217 deleteStmt->setUInt32(0, i);
218 trans->Append(deleteStmt);
219 }
220
221 if (newSecret)
222 {
223 BigNumber salt;
224 salt.SetRand(128);
225 Optional<std::string> hash = Trinity::Crypto::Argon2::Hash(newSecret->AsHexStr(), salt);
226 if (!hash)
227 return std::string("Failed to hash new secret");
228
229 LoginDatabasePreparedStatement* insertStmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_SECRET_DIGEST);
230 insertStmt->setUInt32(0, i);
231 insertStmt->setString(1, *hash);
232 trans->Append(insertStmt);
233 }
234
235 LoginDatabase.CommitTransaction(trans);
236 return {};
237}
#define sConfigMgr
Definition: Config.h:61
SQLTransaction< LoginDatabaseConnection > LoginDatabaseTransaction
std::shared_ptr< ResultSet > QueryResult
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< LoginDatabaseConnection > LoginDatabase
Accessor to the realm/login database.
Definition: DatabaseEnv.cpp:22
uint64_t uint64
Definition: Define.h:141
uint16_t uint16
Definition: Define.h:143
uint32_t uint32
Definition: Define.h:142
uint16 flags
Definition: DisableMgr.cpp:49
#define ABORT
Definition: Errors.h:74
#define ASSERT
Definition: Errors.h:68
LogLevel
Definition: LogCommon.h:25
@ LOG_LEVEL_ERROR
Definition: LogCommon.h:31
@ LOG_LEVEL_FATAL
Definition: LogCommon.h:32
#define TC_LOG_ERROR(filterType__,...)
Definition: Log.h:165
#define TC_LOG_MESSAGE_BODY(filterType__, level__,...)
Definition: Log.h:143
#define TC_LOG_INFO(filterType__,...)
Definition: Log.h:159
#define TC_LOG_FATAL(filterType__,...)
Definition: Log.h:168
@ LOGIN_UPD_ACCOUNT_TOTP_SECRET
@ LOGIN_SEL_SECRET_DIGEST
@ LOGIN_INS_SECRET_DIGEST
@ LOGIN_DEL_SECRET_DIGEST
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition: Optional.h:25
SecretFlags
Definition: SecretMgr.cpp:30
static Optional< BigNumber > GetHexFromConfig(char const *configKey, int bits)
Definition: SecretMgr.cpp:59
#define SECRET_FLAG(key, val)
Definition: SecretMgr.cpp:28
static constexpr SecretInfo secret_info[NUM_SECRETS]
Definition: SecretMgr.cpp:46
SecretOwner
Definition: SecretMgr.h:38
@ SECRET_OWNER_BNETSERVER
Definition: SecretMgr.h:39
Secrets
Definition: SecretMgr.h:30
@ NUM_SECRETS
Definition: SecretMgr.h:34
@ SECRET_TOTP_MASTER_KEY
Definition: SecretMgr.h:31
void SetRand(int32 numbits)
Definition: BigNumber.cpp:70
bool SetHexStr(char const *str)
Definition: BigNumber.cpp:64
Class used to access individual fields of database query result.
Definition: Field.h:90
std::vector< uint8 > GetBinary() const
Definition: Field.cpp:142
uint32 GetUInt32() const
Definition: Field.cpp:62
void setBinary(const uint8 index, const std::vector< uint8 > &value)
void setUInt32(const uint8 index, const uint32 value)
void setString(const uint8 index, const std::string &value)
std::array< Secret, NUM_SECRETS > _secrets
Definition: SecretMgr.h:80
static SecretMgr * instance()
Definition: SecretMgr.cpp:53
Optional< std::string > AttemptTransition(Secrets i, Optional< BigNumber > const &newSecret, Optional< BigNumber > const &oldSecret, bool hadOldSecret) const
Definition: SecretMgr.cpp:171
void Initialize(SecretOwner owner)
Definition: SecretMgr.cpp:85
void AttemptLoad(Secrets i, LogLevel errorLevel, std::unique_lock< std::mutex > const &)
Definition: SecretMgr.cpp:109
static SecretOwner OWNER
Definition: SecretMgr.h:52
Secret const & GetSecret(Secrets i)
Definition: SecretMgr.cpp:100
static constexpr size_t KEY_SIZE_BYTES
Definition: AES.h:31
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default TC string format function.
Definition: StringFormat.h:38
uint16 flags() const
Definition: SecretMgr.cpp:43
uint64 _flags
Definition: SecretMgr.cpp:42
char const * oldKey
Definition: SecretMgr.cpp:39
SecretOwner owner
Definition: SecretMgr.cpp:41
char const * configKey
Definition: SecretMgr.cpp:38
static Optional< std::string > Hash(std::string const &password, BigNumber const &salt, uint32 nIterations=DEFAULT_ITERATIONS, uint32 kibMemoryCost=DEFAULT_MEMORY_COST)
Definition: Argon2.cpp:21
static bool Verify(std::string const &password, std::string const &hash)
Definition: Argon2.cpp:40