diff --git a/src/network/network.cpp b/src/network/network.cpp index 5fe99cadad..6c457036ea 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -37,7 +37,9 @@ #include "../error.h" #include "../core/checksum_func.hpp" #include "../string_func_extra.h" +#include "../core/serialisation.hpp" #include "../3rdparty/randombytes/randombytes.h" +#include "../3rdparty/monocypher/monocypher.h" #include "../settings_internal.h" #include #include @@ -221,6 +223,44 @@ std::string GenerateCompanyPasswordHash(const std::string &password, const std:: return hashed_password.str(); } +struct HashBuffer : public BufferSerialisationHelper { + std::vector &buffer; + + HashBuffer(std::vector &buffer) : buffer(buffer) {} + + std::vector &GetSerialisationBuffer() { return this->buffer; } + size_t GetSerialisationLimit() const { return UINT32_MAX; } +}; + +/** + * Hash the given password using server ID and game seed. + * @param password Password to hash. + * @param password_server_id Server ID. + * @param password_game_seed Game seed. + * @return The hashed password. + */ +std::vector GenerateGeneralPasswordHash(const std::string &password, const std::string &password_server_id, uint64 password_game_seed) +{ + if (password.empty()) return {}; + + std::vector data; + data.reserve(password.size() + password_server_id.size() + 6); + HashBuffer buffer(data); + + /* key field */ + buffer.Send_uint64(password_game_seed); + + /* message field */ + buffer.Send_string(password_server_id); + buffer.Send_string(password); + + std::vector output; + output.resize(64); + crypto_blake2b_general(output.data(), output.size(), data.data(), 8, data.data() + 8, data.size() - 8); + + return output; +} + /** * Check if the company we want to join requires a password. * @param company_id id of the company we want to check the 'passworded' flag for. diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 976f9f6837..9d405eb5d8 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -381,11 +381,11 @@ 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 uint32 _server_password_game_seed; +static uint64 _server_password_game_seed; /** One bit of 'entropy' used to generate a salt for the rcon passwords. */ -static uint32 _rcon_password_game_seed; +static uint64 _rcon_password_game_seed; /** One bit of 'entropy' used to generate a salt for the settings passwords. */ -static uint32 _settings_password_game_seed; +static uint64 _settings_password_game_seed; /** 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. */ @@ -439,7 +439,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendNewGRFsOk() NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const std::string &password) { Packet *p = new Packet(PACKET_CLIENT_GAME_PASSWORD, SHRT_MAX); - p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _server_password_game_seed)); + p->Send_buffer(GenerateGeneralPasswordHash(password, _password_server_id, _server_password_game_seed)); my_client->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; } @@ -464,9 +464,9 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendSettingsPassword(const std { Packet *p = new Packet(PACKET_CLIENT_SETTINGS_PASSWORD, SHRT_MAX); if (password.empty()) { - p->Send_string(""); + p->Send_buffer(nullptr, 0); } else { - p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _settings_password_game_seed)); + p->Send_buffer(GenerateGeneralPasswordHash(password, _password_server_id, _settings_password_game_seed)); } my_client->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; @@ -638,7 +638,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_string(GenerateCompanyPasswordHash(pass, _password_server_id, _rcon_password_game_seed)); + 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; @@ -836,7 +836,7 @@ 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_uint32(); + _server_password_game_seed = p->Recv_uint64(); _password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH); if (this->HasClientQuit()) return NETWORK_RECV_STATUS_MALFORMED_PACKET; @@ -876,9 +876,9 @@ 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_uint32(); - _rcon_password_game_seed = p->Recv_uint32(); - _settings_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(); _password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH); _company_password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH); diff --git a/src/network/network_internal.h b/src/network/network_internal.h index 7335fccf3a..1b37aa6966 100644 --- a/src/network/network_internal.h +++ b/src/network/network_internal.h @@ -147,6 +147,7 @@ uint NetworkCalculateLag(const NetworkClientSocket *cs); 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 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 21a3ae8dc3..8d5ce6a5c3 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -30,6 +30,7 @@ #include "../core/random_func.hpp" #include "../rev.h" #include "../crashlog.h" +#include "../3rdparty/randombytes/randombytes.h" #include #include #if defined(__MINGW32__) @@ -192,6 +193,16 @@ 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. @@ -201,9 +212,17 @@ ServerNetworkGameSocketHandler::ServerNetworkGameSocketHandler(SOCKET s) : Netwo this->status = STATUS_INACTIVE; this->client_id = _network_client_id++; this->receive_limit = _settings_client.network.bytes_per_frame_burst; - this->server_hash_bits = InteractiveRandom(); - this->rcon_hash_bits = InteractiveRandom(); - this->settings_hash_bits = InteractiveRandom(); + + uint64 seeds[3]; + if (randombytes(&seeds, sizeof(uint64) * lengthof(seeds)) < 0) { + /* Can't get random data, use InteractiveRandom */ + for (uint64 &seed : seeds) { + seed = (uint64)(InteractiveRandom()) | (((uint64)(InteractiveRandom())) << 32); + } + } + 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 @@ -472,7 +491,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNeedGamePassword() this->last_frame = this->last_frame_server = _frame_counter; Packet *p = new Packet(PACKET_SERVER_NEED_GAME_PASSWORD, SHRT_MAX); - p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->server_hash_bits); + p->Send_uint64(this->server_hash_bits); p->Send_string(_settings_client.network.network_id); this->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; @@ -512,9 +531,9 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendWelcome() 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_uint32(_settings_game.game_creation.generation_seed ^ this->server_hash_bits); - p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->rcon_hash_bits); - p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->settings_hash_bits); + p->Send_uint64(this->server_hash_bits); + p->Send_uint64(this->rcon_hash_bits); + p->Send_uint64(this->settings_hash_bits); p->Send_string(_settings_client.network.network_id); p->Send_string(_network_company_server_id); this->SendPacket(p); @@ -963,11 +982,11 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_GAME_PASSWORD(P return this->SendError(NETWORK_ERROR_NOT_EXPECTED); } - std::string password = p->Recv_string(NETWORK_PASSWORD_LENGTH); + 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 != GenerateCompanyPasswordHash(_settings_client.network.server_password.c_str(), _settings_client.network.network_id.c_str(), _settings_game.game_creation.generation_seed ^ this->server_hash_bits)) { + 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); } @@ -1009,14 +1028,14 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_SETTINGS_PASSWO return this->SendError(NETWORK_ERROR_NOT_EXPECTED); } - std::string password = p->Recv_string(NETWORK_PASSWORD_LENGTH); + std::vector password = p->Recv_buffer(); /* Check settings password. Deny if no password is set */ if (password.empty()) { 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 != GenerateCompanyPasswordHash(_settings_client.network.settings_password.c_str(), _settings_client.network.network_id.c_str(), _settings_game.game_creation.generation_seed ^ this->settings_hash_bits)) { + password != this->settings_password_hash_cache.GetHash(_settings_client.network.settings_password, this->settings_hash_bits)) { DEBUG(net, 0, "[settings-ctrl] wrong password from client-id %d", this->client_id); NetworkServerSendRcon(this->client_id, CC_ERROR, "Access Denied"); this->settings_authed = false; @@ -1576,10 +1595,10 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_RCON(Packet *p) return NETWORK_RECV_STATUS_OKAY; } - std::string password = p->Recv_string(NETWORK_PASSWORD_LENGTH); + std::vector password = p->Recv_buffer(); std::string command = p->Recv_string(NETWORK_RCONCOMMAND_LENGTH); - if (password != GenerateCompanyPasswordHash(_settings_client.network.rcon_password.c_str(), _settings_client.network.network_id.c_str(), _settings_game.game_creation.generation_seed ^ this->rcon_hash_bits)) { + if (password != this->rcon_password_hash_cache.GetHash(_settings_client.network.rcon_password, this->rcon_hash_bits)) { DEBUG(net, 0, "[rcon] wrong password from client-id %d", this->client_id); NetworkServerSendRcon(this->client_id, CC_ERROR, "Access Denied"); return NETWORK_RECV_STATUS_OKAY; diff --git a/src/network/network_server.h b/src/network/network_server.h index 59b7980330..19e2dbe594 100644 --- a/src/network/network_server.h +++ b/src/network/network_server.h @@ -22,6 +22,16 @@ 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; + protected: NetworkRecvStatus Receive_CLIENT_JOIN(Packet *p) override; NetworkRecvStatus Receive_CLIENT_GAME_INFO(Packet *p) override; @@ -76,9 +86,9 @@ 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 - uint32 server_hash_bits; ///< Server password hash entropy bits - uint32 rcon_hash_bits; ///< Rcon password hash entropy bits - uint32 settings_hash_bits; ///< Settings password hash entropy bits + 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