// SPDX-FileCopyrightText: Nheko Contributors // // SPDX-License-Identifier: GPL-3.0-or-later #include "DeviceVerificationFlow.h" #include #include #include #include #include #include "Cache.h" #include "Cache_p.h" #include "ChatPage.h" #include "Logging.h" #include "MatrixClient.h" #include "Utils.h" #include "timeline/TimelineModel.h" static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes static constexpr std::string_view mac_method_alg_v1 = "hkdf-hmac-sha256"; static constexpr std::string_view mac_method_alg_v2 = "hkdf-hmac-sha256.v2"; DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow::Type flow_type, TimelineModel *model, const QString &userID, const std::vector &deviceIds_) : sender(false) , type(flow_type) , deviceIds(std::move(deviceIds_)) , model_(model) { nhlog::crypto()->debug("CREATING NEW FLOW, {}, {}", static_cast(flow_type), (void *)this); if (deviceIds.size() == 1) deviceId = deviceIds.front(); 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(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(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(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(this->model_, &TimelineModel::updateFlowEventId, this, [this](const 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) { if (state_ == Failed || state_ == Success) return; nhlog::crypto()->info("verification: received accept with mac methods {}", fmt::join(msg.message_authentication_code, ", ")); 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 == mac_method_alg_v1 || msg.message_authentication_code == mac_method_alg_v2)) { 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, {} : {}", msg.code, msg.reason); 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) { if (state_ == Failed || state_ == Success) return; nhlog::crypto()->info( "verification: received key, sender {}, state {}", sender, state().toStdString()); 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 && state_ != WaitingForKeys) { 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))) { 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) { if (state_ == Failed || state_ == Success) return; 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 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 = sas->calculate_mac(mac_method, 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 for (const auto &mac : msg.mac) { if (their_keys.master_keys.keys.count(mac.first)) { nlohmann::json j = their_keys.master_keys; j.erase("signatures"); j.erase("unsigned"); mtx::crypto::CrossSigningKeys master_key = j.get(); 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)) { nlohmann::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.get(); dev.signatures[utils::localUser().toStdString()] ["ed25519:" + ssk.public_key()] = ssk.sign(j.dump()); req.signatures[utils::localUser().toStdString()][device_id] = dev; } } } } else { // Sign their master key with user signing key for (const auto &mac : msg.mac) { if (their_keys.master_keys.keys.count(mac.first)) { nlohmann::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.get(); 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; } } } if (!req.signatures.empty()) { nhlog::crypto()->debug("Signatures to send: {}", nlohmann::json(req).dump(2)); 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(err->status_code)); } // MSVC bug, error C3493: 'key_id' cannot be implicitly captured because no // default capture mode has been specified // 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); for (const auto &error : res.errors) { const auto &user_id = error.first; for (const auto &key_error : error.second) { const auto &key_id = key_error.first; const auto &e = key_error.second; 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); } }); connect( ChatPage::instance(), &ChatPage::receivedDeviceVerificationReady, this, [this](const mtx::events::msg::KeyVerificationReady &msg) { if (state_ == Failed || state_ == Success) return; nhlog::crypto()->info("verification: received ready {}", (void *)this); if (!sender) { if (msg.from_device != this->deviceId.toStdString()) { nhlog::crypto()->debug("Accepted by {}, we are communicating with {}", msg.from_device, this->deviceId.toStdString()); cancelVerification(AcceptedOnOtherDevice); } return; } 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 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(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); } } else { return; } nhlog::crypto()->info("verification: received ready sending start {}", (void *)this); this->startVerificationRequest(); }); connect(ChatPage::instance(), &ChatPage::receivedDeviceVerificationDone, this, [this](const mtx::events::msg::KeyVerificationDone &msg) { nhlog::crypto()->info("verification: received done"); 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"); }); timeout->start(TIMEOUT); } QString DeviceVerificationFlow::state() { switch (state_) { case PromptStartVerification: return QStringLiteral("PromptStartVerification"); case CompareEmoji: return QStringLiteral("CompareEmoji"); case CompareNumber: return QStringLiteral("CompareNumber"); case WaitingForKeys: return QStringLiteral("WaitingForKeys"); case WaitingForOtherToAccept: return QStringLiteral("WaitingForOtherToAccept"); case WaitingForMac: return QStringLiteral("WaitingForMac"); case Success: return QStringLiteral("Success"); case Failed: return QStringLiteral("Failed"); default: return QString(); } } 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.empty()) 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 DeviceVerificationFlow::getSasList() { return this->sasList; } bool DeviceVerificationFlow::isSelfVerification() const { return this->toClient.to_string() == http::client()->user_id().to_string(); } void DeviceVerificationFlow::setEventId(const 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) { if (state_ == Failed || state_ == Success) return; 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; } else { return; } if (state_ == Failed) return; if (msg.from_device != this->deviceId.toStdString()) { cancelVerification(AcceptedOnOtherDevice); return; } nhlog::crypto()->info("verification: received start with mac methods {}", fmt::join(msg.message_authentication_codes, ", ")); // TODO(Nico): Replace with contains once we use C++23 if (std::ranges::count(msg.key_agreement_protocols, "curve25519-hkdf-sha256") && std::ranges::count(msg.hashes, "sha256")) { if (std::ranges::count(msg.message_authentication_codes, mac_method_alg_v2)) { this->mac_method = mac_method_alg_v2; } else if (std::ranges::count(msg.message_authentication_codes, mac_method_alg_v1)) { this->mac_method = mac_method_alg_v1; } else { this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); return; } if (std::ranges::count(msg.short_authentication_string, mtx::events::msg::SASMethods::Emoji)) { this->method = mtx::events::msg::SASMethods::Emoji; } else if (std::ranges::count(msg.short_authentication_string, mtx::events::msg::SASMethods::Decimal)) { this->method = mtx::events::msg::SASMethods::Decimal; } else { this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); return; } if (!sender) this->canonical_json = nlohmann::json(msg).dump(); else { // resolve glare if (std::tuple(this->toClient.to_string(), this->deviceId.toStdString()) < std::tuple(utils::localUser().toStdString(), http::client()->device_id())) { // treat this as if the user with the smaller mxid or smaller deviceid (if the mxid // was the same) was the sender of "start" this->canonical_json = nlohmann::json(msg).dump(); this->sender = false; } if (msg.method != mtx::events::msg::VerificationMethods::SASv1) { cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); return; } } // If we didn't send "start", accept the verification (otherwise wait for the other side to // accept if (state_ != PromptStartVerification && !sender) this->acceptVerificationRequest(); } else { this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); } } //! accepts a verification void DeviceVerificationFlow::acceptVerificationRequest() { if (state_ == Failed || state_ == Success) return; if (acceptSent) return; if (mac_method.empty()) { nhlog::crypto()->critical("Ignoring start without mac method set!"); return; } else { nhlog::crypto()->debug("Accepted verification using mac_method {}", mac_method); } acceptSent = true; 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 = this->mac_method; 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)); 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; send(req); } //! starts the verification flow void DeviceVerificationFlow::startVerificationRequest() { if (startSent) return; startSent = true; 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 = {std::string(mac_method_alg_v1), std::string(mac_method_alg_v2)}; req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal, mtx::events::msg::SASMethods::Emoji}; if (this->type == DeviceVerificationFlow::Type::ToDevice) { mtx::requests::ToDeviceMessages body; req.transaction_id = this->transaction_id; this->canonical_json = nlohmann::json(req).dump(); } 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).dump(); } 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"; // clang-format off // clang-format < 12 is buggy on this 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 } send(req); setState(WaitingForOtherToAccept); } //! cancels a verification flow void DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_code) { 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; this->setState(Failed); emit errorChanged(); // Don't cancel if the user accepted the request elsewhere, instead just silently stop if (error_code != AcceptedOnOtherDevice) send(req); } //! sends the verification key void DeviceVerificationFlow::sendVerificationKey() { if (keySent) return; keySent = true; mtx::events::msg::KeyVerificationKey req; req.key = this->sas->public_key(); send(req); } //! sends the mac of the keys void DeviceVerificationFlow::sendVerificationMac() { if (macSent) return; macSent = true; nhlog::crypto()->debug("Sending mac using mac_method {}", mac_method); std::map key_list; key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519; // 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; mtx::events::msg::KeyVerificationMac req = sas->calculate_mac(mac_method, 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); // 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(); } } } void DeviceVerificationFlow::unverify() { cache::markDeviceUnverified(this->toClient.to_string(), this->deviceId.toStdString()); emit refreshProfile(); } QSharedPointer DeviceVerificationFlow::NewInRoomVerification(QObject *parent_, TimelineModel *timelineModel_, const mtx::events::msg::KeyVerificationRequest &msg, const QString &other_user_, const QString &event_id_) { QSharedPointer flow( new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, other_user_, {QString::fromStdString(msg.from_device)})); 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::NewToDeviceVerification(QObject *parent_, const mtx::events::msg::KeyVerificationRequest &msg, const QString &other_user_, const QString &txn_id_) { QSharedPointer 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); } return flow; } QSharedPointer DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, const mtx::events::msg::KeyVerificationStart &msg, const QString &other_user_, const QString &txn_id_) { QSharedPointer 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::InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, const QString &userid) { QSharedPointer flow( new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, {})); flow->sender = true; return flow; } QSharedPointer DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, const QString &userid, const std::vector &devices) { assert(!devices.empty()); QSharedPointer flow( new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, devices)); flow->sender = true; flow->transaction_id = http::client()->generate_txn_id(); return flow; } template void DeviceVerificationFlow::send(T msg) { if (this->type == DeviceVerificationFlow::Type::ToDevice) { mtx::requests::ToDeviceMessages body; msg.transaction_id = this->transaction_id; for (const auto &d : deviceIds) body[this->toClient][d.toStdString()] = msg; 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(err->status_code)); }); } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { if constexpr (!std::is_same_v) { msg.relations.relations.push_back(this->relation); // Set synthesized to surpress the nheko relation extensions msg.relations.synthesized = true; } (model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type); } nhlog::net()->debug("Sent verification step: {} in state: {}", mtx::events::to_string(mtx::events::to_device_content_to_type), state().toStdString()); } #include "moc_DeviceVerificationFlow.cpp"