Automatically fetch keys for undecrypted messages after verification

Also fix rerendering edited messages after keys are received.

fixes #1375
fixes #770
fixes #888
This commit is contained in:
Nicolas Werner 2023-02-18 02:59:33 +01:00
parent 05727b8a45
commit 20740c9976
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
8 changed files with 81 additions and 16 deletions

View file

@ -547,6 +547,12 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
&Cache::newReadReceipts, &Cache::newReadReceipts,
view_manager_, view_manager_,
&TimelineViewManager::updateReadReceipts); &TimelineViewManager::updateReadReceipts);
connect(cache::client(), &Cache::secretChanged, this, [this](const std::string &secret) {
if (secret == mtx::secret_storage::secrets::megolm_backup_v1) {
getBackupVersion();
}
});
} catch (const lmdb::error &e) { } catch (const lmdb::error &e) {
nhlog::db()->critical("failure during boot: {}", e.what()); nhlog::db()->critical("failure during boot: {}", e.what());
emit dropToLoginPageCb(tr("Failed to open database, logging out!")); emit dropToLoginPageCb(tr("Failed to open database, logging out!"));
@ -1224,7 +1230,7 @@ ChatPage::getBackupVersion()
} }
// switch to UI thread for secrets stuff // switch to UI thread for secrets stuff
QTimer::singleShot(0, this, [res] { QTimer::singleShot(0, this, [this, res] {
auto auth_data = nlohmann::json::parse(res.auth_data); auto auth_data = nlohmann::json::parse(res.auth_data);
if (res.algorithm == "m.megolm_backup.v1.curve25519-aes-sha2") { if (res.algorithm == "m.megolm_backup.v1.curve25519-aes-sha2") {
@ -1247,11 +1253,17 @@ ChatPage::getBackupVersion()
return; return;
} }
auto oldBackupVersion = cache::client()->backupVersion();
nhlog::crypto()->info("Using online key backup."); nhlog::crypto()->info("Using online key backup.");
OnlineBackupVersion data{}; OnlineBackupVersion data{};
data.algorithm = res.algorithm; data.algorithm = res.algorithm;
data.version = res.version; data.version = res.version;
cache::client()->saveBackupVersion(data); cache::client()->saveBackupVersion(data);
if (!oldBackupVersion || oldBackupVersion->version != data.version) {
view_manager_->rooms()->refetchOnlineKeyBackupKeys();
}
} else { } else {
nhlog::crypto()->info("Unsupported key backup algorithm: {}", res.algorithm); nhlog::crypto()->info("Unsupported key backup algorithm: {}", res.algorithm);
cache::client()->deleteBackupVersion(); cache::client()->deleteBackupVersion();

View file

@ -180,6 +180,7 @@ signals:
QString reason = "", QString reason = "",
bool failedJoin = false, bool failedJoin = false,
bool promptForConfirmation = true); bool promptForConfirmation = true);
void newOnlineKeyBackupAvailable();
private slots: private slots:
void logout(); void logout();

View file

@ -896,12 +896,15 @@ download_full_keybackup()
{ {
if (!UserSettings::instance()->useOnlineKeyBackup()) { if (!UserSettings::instance()->useOnlineKeyBackup()) {
// Online key backup disabled // Online key backup disabled
nhlog::crypto()->debug("Not downloading full online key backup, because it is disabled.");
return; return;
} }
auto backupVersion = cache::client()->backupVersion(); auto backupVersion = cache::client()->backupVersion();
if (!backupVersion) { if (!backupVersion) {
// no trusted OKB // no trusted OKB
nhlog::crypto()->debug(
"Not downloading full online key backup, because we don't have a version for it.");
return; return;
} }
@ -910,10 +913,14 @@ download_full_keybackup()
auto decryptedSecret = cache::secret(mtx::secret_storage::secrets::megolm_backup_v1); auto decryptedSecret = cache::secret(mtx::secret_storage::secrets::megolm_backup_v1);
if (!decryptedSecret) { if (!decryptedSecret) {
// no backup key available // no backup key available
nhlog::crypto()->debug(
"Not downloading full online key backup, because we don't have a key for it.");
return; return;
} }
auto sessionDecryptionKey = to_binary_buf(base642bin(*decryptedSecret)); auto sessionDecryptionKey = to_binary_buf(base642bin(*decryptedSecret));
nhlog::crypto()->debug("Downloading full online key backup.");
http::client()->room_keys( http::client()->room_keys(
backupVersion->version, backupVersion->version,
[sessionDecryptionKey](const mtx::responses::backup::KeysBackup &bk, [sessionDecryptionKey](const mtx::responses::backup::KeysBackup &bk,
@ -925,11 +932,12 @@ download_full_keybackup()
err->matrix_error.error); err->matrix_error.error);
return; return;
} }
nhlog::crypto()->debug("Storing full online key backup.");
mtx::crypto::ExportedSessionKeys allKeys; mtx::crypto::ExportedSessionKeys allKeys;
try { for (const auto &[room, roomKey] : bk.rooms) {
for (const auto &[room, roomKey] : bk.rooms) { for (const auto &[session_id, encSession] : roomKey.sessions) {
for (const auto &[session_id, encSession] : roomKey.sessions) { try {
auto session = decrypt_session(encSession.session_data, sessionDecryptionKey); auto session = decrypt_session(encSession.session_data, sessionDecryptionKey);
if (session.algorithm != mtx::crypto::MEGOLM_ALGO) if (session.algorithm != mtx::crypto::MEGOLM_ALGO)
@ -946,16 +954,22 @@ download_full_keybackup()
sess.sender_key = std::move(session.sender_key); sess.sender_key = std::move(session.sender_key);
sess.session_key = std::move(session.session_key); sess.session_key = std::move(session.session_key);
allKeys.sessions.push_back(std::move(sess)); allKeys.sessions.push_back(std::move(sess));
} catch (const olm_exception &e) {
nhlog::crypto()->critical("failed to decrypt inbound megolm session: {}",
e.what());
} }
} }
// call on UI thread
QTimer::singleShot(0, ChatPage::instance(), [keys = std::move(allKeys)] {
cache::importSessionKeys(keys);
});
} catch (const lmdb::error &e) {
nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what());
} }
// call on UI thread
QTimer::singleShot(0, ChatPage::instance(), [keys = std::move(allKeys)] {
try {
cache::importSessionKeys(keys);
nhlog::crypto()->debug("Storing full online key backup completed.");
} catch (const lmdb::error &e) {
nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what());
}
});
}); });
} }
void void
@ -1083,8 +1097,6 @@ send_key_request_for(mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> e,
nhlog::net()->info( nhlog::net()->info(
"m.room_key_request sent to {}:{} and your own devices", e.sender, e.content.device_id); "m.room_key_request sent to {}:{} and your own devices", e.sender, e.content.device_id);
}); });
// http::client()->room_keys
} }
void void

View file

@ -17,6 +17,7 @@
#include "EventAccessors.h" #include "EventAccessors.h"
#include "Logging.h" #include "Logging.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "TimelineModel.h"
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
#include "Utils.h" #include "Utils.h"
@ -353,6 +354,16 @@ EventStore::receivedSessionKey(const std::string &session_id)
events_.remove({room_id_, toInternalIdx(*idx)}); events_.remove({room_id_, toInternalIdx(*idx)});
emit dataChanged(*idx, *idx); emit dataChanged(*idx, *idx);
} }
if (auto edit = e.content.relations.replaces()) {
auto edit_idx = idToIndex(edit.value());
if (edit_idx) {
decryptedEvents_.remove({room_id_, e.event_id});
events_by_id_.remove({room_id_, e.event_id});
events_.remove({room_id_, toInternalIdx(*edit_idx)});
emit dataChanged(*edit_idx, *edit_idx);
}
}
} }
} }
@ -538,7 +549,7 @@ EventStore::edits(const std::string &event_id)
// spec does not allow changing relatings in an edit. So if we are not using the multi // spec does not allow changing relatings in an edit. So if we are not using the multi
// relation format specific to Nheko, just use the original relations + the edit... // relation format specific to Nheko, just use the original relations + the edit...
if (edit_rel.synthesized) { if (edit_rel.synthesized) {
auto merged_relations = original_relations; auto merged_relations = original_relations;
merged_relations.synthesized = true; merged_relations.synthesized = true;
merged_relations.relations.push_back( merged_relations.relations.push_back(
{mtx::common::RelationType::Replace, event_id}); {mtx::common::RelationType::Replace, event_id});
@ -753,6 +764,15 @@ EventStore::decryptEvent(const IdIndex &idx,
return asCacheEntry(std::move(decryptionResult)); return asCacheEntry(std::move(decryptionResult));
} }
void
EventStore::refetchOnlineKeyBackupKeys(TimelineModel *room)
{
for (const auto &[session_id, request] : room->events.pending_key_requests) {
(void)request;
olm::lookup_keybackup(room->events.room_id_, session_id);
}
}
void void
EventStore::requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev, EventStore::requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev,
bool manual) bool manual)
@ -767,8 +787,8 @@ EventStore::requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::E
auto &r = pending_key_requests.at(ev.content.session_id); auto &r = pending_key_requests.at(ev.content.session_id);
r.events.push_back(copy); r.events.push_back(copy);
// automatically request once every 10 min, manually every 1 min // automatically request once every 2 min, manually every 30 s
qint64 delay = manual ? 60 : (60 * 10); qint64 delay = manual ? 30 : (60 * 2);
if (r.requested_at + delay < QDateTime::currentSecsSinceEpoch()) { if (r.requested_at + delay < QDateTime::currentSecsSinceEpoch()) {
r.requested_at = QDateTime::currentSecsSinceEpoch(); r.requested_at = QDateTime::currentSecsSinceEpoch();
olm::lookup_keybackup(room_id_, ev.content.session_id); olm::lookup_keybackup(room_id_, ev.content.session_id);

View file

@ -20,6 +20,8 @@
#include "Reaction.h" #include "Reaction.h"
#include "encryption/Olm.h" #include "encryption/Olm.h"
class TimelineModel;
class EventStore final : public QObject class EventStore final : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -27,6 +29,8 @@ class EventStore final : public QObject
public: public:
EventStore(std::string room_id, QObject *parent); EventStore(std::string room_id, QObject *parent);
static void refetchOnlineKeyBackupKeys(TimelineModel *room);
// taken from QtPrivate::QHashCombine // taken from QtPrivate::QHashCombine
static uint hashCombine(uint hash, uint seed) static uint hashCombine(uint hash, uint seed)
{ {

View file

@ -812,6 +812,18 @@ RoomlistModel::setCurrentRoom(const QString &roomid)
} }
} }
void
RoomlistModel::refetchOnlineKeyBackupKeys()
{
for (auto i = models.begin(); i != models.end(); ++i) {
auto ptr = i.value();
if (!ptr.isNull()) {
EventStore::refetchOnlineKeyBackupKeys(ptr.data());
}
}
}
namespace { namespace {
enum NotificationImportance : short enum NotificationImportance : short
{ {

View file

@ -95,6 +95,8 @@ public:
} }
RoomPreview getRoomPreviewById(QString roomid) const; RoomPreview getRoomPreviewById(QString roomid) const;
void refetchOnlineKeyBackupKeys();
public slots: public slots:
void initializeRooms(); void initializeRooms();
void sync(const mtx::responses::Sync &sync_); void sync(const mtx::responses::Sync &sync_);

View file

@ -528,6 +528,8 @@ private:
std::unique_ptr<RoomSummary, DeleteLaterDeleter> parentSummary = nullptr; std::unique_ptr<RoomSummary, DeleteLaterDeleter> parentSummary = nullptr;
bool parentChecked = false; bool parentChecked = false;
friend void EventStore::refetchOnlineKeyBackupKeys(TimelineModel *room);
}; };
template<class T> template<class T>