piqueserver / aosprotocol

Documentation and development of the protocol used by AoS 0.x, OpenSpades, pyspades and piqueserver
https://piqueserver.github.io/aosprotocol/
Apache License 2.0
26 stars 9 forks source link

PP (of sorts): Ed25519 Authentication #51

Open DavidCo113 opened 12 months ago

DavidCo113 commented 12 months ago

Do C source files count? Well here's one anyway. Also, I have made an implementation already for both the client and server parts of my libspades project, and it seems to work well enough. My MitM proxy and server programs use libsodium for the cryptography.

/** @file Ed25519Spec.c
 * Ace of Spades Ed25519 Authentication extension specification version 1.
 *
 * The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
 * NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED",
 * "MAY", and "OPTIONAL" in this document are to be interpreted as
 * described in BCP 14 [RFC2119] [RFC8174] when, and only when, they
 * appear in all capitals, as shown here.
 *
 * I know this is probably a bit much for Minecraft with guns,
 * but I am certain this extension, if implemented properly,
 * will be better than the current /login method in every way,
 * shape, and even form! The major reasons are listed as follows:
 *
 * - Completely automatic, although a client MAY use a whitelist/blacklist.
 * - Does not need manual distribution of passwords from servers,
 *   but rather automatic distribution of a public key from the client.
 * - Very difficult to leak accidentally compared to /login, such as by not properly prefixing the
 *   chat message with the '/' character, or by using video/packet recording software.
 * - Minimally vulnerable to attacks, exploits, and misbehaving administrators,
 *   especially when used with the method described on the last paragraph of this comment.
 *
 * A normal authentication transaction is shown:
 *
 * ```
 * Client <-- RequestAuthentication <-- Server
 * Client -->     SendPublicKey     --> Server
 * Client <--       SendNonce       <-- Server
 * Client -->     SendSignature     --> Server
 * Client <--   EndAuthentication   <-- Server
 * ```
 *
 * The client MAY stop sending packets at any part of the transaction, should it desire.
 * In this case, the server SHOULD consider the client un-authenticated until it
 * resumes and finishes, if it does at all.
 *
 * The server SHOULD end the transaction soon after it starts, such as when the game is joined by the client,
 * or when a time limit expires, or even both.
 *
 * The server is allowed to start a transaction an unlimited amount of times, at any time.
 * This can be used to ensure that the original client is still connected
 * and has not fallen victim to an evil router, for instance.
 */

#include <stdint.h>

#define VARIABLE_LENGTH 1
#pragma pack(1)

/** Requests the client to start an authentication transaction. @ingroup packets
 * The server MUST NOT allow the client to skip ahead in the transaction, or start it by itself.
 * Otherwise, that is just an obvious security risk, and asking for exploitation of the behaviour.
 * For instance, how would the server verify a signature for a non-existant nonce and key?
 * What if the last nonce and key haven't been cleared from memory yet, and the client tries a replay attack?
 */
struct PacketRequestAuthentication {
    uint8_t packetID; /**< 65 */
    uint8_t subPacketID; /**< 0 `(Client<--Server)` */
};

/** Ends the authentication transaction and tells the client its permission level. @ingroup packets
 * Note that a server MAY either send this packet to the client or disconnect the client in the following events:
 * - The client sends a packet of invalid length or subpacket ID.
 * - The server does not have the public key in its database,
 *   and does not have the capability of authenticating un-registered keys.
 * - The client sends a signature which does not correspond to the public key and the nonce.
 */
struct PacketEndAuthentication {
    uint8_t packetID; /**< 65 */
    uint8_t subPacketID; /**< 1 `(Client<--Server)` */
    uint8_t success; /**< Equal to 1 if the client was successfully authenticated. Equal to 0 otherwise. */
    char permissionLevel[VARIABLE_LENGTH];
    /**< NULL-terminated string encoded with UTF-8 that contains the permission level granted.
     * For example, "Player", "Trusted", "Admin"
     */
};

/** Sends the client's public key to the server in response to the request for authentication. @ingroup packets */
struct PacketSendPublicKey {
    uint8_t packetID; /**< 65 */
    uint8_t subPacketID; /**< 2 `(Client-->Server)` */
    uint8_t publicKey[32]; /**< Ed25519 public key to be used for authentication. */
};

/** Sends the client a nonce to sign after receiving its public key. @ingroup packets
 * The nonce MUST NOT be likely re-used, and SHOULD be from a secure random source.
 * At least 32 bytes is RECOMMENDED.
 */
struct PacketSendNonce {
    uint8_t packetID; /**< 65 */
    uint8_t subPacketID; /**< 3 `(Client<--Server)` */
    uint8_t nonce[VARIABLE_LENGTH]; /**< Data to be signed with the client's secret key. */
};

/** Sends the server its ENet address, with the nonce appended to it,
 * signed as a detached signature with the client's secret key. @ingroup packets
 **/
struct PacketSendSignature {
    uint8_t packetID; /**< 65 */
    uint8_t subPacketID; /**< 4 `(Client-->Server)` */
    uint8_t signature[64]; /**< The server's ENet address concatenated with the nonce, signed with the client's secret key. */
};

enum PacketType {
    PacketTypeEd25519Authentication = 65
};

enum SubPacketType {
    /* PacketTypeEd25519Authentication */
    SubPacketTypeRequestAuthentication = 0, /* Client<--Server */
    SubPacketTypeEndAuthentication = 1, /* Client<--Server */
    SubPacketTypeSendPublicKey = 2, /* Client-->Server */
    SubPacketTypeSendNonce = 3, /* Client<--Server */
    SubPacketTypeSendSignature = 4 /* Client-->Server */
};
NotAFile commented 12 months ago

I had looked into a design here too a while back. I can try to find my notes again but it avoids a few pitfalls that this is vulnerable to, like a malicious server A replaying a nonce received from server B to receive an authenticated connection. In general, you can't really solve that without distributing server keys to users too, which makes things a bit more involved than your proposal.

DavidCo113 commented 12 months ago

In that case, I think adding the address to the signature should help.

DavidCo113 commented 12 months ago

The source has been updated to include that.

DavidCo113 commented 12 months ago

Nevermind about that, looks like I forgot the possibility of connecting from the LAN.

DavidCo113 commented 12 months ago

Perhaps if the address is also sent in plain-text and checked to be valid, it could work.

DavidCo113 commented 12 months ago

Maybe I could take inspiration from SSH?

DavidCo113 commented 12 months ago

Well, I know one way to make it work, but we would have to add encryption to the protocol.

NotAFile commented 12 months ago

The solution I ended up with was using the server list protocol to send the server keys out-of-band and to derive new client keys per server for security and privacy. But that was part of a more general user authentication scheme, I think things could be simplified from that if one accepts just putting pubkeys in the config like with ssh.

Having some way to encrypt/sign messages would be desirable in general to prevent an attacker on the same network from e.g. injecting malicious commands into the connection, but I think in this day and age of 99% online play over public networks that are unlikely to care about a block game, I think that's an acceptable risk.

DavidCo113 commented 12 months ago

That does create issues when a player attempts to join a server that is not listed, or maybe join from the LAN. With a simple encryption method (or possibly even by signing all packets) however, I do not see a way for a middleman to authenticate to a server with the client's key and also send/receive packets as the client.

There are only two things that I can see the middleman doing:

In addition, it may be worth disallowing other servers on the serverlist from connecting to each other, as long as their IP addresses are not in use by a real player.