From 5688b2647ee686559303203a394bad1a92a744b0 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 30 Oct 2021 00:22:47 +0200 Subject: [PATCH] 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 | 63 ++++++++----- src/encryption/SelfVerificationStatus.cpp | 47 +++++++++- src/encryption/SelfVerificationStatus.h | 5 ++ src/encryption/VerificationManager.cpp | 11 ++- src/encryption/VerificationManager.h | 1 + 9 files changed, 285 insertions(+), 72 deletions(-) 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; + self_signing_key = secret; - std::map> - secrets; + 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; - 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; + std::map> + secrets; - for (const auto &[key, secrets] : secrets) - unlock_secrets(key, 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);