aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--autotests/testolmaccount.cpp80
-rw-r--r--lib/connection.cpp227
-rw-r--r--lib/connection.h34
-rw-r--r--lib/csapi/keys.h4
-rw-r--r--lib/e2ee/qolmsession.cpp5
-rw-r--r--lib/e2ee/qolmsession.h2
-rw-r--r--lib/events/roomkeyevent.h4
-rw-r--r--lib/room.cpp113
8 files changed, 249 insertions, 220 deletions
diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp
index b509d12f..3fb8ac24 100644
--- a/autotests/testolmaccount.cpp
+++ b/autotests/testolmaccount.cpp
@@ -378,47 +378,47 @@ void TestOlmAccount::claimKeys()
// Alice retrieves bob's keys & claims one signed one-time key.
QHash<QString, QStringList> deviceKeys;
deviceKeys[bob->userId()] = QStringList();
- auto job = alice->callApi<QueryKeysJob>(deviceKeys);
- connect(job, &BaseJob::result, this, [bob, alice, job, this] {
- const auto& bobDevices = job->deviceKeys().value(bob->userId());
- QVERIFY(!bobDevices.empty());
-
- // Retrieve the identity key for the current device.
- const auto& bobEd25519 =
- bobDevices.value(bob->deviceId()).keys["ed25519:" + bob->deviceId()];
-
- const auto currentDevice = bobDevices[bob->deviceId()];
-
- // Verify signature.
- QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(),
- bob->userId()));
-
- QHash<QString, QHash<QString, QString>> oneTimeKeys;
- oneTimeKeys[bob->userId()] = QHash<QString, QString>();
- oneTimeKeys[bob->userId()][bob->deviceId()] = SignedCurve25519Key;
-
- auto job = alice->callApi<ClaimKeysJob>(oneTimeKeys);
- connect(job, &BaseJob::result, this, [bob, bobEd25519, job] {
- const auto userId = bob->userId();
- const auto deviceId = bob->deviceId();
-
- // The device exists.
- QCOMPARE(job->oneTimeKeys().size(), 1);
- QCOMPARE(job->oneTimeKeys().value(userId).size(), 1);
-
- // The key is the one bob sent.
- const auto& oneTimeKey =
- job->oneTimeKeys().value(userId).value(deviceId);
- QVERIFY(oneTimeKey.canConvert<QVariantMap>());
-
- const auto varMap = oneTimeKey.toMap();
- QVERIFY(std::any_of(varMap.constKeyValueBegin(),
- varMap.constKeyValueEnd(), [](const auto& kv) {
- return kv.first.startsWith(
- SignedCurve25519Key);
- }));
- });
+ auto queryKeysJob = alice->callApi<QueryKeysJob>(deviceKeys);
+ QSignalSpy requestSpy2(queryKeysJob, &BaseJob::result);
+ QVERIFY(requestSpy2.wait(10000));
+
+ const auto& bobDevices = queryKeysJob->deviceKeys().value(bob->userId());
+ QVERIFY(!bobDevices.empty());
+
+ const auto currentDevice = bobDevices[bob->deviceId()];
+
+ // Verify signature.
+ QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(),
+ bob->userId()));
+ // Retrieve the identity key for the current device.
+ const auto& bobEd25519 =
+ bobDevices.value(bob->deviceId()).keys["ed25519:" + bob->deviceId()];
+
+ QHash<QString, QHash<QString, QString>> oneTimeKeys;
+ oneTimeKeys[bob->userId()] = QHash<QString, QString>();
+ oneTimeKeys[bob->userId()][bob->deviceId()] = SignedCurve25519Key;
+
+ auto claimKeysJob = alice->callApi<ClaimKeysJob>(oneTimeKeys);
+ connect(claimKeysJob, &BaseJob::result, this, [bob, bobEd25519, claimKeysJob] {
+ const auto userId = bob->userId();
+ const auto deviceId = bob->deviceId();
+
+ // The device exists.
+ QCOMPARE(claimKeysJob->oneTimeKeys().size(), 1);
+ QCOMPARE(claimKeysJob->oneTimeKeys().value(userId).size(), 1);
+
+ // The key is the one bob sent.
+ const auto& oneTimeKeys =
+ claimKeysJob->oneTimeKeys().value(userId).value(deviceId);
+ for (auto it = oneTimeKeys.begin(); it != oneTimeKeys.end(); ++it) {
+ if (it.key().startsWith(SignedCurve25519Key)
+ && it.value().isObject())
+ return;
+ }
+ QFAIL("The claimed one time key is not in /claim response");
});
+ QSignalSpy completionSpy(claimKeysJob, &BaseJob::result);
+ QVERIFY(completionSpy.wait(10000));
}
void TestOlmAccount::claimMultipleKeys()
diff --git a/lib/connection.cpp b/lib/connection.cpp
index 1193eb75..ab4a7dea 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -39,6 +39,7 @@
# include "e2ee/qolmaccount.h"
# include "e2ee/qolminboundsession.h"
# include "e2ee/qolmsession.h"
+# include "e2ee/qolmutility.h"
# include "e2ee/qolmutils.h"
# if QT_VERSION_MAJOR >= 6
@@ -62,7 +63,6 @@
#include <QtCore/QStringBuilder>
#include <QtNetwork/QDnsLookup>
-
using namespace Quotient;
// This is very much Qt-specific; STL iterators don't have key() and value()
@@ -210,11 +210,11 @@ public:
#ifdef Quotient_E2EE_ENABLED
void loadSessions() {
- olmSessions = q->database()->loadOlmSessions(q->picklingMode());
+ olmSessions = q->database()->loadOlmSessions(picklingMode);
}
- void saveSession(QOlmSession& session, const QString& senderKey)
+ void saveSession(const QOlmSession& session, const QString& senderKey) const
{
- if (auto pickleResult = session.pickle(q->picklingMode()))
+ if (auto pickleResult = session.pickle(picklingMode))
q->database()->saveOlmSession(senderKey, session.sessionId(),
*pickleResult,
QDateTime::currentDateTime());
@@ -364,9 +364,27 @@ public:
#endif // Quotient_E2EE_ENABLED
}
#ifdef Quotient_E2EE_ENABLED
+ bool isKnownCurveKey(const QString& userId, const QString& curveKey) const;
+
void loadOutdatedUserDevices();
void saveDevicesList();
void loadDevicesList();
+
+ // This function assumes that an olm session with (user, device) exists
+ std::pair<QOlmMessage::Type, QByteArray> olmEncryptMessage(
+ const QString& userId, const QString& device,
+ const QByteArray& message) const;
+ bool createOlmSession(const QString& targetUserId,
+ const QString& targetDeviceId,
+ const QJsonObject& oneTimeKeyObject);
+ QString curveKeyForUserDevice(const QString& userId,
+ const QString& device) const;
+ QString edKeyForUserDevice(const QString& userId,
+ const QString& device) const;
+ std::unique_ptr<EncryptedEvent> makeEventForSessionKey(
+ const QString& roomId, const QString& targetUserId,
+ const QString& targetDeviceId, const QByteArray& sessionId,
+ const QByteArray& sessionKey) const;
#endif
};
@@ -935,20 +953,23 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents)
{
#ifdef Quotient_E2EE_ENABLED
if (!toDeviceEvents.empty()) {
- qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events";
+ qCDebug(E2EE) << "Consuming" << toDeviceEvents.size()
+ << "to-device events";
visitEach(toDeviceEvents, [this](const EncryptedEvent& event) {
if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) {
- qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm();
+ qCDebug(E2EE) << "Unsupported algorithm" << event.id()
+ << "for event" << event.algorithm();
return;
}
- if (q->isKnownCurveKey(event.senderId(), event.senderKey())) {
+ if (isKnownCurveKey(event.senderId(), event.senderKey())) {
handleEncryptedToDeviceEvent(event);
return;
}
trackedUsers += event.senderId();
outdatedUsers += event.senderId();
encryptionUpdateRequired = true;
- pendingEncryptedEvents.push_back(std::make_unique<EncryptedEvent>(event.fullJson()));
+ pendingEncryptedEvents.push_back(
+ makeEvent<EncryptedEvent>(event.fullJson()));
});
}
#endif
@@ -1316,9 +1337,8 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id)
return forgetJob;
}
-SendToDeviceJob*
-Connection::sendToDevices(const QString& eventType,
- const UsersToDevicesToEvents& eventsMap)
+SendToDeviceJob* Connection::sendToDevices(
+ const QString& eventType, const UsersToDevicesToEvents& eventsMap)
{
QHash<QString, QHash<QString, QJsonObject>> json;
json.reserve(int(eventsMap.size()));
@@ -2063,7 +2083,7 @@ void Connection::Private::loadOutdatedUserDevices()
saveDevicesList();
for(size_t i = 0; i < pendingEncryptedEvents.size();) {
- if (q->isKnownCurveKey(
+ if (isKnownCurveKey(
pendingEncryptedEvents[i]->fullJson()[SenderKeyL].toString(),
pendingEncryptedEvents[i]->contentPart<QString>("sender_key"_ls))) {
handleEncryptedToDeviceEvent(*pendingEncryptedEvents[i]);
@@ -2193,13 +2213,13 @@ Database* Connection::database() const
}
UnorderedMap<QString, QOlmInboundGroupSessionPtr>
-Connection::loadRoomMegolmSessions(const Room* room)
+Connection::loadRoomMegolmSessions(const Room* room) const
{
return database()->loadMegolmSessions(room->id(), picklingMode());
}
void Connection::saveMegolmSession(const Room* room,
- const QOlmInboundGroupSession& session)
+ const QOlmInboundGroupSession& session) const
{
database()->saveMegolmSession(room->id(), session.sessionId(),
session.pickle(picklingMode()),
@@ -2211,64 +2231,179 @@ QStringList Connection::devicesForUser(const QString& userId) const
return d->deviceKeys[userId].keys();
}
-QString Connection::curveKeyForUserDevice(const QString& userId,
- const QString& device) const
+QString Connection::Private::curveKeyForUserDevice(const QString& userId,
+ const QString& device) const
{
- return d->deviceKeys[userId][device].keys["curve25519:" % device];
+ return deviceKeys[userId][device].keys["curve25519:" % device];
}
-QString Connection::edKeyForUserDevice(const QString& userId,
- const QString& device) const
+QString Connection::Private::edKeyForUserDevice(const QString& userId,
+ const QString& device) const
{
- return d->deviceKeys[userId][device].keys["ed25519:" % device];
+ return deviceKeys[userId][device].keys["ed25519:" % device];
}
-bool Connection::isKnownCurveKey(const QString& userId,
- const QString& curveKey) const
+bool Connection::Private::isKnownCurveKey(const QString& userId,
+ const QString& curveKey) const
{
- auto query = database()->prepareQuery(QStringLiteral("SELECT * FROM tracked_devices WHERE matrixId=:matrixId AND curveKey=:curveKey"));
+ auto query = database->prepareQuery(
+ QStringLiteral("SELECT * FROM tracked_devices WHERE matrixId=:matrixId "
+ "AND curveKey=:curveKey"));
query.bindValue(":matrixId", userId);
query.bindValue(":curveKey", curveKey);
- database()->execute(query);
+ database->execute(query);
return query.next();
}
-bool Connection::hasOlmSession(const QString& user, const QString& deviceId) const
+bool Connection::hasOlmSession(const QString& user,
+ const QString& deviceId) const
{
- const auto& curveKey = curveKeyForUserDevice(user, deviceId);
+ const auto& curveKey = d->curveKeyForUserDevice(user, deviceId);
return d->olmSessions.contains(curveKey) && !d->olmSessions[curveKey].empty();
}
-std::pair<QOlmMessage::Type, QByteArray> Connection::olmEncryptMessage(
- const QString& userId, const QString& device, const QByteArray& message) const
+std::pair<QOlmMessage::Type, QByteArray> Connection::Private::olmEncryptMessage(
+ const QString& userId, const QString& device,
+ const QByteArray& message) const
{
const auto& curveKey = curveKeyForUserDevice(userId, device);
- QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType();
- const auto result = d->olmSessions[curveKey][0]->encrypt(message);
- if (const auto pickle =
- d->olmSessions[curveKey][0]->pickle(picklingMode())) {
- database()->updateOlmSession(curveKey,
- d->olmSessions[curveKey][0]->sessionId(),
- *pickle);
+ const auto& olmSession = olmSessions.at(curveKey).front();
+ QOlmMessage::Type type = olmSession->encryptMessageType();
+ const auto result = olmSession->encrypt(message);
+ if (const auto pickle = olmSession->pickle(picklingMode)) {
+ database->updateOlmSession(curveKey, olmSession->sessionId(), *pickle);
} else {
- qCWarning(E2EE) << "Failed to pickle olm session: " << pickle.error();
+ qWarning(E2EE) << "Failed to pickle olm session: " << pickle.error();
}
return { type, result.toCiphertext() };
}
-void Connection::createOlmSession(const QString& theirIdentityKey,
- const QString& theirOneTimeKey) const
-{
- auto session = QOlmSession::createOutboundSession(olmAccount(),
- theirIdentityKey,
- theirOneTimeKey);
+bool Connection::Private::createOlmSession(const QString& targetUserId,
+ const QString& targetDeviceId,
+ const QJsonObject& oneTimeKeyObject)
+{
+ static QOlmUtility verifier;
+ qDebug(E2EE) << "Creating a new session for" << targetUserId
+ << targetDeviceId;
+ if (oneTimeKeyObject.isEmpty()) {
+ qWarning(E2EE) << "No one time key for" << targetUserId
+ << targetDeviceId;
+ return false;
+ }
+ auto signedOneTimeKey = oneTimeKeyObject.constBegin()->toObject();
+ // Verify contents of signedOneTimeKey - for that, drop `signatures` and
+ // `unsigned` and then verify the object against the respective signature
+ const auto signature =
+ signedOneTimeKey.take("signatures"_ls)[targetUserId]["ed25519:"_ls % targetDeviceId]
+ .toString()
+ .toLatin1();
+ signedOneTimeKey.remove("unsigned"_ls);
+ if (!verifier.ed25519Verify(
+ edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(),
+ QJsonDocument(signedOneTimeKey).toJson(QJsonDocument::Compact),
+ signature)) {
+ qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId
+ << targetDeviceId << ". Skipping this device.";
+ return false;
+ }
+ const auto recipientCurveKey =
+ curveKeyForUserDevice(targetUserId, targetDeviceId);
+ auto session =
+ QOlmSession::createOutboundSession(olmAccount.get(), recipientCurveKey,
+ signedOneTimeKey["key"].toString());
if (!session) {
qCWarning(E2EE) << "Failed to create olm session for "
- << theirIdentityKey << session.error();
+ << recipientCurveKey << session.error();
+ return false;
+ }
+ saveSession(**session, recipientCurveKey);
+ olmSessions[recipientCurveKey].push_back(std::move(*session));
+ return true;
+}
+
+std::unique_ptr<EncryptedEvent> Connection::Private::makeEventForSessionKey(
+ const QString& roomId, const QString& targetUserId,
+ const QString& targetDeviceId, const QByteArray& sessionId,
+ const QByteArray& sessionKey) const
+{
+ // Noisy but nice for debugging
+ // qDebug(E2EE) << "Creating the payload for" << data->userId() << device <<
+ // sessionId << sessionKey.toHex();
+ const auto event = makeEvent<RoomKeyEvent>("m.megolm.v1.aes-sha2", roomId,
+ sessionId, sessionKey,
+ data->userId());
+ auto payloadJson = event->fullJson();
+ payloadJson.insert("recipient"_ls, targetUserId);
+ payloadJson.insert(SenderKeyL, data->userId());
+ payloadJson.insert("recipient_keys"_ls,
+ QJsonObject { { Ed25519Key,
+ edKeyForUserDevice(targetUserId,
+ targetDeviceId) } });
+ payloadJson.insert("keys"_ls,
+ QJsonObject {
+ { Ed25519Key,
+ QString(olmAccount->identityKeys().ed25519) } });
+ payloadJson.insert("sender_device"_ls, data->deviceId());
+
+ const auto [type, cipherText] = olmEncryptMessage(
+ targetUserId, targetDeviceId,
+ QJsonDocument(payloadJson).toJson(QJsonDocument::Compact));
+ QJsonObject encrypted {
+ { curveKeyForUserDevice(targetUserId, targetDeviceId),
+ QJsonObject { { "type"_ls, type },
+ { "body"_ls, QString(cipherText) } } }
+ };
+
+ return makeEvent<EncryptedEvent>(encrypted,
+ olmAccount->identityKeys().curve25519);
+}
+
+void Connection::sendSessionKeyToDevices(
+ const QString& roomId, const QByteArray& sessionId,
+ const QByteArray& sessionKey, const QMultiHash<QString, QString>& devices,
+ int index)
+{
+ qDebug(E2EE) << "Sending room key to devices:" << sessionId
+ << sessionKey.toHex();
+ QHash<QString, QHash<QString, QString>> hash;
+ for (const auto& [userId, deviceId] : asKeyValueRange(devices))
+ if (!hasOlmSession(userId, deviceId)) {
+ hash[userId].insert(deviceId, "signed_curve25519"_ls);
+ qDebug(E2EE) << "Adding" << userId << deviceId
+ << "to keys to claim";
+ }
+
+ if (hash.isEmpty())
return;
- }
- d->saveSession(**session, theirIdentityKey);
- d->olmSessions[theirIdentityKey].push_back(std::move(*session));
+
+ auto job = callApi<ClaimKeysJob>(hash);
+ connect(job, &BaseJob::success, this, [job, this, roomId, sessionId, sessionKey, devices, index] {
+ UsersToDevicesToEvents usersToDevicesToEvents;
+ const auto oneTimeKeys = job->oneTimeKeys();
+ for (const auto& [targetUserId, targetDeviceId] :
+ asKeyValueRange(devices)) {
+ if (!hasOlmSession(targetUserId, targetDeviceId)
+ && !d->createOlmSession(
+ targetUserId, targetDeviceId,
+ oneTimeKeys[targetUserId][targetDeviceId]))
+ continue;
+
+ usersToDevicesToEvents[targetUserId][targetDeviceId] =
+ d->makeEventForSessionKey(roomId, targetUserId, targetDeviceId,
+ sessionId, sessionKey);
+ }
+ if (!usersToDevicesToEvents.empty()) {
+ sendToDevices(EncryptedEvent::TypeId, usersToDevicesToEvents);
+ QVector<std::tuple<QString, QString, QString>> receivedDevices;
+ receivedDevices.reserve(devices.size());
+ for (const auto& [user, device] : asKeyValueRange(devices))
+ receivedDevices.push_back(
+ { user, device, d->curveKeyForUserDevice(user, device) });
+
+ database()->setDevicesReceivedKey(roomId, receivedDevices,
+ sessionId, index);
+ }
+ });
}
QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(
diff --git a/lib/connection.h b/lib/connection.h
index a2824744..5b806350 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -319,22 +319,26 @@ public:
#ifdef Quotient_E2EE_ENABLED
QOlmAccount* olmAccount() const;
Database* database() const;
+ PicklingMode picklingMode() const;
UnorderedMap<QString, QOlmInboundGroupSessionPtr> loadRoomMegolmSessions(
- const Room* room);
+ const Room* room) const;
void saveMegolmSession(const Room* room,
- const QOlmInboundGroupSession& session);
+ const QOlmInboundGroupSession& session) const;
bool hasOlmSession(const QString& user, const QString& deviceId) const;
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(
- const QString& userId, const QString& device,
- const QByteArray& message) const;
- void createOlmSession(const QString& theirIdentityKey,
- const QString& theirOneTimeKey) const;
+ void saveCurrentOutboundMegolmSession(
+ const QString& roomId, const QOlmOutboundGroupSession& session) const;
+
+ void sendSessionKeyToDevices(const QString& roomId,
+ const QByteArray& sessionId,
+ const QByteArray& sessionKey,
+ const QMultiHash<QString, QString>& devices,
+ int index);
+
+ QJsonObject decryptNotification(const QJsonObject &notification);
+ QStringList devicesForUser(const QString& userId) const;
#endif // Quotient_E2EE_ENABLED
Q_INVOKABLE Quotient::SyncJob* syncJob() const;
Q_INVOKABLE int millisToReconnect() const;
@@ -695,16 +699,8 @@ public Q_SLOTS:
#ifdef Quotient_E2EE_ENABLED
void encryptionUpdate(Room *room);
- PicklingMode picklingMode() const;
- QJsonObject decryptNotification(const QJsonObject &notification);
-
- QStringList devicesForUser(const QString& userId) const;
- QString curveKeyForUserDevice(const QString& userId,
- const QString& device) const;
- QString edKeyForUserDevice(const QString& userId,
- const QString& device) const;
- bool isKnownCurveKey(const QString& userId, const QString& curveKey) const;
#endif
+
Q_SIGNALS:
/// \brief Initial server resolution has failed
///
diff --git a/lib/csapi/keys.h b/lib/csapi/keys.h
index ce1ca9ed..bcf1ad41 100644
--- a/lib/csapi/keys.h
+++ b/lib/csapi/keys.h
@@ -207,9 +207,9 @@ public:
///
/// See the [key algorithms](/client-server-api/#key-algorithms) section for
/// information on the Key Object format.
- QHash<QString, QHash<QString, QVariant>> oneTimeKeys() const
+ QHash<QString, QHash<QString, QJsonObject>> oneTimeKeys() const
{
- return loadFromJson<QHash<QString, QHash<QString, QVariant>>>(
+ return loadFromJson<QHash<QString, QHash<QString, QJsonObject>>>(
"one_time_keys"_ls);
}
};
diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp
index 2b149aac..2a98d5d8 100644
--- a/lib/e2ee/qolmsession.cpp
+++ b/lib/e2ee/qolmsession.cpp
@@ -96,12 +96,13 @@ QOlmExpected<QOlmSessionPtr> QOlmSession::createOutboundSession(
return std::make_unique<QOlmSession>(olmOutboundSession);
}
-QOlmExpected<QByteArray> QOlmSession::pickle(const PicklingMode &mode)
+QOlmExpected<QByteArray> QOlmSession::pickle(const PicklingMode &mode) const
{
QByteArray pickledBuf(olm_pickle_session_length(m_session), '0');
QByteArray key = toKey(mode);
const auto error = olm_pickle_session(m_session, key.data(), key.length(),
- pickledBuf.data(), pickledBuf.length());
+ pickledBuf.data(),
+ pickledBuf.length());
if (error == olm_error()) {
return lastError(m_session);
diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h
index faae16ef..021092c7 100644
--- a/lib/e2ee/qolmsession.h
+++ b/lib/e2ee/qolmsession.h
@@ -31,7 +31,7 @@ public:
const QString& theirOneTimeKey);
//! Serialises an `QOlmSession` 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 `QOlmSession`.
static QOlmExpected<QOlmSessionPtr> unpickle(
diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h
index 2bda3086..3093db41 100644
--- a/lib/events/roomkeyevent.h
+++ b/lib/events/roomkeyevent.h
@@ -12,7 +12,9 @@ public:
DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent)
explicit RoomKeyEvent(const QJsonObject& obj);
- explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, const QString &sessionId, const QString& sessionKey, const QString& senderId);
+ explicit RoomKeyEvent(const QString& algorithm, const QString& roomId,
+ const QString& sessionId, const QString& sessionKey,
+ const QString& senderId);
QString algorithm() const { return contentPart<QString>("algorithm"_ls); }
QString roomId() const { return contentPart<QString>(RoomIdKeyL); }
diff --git a/lib/room.cpp b/lib/room.cpp
index 07d03467..6ec06aa8 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -457,28 +457,6 @@ public:
addInboundGroupSession(currentOutboundMegolmSession->sessionId(), *sessionKey, q->localUser()->id(), "SELF"_ls);
}
- std::unique_ptr<EncryptedEvent> payloadForUserDevice(QString user, const QString& device, const QByteArray& sessionId, const QByteArray& sessionKey)
- {
- // Noisy but nice for debugging
- //qCDebug(E2EE) << "Creating the payload for" << user->id() << device << sessionId << sessionKey.toHex();
- const auto event = makeEvent<RoomKeyEvent>("m.megolm.v1.aes-sha2", q->id(), sessionId, sessionKey, q->localUser()->id());
- QJsonObject payloadJson = event->fullJson();
- payloadJson["recipient"] = user;
- payloadJson["sender"] = connection->user()->id();
- QJsonObject recipientObject;
- recipientObject["ed25519"] = connection->edKeyForUserDevice(user, device);
- payloadJson["recipient_keys"] = recipientObject;
- QJsonObject senderObject;
- senderObject["ed25519"] = QString(connection->olmAccount()->identityKeys().ed25519);
- payloadJson["keys"] = senderObject;
- payloadJson["sender_device"] = connection->deviceId();
- auto cipherText = connection->olmEncryptMessage(user, device, QJsonDocument(payloadJson).toJson(QJsonDocument::Compact));
- QJsonObject encrypted;
- encrypted[connection->curveKeyForUserDevice(user, device)] = QJsonObject{{"type", cipherText.first}, {"body", QString(cipherText.second)}};
-
- return makeEvent<EncryptedEvent>(encrypted, connection->olmAccount()->identityKeys().curve25519);
- }
-
QMultiHash<QString, QString> getDevicesWithoutKey() const
{
QMultiHash<QString, QString> devices;
@@ -490,91 +468,7 @@ public:
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;
- }
- 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 QMultiHash<QString, QString>& devices,
- int index)
- {
- qDebug(E2EE) << "Sending room key to devices:" << sessionId
- << sessionKey.toHex();
- QHash<QString, QHash<QString, QString>> hash;
- 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 (hash.isEmpty())
- return;
-
- auto job = connection->callApi<ClaimKeysJob>(hash);
- connect(job, &BaseJob::success, q,
- [job, this, sessionId, sessionKey, devices, index] {
- Connection::UsersToDevicesToEvents usersToDevicesToEvents;
- const auto data = job->jsonData();
- 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"_ls,
- usersToDevicesToEvents);
- QVector<std::tuple<QString, QString, QString>> receivedDevices;
- 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 QMultiHash<QString, QString>& devices) {
+ void sendMegolmSession(const QMultiHash<QString, QString>& devices) const {
// Save the session to this device
const auto sessionId = currentOutboundMegolmSession->sessionId();
const auto sessionKey = currentOutboundMegolmSession->sessionKey();
@@ -584,8 +478,9 @@ public:
}
// Send the session to other people
- sendRoomKeyToDevices(sessionId, *sessionKey, devices,
- currentOutboundMegolmSession->sessionMessageIndex());
+ connection->sendSessionKeyToDevices(
+ id, sessionId, *sessionKey, devices,
+ currentOutboundMegolmSession->sessionMessageIndex());
}
#endif // Quotient_E2EE_ENABLED