Newer
Older
#include "DeviceVerificationFlow.h"
#include <QTimer>
static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes
namespace msgs = mtx::events::msg;
static mtx::events::msg::KeyVerificationMac
key_verification_mac(mtx::crypto::SAS *sas,
mtx::identifiers::User sender,
const std::string &senderDevice,
mtx::identifiers::User receiver,
const std::string &receiverDevice,
const std::string &transactionId,
std::map<std::string, std::string> keys);
DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
DeviceVerificationFlow::Type flow_type,
TimelineModel *model,
QString userID,
QString deviceId_)
: sender(false)
, type(flow_type)
, deviceId(deviceId_)
{
timeout = new QTimer(this);
timeout->setSingleShot(true);
this->sas = olm::client()->sas_init();
this->isMacVerified = false;
auto user_id = userID.toStdString();
this->toClient = mtx::identifiers::parse<mtx::identifiers::User>(user_id);
ChatPage::instance()->query_keys(
user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("failed to query device keys: {},{}",
err->matrix_error.errcode,
static_cast<int>(err->status_code));
return;
}
if (!this->deviceId.isEmpty() &&
(res.device_keys.find(deviceId.toStdString()) == res.device_keys.end())) {
nhlog::net()->warn("no devices retrieved {}", user_id);
return;
}
if (model) {
connect(this->model_,
&TimelineModel::updateFlowEventId,
this,
this->relation.rel_type = mtx::common::RelationType::Reference;
this->relation.event_id = event_id_;
this->transaction_id = event_id_;
connect(timeout, &QTimer::timeout, this, [this]() {
if (state_ != Success && state_ != Failed)
this->cancelVerification(DeviceVerificationFlow::Error::Timeout);
&ChatPage::receivedDeviceVerificationStart,
this,
&DeviceVerificationFlow::handleStartMessage);
connect(ChatPage::instance(),
&ChatPage::receivedDeviceVerificationAccept,
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().event_id != this->relation.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 = mtx::events::msg::SASMethods::Emoji;
this->method = mtx::events::msg::SASMethods::Decimal;
}
this->mac_method = msg.message_authentication_code;
this->sendVerificationKey();
} else {
this->cancelVerification(
DeviceVerificationFlow::Error::UnknownMethod);
}
});
connect(ChatPage::instance(),
&ChatPage::receivedDeviceVerificationCancel,
[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().event_id != this->relation.event_id)
&ChatPage::receivedDeviceVerificationKey,
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().event_id != this->relation.event_id)
if (sender) {
if (state_ != WaitingForOtherToAccept) {
this->cancelVerification(OutOfOrder);
return;
}
} else {
if (state_ != WaitingForKeys) {
this->cancelVerification(OutOfOrder);
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->sender == false) {
this->sendVerificationKey();
} else {
mtx::crypto::bin2base64_unpadded(
mtx::crypto::sha256(msg.key + this->canonical_json.dump()))) {
this->cancelVerification(
DeviceVerificationFlow::Error::MismatchedCommitment);
if (this->method == mtx::events::msg::SASMethods::Emoji) {
this->sasList = this->sas->generate_bytes_emoji(info);
setState(CompareEmoji);
} else if (this->method == mtx::events::msg::SASMethods::Decimal) {
this->sasList = this->sas->generate_bytes_decimal(info);
setState(CompareNumber);
}
connect(
ChatPage::instance(),
&ChatPage::receivedDeviceVerificationMac,
[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().event_id != this->relation.event_id)
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
for (const auto &mac : msg.mac) {
for (const auto &[deviceid, key] : their_keys.device_keys)
if (key.keys.count(mac.first))
key_list[mac.first] = key.keys.at(mac.first);
if (their_keys.master_keys.keys.count(mac.first))
key_list[mac.first] = their_keys.master_keys.keys[mac.first];
if (their_keys.user_signing_keys.keys.count(mac.first))
key_list[mac.first] =
their_keys.user_signing_keys.keys[mac.first];
if (their_keys.self_signing_keys.keys.count(mac.first))
key_list[mac.first] =
their_keys.self_signing_keys.keys[mac.first];
}
auto macs = key_verification_mac(sas.get(),
toClient,
this->deviceId.toStdString(),
http::client()->user_id(),
http::client()->device_id(),
this->transaction_id,
key_list);
for (const auto &[key, mac] : macs.mac) {
if (mac != msg.mac.at(key)) {
this->cancelVerification(
DeviceVerificationFlow::Error::KeyMismatch);
return;
this->isMacVerified = true;
this->acceptDevice();
} else {
this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch);
&ChatPage::receivedDeviceVerificationReady,
[this](const mtx::events::msg::KeyVerificationReady &msg) {
if (!sender) {
if (msg.from_device != http::client()->device_id()) {
error_ = User;
emit errorChanged();
setState(Failed);
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if ((msg.relates_to.has_value() && sender)) {
if (msg.relates_to.value().event_id != this->relation.event_id)
else {
this->deviceId = QString::fromStdString(msg.from_device);
}
&ChatPage::receivedDeviceVerificationDone,
[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().event_id != this->relation.event_id)
nhlog::ui()->info("Flow done on other side");
timeout->start(TIMEOUT);
}
QString
DeviceVerificationFlow::state()
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
switch (state_) {
case PromptStartVerification:
return "PromptStartVerification";
case CompareEmoji:
return "CompareEmoji";
case CompareNumber:
return "CompareNumber";
case WaitingForKeys:
return "WaitingForKeys";
case WaitingForOtherToAccept:
return "WaitingForOtherToAccept";
case WaitingForMac:
return "WaitingForMac";
case Success:
return "Success";
case Failed:
return "Failed";
default:
return "";
}
}
void
DeviceVerificationFlow::next()
{
if (sender) {
switch (state_) {
case PromptStartVerification:
sendVerificationRequest();
break;
case CompareEmoji:
case CompareNumber:
sendVerificationMac();
break;
case WaitingForKeys:
case WaitingForOtherToAccept:
case WaitingForMac:
case Success:
case Failed:
nhlog::db()->error("verification: Invalid state transition!");
break;
}
} else {
switch (state_) {
case PromptStartVerification:
if (canonical_json.is_null())
sendVerificationReady();
else // legacy path without request and ready
acceptVerificationRequest();
break;
case CompareEmoji:
[[fallthrough]];
case CompareNumber:
sendVerificationMac();
break;
case WaitingForKeys:
case WaitingForOtherToAccept:
case WaitingForMac:
case Success:
case Failed:
nhlog::db()->error("verification: Invalid state transition!");
break;
}
}
}
QString
DeviceVerificationFlow::getUserId()
return QString::fromStdString(this->toClient.to_string());
}
QString
DeviceVerificationFlow::getDeviceId()
{
return this->deviceId;
}
bool
DeviceVerificationFlow::getSender()
{
return this->sender;
}
std::vector<int>
DeviceVerificationFlow::getSasList()
{
return this->sasList;
}
void
DeviceVerificationFlow::setEventId(std::string event_id_)
this->relation.rel_type = mtx::common::RelationType::Reference;
this->relation.event_id = event_id_;
this->transaction_id = event_id_;
void
DeviceVerificationFlow::handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg,
std::string)
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
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().event_id != this->relation.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(),
"hkdf-hmac-sha256") != msg.message_authentication_codes.end())) {
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 = mtx::events::msg::SASMethods::Emoji;
} else 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 = mtx::events::msg::SASMethods::Decimal;
} else {
this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
return;
}
if (!sender)
this->canonical_json = nlohmann::json(msg);
else {
if (utils::localUser().toStdString() < this->toClient.to_string()) {
this->canonical_json = nlohmann::json(msg);
}
}
if (state_ != PromptStartVerification)
this->acceptVerificationRequest();
} else {
this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
}
//! 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 == mtx::events::msg::SASMethods::Emoji)
req.short_authentication_string = {mtx::events::msg::SASMethods::Emoji};
else if (this->method == mtx::events::msg::SASMethods::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()));
send(req);
setState(WaitingForKeys);
//! 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};
send(req);
setState(WaitingForKeys);
}
//! accepts a verification
void
DeviceVerificationFlow::sendVerificationDone()
{
mtx::events::msg::KeyVerificationDone req;
//! 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);
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
req.relates_to = this->relation;
this->canonical_json = nlohmann::json(req);
send(req);
setState(WaitingForOtherToAccept);
}
//! sends a verification request
void
DeviceVerificationFlow::sendVerificationRequest()
{
mtx::events::msg::KeyVerificationRequest req;
req.from_device = http::client()->device_id();
req.methods = {mtx::events::msg::VerificationMethods::SASv1};
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
QDateTime currentTime = QDateTime::currentDateTimeUtc();
req.timestamp = (uint64_t)currentTime.toMSecsSinceEpoch();
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
req.to = this->toClient.to_string();
req.msgtype = "m.key.verification.request";
req.body = "User is requesting to verify keys with you. However, your client does "
"not support this method, so you will need to use the legacy method of "
"key verification.";
send(req);
setState(WaitingForOtherToAccept);
}
//! 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 received";
} 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";
} else if (error_code == DeviceVerificationFlow::Error::OutOfOrder) {
req.code = "m.unexpected_message";
req.reason = "received messages out of order";
this->error_ = error_code;
emit errorChanged();
this->setState(Failed);
//! sends the verification key
void
DeviceVerificationFlow::sendVerificationKey()
{
mtx::events::msg::KeyVerificationKey req;
req.key = this->sas->public_key();
mtx::events::msg::KeyVerificationMac
key_verification_mac(mtx::crypto::SAS *sas,
mtx::identifiers::User sender,
const std::string &senderDevice,
mtx::identifiers::User receiver,
const std::string &receiverDevice,
const std::string &transactionId,
std::map<std::string, std::string> keys)
{
mtx::events::msg::KeyVerificationMac req;
std::string info = "MATRIX_KEY_VERIFICATION_MAC" + sender.to_string() + senderDevice +
receiver.to_string() + receiverDevice + transactionId;
std::string key_list;
bool first = true;
for (const auto &[key_id, key] : keys) {
req.mac[key_id] = sas->calculate_mac(key, info + key_id);
if (!first)
key_list += ",";
key_list += key_id;
first = false;
req.keys = sas->calculate_mac(key_list, info + "KEY_IDS");
return req;
}
//! sends the mac of the keys
void
DeviceVerificationFlow::sendVerificationMac()
{
std::map<std::string, std::string> key_list;
key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519;
mtx::events::msg::KeyVerificationMac req =
key_verification_mac(sas.get(),
http::client()->user_id(),
http::client()->device_id(),
this->toClient,
this->deviceId.toStdString(),
this->transaction_id,
key_list);
send(req);
setState(WaitingForMac);
acceptDevice();
//! Completes the verification flow
void
DeviceVerificationFlow::acceptDevice()
{
if (!isMacVerified) {
setState(WaitingForMac);
} else if (state_ == WaitingForMac) {
cache::markDeviceVerified(this->toClient.to_string(), this->deviceId.toStdString());
this->sendVerificationDone();
setState(Success);
}
}
void
DeviceVerificationFlow::unverify()
{
cache::markDeviceUnverified(this->toClient.to_string(), this->deviceId.toStdString());
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::NewInRoomVerification(QObject *parent_,
TimelineModel *timelineModel_,
const mtx::events::msg::KeyVerificationRequest &msg,
QString other_user_,
QString event_id_)
QSharedPointer<DeviceVerificationFlow> flow(
new DeviceVerificationFlow(parent_,
Type::RoomMsg,
timelineModel_,
other_user_,
QString::fromStdString(msg.from_device)));
if (std::find(msg.methods.begin(),
msg.methods.end(),
mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) {
flow->cancelVerification(UnknownMethod);
return flow;
}
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
const mtx::events::msg::KeyVerificationRequest &msg,
QString other_user_,
QString txn_id_)
{
QSharedPointer<DeviceVerificationFlow> flow(new DeviceVerificationFlow(
parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device)));
flow->transaction_id = txn_id_.toStdString();
if (std::find(msg.methods.begin(),
msg.methods.end(),
mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) {
flow->cancelVerification(UnknownMethod);
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
const mtx::events::msg::KeyVerificationStart &msg,
QString other_user_,
QString txn_id_)
{
QSharedPointer<DeviceVerificationFlow> flow(new DeviceVerificationFlow(
parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device)));
flow->transaction_id = txn_id_.toStdString();
flow->handleStartMessage(msg, "");
return flow;
}
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::InitiateUserVerification(QObject *parent_,
TimelineModel *timelineModel_,
QString userid)
QSharedPointer<DeviceVerificationFlow> flow(
new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, ""));
flow->sender = true;
return flow;
}
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, QString userid, QString device)
{
QSharedPointer<DeviceVerificationFlow> flow(
new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device));
flow->sender = true;
flow->transaction_id = http::client()->generate_txn_id();
return flow;