diff options
author | Carl Schwan <carl@carlschwan.eu> | 2021-01-24 01:45:43 +0100 |
---|---|---|
committer | Tobias Fella <fella@posteo.de> | 2021-12-01 21:34:52 +0100 |
commit | 8706c055e69b01097b702403aaa0d222e5ab0d29 (patch) | |
tree | 063534b589d598a14af2018c516ce5eb992399ff | |
parent | c8d67f737e84bbec98a54fc19a8aa56dbc39d542 (diff) | |
download | libquotient-8706c055e69b01097b702403aaa0d222e5ab0d29.tar.gz libquotient-8706c055e69b01097b702403aaa0d222e5ab0d29.zip |
Implement outboundsession
-rw-r--r-- | CMakeLists.txt | 4 | ||||
-rw-r--r-- | autotests/CMakeLists.txt | 1 | ||||
-rw-r--r-- | autotests/testgroupsession.cpp | 39 | ||||
-rw-r--r-- | autotests/testgroupsession.h | 15 | ||||
-rw-r--r-- | autotests/testolmaccount.cpp | 2 | ||||
-rw-r--r-- | lib/olm/errors.h | 2 | ||||
-rw-r--r-- | lib/olm/qolmaccount.cpp | 27 | ||||
-rw-r--r-- | lib/olm/qolminboundsession.cpp | 51 | ||||
-rw-r--r-- | lib/olm/qolminboundsession.h | 9 | ||||
-rw-r--r-- | lib/olm/qolmoutboundsession.cpp | 121 | ||||
-rw-r--r-- | lib/olm/qolmoutboundsession.h | 47 | ||||
-rw-r--r-- | lib/olm/utils.cpp | 22 | ||||
-rw-r--r-- | lib/olm/utils.h | 13 |
13 files changed, 291 insertions, 62 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b4d4ef8b..18a7b622 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,10 @@ list(APPEND lib_SRCS lib/jobs/mediathumbnailjob.cpp lib/jobs/downloadfilejob.cpp lib/olm/qolmaccount.cpp + lib/olm/qolminboundsession.cpp + lib/olm/qolmoutboundsession.cpp + lib/olm/utils.cpp + lib/olm/errors.cpp ) # Configure API files generation diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 07c22ad6..31cdb446 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -13,3 +13,4 @@ endfunction() quotient_add_test(NAME callcandidateseventtest) quotient_add_test(NAME testolmaccount) +quotient_add_test(NAME testgroupsession) diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp new file mode 100644 index 00000000..02892366 --- /dev/null +++ b/autotests/testgroupsession.cpp @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "testgroupsession.h" +#include "olm/qolminboundsession.h" +#include "olm/qolmoutboundsession.h" +#include "olm/utils.h" + +using namespace Quotient; + +void TestOlmSession::groupSessionPicklingValid() +{ + auto ogs = std::get<QOlmOutboundGroupSession>(QOlmOutboundGroupSession::create()); + const auto ogsId = std::get<QByteArray>(ogs.sessionId()); + QVERIFY(QByteArray::fromBase64Encoding(ogsId).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + QCOMPARE(0, ogs.sessionMessageIndex()); + + auto ogsPickled = std::get<QByteArray>(ogs.pickle(Unencrypted {})); + ogs = std::get<QOlmOutboundGroupSession>(QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {})); + QCOMPARE(ogsId, std::get<QByteArray>(ogs.sessionId())); + + qDebug() << std::get<QByteArray>(ogs.sessionKey()); + auto igs = std::get<QOlmInboundGroupSession>(QOlmInboundGroupSession::create(std::get<QByteArray>(ogs.sessionKey()))); + const auto igsId = std::get<QByteArray>(igs.sessionId()); + // ID is valid base64? + QVERIFY(QByteArray::fromBase64Encoding(igsId).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + + //// no messages have been sent yet + QCOMPARE(0, igs.firstKnownIndex()); + + auto igsPickled = std::get<QByteArray>(igs.pickle(Unencrypted {})); + igs = std::get<QOlmInboundGroupSession>(QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {})); + QCOMPARE(igsId, std::get<QByteArray>(igs.sessionId())); +} + +QTEST_MAIN(TestOlmSession) +#endif diff --git a/autotests/testgroupsession.h b/autotests/testgroupsession.h new file mode 100644 index 00000000..28ebf4c9 --- /dev/null +++ b/autotests/testgroupsession.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include <QtTest/QtTest> + +class TestOlmSession : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void groupSessionPicklingValid(); +}; +#endif diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 549f07ea..45a7e3a5 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -6,6 +6,8 @@ #include "testolmaccount.h" #include "olm/qolmaccount.h" +using namespace Quotient; + void TestOlmAccount::pickleUnpickedTest() { auto olmAccount = QOlmAccount::create().value(); diff --git a/lib/olm/errors.h b/lib/olm/errors.h index fc2ae2e9..3dc4c796 100644 --- a/lib/olm/errors.h +++ b/lib/olm/errors.h @@ -24,6 +24,8 @@ enum OlmError Unknown, }; +OlmError fromString(const std::string &error_raw); + } //namespace Quotient #endif diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp index bde9b712..89d82832 100644 --- a/lib/olm/qolmaccount.cpp +++ b/lib/olm/qolmaccount.cpp @@ -4,6 +4,7 @@ #ifdef Quotient_E2EE_ENABLED #include "qolmaccount.h" +#include "olm/utils.h" #include <QJsonObject> #include <QJsonDocument> #include <QDebug> @@ -24,37 +25,11 @@ std::optional<QMap<QString, QString>> OneTimeKeys::get(QString keyType) const return keys[keyType]; } -// Convert PicklingMode to key -QByteArray toKey(const PicklingMode &mode) -{ - if (std::holds_alternative<Unencrypted>(mode)) { - return ""; - } - return std::get<Encrypted>(mode).key; -} - bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) { return lhs.curve25519 == rhs.curve25519 &&& lhs.ed25519 == rhs.ed25519; } -// TODO use impl from errors.cpp -OlmError 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; - } -} - // Conver olm error to enum OlmError lastError(OlmAccount *account) { const std::string error_raw = olm_account_last_error(account); diff --git a/lib/olm/qolminboundsession.cpp b/lib/olm/qolminboundsession.cpp index 62de138f..37dd60f8 100644 --- a/lib/olm/qolminboundsession.cpp +++ b/lib/olm/qolminboundsession.cpp @@ -3,64 +3,51 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "olm/qolminboundsession.h" -#include <QString> -#include <QByteArray> - +#include <QDebug> +#include <iostream> using namespace Quotient; -// TODO move to errors.cpp -OlmError 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; - } -} - 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, const QByteArray &buffer) +QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session, QByteArray buffer) : m_groupSession(session) , m_buffer(buffer) { } -std::variant<QOlmInboundGroupSession, OlmError> QOlmInboundGroupSession::create(const QString &key) +QOlmInboundGroupSession::~QOlmInboundGroupSession() { - auto olmInboundGroupSessionBuf = QByteArray(olm_inbound_group_session_size(), '0'); + olm_clear_inbound_group_session(m_groupSession); +} +std::variant<QOlmInboundGroupSession, OlmError> QOlmInboundGroupSession::create(const QByteArray &key) +{ + QByteArray olmInboundGroupSessionBuf(olm_inbound_group_session_size(), '0'); const auto olmInboundGroupSession = olm_inbound_group_session(olmInboundGroupSessionBuf.data()); - QByteArray keyBuf = key.toUtf8(); + const auto temp = key; const auto error = olm_init_inbound_group_session(olmInboundGroupSession, - reinterpret_cast<const uint8_t *>(keyBuf.data()), keyBuf.size()); + reinterpret_cast<const uint8_t *>(temp.data()), temp.size()); if (error == olm_error()) { return lastError(olmInboundGroupSession); } - return QOlmInboundGroupSession(olmInboundGroupSession, olmInboundGroupSessionBuf); + return QOlmInboundGroupSession(olmInboundGroupSession, std::move(olmInboundGroupSessionBuf)); } -std::variant<QOlmInboundGroupSession, OlmError> QOlmInboundGroupSession::import(const QString &key) +std::variant<QOlmInboundGroupSession, OlmError> QOlmInboundGroupSession::import(const QByteArray &key) { - auto olmInboundGroupSessionBuf = QByteArray(olm_inbound_group_session_size(), '0'); + QByteArray olmInboundGroupSessionBuf(olm_inbound_group_session_size(), '0'); const auto olmInboundGroupSession = olm_inbound_group_session(olmInboundGroupSessionBuf.data()); - QByteArray keyBuf = key.toUtf8(); + QByteArray keyBuf = key; const auto error = olm_import_inbound_group_session(olmInboundGroupSession, reinterpret_cast<const uint8_t *>(keyBuf.data()), keyBuf.size()); @@ -68,7 +55,7 @@ std::variant<QOlmInboundGroupSession, OlmError> QOlmInboundGroupSession::import( return lastError(olmInboundGroupSession); } - return QOlmInboundGroupSession(olmInboundGroupSession, olmInboundGroupSessionBuf); + return QOlmInboundGroupSession(olmInboundGroupSession, std::move(olmInboundGroupSessionBuf)); } QByteArray toKey(const PicklingMode &mode) @@ -91,7 +78,7 @@ std::variant<QByteArray, OlmError> QOlmInboundGroupSession::pickle(const Picklin return pickledBuf; } -std::variant<QOlmInboundGroupSession, OlmError> QOlmInboundGroupSession::unpicke(QByteArray &picked, const PicklingMode &mode) +std::variant<QOlmInboundGroupSession, OlmError> QOlmInboundGroupSession::unpickle(QByteArray &picked, const PicklingMode &mode) { QByteArray groupSessionBuf(olm_inbound_group_session_size(), '0'); auto groupSession = olm_inbound_group_session(groupSessionBuf.data()); @@ -100,7 +87,7 @@ std::variant<QOlmInboundGroupSession, OlmError> QOlmInboundGroupSession::unpicke if (error == olm_error()) { return lastError(groupSession); } - return QOlmInboundGroupSession(groupSession, groupSessionBuf); + return QOlmInboundGroupSession(groupSession, std::move(groupSessionBuf)); } std::variant<std::pair<QString, uint32_t>, OlmError> QOlmInboundGroupSession::decrypt(QString &message) diff --git a/lib/olm/qolminboundsession.h b/lib/olm/qolminboundsession.h index c75b7285..82802520 100644 --- a/lib/olm/qolminboundsession.h +++ b/lib/olm/qolminboundsession.h @@ -17,15 +17,16 @@ namespace Quotient { struct QOlmInboundGroupSession { public: + ~QOlmInboundGroupSession(); //! Creates a new instance of `OlmInboundGroupSession`. - static std::variant<QOlmInboundGroupSession, OlmError> create(const QString &key); + static std::variant<QOlmInboundGroupSession, OlmError> create(const QByteArray &key); //! Import an inbound group session, from a previous export. - static std::variant<QOlmInboundGroupSession, OlmError> import(const QString &key); + static std::variant<QOlmInboundGroupSession, OlmError> import(const QByteArray &key); //! Serialises an `OlmInboundGroupSession` to encrypted Base64. std::variant<QByteArray, OlmError> pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. - static std::variant<QOlmInboundGroupSession, OlmError> unpicke(QByteArray &picked, const PicklingMode &mode); + static std::variant<QOlmInboundGroupSession, OlmError> unpickle(QByteArray &picked, const PicklingMode &mode); //! Decrypts ciphertext received for this group session. std::variant<std::pair<QString, uint32_t>, OlmError> decrypt(QString &message); //! Export the base64-encoded ratchet key for this session, at the given index, @@ -37,7 +38,7 @@ public: std::variant<QByteArray, OlmError> sessionId() const; bool isVerified() const; private: - QOlmInboundGroupSession(OlmInboundGroupSession *session, const QByteArray &buffer); + QOlmInboundGroupSession(OlmInboundGroupSession *session, QByteArray buffer); OlmInboundGroupSession *m_groupSession; QByteArray m_buffer; }; diff --git a/lib/olm/qolmoutboundsession.cpp b/lib/olm/qolmoutboundsession.cpp new file mode 100644 index 00000000..8a6b966b --- /dev/null +++ b/lib/olm/qolmoutboundsession.cpp @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> +// +// SPDX-License-Identifier: LGPL-2.1-or-later +// +#include "olm/qolmoutboundsession.h" +#include "olm/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, const QByteArray &buffer) + : m_groupSession(session) + , m_buffer(buffer) +{ +} + +QOlmOutboundGroupSession::~QOlmOutboundGroupSession() +{ + olm_clear_outbound_group_session(m_groupSession); +} + +std::variant<QOlmOutboundGroupSession, OlmError> QOlmOutboundGroupSession::create() +{ + QByteArray sessionBuffer(olm_outbound_group_session_size(), '0'); + auto *olmOutboundGroupSession = olm_outbound_group_session(sessionBuffer.data()); + 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()) { + return lastError(olmOutboundGroupSession); + } + + randomBuf.clear(); + + return QOlmOutboundGroupSession(olmOutboundGroupSession, sessionBuffer); +} + +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<QOlmOutboundGroupSession, OlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +{ + QByteArray pickledBuf = pickled; + QByteArray olmOutboundGroupSessionBuf(olm_outbound_group_session_size(), '0'); + QByteArray key = toKey(mode); + auto olmOutboundGroupSession = olm_outbound_group_session(reinterpret_cast<uint8_t *>(olmOutboundGroupSessionBuf.data())); + const auto error = olm_unpickle_outbound_group_session(olmOutboundGroupSession, key.data(), key.length(), + pickled.data(), pickled.length()); + if (error == olm_error()) { + return lastError(olmOutboundGroupSession); + } + + key.clear(); + return QOlmOutboundGroupSession(olmOutboundGroupSession, olmOutboundGroupSessionBuf); +} + +std::variant<QString, OlmError> QOlmOutboundGroupSession::encrypt(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); +} + +std::variant<QByteArray, OlmError> 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()) { + return 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; +} diff --git a/lib/olm/qolmoutboundsession.h b/lib/olm/qolmoutboundsession.h new file mode 100644 index 00000000..147c0e03 --- /dev/null +++ b/lib/olm/qolmoutboundsession.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> +// +// SPDX-License-Identifier: LGPL-2.1-or-later +#pragma once + +#include "olm/olm.h" // from Olm +#include "olm/errors.h" +#include "olm/e2ee.h" + +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`. + static std::variant<QOlmOutboundGroupSession, OlmError> 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 an `QOlmOutboundGroupSession`. + static std::variant<QOlmOutboundGroupSession, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + std::variant<QString, OlmError> encrypt(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. + std::variant<QByteArray, OlmError> 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; +private: + QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession, const QByteArray &groupSessionBuf); + OlmOutboundGroupSession *m_groupSession; + QByteArray m_buffer; +}; +} diff --git a/lib/olm/utils.cpp b/lib/olm/utils.cpp new file mode 100644 index 00000000..4966af15 --- /dev/null +++ b/lib/olm/utils.cpp @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "olm/utils.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'); + std::generate(buffer.begin(), buffer.end(), std::rand); + return buffer; +} diff --git a/lib/olm/utils.h b/lib/olm/utils.h new file mode 100644 index 00000000..ec0da784 --- /dev/null +++ b/lib/olm/utils.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "olm/e2ee.h" + +namespace Quotient { +// Convert PicklingMode to key +QByteArray toKey(const PicklingMode &mode); +QByteArray getRandom(size_t bufferSize); +} |