Add context menu action to request encryption keys

This commit is contained in:
Konstantinos Sideris 2018-07-07 23:59:23 +03:00
parent 9a0e18dea7
commit 278eccc040
7 changed files with 312 additions and 10 deletions

4
deps/CMakeLists.txt vendored
View file

@ -39,10 +39,10 @@ set(BOOST_SHA256
5721818253e6a0989583192f96782c4a98eb6204965316df9f5ad75819225ca9) 5721818253e6a0989583192f96782c4a98eb6204965316df9f5ad75819225ca9)
set(MATRIX_STRUCTS_URL https://github.com/mujx/matrix-structs) 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_URL https://github.com/mujx/mtxclient)
set(MTXCLIENT_TAG 73491268f94ddeb606284836bb5f512d11b0e249) set(MTXCLIENT_TAG 708c8c6772b9bd99d77c5be6bb3ba58643258628)
set(TWEENY_URL https://github.com/mobius3/tweeny) set(TWEENY_URL https://github.com/mobius3/tweeny)
set(TWEENY_TAG b94ce07cfb02a0eb8ac8aaf66137dabdaea857cf) set(TWEENY_TAG b94ce07cfb02a0eb8ac8aaf66137dabdaea857cf)

View file

@ -67,4 +67,20 @@ encrypt_group_message(const std::string &room_id,
void void
mark_keys_as_published(); 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<mtx::events::msg::Encrypted> &);
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 } // namespace olm

View file

@ -241,6 +241,7 @@ public:
//! Add a user avatar for this event. //! Add a user avatar for this event.
void addAvatar(); void addAvatar();
void addKeyRequestAction();
signals: signals:
void eventRedacted(const QString &event_id); void eventRedacted(const QString &event_id);

View file

@ -987,12 +987,11 @@ Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id)
if (obj.count("event") == 0 || obj.count("token") == 0) if (obj.count("event") == 0 || obj.count("token") == 0)
continue; continue;
mtx::events::collections::TimelineEvents event; mtx::events::collections::TimelineEvent event = obj.at("event");
mtx::events::collections::from_json(obj.at("event"), event);
index += 1; index += 1;
timeline.events.push_back(event); timeline.events.push_back(event.data);
timeline.prev_batch = obj.at("token").get<std::string>(); timeline.prev_batch = obj.at("token").get<std::string>();
} }
cursor.close(); cursor.close();
@ -1059,12 +1058,11 @@ Cache::getLastMessageInfo(lmdb::txn &txn, const std::string &room_id)
if (obj.count("event") == 0) if (obj.count("event") == 0)
continue; continue;
mtx::events::collections::TimelineEvents event; mtx::events::collections::TimelineEvent event = obj.at("event");
mtx::events::collections::from_json(obj.at("event"), event);
cursor.close(); cursor.close();
return utils::getMessageDescription( return utils::getMessageDescription(
event, local_user, QString::fromStdString(room_id)); event.data, local_user, QString::fromStdString(room_id));
} }
cursor.close(); cursor.close();

View file

@ -2,6 +2,7 @@
#include "Cache.h" #include "Cache.h"
#include "Logging.hpp" #include "Logging.hpp"
#include "MatrixClient.h"
using namespace mtx::crypto; 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)); "validation error for olm message: {} {}", e.what(), msg.dump(2));
} }
// TODO: Move this event type into matrix-structs } else if (msg_type == to_string(mtx::events::EventType::RoomKeyRequest)) {
} else if (msg_type == "m.room_key_request") {
nhlog::crypto()->warn("handling key request event: {}", msg.dump(2)); 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 { } else {
nhlog::crypto()->warn("unhandled event: {}", msg.dump(2)); 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)); 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 } // namespace olm

View file

@ -24,6 +24,7 @@
#include "ChatPage.h" #include "ChatPage.h"
#include "Config.h" #include "Config.h"
#include "Logging.hpp" #include "Logging.hpp"
#include "Olm.hpp"
#include "Painter.h" #include "Painter.h"
#include "timeline/TimelineItem.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 void
TimelineItem::addAvatar() TimelineItem::addAvatar()
{ {

View file

@ -261,6 +261,8 @@ TimelineView::parseMessageEvent(const mtx::events::collections::TimelineEvents &
if (item && res.isDecrypted) if (item && res.isDecrypted)
item->markReceived(true); item->markReceived(true);
else if (item && !res.isDecrypted)
item->addKeyRequestAction();
return widget; return widget;
} }