From fb9d4afa5c5115b39afb06b09ffb8d8ccd11fba0 Mon Sep 17 00:00:00 2001 From: Rubidium Date: Thu, 14 Mar 2024 20:07:31 +0100 Subject: [PATCH] Codechange: add set of classes providing authentication and encryption --- src/network/CMakeLists.txt | 3 + src/network/network_crypto.cpp | 474 ++++++++++++++++++++++++++ src/network/network_crypto.h | 287 ++++++++++++++++ src/network/network_crypto_internal.h | 341 ++++++++++++++++++ src/tests/CMakeLists.txt | 1 + src/tests/test_network_crypto.cpp | 199 +++++++++++ 6 files changed, 1305 insertions(+) create mode 100644 src/network/network_crypto.cpp create mode 100644 src/network/network_crypto.h create mode 100644 src/network/network_crypto_internal.h create mode 100644 src/tests/test_network_crypto.cpp diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 5b97de5cd2..b78142f457 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -16,6 +16,9 @@ add_files( network_content_gui.h network_coordinator.cpp network_coordinator.h + network_crypto.cpp + network_crypto.h + network_crypto_internal.h network_func.h network_gamelist.cpp network_gamelist.h diff --git a/src/network/network_crypto.cpp b/src/network/network_crypto.cpp new file mode 100644 index 0000000000..7c56a1d988 --- /dev/null +++ b/src/network/network_crypto.cpp @@ -0,0 +1,474 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file network_crypto.cpp Implementation of the network specific cryptography helpers. */ + +#include "../stdafx.h" + +#include "network_crypto_internal.h" +#include "core/packet.h" + +#include "../3rdparty/monocypher/monocypher.h" +#include "../core/random_func.hpp" +#include "../debug.h" +#include "../string_func.h" + +#include "../safeguards.h" + +/** + * Call \c crypto_wipe for all the data in the given span. + * @param span The span to cryptographically wipe. + */ +static void crypto_wipe(std::span span) +{ + crypto_wipe(span.data(), span.size()); +} + +/** Ensure the derived keys do not get leaked when we're done with it. */ +X25519DerivedKeys::~X25519DerivedKeys() +{ + crypto_wipe(keys); +} + +/** + * Get the key to encrypt or decrypt a message sent from the client to the server. + * @return The raw bytes of the key. + */ +std::span X25519DerivedKeys::ClientToServer() const +{ + return std::span(this->keys.data(), X25519_KEY_SIZE); +} + +/** + * Get the key to encrypt or decrypt a message sent from the server to the client. + * @return The raw bytes of the key. + */ +std::span X25519DerivedKeys::ServerToClient() const +{ + return std::span(this->keys.data() + X25519_KEY_SIZE, X25519_KEY_SIZE); +} + +/** + * Perform the actual key exchange. + * @param peer_public_key The public key chosen by the other participant of the key exchange. + * @param side Whether we are the client or server; used to hash the public key of us and the peer in the right order. + * @param our_secret_key The secret key of us. + * @param our_public_key The public key of us. + * @param extra_payload Extra payload to put into the hash function to create the derived keys. + * @return True when the key exchange has succeeded, false when an illegal public key was given. + */ +bool X25519DerivedKeys::Exchange(const X25519PublicKey &peer_public_key, X25519KeyExchangeSide side, + const X25519SecretKey &our_secret_key, const X25519PublicKey &our_public_key, std::string_view extra_payload) +{ + X25519Key shared_secret; + crypto_x25519(shared_secret.data(), our_secret_key.data(), peer_public_key.data()); + if (std::all_of(shared_secret.begin(), shared_secret.end(), [](auto v) { return v == 0; })) { + /* A shared secret of all zeros means that the peer tried to force the shared secret to a known constant. */ + return false; + } + + crypto_blake2b_ctx ctx; + crypto_blake2b_init(&ctx, this->keys.size()); + crypto_blake2b_update(&ctx, shared_secret.data(), shared_secret.size()); + switch (side) { + case X25519KeyExchangeSide::SERVER: + crypto_blake2b_update(&ctx, our_public_key.data(), our_public_key.size()); + crypto_blake2b_update(&ctx, peer_public_key.data(), peer_public_key.size()); + break; + case X25519KeyExchangeSide::CLIENT: + crypto_blake2b_update(&ctx, peer_public_key.data(), peer_public_key.size()); + crypto_blake2b_update(&ctx, our_public_key.data(), our_public_key.size()); + break; + default: + NOT_REACHED(); + } + crypto_blake2b_update(&ctx, reinterpret_cast(extra_payload.data()), extra_payload.size()); + crypto_blake2b_final(&ctx, this->keys.data()); + return true; +} + +/** + * Encryption handler implementation for monocypther encryption after a X25519 key exchange. + */ +class X25519EncryptionHandler : public NetworkEncryptionHandler { +private: + crypto_aead_ctx context; ///< The actual encryption context. + +public: + /** + * Create the encryption handler. + * @param key The key used for the encryption. + * @param nonce The nonce used for the encryption. + */ + X25519EncryptionHandler(const std::span key, const X25519Nonce &nonce) + { + assert(key.size() == X25519_KEY_SIZE); + crypto_aead_init_x(&this->context, key.data(), nonce.data()); + } + + /** Ensure the encryption context is wiped! */ + ~X25519EncryptionHandler() + { + crypto_wipe(&this->context, sizeof(this->context)); + } + + size_t MACSize() const override + { + return X25519_MAC_SIZE; + } + + bool Decrypt(std::span mac, std::span message) override + { + return crypto_aead_read(&this->context, message.data(), mac.data(), nullptr, 0, message.data(), message.size()) == 0; + } + + void Encrypt(std::span mac, std::span message) override + { + crypto_aead_write(&this->context, message.data(), mac.data(), nullptr, 0, message.data(), message.size()); + } +}; + +/** Ensure the key does not get leaked when we're done with it. */ +X25519Key::~X25519Key() +{ + crypto_wipe(*this); +} + +/** + * Create a new secret key that's filled with random bytes. + * @return The randomly filled key. + */ +/* static */ X25519SecretKey X25519SecretKey::CreateRandom() +{ + X25519SecretKey secret_key; + RandomBytesWithFallback(secret_key); + return secret_key; +} + +/** + * Create the public key associated with this secret key. + * @return The public key. + */ +X25519PublicKey X25519SecretKey::CreatePublicKey() const +{ + X25519PublicKey public_key; + crypto_x25519_public_key(public_key.data(), this->data()); + return public_key; +} + +/** + * Create a new nonce that's filled with random bytes. + * @return The randomly filled nonce. + */ +/* static */ X25519Nonce X25519Nonce::CreateRandom() +{ + X25519Nonce nonce; + RandomBytesWithFallback(nonce); + return nonce; +} + +/** Ensure the nonce does not get leaked when we're done with it. */ +X25519Nonce::~X25519Nonce() +{ + crypto_wipe(*this); +} + +/** + * Create the handler, and generate the public keys accordingly. + * @param secret_key The secret key to use for this handler. Defaults to secure random data. + */ +X25519AuthenticationHandler::X25519AuthenticationHandler(const X25519SecretKey &secret_key) : + our_secret_key(secret_key), our_public_key(secret_key.CreatePublicKey()), nonce(X25519Nonce::CreateRandom()) +{ +} + +/* virtual */ void X25519AuthenticationHandler::SendRequest(Packet &p) +{ + p.Send_bytes(this->our_public_key); + p.Send_bytes(this->nonce); +} + +/** + * Read the key exchange data from a \c Packet that came from the server, + * @param p The packet that has been received. + * @return True when the data seems correct. + */ +bool X25519AuthenticationHandler::ReceiveRequest(Packet &p) +{ + if (p.RemainingBytesToTransfer() != X25519_KEY_SIZE + X25519_NONCE_SIZE) { + Debug(net, 1, "[crypto] Received auth response of illegal size; authentication aborted."); + return false; + } + + p.Recv_bytes(this->peer_public_key); + p.Recv_bytes(this->nonce); + return true; +} + +/** + * Perform the key exchange, and when that is correct fill the \c Packet with the appropriate data. + * @param p The packet that has to be sent. + * @param derived_key_extra_payload The extra payload to pass to the key exchange. + * @return Whether the key exchange was successful or not. + */ +bool X25519AuthenticationHandler::SendResponse(Packet &p, std::string_view derived_key_extra_payload) +{ + if (!this->derived_keys.Exchange(this->peer_public_key, X25519KeyExchangeSide::CLIENT, + this->our_secret_key, this->our_public_key, derived_key_extra_payload)) { + Debug(net, 0, "[crypto] Peer sent an illegal public key; authentication aborted."); + return false; + } + + X25519KeyExchangeMessage message; + RandomBytesWithFallback(message); + X25519Mac mac; + + crypto_aead_lock(message.data(), mac.data(), this->derived_keys.ClientToServer().data(), nonce.data(), + this->our_public_key.data(), this->our_public_key.size(), message.data(), message.size()); + + p.Send_bytes(this->our_public_key); + p.Send_bytes(mac); + p.Send_bytes(message); + return true; +} + +/** + * Get the public key the peer provided for the key exchange. + * @return The hexadecimal string representation of the peer's public key. + */ +std::string X25519AuthenticationHandler::GetPeerPublicKey() const +{ + return FormatArrayAsHex(this->peer_public_key); +} + +std::unique_ptr X25519AuthenticationHandler::CreateClientToServerEncryptionHandler() const +{ + return std::make_unique(this->derived_keys.ClientToServer(), this->nonce); +} + +std::unique_ptr X25519AuthenticationHandler::CreateServerToClientEncryptionHandler() const +{ + return std::make_unique(this->derived_keys.ServerToClient(), this->nonce); +} + +/** + * Read the key exchange data from a \c Packet that came from the client, and check whether the client passes the key + * exchange successfully. + * @param p The packet that has been received. + * @param derived_key_extra_payload The extra payload to pass to the key exchange. + * @return Whether the authentication was successful or not. + */ +NetworkAuthenticationServerHandler::ResponseResult X25519AuthenticationHandler::ReceiveResponse(Packet &p, std::string_view derived_key_extra_payload) +{ + if (p.RemainingBytesToTransfer() != X25519_KEY_SIZE + X25519_MAC_SIZE + X25519_KEY_EXCHANGE_MESSAGE_SIZE) { + Debug(net, 1, "[crypto] Received auth response of illegal size; authentication aborted."); + return NetworkAuthenticationServerHandler::NOT_AUTHENTICATED; + } + + X25519KeyExchangeMessage message{}; + X25519Mac mac; + + p.Recv_bytes(this->peer_public_key); + p.Recv_bytes(mac); + p.Recv_bytes(message); + + if (!this->derived_keys.Exchange(this->peer_public_key, X25519KeyExchangeSide::SERVER, + this->our_secret_key, this->our_public_key, derived_key_extra_payload)) { + Debug(net, 0, "[crypto] Peer sent an illegal public key; authentication aborted."); + return NetworkAuthenticationServerHandler::NOT_AUTHENTICATED; + } + + if (crypto_aead_unlock(message.data(), mac.data(), this->derived_keys.ClientToServer().data(), nonce.data(), + this->peer_public_key.data(), this->peer_public_key.size(), message.data(), message.size()) != 0) { + /* + * The ciphertext and the message authentication code do not match with the encryption key. + * This is most likely an invalid password, or possibly a bug in the client. + */ + return NetworkAuthenticationServerHandler::NOT_AUTHENTICATED; + } + + return NetworkAuthenticationServerHandler::AUTHENTICATED; +} + + +/* virtual */ NetworkAuthenticationClientHandler::RequestResult X25519PAKEClientHandler::ReceiveRequest(struct Packet &p) +{ + bool success = this->X25519AuthenticationHandler::ReceiveRequest(p); + if (!success) return NetworkAuthenticationClientHandler::INVALID; + + this->handler->AskUserForPassword(this->handler); + return NetworkAuthenticationClientHandler::AWAIT_USER_INPUT; +} + +/** + * Get the secret key from the given string. If that is not a valid secret key, reset it with a random one. + * Furthermore update the public key so it is always in sync with the private key. + * @param secret_key The secret key to read/validate/fix. + * @param public_key The public key to update. + * @return The valid secret key. + */ +/* static */ X25519SecretKey X25519AuthorizedKeyClientHandler::GetValidSecretKeyAndUpdatePublicKey(std::string &secret_key, std::string &public_key) +{ + X25519SecretKey key{}; + if (!ConvertHexToBytes(secret_key, key)) { + if (secret_key.empty()) { + Debug(net, 3, "[crypto] Creating a new random key"); + } else { + Debug(net, 0, "[crypto] Found invalid secret key, creating a new random key"); + } + key = X25519SecretKey::CreateRandom(); + secret_key = FormatArrayAsHex(key); + } + + public_key = FormatArrayAsHex(key.CreatePublicKey()); + return key; +} + +/* virtual */ NetworkAuthenticationServerHandler::ResponseResult X25519AuthorizedKeyServerHandler::ReceiveResponse(Packet &p) +{ + ResponseResult result = this->X25519AuthenticationHandler::ReceiveResponse(p, {}); + if (result != AUTHENTICATED) return result; + + std::string peer_public_key = this->GetPeerPublicKey(); + return this->authorized_key_handler->IsAllowed(peer_public_key) ? AUTHENTICATED : NOT_AUTHENTICATED; +} + + +/* virtual */ NetworkAuthenticationClientHandler::RequestResult CombinedAuthenticationClientHandler::ReceiveRequest(struct Packet &p) +{ + NetworkAuthenticationMethod method = static_cast(p.Recv_uint8()); + + auto is_of_method = [method](Handler &handler) { return handler->GetAuthenticationMethod() == method; }; + auto it = std::find_if(handlers.begin(), handlers.end(), is_of_method); + if (it == handlers.end()) return INVALID; + + this->current_handler = it->get(); + + Debug(net, 9, "Received {} authentication request", this->GetName()); + return this->current_handler->ReceiveRequest(p); +} + +/* virtual */ bool CombinedAuthenticationClientHandler::SendResponse(struct Packet &p) +{ + Debug(net, 9, "Sending {} authentication response", this->GetName()); + + return this->current_handler->SendResponse(p); +} + +/* virtual */ std::string_view CombinedAuthenticationClientHandler::GetName() const +{ + return this->current_handler != nullptr ? this->current_handler->GetName() : "Unknown"; +} + +/* virtual */ NetworkAuthenticationMethod CombinedAuthenticationClientHandler::GetAuthenticationMethod() const +{ + return this->current_handler != nullptr ? this->current_handler->GetAuthenticationMethod() : NETWORK_AUTH_METHOD_END; +} + + +/** + * Add the given sub-handler to this handler, if the handler can be used (e.g. there are authorized keys or there is a password). + * @param handler The handler to add. + */ +void CombinedAuthenticationServerHandler::Add(CombinedAuthenticationServerHandler::Handler &&handler) +{ + /* Is the handler configured correctly, e.g. does it have a password? */ + if (!handler->CanBeUsed()) return; + + this->handlers.push_back(std::move(handler)); +} + +/* virtual */ void CombinedAuthenticationServerHandler::SendRequest(struct Packet &p) +{ + Debug(net, 9, "Sending {} authentication request", this->GetName()); + + p.Send_uint8(this->handlers.back()->GetAuthenticationMethod()); + this->handlers.back()->SendRequest(p); +} + +/* virtual */ NetworkAuthenticationServerHandler::ResponseResult CombinedAuthenticationServerHandler::ReceiveResponse(struct Packet &p) +{ + Debug(net, 9, "Receiving {} authentication response", this->GetName()); + + ResponseResult result = this->handlers.back()->ReceiveResponse(p); + if (result != NOT_AUTHENTICATED) return result; + + this->handlers.pop_back(); + return this->CanBeUsed() ? RETRY_NEXT_METHOD : NOT_AUTHENTICATED; +} + +/* virtual */ std::string_view CombinedAuthenticationServerHandler::GetName() const +{ + return this->CanBeUsed() ? this->handlers.back()->GetName() : "Unknown"; +} + +/* virtual */ NetworkAuthenticationMethod CombinedAuthenticationServerHandler::GetAuthenticationMethod() const +{ + return this->CanBeUsed() ? this->handlers.back()->GetAuthenticationMethod() : NETWORK_AUTH_METHOD_END; +} + +/* virtual */ bool CombinedAuthenticationServerHandler::CanBeUsed() const +{ + return !this->handlers.empty(); +} + + +/* virtual */ void NetworkAuthenticationPasswordRequestHandler::Reply(const std::string &password) +{ + this->password = password; + this->SendResponse(); +} + +/* virtual */ bool NetworkAuthenticationDefaultAuthorizedKeyHandler::IsAllowed(std::string_view peer_public_key) const +{ + for (const auto &allowed : *this->authorized_keys) { + if (StrEqualsIgnoreCase(allowed, peer_public_key)) return true; + } + return false; +} + + +/** + * Create a NetworkAuthenticationClientHandler. + * @param password_handler The handler for when a request for password needs to be passed on to the user. + * @param secret_key The location where the secret key is stored; can be overwritten when invalid. + * @param public_key The location where the public key is stored; can be overwritten when invalid. + */ +/* static */ std::unique_ptr NetworkAuthenticationClientHandler::Create(std::shared_ptr password_handler, std::string &secret_key, std::string &public_key) +{ + auto secret = X25519AuthorizedKeyClientHandler::GetValidSecretKeyAndUpdatePublicKey(secret_key, public_key); + auto handler = std::make_unique(); + handler->Add(std::make_unique(secret)); + handler->Add(std::make_unique(secret, std::move(password_handler))); + handler->Add(std::make_unique(secret)); + return handler; +} + +/** + * Create a NetworkAuthenticationServerHandler. + * @param password_provider Callback to provide the password handling. Must remain valid until the authentication has succeeded or failed. Can be \c nullptr to skip password checks. + * @param authorized_key_handler Callback to provide the authorized key handling. Must remain valid until the authentication has succeeded or failed. Can be \c nullptr to skip authorized key checks. + * @param client_supported_method_mask Bitmask of the methods that are supported by the client. Defaults to support of all methods. + */ +std::unique_ptr NetworkAuthenticationServerHandler::Create(const NetworkAuthenticationPasswordProvider *password_provider, const NetworkAuthenticationAuthorizedKeyHandler *authorized_key_handler, NetworkAuthenticationMethodMask client_supported_method_mask) +{ + auto secret = X25519SecretKey::CreateRandom(); + auto handler = std::make_unique(); + if (password_provider != nullptr && HasBit(client_supported_method_mask, NETWORK_AUTH_METHOD_X25519_PAKE)) { + handler->Add(std::make_unique(secret, password_provider)); + } + + if (authorized_key_handler != nullptr && HasBit(client_supported_method_mask, NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY)) { + handler->Add(std::make_unique(secret, authorized_key_handler)); + } + + if (!handler->CanBeUsed() && HasBit(client_supported_method_mask, NETWORK_AUTH_METHOD_X25519_KEY_EXCHANGE_ONLY)) { + /* Fall back to the plain handler when neither password, nor authorized keys are configured. */ + handler->Add(std::make_unique(secret)); + } + return handler; +} diff --git a/src/network/network_crypto.h b/src/network/network_crypto.h new file mode 100644 index 0000000000..60e2b6b71b --- /dev/null +++ b/src/network/network_crypto.h @@ -0,0 +1,287 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file network_crypto.h Crypto specific bits of the network handling. + * + * This provides a set of functionality to perform authentication combined with a key exchange, + * to create a shared secret as well as encryption using those shared secrets. + * + * For the authentication/key exchange, the server determines the available methods and creates + * the appropriate \c NetworkAuthenticationServerHandler. This will be used to create a request + * for the client, which instantiates a \c NetworkAuthenticationClientHandler to handle that + * request. + * At the moment there are three types of request: key exchange only, password-authenticated key + * exchange (PAKE) and authorized keys. When the request is for a password, the user is asked + * for the password via an essentially asynchronous callback from the client handler. For the + * other requests no input from the user is needed, and these are immediately ready to generate + * the response for the server. + * + * The server will validate the response resulting in either the user being authenticated or not. + * When the user failed authentication, there might be a possibility to retry. For example when + * the server has configured authorized keys and passwords; when the client fails with the + * authorized keys, it will retry with the password. + * + * Once the key exchange/authentication has been done, the server can signal the client to + * upgrade the network connection to use encryption using the shared secret of the key exchange. + */ + +#ifndef NETWORK_CRYPTO_H +#define NETWORK_CRYPTO_H + +/** + * Base class for handling the encryption (or decryption) of a network connection. + */ +class NetworkEncryptionHandler { +public: + virtual ~NetworkEncryptionHandler() {} + + /** + * Get the size of the MAC (Message Authentication Code) used by the underlying encryption protocol. + * @return The size, in bytes, of the MACs. + */ + virtual size_t MACSize() const = 0; + + /** + * Decrypt the given message in-place, validating against the given MAC. + * @param mac The message authentication code (MAC). + * @param message The location of the message to decrypt. + * @return Whether decryption and authentication/validation of the message succeeded. + */ + virtual bool Decrypt(std::span mac, std::span message) = 0; + + /** + * Encrypt the given message in-place, and write the associated MAC. + * @param mac The location to write the message authentication code (MAC) to. + * @param message The location of the message to encrypt. + */ + virtual void Encrypt(std::span mac, std::span message) = 0; +}; + + +/** + * Callback interface for requests for passwords in the context of network authentication. + */ +class NetworkAuthenticationPasswordRequest { +public: + virtual ~NetworkAuthenticationPasswordRequest() {} + + /** + * Reply to the request with the given password. + */ + virtual void Reply(const std::string &password) = 0; +}; + +/** + * Callback interface for client implementations to provide the handling of the password requests. + */ +class NetworkAuthenticationPasswordRequestHandler : public NetworkAuthenticationPasswordRequest { +protected: + friend class X25519PAKEClientHandler; + + std::string password; ///< The entered password. +public: + + virtual void Reply(const std::string &password) override; + + /** + * Callback to trigger sending the response for the password request. + */ + virtual void SendResponse() = 0; + + /** + * Callback to trigger asking the user for the password. + * @param request The request to the user, to which it can reply with the password. + */ + virtual void AskUserForPassword(std::shared_ptr request) = 0; +}; + + +/** + * Callback interface for server implementations to provide the current password. + */ +class NetworkAuthenticationPasswordProvider { +public: + virtual ~NetworkAuthenticationPasswordProvider() {} + + /** + * Callback to return the password where to validate against. + * @return \c std::string_view of the current password; an empty view means no password check will be performed. + */ + virtual std::string_view GetPassword() const = 0; +}; + +/** + * Default implementation of the password provider. + */ +class NetworkAuthenticationDefaultPasswordProvider : public NetworkAuthenticationPasswordProvider { +private: + const std::string *password; ///< The password to check against. +public: + /** + * Create the provider with the pointer to the password that is to be used. A pointer, so this can handle + * situations where the password gets changed over time. + * @param password The reference to the configured password. + */ + NetworkAuthenticationDefaultPasswordProvider(const std::string &password) : password(&password) {} + + std::string_view GetPassword() const override { return *this->password; }; +}; + +/** + * Callback interface for server implementations to provide the authorized key validation. + */ +class NetworkAuthenticationAuthorizedKeyHandler { +public: + virtual ~NetworkAuthenticationAuthorizedKeyHandler() {} + + /** + * Check whether the key handler can be used, i.e. whether there are authorized keys to check against. + * @return \c true when it can be used, otherwise \c false. + */ + virtual bool CanBeUsed() const = 0; + + /** + * Check whether the given public key of the peer is allowed in. + * @param peer_public_key The public key of the peer to check against. + * @return \c true when the key is allowed, otherwise \c false. + */ + virtual bool IsAllowed(std::string_view peer_public_key) const = 0; +}; + +/** + * Default implementation for the authorized key handler. + */ +class NetworkAuthenticationDefaultAuthorizedKeyHandler : public NetworkAuthenticationAuthorizedKeyHandler { +private: + const std::vector *authorized_keys; ///< The authorized keys to check against. +public: + /** + * Create the handler that uses the given authorized keys to check against. + * @param authorized_keys The reference to the authorized keys to check against. + */ + NetworkAuthenticationDefaultAuthorizedKeyHandler(const std::vector &authorized_keys) : authorized_keys(&authorized_keys) {} + + bool CanBeUsed() const override { return !this->authorized_keys->empty(); } + bool IsAllowed(std::string_view peer_public_key) const override; +}; + + +/** 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_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) +}; + +/** The mask of authentication methods that can be used. */ +using NetworkAuthenticationMethodMask = uint16_t; + +/** + * Base class for cryptographic authentication handlers. + */ +class NetworkAuthenticationHandler { +public: + virtual ~NetworkAuthenticationHandler() {} + + /** + * Get the name of the handler for debug messages. + * @return The name of the handler. + */ + virtual std::string_view GetName() const = 0; + + /** + * Get the method this handler is providing functionality for. + * @return The \c NetworkAuthenticationMethod. + */ + virtual NetworkAuthenticationMethod GetAuthenticationMethod() const = 0; + + /** + * Create a \a NetworkEncryptionHandler to encrypt or decrypt messages from the client to the server. + * @return The handler for the client to server encryption. + */ + virtual std::unique_ptr CreateClientToServerEncryptionHandler() const = 0; + + /** + * Create a \a NetworkEncryptionHandler to encrypt or decrypt messages from the server to the client. + * @return The handler for the server to client encryption. + */ + virtual std::unique_ptr CreateServerToClientEncryptionHandler() const = 0; +}; + +/** + * Base class for client side cryptographic authentication handlers. + */ +class NetworkAuthenticationClientHandler : public NetworkAuthenticationHandler { +public: + /** The processing result of receiving a request. */ + enum RequestResult { + AWAIT_USER_INPUT, ///< We have requested some user input, but must wait on that. + READY_FOR_RESPONSE, ///< We do not have to wait for user input, and can immediately respond to the server. + INVALID, ///< We have received an invalid request. + }; + + /** + * Read a request from the server. + * @param p The packet to read the request from. + * @return True when valid, otherwise false. + */ + virtual RequestResult ReceiveRequest(struct Packet &p) = 0; + + /** + * Create the response to send to the server. + * @param p The packet to write the response from. + * @return True when a valid packet was made, otherwise false. + */ + virtual bool SendResponse(struct Packet &p) = 0; + + static std::unique_ptr Create(std::shared_ptr password_handler, std::string &secret_key, std::string &public_key); +}; + +/** + * Base class for server side cryptographic authentication handlers. + */ +class NetworkAuthenticationServerHandler : public NetworkAuthenticationHandler { +public: + /** The processing result of receiving a response. */ + enum ResponseResult { + AUTHENTICATED, ///< The client was authenticated successfully. + NOT_AUTHENTICATED, ///< All authentications for this handler have been exhausted. + RETRY_NEXT_METHOD, ///< The client failed to authenticate, but there is another method to try. + }; + + /** + * Create the request to send to the client. + * @param p The packet to write the request to. + */ + virtual void SendRequest(struct Packet &p) = 0; + + /** + * Read the response from the client. + * @param p The packet to read the response from. + * @return The \c ResponseResult describing the result. + */ + virtual ResponseResult ReceiveResponse(struct Packet &p) = 0; + + /** + * Checks whether this handler can be used with the current configuration. + * For example when there is no password, the handler cannot be used. + * @return True when this handler can be used. + */ + virtual bool CanBeUsed() const = 0; + + /** + * Get the public key the peer provided during the authentication. + * @return The hexadecimal string representation of the peer's public key. + */ + virtual std::string GetPeerPublicKey() const = 0; + + static std::unique_ptr Create(const NetworkAuthenticationPasswordProvider *password_provider, const NetworkAuthenticationAuthorizedKeyHandler *authorized_key_handler, NetworkAuthenticationMethodMask client_supported_method_mask = ~static_cast(0)); +}; + +#endif /* NETWORK_CRYPTO_H */ diff --git a/src/network/network_crypto_internal.h b/src/network/network_crypto_internal.h new file mode 100644 index 0000000000..a073e0931b --- /dev/null +++ b/src/network/network_crypto_internal.h @@ -0,0 +1,341 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file network_crypto_internal.h Internal bits to the crypto of the network handling. */ + +#ifndef NETWORK_CRYPTO_INTERNAL_H +#define NETWORK_CRYPTO_INTERNAL_H + +#include "network_crypto.h" + +/** The number of bytes the public and secret keys are in X25519. */ +constexpr size_t X25519_KEY_SIZE = 32; +/** The number of bytes the nonces are in X25519. */ +constexpr size_t X25519_NONCE_SIZE = 24; +/** The number of bytes the message authentication codes are in X25519. */ +constexpr size_t X25519_MAC_SIZE = 16; +/** The number of bytes the (random) payload of the authentication message has. */ +constexpr size_t X25519_KEY_EXCHANGE_MESSAGE_SIZE = 8; + +/** Container for a X25519 key that is automatically crypto-wiped when destructed. */ +struct X25519Key : std::array { + ~X25519Key(); +}; + +/** Container for a X25519 public key. */ +struct X25519PublicKey : X25519Key { +}; + +/** Container for a X25519 secret key. */ +struct X25519SecretKey : X25519Key { + static X25519SecretKey CreateRandom(); + X25519PublicKey CreatePublicKey() const; +}; + +/** Container for a X25519 nonce that is automatically crypto-wiped when destructed. */ +struct X25519Nonce : std::array { + static X25519Nonce CreateRandom(); + ~X25519Nonce(); +}; + +/** Container for a X25519 message authentication code. */ +using X25519Mac = std::array; + +/** Container for a X25519 key exchange message. */ +using X25519KeyExchangeMessage = std::array; + +/** The side of the key exchange. */ +enum class X25519KeyExchangeSide { + CLIENT, ///< We are the client. + SERVER, ///< We are the server. +}; + +/** + * Container for the keys that derived from the X25519 key exchange mechanism. This mechanism derives + * a key to encrypt both the client-to-server and a key to encrypt server-to-client communication. + */ +class X25519DerivedKeys { +private: + /** Single contiguous buffer to store the derived keys in, as they are generated as a single hash. */ + std::array keys; +public: + ~X25519DerivedKeys(); + std::span ClientToServer() const; + std::span ServerToClient() const; + bool Exchange(const X25519PublicKey &peer_public_key, X25519KeyExchangeSide side, + const X25519SecretKey &our_secret_key, const X25519PublicKey &our_public_key, std::string_view extra_payload); +}; + +/** + * Base for handlers using a X25519 key exchange to perform authentication. + * + * In general this works as follows: + * 1) the client and server have or generate a secret and public X25519 key. + * 2) the X25519 key exchange is performed at both the client and server, with their own secret key and their peer's public key. + * 3) a pair of derived keys is created by BLAKE2b-hashing the following into 64 bytes, in this particular order: + * - the shared secret from the key exchange; + * - the public key of the server; + * - the public key of the client; + * - optional extra payload, e.g. a password in the case of PAKE. + * The first of the pair of derived keys is usually used to encrypt client-to-server communication, and the second of the pair + * is usually used to encrypt server-to-client communication. + * 4) a XChaCha20-Poly1305 (authenticated) encryption is performed using: + * - the first of the pair of derived keys as encryption key; + * - a 24 byte nonce; + * - the public key of the client as additional authenticated data. + * - a 8 byte random number as content/message. + * + * The server initiates the request by sending its public key and a 24 byte nonce that is randomly generated. Normally the side + * that sends the encrypted data sends the nonce in their packet, which would be the client on our case. However, there are + * many implementations of clients due to the admin-protocol where this is used, and we cannot guarantee that they generate a + * good enough nonce. As such the server sends one instead. The server will create a new set of keys for each session. + * + * The client receives the request, performs the key exchange, generates the derived keys and then encrypts the message. This + * message must contain some content, so it has to be filled with 8 random bytes. Once the message has been encrypted, the + * client sends their public key, the encrypted message and the message authentication code (MAC) to the server in a response. + * + * The server receives the response, performs the key exchange, generates the derived keys, decrypts the message and validates the + * message authentication code, and finally the message. It is up to the sub class to perform the final authentication checks. + */ +class X25519AuthenticationHandler { +private: + X25519SecretKey our_secret_key; ///< The secret key used by us. + X25519PublicKey our_public_key; ///< The public key used by us. + X25519Nonce nonce; ///< The nonce to prevent replay attacks. + X25519DerivedKeys derived_keys; ///< Keys derived from the authentication process. + X25519PublicKey peer_public_key; ///< The public key used by our peer. + +protected: + X25519AuthenticationHandler(const X25519SecretKey &secret_key); + + void SendRequest(struct Packet &p); + bool ReceiveRequest(struct Packet &p); + bool SendResponse(struct Packet &p, std::string_view derived_key_extra_payload); + NetworkAuthenticationServerHandler::ResponseResult ReceiveResponse(struct Packet &p, std::string_view derived_key_extra_payload); + + std::string GetPeerPublicKey() const; + + std::unique_ptr CreateClientToServerEncryptionHandler() const; + std::unique_ptr CreateServerToClientEncryptionHandler() const; +}; + +/** + * Client side handler for using X25519 without actual authentication. + * + * This follows the method described in \c X25519AuthenticationHandler, without an extra payload. + */ +class X25519KeyExchangeOnlyClientHandler : protected X25519AuthenticationHandler, public NetworkAuthenticationClientHandler { +public: + /** + * Create the handler that that one does the key exchange. + * @param secret_key The secret key to initialize this handler with. + */ + X25519KeyExchangeOnlyClientHandler(const X25519SecretKey &secret_key) : X25519AuthenticationHandler(secret_key) {} + + virtual RequestResult ReceiveRequest(struct Packet &p) override { return this->X25519AuthenticationHandler::ReceiveRequest(p) ? READY_FOR_RESPONSE : INVALID; } + virtual bool SendResponse(struct Packet &p) override { return this->X25519AuthenticationHandler::SendResponse(p, {}); } + + virtual std::string_view GetName() const override { return "X25519-KeyExchangeOnly-client"; } + virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override { return NETWORK_AUTH_METHOD_X25519_KEY_EXCHANGE_ONLY; } + + virtual std::unique_ptr CreateClientToServerEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateClientToServerEncryptionHandler(); } + virtual std::unique_ptr CreateServerToClientEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateServerToClientEncryptionHandler(); } +}; + +/** + * Server side handler for using X25519 without actual authentication. + * + * This follows the method described in \c X25519AuthenticationHandler, without an extra payload. + */ +class X25519KeyExchangeOnlyServerHandler : protected X25519AuthenticationHandler, public NetworkAuthenticationServerHandler { +public: + /** + * Create the handler that that one does the key exchange. + * @param secret_key The secret key to initialize this handler with. + */ + X25519KeyExchangeOnlyServerHandler(const X25519SecretKey &secret_key) : X25519AuthenticationHandler(secret_key) {} + + virtual void SendRequest(struct Packet &p) override { this->X25519AuthenticationHandler::SendRequest(p); } + virtual ResponseResult ReceiveResponse(struct Packet &p) override { return this->X25519AuthenticationHandler::ReceiveResponse(p, {}); } + + virtual std::string_view GetName() const override { return "X25519-KeyExchangeOnly-server"; } + virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override { return NETWORK_AUTH_METHOD_X25519_KEY_EXCHANGE_ONLY; } + virtual bool CanBeUsed() const override { return true; } + + virtual std::string GetPeerPublicKey() const override { return this->X25519AuthenticationHandler::GetPeerPublicKey(); } + virtual std::unique_ptr CreateClientToServerEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateClientToServerEncryptionHandler(); } + virtual std::unique_ptr CreateServerToClientEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateServerToClientEncryptionHandler(); } +}; + +/** + * Client side handler for using X25519 with a password-authenticated key exchange. + * + * This follows the method described in \c X25519AuthenticationHandler, were the password is the extra payload. + */ +class X25519PAKEClientHandler : protected X25519AuthenticationHandler, public NetworkAuthenticationClientHandler { +private: + std::shared_ptr handler; + +public: + /** + * Create the handler with the given password handler. + * @param secret_key The secret key to initialize this handler with. + * @param handler The handler requesting the password from the user, if required. + */ + X25519PAKEClientHandler(const X25519SecretKey &secret_key, std::shared_ptr handler) : X25519AuthenticationHandler(secret_key), handler(handler) {} + + virtual RequestResult ReceiveRequest(struct Packet &p) override; + virtual bool SendResponse(struct Packet &p) override { return this->X25519AuthenticationHandler::SendResponse(p, this->handler->password); } + + virtual std::string_view GetName() const override { return "X25519-PAKE-client"; } + virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override { return NETWORK_AUTH_METHOD_X25519_PAKE; } + + virtual std::unique_ptr CreateClientToServerEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateClientToServerEncryptionHandler(); } + virtual std::unique_ptr CreateServerToClientEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateServerToClientEncryptionHandler(); } +}; + +/** + * Server side handler for using X25519 with a password-authenticated key exchange. + * + * This follows the method described in \c X25519AuthenticationHandler, were the password is the extra payload. + */ +class X25519PAKEServerHandler : protected X25519AuthenticationHandler, public NetworkAuthenticationServerHandler { +private: + const NetworkAuthenticationPasswordProvider *password_provider; ///< The password to check against. +public: + /** + * Create the handler with the given password provider. + * @param secret_key The secret key to initialize this handler with. + * @param password_provider The provider for the passwords. + */ + X25519PAKEServerHandler(const X25519SecretKey &secret_key, const NetworkAuthenticationPasswordProvider *password_provider) : X25519AuthenticationHandler(secret_key), password_provider(password_provider) {} + + virtual void SendRequest(struct Packet &p) override { this->X25519AuthenticationHandler::SendRequest(p); } + virtual ResponseResult ReceiveResponse(struct Packet &p) override { return this->X25519AuthenticationHandler::ReceiveResponse(p, this->password_provider->GetPassword()); } + + virtual std::string_view GetName() const override { return "X25519-PAKE-server"; } + virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override { return NETWORK_AUTH_METHOD_X25519_PAKE; } + virtual bool CanBeUsed() const override { return !this->password_provider->GetPassword().empty(); } + + virtual std::string GetPeerPublicKey() const override { return this->X25519AuthenticationHandler::GetPeerPublicKey(); } + virtual std::unique_ptr CreateClientToServerEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateClientToServerEncryptionHandler(); } + virtual std::unique_ptr CreateServerToClientEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateServerToClientEncryptionHandler(); } +}; + + +/** + * Handler for clients using a X25519 key exchange to perform authentication via a set of authorized (public) keys of clients. + * + * This follows the method described in \c X25519AuthenticationHandler. Once all these checks have succeeded, it will + * check whether the public key of the client is in the list of authorized keys to login. + */ +class X25519AuthorizedKeyClientHandler : protected X25519AuthenticationHandler, public NetworkAuthenticationClientHandler { +public: + /** + * Create the handler that uses the given password to check against. + * @param secret_key The secret key to initialize this handler with. + */ + X25519AuthorizedKeyClientHandler(const X25519SecretKey &secret_key) : X25519AuthenticationHandler(secret_key) {} + + virtual RequestResult ReceiveRequest(struct Packet &p) override { return this->X25519AuthenticationHandler::ReceiveRequest(p) ? READY_FOR_RESPONSE : INVALID; } + virtual bool SendResponse(struct Packet &p) override { return this->X25519AuthenticationHandler::SendResponse(p, {}); } + + virtual std::string_view GetName() const override { return "X25519-AuthorizedKey-client"; } + virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override { return NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY; } + + virtual std::unique_ptr CreateClientToServerEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateClientToServerEncryptionHandler(); } + virtual std::unique_ptr CreateServerToClientEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateServerToClientEncryptionHandler(); } + + static X25519SecretKey GetValidSecretKeyAndUpdatePublicKey(std::string &secret_key, std::string &public_key); +}; + +/** + * Handler for servers using a X25519 key exchange to perform authentication via a set of authorized (public) keys of clients. + * + * This follows the method described in \c X25519AuthenticationHandler. Once all these checks have succeeded, it will + * check whether the public key of the client is in the list of authorized keys to login. + */ +class X25519AuthorizedKeyServerHandler : protected X25519AuthenticationHandler, public NetworkAuthenticationServerHandler { +private: + const NetworkAuthenticationAuthorizedKeyHandler *authorized_key_handler; ///< The handler of the authorized keys. +public: + /** + * Create the handler that uses the given authorized keys to check against. + * @param secret_key The secret key to initialize this handler with. + * @param authorized_key_handler The handler of the authorized keys. + */ + X25519AuthorizedKeyServerHandler(const X25519SecretKey &secret_key, const NetworkAuthenticationAuthorizedKeyHandler *authorized_key_handler) : X25519AuthenticationHandler(secret_key), authorized_key_handler(authorized_key_handler) {} + + virtual void SendRequest(struct Packet &p) override { this->X25519AuthenticationHandler::SendRequest(p); } + virtual ResponseResult ReceiveResponse(struct Packet &p) override; + + virtual std::string_view GetName() const override { return "X25519-AuthorizedKey-server"; } + virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override { return NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY; } + virtual bool CanBeUsed() const override { return this->authorized_key_handler->CanBeUsed(); } + + virtual std::string GetPeerPublicKey() const override { return this->X25519AuthenticationHandler::GetPeerPublicKey(); } + virtual std::unique_ptr CreateClientToServerEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateClientToServerEncryptionHandler(); } + virtual std::unique_ptr CreateServerToClientEncryptionHandler() const override { return this->X25519AuthenticationHandler::CreateServerToClientEncryptionHandler(); } +}; + + +/** + * Handler for combining a number of authentication handlers, where the failure of one of the handlers will retry with + * another handler. For example when authorized keys fail, it can still fall back to a password. + */ +class CombinedAuthenticationClientHandler : public NetworkAuthenticationClientHandler { +public: + using Handler = std::unique_ptr; ///< The type of the inner handlers. + +private: + std::vector handlers; ///< The handlers that we can authenticate with. + NetworkAuthenticationClientHandler *current_handler = nullptr; ///< The currently active handler. + +public: + /** + * Add the given sub-handler to this handler. + * @param handler The handler to add. + */ + void Add(Handler &&handler) { this->handlers.push_back(std::move(handler)); } + + virtual RequestResult ReceiveRequest(struct Packet &p) override; + virtual bool SendResponse(struct Packet &p) override; + + virtual std::string_view GetName() const override; + virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override; + + virtual std::unique_ptr CreateClientToServerEncryptionHandler() const override { return this->current_handler->CreateClientToServerEncryptionHandler(); } + virtual std::unique_ptr CreateServerToClientEncryptionHandler() const override { return this->current_handler->CreateServerToClientEncryptionHandler(); } +}; + +/** + * Handler for combining a number of authentication handlers, where the failure of one of the handlers will retry with + * another handler. For example when authorized keys fail, it can still fall back to a password. + */ +class CombinedAuthenticationServerHandler : public NetworkAuthenticationServerHandler { +public: + using Handler = std::unique_ptr; ///< The type of the inner handlers. + +private: + std::vector handlers; ///< The handlers that we can (still) authenticate with. + +public: + void Add(Handler &&handler); + + virtual void SendRequest(struct Packet &p) override; + virtual ResponseResult ReceiveResponse(struct Packet &p) override; + + virtual std::string_view GetName() const override; + virtual NetworkAuthenticationMethod GetAuthenticationMethod() const override; + virtual bool CanBeUsed() const override; + + virtual std::string GetPeerPublicKey() const override { return this->handlers.back()->GetPeerPublicKey(); } + virtual std::unique_ptr CreateClientToServerEncryptionHandler() const override { return this->handlers.back()->CreateClientToServerEncryptionHandler(); } + virtual std::unique_ptr CreateServerToClientEncryptionHandler() const override { return this->handlers.back()->CreateServerToClientEncryptionHandler(); } +}; + +#endif /* NETWORK_CRYPTO_INTERNAL_H */ diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 53884be7d7..475174379d 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -9,6 +9,7 @@ add_test_files( string_func.cpp strings_func.cpp test_main.cpp + test_network_crypto.cpp test_script_admin.cpp test_window_desc.cpp ) diff --git a/src/tests/test_network_crypto.cpp b/src/tests/test_network_crypto.cpp new file mode 100644 index 0000000000..0438a6ca65 --- /dev/null +++ b/src/tests/test_network_crypto.cpp @@ -0,0 +1,199 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file test_network_crypto.cpp Tests for network related crypto functions. */ + +#include "../stdafx.h" + +#include "../3rdparty/catch2/catch.hpp" + +#include "../core/format.hpp" +#include "../network/network_crypto_internal.h" +#include "../network/core/packet.h" +#include "../string_func.h" + +class MockNetworkSocketHandler : public NetworkSocketHandler { +}; + +static MockNetworkSocketHandler mock_socket_handler; + +static Packet CreatePacketForReading(Packet &source) +{ + source.PrepareToSend(); + + Packet dest(&mock_socket_handler, COMPAT_MTU, source.Size()); + + auto transfer_in = [](Packet &source, char *dest_data, size_t length) { + auto transfer_out = [](char *dest_data, const char *source_data, size_t length) { + std::copy(source_data, source_data + length, dest_data); + return length; + }; + return source.TransferOutWithLimit(transfer_out, length, dest_data); + }; + dest.TransferIn(transfer_in, source); + + dest.PrepareToRead(); + dest.Recv_uint8(); // Ignore the type + return dest; +} + +class TestPasswordRequestHandler : public NetworkAuthenticationPasswordRequestHandler { +private: + std::string password; +public: + TestPasswordRequestHandler(std::string &password) : password(password) {} + void SendResponse() override {} + void AskUserForPassword(std::shared_ptr request) override { request->Reply(this->password); } +}; + +static void TestAuthentication(NetworkAuthenticationServerHandler &server, NetworkAuthenticationClientHandler &client, + NetworkAuthenticationServerHandler::ResponseResult expected_response_result, + NetworkAuthenticationClientHandler::RequestResult expected_request_result) +{ + Packet request(&mock_socket_handler, PacketType{}); + server.SendRequest(request); + + request = CreatePacketForReading(request); + CHECK(client.ReceiveRequest(request) == expected_request_result); + + Packet response(&mock_socket_handler, PacketType{}); + client.SendResponse(response); + + response = CreatePacketForReading(response); + CHECK(server.ReceiveResponse(response) == expected_response_result); +} + + +TEST_CASE("Authentication_KeyExchangeOnly") +{ + X25519KeyExchangeOnlyServerHandler server(X25519SecretKey::CreateRandom()); + X25519KeyExchangeOnlyClientHandler client(X25519SecretKey::CreateRandom()); + + TestAuthentication(server, client, NetworkAuthenticationServerHandler::AUTHENTICATED, NetworkAuthenticationClientHandler::READY_FOR_RESPONSE); +} + + +static void TestAuthenticationPAKE(std::string server_password, std::string client_password, + NetworkAuthenticationServerHandler::ResponseResult expected_response_result) +{ + NetworkAuthenticationDefaultPasswordProvider server_password_provider(server_password); + X25519PAKEServerHandler server(X25519SecretKey::CreateRandom(), &server_password_provider); + X25519PAKEClientHandler client(X25519SecretKey::CreateRandom(), std::make_shared(client_password)); + + TestAuthentication(server, client, expected_response_result, NetworkAuthenticationClientHandler::AWAIT_USER_INPUT); +} + +TEST_CASE("Authentication_PAKE") +{ + SECTION("Correct password") { + TestAuthenticationPAKE("sikrit", "sikrit", NetworkAuthenticationServerHandler::AUTHENTICATED); + } + + SECTION("Empty password") { + TestAuthenticationPAKE("", "", NetworkAuthenticationServerHandler::AUTHENTICATED); + } + + SECTION("Wrong password") { + TestAuthenticationPAKE("sikrit", "secret", NetworkAuthenticationServerHandler::NOT_AUTHENTICATED); + } +} + + +static void TestAuthenticationAuthorizedKey(const X25519SecretKey &client_secret_key, const X25519PublicKey &server_expected_public_key, + NetworkAuthenticationServerHandler::ResponseResult expected_response_result) +{ + std::vector authorized_keys; + authorized_keys.emplace_back(FormatArrayAsHex(server_expected_public_key)); + + NetworkAuthenticationDefaultAuthorizedKeyHandler authorized_key_handler(authorized_keys); + X25519AuthorizedKeyServerHandler server(X25519SecretKey::CreateRandom(), &authorized_key_handler); + X25519AuthorizedKeyClientHandler client(client_secret_key); + + TestAuthentication(server, client, expected_response_result, NetworkAuthenticationClientHandler::READY_FOR_RESPONSE); +} + +TEST_CASE("Authentication_AuthorizedKey") +{ + auto client_secret_key = X25519SecretKey::CreateRandom(); + auto valid_client_public_key = client_secret_key.CreatePublicKey(); + auto invalid_client_public_key = X25519SecretKey::CreateRandom().CreatePublicKey(); + + SECTION("Correct public key") { + TestAuthenticationAuthorizedKey(client_secret_key, valid_client_public_key, NetworkAuthenticationServerHandler::AUTHENTICATED); + } + + SECTION("Incorrect public key") { + TestAuthenticationAuthorizedKey(client_secret_key, invalid_client_public_key, NetworkAuthenticationServerHandler::NOT_AUTHENTICATED); + } +} + + +TEST_CASE("Authentication_Combined") +{ + auto client_secret_key = X25519SecretKey::CreateRandom(); + std::string client_secret_key_str = FormatArrayAsHex(client_secret_key); + auto client_public_key = client_secret_key.CreatePublicKey(); + std::string client_public_key_str = FormatArrayAsHex(client_public_key); + + std::vector valid_authorized_keys; + valid_authorized_keys.emplace_back(client_public_key_str); + NetworkAuthenticationDefaultAuthorizedKeyHandler valid_authorized_key_handler(valid_authorized_keys); + + std::vector invalid_authorized_keys; + invalid_authorized_keys.emplace_back("not-a-valid-authorized-key"); + NetworkAuthenticationDefaultAuthorizedKeyHandler invalid_authorized_key_handler(invalid_authorized_keys); + + std::vector no_authorized_keys; + NetworkAuthenticationDefaultAuthorizedKeyHandler no_authorized_key_handler(no_authorized_keys); + + std::string no_password = ""; + NetworkAuthenticationDefaultPasswordProvider no_password_provider(no_password); + std::string valid_password = "sikrit"; + NetworkAuthenticationDefaultPasswordProvider valid_password_provider(valid_password); + std::string invalid_password = "secret"; + NetworkAuthenticationDefaultPasswordProvider invalid_password_provider(invalid_password); + + auto client = NetworkAuthenticationClientHandler::Create(std::make_shared(valid_password), client_secret_key_str, client_public_key_str); + + SECTION("Invalid authorized keys, invalid password") { + auto server = NetworkAuthenticationServerHandler::Create(&invalid_password_provider, &invalid_authorized_key_handler); + + TestAuthentication(*server, *client, NetworkAuthenticationServerHandler::RETRY_NEXT_METHOD, NetworkAuthenticationClientHandler::READY_FOR_RESPONSE); + TestAuthentication(*server, *client, NetworkAuthenticationServerHandler::NOT_AUTHENTICATED, NetworkAuthenticationClientHandler::AWAIT_USER_INPUT); + } + + SECTION("Invalid authorized keys, valid password") { + auto server = NetworkAuthenticationServerHandler::Create(&valid_password_provider, &invalid_authorized_key_handler); + + TestAuthentication(*server, *client, NetworkAuthenticationServerHandler::RETRY_NEXT_METHOD, NetworkAuthenticationClientHandler::READY_FOR_RESPONSE); + TestAuthentication(*server, *client, NetworkAuthenticationServerHandler::AUTHENTICATED, NetworkAuthenticationClientHandler::AWAIT_USER_INPUT); + } + + SECTION("Valid authorized keys, valid password") { + auto server = NetworkAuthenticationServerHandler::Create(&valid_password_provider, &valid_authorized_key_handler); + + TestAuthentication(*server, *client, NetworkAuthenticationServerHandler::AUTHENTICATED, NetworkAuthenticationClientHandler::READY_FOR_RESPONSE); + } + + SECTION("No authorized keys, invalid password") { + auto server = NetworkAuthenticationServerHandler::Create(&invalid_password_provider, &no_authorized_key_handler); + + TestAuthentication(*server, *client, NetworkAuthenticationServerHandler::NOT_AUTHENTICATED, NetworkAuthenticationClientHandler::AWAIT_USER_INPUT); + } + + SECTION("No authorized keys, valid password") { + auto server = NetworkAuthenticationServerHandler::Create(&valid_password_provider, &no_authorized_key_handler); + + TestAuthentication(*server, *client, NetworkAuthenticationServerHandler::AUTHENTICATED, NetworkAuthenticationClientHandler::AWAIT_USER_INPUT); + } + + SECTION("No authorized keys, no password") { + auto server = NetworkAuthenticationServerHandler::Create(&no_password_provider, &no_authorized_key_handler); + + TestAuthentication(*server, *client, NetworkAuthenticationServerHandler::AUTHENTICATED, NetworkAuthenticationClientHandler::READY_FOR_RESPONSE); + } +}