aboutsummaryrefslogtreecommitdiff
path: root/src/server/scripts
diff options
context:
space:
mode:
authorTreeston <treeston.mmoc@gmail.com>2019-08-10 21:34:51 +0200
committerShauren <shauren.trinity@gmail.com>2021-12-18 20:24:50 +0100
commit0b61c3b7b1399f5dd0cab90da36002b7d8e0af6b (patch)
treed869f832263c29814004569c1353251fc8e8538c /src/server/scripts
parent92e92e818b704803377f44dbc6a8158b3d38225b (diff)
[3.3.5] Core/Authserver: TOTP rewrite: (PR #23633)
- Proper management commands (.account 2fa) - Secrets can now be encrypted (set TOTPTokenSecret in .conf) - Secret now stored in binary - Argon2 and AES primitives - Base32/64 support (cherry picked from commit 4211645834c467a03c60248e80818d3607be9ea7)
Diffstat (limited to 'src/server/scripts')
-rw-r--r--src/server/scripts/Commands/cs_account.cpp209
1 files changed, 209 insertions, 0 deletions
diff --git a/src/server/scripts/Commands/cs_account.cpp b/src/server/scripts/Commands/cs_account.cpp
index 9c4503560c2..c4a68da5401 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;
@@ -55,6 +62,12 @@ public:
{ "gmlevel", rbac::RBAC_PERM_COMMAND_ACCOUNT_SET_SECLEVEL, true, &HandleAccountSetSecLevelCommand, "" }, // temp for a transition period
{ "seclevel", rbac::RBAC_PERM_COMMAND_ACCOUNT_SET_SECLEVEL, true, &HandleAccountSetSecLevelCommand, "" },
{ "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 =
{
@@ -63,6 +76,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, "" },
@@ -80,6 +94,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
+ LoginDatabasePreparedStatement* 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);
+
+ LoginDatabasePreparedStatement* 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
+ LoginDatabasePreparedStatement* 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))
+ {
+ LoginDatabasePreparedStatement* 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))
@@ -657,6 +803,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")
+ {
+ LoginDatabasePreparedStatement* 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);
+
+ LoginDatabasePreparedStatement* 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)
{