From 534b6f3fb3a4bd14d78942cab7eb430dd98e471c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 23 Apr 2022 19:46:58 +0200 Subject: SLICE() Add a macro to make slicing clear in the code and quiet for static analysis. --- lib/e2ee/qolmmessage.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmmessage.cpp b/lib/e2ee/qolmmessage.cpp index 81b166b0..f9b4a5c2 100644 --- a/lib/e2ee/qolmmessage.cpp +++ b/lib/e2ee/qolmmessage.cpp @@ -4,6 +4,8 @@ #include "qolmmessage.h" +#include "util.h" + using namespace Quotient; QOlmMessage::QOlmMessage(QByteArray ciphertext, QOlmMessage::Type type) @@ -26,7 +28,7 @@ QOlmMessage::Type QOlmMessage::type() const QByteArray QOlmMessage::toCiphertext() const { - return QByteArray(*this); + return SLICE(*this, QByteArray); } QOlmMessage QOlmMessage::fromCiphertext(const QByteArray &ciphertext) -- cgit v1.2.3 From 572b727b22d66a79431326c924236ef431fd972b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 14 May 2022 11:20:43 +0200 Subject: Cleanup across the board Mainly driven by clang-tidy and SonarCloud warnings (sadly, SonarCloud doesn't store historical reports so no link can be provided here). --- lib/connection.cpp | 72 ++++++++++++++++++++--------------------- lib/connection.h | 6 ++-- lib/converters.h | 8 +++++ lib/e2ee/qolminboundsession.cpp | 4 +-- lib/e2ee/qolminboundsession.h | 2 +- lib/events/callinviteevent.cpp | 2 +- lib/events/callinviteevent.h | 4 +-- lib/events/roomkeyevent.h | 5 ++- lib/room.cpp | 20 ++++++------ lib/room.h | 4 +-- 10 files changed, 70 insertions(+), 57 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4418958e..bd4d9251 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -219,7 +219,9 @@ public: } q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult), QDateTime::currentDateTime()); } - std::pair sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) + + std::pair sessionDecryptPrekey(const QOlmMessage& message, + const QString& senderKey) { Q_ASSERT(message.type() == QOlmMessage::PreKey); for(auto& session : olmSessions[senderKey]) { @@ -273,24 +275,19 @@ public: } std::pair sessionDecryptMessage( - const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) + const QJsonObject& personalCipherObject, const QByteArray& senderKey) { - QString decrypted; - QString olmSessionId; - int type = personalCipherObject.value(TypeKeyL).toInt(-1); - QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); - if (type == QOlmMessage::PreKey) { - QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); - auto result = sessionDecryptPrekey(preKeyMessage, senderKey, account); - decrypted = result.first; - olmSessionId = result.second; - } else if (type == QOlmMessage::General) { - QOlmMessage message(body, QOlmMessage::General); - auto result = sessionDecryptGeneral(message, senderKey); - decrypted = result.first; - olmSessionId = result.second; - } - return { decrypted, olmSessionId }; + QOlmMessage message { + personalCipherObject.value(BodyKeyL).toString().toLatin1(), + static_cast( + personalCipherObject.value(TypeKeyL).toInt(-1)) + }; + if (message.type() == QOlmMessage::PreKey) + return sessionDecryptPrekey(message, senderKey); + if (message.type() == QOlmMessage::General) + return sessionDecryptGeneral(message, senderKey); + qCWarning(E2EE) << "Olm message has incorrect type" << message.type(); + return {}; } #endif @@ -310,8 +307,9 @@ public: qCDebug(E2EE) << "Encrypted event is not for the current device"; return {}; } - const auto [decrypted, olmSessionId] = sessionDecryptMessage( - personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); + const auto [decrypted, olmSessionId] = + sessionDecryptMessage(personalCipherObject, + encryptedEvent.senderKey().toLatin1()); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" << encryptedEvent.senderKey() @@ -950,8 +948,6 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) outdatedUsers += event.senderId(); encryptionUpdateRequired = true; pendingEncryptedEvents.push_back(std::make_unique(event.fullJson())); - }, [](const Event& e){ - // Unhandled }); } #endif @@ -967,7 +963,7 @@ void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& eve } switchOnType(*decryptedEvent, - [this, senderKey = event.senderKey(), &event, olmSessionId = olmSessionId](const RoomKeyEvent& roomKeyEvent) { + [this, &event, olmSessionId = olmSessionId](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, event.senderId(), olmSessionId); } else { @@ -2074,12 +2070,13 @@ void Connection::Private::loadOutdatedUserDevices() saveDevicesList(); for(size_t i = 0; i < pendingEncryptedEvents.size();) { - if (q->isKnownCurveKey(pendingEncryptedEvents[i]->fullJson()[SenderKeyL].toString(), pendingEncryptedEvents[i]->contentJson()["sender_key"].toString())) { - handleEncryptedToDeviceEvent(*(pendingEncryptedEvents[i].get())); + if (q->isKnownCurveKey( + pendingEncryptedEvents[i]->fullJson()[SenderKeyL].toString(), + pendingEncryptedEvents[i]->contentPart("sender_key"_ls))) { + handleEncryptedToDeviceEvent(*pendingEncryptedEvents[i]); pendingEncryptedEvents.erase(pendingEncryptedEvents.begin() + i); - } else { - i++; - } + } else + ++i; } }); } @@ -2188,13 +2185,10 @@ void Connection::saveOlmAccount() #ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { - auto room = this->room(notification["room_id"].toString()); + auto r = room(notification["room_id"].toString()); auto event = makeEvent(notification["event"].toObject()); - auto decrypted = room->decryptMessage(*event); - if(!decrypted) { - return QJsonObject(); - } - return decrypted->fullJson(); + const auto decrypted = r->decryptMessage(*event); + return decrypted ? decrypted->fullJson() : QJsonObject(); } Database* Connection::database() @@ -2202,14 +2196,18 @@ Database* Connection::database() return d->database; } -UnorderedMap Connection::loadRoomMegolmSessions(Room* room) +UnorderedMap +Connection::loadRoomMegolmSessions(const Room* room) { return database()->loadMegolmSessions(room->id(), picklingMode()); } -void Connection::saveMegolmSession(Room* room, QOlmInboundGroupSession* session) +void Connection::saveMegolmSession(const Room* room, + const QOlmInboundGroupSession& session) { - database()->saveMegolmSession(room->id(), session->sessionId(), session->pickle(picklingMode()), session->senderId(), session->olmSessionId()); + database()->saveMegolmSession(room->id(), session.sessionId(), + session.pickle(picklingMode()), + session.senderId(), session.olmSessionId()); } QStringList Connection::devicesForUser(User* user) const diff --git a/lib/connection.h b/lib/connection.h index 29731593..b75bd5b5 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -317,8 +317,10 @@ public: #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; Database* database(); - UnorderedMap loadRoomMegolmSessions(Room* room); - void saveMegolmSession(Room* room, QOlmInboundGroupSession* session); + UnorderedMap loadRoomMegolmSessions( + const Room* room); + void saveMegolmSession(const Room* room, + const QOlmInboundGroupSession& session); #endif // Quotient_E2EE_ENABLED Q_INVOKABLE Quotient::SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; diff --git a/lib/converters.h b/lib/converters.h index 6515310a..5e3becb8 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -163,6 +163,14 @@ inline qint64 fromJson(const QJsonValue& jv) { return qint64(jv.toDouble()); } template <> inline QString fromJson(const QJsonValue& jv) { return jv.toString(); } +//! Use fromJson and use toLatin1()/toUtf8()/... to make QByteArray +//! +//! QJsonValue can only convert to QString and there's ambiguity whether +//! conversion to QByteArray should use (fast but very limited) toLatin1() or +//! (all encompassing and conforming to the JSON spec but slow) toUtf8(). +template <> +inline QByteArray fromJson(const QJsonValue& jv) = delete; + template <> inline QJsonArray fromJson(const QJsonValue& jv) { return jv.toArray(); } diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 60d871ef..62856831 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -154,9 +154,9 @@ QString QOlmInboundGroupSession::olmSessionId() const { return m_olmSessionId; } -void QOlmInboundGroupSession::setOlmSessionId(const QString& olmSessionId) +void QOlmInboundGroupSession::setOlmSessionId(const QString& newOlmSessionId) { - m_olmSessionId = olmSessionId; + m_olmSessionId = newOlmSessionId; } QString QOlmInboundGroupSession::senderId() const diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 32112b97..13515434 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -44,7 +44,7 @@ public: //! The olm session that this session was received from. //! Required to get the device this session is from. QString olmSessionId() const; - void setOlmSessionId(const QString& setOlmSessionId); + void setOlmSessionId(const QString& newOlmSessionId); //! The sender of this session. QString senderId() const; diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index 11d50768..2f26a1cb 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -33,7 +33,7 @@ CallInviteEvent::CallInviteEvent(const QJsonObject& obj) qCDebug(EVENTS) << "Call Invite event"; } -CallInviteEvent::CallInviteEvent(const QString& callId, const int lifetime, +CallInviteEvent::CallInviteEvent(const QString& callId, int lifetime, const QString& sdp) : CallEventBase( typeId(), matrixTypeId(), callId, 0, diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index 1b1f4f0f..5b4ca0df 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -13,10 +13,10 @@ public: explicit CallInviteEvent(const QJsonObject& obj); - explicit CallInviteEvent(const QString& callId, const int lifetime, + explicit CallInviteEvent(const QString& callId, int lifetime, const QString& sdp); - QUO_CONTENT_GETTER(int, lifetime) // FIXME: Omittable<>? + QUO_CONTENT_GETTER(int, lifetime) QString sdp() const { return contentPart("offer"_ls).value("sdp"_ls).toString(); diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index c4df7936..ed4c9440 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -16,7 +16,10 @@ public: QString algorithm() const { return contentPart("algorithm"_ls); } QString roomId() const { return contentPart(RoomIdKeyL); } QString sessionId() const { return contentPart("session_id"_ls); } - QString sessionKey() const { return contentPart("session_key"_ls); } + QByteArray sessionKey() const + { + return contentPart("session_key"_ls).toLatin1(); + } }; REGISTER_EVENT_TYPE(RoomKeyEvent) } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index 993455be..0a997b9c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -339,14 +339,16 @@ public: #ifdef Quotient_E2EE_ENABLED UnorderedMap groupSessions; - bool addInboundGroupSession(QString sessionId, QString sessionKey, const QString& senderId, const QString& olmSessionId) + bool addInboundGroupSession(QString sessionId, QByteArray sessionKey, + const QString& senderId, + const QString& olmSessionId) { - if (groupSessions.find(sessionId) != groupSessions.end()) { + if (groupSessions.contains(sessionId)) { qCWarning(E2EE) << "Inbound Megolm session" << sessionId << "already exists"; return false; } - auto megolmSession = QOlmInboundGroupSession::create(sessionKey.toLatin1()); + auto megolmSession = QOlmInboundGroupSession::create(sessionKey); if (megolmSession->sessionId() != sessionId) { qCWarning(E2EE) << "Session ID mismatch in m.room_key event"; return false; @@ -354,13 +356,12 @@ public: megolmSession->setSenderId(senderId); megolmSession->setOlmSessionId(olmSessionId); qCWarning(E2EE) << "Adding inbound session"; - connection->saveMegolmSession(q, megolmSession.get()); + connection->saveMegolmSession(q, *megolmSession); groupSessions[sessionId] = std::move(megolmSession); return true; } QString groupSessionDecryptMessage(QByteArray cipher, - const QString& senderKey, const QString& sessionId, const QString& eventId, QDateTime timestamp, @@ -1478,9 +1479,9 @@ RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) return {}; } QString decrypted = d->groupSessionDecryptMessage( - encryptedEvent.ciphertext(), encryptedEvent.senderKey(), - encryptedEvent.sessionId(), encryptedEvent.id(), - encryptedEvent.originTimestamp(), encryptedEvent.senderId()); + encryptedEvent.ciphertext(), encryptedEvent.sessionId(), + encryptedEvent.id(), encryptedEvent.originTimestamp(), + encryptedEvent.senderId()); if (decrypted.isEmpty()) { // qCWarning(E2EE) << "Encrypted message is empty"; return {}; @@ -1509,7 +1510,8 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, << roomKeyEvent.algorithm() << "in m.room_key event"; } if (d->addInboundGroupSession(roomKeyEvent.sessionId(), - roomKeyEvent.sessionKey(), senderId, olmSessionId)) { + roomKeyEvent.sessionKey(), senderId, + olmSessionId)) { qCWarning(E2EE) << "added new inboundGroupSession:" << d->groupSessions.size(); auto undecryptedEvents = d->undecryptedEvents[roomKeyEvent.sessionId()]; diff --git a/lib/room.h b/lib/room.h index 2e2ebf9a..6e6071f0 100644 --- a/lib/room.h +++ b/lib/room.h @@ -871,8 +871,8 @@ public Q_SLOTS: void inviteCall(const QString& callId, const int lifetime, const QString& sdp); void sendCallCandidates(const QString& callId, const QJsonArray& candidates); - [[deprecated("Lifetime argument is no more in use here; " - "use 2-arg Room::answerCall() instead")]] + //! \deprecated Lifetime argument is no more passed; use 2-arg + //! Room::answerCall() instead void answerCall(const QString& callId, int lifetime, const QString& sdp); void answerCall(const QString& callId, const QString& sdp); void hangupCall(const QString& callId); -- cgit v1.2.3 From 3fcc0c5acb160364e819688cc67a8aaf814fafef Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 14 May 2022 20:51:05 +0200 Subject: Optimise #includes for QOlm* classes --- autotests/testolmutility.cpp | 2 ++ lib/connection.cpp | 15 ++++++++------- lib/e2ee/qolmaccount.cpp | 3 +++ lib/e2ee/qolmaccount.h | 6 ------ lib/e2ee/qolmsession.cpp | 7 ++++--- lib/e2ee/qolmsession.h | 7 ++----- 6 files changed, 19 insertions(+), 21 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index b4532c8d..92b8b5b2 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -6,6 +6,8 @@ #include "e2ee/qolmaccount.h" #include "e2ee/qolmutility.h" +#include + using namespace Quotient; void TestOlmUtility::canonicalJSON() diff --git a/lib/connection.cpp b/lib/connection.cpp index bd4d9251..511b64e2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -35,16 +35,17 @@ #include "jobs/syncjob.h" #ifdef Quotient_E2EE_ENABLED -# include "e2ee/qolmaccount.h" -# include "e2ee/qolmutils.h" # include "database.h" +# include "e2ee/qolmaccount.h" # include "e2ee/qolminboundsession.h" +# include "e2ee/qolmsession.h" +# include "e2ee/qolmutils.h" -#if QT_VERSION_MAJOR >= 6 -# include -#else -# include -#endif +# if QT_VERSION_MAJOR >= 6 +# include +# else +# include +# endif #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 476a60bd..af9eb1bf 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -5,6 +5,7 @@ #include "qolmaccount.h" #include "connection.h" +#include "e2ee/qolmsession.h" #include "e2ee/qolmutility.h" #include "e2ee/qolmutils.h" @@ -12,6 +13,8 @@ #include +#include + using namespace Quotient; QHash OneTimeKeys::curve25519() const diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 17f43f1a..08301998 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -9,18 +9,12 @@ #include "e2ee/e2ee.h" #include "e2ee/qolmerrors.h" #include "e2ee/qolmmessage.h" -#include "e2ee/qolmsession.h" #include struct OlmAccount; namespace Quotient { -class QOlmSession; -class Connection; - -using QOlmSessionPtr = std::unique_ptr; - //! An olm account manages all cryptographic keys used on a device. //! \code{.cpp} //! const auto olmAccount = new QOlmAccount(this); diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index e575ff39..531a6696 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -3,10 +3,12 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "qolmsession.h" + #include "e2ee/qolmutils.h" #include "logging.h" + #include -#include +#include using namespace Quotient; @@ -247,5 +249,4 @@ std::variant QOlmSession::matchesInboundSessionFrom(const QStri QOlmSession::QOlmSession(OlmSession *session) : m_session(session) -{ -} +{} diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index f20c9837..4eb151b6 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -4,17 +4,14 @@ #pragma once -#include -#include // FIXME: OlmSession #include "e2ee/e2ee.h" #include "e2ee/qolmmessage.h" #include "e2ee/qolmerrors.h" #include "e2ee/qolmaccount.h" -namespace Quotient { +struct OlmSession; -class QOlmAccount; -class QOlmSession; +namespace Quotient { //! Either an outbound or inbound session for secure communication. class QUOTIENT_API QOlmSession -- cgit v1.2.3 From 1fe8dc00de4d9bb7072ec9677ec7f8e73e4fc769 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 15 May 2022 21:52:31 +0200 Subject: QOlmAccount::needsSave() shouldn't be const Making Qt signals const is an impossible commitment - once the signal is out, you can't control if any called slot will change the emitting class or not. The code compiles but const-ness is not preserved. --- lib/e2ee/qolmaccount.cpp | 5 +++-- lib/e2ee/qolmaccount.h | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index af9eb1bf..3339ce46 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -143,7 +143,7 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const return olm_account_max_number_of_one_time_keys(m_account); } -size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const +size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) { const size_t randomLength = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); QByteArray randomBuffer = getRandom(randomLength); @@ -196,7 +196,8 @@ QByteArray QOlmAccount::signOneTimeKey(const QString &key) const return sign(j.toJson(QJsonDocument::Compact)); } -std::optional QOlmAccount::removeOneTimeKeys(const QOlmSessionPtr &session) const +std::optional QOlmAccount::removeOneTimeKeys( + const QOlmSessionPtr& session) { const auto error = olm_remove_one_time_keys(m_account, session->raw()); diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 08301998..1f36919a 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -24,7 +24,7 @@ class QUOTIENT_API QOlmAccount : public QObject Q_OBJECT public: QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent = nullptr); - ~QOlmAccount(); + ~QOlmAccount() override; //! Creates a new instance of OlmAccount. During the instantiation //! the Ed25519 fingerprint key pair and the Curve25519 identity key @@ -55,7 +55,7 @@ public: size_t maxNumberOfOneTimeKeys() const; //! Generates the supplied number of one time keys. - size_t generateOneTimeKeys(size_t numberOfKeys) const; + size_t generateOneTimeKeys(size_t numberOfKeys); //! Gets the OlmAccount's one time keys formatted as JSON. OneTimeKeys oneTimeKeys() const; @@ -97,7 +97,7 @@ public: OlmAccount *data(); Q_SIGNALS: - void needsSave() const; + void needsSave(); private: OlmAccount *m_account = nullptr; // owning -- cgit v1.2.3 From a3486fd0e9786c47564ce8173a2e4039135b10f9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 15 May 2022 22:08:09 +0200 Subject: Simplify QOlmSession::matchesInboundSession*() There's no particular use in letting `QOlmError` out, only to confirm that, well, `QOlmError` is just another form of no-match. --- autotests/testolmsession.cpp | 2 +- lib/connection.cpp | 3 +-- lib/e2ee/qolmsession.cpp | 43 +++++++++++++++++-------------------------- 3 files changed, 19 insertions(+), 29 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 5436c392..2674b60b 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -45,7 +45,7 @@ void TestOlmSession::olmEncryptDecrypt() const auto encrypted = outboundSession->encrypt("Hello world!"); if (encrypted.type() == QOlmMessage::PreKey) { QOlmMessage m(encrypted); // clone - QVERIFY(std::get(inboundSession->matchesInboundSession(m))); + QVERIFY(inboundSession->matchesInboundSession(m)); } const auto decrypted = std::get(inboundSession->decrypt(encrypted)); diff --git a/lib/connection.cpp b/lib/connection.cpp index 511b64e2..955b5b1a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -226,8 +226,7 @@ public: { Q_ASSERT(message.type() == QOlmMessage::PreKey); for(auto& session : olmSessions[senderKey]) { - const auto matches = session->matchesInboundSessionFrom(senderKey, message); - if(std::holds_alternative(matches) && std::get(matches)) { + if (session->matchesInboundSessionFrom(senderKey, message)) { qCDebug(E2EE) << "Found inbound session"; const auto result = session->decrypt(message); if(std::holds_alternative(result)) { diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 531a6696..5ccbfd40 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -209,42 +209,33 @@ bool QOlmSession::hasReceivedMessage() const return olm_session_has_received_message(m_session); } -std::variant QOlmSession::matchesInboundSession(const QOlmMessage &preKeyMessage) const +bool QOlmSession::matchesInboundSession(const QOlmMessage& preKeyMessage) const { Q_ASSERT(preKeyMessage.type() == QOlmMessage::Type::PreKey); QByteArray oneTimeKeyBuf(preKeyMessage.data()); - const auto matchesResult = olm_matches_inbound_session(m_session, oneTimeKeyBuf.data(), oneTimeKeyBuf.length()); + const auto maybeMatches = + olm_matches_inbound_session(m_session, oneTimeKeyBuf.data(), + oneTimeKeyBuf.length()); - if (matchesResult == olm_error()) { + if (maybeMatches == olm_error()) { return lastError(m_session); } - switch (matchesResult) { - case 0: - return false; - case 1: - return true; - default: - return QOlmError::Unknown; - } + return maybeMatches == 1; } -std::variant QOlmSession::matchesInboundSessionFrom(const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) const + +bool QOlmSession::matchesInboundSessionFrom( + const QString& theirIdentityKey, const QOlmMessage& preKeyMessage) const { const auto theirIdentityKeyBuf = theirIdentityKey.toUtf8(); auto oneTimeKeyMessageBuf = preKeyMessage.toCiphertext(); - const auto error = olm_matches_inbound_session_from(m_session, theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), - oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); - - if (error == olm_error()) { - return lastError(m_session); - } - switch (error) { - case 0: - return false; - case 1: - return true; - default: - return QOlmError::Unknown; - } + const auto maybeMatches = olm_matches_inbound_session_from( + m_session, theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), + oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); + + if (maybeMatches == olm_error()) + qCWarning(E2EE) << "Error matching an inbound session:" + << olm_session_last_error(m_session); + return maybeMatches == 1; } QOlmSession::QOlmSession(OlmSession *session) -- cgit v1.2.3 From 79b3dba1ed4b6870c4e989ada88e33b1ce0ddc21 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 16 May 2022 10:41:54 +0200 Subject: QOlmExpected and associated refactoring As mentioned in the commit introducing `Expected`, `QOlmExpected` is simply an alias for `Expected`. This simplifies quite a few function signatures in `QOlm*` classes and collapses unwieldy `std::holds_alternative<>`/`std::get<>` constructs into a neat contextual bool cast and an invocation of `operator*` or `value()`/`error()` accessors that don't need to specify the type. While refactoring the code, I found a couple of cases of mismatching `uint32_t` and `qint32_t` in return values; a couple of cases where `decrypt()` returns `QString` which is in fact `QByteArray` (e.g., in `QOlmSession::decrypt()`); there's a repetitive algorithm in `Connection::Private::sessionDecryptPrekey()` and `sessionDecryptGeneral()` --- autotests/testgroupsession.cpp | 14 ++-- autotests/testolmaccount.cpp | 5 +- autotests/testolmsession.cpp | 14 ++-- autotests/testolmutility.cpp | 5 +- lib/connection.cpp | 136 ++++++++++++++++++++------------------- lib/database.cpp | 32 ++++----- lib/e2ee/e2ee.h | 8 +++ lib/e2ee/qolmaccount.cpp | 20 +++--- lib/e2ee/qolmaccount.h | 21 +++--- lib/e2ee/qolminboundsession.cpp | 10 +-- lib/e2ee/qolminboundsession.h | 14 ++-- lib/e2ee/qolmoutboundsession.cpp | 18 +++--- lib/e2ee/qolmoutboundsession.h | 19 +++--- lib/e2ee/qolmsession.cpp | 22 +++++-- lib/e2ee/qolmsession.h | 35 +++++----- lib/e2ee/qolmutility.cpp | 14 ++-- lib/e2ee/qolmutility.h | 7 +- lib/room.cpp | 17 +++-- 18 files changed, 215 insertions(+), 196 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 2566669e..3c329a8a 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -16,11 +16,11 @@ void TestGroupSession::groupSessionPicklingValid() QVERIFY(QByteArray::fromBase64(ogsId).size() > 0); QCOMPARE(0, ogs->sessionMessageIndex()); - auto ogsPickled = std::get(ogs->pickle(Unencrypted {})); - auto ogs2 = std::get(QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {})); + auto ogsPickled = ogs->pickle(Unencrypted {}).value(); + auto ogs2 = QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {}).value(); QCOMPARE(ogsId, ogs2->sessionId()); - auto igs = QOlmInboundGroupSession::create(std::get(ogs->sessionKey())); + auto igs = QOlmInboundGroupSession::create(ogs->sessionKey().value()); const auto igsId = igs->sessionId(); // ID is valid base64? QVERIFY(QByteArray::fromBase64(igsId).size() > 0); @@ -29,22 +29,22 @@ void TestGroupSession::groupSessionPicklingValid() QCOMPARE(0, igs->firstKnownIndex()); auto igsPickled = igs->pickle(Unencrypted {}); - igs = std::get(QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {})); + igs = QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {}).value(); QCOMPARE(igsId, igs->sessionId()); } void TestGroupSession::groupSessionCryptoValid() { auto ogs = QOlmOutboundGroupSession::create(); - auto igs = QOlmInboundGroupSession::create(std::get(ogs->sessionKey())); + auto igs = QOlmInboundGroupSession::create(ogs->sessionKey().value()); QCOMPARE(ogs->sessionId(), igs->sessionId()); const auto plainText = QStringLiteral("Hello world!"); - const auto ciphertext = std::get(ogs->encrypt(plainText)); + const auto ciphertext = ogs->encrypt(plainText).value(); // ciphertext valid base64? QVERIFY(QByteArray::fromBase64(ciphertext).size() > 0); - const auto decryptionResult = std::get>(igs->decrypt(ciphertext)); + const auto decryptionResult = igs->decrypt(ciphertext).value(); //// correct plaintext? QCOMPARE(plainText, decryptionResult.first); diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 9989665a..e31ff6d3 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -21,7 +21,7 @@ void TestOlmAccount::pickleUnpickledTest() QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); olmAccount.createNewAccount(); auto identityKeys = olmAccount.identityKeys(); - auto pickled = std::get(olmAccount.pickle(Unencrypted{})); + auto pickled = olmAccount.pickle(Unencrypted{}).value(); QOlmAccount olmAccount2(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); olmAccount2.unpickle(pickled, Unencrypted{}); auto identityKeys2 = olmAccount2.identityKeys(); @@ -57,8 +57,7 @@ void TestOlmAccount::signatureValid() const auto identityKeys = olmAccount.identityKeys(); const auto ed25519Key = identityKeys.ed25519; const auto verify = utility.ed25519Verify(ed25519Key, message, signature); - QVERIFY(std::holds_alternative(verify)); - QVERIFY(std::get(verify) == true); + QVERIFY(verify.value_or(false)); } void TestOlmAccount::oneTimeKeysValid() diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 2674b60b..182659e7 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -20,8 +20,8 @@ std::pair createSessionPair() const QByteArray oneTimeKeyA("WzsbsjD85iB1R32iWxfJdwkgmdz29ClMbJSJziECYwk"); const QByteArray identityKeyB("q/YhJtog/5VHCAS9rM9uUf6AaFk1yPe4GYuyUOXyQCg"); const QByteArray oneTimeKeyB("oWvzryma+B2onYjo3hM6A3Mgo/Yepm8HvgSvwZMTnjQ"); - auto outbound = std::get(accountA - .createOutboundSession(identityKeyB, oneTimeKeyB)); + auto outbound = + accountA.createOutboundSession(identityKeyB, oneTimeKeyB).value(); const auto preKey = outbound->encrypt(""); // Payload does not matter for PreKey @@ -29,7 +29,7 @@ std::pair createSessionPair() // We can't call QFail here because it's an helper function returning a value throw "Wrong first message type received, can't create session"; } - auto inbound = std::get(accountB.createInboundSession(preKey)); + auto inbound = accountB.createInboundSession(preKey).value(); return { std::move(inbound), std::move(outbound) }; } @@ -48,7 +48,7 @@ void TestOlmSession::olmEncryptDecrypt() QVERIFY(inboundSession->matchesInboundSession(m)); } - const auto decrypted = std::get(inboundSession->decrypt(encrypted)); + const auto decrypted = inboundSession->decrypt(encrypted).value(); QCOMPARE(decrypted, "Hello world!"); } @@ -56,11 +56,11 @@ void TestOlmSession::olmEncryptDecrypt() void TestOlmSession::correctSessionOrdering() { // n0W5IJ2ZmaI9FxKRj/wohUQ6WEU0SfoKsgKKHsr4VbM - auto session1 = std::get(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGvlaV6t/0ihD2/0QGckDIvbmE1aV+PxB0zUtHXh99bI/60N+PWkCLA84jEY4sz3d45ui/TVoFGLDHlymKxvlj7XngXrbtlxSkVntsPzDiNpKEXCa26N2ubKpQ0fbjrV5gbBTYWfU04DXHPXFDTksxpNALYt/h0eVMVhf6hB0ZzpLBsOG0mpwkLufwub0CuDEDGGmRddz3TcNCLq5NnI8R9udDWvHAkTS1UTbHuIf/y6cZg875nJyXpAvd8/XhL8TOo8ot2sE1fElBa4vrH/m9rBQMC1GPkhLBIizmY44C+Sq9PQRnF+uCZ", Unencrypted{})); + auto session1 = QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGvlaV6t/0ihD2/0QGckDIvbmE1aV+PxB0zUtHXh99bI/60N+PWkCLA84jEY4sz3d45ui/TVoFGLDHlymKxvlj7XngXrbtlxSkVntsPzDiNpKEXCa26N2ubKpQ0fbjrV5gbBTYWfU04DXHPXFDTksxpNALYt/h0eVMVhf6hB0ZzpLBsOG0mpwkLufwub0CuDEDGGmRddz3TcNCLq5NnI8R9udDWvHAkTS1UTbHuIf/y6cZg875nJyXpAvd8/XhL8TOo8ot2sE1fElBa4vrH/m9rBQMC1GPkhLBIizmY44C+Sq9PQRnF+uCZ", Unencrypted{}).value(); // +9pHJhP3K4E5/2m8PYBPLh8pS9CJodwUOh8yz3mnmw0 - auto session2 = std::get(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UFD+q37/WlfTAzQsSjCdD07FcErZ4siEy5vpiB+pyO8i53ptZvb2qRvqNKFzPaXuu33PS2PBTmmnR+kJt+DgDNqWadyaj/WqEAejc7ALqSs5GuhbZtpoLe+lRSRK0rwVX3gzz4qrl8pm0pD5pSZAUWRXDRlieGWMclz68VUvnSaQH7ElTo4S634CJk+xQfFFCD26v0yONPSN6rwouS1cWPuG5jTlnV8vCFVTU2+lduKh54Ko6FUJ/ei4xR8Nk2duBGSc/TdllX9e2lDYHSUkWoD4ti5xsFioB8Blus7JK9BZfcmRmdlxIOD", Unencrypted {})); + auto session2 = QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UFD+q37/WlfTAzQsSjCdD07FcErZ4siEy5vpiB+pyO8i53ptZvb2qRvqNKFzPaXuu33PS2PBTmmnR+kJt+DgDNqWadyaj/WqEAejc7ALqSs5GuhbZtpoLe+lRSRK0rwVX3gzz4qrl8pm0pD5pSZAUWRXDRlieGWMclz68VUvnSaQH7ElTo4S634CJk+xQfFFCD26v0yONPSN6rwouS1cWPuG5jTlnV8vCFVTU2+lduKh54Ko6FUJ/ei4xR8Nk2duBGSc/TdllX9e2lDYHSUkWoD4ti5xsFioB8Blus7JK9BZfcmRmdlxIOD", Unencrypted {}).value(); // MC7n8hX1l7WlC2/WJGHZinMocgiBZa4vwGAOredb/ME - auto session3 = std::get(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGNk2TmVDJ95K0Nywf24FNklNVtXtFDiFPHFwNSmCbHNCp3hsGtZlt0AHUkMmL48XklLqzwtVk5/v2RRmSKR5LqYdIakrtuK/fY0ENhBZIbI1sRetaJ2KMbY9l6rCJNfFg8VhpZ4KTVvEZVuP9g/eZkCnP5NxzXiBRF6nfY3O/zhcKxa3acIqs6BMhyLsfuJ80t+hQ1HvVyuhBerGujdSDzV9tJ9SPidOwfYATk81LVF9hTmnI0KaZa7qCtFzhG0dU/Z3hIWH9HOaw1aSB/IPmughbwdJOwERyhuo3YHoznlQnJ7X252BlI", Unencrypted{})); + auto session3 = QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGNk2TmVDJ95K0Nywf24FNklNVtXtFDiFPHFwNSmCbHNCp3hsGtZlt0AHUkMmL48XklLqzwtVk5/v2RRmSKR5LqYdIakrtuK/fY0ENhBZIbI1sRetaJ2KMbY9l6rCJNfFg8VhpZ4KTVvEZVuP9g/eZkCnP5NxzXiBRF6nfY3O/zhcKxa3acIqs6BMhyLsfuJ80t+hQ1HvVyuhBerGujdSDzV9tJ9SPidOwfYATk81LVF9hTmnI0KaZa7qCtFzhG0dU/Z3hIWH9HOaw1aSB/IPmughbwdJOwERyhuo3YHoznlQnJ7X252BlI", Unencrypted{}).value(); const auto session1Id = session1->sessionId(); const auto session2Id = session2->sessionId(); diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index 92b8b5b2..c1323533 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -81,7 +81,10 @@ void TestOlmUtility::verifySignedOneTimeKey() delete[](reinterpret_cast(utility)); QOlmUtility utility2; - auto res2 = std::get(utility2.ed25519Verify(aliceOlm.identityKeys().ed25519, msg, signatureBuf1)); + auto res2 = + utility2 + .ed25519Verify(aliceOlm.identityKeys().ed25519, msg, signatureBuf1) + .value_or(false); //QCOMPARE(std::string(olm_utility_last_error(utility)), "SUCCESS"); QCOMPARE(res2, true); diff --git a/lib/connection.cpp b/lib/connection.cpp index 955b5b1a..2528b70b 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -212,82 +212,83 @@ public: void loadSessions() { olmSessions = q->database()->loadOlmSessions(q->picklingMode()); } - void saveSession(QOlmSessionPtr& session, const QString &senderKey) { - auto pickleResult = session->pickle(q->picklingMode()); - if (std::holds_alternative(pickleResult)) { - qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); - return; - } - q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult), QDateTime::currentDateTime()); - } - - std::pair sessionDecryptPrekey(const QOlmMessage& message, - const QString& senderKey) + void saveSession(QOlmSession& session, const QString& senderKey) { - Q_ASSERT(message.type() == QOlmMessage::PreKey); - for(auto& session : olmSessions[senderKey]) { - if (session->matchesInboundSessionFrom(senderKey, message)) { - qCDebug(E2EE) << "Found inbound session"; - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); - return { std::get(result), session->sessionId() }; - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message"; - return {}; - } - } - } - qCDebug(E2EE) << "Creating new inbound session"; - auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); - if(std::holds_alternative(newSessionResult)) { - qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); - return {}; - } - auto newSession = std::move(std::get(newSessionResult)); - auto error = olmAccount->removeOneTimeKeys(newSession); - if (error) { - qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); - } - const auto result = newSession->decrypt(message); - QString sessionId = newSession->sessionId(); - saveSession(newSession, senderKey); - olmSessions[senderKey].push_back(std::move(newSession)); - if(std::holds_alternative(result)) { - return { std::get(result), sessionId }; - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; - return {}; - } + if (auto pickleResult = session.pickle(q->picklingMode())) + q->database()->saveOlmSession(senderKey, session.sessionId(), + *pickleResult, + QDateTime::currentDateTime()); + else + qCWarning(E2EE) << "Failed to pickle olm session. Error" + << pickleResult.error(); } - std::pair sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) + + template + std::pair doDecryptMessage(const QOlmSession& session, + const QOlmMessage& message, + FnT&& andThen) const { - Q_ASSERT(message.type() == QOlmMessage::General); - for(auto& session : olmSessions[senderKey]) { - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); - return { std::get(result), session->sessionId() }; - } + const auto expectedMessage = session.decrypt(message); + if (expectedMessage) { + const auto result = + std::make_pair(*expectedMessage, session.sessionId()); + andThen(); + return result; } - qCWarning(E2EE) << "Failed to decrypt message"; + const auto errorLine = message.type() == QOlmMessage::PreKey + ? "Failed to decrypt prekey message:" + : "Failed to decrypt message:"; + qCDebug(E2EE) << errorLine << expectedMessage.error(); return {}; } std::pair sessionDecryptMessage( const QJsonObject& personalCipherObject, const QByteArray& senderKey) { + const auto msgType = static_cast( + personalCipherObject.value(TypeKeyL).toInt(-1)); + if (msgType != QOlmMessage::General && msgType != QOlmMessage::PreKey) { + qCWarning(E2EE) << "Olm message has incorrect type" << msgType; + return {}; + } QOlmMessage message { - personalCipherObject.value(BodyKeyL).toString().toLatin1(), - static_cast( - personalCipherObject.value(TypeKeyL).toInt(-1)) + personalCipherObject.value(BodyKeyL).toString().toLatin1(), msgType }; - if (message.type() == QOlmMessage::PreKey) - return sessionDecryptPrekey(message, senderKey); - if (message.type() == QOlmMessage::General) - return sessionDecryptGeneral(message, senderKey); - qCWarning(E2EE) << "Olm message has incorrect type" << message.type(); - return {}; + for (const auto& session : olmSessions[senderKey]) + if (msgType == QOlmMessage::General + || session->matchesInboundSessionFrom(senderKey, message)) { + return doDecryptMessage(*session, message, [this, &session] { + q->database()->setOlmSessionLastReceived( + session->sessionId(), QDateTime::currentDateTime()); + }); + } + + if (msgType == QOlmMessage::General) { + qCWarning(E2EE) << "Failed to decrypt message"; + return {}; + } + + qCDebug(E2EE) << "Creating new inbound session"; // Pre-key messages only + auto newSessionResult = + olmAccount->createInboundSessionFrom(senderKey, message); + if (!newSessionResult) { + qCWarning(E2EE) + << "Failed to create inbound session for" << senderKey + << "with error" << newSessionResult.error(); + return {}; + } + auto newSession = std::move(*newSessionResult); + auto error = olmAccount->removeOneTimeKeys(*newSession); + if (error) { + qWarning(E2EE) << "Failed to remove one time key for session" + << newSession->sessionId(); + // Keep going though + } + return doDecryptMessage( + *newSession, message, [this, &senderKey, &newSession] { + saveSession(*newSession, senderKey); + olmSessions[senderKey].push_back(std::move(newSession)); + }); } #endif @@ -2177,8 +2178,11 @@ void Connection::saveOlmAccount() { qCDebug(E2EE) << "Saving olm account"; #ifdef Quotient_E2EE_ENABLED - auto pickle = d->olmAccount->pickle(d->picklingMode); - d->database->setAccountPickle(std::get(pickle)); + if (const auto expectedPickle = d->olmAccount->pickle(d->picklingMode)) + d->database->setAccountPickle(*expectedPickle); + else + qCWarning(E2EE) << "Couldn't save Olm account pickle:" + << expectedPickle.error(); #endif } diff --git a/lib/database.cpp b/lib/database.cpp index 3189b3f1..e2e7acc9 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -184,12 +184,14 @@ UnorderedMap> Database::loadOlmSessions(con commit(); UnorderedMap> sessions; while (query.next()) { - auto session = QOlmSession::unpickle(query.value("pickle").toByteArray(), picklingMode); - if (std::holds_alternative(session)) { - qCWarning(E2EE) << "Failed to unpickle olm session"; - continue; - } - sessions[query.value("senderKey").toString()].push_back(std::move(std::get(session))); + if (auto expectedSession = + QOlmSession::unpickle(query.value("pickle").toByteArray(), + picklingMode)) { + sessions[query.value("senderKey").toString()].emplace_back( + std::move(*expectedSession)); + } else + qCWarning(E2EE) + << "Failed to unpickle olm session:" << expectedSession.error(); } return sessions; } @@ -203,15 +205,15 @@ UnorderedMap Database::loadMegolmSessions(c commit(); UnorderedMap sessions; while (query.next()) { - auto session = QOlmInboundGroupSession::unpickle(query.value("pickle").toByteArray(), picklingMode); - if (std::holds_alternative(session)) { - qCWarning(E2EE) << "Failed to unpickle megolm session"; - continue; - } - - sessions[query.value("sessionId").toString()] = std::move(std::get(session)); - sessions[query.value("sessionId").toString()]->setOlmSessionId(query.value("olmSessionId").toString()); - sessions[query.value("sessionId").toString()]->setSenderId(query.value("senderId").toString()); + if (auto expectedSession = QOlmInboundGroupSession::unpickle( + query.value("pickle").toByteArray(), picklingMode)) { + auto& sessionPtr = sessions[query.value("sessionId").toString()] = + std::move(*expectedSession); + sessionPtr->setOlmSessionId(query.value("olmSessionId").toString()); + sessionPtr->setSenderId(query.value("senderId").toString()); + } else + qCWarning(E2EE) << "Failed to unpickle megolm session:" + << expectedSession.error(); } return sessions; } diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 268cb525..8e433d60 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -6,6 +6,8 @@ #pragma once #include "converters.h" +#include "expected.h" +#include "qolmerrors.h" #include "quotient_common.h" #include @@ -55,6 +57,12 @@ using QOlmSessionPtr = std::unique_ptr; class QOlmInboundGroupSession; using QOlmInboundGroupSessionPtr = std::unique_ptr; +class QOlmOutboundGroupSession; +using QOlmOutboundGroupSessionPtr = std::unique_ptr; + +template +using QOlmExpected = Expected; + struct IdentityKeys { QByteArray curve25519; diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 3339ce46..72dddafb 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -73,7 +73,7 @@ void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) } } -std::variant QOlmAccount::pickle(const PicklingMode &mode) +QOlmExpected QOlmAccount::pickle(const PicklingMode &mode) { const QByteArray key = toKey(mode); const size_t pickleLength = olm_pickle_account_length(m_account); @@ -197,9 +197,9 @@ QByteArray QOlmAccount::signOneTimeKey(const QString &key) const } std::optional QOlmAccount::removeOneTimeKeys( - const QOlmSessionPtr& session) + const QOlmSession& session) { - const auto error = olm_remove_one_time_keys(m_account, session->raw()); + const auto error = olm_remove_one_time_keys(m_account, session.raw()); if (error == olm_error()) { return lastError(m_account); @@ -245,19 +245,19 @@ UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKey return new UploadKeysJob(keys, oneTimeKeysSigned); } -std::variant QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) +QOlmExpected QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSession(this, preKeyMessage); } -std::variant QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) +QOlmExpected QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); } -std::variant QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) +QOlmExpected QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) { return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); } @@ -296,10 +296,6 @@ bool Quotient::ed25519VerifySignature(const QString& signingKey, QByteArray signingKeyBuf = signingKey.toUtf8(); QOlmUtility utility; auto signatureBuf = signature.toUtf8(); - auto result = utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf); - if (std::holds_alternative(result)) { - return false; - } - - return std::get(result); + return utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf) + .value_or(false); } diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 1f36919a..ee2aa69d 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -5,11 +5,12 @@ #pragma once -#include "csapi/keys.h" #include "e2ee/e2ee.h" -#include "e2ee/qolmerrors.h" #include "e2ee/qolmmessage.h" -#include + +#include "csapi/keys.h" + +#include struct OlmAccount; @@ -38,7 +39,7 @@ public: void unpickle(QByteArray &pickled, const PicklingMode &mode); //! Serialises an OlmAccount to encrypted Base64. - std::variant pickle(const PicklingMode &mode); + QOlmExpected pickle(const PicklingMode &mode); //! Returns the account's public identity keys already formatted as JSON IdentityKeys identityKeys() const; @@ -73,22 +74,26 @@ public: DeviceKeys deviceKeys() const; //! Remove the one time key used to create the supplied session. - [[nodiscard]] std::optional removeOneTimeKeys(const QOlmSessionPtr &session) const; + [[nodiscard]] std::optional removeOneTimeKeys( + const QOlmSession& session); //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. //! //! \param message An Olm pre-key message that was encrypted for this account. - std::variant createInboundSession(const QOlmMessage &preKeyMessage); + QOlmExpected createInboundSession( + const QOlmMessage& preKeyMessage); //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. //! //! \param theirIdentityKey - The identity key of the Olm account that //! encrypted this Olm message. - std::variant createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage); + QOlmExpected createInboundSessionFrom( + const QByteArray& theirIdentityKey, const QOlmMessage& preKeyMessage); //! Creates an outbound session for sending messages to a specific /// identity and one time key. - std::variant createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); + QOlmExpected createOutboundSession( + const QByteArray& theirIdentityKey, const QByteArray& theirOneTimeKey); void markKeysAsPublished(); diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 62856831..17f06205 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -70,7 +70,8 @@ QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const return pickledBuf; } -std::variant, QOlmError> QOlmInboundGroupSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +QOlmExpected 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()]); @@ -85,7 +86,8 @@ std::variant, QOlmError> QOlmInboundGro return std::make_unique(groupSession); } -std::variant, QOlmError> QOlmInboundGroupSession::decrypt(const QByteArray &message) +QOlmExpected> QOlmInboundGroupSession::decrypt( + const QByteArray& message) { // This is for capturing the output of olm_group_decrypt uint32_t messageIndex = 0; @@ -114,10 +116,10 @@ std::variant, QOlmError> QOlmInboundGroupSession::d QByteArray output(plaintextLen, '0'); std::memcpy(output.data(), plaintextBuf.data(), plaintextLen); - return std::make_pair(QString(output), messageIndex); + return std::make_pair(output, messageIndex); } -std::variant QOlmInboundGroupSession::exportSession(uint32_t messageIndex) +QOlmExpected QOlmInboundGroupSession::exportSession(uint32_t messageIndex) { const auto keyLength = olm_export_inbound_group_session_length(m_groupSession); QByteArray keyBuf(keyLength, '0'); diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 13515434..1a9b4415 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -5,11 +5,8 @@ #pragma once #include "e2ee/e2ee.h" -#include "e2ee/qolmerrors.h" -#include "olm/olm.h" -#include -#include +#include namespace Quotient { @@ -27,14 +24,13 @@ public: QByteArray pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. - static std::variant, QOlmError> - unpickle(const QByteArray& picked, const PicklingMode& mode); + static QOlmExpected unpickle( + const QByteArray& pickled, const PicklingMode& mode); //! Decrypts ciphertext received for this group session. - std::variant, QOlmError> decrypt( - const QByteArray& message); + QOlmExpected > decrypt(const QByteArray& message); //! Export the base64-encoded ratchet key for this session, at the given index, //! in a format which can be used by import. - std::variant exportSession(uint32_t messageIndex); + QOlmExpected exportSession(uint32_t messageIndex); //! Get the first message index we know how to decrypt. uint32_t firstKnownIndex() const; //! Get a base64-encoded identifier for this session. diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index da32417b..96bad344 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -13,8 +13,7 @@ QOlmError lastError(OlmOutboundGroupSession *session) { QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session) : m_groupSession(session) -{ -} +{} QOlmOutboundGroupSession::~QOlmOutboundGroupSession() { @@ -22,7 +21,7 @@ QOlmOutboundGroupSession::~QOlmOutboundGroupSession() delete[](reinterpret_cast(m_groupSession)); } -std::unique_ptr QOlmOutboundGroupSession::create() +QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() { auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); const auto randomLength = olm_init_outbound_group_session_random_length(olmOutboundGroupSession); @@ -45,7 +44,7 @@ std::unique_ptr QOlmOutboundGroupSession::create() return std::make_unique(olmOutboundGroupSession); } -std::variant QOlmOutboundGroupSession::pickle(const PicklingMode &mode) +QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mode) { QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); QByteArray key = toKey(mode); @@ -61,7 +60,7 @@ std::variant QOlmOutboundGroupSession::pickle(const Pickl return pickledBuf; } -std::variant, QOlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +QOlmExpected QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) { QByteArray pickledBuf = pickled; auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); @@ -80,7 +79,7 @@ std::variant, QOlmError> QOlmOutboundG return std::make_unique(olmOutboundGroupSession); } -std::variant QOlmOutboundGroupSession::encrypt(const QString &plaintext) +QOlmExpected QOlmOutboundGroupSession::encrypt(const QString &plaintext) { QByteArray plaintextBuf = plaintext.toUtf8(); const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); @@ -112,12 +111,13 @@ QByteArray QOlmOutboundGroupSession::sessionId() const return idBuffer; } -std::variant QOlmOutboundGroupSession::sessionKey() const +QOlmExpected QOlmOutboundGroupSession::sessionKey() const { const auto keyMaxLength = olm_outbound_group_session_key_length(m_groupSession); QByteArray keyBuffer(keyMaxLength, '0'); - const auto error = olm_outbound_group_session_key(m_groupSession, reinterpret_cast(keyBuffer.data()), - keyMaxLength); + const auto error = olm_outbound_group_session_key( + m_groupSession, reinterpret_cast(keyBuffer.data()), + keyMaxLength); if (error == olm_error()) { return lastError(m_groupSession); } diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 32ba2b3b..8058bbb1 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -4,10 +4,10 @@ #pragma once -#include "olm/olm.h" -#include "e2ee/qolmerrors.h" #include "e2ee/e2ee.h" + #include +#include namespace Quotient { @@ -19,15 +19,15 @@ public: ~QOlmOutboundGroupSession(); //! Creates a new instance of `QOlmOutboundGroupSession`. //! Throw OlmError on errors - static std::unique_ptr create(); + static QOlmOutboundGroupSessionPtr create(); //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); + QOlmExpected pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. - static std::variant, QOlmError> - unpickle(QByteArray& pickled, const PicklingMode& mode); + static QOlmExpected unpickle( + QByteArray& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. - std::variant encrypt(const QString &plaintext); + QOlmExpected encrypt(const QString& plaintext); //! Get the current message index for this session. //! @@ -42,11 +42,10 @@ public: //! //! Each message is sent with a different ratchet key. This function returns the //! ratchet key that will be used for the next message. - std::variant sessionKey() const; + QOlmExpected sessionKey() const; QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); private: OlmOutboundGroupSession *m_groupSession; }; -using QOlmOutboundGroupSessionPtr = std::unique_ptr; -} +} // namespace Quotient diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 5ccbfd40..2b149aac 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -27,7 +27,9 @@ OlmSession* QOlmSession::create() return olm_session(new uint8_t[olm_session_size()]); } -std::variant QOlmSession::createInbound(QOlmAccount *account, const QOlmMessage &preKeyMessage, bool from, const QString &theirIdentityKey) +QOlmExpected QOlmSession::createInbound( + QOlmAccount* account, const QOlmMessage& preKeyMessage, bool from, + const QString& theirIdentityKey) { if (preKeyMessage.type() != QOlmMessage::PreKey) { qCCritical(E2EE) << "The message is not a pre-key in when creating inbound session" << BadMessageFormat; @@ -53,17 +55,22 @@ std::variant QOlmSession::createInbound(QOlmAccount * return std::make_unique(olmSession); } -std::variant QOlmSession::createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage) +QOlmExpected QOlmSession::createInboundSession( + QOlmAccount* account, const QOlmMessage& preKeyMessage) { return createInbound(account, preKeyMessage); } -std::variant QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) +QOlmExpected QOlmSession::createInboundSessionFrom( + QOlmAccount* account, const QString& theirIdentityKey, + const QOlmMessage& preKeyMessage) { return createInbound(account, preKeyMessage, true, theirIdentityKey); } -std::variant QOlmSession::createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey) +QOlmExpected QOlmSession::createOutboundSession( + QOlmAccount* account, const QString& theirIdentityKey, + const QString& theirOneTimeKey) { auto *olmOutboundSession = create(); const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); @@ -89,7 +96,7 @@ std::variant QOlmSession::createOutboundSession(QOlmA return std::make_unique(olmOutboundSession); } -std::variant QOlmSession::pickle(const PicklingMode &mode) +QOlmExpected QOlmSession::pickle(const PicklingMode &mode) { QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); QByteArray key = toKey(mode); @@ -105,7 +112,8 @@ std::variant QOlmSession::pickle(const PicklingMode &mode return pickledBuf; } -std::variant QOlmSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +QOlmExpected QOlmSession::unpickle(const QByteArray& pickled, + const PicklingMode& mode) { QByteArray pickledBuf = pickled; auto *olmSession = create(); @@ -140,7 +148,7 @@ QOlmMessage QOlmSession::encrypt(const QString &plaintext) return QOlmMessage(messageBuf, messageType); } -std::variant QOlmSession::decrypt(const QOlmMessage &message) const +QOlmExpected QOlmSession::decrypt(const QOlmMessage &message) const { const auto messageType = message.type(); const auto ciphertext = message.toCiphertext(); diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index 4eb151b6..faae16ef 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -19,32 +19,31 @@ class QUOTIENT_API QOlmSession public: ~QOlmSession(); //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. - static std::variant, QOlmError> - createInboundSession(QOlmAccount* account, const QOlmMessage& preKeyMessage); + static QOlmExpected createInboundSession( + QOlmAccount* account, const QOlmMessage& preKeyMessage); - static std::variant, QOlmError> - createInboundSessionFrom(QOlmAccount* account, - const QString& theirIdentityKey, - const QOlmMessage& preKeyMessage); + static QOlmExpected createInboundSessionFrom( + QOlmAccount* account, const QString& theirIdentityKey, + const QOlmMessage& preKeyMessage); - static std::variant, QOlmError> - createOutboundSession(QOlmAccount* account, const QString& theirIdentityKey, - const QString& theirOneTimeKey); + static QOlmExpected createOutboundSession( + QOlmAccount* account, const QString& theirIdentityKey, + const QString& theirOneTimeKey); //! Serialises an `QOlmSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); + QOlmExpected pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static std::variant, QOlmError> unpickle( + static QOlmExpected unpickle( const QByteArray& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. QOlmMessage encrypt(const QString &plaintext); - //! Decrypts a message using this session. Decoding is lossy, meaing if + //! Decrypts a message using this session. Decoding is lossy, meaning if //! the decrypted plaintext contains invalid UTF-8 symbols, they will //! be returned as `U+FFFD` (�). - std::variant decrypt(const QOlmMessage &message) const; + QOlmExpected decrypt(const QOlmMessage &message) const; //! Get a base64-encoded identifier for this session. QByteArray sessionId() const; @@ -56,11 +55,10 @@ public: bool hasReceivedMessage() const; //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSession( - const QOlmMessage& preKeyMessage) const; + bool matchesInboundSession(const QOlmMessage& preKeyMessage) const; //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSessionFrom( + bool matchesInboundSessionFrom( const QString& theirIdentityKey, const QOlmMessage& preKeyMessage) const; friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) @@ -68,8 +66,7 @@ public: return lhs.sessionId() < rhs.sessionId(); } - friend bool operator<(const std::unique_ptr& lhs, - const std::unique_ptr& rhs) + friend bool operator<(const QOlmSessionPtr& lhs, const QOlmSessionPtr& rhs) { return *lhs < *rhs; } @@ -80,7 +77,7 @@ public: private: //! Helper function for creating new sessions and handling errors. static OlmSession* create(); - static std::variant, QOlmError> createInbound( + static QOlmExpected createInbound( QOlmAccount* account, const QOlmMessage& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); OlmSession* m_session; diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index 9f09a37f..84559085 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -3,8 +3,8 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "e2ee/qolmutility.h" -#include "olm/olm.h" -#include + +#include using namespace Quotient; @@ -40,8 +40,9 @@ QString QOlmUtility::sha256Utf8Msg(const QString &message) const return sha256Bytes(message.toUtf8()); } -std::variant QOlmUtility::ed25519Verify(const QByteArray &key, - const QByteArray &message, const QByteArray &signature) +QOlmExpected QOlmUtility::ed25519Verify(const QByteArray& key, + const QByteArray& message, + const QByteArray& signature) { QByteArray signatureBuf(signature.length(), '0'); std::copy(signature.begin(), signature.end(), signatureBuf.begin()); @@ -57,8 +58,5 @@ std::variant QOlmUtility::ed25519Verify(const QByteArray &key, return error; } - if (ret != 0) { - return false; - } - return true; + return !ret; // ret == 0 means success } diff --git a/lib/e2ee/qolmutility.h b/lib/e2ee/qolmutility.h index a12af49a..5f6bcdc5 100644 --- a/lib/e2ee/qolmutility.h +++ b/lib/e2ee/qolmutility.h @@ -4,15 +4,12 @@ #pragma once -#include -#include "e2ee/qolmerrors.h" +#include "e2ee/e2ee.h" struct OlmUtility; namespace Quotient { -class QOlmSession; - //! Allows you to make use of crytographic hashing via SHA-2 and //! verifying ed25519 signatures. class QUOTIENT_API QOlmUtility @@ -32,7 +29,7 @@ public: //! \param key QByteArray The public part of the ed25519 key that signed the message. //! \param message QByteArray The message that was signed. //! \param signature QByteArray The signature of the message. - std::variant ed25519Verify(const QByteArray &key, + QOlmExpected ed25519Verify(const QByteArray &key, const QByteArray &message, const QByteArray &signature); private: diff --git a/lib/room.cpp b/lib/room.cpp index db49e80f..1314803e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -380,17 +380,22 @@ public: return {}; } auto decryptResult = senderSession->decrypt(cipher); - if(std::holds_alternative(decryptResult)) { + if(!decryptResult) { qCWarning(E2EE) << "Unable to decrypt event" << eventId - << "with matching megolm session:" << std::get(decryptResult); + << "with matching megolm session:" << decryptResult.error(); return {}; } - const auto& [content, index] = std::get>(decryptResult); - const auto& [recordEventId, ts] = q->connection()->database()->groupSessionIndexRecord(q->id(), senderSession->sessionId(), index); + const auto& [content, index] = *decryptResult; + const auto& [recordEventId, ts] = + q->connection()->database()->groupSessionIndexRecord( + q->id(), senderSession->sessionId(), index); if (recordEventId.isEmpty()) { - q->connection()->database()->addGroupSessionIndexRecord(q->id(), senderSession->sessionId(), index, eventId, timestamp.toMSecsSinceEpoch()); + q->connection()->database()->addGroupSessionIndexRecord( + q->id(), senderSession->sessionId(), index, eventId, + timestamp.toMSecsSinceEpoch()); } else { - if ((eventId != recordEventId) || (ts != timestamp.toMSecsSinceEpoch())) { + if ((eventId != recordEventId) + || (ts != timestamp.toMSecsSinceEpoch())) { qCWarning(E2EE) << "Detected a replay attack on event" << eventId; return {}; } -- cgit v1.2.3 From 3eb7ad8b0a1ac0f6f9cda679108937a01268f184 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 16 May 2022 20:46:34 +0200 Subject: Save and load outgoing megolm session --- lib/connection.cpp | 11 ++++++++++- lib/connection.h | 5 +++++ lib/database.cpp | 41 +++++++++++++++++++++++++++++++++++++++- lib/database.h | 3 +++ lib/e2ee/qolmoutboundsession.cpp | 22 ++++++++++++++++++++- lib/e2ee/qolmoutboundsession.h | 9 +++++++++ lib/room.cpp | 23 +++++++++++----------- 7 files changed, 99 insertions(+), 15 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index a66a4168..b11ec731 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2247,7 +2247,6 @@ bool Connection::hasOlmSession(User* user, const QString& deviceId) const QPair Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) { //TODO be smarter about choosing a session; see e2ee impl guide - //TODO create session? const auto& curveKey = curveKeyForUserDevice(user->id(), device); QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); auto result = d->olmSessions[curveKey][0]->encrypt(message); @@ -2266,4 +2265,14 @@ void Connection::createOlmSession(const QString& theirIdentityKey, const QString d->olmSessions[theirIdentityKey].push_back(std::move(std::get>(session))); } +QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(Room* room) +{ + return d->database->loadCurrentOutboundMegolmSession(room->id(), d->picklingMode); +} + +void Connection::saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data) +{ + d->database->saveCurrentOutboundMegolmSession(room->id(), d->picklingMode, data); +} + #endif diff --git a/lib/connection.h b/lib/connection.h index afa4a657..8bed55da 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -25,6 +25,7 @@ #ifdef Quotient_E2EE_ENABLED #include "e2ee/e2ee.h" #include "e2ee/qolmmessage.h" +#include "e2ee/qolmoutboundsession.h" #endif Q_DECLARE_METATYPE(Quotient::GetLoginFlowsJob::LoginFlow) @@ -324,6 +325,10 @@ public: const QOlmInboundGroupSession& session); bool hasOlmSession(User* user, const QString& deviceId) const; + QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(Room* room); + void saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data); + + //This currently assumes that an olm session with (user, device) exists //TODO make this return an event? QPair olmEncryptMessage(User* user, const QString& device, const QByteArray& message); diff --git a/lib/database.cpp b/lib/database.cpp index e2e7acc9..8cb3a9d1 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -86,7 +86,7 @@ void Database::migrateTo1() execute(QStringLiteral("CREATE TABLE accounts (pickle TEXT);")); execute(QStringLiteral("CREATE TABLE olm_sessions (senderKey TEXT, sessionId TEXT, pickle TEXT);")); execute(QStringLiteral("CREATE TABLE inbound_megolm_sessions (roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); - execute(QStringLiteral("CREATE TABLE outbound_megolm_sessions (roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE outbound_megolm_sessions (roomId TEXT, sessionId TEXT, pickle TEXT, creationTime TEXT, messageCount INTEGER);")); execute(QStringLiteral("CREATE TABLE group_session_record_index (roomId TEXT, sessionId TEXT, i INTEGER, eventId TEXT, ts INTEGER);")); execute(QStringLiteral("CREATE TABLE tracked_users (matrixId TEXT);")); execute(QStringLiteral("CREATE TABLE outdated_users (matrixId TEXT);")); @@ -292,3 +292,42 @@ void Database::setOlmSessionLastReceived(const QString& sessionId, const QDateTi execute(query); commit(); } + +void Database::saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& session) +{ + const auto pickle = session->pickle(picklingMode); + if (std::holds_alternative(pickle)) { + auto deleteQuery = prepareQuery(QStringLiteral("DELETE FROM outbound_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId;")); + deleteQuery.bindValue(":roomId", roomId); + deleteQuery.bindValue(":sessionId", session->sessionId()); + + auto insertQuery = prepareQuery(QStringLiteral("INSERT INTO outbound_megolm_sessions(roomId, sessionId, pickle, creationTime, messageCount) VALUES(:roomId, :sessionId, :pickle, :creationTime, :messageCount);")); + insertQuery.bindValue(":roomId", roomId); + insertQuery.bindValue(":sessionId", session->sessionId()); + insertQuery.bindValue(":pickle", std::get(pickle)); + insertQuery.bindValue(":creationTime", session->creationTime()); + insertQuery.bindValue(":messageCount", session->messageCount()); + + transaction(); + execute(deleteQuery); + execute(insertQuery); + commit(); + } +} + +QOlmOutboundGroupSessionPtr Database::loadCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode) +{ + auto query = prepareQuery(QStringLiteral("SELECT * FROM outbound_megolm_sessions WHERE roomId=:roomId ORDER BY creationTime DESC;")); + query.bindValue(":roomId", roomId); + execute(query); + if (query.next()) { + auto sessionResult = QOlmOutboundGroupSession::unpickle(query.value("pickle").toByteArray(), picklingMode); + if (std::holds_alternative(sessionResult)) { + auto session = std::move(std::get(sessionResult)); + session->setCreationTime(query.value("creationTime").toDateTime()); + session->setMessageCount(query.value("messageCount").toInt()); + return session; + } + } + return nullptr; +} diff --git a/lib/database.h b/lib/database.h index 08fe49f3..751ebd1d 100644 --- a/lib/database.h +++ b/lib/database.h @@ -8,6 +8,7 @@ #include #include "e2ee/e2ee.h" + namespace Quotient { class QUOTIENT_API Database : public QObject { @@ -34,6 +35,8 @@ public: std::pair groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index); void clearRoomData(const QString& roomId); 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); private: void migrateTo1(); diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 96bad344..10b0c4de 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -66,7 +66,7 @@ QOlmExpected QOlmOutboundGroupSession::unpickle(QBy auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); QByteArray key = toKey(mode); const auto error = olm_unpickle_outbound_group_session(olmOutboundGroupSession, key.data(), key.length(), - pickled.data(), pickled.length()); + pickledBuf.data(), pickledBuf.length()); if (error == olm_error()) { return lastError(olmOutboundGroupSession); } @@ -123,3 +123,23 @@ QOlmExpected QOlmOutboundGroupSession::sessionKey() const } return keyBuffer; } + +int QOlmOutboundGroupSession::messageCount() const +{ + return m_messageCount; +} + +void QOlmOutboundGroupSession::setMessageCount(int messageCount) +{ + m_messageCount = messageCount; +} + +QDateTime QOlmOutboundGroupSession::creationTime() const +{ + return m_creationTime; +} + +void QOlmOutboundGroupSession::setCreationTime(const QDateTime& creationTime) +{ + m_creationTime = creationTime; +} diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 8058bbb1..56b25974 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -26,6 +26,7 @@ public: //! pickling a `QOlmOutboundGroupSession`. static QOlmExpected unpickle( QByteArray& pickled, const PicklingMode& mode); + //! Encrypts a plaintext message using the session. QOlmExpected encrypt(const QString& plaintext); @@ -44,8 +45,16 @@ public: //! ratchet key that will be used for the next message. QOlmExpected sessionKey() const; QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); + + int messageCount() const; + void setMessageCount(int messageCount); + + QDateTime creationTime() const; + void setCreationTime(const QDateTime& creationTime); private: OlmOutboundGroupSession *m_groupSession; + int m_messageCount = 0; + QDateTime m_creationTime = QDateTime::currentDateTime(); }; } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index e8b63235..9e2bd7dd 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -344,7 +344,7 @@ public: int currentMegolmSessionMessageCount = 0; //TODO save this to database unsigned long long currentMegolmSessionCreationTimestamp = 0; - std::unique_ptr currentOutboundMegolmSession = nullptr; + QOlmOutboundGroupSessionPtr currentOutboundMegolmSession = nullptr; bool addInboundGroupSession(QString sessionId, QByteArray sessionKey, const QString& senderId, @@ -415,7 +415,7 @@ public: if (!q->usesEncryption()) { return false; } - return currentMegolmSessionMessageCount >= rotationMessageCount() || (currentMegolmSessionCreationTimestamp + rotationInterval()) < QDateTime::currentMSecsSinceEpoch(); + return currentOutboundMegolmSession->messageCount() >= rotationMessageCount() || currentOutboundMegolmSession->creationTime().addMSecs(rotationInterval()) < QDateTime::currentDateTime(); } bool hasValidMegolmSession() const @@ -446,9 +446,7 @@ public: void createMegolmSession() { qCDebug(E2EE) << "Creating new outbound megolm session for room " << q->id(); currentOutboundMegolmSession = QOlmOutboundGroupSession::create(); - currentMegolmSessionMessageCount = 0; - currentMegolmSessionCreationTimestamp = QDateTime::currentMSecsSinceEpoch(); - //TODO store megolm session to database + connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); } std::unique_ptr payloadForUserDevice(User* user, const QString& device, const QByteArray& sessionId, const QByteArray& sessionKey) @@ -476,7 +474,7 @@ public: void sendRoomKeyToDevices(const QByteArray& sessionId, const QByteArray& sessionKey) { - qWarning() << "Sending room key to devices" << sessionId, sessionKey.toHex(); + qCDebug(E2EE) << "Sending room key to devices" << sessionId, sessionKey.toHex(); QHash> hash; for (const auto& user : q->users()) { QHash u; @@ -493,7 +491,7 @@ public: auto job = connection->callApi(hash); connect(job, &BaseJob::success, q, [job, this, sessionId, sessionKey](){ Connection::UsersToDevicesToEvents usersToDevicesToEvents; - auto data = job->jsonData(); + const auto data = job->jsonData(); for(const auto &user : q->users()) { for(const auto &device : connection->devicesForUser(user)) { const auto recipientCurveKey = connection->curveKeyForUserDevice(user->id(), device); @@ -527,8 +525,6 @@ public: }); } - //TODO load outbound megolm sessions from database - void sendMegolmSession() { // Save the session to this device const auto sessionId = currentOutboundMegolmSession->sessionId(); @@ -577,14 +573,16 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) } }); d->groupSessions = connection->loadRoomMegolmSessions(this); - //TODO load outbound session + d->currentOutboundMegolmSession = connection->loadCurrentOutboundMegolmSession(this); + if (d->shouldRotateMegolmSession()) { + d->currentOutboundMegolmSession = nullptr; + } connect(this, &Room::userRemoved, this, [this](){ if (!usesEncryption()) { return; } d->currentOutboundMegolmSession = nullptr; qCDebug(E2EE) << "Invalidating current megolm session because user left"; - //TODO save old session probably }); @@ -2074,6 +2072,8 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) //TODO check if we increment the sent message count event->setRoomId(id); const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(event->fullJson()).toJson()); + currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); + connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); if(std::holds_alternative(encrypted)) { //TODO something qWarning(E2EE) << "Error encrypting message" << std::get(encrypted); @@ -2084,7 +2084,6 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) encryptedEvent->setRoomId(id); encryptedEvent->setSender(connection->userId()); event->setTransactionId(encryptedEvent->transactionId()); - currentMegolmSessionMessageCount++; // We show the unencrypted event locally while pending. The echo check will throw the encrypted version out addAsPending(std::move(event)); return doSendEvent(encryptedEvent, true); -- cgit v1.2.3 From 89d8f6c44f86a27df28b1d89a80564fb0d4d89fc Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 16 May 2022 21:26:14 +0200 Subject: Fix build failures --- lib/connection.cpp | 12 ++++++------ lib/database.cpp | 10 +++++----- lib/e2ee/qolmoutboundsession.cpp | 2 +- lib/e2ee/qolmoutboundsession.h | 2 +- lib/room.cpp | 16 ++++++++-------- 5 files changed, 21 insertions(+), 21 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 82046d53..a5615f64 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2251,8 +2251,8 @@ QPair Connection::olmEncryptMessage(User* user, c 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(pickle)) { - database()->updateOlmSession(curveKey, d->olmSessions[curveKey][0]->sessionId(), std::get(pickle)); + if (pickle) { + database()->updateOlmSession(curveKey, d->olmSessions[curveKey][0]->sessionId(), *pickle); } else { qCWarning(E2EE) << "Failed to pickle olm session."; } @@ -2262,12 +2262,12 @@ QPair Connection::olmEncryptMessage(User* user, c void Connection::createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey) { auto session = QOlmSession::createOutboundSession(olmAccount(), theirIdentityKey, theirOneTimeKey); - if (std::holds_alternative(session)) { - qCWarning(E2EE) << "Failed to create olm session for " << theirIdentityKey << std::get(session); + if (!session) { + qCWarning(E2EE) << "Failed to create olm session for " << theirIdentityKey << session.error(); return; } - d->saveSession(std::get>(session), theirIdentityKey); - d->olmSessions[theirIdentityKey].push_back(std::move(std::get>(session))); + d->saveSession(**session, theirIdentityKey); + d->olmSessions[theirIdentityKey].push_back(std::move(*session)); } QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(Room* room) diff --git a/lib/database.cpp b/lib/database.cpp index d2d33006..87275e1f 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -132,7 +132,7 @@ void Database::migrateTo3() commit(); } -void Database::migrateTo3() +void Database::migrateTo4() { qCDebug(DATABASE) << "Migrating database to version 4"; transaction(); @@ -313,7 +313,7 @@ void Database::setOlmSessionLastReceived(const QString& sessionId, const QDateTi void Database::saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& session) { const auto pickle = session->pickle(picklingMode); - if (std::holds_alternative(pickle)) { + if (pickle) { auto deleteQuery = prepareQuery(QStringLiteral("DELETE FROM outbound_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId;")); deleteQuery.bindValue(":roomId", roomId); deleteQuery.bindValue(":sessionId", session->sessionId()); @@ -321,7 +321,7 @@ void Database::saveCurrentOutboundMegolmSession(const QString& roomId, const Pic auto insertQuery = prepareQuery(QStringLiteral("INSERT INTO outbound_megolm_sessions(roomId, sessionId, pickle, creationTime, messageCount) VALUES(:roomId, :sessionId, :pickle, :creationTime, :messageCount);")); insertQuery.bindValue(":roomId", roomId); insertQuery.bindValue(":sessionId", session->sessionId()); - insertQuery.bindValue(":pickle", std::get(pickle)); + insertQuery.bindValue(":pickle", pickle.value()); insertQuery.bindValue(":creationTime", session->creationTime()); insertQuery.bindValue(":messageCount", session->messageCount()); @@ -339,8 +339,8 @@ QOlmOutboundGroupSessionPtr Database::loadCurrentOutboundMegolmSession(const QSt execute(query); if (query.next()) { auto sessionResult = QOlmOutboundGroupSession::unpickle(query.value("pickle").toByteArray(), picklingMode); - if (std::holds_alternative(sessionResult)) { - auto session = std::move(std::get(sessionResult)); + if (sessionResult) { + auto session = std::move(*sessionResult); session->setCreationTime(query.value("creationTime").toDateTime()); session->setMessageCount(query.value("messageCount").toInt()); return session; diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 10b0c4de..76188d08 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -60,7 +60,7 @@ QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mo return pickledBuf; } -QOlmExpected QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +QOlmExpected QOlmOutboundGroupSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) { QByteArray pickledBuf = pickled; auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 56b25974..c20613d3 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -25,7 +25,7 @@ public: //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. static QOlmExpected unpickle( - QByteArray& pickled, const PicklingMode& mode); + const QByteArray& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. QOlmExpected encrypt(const QString& plaintext); diff --git a/lib/room.cpp b/lib/room.cpp index 35de59ed..d77bf9ef 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -448,11 +448,11 @@ public: connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); const auto sessionKey = currentOutboundMegolmSession->sessionKey(); - if(std::holds_alternative(sessionKey)) { + if(!sessionKey) { qCWarning(E2EE) << "Failed to load key for new megolm session"; return; } - addInboundGroupSession(currentOutboundMegolmSession->sessionId(), std::get(sessionKey), q->localUser()->id(), "SELF"_ls); + addInboundGroupSession(currentOutboundMegolmSession->sessionId(), *sessionKey, q->localUser()->id(), "SELF"_ls); } std::unique_ptr payloadForUserDevice(User* user, const QString& device, const QByteArray& sessionId, const QByteArray& sessionKey) @@ -526,7 +526,7 @@ public: signedData.remove("unsigned"); signedData.remove("signatures"); auto signatureMatch = QOlmUtility().ed25519Verify(connection->edKeyForUserDevice(user->id(), device).toLatin1(), QJsonDocument(signedData).toJson(QJsonDocument::Compact), signature); - if (std::holds_alternative(signatureMatch)) { + if (!signatureMatch) { qCWarning(E2EE) << "Failed to verify one-time-key signature for" << user->id() << device << ". Skipping this device."; continue; } else { @@ -547,11 +547,11 @@ public: // Save the session to this device const auto sessionId = currentOutboundMegolmSession->sessionId(); const auto _sessionKey = currentOutboundMegolmSession->sessionKey(); - if(std::holds_alternative(_sessionKey)) { + if(!_sessionKey) { qCWarning(E2EE) << "Error loading session key"; return; } - const auto sessionKey = std::get(_sessionKey); + const auto sessionKey = *_sessionKey; const auto senderKey = q->connection()->olmAccount()->identityKeys().curve25519; // Send the session to other people @@ -2101,11 +2101,11 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(pEvent->fullJson()).toJson()); currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); - if(std::holds_alternative(encrypted)) { - qWarning(E2EE) << "Error encrypting message" << std::get(encrypted); + if(!encrypted) { + qWarning(E2EE) << "Error encrypting message" << encrypted.error(); return {}; } - auto encryptedEvent = new EncryptedEvent(std::get(encrypted), q->connection()->olmAccount()->identityKeys().curve25519, q->connection()->deviceId(), currentOutboundMegolmSession->sessionId()); + auto encryptedEvent = new EncryptedEvent(*encrypted, q->connection()->olmAccount()->identityKeys().curve25519, q->connection()->deviceId(), currentOutboundMegolmSession->sessionId()); encryptedEvent->setTransactionId(connection->generateTxnId()); encryptedEvent->setRoomId(id); encryptedEvent->setSender(connection->userId()); -- cgit v1.2.3 From 64797165f04a16d290dd27c2f962060b40f85be3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 25 May 2022 22:48:53 +0200 Subject: Refactor creation of Megolm sessions in Room Notably, replace a multi-level hash map with QMultiHash and factor out Room::P::createOlmSession(). --- lib/connection.cpp | 14 ++-- lib/connection.h | 8 +- lib/database.cpp | 21 ++++-- lib/database.h | 42 ++++++++--- lib/e2ee/qolmoutboundsession.cpp | 4 +- lib/e2ee/qolmoutboundsession.h | 4 +- lib/room.cpp | 156 +++++++++++++++++++++++---------------- 7 files changed, 151 insertions(+), 98 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 8fd2d6cf..1193eb75 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2187,7 +2187,7 @@ QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) return decrypted ? decrypted->fullJson() : QJsonObject(); } -Database* Connection::database() +Database* Connection::database() const { return d->database; } @@ -2271,14 +2271,18 @@ void Connection::createOlmSession(const QString& theirIdentityKey, d->olmSessions[theirIdentityKey].push_back(std::move(*session)); } -QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(Room* room) +QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession( + const QString& roomId) const { - return d->database->loadCurrentOutboundMegolmSession(room->id(), d->picklingMode); + return d->database->loadCurrentOutboundMegolmSession(roomId, + d->picklingMode); } -void Connection::saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data) +void Connection::saveCurrentOutboundMegolmSession( + const QString& roomId, const QOlmOutboundGroupSession& session) const { - d->database->saveCurrentOutboundMegolmSession(room->id(), d->picklingMode, data); + d->database->saveCurrentOutboundMegolmSession(roomId, d->picklingMode, + session); } #endif diff --git a/lib/connection.h b/lib/connection.h index 43e285c1..a2824744 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -318,16 +318,16 @@ public: bool isLoggedIn() const; #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; - Database* database(); + Database* database() const; UnorderedMap loadRoomMegolmSessions( const Room* room); void saveMegolmSession(const Room* room, const QOlmInboundGroupSession& session); bool hasOlmSession(const QString& user, const QString& deviceId) const; - QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(Room* room); - void saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data); - + QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession( + const QString& roomId) const; + void saveCurrentOutboundMegolmSession(const QString& roomId, const QOlmOutboundGroupSession &session) const; //This assumes that an olm session with (user, device) exists std::pair olmEncryptMessage( diff --git a/lib/database.cpp b/lib/database.cpp index 0119b35c..193ff54e 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -307,20 +307,22 @@ void Database::setOlmSessionLastReceived(const QString& sessionId, const QDateTi commit(); } -void Database::saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& session) +void Database::saveCurrentOutboundMegolmSession( + const QString& roomId, const PicklingMode& picklingMode, + const QOlmOutboundGroupSession& session) { - const auto pickle = session->pickle(picklingMode); + const auto pickle = session.pickle(picklingMode); if (pickle) { auto deleteQuery = prepareQuery(QStringLiteral("DELETE FROM outbound_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId;")); deleteQuery.bindValue(":roomId", roomId); - deleteQuery.bindValue(":sessionId", session->sessionId()); + deleteQuery.bindValue(":sessionId", session.sessionId()); auto insertQuery = prepareQuery(QStringLiteral("INSERT INTO outbound_megolm_sessions(roomId, sessionId, pickle, creationTime, messageCount) VALUES(:roomId, :sessionId, :pickle, :creationTime, :messageCount);")); insertQuery.bindValue(":roomId", roomId); - insertQuery.bindValue(":sessionId", session->sessionId()); + insertQuery.bindValue(":sessionId", session.sessionId()); insertQuery.bindValue(":pickle", pickle.value()); - insertQuery.bindValue(":creationTime", session->creationTime()); - insertQuery.bindValue(":messageCount", session->messageCount()); + insertQuery.bindValue(":creationTime", session.creationTime()); + insertQuery.bindValue(":messageCount", session.messageCount()); transaction(); execute(deleteQuery); @@ -362,7 +364,9 @@ void Database::setDevicesReceivedKey(const QString& roomId, const QVector Database::devicesWithoutKey(const QString& roomId, QHash& devices, const QString &sessionId) +QMultiHash Database::devicesWithoutKey( + const QString& roomId, QMultiHash devices, + const QString& sessionId) { auto query = prepareQuery(QStringLiteral("SELECT userId, deviceId FROM sent_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId")); query.bindValue(":roomId", roomId); @@ -371,7 +375,8 @@ QHash Database::devicesWithoutKey(const QString& roomId, Q execute(query); commit(); while (query.next()) { - devices[query.value("userId").toString()].removeAll(query.value("deviceId").toString()); + devices.remove(query.value("userId").toString(), + query.value("deviceId").toString()); } return devices; } diff --git a/lib/database.h b/lib/database.h index 45348c8d..4091d61b 100644 --- a/lib/database.h +++ b/lib/database.h @@ -32,22 +32,40 @@ public: QByteArray accountPickle(); void setAccountPickle(const QByteArray &pickle); void clear(); - void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle, const QDateTime& timestamp); - UnorderedMap> loadOlmSessions(const PicklingMode& picklingMode); - UnorderedMap loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode); - void saveMegolmSession(const QString& roomId, const QString& sessionId, const QByteArray& pickle, const QString& senderId, const QString& olmSessionId); - void addGroupSessionIndexRecord(const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts); - std::pair groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index); + void saveOlmSession(const QString& senderKey, const QString& sessionId, + const QByteArray& pickle, const QDateTime& timestamp); + UnorderedMap> loadOlmSessions( + const PicklingMode& picklingMode); + UnorderedMap loadMegolmSessions( + const QString& roomId, const PicklingMode& picklingMode); + void saveMegolmSession(const QString& roomId, const QString& sessionId, + const QByteArray& pickle, const QString& senderId, + const QString& olmSessionId); + void addGroupSessionIndexRecord(const QString& roomId, + const QString& sessionId, uint32_t index, + const QString& eventId, qint64 ts); + std::pair groupSessionIndexRecord(const QString& roomId, + const QString& sessionId, + qint64 index); void clearRoomData(const QString& roomId); - 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); + 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 QOlmOutboundGroupSession& session); + void updateOlmSession(const QString& senderKey, const QString& sessionId, + const QByteArray& pickle); // Returns a map UserId -> [DeviceId] that have not received key yet - QHash devicesWithoutKey(const QString& roomId, QHash& devices, const QString &sessionId); + QMultiHash devicesWithoutKey(const QString& roomId, QMultiHash devices, + const QString& sessionId); // 'devices' contains tuples {userId, deviceId, curveKey} - void setDevicesReceivedKey(const QString& roomId, const QVector>& devices, const QString& sessionId, int index); + void setDevicesReceivedKey( + const QString& roomId, + const QVector>& devices, + const QString& sessionId, int index); private: void migrateTo1(); diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 76188d08..a2eff2c8 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -44,7 +44,7 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() return std::make_unique(olmOutboundGroupSession); } -QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mode) +QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mode) const { QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); QByteArray key = toKey(mode); @@ -79,7 +79,7 @@ QOlmExpected QOlmOutboundGroupSession::unpickle(con return std::make_unique(olmOutboundGroupSession); } -QOlmExpected QOlmOutboundGroupSession::encrypt(const QString &plaintext) +QOlmExpected QOlmOutboundGroupSession::encrypt(const QString &plaintext) const { QByteArray plaintextBuf = plaintext.toUtf8(); const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index c20613d3..9a82d22a 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -21,14 +21,14 @@ public: //! Throw OlmError on errors static QOlmOutboundGroupSessionPtr create(); //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. - QOlmExpected pickle(const PicklingMode &mode); + QOlmExpected pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. static QOlmExpected unpickle( const QByteArray& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. - QOlmExpected encrypt(const QString& plaintext); + QOlmExpected encrypt(const QString& plaintext) const; //! Get the current message index for this session. //! diff --git a/lib/room.cpp b/lib/room.cpp index 26fe80e3..07d03467 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -443,9 +443,11 @@ public: return q->getCurrentState()->rotationPeriodMsgs(); } void createMegolmSession() { - qCDebug(E2EE) << "Creating new outbound megolm session for room " << q->id(); + qCDebug(E2EE) << "Creating new outbound megolm session for room " + << q->objectName(); currentOutboundMegolmSession = QOlmOutboundGroupSession::create(); - connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); + connection->saveCurrentOutboundMegolmSession( + id, *currentOutboundMegolmSession); const auto sessionKey = currentOutboundMegolmSession->sessionKey(); if(!sessionKey) { @@ -477,90 +479,113 @@ public: return makeEvent(encrypted, connection->olmAccount()->identityKeys().curve25519); } - QHash getDevicesWithoutKey() const + QMultiHash getDevicesWithoutKey() const { - QHash devices; - for (const auto& user : q->users()) { - devices[user->id()] = q->connection()->devicesForUser(user->id()); + QMultiHash devices; + for (const auto& user : q->users()) + for (const auto& deviceId : connection->devicesForUser(user->id())) + devices.insert(user->id(), deviceId); + + return connection->database()->devicesWithoutKey( + id, devices, currentOutboundMegolmSession->sessionId()); + } + + bool createOlmSession(const QString& user, const QString& device, + const QJsonObject& oneTimeKeyObject) const + { + static QOlmUtility verifier; + qDebug(E2EE) << "Creating a new session for" << user << device; + if (oneTimeKeyObject.isEmpty()) { + qWarning(E2EE) << "No one time key for" << user << device; + return false; } - return q->connection()->database()->devicesWithoutKey(q->id(), devices, QString(currentOutboundMegolmSession->sessionId())); + const auto oneTimeKeyForId = *oneTimeKeyObject.constBegin(); + const auto signature = + oneTimeKeyForId["signatures"][user]["ed25519:"_ls % device] + .toString() + .toLatin1(); + auto signedObject = oneTimeKeyForId.toObject(); + signedObject.remove("unsigned"_ls); + signedObject.remove("signatures"_ls); + const auto signedData = + QJsonDocument(signedObject).toJson(QJsonDocument::Compact); + if (!verifier.ed25519Verify( + connection->edKeyForUserDevice(user, device).toLatin1(), + signedData, signature)) { + qWarning(E2EE) << "Failed to verify one-time-key signature for" + << user << device << ". Skipping this device."; + return false; + } + const auto recipientCurveKey = + connection->curveKeyForUserDevice(user, device); + connection->createOlmSession(recipientCurveKey, + oneTimeKeyForId["key"].toString()); + return true; } - void sendRoomKeyToDevices(const QByteArray& sessionId, const QByteArray& sessionKey, const QHash devices, int index) + void sendRoomKeyToDevices(const QByteArray& sessionId, + const QByteArray& sessionKey, + const QMultiHash& devices, + int index) { - qCDebug(E2EE) << "Sending room key to devices" << sessionId, sessionKey.toHex(); + qDebug(E2EE) << "Sending room key to devices:" << sessionId + << sessionKey.toHex(); QHash> hash; - for (const auto& user : devices.keys()) { - QHash u; - for(const auto &device : devices[user]) { - if (!connection->hasOlmSession(user, device)) { - u[device] = "signed_curve25519"_ls; - qCDebug(E2EE) << "Adding" << user << device << "to keys to claim"; - } + for (const auto& [userId, deviceId] : asKeyValueRange(devices)) + if (!connection->hasOlmSession(userId, deviceId)) { + hash[userId].insert(deviceId, "signed_curve25519"_ls); + qDebug(E2EE) + << "Adding" << userId << deviceId << "to keys to claim"; } - if (!u.isEmpty()) { - hash[user] = u; - } - } - if (hash.isEmpty()) { + + if (hash.isEmpty()) return; - } + auto job = connection->callApi(hash); - connect(job, &BaseJob::success, q, [job, this, sessionId, sessionKey, devices, index](){ + connect(job, &BaseJob::success, q, + [job, this, sessionId, sessionKey, devices, index] { Connection::UsersToDevicesToEvents usersToDevicesToEvents; const auto data = job->jsonData(); - for(const auto &user : devices.keys()) { - for(const auto &device : devices[user]) { - const auto recipientCurveKey = connection->curveKeyForUserDevice(user, device); - if (!connection->hasOlmSession(user, device)) { - qCDebug(E2EE) << "Creating a new session for" << user << device; - if(data["one_time_keys"][user][device].toObject().isEmpty()) { - qWarning() << "No one time key for" << user << device; - continue; - } - const auto keyId = data["one_time_keys"][user][device].toObject().keys()[0]; - const auto oneTimeKey = data["one_time_keys"][user][device][keyId]["key"].toString(); - const auto signature = data["one_time_keys"][user][device][keyId]["signatures"][user][QStringLiteral("ed25519:") + device].toString().toLatin1(); - auto signedData = data["one_time_keys"][user][device][keyId].toObject(); - signedData.remove("unsigned"); - signedData.remove("signatures"); - auto signatureMatch = QOlmUtility().ed25519Verify(connection->edKeyForUserDevice(user, device).toLatin1(), QJsonDocument(signedData).toJson(QJsonDocument::Compact), signature); - if (!signatureMatch) { - qCWarning(E2EE) << "Failed to verify one-time-key signature for" << user << device << ". Skipping this device."; - continue; - } else { - } - connection->createOlmSession(recipientCurveKey, oneTimeKey); - } - usersToDevicesToEvents[user][device] = payloadForUserDevice(user, device, sessionId, sessionKey); - } + for (const auto& [user, device] : asKeyValueRange(devices)) { + if (!connection->hasOlmSession(user, device) + && !createOlmSession( + user, device, + data["one_time_keys"][user][device].toObject())) + continue; + + usersToDevicesToEvents[user][device] = + payloadForUserDevice(user, device, sessionId, + sessionKey); } if (!usersToDevicesToEvents.empty()) { - connection->sendToDevices("m.room.encrypted", usersToDevicesToEvents); + connection->sendToDevices("m.room.encrypted"_ls, + usersToDevicesToEvents); QVector> receivedDevices; - for (const auto& user : devices.keys()) { - for (const auto& device : devices[user]) { - receivedDevices += {user, device, q->connection()->curveKeyForUserDevice(user, device) }; - } - } - connection->database()->setDevicesReceivedKey(q->id(), receivedDevices, sessionId, index); + receivedDevices.reserve(devices.size()); + for (const auto& [user, device] : asKeyValueRange(devices)) + receivedDevices.push_back( + { user, device, + connection->curveKeyForUserDevice(user, device) }); + + connection->database()->setDevicesReceivedKey(id, + receivedDevices, + sessionId, index); } }); } - void sendMegolmSession(const QHash& devices) { + void sendMegolmSession(const QMultiHash& devices) { // Save the session to this device const auto sessionId = currentOutboundMegolmSession->sessionId(); - const auto _sessionKey = currentOutboundMegolmSession->sessionKey(); - if(!_sessionKey) { + const auto sessionKey = currentOutboundMegolmSession->sessionKey(); + if(!sessionKey) { qCWarning(E2EE) << "Error loading session key"; return; } - const auto sessionKey = *_sessionKey; - const auto senderKey = q->connection()->olmAccount()->identityKeys().curve25519; // Send the session to other people - sendRoomKeyToDevices(sessionId, sessionKey, devices, currentOutboundMegolmSession->sessionMessageIndex()); + sendRoomKeyToDevices(sessionId, *sessionKey, devices, + currentOutboundMegolmSession->sessionMessageIndex()); } #endif // Quotient_E2EE_ENABLED @@ -592,7 +617,8 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) } }); d->groupSessions = connection->loadRoomMegolmSessions(this); - d->currentOutboundMegolmSession = connection->loadCurrentOutboundMegolmSession(this); + d->currentOutboundMegolmSession = + connection->loadCurrentOutboundMegolmSession(this->id()); if (d->shouldRotateMegolmSession()) { d->currentOutboundMegolmSession = nullptr; } @@ -2101,12 +2127,12 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) if (!hasValidMegolmSession() || shouldRotateMegolmSession()) { createMegolmSession(); } - const auto devicesWithoutKey = getDevicesWithoutKey(); - sendMegolmSession(devicesWithoutKey); + sendMegolmSession(getDevicesWithoutKey()); const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(pEvent->fullJson()).toJson()); currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); - connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); + connection->saveCurrentOutboundMegolmSession( + id, *currentOutboundMegolmSession); if(!encrypted) { qWarning(E2EE) << "Error encrypting message" << encrypted.error(); return {}; -- cgit v1.2.3 From 0f8335a32debc4c61d9fc9875c79c0ba6ba05357 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 27 May 2022 19:09:26 +0200 Subject: Move some Meg/Olm session logic from Room::Private to Connection::Private Functions (Room::Private::)createOlmSession, payloadForUserDevice and sendRoomKeyToDevices don't have a lot to do with the given Room object but deal with quite a few things stored in Connection. This commit moves them to Connection::Private, exposing sendSessionKeyToDevices (the new name for sendRoomKeyToDevices) in Connection so that Room could call it from Room::P::sendMegolmSession(). While moving these over, a few additional things were adjusted: - more functions marked as const - a few functions could be moved now from Connection to Connection::Private - false slots in Connection (such as picklingMode) are moved out of the slots block - keys.yml in Matrix CS API definitions has been adjusted to match the real structure of `/claim` response (see quotient-im/matrix-spec repo); csapi/keys.h has been regenerated accordingly. --- autotests/testolmaccount.cpp | 80 +++++++-------- lib/connection.cpp | 227 ++++++++++++++++++++++++++++++++++--------- lib/connection.h | 34 +++---- lib/csapi/keys.h | 4 +- lib/e2ee/qolmsession.cpp | 5 +- lib/e2ee/qolmsession.h | 2 +- lib/events/roomkeyevent.h | 4 +- lib/room.cpp | 113 +-------------------- 8 files changed, 249 insertions(+), 220 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index b509d12f..3fb8ac24 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -378,47 +378,47 @@ void TestOlmAccount::claimKeys() // Alice retrieves bob's keys & claims one signed one-time key. QHash deviceKeys; deviceKeys[bob->userId()] = QStringList(); - auto job = alice->callApi(deviceKeys); - connect(job, &BaseJob::result, this, [bob, alice, job, this] { - const auto& bobDevices = job->deviceKeys().value(bob->userId()); - QVERIFY(!bobDevices.empty()); - - // Retrieve the identity key for the current device. - const auto& bobEd25519 = - bobDevices.value(bob->deviceId()).keys["ed25519:" + bob->deviceId()]; - - const auto currentDevice = bobDevices[bob->deviceId()]; - - // Verify signature. - QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(), - bob->userId())); - - QHash> oneTimeKeys; - oneTimeKeys[bob->userId()] = QHash(); - oneTimeKeys[bob->userId()][bob->deviceId()] = SignedCurve25519Key; - - auto job = alice->callApi(oneTimeKeys); - connect(job, &BaseJob::result, this, [bob, bobEd25519, job] { - const auto userId = bob->userId(); - const auto deviceId = bob->deviceId(); - - // The device exists. - QCOMPARE(job->oneTimeKeys().size(), 1); - QCOMPARE(job->oneTimeKeys().value(userId).size(), 1); - - // The key is the one bob sent. - const auto& oneTimeKey = - job->oneTimeKeys().value(userId).value(deviceId); - QVERIFY(oneTimeKey.canConvert()); - - const auto varMap = oneTimeKey.toMap(); - QVERIFY(std::any_of(varMap.constKeyValueBegin(), - varMap.constKeyValueEnd(), [](const auto& kv) { - return kv.first.startsWith( - SignedCurve25519Key); - })); - }); + auto queryKeysJob = alice->callApi(deviceKeys); + QSignalSpy requestSpy2(queryKeysJob, &BaseJob::result); + QVERIFY(requestSpy2.wait(10000)); + + const auto& bobDevices = queryKeysJob->deviceKeys().value(bob->userId()); + QVERIFY(!bobDevices.empty()); + + const auto currentDevice = bobDevices[bob->deviceId()]; + + // Verify signature. + QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(), + bob->userId())); + // Retrieve the identity key for the current device. + const auto& bobEd25519 = + bobDevices.value(bob->deviceId()).keys["ed25519:" + bob->deviceId()]; + + QHash> oneTimeKeys; + oneTimeKeys[bob->userId()] = QHash(); + oneTimeKeys[bob->userId()][bob->deviceId()] = SignedCurve25519Key; + + auto claimKeysJob = alice->callApi(oneTimeKeys); + connect(claimKeysJob, &BaseJob::result, this, [bob, bobEd25519, claimKeysJob] { + const auto userId = bob->userId(); + const auto deviceId = bob->deviceId(); + + // The device exists. + QCOMPARE(claimKeysJob->oneTimeKeys().size(), 1); + QCOMPARE(claimKeysJob->oneTimeKeys().value(userId).size(), 1); + + // The key is the one bob sent. + const auto& oneTimeKeys = + claimKeysJob->oneTimeKeys().value(userId).value(deviceId); + for (auto it = oneTimeKeys.begin(); it != oneTimeKeys.end(); ++it) { + if (it.key().startsWith(SignedCurve25519Key) + && it.value().isObject()) + return; + } + QFAIL("The claimed one time key is not in /claim response"); }); + QSignalSpy completionSpy(claimKeysJob, &BaseJob::result); + QVERIFY(completionSpy.wait(10000)); } void TestOlmAccount::claimMultipleKeys() diff --git a/lib/connection.cpp b/lib/connection.cpp index 1193eb75..ab4a7dea 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -39,6 +39,7 @@ # include "e2ee/qolmaccount.h" # include "e2ee/qolminboundsession.h" # include "e2ee/qolmsession.h" +# include "e2ee/qolmutility.h" # include "e2ee/qolmutils.h" # if QT_VERSION_MAJOR >= 6 @@ -62,7 +63,6 @@ #include #include - using namespace Quotient; // This is very much Qt-specific; STL iterators don't have key() and value() @@ -210,11 +210,11 @@ public: #ifdef Quotient_E2EE_ENABLED void loadSessions() { - olmSessions = q->database()->loadOlmSessions(q->picklingMode()); + olmSessions = q->database()->loadOlmSessions(picklingMode); } - void saveSession(QOlmSession& session, const QString& senderKey) + void saveSession(const QOlmSession& session, const QString& senderKey) const { - if (auto pickleResult = session.pickle(q->picklingMode())) + if (auto pickleResult = session.pickle(picklingMode)) q->database()->saveOlmSession(senderKey, session.sessionId(), *pickleResult, QDateTime::currentDateTime()); @@ -364,9 +364,27 @@ public: #endif // Quotient_E2EE_ENABLED } #ifdef Quotient_E2EE_ENABLED + bool isKnownCurveKey(const QString& userId, const QString& curveKey) const; + void loadOutdatedUserDevices(); void saveDevicesList(); void loadDevicesList(); + + // This function assumes that an olm session with (user, device) exists + std::pair olmEncryptMessage( + const QString& userId, const QString& device, + const QByteArray& message) const; + bool createOlmSession(const QString& targetUserId, + const QString& targetDeviceId, + const QJsonObject& oneTimeKeyObject); + QString curveKeyForUserDevice(const QString& userId, + const QString& device) const; + QString edKeyForUserDevice(const QString& userId, + const QString& device) const; + std::unique_ptr makeEventForSessionKey( + const QString& roomId, const QString& targetUserId, + const QString& targetDeviceId, const QByteArray& sessionId, + const QByteArray& sessionKey) const; #endif }; @@ -935,20 +953,23 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { #ifdef Quotient_E2EE_ENABLED if (!toDeviceEvents.empty()) { - qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events"; + qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() + << "to-device events"; visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { - qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); + qCDebug(E2EE) << "Unsupported algorithm" << event.id() + << "for event" << event.algorithm(); return; } - if (q->isKnownCurveKey(event.senderId(), event.senderKey())) { + if (isKnownCurveKey(event.senderId(), event.senderKey())) { handleEncryptedToDeviceEvent(event); return; } trackedUsers += event.senderId(); outdatedUsers += event.senderId(); encryptionUpdateRequired = true; - pendingEncryptedEvents.push_back(std::make_unique(event.fullJson())); + pendingEncryptedEvents.push_back( + makeEvent(event.fullJson())); }); } #endif @@ -1316,9 +1337,8 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) return forgetJob; } -SendToDeviceJob* -Connection::sendToDevices(const QString& eventType, - const UsersToDevicesToEvents& eventsMap) +SendToDeviceJob* Connection::sendToDevices( + const QString& eventType, const UsersToDevicesToEvents& eventsMap) { QHash> json; json.reserve(int(eventsMap.size())); @@ -2063,7 +2083,7 @@ void Connection::Private::loadOutdatedUserDevices() saveDevicesList(); for(size_t i = 0; i < pendingEncryptedEvents.size();) { - if (q->isKnownCurveKey( + if (isKnownCurveKey( pendingEncryptedEvents[i]->fullJson()[SenderKeyL].toString(), pendingEncryptedEvents[i]->contentPart("sender_key"_ls))) { handleEncryptedToDeviceEvent(*pendingEncryptedEvents[i]); @@ -2193,13 +2213,13 @@ Database* Connection::database() const } UnorderedMap -Connection::loadRoomMegolmSessions(const Room* room) +Connection::loadRoomMegolmSessions(const Room* room) const { return database()->loadMegolmSessions(room->id(), picklingMode()); } void Connection::saveMegolmSession(const Room* room, - const QOlmInboundGroupSession& session) + const QOlmInboundGroupSession& session) const { database()->saveMegolmSession(room->id(), session.sessionId(), session.pickle(picklingMode()), @@ -2211,64 +2231,179 @@ QStringList Connection::devicesForUser(const QString& userId) const return d->deviceKeys[userId].keys(); } -QString Connection::curveKeyForUserDevice(const QString& userId, - const QString& device) const +QString Connection::Private::curveKeyForUserDevice(const QString& userId, + const QString& device) const { - return d->deviceKeys[userId][device].keys["curve25519:" % device]; + return deviceKeys[userId][device].keys["curve25519:" % device]; } -QString Connection::edKeyForUserDevice(const QString& userId, - const QString& device) const +QString Connection::Private::edKeyForUserDevice(const QString& userId, + const QString& device) const { - return d->deviceKeys[userId][device].keys["ed25519:" % device]; + return deviceKeys[userId][device].keys["ed25519:" % device]; } -bool Connection::isKnownCurveKey(const QString& userId, - const QString& curveKey) const +bool Connection::Private::isKnownCurveKey(const QString& userId, + const QString& curveKey) const { - auto query = database()->prepareQuery(QStringLiteral("SELECT * FROM tracked_devices WHERE matrixId=:matrixId AND curveKey=:curveKey")); + auto query = database->prepareQuery( + QStringLiteral("SELECT * FROM tracked_devices WHERE matrixId=:matrixId " + "AND curveKey=:curveKey")); query.bindValue(":matrixId", userId); query.bindValue(":curveKey", curveKey); - database()->execute(query); + database->execute(query); return query.next(); } -bool Connection::hasOlmSession(const QString& user, const QString& deviceId) const +bool Connection::hasOlmSession(const QString& user, + const QString& deviceId) const { - const auto& curveKey = curveKeyForUserDevice(user, deviceId); + const auto& curveKey = d->curveKeyForUserDevice(user, deviceId); return d->olmSessions.contains(curveKey) && !d->olmSessions[curveKey].empty(); } -std::pair Connection::olmEncryptMessage( - const QString& userId, const QString& device, const QByteArray& message) const +std::pair Connection::Private::olmEncryptMessage( + const QString& userId, const QString& device, + const QByteArray& message) const { const auto& curveKey = curveKeyForUserDevice(userId, device); - QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); - const auto result = d->olmSessions[curveKey][0]->encrypt(message); - if (const auto pickle = - d->olmSessions[curveKey][0]->pickle(picklingMode())) { - database()->updateOlmSession(curveKey, - d->olmSessions[curveKey][0]->sessionId(), - *pickle); + const auto& olmSession = olmSessions.at(curveKey).front(); + QOlmMessage::Type type = olmSession->encryptMessageType(); + const auto result = olmSession->encrypt(message); + if (const auto pickle = olmSession->pickle(picklingMode)) { + database->updateOlmSession(curveKey, olmSession->sessionId(), *pickle); } else { - qCWarning(E2EE) << "Failed to pickle olm session: " << pickle.error(); + qWarning(E2EE) << "Failed to pickle olm session: " << pickle.error(); } return { type, result.toCiphertext() }; } -void Connection::createOlmSession(const QString& theirIdentityKey, - const QString& theirOneTimeKey) const -{ - auto session = QOlmSession::createOutboundSession(olmAccount(), - theirIdentityKey, - theirOneTimeKey); +bool Connection::Private::createOlmSession(const QString& targetUserId, + const QString& targetDeviceId, + const QJsonObject& oneTimeKeyObject) +{ + static QOlmUtility verifier; + qDebug(E2EE) << "Creating a new session for" << targetUserId + << targetDeviceId; + if (oneTimeKeyObject.isEmpty()) { + qWarning(E2EE) << "No one time key for" << targetUserId + << targetDeviceId; + return false; + } + auto signedOneTimeKey = oneTimeKeyObject.constBegin()->toObject(); + // Verify contents of signedOneTimeKey - for that, drop `signatures` and + // `unsigned` and then verify the object against the respective signature + const auto signature = + signedOneTimeKey.take("signatures"_ls)[targetUserId]["ed25519:"_ls % targetDeviceId] + .toString() + .toLatin1(); + signedOneTimeKey.remove("unsigned"_ls); + if (!verifier.ed25519Verify( + edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), + QJsonDocument(signedOneTimeKey).toJson(QJsonDocument::Compact), + signature)) { + qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId + << targetDeviceId << ". Skipping this device."; + return false; + } + const auto recipientCurveKey = + curveKeyForUserDevice(targetUserId, targetDeviceId); + auto session = + QOlmSession::createOutboundSession(olmAccount.get(), recipientCurveKey, + signedOneTimeKey["key"].toString()); if (!session) { qCWarning(E2EE) << "Failed to create olm session for " - << theirIdentityKey << session.error(); + << recipientCurveKey << session.error(); + return false; + } + saveSession(**session, recipientCurveKey); + olmSessions[recipientCurveKey].push_back(std::move(*session)); + return true; +} + +std::unique_ptr Connection::Private::makeEventForSessionKey( + const QString& roomId, const QString& targetUserId, + const QString& targetDeviceId, const QByteArray& sessionId, + const QByteArray& sessionKey) const +{ + // Noisy but nice for debugging + // qDebug(E2EE) << "Creating the payload for" << data->userId() << device << + // sessionId << sessionKey.toHex(); + const auto event = makeEvent("m.megolm.v1.aes-sha2", roomId, + sessionId, sessionKey, + data->userId()); + auto payloadJson = event->fullJson(); + payloadJson.insert("recipient"_ls, targetUserId); + payloadJson.insert(SenderKeyL, data->userId()); + payloadJson.insert("recipient_keys"_ls, + QJsonObject { { Ed25519Key, + edKeyForUserDevice(targetUserId, + targetDeviceId) } }); + payloadJson.insert("keys"_ls, + QJsonObject { + { Ed25519Key, + QString(olmAccount->identityKeys().ed25519) } }); + payloadJson.insert("sender_device"_ls, data->deviceId()); + + const auto [type, cipherText] = olmEncryptMessage( + targetUserId, targetDeviceId, + QJsonDocument(payloadJson).toJson(QJsonDocument::Compact)); + QJsonObject encrypted { + { curveKeyForUserDevice(targetUserId, targetDeviceId), + QJsonObject { { "type"_ls, type }, + { "body"_ls, QString(cipherText) } } } + }; + + return makeEvent(encrypted, + olmAccount->identityKeys().curve25519); +} + +void Connection::sendSessionKeyToDevices( + const QString& roomId, const QByteArray& sessionId, + const QByteArray& sessionKey, const QMultiHash& devices, + int index) +{ + qDebug(E2EE) << "Sending room key to devices:" << sessionId + << sessionKey.toHex(); + QHash> hash; + for (const auto& [userId, deviceId] : asKeyValueRange(devices)) + if (!hasOlmSession(userId, deviceId)) { + hash[userId].insert(deviceId, "signed_curve25519"_ls); + qDebug(E2EE) << "Adding" << userId << deviceId + << "to keys to claim"; + } + + if (hash.isEmpty()) return; - } - d->saveSession(**session, theirIdentityKey); - d->olmSessions[theirIdentityKey].push_back(std::move(*session)); + + auto job = callApi(hash); + connect(job, &BaseJob::success, this, [job, this, roomId, sessionId, sessionKey, devices, index] { + UsersToDevicesToEvents usersToDevicesToEvents; + const auto oneTimeKeys = job->oneTimeKeys(); + for (const auto& [targetUserId, targetDeviceId] : + asKeyValueRange(devices)) { + if (!hasOlmSession(targetUserId, targetDeviceId) + && !d->createOlmSession( + targetUserId, targetDeviceId, + oneTimeKeys[targetUserId][targetDeviceId])) + continue; + + usersToDevicesToEvents[targetUserId][targetDeviceId] = + d->makeEventForSessionKey(roomId, targetUserId, targetDeviceId, + sessionId, sessionKey); + } + if (!usersToDevicesToEvents.empty()) { + sendToDevices(EncryptedEvent::TypeId, usersToDevicesToEvents); + QVector> receivedDevices; + receivedDevices.reserve(devices.size()); + for (const auto& [user, device] : asKeyValueRange(devices)) + receivedDevices.push_back( + { user, device, d->curveKeyForUserDevice(user, device) }); + + database()->setDevicesReceivedKey(roomId, receivedDevices, + sessionId, index); + } + }); } QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession( diff --git a/lib/connection.h b/lib/connection.h index a2824744..5b806350 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -319,22 +319,26 @@ public: #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; Database* database() const; + PicklingMode picklingMode() const; UnorderedMap loadRoomMegolmSessions( - const Room* room); + const Room* room) const; void saveMegolmSession(const Room* room, - const QOlmInboundGroupSession& session); + 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; - - //This assumes that an olm session with (user, device) exists - std::pair olmEncryptMessage( - const QString& userId, const QString& device, - const QByteArray& message) const; - void createOlmSession(const QString& theirIdentityKey, - const QString& theirOneTimeKey) const; + void saveCurrentOutboundMegolmSession( + const QString& roomId, const QOlmOutboundGroupSession& session) const; + + void sendSessionKeyToDevices(const QString& roomId, + const QByteArray& sessionId, + const QByteArray& sessionKey, + const QMultiHash& devices, + int index); + + QJsonObject decryptNotification(const QJsonObject ¬ification); + QStringList devicesForUser(const QString& userId) const; #endif // Quotient_E2EE_ENABLED Q_INVOKABLE Quotient::SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; @@ -695,16 +699,8 @@ public Q_SLOTS: #ifdef Quotient_E2EE_ENABLED void encryptionUpdate(Room *room); - PicklingMode picklingMode() const; - QJsonObject decryptNotification(const QJsonObject ¬ification); - - QStringList devicesForUser(const QString& userId) const; - QString curveKeyForUserDevice(const QString& userId, - const QString& device) const; - QString edKeyForUserDevice(const QString& userId, - const QString& device) const; - bool isKnownCurveKey(const QString& userId, const QString& curveKey) const; #endif + Q_SIGNALS: /// \brief Initial server resolution has failed /// diff --git a/lib/csapi/keys.h b/lib/csapi/keys.h index ce1ca9ed..bcf1ad41 100644 --- a/lib/csapi/keys.h +++ b/lib/csapi/keys.h @@ -207,9 +207,9 @@ public: /// /// See the [key algorithms](/client-server-api/#key-algorithms) section for /// information on the Key Object format. - QHash> oneTimeKeys() const + QHash> oneTimeKeys() const { - return loadFromJson>>( + return loadFromJson>>( "one_time_keys"_ls); } }; diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 2b149aac..2a98d5d8 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -96,12 +96,13 @@ QOlmExpected QOlmSession::createOutboundSession( return std::make_unique(olmOutboundSession); } -QOlmExpected QOlmSession::pickle(const PicklingMode &mode) +QOlmExpected QOlmSession::pickle(const PicklingMode &mode) const { QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); QByteArray key = toKey(mode); const auto error = olm_pickle_session(m_session, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()); + pickledBuf.data(), + pickledBuf.length()); if (error == olm_error()) { return lastError(m_session); diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index faae16ef..021092c7 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -31,7 +31,7 @@ public: const QString& theirOneTimeKey); //! Serialises an `QOlmSession` to encrypted Base64. - QOlmExpected pickle(const PicklingMode &mode); + QOlmExpected pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. static QOlmExpected unpickle( diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 2bda3086..3093db41 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -12,7 +12,9 @@ public: DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent) explicit RoomKeyEvent(const QJsonObject& obj); - explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, const QString &sessionId, const QString& sessionKey, const QString& senderId); + explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, + const QString& sessionId, const QString& sessionKey, + const QString& senderId); QString algorithm() const { return contentPart("algorithm"_ls); } QString roomId() const { return contentPart(RoomIdKeyL); } diff --git a/lib/room.cpp b/lib/room.cpp index 07d03467..6ec06aa8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -457,28 +457,6 @@ public: addInboundGroupSession(currentOutboundMegolmSession->sessionId(), *sessionKey, q->localUser()->id(), "SELF"_ls); } - std::unique_ptr payloadForUserDevice(QString user, const QString& device, const QByteArray& sessionId, const QByteArray& sessionKey) - { - // Noisy but nice for debugging - //qCDebug(E2EE) << "Creating the payload for" << user->id() << device << sessionId << sessionKey.toHex(); - const auto event = makeEvent("m.megolm.v1.aes-sha2", q->id(), sessionId, sessionKey, q->localUser()->id()); - QJsonObject payloadJson = event->fullJson(); - payloadJson["recipient"] = user; - payloadJson["sender"] = connection->user()->id(); - QJsonObject recipientObject; - recipientObject["ed25519"] = connection->edKeyForUserDevice(user, device); - payloadJson["recipient_keys"] = recipientObject; - QJsonObject senderObject; - senderObject["ed25519"] = QString(connection->olmAccount()->identityKeys().ed25519); - payloadJson["keys"] = senderObject; - payloadJson["sender_device"] = connection->deviceId(); - auto cipherText = connection->olmEncryptMessage(user, device, QJsonDocument(payloadJson).toJson(QJsonDocument::Compact)); - QJsonObject encrypted; - encrypted[connection->curveKeyForUserDevice(user, device)] = QJsonObject{{"type", cipherText.first}, {"body", QString(cipherText.second)}}; - - return makeEvent(encrypted, connection->olmAccount()->identityKeys().curve25519); - } - QMultiHash getDevicesWithoutKey() const { QMultiHash devices; @@ -490,91 +468,7 @@ public: id, devices, currentOutboundMegolmSession->sessionId()); } - bool createOlmSession(const QString& user, const QString& device, - const QJsonObject& oneTimeKeyObject) const - { - static QOlmUtility verifier; - qDebug(E2EE) << "Creating a new session for" << user << device; - if (oneTimeKeyObject.isEmpty()) { - qWarning(E2EE) << "No one time key for" << user << device; - return false; - } - const auto oneTimeKeyForId = *oneTimeKeyObject.constBegin(); - const auto signature = - oneTimeKeyForId["signatures"][user]["ed25519:"_ls % device] - .toString() - .toLatin1(); - auto signedObject = oneTimeKeyForId.toObject(); - signedObject.remove("unsigned"_ls); - signedObject.remove("signatures"_ls); - const auto signedData = - QJsonDocument(signedObject).toJson(QJsonDocument::Compact); - if (!verifier.ed25519Verify( - connection->edKeyForUserDevice(user, device).toLatin1(), - signedData, signature)) { - qWarning(E2EE) << "Failed to verify one-time-key signature for" - << user << device << ". Skipping this device."; - return false; - } - const auto recipientCurveKey = - connection->curveKeyForUserDevice(user, device); - connection->createOlmSession(recipientCurveKey, - oneTimeKeyForId["key"].toString()); - return true; - } - - void sendRoomKeyToDevices(const QByteArray& sessionId, - const QByteArray& sessionKey, - const QMultiHash& devices, - int index) - { - qDebug(E2EE) << "Sending room key to devices:" << sessionId - << sessionKey.toHex(); - QHash> hash; - for (const auto& [userId, deviceId] : asKeyValueRange(devices)) - if (!connection->hasOlmSession(userId, deviceId)) { - hash[userId].insert(deviceId, "signed_curve25519"_ls); - qDebug(E2EE) - << "Adding" << userId << deviceId << "to keys to claim"; - } - - if (hash.isEmpty()) - return; - - auto job = connection->callApi(hash); - connect(job, &BaseJob::success, q, - [job, this, sessionId, sessionKey, devices, index] { - Connection::UsersToDevicesToEvents usersToDevicesToEvents; - const auto data = job->jsonData(); - for (const auto& [user, device] : asKeyValueRange(devices)) { - if (!connection->hasOlmSession(user, device) - && !createOlmSession( - user, device, - data["one_time_keys"][user][device].toObject())) - continue; - - usersToDevicesToEvents[user][device] = - payloadForUserDevice(user, device, sessionId, - sessionKey); - } - if (!usersToDevicesToEvents.empty()) { - connection->sendToDevices("m.room.encrypted"_ls, - usersToDevicesToEvents); - QVector> receivedDevices; - receivedDevices.reserve(devices.size()); - for (const auto& [user, device] : asKeyValueRange(devices)) - receivedDevices.push_back( - { user, device, - connection->curveKeyForUserDevice(user, device) }); - - connection->database()->setDevicesReceivedKey(id, - receivedDevices, - sessionId, index); - } - }); - } - - void sendMegolmSession(const QMultiHash& devices) { + void sendMegolmSession(const QMultiHash& devices) const { // Save the session to this device const auto sessionId = currentOutboundMegolmSession->sessionId(); const auto sessionKey = currentOutboundMegolmSession->sessionKey(); @@ -584,8 +478,9 @@ public: } // Send the session to other people - sendRoomKeyToDevices(sessionId, *sessionKey, devices, - currentOutboundMegolmSession->sessionMessageIndex()); + connection->sendSessionKeyToDevices( + id, sessionId, *sessionKey, devices, + currentOutboundMegolmSession->sessionMessageIndex()); } #endif // Quotient_E2EE_ENABLED -- cgit v1.2.3 From 21ae4eca4c06e500ec04a52ad42772bf8e8e9b6f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 31 May 2022 18:58:27 +0200 Subject: Tweak QOlmAccount and data structures around This is mainly to plug the definition of a string-to-variant map for one-time keys (see https://spec.matrix.org/v1.2/client-server-api/#key-algorithms) into the CS API generated code (see the "shortcut OneTimeKeys" commit for gtad.yaml); but along with it came considerable streamlining of code in qolmaccount.cpp. Using std::variant to store that map also warranted converters.h to gain support for that type (even wider than toJson() that is already in dev - a non-trivial merge from dev is in order). --- autotests/testolmaccount.cpp | 18 +++----- lib/converters.h | 20 ++++++++ lib/e2ee/e2ee.h | 10 ++-- lib/e2ee/qolmaccount.cpp | 108 ++++++++++++++++--------------------------- lib/e2ee/qolmaccount.h | 9 ++-- 5 files changed, 73 insertions(+), 92 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index e31ff6d3..c85718dd 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -198,7 +198,7 @@ void TestOlmAccount::uploadIdentityKey() QVERIFY(idKeys.curve25519.size() > 10); - OneTimeKeys unused; + UnsignedOneTimeKeys unused; auto request = olmAccount->createUploadKeyRequest(unused); connect(request, &BaseJob::result, this, [request, conn] { QCOMPARE(request->oneTimeKeyCounts().size(), 0); @@ -221,7 +221,7 @@ void TestOlmAccount::uploadOneTimeKeys() auto oneTimeKeys = olmAccount->oneTimeKeys(); - QHash oneTimeKeysHash; + OneTimeKeys oneTimeKeysHash; const auto curve = oneTimeKeys.curve25519(); for (const auto &[keyId, key] : asKeyValueRange(curve)) { oneTimeKeysHash["curve25519:"+keyId] = key; @@ -247,12 +247,10 @@ void TestOlmAccount::uploadSignedOneTimeKeys() QCOMPARE(nKeys, 5); auto oneTimeKeys = olmAccount->oneTimeKeys(); - QHash oneTimeKeysHash; + OneTimeKeys oneTimeKeysHash; const auto signedKey = olmAccount->signOneTimeKeys(oneTimeKeys); for (const auto &[keyId, key] : asKeyValueRange(signedKey)) { - QVariant var; - var.setValue(key); - oneTimeKeysHash[keyId] = var; + oneTimeKeysHash[keyId] = key; } auto request = new UploadKeysJob(none, oneTimeKeysHash); connect(request, &BaseJob::result, this, [request, nKeys, conn] { @@ -410,11 +408,9 @@ void TestOlmAccount::claimKeys() // The key is the one bob sent. const auto& oneTimeKey = job->oneTimeKeys().value(userId).value(deviceId); - QVERIFY(oneTimeKey.canConvert()); - - const auto varMap = oneTimeKey.toMap(); - QVERIFY(std::any_of(varMap.constKeyValueBegin(), - varMap.constKeyValueEnd(), [](const auto& kv) { + QVERIFY(std::any_of(oneTimeKey.constKeyValueBegin(), + oneTimeKey.constKeyValueEnd(), + [](const auto& kv) { return kv.first.startsWith( SignedCurve25519Key); })); diff --git a/lib/converters.h b/lib/converters.h index 5e3becb8..64a5cfb6 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -224,6 +224,26 @@ struct QUOTIENT_API JsonConverter { static QVariant load(const QJsonValue& jv); }; +template +inline QJsonValue toJson(const std::variant& v) +{ + // std::visit requires all overloads to return the same type - and + // QJsonValue is a perfect candidate for that same type (assuming that + // variants never occur on the top level in Matrix API) + return std::visit( + [](const auto& value) { return QJsonValue { toJson(value) }; }, v); +} + +template +struct QUOTIENT_API JsonConverter> { + static std::variant load(const QJsonValue& jv) + { + if (jv.isString()) + return fromJson(jv); + return fromJson(jv); + } +}; + template struct JsonConverter> { static QJsonValue dump(const Omittable& from) diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 8e433d60..f97eb27a 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -70,15 +70,12 @@ struct IdentityKeys }; //! Struct representing the one-time keys. -struct QUOTIENT_API OneTimeKeys +struct QUOTIENT_API UnsignedOneTimeKeys { QHash> keys; //! Get the HashMap containing the curve25519 one-time keys. - QHash curve25519() const; - - //! Get a reference to the hashmap corresponding to given key type. -// std::optional> get(QString keyType) const; + QHash curve25519() const { return keys[Curve25519Key]; } }; //! Struct representing the signed one-time keys. @@ -93,7 +90,6 @@ public: QHash> signatures; }; - template <> struct JsonObjectConverter { static void fillFrom(const QJsonObject& jo, SignedOneTimeKey& result) @@ -109,6 +105,8 @@ struct JsonObjectConverter { } }; +using OneTimeKeys = QHash>; + template class asKeyValueRange { diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 72dddafb..4cfc6151 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -17,19 +17,6 @@ using namespace Quotient; -QHash OneTimeKeys::curve25519() const -{ - return keys[Curve25519Key]; -} - -//std::optional> OneTimeKeys::get(QString keyType) const -//{ -// if (!keys.contains(keyType)) { -// return std::nullopt; -// } -// return keys[keyType]; -//} - // Convert olm error to enum QOlmError lastError(OlmAccount *account) { return fromString(olm_account_last_error(account)); @@ -122,20 +109,15 @@ QByteArray QOlmAccount::sign(const QJsonObject &message) const QByteArray QOlmAccount::signIdentityKeys() const { const auto keys = identityKeys(); - QJsonObject body - { - {"algorithms", QJsonArray{"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}}, - {"user_id", m_userId}, - {"device_id", m_deviceId}, - {"keys", - QJsonObject{ - {QStringLiteral("curve25519:") + m_deviceId, QString::fromUtf8(keys.curve25519)}, - {QStringLiteral("ed25519:") + m_deviceId, QString::fromUtf8(keys.ed25519)} - } - } - }; - return sign(QJsonDocument(body).toJson(QJsonDocument::Compact)); - + return sign(QJsonObject { + { "algorithms", QJsonArray { "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" } }, + { "user_id", m_userId }, + { "device_id", m_deviceId }, + { "keys", QJsonObject { { QStringLiteral("curve25519:") + m_deviceId, + QString::fromUtf8(keys.curve25519) }, + { QStringLiteral("ed25519:") + m_deviceId, + QString::fromUtf8(keys.ed25519) } } } }); } size_t QOlmAccount::maxNumberOfOneTimeKeys() const @@ -145,9 +127,13 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) { - const size_t randomLength = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); + const size_t randomLength = + olm_account_generate_one_time_keys_random_length(m_account, + numberOfKeys); QByteArray randomBuffer = getRandom(randomLength); - const auto error = olm_account_generate_one_time_keys(m_account, numberOfKeys, randomBuffer.data(), randomLength); + const auto error = + olm_account_generate_one_time_keys(m_account, numberOfKeys, + randomBuffer.data(), randomLength); if (error == olm_error()) { throw lastError(m_account); @@ -156,7 +142,7 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) return error; } -OneTimeKeys QOlmAccount::oneTimeKeys() const +UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const { const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0'); @@ -166,34 +152,25 @@ OneTimeKeys QOlmAccount::oneTimeKeys() const throw lastError(m_account); } const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); - OneTimeKeys oneTimeKeys; + UnsignedOneTimeKeys oneTimeKeys; fromJson(json, oneTimeKeys.keys); return oneTimeKeys; } -QHash QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const +OneTimeKeys QOlmAccount::signOneTimeKeys(const UnsignedOneTimeKeys &keys) const { - QHash signedOneTimeKeys; - for (const auto &keyid : keys.curve25519().keys()) { - const auto oneTimeKey = keys.curve25519()[keyid]; - QByteArray sign = signOneTimeKey(oneTimeKey); - signedOneTimeKeys["signed_curve25519:" + keyid] = signedOneTimeKey(oneTimeKey.toUtf8(), sign); - } + OneTimeKeys signedOneTimeKeys; + const auto& curveKeys = keys.curve25519(); + for (const auto& [keyId, key] : asKeyValueRange(curveKeys)) + signedOneTimeKeys["signed_curve25519:" % keyId] = + signedOneTimeKey(key.toUtf8(), sign(QJsonObject{{"key", key}})); return signedOneTimeKeys; } -SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray &key, const QString &signature) const +SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray& key, + const QString& signature) const { - SignedOneTimeKey sign{}; - sign.key = key; - sign.signatures = {{m_userId, {{"ed25519:" + m_deviceId, signature}}}}; - return sign; -} - -QByteArray QOlmAccount::signOneTimeKey(const QString &key) const -{ - QJsonDocument j(QJsonObject{{"key", key}}); - return sign(j.toJson(QJsonDocument::Compact)); + return { key, { { m_userId, { { "ed25519:" + m_deviceId, signature } } } } }; } std::optional QOlmAccount::removeOneTimeKeys( @@ -227,39 +204,32 @@ DeviceKeys QOlmAccount::deviceKeys() const return deviceKeys; } -UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKeys) +UploadKeysJob* QOlmAccount::createUploadKeyRequest( + const UnsignedOneTimeKeys& oneTimeKeys) const { - auto keys = deviceKeys(); - - if (oneTimeKeys.curve25519().isEmpty()) { - return new UploadKeysJob(keys); - } - - // Sign & append the one time keys. - auto temp = signOneTimeKeys(oneTimeKeys); - QHash oneTimeKeysSigned; - for (const auto &[keyId, key] : asKeyValueRange(temp)) { - oneTimeKeysSigned[keyId] = QVariant::fromValue(toJson(key)); - } - - return new UploadKeysJob(keys, oneTimeKeysSigned); + return new UploadKeysJob(deviceKeys(), signOneTimeKeys(oneTimeKeys)); } -QOlmExpected QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) +QOlmExpected QOlmAccount::createInboundSession( + const QOlmMessage& preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSession(this, preKeyMessage); } -QOlmExpected QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) +QOlmExpected QOlmAccount::createInboundSessionFrom( + const QByteArray& theirIdentityKey, const QOlmMessage& preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); - return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); + return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, + preKeyMessage); } -QOlmExpected QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) +QOlmExpected QOlmAccount::createOutboundSession( + const QByteArray& theirIdentityKey, const QByteArray& theirOneTimeKey) { - return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); + return QOlmSession::createOutboundSession(this, theirIdentityKey, + theirOneTimeKey); } void QOlmAccount::markKeysAsPublished() diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index ee2aa69d..23fe58dd 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -59,17 +59,14 @@ public: size_t generateOneTimeKeys(size_t numberOfKeys); //! Gets the OlmAccount's one time keys formatted as JSON. - OneTimeKeys oneTimeKeys() const; + UnsignedOneTimeKeys oneTimeKeys() const; //! Sign all one time keys. - QHash signOneTimeKeys(const OneTimeKeys &keys) const; - - //! Sign one time key. - QByteArray signOneTimeKey(const QString &key) const; + OneTimeKeys signOneTimeKeys(const UnsignedOneTimeKeys &keys) const; SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; - UploadKeysJob *createUploadKeyRequest(const OneTimeKeys &oneTimeKeys); + UploadKeysJob* createUploadKeyRequest(const UnsignedOneTimeKeys& oneTimeKeys) const; DeviceKeys deviceKeys() const; -- cgit v1.2.3 From 2ecc08357fab7d22947b9cb5251d2f29be2ec55b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 4 Jun 2022 22:35:06 +0200 Subject: Address Sonar warnings --- lib/e2ee/qolmaccount.cpp | 4 ++-- lib/room.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 4cfc6151..241ae750 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -160,8 +160,8 @@ UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const OneTimeKeys QOlmAccount::signOneTimeKeys(const UnsignedOneTimeKeys &keys) const { OneTimeKeys signedOneTimeKeys; - const auto& curveKeys = keys.curve25519(); - for (const auto& [keyId, key] : asKeyValueRange(curveKeys)) + for (const auto& curveKeys = keys.curve25519(); + const auto& [keyId, key] : asKeyValueRange(curveKeys)) signedOneTimeKeys["signed_curve25519:" % keyId] = signedOneTimeKey(key.toUtf8(), sign(QJsonObject{{"key", key}})); return signedOneTimeKeys; diff --git a/lib/room.cpp b/lib/room.cpp index 9e2a5053..284d19df 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1531,7 +1531,7 @@ QStringList Room::safeMemberNames() const { QStringList res; res.reserve(d->membersMap.size()); - for (auto u: std::as_const(d->membersMap)) + for (const auto* u: std::as_const(d->membersMap)) res.append(safeMemberName(u->id())); return res; @@ -1541,7 +1541,7 @@ QStringList Room::htmlSafeMemberNames() const { QStringList res; res.reserve(d->membersMap.size()); - for (auto u: std::as_const(d->membersMap)) + for (const auto* u: std::as_const(d->membersMap)) res.append(htmlSafeMemberName(u->id())); return res; @@ -3378,7 +3378,7 @@ QString Room::Private::calculateDisplayname() const shortlist = buildShortlist(membersLeft); QStringList names; - for (auto u : shortlist) { + for (const auto* u : shortlist) { if (u == nullptr || isLocalUser(u)) break; // Only disambiguate if the room is not empty -- cgit v1.2.3 From 5a63f8a18645d612decdcc853335df0682c41d03 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 18 Jun 2022 21:29:27 +0200 Subject: Drop make_array(); use std::to_array() where needed make_array() has been introduced to cover for shortcomings on macOS and Windows. These shortcomings are no more there, so we can just use the standardrlibrary. --- lib/e2ee/e2ee.h | 5 +++-- lib/events/encryptionevent.cpp | 4 +--- lib/jobs/basejob.cpp | 20 ++++++++++---------- lib/quotient_common.h | 27 +++++++-------------------- 4 files changed, 21 insertions(+), 35 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index f97eb27a..1efd0f16 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -37,8 +37,9 @@ constexpr auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls; inline bool isSupportedAlgorithm(const QString& algorithm) { - static constexpr auto SupportedAlgorithms = - make_array(OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey); + static constexpr std::array SupportedAlgorithms { + OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey + }; return std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), algorithm) != SupportedAlgorithms.cend(); diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 6e994cd4..eb15f38e 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -9,9 +9,7 @@ #include namespace Quotient { -static const std::array encryptionStrings = { - { MegolmV1AesSha2AlgoKey } -}; +static constexpr std::array encryptionStrings { MegolmV1AesSha2AlgoKey }; template <> struct JsonConverter { diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index fe70911e..da645a2d 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -138,9 +138,8 @@ public: QTimer timer; QTimer retryTimer; - static constexpr std::array errorStrategy { - { { 90s, 5s }, { 90s, 10s }, { 120s, 30s } } - }; + static constexpr auto errorStrategy = std::to_array( + { { 90s, 5s }, { 90s, 10s }, { 120s, 30s } }); int maxRetries = int(errorStrategy.size()); int retriesTaken = 0; @@ -152,10 +151,8 @@ public: [[nodiscard]] QString dumpRequest() const { - // FIXME: use std::array {} when Apple stdlib gets deduction guides for it - static const auto verbs = - make_array(QStringLiteral("GET"), QStringLiteral("PUT"), - QStringLiteral("POST"), QStringLiteral("DELETE")); + static const std::array verbs { "GET"_ls, "PUT"_ls, "POST"_ls, + "DELETE"_ls }; const auto verbWord = verbs.at(size_t(verb)); return verbWord % ' ' % (reply ? reply->url().toString(QUrl::RemoveQuery) @@ -748,11 +745,14 @@ QString BaseJob::statusCaption() const } } -int BaseJob::error() const { return d->status.code; } +int BaseJob::error() const { + return d->status.code; } -QString BaseJob::errorString() const { return d->status.message; } +QString BaseJob::errorString() const { + return d->status.message; } -QUrl BaseJob::errorUrl() const { return d->errorUrl; } +QUrl BaseJob::errorUrl() const { + return d->errorUrl; } void BaseJob::setStatus(Status s) { diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 8bcd5ca6..136e9f79 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -41,7 +41,6 @@ Q_ENUM_NS_IMPL(Enum) \ Q_FLAG_NS(Flags) -// Apple Clang hasn't caught up with explicit(bool) yet #if __cpp_conditional_explicit >= 201806L #define QUO_IMPLICIT explicit(false) #else @@ -54,19 +53,6 @@ namespace Quotient { Q_NAMESPACE_EXPORT(QUOTIENT_API) -// std::array {} needs explicit template parameters on macOS because -// Apple stdlib doesn't have deduction guides for std::array. C++20 has -// to_array() but that can't be borrowed, this time because of MSVC: -// https://developercommunity.visualstudio.com/t/vc-ice-p1-initc-line-3652-from-stdto-array/1464038 -// Therefore a simpler (but also slightly more wobbly - it resolves the element -// type using std::common_type<>) make_array facility is implemented here. -template -constexpr auto make_array(Ts&&... items) -{ - return std::array, sizeof...(items)>( - { std::forward(items)... }); -} - // TODO: code like this should be generated from the CS API definition //! \brief Membership states @@ -87,9 +73,10 @@ enum class Membership : unsigned int { }; QUO_DECLARE_FLAGS_NS(MembershipMask, Membership) -constexpr auto MembershipStrings = make_array( - // The order MUST be the same as the order in the original enum - "join", "leave", "invite", "knock", "ban"); +constexpr std::array MembershipStrings { + // The order MUST be the same as the order in the Membership enum + "join", "leave", "invite", "knock", "ban" +}; //! \brief Local user join-state names //! @@ -105,10 +92,10 @@ enum class JoinState : std::underlying_type_t { }; QUO_DECLARE_FLAGS_NS(JoinStates, JoinState) -[[maybe_unused]] constexpr auto JoinStateStrings = make_array( +[[maybe_unused]] constexpr std::array JoinStateStrings { MembershipStrings[0], MembershipStrings[1], MembershipStrings[2], MembershipStrings[3] /* same as MembershipStrings, sans "ban" */ -); +}; //! \brief Network job running policy flags //! @@ -135,7 +122,7 @@ enum RoomType { }; Q_ENUM_NS(RoomType) -[[maybe_unused]] constexpr auto RoomTypeStrings = make_array("m.space"); +[[maybe_unused]] constexpr std::array RoomTypeStrings { "m.space" }; } // namespace Quotient Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask) -- cgit v1.2.3 From 7ef84728ab3744192583eb587a4585c576f5a176 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 18 Jun 2022 21:39:42 +0200 Subject: Move C++-only macros to util.h This pertains to QUO_IMPLICIT and DECL_DEPRECATED_ENUMERATOR - both can be used with no connection to Qt meta-type system (which is what quotient_common.h is for). --- lib/e2ee/e2ee.h | 3 +-- lib/events/encryptionevent.cpp | 2 -- lib/events/encryptionevent.h | 1 - lib/quotient_common.h | 9 --------- lib/util.h | 9 +++++++++ 5 files changed, 10 insertions(+), 14 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 1efd0f16..9501b263 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -8,7 +8,6 @@ #include "converters.h" #include "expected.h" #include "qolmerrors.h" -#include "quotient_common.h" #include #include @@ -71,7 +70,7 @@ struct IdentityKeys }; //! Struct representing the one-time keys. -struct QUOTIENT_API UnsignedOneTimeKeys +struct UnsignedOneTimeKeys { QHash> keys; diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index eb15f38e..1654d6f3 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -6,8 +6,6 @@ #include "e2ee/e2ee.h" -#include - namespace Quotient { static constexpr std::array encryptionStrings { MegolmV1AesSha2AlgoKey }; diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 5b5420ec..c73e5598 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -5,7 +5,6 @@ #pragma once #include "stateevent.h" -#include "quotient_common.h" namespace Quotient { class QUOTIENT_API EncryptionEventContent { diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 136e9f79..e087e7d3 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -41,15 +41,6 @@ Q_ENUM_NS_IMPL(Enum) \ Q_FLAG_NS(Flags) -#if __cpp_conditional_explicit >= 201806L -#define QUO_IMPLICIT explicit(false) -#else -#define QUO_IMPLICIT -#endif - -#define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \ - Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended - namespace Quotient { Q_NAMESPACE_EXPORT(QUOTIENT_API) diff --git a/lib/util.h b/lib/util.h index 5dd69d74..d1623881 100644 --- a/lib/util.h +++ b/lib/util.h @@ -37,6 +37,15 @@ static_assert(false, "Use Q_DISABLE_MOVE instead; Quotient enables it across all QT_WARNING_POP #endif +#if __cpp_conditional_explicit >= 201806L +#define QUO_IMPLICIT explicit(false) +#else +#define QUO_IMPLICIT +#endif + +#define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \ + Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended + /// \brief Copy an object with slicing /// /// Unintended slicing is bad, which why there's a C++ Core Guideline that -- cgit v1.2.3 From 7c1125cdd146227320aa7eb082225c4051ea0563 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 19 Jun 2022 20:55:00 +0200 Subject: Add a missing #include --- lib/e2ee/e2ee.h | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 9501b263..234d4bcb 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -11,6 +11,7 @@ #include #include +#include namespace Quotient { -- cgit v1.2.3 From 5de8d0a4bc9744327703c1613fc5ac3f232f44a8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Jun 2022 21:36:43 +0200 Subject: Fix signature verification toJson(SignedOneTimeKey) incorrectly generated a "signatures" key mapped to an empty object when no signatures were in the C++ value. Also: fallback keys have an additional flag that also has to be taken into account when verifying signatures. --- lib/e2ee/e2ee.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 234d4bcb..aba795a4 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -89,6 +89,8 @@ public: //! Required. Signatures of the key object. //! The signature is calculated using the process described at Signing JSON. QHash> signatures; + + bool fallback = false; }; template <> @@ -97,12 +99,14 @@ struct JsonObjectConverter { { fromJson(jo.value("key"_ls), result.key); fromJson(jo.value("signatures"_ls), result.signatures); + fromJson(jo.value("fallback"_ls), result.fallback); } static void dumpTo(QJsonObject &jo, const SignedOneTimeKey &result) { - addParam<>(jo, QStringLiteral("key"), result.key); - addParam<>(jo, QStringLiteral("signatures"), result.signatures); + addParam<>(jo, "key"_ls, result.key); + addParam(jo, "signatures"_ls, result.signatures); + addParam(jo, "key"_ls, result.fallback); } }; -- cgit v1.2.3 From 9f7a65b04c246de4c27b205ece778ede1ad7df7e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 09:18:09 +0200 Subject: Fix copy-pasta in signed one-time key JSON dumper --- lib/connection.cpp | 5 +++-- lib/e2ee/e2ee.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 2319a38a..13a35684 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2306,10 +2306,11 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, signedOneTimeKey ->signatures[targetUserId]["ed25519:"_ls % targetDeviceId] .toLatin1(); + const auto payloadObject = + toJson(SignedOneTimeKey { signedOneTimeKey->key, {} }); if (!verifier.ed25519Verify( edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), - QJsonDocument(toJson(SignedOneTimeKey { signedOneTimeKey->key, {} })) - .toJson(QJsonDocument::Compact), + QJsonDocument(payloadObject).toJson(QJsonDocument::Compact), signature)) { qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId << targetDeviceId << ". Skipping this device."; diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index aba795a4..17c87f53 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -106,7 +106,7 @@ struct JsonObjectConverter { { addParam<>(jo, "key"_ls, result.key); addParam(jo, "signatures"_ls, result.signatures); - addParam(jo, "key"_ls, result.fallback); + addParam(jo, "fallback"_ls, result.fallback); } }; -- cgit v1.2.3 From 6ae41d68dcdb91e5ec4a3ea48a151daaa0765765 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 15:10:33 +0200 Subject: Rework SignedOneTimeKey as a QJsonObject wrapper Since this object has to be verified against a signature it also carries there's a rather specific procedure described in The Spec for that. That procedure basically assumes handling the signed one-time key object as a JSON object, not as a C++ object. And originally Quotient E2EE code was exactly like that (obtaining the right QJsonObject from the job result and handling it as specced) but then one enthusiastic developer (me) decided it's better to use a proper C++ structure - breaking the verification logic along the way. After a couple attempts to fix it, here we are again: SignedOneTimeKey is a proper QJsonObject, and even provides a method returning its JSON in the form prepared for verification (according to the spec). --- lib/connection.cpp | 10 +++----- lib/e2ee/e2ee.h | 62 ++++++++++++++++++++++++++++++++---------------- lib/e2ee/qolmaccount.cpp | 12 ++++------ 3 files changed, 48 insertions(+), 36 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 13a35684..690b3f6a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2303,14 +2303,10 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, // Verify contents of signedOneTimeKey - for that, drop `signatures` and // `unsigned` and then verify the object against the respective signature const auto signature = - signedOneTimeKey - ->signatures[targetUserId]["ed25519:"_ls % targetDeviceId] - .toLatin1(); - const auto payloadObject = - toJson(SignedOneTimeKey { signedOneTimeKey->key, {} }); + signedOneTimeKey->signature(targetUserId, targetDeviceId); if (!verifier.ed25519Verify( edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), - QJsonDocument(payloadObject).toJson(QJsonDocument::Compact), + signedOneTimeKey->toJsonForVerification(), signature)) { qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId << targetDeviceId << ". Skipping this device."; @@ -2320,7 +2316,7 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, curveKeyForUserDevice(targetUserId, targetDeviceId); auto session = QOlmSession::createOutboundSession(olmAccount.get(), recipientCurveKey, - signedOneTimeKey->key); + signedOneTimeKey->key()); if (!session) { qCWarning(E2EE) << "Failed to create olm session for " << recipientCurveKey << session.error(); diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 17c87f53..7b9b5820 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -10,8 +10,10 @@ #include "qolmerrors.h" #include -#include +#include + #include +#include namespace Quotient { @@ -79,35 +81,53 @@ struct UnsignedOneTimeKeys QHash curve25519() const { return keys[Curve25519Key]; } }; -//! Struct representing the signed one-time keys. -class SignedOneTimeKey -{ +class SignedOneTimeKey { public: - //! Required. The unpadded Base64-encoded 32-byte Curve25519 public key. - QString key; + explicit SignedOneTimeKey(const QString& unsignedKey, const QString& userId, + const QString& deviceId, const QString& signature) + : payload { { "key"_ls, unsignedKey }, + { "signatures"_ls, + QJsonObject { + { userId, QJsonObject { { "ed25519:"_ls % deviceId, + signature } } } } } } + {} + explicit SignedOneTimeKey(const QJsonObject& jo = {}) + : payload(jo) + {} - //! Required. Signatures of the key object. - //! The signature is calculated using the process described at Signing JSON. - QHash> signatures; + //! Unpadded Base64-encoded 32-byte Curve25519 public key + QString key() const { return payload["key"_ls].toString(); } - bool fallback = false; -}; + //! \brief Signatures of the key object + //! + //! The signature is calculated using the process described at + //! https://spec.matrix.org/v1.3/appendices/#signing-json + auto signatures() const + { + return fromJson>>( + payload["signatures"_ls]); + } -template <> -struct JsonObjectConverter { - static void fillFrom(const QJsonObject& jo, SignedOneTimeKey& result) + QByteArray signature(QStringView userId, QStringView deviceId) const { - fromJson(jo.value("key"_ls), result.key); - fromJson(jo.value("signatures"_ls), result.signatures); - fromJson(jo.value("fallback"_ls), result.fallback); + return payload["signatures"_ls][userId]["ed25519:"_ls % deviceId] + .toString() + .toLatin1(); } - static void dumpTo(QJsonObject &jo, const SignedOneTimeKey &result) + //! Whether the key is a fallback key + bool isFallback() const { return payload["fallback"_ls].toBool(); } + auto toJson() const { return payload; } + auto toJsonForVerification() const { - addParam<>(jo, "key"_ls, result.key); - addParam(jo, "signatures"_ls, result.signatures); - addParam(jo, "fallback"_ls, result.fallback); + auto json = payload; + json.remove("signatures"_ls); + json.remove("unsigned"_ls); + return QJsonDocument(json).toJson(QJsonDocument::Compact); } + +private: + QJsonObject payload; }; using OneTimeKeys = QHash>; diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 241ae750..c3714363 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -162,17 +162,13 @@ OneTimeKeys QOlmAccount::signOneTimeKeys(const UnsignedOneTimeKeys &keys) const OneTimeKeys signedOneTimeKeys; for (const auto& curveKeys = keys.curve25519(); const auto& [keyId, key] : asKeyValueRange(curveKeys)) - signedOneTimeKeys["signed_curve25519:" % keyId] = - signedOneTimeKey(key.toUtf8(), sign(QJsonObject{{"key", key}})); + signedOneTimeKeys.insert("signed_curve25519:" % keyId, + SignedOneTimeKey { + key, m_userId, m_deviceId, + sign(QJsonObject { { "key", key } }) }); return signedOneTimeKeys; } -SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray& key, - const QString& signature) const -{ - return { key, { { m_userId, { { "ed25519:" + m_deviceId, signature } } } } }; -} - std::optional QOlmAccount::removeOneTimeKeys( const QOlmSession& session) { -- cgit v1.2.3 From 7fdb1a8653863f580b2672faefc08fb372258df8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 15:56:03 +0200 Subject: Code cleanup and reformatting --- lib/connection.cpp | 5 +++-- lib/converters.h | 4 ++-- lib/e2ee/qolmaccount.cpp | 6 ++++-- lib/e2ee/qolmaccount.h | 2 -- 4 files changed, 9 insertions(+), 8 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 690b3f6a..701f78c2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2308,8 +2308,9 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), signedOneTimeKey->toJsonForVerification(), signature)) { - qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId - << targetDeviceId << ". Skipping this device."; + qWarning(E2EE) << "Failed to verify one-time-key signature for" + << targetUserId << targetDeviceId + << ". Skipping this device."; return false; } const auto recipientCurveKey = diff --git a/lib/converters.h b/lib/converters.h index 385982ab..c445442c 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -86,8 +86,8 @@ struct JsonConverter : _impl::JsonExporter { static T load(const QJsonDocument& jd) { return doLoad(jd.object()); } }; -template >> +template + requires (!std::is_constructible_v) inline auto toJson(const T& pod) // -> can return anything from which QJsonValue or, in some cases, QJsonDocument // is constructible diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index c3714363..cd10f165 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -145,9 +145,11 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const { const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); - QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0'); + QByteArray oneTimeKeysBuffer(static_cast(oneTimeKeyLength), '0'); - const auto error = olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), oneTimeKeyLength); + const auto error = olm_account_one_time_keys(m_account, + oneTimeKeysBuffer.data(), + oneTimeKeyLength); if (error == olm_error()) { throw lastError(m_account); } diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 23fe58dd..f2a31314 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -64,8 +64,6 @@ public: //! Sign all one time keys. OneTimeKeys signOneTimeKeys(const UnsignedOneTimeKeys &keys) const; - SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; - UploadKeysJob* createUploadKeyRequest(const UnsignedOneTimeKeys& oneTimeKeys) const; DeviceKeys deviceKeys() const; -- cgit v1.2.3 From 4d4d363b29ff4e471511ff454a58d7d8b88d215d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 15:54:36 +0200 Subject: Start using C++20's designated initializers --- lib/e2ee/e2ee.h | 6 +++--- lib/e2ee/qolmaccount.cpp | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 7b9b5820..449e6ef7 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -37,11 +37,11 @@ constexpr auto SignedCurve25519Key = "signed_curve25519"_ls; constexpr auto OlmV1Curve25519AesSha2AlgoKey = "m.olm.v1.curve25519-aes-sha2"_ls; constexpr auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls; +constexpr std::array SupportedAlgorithms { OlmV1Curve25519AesSha2AlgoKey, + MegolmV1AesSha2AlgoKey }; + inline bool isSupportedAlgorithm(const QString& algorithm) { - static constexpr std::array SupportedAlgorithms { - OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey - }; return std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), algorithm) != SupportedAlgorithms.cend(); diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index cd10f165..ccb191f4 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -187,19 +187,19 @@ OlmAccount* QOlmAccount::data() { return m_account; } DeviceKeys QOlmAccount::deviceKeys() const { - DeviceKeys deviceKeys; - deviceKeys.userId = m_userId; - deviceKeys.deviceId = m_deviceId; - deviceKeys.algorithms = QStringList {"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}; + static QStringList Algorithms(SupportedAlgorithms.cbegin(), + SupportedAlgorithms.cend()); const auto idKeys = identityKeys(); - deviceKeys.keys["curve25519:" + m_deviceId] = idKeys.curve25519; - deviceKeys.keys["ed25519:" + m_deviceId] = idKeys.ed25519; - - const auto sign = signIdentityKeys(); - deviceKeys.signatures[m_userId]["ed25519:" + m_deviceId] = sign; - - return deviceKeys; + return DeviceKeys { + .userId = m_userId, + .deviceId = m_deviceId, + .algorithms = Algorithms, + .keys { { "curve25519:" + m_deviceId, idKeys.curve25519 }, + { "ed25519:" + m_deviceId, idKeys.ed25519 } }, + .signatures { + { m_userId, { { "ed25519:" + m_deviceId, signIdentityKeys() } } } } + }; } UploadKeysJob* QOlmAccount::createUploadKeyRequest( -- cgit v1.2.3 From 63e4cce8cc32af9bd92ead9876a3642d7cbdfb31 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 22:41:15 +0200 Subject: Fix the just introduced Sonar warning Too many parameters of the same type in a row. --- lib/e2ee/e2ee.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 449e6ef7..0772b70a 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -84,12 +84,13 @@ struct UnsignedOneTimeKeys class SignedOneTimeKey { public: explicit SignedOneTimeKey(const QString& unsignedKey, const QString& userId, - const QString& deviceId, const QString& signature) + const QString& deviceId, + const QByteArray& signature) : payload { { "key"_ls, unsignedKey }, { "signatures"_ls, QJsonObject { { userId, QJsonObject { { "ed25519:"_ls % deviceId, - signature } } } } } } + QString(signature) } } } } } } {} explicit SignedOneTimeKey(const QJsonObject& jo = {}) : payload(jo) -- cgit v1.2.3