Newer
Older
#include "DeviceVerificationFlow.h"
#include <QTimer>
static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes
namespace msgs = mtx::events::msg;
DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow::Type)
{
timeout = new QTimer(this);
timeout->setSingleShot(true);
this->sas = olm::client()->sas_init();
this->isMacVerified = false;
connect(timeout, &QTimer::timeout, this, [this]() {
emit timedout();
this->cancelVerification(DeviceVerificationFlow::Error::Timeout);
this->deleteLater();
});
connect(
ChatPage::instance(),
&ChatPage::recievedDeviceVerificationStart,
this,
[this](const mtx::events::msg::KeyVerificationStart &msg, std::string) {
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relates_to.has_value()) {
if (msg.relates_to.value().in_reply_to.event_id !=
this->relation.in_reply_to.event_id)
return;
if ((std::find(msg.key_agreement_protocols.begin(),
msg.key_agreement_protocols.end(),
"curve25519-hkdf-sha256") != msg.key_agreement_protocols.end()) &&
(std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") !=
msg.hashes.end()) &&
(std::find(msg.message_authentication_codes.begin(),
msg.message_authentication_codes.end(),
"hmac-sha256") != msg.message_authentication_codes.end())) {
if (std::find(msg.short_authentication_string.begin(),
msg.short_authentication_string.end(),
mtx::events::msg::SASMethods::Decimal) !=
msg.short_authentication_string.end()) {
this->method = DeviceVerificationFlow::Method::Emoji;
} else if (std::find(msg.short_authentication_string.begin(),
msg.short_authentication_string.end(),
mtx::events::msg::SASMethods::Emoji) !=
msg.short_authentication_string.end()) {
this->method = DeviceVerificationFlow::Method::Decimal;
this->cancelVerification(
DeviceVerificationFlow::Error::UnknownMethod);
this->acceptVerificationRequest();
this->canonical_json = nlohmann::json(msg);
} else {
this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
connect(ChatPage::instance(),
&ChatPage::recievedDeviceVerificationAccept,
this,
[this](const mtx::events::msg::KeyVerificationAccept &msg) {
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relates_to.has_value()) {
if (msg.relates_to.value().in_reply_to.event_id !=
this->relation.in_reply_to.event_id)
return;
}
if ((msg.key_agreement_protocol == "curve25519-hkdf-sha256") &&
(msg.hash == "sha256") &&
(msg.message_authentication_code == "hkdf-hmac-sha256")) {
this->commitment = msg.commitment;
if (std::find(msg.short_authentication_string.begin(),
msg.short_authentication_string.end(),
mtx::events::msg::SASMethods::Emoji) !=
msg.short_authentication_string.end()) {
this->method = DeviceVerificationFlow::Method::Emoji;
} else {
this->method = DeviceVerificationFlow::Method::Decimal;
}
this->mac_method = msg.message_authentication_code;
this->sendVerificationKey();
} else {
this->cancelVerification(
DeviceVerificationFlow::Error::UnknownMethod);
}
});
connect(ChatPage::instance(),
&ChatPage::recievedDeviceVerificationCancel,
this,
[this](const mtx::events::msg::KeyVerificationCancel &msg) {
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relates_to.has_value()) {
if (msg.relates_to.value().in_reply_to.event_id !=
this->relation.in_reply_to.event_id)
return;
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
connect(ChatPage::instance(),
&ChatPage::recievedDeviceVerificationKey,
this,
[this](const mtx::events::msg::KeyVerificationKey &msg) {
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relates_to.has_value()) {
if (msg.relates_to.value().in_reply_to.event_id !=
this->relation.in_reply_to.event_id)
return;
}
this->sas->set_their_key(msg.key);
std::string info;
if (this->sender == true) {
info = "MATRIX_KEY_VERIFICATION_SAS|" +
http::client()->user_id().to_string() + "|" +
http::client()->device_id() + "|" + this->sas->public_key() +
"|" + this->toClient.to_string() + "|" +
this->deviceId.toStdString() + "|" + msg.key + "|" +
this->transaction_id;
} else {
info = "MATRIX_KEY_VERIFICATION_SAS|" + this->toClient.to_string() +
"|" + this->deviceId.toStdString() + "|" + msg.key + "|" +
http::client()->user_id().to_string() + "|" +
http::client()->device_id() + "|" + this->sas->public_key() +
"|" + this->transaction_id;
}
if (this->method == DeviceVerificationFlow::Method::Emoji) {
this->sasList = this->sas->generate_bytes_emoji(info);
} else if (this->method == DeviceVerificationFlow::Method::Decimal) {
this->sasList = this->sas->generate_bytes_decimal(info);
}
if (this->sender == false) {
emit this->verificationRequestAccepted(this->method);
this->sendVerificationKey();
} else {
if (this->commitment ==
mtx::crypto::bin2base64_unpadded(
mtx::crypto::sha256(msg.key + this->canonical_json.dump()))) {
emit this->verificationRequestAccepted(this->method);
} else {
this->cancelVerification(
DeviceVerificationFlow::Error::MismatchedCommitment);
}
}
});
connect(
ChatPage::instance(),
&ChatPage::recievedDeviceVerificationMac,
[this](const mtx::events::msg::KeyVerificationMac &msg) {
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relates_to.has_value()) {
if (msg.relates_to.value().in_reply_to.event_id !=
this->relation.in_reply_to.event_id)
return;
}
std::string info = "MATRIX_KEY_VERIFICATION_MAC" + this->toClient.to_string() +
this->deviceId.toStdString() +
http::client()->user_id().to_string() +
http::client()->device_id() + this->transaction_id;
std::vector<std::string> key_list;
std::string key_string;
for (auto mac : msg.mac) {
key_string += mac.first + ",";
if (device_keys[mac.first] != "") {
if (mac.second ==
this->sas->calculate_mac(this->device_keys[mac.first],
info + mac.first)) {
DeviceVerificationFlow::Error::KeyMismatch);
return;
key_string = key_string.substr(0, key_string.length() - 1);
if (msg.keys == this->sas->calculate_mac(key_string, info + "KEY_IDS")) {
// uncomment this in future to be compatible with the
// MSC2366 this->sendVerificationDone(); and remove the
// below line
if (this->isMacVerified == true) {
this->acceptDevice();
} else
this->isMacVerified = true;
} else {
this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch);
connect(ChatPage::instance(),
&ChatPage::recievedDeviceVerificationReady,
this,
[this](const mtx::events::msg::KeyVerificationReady &msg) {
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relates_to.has_value()) {
if (msg.relates_to.value().in_reply_to.event_id !=
this->relation.in_reply_to.event_id)
return;
connect(ChatPage::instance(),
&ChatPage::recievedDeviceVerificationDone,
this,
[this](const mtx::events::msg::KeyVerificationDone &msg) {
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relates_to.has_value()) {
if (msg.relates_to.value().in_reply_to.event_id !=
this->relation.in_reply_to.event_id)
return;
timeout->start(TIMEOUT);
}
QString
DeviceVerificationFlow::getTransactionId()
{
return QString::fromStdString(this->transaction_id);
}
QString
DeviceVerificationFlow::getUserId()
return this->userId;
}
QString
DeviceVerificationFlow::getDeviceId()
{
return this->deviceId;
}
DeviceVerificationFlow::Method
DeviceVerificationFlow::getMethod()
{
return this->method;
}
bool
DeviceVerificationFlow::getSender()
{
return this->sender;
}
std::vector<int>
DeviceVerificationFlow::getSasList()
{
return this->sasList;
}
void
DeviceVerificationFlow::setTransactionId(QString transaction_id_)
{
this->transaction_id = transaction_id_.toStdString();
}
void
DeviceVerificationFlow::setUserId(QString userID)
{
this->userId = userID;
this->toClient = mtx::identifiers::parse<mtx::identifiers::User>(userID.toStdString());
auto user_cache = cache::getUserCache(userID.toStdString());
if (user_cache.has_value()) {
this->callback_fn(user_cache->keys, {}, userID.toStdString());
} else {
mtx::requests::QueryKeys req;
req.device_keys[userID.toStdString()] = {};
http::client()->query_keys(
req,
[user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res,
mtx::http::RequestErr err) {
this->callback_fn(res, err, user_id);
});
}
}
void
DeviceVerificationFlow::setDeviceId(QString deviceID)
{
this->deviceId = deviceID;
}
void
DeviceVerificationFlow::setMethod(DeviceVerificationFlow::Method method_)
{
this->method = method_;
void
DeviceVerificationFlow::setSender(bool sender_)
{
this->sender = sender_;
if (this->sender == true && this->type == DeviceVerificationFlow::Type::ToDevice)
this->transaction_id = http::client()->generate_txn_id();
else if (this->sender == true && this->type == DeviceVerificationFlow::Type::RoomMsg)
this->relation.in_reply_to.event_id = http::client()->generate_txn_id();
}
//! accepts a verification
void
DeviceVerificationFlow::acceptVerificationRequest()
{
mtx::events::msg::KeyVerificationAccept req;
req.method = mtx::events::msg::VerificationMethods::SASv1;
req.key_agreement_protocol = "curve25519-hkdf-sha256";
req.message_authentication_code = "hkdf-hmac-sha256";
if (this->method == DeviceVerificationFlow::Method::Emoji)
req.short_authentication_string = {mtx::events::msg::SASMethods::Emoji};
else if (this->method == DeviceVerificationFlow::Method::Decimal)
req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal};
req.commitment = mtx::crypto::bin2base64_unpadded(
mtx::crypto::sha256(this->sas->public_key() + this->canonical_json.dump()));
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationAccept> body;
req.transaction_id = this->transaction_id;
body[this->toClient][this->deviceId.toStdString()] = req;
http::client()
->send_to_device<mtx::events::msg::KeyVerificationAccept,
mtx::events::EventType::KeyVerificationAccept>(
this->transaction_id, body, [](mtx::http::RequestErr err) {
if (err)
nhlog::net()->warn(
"failed to accept verification request: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
});
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
req.relates_to = this->relation;
}
//! responds verification request
void
DeviceVerificationFlow::sendVerificationReady()
{
mtx::events::msg::KeyVerificationReady req;
req.from_device = http::client()->device_id();
req.methods = {mtx::events::msg::VerificationMethods::SASv1};
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
req.transaction_id = this->transaction_id;
mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationReady> body;
body[this->toClient][this->deviceId.toStdString()] = req;
http::client()
->send_to_device<mtx::events::msg::KeyVerificationReady,
mtx::events::EventType::KeyVerificationReady>(
this->transaction_id, body, [](mtx::http::RequestErr err) {
if (err)
nhlog::net()->warn("failed to send verification ready: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
});
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
req.relates_to = this->relation;
}
}
//! accepts a verification
void
DeviceVerificationFlow::sendVerificationDone()
{
mtx::events::msg::KeyVerificationDone req;
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationDone> body;
req.transaction_id = this->transaction_id;
body[this->toClient][this->deviceId.toStdString()] = req;
http::client()
->send_to_device<mtx::events::msg::KeyVerificationDone,
mtx::events::EventType::KeyVerificationDone>(
this->transaction_id, body, [](mtx::http::RequestErr err) {
if (err)
nhlog::net()->warn("failed to send verification done: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
});
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
req.relates_to = this->relation;
}
//! starts the verification flow
void
DeviceVerificationFlow::startVerificationRequest()
{
mtx::events::msg::KeyVerificationStart req;
req.from_device = http::client()->device_id();
req.method = mtx::events::msg::VerificationMethods::SASv1;
req.key_agreement_protocols = {"curve25519-hkdf-sha256"};
req.hashes = {"sha256"};
req.message_authentication_codes = {"hkdf-hmac-sha256"};
req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal,
mtx::events::msg::SASMethods::Emoji};
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationStart> body;
req.transaction_id = this->transaction_id;
this->canonical_json = nlohmann::json(req);
body[this->toClient][this->deviceId.toStdString()] = req;
http::client()
->send_to_device<mtx::events::msg::KeyVerificationStart,
mtx::events::EventType::KeyVerificationStart>(
this->transaction_id, body, [body](mtx::http::RequestErr err) {
if (err)
nhlog::net()->warn(
"failed to start verification request: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
});
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
req.relates_to = this->relation;
}
}
//! sends a verification request
void
DeviceVerificationFlow::sendVerificationRequest()
{
mtx::events::msg::KeyVerificationRequest req;
req.from_device = http::client()->device_id();
req.methods.resize(1);
req.methods[0] = mtx::events::msg::VerificationMethods::SASv1;
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
QDateTime CurrentTime = QDateTime::currentDateTimeUtc();
req.transaction_id = this->transaction_id;
req.timestamp = (uint64_t)CurrentTime.toTime_t();
mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationRequest> body;
body[this->toClient][this->deviceId.toStdString()] = req;
http::client()
->send_to_device<mtx::events::msg::KeyVerificationRequest,
mtx::events::EventType::KeyVerificationRequest>(
this->transaction_id, body, [](mtx::http::RequestErr err) {
if (err)
nhlog::net()->warn("failed to send verification request: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
});
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
std::cout << "lulz" << std::endl;
}
}
//! cancels a verification flow
void
DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_code)
mtx::events::msg::KeyVerificationCancel req;
if (error_code == DeviceVerificationFlow::Error::UnknownMethod) {
req.code = "m.unknown_method";
req.reason = "unknown method recieved";
} else if (error_code == DeviceVerificationFlow::Error::MismatchedCommitment) {
req.code = "m.mismatched_commitment";
req.reason = "commitment didn't match";
} else if (error_code == DeviceVerificationFlow::Error::MismatchedSAS) {
req.code = "m.mismatched_sas";
req.reason = "sas didn't match";
} else if (error_code == DeviceVerificationFlow::Error::KeyMismatch) {
req.code = "m.key_match";
req.reason = "keys did not match";
} else if (error_code == DeviceVerificationFlow::Error::Timeout) {
req.code = "m.timeout";
req.reason = "timed out";
} else if (error_code == DeviceVerificationFlow::Error::User) {
req.code = "m.user";
req.reason = "user cancelled the verification";
}
emit this->verificationCanceled();
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
req.transaction_id = this->transaction_id;
mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationCancel> body;
body[this->toClient][deviceId.toStdString()] = req;
http::client()
->send_to_device<mtx::events::msg::KeyVerificationCancel,
mtx::events::EventType::KeyVerificationCancel>(
this->transaction_id, body, [this](mtx::http::RequestErr err) {
if (err)
nhlog::net()->warn(
"failed to cancel verification request: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
this->deleteLater();
});
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
req.relates_to = this->relation;
}
// TODO : Handle Blocking user better
// auto verified_cache = cache::getVerifiedCache(this->userId.toStdString());
// if (verified_cache.has_value()) {
// verified_cache->device_blocked.push_back(this->deviceId.toStdString());
// cache::setVerifiedCache(this->userId.toStdString(),
// verified_cache.value());
// } else {
// cache::setVerifiedCache(
// this->userId.toStdString(),
// DeviceVerifiedCache{{}, {}, {this->deviceId.toStdString()}});
// }
//! sends the verification key
void
DeviceVerificationFlow::sendVerificationKey()
{
mtx::events::msg::KeyVerificationKey req;
req.key = this->sas->public_key();
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationKey> body;
req.transaction_id = this->transaction_id;
body[this->toClient][deviceId.toStdString()] = req;
http::client()
->send_to_device<mtx::events::msg::KeyVerificationKey,
mtx::events::EventType::KeyVerificationKey>(
this->transaction_id, body, [](mtx::http::RequestErr err) {
if (err)
nhlog::net()->warn("failed to send verification key: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
});
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
req.relates_to = this->relation;
}
}
//! sends the mac of the keys
void
DeviceVerificationFlow::sendVerificationMac()
{
mtx::events::msg::KeyVerificationMac req;
std::string info = "MATRIX_KEY_VERIFICATION_MAC" + http::client()->user_id().to_string() +
http::client()->device_id() + this->toClient.to_string() +
this->deviceId.toStdString() + this->transaction_id;
//! this vector stores the type of the key and the key
std::vector<std::pair<std::string, std::string>> key_list;
key_list.push_back(make_pair("ed25519", olm::client()->identity_keys().ed25519));
std::sort(key_list.begin(), key_list.end());
for (auto x : key_list) {
req.mac.insert(
std::make_pair(x.first + ":" + http::client()->device_id(),
this->sas->calculate_mac(
x.second, info + x.first + ":" + http::client()->device_id())));
req.keys += x.first + ":" + http::client()->device_id() + ",";
}
req.keys =
this->sas->calculate_mac(req.keys.substr(0, req.keys.size() - 1), info + "KEY_IDS");
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationMac> body;
req.transaction_id = this->transaction_id;
body[this->toClient][deviceId.toStdString()] = req;
http::client()
->send_to_device<mtx::events::msg::KeyVerificationMac,
mtx::events::EventType::KeyVerificationMac>(
this->transaction_id, body, [this](mtx::http::RequestErr err) {
if (err)
nhlog::net()->warn("failed to send verification MAC: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
if (this->isMacVerified == true)
this->acceptDevice();
else
this->isMacVerified = true;
});
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg) {
req.relates_to = this->relation;
}
//! Completes the verification flow
void
DeviceVerificationFlow::acceptDevice()
{
auto verified_cache = cache::getVerifiedCache(this->userId.toStdString());
if (verified_cache.has_value()) {
verified_cache->device_verified.push_back(this->deviceId.toStdString());
verified_cache->device_blocked.erase(
std::remove(verified_cache->device_blocked.begin(),
verified_cache->device_blocked.end(),
this->deviceId.toStdString()),
verified_cache->device_blocked.end());
cache::setVerifiedCache(
this->userId.toStdString(),
DeviceVerifiedCache{{this->deviceId.toStdString()}, {}, {}});
emit deviceVerified();
this->deleteLater();
}
//! callback function to keep track of devices
void
DeviceVerificationFlow::callback_fn(const mtx::responses::QueryKeys &res,
mtx::http::RequestErr err,
std::string user_id)
{
if (err) {
nhlog::net()->warn("failed to query device keys: {},{}",
err->matrix_error.errcode,
static_cast<int>(err->status_code));
return;
}
if (res.device_keys.empty() || (res.device_keys.find(user_id) == res.device_keys.end())) {
nhlog::net()->warn("no devices retrieved {}", user_id);
return;
}
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
for (auto x : res.device_keys) {
for (auto y : x.second) {
auto z = y.second;
if (z.user_id == user_id && z.device_id == this->deviceId.toStdString()) {
for (auto a : z.keys) {
// TODO: Verify Signatures
this->device_keys[a.first] = a.second;
}
}
}
}
}
void
DeviceVerificationFlow::unverify()
{
auto verified_cache = cache::getVerifiedCache(this->userId.toStdString());
if (verified_cache.has_value()) {
auto it = std::remove(verified_cache->device_verified.begin(),
verified_cache->device_verified.end(),
this->deviceId.toStdString());
verified_cache->device_verified.erase(it);
cache::setVerifiedCache(this->userId.toStdString(), verified_cache.value());
}
emit refreshProfile();