matrixion/src/encryption/DeviceVerificationFlow.cpp

889 lines
34 KiB
C++
Raw Normal View History

2021-03-05 02:35:15 +03:00
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "DeviceVerificationFlow.h"
2020-07-29 00:55:47 +03:00
2020-07-05 19:03:27 +03:00
#include "Cache.h"
2020-10-27 19:45:28 +03:00
#include "Cache_p.h"
#include "ChatPage.h"
#include "Logging.h"
2020-10-27 19:45:28 +03:00
#include "Utils.h"
2020-07-29 00:55:47 +03:00
#include "timeline/TimelineModel.h"
#include <QDateTime>
#include <QTimer>
2020-08-29 11:07:51 +03:00
#include <iostream>
static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes
namespace msgs = mtx::events::msg;
2020-10-06 18:02:41 +03:00
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);
2020-08-18 08:59:02 +03:00
DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
DeviceVerificationFlow::Type flow_type,
TimelineModel *model,
QString userID,
2021-10-30 01:22:47 +03:00
std::vector<QString> deviceIds_)
: sender(false)
, type(flow_type)
2021-10-30 01:22:47 +03:00
, deviceIds(std::move(deviceIds_))
2020-08-18 08:59:02 +03:00
, model_(model)
{
2021-10-30 01:22:47 +03:00
if (deviceIds.size() == 1)
deviceId = deviceIds.front();
2021-09-18 01:22:33 +03:00
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);
cache::client()->query_keys(
user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("failed to query device keys: {},{}",
mtx::errors::to_string(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;
}
this->their_keys = res;
});
cache::client()->query_keys(
http::client()->user_id().to_string(),
[this](const UserKeyCache &res, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("failed to query device keys: {},{}",
mtx::errors::to_string(err->matrix_error.errcode),
static_cast<int>(err->status_code));
return;
}
if (res.master_keys.keys.empty())
return;
if (auto status = cache::verificationStatus(http::client()->user_id().to_string());
status && status->user_verified == crypto::Trust::Verified)
this->our_trusted_master_key = res.master_keys.keys.begin()->second;
});
if (model) {
connect(
2021-09-18 01:22:33 +03:00
this->model_, &TimelineModel::updateFlowEventId, this, [this](std::string event_id_) {
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]() {
nhlog::crypto()->info("verification: timeout");
if (state_ != Success && state_ != Failed)
this->cancelVerification(DeviceVerificationFlow::Error::Timeout);
});
connect(ChatPage::instance(),
&ChatPage::receivedDeviceVerificationStart,
this,
&DeviceVerificationFlow::handleStartMessage);
connect(ChatPage::instance(),
&ChatPage::receivedDeviceVerificationAccept,
this,
[this](const mtx::events::msg::KeyVerificationAccept &msg) {
nhlog::crypto()->info("verification: received accept");
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relations.references()) {
if (msg.relations.references() != 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;
} else {
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,
[this](const mtx::events::msg::KeyVerificationCancel &msg) {
nhlog::crypto()->info("verification: received cancel");
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relations.references()) {
if (msg.relations.references() != this->relation.event_id)
return;
}
error_ = User;
emit errorChanged();
setState(Failed);
});
connect(
ChatPage::instance(),
&ChatPage::receivedDeviceVerificationKey,
this,
[this](const mtx::events::msg::KeyVerificationKey &msg) {
nhlog::crypto()->info("verification: received key");
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relations.references()) {
if (msg.relations.references() != this->relation.event_id)
return;
}
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;
}
nhlog::ui()->info("Info is: '{}'", info);
if (this->sender == false) {
this->sendVerificationKey();
} else {
if (this->commitment != mtx::crypto::bin2base64_unpadded(mtx::crypto::sha256(
msg.key + this->canonical_json.dump()))) {
this->cancelVerification(DeviceVerificationFlow::Error::MismatchedCommitment);
return;
}
}
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,
[this](const mtx::events::msg::KeyVerificationMac &msg) {
nhlog::crypto()->info("verification: received mac");
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relations.references()) {
if (msg.relations.references() != this->relation.event_id)
return;
}
std::map<std::string, std::string> key_list;
std::string key_string;
for (const auto &mac : msg.mac) {
for (const auto &[deviceid, key] : their_keys.device_keys) {
(void)deviceid;
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;
}
}
if (msg.keys == macs.keys) {
mtx::requests::KeySignaturesUpload req;
if (utils::localUser().toStdString() == this->toClient.to_string()) {
// self verification, sign master key with device key, if we
// verified it
2020-10-06 18:02:41 +03:00
for (const auto &mac : msg.mac) {
2021-09-18 01:22:33 +03:00
if (their_keys.master_keys.keys.count(mac.first)) {
json j = their_keys.master_keys;
j.erase("signatures");
j.erase("unsigned");
mtx::crypto::CrossSigningKeys master_key = j;
master_key.signatures[utils::localUser().toStdString()]
["ed25519:" + http::client()->device_id()] =
olm::client()->sign_message(j.dump());
req.signatures[utils::localUser().toStdString()]
[master_key.keys.at(mac.first)] = master_key;
} else if (mac.first == "ed25519:" + this->deviceId.toStdString()) {
// Sign their device key with self signing key
auto device_id = this->deviceId.toStdString();
if (their_keys.device_keys.count(device_id)) {
json j = their_keys.device_keys.at(device_id);
j.erase("signatures");
j.erase("unsigned");
auto secret = cache::secret(
mtx::secret_storage::secrets::cross_signing_self_signing);
if (!secret)
continue;
auto ssk = mtx::crypto::PkSigning::from_seed(*secret);
mtx::crypto::DeviceKeys dev = j;
dev.signatures[utils::localUser().toStdString()]
["ed25519:" + ssk.public_key()] = ssk.sign(j.dump());
req.signatures[utils::localUser().toStdString()][device_id] = dev;
2020-10-08 20:29:42 +03:00
}
2021-09-18 01:22:33 +03:00
}
2020-10-06 18:02:41 +03:00
}
2021-09-18 01:22:33 +03:00
} else {
// Sign their master key with user signing key
for (const auto &mac : msg.mac) {
if (their_keys.master_keys.keys.count(mac.first)) {
json j = their_keys.master_keys;
j.erase("signatures");
j.erase("unsigned");
auto secret =
cache::secret(mtx::secret_storage::secrets::cross_signing_user_signing);
if (!secret)
continue;
auto usk = mtx::crypto::PkSigning::from_seed(*secret);
mtx::crypto::CrossSigningKeys master_key = j;
master_key.signatures[utils::localUser().toStdString()]
["ed25519:" + usk.public_key()] = usk.sign(j.dump());
req.signatures[toClient.to_string()][master_key.keys.at(mac.first)] =
master_key;
}
}
2021-09-18 01:22:33 +03:00
}
if (!req.signatures.empty()) {
http::client()->keys_signatures_upload(
req,
[](const mtx::responses::KeySignaturesUpload &res, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->error("failed to upload signatures: {},{}",
mtx::errors::to_string(err->matrix_error.errcode),
static_cast<int>(err->status_code));
}
2020-10-06 18:02:41 +03:00
2021-09-18 01:22:33 +03:00
for (const auto &[user_id, tmp] : res.errors)
for (const auto &[key_id, e] : tmp)
nhlog::net()->error("signature error for user {} and key "
"id {}: {}, {}",
user_id,
key_id,
mtx::errors::to_string(e.errcode),
e.error);
});
}
this->isMacVerified = true;
this->acceptDevice();
} else {
this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch);
}
});
2021-10-30 01:22:47 +03:00
connect(
ChatPage::instance(),
&ChatPage::receivedDeviceVerificationReady,
this,
[this](const mtx::events::msg::KeyVerificationReady &msg) {
nhlog::crypto()->info("verification: received ready");
if (!sender) {
if (msg.from_device != http::client()->device_id()) {
error_ = User;
emit errorChanged();
setState(Failed);
}
2020-07-17 23:16:30 +03:00
2021-10-30 01:22:47 +03:00
return;
}
2021-10-30 01:22:47 +03:00
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
if (this->deviceId.isEmpty() && this->deviceIds.size() > 1) {
auto from = QString::fromStdString(msg.from_device);
if (std::find(deviceIds.begin(), deviceIds.end(), from) != deviceIds.end()) {
mtx::events::msg::KeyVerificationCancel req{};
req.code = "m.user";
req.reason = "accepted by other device";
req.transaction_id = this->transaction_id;
mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationCancel> body;
for (const auto &d : this->deviceIds) {
if (d != from)
body[this->toClient][d.toStdString()] = req;
}
http::client()->send_to_device(
http::client()->generate_txn_id(), body, [](mtx::http::RequestErr err) {
if (err)
nhlog::net()->warn(
"failed to send verification to_device message: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
});
this->deviceId = from;
this->deviceIds = {from};
}
}
} else if (msg.relations.references()) {
if (msg.relations.references() != this->relation.event_id)
return;
else {
this->deviceId = QString::fromStdString(msg.from_device);
}
}
this->startVerificationRequest();
});
2021-09-18 01:22:33 +03:00
connect(ChatPage::instance(),
&ChatPage::receivedDeviceVerificationDone,
this,
[this](const mtx::events::msg::KeyVerificationDone &msg) {
2021-09-20 02:15:51 +03:00
nhlog::crypto()->info("verification: received done");
2021-09-18 01:22:33 +03:00
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relations.references()) {
if (msg.relations.references() != this->relation.event_id)
return;
}
nhlog::ui()->info("Flow done on other side");
});
2020-07-17 23:16:30 +03:00
2021-09-18 01:22:33 +03:00
timeout->start(TIMEOUT);
}
QString
DeviceVerificationFlow::state()
{
2021-09-18 01:22:33 +03:00
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:
2021-09-18 01:22:33 +03:00
sendVerificationRequest();
break;
case CompareEmoji:
case CompareNumber:
2021-09-18 01:22:33 +03:00
sendVerificationMac();
break;
case WaitingForKeys:
case WaitingForOtherToAccept:
case WaitingForMac:
case Success:
case Failed:
2021-09-18 01:22:33 +03:00
nhlog::db()->error("verification: Invalid state transition!");
break;
}
2021-09-18 01:22:33 +03:00
} 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;
}
2021-09-18 01:22:33 +03:00
}
}
QString
DeviceVerificationFlow::getUserId()
{
2021-09-18 01:22:33 +03:00
return QString::fromStdString(this->toClient.to_string());
}
QString
DeviceVerificationFlow::getDeviceId()
{
2021-09-18 01:22:33 +03:00
return this->deviceId;
}
bool
DeviceVerificationFlow::getSender()
{
2021-09-18 01:22:33 +03:00
return this->sender;
}
std::vector<int>
DeviceVerificationFlow::getSasList()
{
2021-09-18 01:22:33 +03:00
return this->sasList;
}
bool
DeviceVerificationFlow::isSelfVerification() const
{
2021-09-18 01:22:33 +03:00
return this->toClient.to_string() == http::client()->user_id().to_string();
}
void
DeviceVerificationFlow::setEventId(std::string event_id_)
2020-07-29 00:55:47 +03:00
{
2021-09-18 01:22:33 +03:00
this->relation.rel_type = mtx::common::RelationType::Reference;
this->relation.event_id = event_id_;
this->transaction_id = event_id_;
2020-07-29 00:55:47 +03:00
}
void
DeviceVerificationFlow::handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg,
std::string)
{
2021-09-18 01:22:33 +03:00
if (msg.transaction_id.has_value()) {
if (msg.transaction_id.value() != this->transaction_id)
return;
} else if (msg.relations.references()) {
if (msg.relations.references() != 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 {
2021-09-18 01:22:33 +03:00
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);
}
}
2021-09-18 01:22:33 +03:00
if (state_ != PromptStartVerification)
this->acceptVerificationRequest();
} else {
this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod);
}
2020-07-29 00:55:47 +03:00
}
//! accepts a verification
void
DeviceVerificationFlow::acceptVerificationRequest()
{
2021-09-18 01:22:33 +03:00
mtx::events::msg::KeyVerificationAccept req;
req.method = mtx::events::msg::VerificationMethods::SASv1;
req.key_agreement_protocol = "curve25519-hkdf-sha256";
req.hash = "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);
}
2020-06-23 01:05:56 +03:00
//! responds verification request
void
DeviceVerificationFlow::sendVerificationReady()
{
2021-09-18 01:22:33 +03:00
mtx::events::msg::KeyVerificationReady req;
2020-06-23 01:05:56 +03:00
2021-09-18 01:22:33 +03:00
req.from_device = http::client()->device_id();
req.methods = {mtx::events::msg::VerificationMethods::SASv1};
2020-07-17 23:16:30 +03:00
2021-09-18 01:22:33 +03:00
send(req);
setState(WaitingForKeys);
2020-06-23 01:05:56 +03:00
}
//! accepts a verification
void
DeviceVerificationFlow::sendVerificationDone()
{
2021-09-18 01:22:33 +03:00
mtx::events::msg::KeyVerificationDone req;
2020-06-23 01:05:56 +03:00
2021-09-18 01:22:33 +03:00
send(req);
2020-06-23 01:05:56 +03:00
}
//! starts the verification flow
void
DeviceVerificationFlow::startVerificationRequest()
{
2021-09-18 01:22:33 +03:00
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.relations.relations.push_back(this->relation);
// Set synthesized to surpress the nheko relation extensions
req.relations.synthesized = true;
this->canonical_json = nlohmann::json(req);
}
send(req);
setState(WaitingForOtherToAccept);
}
//! sends a verification request
void
DeviceVerificationFlow::sendVerificationRequest()
{
2021-09-18 01:22:33 +03:00
mtx::events::msg::KeyVerificationRequest req;
2021-09-18 01:22:33 +03:00
req.from_device = http::client()->device_id();
req.methods = {mtx::events::msg::VerificationMethods::SASv1};
2020-07-17 23:16:30 +03:00
2021-09-18 01:22:33 +03:00
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
QDateTime currentTime = QDateTime::currentDateTimeUtc();
2020-07-17 23:16:30 +03:00
2021-09-18 01:22:33 +03:00
req.timestamp = (uint64_t)currentTime.toMSecsSinceEpoch();
2020-07-17 23:16:30 +03:00
2021-09-18 01:22:33 +03:00
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
req.to = this->toClient.to_string();
req.msgtype = "m.key.verification.request";
// clang-format off
// clang-format < 12 is buggy on this
2021-09-18 01:22:33 +03:00
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.";
// clang-format on
2021-09-18 01:22:33 +03:00
}
2021-09-18 01:22:33 +03:00
send(req);
setState(WaitingForOtherToAccept);
}
//! cancels a verification flow
void
2020-06-26 12:40:37 +03:00
DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_code)
{
2021-09-18 01:22:33 +03:00
if (state_ == State::Success || state_ == State::Failed)
return;
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);
send(req);
}
//! sends the verification key
void
DeviceVerificationFlow::sendVerificationKey()
{
2021-09-18 01:22:33 +03:00
mtx::events::msg::KeyVerificationKey req;
2021-09-18 01:22:33 +03:00
req.key = this->sas->public_key();
2020-07-17 23:16:30 +03:00
2021-09-18 01:22:33 +03:00
send(req);
}
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)
{
2021-09-18 01:22:33 +03:00
mtx::events::msg::KeyVerificationMac req;
2021-09-18 01:22:33 +03:00
std::string info = "MATRIX_KEY_VERIFICATION_MAC" + sender.to_string() + senderDevice +
receiver.to_string() + receiverDevice + transactionId;
2021-09-18 01:22:33 +03:00
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);
2021-09-18 01:22:33 +03:00
if (!first)
key_list += ",";
key_list += key_id;
first = false;
}
2020-06-23 01:05:56 +03:00
2021-09-18 01:22:33 +03:00
req.keys = sas->calculate_mac(key_list, info + "KEY_IDS");
2021-09-18 01:22:33 +03:00
return req;
}
//! sends the mac of the keys
void
DeviceVerificationFlow::sendVerificationMac()
{
2021-09-18 01:22:33 +03:00
std::map<std::string, std::string> key_list;
key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519;
2021-09-18 01:22:33 +03:00
// send our master key, if we trust it
if (!this->our_trusted_master_key.empty())
key_list["ed25519:" + our_trusted_master_key] = our_trusted_master_key;
2021-09-18 01:22:33 +03:00
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);
2021-09-18 01:22:33 +03:00
send(req);
2021-09-18 01:22:33 +03:00
setState(WaitingForMac);
acceptDevice();
}
//! Completes the verification flow
void
DeviceVerificationFlow::acceptDevice()
{
2021-09-18 01:22:33 +03:00
if (!isMacVerified) {
setState(WaitingForMac);
} else if (state_ == WaitingForMac) {
cache::markDeviceVerified(this->toClient.to_string(), this->deviceId.toStdString());
this->sendVerificationDone();
setState(Success);
// Request secrets. We should probably check somehow, if a device knowns about the
// secrets.
if (utils::localUser().toStdString() == this->toClient.to_string() &&
(!cache::secret(mtx::secret_storage::secrets::cross_signing_self_signing) ||
!cache::secret(mtx::secret_storage::secrets::cross_signing_user_signing))) {
olm::request_cross_signing_keys();
}
2021-09-18 01:22:33 +03:00
}
}
void
DeviceVerificationFlow::unverify()
{
2021-09-18 01:22:33 +03:00
cache::markDeviceUnverified(this->toClient.to_string(), this->deviceId.toStdString());
2020-07-05 19:03:27 +03:00
2021-09-18 01:22:33 +03:00
emit refreshProfile();
2020-07-05 19:03:27 +03:00
}
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::NewInRoomVerification(QObject *parent_,
TimelineModel *timelineModel_,
const mtx::events::msg::KeyVerificationRequest &msg,
QString other_user_,
QString event_id_)
2020-07-05 19:03:27 +03:00
{
2021-09-18 01:22:33 +03:00
QSharedPointer<DeviceVerificationFlow> flow(
new DeviceVerificationFlow(parent_,
Type::RoomMsg,
timelineModel_,
other_user_,
2021-10-30 01:22:47 +03:00
{QString::fromStdString(msg.from_device)}));
2021-09-18 01:22:33 +03:00
flow->setEventId(event_id_.toStdString());
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_)
{
2021-09-18 01:22:33 +03:00
QSharedPointer<DeviceVerificationFlow> flow(new DeviceVerificationFlow(
2021-10-30 01:22:47 +03:00
parent_, Type::ToDevice, nullptr, other_user_, {QString::fromStdString(msg.from_device)}));
2021-09-18 01:22:33 +03:00
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);
}
2021-09-18 01:22:33 +03:00
return flow;
2020-07-05 19:03:27 +03:00
}
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_,
const mtx::events::msg::KeyVerificationStart &msg,
QString other_user_,
QString txn_id_)
{
2021-09-18 01:22:33 +03:00
QSharedPointer<DeviceVerificationFlow> flow(new DeviceVerificationFlow(
2021-10-30 01:22:47 +03:00
parent_, Type::ToDevice, nullptr, other_user_, {QString::fromStdString(msg.from_device)}));
2021-09-18 01:22:33 +03:00
flow->transaction_id = txn_id_.toStdString();
2020-07-05 19:03:27 +03:00
2021-09-18 01:22:33 +03:00
flow->handleStartMessage(msg, "");
2021-09-18 01:22:33 +03:00
return flow;
}
QSharedPointer<DeviceVerificationFlow>
DeviceVerificationFlow::InitiateUserVerification(QObject *parent_,
TimelineModel *timelineModel_,
QString userid)
2020-07-05 19:03:27 +03:00
{
2021-09-18 01:22:33 +03:00
QSharedPointer<DeviceVerificationFlow> flow(
2021-10-30 01:22:47 +03:00
new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, {}));
2021-09-18 01:22:33 +03:00
flow->sender = true;
return flow;
}
QSharedPointer<DeviceVerificationFlow>
2021-10-30 01:22:47 +03:00
DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_,
QString userid,
std::vector<QString> devices)
{
2021-10-30 01:22:47 +03:00
assert(!devices.empty());
2021-09-18 01:22:33 +03:00
QSharedPointer<DeviceVerificationFlow> flow(
2021-10-30 01:22:47 +03:00
new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, devices));
2020-07-05 19:03:27 +03:00
2021-09-18 01:22:33 +03:00
flow->sender = true;
flow->transaction_id = http::client()->generate_txn_id();
2021-09-18 01:22:33 +03:00
return flow;
}