2017-01-19 16:23:59 -05:00
# include "STDInclude.hpp"
namespace Components
{
Auth : : TokenIncrementing Auth : : TokenContainer ;
Utils : : Cryptography : : Token Auth : : GuidToken ;
Utils : : Cryptography : : Token Auth : : ComputeToken ;
Utils : : Cryptography : : ECC : : Key Auth : : GuidKey ;
void Auth : : Frame ( )
{
if ( Auth : : TokenContainer . generating )
{
static double mseconds = 0 ;
static Utils : : Time : : Interval interval ;
if ( interval . elapsed ( 500 ms ) )
{
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 ;
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 ( ) ) ) ;
}
else if ( Auth : : TokenContainer . thread . joinable ( ) )
{
Auth : : TokenContainer . thread . join ( ) ;
Auth : : TokenContainer . generating = false ;
Auth : : StoreKey ( ) ;
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 ( ) )
{
2017-02-06 15:09:41 -05:00
Game : : ShowMessageBox ( Utils : : String : : VA ( " Your new security level is %d " , Auth : : GetSecurityLevel ( ) ) , " Success " ) ;
2017-01-19 16:23:59 -05:00
}
else
{
Toast : : Show ( " cardicon_locked " , " Success " , Utils : : String : : VA ( " Your new security level is %d " , Auth : : GetSecurityLevel ( ) ) , 5000 ) ;
Command : : Execute ( Auth : : TokenContainer . command , false ) ;
}
}
Auth : : TokenContainer . cancel = false ;
}
}
void Auth : : SendConnectDataStub ( Game : : netsrc_t sock , Game : : netadr_t adr , const char * format , int len )
{
// Ensure our certificate is loaded
Steam : : SteamUser ( ) - > GetSteamID ( ) ;
if ( ! Auth : : GuidKey . isValid ( ) )
{
Logger : : SoftError ( " Connecting failed: Guid key is invalid! " ) ;
return ;
}
std : : string connectString ( format , len ) ;
Game : : SV_Cmd_TokenizeString ( connectString . data ( ) ) ;
Command : : ServerParams params ;
if ( params . length ( ) < 3 )
{
Game : : SV_Cmd_EndTokenizedString ( ) ;
Logger : : SoftError ( " Connecting failed: Command parsing error! " ) ;
return ;
}
Utils : : InfoString infostr ( params [ 2 ] ) ;
std : : string challenge = infostr . get ( " challenge " ) ;
if ( challenge . empty ( ) )
{
Game : : SV_Cmd_EndTokenizedString ( ) ;
Logger : : SoftError ( " Connecting failed: Challenge parsing error! " ) ;
return ;
}
2017-02-18 11:26:14 -05:00
if ( Steam : : Enabled ( ) & & ! Dvar : : Var ( " cl_anonymous " ) . get < bool > ( ) & & Steam : : Proxy : : SteamUser_ )
2017-02-17 06:26:07 -05:00
{
2017-02-18 03:42:55 -05:00
infostr . set ( " realsteamId " , Utils : : String : : VA ( " %llX " , Steam : : Proxy : : SteamUser_ - > GetSteamID ( ) . bits ) ) ;
2017-02-17 06:26:07 -05: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 16:23:59 -05:00
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_infostring ( connectString ) ;
Network : : SendCommand ( sock , adr , " connect " , connectData . SerializeAsString ( ) ) ;
}
2017-02-01 07:44:25 -05:00
void Auth : : ParseConnectData ( Game : : msg_t * msg , Game : : netadr_t * addr )
2017-01-19 16:23:59 -05:00
{
Network : : Address address ( addr ) ;
// Parse proto data
Proto : : Auth : : Connect connectData ;
if ( msg - > cursize < = 12 | | ! connectData . ParseFromString ( std : : string ( & msg - > data [ 12 ] , msg - > cursize - 12 ) ) )
{
Network : : Send ( address , " error \n Invalid connect packet! " ) ;
return ;
}
2017-01-20 08:36:52 -05:00
// Simply connect, if we're in debug mode, we ignore all security checks
# ifndef DEBUG
if ( address . isLoopback ( ) )
2017-01-19 16:23:59 -05:00
# endif
{
if ( ! connectData . infostring ( ) . empty ( ) )
{
Game : : SV_Cmd_EndTokenizedString ( ) ;
Game : : SV_Cmd_TokenizeString ( connectData . infostring ( ) . data ( ) ) ;
Game : : SV_DirectConnect ( * address . get ( ) ) ;
}
else
{
Network : : Send ( address , " error \n Invalid infostring data! " ) ;
}
}
2017-01-20 08:36:52 -05:00
# ifndef DEBUG
2017-01-19 16:23:59 -05:00
else
{
// Validate proto data
if ( connectData . signature ( ) . empty ( ) | | connectData . publickey ( ) . empty ( ) | | connectData . token ( ) . empty ( ) | | connectData . infostring ( ) . empty ( ) )
{
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
if ( params . length ( ) < 3 )
{
Network : : Send ( address , " error \n Invalid connect string! " ) ;
return ;
}
// Parse the infostring
Utils : : InfoString infostr ( params [ 2 ] ) ;
// Read the required data
std : : string steamId = infostr . get ( " xuid " ) ;
std : : string challenge = infostr . get ( " challenge " ) ;
if ( steamId . empty ( ) | | challenge . empty ( ) )
{
Network : : Send ( address , " error \n Invalid connect data! " ) ;
return ;
}
// Parse the id
unsigned __int64 xuid = strtoull ( steamId . data ( ) , nullptr , 16 ) ;
SteamID guid ;
2017-02-18 03:42:55 -05:00
guid . bits = xuid ;
2017-01-19 16:23:59 -05:00
if ( Bans : : IsBanned ( { guid , address . getIP ( ) } ) )
{
Network : : Send ( address , " error \n EXE_ERR_BANNED_PERM " ) ;
return ;
}
if ( xuid ! = Auth : : GetKeyHash ( connectData . publickey ( ) ) )
{
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
uint32_t ourLevel = static_cast < uint32_t > ( Dvar : : Var ( " sv_securityLevel " ) . get < int > ( ) ) ;
uint32_t userLevel = Auth : : GetZeroBits ( connectData . token ( ) , connectData . publickey ( ) ) ;
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 ;
}
Logger : : Print ( " Verified XUID %llX (%d) from %s \n " , xuid , userLevel , address . getCString ( ) ) ;
Game : : SV_DirectConnect ( * address . get ( ) ) ;
}
2017-01-20 08:36:52 -05:00
# endif
2017-01-19 16:23:59 -05:00
}
__declspec ( naked ) void Auth : : DirectConnectStub ( )
{
__asm
{
2017-02-01 07:44:25 -05:00
pushad
lea eax , [ esp + 20 h ]
push eax
2017-01-19 16:23:59 -05:00
push esi
call Auth : : ParseConnectData
pop esi
2017-02-01 07:44:25 -05:00
pop eax
popad
2017-01-19 16:23:59 -05:00
2017-02-01 07:44:25 -05:00
push 6265F Eh
retn
2017-01-19 16:23:59 -05:00
}
}
2018-12-17 08:29:18 -05:00
unsigned __int64 Auth : : GetKeyHash ( const std : : string & key )
2017-01-19 16:23:59 -05: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 ( )
{
Auth : : LoadKey ( ) ;
return Auth : : GetKeyHash ( Auth : : GuidKey . getPublicKey ( ) ) ;
}
void Auth : : StoreKey ( )
{
2017-02-25 09:58:43 -05:00
if ( ! Dedicated : : IsEnabled ( ) & & ! ZoneBuilder : : IsEnabled ( ) & & ! Monitor : : IsEnabled ( ) & & Auth : : GuidKey . isValid ( ) )
2017-01-19 16:23:59 -05:00
{
Proto : : Auth : : Certificate cert ;
cert . set_token ( Auth : : GuidToken . toString ( ) ) ;
cert . set_ctoken ( Auth : : ComputeToken . toString ( ) ) ;
cert . set_privatekey ( Auth : : GuidKey . serialize ( PK_PRIVATE ) ) ;
Utils : : IO : : WriteFile ( " players/guid.dat " , cert . SerializeAsString ( ) ) ;
}
}
void Auth : : LoadKey ( bool force )
{
if ( Dedicated : : IsEnabled ( ) | | ZoneBuilder : : IsEnabled ( ) ) return ;
if ( ! force & & Auth : : 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 ( ) ;
}
else
{
Auth : : GuidKey . free ( ) ;
}
if ( ! Auth : : GuidKey . isValid ( ) )
{
Auth : : GuidToken . clear ( ) ;
Auth : : ComputeToken . clear ( ) ;
Auth : : GuidKey = Utils : : Cryptography : : ECC : : GenerateKey ( 512 ) ;
Auth : : StoreKey ( ) ;
}
}
uint32_t Auth : : GetSecurityLevel ( )
{
return Auth : : GetZeroBits ( Auth : : GuidToken , Auth : : GuidKey . getPublicKey ( ) ) ;
}
2018-12-17 08:29:18 -05:00
void Auth : : IncreaseSecurityLevel ( uint32_t level , const std : : string & command )
2017-01-19 16:23:59 -05:00
{
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
2017-04-28 18:18:51 -04:00
Auth : : TokenContainer . thread = std : : thread ( [ & level ] ( )
2017-01-19 16:23:59 -05:00
{
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 ;
if ( Auth : : TokenContainer . cancel )
{
Logger : : Print ( " Token incrementation thread terminated \n " ) ;
}
} ) ;
}
}
2018-12-17 08:29:18 -05:00
uint32_t Auth : : GetZeroBits ( Utils : : Cryptography : : Token token , const std : : string & publicKey )
2017-01-19 16:23:59 -05: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 08:29:18 -05: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 16:23:59 -05:00
{
if ( zeroBits > 512 ) return ; // Not possible, due to SHA512
if ( computeToken < token )
{
computeToken = token ;
}
// Check if we already have the desired security level
uint32_t lastLevel = Auth : : GetZeroBits ( token , publicKey ) ;
uint32_t level = lastLevel ;
if ( level > = zeroBits ) return ;
do
{
+ + computeToken ;
if ( count ) + + ( * count ) ;
level = Auth : : GetZeroBits ( computeToken , publicKey ) ;
// 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-28 18:18:51 -04:00
} while ( level < zeroBits ) ;
2017-01-19 16:23:59 -05:00
token = computeToken ;
}
Auth : : Auth ( )
{
Auth : : TokenContainer . cancel = false ;
Auth : : TokenContainer . generating = false ;
Localization : : Set ( " MPUI_SECURITY_INCREASE_MESSAGE " , " " ) ;
// Load the key
Auth : : LoadKey ( true ) ;
Steam : : SteamUser ( ) - > GetSteamID ( ) ;
2017-05-31 09:45:12 -04:00
Scheduler : : OnFrame ( Auth : : Frame ) ;
2017-01-19 16:23:59 -05:00
// Register dvar
Dvar : : Register < int > ( " sv_securityLevel " , 23 , 0 , 512 , Game : : dvar_flag : : DVAR_FLAG_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 ( ) ;
// 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 ) ;
// Guid command
2017-04-28 18:18:51 -04:00
Command : : Add ( " guid " , [ ] ( Command : : Params * )
2017-01-19 16:23:59 -05:00
{
2017-02-18 03:42:55 -05:00
Logger : : Print ( " Your guid: %llX \n " , Steam : : SteamUser ( ) - > GetSteamID ( ) . bits ) ;
2017-01-19 16:23:59 -05:00
} ) ;
if ( ! Dedicated : : IsEnabled ( ) & & ! ZoneBuilder : : IsEnabled ( ) )
{
2017-04-28 18:18:51 -04:00
Command : : Add ( " securityLevel " , [ ] ( Command : : Params * params )
2017-01-19 16:23:59 -05:00
{
if ( params - > length ( ) < 2 )
{
uint32_t level = Auth : : GetZeroBits ( Auth : : GuidToken , Auth : : GuidKey . getPublicKey ( ) ) ;
Logger : : Print ( " Your current security level is %d \n " , level ) ;
Logger : : Print ( " Your security token is: %s \n " , Utils : : String : : DumpHex ( Auth : : GuidToken . toString ( ) , " " ) . data ( ) ) ;
Logger : : Print ( " Your computation token is: %s \n " , Utils : : String : : DumpHex ( Auth : : ComputeToken . toString ( ) , " " ) . data ( ) ) ;
Toast : : Show ( " cardicon_locked " , " ^5Security Level " , Utils : : String : : VA ( " Your security level is %d " , level ) , 3000 ) ;
}
else
{
uint32_t level = static_cast < uint32_t > ( atoi ( params - > get ( 1 ) ) ) ;
Auth : : IncreaseSecurityLevel ( level ) ;
}
} ) ;
}
2017-04-28 18:18:51 -04:00
UIScript : : Add ( " security_increase_cancel " , [ ] ( UIScript : : Token )
2017-01-19 16:23:59 -05:00
{
Auth : : TokenContainer . cancel = true ;
Logger : : Print ( " Token incrementation process canceled! \n " ) ;
} ) ;
}
Auth : : ~ Auth ( )
2017-01-23 16:06:50 -05:00
{
Auth : : StoreKey ( ) ;
}
void Auth : : preDestroy ( )
2017-01-19 16:23:59 -05:00
{
Auth : : TokenContainer . cancel = true ;
Auth : : TokenContainer . generating = false ;
// Terminate thread
if ( Auth : : TokenContainer . thread . joinable ( ) )
{
Auth : : TokenContainer . thread . join ( ) ;
}
}
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 ;
}
}