2022-02-27 12:53:44 +00:00
# include <STDInclude.hpp>
2023-01-03 12:16:44 +00:00
# include <Utils/InfoString.hpp>
# include <proto/auth.pb.h>
2023-04-17 14:47:29 +02:00
# include "Auth.hpp"
2022-12-26 13:07:24 +01:00
# include "Bans.hpp"
2023-04-16 11:27:19 +02:00
# include "Bots.hpp"
2023-04-17 14:47:29 +02:00
# include "Friends.hpp"
# include "Toast.hpp"
2017-01-19 22:23:59 +01:00
namespace Components
{
Auth : : TokenIncrementing Auth : : TokenContainer ;
Utils : : Cryptography : : Token Auth : : GuidToken ;
Utils : : Cryptography : : Token Auth : : ComputeToken ;
Utils : : Cryptography : : ECC : : Key Auth : : GuidKey ;
2022-02-26 22:50:53 +00:00
std : : vector < std : : uint64_t > Auth : : BannedUids =
{
2024-02-03 00:31:03 +01:00
// No longer necessary
2024-02-04 19:13:47 +01:00
0xf4d2c30b712ac6e3 ,
2020-08-08 12:19:55 +02:00
0xf7e33c4081337fa3 ,
2023-10-28 22:18:13 +02:00
0x6f5597f103cc50e9 ,
2024-02-04 19:13:47 +01:00
0xecd542eee54ffccf ,
0xA46B84C54694FD5B ,
0xECD542EEE54FFCCF ,
2019-10-03 09:10:00 +02:00
} ;
2023-04-06 19:57:40 +02:00
bool Auth : : HasAccessToReservedSlot ;
2024-02-03 00:31:03 +01:00
2017-01-19 22:23:59 +01:00
void Auth : : Frame ( )
{
2023-04-06 19:57:40 +02:00
if ( TokenContainer . generating )
2017-01-19 22:23:59 +01:00
{
static double mseconds = 0 ;
static Utils : : Time : : Interval interval ;
if ( interval . elapsed ( 500 ms ) )
{
interval . update ( ) ;
2023-04-06 19:57:40 +02:00
int diff = Game : : Sys_Milliseconds ( ) - TokenContainer . startTime ;
double hashPMS = ( TokenContainer . hashes * 1.0 ) / diff ;
double requiredHashes = std : : pow ( 2 , TokenContainer . targetLevel + 1 ) - TokenContainer . hashes ;
2017-01-19 22:23:59 +01:00
mseconds = requiredHashes / hashPMS ;
if ( mseconds < 0 ) mseconds = 0 ;
}
2024-02-03 00:31:03 +01:00
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 ( ) ) ) ;
2017-01-19 22:23:59 +01:00
}
2023-04-06 19:57:40 +02:00
else if ( TokenContainer . thread . joinable ( ) )
2017-01-19 22:23:59 +01:00
{
2023-04-06 19:57:40 +02:00
TokenContainer . thread . join ( ) ;
TokenContainer . generating = false ;
2017-01-19 22:23:59 +01:00
2023-04-06 19:57:40 +02:00
StoreKey ( ) ;
2024-02-03 00:31:03 +01:00
Logger : : Debug ( " Security level is {} " , GetSecurityLevel ( ) ) ;
2017-01-19 22:23:59 +01:00
Command : : Execute ( " closemenu security_increase_popmenu " , false ) ;
2023-04-06 19:57:40 +02:00
if ( ! TokenContainer . cancel )
2017-01-19 22:23:59 +01:00
{
2023-04-06 19:57:40 +02:00
if ( TokenContainer . command . empty ( ) )
2017-01-19 22:23:59 +01:00
{
2023-04-06 19:57:40 +02:00
Game : : ShowMessageBox ( Utils : : String : : VA ( " Your new security level is %d " , GetSecurityLevel ( ) ) , " Success " ) ;
2017-01-19 22:23:59 +01:00
}
else
{
2023-04-06 19:57:40 +02:00
Toast : : Show ( " cardicon_locked " , " Success " , Utils : : String : : VA ( " Your new security level is %d " , GetSecurityLevel ( ) ) , 5000 ) ;
Command : : Execute ( TokenContainer . command , false ) ;
2017-01-19 22:23:59 +01:00
}
}
2023-04-06 19:57:40 +02:00
TokenContainer . cancel = false ;
2017-01-19 22:23:59 +01:00
}
}
2023-05-03 00:10:14 +01:00
2023-04-06 17:28:57 +02:00
void Auth : : SendConnectDataStub ( Game : : netsrc_t sock , Game : : netadr_t adr , const char * format , int len )
2017-01-19 22:23:59 +01:00
{
// Ensure our certificate is loaded
Steam : : SteamUser ( ) - > GetSteamID ( ) ;
2023-04-06 19:57:40 +02:00
if ( ! GuidKey . isValid ( ) )
2017-01-19 22:23:59 +01:00
{
2022-06-12 23:07:53 +02:00
Logger : : Error ( Game : : ERR_SERVERDISCONNECT , " Connecting failed: Guid key is invalid! " ) ;
2017-01-19 22:23:59 +01:00
return ;
}
2023-04-06 19:57:40 +02:00
if ( std : : find ( BannedUids . begin ( ) , BannedUids . end ( ) , Steam : : SteamUser ( ) - > GetSteamID ( ) . bits ) ! = BannedUids . end ( ) )
2019-10-03 09:10:00 +02:00
{
2023-04-06 19:57:40 +02:00
GenerateKey ( ) ;
2022-06-12 23:07:53 +02:00
Logger : : Error ( Game : : ERR_SERVERDISCONNECT , " Your online profile is invalid. A new key has been generated. " ) ;
2019-10-03 09:10:00 +02:00
return ;
}
2023-05-03 00:10:14 +01:00
2017-01-19 22:23:59 +01:00
std : : string connectString ( format , len ) ;
Game : : SV_Cmd_TokenizeString ( connectString . data ( ) ) ;
Command : : ServerParams params ;
2022-03-17 18:50:20 +00:00
if ( params . size ( ) < 3 )
2017-01-19 22:23:59 +01:00
{
Game : : SV_Cmd_EndTokenizedString ( ) ;
2022-06-12 23:07:53 +02:00
Logger : : Error ( Game : : ERR_SERVERDISCONNECT , " Connecting failed: Command parsing error! " ) ;
2017-01-19 22:23:59 +01:00
return ;
}
Utils : : InfoString infostr ( params [ 2 ] ) ;
std : : string challenge = infostr . get ( " challenge " ) ;
if ( challenge . empty ( ) )
{
Game : : SV_Cmd_EndTokenizedString ( ) ;
2022-06-12 23:07:53 +02:00
Logger : : Error ( Game : : ERR_SERVERDISCONNECT , " Connecting failed: Challenge parsing error! " ) ;
2017-01-19 22:23:59 +01:00
return ;
}
2019-01-10 21:18:18 +01:00
if ( Steam : : Enabled ( ) & & ! Friends : : IsInvisible ( ) & & ! Dvar : : Var ( " cl_anonymous " ) . get < bool > ( ) & & Steam : : Proxy : : SteamUser_ )
2017-02-17 12:26:07 +01:00
{
2017-02-18 09:42:55 +01:00
infostr . set ( " realsteamId " , Utils : : String : : VA ( " %llX " , Steam : : Proxy : : SteamUser_ - > GetSteamID ( ) . bits ) ) ;
2017-02-17 12:26:07 +01:00
}
// Build new connect string
connectString . clear ( ) ;
connectString . append ( params [ 0 ] ) ;
connectString . append ( " " ) ;
connectString . append ( params [ 1 ] ) ;
connectString . append ( " " ) ;
connectString . append ( " \" " + infostr . build ( ) + " \" " ) ;
2017-01-19 22:23:59 +01:00
Game : : SV_Cmd_EndTokenizedString ( ) ;
2024-02-03 17:56:56 +01:00
if ( GuidToken . toString ( ) . empty ( ) & & adr . type ! = Game : : NA_LOOPBACK )
2024-02-03 15:34:00 +01:00
{
Game : : SV_Cmd_EndTokenizedString ( ) ;
Logger : : Error ( Game : : ERR_SERVERDISCONNECT , " Connecting failed: Empty GUID token! " ) ;
return ;
}
2017-01-19 22:23:59 +01:00
Proto : : Auth : : Connect connectData ;
2023-04-06 19:57:40 +02:00
connectData . set_token ( GuidToken . toString ( ) ) ;
connectData . set_publickey ( GuidKey . getPublicKey ( ) ) ;
connectData . set_signature ( Utils : : Cryptography : : ECC : : SignMessage ( GuidKey , challenge ) ) ;
2017-01-19 22:23:59 +01:00
connectData . set_infostring ( connectString ) ;
Network : : SendCommand ( sock , adr , " connect " , connectData . SerializeAsString ( ) ) ;
}
2017-02-01 13:44:25 +01:00
void Auth : : ParseConnectData ( Game : : msg_t * msg , Game : : netadr_t * addr )
2017-01-19 22:23:59 +01:00
{
Network : : Address address ( addr ) ;
// Parse proto data
Proto : : Auth : : Connect connectData ;
2022-08-13 17:19:45 +02:00
if ( msg - > cursize < = 12 | | ! connectData . ParseFromString ( std : : string ( reinterpret_cast < char * > ( & msg - > data [ 12 ] ) , msg - > cursize - 12 ) ) )
2017-01-19 22:23:59 +01:00
{
2024-01-12 09:53:58 +01:00
Logger : : PrintFail2Ban ( " Failed connect attempt from IP address: {} \n " , Network : : AdrToString ( address ) ) ;
2017-01-19 22:23:59 +01:00
Network : : Send ( address , " error \n Invalid connect packet! " ) ;
return ;
}
2017-01-20 14:36:52 +01:00
// Simply connect, if we're in debug mode, we ignore all security checks
# ifndef DEBUG
if ( address . isLoopback ( ) )
2017-01-19 22:23:59 +01:00
# endif
{
if ( ! connectData . infostring ( ) . empty ( ) )
{
Game : : SV_Cmd_EndTokenizedString ( ) ;
Game : : SV_Cmd_TokenizeString ( connectData . infostring ( ) . data ( ) ) ;
Game : : SV_DirectConnect ( * address . get ( ) ) ;
}
else
{
2024-01-12 09:53:58 +01:00
Logger : : PrintFail2Ban ( " Failed connect attempt from IP address: {} \n " , Network : : AdrToString ( address ) ) ;
2017-01-19 22:23:59 +01:00
Network : : Send ( address , " error \n Invalid infostring data! " ) ;
}
}
2017-01-20 14:36:52 +01:00
# ifndef DEBUG
2017-01-19 22:23:59 +01:00
else
{
// Validate proto data
if ( connectData . signature ( ) . empty ( ) | | connectData . publickey ( ) . empty ( ) | | connectData . token ( ) . empty ( ) | | connectData . infostring ( ) . empty ( ) )
{
2024-01-12 09:53:58 +01:00
Logger : : PrintFail2Ban ( " Failed connect attempt from IP address: {} \n " , Network : : AdrToString ( address ) ) ;
2017-01-19 22:23:59 +01:00
Network : : Send ( address , " error \n Invalid connect data! " ) ;
return ;
}
// Setup new cmd params
Game : : SV_Cmd_EndTokenizedString ( ) ;
Game : : SV_Cmd_TokenizeString ( connectData . infostring ( ) . data ( ) ) ;
// Access the params
Command : : ServerParams params ;
// Ensure there are enough params
2022-03-17 18:50:20 +00:00
if ( params . size ( ) < 3 )
2017-01-19 22:23:59 +01:00
{
2024-01-12 09:53:58 +01:00
Logger : : PrintFail2Ban ( " Failed connect attempt from IP address: {} \n " , Network : : AdrToString ( address ) ) ;
2017-01-19 22:23:59 +01:00
Network : : Send ( address , " error \n Invalid connect string! " ) ;
return ;
}
// Parse the infostring
2023-04-06 19:57:40 +02:00
Utils : : InfoString infostr ( params . get ( 2 ) ) ;
2017-01-19 22:23:59 +01:00
// Read the required data
2023-04-06 17:28:57 +02:00
const auto steamId = infostr . get ( " xuid " ) ;
const auto challenge = infostr . get ( " challenge " ) ;
2017-01-19 22:23:59 +01:00
if ( steamId . empty ( ) | | challenge . empty ( ) )
{
2024-01-12 09:53:58 +01:00
Logger : : PrintFail2Ban ( " Failed connect attempt from IP address: {} \n " , Network : : AdrToString ( address ) ) ;
2017-01-19 22:23:59 +01:00
Network : : Send ( address , " error \n Invalid connect data! " ) ;
return ;
}
// Parse the id
2022-02-26 23:02:04 +00:00
const auto xuid = std : : strtoull ( steamId . data ( ) , nullptr , 16 ) ;
2017-01-19 22:23:59 +01:00
SteamID guid ;
2017-02-18 09:42:55 +01:00
guid . bits = xuid ;
2017-01-19 22:23:59 +01:00
2024-02-03 00:31:03 +01:00
if ( Bans : : IsBanned ( { guid , address . getIP ( ) } ) )
2017-01-19 22:23:59 +01:00
{
2024-01-21 11:30:40 +01:00
Logger : : PrintFail2Ban ( " Failed connect attempt from IP address: {} \n " , Network : : AdrToString ( address ) ) ;
2017-01-19 22:23:59 +01:00
Network : : Send ( address , " error \n EXE_ERR_BANNED_PERM " ) ;
return ;
}
2023-04-06 19:57:40 +02:00
if ( std : : find ( BannedUids . begin ( ) , BannedUids . end ( ) , xuid ) ! = BannedUids . end ( ) )
2019-10-03 09:10:00 +02:00
{
Network : : Send ( address , " error \n Your online profile is invalid. Delete your players folder and restart ^2IW4x^7. " ) ;
return ;
}
2023-04-06 19:57:40 +02:00
if ( xuid ! = GetKeyHash ( connectData . publickey ( ) ) )
2017-01-19 22:23:59 +01:00
{
Network : : Send ( address , " error \n XUID doesn't match the certificate! " ) ;
return ;
}
// Verify the signature
Utils : : Cryptography : : ECC : : Key key ;
key . set ( connectData . publickey ( ) ) ;
if ( ! key . isValid ( ) | | ! Utils : : Cryptography : : ECC : : VerifyMessage ( key , challenge , connectData . signature ( ) ) )
{
Network : : Send ( address , " error \n Challenge signature was invalid! " ) ;
return ;
}
// Verify the security level
2022-02-26 22:50:53 +00:00
auto ourLevel = Dvar : : Var ( " sv_securityLevel " ) . get < unsigned int > ( ) ;
2023-04-06 19:57:40 +02:00
auto userLevel = GetZeroBits ( connectData . token ( ) , connectData . publickey ( ) ) ;
2017-01-19 22:23:59 +01:00
if ( userLevel < ourLevel )
{
Network : : Send ( address , Utils : : String : : VA ( " error \n Your security level (%d) is lower than the server's security level (%d) " , userLevel , ourLevel ) ) ;
return ;
}
2022-08-20 01:10:35 +02:00
Logger : : Debug ( " Verified XUID {:#X} ({}) from {} " , xuid , userLevel , address . getString ( ) ) ;
2017-01-19 22:23:59 +01:00
Game : : SV_DirectConnect ( * address . get ( ) ) ;
}
2017-01-20 14:36:52 +01:00
# endif
2017-01-19 22:23:59 +01:00
}
__declspec ( naked ) void Auth : : DirectConnectStub ( )
{
__asm
{
2017-02-01 13:44:25 +01:00
pushad
lea eax , [ esp + 20 h ]
push eax
2017-01-19 22:23:59 +01:00
push esi
2023-04-06 19:57:40 +02:00
call ParseConnectData
2017-01-19 22:23:59 +01:00
pop esi
2017-02-01 13:44:25 +01:00
pop eax
popad
2017-01-19 22:23:59 +01:00
2017-02-01 13:44:25 +01:00
push 6265F Eh
retn
2017-01-19 22:23:59 +01:00
}
}
2023-04-06 19:57:40 +02:00
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 ;
2023-04-16 11:36:06 +02:00
// This stub runs right before the 'server is full check' so we can call this here
2023-04-16 11:27:19 +02:00
Bots : : SV_DirectConnect_Full_Check ( ) ;
2023-04-06 19:57:40 +02:00
return value ;
}
2023-04-16 11:05:51 +02:00
__declspec ( naked ) void Auth : : DirectConnectPrivateClientStub ( )
2023-04-06 19:57:40 +02:00
{
2023-04-16 11:05:51 +02:00
__asm
2023-04-06 19:57:40 +02:00
{
2023-04-16 11:05:51 +02:00
push eax
mov al , HasAccessToReservedSlot
test al , al
pop eax
2023-04-16 10:47:02 +02:00
2023-04-16 11:05:51 +02:00
je noAccess
// Set the number of private clients to 0 if the client has the right password
xor eax , eax
jmp safeContinue
2024-02-03 00:31:03 +01:00
noAccess :
mov eax , dword ptr [ edx + 0x10 ]
2023-04-16 11:05:51 +02:00
2024-02-03 00:31:03 +01:00
safeContinue :
// Game code skipped by hook
add esp , 0xC
2023-04-16 11:05:51 +02:00
2024-02-03 00:31:03 +01:00
push 0x460FB3
ret
2023-04-16 11:05:51 +02:00
}
2023-04-06 19:57:40 +02:00
}
2024-02-04 19:21:50 +01:00
std : : string Auth : : GetGUIDFilePath ( )
{
const auto appdata = Components : : FileSystem : : GetAppdataPath ( ) ;
Utils : : IO : : CreateDir ( appdata . string ( ) ) ;
const auto guidPath = appdata / " guid.dat " ;
return guidPath . string ( ) ;
}
2024-01-21 11:30:40 +01:00
void ClientConnectFailedStub ( Game : : netsrc_t sock , Game : : netadr_t adr , const char * data )
{
Logger : : PrintFail2Ban ( " Failed connect attempt from IP address: {} \n " , Network : : AdrToString ( adr ) ) ;
Game : : NET_OutOfBandPrint ( sock , adr , data ) ;
}
2018-12-17 14:29:18 +01:00
unsigned __int64 Auth : : GetKeyHash ( const std : : string & key )
2017-01-19 22:23:59 +01:00
{
std : : string hash = Utils : : Cryptography : : SHA1 : : Compute ( key ) ;
if ( hash . size ( ) > = 8 )
{
return * reinterpret_cast < unsigned __int64 * > ( const_cast < char * > ( hash . data ( ) ) ) ;
}
return 0 ;
}
unsigned __int64 Auth : : GetKeyHash ( )
{
2023-04-06 19:57:40 +02:00
LoadKey ( ) ;
return GetKeyHash ( GuidKey . getPublicKey ( ) ) ;
2017-01-19 22:23:59 +01:00
}
void Auth : : StoreKey ( )
{
2023-04-06 19:57:40 +02:00
if ( ! Dedicated : : IsEnabled ( ) & & ! ZoneBuilder : : IsEnabled ( ) & & GuidKey . isValid ( ) )
2017-01-19 22:23:59 +01:00
{
Proto : : Auth : : Certificate cert ;
2023-04-06 19:57:40 +02:00
cert . set_token ( GuidToken . toString ( ) ) ;
cert . set_ctoken ( ComputeToken . toString ( ) ) ;
cert . set_privatekey ( GuidKey . serialize ( PK_PRIVATE ) ) ;
2024-02-04 19:21:50 +01:00
const auto guidPath = GetGUIDFilePath ( ) ;
Utils : : IO : : WriteFile ( guidPath , cert . SerializeAsString ( ) ) ;
2017-01-19 22:23:59 +01:00
}
}
2019-10-03 09:16:26 +02:00
void Auth : : GenerateKey ( )
{
2023-04-06 19:57:40 +02:00
GuidToken . clear ( ) ;
ComputeToken . clear ( ) ;
2024-02-04 19:13:47 +01:00
GuidKey = Utils : : Cryptography : : ECC : : GenerateKey ( 512 , GetMachineEntropy ( ) ) ;
2023-04-06 19:57:40 +02:00
StoreKey ( ) ;
2019-10-03 09:16:26 +02:00
}
2017-01-19 22:23:59 +01:00
void Auth : : LoadKey ( bool force )
{
if ( Dedicated : : IsEnabled ( ) | | ZoneBuilder : : IsEnabled ( ) ) return ;
2023-04-06 19:57:40 +02:00
if ( ! force & & GuidKey . isValid ( ) ) return ;
2017-01-19 22:23:59 +01:00
2024-02-04 19:21:50 +01:00
const auto guidPath = GetGUIDFilePath ( ) ;
2024-02-04 19:13:47 +01:00
# ifndef REGENERATE_INVALID_KEY
// Migrate old file
const auto oldGuidPath = " players/guid.dat " ;
if ( Utils : : IO : : FileExists ( oldGuidPath ) )
{
2024-02-04 19:21:50 +01:00
if ( MoveFileA ( oldGuidPath , guidPath . data ( ) ) )
2024-02-04 19:13:47 +01:00
{
Utils : : IO : : RemoveFile ( oldGuidPath ) ;
}
}
# endif
2024-02-04 19:21:50 +01:00
const auto guidFile = Utils : : IO : : ReadFile ( guidPath ) ;
2024-02-04 19:13:47 +01:00
2017-01-19 22:23:59 +01:00
Proto : : Auth : : Certificate cert ;
2024-02-04 19:13:47 +01:00
if ( cert . ParseFromString ( guidFile ) )
2017-01-19 22:23:59 +01:00
{
2023-04-06 19:57:40 +02:00
GuidKey . deserialize ( cert . privatekey ( ) ) ;
GuidToken = cert . token ( ) ;
ComputeToken = cert . ctoken ( ) ;
2024-02-04 19:13:47 +01:00
}
2017-01-19 22:23:59 +01:00
else
{
2023-04-06 19:57:40 +02:00
GuidKey . free ( ) ;
2017-01-19 22:23:59 +01:00
}
2024-02-03 15:34:00 +01:00
if ( GuidKey . isValid ( ) )
{
2024-02-04 19:13:47 +01:00
# ifdef REGENERATE_INVALID_KEY
auto machineKey = Utils : : Cryptography : : ECC : : GenerateKey ( 512 , GetMachineEntropy ( ) ) ;
if ( GetKeyHash ( machineKey . getPublicKey ( ) ) ! = GetKeyHash ( ) )
2024-02-03 15:34:00 +01:00
{
2024-02-04 19:13:47 +01:00
// kill! The user has changed machine or copied files from another
Auth : : GenerateKey ( ) ;
2024-02-03 15:34:00 +01:00
}
2024-02-04 19:13:47 +01:00
# endif
//All good, nothing to do
2024-02-03 15:34:00 +01:00
}
else
{
2019-10-03 09:16:26 +02:00
Auth : : GenerateKey ( ) ;
2024-02-03 15:34:00 +01:00
}
}
2017-01-19 22:23:59 +01:00
uint32_t Auth : : GetSecurityLevel ( )
{
2024-02-03 15:34:00 +01:00
return GuidToken . toString ( ) . empty ( ) ? 0 : GetZeroBits ( GuidToken , GuidKey . getPublicKey ( ) ) ;
2017-01-19 22:23:59 +01:00
}
2018-12-17 14:29:18 +01:00
void Auth : : IncreaseSecurityLevel ( uint32_t level , const std : : string & command )
2017-01-19 22:23:59 +01:00
{
2023-04-06 19:57:40 +02:00
if ( GetSecurityLevel ( ) > = level ) return ;
2017-01-19 22:23:59 +01:00
2023-04-06 19:57:40 +02:00
if ( ! TokenContainer . generating )
2017-01-19 22:23:59 +01:00
{
2023-04-06 19:57:40 +02:00
TokenContainer . cancel = false ;
TokenContainer . targetLevel = level ;
TokenContainer . command = command ;
2017-01-19 22:23:59 +01:00
// Open menu
Command : : Execute ( " openmenu security_increase_popmenu " , true ) ;
// Start thread
2023-04-06 19:57:40 +02:00
TokenContainer . thread = std : : thread ( [ & level ] ( )
2017-01-19 22:23:59 +01:00
{
2024-02-03 00:31:03 +01:00
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 ( TokenContainer . cancel )
{
Logger : : Print ( " Token incrementation thread terminated \n " ) ;
}
} ) ;
2017-01-19 22:23:59 +01:00
}
}
2018-12-17 14:29:18 +01:00
uint32_t Auth : : GetZeroBits ( Utils : : Cryptography : : Token token , const std : : string & publicKey )
2017-01-19 22:23:59 +01:00
{
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 ;
}
2018-12-17 14:29:18 +01:00
void Auth : : IncrementToken ( Utils : : Cryptography : : Token & token , Utils : : Cryptography : : Token & computeToken , const std : : string & publicKey , uint32_t zeroBits , bool * cancel , uint64_t * count )
2017-01-19 22:23:59 +01:00
{
if ( zeroBits > 512 ) return ; // Not possible, due to SHA512
if ( computeToken < token )
{
computeToken = token ;
}
// Check if we already have the desired security level
2024-02-03 15:34:00 +01:00
uint32_t lastLevel = token . toString ( ) . empty ( ) ? 0 : GetZeroBits ( token , publicKey ) ;
2017-01-19 22:23:59 +01:00
uint32_t level = lastLevel ;
if ( level > = zeroBits ) return ;
do
{
+ + computeToken ;
if ( count ) + + ( * count ) ;
2023-04-06 19:57:40 +02:00
level = GetZeroBits ( computeToken , publicKey ) ;
2017-01-19 22:23:59 +01:00
// Store level if higher than the last one
if ( level > = lastLevel )
{
token = computeToken ;
lastLevel = level ;
}
// Allow canceling that shit
if ( cancel & & * cancel ) return ;
2017-04-29 00:18:51 +02:00
} while ( level < zeroBits ) ;
2017-01-19 22:23:59 +01:00
token = computeToken ;
}
2024-02-04 19:13:47 +01:00
// 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 )
{
2024-02-11 15:42:51 +01:00
case IF_TYPE_IEEE80211 :
case MIB_IF_TYPE_ETHERNET :
{
2024-02-04 19:13:47 +01:00
2024-02-11 15:42:51 +01:00
std : : string macAddress { } ;
for ( size_t i = 0 ; i < ARRAYSIZE ( pIpAdapterInfo - > Address ) ; i + + )
{
entropy + = std : : to_string ( pIpAdapterInfo - > Address [ i ] ) ;
}
2024-02-04 19:13:47 +01:00
2024-02-11 15:42:51 +01:00
break ;
2024-02-04 19:13:47 +01:00
}
}
2024-02-11 15:42:51 +01:00
pIpAdapterInfo = pIpAdapterInfo - > Next ;
2024-02-04 19:13:47 +01:00
}
}
// 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 ;
}
}
2017-01-19 22:23:59 +01:00
Auth : : Auth ( )
{
2023-04-06 19:57:40 +02:00
TokenContainer . cancel = false ;
TokenContainer . generating = false ;
HasAccessToReservedSlot = false ;
2017-01-19 22:23:59 +01:00
Localization : : Set ( " MPUI_SECURITY_INCREASE_MESSAGE " , " " ) ;
// Load the key
2023-04-06 19:57:40 +02:00
LoadKey ( true ) ;
2017-01-19 22:23:59 +01:00
Steam : : SteamUser ( ) - > GetSteamID ( ) ;
2023-04-06 19:57:40 +02:00
Scheduler : : Loop ( Frame , Scheduler : : Pipeline : : MAIN ) ;
2017-01-19 22:23:59 +01:00
// Register dvar
2022-07-02 19:52:57 +02:00
Dvar : : Register < int > ( " sv_securityLevel " , 23 , 0 , 512 , Game : : DVAR_SERVERINFO , " Security level for GUID certificates (POW) " ) ;
2017-01-19 22:23:59 +01:00
// Install registration hook
2023-04-06 19:57:40 +02:00
Utils : : Hook ( 0x6265F9 , DirectConnectStub , HOOK_JUMP ) . install ( ) - > quick ( ) ;
Utils : : Hook ( 0x460EF5 , Info_ValueForKeyStub , HOOK_CALL ) . install ( ) - > quick ( ) ;
2023-04-16 11:05:51 +02:00
Utils : : Hook ( 0x460FAD , DirectConnectPrivateClientStub , HOOK_JUMP ) . install ( ) - > quick ( ) ;
Utils : : Hook : : Nop ( 0x460FAD + 5 , 1 ) ;
2023-04-06 19:57:40 +02:00
Utils : : Hook ( 0x41D3E3 , SendConnectDataStub , HOOK_CALL ) . install ( ) - > quick ( ) ;
2017-01-19 22:23:59 +01:00
2024-01-21 11:30:40 +01:00
// Hook for Fail2Ban (Hook near client connect to detect password brute forcing)
Utils : : Hook ( 0x4611CA , ClientConnectFailedStub , HOOK_CALL ) . install ( ) - > quick ( ) ; // NET_OutOfBandPrint (Grab IP super easy)
2017-01-19 22:23:59 +01:00
// 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.
2023-04-06 19:57:40 +02:00
Utils : : Hook : : Set < std : : uint32_t > ( 0x4D0D60 , 0xC301B0 ) ;
2017-01-19 22:23:59 +01:00
// Guid command
2023-03-23 14:08:21 +00:00
Command : : Add ( " guid " , [ ]
2024-02-03 00:31:03 +01:00
{
Logger : : Print ( " Your guid: {:#X} \n " , Steam : : SteamUser ( ) - > GetSteamID ( ) . bits ) ;
} ) ;
2017-01-19 22:23:59 +01:00
if ( ! Dedicated : : IsEnabled ( ) & & ! ZoneBuilder : : IsEnabled ( ) )
{
2023-05-06 14:20:56 +01:00
Command : : Add ( " securityLevel " , [ ] ( const Command : : Params * params )
2017-01-19 22:23:59 +01:00
{
2024-02-03 00:31:03 +01:00
if ( params - > size ( ) < 2 )
{
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 ( 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 ) ;
IncreaseSecurityLevel ( level ) ;
}
} ) ;
2017-01-19 22:23:59 +01:00
}
2022-08-24 16:38:14 +02:00
UIScript : : Add ( " security_increase_cancel " , [ ] ( [[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game : : uiInfo_s * info )
2024-02-03 00:31:03 +01:00
{
TokenContainer . cancel = true ;
Logger : : Print ( " Token incrementation process canceled! \n " ) ;
} ) ;
2017-01-19 22:23:59 +01:00
}
Auth : : ~ Auth ( )
2017-01-23 22:06:50 +01:00
{
2023-04-06 19:57:40 +02:00
StoreKey ( ) ;
2017-01-23 22:06:50 +01:00
}
void Auth : : preDestroy ( )
2017-01-19 22:23:59 +01:00
{
2023-04-06 19:57:40 +02:00
TokenContainer . cancel = true ;
TokenContainer . generating = false ;
2017-01-19 22:23:59 +01:00
2023-04-06 19:57:40 +02:00
if ( TokenContainer . thread . joinable ( ) )
2017-01-19 22:23:59 +01:00
{
2023-04-06 19:57:40 +02:00
TokenContainer . thread . join ( ) ;
2017-01-19 22:23:59 +01: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 ;
}
}