Portal connection
Time to dig into the specifics of Guild Wars 2's connection...
The portal connection starts off in clear text. The game client will connect, then send a /Sts/Connect command which will include information about the game client.
Example:
P /Sts/Connect STS/1.0
l:252
<Connect>
<ConnType>400</ConnType>
<Address>192.168.1.1</Address>
<ProductType>0</ProductType>
<ProductName>gw2</ProductName>
<AppIndex>1</AppIndex>
<Epoch>999999999</Epoch>
<Program>101</Program>
<Build>1002</Build>
<Process>9999</Process>
</Connect>
P /Auth/StartTls STS/1.0
s:1
l:11
This looks similar to how HTTP requests work. Th
Example:
STS/1.0 400 Success s:1R l:46
<Error server="1001" module="2" line="1518"/>
The 's:1R' here, refers to the original client request. The
The client will begin by sending a buffer starting with:
16 03 03 xx xx
The first byte indicates what kind of packet this is:
14 = ack
15 = server error
16 = auth phase
17 = game packet
The 2 last bytes are the size of the sub buffer that follows.
So looking at the first response the client sends:
16 03 03 00 4e
01 00 00 4a 03 03 xx xx xx ...
Shows a subbuffer of size 0x4e (78 bytes). The first byte is the type of the buffer. If you look closely, it's apparent this buffer is also split up (ie. 0x00 0x4a is yet another size indicator of a sub part). I'm going to skip on some details, if you fire up wireshark you can more easily look at what's there.
There's 1 part that is important in this 0x01 sub buffer and that is a client seed that will be used further on. We need to save 0x20 bytes starting at xx as indicated above.
Also important, is that these sub buffers are used in the HMAC calculation, so we need to keep a sha256 hash of them, excluding the 0x14 sub buffer for digest1.
In code:
if (packet[0] != 0x14)
digest1.process(packet);
digest2.process(packet);
16 03 03 00 34
So we already know 00 34 is the size of the sub buffer, which contains:
02 00 00 30 03 03 xx xx xx
For what follows now, it's useful to read RFC5054 paragraph 2.2. and have a look at the SRP demo page.
The server follows up with a new large packet:
16 03 03 01 14
0c 00 01 10
00 80 xx xx xx .....
00 01 02
08 yy yy yy yy yy yy yy yy
00 80 zz zz zz .....
N = xx xx xx ...
g = 2
s = yy yy yy yy yy yy yy yy
B = zz zz zz ...
16 03 03 00 04
0e 00 00 00
16 03 03 00 86
10 00 00 82
00 80 xx xx xx xx
with A = xx xx xx ...
If you look at the SRP demo page (SRP-6a), you can see how all these values fit together. However GuildWars 2 uses a slightly different way to calculate value x, the RFC version uses:
x = H(salt || H(username||':'||password))
whereas Guild Wars 2 uses:
Then converts this passhash buffer using htonl() and hashes it to form the password field to use in the calculation:passhash = H(lowercase(unicode_pass||unicode_login))
x = H(salt || H(lowercase(username)||':'||H(passhash)))
With all these values, we're able to calculate the shared key S.
The client will now activate encryption by sending the packet:
14 03 03 00 01 01
From now on, the buffers will be encrypted, however the shared key S isn't used as-is for the encryption. First a master key is generated from this and then a key expansion function is used to generate a send and a receive key.
But that's for another post...