iw4x-client/src/Components/Modules/Auth.cpp

493 lines
14 KiB
C++
Raw Normal View History

2016-02-21 13:57:56 -05:00
#include "STDInclude.hpp"
namespace Components
{
Auth::AuthInfo Auth::ClientAuthInfo[18];
2016-02-22 20:03:05 -05:00
Auth::TokenIncrementing Auth::TokenContainer;
2016-02-21 13:57:56 -05:00
2016-02-22 17:35:53 -05:00
Utils::Cryptography::Token Auth::GuidToken;
2016-04-11 07:32:11 -04:00
Utils::Cryptography::Token Auth::ComputeToken;
Utils::Cryptography::ECC::Key Auth::GuidKey;
2016-02-22 17:35:53 -05:00
2016-02-21 13:57:56 -05:00
void Auth::Frame()
{
2016-03-10 16:51:26 -05:00
#ifndef DEBUG
2016-02-21 13:57:56 -05:00
for (int i = 0; i < *Game::svs_numclients; i++)
{
Game::client_t* client = &Game::svs_clients[i];
Auth::AuthInfo* info = &Auth::ClientAuthInfo[i];
// State must be 5 or greater here, as otherwise the client will crash when being kicked.
// That's due to the hunk being freed by that time, but it hasn't been reallocated, therefore all future allocations will cause a crash.
// Additionally, the game won't catch the errors and simply lose the connection, so we even have to add a delay to send the data.
// Not sure if that's potentially unsafe, though.
// Players faking their GUID will be connected for 5 seconds, which allows them to fuck up everything.
// I think we have to perform the verification when clients are still in state 3, but for now it works.
// I think we even have to lock the client into state 3 until the verification is done.
// Intercepting the entire connection process to perform the authentication within state 3 solely is necessary, due to having a timeout.
// Not sending a response might allow the player to connect for a few seconds (<= 5) until the timeout is reached.
if (client->state >= 5)
{
2016-02-22 07:37:13 -05:00
if (info->state == Auth::STATE_NEGOTIATING && (Game::Com_Milliseconds() - info->time) > 1000 * 5)
2016-02-21 13:57:56 -05:00
{
info->state = Auth::STATE_INVALID;
info->time = Game::Com_Milliseconds();
2016-02-21 15:31:49 -05:00
Game::SV_KickClientError(client, "XUID verification timed out!");
2016-02-21 13:57:56 -05:00
}
2016-02-22 07:37:13 -05:00
else if (info->state == Auth::STATE_UNKNOWN && info->time && (Game::Com_Milliseconds() - info->time) > 1000 * 5) // Wait 5 seconds (error delay)
2016-02-21 13:57:56 -05:00
{
2016-02-23 07:54:26 -05:00
if ((client->steamid & 0xFFFFFFFF00000000) != 0x110000100000000)
{
info->state = Auth::STATE_INVALID;
info->time = Game::Com_Milliseconds();
Game::SV_KickClientError(client, "Your XUID is invalid!");
}
else
{
Logger::Print("Sending XUID authentication request to %s\n", Network::Address(client->adr).GetString());
2016-02-21 13:57:56 -05:00
2016-02-23 07:54:26 -05:00
info->state = Auth::STATE_NEGOTIATING;
info->time = Game::Com_Milliseconds();
info->challenge = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt());
Network::SendCommand(client->adr, "xuidAuthReq", info->challenge);
}
2016-02-21 13:57:56 -05:00
}
else if (info->state == Auth::STATE_UNKNOWN && !info->time)
{
info->time = Game::Com_Milliseconds();
}
}
}
2016-03-10 16:51:26 -05:00
#endif
2016-02-22 20:03:05 -05:00
if (Auth::TokenContainer.generating)
{
2016-02-23 07:39:38 -05:00
static int lastCalc = 0;
static double mseconds = 0;
2016-02-23 09:09:13 -05:00
if (!lastCalc || (Game::Com_Milliseconds() - lastCalc) > 500)
2016-02-23 07:39:38 -05:00
{
2016-02-23 08:06:38 -05:00
lastCalc = Game::Com_Milliseconds();
2016-02-23 07:39:38 -05:00
int diff = Game::Com_Milliseconds() - Auth::TokenContainer.startTime;
double hashPMS = (Auth::TokenContainer.hashes * 1.0) / diff;
2016-02-23 07:44:41 -05:00
double requiredHashes = std::pow(2, Auth::TokenContainer.targetLevel + 1) - Auth::TokenContainer.hashes;
2016-02-23 07:39:38 -05:00
mseconds = requiredHashes / hashPMS;
if (mseconds < 0) mseconds = 0;
}
Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", Utils::VA("Increasing security level from %d to %d (est. %s)", Auth::GetSecurityLevel(), Auth::TokenContainer.targetLevel, Utils::FormatTimeSpan(static_cast<int>(mseconds)).data()));
2016-02-22 20:03:05 -05:00
}
2016-06-03 18:06:07 -04:00
else if(Auth::TokenContainer.thread.joinable())
2016-02-22 20:03:05 -05:00
{
2016-06-03 18:06:07 -04:00
Auth::TokenContainer.thread.join();
2016-02-22 20:03:05 -05:00
Auth::TokenContainer.generating = false;
2016-02-23 17:56:05 -05:00
Auth::StoreKey();
2016-02-22 20:03:05 -05:00
Logger::Print("Security level is %d\n", Auth::GetSecurityLevel());
Command::Execute("closemenu security_increase_popmenu", false);
if (!Auth::TokenContainer.cancel)
{
if (Auth::TokenContainer.command.empty())
{
2016-02-23 18:05:23 -05:00
Game::MessageBox(Utils::VA("Your new security level is %d", Auth::GetSecurityLevel()), "Success");
2016-02-22 20:03:05 -05:00
}
else
{
Command::Execute(Auth::TokenContainer.command, false);
}
}
Auth::TokenContainer.cancel = false;
}
2016-02-21 13:57:56 -05:00
}
void Auth::RegisterClient(int clientNum)
{
if (clientNum >= 18) return;
Network::Address address(Game::svs_clients[clientNum].adr);
if (address.GetType() == Game::netadrtype_t::NA_BOT)
{
Auth::ClientAuthInfo[clientNum].state = Auth::STATE_VALID;
}
else
{
Logger::Print("Registering client %s\n", address.GetString());
Auth::ClientAuthInfo[clientNum].time = 0;
Auth::ClientAuthInfo[clientNum].state = Auth::STATE_UNKNOWN;
}
}
void __declspec(naked) Auth::RegisterClientStub()
{
__asm
{
push esi
call Auth::RegisterClient
pop esi
imul esi, 366Ch
mov eax, 478A18h
jmp eax
}
}
2016-02-22 17:35:53 -05:00
unsigned int Auth::GetKeyHash()
{
Auth::LoadKey();
std::string key = Auth::GuidKey.GetPublicKey();
2016-04-11 07:32:11 -04:00
return (Utils::Cryptography::JenkinsOneAtATime::Compute(key.data(), key.size()));
2016-02-22 17:35:53 -05:00
}
void Auth::StoreKey()
{
2016-02-23 12:54:28 -05:00
if (!Dedicated::IsDedicated() && !ZoneBuilder::IsEnabled())
{
Proto::Auth::Certificate cert;
cert.set_token(Auth::GuidToken.ToString());
2016-04-11 07:32:11 -04:00
cert.set_ctoken(Auth::ComputeToken.ToString());
2016-02-23 12:54:28 -05:00
cert.set_privatekey(Auth::GuidKey.Export(PK_PRIVATE));
2016-02-22 17:35:53 -05:00
2016-02-23 12:54:28 -05:00
Utils::WriteFile("players/guid.dat", cert.SerializeAsString());
}
2016-02-22 17:35:53 -05:00
}
void Auth::LoadKey(bool force)
{
2016-02-23 12:54:28 -05:00
if (Dedicated::IsDedicated() || ZoneBuilder::IsEnabled()) return;
2016-02-22 17:35:53 -05:00
if (!force && Auth::GuidKey.IsValid()) return;
Proto::Auth::Certificate cert;
if (cert.ParseFromString(::Utils::ReadFile("players/guid.dat")))
{
Auth::GuidKey.Import(cert.privatekey(), PK_PRIVATE);
Auth::GuidToken = cert.token();
2016-04-11 07:32:11 -04:00
Auth::ComputeToken = cert.ctoken();
2016-02-22 17:35:53 -05:00
}
else
{
Auth::GuidKey.Free();
}
if (!Auth::GuidKey.IsValid())
{
Auth::GuidToken.Clear();
2016-04-11 07:32:11 -04:00
Auth::ComputeToken.Clear();
Auth::GuidKey = Utils::Cryptography::ECC::GenerateKey(512);
2016-02-22 17:35:53 -05:00
Auth::StoreKey();
}
}
uint32_t Auth::GetSecurityLevel()
{
return Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.GetPublicKey());
}
2016-02-22 20:03:05 -05:00
void Auth::IncreaseSecurityLevel(uint32_t level, std::string command)
{
if (Auth::GetSecurityLevel() >= level) return;
if (!Auth::TokenContainer.generating)
{
Auth::TokenContainer.cancel = false;
Auth::TokenContainer.targetLevel = level;
Auth::TokenContainer.command = command;
// Open menu
Command::Execute("openmenu security_increase_popmenu", true);
// Start thread
2016-06-02 09:11:31 -04:00
Auth::TokenContainer.thread = std::thread([&level] ()
2016-02-22 20:03:05 -05:00
{
Auth::TokenContainer.generating = true;
2016-02-23 07:39:38 -05:00
Auth::TokenContainer.hashes = 0;
2016-02-22 20:03:05 -05:00
Auth::TokenContainer.startTime = Game::Com_Milliseconds();
2016-04-11 07:32:11 -04:00
Auth::IncrementToken(Auth::GuidToken, Auth::ComputeToken, Auth::GuidKey.GetPublicKey(), Auth::TokenContainer.targetLevel, &Auth::TokenContainer.cancel, &Auth::TokenContainer.hashes);
2016-02-22 20:03:05 -05:00
Auth::TokenContainer.generating = false;
if (Auth::TokenContainer.cancel)
{
Logger::Print("Token incrementation thread terminated\n");
}
});
}
}
2016-02-22 17:35:53 -05:00
uint32_t Auth::GetZeroBits(Utils::Cryptography::Token token, std::string publicKey)
{
std::string message = publicKey + token.ToString();
std::string hash = Utils::Cryptography::SHA512::Compute(message, false);
uint32_t bits = 0;
for (unsigned int i = 0; i < hash.size(); ++i)
{
if (hash[i] == '\0')
{
bits += 8;
continue;
}
uint8_t value = static_cast<uint8_t>(hash[i]);
for (int j = 7; j >= 0; --j)
{
if ((value >> j) & 1)
{
return bits;
}
bits++;
}
}
return bits;
}
2016-04-11 07:32:11 -04:00
void Auth::IncrementToken(Utils::Cryptography::Token& token, Utils::Cryptography::Token& computeToken, std::string publicKey, uint32_t zeroBits, bool* cancel, uint64_t* count)
2016-02-22 17:35:53 -05:00
{
if (zeroBits > 512) return; // Not possible, due to SHA512
2016-04-11 07:32:11 -04:00
if (computeToken < token)
{
computeToken = token;
}
2016-02-22 17:35:53 -05:00
// Check if we already have the desired security level
2016-04-11 07:32:11 -04:00
uint32_t lastLevel = Auth::GetZeroBits(token, publicKey);
uint32_t level = lastLevel;
if (level >= zeroBits) return;
do
2016-02-22 17:35:53 -05:00
{
2016-04-11 07:32:11 -04:00
++computeToken;
2016-02-23 07:39:38 -05:00
if (count) ++(*count);
2016-04-11 07:32:11 -04:00
level = Auth::GetZeroBits(computeToken, publicKey);
// Store level if higher than the last one
if (level >= lastLevel)
{
2016-04-11 07:32:11 -04:00
token = computeToken;
lastLevel = level;
}
2016-02-22 20:03:05 -05:00
// Allow canceling that shit
if (cancel && *cancel) return;
2016-02-22 17:35:53 -05:00
}
while (level < zeroBits);
2016-02-22 17:35:53 -05:00
2016-04-11 07:32:11 -04:00
token = computeToken;
2016-02-22 17:35:53 -05:00
}
2016-02-21 13:57:56 -05:00
Auth::Auth()
{
2016-02-22 20:03:05 -05:00
Auth::TokenContainer.cancel = false;
Auth::TokenContainer.generating = false;
Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", "");
2016-02-22 17:35:53 -05:00
Auth::LoadKey(true);
2016-02-21 13:57:56 -05:00
// Only clients receive the auth request
if (!Dedicated::IsDedicated())
{
Network::Handle("xuidAuthReq", [] (Network::Address address, std::string data)
{
Logger::Print("Received XUID authentication request from %s\n", address.GetString());
// Only accept requests from the server we're connected to
if (address != *Game::connectedHost) return;
2016-02-22 07:37:13 -05:00
// Ensure our certificate is loaded
Steam::SteamUser()->GetSteamID();
2016-02-22 17:35:53 -05:00
if (!Auth::GuidKey.IsValid()) return;
2016-02-21 13:57:56 -05:00
Proto::Auth::Response response;
2016-02-22 17:35:53 -05:00
response.set_token(Auth::GuidToken.ToString());
response.set_publickey(Auth::GuidKey.GetPublicKey());
2016-04-11 07:32:11 -04:00
response.set_signature(Utils::Cryptography::ECC::SignMessage(Auth::GuidKey, data));
2016-02-21 13:57:56 -05:00
Network::SendCommand(address, "xuidAuthResp", response.SerializeAsString());
});
}
Network::Handle("xuidAuthResp", [] (Network::Address address, std::string data)
{
Logger::Print("Received XUID authentication response from %s\n", address.GetString());
for (int i = 0; i < *Game::svs_numclients; i++)
{
Game::client_t* client = &Game::svs_clients[i];
Auth::AuthInfo* info = &Auth::ClientAuthInfo[i];
if (client->state >= 3 && address == client->adr && info->state == Auth::STATE_NEGOTIATING)
{
2016-02-22 07:37:13 -05:00
Proto::Auth::Response response;
2016-02-21 13:57:56 -05:00
unsigned int id = static_cast<unsigned int>(~0x110000100000000 & client->steamid);
2016-02-22 07:37:13 -05:00
// Check if response is valid
2016-02-22 17:35:53 -05:00
if (!response.ParseFromString(data) || response.signature().empty() || response.publickey().empty() || response.token().empty())
2016-02-22 07:37:13 -05:00
{
info->state = Auth::STATE_INVALID;
Game::SV_KickClientError(client, "XUID authentication response was invalid!");
}
2016-02-21 13:57:56 -05:00
// Check if guid matches the certificate
2016-04-11 07:32:11 -04:00
else if (id != (Utils::Cryptography::JenkinsOneAtATime::Compute(response.publickey().data(), response.publickey().size()) & ~0x80000000))
2016-02-21 13:57:56 -05:00
{
info->state = Auth::STATE_INVALID;
Game::SV_KickClientError(client, "XUID doesn't match the certificate!");
}
2016-02-22 07:37:13 -05:00
// Verify GUID using the signature and certificate
2016-02-21 13:57:56 -05:00
else
{
info->publicKey.Set(response.publickey());
2016-04-11 07:32:11 -04:00
if (Utils::Cryptography::ECC::VerifyMessage(info->publicKey, info->challenge, response.signature()))
2016-02-21 13:57:56 -05:00
{
2016-02-22 17:35:53 -05:00
uint32_t ourLevel = static_cast<uint32_t>(Dvar::Var("sv_securityLevel").Get<int>());
uint32_t userLevel = Auth::GetZeroBits(response.token(), response.publickey());
if (userLevel >= ourLevel)
{
info->state = Auth::STATE_VALID;
2016-02-22 20:31:32 -05:00
Logger::Print("Verified XUID %llX (%d) from %s\n", client->steamid, userLevel, address.GetString());
2016-02-22 17:35:53 -05:00
}
else
{
info->state = Auth::STATE_INVALID;
2016-05-14 09:32:43 -04:00
Game::SV_KickClientError(client, Utils::VA("Your security level (%d) is lower than the server's security level (%d)", userLevel, ourLevel));
2016-02-22 17:35:53 -05:00
}
2016-02-21 13:57:56 -05:00
}
else
{
info->state = Auth::STATE_INVALID;
Game::SV_KickClientError(client, "Challenge signature was invalid!");
}
}
2016-02-22 07:37:13 -05:00
break;
2016-02-21 13:57:56 -05:00
}
}
});
// Install frame handlers
2016-03-01 07:37:51 -05:00
QuickPatch::OnFrame(Auth::Frame);
2016-02-21 13:57:56 -05:00
2016-02-22 17:35:53 -05:00
// Register dvar
2016-02-29 09:59:32 -05:00
Dvar::Register<int>("sv_securityLevel", 23, 0, 512, Game::dvar_flag::DVAR_FLAG_SERVERINFO, "Security level for GUID certificates (POW)");
2016-02-22 17:35:53 -05:00
2016-03-10 16:51:26 -05:00
#ifndef DEBUG
2016-02-21 13:57:56 -05:00
// Install registration hook
Utils::Hook(0x478A12, Auth::RegisterClientStub, HOOK_JUMP).Install()->Quick();
2016-03-10 16:51:26 -05:00
#endif
2016-02-22 17:35:53 -05:00
// Guid command
Command::Add("guid", [] (Command::Params params)
{
Logger::Print("Your guid: %llX\n", Steam::SteamUser()->GetSteamID().Bits);
});
Command::Add("securityLevel", [] (Command::Params params)
{
if (params.Length() < 2)
{
Logger::Print("Your current security level is %d\n", Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.GetPublicKey()));
2016-02-22 20:03:05 -05:00
Logger::Print("Your security token is: %s\n", Utils::DumpHex(Auth::GuidToken.ToString(), "").data());
2016-04-11 07:32:11 -04:00
Logger::Print("Your computation token is: %s\n", Utils::DumpHex(Auth::ComputeToken.ToString(), "").data());
2016-02-22 17:35:53 -05:00
}
else
{
uint32_t level = static_cast<uint32_t>(atoi(params[1]));
2016-02-22 20:03:05 -05:00
Auth::IncreaseSecurityLevel(level);
2016-02-22 17:35:53 -05:00
}
2016-02-22 20:03:05 -05:00
});
2016-02-22 20:03:05 -05:00
UIScript::Add("security_increase_cancel", [] ()
{
Auth::TokenContainer.cancel = true;
Logger::Print("Token incrementation process canceled!\n");
2016-02-22 17:35:53 -05:00
});
2016-02-21 13:57:56 -05:00
}
Auth::~Auth()
{
2016-02-22 20:03:05 -05:00
Auth::TokenContainer.cancel = true;
Auth::TokenContainer.generating = false;
// Terminate thread
2016-06-02 09:11:31 -04:00
if (Auth::TokenContainer.thread.joinable())
2016-02-22 20:03:05 -05:00
{
2016-06-02 09:11:31 -04:00
Auth::TokenContainer.thread.join();
2016-02-22 20:03:05 -05:00
}
2016-02-22 17:35:53 -05:00
Auth::StoreKey();
2016-02-21 13:57:56 -05:00
}
2016-04-11 07:32:11 -04:00
bool Auth::UnitTest()
{
bool success = true;
printf("Testing logical token operators:\n");
Utils::Cryptography::Token token1;
Utils::Cryptography::Token token2;
++token1, token2++; // Test incrementation operator
printf("Operator == : ");
if (token1 == token2 && !(++token1 == token2)) printf("Success\n");
else
{
printf("Error\n");
success = false;
}
printf("Operator != : ");
if (token1 != token2 && !(++token2 != token1)) printf("Success\n");
else
{
printf("Error\n");
success = false;
}
printf("Operator >= : ");
if (token1 >= token2 && ++token1 >= token2) printf("Success\n");
else
{
printf("Error\n");
success = false;
}
printf("Operator > : ");
if (token1 > token2) printf("Success\n");
else
{
printf("Error\n");
success = false;
}
printf("Operator <= : ");
if (token1 <= ++token2 && token1 <= ++token2) printf("Success\n");
else
{
printf("Error\n");
success = false;
}
printf("Operator < : ");
if (token1 < token2) printf("Success\n");
else
{
printf("Error\n");
success = false;
}
return success;
}
2016-02-21 13:57:56 -05:00
}