From 550c80525a1633edc983a7fe0d1dae11220cb35f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 14 Oct 2021 22:53:11 +0200 Subject: Move voip and encryption stuff into their own directories --- src/encryption/DeviceVerificationFlow.cpp | 849 ++++++++++++++++++++++++++++++ 1 file changed, 849 insertions(+) create mode 100644 src/encryption/DeviceVerificationFlow.cpp (limited to 'src/encryption/DeviceVerificationFlow.cpp') diff --git a/src/encryption/DeviceVerificationFlow.cpp b/src/encryption/DeviceVerificationFlow.cpp new file mode 100644 index 00000000..2481d4f9 --- /dev/null +++ b/src/encryption/DeviceVerificationFlow.cpp @@ -0,0 +1,849 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "DeviceVerificationFlow.h" + +#include "Cache.h" +#include "Cache_p.h" +#include "ChatPage.h" +#include "Logging.h" +#include "Utils.h" +#include "timeline/TimelineModel.h" + +#include +#include +#include + +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 keys); + +DeviceVerificationFlow::DeviceVerificationFlow(QObject *, + DeviceVerificationFlow::Type flow_type, + TimelineModel *model, + QString userID, + QString deviceId_) + : sender(false) + , type(flow_type) + , deviceId(deviceId_) + , model_(model) +{ + 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](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 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 + 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"); + 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; + } + } + } + } 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; + } + } + } + + 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(err->status_code)); + } + + 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); + } + }); + + 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); + } + + 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 { + this->deviceId = QString::fromStdString(msg.from_device); + } + } + 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 "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 +DeviceVerificationFlow::getSasList() +{ + return this->sasList; +} + +bool +DeviceVerificationFlow::isSelfVerification() const +{ + return this->toClient.to_string() == http::client()->user_id().to_string(); +} + +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) +{ + 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 { + 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.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); +} +//! 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() +{ + 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 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() +{ + 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) +{ + 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() +{ + mtx::events::msg::KeyVerificationKey req; + + req.key = this->sas->public_key(); + + 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 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 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 = 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); + + // 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, + QString other_user_, + 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, + QString other_user_, + 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, + QString other_user_, + 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_, + QString userid) +{ + QSharedPointer flow( + new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, "")); + flow->sender = true; + return flow; +} +QSharedPointer +DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, QString userid, QString device) +{ + QSharedPointer flow( + new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device)); + + flow->sender = true; + flow->transaction_id = http::client()->generate_txn_id(); + + return flow; +} -- cgit 1.5.1 From 6793bdf3fd30096e018bb479ae8b52854209b108 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 17 Oct 2021 17:20:51 +0200 Subject: lint --- resources/qml/SelfVerificationCheck.qml | 4 ++-- src/encryption/DeviceVerificationFlow.cpp | 4 ++-- src/encryption/VerificationManager.h | 1 - src/notifications/ManagerWin.cpp | 12 +++++++++--- src/voip/WebRTCSession.cpp | 6 +++++- 5 files changed, 18 insertions(+), 9 deletions(-) (limited to 'src/encryption/DeviceVerificationFlow.cpp') diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml index c5eee669..a7a9e41a 100644 --- a/resources/qml/SelfVerificationCheck.qml +++ b/resources/qml/SelfVerificationCheck.qml @@ -237,8 +237,8 @@ Item { console.log("STATUS CHANGED: " + SelfVerificationStatus.status); if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) bootstrapCrosssigning.open(); - else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) - verifyMasterKey.open(); +// else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) +// verifyMasterKey.open(); } diff --git a/src/encryption/DeviceVerificationFlow.cpp b/src/encryption/DeviceVerificationFlow.cpp index 2481d4f9..ea5a060d 100644 --- a/src/encryption/DeviceVerificationFlow.cpp +++ b/src/encryption/DeviceVerificationFlow.cpp @@ -631,8 +631,8 @@ DeviceVerificationFlow::sendVerificationRequest() 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."; + "not support this method, so you will need to use the legacy method of " + "key verification."; } send(req); diff --git a/src/encryption/VerificationManager.h b/src/encryption/VerificationManager.h index e00ddc10..d6a39ccf 100644 --- a/src/encryption/VerificationManager.h +++ b/src/encryption/VerificationManager.h @@ -45,4 +45,3 @@ private: bool isInitialSync_ = false; RoomlistModel *rooms_; }; - diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 4376e4d8..556ca028 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -100,10 +100,16 @@ NotificationsManager::systemPostNotification(const QString &line1, WinToast::instance()->showToast(templ, new CustomHandler()); } -void NotificationsManager::actionInvoked(uint, QString) {} -void NotificationsManager::notificationReplied(uint, QString) {} +void +NotificationsManager::actionInvoked(uint, QString) +{} +void +NotificationsManager::notificationReplied(uint, QString) +{} -void NotificationsManager::notificationClosed(uint, uint) {} +void +NotificationsManager::notificationClosed(uint, uint) +{} void NotificationsManager::removeNotification(const QString &, const QString &) diff --git a/src/voip/WebRTCSession.cpp b/src/voip/WebRTCSession.cpp index 801a365c..8e0a9f79 100644 --- a/src/voip/WebRTCSession.cpp +++ b/src/voip/WebRTCSession.cpp @@ -1114,7 +1114,11 @@ WebRTCSession::haveLocalPiP() const return false; } -bool WebRTCSession::createOffer(webrtc::CallType, uint32_t) { return false; } +bool +WebRTCSession::createOffer(webrtc::CallType, uint32_t) +{ + return false; +} bool WebRTCSession::acceptOffer(const std::string &) -- cgit 1.5.1 From 2981f71d2268087b04f6dfef2fd0d42a4f786b4d Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 17 Oct 2021 17:33:59 +0200 Subject: lint using clang11 --- src/encryption/DeviceVerificationFlow.cpp | 4 ++-- src/notifications/ManagerWin.cpp | 12 +++--------- src/voip/WebRTCSession.cpp | 6 +----- 3 files changed, 6 insertions(+), 16 deletions(-) (limited to 'src/encryption/DeviceVerificationFlow.cpp') diff --git a/src/encryption/DeviceVerificationFlow.cpp b/src/encryption/DeviceVerificationFlow.cpp index ea5a060d..2481d4f9 100644 --- a/src/encryption/DeviceVerificationFlow.cpp +++ b/src/encryption/DeviceVerificationFlow.cpp @@ -631,8 +631,8 @@ DeviceVerificationFlow::sendVerificationRequest() 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."; + "not support this method, so you will need to use the legacy method of " + "key verification."; } send(req); diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 556ca028..4376e4d8 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -100,16 +100,10 @@ NotificationsManager::systemPostNotification(const QString &line1, WinToast::instance()->showToast(templ, new CustomHandler()); } -void -NotificationsManager::actionInvoked(uint, QString) -{} -void -NotificationsManager::notificationReplied(uint, QString) -{} +void NotificationsManager::actionInvoked(uint, QString) {} +void NotificationsManager::notificationReplied(uint, QString) {} -void -NotificationsManager::notificationClosed(uint, uint) -{} +void NotificationsManager::notificationClosed(uint, uint) {} void NotificationsManager::removeNotification(const QString &, const QString &) diff --git a/src/voip/WebRTCSession.cpp b/src/voip/WebRTCSession.cpp index 8e0a9f79..801a365c 100644 --- a/src/voip/WebRTCSession.cpp +++ b/src/voip/WebRTCSession.cpp @@ -1114,11 +1114,7 @@ WebRTCSession::haveLocalPiP() const return false; } -bool -WebRTCSession::createOffer(webrtc::CallType, uint32_t) -{ - return false; -} +bool WebRTCSession::createOffer(webrtc::CallType, uint32_t) { return false; } bool WebRTCSession::acceptOffer(const std::string &) -- cgit 1.5.1 From 5688b2647ee686559303203a394bad1a92a744b0 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 30 Oct 2021 00:22:47 +0200 Subject: Add self verification after login --- resources/qml/SelfVerificationCheck.qml | 51 +++++++++++++-- src/ChatPage.cpp | 62 +++++++++++++++++- src/encryption/DeviceVerificationFlow.cpp | 102 ++++++++++++++++++++---------- src/encryption/DeviceVerificationFlow.h | 15 +++-- src/encryption/Olm.cpp | 69 ++++++++++++-------- src/encryption/SelfVerificationStatus.cpp | 47 +++++++++++++- src/encryption/SelfVerificationStatus.h | 5 ++ src/encryption/VerificationManager.cpp | 11 +++- src/encryption/VerificationManager.h | 1 + 9 files changed, 288 insertions(+), 75 deletions(-) (limited to 'src/encryption/DeviceVerificationFlow.cpp') diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml index a7a9e41a..0242a149 100644 --- a/resources/qml/SelfVerificationCheck.qml +++ b/resources/qml/SelfVerificationCheck.qml @@ -93,6 +93,7 @@ Item { columns: 2 rowSpacing: 0 columnSpacing: 0 + z: 1 Label { Layout.margins: Nheko.paddingMedium @@ -220,15 +221,53 @@ Item { MainWindowDialog { id: verifyMasterKey - onAccepted: SelfVerificationStatus.verifyMasterKey() + standardButtons: Dialog.Cancel GridLayout { id: masterGrid width: verifyMasterKey.useableWidth - columns: 2 - rowSpacing: 0 - columnSpacing: 0 + columns: 1 + z: 1 + + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignHCenter + //Layout.columnSpan: 2 + font.pointSize: fontMetrics.font.pointSize * 2 + text: qsTr("Activate Encryption") + color: Nheko.colors.text + wrapMode: Text.Wrap + } + + Label { + Layout.margins: Nheko.paddingMedium + Layout.alignment: Qt.AlignLeft + //Layout.columnSpan: 2 + Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 + text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.") + color: Nheko.colors.text + wrapMode: Text.Wrap + } + + FlatButton { + Layout.alignment: Qt.AlignHCenter + text: qsTr("verify") + onClicked: { + console.log("AAAAA"); + SelfVerificationStatus.verifyMasterKey(); + verifyMasterKey.close(); + } + } + FlatButton { + visible: SelfVerificationStatus.hasSSSS + Layout.alignment: Qt.AlignHCenter + text: qsTr("enter passphrase") + onClicked: { + SelfVerificationStatus.verifyMasterKeyWithPassphrase() + verifyMasterKey.close(); + } + } } } @@ -237,8 +276,8 @@ Item { console.log("STATUS CHANGED: " + SelfVerificationStatus.status); if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) bootstrapCrosssigning.open(); -// else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) -// verifyMasterKey.open(); + else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) + verifyMasterKey.open(); } diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 9239e342..d262387c 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -1131,11 +1131,71 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio return; } + auto deviceKeys = cache::client()->userKeys(http::client()->user_id().to_string()); + mtx::requests::KeySignaturesUpload req; + for (const auto &[secretName, encryptedSecret] : secrets) { auto decrypted = mtx::crypto::decrypt(encryptedSecret, *decryptionKey, secretName); - if (!decrypted.empty()) + if (!decrypted.empty()) { cache::storeSecret(secretName, decrypted); + + if (deviceKeys && + secretName == mtx::secret_storage::secrets::cross_signing_self_signing) { + auto myKey = deviceKeys->device_keys.at(http::client()->device_id()); + if (myKey.user_id == http::client()->user_id().to_string() && + myKey.device_id == http::client()->device_id() && + myKey.keys["ed25519:" + http::client()->device_id()] == + olm::client()->identity_keys().ed25519 && + myKey.keys["curve25519:" + http::client()->device_id()] == + olm::client()->identity_keys().curve25519) { + json j = myKey; + j.erase("signatures"); + j.erase("unsigned"); + + auto ssk = mtx::crypto::PkSigning::from_seed(decrypted); + myKey.signatures[http::client()->user_id().to_string()] + ["ed25519:" + ssk.public_key()] = ssk.sign(j.dump()); + req.signatures[http::client()->user_id().to_string()] + [http::client()->device_id()] = myKey; + } + } else if (deviceKeys && + secretName == mtx::secret_storage::secrets::cross_signing_master) { + auto mk = mtx::crypto::PkSigning::from_seed(decrypted); + + if (deviceKeys->master_keys.user_id == http::client()->user_id().to_string() && + deviceKeys->master_keys.keys["ed25519:" + mk.public_key()] == mk.public_key()) { + json j = deviceKeys->master_keys; + j.erase("signatures"); + j.erase("unsigned"); + mtx::crypto::CrossSigningKeys master_key = j; + master_key.signatures[http::client()->user_id().to_string()] + ["ed25519:" + http::client()->device_id()] = + olm::client()->sign_message(j.dump()); + req.signatures[http::client()->user_id().to_string()][mk.public_key()] = + master_key; + } + } + } } + + 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(err->status_code)); + } + + 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); + }); } void diff --git a/src/encryption/DeviceVerificationFlow.cpp b/src/encryption/DeviceVerificationFlow.cpp index 2481d4f9..f05d5c9f 100644 --- a/src/encryption/DeviceVerificationFlow.cpp +++ b/src/encryption/DeviceVerificationFlow.cpp @@ -32,12 +32,15 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow::Type flow_type, TimelineModel *model, QString userID, - QString deviceId_) + std::vector deviceIds_) : sender(false) , type(flow_type) - , deviceId(deviceId_) + , deviceIds(std::move(deviceIds_)) , model_(model) { + if (deviceIds.size() == 1) + deviceId = deviceIds.front(); + timeout = new QTimer(this); timeout->setSingleShot(true); this->sas = olm::client()->sas_init(); @@ -346,33 +349,62 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, } }); - 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); - } + 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); + } - return; - } + 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 { - this->deviceId = QString::fromStdString(msg.from_device); - } - } - this->startVerificationRequest(); - }); + 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); + } + } + this->startVerificationRequest(); + }); connect(ChatPage::instance(), &ChatPage::receivedDeviceVerificationDone, @@ -782,7 +814,7 @@ DeviceVerificationFlow::NewInRoomVerification(QObject *parent_, Type::RoomMsg, timelineModel_, other_user_, - QString::fromStdString(msg.from_device))); + {QString::fromStdString(msg.from_device)})); flow->setEventId(event_id_.toStdString()); @@ -801,7 +833,7 @@ DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, QString txn_id_) { QSharedPointer flow(new DeviceVerificationFlow( - parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device))); + parent_, Type::ToDevice, nullptr, other_user_, {QString::fromStdString(msg.from_device)})); flow->transaction_id = txn_id_.toStdString(); if (std::find(msg.methods.begin(), @@ -819,7 +851,7 @@ DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, QString txn_id_) { QSharedPointer flow(new DeviceVerificationFlow( - parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device))); + parent_, Type::ToDevice, nullptr, other_user_, {QString::fromStdString(msg.from_device)})); flow->transaction_id = txn_id_.toStdString(); flow->handleStartMessage(msg, ""); @@ -832,15 +864,19 @@ DeviceVerificationFlow::InitiateUserVerification(QObject *parent_, QString userid) { QSharedPointer flow( - new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, "")); + new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, {})); flow->sender = true; return flow; } QSharedPointer -DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, QString userid, QString device) +DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, + QString userid, + std::vector devices) { + assert(!devices.empty()); + QSharedPointer flow( - new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device)); + new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, devices)); flow->sender = true; flow->transaction_id = http::client()->generate_txn_id(); diff --git a/src/encryption/DeviceVerificationFlow.h b/src/encryption/DeviceVerificationFlow.h index f71fa337..55713def 100644 --- a/src/encryption/DeviceVerificationFlow.h +++ b/src/encryption/DeviceVerificationFlow.h @@ -120,9 +120,8 @@ public: QString txn_id_); static QSharedPointer InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid); - static QSharedPointer InitiateDeviceVerification(QObject *parent, - QString userid, - QString device); + static QSharedPointer + InitiateDeviceVerification(QObject *parent, QString userid, std::vector devices); // getters QString state(); @@ -161,7 +160,7 @@ private: DeviceVerificationFlow::Type flow_type, TimelineModel *model, QString userID, - QString deviceId_); + std::vector deviceIds_); void setState(State state) { if (state != state_) { @@ -196,6 +195,7 @@ private: Type type; mtx::identifiers::User toClient; QString deviceId; + std::vector deviceIds; // public part of our master key, when trusted or empty std::string our_trusted_master_key; @@ -222,11 +222,12 @@ private: { if (this->type == DeviceVerificationFlow::Type::ToDevice) { mtx::requests::ToDeviceMessages body; - msg.transaction_id = this->transaction_id; - body[this->toClient][deviceId.toStdString()] = msg; + msg.transaction_id = this->transaction_id; + for (const auto &d : deviceIds) + body[this->toClient][d.toStdString()] = msg; http::client()->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { + 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, diff --git a/src/encryption/Olm.cpp b/src/encryption/Olm.cpp index 14c97984..01a16ba7 100644 --- a/src/encryption/Olm.cpp +++ b/src/encryption/Olm.cpp @@ -1540,6 +1540,7 @@ request_cross_signing_keys() }); }; + request(mtx::secret_storage::secrets::cross_signing_master); request(mtx::secret_storage::secrets::cross_signing_self_signing); request(mtx::secret_storage::secrets::cross_signing_user_signing); request(mtx::secret_storage::secrets::megolm_backup_v1); @@ -1574,36 +1575,52 @@ download_cross_signing_keys() backup_key = secret; http::client()->secret_storage_secret( - secrets::cross_signing_self_signing, - [backup_key](Secret secret, mtx::http::RequestErr err) { - std::optional self_signing_key; + secrets::cross_signing_master, [backup_key](Secret secret, mtx::http::RequestErr err) { + std::optional master_key; if (!err) - self_signing_key = secret; + master_key = secret; http::client()->secret_storage_secret( - secrets::cross_signing_user_signing, - [backup_key, self_signing_key](Secret secret, mtx::http::RequestErr err) { - std::optional user_signing_key; + secrets::cross_signing_self_signing, + [backup_key, master_key](Secret secret, mtx::http::RequestErr err) { + std::optional self_signing_key; if (!err) - user_signing_key = secret; - - std::map> - secrets; - - if (backup_key && !backup_key->encrypted.empty()) - secrets[backup_key->encrypted.begin()->first][secrets::megolm_backup_v1] = - backup_key->encrypted.begin()->second; - if (self_signing_key && !self_signing_key->encrypted.empty()) - secrets[self_signing_key->encrypted.begin()->first] - [secrets::cross_signing_self_signing] = - self_signing_key->encrypted.begin()->second; - if (user_signing_key && !user_signing_key->encrypted.empty()) - secrets[user_signing_key->encrypted.begin()->first] - [secrets::cross_signing_user_signing] = - user_signing_key->encrypted.begin()->second; - - for (const auto &[key, secrets] : secrets) - unlock_secrets(key, secrets); + self_signing_key = secret; + + http::client()->secret_storage_secret( + secrets::cross_signing_user_signing, + [backup_key, self_signing_key, master_key](Secret secret, + mtx::http::RequestErr err) { + std::optional user_signing_key; + if (!err) + user_signing_key = secret; + + std::map> + secrets; + + if (backup_key && !backup_key->encrypted.empty()) + secrets[backup_key->encrypted.begin()->first] + [secrets::megolm_backup_v1] = + backup_key->encrypted.begin()->second; + + if (master_key && !master_key->encrypted.empty()) + secrets[master_key->encrypted.begin()->first] + [secrets::cross_signing_master] = + master_key->encrypted.begin()->second; + + if (self_signing_key && !self_signing_key->encrypted.empty()) + secrets[self_signing_key->encrypted.begin()->first] + [secrets::cross_signing_self_signing] = + self_signing_key->encrypted.begin()->second; + + if (user_signing_key && !user_signing_key->encrypted.empty()) + secrets[user_signing_key->encrypted.begin()->first] + [secrets::cross_signing_user_signing] = + user_signing_key->encrypted.begin()->second; + + for (const auto &[key, secrets] : secrets) + unlock_secrets(key, secrets); + }); }); }); }); diff --git a/src/encryption/SelfVerificationStatus.cpp b/src/encryption/SelfVerificationStatus.cpp index d75a2109..d4be4442 100644 --- a/src/encryption/SelfVerificationStatus.cpp +++ b/src/encryption/SelfVerificationStatus.cpp @@ -5,10 +5,12 @@ #include "SelfVerificationStatus.h" #include "Cache_p.h" +#include "ChatPage.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" #include "Olm.h" +#include "timeline/TimelineViewManager.h" #include "ui/UIA.h" #include @@ -196,6 +198,35 @@ void SelfVerificationStatus::verifyMasterKey() { nhlog::db()->info("Clicked verify master key"); + + const auto this_user = http::client()->user_id().to_string(); + + auto keys = cache::client()->userKeys(this_user); + const auto &sigs = keys->master_keys.signatures[this_user]; + + std::vector devices; + for (const auto &[dev, sig] : sigs) { + (void)sig; + + auto d = QString::fromStdString(dev); + if (d.startsWith("ed25519:")) { + d.remove("ed25519:"); + + if (keys->device_keys.count(d.toStdString())) + devices.push_back(std::move(d)); + } + } + + if (!devices.empty()) + ChatPage::instance()->timelineManager()->verificationManager()->verifyOneOfDevices( + QString::fromStdString(this_user), std::move(devices)); +} + +void +SelfVerificationStatus::verifyMasterKeyWithPassphrase() +{ + nhlog::db()->info("Clicked verify master key with passphrase"); + olm::download_cross_signing_keys(); } void @@ -207,9 +238,15 @@ SelfVerificationStatus::verifyUnverifiedDevices() void SelfVerificationStatus::invalidate() { + using namespace mtx::secret_storage; + nhlog::db()->info("Invalidating self verification status"); + this->hasSSSS_ = false; + emit hasSSSSChanged(); + auto keys = cache::client()->userKeys(http::client()->user_id().to_string()); - if (!keys) { + if (!keys || keys->device_keys.find(http::client()->device_id()) == keys->device_keys.end()) { + cache::client()->markUserKeysOutOfDate({http::client()->user_id().to_string()}); cache::client()->query_keys(http::client()->user_id().to_string(), [](const UserKeyCache &, mtx::http::RequestErr) {}); return; @@ -223,6 +260,14 @@ SelfVerificationStatus::invalidate() return; } + http::client()->secret_storage_secret(secrets::cross_signing_self_signing, + [this](Secret secret, mtx::http::RequestErr err) { + if (!err && !secret.encrypted.empty()) { + this->hasSSSS_ = true; + emit hasSSSSChanged(); + } + }); + auto verifStatus = cache::client()->verificationStatus(http::client()->user_id().to_string()); if (!verifStatus.user_verified) { diff --git a/src/encryption/SelfVerificationStatus.h b/src/encryption/SelfVerificationStatus.h index 8cb54df6..b1f315f4 100644 --- a/src/encryption/SelfVerificationStatus.h +++ b/src/encryption/SelfVerificationStatus.h @@ -11,6 +11,7 @@ class SelfVerificationStatus : public QObject Q_OBJECT Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(bool hasSSSS READ hasSSSS NOTIFY hasSSSSChanged) public: SelfVerificationStatus(QObject *o = nullptr); @@ -25,12 +26,15 @@ public: Q_INVOKABLE void setupCrosssigning(bool useSSSS, QString password, bool useOnlineKeyBackup); Q_INVOKABLE void verifyMasterKey(); + Q_INVOKABLE void verifyMasterKeyWithPassphrase(); Q_INVOKABLE void verifyUnverifiedDevices(); Status status() const { return status_; } + bool hasSSSS() const { return hasSSSS_; } signals: void statusChanged(); + void hasSSSSChanged(); void setupCompleted(); void showRecoveryKey(QString key); void setupFailed(QString message); @@ -40,4 +44,5 @@ public slots: private: Status status_ = AllVerified; + bool hasSSSS_ = true; }; diff --git a/src/encryption/VerificationManager.cpp b/src/encryption/VerificationManager.cpp index b9b51d35..f4c7ddf2 100644 --- a/src/encryption/VerificationManager.cpp +++ b/src/encryption/VerificationManager.cpp @@ -120,7 +120,16 @@ VerificationManager::removeVerificationFlow(DeviceVerificationFlow *flow) void VerificationManager::verifyDevice(QString userid, QString deviceid) { - auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, deviceid); + auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, {deviceid}); + this->dvList[flow->transactionId()] = flow; + emit newDeviceVerificationRequest(flow.data()); +} + +void +VerificationManager::verifyOneOfDevices(QString userid, std::vector deviceids) +{ + auto flow = + DeviceVerificationFlow::InitiateDeviceVerification(this, userid, std::move(deviceids)); this->dvList[flow->transactionId()] = flow; emit newDeviceVerificationRequest(flow.data()); } diff --git a/src/encryption/VerificationManager.h b/src/encryption/VerificationManager.h index d6a39ccf..da646c2f 100644 --- a/src/encryption/VerificationManager.h +++ b/src/encryption/VerificationManager.h @@ -27,6 +27,7 @@ public: Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); void verifyUser(QString userid); void verifyDevice(QString userid, QString deviceid); + void verifyOneOfDevices(QString userid, std::vector deviceids); signals: void newDeviceVerificationRequest(DeviceVerificationFlow *flow); -- cgit 1.5.1