Store megolm session data in separate database

This commit is contained in:
Nicolas Werner 2021-07-17 01:27:37 +02:00
parent 0b864d9485
commit 9fadd14871
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
7 changed files with 210 additions and 86 deletions

View file

@ -78,6 +78,8 @@ constexpr auto ENCRYPTED_ROOMS_DB("encrypted_rooms");
constexpr auto INBOUND_MEGOLM_SESSIONS_DB("inbound_megolm_sessions"); constexpr auto INBOUND_MEGOLM_SESSIONS_DB("inbound_megolm_sessions");
//! MegolmSessionIndex -> pickled OlmOutboundGroupSession //! MegolmSessionIndex -> pickled OlmOutboundGroupSession
constexpr auto OUTBOUND_MEGOLM_SESSIONS_DB("outbound_megolm_sessions"); constexpr auto OUTBOUND_MEGOLM_SESSIONS_DB("outbound_megolm_sessions");
//! MegolmSessionIndex -> session data about which devices have access to this
constexpr auto MEGOLM_SESSIONS_DATA_DB("megolm_sessions_data_db");
using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>; using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>; using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
@ -284,6 +286,7 @@ Cache::setup()
// Session management // Session management
inboundMegolmSessionDb_ = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); inboundMegolmSessionDb_ = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE);
txn.commit(); txn.commit();
@ -387,9 +390,14 @@ Cache::importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys)
index.session_id = s.session_id; index.session_id = s.session_id;
index.sender_key = s.sender_key; index.sender_key = s.sender_key;
GroupSessionData data{};
data.forwarding_curve25519_key_chain = s.forwarding_curve25519_key_chain;
if (s.sender_claimed_keys.count("ed25519"))
data.sender_claimed_ed25519_key = s.sender_claimed_keys.at("ed25519");
auto exported_session = mtx::crypto::import_session(s.session_key); auto exported_session = mtx::crypto::import_session(s.session_key);
saveInboundMegolmSession(index, std::move(exported_session)); saveInboundMegolmSession(index, std::move(exported_session), data);
ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id);
} }
} }
@ -400,7 +408,8 @@ Cache::importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys)
void void
Cache::saveInboundMegolmSession(const MegolmSessionIndex &index, Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
mtx::crypto::InboundGroupSessionPtr session) mtx::crypto::InboundGroupSessionPtr session,
const GroupSessionData &data)
{ {
using namespace mtx::crypto; using namespace mtx::crypto;
const auto key = json(index).dump(); const auto key = json(index).dump();
@ -420,6 +429,7 @@ Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
} }
inboundMegolmSessionDb_.put(txn, key, pickled); inboundMegolmSessionDb_.put(txn, key, pickled);
megolmSessionDataDb_.put(txn, key, json(data).dump());
txn.commit(); txn.commit();
} }
@ -464,7 +474,7 @@ Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index)
void void
Cache::updateOutboundMegolmSession(const std::string &room_id, Cache::updateOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data_, const GroupSessionData &data_,
mtx::crypto::OutboundGroupSessionPtr &ptr) mtx::crypto::OutboundGroupSessionPtr &ptr)
{ {
using namespace mtx::crypto; using namespace mtx::crypto;
@ -472,18 +482,20 @@ Cache::updateOutboundMegolmSession(const std::string &room_id,
if (!outboundMegolmSessionExists(room_id)) if (!outboundMegolmSessionExists(room_id))
return; return;
OutboundGroupSessionData data = data_; GroupSessionData data = data_;
data.message_index = olm_outbound_group_session_message_index(ptr.get()); data.message_index = olm_outbound_group_session_message_index(ptr.get());
data.session_id = mtx::crypto::session_id(ptr.get()); MegolmSessionIndex index;
data.session_key = mtx::crypto::session_key(ptr.get()); index.room_id = room_id;
index.sender_key = olm::client()->identity_keys().ed25519;
index.session_id = mtx::crypto::session_id(ptr.get());
// Save the updated pickled data for the session. // Save the updated pickled data for the session.
json j; json j;
j["data"] = data;
j["session"] = pickle<OutboundSessionObject>(ptr.get(), SECRET); j["session"] = pickle<OutboundSessionObject>(ptr.get(), SECRET);
auto txn = lmdb::txn::begin(env_); auto txn = lmdb::txn::begin(env_);
outboundMegolmSessionDb_.put(txn, room_id, j.dump()); outboundMegolmSessionDb_.put(txn, room_id, j.dump());
megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump());
txn.commit(); txn.commit();
} }
@ -498,24 +510,32 @@ Cache::dropOutboundMegolmSession(const std::string &room_id)
{ {
auto txn = lmdb::txn::begin(env_); auto txn = lmdb::txn::begin(env_);
outboundMegolmSessionDb_.del(txn, room_id); outboundMegolmSessionDb_.del(txn, room_id);
// don't delete session data, so that we can still share the session.
txn.commit(); txn.commit();
} }
} }
void void
Cache::saveOutboundMegolmSession(const std::string &room_id, Cache::saveOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data, const GroupSessionData &data_,
mtx::crypto::OutboundGroupSessionPtr &session) mtx::crypto::OutboundGroupSessionPtr &session)
{ {
using namespace mtx::crypto; using namespace mtx::crypto;
const auto pickled = pickle<OutboundSessionObject>(session.get(), SECRET); const auto pickled = pickle<OutboundSessionObject>(session.get(), SECRET);
GroupSessionData data = data_;
data.message_index = olm_outbound_group_session_message_index(session.get());
MegolmSessionIndex index;
index.room_id = room_id;
index.sender_key = olm::client()->identity_keys().ed25519;
index.session_id = mtx::crypto::session_id(session.get());
json j; json j;
j["data"] = data;
j["session"] = pickled; j["session"] = pickled;
auto txn = lmdb::txn::begin(env_); auto txn = lmdb::txn::begin(env_);
outboundMegolmSessionDb_.put(txn, room_id, j.dump()); outboundMegolmSessionDb_.put(txn, room_id, j.dump());
megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump());
txn.commit(); txn.commit();
} }
@ -544,8 +564,17 @@ Cache::getOutboundMegolmSession(const std::string &room_id)
auto obj = json::parse(value); auto obj = json::parse(value);
OutboundGroupSessionDataRef ref{}; OutboundGroupSessionDataRef ref{};
ref.data = obj.at("data").get<OutboundGroupSessionData>();
ref.session = unpickle<OutboundSessionObject>(obj.at("session"), SECRET); ref.session = unpickle<OutboundSessionObject>(obj.at("session"), SECRET);
MegolmSessionIndex index;
index.room_id = room_id;
index.sender_key = olm::client()->identity_keys().ed25519;
index.session_id = mtx::crypto::session_id(ref.session.get());
if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) {
ref.data = nlohmann::json::parse(value).get<GroupSessionData>();
}
return ref; return ref;
} catch (std::exception &e) { } catch (std::exception &e) {
nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what()); nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what());
@ -829,6 +858,7 @@ Cache::deleteData()
lmdb::dbi_close(env_, inboundMegolmSessionDb_); lmdb::dbi_close(env_, inboundMegolmSessionDb_);
lmdb::dbi_close(env_, outboundMegolmSessionDb_); lmdb::dbi_close(env_, outboundMegolmSessionDb_);
lmdb::dbi_close(env_, megolmSessionDataDb_);
env_.close(); env_.close();
@ -3525,6 +3555,7 @@ to_json(json &j, const UserKeyCache &info)
{ {
j["device_keys"] = info.device_keys; j["device_keys"] = info.device_keys;
j["seen_device_keys"] = info.seen_device_keys; j["seen_device_keys"] = info.seen_device_keys;
j["seen_device_ids"] = info.seen_device_ids;
j["master_keys"] = info.master_keys; j["master_keys"] = info.master_keys;
j["master_key_changed"] = info.master_key_changed; j["master_key_changed"] = info.master_key_changed;
j["user_signing_keys"] = info.user_signing_keys; j["user_signing_keys"] = info.user_signing_keys;
@ -3538,6 +3569,7 @@ from_json(const json &j, UserKeyCache &info)
{ {
info.device_keys = j.value("device_keys", std::map<std::string, mtx::crypto::DeviceKeys>{}); info.device_keys = j.value("device_keys", std::map<std::string, mtx::crypto::DeviceKeys>{});
info.seen_device_keys = j.value("seen_device_keys", std::set<std::string>{}); info.seen_device_keys = j.value("seen_device_keys", std::set<std::string>{});
info.seen_device_ids = j.value("seen_device_ids", std::set<std::string>{});
info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{}); info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{});
info.master_key_changed = j.value("master_key_changed", false); info.master_key_changed = j.value("master_key_changed", false);
info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{}); info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{});
@ -3634,6 +3666,15 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
keyReused = true; keyReused = true;
break; break;
} }
if (updateToWrite.seen_device_ids.count(
device_id)) {
nhlog::crypto()->warn(
"device_id '{}' reused by ({})",
device_id,
user);
keyReused = true;
break;
}
} }
if (!keyReused && !oldDeviceKeys.count(device_id)) if (!keyReused && !oldDeviceKeys.count(device_id))
@ -3644,6 +3685,7 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
(void)key_id; (void)key_id;
updateToWrite.seen_device_keys.insert(key); updateToWrite.seen_device_keys.insert(key);
} }
updateToWrite.seen_device_ids.insert(device_id);
} }
} }
db.put(txn, user, json(updateToWrite).dump()); db.put(txn, user, json(updateToWrite).dump());
@ -4077,17 +4119,15 @@ from_json(const json &j, MemberInfo &info)
} }
void void
to_json(nlohmann::json &obj, const DeviceAndMasterKeys &msg) to_json(nlohmann::json &obj, const DeviceKeysToMsgIndex &msg)
{ {
obj["devices"] = msg.devices; obj["deviceids"] = msg.deviceids;
obj["master_keys"] = msg.master_keys;
} }
void void
from_json(const nlohmann::json &obj, DeviceAndMasterKeys &msg) from_json(const nlohmann::json &obj, DeviceKeysToMsgIndex &msg)
{ {
msg.devices = obj.at("devices").get<decltype(msg.devices)>(); msg.deviceids = obj.at("deviceids").get<decltype(msg.deviceids)>();
msg.master_keys = obj.at("master_keys").get<decltype(msg.master_keys)>();
} }
void void
@ -4099,30 +4139,31 @@ to_json(nlohmann::json &obj, const SharedWithUsers &msg)
void void
from_json(const nlohmann::json &obj, SharedWithUsers &msg) from_json(const nlohmann::json &obj, SharedWithUsers &msg)
{ {
msg.keys = obj.at("keys").get<std::map<std::string, DeviceAndMasterKeys>>(); msg.keys = obj.at("keys").get<std::map<std::string, DeviceKeysToMsgIndex>>();
} }
void void
to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg) to_json(nlohmann::json &obj, const GroupSessionData &msg)
{ {
obj["session_id"] = msg.session_id;
obj["session_key"] = msg.session_key;
obj["message_index"] = msg.message_index; obj["message_index"] = msg.message_index;
obj["ts"] = msg.timestamp; obj["ts"] = msg.timestamp;
obj["initially"] = msg.initially; obj["sender_claimed_ed25519_key"] = msg.sender_claimed_ed25519_key;
obj["forwarding_curve25519_key_chain"] = msg.forwarding_curve25519_key_chain;
obj["currently"] = msg.currently; obj["currently"] = msg.currently;
} }
void void
from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg) from_json(const nlohmann::json &obj, GroupSessionData &msg)
{ {
msg.session_id = obj.at("session_id");
msg.session_key = obj.at("session_key");
msg.message_index = obj.at("message_index"); msg.message_index = obj.at("message_index");
msg.timestamp = obj.value("ts", 0ULL); msg.timestamp = obj.value("ts", 0ULL);
msg.initially = obj.value("initially", SharedWithUsers{}); msg.sender_claimed_ed25519_key = obj.value("sender_claimed_ed25519_key", "");
msg.forwarding_curve25519_key_chain =
obj.value("forwarding_curve25519_key_chain", std::vector<std::string>{});
msg.currently = obj.value("currently", SharedWithUsers{}); msg.currently = obj.value("currently", SharedWithUsers{});
} }
@ -4522,7 +4563,7 @@ isRoomMember(const std::string &user_id, const std::string &room_id)
// //
void void
saveOutboundMegolmSession(const std::string &room_id, saveOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data, const GroupSessionData &data,
mtx::crypto::OutboundGroupSessionPtr &session) mtx::crypto::OutboundGroupSessionPtr &session)
{ {
instance_->saveOutboundMegolmSession(room_id, data, session); instance_->saveOutboundMegolmSession(room_id, data, session);
@ -4539,7 +4580,7 @@ outboundMegolmSessionExists(const std::string &room_id) noexcept
} }
void void
updateOutboundMegolmSession(const std::string &room_id, updateOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data, const GroupSessionData &data,
mtx::crypto::OutboundGroupSessionPtr &session) mtx::crypto::OutboundGroupSessionPtr &session)
{ {
instance_->updateOutboundMegolmSession(room_id, data, session); instance_->updateOutboundMegolmSession(room_id, data, session);
@ -4566,9 +4607,10 @@ exportSessionKeys()
// //
void void
saveInboundMegolmSession(const MegolmSessionIndex &index, saveInboundMegolmSession(const MegolmSessionIndex &index,
mtx::crypto::InboundGroupSessionPtr session) mtx::crypto::InboundGroupSessionPtr session,
const GroupSessionData &data)
{ {
instance_->saveInboundMegolmSession(index, std::move(session)); instance_->saveInboundMegolmSession(index, std::move(session), data);
} }
mtx::crypto::InboundGroupSessionPtr mtx::crypto::InboundGroupSessionPtr
getInboundMegolmSession(const MegolmSessionIndex &index) getInboundMegolmSession(const MegolmSessionIndex &index)

View file

@ -200,7 +200,7 @@ isRoomMember(const std::string &user_id, const std::string &room_id);
// //
void void
saveOutboundMegolmSession(const std::string &room_id, saveOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data, const GroupSessionData &data,
mtx::crypto::OutboundGroupSessionPtr &session); mtx::crypto::OutboundGroupSessionPtr &session);
OutboundGroupSessionDataRef OutboundGroupSessionDataRef
getOutboundMegolmSession(const std::string &room_id); getOutboundMegolmSession(const std::string &room_id);
@ -208,7 +208,7 @@ bool
outboundMegolmSessionExists(const std::string &room_id) noexcept; outboundMegolmSessionExists(const std::string &room_id) noexcept;
void void
updateOutboundMegolmSession(const std::string &room_id, updateOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data, const GroupSessionData &data,
mtx::crypto::OutboundGroupSessionPtr &session); mtx::crypto::OutboundGroupSessionPtr &session);
void void
dropOutboundMegolmSession(const std::string &room_id); dropOutboundMegolmSession(const std::string &room_id);
@ -223,7 +223,8 @@ exportSessionKeys();
// //
void void
saveInboundMegolmSession(const MegolmSessionIndex &index, saveInboundMegolmSession(const MegolmSessionIndex &index,
mtx::crypto::InboundGroupSessionPtr session); mtx::crypto::InboundGroupSessionPtr session,
const GroupSessionData &data);
mtx::crypto::InboundGroupSessionPtr mtx::crypto::InboundGroupSessionPtr
getInboundMegolmSession(const MegolmSessionIndex &index); getInboundMegolmSession(const MegolmSessionIndex &index);
bool bool

View file

@ -27,40 +27,43 @@ enum Trust
Q_ENUM_NS(Trust) Q_ENUM_NS(Trust)
} }
struct DeviceAndMasterKeys struct DeviceKeysToMsgIndex
{ {
// map from device id or master key id to message_index // map from device key to message_index
std::map<std::string, uint64_t> devices, master_keys; // Using the device id is safe because we check for reuse on device list updates
// Using the device id makes our logic much easier to read.
std::map<std::string, uint64_t> deviceids;
}; };
struct SharedWithUsers struct SharedWithUsers
{ {
// userid to keys // userid to keys
std::map<std::string, DeviceAndMasterKeys> keys; std::map<std::string, DeviceKeysToMsgIndex> keys;
}; };
// Extra information associated with an outbound megolm session. // Extra information associated with an outbound megolm session.
struct OutboundGroupSessionData struct GroupSessionData
{ {
std::string session_id;
std::string session_key;
uint64_t message_index = 0; uint64_t message_index = 0;
uint64_t timestamp = 0; uint64_t timestamp = 0;
std::string sender_claimed_ed25519_key;
std::vector<std::string> forwarding_curve25519_key_chain;
// who has access to this session. // who has access to this session.
// Rotate, when a user leaves the room and share, when a user gets added. // Rotate, when a user leaves the room and share, when a user gets added.
SharedWithUsers initially, currently; SharedWithUsers currently;
}; };
void void
to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg); to_json(nlohmann::json &obj, const GroupSessionData &msg);
void void
from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg); from_json(const nlohmann::json &obj, GroupSessionData &msg);
struct OutboundGroupSessionDataRef struct OutboundGroupSessionDataRef
{ {
mtx::crypto::OutboundGroupSessionPtr session; mtx::crypto::OutboundGroupSessionPtr session;
OutboundGroupSessionData data; GroupSessionData data;
}; };
struct DevicePublicKeys struct DevicePublicKeys
@ -134,6 +137,8 @@ struct UserKeyCache
bool master_key_changed = false; bool master_key_changed = false;
//! Device keys that were already used at least once //! Device keys that were already used at least once
std::set<std::string> seen_device_keys; std::set<std::string> seen_device_keys;
//! Device ids that were already used at least once
std::set<std::string> seen_device_ids;
}; };
void void

View file

@ -238,12 +238,12 @@ public:
// Outbound Megolm Sessions // Outbound Megolm Sessions
// //
void saveOutboundMegolmSession(const std::string &room_id, void saveOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data, const GroupSessionData &data,
mtx::crypto::OutboundGroupSessionPtr &session); mtx::crypto::OutboundGroupSessionPtr &session);
OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id); OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
bool outboundMegolmSessionExists(const std::string &room_id) noexcept; bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
void updateOutboundMegolmSession(const std::string &room_id, void updateOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data, const GroupSessionData &data,
mtx::crypto::OutboundGroupSessionPtr &session); mtx::crypto::OutboundGroupSessionPtr &session);
void dropOutboundMegolmSession(const std::string &room_id); void dropOutboundMegolmSession(const std::string &room_id);
@ -254,7 +254,8 @@ public:
// Inbound Megolm Sessions // Inbound Megolm Sessions
// //
void saveInboundMegolmSession(const MegolmSessionIndex &index, void saveInboundMegolmSession(const MegolmSessionIndex &index,
mtx::crypto::InboundGroupSessionPtr session); mtx::crypto::InboundGroupSessionPtr session,
const GroupSessionData &data);
mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession( mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession(
const MegolmSessionIndex &index); const MegolmSessionIndex &index);
bool inboundMegolmSessionExists(const MegolmSessionIndex &index); bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
@ -676,6 +677,7 @@ private:
lmdb::dbi inboundMegolmSessionDb_; lmdb::dbi inboundMegolmSessionDb_;
lmdb::dbi outboundMegolmSessionDb_; lmdb::dbi outboundMegolmSessionDb_;
lmdb::dbi megolmSessionDataDb_;
QString localUserId_; QString localUserId_;
QString cacheDirectory_; QString cacheDirectory_;

View file

@ -939,12 +939,16 @@ ChatPage::ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &counts)
[](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) { [](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) {
if (err) { if (err) {
nhlog::crypto()->warn( nhlog::crypto()->warn(
"failed to update one-time keys: {} {}", "failed to update one-time keys: {} {} {}",
err->matrix_error.error, err->matrix_error.error,
static_cast<int>(err->status_code)); static_cast<int>(err->status_code),
return; static_cast<int>(err->error_code));
if (err->status_code < 400 || err->status_code >= 500)
return;
} }
// mark as published anyway, otherwise we may end up in a loop.
olm::mark_keys_as_published(); olm::mark_keys_as_published();
}); });
} }

View file

@ -123,7 +123,17 @@ handle_to_device_messages(const std::vector<mtx::events::collections::DeviceEven
if (msg_type == to_string(mtx::events::EventType::RoomEncrypted)) { if (msg_type == to_string(mtx::events::EventType::RoomEncrypted)) {
try { try {
olm::OlmMessage olm_msg = j_msg; olm::OlmMessage olm_msg = j_msg;
handle_olm_message(std::move(olm_msg)); cache::client()->query_keys(
olm_msg.sender,
[olm_msg](const UserKeyCache &userKeys, mtx::http::RequestErr e) {
if (e) {
nhlog::crypto()->error(
"Failed to query user keys, dropping olm "
"message");
return;
}
handle_olm_message(std::move(olm_msg), userKeys);
});
} catch (const nlohmann::json::exception &e) { } catch (const nlohmann::json::exception &e) {
nhlog::crypto()->warn( nhlog::crypto()->warn(
"parsing error for olm message: {} {}", e.what(), j_msg.dump(2)); "parsing error for olm message: {} {}", e.what(), j_msg.dump(2));
@ -197,7 +207,7 @@ handle_to_device_messages(const std::vector<mtx::events::collections::DeviceEven
} }
void void
handle_olm_message(const OlmMessage &msg) handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKeys)
{ {
nhlog::crypto()->info("sender : {}", msg.sender); nhlog::crypto()->info("sender : {}", msg.sender);
nhlog::crypto()->info("sender_key: {}", msg.sender_key); nhlog::crypto()->info("sender_key: {}", msg.sender_key);
@ -209,7 +219,7 @@ handle_olm_message(const OlmMessage &msg)
if (cipher.first != my_key) { if (cipher.first != my_key) {
nhlog::crypto()->debug( nhlog::crypto()->debug(
"Skipping message for {} since we are {}.", cipher.first, my_key); "Skipping message for {} since we are {}.", cipher.first, my_key);
continue; return;
} }
const auto type = cipher.second.type; const auto type = cipher.second.type;
@ -231,6 +241,57 @@ handle_olm_message(const OlmMessage &msg)
if (!payload.is_null()) { if (!payload.is_null()) {
mtx::events::collections::DeviceEvents device_event; mtx::events::collections::DeviceEvents device_event;
// Other properties are included in order to prevent an attacker from
// publishing someone else's curve25519 keys as their own and subsequently
// claiming to have sent messages which they didn't. sender must correspond
// to the user who sent the event, recipient to the local user, and
// recipient_keys to the local ed25519 key.
std::string receiver_ed25519 = payload["recipient_keys"]["ed25519"];
if (receiver_ed25519.empty() ||
receiver_ed25519 != olm::client()->identity_keys().ed25519) {
nhlog::crypto()->warn(
"Decrypted event doesn't include our ed25519: {}",
payload.dump());
return;
}
std::string receiver = payload["recipient"];
if (receiver.empty() || receiver != http::client()->user_id().to_string()) {
nhlog::crypto()->warn(
"Decrypted event doesn't include our user_id: {}",
payload.dump());
return;
}
// Clients must confirm that the sender_key and the ed25519 field value
// under the keys property match the keys returned by /keys/query for the
// given user, and must also verify the signature of the payload. Without
// this check, a client cannot be sure that the sender device owns the
// private part of the ed25519 key it claims to have in the Olm payload.
// This is crucial when the ed25519 key corresponds to a verified device.
std::string sender_ed25519 = payload["keys"]["ed25519"];
if (sender_ed25519.empty()) {
nhlog::crypto()->warn(
"Decrypted event doesn't include sender ed25519: {}",
payload.dump());
return;
}
bool from_their_device = false;
for (auto [device_id, key] : otherUserDeviceKeys.device_keys) {
if (key.keys.at("curve25519:" + device_id) == msg.sender_key) {
if (key.keys.at("ed25519:" + device_id) == sender_ed25519) {
from_their_device = true;
break;
}
}
}
if (!from_their_device) {
nhlog::crypto()->warn("Decrypted event isn't sent from a device "
"listed by that user! {}",
payload.dump());
return;
}
{ {
std::string msg_type = payload["type"]; std::string msg_type = payload["type"];
json event_array = json::array(); json event_array = json::array();
@ -242,7 +303,7 @@ handle_olm_message(const OlmMessage &msg)
if (temp_events.empty()) { if (temp_events.empty()) {
nhlog::crypto()->warn("Decrypted unknown event: {}", nhlog::crypto()->warn("Decrypted unknown event: {}",
payload.dump()); payload.dump());
continue; return;
} }
device_event = temp_events.at(0); device_event = temp_events.at(0);
} }
@ -276,17 +337,20 @@ handle_olm_message(const OlmMessage &msg)
ChatPage::instance()->receivedDeviceVerificationDone(e8->content); ChatPage::instance()->receivedDeviceVerificationDone(e8->content);
} else if (auto roomKey = } else if (auto roomKey =
std::get_if<DeviceEvent<msg::RoomKey>>(&device_event)) { std::get_if<DeviceEvent<msg::RoomKey>>(&device_event)) {
create_inbound_megolm_session(*roomKey, msg.sender_key); create_inbound_megolm_session(
*roomKey, msg.sender_key, sender_ed25519);
} else if (auto forwardedRoomKey = } else if (auto forwardedRoomKey =
std::get_if<DeviceEvent<msg::ForwardedRoomKey>>( std::get_if<DeviceEvent<msg::ForwardedRoomKey>>(
&device_event)) { &device_event)) {
forwardedRoomKey->content.forwarding_curve25519_key_chain.push_back(
msg.sender_key);
import_inbound_megolm_session(*forwardedRoomKey); import_inbound_megolm_session(*forwardedRoomKey);
} else if (auto e = } else if (auto e =
std::get_if<DeviceEvent<msg::SecretSend>>(&device_event)) { std::get_if<DeviceEvent<msg::SecretSend>>(&device_event)) {
auto local_user = http::client()->user_id(); auto local_user = http::client()->user_id();
if (msg.sender != local_user.to_string()) if (msg.sender != local_user.to_string())
continue; return;
auto secret_name = auto secret_name =
request_id_to_secret_name.find(e->content.request_id); request_id_to_secret_name.find(e->content.request_id);
@ -306,7 +370,7 @@ handle_olm_message(const OlmMessage &msg)
cache::verificationStatus(local_user.to_string()); cache::verificationStatus(local_user.to_string());
if (!verificationStatus) if (!verificationStatus)
continue; return;
auto deviceKeys = cache::userKeys(local_user.to_string()); auto deviceKeys = cache::userKeys(local_user.to_string());
std::string sender_device_id; std::string sender_device_id;
@ -344,7 +408,6 @@ handle_olm_message(const OlmMessage &msg)
"for secrect " "for secrect "
"'{}'", "'{}'",
name); name);
return;
} }
}); });
@ -364,13 +427,8 @@ handle_olm_message(const OlmMessage &msg)
} }
try { try {
auto otherUserDeviceKeys = cache::userKeys(msg.sender);
if (!otherUserDeviceKeys)
return;
std::map<std::string, std::vector<std::string>> targets; std::map<std::string, std::vector<std::string>> targets;
for (auto [device_id, key] : otherUserDeviceKeys->device_keys) { for (auto [device_id, key] : otherUserDeviceKeys.device_keys) {
if (key.keys.at("curve25519:" + device_id) == msg.sender_key) if (key.keys.at("curve25519:" + device_id) == msg.sender_key)
targets[msg.sender].push_back(device_id); targets[msg.sender].push_back(device_id);
} }
@ -450,7 +508,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
std::map<std::string, std::vector<std::string>> sendSessionTo; std::map<std::string, std::vector<std::string>> sendSessionTo;
mtx::crypto::OutboundGroupSessionPtr session = nullptr; mtx::crypto::OutboundGroupSessionPtr session = nullptr;
OutboundGroupSessionData group_session_data; GroupSessionData group_session_data;
if (cache::outboundMegolmSessionExists(room_id)) { if (cache::outboundMegolmSessionExists(room_id)) {
auto res = cache::getOutboundMegolmSession(room_id); auto res = cache::getOutboundMegolmSession(room_id);
@ -519,7 +577,8 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
} else { } else {
// compare devices // compare devices
bool device_removed = false; bool device_removed = false;
for (const auto &dev : session_member_it->second.devices) { for (const auto &dev :
session_member_it->second.deviceids) {
if (!member_it->second || if (!member_it->second ||
!member_it->second->device_keys.count( !member_it->second->device_keys.count(
dev.first)) { dev.first)) {
@ -541,7 +600,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
if (member_it->second) if (member_it->second)
for (const auto &dev : for (const auto &dev :
member_it->second->device_keys) member_it->second->device_keys)
if (!session_member_it->second.devices if (!session_member_it->second.deviceids
.count(dev.first) && .count(dev.first) &&
(member_it->first != own_user_id || (member_it->first != own_user_id ||
dev.first != device_id)) dev.first != device_id))
@ -571,9 +630,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
const auto session_key = mtx::crypto::session_key(session.get()); const auto session_key = mtx::crypto::session_key(session.get());
// Saving the new megolm session. // Saving the new megolm session.
OutboundGroupSessionData session_data{}; GroupSessionData session_data{};
session_data.session_id = mtx::crypto::session_id(session.get());
session_data.session_key = mtx::crypto::session_key(session.get());
session_data.message_index = 0; session_data.message_index = 0;
session_data.timestamp = QDateTime::currentMSecsSinceEpoch(); session_data.timestamp = QDateTime::currentMSecsSinceEpoch();
@ -581,22 +638,19 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
for (const auto &[user, devices] : members) { for (const auto &[user, devices] : members) {
sendSessionTo[user] = {}; sendSessionTo[user] = {};
session_data.initially.keys[user] = {}; session_data.currently.keys[user] = {};
if (devices) { if (devices) {
for (const auto &[device_id_, key] : devices->device_keys) { for (const auto &[device_id_, key] : devices->device_keys) {
(void)key; (void)key;
if (device_id != device_id_ || user != own_user_id) { if (device_id != device_id_ || user != own_user_id) {
sendSessionTo[user].push_back(device_id_); sendSessionTo[user].push_back(device_id_);
session_data.initially.keys[user] session_data.currently.keys[user]
.devices[device_id_] = 0; .deviceids[device_id_] = 0;
} }
} }
} }
} }
cache::saveOutboundMegolmSession(room_id, session_data, session);
group_session_data = std::move(session_data);
{ {
MegolmSessionIndex index; MegolmSessionIndex index;
index.room_id = room_id; index.room_id = room_id;
@ -604,8 +658,12 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
index.sender_key = olm::client()->identity_keys().curve25519; index.sender_key = olm::client()->identity_keys().curve25519;
auto megolm_session = auto megolm_session =
olm::client()->init_inbound_group_session(session_key); olm::client()->init_inbound_group_session(session_key);
cache::saveInboundMegolmSession(index, std::move(megolm_session)); cache::saveInboundMegolmSession(
index, std::move(megolm_session), session_data);
} }
cache::saveOutboundMegolmSession(room_id, session_data, session);
group_session_data = std::move(session_data);
} }
mtx::events::DeviceEvent<mtx::events::msg::RoomKey> megolm_payload{}; mtx::events::DeviceEvent<mtx::events::msg::RoomKey> megolm_payload{};
@ -641,8 +699,8 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
group_session_data.currently.keys[user] = {}; group_session_data.currently.keys[user] = {};
for (const auto &device_id_ : devices) { for (const auto &device_id_ : devices) {
if (!group_session_data.currently.keys[user].devices.count(device_id_)) if (!group_session_data.currently.keys[user].deviceids.count(device_id_))
group_session_data.currently.keys[user].devices[device_id_] = group_session_data.currently.keys[user].deviceids[device_id_] =
group_session_data.message_index; group_session_data.message_index;
} }
} }
@ -704,7 +762,8 @@ try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCip
void void
create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::RoomKey> &roomKey, create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::RoomKey> &roomKey,
const std::string &sender_key) const std::string &sender_key,
const std::string &sender_ed25519)
{ {
MegolmSessionIndex index; MegolmSessionIndex index;
index.room_id = roomKey.content.room_id; index.room_id = roomKey.content.room_id;
@ -712,9 +771,13 @@ create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::R
index.sender_key = sender_key; index.sender_key = sender_key;
try { try {
GroupSessionData data{};
data.forwarding_curve25519_key_chain = {sender_key};
data.sender_claimed_ed25519_key = sender_ed25519;
auto megolm_session = auto megolm_session =
olm::client()->init_inbound_group_session(roomKey.content.session_key); olm::client()->init_inbound_group_session(roomKey.content.session_key);
cache::saveInboundMegolmSession(index, std::move(megolm_session)); cache::saveInboundMegolmSession(index, std::move(megolm_session), data);
} catch (const lmdb::error &e) { } catch (const lmdb::error &e) {
nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what());
return; return;
@ -741,7 +804,13 @@ import_inbound_megolm_session(
try { try {
auto megolm_session = auto megolm_session =
olm::client()->import_inbound_group_session(roomKey.content.session_key); olm::client()->import_inbound_group_session(roomKey.content.session_key);
cache::saveInboundMegolmSession(index, std::move(megolm_session));
GroupSessionData data{};
data.forwarding_curve25519_key_chain =
roomKey.content.forwarding_curve25519_key_chain;
data.sender_claimed_ed25519_key = roomKey.content.sender_claimed_ed25519_key;
cache::saveInboundMegolmSession(index, std::move(megolm_session), data);
} catch (const lmdb::error &e) { } catch (const lmdb::error &e) {
nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what());
return; return;
@ -875,10 +944,10 @@ handle_key_request_message(const mtx::events::DeviceEvent<mtx::events::msg::KeyR
uint64_t minimumIndex = -1; uint64_t minimumIndex = -1;
if (outboundSession.data.currently.keys.count(req.sender)) { if (outboundSession.data.currently.keys.count(req.sender)) {
if (outboundSession.data.currently.keys.at(req.sender) if (outboundSession.data.currently.keys.at(req.sender)
.devices.count(req.content.requesting_device_id)) { .deviceids.count(req.content.requesting_device_id)) {
shouldSeeKeys = true; shouldSeeKeys = true;
minimumIndex = outboundSession.data.currently.keys.at(req.sender) minimumIndex = outboundSession.data.currently.keys.at(req.sender)
.devices.at(req.content.requesting_device_id); .deviceids.at(req.content.requesting_device_id);
} }
} }

View file

@ -59,12 +59,13 @@ try_olm_decryption(const std::string &sender_key,
const mtx::events::msg::OlmCipherContent &content); const mtx::events::msg::OlmCipherContent &content);
void void
handle_olm_message(const OlmMessage &msg); handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKeys);
//! Establish a new inbound megolm session with the decrypted payload from olm. //! Establish a new inbound megolm session with the decrypted payload from olm.
void void
create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::RoomKey> &roomKey, create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::RoomKey> &roomKey,
const std::string &sender_key); const std::string &sender_key,
const std::string &sender_ed25519);
void void
import_inbound_megolm_session( import_inbound_megolm_session(
const mtx::events::DeviceEvent<mtx::events::msg::ForwardedRoomKey> &roomKey); const mtx::events::DeviceEvent<mtx::events::msg::ForwardedRoomKey> &roomKey);