From 0b5e72a2c6502f22a752b72b4df5fa25746fdd25 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 26 May 2022 08:51:22 +0200 Subject: Refactor EncryptedFile and EC::FileInfo::file Besides having a misleading name (and it goes back to the spec), EncryptedFile under `file` key preempts the `url` (or `thumbnail_url`) string value so only one of the two should exist. This is a case for using std::variant<> - despite its clumsy syntax, it can actually simplify and streamline code when all the necessary bits are in place (such as conversion to JSON and getting the common piece - the URL - out of it). This commit replaces `FileInfo::url` and `FileInfo::file` with a common field `source` of type `FileSourceInfo` that is an alias for a variant type covering both underlying types; and `url()` is reintroduced as a function instead, to allow simplified access to whichever URL is available inside the variant. Oh, and EncryptedFile is EncryptedFileMetadata now, to clarify that it does not represent the file payload itself but rather the data necessary to obtain that payload. --- lib/room.cpp | 85 ++++++++++++++++++++++++++++-------------------------------- 1 file changed, 40 insertions(+), 45 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 7022a49d..20ea1159 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1525,7 +1525,7 @@ QUrl Room::urlToThumbnail(const QString& eventId) const auto* thumbnail = event->content()->thumbnailInfo(); Q_ASSERT(thumbnail != nullptr); return connection()->getUrlForApi( - thumbnail->url, thumbnail->imageSize); + thumbnail->url(), thumbnail->imageSize); } qCDebug(MAIN) << "Event" << eventId << "has no thumbnail"; return {}; @@ -1536,7 +1536,7 @@ QUrl Room::urlToDownload(const QString& eventId) const if (auto* event = d->getEventWithFile(eventId)) { auto* fileInfo = event->content()->fileInfo(); Q_ASSERT(fileInfo != nullptr); - return connection()->getUrlForApi(fileInfo->url); + return connection()->getUrlForApi(fileInfo->url()); } return {}; } @@ -2275,28 +2275,26 @@ QString Room::Private::doPostFile(RoomEventPtr&& msgEvent, const QUrl& localUrl) // Below, the upload job is used as a context object to clean up connections const auto& transferJob = fileTransfers.value(txnId).job; connect(q, &Room::fileTransferCompleted, transferJob, - [this, txnId](const QString& tId, const QUrl&, const QUrl& mxcUri, Omittable encryptedFile) { - if (tId != txnId) - return; + [this, txnId](const QString& tId, const QUrl&, + const FileSourceInfo fileMetadata) { + if (tId != txnId) + return; - const auto it = q->findPendingEvent(txnId); - if (it != unsyncedEvents.end()) { - it->setFileUploaded(mxcUri); - if (encryptedFile) { - it->setEncryptedFile(*encryptedFile); - } - emit q->pendingEventChanged( - int(it - unsyncedEvents.begin())); - doSendEvent(it->get()); - } else { - // Normally in this situation we should instruct - // the media server to delete the file; alas, there's no - // API specced for that. - qCWarning(MAIN) << "File uploaded to" << mxcUri - << "but the event referring to it was " - "cancelled"; - } - }); + const auto it = q->findPendingEvent(txnId); + if (it != unsyncedEvents.end()) { + it->setFileUploaded(fileMetadata); + emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); + doSendEvent(it->get()); + } else { + // Normally in this situation we should instruct + // the media server to delete the file; alas, there's no + // API specced for that. + qCWarning(MAIN) + << "File uploaded to" << getUrlFromSourceInfo(fileMetadata) + << "but the event referring to it was " + "cancelled"; + } + }); connect(q, &Room::fileTransferFailed, transferJob, [this, txnId](const QString& tId) { if (tId != txnId) @@ -2322,13 +2320,13 @@ QString Room::postFile(const QString& plainText, Q_ASSERT(content != nullptr && content->fileInfo() != nullptr); const auto* const fileInfo = content->fileInfo(); Q_ASSERT(fileInfo != nullptr); - QFileInfo localFile { fileInfo->url.toLocalFile() }; + QFileInfo localFile { fileInfo->url().toLocalFile() }; Q_ASSERT(localFile.isFile()); return d->doPostFile( makeEvent( plainText, RoomMessageEvent::rawMsgTypeForFile(localFile), content), - fileInfo->url); + fileInfo->url()); } #if QT_VERSION_MAJOR < 6 @@ -2520,18 +2518,19 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, Q_ASSERT_X(localFilename.isLocalFile(), __FUNCTION__, "localFilename should point at a local file"); auto fileName = localFilename.toLocalFile(); - Omittable encryptedFile { none }; + FileSourceInfo fileMetadata; #ifdef Quotient_E2EE_ENABLED QTemporaryFile tempFile; if (usesEncryption()) { tempFile.open(); QFile file(localFilename.toLocalFile()); file.open(QFile::ReadOnly); - auto [e, data] = EncryptedFile::encryptFile(file.readAll()); + QByteArray data; + std::tie(fileMetadata, data) = + EncryptedFileMetadata::encryptFile(file.readAll()); tempFile.write(data); tempFile.close(); fileName = QFileInfo(tempFile).absoluteFilePath(); - encryptedFile = e; } #endif auto job = connection()->uploadFile(fileName, overrideContentType); @@ -2542,17 +2541,13 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, d->fileTransfers[id].update(sent, total); emit fileTransferProgress(id, sent, total); }); - connect(job, &BaseJob::success, this, [this, id, localFilename, job, encryptedFile] { - d->fileTransfers[id].status = FileTransferInfo::Completed; - if (encryptedFile) { - auto file = *encryptedFile; - file.url = QUrl(job->contentUri()); - emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri()), file); - } else { - emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri()), none); - } - - }); + connect(job, &BaseJob::success, this, + [this, id, localFilename, job, fileMetadata]() mutable { + // The lambda is mutable to change encryptedFileMetadata + d->fileTransfers[id].status = FileTransferInfo::Completed; + setUrlInSourceInfo(fileMetadata, QUrl(job->contentUri())); + emit fileTransferCompleted(id, localFilename, fileMetadata); + }); connect(job, &BaseJob::failure, this, std::bind(&Private::failedTransfer, d, id, job->errorString())); emit newFileTransfer(id, localFilename); @@ -2585,11 +2580,11 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) << "has an empty or malformed mxc URL; won't download"; return; } - const auto fileUrl = fileInfo->url; + const auto fileUrl = fileInfo->url(); auto filePath = localFilename.toLocalFile(); if (filePath.isEmpty()) { // Setup default file path filePath = - fileInfo->url.path().mid(1) % '_' % d->fileNameToDownload(event); + fileInfo->url().path().mid(1) % '_' % d->fileNameToDownload(event); if (filePath.size() > 200) // If too long, elide in the middle filePath.replace(128, filePath.size() - 192, "---"); @@ -2599,9 +2594,9 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) } DownloadFileJob *job = nullptr; #ifdef Quotient_E2EE_ENABLED - if(fileInfo->file.has_value()) { - auto file = *fileInfo->file; - job = connection()->downloadFile(fileUrl, file, filePath); + if (auto* fileMetadata = + std::get_if(&fileInfo->source)) { + job = connection()->downloadFile(fileUrl, *fileMetadata, filePath); } else { #endif job = connection()->downloadFile(fileUrl, filePath); @@ -2619,7 +2614,7 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) connect(job, &BaseJob::success, this, [this, eventId, fileUrl, job] { d->fileTransfers[eventId].status = FileTransferInfo::Completed; emit fileTransferCompleted( - eventId, fileUrl, QUrl::fromLocalFile(job->targetFileName()), none); + eventId, fileUrl, QUrl::fromLocalFile(job->targetFileName())); }); connect(job, &BaseJob::failure, this, std::bind(&Private::failedTransfer, d, eventId, -- cgit v1.2.3 From 841846ea5efad80ce20e0d42b1885def224e58ad Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 26 May 2022 11:03:16 +0200 Subject: Cleanup and fix Sonar warnings --- lib/room.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 20ea1159..0cef1025 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2130,7 +2130,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) Room::connect(call, &BaseJob::sentRequest, q, [this, txnId] { auto it = q->findPendingEvent(txnId); if (it == unsyncedEvents.end()) { - qCWarning(EVENTS) << "Pending event for transaction" << txnId + qWarning(EVENTS) << "Pending event for transaction" << txnId << "not found - got synced so soon?"; return; } @@ -2140,7 +2140,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) Room::connect(call, &BaseJob::failure, q, std::bind(&Room::Private::onEventSendingFailure, this, txnId, call)); - Room::connect(call, &BaseJob::success, q, [this, call, txnId, _event] { + Room::connect(call, &BaseJob::success, q, [this, call, txnId] { auto it = q->findPendingEvent(txnId); if (it != unsyncedEvents.end()) { if (it->deliveryStatus() != EventStatus::ReachedServer) { @@ -2148,7 +2148,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); } } else - qCDebug(EVENTS) << "Pending event for transaction" << txnId + qDebug(EVENTS) << "Pending event for transaction" << txnId << "already merged"; emit q->messageSent(txnId, call->eventId()); @@ -2206,11 +2206,9 @@ QString Room::retryMessage(const QString& txnId) return d->doSendEvent(it->event()); } -// Lambda defers actual tr() invocation to the moment when translations are -// initialised -const auto FileTransferCancelledMsg = [] { - return Room::tr("File transfer cancelled"); -}; +// Using a function defers actual tr() invocation to the moment when +// translations are initialised +auto FileTransferCancelledMsg() { return Room::tr("File transfer cancelled"); } void Room::discardMessage(const QString& txnId) { -- cgit v1.2.3 From c2d87291dbf8bd240e3e96138ec52aa5da22416b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 26 May 2022 12:50:30 +0200 Subject: Move encryptFile/decryptFile out of EncryptedFileMetadata These are not operations on EncryptedFileMetadata but rather on a combination of EncryptedFileMetadata and ciphertext. If C++ had multimethods these could be bound to such a combination. --- lib/room.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 0cef1025..4cb01a39 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2524,8 +2524,7 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, QFile file(localFilename.toLocalFile()); file.open(QFile::ReadOnly); QByteArray data; - std::tie(fileMetadata, data) = - EncryptedFileMetadata::encryptFile(file.readAll()); + std::tie(fileMetadata, data) = encryptFile(file.readAll()); tempFile.write(data); tempFile.close(); fileName = QFileInfo(tempFile).absoluteFilePath(); -- cgit v1.2.3 From c2e9256b1c334bdadcc208429084cbc83496fb4b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 26 May 2022 12:57:23 +0200 Subject: Cleanup and address Sonar warnings --- lib/room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/room.cpp') diff --git a/lib/room.cpp b/lib/room.cpp index 4cb01a39..26fe80e3 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2274,7 +2274,7 @@ QString Room::Private::doPostFile(RoomEventPtr&& msgEvent, const QUrl& localUrl) const auto& transferJob = fileTransfers.value(txnId).job; connect(q, &Room::fileTransferCompleted, transferJob, [this, txnId](const QString& tId, const QUrl&, - const FileSourceInfo fileMetadata) { + const FileSourceInfo& fileMetadata) { if (tId != txnId) return; -- cgit v1.2.3 From 64797165f04a16d290dd27c2f962060b40f85be3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 25 May 2022 22:48:53 +0200 Subject: Refactor creation of Megolm sessions in Room Notably, replace a multi-level hash map with QMultiHash and factor out Room::P::createOlmSession(). --- lib/room.cpp | 156 ++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 91 insertions(+), 65 deletions(-) (limited to 'lib/room.cpp') 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()->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(encrypted, connection->olmAccount()->identityKeys().curve25519); } - QHash getDevicesWithoutKey() const + QMultiHash getDevicesWithoutKey() const { - QHash devices; - for (const auto& user : q->users()) { - devices[user->id()] = q->connection()->devicesForUser(user->id()); + QMultiHash 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 devices, int index) + void sendRoomKeyToDevices(const QByteArray& sessionId, + const QByteArray& sessionKey, + const QMultiHash& devices, + int index) { - qCDebug(E2EE) << "Sending room key to devices" << sessionId, sessionKey.toHex(); + qDebug(E2EE) << "Sending room key to devices:" << sessionId + << sessionKey.toHex(); QHash> hash; - for (const auto& user : devices.keys()) { - QHash 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(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> 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& devices) { + void sendMegolmSession(const QMultiHash& 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 {}; -- cgit v1.2.3 From 0f8335a32debc4c61d9fc9875c79c0ba6ba05357 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 27 May 2022 19:09:26 +0200 Subject: Move some Meg/Olm session logic from Room::Private to Connection::Private Functions (Room::Private::)createOlmSession, payloadForUserDevice and sendRoomKeyToDevices don't have a lot to do with the given Room object but deal with quite a few things stored in Connection. This commit moves them to Connection::Private, exposing sendSessionKeyToDevices (the new name for sendRoomKeyToDevices) in Connection so that Room could call it from Room::P::sendMegolmSession(). While moving these over, a few additional things were adjusted: - more functions marked as const - a few functions could be moved now from Connection to Connection::Private - false slots in Connection (such as picklingMode) are moved out of the slots block - keys.yml in Matrix CS API definitions has been adjusted to match the real structure of `/claim` response (see quotient-im/matrix-spec repo); csapi/keys.h has been regenerated accordingly. --- lib/room.cpp | 113 +++-------------------------------------------------------- 1 file changed, 4 insertions(+), 109 deletions(-) (limited to 'lib/room.cpp') 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 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("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(encrypted, connection->olmAccount()->identityKeys().curve25519); - } - QMultiHash getDevicesWithoutKey() const { QMultiHash 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& devices, - int index) - { - qDebug(E2EE) << "Sending room key to devices:" << sessionId - << sessionKey.toHex(); - QHash> 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(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> 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& devices) { + void sendMegolmSession(const QMultiHash& 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 -- cgit v1.2.3