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<std::string, VerificationStatus> tmp;
+ const auto local_user = utils::localUser().toStdString();
+
+ {
+ std::unique_lock<std::mutex> 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<std::vector<std::string>>();
- info.device_verified = j.at("device_verified").get<std::vector<std::string>>();
- info.device_blocked = j.at("device_blocked").get<std::vector<std::string>>();
+ info.device_verified = j.at("device_verified").get<std::vector<std::string>>();
+ info.device_blocked = j.at("device_blocked").get<std::vector<std::string>>();
}
std::optional<VerificationCache>
-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<std::string, VerificationStatus> tmp;
+ {
+ std::unique_lock<std::mutex> 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<std::string, VerificationStatus> tmp;
+ {
+ std::unique_lock<std::mutex> 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<std::mutex> 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<std::string, std::string> &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<VerificationCache>
+std::optional<VerificationStatus>
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<std::string>
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<VerificationCache>
+std::optional<VerificationStatus>
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<std::string> verified_devices;
+};
+
+//! In memory cache of verification status
+struct VerificationStorage
+{
+ //! mapping of user to verification status
+ std::map<std::string, VerificationStatus> 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<std::string> device_verified;
- //! list of verified device_ids with cross-signing, calculated from master key
- std::vector<std::string> cross_verified;
//! list of devices the user blocks
std::vector<std::string> 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<std::string> &user_ids);
// device & user verification cache
- std::optional<VerificationCache> 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> 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<QString, QString> 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<T> 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 <iostream> // 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<int, QByteArray>
@@ -126,107 +149,27 @@ UserProfile::fetchDeviceList(const QString &userID)
}
std::vector<DeviceInfo> 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<crypto::CrossSigningKeys> 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<DeviceInfo> 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<std::string> cross_verified;
DeviceInfoModel deviceList_;
bool isUserVerified = false;
TimelineViewManager *manager;
|