aboutsummaryrefslogtreecommitdiff
path: root/lib/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'lib/crypto')
-rw-r--r--lib/crypto/e2ee.h82
-rw-r--r--lib/crypto/errors.cpp21
-rw-r--r--lib/crypto/errors.h31
-rw-r--r--lib/crypto/message.cpp42
-rw-r--r--lib/crypto/message.h46
-rw-r--r--lib/crypto/qolmaccount.cpp217
-rw-r--r--lib/crypto/qolmaccount.h96
-rw-r--r--lib/crypto/qolminboundsession.cpp157
-rw-r--r--lib/crypto/qolminboundsession.h51
-rw-r--r--lib/crypto/qolmoutboundsession.cpp131
-rw-r--r--lib/crypto/qolmoutboundsession.h54
-rw-r--r--lib/crypto/qolmsession.cpp29
-rw-r--r--lib/crypto/qolmsession.h49
-rw-r--r--lib/crypto/session.cpp242
-rw-r--r--lib/crypto/session.h77
-rw-r--r--lib/crypto/utils.cpp26
-rw-r--r--lib/crypto/utils.h15
17 files changed, 1366 insertions, 0 deletions
diff --git a/lib/crypto/e2ee.h b/lib/crypto/e2ee.h
new file mode 100644
index 00000000..74f876e4
--- /dev/null
+++ b/lib/crypto/e2ee.h
@@ -0,0 +1,82 @@
+// 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 "util.h"
+#include <optional>
+#include <string>
+#include <variant>
+#include <QMap>
+
+#include <QStringList>
+
+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>;
+
+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.
+struct SignedOneTimeKey
+{
+ //! 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.
+ QMap<QString, QMap<QString, QString>> signatures;
+};
+
+bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs);
+
+} // namespace Quotient
diff --git a/lib/crypto/errors.cpp b/lib/crypto/errors.cpp
new file mode 100644
index 00000000..00ff962d
--- /dev/null
+++ b/lib/crypto/errors.cpp
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+#ifdef Quotient_E2EE_ENABLED
+#include "crypto/errors.h"
+
+Quotient::OlmError Quotient::fromString(const std::string &error_raw) {
+ if (error_raw.compare("BAD_ACCOUNT_KEY")) {
+ return OlmError::BadAccountKey;
+ } else if (error_raw.compare("BAD_MESSAGE_KEY_ID")) {
+ return OlmError::BadMessageKeyId;
+ } else if (error_raw.compare("INVALID_BASE64")) {
+ return OlmError::InvalidBase64;
+ } else if (error_raw.compare("NOT_ENOUGH_RANDOM")) {
+ return OlmError::NotEnoughRandom;
+ } else if (error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) {
+ return OlmError::OutputBufferTooSmall;
+ } else {
+ return OlmError::Unknown;
+ }
+}
+#endif
diff --git a/lib/crypto/errors.h b/lib/crypto/errors.h
new file mode 100644
index 00000000..09d2a989
--- /dev/null
+++ b/lib/crypto/errors.h
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#ifdef Quotient_E2EE_ENABLED
+#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 OlmError
+{
+ BadAccountKey,
+ BadMessageFormat,
+ BadMessageKeyId,
+ BadMessageMac,
+ BadMessageVersion,
+ InvalidBase64,
+ NotEnoughRandom,
+ OutputBufferTooSmall,
+ UnknownMessageIndex,
+ Unknown,
+};
+
+OlmError fromString(const std::string &error_raw);
+
+} //namespace Quotient
+
+#endif
diff --git a/lib/crypto/message.cpp b/lib/crypto/message.cpp
new file mode 100644
index 00000000..830633bf
--- /dev/null
+++ b/lib/crypto/message.cpp
@@ -0,0 +1,42 @@
+// SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifdef Quotient_E2EE_ENABLED
+#include "crypto/message.h"
+
+using namespace Quotient;
+
+Message::Message(const QByteArray &ciphertext, Message::Type type)
+ : QByteArray(std::move(ciphertext))
+ , m_messageType(type)
+{
+ Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty");
+}
+
+Message::Message(const Message &message)
+ : QByteArray(message)
+ , m_messageType(message.type())
+{
+}
+
+Message::Type Message::type() const
+{
+ return m_messageType;
+}
+
+QByteArray Message::toCiphertext() const
+{
+ return QByteArray(*this);
+}
+
+Message Message::fromCiphertext(const QByteArray &ciphertext)
+{
+ return Message(ciphertext, Message::General);
+}
+
+
+#endif // Quotient_E2EE_ENABLED
+
+
+
diff --git a/lib/crypto/message.h b/lib/crypto/message.h
new file mode 100644
index 00000000..1ae19ba8
--- /dev/null
+++ b/lib/crypto/message.h
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#ifdef Quotient_E2EE_ENABLED
+
+#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 Message : public QByteArray {
+ Q_GADGET
+public:
+ enum Type {
+ General,
+ PreKey,
+ };
+ Q_ENUM(Type)
+
+ Message() = default;
+ explicit Message(const QByteArray &ciphertext, Type type = General);
+ explicit Message(const Message &message);
+
+ static Message fromCiphertext(const QByteArray &ciphertext);
+
+ Q_INVOKABLE Type type() const;
+ Q_INVOKABLE QByteArray toCiphertext() const;
+
+private:
+ Type m_messageType = General;
+};
+
+
+} //namespace Quotient
+
+#endif // Quotient_E2EE_ENABLED
diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp
new file mode 100644
index 00000000..8824e7ef
--- /dev/null
+++ b/lib/crypto/qolmaccount.cpp
@@ -0,0 +1,217 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifdef Quotient_E2EE_ENABLED
+#include "crypto/qolmaccount.h"
+#include "crypto/utils.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
+OlmError 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)
+ : 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);
+ }
+}
+
+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()) {
+ throw lastError(m_account);
+ }
+}
+
+std::variant<QByteArray, OlmError> 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
+{
+ const size_t signatureLength = olm_account_signature_length(m_account);
+ QByteArray signatureBuffer(signatureLength, '0');
+ const auto error = olm_account_sign(m_account, message.data(), message.length(),
+ signatureBuffer.data(), signatureLength);
+
+ if (error == olm_error()) {
+ throw lastError(m_account);
+ }
+ return signatureBuffer;
+}
+
+QByteArray QOlmAccount::signIdentityKeys() const
+{
+ const auto keys = identityKeys();
+ const QJsonObject j{ {Curve25519Key, QString(keys.curve25519)}, {Ed25519Key, QString(keys.ed25519)} };
+ QJsonDocument doc;
+ doc.setObject(j);
+ return sign(doc.toJson());
+
+}
+
+size_t QOlmAccount::maxNumberOfOneTimeKeys() const
+{
+ return olm_account_max_number_of_one_time_keys(m_account);
+}
+
+void QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const
+{
+ const size_t randomLen = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys);
+ QByteArray randomBuffer = getRandom(randomLen);
+ const auto error = olm_account_generate_one_time_keys(m_account, numberOfKeys, randomBuffer.data(), randomLen);
+
+ if (error == olm_error()) {
+ throw lastError(m_account);
+ }
+}
+
+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 QJsonValue &key1 : json.keys()) {
+ auto oneTimeKeyObject = json[key1.toString()].toObject();
+ auto keyMap = QMap<QString, QString>();
+ for (const QString &key2 : oneTimeKeyObject.keys()) {
+ keyMap[key2] = oneTimeKeyObject[key2].toString();
+ }
+ oneTimeKeys.keys[key1.toString()] = 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());
+}
+
+OlmAccount *Quotient::QOlmAccount::data()
+{
+ return m_account;
+}
+
+std::variant<std::unique_ptr<QOlmSession>, OlmError> QOlmAccount::createInboundSession(const Message &preKeyMessage)
+{
+ Q_ASSERT(preKeyMessage.type() == Message::PreKey);
+ return QOlmSession::createInboundSession(this, preKeyMessage);
+}
+
+std::variant<std::unique_ptr<QOlmSession>, OlmError> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const Message &preKeyMessage)
+{
+ Q_ASSERT(preKeyMessage.type() == Message::PreKey);
+ return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage);
+}
+
+std::variant<std::unique_ptr<QOlmSession>, OlmError> QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey)
+{
+ return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey);
+}
+
+#endif
diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h
new file mode 100644
index 00000000..f98d78ba
--- /dev/null
+++ b/lib/crypto/qolmaccount.h
@@ -0,0 +1,96 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+#pragma once
+#ifdef Quotient_E2EE_ENABLED
+
+#include "crypto/e2ee.h"
+#include "crypto/errors.h"
+#include "crypto/session.h"
+#include <olm/olm.h>
+#include <QObject>
+
+struct OlmAccount;
+
+namespace Quotient {
+
+class QOlmSession;
+
+//! An olm account manages all cryptographic keys used on a device.
+//! \code{.cpp}
+//! const auto olmAccount = new QOlmAccount(this);
+//! \endcode
+class QOlmAccount
+{
+public:
+ QOlmAccount(const QString &userId, const QString &deviceId);
+ ~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 &picked, const PicklingMode &mode);
+
+ //! Serialises an OlmAccount to encrypted Base64.
+ std::variant<QByteArray, OlmError> 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;
+
+ //! 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.
+ void generateOneTimeKeys(size_t numberOfKeys) const;
+
+ //! Gets the OlmAccount's one time keys formatted as JSON.
+ OneTimeKeys oneTimeKeys() const;
+
+ //! Sign all time key.
+ 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;
+
+ //! 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<std::unique_ptr<QOlmSession>, OlmError> createInboundSession(const Message &preKeyMessage);
+
+ //! Creates an inbound session for sending/receiving messages from a received 'prekey' message.
+ //!
+ //! \param theirIdentityKey - The identity key of an Olm account that
+ //! encrypted this Olm message.
+ std::variant<std::unique_ptr<QOlmSession>, OlmError> createInboundSessionFrom(const QByteArray &theirIdentityKey, const Message &preKeyMessage);
+
+ //! Creates an outbound session for sending messages to a specific
+ /// identity and one time key.
+ std::variant<std::unique_ptr<QOlmSession>, OlmError> createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey);
+
+ // HACK do not use directly
+ QOlmAccount(OlmAccount *account);
+ OlmAccount *data();
+private:
+ OlmAccount *m_account = nullptr;
+ QString m_userId;
+ QString m_deviceId;
+};
+
+} // namespace Quotient
+
+#endif
diff --git a/lib/crypto/qolminboundsession.cpp b/lib/crypto/qolminboundsession.cpp
new file mode 100644
index 00000000..539fdc51
--- /dev/null
+++ b/lib/crypto/qolminboundsession.cpp
@@ -0,0 +1,157 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifdef Quotient_E2EE_ENABLED
+#include "crypto/qolminboundsession.h"
+#include <iostream>
+#include <cstring>
+using namespace Quotient;
+
+OlmError lastError(OlmInboundGroupSession *session) {
+ const std::string error_raw = olm_inbound_group_session_last_error(session);
+
+ std::cout << error_raw;
+ 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>, OlmError> QOlmInboundGroupSession::unpickle(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>, OlmError> 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, OlmError> QOlmInboundGroupSession::exportSession(uint32_t messageIndex)
+{
+ const auto keyLen = olm_export_inbound_group_session_length(m_groupSession);
+ QByteArray keyBuf(keyLen, '0');
+ const auto error = olm_export_inbound_group_session(m_groupSession, reinterpret_cast<uint8_t *>(keyBuf.data()), keyLen, 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;
+}
+#endif
diff --git a/lib/crypto/qolminboundsession.h b/lib/crypto/qolminboundsession.h
new file mode 100644
index 00000000..a58fbbbc
--- /dev/null
+++ b/lib/crypto/qolminboundsession.h
@@ -0,0 +1,51 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#ifdef Quotient_E2EE_ENABLED
+
+#include <QByteArray>
+#include <variant>
+#include <memory>
+#include "olm/olm.h"
+#include "crypto/errors.h"
+#include "crypto/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>, OlmError> unpickle(QByteArray &picked, const PicklingMode &mode);
+ //! Decrypts ciphertext received for this group session.
+ std::variant<std::pair<QString, uint32_t>, OlmError> 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, OlmError> 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
+#endif
diff --git a/lib/crypto/qolmoutboundsession.cpp b/lib/crypto/qolmoutboundsession.cpp
new file mode 100644
index 00000000..3bfb0187
--- /dev/null
+++ b/lib/crypto/qolmoutboundsession.cpp
@@ -0,0 +1,131 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifdef Quotient_E2EE_ENABLED
+#include "crypto/qolmoutboundsession.h"
+#include "crypto/utils.h"
+
+using namespace Quotient;
+
+OlmError 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 randomLen = olm_init_outbound_group_session_random_length(olmOutboundGroupSession);
+ QByteArray randomBuf = getRandom(randomLen);
+
+ 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, OlmError> 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>, OlmError> 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, OlmError> QOlmOutboundGroupSession::encrypt(const QString &plaintext)
+{
+ QByteArray plaintextBuf = plaintext.toUtf8();
+ const auto messageMaxLen = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length());
+ QByteArray messageBuf(messageMaxLen, '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, OlmError> 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;
+}
+
+#endif
diff --git a/lib/crypto/qolmoutboundsession.h b/lib/crypto/qolmoutboundsession.h
new file mode 100644
index 00000000..6c6c635c
--- /dev/null
+++ b/lib/crypto/qolmoutboundsession.h
@@ -0,0 +1,54 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+#pragma once
+#ifdef Quotient_E2EE_ENABLED
+
+#include "olm/olm.h" // from Olm
+#include "crypto/errors.h"
+#include "crypto/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 an `QOlmOutboundGroupSession` to encrypted Base64.
+ std::variant<QByteArray, OlmError> pickle(const PicklingMode &mode);
+ //! Deserialises from encrypted Base64 that was previously obtained by
+ //! pickling a `QOlmOutboundGroupSession`.
+ static std::variant<std::unique_ptr<QOlmOutboundGroupSession>, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode);
+ //! Encrypts a plaintext message using the session.
+ std::variant<QByteArray, OlmError> 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, OlmError> sessionKey() const;
+ QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession);
+private:
+ OlmOutboundGroupSession *m_groupSession;
+};
+
+using QOlmOutboundGroupSessionPtr = std::unique_ptr<QOlmOutboundGroupSession>;
+using OlmOutboundGroupSessionPtr = std::unique_ptr<OlmOutboundGroupSession>;
+}
+#endif
diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp
new file mode 100644
index 00000000..afa42728
--- /dev/null
+++ b/lib/crypto/qolmsession.cpp
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "crypto/qolmsession.h"
+
+using namespace Quotient;
+
+std::optional<OlmMessage> fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext)
+{
+ if (messageType == OLM_MESSAGE_TYPE_PRE_KEY) {
+ return PreKeyMessage { ciphertext };
+ } else if (messageType == OLM_MESSAGE_TYPE_MESSAGE) {
+ return Message { ciphertext };
+ }
+ return std::nullopt;
+}
+
+std::pair<OlmMessageType, QByteArray> toPair(const OlmMessage &message)
+{
+ return std::visit([](auto &arg) {
+ using T = std::decay_t<decltype(arg)>;
+ if constexpr (std::is_same_v<T, Message>) {
+ return std::make_pair<OlmMessageType, QByteArray>(MessageType, QByteArray(arg.message));
+ } else if constexpr (std::is_same_v<T, PreKeyMessage>) {
+ return std::make_pair<OlmMessageType, QByteArray>(PreKeyType, QByteArray(arg.message));
+ }
+ }, message);
+}
diff --git a/lib/crypto/qolmsession.h b/lib/crypto/qolmsession.h
new file mode 100644
index 00000000..3be3c7fc
--- /dev/null
+++ b/lib/crypto/qolmsession.h
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <variant>
+#include <olm/olm.h>
+#include "crypto/e2ee.h"
+#include "crypto/errors.h"
+
+namespace Quotient {
+
+//! An encrypted Olm message.
+struct Message {
+ QByteArray message;
+};
+
+//! A encrypted Olm pre-key message.
+//!
+//! This message, unlike a normal Message, can be used to create new Olm sessions.
+struct PreKeyMessage
+{
+ QByteArray message;
+};
+
+enum OlmMessageType
+{
+ PreKeyType,
+ MessageType,
+};
+
+using OlmMessage = std::variant<Message, PreKeyMessage>;
+
+std::optional<OlmMessage> fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext);
+
+std::pair<OlmMessageType, QByteArray> toPair(const OlmMessage &message);
+
+//class QOlmSession
+//{
+// /// Creates an inbound session for sending/receiving messages from a received 'prekey' message.
+// static std::variant<std::unique_ptr<QOlmSession>, OlmError> createInboundSession(const QOlmAccount &account,
+// PreKeyMessage &message);
+//
+////private:
+// //static std::variant<std::unique_ptr<QOlmSession>, OlmError> createSessionWith(std::function<std::variant<size_t(OlmSession *)>> func);
+//}
+
+}
diff --git a/lib/crypto/session.cpp b/lib/crypto/session.cpp
new file mode 100644
index 00000000..8b2cb022
--- /dev/null
+++ b/lib/crypto/session.cpp
@@ -0,0 +1,242 @@
+// SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifdef Quotient_E2EE_ENABLED
+#include "crypto/session.h"
+#include "crypto/utils.h"
+#include "logging.h"
+#include <cstring>
+
+using namespace Quotient;
+
+OlmError 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<std::unique_ptr<QOlmSession>, OlmError> QOlmSession::createInbound(QOlmAccount *account, const Message &preKeyMessage, bool from, const QString &theirIdentityKey)
+{
+ if (preKeyMessage.type() != Message::PreKey) {
+ qCDebug(E2EE) << "The message is not a pre-key";
+ throw 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);
+ if (lastErr == OlmError::NotEnoughRandom) {
+ throw lastErr;
+ }
+ return lastErr;
+ }
+
+ return std::make_unique<QOlmSession>(olmSession);
+}
+
+std::variant<std::unique_ptr<QOlmSession>, OlmError> QOlmSession::createInboundSession(QOlmAccount *account, const Message &preKeyMessage)
+{
+ return createInbound(account, preKeyMessage);
+}
+
+std::variant<std::unique_ptr<QOlmSession>, OlmError> QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage)
+{
+ return createInbound(account, preKeyMessage, true, theirIdentityKey);
+}
+
+std::variant<std::unique_ptr<QOlmSession>, OlmError> 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 == OlmError::NotEnoughRandom) {
+ throw lastErr;
+ }
+ return lastErr;
+ }
+
+ randomBuf.clear();
+ return std::make_unique<QOlmSession>(olmOutboundSession);
+}
+
+std::variant<QByteArray, OlmError> 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<std::unique_ptr<QOlmSession>, OlmError> 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);
+}
+
+Message 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 Message(messageBuf, messageType);
+}
+
+std::variant<QString, OlmError> QOlmSession::decrypt(const Message &message) const
+{
+ const auto messageType = message.type();
+ const auto ciphertext = message.toCiphertext();
+ const auto messageTypeValue = messageType == Message::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 == OlmError::OutputBufferTooSmall) {
+ throw lastErr;
+ }
+ return lastErr;
+ }
+ QByteArray output(plaintextResultLen, '0');
+ std::memcpy(output.data(), plaintextBuf.data(), plaintextResultLen);
+ plaintextBuf.clear();
+ return output;
+}
+
+Message::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 Message::PreKey;
+ }
+ return Message::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, OlmError> QOlmSession::matchesInboundSession(Message &preKeyMessage)
+{
+ Q_ASSERT(preKeyMessage.type() == Message::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 OlmError::Unknown;
+ }
+}
+
+QOlmSession::QOlmSession(OlmSession *session)
+ : m_session(session)
+{
+}
+
+#endif // Quotient_E2EE_ENABLED
+
+
+
diff --git a/lib/crypto/session.h b/lib/crypto/session.h
new file mode 100644
index 00000000..24702564
--- /dev/null
+++ b/lib/crypto/session.h
@@ -0,0 +1,77 @@
+// SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#ifdef Quotient_E2EE_ENABLED
+
+#include <QDebug>
+#include <olm/olm.h>
+#include "crypto/e2ee.h"
+#include "crypto/message.h"
+#include "crypto/errors.h"
+#include "crypto/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>, OlmError> createInboundSession(QOlmAccount *account, const Message &preKeyMessage);
+ static std::variant<std::unique_ptr<QOlmSession>, OlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage);
+ static std::variant<std::unique_ptr<QOlmSession>, OlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey);
+ //! Serialises an `QOlmSession` to encrypted Base64.
+ std::variant<QByteArray, OlmError> pickle(const PicklingMode &mode);
+ //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`.
+ static std::variant<std::unique_ptr<QOlmSession>, OlmError> unpickle(const QByteArray &pickled, const PicklingMode &mode);
+ //! Encrypts a plaintext message using the session.
+ Message 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, OlmError> decrypt(const Message &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.
+ Message::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, OlmError> matchesInboundSession(Message &preKeyMessage);
+
+ 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;
+ }
+
+ QOlmSession(OlmSession* session);
+private:
+ //! Helper function for creating new sessions and handling errors.
+ static OlmSession* create();
+ static std::variant<std::unique_ptr<QOlmSession>, OlmError> createInbound(QOlmAccount *account, const Message& preKeyMessage, bool from = false, const QString& theirIdentityKey = "");
+ OlmSession* m_session;
+};
+
+
+//using QOlmSessionPtr = std::unique_ptr<QOlmSession>;
+
+} //namespace Quotient
+
+#endif // Quotient_E2EE_ENABLED
diff --git a/lib/crypto/utils.cpp b/lib/crypto/utils.cpp
new file mode 100644
index 00000000..cb20abf8
--- /dev/null
+++ b/lib/crypto/utils.cpp
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifdef Quotient_E2EE_ENABLED
+#include "crypto/utils.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;
+}
+#endif
diff --git a/lib/crypto/utils.h b/lib/crypto/utils.h
new file mode 100644
index 00000000..cea87144
--- /dev/null
+++ b/lib/crypto/utils.h
@@ -0,0 +1,15 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+#ifdef Quotient_E2EE_ENABLED
+
+#include "crypto/e2ee.h"
+
+namespace Quotient {
+// Convert PicklingMode to key
+QByteArray toKey(const PicklingMode &mode);
+QByteArray getRandom(size_t bufferSize);
+}
+#endif