summary refs log tree commit diff
path: root/src/timeline
diff options
context:
space:
mode:
authorKonstantinos Sideris <sideris.konstantin@gmail.com>2018-06-13 12:28:00 +0300
committerKonstantinos Sideris <sideris.konstantin@gmail.com>2018-06-13 12:28:00 +0300
commit5d47cc3940076f6e7da4578d985253c869117f21 (patch)
treef851f52a57561787c2ba856d5d2d864825eea215 /src/timeline
parentAdd method to convert PendingMessage's to event types (diff)
downloadnheko-5d47cc3940076f6e7da4578d985253c869117f21.tar.xz
Add support for sending encrypted messages
Diffstat (limited to 'src/timeline')
-rw-r--r--src/timeline/TimelineView.cc249
1 files changed, 248 insertions, 1 deletions
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; + } +}