aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/connection.cpp38
-rw-r--r--lib/connection.h3
-rw-r--r--lib/database.cpp18
-rw-r--r--lib/database.h3
-rw-r--r--lib/events/encryptedfile.cpp4
-rw-r--r--lib/events/roomevent.h8
-rw-r--r--lib/room.cpp27
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 =