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
This commit is contained in:
@@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user