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;