aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/connection.cpp152
-rw-r--r--lib/database.cpp32
-rw-r--r--lib/e2ee/e2ee.h8
-rw-r--r--lib/e2ee/qolmaccount.cpp26
-rw-r--r--lib/e2ee/qolmaccount.h33
-rw-r--r--lib/e2ee/qolminboundsession.cpp10
-rw-r--r--lib/e2ee/qolminboundsession.h14
-rw-r--r--lib/e2ee/qolmoutboundsession.cpp18
-rw-r--r--lib/e2ee/qolmoutboundsession.h19
-rw-r--r--lib/e2ee/qolmsession.cpp70
-rw-r--r--lib/e2ee/qolmsession.h42
-rw-r--r--lib/e2ee/qolmutility.cpp14
-rw-r--r--lib/e2ee/qolmutility.h7
-rw-r--r--lib/expected.h77
-rw-r--r--lib/room.cpp17
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 {};
}