summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2021-08-07 22:51:09 +0200
committerNicolas Werner <nicolas.werner@hotmail.de>2021-08-07 22:51:09 +0200
commit72bbad7485db6ac1803f81344c29b93d9fa70945 (patch)
tree6fb74f887102affa0d5a6f4fdac0b87c1881c6ad /src
parentMerge pull request #664 from govynnus/token-registration (diff)
downloadnheko-72bbad7485db6ac1803f81344c29b93d9fa70945.tar.xz
Show encryption errors in qml and add request keys button
Diffstat (limited to '')
-rw-r--r--src/Olm.cpp2
-rw-r--r--src/Olm.h9
-rw-r--r--src/timeline/EventStore.cpp256
-rw-r--r--src/timeline/EventStore.h9
-rw-r--r--src/timeline/TimelineModel.cpp16
-rw-r--r--src/timeline/TimelineModel.h3
-rw-r--r--src/timeline/TimelineViewManager.cpp2
7 files changed, 142 insertions, 155 deletions
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 048a6c0f..293b12de 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -1069,7 +1069,7 @@ decryptEvent(const MegolmSessionIndex &index,
                 mtx::events::collections::TimelineEvent te;
                 mtx::events::collections::from_json(body, te);
 
-                return {std::nullopt, std::nullopt, std::move(te.data)};
+                return {DecryptionErrorCode::NoError, std::nullopt, std::move(te.data)};
         } catch (std::exception &e) {
                 return {DecryptionErrorCode::ParsingFailed, e.what(), std::nullopt};
         }
diff --git a/src/Olm.h b/src/Olm.h
index a18cbbfb..ac1a1617 100644
--- a/src/Olm.h
+++ b/src/Olm.h
@@ -14,9 +14,11 @@
 constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2";
 
 namespace olm {
+Q_NAMESPACE
 
-enum class DecryptionErrorCode
+enum DecryptionErrorCode
 {
+        NoError,
         MissingSession, // Session was not found, retrieve from backup or request from other devices
                         // and try again
         MissingSessionIndex, // Session was found, but it does not reach back enough to this index,
@@ -25,14 +27,13 @@ enum class DecryptionErrorCode
         DecryptionFailed,    // libolm error
         ParsingFailed,       // Failed to parse the actual event
         ReplayAttack,        // Megolm index reused
-        UnknownFingerprint,  // Unknown device Fingerprint
 };
+Q_ENUM_NS(DecryptionErrorCode)
 
 struct DecryptionResult
 {
-        std::optional<DecryptionErrorCode> error;
+        DecryptionErrorCode error;
         std::optional<std::string> error_message;
-
         std::optional<mtx::events::collections::TimelineEvents> event;
 };
 
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index 9a91ff79..742f8dbb 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -20,8 +20,7 @@
 
 Q_DECLARE_METATYPE(Reaction)
 
-QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::decryptedEvents_{
-  1000};
+QCache<EventStore::IdIndex, olm::DecryptionResult> EventStore::decryptedEvents_{1000};
 QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::events_by_id_{
   1000};
 QCache<EventStore::Index, mtx::events::collections::TimelineEvents> EventStore::events_{1000};
@@ -144,12 +143,16 @@ EventStore::EventStore(std::string room_id, QObject *)
                                                             mtx::events::msg::Encrypted>) {
                                                     auto event =
                                                       decryptEvent({room_id_, e.event_id}, e);
-                                                    if (auto dec =
-                                                          std::get_if<mtx::events::RoomEvent<
-                                                            mtx::events::msg::
-                                                              KeyVerificationRequest>>(event)) {
-                                                            emit updateFlowEventId(
-                                                              event_id.event_id.to_string());
+                                                    if (event->event) {
+                                                            if (auto dec = std::get_if<
+                                                                  mtx::events::RoomEvent<
+                                                                    mtx::events::msg::
+                                                                      KeyVerificationRequest>>(
+                                                                  &event->event.value())) {
+                                                                    emit updateFlowEventId(
+                                                                      event_id.event_id
+                                                                        .to_string());
+                                                            }
                                                     }
                                             }
                                     });
@@ -393,12 +396,12 @@ EventStore::handleSync(const mtx::responses::Timeline &events)
                 if (auto encrypted =
                       std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
                         &event)) {
-                        mtx::events::collections::TimelineEvents *d_event =
-                          decryptEvent({room_id_, encrypted->event_id}, *encrypted);
-                        if (std::visit(
+                        auto d_event = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
+                        if (d_event->event &&
+                            std::visit(
                               [](auto e) { return (e.sender != utils::localUser().toStdString()); },
-                              *d_event)) {
-                                handle_room_verification(*d_event);
+                              *d_event->event)) {
+                                handle_room_verification(*d_event->event);
                         }
                 }
         }
@@ -599,11 +602,15 @@ EventStore::get(int idx, bool decrypt)
                 events_.insert(index, event_ptr);
         }
 
-        if (decrypt)
+        if (decrypt) {
                 if (auto encrypted =
                       std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
-                        event_ptr))
-                        return decryptEvent({room_id_, encrypted->event_id}, *encrypted);
+                        event_ptr)) {
+                        auto decrypted = decryptEvent({room_id_, encrypted->event_id}, *encrypted);
+                        if (decrypted->event)
+                                return &*decrypted->event;
+                }
+        }
 
         return event_ptr;
 }
@@ -629,7 +636,7 @@ EventStore::indexToId(int idx) const
         return cache::client()->getTimelineEventId(room_id_, toInternalIdx(idx));
 }
 
-mtx::events::collections::TimelineEvents *
+olm::DecryptionResult *
 EventStore::decryptEvent(const IdIndex &idx,
                          const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e)
 {
@@ -641,57 +648,24 @@ EventStore::decryptEvent(const IdIndex &idx,
         index.session_id = e.content.session_id;
         index.sender_key = e.content.sender_key;
 
-        auto asCacheEntry = [&idx](mtx::events::collections::TimelineEvents &&event) {
-                auto event_ptr = new mtx::events::collections::TimelineEvents(std::move(event));
+        auto asCacheEntry = [&idx](olm::DecryptionResult &&event) {
+                auto event_ptr = new olm::DecryptionResult(std::move(event));
                 decryptedEvents_.insert(idx, event_ptr);
                 return event_ptr;
         };
 
         auto decryptionResult = olm::decryptEvent(index, e);
 
-        mtx::events::RoomEvent<mtx::events::msg::Notice> dummy;
-        dummy.origin_server_ts = e.origin_server_ts;
-        dummy.event_id         = e.event_id;
-        dummy.sender           = e.sender;
-
         if (decryptionResult.error) {
-                switch (*decryptionResult.error) {
+                switch (decryptionResult.error) {
                 case olm::DecryptionErrorCode::MissingSession:
                 case olm::DecryptionErrorCode::MissingSessionIndex: {
-                        if (decryptionResult.error == olm::DecryptionErrorCode::MissingSession)
-                                dummy.content.body =
-                                  tr("-- Encrypted Event (No keys found for decryption) --",
-                                     "Placeholder, when the message was not decrypted yet or can't "
-                                     "be "
-                                     "decrypted.")
-                                    .toStdString();
-                        else
-                                dummy.content.body =
-                                  tr("-- Encrypted Event (Key not valid for this index) --",
-                                     "Placeholder, when the message can't be decrypted with this "
-                                     "key since it is not valid for this index ")
-                                    .toStdString();
                         nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
                                               index.room_id,
                                               index.session_id,
                                               e.sender);
-                        // we may not want to request keys during initial sync and such
-                        if (suppressKeyRequests)
-                                break;
-                        // TODO: Check if this actually works and look in key backup
-                        auto copy    = e;
-                        copy.room_id = room_id_;
-                        if (pending_key_requests.count(e.content.session_id)) {
-                                pending_key_requests.at(e.content.session_id)
-                                  .events.push_back(copy);
-                        } else {
-                                PendingKeyRequests request;
-                                request.request_id =
-                                  "key_request." + http::client()->generate_txn_id();
-                                request.events.push_back(copy);
-                                olm::send_key_request_for(copy, request.request_id);
-                                pending_key_requests[e.content.session_id] = request;
-                        }
+
+                        requestSession(e, false);
                         break;
                 }
                 case olm::DecryptionErrorCode::DbError:
@@ -701,12 +675,6 @@ EventStore::decryptEvent(const IdIndex &idx,
                           index.session_id,
                           index.sender_key,
                           decryptionResult.error_message.value_or(""));
-                        dummy.content.body =
-                          tr("-- Decryption Error (failed to retrieve megolm keys from db) --",
-                             "Placeholder, when the message can't be decrypted, because the DB "
-                             "access "
-                             "failed.")
-                            .toStdString();
                         break;
                 case olm::DecryptionErrorCode::DecryptionFailed:
                         nhlog::crypto()->critical(
@@ -715,22 +683,8 @@ EventStore::decryptEvent(const IdIndex &idx,
                           index.session_id,
                           index.sender_key,
                           decryptionResult.error_message.value_or(""));
-                        dummy.content.body =
-                          tr("-- Decryption Error (%1) --",
-                             "Placeholder, when the message can't be decrypted. In this case, the "
-                             "Olm "
-                             "decrytion returned an error, which is passed as %1.")
-                            .arg(
-                              QString::fromStdString(decryptionResult.error_message.value_or("")))
-                            .toStdString();
                         break;
                 case olm::DecryptionErrorCode::ParsingFailed:
-                        dummy.content.body =
-                          tr("-- Encrypted Event (Unknown event type) --",
-                             "Placeholder, when the message was decrypted, but we couldn't parse "
-                             "it, because "
-                             "Nheko/mtxclient don't support that event type yet.")
-                            .toStdString();
                         break;
                 case olm::DecryptionErrorCode::ReplayAttack:
                         nhlog::crypto()->critical(
@@ -738,85 +692,50 @@ EventStore::decryptEvent(const IdIndex &idx,
                           e.event_id,
                           room_id_,
                           index.sender_key);
-                        dummy.content.body =
-                          tr("-- Replay attack! This message index was reused! --").toStdString();
                         break;
-                case olm::DecryptionErrorCode::UnknownFingerprint:
-                        // TODO: don't fail, just show in UI.
-                        nhlog::crypto()->critical("Message by unverified fingerprint {}",
-                                                  index.sender_key);
-                        dummy.content.body =
-                          tr("-- Message by unverified device! --").toStdString();
+                case olm::DecryptionErrorCode::NoError:
+                        // unreachable
                         break;
                 }
-                return asCacheEntry(std::move(dummy));
-        }
-
-        std::string msg_str;
-        try {
-                auto session = cache::client()->getInboundMegolmSession(index);
-                auto res =
-                  olm::client()->decrypt_group_message(session.get(), e.content.ciphertext);
-                msg_str = std::string((char *)res.data.data(), res.data.size());
-        } catch (const lmdb::error &e) {
-                nhlog::db()->critical("failed to retrieve megolm session with index ({}, {}, {})",
-                                      index.room_id,
-                                      index.session_id,
-                                      index.sender_key,
-                                      e.what());
-                dummy.content.body =
-                  tr("-- Decryption Error (failed to retrieve megolm keys from db) --",
-                     "Placeholder, when the message can't be decrypted, because the DB "
-                     "access "
-                     "failed.")
-                    .toStdString();
-                return asCacheEntry(std::move(dummy));
-        } catch (const mtx::crypto::olm_exception &e) {
-                nhlog::crypto()->critical("failed to decrypt message with index ({}, {}, {}): {}",
-                                          index.room_id,
-                                          index.session_id,
-                                          index.sender_key,
-                                          e.what());
-                dummy.content.body =
-                  tr("-- Decryption Error (%1) --",
-                     "Placeholder, when the message can't be decrypted. In this case, the "
-                     "Olm "
-                     "decrytion returned an error, which is passed as %1.")
-                    .arg(e.what())
-                    .toStdString();
-                return asCacheEntry(std::move(dummy));
-        }
-
-        // Add missing fields for the event.
-        json body                = json::parse(msg_str);
-        body["event_id"]         = e.event_id;
-        body["sender"]           = e.sender;
-        body["origin_server_ts"] = e.origin_server_ts;
-        body["unsigned"]         = e.unsigned_data;
-
-        // relations are unencrypted in content...
-        mtx::common::add_relations(body["content"], e.content.relations);
-
-        json event_array = json::array();
-        event_array.push_back(body);
-
-        std::vector<mtx::events::collections::TimelineEvents> temp_events;
-        mtx::responses::utils::parse_timeline_events(event_array, temp_events);
-
-        if (temp_events.size() == 1) {
-                auto encInfo = mtx::accessors::file(temp_events[0]);
-
-                if (encInfo)
-                        emit newEncryptedImage(encInfo.value());
-
-                return asCacheEntry(std::move(temp_events[0]));
+                return asCacheEntry(std::move(decryptionResult));
         }
 
         auto encInfo = mtx::accessors::file(decryptionResult.event.value());
         if (encInfo)
                 emit newEncryptedImage(encInfo.value());
 
-        return asCacheEntry(std::move(decryptionResult.event.value()));
+        return asCacheEntry(std::move(decryptionResult));
+}
+
+void
+EventStore::requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev,
+                           bool manual)
+{
+        // we may not want to request keys during initial sync and such
+        if (suppressKeyRequests)
+                return;
+
+        // TODO: Look in key backup
+        auto copy    = ev;
+        copy.room_id = room_id_;
+        if (pending_key_requests.count(ev.content.session_id)) {
+                auto &r = pending_key_requests.at(ev.content.session_id);
+                r.events.push_back(copy);
+
+                // automatically request once every 10 min, manually every 1 min
+                qint64 delay = manual ? 60 : (60 * 10);
+                if (r.requested_at + delay < QDateTime::currentSecsSinceEpoch()) {
+                        r.requested_at = QDateTime::currentSecsSinceEpoch();
+                        olm::send_key_request_for(copy, r.request_id);
+                }
+        } else {
+                PendingKeyRequests request;
+                request.request_id   = "key_request." + http::client()->generate_txn_id();
+                request.requested_at = QDateTime::currentSecsSinceEpoch();
+                request.events.push_back(copy);
+                olm::send_key_request_for(copy, request.request_id);
+                pending_key_requests[ev.content.session_id] = request;
+        }
 }
 
 void
@@ -877,15 +796,56 @@ EventStore::get(std::string id, std::string_view related_to, bool decrypt, bool
                 events_by_id_.insert(index, event_ptr);
         }
 
-        if (decrypt)
+        if (decrypt) {
                 if (auto encrypted =
                       std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
-                        event_ptr))
-                        return decryptEvent(index, *encrypted);
+                        event_ptr)) {
+                        auto decrypted = decryptEvent(index, *encrypted);
+                        if (decrypted->event)
+                                return &*decrypted->event;
+                }
+        }
 
         return event_ptr;
 }
 
+olm::DecryptionErrorCode
+EventStore::decryptionError(std::string id)
+{
+        if (this->thread() != QThread::currentThread())
+                nhlog::db()->warn("{} called from a different thread!", __func__);
+
+        if (id.empty())
+                return olm::DecryptionErrorCode::NoError;
+
+        IdIndex index{room_id_, std::move(id)};
+        auto edits_ = edits(index.id);
+        if (!edits_.empty()) {
+                index.id = mtx::accessors::event_id(edits_.back());
+                auto event_ptr =
+                  new mtx::events::collections::TimelineEvents(std::move(edits_.back()));
+                events_by_id_.insert(index, event_ptr);
+        }
+
+        auto event_ptr = events_by_id_.object(index);
+        if (!event_ptr) {
+                auto event = cache::client()->getEvent(room_id_, index.id);
+                if (!event) {
+                        return olm::DecryptionErrorCode::NoError;
+                }
+                event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data));
+                events_by_id_.insert(index, event_ptr);
+        }
+
+        if (auto encrypted =
+              std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(event_ptr)) {
+                auto decrypted = decryptEvent(index, *encrypted);
+                return decrypted->error;
+        }
+
+        return olm::DecryptionErrorCode::NoError;
+}
+
 void
 EventStore::fetchMore()
 {
diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h
index 7c404102..59c1c7c0 100644
--- a/src/timeline/EventStore.h
+++ b/src/timeline/EventStore.h
@@ -15,6 +15,7 @@
 #include <mtx/responses/messages.hpp>
 #include <mtx/responses/sync.hpp>
 
+#include "Olm.h"
 #include "Reaction.h"
 
 class EventStore : public QObject
@@ -78,6 +79,9 @@ public:
         mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true);
 
         QVariantList reactions(const std::string &event_id);
+        olm::DecryptionErrorCode decryptionError(std::string id);
+        void requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev,
+                            bool manual);
 
         int size() const
         {
@@ -119,7 +123,7 @@ public slots:
 
 private:
         std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id);
-        mtx::events::collections::TimelineEvents *decryptEvent(
+        olm::DecryptionResult *decryptEvent(
           const IdIndex &idx,
           const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
         void handle_room_verification(mtx::events::collections::TimelineEvents event);
@@ -129,7 +133,7 @@ private:
         uint64_t first = std::numeric_limits<uint64_t>::max(),
                  last  = std::numeric_limits<uint64_t>::max();
 
-        static QCache<IdIndex, mtx::events::collections::TimelineEvents> decryptedEvents_;
+        static QCache<IdIndex, olm::DecryptionResult> decryptedEvents_;
         static QCache<Index, mtx::events::collections::TimelineEvents> events_;
         static QCache<IdIndex, mtx::events::collections::TimelineEvents> events_by_id_;
 
@@ -137,6 +141,7 @@ private:
         {
                 std::string request_id;
                 std::vector<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>> events;
+                qint64 requested_at;
         };
         std::map<std::string, PendingKeyRequests> pending_key_requests;
 
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 10d9788d..99e00a67 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -452,6 +452,7 @@ TimelineModel::roleNames() const
           {IsEditable, "isEditable"},
           {IsEncrypted, "isEncrypted"},
           {Trustlevel, "trustlevel"},
+          {EncryptionError, "encryptionError"},
           {ReplyTo, "replyTo"},
           {Reactions, "reactions"},
           {RoomId, "roomId"},
@@ -639,6 +640,9 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
                 return crypto::Trust::Unverified;
         }
 
+        case EncryptionError:
+                return events.decryptionError(event_id(event));
+
         case ReplyTo:
                 return QVariant(QString::fromStdString(relations(event).reply_to().value_or("")));
         case Reactions: {
@@ -690,6 +694,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
                 m.insert(names[RoomName], data(event, static_cast<int>(RoomName)));
                 m.insert(names[RoomTopic], data(event, static_cast<int>(RoomTopic)));
                 m.insert(names[CallType], data(event, static_cast<int>(CallType)));
+                m.insert(names[EncryptionError], data(event, static_cast<int>(EncryptionError)));
 
                 return QVariant(m);
         }
@@ -1552,6 +1557,17 @@ TimelineModel::scrollTimerEvent()
 }
 
 void
+TimelineModel::requestKeyForEvent(QString id)
+{
+        auto encrypted_event = events.get(id.toStdString(), "", false);
+        if (encrypted_event) {
+                if (auto ev = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+                      encrypted_event))
+                        events.requestSession(*ev, true);
+        }
+}
+
+void
 TimelineModel::copyLinkToEvent(QString eventId) const
 {
         QStringList vias;
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index b5c8ca37..ad7cfbbb 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -212,6 +212,7 @@ public:
                 IsEditable,
                 IsEncrypted,
                 Trustlevel,
+                EncryptionError,
                 ReplyTo,
                 Reactions,
                 RoomId,
@@ -264,6 +265,8 @@ public:
                 endResetModel();
         }
 
+        Q_INVOKABLE void requestKeyForEvent(QString id);
+
         std::vector<::Reaction> reactions(const std::string &event_id)
         {
                 auto list = events.reactions(event_id);
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 76bc127e..b23ed278 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -158,6 +158,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
                                          "MtxEvent",
                                          "Can't instantiate enum!");
         qmlRegisterUncreatableMetaObject(
+          olm::staticMetaObject, "im.nheko", 1, 0, "Olm", "Can't instantiate enum!");
+        qmlRegisterUncreatableMetaObject(
           crypto::staticMetaObject, "im.nheko", 1, 0, "Crypto", "Can't instantiate enum!");
         qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
                                          "im.nheko",