AzerothCore 3.3.5a
OpenSource WoW Emulator
Loading...
Searching...
No Matches
SecretMgr Class Reference

#include "SecretMgr.h"

Classes

struct  Secret
 

Public Member Functions

 SecretMgr (SecretMgr const &)=delete
 
void Initialize ()
 
Secret const & GetSecret (Secrets i)
 

Static Public Member Functions

static SecretMgrinstance ()
 

Private Member Functions

 SecretMgr ()=default
 
 ~SecretMgr ()=default
 
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
 

Private Attributes

std::array< Secret, NUM_SECRETS_secrets
 

Detailed Description

Constructor & Destructor Documentation

◆ SecretMgr() [1/2]

SecretMgr::SecretMgr ( )
privatedefault

◆ ~SecretMgr()

SecretMgr::~SecretMgr ( )
privatedefault

◆ SecretMgr() [2/2]

SecretMgr::SecretMgr ( SecretMgr const &  )
delete

Member Function Documentation

◆ AttemptLoad()

void SecretMgr::AttemptLoad ( Secrets  i,
LogLevel  errorLevel,
std::unique_lock< std::mutex > const &   
)
private
107{
108 auto const& info = secret_info[i];
109
110 Optional<std::string> oldDigest;
111 {
112 auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_SECRET_DIGEST);
113 stmt->SetData(0, i);
114 PreparedQueryResult result = LoginDatabase.Query(stmt);
115 if (result)
116 oldDigest = result->Fetch()->Get<std::string>();
117 }
118
119 Optional<BigNumber> currentValue = GetHexFromConfig(info.configKey, info.bits);
120
121 // verify digest
122 if (
123 ((!oldDigest) != (!currentValue)) || // there is an old digest, but no current secret (or vice versa)
124 (oldDigest && !Acore::Crypto::Argon2::Verify(currentValue->AsHexStr(), *oldDigest)) // there is an old digest, and the current secret does not match it
125 )
126 {
127 if (info.owner != THIS_SERVER_PROCESS)
128 {
129 if (currentValue)
130 LOG_MESSAGE_BODY("server.loading", errorLevel, "Invalid value for '{}' specified - this is not actually the secret being used in your auth DB.", info.configKey);
131 else
132 LOG_MESSAGE_BODY("server.loading", errorLevel, "No value for '{}' specified - please specify the secret currently being used in your auth DB.", info.configKey);
133 _secrets[i].state = Secret::LOAD_FAILED;
134 return;
135 }
136
137 Optional<BigNumber> oldSecret;
138 if (oldDigest && info.oldKey) // there is an old digest, so there might be an old secret (if possible)
139 {
140 oldSecret = GetHexFromConfig(info.oldKey, info.bits);
141 if (oldSecret && !Acore::Crypto::Argon2::Verify(oldSecret->AsHexStr(), *oldDigest))
142 {
143 LOG_MESSAGE_BODY("server.loading", errorLevel, "Invalid value for '{}' specified - this is not actually the secret previously used in your auth DB.", info.oldKey);
144 _secrets[i].state = Secret::LOAD_FAILED;
145 return;
146 }
147 }
148
149 // attempt to transition us to the new key, if possible
150 Optional<std::string> error = AttemptTransition(Secrets(i), currentValue, oldSecret, static_cast<bool>(oldDigest));
151 if (error)
152 {
153 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());
154 _secrets[i].state = Secret::LOAD_FAILED;
155 return;
156 }
157
158 LOG_INFO("server.loading", "Successfully transitioned database to new '{}' value.", info.configKey);
159 }
160
161 if (currentValue)
162 {
163 _secrets[i].state = Secret::PRESENT;
164 _secrets[i].value = *currentValue;
165 }
166 else
167 _secrets[i].state = Secret::NOT_PRESENT;
168}
#define LOG_MESSAGE_BODY(filterType__, level__,...)
Definition: Log.h:146
#define LOG_INFO(filterType__,...)
Definition: Log.h:167
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition: Optional.h:24
DatabaseWorkerPool< LoginDatabaseConnection > LoginDatabase
Accessor to the realm/login database.
Definition: DatabaseEnv.cpp:22
std::shared_ptr< PreparedResultSet > PreparedQueryResult
Definition: DatabaseEnvFwd.h:50
@ LOGIN_SEL_SECRET_DIGEST
Definition: LoginDatabase.h:114
static Optional< BigNumber > GetHexFromConfig(char const *configKey, int bits)
Definition: SecretMgr.cpp:58
static constexpr SecretInfo secret_info[NUM_SECRETS]
Definition: SecretMgr.cpp:47
Secrets
Definition: SecretMgr.h:30
#define THIS_SERVER_PROCESS
Definition: SharedDefines.h:3738
static bool Verify(std::string const &password, std::string const &hash)
Definition: Argon2.cpp:40
std::array< Secret, NUM_SECRETS > _secrets
Definition: SecretMgr.h:70
Optional< std::string > AttemptTransition(Secrets i, Optional< BigNumber > const &newSecret, Optional< BigNumber > const &oldSecret, bool hadOldSecret) const
Definition: SecretMgr.cpp:170
@ LOAD_FAILED
Definition: SecretMgr.h:57
@ PRESENT
Definition: SecretMgr.h:57
@ NOT_PRESENT
Definition: SecretMgr.h:57

References _secrets, AttemptTransition(), GetHexFromConfig(), SecretMgr::Secret::LOAD_FAILED, LOG_INFO, LOG_MESSAGE_BODY, LOGIN_SEL_SECRET_DIGEST, LoginDatabase, SecretMgr::Secret::NOT_PRESENT, SecretMgr::Secret::PRESENT, secret_info, THIS_SERVER_PROCESS, and Acore::Crypto::Argon2::Verify().

Referenced by GetSecret(), and Initialize().

◆ AttemptTransition()

Optional< std::string > SecretMgr::AttemptTransition ( Secrets  i,
Optional< BigNumber > const &  newSecret,
Optional< BigNumber > const &  oldSecret,
bool  hadOldSecret 
) const
private
171{
172 auto trans = LoginDatabase.BeginTransaction();
173
174 switch (i)
175 {
177 {
178 QueryResult result = LoginDatabase.Query("SELECT id, totp_secret FROM account");
179 if (result) do
180 {
181 Field* fields = result->Fetch();
182 if (fields[1].IsNull())
183 continue;
184
185 uint32 id = fields[0].Get<uint32>();
186 std::vector<uint8> totpSecret = fields[1].Get<Binary>();
187
188 if (hadOldSecret)
189 {
190 if (!oldSecret)
191 return Acore::StringFormat("Cannot decrypt old TOTP tokens - add config key '%s' to authserver.conf!", secret_info[i].oldKey);
192
193 bool success = Acore::Crypto::AEDecrypt<Acore::Crypto::AES>(totpSecret, oldSecret->ToByteArray<Acore::Crypto::AES::KEY_SIZE_BYTES>());
194 if (!success)
195 return Acore::StringFormat("Cannot decrypt old TOTP tokens - value of '%s' is incorrect for some users!", secret_info[i].oldKey);
196 }
197
198 if (newSecret)
199 Acore::Crypto::AEEncryptWithRandomIV<Acore::Crypto::AES>(totpSecret, newSecret->ToByteArray<Acore::Crypto::AES::KEY_SIZE_BYTES>());
200
201 auto* updateStmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_TOTP_SECRET);
202 updateStmt->SetData(0, totpSecret);
203 updateStmt->SetData(1, id);
204 trans->Append(updateStmt);
205 } while (result->NextRow());
206
207 break;
208 }
209 default:
210 return std::string("Unknown secret index - huh?");
211 }
212
213 if (hadOldSecret)
214 {
215 auto* deleteStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_SECRET_DIGEST);
216 deleteStmt->SetData(0, i);
217 trans->Append(deleteStmt);
218 }
219
220 if (newSecret)
221 {
222 BigNumber salt;
223 salt.SetRand(128);
224 Optional<std::string> hash = Acore::Crypto::Argon2::Hash(newSecret->AsHexStr(), salt);
225 if (!hash)
226 return std::string("Failed to hash new secret");
227
228 auto* insertStmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_SECRET_DIGEST);
229 insertStmt->SetData(0, i);
230 insertStmt->SetData(1, *hash);
231 trans->Append(insertStmt);
232 }
233
234 LoginDatabase.CommitTransaction(trans);
235 return {};
236}
std::uint32_t uint32
Definition: Define.h:108
std::shared_ptr< ResultSet > QueryResult
Definition: DatabaseEnvFwd.h:28
std::vector< uint8 > Binary
Definition: Field.h:41
@ LOGIN_UPD_ACCOUNT_TOTP_SECRET
Definition: LoginDatabase.h:119
@ LOGIN_INS_SECRET_DIGEST
Definition: LoginDatabase.h:115
@ LOGIN_DEL_SECRET_DIGEST
Definition: LoginDatabase.h:116
@ SECRET_TOTP_MASTER_KEY
Definition: SecretMgr.h:31
std::string StringFormat(Format &&fmt, Args &&... args)
Default AC string format function.
Definition: StringFormat.h:29
static constexpr size_t KEY_SIZE_BYTES
Definition: AES.h:31
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
Definition: BigNumber.h:29
void SetRand(int32 numbits)
Definition: BigNumber.cpp:84
Class used to access individual fields of database query result.
Definition: Field.h:99
std::enable_if_t< std::is_arithmetic_v< T >, T > Get() const
Definition: Field.h:113

References Field::Get(), Acore::Crypto::Argon2::Hash(), Acore::Crypto::AES::KEY_SIZE_BYTES, LOGIN_DEL_SECRET_DIGEST, LOGIN_INS_SECRET_DIGEST, LOGIN_UPD_ACCOUNT_TOTP_SECRET, LoginDatabase, secret_info, SECRET_TOTP_MASTER_KEY, BigNumber::SetRand(), and Acore::StringFormat().

Referenced by AttemptLoad().

◆ GetSecret()

SecretMgr::Secret const & SecretMgr::GetSecret ( Secrets  i)
98{
99 std::unique_lock<std::mutex> lock(_secrets[i].lock);
100
101 if (_secrets[i].state == Secret::NOT_LOADED_YET)
102 AttemptLoad(i, LogLevel::LOG_LEVEL_ERROR, lock);
103 return _secrets[i];
104}
void AttemptLoad(Secrets i, LogLevel errorLevel, std::unique_lock< std::mutex > const &)
Definition: SecretMgr.cpp:106
@ NOT_LOADED_YET
Definition: SecretMgr.h:57

References _secrets, AttemptLoad(), and SecretMgr::Secret::NOT_LOADED_YET.

◆ Initialize()

void SecretMgr::Initialize ( )
85{
86 for (uint32 i = 0; i < NUM_SECRETS; ++i)
87 {
88 if (secret_info[i].flags() & SECRET_FLAG_DEFER_LOAD)
89 continue;
90 std::unique_lock<std::mutex> lock(_secrets[i].lock);
91 AttemptLoad(Secrets(i), LogLevel::LOG_LEVEL_FATAL, lock);
92 if (!_secrets[i].IsAvailable())
93 ABORT(); // load failed
94 }
95}
#define ABORT
Definition: Errors.h:76
@ NUM_SECRETS
Definition: SecretMgr.h:34

References _secrets, ABORT, AttemptLoad(), NUM_SECRETS, and secret_info.

◆ instance()

SecretMgr * SecretMgr::instance ( )
static
53{
54 static SecretMgr instance;
55 return &instance;
56}
Definition: SecretMgr.h:38
static SecretMgr * instance()
Definition: SecretMgr.cpp:52

References instance().

Referenced by instance().

Member Data Documentation

◆ _secrets

std::array<Secret, NUM_SECRETS> SecretMgr::_secrets
private

Referenced by AttemptLoad(), GetSecret(), and Initialize().