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

277 lines
13 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::unique_lock state_lock{client->state_lock};
if(client->connectionState() == ConnectionState::CONNECTED) { /* we've a reconnect */
auto lastPingResponse = std::max(this->connection->ping_handler().last_ping_response(), this->connection->ping_handler().last_command_acknowledged());
if(std::chrono::system_clock::now() - lastPingResponse < std::chrono::seconds(5)) {
logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt",
this->connection->log_prefix(),
duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - lastPingResponse).count()
);
return ts::command_result{error::ok};
} else if(!config::voice::allow_session_reinitialize) {
logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect and last ping response is older then 5 seconds ({}). Dropping attempt because its not allowed due to config settings",
this->connection->log_prefix(),
duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - lastPingResponse).count()
);
return ts::command_result{error::ok};
}
logMessage(this->connection->virtual_server_id(), "{} Client initialized reconnect and last ping response is older then 5 seconds ({}). Allowing attempt",
this->connection->log_prefix(),
duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - lastPingResponse).count()
);
state_lock.unlock();
{
std::unique_lock server_channel_lock(client->server->get_channel_tree_lock()); /* we cant get moved if this is locked! */
if(client->currentChannel)
client->server->client_move(client->ref(), nullptr, nullptr, config::messages::timeout::connection_reinitialized, ViewReasonId::VREASON_TIMEOUT, false, server_channel_lock);
}
client->finalDisconnect();
state_lock.lock();
} else if(client->state >= ConnectionState::DISCONNECTING) {
state_lock.unlock();
std::shared_lock disconnect_finish{client->finalDisconnectLock}; /* await until the last disconnect has been processed */
state_lock.lock();
client->state = ConnectionState::INIT_HIGH;
} else if(client->state == 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 */
} else {
client->state = ConnectionState::INIT_HIGH;
}
state_lock.unlock();
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;
}
/* normal TeamSpeak handling */
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");
{
this->remote_key = std::shared_ptr<ecc_key>(new ecc_key{}, [](ecc_key* key){
if(!key) return;
ecc_free(key);
delete key;
});
auto state = ecc_import((const unsigned char *) clientOmega.data(), clientOmega.length(), &*this->remote_key);
if(state != CRYPT_OK) {
this->remote_key = nullptr;
return ts::command_result{error::client_could_not_validate_identity};
}
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);
{
size_t server_seed_length = this->new_protocol ? 54 : 10;
char beta[server_seed_length];
generate_random((uint8_t *) beta, server_seed_length);
this->seed_server = std::string{beta, server_seed_length};
}
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{128};
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::PacketFlag::PacketFlag) pflags, buffer, 2);
//Send the encrypted acknowledge (most the times the second packet; If not we're going into the resend loop)
//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;
}