mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 12:38:48 +03:00
Show verification status next to messages
This commit is contained in:
parent
7333de19da
commit
0d0709ccd3
14 changed files with 108 additions and 22 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
12
src/Olm.cpp
12
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: 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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,6 +196,7 @@ public:
|
||||||
IsEdited,
|
IsEdited,
|
||||||
IsEditable,
|
IsEditable,
|
||||||
IsEncrypted,
|
IsEncrypted,
|
||||||
|
Trustlevel,
|
||||||
IsRoomEncrypted,
|
IsRoomEncrypted,
|
||||||
ReplyTo,
|
ReplyTo,
|
||||||
Reactions,
|
Reactions,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -135,7 +135,7 @@ UserProfile::isGlobalUserProfile() const
|
||||||
return roomid_ == "";
|
return roomid_ == "";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
crypto::Trust
|
||||||
UserProfile::getUserStatus()
|
UserProfile::getUserStatus()
|
||||||
{
|
{
|
||||||
return isUserVerified;
|
return isUserVerified;
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue