forgottenserver icon indicating copy to clipboard operation
forgottenserver copied to clipboard

Reduced JWT tokens for HTTP login

Open ranisalt opened this issue 5 months ago • 3 comments

Pull Request Prelude

Changes Proposed

See #4963

Current issues:

  • ~~Tibia client sends a malformed packet for login (does not decrypt), could be my client misbehaving.~~
  • ~~OTClient crashes hard because the token is too long for it's expectations.~~ OTClient bugs printing that a message was received with the wrong checksum. Probably just a bug.

ranisalt avatar Jul 05 '25 23:07 ranisalt

Current issues:

  • Tibia client sends a malformed packet for login (does not decrypt), could be my client misbehaving.
  • OTClient crashes hard because the token is too long for it's expectations. Probably just a bug.

I think I know why it happens. Session key is send encrypted using RSA. Tibia uses 1024-bit RSA key, which is 128 bytes, from which ~~117~~ 127 bytes are for data (~~11~~ 1 for padding). (EDIT: I tested how many bytes has RSA packet after decrypting, it has 127, as Tibia protocol does not use padding.) I saw the same problem on some 8.x server, when some OTS acc. maker allowed very long logins and passwords.

All bytes from msg in this part of code: https://github.com/otland/forgottenserver/blob/c519cc6e6f9864e933c8a5a94d20afee538e70de/src/protocolgame.cpp#L367-L411 must fit together into 117 bytes. IDK how many bytes are there for session token.

JWT won't work in this scenario. We need much shorter format ex. first format proposed here: https://github.com/otland/forgottenserver/issues/4963#issuecomment-3040349084

4 lines (no JSON, as it wastes too many characters for formatting):

  1. account identifier (1-10 bytes)
  2. account password hash truncated to 20 first characters (20 bytes)
  3. 'created at' date as unix timestamp (10 bytes) - OPTIONAL, if we want sessions time limit
  4. SHA1 sign: checksum of first 3 lines (concat using ; separator) with key.pem SHA1 (SHA1 of RSA key of OTS) added at end of SHA1 sign truncated to 20 first characters (20 bytes)

I don't see any chance to put full client IP there. It would be possible, if we allow only IPv4, but with IPv6 it's too long. If we want IP verification, we would need to add it as part of 4th line using IP as part of sign. Or add some weak IP verification by putting ex. 5 bytes of IP SHA1 hash and 5 bytes of sign of that IP.

EDIT: Actually we don't need account ID and password as separate fields. We will know what account ID to load, because after session token, client sends character name, so all we need is some small checksum, to detect, if account ID/password changed ex. first 10 letters of SHA1. 4th line 'SHA1 sign' can be also shorter ex. 10 letters, it must be only secure enough to prevent someone from brute forcing it - we are still talking about brute forcing it by someone who knows password to account.

gesior avatar Jul 07 '25 02:07 gesior

I see no value in adding a checksum at the end. However, sending the account id + timestamp and signing those with HMAC should be safe enough to assume it isn't fake and still be secure. It's a thinner version of JWT without the fixed header + JSON boilerplate

ranisalt avatar Jul 07 '25 20:07 ranisalt

I see no value in adding a checksum at the end.

By checksum, I meant "4. SHA1 sign:", which is like HMAC-SHA1, but as I described, we must truncate it to 10-20 ASCII characters (from 40 in hexadecimal format). I didn't want to call it HMAC, because HMAC is known format and we will use something custom. login.php/login.cpp returns this value as part of JSON, so it must be in format compatible with JSON string, without extra encoding (base64/hex). Otherwise it will add more bytes.

However, sending the account id + timestamp and signing those with HMAC should be safe enough to assume it isn't fake and still be secure.

Don't forget about password. We need some hash of password to make session token fail to login into game, after password to account is changed.

I checked how many bytes are used right now by Tibia 13.10, there are 50-70 free bytes (of 127 RSA-1024 bytes), even, if we allow character name with up to 30 letters.

Let's go back with discussion about format to https://github.com/otland/forgottenserver/issues/4963

gesior avatar Jul 07 '25 22:07 gesior