Rotate session keys properly

This commit is contained in:
Nicolas Werner 2021-03-15 16:24:01 +01:00
parent 61c5dffffd
commit 569ea5b5f4
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
6 changed files with 121 additions and 70 deletions

View file

@ -358,7 +358,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare( FetchContent_Declare(
MatrixClient MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
GIT_TAG b1b1ef9ad088f9666582f46d81b75a80c6b2b1c0 GIT_TAG 7194b4f058406b1c10d3741d83abcf2d8963d849
) )
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")

View file

@ -220,7 +220,7 @@
"name": "mtxclient", "name": "mtxclient",
"sources": [ "sources": [
{ {
"commit": "b1b1ef9ad088f9666582f46d81b75a80c6b2b1c0", "commit": "7194b4f058406b1c10d3741d83abcf2d8963d849",
"type": "git", "type": "git",
"url": "https://github.com/Nheko-Reborn/mtxclient.git" "url": "https://github.com/Nheko-Reborn/mtxclient.git"
} }

View file

@ -261,6 +261,36 @@ Cache::isRoomEncrypted(const std::string &room_id)
return res; return res;
} }
std::optional<mtx::events::state::Encryption>
Cache::roomEncryptionSettings(const std::string &room_id)
{
using namespace mtx::events;
using namespace mtx::events::state;
try {
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
auto statesdb = getStatesDb(txn, room_id);
std::string_view event;
bool res =
statesdb.get(txn, to_string(mtx::events::EventType::RoomEncryption), event);
if (res) {
try {
StateEvent<Encryption> msg = json::parse(event);
return msg.content;
} catch (const json::exception &e) {
nhlog::db()->warn("failed to parse m.room.encryption event: {}",
e.what());
return Encryption{};
}
}
} catch (lmdb::error &) {
}
return std::nullopt;
}
mtx::crypto::ExportedSessionKeys mtx::crypto::ExportedSessionKeys
Cache::exportSessionKeys() Cache::exportSessionKeys()
{ {
@ -3893,6 +3923,7 @@ to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg)
obj["session_id"] = msg.session_id; obj["session_id"] = msg.session_id;
obj["session_key"] = msg.session_key; obj["session_key"] = msg.session_key;
obj["message_index"] = msg.message_index; obj["message_index"] = msg.message_index;
obj["ts"] = msg.timestamp;
obj["initially"] = msg.initially; obj["initially"] = msg.initially;
obj["currently"] = msg.currently; obj["currently"] = msg.currently;
@ -3904,6 +3935,7 @@ from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg)
msg.session_id = obj.at("session_id"); msg.session_id = obj.at("session_id");
msg.session_key = obj.at("session_key"); 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.initially = obj.value("initially", SharedWithUsers{}); msg.initially = obj.value("initially", SharedWithUsers{});
msg.currently = obj.value("currently", SharedWithUsers{}); msg.currently = obj.value("currently", SharedWithUsers{});

View file

@ -28,6 +28,7 @@ struct OutboundGroupSessionData
std::string session_id; std::string session_id;
std::string session_key; std::string session_key;
uint64_t message_index = 0; uint64_t message_index = 0;
uint64_t timestamp = 0;
// 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.

View file

@ -221,6 +221,8 @@ public:
//! Mark a room that uses e2e encryption. //! Mark a room that uses e2e encryption.
void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id); void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
bool isRoomEncrypted(const std::string &room_id); bool isRoomEncrypted(const std::string &room_id);
std::optional<mtx::events::state::Encryption> roomEncryptionSettings(
const std::string &room_id);
//! Check if a user is a member of the room. //! Check if a user is a member of the room.
bool isRoomMember(const std::string &user_id, const std::string &room_id); bool isRoomMember(const std::string &user_id, const std::string &room_id);

View file

@ -431,92 +431,107 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
if (cache::outboundMegolmSessionExists(room_id)) { if (cache::outboundMegolmSessionExists(room_id)) {
auto res = cache::getOutboundMegolmSession(room_id); auto res = cache::getOutboundMegolmSession(room_id);
auto encryptionSettings = cache::client()->roomEncryptionSettings(room_id);
mtx::events::state::Encryption defaultSettings;
auto member_it = members.begin(); // rotate if we crossed the limits for this key
auto session_member_it = res.data.currently.keys.begin(); if (res.data.message_index <
auto session_member_it_end = res.data.currently.keys.end(); encryptionSettings.value_or(defaultSettings).rotation_period_msgs &&
(QDateTime::currentMSecsSinceEpoch() - res.data.timestamp) <
encryptionSettings.value_or(defaultSettings).rotation_period_ms) {
auto member_it = members.begin();
auto session_member_it = res.data.currently.keys.begin();
auto session_member_it_end = res.data.currently.keys.end();
while (member_it != members.end() || session_member_it != session_member_it_end) { while (member_it != members.end() ||
if (member_it == members.end()) { session_member_it != session_member_it_end) {
// a member left, purge session! if (member_it == members.end()) {
nhlog::crypto()->debug( // a member left, purge session!
"Rotating megolm session because of left member"); nhlog::crypto()->debug(
break; "Rotating megolm session because of left member");
} break;
}
if (session_member_it == session_member_it_end) { if (session_member_it == session_member_it_end) {
// share with all remaining members // share with all remaining members
while (member_it != members.end()) { while (member_it != members.end()) {
sendSessionTo[member_it->first] = {};
if (member_it->second)
for (const auto &dev :
member_it->second->device_keys)
if (member_it->first !=
own_user_id ||
dev.first != device_id)
sendSessionTo[member_it
->first]
.push_back(dev.first);
++member_it;
}
session = std::move(res.session);
break;
}
if (member_it->first > session_member_it->first) {
// a member left, purge session
nhlog::crypto()->debug(
"Rotating megolm session because of left member");
break;
} else if (member_it->first < session_member_it->first) {
// new member, send them the session at this index
sendSessionTo[member_it->first] = {}; sendSessionTo[member_it->first] = {};
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 (member_it->first != own_user_id || if (member_it->first != own_user_id ||
dev.first != device_id) dev.first != device_id)
sendSessionTo[member_it->first] sendSessionTo[member_it->first]
.push_back(dev.first); .push_back(dev.first);
}
++member_it; ++member_it;
} } else {
// compare devices
bool device_removed = false;
for (const auto &dev : session_member_it->second.devices) {
if (!member_it->second ||
!member_it->second->device_keys.count(
dev.first)) {
device_removed = true;
break;
}
}
session = std::move(res.session); if (device_removed) {
break; // device removed, rotate session!
} nhlog::crypto()->debug(
"Rotating megolm session because of removed "
if (member_it->first > session_member_it->first) { "device of {}",
// a member left, purge session member_it->first);
nhlog::crypto()->debug(
"Rotating megolm session because of left member");
break;
} else if (member_it->first < session_member_it->first) {
// new member, send them the session at this index
sendSessionTo[member_it->first] = {};
if (member_it->second) {
for (const auto &dev : member_it->second->device_keys)
if (member_it->first != own_user_id ||
dev.first != device_id)
sendSessionTo[member_it->first].push_back(
dev.first);
}
++member_it;
} else {
// compare devices
bool device_removed = false;
for (const auto &dev : session_member_it->second.devices) {
if (!member_it->second ||
!member_it->second->device_keys.count(dev.first)) {
device_removed = true;
break; break;
} }
}
if (device_removed) { // check for new devices to share with
// device removed, rotate session! if (member_it->second)
nhlog::crypto()->debug( for (const auto &dev :
"Rotating megolm session because of removed device of {}", member_it->second->device_keys)
member_it->first); if (!session_member_it->second.devices
break; .count(dev.first) &&
} (member_it->first != own_user_id ||
dev.first != device_id))
sendSessionTo[member_it->first]
.push_back(dev.first);
// check for new devices to share with ++member_it;
if (member_it->second) ++session_member_it;
for (const auto &dev : member_it->second->device_keys) if (member_it == members.end() &&
if (!session_member_it->second.devices.count( session_member_it == session_member_it_end) {
dev.first) && // all devices match or are newly added
(member_it->first != own_user_id || session = std::move(res.session);
dev.first != device_id)) }
sendSessionTo[member_it->first].push_back(
dev.first);
++member_it;
++session_member_it;
if (member_it == members.end() &&
session_member_it == session_member_it_end) {
// all devices match or are newly added
session = std::move(res.session);
} }
} }
} }
@ -537,6 +552,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
session_data.session_id = mtx::crypto::session_id(session.get()); session_data.session_id = mtx::crypto::session_id(session.get());
session_data.session_key = mtx::crypto::session_key(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();
sendSessionTo.clear(); sendSessionTo.clear();