From 715ba561b142e3494ccc684370a5244436da6157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Janiszewski?= Date: Thu, 19 May 2016 10:23:42 +0200 Subject: [PATCH] Initial version of key-based auth --- src/network/NetworkKey.cpp | 97 ++++++++++--- src/network/NetworkKey.h | 31 +++-- src/network/network.cpp | 276 ++++++++++++++++++++++++++++++++----- src/network/network.h | 28 +++- 4 files changed, 354 insertions(+), 78 deletions(-) diff --git a/src/network/NetworkKey.cpp b/src/network/NetworkKey.cpp index a396dc5c0e..917812b1f0 100644 --- a/src/network/NetworkKey.cpp +++ b/src/network/NetworkKey.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #define KEY_LENGTH_BITS 2048 #define KEY_TYPE EVP_PKEY_RSA @@ -37,6 +38,7 @@ NetworkKey::~NetworkKey() Unload(); if (m_ctx != nullptr) { EVP_PKEY_CTX_free(m_ctx); + m_ctx = nullptr; } } @@ -57,19 +59,21 @@ bool NetworkKey::Generate() #if KEY_TYPE == EVP_PKEY_RSA if (!EVP_PKEY_CTX_set_rsa_keygen_bits(m_ctx, KEY_LENGTH_BITS)) { log_error("Failed to set keygen params"); - EVP_PKEY_CTX_free(m_ctx); - m_ctx = nullptr; return false; } #else #error Only RSA is supported! #endif - if (!EVP_PKEY_keygen(m_ctx, &m_key)) { - log_error("Failed to generate new key!"); - EVP_PKEY_CTX_free(m_ctx); - m_ctx = nullptr; + if (EVP_PKEY_keygen_init(m_ctx) <= 0) { + log_error("Failed to initialise keygen algorithm"); return false; } + if (EVP_PKEY_keygen(m_ctx, &m_key) <= 0) { + log_error("Failed to generate new key!"); + return false; + } else { + log_warning("key ok"); + } log_verbose("New key of type %d, length %d generated successfully.", KEY_TYPE, KEY_LENGTH_BITS); return true; } @@ -84,19 +88,20 @@ bool NetworkKey::LoadPrivate(SDL_RWops *file) log_error("Key file suspiciously large, refusing to load it"); return false; } - BIO *bio = BIO_new(BIO_s_mem()); - if (bio == nullptr) { - log_error("Failed to initialise OpenSSL's BIO!"); - return false; - } char *priv_key = new char[size]; file->read(file, priv_key, 1, size); - BIO_write(bio, priv_key, size); + BIO *bio = BIO_new_mem_buf(priv_key, size); + if (bio == nullptr) { + log_error("Failed to initialise OpenSSL's BIO!"); + delete [] priv_key; + return false; + } RSA *rsa; - PEM_read_bio_RSAPrivateKey(bio, &rsa, nullptr, nullptr); + rsa = PEM_read_bio_RSAPrivateKey(bio, nullptr, nullptr, nullptr); if (!RSA_check_key(rsa)) { log_error("Loaded RSA key is invalid"); BIO_free_all(bio); + delete [] priv_key; return false; } if (m_key != nullptr) { @@ -106,6 +111,7 @@ bool NetworkKey::LoadPrivate(SDL_RWops *file) EVP_PKEY_set1_RSA(m_key, rsa); BIO_free_all(bio); RSA_free(rsa); + delete [] priv_key; return true; } @@ -119,16 +125,16 @@ bool NetworkKey::LoadPublic(SDL_RWops *file) log_error("Key file suspiciously large, refusing to load it"); return false; } - BIO *bio = BIO_new(BIO_s_mem()); + char *pub_key = new char[size]; + file->read(file, pub_key, 1, size); + BIO *bio = BIO_new_mem_buf(pub_key, size); if (bio == nullptr) { log_error("Failed to initialise OpenSSL's BIO!"); + delete [] pub_key; return false; } - char *priv_key = new char[size]; - file->read(file, priv_key, 1, size); - BIO_write(bio, priv_key, size); RSA *rsa; - PEM_read_bio_RSAPublicKey(bio, &rsa, nullptr, nullptr); + rsa = PEM_read_bio_RSAPublicKey(bio, nullptr, nullptr, nullptr); if (m_key != nullptr) { EVP_PKEY_free(m_key); } @@ -136,6 +142,7 @@ bool NetworkKey::LoadPublic(SDL_RWops *file) EVP_PKEY_set1_RSA(m_key, rsa); BIO_free_all(bio); RSA_free(rsa); + delete [] pub_key; return true; } @@ -172,6 +179,7 @@ bool NetworkKey::SavePrivate(SDL_RWops *file) char *pem_key = new char[keylen]; BIO_read(bio, pem_key, keylen); file->write(file, pem_key, keylen, 1); + log_verbose("saving key of length %u", keylen); BIO_free_all(bio); delete [] pem_key; #else @@ -215,7 +223,7 @@ bool NetworkKey::SavePublic(SDL_RWops *file) return true; } -char *NetworkKey::PublicKeyString() +std::string NetworkKey::PublicKeyString() { if (m_key == nullptr) { log_error("No key loaded"); @@ -246,11 +254,56 @@ char *NetworkKey::PublicKeyString() BIO_read(bio, pem_key, keylen); BIO_free_all(bio); pem_key[keylen] = '\0'; + std::string pem_key_out(pem_key); + free(pem_key); - return pem_key; + return pem_key_out; } -bool NetworkKey::Sign(const char *md, const size_t len, char **signature, size_t *out_size) +/** + * @brief NetworkKey::PublicKeyHash + * Computes a short, human-readable (e.g. asciif-ied hex) hash for a given + * public key. Serves a purpose of easy identification keys in multiplayer + * overview, multiplayer settings. + * + * In particular, any of digest functions applied to a standarised key + * representation, like PEM, will be sufficient. + * + * @return returns a string containing key hash. + */ +std::string NetworkKey::PublicKeyHash() +{ + std::string key = PublicKeyString(); + if (key.empty()) { + log_error("No key found"); + return nullptr; + } + EVP_MD_CTX *ctx = EVP_MD_CTX_create(); + if (EVP_DigestInit_ex(ctx, EVP_sha1(), nullptr) <= 0) { + log_error("Failed to initialise digest context"); + EVP_MD_CTX_destroy(ctx); + return nullptr; + } + if (EVP_DigestUpdate(ctx, key.c_str(), key.size()) <= 0) { + log_error("Failed to update digset"); + EVP_MD_CTX_destroy(ctx); + return nullptr; + } + unsigned int digest_size = EVP_MAX_MD_SIZE; + std::vector digest(EVP_MAX_MD_SIZE); + // Cleans up `ctx` automatically. + EVP_DigestFinal(ctx, digest.data(), &digest_size); + std::string digest_out; + digest_out.reserve(EVP_MAX_MD_SIZE * 2 + 1); + for (int i = 0; i < digest_size; i++) { + char buf[3]; + sprintf(buf, "%02x", digest[i]); + digest_out.append(buf); + } + return digest_out; +} + +bool NetworkKey::Sign(const char *md, const size_t len, char **signature, unsigned int *out_size) { EVP_MD_CTX *mdctx = NULL; @@ -335,6 +388,6 @@ bool NetworkKey::Verify(const char *md, const size_t len, const char* sig, const } else { EVP_MD_CTX_destroy(mdctx); log_error("Signature is invalid"); - return true; + return false; } } diff --git a/src/network/NetworkKey.h b/src/network/NetworkKey.h index 86773192e5..1cc30a8d95 100644 --- a/src/network/NetworkKey.h +++ b/src/network/NetworkKey.h @@ -17,8 +17,13 @@ #ifndef NETWORKKEY_H #define NETWORKKEY_H -#include -#include +#ifndef DISABLE_NETWORK + +#include +#include + +typedef struct evp_pkey_st EVP_PKEY; +typedef struct evp_pkey_ctx_st EVP_PKEY_CTX; class NetworkKey { @@ -26,17 +31,21 @@ public: NetworkKey(); ~NetworkKey(); bool Generate(); - bool LoadPrivate(SDL_RWops *file); - bool LoadPublic(SDL_RWops *file); - bool SavePrivate(SDL_RWops *file); - bool SavePublic(SDL_RWops *file); - char *PublicKeyString(); + bool LoadPrivate(SDL_RWops * file); + bool LoadPublic(SDL_RWops * file); + bool SavePrivate(SDL_RWops * file); + bool SavePublic(SDL_RWops * file); + std::string PublicKeyString(); + std::string PublicKeyHash(); void Unload(); - bool Sign(const char *md, const size_t len, char **signature, size_t *out_size); - bool Verify(const char *md, const size_t len, const char* sig, const size_t siglen); + bool Sign(const char * md, const size_t len, char ** signature, unsigned int * out_size); + bool Verify(const char * md, const size_t len, const char * sig, const size_t siglen); private: - EVP_PKEY_CTX *m_ctx = nullptr; - EVP_PKEY *m_key = nullptr; + NetworkKey ( const NetworkKey & ) = delete; + EVP_PKEY_CTX * m_ctx = nullptr; + EVP_PKEY * m_key = nullptr; }; +#endif // DISABLE_NETWORK + #endif // NETWORKKEY_H diff --git a/src/network/network.cpp b/src/network/network.cpp index efeb9d6663..7c104306f6 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -48,6 +48,8 @@ extern "C" { #include "../windows/error.h" #include "../util/util.h" #include "../cheats.h" + +#include // just for OpenSSL_add_all_algorithms() } #pragma comment(lib, "Ws2_32.lib") @@ -76,6 +78,7 @@ enum { NETWORK_COMMAND_SHOWERROR, NETWORK_COMMAND_GROUPLIST, NETWORK_COMMAND_EVENT, + NETWORK_COMMAND_TOKEN, NETWORK_COMMAND_MAX, NETWORK_COMMAND_INVALID = -1 }; @@ -135,7 +138,7 @@ uint32 NetworkPacket::GetCommand() } } -void NetworkPacket::Write(uint8* bytes, unsigned int size) +void NetworkPacket::Write(const uint8* bytes, unsigned int size) { data->insert(data->end(), bytes, bytes + size); } @@ -183,6 +186,7 @@ bool NetworkPacket::CommandRequiresAuth() switch (GetCommand()) { case NETWORK_COMMAND_PING: case NETWORK_COMMAND_AUTH: + case NETWORK_COMMAND_TOKEN: case NETWORK_COMMAND_GAMEINFO: return false; default: @@ -613,12 +617,15 @@ Network::Network() client_command_handlers[NETWORK_COMMAND_SHOWERROR] = &Network::Client_Handle_SHOWERROR; client_command_handlers[NETWORK_COMMAND_GROUPLIST] = &Network::Client_Handle_GROUPLIST; client_command_handlers[NETWORK_COMMAND_EVENT] = &Network::Client_Handle_EVENT; + client_command_handlers[NETWORK_COMMAND_TOKEN] = &Network::Client_Handle_TOKEN; server_command_handlers.resize(NETWORK_COMMAND_MAX, 0); server_command_handlers[NETWORK_COMMAND_AUTH] = &Network::Server_Handle_AUTH; server_command_handlers[NETWORK_COMMAND_CHAT] = &Network::Server_Handle_CHAT; server_command_handlers[NETWORK_COMMAND_GAMECMD] = &Network::Server_Handle_GAMECMD; server_command_handlers[NETWORK_COMMAND_PING] = &Network::Server_Handle_PING; server_command_handlers[NETWORK_COMMAND_GAMEINFO] = &Network::Server_Handle_GAMEINFO; + server_command_handlers[NETWORK_COMMAND_TOKEN] = &Network::Server_Handle_TOKEN; + OpenSSL_add_all_algorithms(); } Network::~Network() @@ -700,6 +707,40 @@ bool Network::BeginClient(const char* host, unsigned short port) }); mode = NETWORK_MODE_CLIENT; + char path[MAX_PATH]; + platform_get_user_directory(path, NULL); + char keyPath[MAX_PATH] = ""; + safe_strcat(keyPath, path, MAX_PATH); + safe_strcat(keyPath, gConfigNetwork.player_name, MAX_PATH); + safe_strcat(keyPath, ".privkey", MAX_PATH); + if (!platform_file_exists(keyPath)) { + log_warning("generating key… this may take a while"); + key.Generate(); + log_verbose("Key generated, saving private bits as %s", keyPath); + SDL_RWops *privkey = SDL_RWFromFile(keyPath, "wb+"); + key.SavePrivate(privkey); + SDL_RWclose(privkey); + + keyPath[0] = '\0'; + safe_strcat(keyPath, path, MAX_PATH); + safe_strcat(keyPath, gConfigNetwork.player_name, MAX_PATH); + safe_strcat(keyPath, "-", MAX_PATH); + safe_strcat(keyPath, key.PublicKeyHash().c_str(), MAX_PATH); + safe_strcat(keyPath, ".pubkey", MAX_PATH); + log_verbose("Key generated, saving public bits as %s", keyPath); + SDL_RWops *pubkey = SDL_RWFromFile(keyPath, "wb+"); + key.SavePublic(pubkey); + SDL_RWclose(pubkey); + } else { + log_verbose("Loading key from %s", keyPath); + SDL_RWops *privkey = SDL_RWFromFile(keyPath, "rb"); + // LoadPrivate returns validity of loaded key + bool ok = key.LoadPrivate(privkey); + SDL_RWclose(privkey); + // Don't store private key in memory when it's not in use. + key.Unload(); + return ok; + } return true; } @@ -710,6 +751,7 @@ bool Network::BeginServer(unsigned short port, const char* address) if (!Init()) return false; + LoadKeyMappings(); NetworkAddress networkaddress; networkaddress.Resolve(address, port, false); @@ -747,7 +789,7 @@ bool Network::BeginServer(unsigned short port, const char* address) cheats_reset(); LoadGroups(); - NetworkPlayer* player = AddPlayer(); + NetworkPlayer* player = AddPlayer(""); player->SetName(gConfigNetwork.player_name); player->flags |= NETWORK_PLAYER_FLAG_ISSERVER; player->group = 0; @@ -804,11 +846,6 @@ uint8 Network::GetPlayerID() return player_id; } -char *Network::NetworkKeyString() -{ - return key.PublicKeyString(); -} - void Network::Update() { switch (GetMode()) { @@ -934,12 +971,6 @@ void Network::UpdateClient() timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; - const char *pubkey = key.PublicKeyString(); - if (pubkey == nullptr) { - log_error("Failed to load public key."); - connectfailed = true; - break; - } if (select(server_connection.socket + 1, NULL, &writeFD, NULL, &timeout) > 0) { error = 0; socklen_t len = sizeof(error); @@ -951,7 +982,7 @@ void Network::UpdateClient() if (error == 0) { status = NETWORK_STATUS_CONNECTED; server_connection.ResetLastPacketTime(); - Client_Send_AUTH(gConfigNetwork.player_name, "", pubkey); + Client_Send_TOKEN(); char str_authenticating[256]; format_string(str_authenticating, STR_MULTIPLAYER_AUTHENTICATING, NULL); window_network_status_open(str_authenticating, []() -> void { @@ -1261,6 +1292,16 @@ void Network::RemoveGroup(uint8 id) } } +uint8 Network::GetGroupIDByHash(const std::string &keyhash) +{ + auto it = key_group_map.find(keyhash); + if (it != key_group_map.end()) { + return it->second; + } else { + return GetDefaultGroup(); + } +} + uint8 Network::GetDefaultGroup() { return default_group; @@ -1281,7 +1322,6 @@ void Network::SaveGroups() platform_get_user_directory(path, NULL); strcat(path, "groups.json"); - std::unique_ptr stream = std::move(NetworkPacket::Allocate()); json_t * jsonGroupsCfg = json_object(); json_t * jsonGroups = json_array(); for (auto it = group_list.begin(); it != group_list.end(); it++) { @@ -1354,20 +1394,96 @@ void Network::LoadGroups() if (default_group >= group_list.size()) { default_group = 0; } + json_decref(json); } -void Network::Client_Send_AUTH(const char* name, const char* password, const char* pubkey) +void Network::SaveKeyMappings() +{ + if (GetMode() == NETWORK_MODE_SERVER) { + utf8 path[MAX_PATH]; + + platform_get_user_directory(path, NULL); + strcat(path, "keymappings.json"); + + json_t * jsonKeyMappings = json_array(); + for (auto it = key_group_map.cbegin(); it != key_group_map.cend(); it++) { + json_t *keyMapping = json_object(); + json_object_set_new(keyMapping, "hash", json_string(it->first.c_str())); + json_object_set_new(keyMapping, "groupId", json_integer(it->second)); + json_array_append_new(jsonKeyMappings, keyMapping); + } + bool result; + try + { + Json::WriteToFile(path, jsonKeyMappings, JSON_INDENT(4) | JSON_PRESERVE_ORDER); + result = true; + } + catch (Exception ex) + { + log_error("Unable to save %s: %s", path, ex.GetMessage()); + result = false; + } + + json_decref(jsonKeyMappings); + } +} + +void Network::LoadKeyMappings() +{ + group_list.clear(); + + utf8 path[MAX_PATH]; + + platform_get_user_directory(path, NULL); + strcat(path, "keymappings.json"); + + if (!platform_file_exists(path)) { + return; + } + + json_t * jsonKeyMappings = Json::ReadFromFile(path); + + size_t groupCount = (size_t)json_array_size(jsonKeyMappings); + for (size_t i = 0; i < groupCount; i++) { + json_t * jsonKeyMapping = json_array_get(jsonKeyMappings, i); + std::string hash(json_string_value(json_object_get(jsonKeyMapping, "hash"))); + key_group_map[hash] = json_integer_value(json_object_get(jsonKeyMapping, "groupId")); + } + json_decref(jsonKeyMappings); +} + +void Network::Client_Send_TOKEN() +{ + log_verbose("requesting token"); + std::unique_ptr packet = std::move(NetworkPacket::Allocate()); + *packet << (uint32)NETWORK_COMMAND_TOKEN; + server_connection.authstatus = NETWORK_AUTH_REQUESTED; + server_connection.QueuePacket(std::move(packet)); +} + +void Network::Client_Send_AUTH(const char* name, const char* password, const char* pubkey, const char *sig, size_t sigsize) { std::unique_ptr packet = std::move(NetworkPacket::Allocate()); *packet << (uint32)NETWORK_COMMAND_AUTH; packet->WriteString(NETWORK_STREAM_ID); packet->WriteString(name); packet->WriteString(password); - packet->WriteString(pubkey); + packet->WriteString(pubkey); + assert(sigsize <= (size_t)UINT32_MAX); + *packet << (uint32)sigsize; + packet->Write((const uint8 *)sig, sigsize); server_connection.authstatus = NETWORK_AUTH_REQUESTED; server_connection.QueuePacket(std::move(packet)); } +void Network::Server_Send_TOKEN(NetworkConnection& connection) +{ + std::unique_ptr packet = std::move(NetworkPacket::Allocate()); + *packet << (uint32)NETWORK_COMMAND_TOKEN << (uint32)connection.challenge.size(); + packet->Write((const uint8 *)connection.challenge.c_str(), connection.challenge.size()); + connection.QueuePacket(std::move(packet)); +} + void Network::Server_Send_AUTH(NetworkConnection& connection) { uint8 new_playerid = 0; @@ -1703,7 +1819,7 @@ void Network::RemoveClient(std::unique_ptr& connection) Server_Send_PLAYERLIST(); } -NetworkPlayer* Network::AddPlayer() +NetworkPlayer* Network::AddPlayer(const std::string &keyhash) { NetworkPlayer* addedplayer = nullptr; int newid = -1; @@ -1723,7 +1839,8 @@ NetworkPlayer* Network::AddPlayer() if (newid != -1) { std::unique_ptr player(new NetworkPlayer); // change to make_unique in c++14 player->id = newid; - player->group = GetDefaultGroup(); + player->keyhash = keyhash; + player->group = GetGroupIDByHash(keyhash); addedplayer = player.get(); player_list.push_back(std::move(player)); } @@ -1745,6 +1862,34 @@ void Network::PrintError() } +void Network::Client_Handle_TOKEN(NetworkConnection& connection, NetworkPacket& packet) +{ + char path[MAX_PATH]; + platform_get_user_directory(path, NULL); + char keyPath[MAX_PATH] = ""; + safe_strcat(keyPath, path, MAX_PATH); + safe_strcat(keyPath, gConfigNetwork.player_name, MAX_PATH); + safe_strcat(keyPath, ".privkey", MAX_PATH); + if (!platform_file_exists(keyPath)) { + log_error("Key file (%s) was not found. Restart client to re-generate it.", keyPath); + return; + } + SDL_RWops *privkey = SDL_RWFromFile(keyPath, "rb"); + key.LoadPrivate(privkey); + uint32 challenge_size; + packet >> challenge_size; + const char *challenge = (const char *)packet.Read(challenge_size); + uint32 sigsize; + char *signature; + const std::string pubkey = key.PublicKeyString(); + bool ok = key.Sign(challenge, challenge_size, &signature, &sigsize); + // Don't keep private key in memory. There's no need and it may get leaked + // when process dump gets collected at some point in future. + key.Unload(); + Client_Send_AUTH(gConfigNetwork.player_name, "", pubkey.c_str(), signature, sigsize); + delete [] signature; +} + void Network::Client_Handle_AUTH(NetworkConnection& connection, NetworkPacket& packet) { packet >> (uint32&)connection.authstatus >> (uint8&)player_id; @@ -1774,13 +1919,57 @@ void Network::Client_Handle_AUTH(NetworkConnection& connection, NetworkPacket& p } } +void Network::Server_Client_Joined(const char* name, const std::string &keyhash, NetworkConnection& connection) +{ + NetworkPlayer* player = AddPlayer(keyhash); + connection.player = player; + if (player) { + player->SetName(name); + char text[256]; + const char * player_name = (const char *) player->name; + format_string(text, STR_MULTIPLAYER_PLAYER_HAS_JOINED_THE_GAME, &player_name); + chat_history_add(text); + Server_Send_MAP(&connection); + gNetwork.Server_Send_EVENT_PLAYER_JOINED(player_name); + Server_Send_GROUPLIST(connection); + Server_Send_PLAYERLIST(); + } +} + +void Network::Server_Handle_TOKEN(NetworkConnection& connection, NetworkPacket& packet) +{ + // TODO: add some randomness here + connection.challenge = "test"; + Server_Send_TOKEN(connection); +} + void Network::Server_Handle_AUTH(NetworkConnection& connection, NetworkPacket& packet) { if (connection.authstatus != NETWORK_AUTH_OK) { const char* gameversion = packet.ReadString(); const char* name = packet.ReadString(); const char* password = packet.ReadString(); - const char* pubkey = packet.ReadString(); + const char *pubkey = (const char *)packet.ReadString(); + uint32 sigsize; + packet >> sigsize; + if (pubkey == nullptr) { + connection.authstatus = NETWORK_AUTH_BADPASSWORD; + } else { + const char *signature = (const char *)packet.Read(sigsize); + SDL_RWops *pubkey_rw = SDL_RWFromConstMem(pubkey, strlen(pubkey)); + connection.key.LoadPublic(pubkey_rw); + SDL_RWclose(pubkey_rw); + bool verified = connection.key.Verify(connection.challenge.c_str(), connection.challenge.size(), signature, sigsize); + if (verified) { + connection.authstatus = NETWORK_AUTH_VERIFIED; + const std::string hash = connection.key.PublicKeyHash(); + log_verbose("Signature verification ok. Hash %s", hash.c_str()); + } else { + log_verbose("Signature verification failed!"); + } + } + + // TODO: check if key is already known if (!gameversion || strcmp(gameversion, NETWORK_STREAM_ID) != 0) { connection.authstatus = NETWORK_AUTH_BADVERSION; } else @@ -1795,21 +1984,13 @@ void Network::Server_Handle_AUTH(NetworkConnection& connection, NetworkPacket& p } else if (gConfigNetwork.maxplayers <= player_list.size()) { connection.authstatus = NETWORK_AUTH_FULL; - } else { + } else + if (connection.authstatus == NETWORK_AUTH_VERIFIED) { connection.authstatus = NETWORK_AUTH_OK; - NetworkPlayer* player = AddPlayer(); - connection.player = player; - if (player) { - player->SetName(name); - char text[256]; - const char * player_name = (const char *) player->name; - format_string(text, STR_MULTIPLAYER_PLAYER_HAS_JOINED_THE_GAME, &player_name); - chat_history_add(text); - Server_Send_MAP(&connection); - gNetwork.Server_Send_EVENT_PLAYER_JOINED(player_name); - Server_Send_GROUPLIST(connection); - Server_Send_PLAYERLIST(); - } + const std::string hash = connection.key.PublicKeyHash(); + Server_Client_Joined(name, hash, connection); + } else { + log_error("Unkown failure while authenticating client"); } Server_Send_AUTH(connection); } @@ -1991,7 +2172,7 @@ void Network::Client_Handle_PLAYERLIST(NetworkConnection& connection, NetworkPac tempplayer.Read(packet); ids.push_back(tempplayer.id); if (!GetPlayerByID(tempplayer.id)) { - NetworkPlayer* player = AddPlayer(); + NetworkPlayer* player = AddPlayer(""); if (player) { *player = tempplayer; if (player->flags & NETWORK_PLAYER_FLAG_ISSERVER) { @@ -2311,6 +2492,8 @@ void game_command_set_player_group(int* eax, int* ebx, int* ecx, int* edx, int* } if (*ebx & GAME_COMMAND_FLAG_APPLY) { player->group = groupid; + gNetwork.key_group_map[player->keyhash] = groupid; + gNetwork.SaveKeyMappings(); window_invalidate_by_number(WC_PLAYER, playerid); } *ebx = 0; @@ -2519,9 +2702,26 @@ void network_send_gamecmd(uint32 eax, uint32 ebx, uint32 ecx, uint32 edx, uint32 void network_send_password(const char* password) { - char *pubkey = gNetwork.NetworkKeyString(); - gNetwork.Client_Send_AUTH(gConfigNetwork.player_name, password, pubkey); - free(pubkey); + log_warning("client name: %s", gConfigNetwork.player_name); + char path[MAX_PATH]; + platform_get_user_directory(path, NULL); + char keyPath[MAX_PATH] = ""; + safe_strcat(keyPath, path, MAX_PATH); + safe_strcat(keyPath, gConfigNetwork.player_name, MAX_PATH); + safe_strcat(keyPath, ".privkey", MAX_PATH); + SDL_RWops *privkey = SDL_RWFromFile(keyPath, "rb"); + // TODO: verify file exists + gNetwork.key.LoadPrivate(privkey); + const std::string pubkey = gNetwork.key.PublicKeyString(); + uint32 sigsize; + char *signature; + bool ok = gNetwork.key.Sign(gNetwork.challenge.c_str(), gNetwork.challenge.size(), &signature, &sigsize); + log_warning("sigsize = %u, strlen(signature) = %u, signature = %s", sigsize, strlen(signature), signature); + // Don't keep private key in memory. There's no need and it may get leaked + // when process dump gets collected at some point in future. + gNetwork.key.Unload(); + gNetwork.Client_Send_AUTH(gConfigNetwork.player_name, password, pubkey.c_str(), signature, sigsize); + delete [] signature; } void network_set_password(const char* password) diff --git a/src/network/network.h b/src/network/network.h index 6401089cba..d77d880450 100644 --- a/src/network/network.h +++ b/src/network/network.h @@ -65,7 +65,7 @@ extern "C" { // This define specifies which version of network stream current build uses. // It is used for making sure only compatible builds get connected, even within // single OpenRCT2 version. -#define NETWORK_STREAM_VERSION "8" +#define NETWORK_STREAM_VERSION "9" #define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION #define NETWORK_DISCONNECT_REASON_BUFFER_SIZE 256 @@ -117,6 +117,7 @@ extern "C" { #include #include #include +#include #include #include "../core/Json.hpp" #include "NetworkKey.h" @@ -145,7 +146,7 @@ public: T swapped = ByteSwapBE(value); uint8* bytes = (uint8*)&swapped; data->insert(data->end(), bytes, bytes + sizeof(value)); return *this; } - void Write(uint8* bytes, unsigned int size); + void Write(const uint8* bytes, unsigned int size); void WriteString(const char* string); template NetworkPacket& operator>>(T& value) { @@ -181,7 +182,7 @@ public: int last_action = -999; uint32 last_action_time = 0; rct_xyz16 last_action_coord = { 0 }; - NetworkKey key; + std::string keyhash; }; class NetworkAction @@ -298,6 +299,8 @@ public: int authstatus = NETWORK_AUTH_NONE; NetworkPlayer* player; uint32 ping_time = 0; + NetworkKey key; + std::string challenge; private: char* last_disconnect_reason; @@ -363,13 +366,18 @@ public: NetworkGroup* AddGroup(); void RemoveGroup(uint8 id); uint8 GetDefaultGroup(); + uint8 GetGroupIDByHash(const std::string &keyhash); void SetDefaultGroup(uint8 id); void SaveGroups(); void LoadGroups(); - char *NetworkKeyString(); + void SaveKeyMappings(); + void LoadKeyMappings(); - void Client_Send_AUTH(const char* name, const char* password, const char *pubkey); + void Client_Send_TOKEN(); + void Client_Send_AUTH(const char* name, const char* password, const char *pubkey, const char *sig, size_t sigsize); + void Client_Send_AUTH(const char* name, const char* password, const char *pubkey); void Server_Send_AUTH(NetworkConnection& connection); + void Server_Send_TOKEN(NetworkConnection& connection); void Server_Send_MAP(NetworkConnection* connection = nullptr); void Client_Send_CHAT(const char* text); void Server_Send_CHAT(const char* text); @@ -389,6 +397,9 @@ public: std::vector> player_list; std::vector> group_list; + NetworkKey key; + std::string challenge; + std::map key_group_map; private: bool ProcessConnection(NetworkConnection& connection); @@ -396,11 +407,10 @@ private: void ProcessGameCommandQueue(); void AddClient(SOCKET socket); void RemoveClient(std::unique_ptr& connection); - NetworkPlayer* AddPlayer(); + NetworkPlayer* AddPlayer(const std::string &keyhash); void PrintError(); const char* GetMasterServerUrl(); std::string GenerateAdvertiseKey(); - NetworkKey key; struct GameCommand { @@ -451,6 +461,7 @@ private: std::vector server_command_handlers; void Client_Handle_AUTH(NetworkConnection& connection, NetworkPacket& packet); void Server_Handle_AUTH(NetworkConnection& connection, NetworkPacket& packet); + void Server_Client_Joined(const char* name, const std::string &keyhash, NetworkConnection& connection); void Client_Handle_MAP(NetworkConnection& connection, NetworkPacket& packet); void Client_Handle_CHAT(NetworkConnection& connection, NetworkPacket& packet); void Server_Handle_CHAT(NetworkConnection& connection, NetworkPacket& packet); @@ -466,6 +477,9 @@ private: void Client_Handle_SHOWERROR(NetworkConnection& connection, NetworkPacket& packet); void Client_Handle_GROUPLIST(NetworkConnection& connection, NetworkPacket& packet); void Client_Handle_EVENT(NetworkConnection& connection, NetworkPacket& packet); + void Client_Handle_TOKEN(NetworkConnection& connection, NetworkPacket& packet); + void Server_Handle_TOKEN(NetworkConnection& connection, NetworkPacket& packet); + void get_key_data(); }; #endif // __cplusplus