Skip to content

Commit

Permalink
Feature: authenticate to the server without sending the password
Browse files Browse the repository at this point in the history
Either using password-authentication key exchange or via authorized keys
  • Loading branch information
rubidium42 committed Mar 17, 2024
1 parent dd532cb commit 5706801
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 79 deletions.
4 changes: 3 additions & 1 deletion src/lang/english.txt
Expand Up @@ -2574,6 +2574,7 @@ STR_NETWORK_ERROR_BAD_PLAYER_NAME :{WHITE}Your pla
STR_NETWORK_ERROR_BAD_SERVER_NAME :{WHITE}Your server name has not been set. The name can be set at the top of the Multiplayer window
STR_NETWORK_ERROR_WRONG_REVISION :{WHITE}The revision of this client does not match the server's revision
STR_NETWORK_ERROR_WRONG_PASSWORD :{WHITE}Wrong password
STR_NETWORK_ERROR_NOT_ON_ALLOW_LIST :{WHITE}You are not on the list of allowed clients
STR_NETWORK_ERROR_SERVER_FULL :{WHITE}The server is full
STR_NETWORK_ERROR_SERVER_BANNED :{WHITE}You are banned from this server
STR_NETWORK_ERROR_KICKED :{WHITE}You were kicked out of the game
Expand All @@ -2589,7 +2590,7 @@ STR_NETWORK_ERROR_INVALID_CLIENT_NAME :{WHITE}Your pla
STR_NETWORK_ERROR_CLIENT_GUI_LOST_CONNECTION_CAPTION :{WHITE}Possible connection loss
STR_NETWORK_ERROR_CLIENT_GUI_LOST_CONNECTION :{WHITE}The last {NUM} second{P "" s} no data has arrived from the server

###length 21
###length 22
STR_NETWORK_ERROR_CLIENT_GENERAL :general error
STR_NETWORK_ERROR_CLIENT_DESYNC :desync error
STR_NETWORK_ERROR_CLIENT_SAVEGAME :could not load map
Expand All @@ -2601,6 +2602,7 @@ STR_NETWORK_ERROR_CLIENT_NOT_EXPECTED :received invali
STR_NETWORK_ERROR_CLIENT_WRONG_REVISION :wrong revision
STR_NETWORK_ERROR_CLIENT_NAME_IN_USE :name already in use
STR_NETWORK_ERROR_CLIENT_WRONG_PASSWORD :wrong password
STR_NETWORK_ERROR_CLIENT_NOT_ON_ALLOW_LIST :not on allow list
STR_NETWORK_ERROR_CLIENT_COMPANY_MISMATCH :wrong company in DoCommand
STR_NETWORK_ERROR_CLIENT_KICKED :kicked by server
STR_NETWORK_ERROR_CLIENT_CHEATER :was trying to use a cheat
Expand Down
10 changes: 6 additions & 4 deletions src/network/core/tcp_game.cpp
Expand Up @@ -82,9 +82,10 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet &p)
case PACKET_SERVER_GAME_INFO: return this->Receive_SERVER_GAME_INFO(p);
case PACKET_SERVER_CLIENT_INFO: return this->Receive_SERVER_CLIENT_INFO(p);
case PACKET_CLIENT_IDENTIFY: return this->Receive_CLIENT_IDENTIFY(p);
case PACKET_SERVER_NEED_GAME_PASSWORD: return this->Receive_SERVER_NEED_GAME_PASSWORD(p);
case PACKET_SERVER_AUTH_REQUEST: return this->Receive_SERVER_AUTH_REQUEST(p);
case PACKET_SERVER_NEED_COMPANY_PASSWORD: return this->Receive_SERVER_NEED_COMPANY_PASSWORD(p);
case PACKET_CLIENT_GAME_PASSWORD: return this->Receive_CLIENT_GAME_PASSWORD(p);
case PACKET_CLIENT_AUTH_RESPONSE: return this->Receive_CLIENT_AUTH_RESPONSE(p);
case PACKET_SERVER_AUTH_COMPLETED: return this->Receive_SERVER_AUTH_COMPLETED(p);
case PACKET_CLIENT_COMPANY_PASSWORD: return this->Receive_CLIENT_COMPANY_PASSWORD(p);
case PACKET_SERVER_WELCOME: return this->Receive_SERVER_WELCOME(p);
case PACKET_CLIENT_GETMAP: return this->Receive_CLIENT_GETMAP(p);
Expand Down Expand Up @@ -164,9 +165,10 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GAME_INFO(Packet &) {
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_GAME_INFO(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_GAME_INFO); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_CLIENT_INFO); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_IDENTIFY); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_NEED_GAME_PASSWORD); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_AUTH_REQUEST(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_AUTH_REQUEST); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_NEED_COMPANY_PASSWORD); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GAME_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GAME_PASSWORD); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_AUTH_RESPONSE(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_AUTH_RESPONSE); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_AUTH_COMPLETED(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_AUTH_COMPLETED); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_COMPANY_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_COMPANY_PASSWORD); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_WELCOME); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GETMAP(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GETMAP); }
Expand Down
34 changes: 24 additions & 10 deletions src/network/core/tcp_game.h
Expand Up @@ -15,6 +15,7 @@
#include "os_abstraction.h"
#include "tcp.h"
#include "../network_type.h"
#include "../network_crypto.h"
#include "../../core/pool_type.hpp"
#include <chrono>

Expand Down Expand Up @@ -56,16 +57,19 @@ enum PacketGameType : uint8_t {
* the map and other important data.
*/

/* After the initial join, the next step is identification. */
/* After the join step, the first perform game authentication and enabling encryption. */
PACKET_SERVER_AUTH_REQUEST, ///< The server requests the client to authenticate using a number of methods.
PACKET_CLIENT_AUTH_RESPONSE, ///< The client responds to the authentication request.
PACKET_SERVER_AUTH_COMPLETED, ///< The server indicates the authentication is completed.

/* After the authentication is done, the next step is identification. */
PACKET_CLIENT_IDENTIFY, ///< Client telling the server the client's name and requested company.

/* After the identify step, the next is checking NewGRFs. */
PACKET_SERVER_CHECK_NEWGRFS, ///< Server sends NewGRF IDs and MD5 checksums for the client to check.
PACKET_CLIENT_NEWGRFS_CHECKED, ///< Client acknowledges that it has all required NewGRFs.

/* Checking the game, and then company passwords. */
PACKET_SERVER_NEED_GAME_PASSWORD, ///< Server requests the (hashed) game password.
PACKET_CLIENT_GAME_PASSWORD, ///< Clients sends the (hashed) game password.
/* Checking the company passwords. */
PACKET_SERVER_NEED_COMPANY_PASSWORD, ///< Server requests the (hashed) company password.
PACKET_CLIENT_COMPANY_PASSWORD, ///< Client sends the (hashed) company password.

Expand Down Expand Up @@ -214,10 +218,13 @@ class NetworkGameSocketHandler : public NetworkTCPSocketHandler {
virtual NetworkRecvStatus Receive_CLIENT_IDENTIFY(Packet &p);

/**
* Indication to the client that the server needs a game password.
* Indication to the client that it needs to authenticate:
* bool Whether to use the password in the key exchange.
* 32 * uint8_t Public key of the server.
* 24 * uint8_t Nonce for the key exchange.
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_SERVER_NEED_GAME_PASSWORD(Packet &p);
virtual NetworkRecvStatus Receive_SERVER_AUTH_REQUEST(Packet &p);

/**
* Indication to the client that the server needs a company password:
Expand All @@ -228,12 +235,19 @@ class NetworkGameSocketHandler : public NetworkTCPSocketHandler {
virtual NetworkRecvStatus Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &p);

/**
* Send a password to the server to authorize:
* uint8_t Password type (see NetworkPasswordType).
* string The password.
* Send the response to the authentication request:
* 32 * uint8_t Public key of the client.
* 8 * uint8_t Random message that got encoded and signed.
* 16 * uint8_t Message authentication code.
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_CLIENT_AUTH_RESPONSE(Packet &p);

/**
* Indication to the client that authentication has completed.
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_CLIENT_GAME_PASSWORD(Packet &p);
virtual NetworkRecvStatus Receive_SERVER_AUTH_COMPLETED(Packet &p);

/**
* Send a password to the server to authorize
Expand Down
1 change: 1 addition & 0 deletions src/network/network.cpp
Expand Up @@ -321,6 +321,7 @@ StringID GetNetworkErrorMsg(NetworkErrorCode err)
STR_NETWORK_ERROR_CLIENT_TIMEOUT_MAP,
STR_NETWORK_ERROR_CLIENT_TIMEOUT_JOIN,
STR_NETWORK_ERROR_CLIENT_INVALID_CLIENT_NAME,
STR_NETWORK_ERROR_CLIENT_NOT_ON_ALLOW_LIST,
};
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);

Expand Down
75 changes: 59 additions & 16 deletions src/network/network_client.cpp
Expand Up @@ -349,7 +349,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendJoin()
p->Send_uint32(_openttd_newgrf_version);
my_client->SendPacket(std::move(p));

return ClientNetworkGameSocketHandler::SendIdentify();
return NETWORK_RECV_STATUS_OKAY;
}

NetworkRecvStatus ClientNetworkGameSocketHandler::SendIdentify()
Expand Down Expand Up @@ -377,13 +377,14 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendNewGRFsOk()
* Set the game password as requested.
* @param password The game password.
*/
NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const std::string &password)
NetworkRecvStatus ClientNetworkGameSocketHandler::SendAuthResponse()
{
Debug(net, 9, "Client::SendGamePassword()");
Debug(net, 9, "Client::SendAuthResponse()");

auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_GAME_PASSWORD);
p->Send_string(password);
auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_AUTH_RESPONSE);
my_client->authentication_handler->SendResponse(*p);
my_client->SendPacket(std::move(p));

return NETWORK_RECV_STATUS_OKAY;
}

Expand Down Expand Up @@ -680,6 +681,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR(Packet &p
STR_NETWORK_ERROR_TIMEOUT_MAP, // NETWORK_ERROR_TIMEOUT_MAP
STR_NETWORK_ERROR_TIMEOUT_JOIN, // NETWORK_ERROR_TIMEOUT_JOIN
STR_NETWORK_ERROR_INVALID_CLIENT_NAME, // NETWORK_ERROR_INVALID_CLIENT_NAME
STR_NETWORK_ERROR_NOT_ON_ALLOW_LIST, // NETWORK_ERROR_NOT_ON_ALLOW_LIST
};
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);

Expand All @@ -705,7 +707,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR(Packet &p

NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHECK_NEWGRFS(Packet &p)
{
if (this->status != STATUS_JOIN) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
if (this->status != STATUS_AUTHENTICATED) return NETWORK_RECV_STATUS_MALFORMED_PACKET;

uint grf_count = p.Recv_uint8();
NetworkRecvStatus ret = NETWORK_RECV_STATUS_OKAY;
Expand Down Expand Up @@ -736,26 +738,67 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHECK_NEWGRFS(P
return ret;
}

NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSWORD(Packet &)
class ClientGamePasswordRequestHandler : public NetworkAuthenticationPasswordRequestHandler {
virtual void SendResponse() override { MyClient::SendAuthResponse(); }
virtual void AskUserForPassword(std::shared_ptr<NetworkAuthenticationPasswordRequest> request) override
{
if (!_network_join.server_password.empty()) {
request->Reply(_network_join.server_password);
} else {
ShowNetworkNeedPassword(NETWORK_GAME_PASSWORD, request);
}
}
};

NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_AUTH_REQUEST(Packet &p)
{
if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_GAME) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
if (this->status != STATUS_JOIN && this->status != STATUS_AUTH_GAME) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
Debug(net, 9, "Client::status = AUTH_GAME");
this->status = STATUS_AUTH_GAME;

Debug(net, 9, "Client::Receive_SERVER_NEED_GAME_PASSWORD()");
Debug(net, 9, "Client::Receive_SERVER_AUTH_REQUEST()");

if (!_network_join.server_password.empty()) {
return SendGamePassword(_network_join.server_password);
if (this->authentication_handler == nullptr) {
this->authentication_handler = NetworkAuthenticationClientHandler::Create(std::make_shared<ClientGamePasswordRequestHandler>(),
_settings_client.network.client_secret_key, _settings_client.network.client_public_key);
}
switch (this->authentication_handler->ReceiveRequest(p)) {
case NetworkAuthenticationClientHandler::READY_FOR_RESPONSE:
return SendAuthResponse();

ShowNetworkNeedPassword(NETWORK_GAME_PASSWORD);
case NetworkAuthenticationClientHandler::AWAIT_USER_INPUT:
return NETWORK_RECV_STATUS_OKAY;

return NETWORK_RECV_STATUS_OKAY;
case NetworkAuthenticationClientHandler::INVALID:
default:
return NETWORK_RECV_STATUS_MALFORMED_PACKET;
}
}

NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_AUTH_COMPLETED(Packet &)
{
if (this->status != STATUS_AUTH_GAME || this->authentication_handler == nullptr) return NETWORK_RECV_STATUS_MALFORMED_PACKET;

Debug(net, 9, "Client::Receive_SERVER_AUTH_COMPLETED()");

this->authentication_handler = nullptr;

Debug(net, 9, "Client::status = AUTHENTICATED");
this->status = STATUS_AUTHENTICATED;

return this->SendIdentify();
}

class CompanyPasswordRequest : public NetworkAuthenticationPasswordRequest {
virtual void Reply(const std::string &password) override
{
MyClient::SendCompanyPassword(password);
}
};

NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &p)
{
if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_COMPANY) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
if (this->status < STATUS_AUTHENTICATED || this->status >= STATUS_AUTH_COMPANY) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
Debug(net, 9, "Client::status = AUTH_COMPANY");
this->status = STATUS_AUTH_COMPANY;

Expand All @@ -769,14 +812,14 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PA
return SendCompanyPassword(_network_join.company_password);
}

ShowNetworkNeedPassword(NETWORK_COMPANY_PASSWORD);
ShowNetworkNeedPassword(NETWORK_COMPANY_PASSWORD, std::make_shared<CompanyPasswordRequest>());

return NETWORK_RECV_STATUS_OKAY;
}

NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet &p)
{
if (this->status < STATUS_JOIN || this->status >= STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
if (this->status < STATUS_AUTHENTICATED || this->status >= STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
Debug(net, 9, "Client::status = AUTHORIZED");
this->status = STATUS_AUTHORIZED;

Expand Down
9 changes: 6 additions & 3 deletions src/network/network_client.h
Expand Up @@ -15,6 +15,7 @@
/** Class for handling the client side of the game connection. */
class ClientNetworkGameSocketHandler : public ZeroedMemoryAllocator, public NetworkGameSocketHandler {
private:
std::unique_ptr<class NetworkAuthenticationClientHandler> authentication_handler; ///< The handler for the authentication.
std::string connection_string; ///< Address we are connected to.
std::shared_ptr<struct PacketReader> savegame; ///< Packet reader for reading the savegame.
uint8_t token; ///< The token we need to send back to the server to prove we're the right client.
Expand All @@ -23,8 +24,9 @@ class ClientNetworkGameSocketHandler : public ZeroedMemoryAllocator, public Netw
enum ServerStatus {
STATUS_INACTIVE, ///< The client is not connected nor active.
STATUS_JOIN, ///< We are trying to join a server.
STATUS_NEWGRFS_CHECK, ///< Last action was checking NewGRFs.
STATUS_AUTH_GAME, ///< Last action was requesting game (server) password.
STATUS_AUTHENTICATED, ///< The game authentication has completed.
STATUS_NEWGRFS_CHECK, ///< Last action was checking NewGRFs.
STATUS_AUTH_COMPANY, ///< Last action was requesting company password.
STATUS_AUTHORIZED, ///< The client is authorized at the server.
STATUS_MAP_WAIT, ///< The client is waiting as someone else is downloading the map.
Expand All @@ -44,7 +46,8 @@ class ClientNetworkGameSocketHandler : public ZeroedMemoryAllocator, public Netw
NetworkRecvStatus Receive_SERVER_BANNED(Packet &p) override;
NetworkRecvStatus Receive_SERVER_ERROR(Packet &p) override;
NetworkRecvStatus Receive_SERVER_CLIENT_INFO(Packet &p) override;
NetworkRecvStatus Receive_SERVER_NEED_GAME_PASSWORD(Packet &p) override;
NetworkRecvStatus Receive_SERVER_AUTH_REQUEST(Packet &p) override;
NetworkRecvStatus Receive_SERVER_AUTH_COMPLETED(Packet &p) override;
NetworkRecvStatus Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &p) override;
NetworkRecvStatus Receive_SERVER_WELCOME(Packet &p) override;
NetworkRecvStatus Receive_SERVER_WAIT(Packet &p) override;
Expand Down Expand Up @@ -86,7 +89,7 @@ class ClientNetworkGameSocketHandler : public ZeroedMemoryAllocator, public Netw
static NetworkRecvStatus SendQuit();
static NetworkRecvStatus SendAck();

static NetworkRecvStatus SendGamePassword(const std::string &password);
static NetworkRecvStatus SendAuthResponse();
static NetworkRecvStatus SendCompanyPassword(const std::string &password);

static NetworkRecvStatus SendChat(NetworkAction action, DestType type, int dest, const std::string &msg, int64_t data);
Expand Down
14 changes: 5 additions & 9 deletions src/network/network_gui.cpp
Expand Up @@ -2103,7 +2103,7 @@ uint32_t _network_join_bytes; ///< The number of bytes we already do
uint32_t _network_join_bytes_total; ///< The total number of bytes to download.

struct NetworkJoinStatusWindow : Window {
NetworkPasswordType password_type;
std::shared_ptr<NetworkAuthenticationPasswordRequest> request;

NetworkJoinStatusWindow(WindowDesc *desc) : Window(desc)
{
Expand Down Expand Up @@ -2199,16 +2199,12 @@ struct NetworkJoinStatusWindow : Window {

void OnQueryTextFinished(char *str) override
{
if (StrEmpty(str)) {
if (StrEmpty(str) || this->request == nullptr) {
NetworkDisconnect();
return;
}

switch (this->password_type) {
case NETWORK_GAME_PASSWORD: MyClient::SendGamePassword (str); break;
case NETWORK_COMPANY_PASSWORD: MyClient::SendCompanyPassword(str); break;
default: NOT_REACHED();
}
this->request->Reply(str);
}
};

Expand Down Expand Up @@ -2236,11 +2232,11 @@ void ShowJoinStatusWindow()
new NetworkJoinStatusWindow(&_network_join_status_window_desc);
}

void ShowNetworkNeedPassword(NetworkPasswordType npt)
void ShowNetworkNeedPassword(NetworkPasswordType npt, std::shared_ptr<NetworkAuthenticationPasswordRequest> request)
{
NetworkJoinStatusWindow *w = (NetworkJoinStatusWindow *)FindWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
if (w == nullptr) return;
w->password_type = npt;
w->request = request;

StringID caption;
switch (npt) {
Expand Down
2 changes: 1 addition & 1 deletion src/network/network_gui.h
Expand Up @@ -17,7 +17,7 @@
#include "network_type.h"
#include "network_gamelist.h"

void ShowNetworkNeedPassword(NetworkPasswordType npt);
void ShowNetworkNeedPassword(NetworkPasswordType npt, std::shared_ptr<class NetworkAuthenticationPasswordRequest> request);
void ShowNetworkChatQueryWindow(DestType type, int dest);
void ShowJoinStatusWindow();
void ShowNetworkGameWindow();
Expand Down

0 comments on commit 5706801

Please sign in to comment.