summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--deps/CMakeLists.txt4
-rw-r--r--include/Olm.hpp16
-rw-r--r--include/timeline/TimelineItem.h1
-rw-r--r--src/Cache.cc10
-rw-r--r--src/Olm.cpp275
-rw-r--r--src/timeline/TimelineItem.cc14
-rw-r--r--src/timeline/TimelineView.cc2
7 files changed, 312 insertions, 10 deletions
diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt
index c0d00d53..13426538 100644
--- a/deps/CMakeLists.txt
+++ b/deps/CMakeLists.txt
@@ -39,10 +39,10 @@ set(BOOST_SHA256
     5721818253e6a0989583192f96782c4a98eb6204965316df9f5ad75819225ca9)
 
 set(MATRIX_STRUCTS_URL https://github.com/mujx/matrix-structs)
-set(MATRIX_STRUCTS_TAG 3a052a95c555ce3ae12b8a2e0508e8bb73266fa1)
+set(MATRIX_STRUCTS_TAG 92a5e99db51301b5abf626aa872a1a87b7727634)
 
 set(MTXCLIENT_URL https://github.com/mujx/mtxclient)
-set(MTXCLIENT_TAG 73491268f94ddeb606284836bb5f512d11b0e249)
+set(MTXCLIENT_TAG 708c8c6772b9bd99d77c5be6bb3ba58643258628)
 
 set(TWEENY_URL https://github.com/mobius3/tweeny)
 set(TWEENY_TAG b94ce07cfb02a0eb8ac8aaf66137dabdaea857cf)
diff --git a/include/Olm.hpp b/include/Olm.hpp
index 58568081..2730b18c 100644
--- a/include/Olm.hpp
+++ b/include/Olm.hpp
@@ -67,4 +67,20 @@ encrypt_group_message(const std::string &room_id,
 void
 mark_keys_as_published();
 
+//! Request the encryption keys from sender's device for the given event.
+void
+request_keys(const std::string &room_id, const std::string &event_id);
+
+void
+send_key_request_for(const std::string &room_id,
+                     const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &);
+
+void
+handle_key_request_message(const mtx::events::msg::KeyRequest &);
+
+void
+send_megolm_key_to_device(const std::string &user_id,
+                          const std::string &device_id,
+                          const json &payload);
+
 } // namespace olm
diff --git a/include/timeline/TimelineItem.h b/include/timeline/TimelineItem.h
index 95d4be3d..d3cab0a0 100644
--- a/include/timeline/TimelineItem.h
+++ b/include/timeline/TimelineItem.h
@@ -241,6 +241,7 @@ public:
 
         //! Add a user avatar for this event.
         void addAvatar();
+        void addKeyRequestAction();
 
 signals:
         void eventRedacted(const QString &event_id);
diff --git a/src/Cache.cc b/src/Cache.cc
index a276f554..1c3fa3dd 100644
--- a/src/Cache.cc
+++ b/src/Cache.cc
@@ -987,12 +987,11 @@ Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id)
                 if (obj.count("event") == 0 || obj.count("token") == 0)
                         continue;
 
-                mtx::events::collections::TimelineEvents event;
-                mtx::events::collections::from_json(obj.at("event"), event);
+                mtx::events::collections::TimelineEvent event = obj.at("event");
 
                 index += 1;
 
-                timeline.events.push_back(event);
+                timeline.events.push_back(event.data);
                 timeline.prev_batch = obj.at("token").get<std::string>();
         }
         cursor.close();
@@ -1059,12 +1058,11 @@ Cache::getLastMessageInfo(lmdb::txn &txn, const std::string &room_id)
                 if (obj.count("event") == 0)
                         continue;
 
-                mtx::events::collections::TimelineEvents event;
-                mtx::events::collections::from_json(obj.at("event"), event);
+                mtx::events::collections::TimelineEvent event = obj.at("event");
 
                 cursor.close();
                 return utils::getMessageDescription(
-                  event, local_user, QString::fromStdString(room_id));
+                  event.data, local_user, QString::fromStdString(room_id));
         }
         cursor.close();
 
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 8a78fd1d..67e375b5 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -2,6 +2,7 @@
 
 #include "Cache.h"
 #include "Logging.hpp"
+#include "MatrixClient.h"
 
 using namespace mtx::crypto;
 
@@ -49,9 +50,22 @@ handle_to_device_messages(const std::vector<nlohmann::json> &msgs)
                                   "validation error for olm message: {} {}", e.what(), msg.dump(2));
                         }
 
-                        // TODO: Move this event type into matrix-structs
-                } else if (msg_type == "m.room_key_request") {
+                } else if (msg_type == to_string(mtx::events::EventType::RoomKeyRequest)) {
                         nhlog::crypto()->warn("handling key request event: {}", msg.dump(2));
+                        try {
+                                mtx::events::msg::KeyRequest req = msg;
+                                if (req.action == mtx::events::msg::RequestAction::Request)
+                                        handle_key_request_message(std::move(req));
+                                else
+                                        nhlog::crypto()->warn(
+                                          "ignore key request (unhandled action): {}",
+                                          req.request_id);
+                        } catch (const nlohmann::json::exception &e) {
+                                nhlog::crypto()->warn(
+                                  "parsing error for key_request message: {} {}",
+                                  e.what(),
+                                  msg.dump(2));
+                        }
                 } else {
                         nhlog::crypto()->warn("unhandled event: {}", msg.dump(2));
                 }
@@ -256,4 +270,261 @@ mark_keys_as_published()
         cache::client()->saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY));
 }
 
+void
+request_keys(const std::string &room_id, const std::string &event_id)
+{
+        nhlog::crypto()->info("requesting keys for event {} at {}", event_id, room_id);
+
+        http::v2::client()->get_event(
+          room_id,
+          event_id,
+          [event_id, room_id](const mtx::events::collections::TimelineEvents &res,
+                              mtx::http::RequestErr err) {
+                  using namespace mtx::events;
+
+                  if (err) {
+                          nhlog::net()->warn(
+                            "failed to retrieve event {} from {}", event_id, room_id);
+                          return;
+                  }
+
+                  if (!mpark::holds_alternative<EncryptedEvent<msg::Encrypted>>(res)) {
+                          nhlog::net()->info(
+                            "retrieved event is not encrypted: {} from {}", event_id, room_id);
+                          return;
+                  }
+
+                  olm::send_key_request_for(room_id,
+                                            mpark::get<EncryptedEvent<msg::Encrypted>>(res));
+          });
+}
+
+void
+send_key_request_for(const std::string &room_id,
+                     const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e)
+{
+        using namespace mtx::events;
+
+        nhlog::crypto()->debug("sending key request: {}", json(e).dump(2));
+        auto payload = json{{"action", "request"},
+                            {"request_id", http::v2::client()->generate_txn_id()},
+                            {"requesting_device_id", http::v2::client()->device_id()},
+                            {"body",
+                             {{"algorithm", MEGOLM_ALGO},
+                              {"room_id", room_id},
+                              {"sender_key", e.content.sender_key},
+                              {"session_id", e.content.session_id}}}};
+
+        json body;
+        body["messages"][e.sender]                      = json::object();
+        body["messages"][e.sender][e.content.device_id] = payload;
+
+        nhlog::crypto()->debug("m.room_key_request: {}", body.dump(2));
+
+        http::v2::client()->send_to_device(
+          "m.room_key_request", body, [e](mtx::http::RequestErr err) {
+                  if (err) {
+                          nhlog::net()->warn("failed to send "
+                                             "send_to_device "
+                                             "message: {}",
+                                             err->matrix_error.error);
+                  }
+
+                  nhlog::net()->info(
+                    "m.room_key_request sent to {}:{}", e.sender, e.content.device_id);
+          });
+}
+
+void
+handle_key_request_message(const mtx::events::msg::KeyRequest &req)
+{
+        if (req.algorithm != MEGOLM_ALGO) {
+                nhlog::crypto()->info("ignoring key request {} with invalid algorithm: {}",
+                                      req.request_id,
+                                      req.algorithm);
+                return;
+        }
+
+        // Check if we were the sender of the session being requested.
+        if (req.sender_key != olm::client()->identity_keys().curve25519) {
+                nhlog::crypto()->info("ignoring key request {} because we were not the sender: "
+                                      "\nrequested({}) ours({})",
+                                      req.request_id,
+                                      req.sender_key,
+                                      olm::client()->identity_keys().curve25519);
+                return;
+        }
+
+        // Check if we have the keys for the requested session.
+        if (!cache::client()->outboundMegolmSessionExists(req.room_id)) {
+                nhlog::crypto()->warn("requested session not found in room: {}", req.room_id);
+                return;
+        }
+
+        // Check that the requested session_id and the one we have saved match.
+        const auto session = cache::client()->getOutboundMegolmSession(req.room_id);
+        if (req.session_id != session.data.session_id) {
+                nhlog::crypto()->warn("session id of retrieved session doesn't match the request: "
+                                      "requested({}), ours({})",
+                                      req.session_id,
+                                      session.data.session_id);
+                return;
+        }
+
+        //
+        // Prepare the m.room_key event.
+        //
+        auto payload = json{{"algorithm", "m.megolm.v1.aes-sha2"},
+                            {"room_id", req.room_id},
+                            {"session_id", req.session_id},
+                            {"session_key", session.data.session_key}};
+
+        send_megolm_key_to_device(req.sender, req.requesting_device_id, payload);
+}
+
+void
+send_megolm_key_to_device(const std::string &user_id,
+                          const std::string &device_id,
+                          const json &payload)
+{
+        mtx::requests::QueryKeys req;
+        req.device_keys[user_id] = {device_id};
+
+        http::v2::client()->query_keys(
+          req,
+          [payload, user_id, device_id](const mtx::responses::QueryKeys &res,
+                                        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;
+                  }
+
+                  nhlog::net()->warn("retrieved device keys from {}, {}", user_id, device_id);
+
+                  if (res.device_keys.empty()) {
+                          nhlog::net()->warn("no devices retrieved {}", user_id);
+                          return;
+                  }
+
+                  auto device = res.device_keys.begin()->second;
+                  if (device.empty()) {
+                          nhlog::net()->warn("no keys retrieved from user, device {}", user_id);
+                          return;
+                  }
+
+                  const auto device_keys = device.begin()->second.keys;
+                  const auto curveKey    = "curve25519:" + device_id;
+                  const auto edKey       = "ed25519:" + device_id;
+
+                  if ((device_keys.find(curveKey) == device_keys.end()) ||
+                      (device_keys.find(edKey) == device_keys.end())) {
+                          nhlog::net()->info("ignoring malformed keys for device {}", device_id);
+                          return;
+                  }
+
+                  DevicePublicKeys pks;
+                  pks.ed25519    = device_keys.at(edKey);
+                  pks.curve25519 = device_keys.at(curveKey);
+
+                  try {
+                          if (!mtx::crypto::verify_identity_signature(json(device.begin()->second),
+                                                                      DeviceId(device_id),
+                                                                      UserId(user_id))) {
+                                  nhlog::crypto()->warn("failed to verify identity keys: {}",
+                                                        json(device).dump(2));
+                                  return;
+                          }
+                  } catch (const json::exception &e) {
+                          nhlog::crypto()->warn("failed to parse device key json: {}", e.what());
+                          return;
+                  } catch (const mtx::crypto::olm_exception &e) {
+                          nhlog::crypto()->warn("failed to verify device key json: {}", e.what());
+                          return;
+                  }
+
+                  auto room_key = olm::client()
+                                    ->create_room_key_event(UserId(user_id), pks.ed25519, payload)
+                                    .dump();
+
+                  http::v2::client()->claim_keys(
+                    user_id,
+                    {device_id},
+                    [room_key, user_id, device_id, pks](const mtx::responses::ClaimKeys &res,
+                                                        mtx::http::RequestErr err) {
+                            if (err) {
+                                    nhlog::net()->warn("claim keys error: {} {} {}",
+                                                       err->matrix_error.error,
+                                                       err->parse_error,
+                                                       static_cast<int>(err->status_code));
+                                    return;
+                            }
+
+                            nhlog::net()->info("claimed keys for {}", user_id);
+
+                            if (res.one_time_keys.size() == 0) {
+                                    nhlog::net()->info("no one-time keys found for user_id: {}",
+                                                       user_id);
+                                    return;
+                            }
+
+                            if (res.one_time_keys.find(user_id) == res.one_time_keys.end()) {
+                                    nhlog::net()->info("no one-time keys found for user_id: {}",
+                                                       user_id);
+                                    return;
+                            }
+
+                            auto retrieved_devices = res.one_time_keys.at(user_id);
+                            if (retrieved_devices.empty()) {
+                                    nhlog::net()->info("claiming keys for {}: no retrieved devices",
+                                                       device_id);
+                                    return;
+                            }
+
+                            json body;
+                            body["messages"][user_id] = json::object();
+
+                            auto device = retrieved_devices.begin()->second;
+                            nhlog::net()->info("{} : \n {}", device_id, device.dump(2));
+
+                            json device_msg;
+
+                            try {
+                                    auto olm_session = olm::client()->create_outbound_session(
+                                      pks.curve25519, device.begin()->at("key"));
+
+                                    auto device_msg = olm::client()->create_olm_encrypted_content(
+                                      olm_session.get(), room_key, pks.curve25519);
+
+                                    cache::client()->saveOlmSession(pks.curve25519,
+                                                                    std::move(olm_session));
+                            } catch (const json::exception &e) {
+                                    nhlog::crypto()->warn("creating outbound session: {}",
+                                                          e.what());
+                                    return;
+                            } catch (const mtx::crypto::olm_exception &e) {
+                                    nhlog::crypto()->warn("creating outbound session: {}",
+                                                          e.what());
+                                    return;
+                            }
+
+                            body["messages"][user_id][device_id] = device_msg;
+
+                            nhlog::net()->info("send_to_device: {}", user_id);
+                            http::v2::client()->send_to_device(
+                              "m.room.encrypted", body, [user_id](mtx::http::RequestErr err) {
+                                      if (err) {
+                                              nhlog::net()->warn("failed to send "
+                                                                 "send_to_device "
+                                                                 "message: {}",
+                                                                 err->matrix_error.error);
+                                      }
+
+                                      nhlog::net()->info("m.room_key send to {}", user_id);
+                              });
+                    });
+          });
+}
+
 } // namespace olm
diff --git a/src/timeline/TimelineItem.cc b/src/timeline/TimelineItem.cc
index 7fc54962..274c1d43 100644
--- a/src/timeline/TimelineItem.cc
+++ b/src/timeline/TimelineItem.cc
@@ -24,6 +24,7 @@
 #include "ChatPage.h"
 #include "Config.h"
 #include "Logging.hpp"
+#include "Olm.hpp"
 #include "Painter.h"
 
 #include "timeline/TimelineItem.h"
@@ -683,6 +684,19 @@ TimelineItem::addReplyAction()
 }
 
 void
+TimelineItem::addKeyRequestAction()
+{
+        if (contextMenu_) {
+                auto requestKeys = new QAction("Request encryption keys", this);
+                contextMenu_->addAction(requestKeys);
+
+                connect(requestKeys, &QAction::triggered, this, [this]() {
+                        olm::request_keys(room_id_.toStdString(), event_id_.toStdString());
+                });
+        }
+}
+
+void
 TimelineItem::addAvatar()
 {
         if (userAvatar_)
diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc
index 35140df0..ee7b9a86 100644
--- a/src/timeline/TimelineView.cc
+++ b/src/timeline/TimelineView.cc
@@ -261,6 +261,8 @@ TimelineView::parseMessageEvent(const mtx::events::collections::TimelineEvents &
 
                 if (item && res.isDecrypted)
                         item->markReceived(true);
+                else if (item && !res.isDecrypted)
+                        item->addKeyRequestAction();
 
                 return widget;
         }