Show verification status next to messages

This commit is contained in:
Nicolas Werner 2021-05-07 12:19:46 +02:00
parent 7333de19da
commit 0d0709ccd3
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
14 changed files with 108 additions and 22 deletions

View file

@ -526,6 +526,7 @@ qt5_wrap_cpp(MOC_HEADERS
src/AvatarProvider.h src/AvatarProvider.h
src/BlurhashProvider.h src/BlurhashProvider.h
src/Cache_p.h src/Cache_p.h
src/CacheCryptoStructs.h
src/CallDevices.h src/CallDevices.h
src/CallManager.h src/CallManager.h
src/ChatPage.h src/ChatPage.h

View file

@ -10,17 +10,38 @@ Image {
id: stateImg id: stateImg
property bool encrypted: false property bool encrypted: false
property int trust: Crypto.Unverified
width: 16 width: 16
height: 16 height: 16
source: { source: {
if (encrypted) if (encrypted) {
return "image://colorimage/:/icons/icons/ui/lock.png?" + colors.buttonText; switch (trust) {
else 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"; return "image://colorimage/:/icons/icons/ui/unlock.png?#dd3d3d";
}
} }
ToolTip.visible: ma.hovered 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 { HoverHandler {
id: ma id: ma

View file

@ -86,6 +86,7 @@ Item {
EncryptionIndicator { EncryptionIndicator {
visible: model.isRoomEncrypted visible: model.isRoomEncrypted
encrypted: model.isEncrypted encrypted: model.isEncrypted
trust: model.trustlevel
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16

View file

@ -137,16 +137,16 @@ ApplicationWindow {
text: qsTr("Verify") text: qsTr("Verify")
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
enabled: !profile.isUserVerified enabled: profile.userVerified != Crypto.Verified
visible: !profile.isUserVerified && !profile.isSelf && profile.userVerificationEnabled visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled
onClicked: profile.verify() onClicked: profile.verify()
} }
Image { Image {
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16
source: "image://colorimage/:/icons/icons/ui/lock.png?green" source: "image://colorimage/:/icons/icons/ui/lock.png?" + ((profile.userVerified == Crypto.Verified) ? "green" : colors.buttonText)
visible: profile.isUserVerified visible: profile.userVerified != Crypto.Unverified
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
} }

View file

@ -3666,8 +3666,11 @@ Cache::verificationStatus(const std::string &user_id)
const auto local_user = utils::localUser().toStdString(); 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()); status.verified_devices.push_back(http::client()->device_id());
trustlevel = crypto::Trust::Verified;
}
verification_storage.status[user_id] = status; verification_storage.status[user_id] = status;
@ -3723,16 +3726,24 @@ Cache::verificationStatus(const std::string &user_id)
master_keys = theirKeys->master_keys.keys; 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)) if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id))
return status; return status;
for (const auto &[device, device_key] : theirKeys->device_keys) { for (const auto &[device, device_key] : theirKeys->device_keys) {
(void)device; (void)device;
if (verifyAtLeastOneSig( try {
device_key, theirKeys->self_signing_keys.keys, user_id)) auto identkey =
status.verified_devices.push_back(device_key.device_id); 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; verification_storage.status[user_id] = status;

View file

@ -4,12 +4,28 @@
#pragma once #pragma once
#include <QObject>
#include <map> #include <map>
#include <mutex> #include <mutex>
#include <mtx/events/encrypted.hpp>
#include <mtx/responses/crypto.hpp> #include <mtx/responses/crypto.hpp>
#include <mtxclient/crypto/objects.hpp> #include <mtxclient/crypto/objects.hpp>
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 struct DeviceAndMasterKeys
{ {
// map from device id or master key id to message_index // 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 struct VerificationStatus
{ {
//! True, if the users master key is verified //! 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 //! List of all devices marked as verified
std::vector<std::string> verified_devices; std::vector<std::string> verified_devices;
//! Map from sender key/curve25519 to trust status
std::map<std::string, crypto::Trust> verified_device_keys;
}; };
//! In memory cache of verification status //! In memory cache of verification status

View file

@ -78,7 +78,7 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
if (auto status = if (auto status =
cache::verificationStatus(http::client()->user_id().to_string()); 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; this->our_trusted_master_key = res.master_keys.keys.begin()->second;
}); });

View file

@ -939,7 +939,6 @@ decryptEvent(const MegolmSessionIndex &index,
} }
// TODO: Lookup index,event_id,origin_server_ts tuple for replay attack errors // TODO: Lookup index,event_id,origin_server_ts tuple for replay attack errors
// TODO: Verify sender_key
std::string msg_str; std::string msg_str;
try { try {
@ -976,6 +975,17 @@ decryptEvent(const MegolmSessionIndex &index,
return {std::nullopt, std::nullopt, std::move(te.data)}; 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 //! Send encrypted to device messages, targets is a map from userid to device ids or {} for all
//! devices //! devices
void void

View file

@ -34,6 +34,7 @@ struct DecryptionResult
{ {
std::optional<DecryptionErrorCode> error; std::optional<DecryptionErrorCode> error;
std::optional<std::string> error_message; std::optional<std::string> error_message;
std::optional<mtx::events::collections::TimelineEvents> event; std::optional<mtx::events::collections::TimelineEvents> event;
}; };
@ -83,6 +84,8 @@ encrypt_group_message(const std::string &room_id,
DecryptionResult DecryptionResult
decryptEvent(const MegolmSessionIndex &index, decryptEvent(const MegolmSessionIndex &index,
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &event); const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &event);
crypto::Trust
calculate_trust(const std::string &user_id, const std::string &curve25519);
void void
mark_keys_as_published(); mark_keys_as_published();

View file

@ -407,6 +407,7 @@ TimelineModel::roleNames() const
{IsEdited, "isEdited"}, {IsEdited, "isEdited"},
{IsEditable, "isEditable"}, {IsEditable, "isEditable"},
{IsEncrypted, "isEncrypted"}, {IsEncrypted, "isEncrypted"},
{Trustlevel, "trustlevel"},
{IsRoomEncrypted, "isRoomEncrypted"}, {IsRoomEncrypted, "isRoomEncrypted"},
{ReplyTo, "replyTo"}, {ReplyTo, "replyTo"},
{Reactions, "reactions"}, {Reactions, "reactions"},
@ -575,6 +576,21 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
*encrypted_event); *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<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
&*encrypted_event)) {
return olm::calculate_trust(encrypted->sender,
encrypted->content.sender_key);
}
}
return crypto::Trust::Unverified;
}
case IsRoomEncrypted: { case IsRoomEncrypted: {
return cache::isRoomEncrypted(room_id_.toStdString()); return cache::isRoomEncrypted(room_id_.toStdString());
} }

View file

@ -196,6 +196,7 @@ public:
IsEdited, IsEdited,
IsEditable, IsEditable,
IsEncrypted, IsEncrypted,
Trustlevel,
IsRoomEncrypted, IsRoomEncrypted,
ReplyTo, ReplyTo,
Reactions, Reactions,

View file

@ -164,6 +164,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
0, 0,
"MtxEvent", "MtxEvent",
"Can't instantiate enum!"); "Can't instantiate enum!");
qmlRegisterUncreatableMetaObject(
crypto::staticMetaObject, "im.nheko", 1, 0, "Crypto", "Can't instantiate enum!");
qmlRegisterUncreatableMetaObject(verification::staticMetaObject, qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
"im.nheko", "im.nheko",
1, 1,

View file

@ -135,7 +135,7 @@ UserProfile::isGlobalUserProfile() const
return roomid_ == ""; return roomid_ == "";
} }
bool crypto::Trust
UserProfile::getUserStatus() UserProfile::getUserStatus()
{ {
return isUserVerified; return isUserVerified;

View file

@ -11,6 +11,8 @@
#include <mtx/responses.hpp> #include <mtx/responses.hpp>
#include <mtx/responses/common.hpp> #include <mtx/responses/common.hpp>
#include "CacheCryptoStructs.h"
namespace verification { namespace verification {
Q_NAMESPACE Q_NAMESPACE
@ -90,7 +92,7 @@ class UserProfile : public QObject
Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT) Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT)
Q_PROPERTY(bool isGlobalUserProfile READ isGlobalUserProfile 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 isLoading READ isLoading NOTIFY loadingChanged)
Q_PROPERTY( Q_PROPERTY(
bool userVerificationEnabled READ userVerificationEnabled NOTIFY userStatusChanged) bool userVerificationEnabled READ userVerificationEnabled NOTIFY userStatusChanged)
@ -108,7 +110,7 @@ public:
QString displayName(); QString displayName();
QString avatarUrl(); QString avatarUrl();
bool isGlobalUserProfile() const; bool isGlobalUserProfile() const;
bool getUserStatus(); crypto::Trust getUserStatus();
bool userVerificationEnabled() const; bool userVerificationEnabled() const;
bool isSelf() const; bool isSelf() const;
bool isLoading() const; bool isLoading() const;
@ -147,9 +149,9 @@ private:
QString globalUsername; QString globalUsername;
QString globalAvatarUrl; QString globalAvatarUrl;
DeviceInfoModel deviceList_; DeviceInfoModel deviceList_;
bool isUserVerified = false; crypto::Trust isUserVerified = crypto::Trust::Unverified;
bool hasMasterKey = false; bool hasMasterKey = false;
bool isLoading_ = false; bool isLoading_ = false;
TimelineViewManager *manager; TimelineViewManager *manager;
TimelineModel *model; TimelineModel *model;
}; };