aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobias Fella <fella@posteo.de>2021-12-07 00:25:05 +0100
committerTobias Fella <fella@posteo.de>2021-12-07 00:25:05 +0100
commit47bd4dfb2bc720d2b5919b93985f87d918af572a (patch)
tree073836555481025d1ebd5c5c200a5933336db295
parent9217026e46d7ac0d761cc5206d7ef00978558c47 (diff)
downloadlibquotient-47bd4dfb2bc720d2b5919b93985f87d918af572a.tar.gz
libquotient-47bd4dfb2bc720d2b5919b93985f87d918af572a.zip
Port E2EE to database instead of JSON files
-rw-r--r--CMakeLists.txt5
-rw-r--r--lib/connection.cpp23
-rw-r--r--lib/crypto/e2ee.h6
-rw-r--r--lib/crypto/qolminboundsession.cpp2
-rw-r--r--lib/crypto/qolminboundsession.h2
-rw-r--r--lib/crypto/qolmsession.h3
-rw-r--r--lib/database.cpp240
-rw-r--r--lib/database.h46
-rw-r--r--lib/encryptionmanager.cpp96
-rw-r--r--lib/events/encryptedevent.cpp1
-rw-r--r--lib/logging.h1
-rw-r--r--lib/room.cpp82
12 files changed, 335 insertions, 172 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dbb43f89..9f886094 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -83,7 +83,7 @@ else()
set(QtExtraModules "Multimedia") # See #483
endif()
string(REGEX REPLACE "^(.).*" "Qt\\1" Qt ${QtMinVersion}) # makes "Qt5" or "Qt6"
-find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModules})
+find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test Sql ${QtExtraModules})
get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE)
message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}")
@@ -133,6 +133,7 @@ list(APPEND lib_SRCS
lib/eventitem.cpp
lib/accountregistry.cpp
lib/mxcreply.cpp
+ lib/database.cpp
lib/events/event.cpp
lib/events/roomevent.cpp
lib/events/stateevent.cpp
@@ -327,7 +328,7 @@ if (${PROJECT_NAME}_ENABLE_E2EE)
find_dependency(OpenSSL)") # For QuotientConfig.cmake.in
endif()
-target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui ${QTKEYCHAIN_LIBRARIES})
+target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui ${Qt}::Sql ${QTKEYCHAIN_LIBRARIES})
if (Qt STREQUAL Qt5) # See #483
target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia)
diff --git a/lib/connection.cpp b/lib/connection.cpp
index ac428a62..f344807e 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -62,6 +62,8 @@
# include <qt5keychain/keychain.h>
#endif
+#include "database.h"
+
using namespace Quotient;
// This is very much Qt-specific; STL iterators don't have key() and value()
@@ -274,6 +276,7 @@ Connection::Connection(const QUrl& server, QObject* parent)
});
#endif
d->q = this; // All d initialization should occur before this line
+ Database::instance();
}
Connection::Connection(QObject* parent) : Connection({}, parent) {}
@@ -439,6 +442,7 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs)
auto loginJob =
q->callApi<LoginJob>(std::forward<LoginArgTs>(loginArgs)...);
connect(loginJob, &BaseJob::success, q, [this, loginJob] {
+ Database::instance().clear(loginJob->userId());
data->setToken(loginJob->accessToken().toLatin1());
data->setDeviceId(loginJob->deviceId());
completeSetup(loginJob->userId());
@@ -504,7 +508,7 @@ void Connection::Private::completeSetup(const QString& mxId)
encryptionManager = new EncryptionManager(q);
- if (accountSettings.encryptionAccountPickle().isEmpty()) {
+ if (Database::instance().accountPickle(data->userId()).isEmpty()) {
// create new account and save unpickle data
olmAccount->createNewAccount();
auto job = q->callApi<UploadKeysJob>(olmAccount->deviceKeys());
@@ -513,7 +517,7 @@ void Connection::Private::completeSetup(const QString& mxId)
});
} else {
// account already existing
- auto pickle = accountSettings.encryptionAccountPickle();
+ auto pickle = Database::instance().accountPickle(data->userId());
olmAccount->unpickle(pickle, picklingMode);
}
#endif // Quotient_E2EE_ENABLED
@@ -1978,15 +1982,9 @@ void Connection::Private::saveDevicesList()
rootObj.insert(QStringLiteral("sync_token"), q->nextBatchToken());
}
-#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
- const auto data =
- cacheToBinary ? QCborValue::fromJsonValue(rootObj).toCbor()
- : QJsonDocument(rootObj).toJson(QJsonDocument::Compact);
-#else
+
QJsonDocument json { rootObj };
- const auto data = cacheToBinary ? json.toBinaryData()
- : json.toJson(QJsonDocument::Compact);
-#endif
+ const auto data = json.toJson();
qCDebug(PROFILER) << "DeviceList generated in" << et;
outFile.write(data.data(), data.size());
@@ -2043,11 +2041,10 @@ PicklingMode Connection::picklingMode() const
void Connection::saveOlmAccount()
{
- qCDebug(E2EE) << "Saving olm account";
+ qDebug() << "Saving olm account";
#ifdef Quotient_E2EE_ENABLED
auto pickle = d->olmAccount->pickle(d->picklingMode);
- AccountSettings(d->data->userId()).setEncryptionAccountPickle(std::get<QByteArray>(pickle));
- //TODO handle errors
+ Database::instance().setAccountPickle(userId(), std::get<QByteArray>(pickle));
#endif
}
diff --git a/lib/crypto/e2ee.h b/lib/crypto/e2ee.h
index 2d280185..41cd2878 100644
--- a/lib/crypto/e2ee.h
+++ b/lib/crypto/e2ee.h
@@ -49,6 +49,12 @@ struct Encrypted {
using PicklingMode = std::variant<Unencrypted, Encrypted>;
+class QOlmSession;
+using QOlmSessionPtr = std::unique_ptr<QOlmSession>;
+
+class QOlmInboundGroupSession;
+using QOlmInboundGroupSessionPtr = std::unique_ptr<QOlmInboundGroupSession>;
+
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
diff --git a/lib/crypto/qolminboundsession.cpp b/lib/crypto/qolminboundsession.cpp
index beaf3299..31d699f1 100644
--- a/lib/crypto/qolminboundsession.cpp
+++ b/lib/crypto/qolminboundsession.cpp
@@ -72,7 +72,7 @@ QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const
return pickledBuf;
}
-std::variant<std::unique_ptr<QOlmInboundGroupSession>, QOlmError> QOlmInboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode)
+std::variant<std::unique_ptr<QOlmInboundGroupSession>, QOlmError> QOlmInboundGroupSession::unpickle(const QByteArray &pickled, const PicklingMode &mode)
{
QByteArray pickledBuf = pickled;
const auto groupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]);
diff --git a/lib/crypto/qolminboundsession.h b/lib/crypto/qolminboundsession.h
index 36ab4942..362e42ba 100644
--- a/lib/crypto/qolminboundsession.h
+++ b/lib/crypto/qolminboundsession.h
@@ -27,7 +27,7 @@ public:
QByteArray pickle(const PicklingMode &mode) const;
//! Deserialises from encrypted Base64 that was previously obtained by pickling
//! an `OlmInboundGroupSession`.
- static std::variant<std::unique_ptr<QOlmInboundGroupSession>, QOlmError> unpickle(QByteArray &picked, const PicklingMode &mode);
+ static std::variant<std::unique_ptr<QOlmInboundGroupSession>, QOlmError> unpickle(const QByteArray &picked, const PicklingMode &mode);
//! Decrypts ciphertext received for this group session.
std::variant<std::pair<QString, uint32_t>, QOlmError> decrypt(const QByteArray &message);
//! Export the base64-encoded ratchet key for this session, at the given index,
diff --git a/lib/crypto/qolmsession.h b/lib/crypto/qolmsession.h
index 7a040b3d..711ca66b 100644
--- a/lib/crypto/qolmsession.h
+++ b/lib/crypto/qolmsession.h
@@ -73,7 +73,4 @@ private:
static std::variant<std::unique_ptr<QOlmSession>, QOlmError> createInbound(QOlmAccount *account, const QOlmMessage& preKeyMessage, bool from = false, const QString& theirIdentityKey = "");
OlmSession* m_session;
};
-
-using QOlmSessionPtr = std::unique_ptr<QOlmSession>;
-
} //namespace Quotient
diff --git a/lib/database.cpp b/lib/database.cpp
new file mode 100644
index 00000000..153aab31
--- /dev/null
+++ b/lib/database.cpp
@@ -0,0 +1,240 @@
+// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "database.h"
+
+#include <QtSql/QSqlDatabase>
+#include <QtSql/QSqlQuery>
+#include <QtSql/QSqlError>
+#include <QtCore/QStandardPaths>
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
+
+#include "crypto/e2ee.h"
+#include "crypto/qolmsession.h"
+#include "crypto/qolminboundsession.h"
+
+//TODO: delete room specific data when leaving room
+
+using namespace Quotient;
+Database::Database()
+{
+ QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"));
+ QString databasePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
+ QDir(databasePath).mkpath(databasePath);
+ QSqlDatabase::database().setDatabaseName(databasePath + QStringLiteral("/database.db3"));
+ QSqlDatabase::database().open();
+
+ switch(version()) {
+ case 0: migrateTo1();
+ }
+}
+
+int Database::version()
+{
+ auto query = execute(QStringLiteral("PRAGMA user_version;"));
+ if (query.next()) {
+ bool ok;
+ int value = query.value(0).toInt(&ok);
+ qDebug() << "Database version" << value;
+ if (ok)
+ return value;
+ } else {
+ qCritical() << "Failed to check database version";
+ }
+ return -1;
+}
+
+QSqlQuery Database::execute(const QString &queryString)
+{
+ auto query = QSqlDatabase::database().exec(queryString);
+ if (query.lastError().type() != QSqlError::NoError) {
+ qCritical() << "Failed to execute query";
+ qCritical() << query.lastQuery();
+ qCritical() << query.lastError();
+ }
+ return query;
+}
+
+QSqlQuery Database::execute(QSqlQuery &query)
+{
+ if (!query.exec()) {
+ qCritical() << "Failed to execute query";
+ qCritical() << query.lastQuery();
+ qCritical() << query.lastError();
+ }
+ return query;
+}
+
+void Database::transaction()
+{
+ QSqlDatabase::database().transaction();
+}
+
+void Database::commit()
+{
+ QSqlDatabase::database().commit();
+}
+
+void Database::migrateTo1()
+{
+ qDebug() << "Migrating database to version 1";
+ transaction();
+ execute(QStringLiteral("CREATE TABLE Accounts (matrixId TEXT UNIQUE, pickle TEXT);"));
+ execute(QStringLiteral("CREATE TABLE OlmSessions (matrixId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);"));
+ execute(QStringLiteral("CREATE TABLE InboundMegolmSessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);"));
+ execute(QStringLiteral("CREATE TABLE OutboundMegolmSessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);"));
+ execute(QStringLiteral("CREATE TABLE GroupSessionIndexRecord (matrixId TEXT, roomId TEXT, sessionId TEXT, i INTEGER, eventId TEXT, ts INTEGER);"));
+ execute(QStringLiteral("PRAGMA user_version = 1;"));
+ commit();
+}
+
+QByteArray Database::accountPickle(const QString &id)
+{
+ QSqlQuery query;
+ query.prepare(QStringLiteral("SELECT pickle FROM Accounts WHERE matrixId=:matrixId;"));
+ query.bindValue(":matrixId", id);
+ execute(query);
+ if (query.next()) {
+ return query.value(QStringLiteral("pickle")).toByteArray();
+ }
+ return {};
+}
+
+void Database::setAccountPickle(const QString &id, const QByteArray &pickle)
+{
+ QSqlQuery query;
+ query.prepare(QStringLiteral("INSERT INTO Accounts(matrixId, pickle) VALUES(:matrixId, :pickle) ON CONFLICT (matrixId) DO UPDATE SET pickle=:pickle WHERE matrixId=:matrixId;"));
+ query.bindValue(":matrixId", id);
+ query.bindValue(":pickle", pickle);
+ transaction();
+ execute(query);
+ commit();
+}
+
+void Database::clear(const QString &id)
+{
+ QSqlQuery query;
+ query.prepare(QStringLiteral("DELETE FROM Accounts(matrixId, pickle) WHERE matrixId=:matrixId;"));
+ query.bindValue(":matrixId", id);
+
+ QSqlQuery sessionsQuery;
+ sessionsQuery.prepare(QStringLiteral("DELETE FROM OlmSessions WHERE matrixId=:matrixId;"));
+ sessionsQuery.bindValue(":matrixId", id);
+
+ QSqlQuery megolmSessionsQuery;
+ megolmSessionsQuery.prepare(QStringLiteral("DELETE FROM InboundMegolmSessions WHERE matrixId=:matrixId;"));
+ megolmSessionsQuery.bindValue(":matrixId", id);
+
+ QSqlQuery groupSessionIndexRecordQuery;
+ groupSessionIndexRecordQuery.prepare(QStringLiteral("DELETE FROM GroupSessionIndexRecord WHERE matrixId=:matrixId;"));
+ groupSessionIndexRecordQuery.bindValue(":matrixId", matrixId);
+
+ transaction();
+ execute(query);
+ execute(sessionsQuery);
+ execute(megolmSessionsQuery);
+ execute(groupSessionIndexRecordQuery);
+ commit();
+
+}
+
+void Database::saveOlmSession(const QString& matrixId, const QString& senderKey, const QString& sessionId, const QByteArray &pickle)
+{
+ QSqlQuery query;
+ query.prepare(QStringLiteral("INSERT INTO OlmSessions(matrixId, senderKey, sessionId, pickle) VALUES(:matrixId, :senderKey, :sessionId, :pickle);"));
+ query.bindValue(":matrixId", matrixId);
+ query.bindValue(":senderKey", senderKey);
+ query.bindValue(":sessionId", sessionId);
+ query.bindValue(":pickle", pickle);
+ transaction();
+ execute(query);
+ commit();
+}
+
+UnorderedMap<QString, std::vector<QOlmSessionPtr>> Database::loadOlmSessions(const QString& matrixId, const PicklingMode& picklingMode)
+{
+ QSqlQuery query;
+ query.prepare(QStringLiteral("SELECT * FROM OlmSessions WHERE matrixId=:matrixId;"));
+ query.bindValue(":matrixId", matrixId);
+ transaction();
+ execute(query);
+ commit();
+ UnorderedMap<QString, std::vector<QOlmSessionPtr>> sessions;
+ while (query.next()) {
+ auto session = QOlmSession::unpickle(query.value("pickle").toByteArray(), picklingMode);
+ if (std::holds_alternative<QOlmError>(session)) {
+ qCWarning(E2EE) << "Failed to unpickle olm session";
+ continue;
+ }
+ sessions[query.value("senderKey").toString()].push_back(std::move(std::get<QOlmSessionPtr>(session)));
+ }
+ return sessions;
+}
+
+UnorderedMap<QPair<QString, QString>, QOlmInboundGroupSessionPtr> Database::loadMegolmSessions(const QString& matrixId, const QString& roomId, const PicklingMode& picklingMode)
+{
+ QSqlQuery query;
+ query.prepare(QStringLiteral("SELECT * FROM InboundMegolmSessions WHERE matrixId=:matrixId AND roomId=:roomId;"));
+ query.bindValue(":matrixId", matrixId);
+ query.bindValue(":roomId", roomId);
+ transaction();
+ execute(query);
+ commit();
+ UnorderedMap<QPair<QString, QString>, QOlmInboundGroupSessionPtr> sessions;
+ while (query.next()) {
+ auto session = QOlmInboundGroupSession::unpickle(query.value("pickle").toByteArray(), picklingMode);
+ if (std::holds_alternative<QOlmError>(session)) {
+ qCWarning(E2EE) << "Failed to unpickle megolm session";
+ continue;
+ }
+ sessions[{query.value("senderKey").toString(), query.value("sessionId").toString()}] = std::move(std::get<QOlmInboundGroupSessionPtr>(session));
+ }
+ return sessions;
+}
+
+void Database::saveMegolmSession(const QString& matrixId, const QString& roomId, const QString& senderKey, const QString& sessionId, const QByteArray& pickle)
+{
+ QSqlQuery query;
+ query.prepare(QStringLiteral("INSERT INTO InboundMegolmSessions(matrixId, roomId, senderKey, sessionId, pickle) VALUES(:matrixId, :roomId, :senderKey, :sessionId, :pickle);"));
+ query.bindValue(":matrixId", matrixId);
+ query.bindValue(":roomId", roomId);
+ query.bindValue(":senderKey", senderKey);
+ query.bindValue(":sessionId", sessionId);
+ query.bindValue(":pickle", pickle);
+ transaction();
+ execute(query);
+ commit();
+}
+
+void Database::addGroupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts)
+{
+ QSqlQuery query;
+ query.prepare("INSERT INTO GroupSessionIndexRecord(matrixId, roomId, sessionId, i, eventId, ts) VALUES(:matrixId, :roomId, :sessionId, :index, :eventId, :ts);");
+ query.bindValue(":matrixId", matrixId);
+ query.bindValue(":roomId", roomId);
+ query.bindValue(":sessionId", sessionId);
+ query.bindValue(":index", index);
+ query.bindValue(":eventId", eventId);
+ query.bindValue(":ts", ts);
+ transaction();
+ execute(query);
+ commit();
+}
+
+QPair<QString, qint64> Database::groupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, qint64 index)
+{
+ QSqlQuery query;
+ query.prepare(QStringLiteral("SELECT * FROM GroupSessionIndexRecord WHERE matrixId=:matrixId AND roomId=:roomId AND sessionId=:sessionId AND i=:index;"));
+ query.bindValue(":matrixId", matrixId);
+ query.bindValue(":roomId", roomId);
+ query.bindValue(":sessionId", sessionId);
+ query.bindValue(":index", index);
+ transaction();
+ execute(query);
+ commit();
+ if (!query.next()) {
+ return {};
+ }
+ return {query.value("eventId").toString(), query.value("ts").toLongLong()};
+}
diff --git a/lib/database.h b/lib/database.h
new file mode 100644
index 00000000..ed356820
--- /dev/null
+++ b/lib/database.h
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <QtCore/QObject>
+#include <QtSql/QSqlQuery>
+#include <QtCore/QVector>
+
+#include "crypto/e2ee.h"
+
+namespace Quotient {
+class Database : public QObject
+{
+ Q_OBJECT
+
+public:
+ static Database &instance()
+ {
+ static Database _instance;
+ return _instance;
+ }
+
+ int version();
+ void transaction();
+ void commit();
+ QSqlQuery execute(const QString &queryString);
+ QSqlQuery execute(QSqlQuery &query);
+
+ QByteArray accountPickle(const QString &id);
+ void setAccountPickle(const QString &id, const QByteArray &pickle);
+ void clear(const QString &id);
+ void saveOlmSession(const QString& matrixId, const QString& senderKey, const QString& sessionId, const QByteArray &pickle);
+ UnorderedMap<QString, std::vector<QOlmSessionPtr>> loadOlmSessions(const QString& matrixId, const PicklingMode& picklingMode);
+ UnorderedMap<QPair<QString, QString>, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& matrixId, const QString& roomId, const PicklingMode& picklingMode);
+ void saveMegolmSession(const QString& matrixId, const QString& roomId, const QString& senderKey, const QString& sessionKey, const QByteArray& pickle);
+ void addGroupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts);
+ QPair<QString, qint64> groupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, qint64 index);
+
+
+private:
+ Database();
+
+ void migrateTo1();
+};
+}
diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp
index 5c106e12..e5fa978f 100644
--- a/lib/encryptionmanager.cpp
+++ b/lib/encryptionmanager.cpp
@@ -8,6 +8,7 @@
#include "connection.h"
#include "crypto/e2ee.h"
#include "events/encryptedfile.h"
+#include "database.h"
#include "csapi/keys.h"
@@ -37,90 +38,28 @@ public:
EncryptionManager* q;
- // A map from senderKey to InboundSession
- UnorderedMap<QString, QOlmSessionPtr> sessions;
- void updateDeviceKeys(
- const QHash<QString,
- QHash<QString, QueryKeysJob::DeviceInformation>>& deviceKeys)
- {
- for (auto userId : deviceKeys.keys()) {
- for (auto deviceId : deviceKeys.value(userId).keys()) {
- auto info = deviceKeys.value(userId).value(deviceId);
- // TODO: ed25519Verify, etc
- }
- }
- }
+ // A map from SenderKey to vector of InboundSession
+ UnorderedMap<QString, std::vector<QOlmSessionPtr>> sessions;
+
void loadSessions() {
- QFile file { static_cast<Connection *>(q->parent())->e2eeDataDir() % "/olmsessions.json" };
- if(!file.exists() || !file.open(QIODevice::ReadOnly)) {
- qCDebug(E2EE) << "No sessions cache exists.";
- return;
- }
- auto data = file.readAll();
- const auto json = data.startsWith('{')
- ? QJsonDocument::fromJson(data).object()
-#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
- : QCborValue::fromCbor(data).toJsonValue().toObject()
-#else
- : QJsonDocument::fromBinaryData(data).object()
-#endif
- ;
- if (json.isEmpty()) {
- qCWarning(MAIN) << "Sessions cache is empty";
- return;
- }
- for(const auto &senderKey : json["sessions"].toObject().keys()) {
- auto pickle = json["sessions"].toObject()[senderKey].toString();
- auto sessionResult = QOlmSession::unpickle(pickle.toLatin1(), static_cast<Connection *>(q->parent())->picklingMode());
- if(std::holds_alternative<QOlmError>(sessionResult)) {
- qCWarning(E2EE) << "Failed to unpickle olm session";
- continue;
- }
- sessions[senderKey] = std::move(std::get<QOlmSessionPtr>(sessionResult));
- }
+ sessions = Database::instance().loadOlmSessions(static_cast<Connection *>(q->parent())->userId(), static_cast<Connection *>(q->parent())->picklingMode());
}
- void saveSessions() {
- QFile outFile { static_cast<Connection *>(q->parent())->e2eeDataDir() % "/olmsessions.json" };
- if (!outFile.open(QFile::WriteOnly)) {
- qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":"
- << outFile.errorString();
- qCWarning(E2EE) << "Failed to write olm sessions";
+ void saveSession(QOlmSessionPtr& session, const QString &senderKey) {
+ auto pickleResult = session->pickle(static_cast<Connection *>(q->parent())->picklingMode());
+ if (std::holds_alternative<QOlmError>(pickleResult)) {
+ qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get<QOlmError>(pickleResult);
return;
}
-
- QJsonObject rootObj {
- { QStringLiteral("cache_version"),
- QJsonObject {
- { QStringLiteral("major"), 1 },
- { QStringLiteral("minor"), 0 } } }
- };
- {
- QJsonObject sessionsJson;
- for (const auto &session : sessions) {
- auto pickleResult = session.second->pickle(static_cast<Connection *>(q->parent())->picklingMode());
- if(std::holds_alternative<QOlmError>(pickleResult)) {
- qCWarning(E2EE) << "Failed to pickle session";
- continue;
- }
- sessionsJson[session.first] = QString(std::get<QByteArray>(pickleResult));
- }
- rootObj.insert(QStringLiteral("sessions"), sessionsJson);
- }
-
- const auto data = QJsonDocument(rootObj).toJson(QJsonDocument::Compact);
-
- outFile.write(data.data(), data.size());
- qCDebug(E2EE) << "Sessions saved to" << outFile.fileName();
+ Database::instance().saveOlmSession(static_cast<Connection *>(q->parent())->userId(), senderKey, session->sessionId(), std::get<QByteArray>(pickleResult));
}
QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr<QOlmAccount>& olmAccount)
{
Q_ASSERT(message.type() == QOlmMessage::PreKey);
- for(auto& session : sessions) {
- const auto matches = session.second->matchesInboundSessionFrom(senderKey, message);
+ for(auto& session : sessions[senderKey]) {
+ 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.second->decrypt(message);
- saveSessions();
+ const auto result = session->decrypt(message);
if(std::holds_alternative<QString>(result)) {
return std::get<QString>(result);
} else {
@@ -141,8 +80,8 @@ public:
qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId();
}
const auto result = newSession->decrypt(message);
- sessions[senderKey] = std::move(newSession);
- saveSessions();
+ saveSession(newSession, senderKey);
+ sessions[senderKey].push_back(std::move(newSession));
if(std::holds_alternative<QString>(result)) {
return std::get<QString>(result);
} else {
@@ -153,10 +92,9 @@ public:
QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey)
{
Q_ASSERT(message.type() == QOlmMessage::General);
- for(auto& session : sessions) {
- const auto result = session.second->decrypt(message);
+ for(auto& session : sessions[senderKey]) {
+ const auto result = session->decrypt(message);
if(std::holds_alternative<QString>(result)) {
- saveSessions();
return std::get<QString>(result);
}
}
diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp
index 2e0d7387..1b5e4441 100644
--- a/lib/events/encryptedevent.cpp
+++ b/lib/events/encryptedevent.cpp
@@ -3,6 +3,7 @@
#include "encryptedevent.h"
#include "roommessageevent.h"
+#include "events/eventloader.h"
using namespace Quotient;
diff --git a/lib/logging.h b/lib/logging.h
index 5bf050a9..fc0a4c99 100644
--- a/lib/logging.h
+++ b/lib/logging.h
@@ -19,6 +19,7 @@ Q_DECLARE_LOGGING_CATEGORY(SYNCJOB)
Q_DECLARE_LOGGING_CATEGORY(THUMBNAILJOB)
Q_DECLARE_LOGGING_CATEGORY(NETWORK)
Q_DECLARE_LOGGING_CATEGORY(PROFILER)
+Q_DECLARE_LOGGING_CATEGORY(DATABASE)
namespace Quotient {
// QDebug manipulators
diff --git a/lib/room.cpp b/lib/room.cpp
index e4fe2fb8..8181f16a 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -70,6 +70,8 @@
#include "crypto/qolminboundsession.h"
#endif // Quotient_E2EE_ENABLED
+#include "database.h"
+
using namespace Quotient;
using namespace std::placeholders;
using std::move;
@@ -363,75 +365,11 @@ public:
bool isLocalUser(const User* u) const { return u == q->localUser(); }
#ifdef Quotient_E2EE_ENABLED
- // A map from <sessionId, messageIndex> to <event_id, origin_server_ts>
- QHash<QPair<QString, uint32_t>, QPair<QString, QDateTime>>
- groupSessionIndexRecord; // TODO: cache
// A map from (senderKey, sessionId) to InboundGroupSession
- UnorderedMap<QPair<QString, QString>, std::unique_ptr<QOlmInboundGroupSession>> groupSessions;
+ UnorderedMap<QPair<QString, QString>, QOlmInboundGroupSessionPtr> groupSessions;
void loadMegOlmSessions() {
- QFile file { connection->e2eeDataDir() + QStringLiteral("/%1.json").arg(id) };
- if(!file.exists() || !file.open(QIODevice::ReadOnly)) {
- qCDebug(E2EE) << "No megolm sessions cache exists.";
- return;
- }
- auto data = file.readAll();
- const auto json = data.startsWith('{')
- ? QJsonDocument::fromJson(data).object()
-#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
- : QCborValue::fromCbor(data).toJsonValue().toObject()
-#else
- : QJsonDocument::fromBinaryData(data).object()
-#endif
- ;
- if (json.isEmpty()) {
- qCWarning(E2EE) << "Megolm sessions cache is empty";
- return;
- }
- for(const auto &s : json["sessions"].toArray()) {
- auto pickle = s.toObject()["pickle"].toString().toLatin1();
- auto senderKey = s.toObject()["sender_key"].toString();
- auto sessionId = s.toObject()["session_id"].toString();
- auto sessionResult = QOlmInboundGroupSession::unpickle(pickle, connection->picklingMode());
- if(std::holds_alternative<QOlmError>(sessionResult)) {
- qCWarning(E2EE) << "Failed to unpickle olm session";
- continue;
- }
- groupSessions[{senderKey, sessionId}] = std::move(std::get<std::unique_ptr<QOlmInboundGroupSession>>(sessionResult));
- }
- }
- void saveMegOlmSessions() {
- QFile outFile { connection->e2eeDataDir() + QStringLiteral("/%1.json").arg(id)};
- if (!outFile.open(QFile::WriteOnly)) {
- qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":"
- << outFile.errorString();
- qCWarning(E2EE) << "Failed to write megolm sessions";
- return;
- }
-
- QJsonObject rootObj {
- { QStringLiteral("cache_version"),
- QJsonObject {
- { QStringLiteral("major"), 1 },
- { QStringLiteral("minor"), 0 } } }
- };
- {
- QJsonArray sessionsJson;
- for (const auto &session : groupSessions) {
- auto pickleResult = session.second->pickle(connection->picklingMode());
- sessionsJson += QJsonObject {
- {QStringLiteral("sender_key"), session.first.first},
- {QStringLiteral("session_id"), session.first.second},
- {QStringLiteral("pickle"), QString(pickleResult)}
- };
- }
- rootObj.insert(QStringLiteral("sessions"), sessionsJson);
- }
-
- const auto data = QJsonDocument(rootObj).toJson(QJsonDocument::Compact);
-
- outFile.write(data.data(), data.size());
- qCDebug(E2EE) << "Megolm sessions saved to" << outFile.fileName();
+ groupSessions = Database::instance().loadMegolmSessions(q->localUser()->id(), q->id(), q->connection()->picklingMode());
}
bool addInboundGroupSession(QString senderKey, QString sessionId,
QString sessionKey)
@@ -449,8 +387,8 @@ public:
return false;
}
qCWarning(E2EE) << "Adding inbound session";
+ Database::instance().saveMegolmSession(q->localUser()->id(), q->id(), senderKey, sessionId, megolmSession->pickle(q->connection()->picklingMode()));
groupSessions[{senderKey, sessionId}] = std::move(megolmSession);
- saveMegOlmSessions();
return true;
}
@@ -476,17 +414,15 @@ public:
return QString();
}
const auto& [content, index] = std::get<std::pair<QString, uint32_t>>(decryptResult);
- const auto& [recordEventId, ts] = groupSessionIndexRecord.value({senderSession->sessionId(), index});
- if (eventId.isEmpty()) {
- groupSessionIndexRecord.insert({senderSession->sessionId(), index}, {recordEventId, timestamp});
+ const auto& [recordEventId, ts] = Database::instance().groupSessionIndexRecord(q->localUser()->id(), q->id(), senderSession->sessionId(), index);
+ if (recordEventId.isEmpty()) {
+ Database::instance().addGroupSessionIndexRecord(q->localUser()->id(), q->id(), senderSession->sessionId(), index, eventId, timestamp.toMSecsSinceEpoch());
} else {
- if ((eventId != recordEventId) || (ts != timestamp)) {
+ if ((eventId != recordEventId) || (ts != timestamp.toMSecsSinceEpoch())) {
qCWarning(E2EE) << "Detected a replay attack on event" << eventId;
return QString();
}
}
- //TODO is this necessary?
- saveMegOlmSessions();
return content;
}
#endif // Quotient_E2EE_ENABLED