aboutsummaryrefslogtreecommitdiff
path: root/lib/e2ee
diff options
context:
space:
mode:
authorTobias Fella <fella@posteo.de>2021-12-07 19:08:29 +0100
committerTobias Fella <fella@posteo.de>2021-12-07 19:08:29 +0100
commit2c6fa33ca52842e9dfba0dd3893a9d5526e10e60 (patch)
tree96ffece23a6f671d71328159357db3c68767365a /lib/e2ee
parent47bd4dfb2bc720d2b5919b93985f87d918af572a (diff)
downloadlibquotient-2c6fa33ca52842e9dfba0dd3893a9d5526e10e60.tar.gz
libquotient-2c6fa33ca52842e9dfba0dd3893a9d5526e10e60.zip
Rename "crypto" -> "e2ee"
Diffstat (limited to 'lib/e2ee')
-rw-r--r--lib/e2ee/e2ee.h132
-rw-r--r--lib/e2ee/qolmaccount.cpp330
-rw-r--r--lib/e2ee/qolmaccount.h123
-rw-r--r--lib/e2ee/qolmerrors.cpp22
-rw-r--r--lib/e2ee/qolmerrors.h28
-rw-r--r--lib/e2ee/qolminboundsession.cpp153
-rw-r--r--lib/e2ee/qolminboundsession.h48
-rw-r--r--lib/e2ee/qolmmessage.cpp35
-rw-r--r--lib/e2ee/qolmmessage.h41
-rw-r--r--lib/e2ee/qolmoutboundsession.cpp128
-rw-r--r--lib/e2ee/qolmoutboundsession.h54
-rw-r--r--lib/e2ee/qolmsession.cpp253
-rw-r--r--lib/e2ee/qolmsession.h76
-rw-r--r--lib/e2ee/qolmutility.cpp63
-rw-r--r--lib/e2ee/qolmutility.h45
-rw-r--r--lib/e2ee/qolmutils.cpp24
-rw-r--r--lib/e2ee/qolmutils.h15
17 files changed, 1570 insertions, 0 deletions
diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h
new file mode 100644
index 00000000..41cd2878
--- /dev/null
+++ b/lib/e2ee/e2ee.h
@@ -0,0 +1,132 @@
+// SPDX-FileCopyrightText: 2019 Alexey Andreyev <aa13q@ya.ru>
+// SPDX-FileCopyrightText: 2019 Kitsune Ral <Kitsune-Ral@users.sf.net>
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include "converters.h"
+#include <variant>
+
+#include <QMap>
+#include <QHash>
+#include <QStringList>
+#include <QMetaType>
+
+#include "util.h"
+
+namespace Quotient {
+
+inline const auto CiphertextKeyL = "ciphertext"_ls;
+inline const auto SenderKeyKeyL = "sender_key"_ls;
+inline const auto DeviceIdKeyL = "device_id"_ls;
+inline const auto SessionIdKeyL = "session_id"_ls;
+
+inline const auto AlgorithmKeyL = "algorithm"_ls;
+inline const auto RotationPeriodMsKeyL = "rotation_period_ms"_ls;
+inline const auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls;
+
+inline const auto AlgorithmKey = QStringLiteral("algorithm");
+inline const auto RotationPeriodMsKey = QStringLiteral("rotation_period_ms");
+inline const auto RotationPeriodMsgsKey =
+ QStringLiteral("rotation_period_msgs");
+
+inline const auto Ed25519Key = QStringLiteral("ed25519");
+inline const auto Curve25519Key = QStringLiteral("curve25519");
+inline const auto SignedCurve25519Key = QStringLiteral("signed_curve25519");
+inline const auto OlmV1Curve25519AesSha2AlgoKey =
+ QStringLiteral("m.olm.v1.curve25519-aes-sha2");
+inline const auto MegolmV1AesSha2AlgoKey =
+ QStringLiteral("m.megolm.v1.aes-sha2");
+inline const QStringList SupportedAlgorithms = { OlmV1Curve25519AesSha2AlgoKey,
+ MegolmV1AesSha2AlgoKey };
+struct Unencrypted {};
+struct Encrypted {
+ QByteArray key;
+};
+
+using PicklingMode = std::variant<Unencrypted, Encrypted>;
+
+class QOlmSession;
+using QOlmSessionPtr = std::unique_ptr<QOlmSession>;
+
+class QOlmInboundGroupSession;
+using QOlmInboundGroupSessionPtr = std::unique_ptr<QOlmInboundGroupSession>;
+
+template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
+template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
+
+struct IdentityKeys
+{
+ QByteArray curve25519;
+ QByteArray ed25519;
+};
+
+//! Struct representing the one-time keys.
+struct OneTimeKeys
+{
+ QMap<QString, QMap<QString, QString>> keys;
+
+ //! Get the HashMap containing the curve25519 one-time keys.
+ QMap<QString, QString> curve25519() const;
+
+ //! Get a reference to the hashmap corresponding to given key type.
+ std::optional<QMap<QString, QString>> get(QString keyType) const;
+};
+
+//! Struct representing the signed one-time keys.
+class SignedOneTimeKey
+{
+public:
+ SignedOneTimeKey() = default;
+ SignedOneTimeKey(const SignedOneTimeKey &) = default;
+ SignedOneTimeKey &operator=(const SignedOneTimeKey &) = default;
+ //! Required. The unpadded Base64-encoded 32-byte Curve25519 public key.
+ QString key;
+
+ //! Required. Signatures of the key object.
+ //! The signature is calculated using the process described at Signing JSON.
+ QHash<QString, QHash<QString, QString>> signatures;
+};
+
+
+template <>
+struct JsonObjectConverter<SignedOneTimeKey> {
+ static void fillFrom(const QJsonObject& jo,
+ SignedOneTimeKey& result)
+ {
+ fromJson(jo.value("key"_ls), result.key);
+ fromJson(jo.value("signatures"_ls), result.signatures);
+ }
+
+ static void dumpTo(QJsonObject &jo, const SignedOneTimeKey &result)
+ {
+ addParam<>(jo, QStringLiteral("key"), result.key);
+ addParam<>(jo, QStringLiteral("signatures"), result.signatures);
+ }
+};
+
+bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs);
+
+template <typename T>
+class asKeyValueRange
+{
+public:
+ asKeyValueRange(T &data)
+ : m_data{data}
+ {
+ }
+
+ auto begin() { return m_data.keyValueBegin(); }
+
+ auto end() { return m_data.keyValueEnd(); }
+
+private:
+ T &m_data;
+};
+
+} // namespace Quotient
+
+Q_DECLARE_METATYPE(Quotient::SignedOneTimeKey)
diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp
new file mode 100644
index 00000000..aaf51946
--- /dev/null
+++ b/lib/e2ee/qolmaccount.cpp
@@ -0,0 +1,330 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "qolmaccount.h"
+#include "connection.h"
+#include "csapi/keys.h"
+#include "e2ee/qolmutils.h"
+#include "e2ee/qolmutility.h"
+#include <QJsonObject>
+#include <QJsonDocument>
+#include <QDebug>
+#include <iostream>
+
+using namespace Quotient;
+
+QMap<QString, QString> OneTimeKeys::curve25519() const
+{
+ return keys[QStringLiteral("curve25519")];
+}
+
+std::optional<QMap<QString, QString>> OneTimeKeys::get(QString keyType) const
+{
+ if (!keys.contains(keyType)) {
+ return std::nullopt;
+ }
+ return keys[keyType];
+}
+
+bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs)
+{
+ return lhs.curve25519 == rhs.curve25519 &&& lhs.ed25519 == rhs.ed25519;
+}
+
+// Convert olm error to enum
+QOlmError lastError(OlmAccount *account) {
+ const std::string error_raw = olm_account_last_error(account);
+
+ return fromString(error_raw);
+}
+
+QByteArray getRandom(size_t bufferSize)
+{
+ QByteArray buffer(bufferSize, '0');
+ std::generate(buffer.begin(), buffer.end(), std::rand);
+ return buffer;
+}
+
+QOlmAccount::QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent)
+ : QObject(parent)
+ , m_userId(userId)
+ , m_deviceId(deviceId)
+{
+}
+
+QOlmAccount::~QOlmAccount()
+{
+ olm_clear_account(m_account);
+ delete[](reinterpret_cast<uint8_t *>(m_account));
+}
+
+void QOlmAccount::createNewAccount()
+{
+ m_account = olm_account(new uint8_t[olm_account_size()]);
+ size_t randomSize = olm_create_account_random_length(m_account);
+ QByteArray randomData = getRandom(randomSize);
+ const auto error = olm_create_account(m_account, randomData.data(), randomSize);
+ if (error == olm_error()) {
+ throw lastError(m_account);
+ }
+ Q_EMIT needsSave();
+}
+
+void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode)
+{
+ m_account = olm_account(new uint8_t[olm_account_size()]);
+ const QByteArray key = toKey(mode);
+ const auto error = olm_unpickle_account(m_account, key.data(), key.length(), pickled.data(), pickled.size());
+ if (error == olm_error()) {
+ qCWarning(E2EE) << "Failed to unpickle olm account";
+ //TODO: Do something that is not dying
+ // Probably log the user out since we have no way of getting to the keys
+ //throw lastError(m_account);
+ }
+}
+
+std::variant<QByteArray, QOlmError> QOlmAccount::pickle(const PicklingMode &mode)
+{
+ const QByteArray key = toKey(mode);
+ const size_t pickleLength = olm_pickle_account_length(m_account);
+ QByteArray pickleBuffer(pickleLength, '0');
+ const auto error = olm_pickle_account(m_account, key.data(),
+ key.length(), pickleBuffer.data(), pickleLength);
+ if (error == olm_error()) {
+ return lastError(m_account);
+ }
+ return pickleBuffer;
+}
+
+IdentityKeys QOlmAccount::identityKeys() const
+{
+ const size_t keyLength = olm_account_identity_keys_length(m_account);
+ QByteArray keyBuffer(keyLength, '0');
+ const auto error = olm_account_identity_keys(m_account, keyBuffer.data(), keyLength);
+ if (error == olm_error()) {
+ throw lastError(m_account);
+ }
+ const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object();
+ return IdentityKeys {
+ key.value(QStringLiteral("curve25519")).toString().toUtf8(),
+ key.value(QStringLiteral("ed25519")).toString().toUtf8()
+ };
+}
+
+QByteArray QOlmAccount::sign(const QByteArray &message) const
+{
+ QByteArray signatureBuffer(olm_account_signature_length(m_account), '0');
+
+ const auto error = olm_account_sign(m_account, message.data(), message.length(),
+ signatureBuffer.data(), signatureBuffer.length());
+
+ if (error == olm_error()) {
+ throw lastError(m_account);
+ }
+ return signatureBuffer;
+}
+
+QByteArray QOlmAccount::sign(const QJsonObject &message) const
+{
+ return sign(QJsonDocument(message).toJson(QJsonDocument::Compact));
+}
+
+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));
+
+}
+
+size_t QOlmAccount::maxNumberOfOneTimeKeys() const
+{
+ return olm_account_max_number_of_one_time_keys(m_account);
+}
+
+size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const
+{
+ 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);
+
+ if (error == olm_error()) {
+ throw lastError(m_account);
+ }
+ Q_EMIT needsSave();
+ return error;
+}
+
+OneTimeKeys QOlmAccount::oneTimeKeys() const
+{
+ const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account);
+ QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0');
+
+ const auto error = olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), oneTimeKeyLength);
+ if (error == olm_error()) {
+ throw lastError(m_account);
+ }
+ const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object();
+ OneTimeKeys oneTimeKeys;
+
+ for (const QString& key1 : json.keys()) {
+ auto oneTimeKeyObject = json[key1].toObject();
+ auto keyMap = QMap<QString, QString>();
+ for (const QString &key2 : oneTimeKeyObject.keys()) {
+ keyMap[key2] = oneTimeKeyObject[key2].toString();
+ }
+ oneTimeKeys.keys[key1] = keyMap;
+ }
+ return oneTimeKeys;
+}
+
+QMap<QString, SignedOneTimeKey> QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const
+{
+ QMap<QString, SignedOneTimeKey> 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);
+ }
+ return signedOneTimeKeys;
+}
+
+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));
+}
+
+std::optional<QOlmError> QOlmAccount::removeOneTimeKeys(const QOlmSessionPtr &session) const
+{
+ const auto error = olm_remove_one_time_keys(m_account, session->raw());
+
+ if (error == olm_error()) {
+ return lastError(m_account);
+ }
+ Q_EMIT needsSave();
+ return std::nullopt;
+}
+
+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"};
+
+ 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;
+}
+
+UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKeys)
+{
+ auto keys = deviceKeys();
+
+ if (oneTimeKeys.curve25519().isEmpty()) {
+ return new UploadKeysJob(keys);
+ }
+
+ // Sign & append the one time keys.
+ auto temp = signOneTimeKeys(oneTimeKeys);
+ QHash<QString, QVariant> oneTimeKeysSigned;
+ for (const auto &[keyId, key] : asKeyValueRange(temp)) {
+ oneTimeKeysSigned[keyId] = QVariant::fromValue(toJson(key));
+ }
+
+ return new UploadKeysJob(keys, oneTimeKeysSigned);
+}
+
+std::variant<QOlmSessionPtr, QOlmError> 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)
+{
+ Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey);
+ return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage);
+}
+
+std::variant<QOlmSessionPtr, QOlmError> QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey)
+{
+ return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey);
+}
+
+void QOlmAccount::markKeysAsPublished()
+{
+ olm_account_mark_keys_as_published(m_account);
+ Q_EMIT needsSave();
+}
+
+bool Quotient::verifyIdentitySignature(const DeviceKeys &deviceKeys,
+ const QString &deviceId,
+ const QString &userId)
+{
+ const auto signKeyId = "ed25519:" + deviceId;
+ const auto signingKey = deviceKeys.keys[signKeyId];
+ const auto signature = deviceKeys.signatures[userId][signKeyId];
+
+ if (signature.isEmpty()) {
+ return false;
+ }
+
+ return ed25519VerifySignature(signingKey, toJson(deviceKeys), signature);
+}
+
+bool Quotient::ed25519VerifySignature(const QString &signingKey,
+ const QJsonObject &obj,
+ const QString &signature)
+{
+ if (signature.isEmpty()) {
+ return false;
+ }
+ QJsonObject obj1 = obj;
+
+ obj1.remove("unsigned");
+ obj1.remove("signatures");
+
+ auto canonicalJson = QJsonDocument(obj1).toJson(QJsonDocument::Compact);
+
+ 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);
+}
diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h
new file mode 100644
index 00000000..00afc0e6
--- /dev/null
+++ b/lib/e2ee/qolmaccount.h
@@ -0,0 +1,123 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+
+#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;
+
+namespace Quotient {
+
+class QOlmSession;
+class Connection;
+
+using QOlmSessionPtr = std::unique_ptr<QOlmSession>;
+
+//! An olm account manages all cryptographic keys used on a device.
+//! \code{.cpp}
+//! const auto olmAccount = new QOlmAccount(this);
+//! \endcode
+class QOlmAccount : public QObject
+{
+ Q_OBJECT
+public:
+ QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent = nullptr);
+ ~QOlmAccount();
+
+ //! Creates a new instance of OlmAccount. During the instantiation
+ //! the Ed25519 fingerprint key pair and the Curve25519 identity key
+ //! pair are generated. For more information see <a
+ //! href="https://matrix.org/docs/guides/e2e_implementation.html#keys-used-in-end-to-end-encryption">here</a>.
+ //! This needs to be called before any other action or use unpickle() instead.
+ void createNewAccount();
+
+ //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmAccount`.
+ //! This needs to be called before any other action or use createNewAccount() instead.
+ void unpickle(QByteArray &pickled, const PicklingMode &mode);
+
+ //! Serialises an OlmAccount to encrypted Base64.
+ std::variant<QByteArray, QOlmError> pickle(const PicklingMode &mode);
+
+ //! Returns the account's public identity keys already formatted as JSON
+ IdentityKeys identityKeys() const;
+
+ //! Returns the signature of the supplied message.
+ QByteArray sign(const QByteArray &message) const;
+ QByteArray sign(const QJsonObject& message) const;
+
+ //! Sign identity keys.
+ QByteArray signIdentityKeys() const;
+
+ //! Maximum number of one time keys that this OlmAccount can
+ //! currently hold.
+ size_t maxNumberOfOneTimeKeys() const;
+
+ //! Generates the supplied number of one time keys.
+ size_t generateOneTimeKeys(size_t numberOfKeys) const;
+
+ //! Gets the OlmAccount's one time keys formatted as JSON.
+ OneTimeKeys oneTimeKeys() const;
+
+ //! Sign all one time keys.
+ QMap<QString, SignedOneTimeKey> signOneTimeKeys(const OneTimeKeys &keys) const;
+
+ //! Sign one time key.
+ QByteArray signOneTimeKey(const QString &key) const;
+
+ SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const;
+
+ UploadKeysJob *createUploadKeyRequest(const OneTimeKeys &oneTimeKeys);
+
+ DeviceKeys deviceKeys() const;
+
+ //! Remove the one time key used to create the supplied session.
+ [[nodiscard]] std::optional<QOlmError> removeOneTimeKeys(const QOlmSessionPtr &session) const;
+
+ //! 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);
+
+ //! 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);
+
+ //! 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);
+
+ void markKeysAsPublished();
+
+ // HACK do not use directly
+ QOlmAccount(OlmAccount *account);
+ OlmAccount *data();
+
+Q_SIGNALS:
+ void needsSave() const;
+
+private:
+ OlmAccount *m_account = nullptr; // owning
+ QString m_userId;
+ QString m_deviceId;
+};
+
+bool verifyIdentitySignature(const DeviceKeys &deviceKeys,
+ const QString &deviceId,
+ const QString &userId);
+
+//! checks if the signature is signed by the signing_key
+bool ed25519VerifySignature(const QString &signingKey,
+ const QJsonObject &obj,
+ const QString &signature);
+
+} // namespace Quotient
diff --git a/lib/e2ee/qolmerrors.cpp b/lib/e2ee/qolmerrors.cpp
new file mode 100644
index 00000000..6db1803c
--- /dev/null
+++ b/lib/e2ee/qolmerrors.cpp
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+
+#include "qolmerrors.h"
+
+Quotient::QOlmError Quotient::fromString(const std::string &error_raw) {
+ if (!error_raw.compare("BAD_ACCOUNT_KEY")) {
+ return QOlmError::BadAccountKey;
+ } else if (!error_raw.compare("BAD_MESSAGE_KEY_ID")) {
+ return QOlmError::BadMessageKeyId;
+ } else if (!error_raw.compare("INVALID_BASE64")) {
+ return QOlmError::InvalidBase64;
+ } else if (!error_raw.compare("NOT_ENOUGH_RANDOM")) {
+ return QOlmError::NotEnoughRandom;
+ } else if (!error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) {
+ return QOlmError::OutputBufferTooSmall;
+ } else {
+ return QOlmError::Unknown;
+ }
+}
diff --git a/lib/e2ee/qolmerrors.h b/lib/e2ee/qolmerrors.h
new file mode 100644
index 00000000..f8390d2a
--- /dev/null
+++ b/lib/e2ee/qolmerrors.h
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <string>
+
+namespace Quotient {
+//! All errors that could be caused by an operation regarding Olm
+//! Errors are named exactly like the ones in libolm.
+enum QOlmError
+{
+ BadAccountKey,
+ BadMessageFormat,
+ BadMessageKeyId,
+ BadMessageMac,
+ BadMessageVersion,
+ InvalidBase64,
+ NotEnoughRandom,
+ OutputBufferTooSmall,
+ UnknownMessageIndex,
+ Unknown,
+};
+
+QOlmError fromString(const std::string &error_raw);
+
+} //namespace Quotient
diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp
new file mode 100644
index 00000000..9bf56b6c
--- /dev/null
+++ b/lib/e2ee/qolminboundsession.cpp
@@ -0,0 +1,153 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "e2ee/qolminboundsession.h"
+#include <iostream>
+#include <cstring>
+using namespace Quotient;
+
+QOlmError lastError(OlmInboundGroupSession *session) {
+ const std::string error_raw = olm_inbound_group_session_last_error(session);
+
+ return fromString(error_raw);
+}
+
+QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session)
+ : m_groupSession(session)
+{
+}
+
+QOlmInboundGroupSession::~QOlmInboundGroupSession()
+{
+ olm_clear_inbound_group_session(m_groupSession);
+ //delete[](reinterpret_cast<uint8_t *>(m_groupSession));
+}
+
+std::unique_ptr<QOlmInboundGroupSession> QOlmInboundGroupSession::create(const QByteArray &key)
+{
+ const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]);
+ const auto temp = key;
+ const auto error = olm_init_inbound_group_session(olmInboundGroupSession,
+ reinterpret_cast<const uint8_t *>(temp.data()), temp.size());
+
+ if (error == olm_error()) {
+ throw lastError(olmInboundGroupSession);
+ }
+
+ return std::make_unique<QOlmInboundGroupSession>(olmInboundGroupSession);
+}
+
+std::unique_ptr<QOlmInboundGroupSession> QOlmInboundGroupSession::import(const QByteArray &key)
+{
+ const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]);
+ QByteArray keyBuf = key;
+
+ const auto error = olm_import_inbound_group_session(olmInboundGroupSession,
+ reinterpret_cast<const uint8_t *>(keyBuf.data()), keyBuf.size());
+ if (error == olm_error()) {
+ throw lastError(olmInboundGroupSession);
+ }
+
+ return std::make_unique<QOlmInboundGroupSession>(olmInboundGroupSession);
+}
+
+QByteArray toKey(const PicklingMode &mode)
+{
+ if (std::holds_alternative<Unencrypted>(mode)) {
+ return "";
+ }
+ return std::get<Encrypted>(mode).key;
+}
+
+QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const
+{
+ QByteArray pickledBuf(olm_pickle_inbound_group_session_length(m_groupSession), '0');
+ const QByteArray key = toKey(mode);
+ const auto error = olm_pickle_inbound_group_session(m_groupSession, key.data(), key.length(), pickledBuf.data(),
+ pickledBuf.length());
+ if (error == olm_error()) {
+ throw lastError(m_groupSession);
+ }
+ return pickledBuf;
+}
+
+std::variant<std::unique_ptr<QOlmInboundGroupSession>, QOlmError> 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()]);
+ QByteArray key = toKey(mode);
+ const auto error = olm_unpickle_inbound_group_session(groupSession, key.data(), key.length(),
+ pickledBuf.data(), pickledBuf.size());
+ if (error == olm_error()) {
+ return lastError(groupSession);
+ }
+ key.clear();
+
+ return std::make_unique<QOlmInboundGroupSession>(groupSession);
+}
+
+std::variant<std::pair<QString, uint32_t>, QOlmError> QOlmInboundGroupSession::decrypt(const QByteArray &message)
+{
+ // This is for capturing the output of olm_group_decrypt
+ uint32_t messageIndex = 0;
+
+ // We need to clone the message because
+ // olm_decrypt_max_plaintext_length destroys the input buffer
+ QByteArray messageBuf(message.length(), '0');
+ std::copy(message.begin(), message.end(), messageBuf.begin());
+
+ QByteArray plaintextBuf(olm_group_decrypt_max_plaintext_length(m_groupSession,
+ reinterpret_cast<uint8_t *>(messageBuf.data()), messageBuf.length()), '0');
+
+ messageBuf = QByteArray(message.length(), '0');
+ std::copy(message.begin(), message.end(), messageBuf.begin());
+
+ const auto plaintextLen = olm_group_decrypt(m_groupSession, reinterpret_cast<uint8_t *>(messageBuf.data()),
+ messageBuf.length(), reinterpret_cast<uint8_t *>(plaintextBuf.data()), plaintextBuf.length(), &messageIndex);
+
+ // Error code or plaintext length is returned
+ const auto decryptError = plaintextLen;
+
+ if (decryptError == olm_error()) {
+ return lastError(m_groupSession);
+ }
+
+ QByteArray output(plaintextLen, '0');
+ std::memcpy(output.data(), plaintextBuf.data(), plaintextLen);
+
+ return std::make_pair<QString, qint32>(QString(output), messageIndex);
+}
+
+std::variant<QByteArray, QOlmError> QOlmInboundGroupSession::exportSession(uint32_t messageIndex)
+{
+ const auto keyLength = olm_export_inbound_group_session_length(m_groupSession);
+ QByteArray keyBuf(keyLength, '0');
+ const auto error = olm_export_inbound_group_session(m_groupSession, reinterpret_cast<uint8_t *>(keyBuf.data()), keyLength, messageIndex);
+
+ if (error == olm_error()) {
+ return lastError(m_groupSession);
+ }
+ return keyBuf;
+}
+
+uint32_t QOlmInboundGroupSession::firstKnownIndex() const
+{
+ return olm_inbound_group_session_first_known_index(m_groupSession);
+}
+
+QByteArray QOlmInboundGroupSession::sessionId() const
+{
+ QByteArray sessionIdBuf(olm_inbound_group_session_id_length(m_groupSession), '0');
+ const auto error = olm_inbound_group_session_id(m_groupSession, reinterpret_cast<uint8_t *>(sessionIdBuf.data()),
+ sessionIdBuf.length());
+ if (error == olm_error()) {
+ throw lastError(m_groupSession);
+ }
+ return sessionIdBuf;
+}
+
+bool QOlmInboundGroupSession::isVerified() const
+{
+ return olm_inbound_group_session_is_verified(m_groupSession) != 0;
+}
diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h
new file mode 100644
index 00000000..7d52991c
--- /dev/null
+++ b/lib/e2ee/qolminboundsession.h
@@ -0,0 +1,48 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <QByteArray>
+#include <variant>
+#include <memory>
+#include "olm/olm.h"
+#include "e2ee/qolmerrors.h"
+#include "e2ee/e2ee.h"
+
+namespace Quotient {
+
+//! An in-bound group session is responsible for decrypting incoming
+//! communication in a Megolm session.
+struct QOlmInboundGroupSession
+{
+public:
+ ~QOlmInboundGroupSession();
+ //! Creates a new instance of `OlmInboundGroupSession`.
+ static std::unique_ptr<QOlmInboundGroupSession> create(const QByteArray &key);
+ //! Import an inbound group session, from a previous export.
+ static std::unique_ptr<QOlmInboundGroupSession> import(const QByteArray &key);
+ //! Serialises an `OlmInboundGroupSession` to encrypted Base64.
+ 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);
+ //! Decrypts ciphertext received for this group session.
+ std::variant<std::pair<QString, uint32_t>, QOlmError> 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);
+ //! Get the first message index we know how to decrypt.
+ uint32_t firstKnownIndex() const;
+ //! Get a base64-encoded identifier for this session.
+ QByteArray sessionId() const;
+ bool isVerified() const;
+ QOlmInboundGroupSession(OlmInboundGroupSession *session);
+private:
+ OlmInboundGroupSession *m_groupSession;
+};
+
+using QOlmInboundGroupSessionPtr = std::unique_ptr<QOlmInboundGroupSession>;
+using OlmInboundGroupSessionPtr = std::unique_ptr<OlmInboundGroupSession>;
+} // namespace Quotient
diff --git a/lib/e2ee/qolmmessage.cpp b/lib/e2ee/qolmmessage.cpp
new file mode 100644
index 00000000..15008b75
--- /dev/null
+++ b/lib/e2ee/qolmmessage.cpp
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "qolmmessage.h"
+
+using namespace Quotient;
+
+QOlmMessage::QOlmMessage(const QByteArray &ciphertext, QOlmMessage::Type type)
+ : QByteArray(std::move(ciphertext))
+ , m_messageType(type)
+{
+ Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty");
+}
+
+QOlmMessage::QOlmMessage(const QOlmMessage &message)
+ : QByteArray(message)
+ , m_messageType(message.type())
+{
+}
+
+QOlmMessage::Type QOlmMessage::type() const
+{
+ return m_messageType;
+}
+
+QByteArray QOlmMessage::toCiphertext() const
+{
+ return QByteArray(*this);
+}
+
+QOlmMessage QOlmMessage::fromCiphertext(const QByteArray &ciphertext)
+{
+ return QOlmMessage(ciphertext, QOlmMessage::General);
+}
diff --git a/lib/e2ee/qolmmessage.h b/lib/e2ee/qolmmessage.h
new file mode 100644
index 00000000..52aba78c
--- /dev/null
+++ b/lib/e2ee/qolmmessage.h
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <QObject>
+#include <QByteArray>
+
+namespace Quotient {
+
+/*! \brief A wrapper around an olm encrypted message
+ *
+ * This class encapsulates a Matrix olm encrypted message,
+ * passed in either of 2 forms: a general message or a pre-key message.
+ *
+ * The class provides functions to get a type and the ciphertext.
+ */
+class QOlmMessage : public QByteArray {
+ Q_GADGET
+public:
+ enum Type {
+ General,
+ PreKey,
+ };
+ Q_ENUM(Type)
+
+ QOlmMessage() = default;
+ explicit QOlmMessage(const QByteArray &ciphertext, Type type = General);
+ explicit QOlmMessage(const QOlmMessage &message);
+
+ static QOlmMessage fromCiphertext(const QByteArray &ciphertext);
+
+ Q_INVOKABLE Type type() const;
+ Q_INVOKABLE QByteArray toCiphertext() const;
+
+private:
+ Type m_messageType = General;
+};
+
+} //namespace Quotient
diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp
new file mode 100644
index 00000000..88e6b2e1
--- /dev/null
+++ b/lib/e2ee/qolmoutboundsession.cpp
@@ -0,0 +1,128 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "e2ee/qolmoutboundsession.h"
+#include "e2ee/qolmutils.h"
+
+using namespace Quotient;
+
+QOlmError lastError(OlmOutboundGroupSession *session) {
+ const std::string error_raw = olm_outbound_group_session_last_error(session);
+
+ return fromString(error_raw);
+}
+
+QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session)
+ : m_groupSession(session)
+{
+}
+
+QOlmOutboundGroupSession::~QOlmOutboundGroupSession()
+{
+ olm_clear_outbound_group_session(m_groupSession);
+ delete[](reinterpret_cast<uint8_t *>(m_groupSession));
+}
+
+std::unique_ptr<QOlmOutboundGroupSession> 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);
+ QByteArray randomBuf = getRandom(randomLength);
+
+ const auto error = olm_init_outbound_group_session(olmOutboundGroupSession,
+ reinterpret_cast<uint8_t *>(randomBuf.data()), randomBuf.length());
+
+ if (error == olm_error()) {
+ throw lastError(olmOutboundGroupSession);
+ }
+
+ const auto keyMaxLength = olm_outbound_group_session_key_length(olmOutboundGroupSession);
+ QByteArray keyBuffer(keyMaxLength, '0');
+ olm_outbound_group_session_key(olmOutboundGroupSession, reinterpret_cast<uint8_t *>(keyBuffer.data()),
+ keyMaxLength);
+
+ randomBuf.clear();
+
+ return std::make_unique<QOlmOutboundGroupSession>(olmOutboundGroupSession);
+}
+
+std::variant<QByteArray, QOlmError> QOlmOutboundGroupSession::pickle(const PicklingMode &mode)
+{
+ QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0');
+ QByteArray key = toKey(mode);
+ const auto error = olm_pickle_outbound_group_session(m_groupSession, key.data(), key.length(),
+ pickledBuf.data(), pickledBuf.length());
+
+ if (error == olm_error()) {
+ return lastError(m_groupSession);
+ }
+
+ key.clear();
+
+ return pickledBuf;
+}
+
+
+std::variant<std::unique_ptr<QOlmOutboundGroupSession>, QOlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode)
+{
+ QByteArray pickledBuf = pickled;
+ 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());
+ if (error == olm_error()) {
+ return lastError(olmOutboundGroupSession);
+ }
+ const auto idMaxLength = olm_outbound_group_session_id_length(olmOutboundGroupSession);
+ QByteArray idBuffer(idMaxLength, '0');
+ olm_outbound_group_session_id(olmOutboundGroupSession, reinterpret_cast<uint8_t *>(idBuffer.data()),
+ idBuffer.length());
+
+ key.clear();
+ return std::make_unique<QOlmOutboundGroupSession>(olmOutboundGroupSession);
+}
+
+std::variant<QByteArray, QOlmError> QOlmOutboundGroupSession::encrypt(const QString &plaintext)
+{
+ QByteArray plaintextBuf = plaintext.toUtf8();
+ const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length());
+ QByteArray messageBuf(messageMaxLength, '0');
+ const auto error = olm_group_encrypt(m_groupSession, reinterpret_cast<uint8_t *>(plaintextBuf.data()),
+ plaintextBuf.length(), reinterpret_cast<uint8_t *>(messageBuf.data()), messageBuf.length());
+
+ if (error == olm_error()) {
+ return lastError(m_groupSession);
+ }
+
+ return messageBuf;
+}
+
+uint32_t QOlmOutboundGroupSession::sessionMessageIndex() const
+{
+ return olm_outbound_group_session_message_index(m_groupSession);
+}
+
+QByteArray QOlmOutboundGroupSession::sessionId() const
+{
+ const auto idMaxLength = olm_outbound_group_session_id_length(m_groupSession);
+ QByteArray idBuffer(idMaxLength, '0');
+ const auto error = olm_outbound_group_session_id(m_groupSession, reinterpret_cast<uint8_t *>(idBuffer.data()),
+ idBuffer.length());
+ if (error == olm_error()) {
+ throw lastError(m_groupSession);
+ }
+ return idBuffer;
+}
+
+std::variant<QByteArray, QOlmError> 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);
+ if (error == olm_error()) {
+ return lastError(m_groupSession);
+ }
+ return keyBuffer;
+}
diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h
new file mode 100644
index 00000000..967f563f
--- /dev/null
+++ b/lib/e2ee/qolmoutboundsession.h
@@ -0,0 +1,54 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+
+#pragma once
+
+#include "olm/olm.h"
+#include "e2ee/qolmerrors.h"
+#include "e2ee/e2ee.h"
+#include <memory>
+
+namespace Quotient {
+
+
+//! An out-bound group session is responsible for encrypting outgoing
+//! communication in a Megolm session.
+class QOlmOutboundGroupSession
+{
+public:
+ ~QOlmOutboundGroupSession();
+ //! Creates a new instance of `QOlmOutboundGroupSession`.
+ //! Throw OlmError on errors
+ static std::unique_ptr<QOlmOutboundGroupSession> create();
+ //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64.
+ std::variant<QByteArray, QOlmError> 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);
+ //! Encrypts a plaintext message using the session.
+ std::variant<QByteArray, QOlmError> encrypt(const QString &plaintext);
+
+ //! Get the current message index for this session.
+ //!
+ //! Each message is sent with an increasing index; this returns the
+ //! index for the next message.
+ uint32_t sessionMessageIndex() const;
+
+ //! Get a base64-encoded identifier for this session.
+ QByteArray sessionId() const;
+
+ //! Get the base64-encoded current ratchet key for this session.
+ //!
+ //! 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;
+ QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession);
+private:
+ OlmOutboundGroupSession *m_groupSession;
+};
+
+using QOlmOutboundGroupSessionPtr = std::unique_ptr<QOlmOutboundGroupSession>;
+using OlmOutboundGroupSessionPtr = std::unique_ptr<OlmOutboundGroupSession>;
+}
diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp
new file mode 100644
index 00000000..69d8b431
--- /dev/null
+++ b/lib/e2ee/qolmsession.cpp
@@ -0,0 +1,253 @@
+// SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "qolmsession.h"
+#include "e2ee/qolmutils.h"
+#include "logging.h"
+#include <cstring>
+#include <QDebug>
+
+using namespace Quotient;
+
+QOlmError lastError(OlmSession* session) {
+ const std::string error_raw = olm_session_last_error(session);
+
+ return fromString(error_raw);
+}
+
+Quotient::QOlmSession::~QOlmSession()
+{
+ olm_clear_session(m_session);
+ delete[](reinterpret_cast<uint8_t *>(m_session));
+}
+
+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)
+{
+ if (preKeyMessage.type() != QOlmMessage::PreKey) {
+ qCCritical(E2EE) << "The message is not a pre-key in when creating inbound session" << BadMessageFormat;
+ }
+
+ const auto olmSession = create();
+
+ QByteArray oneTimeKeyMessageBuf = preKeyMessage.toCiphertext();
+ QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8();
+ size_t error = 0;
+ if (from) {
+ error = olm_create_inbound_session_from(olmSession, account->data(), theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length());
+ } else {
+ error = olm_create_inbound_session(olmSession, account->data(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length());
+ }
+
+ if (error == olm_error()) {
+ const auto lastErr = lastError(olmSession);
+ qCWarning(E2EE) << "Error when creating inbound session" << lastErr;
+ return lastErr;
+ }
+
+ return std::make_unique<QOlmSession>(olmSession);
+}
+
+std::variant<QOlmSessionPtr, QOlmError> 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)
+{
+ return createInbound(account, preKeyMessage, true, theirIdentityKey);
+}
+
+std::variant<QOlmSessionPtr, QOlmError> QOlmSession::createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey)
+{
+ auto *olmOutboundSession = create();
+ const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession);
+ QByteArray randomBuf = getRandom(randomLen);
+
+ QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8();
+ QByteArray theirOneTimeKeyBuf = theirOneTimeKey.toUtf8();
+ const auto error = olm_create_outbound_session(olmOutboundSession,
+ account->data(),
+ reinterpret_cast<uint8_t *>(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(),
+ reinterpret_cast<uint8_t *>(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(),
+ reinterpret_cast<uint8_t *>(randomBuf.data()), randomBuf.length());
+
+ if (error == olm_error()) {
+ const auto lastErr = lastError(olmOutboundSession);
+ if (lastErr == QOlmError::NotEnoughRandom) {
+ throw lastErr;
+ }
+ return lastErr;
+ }
+
+ randomBuf.clear();
+ return std::make_unique<QOlmSession>(olmOutboundSession);
+}
+
+std::variant<QByteArray, QOlmError> QOlmSession::pickle(const PicklingMode &mode)
+{
+ 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());
+
+ if (error == olm_error()) {
+ return lastError(m_session);
+ }
+
+ key.clear();
+
+ return pickledBuf;
+}
+
+std::variant<QOlmSessionPtr, QOlmError> QOlmSession::unpickle(const QByteArray &pickled, const PicklingMode &mode)
+{
+ QByteArray pickledBuf = pickled;
+ auto *olmSession = create();
+ QByteArray key = toKey(mode);
+ const auto error = olm_unpickle_session(olmSession, key.data(), key.length(),
+ pickledBuf.data(), pickledBuf.length());
+ if (error == olm_error()) {
+ return lastError(olmSession);
+ }
+
+ key.clear();
+ return std::make_unique<QOlmSession>(olmSession);
+}
+
+QOlmMessage QOlmSession::encrypt(const QString &plaintext)
+{
+ QByteArray plaintextBuf = plaintext.toUtf8();
+ const auto messageMaxLen = olm_encrypt_message_length(m_session, plaintextBuf.length());
+ QByteArray messageBuf(messageMaxLen, '0');
+ const auto messageType = encryptMessageType();
+ const auto randomLen = olm_encrypt_random_length(m_session);
+ QByteArray randomBuf = getRandom(randomLen);
+ const auto error = olm_encrypt(m_session,
+ reinterpret_cast<uint8_t *>(plaintextBuf.data()), plaintextBuf.length(),
+ reinterpret_cast<uint8_t *>(randomBuf.data()), randomBuf.length(),
+ reinterpret_cast<uint8_t *>(messageBuf.data()), messageBuf.length());
+
+ if (error == olm_error()) {
+ throw lastError(m_session);
+ }
+
+ return QOlmMessage(messageBuf, messageType);
+}
+
+std::variant<QString, QOlmError> QOlmSession::decrypt(const QOlmMessage &message) const
+{
+ const auto messageType = message.type();
+ const auto ciphertext = message.toCiphertext();
+ const auto messageTypeValue = messageType == QOlmMessage::Type::General
+ ? OLM_MESSAGE_TYPE_MESSAGE : OLM_MESSAGE_TYPE_PRE_KEY;
+
+ // We need to clone the message because
+ // olm_decrypt_max_plaintext_length destroys the input buffer
+ QByteArray messageBuf(ciphertext.length(), '0');
+ std::copy(message.begin(), message.end(), messageBuf.begin());
+
+ const auto plaintextMaxLen = olm_decrypt_max_plaintext_length(m_session, messageTypeValue,
+ reinterpret_cast<uint8_t *>(messageBuf.data()), messageBuf.length());
+
+ if (plaintextMaxLen == olm_error()) {
+ return lastError(m_session);
+ }
+
+ QByteArray plaintextBuf(plaintextMaxLen, '0');
+ QByteArray messageBuf2(ciphertext.length(), '0');
+ std::copy(message.begin(), message.end(), messageBuf2.begin());
+
+ const auto plaintextResultLen = olm_decrypt(m_session, messageTypeValue,
+ reinterpret_cast<uint8_t *>(messageBuf2.data()), messageBuf2.length(),
+ reinterpret_cast<uint8_t *>(plaintextBuf.data()), plaintextMaxLen);
+
+ if (plaintextResultLen == olm_error()) {
+ const auto lastErr = lastError(m_session);
+ if (lastErr == QOlmError::OutputBufferTooSmall) {
+ throw lastErr;
+ }
+ return lastErr;
+ }
+ QByteArray output(plaintextResultLen, '0');
+ std::memcpy(output.data(), plaintextBuf.data(), plaintextResultLen);
+ plaintextBuf.clear();
+ return output;
+}
+
+QOlmMessage::Type QOlmSession::encryptMessageType()
+{
+ const auto messageTypeResult = olm_encrypt_message_type(m_session);
+ if (messageTypeResult == olm_error()) {
+ throw lastError(m_session);
+ }
+ if (messageTypeResult == OLM_MESSAGE_TYPE_PRE_KEY) {
+ return QOlmMessage::PreKey;
+ }
+ return QOlmMessage::General;
+}
+
+QByteArray QOlmSession::sessionId() const
+{
+ const auto idMaxLength = olm_session_id_length(m_session);
+ QByteArray idBuffer(idMaxLength, '0');
+ const auto error = olm_session_id(m_session, reinterpret_cast<uint8_t *>(idBuffer.data()),
+ idBuffer.length());
+ if (error == olm_error()) {
+ throw lastError(m_session);
+ }
+ return idBuffer;
+}
+
+bool QOlmSession::hasReceivedMessage() const
+{
+ return olm_session_has_received_message(m_session);
+}
+
+std::variant<bool, QOlmError> 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());
+
+ if (matchesResult == olm_error()) {
+ return lastError(m_session);
+ }
+ switch (matchesResult) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ return QOlmError::Unknown;
+ }
+}
+std::variant<bool, QOlmError> 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;
+ }
+}
+
+QOlmSession::QOlmSession(OlmSession *session)
+ : m_session(session)
+{
+}
diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h
new file mode 100644
index 00000000..1febfa0f
--- /dev/null
+++ b/lib/e2ee/qolmsession.h
@@ -0,0 +1,76 @@
+// SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#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 {
+
+class QOlmAccount;
+class QOlmSession;
+
+
+//! Either an outbound or inbound session for secure communication.
+class 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 std::variant<std::unique_ptr<QOlmSession>, QOlmError> 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);
+ //! Serialises an `QOlmSession` to encrypted Base64.
+ std::variant<QByteArray, QOlmError> 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(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
+ //! the decrypted plaintext contains invalid UTF-8 symbols, they will
+ //! be returned as `U+FFFD` (�).
+ std::variant<QString, QOlmError> decrypt(const QOlmMessage &message) const;
+
+ //! Get a base64-encoded identifier for this session.
+ QByteArray sessionId() const;
+
+ //! The type of the next message that will be returned from encryption.
+ QOlmMessage::Type encryptMessageType();
+
+ //! Checker for any received messages for this session.
+ bool hasReceivedMessage() const;
+
+ //! Checks if the 'prekey' message is for this in-bound session.
+ std::variant<bool, QOlmError> matchesInboundSession(const QOlmMessage &preKeyMessage) const;
+
+ //! Checks if the 'prekey' message is for this in-bound session.
+ std::variant<bool, QOlmError> matchesInboundSessionFrom(const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) const;
+
+ friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs)
+ {
+ return lhs.sessionId() < rhs.sessionId();
+ }
+
+ friend bool operator<(const std::unique_ptr<QOlmSession> &lhs, const std::unique_ptr<QOlmSession> &rhs) {
+ return *lhs < *rhs;
+ }
+
+ OlmSession *raw() const
+ {
+ return m_session;
+ }
+ QOlmSession(OlmSession* session);
+private:
+ //! Helper function for creating new sessions and handling errors.
+ static OlmSession* create();
+ static std::variant<std::unique_ptr<QOlmSession>, QOlmError> createInbound(QOlmAccount *account, const QOlmMessage& preKeyMessage, bool from = false, const QString& theirIdentityKey = "");
+ OlmSession* m_session;
+};
+} //namespace Quotient
diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp
new file mode 100644
index 00000000..d0684055
--- /dev/null
+++ b/lib/e2ee/qolmutility.cpp
@@ -0,0 +1,63 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "e2ee/qolmutility.h"
+#include "olm/olm.h"
+#include <QDebug>
+
+using namespace Quotient;
+
+// Convert olm error to enum
+QOlmError lastError(OlmUtility *utility) {
+ const std::string error_raw = olm_utility_last_error(utility);
+
+ return fromString(error_raw);
+}
+
+QOlmUtility::QOlmUtility()
+{
+ auto utility = new uint8_t[olm_utility_size()];
+ m_utility = olm_utility(utility);
+}
+
+QOlmUtility::~QOlmUtility()
+{
+ olm_clear_utility(m_utility);
+ delete[](reinterpret_cast<uint8_t *>(m_utility));
+}
+
+QString QOlmUtility::sha256Bytes(const QByteArray &inputBuf) const
+{
+ const auto outputLen = olm_sha256_length(m_utility);
+ QByteArray outputBuf(outputLen, '0');
+ olm_sha256(m_utility, inputBuf.data(), inputBuf.length(),
+ outputBuf.data(), outputBuf.length());
+
+ return QString::fromUtf8(outputBuf);
+}
+
+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)
+{
+ QByteArray signatureBuf(signature.length(), '0');
+ std::copy(signature.begin(), signature.end(), signatureBuf.begin());
+
+ const auto ret = olm_ed25519_verify(m_utility, key.data(), key.size(),
+ message.data(), message.size(), (void *)signatureBuf.data(), signatureBuf.size());
+
+ const auto error = ret;
+ if (error == olm_error()) {
+ return lastError(m_utility);
+ }
+
+ if (ret != 0) {
+ return false;
+ }
+ return true;
+}
diff --git a/lib/e2ee/qolmutility.h b/lib/e2ee/qolmutility.h
new file mode 100644
index 00000000..b360d625
--- /dev/null
+++ b/lib/e2ee/qolmutility.h
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <QObject>
+#include <variant>
+#include "e2ee/qolmerrors.h"
+
+struct OlmUtility;
+
+namespace Quotient {
+
+class QOlmSession;
+class Connection;
+
+//! Allows you to make use of crytographic hashing via SHA-2 and
+//! verifying ed25519 signatures.
+class QOlmUtility
+{
+public:
+ QOlmUtility();
+ ~QOlmUtility();
+
+ //! Returns a sha256 of the supplied byte slice.
+ QString sha256Bytes(const QByteArray &inputBuf) const;
+
+ //! Convenience function that converts the UTF-8 message
+ //! to bytes and then calls `sha256Bytes()`, returning its output.
+ QString sha256Utf8Msg(const QString &message) const;
+
+ //! Verify a ed25519 signature.
+ //! \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,
+ const QByteArray &message, const QByteArray &signature);
+
+
+private:
+ OlmUtility *m_utility;
+
+};
+}
diff --git a/lib/e2ee/qolmutils.cpp b/lib/e2ee/qolmutils.cpp
new file mode 100644
index 00000000..ce27710d
--- /dev/null
+++ b/lib/e2ee/qolmutils.cpp
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "e2ee/qolmutils.h"
+#include <QDebug>
+#include <openssl/rand.h>
+
+using namespace Quotient;
+
+QByteArray Quotient::toKey(const Quotient::PicklingMode &mode)
+{
+ if (std::holds_alternative<Quotient::Unencrypted>(mode)) {
+ return {};
+ }
+ return std::get<Quotient::Encrypted>(mode).key;
+}
+
+QByteArray Quotient::getRandom(size_t bufferSize)
+{
+ QByteArray buffer(bufferSize, '0');
+ RAND_bytes(reinterpret_cast<uint8_t *>(buffer.data()), buffer.size());
+ return buffer;
+}
diff --git a/lib/e2ee/qolmutils.h b/lib/e2ee/qolmutils.h
new file mode 100644
index 00000000..bbd71332
--- /dev/null
+++ b/lib/e2ee/qolmutils.h
@@ -0,0 +1,15 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <QByteArray>
+
+#include "e2ee/e2ee.h"
+
+namespace Quotient {
+// Convert PicklingMode to key
+QByteArray toKey(const PicklingMode &mode);
+QByteArray getRandom(size_t bufferSize);
+}