From 3d2dc77aa21bd91052cd700bf7576061d86d6d93 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 15 Jun 2023 21:32:15 +0100 Subject: [PATCH] Network: Change protocol for game/rcon/settings password auth Instead of sending a hash, do a DH/X25519 key exchange using the password. This also allows authenticating the associated rcon payload and response. --- src/network/network.cpp | 17 ++++ src/network/network_client.cpp | 128 +++++++++++++++++++------ src/network/network_client.h | 12 +++ src/network/network_func.h | 1 + src/network/network_internal.h | 15 +++ src/network/network_server.cpp | 164 ++++++++++++++++++++++++--------- src/network/network_server.h | 24 ++--- 7 files changed, 280 insertions(+), 81 deletions(-) diff --git a/src/network/network.cpp b/src/network/network.cpp index 0b0b7c6fd2..775362056c 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -1412,6 +1412,23 @@ void NetworkRandomBytesWithFallback(void *buf, size_t bytes) } } +void NetworkGameKeys::Initialise() +{ + assert(!this->inited); + + this->inited = true; + + static_assert(sizeof(this->x25519_priv_key) == 32); + NetworkRandomBytesWithFallback(this->x25519_priv_key, sizeof(this->x25519_priv_key)); + crypto_x25519_public_key(this->x25519_pub_key, this->x25519_priv_key); +} + +NetworkSharedSecrets::~NetworkSharedSecrets() +{ + static_assert(sizeof(*this) == 64); + crypto_wipe(this, sizeof(*this)); +} + #ifdef __EMSCRIPTEN__ extern "C" { diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index a4aaf5c111..b04b8e642b 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -33,6 +33,7 @@ #include "../core/checksum_func.hpp" #include "../fileio_func.h" #include "../debug_settings.h" +#include "../3rdparty/monocypher/monocypher.h" #include "table/strings.h" @@ -40,6 +41,8 @@ /* This file handles all the client-commands */ +static void ResetClientConnectionKeyStates(); + /** Read some packets, and when do use that data as initial load filter. */ struct PacketReader : LoadFilter { static const size_t CHUNK = 32 * 1024; ///< 32 KiB chunks of memory. @@ -174,6 +177,8 @@ ClientNetworkGameSocketHandler::~ClientNetworkGameSocketHandler() FioFCloseFile(this->desync_log_file); this->desync_log_file = nullptr; } + + ResetClientConnectionKeyStates(); } NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvStatus status) @@ -380,12 +385,10 @@ static uint32 last_ack_frame; /** One bit of 'entropy' used to generate a salt for the company passwords. */ static uint32 _company_password_game_seed; -/** One bit of 'entropy' used to generate a salt for the server passwords. */ -static uint64 _server_password_game_seed; -/** One bit of 'entropy' used to generate a salt for the rcon passwords. */ -static uint64 _rcon_password_game_seed; -/** One bit of 'entropy' used to generate a salt for the settings passwords. */ -static uint64 _settings_password_game_seed; +/** Network server's x25519 public key, used for key derivation */ +static byte _server_x25519_pub_key[32]; +/** Key message ID counter */ +static uint64 _next_key_message_id; /** The other bit of 'entropy' used to generate a salt for the server, rcon, and settings passwords. */ static std::string _password_server_id; /** The other bit of 'entropy' used to generate a salt for the company passwords. */ @@ -402,6 +405,60 @@ NetworkJoinInfo _network_join; /** Make sure the server ID length is the same as a md5 hash. */ static_assert(NETWORK_SERVER_ID_LENGTH == 16 * 2 + 1); +NetworkRecvStatus ClientNetworkGameSocketHandler::SendKeyPasswordPacket(PacketType packet_type, NetworkSharedSecrets &ss, const std::string &password, const std::string *payload) +{ + const NetworkGameKeys &keys = this->GetKeys(); + + byte shared_secret[32]; // Shared secret + crypto_x25519(shared_secret, keys.x25519_priv_key, _server_x25519_pub_key); + if (std::count(shared_secret, shared_secret + 32, 0) == 32) { + /* Secret is all 0 because public key is all 0, just give up at this point */ + return NETWORK_RECV_STATUS_MALFORMED_PACKET; + } + + crypto_blake2b_ctx ctx; + crypto_blake2b_init (&ctx, 64); + crypto_blake2b_update(&ctx, shared_secret, 32); // Shared secret + crypto_blake2b_update(&ctx, keys.x25519_pub_key, 32); // Client pub key + crypto_blake2b_update(&ctx, _server_x25519_pub_key, 32); // Server pub key + crypto_blake2b_update(&ctx, (const byte *)password.data(), password.size()); // Password + crypto_blake2b_final (&ctx, ss.shared_data); + + /* NetworkSharedSecrets::shared_data now contains 2 keys worth of hash, first key is used for up direction, second key for down direction (if any) */ + + crypto_wipe(shared_secret, 32); + + std::vector message; + BufferSerialiser buffer(message); + + /* Put monotonically increasing counter in message */ + buffer.Send_uint64(_next_key_message_id); + + /* Put actual payload in message, if there is one */ + if (payload != nullptr) buffer.Send_string(*payload); + + /* Message authentication code */ + uint8 mac[16]; + + /* Use only once per key: random */ + uint8 nonce[24]; + NetworkRandomBytesWithFallback(nonce, 24); + + /* Encrypt in place, use first half of hash as key */ + crypto_aead_lock(message.data(), mac, ss.shared_data, nonce, keys.x25519_pub_key, 32, message.data(), message.size()); + + Packet *p = new Packet(packet_type, SHRT_MAX); + p->Send_binary(keys.x25519_pub_key, 32); + p->Send_binary(nonce, 24); + p->Send_binary(mac, 16); + p->Send_binary(message.data(), message.size()); + + _next_key_message_id++; + + my_client->SendPacket(p); + return NETWORK_RECV_STATUS_OKAY; +} + /*********** * Sending functions * DEF_CLIENT_SEND_COMMAND has no parameters @@ -438,10 +495,8 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendNewGRFsOk() */ NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const std::string &password) { - Packet *p = new Packet(PACKET_CLIENT_GAME_PASSWORD, SHRT_MAX); - p->Send_buffer(GenerateGeneralPasswordHash(password, _password_server_id, _server_password_game_seed)); - my_client->SendPacket(p); - return NETWORK_RECV_STATUS_OKAY; + NetworkSharedSecrets ss; + return my_client->SendKeyPasswordPacket(PACKET_CLIENT_GAME_PASSWORD, ss, password, nullptr); } /** @@ -462,14 +517,14 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendCompanyPassword(const std: */ NetworkRecvStatus ClientNetworkGameSocketHandler::SendSettingsPassword(const std::string &password) { - Packet *p = new Packet(PACKET_CLIENT_SETTINGS_PASSWORD, SHRT_MAX); if (password.empty()) { - p->Send_buffer(nullptr, 0); + Packet *p = new Packet(PACKET_CLIENT_SETTINGS_PASSWORD, SHRT_MAX); + my_client->SendPacket(p); + return NETWORK_RECV_STATUS_OKAY; } else { - p->Send_buffer(GenerateGeneralPasswordHash(password, _password_server_id, _settings_password_game_seed)); + NetworkSharedSecrets ss; + return my_client->SendKeyPasswordPacket(PACKET_CLIENT_SETTINGS_PASSWORD, ss, password, nullptr); } - my_client->SendPacket(p); - return NETWORK_RECV_STATUS_OKAY; } /** Request the map from the server. */ @@ -637,11 +692,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendQuit() */ NetworkRecvStatus ClientNetworkGameSocketHandler::SendRCon(const std::string &pass, const std::string &command) { - Packet *p = new Packet(PACKET_CLIENT_RCON, SHRT_MAX); - p->Send_buffer(GenerateGeneralPasswordHash(pass, _password_server_id, _rcon_password_game_seed)); - p->Send_string(command); - my_client->SendPacket(p); - return NETWORK_RECV_STATUS_OKAY; + return my_client->SendKeyPasswordPacket(PACKET_CLIENT_RCON, my_client->last_rcon_shared_secrets, pass, &command); } /** @@ -836,7 +887,8 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSW if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_GAME) return NETWORK_RECV_STATUS_MALFORMED_PACKET; this->status = STATUS_AUTH_GAME; - _server_password_game_seed = p->Recv_uint64(); + static_assert(sizeof(_server_x25519_pub_key) == 32); + p->Recv_binary(_server_x25519_pub_key, sizeof(_server_x25519_pub_key)); _password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH); if (this->HasClientQuit()) return NETWORK_RECV_STATUS_MALFORMED_PACKET; @@ -876,9 +928,8 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet /* Initialize the password hash salting variables, even if they were previously. */ _company_password_game_seed = p->Recv_uint32(); - _server_password_game_seed = p->Recv_uint64(); - _rcon_password_game_seed = p->Recv_uint64(); - _settings_password_game_seed = p->Recv_uint64(); + static_assert(sizeof(_server_x25519_pub_key) == 32); + p->Recv_binary(_server_x25519_pub_key, sizeof(_server_x25519_pub_key)); _password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH); _company_password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH); @@ -1250,12 +1301,26 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_RCON(Packet *p) { if (this->status < STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET; - TextColour colour_code = (TextColour)p->Recv_uint16(); - if (!IsValidConsoleColour(colour_code)) return NETWORK_RECV_STATUS_MALFORMED_PACKET; + if (!p->CanReadFromPacket(1)) { + IConsolePrint(CC_ERROR, "Access Denied"); + return NETWORK_RECV_STATUS_OKAY; + } - std::string rcon_out = p->Recv_string(NETWORK_RCONCOMMAND_LENGTH); + byte nonce[24]; + byte mac[16]; + p->Recv_binary(nonce, 24); + p->Recv_binary(mac, 16); - IConsolePrint(colour_code, rcon_out.c_str()); + std::vector message = p->Recv_binary(p->RemainingBytesToTransfer()); + + if (crypto_aead_unlock(message.data(), mac, this->last_rcon_shared_secrets.shared_data + 32, nonce, nullptr, 0, message.data(), message.size()) == 0) { + SubPacketDeserialiser spd(p, message); + TextColour colour_code = (TextColour)spd.Recv_uint16(); + if (!IsValidConsoleColour(colour_code)) return NETWORK_RECV_STATUS_MALFORMED_PACKET; + + std::string rcon_out = spd.Recv_string(NETWORK_RCONCOMMAND_LENGTH); + IConsolePrint(colour_code, rcon_out.c_str()); + } return NETWORK_RECV_STATUS_OKAY; } @@ -1373,6 +1438,12 @@ std::string ClientNetworkGameSocketHandler::GetDebugInfo() const return stdstr_fmt("status: %d (%s)", this->status, GetServerStatusName(this->status)); } +static void ResetClientConnectionKeyStates() +{ + _next_key_message_id = 0; + crypto_wipe(_server_x25519_pub_key, sizeof(_server_x25519_pub_key)); +} + /** Is called after a client is connected to the server */ void NetworkClient_Connected() @@ -1381,6 +1452,7 @@ void NetworkClient_Connected() _frame_counter = 0; _frame_counter_server = 0; last_ack_frame = 0; + ResetClientConnectionKeyStates(); /* Request the game-info */ MyClient::SendJoin(); } diff --git a/src/network/network_client.h b/src/network/network_client.h index ede5f48bf1..4044e196f0 100644 --- a/src/network/network_client.h +++ b/src/network/network_client.h @@ -18,6 +18,7 @@ private: std::string connection_string; ///< Address we are connected to. struct PacketReader *savegame; ///< Packet reader for reading the savegame. byte token; ///< The token we need to send back to the server to prove we're the right client. + NetworkSharedSecrets last_rcon_shared_secrets; ///< Keys for last rcon (and incoming replies) /** Status of the connection with the server. */ enum ServerStatus { @@ -40,6 +41,8 @@ private: std::string server_desync_log; bool emergency_save_done = false; + NetworkGameKeys intl_keys; + static const char *GetServerStatusName(ServerStatus status); protected: @@ -81,6 +84,9 @@ protected: static NetworkRecvStatus SendGetMap(); static NetworkRecvStatus SendMapOk(); void CheckConnection(); + + NetworkRecvStatus SendKeyPasswordPacket(PacketType packet_type, NetworkSharedSecrets &ss, const std::string &password, const std::string *payload); + public: ClientNetworkGameSocketHandler(SOCKET s, std::string connection_string); ~ClientNetworkGameSocketHandler(); @@ -90,6 +96,12 @@ public: std::string GetDebugInfo() const override; + const NetworkGameKeys &GetKeys() + { + if (!this->intl_keys.inited) this->intl_keys.Initialise(); + return this->intl_keys; + } + static NetworkRecvStatus SendJoin(); static NetworkRecvStatus SendCommand(const CommandPacket *cp); static NetworkRecvStatus SendError(NetworkErrorCode errorno, NetworkRecvStatus recvstatus = NETWORK_RECV_STATUS_OKAY); diff --git a/src/network/network_func.h b/src/network/network_func.h index 90fb5a16ca..ae695f99a6 100644 --- a/src/network/network_func.h +++ b/src/network/network_func.h @@ -84,6 +84,7 @@ bool NetworkServerChangeClientName(ClientID client_id, const std::string &new_na void NetworkServerDoMove(ClientID client_id, CompanyID company_id); void NetworkServerSendRcon(ClientID client_id, TextColour colour_code, const std::string &string); +void NetworkServerSendRconDenied(ClientID client_id); void NetworkServerSendChat(NetworkAction action, DestType type, int dest, const std::string &msg, ClientID from_id, NetworkTextMessageData data = NetworkTextMessageData(), bool from_admin = false); void NetworkServerSendExternalChat(const std::string &source, TextColour colour, const std::string &user, const std::string &msg); diff --git a/src/network/network_internal.h b/src/network/network_internal.h index b64e045b8a..f41a97f529 100644 --- a/src/network/network_internal.h +++ b/src/network/network_internal.h @@ -122,6 +122,20 @@ struct NetworkGameList *NetworkAddServer(const std::string &connection_string, b void NetworkRebuildHostList(); void UpdateNetworkGameWindow(); +struct NetworkGameKeys { + byte x25519_priv_key[32]; ///< x25519 key: private part + byte x25519_pub_key[32]; ///< x25519 key: public part + bool inited = false; + + void Initialise(); +}; + +struct NetworkSharedSecrets { + byte shared_data[64]; + + ~NetworkSharedSecrets(); +}; + /* From network_command.cpp */ /** * Everything we need to know about a command to be able to execute it. @@ -148,6 +162,7 @@ StringID GetNetworkErrorMsg(NetworkErrorCode err); bool NetworkMakeClientNameUnique(std::string &new_name); std::string GenerateCompanyPasswordHash(const std::string &password, const std::string &password_server_id, uint32 password_game_seed); std::vector GenerateGeneralPasswordHash(const std::string &password, const std::string &password_server_id, uint64 password_game_seed); +std::string BytesToHexString(const byte *data, uint length); std::string NetworkGenerateRandomKeyString(uint bytes); std::string_view ParseCompanyFromConnectionString(const std::string &connection_string, CompanyID *company_id); diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index c47c457be8..a01812b8e2 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -31,6 +31,7 @@ #include "../rev.h" #include "../crashlog.h" #include "../3rdparty/randombytes/randombytes.h" +#include "../3rdparty/monocypher/monocypher.h" #include #include #if defined(__MINGW32__) @@ -193,16 +194,6 @@ struct PacketWriter : SaveFilter { }; -const std::vector &ServerNetworkGameSocketHandler::CachedPassword::GetHash(const std::string &password, uint64 password_game_seed) -{ - if (password != this->source) { - this->source = password; - this->cached_hash = GenerateGeneralPasswordHash(password, _settings_client.network.network_id, password_game_seed); - } - - return this->cached_hash; -} - /** * Create a new socket for the server side of the game connection. * @param s The socket to connect with. @@ -213,14 +204,6 @@ ServerNetworkGameSocketHandler::ServerNetworkGameSocketHandler(SOCKET s) : Netwo this->client_id = _network_client_id++; this->receive_limit = _settings_client.network.bytes_per_frame_burst; - uint64 seeds[3]; - static_assert(sizeof(seeds) == 24); - NetworkRandomBytesWithFallback(seeds, sizeof(seeds)); - - this->server_hash_bits = seeds[0]; - this->rcon_hash_bits = seeds[1]; - this->settings_hash_bits = seeds[2]; - /* The Socket and Info pools need to be the same in size. After all, * each Socket will be associated with at most one Info object. As * such if the Socket was allocated the Info object can as well. */ @@ -244,6 +227,61 @@ ServerNetworkGameSocketHandler::~ServerNetworkGameSocketHandler() } } +bool ServerNetworkGameSocketHandler::ParseKeyPasswordPacket(Packet *p, NetworkSharedSecrets &ss, const std::string &password, std::string *payload, size_t length) +{ + byte client_pub_key[32]; + byte nonce[24]; + byte mac[16]; + p->Recv_binary(client_pub_key, 32); + p->Recv_binary(nonce, 24); + p->Recv_binary(mac, 16); + + const NetworkGameKeys &keys = this->GetKeys(); + + byte shared_secret[32]; // Shared secret + crypto_x25519(shared_secret, keys.x25519_priv_key, client_pub_key); + if (std::count(shared_secret, shared_secret + 32, 0) == 32) { + /* Secret is all 0 because public key is all 0, just reject it */ + return false; + } + + crypto_blake2b_ctx ctx; + crypto_blake2b_init (&ctx, 64); + crypto_blake2b_update(&ctx, shared_secret, 32); // Shared secret + crypto_blake2b_update(&ctx, client_pub_key, 32); // Client pub key + crypto_blake2b_update(&ctx, keys.x25519_pub_key, 32); // Server pub key + crypto_blake2b_update(&ctx, (const byte *)password.data(), password.size()); // Password + crypto_blake2b_final (&ctx, ss.shared_data); + + /* NetworkSharedSecrets::shared_data now contains 2 keys worth of hash, first key is used for up direction, second key for down direction (if any) */ + + crypto_wipe(shared_secret, 32); + + std::vector message = p->Recv_binary(p->RemainingBytesToTransfer()); + if (message.size() < 8) return false; + if ((message.size() == 8) != (payload == nullptr)) { + /* Payload expected but not present, or vice versa, just give up */ + return false; + } + + /* Decrypt in place, use first half of hash as key */ + if (crypto_aead_unlock(message.data(), mac, ss.shared_data, nonce, client_pub_key, 32, message.data(), message.size()) == 0) { + SubPacketDeserialiser spd(p, message); + uint64 message_id = spd.Recv_uint64(); + if (message_id < this->min_key_message_id) { + /* ID has not increased monotonically, reject the message */ + return false; + } + this->min_key_message_id = message_id + 1; + if (payload != nullptr) { + *payload = spd.Recv_string(length); + } + return true; + } + + return false; +} + std::unique_ptr ServerNetworkGameSocketHandler::ReceivePacket() { /* Only allow receiving when we have some buffer free; this value @@ -487,8 +525,11 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNeedGamePassword() /* Reset 'lag' counters */ this->last_frame = this->last_frame_server = _frame_counter; + const NetworkGameKeys &keys = this->GetKeys(); + Packet *p = new Packet(PACKET_SERVER_NEED_GAME_PASSWORD, SHRT_MAX); - p->Send_uint64(this->server_hash_bits); + static_assert(sizeof(keys.x25519_pub_key) == 32); + p->Send_binary(keys.x25519_pub_key, sizeof(keys.x25519_pub_key)); p->Send_string(_settings_client.network.network_id); this->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; @@ -525,12 +566,13 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendWelcome() _network_game_info.clients_on++; + const NetworkGameKeys &keys = this->GetKeys(); + p = new Packet(PACKET_SERVER_WELCOME, SHRT_MAX); p->Send_uint32(this->client_id); p->Send_uint32(_settings_game.game_creation.generation_seed); - p->Send_uint64(this->server_hash_bits); - p->Send_uint64(this->rcon_hash_bits); - p->Send_uint64(this->settings_hash_bits); + static_assert(sizeof(keys.x25519_pub_key) == 32); + p->Send_binary(keys.x25519_pub_key, sizeof(keys.x25519_pub_key)); p->Send_string(_settings_client.network.network_id); p->Send_string(_network_company_server_id); this->SendPacket(p); @@ -804,10 +846,38 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGame() */ NetworkRecvStatus ServerNetworkGameSocketHandler::SendRConResult(uint16 colour, const std::string &command) { - Packet *p = new Packet(PACKET_SERVER_RCON, SHRT_MAX); + assert(this->rcon_reply_key != nullptr); - p->Send_uint16(colour); - p->Send_string(command); + std::vector message; + BufferSerialiser buffer(message); + buffer.Send_uint16(colour); + buffer.Send_string(command); + + /* Message authentication code */ + uint8 mac[16]; + + /* Use only once per key: random */ + uint8 nonce[24]; + NetworkRandomBytesWithFallback(nonce, 24); + + /* Encrypt in place, use first half of hash as key */ + crypto_aead_lock(message.data(), mac, this->rcon_reply_key, nonce, nullptr, 0, message.data(), message.size()); + + Packet *p = new Packet(PACKET_SERVER_RCON, SHRT_MAX); + p->Send_binary(nonce, 24); + p->Send_binary(mac, 16); + p->Send_binary(message.data(), message.size()); + + this->SendPacket(p); + return NETWORK_RECV_STATUS_OKAY; +} + +/** + * Send a deny result of a console action. + */ +NetworkRecvStatus ServerNetworkGameSocketHandler::SendRConDenied() +{ + Packet *p = new Packet(PACKET_SERVER_RCON, SHRT_MAX); this->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; } @@ -979,13 +1049,13 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_GAME_PASSWORD(P return this->SendError(NETWORK_ERROR_NOT_EXPECTED); } - std::vector password = p->Recv_buffer(); - /* Check game password. Allow joining if we cleared the password meanwhile */ - if (!_settings_client.network.server_password.empty() && - password != this->game_password_hash_cache.GetHash(_settings_client.network.server_password, this->server_hash_bits)) { - /* Password is invalid */ - return this->SendError(NETWORK_ERROR_WRONG_PASSWORD); + if (!_settings_client.network.server_password.empty()) { + NetworkSharedSecrets ss; + if (!this->ParseKeyPasswordPacket(p, ss, _settings_client.network.server_password, nullptr, 0)) { + /* Password is invalid */ + return this->SendError(NETWORK_ERROR_WRONG_PASSWORD); + } } const NetworkClientInfo *ci = this->GetInfo(); @@ -1025,16 +1095,16 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_SETTINGS_PASSWO return this->SendError(NETWORK_ERROR_NOT_EXPECTED); } - std::vector password = p->Recv_buffer(); + NetworkSharedSecrets ss; /* Check settings password. Deny if no password is set */ - if (password.empty()) { + if (!p->CanReadFromPacket(1)) { if (this->settings_authed) DEBUG(net, 0, "[settings-ctrl] client-id %d deauthed", this->client_id); this->settings_authed = false; } else if (_settings_client.network.settings_password.empty() || - password != this->settings_password_hash_cache.GetHash(_settings_client.network.settings_password, this->settings_hash_bits)) { + !this->ParseKeyPasswordPacket(p, ss, _settings_client.network.settings_password, nullptr, 0)) { DEBUG(net, 0, "[settings-ctrl] wrong password from client-id %d", this->client_id); - NetworkServerSendRcon(this->client_id, CC_ERROR, "Access Denied"); + NetworkServerSendRconDenied(this->client_id); this->settings_authed = false; NetworkRecvStatus status = this->HandleAuthFailure(this->settings_auth_failures); if (status != NETWORK_RECV_STATUS_OKAY) return status; @@ -1591,25 +1661,26 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_RCON(Packet *p) if (this->status != STATUS_ACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED); if (_settings_client.network.rcon_password.empty()) { - NetworkServerSendRcon(this->client_id, CC_ERROR, "Access Denied"); + NetworkServerSendRconDenied(this->client_id); return this->HandleAuthFailure(this->rcon_auth_failures); } - std::vector password = p->Recv_buffer(); - std::string command = p->Recv_string(NETWORK_RCONCOMMAND_LENGTH); - - if (password != this->rcon_password_hash_cache.GetHash(_settings_client.network.rcon_password, this->rcon_hash_bits)) { + std::string command; + NetworkSharedSecrets ss; + if (!this->ParseKeyPasswordPacket(p, ss, _settings_client.network.rcon_password, &command, NETWORK_RCONCOMMAND_LENGTH)) { DEBUG(net, 0, "[rcon] wrong password from client-id %d", this->client_id); - NetworkServerSendRcon(this->client_id, CC_ERROR, "Access Denied"); + NetworkServerSendRconDenied(this->client_id); return this->HandleAuthFailure(this->rcon_auth_failures); } DEBUG(net, 3, "[rcon] Client-id %d executed: %s", this->client_id, command.c_str()); _redirect_console_to_client = this->client_id; + this->rcon_reply_key = ss.shared_data + 32; /* second key */ IConsoleCmdExec(command.c_str()); _redirect_console_to_client = INVALID_CLIENT_ID; this->rcon_auth_failures = 0; + this->rcon_reply_key = nullptr; return NETWORK_RECV_STATUS_OKAY; } @@ -2204,6 +2275,17 @@ void NetworkServerSendRcon(ClientID client_id, TextColour colour_code, const std NetworkClientSocket::GetByClientID(client_id)->SendRConResult(colour_code, string); } +/** + * Send an rcon reply to the client. + * @param client_id The identifier of the client. + * @param colour_code The colour of the text. + * @param string The actual reply. + */ +void NetworkServerSendRconDenied(ClientID client_id) +{ + NetworkClientSocket::GetByClientID(client_id)->SendRConDenied(); +} + /** * Kick a single client. * @param client_id The client to kick. diff --git a/src/network/network_server.h b/src/network/network_server.h index b0caabdb1d..c9c74c2854 100644 --- a/src/network/network_server.h +++ b/src/network/network_server.h @@ -22,15 +22,9 @@ extern NetworkClientSocketPool _networkclientsocket_pool; /** Class for handling the server side of the game connection. */ class ServerNetworkGameSocketHandler : public NetworkClientSocketPool::PoolItem<&_networkclientsocket_pool>, public NetworkGameSocketHandler, public TCPListenHandler { - struct CachedPassword { - std::string source; - std::vector cached_hash; - - const std::vector &GetHash(const std::string &password, uint64 password_game_seed); - }; - CachedPassword game_password_hash_cache; - CachedPassword rcon_password_hash_cache; - CachedPassword settings_password_hash_cache; + NetworkGameKeys intl_keys; + uint64 min_key_message_id = 0; + byte *rcon_reply_key = nullptr; protected: NetworkRecvStatus Receive_CLIENT_JOIN(Packet *p) override; @@ -61,6 +55,8 @@ protected: NetworkRecvStatus SendNeedGamePassword(); NetworkRecvStatus SendNeedCompanyPassword(); + bool ParseKeyPasswordPacket(Packet *p, NetworkSharedSecrets &ss, const std::string &password, std::string *payload, size_t length); + public: /** Status of a client */ enum ClientStatus { @@ -86,9 +82,6 @@ public: ClientStatus status; ///< Status of this client CommandQueue outgoing_queue; ///< The command-queue awaiting delivery size_t receive_limit; ///< Amount of bytes that we can receive at this moment - uint64 server_hash_bits; ///< Server password hash entropy bits - uint64 rcon_hash_bits; ///< Rcon password hash entropy bits - uint64 settings_hash_bits; ///< Settings password hash entropy bits bool settings_authed = false;///< Authorised to control all game settings bool supports_zstd = false; ///< Client supports zstd compression @@ -119,6 +112,7 @@ public: NetworkRecvStatus SendShutdown(); NetworkRecvStatus SendNewGame(); NetworkRecvStatus SendRConResult(uint16 colour, const std::string &command); + NetworkRecvStatus SendRConDenied(); NetworkRecvStatus SendMove(ClientID client_id, CompanyID company_id); NetworkRecvStatus SendClientInfo(NetworkClientInfo *ci); @@ -138,6 +132,12 @@ public: std::string GetDebugInfo() const override; + const NetworkGameKeys &GetKeys() + { + if (!this->intl_keys.inited) this->intl_keys.Initialise(); + return this->intl_keys; + } + static void Send(); static void AcceptConnection(SOCKET s, const NetworkAddress &address); static bool AllowConnection();