Teaspeak-Server/server/src/client/voice/VoiceClientHandschake.cpp
2020-01-26 14:21:34 +01:00

186 lines
8.9 KiB
C++

#include <tommath.h>
#include <tomcrypt.h>
#include <misc/endianness.h>
#include <misc/digest.h>
#include <misc/base64.h>
#include <openssl/sha.h>
#include <log/LogUtils.h>
#include <src/client/SpeakingClient.h>
#include "VoiceClient.h"
#include "../../InstanceHandler.h"
#include "../../server/VoiceServer.h"
using namespace std;
using namespace std::chrono;
using namespace ts::server;
using namespace ts::protocol;
using namespace ts::connection;
inline void generate_random(uint8_t *destination, size_t length) {
while(length-- > 0)
*(destination++) = (uint8_t) rand();
}
ts::command_result VoiceClient::handleCommandClientInitIv(Command& command) {
this->last_packet_handshake = system_clock::now();
if(this->state == ConnectionState::CONNECTED) { /* we've a reconnect */
if(system_clock::now() - this->lastPingResponse < seconds(5)) {
logMessage(this->getServerId(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt",
CLIENT_STR_LOG_PREFIX,
duration_cast<milliseconds>(system_clock::now() - this->lastPingResponse).count()
);
return ts::command_result{error::ok};
} else if(!config::voice::allow_session_reinitialize) {
logMessage(this->getServerId(), "{} Client initialized session reconnect and last ping response is older then 5 seconds ({}). Dropping attempt because its not allowed due to config settings",
CLIENT_STR_LOG_PREFIX,
duration_cast<milliseconds>(system_clock::now() - this->lastPingResponse).count()
);
return ts::command_result{error::ok};
}
logMessage(this->getServerId(), "{} Client initialized reconnect and last ping response is older then 5 seconds ({}). Allowing attempt",
CLIENT_STR_LOG_PREFIX,
duration_cast<milliseconds>(system_clock::now() - this->lastPingResponse).count()
);
{
unique_lock server_channel_lock(this->server->channel_tree_lock); /* we cant get moved if this is locked! */
if(this->currentChannel) {
this->server->client_move(this->ref(), nullptr, nullptr, config::messages::timeout::connection_reinitialized, ViewReasonId::VREASON_TIMEOUT, false, server_channel_lock);
}
}
this->finalDisconnect();
} else if(this->state == ConnectionState::DISCONNECTING) {
this->closeConnection(); /* executing final disconnect right now! */
}
this->connection->reset();
{
auto& read_queue = this->connection->packet_buffers()[protocol::COMMAND];
unique_lock buffer_lock(read_queue.buffer_lock);
read_queue.set_full_index_to(1); /* the first packet (0) is already the clientinitiv packet */
}
this->state = ConnectionState::INIT_HIGH;
bool use_teaspeak = command.hasParm("teaspeak");
if(use_teaspeak) {
debugMessage(this->getServerId(), "{} Client using TeaSpeak with auth type {}", CLIENT_STR_LOG_PREFIX, command["verify_type"].string());
this->properties()[property::CLIENT_TYPE_EXACT] = ClientType::CLIENT_TEASPEAK;
}
/* normal TeamSpeak handling */
string clientAlpha = base64::decode(command["alpha"]); //random
if(clientAlpha.length() != 10) return ts::command_result{error::parameter_invalid};
string clientOmega = base64::decode(command["omega"]); //The identity public key
string ip = command["ip"];
bool ot = command[0].has("ot") ? command["ot"] : false;
this->crypto.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->crypto.remote_key.get());
if(state != CRYPT_OK) {
this->crypto.remote_key.reset();
return ts::command_result{error::client_could_not_validate_identity};
}
this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(command["omega"].string()));
this->crypto.alpha = clientAlpha;
this->crypto.new_protocol = !use_teaspeak && ot && config::experimental_31 && (this->crypto.client_time >= 173265950 || this->crypto.client_time == 5680278000UL);
{
size_t beta_length = this->crypto.new_protocol ? 54 : 10;
char beta[beta_length];
generate_random((uint8_t *) beta, beta_length);
this->crypto.beta = string(beta, beta_length);
}
if(this->crypto.new_protocol) {
//Pre setup
//Generate chain
debugMessage(this->getServerId(), "{} Got client 3.1 protocol with build timestamp {}", CLIENT_STR_LOG_PREFIX, this->crypto.client_time);
this->crypto.chain_data = serverInstance->getTeamSpeakLicense()->license();
this->crypto.chain_data->chain->addEphemeralEntry();
auto rawLicense = this->crypto.chain_data->chain->exportChain();
//Sign license
auto serverOmega = this->getServer()->publicServerKey();
auto rawServerOmega = base64::decode(serverOmega);
auto licenseHash = digest::sha256(rawLicense);
size_t signBufferLength = 128;
char signBuffer[signBufferLength];
prng_state prngState{};
memset(&prngState, 0, sizeof(prngState));
if(ecc_sign_hash((u_char*) licenseHash.data(), licenseHash.length(), (u_char*) signBuffer, &signBufferLength, &prngState, find_prng("sprng"), this->getServer()->serverKey()) != CRYPT_OK) {
logError(this->getServerId(), "Failed to sign crypto chain!");
return ts::command_result{error::vs_critical};
}
auto proof = base64::encode(signBuffer, signBufferLength);
Command initivexpand2("initivexpand2");
initivexpand2["time"] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
initivexpand2["l"] = base64::encode(rawLicense);
initivexpand2["beta"] = base64::encode(this->crypto.beta);
initivexpand2["omega"] = serverOmega;
initivexpand2["proof"] = proof;
initivexpand2["tvd"] = "";
initivexpand2["root"] = base64::encode((char*) this->crypto.chain_data->public_key, 32);
initivexpand2["ot"] = 1;
this->sendCommand(initivexpand2);
this->handshake.state = HandshakeState::SUCCEEDED; /* we're doing the verify via TeamSpeak */
} else {
debugMessage(this->getServerId(), "{} Got non client 3.1 protocol with build timestamp {}", CLIENT_STR_LOG_PREFIX, this->crypto.client_time);
auto serverOmega = this->getServer()->publicServerKey();
if(serverOmega.empty()) {
logError(this->getServerId(), "Failed to export server public key!");
return ts::command_result{error::vs_critical};;
}
{
Command initivexpand("initivexpand");
initivexpand["alpha"] = base64::encode(clientAlpha);
initivexpand["beta"] = base64::encode(this->crypto.beta);
initivexpand["omega"] = serverOmega;
if(use_teaspeak) {
initivexpand["teaspeak"] = 1;
this->handshake.state = HandshakeState::BEGIN; /* we need to start the handshake */
} else {
this->handshake.state = HandshakeState::SUCCEEDED; /* we're doing the verify via TeamSpeak */
}
this->sendCommand0(initivexpand.build(), false, true); //If we setup the encryption now
}
{
this->connection->getCryptHandler()->block(); //Block until the key is setuped
string error;
if(!this->connection->getCryptHandler()->setupSharedSecret(this->crypto.alpha, this->crypto.beta, this->crypto.remote_key.get(), this->server->serverKey(), error)){
this->connection->getCryptHandler()->unblock();
logError(this->server->getServerId(), "Could not setup shared secret! (" + error + ")");
return ts::command_result{error::vs_critical};
}
this->connection->getCryptHandler()->unblock();
this->crypto.protocol_encrypted = true;
}
}
return ts::command_result{error::ok};
}
ts::command_result VoiceClient::handleCommandClientEk(const std::unique_ptr<protocol::ClientPacket>& packet, Command& cmd) {
this->last_packet_handshake = system_clock::now();
debugMessage(this->getServerId(), "{} Got client ek!", CLIENT_STR_LOG_PREFIX);
auto client_key = base64::decode(cmd["ek"]);
auto x = this->crypto.chain_data->chain->generatePrivateKey(this->crypto.chain_data->root_key, this->crypto.chain_data->root_index);
this->connection->getCryptHandler()->setupSharedSecretNew(this->crypto.alpha, this->crypto.beta, (char*) x.data(), client_key.data());
this->connection->acknowledge_handler.reset();
this->crypto.protocol_encrypted = true;
this->sendAcknowledge(packet->packetId()); //Send the encrypted acknowledge
return ts::command_result{error::ok};
}