summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--include/Cache.h8
-rw-r--r--include/Olm.hpp7
-rw-r--r--include/timeline/TimelineView.h1
-rw-r--r--src/Cache.cc65
-rw-r--r--src/Olm.cpp27
-rw-r--r--src/timeline/TimelineView.cc249
6 files changed, 345 insertions, 12 deletions
diff --git a/include/Cache.h b/include/Cache.h
index 76266ebd..b4dcdb90 100644
--- a/include/Cache.h
+++ b/include/Cache.h
@@ -286,6 +286,9 @@ public:
         bool isFormatValid();
         void setCurrentFormat();
 
+        //! Retrieve all the user ids from a room.
+        std::vector<std::string> roomMembers(const std::string &room_id);
+
         //! Check if the given user has power leve greater than than
         //! lowest power level of the given events.
         bool hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
@@ -358,8 +361,9 @@ public:
         void saveOutboundMegolmSession(const std::string &room_id,
                                        const OutboundGroupSessionData &data,
                                        mtx::crypto::OutboundGroupSessionPtr session);
-        OutboundGroupSessionDataRef getOutboundMegolmSession(const MegolmSessionIndex &index);
-        bool outboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept;
+        OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
+        bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
+        void updateOutboundMegolmSession(const std::string &room_id, int message_index);
 
         //
         // Inbound Megolm Sessions
diff --git a/include/Olm.hpp b/include/Olm.hpp
index 2f7b1d64..0839f01c 100644
--- a/include/Olm.hpp
+++ b/include/Olm.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <memory>
+#include <mtx.hpp>
 #include <mtxclient/crypto/client.hpp>
 
 constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2";
@@ -62,4 +63,10 @@ void
 handle_pre_key_olm_message(const std::string &sender,
                            const std::string &sender_key,
                            const OlmCipherContent &content);
+
+mtx::events::msg::Encrypted
+encrypt_group_message(const std::string &room_id,
+                      const std::string &device_id,
+                      const std::string &body);
+
 } // namespace olm
diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h
index 2c369d5f..5b5c2292 100644
--- a/include/timeline/TimelineView.h
+++ b/include/timeline/TimelineView.h
@@ -185,6 +185,7 @@ private:
         void sendRoomMessageHandler(const std::string &txn_id,
                                     const mtx::responses::EventId &res,
                                     mtx::http::RequestErr err);
+        void prepareEncryptedMessage(const PendingMessage &msg);
 
         //! Call the /messages endpoint to fill the timeline.
         void getMessages();
diff --git a/src/Cache.cc b/src/Cache.cc
index 35ad8f9d..7c678b72 100644
--- a/src/Cache.cc
+++ b/src/Cache.cc
@@ -251,6 +251,36 @@ Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept
 }
 
 void
+Cache::updateOutboundMegolmSession(const std::string &room_id, int message_index)
+{
+        using namespace mtx::crypto;
+
+        if (!outboundMegolmSessionExists(room_id))
+                return;
+
+        OutboundGroupSessionData data;
+        OlmOutboundGroupSession *session;
+        {
+                std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
+                data    = session_storage.group_outbound_session_data[room_id];
+                session = session_storage.group_outbound_sessions[room_id].get();
+
+                // Update with the current message.
+                data.message_index                                   = message_index;
+                session_storage.group_outbound_session_data[room_id] = data;
+        }
+
+        // Save the updated pickled data for the session.
+        json j;
+        j["data"]    = data;
+        j["session"] = pickle<OutboundSessionObject>(session, SECRET);
+
+        auto txn = lmdb::txn::begin(env_);
+        lmdb::dbi_put(txn, outboundMegolmSessionDb_, lmdb::val(room_id), lmdb::val(j.dump()));
+        txn.commit();
+}
+
+void
 Cache::saveOutboundMegolmSession(const std::string &room_id,
                                  const OutboundGroupSessionData &data,
                                  mtx::crypto::OutboundGroupSessionPtr session)
@@ -274,24 +304,21 @@ Cache::saveOutboundMegolmSession(const std::string &room_id,
 }
 
 bool
-Cache::outboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept
+Cache::outboundMegolmSessionExists(const std::string &room_id) noexcept
 {
-        const auto key = index.to_hash();
-
         std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
-        return (session_storage.group_outbound_sessions.find(key) !=
+        return (session_storage.group_outbound_sessions.find(room_id) !=
                 session_storage.group_outbound_sessions.end()) &&
-               (session_storage.group_outbound_session_data.find(key) !=
+               (session_storage.group_outbound_session_data.find(room_id) !=
                 session_storage.group_outbound_session_data.end());
 }
 
 OutboundGroupSessionDataRef
-Cache::getOutboundMegolmSession(const MegolmSessionIndex &index)
+Cache::getOutboundMegolmSession(const std::string &room_id)
 {
-        const auto key = index.to_hash();
         std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
-        return OutboundGroupSessionDataRef{session_storage.group_outbound_sessions[key].get(),
-                                           session_storage.group_outbound_session_data[key]};
+        return OutboundGroupSessionDataRef{session_storage.group_outbound_sessions[room_id].get(),
+                                           session_storage.group_outbound_session_data[room_id]};
 }
 
 void
@@ -1537,6 +1564,26 @@ Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes
         return user_level >= min_event_level;
 }
 
+std::vector<std::string>
+Cache::roomMembers(const std::string &room_id)
+{
+        auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+
+        std::vector<std::string> members;
+        std::string user_id, unused;
+
+        auto db = getMembersDb(txn, room_id);
+
+        auto cursor = lmdb::cursor::open(txn, db);
+        while (cursor.get(user_id, unused, MDB_NEXT))
+                members.emplace_back(std::move(user_id));
+        cursor.close();
+
+        txn.commit();
+
+        return members;
+}
+
 QHash<QString, QString> Cache::DisplayNames;
 QHash<QString, QString> Cache::AvatarUrls;
 
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 769b0234..6e130277 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -136,4 +136,31 @@ handle_olm_normal_message(const std::string &, const std::string &, const OlmCip
         log::crypto()->warn("olm(1) not implemeted yet");
 }
 
+mtx::events::msg::Encrypted
+encrypt_group_message(const std::string &room_id,
+                      const std::string &device_id,
+                      const std::string &body)
+{
+        using namespace mtx::events;
+
+        // Always chech before for existence.
+        auto res     = cache::client()->getOutboundMegolmSession(room_id);
+        auto payload = olm::client()->encrypt_group_message(res.session, body);
+
+        // 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 = res.data.session_id;
+        data.device_id  = device_id;
+
+        auto message_index = olm_outbound_group_session_message_index(res.session);
+        log::crypto()->info("next message_index {}", message_index);
+
+        // We need to re-pickle the session after we send a message to save the new message_index.
+        cache::client()->updateOutboundMegolmSession(room_id, message_index);
+
+        return data;
+}
+
 } // namespace olm
diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc
index 67da5dc6..9276a7bc 100644
--- a/src/timeline/TimelineView.cc
+++ b/src/timeline/TimelineView.cc
@@ -34,6 +34,19 @@
 #include "timeline/widgets/ImageItem.h"
 #include "timeline/widgets/VideoItem.h"
 
+class StateKeeper
+{
+public:
+        StateKeeper(std::function<void()> &&fn)
+          : fn_(std::move(fn))
+        {}
+
+        ~StateKeeper() { fn_(); }
+
+private:
+        std::function<void()> fn_;
+};
+
 using TimelineEvent = mtx::events::collections::TimelineEvents;
 
 DateSeparator::DateSeparator(QDateTime datetime, QWidget *parent)
@@ -329,6 +342,7 @@ TimelineView::parseEncryptedEvent(const mtx::events::EncryptedEvent<mtx::events:
         body["event_id"]         = e.event_id;
         body["sender"]           = e.sender;
         body["origin_server_ts"] = e.origin_server_ts;
+        body["unsigned"]         = e.unsigned_data;
 
         log::crypto()->info("decrypted data: \n {}", body.dump(2));
 
@@ -665,7 +679,7 @@ TimelineView::sendNextPendingMessage()
         log::main()->info("[{}] sending next queued message", m.txn_id);
 
         if (m.is_encrypted) {
-                // sendEncryptedMessage(m);
+                prepareEncryptedMessage(std::move(m));
                 log::main()->info("[{}] sending encrypted event", m.txn_id);
                 return;
         }
@@ -1124,3 +1138,236 @@ toRoomMessage<mtx::events::msg::Text>(const PendingMessage &m)
         text.body = m.body.toStdString();
         return text;
 }
+
+void
+TimelineView::prepareEncryptedMessage(const PendingMessage &msg)
+{
+        const auto room_id = room_id_.toStdString();
+
+        using namespace mtx::events;
+        using namespace mtx::identifiers;
+
+        json content;
+
+        // Serialize the message to the plaintext that will be encrypted.
+        switch (msg.ty) {
+        case MessageType::Audio: {
+                content = json(toRoomMessage<msg::Audio>(msg));
+                break;
+        }
+        case MessageType::Emote: {
+                content = json(toRoomMessage<msg::Emote>(msg));
+                break;
+        }
+        case MessageType::File: {
+                content = json(toRoomMessage<msg::File>(msg));
+                break;
+        }
+        case MessageType::Image: {
+                content = json(toRoomMessage<msg::Image>(msg));
+                break;
+        }
+        case MessageType::Text: {
+                content = json(toRoomMessage<msg::Text>(msg));
+                break;
+        }
+        case MessageType::Video: {
+                content = json(toRoomMessage<msg::Video>(msg));
+                break;
+        }
+        default:
+                break;
+        }
+
+        json doc{{"type", "m.room.message"}, {"content", content}, {"room_id", room_id}};
+
+        try {
+                // Check if we have already an outbound megolm session then we can use.
+                if (cache::client()->outboundMegolmSessionExists(room_id)) {
+                        auto data = olm::encrypt_group_message(
+                          room_id, http::v2::client()->device_id(), doc.dump());
+
+                        http::v2::client()
+                          ->send_room_message<msg::Encrypted, EventType::RoomEncrypted>(
+                            room_id,
+                            msg.txn_id,
+                            data,
+                            std::bind(&TimelineView::sendRoomMessageHandler,
+                                      this,
+                                      msg.txn_id,
+                                      std::placeholders::_1,
+                                      std::placeholders::_2));
+                        return;
+                }
+
+                log::main()->info("creating new outbound megolm session");
+
+                // Create a new outbound megolm session.
+                auto outbound_session  = olm::client()->init_outbound_group_session();
+                const auto session_id  = mtx::crypto::session_id(outbound_session.get());
+                const auto session_key = mtx::crypto::session_key(outbound_session.get());
+
+                // TODO: needs to be moved in the lib.
+                auto megolm_payload = json{{"algorithm", "m.megolm.v1.aes-sha2"},
+                                           {"room_id", room_id},
+                                           {"session_id", session_id},
+                                           {"session_key", session_key}};
+
+                // Saving the new megolm session.
+                // TODO: Maybe it's too early to save.
+                OutboundGroupSessionData session_data;
+                session_data.session_id    = session_id;
+                session_data.session_key   = session_key;
+                session_data.message_index = 0; // TODO Update me
+                cache::client()->saveOutboundMegolmSession(
+                  room_id, session_data, std::move(outbound_session));
+
+                const auto members = cache::client()->roomMembers(room_id);
+                log::main()->info("retrieved {} members for {}", members.size(), room_id);
+
+                auto keeper = std::make_shared<StateKeeper>(
+                  [megolm_payload, room_id, doc, txn_id = msg.txn_id, this]() {
+                          try {
+                                  auto data = olm::encrypt_group_message(
+                                    room_id, http::v2::client()->device_id(), doc.dump());
+
+                                  http::v2::client()
+                                    ->send_room_message<msg::Encrypted, EventType::RoomEncrypted>(
+                                      room_id,
+                                      txn_id,
+                                      data,
+                                      std::bind(&TimelineView::sendRoomMessageHandler,
+                                                this,
+                                                txn_id,
+                                                std::placeholders::_1,
+                                                std::placeholders::_2));
+
+                          } catch (const lmdb::error &e) {
+                                  log::db()->critical("failed to save megolm outbound session: {}",
+                                                      e.what());
+                          }
+                  });
+
+                mtx::requests::QueryKeys req;
+                for (const auto &member : members)
+                        req.device_keys[member] = {};
+
+                http::v2::client()->query_keys(
+                  req,
+                  [keeper = std::move(keeper), megolm_payload](const mtx::responses::QueryKeys &res,
+                                                               mtx::http::RequestErr err) {
+                          if (err) {
+                                  log::net()->warn("failed to query device keys: {} {}",
+                                                   err->matrix_error.error,
+                                                   static_cast<int>(err->status_code));
+                                  // TODO: Mark the event as failed. Communicate with the UI.
+                                  return;
+                          }
+
+                          for (const auto &entry : res.device_keys) {
+                                  for (const auto &dev : entry.second) {
+                                          log::net()->info("received device {}", dev.first);
+
+                                          const auto device_keys = dev.second.keys;
+                                          const auto curveKey    = "curve25519:" + dev.first;
+                                          const auto edKey       = "ed25519:" + dev.first;
+
+                                          if ((device_keys.find(curveKey) == device_keys.end()) ||
+                                              (device_keys.find(edKey) == device_keys.end())) {
+                                                  log::net()->info(
+                                                    "ignoring malformed keys for device {}",
+                                                    dev.first);
+                                                  continue;
+                                          }
+
+                                          DevicePublicKeys pks;
+                                          pks.ed25519    = device_keys.at(edKey);
+                                          pks.curve25519 = device_keys.at(curveKey);
+
+                                          // Validate signatures
+                                          for (const auto &algo : dev.second.keys) {
+                                                  log::net()->info(
+                                                    "dev keys {} {}", algo.first, algo.second);
+                                          }
+
+                                          auto room_key =
+                                            olm::client()
+                                              ->create_room_key_event(UserId(dev.second.user_id),
+                                                                      pks.ed25519,
+                                                                      megolm_payload)
+                                              .dump();
+
+                                          http::v2::client()->claim_keys(
+                                            dev.second.user_id,
+                                            {dev.second.device_id},
+                                            [keeper,
+                                             room_key,
+                                             pks,
+                                             user_id   = dev.second.user_id,
+                                             device_id = dev.second.device_id](
+                                              const mtx::responses::ClaimKeys &res,
+                                              mtx::http::RequestErr err) {
+                                                    if (err) {
+                                                            log::net()->warn(
+                                                              "claim keys error: {}",
+                                                              err->matrix_error.error);
+                                                            return;
+                                                    }
+
+                                                    log::net()->info("claimed keys for {} - {}",
+                                                                     user_id,
+                                                                     device_id);
+
+                                                    auto retrieved_devices =
+                                                      res.one_time_keys.at(user_id);
+                                                    for (const auto &rd : retrieved_devices) {
+                                                            log::net()->info("{} : \n {}",
+                                                                             rd.first,
+                                                                             rd.second.dump(2));
+
+                                                            // TODO: Verify signatures
+                                                            auto otk = rd.second.begin()->at("key");
+                                                            auto id_key = pks.curve25519;
+
+                                                            auto session =
+                                                              olm::client()
+                                                                ->create_outbound_session(id_key,
+                                                                                          otk);
+
+                                                            auto device_msg =
+                                                              olm::client()
+                                                                ->create_olm_encrypted_content(
+                                                                  session.get(),
+                                                                  room_key,
+                                                                  pks.curve25519);
+
+                                                            json body{
+                                                              {"messages",
+                                                               {{user_id,
+                                                                 {{device_id, device_msg}}}}}};
+
+                                                            http::v2::client()->send_to_device(
+                                                              "m.room.encrypted",
+                                                              body,
+                                                              [keeper](mtx::http::RequestErr err) {
+                                                                      if (err) {
+                                                                              log::net()->warn(
+                                                                                "failed to send "
+                                                                                "send_to_device "
+                                                                                "message: {}",
+                                                                                err->matrix_error
+                                                                                  .error);
+                                                                      }
+                                                              });
+                                                    }
+                                            });
+                                  }
+                          }
+                  });
+
+        } catch (const lmdb::error &e) {
+                log::db()->critical(
+                  "failed to open outbound megolm session ({}): {}", room_id, e.what());
+                return;
+        }
+}