Store encrypted company password hashes in server saves
Restore when loading back into the server if server has required secret
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
#include "../core/checksum_func.hpp"
|
||||
#include "../string_func_extra.h"
|
||||
#include "../3rdparty/randombytes/randombytes.h"
|
||||
#include "../settings_internal.h"
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
@@ -64,6 +65,8 @@ bool _is_network_server; ///< Does this client wants to be a network-server?
|
||||
bool _network_settings_access; ///< Can this client change server settings?
|
||||
NetworkCompanyState *_network_company_states = nullptr; ///< Statistics about some companies.
|
||||
std::string _network_company_server_id; ///< Server ID string used for company passwords
|
||||
uint8 _network_company_password_storage_token[16]; ///< Non-secret token for storage of company passwords in savegames
|
||||
uint8 _network_company_password_storage_key[32]; ///< Key for storage of company passwords in savegames
|
||||
ClientID _network_own_client_id; ///< Our client identifier.
|
||||
ClientID _redirect_console_to_client; ///< If not invalid, redirect the console output to a client.
|
||||
uint8 _network_reconnect; ///< Reconnect timeout
|
||||
@@ -920,7 +923,7 @@ bool NetworkServerStart()
|
||||
NetworkUDPServerListen();
|
||||
|
||||
_network_company_states = new NetworkCompanyState[MAX_COMPANIES];
|
||||
_network_company_server_id = NetworkGenerateRandomKeyString();
|
||||
_network_company_server_id = NetworkGenerateRandomKeyString(16);
|
||||
_network_server = true;
|
||||
_networking = true;
|
||||
_frame_counter = 0;
|
||||
@@ -1261,24 +1264,27 @@ static void NetworkGenerateServerId()
|
||||
_settings_client.network.network_id = hex_output;
|
||||
}
|
||||
|
||||
std::string NetworkGenerateRandomKeyString()
|
||||
std::string NetworkGenerateRandomKeyString(uint bytes)
|
||||
{
|
||||
uint8 key[16];
|
||||
char hex_output[16 * 2 + 1];
|
||||
uint8 *key = AllocaM(uint8, bytes);
|
||||
char *hex_output = AllocaM(char, bytes * 2);
|
||||
|
||||
if (randombytes(key, 16) < 0) {
|
||||
if (randombytes(key, bytes) < 0) {
|
||||
/* Fallback poor-quality random */
|
||||
DEBUG(misc, 0, "High quality random source unavailable");
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (uint i = 0; i < bytes; i++) {
|
||||
key[i] = (uint8)InteractiveRandom();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
seprintf(hex_output + i * 2, lastof(hex_output), "%02x", key[i]);
|
||||
char txt[3];
|
||||
for (uint i = 0; i < bytes; ++i) {
|
||||
seprintf(txt, lastof(txt), "%02x", key[i]);
|
||||
hex_output[i * 2] = txt[0];
|
||||
hex_output[i * 2 + 1] = txt[1];
|
||||
}
|
||||
|
||||
return std::string(hex_output);
|
||||
return std::string(hex_output, hex_output + bytes * 2);
|
||||
}
|
||||
|
||||
class TCPNetworkDebugConnecter : TCPConnecter {
|
||||
@@ -1319,6 +1325,11 @@ void NetworkStartUp()
|
||||
/* Generate an server id when there is none yet */
|
||||
if (_settings_client.network.network_id.empty()) NetworkGenerateServerId();
|
||||
|
||||
if (_settings_client.network.company_password_storage_token.empty() || _settings_client.network.company_password_storage_secret.empty()) {
|
||||
SetSettingValue(GetSettingFromName("network.company_password_storage_token")->AsStringSetting(), NetworkGenerateRandomKeyString(16));
|
||||
SetSettingValue(GetSettingFromName("network.company_password_storage_secret")->AsStringSetting(), NetworkGenerateRandomKeyString(32));
|
||||
}
|
||||
|
||||
_network_game_info = {};
|
||||
|
||||
NetworkInitialize();
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
extern NetworkCompanyState *_network_company_states;
|
||||
extern std::string _network_company_server_id;
|
||||
extern uint8 _network_company_password_storage_token[16];
|
||||
extern uint8 _network_company_password_storage_key[32];
|
||||
|
||||
extern ClientID _network_own_client_id;
|
||||
extern ClientID _redirect_console_to_client;
|
||||
|
||||
@@ -131,7 +131,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::string NetworkGenerateRandomKeyString();
|
||||
std::string NetworkGenerateRandomKeyString(uint bytes);
|
||||
|
||||
std::string_view ParseCompanyFromConnectionString(const std::string &connection_string, CompanyID *company_id);
|
||||
NetworkAddress ParseConnectionString(const std::string &connection_string, uint16 default_port);
|
||||
|
||||
@@ -16,8 +16,14 @@
|
||||
#include "../station_base.h"
|
||||
#include "../settings_func.h"
|
||||
#include "../strings_func.h"
|
||||
#include "../network/network.h"
|
||||
#include "../network/network_func.h"
|
||||
#include "../network/network_server.h"
|
||||
#include "../3rdparty/randombytes/randombytes.h"
|
||||
#include "../3rdparty/monocypher/monocypher.h"
|
||||
|
||||
#include "saveload.h"
|
||||
#include "saveload_buffer.h"
|
||||
|
||||
#include "table/strings.h"
|
||||
|
||||
@@ -557,9 +563,107 @@ static void Save_PLYX()
|
||||
SaveSettingsPlyx();
|
||||
}
|
||||
|
||||
static void Load_PLYP()
|
||||
{
|
||||
size_t size = SlGetFieldLength();
|
||||
if (size <= 16 + 24 + 16 || !_network_server) {
|
||||
SlSkipBytes(size);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8 token[16];
|
||||
ReadBuffer::GetCurrent()->CopyBytes(token, 16);
|
||||
if (memcmp(token, _network_company_password_storage_token, 16) != 0) {
|
||||
DEBUG(sl, 2, "Skipping encrypted company passwords");
|
||||
SlSkipBytes(size - 16);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8 nonce[24];
|
||||
uint8 mac[16];
|
||||
ReadBuffer::GetCurrent()->CopyBytes(nonce, 24);
|
||||
ReadBuffer::GetCurrent()->CopyBytes(mac, 16);
|
||||
|
||||
std::vector<uint8> buffer(size - 16 - 24 - 16);
|
||||
ReadBuffer::GetCurrent()->CopyBytes(buffer.data(), buffer.size());
|
||||
|
||||
if (crypto_unlock(buffer.data(), _network_company_password_storage_key, nonce, mac, buffer.data(), buffer.size()) == 0) {
|
||||
SlLoadFromBuffer(buffer.data(), buffer.size(), [](void *) {
|
||||
_network_company_server_id.resize(SlReadUint32());
|
||||
ReadBuffer::GetCurrent()->CopyBytes((uint8 *)_network_company_server_id.data(), _network_company_server_id.size());
|
||||
|
||||
while (true) {
|
||||
uint16 cid = SlReadUint16();
|
||||
if (cid >= MAX_COMPANIES) break;
|
||||
std::string password;
|
||||
password.resize(SlReadUint32());
|
||||
ReadBuffer::GetCurrent()->CopyBytes((uint8 *)password.data(), password.size());
|
||||
NetworkServerSetCompanyPassword((CompanyID)cid, password, true);
|
||||
}
|
||||
|
||||
ReadBuffer::GetCurrent()->SkipBytes(SlReadByte()); // Skip padding
|
||||
}, nullptr);
|
||||
DEBUG(sl, 2, "Decrypted company passwords");
|
||||
} else {
|
||||
DEBUG(sl, 2, "Failed to decrypt company passwords");
|
||||
}
|
||||
}
|
||||
|
||||
static void Save_PLYP()
|
||||
{
|
||||
if (!_network_server || IsNetworkServerSave()) {
|
||||
SlSetLength(0);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8 nonce[24]; /* Use only once per key: random */
|
||||
if (randombytes(nonce, 24) < 0) {
|
||||
/* Can't get a random nonce, just give up */
|
||||
SlSetLength(0);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<byte> buffer = SlSaveToVector([](void *) {
|
||||
SlWriteUint32(_network_company_server_id.size());
|
||||
MemoryDumper::GetCurrent()->CopyBytes((const uint8 *)_network_company_server_id.data(), _network_company_server_id.size());
|
||||
|
||||
for (const Company *c : Company::Iterate()) {
|
||||
SlWriteUint16(c->index);
|
||||
|
||||
const std::string &password = _network_company_states[c->index].password;
|
||||
SlWriteUint32(password.size());
|
||||
MemoryDumper::GetCurrent()->CopyBytes((const uint8 *)password.data(), password.size());
|
||||
}
|
||||
|
||||
SlWriteUint16(0xFFFF);
|
||||
|
||||
/* Add some random length padding to not make it too obvious from the length whether passwords are set or not */
|
||||
uint8 padding[256];
|
||||
if (randombytes(padding, 256) >= 0) {
|
||||
SlWriteByte(padding[0]);
|
||||
MemoryDumper::GetCurrent()->CopyBytes(padding + 1, padding[0]);
|
||||
} else {
|
||||
SlWriteByte(0);
|
||||
}
|
||||
}, nullptr);
|
||||
|
||||
|
||||
uint8 mac[16]; /* Message authentication code */
|
||||
|
||||
/* Encrypt in place */
|
||||
crypto_lock(mac, buffer.data(), _network_company_password_storage_key, nonce, buffer.data(), buffer.size());
|
||||
|
||||
SlSetLength(16 + 24 + 16 + buffer.size());
|
||||
MemoryDumper::GetCurrent()->CopyBytes(_network_company_password_storage_token, 16);
|
||||
MemoryDumper::GetCurrent()->CopyBytes(nonce, 24);
|
||||
MemoryDumper::GetCurrent()->CopyBytes(mac, 16);
|
||||
MemoryDumper::GetCurrent()->CopyBytes(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
static const ChunkHandler company_chunk_handlers[] = {
|
||||
{ 'PLYR', Save_PLYR, Load_PLYR, Ptrs_PLYR, Check_PLYR, CH_ARRAY },
|
||||
{ 'PLYX', Save_PLYX, Load_PLYX, nullptr, Check_PLYX, CH_RIFF },
|
||||
{ 'PLYP', Save_PLYP, Load_PLYP, nullptr, nullptr, CH_RIFF },
|
||||
};
|
||||
|
||||
extern const ChunkHandlerTable _company_chunk_handlers(company_chunk_handlers);
|
||||
|
||||
@@ -161,6 +161,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = {
|
||||
{ XSLFI_BANKRUPTCY_EXTRA, XSCF_NULL, 1, 1, "bankruptcy_extra", nullptr, nullptr, nullptr },
|
||||
{ XSLFI_OBJECT_GROUND_TYPES, XSCF_NULL, 1, 1, "object_ground_types", nullptr, nullptr, nullptr },
|
||||
{ XSLFI_LINKGRAPH_AIRCRAFT, XSCF_NULL, 1, 1, "linkgraph_aircraft", nullptr, nullptr, nullptr },
|
||||
{ XSLFI_COMPANY_PW, XSCF_IGNORABLE_ALL, 1, 1, "company_password", nullptr, nullptr, "PLYP" },
|
||||
{ XSLFI_SCRIPT_INT64, XSCF_NULL, 1, 1, "script_int64", nullptr, nullptr, nullptr },
|
||||
{ XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker
|
||||
};
|
||||
|
||||
@@ -115,6 +115,7 @@ enum SlXvFeatureIndex {
|
||||
XSLFI_BANKRUPTCY_EXTRA, ///< Extra company bankruptcy fields
|
||||
XSLFI_OBJECT_GROUND_TYPES, ///< Object ground types
|
||||
XSLFI_LINKGRAPH_AIRCRAFT, ///< Link graph last aircraft update field and aircraft link scaling setting
|
||||
XSLFI_COMPANY_PW, ///< Company passwords
|
||||
|
||||
XSLFI_SCRIPT_INT64, ///< See: SLV_SCRIPT_INT64
|
||||
|
||||
|
||||
@@ -143,6 +143,7 @@ typedef void SettingDescProc(IniFile &ini, const SettingTable &desc, const char
|
||||
typedef void SettingDescProcList(IniFile &ini, const char *grpname, StringList &list);
|
||||
|
||||
static bool IsSignedVarMemType(VarType vt);
|
||||
static bool DecodeHexText(const char *pos, uint8 *dest, size_t dest_size);
|
||||
|
||||
|
||||
/**
|
||||
@@ -1684,6 +1685,38 @@ static bool ReplaceAsteriskWithEmptyPassword(std::string &newval)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsValidHexKeyString(const std::string &newval)
|
||||
{
|
||||
for (const char c : newval) {
|
||||
if (!IsValidChar(c, CS_HEXADECIMAL)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsValidHex128BitKeyString(std::string &newval)
|
||||
{
|
||||
return newval.size() == 32 && IsValidHexKeyString(newval);
|
||||
}
|
||||
|
||||
static bool IsValidHex256BitKeyString(std::string &newval)
|
||||
{
|
||||
return newval.size() == 64 && IsValidHexKeyString(newval);
|
||||
}
|
||||
|
||||
static void ParseCompanyPasswordStorageToken(const std::string &value)
|
||||
{
|
||||
extern uint8 _network_company_password_storage_token[16];
|
||||
if (value.size() != 32) return;
|
||||
DecodeHexText(value.c_str(), _network_company_password_storage_token, 16);
|
||||
}
|
||||
|
||||
static void ParseCompanyPasswordStorageSecret(const std::string &value)
|
||||
{
|
||||
extern uint8 _network_company_password_storage_key[32];
|
||||
if (value.size() != 64) return;
|
||||
DecodeHexText(value.c_str(), _network_company_password_storage_key, 32);
|
||||
}
|
||||
|
||||
/** Update the game info, and send it to the clients when we are running as a server. */
|
||||
static void UpdateClientConfigValues()
|
||||
{
|
||||
|
||||
@@ -361,6 +361,8 @@ struct NetworkSettings {
|
||||
std::string default_company_pass; ///< default password for new companies in encrypted form
|
||||
std::string connect_to_ip; ///< default for the "Add server" query
|
||||
std::string network_id; ///< network ID for servers
|
||||
std::string company_password_storage_token; ///< company password storage token
|
||||
std::string company_password_storage_secret; ///< company password storage secret
|
||||
bool autoclean_companies; ///< automatically remove companies that are not in use
|
||||
uint8 autoclean_unprotected; ///< remove passwordless companies after this many months
|
||||
uint8 autoclean_protected; ///< remove the password from passworded companies after this many months
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
[pre-amble]
|
||||
static bool ReplaceAsteriskWithEmptyPassword(std::string &newval);
|
||||
static bool IsValidHex128BitKeyString(std::string &newval);
|
||||
static bool IsValidHex256BitKeyString(std::string &newval);
|
||||
static void ParseCompanyPasswordStorageToken(const std::string &value);
|
||||
static void ParseCompanyPasswordStorageSecret(const std::string &value);
|
||||
|
||||
static const SettingTable _network_secrets_settings = {
|
||||
[post-amble]
|
||||
@@ -99,3 +103,23 @@ type = SLE_STR
|
||||
length = NETWORK_INVITE_CODE_SECRET_LENGTH
|
||||
flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY
|
||||
def = nullptr
|
||||
|
||||
[SDTC_SSTR]
|
||||
var = network.company_password_storage_token
|
||||
type = SLE_STR
|
||||
length = 33
|
||||
flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY | SF_RUN_CALLBACKS_ON_PARSE
|
||||
def = nullptr
|
||||
pre_cb = IsValidHex128BitKeyString
|
||||
post_cb = ParseCompanyPasswordStorageToken
|
||||
startup = true
|
||||
|
||||
[SDTC_SSTR]
|
||||
var = network.company_password_storage_secret
|
||||
type = SLE_STR
|
||||
length = 65
|
||||
flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY | SF_RUN_CALLBACKS_ON_PARSE
|
||||
def = nullptr
|
||||
pre_cb = IsValidHex256BitKeyString
|
||||
post_cb = ParseCompanyPasswordStorageSecret
|
||||
startup = true
|
||||
|
||||
Reference in New Issue
Block a user