mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 11:00:48 +03:00
Clean up verification and key cache a bit
This commit is contained in:
parent
4802c34009
commit
94690ebd4c
11 changed files with 416 additions and 335 deletions
|
@ -136,7 +136,7 @@ ApplicationWindow{
|
|||
model: profile.deviceList
|
||||
|
||||
delegate: RowLayout{
|
||||
width: parent.width
|
||||
width: devicelist.width
|
||||
spacing: 4
|
||||
|
||||
ColumnLayout{
|
||||
|
|
367
src/Cache.cpp
367
src/Cache.cpp
|
@ -91,6 +91,7 @@ Q_DECLARE_METATYPE(RoomMember)
|
|||
Q_DECLARE_METATYPE(mtx::responses::Timeline)
|
||||
Q_DECLARE_METATYPE(RoomSearchResult)
|
||||
Q_DECLARE_METATYPE(RoomInfo)
|
||||
Q_DECLARE_METATYPE(mtx::responses::QueryKeys)
|
||||
|
||||
namespace {
|
||||
std::unique_ptr<Cache> instance_ = nullptr;
|
||||
|
@ -155,26 +156,7 @@ Cache::Cache(const QString &userId, QObject *parent)
|
|||
, localUserId_{userId}
|
||||
{
|
||||
setup();
|
||||
connect(
|
||||
this,
|
||||
&Cache::updateUserCacheFlag,
|
||||
this,
|
||||
[this](const std::string &user_id) {
|
||||
std::optional<UserCache> cache_ = getUserCache(user_id);
|
||||
if (cache_.has_value()) {
|
||||
cache_.value().isUpdated = false;
|
||||
setUserCache(user_id, cache_.value());
|
||||
} else {
|
||||
setUserCache(user_id, UserCache{});
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
connect(
|
||||
this,
|
||||
&Cache::deleteLeftUsers,
|
||||
this,
|
||||
[this](const std::string &user_id) { deleteUserCache(user_id); },
|
||||
Qt::QueuedConnection);
|
||||
connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1017,6 +999,8 @@ Cache::saveState(const mtx::responses::Sync &res)
|
|||
using namespace mtx::events;
|
||||
auto user_id = this->localUserId_.toStdString();
|
||||
|
||||
auto currentBatchToken = nextBatchToken();
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
|
||||
setNextBatchToken(txn, res.next_batch);
|
||||
|
@ -1034,6 +1018,8 @@ Cache::saveState(const mtx::responses::Sync &res)
|
|||
ev);
|
||||
}
|
||||
|
||||
auto userKeyCacheDb = getUserKeysDb(txn);
|
||||
|
||||
// Save joined rooms
|
||||
for (const auto &room : res.rooms.join) {
|
||||
auto statesdb = getStatesDb(txn, room.first);
|
||||
|
@ -1107,7 +1093,8 @@ Cache::saveState(const mtx::responses::Sync &res)
|
|||
|
||||
savePresence(txn, res.presence);
|
||||
|
||||
updateUserCache(res.device_lists);
|
||||
markUserKeysOutOfDate(txn, userKeyCacheDb, res.device_lists.changed, currentBatchToken);
|
||||
deleteUserKeys(txn, userKeyCacheDb, res.device_lists.left);
|
||||
|
||||
removeLeftRooms(txn, res.rooms.leave);
|
||||
|
||||
|
@ -3098,126 +3085,246 @@ Cache::statusMessage(const std::string &user_id)
|
|||
}
|
||||
|
||||
void
|
||||
to_json(json &j, const UserCache &info)
|
||||
to_json(json &j, const UserKeyCache &info)
|
||||
{
|
||||
j["keys"] = info.keys;
|
||||
j["isUpdated"] = info.isUpdated;
|
||||
j["device_keys"] = info.device_keys;
|
||||
j["master_keys"] = info.master_keys;
|
||||
j["user_signing_keys"] = info.user_signing_keys;
|
||||
j["self_signing_keys"] = info.self_signing_keys;
|
||||
j["updated_at"] = info.updated_at;
|
||||
j["last_changed"] = info.last_changed;
|
||||
}
|
||||
|
||||
void
|
||||
from_json(const json &j, UserCache &info)
|
||||
from_json(const json &j, UserKeyCache &info)
|
||||
{
|
||||
info.keys = j.at("keys").get<mtx::responses::QueryKeys>();
|
||||
info.isUpdated = j.at("isUpdated").get<bool>();
|
||||
info.device_keys = j.value("device_keys", std::map<std::string, mtx::crypto::DeviceKeys>{});
|
||||
info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{});
|
||||
info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{});
|
||||
info.self_signing_keys = j.value("self_signing_keys", mtx::crypto::CrossSigningKeys{});
|
||||
info.updated_at = j.value("updated_at", "");
|
||||
info.last_changed = j.value("last_changed", "");
|
||||
}
|
||||
|
||||
std::optional<UserCache>
|
||||
Cache::getUserCache(const std::string &user_id)
|
||||
std::optional<UserKeyCache>
|
||||
Cache::userKeys(const std::string &user_id)
|
||||
{
|
||||
lmdb::val verifiedVal;
|
||||
lmdb::val keys;
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
auto db = getUserCacheDb(txn);
|
||||
auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal);
|
||||
try {
|
||||
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||
auto db = getUserKeysDb(txn);
|
||||
auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), keys);
|
||||
|
||||
txn.commit();
|
||||
|
||||
UserCache verified_state;
|
||||
if (res) {
|
||||
verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size()));
|
||||
return verified_state;
|
||||
} else {
|
||||
if (res) {
|
||||
return json::parse(std::string_view(keys.data(), keys.size()))
|
||||
.get<UserKeyCache>();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
} catch (std::exception &) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
//! be careful when using make sure is_user_verified is not changed
|
||||
int
|
||||
Cache::setUserCache(const std::string &user_id, const UserCache &body)
|
||||
void
|
||||
Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery)
|
||||
{
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
auto db = getUserCacheDb(txn);
|
||||
auto db = getUserKeysDb(txn);
|
||||
|
||||
auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump()));
|
||||
std::map<std::string, UserKeyCache> updates;
|
||||
|
||||
txn.commit();
|
||||
for (const auto &[user, keys] : keyQuery.device_keys)
|
||||
updates[user].device_keys = keys;
|
||||
for (const auto &[user, keys] : keyQuery.master_keys)
|
||||
updates[user].master_keys = keys;
|
||||
for (const auto &[user, keys] : keyQuery.user_signing_keys)
|
||||
updates[user].user_signing_keys = keys;
|
||||
for (const auto &[user, keys] : keyQuery.self_signing_keys)
|
||||
updates[user].self_signing_keys = keys;
|
||||
|
||||
return res;
|
||||
}
|
||||
for (auto &[user, update] : updates) {
|
||||
lmdb::val oldKeys;
|
||||
auto res = lmdb::dbi_get(txn, db, lmdb::val(user), oldKeys);
|
||||
|
||||
void
|
||||
Cache::updateUserCache(const mtx::responses::DeviceLists body)
|
||||
{
|
||||
for (std::string user_id : body.changed) {
|
||||
emit updateUserCacheFlag(user_id);
|
||||
if (res) {
|
||||
auto last_changed =
|
||||
json::parse(std::string_view(oldKeys.data(), oldKeys.size()))
|
||||
.get<UserKeyCache>()
|
||||
.last_changed;
|
||||
// skip if we are tracking this and expect it to be up to date with the last
|
||||
// sync token
|
||||
if (!last_changed.empty() && last_changed != sync_token)
|
||||
continue;
|
||||
}
|
||||
lmdb::dbi_put(txn, db, lmdb::val(user), lmdb::val(json(update).dump()));
|
||||
}
|
||||
|
||||
for (std::string user_id : body.left) {
|
||||
emit deleteLeftUsers(user_id);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
Cache::deleteUserCache(const std::string &user_id)
|
||||
{
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
auto db = getUserCacheDb(txn);
|
||||
auto res = lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr);
|
||||
|
||||
txn.commit();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
to_json(json &j, const DeviceVerifiedCache &info)
|
||||
Cache::deleteUserKeys(lmdb::txn &txn, lmdb::dbi &db, const std::vector<std::string> &user_ids)
|
||||
{
|
||||
j["is_user_verified"] = info.is_user_verified;
|
||||
j["cross_verified"] = info.cross_verified;
|
||||
j["device_verified"] = info.device_verified;
|
||||
j["device_blocked"] = info.device_blocked;
|
||||
for (const auto &user_id : user_ids)
|
||||
lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
from_json(const json &j, DeviceVerifiedCache &info)
|
||||
Cache::markUserKeysOutOfDate(lmdb::txn &txn,
|
||||
lmdb::dbi &db,
|
||||
const std::vector<std::string> &user_ids,
|
||||
const std::string &sync_token)
|
||||
{
|
||||
info.is_user_verified = j.at("is_user_verified");
|
||||
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>>();
|
||||
mtx::requests::QueryKeys query;
|
||||
query.token = sync_token;
|
||||
|
||||
for (const auto &user : user_ids) {
|
||||
lmdb::val oldKeys;
|
||||
auto res = lmdb::dbi_get(txn, db, lmdb::val(user), oldKeys);
|
||||
|
||||
if (!res)
|
||||
continue;
|
||||
|
||||
auto cacheEntry =
|
||||
json::parse(std::string_view(oldKeys.data(), oldKeys.size())).get<UserKeyCache>();
|
||||
cacheEntry.last_changed = sync_token;
|
||||
lmdb::dbi_put(txn, db, lmdb::val(user), lmdb::val(json(cacheEntry).dump()));
|
||||
|
||||
query.device_keys[user] = {};
|
||||
}
|
||||
|
||||
if (!query.device_keys.empty())
|
||||
http::client()->query_keys(query,
|
||||
[this, sync_token](const mtx::responses::QueryKeys &keys,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn(
|
||||
"failed to query device keys: {} {}",
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
emit userKeysUpdate(sync_token, keys);
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<DeviceVerifiedCache>
|
||||
Cache::getVerifiedCache(const std::string &user_id)
|
||||
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;
|
||||
}
|
||||
|
||||
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>>();
|
||||
}
|
||||
|
||||
std::optional<VerificationCache>
|
||||
Cache::verificationStatus(const std::string &user_id)
|
||||
{
|
||||
lmdb::val verifiedVal;
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
auto db = getDeviceVerifiedDb(txn);
|
||||
auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal);
|
||||
auto db = getVerificationDb(txn);
|
||||
|
||||
txn.commit();
|
||||
|
||||
DeviceVerifiedCache verified_state;
|
||||
if (res) {
|
||||
verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size()));
|
||||
return verified_state;
|
||||
} else {
|
||||
try {
|
||||
VerificationCache verified_state;
|
||||
auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal);
|
||||
if (res) {
|
||||
verified_state =
|
||||
json::parse(std::string_view(verifiedVal.data(), verifiedVal.size()));
|
||||
return verified_state;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
} catch (std::exception &) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
Cache::setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body)
|
||||
void
|
||||
Cache::markDeviceVerified(const std::string &user_id, const std::string &key)
|
||||
{
|
||||
lmdb::val val;
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
auto db = getDeviceVerifiedDb(txn);
|
||||
auto db = getVerificationDb(txn);
|
||||
|
||||
auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump()));
|
||||
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()));
|
||||
}
|
||||
|
||||
txn.commit();
|
||||
for (const auto &device : verified_state.device_verified)
|
||||
if (device == key)
|
||||
return;
|
||||
|
||||
return res;
|
||||
verified_state.device_verified.push_back(key);
|
||||
lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump()));
|
||||
txn.commit();
|
||||
} catch (std::exception &) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Cache::markDeviceUnverified(const std::string &user_id, const std::string &key)
|
||||
{
|
||||
lmdb::val val;
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
auto db = getVerificationDb(txn);
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
verified_state.device_verified.erase(
|
||||
std::remove(verified_state.device_verified.begin(),
|
||||
verified_state.device_verified.end(),
|
||||
key),
|
||||
verified_state.device_verified.end());
|
||||
|
||||
lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump()));
|
||||
txn.commit();
|
||||
} catch (std::exception &) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Cache::markMasterKeyVerified(const std::string &user_id, const std::string &key)
|
||||
{
|
||||
lmdb::val val;
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
auto db = getVerificationDb(txn);
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
verified_state.verified_master_key = key;
|
||||
lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(verified_state).dump()));
|
||||
txn.commit();
|
||||
} catch (std::exception &) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -3401,41 +3508,6 @@ statusMessage(const std::string &user_id)
|
|||
{
|
||||
return instance_->statusMessage(user_id);
|
||||
}
|
||||
std::optional<UserCache>
|
||||
getUserCache(const std::string &user_id)
|
||||
{
|
||||
return instance_->getUserCache(user_id);
|
||||
}
|
||||
|
||||
void
|
||||
updateUserCache(const mtx::responses::DeviceLists body)
|
||||
{
|
||||
instance_->updateUserCache(body);
|
||||
}
|
||||
|
||||
int
|
||||
setUserCache(const std::string &user_id, const UserCache &body)
|
||||
{
|
||||
return instance_->setUserCache(user_id, body);
|
||||
}
|
||||
|
||||
int
|
||||
deleteUserCache(const std::string &user_id)
|
||||
{
|
||||
return instance_->deleteUserCache(user_id);
|
||||
}
|
||||
|
||||
std::optional<DeviceVerifiedCache>
|
||||
getVerifiedCache(const std::string &user_id)
|
||||
{
|
||||
return instance_->getVerifiedCache(user_id);
|
||||
}
|
||||
|
||||
int
|
||||
setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body)
|
||||
{
|
||||
return instance_->setVerifiedCache(user_id, body);
|
||||
}
|
||||
|
||||
//! Load saved data for the display names & avatars.
|
||||
void
|
||||
|
@ -3444,6 +3516,43 @@ populateMembers()
|
|||
instance_->populateMembers();
|
||||
}
|
||||
|
||||
// user cache stores user keys
|
||||
std::optional<UserKeyCache>
|
||||
userKeys(const std::string &user_id)
|
||||
{
|
||||
return instance_->userKeys(user_id);
|
||||
}
|
||||
void
|
||||
updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery)
|
||||
{
|
||||
instance_->updateUserKeys(sync_token, keyQuery);
|
||||
}
|
||||
|
||||
// device & user verification cache
|
||||
std::optional<VerificationCache>
|
||||
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)
|
||||
{
|
||||
instance_->markDeviceUnverified(user_id, key);
|
||||
}
|
||||
|
||||
void
|
||||
markMasterKeyVerified(const std::string &user_id, const std::string &key)
|
||||
{
|
||||
instance_->markMasterKeyVerified(user_id, key);
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
joinedRooms()
|
||||
{
|
||||
|
|
30
src/Cache.h
30
src/Cache.h
|
@ -60,25 +60,21 @@ presenceState(const std::string &user_id);
|
|||
std::string
|
||||
statusMessage(const std::string &user_id);
|
||||
|
||||
//! user Cache
|
||||
std::optional<UserCache>
|
||||
getUserCache(const std::string &user_id);
|
||||
|
||||
// user cache stores user keys
|
||||
std::optional<UserKeyCache>
|
||||
userKeys(const std::string &user_id);
|
||||
void
|
||||
updateUserCache(const mtx::responses::DeviceLists body);
|
||||
updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery);
|
||||
|
||||
int
|
||||
setUserCache(const std::string &user_id, const UserCache &body);
|
||||
|
||||
int
|
||||
deleteUserCache(const std::string &user_id);
|
||||
|
||||
//! verified Cache
|
||||
std::optional<DeviceVerifiedCache>
|
||||
getVerifiedCache(const std::string &user_id);
|
||||
|
||||
int
|
||||
setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body);
|
||||
// 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);
|
||||
|
||||
//! Load saved data for the display names & avatars.
|
||||
void
|
||||
|
|
|
@ -67,52 +67,38 @@ struct OlmSessionStorage
|
|||
};
|
||||
|
||||
// this will store the keys of the user with whom a encrypted room is shared with
|
||||
struct UserCache
|
||||
struct UserKeyCache
|
||||
{
|
||||
//! map of public key key_ids and their public_key
|
||||
mtx::responses::QueryKeys keys;
|
||||
//! if the current cache is updated or not
|
||||
bool isUpdated = false;
|
||||
|
||||
UserCache(mtx::responses::QueryKeys res, bool isUpdated_ = false)
|
||||
: keys(res)
|
||||
, isUpdated(isUpdated_)
|
||||
{}
|
||||
UserCache() {}
|
||||
//! Device id to device keys
|
||||
std::map<std::string, mtx::crypto::DeviceKeys> device_keys;
|
||||
//! corss signing keys
|
||||
mtx::crypto::CrossSigningKeys master_keys, user_signing_keys, self_signing_keys;
|
||||
//! Sync token when nheko last fetched the keys
|
||||
std::string updated_at;
|
||||
//! Sync token when the keys last changed. updated != last_changed means they are outdated.
|
||||
std::string last_changed;
|
||||
};
|
||||
|
||||
void
|
||||
to_json(nlohmann::json &j, const UserCache &info);
|
||||
to_json(nlohmann::json &j, const UserKeyCache &info);
|
||||
void
|
||||
from_json(const nlohmann::json &j, UserCache &info);
|
||||
from_json(const nlohmann::json &j, UserKeyCache &info);
|
||||
|
||||
// the reason these are stored in a seperate cache rather than storing it in the user cache is
|
||||
// UserCache stores only keys of users with which encrypted room is shared
|
||||
struct DeviceVerifiedCache
|
||||
// UserKeyCache stores only keys of users with which encrypted room is shared
|
||||
struct VerificationCache
|
||||
{
|
||||
//! list of verified device_ids with device-verification
|
||||
std::vector<std::string> device_verified;
|
||||
//! list of verified device_ids with cross-signing
|
||||
//! 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;
|
||||
//! this stores if the user is verified (with cross-signing)
|
||||
bool is_user_verified = false;
|
||||
|
||||
DeviceVerifiedCache(std::vector<std::string> device_verified_,
|
||||
std::vector<std::string> cross_verified_,
|
||||
std::vector<std::string> device_blocked_,
|
||||
bool is_user_verified_ = false)
|
||||
: device_verified(device_verified_)
|
||||
, cross_verified(cross_verified_)
|
||||
, device_blocked(device_blocked_)
|
||||
, is_user_verified(is_user_verified_)
|
||||
{}
|
||||
|
||||
DeviceVerifiedCache() {}
|
||||
//! The verified master key.
|
||||
std::string verified_master_key;
|
||||
};
|
||||
|
||||
void
|
||||
to_json(nlohmann::json &j, const DeviceVerifiedCache &info);
|
||||
to_json(nlohmann::json &j, const VerificationCache &info);
|
||||
void
|
||||
from_json(const nlohmann::json &j, DeviceVerifiedCache &info);
|
||||
from_json(const nlohmann::json &j, VerificationCache &info);
|
||||
|
|
|
@ -55,14 +55,22 @@ public:
|
|||
std::string statusMessage(const std::string &user_id);
|
||||
|
||||
// user cache stores user keys
|
||||
std::optional<UserCache> getUserCache(const std::string &user_id);
|
||||
void updateUserCache(const mtx::responses::DeviceLists body);
|
||||
int setUserCache(const std::string &user_id, const UserCache &body);
|
||||
int deleteUserCache(const std::string &user_id);
|
||||
std::optional<UserKeyCache> userKeys(const std::string &user_id);
|
||||
void updateUserKeys(const std::string &sync_token,
|
||||
const mtx::responses::QueryKeys &keyQuery);
|
||||
void markUserKeysOutOfDate(lmdb::txn &txn,
|
||||
lmdb::dbi &db,
|
||||
const std::vector<std::string> &user_ids,
|
||||
const std::string &sync_token);
|
||||
void deleteUserKeys(lmdb::txn &txn,
|
||||
lmdb::dbi &db,
|
||||
const std::vector<std::string> &user_ids);
|
||||
|
||||
// device verified cache
|
||||
std::optional<DeviceVerifiedCache> getVerifiedCache(const std::string &user_id);
|
||||
int setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body);
|
||||
// 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);
|
||||
|
||||
static void removeDisplayName(const QString &room_id, const QString &user_id);
|
||||
static void removeAvatarUrl(const QString &room_id, const QString &user_id);
|
||||
|
@ -272,8 +280,8 @@ signals:
|
|||
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|
||||
void roomReadStatus(const std::map<QString, bool> &status);
|
||||
void removeNotification(const QString &room_id, const QString &event_id);
|
||||
void updateUserCacheFlag(const std::string &user_id);
|
||||
void deleteLeftUsers(const std::string &user_id);
|
||||
void userKeysUpdate(const std::string &sync_token,
|
||||
const mtx::responses::QueryKeys &keyQuery);
|
||||
|
||||
private:
|
||||
//! Save an invited room.
|
||||
|
@ -539,12 +547,12 @@ private:
|
|||
return lmdb::dbi::open(txn, "presence", MDB_CREATE);
|
||||
}
|
||||
|
||||
lmdb::dbi getUserCacheDb(lmdb::txn &txn)
|
||||
lmdb::dbi getUserKeysDb(lmdb::txn &txn)
|
||||
{
|
||||
return lmdb::dbi::open(txn, "user_cache", MDB_CREATE);
|
||||
return lmdb::dbi::open(txn, "user_key", MDB_CREATE);
|
||||
}
|
||||
|
||||
lmdb::dbi getDeviceVerifiedDb(lmdb::txn &txn)
|
||||
lmdb::dbi getVerificationDb(lmdb::txn &txn)
|
||||
{
|
||||
return lmdb::dbi::open(txn, "verified", MDB_CREATE);
|
||||
}
|
||||
|
|
|
@ -1466,35 +1466,43 @@ ChatPage::initiateLogout()
|
|||
}
|
||||
|
||||
void
|
||||
ChatPage::query_keys(
|
||||
const mtx::requests::QueryKeys &req,
|
||||
std::function<void(const mtx::responses::QueryKeys &, mtx::http::RequestErr)> cb)
|
||||
ChatPage::query_keys(const std::string &user_id,
|
||||
std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb)
|
||||
{
|
||||
std::string user_id = req.device_keys.begin()->first;
|
||||
auto cache_ = cache::getUserCache(user_id);
|
||||
auto cache_ = cache::userKeys(user_id);
|
||||
|
||||
if (cache_.has_value()) {
|
||||
if (cache_.value().isUpdated) {
|
||||
cb(cache_.value().keys, {});
|
||||
} else {
|
||||
http::client()->query_keys(
|
||||
req,
|
||||
[cb, user_id](const mtx::responses::QueryKeys &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to query device keys: {},{}",
|
||||
err->matrix_error.errcode,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
cache::setUserCache(std::move(user_id),
|
||||
std::move(UserCache{res, true}));
|
||||
cb(res, err);
|
||||
});
|
||||
if (!cache_->updated_at.empty() && cache_->updated_at == cache_->last_changed) {
|
||||
cb(cache_.value(), {});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
http::client()->query_keys(req, cb);
|
||||
}
|
||||
|
||||
mtx::requests::QueryKeys req;
|
||||
req.device_keys[user_id] = {};
|
||||
|
||||
std::string last_changed;
|
||||
if (cache_)
|
||||
last_changed = cache_->last_changed;
|
||||
req.token = last_changed;
|
||||
|
||||
http::client()->query_keys(req,
|
||||
[cb, user_id, last_changed](const mtx::responses::QueryKeys &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn(
|
||||
"failed to query device keys: {},{}",
|
||||
err->matrix_error.errcode,
|
||||
static_cast<int>(err->status_code));
|
||||
cb({}, err);
|
||||
return;
|
||||
}
|
||||
|
||||
cache::updateUserKeys(last_changed, res);
|
||||
|
||||
auto keys = cache::userKeys(user_id);
|
||||
cb(keys.value_or(UserKeyCache{}), err);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
#include "CacheCryptoStructs.h"
|
||||
#include "CacheStructs.h"
|
||||
#include "CallManager.h"
|
||||
#include "CommunitiesList.h"
|
||||
|
@ -89,9 +90,8 @@ public:
|
|||
//! Show the room/group list (if it was visible).
|
||||
void showSideBars();
|
||||
void initiateLogout();
|
||||
void query_keys(
|
||||
const mtx::requests::QueryKeys &req,
|
||||
std::function<void(const mtx::responses::QueryKeys &, mtx::http::RequestErr)> cb);
|
||||
void query_keys(const std::string &req,
|
||||
std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb);
|
||||
void focusMessageInput();
|
||||
|
||||
QString status() const;
|
||||
|
|
|
@ -328,22 +328,14 @@ DeviceVerificationFlow::setTransactionId(QString transaction_id_)
|
|||
void
|
||||
DeviceVerificationFlow::setUserId(QString userID)
|
||||
{
|
||||
this->userId = userID;
|
||||
this->toClient = mtx::identifiers::parse<mtx::identifiers::User>(userID.toStdString());
|
||||
auto user_cache = cache::getUserCache(userID.toStdString());
|
||||
this->userId = userID;
|
||||
this->toClient = mtx::identifiers::parse<mtx::identifiers::User>(userID.toStdString());
|
||||
|
||||
if (user_cache.has_value()) {
|
||||
this->callback_fn(user_cache->keys, {}, userID.toStdString());
|
||||
} else {
|
||||
mtx::requests::QueryKeys req;
|
||||
req.device_keys[userID.toStdString()] = {};
|
||||
http::client()->query_keys(
|
||||
req,
|
||||
[user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res,
|
||||
mtx::http::RequestErr err) {
|
||||
this->callback_fn(res, err, user_id);
|
||||
});
|
||||
}
|
||||
auto user_id = userID.toStdString();
|
||||
ChatPage::instance()->query_keys(
|
||||
user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) {
|
||||
this->callback_fn(res, err, user_id);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -622,30 +614,52 @@ DeviceVerificationFlow::sendVerificationKey()
|
|||
(model_)->sendMessageEvent(req, mtx::events::EventType::KeyVerificationKey);
|
||||
}
|
||||
}
|
||||
|
||||
mtx::events::msg::KeyVerificationMac
|
||||
key_verification_mac(mtx::crypto::SAS *sas,
|
||||
mtx::identifiers::User sender,
|
||||
const std::string &senderDevice,
|
||||
mtx::identifiers::User receiver,
|
||||
const std::string &receiverDevice,
|
||||
const std::string &transactionId,
|
||||
std::map<std::string, std::string> keys)
|
||||
{
|
||||
mtx::events::msg::KeyVerificationMac req;
|
||||
|
||||
std::string info = "MATRIX_KEY_VERIFICATION_MAC" + sender.to_string() + senderDevice +
|
||||
receiver.to_string() + receiverDevice + transactionId;
|
||||
|
||||
std::string key_list;
|
||||
bool first = true;
|
||||
for (const auto &[key_id, key] : keys) {
|
||||
req.mac[key_id] = sas->calculate_mac(key, info + key_id);
|
||||
|
||||
if (!first)
|
||||
key_list += ",";
|
||||
key_list += key_id;
|
||||
first = false;
|
||||
}
|
||||
|
||||
req.keys = sas->calculate_mac(key_list, info + "KEY_IDS");
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
//! sends the mac of the keys
|
||||
void
|
||||
DeviceVerificationFlow::sendVerificationMac()
|
||||
{
|
||||
mtx::events::msg::KeyVerificationMac req;
|
||||
std::map<std::string, std::string> key_list;
|
||||
key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519;
|
||||
|
||||
std::string info = "MATRIX_KEY_VERIFICATION_MAC" + http::client()->user_id().to_string() +
|
||||
http::client()->device_id() + this->toClient.to_string() +
|
||||
this->deviceId.toStdString() + this->transaction_id;
|
||||
|
||||
//! this vector stores the type of the key and the key
|
||||
std::vector<std::pair<std::string, std::string>> key_list;
|
||||
key_list.push_back(make_pair("ed25519", olm::client()->identity_keys().ed25519));
|
||||
std::sort(key_list.begin(), key_list.end());
|
||||
for (auto x : key_list) {
|
||||
req.mac.insert(
|
||||
std::make_pair(x.first + ":" + http::client()->device_id(),
|
||||
this->sas->calculate_mac(
|
||||
x.second, info + x.first + ":" + http::client()->device_id())));
|
||||
req.keys += x.first + ":" + http::client()->device_id() + ",";
|
||||
}
|
||||
|
||||
req.keys =
|
||||
this->sas->calculate_mac(req.keys.substr(0, req.keys.size() - 1), info + "KEY_IDS");
|
||||
mtx::events::msg::KeyVerificationMac req =
|
||||
key_verification_mac(sas.get(),
|
||||
http::client()->user_id(),
|
||||
http::client()->device_id(),
|
||||
this->toClient,
|
||||
this->deviceId.toStdString(),
|
||||
this->transaction_id,
|
||||
key_list);
|
||||
|
||||
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
|
||||
mtx::requests::ToDeviceMessages<mtx::events::msg::KeyVerificationMac> body;
|
||||
|
@ -673,27 +687,16 @@ DeviceVerificationFlow::sendVerificationMac()
|
|||
void
|
||||
DeviceVerificationFlow::acceptDevice()
|
||||
{
|
||||
auto verified_cache = cache::getVerifiedCache(this->userId.toStdString());
|
||||
if (verified_cache.has_value()) {
|
||||
verified_cache->device_verified.push_back(this->deviceId.toStdString());
|
||||
verified_cache->device_blocked.erase(
|
||||
std::remove(verified_cache->device_blocked.begin(),
|
||||
verified_cache->device_blocked.end(),
|
||||
this->deviceId.toStdString()),
|
||||
verified_cache->device_blocked.end());
|
||||
} else {
|
||||
cache::setVerifiedCache(
|
||||
this->userId.toStdString(),
|
||||
DeviceVerifiedCache{{this->deviceId.toStdString()}, {}, {}});
|
||||
}
|
||||
cache::markDeviceVerified(this->userId.toStdString(), this->deviceId.toStdString());
|
||||
|
||||
emit deviceVerified();
|
||||
emit refreshProfile();
|
||||
this->deleteLater();
|
||||
}
|
||||
|
||||
//! callback function to keep track of devices
|
||||
void
|
||||
DeviceVerificationFlow::callback_fn(const mtx::responses::QueryKeys &res,
|
||||
DeviceVerificationFlow::callback_fn(const UserKeyCache &res,
|
||||
mtx::http::RequestErr err,
|
||||
std::string user_id)
|
||||
{
|
||||
|
@ -704,35 +707,22 @@ DeviceVerificationFlow::callback_fn(const mtx::responses::QueryKeys &res,
|
|||
return;
|
||||
}
|
||||
|
||||
if (res.device_keys.empty() || (res.device_keys.find(user_id) == res.device_keys.end())) {
|
||||
if (res.device_keys.empty() ||
|
||||
(res.device_keys.find(deviceId.toStdString()) == res.device_keys.end())) {
|
||||
nhlog::net()->warn("no devices retrieved {}", user_id);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto x : res.device_keys) {
|
||||
for (auto y : x.second) {
|
||||
auto z = y.second;
|
||||
if (z.user_id == user_id && z.device_id == this->deviceId.toStdString()) {
|
||||
for (auto a : z.keys) {
|
||||
// TODO: Verify Signatures
|
||||
this->device_keys[a.first] = a.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &[algorithm, key] : res.device_keys.at(deviceId.toStdString()).keys) {
|
||||
// TODO: Verify Signatures
|
||||
this->device_keys[algorithm] = key;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DeviceVerificationFlow::unverify()
|
||||
{
|
||||
auto verified_cache = cache::getVerifiedCache(this->userId.toStdString());
|
||||
if (verified_cache.has_value()) {
|
||||
auto it = std::remove(verified_cache->device_verified.begin(),
|
||||
verified_cache->device_verified.end(),
|
||||
this->deviceId.toStdString());
|
||||
verified_cache->device_verified.erase(it);
|
||||
cache::setVerifiedCache(this->userId.toStdString(), verified_cache.value());
|
||||
}
|
||||
cache::markDeviceUnverified(this->userId.toStdString(), this->deviceId.toStdString());
|
||||
|
||||
emit refreshProfile();
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "Olm.h"
|
||||
|
||||
#include "MatrixClient.h"
|
||||
#include "mtx/responses/crypto.hpp"
|
||||
#include <QObject>
|
||||
|
||||
#include <mtx/responses/crypto.hpp>
|
||||
|
||||
#include "CacheCryptoStructs.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "Olm.h"
|
||||
|
||||
class QTimer;
|
||||
|
||||
using sas_ptr = std::unique_ptr<mtx::crypto::SAS>;
|
||||
|
@ -71,9 +73,7 @@ public:
|
|||
void setSender(bool sender_);
|
||||
void setEventId(std::string event_id);
|
||||
|
||||
void callback_fn(const mtx::responses::QueryKeys &res,
|
||||
mtx::http::RequestErr err,
|
||||
std::string user_id);
|
||||
void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id);
|
||||
|
||||
nlohmann::json canonical_json;
|
||||
|
||||
|
|
Binary file not shown.
|
@ -89,12 +89,10 @@ UserProfile::fetchDeviceList(const QString &userID)
|
|||
{
|
||||
auto localUser = utils::localUser();
|
||||
|
||||
mtx::requests::QueryKeys req;
|
||||
req.device_keys[userID.toStdString()] = {};
|
||||
ChatPage::instance()->query_keys(
|
||||
req,
|
||||
[user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res,
|
||||
mtx::http::RequestErr err) {
|
||||
userID.toStdString(),
|
||||
[other_user_id = userID.toStdString(), this](const UserKeyCache &other_user_keys,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to query device keys: {},{}",
|
||||
err->matrix_error.errcode,
|
||||
|
@ -102,20 +100,11 @@ UserProfile::fetchDeviceList(const QString &userID)
|
|||
return;
|
||||
}
|
||||
|
||||
if (res.device_keys.empty() ||
|
||||
(res.device_keys.find(user_id) == res.device_keys.end())) {
|
||||
nhlog::net()->warn("no devices retrieved {}", user_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Finding if the User is Verified or not based on the Signatures
|
||||
mtx::requests::QueryKeys req;
|
||||
req.device_keys[utils::localUser().toStdString()] = {};
|
||||
|
||||
ChatPage::instance()->query_keys(
|
||||
req,
|
||||
[user_id, other_res = res, this](const mtx::responses::QueryKeys &res,
|
||||
mtx::http::RequestErr err) {
|
||||
utils::localUser().toStdString(),
|
||||
[other_user_id, other_user_keys, this](const UserKeyCache &res,
|
||||
mtx::http::RequestErr err) {
|
||||
using namespace mtx;
|
||||
std::string local_user_id = utils::localUser().toStdString();
|
||||
|
||||
|
@ -126,34 +115,28 @@ UserProfile::fetchDeviceList(const QString &userID)
|
|||
return;
|
||||
}
|
||||
|
||||
if (res.device_keys.empty() ||
|
||||
(res.device_keys.find(local_user_id) == res.device_keys.end())) {
|
||||
nhlog::net()->warn("no devices retrieved {}", user_id);
|
||||
if (res.device_keys.empty()) {
|
||||
nhlog::net()->warn("no devices retrieved {}", local_user_id);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<DeviceInfo> deviceInfo;
|
||||
auto devices = other_res.device_keys.at(user_id);
|
||||
auto device_verified = cache::getVerifiedCache(user_id);
|
||||
auto devices = other_user_keys.device_keys;
|
||||
auto device_verified = cache::verificationStatus(other_user_id);
|
||||
|
||||
if (device_verified.has_value()) {
|
||||
isUserVerified = device_verified.value().is_user_verified;
|
||||
// TODO: properly check cross-signing signatures here
|
||||
isUserVerified = !device_verified->verified_master_key.empty();
|
||||
}
|
||||
|
||||
std::optional<crypto::CrossSigningKeys> lmk, lsk, luk, mk, sk, uk;
|
||||
|
||||
if (!res.master_keys.empty())
|
||||
lmk = res.master_keys.at(local_user_id);
|
||||
if (!res.user_signing_keys.empty())
|
||||
luk = res.user_signing_keys.at(local_user_id);
|
||||
if (!res.self_signing_keys.empty())
|
||||
lsk = res.self_signing_keys.at(local_user_id);
|
||||
if (!other_res.master_keys.empty())
|
||||
mk = other_res.master_keys.at(user_id);
|
||||
if (!other_res.user_signing_keys.empty())
|
||||
uk = other_res.user_signing_keys.at(user_id);
|
||||
if (!other_res.self_signing_keys.empty())
|
||||
sk = other_res.self_signing_keys.at(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()) {
|
||||
|
@ -202,7 +185,7 @@ UserProfile::fetchDeviceList(const QString &userID)
|
|||
device_verified->device_blocked.end())
|
||||
verified = verification::Status::BLOCKED;
|
||||
} else if (isUserVerified) {
|
||||
device_verified = DeviceVerifiedCache{};
|
||||
device_verified = VerificationCache{};
|
||||
}
|
||||
|
||||
// won't check for already verified devices
|
||||
|
@ -211,7 +194,7 @@ UserProfile::fetchDeviceList(const QString &userID)
|
|||
if ((sk.has_value()) && (!device.signatures.empty())) {
|
||||
for (auto sign_key : sk.value().keys) {
|
||||
auto signs =
|
||||
device.signatures.at(user_id);
|
||||
device.signatures.at(other_user_id);
|
||||
try {
|
||||
if (olm::client()
|
||||
->ed25519_verify_sig(
|
||||
|
@ -232,12 +215,13 @@ UserProfile::fetchDeviceList(const QString &userID)
|
|||
}
|
||||
}
|
||||
|
||||
if (device_verified.has_value()) {
|
||||
device_verified.value().is_user_verified =
|
||||
isUserVerified;
|
||||
cache::setVerifiedCache(user_id,
|
||||
device_verified.value());
|
||||
}
|
||||
// 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(
|
||||
{QString::fromStdString(d.first),
|
||||
|
|
Loading…
Reference in a new issue