summary refs log tree commit diff
path: root/src/Olm.cpp
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2020-11-30 00:26:27 +0100
committerNicolas Werner <nicolas.werner@hotmail.de>2020-11-30 01:54:53 +0100
commit2ce129e6b61d18ac4a2e79702d3c1fd1302c6631 (patch)
tree4cab52d78c428bbebc9c854c2019e7b0fc3b71d4 /src/Olm.cpp
parentRemove outbound session storage (diff)
downloadnheko-2ce129e6b61d18ac4a2e79702d3c1fd1302c6631.tar.xz
Properly share and rotate sessions on member and device changes
Diffstat (limited to 'src/Olm.cpp')
-rw-r--r--src/Olm.cpp183
1 files changed, 175 insertions, 8 deletions
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 88e67159..c2200703 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -278,11 +278,168 @@ mtx::events::msg::Encrypted
 encrypt_group_message(const std::string &room_id, const std::string &device_id, nlohmann::json body)
 {
         using namespace mtx::events;
+        using namespace mtx::identifiers;
+
+        auto own_user_id = http::client()->user_id().to_string();
+
+        auto members = cache::client()->getMembersWithKeys(room_id);
+
+        std::map<std::string, std::vector<std::string>> sendSessionTo;
+        mtx::crypto::OutboundGroupSessionPtr session = nullptr;
+        OutboundGroupSessionData group_session_data;
+
+        if (cache::outboundMegolmSessionExists(room_id)) {
+                auto res = cache::getOutboundMegolmSession(room_id);
+
+                auto member_it             = members.begin();
+                auto session_member_it     = res.data.currently.keys.begin();
+                auto session_member_it_end = res.data.currently.keys.end();
+
+                while (member_it != members.end() || session_member_it != session_member_it_end) {
+                        if (member_it == members.end()) {
+                                // a member left, purge session!
+                                nhlog::crypto()->debug(
+                                  "Rotating megolm session because of left member");
+                                break;
+                        }
+
+                        if (session_member_it == session_member_it_end) {
+                                // share with all remaining members
+                                while (member_it != members.end()) {
+                                        sendSessionTo[member_it->first] = {};
+
+                                        if (member_it->second)
+                                                for (const auto &dev :
+                                                     member_it->second->device_keys)
+                                                        if (member_it->first != own_user_id ||
+                                                            dev.first != device_id)
+                                                                sendSessionTo[member_it->first]
+                                                                  .push_back(dev.first);
+
+                                        ++member_it;
+                                }
+
+                                session = std::move(res.session);
+                                break;
+                        }
+
+                        if (member_it->first > session_member_it->first) {
+                                // a member left, purge session
+                                nhlog::crypto()->debug(
+                                  "Rotating megolm session because of left member");
+                                break;
+                        } else if (member_it->first < session_member_it->first) {
+                                // new member, send them the session at this index
+                                sendSessionTo[member_it->first] = {};
+
+                                for (const auto &dev : member_it->second->device_keys)
+                                        if (member_it->first != own_user_id ||
+                                            dev.first != device_id)
+                                                sendSessionTo[member_it->first].push_back(
+                                                  dev.first);
+
+                                ++member_it;
+                        } else {
+                                // compare devices
+                                bool device_removed = false;
+                                for (const auto &dev : session_member_it->second.devices) {
+                                        if (!member_it->second ||
+                                            !member_it->second->device_keys.count(dev.first)) {
+                                                device_removed = true;
+                                                break;
+                                        }
+                                }
+
+                                if (device_removed) {
+                                        // device removed, rotate session!
+                                        nhlog::crypto()->debug(
+                                          "Rotating megolm session because of removed device of {}",
+                                          member_it->first);
+                                        break;
+                                }
+
+                                // check for new devices to share with
+                                if (member_it->second)
+                                        for (const auto &dev : member_it->second->device_keys)
+                                                if (!session_member_it->second.devices.count(
+                                                      dev.first) &&
+                                                    (member_it->first != own_user_id ||
+                                                     dev.first != device_id))
+                                                        sendSessionTo[member_it->first].push_back(
+                                                          dev.first);
+
+                                ++member_it;
+                                ++session_member_it;
+                                if (member_it == members.end() &&
+                                    session_member_it == session_member_it_end) {
+                                        // all devices match or are newly added
+                                        session = std::move(res.session);
+                                }
+                        }
+                }
+
+                group_session_data = std::move(res.data);
+        }
+
+        if (!session) {
+                nhlog::ui()->debug("creating new outbound megolm session");
+
+                // Create a new outbound megolm session.
+                session                = olm::client()->init_outbound_group_session();
+                const auto session_id  = mtx::crypto::session_id(session.get());
+                const auto session_key = mtx::crypto::session_key(session.get());
+
+                // Saving the new megolm session.
+                OutboundGroupSessionData session_data{};
+                session_data.session_id    = mtx::crypto::session_id(session.get());
+                session_data.session_key   = mtx::crypto::session_key(session.get());
+                session_data.message_index = 0;
+
+                sendSessionTo.clear();
+
+                for (const auto &[user, devices] : members) {
+                        sendSessionTo[user]               = {};
+                        session_data.initially.keys[user] = {};
+                        if (devices) {
+                                for (const auto &[device_id_, key] : devices->device_keys) {
+                                        (void)key;
+                                        if (device_id != device_id_ || user != own_user_id) {
+                                                sendSessionTo[user].push_back(device_id_);
+                                                session_data.initially.keys[user]
+                                                  .devices[device_id_] = 0;
+                                        }
+                                }
+                        }
+                }
+
+                cache::saveOutboundMegolmSession(room_id, session_data, session);
+                group_session_data = std::move(session_data);
+
+                {
+                        MegolmSessionIndex index;
+                        index.room_id    = room_id;
+                        index.session_id = session_id;
+                        index.sender_key = olm::client()->identity_keys().curve25519;
+                        auto megolm_session =
+                          olm::client()->init_inbound_group_session(session_key);
+                        cache::saveInboundMegolmSession(index, std::move(megolm_session));
+                }
+        }
+
+        mtx::events::DeviceEvent<mtx::events::msg::RoomKey> megolm_payload{};
+        megolm_payload.content.algorithm   = MEGOLM_ALGO;
+        megolm_payload.content.room_id     = room_id;
+        megolm_payload.content.session_id  = mtx::crypto::session_id(session.get());
+        megolm_payload.content.session_key = mtx::crypto::session_key(session.get());
+        megolm_payload.type                = mtx::events::EventType::RoomKey;
+
+        if (!sendSessionTo.empty())
+                olm::send_encrypted_to_device_messages(sendSessionTo, megolm_payload);
 
-        // relations shouldn't be encrypted...
         mtx::common::ReplyRelatesTo relation;
         mtx::common::RelatesTo r_relation;
 
+        // relations shouldn't be encrypted...
         if (body["content"].contains("m.relates_to") &&
             body["content"]["m.relates_to"].contains("m.in_reply_to")) {
                 relation = body["content"]["m.relates_to"];
@@ -292,25 +449,35 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
                 body["content"].erase("m.relates_to");
         }
 
-        // Always check before for existence.
-        auto res     = cache::getOutboundMegolmSession(room_id);
-        auto payload = olm::client()->encrypt_group_message(res.session.get(), body.dump());
+        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(res.session.get());
+        data.session_id   = mtx::crypto::session_id(session.get());
         data.device_id    = device_id;
         data.algorithm    = MEGOLM_ALGO;
         data.relates_to   = relation;
         data.r_relates_to = r_relation;
 
-        res.data.message_index = olm_outbound_group_session_message_index(res.session.get());
-        nhlog::crypto()->debug("next message_index {}", res.data.message_index);
+        group_session_data.message_index = olm_outbound_group_session_message_index(session.get());
+        nhlog::crypto()->debug("next message_index {}", group_session_data.message_index);
+
+        // update current set of members for the session with the new members and that message_index
+        for (const auto &[user, devices] : sendSessionTo) {
+                if (!group_session_data.currently.keys.count(user))
+                        group_session_data.currently.keys[user] = {};
+
+                for (const auto &device_id : devices) {
+                        if (!group_session_data.currently.keys[user].devices.count(device_id))
+                                group_session_data.currently.keys[user].devices[device_id] =
+                                  group_session_data.message_index;
+                }
+        }
 
         // We need to re-pickle the session after we send a message to save the new message_index.
-        cache::updateOutboundMegolmSession(room_id, res.session);
+        cache::updateOutboundMegolmSession(room_id, group_session_data, session);
 
         return data;
 }