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 ;
Utils : : Cryptography : : ECDSA : : Key Auth : : GuidKey ;
2016-02-21 13:57:56 -05:00
void Auth : : Frame ( )
{
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-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
}
else if ( Auth : : TokenContainer . thread )
{
if ( Auth : : TokenContainer . thread - > joinable ( ) )
{
Auth : : TokenContainer . thread - > join ( ) ;
}
delete Auth : : TokenContainer . thread ;
Auth : : TokenContainer . thread = nullptr ;
Auth : : TokenContainer . generating = false ;
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 ( ) )
{
Game : : MessageBox ( Utils : : VA ( " Your new security level is now %d " , Auth : : GetSecurityLevel ( ) ) , " Success " ) ;
}
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 , 366 Ch
mov eax , 478 A18h
jmp eax
}
}
2016-02-22 17:35:53 -05:00
unsigned int Auth : : GetKeyHash ( )
{
Auth : : LoadKey ( ) ;
std : : string key = Auth : : GuidKey . GetPublicKey ( ) ;
return ( Utils : : OneAtATime ( key . data ( ) , key . size ( ) ) ) ;
}
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 ( ) ) ;
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 ( ) ;
}
else
{
Auth : : GuidKey . Free ( ) ;
}
if ( ! Auth : : GuidKey . IsValid ( ) )
{
Auth : : GuidToken . Clear ( ) ;
Auth : : GuidKey = Utils : : Cryptography : : ECDSA : : GenerateKey ( 512 ) ;
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
Auth : : TokenContainer . thread = new std : : thread ( [ & level ] ( )
{
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-02-23 07:39:38 -05:00
Auth : : IncrementToken ( Auth : : GuidToken , 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-02-23 07:39:38 -05:00
void Auth : : IncrementToken ( Utils : : Cryptography : : Token & token , 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
Utils : : Cryptography : : Token tempToken ( token ) ;
2016-02-22 17:57:17 -05:00
// Check if we already have the desired security level
uint32_t lastLevel = Auth : : GetZeroBits ( tempToken , publicKey ) ;
uint32_t level = lastLevel ;
if ( level > = zeroBits ) return ;
do
2016-02-22 17:35:53 -05:00
{
+ + tempToken ;
2016-02-23 07:39:38 -05:00
if ( count ) + + ( * count ) ;
2016-02-22 17:57:17 -05:00
level = Auth : : GetZeroBits ( tempToken , publicKey ) ;
// Store level if higher than the last one
if ( level > = lastLevel )
{
token = tempToken ;
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
}
2016-02-22 17:57:17 -05:00
while ( level < zeroBits ) ;
2016-02-22 17:35:53 -05:00
token = tempToken ;
}
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 ;
Auth : : TokenContainer . thread = nullptr ;
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 ( ) ) ;
response . set_signature ( Utils : : Cryptography : : ECDSA : : 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-02-22 07:37:13 -05:00
else if ( id ! = ( Utils : : OneAtATime ( 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 ( ) ) ;
if ( Utils : : Cryptography : : ECDSA : : VerifyMessage ( info - > publicKey , info - > challenge , response . signature ( ) ) )
{
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 ;
Game : : SV_KickClientError ( client , Utils : : VA ( " Your security level (%d) does not match the server's security level (%d) " , userLevel , ourLevel ) ) ;
}
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
Dedicated : : OnFrame ( Auth : : Frame ) ;
Renderer : : OnFrame ( Auth : : Frame ) ;
2016-02-22 17:35:53 -05:00
// Register dvar
Dvar : : Register < int > ( " sv_securityLevel " , 20 , 0 , 512 , Game : : dvar_flag : : DVAR_FLAG_SERVERINFO , " Security level for GUID certificates (POW) " ) ;
2016-02-21 13:57:56 -05:00
// Install registration hook
Utils : : Hook ( 0x478A12 , Auth : : RegisterClientStub , HOOK_JUMP ) . Install ( ) - > Quick ( ) ;
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-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 17:57:17 -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
if ( Auth : : TokenContainer . thread )
{
if ( Auth : : TokenContainer . thread - > joinable ( ) )
{
Auth : : TokenContainer . thread - > join ( ) ;
}
delete Auth : : TokenContainer . thread ;
Auth : : TokenContainer . thread = nullptr ;
}
2016-02-22 17:35:53 -05:00
Auth : : StoreKey ( ) ;
2016-02-21 13:57:56 -05:00
}
2016-02-21 18:01:20 -05:00
bool Auth : : UnitTest ( )
{
2016-02-22 17:35:53 -05:00
// Utils::Cryptography::Token t;
// auto _key = Utils::Cryptography::ECDSA::GenerateKey(512);
// Auth::IncrementToken(t, _key.GetPublicKey(), 22);
//
// Utils::WriteFile("pubKey.dat", _key.GetPublicKey());
// Utils::WriteFile("token.dat", t.ToString());
2016-02-21 18:01:20 -05:00
/*
Utils : : Cryptography : : Token t ;
for ( int i = 0 ; i < 1'000'000 ; + + i , + + t )
{
printf ( " %s \n " , Utils : : DumpHex ( t . ToString ( ) ) . data ( ) ) ;
}
*/
2016-02-22 17:35:53 -05:00
// auto testSecurityLevel = [](size_t level, std::string key)
// {
// auto startTime = std::chrono::high_resolution_clock::now();
// Utils::Cryptography::Token t;
// Auth::IncrementToken(t, key, level);
// return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - startTime).count();
// };
//
// for (int j = 10; j < 30; ++j)
// {
// printf("\nTesting security level %i:\n", j);
//
// std::vector<long long> times;
//
// for (int i = 0; i < 10; ++i)
// {
// auto key = Utils::Cryptography::ECDSA::GenerateKey(512);
//
// auto time = testSecurityLevel(j, key.GetPublicKey());
// times.push_back(time);
// printf("\t%i: %llims\n", i, time);
// }
//
// long long average = 0;
//
// for (auto time : times)
// {
// average += time;
// }
//
// average /= times.size();
//
// printf("\n Average: %llims\n", average);
// }
2016-02-21 18:01:20 -05:00
return true ;
}
2016-02-21 13:57:56 -05:00
}