[Server]: Complete sv_privatePassword implementation (#908)

This commit is contained in:
Edo 2023-04-06 19:57:40 +02:00 committed by GitHub
parent bd0acdb6cb
commit 44ca51e11b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 131 additions and 77 deletions

View File

@ -19,10 +19,12 @@ namespace Components
0xf7e33c4081337fa3,
0x6f5597f103cc50e9
};
bool Auth::HasAccessToReservedSlot;
void Auth::Frame()
{
if (Auth::TokenContainer.generating)
if (TokenContainer.generating)
{
static double mseconds = 0;
static Utils::Time::Interval interval;
@ -31,38 +33,38 @@ namespace Components
{
interval.update();
int diff = Game::Sys_Milliseconds() - Auth::TokenContainer.startTime;
double hashPMS = (Auth::TokenContainer.hashes * 1.0) / diff;
double requiredHashes = std::pow(2, Auth::TokenContainer.targetLevel + 1) - Auth::TokenContainer.hashes;
int diff = Game::Sys_Milliseconds() - TokenContainer.startTime;
double hashPMS = (TokenContainer.hashes * 1.0) / diff;
double requiredHashes = std::pow(2, TokenContainer.targetLevel + 1) - TokenContainer.hashes;
mseconds = requiredHashes / hashPMS;
if (mseconds < 0) mseconds = 0;
}
Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", Utils::String::VA("Increasing security level from %d to %d (est. %s)", Auth::GetSecurityLevel(), Auth::TokenContainer.targetLevel, Utils::String::FormatTimeSpan(static_cast<int>(mseconds)).data()));
Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", Utils::String::VA("Increasing security level from %d to %d (est. %s)",GetSecurityLevel(), TokenContainer.targetLevel, Utils::String::FormatTimeSpan(static_cast<int>(mseconds)).data()));
}
else if (Auth::TokenContainer.thread.joinable())
else if (TokenContainer.thread.joinable())
{
Auth::TokenContainer.thread.join();
Auth::TokenContainer.generating = false;
TokenContainer.thread.join();
TokenContainer.generating = false;
Auth::StoreKey();
Logger::Debug("Security level is {}", Auth::GetSecurityLevel());
StoreKey();
Logger::Debug("Security level is {}",GetSecurityLevel());
Command::Execute("closemenu security_increase_popmenu", false);
if (!Auth::TokenContainer.cancel)
if (!TokenContainer.cancel)
{
if (Auth::TokenContainer.command.empty())
if (TokenContainer.command.empty())
{
Game::ShowMessageBox(Utils::String::VA("Your new security level is %d", Auth::GetSecurityLevel()), "Success");
Game::ShowMessageBox(Utils::String::VA("Your new security level is %d", GetSecurityLevel()), "Success");
}
else
{
Toast::Show("cardicon_locked", "Success", Utils::String::VA("Your new security level is %d", Auth::GetSecurityLevel()), 5000);
Command::Execute(Auth::TokenContainer.command, false);
Toast::Show("cardicon_locked", "Success", Utils::String::VA("Your new security level is %d", GetSecurityLevel()), 5000);
Command::Execute(TokenContainer.command, false);
}
}
Auth::TokenContainer.cancel = false;
TokenContainer.cancel = false;
}
}
@ -70,15 +72,15 @@ namespace Components
{
// Ensure our certificate is loaded
Steam::SteamUser()->GetSteamID();
if (!Auth::GuidKey.isValid())
if (!GuidKey.isValid())
{
Logger::Error(Game::ERR_SERVERDISCONNECT, "Connecting failed: Guid key is invalid!");
return;
}
if (std::find(Auth::BannedUids.begin(), Auth::BannedUids.end(), Steam::SteamUser()->GetSteamID().bits) != Auth::BannedUids.end())
if (std::find(BannedUids.begin(), BannedUids.end(), Steam::SteamUser()->GetSteamID().bits) != BannedUids.end())
{
Auth::GenerateKey();
GenerateKey();
Logger::Error(Game::ERR_SERVERDISCONNECT, "Your online profile is invalid. A new key has been generated.");
return;
}
@ -121,9 +123,9 @@ namespace Components
Game::SV_Cmd_EndTokenizedString();
Proto::Auth::Connect connectData;
connectData.set_token(Auth::GuidToken.toString());
connectData.set_publickey(Auth::GuidKey.getPublicKey());
connectData.set_signature(Utils::Cryptography::ECC::SignMessage(Auth::GuidKey, challenge));
connectData.set_token(GuidToken.toString());
connectData.set_publickey(GuidKey.getPublicKey());
connectData.set_signature(Utils::Cryptography::ECC::SignMessage(GuidKey, challenge));
connectData.set_infostring(connectString);
Network::SendCommand(sock, adr, "connect", connectData.SerializeAsString());
@ -182,7 +184,7 @@ namespace Components
}
// Parse the infostring
Utils::InfoString infostr(params[2]);
Utils::InfoString infostr(params.get(2));
// Read the required data
const auto steamId = infostr.get("xuid");
@ -206,13 +208,13 @@ namespace Components
return;
}
if (std::find(Auth::BannedUids.begin(), Auth::BannedUids.end(), xuid) != Auth::BannedUids.end())
if (std::find(BannedUids.begin(), BannedUids.end(), xuid) != BannedUids.end())
{
Network::Send(address, "error\nYour online profile is invalid. Delete your players folder and restart ^2IW4x^7.");
return;
}
if (xuid != Auth::GetKeyHash(connectData.publickey()))
if (xuid != GetKeyHash(connectData.publickey()))
{
Network::Send(address, "error\nXUID doesn't match the certificate!");
return;
@ -230,7 +232,7 @@ namespace Components
// Verify the security level
auto ourLevel = Dvar::Var("sv_securityLevel").get<unsigned int>();
auto userLevel = Auth::GetZeroBits(connectData.token(), connectData.publickey());
auto userLevel = GetZeroBits(connectData.token(), connectData.publickey());
if (userLevel < ourLevel)
{
@ -252,7 +254,7 @@ namespace Components
lea eax, [esp + 20h]
push eax
push esi
call Auth::ParseConnectData
call ParseConnectData
pop esi
pop eax
popad
@ -262,6 +264,44 @@ namespace Components
}
}
char* Auth::Info_ValueForKeyStub(const char* s, const char* key)
{
auto* value = Game::Info_ValueForKey(s, key);
HasAccessToReservedSlot = std::strcmp((*Game::sv_privatePassword)->current.string, value) == 0;
return value;
}
__declspec(naked) void Auth::DirectConnectPrivateClientStub()
{
__asm
{
push eax
mov al, HasAccessToReservedSlot
test al, al
pop eax
je noAccess
// Set the number of private clients to 0 if the client has the right password
xor eax, eax
jmp safeContinue
noAccess:
mov eax, dword ptr [edx + 0x10]
safeContinue:
// Game code skipped by hook
add esp, 0xC
push 0x460FB3
ret
}
}
unsigned __int64 Auth::GetKeyHash(const std::string& key)
{
std::string hash = Utils::Cryptography::SHA1::Compute(key);
@ -276,18 +316,18 @@ namespace Components
unsigned __int64 Auth::GetKeyHash()
{
Auth::LoadKey();
return Auth::GetKeyHash(Auth::GuidKey.getPublicKey());
LoadKey();
return GetKeyHash(GuidKey.getPublicKey());
}
void Auth::StoreKey()
{
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && Auth::GuidKey.isValid())
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && GuidKey.isValid())
{
Proto::Auth::Certificate cert;
cert.set_token(Auth::GuidToken.toString());
cert.set_ctoken(Auth::ComputeToken.toString());
cert.set_privatekey(Auth::GuidKey.serialize(PK_PRIVATE));
cert.set_token(GuidToken.toString());
cert.set_ctoken(ComputeToken.toString());
cert.set_privatekey(GuidKey.serialize(PK_PRIVATE));
Utils::IO::WriteFile("players/guid.dat", cert.SerializeAsString());
}
@ -295,30 +335,30 @@ namespace Components
void Auth::GenerateKey()
{
Auth::GuidToken.clear();
Auth::ComputeToken.clear();
Auth::GuidKey = Utils::Cryptography::ECC::GenerateKey(512);
Auth::StoreKey();
GuidToken.clear();
ComputeToken.clear();
GuidKey = Utils::Cryptography::ECC::GenerateKey(512);
StoreKey();
}
void Auth::LoadKey(bool force)
{
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
if (!force && Auth::GuidKey.isValid()) return;
if (!force && GuidKey.isValid()) return;
Proto::Auth::Certificate cert;
if (cert.ParseFromString(::Utils::IO::ReadFile("players/guid.dat")))
{
Auth::GuidKey.deserialize(cert.privatekey());
Auth::GuidToken = cert.token();
Auth::ComputeToken = cert.ctoken();
GuidKey.deserialize(cert.privatekey());
GuidToken = cert.token();
ComputeToken = cert.ctoken();
}
else
{
Auth::GuidKey.free();
GuidKey.free();
}
if (!Auth::GuidKey.isValid())
if (!GuidKey.isValid())
{
Auth::GenerateKey();
}
@ -326,32 +366,32 @@ namespace Components
uint32_t Auth::GetSecurityLevel()
{
return Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.getPublicKey());
return GetZeroBits(GuidToken, GuidKey.getPublicKey());
}
void Auth::IncreaseSecurityLevel(uint32_t level, const std::string& command)
{
if (Auth::GetSecurityLevel() >= level) return;
if (GetSecurityLevel() >= level) return;
if (!Auth::TokenContainer.generating)
if (!TokenContainer.generating)
{
Auth::TokenContainer.cancel = false;
Auth::TokenContainer.targetLevel = level;
Auth::TokenContainer.command = command;
TokenContainer.cancel = false;
TokenContainer.targetLevel = level;
TokenContainer.command = command;
// Open menu
Command::Execute("openmenu security_increase_popmenu", true);
// Start thread
Auth::TokenContainer.thread = std::thread([&level]()
TokenContainer.thread = std::thread([&level]()
{
Auth::TokenContainer.generating = true;
Auth::TokenContainer.hashes = 0;
Auth::TokenContainer.startTime = Game::Sys_Milliseconds();
Auth::IncrementToken(Auth::GuidToken, Auth::ComputeToken, Auth::GuidKey.getPublicKey(), Auth::TokenContainer.targetLevel, &Auth::TokenContainer.cancel, &Auth::TokenContainer.hashes);
Auth::TokenContainer.generating = false;
TokenContainer.generating = true;
TokenContainer.hashes = 0;
TokenContainer.startTime = Game::Sys_Milliseconds();
IncrementToken(GuidToken, ComputeToken, GuidKey.getPublicKey(), TokenContainer.targetLevel, &TokenContainer.cancel, &TokenContainer.hashes);
TokenContainer.generating = false;
if (Auth::TokenContainer.cancel)
if (TokenContainer.cancel)
{
Logger::Print("Token incrementation thread terminated\n");
}
@ -399,7 +439,7 @@ namespace Components
}
// Check if we already have the desired security level
uint32_t lastLevel = Auth::GetZeroBits(token, publicKey);
uint32_t lastLevel = GetZeroBits(token, publicKey);
uint32_t level = lastLevel;
if (level >= zeroBits) return;
@ -407,7 +447,7 @@ namespace Components
{
++computeToken;
if (count) ++(*count);
level = Auth::GetZeroBits(computeToken, publicKey);
level = GetZeroBits(computeToken, publicKey);
// Store level if higher than the last one
if (level >= lastLevel)
@ -425,30 +465,36 @@ namespace Components
Auth::Auth()
{
Auth::TokenContainer.cancel = false;
Auth::TokenContainer.generating = false;
TokenContainer.cancel = false;
TokenContainer.generating = false;
HasAccessToReservedSlot = false;
Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", "");
// Load the key
Auth::LoadKey(true);
LoadKey(true);
Steam::SteamUser()->GetSteamID();
Scheduler::Loop(Auth::Frame, Scheduler::Pipeline::MAIN);
Scheduler::Loop(Frame, Scheduler::Pipeline::MAIN);
// Register dvar
Dvar::Register<int>("sv_securityLevel", 23, 0, 512, Game::DVAR_SERVERINFO, "Security level for GUID certificates (POW)");
// Install registration hook
Utils::Hook(0x6265F9, Auth::DirectConnectStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x41D3E3, Auth::SendConnectDataStub, HOOK_CALL).install()->quick();
Utils::Hook(0x6265F9, DirectConnectStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x460EF5, Info_ValueForKeyStub, HOOK_CALL).install()->quick();
Utils::Hook(0x460FAD, DirectConnectPrivateClientStub, HOOK_JUMP).install()->quick();
Utils::Hook::Nop(0x460FAD + 5, 1);
Utils::Hook(0x41D3E3, SendConnectDataStub, HOOK_CALL).install()->quick();
// SteamIDs can only contain 31 bits of actual 'id' data.
// The other 33 bits are steam internal data like universe and so on.
// Using only 31 bits for fingerprints is pretty insecure.
// The function below verifies the integrity steam's part of the SteamID.
// Patching that check allows us to use 64 bit for fingerprints.
Utils::Hook::Set<DWORD>(0x4D0D60, 0xC301B0);
Utils::Hook::Set<std::uint32_t>(0x4D0D60, 0xC301B0);
// Guid command
Command::Add("guid", []
@ -462,42 +508,42 @@ namespace Components
{
if (params->size() < 2)
{
const auto level = Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.getPublicKey());
const auto level = GetZeroBits(GuidToken, GuidKey.getPublicKey());
Logger::Print("Your current security level is {}\n", level);
Logger::Print("Your security token is: {}\n", Utils::String::DumpHex(Auth::GuidToken.toString(), ""));
Logger::Print("Your computation token is: {}\n", Utils::String::DumpHex(Auth::ComputeToken.toString(), ""));
Logger::Print("Your security token is: {}\n", Utils::String::DumpHex(GuidToken.toString(), ""));
Logger::Print("Your computation token is: {}\n", Utils::String::DumpHex(ComputeToken.toString(), ""));
Toast::Show("cardicon_locked", "^5Security Level", Utils::String::VA("Your security level is %d", level), 3000);
}
else
{
const auto level = std::strtoul(params->get(1), nullptr, 10);
Auth::IncreaseSecurityLevel(level);
IncreaseSecurityLevel(level);
}
});
}
UIScript::Add("security_increase_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
Auth::TokenContainer.cancel = true;
TokenContainer.cancel = true;
Logger::Print("Token incrementation process canceled!\n");
});
}
Auth::~Auth()
{
Auth::StoreKey();
StoreKey();
}
void Auth::preDestroy()
{
Auth::TokenContainer.cancel = true;
Auth::TokenContainer.generating = false;
TokenContainer.cancel = true;
TokenContainer.generating = false;
// Terminate thread
if (Auth::TokenContainer.thread.joinable())
if (TokenContainer.thread.joinable())
{
Auth::TokenContainer.thread.join();
TokenContainer.thread.join();
}
}

View File

@ -44,10 +44,14 @@ namespace Components
static Utils::Cryptography::Token ComputeToken;
static Utils::Cryptography::ECC::Key GuidKey;
static std::vector<std::uint64_t> BannedUids;
static bool HasAccessToReservedSlot;
static void SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char* format, int len);
static void ParseConnectData(Game::msg_t* msg, Game::netadr_t* addr);
static void DirectConnectStub();
static char* Info_ValueForKeyStub(const char* s, const char* key);
static void DirectConnectPrivateClientStub();
static void Frame();
};

View File

@ -936,7 +936,7 @@ namespace Components
UIScript::Add("CreateListFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
auto* serverInfo = GetCurrentServer();
if (info)
if (info && serverInfo && serverInfo->addr.isValid())
{
StoreFavourite(serverInfo->addr.getString());
}
@ -944,7 +944,11 @@ namespace Components
UIScript::Add("CreateFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
StoreFavourite(Dvar::Var("ui_favoriteAddress").get<std::string>());
const auto value = Dvar::Var("ui_favoriteAddress").get<std::string>();
if (!value.empty())
{
StoreFavourite(value);
}
});
UIScript::Add("CreateCurrentServerFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)