From 8e20139079c44504bae30b9b9013f199ebdd788d Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 27 Feb 2022 06:41:48 +0100 Subject: [PATCH] Allow properly editing pending encrypted messages --- src/encryption/Olm.cpp | 38 ++++++++++++++++-------- src/encryption/Olm.h | 5 ++++ src/timeline/EventStore.cpp | 54 +++++++++++++++++++++++++++++++--- src/timeline/TimelineModel.cpp | 29 ++++++++++-------- 4 files changed, 96 insertions(+), 30 deletions(-) diff --git a/src/encryption/Olm.cpp b/src/encryption/Olm.cpp index 5de18fa3..e6426658 100644 --- a/src/encryption/Olm.cpp +++ b/src/encryption/Olm.cpp @@ -470,6 +470,30 @@ handle_pre_key_olm_message(const std::string &sender, return plaintext; } +mtx::events::msg::Encrypted +encrypt_group_message_with_session(mtx::crypto::OutboundGroupSessionPtr &session, + const std::string &device_id, + nlohmann::json body) +{ + using namespace mtx::events; + + // relations shouldn't be encrypted... + mtx::common::Relations relations = mtx::common::parse_relations(body["content"]); + + auto payload = olm::client()->encrypt_group_message(session.get(), body.dump()); + + // Prepare the m.room.encrypted event. + msg::Encrypted data; + data.ciphertext = std::string((char *)payload.data(), payload.size()); + data.sender_key = olm::client()->identity_keys().curve25519; + data.session_id = mtx::crypto::session_id(session.get()); + data.device_id = device_id; + data.algorithm = MEGOLM_ALGO; + data.relations = relations; + + return data; +} + mtx::events::msg::Encrypted encrypt_group_message(const std::string &room_id, const std::string &device_id, nlohmann::json body) { @@ -631,19 +655,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id, if (!sendSessionTo.empty()) olm::send_encrypted_to_device_messages(sendSessionTo, megolm_payload); - // relations shouldn't be encrypted... - mtx::common::Relations relations = mtx::common::parse_relations(body["content"]); - - auto payload = olm::client()->encrypt_group_message(session.get(), body.dump()); - - // Prepare the m.room.encrypted event. - msg::Encrypted data; - data.ciphertext = std::string((char *)payload.data(), payload.size()); - data.sender_key = olm::client()->identity_keys().curve25519; - data.session_id = mtx::crypto::session_id(session.get()); - data.device_id = device_id; - data.algorithm = MEGOLM_ALGO; - data.relations = relations; + auto data = encrypt_group_message_with_session(session, device_id, body); group_session_data.message_index = olm_outbound_group_session_message_index(session.get()); nhlog::crypto()->debug("next message_index {}", group_session_data.message_index); diff --git a/src/encryption/Olm.h b/src/encryption/Olm.h index a6822b68..9d99bcf4 100644 --- a/src/encryption/Olm.h +++ b/src/encryption/Olm.h @@ -79,6 +79,11 @@ handle_pre_key_olm_message(const std::string &sender, const std::string &sender_key, const mtx::events::msg::OlmCipherContent &content); +mtx::events::msg::Encrypted +encrypt_group_message_with_session(mtx::crypto::OutboundGroupSessionPtr &session, + const std::string &device_id, + nlohmann::json body); + mtx::events::msg::Encrypted encrypt_group_message(const std::string &room_id, const std::string &device_id, diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index f7d15b61..4151356f 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -184,11 +184,21 @@ EventStore::EventStore(std::string room_id, QObject *) // Replace the event_id in pending edits/replies/redactions with the actual // event_id of this event. This allows one to edit and reply to events that are // currently pending. - - // FIXME (introduced by balsoft): this doesn't work for encrypted events, but - // allegedly it's hard to fix so I'll leave my first contribution at that for (const auto &pending_event_id : cache::client()->pendingEvents(room_id_)) { if (auto pending_event = cache::client()->getEvent(room_id_, pending_event_id)) { + bool was_encrypted = false; + mtx::events::EncryptedEvent original_encrypted; + if (auto encrypted = + std::get_if>( + &pending_event->data)) { + auto d_event = decryptEvent({room_id_, encrypted->event_id}, *encrypted); + if (d_event->event) { + was_encrypted = true; + original_encrypted = *encrypted; + pending_event->data = *d_event->event; + } + } + auto relations = mtx::accessors::relations(pending_event->data); // Replace the blockquote in fallback reply @@ -202,13 +212,49 @@ EventStore::EventStore(std::string room_id, QObject *) } } + bool replaced_txn = false; for (mtx::common::Relation &rel : relations.relations) { - if (rel.event_id == txn_id) + if (rel.event_id == txn_id) { rel.event_id = event_id; + replaced_txn = true; + } } + if (!replaced_txn) + continue; + mtx::accessors::set_relations(pending_event->data, std::move(relations)); + // reencrypt. This is a bit of a hack and might make people able to read the + // message, that were in the room at the time of sending the last pending message. + // That window is pretty small though, so it should be good enough. We also just + // fail, if there was no session. But there SHOULD always be one. Let's wait until + // I am proven wrong :3 + if (was_encrypted) { + auto session = cache::getOutboundMegolmSession(room_id_); + if (!session.session) + continue; + + std::visit( + [&pending_event, &original_encrypted, &session, this](auto &msg) { + json doc = {{"type", mtx::events::to_string(msg.type)}, + {"content", json(msg.content)}, + {"room_id", room_id_}}; + + auto data = olm::encrypt_group_message_with_session( + session.session, http::client()->device_id(), doc); + + session.data.message_index = + olm_outbound_group_session_message_index(session.session.get()); + cache::updateOutboundMegolmSession( + room_id_, session.data, session.session); + + original_encrypted.content = data; + pending_event->data = original_encrypted; + }, + pending_event->data); + } + cache::client()->replaceEvent(room_id_, pending_event_id, *pending_event); auto idx = idToIndex(pending_event_id); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 7892c2e4..a8b6cf6f 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -423,19 +423,22 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj // When a message is sent, check if the current edit/reply relates to that message, // and update the event_id so that it points to the sent message and not the pending one. - connect(&events, - &EventStore::messageSent, - this, - [this](const std::string &txn_id, const std::string &event_id) { - if (edit_.toStdString() == txn_id) { - edit_ = QString::fromStdString(event_id); - emit editChanged(edit_); - } - if (reply_.toStdString() == txn_id) { - reply_ = QString::fromStdString(event_id); - emit replyChanged(reply_); - } - }); + connect( + &events, + &EventStore::messageSent, + this, + [this](const std::string &txn_id, const std::string &event_id) { + if (edit_.toStdString() == txn_id) { + edit_ = QString::fromStdString(event_id); + emit editChanged(edit_); + } + nhlog::net()->debug("reply {}\ntxn {}\nev {}", reply_.toStdString(), txn_id, event_id); + if (reply_.toStdString() == txn_id) { + reply_ = QString::fromStdString(event_id); + emit replyChanged(reply_); + } + }, + Qt::QueuedConnection); connect( manager_, &TimelineViewManager::initialSyncChanged, &events, &EventStore::enableKeyRequests);