diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/connection.cpp | 152 | ||||
-rw-r--r-- | lib/database.cpp | 32 | ||||
-rw-r--r-- | lib/e2ee/e2ee.h | 8 | ||||
-rw-r--r-- | lib/e2ee/qolmaccount.cpp | 26 | ||||
-rw-r--r-- | lib/e2ee/qolmaccount.h | 33 | ||||
-rw-r--r-- | lib/e2ee/qolminboundsession.cpp | 10 | ||||
-rw-r--r-- | lib/e2ee/qolminboundsession.h | 14 | ||||
-rw-r--r-- | lib/e2ee/qolmoutboundsession.cpp | 18 | ||||
-rw-r--r-- | lib/e2ee/qolmoutboundsession.h | 19 | ||||
-rw-r--r-- | lib/e2ee/qolmsession.cpp | 70 | ||||
-rw-r--r-- | lib/e2ee/qolmsession.h | 42 | ||||
-rw-r--r-- | lib/e2ee/qolmutility.cpp | 14 | ||||
-rw-r--r-- | lib/e2ee/qolmutility.h | 7 | ||||
-rw-r--r-- | lib/expected.h | 77 | ||||
-rw-r--r-- | lib/room.cpp | 17 |
15 files changed, 310 insertions, 229 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp index bd4d9251..2528b70b 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 <qt6keychain/keychain.h> -#else -# include <qt5keychain/keychain.h> -#endif +# if QT_VERSION_MAJOR >= 6 +# include <qt6keychain/keychain.h> +# else +# include <qt5keychain/keychain.h> +# endif #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -211,83 +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<QOlmError>(pickleResult)) { - qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get<QOlmError>(pickleResult); - return; - } - q->database()->saveOlmSession(senderKey, session->sessionId(), std::get<QByteArray>(pickleResult), QDateTime::currentDateTime()); - } - - std::pair<QString, QString> 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]) { - const auto matches = session->matchesInboundSessionFrom(senderKey, message); - if(std::holds_alternative<bool>(matches) && std::get<bool>(matches)) { - qCDebug(E2EE) << "Found inbound session"; - const auto result = session->decrypt(message); - if(std::holds_alternative<QString>(result)) { - q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); - return { std::get<QString>(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<QOlmError>(newSessionResult)) { - qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get<QOlmError>(newSessionResult); - return {}; - } - auto newSession = std::move(std::get<QOlmSessionPtr>(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<QString>(result)) { - return { std::get<QString>(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<QString, QString> sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) + + template <typename FnT> + std::pair<QString, QString> 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<QString>(result)) { - q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); - return { std::get<QString>(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<QString, QString> sessionDecryptMessage( const QJsonObject& personalCipherObject, const QByteArray& senderKey) { + const auto msgType = static_cast<QOlmMessage::Type>( + 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<QOlmMessage::Type>( - 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<QByteArray>(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<QString, std::vector<QOlmSessionPtr>> Database::loadOlmSessions(con commit(); UnorderedMap<QString, std::vector<QOlmSessionPtr>> sessions; while (query.next()) { - auto session = QOlmSession::unpickle(query.value("pickle").toByteArray(), picklingMode); - if (std::holds_alternative<QOlmError>(session)) { - qCWarning(E2EE) << "Failed to unpickle olm session"; - continue; - } - sessions[query.value("senderKey").toString()].push_back(std::move(std::get<QOlmSessionPtr>(session))); + 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<QString, QOlmInboundGroupSessionPtr> Database::loadMegolmSessions(c commit(); UnorderedMap<QString, QOlmInboundGroupSessionPtr> sessions; while (query.next()) { - auto session = QOlmInboundGroupSession::unpickle(query.value("pickle").toByteArray(), picklingMode); - if (std::holds_alternative<QOlmError>(session)) { - qCWarning(E2EE) << "Failed to unpickle megolm session"; - continue; - } - - sessions[query.value("sessionId").toString()] = std::move(std::get<QOlmInboundGroupSessionPtr>(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 <QtCore/QMetaType> @@ -55,6 +57,12 @@ using QOlmSessionPtr = std::unique_ptr<QOlmSession>; class QOlmInboundGroupSession; using QOlmInboundGroupSessionPtr = std::unique_ptr<QOlmInboundGroupSession>; +class QOlmOutboundGroupSession; +using QOlmOutboundGroupSessionPtr = std::unique_ptr<QOlmOutboundGroupSession>; + +template <typename T> +using QOlmExpected = Expected<T, QOlmError>; + struct IdentityKeys { QByteArray curve25519; diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 476a60bd..72dddafb 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 <QtCore/QRandomGenerator> +#include <olm/olm.h> + using namespace Quotient; QHash<QString, QString> OneTimeKeys::curve25519() const @@ -70,7 +73,7 @@ void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) } } -std::variant<QByteArray, QOlmError> QOlmAccount::pickle(const PicklingMode &mode) +QOlmExpected<QByteArray> QOlmAccount::pickle(const PicklingMode &mode) { const QByteArray key = toKey(mode); const size_t pickleLength = olm_pickle_account_length(m_account); @@ -140,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); @@ -193,9 +196,10 @@ QByteArray QOlmAccount::signOneTimeKey(const QString &key) const return sign(j.toJson(QJsonDocument::Compact)); } -std::optional<QOlmError> QOlmAccount::removeOneTimeKeys(const QOlmSessionPtr &session) const +std::optional<QOlmError> QOlmAccount::removeOneTimeKeys( + 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); @@ -241,19 +245,19 @@ UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKey return new UploadKeysJob(keys, oneTimeKeysSigned); } -std::variant<QOlmSessionPtr, QOlmError> QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) +QOlmExpected<QOlmSessionPtr> QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSession(this, preKeyMessage); } -std::variant<QOlmSessionPtr, QOlmError> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) +QOlmExpected<QOlmSessionPtr> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); } -std::variant<QOlmSessionPtr, QOlmError> QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) +QOlmExpected<QOlmSessionPtr> QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) { return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); } @@ -292,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<QOlmError>(result)) { - return false; - } - - return std::get<bool>(result); + return utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf) + .value_or(false); } diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 17f43f1a..ee2aa69d 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -5,21 +5,16 @@ #pragma once -#include "csapi/keys.h" #include "e2ee/e2ee.h" -#include "e2ee/qolmerrors.h" #include "e2ee/qolmmessage.h" -#include "e2ee/qolmsession.h" -#include <QObject> -struct OlmAccount; +#include "csapi/keys.h" -namespace Quotient { +#include <QtCore/QObject> -class QOlmSession; -class Connection; +struct OlmAccount; -using QOlmSessionPtr = std::unique_ptr<QOlmSession>; +namespace Quotient { //! An olm account manages all cryptographic keys used on a device. //! \code{.cpp} @@ -30,7 +25,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 @@ -44,7 +39,7 @@ public: void unpickle(QByteArray &pickled, const PicklingMode &mode); //! Serialises an OlmAccount to encrypted Base64. - std::variant<QByteArray, QOlmError> pickle(const PicklingMode &mode); + QOlmExpected<QByteArray> pickle(const PicklingMode &mode); //! Returns the account's public identity keys already formatted as JSON IdentityKeys identityKeys() const; @@ -61,7 +56,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; @@ -79,22 +74,26 @@ public: DeviceKeys deviceKeys() const; //! Remove the one time key used to create the supplied session. - [[nodiscard]] std::optional<QOlmError> removeOneTimeKeys(const QOlmSessionPtr &session) const; + [[nodiscard]] std::optional<QOlmError> 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<QOlmSessionPtr, QOlmError> createInboundSession(const QOlmMessage &preKeyMessage); + QOlmExpected<QOlmSessionPtr> 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<QOlmSessionPtr, QOlmError> createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage); + QOlmExpected<QOlmSessionPtr> createInboundSessionFrom( + const QByteArray& theirIdentityKey, const QOlmMessage& preKeyMessage); //! Creates an outbound session for sending messages to a specific /// identity and one time key. - std::variant<QOlmSessionPtr, QOlmError> createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); + QOlmExpected<QOlmSessionPtr> createOutboundSession( + const QByteArray& theirIdentityKey, const QByteArray& theirOneTimeKey); void markKeysAsPublished(); @@ -103,7 +102,7 @@ public: OlmAccount *data(); Q_SIGNALS: - void needsSave() const; + void needsSave(); private: OlmAccount *m_account = nullptr; // owning 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<std::unique_ptr<QOlmInboundGroupSession>, QOlmError> QOlmInboundGroupSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +QOlmExpected<QOlmInboundGroupSessionPtr> 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<std::unique_ptr<QOlmInboundGroupSession>, QOlmError> QOlmInboundGro return std::make_unique<QOlmInboundGroupSession>(groupSession); } -std::variant<std::pair<QString, uint32_t>, QOlmError> QOlmInboundGroupSession::decrypt(const QByteArray &message) +QOlmExpected<std::pair<QByteArray, uint32_t>> 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<std::pair<QString, uint32_t>, QOlmError> QOlmInboundGroupSession::d QByteArray output(plaintextLen, '0'); std::memcpy(output.data(), plaintextBuf.data(), plaintextLen); - return std::make_pair<QString, qint32>(QString(output), messageIndex); + return std::make_pair(output, messageIndex); } -std::variant<QByteArray, QOlmError> QOlmInboundGroupSession::exportSession(uint32_t messageIndex) +QOlmExpected<QByteArray> 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 <memory> -#include <variant> +#include <olm/olm.h> 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<std::unique_ptr<QOlmInboundGroupSession>, QOlmError> - unpickle(const QByteArray& picked, const PicklingMode& mode); + static QOlmExpected<QOlmInboundGroupSessionPtr> unpickle( + const QByteArray& pickled, const PicklingMode& mode); //! Decrypts ciphertext received for this group session. - std::variant<std::pair<QString, uint32_t>, QOlmError> decrypt( - const QByteArray& message); + QOlmExpected<std::pair<QByteArray, uint32_t> > 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<QByteArray, QOlmError> exportSession(uint32_t messageIndex); + QOlmExpected<QByteArray> 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<uint8_t *>(m_groupSession)); } -std::unique_ptr<QOlmOutboundGroupSession> 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> QOlmOutboundGroupSession::create() return std::make_unique<QOlmOutboundGroupSession>(olmOutboundGroupSession); } -std::variant<QByteArray, QOlmError> QOlmOutboundGroupSession::pickle(const PicklingMode &mode) +QOlmExpected<QByteArray> 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<QByteArray, QOlmError> QOlmOutboundGroupSession::pickle(const Pickl return pickledBuf; } -std::variant<std::unique_ptr<QOlmOutboundGroupSession>, QOlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +QOlmExpected<QOlmOutboundGroupSessionPtr> 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<std::unique_ptr<QOlmOutboundGroupSession>, QOlmError> QOlmOutboundG return std::make_unique<QOlmOutboundGroupSession>(olmOutboundGroupSession); } -std::variant<QByteArray, QOlmError> QOlmOutboundGroupSession::encrypt(const QString &plaintext) +QOlmExpected<QByteArray> 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<QByteArray, QOlmError> QOlmOutboundGroupSession::sessionKey() const +QOlmExpected<QByteArray> 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<uint8_t *>(keyBuffer.data()), - keyMaxLength); + const auto error = olm_outbound_group_session_key( + m_groupSession, reinterpret_cast<uint8_t*>(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 <memory> +#include <olm/olm.h> namespace Quotient { @@ -19,15 +19,15 @@ public: ~QOlmOutboundGroupSession(); //! Creates a new instance of `QOlmOutboundGroupSession`. //! Throw OlmError on errors - static std::unique_ptr<QOlmOutboundGroupSession> create(); + static QOlmOutboundGroupSessionPtr create(); //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. - std::variant<QByteArray, QOlmError> pickle(const PicklingMode &mode); + QOlmExpected<QByteArray> pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. - static std::variant<std::unique_ptr<QOlmOutboundGroupSession>, QOlmError> - unpickle(QByteArray& pickled, const PicklingMode& mode); + static QOlmExpected<QOlmOutboundGroupSessionPtr> unpickle( + QByteArray& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. - std::variant<QByteArray, QOlmError> encrypt(const QString &plaintext); + QOlmExpected<QByteArray> 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<QByteArray, QOlmError> sessionKey() const; + QOlmExpected<QByteArray> sessionKey() const; QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); private: OlmOutboundGroupSession *m_groupSession; }; -using QOlmOutboundGroupSessionPtr = std::unique_ptr<QOlmOutboundGroupSession>; -} +} // namespace Quotient diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index e575ff39..2b149aac 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 <cstring> -#include <QDebug> +#include <olm/olm.h> using namespace Quotient; @@ -25,7 +27,9 @@ OlmSession* QOlmSession::create() return olm_session(new uint8_t[olm_session_size()]); } -std::variant<QOlmSessionPtr, QOlmError> QOlmSession::createInbound(QOlmAccount *account, const QOlmMessage &preKeyMessage, bool from, const QString &theirIdentityKey) +QOlmExpected<QOlmSessionPtr> 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; @@ -51,17 +55,22 @@ std::variant<QOlmSessionPtr, QOlmError> QOlmSession::createInbound(QOlmAccount * return std::make_unique<QOlmSession>(olmSession); } -std::variant<QOlmSessionPtr, QOlmError> QOlmSession::createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage) +QOlmExpected<QOlmSessionPtr> QOlmSession::createInboundSession( + QOlmAccount* account, const QOlmMessage& preKeyMessage) { return createInbound(account, preKeyMessage); } -std::variant<QOlmSessionPtr, QOlmError> QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) +QOlmExpected<QOlmSessionPtr> QOlmSession::createInboundSessionFrom( + QOlmAccount* account, const QString& theirIdentityKey, + const QOlmMessage& preKeyMessage) { return createInbound(account, preKeyMessage, true, theirIdentityKey); } -std::variant<QOlmSessionPtr, QOlmError> QOlmSession::createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey) +QOlmExpected<QOlmSessionPtr> QOlmSession::createOutboundSession( + QOlmAccount* account, const QString& theirIdentityKey, + const QString& theirOneTimeKey) { auto *olmOutboundSession = create(); const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); @@ -87,7 +96,7 @@ std::variant<QOlmSessionPtr, QOlmError> QOlmSession::createOutboundSession(QOlmA return std::make_unique<QOlmSession>(olmOutboundSession); } -std::variant<QByteArray, QOlmError> QOlmSession::pickle(const PicklingMode &mode) +QOlmExpected<QByteArray> QOlmSession::pickle(const PicklingMode &mode) { QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); QByteArray key = toKey(mode); @@ -103,7 +112,8 @@ std::variant<QByteArray, QOlmError> QOlmSession::pickle(const PicklingMode &mode return pickledBuf; } -std::variant<QOlmSessionPtr, QOlmError> QOlmSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +QOlmExpected<QOlmSessionPtr> QOlmSession::unpickle(const QByteArray& pickled, + const PicklingMode& mode) { QByteArray pickledBuf = pickled; auto *olmSession = create(); @@ -138,7 +148,7 @@ QOlmMessage QOlmSession::encrypt(const QString &plaintext) return QOlmMessage(messageBuf, messageType); } -std::variant<QString, QOlmError> QOlmSession::decrypt(const QOlmMessage &message) const +QOlmExpected<QByteArray> QOlmSession::decrypt(const QOlmMessage &message) const { const auto messageType = message.type(); const auto ciphertext = message.toCiphertext(); @@ -207,45 +217,35 @@ bool QOlmSession::hasReceivedMessage() const return olm_session_has_received_message(m_session); } -std::variant<bool, QOlmError> 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<bool, QOlmError> 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()); + const auto maybeMatches = 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; - } + if (maybeMatches == olm_error()) + qCWarning(E2EE) << "Error matching an inbound session:" + << olm_session_last_error(m_session); + return maybeMatches == 1; } QOlmSession::QOlmSession(OlmSession *session) : m_session(session) -{ -} +{} diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index f20c9837..faae16ef 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -4,17 +4,14 @@ #pragma once -#include <QDebug> -#include <olm/olm.h> // 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 @@ -22,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<std::unique_ptr<QOlmSession>, QOlmError> - createInboundSession(QOlmAccount* account, const QOlmMessage& preKeyMessage); + static QOlmExpected<QOlmSessionPtr> createInboundSession( + QOlmAccount* account, const QOlmMessage& preKeyMessage); - static std::variant<std::unique_ptr<QOlmSession>, QOlmError> - createInboundSessionFrom(QOlmAccount* account, - const QString& theirIdentityKey, - const QOlmMessage& preKeyMessage); + static QOlmExpected<QOlmSessionPtr> createInboundSessionFrom( + QOlmAccount* account, const QString& theirIdentityKey, + const QOlmMessage& preKeyMessage); - static std::variant<std::unique_ptr<QOlmSession>, QOlmError> - createOutboundSession(QOlmAccount* account, const QString& theirIdentityKey, - const QString& theirOneTimeKey); + static QOlmExpected<QOlmSessionPtr> createOutboundSession( + QOlmAccount* account, const QString& theirIdentityKey, + const QString& theirOneTimeKey); //! Serialises an `QOlmSession` to encrypted Base64. - std::variant<QByteArray, QOlmError> pickle(const PicklingMode &mode); + QOlmExpected<QByteArray> pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static std::variant<std::unique_ptr<QOlmSession>, QOlmError> unpickle( + static QOlmExpected<QOlmSessionPtr> 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<QString, QOlmError> decrypt(const QOlmMessage &message) const; + QOlmExpected<QByteArray> decrypt(const QOlmMessage &message) const; //! Get a base64-encoded identifier for this session. QByteArray sessionId() const; @@ -59,11 +55,10 @@ public: bool hasReceivedMessage() const; //! Checks if the 'prekey' message is for this in-bound session. - std::variant<bool, QOlmError> matchesInboundSession( - const QOlmMessage& preKeyMessage) const; + bool matchesInboundSession(const QOlmMessage& preKeyMessage) const; //! Checks if the 'prekey' message is for this in-bound session. - std::variant<bool, QOlmError> matchesInboundSessionFrom( + bool matchesInboundSessionFrom( const QString& theirIdentityKey, const QOlmMessage& preKeyMessage) const; friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) @@ -71,8 +66,7 @@ public: return lhs.sessionId() < rhs.sessionId(); } - friend bool operator<(const std::unique_ptr<QOlmSession>& lhs, - const std::unique_ptr<QOlmSession>& rhs) + friend bool operator<(const QOlmSessionPtr& lhs, const QOlmSessionPtr& rhs) { return *lhs < *rhs; } @@ -83,7 +77,7 @@ public: private: //! Helper function for creating new sessions and handling errors. static OlmSession* create(); - static std::variant<std::unique_ptr<QOlmSession>, QOlmError> createInbound( + static QOlmExpected<QOlmSessionPtr> 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 <QDebug> + +#include <olm/olm.h> using namespace Quotient; @@ -40,8 +40,9 @@ QString QOlmUtility::sha256Utf8Msg(const QString &message) const return sha256Bytes(message.toUtf8()); } -std::variant<bool, QOlmError> QOlmUtility::ed25519Verify(const QByteArray &key, - const QByteArray &message, const QByteArray &signature) +QOlmExpected<bool> 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<bool, QOlmError> 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 <variant> -#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<bool, QOlmError> ed25519Verify(const QByteArray &key, + QOlmExpected<bool> ed25519Verify(const QByteArray &key, const QByteArray &message, const QByteArray &signature); private: diff --git a/lib/expected.h b/lib/expected.h new file mode 100644 index 00000000..7b9e7f1d --- /dev/null +++ b/lib/expected.h @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2022 Kitsune Ral <Kitsune-Ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include <variant> + +namespace Quotient { + +//! \brief A minimal subset of std::expected from C++23 +template <typename T, typename E, + std::enable_if_t<!std::is_same_v<T, E>, bool> = true> +class Expected { +private: + template <typename X> + using enable_if_constructible_t = std::enable_if_t< + std::is_constructible_v<T, X> || std::is_constructible_v<E, X>>; + +public: + using value_type = T; + using error_type = E; + + Expected() = default; + explicit Expected(const Expected&) = default; + explicit Expected(Expected&&) noexcept = default; + + template <typename X, typename = enable_if_constructible_t<X>> + Expected(X&& x) + : data(std::forward<X>(x)) + {} + + Expected& operator=(const Expected&) = default; + Expected& operator=(Expected&&) noexcept = default; + + template <typename X, typename = enable_if_constructible_t<X>> + Expected& operator=(X&& x) + { + data = std::forward<X>(x); + return *this; + } + + bool has_value() const { return std::holds_alternative<T>(data); } + explicit operator bool() const { return has_value(); } + + const value_type& value() const& { return std::get<T>(data); } + value_type& value() & { return std::get<T>(data); } + value_type value() && { return std::get<T>(std::move(data)); } + + const value_type& operator*() const& { return value(); } + value_type& operator*() & { return value(); } + + const value_type* operator->() const& { return std::get_if<T>(&data); } + value_type* operator->() & { return std::get_if<T>(&data); } + + template <class U> + T value_or(U&& fallback) const& + { + if (has_value()) + return value(); + return std::forward<U>(fallback); + } + template <class U> + T value_or(U&& fallback) && + { + if (has_value()) + return value(); + return std::forward<U>(fallback); + } + + const E& error() const& { return std::get<E>(data); } + E& error() & { return std::get<E>(data); } + +private: + std::variant<T, E> data; +}; + +} // namespace Quotient 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<QOlmError>(decryptResult)) { + if(!decryptResult) { qCWarning(E2EE) << "Unable to decrypt event" << eventId - << "with matching megolm session:" << std::get<QOlmError>(decryptResult); + << "with matching megolm session:" << decryptResult.error(); return {}; } - const auto& [content, index] = std::get<std::pair<QString, uint32_t>>(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 {}; } |