diff options
-rw-r--r-- | lib/connection.cpp | 38 | ||||
-rw-r--r-- | lib/connection.h | 3 | ||||
-rw-r--r-- | lib/database.cpp | 18 | ||||
-rw-r--r-- | lib/database.h | 3 | ||||
-rw-r--r-- | lib/events/encryptedfile.cpp | 4 | ||||
-rw-r--r-- | lib/events/roomevent.h | 8 | ||||
-rw-r--r-- | lib/room.cpp | 27 |
7 files changed, 77 insertions, 24 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp index 691f4ab3..0ef002ca 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -33,6 +33,7 @@ #include "jobs/downloadfilejob.h" #include "jobs/mediathumbnailjob.h" #include "jobs/syncjob.h" +#include <variant> #ifdef Quotient_E2EE_ENABLED # include "e2ee/qolmaccount.h" @@ -221,13 +222,23 @@ public: QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr<QOlmAccount>& olmAccount) { Q_ASSERT(message.type() == QOlmMessage::PreKey); - for(auto& session : olmSessions[senderKey]) { + for (size_t i = 0; i < olmSessions[senderKey].size(); i++) { + auto& session = olmSessions[senderKey][i]; const auto matches = session->matchesInboundSessionFrom(senderKey, message); if(std::holds_alternative<bool>(matches) && std::get<bool>(matches)) { qCDebug(E2EE) << "Found inbound session"; const auto result = session->decrypt(message); if(std::holds_alternative<QString>(result)) { q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); + auto pickle = session->pickle(q->picklingMode()); + if (std::holds_alternative<QByteArray>(pickle)) { + q->database()->updateOlmSession(senderKey, session->sessionId(), std::get<QByteArray>(pickle)); + } else { + qCWarning(E2EE) << "Failed to pickle olm session."; + } + auto s = std::move(session); + olmSessions[senderKey].erase(olmSessions[senderKey].begin() + i); + olmSessions[senderKey].insert(olmSessions[senderKey].begin(), std::move(s)); return std::get<QString>(result); } else { qCDebug(E2EE) << "Failed to decrypt prekey message"; @@ -248,7 +259,7 @@ public: } const auto result = newSession->decrypt(message); saveSession(newSession, senderKey); - olmSessions[senderKey].push_back(std::move(newSession)); + olmSessions[senderKey].insert(olmSessions[senderKey].begin(), std::move(newSession)); if(std::holds_alternative<QString>(result)) { return std::get<QString>(result); } else { @@ -259,10 +270,20 @@ public: QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) { Q_ASSERT(message.type() == QOlmMessage::General); - for(auto& session : olmSessions[senderKey]) { + for (size_t i = 0; i < olmSessions[senderKey].size(); i++) { + auto& session = olmSessions[senderKey][i]; const auto result = session->decrypt(message); if(std::holds_alternative<QString>(result)) { q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); + auto pickle = session->pickle(q->picklingMode()); + if (std::holds_alternative<QByteArray>(pickle)) { + q->database()->updateOlmSession(senderKey, session->sessionId(), std::get<QByteArray>(pickle)); + } else { + qCWarning(E2EE) << "Failed to pickle olm session."; + } + auto s = std::move(session); + olmSessions[senderKey].erase(olmSessions[senderKey].begin() + i); + olmSessions[senderKey].insert(olmSessions[senderKey].begin(), std::move(s)); return std::get<QString>(result); } } @@ -2192,21 +2213,24 @@ bool Connection::hasOlmSession(User* user, const QString& deviceId) const QPair<QOlmMessage::Type, QByteArray> Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) { - //TODO be smarter about choosing a session; see e2ee impl guide - //TODO do we need to save the olm session after sending a message? const auto& curveKey = curveKeyForUserDevice(user->id(), device); QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); auto result = d->olmSessions[curveKey][0]->encrypt(message); + auto pickle = d->olmSessions[curveKey][0]->pickle(picklingMode()); + if (std::holds_alternative<QByteArray>(pickle)) { + database()->updateOlmSession(curveKey, d->olmSessions[curveKey][0]->sessionId(), std::get<QByteArray>(pickle)); + } else { + qCWarning(E2EE) << "Failed to pickle olm session."; + } return qMakePair(type, result.toCiphertext()); } -//TODO be more consistent with curveKey and identityKey void Connection::createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey) { auto session = QOlmSession::createOutboundSession(olmAccount(), theirIdentityKey, theirOneTimeKey); if (std::holds_alternative<QOlmError>(session)) { - //TODO something qCWarning(E2EE) << "Failed to create olm session for " << theirIdentityKey << std::get<QOlmError>(session); + return; } d->saveSession(std::get<std::unique_ptr<QOlmSession>>(session), theirIdentityKey); d->olmSessions[theirIdentityKey].push_back(std::move(std::get<std::unique_ptr<QOlmSession>>(session))); diff --git a/lib/connection.h b/lib/connection.h index 6f75b068..52e1700c 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -327,8 +327,7 @@ public: void saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data); - //This currently assumes that an olm session with (user, device) exists - //TODO make this return an event? + //This assumes that an olm session with (user, device) exists QPair<QOlmMessage::Type, QByteArray> olmEncryptMessage(User* user, const QString& device, const QByteArray& message); void createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey); #endif // Quotient_E2EE_ENABLED diff --git a/lib/database.cpp b/lib/database.cpp index 863cbf0d..d9dce517 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -9,6 +9,7 @@ #include <QtCore/QStandardPaths> #include <QtCore/QDebug> #include <QtCore/QDir> +#include <ctime> #include "e2ee/e2ee.h" #include "e2ee/qolmsession.h" @@ -166,7 +167,7 @@ void Database::saveOlmSession(const QString& senderKey, const QString& sessionId UnorderedMap<QString, std::vector<QOlmSessionPtr>> Database::loadOlmSessions(const PicklingMode& picklingMode) { - auto query = prepareQuery(QStringLiteral("SELECT * FROM olm_sessions;")); + auto query = prepareQuery(QStringLiteral("SELECT * FROM olm_sessions ORDER BY lastReceived DESC;")); transaction(); execute(query); commit(); @@ -317,7 +318,6 @@ QOlmOutboundGroupSessionPtr Database::loadCurrentOutboundMegolmSession(const QSt void Database::setDevicesReceivedKey(const QString& roomId, QHash<User *, QStringList> devices, const QString& sessionId, int index) { - //TODO this better auto connection = dynamic_cast<Connection *>(parent()); transaction(); for (const auto& user : devices.keys()) { @@ -339,7 +339,7 @@ QHash<QString, QStringList> Database::devicesWithoutKey(Room* room, const QStrin { auto connection = dynamic_cast<Connection *>(parent()); QHash<QString, QStringList> devices; - for (const auto& user : room->users()) {//TODO does this include invited & left? + for (const auto& user : room->users()) { devices[user->id()] = connection->devicesForUser(user); } @@ -354,3 +354,15 @@ QHash<QString, QStringList> Database::devicesWithoutKey(Room* room, const QStrin } return devices; } + +void Database::updateOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle) +{ + auto query = prepareQuery(QStringLiteral("UPDATE olm_sessions SET pickle=:pickle WHERE senderKey=:senderKey AND sessionId=:sessionId;")); + query.bindValue(":pickle", pickle); + query.bindValue(":senderKey", senderKey); + query.bindValue(":sessionId", sessionId); + transaction(); + execute(query); + commit(); +} + diff --git a/lib/database.h b/lib/database.h index ed567a86..76d86f12 100644 --- a/lib/database.h +++ b/lib/database.h @@ -33,7 +33,7 @@ public: QByteArray accountPickle(); void setAccountPickle(const QByteArray &pickle); void clear(); - void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray &pickle, const QDateTime& timestamp); + void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle, const QDateTime& timestamp); UnorderedMap<QString, std::vector<QOlmSessionPtr>> loadOlmSessions(const PicklingMode& picklingMode); UnorderedMap<std::pair<QString, QString>, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode); void saveMegolmSession(const QString& roomId, const QString& senderKey, const QString& sessionKey, const QString& ed25519Key, const QByteArray& pickle); @@ -43,6 +43,7 @@ public: 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); // Returns a map User -> [Device] that have not received key yet QHash<QString, QStringList> devicesWithoutKey(Room* room, const QString &sessionId); diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index e90be428..bb4e26c7 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -56,6 +56,7 @@ QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const std::pair<EncryptedFile, QByteArray> EncryptedFile::encryptFile(const QByteArray &plainText) { +#ifdef Quotient_E2EE_ENABLED QByteArray k = getRandom(32); auto kBase64 = k.toBase64(); QByteArray iv = getRandom(16); @@ -73,6 +74,9 @@ std::pair<EncryptedFile, QByteArray> EncryptedFile::encryptFile(const QByteArray auto ivBase64 = iv.toBase64(); EncryptedFile file = {{}, key, ivBase64.left(ivBase64.indexOf('=')), {{QStringLiteral("sha256"), hash.left(hash.indexOf('='))}}, "v2"_ls}; return {file, cipherText}; +#else + return {{}, {}}; +#endif } void JsonObjectConverter<EncryptedFile>::dumpTo(QJsonObject& jo, diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index c4b0131a..a7d6c428 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -80,6 +80,14 @@ using RoomEventPtr = event_ptr_tt<RoomEvent>; using RoomEvents = EventsArray<RoomEvent>; using RoomEventsRange = Range<RoomEvents>; +template <> +inline EventPtr doLoadEvent(const QJsonObject& json, const QString& matrixType) +{ + if (matrixType == "m.room.encrypted") + return RoomEvent::factory.loadEvent(json, matrixType); + return Event::factory.loadEvent(json, matrixType); +} + class QUOTIENT_API CallEventBase : public RoomEvent { public: CallEventBase(Type type, event_mtype_t matrixType, const QString& callId, diff --git a/lib/room.cpp b/lib/room.cpp index 1762fd5a..6d938d4a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -438,8 +438,8 @@ public: const auto sessionKey = currentOutboundMegolmSession->sessionKey(); if(std::holds_alternative<QOlmError>(sessionKey)) { - qCWarning(E2EE) << "Session error"; - //TODO something + qCWarning(E2EE) << "Failed to load key for new megolm session"; + return; } addInboundGroupSession(q->connection()->olmAccount()->identityKeys().curve25519, currentOutboundMegolmSession->sessionId(), std::get<QByteArray>(sessionKey), QString(connection->olmAccount()->identityKeys().ed25519)); } @@ -448,7 +448,6 @@ public: { // Noisy but nice for debugging //qCDebug(E2EE) << "Creating the payload for" << user->id() << device << sessionId << sessionKey.toHex(); - //TODO: store {user->id(), device, sessionId, theirIdentityKey}; required for key requests const auto event = makeEvent<RoomKeyEvent>("m.megolm.v1.aes-sha2", q->id(), sessionId, sessionKey, q->localUser()->id()); QJsonObject payloadJson = event->fullJson(); payloadJson["recipient"] = user->id(); @@ -493,6 +492,9 @@ public: hash[user->id()] = u; } } + if (hash.isEmpty()) { + return; + } auto job = connection->callApi<ClaimKeysJob>(hash); connect(job, &BaseJob::success, q, [job, this, sessionId, sessionKey, devices, index](){ Connection::UsersToDevicesToEvents usersToDevicesToEvents; @@ -514,7 +516,6 @@ public: signedData.remove("signatures"); auto signatureMatch = QOlmUtility().ed25519Verify(connection->edKeyForUserDevice(user->id(), device).toLatin1(), QJsonDocument(signedData).toJson(QJsonDocument::Compact), signature); if (std::holds_alternative<QOlmError>(signatureMatch)) { - //TODO i think there are more failed signature checks than expected. Investigate qCWarning(E2EE) << "Failed to verify one-time-key signature for" << user->id() << device << ". Skipping this device."; continue; } else { @@ -524,8 +525,10 @@ public: usersToDevicesToEvents[user->id()][device] = payloadForUserDevice(user, device, sessionId, sessionKey); } } - connection->sendToDevices("m.room.encrypted", usersToDevicesToEvents); - connection->database()->setDevicesReceivedKey(q->id(), devices, sessionId, index); + if (!usersToDevicesToEvents.empty()) { + connection->sendToDevices("m.room.encrypted", usersToDevicesToEvents); + connection->database()->setDevicesReceivedKey(q->id(), devices, sessionId, index); + } }); } @@ -534,8 +537,8 @@ public: const auto sessionId = currentOutboundMegolmSession->sessionId(); const auto _sessionKey = currentOutboundMegolmSession->sessionKey(); if(std::holds_alternative<QOlmError>(_sessionKey)) { - qCWarning(E2EE) << "Session error"; - //TODO something + qCWarning(E2EE) << "Error loading session key"; + return; } const auto sessionKey = std::get<QByteArray>(_sessionKey); const auto senderKey = q->connection()->olmAccount()->identityKeys().curve25519; @@ -575,7 +578,6 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) connect(this, &Room::userAdded, this, [this, connection](){ if(usesEncryption()) { connection->encryptionUpdate(this); - //TODO key at currentIndex to all user devices } }); d->groupSessions = connection->loadRoomMegolmSessions(this); @@ -2070,18 +2072,20 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) const RoomEvent* _event = pEvent; if (q->usesEncryption()) { +#ifndef Quotient_E2EE_ENABLED + qWarning() << "This build of libQuotient does not support E2EE."; + return {}; +#else if (!hasValidMegolmSession() || shouldRotateMegolmSession()) { createMegolmSession(); } const auto devicesWithoutKey = getDevicesWithoutKey(); sendMegolmSession(devicesWithoutKey); - //TODO check if we increment the sent message count const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(pEvent->fullJson()).toJson()); currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); if(std::holds_alternative<QOlmError>(encrypted)) { - //TODO something qWarning(E2EE) << "Error encrypting message" << std::get<QOlmError>(encrypted); return {}; } @@ -2094,6 +2098,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) } // We show the unencrypted event locally while pending. The echo check will throw the encrypted version out _event = encryptedEvent; +#endif } if (auto call = |