Calculate verification status from cross-signing sigs and update dynamically

This commit is contained in:
Nicolas Werner 2020-10-07 23:03:14 +02:00
parent 64d5a193f1
commit 7b6fab3373
9 changed files with 223 additions and 157 deletions

View file

@ -56,7 +56,7 @@ ApplicationWindow{
Button { Button {
id: verifyUserButton id: verifyUserButton
text: "Verify" text: qsTr("Verify")
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
enabled: !profile.isUserVerified enabled: !profile.isUserVerified
visible: !profile.isUserVerified visible: !profile.isUserVerified
@ -155,7 +155,6 @@ ApplicationWindow{
onClicked: { onClicked: {
if(model.verificationStatus == VerificationStatus.VERIFIED){ if(model.verificationStatus == VerificationStatus.VERIFIED){
profile.unverify(model.deviceId) profile.unverify(model.deviceId)
deviceVerificationList.updateProfile(newFlow.userId);
}else{ }else{
profile.verify(model.deviceId); profile.verify(model.deviceId);
} }

View file

@ -1,6 +1,6 @@
import QtQuick 2.3 import QtQuick 2.10
import QtQuick.Controls 2.10 import QtQuick.Controls 2.10
import QtQuick.Window 2.2 import QtQuick.Window 2.10
import im.nheko 1.0 import im.nheko 1.0

View file

@ -3184,6 +3184,28 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
} }
txn.commit(); 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 void
@ -3236,8 +3258,6 @@ Cache::markUserKeysOutOfDate(lmdb::txn &txn,
void void
to_json(json &j, const VerificationCache &info) 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_verified"] = info.device_verified;
j["device_blocked"] = info.device_blocked; j["device_blocked"] = info.device_blocked;
} }
@ -3245,14 +3265,12 @@ to_json(json &j, const VerificationCache &info)
void void
from_json(const json &j, VerificationCache &info) 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_verified = j.at("device_verified").get<std::vector<std::string>>();
info.device_blocked = j.at("device_blocked").get<std::vector<std::string>>(); info.device_blocked = j.at("device_blocked").get<std::vector<std::string>>();
} }
std::optional<VerificationCache> std::optional<VerificationCache>
Cache::verificationStatus(const std::string &user_id) Cache::verificationCache(const std::string &user_id)
{ {
lmdb::val verifiedVal; lmdb::val verifiedVal;
@ -3298,6 +3316,23 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key)
txn.commit(); txn.commit();
} catch (std::exception &) { } 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 void
@ -3325,27 +3360,112 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key)
txn.commit(); txn.commit();
} catch (std::exception &) { } 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 VerificationStatus
Cache::markMasterKeyVerified(const std::string &user_id, const std::string &key) 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_); VerificationStatus status;
auto db = getVerificationDb(txn);
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 { try {
VerificationCache verified_state; // for local user verify this device_key -> our master_key -> our self_signing_key
auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), val); // -> our device_keys
if (res) { //
verified_state = json::parse(std::string_view(val.data(), val.size())); // 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; status.user_verified = true;
lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump()));
txn.commit(); 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 &) { } catch (std::exception &) {
return status;
} }
} }
@ -3551,28 +3671,22 @@ updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &k
} }
// device & user verification cache // device & user verification cache
std::optional<VerificationCache> std::optional<VerificationStatus>
verificationStatus(const std::string &user_id) verificationStatus(const std::string &user_id)
{ {
return instance_->verificationStatus(user_id); return instance_->verificationStatus(user_id);
} }
void void
markDeviceVerified(const std::string &user_id, const std::string &key) markDeviceVerified(const std::string &user_id, const std::string &device)
{ {
instance_->markDeviceVerified(user_id, key); instance_->markDeviceVerified(user_id, device);
} }
void void
markDeviceUnverified(const std::string &user_id, const std::string &key) markDeviceUnverified(const std::string &user_id, const std::string &device)
{ {
instance_->markDeviceUnverified(user_id, key); instance_->markDeviceUnverified(user_id, device);
}
void
markMasterKeyVerified(const std::string &user_id, const std::string &key)
{
instance_->markMasterKeyVerified(user_id, key);
} }
std::vector<std::string> std::vector<std::string>

View file

@ -67,14 +67,12 @@ void
updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery);
// device & user verification cache // device & user verification cache
std::optional<VerificationCache> std::optional<VerificationStatus>
verificationStatus(const std::string &user_id); verificationStatus(const std::string &user_id);
void void
markDeviceVerified(const std::string &user_id, const std::string &key); markDeviceVerified(const std::string &user_id, const std::string &device);
void void
markDeviceUnverified(const std::string &user_id, const std::string &key); markDeviceUnverified(const std::string &user_id, const std::string &device);
void
markMasterKeyVerified(const std::string &user_id, const std::string &key);
//! Load saved data for the display names & avatars. //! Load saved data for the display names & avatars.
void void

View file

@ -66,6 +66,23 @@ struct OlmSessionStorage
std::mutex group_inbound_mtx; 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 // this will store the keys of the user with whom a encrypted room is shared with
struct UserKeyCache struct UserKeyCache
{ {
@ -90,12 +107,8 @@ struct VerificationCache
{ {
//! list of verified device_ids with device-verification //! list of verified device_ids with device-verification
std::vector<std::string> device_verified; 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 //! list of devices the user blocks
std::vector<std::string> device_blocked; std::vector<std::string> device_blocked;
//! The verified master key.
std::string verified_master_key;
}; };
void void

View file

@ -67,10 +67,9 @@ public:
const std::vector<std::string> &user_ids); const std::vector<std::string> &user_ids);
// device & user verification cache // device & user verification cache
std::optional<VerificationCache> verificationStatus(const std::string &user_id); VerificationStatus verificationStatus(const std::string &user_id);
void markDeviceVerified(const std::string &user_id, const std::string &key); void markDeviceVerified(const std::string &user_id, const std::string &device);
void markDeviceUnverified(const std::string &user_id, const std::string &key); void markDeviceUnverified(const std::string &user_id, const std::string &device);
void markMasterKeyVerified(const std::string &user_id, const std::string &key);
static void removeDisplayName(const QString &room_id, const QString &user_id); static void removeDisplayName(const QString &room_id, const QString &user_id);
static void removeAvatarUrl(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 removeNotification(const QString &room_id, const QString &event_id);
void userKeysUpdate(const std::string &sync_token, void userKeysUpdate(const std::string &sync_token,
const mtx::responses::QueryKeys &keyQuery); const mtx::responses::QueryKeys &keyQuery);
void verificationStatusChanged(const std::string &userid);
private: private:
//! Save an invited room. //! Save an invited room.
@ -576,6 +576,8 @@ private:
return QString::fromStdString(event.state_key); 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 std::string &token);
void setNextBatchToken(lmdb::txn &txn, const QString &token); void setNextBatchToken(lmdb::txn &txn, const QString &token);
@ -600,6 +602,7 @@ private:
static QHash<QString, QString> AvatarUrls; static QHash<QString, QString> AvatarUrls;
OlmSessionStorage session_storage; OlmSessionStorage session_storage;
VerificationStorage verification_storage;
}; };
namespace cache { namespace cache {

View file

@ -1031,7 +1031,7 @@ TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::
try { try {
if (!mtx::crypto::verify_identity_signature( if (!mtx::crypto::verify_identity_signature(
json(dev.second), device_id, user_id)) { dev.second, device_id, user_id)) {
nhlog::crypto()->warn( nhlog::crypto()->warn(
"failed to verify identity keys: {}", "failed to verify identity keys: {}",
json(dev.second).dump(2)); json(dev.second).dump(2));

View file

@ -1,5 +1,5 @@
#include "UserProfile.h" #include "UserProfile.h"
#include "Cache.h" #include "Cache_p.h"
#include "ChatPage.h" #include "ChatPage.h"
#include "DeviceVerificationFlow.h" #include "DeviceVerificationFlow.h"
#include "Logging.h" #include "Logging.h"
@ -8,8 +8,6 @@
#include "timeline/TimelineModel.h" #include "timeline/TimelineModel.h"
#include "timeline/TimelineViewManager.h" #include "timeline/TimelineViewManager.h"
#include <iostream> // only for debugging
UserProfile::UserProfile(QString roomid, UserProfile::UserProfile(QString roomid,
QString userid, QString userid,
TimelineViewManager *manager_, TimelineViewManager *manager_,
@ -21,6 +19,31 @@ UserProfile::UserProfile(QString roomid,
, model(parent) , model(parent)
{ {
fetchDeviceList(this->userid_); 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> QHash<int, QByteArray>
@ -127,106 +150,26 @@ UserProfile::fetchDeviceList(const QString &userID)
std::vector<DeviceInfo> deviceInfo; std::vector<DeviceInfo> deviceInfo;
auto devices = other_user_keys.device_keys; auto devices = other_user_keys.device_keys;
auto device_verified = cache::verificationStatus(other_user_id); auto verificationStatus =
cache::client()->verificationStatus(other_user_id);
if (device_verified.has_value()) { isUserVerified = verificationStatus.user_verified;
// TODO: properly check cross-signing signatures here emit userStatusChanged();
isUserVerified = !device_verified->verified_master_key.empty();
}
std::optional<crypto::CrossSigningKeys> lmk, lsk, luk, mk, sk, uk;
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;
}
}
}
}
for (const auto &d : devices) { for (const auto &d : devices) {
auto device = d.second; auto device = d.second;
verification::Status verified = verification::Status verified =
verification::Status::UNVERIFIED; verification::Status::UNVERIFIED;
if (device_verified.has_value()) { if (std::find(verificationStatus.verified_devices.begin(),
if (std::find(device_verified->cross_verified.begin(), verificationStatus.verified_devices.end(),
device_verified->cross_verified.end(), device.device_id) !=
d.first) != verificationStatus.verified_devices.end() &&
device_verified->cross_verified.end()) mtx::crypto::verify_identity_signature(
device,
DeviceId(device.device_id),
UserId(other_user_id)))
verified = verification::Status::VERIFIED; 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());
//}
deviceInfo.push_back( deviceInfo.push_back(
{QString::fromStdString(d.first), {QString::fromStdString(d.first),
@ -235,14 +178,6 @@ UserProfile::fetchDeviceList(const QString &userID)
verified}); 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)); this->deviceList_.queueReset(std::move(deviceInfo));
}); });
}); });

View file

@ -74,6 +74,8 @@ public slots:
private: private:
std::vector<DeviceInfo> deviceList_; std::vector<DeviceInfo> deviceList_;
friend class UserProfile;
}; };
class UserProfile : public QObject class UserProfile : public QObject
@ -83,7 +85,7 @@ class UserProfile : public QObject
Q_PROPERTY(QString userid READ userid CONSTANT) Q_PROPERTY(QString userid READ userid CONSTANT)
Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT) Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT)
Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT) Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT)
Q_PROPERTY(bool isUserVerified READ getUserStatus CONSTANT) Q_PROPERTY(bool isUserVerified READ getUserStatus NOTIFY userStatusChanged)
public: public:
UserProfile(QString roomid, UserProfile(QString roomid,
QString userid, QString userid,
@ -105,9 +107,11 @@ public:
Q_INVOKABLE void kickUser(); Q_INVOKABLE void kickUser();
Q_INVOKABLE void startChat(); Q_INVOKABLE void startChat();
signals:
void userStatusChanged();
private: private:
QString roomid_, userid_; QString roomid_, userid_;
std::optional<std::string> cross_verified;
DeviceInfoModel deviceList_; DeviceInfoModel deviceList_;
bool isUserVerified = false; bool isUserVerified = false;
TimelineViewManager *manager; TimelineViewManager *manager;