diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | lib/olm/message.cpp | 35 | ||||
-rw-r--r-- | lib/olm/message.h | 46 | ||||
-rw-r--r-- | lib/olm/qolmaccount.cpp | 5 | ||||
-rw-r--r-- | lib/olm/qolmaccount.h | 4 | ||||
-rw-r--r-- | lib/olm/qolminboundsession.h | 1 | ||||
-rw-r--r-- | lib/olm/qolmoutboundsession.h | 1 | ||||
-rw-r--r-- | lib/olm/session.cpp | 155 | ||||
-rw-r--r-- | lib/olm/session.h | 46 |
9 files changed, 295 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index a359ae07..476b7d81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,6 +184,8 @@ list(APPEND lib_SRCS lib/olm/qolmoutboundsession.cpp lib/olm/utils.cpp lib/olm/errors.cpp + lib/olm/session.cpp + lib/olm/message.cpp ) # Configure API files generation diff --git a/lib/olm/message.cpp b/lib/olm/message.cpp new file mode 100644 index 00000000..0998a66b --- /dev/null +++ b/lib/olm/message.cpp @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru> +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "olm/message.h" + +using namespace Quotient; + +Message::Message(const QByteArray &ciphertext, Message::Type type) + : QByteArray(std::move(ciphertext)), _messageType(type) +{ + Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); +} + +Message::Message(QByteArray ciphertext) : QByteArray(std::move(ciphertext)) +{ + Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); +} + +Message::Type Message::type() const +{ + return _messageType; +} + +QByteArray Message::toCiphertext() const +{ + return QByteArray(*this); +} + + +#endif // Quotient_E2EE_ENABLED + + + diff --git a/lib/olm/message.h b/lib/olm/message.h new file mode 100644 index 00000000..6c8ab485 --- /dev/null +++ b/lib/olm/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 <QtCore/QObject> +#include <QtCore/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 : private QByteArray { + Q_GADGET +public: + enum Type { + General, + PreKey, + }; + Q_ENUM(Type) + + Message() = default; + explicit Message(const QByteArray& ciphertext, Type type = General); + explicit Message(QByteArray ciphertext); + + static Message fromCiphertext(QByteArray ciphertext); + + Q_INVOKABLE Type type() const; + Q_INVOKABLE QByteArray toCiphertext() const; + +private: + Type _messageType = General; +}; + + +} //namespace Quotient + +#endif // Quotient_E2EE_ENABLED diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp index 8872f66e..9530d675 100644 --- a/lib/olm/qolmaccount.cpp +++ b/lib/olm/qolmaccount.cpp @@ -192,4 +192,9 @@ QByteArray QOlmAccount::signOneTimeKey(const QString &key) const return sign(j.toJson()); } +OlmAccount *Quotient::QOlmAccount::data() +{ + return m_account; +} + #endif diff --git a/lib/olm/qolmaccount.h b/lib/olm/qolmaccount.h index 3b55212d..3260ca71 100644 --- a/lib/olm/qolmaccount.h +++ b/lib/olm/qolmaccount.h @@ -63,6 +63,10 @@ public: QByteArray signOneTimeKey(const QString &key) const; SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; + OlmAccount *data(); + + // HACK do not use directly + QOlmAccount(OlmAccount *account); private: OlmAccount *m_account = nullptr; QString m_userId; diff --git a/lib/olm/qolminboundsession.h b/lib/olm/qolminboundsession.h index eb698868..739a8411 100644 --- a/lib/olm/qolminboundsession.h +++ b/lib/olm/qolminboundsession.h @@ -46,5 +46,6 @@ private: }; using QOlmInboundGroupSessionPtr = std::unique_ptr<QOlmInboundGroupSession>; +using OlmInboundGroupSessionPtr = std::unique_ptr<OlmInboundGroupSession>; } // namespace Quotient #endif diff --git a/lib/olm/qolmoutboundsession.h b/lib/olm/qolmoutboundsession.h index a642f581..70c4d27f 100644 --- a/lib/olm/qolmoutboundsession.h +++ b/lib/olm/qolmoutboundsession.h @@ -49,5 +49,6 @@ private: }; using QOlmOutboundGroupSessionPtr = std::unique_ptr<QOlmOutboundGroupSession>; +using OlmOutboundGroupSessionPtr = std::unique_ptr<OlmOutboundGroupSession>; } #endif diff --git a/lib/olm/session.cpp b/lib/olm/session.cpp new file mode 100644 index 00000000..a2a7d28a --- /dev/null +++ b/lib/olm/session.cpp @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru> +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "olm/session.h" +#include "olm/utils.h" +#include "logging.h" + +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); +} + +OlmSession* QOlmSession::create() +{ + return olm_session(new uint8_t[olm_session_size()]); +} + +std::unique_ptr<QOlmSession> 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()) { + throw lastError(olmSession); + } + + return std::make_unique<QOlmSession>(olmSession); +} + +std::unique_ptr<QOlmSession> QOlmSession::createInboundSession(QOlmAccount& account, const Message &preKeyMessage) +{ + return createInbound(account, preKeyMessage); +} + +std::unique_ptr<QOlmSession> QOlmSession::createInboundSessionFrom(QOlmAccount &account, const QString &theirIdentityKey, const Message &preKeyMessage) +{ + return createInbound(account, preKeyMessage, true, theirIdentityKey); +} + +std::unique_ptr<QOlmSession> 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(), + theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), + theirOneTimeKeyBuf.data(), theirOneTimeKeyBuf.length(), + reinterpret_cast<uint8_t *>(randomBuf.data()), randomBuf.length()); + + if (error == olm_error()) { + throw lastError(olmOutboundSession); + } + + 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(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(), + pickled.data(), pickled.length()); + if (error == olm_error()) { + return lastError(olmSession); + } + + key.clear(); + return std::make_unique<QOlmSession>(olmSession); +} + +std::variant<Message, OlmError> 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 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()) { + return lastError(m_session); + } + + return Message::fromCiphertext(messageBuf); +} + +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; +} + +QOlmSession::QOlmSession(OlmSession *session): m_session(session) +{ + +} + +#endif // Quotient_E2EE_ENABLED + + + diff --git a/lib/olm/session.h b/lib/olm/session.h new file mode 100644 index 00000000..76c1df29 --- /dev/null +++ b/lib/olm/session.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 "olm/e2ee.h" +#include "olm/message.h" +#include "olm/errors.h" +#include "olm/qolmaccount.h" + +namespace Quotient { + +//! 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::unique_ptr<QOlmSession> createInboundSession(QOlmAccount& account, const Message& preKeyMessage); + static std::unique_ptr<QOlmSession> createInboundSessionFrom(QOlmAccount& account, const QString& theirIdentityKey, const Message& preKeyMessage); + static std::unique_ptr<QOlmSession> 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(QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + std::variant<Message, OlmError> encrypt(const QString &plaintext); + // TODO: WiP + + //! Get a base64-encoded identifier for this session. + QByteArray sessionId() const; + + QOlmSession(OlmSession* session); +private: + //! Helper function for creating new sessions and handling errors. + static OlmSession* create(); + static std::unique_ptr<QOlmSession> createInbound(QOlmAccount& account, const Message& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); + OlmSession* m_session; +}; + +} //namespace Quotient + +#endif // Quotient_E2EE_ENABLED |