From 278eccc04086f3033c71a753f0fc0ab35a3cbb66 Mon Sep 17 00:00:00 2001 From: Konstantinos Sideris Date: Sat, 7 Jul 2018 23:59:23 +0300 Subject: [PATCH] Add context menu action to request encryption keys --- deps/CMakeLists.txt | 4 +- include/Olm.hpp | 16 ++ include/timeline/TimelineItem.h | 1 + src/Cache.cc | 10 +- src/Olm.cpp | 275 +++++++++++++++++++++++++++++++- src/timeline/TimelineItem.cc | 14 ++ src/timeline/TimelineView.cc | 2 + 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 &); + +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(); } 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 &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>(res)) { + nhlog::net()->info( + "retrieved event is not encrypted: {} from {}", event_id, room_id); + return; + } + + olm::send_key_request_for(room_id, + mpark::get>(res)); + }); +} + +void +send_key_request_for(const std::string &room_id, + const mtx::events::EncryptedEvent &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(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(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" @@ -682,6 +683,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() { 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; }