Teaspeak-Server/server/src/client/voice/CryptSetupHandler.cpp

261 lines
11 KiB
C++

//
// Created by WolverinDEV on 29/07/2020.
//
#include <log/LogUtils.h>
#include <misc/base64.h>
#include <misc/digest.h>
#include <src/InstanceHandler.h>
#include <misc/endianness.h>
#include "CryptSetupHandler.h"
#include "./VoiceClientConnection.h"
using namespace ts;
using namespace ts::connection;
using namespace ts::server::server::udp;
inline void generate_random(uint8_t *destination, size_t length) {
while(length-- > 0) {
*(destination++) = (uint8_t) rand();
}
}
CryptSetupHandler::CryptSetupHandler(VoiceClientConnection *connection) : connection{connection} { }
CryptSetupHandler::CommandHandleResult CryptSetupHandler::handle_command(const std::string_view &payload) {
std::variant<ts::command_result, CommandHandleResult>(CryptSetupHandler::*command_handler)(const ts::command_parser&) = nullptr;
if(payload.starts_with("clientinitiv ")) {
command_handler = &CryptSetupHandler::handleCommandClientInitIv;
} else if(payload.starts_with("clientek ")) {
command_handler = &CryptSetupHandler::handleCommandClientEk;
} else if(payload.starts_with("clientinit ")) {
command_handler = &CryptSetupHandler::handleCommandClientInit;
}
if(!command_handler) {
return CommandHandleResult::PASS_THROUGH;
}
this->last_command_ = std::chrono::system_clock::now();
ts::command_parser parser{payload};
try {
std::unique_lock cmd_lock{this->command_lock};
auto result = (this->*command_handler)(parser);
CommandHandleResult handle_result;
if(std::holds_alternative<CommandHandleResult>(result)) {
handle_result = std::get<CommandHandleResult>(result);
} else {
auto cmd_result = std::move(std::get<ts::command_result>(result));
ts::command_builder notify{"error"};
cmd_result.build_error_response(notify, "id");
if(parser.has_key("return_code")) {
notify.put_unchecked(0, "return_code", parser.value("return_code"));
}
this->connection->send_command(notify.build(), false, nullptr);
handle_result = cmd_result.has_error() ? CommandHandleResult::CLOSE_CONNECTION : CommandHandleResult::CONSUME_COMMAND;
cmd_result.release_data();
}
return handle_result;
} catch (std::exception& ex) {
debugMessage(this->connection->virtual_server_id(), "{} Failed to handle connection command: {}. Closing connection.", this->connection->log_prefix(), ex.what());
return CommandHandleResult::CLOSE_CONNECTION;
}
}
CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(const ts::command_parser &cmd) {
auto client = this->connection->getCurrentClient();
assert(client);
{
std::lock_guard state_lock{client->state_lock};
switch(client->state) {
case ConnectionState::INIT_LOW:
client->state = ConnectionState::INIT_HIGH;
break;
case ConnectionState::INIT_HIGH:
logTrace(client->getServerId(), "{} Received a duplicated initiv. It seems like our initivexpand2 hasn't yet reached the client. The acknowledge handler should handle this issue for us.", CLIENT_STR_LOG_PREFIX_(client));
return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */
case ConnectionState::CONNECTED:
case ConnectionState::DISCONNECTING:
case ConnectionState::DISCONNECTED:
/* That's really odd an should not happen */
return CommandHandleResult::PASS_THROUGH;
case ConnectionState::UNKNWON:
default:
assert(false);
return CommandHandleResult::PASS_THROUGH;
}
}
this->connection->reset();
this->connection->packet_decoder().register_initiv_packet();
this->connection->packet_statistics().reset_offsets();
bool use_teaspeak = cmd.has_switch("teaspeak");
if(!use_teaspeak && !config::server::clients::teamspeak) {
return command_result{error::client_type_is_not_allowed, config::server::clients::teamspeak_not_allowed_message};
}
if(use_teaspeak) {
debugMessage(this->connection->virtual_server_id(), "{} Client using TeaSpeak.", this->connection->log_prefix());
client->properties()[property::CLIENT_TYPE_EXACT] = ClientType::CLIENT_TEASPEAK;
}
this->seed_client = base64::decode(cmd.value("alpha"));
if(this->seed_client.length() != 10) {
return ts::command_result{error::parameter_invalid, "alpha"};
}
std::string clientOmega = base64::decode(cmd.value("omega")); //The identity public key
std::string ip = cmd.value("ip");
bool ot = cmd.has_key("ot") && cmd.value_as<bool>("ot");
{
auto remote_key_{new ecc_key{}};
auto state = ecc_import((const unsigned char *) clientOmega.data(), clientOmega.length(), remote_key_);
if(state != CRYPT_OK) {
delete remote_key_;
return ts::command_result{error::client_could_not_validate_identity};
}
this->remote_key = std::shared_ptr<ecc_key>{remote_key_, [](ecc_key* key) {
if(!key) {
return;
}
/* We can only call ecc_free if we've really initialized the remote key */
ecc_free(key);
delete key;
}};
client->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(cmd.value("omega")));
}
this->new_protocol = !use_teaspeak && ot && config::experimental_31 && (this->client_protocol_time_ >= 173265950ULL || this->client_protocol_time_ == (uint32_t) 5680278000ULL);
this->seed_server.resize(this->new_protocol ? 54 : 10);
for(auto& byte : this->seed_server) {
byte = (uint8_t) rand();
}
auto server_public_key = client->getServer()->publicServerKey();
if(server_public_key.empty()) {
return ts::command_result{error::vs_critical, "failed to export server public key"};
}
if(this->new_protocol) {
//Pre setup
//Generate chain
debugMessage(this->connection->virtual_server_id(), "{} Got client 3.1 protocol with build timestamp {}", this->connection->log_prefix(), this->client_protocol_time_);
this->chain_data = serverInstance->getTeamSpeakLicense()->license();
this->chain_data->chain->addEphemeralEntry();
auto crypto_chain = this->chain_data->chain->exportChain();
auto crypto_chain_hash = digest::sha256(crypto_chain);
size_t sign_buffer_size{512};
char sign_buffer[sign_buffer_size];
prng_state prng_state{};
memset(&prng_state, 0, sizeof(prng_state));
auto sign_result = ecc_sign_hash(
(u_char*) crypto_chain_hash.data(), crypto_chain_hash.length(),
(u_char*) sign_buffer, &sign_buffer_size,
&prng_state, find_prng("sprng"),
client->getServer()->serverKey());
if(sign_result != CRYPT_OK) {
return ts::command_result{error::vs_critical, "failed to sign crypto chain"};
}
ts::command_builder answer{"initivexpand2"};
answer.put_unchecked(0, "time", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
answer.put_unchecked(0, "l", base64::encode(crypto_chain));
answer.put_unchecked(0, "beta", base64::encode(this->seed_server));
answer.put_unchecked(0, "omega", server_public_key);
answer.put_unchecked(0, "proof", base64::encode((const char*) sign_buffer, sign_buffer_size));
answer.put_unchecked(0, "tvd", "");
answer.put_unchecked(0, "root", base64::encode((char*) this->chain_data->public_key, 32));
answer.put_unchecked(0, "ot", "1");
this->connection->send_command(answer.build(), false, nullptr);
client->handshake.state = SpeakingClient::HandshakeState::SUCCEEDED; /* we're doing the verify via TeamSpeak */
return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */
} else {
debugMessage(this->connection->virtual_server_id(), "{} Got non client 3.1 protocol with build timestamp {}", this->connection->log_prefix(), this->client_protocol_time_, this->client_protocol_time_);
{
ts::command_builder answer{"initivexpand"};
answer.put_unchecked(0, "alpha", base64::encode(this->seed_client));
answer.put_unchecked(0, "beta", base64::encode(this->seed_server));
answer.put_unchecked(0, "omega", server_public_key);
if(use_teaspeak) {
answer.put_unchecked(0, "teaspeak", "1");
client->handshake.state = SpeakingClient::HandshakeState::BEGIN; /* we need to start the handshake */
} else {
client->handshake.state = SpeakingClient::HandshakeState::SUCCEEDED; /* we're using the provided identity as identity */
}
this->connection->send_command(answer.build(), false, nullptr);
this->connection->packet_encoder().encrypt_pending_packets();
}
std::string error;
if(!this->connection->getCryptHandler()->setupSharedSecret(this->seed_client, this->seed_server, &*this->remote_key, client->getServer()->serverKey(), error)){
logError(this->connection->virtual_server_id(), "{} Failed to calculate shared secret {}. Dropping client.",
this->connection->log_prefix(), error);
return ts::command_result{error::vs_critical};
}
return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */
}
}
CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientEk(const ts::command_parser &cmd) {
debugMessage(this->connection->virtual_server_id(), "{} Got client ek!", this->connection->log_prefix());
if(!this->chain_data || !this->chain_data->chain) {
return ts::command_result{error::vs_critical, "missing chain data"};
}
auto client_key = base64::decode(cmd.value("ek"));
auto private_key = this->chain_data->chain->generatePrivateKey(this->chain_data->root_key, this->chain_data->root_index);
this->connection->getCryptHandler()->setupSharedSecretNew(this->seed_client, this->seed_server, (char*) private_key.data(), client_key.data());
this->connection->packet_encoder().acknowledge_manager().reset();
{
char buffer[2];
le2be16(1, buffer);
auto pflags = protocol::PacketFlag::NewProtocol;
this->connection->send_packet(protocol::PacketType::ACK, (protocol::PacketFlags) pflags, buffer, 2);
//We cant use the send_packet_acknowledge function since it sends the acknowledge unencrypted
}
return CommandHandleResult::CONSUME_COMMAND; /* we don't want to send an error id=0 msg=ok */
}
CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInit(const ts::command_parser &) {
/* the client must have received everything */
this->connection->packet_encoder().acknowledge_manager().reset();
this->seed_client.clear();
this->seed_server.clear();
this->chain_data = nullptr;
return CommandHandleResult::PASS_THROUGH;
}