From 6fae36abc404ffb7e6ae29c9edceda5231400f0a Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Sun, 28 Jun 2020 21:01:34 +0530 Subject: [WIP] Add Caching for users --- src/Cache.cpp | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) (limited to 'src/Cache.cpp') diff --git a/src/Cache.cpp b/src/Cache.cpp index 0c692d07..5afeab06 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -2882,6 +2882,115 @@ Cache::statusMessage(const std::string &user_id) return status_msg; } +void +to_json(json &j, const UserCache &info) +{ + j["user_id"] = info.user_id; + j["is_user_verified"] = info.is_user_verified; + j["cross_verified"] = info.cross_verified; + j["keys"] = info.keys; +} + +void +from_json(const json &j, UserCache &info) +{ + info.user_id = j.at("user_id"); + info.is_user_verified = j.at("is_user_verified"); + info.cross_verified = j.at("cross_verified").get>(); + info.keys = j.at("keys").get(); +} + +UserCache +Cache::getUserCache(const std::string &user_id) +{ + lmdb::val verifiedVal; + + auto txn = lmdb::txn::begin(env_); + auto db = getUserCacheDb(txn); + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal); + + UserCache verified_state; + if (res) { + verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size())); + } + + txn.commit(); + + return verified_state; +} + +//! be careful when using make sure is_user_verified is not changed +int +Cache::setUserCache(const std::string &user_id, const UserCache &body) +{ + auto txn = lmdb::txn::begin(env_); + auto db = getUserCacheDb(txn); + + auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump())); + + txn.commit(); + + return res; +} + +int +Cache::deleteUserCache(const std::string &user_id) +{ + auto txn = lmdb::txn::begin(env_); + auto db = getUserCacheDb(txn); + auto res = lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr); + + txn.commit(); + + return res; +} + +void +to_json(json &j, const DeviceVerifiedCache &info) +{ + j["user_id"] = info.user_id; + j["device_verified"] = info.device_verified; +} + +void +from_json(const json &j, DeviceVerifiedCache &info) +{ + info.user_id = j.at("user_id"); + info.device_verified = j.at("device_verified").get>(); +} + +DeviceVerifiedCache +Cache::getVerifiedCache(const std::string &user_id) +{ + lmdb::val verifiedVal; + + auto txn = lmdb::txn::begin(env_); + auto db = getDeviceVerifiedDb(txn); + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal); + + DeviceVerifiedCache verified_state; + if (res) { + verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size())); + } + + txn.commit(); + + return verified_state; +} + +int +Cache::setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body) +{ + auto txn = lmdb::txn::begin(env_); + auto db = getDeviceVerifiedDb(txn); + + auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump())); + + txn.commit(); + + return res; +} + void to_json(json &j, const RoomInfo &info) { @@ -3063,6 +3172,35 @@ statusMessage(const std::string &user_id) { return instance_->statusMessage(user_id); } +UserCache +getUserCache(const std::string &user_id) +{ + return instance_->getUserCache(user_id); +} + +int +setUserCache(const std::string &user_id, const UserCache &body) +{ + return instance_->setUserCache(user_id, body); +} + +int +deleteUserCache(const std::string &user_id) +{ + return instance_->deleteUserCache(user_id); +} + +DeviceVerifiedCache +getVerifiedCache(const std::string &user_id) +{ + return instance_->getVerifiedCache(user_id); +} + +int +setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body) +{ + return instance_->setVerifiedCache(user_id, body); +} //! Load saved data for the display names & avatars. void -- cgit 1.5.1 From ac1fbbb69fdd4e313072cbf95eb9288db1257a9d Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Wed, 1 Jul 2020 17:47:10 +0530 Subject: Some issue with UserProfile --- CMakeLists.txt | 3 ++ resources/qml/UserProfile.qml | 44 +++++++++++++-------------- src/Cache.cpp | 32 +++++++++---------- src/Cache.h | 4 +-- src/CacheCryptoStructs.h | 5 +-- src/Cache_p.h | 4 +-- src/ui/UserProfile.cpp | 71 +++++++++++++++++++++++++++++-------------- src/ui/UserProfile.h | 54 +++++++++++++++++++++----------- src/ui/UserProfileModel.cpp | 45 +++++++++++++-------------- src/ui/UserProfileModel.h | 7 +++-- 10 files changed, 154 insertions(+), 115 deletions(-) (limited to 'src/Cache.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index 654eae37..c39ff3af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,9 @@ set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard") set(CMAKE_CXX_STANDARD_REQUIRED ON CACHE BOOL "Require C++ standard to be supported") set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "compile as PIC by default") +# set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") +# set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") + option(HUNTER_ENABLED "Enable Hunter package manager" OFF) include("cmake/HunterGate.cmake") HunterGate( diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index a0b0f993..db44ec15 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -19,17 +19,6 @@ ApplicationWindow{ Layout.alignment: Qt.AlignHCenter palette: colors - UserProfileList{ - id: userProfileList - userId: user_data.userId - onUserIdChanged : { - userProfileList.updateDeviceList() - } - onDeviceListUpdated : { - modelDeviceList.deviceList = userProfileList - } - } - Component { id: deviceVerificationDialog DeviceVerification {} @@ -94,7 +83,7 @@ ApplicationWindow{ ToolTip.visible: hovered ToolTip.text: qsTr("Ban the user") onClicked : { - userProfileList.banUser() + modelDeviceList.deviceList.banUser() } } // ImageButton{ @@ -106,7 +95,7 @@ ApplicationWindow{ // ToolTip.visible: hovered // ToolTip.text: qsTr("Ignore messages from this user") // onClicked : { - // userProfileList.ignoreUser() + // modelDeviceList.deviceList.ignoreUser() // } // } ImageButton{ @@ -118,7 +107,7 @@ ApplicationWindow{ ToolTip.visible: hovered ToolTip.text: qsTr("Start a private chat") onClicked : { - userProfileList.startChat() + modelDeviceList.deviceList.startChat() } } ImageButton{ @@ -130,7 +119,7 @@ ApplicationWindow{ ToolTip.visible: hovered ToolTip.text: qsTr("Kick the user") onClicked : { - userProfileList.kickUser() + modelDeviceList.deviceList.kickUser() } } } @@ -142,14 +131,15 @@ ApplicationWindow{ Layout.alignment: Qt.AlignHCenter ListView{ - id: deviceList + id: devicelist anchors.fill: parent clip: true spacing: 4 model: UserProfileModel{ id: modelDeviceList - } + deviceList.userId : user_data.userId + } delegate: RowLayout{ width: parent.width @@ -157,12 +147,20 @@ ApplicationWindow{ top : 50 } ColumnLayout{ - Text{ - Layout.fillWidth: true - color: colors.text - font.bold: true - Layout.alignment: Qt.AlignRight - text: deviceID + RowLayout{ + Text{ + Layout.fillWidth: true + color: colors.text + font.bold: true + Layout.alignment: Qt.AlignLeft + text: deviceID + } + Text{ + Layout.fillWidth: true + color:colors.text + Layout.alignment: Qt.AlignLeft + text: (verified_status == UserProfileList.VERIFIED?"V":(verified_status == UserProfileList.UNVERIFIED?"NV":"B")) + } } Text{ Layout.fillWidth: true diff --git a/src/Cache.cpp b/src/Cache.cpp index 5afeab06..553d1e97 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -2885,7 +2885,6 @@ Cache::statusMessage(const std::string &user_id) void to_json(json &j, const UserCache &info) { - j["user_id"] = info.user_id; j["is_user_verified"] = info.is_user_verified; j["cross_verified"] = info.cross_verified; j["keys"] = info.keys; @@ -2894,13 +2893,12 @@ to_json(json &j, const UserCache &info) void from_json(const json &j, UserCache &info) { - info.user_id = j.at("user_id"); info.is_user_verified = j.at("is_user_verified"); info.cross_verified = j.at("cross_verified").get>(); info.keys = j.at("keys").get(); } -UserCache +std::optional Cache::getUserCache(const std::string &user_id) { lmdb::val verifiedVal; @@ -2909,14 +2907,15 @@ Cache::getUserCache(const std::string &user_id) auto db = getUserCacheDb(txn); auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal); + txn.commit(); + UserCache verified_state; if (res) { verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size())); + return verified_state; + } else { + return {}; } - - txn.commit(); - - return verified_state; } //! be careful when using make sure is_user_verified is not changed @@ -2948,18 +2947,18 @@ Cache::deleteUserCache(const std::string &user_id) void to_json(json &j, const DeviceVerifiedCache &info) { - j["user_id"] = info.user_id; j["device_verified"] = info.device_verified; + j["device_blocked"] = info.device_blocked; } void from_json(const json &j, DeviceVerifiedCache &info) { - info.user_id = j.at("user_id"); info.device_verified = j.at("device_verified").get>(); + info.device_blocked = j.at("device_blocked").get>(); } -DeviceVerifiedCache +std::optional Cache::getVerifiedCache(const std::string &user_id) { lmdb::val verifiedVal; @@ -2968,14 +2967,15 @@ Cache::getVerifiedCache(const std::string &user_id) auto db = getDeviceVerifiedDb(txn); auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal); + txn.commit(); + DeviceVerifiedCache verified_state; if (res) { verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size())); + return verified_state; + } else { + return {}; } - - txn.commit(); - - return verified_state; } int @@ -3172,7 +3172,7 @@ statusMessage(const std::string &user_id) { return instance_->statusMessage(user_id); } -UserCache +std::optional getUserCache(const std::string &user_id) { return instance_->getUserCache(user_id); @@ -3190,7 +3190,7 @@ deleteUserCache(const std::string &user_id) return instance_->deleteUserCache(user_id); } -DeviceVerifiedCache +std::optional getVerifiedCache(const std::string &user_id) { return instance_->getVerifiedCache(user_id); diff --git a/src/Cache.h b/src/Cache.h index 34e79ab5..0c955c92 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -61,7 +61,7 @@ std::string statusMessage(const std::string &user_id); //! user Cache -UserCache +std::optional getUserCache(const std::string &user_id); int @@ -71,7 +71,7 @@ int deleteUserCache(const std::string &user_id); //! verified Cache -DeviceVerifiedCache +std::optional getVerifiedCache(const std::string &user_id); int diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 7344aef9..241cac76 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -68,8 +68,6 @@ struct OlmSessionStorage struct UserCache { - //! user_id of the user - std::string user_id; //! this stores if the user is verified (with cross-signing) bool is_user_verified = false; //! list of verified device_ids with cross-signing @@ -85,10 +83,9 @@ from_json(const nlohmann::json &j, UserCache &info); struct DeviceVerifiedCache { - //! user_id of the user - std::string user_id; //! list of verified device_ids with device-verification std::vector device_verified; + std::vector device_blocked; }; void diff --git a/src/Cache_p.h b/src/Cache_p.h index cf4416ce..60fa930f 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -55,12 +55,12 @@ public: std::string statusMessage(const std::string &user_id); // user cache stores user keys - UserCache getUserCache(const std::string &user_id); + std::optional getUserCache(const std::string &user_id); int setUserCache(const std::string &user_id, const UserCache &body); int deleteUserCache(const std::string &user_id); // device verified cache - DeviceVerifiedCache getVerifiedCache(const std::string &user_id); + std::optional getVerifiedCache(const std::string &user_id); int setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body); static void removeDisplayName(const QString &room_id, const QString &user_id); diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index c637280b..8c6fb8e4 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -7,11 +7,25 @@ #include // only for debugging +Q_DECLARE_METATYPE(UserProfile::Status) + UserProfile::UserProfile(QObject *parent) : QObject(parent) -{} +{ + qRegisterMetaType(); + connect( + this, &UserProfile::updateDeviceList, this, [this]() { fetchDeviceList(this->userId); }); + connect( + this, + &UserProfile::appendDeviceList, + this, + [this](QString device_id, QString device_name, UserProfile::Status verification_status) { + this->deviceList.push_back( + DeviceInfo{device_id, device_name, verification_status}); + }); +} -QVector +std::vector UserProfile::getDeviceList() { return this->deviceList; @@ -37,7 +51,8 @@ UserProfile::setUserId(const QString &user_id) void UserProfile::callback_fn(const mtx::responses::QueryKeys &res, mtx::http::RequestErr err, - std::string user_id) + std::string user_id, + std::optional> cross_verified) { if (err) { nhlog::net()->warn("failed to query device keys: {},{}", @@ -52,24 +67,40 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res, } auto devices = res.device_keys.at(user_id); - QVector deviceInfo; + std::vector deviceInfo; + auto device_verified = cache::getVerifiedCache(user_id); for (const auto &d : devices) { auto device = d.second; // TODO: Verify signatures and ignore those that don't pass. - DeviceInfo newdevice( + UserProfile::Status verified = UserProfile::Status::UNVERIFIED; + if (cross_verified.has_value()) { + if (std::find(cross_verified->begin(), cross_verified->end(), d.first) != + cross_verified->end()) + verified = UserProfile::Status::VERIFIED; + } else if (device_verified.has_value()) { + if (std::find(device_verified->device_verified.begin(), + device_verified->device_verified.end(), + d.first) != device_verified->device_verified.end()) + verified = UserProfile::Status::VERIFIED; + } else if (device_verified.has_value()) { + if (std::find(device_verified->device_blocked.begin(), + device_verified->device_blocked.end(), + d.first) != device_verified->device_blocked.end()) + verified = UserProfile::Status::BLOCKED; + } + + emit UserProfile::appendDeviceList( QString::fromStdString(d.first), - QString::fromStdString(device.unsigned_info.device_display_name)); - QString::fromStdString(device.unsigned_info.device_display_name); - - deviceInfo.append(std::move(newdevice)); + QString::fromStdString(device.unsigned_info.device_display_name), + verified); } - std::sort( - deviceInfo.begin(), deviceInfo.end(), [](const DeviceInfo &a, const DeviceInfo &b) { - return a.device_id > b.device_id; - }); + // std::sort( + // deviceInfo.begin(), deviceInfo.end(), [](const DeviceInfo &a, const DeviceInfo &b) { + // return a.device_id > b.device_id; + // }); this->deviceList = std::move(deviceInfo); emit UserProfile::deviceListUpdated(); @@ -81,9 +112,9 @@ UserProfile::fetchDeviceList(const QString &userID) auto localUser = utils::localUser(); auto user_cache = cache::getUserCache(userID.toStdString()); - if (user_cache.user_id == userID.toStdString()) { - mtx::http::ClientError error; - this->callback_fn(user_cache.keys, std::move(error), userID.toStdString()); + if (user_cache.has_value()) { + this->callback_fn( + user_cache->keys, {}, userID.toStdString(), user_cache->cross_verified); } else { mtx::requests::QueryKeys req; req.device_keys[userID.toStdString()] = {}; @@ -91,17 +122,11 @@ UserProfile::fetchDeviceList(const QString &userID) req, [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) { - this->callback_fn(res, err, user_id); + this->callback_fn(res, err, user_id, {}); }); } } -void -UserProfile::updateDeviceList() -{ - fetchDeviceList(this->userId); -} - void UserProfile::banUser() { diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index befd82ec..1725b961 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -5,36 +5,32 @@ #include #include "MatrixClient.h" -class DeviceInfo -{ -public: - DeviceInfo(const QString deviceID, const QString displayName) - : device_id(deviceID) - , display_name(displayName) - {} - DeviceInfo() {} - - QString device_id; - QString display_name; -}; +class DeviceInfo; class UserProfile : public QObject { Q_OBJECT Q_PROPERTY(QString userId READ getUserId WRITE setUserId NOTIFY userIdChanged) - Q_PROPERTY(QVector deviceList READ getDeviceList NOTIFY deviceListUpdated) + Q_PROPERTY(std::vector deviceList READ getDeviceList NOTIFY deviceListUpdated) public: // constructor explicit UserProfile(QObject *parent = 0); // getters - QVector getDeviceList(); + std::vector getDeviceList(); QString getUserId(); // setters void setUserId(const QString &userId); - Q_INVOKABLE void fetchDeviceList(const QString &userID); - Q_INVOKABLE void updateDeviceList(); + enum Status + { + VERIFIED, + UNVERIFIED, + BLOCKED + }; + Q_ENUM(Status) + + void fetchDeviceList(const QString &userID); Q_INVOKABLE void banUser(); // Q_INVOKABLE void ignoreUser(); Q_INVOKABLE void kickUser(); @@ -43,12 +39,34 @@ public: signals: void userIdChanged(); void deviceListUpdated(); + void updateDeviceList(); + void appendDeviceList(const QString device_id, + const QString device_naem, + const UserProfile::Status verification_status); private: - QVector deviceList; + std::vector deviceList; QString userId; + std::optional cross_verified; void callback_fn(const mtx::responses::QueryKeys &res, mtx::http::RequestErr err, - std::string user_id); + std::string user_id, + std::optional> cross_verified); +}; + +class DeviceInfo +{ +public: + DeviceInfo(const QString deviceID, + const QString displayName, + UserProfile::Status verification_status_) + : device_id(deviceID) + , display_name(displayName) + , verification_status(verification_status_) + {} + + QString device_id; + QString display_name; + UserProfile::Status verification_status; }; \ No newline at end of file diff --git a/src/ui/UserProfileModel.cpp b/src/ui/UserProfileModel.cpp index ec0456cd..3fa8fe2d 100644 --- a/src/ui/UserProfileModel.cpp +++ b/src/ui/UserProfileModel.cpp @@ -1,11 +1,23 @@ #include "UserProfileModel.h" -#include "UserProfile.h" #include UserProfileModel::UserProfileModel(QObject *parent) : QAbstractListModel(parent) , deviceList(nullptr) -{} +{ + this->deviceList = new UserProfile(this); + + connect(this->deviceList, &UserProfile::userIdChanged, this, [this]() { + emit this->deviceList->updateDeviceList(); + }); + connect(this->deviceList, &UserProfile::deviceListUpdated, this, [this]() { + beginResetModel(); + this->beginInsertRows( + QModelIndex(), 0, this->deviceList->getDeviceList().size() - 1); + this->endInsertRows(); + endResetModel(); + }); +} int UserProfileModel::rowCount(const QModelIndex &parent) const @@ -18,7 +30,8 @@ UserProfileModel::rowCount(const QModelIndex &parent) const QVariant UserProfileModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || !this->deviceList) + if (!index.isValid() && + static_cast(this->deviceList->getDeviceList().size()) <= index.row()) return QVariant(); const DeviceInfo device = this->deviceList->getDeviceList().at(index.row()); @@ -27,6 +40,8 @@ UserProfileModel::data(const QModelIndex &index, int role) const return QVariant(device.device_id); case DISPLAYNAME: return QVariant(device.display_name); + case VERIFIED_STATUS: + return device.verification_status; } return QVariant(); } @@ -35,8 +50,9 @@ QHash UserProfileModel::roleNames() const { QHash names; - names[DEVICEID] = "deviceID"; - names[DISPLAYNAME] = "displayName"; + names[DEVICEID] = "deviceID"; + names[DISPLAYNAME] = "displayName"; + names[VERIFIED_STATUS] = "verified_status"; return names; } @@ -45,22 +61,3 @@ UserProfileModel::getList() const { return (this->deviceList); } - -void -UserProfileModel::setList(UserProfile *devices) -{ - beginResetModel(); - - if (devices) - devices->disconnect(this); - - if (this->deviceList) { - const int index = this->deviceList->getDeviceList().size(); - beginInsertRows(QModelIndex(), index, index); - endInsertRows(); - } - - this->deviceList = devices; - - endResetModel(); -} \ No newline at end of file diff --git a/src/ui/UserProfileModel.h b/src/ui/UserProfileModel.h index c21a806d..ba7a2525 100644 --- a/src/ui/UserProfileModel.h +++ b/src/ui/UserProfileModel.h @@ -1,5 +1,6 @@ #pragma once +#include "UserProfile.h" #include class UserProfile; // forward declaration of the class UserProfile @@ -7,7 +8,7 @@ class UserProfile; // forward declaration of the class UserProfile class UserProfileModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(UserProfile *deviceList READ getList WRITE setList) + Q_PROPERTY(UserProfile *deviceList READ getList) public: explicit UserProfileModel(QObject *parent = nullptr); @@ -15,10 +16,10 @@ public: enum { DEVICEID, - DISPLAYNAME + DISPLAYNAME, + VERIFIED_STATUS }; UserProfile *getList() const; - void setList(UserProfile *devices); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; -- cgit 1.5.1 From a2979c2df1b2059e2e8a969f5c1a3a804bd2550c Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Mon, 6 Jul 2020 21:32:21 +0530 Subject: Updating keys of outdated encrypted users --- src/Cache.cpp | 56 ++++++++++++++++++++++++++++++++++-------- src/Cache.h | 3 +++ src/CacheCryptoStructs.h | 29 +++++++++++++++++++--- src/Cache_p.h | 5 ++-- src/DeviceVerificationFlow.cpp | 8 +++--- src/ui/UserProfile.cpp | 17 ++++++------- src/ui/UserProfile.h | 3 +-- 7 files changed, 89 insertions(+), 32 deletions(-) (limited to 'src/Cache.cpp') diff --git a/src/Cache.cpp b/src/Cache.cpp index 553d1e97..1008277a 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -31,8 +31,10 @@ #include "Cache.h" #include "Cache_p.h" +#include "ChatPage.h" #include "EventAccessors.h" #include "Logging.h" +#include "MatrixClient.h" #include "Utils.h" //! Should be changed when a breaking change occurs in the cache format. @@ -1009,6 +1011,8 @@ Cache::saveState(const mtx::responses::Sync &res) savePresence(txn, res.presence); + updateUserCache(res.device_lists); + removeLeftRooms(txn, res.rooms.leave); txn.commit(); @@ -2885,17 +2889,13 @@ Cache::statusMessage(const std::string &user_id) void to_json(json &j, const UserCache &info) { - j["is_user_verified"] = info.is_user_verified; - j["cross_verified"] = info.cross_verified; - j["keys"] = info.keys; + j["keys"] = info.keys; } void from_json(const json &j, UserCache &info) { - info.is_user_verified = j.at("is_user_verified"); - info.cross_verified = j.at("cross_verified").get>(); - info.keys = j.at("keys").get(); + info.keys = j.at("keys").get(); } std::optional @@ -2932,6 +2932,32 @@ Cache::setUserCache(const std::string &user_id, const UserCache &body) return res; } +void +Cache::updateUserCache(const mtx::responses::DeviceLists body) +{ + for (auto user_id : body.changed) { + mtx::requests::QueryKeys req; + req.device_keys[user_id] = {}; + + http::client()->query_keys( + req, + [user_id, this](const mtx::responses::QueryKeys res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {},{}", + err->matrix_error.errcode, + static_cast(err->status_code)); + return; + } + + setUserCache(user_id, UserCache{std::move(res)}); + }); + } + + for (std::string user_id : body.left) { + deleteUserCache(user_id); + } +} + int Cache::deleteUserCache(const std::string &user_id) { @@ -2947,15 +2973,19 @@ Cache::deleteUserCache(const std::string &user_id) void to_json(json &j, const DeviceVerifiedCache &info) { - j["device_verified"] = info.device_verified; - j["device_blocked"] = info.device_blocked; + j["is_user_verified"] = info.is_user_verified; + j["cross_verified"] = info.cross_verified; + j["device_verified"] = info.device_verified; + j["device_blocked"] = info.device_blocked; } void from_json(const json &j, DeviceVerifiedCache &info) { - info.device_verified = j.at("device_verified").get>(); - info.device_blocked = j.at("device_blocked").get>(); + info.is_user_verified = j.at("is_user_verified"); + info.cross_verified = j.at("cross_verified").get>(); + info.device_verified = j.at("device_verified").get>(); + info.device_blocked = j.at("device_blocked").get>(); } std::optional @@ -3178,6 +3208,12 @@ getUserCache(const std::string &user_id) return instance_->getUserCache(user_id); } +void +updateUserCache(const mtx::responses::DeviceLists body) +{ + instance_->updateUserCache(body); +} + int setUserCache(const std::string &user_id, const UserCache &body) { diff --git a/src/Cache.h b/src/Cache.h index 0c955c92..82d909ae 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -64,6 +64,9 @@ statusMessage(const std::string &user_id); std::optional getUserCache(const std::string &user_id); +void +updateUserCache(const mtx::responses::DeviceLists body); + int setUserCache(const std::string &user_id, const UserCache &body); diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 241cac76..ba746f59 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -66,14 +66,16 @@ struct OlmSessionStorage std::mutex group_inbound_mtx; }; +// this will store the keys of the user with whom a encrypted room is shared with struct UserCache { - //! this stores if the user is verified (with cross-signing) - bool is_user_verified = false; - //! list of verified device_ids with cross-signing - std::vector cross_verified; //! map of public key key_ids and their public_key mtx::responses::QueryKeys keys; + + UserCache(mtx::responses::QueryKeys res) + : keys(res) + {} + UserCache() {} }; void @@ -81,11 +83,30 @@ to_json(nlohmann::json &j, const UserCache &info); void from_json(const nlohmann::json &j, UserCache &info); +// the reason these are stored in a seperate cache rather than storing it in the user cache is +// UserCache stores only keys of users with which encrypted room is shared struct DeviceVerifiedCache { //! list of verified device_ids with device-verification std::vector device_verified; + //! list of verified device_ids with cross-signing + std::vector cross_verified; + //! list of devices the user blocks std::vector device_blocked; + //! this stores if the user is verified (with cross-signing) + bool is_user_verified = false; + + DeviceVerifiedCache(std::vector device_verified_, + std::vector cross_verified_, + std::vector device_blocked_, + bool is_user_verified_ = false) + : device_verified(device_verified_) + , cross_verified(cross_verified_) + , device_blocked(device_blocked_) + , is_user_verified(is_user_verified_) + {} + + DeviceVerifiedCache() {} }; void diff --git a/src/Cache_p.h b/src/Cache_p.h index 60fa930f..f75b0f4e 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -56,6 +56,7 @@ public: // user cache stores user keys std::optional getUserCache(const std::string &user_id); + void updateUserCache(const mtx::responses::DeviceLists body); int setUserCache(const std::string &user_id, const UserCache &body); int deleteUserCache(const std::string &user_id); @@ -521,12 +522,12 @@ private: lmdb::dbi getUserCacheDb(lmdb::txn &txn) { - return lmdb::dbi::open(txn, std::string("user_cache").c_str(), MDB_CREATE); + return lmdb::dbi::open(txn, "user_cache", MDB_CREATE); } lmdb::dbi getDeviceVerifiedDb(lmdb::txn &txn) { - return lmdb::dbi::open(txn, std::string("verified").c_str(), MDB_CREATE); + return lmdb::dbi::open(txn, "verified", MDB_CREATE); } //! Retrieves or creates the database that stores the open OLM sessions between our device diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 7829c41d..0122e691 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -477,7 +477,7 @@ DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_c } else { cache::setVerifiedCache( this->userId.toStdString(), - DeviceVerifiedCache{{}, {this->deviceId.toStdString()}}); + DeviceVerifiedCache{{}, {}, {this->deviceId.toStdString()}}); } this->deleteLater(); }); @@ -564,8 +564,9 @@ DeviceVerificationFlow::acceptDevice() } cache::setVerifiedCache(this->userId.toStdString(), verified_cache.value()); } else { - cache::setVerifiedCache(this->userId.toStdString(), - DeviceVerifiedCache{{this->deviceId.toStdString()}, {}}); + cache::setVerifiedCache( + this->userId.toStdString(), + DeviceVerifiedCache{{this->deviceId.toStdString()}, {}, {}}); } emit deviceVerified(); @@ -616,5 +617,4 @@ DeviceVerificationFlow::unverify() } emit refreshProfile(); - this->deleteLater(); } diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index b4938e8d..6ae04d0b 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -77,8 +77,7 @@ UserProfile::avatarUrl() void UserProfile::callback_fn(const mtx::responses::QueryKeys &res, mtx::http::RequestErr err, - std::string user_id, - std::optional> cross_verified) + std::string user_id) { if (err) { nhlog::net()->warn("failed to query device keys: {},{}", @@ -101,16 +100,15 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res, // TODO: Verify signatures and ignore those that don't pass. verification::Status verified = verification::Status::UNVERIFIED; - if (cross_verified.has_value()) { - if (std::find(cross_verified->begin(), cross_verified->end(), d.first) != - cross_verified->end()) + if (device_verified.has_value()) { + if (std::find(device_verified->cross_verified.begin(), + device_verified->cross_verified.end(), + d.first) != device_verified->cross_verified.end()) verified = verification::Status::VERIFIED; - } else if (device_verified.has_value()) { if (std::find(device_verified->device_verified.begin(), device_verified->device_verified.end(), d.first) != device_verified->device_verified.end()) verified = verification::Status::VERIFIED; - } else if (device_verified.has_value()) { if (std::find(device_verified->device_blocked.begin(), device_verified->device_blocked.end(), d.first) != device_verified->device_blocked.end()) @@ -138,8 +136,7 @@ UserProfile::fetchDeviceList(const QString &userID) auto user_cache = cache::getUserCache(userID.toStdString()); if (user_cache.has_value()) { - this->callback_fn( - user_cache->keys, {}, userID.toStdString(), user_cache->cross_verified); + this->callback_fn(user_cache->keys, {}, userID.toStdString()); } else { mtx::requests::QueryKeys req; req.device_keys[userID.toStdString()] = {}; @@ -147,7 +144,7 @@ UserProfile::fetchDeviceList(const QString &userID) req, [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) { - this->callback_fn(res, err, user_id, {}); + this->callback_fn(res, err, user_id); }); } } diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index 99c6a755..4e048400 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -103,6 +103,5 @@ private: void callback_fn(const mtx::responses::QueryKeys &res, mtx::http::RequestErr err, - std::string user_id, - std::optional> cross_verified); + std::string user_id); }; -- cgit 1.5.1 From 1fcd768f88f7e84978d19283c9fa6205624f2544 Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Sat, 18 Jul 2020 01:46:30 +0530 Subject: Adding Room Key Verification Stuff --- resources/qml/UserProfile.qml | 22 +- resources/qml/delegates/MessageDelegate.qml | 54 +++ src/Cache.cpp | 2 +- src/ChatPage.h | 18 +- src/DeviceVerificationFlow.cpp | 665 +++++++++++++++------------- src/DeviceVerificationFlow.h | 17 +- src/EventAccessors.cpp | 11 +- src/Olm.cpp | 39 +- src/timeline/TimelineModel.cpp | 187 +++++++- src/timeline/TimelineModel.h | 8 + src/timeline/TimelineViewManager.cpp | 76 ++-- src/timeline/TimelineViewManager.h | 9 +- src/ui/UserProfile.cpp | 34 ++ src/ui/UserProfile.h | 4 + 14 files changed, 786 insertions(+), 360 deletions(-) (limited to 'src/Cache.cpp') diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 5bdccb4d..c7dbc9aa 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -74,6 +74,26 @@ ApplicationWindow{ Layout.alignment: Qt.AlignHCenter } + Button { + id: verifyUserButton + text: "Verify" + Layout.alignment: Qt.AlignHCenter + enabled: profile.isUserVerified?false:true + visible: profile.isUserVerified?false:true + palette { + button: "white" + } + contentItem: Text { + text: verifyUserButton.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + profile.verifyUser(); + } + } + RowLayout { Layout.alignment: Qt.AlignHCenter ImageButton { @@ -127,7 +147,7 @@ ApplicationWindow{ } ScrollView { - implicitHeight: userProfileDialog.height/2 + 20 + implicitHeight: userProfileDialog.height/2-13 implicitWidth: userProfileDialog.width-20 clip: true Layout.alignment: Qt.AlignHCenter diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index 6f69f026..c556a978 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -127,6 +127,60 @@ Item { text: TimelineManager.timeline.formatMemberEvent(model.data.id); } } + DelegateChoice { + roleValue: MtxEvent.KeyVerificationRequest + NoticeMessage { + text: "KeyVerificationRequest"; + } + } + DelegateChoice { + roleValue: MtxEvent.KeyVerificationStart + NoticeMessage { + text: "KeyVerificationStart"; + } + } + DelegateChoice { + roleValue: MtxEvent.KeyVerificationReady + NoticeMessage { + text: "KeyVerificationReady"; + } + } + DelegateChoice { + roleValue: MtxEvent.KeyVerificationCancel + NoticeMessage { + text: "KeyVerificationCancel"; + } + } + DelegateChoice { + roleValue: MtxEvent.KeyVerificationKey + NoticeMessage { + text: "KeyVerificationKey"; + } + } + DelegateChoice { + roleValue: MtxEvent.KeyVerificationMac + NoticeMessage { + text: "KeyVerificationMac"; + } + } + DelegateChoice { + roleValue: MtxEvent.KeyVerificationDone + NoticeMessage { + text: "KeyVerificationDone"; + } + } + DelegateChoice { + roleValue: MtxEvent.KeyVerificationDone + NoticeMessage { + text: "KeyVerificationDone"; + } + } + DelegateChoice { + roleValue: MtxEvent.KeyVerificationAccept + NoticeMessage { + text: "KeyVerificationAccept"; + } + } DelegateChoice { Placeholder {} } diff --git a/src/Cache.cpp b/src/Cache.cpp index 1008277a..8cee3453 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -1011,7 +1011,7 @@ Cache::saveState(const mtx::responses::Sync &res) savePresence(txn, res.presence); - updateUserCache(res.device_lists); + // updateUserCache(res.device_lists); removeLeftRooms(txn, res.rooms.leave); diff --git a/src/ChatPage.h b/src/ChatPage.h index 172f74fe..0e7c889f 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -167,16 +167,18 @@ signals: //! Signals for device verificaiton void recievedDeviceVerificationAccept( - const mtx::events::collections::DeviceEvents &message); + const mtx::events::msg::KeyVerificationAccept &message); void recievedDeviceVerificationRequest( - const mtx::events::collections::DeviceEvents &message); + const mtx::events::msg::KeyVerificationRequest &message, + std::string sender); void recievedDeviceVerificationCancel( - const mtx::events::collections::DeviceEvents &message); - void recievedDeviceVerificationKey(const mtx::events::collections::DeviceEvents &message); - void recievedDeviceVerificationMac(const mtx::events::collections::DeviceEvents &message); - void recievedDeviceVerificationStart(const mtx::events::collections::DeviceEvents &message); - void recievedDeviceVerificationReady(const mtx::events::collections::DeviceEvents &message); - void recievedDeviceVerificationDone(const mtx::events::collections::DeviceEvents &message); + const mtx::events::msg::KeyVerificationCancel &message); + void recievedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message); + void recievedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message); + void recievedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message, + std::string sender); + void recievedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message); + void recievedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message); private slots: void showUnreadMessageNotification(int count); diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 0122e691..69de4937 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -6,11 +6,13 @@ #include #include +#include + static constexpr int TIMEOUT = 2 * 60 * 1000; // 2 minutes namespace msgs = mtx::events::msg; -DeviceVerificationFlow::DeviceVerificationFlow(QObject *) +DeviceVerificationFlow::DeviceVerificationFlow(QObject *, DeviceVerificationFlow::Type) { timeout = new QTimer(this); timeout->setSingleShot(true); @@ -26,192 +28,218 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *) ChatPage::instance(), &ChatPage::recievedDeviceVerificationStart, this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = - std::get>(message); - if (msg.content.transaction_id == this->transaction_id) { - if ((std::find(msg.content.key_agreement_protocols.begin(), - msg.content.key_agreement_protocols.end(), - "curve25519-hkdf-sha256") != - msg.content.key_agreement_protocols.end()) && - (std::find(msg.content.hashes.begin(), - msg.content.hashes.end(), - "sha256") != msg.content.hashes.end()) && - (std::find(msg.content.message_authentication_codes.begin(), - msg.content.message_authentication_codes.end(), - "hmac-sha256") != - msg.content.message_authentication_codes.end())) { - if (std::find(msg.content.short_authentication_string.begin(), - msg.content.short_authentication_string.end(), - mtx::events::msg::SASMethods::Decimal) != - msg.content.short_authentication_string.end()) { - this->method = DeviceVerificationFlow::Method::Emoji; - } else if (std::find( - msg.content.short_authentication_string.begin(), - msg.content.short_authentication_string.end(), - mtx::events::msg::SASMethods::Emoji) != - msg.content.short_authentication_string.end()) { - this->method = DeviceVerificationFlow::Method::Decimal; - } else { - this->cancelVerification( - DeviceVerificationFlow::Error::UnknownMethod); - return; - } - this->acceptVerificationRequest(); - this->canonical_json = nlohmann::json(msg.content); - } else { - this->cancelVerification( - DeviceVerificationFlow::Error::UnknownMethod); - } + [this](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.relates_to.has_value()) { + if (msg.relates_to.value().in_reply_to.event_id != + this->relation.in_reply_to.event_id) + return; } - }); - connect( - ChatPage::instance(), - &ChatPage::recievedDeviceVerificationAccept, - this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = - std::get>(message); - if (msg.content.transaction_id == this->transaction_id) { - if ((msg.content.key_agreement_protocol == "curve25519-hkdf-sha256") && - (msg.content.hash == "sha256") && - (msg.content.message_authentication_code == "hkdf-hmac-sha256")) { - this->commitment = msg.content.commitment; - if (std::find(msg.content.short_authentication_string.begin(), - msg.content.short_authentication_string.end(), - mtx::events::msg::SASMethods::Emoji) != - msg.content.short_authentication_string.end()) { - this->method = DeviceVerificationFlow::Method::Emoji; - } else { - this->method = DeviceVerificationFlow::Method::Decimal; - } - this->mac_method = msg.content.message_authentication_code; - this->sendVerificationKey(); + 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(), + "hmac-sha256") != msg.message_authentication_codes.end())) { + 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 = DeviceVerificationFlow::Method::Emoji; + } else 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 = DeviceVerificationFlow::Method::Decimal; } else { this->cancelVerification( DeviceVerificationFlow::Error::UnknownMethod); + return; } + this->acceptVerificationRequest(); + this->canonical_json = nlohmann::json(msg); + } else { + this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); } }); + + connect(ChatPage::instance(), + &ChatPage::recievedDeviceVerificationAccept, + this, + [this](const mtx::events::msg::KeyVerificationAccept &msg) { + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relates_to.has_value()) { + if (msg.relates_to.value().in_reply_to.event_id != + this->relation.in_reply_to.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 = DeviceVerificationFlow::Method::Emoji; + } else { + this->method = DeviceVerificationFlow::Method::Decimal; + } + this->mac_method = msg.message_authentication_code; + this->sendVerificationKey(); + } else { + this->cancelVerification( + DeviceVerificationFlow::Error::UnknownMethod); + } + }); + connect(ChatPage::instance(), &ChatPage::recievedDeviceVerificationCancel, this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = - std::get>(message); - if (msg.content.transaction_id == this->transaction_id) { - emit verificationCanceled(); + [this](const mtx::events::msg::KeyVerificationCancel &msg) { + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relates_to.has_value()) { + if (msg.relates_to.value().in_reply_to.event_id != + this->relation.in_reply_to.event_id) + return; + } + emit verificationCanceled(); + }); + + connect(ChatPage::instance(), + &ChatPage::recievedDeviceVerificationKey, + this, + [this](const mtx::events::msg::KeyVerificationKey &msg) { + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relates_to.has_value()) { + if (msg.relates_to.value().in_reply_to.event_id != + this->relation.in_reply_to.event_id) + 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; + } + + if (this->method == DeviceVerificationFlow::Method::Emoji) { + this->sasList = this->sas->generate_bytes_emoji(info); + } else if (this->method == DeviceVerificationFlow::Method::Decimal) { + this->sasList = this->sas->generate_bytes_decimal(info); + } + if (this->sender == false) { + emit this->verificationRequestAccepted(this->method); + this->sendVerificationKey(); + } else { + if (this->commitment == + mtx::crypto::bin2base64_unpadded( + mtx::crypto::sha256(msg.key + this->canonical_json.dump()))) { + emit this->verificationRequestAccepted(this->method); + } else { + this->cancelVerification( + DeviceVerificationFlow::Error::MismatchedCommitment); + } } }); + connect( ChatPage::instance(), - &ChatPage::recievedDeviceVerificationKey, + &ChatPage::recievedDeviceVerificationMac, this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = std::get>(message); - if (msg.content.transaction_id == this->transaction_id) { - this->sas->set_their_key(msg.content.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.content.key + - "|" + this->transaction_id; - } else { - info = "MATRIX_KEY_VERIFICATION_SAS|" + - this->toClient.to_string() + "|" + - this->deviceId.toStdString() + "|" + msg.content.key + - "|" + http::client()->user_id().to_string() + "|" + - http::client()->device_id() + "|" + - this->sas->public_key() + "|" + this->transaction_id; - } - - if (this->method == DeviceVerificationFlow::Method::Emoji) { - this->sasList = this->sas->generate_bytes_emoji(info); - } else if (this->method == DeviceVerificationFlow::Method::Decimal) { - this->sasList = this->sas->generate_bytes_decimal(info); - } - if (this->sender == false) { - emit this->verificationRequestAccepted(this->method); - this->sendVerificationKey(); - } else { - if (this->commitment == - mtx::crypto::bin2base64_unpadded(mtx::crypto::sha256( - msg.content.key + this->canonical_json.dump()))) { - emit this->verificationRequestAccepted(this->method); + [this](const mtx::events::msg::KeyVerificationMac &msg) { + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relates_to.has_value()) { + if (msg.relates_to.value().in_reply_to.event_id != + this->relation.in_reply_to.event_id) + return; + } + std::string info = "MATRIX_KEY_VERIFICATION_MAC" + this->toClient.to_string() + + this->deviceId.toStdString() + + http::client()->user_id().to_string() + + http::client()->device_id() + this->transaction_id; + + std::vector key_list; + std::string key_string; + for (auto mac : msg.mac) { + key_string += mac.first + ","; + if (device_keys[mac.first] != "") { + if (mac.second == + this->sas->calculate_mac(this->device_keys[mac.first], + info + mac.first)) { } else { this->cancelVerification( - DeviceVerificationFlow::Error::MismatchedCommitment); + DeviceVerificationFlow::Error::KeyMismatch); + return; } } } - }); - connect( - ChatPage::instance(), - &ChatPage::recievedDeviceVerificationMac, - this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = std::get>(message); - if (msg.content.transaction_id == this->transaction_id) { - std::string info = - "MATRIX_KEY_VERIFICATION_MAC" + this->toClient.to_string() + - this->deviceId.toStdString() + http::client()->user_id().to_string() + - http::client()->device_id() + this->transaction_id; - - std::vector key_list; - std::string key_string; - for (auto mac : msg.content.mac) { - key_string += mac.first + ","; - if (device_keys[mac.first] != "") { - if (mac.second == - this->sas->calculate_mac(this->device_keys[mac.first], - info + mac.first)) { - } else { - this->cancelVerification( - DeviceVerificationFlow::Error::KeyMismatch); - return; - } - } - } - key_string = key_string.substr(0, key_string.length() - 1); - if (msg.content.keys == - this->sas->calculate_mac(key_string, info + "KEY_IDS")) { - // uncomment this in future to be compatible with the - // MSC2366 this->sendVerificationDone(); and remove the - // below line - if (this->isMacVerified == true) { - this->acceptDevice(); - } else - this->isMacVerified = true; - } else { - this->cancelVerification( - DeviceVerificationFlow::Error::KeyMismatch); - } + key_string = key_string.substr(0, key_string.length() - 1); + if (msg.keys == this->sas->calculate_mac(key_string, info + "KEY_IDS")) { + // uncomment this in future to be compatible with the + // MSC2366 this->sendVerificationDone(); and remove the + // below line + if (this->isMacVerified == true) { + this->acceptDevice(); + } else + this->isMacVerified = true; + } else { + this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch); } }); + connect(ChatPage::instance(), &ChatPage::recievedDeviceVerificationReady, this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = - std::get>(message); - if (msg.content.transaction_id == this->transaction_id) { - this->startVerificationRequest(); + [this](const mtx::events::msg::KeyVerificationReady &msg) { + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relates_to.has_value()) { + if (msg.relates_to.value().in_reply_to.event_id != + this->relation.in_reply_to.event_id) + return; } + this->startVerificationRequest(); }); + connect(ChatPage::instance(), &ChatPage::recievedDeviceVerificationDone, this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = - std::get>(message); - if (msg.content.transaction_id == this->transaction_id) { - this->acceptDevice(); + [this](const mtx::events::msg::KeyVerificationDone &msg) { + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relates_to.has_value()) { + if (msg.relates_to.value().in_reply_to.event_id != + this->relation.in_reply_to.event_id) + return; } + this->acceptDevice(); }); + timeout->start(TIMEOUT); } @@ -294,18 +322,18 @@ void DeviceVerificationFlow::setSender(bool sender_) { this->sender = sender_; - if (this->sender == true) + if (this->sender == true && this->type == DeviceVerificationFlow::Type::ToDevice) this->transaction_id = http::client()->generate_txn_id(); + else if (this->sender == true && this->type == DeviceVerificationFlow::Type::RoomMsg) + this->relation.in_reply_to.event_id = http::client()->generate_txn_id(); } //! accepts a verification void DeviceVerificationFlow::acceptVerificationRequest() { - mtx::requests::ToDeviceMessages body; mtx::events::msg::KeyVerificationAccept req; - req.transaction_id = this->transaction_id; req.method = mtx::events::msg::VerificationMethods::SASv1; req.key_agreement_protocol = "curve25519-hkdf-sha256"; req.hash = "sha256"; @@ -317,126 +345,152 @@ DeviceVerificationFlow::acceptVerificationRequest() req.commitment = mtx::crypto::bin2base64_unpadded( mtx::crypto::sha256(this->sas->public_key() + this->canonical_json.dump())); - body[this->toClient][this->deviceId.toStdString()] = req; - - http::client() - ->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to accept verification request: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); + if (this->type == DeviceVerificationFlow::Type::ToDevice) { + mtx::requests::ToDeviceMessages body; + req.transaction_id = this->transaction_id; + + body[this->toClient][this->deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + this->transaction_id, body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn( + "failed to accept verification request: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); + } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) { + req.relates_to = this->relation; + } } //! responds verification request void DeviceVerificationFlow::sendVerificationReady() { - mtx::requests::ToDeviceMessages body; mtx::events::msg::KeyVerificationReady req; - req.from_device = http::client()->device_id(); - req.transaction_id = this->transaction_id; - req.methods = {mtx::events::msg::VerificationMethods::SASv1}; - - body[this->toClient][this->deviceId.toStdString()] = req; - - http::client() - ->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to send verification ready: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); + req.from_device = http::client()->device_id(); + req.methods = {mtx::events::msg::VerificationMethods::SASv1}; + + if (this->type == DeviceVerificationFlow::Type::ToDevice) { + req.transaction_id = this->transaction_id; + mtx::requests::ToDeviceMessages body; + + body[this->toClient][this->deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + this->transaction_id, body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to send verification ready: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); + } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) { + req.relates_to = this->relation; + } } //! accepts a verification void DeviceVerificationFlow::sendVerificationDone() { - mtx::requests::ToDeviceMessages body; mtx::events::msg::KeyVerificationDone req; - req.transaction_id = this->transaction_id; - - body[this->toClient][this->deviceId.toStdString()] = req; - - http::client() - ->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to send verification done: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); + if (this->type == DeviceVerificationFlow::Type::ToDevice) { + mtx::requests::ToDeviceMessages body; + req.transaction_id = this->transaction_id; + + body[this->toClient][this->deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + this->transaction_id, body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to send verification done: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); + } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) { + req.relates_to = this->relation; + } } //! starts the verification flow void DeviceVerificationFlow::startVerificationRequest() { - mtx::requests::ToDeviceMessages body; mtx::events::msg::KeyVerificationStart req; req.from_device = http::client()->device_id(); - req.transaction_id = this->transaction_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", "hmac-sha256"}; + req.message_authentication_codes = {"hkdf-hmac-sha256"}; req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal, mtx::events::msg::SASMethods::Emoji}; - body[this->toClient][this->deviceId.toStdString()] = req; - this->canonical_json = nlohmann::json(req); - - http::client() - ->send_to_device( - this->transaction_id, body, [body](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to start verification request: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); + if (this->type == DeviceVerificationFlow::Type::ToDevice) { + mtx::requests::ToDeviceMessages body; + req.transaction_id = this->transaction_id; + this->canonical_json = nlohmann::json(req); + body[this->toClient][this->deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + this->transaction_id, body, [body](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn( + "failed to start verification request: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); + } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) { + req.relates_to = this->relation; + } } //! sends a verification request void DeviceVerificationFlow::sendVerificationRequest() { - QDateTime CurrentTime = QDateTime::currentDateTimeUtc(); - - mtx::requests::ToDeviceMessages body; mtx::events::msg::KeyVerificationRequest req; - req.from_device = http::client()->device_id(); - req.transaction_id = this->transaction_id; + req.from_device = http::client()->device_id(); req.methods.resize(1); req.methods[0] = mtx::events::msg::VerificationMethods::SASv1; - req.timestamp = (uint64_t)CurrentTime.toTime_t(); - - body[this->toClient][this->deviceId.toStdString()] = req; - - http::client() - ->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to send verification request: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); + + if (this->type == DeviceVerificationFlow::Type::ToDevice) { + QDateTime CurrentTime = QDateTime::currentDateTimeUtc(); + + req.transaction_id = this->transaction_id; + req.timestamp = (uint64_t)CurrentTime.toTime_t(); + + mtx::requests::ToDeviceMessages body; + + body[this->toClient][this->deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + this->transaction_id, body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to send verification request: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); + } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) { + std::cout << "lulz" << std::endl; + } } //! cancels a verification flow void DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_code) { - mtx::requests::ToDeviceMessages body; mtx::events::msg::KeyVerificationCancel req; - req.transaction_id = this->transaction_id; if (error_code == DeviceVerificationFlow::Error::UnknownMethod) { req.code = "m.unknown_method"; req.reason = "unknown method recieved"; @@ -457,65 +511,79 @@ DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_c req.reason = "user cancelled the verification"; } - body[this->toClient][deviceId.toStdString()] = req; - emit this->verificationCanceled(); - http::client() - ->send_to_device( - this->transaction_id, body, [this](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to cancel verification request: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - auto verified_cache = cache::getVerifiedCache(this->userId.toStdString()); - if (verified_cache.has_value()) { - verified_cache->device_blocked.push_back(this->deviceId.toStdString()); - cache::setVerifiedCache(this->userId.toStdString(), - verified_cache.value()); - } else { - cache::setVerifiedCache( - this->userId.toStdString(), - DeviceVerifiedCache{{}, {}, {this->deviceId.toStdString()}}); - } - this->deleteLater(); - }); + if (this->type == DeviceVerificationFlow::Type::ToDevice) { + req.transaction_id = this->transaction_id; + mtx::requests::ToDeviceMessages body; + + body[this->toClient][deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + this->transaction_id, body, [this](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn( + "failed to cancel verification request: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + + this->deleteLater(); + }); + } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) { + req.relates_to = this->relation; + } + + // TODO : Handle Blocking user better + // auto verified_cache = cache::getVerifiedCache(this->userId.toStdString()); + // if (verified_cache.has_value()) { + // verified_cache->device_blocked.push_back(this->deviceId.toStdString()); + // cache::setVerifiedCache(this->userId.toStdString(), + // verified_cache.value()); + // } else { + // cache::setVerifiedCache( + // this->userId.toStdString(), + // DeviceVerifiedCache{{}, {}, {this->deviceId.toStdString()}}); + // } } //! sends the verification key void DeviceVerificationFlow::sendVerificationKey() { - mtx::requests::ToDeviceMessages body; mtx::events::msg::KeyVerificationKey req; - req.key = this->sas->public_key(); - req.transaction_id = this->transaction_id; - - body[this->toClient][deviceId.toStdString()] = req; - - http::client() - ->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to send verification key: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); + req.key = this->sas->public_key(); + + if (this->type == DeviceVerificationFlow::Type::ToDevice) { + mtx::requests::ToDeviceMessages body; + req.transaction_id = this->transaction_id; + + body[this->toClient][deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + this->transaction_id, body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to send verification key: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); + } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) { + req.relates_to = this->relation; + } } //! sends the mac of the keys void DeviceVerificationFlow::sendVerificationMac() { - mtx::requests::ToDeviceMessages body; mtx::events::msg::KeyVerificationMac req; std::string info = "MATRIX_KEY_VERIFICATION_MAC" + http::client()->user_id().to_string() + http::client()->device_id() + this->toClient.to_string() + this->deviceId.toStdString() + this->transaction_id; - req.transaction_id = this->transaction_id; //! this vector stores the type of the key and the key std::vector> key_list; key_list.push_back(make_pair("ed25519", olm::client()->identity_keys().ed25519)); @@ -531,22 +599,28 @@ DeviceVerificationFlow::sendVerificationMac() req.keys = this->sas->calculate_mac(req.keys.substr(0, req.keys.size() - 1), info + "KEY_IDS"); - body[this->toClient][deviceId.toStdString()] = req; - - http::client() - ->send_to_device( - this->transaction_id, body, [this](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn("failed to send verification MAC: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - - if (this->isMacVerified == true) - this->acceptDevice(); - else - this->isMacVerified = true; - }); + if (this->type == DeviceVerificationFlow::Type::ToDevice) { + mtx::requests::ToDeviceMessages body; + req.transaction_id = this->transaction_id; + body[this->toClient][deviceId.toStdString()] = req; + + http::client() + ->send_to_device( + this->transaction_id, body, [this](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to send verification MAC: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + + if (this->isMacVerified == true) + this->acceptDevice(); + else + this->isMacVerified = true; + }); + } else if (this->type == DeviceVerificationFlow::Type::RoomMsg) { + req.relates_to = this->relation; + } } //! Completes the verification flow void @@ -555,14 +629,11 @@ DeviceVerificationFlow::acceptDevice() auto verified_cache = cache::getVerifiedCache(this->userId.toStdString()); if (verified_cache.has_value()) { verified_cache->device_verified.push_back(this->deviceId.toStdString()); - for (auto it = verified_cache->device_blocked.begin(); - it != verified_cache->device_blocked.end(); - it++) { - if (*it == this->deviceId.toStdString()) { - verified_cache->device_blocked.erase(it); - } - } - cache::setVerifiedCache(this->userId.toStdString(), verified_cache.value()); + verified_cache->device_blocked.erase( + std::remove(verified_cache->device_blocked.begin(), + verified_cache->device_blocked.end(), + this->deviceId.toStdString()), + verified_cache->device_blocked.end()); } else { cache::setVerifiedCache( this->userId.toStdString(), diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index edff7c8e..3f999e80 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -2,8 +2,8 @@ #include "Olm.h" +#include "MatrixClient.h" #include "mtx/responses/crypto.hpp" -#include #include class QTimer; @@ -19,15 +19,22 @@ class DeviceVerificationFlow : public QObject Q_PROPERTY(QString userId READ getUserId WRITE setUserId) Q_PROPERTY(QString deviceId READ getDeviceId WRITE setDeviceId) Q_PROPERTY(Method method READ getMethod WRITE setMethod) - Q_PROPERTY(std::vector sasList READ getSasList) + Q_PROPERTY(std::vector sasList READ getSasList CONSTANT) public: + enum Type + { + ToDevice, + RoomMsg + }; + enum Method { Decimal, Emoji }; Q_ENUM(Method) + enum Error { UnknownMethod, @@ -39,7 +46,9 @@ public: }; Q_ENUM(Error) - DeviceVerificationFlow(QObject *parent = nullptr); + DeviceVerificationFlow( + QObject *parent = nullptr, + DeviceVerificationFlow::Type = DeviceVerificationFlow::Type::ToDevice); QString getTransactionId(); QString getUserId(); QString getDeviceId(); @@ -90,6 +99,7 @@ private: QString userId; QString deviceId; Method method; + Type type; bool sender; QTimer *timeout = nullptr; @@ -101,4 +111,5 @@ private: mtx::identifiers::User toClient; std::vector sasList; std::map device_keys; + mtx::common::ReplyRelatesTo relation; }; diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp index 0618206c..869687f4 100644 --- a/src/EventAccessors.cpp +++ b/src/EventAccessors.cpp @@ -72,8 +72,15 @@ struct EventBody template std::string operator()(const mtx::events::Event &e) { - if constexpr (is_detected::value) - return e.content.body; + if constexpr (is_detected::value) { + if constexpr (std::is_same_v, + std::remove_cv_t>) + return e.content.body ? e.content.body.value() : ""; + else if constexpr (std::is_same_v< + std::string, + std::remove_cv_t>) + return e.content.body; + } return ""; } }; diff --git a/src/Olm.cpp b/src/Olm.cpp index 7d7037c9..ff6ea2f4 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -5,10 +5,10 @@ #include "Cache.h" #include "ChatPage.h" +#include "DeviceVerificationFlow.h" #include "Logging.h" #include "MatrixClient.h" #include "Utils.h" -#include static const std::string STORAGE_SECRET_KEY("secret"); constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2"; @@ -77,21 +77,42 @@ handle_to_device_messages(const std::vectorrecievedDeviceVerificationAccept(msg); + auto message = std::get< + mtx::events::DeviceEvent>(msg); + ChatPage::instance()->recievedDeviceVerificationAccept(message.content); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationRequest)) { - ChatPage::instance()->recievedDeviceVerificationRequest(msg); + auto message = std::get< + mtx::events::DeviceEvent>(msg); + ChatPage::instance()->recievedDeviceVerificationRequest(message.content, + message.sender); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationCancel)) { - ChatPage::instance()->recievedDeviceVerificationCancel(msg); + auto message = std::get< + mtx::events::DeviceEvent>(msg); + ChatPage::instance()->recievedDeviceVerificationCancel(message.content); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationKey)) { - ChatPage::instance()->recievedDeviceVerificationKey(msg); + auto message = + std::get>( + msg); + ChatPage::instance()->recievedDeviceVerificationKey(message.content); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationMac)) { - ChatPage::instance()->recievedDeviceVerificationMac(msg); + auto message = + std::get>( + msg); + ChatPage::instance()->recievedDeviceVerificationMac(message.content); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationStart)) { - ChatPage::instance()->recievedDeviceVerificationStart(msg); + auto message = std::get< + mtx::events::DeviceEvent>(msg); + ChatPage::instance()->recievedDeviceVerificationStart(message.content, + message.sender); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationReady)) { - ChatPage::instance()->recievedDeviceVerificationReady(msg); + auto message = std::get< + mtx::events::DeviceEvent>(msg); + ChatPage::instance()->recievedDeviceVerificationReady(message.content); } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationDone)) { - ChatPage::instance()->recievedDeviceVerificationDone(msg); + auto message = + std::get>( + msg); + ChatPage::instance()->recievedDeviceVerificationDone(message.content); } else { nhlog::crypto()->warn("unhandled event: {}", j_msg.dump(2)); } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 773a5a23..71cc53c5 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -22,6 +22,8 @@ #include "Utils.h" #include "dialogs/RawMessage.h" +#include + Q_DECLARE_METATYPE(QModelIndex) namespace std { @@ -116,7 +118,41 @@ struct RoomEventType { return qml_mtx_events::EventType::VideoMessage; } - + qml_mtx_events::EventType operator()( + const mtx::events::Event &) + { + return qml_mtx_events::EventType::KeyVerificationRequest; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event &) + { + return qml_mtx_events::EventType::KeyVerificationStart; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event &) + { + return qml_mtx_events::EventType::KeyVerificationMac; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event &) + { + return qml_mtx_events::EventType::KeyVerificationAccept; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event &) + { + return qml_mtx_events::EventType::KeyVerificationCancel; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event &) + { + return qml_mtx_events::EventType::KeyVerificationKey; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event &) + { + return qml_mtx_events::EventType::KeyVerificationDone; + } qml_mtx_events::EventType operator()(const mtx::events::Event &) { return qml_mtx_events::EventType::Redacted; @@ -572,6 +608,155 @@ TimelineModel::updateLastMessage() } } +std::vector +TimelineModel::internalAddEvents( + const std::vector &timeline) +{ + std::vector ids; + for (auto e : timeline) { + QString id = QString::fromStdString(mtx::accessors::event_id(e)); + + if (this->events.contains(id)) { + this->events.insert(id, e); + int idx = idToIndex(id); + emit dataChanged(index(idx, 0), index(idx, 0)); + continue; + } + + QString txid = QString::fromStdString(mtx::accessors::transaction_id(e)); + if (this->pending.removeOne(txid)) { + this->events.insert(id, e); + this->events.remove(txid); + int idx = idToIndex(txid); + if (idx < 0) { + nhlog::ui()->warn("Received index out of range"); + continue; + } + eventOrder[idx] = id; + emit dataChanged(index(idx, 0), index(idx, 0)); + continue; + } + + if (std::get_if>( + &e)) { + std::cout << "got a request" << std::endl; + } + + if (auto cancelVerification = + std::get_if>( + &e)) { + std::cout<<"it is happening"<content.relates_to.has_value()) { + QString event_id = QString::fromStdString( + cancelVerification->content.relates_to.value() + .in_reply_to.event_id); + auto request = + std::find(eventOrder.begin(), eventOrder.end(), event_id); + if (request != eventOrder.end()) { + auto event = events.value(event_id); + auto e = std::get_if>(&event); + std::cout<>(&e)) { + QString redacts = QString::fromStdString(redaction->redacts); + auto redacted = std::find(eventOrder.begin(), eventOrder.end(), redacts); + + auto event = events.value(redacts); + if (auto reaction = + std::get_if>( + &event)) { + QString reactedTo = + QString::fromStdString(reaction->content.relates_to.event_id); + reactions[reactedTo].removeReaction(*reaction); + int idx = idToIndex(reactedTo); + if (idx >= 0) + emit dataChanged(index(idx, 0), index(idx, 0)); + } + + if (redacted != eventOrder.end()) { + auto redactedEvent = std::visit( + [](const auto &ev) + -> mtx::events::RoomEvent { + mtx::events::RoomEvent + replacement = {}; + replacement.event_id = ev.event_id; + replacement.room_id = ev.room_id; + replacement.sender = ev.sender; + replacement.origin_server_ts = ev.origin_server_ts; + replacement.type = ev.type; + return replacement; + }, + e); + events.insert(redacts, redactedEvent); + + int row = (int)std::distance(eventOrder.begin(), redacted); + emit dataChanged(index(row, 0), index(row, 0)); + } + + continue; // don't insert redaction into timeline + } + + if (auto reaction = + std::get_if>(&e)) { + QString reactedTo = + QString::fromStdString(reaction->content.relates_to.event_id); + events.insert(id, e); + + // remove local echo + if (!txid.isEmpty()) { + auto rCopy = *reaction; + rCopy.event_id = txid.toStdString(); + reactions[reactedTo].removeReaction(rCopy); + } + + reactions[reactedTo].addReaction(room_id_.toStdString(), *reaction); + int idx = idToIndex(reactedTo); + if (idx >= 0) + emit dataChanged(index(idx, 0), index(idx, 0)); + continue; // don't insert reaction into timeline + } + + if (auto event = + std::get_if>(&e)) { + auto e_ = decryptEvent(*event).event; + auto encInfo = mtx::accessors::file(e_); + + if (encInfo) + emit newEncryptedImage(encInfo.value()); + } + + this->events.insert(id, e); + ids.push_back(id); + + auto replyTo = mtx::accessors::in_reply_to_event(e); + auto qReplyTo = QString::fromStdString(replyTo); + if (!replyTo.empty() && !events.contains(qReplyTo)) { + http::client()->get_event( + this->room_id_.toStdString(), + replyTo, + [this, id, replyTo]( + const mtx::events::collections::TimelineEvents &timeline, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error( + "Failed to retrieve event with id {}, which was " + "requested to show the replyTo for event {}", + replyTo, + id.toStdString()); + return; + } + emit eventFetched(id, timeline); + }); + } + } + return ids; +} + void TimelineModel::setCurrentIndex(int index) { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 104a475c..708ed38e 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -84,6 +84,14 @@ enum EventType VideoMessage, Redacted, UnknownMessage, + KeyVerificationRequest, + KeyVerificationStart, + KeyVerificationMac, + KeyVerificationAccept, + KeyVerificationCancel, + KeyVerificationKey, + KeyVerificationDone, + KeyVerificationReady }; Q_ENUM_NS(EventType) diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 81c8d6d3..02b74d20 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -101,7 +101,15 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin , blurhashProvider(new BlurhashProvider()) , settings(userSettings) { - qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, "im.nheko", 1, @@ -181,21 +189,19 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin dynamic_cast(parent), &ChatPage::recievedDeviceVerificationRequest, this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = - std::get>(message); + [this](const mtx::events::msg::KeyVerificationRequest &msg, std::string sender) { auto flow = new DeviceVerificationFlow(this); - if (!(this->dvList->exist(QString::fromStdString(msg.content.transaction_id)))) { - if (std::find(msg.content.methods.begin(), - msg.content.methods.end(), + if (!(this->dvList->exist(QString::fromStdString(msg.transaction_id.value())))) { + if (std::find(msg.methods.begin(), + msg.methods.end(), mtx::events::msg::VerificationMethods::SASv1) != - msg.content.methods.end()) { + msg.methods.end()) { // flow->sendVerificationReady(); emit newDeviceVerificationRequest( std::move(flow), - QString::fromStdString(msg.content.transaction_id), - QString::fromStdString(msg.sender), - QString::fromStdString(msg.content.from_device)); + QString::fromStdString(msg.transaction_id.value()), + QString::fromStdString(sender), + QString::fromStdString(msg.from_device)); } else { flow->cancelVerification( DeviceVerificationFlow::Error::UnknownMethod); @@ -206,33 +212,29 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin dynamic_cast(parent), &ChatPage::recievedDeviceVerificationStart, this, - [this](const mtx::events::collections::DeviceEvents &message) { - auto msg = - std::get>(message); + [this](const mtx::events::msg::KeyVerificationStart &msg, std::string sender) { auto flow = new DeviceVerificationFlow(this); - flow->canonical_json = nlohmann::json(msg.content); - if (!(this->dvList->exist(QString::fromStdString(msg.content.transaction_id)))) { - if ((std::find(msg.content.key_agreement_protocols.begin(), - msg.content.key_agreement_protocols.end(), + flow->canonical_json = nlohmann::json(msg); + if (!(this->dvList->exist(QString::fromStdString(msg.transaction_id.value())))) { + if ((std::find(msg.key_agreement_protocols.begin(), + msg.key_agreement_protocols.end(), "curve25519-hkdf-sha256") != - msg.content.key_agreement_protocols.end()) && - (std::find(msg.content.hashes.begin(), - msg.content.hashes.end(), - "sha256") != msg.content.hashes.end()) && - (std::find(msg.content.message_authentication_codes.begin(), - msg.content.message_authentication_codes.end(), + 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(), "hmac-sha256") != - msg.content.message_authentication_codes.end())) { - if (std::find(msg.content.short_authentication_string.begin(), - msg.content.short_authentication_string.end(), + msg.message_authentication_codes.end())) { + if (std::find(msg.short_authentication_string.begin(), + msg.short_authentication_string.end(), mtx::events::msg::SASMethods::Emoji) != - msg.content.short_authentication_string.end()) { + msg.short_authentication_string.end()) { flow->setMethod(DeviceVerificationFlow::Method::Emoji); - } else if (std::find( - msg.content.short_authentication_string.begin(), - msg.content.short_authentication_string.end(), - mtx::events::msg::SASMethods::Decimal) != - msg.content.short_authentication_string.end()) { + } else if (std::find(msg.short_authentication_string.begin(), + msg.short_authentication_string.end(), + mtx::events::msg::SASMethods::Decimal) != + msg.short_authentication_string.end()) { flow->setMethod(DeviceVerificationFlow::Method::Decimal); } else { flow->cancelVerification( @@ -241,10 +243,10 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin } emit newDeviceVerificationRequest( std::move(flow), - QString::fromStdString(msg.content.transaction_id), - QString::fromStdString(msg.sender), - QString::fromStdString(msg.content.from_device)); - flow->canonical_json = nlohmann::json(msg.content); + QString::fromStdString(msg.transaction_id.value()), + QString::fromStdString(sender), + QString::fromStdString(msg.from_device)); + flow->canonical_json = nlohmann::json(msg); } else { flow->cancelVerification( DeviceVerificationFlow::Error::UnknownMethod); diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index a438ef5e..71aee5ef 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -133,4 +133,11 @@ private: DeviceVerificationList *dvList; }; -Q_DECLARE_METATYPE(mtx::events::collections::DeviceEvents) +Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationAccept) +Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationCancel) +Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationDone) +Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationKey) +Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationMac) +Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationReady) +Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationRequest) +Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationStart) diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 6ae04d0b..3499384c 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -6,6 +6,8 @@ #include "Utils.h" #include "mtx/responses/crypto.hpp" +#include // only for debugging + UserProfile::UserProfile(QString roomid, QString userid, QObject *parent) : QObject(parent) , roomid_(roomid) @@ -74,6 +76,12 @@ UserProfile::avatarUrl() return cache::avatarUrl(roomid_, userid_); } +bool +UserProfile::getUserStatus() +{ + return isUserVerified; +} + void UserProfile::callback_fn(const mtx::responses::QueryKeys &res, mtx::http::RequestErr err, @@ -100,6 +108,7 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res, // TODO: Verify signatures and ignore those that don't pass. verification::Status verified = verification::Status::UNVERIFIED; + isUserVerified = device_verified->is_user_verified; if (device_verified.has_value()) { if (std::find(device_verified->cross_verified.begin(), device_verified->cross_verified.end(), @@ -174,4 +183,29 @@ UserProfile::startChat() if (utils::localUser() != this->userid_) req.invite = {this->userid_.toStdString()}; emit ChatPage::instance()->createRoom(req); +} + +void +UserProfile::verifyUser() +{ + std::cout << "Checking if to start to device verification or room message verification" + << std::endl; + auto joined_rooms = cache::joinedRooms(); + auto room_infos = cache::getRoomInfo(joined_rooms); + + for (std::string room_id : joined_rooms) { + if ((room_infos[QString::fromStdString(room_id)].member_count == 2) && + cache::isRoomEncrypted(room_id)) { + auto room_members = cache::roomMembers(room_id); + if (std::find(room_members.begin(), + room_members.end(), + (this->userid()).toStdString()) != room_members.end()) { + std::cout << "FOUND A ENCRYPTED ROOM WITH THIS USER : " << room_id + << std::endl; + return; + } + } + } + + std::cout << "DIDN'T FIND A ENCRYPTED ROOM WITH THIS USER" << std::endl; } \ No newline at end of file diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index 4e048400..3f9cbe6f 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -81,6 +81,7 @@ class UserProfile : public QObject Q_PROPERTY(QString userid READ userid CONSTANT) Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT) Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT) + Q_PROPERTY(bool isUserVerified READ getUserStatus CONSTANT) public: UserProfile(QString roomid, QString userid, QObject *parent = 0); @@ -89,17 +90,20 @@ public: QString userid(); QString displayName(); QString avatarUrl(); + bool getUserStatus(); Q_INVOKABLE void fetchDeviceList(const QString &userID); Q_INVOKABLE void banUser(); // Q_INVOKABLE void ignoreUser(); Q_INVOKABLE void kickUser(); Q_INVOKABLE void startChat(); + Q_INVOKABLE void verifyUser(); private: QString roomid_, userid_; std::optional cross_verified; DeviceInfoModel deviceList_; + bool isUserVerified = false; void callback_fn(const mtx::responses::QueryKeys &res, mtx::http::RequestErr err, -- cgit 1.5.1 From 1d299951b61390c381013ca0503fb09df548c6ec Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Mon, 24 Aug 2020 13:56:50 +0530 Subject: Cache Fix --- src/Cache.cpp | 46 ++++++------ src/CacheCryptoStructs.h | 5 +- src/Cache_p.h | 2 + src/ChatPage.cpp | 35 ++++++++++ src/ChatPage.h | 3 + src/ui/UserProfile.cpp | 177 +++++++++++++++++++++++++++++------------------ 6 files changed, 181 insertions(+), 87 deletions(-) (limited to 'src/Cache.cpp') diff --git a/src/Cache.cpp b/src/Cache.cpp index 8cee3453..cff0029e 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -111,6 +111,24 @@ Cache::Cache(const QString &userId, QObject *parent) , localUserId_{userId} { setup(); + connect(this, + &Cache::updateUserCacheFlag, + this, + [this](const std::string &user_id) { + std::optional cache_ = getUserCache(user_id); + if (cache_.has_value()) { + cache_.value().isUpdated = false; + setUserCache(user_id, cache_.value()); + } else { + setUserCache(user_id, UserCache{}); + } + }, + Qt::QueuedConnection); + connect(this, + &Cache::deleteLeftUsers, + this, + [this](const std::string &user_id) { deleteUserCache(user_id); }, + Qt::QueuedConnection); } void @@ -1011,7 +1029,7 @@ Cache::saveState(const mtx::responses::Sync &res) savePresence(txn, res.presence); - // updateUserCache(res.device_lists); + updateUserCache(res.device_lists); removeLeftRooms(txn, res.rooms.leave); @@ -2889,13 +2907,15 @@ Cache::statusMessage(const std::string &user_id) void to_json(json &j, const UserCache &info) { - j["keys"] = info.keys; + j["keys"] = info.keys; + j["isUpdated"] = info.isUpdated; } void from_json(const json &j, UserCache &info) { - info.keys = j.at("keys").get(); + info.keys = j.at("keys").get(); + info.isUpdated = j.at("isUpdated").get(); } std::optional @@ -2935,26 +2955,12 @@ Cache::setUserCache(const std::string &user_id, const UserCache &body) void Cache::updateUserCache(const mtx::responses::DeviceLists body) { - for (auto user_id : body.changed) { - mtx::requests::QueryKeys req; - req.device_keys[user_id] = {}; - - http::client()->query_keys( - req, - [user_id, this](const mtx::responses::QueryKeys res, mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to query device keys: {},{}", - err->matrix_error.errcode, - static_cast(err->status_code)); - return; - } - - setUserCache(user_id, UserCache{std::move(res)}); - }); + for (std::string user_id : body.changed) { + emit updateUserCacheFlag(user_id); } for (std::string user_id : body.left) { - deleteUserCache(user_id); + emit deleteLeftUsers(user_id); } } diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index ba746f59..1dde21ce 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -71,9 +71,12 @@ struct UserCache { //! map of public key key_ids and their public_key mtx::responses::QueryKeys keys; + //! if the current cache is updated or not + bool isUpdated = false; - UserCache(mtx::responses::QueryKeys res) + UserCache(mtx::responses::QueryKeys res, bool isUpdated_ = false) : keys(res) + , isUpdated(isUpdated_) {} UserCache() {} }; diff --git a/src/Cache_p.h b/src/Cache_p.h index f75b0f4e..174090a9 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -269,6 +269,8 @@ signals: void newReadReceipts(const QString &room_id, const std::vector &event_ids); void roomReadStatus(const std::map &status); void removeNotification(const QString &room_id, const QString &event_id); + void updateUserCacheFlag(const std::string &user_id); + void deleteLeftUsers(const std::string &user_id); private: //! Save an invited room. diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index b97b6b30..f8cb31a2 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -52,6 +52,8 @@ #include "blurhash.hpp" +#include // only for debugging + // TODO: Needs to be updated with an actual secret. static const std::string STORAGE_SECRET_KEY("secret"); @@ -1446,3 +1448,36 @@ ChatPage::initiateLogout() emit showOverlayProgressBar(); } + +void +ChatPage::query_keys( + const mtx::requests::QueryKeys &req, + std::function cb) +{ + std::string user_id = req.device_keys.begin()->first; + auto cache_ = cache::getUserCache(user_id); + + if (cache_.has_value()) { + if (cache_.value().isUpdated) { + cb(cache_.value().keys, {}); + } else { + http::client()->query_keys( + req, + [cb, user_id](const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {},{}", + err->matrix_error.errcode, + static_cast(err->status_code)); + return; + } + std::cout << "Over here " << user_id << std::endl; + cache::setUserCache(std::move(user_id), + std::move(UserCache{res, true})); + cb(res, err); + }); + } + } else { + http::client()->query_keys(req, cb); + } +} diff --git a/src/ChatPage.h b/src/ChatPage.h index 72adfe19..10801342 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -89,6 +89,9 @@ public: //! Show the room/group list (if it was visible). void showSideBars(); void initiateLogout(); + void query_keys( + const mtx::requests::QueryKeys &req, + std::function cb); void focusMessageInput(); QString status() const; diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 87eae001..2426fe6c 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -85,79 +85,124 @@ UserProfile::getUserStatus() } void -UserProfile::callback_fn(const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err, - std::string user_id) +UserProfile::fetchDeviceList(const QString &userID) { - if (err) { - nhlog::net()->warn("failed to query device keys: {},{}", - err->matrix_error.errcode, - static_cast(err->status_code)); - return; - } + auto localUser = utils::localUser(); - if (res.device_keys.empty() || (res.device_keys.find(user_id) == res.device_keys.end())) { - nhlog::net()->warn("no devices retrieved {}", user_id); - return; - } + mtx::requests::QueryKeys req; + req.device_keys[userID.toStdString()] = {}; + ChatPage::instance()->query_keys( + req, + [user_id = userID.toStdString(), local_user_id = localUser.toStdString(), this]( + const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {},{}", + err->matrix_error.errcode, + static_cast(err->status_code)); + return; + } - auto devices = res.device_keys.at(user_id); - std::vector deviceInfo; - auto device_verified = cache::getVerifiedCache(user_id); - - for (const auto &d : devices) { - auto device = d.second; - - // TODO: Verify signatures and ignore those that don't pass. - verification::Status verified = verification::Status::UNVERIFIED; - isUserVerified = device_verified->is_user_verified; - if (device_verified.has_value()) { - if (std::find(device_verified->cross_verified.begin(), - device_verified->cross_verified.end(), - d.first) != device_verified->cross_verified.end()) - verified = verification::Status::VERIFIED; - if (std::find(device_verified->device_verified.begin(), - device_verified->device_verified.end(), - d.first) != device_verified->device_verified.end()) - verified = verification::Status::VERIFIED; - if (std::find(device_verified->device_blocked.begin(), - device_verified->device_blocked.end(), - d.first) != device_verified->device_blocked.end()) - verified = verification::Status::BLOCKED; - } + if (res.device_keys.empty() || + (res.device_keys.find(user_id) == res.device_keys.end())) { + nhlog::net()->warn("no devices retrieved {}", user_id); + return; + } - deviceInfo.push_back( - {QString::fromStdString(d.first), - QString::fromStdString(device.unsigned_info.device_display_name), - verified}); - } + auto devices = res.device_keys.at(user_id); + std::vector deviceInfo; + auto device_verified = cache::getVerifiedCache(user_id); - std::sort( - deviceInfo.begin(), deviceInfo.end(), [](const DeviceInfo &a, const DeviceInfo &b) { - return a.device_id > b.device_id; - }); + for (const auto &d : devices) { + auto device = d.second; - this->deviceList_.queueReset(std::move(deviceInfo)); -} + // TODO: Verify signatures and ignore those that don't pass. + verification::Status verified = verification::Status::UNVERIFIED; + isUserVerified = device_verified->is_user_verified; + if (device_verified.has_value()) { + if (std::find(device_verified->cross_verified.begin(), + device_verified->cross_verified.end(), + d.first) != device_verified->cross_verified.end()) + verified = verification::Status::VERIFIED; + if (std::find(device_verified->device_verified.begin(), + device_verified->device_verified.end(), + d.first) != device_verified->device_verified.end()) + verified = verification::Status::VERIFIED; + if (std::find(device_verified->device_blocked.begin(), + device_verified->device_blocked.end(), + d.first) != device_verified->device_blocked.end()) + verified = verification::Status::BLOCKED; + } -void -UserProfile::fetchDeviceList(const QString &userID) -{ - auto localUser = utils::localUser(); - auto user_cache = cache::getUserCache(userID.toStdString()); - - if (user_cache.has_value()) { - this->callback_fn(user_cache->keys, {}, userID.toStdString()); - } else { - mtx::requests::QueryKeys req; - req.device_keys[userID.toStdString()] = {}; - http::client()->query_keys( - req, - [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { - this->callback_fn(res, err, user_id); - }); - } + deviceInfo.push_back( + {QString::fromStdString(d.first), + QString::fromStdString(device.unsigned_info.device_display_name), + verified}); + } + + // Finding if the User is Verified or not based on the Signatures + mtx::requests::QueryKeys req; + req.device_keys[local_user_id] = {}; + + ChatPage::instance()->query_keys( + req, + [&local_user_id, &user_id, other_res = res, this]( + const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) { + using namespace mtx; + + if (err) { + nhlog::net()->warn("failed to query device keys: {},{}", + err->matrix_error.errcode, + static_cast(err->status_code)); + return; + } + + std::optional lmk, lsk, luk, mk, sk, uk; + + if (res.master_keys.find(local_user_id) != res.master_keys.end()) + lmk = res.master_keys.at(local_user_id); + if (res.user_signing_keys.find(local_user_id) != + res.user_signing_keys.end()) + luk = res.user_signing_keys.at(local_user_id); + if (res.self_signing_keys.find(local_user_id) != + res.self_signing_keys.end()) + lsk = res.self_signing_keys.at(local_user_id); + if (other_res.master_keys.find(user_id) != other_res.master_keys.end()) + mk = other_res.master_keys.at(user_id); + if (other_res.user_signing_keys.find(user_id) != + other_res.user_signing_keys.end()) + uk = other_res.user_signing_keys.at(user_id); + if (other_res.self_signing_keys.find(user_id) != + other_res.self_signing_keys.end()) + sk = other_res.self_signing_keys.at(user_id); + + // First checking if the user is verified + if (lmk.has_value() && luk.has_value()) { + bool is_user_verified = false; + for (auto sign_key : lmk.value().keys) { + if (!luk.value().signatures.empty()) { + for (auto signature : + luk.value().signatures.at(local_user_id)) { + is_user_verified = + is_user_verified || + (olm::client()->ed25519_verify_sig( + sign_key.second, + json(luk.value()), + signature.second)); + } + } + } + std::cout << (isUserVerified ? "Yes" : "No") << std::endl; + } + }); + + std::sort(deviceInfo.begin(), + deviceInfo.end(), + [](const DeviceInfo &a, const DeviceInfo &b) { + return a.device_id > b.device_id; + }); + + this->deviceList_.queueReset(std::move(deviceInfo)); + }); } void -- cgit 1.5.1 From e8eeb480d51b6fc60c9807dd92195e9068582592 Mon Sep 17 00:00:00 2001 From: Chethan2k1 <40890937+Chethan2k1@users.noreply.github.com> Date: Fri, 4 Sep 2020 12:02:24 +0530 Subject: Fix Wrong Emojis Issue in Room Verification --- resources/qml/MatrixText.qml | 2 +- src/Cache.cpp | 38 +++---- src/ChatPage.cpp | 11 +- src/DeviceVerificationFlow.cpp | 28 ++--- src/DeviceVerificationFlow.h | 2 +- src/timeline/EventStore.cpp | 231 +++++++++++++++++++++++------------------ src/timeline/TimelineModel.cpp | 33 +++--- src/ui/UserProfile.h | 2 +- 8 files changed, 190 insertions(+), 157 deletions(-) (limited to 'src/Cache.cpp') diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml index bbbb80cf..29214168 100644 --- a/resources/qml/MatrixText.qml +++ b/resources/qml/MatrixText.qml @@ -19,7 +19,7 @@ TextEdit { TimelineManager.setHistoryView(match[1]) chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain) } - else timelineManager.openLink(link) + else TimelineManager.openLink(link) } MouseArea { diff --git a/src/Cache.cpp b/src/Cache.cpp index 5302218a..07d01819 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -139,24 +139,26 @@ Cache::Cache(const QString &userId, QObject *parent) , localUserId_{userId} { setup(); - connect(this, - &Cache::updateUserCacheFlag, - this, - [this](const std::string &user_id) { - std::optional cache_ = getUserCache(user_id); - if (cache_.has_value()) { - cache_.value().isUpdated = false; - setUserCache(user_id, cache_.value()); - } else { - setUserCache(user_id, UserCache{}); - } - }, - Qt::QueuedConnection); - connect(this, - &Cache::deleteLeftUsers, - this, - [this](const std::string &user_id) { deleteUserCache(user_id); }, - Qt::QueuedConnection); + connect( + this, + &Cache::updateUserCacheFlag, + this, + [this](const std::string &user_id) { + std::optional cache_ = getUserCache(user_id); + if (cache_.has_value()) { + cache_.value().isUpdated = false; + setUserCache(user_id, cache_.value()); + } else { + setUserCache(user_id, UserCache{}); + } + }, + Qt::QueuedConnection); + connect( + this, + &Cache::deleteLeftUsers, + this, + [this](const std::string &user_id) { deleteUserCache(user_id); }, + Qt::QueuedConnection); } void diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 31ba38d7..704543b5 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -606,11 +606,12 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) connect( this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection); connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection); - connect(this, - &ChatPage::tryDelayedSyncCb, - this, - [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); }, - Qt::QueuedConnection); + connect( + this, + &ChatPage::tryDelayedSyncCb, + this, + [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); }, + Qt::QueuedConnection); connect(this, &ChatPage::newSyncResponse, diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 37866716..ae054af7 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -28,10 +28,10 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, connect(this->model_, &TimelineModel::updateFlowEventId, this, - [this](std::string event_id) { + [this](std::string event_id_) { this->relation.rel_type = mtx::common::RelationType::Reference; - this->relation.event_id = event_id; - this->transaction_id = event_id; + this->relation.event_id = event_id_; + this->transaction_id = event_id_; }); } @@ -60,7 +60,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, msg.hashes.end()) && (std::find(msg.message_authentication_codes.begin(), msg.message_authentication_codes.end(), - "hmac-sha256") != 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::Decimal) != @@ -236,11 +236,15 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, &ChatPage::recievedDeviceVerificationReady, this, [this](const mtx::events::msg::KeyVerificationReady &msg) { - if (!sender && msg.from_device != http::client()->device_id()) { - this->deleteLater(); - emit verificationCanceled(); + if (!sender) { + if (msg.from_device != http::client()->device_id()) { + this->deleteLater(); + emit verificationCanceled(); + } + return; } + if (msg.transaction_id.has_value()) { if (msg.transaction_id.value() != this->transaction_id) return; @@ -353,9 +357,9 @@ DeviceVerificationFlow::setMethod(DeviceVerificationFlow::Method method_) } void -DeviceVerificationFlow::setType(Type type) +DeviceVerificationFlow::setType(Type type_) { - this->type = type; + this->type = type_; } void @@ -367,11 +371,11 @@ DeviceVerificationFlow::setSender(bool sender_) } void -DeviceVerificationFlow::setEventId(std::string event_id) +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; + this->relation.event_id = event_id_; + this->transaction_id = event_id_; } //! accepts a verification diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index 6b2ab81f..c2150980 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -10,7 +10,7 @@ class QTimer; using sas_ptr = std::unique_ptr; -struct TimelineModel; +class TimelineModel; class DeviceVerificationFlow : public QObject { diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index bfc16a02..e5eaefb8 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -32,38 +32,40 @@ EventStore::EventStore(std::string room_id, QObject *) this->last = range->last; } - connect(this, - &EventStore::eventFetched, - this, - [this](std::string id, - std::string relatedTo, - mtx::events::collections::TimelineEvents timeline) { - cache::client()->storeEvent(room_id_, id, {timeline}); - - if (!relatedTo.empty()) { - auto idx = idToIndex(relatedTo); - if (idx) - emit dataChanged(*idx, *idx); - } - }, - Qt::QueuedConnection); - - connect(this, - &EventStore::oldMessagesRetrieved, - this, - [this](const mtx::responses::Messages &res) { - uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); - if (newFirst == first && !res.chunk.empty()) - fetchMore(); - else { - emit beginInsertRows(toExternalIdx(newFirst), - toExternalIdx(this->first - 1)); - this->first = newFirst; - emit endInsertRows(); - emit fetchedMore(); - } - }, - Qt::QueuedConnection); + connect( + this, + &EventStore::eventFetched, + this, + [this](std::string id, + std::string relatedTo, + mtx::events::collections::TimelineEvents timeline) { + cache::client()->storeEvent(room_id_, id, {timeline}); + + if (!relatedTo.empty()) { + auto idx = idToIndex(relatedTo); + if (idx) + emit dataChanged(*idx, *idx); + } + }, + Qt::QueuedConnection); + + connect( + this, + &EventStore::oldMessagesRetrieved, + this, + [this](const mtx::responses::Messages &res) { + uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); + if (newFirst == first) + fetchMore(); + else { + emit beginInsertRows(toExternalIdx(newFirst), + toExternalIdx(this->first - 1)); + this->first = newFirst; + emit endInsertRows(); + emit fetchedMore(); + } + }, + Qt::QueuedConnection); connect(this, &EventStore::processPending, this, [this]() { if (!current_txn.empty()) { @@ -128,46 +130,48 @@ EventStore::EventStore(std::string room_id, QObject *) event->data); }); - connect(this, - &EventStore::messageFailed, - this, - [this](std::string txn_id) { - if (current_txn == txn_id) { - current_txn_error_count++; - if (current_txn_error_count > 10) { - nhlog::ui()->debug("failing txn id '{}'", txn_id); - cache::client()->removePendingStatus(room_id_, txn_id); - current_txn_error_count = 0; - } - } - QTimer::singleShot(1000, this, [this]() { - nhlog::ui()->debug("timeout"); - this->current_txn = ""; - emit processPending(); - }); - }, - Qt::QueuedConnection); - - connect(this, - &EventStore::messageSent, - this, - [this](std::string txn_id, std::string event_id) { - nhlog::ui()->debug("sent {}", txn_id); - - http::client()->read_event( - room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to read_event ({}, {})", room_id_, event_id); - } - }); - - cache::client()->removePendingStatus(room_id_, txn_id); - this->current_txn = ""; - this->current_txn_error_count = 0; - emit processPending(); - }, - Qt::QueuedConnection); + connect( + this, + &EventStore::messageFailed, + this, + [this](std::string txn_id) { + if (current_txn == txn_id) { + current_txn_error_count++; + if (current_txn_error_count > 10) { + nhlog::ui()->debug("failing txn id '{}'", txn_id); + cache::client()->removePendingStatus(room_id_, txn_id); + current_txn_error_count = 0; + } + } + QTimer::singleShot(1000, this, [this]() { + nhlog::ui()->debug("timeout"); + this->current_txn = ""; + emit processPending(); + }); + }, + Qt::QueuedConnection); + + connect( + this, + &EventStore::messageSent, + this, + [this](std::string txn_id, std::string event_id) { + nhlog::ui()->debug("sent {}", txn_id); + + http::client()->read_event( + room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn( + "failed to read_event ({}, {})", room_id_, event_id); + } + }); + + cache::client()->removePendingStatus(room_id_, txn_id); + this->current_txn = ""; + this->current_txn_error_count = 0; + emit processPending(); + }, + Qt::QueuedConnection); } void @@ -280,50 +284,77 @@ EventStore::handleSync(const mtx::responses::Timeline &events) if (auto encrypted = std::get_if>( &event)) { - auto event = decryptEvent({room_id_, encrypted->event_id}, *encrypted); + auto d_event = decryptEvent({room_id_, encrypted->event_id}, *encrypted); if (std::visit( [](auto e) { return (e.sender != utils::localUser().toStdString()); }, - *event)) { - if (auto msg = std::get_if>(event)) { + *d_event)) { + if (std::get_if>(d_event)) { + auto msg = std::get_if>(d_event); last_verification_request_event = *msg; - } else if (auto msg = std::get_if>(event)) { + continue; + } else if (std::get_if>(d_event)) { + auto msg = std::get_if>(d_event); last_verification_cancel_event = *msg; ChatPage::instance()->recievedDeviceVerificationCancel( msg->content); - } else if (auto msg = std::get_if>(event)) { + continue; + } else if (std::get_if>(d_event)) { + auto msg = std::get_if>(d_event); ChatPage::instance()->recievedDeviceVerificationAccept( msg->content); - } else if (auto msg = std::get_if>(event)) { + continue; + } else if (std::get_if>(d_event)) { + auto msg = std::get_if>(d_event); ChatPage::instance()->recievedDeviceVerificationKey( msg->content); - } else if (auto msg = std::get_if>(event)) { + continue; + } else if (std::get_if>(d_event)) { + auto msg = std::get_if>(d_event); ChatPage::instance()->recievedDeviceVerificationMac( msg->content); - } else if (auto msg = std::get_if>(event)) { + continue; + } else if (std::get_if>(d_event)) { + auto msg = std::get_if>(d_event); ChatPage::instance()->recievedDeviceVerificationReady( msg->content); - } else if (auto msg = std::get_if>(event)) { + continue; + } else if (std::get_if>(d_event)) { + auto msg = std::get_if>(d_event); ChatPage::instance()->recievedDeviceVerificationDone( msg->content); - } else if (auto msg = std::get_if>(event)) { + continue; + } else if (std::get_if>(d_event)) { + auto msg = std::get_if>(d_event); ChatPage::instance()->recievedDeviceVerificationStart( msg->content, msg->sender); + continue; + } + } else { + // only the key.verification.ready sent by localuser's other device + // is of significance as it is used for detecting accepted request + if (std::get_if>(d_event)) { + auto msg = std::get_if>(d_event); + ChatPage::instance()->recievedDeviceVerificationReady( + msg->content); } - } - // only the key.verification.ready sent by localuser's other device is of - // significance as it is used for detecting accepted request - if (auto msg = std::get_if< - mtx::events::RoomEvent>( - event)) { - ChatPage::instance()->recievedDeviceVerificationReady(msg->content); } } } @@ -614,12 +645,6 @@ EventStore::decryptEvent(const IdIndex &idx, return asCacheEntry(std::move(temp_events[0])); } - dummy.content.body = tr("-- Encrypted Event (Unknown event type) --", - "Placeholder, when the message was decrypted, but we " - "couldn't parse it, because " - "Nheko/mtxclient don't support that event type yet.") - .toStdString(); - return asCacheEntry(std::move(dummy)); auto encInfo = mtx::accessors::file(decryptionResult.event.value()); if (encInfo) emit newEncryptedImage(encInfo.value()); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 8f0e470e..570186a5 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -204,11 +204,12 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj , room_id_(room_id) , manager_(manager) { - connect(this, - &TimelineModel::redactionFailed, - this, - [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, - Qt::QueuedConnection); + connect( + this, + &TimelineModel::redactionFailed, + this, + [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, + Qt::QueuedConnection); connect(this, &TimelineModel::newMessageToSend, @@ -217,17 +218,17 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj Qt::QueuedConnection); connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending); - connect(&events, - &EventStore::dataChanged, - this, - [this](int from, int to) { - nhlog::ui()->debug("data changed {} to {}", - events.size() - to - 1, - events.size() - from - 1); - emit dataChanged(index(events.size() - to - 1, 0), - index(events.size() - from - 1, 0)); - }, - Qt::QueuedConnection); + connect( + &events, + &EventStore::dataChanged, + this, + [this](int from, int to) { + nhlog::ui()->debug( + "data changed {} to {}", events.size() - to - 1, events.size() - from - 1); + emit dataChanged(index(events.size() - to - 1, 0), + index(events.size() - from - 1, 0)); + }, + Qt::QueuedConnection); connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) { int first = events.size() - to; diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index 3d0d2981..de55b6ab 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -59,7 +59,7 @@ public: connect(this, &DeviceInfoModel::queueReset, this, &DeviceInfoModel::reset); }; QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const + int rowCount(const QModelIndex &parent = QModelIndex()) const override { (void)parent; return (int)deviceList_.size(); -- cgit 1.5.1 From 8eb74daf766ea32804171154d45cb28e32e39e40 Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Mon, 14 Sep 2020 17:27:49 +0530 Subject: Split qml part of Device Verification --- resources/qml/UserProfile.qml | 4 +- .../AcceptNewVerificationRequest.qml | 65 +++ .../AwaitingVerificationConfirmation.qml | 48 ++ .../AwaitingVerificationRequest.qml | 48 ++ .../qml/device-verification/DeviceVerification.qml | 621 ++------------------- .../qml/device-verification/DigitVerification.qml | 80 +++ resources/qml/device-verification/EmojiElement.qml | 1 + .../qml/device-verification/EmojiVerification.qml | 160 ++++++ .../device-verification/NewVerificationRequest.qml | 71 +++ .../qml/device-verification/PartnerAborted.qml | 42 ++ resources/qml/device-verification/TimedOut.qml | 44 ++ .../device-verification/VerificationSuccess.qml | 43 ++ resources/res.qrc | 9 + src/Cache.cpp | 38 +- src/ChatPage.cpp | 11 +- src/emoji/EmojiSearchModel.h | 1 - src/timeline/EventStore.cpp | 148 +++-- src/timeline/TimelineModel.cpp | 33 +- 18 files changed, 757 insertions(+), 710 deletions(-) create mode 100644 resources/qml/device-verification/AcceptNewVerificationRequest.qml create mode 100644 resources/qml/device-verification/AwaitingVerificationConfirmation.qml create mode 100644 resources/qml/device-verification/AwaitingVerificationRequest.qml create mode 100644 resources/qml/device-verification/DigitVerification.qml create mode 100644 resources/qml/device-verification/EmojiVerification.qml create mode 100644 resources/qml/device-verification/NewVerificationRequest.qml create mode 100644 resources/qml/device-verification/PartnerAborted.qml create mode 100644 resources/qml/device-verification/TimedOut.qml create mode 100644 resources/qml/device-verification/VerificationSuccess.qml (limited to 'src/Cache.cpp') diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 9d59184d..1ca9dcc8 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -70,8 +70,8 @@ ApplicationWindow{ id: verifyUserButton text: "Verify" Layout.alignment: Qt.AlignHCenter - enabled: profile.isUserVerified - visible: profile.isUserVerified + enabled: !profile.isUserVerified + visible: !profile.isUserVerified onClicked: { var newFlow = profile.createFlow(true); diff --git a/resources/qml/device-verification/AcceptNewVerificationRequest.qml b/resources/qml/device-verification/AcceptNewVerificationRequest.qml new file mode 100644 index 00000000..872fabe1 --- /dev/null +++ b/resources/qml/device-verification/AcceptNewVerificationRequest.qml @@ -0,0 +1,65 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.10 +import QtQuick.Layouts 1.10 + +import im.nheko 1.0 + +Pane { + property string title: qsTr("Recieving Device Verification Request") + Component { + id: awaitingVerificationRequestAccept + AwaitingVerificationRequest {} + } + ColumnLayout { + spacing: 16 + Label { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + text: qsTr("The device was requested to be verified") + color:colors.text + verticalAlignment: Text.AlignVCenter + } + RowLayout { + Button { + Layout.alignment: Qt.AlignLeft + text: qsTr("Deny") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + flow.cancelVerification(DeviceVerificationFlow.User); + deviceVerificationList.remove(tran_id); + dialog.destroy(); + } + } + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: qsTr("Accept") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + stack.replace(awaitingVerificationRequestAccept); + isRequest?flow.sendVerificationReady():flow.acceptVerificationRequest(); + } + } + } + } +} diff --git a/resources/qml/device-verification/AwaitingVerificationConfirmation.qml b/resources/qml/device-verification/AwaitingVerificationConfirmation.qml new file mode 100644 index 00000000..e0786343 --- /dev/null +++ b/resources/qml/device-verification/AwaitingVerificationConfirmation.qml @@ -0,0 +1,48 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.10 +import QtQuick.Layouts 1.10 + +import im.nheko 1.0 + +Pane { + property string title: qsTr("Awaiting Confirmation") + ColumnLayout { + spacing: 16 + Label { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + id: content + text: qsTr("Waiting for other side to complete verification.") + color:colors.text + verticalAlignment: Text.AlignVCenter + } + BusyIndicator { + Layout.alignment: Qt.AlignHCenter + } + RowLayout { + Button { + Layout.alignment: Qt.AlignLeft + text: qsTr("Cancel") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + flow.cancelVerification(DeviceVerificationFlow.User); + deviceVerificationList.remove(tran_id); + dialog.destroy(); + } + } + Item { + Layout.fillWidth: true + } + } + } +} diff --git a/resources/qml/device-verification/AwaitingVerificationRequest.qml b/resources/qml/device-verification/AwaitingVerificationRequest.qml new file mode 100644 index 00000000..22a504c2 --- /dev/null +++ b/resources/qml/device-verification/AwaitingVerificationRequest.qml @@ -0,0 +1,48 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.10 +import QtQuick.Layouts 1.10 + +import im.nheko 1.0 + +Pane { + property string title: qsTr("Waiting for other party") + ColumnLayout { + spacing: 16 + Label { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + id: content + text: qsTr("Waiting for other side to accept the verification request.") + color:colors.text + verticalAlignment: Text.AlignVCenter + } + BusyIndicator { + Layout.alignment: Qt.AlignHCenter + } + RowLayout { + Button { + Layout.alignment: Qt.AlignLeft + text: qsTr("Cancel") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + flow.cancelVerification(DeviceVerificationFlow.User); + deviceVerificationList.remove(tran_id); + dialog.destroy(); + } + } + Item { + Layout.fillWidth: true + } + } + } +} diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index 6e4b4621..e409b0fe 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -1,11 +1,14 @@ import QtQuick 2.3 import QtQuick.Controls 2.10 import QtQuick.Window 2.2 -import QtQuick.Layouts 1.10 import im.nheko 1.0 ApplicationWindow { + property var flow + property bool isRequest + property var tran_id + title: stack.currentItem.title id: dialog @@ -15,6 +18,17 @@ ApplicationWindow { height: stack.implicitHeight width: stack.implicitWidth + + Component{ + id: newVerificationRequest + NewVerificationRequest {} + } + + Component{ + id: acceptNewVerificationRequest + AcceptNewVerificationRequest {} + } + StackView { id: stack initialItem: flow.sender == true?newVerificationRequest:acceptNewVerificationRequest @@ -22,613 +36,44 @@ ApplicationWindow { implicitHeight: currentItem.implicitHeight } - property var flow - property bool isRequest - property var tran_id - - Connections { - target: flow - onVerificationCanceled: stack.replace(partnerAborted) - onTimedout: stack.replace(timedout) - onDeviceVerified: stack.replace(verificationSuccess) - - onVerificationRequestAccepted: switch(method) { - case DeviceVerificationFlow.Decimal: stack.replace(digitVerification); break; - case DeviceVerificationFlow.Emoji: stack.replace(emojiVerification); break; - } - - onRefreshProfile: { - deviceVerificationList.updateProfile(flow.userId); - } - } - Component { - id: newVerificationRequest - Pane { - property string title: qsTr("Sending Device Verification Request") - ColumnLayout { - spacing: 16 - Label { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - text: qsTr("A new device was added.") - color:colors.text - verticalAlignment: Text.AlignVCenter - } - - Label { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - text: qsTr("The device may have been added by you signing in from another client or physical device. To ensure that no malicious user can eavesdrop on your encrypted communications, you should verify the new device.") - color:colors.text - verticalAlignment: Text.AlignVCenter - } - - RowLayout { - Button { - Layout.alignment: Qt.AlignLeft - text: qsTr("Cancel") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { - deviceVerificationList.remove(tran_id); - flow.deleteFlow(); - dialog.destroy(); - } - } - Item { - Layout.fillWidth: true - } - Button { - Layout.alignment: Qt.AlignRight - text: qsTr("Start verification") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { - stack.replace(awaitingVerificationRequestAccept); - isRequest?flow.sendVerificationRequest():flow.startVerificationRequest(); } - } - } - } - } + id: partnerAborted + PartnerAborted {} } Component { - id: acceptNewVerificationRequest - Pane { - property string title: qsTr("Recieving Device Verification Request") - ColumnLayout { - spacing: 16 - - Label { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - text: qsTr("The device was requested to be verified") - color:colors.text - verticalAlignment: Text.AlignVCenter - } - RowLayout { - Button { - Layout.alignment: Qt.AlignLeft - text: qsTr("Deny") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { - flow.cancelVerification(DeviceVerificationFlow.User); - deviceVerificationList.remove(tran_id); - dialog.destroy(); - } - } - Item { - Layout.fillWidth: true - } - Button { - Layout.alignment: Qt.AlignRight - text: qsTr("Accept") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { - stack.replace(awaitingVerificationRequestAccept); - isRequest?flow.sendVerificationReady():flow.acceptVerificationRequest(); - } - } - } - } - } + id: timedout + TimedOut {} } Component { - id: awaitingVerificationRequestAccept - Pane { - property string title: qsTr("Waiting for other party") - ColumnLayout { - spacing: 16 - Label { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - id: content - text: qsTr("Waiting for other side to accept the verification request.") - color:colors.text - verticalAlignment: Text.AlignVCenter - } - - BusyIndicator { - Layout.alignment: Qt.AlignHCenter - } - RowLayout { - Button { - Layout.alignment: Qt.AlignLeft - text: qsTr("Cancel") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { - flow.cancelVerification(DeviceVerificationFlow.User); - deviceVerificationList.remove(tran_id); - dialog.destroy(); - } - } - Item { - Layout.fillWidth: true - } - } - } - } + id: verificationSuccess + VerificationSuccess {} } Component { id: digitVerification - Pane { - property string title: qsTr("Verification Code") - ColumnLayout { - spacing: 16 - Label { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!") - color:colors.text - verticalAlignment: Text.AlignVCenter - } - - RowLayout { - Layout.alignment: Qt.AlignHCenter - Label { - font.pixelSize: Qt.application.font.pixelSize * 2 - text: flow.sasList[0] - color:colors.text - } - Label { - font.pixelSize: Qt.application.font.pixelSize * 2 - text: flow.sasList[1] - color:colors.text - } - Label { - font.pixelSize: Qt.application.font.pixelSize * 2 - text: flow.sasList[2] - color:colors.text - } - } - - RowLayout { - Button { - Layout.alignment: Qt.AlignLeft - text: qsTr("They do not match!") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { - flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS); - deviceVerificationList.remove(tran_id); - dialog.destroy(); - } - } - Item { - Layout.fillWidth: true - } - Button { - Layout.alignment: Qt.AlignRight - text: qsTr("They match!") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); } - } - } - } - } + DigitVerification {} } Component { id: emojiVerification - Pane { - property string title: qsTr("Verification Code") - ColumnLayout { - spacing: 16 - Label { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!") - color:colors.text - verticalAlignment: Text.AlignVCenter - } - - RowLayout { - Layout.alignment: Qt.AlignHCenter - - id: emojis - - property var mapping: [ - {"number": 0, "emoji": "🐶", "description": "Dog", "unicode": "U+1F436"}, - {"number": 1, "emoji": "🐱", "description": "Cat", "unicode": "U+1F431"}, - {"number": 2, "emoji": "🦁", "description": "Lion", "unicode": "U+1F981"}, - {"number": 3, "emoji": "🐎", "description": "Horse", "unicode": "U+1F40E"}, - {"number": 4, "emoji": "🦄", "description": "Unicorn", "unicode": "U+1F984"}, - {"number": 5, "emoji": "🐷", "description": "Pig", "unicode": "U+1F437"}, - {"number": 6, "emoji": "🐘", "description": "Elephant", "unicode": "U+1F418"}, - {"number": 7, "emoji": "🐰", "description": "Rabbit", "unicode": "U+1F430"}, - {"number": 8, "emoji": "🐼", "description": "Panda", "unicode": "U+1F43C"}, - {"number": 9, "emoji": "🐓", "description": "Rooster", "unicode": "U+1F413"}, - {"number": 10, "emoji": "🐧", "description": "Penguin", "unicode": "U+1F427"}, - {"number": 11, "emoji": "🐢", "description": "Turtle", "unicode": "U+1F422"}, - {"number": 12, "emoji": "🐟", "description": "Fish", "unicode": "U+1F41F"}, - {"number": 13, "emoji": "🐙", "description": "Octopus", "unicode": "U+1F419"}, - {"number": 14, "emoji": "🦋", "description": "Butterfly", "unicode": "U+1F98B"}, - {"number": 15, "emoji": "🌷", "description": "Flower", "unicode": "U+1F337"}, - {"number": 16, "emoji": "🌳", "description": "Tree", "unicode": "U+1F333"}, - {"number": 17, "emoji": "🌵", "description": "Cactus", "unicode": "U+1F335"}, - {"number": 18, "emoji": "🍄", "description": "Mushroom", "unicode": "U+1F344"}, - {"number": 19, "emoji": "🌏", "description": "Globe", "unicode": "U+1F30F"}, - {"number": 20, "emoji": "🌙", "description": "Moon", "unicode": "U+1F319"}, - {"number": 21, "emoji": "☁️", "description": "Cloud", "unicode": "U+2601U+FE0F"}, - {"number": 22, "emoji": "🔥", "description": "Fire", "unicode": "U+1F525"}, - {"number": 23, "emoji": "🍌", "description": "Banana", "unicode": "U+1F34C"}, - {"number": 24, "emoji": "🍎", "description": "Apple", "unicode": "U+1F34E"}, - {"number": 25, "emoji": "🍓", "description": "Strawberry", "unicode": "U+1F353"}, - {"number": 26, "emoji": "🌽", "description": "Corn", "unicode": "U+1F33D"}, - {"number": 27, "emoji": "🍕", "description": "Pizza", "unicode": "U+1F355"}, - {"number": 28, "emoji": "🎂", "description": "Cake", "unicode": "U+1F382"}, - {"number": 29, "emoji": "❤️", "description": "Heart", "unicode": "U+2764U+FE0F"}, - {"number": 30, "emoji": "😀", "description": "Smiley", "unicode": "U+1F600"}, - {"number": 31, "emoji": "🤖", "description": "Robot", "unicode": "U+1F916"}, - {"number": 32, "emoji": "🎩", "description": "Hat", "unicode": "U+1F3A9"}, - {"number": 33, "emoji": "👓", "description": "Glasses", "unicode": "U+1F453"}, - {"number": 34, "emoji": "🔧", "description": "Spanner", "unicode": "U+1F527"}, - {"number": 35, "emoji": "🎅", "description": "Santa", "unicode": "U+1F385"}, - {"number": 36, "emoji": "👍", "description": "Thumbs Up", "unicode": "U+1F44D"}, - {"number": 37, "emoji": "☂️", "description": "Umbrella", "unicode": "U+2602U+FE0F"}, - {"number": 38, "emoji": "⌛", "description": "Hourglass", "unicode": "U+231B"}, - {"number": 39, "emoji": "⏰", "description": "Clock", "unicode": "U+23F0"}, - {"number": 40, "emoji": "🎁", "description": "Gift", "unicode": "U+1F381"}, - {"number": 41, "emoji": "💡", "description": "Light Bulb", "unicode": "U+1F4A1"}, - {"number": 42, "emoji": "📕", "description": "Book", "unicode": "U+1F4D5"}, - {"number": 43, "emoji": "✏️", "description": "Pencil", "unicode": "U+270FU+FE0F"}, - {"number": 44, "emoji": "📎", "description": "Paperclip", "unicode": "U+1F4CE"}, - {"number": 45, "emoji": "✂️", "description": "Scissors", "unicode": "U+2702U+FE0F"}, - {"number": 46, "emoji": "🔒", "description": "Lock", "unicode": "U+1F512"}, - {"number": 47, "emoji": "🔑", "description": "Key", "unicode": "U+1F511"}, - {"number": 48, "emoji": "🔨", "description": "Hammer", "unicode": "U+1F528"}, - {"number": 49, "emoji": "☎️", "description": "Telephone", "unicode": "U+260EU+FE0F"}, - {"number": 50, "emoji": "🏁", "description": "Flag", "unicode": "U+1F3C1"}, - {"number": 51, "emoji": "🚂", "description": "Train", "unicode": "U+1F682"}, - {"number": 52, "emoji": "🚲", "description": "Bicycle", "unicode": "U+1F6B2"}, - {"number": 53, "emoji": "✈️", "description": "Aeroplane", "unicode": "U+2708U+FE0F"}, - {"number": 54, "emoji": "🚀", "description": "Rocket", "unicode": "U+1F680"}, - {"number": 55, "emoji": "🏆", "description": "Trophy", "unicode": "U+1F3C6"}, - {"number": 56, "emoji": "⚽", "description": "Ball", "unicode": "U+26BD"}, - {"number": 57, "emoji": "🎸", "description": "Guitar", "unicode": "U+1F3B8"}, - {"number": 58, "emoji": "🎺", "description": "Trumpet", "unicode": "U+1F3BA"}, - {"number": 59, "emoji": "🔔", "description": "Bell", "unicode": "U+1F514"}, - {"number": 60, "emoji": "⚓", "description": "Anchor", "unicode": "U+2693"}, - {"number": 61, "emoji": "🎧", "description": "Headphones", "unicode": "U+1F3A7"}, - {"number": 62, "emoji": "📁", "description": "Folder", "unicode": "U+1F4C1"}, - {"number": 63, "emoji": "📌", "description": "Pin", "unicode": "U+1F4CC"} - ] - - Repeater { - id: repeater - model: 7 - delegate: Rectangle { - color: "transparent" - implicitHeight: Qt.application.font.pixelSize * 8 - implicitWidth: col.width - ColumnLayout { - id: col - Layout.fillWidth: true - anchors.bottom: parent.bottom - property var emoji: emojis.mapping[flow.sasList[index]] - Label { - //height: font.pixelSize * 2 - Layout.alignment: Qt.AlignHCenter - text: col.emoji.emoji - font.pixelSize: Qt.application.font.pixelSize * 2 - font.family: Settings.emojiFont - color:colors.text - } - Label { - Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom - text: col.emoji.description - color:colors.text - } - } - } - } - } - - RowLayout { - Button { - Layout.alignment: Qt.AlignLeft - text: qsTr("They do not match!") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { - flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS); - deviceVerificationList.remove(tran_id); - dialog.destroy(); - } - } - Item { - Layout.fillWidth: true - } - Button { - Layout.alignment: Qt.AlignRight - text: qsTr("They match!") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); } - } - } - } - } - } - - Component { - id: awaitingVerificationConfirmation - Pane { - property string title: qsTr("Awaiting Confirmation") - ColumnLayout { - spacing: 16 - Label { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - id: content - text: qsTr("Waiting for other side to complete verification.") - color:colors.text - verticalAlignment: Text.AlignVCenter - } - - BusyIndicator { - Layout.alignment: Qt.AlignHCenter - } - RowLayout { - Button { - Layout.alignment: Qt.AlignLeft - text: qsTr("Cancel") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { - flow.cancelVerification(DeviceVerificationFlow.User); - deviceVerificationList.remove(tran_id); - dialog.destroy(); - } - } - Item { - Layout.fillWidth: true - } - } - } - } + EmojiVerification {} } - Component { - id: verificationSuccess - Pane { - property string title: qsTr("Successful Verification") - ColumnLayout { - spacing: 16 - Label { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - id: content - text: qsTr("Verification successful! Both sides verified their devices!") - color:colors.text - verticalAlignment: Text.AlignVCenter - } - - RowLayout { - Item { - Layout.fillWidth: true - } - Button { - Layout.alignment: Qt.AlignRight - text: qsTr("Close") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { - deviceVerificationList.remove(tran_id); - flow.deleteFlow(); - dialog.destroy(); - } - } - } - } - } - } - - Component { - id: partnerAborted - Pane { - property string title: qsTr("Verification aborted!") - ColumnLayout { - spacing: 16 - Label { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - id: content - text: qsTr("Verification canceled by the other party!") - color:colors.text - verticalAlignment: Text.AlignVCenter - } + Connections { + target: flow + onVerificationCanceled: stack.replace(partnerAborted) + onTimedout: stack.replace(timedout) + onDeviceVerified: stack.replace(verificationSuccess) - RowLayout { - Item { - Layout.fillWidth: true - } - Button { - Layout.alignment: Qt.AlignRight - text: qsTr("Close") - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - onClicked: { - deviceVerificationList.remove(tran_id); - dialog.destroy(); - } - } - } - } + onVerificationRequestAccepted: switch(method) { + case DeviceVerificationFlow.Decimal: stack.replace(digitVerification); break; + case DeviceVerificationFlow.Emoji: stack.replace(emojiVerification); break; } - } - Component { - id: timedout - Pane { - property string title: qsTr("Verification timed out") - ColumnLayout { - spacing: 16 - Text { - Layout.maximumWidth: 400 - Layout.fillHeight: true - Layout.fillWidth: true - wrapMode: Text.Wrap - id: content - text: qsTr("Device verification timed out.") - color:colors.text - verticalAlignment: Text.AlignVCenter - } - - RowLayout { - Item { - Layout.fillWidth: true - } - Button { - id: timedOutCancel - Layout.alignment: Qt.AlignRight - palette { - button: "white" - } - contentItem: Text { - text: parent.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - text: qsTr("Close") - onClicked: { - deviceVerificationList.remove(tran_id); - flow.deleteFlow(); - dialog.destroy() - } - } - } - } + onRefreshProfile: { + deviceVerificationList.updateProfile(flow.userId); } } } diff --git a/resources/qml/device-verification/DigitVerification.qml b/resources/qml/device-verification/DigitVerification.qml new file mode 100644 index 00000000..241ccbd0 --- /dev/null +++ b/resources/qml/device-verification/DigitVerification.qml @@ -0,0 +1,80 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.10 +import QtQuick.Layouts 1.10 + +import im.nheko 1.0 + +Pane { + property string title: qsTr("Verification Code") + Component { + id: awaitingVerificationConfirmation + AwaitingVerificationConfirmation {} + } + ColumnLayout { + spacing: 16 + Label { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!") + color:colors.text + verticalAlignment: Text.AlignVCenter + } + RowLayout { + Layout.alignment: Qt.AlignHCenter + Label { + font.pixelSize: Qt.application.font.pixelSize * 2 + text: flow.sasList[0] + color:colors.text + } + Label { + font.pixelSize: Qt.application.font.pixelSize * 2 + text: flow.sasList[1] + color:colors.text + } + Label { + font.pixelSize: Qt.application.font.pixelSize * 2 + text: flow.sasList[2] + color:colors.text + } + } + RowLayout { + Button { + Layout.alignment: Qt.AlignLeft + text: qsTr("They do not match!") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS); + deviceVerificationList.remove(tran_id); + dialog.destroy(); + } + } + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: qsTr("They match!") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); } + } + } + } +} \ No newline at end of file diff --git a/resources/qml/device-verification/EmojiElement.qml b/resources/qml/device-verification/EmojiElement.qml index 22f9e414..7e364594 100644 --- a/resources/qml/device-verification/EmojiElement.qml +++ b/resources/qml/device-verification/EmojiElement.qml @@ -1,4 +1,5 @@ import QtQuick 2.3 +import QtQuick.Controls 2.10 import QtQuick.Layouts 1.10 Rectangle { diff --git a/resources/qml/device-verification/EmojiVerification.qml b/resources/qml/device-verification/EmojiVerification.qml new file mode 100644 index 00000000..fae08f25 --- /dev/null +++ b/resources/qml/device-verification/EmojiVerification.qml @@ -0,0 +1,160 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.10 +import QtQuick.Layouts 1.10 + +import im.nheko 1.0 + +Pane { + property string title: qsTr("Verification Code") + Component { + id: awaitingVerificationConfirmation + AwaitingVerificationConfirmation{} + } + ColumnLayout { + spacing: 16 + Label { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!") + color:colors.text + verticalAlignment: Text.AlignVCenter + } + RowLayout { + Layout.alignment: Qt.AlignHCenter + id: emojis + property var mapping: [ + {"number": 0, "emoji": "🐶", "description": "Dog", "unicode": "U+1F436"}, + {"number": 1, "emoji": "🐱", "description": "Cat", "unicode": "U+1F431"}, + {"number": 2, "emoji": "🦁", "description": "Lion", "unicode": "U+1F981"}, + {"number": 3, "emoji": "🐎", "description": "Horse", "unicode": "U+1F40E"}, + {"number": 4, "emoji": "🦄", "description": "Unicorn", "unicode": "U+1F984"}, + {"number": 5, "emoji": "🐷", "description": "Pig", "unicode": "U+1F437"}, + {"number": 6, "emoji": "🐘", "description": "Elephant", "unicode": "U+1F418"}, + {"number": 7, "emoji": "🐰", "description": "Rabbit", "unicode": "U+1F430"}, + {"number": 8, "emoji": "🐼", "description": "Panda", "unicode": "U+1F43C"}, + {"number": 9, "emoji": "🐓", "description": "Rooster", "unicode": "U+1F413"}, + {"number": 10, "emoji": "🐧", "description": "Penguin", "unicode": "U+1F427"}, + {"number": 11, "emoji": "🐢", "description": "Turtle", "unicode": "U+1F422"}, + {"number": 12, "emoji": "🐟", "description": "Fish", "unicode": "U+1F41F"}, + {"number": 13, "emoji": "🐙", "description": "Octopus", "unicode": "U+1F419"}, + {"number": 14, "emoji": "🦋", "description": "Butterfly", "unicode": "U+1F98B"}, + {"number": 15, "emoji": "🌷", "description": "Flower", "unicode": "U+1F337"}, + {"number": 16, "emoji": "🌳", "description": "Tree", "unicode": "U+1F333"}, + {"number": 17, "emoji": "🌵", "description": "Cactus", "unicode": "U+1F335"}, + {"number": 18, "emoji": "🍄", "description": "Mushroom", "unicode": "U+1F344"}, + {"number": 19, "emoji": "🌏", "description": "Globe", "unicode": "U+1F30F"}, + {"number": 20, "emoji": "🌙", "description": "Moon", "unicode": "U+1F319"}, + {"number": 21, "emoji": "☁️", "description": "Cloud", "unicode": "U+2601U+FE0F"}, + {"number": 22, "emoji": "🔥", "description": "Fire", "unicode": "U+1F525"}, + {"number": 23, "emoji": "🍌", "description": "Banana", "unicode": "U+1F34C"}, + {"number": 24, "emoji": "🍎", "description": "Apple", "unicode": "U+1F34E"}, + {"number": 25, "emoji": "🍓", "description": "Strawberry", "unicode": "U+1F353"}, + {"number": 26, "emoji": "🌽", "description": "Corn", "unicode": "U+1F33D"}, + {"number": 27, "emoji": "🍕", "description": "Pizza", "unicode": "U+1F355"}, + {"number": 28, "emoji": "🎂", "description": "Cake", "unicode": "U+1F382"}, + {"number": 29, "emoji": "❤️", "description": "Heart", "unicode": "U+2764U+FE0F"}, + {"number": 30, "emoji": "😀", "description": "Smiley", "unicode": "U+1F600"}, + {"number": 31, "emoji": "🤖", "description": "Robot", "unicode": "U+1F916"}, + {"number": 32, "emoji": "🎩", "description": "Hat", "unicode": "U+1F3A9"}, + {"number": 33, "emoji": "👓", "description": "Glasses", "unicode": "U+1F453"}, + {"number": 34, "emoji": "🔧", "description": "Spanner", "unicode": "U+1F527"}, + {"number": 35, "emoji": "🎅", "description": "Santa", "unicode": "U+1F385"}, + {"number": 36, "emoji": "👍", "description": "Thumbs Up", "unicode": "U+1F44D"}, + {"number": 37, "emoji": "☂️", "description": "Umbrella", "unicode": "U+2602U+FE0F"}, + {"number": 38, "emoji": "⌛", "description": "Hourglass", "unicode": "U+231B"}, + {"number": 39, "emoji": "⏰", "description": "Clock", "unicode": "U+23F0"}, + {"number": 40, "emoji": "🎁", "description": "Gift", "unicode": "U+1F381"}, + {"number": 41, "emoji": "💡", "description": "Light Bulb", "unicode": "U+1F4A1"}, + {"number": 42, "emoji": "📕", "description": "Book", "unicode": "U+1F4D5"}, + {"number": 43, "emoji": "✏️", "description": "Pencil", "unicode": "U+270FU+FE0F"}, + {"number": 44, "emoji": "📎", "description": "Paperclip", "unicode": "U+1F4CE"}, + {"number": 45, "emoji": "✂️", "description": "Scissors", "unicode": "U+2702U+FE0F"}, + {"number": 46, "emoji": "🔒", "description": "Lock", "unicode": "U+1F512"}, + {"number": 47, "emoji": "🔑", "description": "Key", "unicode": "U+1F511"}, + {"number": 48, "emoji": "🔨", "description": "Hammer", "unicode": "U+1F528"}, + {"number": 49, "emoji": "☎️", "description": "Telephone", "unicode": "U+260EU+FE0F"}, + {"number": 50, "emoji": "🏁", "description": "Flag", "unicode": "U+1F3C1"}, + {"number": 51, "emoji": "🚂", "description": "Train", "unicode": "U+1F682"}, + {"number": 52, "emoji": "🚲", "description": "Bicycle", "unicode": "U+1F6B2"}, + {"number": 53, "emoji": "✈️", "description": "Aeroplane", "unicode": "U+2708U+FE0F"}, + {"number": 54, "emoji": "🚀", "description": "Rocket", "unicode": "U+1F680"}, + {"number": 55, "emoji": "🏆", "description": "Trophy", "unicode": "U+1F3C6"}, + {"number": 56, "emoji": "⚽", "description": "Ball", "unicode": "U+26BD"}, + {"number": 57, "emoji": "🎸", "description": "Guitar", "unicode": "U+1F3B8"}, + {"number": 58, "emoji": "🎺", "description": "Trumpet", "unicode": "U+1F3BA"}, + {"number": 59, "emoji": "🔔", "description": "Bell", "unicode": "U+1F514"}, + {"number": 60, "emoji": "⚓", "description": "Anchor", "unicode": "U+2693"}, + {"number": 61, "emoji": "🎧", "description": "Headphones", "unicode": "U+1F3A7"}, + {"number": 62, "emoji": "📁", "description": "Folder", "unicode": "U+1F4C1"}, + {"number": 63, "emoji": "📌", "description": "Pin", "unicode": "U+1F4CC"} + ] + Repeater { + id: repeater + model: 7 + delegate: Rectangle { + color: "transparent" + implicitHeight: Qt.application.font.pixelSize * 8 + implicitWidth: col.width + ColumnLayout { + id: col + Layout.fillWidth: true + anchors.bottom: parent.bottom + property var emoji: emojis.mapping[flow.sasList[index]] + Label { + //height: font.pixelSize * 2 + Layout.alignment: Qt.AlignHCenter + text: col.emoji.emoji + font.pixelSize: Qt.application.font.pixelSize * 2 + font.family: Settings.emojiFont + color:colors.text + } + Label { + Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + text: col.emoji.description + color:colors.text + } + } + } + } + } + RowLayout { + Button { + Layout.alignment: Qt.AlignLeft + text: qsTr("They do not match!") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + flow.cancelVerification(DeviceVerificationFlow.MismatchedSAS); + deviceVerificationList.remove(tran_id); + dialog.destroy(); + } + } + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: qsTr("They match!") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { stack.replace(awaitingVerificationConfirmation); flow.sendVerificationMac(); } + } + } + } +} diff --git a/resources/qml/device-verification/NewVerificationRequest.qml b/resources/qml/device-verification/NewVerificationRequest.qml new file mode 100644 index 00000000..d8fc65a0 --- /dev/null +++ b/resources/qml/device-verification/NewVerificationRequest.qml @@ -0,0 +1,71 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.10 +import QtQuick.Layouts 1.10 + +Pane { + property string title: qsTr("Sending Device Verification Request") + Component { + id: awaitingVerificationRequestAccept + AwaitingVerificationRequest {} + } + ColumnLayout { + spacing: 16 + Label { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + text: qsTr("A new device was added.") + color:colors.text + verticalAlignment: Text.AlignVCenter + } + Label { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + text: qsTr("The device may have been added by you signing in from another client or physical device. To ensure that no malicious user can eavesdrop on your encrypted communications, you should verify the new device.") + color:colors.text + verticalAlignment: Text.AlignVCenter + } + RowLayout { + Button { + Layout.alignment: Qt.AlignLeft + text: qsTr("Cancel") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + deviceVerificationList.remove(tran_id); + flow.deleteFlow(); + dialog.destroy(); + } + } + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: qsTr("Start verification") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + stack.replace(awaitingVerificationRequestAccept); + isRequest?flow.sendVerificationRequest():flow.startVerificationRequest(); } + } + } + } +} diff --git a/resources/qml/device-verification/PartnerAborted.qml b/resources/qml/device-verification/PartnerAborted.qml new file mode 100644 index 00000000..62787b18 --- /dev/null +++ b/resources/qml/device-verification/PartnerAborted.qml @@ -0,0 +1,42 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.10 +import QtQuick.Layouts 1.10 + +Pane { + property string title: qsTr("Verification aborted!") + ColumnLayout { + spacing: 16 + Label { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + id: content + text: qsTr("Verification canceled by the other party!") + color:colors.text + verticalAlignment: Text.AlignVCenter + } + RowLayout { + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: qsTr("Close") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + deviceVerificationList.remove(tran_id); + dialog.destroy(); + } + } + } + } +} \ No newline at end of file diff --git a/resources/qml/device-verification/TimedOut.qml b/resources/qml/device-verification/TimedOut.qml new file mode 100644 index 00000000..40528693 --- /dev/null +++ b/resources/qml/device-verification/TimedOut.qml @@ -0,0 +1,44 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.10 +import QtQuick.Layouts 1.10 + +Pane { + property string title: qsTr("Verification timed out") + ColumnLayout { + spacing: 16 + Text { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + id: content + text: qsTr("Device verification timed out.") + color:colors.text + verticalAlignment: Text.AlignVCenter + } + RowLayout { + Item { + Layout.fillWidth: true + } + Button { + id: timedOutCancel + Layout.alignment: Qt.AlignRight + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + text: qsTr("Close") + onClicked: { + deviceVerificationList.remove(tran_id); + flow.deleteFlow(); + dialog.destroy() + } + } + } + } +} diff --git a/resources/qml/device-verification/VerificationSuccess.qml b/resources/qml/device-verification/VerificationSuccess.qml new file mode 100644 index 00000000..c87488da --- /dev/null +++ b/resources/qml/device-verification/VerificationSuccess.qml @@ -0,0 +1,43 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.10 +import QtQuick.Layouts 1.10 + +Pane { + property string title: qsTr("Successful Verification") + ColumnLayout { + spacing: 16 + Label { + Layout.maximumWidth: 400 + Layout.fillHeight: true + Layout.fillWidth: true + wrapMode: Text.Wrap + id: content + text: qsTr("Verification successful! Both sides verified their devices!") + color:colors.text + verticalAlignment: Text.AlignVCenter + } + RowLayout { + Item { + Layout.fillWidth: true + } + Button { + Layout.alignment: Qt.AlignRight + text: qsTr("Close") + palette { + button: "white" + } + contentItem: Text { + text: parent.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + onClicked: { + deviceVerificationList.remove(tran_id); + if(flow) flow.deleteFlow(); + dialog.destroy(); + } + } + } + } +} diff --git a/resources/res.qrc b/resources/res.qrc index e8f1f7be..7ef7ecf9 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -141,7 +141,16 @@ qml/delegates/Pill.qml qml/delegates/Placeholder.qml qml/delegates/Reply.qml + qml/device-verification/AcceptNewVerificationRequest.qml + qml/device-verification/AwaitingVerificationConfirmation.qml + qml/device-verification/AwaitingVerificationRequest.qml qml/device-verification/DeviceVerification.qml + qml/device-verification/DigitVerification.qml + qml/device-verification/EmojiVerification.qml + qml/device-verification/NewVerificationRequest.qml + qml/device-verification/PartnerAborted.qml + qml/device-verification/TimedOut.qml + qml/device-verification/VerificationSuccess.qml media/ring.ogg diff --git a/src/Cache.cpp b/src/Cache.cpp index 07d01819..5302218a 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -139,26 +139,24 @@ Cache::Cache(const QString &userId, QObject *parent) , localUserId_{userId} { setup(); - connect( - this, - &Cache::updateUserCacheFlag, - this, - [this](const std::string &user_id) { - std::optional cache_ = getUserCache(user_id); - if (cache_.has_value()) { - cache_.value().isUpdated = false; - setUserCache(user_id, cache_.value()); - } else { - setUserCache(user_id, UserCache{}); - } - }, - Qt::QueuedConnection); - connect( - this, - &Cache::deleteLeftUsers, - this, - [this](const std::string &user_id) { deleteUserCache(user_id); }, - Qt::QueuedConnection); + connect(this, + &Cache::updateUserCacheFlag, + this, + [this](const std::string &user_id) { + std::optional cache_ = getUserCache(user_id); + if (cache_.has_value()) { + cache_.value().isUpdated = false; + setUserCache(user_id, cache_.value()); + } else { + setUserCache(user_id, UserCache{}); + } + }, + Qt::QueuedConnection); + connect(this, + &Cache::deleteLeftUsers, + this, + [this](const std::string &user_id) { deleteUserCache(user_id); }, + Qt::QueuedConnection); } void diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 704543b5..31ba38d7 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -606,12 +606,11 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) connect( this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection); connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection); - connect( - this, - &ChatPage::tryDelayedSyncCb, - this, - [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); }, - Qt::QueuedConnection); + connect(this, + &ChatPage::tryDelayedSyncCb, + this, + [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); }, + Qt::QueuedConnection); connect(this, &ChatPage::newSyncResponse, diff --git a/src/emoji/EmojiSearchModel.h b/src/emoji/EmojiSearchModel.h index 1ff5f4e9..13a03934 100644 --- a/src/emoji/EmojiSearchModel.h +++ b/src/emoji/EmojiSearchModel.h @@ -33,5 +33,4 @@ private: return shortname.replace(" ", "-").replace(":", "-").replace("--", "-").toLower(); } }; - } diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index af1f7b23..66a6d799 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -32,40 +32,38 @@ EventStore::EventStore(std::string room_id, QObject *) this->last = range->last; } - connect( - this, - &EventStore::eventFetched, - this, - [this](std::string id, - std::string relatedTo, - mtx::events::collections::TimelineEvents timeline) { - cache::client()->storeEvent(room_id_, id, {timeline}); - - if (!relatedTo.empty()) { - auto idx = idToIndex(relatedTo); - if (idx) - emit dataChanged(*idx, *idx); - } - }, - Qt::QueuedConnection); - - connect( - this, - &EventStore::oldMessagesRetrieved, - this, - [this](const mtx::responses::Messages &res) { - uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); - if (newFirst == first) - fetchMore(); - else { - emit beginInsertRows(toExternalIdx(newFirst), - toExternalIdx(this->first - 1)); - this->first = newFirst; - emit endInsertRows(); - emit fetchedMore(); - } - }, - Qt::QueuedConnection); + connect(this, + &EventStore::eventFetched, + this, + [this](std::string id, + std::string relatedTo, + mtx::events::collections::TimelineEvents timeline) { + cache::client()->storeEvent(room_id_, id, {timeline}); + + if (!relatedTo.empty()) { + auto idx = idToIndex(relatedTo); + if (idx) + emit dataChanged(*idx, *idx); + } + }, + Qt::QueuedConnection); + + connect(this, + &EventStore::oldMessagesRetrieved, + this, + [this](const mtx::responses::Messages &res) { + uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); + if (newFirst == first) + fetchMore(); + else { + emit beginInsertRows(toExternalIdx(newFirst), + toExternalIdx(this->first - 1)); + this->first = newFirst; + emit endInsertRows(); + emit fetchedMore(); + } + }, + Qt::QueuedConnection); connect(this, &EventStore::processPending, this, [this]() { if (!current_txn.empty()) { @@ -130,48 +128,46 @@ EventStore::EventStore(std::string room_id, QObject *) event->data); }); - connect( - this, - &EventStore::messageFailed, - this, - [this](std::string txn_id) { - if (current_txn == txn_id) { - current_txn_error_count++; - if (current_txn_error_count > 10) { - nhlog::ui()->debug("failing txn id '{}'", txn_id); - cache::client()->removePendingStatus(room_id_, txn_id); - current_txn_error_count = 0; - } - } - QTimer::singleShot(1000, this, [this]() { - nhlog::ui()->debug("timeout"); - this->current_txn = ""; - emit processPending(); - }); - }, - Qt::QueuedConnection); - - connect( - this, - &EventStore::messageSent, - this, - [this](std::string txn_id, std::string event_id) { - nhlog::ui()->debug("sent {}", txn_id); - - http::client()->read_event( - room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to read_event ({}, {})", room_id_, event_id); - } - }); - - cache::client()->removePendingStatus(room_id_, txn_id); - this->current_txn = ""; - this->current_txn_error_count = 0; - emit processPending(); - }, - Qt::QueuedConnection); + connect(this, + &EventStore::messageFailed, + this, + [this](std::string txn_id) { + if (current_txn == txn_id) { + current_txn_error_count++; + if (current_txn_error_count > 10) { + nhlog::ui()->debug("failing txn id '{}'", txn_id); + cache::client()->removePendingStatus(room_id_, txn_id); + current_txn_error_count = 0; + } + } + QTimer::singleShot(1000, this, [this]() { + nhlog::ui()->debug("timeout"); + this->current_txn = ""; + emit processPending(); + }); + }, + Qt::QueuedConnection); + + connect(this, + &EventStore::messageSent, + this, + [this](std::string txn_id, std::string event_id) { + nhlog::ui()->debug("sent {}", txn_id); + + http::client()->read_event( + room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn( + "failed to read_event ({}, {})", room_id_, event_id); + } + }); + + cache::client()->removePendingStatus(room_id_, txn_id); + this->current_txn = ""; + this->current_txn_error_count = 0; + emit processPending(); + }, + Qt::QueuedConnection); } void diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 570186a5..8f0e470e 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -204,12 +204,11 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj , room_id_(room_id) , manager_(manager) { - connect( - this, - &TimelineModel::redactionFailed, - this, - [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, - Qt::QueuedConnection); + connect(this, + &TimelineModel::redactionFailed, + this, + [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, + Qt::QueuedConnection); connect(this, &TimelineModel::newMessageToSend, @@ -218,17 +217,17 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj Qt::QueuedConnection); connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending); - connect( - &events, - &EventStore::dataChanged, - this, - [this](int from, int to) { - nhlog::ui()->debug( - "data changed {} to {}", events.size() - to - 1, events.size() - from - 1); - emit dataChanged(index(events.size() - to - 1, 0), - index(events.size() - from - 1, 0)); - }, - Qt::QueuedConnection); + connect(&events, + &EventStore::dataChanged, + this, + [this](int from, int to) { + nhlog::ui()->debug("data changed {} to {}", + events.size() - to - 1, + events.size() - from - 1); + emit dataChanged(index(events.size() - to - 1, 0), + index(events.size() - from - 1, 0)); + }, + Qt::QueuedConnection); connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) { int first = events.size() - to; -- cgit 1.5.1 From 54db9c89ede00bdcc48a64397cb45073b4fc6625 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 20 Sep 2020 23:04:14 +0200 Subject: Simplify outbound session setup Don't send inbound session to self and claim and send all keys at once. --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.json | 2 +- src/Cache.cpp | 38 +++---- src/ChatPage.cpp | 11 +- src/Olm.cpp | 6 +- src/timeline/.TimelineModel.cpp.swn | Bin 0 -> 237568 bytes src/timeline/EventStore.cpp | 148 +++++++++++++------------- src/timeline/TimelineModel.cpp | 204 +++++++++++++++++------------------- src/timeline/TimelineModel.h | 12 +-- 9 files changed, 209 insertions(+), 214 deletions(-) create mode 100644 src/timeline/.TimelineModel.cpp.swn (limited to 'src/Cache.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index 46d83f67..47f08657 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -342,7 +342,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 0665c8baf4af0ce192adb8ca97761b63b681d569 + GIT_TAG f84611f129b46746a4b586acaba54fc31a303bc6 ) FetchContent_MakeAvailable(MatrixClient) else() diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index c71a5771..da1b5a37 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -146,7 +146,7 @@ "name": "mtxclient", "sources": [ { - "commit": "0665c8baf4af0ce192adb8ca97761b63b681d569", + "commit": "f84611f129b46746a4b586acaba54fc31a303bc6", "type": "git", "url": "https://github.com/Nheko-Reborn/mtxclient.git" } diff --git a/src/Cache.cpp b/src/Cache.cpp index 5302218a..07d01819 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -139,24 +139,26 @@ Cache::Cache(const QString &userId, QObject *parent) , localUserId_{userId} { setup(); - connect(this, - &Cache::updateUserCacheFlag, - this, - [this](const std::string &user_id) { - std::optional cache_ = getUserCache(user_id); - if (cache_.has_value()) { - cache_.value().isUpdated = false; - setUserCache(user_id, cache_.value()); - } else { - setUserCache(user_id, UserCache{}); - } - }, - Qt::QueuedConnection); - connect(this, - &Cache::deleteLeftUsers, - this, - [this](const std::string &user_id) { deleteUserCache(user_id); }, - Qt::QueuedConnection); + connect( + this, + &Cache::updateUserCacheFlag, + this, + [this](const std::string &user_id) { + std::optional cache_ = getUserCache(user_id); + if (cache_.has_value()) { + cache_.value().isUpdated = false; + setUserCache(user_id, cache_.value()); + } else { + setUserCache(user_id, UserCache{}); + } + }, + Qt::QueuedConnection); + connect( + this, + &Cache::deleteLeftUsers, + this, + [this](const std::string &user_id) { deleteUserCache(user_id); }, + Qt::QueuedConnection); } void diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 31ba38d7..704543b5 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -606,11 +606,12 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) connect( this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection); connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection); - connect(this, - &ChatPage::tryDelayedSyncCb, - this, - [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); }, - Qt::QueuedConnection); + connect( + this, + &ChatPage::tryDelayedSyncCb, + this, + [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); }, + Qt::QueuedConnection); connect(this, &ChatPage::newSyncResponse, diff --git a/src/Olm.cpp b/src/Olm.cpp index 9e997801..4f0c5893 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -581,9 +581,11 @@ send_megolm_key_to_device(const std::string &user_id, ->create_room_key_event(UserId(user_id), pks.ed25519, payload) .dump(); + mtx::requests::ClaimKeys claim_keys; + claim_keys.one_time_keys[user_id][device_id] = mtx::crypto::SIGNED_CURVE25519; + http::client()->claim_keys( - user_id, - {device_id}, + claim_keys, [room_key, user_id, device_id, pks](const mtx::responses::ClaimKeys &res, mtx::http::RequestErr err) { if (err) { diff --git a/src/timeline/.TimelineModel.cpp.swn b/src/timeline/.TimelineModel.cpp.swn new file mode 100644 index 00000000..9e965702 Binary files /dev/null and b/src/timeline/.TimelineModel.cpp.swn differ diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 66a6d799..af1f7b23 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -32,38 +32,40 @@ EventStore::EventStore(std::string room_id, QObject *) this->last = range->last; } - connect(this, - &EventStore::eventFetched, - this, - [this](std::string id, - std::string relatedTo, - mtx::events::collections::TimelineEvents timeline) { - cache::client()->storeEvent(room_id_, id, {timeline}); - - if (!relatedTo.empty()) { - auto idx = idToIndex(relatedTo); - if (idx) - emit dataChanged(*idx, *idx); - } - }, - Qt::QueuedConnection); - - connect(this, - &EventStore::oldMessagesRetrieved, - this, - [this](const mtx::responses::Messages &res) { - uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); - if (newFirst == first) - fetchMore(); - else { - emit beginInsertRows(toExternalIdx(newFirst), - toExternalIdx(this->first - 1)); - this->first = newFirst; - emit endInsertRows(); - emit fetchedMore(); - } - }, - Qt::QueuedConnection); + connect( + this, + &EventStore::eventFetched, + this, + [this](std::string id, + std::string relatedTo, + mtx::events::collections::TimelineEvents timeline) { + cache::client()->storeEvent(room_id_, id, {timeline}); + + if (!relatedTo.empty()) { + auto idx = idToIndex(relatedTo); + if (idx) + emit dataChanged(*idx, *idx); + } + }, + Qt::QueuedConnection); + + connect( + this, + &EventStore::oldMessagesRetrieved, + this, + [this](const mtx::responses::Messages &res) { + uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); + if (newFirst == first) + fetchMore(); + else { + emit beginInsertRows(toExternalIdx(newFirst), + toExternalIdx(this->first - 1)); + this->first = newFirst; + emit endInsertRows(); + emit fetchedMore(); + } + }, + Qt::QueuedConnection); connect(this, &EventStore::processPending, this, [this]() { if (!current_txn.empty()) { @@ -128,46 +130,48 @@ EventStore::EventStore(std::string room_id, QObject *) event->data); }); - connect(this, - &EventStore::messageFailed, - this, - [this](std::string txn_id) { - if (current_txn == txn_id) { - current_txn_error_count++; - if (current_txn_error_count > 10) { - nhlog::ui()->debug("failing txn id '{}'", txn_id); - cache::client()->removePendingStatus(room_id_, txn_id); - current_txn_error_count = 0; - } - } - QTimer::singleShot(1000, this, [this]() { - nhlog::ui()->debug("timeout"); - this->current_txn = ""; - emit processPending(); - }); - }, - Qt::QueuedConnection); - - connect(this, - &EventStore::messageSent, - this, - [this](std::string txn_id, std::string event_id) { - nhlog::ui()->debug("sent {}", txn_id); - - http::client()->read_event( - room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to read_event ({}, {})", room_id_, event_id); - } - }); - - cache::client()->removePendingStatus(room_id_, txn_id); - this->current_txn = ""; - this->current_txn_error_count = 0; - emit processPending(); - }, - Qt::QueuedConnection); + connect( + this, + &EventStore::messageFailed, + this, + [this](std::string txn_id) { + if (current_txn == txn_id) { + current_txn_error_count++; + if (current_txn_error_count > 10) { + nhlog::ui()->debug("failing txn id '{}'", txn_id); + cache::client()->removePendingStatus(room_id_, txn_id); + current_txn_error_count = 0; + } + } + QTimer::singleShot(1000, this, [this]() { + nhlog::ui()->debug("timeout"); + this->current_txn = ""; + emit processPending(); + }); + }, + Qt::QueuedConnection); + + connect( + this, + &EventStore::messageSent, + this, + [this](std::string txn_id, std::string event_id) { + nhlog::ui()->debug("sent {}", txn_id); + + http::client()->read_event( + room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn( + "failed to read_event ({}, {})", room_id_, event_id); + } + }); + + cache::client()->removePendingStatus(room_id_, txn_id); + this->current_txn = ""; + this->current_txn_error_count = 0; + emit processPending(); + }, + Qt::QueuedConnection); } void diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 8f0e470e..ddd238b9 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -204,11 +204,12 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj , room_id_(room_id) , manager_(manager) { - connect(this, - &TimelineModel::redactionFailed, - this, - [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, - Qt::QueuedConnection); + connect( + this, + &TimelineModel::redactionFailed, + this, + [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, + Qt::QueuedConnection); connect(this, &TimelineModel::newMessageToSend, @@ -217,17 +218,17 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj Qt::QueuedConnection); connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending); - connect(&events, - &EventStore::dataChanged, - this, - [this](int from, int to) { - nhlog::ui()->debug("data changed {} to {}", - events.size() - to - 1, - events.size() - from - 1); - emit dataChanged(index(events.size() - to - 1, 0), - index(events.size() - from - 1, 0)); - }, - Qt::QueuedConnection); + connect( + &events, + &EventStore::dataChanged, + this, + [this](int from, int to) { + nhlog::ui()->debug( + "data changed {} to {}", events.size() - to - 1, events.size() - from - 1); + emit dataChanged(index(events.size() - to - 1, 0), + index(events.size() - from - 1, 0)); + }, + Qt::QueuedConnection); connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) { int first = events.size() - to; @@ -916,10 +917,20 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: OutboundGroupSessionData session_data; session_data.session_id = session_id; session_data.session_key = session_key; - session_data.message_index = 0; // TODO Update me + session_data.message_index = 0; cache::saveOutboundMegolmSession( room_id, session_data, std::move(outbound_session)); + { + MegolmSessionIndex index; + index.room_id = room_id; + index.session_id = session_id; + index.sender_key = olm::client()->identity_keys().curve25519; + auto megolm_session = + olm::client()->init_inbound_group_session(session_key); + cache::saveInboundMegolmSession(index, std::move(megolm_session)); + } + const auto members = cache::roomMembers(room_id); nhlog::ui()->info("retrieved {} members for {}", members.size(), room_id); @@ -961,19 +972,23 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: return; } - for (const auto &user : res.device_keys) { - // Mapping from a device_id with valid identity keys to the - // generated room_key event used for sharing the megolm session. - std::map room_key_msgs; - std::map deviceKeys; + mtx::requests::ClaimKeys claim_keys; - room_key_msgs.clear(); - deviceKeys.clear(); + // Mapping from user id to a device_id with valid identity keys to the + // generated room_key event used for sharing the megolm session. + std::map> room_key_msgs; + std::map> deviceKeys; + for (const auto &user : res.device_keys) { for (const auto &dev : user.second) { const auto user_id = ::UserId(dev.second.user_id); const auto device_id = DeviceId(dev.second.device_id); + if (user_id.get() == + http::client()->user_id().to_string() && + device_id.get() == http::client()->device_id()) + continue; + const auto device_keys = dev.second.keys; const auto curveKey = "curve25519:" + device_id.get(); const auto edKey = "ed25519:" + device_id.get(); @@ -1015,42 +1030,25 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: user_id, pks.ed25519, megolm_payload) .dump(); - room_key_msgs.emplace(device_id, room_key); - deviceKeys.emplace(device_id, pks); - } - - std::vector valid_devices; - valid_devices.reserve(room_key_msgs.size()); - for (auto const &d : room_key_msgs) { - valid_devices.push_back(d.first); + room_key_msgs[user_id].emplace(device_id, room_key); + deviceKeys[user_id].emplace(device_id, pks); + claim_keys.one_time_keys[user.first][device_id] = + mtx::crypto::SIGNED_CURVE25519; - nhlog::net()->info("{}", d.first); - nhlog::net()->info(" curve25519 {}", - deviceKeys.at(d.first).curve25519); - nhlog::net()->info(" ed25519 {}", - deviceKeys.at(d.first).ed25519); + nhlog::net()->info("{}", device_id.get()); + nhlog::net()->info(" curve25519 {}", pks.curve25519); + nhlog::net()->info(" ed25519 {}", pks.ed25519); } - - nhlog::net()->info( - "sending claim request for user {} with {} devices", - user.first, - valid_devices.size()); - - http::client()->claim_keys( - user.first, - valid_devices, - std::bind(&TimelineModel::handleClaimedKeys, - this, - keeper, - room_key_msgs, - deviceKeys, - user.first, - std::placeholders::_1, - std::placeholders::_2)); - - // TODO: Wait before sending the next batch of requests. - std::this_thread::sleep_for(std::chrono::milliseconds(500)); } + + http::client()->claim_keys(claim_keys, + std::bind(&TimelineModel::handleClaimedKeys, + this, + keeper, + room_key_msgs, + deviceKeys, + std::placeholders::_1, + std::placeholders::_2)); }); // TODO: Let the user know about the errors. @@ -1068,12 +1066,12 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: } void -TimelineModel::handleClaimedKeys(std::shared_ptr keeper, - const std::map &room_keys, - const std::map &pks, - const std::string &user_id, - const mtx::responses::ClaimKeys &res, - mtx::http::RequestErr err) +TimelineModel::handleClaimedKeys( + std::shared_ptr keeper, + const std::map> &room_keys, + const std::map> &pks, + const mtx::responses::ClaimKeys &res, + mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("claim keys error: {} {} {}", @@ -1083,65 +1081,53 @@ TimelineModel::handleClaimedKeys(std::shared_ptr keeper, return; } - nhlog::net()->debug("claimed keys for {}", user_id); - - if (res.one_time_keys.size() == 0) { - nhlog::net()->debug("no one-time keys found for user_id: {}", user_id); - return; - } - - if (res.one_time_keys.find(user_id) == res.one_time_keys.end()) { - nhlog::net()->debug("no one-time keys found for user_id: {}", user_id); - return; - } - - auto retrieved_devices = res.one_time_keys.at(user_id); - // Payload with all the to_device message to be sent. - json body; - body["messages"][user_id] = json::object(); + nlohmann::json body; - for (const auto &rd : retrieved_devices) { - const auto device_id = rd.first; - nhlog::net()->debug("{} : \n {}", device_id, rd.second.dump(2)); + for (const auto &[user_id, retrieved_devices] : res.one_time_keys) { + nhlog::net()->debug("claimed keys for {}", user_id); + if (retrieved_devices.size() == 0) { + nhlog::net()->debug("no one-time keys found for user_id: {}", user_id); + return; + } - // TODO: Verify signatures - auto otk = rd.second.begin()->at("key"); + for (const auto &rd : retrieved_devices) { + const auto device_id = rd.first; - if (pks.find(device_id) == pks.end()) { - nhlog::net()->critical("couldn't find public key for device: {}", - device_id); - continue; - } + nhlog::net()->debug("{} : \n {}", device_id, rd.second.dump(2)); - auto id_key = pks.at(device_id).curve25519; - auto s = olm::client()->create_outbound_session(id_key, otk); + // TODO: Verify signatures + auto otk = rd.second.begin()->at("key"); - if (room_keys.find(device_id) == room_keys.end()) { - nhlog::net()->critical("couldn't find m.room_key for device: {}", - device_id); - continue; - } + auto id_key = pks.at(user_id).at(device_id).curve25519; + auto s = olm::client()->create_outbound_session(id_key, otk); + + auto device_msg = olm::client()->create_olm_encrypted_content( + s.get(), + room_keys.at(user_id).at(device_id), + pks.at(user_id).at(device_id).curve25519); - auto device_msg = olm::client()->create_olm_encrypted_content( - s.get(), room_keys.at(device_id), pks.at(device_id).curve25519); + try { + cache::saveOlmSession(id_key, std::move(s)); + } catch (const lmdb::error &e) { + nhlog::db()->critical("failed to save outbound olm session: {}", + e.what()); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical( + "failed to pickle outbound olm session: {}", e.what()); + } - try { - cache::saveOlmSession(id_key, std::move(s)); - } catch (const lmdb::error &e) { - nhlog::db()->critical("failed to save outbound olm session: {}", e.what()); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to pickle outbound olm session: {}", - e.what()); + body["messages"][user_id][device_id] = device_msg; } - body["messages"][user_id][device_id] = device_msg; + nhlog::net()->info("send_to_device: {}", user_id); } - nhlog::net()->info("send_to_device: {}", user_id); - http::client()->send_to_device( - "m.room.encrypted", body, [keeper](mtx::http::RequestErr err) { + mtx::events::to_string(mtx::events::EventType::RoomEncrypted), + http::client()->generate_txn_id(), + body, + [keeper](mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("failed to send " "send_to_device " diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 390fa1ed..61d00df9 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -285,12 +285,12 @@ signals: private: template void sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events::EventType eventType); - void handleClaimedKeys(std::shared_ptr keeper, - const std::map &room_key, - const std::map &pks, - const std::string &user_id, - const mtx::responses::ClaimKeys &res, - mtx::http::RequestErr err); + void handleClaimedKeys( + std::shared_ptr keeper, + const std::map> &room_keys, + const std::map> &pks, + const mtx::responses::ClaimKeys &res, + mtx::http::RequestErr err); void readEvent(const std::string &id); void setPaginationInProgress(const bool paginationInProgress); -- cgit 1.5.1 From 94690ebd4c22c8928b92c4f1723d1c6c5b798698 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 2 Oct 2020 01:14:42 +0200 Subject: Clean up verification and key cache a bit --- resources/qml/UserProfile.qml | 2 +- src/Cache.cpp | 331 ++++++++++++++++++++++++------------ src/Cache.h | 30 ++-- src/CacheCryptoStructs.h | 50 ++---- src/Cache_p.h | 34 ++-- src/ChatPage.cpp | 56 +++--- src/ChatPage.h | 6 +- src/DeviceVerificationFlow.cpp | 126 +++++++------- src/DeviceVerificationFlow.h | 12 +- src/timeline/.TimelineModel.cpp.swn | Bin 237568 -> 0 bytes src/ui/UserProfile.cpp | 70 +++----- 11 files changed, 399 insertions(+), 318 deletions(-) delete mode 100644 src/timeline/.TimelineModel.cpp.swn (limited to 'src/Cache.cpp') diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 1ca9dcc8..dc6bc165 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -136,7 +136,7 @@ ApplicationWindow{ model: profile.deviceList delegate: RowLayout{ - width: parent.width + width: devicelist.width spacing: 4 ColumnLayout{ diff --git a/src/Cache.cpp b/src/Cache.cpp index 667506c5..8b47c357 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -91,6 +91,7 @@ Q_DECLARE_METATYPE(RoomMember) Q_DECLARE_METATYPE(mtx::responses::Timeline) Q_DECLARE_METATYPE(RoomSearchResult) Q_DECLARE_METATYPE(RoomInfo) +Q_DECLARE_METATYPE(mtx::responses::QueryKeys) namespace { std::unique_ptr instance_ = nullptr; @@ -155,26 +156,7 @@ Cache::Cache(const QString &userId, QObject *parent) , localUserId_{userId} { setup(); - connect( - this, - &Cache::updateUserCacheFlag, - this, - [this](const std::string &user_id) { - std::optional cache_ = getUserCache(user_id); - if (cache_.has_value()) { - cache_.value().isUpdated = false; - setUserCache(user_id, cache_.value()); - } else { - setUserCache(user_id, UserCache{}); - } - }, - Qt::QueuedConnection); - connect( - this, - &Cache::deleteLeftUsers, - this, - [this](const std::string &user_id) { deleteUserCache(user_id); }, - Qt::QueuedConnection); + connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection); } void @@ -1017,6 +999,8 @@ Cache::saveState(const mtx::responses::Sync &res) using namespace mtx::events; auto user_id = this->localUserId_.toStdString(); + auto currentBatchToken = nextBatchToken(); + auto txn = lmdb::txn::begin(env_); setNextBatchToken(txn, res.next_batch); @@ -1034,6 +1018,8 @@ Cache::saveState(const mtx::responses::Sync &res) ev); } + auto userKeyCacheDb = getUserKeysDb(txn); + // Save joined rooms for (const auto &room : res.rooms.join) { auto statesdb = getStatesDb(txn, room.first); @@ -1107,7 +1093,8 @@ Cache::saveState(const mtx::responses::Sync &res) savePresence(txn, res.presence); - updateUserCache(res.device_lists); + markUserKeysOutOfDate(txn, userKeyCacheDb, res.device_lists.changed, currentBatchToken); + deleteUserKeys(txn, userKeyCacheDb, res.device_lists.left); removeLeftRooms(txn, res.rooms.leave); @@ -3098,126 +3085,246 @@ Cache::statusMessage(const std::string &user_id) } void -to_json(json &j, const UserCache &info) +to_json(json &j, const UserKeyCache &info) { - j["keys"] = info.keys; - j["isUpdated"] = info.isUpdated; + j["device_keys"] = info.device_keys; + j["master_keys"] = info.master_keys; + j["user_signing_keys"] = info.user_signing_keys; + j["self_signing_keys"] = info.self_signing_keys; + j["updated_at"] = info.updated_at; + j["last_changed"] = info.last_changed; } void -from_json(const json &j, UserCache &info) +from_json(const json &j, UserKeyCache &info) { - info.keys = j.at("keys").get(); - info.isUpdated = j.at("isUpdated").get(); + info.device_keys = j.value("device_keys", std::map{}); + info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{}); + info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{}); + info.self_signing_keys = j.value("self_signing_keys", mtx::crypto::CrossSigningKeys{}); + info.updated_at = j.value("updated_at", ""); + info.last_changed = j.value("last_changed", ""); } -std::optional -Cache::getUserCache(const std::string &user_id) +std::optional +Cache::userKeys(const std::string &user_id) { - lmdb::val verifiedVal; + lmdb::val keys; - auto txn = lmdb::txn::begin(env_); - auto db = getUserCacheDb(txn); - auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal); - - txn.commit(); + try { + auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); + auto db = getUserKeysDb(txn); + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), keys); - UserCache verified_state; - if (res) { - verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size())); - return verified_state; - } else { + if (res) { + return json::parse(std::string_view(keys.data(), keys.size())) + .get(); + } else { + return {}; + } + } catch (std::exception &) { return {}; } } -//! be careful when using make sure is_user_verified is not changed -int -Cache::setUserCache(const std::string &user_id, const UserCache &body) +void +Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery) { auto txn = lmdb::txn::begin(env_); - auto db = getUserCacheDb(txn); + auto db = getUserKeysDb(txn); - auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump())); + std::map updates; - txn.commit(); + for (const auto &[user, keys] : keyQuery.device_keys) + updates[user].device_keys = keys; + for (const auto &[user, keys] : keyQuery.master_keys) + updates[user].master_keys = keys; + for (const auto &[user, keys] : keyQuery.user_signing_keys) + updates[user].user_signing_keys = keys; + for (const auto &[user, keys] : keyQuery.self_signing_keys) + updates[user].self_signing_keys = keys; - return res; + for (auto &[user, update] : updates) { + lmdb::val oldKeys; + auto res = lmdb::dbi_get(txn, db, lmdb::val(user), oldKeys); + + if (res) { + auto last_changed = + json::parse(std::string_view(oldKeys.data(), oldKeys.size())) + .get() + .last_changed; + // skip if we are tracking this and expect it to be up to date with the last + // sync token + if (!last_changed.empty() && last_changed != sync_token) + continue; + } + lmdb::dbi_put(txn, db, lmdb::val(user), lmdb::val(json(update).dump())); + } + + txn.commit(); } void -Cache::updateUserCache(const mtx::responses::DeviceLists body) +Cache::deleteUserKeys(lmdb::txn &txn, lmdb::dbi &db, const std::vector &user_ids) { - for (std::string user_id : body.changed) { - emit updateUserCacheFlag(user_id); - } - - for (std::string user_id : body.left) { - emit deleteLeftUsers(user_id); - } + for (const auto &user_id : user_ids) + lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr); } -int -Cache::deleteUserCache(const std::string &user_id) +void +Cache::markUserKeysOutOfDate(lmdb::txn &txn, + lmdb::dbi &db, + const std::vector &user_ids, + const std::string &sync_token) { - auto txn = lmdb::txn::begin(env_); - auto db = getUserCacheDb(txn); - auto res = lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr); + mtx::requests::QueryKeys query; + query.token = sync_token; - txn.commit(); + for (const auto &user : user_ids) { + lmdb::val oldKeys; + auto res = lmdb::dbi_get(txn, db, lmdb::val(user), oldKeys); - return res; + if (!res) + continue; + + auto cacheEntry = + json::parse(std::string_view(oldKeys.data(), oldKeys.size())).get(); + cacheEntry.last_changed = sync_token; + lmdb::dbi_put(txn, db, lmdb::val(user), lmdb::val(json(cacheEntry).dump())); + + query.device_keys[user] = {}; + } + + if (!query.device_keys.empty()) + http::client()->query_keys(query, + [this, sync_token](const mtx::responses::QueryKeys &keys, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn( + "failed to query device keys: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + return; + } + + emit userKeysUpdate(sync_token, keys); + }); } void -to_json(json &j, const DeviceVerifiedCache &info) +to_json(json &j, const VerificationCache &info) { - j["is_user_verified"] = info.is_user_verified; - j["cross_verified"] = info.cross_verified; - j["device_verified"] = info.device_verified; - j["device_blocked"] = info.device_blocked; + j["verified_master_key"] = info.verified_master_key; + j["cross_verified"] = info.cross_verified; + j["device_verified"] = info.device_verified; + j["device_blocked"] = info.device_blocked; } void -from_json(const json &j, DeviceVerifiedCache &info) +from_json(const json &j, VerificationCache &info) { - info.is_user_verified = j.at("is_user_verified"); - info.cross_verified = j.at("cross_verified").get>(); - info.device_verified = j.at("device_verified").get>(); - info.device_blocked = j.at("device_blocked").get>(); + info.verified_master_key = j.at("verified_master_key"); + info.cross_verified = j.at("cross_verified").get>(); + info.device_verified = j.at("device_verified").get>(); + info.device_blocked = j.at("device_blocked").get>(); } -std::optional -Cache::getVerifiedCache(const std::string &user_id) +std::optional +Cache::verificationStatus(const std::string &user_id) { lmdb::val verifiedVal; auto txn = lmdb::txn::begin(env_); - auto db = getDeviceVerifiedDb(txn); - auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal); + auto db = getVerificationDb(txn); - txn.commit(); - - DeviceVerifiedCache verified_state; - if (res) { - verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size())); - return verified_state; - } else { + try { + VerificationCache verified_state; + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal); + if (res) { + verified_state = + json::parse(std::string_view(verifiedVal.data(), verifiedVal.size())); + return verified_state; + } else { + return {}; + } + } catch (std::exception &) { return {}; } } -int -Cache::setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body) +void +Cache::markDeviceVerified(const std::string &user_id, const std::string &key) { + lmdb::val val; + auto txn = lmdb::txn::begin(env_); - auto db = getDeviceVerifiedDb(txn); + auto db = getVerificationDb(txn); - auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump())); + try { + VerificationCache verified_state; + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), val); + if (res) { + verified_state = json::parse(std::string_view(val.data(), val.size())); + } - txn.commit(); + for (const auto &device : verified_state.device_verified) + if (device == key) + return; - return res; + verified_state.device_verified.push_back(key); + lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump())); + txn.commit(); + } catch (std::exception &) { + } +} + +void +Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) +{ + lmdb::val val; + + auto txn = lmdb::txn::begin(env_); + auto db = getVerificationDb(txn); + + try { + VerificationCache verified_state; + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), val); + if (res) { + verified_state = json::parse(std::string_view(val.data(), val.size())); + } + + verified_state.device_verified.erase( + std::remove(verified_state.device_verified.begin(), + verified_state.device_verified.end(), + key), + verified_state.device_verified.end()); + + lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump())); + txn.commit(); + } catch (std::exception &) { + } +} + +void +Cache::markMasterKeyVerified(const std::string &user_id, const std::string &key) +{ + lmdb::val val; + + auto txn = lmdb::txn::begin(env_); + auto db = getVerificationDb(txn); + + try { + VerificationCache verified_state; + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), val); + if (res) { + verified_state = json::parse(std::string_view(val.data(), val.size())); + } + + verified_state.verified_master_key = key; + lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump())); + txn.commit(); + } catch (std::exception &) { + } } void @@ -3401,47 +3508,49 @@ statusMessage(const std::string &user_id) { return instance_->statusMessage(user_id); } -std::optional -getUserCache(const std::string &user_id) -{ - return instance_->getUserCache(user_id); -} +//! Load saved data for the display names & avatars. void -updateUserCache(const mtx::responses::DeviceLists body) +populateMembers() { - instance_->updateUserCache(body); + instance_->populateMembers(); } -int -setUserCache(const std::string &user_id, const UserCache &body) +// user cache stores user keys +std::optional +userKeys(const std::string &user_id) +{ + return instance_->userKeys(user_id); +} +void +updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery) { - return instance_->setUserCache(user_id, body); + instance_->updateUserKeys(sync_token, keyQuery); } -int -deleteUserCache(const std::string &user_id) +// device & user verification cache +std::optional +verificationStatus(const std::string &user_id) { - return instance_->deleteUserCache(user_id); + return instance_->verificationStatus(user_id); } -std::optional -getVerifiedCache(const std::string &user_id) +void +markDeviceVerified(const std::string &user_id, const std::string &key) { - return instance_->getVerifiedCache(user_id); + instance_->markDeviceVerified(user_id, key); } -int -setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body) +void +markDeviceUnverified(const std::string &user_id, const std::string &key) { - return instance_->setVerifiedCache(user_id, body); + instance_->markDeviceUnverified(user_id, key); } -//! Load saved data for the display names & avatars. void -populateMembers() +markMasterKeyVerified(const std::string &user_id, const std::string &key) { - instance_->populateMembers(); + instance_->markMasterKeyVerified(user_id, key); } std::vector diff --git a/src/Cache.h b/src/Cache.h index 82d909ae..edad5993 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -60,25 +60,21 @@ presenceState(const std::string &user_id); std::string statusMessage(const std::string &user_id); -//! user Cache -std::optional -getUserCache(const std::string &user_id); - +// user cache stores user keys +std::optional +userKeys(const std::string &user_id); void -updateUserCache(const mtx::responses::DeviceLists body); - -int -setUserCache(const std::string &user_id, const UserCache &body); - -int -deleteUserCache(const std::string &user_id); - -//! verified Cache -std::optional -getVerifiedCache(const std::string &user_id); +updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); -int -setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body); +// device & user verification cache +std::optional +verificationStatus(const std::string &user_id); +void +markDeviceVerified(const std::string &user_id, const std::string &key); +void +markDeviceUnverified(const std::string &user_id, const std::string &key); +void +markMasterKeyVerified(const std::string &user_id, const std::string &key); //! Load saved data for the display names & avatars. void diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 1dde21ce..10636ac6 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -67,52 +67,38 @@ struct OlmSessionStorage }; // this will store the keys of the user with whom a encrypted room is shared with -struct UserCache +struct UserKeyCache { - //! map of public key key_ids and their public_key - mtx::responses::QueryKeys keys; - //! if the current cache is updated or not - bool isUpdated = false; - - UserCache(mtx::responses::QueryKeys res, bool isUpdated_ = false) - : keys(res) - , isUpdated(isUpdated_) - {} - UserCache() {} + //! Device id to device keys + std::map device_keys; + //! corss signing keys + mtx::crypto::CrossSigningKeys master_keys, user_signing_keys, self_signing_keys; + //! Sync token when nheko last fetched the keys + std::string updated_at; + //! Sync token when the keys last changed. updated != last_changed means they are outdated. + std::string last_changed; }; void -to_json(nlohmann::json &j, const UserCache &info); +to_json(nlohmann::json &j, const UserKeyCache &info); void -from_json(const nlohmann::json &j, UserCache &info); +from_json(const nlohmann::json &j, UserKeyCache &info); // the reason these are stored in a seperate cache rather than storing it in the user cache is -// UserCache stores only keys of users with which encrypted room is shared -struct DeviceVerifiedCache +// UserKeyCache stores only keys of users with which encrypted room is shared +struct VerificationCache { //! list of verified device_ids with device-verification std::vector device_verified; - //! list of verified device_ids with cross-signing + //! list of verified device_ids with cross-signing, calculated from master key std::vector cross_verified; //! list of devices the user blocks std::vector device_blocked; - //! this stores if the user is verified (with cross-signing) - bool is_user_verified = false; - - DeviceVerifiedCache(std::vector device_verified_, - std::vector cross_verified_, - std::vector device_blocked_, - bool is_user_verified_ = false) - : device_verified(device_verified_) - , cross_verified(cross_verified_) - , device_blocked(device_blocked_) - , is_user_verified(is_user_verified_) - {} - - DeviceVerifiedCache() {} + //! The verified master key. + std::string verified_master_key; }; void -to_json(nlohmann::json &j, const DeviceVerifiedCache &info); +to_json(nlohmann::json &j, const VerificationCache &info); void -from_json(const nlohmann::json &j, DeviceVerifiedCache &info); +from_json(const nlohmann::json &j, VerificationCache &info); diff --git a/src/Cache_p.h b/src/Cache_p.h index ce6414ab..034c6d76 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -55,14 +55,22 @@ public: std::string statusMessage(const std::string &user_id); // user cache stores user keys - std::optional getUserCache(const std::string &user_id); - void updateUserCache(const mtx::responses::DeviceLists body); - int setUserCache(const std::string &user_id, const UserCache &body); - int deleteUserCache(const std::string &user_id); - - // device verified cache - std::optional getVerifiedCache(const std::string &user_id); - int setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body); + std::optional userKeys(const std::string &user_id); + void updateUserKeys(const std::string &sync_token, + const mtx::responses::QueryKeys &keyQuery); + void markUserKeysOutOfDate(lmdb::txn &txn, + lmdb::dbi &db, + const std::vector &user_ids, + const std::string &sync_token); + void deleteUserKeys(lmdb::txn &txn, + lmdb::dbi &db, + const std::vector &user_ids); + + // device & user verification cache + std::optional verificationStatus(const std::string &user_id); + void markDeviceVerified(const std::string &user_id, const std::string &key); + void markDeviceUnverified(const std::string &user_id, const std::string &key); + void markMasterKeyVerified(const std::string &user_id, const std::string &key); static void removeDisplayName(const QString &room_id, const QString &user_id); static void removeAvatarUrl(const QString &room_id, const QString &user_id); @@ -272,8 +280,8 @@ signals: void newReadReceipts(const QString &room_id, const std::vector &event_ids); void roomReadStatus(const std::map &status); void removeNotification(const QString &room_id, const QString &event_id); - void updateUserCacheFlag(const std::string &user_id); - void deleteLeftUsers(const std::string &user_id); + void userKeysUpdate(const std::string &sync_token, + const mtx::responses::QueryKeys &keyQuery); private: //! Save an invited room. @@ -539,12 +547,12 @@ private: return lmdb::dbi::open(txn, "presence", MDB_CREATE); } - lmdb::dbi getUserCacheDb(lmdb::txn &txn) + lmdb::dbi getUserKeysDb(lmdb::txn &txn) { - return lmdb::dbi::open(txn, "user_cache", MDB_CREATE); + return lmdb::dbi::open(txn, "user_key", MDB_CREATE); } - lmdb::dbi getDeviceVerifiedDb(lmdb::txn &txn) + lmdb::dbi getVerificationDb(lmdb::txn &txn) { return lmdb::dbi::open(txn, "verified", MDB_CREATE); } diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index c6978a59..6abe4078 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -1466,35 +1466,43 @@ ChatPage::initiateLogout() } void -ChatPage::query_keys( - const mtx::requests::QueryKeys &req, - std::function cb) +ChatPage::query_keys(const std::string &user_id, + std::function cb) { - std::string user_id = req.device_keys.begin()->first; - auto cache_ = cache::getUserCache(user_id); + auto cache_ = cache::userKeys(user_id); if (cache_.has_value()) { - if (cache_.value().isUpdated) { - cb(cache_.value().keys, {}); - } else { - http::client()->query_keys( - req, - [cb, user_id](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to query device keys: {},{}", - err->matrix_error.errcode, - static_cast(err->status_code)); - return; - } - cache::setUserCache(std::move(user_id), - std::move(UserCache{res, true})); - cb(res, err); - }); + if (!cache_->updated_at.empty() && cache_->updated_at == cache_->last_changed) { + cb(cache_.value(), {}); + return; } - } else { - http::client()->query_keys(req, cb); } + + mtx::requests::QueryKeys req; + req.device_keys[user_id] = {}; + + std::string last_changed; + if (cache_) + last_changed = cache_->last_changed; + req.token = last_changed; + + http::client()->query_keys(req, + [cb, user_id, last_changed](const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn( + "failed to query device keys: {},{}", + err->matrix_error.errcode, + static_cast(err->status_code)); + cb({}, err); + return; + } + + cache::updateUserKeys(last_changed, res); + + auto keys = cache::userKeys(user_id); + cb(keys.value_or(UserKeyCache{}), err); + }); } template diff --git a/src/ChatPage.h b/src/ChatPage.h index 9d8abb24..f363c4fe 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -35,6 +35,7 @@ #include #include +#include "CacheCryptoStructs.h" #include "CacheStructs.h" #include "CallManager.h" #include "CommunitiesList.h" @@ -89,9 +90,8 @@ public: //! Show the room/group list (if it was visible). void showSideBars(); void initiateLogout(); - void query_keys( - const mtx::requests::QueryKeys &req, - std::function cb); + void query_keys(const std::string &req, + std::function cb); void focusMessageInput(); QString status() const; diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 96fed55a..aa8b5b44 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -328,22 +328,14 @@ DeviceVerificationFlow::setTransactionId(QString transaction_id_) void DeviceVerificationFlow::setUserId(QString userID) { - this->userId = userID; - this->toClient = mtx::identifiers::parse(userID.toStdString()); - auto user_cache = cache::getUserCache(userID.toStdString()); - - if (user_cache.has_value()) { - this->callback_fn(user_cache->keys, {}, userID.toStdString()); - } else { - mtx::requests::QueryKeys req; - req.device_keys[userID.toStdString()] = {}; - http::client()->query_keys( - req, - [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { - this->callback_fn(res, err, user_id); - }); - } + this->userId = userID; + this->toClient = mtx::identifiers::parse(userID.toStdString()); + + auto user_id = userID.toStdString(); + ChatPage::instance()->query_keys( + user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) { + this->callback_fn(res, err, user_id); + }); } void @@ -622,30 +614,52 @@ DeviceVerificationFlow::sendVerificationKey() (model_)->sendMessageEvent(req, mtx::events::EventType::KeyVerificationKey); } } -//! sends the mac of the keys -void -DeviceVerificationFlow::sendVerificationMac() + +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" + http::client()->user_id().to_string() + - http::client()->device_id() + this->toClient.to_string() + - this->deviceId.toStdString() + this->transaction_id; - - //! this vector stores the type of the key and the key - std::vector> key_list; - key_list.push_back(make_pair("ed25519", olm::client()->identity_keys().ed25519)); - std::sort(key_list.begin(), key_list.end()); - for (auto x : key_list) { - req.mac.insert( - std::make_pair(x.first + ":" + http::client()->device_id(), - this->sas->calculate_mac( - x.second, info + x.first + ":" + http::client()->device_id()))); - req.keys += x.first + ":" + http::client()->device_id() + ","; + 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 = - this->sas->calculate_mac(req.keys.substr(0, req.keys.size() - 1), info + "KEY_IDS"); + 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; + + 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); if (this->type == DeviceVerificationFlow::Type::ToDevice) { mtx::requests::ToDeviceMessages body; @@ -673,27 +687,16 @@ DeviceVerificationFlow::sendVerificationMac() void DeviceVerificationFlow::acceptDevice() { - auto verified_cache = cache::getVerifiedCache(this->userId.toStdString()); - if (verified_cache.has_value()) { - verified_cache->device_verified.push_back(this->deviceId.toStdString()); - verified_cache->device_blocked.erase( - std::remove(verified_cache->device_blocked.begin(), - verified_cache->device_blocked.end(), - this->deviceId.toStdString()), - verified_cache->device_blocked.end()); - } else { - cache::setVerifiedCache( - this->userId.toStdString(), - DeviceVerifiedCache{{this->deviceId.toStdString()}, {}, {}}); - } + cache::markDeviceVerified(this->userId.toStdString(), this->deviceId.toStdString()); emit deviceVerified(); emit refreshProfile(); this->deleteLater(); } + //! callback function to keep track of devices void -DeviceVerificationFlow::callback_fn(const mtx::responses::QueryKeys &res, +DeviceVerificationFlow::callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id) { @@ -704,35 +707,22 @@ DeviceVerificationFlow::callback_fn(const mtx::responses::QueryKeys &res, return; } - if (res.device_keys.empty() || (res.device_keys.find(user_id) == res.device_keys.end())) { + if (res.device_keys.empty() || + (res.device_keys.find(deviceId.toStdString()) == res.device_keys.end())) { nhlog::net()->warn("no devices retrieved {}", user_id); return; } - for (auto x : res.device_keys) { - for (auto y : x.second) { - auto z = y.second; - if (z.user_id == user_id && z.device_id == this->deviceId.toStdString()) { - for (auto a : z.keys) { - // TODO: Verify Signatures - this->device_keys[a.first] = a.second; - } - } - } + for (const auto &[algorithm, key] : res.device_keys.at(deviceId.toStdString()).keys) { + // TODO: Verify Signatures + this->device_keys[algorithm] = key; } } void DeviceVerificationFlow::unverify() { - auto verified_cache = cache::getVerifiedCache(this->userId.toStdString()); - if (verified_cache.has_value()) { - auto it = std::remove(verified_cache->device_verified.begin(), - verified_cache->device_verified.end(), - this->deviceId.toStdString()); - verified_cache->device_verified.erase(it); - cache::setVerifiedCache(this->userId.toStdString(), verified_cache.value()); - } + cache::markDeviceUnverified(this->userId.toStdString(), this->deviceId.toStdString()); emit refreshProfile(); } diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index b85cbec2..31d2facc 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -1,10 +1,12 @@ #pragma once -#include "Olm.h" +#include + +#include +#include "CacheCryptoStructs.h" #include "MatrixClient.h" -#include "mtx/responses/crypto.hpp" -#include +#include "Olm.h" class QTimer; @@ -71,9 +73,7 @@ public: void setSender(bool sender_); void setEventId(std::string event_id); - void callback_fn(const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err, - std::string user_id); + void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id); nlohmann::json canonical_json; diff --git a/src/timeline/.TimelineModel.cpp.swn b/src/timeline/.TimelineModel.cpp.swn deleted file mode 100644 index 9e965702..00000000 Binary files a/src/timeline/.TimelineModel.cpp.swn and /dev/null differ diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 08c30097..2ea3f7b6 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -89,12 +89,10 @@ UserProfile::fetchDeviceList(const QString &userID) { auto localUser = utils::localUser(); - mtx::requests::QueryKeys req; - req.device_keys[userID.toStdString()] = {}; ChatPage::instance()->query_keys( - req, - [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { + userID.toStdString(), + [other_user_id = userID.toStdString(), this](const UserKeyCache &other_user_keys, + mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("failed to query device keys: {},{}", err->matrix_error.errcode, @@ -102,20 +100,11 @@ UserProfile::fetchDeviceList(const QString &userID) return; } - if (res.device_keys.empty() || - (res.device_keys.find(user_id) == res.device_keys.end())) { - nhlog::net()->warn("no devices retrieved {}", user_id); - return; - } - // Finding if the User is Verified or not based on the Signatures - mtx::requests::QueryKeys req; - req.device_keys[utils::localUser().toStdString()] = {}; - ChatPage::instance()->query_keys( - req, - [user_id, other_res = res, this](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { + utils::localUser().toStdString(), + [other_user_id, other_user_keys, this](const UserKeyCache &res, + mtx::http::RequestErr err) { using namespace mtx; std::string local_user_id = utils::localUser().toStdString(); @@ -126,34 +115,28 @@ UserProfile::fetchDeviceList(const QString &userID) return; } - if (res.device_keys.empty() || - (res.device_keys.find(local_user_id) == res.device_keys.end())) { - nhlog::net()->warn("no devices retrieved {}", user_id); + if (res.device_keys.empty()) { + nhlog::net()->warn("no devices retrieved {}", local_user_id); return; } std::vector deviceInfo; - auto devices = other_res.device_keys.at(user_id); - auto device_verified = cache::getVerifiedCache(user_id); + auto devices = other_user_keys.device_keys; + auto device_verified = cache::verificationStatus(other_user_id); if (device_verified.has_value()) { - isUserVerified = device_verified.value().is_user_verified; + // TODO: properly check cross-signing signatures here + isUserVerified = !device_verified->verified_master_key.empty(); } std::optional lmk, lsk, luk, mk, sk, uk; - if (!res.master_keys.empty()) - lmk = res.master_keys.at(local_user_id); - if (!res.user_signing_keys.empty()) - luk = res.user_signing_keys.at(local_user_id); - if (!res.self_signing_keys.empty()) - lsk = res.self_signing_keys.at(local_user_id); - if (!other_res.master_keys.empty()) - mk = other_res.master_keys.at(user_id); - if (!other_res.user_signing_keys.empty()) - uk = other_res.user_signing_keys.at(user_id); - if (!other_res.self_signing_keys.empty()) - sk = other_res.self_signing_keys.at(user_id); + lmk = res.master_keys; + luk = res.user_signing_keys; + lsk = res.self_signing_keys; + mk = other_user_keys.master_keys; + uk = other_user_keys.user_signing_keys; + sk = other_user_keys.self_signing_keys; // First checking if the user is verified if (luk.has_value() && mk.has_value()) { @@ -202,7 +185,7 @@ UserProfile::fetchDeviceList(const QString &userID) device_verified->device_blocked.end()) verified = verification::Status::BLOCKED; } else if (isUserVerified) { - device_verified = DeviceVerifiedCache{}; + device_verified = VerificationCache{}; } // won't check for already verified devices @@ -211,7 +194,7 @@ UserProfile::fetchDeviceList(const QString &userID) if ((sk.has_value()) && (!device.signatures.empty())) { for (auto sign_key : sk.value().keys) { auto signs = - device.signatures.at(user_id); + device.signatures.at(other_user_id); try { if (olm::client() ->ed25519_verify_sig( @@ -232,12 +215,13 @@ UserProfile::fetchDeviceList(const QString &userID) } } - if (device_verified.has_value()) { - device_verified.value().is_user_verified = - isUserVerified; - cache::setVerifiedCache(user_id, - device_verified.value()); - } + // TODO(Nico): properly show cross-signing + // if (device_verified.has_value()) { + // device_verified.value().is_user_verified = + // isUserVerified; + // cache::setVerifiedCache(user_id, + // device_verified.value()); + //} deviceInfo.push_back( {QString::fromStdString(d.first), -- cgit 1.5.1 From 600df6d2ec84df5238781f1274b6f5ff31a34646 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 2 Oct 2020 16:39:20 +0200 Subject: Fix fetching inexistent batch tokens --- src/Cache.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/Cache.cpp') diff --git a/src/Cache.cpp b/src/Cache.cpp index 8b47c357..b7a25343 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -687,11 +687,14 @@ Cache::nextBatchToken() const auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); lmdb::val token; - lmdb::dbi_get(txn, syncStateDb_, NEXT_BATCH_KEY, token); + auto result = lmdb::dbi_get(txn, syncStateDb_, NEXT_BATCH_KEY, token); txn.commit(); - return std::string(token.data(), token.size()); + if (result) + return std::string(token.data(), token.size()); + else + return ""; } void -- cgit 1.5.1 From 2a79cd2b6b3548ac86a646b16f5a8d658fdb7282 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 3 Oct 2020 18:38:28 +0200 Subject: Don't fail on missing key for a device and /rotate-megolm-session command --- src/Cache.cpp | 24 ++++++++++++++++++++++++ src/Cache.h | 2 ++ src/Cache_p.h | 1 + src/ChatPage.cpp | 4 ++++ src/TextInputWidget.cpp | 2 ++ src/TextInputWidget.h | 1 + src/timeline/TimelineModel.cpp | 6 ++++++ 7 files changed, 40 insertions(+) (limited to 'src/Cache.cpp') diff --git a/src/Cache.cpp b/src/Cache.cpp index b7a25343..63f6e426 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -372,6 +372,25 @@ Cache::updateOutboundMegolmSession(const std::string &room_id, int message_index txn.commit(); } +void +Cache::dropOutboundMegolmSession(const std::string &room_id) +{ + using namespace mtx::crypto; + + if (!outboundMegolmSessionExists(room_id)) + return; + + { + std::unique_lock lock(session_storage.group_outbound_mtx); + session_storage.group_outbound_session_data.erase(room_id); + session_storage.group_outbound_sessions.erase(room_id); + + auto txn = lmdb::txn::begin(env_); + lmdb::dbi_del(txn, outboundMegolmSessionDb_, lmdb::val(room_id), nullptr); + txn.commit(); + } +} + void Cache::saveOutboundMegolmSession(const std::string &room_id, const OutboundGroupSessionData &data, @@ -3889,6 +3908,11 @@ updateOutboundMegolmSession(const std::string &room_id, int message_index) { instance_->updateOutboundMegolmSession(room_id, message_index); } +void +dropOutboundMegolmSession(const std::string &room_id) +{ + instance_->dropOutboundMegolmSession(room_id); +} void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys) diff --git a/src/Cache.h b/src/Cache.h index edad5993..fca80145 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -271,6 +271,8 @@ bool outboundMegolmSessionExists(const std::string &room_id) noexcept; void updateOutboundMegolmSession(const std::string &room_id, int message_index); +void +dropOutboundMegolmSession(const std::string &room_id); void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys); diff --git a/src/Cache_p.h b/src/Cache_p.h index 034c6d76..b37eae58 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -251,6 +251,7 @@ public: OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id); bool outboundMegolmSessionExists(const std::string &room_id) noexcept; void updateOutboundMegolmSession(const std::string &room_id, int message_index); + void dropOutboundMegolmSession(const std::string &room_id); void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys); mtx::crypto::ExportedSessionKeys exportSessionKeys(); diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 6abe4078..87b4c277 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -168,6 +168,10 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) view_manager_, &TimelineViewManager::clearCurrentRoomTimeline); + connect(text_input_, &TextInputWidget::rotateMegolmSession, this, [this]() { + cache::dropOutboundMegolmSession(current_room_.toStdString()); + }); + connect( new QShortcut(QKeySequence("Ctrl+Down"), this), &QShortcut::activated, this, [this]() { if (isVisible()) diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index 4a25c4cf..d1be7fb4 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -709,6 +709,8 @@ TextInputWidget::command(QString command, QString args) emit sendTextMessage("ノ┬─┬ノ ︵ ( \\o°o)\\"); } else if (command == "clear-timeline") { emit clearRoomTimeline(); + } else if (command == "rotate-megolm-session") { + emit rotateMegolmSession(); } } diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h index 3aa05c39..092e0ff2 100644 --- a/src/TextInputWidget.h +++ b/src/TextInputWidget.h @@ -186,6 +186,7 @@ signals: void sendBanRoomRequest(const QString &userid, const QString &reason); void sendUnbanRoomRequest(const QString &userid, const QString &reason); void changeRoomNick(const QString &displayname); + void rotateMegolmSession(); void startedTyping(); void stoppedTyping(); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index af26a543..5e8952fc 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1120,6 +1120,12 @@ TimelineModel::handleClaimedKeys( nhlog::net()->debug("{} : \n {}", device_id, rd.second.dump(2)); + if (rd.second.empty() || !rd.second.begin()->contains("key")) { + nhlog::net()->warn("Skipping device {} as it has no key.", + device_id); + continue; + } + // TODO: Verify signatures auto otk = rd.second.begin()->at("key"); -- cgit 1.5.1 From 7b6fab33731e369a860ab217709190e9457d6d76 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 7 Oct 2020 23:03:14 +0200 Subject: Calculate verification status from cross-signing sigs and update dynamically --- resources/qml/UserProfile.qml | 3 +- .../qml/device-verification/DeviceVerification.qml | 4 +- src/Cache.cpp | 178 +++++++++++++++++---- src/Cache.h | 8 +- src/CacheCryptoStructs.h | 21 ++- src/Cache_p.h | 11 +- src/timeline/TimelineModel.cpp | 2 +- src/ui/UserProfile.cpp | 145 +++++------------ src/ui/UserProfile.h | 8 +- 9 files changed, 223 insertions(+), 157 deletions(-) (limited to 'src/Cache.cpp') diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index e7dcc777..562dd4f9 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -56,7 +56,7 @@ ApplicationWindow{ Button { id: verifyUserButton - text: "Verify" + text: qsTr("Verify") Layout.alignment: Qt.AlignHCenter enabled: !profile.isUserVerified visible: !profile.isUserVerified @@ -155,7 +155,6 @@ ApplicationWindow{ onClicked: { if(model.verificationStatus == VerificationStatus.VERIFIED){ profile.unverify(model.deviceId) - deviceVerificationList.updateProfile(newFlow.userId); }else{ profile.verify(model.deviceId); } diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index d6185a01..2e8f7504 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -1,6 +1,6 @@ -import QtQuick 2.3 +import QtQuick 2.10 import QtQuick.Controls 2.10 -import QtQuick.Window 2.2 +import QtQuick.Window 2.10 import im.nheko 1.0 diff --git a/src/Cache.cpp b/src/Cache.cpp index 63f6e426..d6da03c6 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3184,6 +3184,28 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query } txn.commit(); + + std::map tmp; + const auto local_user = utils::localUser().toStdString(); + + { + std::unique_lock lock(verification_storage.verification_storage_mtx); + for (auto &[user_id, update] : updates) { + if (user_id == local_user) { + std::swap(tmp, verification_storage.status); + } else { + verification_storage.status.erase(user_id); + } + } + } + for (auto &[user_id, update] : updates) { + if (user_id == local_user) { + for (const auto &[user, status] : tmp) + emit verificationStatusChanged(user); + } else { + emit verificationStatusChanged(user_id); + } + } } void @@ -3236,23 +3258,19 @@ Cache::markUserKeysOutOfDate(lmdb::txn &txn, void to_json(json &j, const VerificationCache &info) { - j["verified_master_key"] = info.verified_master_key; - j["cross_verified"] = info.cross_verified; - j["device_verified"] = info.device_verified; - j["device_blocked"] = info.device_blocked; + j["device_verified"] = info.device_verified; + j["device_blocked"] = info.device_blocked; } void from_json(const json &j, VerificationCache &info) { - info.verified_master_key = j.at("verified_master_key"); - info.cross_verified = j.at("cross_verified").get>(); - info.device_verified = j.at("device_verified").get>(); - info.device_blocked = j.at("device_blocked").get>(); + info.device_verified = j.at("device_verified").get>(); + info.device_blocked = j.at("device_blocked").get>(); } std::optional -Cache::verificationStatus(const std::string &user_id) +Cache::verificationCache(const std::string &user_id) { lmdb::val verifiedVal; @@ -3298,6 +3316,23 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key) txn.commit(); } catch (std::exception &) { } + + const auto local_user = utils::localUser().toStdString(); + std::map tmp; + { + std::unique_lock lock(verification_storage.verification_storage_mtx); + if (user_id == local_user) { + std::swap(tmp, verification_storage.status); + } else { + verification_storage.status.erase(user_id); + } + } + if (user_id == local_user) { + for (const auto &[user, status] : tmp) + emit verificationStatusChanged(user); + } else { + emit verificationStatusChanged(user_id); + } } void @@ -3325,27 +3360,112 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) txn.commit(); } catch (std::exception &) { } + + const auto local_user = utils::localUser().toStdString(); + std::map tmp; + { + std::unique_lock lock(verification_storage.verification_storage_mtx); + if (user_id == local_user) { + std::swap(tmp, verification_storage.status); + } else { + verification_storage.status.erase(user_id); + } + } + if (user_id == local_user) { + for (const auto &[user, status] : tmp) + emit verificationStatusChanged(user); + } else { + emit verificationStatusChanged(user_id); + } } -void -Cache::markMasterKeyVerified(const std::string &user_id, const std::string &key) +VerificationStatus +Cache::verificationStatus(const std::string &user_id) { - lmdb::val val; + std::unique_lock lock(verification_storage.verification_storage_mtx); + if (verification_storage.status.count(user_id)) + return verification_storage.status.at(user_id); - auto txn = lmdb::txn::begin(env_); - auto db = getVerificationDb(txn); + VerificationStatus status; + + if (auto verifCache = verificationCache(user_id)) { + status.verified_devices = verifCache->device_verified; + } + + const auto local_user = utils::localUser().toStdString(); + + if (user_id == local_user) + status.verified_devices.push_back(http::client()->device_id()); + + verification_storage.status[user_id] = status; + + auto verifyAtLeastOneSig = [](const auto &toVerif, + const std::map &keys, + const std::string &keyOwner) { + if (!toVerif.signatures.count(keyOwner)) + return false; + + for (const auto &[key_id, signature] : toVerif.signatures.at(keyOwner)) { + if (!keys.count(key_id)) + continue; + + if (mtx::crypto::ed25519_verify_signature( + keys.at(key_id), json(toVerif), signature)) + return true; + } + return false; + }; try { - VerificationCache verified_state; - auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), val); - if (res) { - verified_state = json::parse(std::string_view(val.data(), val.size())); + // for local user verify this device_key -> our master_key -> our self_signing_key + // -> our device_keys + // + // for other user verify this device_key -> our master_key -> our user_signing_key + // -> their master_key -> their self_signing_key -> their device_keys + // + // This means verifying the other user adds 2 extra steps,verifying our user_signing + // key and their master key + auto ourKeys = userKeys(local_user); + auto theirKeys = userKeys(user_id); + if (!ourKeys || !theirKeys) + return status; + + if (!mtx::crypto::ed25519_verify_signature( + olm::client()->identity_keys().ed25519, + json(ourKeys->master_keys), + ourKeys->master_keys.signatures.at(local_user) + .at("ed25519:" + http::client()->device_id()))) + return status; + + auto master_keys = ourKeys->master_keys.keys; + + if (user_id != local_user) { + if (!verifyAtLeastOneSig( + ourKeys->user_signing_keys, master_keys, local_user)) + return status; + + if (!verifyAtLeastOneSig( + theirKeys->master_keys, ourKeys->user_signing_keys.keys, local_user)) + return status; + + master_keys = theirKeys->master_keys.keys; } - verified_state.verified_master_key = key; - lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump())); - txn.commit(); + status.user_verified = true; + + if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id)) + return status; + + for (const auto &[device, device_key] : theirKeys->device_keys) { + if (verifyAtLeastOneSig( + device_key, theirKeys->self_signing_keys.keys, user_id)) + status.verified_devices.push_back(device_key.device_id); + } + + verification_storage.status[user_id] = status; + return status; } catch (std::exception &) { + return status; } } @@ -3551,28 +3671,22 @@ updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &k } // device & user verification cache -std::optional +std::optional verificationStatus(const std::string &user_id) { return instance_->verificationStatus(user_id); } void -markDeviceVerified(const std::string &user_id, const std::string &key) -{ - instance_->markDeviceVerified(user_id, key); -} - -void -markDeviceUnverified(const std::string &user_id, const std::string &key) +markDeviceVerified(const std::string &user_id, const std::string &device) { - instance_->markDeviceUnverified(user_id, key); + instance_->markDeviceVerified(user_id, device); } void -markMasterKeyVerified(const std::string &user_id, const std::string &key) +markDeviceUnverified(const std::string &user_id, const std::string &device) { - instance_->markMasterKeyVerified(user_id, key); + instance_->markDeviceUnverified(user_id, device); } std::vector diff --git a/src/Cache.h b/src/Cache.h index fca80145..cd96708e 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -67,14 +67,12 @@ void updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); // device & user verification cache -std::optional +std::optional verificationStatus(const std::string &user_id); void -markDeviceVerified(const std::string &user_id, const std::string &key); +markDeviceVerified(const std::string &user_id, const std::string &device); void -markDeviceUnverified(const std::string &user_id, const std::string &key); -void -markMasterKeyVerified(const std::string &user_id, const std::string &key); +markDeviceUnverified(const std::string &user_id, const std::string &device); //! Load saved data for the display names & avatars. void diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 10636ac6..935d6493 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -66,6 +66,23 @@ struct OlmSessionStorage std::mutex group_inbound_mtx; }; +//! Verification status of a single user +struct VerificationStatus +{ + //! True, if the users master key is verified + bool user_verified = false; + //! List of all devices marked as verified + std::vector verified_devices; +}; + +//! In memory cache of verification status +struct VerificationStorage +{ + //! mapping of user to verification status + std::map status; + std::mutex verification_storage_mtx; +}; + // this will store the keys of the user with whom a encrypted room is shared with struct UserKeyCache { @@ -90,12 +107,8 @@ struct VerificationCache { //! list of verified device_ids with device-verification std::vector device_verified; - //! list of verified device_ids with cross-signing, calculated from master key - std::vector cross_verified; //! list of devices the user blocks std::vector device_blocked; - //! The verified master key. - std::string verified_master_key; }; void diff --git a/src/Cache_p.h b/src/Cache_p.h index b37eae58..b3f4c58c 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -67,10 +67,9 @@ public: const std::vector &user_ids); // device & user verification cache - std::optional verificationStatus(const std::string &user_id); - void markDeviceVerified(const std::string &user_id, const std::string &key); - void markDeviceUnverified(const std::string &user_id, const std::string &key); - void markMasterKeyVerified(const std::string &user_id, const std::string &key); + VerificationStatus verificationStatus(const std::string &user_id); + void markDeviceVerified(const std::string &user_id, const std::string &device); + void markDeviceUnverified(const std::string &user_id, const std::string &device); static void removeDisplayName(const QString &room_id, const QString &user_id); static void removeAvatarUrl(const QString &room_id, const QString &user_id); @@ -283,6 +282,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); private: //! Save an invited room. @@ -576,6 +576,8 @@ private: return QString::fromStdString(event.state_key); } + std::optional verificationCache(const std::string &user_id); + void setNextBatchToken(lmdb::txn &txn, const std::string &token); void setNextBatchToken(lmdb::txn &txn, const QString &token); @@ -600,6 +602,7 @@ private: static QHash AvatarUrls; OlmSessionStorage session_storage; + VerificationStorage verification_storage; }; namespace cache { diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index e8d381df..359e95bc 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1031,7 +1031,7 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events:: try { if (!mtx::crypto::verify_identity_signature( - json(dev.second), device_id, user_id)) { + dev.second, device_id, user_id)) { nhlog::crypto()->warn( "failed to verify identity keys: {}", json(dev.second).dump(2)); diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 2a1eecdf..2bb0370f 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -1,5 +1,5 @@ #include "UserProfile.h" -#include "Cache.h" +#include "Cache_p.h" #include "ChatPage.h" #include "DeviceVerificationFlow.h" #include "Logging.h" @@ -8,8 +8,6 @@ #include "timeline/TimelineModel.h" #include "timeline/TimelineViewManager.h" -#include // only for debugging - UserProfile::UserProfile(QString roomid, QString userid, TimelineViewManager *manager_, @@ -21,6 +19,31 @@ UserProfile::UserProfile(QString roomid, , model(parent) { fetchDeviceList(this->userid_); + + connect(cache::client(), + &Cache::verificationStatusChanged, + this, + [this](const std::string &user_id) { + if (user_id != this->userid_.toStdString()) + return; + + auto status = cache::verificationStatus(user_id); + if (!status) + return; + this->isUserVerified = status->user_verified; + emit userStatusChanged(); + + for (auto &deviceInfo : deviceList_.deviceList_) { + deviceInfo.verification_status = + std::find(status->verified_devices.begin(), + status->verified_devices.end(), + deviceInfo.device_id.toStdString()) == + status->verified_devices.end() + ? verification::UNVERIFIED + : verification::VERIFIED; + } + deviceList_.reset(deviceList_.deviceList_); + }); } QHash @@ -126,107 +149,27 @@ UserProfile::fetchDeviceList(const QString &userID) } std::vector deviceInfo; - auto devices = other_user_keys.device_keys; - auto device_verified = cache::verificationStatus(other_user_id); - - if (device_verified.has_value()) { - // TODO: properly check cross-signing signatures here - isUserVerified = !device_verified->verified_master_key.empty(); - } - - std::optional lmk, lsk, luk, mk, sk, uk; + auto devices = other_user_keys.device_keys; + auto verificationStatus = + cache::client()->verificationStatus(other_user_id); - lmk = res.master_keys; - luk = res.user_signing_keys; - lsk = res.self_signing_keys; - mk = other_user_keys.master_keys; - uk = other_user_keys.user_signing_keys; - sk = other_user_keys.self_signing_keys; - - // First checking if the user is verified - if (luk.has_value() && mk.has_value()) { - // iterating through the public key of local user_signing keys - for (auto sign_key : luk.value().keys) { - // checking if the signatures are empty as "at" could - // cause exceptions - auto signs = mk->signatures; - if (!signs.empty() && - signs.find(local_user_id) != signs.end()) { - auto sign = signs.at(local_user_id); - try { - isUserVerified = - isUserVerified || - (olm::client()->ed25519_verify_sig( - sign_key.second, - json(mk.value()), - sign.at(sign_key.first))); - } catch (std::out_of_range &) { - isUserVerified = - isUserVerified || false; - } - } - } - } + isUserVerified = verificationStatus.user_verified; + emit userStatusChanged(); for (const auto &d : devices) { auto device = d.second; verification::Status verified = verification::Status::UNVERIFIED; - if (device_verified.has_value()) { - if (std::find(device_verified->cross_verified.begin(), - device_verified->cross_verified.end(), - d.first) != - device_verified->cross_verified.end()) - verified = verification::Status::VERIFIED; - if (std::find(device_verified->device_verified.begin(), - device_verified->device_verified.end(), - d.first) != - device_verified->device_verified.end()) - verified = verification::Status::VERIFIED; - if (std::find(device_verified->device_blocked.begin(), - device_verified->device_blocked.end(), - d.first) != - device_verified->device_blocked.end()) - verified = verification::Status::BLOCKED; - } else if (isUserVerified) { - device_verified = VerificationCache{}; - } - - // won't check for already verified devices - if (verified != verification::Status::VERIFIED && - isUserVerified) { - if ((sk.has_value()) && (!device.signatures.empty())) { - for (auto sign_key : sk.value().keys) { - auto signs = - device.signatures.at(other_user_id); - try { - if (olm::client() - ->ed25519_verify_sig( - sign_key.second, - json(device), - signs.at( - sign_key.first))) { - verified = - verification::Status:: - VERIFIED; - device_verified.value() - .cross_verified - .push_back(d.first); - } - } catch (std::out_of_range &) { - } - } - } - } - - // TODO(Nico): properly show cross-signing - // if (device_verified.has_value()) { - // device_verified.value().is_user_verified = - // isUserVerified; - // cache::setVerifiedCache(user_id, - // device_verified.value()); - //} + if (std::find(verificationStatus.verified_devices.begin(), + verificationStatus.verified_devices.end(), + device.device_id) != + verificationStatus.verified_devices.end() && + mtx::crypto::verify_identity_signature( + device, + DeviceId(device.device_id), + UserId(other_user_id))) + verified = verification::Status::VERIFIED; deviceInfo.push_back( {QString::fromStdString(d.first), @@ -235,14 +178,6 @@ UserProfile::fetchDeviceList(const QString &userID) verified}); } - std::cout << (isUserVerified ? "Yes" : "No") << std::endl; - - std::sort(deviceInfo.begin(), - deviceInfo.end(), - [](const DeviceInfo &a, const DeviceInfo &b) { - return a.device_id > b.device_id; - }); - this->deviceList_.queueReset(std::move(deviceInfo)); }); }); diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index 18933727..77b22323 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -74,6 +74,8 @@ public slots: private: std::vector deviceList_; + + friend class UserProfile; }; class UserProfile : public QObject @@ -83,7 +85,7 @@ class UserProfile : public QObject Q_PROPERTY(QString userid READ userid CONSTANT) Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT) Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT) - Q_PROPERTY(bool isUserVerified READ getUserStatus CONSTANT) + Q_PROPERTY(bool isUserVerified READ getUserStatus NOTIFY userStatusChanged) public: UserProfile(QString roomid, QString userid, @@ -105,9 +107,11 @@ public: Q_INVOKABLE void kickUser(); Q_INVOKABLE void startChat(); +signals: + void userStatusChanged(); + private: QString roomid_, userid_; - std::optional cross_verified; DeviceInfoModel deviceList_; bool isUserVerified = false; TimelineViewManager *manager; -- cgit 1.5.1 From cd43147b77da5852c336fcac857da5c6e7f09569 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 8 Oct 2020 16:17:38 +0200 Subject: Implement signature upload for own master key --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.json | 2 +- src/Cache.cpp | 1 + src/DeviceVerificationFlow.cpp | 49 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) (limited to 'src/Cache.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index bdfc7798..9280f7aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -341,7 +341,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG f84611f129b46746a4b586acaba54fc31a303bc6 + GIT_TAG ad5575bc24089dc385e97d9ace026414b618775c ) FetchContent_MakeAvailable(MatrixClient) else() diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index da1b5a37..930b39ea 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -146,7 +146,7 @@ "name": "mtxclient", "sources": [ { - "commit": "f84611f129b46746a4b586acaba54fc31a303bc6", + "commit": "ad5575bc24089dc385e97d9ace026414b618775c", "type": "git", "url": "https://github.com/Nheko-Reborn/mtxclient.git" } diff --git a/src/Cache.cpp b/src/Cache.cpp index d6da03c6..d1afa2a1 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3591,6 +3591,7 @@ init(const QString &user_id) qRegisterMetaType>(); qRegisterMetaType>(); qRegisterMetaType>(); + qRegisterMetaType(); instance_ = std::make_unique(user_id); } diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 549569f4..97f42592 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -234,6 +234,55 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, } 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; + } + } + // TODO(Nico): Sign their device key with self signing key + } else { + // TODO(Nico): Sign their master key with user signing 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: {},{}", + 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, + e.errcode, + e.error); + }); + } + this->isMacVerified = true; this->acceptDevice(); } else { -- cgit 1.5.1 From 56ba7de50135f566766da334b6ae955f7d6344c6 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 8 Oct 2020 18:38:55 +0200 Subject: Fix unused variable warnings on old compilers --- src/Cache.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'src/Cache.cpp') diff --git a/src/Cache.cpp b/src/Cache.cpp index d1afa2a1..08b6f155 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3191,6 +3191,7 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query { std::unique_lock lock(verification_storage.verification_storage_mtx); for (auto &[user_id, update] : updates) { + (void)update; if (user_id == local_user) { std::swap(tmp, verification_storage.status); } else { @@ -3199,9 +3200,12 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query } } for (auto &[user_id, update] : updates) { + (void)update; if (user_id == local_user) { - for (const auto &[user, status] : tmp) + for (const auto &[user, status] : tmp) { + (void)status; emit verificationStatusChanged(user); + } } else { emit verificationStatusChanged(user_id); } @@ -3328,8 +3332,10 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key) } } if (user_id == local_user) { - for (const auto &[user, status] : tmp) + for (const auto &[user, status] : tmp) { + (void)status; emit verificationStatusChanged(user); + } } else { emit verificationStatusChanged(user_id); } @@ -3372,8 +3378,10 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) } } if (user_id == local_user) { - for (const auto &[user, status] : tmp) + for (const auto &[user, status] : tmp) { + (void)status; emit verificationStatusChanged(user); + } } else { emit verificationStatusChanged(user_id); } @@ -3457,6 +3465,7 @@ Cache::verificationStatus(const std::string &user_id) return status; for (const auto &[device, device_key] : theirKeys->device_keys) { + (void)device; if (verifyAtLeastOneSig( device_key, theirKeys->self_signing_keys.keys, user_id)) status.verified_devices.push_back(device_key.device_id); -- cgit 1.5.1