aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/connection.cpp133
-rw-r--r--lib/connection.h27
-rw-r--r--lib/database.cpp32
-rw-r--r--lib/database.h11
-rw-r--r--lib/events/keyverificationevent.h74
-rw-r--r--lib/events/roomevent.h2
-rw-r--r--lib/keyverificationsession.cpp524
-rw-r--r--lib/keyverificationsession.h140
-rw-r--r--lib/room.cpp1
9 files changed, 906 insertions, 38 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp
index 9e4444df..3e1e556f 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 "database.h"
@@ -41,6 +42,8 @@
# include "e2ee/qolmsession.h"
# include "e2ee/qolmutility.h"
# include "e2ee/qolmutils.h"
+# include "events/keyverificationevent.h"
+# include "keyverificationsession.h"
#endif // Quotient_E2EE_ENABLED
#if QT_VERSION_MAJOR >= 6
@@ -366,8 +369,6 @@ public:
const OneTimeKeys &oneTimeKeyObject);
QString curveKeyForUserDevice(const QString& userId,
const QString& device) const;
- QString edKeyForUserDevice(const QString& userId,
- const QString& device) const;
QJsonObject encryptSessionKeyEvent(QJsonObject payloadJson,
const QString& targetUserId,
const QString& targetDeviceId) const;
@@ -968,7 +969,7 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents)
if (!toDeviceEvents.empty()) {
qCDebug(E2EE) << "Consuming" << toDeviceEvents.size()
<< "to-device events";
- for (auto&& tdEvt : toDeviceEvents)
+ for (auto&& tdEvt : toDeviceEvents) {
if (auto&& event = eventCast<EncryptedEvent>(std::move(tdEvt))) {
if (event->algorithm() != OlmV1Curve25519AesSha2AlgoKey) {
qCDebug(E2EE) << "Unsupported algorithm" << event->id()
@@ -983,7 +984,28 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents)
outdatedUsers += event->senderId();
encryptionUpdateRequired = true;
pendingEncryptedEvents.push_back(std::move(event));
+ continue;
}
+ switchOnType(*tdEvt,
+ [this](const KeyVerificationRequestEvent& event) {
+ auto session = new KeyVerificationSession(q->userId(), event, q, false, q);
+ emit q->newKeyVerificationSession(session);
+ }, [this](const KeyVerificationReadyEvent& event) {
+ emit q->incomingKeyVerificationReady(event);
+ }, [this](const KeyVerificationStartEvent& event) {
+ emit q->incomingKeyVerificationStart(event);
+ }, [this](const KeyVerificationAcceptEvent& event) {
+ emit q->incomingKeyVerificationAccept(event);
+ }, [this](const KeyVerificationKeyEvent& event) {
+ emit q->incomingKeyVerificationKey(event);
+ }, [this](const KeyVerificationMacEvent& event) {
+ emit q->incomingKeyVerificationMac(event);
+ }, [this](const KeyVerificationDoneEvent& event) {
+ emit q->incomingKeyVerificationDone(event);
+ }, [this](const KeyVerificationCancelEvent& event) {
+ emit q->incomingKeyVerificationCancel(event);
+ });
+ }
}
#endif
}
@@ -1005,9 +1027,25 @@ void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& eve
qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId()
<< "is not found at the connection" << q->objectName();
}
- },
- [](const Event& evt) {
- qCDebug(E2EE) << "Skipping encrypted to_device event, type"
+ }, [this](const KeyVerificationRequestEvent& event) {
+ auto session = new KeyVerificationSession(q->userId(), event, q, true, q);
+ emit q->newKeyVerificationSession(session);
+ }, [this](const KeyVerificationReadyEvent& event) {
+ emit q->incomingKeyVerificationReady(event);
+ }, [this](const KeyVerificationStartEvent& event) {
+ emit q->incomingKeyVerificationStart(event);
+ }, [this](const KeyVerificationAcceptEvent& event) {
+ emit q->incomingKeyVerificationAccept(event);
+ }, [this](const KeyVerificationKeyEvent& event) {
+ emit q->incomingKeyVerificationKey(event);
+ }, [this](const KeyVerificationMacEvent& event) {
+ emit q->incomingKeyVerificationMac(event);
+ }, [this](const KeyVerificationDoneEvent& event) {
+ emit q->incomingKeyVerificationDone(event);
+ }, [this](const KeyVerificationCancelEvent& event) {
+ emit q->incomingKeyVerificationCancel(event);
+ }, [](const Event& evt) {
+ qCWarning(E2EE) << "Skipping encrypted to_device event, type"
<< evt.matrixType();
});
}
@@ -2114,8 +2152,8 @@ void Connection::Private::saveDevicesList()
query.prepare(QStringLiteral(
"INSERT INTO tracked_devices"
- "(matrixId, deviceId, curveKeyId, curveKey, edKeyId, edKey) "
- "VALUES(:matrixId, :deviceId, :curveKeyId, :curveKey, :edKeyId, :edKey);"
+ "(matrixId, deviceId, curveKeyId, curveKey, edKeyId, edKey, verified) "
+ "SELECT :matrixId, :deviceId, :curveKeyId, :curveKey, :edKeyId, :edKey, :verified WHERE NOT EXISTS(SELECT 1 FROM tracked_devices WHERE matrixId=:matrixId AND deviceId=:deviceId);"
));
for (const auto& user : deviceKeys.keys()) {
for (const auto& device : deviceKeys[user]) {
@@ -2129,6 +2167,8 @@ void Connection::Private::saveDevicesList()
query.bindValue(":curveKey", device.keys[curveKeyId]);
query.bindValue(":edKeyId", edKeyId);
query.bindValue(":edKey", device.keys[edKeyId]);
+ // If the device gets saved here, it can't be verified
+ query.bindValue(":verified", false);
q->database()->execute(query);
}
@@ -2233,10 +2273,10 @@ QString Connection::Private::curveKeyForUserDevice(const QString& userId,
return deviceKeys[userId][device].keys["curve25519:" % device];
}
-QString Connection::Private::edKeyForUserDevice(const QString& userId,
- const QString& device) const
+QString Connection::edKeyForUserDevice(const QString& userId,
+ const QString& device) const
{
- return deviceKeys[userId][device].keys["ed25519:" % device];
+ return d->deviceKeys[userId][device].keys["ed25519:" % device];
}
bool Connection::Private::isKnownCurveKey(const QString& userId,
@@ -2298,7 +2338,7 @@ bool Connection::Private::createOlmSession(const QString& targetUserId,
const auto signature =
signedOneTimeKey->signature(targetUserId, targetDeviceId);
if (!verifier.ed25519Verify(
- edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(),
+ q->edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(),
signedOneTimeKey->toJsonForVerification(),
signature)) {
qWarning(E2EE) << "Failed to verify one-time-key signature for"
@@ -2326,10 +2366,10 @@ QJsonObject Connection::Private::encryptSessionKeyEvent(
const QString& targetDeviceId) const
{
payloadJson.insert("recipient"_ls, targetUserId);
- payloadJson.insert("recipient_keys"_ls,
- QJsonObject { { Ed25519Key,
- edKeyForUserDevice(targetUserId,
- targetDeviceId) } });
+ payloadJson.insert(
+ "recipient_keys"_ls,
+ QJsonObject{ { Ed25519Key,
+ q->edKeyForUserDevice(targetUserId, targetDeviceId) } });
const auto [type, cipherText] = olmEncryptMessage(
targetUserId, targetDeviceId,
QJsonDocument(payloadJson).toJson(QJsonDocument::Compact));
@@ -2419,3 +2459,64 @@ void Connection::saveCurrentOutboundMegolmSession(
}
#endif
+
+void Connection::startKeyVerificationSession(const QString& deviceId)
+{
+ auto session = new KeyVerificationSession(userId(), deviceId, this, this);
+ Q_EMIT newKeyVerificationSession(session);
+}
+
+void Connection::sendToDevice(const QString& userId, const QString& deviceId,
+ event_ptr_tt<Event> event, bool encrypted)
+{
+ if (encrypted) {
+ QJsonObject payloadJson = event->fullJson();
+ payloadJson["recipient"] = userId;
+ payloadJson["sender"] = user()->id();
+ QJsonObject recipientObject;
+ recipientObject["ed25519"] = edKeyForUserDevice(userId, deviceId);
+ payloadJson["recipient_keys"] = recipientObject;
+ QJsonObject senderObject;
+ senderObject["ed25519"] = QString(olmAccount()->identityKeys().ed25519);
+ payloadJson["keys"] = senderObject;
+
+ auto cipherText = d->olmEncryptMessage(
+ userId, deviceId,
+ QJsonDocument(payloadJson).toJson(QJsonDocument::Compact));
+ QJsonObject encryptedJson;
+ encryptedJson[d->curveKeyForUserDevice(userId, deviceId)] =
+ QJsonObject{ { "type", cipherText.first },
+ { "body", QString(cipherText.second) },
+ { "sender", this->userId() } };
+ const auto& contentJson =
+ EncryptedEvent(encryptedJson,
+ olmAccount()->identityKeys().curve25519)
+ .contentJson();
+ sendToDevices(EncryptedEvent::TypeId,
+ { { userId, { { deviceId, contentJson } } } });
+ } else
+ sendToDevices(event->matrixType(),
+ { { userId, { { deviceId, event->contentJson() } } } });
+}
+
+bool Connection::isVerifiedSession(const QString& megolmSessionId)
+{
+ auto query = database()->prepareQuery("SELECT olmSessionId FROM inbound_megolm_sessions WHERE sessionId=:sessionId;"_ls);
+ query.bindValue(":sessionId", megolmSessionId);
+ database()->execute(query);
+ if (!query.next()) {
+ return false;
+ }
+ auto olmSessionId = query.value("olmSessionId").toString();
+ query.prepare("SELECT senderKey FROM olm_sessions WHERE sessionId=:sessionId;"_ls);
+ query.bindValue(":sessionId", olmSessionId);
+ database()->execute(query);
+ if (!query.next()) {
+ return false;
+ }
+ auto curveKey = query.value("senderKey"_ls).toString();
+ query.prepare("SELECT verified FROM tracked_devices WHERE curveKey=:curveKey;"_ls);
+ query.bindValue(":curveKey", curveKey);
+ database()->execute(query);
+ return query.next() && query.value("verified").toBool();
+}
diff --git a/lib/connection.h b/lib/connection.h
index 0d0c85b6..b684d16b 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -26,6 +26,8 @@
#include "e2ee/e2ee.h"
#include "e2ee/qolmmessage.h"
#include "e2ee/qolmoutboundsession.h"
+#include "events/keyverificationevent.h"
+#include "keyverificationsession.h"
#endif
Q_DECLARE_METATYPE(Quotient::GetLoginFlowsJob::LoginFlow)
@@ -320,17 +322,23 @@ public:
QOlmAccount* olmAccount() const;
Database* database() const;
PicklingMode picklingMode() const;
+
UnorderedMap<QString, QOlmInboundGroupSessionPtr> loadRoomMegolmSessions(
const Room* room) const;
void saveMegolmSession(const Room* room,
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;
+
+ QString edKeyForUserDevice(const QString& user, const QString& device) const;
+ bool hasOlmSession(const QString& user, const QString& deviceId) const;
+
+ /// Returns true if this megolm session comes from a verified device
+ bool isVerifiedSession(const QString& megolmSessionId);
+
void sendSessionKeyToDevices(const QString& roomId,
const QByteArray& sessionId,
const QByteArray& sessionKey,
@@ -520,6 +528,9 @@ public:
/// Saves the olm account data to disk. Usually doesn't need to be called manually.
void saveOlmAccount();
+ // This assumes that an olm session already exists. If it doesn't, no message is sent.
+ void sendToDevice(const QString& userId, const QString& deviceId, event_ptr_tt<Event> event, bool encrypted);
+
public Q_SLOTS:
/// \brief Set the homeserver base URL and retrieve its login flows
///
@@ -697,6 +708,8 @@ public Q_SLOTS:
/** \deprecated Do not use this directly, use Room::leaveRoom() instead */
virtual LeaveRoomJob* leaveRoom(Room* room);
+ void startKeyVerificationSession(const QString& deviceId);
+
#ifdef Quotient_E2EE_ENABLED
void encryptionUpdate(Room *room);
#endif
@@ -858,6 +871,16 @@ Q_SIGNALS:
void lazyLoadingChanged();
void turnServersChanged(const QJsonObject& servers);
void devicesListLoaded();
+ void incomingKeyVerificationReady(const KeyVerificationReadyEvent& event);
+ void incomingKeyVerificationStart(const KeyVerificationStartEvent& event);
+ void incomingKeyVerificationAccept(const KeyVerificationAcceptEvent& event);
+ void incomingKeyVerificationKey(const KeyVerificationKeyEvent& event);
+ void incomingKeyVerificationMac(const KeyVerificationMacEvent& event);
+ void incomingKeyVerificationDone(const KeyVerificationDoneEvent& event);
+ void incomingKeyVerificationCancel(const KeyVerificationCancelEvent& event);
+
+ void newKeyVerificationSession(KeyVerificationSession* session);
+ void sessionVerified(const QString& userId, const QString& deviceId);
protected:
/**
diff --git a/lib/database.cpp b/lib/database.cpp
index ed7bd794..79793b9d 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"
@@ -31,7 +32,8 @@ Database::Database(const QString& matrixId, const QString& deviceId, QObject* pa
case 0: migrateTo1(); [[fallthrough]];
case 1: migrateTo2(); [[fallthrough]];
case 2: migrateTo3(); [[fallthrough]];
- case 3: migrateTo4();
+ case 3: migrateTo4(); [[fallthrough]];
+ case 4: migrateTo5();
}
}
@@ -102,7 +104,7 @@ void Database::migrateTo2()
{
qCDebug(DATABASE) << "Migrating database to version 2";
transaction();
- //TODO remove this column again - we don't need it after all
+
execute(QStringLiteral("ALTER TABLE inbound_megolm_sessions ADD ed25519Key TEXT"));
execute(QStringLiteral("ALTER TABLE olm_sessions ADD lastReceived TEXT"));
@@ -141,6 +143,16 @@ void Database::migrateTo4()
commit();
}
+void Database::migrateTo5()
+{
+ qCDebug(DATABASE) << "Migrating database to version 5";
+ transaction();
+
+ execute(QStringLiteral("ALTER TABLE tracked_devices ADD verified BOOL;"));
+ execute(QStringLiteral("PRAGMA user_version = 5"));
+ commit();
+}
+
QByteArray Database::accountPickle()
{
auto query = prepareQuery(QStringLiteral("SELECT pickle FROM accounts;"));
@@ -392,3 +404,19 @@ void Database::updateOlmSession(const QString& senderKey, const QString& session
commit();
}
+void Database::setSessionVerified(const QString& edKeyId)
+{
+ auto query = prepareQuery(QStringLiteral("UPDATE tracked_devices SET verified=true WHERE edKeyId=:edKeyId;"));
+ query.bindValue(":edKeyId", edKeyId);
+ transaction();
+ execute(query);
+ commit();
+}
+
+bool Database::isSessionVerified(const QString& edKey)
+{
+ auto query = prepareQuery(QStringLiteral("SELECT verified FROM tracked_devices WHERE edKey=:edKey"));
+ query.bindValue(":edKey", edKey);
+ execute(query);
+ return query.next() && query.value("verified").toBool();
+}
diff --git a/lib/database.h b/lib/database.h
index 4091d61b..8a133f8e 100644
--- a/lib/database.h
+++ b/lib/database.h
@@ -12,8 +12,6 @@
#include "e2ee/e2ee.h"
namespace Quotient {
-class User;
-class Room;
class QUOTIENT_API Database : public QObject
{
@@ -59,7 +57,8 @@ public:
const QByteArray& pickle);
// Returns a map UserId -> [DeviceId] that have not received key yet
- QMultiHash<QString, QString> devicesWithoutKey(const QString& roomId, QMultiHash<QString, QString> devices,
+ QMultiHash<QString, QString> devicesWithoutKey(
+ const QString& roomId, QMultiHash<QString, QString> devices,
const QString& sessionId);
// 'devices' contains tuples {userId, deviceId, curveKey}
void setDevicesReceivedKey(
@@ -67,12 +66,16 @@ public:
const QVector<std::tuple<QString, QString, QString>>& devices,
const QString& sessionId, int index);
+ bool isSessionVerified(const QString& edKey);
+ void setSessionVerified(const QString& edKeyId);
+
private:
void migrateTo1();
void migrateTo2();
void migrateTo3();
void migrateTo4();
+ void migrateTo5();
QString m_matrixId;
};
-}
+} // namespace Quotient
diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h
index 78457e0c..cdbd5d74 100644
--- a/lib/events/keyverificationevent.h
+++ b/lib/events/keyverificationevent.h
@@ -1,17 +1,23 @@
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: LGPL-2.1-or-later
+#pragma once
+
#include "event.h"
namespace Quotient {
+static constexpr auto SasV1Method = "m.sas.v1"_ls;
+
/// Requests a key verification with another user's devices.
/// Typically sent as a to-device event.
class QUOTIENT_API KeyVerificationRequestEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.request", KeyVerificationRequestEvent)
- explicit KeyVerificationRequestEvent(const QJsonObject& obj);
+ explicit KeyVerificationRequestEvent(const QJsonObject& obj)
+ : Event(TypeId, obj)
+ {}
/// The device ID which is initiating the request.
QUO_CONTENT_GETTER(QString, fromDevice)
@@ -27,16 +33,38 @@ public:
/// made. If the request is in the future by more than 5 minutes or
/// more than 10 minutes in the past, the message should be ignored
/// by the receiver.
- QUO_CONTENT_GETTER(uint64_t, timestamp)
+ QUO_CONTENT_GETTER(QDateTime, timestamp)
};
REGISTER_EVENT_TYPE(KeyVerificationRequestEvent)
+class QUOTIENT_API KeyVerificationReadyEvent : public Event {
+public:
+ DEFINE_EVENT_TYPEID("m.key.verification.ready", KeyVerificationReadyEvent)
+
+ explicit KeyVerificationReadyEvent(const QJsonObject& obj)
+ : Event(TypeId, obj)
+ {}
+
+ /// The device ID which is accepting the request.
+ QUO_CONTENT_GETTER(QString, fromDevice)
+
+ /// The transaction id of the verification request
+ QUO_CONTENT_GETTER(QString, transactionId)
+
+ /// The verification methods supported by the sender.
+ QUO_CONTENT_GETTER(QStringList, methods)
+};
+REGISTER_EVENT_TYPE(KeyVerificationReadyEvent)
+
+
/// Begins a key verification process.
class QUOTIENT_API KeyVerificationStartEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.start", KeyVerificationStartEvent)
- explicit KeyVerificationStartEvent(const QJsonObject &obj);
+ explicit KeyVerificationStartEvent(const QJsonObject &obj)
+ : Event(TypeId, obj)
+ {}
/// The device ID which is initiating the process.
QUO_CONTENT_GETTER(QString, fromDevice)
@@ -57,7 +85,7 @@ public:
/// \note Only exist if method is m.sas.v1
QStringList keyAgreementProtocols() const
{
- Q_ASSERT(method() == QStringLiteral("m.sas.v1"));
+ Q_ASSERT(method() == SasV1Method);
return contentPart<QStringList>("key_agreement_protocols"_ls);
}
@@ -65,7 +93,7 @@ public:
/// \note Only exist if method is m.sas.v1
QStringList hashes() const
{
- Q_ASSERT(method() == QStringLiteral("m.sas.v1"));
+ Q_ASSERT(method() == SasV1Method);
return contentPart<QStringList>("hashes"_ls);
}
@@ -73,7 +101,7 @@ public:
/// \note Only exist if method is m.sas.v1
QStringList messageAuthenticationCodes() const
{
- Q_ASSERT(method() == QStringLiteral("m.sas.v1"));
+ Q_ASSERT(method() == SasV1Method);
return contentPart<QStringList>("message_authentication_codes"_ls);
}
@@ -82,7 +110,7 @@ public:
/// \note Only exist if method is m.sas.v1
QString shortAuthenticationString() const
{
- Q_ASSERT(method() == QStringLiteral("m.sas.v1"));
+ Q_ASSERT(method() == SasV1Method);
return contentPart<QString>("short_authentification_string"_ls);
}
};
@@ -94,7 +122,9 @@ class QUOTIENT_API KeyVerificationAcceptEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.accept", KeyVerificationAcceptEvent)
- explicit KeyVerificationAcceptEvent(const QJsonObject& obj);
+ explicit KeyVerificationAcceptEvent(const QJsonObject& obj)
+ : Event(TypeId, obj)
+ {}
/// An opaque identifier for the verification process.
QUO_CONTENT_GETTER(QString, transactionId)
@@ -131,7 +161,9 @@ class QUOTIENT_API KeyVerificationCancelEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.cancel", KeyVerificationCancelEvent)
- explicit KeyVerificationCancelEvent(const QJsonObject &obj);
+ explicit KeyVerificationCancelEvent(const QJsonObject &obj)
+ : Event(TypeId, obj)
+ {}
/// An opaque identifier for the verification process.
QUO_CONTENT_GETTER(QString, transactionId)
@@ -147,11 +179,13 @@ REGISTER_EVENT_TYPE(KeyVerificationCancelEvent)
/// Sends the ephemeral public key for a device to the partner device.
/// Typically sent as a to-device event.
-class KeyVerificationKeyEvent : public Event {
+class QUOTIENT_API KeyVerificationKeyEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent)
- explicit KeyVerificationKeyEvent(const QJsonObject &obj);
+ explicit KeyVerificationKeyEvent(const QJsonObject &obj)
+ : Event(TypeId, obj)
+ {}
/// An opaque identifier for the verification process.
QUO_CONTENT_GETTER(QString, transactionId)
@@ -166,7 +200,9 @@ class QUOTIENT_API KeyVerificationMacEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.key.verification.mac", KeyVerificationMacEvent)
- explicit KeyVerificationMacEvent(const QJsonObject &obj);
+ explicit KeyVerificationMacEvent(const QJsonObject &obj)
+ : Event(TypeId, obj)
+ {}
/// An opaque identifier for the verification process.
QUO_CONTENT_GETTER(QString, transactionId)
@@ -180,4 +216,18 @@ public:
}
};
REGISTER_EVENT_TYPE(KeyVerificationMacEvent)
+
+class QUOTIENT_API KeyVerificationDoneEvent : public Event {
+public:
+ DEFINE_EVENT_TYPEID("m.key.verification.done", KeyVerificationRequestEvent)
+
+ explicit KeyVerificationDoneEvent(const QJsonObject& obj)
+ : Event(TypeId, obj)
+ {}
+
+ /// The same transactionId as before
+ QUO_CONTENT_GETTER(QString, transactionId)
+};
+REGISTER_EVENT_TYPE(KeyVerificationDoneEvent)
+
} // namespace Quotient
diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h
index 7f724689..9461340b 100644
--- a/lib/events/roomevent.h
+++ b/lib/events/roomevent.h
@@ -62,7 +62,7 @@ public:
#ifdef Quotient_E2EE_ENABLED
void setOriginalEvent(event_ptr_tt<RoomEvent>&& originalEvent);
- const RoomEvent* originalEvent() { return _originalEvent.get(); }
+ const RoomEvent* originalEvent() const { return _originalEvent.get(); }
const QJsonObject encryptedJson() const;
#endif
diff --git a/lib/keyverificationsession.cpp b/lib/keyverificationsession.cpp
new file mode 100644
index 00000000..d889b465
--- /dev/null
+++ b/lib/keyverificationsession.cpp
@@ -0,0 +1,524 @@
+// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "connection.h"
+#include "keyverificationsession.h"
+#include "olm/sas.h"
+#include "e2ee/qolmaccount.h"
+#include "e2ee/qolmutils.h"
+#include "events/event.h"
+#include <QtCore/QCryptographicHash>
+#include <QtCore/QUuid>
+#include <QtCore/QTimer>
+#include "database.h"
+
+using namespace Quotient;
+
+KeyVerificationSession::KeyVerificationSession(const QString& remoteUserId, const KeyVerificationRequestEvent& event, Connection *connection, bool encrypted, QObject* parent)
+ : QObject(parent)
+ , m_remoteUserId(remoteUserId)
+ , m_remoteDeviceId(event.fromDevice())
+ , m_transactionId(event.transactionId())
+ , m_connection(connection)
+ , m_encrypted(encrypted)
+ , m_remoteSupportedMethods(event.methods())
+{
+ auto timeoutTime = std::min(event.timestamp().addSecs(600),
+ QDateTime::currentDateTime().addSecs(120));
+ m_timeout =
+ timeoutTime.toMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch();
+ if (m_timeout <= 5000) {
+ return;
+ }
+ init();
+ setState(INCOMING);
+}
+
+KeyVerificationSession::KeyVerificationSession(const QString& userId, const QString& deviceId, Connection* connection, QObject* parent)
+ : QObject(parent)
+ , m_remoteUserId(userId)
+ , m_remoteDeviceId(deviceId)
+ , m_transactionId(QUuid::createUuid().toString())
+ , m_connection(connection)
+ , m_encrypted(false)
+{
+ m_timeout = 600000;
+ init();
+ QMetaObject::invokeMethod(this, &KeyVerificationSession::sendRequest);
+}
+
+void KeyVerificationSession::init()
+{
+ connect(m_connection, &Connection::incomingKeyVerificationReady, this, [this](const KeyVerificationReadyEvent& event) {
+ if (event.transactionId() == m_transactionId && event.fromDevice() == m_remoteDeviceId) {
+ handleReady(event);
+ }
+ });
+ connect(m_connection, &Connection::incomingKeyVerificationStart, this, [this](const KeyVerificationStartEvent& event) {
+ if (event.transactionId() == m_transactionId && event.fromDevice() == m_remoteDeviceId) {
+ handleStart(event);
+ }
+ });
+ connect(m_connection, &Connection::incomingKeyVerificationAccept, this, [this](const KeyVerificationAcceptEvent& event) {
+ if (event.transactionId() == m_transactionId) {
+ handleAccept(event);
+ }
+ });
+ connect(m_connection, &Connection::incomingKeyVerificationKey, this, [this](const KeyVerificationKeyEvent& event) {
+ if (event.transactionId() == m_transactionId) {
+ handleKey(event);
+ }
+ });
+ connect(m_connection, &Connection::incomingKeyVerificationMac, this, [this](const KeyVerificationMacEvent& event) {
+ if (event.transactionId() == m_transactionId) {
+ handleMac(event);
+ }
+ });
+ connect(m_connection, &Connection::incomingKeyVerificationDone, this, [this](const KeyVerificationDoneEvent& event) {
+ if (event.transactionId() == m_transactionId) {
+ handleDone(event);
+ }
+ });
+ connect(m_connection, &Connection::incomingKeyVerificationCancel, this, [this](const KeyVerificationCancelEvent& event) {
+ if (event.transactionId() == m_transactionId) {
+ handleCancel(event);
+ }
+ });
+
+ QTimer::singleShot(m_timeout, this, [this] {
+ cancelVerification(TIMEOUT);
+ });
+
+
+ m_sas = olm_sas(new uint8_t[olm_sas_size()]);
+ auto randomSize = olm_create_sas_random_length(m_sas);
+ auto random = getRandom(randomSize);
+ olm_create_sas(m_sas, random.data(), randomSize);
+
+ m_language = QLocale::system().uiLanguages()[0];
+ m_language = m_language.left(m_language.indexOf('-'));
+}
+
+KeyVerificationSession::~KeyVerificationSession()
+{
+ delete[] reinterpret_cast<uint8_t*>(m_sas);
+}
+
+void KeyVerificationSession::handleKey(const KeyVerificationKeyEvent& event)
+{
+ if (state() != WAITINGFORKEY && state() != WAITINGFORVERIFICATION) {
+ cancelVerification(UNEXPECTED_MESSAGE);
+ return;
+ }
+ olm_sas_set_their_key(m_sas, event.key().toLatin1().data(), event.key().toLatin1().size());
+
+ if (startSentByUs) {
+ auto commitment = QString(QCryptographicHash::hash((event.key() % m_startEvent).toLatin1(), QCryptographicHash::Sha256).toBase64());
+ commitment = commitment.left(commitment.indexOf('='));
+ if (commitment != m_commitment) {
+ qCWarning(E2EE) << "Commitment mismatch; aborting verification";
+ cancelVerification(MISMATCHED_COMMITMENT);
+ return;
+ }
+ } else {
+ sendKey();
+ }
+ setState(WAITINGFORVERIFICATION);
+
+ QByteArray keyBytes(olm_sas_pubkey_length(m_sas), '\0');
+ olm_sas_get_pubkey(m_sas, keyBytes.data(), keyBytes.size());
+ QString key = QString(keyBytes);
+
+ QByteArray output(6, '\0');
+ QString infoTemplate = startSentByUs ? "MATRIX_KEY_VERIFICATION_SAS|%1|%2|%3|%4|%5|%6|%7"_ls : "MATRIX_KEY_VERIFICATION_SAS|%4|%5|%6|%1|%2|%3|%7"_ls;
+
+ auto info = infoTemplate.arg(m_connection->userId()).arg(m_connection->deviceId()).arg(key).arg(m_remoteUserId).arg(m_remoteDeviceId).arg(event.key()).arg(m_transactionId);
+ olm_sas_generate_bytes(m_sas, info.toLatin1().data(), info.toLatin1().size(), output.data(), output.size());
+
+ QVector<uint8_t> code(7, 0);
+ const auto& data = (uint8_t *) output.data();
+
+ code[0] = data[0] >> 2;
+ code[1] = (data[0] << 4 & 0x3f) | data[1] >> 4;
+ code[2] = (data[1] << 2 & 0x3f) | data[2] >> 6;
+ code[3] = data[2] & 0x3f;
+ code[4] = data[3] >> 2;
+ code[5] = (data[3] << 4 & 0x3f) | data[4] >> 4;
+ code[6] = (data[4] << 2 & 0x3f) | data[5] >> 6;
+
+ for (const auto& c : code) {
+ auto [emoji, description] = emojiForCode(c);
+ QVariantMap map;
+ map["emoji"] = emoji;
+ map["description"] = description;
+ m_sasEmojis += map;
+ }
+ emit sasEmojisChanged();
+ emit keyReceived();
+}
+
+QByteArray KeyVerificationSession::macInfo(bool verifying, const QString& key)
+{
+ return (verifying ? "MATRIX_KEY_VERIFICATION_MAC%3%4%1%2%5%6"_ls : "MATRIX_KEY_VERIFICATION_MAC%1%2%3%4%5%6"_ls).arg(m_connection->userId()).arg(m_connection->deviceId()).arg(m_remoteUserId).arg(m_remoteDeviceId).arg(m_transactionId).arg(key).toLatin1();
+}
+
+QString KeyVerificationSession::calculateMac(const QString& input, bool verifying, const QString& keyId)
+{
+ QByteArray inputBytes = input.toLatin1();
+ QByteArray outputBytes(olm_sas_mac_length(m_sas), '\0');
+ olm_sas_calculate_mac(m_sas, inputBytes.data(), inputBytes.size(), macInfo(verifying, keyId).data(), macInfo(verifying, keyId).size(), outputBytes.data(), outputBytes.size());
+ auto output = QString(outputBytes);
+ return output.left(output.indexOf('='));
+}
+
+void KeyVerificationSession::sendMac()
+{
+ QString edKeyId = "ed25519:" % m_connection->deviceId();
+
+ auto keys = calculateMac(edKeyId, false);
+
+ QJsonObject mac;
+ auto key = m_connection->olmAccount()->deviceKeys().keys[edKeyId];
+ mac[edKeyId] = calculateMac(key, false, edKeyId);
+
+ auto event = makeEvent<KeyVerificationMacEvent>(QJsonObject {
+ {"type", "m.key.verification.mac"},
+ {"content", QJsonObject{
+ {"transaction_id", m_transactionId},
+ {"keys", keys},
+ {"mac", mac},
+ }}
+ });
+ m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted);
+ setState (macReceived ? DONE : WAITINGFORMAC);
+}
+
+void KeyVerificationSession::sendDone()
+{
+ auto event = makeEvent<KeyVerificationDoneEvent>(QJsonObject {
+ {"type", "m.key.verification.done"},
+ {"content", QJsonObject{
+ {"transaction_id", m_transactionId},
+ }}
+ });
+ m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted);
+}
+
+void KeyVerificationSession::sendKey()
+{
+ QByteArray keyBytes(olm_sas_pubkey_length(m_sas), '\0');
+ olm_sas_get_pubkey(m_sas, keyBytes.data(), keyBytes.size());
+ QString key = QString(keyBytes);
+ auto event = makeEvent<KeyVerificationKeyEvent>(QJsonObject {
+ {"type", "m.key.verification.key"},
+ {"content", QJsonObject{
+ {"transaction_id", m_transactionId},
+ {"key", key},
+ }}
+ });
+ m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted);
+}
+
+
+void KeyVerificationSession::cancelVerification(Error error)
+{
+ auto event = makeEvent<KeyVerificationCancelEvent>(QJsonObject {
+ {"type", "m.key.verification.cancel"},
+ {"content", QJsonObject{
+ {"code", errorToString(error)},
+ {"reason", errorToString(error)},
+ {"transaction_id", m_transactionId}
+ }}
+ });
+ m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted);
+ setState(CANCELED);
+ setError(error);
+ emit finished();
+ deleteLater();
+}
+
+void KeyVerificationSession::sendReady()
+{
+ auto methods = commonSupportedMethods(m_remoteSupportedMethods);
+
+ if (methods.isEmpty()) {
+ cancelVerification(UNKNOWN_METHOD);
+ return;
+ }
+
+ auto event = makeEvent<KeyVerificationReadyEvent>(QJsonObject {
+ {"type", "m.key.verification.ready"},
+ {"content", QJsonObject {
+ {"from_device", m_connection->deviceId()},
+ {"methods", toJson(methods)},
+ {"transaction_id", m_transactionId},
+ }}
+ });
+ m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted);
+ setState(READY);
+
+ if (methods.size() == 1) {
+ sendStartSas();
+ }
+}
+
+void KeyVerificationSession::sendStartSas()
+{
+ startSentByUs = true;
+ auto event = makeEvent<KeyVerificationStartEvent>(QJsonObject {
+ {"type", "m.key.verification.start"},
+ {"content", QJsonObject {
+ {"from_device", m_connection->deviceId()},
+ {"hashes", QJsonArray {"sha256"}},
+ {"key_agreement_protocols", QJsonArray { "curve25519-hkdf-sha256" }},
+ {"message_authentication_codes", QJsonArray { "hkdf-hmac-sha256" }},
+ {"method", "m.sas.v1"},
+ {"short_authentication_string", QJsonArray { "decimal", "emoji" }},
+ {"transaction_id", m_transactionId},
+ }}
+ });
+ m_startEvent = QJsonDocument(event->contentJson()).toJson(QJsonDocument::Compact);
+ m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted);
+ setState(WAITINGFORACCEPT);
+}
+
+void KeyVerificationSession::handleReady(const KeyVerificationReadyEvent& event)
+{
+ if (state() != WAITINGFORREADY) {
+ cancelVerification(UNEXPECTED_MESSAGE);
+ return;
+ }
+ setState(READY);
+ m_remoteSupportedMethods = event.methods();
+ auto methods = commonSupportedMethods(m_remoteSupportedMethods);
+
+ if (methods.isEmpty()) {
+ cancelVerification(UNKNOWN_METHOD);
+ return;
+ }
+
+ if (methods.size() == 1) {
+ sendStartSas();
+ }
+}
+
+void KeyVerificationSession::handleStart(const KeyVerificationStartEvent& event)
+{
+ if (state() != READY) {
+ cancelVerification(UNEXPECTED_MESSAGE);
+ return;
+ }
+ if (startSentByUs) {
+ if (m_remoteUserId > m_connection->userId() || (m_remoteUserId == m_connection->userId() && m_remoteDeviceId > m_connection->deviceId())) {
+ return;
+ } else {
+ startSentByUs = false;
+ }
+ }
+ QByteArray publicKey(olm_sas_pubkey_length(m_sas), '\0');
+ olm_sas_get_pubkey(m_sas, publicKey.data(), publicKey.size());
+ const auto canonicalEvent = QString(QJsonDocument(event.contentJson()).toJson(QJsonDocument::Compact));
+ auto commitment = QString(QCryptographicHash::hash((QString(publicKey) % canonicalEvent).toLatin1(), QCryptographicHash::Sha256).toBase64());
+ commitment = commitment.left(commitment.indexOf('='));
+
+ auto acceptEvent = makeEvent<KeyVerificationAcceptEvent>(QJsonObject {
+ {"type", "m.key.verification.accept"},
+ {"content", QJsonObject {
+ {"commitment", commitment},
+ {"hash", "sha256"},
+ {"key_agreement_protocol", "curve25519-hkdf-sha256"},
+ {"message_authentication_code", "hkdf-hmac-sha256"},
+ {"method", "m.sas.v1"},
+ {"short_authentication_string", QJsonArray {
+ "decimal",
+ "emoji",
+ }},
+ {"transaction_id", m_transactionId},
+ }}
+ });
+ m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(acceptEvent), m_encrypted);
+ setState(ACCEPTED);
+}
+
+void KeyVerificationSession::handleAccept(const KeyVerificationAcceptEvent& event)
+{
+ if(state() != WAITINGFORACCEPT) {
+ cancelVerification(UNEXPECTED_MESSAGE);
+ return;
+ }
+ m_commitment = event.commitment();
+ sendKey();
+ setState(WAITINGFORKEY);
+}
+
+void KeyVerificationSession::handleMac(const KeyVerificationMacEvent& event)
+{
+ QStringList keys = event.mac().keys();
+ keys.sort();
+ const auto& key = keys.join(",");
+ const QString edKeyId = "ed25519:"_ls % m_remoteDeviceId;
+
+ if (calculateMac(m_connection->edKeyForUserDevice(m_remoteUserId, m_remoteDeviceId), true, edKeyId) != event.mac()[edKeyId]) {
+ cancelVerification(KEY_MISMATCH);
+ return;
+ }
+
+ if (calculateMac(key, true) != event.keys()) {
+ cancelVerification(KEY_MISMATCH);
+ return;
+ }
+
+ m_connection->database()->setSessionVerified(edKeyId);
+ emit m_connection->sessionVerified(m_remoteUserId, m_remoteDeviceId);
+ macReceived = true;
+
+ if (state() == WAITINGFORMAC) {
+ setState(DONE);
+ sendDone();
+ emit finished();
+ deleteLater();
+ }
+}
+
+void KeyVerificationSession::handleDone(const KeyVerificationDoneEvent& event)
+{
+ if (state() != DONE) {
+ cancelVerification(UNEXPECTED_MESSAGE);
+ }
+}
+
+void KeyVerificationSession::handleCancel(const KeyVerificationCancelEvent& event)
+{
+ setError(stringToError(event.code()));
+ setState(CANCELED);
+}
+
+std::pair<QString, QString> KeyVerificationSession::emojiForCode(int code)
+{
+ static QJsonArray data;
+ if (data.isEmpty()) {
+ QFile dataFile(":/sas-emoji.json");
+ dataFile.open(QFile::ReadOnly);
+ data = QJsonDocument::fromJson(dataFile.readAll()).array();
+ }
+ if (data[code].toObject()["translated_descriptions"].toObject().contains(m_language)) {
+ return {data[code].toObject()["emoji"].toString(), data[code].toObject()["translated_descriptions"].toObject()[m_language].toString()};
+ }
+ return {data[code].toObject()["emoji"].toString(), data[code].toObject()["description"].toString()};
+}
+
+QList<QVariantMap> KeyVerificationSession::sasEmojis() const
+{
+ return m_sasEmojis;
+}
+
+void KeyVerificationSession::sendRequest()
+{
+ QJsonArray methods = toJson(m_supportedMethods);
+ auto event = makeEvent<KeyVerificationRequestEvent>(QJsonObject {
+ {"type", "m.key.verification.request"},
+ {"content", QJsonObject {
+ {"from_device", m_connection->deviceId()},
+ {"transaction_id", m_transactionId},
+ {"methods", methods},
+ {"timestamp", QDateTime::currentMSecsSinceEpoch()},
+ }},
+ });
+ m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted);
+ setState(WAITINGFORREADY);
+}
+
+KeyVerificationSession::State KeyVerificationSession::state() const
+{
+ return m_state;
+}
+
+void KeyVerificationSession::setState(KeyVerificationSession::State state)
+{
+ m_state = state;
+ emit stateChanged();
+}
+
+KeyVerificationSession::Error KeyVerificationSession::error() const
+{
+ return m_error;
+}
+
+void KeyVerificationSession::setError(Error error)
+{
+ m_error = error;
+ emit errorChanged();
+}
+
+QString KeyVerificationSession::errorToString(Error error) const
+{
+ switch(error) {
+ case NONE:
+ return "none"_ls;
+ case TIMEOUT:
+ return "m.timeout"_ls;
+ case USER:
+ return "m.user"_ls;
+ case UNEXPECTED_MESSAGE:
+ return "m.unexpected_message"_ls;
+ case UNKNOWN_TRANSACTION:
+ return "m.unknown_transaction"_ls;
+ case UNKNOWN_METHOD:
+ return "m.unknown_method"_ls;
+ case KEY_MISMATCH:
+ return "m.key_mismatch"_ls;
+ case USER_MISMATCH:
+ return "m.user_mismatch"_ls;
+ case INVALID_MESSAGE:
+ return "m.invalid_message"_ls;
+ case SESSION_ACCEPTED:
+ return "m.accepted"_ls;
+ case MISMATCHED_COMMITMENT:
+ return "m.mismatched_commitment"_ls;
+ case MISMATCHED_SAS:
+ return "m.mismatched_sas"_ls;
+ default:
+ return "m.user"_ls;
+ }
+}
+
+KeyVerificationSession::Error KeyVerificationSession::stringToError(const QString& error) const
+{
+ if (error == "m.timeout"_ls) {
+ return REMOTE_TIMEOUT;
+ } else if (error == "m.user"_ls) {
+ return REMOTE_USER;
+ } else if (error == "m.unexpected_message"_ls) {
+ return REMOTE_UNEXPECTED_MESSAGE;
+ } else if (error == "m.unknown_message"_ls) {
+ return REMOTE_UNEXPECTED_MESSAGE;
+ } else if (error == "m.unknown_transaction"_ls) {
+ return REMOTE_UNKNOWN_TRANSACTION;
+ } else if (error == "m.unknown_method"_ls) {
+ return REMOTE_UNKNOWN_METHOD;
+ } else if (error == "m.key_mismatch"_ls) {
+ return REMOTE_KEY_MISMATCH;
+ } else if (error == "m.user_mismatch"_ls) {
+ return REMOTE_USER_MISMATCH;
+ } else if (error == "m.invalid_message"_ls) {
+ return REMOTE_INVALID_MESSAGE;
+ } else if (error == "m.accepted"_ls) {
+ return REMOTE_SESSION_ACCEPTED;
+ } else if (error == "m.mismatched_commitment"_ls) {
+ return REMOTE_MISMATCHED_COMMITMENT;
+ } else if (error == "m.mismatched_sas"_ls) {
+ return REMOTE_MISMATCHED_SAS;
+ }
+ return NONE;
+}
+
+QStringList KeyVerificationSession::commonSupportedMethods(const QStringList& remoteMethods) const
+{
+ QStringList result;
+ for (const auto& method : remoteMethods) {
+ if (m_supportedMethods.contains(method)) {
+ result += method;
+ }
+ }
+ return result;
+}
diff --git a/lib/keyverificationsession.h b/lib/keyverificationsession.h
new file mode 100644
index 00000000..cb7a99e9
--- /dev/null
+++ b/lib/keyverificationsession.h
@@ -0,0 +1,140 @@
+// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include "events/keyverificationevent.h"
+#include <QtCore/QObject>
+#include <qchar.h>
+
+class OlmSAS;
+
+namespace Quotient {
+class Connection;
+
+/** A key verification session. Listen for incoming sessions by connecting to Connection::newKeyVerificationSession.
+ Start a new session using Connection::startKeyVerificationSession.
+ The object is delete after finished is emitted.
+*/
+class QUOTIENT_API KeyVerificationSession : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum State {
+ INCOMING, // There is a request for verification incoming
+ WAITINGFORREADY, // We sent a request for verification and are waiting for ready
+ READY, // Either party sent a ready as a response to a request; The user selects a method
+ WAITINGFORACCEPT, // We sent a start and are waiting for an accept
+ ACCEPTED, // The other party sent an accept and is waiting for a key
+ WAITINGFORKEY, // We're waiting for a key
+ WAITINGFORVERIFICATION, // We're waiting for the *user* to verify the emojis
+ WAITINGFORMAC, // We're waiting for the mac
+ CANCELED, // The session has been canceled
+ DONE, // The verification is done
+ };
+ Q_ENUM(State)
+
+ enum Error {
+ NONE,
+ TIMEOUT,
+ REMOTE_TIMEOUT,
+ USER,
+ REMOTE_USER,
+ UNEXPECTED_MESSAGE,
+ REMOTE_UNEXPECTED_MESSAGE,
+ UNKNOWN_TRANSACTION,
+ REMOTE_UNKNOWN_TRANSACTION,
+ UNKNOWN_METHOD,
+ REMOTE_UNKNOWN_METHOD,
+ KEY_MISMATCH,
+ REMOTE_KEY_MISMATCH,
+ USER_MISMATCH,
+ REMOTE_USER_MISMATCH,
+ INVALID_MESSAGE,
+ REMOTE_INVALID_MESSAGE,
+ SESSION_ACCEPTED,
+ REMOTE_SESSION_ACCEPTED,
+ MISMATCHED_COMMITMENT,
+ REMOTE_MISMATCHED_COMMITMENT,
+ MISMATCHED_SAS,
+ REMOTE_MISMATCHED_SAS,
+ };
+ Q_ENUM(Error);
+
+ //Q_PROPERTY(int timeLeft READ timeLeft NOTIFY timeLeftChanged)
+ Q_PROPERTY(QString remoteDeviceId MEMBER m_remoteDeviceId CONSTANT)
+ Q_PROPERTY(QList<QVariantMap> sasEmojis READ sasEmojis NOTIFY sasEmojisChanged)
+ Q_PROPERTY(State state READ state NOTIFY stateChanged)
+ Q_PROPERTY(Error error READ error NOTIFY errorChanged)
+
+ KeyVerificationSession(const QString& remoteUserId, const KeyVerificationRequestEvent& event, Connection* connection, bool encrypted, QObject* parent = nullptr);
+ KeyVerificationSession(const QString& userId, const QString& deviceId, Connection* connection, QObject* parent = nullptr);
+ ~KeyVerificationSession();
+
+ int timeLeft() const;
+ QList<QVariantMap> sasEmojis() const;
+ State state() const;
+
+ Error error() const;
+
+public Q_SLOTS:
+ void sendRequest();
+ void sendReady();
+ void sendMac();
+ void sendStartSas();
+ void sendKey();
+ void sendDone();
+ void cancelVerification(Error error);
+
+Q_SIGNALS:
+
+ void timeLeftChanged();
+ void startReceived();
+ void keyReceived();
+ void sasEmojisChanged();
+ void stateChanged();
+ void errorChanged();
+ void finished();
+
+private:
+ QString m_remoteUserId;
+ QString m_remoteDeviceId;
+ QString m_transactionId;
+ Connection* m_connection;
+ OlmSAS* m_sas = nullptr;
+ int timeleft = 0;
+ QList<QVariantMap> m_sasEmojis;
+ bool startSentByUs = false;
+ State m_state;
+ Error m_error;
+ QString m_startEvent;
+ QString m_commitment;
+ QString m_language;
+ int m_timeout;
+ bool macReceived = false;
+ bool m_encrypted;
+ QStringList m_remoteSupportedMethods;
+
+ void handleReady(const KeyVerificationReadyEvent& event);
+ void handleStart(const KeyVerificationStartEvent& event);
+ void handleAccept(const KeyVerificationAcceptEvent& event);
+ void handleKey(const KeyVerificationKeyEvent& event);
+ void handleMac(const KeyVerificationMacEvent& event);
+ void handleDone(const KeyVerificationDoneEvent& event);
+ void handleCancel(const KeyVerificationCancelEvent& event);
+ void init();
+ void setState(State state);
+ void setError(Error error);
+ QStringList commonSupportedMethods(const QStringList& remoteSupportedMethods) const;
+ QString errorToString(Error error) const;
+ Error stringToError(const QString& error) const;
+ QStringList m_supportedMethods = { "m.sas.v1"_ls };
+
+ QByteArray macInfo(bool verifying, const QString& key = "KEY_IDS"_ls);
+ QString calculateMac(const QString& input, bool verifying, const QString& keyId= "KEY_IDS"_ls);
+
+ std::pair<QString, QString> emojiForCode(int code);
+};
+
+}
diff --git a/lib/room.cpp b/lib/room.cpp
index c6fbb353..3ee81dcc 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -69,7 +69,6 @@
#include "e2ee/qolmaccount.h"
#include "e2ee/qolmerrors.h"
#include "e2ee/qolminboundsession.h"
-#include "e2ee/qolmoutboundsession.h"
#include "e2ee/qolmutility.h"
#include "database.h"
#endif // Quotient_E2EE_ENABLED