aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/connection.cpp14
-rw-r--r--lib/connection.h8
-rw-r--r--lib/database.cpp21
-rw-r--r--lib/database.h42
-rw-r--r--lib/e2ee/qolmoutboundsession.cpp4
-rw-r--r--lib/e2ee/qolmoutboundsession.h4
-rw-r--r--lib/room.cpp156
7 files changed, 151 insertions, 98 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp
index 8fd2d6cf..1193eb75 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -2187,7 +2187,7 @@ QJsonObject Connection::decryptNotification(const QJsonObject &notification)
return decrypted ? decrypted->fullJson() : QJsonObject();
}
-Database* Connection::database()
+Database* Connection::database() const
{
return d->database;
}
@@ -2271,14 +2271,18 @@ void Connection::createOlmSession(const QString& theirIdentityKey,
d->olmSessions[theirIdentityKey].push_back(std::move(*session));
}
-QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(Room* room)
+QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(
+ const QString& roomId) const
{
- return d->database->loadCurrentOutboundMegolmSession(room->id(), d->picklingMode);
+ return d->database->loadCurrentOutboundMegolmSession(roomId,
+ d->picklingMode);
}
-void Connection::saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data)
+void Connection::saveCurrentOutboundMegolmSession(
+ const QString& roomId, const QOlmOutboundGroupSession& session) const
{
- d->database->saveCurrentOutboundMegolmSession(room->id(), d->picklingMode, data);
+ d->database->saveCurrentOutboundMegolmSession(roomId, d->picklingMode,
+ session);
}
#endif
diff --git a/lib/connection.h b/lib/connection.h
index 43e285c1..a2824744 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -318,16 +318,16 @@ public:
bool isLoggedIn() const;
#ifdef Quotient_E2EE_ENABLED
QOlmAccount* olmAccount() const;
- Database* database();
+ Database* database() const;
UnorderedMap<QString, QOlmInboundGroupSessionPtr> loadRoomMegolmSessions(
const Room* room);
void saveMegolmSession(const Room* room,
const QOlmInboundGroupSession& session);
bool hasOlmSession(const QString& user, const QString& deviceId) const;
- QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(Room* room);
- void saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data);
-
+ QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(
+ const QString& roomId) const;
+ void saveCurrentOutboundMegolmSession(const QString& roomId, const QOlmOutboundGroupSession &session) const;
//This assumes that an olm session with (user, device) exists
std::pair<QOlmMessage::Type, QByteArray> olmEncryptMessage(
diff --git a/lib/database.cpp b/lib/database.cpp
index 0119b35c..193ff54e 100644
--- a/lib/database.cpp
+++ b/lib/database.cpp
@@ -307,20 +307,22 @@ void Database::setOlmSessionLastReceived(const QString& sessionId, const QDateTi
commit();
}
-void Database::saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& session)
+void Database::saveCurrentOutboundMegolmSession(
+ const QString& roomId, const PicklingMode& picklingMode,
+ const QOlmOutboundGroupSession& session)
{
- const auto pickle = session->pickle(picklingMode);
+ const auto pickle = session.pickle(picklingMode);
if (pickle) {
auto deleteQuery = prepareQuery(QStringLiteral("DELETE FROM outbound_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId;"));
deleteQuery.bindValue(":roomId", roomId);
- deleteQuery.bindValue(":sessionId", session->sessionId());
+ deleteQuery.bindValue(":sessionId", session.sessionId());
auto insertQuery = prepareQuery(QStringLiteral("INSERT INTO outbound_megolm_sessions(roomId, sessionId, pickle, creationTime, messageCount) VALUES(:roomId, :sessionId, :pickle, :creationTime, :messageCount);"));
insertQuery.bindValue(":roomId", roomId);
- insertQuery.bindValue(":sessionId", session->sessionId());
+ insertQuery.bindValue(":sessionId", session.sessionId());
insertQuery.bindValue(":pickle", pickle.value());
- insertQuery.bindValue(":creationTime", session->creationTime());
- insertQuery.bindValue(":messageCount", session->messageCount());
+ insertQuery.bindValue(":creationTime", session.creationTime());
+ insertQuery.bindValue(":messageCount", session.messageCount());
transaction();
execute(deleteQuery);
@@ -362,7 +364,9 @@ void Database::setDevicesReceivedKey(const QString& roomId, const QVector<std::t
commit();
}
-QHash<QString, QStringList> Database::devicesWithoutKey(const QString& roomId, QHash<QString, QStringList>& devices, const QString &sessionId)
+QMultiHash<QString, QString> Database::devicesWithoutKey(
+ const QString& roomId, QMultiHash<QString, QString> devices,
+ const QString& sessionId)
{
auto query = prepareQuery(QStringLiteral("SELECT userId, deviceId FROM sent_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId"));
query.bindValue(":roomId", roomId);
@@ -371,7 +375,8 @@ QHash<QString, QStringList> Database::devicesWithoutKey(const QString& roomId, Q
execute(query);
commit();
while (query.next()) {
- devices[query.value("userId").toString()].removeAll(query.value("deviceId").toString());
+ devices.remove(query.value("userId").toString(),
+ query.value("deviceId").toString());
}
return devices;
}
diff --git a/lib/database.h b/lib/database.h
index 45348c8d..4091d61b 100644
--- a/lib/database.h
+++ b/lib/database.h
@@ -32,22 +32,40 @@ public:
QByteArray accountPickle();
void setAccountPickle(const QByteArray &pickle);
void clear();
- void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle, const QDateTime& timestamp);
- UnorderedMap<QString, std::vector<QOlmSessionPtr>> loadOlmSessions(const PicklingMode& picklingMode);
- UnorderedMap<QString, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode);
- void saveMegolmSession(const QString& roomId, const QString& sessionId, const QByteArray& pickle, const QString& senderId, const QString& olmSessionId);
- void addGroupSessionIndexRecord(const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts);
- std::pair<QString, qint64> groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index);
+ void saveOlmSession(const QString& senderKey, const QString& sessionId,
+ const QByteArray& pickle, const QDateTime& timestamp);
+ UnorderedMap<QString, std::vector<QOlmSessionPtr>> loadOlmSessions(
+ const PicklingMode& picklingMode);
+ UnorderedMap<QString, QOlmInboundGroupSessionPtr> loadMegolmSessions(
+ const QString& roomId, const PicklingMode& picklingMode);
+ void saveMegolmSession(const QString& roomId, const QString& sessionId,
+ const QByteArray& pickle, const QString& senderId,
+ const QString& olmSessionId);
+ void addGroupSessionIndexRecord(const QString& roomId,
+ const QString& sessionId, uint32_t index,
+ const QString& eventId, qint64 ts);
+ std::pair<QString, qint64> groupSessionIndexRecord(const QString& roomId,
+ const QString& sessionId,
+ qint64 index);
void clearRoomData(const QString& roomId);
- void setOlmSessionLastReceived(const QString& sessionId, const QDateTime& timestamp);
- QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode);
- void saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& data);
- void updateOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle);
+ void setOlmSessionLastReceived(const QString& sessionId,
+ const QDateTime& timestamp);
+ QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(
+ const QString& roomId, const PicklingMode& picklingMode);
+ void saveCurrentOutboundMegolmSession(
+ const QString& roomId, const PicklingMode& picklingMode,
+ const QOlmOutboundGroupSession& session);
+ void updateOlmSession(const QString& senderKey, const QString& sessionId,
+ const QByteArray& pickle);
// Returns a map UserId -> [DeviceId] that have not received key yet
- QHash<QString, QStringList> devicesWithoutKey(const QString& roomId, QHash<QString, QStringList>& devices, const QString &sessionId);
+ QMultiHash<QString, QString> devicesWithoutKey(const QString& roomId, QMultiHash<QString, QString> devices,
+ const QString& sessionId);
// 'devices' contains tuples {userId, deviceId, curveKey}
- void setDevicesReceivedKey(const QString& roomId, const QVector<std::tuple<QString, QString, QString>>& devices, const QString& sessionId, int index);
+ void setDevicesReceivedKey(
+ const QString& roomId,
+ const QVector<std::tuple<QString, QString, QString>>& devices,
+ const QString& sessionId, int index);
private:
void migrateTo1();
diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp
index 76188d08..a2eff2c8 100644
--- a/lib/e2ee/qolmoutboundsession.cpp
+++ b/lib/e2ee/qolmoutboundsession.cpp
@@ -44,7 +44,7 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create()
return std::make_unique<QOlmOutboundGroupSession>(olmOutboundGroupSession);
}
-QOlmExpected<QByteArray> QOlmOutboundGroupSession::pickle(const PicklingMode &mode)
+QOlmExpected<QByteArray> QOlmOutboundGroupSession::pickle(const PicklingMode &mode) const
{
QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0');
QByteArray key = toKey(mode);
@@ -79,7 +79,7 @@ QOlmExpected<QOlmOutboundGroupSessionPtr> QOlmOutboundGroupSession::unpickle(con
return std::make_unique<QOlmOutboundGroupSession>(olmOutboundGroupSession);
}
-QOlmExpected<QByteArray> QOlmOutboundGroupSession::encrypt(const QString &plaintext)
+QOlmExpected<QByteArray> QOlmOutboundGroupSession::encrypt(const QString &plaintext) const
{
QByteArray plaintextBuf = plaintext.toUtf8();
const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length());
diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h
index c20613d3..9a82d22a 100644
--- a/lib/e2ee/qolmoutboundsession.h
+++ b/lib/e2ee/qolmoutboundsession.h
@@ -21,14 +21,14 @@ public:
//! Throw OlmError on errors
static QOlmOutboundGroupSessionPtr create();
//! Serialises a `QOlmOutboundGroupSession` to encrypted Base64.
- QOlmExpected<QByteArray> pickle(const PicklingMode &mode);
+ QOlmExpected<QByteArray> pickle(const PicklingMode &mode) const;
//! Deserialises from encrypted Base64 that was previously obtained by
//! pickling a `QOlmOutboundGroupSession`.
static QOlmExpected<QOlmOutboundGroupSessionPtr> unpickle(
const QByteArray& pickled, const PicklingMode& mode);
//! Encrypts a plaintext message using the session.
- QOlmExpected<QByteArray> encrypt(const QString& plaintext);
+ QOlmExpected<QByteArray> encrypt(const QString& plaintext) const;
//! Get the current message index for this session.
//!
diff --git a/lib/room.cpp b/lib/room.cpp
index 26fe80e3..07d03467 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -443,9 +443,11 @@ public:
return q->getCurrentState<EncryptionEvent>()->rotationPeriodMsgs();
}
void createMegolmSession() {
- qCDebug(E2EE) << "Creating new outbound megolm session for room " << q->id();
+ qCDebug(E2EE) << "Creating new outbound megolm session for room "
+ << q->objectName();
currentOutboundMegolmSession = QOlmOutboundGroupSession::create();
- connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession);
+ connection->saveCurrentOutboundMegolmSession(
+ id, *currentOutboundMegolmSession);
const auto sessionKey = currentOutboundMegolmSession->sessionKey();
if(!sessionKey) {
@@ -477,90 +479,113 @@ public:
return makeEvent<EncryptedEvent>(encrypted, connection->olmAccount()->identityKeys().curve25519);
}
- QHash<QString, QStringList> getDevicesWithoutKey() const
+ QMultiHash<QString, QString> getDevicesWithoutKey() const
{
- QHash<QString, QStringList> devices;
- for (const auto& user : q->users()) {
- devices[user->id()] = q->connection()->devicesForUser(user->id());
+ QMultiHash<QString, QString> devices;
+ for (const auto& user : q->users())
+ for (const auto& deviceId : connection->devicesForUser(user->id()))
+ devices.insert(user->id(), deviceId);
+
+ return connection->database()->devicesWithoutKey(
+ id, devices, currentOutboundMegolmSession->sessionId());
+ }
+
+ bool createOlmSession(const QString& user, const QString& device,
+ const QJsonObject& oneTimeKeyObject) const
+ {
+ static QOlmUtility verifier;
+ qDebug(E2EE) << "Creating a new session for" << user << device;
+ if (oneTimeKeyObject.isEmpty()) {
+ qWarning(E2EE) << "No one time key for" << user << device;
+ return false;
}
- return q->connection()->database()->devicesWithoutKey(q->id(), devices, QString(currentOutboundMegolmSession->sessionId()));
+ const auto oneTimeKeyForId = *oneTimeKeyObject.constBegin();
+ const auto signature =
+ oneTimeKeyForId["signatures"][user]["ed25519:"_ls % device]
+ .toString()
+ .toLatin1();
+ auto signedObject = oneTimeKeyForId.toObject();
+ signedObject.remove("unsigned"_ls);
+ signedObject.remove("signatures"_ls);
+ const auto signedData =
+ QJsonDocument(signedObject).toJson(QJsonDocument::Compact);
+ if (!verifier.ed25519Verify(
+ connection->edKeyForUserDevice(user, device).toLatin1(),
+ signedData, signature)) {
+ qWarning(E2EE) << "Failed to verify one-time-key signature for"
+ << user << device << ". Skipping this device.";
+ return false;
+ }
+ const auto recipientCurveKey =
+ connection->curveKeyForUserDevice(user, device);
+ connection->createOlmSession(recipientCurveKey,
+ oneTimeKeyForId["key"].toString());
+ return true;
}
- void sendRoomKeyToDevices(const QByteArray& sessionId, const QByteArray& sessionKey, const QHash<QString, QStringList> devices, int index)
+ void sendRoomKeyToDevices(const QByteArray& sessionId,
+ const QByteArray& sessionKey,
+ const QMultiHash<QString, QString>& devices,
+ int index)
{
- qCDebug(E2EE) << "Sending room key to devices" << sessionId, sessionKey.toHex();
+ qDebug(E2EE) << "Sending room key to devices:" << sessionId
+ << sessionKey.toHex();
QHash<QString, QHash<QString, QString>> hash;
- for (const auto& user : devices.keys()) {
- QHash<QString, QString> u;
- for(const auto &device : devices[user]) {
- if (!connection->hasOlmSession(user, device)) {
- u[device] = "signed_curve25519"_ls;
- qCDebug(E2EE) << "Adding" << user << device << "to keys to claim";
- }
+ for (const auto& [userId, deviceId] : asKeyValueRange(devices))
+ if (!connection->hasOlmSession(userId, deviceId)) {
+ hash[userId].insert(deviceId, "signed_curve25519"_ls);
+ qDebug(E2EE)
+ << "Adding" << userId << deviceId << "to keys to claim";
}
- if (!u.isEmpty()) {
- hash[user] = u;
- }
- }
- if (hash.isEmpty()) {
+
+ if (hash.isEmpty())
return;
- }
+
auto job = connection->callApi<ClaimKeysJob>(hash);
- connect(job, &BaseJob::success, q, [job, this, sessionId, sessionKey, devices, index](){
+ connect(job, &BaseJob::success, q,
+ [job, this, sessionId, sessionKey, devices, index] {
Connection::UsersToDevicesToEvents usersToDevicesToEvents;
const auto data = job->jsonData();
- for(const auto &user : devices.keys()) {
- for(const auto &device : devices[user]) {
- const auto recipientCurveKey = connection->curveKeyForUserDevice(user, device);
- if (!connection->hasOlmSession(user, device)) {
- qCDebug(E2EE) << "Creating a new session for" << user << device;
- if(data["one_time_keys"][user][device].toObject().isEmpty()) {
- qWarning() << "No one time key for" << user << device;
- continue;
- }
- const auto keyId = data["one_time_keys"][user][device].toObject().keys()[0];
- const auto oneTimeKey = data["one_time_keys"][user][device][keyId]["key"].toString();
- const auto signature = data["one_time_keys"][user][device][keyId]["signatures"][user][QStringLiteral("ed25519:") + device].toString().toLatin1();
- auto signedData = data["one_time_keys"][user][device][keyId].toObject();
- signedData.remove("unsigned");
- signedData.remove("signatures");
- auto signatureMatch = QOlmUtility().ed25519Verify(connection->edKeyForUserDevice(user, device).toLatin1(), QJsonDocument(signedData).toJson(QJsonDocument::Compact), signature);
- if (!signatureMatch) {
- qCWarning(E2EE) << "Failed to verify one-time-key signature for" << user << device << ". Skipping this device.";
- continue;
- } else {
- }
- connection->createOlmSession(recipientCurveKey, oneTimeKey);
- }
- usersToDevicesToEvents[user][device] = payloadForUserDevice(user, device, sessionId, sessionKey);
- }
+ for (const auto& [user, device] : asKeyValueRange(devices)) {
+ if (!connection->hasOlmSession(user, device)
+ && !createOlmSession(
+ user, device,
+ data["one_time_keys"][user][device].toObject()))
+ continue;
+
+ usersToDevicesToEvents[user][device] =
+ payloadForUserDevice(user, device, sessionId,
+ sessionKey);
}
if (!usersToDevicesToEvents.empty()) {
- connection->sendToDevices("m.room.encrypted", usersToDevicesToEvents);
+ connection->sendToDevices("m.room.encrypted"_ls,
+ usersToDevicesToEvents);
QVector<std::tuple<QString, QString, QString>> receivedDevices;
- for (const auto& user : devices.keys()) {
- for (const auto& device : devices[user]) {
- receivedDevices += {user, device, q->connection()->curveKeyForUserDevice(user, device) };
- }
- }
- connection->database()->setDevicesReceivedKey(q->id(), receivedDevices, sessionId, index);
+ receivedDevices.reserve(devices.size());
+ for (const auto& [user, device] : asKeyValueRange(devices))
+ receivedDevices.push_back(
+ { user, device,
+ connection->curveKeyForUserDevice(user, device) });
+
+ connection->database()->setDevicesReceivedKey(id,
+ receivedDevices,
+ sessionId, index);
}
});
}
- void sendMegolmSession(const QHash<QString, QStringList>& devices) {
+ void sendMegolmSession(const QMultiHash<QString, QString>& devices) {
// Save the session to this device
const auto sessionId = currentOutboundMegolmSession->sessionId();
- const auto _sessionKey = currentOutboundMegolmSession->sessionKey();
- if(!_sessionKey) {
+ const auto sessionKey = currentOutboundMegolmSession->sessionKey();
+ if(!sessionKey) {
qCWarning(E2EE) << "Error loading session key";
return;
}
- const auto sessionKey = *_sessionKey;
- const auto senderKey = q->connection()->olmAccount()->identityKeys().curve25519;
// Send the session to other people
- sendRoomKeyToDevices(sessionId, sessionKey, devices, currentOutboundMegolmSession->sessionMessageIndex());
+ sendRoomKeyToDevices(sessionId, *sessionKey, devices,
+ currentOutboundMegolmSession->sessionMessageIndex());
}
#endif // Quotient_E2EE_ENABLED
@@ -592,7 +617,8 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState)
}
});
d->groupSessions = connection->loadRoomMegolmSessions(this);
- d->currentOutboundMegolmSession = connection->loadCurrentOutboundMegolmSession(this);
+ d->currentOutboundMegolmSession =
+ connection->loadCurrentOutboundMegolmSession(this->id());
if (d->shouldRotateMegolmSession()) {
d->currentOutboundMegolmSession = nullptr;
}
@@ -2101,12 +2127,12 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent)
if (!hasValidMegolmSession() || shouldRotateMegolmSession()) {
createMegolmSession();
}
- const auto devicesWithoutKey = getDevicesWithoutKey();
- sendMegolmSession(devicesWithoutKey);
+ sendMegolmSession(getDevicesWithoutKey());
const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(pEvent->fullJson()).toJson());
currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1);
- connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession);
+ connection->saveCurrentOutboundMegolmSession(
+ id, *currentOutboundMegolmSession);
if(!encrypted) {
qWarning(E2EE) << "Error encrypting message" << encrypted.error();
return {};