mirror of https://github.com/OpenTTD/OpenTTD.git
496 lines
19 KiB
C++
496 lines
19 KiB
C++
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/** @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<uint8_t> 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<const uint8_t> 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<const uint8_t> 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<const uint8_t *>(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<const uint8_t> 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<std::uint8_t> mac, std::span<std::uint8_t> message) override
|
|
{
|
|
return crypto_aead_read(&this->context, message.data(), mac.data(), nullptr, 0, message.data(), message.size()) == 0;
|
|
}
|
|
|
|
void Encrypt(std::span<std::uint8_t> mac, std::span<std::uint8_t> 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()),
|
|
key_exchange_nonce(X25519Nonce::CreateRandom()), encryption_nonce(X25519Nonce::CreateRandom())
|
|
{
|
|
}
|
|
|
|
/* virtual */ void X25519AuthenticationHandler::SendRequest(Packet &p)
|
|
{
|
|
p.Send_bytes(this->our_public_key);
|
|
p.Send_bytes(this->key_exchange_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->key_exchange_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(), this->key_exchange_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);
|
|
}
|
|
|
|
/**
|
|
* Send the initial nonce for the encrypted connection.
|
|
* @param p The packet to send the data in.
|
|
*/
|
|
void X25519AuthenticationHandler::SendEnableEncryption(struct Packet &p) const
|
|
{
|
|
p.Send_bytes(this->encryption_nonce);
|
|
}
|
|
|
|
/**
|
|
* Receive the initial nonce for the encrypted connection.
|
|
* @param p The packet to read the data from.
|
|
* @return \c true when enough bytes could be read for the nonce, otherwise \c false.
|
|
*/
|
|
bool X25519AuthenticationHandler::ReceiveEnableEncryption(struct Packet &p)
|
|
{
|
|
return p.Recv_bytes(this->encryption_nonce) == this->encryption_nonce.size();
|
|
}
|
|
|
|
std::unique_ptr<NetworkEncryptionHandler> X25519AuthenticationHandler::CreateClientToServerEncryptionHandler() const
|
|
{
|
|
return std::make_unique<X25519EncryptionHandler>(this->derived_keys.ClientToServer(), this->encryption_nonce);
|
|
}
|
|
|
|
std::unique_ptr<NetworkEncryptionHandler> X25519AuthenticationHandler::CreateServerToClientEncryptionHandler() const
|
|
{
|
|
return std::make_unique<X25519EncryptionHandler>(this->derived_keys.ServerToClient(), this->encryption_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(), this->key_exchange_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<NetworkAuthenticationMethod>(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();
|
|
}
|
|
|
|
/**
|
|
* Ensures that the given secret key is valid, and when not overwrite it with a valid secret key. Then update the public key to be associated with the secret key.
|
|
* @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 */ void NetworkAuthenticationClientHandler::EnsureValidSecretKeyAndUpdatePublicKey(std::string &secret_key, std::string &public_key)
|
|
{
|
|
X25519AuthorizedKeyClientHandler::GetValidSecretKeyAndUpdatePublicKey(secret_key, public_key);
|
|
}
|
|
|
|
/**
|
|
* 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> NetworkAuthenticationClientHandler::Create(std::shared_ptr<NetworkAuthenticationPasswordRequestHandler> password_handler, std::string &secret_key, std::string &public_key)
|
|
{
|
|
auto secret = X25519AuthorizedKeyClientHandler::GetValidSecretKeyAndUpdatePublicKey(secret_key, public_key);
|
|
auto handler = std::make_unique<CombinedAuthenticationClientHandler>();
|
|
handler->Add(std::make_unique<X25519KeyExchangeOnlyClientHandler>(secret));
|
|
handler->Add(std::make_unique<X25519PAKEClientHandler>(secret, std::move(password_handler)));
|
|
handler->Add(std::make_unique<X25519AuthorizedKeyClientHandler>(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> 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<CombinedAuthenticationServerHandler>();
|
|
if (password_provider != nullptr && HasBit(client_supported_method_mask, NETWORK_AUTH_METHOD_X25519_PAKE)) {
|
|
handler->Add(std::make_unique<X25519PAKEServerHandler>(secret, password_provider));
|
|
}
|
|
|
|
if (authorized_key_handler != nullptr && HasBit(client_supported_method_mask, NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY)) {
|
|
handler->Add(std::make_unique<X25519AuthorizedKeyServerHandler>(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<X25519KeyExchangeOnlyServerHandler>(secret));
|
|
}
|
|
return handler;
|
|
}
|