From 4ff13373589f961d25ba485b1c582fa4a1671925 Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Sun, 4 Feb 2024 19:13:47 +0100 Subject: [PATCH] New GUID generation & migration --- src/Components/Modules/Auth.cpp | 125 +++++++++++++++++++++----- src/Components/Modules/Auth.hpp | 2 + src/Components/Modules/FileSystem.cpp | 2 +- src/Utils/Cryptography.cpp | 96 +++++--------------- src/Utils/Cryptography.hpp | 3 +- 5 files changed, 130 insertions(+), 98 deletions(-) diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp index 97e8e3b4..08931f79 100644 --- a/src/Components/Modules/Auth.cpp +++ b/src/Components/Modules/Auth.cpp @@ -20,10 +20,12 @@ namespace Components std::vector Auth::BannedUids = { // No longer necessary - /* 0xf4d2c30b712ac6e3, + 0xf4d2c30b712ac6e3, 0xf7e33c4081337fa3, 0x6f5597f103cc50e9, - 0xecd542eee54ffccf,*/ + 0xecd542eee54ffccf, + 0xA46B84C54694FD5B, + 0xECD542EEE54FFCCF, }; bool Auth::HasAccessToReservedSlot; @@ -365,7 +367,7 @@ namespace Components { GuidToken.clear(); ComputeToken.clear(); - GuidKey = Utils::Cryptography::ECC::GenerateKey(512); + GuidKey = Utils::Cryptography::ECC::GenerateKey(512, GetMachineEntropy()); StoreKey(); } @@ -374,21 +376,32 @@ namespace Components if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return; if (!force && GuidKey.isValid()) return; - // We no longer read the key from disk - // While having obvious advantages to palliate the fact that some users are not playing on Steam, - // 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 - // 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 - // It is not (necessarily) stored and therefore, not loaded, so it could make it harder to evade bans without - // using a custom client that would need regeneration at each update. + const auto appdata = Components::FileSystem::GetAppdataPath(); + Utils::IO::CreateDir(appdata.string()); + + const auto guidPath = appdata / "guid.dat"; + +#ifndef REGENERATE_INVALID_KEY + // Migrate old file + 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; - if (cert.ParseFromString(::Utils::IO::ReadFile("players/guid.dat"))) + if (cert.ParseFromString(guidFile)) { GuidKey.deserialize(cert.privatekey()); GuidToken = cert.token(); ComputeToken = cert.ctoken(); - } + } else { GuidKey.free(); @@ -396,16 +409,15 @@ namespace Components if (GuidKey.isValid()) { - auto machineKey = Utils::Cryptography::ECC::GenerateKey(512); - if (GetKeyHash(machineKey.getPublicKey()) == GetKeyHash()) +#ifdef REGENERATE_INVALID_KEY + auto machineKey = Utils::Cryptography::ECC::GenerateKey(512, GetMachineEntropy()); + if (GetKeyHash(machineKey.getPublicKey()) != GetKeyHash()) { - //All good, nothing to do - } - else - { - // kill! The user has changed machine or copied files from another - Auth::GenerateKey(); + // kill! The user has changed machine or copied files from another + Auth::GenerateKey(); } +#endif + //All good, nothing to do } else { @@ -512,6 +524,77 @@ namespace Components 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(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() { TokenContainer.cancel = false; diff --git a/src/Components/Modules/Auth.hpp b/src/Components/Modules/Auth.hpp index 5ab438ce..4644281c 100644 --- a/src/Components/Modules/Auth.hpp +++ b/src/Components/Modules/Auth.hpp @@ -24,6 +24,8 @@ namespace Components 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 std::string GetMachineEntropy(); + private: class TokenIncrementing diff --git a/src/Components/Modules/FileSystem.cpp b/src/Components/Modules/FileSystem.cpp index eae77d1a..3880a6d2 100644 --- a/src/Components/Modules/FileSystem.cpp +++ b/src/Components/Modules/FileSystem.cpp @@ -153,7 +153,7 @@ namespace Components CoTaskMemFree(path); }); - return std::filesystem::path(path) / "xlabs"; + return std::filesystem::path(path) / "iw4x"; } std::vector FileSystem::GetFileList(const std::string& path, const std::string& extension) diff --git a/src/Utils/Cryptography.cpp b/src/Utils/Cryptography.cpp index cbfe3c82..06cbe1e9 100644 --- a/src/Utils/Cryptography.cpp +++ b/src/Utils/Cryptography.cpp @@ -11,77 +11,6 @@ namespace Utils 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(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 prng_state Rand::State; @@ -98,6 +27,13 @@ namespace Utils return std::string{ buffer, static_cast(pos) }; } + std::uint64_t Rand::GenerateLong() + { + std::uint64_t number = 0; + fortuna_read(reinterpret_cast(&number), sizeof(number), &Rand::State); + return number; + } + std::uint32_t Rand::GenerateInt() { std::uint32_t number = 0; @@ -116,20 +52,30 @@ namespace Utils #pragma region ECC - ECC::Key ECC::GenerateKey(int bits) + ECC::Key ECC::GenerateKey(int bits, const std::string& entropy) { Key key; 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(); chacha20_prng_start(state); - const auto entropy = Cryptography::GetEntropy(); chacha20_prng_add_entropy(reinterpret_cast(entropy.data()), entropy.size(), state); chacha20_prng_ready(state); diff --git a/src/Utils/Cryptography.hpp b/src/Utils/Cryptography.hpp index 36fbdd28..67734f81 100644 --- a/src/Utils/Cryptography.hpp +++ b/src/Utils/Cryptography.hpp @@ -132,6 +132,7 @@ namespace Utils { public: static std::string GenerateChallenge(); + static std::uint64_t GenerateLong(); static std::uint32_t GenerateInt(); static void Initialize(); @@ -236,7 +237,7 @@ namespace Utils std::shared_ptr 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 bool VerifyMessage(Key key, const std::string& message, const std::string& signature); };