mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-29 06:08:48 +03:00
Add context menu action to request encryption keys
This commit is contained in:
parent
9a0e18dea7
commit
278eccc040
7 changed files with 312 additions and 10 deletions
4
deps/CMakeLists.txt
vendored
4
deps/CMakeLists.txt
vendored
|
@ -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)
|
||||
|
|
|
@ -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<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
|
||||
|
|
|
@ -241,6 +241,7 @@ public:
|
|||
|
||||
//! Add a user avatar for this event.
|
||||
void addAvatar();
|
||||
void addKeyRequestAction();
|
||||
|
||||
signals:
|
||||
void eventRedacted(const QString &event_id);
|
||||
|
|
10
src/Cache.cc
10
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<std::string>();
|
||||
}
|
||||
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();
|
||||
|
||||
|
|
275
src/Olm.cpp
275
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
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue