New GUID generation & migration

This commit is contained in:
Louvenarde 2024-02-04 19:13:47 +01:00
parent 229861cde9
commit 4ff1337358
5 changed files with 130 additions and 98 deletions

View File

@ -20,10 +20,12 @@ namespace Components
std::vector<std::uint64_t> Auth::BannedUids = std::vector<std::uint64_t> Auth::BannedUids =
{ {
// No longer necessary // No longer necessary
/* 0xf4d2c30b712ac6e3, 0xf4d2c30b712ac6e3,
0xf7e33c4081337fa3, 0xf7e33c4081337fa3,
0x6f5597f103cc50e9, 0x6f5597f103cc50e9,
0xecd542eee54ffccf,*/ 0xecd542eee54ffccf,
0xA46B84C54694FD5B,
0xECD542EEE54FFCCF,
}; };
bool Auth::HasAccessToReservedSlot; bool Auth::HasAccessToReservedSlot;
@ -365,7 +367,7 @@ namespace Components
{ {
GuidToken.clear(); GuidToken.clear();
ComputeToken.clear(); ComputeToken.clear();
GuidKey = Utils::Cryptography::ECC::GenerateKey(512); GuidKey = Utils::Cryptography::ECC::GenerateKey(512, GetMachineEntropy());
StoreKey(); StoreKey();
} }
@ -374,21 +376,32 @@ namespace Components
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return; if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
if (!force && GuidKey.isValid()) return; if (!force && GuidKey.isValid()) return;
// We no longer read the key from disk const auto appdata = Components::FileSystem::GetAppdataPath();
// While having obvious advantages to palliate the fact that some users are not playing on Steam, Utils::IO::CreateDir(appdata.string());
// it is creating a lot of issues because GUID files get packaged with the game when people share it
// and it makes it harder for server owners to identify players uniquely const auto guidPath = appdata / "guid.dat";
// Note that we could store it in Appdata, but then it would be dissociated from the rest of player files,
// so for now we're doing something else: the key is generated uniquely from the machine's characteristics #ifndef REGENERATE_INVALID_KEY
// It is not (necessarily) stored and therefore, not loaded, so it could make it harder to evade bans without // Migrate old file
// using a custom client that would need regeneration at each update. const auto oldGuidPath = "players/guid.dat";
if (Utils::IO::FileExists(oldGuidPath))
{
if (MoveFileA(oldGuidPath, guidPath.string().data()))
{
Utils::IO::RemoveFile(oldGuidPath);
}
}
#endif
const auto guidFile = Utils::IO::ReadFile(guidPath.string());
Proto::Auth::Certificate cert; Proto::Auth::Certificate cert;
if (cert.ParseFromString(::Utils::IO::ReadFile("players/guid.dat"))) if (cert.ParseFromString(guidFile))
{ {
GuidKey.deserialize(cert.privatekey()); GuidKey.deserialize(cert.privatekey());
GuidToken = cert.token(); GuidToken = cert.token();
ComputeToken = cert.ctoken(); ComputeToken = cert.ctoken();
} }
else else
{ {
GuidKey.free(); GuidKey.free();
@ -396,16 +409,15 @@ namespace Components
if (GuidKey.isValid()) if (GuidKey.isValid())
{ {
auto machineKey = Utils::Cryptography::ECC::GenerateKey(512); #ifdef REGENERATE_INVALID_KEY
if (GetKeyHash(machineKey.getPublicKey()) == GetKeyHash()) auto machineKey = Utils::Cryptography::ECC::GenerateKey(512, GetMachineEntropy());
if (GetKeyHash(machineKey.getPublicKey()) != GetKeyHash())
{ {
//All good, nothing to do // kill! The user has changed machine or copied files from another
} Auth::GenerateKey();
else
{
// kill! The user has changed machine or copied files from another
Auth::GenerateKey();
} }
#endif
//All good, nothing to do
} }
else else
{ {
@ -512,6 +524,77 @@ namespace Components
token = computeToken; token = computeToken;
} }
// A somewhat hardware tied 48 bit value
std::string Auth::GetMachineEntropy()
{
std::string entropy{};
DWORD volumeID;
if (GetVolumeInformationA("C:\\",
NULL,
NULL,
&volumeID,
NULL,
NULL,
NULL,
NULL
))
{
// Drive info
entropy += std::to_string(volumeID);
}
// MAC Address
{
unsigned long outBufLen = 0;
DWORD dwResult = GetAdaptersInfo(NULL, &outBufLen);
if (dwResult == ERROR_BUFFER_OVERFLOW) // This is what we're expecting
{
// Now allocate a structure of the required size.
PIP_ADAPTER_INFO pIpAdapterInfo = reinterpret_cast<PIP_ADAPTER_INFO>(malloc(outBufLen));
dwResult = GetAdaptersInfo(pIpAdapterInfo, &outBufLen);
if (dwResult == ERROR_SUCCESS)
{
while (pIpAdapterInfo)
{
switch (pIpAdapterInfo->Type)
{
default:
pIpAdapterInfo = pIpAdapterInfo->Next;
continue;
case IF_TYPE_IEEE80211:
case MIB_IF_TYPE_ETHERNET:
{
std::string macAddress{};
for (size_t i = 0; i < ARRAYSIZE(pIpAdapterInfo->Address); i++)
{
entropy += std::to_string(pIpAdapterInfo->Address[i]);
}
break;
}
}
}
}
// Free before going next because clearly this is not working
free(pIpAdapterInfo);
}
}
if (entropy.empty())
{
// ultimate fallback
return std::to_string(Utils::Cryptography::Rand::GenerateLong());
}
else
{
return entropy;
}
}
Auth::Auth() Auth::Auth()
{ {
TokenContainer.cancel = false; TokenContainer.cancel = false;

View File

@ -24,6 +24,8 @@ namespace Components
static uint32_t GetZeroBits(Utils::Cryptography::Token token, const std::string& publicKey); static uint32_t GetZeroBits(Utils::Cryptography::Token token, const std::string& publicKey);
static void IncrementToken(Utils::Cryptography::Token& token, Utils::Cryptography::Token& computeToken, const std::string& publicKey, uint32_t zeroBits, bool* cancel = nullptr, uint64_t* count = nullptr); static void IncrementToken(Utils::Cryptography::Token& token, Utils::Cryptography::Token& computeToken, const std::string& publicKey, uint32_t zeroBits, bool* cancel = nullptr, uint64_t* count = nullptr);
static std::string GetMachineEntropy();
private: private:
class TokenIncrementing class TokenIncrementing

View File

@ -153,7 +153,7 @@ namespace Components
CoTaskMemFree(path); CoTaskMemFree(path);
}); });
return std::filesystem::path(path) / "xlabs"; return std::filesystem::path(path) / "iw4x";
} }
std::vector<std::string> FileSystem::GetFileList(const std::string& path, const std::string& extension) std::vector<std::string> FileSystem::GetFileList(const std::string& path, const std::string& extension)

View File

@ -11,77 +11,6 @@ namespace Utils
Rand::Initialize(); Rand::Initialize();
} }
std::string GetEntropy()
{
DWORD volumeID;
if (GetVolumeInformationA("C:\\",
NULL,
NULL,
&volumeID,
NULL,
NULL,
NULL,
NULL
))
{
// Drive info
return std::to_string(volumeID);
}
else
{
// Resort to mac address
unsigned long outBufLen = 0;
DWORD dwResult = GetAdaptersInfo(NULL, &outBufLen);
if (dwResult == ERROR_BUFFER_OVERFLOW) // This is what we're expecting
{
// Now allocate a structure of the required size.
PIP_ADAPTER_INFO pIpAdapterInfo = reinterpret_cast<PIP_ADAPTER_INFO>(malloc(outBufLen));
dwResult = GetAdaptersInfo(pIpAdapterInfo, &outBufLen);
if (dwResult == ERROR_SUCCESS)
{
while (pIpAdapterInfo)
{
switch (pIpAdapterInfo->Type)
{
default:
pIpAdapterInfo = pIpAdapterInfo->Next;
continue;
case IF_TYPE_IEEE80211:
case MIB_IF_TYPE_ETHERNET:
{
std::string macAddress{};
for (size_t i = 0; i < ARRAYSIZE(pIpAdapterInfo->Address); i++)
{
macAddress += std::to_string(pIpAdapterInfo->Address[i]);
}
free(pIpAdapterInfo);
return macAddress;
}
}
}
}
else
{
// :(
// Continue to fallback
}
// Free before going next because clearly this is not working
free(pIpAdapterInfo);
}
else
{
// No MAC, no C: drive? Come on
}
// ultimate fallback
return std::to_string(Rand::GenerateInt());
}
}
#pragma region Rand #pragma region Rand
prng_state Rand::State; prng_state Rand::State;
@ -98,6 +27,13 @@ namespace Utils
return std::string{ buffer, static_cast<std::size_t>(pos) }; return std::string{ buffer, static_cast<std::size_t>(pos) };
} }
std::uint64_t Rand::GenerateLong()
{
std::uint64_t number = 0;
fortuna_read(reinterpret_cast<std::uint8_t*>(&number), sizeof(number), &Rand::State);
return number;
}
std::uint32_t Rand::GenerateInt() std::uint32_t Rand::GenerateInt()
{ {
std::uint32_t number = 0; std::uint32_t number = 0;
@ -116,20 +52,30 @@ namespace Utils
#pragma region ECC #pragma region ECC
ECC::Key ECC::GenerateKey(int bits) ECC::Key ECC::GenerateKey(int bits, const std::string& entropy)
{ {
Key key; Key key;
ltc_mp = ltm_desc; ltc_mp = ltm_desc;
int descriptorIndex = register_prng(&chacha20_prng_desc);
// allocate state if (entropy.empty())
{ {
register_prng(&sprng_desc);
const auto result = ecc_make_key(nullptr, find_prng("sprng"), bits / 8, key.getKeyPtr());
if (result != CRYPT_OK)
{
Components::Logger::PrintError(Game::conChannel_t::CON_CHANNEL_ERROR, "There was an issue generating a secured random key! Please contact support");
}
}
else
{
int descriptorIndex = register_prng(&chacha20_prng_desc);
// allocate state
prng_state* state = new prng_state(); prng_state* state = new prng_state();
chacha20_prng_start(state); chacha20_prng_start(state);
const auto entropy = Cryptography::GetEntropy();
chacha20_prng_add_entropy(reinterpret_cast<const unsigned char*>(entropy.data()), entropy.size(), state); chacha20_prng_add_entropy(reinterpret_cast<const unsigned char*>(entropy.data()), entropy.size(), state);
chacha20_prng_ready(state); chacha20_prng_ready(state);

View File

@ -132,6 +132,7 @@ namespace Utils
{ {
public: public:
static std::string GenerateChallenge(); static std::string GenerateChallenge();
static std::uint64_t GenerateLong();
static std::uint32_t GenerateInt(); static std::uint32_t GenerateInt();
static void Initialize(); static void Initialize();
@ -236,7 +237,7 @@ namespace Utils
std::shared_ptr<ecc_key> keyStorage; std::shared_ptr<ecc_key> keyStorage;
}; };
static Key GenerateKey(int bits); static Key GenerateKey(int bits, const std::string& entropy = {});
static std::string SignMessage(Key key, const std::string& message); static std::string SignMessage(Key key, const std::string& message);
static bool VerifyMessage(Key key, const std::string& message, const std::string& signature); static bool VerifyMessage(Key key, const std::string& message, const std::string& signature);
}; };