Skip to content

Commit

Permalink
Added some authentication documentation and changed the rewardName to…
Browse files Browse the repository at this point in the history
… rewardId because that's what it is now.
  • Loading branch information
Medo42 committed Jan 27, 2013
1 parent 13f3642 commit ad547fb
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 19 deletions.
39 changes: 39 additions & 0 deletions Documentation/Authentication.txt
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,39 @@
Here is a small overview of the authentication process, since it is difficult to understand from the source code (which is split over several functions and languages).

There are three parties to this process: The client, the game server, and the auth server (ganggarrison.com). The objective is this: The client wants to prove his identity to the game server, and the game server wants to get extra information about the user (e.g. rewards) that is known by the auth server. A secret key which is only known to the client and the auth server is used as proof of identity.

A simplified version

In order to explain the process we'll first look at a simplified version: Let's assume the client tries to authenticate with the auth server directly. The easiest solution would be to just send his user ID and secret key to the auth server, which would check if the information matches that stored in a database. This is basically the same as login with username and password (though the key is stored in plaintext on the auth server).

This has the drawback that the key is also transmitted in plaintext, and we don't want anyone listening in and learning the secret key that way. In order to prevent that, we will use a challenge-response mechanism instead: The auth server sends a random number to the client (the challenge), and the client will send back the result of a calculation that has the secret key and the random number as input. Since the auth server knows the secret key as well, he can perform the same calculation and check the result. If it matches, this proves that the client knows the secret key without transmitting it directly.

The calculation we use is hmac_md5. It takes a key and a message as inputs, and returns a signature of the message that is practically impossible to fake without knowing the key. It is also practically impossible to calculate the key from the message and the signature. hmac_md5 is still considered secure, even though md5 is considered broken.

So the auth server will send <challenge>, and the client replies with hmac_md5(<key>, <challenge>). The server can calculate this value as well, and if the result matches, the client is authenticated.

Adding the game server

The addition of the game server complicates things a bit, but the general idea is still the same. I'll present the process first and then explain why it works this way:

The client starts the process by announcing to the game server that he wants to authenticate. In that message, he also transmits his user ID:

Client -> GameServer: REWARD_REQUEST <user_id>

The game server has a user ID now, but no proof. So he creates a random challenge (see above) and sends it back to the client. The client replies with the signature of the challenge PLUS the server's IP address.

GameServer -> Client: REWARD_CHALLENGE_CODE <challenge>
Client -> GameServer: REWARD_CHALLENGE_RESPONSE hmac_md5(<key>, <challenge>+<gameServerIp>)

Note that the game server can choose the challenge, but he can't figure out the key by doing that. Also, the reply will only be valid for this particular game server, since the client will include his IP in the calculation. This prevents the game server from using the reply to authenticating at a different game server.

Now the game server has a reply, but he can't check the result because he doesn't know the user's secret key. The authentication server does, though, so the game server forwards all the relevant information there. <response> is the client's reply here:

GameServer -> AuthServer: AUTHENTICATE <response> <challenge> <user_id>
AuthServer -> GameServer: OK <user_info>
oder
AuthServer -> GameServer: FAIL

The auth server knows the user's key, and also the game server's IP (since it is the game server who is asking), so it can calculate hmac_md5(<key>, <challenge>+<gameServerIp>) and compare it against the client's response. If it matches, the auth server will send back all the interesting user data - at the moment, only the Haxxy rewards. Otherwise, it will send back a failure code.

There is a little snag: The game server will want to authenticate himself to get at his user info, but it might not know its own IP (e.g. if it is behind a NAT router). However, since this is a direct communication with the auth server we don't actually need the extra protection of including the IP. To allow authenticating without the IP check, there is simply a flag in the AUTHENTICATE message which I left out above for clarity. So the game server can just send hmac_md5(<key>, <challenge>) and authenticate that way. This is vulnerable to replay attacks, so if someone sniffs the message he can also authenticate as that server, but since the server can just claim anything to the clients anyway that's not too important.
2 changes: 1 addition & 1 deletion Source/gg2/Objects/InGameElements/Gibbage/XmasHat.xml
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<object id="806"> <object id="807">
<sprite>XmashatS</sprite> <sprite>XmashatS</sprite>
<solid>false</solid> <solid>false</solid>
<visible>true</visible> <visible>true</visible>
Expand Down
2 changes: 1 addition & 1 deletion Source/gg2/Objects/Overlays/SandwichHud.xml
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<object id="807"> <object id="808">
<sprite>SandwichHudS</sprite> <sprite>SandwichHudS</sprite>
<solid>false</solid> <solid>false</solid>
<visible>true</visible> <visible>true</visible>
Expand Down
8 changes: 5 additions & 3 deletions Source/gg2/Scripts/Client/ClientBeginStep.gml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@ do {
} }
} }
ClientPlayerJoin(global.serverSocket); ClientPlayerJoin(global.serverSocket);
if(global.rewardKey != "" and global.rewardName != "") if(global.rewardKey != "" and global.rewardId != "")
{ {
var rewardId;
rewardId = string_copy
write_ubyte(global.serverSocket, REWARD_REQUEST); write_ubyte(global.serverSocket, REWARD_REQUEST);
write_ubyte(global.serverSocket, string_length(global.rewardName)); write_ubyte(global.serverSocket, string_length(global.rewardId));
write_string(global.serverSocket, global.rewardName); write_string(global.serverSocket, global.rewardId);
} }
socket_send(global.serverSocket); socket_send(global.serverSocket);
break; break;
Expand Down
8 changes: 6 additions & 2 deletions Source/gg2/Scripts/GameServer/GameServerCreate.gml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -72,8 +72,12 @@


global.playerID = 0; global.playerID = 0;
global.myself = serverPlayer; global.myself = serverPlayer;
if(global.rewardKey != "" and global.rewardName != "") if(global.rewardKey != "" and global.rewardId != "")
rewardAuthStart(serverPlayer, hmac_md5_bin(global.rewardKey, "0000000000000000"), "0000000000000000", false, global.rewardName); {
var challenge;
challenge = rewardCreateChallenge();
rewardAuthStart(serverPlayer, hmac_md5_bin(global.rewardKey, challenge), challenge, false, global.rewardId);
}
instance_create(0,0,PlayerControl); instance_create(0,0,PlayerControl);


global.currentMap = ds_list_find_value(global.map_rotation, global.currentMapIndex); global.currentMap = ds_list_find_value(global.map_rotation, global.currentMapIndex);
Expand Down
13 changes: 6 additions & 7 deletions Source/gg2/Scripts/GameServer/processClientCommands.gml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -286,21 +286,20 @@ while(commandLimitRemaining > 0) {
break; break;


case REWARD_REQUEST: case REWARD_REQUEST:
player.rewardName = read_string(socket, socket_receivebuffer_size(socket)); player.rewardId = read_string(socket, socket_receivebuffer_size(socket));
player.challenge = ""; player.challenge = rewardCreateChallenge();
repeat(16)
player.challenge += chr(irandom_range(0,255));


write_ubyte(socket, REWARD_CHALLENGE_CODE); write_binstring(socket, player.challenge); write_ubyte(socket, REWARD_CHALLENGE_CODE);
write_binstring(socket, player.challenge);
break; break;


case REWARD_CHALLENGE_RESPONSE: case REWARD_CHALLENGE_RESPONSE:
var answer, i, authbuffer; var answer, i, authbuffer;
answer = read_binstring(socket, 16); answer = read_binstring(socket, 16);


with(player) with(player)
if(variable_local_exists("challenge") and variable_local_exists("rewardName")) if(variable_local_exists("challenge") and variable_local_exists("rewardId"))
rewardAuthStart(player, answer, challenge, true, rewardName); rewardAuthStart(player, answer, challenge, true, rewardId);


break; break;
} }
Expand Down
1 change: 1 addition & 0 deletions Source/gg2/Scripts/Rewards/_resources.list.xml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
<resource name="rewardAuthSuccess" type="RESOURCE"/> <resource name="rewardAuthSuccess" type="RESOURCE"/>
<resource name="hasRewardWeapon" type="RESOURCE"/> <resource name="hasRewardWeapon" type="RESOURCE"/>
<resource name="hasRewardStatue" type="RESOURCE"/> <resource name="hasRewardStatue" type="RESOURCE"/>
<resource name="rewardCreateChallenge" type="RESOURCE"/>
</resources> </resources>
8 changes: 4 additions & 4 deletions Source/gg2/Scripts/Rewards/rewardAuthStart.gml
Original file line number Original file line Diff line number Diff line change
@@ -1,9 +1,9 @@
var player, answer, challenge, ipCheck, rewardName, authbuffer, item; var player, answer, challenge, ipCheck, rewardId, authbuffer, item;
player = argument0; player = argument0;
answer = argument1; answer = argument1;
challenge = argument2; challenge = argument2;
ipCheck = argument3; ipCheck = argument3;
rewardName = argument4; rewardId = argument4;


// Prevent one player from requesting auth several times // Prevent one player from requesting auth several times
with(player) with(player)
Expand All @@ -19,11 +19,11 @@ if(ds_queue_size(RewardAuthChecker.workQueue) > 50) exit;
// Prepare buffer to query the server // Prepare buffer to query the server
authbuffer = buffer_create(); authbuffer = buffer_create();
parseUuid("205e2d84-4833-89d4-15d9-c0249667df1c", authbuffer); parseUuid("205e2d84-4833-89d4-15d9-c0249667df1c", authbuffer);
write_ushort(authbuffer, string_length(answer)+string_length(challenge)+1+string_length(rewardName)); write_ushort(authbuffer, string_length(answer)+string_length(challenge)+1+string_length(rewardId));
write_binstring(authbuffer, answer); write_binstring(authbuffer, answer);
write_binstring(authbuffer, challenge); write_binstring(authbuffer, challenge);
write_ubyte(authbuffer, ipCheck); write_ubyte(authbuffer, ipCheck);
write_string(authbuffer, rewardName); write_string(authbuffer, rewardId);


// Enqueue the check // Enqueue the check
item = ds_list_create(); item = ds_list_create();
Expand Down
2 changes: 1 addition & 1 deletion Source/gg2/Scripts/game_init.gml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
global.autobalance = ini_read_real("Server", "AutoBalance",1); global.autobalance = ini_read_real("Server", "AutoBalance",1);
global.Server_RespawntimeSec = ini_read_real("Server", "Respawn Time", 5); global.Server_RespawntimeSec = ini_read_real("Server", "Respawn Time", 5);
global.rewardKey = unhex(ini_read_string("Haxxy", "RewardKey", "")); global.rewardKey = unhex(ini_read_string("Haxxy", "RewardKey", ""));
global.rewardName = ini_read_string("Haxxy", "RewardName", ""); global.rewardId = ini_read_string("Haxxy", "RewardId", "");
global.mapdownloadLimitBps = ini_read_real("Server", "Total bandwidth limit for map downloads in bytes per second", 50000); global.mapdownloadLimitBps = ini_read_real("Server", "Total bandwidth limit for map downloads in bytes per second", 50000);
global.updaterBetaChannel = ini_read_real("General", "UpdaterBetaChannel", isBetaVersion()); global.updaterBetaChannel = ini_read_real("General", "UpdaterBetaChannel", isBetaVersion());
global.attemptPortForward = ini_read_real("Server", "Attempt UPnP Forwarding", 0); global.attemptPortForward = ini_read_real("Server", "Attempt UPnP Forwarding", 0);
Expand Down

0 comments on commit ad547fb

Please sign in to comment.