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/SelfVerificationStatus.cpp | 249 ++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 src/encryption/SelfVerificationStatus.cpp (limited to 'src/encryption/SelfVerificationStatus.cpp') diff --git a/src/encryption/SelfVerificationStatus.cpp b/src/encryption/SelfVerificationStatus.cpp new file mode 100644 index 00000000..d75a2109 --- /dev/null +++ b/src/encryption/SelfVerificationStatus.cpp @@ -0,0 +1,249 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "SelfVerificationStatus.h" + +#include "Cache_p.h" +#include "Logging.h" +#include "MainWindow.h" +#include "MatrixClient.h" +#include "Olm.h" +#include "ui/UIA.h" + +#include + +SelfVerificationStatus::SelfVerificationStatus(QObject *o) + : QObject(o) +{ + connect(MainWindow::instance(), &MainWindow::reload, this, [this] { + connect(cache::client(), + &Cache::selfUnverified, + this, + &SelfVerificationStatus::invalidate, + Qt::UniqueConnection); + invalidate(); + }); +} + +void +SelfVerificationStatus::setupCrosssigning(bool useSSSS, QString password, bool useOnlineKeyBackup) +{ + nhlog::db()->info("Clicked setup crossigning"); + + auto xsign_keys = olm::client()->create_crosssigning_keys(); + + if (!xsign_keys) { + nhlog::crypto()->critical("Failed to setup cross-signing keys!"); + emit setupFailed(tr("Failed to create keys for cross-signing!")); + return; + } + + cache::client()->storeSecret(mtx::secret_storage::secrets::cross_signing_master, + xsign_keys->private_master_key); + cache::client()->storeSecret(mtx::secret_storage::secrets::cross_signing_self_signing, + xsign_keys->private_self_signing_key); + cache::client()->storeSecret(mtx::secret_storage::secrets::cross_signing_user_signing, + xsign_keys->private_user_signing_key); + + std::optional okb; + if (useOnlineKeyBackup) { + okb = olm::client()->create_online_key_backup(xsign_keys->private_master_key); + if (!okb) { + nhlog::crypto()->critical("Failed to setup online key backup!"); + emit setupFailed(tr("Failed to create keys for online key backup!")); + return; + } + + cache::client()->storeSecret( + mtx::secret_storage::secrets::megolm_backup_v1, + mtx::crypto::bin2base64(mtx::crypto::to_string(okb->privateKey))); + + http::client()->post_backup_version( + okb->backupVersion.algorithm, + okb->backupVersion.auth_data, + [](const mtx::responses::Version &v, mtx::http::RequestErr e) { + if (e) { + nhlog::net()->error("error setting up online key backup: {} {} {} {}", + e->parse_error, + e->status_code, + e->error_code, + e->matrix_error.error); + } else { + nhlog::crypto()->info("Set up online key backup: '{}'", v.version); + } + }); + } + + std::optional ssss; + if (useSSSS) { + ssss = olm::client()->create_ssss_key(password.toStdString()); + if (!ssss) { + nhlog::crypto()->critical("Failed to setup secure server side secret storage!"); + emit setupFailed(tr("Failed to create keys secure server side secret storage!")); + return; + } + + auto master = mtx::crypto::PkSigning::from_seed(xsign_keys->private_master_key); + nlohmann::json j = ssss->keyDescription; + j.erase("signatures"); + ssss->keyDescription + .signatures[http::client()->user_id().to_string()]["ed25519:" + master.public_key()] = + master.sign(j.dump()); + + http::client()->upload_secret_storage_key( + ssss->keyDescription.name, ssss->keyDescription, [](mtx::http::RequestErr) {}); + http::client()->set_secret_storage_default_key(ssss->keyDescription.name, + [](mtx::http::RequestErr) {}); + + auto uploadSecret = [ssss](const std::string &key_name, const std::string &secret) { + mtx::secret_storage::Secret s; + s.encrypted[ssss->keyDescription.name] = + mtx::crypto::encrypt(secret, ssss->privateKey, key_name); + http::client()->upload_secret_storage_secret( + key_name, s, [key_name](mtx::http::RequestErr) { + nhlog::crypto()->info("Uploaded secret: {}", key_name); + }); + }; + + uploadSecret(mtx::secret_storage::secrets::cross_signing_master, + xsign_keys->private_master_key); + uploadSecret(mtx::secret_storage::secrets::cross_signing_self_signing, + xsign_keys->private_self_signing_key); + uploadSecret(mtx::secret_storage::secrets::cross_signing_user_signing, + xsign_keys->private_user_signing_key); + + if (okb) + uploadSecret(mtx::secret_storage::secrets::megolm_backup_v1, + mtx::crypto::bin2base64(mtx::crypto::to_string(okb->privateKey))); + } + + mtx::requests::DeviceSigningUpload device_sign{}; + device_sign.master_key = xsign_keys->master_key; + device_sign.self_signing_key = xsign_keys->self_signing_key; + device_sign.user_signing_key = xsign_keys->user_signing_key; + http::client()->device_signing_upload( + device_sign, + UIA::instance()->genericHandler(tr("Encryption Setup")), + [this, ssss, xsign_keys](mtx::http::RequestErr e) { + if (e) { + nhlog::crypto()->critical("Failed to upload cross signing keys: {}", + e->matrix_error.error); + + emit setupFailed(tr("Encryption setup failed: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + return; + } + nhlog::crypto()->info("Crosssigning keys uploaded!"); + + auto deviceKeys = cache::client()->userKeys(http::client()->user_id().to_string()); + if (deviceKeys) { + 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(xsign_keys->private_self_signing_key); + myKey.signatures[http::client()->user_id().to_string()] + ["ed25519:" + ssk.public_key()] = ssk.sign(j.dump()); + mtx::requests::KeySignaturesUpload req; + req.signatures[http::client()->user_id().to_string()] + [http::client()->device_id()] = myKey; + + 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); + }); + } + } + + if (ssss) { + auto k = QString::fromStdString(mtx::crypto::key_to_recoverykey(ssss->privateKey)); + + QString r; + for (int i = 0; i < k.size(); i += 4) + r += k.mid(i, 4) + " "; + + emit showRecoveryKey(r.trimmed()); + } else { + emit setupCompleted(); + } + }); +} + +void +SelfVerificationStatus::verifyMasterKey() +{ + nhlog::db()->info("Clicked verify master key"); +} + +void +SelfVerificationStatus::verifyUnverifiedDevices() +{ + nhlog::db()->info("Clicked verify unverified devices"); +} + +void +SelfVerificationStatus::invalidate() +{ + nhlog::db()->info("Invalidating self verification status"); + auto keys = cache::client()->userKeys(http::client()->user_id().to_string()); + if (!keys) { + cache::client()->query_keys(http::client()->user_id().to_string(), + [](const UserKeyCache &, mtx::http::RequestErr) {}); + return; + } + + if (keys->master_keys.keys.empty()) { + if (status_ != SelfVerificationStatus::NoMasterKey) { + this->status_ = SelfVerificationStatus::NoMasterKey; + emit statusChanged(); + } + return; + } + + auto verifStatus = cache::client()->verificationStatus(http::client()->user_id().to_string()); + + if (!verifStatus.user_verified) { + if (status_ != SelfVerificationStatus::UnverifiedMasterKey) { + this->status_ = SelfVerificationStatus::UnverifiedMasterKey; + emit statusChanged(); + } + return; + } + + if (verifStatus.unverified_device_count > 0) { + if (status_ != SelfVerificationStatus::UnverifiedDevices) { + this->status_ = SelfVerificationStatus::UnverifiedDevices; + emit statusChanged(); + } + return; + } + + if (status_ != SelfVerificationStatus::AllVerified) { + this->status_ = SelfVerificationStatus::AllVerified; + emit statusChanged(); + return; + } +} -- 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/SelfVerificationStatus.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 From 2aabe9dcacbef4f753761f6df840da4292561d11 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 1 Nov 2021 22:20:15 +0100 Subject: Prompt user when there are unverified devices --- resources/qml/RoomList.qml | 80 ++++++++++++++++++++++ resources/qml/SelfVerificationCheck.qml | 4 ++ .../device-verification/NewVerificationRequest.qml | 5 +- src/Cache.cpp | 10 ++- src/Cache_p.h | 2 +- src/encryption/DeviceVerificationFlow.h | 2 + src/encryption/SelfVerificationStatus.cpp | 20 +++++- src/ui/Theme.cpp | 3 + src/ui/Theme.h | 4 +- 9 files changed, 120 insertions(+), 10 deletions(-) (limited to 'src/encryption/SelfVerificationStatus.cpp') diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 85087bc4..72ac49e1 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -502,6 +502,86 @@ Page { Layout.fillWidth: true } + Rectangle { + id: unverifiedStuffBubble + color: Qt.lighter(Nheko.theme.orange, verifyButtonHovered.hovered ? 1.2 : 1.0) + Layout.fillWidth: true + implicitHeight: explanation.height + Nheko.paddingMedium * 2 + visible: SelfVerificationStatus.status != SelfVerificationStatus.AllVerified + + RowLayout { + id: unverifiedStuffBubbleContainer + width: parent.width + height: explanation.height + Nheko.paddingMedium * 2 + spacing: 0 + + Label { + id: explanation + Layout.margins: Nheko.paddingMedium + Layout.rightMargin: Nheko.paddingSmall + color: Nheko.colors.buttonText + Layout.fillWidth: true + text: switch(SelfVerificationStatus.status) { + case SelfVerificationStatus.NoMasterKey: + //: Cross-signing setup has not run yet. + return qsTr("Encryption not set up"); + case SelfVerificationStatus.UnverifiedMasterKey: + //: The user just signed in with this device and hasn't verified their master key. + return qsTr("Unverified login"); + case SelfVerificationStatus.UnverifiedDevices: + //: There are unverified devices signed in to this account. + return qsTr("Please verify your other devices"); + default: + return "" + } + textFormat: Text.PlainText + wrapMode: Text.Wrap + } + + ImageButton { + id: closeUnverifiedBubble + + Layout.rightMargin: Nheko.paddingMedium + Layout.topMargin: Nheko.paddingMedium + Layout.alignment: Qt.AlignRight | Qt.AlignTop + hoverEnabled: true + width: fontMetrics.font.pixelSize + height: fontMetrics.font.pixelSize + image: ":/icons/icons/ui/remove-symbol.png" + ToolTip.visible: closeUnverifiedBubble.hovered + ToolTip.text: qsTr("Close") + onClicked: unverifiedStuffBubble.visible = false + } + + } + + HoverHandler { + id: verifyButtonHovered + enabled: !closeUnverifiedBubble.hovered + + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad + } + + TapHandler { + enabled: !closeUnverifiedBubble.hovered + acceptedButtons: Qt.LeftButton + onSingleTapped: { + if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedDevices) { + SelfVerificationStatus.verifyUnverifiedDevices(); + } else { + SelfVerificationStatus.statusChanged(); + } + } + } + } + + Rectangle { + color: Nheko.theme.separator + height: 1 + Layout.fillWidth: true + visible: unverifiedStuffBubble.visible + } + } footer: ColumnLayout { diff --git a/resources/qml/SelfVerificationCheck.qml b/resources/qml/SelfVerificationCheck.qml index 26af82b3..a7502d8d 100644 --- a/resources/qml/SelfVerificationCheck.qml +++ b/resources/qml/SelfVerificationCheck.qml @@ -277,6 +277,10 @@ Item { bootstrapCrosssigning.open(); else if (SelfVerificationStatus.status == SelfVerificationStatus.UnverifiedMasterKey) verifyMasterKey.open(); + else { + bootstrapCrosssigning.close(); + verifyMasterKey.close(); + } } diff --git a/resources/qml/device-verification/NewVerificationRequest.qml b/resources/qml/device-verification/NewVerificationRequest.qml index 5ae2d25b..7e521605 100644 --- a/resources/qml/device-verification/NewVerificationRequest.qml +++ b/resources/qml/device-verification/NewVerificationRequest.qml @@ -23,7 +23,10 @@ Pane { text: { if (flow.sender) { if (flow.isSelfVerification) - return qsTr("To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now?").arg(flow.deviceId); + if (flow.isMultiDeviceVerification) + return qsTr("To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify an unverified device now? (Please make sure you have one of those devices available.)"); + else + return qsTr("To allow other users to see, which of your devices actually belong to you, you can verify them. This also allows key backup to work automatically. Verify %1 now?").arg(flow.deviceId); else return qsTr("To ensure that no malicious user can eavesdrop on your encrypted communications you can verify the other party."); } else { diff --git a/src/Cache.cpp b/src/Cache.cpp index 5b77a9d4..45cc642d 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -208,8 +208,7 @@ Cache::Cache(const QString &userId, QObject *parent) [this](const std::string &u) { if (u == localUserId_.toStdString()) { auto status = verificationStatus(u); - if (status.unverified_device_count || !status.user_verified) - emit selfUnverified(); + emit selfVerificationStatusChanged(); } }, Qt::QueuedConnection); @@ -4265,6 +4264,7 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key) std::unique_lock lock(verification_storage.verification_storage_mtx); if (user_id == local_user) { std::swap(tmp, verification_storage.status); + verification_storage.status.clear(); } else { verification_storage.status.erase(user_id); } @@ -4274,9 +4274,8 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key) (void)status; emit verificationStatusChanged(user); } - } else { - emit verificationStatusChanged(user_id); } + emit verificationStatusChanged(user_id); } void @@ -4316,9 +4315,8 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) (void)status; emit verificationStatusChanged(user); } - } else { - emit verificationStatusChanged(user_id); } + emit verificationStatusChanged(user_id); } VerificationStatus diff --git a/src/Cache_p.h b/src/Cache_p.h index f7db77d4..651d73d7 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -310,7 +310,7 @@ signals: void removeNotification(const QString &room_id, const QString &event_id); void userKeysUpdate(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); void verificationStatusChanged(const std::string &userid); - void selfUnverified(); + void selfVerificationStatusChanged(); void secretChanged(const std::string name); private: diff --git a/src/encryption/DeviceVerificationFlow.h b/src/encryption/DeviceVerificationFlow.h index 55713def..537adf31 100644 --- a/src/encryption/DeviceVerificationFlow.h +++ b/src/encryption/DeviceVerificationFlow.h @@ -69,6 +69,7 @@ class DeviceVerificationFlow : public QObject Q_PROPERTY(std::vector sasList READ getSasList CONSTANT) Q_PROPERTY(bool isDeviceVerification READ isDeviceVerification CONSTANT) Q_PROPERTY(bool isSelfVerification READ isSelfVerification CONSTANT) + Q_PROPERTY(bool isMultiDeviceVerification READ isMultiDeviceVerification CONSTANT) public: enum State @@ -139,6 +140,7 @@ public: return this->type == DeviceVerificationFlow::Type::ToDevice; } bool isSelfVerification() const; + bool isMultiDeviceVerification() const { return deviceIds.size() > 1; } void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id); diff --git a/src/encryption/SelfVerificationStatus.cpp b/src/encryption/SelfVerificationStatus.cpp index d4be4442..ebb6b548 100644 --- a/src/encryption/SelfVerificationStatus.cpp +++ b/src/encryption/SelfVerificationStatus.cpp @@ -20,7 +20,7 @@ SelfVerificationStatus::SelfVerificationStatus(QObject *o) { connect(MainWindow::instance(), &MainWindow::reload, this, [this] { connect(cache::client(), - &Cache::selfUnverified, + &Cache::selfVerificationStatusChanged, this, &SelfVerificationStatus::invalidate, Qt::UniqueConnection); @@ -233,6 +233,24 @@ void SelfVerificationStatus::verifyUnverifiedDevices() { nhlog::db()->info("Clicked verify unverified devices"); + const auto this_user = http::client()->user_id().to_string(); + + auto keys = cache::client()->userKeys(this_user); + auto verif = cache::client()->verificationStatus(this_user); + + if (!keys) + return; + + std::vector devices; + for (const auto &[device, keys] : keys->device_keys) { + (void)keys; + if (!verif.verified_devices.count(device)) + devices.push_back(QString::fromStdString(device)); + } + + if (!devices.empty()) + ChatPage::instance()->timelineManager()->verificationManager()->verifyOneOfDevices( + QString::fromStdString(this_user), std::move(devices)); } void diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp index d6f0b72f..d7c92fb8 100644 --- a/src/ui/Theme.cpp +++ b/src/ui/Theme.cpp @@ -62,13 +62,16 @@ Theme::Theme(std::string_view theme) sidebarBackground_ = QColor("#233649"); alternateButton_ = QColor("#ccc"); red_ = QColor("#a82353"); + orange_ = QColor("#fcbe05"); } else if (theme == "dark") { sidebarBackground_ = QColor("#2d3139"); alternateButton_ = QColor("#414A59"); red_ = QColor("#a82353"); + orange_ = QColor("#fcc53a"); } else { sidebarBackground_ = p.window().color(); alternateButton_ = p.dark().color(); red_ = QColor("red"); + orange_ = QColor("orange"); } } diff --git a/src/ui/Theme.h b/src/ui/Theme.h index f3e7c287..4fef897d 100644 --- a/src/ui/Theme.h +++ b/src/ui/Theme.h @@ -62,6 +62,7 @@ class Theme : public QPalette Q_PROPERTY(QColor alternateButton READ alternateButton CONSTANT) Q_PROPERTY(QColor separator READ separator CONSTANT) Q_PROPERTY(QColor red READ red CONSTANT) + Q_PROPERTY(QColor orange READ orange CONSTANT) public: Theme() {} explicit Theme(std::string_view theme); @@ -71,7 +72,8 @@ public: QColor alternateButton() const { return alternateButton_; } QColor separator() const { return separator_; } QColor red() const { return red_; } + QColor orange() const { return orange_; } private: - QColor sidebarBackground_, separator_, red_, alternateButton_; + QColor sidebarBackground_, separator_, red_, orange_, alternateButton_; }; -- cgit 1.5.1