Skip to content

Commit cd68bbf

Browse files
voice client methods for DAVE stuff
1 parent c5e7664 commit cd68bbf

File tree

2 files changed

+115
-40
lines changed

2 files changed

+115
-40
lines changed

include/dpp/discordvoiceclient.h

+40-7
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ inline constexpr size_t send_audio_raw_max_length = 11520;
7171

7272
inline constexpr size_t secret_key_size = 32;
7373

74-
struct dave_transient_key;
74+
struct dave_state;
7575
struct dave_encryptors;
7676

7777
/*
@@ -202,6 +202,8 @@ struct dave_binary_header_t {
202202
};
203203
#pragma pack(pop)
204204

205+
using privacy_code_callback_t = std::function<void(const std::string&)>;
206+
205207
/** @brief Implements a discord voice connection.
206208
* Each discord_voice_client connects to one voice channel and derives from a websocket client.
207209
*/
@@ -413,9 +415,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client
413415

414416
std::unique_ptr<dave::mls::Session> dave_session{};
415417

416-
std::unique_ptr<dave_transient_key> transient_key{};
417-
418-
std::unique_ptr<dave_encryptors> encryptors{};
418+
std::unique_ptr<dave_state> mls_state{};
419419

420420
#else
421421
/**
@@ -431,9 +431,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client
431431

432432
std::unique_ptr<int> dave_session{};
433433

434-
std::unique_ptr<int> transient_key{};
435-
436-
std::unique_ptr<int> encryptors{};
434+
std::unique_ptr<int> mls_state{};
437435
#endif
438436

439437
std::set<std::string> dave_mls_user_list;
@@ -1114,6 +1112,41 @@ class DPP_EXPORT discord_voice_client : public websocket_client
11141112
* for a single packet from Discord's voice servers.
11151113
*/
11161114
std::string discover_ip();
1115+
1116+
/**
1117+
* Returns true if end-to-end encryption is enabled
1118+
* for the active voice call (Discord Audio Visual
1119+
* Encryption, a.k.a. DAVE).
1120+
*
1121+
* @return True if end-to-end encrypted
1122+
*/
1123+
bool is_end_to_end_encrypted() const;
1124+
1125+
/**
1126+
* Returns the privacy code for the end to end encryption
1127+
* scheme ("DAVE"). if end-to-end encryption is not active,
1128+
* or is not yet established, this will return an empty
1129+
* string.
1130+
*
1131+
* @return A sequence of six five-digit integers which
1132+
* can be matched against the Discord client, in the
1133+
* privacy tab for the properties of the voice call.
1134+
*/
1135+
std::string get_privacy_code() const;
1136+
1137+
/**
1138+
* Returns the privacy code for a given user by id,
1139+
* if they are in the voice call, and enc-to-end encryption
1140+
* is enabled.
1141+
*
1142+
* @param user User ID to fetch the privacy code for
1143+
* @param callback Callback to call with the privacy code when
1144+
* the creation of the code is complete.
1145+
* @warning This call spawns a thread, as getting a user's
1146+
* privacy code is a CPU-intensive and memory-intensive operation
1147+
* which internally uses scrypt.
1148+
*/
1149+
void get_user_privacy_code(const dpp::snowflake user, privacy_code_callback_t callback) const;
11171150
};
11181151

11191152
} // namespace dpp

src/dpp/discordvoiceclient.cpp

+75-33
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,13 @@ constexpr uint8_t voice_protocol_version = 8;
7474
*/
7575
static std::string external_ip;
7676

77-
struct dave_transient_key {
77+
struct dave_state {
7878
std::shared_ptr<::mlspp::SignaturePrivateKey> mls_key;
7979
std::vector<uint8_t> cached_commit;
8080
uint64_t transition_id{0};
81-
};
82-
83-
struct dave_encryptors {
81+
std::map<dpp::snowflake, std::unique_ptr<dave::Decryptor>> decryptors;
8482
std::unique_ptr<dave::Encryptor> encryptor;
85-
std::unique_ptr<dave::Decryptor> decryptor;
83+
std::string privacy_code;
8684
};
8785

8886
/**
@@ -187,6 +185,25 @@ size_t audio_mix(discord_voice_client& client, audio_mixer& mixer, opus_int32* p
187185
max_samples = (std::max)(samples, max_samples);
188186
return park_count + 1;
189187
}
188+
189+
std::string generate_displayable_code(const std::vector<uint8_t>& data, size_t desired_length = 30, size_t group_size = 5) {
190+
191+
const size_t group_modulus = std::pow(10, group_size);
192+
std::stringstream result;
193+
194+
for (size_t i = 0; i < desired_length; i += group_size) {
195+
size_t group_value{0};
196+
197+
for (size_t j = group_size; j > 0; --j) {
198+
const size_t next_byte = data.at(i + (group_size - j));
199+
group_value = (group_value << 8) | next_byte;
200+
}
201+
group_value %= group_modulus;
202+
result << std::setw(group_size) << std::setfill('0') << std::to_string(group_value) << " ";
203+
}
204+
205+
return result.str();
206+
}
190207
#endif
191208

192209
void discord_voice_client::voice_courier_loop(discord_voice_client& client, courier_shared_state_t& shared_state) {
@@ -512,8 +529,26 @@ std::vector<uint8_t> dave_binary_header_t::get_welcome_data(size_t length) const
512529
return std::vector<uint8_t>(package + sizeof(uint16_t), package + length - sizeof(dave_binary_header_t));
513530
}
514531

515-
bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcode)
516-
{
532+
std::string discord_voice_client::get_privacy_code() const {
533+
return is_end_to_end_encrypted() ? mls_state->privacy_code : "";
534+
}
535+
536+
void discord_voice_client::get_user_privacy_code(const dpp::snowflake user, privacy_code_callback_t callback) const {
537+
if (!is_end_to_end_encrypted()) {
538+
callback("");
539+
return;
540+
}
541+
dave_session->GetPairwiseFingerprint(0x0000, user.str(), [callback](const std::vector<uint8_t>& data) {
542+
std::cout << dpp::utility::debug_dump((uint8_t*)data.data(), data.size());
543+
callback(data.size() == 64 ? generate_displayable_code(data, 45) : "");
544+
});
545+
}
546+
547+
bool discord_voice_client::is_end_to_end_encrypted() const {
548+
return dave_session && mls_state && !mls_state->privacy_code.empty();
549+
}
550+
551+
bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcode) {
517552
json j;
518553

519554
/**
@@ -529,12 +564,8 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod
529564

530565
dave_session->SetExternalSender(dave_header->get_data(data.length()));
531566

532-
encryptors = std::make_unique<dave_encryptors>();
533-
encryptors->encryptor = std::make_unique<dave::Encryptor>();
534-
/**
535-
* TODO: There should be one of these per user but only one of the encryptor, above
536-
*/
537-
encryptors->decryptor = std::make_unique<dave::Decryptor>();
567+
mls_state->encryptor = std::make_unique<dave::Encryptor>();
568+
mls_state->decryptors.clear();
538569
}
539570
break;
540571
case voice_client_dave_mls_proposals: {
@@ -543,43 +574,54 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod
543574
std::optional<std::vector<uint8_t>> response = dave_session->ProcessProposals(dave_header->get_data(data.length()), dave_mls_user_list);
544575
if (response.has_value()) {
545576
auto r = response.value();
546-
transient_key->cached_commit = r;
577+
mls_state->cached_commit = r;
547578
r.insert(r.begin(), voice_client_dave_mls_commit_message);
548579
this->write(std::string_view(reinterpret_cast<const char*>(r.data()), r.size()), OP_BINARY);
549580
}
550581
}
551582
break;
552583
case voice_client_dave_announce_commit_transaction: {
553584
log(ll_debug, "voice_client_dave_announce_commit_transaction");
554-
auto r = dave_session->ProcessCommit(transient_key->cached_commit);
585+
auto r = dave_session->ProcessCommit(mls_state->cached_commit);
555586
for (const auto& user : dave_mls_user_list) {
556587
log(ll_debug, "Setting decryptor key ratchet for user: " + user + ", protocol version: " + std::to_string(dave_session->GetProtocolVersion()));
557-
encryptors->decryptor->TransitionToKeyRatchet(dave_session->GetKeyRatchet(user));
588+
dpp::snowflake u{user};
589+
mls_state->decryptors.emplace(u, std::make_unique<dpp::dave::Decryptor>());
590+
mls_state->decryptors.find(u)->second->TransitionToKeyRatchet(dave_session->GetKeyRatchet(user));
558591
}
559-
encryptors->encryptor->SetKeyRatchet(dave_session->GetKeyRatchet(creator->me.id.str()));
592+
mls_state->encryptor->SetKeyRatchet(dave_session->GetKeyRatchet(creator->me.id.str()));
560593

561594
/**
562595
* https://www.ietf.org/archive/id/draft-ietf-mls-protocol-14.html#name-epoch-authenticators
563596
* 9.7. Epoch Authenticators
564597
* The main MLS key schedule provides a per-epoch epoch_authenticator. If one member of the group is being impersonated by an active attacker,
565598
* the epoch_authenticator computed by their client will differ from those computed by the other group members.
566599
*/
567-
auto epoch = dave_session->GetLastEpochAuthenticator();
568-
log(ll_debug, "DAVE epoch authenticator: " + dpp::base64_encode((unsigned char const*)epoch.data(), epoch.size()));
600+
mls_state->privacy_code = generate_displayable_code(dave_session->GetLastEpochAuthenticator());
601+
log(ll_debug, "E2EE Privacy Code: " + mls_state->privacy_code);
602+
get_user_privacy_code(189759562910400512, [this](const std::string& code) {
603+
log(ll_debug, "Test pairwise code: " + code);
604+
});
569605
}
570606
break;
571607
case voice_client_dave_mls_welcome: {
572-
this->transient_key->transition_id = dave_header->get_welcome_transition_id();
573-
log(ll_debug, "voice_client_dave_mls_welcome with transition id " + std::to_string(this->transient_key->transition_id));
608+
this->mls_state->transition_id = dave_header->get_welcome_transition_id();
609+
log(ll_debug, "voice_client_dave_mls_welcome with transition id " + std::to_string(this->mls_state->transition_id));
574610
auto r = dave_session->ProcessWelcome(dave_header->get_welcome_data(data.length()), dave_mls_user_list);
575611
if (r.has_value()) {
576-
for (const auto& user_key_pair : r.value()) {
577-
std::cout << "WEL: " << user_key_pair.first << "\n";
612+
for (const auto& user : dave_mls_user_list) {
613+
log(ll_debug, "Setting decryptor key ratchet for user: " + user + ", protocol version: " + std::to_string(dave_session->GetProtocolVersion()));
614+
dpp::snowflake u{user};
615+
mls_state->decryptors.emplace(u, std::make_unique<dpp::dave::Decryptor>());
616+
mls_state->decryptors.find(u)->second->TransitionToKeyRatchet(dave_session->GetKeyRatchet(user));
578617
}
579-
encryptors->encryptor->SetKeyRatchet(dave_session->GetKeyRatchet(creator->me.id.str()));
580-
} else {
581-
std::cout << "Welcome has no value\n";
618+
mls_state->encryptor->SetKeyRatchet(dave_session->GetKeyRatchet(creator->me.id.str()));
582619
}
620+
mls_state->privacy_code = generate_displayable_code(dave_session->GetLastEpochAuthenticator());
621+
log(ll_debug, "E2EE Privacy Code: " + mls_state->privacy_code);
622+
get_user_privacy_code(189759562910400512, [this](const std::string& code) {
623+
log(ll_debug, "Test pairwise code: " + code);
624+
});
583625
}
584626
break;
585627
default:
@@ -637,19 +679,19 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod
637679
}
638680
break;
639681
case voice_client_dave_mls_invalid_commit_welcome: {
640-
this->transient_key->transition_id = j["d"]["transition_id"];
641-
log(ll_debug, "voice_client_dave_mls_invalid_commit_welcome transition id " + std::to_string(this->transient_key->transition_id));
682+
this->mls_state->transition_id = j["d"]["transition_id"];
683+
log(ll_debug, "voice_client_dave_mls_invalid_commit_welcome transition id " + std::to_string(this->mls_state->transition_id));
642684
}
643685
break;
644686
case voice_client_dave_execute_transition: {
645687
log(ll_debug, "voice_client_dave_execute_transition");
646-
this->transient_key->transition_id = j["d"]["transition_id"];
688+
this->mls_state->transition_id = j["d"]["transition_id"];
647689
json obj = {
648690
{ "op", voice_client_dave_transition_ready },
649691
{
650692
"d",
651693
{
652-
{ "transition_id", this->transient_key->transition_id },
694+
{ "transition_id", this->mls_state->transition_id },
653695
}
654696
}
655697
};
@@ -669,7 +711,7 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod
669711
log(ll_debug, "voice_client_dave_prepare_epoch version=" + std::to_string(protocol_version) + " for epoch " + std::to_string(epoch));
670712
if (epoch == 1) {
671713
dave_session->Reset();
672-
dave_session->Init(dave::MaxSupportedProtocolVersion(), channel_id, creator->me.id.str(), transient_key->mls_key);
714+
dave_session->Init(dave::MaxSupportedProtocolVersion(), channel_id, creator->me.id.str(), mls_state->mls_key);
673715
}
674716
}
675717
break;
@@ -785,8 +827,8 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod
785827
nullptr, "" /* sessionid */, [this](std::string const& s1, std::string const& s2) {
786828
log(ll_debug, "Dave session constructor callback: " + s1 + ", " + s2);
787829
});
788-
transient_key = std::make_unique<dave_transient_key>();
789-
dave_session->Init(dave::MaxSupportedProtocolVersion(), channel_id, creator->me.id.str(), transient_key->mls_key);
830+
mls_state = std::make_unique<dave_state>();
831+
dave_session->Init(dave::MaxSupportedProtocolVersion(), channel_id, creator->me.id.str(), mls_state->mls_key);
790832
auto key_response = dave_session->GetMarshalledKeyPackage();
791833
key_response.insert(key_response.begin(), voice_client_dave_mls_key_package);
792834
this->write(std::string_view(reinterpret_cast<const char*>(key_response.data()), key_response.size()), OP_BINARY);

0 commit comments

Comments
 (0)