summary refs log tree commit diff
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2020-10-02 01:14:42 +0200
committerNicolas Werner <nicolas.werner@hotmail.de>2020-10-02 01:14:42 +0200
commit94690ebd4c22c8928b92c4f1723d1c6c5b798698 (patch)
tree2aceea98d5834692805c93fb818780f2b9c52418
parentMerge remote-tracking branch 'origin/master' into cross-signing (diff)
downloadnheko-94690ebd4c22c8928b92c4f1723d1c6c5b798698.tar.xz
Clean up verification and key cache a bit
-rw-r--r--resources/qml/UserProfile.qml2
-rw-r--r--src/Cache.cpp331
-rw-r--r--src/Cache.h30
-rw-r--r--src/CacheCryptoStructs.h50
-rw-r--r--src/Cache_p.h34
-rw-r--r--src/ChatPage.cpp56
-rw-r--r--src/ChatPage.h6
-rw-r--r--src/DeviceVerificationFlow.cpp126
-rw-r--r--src/DeviceVerificationFlow.h12
-rw-r--r--src/timeline/.TimelineModel.cpp.swnbin237568 -> 0 bytes
-rw-r--r--src/ui/UserProfile.cpp70
11 files changed, 399 insertions, 318 deletions
diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index 1ca9dcc8..dc6bc165 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -136,7 +136,7 @@ ApplicationWindow{
 			model: profile.deviceList
 
 			delegate: RowLayout{
-				width: parent.width
+				width: devicelist.width
 				spacing: 4
 
 				ColumnLayout{
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 667506c5..8b47c357 100644
--- a/src/Cache.cpp
+++ b/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);
-
-        txn.commit();
+        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);
 
-        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);
+
+                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()));
+        }
+
+        txn.commit();
 }
 
 void
-Cache::updateUserCache(const mtx::responses::DeviceLists body)
+Cache::deleteUserKeys(lmdb::txn &txn, lmdb::dbi &db, const std::vector<std::string> &user_ids)
 {
-        for (std::string user_id : body.changed) {
-                emit updateUserCacheFlag(user_id);
-        }
-
-        for (std::string user_id : body.left) {
-                emit deleteLeftUsers(user_id);
-        }
+        for (const auto &user_id : user_ids)
+                lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr);
 }
 
-int
-Cache::deleteUserCache(const std::string &user_id)
+void
+Cache::markUserKeysOutOfDate(lmdb::txn &txn,
+                             lmdb::dbi &db,
+                             const std::vector<std::string> &user_ids,
+                             const std::string &sync_token)
 {
-        auto txn = lmdb::txn::begin(env_);
-        auto db  = getUserCacheDb(txn);
-        auto res = lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr);
+        mtx::requests::QueryKeys query;
+        query.token = sync_token;
 
-        txn.commit();
+        for (const auto &user : user_ids) {
+                lmdb::val oldKeys;
+                auto res = lmdb::dbi_get(txn, db, lmdb::val(user), oldKeys);
 
-        return res;
+                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);
+                                           });
 }
 
 void
-to_json(json &j, const DeviceVerifiedCache &info)
+to_json(json &j, const VerificationCache &info)
 {
-        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;
+        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, DeviceVerifiedCache &info)
+from_json(const json &j, VerificationCache &info)
 {
-        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>>();
+        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<DeviceVerifiedCache>
-Cache::getVerifiedCache(const std::string &user_id)
+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,47 +3508,49 @@ 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);
-}
 
+//! Load saved data for the display names & avatars.
 void
-updateUserCache(const mtx::responses::DeviceLists body)
+populateMembers()
 {
-        instance_->updateUserCache(body);
+        instance_->populateMembers();
 }
 
-int
-setUserCache(const std::string &user_id, const UserCache &body)
+// 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)
 {
-        return instance_->setUserCache(user_id, body);
+        instance_->updateUserKeys(sync_token, keyQuery);
 }
 
-int
-deleteUserCache(const std::string &user_id)
+// device & user verification cache
+std::optional<VerificationCache>
+verificationStatus(const std::string &user_id)
 {
-        return instance_->deleteUserCache(user_id);
+        return instance_->verificationStatus(user_id);
 }
 
-std::optional<DeviceVerifiedCache>
-getVerifiedCache(const std::string &user_id)
+void
+markDeviceVerified(const std::string &user_id, const std::string &key)
 {
-        return instance_->getVerifiedCache(user_id);
+        instance_->markDeviceVerified(user_id, key);
 }
 
-int
-setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body)
+void
+markDeviceUnverified(const std::string &user_id, const std::string &key)
 {
-        return instance_->setVerifiedCache(user_id, body);
+        instance_->markDeviceUnverified(user_id, key);
 }
 
-//! Load saved data for the display names & avatars.
 void
-populateMembers()
+markMasterKeyVerified(const std::string &user_id, const std::string &key)
 {
-        instance_->populateMembers();
+        instance_->markMasterKeyVerified(user_id, key);
 }
 
 std::vector<std::string>
diff --git a/src/Cache.h b/src/Cache.h
index 82d909ae..edad5993 100644
--- a/src/Cache.h
+++ b/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);
-
-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);
+updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery);
 
-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
diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h
index 1dde21ce..10636ac6 100644
--- a/src/CacheCryptoStructs.h
+++ b/src/CacheCryptoStructs.h
@@ -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);
diff --git a/src/Cache_p.h b/src/Cache_p.h
index ce6414ab..034c6d76 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -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);
-
-        // device verified cache
-        std::optional<DeviceVerifiedCache> getVerifiedCache(const std::string &user_id);
-        int setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body);
+        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 & 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);
         }
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index c6978a59..6abe4078 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -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>
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 9d8abb24..f363c4fe 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -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;
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 96fed55a..aa8b5b44 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -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());
-
-        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);
-                  });
-        }
+        this->userId   = userID;
+        this->toClient = mtx::identifiers::parse<mtx::identifiers::User>(userID.toStdString());
+
+        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);
         }
 }
-//! sends the mac of the keys
-void
-DeviceVerificationFlow::sendVerificationMac()
+
+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" + 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() + ",";
+        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 =
-          this->sas->calculate_mac(req.keys.substr(0, req.keys.size() - 1), info + "KEY_IDS");
+        req.keys = sas->calculate_mac(key_list, info + "KEY_IDS");
+
+        return req;
+}
+
+//! sends the mac of the keys
+void
+DeviceVerificationFlow::sendVerificationMac()
+{
+        std::map<std::string, std::string> key_list;
+        key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519;
+
+        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();
 }
diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h
index b85cbec2..31d2facc 100644
--- a/src/DeviceVerificationFlow.h
+++ b/src/DeviceVerificationFlow.h
@@ -1,10 +1,12 @@
 #pragma once
 
-#include "Olm.h"
+#include <QObject>
+
+#include <mtx/responses/crypto.hpp>
 
+#include "CacheCryptoStructs.h"
 #include "MatrixClient.h"
-#include "mtx/responses/crypto.hpp"
-#include <QObject>
+#include "Olm.h"
 
 class QTimer;
 
@@ -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;
 
diff --git a/src/timeline/.TimelineModel.cpp.swn b/src/timeline/.TimelineModel.cpp.swn
deleted file mode 100644
index 9e965702..00000000
--- a/src/timeline/.TimelineModel.cpp.swn
+++ /dev/null
Binary files differdiff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 08c30097..2ea3f7b6 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -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),