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

PP (of sorts): Ed25519 Authentication #51

Open
DavidCo113 opened this issue Jul 7, 2023 · 9 comments
Open

PP (of sorts): Ed25519 Authentication #51

DavidCo113 opened this issue Jul 7, 2023 · 9 comments

Comments

@DavidCo113
Copy link
Contributor

DavidCo113 commented Jul 7, 2023

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
Copy link
Member

NotAFile commented Jul 9, 2023

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
Copy link
Contributor Author

DavidCo113 commented Jul 9, 2023

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

@DavidCo113
Copy link
Contributor Author

The source has been updated to include that.

@DavidCo113
Copy link
Contributor Author

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

@DavidCo113
Copy link
Contributor Author

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

@DavidCo113
Copy link
Contributor Author

Maybe I could take inspiration from SSH?

@DavidCo113
Copy link
Contributor Author

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

@NotAFile
Copy link
Member

NotAFile commented Jul 10, 2023

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
Copy link
Contributor Author

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:

  • The client and the middleman authenticate/encrypt, then (optionally) the middleman and the server authenticate/encrypt, which just authenticates to the server with the middleman's key (or none at all), making the encryption useless but also the authentication. This method requires that the middleman mirror the server, however. Even if it doesn't manage to authenticate with the client's key.
  • The middleman pretends to have the server's key, and makes the client authenticate/encrypt with the server's key, making authentication with the server possible. But now only the client and the server can communicate, not the middleman.

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.

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