Are there any particular flaws in this protocol?
(Yes I know about SSL)
Three participants:
Login Server LS
Game Server GS
Client C
Asymmetric key-pair Kpriv Kpub
Shared 128bit secret key Ksec
Shared 128bit signing key Ksecsign
Client secret Csec
Login Server nonce NonceLS
Client Login nonce NonceC
Game Server nonce NonceGS
Session S
Before start
LS holds Kpriv, Ksec and Ksecsign
GS hold Ksec, Ksecsign
C holds Kpub
All symmetric encryption uses AES-128, asymmetric uses 2048 bit RSA. HMAC uses SHA-256. PBKDF2 uses HMAC SHA-256.
- C connects to LS.
- C sends a puzzle request.
- LS responds with a puzzle challenge which is a unique random nonce and a difficulty.
- C computes a puzzle reply and creates a random nonce Csec
- C encrypts nonce + client information (type of device etc) using Kpub.
- C Sends a session request with the puzzle reply in the clear, and the rest of the data encrypted (as detailed in 5). I.e. puzzle-reply || EKpub(Csec || device data)
- LS queues C's request according to the quality of the puzzle reply. If the queue is full and C's puzzle answer isn't better than the worst reply in the queue, it replies with a request failed. C can then start from (2)
- LS decrypts the payload. It then creates NonceLS and creates two keys using PBKDF2 with HmacSHA256 from (NonceLS || Csec) and two different salts.
- LS then encrypts (Csec || device data || NonceLS) using Ksec and signs this with a HMAC using Ksecsign. This is the new session. (S <- HMACKsecsign(IV || EKsec(...)) || EKsec(Csec || device data || NonceLS || timestamp) || IV).
- LS Then uses the same method (encrypt-then-sign) as above with the derived keys in (8) to create encrypted data to give to the client. I.e. the client receives NonceLS || HMAC(IV || E(S)) || E(S) || IV.
- C Construct the same keys as in (8) and use them to verify, then decrypt the message. C now has NonceLS, S and Csec.
- C connects to GS
- C creates a new nonce NonceC and sends NonceC || S in the clear to GS
- GS decrypts and verifies S using the shared Ksec and Ksecsign. It logs the device information (note that forged device information isn't a problem) and creates a new nonce NonceGS. It then generates a key and sign key using different salts with PBKDF2 and NonceC || NonceLS || NonceGS || Csec. (If the session is expired, then an error code is returned, which requires C to request a new S from the LS)
- GS Then replies in the clear with (1 byte result || NonceGS)
- C Sets up the same key as GS and start sending packets in the following format: (2-byte packet length || HMAC(IV || E(...)) || E(packet number || data) || IV). Packet numbers start at 0 and are increased by 1 for each packet sent. HMAC is first verified, then decryption is done, followed by packet number verification.
For both server and client decryption, if there is any packet integrity error anywhere, the server/client disconnects. In order to reconnect a new handshake is required, which will mean a new NonceC and new NonceGS.
There are some omissions in this protocol as far as I can tell, which I don't know how they will affect the general security of the protocol:
- There is no signing in (6)
- Packet length can be tampered with.
- (10) NonceLS is not included in the HMAC (I will fix this)
I'm also considering switching to - for example - AES in CTR mode for (16) to avoid the IV / packet number / padding overheads for each packet.
EDIT:
I try to assure the following:
- Only a server holding Kpriv can issue a valid session S to a client.
- No one but the holders of Ksec can decrypt a session S.
- The session S is resistant to tampering.
- A holder of a session S and the secret key Csec can initiate use these to initiate a confidential connection with a server holding Ksec
- Encrypted sessions in (4) is not vulnerable to man-in-the-middle, replay or other attacks.
- A large amount of session requests (1) cannot CPU-exchaust a server.
- Initiating a encrypted session (4) is inexpensive CPU-wise.
- Session requests and replies are not vulnerable to attacks such as replay or man-in-the-middle.
EDIT 2:
To clarify, this is TCP all the way.
EDIT 3:
The key exchange works like this:
Client A, Login Server S, Game Server B
(1) A → S : { Na, A }Kpub
Ksa ← { Na, Ns, A, Saltenc }PBKDF2
Ksasign ← { Na, Ns, A, Saltsign }PBKDF2
M ← IV || HMAC(IV, { Na, A }Ksb)Ksbsign || { Na, A }Ksb
(2) S ➝ A : { Ns, IV, HMAC(IV, { Ns, M }Ksa)Ksasign, { Ns, M }Ksa }
(3) A ➝ B : { N'a, M }
(4) B ➝ A : { Nb }
Kab ← { Na, N'a Nb, Saltenc }PBKDF2
Kabsign ← { Na, N'a Nb, Saltsign }PBKDF2
Encryption proceeds using Kab, signing each with Kabsign and keeping a running counter with each packet.
There are some weaknesses in this as written.
If you have a well-tested strong key exchange protocol I could use as a drop-in replacement that would be fine.
The requirements are:
- No communication between S and B.
- S and B can share a finite limits of secrets before all exchanges.
- A possess a public key and S holds the private counterpart.
- A-B key negotiation should not use asymmetric key exchange.