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
|