Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Connect Token Design #83

Closed
RandyGaul opened this issue Apr 2, 2019 · 5 comments
Closed

Question: Connect Token Design #83

RandyGaul opened this issue Apr 2, 2019 · 5 comments

Comments

@RandyGaul
Copy link

RandyGaul commented Apr 2, 2019

Hi there, I was studying the connect token design and have a couple questions. I am just trying to learn about design intentions and use-cases.

  1. timeout seconds - Why is this included in the connect token, instead of living in client/server as a tunable parameter? I was trying to think about use-cases for variable timeout. Is this perhaps somehow useful under DoS circumstances?
  2. Redundant information - Did you ever consider consolidating the connect token public and secret data that have similarities? The redundancy was a bit confusing when reading the standard, and for a while I thought I was having a misunderstanding. A tidbit in the standard describing the intent of redundancy could help make it much simpler to read.
  3. It looks like the only unique information in the encrypted section of the connect token format is the user data and the client id. Are there any use cases (besides testing) for having the public/secret server lists differ from one another (I couldn't think of any)? If not, would a format like this make any sense to you? I consolidated the redundant information, especially the server list, into a public portion of the connection request packet, instead of encrypting the majority of the packet like before. This leads to simplifications in the standard and in the implementation.
// Proposed new design for connect token. The entire token, minus the REST SECTION,
// serves dual purpose: provide the client with necessary information, and also act in
// its entirety as a connection request packet.

// -- BEGIN PUBLIC SECTION --
// --  BEGIN REST SECTION  --
[version info] (13 bytes)       // "NETCODE 1.02" ASCII with null terminator.
[protocol id] (uint64)          // 64 bit value unique to this particular game/application
[client to server key] (32 bytes)
[server to client key] (32 bytes)
// --  END REST SECTION    --
[zero byte]                     // Packet type connection request
[version info] (13 bytes)       // "NETCODE 1.02" ASCII with null terminator.
[protocol id] (uint64)          // 64 bit value unique to this particular game/application
[create timestamp] (uint64)     // 64 bit unix timestamp when this connect token was created
[expire timestamp] (uint64)     // 64 bit unix timestamp when this connect token expires
[timeout seconds] (uint32)      // timeout in seconds. negative values disable timeout (dev only)
[num server addresses] (uint32) // in [1,32]
<for each server address>
{
    [address type] (uint8)      // value of 1 = IPv4 address, 2 = IPv6 address.
    <if IPV4 address>
    {
        // for a given IPv4 address: a.b.c.d:port
        [a] (uint8)
        [b] (uint8)
        [c] (uint8)
        [d] (uint8)
        [port] (uint16)
    }
    <else IPv6 address>
    {
        // for a given IPv6 address: [a:b:c:d:e:f:g:h]:port
        [a] (uint16)
        [b] (uint16)
        [c] (uint16)
        [d] (uint16)
        [e] (uint16)
        [f] (uint16)
        [g] (uint16)
        [h] (uint16)
        [port] (uint16)
    }
}
[connect token nonce] (24 bytes)
<zero pad to 744 bytes>
// --  END PUBLIC SECTION  --
// -- BEGIN SECRET SECTION --
[client id] (uint64)            // globally unique identifier for an authenticated client
[client to server key] (32 bytes)
[server to client key] (32 bytes)
[user data] (256 bytes)         // user defined data specific to this protocol id
// --  END SECRET SECTION  --
[hmac bytes] (16 bytes)

The connection request packet is defined as the connect token minus the REST SECTION. The PUBLIC SECTION of the connection request packet can be used as the additional data in AEAD. This way the connection request packet is entirely protected from tampering, and the client id/userdata/keys are still encrypted. I imagine there is no security risk, since unencrypted information is publicly knowable to authenticated clients anyways.

This design might have some benefits:

  • The server list and timeout is not redundantly included in both the PUBLIC SECTION and SECRET SECTION. The keys are still redundant, since they must not be publicly viewable. This might simplify the standard quite a bit, since the entire connect token only needs to be described once, and the different sections are very clear. It is also now easy to understand why each piece of redundant information is in both the PUBLIC and SECRET sections.
  • The PUBLIC SECTION of the token can be read by the client, and the entire token, minus the REST SECTION, can be immediately forwarded as-is like a connection request packet to the server. When a client gets a connect token via REST call, they can trivially lop off the REST SECTION and send the remainder as a connection request packet, simplifying a lot of code in the reference implementation.

Does this consolidation/simplification make any sense? I'm probably missing some obvious problems, and am asking to better understand the netcode design.

P.S.
I moved the SECRET SECTION to the end of the packet. This way the SECRET SECTION resides at a known byte-offset, and the server can quickly validate connect tokens, just as before, without the need to parse the server address list.

@ghost
Copy link

ghost commented Apr 2, 2019

I had personally assumed that one of the use cases for having redundant server addresses in the secret client data was so that a game server could verify if a client was allowed to connect to that server, without needing to access some sort of central "sessions" database.

@RandyGaul
Copy link
Author

RandyGaul commented Apr 2, 2019

@stellarLuminant Right, that makes total sense. I guess a better question would have been "why not make the connect token secret section a lot smaller", since that seems to remove redundancy, simplify things quite a bit, all without sacrificing any security.

Also, I edited my OP a lot -- my apologies! You probably responded to an old version (just fyi in case you had insights to any new questions I wrote).

@gafferongames
Copy link
Contributor

gafferongames commented Apr 3, 2019

The public token data is unencrypted, because it’s stuff the client needs to connect to the server, eg. The server IP address, private keys, timeout whatever. It’s not encrypted by anything, because the token is sent down from backend to client via a secure channel, typically HTTPS.

The internal part of the token, the private stuff that you think is redundant, is encrypted by a key that the client does not have (by design). The client passes this opaque blob over an unsecure channel (UDP packets) to connect to the server.

The server uses this data to verify that yes, in fact this client is authorized by the backend and is allowed to connect to the server.

That is all. There is no redundancy. You are trying to optimize something you don’t understand. Read the spec again, and understand why the client cannot read, modify or generate the private token data, and why this is necessary for security, then you’ll see why no redundancy exists.

Cheers

@RandyGaul
Copy link
Author

Thanks for the response Glenn! I appreciate you helping me to learn about your design.

There’s one thing I’m confused about. Does the typical use-case have the connect token share the same IP address list in both the private and public sections?

If so, is there an error in my proposal? I know you’re busy, and the nature of the question is complicated. You’re right, it’s an attempted optimization. This is typically how I personally learn. I understand it’s a long question though, and I apologize for not being able to shorten it any further. So feel free to ignore if you prefer :)

@RandyGaul
Copy link
Author

RandyGaul commented Apr 3, 2019

OK so I just figured out the problem. Not encrypting server IPs lets packet sniffers attempt to steal tokens before they are used, by knowing which server the token can be used to connect with. Which actually leads to a new question... I'll open a different issue.

Edit: Nope sniffing isn't possible: #84. So it still looks like the server list in particular does not need to be secret, so my original proposal still looks ok. All that's needed is for public info to be covered in the HMAC with the Additional Data part of the AEAD.

After thinking about all this some more, I do think the netcode approach is also quite simple, since just encrypting the entire token and duplicating the server list is very straightforward to understand once the design intent is understood. As far as I can see: both approaches should be pretty much equivalent with negligible differences.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants