summary refs log tree commit diff
path: root/src/Olm.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Olm.cpp')
-rw-r--r--src/Olm.cpp275
1 files changed, 273 insertions, 2 deletions
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