1
0
mirror of https://github.com/OpenTTD/OpenTTD synced 2025-12-18 19:02:20 +01:00

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
committed by rubidium42
parent b03ae8ad75
commit 3094b0ce1d
12 changed files with 140 additions and 8 deletions

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
*/