Skip to content

Latest commit

 

History

History
284 lines (224 loc) · 13.6 KB

proxy-auth.md

File metadata and controls

284 lines (224 loc) · 13.6 KB

I, along with many other people, believe that web-based MUD clients are going to become more and more prevalent. Due to the nature of browsers, these clients will require MUD codebases that speak WebSockets or serve Flash policy files. Another solution is proxy servers that DO speak these transports and can then proxy the connection to the MUD. However, this proxying has its own host of problems due to IP-based multiplaying restrictions, amongst other things.

This draft attempts to define an easy-to-implement, secure authentication mechanism between proxies and MUD servers that would allow a MUD server to trust that the connection coming from the proxy is legitimately from the proxy, and not from a malicious hacker trying to get around multiplaying restrictions.

The basic idea is a new telnet suboption that sends a specially formatted HMAC (using SHA-1 for the hash function) along with the client information. The HMAC is generated using a secret key shared between the proxy and the server. The inspiration for this comes from Amazon AWS, and more specifically a distilled version of this auth style that I read at http://www.thebuzzmedia.com/designing-a-secure-rest-api-without-oauth-authentication/

Workflow

The first stage is that PROXY and SERVER communicate before hand to exchange a secret key, and a piece of identifiable information that can be used to map to the secret key. We'll call this the public key.

Note, the above step only occurs once, ever. After that point, the secret key will never be transmitted and will ONLY be used to sign messages.

Following is the basic communication workflow. More details are in the sections on the ClientInfo and Disconnect commands below.

  • CLIENT connects to PROXY, selects the MUD they want to play from a list.
  • PROXY looks up the secret key and the public key for that MUD.
  • PROXY initiates connection to SERVER.
  • During telnet negotation, PROXY informs the server that it IS a proxy by sending IAC WILL PROXY.
  • SERVER responds that it supports the PROXY option.
  • PROXY gets the client address and current timestamp (UTC time), as well as some other information, then generates the signature from it and sends it to SERVER as a ClientInfo command.
  • SERVER receives the message and generates its own signature and compares.
  • If there's something wrong, SERVER sends a Disconnect command back. Otherwise, proceed as normal.

Telnet Options and General Information

The actual telnet option will be \xCA which is decimal 202. It will use a command/value syntax with JSON payloads (mostly) similar to GMCP.

Keys for Identification

The public key will be unique per proxy server, and should be a UUID in hex format (32 bytes). The secret key may be provided/generated by either party and currently has no restrictions.

Key Exchange

The key exchange can be initiated by either party, either a new proxy server wants to add a list of MUDs that support this protocol, or a new MUD wants to properly support proxies. Most likely, developers that run a proxy will also provide a web interface for a MUD to add themselves to the list and receive a secret key. As long as these keys are unique per proxy/server pair, either party can generate the secret key.

Signing / Authentication

Any command that requires a signature will be listed in this document with [SIGNED] appended to the end of the command name.

HMAC

HMAC is a technique for calculating a message authentication code involving a cryptographic hash function in combination with a secret key. The HMAC provides two purposes, to verify the integrity of the message, as well as the authenticity of the sender. Typically, any hash function can be used, but this protocol will specifically use SHA-1. Since the resulting output of HMAC (known as the signature) is of the same length as the hash function that was used to generate it, the signatures for this protocol will always be 160 bits (40 bytes over the wire since we will translate to hex).

Signed Message Format

For the purposes of this document, the result of the HMAC technique using the secret key and the message will be known as the signature.

A signed message MUST follow the following format: <Command> <signature>:<data> where the signature and the data are separated by a colon :. The data MUST be a JSON-encoded string that contains at least two keys: public_key that contains the 32 byte hex digest that represents the proxies UUID, and timestamp, the current UNIX time when the message was generated.

The signature MUST be generated by running the secret key and the message through the HMAC algorithm using SHA-1 as the hash function. On the sender's side, the data should be serialized to a JSON string, and the resulting string run through the HMAC function. On the receiving end, the exact string after the signature should be run through the HMAC function in the same fashion. This ensures both parties generate the signature using the exact same message.

The timestamp MAY be used by the receiving party and compared to the time the message was received. If the time difference is greater than a reasonable amount of time (say, 1 minute) the receiving party can assume that the message is invalid.

Commands

At first, there will be two subcommands, ClientInfo which is sent by the proxy at the start, and Disconnect which MUST be sent by the server if it will not accept the proxied connection, shortly before it kills it.

ClientInfo [SIGNED]

The ClientInfo command is the command that's sent by the proxy to the server to pass along the client information and to authenticate itself. The format of the message will be:

IAC SB PROXY ClientInfo <signature>:<data> IAC SE

The JSON string will, at a minimum, contain keys for: proxy name, proxy version, and client address. The client address value MUST be a list (array) of the following format: ["<ip:string>", <port:int>]

These keys are in addition to the keys required for signed messages (see the Signed Message Format section)

Example JSON object:

{
    "proxy_name": "RedLantern",
    "proxy_version": "0.1.1",
    "client_addr": ["192.168.0.2", 3452],
    "timestamp": 123456789,
    "public_key": "5e3f7ade701644eb8c8b8e34558d6cc2"
}

The server MUST accept the listed keys, and MUST NOT assume that these keys are the only keys that will be sent. A proxy MAY send an arbitrary number of additional key/value pairs in this message, and the server MUST accept these values, even if they do nothing with them.

Disconnect

The Disconnect command is a way for a MUD server to communicate back to the proxy that it will not be accepting the connection. The Disconnect command MUST be sent if the server will not accept a particular connection.

The value of the Disconnect command MUST be a JSON object with at least one key: reason, which maps to one of the reasons listed below. Some of the reasons listed below will have additional keys that SHOULD be sent, and the proxy MUST accept Disconnect commands without those additional keys present.

The server MAY optionally supply another key named message, containing a textual description of why the connection will not be allowed. The proxy and the server both MUST NOT use the message as a way to pass additional metadata.

Possible reasons:

  • EXPIRED
    • If the timestamp is too old, the server MAY send back a message like so: IAC SB PROXY Disconnect {"reason": "EXPIRED"} IAC SE
  • UNAUTHORIZED
    • If the public key could not be located or is revoked: IAC SB PROXY Disconnect {"reason": "UNAUTHORIZED"} IAC SE
  • TOOMANY
    • If the server has a connection limit for the proxy, the server MAY send this back: IAC SB PROXY Disconnect {"reason": "TOOMANY"} IAC SE
    • Additional keys:
      • max_connections, the value of which should be an integer representing the maximum number of allowed connections from this proxy.
      • current_connections, the value of which should be an integer representing the current number of connections from this proxy.
  • BANNED
    • The server can inform the proxy that a certain client address is banned using this reason. The server may also send a message informing the client why they were banned: IAC SB PROXY Disconnect {"reason": "BANNED", "message": "Temporary ban for being an idiot. Come back in 2 weeks."} IAC SE
    • Additional keys:
      • expiration the value of which should be the number of seconds that the ban will last. This may be presented to the user.

The proxy MAY do what it wants with the message. In some cases, it may present them to the user, in other cases, it may log them internally. The message must NOT be used to pass additional metadata (such as the proxy using the content of the message to determine the actual connection limit).

Additional Information

Key Leaks

There are two parties that can leak the shared secret keys, the MUD and the proxy.

If the MUD leaks a shared key, the damage is minimal. The only thing that can happen is that a malicious user (if they had the public key as well) can present themselves as the proxy that was previously identified by the leaked key. It is recommended that MUD servers log the origin of each connection from a supposed proxy, and raise a red flag if they receive a connection from a source that they are not expecting.

If the proxy leaks a particular shared key, the problem is the same as the server leaking it, and the damage can be mitigated in the same way, by the MUD server logging all connection attempts using a given public key and comparing the origin of that connection with the expected origin.

If either party discovers a leak, they should immediately notify the other party and remove the leaked key from their respective configurations, and generate a new shared key to use.

FAQ

Why a new protocol, why not make it a command in an existing protocol?

The primary reason is because existing protocols are intended to be client <-> server. By rolling this into an existing protocol, the server and proxy must both support the full spec of the existing protocol, and the proxy must tell the server that it supports this protocol, which could in turn lead to the server making assumptions about capabilities.

For example, let's say it was supported as a GMCP package. The server must have support for the GMCP spec, to start with and so must the proxy. The proxy must then attempt to negotiate support for GMCP with the server to send the client information. The proxy then must inspect every incoming message from the server to see if it's a GMCP message and if it's a GMCP message that it needs to act on. It also needs to ensure that if the client does (or does not) support GMCP that it properly forwards or blocks those messages.

Wouldn't it be simpler to embed the signature into the data object?

The problem with embedding the signature into the data object (ie, as a key in the json object instead of preceding it) is that both parties need to generate the signature using the exact same string. If we took this approach, then this is what would need to happen:

  • Sender builds the object containing the properties it needs to send, then serializes this object according to a very strict set of rules.

  • Sender generates the signature using the serialized string.

  • Sender inserts the signature into the original object, then reserializes it and creates the full message.

  • Sender sends the full message to the receiver.

  • Receiver parses the message and extracts the data.

  • Receiver deserializes the data, extracts the public key, timestamp, and signature.

  • Receiver looks up the secret key, and reserializes the data object (without the signature) using the exact same set of strict rules as the sender.

  • Receiver generates the signature using the reserialized string.

  • Receiver compares the two signatures.

The biggest issue here is that you have to have a very clearly defined set of rules as to how you are supposed to format the serialized string, since both parties need to use exactly the same string and secret key to generate the same signature.

Instead of relying on these rules that complicate things, we move the signature out of the serialized string. This allows both parties to use the exact same serialized string without any additional parsing/restructuring overhead.

What happens if someone else poses as an existing MUD server, and a proxy with support for that server connects to it?

In this scenario (user A intercepts requests for the hostname/ip that MUD M uses, which causes connections from proxy P to go to MUD B), the user running the fake MUD B would receive the connection attempts, but since there is nothing sensitive that is actually passed over this protocol, there is nothing that he can do with the information.

One potential problem here is if MUD B exists solely to phish credentials from users that think they are connecting to MUD M, the plan will succeed just fine. However, there's nothing we can reasonably do about this, considering this attack would affect everyone, not just users going through a particular proxy.

Final Thoughts

The implementation is simple. The code is simple. The timestamp check ensures that replay attacks (an attacker somehow getting hold of a message and trying to use it to authenticate itself as the proxy) are extremely difficult (if not impossible). Because of the shared secret keys, the server can trust what is coming through, and it means that an attacker cannot spoof themselves as a trusted proxy to get around IP based multiplaying restrictions without the secret key.