diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dd59b70..96948827 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -526,6 +526,7 @@ qt5_wrap_cpp(MOC_HEADERS src/AvatarProvider.h src/BlurhashProvider.h src/Cache_p.h + src/CacheCryptoStructs.h src/CallDevices.h src/CallManager.h src/ChatPage.h diff --git a/resources/qml/EncryptionIndicator.qml b/resources/qml/EncryptionIndicator.qml index 58805f48..1e5d4d38 100644 --- a/resources/qml/EncryptionIndicator.qml +++ b/resources/qml/EncryptionIndicator.qml @@ -10,17 +10,38 @@ Image { id: stateImg property bool encrypted: false + property int trust: Crypto.Unverified width: 16 height: 16 source: { - if (encrypted) - return "image://colorimage/:/icons/icons/ui/lock.png?" + colors.buttonText; - else + if (encrypted) { + switch (trust) { + case Crypto.Verified: + return "image://colorimage/:/icons/icons/ui/lock.png?green"; + case Crypto.TOFU: + return "image://colorimage/:/icons/icons/ui/lock.png?" + colors.buttonText; + default: + return "image://colorimage/:/icons/icons/ui/lock.png?#dd3d3d"; + } + } else { return "image://colorimage/:/icons/icons/ui/unlock.png?#dd3d3d"; + } } ToolTip.visible: ma.hovered - ToolTip.text: encrypted ? qsTr("Encrypted") : qsTr("This message is not encrypted!") + ToolTip.text: { + if (!encrypted) + return qsTr("This message is not encrypted!"); + + switch (trust) { + case Crypto.Verified: + return qsTr("Encrypted by a verified device"); + case Crypto.TOFU: + return qsTr("Encrypted by an unverified device, but you have trusted that user so far."); + default: + return qsTr("Encrypted by an unverified device"); + } + } HoverHandler { id: ma diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 715e8bd1..09a55e60 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -86,6 +86,7 @@ Item { EncryptionIndicator { visible: model.isRoomEncrypted encrypted: model.isEncrypted + trust: model.trustlevel Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.preferredHeight: 16 Layout.preferredWidth: 16 diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index bd25b74e..21c44793 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -137,16 +137,16 @@ ApplicationWindow { text: qsTr("Verify") Layout.alignment: Qt.AlignHCenter - enabled: !profile.isUserVerified - visible: !profile.isUserVerified && !profile.isSelf && profile.userVerificationEnabled + enabled: profile.userVerified != Crypto.Verified + visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled onClicked: profile.verify() } Image { Layout.preferredHeight: 16 Layout.preferredWidth: 16 - source: "image://colorimage/:/icons/icons/ui/lock.png?green" - visible: profile.isUserVerified + source: "image://colorimage/:/icons/icons/ui/lock.png?" + ((profile.userVerified == Crypto.Verified) ? "green" : colors.buttonText) + visible: profile.userVerified != Crypto.Unverified Layout.alignment: Qt.AlignHCenter } diff --git a/src/Cache.cpp b/src/Cache.cpp index 0de790ac..4022ce85 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3666,8 +3666,11 @@ Cache::verificationStatus(const std::string &user_id) const auto local_user = utils::localUser().toStdString(); - if (user_id == local_user) + crypto::Trust trustlevel = crypto::Trust::Unverified; + if (user_id == local_user) { status.verified_devices.push_back(http::client()->device_id()); + trustlevel = crypto::Trust::Verified; + } verification_storage.status[user_id] = status; @@ -3723,16 +3726,24 @@ Cache::verificationStatus(const std::string &user_id) master_keys = theirKeys->master_keys.keys; } - status.user_verified = true; + trustlevel = crypto::Trust::Verified; + status.user_verified = crypto::Trust::Verified; if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, 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); + try { + auto identkey = + device_key.keys.at("curve25519:" + device_key.device_id); + if (verifyAtLeastOneSig( + device_key, theirKeys->self_signing_keys.keys, user_id)) { + status.verified_devices.push_back(device_key.device_id); + status.verified_device_keys[identkey] = trustlevel; + } + } catch (...) { + } } verification_storage.status[user_id] = status; diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index c884107e..b665be86 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -4,12 +4,28 @@ #pragma once +#include + #include #include +#include #include #include +namespace crypto { +Q_NAMESPACE +//! How much a participant is trusted. +enum Trust +{ + Unverified, //! Device unverified or master key changed. + TOFU, //! Device is signed by the sender, but the user is not verified, but they never + //! changed the master key. + Verified, //! User was verified and has crosssigned this device or device is verified. +}; +Q_ENUM_NS(Trust) +} + struct DeviceAndMasterKeys { // map from device id or master key id to message_index @@ -87,9 +103,11 @@ from_json(const nlohmann::json &obj, StoredOlmSession &msg); struct VerificationStatus { //! True, if the users master key is verified - bool user_verified = false; + crypto::Trust user_verified = crypto::Trust::Unverified; //! List of all devices marked as verified std::vector verified_devices; + //! Map from sender key/curve25519 to trust status + std::map verified_device_keys; }; //! In memory cache of verification status diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index f7fb6c35..cd375cc9 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -78,7 +78,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, if (auto status = cache::verificationStatus(http::client()->user_id().to_string()); - status && status->user_verified) + status && status->user_verified == crypto::Trust::Verified) this->our_trusted_master_key = res.master_keys.keys.begin()->second; }); diff --git a/src/Olm.cpp b/src/Olm.cpp index b218ba2c..d08c1b3e 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -939,7 +939,6 @@ decryptEvent(const MegolmSessionIndex &index, } // TODO: Lookup index,event_id,origin_server_ts tuple for replay attack errors - // TODO: Verify sender_key std::string msg_str; try { @@ -976,6 +975,17 @@ decryptEvent(const MegolmSessionIndex &index, return {std::nullopt, std::nullopt, std::move(te.data)}; } +crypto::Trust +calculate_trust(const std::string &user_id, const std::string &curve25519) +{ + auto status = cache::client()->verificationStatus(user_id); + crypto::Trust trustlevel = crypto::Trust::Unverified; + if (status.verified_device_keys.count(curve25519)) + trustlevel = status.verified_device_keys.at(curve25519); + + return trustlevel; +} + //! Send encrypted to device messages, targets is a map from userid to device ids or {} for all //! devices void diff --git a/src/Olm.h b/src/Olm.h index bcb486a3..d356cb55 100644 --- a/src/Olm.h +++ b/src/Olm.h @@ -34,6 +34,7 @@ struct DecryptionResult { std::optional error; std::optional error_message; + std::optional event; }; @@ -83,6 +84,8 @@ encrypt_group_message(const std::string &room_id, DecryptionResult decryptEvent(const MegolmSessionIndex &index, const mtx::events::EncryptedEvent &event); +crypto::Trust +calculate_trust(const std::string &user_id, const std::string &curve25519); void mark_keys_as_published(); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index a1e9ac0c..5fa28234 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -407,6 +407,7 @@ TimelineModel::roleNames() const {IsEdited, "isEdited"}, {IsEditable, "isEditable"}, {IsEncrypted, "isEncrypted"}, + {Trustlevel, "trustlevel"}, {IsRoomEncrypted, "isRoomEncrypted"}, {ReplyTo, "replyTo"}, {Reactions, "reactions"}, @@ -575,6 +576,21 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r mtx::events::EncryptedEvent>( *encrypted_event); } + + case Trustlevel: { + auto id = event_id(event); + auto encrypted_event = events.get(id, id, false); + if (encrypted_event) { + if (auto encrypted = + std::get_if>( + &*encrypted_event)) { + return olm::calculate_trust(encrypted->sender, + encrypted->content.sender_key); + } + } + return crypto::Trust::Unverified; + } + case IsRoomEncrypted: { return cache::isRoomEncrypted(room_id_.toStdString()); } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index caeb25cf..92fccd2d 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -196,6 +196,7 @@ public: IsEdited, IsEditable, IsEncrypted, + Trustlevel, IsRoomEncrypted, ReplyTo, Reactions, diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index decd245c..628f3c31 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -164,6 +164,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par 0, "MtxEvent", "Can't instantiate enum!"); + qmlRegisterUncreatableMetaObject( + crypto::staticMetaObject, "im.nheko", 1, 0, "Crypto", "Can't instantiate enum!"); qmlRegisterUncreatableMetaObject(verification::staticMetaObject, "im.nheko", 1, diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index b5feb353..0f330964 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -135,7 +135,7 @@ UserProfile::isGlobalUserProfile() const return roomid_ == ""; } -bool +crypto::Trust UserProfile::getUserStatus() { return isUserVerified; diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index aa7266ab..bf71d0de 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -11,6 +11,8 @@ #include #include +#include "CacheCryptoStructs.h" + namespace verification { Q_NAMESPACE @@ -90,7 +92,7 @@ class UserProfile : public QObject Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT) Q_PROPERTY(bool isGlobalUserProfile READ isGlobalUserProfile CONSTANT) - Q_PROPERTY(bool isUserVerified READ getUserStatus NOTIFY userStatusChanged) + Q_PROPERTY(int userVerified READ getUserStatus NOTIFY userStatusChanged) Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged) Q_PROPERTY( bool userVerificationEnabled READ userVerificationEnabled NOTIFY userStatusChanged) @@ -108,7 +110,7 @@ public: QString displayName(); QString avatarUrl(); bool isGlobalUserProfile() const; - bool getUserStatus(); + crypto::Trust getUserStatus(); bool userVerificationEnabled() const; bool isSelf() const; bool isLoading() const; @@ -147,9 +149,9 @@ private: QString globalUsername; QString globalAvatarUrl; DeviceInfoModel deviceList_; - bool isUserVerified = false; - bool hasMasterKey = false; - bool isLoading_ = false; + crypto::Trust isUserVerified = crypto::Trust::Unverified; + bool hasMasterKey = false; + bool isLoading_ = false; TimelineViewManager *manager; TimelineModel *model; };