Feature: admin support for password authentication without sending password

Using either password authenticated key exchange (PAKE) or authorized keys
This commit is contained in:
Rubidium 2024-01-28 13:48:03 +01:00
parent 4f981f965e
commit 2376d18c8b
12 changed files with 140 additions and 8 deletions

View File

@ -1909,6 +1909,7 @@ DEF_CONSOLE_CMD(ConSayClient)
/** All the known authorized keys with their name. */
static std::vector<std::pair<std::string_view, NetworkAuthorizedKeys *>> _console_cmd_authorized_keys{
{ "admin", &_settings_client.network.admin_authorized_keys },
{ "rcon", &_settings_client.network.rcon_authorized_keys },
{ "server", &_settings_client.network.server_authorized_keys },
};

View File

@ -2576,7 +2576,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 22
###length 23
STR_NETWORK_ERROR_CLIENT_GENERAL :general error
STR_NETWORK_ERROR_CLIENT_DESYNC :desync error
STR_NETWORK_ERROR_CLIENT_SAVEGAME :could not load map
@ -2589,6 +2589,7 @@ 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_NO_AUTHENTICATION_METHOD_AVAILABLE :none of the requested authentication methods is available
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

View File

@ -63,6 +63,8 @@ NetworkRecvStatus NetworkAdminSocketHandler::HandlePacket(Packet &p)
case ADMIN_PACKET_ADMIN_RCON: return this->Receive_ADMIN_RCON(p);
case ADMIN_PACKET_ADMIN_GAMESCRIPT: return this->Receive_ADMIN_GAMESCRIPT(p);
case ADMIN_PACKET_ADMIN_PING: return this->Receive_ADMIN_PING(p);
case ADMIN_PACKET_ADMIN_JOIN_SECURE: return this->Receive_ADMIN_JOIN_SECURE(p);
case ADMIN_PACKET_ADMIN_AUTH_RESPONSE: return this->Receive_ADMIN_AUTH_RESPONSE(p);
case ADMIN_PACKET_SERVER_FULL: return this->Receive_SERVER_FULL(p);
case ADMIN_PACKET_SERVER_BANNED: return this->Receive_SERVER_BANNED(p);
@ -91,6 +93,7 @@ NetworkRecvStatus NetworkAdminSocketHandler::HandlePacket(Packet &p)
case ADMIN_PACKET_SERVER_CMD_LOGGING: return this->Receive_SERVER_CMD_LOGGING(p);
case ADMIN_PACKET_SERVER_RCON_END: return this->Receive_SERVER_RCON_END(p);
case ADMIN_PACKET_SERVER_PONG: return this->Receive_SERVER_PONG(p);
case ADMIN_PACKET_SERVER_AUTH_REQUEST: return this->Receive_SERVER_AUTH_REQUEST(p);
default:
Debug(net, 0, "[tcp/admin] Received invalid packet type {} from '{}' ({})", type, this->admin_name, this->admin_version);
@ -137,6 +140,8 @@ NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_EXTERNAL_CHAT(Packet
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_RCON(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_RCON); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_GAMESCRIPT(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_GAMESCRIPT); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_PING(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_PING); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_JOIN_SECURE(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_JOIN_SECURE); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_AUTH_RESPONSE(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_AUTH_RESPONSE); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_FULL(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_FULL); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_BANNED(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_BANNED); }
@ -165,3 +170,4 @@ NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_CMD_NAMES(Packet &)
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_CMD_LOGGING(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_CMD_LOGGING); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_RCON_END(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_RCON_END); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_PONG(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_PONG); }
NetworkRecvStatus NetworkAdminSocketHandler::Receive_SERVER_AUTH_REQUEST(Packet &) { return this->ReceiveInvalidPacket(ADMIN_PACKET_SERVER_AUTH_REQUEST); }

View File

@ -22,7 +22,7 @@
* This protocol may only be extended to ensure stability.
*/
enum PacketAdminType : uint8_t {
ADMIN_PACKET_ADMIN_JOIN, ///< The admin announces and authenticates itself to the server.
ADMIN_PACKET_ADMIN_JOIN, ///< The admin announces and authenticates itself to the server using an unsecured passwords.
ADMIN_PACKET_ADMIN_QUIT, ///< The admin tells the server that it is quitting.
ADMIN_PACKET_ADMIN_UPDATE_FREQUENCY, ///< The admin tells the server the update frequency of a particular piece of information.
ADMIN_PACKET_ADMIN_POLL, ///< The admin explicitly polls for a piece of information.
@ -31,6 +31,8 @@ enum PacketAdminType : uint8_t {
ADMIN_PACKET_ADMIN_GAMESCRIPT, ///< The admin sends a JSON string for the GameScript.
ADMIN_PACKET_ADMIN_PING, ///< The admin sends a ping to the server, expecting a ping-reply (PONG) packet.
ADMIN_PACKET_ADMIN_EXTERNAL_CHAT, ///< The admin sends a chat message from external source.
ADMIN_PACKET_ADMIN_JOIN_SECURE, ///< The admin announces and starts a secure authentication handshake.
ADMIN_PACKET_ADMIN_AUTH_RESPONSE, ///< The admin responds to the authentication request.
ADMIN_PACKET_SERVER_FULL = 100, ///< The server tells the admin it cannot accept the admin.
ADMIN_PACKET_SERVER_BANNED, ///< The server tells the admin it is banned.
@ -61,6 +63,7 @@ enum PacketAdminType : uint8_t {
ADMIN_PACKET_SERVER_RCON_END, ///< The server indicates that the remote console command has completed.
ADMIN_PACKET_SERVER_PONG, ///< The server replies to a ping request from the admin.
ADMIN_PACKET_SERVER_CMD_LOGGING, ///< The server gives the admin copies of incoming command packets.
ADMIN_PACKET_SERVER_AUTH_REQUEST, ///< The server gives the admin the used authentication method and required parameters.
INVALID_ADMIN_PACKET = 0xFF, ///< An invalid marker for admin packets.
};
@ -119,8 +122,8 @@ protected:
NetworkRecvStatus ReceiveInvalidPacket(PacketAdminType type);
/**
* Join the admin network:
* string Password the server is expecting for this network.
* Join the admin network using an unsecured password exchange:
* string Unsecured password the server is expecting for this network.
* string Name of the application being used to connect.
* string Version string of the application being used to connect.
* @param p The packet that was just received.
@ -201,6 +204,32 @@ protected:
*/
virtual NetworkRecvStatus Receive_ADMIN_PING(Packet &p);
/**
* Join the admin network using a secure authentication method:
* string Name of the application being used to connect.
* string Version string of the application being used to connect.
* uint16_t Bitmask of supported authentication methods. See \c NetworkAuthenticationMethod for the supported methods.
*
* The server will determine which of the authentication methods supplied by the client will be used.
* When there is no supported authentication method, an \c ADMIN_PACKET_SERVER_ERROR packet will be
* sent with \c NETWORK_ERROR_NO_AUTHENTICATION_METHOD_AVAILABLE as error.
* @param p The packet that was just received.
* @return The state the network should have.
*/
virtual NetworkRecvStatus Receive_ADMIN_JOIN_SECURE(Packet &p);
/**
* Admin responds to \c ADMIN_PACKET_SERVER_AUTH_REQUEST with the appropriate
* data given the agreed upon \c NetworkAuthenticationMethod.
* With \c NETWORK_AUTH_METHOD_X25519_PAKE and \c NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY:
* 32 * uint8_t Public key of the client.
* 16 * uint8_t Message authentication code (mac).
* 8 * uint8_t Encrypted message of the authentication (just random bytes).
* @param p The packet that was just received.
* @return The state the network should have.
*/
virtual NetworkRecvStatus Receive_ADMIN_AUTH_RESPONSE(Packet &p);
/**
* The server is full (connection gets closed).
* @param p The packet that was just received.
@ -473,6 +502,17 @@ protected:
*/
virtual NetworkRecvStatus Receive_SERVER_CMD_LOGGING(Packet &p);
/**
* Server requests authentication challenge from the admin.
* uint8_t The chosen authentication method from \c NetworkAuthenticationMethod.
* With \c NETWORK_AUTH_METHOD_X25519_PAKE and \c NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY:
* 32 * uint8_t Public key of the server.
* 24 * uint8_t Nonce to use for the encryption.
* @param p The packet that was just received.
* @return The state the network should have.
*/
virtual NetworkRecvStatus Receive_SERVER_AUTH_REQUEST(Packet &p);
/**
* Send a ping-reply (pong) to the admin that sent us the ping packet.
* uint32_t Integer identifier - should be the same as read from the admins ping packet.

View File

@ -339,6 +339,7 @@ StringID GetNetworkErrorMsg(NetworkErrorCode err)
STR_NETWORK_ERROR_CLIENT_TIMEOUT_JOIN,
STR_NETWORK_ERROR_CLIENT_INVALID_CLIENT_NAME,
STR_NETWORK_ERROR_CLIENT_NOT_ON_ALLOW_LIST,
STR_NETWORK_ERROR_CLIENT_NO_AUTHENTICATION_METHOD_AVAILABLE,
};
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);
@ -905,8 +906,8 @@ bool NetworkServerStart()
Debug(net, 5, "Starting listeners for clients");
if (!ServerNetworkGameSocketHandler::Listen(_settings_client.network.server_port)) return false;
/* Only listen for admins when the password isn't empty. */
if (!_settings_client.network.admin_password.empty()) {
/* Only listen for admins when the authentication is configured. */
if (_settings_client.network.AdminAuthenticationConfigured()) {
Debug(net, 5, "Starting listeners for admins");
if (!ServerNetworkAdminSocketHandler::Listen(_settings_client.network.server_admin_port)) return false;
}

View File

@ -38,6 +38,9 @@ uint8_t _network_admins_connected = 0;
NetworkAdminSocketPool _networkadminsocket_pool("NetworkAdminSocket");
INSTANTIATE_POOL_METHODS(NetworkAdminSocket)
static NetworkAuthenticationDefaultPasswordProvider _admin_password_provider(_settings_client.network.admin_password); ///< Provides the password validation for the game's password.
static NetworkAuthenticationDefaultAuthorizedKeyHandler _admin_authorized_key_handler(_settings_client.network.admin_authorized_keys); ///< Provides the authorized key handling for the game authentication.
/** The timeout for authorisation of the client. */
static const std::chrono::seconds ADMIN_AUTHORISATION_TIMEOUT(10);
@ -90,7 +93,7 @@ ServerNetworkAdminSocketHandler::~ServerNetworkAdminSocketHandler()
*/
/* static */ bool ServerNetworkAdminSocketHandler::AllowConnection()
{
bool accept = !_settings_client.network.admin_password.empty() && _network_admins_connected < MAX_ADMINS;
bool accept = _settings_client.network.AdminAuthenticationConfigured() && _network_admins_connected < MAX_ADMINS;
/* We can't go over the MAX_ADMINS limit here. However, if we accept
* the connection, there has to be space in the pool. */
static_assert(NetworkAdminSocketPool::MAX_SIZE == MAX_ADMINS);
@ -134,6 +137,9 @@ ServerNetworkAdminSocketHandler::~ServerNetworkAdminSocketHandler()
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendError(NetworkErrorCode error)
{
/* Whatever the error might be, authentication (keys) must be released as soon as possible. */
this->authentication_handler = nullptr;
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_ERROR);
p->Send_uint8(error);
@ -791,6 +797,71 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_EXTERNAL_CHAT(P
return NETWORK_RECV_STATUS_OKAY;
}
/*
* Secure authentication send and receive methods.
*/
NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_JOIN_SECURE(Packet &p)
{
if (this->status != ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
this->admin_name = p.Recv_string(NETWORK_CLIENT_NAME_LENGTH);
this->admin_version = p.Recv_string(NETWORK_REVISION_LENGTH);
NetworkAuthenticationMethodMask method_mask = p.Recv_uint16();
/* Always exclude key exchange only, as that provides no credential checking. */
ClrBit(method_mask, NETWORK_AUTH_METHOD_X25519_KEY_EXCHANGE_ONLY);
if (this->admin_name.empty() || this->admin_version.empty()) {
/* No name or version supplied. */
return this->SendError(NETWORK_ERROR_ILLEGAL_PACKET);
}
auto handler = NetworkAuthenticationServerHandler::Create(&_admin_password_provider, &_admin_authorized_key_handler, method_mask);
if (!handler->CanBeUsed()) return this->SendError(NETWORK_ERROR_NO_AUTHENTICATION_METHOD_AVAILABLE);
this->authentication_handler = std::move(handler);
Debug(net, 3, "[admin] '{}' ({}) has connected", this->admin_name, this->admin_version);
return this->SendAuthRequest();
}
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendAuthRequest()
{
this->status = ADMIN_STATUS_AUTHENTICATE;
Debug(net, 6, "[admin] '{}' ({}) authenticating using {}", this->admin_name, this->admin_version, this->authentication_handler->GetName());
auto p = std::make_unique<Packet>(this, ADMIN_PACKET_SERVER_AUTH_REQUEST);
this->authentication_handler->SendRequest(*p);
this->SendPacket(std::move(p));
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_AUTH_RESPONSE(Packet &p)
{
if (this->status != ADMIN_STATUS_AUTHENTICATE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
switch (this->authentication_handler->ReceiveResponse(p)) {
case NetworkAuthenticationServerHandler::AUTHENTICATED:
Debug(net, 3, "[admin] '{}' ({}) authenticated", this->admin_name, this->admin_version);
this->authentication_handler = nullptr;
return this->SendProtocol();
case NetworkAuthenticationServerHandler::RETRY_NEXT_METHOD:
Debug(net, 6, "[admin] '{}' ({}) authentication failed, trying next method", this->admin_name, this->admin_version);
return this->SendAuthRequest();
case NetworkAuthenticationServerHandler::NOT_AUTHENTICATED:
default:
Debug(net, 3, "[admin] '{}' ({}) authentication failed", this->admin_name, this->admin_version);
return this->SendError(NETWORK_ERROR_WRONG_PASSWORD);
}
}
/*
* Useful wrapper functions
*/

View File

@ -23,6 +23,8 @@ extern NetworkAdminSocketPool _networkadminsocket_pool;
/** Class for handling the server side of the game connection. */
class ServerNetworkAdminSocketHandler : public NetworkAdminSocketPool::PoolItem<&_networkadminsocket_pool>, public NetworkAdminSocketHandler, public TCPListenHandler<ServerNetworkAdminSocketHandler, ADMIN_PACKET_SERVER_FULL, ADMIN_PACKET_SERVER_BANNED> {
private:
std::unique_ptr<NetworkAuthenticationServerHandler> authentication_handler; ///< The handler for the authentication.
protected:
NetworkRecvStatus Receive_ADMIN_JOIN(Packet &p) override;
NetworkRecvStatus Receive_ADMIN_QUIT(Packet &p) override;
@ -33,9 +35,12 @@ protected:
NetworkRecvStatus Receive_ADMIN_RCON(Packet &p) override;
NetworkRecvStatus Receive_ADMIN_GAMESCRIPT(Packet &p) override;
NetworkRecvStatus Receive_ADMIN_PING(Packet &p) override;
NetworkRecvStatus Receive_ADMIN_JOIN_SECURE(Packet &p) override;
NetworkRecvStatus Receive_ADMIN_AUTH_RESPONSE(Packet &p) override;
NetworkRecvStatus SendProtocol();
NetworkRecvStatus SendPong(uint32_t d1);
NetworkRecvStatus SendAuthRequest();
public:
AdminUpdateFrequency update_frequency[ADMIN_UPDATE_END]; ///< Admin requested update intervals.
std::chrono::steady_clock::time_point connect_time; ///< Time of connection.

View File

@ -646,6 +646,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR(Packet &p
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
STR_NETWORK_ERROR_SERVER_ERROR, // NETWORK_ERROR_NO_AUTHENTICATION_METHOD_AVAILABLE
};
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);

View File

@ -175,7 +175,7 @@ public:
/** The authentication method that can be used. */
enum NetworkAuthenticationMethod : uint8_t {
NETWORK_AUTH_METHOD_X25519_KEY_EXCHANGE_ONLY, ///< No actual authentication is taking place, just perform a x25519 key exchange.
NETWORK_AUTH_METHOD_X25519_KEY_EXCHANGE_ONLY, ///< No actual authentication is taking place, just perform a x25519 key exchange. This method is not supported for the admin connection.
NETWORK_AUTH_METHOD_X25519_PAKE, ///< Authentication using x25519 password-authenticated key agreement.
NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY, ///< Authentication using x22519 key exchange and authorized keys.
NETWORK_AUTH_METHOD_END, ///< Must ALWAYS be on the end of this list!! (period)

View File

@ -134,6 +134,7 @@ enum NetworkErrorCode {
NETWORK_ERROR_TIMEOUT_JOIN,
NETWORK_ERROR_INVALID_CLIENT_NAME,
NETWORK_ERROR_NOT_ON_ALLOW_LIST,
NETWORK_ERROR_NO_AUTHENTICATION_METHOD_AVAILABLE,
NETWORK_ERROR_END,
};

View File

@ -140,6 +140,7 @@ private:
"server_bind_addresses",
"server_authorized_keys",
"rcon_authorized_keys",
"admin_authorized_keys"
};
public:
@ -1266,6 +1267,7 @@ static void HandleSettingDescs(IniFile &generic_ini, IniFile &private_ini, IniFi
proc_list(private_ini, "bans", _network_ban_list);
proc_list(private_ini, "server_authorized_keys", _settings_client.network.server_authorized_keys);
proc_list(private_ini, "rcon_authorized_keys", _settings_client.network.rcon_authorized_keys);
proc_list(private_ini, "admin_authorized_keys", _settings_client.network.admin_authorized_keys);
}
}

View File

@ -317,6 +317,7 @@ struct NetworkSettings {
std::string rcon_password; ///< password for rconsole (server side)
NetworkAuthorizedKeys rcon_authorized_keys; ///< Public keys of clients that are authorized to use the rconsole (server side).
std::string admin_password; ///< password for the admin network
NetworkAuthorizedKeys admin_authorized_keys; ///< Public keys of clients that are authorized to use the admin network.
std::string client_name; ///< name of the player (as client)
std::string client_secret_key; ///< The secret key of the client for authorized key logins.
std::string client_public_key; ///< The public key of the client for authorized key logins.
@ -333,6 +334,8 @@ struct NetworkSettings {
std::string last_joined; ///< Last joined server
UseRelayService use_relay_service; ///< Use relay service?
ParticipateSurvey participate_survey; ///< Participate in the automated survey
bool AdminAuthenticationConfigured() const { return !this->admin_password.empty() || !this->admin_authorized_keys.empty(); }
};
/** Settings related to the creation of games. */