Network: Change hash function for non-company passwords

Increase size of per-connection salts, simplify management
Cache per-connection hashes at server end
Send hashes as binary rather than bothering to stringify them
pull/562/head
Jonathan G Rennison 1 year ago
parent 79255c0294
commit c285413853

@ -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 <sstream>
#include <iomanip>
@ -221,6 +223,44 @@ std::string GenerateCompanyPasswordHash(const std::string &password, const std::
return hashed_password.str();
}
struct HashBuffer : public BufferSerialisationHelper<HashBuffer> {
std::vector<byte> &buffer;
HashBuffer(std::vector<byte> &buffer) : buffer(buffer) {}
std::vector<byte> &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<uint8> GenerateGeneralPasswordHash(const std::string &password, const std::string &password_server_id, uint64 password_game_seed)
{
if (password.empty()) return {};
std::vector<byte> 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<byte> 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.

@ -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);

@ -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<uint8> 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);

@ -30,6 +30,7 @@
#include "../core/random_func.hpp"
#include "../rev.h"
#include "../crashlog.h"
#include "../3rdparty/randombytes/randombytes.h"
#include <mutex>
#include <condition_variable>
#if defined(__MINGW32__)
@ -192,6 +193,16 @@ struct PacketWriter : SaveFilter {
};
const std::vector<byte> &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<byte> 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<byte> 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<byte> 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;

@ -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<ServerNetworkGameSocketHandler, PACKET_SERVER_FULL, PACKET_SERVER_BANNED> {
struct CachedPassword {
std::string source;
std::vector<byte> cached_hash;
const std::vector<byte> &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

Loading…
Cancel
Save