Files
itsRevela-LCE_Revelations/Minecraft.Server/Security/IdentityTokenManager.cpp
2026-04-08 12:03:15 +03:00

289 lines
7.1 KiB
C++

#include "stdafx.h"
#include "IdentityTokenManager.h"
#ifdef _WINDOWS64
#include <bcrypt.h>
#endif
#include "../Common/FileUtils.h"
#include "../Common/StringUtils.h"
#include "../ServerLogger.h"
#include "../vendor/nlohmann/json.hpp"
#include <algorithm>
namespace ServerRuntime
{
namespace Security
{
using OrderedJson = nlohmann::ordered_json;
IdentityTokenManager::IdentityTokenManager()
: m_initialized(false)
{
InitializeCriticalSection(&m_lock);
}
IdentityTokenManager::~IdentityTokenManager()
{
DeleteCriticalSection(&m_lock);
}
static std::string BytesToBase64(const uint8_t *data, int length)
{
static const char kTable[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string out;
out.reserve(((length + 2) / 3) * 4);
for (int i = 0; i < length; i += 3)
{
uint32_t n = static_cast<uint32_t>(data[i]) << 16;
if (i + 1 < length) n |= static_cast<uint32_t>(data[i + 1]) << 8;
if (i + 2 < length) n |= static_cast<uint32_t>(data[i + 2]);
out.push_back(kTable[(n >> 18) & 0x3F]);
out.push_back(kTable[(n >> 12) & 0x3F]);
out.push_back((i + 1 < length) ? kTable[(n >> 6) & 0x3F] : '=');
out.push_back((i + 2 < length) ? kTable[n & 0x3F] : '=');
}
return out;
}
static bool Base64ToBytes(const std::string &encoded, std::vector<uint8_t> &out)
{
static const int kDecodeTable[128] = {
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1
};
out.clear();
out.reserve(encoded.size() * 3 / 4);
uint32_t buf = 0;
int bits = 0;
for (char c : encoded)
{
if (c == '=') break;
if (c < 0 || c >= 128 || kDecodeTable[(int)c] < 0) return false;
buf = (buf << 6) | kDecodeTable[(int)c];
bits += 6;
if (bits >= 8)
{
bits -= 8;
out.push_back(static_cast<uint8_t>((buf >> bits) & 0xFF));
}
}
return true;
}
static std::string FormatXuid(PlayerUID xuid)
{
char buffer[32] = {};
sprintf_s(buffer, sizeof(buffer), "0x%016llx", (unsigned long long)xuid);
return buffer;
}
bool IdentityTokenManager::Initialize(const std::string &filePath)
{
EnterCriticalSection(&m_lock);
m_filePath = filePath;
m_tokens.clear();
bool ok = Load();
m_initialized = true;
LeaveCriticalSection(&m_lock);
if (ok)
{
LogInfof("security", "loaded %u identity tokens from %s",
(unsigned)m_tokens.size(), filePath.c_str());
}
else
{
LogInfof("security", "no existing identity tokens found, starting fresh");
}
return true;
}
void IdentityTokenManager::Shutdown()
{
EnterCriticalSection(&m_lock);
m_tokens.clear();
m_initialized = false;
LeaveCriticalSection(&m_lock);
}
bool IdentityTokenManager::HasToken(PlayerUID xuid) const
{
EnterCriticalSection(&m_lock);
bool found = m_tokens.find(xuid) != m_tokens.end();
LeaveCriticalSection(&m_lock);
return found;
}
bool IdentityTokenManager::GetToken(PlayerUID xuid, uint8_t outToken[TOKEN_SIZE]) const
{
EnterCriticalSection(&m_lock);
auto it = m_tokens.find(xuid);
if (it == m_tokens.end() || it->second.size() != TOKEN_SIZE)
{
LeaveCriticalSection(&m_lock);
return false;
}
memcpy(outToken, it->second.data(), TOKEN_SIZE);
LeaveCriticalSection(&m_lock);
return true;
}
bool IdentityTokenManager::IssueToken(PlayerUID xuid, uint8_t outToken[TOKEN_SIZE])
{
// Generate a random 32-byte identity token
uint8_t token[TOKEN_SIZE];
#ifdef _WINDOWS64
NTSTATUS status = BCryptGenRandom(nullptr, token, TOKEN_SIZE,
BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (!BCRYPT_SUCCESS(status))
{
SecureZeroMemory(token, sizeof(token));
return false;
}
#else
for (int i = 0; i < TOKEN_SIZE; ++i)
token[i] = static_cast<uint8_t>(rand() & 0xFF);
#endif
EnterCriticalSection(&m_lock);
m_tokens[xuid] = std::vector<uint8_t>(token, token + TOKEN_SIZE);
bool saved = Save();
LeaveCriticalSection(&m_lock);
if (saved)
{
memcpy(outToken, token, TOKEN_SIZE);
SecureZeroMemory(token, sizeof(token));
return true;
}
SecureZeroMemory(token, sizeof(token));
return false;
}
bool IdentityTokenManager::VerifyToken(PlayerUID xuid, const uint8_t token[TOKEN_SIZE]) const
{
EnterCriticalSection(&m_lock);
auto it = m_tokens.find(xuid);
if (it == m_tokens.end() || it->second.size() != TOKEN_SIZE)
{
LeaveCriticalSection(&m_lock);
return false;
}
// Constant-time comparison to prevent timing attacks
uint8_t diff = 0;
for (int i = 0; i < TOKEN_SIZE; ++i)
{
diff |= it->second[i] ^ token[i];
}
LeaveCriticalSection(&m_lock);
return diff == 0;
}
bool IdentityTokenManager::RevokeToken(PlayerUID xuid)
{
EnterCriticalSection(&m_lock);
auto it = m_tokens.find(xuid);
if (it == m_tokens.end())
{
LeaveCriticalSection(&m_lock);
return false;
}
SecureZeroMemory(it->second.data(), it->second.size());
m_tokens.erase(it);
bool saved = Save();
LeaveCriticalSection(&m_lock);
return saved;
}
bool IdentityTokenManager::Load()
{
std::string text;
if (!FileUtils::ReadTextFile(m_filePath, &text))
{
return false;
}
if (text.empty())
{
return true;
}
OrderedJson root;
try
{
root = OrderedJson::parse(StringUtils::StripUtf8Bom(text));
}
catch (const nlohmann::json::exception &)
{
LogErrorf("security", "failed to parse %s", m_filePath.c_str());
return false;
}
if (!root.is_object() || !root.contains("tokens") || !root["tokens"].is_object())
{
return true;
}
for (auto it = root["tokens"].begin(); it != root["tokens"].end(); ++it)
{
const std::string &xuidStr = it.key();
if (!it.value().is_string()) continue;
unsigned long long parsed = 0;
if (!StringUtils::TryParseUnsignedLongLong(xuidStr, &parsed) || parsed == 0ULL)
continue;
std::vector<uint8_t> tokenBytes;
if (!Base64ToBytes(it.value().get<std::string>(), tokenBytes))
continue;
if (tokenBytes.size() != TOKEN_SIZE)
continue;
m_tokens[static_cast<PlayerUID>(parsed)] = tokenBytes;
}
return true;
}
bool IdentityTokenManager::Save() const
{
OrderedJson root = OrderedJson::object();
OrderedJson tokens = OrderedJson::object();
for (const auto &pair : m_tokens)
{
std::string xuidStr = FormatXuid(pair.first);
std::string tokenB64 = BytesToBase64(pair.second.data(), TOKEN_SIZE);
tokens[xuidStr] = tokenB64;
}
root["tokens"] = tokens;
std::string json = root.dump(2) + "\n";
if (!FileUtils::WriteTextFileAtomic(m_filePath, json))
{
LogErrorf("security", "failed to write %s", m_filePath.c_str());
return false;
}
return true;
}
IdentityTokenManager &GetIdentityTokenManager()
{
static IdentityTokenManager s_instance;
return s_instance;
}
}
}