From d9286b1ad5516082bc9b40adaceb9485acf4a553 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 22 Jan 2021 23:38:32 +0100 Subject: Add tests --- autotests/CMakeLists.txt | 1 + autotests/testolmaccount.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++++ autotests/testolmaccount.h | 19 +++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 autotests/testolmaccount.cpp create mode 100644 autotests/testolmaccount.h (limited to 'autotests') diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 282ab036..07c22ad6 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -12,3 +12,4 @@ function(QUOTIENT_ADD_TEST) endfunction() quotient_add_test(NAME callcandidateseventtest) +quotient_add_test(NAME testolmaccount) diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp new file mode 100644 index 00000000..549f07ea --- /dev/null +++ b/autotests/testolmaccount.cpp @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "testolmaccount.h" +#include "olm/qolmaccount.h" + +void TestOlmAccount::pickleUnpickedTest() +{ + auto olmAccount = QOlmAccount::create().value(); + auto identityKeys = std::get(olmAccount.identityKeys()); + auto pickled = std::get(olmAccount.pickle(Unencrypted{})); + auto olmAccount2 = std::get(QOlmAccount::unpickle(pickled, Unencrypted{})); + auto identityKeys2 = std::get(olmAccount2.identityKeys()); + QCOMPARE(identityKeys.curve25519, identityKeys2.curve25519); + QCOMPARE(identityKeys.ed25519, identityKeys2.ed25519); +} + +void TestOlmAccount::identityKeysValid() +{ + auto olmAccount = QOlmAccount::create().value(); + const auto identityKeys = std::get(olmAccount.identityKeys()); + const auto curve25519 = identityKeys.curve25519; + const auto ed25519 = identityKeys.ed25519; + // verify encoded keys length + QCOMPARE(curve25519.size(), 43); + QCOMPARE(ed25519.size(), 43); + + // encoded as valid base64? + QVERIFY(QByteArray::fromBase64Encoding(curve25519).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + QVERIFY(QByteArray::fromBase64Encoding(ed25519).decodingStatus == QByteArray::Base64DecodingStatus::Ok); +} + +void TestOlmAccount::signatureValid() +{ + const auto olmAccount = QOlmAccount::create().value(); + const auto message = "Hello world!"; + const auto signature = std::get(olmAccount.sign(message)); + QVERIFY(QByteArray::fromBase64Encoding(signature.toUtf8()).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + + //let utility = OlmUtility::new(); + //let identity_keys = olm_account.parsed_identity_keys(); + //let ed25519_key = identity_keys.ed25519(); + //assert!(utility + // .ed25519_verify(&ed25519_key, message, &signature) + // .unwrap()); +} + +void TestOlmAccount::oneTimeKeysValid() +{ + const auto olmAccount = QOlmAccount::create().value(); + const auto maxNumberOfOneTimeKeys = olmAccount.maxNumberOfOneTimeKeys(); + QCOMPARE(100, maxNumberOfOneTimeKeys); + + const auto oneTimeKeysEmpty = std::get(olmAccount.oneTimeKeys()); + QVERIFY(oneTimeKeysEmpty.curve25519().isEmpty()); + + olmAccount.generateOneTimeKeys(20); + const auto oneTimeKeysFilled = std::get(olmAccount.oneTimeKeys()); + QCOMPARE(20, oneTimeKeysFilled.curve25519().count()); +} + +QTEST_MAIN(TestOlmAccount) +#endif diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h new file mode 100644 index 00000000..c3297b5f --- /dev/null +++ b/autotests/testolmaccount.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include + +class TestOlmAccount : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void pickleUnpickedTest(); + void identityKeysValid(); + void signatureValid(); + void oneTimeKeysValid(); + //void removeOneTimeKeys(); +}; +#endif -- cgit v1.2.3 From 8706c055e69b01097b702403aaa0d222e5ab0d29 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 24 Jan 2021 01:45:43 +0100 Subject: Implement outboundsession --- CMakeLists.txt | 4 ++ autotests/CMakeLists.txt | 1 + autotests/testgroupsession.cpp | 39 +++++++++++++ autotests/testgroupsession.h | 15 +++++ autotests/testolmaccount.cpp | 2 + lib/olm/errors.h | 2 + lib/olm/qolmaccount.cpp | 27 +-------- lib/olm/qolminboundsession.cpp | 51 +++++++---------- lib/olm/qolminboundsession.h | 9 +-- lib/olm/qolmoutboundsession.cpp | 121 ++++++++++++++++++++++++++++++++++++++++ lib/olm/qolmoutboundsession.h | 47 ++++++++++++++++ lib/olm/utils.cpp | 22 ++++++++ lib/olm/utils.h | 13 +++++ 13 files changed, 291 insertions(+), 62 deletions(-) create mode 100644 autotests/testgroupsession.cpp create mode 100644 autotests/testgroupsession.h create mode 100644 lib/olm/qolmoutboundsession.cpp create mode 100644 lib/olm/qolmoutboundsession.h create mode 100644 lib/olm/utils.cpp create mode 100644 lib/olm/utils.h (limited to 'autotests') 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 +// +// 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::create()); + const auto ogsId = std::get(ogs.sessionId()); + QVERIFY(QByteArray::fromBase64Encoding(ogsId).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + QCOMPARE(0, ogs.sessionMessageIndex()); + + auto ogsPickled = std::get(ogs.pickle(Unencrypted {})); + ogs = std::get(QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {})); + QCOMPARE(ogsId, std::get(ogs.sessionId())); + + qDebug() << std::get(ogs.sessionKey()); + auto igs = std::get(QOlmInboundGroupSession::create(std::get(ogs.sessionKey()))); + const auto igsId = std::get(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(igs.pickle(Unencrypted {})); + igs = std::get(QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {})); + QCOMPARE(igsId, std::get(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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include + +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 #include #include @@ -24,37 +25,11 @@ std::optional> OneTimeKeys::get(QString keyType) const return keys[keyType]; } -// Convert PicklingMode to key -QByteArray toKey(const PicklingMode &mode) -{ - if (std::holds_alternative(mode)) { - return ""; - } - return std::get(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 -#include - +#include +#include 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::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::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(keyBuf.data()), keyBuf.size()); + reinterpret_cast(temp.data()), temp.size()); if (error == olm_error()) { return lastError(olmInboundGroupSession); } - return QOlmInboundGroupSession(olmInboundGroupSession, olmInboundGroupSessionBuf); + return QOlmInboundGroupSession(olmInboundGroupSession, std::move(olmInboundGroupSessionBuf)); } -std::variant QOlmInboundGroupSession::import(const QString &key) +std::variant 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(keyBuf.data()), keyBuf.size()); @@ -68,7 +55,7 @@ std::variant 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 QOlmInboundGroupSession::pickle(const Picklin return pickledBuf; } -std::variant QOlmInboundGroupSession::unpicke(QByteArray &picked, const PicklingMode &mode) +std::variant 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::unpicke if (error == olm_error()) { return lastError(groupSession); } - return QOlmInboundGroupSession(groupSession, groupSessionBuf); + return QOlmInboundGroupSession(groupSession, std::move(groupSessionBuf)); } std::variant, 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 create(const QString &key); + static std::variant create(const QByteArray &key); //! Import an inbound group session, from a previous export. - static std::variant import(const QString &key); + static std::variant import(const QByteArray &key); //! Serialises an `OlmInboundGroupSession` to encrypted Base64. std::variant pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. - static std::variant unpicke(QByteArray &picked, const PicklingMode &mode); + static std::variant unpickle(QByteArray &picked, const PicklingMode &mode); //! Decrypts ciphertext received for this group session. std::variant, OlmError> decrypt(QString &message); //! Export the base64-encoded ratchet key for this session, at the given index, @@ -37,7 +38,7 @@ public: std::variant 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 +// +// 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::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(randomBuf.data()), randomBuf.length()); + + if (error == olm_error()) { + return lastError(olmOutboundGroupSession); + } + + randomBuf.clear(); + + return QOlmOutboundGroupSession(olmOutboundGroupSession, sessionBuffer); +} + +std::variant 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::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(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 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(plaintextBuf.data()), + plaintextBuf.length(), reinterpret_cast(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 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(idBuffer.data()), + idBuffer.length()); + if (error == olm_error()) { + return lastError(m_groupSession); + } + return idBuffer; +} + +std::variant 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(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 +// +// 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 create(); + //! Serialises an `QOlmOutboundGroupSession` to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by + //! pickling an `QOlmOutboundGroupSession`. + static std::variant unpickle(QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + std::variant 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 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 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 +// +// 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(mode)) { + return ""; + } + return std::get(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 +// +// 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); +} -- cgit v1.2.3 From 4e0fe24681daef069abde3448c26f5cb850d8bb7 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 24 Jan 2021 20:13:16 +0100 Subject: Update test --- autotests/testgroupsession.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) (limited to 'autotests') diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 02892366..1cfe38a9 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -12,27 +12,26 @@ using namespace Quotient; void TestOlmSession::groupSessionPicklingValid() { - auto ogs = std::get(QOlmOutboundGroupSession::create()); - const auto ogsId = std::get(ogs.sessionId()); + auto ogs = QOlmOutboundGroupSession::create(); + const auto ogsId = std::get(ogs->sessionId()); QVERIFY(QByteArray::fromBase64Encoding(ogsId).decodingStatus == QByteArray::Base64DecodingStatus::Ok); - QCOMPARE(0, ogs.sessionMessageIndex()); + QCOMPARE(0, ogs->sessionMessageIndex()); - auto ogsPickled = std::get(ogs.pickle(Unencrypted {})); - ogs = std::get(QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {})); - QCOMPARE(ogsId, std::get(ogs.sessionId())); + auto ogsPickled = std::get(ogs->pickle(Unencrypted {})); + auto ogs2 = std::get>(QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {})); + QCOMPARE(ogsId, std::get(ogs2->sessionId())); - qDebug() << std::get(ogs.sessionKey()); - auto igs = std::get(QOlmInboundGroupSession::create(std::get(ogs.sessionKey()))); - const auto igsId = std::get(igs.sessionId()); + auto igs = QOlmInboundGroupSession::create(std::get(ogs->sessionKey())); + const auto igsId = igs->sessionId(); // ID is valid base64? QVERIFY(QByteArray::fromBase64Encoding(igsId).decodingStatus == QByteArray::Base64DecodingStatus::Ok); //// no messages have been sent yet - QCOMPARE(0, igs.firstKnownIndex()); + QCOMPARE(0, igs->firstKnownIndex()); - auto igsPickled = std::get(igs.pickle(Unencrypted {})); - igs = std::get(QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {})); - QCOMPARE(igsId, std::get(igs.sessionId())); + auto igsPickled = igs->pickle(Unencrypted {}); + igs = std::get>(QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {})); + QCOMPARE(igsId, igs->sessionId()); } QTEST_MAIN(TestOlmSession) -- cgit v1.2.3 From dca69e8326ce6fd0374123ad8c167a2b0051d8fb Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 24 Jan 2021 20:54:30 +0100 Subject: Add group session decrypt/encrypt test and fix bug found by it --- autotests/testgroupsession.cpp | 27 +++++++++++++++++++++++---- autotests/testgroupsession.h | 1 + lib/olm/qolminboundsession.cpp | 19 ++++++++++++------- lib/olm/qolminboundsession.h | 4 +++- lib/olm/qolmoutboundsession.cpp | 6 +++--- lib/olm/qolmoutboundsession.h | 7 +++++-- 6 files changed, 47 insertions(+), 17 deletions(-) (limited to 'autotests') diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 1cfe38a9..a99172d7 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -13,13 +13,13 @@ using namespace Quotient; void TestOlmSession::groupSessionPicklingValid() { auto ogs = QOlmOutboundGroupSession::create(); - const auto ogsId = std::get(ogs->sessionId()); + const auto ogsId = ogs->sessionId(); QVERIFY(QByteArray::fromBase64Encoding(ogsId).decodingStatus == QByteArray::Base64DecodingStatus::Ok); QCOMPARE(0, ogs->sessionMessageIndex()); auto ogsPickled = std::get(ogs->pickle(Unencrypted {})); - auto ogs2 = std::get>(QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {})); - QCOMPARE(ogsId, std::get(ogs2->sessionId())); + auto ogs2 = std::get(QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {})); + QCOMPARE(ogsId, ogs2->sessionId()); auto igs = QOlmInboundGroupSession::create(std::get(ogs->sessionKey())); const auto igsId = igs->sessionId(); @@ -30,9 +30,28 @@ void TestOlmSession::groupSessionPicklingValid() QCOMPARE(0, igs->firstKnownIndex()); auto igsPickled = igs->pickle(Unencrypted {}); - igs = std::get>(QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {})); + igs = std::get(QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {})); QCOMPARE(igsId, igs->sessionId()); } +void TestOlmSession::groupSessionCryptoValid() +{ + auto ogs = QOlmOutboundGroupSession::create(); + auto igs = QOlmInboundGroupSession::create(std::get(ogs->sessionKey())); + QCOMPARE(ogs->sessionId(), igs->sessionId()); + + const auto plainText = QStringLiteral("Hello world!"); + const auto ciphertext = std::get(ogs->encrypt(plainText)); + qDebug() << ciphertext; + // ciphertext valid base64? + QVERIFY(QByteArray::fromBase64Encoding(ciphertext).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + + const auto decryptionResult = std::get>(igs->decrypt(ciphertext)); + + //// correct plaintext? + QCOMPARE(plainText, decryptionResult.first); + + QCOMPARE(0, decryptionResult.second); +} QTEST_MAIN(TestOlmSession) #endif diff --git a/autotests/testgroupsession.h b/autotests/testgroupsession.h index 28ebf4c9..c9192990 100644 --- a/autotests/testgroupsession.h +++ b/autotests/testgroupsession.h @@ -11,5 +11,6 @@ class TestOlmSession : public QObject private Q_SLOTS: void groupSessionPicklingValid(); + void groupSessionCryptoValid(); }; #endif diff --git a/lib/olm/qolminboundsession.cpp b/lib/olm/qolminboundsession.cpp index d3b98a63..11558f51 100644 --- a/lib/olm/qolminboundsession.cpp +++ b/lib/olm/qolminboundsession.cpp @@ -5,6 +5,7 @@ #ifdef Quotient_E2EE_ENABLED #include "olm/qolminboundsession.h" #include +#include using namespace Quotient; OlmError lastError(OlmInboundGroupSession *session) { @@ -89,22 +90,24 @@ std::variant, OlmError> QOlmInboundGrou return std::make_unique(groupSession); } -std::variant, OlmError> QOlmInboundGroupSession::decrypt(QString &message) +std::variant, 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.toUtf8(); + 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(messageBuf.data()), messageBuf.length()), '0'); - const auto messageLen = messageBuf.length(); - const auto plaintextMaxLen = plaintextBuf.length(); + + messageBuf = QByteArray(message.length(), '0'); + std::copy(message.begin(), message.end(), messageBuf.begin()); const auto plaintextLen = olm_group_decrypt(m_groupSession, reinterpret_cast(messageBuf.data()), - messageLen, reinterpret_cast(plaintextBuf.data()), plaintextMaxLen, &messageIndex); + messageBuf.length(), reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), &messageIndex); // Error code or plaintext length is returned const auto decryptError = plaintextLen; @@ -113,8 +116,10 @@ std::variant, OlmError> QOlmInboundGroupSession::de return lastError(m_groupSession); } - plaintextBuf.truncate(plaintextLen); - return std::make_pair(QString(plaintextBuf), messageIndex); + QByteArray output(plaintextLen, '0'); + std::memcpy(output.data(), plaintextBuf.data(), plaintextLen); + + return std::make_pair(QString(output), messageIndex); } std::variant QOlmInboundGroupSession::exportSession(uint32_t messageIndex) diff --git a/lib/olm/qolminboundsession.h b/lib/olm/qolminboundsession.h index ccc53ba8..eb698868 100644 --- a/lib/olm/qolminboundsession.h +++ b/lib/olm/qolminboundsession.h @@ -31,7 +31,7 @@ public: //! an `OlmInboundGroupSession`. static std::variant, OlmError> unpickle(QByteArray &picked, const PicklingMode &mode); //! Decrypts ciphertext received for this group session. - std::variant, OlmError> decrypt(QString &message); + std::variant, 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 exportSession(uint32_t messageIndex); @@ -44,5 +44,7 @@ public: private: OlmInboundGroupSession *m_groupSession; }; + +using QOlmInboundGroupSessionPtr = std::unique_ptr; } // namespace Quotient #endif diff --git a/lib/olm/qolmoutboundsession.cpp b/lib/olm/qolmoutboundsession.cpp index ba8be4f6..4f3cc827 100644 --- a/lib/olm/qolmoutboundsession.cpp +++ b/lib/olm/qolmoutboundsession.cpp @@ -84,7 +84,7 @@ std::variant, OlmError> QOlmOutboundGr return std::make_unique(olmOutboundGroupSession); } -std::variant QOlmOutboundGroupSession::encrypt(QString &plaintext) +std::variant QOlmOutboundGroupSession::encrypt(const QString &plaintext) { QByteArray plaintextBuf = plaintext.toUtf8(); const auto messageMaxLen = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); @@ -104,14 +104,14 @@ uint32_t QOlmOutboundGroupSession::sessionMessageIndex() const return olm_outbound_group_session_message_index(m_groupSession); } -std::variant QOlmOutboundGroupSession::sessionId() const +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(idBuffer.data()), idBuffer.length()); if (error == olm_error()) { - return lastError(m_groupSession); + throw lastError(m_groupSession); } return idBuffer; } diff --git a/lib/olm/qolmoutboundsession.h b/lib/olm/qolmoutboundsession.h index 29776a3d..a642f581 100644 --- a/lib/olm/qolmoutboundsession.h +++ b/lib/olm/qolmoutboundsession.h @@ -11,6 +11,7 @@ namespace Quotient { + //! An out-bound group session is responsible for encrypting outgoing //! communication in a Megolm session. class QOlmOutboundGroupSession @@ -26,7 +27,7 @@ public: //! pickling a `QOlmOutboundGroupSession`. static std::variant, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); //! Encrypts a plaintext message using the session. - std::variant encrypt(QString &plaintext); + std::variant encrypt(const QString &plaintext); //! Get the current message index for this session. //! @@ -35,7 +36,7 @@ public: uint32_t sessionMessageIndex() const; //! Get a base64-encoded identifier for this session. - std::variant sessionId() const; + QByteArray sessionId() const; //! Get the base64-encoded current ratchet key for this session. //! @@ -46,5 +47,7 @@ public: private: OlmOutboundGroupSession *m_groupSession; }; + +using QOlmOutboundGroupSessionPtr = std::unique_ptr; } #endif -- cgit v1.2.3 From 987d399bc9ce628c376d505e3ebb78ae703d7c68 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Tue, 26 Jan 2021 20:13:20 +0100 Subject: Improve API --- CMakeLists.txt | 1 + autotests/testolmaccount.cpp | 29 +++++++++------- lib/olm/e2ee.h | 11 ++++++ lib/olm/qolmaccount.cpp | 83 +++++++++++++++++++++++++++++++------------- lib/olm/qolmaccount.h | 35 ++++++++++++++----- lib/olm/qolmsession.cpp | 29 ++++++++++++++++ lib/olm/qolmsession.h | 49 ++++++++++++++++++++++++++ 7 files changed, 192 insertions(+), 45 deletions(-) create mode 100644 lib/olm/qolmsession.cpp create mode 100644 lib/olm/qolmsession.h (limited to 'autotests') diff --git a/CMakeLists.txt b/CMakeLists.txt index a74d2b7b..a359ae07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,6 +179,7 @@ list(APPEND lib_SRCS lib/jobs/mediathumbnailjob.cpp lib/jobs/downloadfilejob.cpp lib/olm/qolmaccount.cpp + lib/olm/qolmsession.cpp lib/olm/qolminboundsession.cpp lib/olm/qolmoutboundsession.cpp lib/olm/utils.cpp diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 45a7e3a5..75102c32 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -10,19 +10,22 @@ using namespace Quotient; void TestOlmAccount::pickleUnpickedTest() { - auto olmAccount = QOlmAccount::create().value(); - auto identityKeys = std::get(olmAccount.identityKeys()); + QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); + olmAccount.createNewAccount(); + auto identityKeys = olmAccount.identityKeys(); auto pickled = std::get(olmAccount.pickle(Unencrypted{})); - auto olmAccount2 = std::get(QOlmAccount::unpickle(pickled, Unencrypted{})); - auto identityKeys2 = std::get(olmAccount2.identityKeys()); + QOlmAccount olmAccount2(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); + olmAccount2.unpickle(pickled, Unencrypted{}); + auto identityKeys2 = olmAccount2.identityKeys(); QCOMPARE(identityKeys.curve25519, identityKeys2.curve25519); QCOMPARE(identityKeys.ed25519, identityKeys2.ed25519); } void TestOlmAccount::identityKeysValid() { - auto olmAccount = QOlmAccount::create().value(); - const auto identityKeys = std::get(olmAccount.identityKeys()); + QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); + olmAccount.createNewAccount(); + const auto identityKeys = olmAccount.identityKeys(); const auto curve25519 = identityKeys.curve25519; const auto ed25519 = identityKeys.ed25519; // verify encoded keys length @@ -36,10 +39,11 @@ void TestOlmAccount::identityKeysValid() void TestOlmAccount::signatureValid() { - const auto olmAccount = QOlmAccount::create().value(); + QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); + olmAccount.createNewAccount(); const auto message = "Hello world!"; - const auto signature = std::get(olmAccount.sign(message)); - QVERIFY(QByteArray::fromBase64Encoding(signature.toUtf8()).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + const auto signature = olmAccount.sign(message); + QVERIFY(QByteArray::fromBase64Encoding(signature).decodingStatus == QByteArray::Base64DecodingStatus::Ok); //let utility = OlmUtility::new(); //let identity_keys = olm_account.parsed_identity_keys(); @@ -51,15 +55,16 @@ void TestOlmAccount::signatureValid() void TestOlmAccount::oneTimeKeysValid() { - const auto olmAccount = QOlmAccount::create().value(); + QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); + olmAccount.createNewAccount(); const auto maxNumberOfOneTimeKeys = olmAccount.maxNumberOfOneTimeKeys(); QCOMPARE(100, maxNumberOfOneTimeKeys); - const auto oneTimeKeysEmpty = std::get(olmAccount.oneTimeKeys()); + const auto oneTimeKeysEmpty = olmAccount.oneTimeKeys(); QVERIFY(oneTimeKeysEmpty.curve25519().isEmpty()); olmAccount.generateOneTimeKeys(20); - const auto oneTimeKeysFilled = std::get(olmAccount.oneTimeKeys()); + const auto oneTimeKeysFilled = olmAccount.oneTimeKeys(); QCOMPARE(20, oneTimeKeysFilled.curve25519().count()); } diff --git a/lib/olm/e2ee.h b/lib/olm/e2ee.h index 1dee0e42..74f876e4 100644 --- a/lib/olm/e2ee.h +++ b/lib/olm/e2ee.h @@ -66,6 +66,17 @@ struct OneTimeKeys std::optional> 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> signatures; +}; + bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); } // namespace Quotient diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp index 742d7d18..8872f66e 100644 --- a/lib/olm/qolmaccount.cpp +++ b/lib/olm/qolmaccount.cpp @@ -44,36 +44,37 @@ QByteArray getRandom(size_t bufferSize) return buffer; } -QOlmAccount::QOlmAccount(OlmAccount *account) - : m_account(account) -{} +QOlmAccount::QOlmAccount(const QString &userId, const QString &deviceId) + : m_userId(userId) + , m_deviceId(deviceId) +{ +} QOlmAccount::~QOlmAccount() { olm_clear_account(m_account); + delete[](reinterpret_cast(m_account)); } -std::optional QOlmAccount::create() +void QOlmAccount::createNewAccount() { - auto account = olm_account(new uint8_t[olm_account_size()]); - size_t randomSize = olm_create_account_random_length(account); + 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(account, randomData.data(), randomSize); + const auto error = olm_create_account(m_account, randomData.data(), randomSize); if (error == olm_error()) { - return std::nullopt; + throw lastError(m_account); } - return std::make_optional(account); } -std::variant QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) +void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) { - auto account = olm_account(new uint8_t[olm_account_size()]); + m_account = olm_account(new uint8_t[olm_account_size()]); const QByteArray key = toKey(mode); - const auto error = olm_unpickle_account(account, key.data(), key.length(), pickled.data(), pickled.size()); + const auto error = olm_unpickle_account(m_account, key.data(), key.length(), pickled.data(), pickled.size()); if (error == olm_error()) { - return lastError(account); + throw lastError(m_account); } - return QOlmAccount(account); } std::variant QOlmAccount::pickle(const PicklingMode &mode) @@ -89,13 +90,13 @@ std::variant QOlmAccount::pickle(const PicklingMode &mode) return pickleBuffer; } -std::variant QOlmAccount::identityKeys() +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()) { - return lastError(m_account); + throw lastError(m_account); } const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object(); return IdentityKeys { @@ -104,7 +105,7 @@ std::variant QOlmAccount::identityKeys() }; } -std::variant QOlmAccount::sign(const QString &message) const +QByteArray QOlmAccount::sign(const QByteArray &message) const { const size_t signatureLength = olm_account_signature_length(m_account); QByteArray signatureBuffer(signatureLength, '0'); @@ -112,9 +113,19 @@ std::variant QOlmAccount::sign(const QString &message) const signatureBuffer.data(), signatureLength); if (error == olm_error()) { - return lastError(m_account); + throw lastError(m_account); } - return QString::fromUtf8(signatureBuffer); + 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 @@ -122,26 +133,25 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const return olm_account_max_number_of_one_time_keys(m_account); } -std::optional QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const +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()) { - return lastError(m_account); + throw lastError(m_account); } - return std::nullopt; } -std::variant QOlmAccount::oneTimeKeys() const +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()) { - return lastError(m_account); + throw lastError(m_account); } const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); OneTimeKeys oneTimeKeys; @@ -157,4 +167,29 @@ std::variant QOlmAccount::oneTimeKeys() const return oneTimeKeys; } +QMap QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const +{ + QMap 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()); +} + #endif diff --git a/lib/olm/qolmaccount.h b/lib/olm/qolmaccount.h index c478c781..3b55212d 100644 --- a/lib/olm/qolmaccount.h +++ b/lib/olm/qolmaccount.h @@ -20,36 +20,53 @@ namespace Quotient { 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 here. - static std::optional create(); - static std::variant unpickle(QByteArray &picked, const PicklingMode &mode); + //! 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 pickle(const PicklingMode &mode); - std::variant identityKeys(); + + //! Returns the account's public identity keys already formatted as JSON + IdentityKeys identityKeys() const; //! Returns the signature of the supplied message. - std::variant sign(const QString &message) const; + 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. - std::optional generateOneTimeKeys(size_t numberOfKeys) const; + void generateOneTimeKeys(size_t numberOfKeys) const; //! Gets the OlmAccount's one time keys formatted as JSON. - std::variant oneTimeKeys() const; + OneTimeKeys oneTimeKeys() const; + + //! Sign all time key. + QMap signOneTimeKeys(const OneTimeKeys &keys) const; + + //! Sign one time key. + QByteArray signOneTimeKey(const QString &key) const; - // HACK do not use directly - QOlmAccount(OlmAccount *account); + SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; private: - OlmAccount *m_account; + OlmAccount *m_account = nullptr; + QString m_userId; + QString m_deviceId; }; } // namespace Quotient diff --git a/lib/olm/qolmsession.cpp b/lib/olm/qolmsession.cpp new file mode 100644 index 00000000..32a108a8 --- /dev/null +++ b/lib/olm/qolmsession.cpp @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "olm/qolmsession.h" + +using namespace Quotient; + +std::optional 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 toPair(const OlmMessage &message) +{ + return std::visit([](auto &arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return std::make_pair(MessageType, QByteArray(arg.message)); + } else if constexpr (std::is_same_v) { + return std::make_pair(PreKeyType, QByteArray(arg.message)); + } + }, message); +} diff --git a/lib/olm/qolmsession.h b/lib/olm/qolmsession.h new file mode 100644 index 00000000..08f47331 --- /dev/null +++ b/lib/olm/qolmsession.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "olm/e2ee.h" +#include "olm/olm.h" +#include "olm/errors.h" +#include + +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; + +std::optional fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext); + +std::pair toPair(const OlmMessage &message); + +//class QOlmSession +//{ +// /// Creates an inbound session for sending/receiving messages from a received 'prekey' message. +// static std::variant, OlmError> createInboundSession(const QOlmAccount &account, +// PreKeyMessage &message); +// +////private: +// //static std::variant, OlmError> createSessionWith(std::function> func); +//} + +} -- cgit v1.2.3 From fe2d5dd577a05e4a0e250d89487cd14025204b02 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 27 Jan 2021 01:43:49 +0100 Subject: Start adding test for session stuff --- autotests/CMakeLists.txt | 1 + autotests/testolmsession.cpp | 28 ++++++++++++++++++++++++++++ lib/olm/message.cpp | 15 ++++++++------- lib/olm/message.h | 7 +++---- lib/olm/qolmaccount.cpp | 2 ++ lib/olm/session.cpp | 4 ++-- lib/olm/session.h | 4 ++++ 7 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 autotests/testolmsession.cpp (limited to 'autotests') diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 31cdb446..f35890a5 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -14,3 +14,4 @@ endfunction() quotient_add_test(NAME callcandidateseventtest) quotient_add_test(NAME testolmaccount) quotient_add_test(NAME testgroupsession) +quotient_add_test(NAME testolmsession) diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp new file mode 100644 index 00000000..1b7fbb9b --- /dev/null +++ b/autotests/testolmsession.cpp @@ -0,0 +1,28 @@ +#include "olm/session.h" + +using namespace Quotient; + +std::pair, std::unique_ptr> createSessionPair() +{ + QByteArray pickledAccountA("eOBXIKivUT6YYowRH031BNv7zNmzqM5B7CpXdyeaPvala5mt7/OeqrG1qVA7vA1SYloFyvJPIy0QNkD3j1HiPl5vtZHN53rtfZ9exXDok03zjmssqn4IJsqcA7Fbo1FZeKafG0NFcWwCPTdmcV7REqxjqGm3I4K8MQFa45AdTGSUu2C12cWeOcbSMlcINiMral+Uyah1sgPmLJ18h1qcnskXUXQvpffZ5DiUw1Iz5zxnwOQF1GVyowPJD7Zdugvj75RQnDxAn6CzyvrY2k2CuedwqDC3fIXM2xdUNWttW4nC2g4InpBhCVvNwhZYxlUb5BUEjmPI2AB3dAL5ry6o9MFncmbN6x5x"); + QByteArray pickledAccountB("eModTvoFi9oOIkax4j4nuxw9Tcl/J8mOmUctUWI68Q89HSaaPTqR+tdlKQ85v2GOs5NlZCp7EuycypN9GQ4fFbHUCrS7nspa3GFBWsR8PnM8+wez5PWmfFZLg3drOvT0jbMjpDx0MjGYClHBqcrEpKx9oFaIRGBaX6HXzT4lRaWSJkXxuX92q8iGNrLn96PuAWFNcD+2JXpPcNFntslwLUNgqzpZ04aIFYwL80GmzyOgq3Bz1GO6u3TgCQEAmTIYN2QkO0MQeuSfe7UoMumhlAJ6R8GPcdSSPtmXNk4tdyzzlgpVq1hm7ZLKto+g8/5Aq3PvnvA8wCqno2+Pi1duK1pZFTIlActr"); + auto accountA = QOlmAccount("accountA:foo.com", "Device1UserA"); + accountA.unpickle(pickledAccountA, Unencrypted{}); + auto accountB = QOlmAccount("accountB:foo.com", "Device1UserB"); + accountB.unpickle(pickledAccountB, Unencrypted{}); + + const QByteArray identityKeyA("qIEr3TWcJQt4CP8QoKKJcCaukByIOpgh6erBkhLEa2o"); + const QByteArray oneTimeKeyA("WzsbsjD85iB1R32iWxfJdwkgmdz29ClMbJSJziECYwk"); + const QByteArray identityKeyB("q/YhJtog/5VHCAS9rM9uUf6AaFk1yPe4GYuyUOXyQCg"); + const QByteArray oneTimeKeyB("oWvzryma+B2onYjo3hM6A3Mgo/Yepm8HvgSvwZMTnjQ"); + auto outbound = std::get>(accountA + .createOutboundSession(identityKeyB, oneTimeKeyB)); + + const auto preKey = std::get(outbound->encrypt("")); // Payload does not matter for PreKey + + if (preKey.type() != Message::General) { + throw "Wrong first message type received, can't create session"; + } + auto inbound = std::get>(accountB.createInboundSession(preKey)); + return std::make_pair, std::unique_ptr>(std::move(inbound), std::move(outbound)); +} diff --git a/lib/olm/message.cpp b/lib/olm/message.cpp index 0998a66b..634a6f0c 100644 --- a/lib/olm/message.cpp +++ b/lib/olm/message.cpp @@ -8,19 +8,15 @@ 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)) + : QByteArray(std::move(ciphertext)) + , m_messageType(type) { Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); } Message::Type Message::type() const { - return _messageType; + return m_messageType; } QByteArray Message::toCiphertext() const @@ -28,6 +24,11 @@ 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/olm/message.h b/lib/olm/message.h index 6c8ab485..067d9b5a 100644 --- a/lib/olm/message.h +++ b/lib/olm/message.h @@ -28,16 +28,15 @@ public: Q_ENUM(Type) Message() = default; - explicit Message(const QByteArray& ciphertext, Type type = General); - explicit Message(QByteArray ciphertext); + explicit Message(const QByteArray &ciphertext, Type type = General); - static Message fromCiphertext(QByteArray ciphertext); + static Message fromCiphertext(const QByteArray &ciphertext); Q_INVOKABLE Type type() const; Q_INVOKABLE QByteArray toCiphertext() const; private: - Type _messageType = General; + Type m_messageType = General; }; diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp index 9c47bc87..ef51a395 100644 --- a/lib/olm/qolmaccount.cpp +++ b/lib/olm/qolmaccount.cpp @@ -199,11 +199,13 @@ OlmAccount *Quotient::QOlmAccount::data() std::variant, OlmError> QOlmAccount::createInboundSession(const Message &preKeyMessage) { + Q_ASSERT(preKeyMessage.type() == Message::PreKey); return QOlmSession::createInboundSession(this, preKeyMessage); } std::variant, OlmError> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const Message &preKeyMessage) { + Q_ASSERT(preKeyMessage.type() == Message::PreKey); return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); } diff --git a/lib/olm/session.cpp b/lib/olm/session.cpp index b5cd7b81..f6cab650 100644 --- a/lib/olm/session.cpp +++ b/lib/olm/session.cpp @@ -152,9 +152,9 @@ QByteArray QOlmSession::sessionId() const return idBuffer; } -QOlmSession::QOlmSession(OlmSession *session): m_session(session) +QOlmSession::QOlmSession(OlmSession *session) + : m_session(session) { - } #endif // Quotient_E2EE_ENABLED diff --git a/lib/olm/session.h b/lib/olm/session.h index e3a52c88..89f5d822 100644 --- a/lib/olm/session.h +++ b/lib/olm/session.h @@ -14,6 +14,8 @@ namespace Quotient { class QOlmAccount; +class QOlmSession; + //! Either an outbound or inbound session for secure communication. class QOlmSession @@ -43,6 +45,8 @@ private: OlmSession* m_session; }; +//using QOlmSessionPtr = std::unique_ptr; + } //namespace Quotient #endif // Quotient_E2EE_ENABLED -- cgit v1.2.3 From f3fdd967d544650f9af8aadbaddfcf6d8a9fe957 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 27 Jan 2021 02:08:09 +0100 Subject: Add first session test and it fails :( --- autotests/testolmsession.cpp | 19 ++++++++++++++++++- autotests/testolmsession.h | 14 ++++++++++++++ lib/olm/session.cpp | 24 +++++++++++++++++++++--- lib/olm/session.h | 7 ++++++- 4 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 autotests/testolmsession.h (limited to 'autotests') diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 1b7fbb9b..6fa2a380 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -1,7 +1,13 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + #include "olm/session.h" +#include "testolmsession.h" using namespace Quotient; +#ifdef Quotient_E2EE_ENABLED std::pair, std::unique_ptr> createSessionPair() { QByteArray pickledAccountA("eOBXIKivUT6YYowRH031BNv7zNmzqM5B7CpXdyeaPvala5mt7/OeqrG1qVA7vA1SYloFyvJPIy0QNkD3j1HiPl5vtZHN53rtfZ9exXDok03zjmssqn4IJsqcA7Fbo1FZeKafG0NFcWwCPTdmcV7REqxjqGm3I4K8MQFa45AdTGSUu2C12cWeOcbSMlcINiMral+Uyah1sgPmLJ18h1qcnskXUXQvpffZ5DiUw1Iz5zxnwOQF1GVyowPJD7Zdugvj75RQnDxAn6CzyvrY2k2CuedwqDC3fIXM2xdUNWttW4nC2g4InpBhCVvNwhZYxlUb5BUEjmPI2AB3dAL5ry6o9MFncmbN6x5x"); @@ -18,7 +24,7 @@ std::pair, std::unique_ptr> createSess auto outbound = std::get>(accountA .createOutboundSession(identityKeyB, oneTimeKeyB)); - const auto preKey = std::get(outbound->encrypt("")); // Payload does not matter for PreKey + const auto preKey = outbound->encrypt(""); // Payload does not matter for PreKey if (preKey.type() != Message::General) { throw "Wrong first message type received, can't create session"; @@ -26,3 +32,14 @@ std::pair, std::unique_ptr> createSess auto inbound = std::get>(accountB.createInboundSession(preKey)); return std::make_pair, std::unique_ptr>(std::move(inbound), std::move(outbound)); } +#endif + +void TestOlmSession::olmOutboundSessionCreation() +{ +#ifdef Quotient_E2EE_ENABLED + const auto [_, outboundSession] = createSessionPair(); + QCOMPARE(0, outboundSession->hasReceivedMessage()); +#endif +} + +QTEST_MAIN(TestOlmSession) diff --git a/autotests/testolmsession.h b/autotests/testolmsession.h new file mode 100644 index 00000000..7e3fc6e4 --- /dev/null +++ b/autotests/testolmsession.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include + +class TestOlmSession : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void olmOutboundSessionCreation(); +}; +#endif diff --git a/lib/olm/session.cpp b/lib/olm/session.cpp index f6cab650..0beb136e 100644 --- a/lib/olm/session.cpp +++ b/lib/olm/session.cpp @@ -121,11 +121,12 @@ std::variant, OlmError> QOlmSession::unpickle(QByte return std::make_unique(olmSession); } -std::variant QOlmSession::encrypt(const QString &plaintext) +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, @@ -134,10 +135,22 @@ std::variant QOlmSession::encrypt(const QString &plaintext) reinterpret_cast(messageBuf.data()), messageBuf.length()); if (error == olm_error()) { - return lastError(m_session); + throw lastError(m_session); } - return Message::fromCiphertext(messageBuf); + return Message(messageBuf, messageType); +} + +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 @@ -152,6 +165,11 @@ QByteArray QOlmSession::sessionId() const return idBuffer; } +bool QOlmSession::hasReceivedMessage() const +{ + return olm_session_has_received_message(m_session); +} + QOlmSession::QOlmSession(OlmSession *session) : m_session(session) { diff --git a/lib/olm/session.h b/lib/olm/session.h index 89f5d822..f9221dec 100644 --- a/lib/olm/session.h +++ b/lib/olm/session.h @@ -31,12 +31,17 @@ public: //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. static std::variant, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); //! Encrypts a plaintext message using the session. - std::variant encrypt(const QString &plaintext); + Message encrypt(const QString &plaintext); // TODO: WiP //! 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(); + + bool hasReceivedMessage() const; + QOlmSession(OlmSession* session); private: //! Helper function for creating new sessions and handling errors. -- cgit v1.2.3 From 069602584e0f3ec10a26380af69b95f5da11a8b7 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 27 Jan 2021 02:26:23 +0100 Subject: Add more test and methods in session handling --- autotests/testolmsession.cpp | 18 +++++++++++++++++- autotests/testolmsession.h | 1 + lib/olm/message.cpp | 6 ++++++ lib/olm/message.h | 3 ++- lib/olm/qolmoutboundsession.cpp | 2 +- lib/olm/session.cpp | 20 ++++++++++++++++++++ lib/olm/session.h | 4 ++++ 7 files changed, 51 insertions(+), 3 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 6fa2a380..2f7a82e9 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -26,7 +26,7 @@ std::pair, std::unique_ptr> createSess const auto preKey = outbound->encrypt(""); // Payload does not matter for PreKey - if (preKey.type() != Message::General) { + if (preKey.type() != Message::PreKey) { throw "Wrong first message type received, can't create session"; } auto inbound = std::get>(accountB.createInboundSession(preKey)); @@ -42,4 +42,20 @@ void TestOlmSession::olmOutboundSessionCreation() #endif } +void TestOlmSession::olmEncryptDecrypt() +{ +#ifdef Quotient_E2EE_ENABLED + const auto [inboundSession, outboundSession] = createSessionPair(); + const auto encrypted = outboundSession->encrypt("Hello world!"); + if (encrypted.type() == Message::PreKey) { + Message m(encrypted); // clone + QVERIFY(std::get(inboundSession->matchesInboundSession(m))); + } + + //const auto decrypted = inboundSession->decrypt(encrypted); + + //QCOMPARE(decrypted, "Hello world!"); +#endif +} + QTEST_MAIN(TestOlmSession) diff --git a/autotests/testolmsession.h b/autotests/testolmsession.h index 7e3fc6e4..49e8c3e3 100644 --- a/autotests/testolmsession.h +++ b/autotests/testolmsession.h @@ -10,5 +10,6 @@ class TestOlmSession : public QObject Q_OBJECT private Q_SLOTS: void olmOutboundSessionCreation(); + void olmEncryptDecrypt(); }; #endif diff --git a/lib/olm/message.cpp b/lib/olm/message.cpp index 634a6f0c..ac7038ae 100644 --- a/lib/olm/message.cpp +++ b/lib/olm/message.cpp @@ -14,6 +14,12 @@ Message::Message(const QByteArray &ciphertext, Message::Type 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; diff --git a/lib/olm/message.h b/lib/olm/message.h index 067d9b5a..d2fe871e 100644 --- a/lib/olm/message.h +++ b/lib/olm/message.h @@ -18,7 +18,7 @@ namespace Quotient { * * The class provides functions to get a type and the ciphertext. */ -class Message : private QByteArray { +class Message : public QByteArray { Q_GADGET public: enum Type { @@ -29,6 +29,7 @@ public: Message() = default; explicit Message(const QByteArray &ciphertext, Type type = General); + explicit Message(const Message &message); static Message fromCiphertext(const QByteArray &ciphertext); diff --git a/lib/olm/qolmoutboundsession.cpp b/lib/olm/qolmoutboundsession.cpp index 4f3cc827..e5c43495 100644 --- a/lib/olm/qolmoutboundsession.cpp +++ b/lib/olm/qolmoutboundsession.cpp @@ -22,7 +22,7 @@ QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *sess QOlmOutboundGroupSession::~QOlmOutboundGroupSession() { olm_clear_outbound_group_session(m_groupSession); - //delete[](reinterpret_cast(m_groupSession)); + delete[](reinterpret_cast(m_groupSession)); } std::unique_ptr QOlmOutboundGroupSession::create() diff --git a/lib/olm/session.cpp b/lib/olm/session.cpp index 0beb136e..d0493fe8 100644 --- a/lib/olm/session.cpp +++ b/lib/olm/session.cpp @@ -18,6 +18,7 @@ OlmError lastError(OlmSession* session) { Quotient::QOlmSession::~QOlmSession() { olm_clear_session(m_session); + delete[](reinterpret_cast(m_session)); } OlmSession* QOlmSession::create() @@ -170,6 +171,25 @@ bool QOlmSession::hasReceivedMessage() const return olm_session_has_received_message(m_session); } +std::variant 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) { diff --git a/lib/olm/session.h b/lib/olm/session.h index f9221dec..c45b6898 100644 --- a/lib/olm/session.h +++ b/lib/olm/session.h @@ -40,8 +40,12 @@ public: //! 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 matchesInboundSession(Message &preKeyMessage); + QOlmSession(OlmSession* session); private: //! Helper function for creating new sessions and handling errors. -- cgit v1.2.3 From eabea7af10b5734a507484478a64d2c9f716279f Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 27 Jan 2021 15:33:46 +0100 Subject: Add QOlmSession::decrypt --- autotests/testolmsession.cpp | 4 ++-- lib/olm/session.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ lib/olm/session.h | 6 +++++- 3 files changed, 48 insertions(+), 3 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 2f7a82e9..fc151621 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -52,9 +52,9 @@ void TestOlmSession::olmEncryptDecrypt() QVERIFY(std::get(inboundSession->matchesInboundSession(m))); } - //const auto decrypted = inboundSession->decrypt(encrypted); + const auto decrypted = std::get(inboundSession->decrypt(encrypted)); - //QCOMPARE(decrypted, "Hello world!"); + QCOMPARE(decrypted, "Hello world!"); #endif } diff --git a/lib/olm/session.cpp b/lib/olm/session.cpp index d0493fe8..a05e0786 100644 --- a/lib/olm/session.cpp +++ b/lib/olm/session.cpp @@ -6,6 +6,7 @@ #include "olm/session.h" #include "olm/utils.h" #include "logging.h" +#include using namespace Quotient; @@ -142,6 +143,46 @@ Message QOlmSession::encrypt(const QString &plaintext) return Message(messageBuf, messageType); } +std::variant 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(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(messageBuf2.data()), messageBuf2.length(), + reinterpret_cast(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); diff --git a/lib/olm/session.h b/lib/olm/session.h index c45b6898..3f1622c7 100644 --- a/lib/olm/session.h +++ b/lib/olm/session.h @@ -32,7 +32,11 @@ public: static std::variant, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); //! Encrypts a plaintext message using the session. Message encrypt(const QString &plaintext); - // TODO: WiP + + //! 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 decrypt(const Message &message) const; //! Get a base64-encoded identifier for this session. QByteArray sessionId() const; -- cgit v1.2.3 From 583d484b2dc27d3216706a1e0858b794d4c5fe19 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 27 Jan 2021 16:09:10 +0100 Subject: Implement session sorting --- autotests/testolmsession.cpp | 26 ++++++++++++++++++++++++++ autotests/testolmsession.h | 1 + lib/olm/session.cpp | 4 ++-- lib/olm/session.h | 13 ++++++++++++- 4 files changed, 41 insertions(+), 3 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index fc151621..77ba35ef 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -58,4 +58,30 @@ void TestOlmSession::olmEncryptDecrypt() #endif } +void TestOlmSession::correctSessionOrdering() +{ +#ifdef Quotient_E2EE_ENABLED + // n0W5IJ2ZmaI9FxKRj/wohUQ6WEU0SfoKsgKKHsr4VbM + auto session1 = std::get>(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGvlaV6t/0ihD2/0QGckDIvbmE1aV+PxB0zUtHXh99bI/60N+PWkCLA84jEY4sz3d45ui/TVoFGLDHlymKxvlj7XngXrbtlxSkVntsPzDiNpKEXCa26N2ubKpQ0fbjrV5gbBTYWfU04DXHPXFDTksxpNALYt/h0eVMVhf6hB0ZzpLBsOG0mpwkLufwub0CuDEDGGmRddz3TcNCLq5NnI8R9udDWvHAkTS1UTbHuIf/y6cZg875nJyXpAvd8/XhL8TOo8ot2sE1fElBa4vrH/m9rBQMC1GPkhLBIizmY44C+Sq9PQRnF+uCZ", Unencrypted{})); + // +9pHJhP3K4E5/2m8PYBPLh8pS9CJodwUOh8yz3mnmw0 + auto session2 = std::get>(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UFD+q37/WlfTAzQsSjCdD07FcErZ4siEy5vpiB+pyO8i53ptZvb2qRvqNKFzPaXuu33PS2PBTmmnR+kJt+DgDNqWadyaj/WqEAejc7ALqSs5GuhbZtpoLe+lRSRK0rwVX3gzz4qrl8pm0pD5pSZAUWRXDRlieGWMclz68VUvnSaQH7ElTo4S634CJk+xQfFFCD26v0yONPSN6rwouS1cWPuG5jTlnV8vCFVTU2+lduKh54Ko6FUJ/ei4xR8Nk2duBGSc/TdllX9e2lDYHSUkWoD4ti5xsFioB8Blus7JK9BZfcmRmdlxIOD", Unencrypted {})); + // MC7n8hX1l7WlC2/WJGHZinMocgiBZa4vwGAOredb/ME + auto session3 = std::get>(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGNk2TmVDJ95K0Nywf24FNklNVtXtFDiFPHFwNSmCbHNCp3hsGtZlt0AHUkMmL48XklLqzwtVk5/v2RRmSKR5LqYdIakrtuK/fY0ENhBZIbI1sRetaJ2KMbY9l6rCJNfFg8VhpZ4KTVvEZVuP9g/eZkCnP5NxzXiBRF6nfY3O/zhcKxa3acIqs6BMhyLsfuJ80t+hQ1HvVyuhBerGujdSDzV9tJ9SPidOwfYATk81LVF9hTmnI0KaZa7qCtFzhG0dU/Z3hIWH9HOaw1aSB/IPmughbwdJOwERyhuo3YHoznlQnJ7X252BlI", Unencrypted{})); + + const auto session1Id = session1->sessionId(); + const auto session2Id = session2->sessionId(); + const auto session3Id = session3->sessionId(); + + std::vector> sessionList; + sessionList.push_back(std::move(session1)); + sessionList.push_back(std::move(session2)); + sessionList.push_back(std::move(session3)); + + std::sort(sessionList.begin(), sessionList.end()); + QCOMPARE(sessionList[0]->sessionId(), session2Id); + QCOMPARE(sessionList[1]->sessionId(), session3Id); + QCOMPARE(sessionList[2]->sessionId(), session1Id); +#endif +} + QTEST_MAIN(TestOlmSession) diff --git a/autotests/testolmsession.h b/autotests/testolmsession.h index 49e8c3e3..c03b7b6a 100644 --- a/autotests/testolmsession.h +++ b/autotests/testolmsession.h @@ -11,5 +11,6 @@ class TestOlmSession : public QObject private Q_SLOTS: void olmOutboundSessionCreation(); void olmEncryptDecrypt(); + void correctSessionOrdering(); }; #endif diff --git a/lib/olm/session.cpp b/lib/olm/session.cpp index a05e0786..94f12db6 100644 --- a/lib/olm/session.cpp +++ b/lib/olm/session.cpp @@ -108,13 +108,13 @@ std::variant QOlmSession::pickle(const PicklingMode &mode) return pickledBuf; } -std::variant, OlmError> QOlmSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +std::variant, 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(), - pickled.data(), pickled.length()); + pickledBuf.data(), pickledBuf.length()); if (error == olm_error()) { return lastError(olmSession); } diff --git a/lib/olm/session.h b/lib/olm/session.h index 3f1622c7..03b3514e 100644 --- a/lib/olm/session.h +++ b/lib/olm/session.h @@ -9,6 +9,7 @@ #include "olm/e2ee.h" #include "olm/message.h" #include "olm/errors.h" +#include #include "olm/qolmaccount.h" namespace Quotient { @@ -29,7 +30,7 @@ public: //! Serialises an `QOlmSession` to encrypted Base64. std::variant pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static std::variant, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); + static std::variant, OlmError> unpickle(const QByteArray &pickled, const PicklingMode &mode); //! Encrypts a plaintext message using the session. Message encrypt(const QString &plaintext); @@ -50,6 +51,15 @@ public: //! Checks if the 'prekey' message is for this in-bound session. std::variant matchesInboundSession(Message &preKeyMessage); + friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) + { + return lhs.sessionId() < rhs.sessionId(); + } + + friend bool operator<(const std::unique_ptr &lhs, const std::unique_ptr &rhs) { + return *lhs < *rhs; + } + QOlmSession(OlmSession* session); private: //! Helper function for creating new sessions and handling errors. @@ -58,6 +68,7 @@ private: OlmSession* m_session; }; + //using QOlmSessionPtr = std::unique_ptr; } //namespace Quotient -- cgit v1.2.3 From efe7e4ebc9c71f68d29c5c1a5a6bacbaea6fd146 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 27 Jan 2021 16:12:17 +0100 Subject: Disable olm test when disabling encryption --- autotests/CMakeLists.txt | 8 +++++--- autotests/testgroupsession.cpp | 2 -- autotests/testgroupsession.h | 2 -- autotests/testolmaccount.cpp | 2 -- autotests/testolmaccount.h | 2 -- autotests/testolmsession.cpp | 8 -------- autotests/testolmsession.h | 2 -- 7 files changed, 5 insertions(+), 21 deletions(-) (limited to 'autotests') diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index f35890a5..6afdf8cc 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -12,6 +12,8 @@ function(QUOTIENT_ADD_TEST) endfunction() quotient_add_test(NAME callcandidateseventtest) -quotient_add_test(NAME testolmaccount) -quotient_add_test(NAME testgroupsession) -quotient_add_test(NAME testolmsession) +if(${PROJECT_NAME}_ENABLE_E2EE) + quotient_add_test(NAME testolmaccount) + quotient_add_test(NAME testgroupsession) + quotient_add_test(NAME testolmsession) +endif() diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index a99172d7..23c5bf8f 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "testgroupsession.h" #include "olm/qolminboundsession.h" #include "olm/qolmoutboundsession.h" @@ -54,4 +53,3 @@ void TestOlmSession::groupSessionCryptoValid() QCOMPARE(0, decryptionResult.second); } QTEST_MAIN(TestOlmSession) -#endif diff --git a/autotests/testgroupsession.h b/autotests/testgroupsession.h index c9192990..27f34bec 100644 --- a/autotests/testgroupsession.h +++ b/autotests/testgroupsession.h @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include class TestOlmSession : public QObject @@ -13,4 +12,3 @@ private Q_SLOTS: void groupSessionPicklingValid(); void groupSessionCryptoValid(); }; -#endif diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 75102c32..9f85e77e 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "testolmaccount.h" #include "olm/qolmaccount.h" @@ -69,4 +68,3 @@ void TestOlmAccount::oneTimeKeysValid() } QTEST_MAIN(TestOlmAccount) -#endif diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index c3297b5f..e7b32b8b 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include class TestOlmAccount : public QObject @@ -16,4 +15,3 @@ private Q_SLOTS: void oneTimeKeysValid(); //void removeOneTimeKeys(); }; -#endif diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 77ba35ef..da0e36e3 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -7,7 +7,6 @@ using namespace Quotient; -#ifdef Quotient_E2EE_ENABLED std::pair, std::unique_ptr> createSessionPair() { QByteArray pickledAccountA("eOBXIKivUT6YYowRH031BNv7zNmzqM5B7CpXdyeaPvala5mt7/OeqrG1qVA7vA1SYloFyvJPIy0QNkD3j1HiPl5vtZHN53rtfZ9exXDok03zjmssqn4IJsqcA7Fbo1FZeKafG0NFcWwCPTdmcV7REqxjqGm3I4K8MQFa45AdTGSUu2C12cWeOcbSMlcINiMral+Uyah1sgPmLJ18h1qcnskXUXQvpffZ5DiUw1Iz5zxnwOQF1GVyowPJD7Zdugvj75RQnDxAn6CzyvrY2k2CuedwqDC3fIXM2xdUNWttW4nC2g4InpBhCVvNwhZYxlUb5BUEjmPI2AB3dAL5ry6o9MFncmbN6x5x"); @@ -32,19 +31,15 @@ std::pair, std::unique_ptr> createSess auto inbound = std::get>(accountB.createInboundSession(preKey)); return std::make_pair, std::unique_ptr>(std::move(inbound), std::move(outbound)); } -#endif void TestOlmSession::olmOutboundSessionCreation() { -#ifdef Quotient_E2EE_ENABLED const auto [_, outboundSession] = createSessionPair(); QCOMPARE(0, outboundSession->hasReceivedMessage()); -#endif } void TestOlmSession::olmEncryptDecrypt() { -#ifdef Quotient_E2EE_ENABLED const auto [inboundSession, outboundSession] = createSessionPair(); const auto encrypted = outboundSession->encrypt("Hello world!"); if (encrypted.type() == Message::PreKey) { @@ -55,12 +50,10 @@ void TestOlmSession::olmEncryptDecrypt() const auto decrypted = std::get(inboundSession->decrypt(encrypted)); QCOMPARE(decrypted, "Hello world!"); -#endif } void TestOlmSession::correctSessionOrdering() { -#ifdef Quotient_E2EE_ENABLED // n0W5IJ2ZmaI9FxKRj/wohUQ6WEU0SfoKsgKKHsr4VbM auto session1 = std::get>(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGvlaV6t/0ihD2/0QGckDIvbmE1aV+PxB0zUtHXh99bI/60N+PWkCLA84jEY4sz3d45ui/TVoFGLDHlymKxvlj7XngXrbtlxSkVntsPzDiNpKEXCa26N2ubKpQ0fbjrV5gbBTYWfU04DXHPXFDTksxpNALYt/h0eVMVhf6hB0ZzpLBsOG0mpwkLufwub0CuDEDGGmRddz3TcNCLq5NnI8R9udDWvHAkTS1UTbHuIf/y6cZg875nJyXpAvd8/XhL8TOo8ot2sE1fElBa4vrH/m9rBQMC1GPkhLBIizmY44C+Sq9PQRnF+uCZ", Unencrypted{})); // +9pHJhP3K4E5/2m8PYBPLh8pS9CJodwUOh8yz3mnmw0 @@ -81,7 +74,6 @@ void TestOlmSession::correctSessionOrdering() QCOMPARE(sessionList[0]->sessionId(), session2Id); QCOMPARE(sessionList[1]->sessionId(), session3Id); QCOMPARE(sessionList[2]->sessionId(), session1Id); -#endif } QTEST_MAIN(TestOlmSession) diff --git a/autotests/testolmsession.h b/autotests/testolmsession.h index c03b7b6a..9a5798fa 100644 --- a/autotests/testolmsession.h +++ b/autotests/testolmsession.h @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include class TestOlmSession : public QObject @@ -13,4 +12,3 @@ private Q_SLOTS: void olmEncryptDecrypt(); void correctSessionOrdering(); }; -#endif -- cgit v1.2.3 From d44a7914c5f2ba231fdd9c830b1eace69c3383d0 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 27 Jan 2021 16:29:32 +0100 Subject: Add device key test --- autotests/testolmaccount.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++++ autotests/testolmaccount.h | 1 + 2 files changed, 66 insertions(+) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 9f85e77e..9a8e253c 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -4,6 +4,7 @@ #include "testolmaccount.h" #include "olm/qolmaccount.h" +#include "csapi/definitions/device_keys.h" using namespace Quotient; @@ -67,4 +68,68 @@ void TestOlmAccount::oneTimeKeysValid() QCOMPARE(20, oneTimeKeysFilled.curve25519().count()); } +void TestOlmAccount::deviceKeys() +{ + // copied from mtxclient + DeviceKeys device1; + device1.userId = "@alice:example.com"; + device1.deviceId = "JLAFKJWSCS"; + device1.keys = {{"curve25519:JLAFKJWSCS", "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"}, + {"ed25519:JLAFKJWSCS", "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"}}; + + // TODO that should be the default value + device1.algorithms = QStringList {"m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2"}; + + device1.signatures = { + {"@alice:example.com", + {{"ed25519:JLAFKJWSCS", + "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/" + "a+myXS367WT6NAIcBA"}}}}; + + QJsonObject j; + JsonObjectConverter::dumpTo(j, device1); + QJsonDocument doc(j); + QCOMPARE(doc.toJson(QJsonDocument::Compact), "{\"algorithms\":[\"m.olm.v1.curve25519-aes-sha2\",\"m.megolm.v1.aes-sha2\"]," + "\"device_id\":\"JLAFKJWSCS\",\"keys\":{\"curve25519:JLAFKJWSCS\":" + "\"3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI\",\"ed25519:JLAFKJWSCS\":" + "\"lEuiRJBit0IG6nUf5pUzWTUEsRVVe/" + "HJkoKuEww9ULI\"},\"signatures\":{\"@alice:example.com\":{\"ed25519:JLAFKJWSCS\":" + "\"dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/" + "a+myXS367WT6NAIcBA\"}},\"user_id\":\"@alice:example.com\"}"); + + auto doc2 = QJsonDocument::fromJson(R"({ + "user_id": "@alice:example.com", + "device_id": "JLAFKJWSCS", + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "keys": { + "curve25519:JLAFKJWSCS": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI" + }, + "signatures": { + "@alice:example.com": { + "ed25519:JLAFKJWSCS": "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA" + } + }, + "unsigned": { + "device_display_name": "Alice's mobile phone" + } + })"); + + DeviceKeys device2; + JsonObjectConverter::fillFrom(doc2.object(), device2); + + QCOMPARE(device2.userId, device1.userId); + QCOMPARE(device2.deviceId, device1.deviceId); + QCOMPARE(device2.keys, device1.keys); + QCOMPARE(device2.algorithms, device1.algorithms); + QCOMPARE(device2.signatures, device1.signatures); + + // UnsignedDeviceInfo is missing from the generated DeviceKeys object :( + // QCOMPARE(device2.unsignedInfo.deviceDisplayName, "Alice's mobile phone"); +} + QTEST_MAIN(TestOlmAccount) diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index e7b32b8b..547c25c1 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -14,4 +14,5 @@ private Q_SLOTS: void signatureValid(); void oneTimeKeysValid(); //void removeOneTimeKeys(); + void deviceKeys(); }; -- cgit v1.2.3 From 5910689306149cacf3ac644d3ea42e10f889e3fe Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 27 Jan 2021 20:16:59 +0100 Subject: Add encrypted file struct --- autotests/testolmaccount.cpp | 30 ++++++++++++++++++++++++++++++ autotests/testolmaccount.h | 1 + 2 files changed, 31 insertions(+) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 9a8e253c..2fac53bd 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -5,6 +5,7 @@ #include "testolmaccount.h" #include "olm/qolmaccount.h" #include "csapi/definitions/device_keys.h" +#include "events/encryptedfile.h" using namespace Quotient; @@ -132,4 +133,33 @@ void TestOlmAccount::deviceKeys() // QCOMPARE(device2.unsignedInfo.deviceDisplayName, "Alice's mobile phone"); } +void TestOlmAccount::encryptedFile() +{ + auto doc = QJsonDocument::fromJson(R"({ + "url": "mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe", + "v": "v2", + "key": { + "alg": "A256CTR", + "ext": true, + "k": "aWF6-32KGYaC3A_FEUCk1Bt0JA37zP0wrStgmdCaW-0", + "key_ops": ["encrypt","decrypt"], + "kty": "oct" + }, + "iv": "w+sE15fzSc0AAAAAAAAAAA", + "hashes": { + "sha256": "fdSLu/YkRx3Wyh3KQabP3rd6+SFiKg5lsJZQHtkSAYA" + }})"); + + EncryptedFile file; + JsonObjectConverter::fillFrom(doc.object(), file); + + QCOMPARE(file.v, "v2"); + QCOMPARE(file.iv, "w+sE15fzSc0AAAAAAAAAAA"); + QCOMPARE(file.hashes["sha256"], "fdSLu/YkRx3Wyh3KQabP3rd6+SFiKg5lsJZQHtkSAYA"); + QCOMPARE(file.key.alg, "A256CTR"); + QCOMPARE(file.key.ext, true); + QCOMPARE(file.key.k, "aWF6-32KGYaC3A_FEUCk1Bt0JA37zP0wrStgmdCaW-0"); + QCOMPARE(file.key.keyOps.count(), 2); + QCOMPARE(file.key.kty, "oct"); +} QTEST_MAIN(TestOlmAccount) diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index 547c25c1..4e270730 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -15,4 +15,5 @@ private Q_SLOTS: void oneTimeKeysValid(); //void removeOneTimeKeys(); void deviceKeys(); + void encryptedFile(); }; -- cgit v1.2.3 From dd0316ce57bd9256a093d66845e1d40cd9426ba4 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 28 Jan 2021 21:54:37 +0100 Subject: Move files --- CMakeLists.txt | 16 +-- autotests/testgroupsession.cpp | 6 +- autotests/testolmaccount.cpp | 6 +- autotests/testolmsession.cpp | 2 +- lib/crypto/e2ee.h | 82 +++++++++++++ lib/crypto/errors.cpp | 21 ++++ lib/crypto/errors.h | 31 +++++ lib/crypto/message.cpp | 42 +++++++ lib/crypto/message.h | 46 +++++++ lib/crypto/qolmaccount.cpp | 217 +++++++++++++++++++++++++++++++++ lib/crypto/qolmaccount.h | 96 +++++++++++++++ lib/crypto/qolminboundsession.cpp | 157 ++++++++++++++++++++++++ lib/crypto/qolminboundsession.h | 51 ++++++++ lib/crypto/qolmoutboundsession.cpp | 131 ++++++++++++++++++++ lib/crypto/qolmoutboundsession.h | 54 +++++++++ lib/crypto/qolmsession.cpp | 29 +++++ lib/crypto/qolmsession.h | 49 ++++++++ lib/crypto/session.cpp | 242 +++++++++++++++++++++++++++++++++++++ lib/crypto/session.h | 77 ++++++++++++ lib/crypto/utils.cpp | 26 ++++ lib/crypto/utils.h | 15 +++ lib/olm/e2ee.h | 82 ------------- lib/olm/errors.cpp | 21 ---- lib/olm/errors.h | 31 ----- lib/olm/message.cpp | 42 ------- lib/olm/message.h | 46 ------- lib/olm/qolmaccount.cpp | 217 --------------------------------- lib/olm/qolmaccount.h | 96 --------------- lib/olm/qolminboundsession.cpp | 157 ------------------------ lib/olm/qolminboundsession.h | 51 -------- lib/olm/qolmoutboundsession.cpp | 131 -------------------- lib/olm/qolmoutboundsession.h | 54 --------- lib/olm/qolmsession.cpp | 29 ----- lib/olm/qolmsession.h | 49 -------- lib/olm/session.cpp | 242 ------------------------------------- lib/olm/session.h | 76 ------------ lib/olm/utils.cpp | 26 ---- lib/olm/utils.h | 15 --- 38 files changed, 1381 insertions(+), 1380 deletions(-) create mode 100644 lib/crypto/e2ee.h create mode 100644 lib/crypto/errors.cpp create mode 100644 lib/crypto/errors.h create mode 100644 lib/crypto/message.cpp create mode 100644 lib/crypto/message.h create mode 100644 lib/crypto/qolmaccount.cpp create mode 100644 lib/crypto/qolmaccount.h create mode 100644 lib/crypto/qolminboundsession.cpp create mode 100644 lib/crypto/qolminboundsession.h create mode 100644 lib/crypto/qolmoutboundsession.cpp create mode 100644 lib/crypto/qolmoutboundsession.h create mode 100644 lib/crypto/qolmsession.cpp create mode 100644 lib/crypto/qolmsession.h create mode 100644 lib/crypto/session.cpp create mode 100644 lib/crypto/session.h create mode 100644 lib/crypto/utils.cpp create mode 100644 lib/crypto/utils.h delete mode 100644 lib/olm/e2ee.h delete mode 100644 lib/olm/errors.cpp delete mode 100644 lib/olm/errors.h delete mode 100644 lib/olm/message.cpp delete mode 100644 lib/olm/message.h delete mode 100644 lib/olm/qolmaccount.cpp delete mode 100644 lib/olm/qolmaccount.h delete mode 100644 lib/olm/qolminboundsession.cpp delete mode 100644 lib/olm/qolminboundsession.h delete mode 100644 lib/olm/qolmoutboundsession.cpp delete mode 100644 lib/olm/qolmoutboundsession.h delete mode 100644 lib/olm/qolmsession.cpp delete mode 100644 lib/olm/qolmsession.h delete mode 100644 lib/olm/session.cpp delete mode 100644 lib/olm/session.h delete mode 100644 lib/olm/utils.cpp delete mode 100644 lib/olm/utils.h (limited to 'autotests') diff --git a/CMakeLists.txt b/CMakeLists.txt index a04da04d..40767573 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -178,14 +178,14 @@ list(APPEND lib_SRCS lib/jobs/syncjob.cpp lib/jobs/mediathumbnailjob.cpp lib/jobs/downloadfilejob.cpp - lib/olm/qolmaccount.cpp - lib/olm/qolmsession.cpp - lib/olm/qolminboundsession.cpp - lib/olm/qolmoutboundsession.cpp - lib/olm/utils.cpp - lib/olm/errors.cpp - lib/olm/session.cpp - lib/olm/message.cpp + lib/crypto/qolmaccount.cpp + lib/crypto/qolmsession.cpp + lib/crypto/qolminboundsession.cpp + lib/crypto/qolmoutboundsession.cpp + lib/crypto/utils.cpp + lib/crypto/errors.cpp + lib/crypto/session.cpp + lib/crypto/message.cpp ) # Configure API files generation diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 23c5bf8f..325ca2ec 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -3,9 +3,9 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testgroupsession.h" -#include "olm/qolminboundsession.h" -#include "olm/qolmoutboundsession.h" -#include "olm/utils.h" +#include +#include +#include using namespace Quotient; diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 2fac53bd..cbce845a 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -3,9 +3,9 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testolmaccount.h" -#include "olm/qolmaccount.h" -#include "csapi/definitions/device_keys.h" -#include "events/encryptedfile.h" +#include +#include +#include using namespace Quotient; diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index da0e36e3..462c8213 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include "olm/session.h" +#include #include "testolmsession.h" using namespace Quotient; 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 +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "util.h" +#include +#include +#include +#include + +#include + +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; + +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...) -> overloaded; + +struct IdentityKeys +{ + QByteArray curve25519; + QByteArray ed25519; +}; + +//! Struct representing the one-time keys. +struct OneTimeKeys +{ + QMap> keys; + + //! Get the HashMap containing the curve25519 one-time keys. + QMap curve25519() const; + + //! Get a reference to the hashmap corresponding to given key type. + std::optional> 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> 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 +// 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED +#include + +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 +// +// 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED + +#include +#include + +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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/qolmaccount.h" +#include "crypto/utils.h" +#include +#include +#include +#include + +using namespace Quotient; + +QMap OneTimeKeys::curve25519() const +{ + return keys[QStringLiteral("curve25519")]; +} + +std::optional> 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(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 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(); + for (const QString &key2 : oneTimeKeyObject.keys()) { + keyMap[key2] = oneTimeKeyObject[key2].toString(); + } + oneTimeKeys.keys[key1.toString()] = keyMap; + } + return oneTimeKeys; +} + +QMap QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const +{ + QMap 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, OlmError> QOlmAccount::createInboundSession(const Message &preKeyMessage) +{ + Q_ASSERT(preKeyMessage.type() == Message::PreKey); + return QOlmSession::createInboundSession(this, preKeyMessage); +} + +std::variant, OlmError> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const Message &preKeyMessage) +{ + Q_ASSERT(preKeyMessage.type() == Message::PreKey); + return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); +} + +std::variant, 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 +// +// 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 +#include + +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 here. + //! 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 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 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, 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, 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, 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/qolminboundsession.h" +#include +#include +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(m_groupSession)); +} + +std::unique_ptr 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(temp.data()), temp.size()); + + if (error == olm_error()) { + throw lastError(olmInboundGroupSession); + } + + return std::make_unique(olmInboundGroupSession); +} + + +std::unique_ptr 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(keyBuf.data()), keyBuf.size()); + if (error == olm_error()) { + throw lastError(olmInboundGroupSession); + } + + return std::make_unique(olmInboundGroupSession); +} + +QByteArray toKey(const PicklingMode &mode) +{ + if (std::holds_alternative(mode)) { + return ""; + } + return std::get(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, 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(groupSession); +} + +std::variant, 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(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(messageBuf.data()), + messageBuf.length(), reinterpret_cast(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(output), messageIndex); +} + +std::variant 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(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(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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED + +#include +#include +#include +#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 create(const QByteArray &key); + //! Import an inbound group session, from a previous export. + static std::unique_ptr 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, OlmError> unpickle(QByteArray &picked, const PicklingMode &mode); + //! Decrypts ciphertext received for this group session. + std::variant, 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 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; +using OlmInboundGroupSessionPtr = std::unique_ptr; +} // 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 +// +// 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(m_groupSession)); +} + +std::unique_ptr 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(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(keyBuffer.data()), + keyMaxLength); + + randomBuf.clear(); + + return std::make_unique(olmOutboundGroupSession); +} + +std::variant 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, 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(idBuffer.data()), + idBuffer.length()); + + key.clear(); + return std::make_unique(olmOutboundGroupSession); +} + +std::variant 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(plaintextBuf.data()), + plaintextBuf.length(), reinterpret_cast(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(idBuffer.data()), + idBuffer.length()); + if (error == olm_error()) { + throw lastError(m_groupSession); + } + return idBuffer; +} + +std::variant 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(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 +// +// 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 + +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 create(); + //! Serialises an `QOlmOutboundGroupSession` to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by + //! pickling a `QOlmOutboundGroupSession`. + static std::variant, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + std::variant 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 sessionKey() const; + QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); +private: + OlmOutboundGroupSession *m_groupSession; +}; + +using QOlmOutboundGroupSessionPtr = std::unique_ptr; +using OlmOutboundGroupSessionPtr = std::unique_ptr; +} +#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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "crypto/qolmsession.h" + +using namespace Quotient; + +std::optional 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 toPair(const OlmMessage &message) +{ + return std::visit([](auto &arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return std::make_pair(MessageType, QByteArray(arg.message)); + } else if constexpr (std::is_same_v) { + return std::make_pair(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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#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; + +std::optional fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext); + +std::pair toPair(const OlmMessage &message); + +//class QOlmSession +//{ +// /// Creates an inbound session for sending/receiving messages from a received 'prekey' message. +// static std::variant, OlmError> createInboundSession(const QOlmAccount &account, +// PreKeyMessage &message); +// +////private: +// //static std::variant, OlmError> createSessionWith(std::function> 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/session.h" +#include "crypto/utils.h" +#include "logging.h" +#include + +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(m_session)); +} + +OlmSession* QOlmSession::create() +{ + return olm_session(new uint8_t[olm_session_size()]); +} + +std::variant, 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(olmSession); +} + +std::variant, OlmError> QOlmSession::createInboundSession(QOlmAccount *account, const Message &preKeyMessage) +{ + return createInbound(account, preKeyMessage); +} + +std::variant, OlmError> QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage) +{ + return createInbound(account, preKeyMessage, true, theirIdentityKey); +} + +std::variant, 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(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), + reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), + reinterpret_cast(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(olmOutboundSession); +} + +std::variant 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, 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(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(plaintextBuf.data()), plaintextBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length(), + reinterpret_cast(messageBuf.data()), messageBuf.length()); + + if (error == olm_error()) { + throw lastError(m_session); + } + + return Message(messageBuf, messageType); +} + +std::variant 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(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(messageBuf2.data()), messageBuf2.length(), + reinterpret_cast(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(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 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED + +#include +#include +#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, OlmError> createInboundSession(QOlmAccount *account, const Message &preKeyMessage); + static std::variant, OlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage); + static std::variant, OlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey); + //! Serialises an `QOlmSession` to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. + static std::variant, 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 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 matchesInboundSession(Message &preKeyMessage); + + friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) + { + return lhs.sessionId() < rhs.sessionId(); + } + + friend bool operator<(const std::unique_ptr &lhs, const std::unique_ptr &rhs) { + return *lhs < *rhs; + } + + QOlmSession(OlmSession* session); +private: + //! Helper function for creating new sessions and handling errors. + static OlmSession* create(); + static std::variant, OlmError> createInbound(QOlmAccount *account, const Message& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); + OlmSession* m_session; +}; + + +//using QOlmSessionPtr = std::unique_ptr; + +} //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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/utils.h" +#include +#include + +using namespace Quotient; + +QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) +{ + if (std::holds_alternative(mode)) { + return ""; + } + return std::get(mode).key; +} + +QByteArray Quotient::getRandom(size_t bufferSize) +{ + QByteArray buffer(bufferSize, '0'); + RAND_bytes(reinterpret_cast(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 +// +// 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 diff --git a/lib/olm/e2ee.h b/lib/olm/e2ee.h deleted file mode 100644 index 74f876e4..00000000 --- a/lib/olm/e2ee.h +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-FileCopyrightText: 2021 Carl Schwan -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "util.h" -#include -#include -#include -#include - -#include - -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; - -template struct overloaded : Ts... { using Ts::operator()...; }; -template overloaded(Ts...) -> overloaded; - -struct IdentityKeys -{ - QByteArray curve25519; - QByteArray ed25519; -}; - -//! Struct representing the one-time keys. -struct OneTimeKeys -{ - QMap> keys; - - //! Get the HashMap containing the curve25519 one-time keys. - QMap curve25519() const; - - //! Get a reference to the hashmap corresponding to given key type. - std::optional> 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> signatures; -}; - -bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); - -} // namespace Quotient diff --git a/lib/olm/errors.cpp b/lib/olm/errors.cpp deleted file mode 100644 index a687e807..00000000 --- a/lib/olm/errors.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED -#include "olm/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/olm/errors.h b/lib/olm/errors.h deleted file mode 100644 index 09d2a989..00000000 --- a/lib/olm/errors.h +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#ifdef Quotient_E2EE_ENABLED -#include - -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/olm/message.cpp b/lib/olm/message.cpp deleted file mode 100644 index ac7038ae..00000000 --- a/lib/olm/message.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// 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)) - , 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/olm/message.h b/lib/olm/message.h deleted file mode 100644 index d2fe871e..00000000 --- a/lib/olm/message.h +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#ifdef Quotient_E2EE_ENABLED - -#include -#include - -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/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp deleted file mode 100644 index ef51a395..00000000 --- a/lib/olm/qolmaccount.cpp +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "qolmaccount.h" -#include "olm/utils.h" -#include -#include -#include -#include - -using namespace Quotient; - -QMap OneTimeKeys::curve25519() const -{ - return keys[QStringLiteral("curve25519")]; -} - -std::optional> 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(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 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(); - for (const QString &key2 : oneTimeKeyObject.keys()) { - keyMap[key2] = oneTimeKeyObject[key2].toString(); - } - oneTimeKeys.keys[key1.toString()] = keyMap; - } - return oneTimeKeys; -} - -QMap QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const -{ - QMap 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, OlmError> QOlmAccount::createInboundSession(const Message &preKeyMessage) -{ - Q_ASSERT(preKeyMessage.type() == Message::PreKey); - return QOlmSession::createInboundSession(this, preKeyMessage); -} - -std::variant, OlmError> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const Message &preKeyMessage) -{ - Q_ASSERT(preKeyMessage.type() == Message::PreKey); - return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); -} - -std::variant, OlmError> QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) -{ - return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); -} - -#endif diff --git a/lib/olm/qolmaccount.h b/lib/olm/qolmaccount.h deleted file mode 100644 index df5e1be2..00000000 --- a/lib/olm/qolmaccount.h +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later -#pragma once -#ifdef Quotient_E2EE_ENABLED - -#include "olm/e2ee.h" -#include "olm/errors.h" -#include "olm/olm.h" -#include "olm/session.h" -#include - -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 here. - //! 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 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 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, 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, 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, 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/olm/qolminboundsession.cpp b/lib/olm/qolminboundsession.cpp deleted file mode 100644 index 11558f51..00000000 --- a/lib/olm/qolminboundsession.cpp +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "olm/qolminboundsession.h" -#include -#include -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(m_groupSession)); -} - -std::unique_ptr 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(temp.data()), temp.size()); - - if (error == olm_error()) { - throw lastError(olmInboundGroupSession); - } - - return std::make_unique(olmInboundGroupSession); -} - - -std::unique_ptr 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(keyBuf.data()), keyBuf.size()); - if (error == olm_error()) { - throw lastError(olmInboundGroupSession); - } - - return std::make_unique(olmInboundGroupSession); -} - -QByteArray toKey(const PicklingMode &mode) -{ - if (std::holds_alternative(mode)) { - return ""; - } - return std::get(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, 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(groupSession); -} - -std::variant, 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(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(messageBuf.data()), - messageBuf.length(), reinterpret_cast(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(output), messageIndex); -} - -std::variant 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(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(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/olm/qolminboundsession.h b/lib/olm/qolminboundsession.h deleted file mode 100644 index 739a8411..00000000 --- a/lib/olm/qolminboundsession.h +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#ifdef Quotient_E2EE_ENABLED - -#include -#include -#include -#include "olm/olm.h" -#include "olm/errors.h" -#include "olm/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 create(const QByteArray &key); - //! Import an inbound group session, from a previous export. - static std::unique_ptr 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, OlmError> unpickle(QByteArray &picked, const PicklingMode &mode); - //! Decrypts ciphertext received for this group session. - std::variant, 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 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; -using OlmInboundGroupSessionPtr = std::unique_ptr; -} // namespace Quotient -#endif diff --git a/lib/olm/qolmoutboundsession.cpp b/lib/olm/qolmoutboundsession.cpp deleted file mode 100644 index e5c43495..00000000 --- a/lib/olm/qolmoutboundsession.cpp +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#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) - : m_groupSession(session) -{ -} - -QOlmOutboundGroupSession::~QOlmOutboundGroupSession() -{ - olm_clear_outbound_group_session(m_groupSession); - delete[](reinterpret_cast(m_groupSession)); -} - -std::unique_ptr 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(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(keyBuffer.data()), - keyMaxLength); - - randomBuf.clear(); - - return std::make_unique(olmOutboundGroupSession); -} - -std::variant 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, 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(idBuffer.data()), - idBuffer.length()); - - key.clear(); - return std::make_unique(olmOutboundGroupSession); -} - -std::variant 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(plaintextBuf.data()), - plaintextBuf.length(), reinterpret_cast(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(idBuffer.data()), - idBuffer.length()); - if (error == olm_error()) { - throw lastError(m_groupSession); - } - return idBuffer; -} - -std::variant 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(keyBuffer.data()), - keyMaxLength); - if (error == olm_error()) { - return lastError(m_groupSession); - } - return keyBuffer; -} - -#endif diff --git a/lib/olm/qolmoutboundsession.h b/lib/olm/qolmoutboundsession.h deleted file mode 100644 index 70c4d27f..00000000 --- a/lib/olm/qolmoutboundsession.h +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later -#pragma once -#ifdef Quotient_E2EE_ENABLED - -#include "olm/olm.h" // from Olm -#include "olm/errors.h" -#include "olm/e2ee.h" -#include - -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 create(); - //! Serialises an `QOlmOutboundGroupSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); - //! Deserialises from encrypted Base64 that was previously obtained by - //! pickling a `QOlmOutboundGroupSession`. - static std::variant, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); - //! Encrypts a plaintext message using the session. - std::variant 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 sessionKey() const; - QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); -private: - OlmOutboundGroupSession *m_groupSession; -}; - -using QOlmOutboundGroupSessionPtr = std::unique_ptr; -using OlmOutboundGroupSessionPtr = std::unique_ptr; -} -#endif diff --git a/lib/olm/qolmsession.cpp b/lib/olm/qolmsession.cpp deleted file mode 100644 index 32a108a8..00000000 --- a/lib/olm/qolmsession.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "olm/qolmsession.h" - -using namespace Quotient; - -std::optional 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 toPair(const OlmMessage &message) -{ - return std::visit([](auto &arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - return std::make_pair(MessageType, QByteArray(arg.message)); - } else if constexpr (std::is_same_v) { - return std::make_pair(PreKeyType, QByteArray(arg.message)); - } - }, message); -} diff --git a/lib/olm/qolmsession.h b/lib/olm/qolmsession.h deleted file mode 100644 index 08f47331..00000000 --- a/lib/olm/qolmsession.h +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "olm/e2ee.h" -#include "olm/olm.h" -#include "olm/errors.h" -#include - -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; - -std::optional fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext); - -std::pair toPair(const OlmMessage &message); - -//class QOlmSession -//{ -// /// Creates an inbound session for sending/receiving messages from a received 'prekey' message. -// static std::variant, OlmError> createInboundSession(const QOlmAccount &account, -// PreKeyMessage &message); -// -////private: -// //static std::variant, OlmError> createSessionWith(std::function> func); -//} - -} diff --git a/lib/olm/session.cpp b/lib/olm/session.cpp deleted file mode 100644 index 94f12db6..00000000 --- a/lib/olm/session.cpp +++ /dev/null @@ -1,242 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "olm/session.h" -#include "olm/utils.h" -#include "logging.h" -#include - -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(m_session)); -} - -OlmSession* QOlmSession::create() -{ - return olm_session(new uint8_t[olm_session_size()]); -} - -std::variant, 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(olmSession); -} - -std::variant, OlmError> QOlmSession::createInboundSession(QOlmAccount *account, const Message &preKeyMessage) -{ - return createInbound(account, preKeyMessage); -} - -std::variant, OlmError> QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage) -{ - return createInbound(account, preKeyMessage, true, theirIdentityKey); -} - -std::variant, 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(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), - reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), - reinterpret_cast(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(olmOutboundSession); -} - -std::variant 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, 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(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(plaintextBuf.data()), plaintextBuf.length(), - reinterpret_cast(randomBuf.data()), randomBuf.length(), - reinterpret_cast(messageBuf.data()), messageBuf.length()); - - if (error == olm_error()) { - throw lastError(m_session); - } - - return Message(messageBuf, messageType); -} - -std::variant 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(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(messageBuf2.data()), messageBuf2.length(), - reinterpret_cast(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(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 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/olm/session.h b/lib/olm/session.h deleted file mode 100644 index 03b3514e..00000000 --- a/lib/olm/session.h +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// 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 -#include "olm/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, OlmError> createInboundSession(QOlmAccount *account, const Message &preKeyMessage); - static std::variant, OlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage); - static std::variant, OlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey); - //! Serialises an `QOlmSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); - //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static std::variant, 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 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 matchesInboundSession(Message &preKeyMessage); - - friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) - { - return lhs.sessionId() < rhs.sessionId(); - } - - friend bool operator<(const std::unique_ptr &lhs, const std::unique_ptr &rhs) { - return *lhs < *rhs; - } - - QOlmSession(OlmSession* session); -private: - //! Helper function for creating new sessions and handling errors. - static OlmSession* create(); - static std::variant, OlmError> createInbound(QOlmAccount *account, const Message& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); - OlmSession* m_session; -}; - - -//using QOlmSessionPtr = std::unique_ptr; - -} //namespace Quotient - -#endif // Quotient_E2EE_ENABLED diff --git a/lib/olm/utils.cpp b/lib/olm/utils.cpp deleted file mode 100644 index 227e6d84..00000000 --- a/lib/olm/utils.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "olm/utils.h" -#include -#include - -using namespace Quotient; - -QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) -{ - if (std::holds_alternative(mode)) { - return ""; - } - return std::get(mode).key; -} - -QByteArray Quotient::getRandom(size_t bufferSize) -{ - QByteArray buffer(bufferSize, '0'); - RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); - return buffer; -} -#endif diff --git a/lib/olm/utils.h b/lib/olm/utils.h deleted file mode 100644 index 85d4605b..00000000 --- a/lib/olm/utils.h +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once -#ifdef Quotient_E2EE_ENABLED - -#include "olm/e2ee.h" - -namespace Quotient { -// Convert PicklingMode to key -QByteArray toKey(const PicklingMode &mode); -QByteArray getRandom(size_t bufferSize); -} -#endif -- cgit v1.2.3 From d72f220e3e3a3b243fdafd93d1405f8207dc516a Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Thu, 28 Jan 2021 23:51:56 +0300 Subject: E2EE: initial port to internal olm wrapper Remove qtolm git module. Update CMakeLists.txt. Rename olm to crypto subdir to prevent disambiguation. Rename internal files accordingly. Comment out not ported E2EE API usage. --- .gitmodules | 3 - 3rdparty/libQtOlm | 1 - CMakeLists.txt | 69 ++++------- autotests/testgroupsession.cpp | 6 +- autotests/testolmaccount.cpp | 6 +- autotests/testolmsession.cpp | 2 +- lib/connection.cpp | 12 +- lib/connection.h | 8 +- lib/crypto/e2ee.h | 7 +- lib/crypto/errors.cpp | 21 ---- lib/crypto/errors.h | 31 ----- lib/crypto/message.cpp | 42 ------- lib/crypto/message.h | 46 ------- lib/crypto/qolmaccount.cpp | 23 ++-- lib/crypto/qolmaccount.h | 15 +-- lib/crypto/qolmerrors.cpp | 21 ++++ lib/crypto/qolmerrors.h | 31 +++++ lib/crypto/qolminboundsession.cpp | 8 +- lib/crypto/qolminboundsession.h | 8 +- lib/crypto/qolmmessage.cpp | 42 +++++++ lib/crypto/qolmmessage.h | 46 +++++++ lib/crypto/qolmoutboundsession.cpp | 14 +-- lib/crypto/qolmoutboundsession.h | 12 +- lib/crypto/qolmsession.cpp | 246 ++++++++++++++++++++++++++++++++++--- lib/crypto/qolmsession.cpp.back | 29 +++++ lib/crypto/qolmsession.h | 92 +++++++++----- lib/crypto/qolmsession.h.back | 49 ++++++++ lib/crypto/qolmutils.cpp | 26 ++++ lib/crypto/qolmutils.h | 17 +++ lib/crypto/session.cpp | 242 ------------------------------------ lib/crypto/session.h | 77 ------------ lib/crypto/utils.cpp | 26 ---- lib/crypto/utils.h | 15 --- lib/encryptionmanager.cpp | 70 ++++++----- lib/encryptionmanager.h | 7 +- lib/room.cpp | 25 ++-- 36 files changed, 690 insertions(+), 705 deletions(-) delete mode 160000 3rdparty/libQtOlm delete mode 100644 lib/crypto/errors.cpp delete mode 100644 lib/crypto/errors.h delete mode 100644 lib/crypto/message.cpp delete mode 100644 lib/crypto/message.h create mode 100644 lib/crypto/qolmerrors.cpp create mode 100644 lib/crypto/qolmerrors.h create mode 100644 lib/crypto/qolmmessage.cpp create mode 100644 lib/crypto/qolmmessage.h create mode 100644 lib/crypto/qolmsession.cpp.back create mode 100644 lib/crypto/qolmsession.h.back create mode 100644 lib/crypto/qolmutils.cpp create mode 100644 lib/crypto/qolmutils.h delete mode 100644 lib/crypto/session.cpp delete mode 100644 lib/crypto/session.h delete mode 100644 lib/crypto/utils.cpp delete mode 100644 lib/crypto/utils.h (limited to 'autotests') diff --git a/.gitmodules b/.gitmodules index eb4c1815..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "3rdparty/libQtOlm"] - path = 3rdparty/libQtOlm - url = https://gitlab.com/b0/libqtolm.git diff --git a/3rdparty/libQtOlm b/3rdparty/libQtOlm deleted file mode 160000 index f2d8e235..00000000 --- a/3rdparty/libQtOlm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f2d8e235a4af0625fdedaaf727fef5d51293bf1b diff --git a/CMakeLists.txt b/CMakeLists.txt index 40767573..8f62af68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,46 +88,26 @@ get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") if (${PROJECT_NAME}_ENABLE_E2EE) + find_package(Olm 3.2.1 REQUIRED) + set_package_properties(Olm PROPERTIES + DESCRIPTION "Implementation of the Olm and Megolm cryptographic ratchets" + URL "https://gitlab.matrix.org/matrix-org/olm" + TYPE REQUIRED + ) + if (Olm_FOUND) + message(STATUS "Using libOlm ${Olm_VERSION} at ${Olm_DIR}") + endif() + find_package(OpenSSL 1.1.0 REQUIRED) set_package_properties(OpenSSL PROPERTIES DESCRIPTION "Open source SSL and TLS implementation and cryptographic library" URL "https://www.openssl.org/" TYPE REQUIRED ) - - if ((NOT DEFINED USE_INTREE_LIBQOLM OR USE_INTREE_LIBQOLM) - AND EXISTS ${PROJECT_SOURCE_DIR}/3rdparty/libQtOlm/lib/utils.h) - add_subdirectory(3rdparty/libQtOlm) - include_directories(3rdparty/libQtOlm) - if (NOT DEFINED USE_INTREE_LIBQOLM) - set (USE_INTREE_LIBQOLM 1) - endif () - endif () - if (USE_INTREE_LIBQOLM) - message( STATUS "Using in-tree libQtOlm") - find_package(Git QUIET) - if (GIT_FOUND) - execute_process(COMMAND - "${GIT_EXECUTABLE}" rev-parse -q HEAD - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/3rdparty/libQtOlm - OUTPUT_VARIABLE QTOLM_GIT_SHA1 - OUTPUT_STRIP_TRAILING_WHITESPACE) - message( STATUS " Library git SHA1: ${QTOLM_GIT_SHA1}") - endif (GIT_FOUND) - else () - set(SAVED_CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}) - set(CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}) - find_package(QtOlm 3.0.1 REQUIRED) - set_package_properties(QtOlm PROPERTIES - DESCRIPTION "QtOlm is a Qt wrapper around libOlm" - PURPOSE "libQtOlm is required to support end-to-end encryption. See also BUILDING.md" - URL "https://gitlab.com/b0/libqtolm" - ) - if (QtOlm_FOUND) - message(STATUS "Using libQtOlm ${QtOlm_VERSION} at ${QtOlm_DIR}") - endif() - endif () -endif () + if (OpenSSL_FOUND) + message(STATUS "Using OpenSSL ${OpenSSL_VERSION} at ${OpenSSL_DIR}") + endif() +endif() # Set up source files list(APPEND lib_SRCS @@ -182,10 +162,10 @@ list(APPEND lib_SRCS lib/crypto/qolmsession.cpp lib/crypto/qolminboundsession.cpp lib/crypto/qolmoutboundsession.cpp - lib/crypto/utils.cpp - lib/crypto/errors.cpp - lib/crypto/session.cpp - lib/crypto/message.cpp + lib/crypto/qolmutils.cpp + lib/crypto/qolmerrors.cpp + lib/crypto/qolmsession.cpp + lib/crypto/qolmmessage.cpp ) # Configure API files generation @@ -332,16 +312,13 @@ target_include_directories(${PROJECT_NAME} PUBLIC $ ) if (${PROJECT_NAME}_ENABLE_E2EE) - target_link_libraries(${PROJECT_NAME} Olm::Olm QtOlm) - set(FIND_DEPS "find_dependency(QtOlm)") # For QuotientConfig.cmake.in + target_link_libraries(${PROJECT_NAME} Olm::Olm + OpenSSL::Crypto + OpenSSL::SSL) + set(FIND_DEPS "find_dependency(Olm OpenSSL)") # For QuotientConfig.cmake.in endif() -target_link_libraries(${PROJECT_NAME} - ${Qt}::Core - ${Qt}::Network - ${Qt}::Gui - OpenSSL::Crypto - OpenSSL::SSL) +target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) if (Qt STREQUAL Qt5) # See #483 target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 325ca2ec..858f29d8 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -3,9 +3,9 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testgroupsession.h" -#include -#include -#include +#include "crypto/qolminboundsession.h" +#include "crypto/qolmoutboundsession.h" +#include "crypto/qolmutils.h" using namespace Quotient; diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index cbce845a..a4dfd7b5 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -3,9 +3,9 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testolmaccount.h" -#include -#include -#include +#include "crypto/qolmaccount.h" +#include "csapi/definitions/device_keys.h" +#include "events/encryptedfile.h" using namespace Quotient; diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 462c8213..6535e4fe 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include +#include "crypto/qolmsession.h" #include "testolmsession.h" using namespace Quotient; diff --git a/lib/connection.cpp b/lib/connection.cpp index e65fdac4..f96eeb71 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -38,7 +38,7 @@ #include "jobs/syncjob.h" #ifdef Quotient_E2EE_ENABLED -# include "account.h" // QtOlm +# include "crypto/qolmaccount.h" #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -191,7 +191,7 @@ public: return {}; const auto identityKey = - encryptionManager->account()->curve25519IdentityKey(); + encryptionManager->account()->identityKeys().curve25519; const auto personalCipherObject = encryptedEvent.ciphertext(identityKey); if (personalCipherObject.isEmpty()) { @@ -203,7 +203,7 @@ public: if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" << encryptedEvent.senderKey() - << encryptionManager->account()->oneTimeKeys(); + << encryptionManager->account()->oneTimeKeys().keys; return {}; } @@ -232,10 +232,10 @@ public: .value(Ed25519Key).toString(); if (ourKey != QString::fromUtf8( - encryptionManager->account()->ed25519IdentityKey())) { + encryptionManager->account()->identityKeys().ed25519)) { qCDebug(E2EE) << "Found key" << ourKey << "instead of ours own ed25519 key" - << encryptionManager->account()->ed25519IdentityKey() + << encryptionManager->account()->identityKeys().ed25519 << "in Olm plaintext"; return {}; } @@ -1226,7 +1226,7 @@ QByteArray Connection::accessToken() const bool Connection::isLoggedIn() const { return !accessToken().isEmpty(); } #ifdef Quotient_E2EE_ENABLED -QtOlm::Account* Connection::olmAccount() const +QOlmAccount *Connection::olmAccount() const { return d->encryptionManager->account(); } diff --git a/lib/connection.h b/lib/connection.h index 05a3bb7f..6729b23d 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -21,10 +21,6 @@ #include -namespace QtOlm { -class Account; -} - Q_DECLARE_METATYPE(Quotient::GetLoginFlowsJob::LoginFlow) namespace Quotient { @@ -48,6 +44,8 @@ class SendToDeviceJob; class SendMessageJob; class LeaveRoomJob; +class QOlmAccount; + using LoginFlow = GetLoginFlowsJob::LoginFlow; /// Predefined login flows @@ -310,7 +308,7 @@ public: QByteArray accessToken() const; bool isLoggedIn() const; #ifdef Quotient_E2EE_ENABLED - QtOlm::Account* olmAccount() const; + QOlmAccount* olmAccount() const; #endif // Quotient_E2EE_ENABLED Q_INVOKABLE Quotient::SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; diff --git a/lib/crypto/e2ee.h b/lib/crypto/e2ee.h index 74f876e4..73dd7f65 100644 --- a/lib/crypto/e2ee.h +++ b/lib/crypto/e2ee.h @@ -5,15 +5,17 @@ #pragma once -#include "util.h" #include #include #include -#include +#include #include +#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; @@ -37,7 +39,6 @@ inline const auto MegolmV1AesSha2AlgoKey = QStringLiteral("m.megolm.v1.aes-sha2"); inline const QStringList SupportedAlgorithms = { OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey }; - struct Unencrypted {}; struct Encrypted { QByteArray key; diff --git a/lib/crypto/errors.cpp b/lib/crypto/errors.cpp deleted file mode 100644 index 00ff962d..00000000 --- a/lib/crypto/errors.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// 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 deleted file mode 100644 index 09d2a989..00000000 --- a/lib/crypto/errors.h +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#ifdef Quotient_E2EE_ENABLED -#include - -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 deleted file mode 100644 index 830633bf..00000000 --- a/lib/crypto/message.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// 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 deleted file mode 100644 index 1ae19ba8..00000000 --- a/lib/crypto/message.h +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#ifdef Quotient_E2EE_ENABLED - -#include -#include - -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 index 8824e7ef..fc0fc1cf 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -3,8 +3,8 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #ifdef Quotient_E2EE_ENABLED -#include "crypto/qolmaccount.h" -#include "crypto/utils.h" +#include "qolmaccount.h" +#include "crypto/qolmutils.h" #include #include #include @@ -31,7 +31,7 @@ bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) } // Convert olm error to enum -OlmError lastError(OlmAccount *account) { +QOlmError lastError(OlmAccount *account) { const std::string error_raw = olm_account_last_error(account); return fromString(error_raw); @@ -77,7 +77,7 @@ void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) } } -std::variant QOlmAccount::pickle(const PicklingMode &mode) +std::variant QOlmAccount::pickle(const PicklingMode &mode) { const QByteArray key = toKey(mode); const size_t pickleLength = olm_pickle_account_length(m_account); @@ -118,6 +118,11 @@ QByteArray QOlmAccount::sign(const QByteArray &message) const return signatureBuffer; } +QByteArray QOlmAccount::sign(const QJsonObject &message) const +{ + return sign(QJsonDocument(message).toJson(QJsonDocument::Compact)); +} + QByteArray QOlmAccount::signIdentityKeys() const { const auto keys = identityKeys(); @@ -197,19 +202,19 @@ OlmAccount *Quotient::QOlmAccount::data() return m_account; } -std::variant, OlmError> QOlmAccount::createInboundSession(const Message &preKeyMessage) +std::variant, QOlmError> QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) { - Q_ASSERT(preKeyMessage.type() == Message::PreKey); + Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSession(this, preKeyMessage); } -std::variant, OlmError> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const Message &preKeyMessage) +std::variant, QOlmError> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) { - Q_ASSERT(preKeyMessage.type() == Message::PreKey); + Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); } -std::variant, OlmError> QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) +std::variant, QOlmError> QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) { return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); } diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index f98d78ba..b33e3768 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -5,9 +5,9 @@ #ifdef Quotient_E2EE_ENABLED #include "crypto/e2ee.h" -#include "crypto/errors.h" -#include "crypto/session.h" -#include +#include "crypto/qolmerrors.h" +#include "crypto/qolmmessage.h" +#include "crypto/qolmsession.h" #include struct OlmAccount; @@ -38,13 +38,14 @@ public: void unpickle(QByteArray &picked, const PicklingMode &mode); //! Serialises an OlmAccount to encrypted Base64. - std::variant pickle(const PicklingMode &mode); + std::variant 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; @@ -70,17 +71,17 @@ public: //! 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, OlmError> createInboundSession(const Message &preKeyMessage); + std::variant, QOlmError> createInboundSession(const QOlmMessage &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, OlmError> createInboundSessionFrom(const QByteArray &theirIdentityKey, const Message &preKeyMessage); + std::variant, 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, OlmError> createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); + std::variant, QOlmError> createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); // HACK do not use directly QOlmAccount(OlmAccount *account); diff --git a/lib/crypto/qolmerrors.cpp b/lib/crypto/qolmerrors.cpp new file mode 100644 index 00000000..f407383e --- /dev/null +++ b/lib/crypto/qolmerrors.cpp @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later +#ifdef Quotient_E2EE_ENABLED +#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; + } +} +#endif diff --git a/lib/crypto/qolmerrors.h b/lib/crypto/qolmerrors.h new file mode 100644 index 00000000..400573c6 --- /dev/null +++ b/lib/crypto/qolmerrors.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED +#include + +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 + +#endif diff --git a/lib/crypto/qolminboundsession.cpp b/lib/crypto/qolminboundsession.cpp index 539fdc51..8f5056d8 100644 --- a/lib/crypto/qolminboundsession.cpp +++ b/lib/crypto/qolminboundsession.cpp @@ -8,7 +8,7 @@ #include using namespace Quotient; -OlmError lastError(OlmInboundGroupSession *session) { +QOlmError lastError(OlmInboundGroupSession *session) { const std::string error_raw = olm_inbound_group_session_last_error(session); std::cout << error_raw; @@ -75,7 +75,7 @@ QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const return pickledBuf; } -std::variant, OlmError> QOlmInboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +std::variant, QOlmError> 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()]); @@ -90,7 +90,7 @@ std::variant, OlmError> QOlmInboundGrou return std::make_unique(groupSession); } -std::variant, OlmError> QOlmInboundGroupSession::decrypt(const QByteArray &message) +std::variant, QOlmError> QOlmInboundGroupSession::decrypt(const QByteArray &message) { // This is for capturing the output of olm_group_decrypt uint32_t messageIndex = 0; @@ -122,7 +122,7 @@ std::variant, OlmError> QOlmInboundGroupSession::de return std::make_pair(QString(output), messageIndex); } -std::variant QOlmInboundGroupSession::exportSession(uint32_t messageIndex) +std::variant QOlmInboundGroupSession::exportSession(uint32_t messageIndex) { const auto keyLen = olm_export_inbound_group_session_length(m_groupSession); QByteArray keyBuf(keyLen, '0'); diff --git a/lib/crypto/qolminboundsession.h b/lib/crypto/qolminboundsession.h index a58fbbbc..6af71cbd 100644 --- a/lib/crypto/qolminboundsession.h +++ b/lib/crypto/qolminboundsession.h @@ -10,7 +10,7 @@ #include #include #include "olm/olm.h" -#include "crypto/errors.h" +#include "crypto/qolmerrors.h" #include "crypto/e2ee.h" namespace Quotient { @@ -29,12 +29,12 @@ public: QByteArray pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. - static std::variant, OlmError> unpickle(QByteArray &picked, const PicklingMode &mode); + static std::variant, QOlmError> unpickle(QByteArray &picked, const PicklingMode &mode); //! Decrypts ciphertext received for this group session. - std::variant, OlmError> decrypt(const QByteArray &message); + std::variant, 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 exportSession(uint32_t messageIndex); + std::variant exportSession(uint32_t messageIndex); //! Get the first message index we know how to decrypt. uint32_t firstKnownIndex() const; //! Get a base64-encoded identifier for this session. diff --git a/lib/crypto/qolmmessage.cpp b/lib/crypto/qolmmessage.cpp new file mode 100644 index 00000000..ae98d52f --- /dev/null +++ b/lib/crypto/qolmmessage.cpp @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#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); +} + + +#endif // Quotient_E2EE_ENABLED + + + diff --git a/lib/crypto/qolmmessage.h b/lib/crypto/qolmmessage.h new file mode 100644 index 00000000..d203364d --- /dev/null +++ b/lib/crypto/qolmmessage.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED + +#include +#include + +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 + +#endif // Quotient_E2EE_ENABLED diff --git a/lib/crypto/qolmoutboundsession.cpp b/lib/crypto/qolmoutboundsession.cpp index 3bfb0187..14b7368e 100644 --- a/lib/crypto/qolmoutboundsession.cpp +++ b/lib/crypto/qolmoutboundsession.cpp @@ -3,12 +3,12 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #ifdef Quotient_E2EE_ENABLED -#include "crypto/qolmoutboundsession.h" -#include "crypto/utils.h" +#include "qolmoutboundsession.h" +#include "crypto/qolmutils.h" using namespace Quotient; -OlmError lastError(OlmOutboundGroupSession *session) { +QOlmError lastError(OlmOutboundGroupSession *session) { const std::string error_raw = olm_outbound_group_session_last_error(session); return fromString(error_raw); @@ -48,7 +48,7 @@ std::unique_ptr QOlmOutboundGroupSession::create() return std::make_unique(olmOutboundGroupSession); } -std::variant QOlmOutboundGroupSession::pickle(const PicklingMode &mode) +std::variant QOlmOutboundGroupSession::pickle(const PicklingMode &mode) { QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); QByteArray key = toKey(mode); @@ -65,7 +65,7 @@ std::variant QOlmOutboundGroupSession::pickle(const Pickli } -std::variant, OlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +std::variant, 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()]); @@ -84,7 +84,7 @@ std::variant, OlmError> QOlmOutboundGr return std::make_unique(olmOutboundGroupSession); } -std::variant QOlmOutboundGroupSession::encrypt(const QString &plaintext) +std::variant QOlmOutboundGroupSession::encrypt(const QString &plaintext) { QByteArray plaintextBuf = plaintext.toUtf8(); const auto messageMaxLen = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); @@ -116,7 +116,7 @@ QByteArray QOlmOutboundGroupSession::sessionId() const return idBuffer; } -std::variant QOlmOutboundGroupSession::sessionKey() const +std::variant QOlmOutboundGroupSession::sessionKey() const { const auto keyMaxLength = olm_outbound_group_session_key_length(m_groupSession); QByteArray keyBuffer(keyMaxLength, '0'); diff --git a/lib/crypto/qolmoutboundsession.h b/lib/crypto/qolmoutboundsession.h index 6c6c635c..6b4fd30b 100644 --- a/lib/crypto/qolmoutboundsession.h +++ b/lib/crypto/qolmoutboundsession.h @@ -4,8 +4,8 @@ #pragma once #ifdef Quotient_E2EE_ENABLED -#include "olm/olm.h" // from Olm -#include "crypto/errors.h" +#include "olm/olm.h" +#include "crypto/qolmerrors.h" #include "crypto/e2ee.h" #include @@ -22,12 +22,12 @@ public: //! Throw OlmError on errors static std::unique_ptr create(); //! Serialises an `QOlmOutboundGroupSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); + std::variant pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. - static std::variant, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); + static std::variant, QOlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); //! Encrypts a plaintext message using the session. - std::variant encrypt(const QString &plaintext); + std::variant encrypt(const QString &plaintext); //! Get the current message index for this session. //! @@ -42,7 +42,7 @@ public: //! //! Each message is sent with a different ratchet key. This function returns the //! ratchet key that will be used for the next message. - std::variant sessionKey() const; + std::variant sessionKey() const; QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); private: OlmOutboundGroupSession *m_groupSession; diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp index afa42728..cfe21650 100644 --- a/lib/crypto/qolmsession.cpp +++ b/lib/crypto/qolmsession.cpp @@ -1,29 +1,243 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-FileCopyrightText: 2021 Alexey Andreyev // // SPDX-License-Identifier: LGPL-2.1-or-later -#include "crypto/qolmsession.h" +#ifdef Quotient_E2EE_ENABLED +#include "qolmsession.h" +#include "crypto/qolmutils.h" +#include "logging.h" +#include +#include using namespace Quotient; -std::optional fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext) +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(m_session)); +} + +OlmSession* QOlmSession::create() { - if (messageType == OLM_MESSAGE_TYPE_PRE_KEY) { - return PreKeyMessage { ciphertext }; - } else if (messageType == OLM_MESSAGE_TYPE_MESSAGE) { - return Message { ciphertext }; + return olm_session(new uint8_t[olm_session_size()]); +} + +std::variant, QOlmError> QOlmSession::createInbound(QOlmAccount *account, const QOlmMessage &preKeyMessage, bool from, const QString &theirIdentityKey) +{ + if (preKeyMessage.type() != QOlmMessage::PreKey) { + qCDebug(E2EE) << "The message is not a pre-key"; + throw BadMessageFormat; } - return std::nullopt; + + 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 == QOlmError::NotEnoughRandom) { + throw lastErr; + } + return lastErr; + } + + return std::make_unique(olmSession); } -std::pair toPair(const OlmMessage &message) +std::variant, QOlmError> QOlmSession::createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage) { - return std::visit([](auto &arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - return std::make_pair(MessageType, QByteArray(arg.message)); - } else if constexpr (std::is_same_v) { - return std::make_pair(PreKeyType, QByteArray(arg.message)); + return createInbound(account, preKeyMessage); +} + +std::variant, QOlmError> QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) +{ + return createInbound(account, preKeyMessage, true, theirIdentityKey); +} + +std::variant, 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(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), + reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length()); + + if (error == olm_error()) { + const auto lastErr = lastError(olmOutboundSession); + if (lastErr == QOlmError::NotEnoughRandom) { + throw lastErr; } - }, message); + return lastErr; + } + + randomBuf.clear(); + return std::make_unique(olmOutboundSession); +} + +std::variant 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, 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(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(plaintextBuf.data()), plaintextBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length(), + reinterpret_cast(messageBuf.data()), messageBuf.length()); + + if (error == olm_error()) { + throw lastError(m_session); + } + + return QOlmMessage(messageBuf, messageType); +} + +std::variant 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(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(messageBuf2.data()), messageBuf2.length(), + reinterpret_cast(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(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 QOlmSession::matchesInboundSession(QOlmMessage &preKeyMessage) +{ + 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; + } +} + +QOlmSession::QOlmSession(OlmSession *session) + : m_session(session) +{ +} + +#endif // Quotient_E2EE_ENABLED + + + diff --git a/lib/crypto/qolmsession.cpp.back b/lib/crypto/qolmsession.cpp.back new file mode 100644 index 00000000..ee8b2a7f --- /dev/null +++ b/lib/crypto/qolmsession.cpp.back @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "olm/qolmsession.h" + +using namespace Quotient; + +std::optional 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 QOlmMessage { ciphertext }; + } + return std::nullopt; +} + +std::pair toPair(const OlmMessage &message) +{ + return std::visit([](auto &arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return std::make_pair(MessageType, QByteArray(arg.message)); + } else if constexpr (std::is_same_v) { + return std::make_pair(PreKeyType, QByteArray(arg.message)); + } + }, message); +} diff --git a/lib/crypto/qolmsession.h b/lib/crypto/qolmsession.h index 3be3c7fc..6e13801e 100644 --- a/lib/crypto/qolmsession.h +++ b/lib/crypto/qolmsession.h @@ -1,49 +1,77 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-FileCopyrightText: 2021 Alexey Andreyev // // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once -#include -#include +#ifdef Quotient_E2EE_ENABLED + +#include +#include // FIXME: OlmSession #include "crypto/e2ee.h" -#include "crypto/errors.h" +#include "crypto/qolmmessage.h" +#include "crypto/qolmerrors.h" +#include "crypto/qolmaccount.h" namespace Quotient { -//! An encrypted Olm message. -struct Message { - QByteArray message; -}; +class QOlmAccount; +class QOlmSession; -//! 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 +//! Either an outbound or inbound session for secure communication. +class QOlmSession { - PreKeyType, - MessageType, -}; +public: + ~QOlmSession(); + //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. + static std::variant, QOlmError> createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage); + static std::variant, QOlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage); + static std::variant, QOlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey); + //! Serialises an `QOlmSession` to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. + static std::variant, QOlmError> unpickle(const QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + QOlmMessage encrypt(const QString &plaintext); -using OlmMessage = std::variant; + //! 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 decrypt(const QOlmMessage &message) const; -std::optional fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext); + //! Get a base64-encoded identifier for this session. + QByteArray sessionId() const; -std::pair toPair(const OlmMessage &message); + //! The type of the next message that will be returned from encryption. + QOlmMessage::Type encryptMessageType(); -//class QOlmSession -//{ -// /// Creates an inbound session for sending/receiving messages from a received 'prekey' message. -// static std::variant, OlmError> createInboundSession(const QOlmAccount &account, -// PreKeyMessage &message); -// -////private: -// //static std::variant, OlmError> createSessionWith(std::function> func); -//} + //! Checker for any received messages for this session. + bool hasReceivedMessage() const; + + //! Checks if the 'prekey' message is for this in-bound session. + std::variant matchesInboundSession(QOlmMessage &preKeyMessage); + + friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) + { + return lhs.sessionId() < rhs.sessionId(); + } + + friend bool operator<(const std::unique_ptr &lhs, const std::unique_ptr &rhs) { + return *lhs < *rhs; + } + + QOlmSession(OlmSession* session); +private: + //! Helper function for creating new sessions and handling errors. + static OlmSession* create(); + static std::variant, QOlmError> createInbound(QOlmAccount *account, const QOlmMessage& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); + OlmSession* m_session; +}; + + +//using QOlmSessionPtr = std::unique_ptr; + +} //namespace Quotient -} +#endif // Quotient_E2EE_ENABLED diff --git a/lib/crypto/qolmsession.h.back b/lib/crypto/qolmsession.h.back new file mode 100644 index 00000000..cbba5cef --- /dev/null +++ b/lib/crypto/qolmsession.h.back @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "olm/e2ee.h" +#include "olm/olm.h" +#include "olm/errors.h" +#include + +namespace Quotient { + +//! An encrypted Olm message. +struct QOlmMessage { + 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; + +std::optional fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext); + +std::pair toPair(const OlmMessage &message); + +//class QOlmSession +//{ +// /// Creates an inbound session for sending/receiving messages from a received 'prekey' message. +// static std::variant, OlmError> createInboundSession(const QOlmAccount &account, +// PreKeyMessage &message); +// +////private: +// //static std::variant, OlmError> createSessionWith(std::function> func); +//} + +} diff --git a/lib/crypto/qolmutils.cpp b/lib/crypto/qolmutils.cpp new file mode 100644 index 00000000..a486ea0f --- /dev/null +++ b/lib/crypto/qolmutils.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/qolmutils.h" +#include +#include + +using namespace Quotient; + +QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) +{ + if (std::holds_alternative(mode)) { + return ""; + } + return std::get(mode).key; +} + +QByteArray Quotient::getRandom(size_t bufferSize) +{ + QByteArray buffer(bufferSize, '0'); + RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); + return buffer; +} +#endif diff --git a/lib/crypto/qolmutils.h b/lib/crypto/qolmutils.h new file mode 100644 index 00000000..11e9f3cc --- /dev/null +++ b/lib/crypto/qolmutils.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once +#ifdef Quotient_E2EE_ENABLED + +#include + +#include "crypto/e2ee.h" + +namespace Quotient { +// Convert PicklingMode to key +QByteArray toKey(const PicklingMode &mode); +QByteArray getRandom(size_t bufferSize); +} +#endif diff --git a/lib/crypto/session.cpp b/lib/crypto/session.cpp deleted file mode 100644 index 8b2cb022..00000000 --- a/lib/crypto/session.cpp +++ /dev/null @@ -1,242 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "crypto/session.h" -#include "crypto/utils.h" -#include "logging.h" -#include - -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(m_session)); -} - -OlmSession* QOlmSession::create() -{ - return olm_session(new uint8_t[olm_session_size()]); -} - -std::variant, 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(olmSession); -} - -std::variant, OlmError> QOlmSession::createInboundSession(QOlmAccount *account, const Message &preKeyMessage) -{ - return createInbound(account, preKeyMessage); -} - -std::variant, OlmError> QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage) -{ - return createInbound(account, preKeyMessage, true, theirIdentityKey); -} - -std::variant, 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(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), - reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), - reinterpret_cast(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(olmOutboundSession); -} - -std::variant 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, 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(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(plaintextBuf.data()), plaintextBuf.length(), - reinterpret_cast(randomBuf.data()), randomBuf.length(), - reinterpret_cast(messageBuf.data()), messageBuf.length()); - - if (error == olm_error()) { - throw lastError(m_session); - } - - return Message(messageBuf, messageType); -} - -std::variant 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(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(messageBuf2.data()), messageBuf2.length(), - reinterpret_cast(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(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 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 deleted file mode 100644 index 24702564..00000000 --- a/lib/crypto/session.h +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#ifdef Quotient_E2EE_ENABLED - -#include -#include -#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, OlmError> createInboundSession(QOlmAccount *account, const Message &preKeyMessage); - static std::variant, OlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage); - static std::variant, OlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey); - //! Serialises an `QOlmSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); - //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static std::variant, 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 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 matchesInboundSession(Message &preKeyMessage); - - friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) - { - return lhs.sessionId() < rhs.sessionId(); - } - - friend bool operator<(const std::unique_ptr &lhs, const std::unique_ptr &rhs) { - return *lhs < *rhs; - } - - QOlmSession(OlmSession* session); -private: - //! Helper function for creating new sessions and handling errors. - static OlmSession* create(); - static std::variant, OlmError> createInbound(QOlmAccount *account, const Message& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); - OlmSession* m_session; -}; - - -//using QOlmSessionPtr = std::unique_ptr; - -} //namespace Quotient - -#endif // Quotient_E2EE_ENABLED diff --git a/lib/crypto/utils.cpp b/lib/crypto/utils.cpp deleted file mode 100644 index cb20abf8..00000000 --- a/lib/crypto/utils.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "crypto/utils.h" -#include -#include - -using namespace Quotient; - -QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) -{ - if (std::holds_alternative(mode)) { - return ""; - } - return std::get(mode).key; -} - -QByteArray Quotient::getRandom(size_t bufferSize) -{ - QByteArray buffer(bufferSize, '0'); - RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); - return buffer; -} -#endif diff --git a/lib/crypto/utils.h b/lib/crypto/utils.h deleted file mode 100644 index cea87144..00000000 --- a/lib/crypto/utils.h +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// 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 diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 569d369a..8081f788 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -13,16 +13,15 @@ #include #include -#include // QtOlm -#include // QtOlm -#include // QtOlm -#include // QtOlm -#include // QtOlm +#include "crypto/qolmaccount.h" +#include "crypto/qolmsession.h" +#include "crypto/qolmmessage.h" +#include "crypto/qolmerrors.h" +#include "crypto/qolmutils.h" #include #include using namespace Quotient; -using namespace QtOlm; using std::move; class EncryptionManager::Private { @@ -36,11 +35,9 @@ public: Q_ASSERT((0 <= signedKeysProportion) && (signedKeysProportion <= 1)); Q_ASSERT((0 <= oneTimeKeyThreshold) && (oneTimeKeyThreshold <= 1)); if (encryptionAccountPickle.isEmpty()) { - olmAccount.reset(new Account()); + // new e2ee TODO: olmAccount.reset(new QOlmAccount()); } else { - olmAccount.reset( - new Account(encryptionAccountPickle)); // TODO: passphrase even - // with qtkeychain? + // new e2ee TODO: olmAccount.reset(new QOlmAccount(encryptionAccountPickle)); // TODO: passphrase even with qtkeychain? } /* * Note about targetKeysNumber: @@ -54,7 +51,7 @@ public: * until the limit is reached and it starts discarding keys, starting by * the oldest. */ - targetKeysNumber = olmAccount->maxOneTimeKeys() / 2; + targetKeysNumber = olmAccount->maxNumberOfOneTimeKeys() / 2; targetOneTimeKeyCounts = { { SignedCurve25519Key, qRound(signedKeysProportion * targetKeysNumber) }, @@ -72,7 +69,7 @@ public: UploadKeysJob* uploadOneTimeKeysJob = nullptr; QueryKeysJob* queryKeysJob = nullptr; - QScopedPointer olmAccount; + QScopedPointer olmAccount; float signedKeysProportion; float oneTimeKeyThreshold; @@ -91,7 +88,7 @@ public: QHash targetOneTimeKeyCounts; // A map from senderKey to InboundSession - QMap sessions; // TODO: cache + QMap sessions; // TODO: cache void updateDeviceKeys( const QHash>& deviceKeys) @@ -103,13 +100,15 @@ public: } } } - QString sessionDecrypt(Message* message, const QString& senderKey) + QString sessionDecrypt(QOlmMessage* message, const QString& senderKey) { QString decrypted; - QList senderSessions = sessions.values(senderKey); + QList senderSessions = sessions.values(senderKey); // Try to decrypt message body using one of the known sessions for that // device bool sessionsPassed = false; + // new e2ee TODO: + /* for (auto senderSession : senderSessions) { if (senderSession == senderSessions.last()) { sessionsPassed = true; @@ -120,11 +119,9 @@ public: << "Success decrypting Olm event using existing session" << senderSession->id(); break; - } catch (OlmError* e) { - if (message->messageType() == 0) { - PreKeyMessage preKeyMessage = - PreKeyMessage(message->cipherText()); - if (senderSession->matches(&preKeyMessage, senderKey)) { + } catch (QOlmError* e) { + if (message->type() == QOlmMessage::PreKey) { + if (senderSession->matches(&message, senderKey)) { // We had a matching session for a pre-key message, but // it didn't work. This means something is wrong, so we // fail now. @@ -138,8 +135,9 @@ public: // Simply keep trying otherwise } } + */ if (sessionsPassed || senderSessions.empty()) { - if (message->messageType() > 0) { + if (message->type() != QOlmMessage::PreKey) { // Not a pre-key message, we should have had a matching session if (!sessions.empty()) { qCDebug(E2EE) << "Error decrypting with existing sessions"; @@ -150,9 +148,11 @@ public: } // We have a pre-key message without any matching session, in this // case we should try to create one. - InboundSession* newSession; + QOlmSession* newSession; qCDebug(E2EE) << "try to establish new InboundSession with" << senderKey; - PreKeyMessage preKeyMessage = PreKeyMessage(message->cipherText()); + QOlmMessage preKeyMessage = QOlmMessage(message->toCiphertext(),QOlmMessage::PreKey); + // new e2ee TODO: + /* try { newSession = new InboundSession(olmAccount.data(), &preKeyMessage, @@ -172,7 +172,9 @@ public: << e->what(); return QString(); } + olmAccount->removeOneTimeKeys(newSession); + */ sessions.insert(senderKey, newSession); } return decrypted; @@ -211,9 +213,9 @@ void EncryptionManager::uploadIdentityKeys(Connection* connection) * as specified by the key algorithm. */ { { Curve25519Key + QStringLiteral(":") + connection->deviceId(), - d->olmAccount->curve25519IdentityKey() }, + d->olmAccount->identityKeys().curve25519 }, { Ed25519Key + QStringLiteral(":") + connection->deviceId(), - d->olmAccount->ed25519IdentityKey() } }, + d->olmAccount->identityKeys().curve25519 } }, /* signatures should be provided after the unsigned deviceKeys generation */ {} @@ -262,8 +264,7 @@ void EncryptionManager::uploadOneTimeKeys(Connection* connection, + unsignedKeysToUploadCount); QHash oneTimeKeys = {}; - const auto& olmAccountCurve25519OneTimeKeys = - d->olmAccount->curve25519OneTimeKeys(); + const auto& olmAccountCurve25519OneTimeKeys = d->olmAccount->oneTimeKeys().curve25519(); int oneTimeKeysCounter = 0; for (auto it = olmAccountCurve25519OneTimeKeys.cbegin(); @@ -273,7 +274,7 @@ void EncryptionManager::uploadOneTimeKeys(Connection* connection, QVariant key; if (oneTimeKeysCounter < signedKeysToUploadCount) { QJsonObject message { { QStringLiteral("key"), - it.value().toString() } }; + it.value() } }; QByteArray signedMessage = d->olmAccount->sign(message); QJsonObject signatures { @@ -297,7 +298,7 @@ void EncryptionManager::uploadOneTimeKeys(Connection* connection, connect(d->uploadOneTimeKeysJob, &BaseJob::success, this, [this] { d->setOneTimeKeyCounts(d->uploadOneTimeKeysJob->oneTimeKeyCounts()); }); - d->olmAccount->markKeysAsPublished(); + // new e2ee TODO: d->olmAccount->markKeysAsPublished(); qCDebug(E2EE) << QString("Uploaded new one-time keys: %1 signed, %2 unsigned.") .arg(signedKeysToUploadCount) .arg(unsignedKeysToUploadCount); @@ -328,11 +329,11 @@ QString EncryptionManager::sessionDecryptMessage( int type = personalCipherObject.value(TypeKeyL).toInt(-1); QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); if (type == 0) { - PreKeyMessage preKeyMessage { body }; - decrypted = d->sessionDecrypt(reinterpret_cast(&preKeyMessage), + QOlmMessage preKeyMessage = QOlmMessage(body, QOlmMessage::PreKey); + decrypted = d->sessionDecrypt(reinterpret_cast(&preKeyMessage), senderKey); } else if (type == 1) { - Message message { body }; + QOlmMessage message = QOlmMessage(body, QOlmMessage::PreKey); decrypted = d->sessionDecrypt(&message, senderKey); } return decrypted; @@ -340,10 +341,11 @@ QString EncryptionManager::sessionDecryptMessage( QByteArray EncryptionManager::olmAccountPickle() { - return d->olmAccount->pickle(); // TODO: passphrase even with qtkeychain? + // new e2ee TODO: return d->olmAccount->pickle(); // TODO: passphrase even with qtkeychain? + return {}; } -QtOlm::Account* EncryptionManager::account() const +QOlmAccount *EncryptionManager::account() const { return d->olmAccount.data(); } diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h index 714f95fd..9d2c8138 100644 --- a/lib/encryptionmanager.h +++ b/lib/encryptionmanager.h @@ -9,12 +9,9 @@ #include #include -namespace QtOlm { -class Account; -} - namespace Quotient { class Connection; +class QOlmAccount; class EncryptionManager : public QObject { Q_OBJECT @@ -39,7 +36,7 @@ public: const QByteArray& senderKey); QByteArray olmAccountPickle(); - QtOlm::Account* account() const; + QOlmAccount* account() const; private: class Private; diff --git a/lib/room.cpp b/lib/room.cpp index 0c9af2b9..d86b2813 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -65,13 +65,12 @@ #include #ifdef Quotient_E2EE_ENABLED -#include // QtOlm -#include // QtOlm -#include // QtOlm +# include "crypto/qolmaccount.h" +# include "crypto/qolmerrors.h" +# include "crypto/qolminboundsession.h" #endif // Quotient_E2EE_ENABLED using namespace Quotient; -using namespace QtOlm; using namespace std::placeholders; using std::move; #if !(defined __GLIBCXX__ && __GLIBCXX__ <= 20150123) @@ -370,23 +369,25 @@ public: // A map from senderKey to a map of sessionId to InboundGroupSession // Not using QMultiHash, because we want to quickly return // a number of relations for a given event without enumerating them. - QHash, InboundGroupSession*> groupSessions; // TODO: + QHash, QOlmInboundGroupSession*> groupSessions; // TODO: // cache bool addInboundGroupSession(QString senderKey, QString sessionId, QString sessionKey) { + // new e2ee TODO: + /* if (groupSessions.contains({ senderKey, sessionId })) { qCDebug(E2EE) << "Inbound Megolm session" << sessionId << "with senderKey" << senderKey << "already exists"; return false; } - InboundGroupSession* megolmSession; + QOlmInboundGroupSession* megolmSession; try { - megolmSession = new InboundGroupSession(sessionKey.toLatin1(), + megolmSession = new QOlmInboundGroupSession(sessionKey.toLatin1(), InboundGroupSession::Init, q); - } catch (OlmError* e) { + } catch (QOlmError* e) { qCDebug(E2EE) << "Unable to create new InboundGroupSession" << e->what(); return false; @@ -398,6 +399,7 @@ public: return false; } groupSessions.insert({ senderKey, sessionId }, megolmSession); + */ return true; } @@ -408,6 +410,8 @@ public: QDateTime timestamp) { std::pair decrypted; + // new e2ee TODO: + /* QPair senderSessionPairKey = qMakePair(senderKey, sessionId); if (!groupSessions.contains(senderSessionPairKey)) { @@ -416,7 +420,7 @@ public: "this message"; return QString(); } - InboundGroupSession* senderSession = + QOlmInboundGroupSession* senderSession = groupSessions.value(senderSessionPairKey); if (!senderSession) { qCDebug(E2EE) << "Unable to decrypt event" << eventId @@ -425,7 +429,7 @@ public: } try { decrypted = senderSession->decrypt(cipher); - } catch (OlmError* e) { + } catch (QOlmError* e) { qCDebug(E2EE) << "Unable to decrypt event" << eventId << "with matching megolm session:" << e->what(); return QString(); @@ -443,6 +447,7 @@ public: return QString(); } } + */ return decrypted.first; } -- cgit v1.2.3 From 12dd7bab004a0b85807347e9bac4ead2baf56bc5 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 28 Jan 2021 23:49:52 +0100 Subject: Fix test --- autotests/testolmsession.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 6535e4fe..72c54174 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -25,7 +25,7 @@ std::pair, std::unique_ptr> createSess const auto preKey = outbound->encrypt(""); // Payload does not matter for PreKey - if (preKey.type() != Message::PreKey) { + if (preKey.type() != QOlmMessage::PreKey) { throw "Wrong first message type received, can't create session"; } auto inbound = std::get>(accountB.createInboundSession(preKey)); @@ -42,8 +42,8 @@ void TestOlmSession::olmEncryptDecrypt() { const auto [inboundSession, outboundSession] = createSessionPair(); const auto encrypted = outboundSession->encrypt("Hello world!"); - if (encrypted.type() == Message::PreKey) { - Message m(encrypted); // clone + if (encrypted.type() == QOlmMessage::PreKey) { + QOlmMessage m(encrypted); // clone QVERIFY(std::get(inboundSession->matchesInboundSession(m))); } -- cgit v1.2.3 From 10b89faeea9e385ea901d45418491cd91dff99b9 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 29 Jan 2021 20:23:42 +0100 Subject: More tests --- .ci/adjust-config.sh | 53 +++++++++++ Makefile | 31 +++++++ autotests/testolmaccount.cpp | 206 ++++++++++++++++++++++++++++++++++++++++++- autotests/testolmaccount.h | 4 + lib/connection.cpp | 51 +++++++---- lib/converters.cpp | 13 ++- lib/crypto/e2ee.h | 47 +++++++++- lib/crypto/qolmaccount.cpp | 36 +++++++- lib/crypto/qolmaccount.h | 8 +- lib/encryptionmanager.cpp | 12 +-- lib/networkaccessmanager.cpp | 6 +- 11 files changed, 435 insertions(+), 32 deletions(-) create mode 100755 .ci/adjust-config.sh create mode 100644 Makefile (limited to 'autotests') diff --git a/.ci/adjust-config.sh b/.ci/adjust-config.sh new file mode 100755 index 00000000..b2ca52b2 --- /dev/null +++ b/.ci/adjust-config.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +CMD="" + +$CMD perl -pi -w -e \ + 's/rc_messages_per_second.*/rc_messages_per_second: 1000/g;' data/homeserver.yaml +$CMD perl -pi -w -e \ + 's/rc_message_burst_count.*/rc_message_burst_count: 10000/g;' data/homeserver.yaml + +( +cat <&1>/dev/null + +restart: stop-synapse synapse diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index a4dfd7b5..c764e023 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -4,7 +4,7 @@ #include "testolmaccount.h" #include "crypto/qolmaccount.h" -#include "csapi/definitions/device_keys.h" +#include "connection.h" #include "events/encryptedfile.h" using namespace Quotient; @@ -162,4 +162,208 @@ void TestOlmAccount::encryptedFile() QCOMPARE(file.key.keyOps.count(), 2); QCOMPARE(file.key.kty, "oct"); } + +void TestOlmAccount::uploadIdentityKey() +{ + auto conn = new Connection(); + conn->resolveServer("@alice:localhost:" + QString::number(443)); + connect(conn, &Connection::loginFlowsChanged, this, [this, conn]() { + conn->loginWithPassword("alice", "secret", "AlicePhone", ""); + connect(conn, &Connection::connected, this, [this, conn] { + auto olmAccount = conn->olmAccount(); + auto idKeys = olmAccount->identityKeys(); + + QVERIFY(idKeys.curve25519.size() > 10); + QVERIFY(idKeys.curve25519.size() > 10); + + + OneTimeKeys unused; + auto request = olmAccount->createUploadKeyRequest(unused); + connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 0); + }); + connect(request, &BaseJob::failure, this, [] { + QFAIL("upload failed"); + }); + conn->run(request); + QSignalSpy spy3(request, &BaseJob::result); + QVERIFY(spy3.wait(10000)); + }); + connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, [=](QString error, const QString &) { + QFAIL("Login failed"); + }); + }); + + connect(conn, &Connection::resolveError, this, [=](QString error) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, this, [=] { + QFAIL("Network error: make sure synapse is running"); + }); + + QSignalSpy spy(conn, &Connection::loginFlowsChanged); + QSignalSpy spy2(conn, &Connection::connected); + QVERIFY(spy.wait(10000)); + QVERIFY(spy2.wait(10000)); + delete conn; +} + +void TestOlmAccount::uploadOneTimeKeys() +{ + auto conn = new Connection(); + conn->resolveServer("@alice:localhost:" + QString::number(443)); + connect(conn, &Connection::loginFlowsChanged, this, [this, conn]() { + conn->loginWithPassword("alice", "secret", "AlicePhone", ""); + connect(conn, &Connection::connected, this, [this, conn] { + auto olmAccount = conn->olmAccount(); + + auto nKeys = olmAccount->generateOneTimeKeys(5); + QCOMPARE(nKeys, 5); + + auto oneTimeKeys = olmAccount->oneTimeKeys(); + + QHash oneTimeKeysHash; + const auto curve = oneTimeKeys.curve25519(); + for (const auto &[keyId, key] : asKeyValueRange(curve)) { + oneTimeKeysHash["curve25519:"+keyId] = key; + } + auto request = new UploadKeysJob(none, oneTimeKeysHash); + connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 1); + QCOMPARE(job2->oneTimeKeyCounts()["curve25519"], 5); + }); + connect(request, &BaseJob::failure, this, [] { + QFAIL("upload failed"); + }); + conn->run(request); + QSignalSpy spy3(request, &BaseJob::result); + QVERIFY(spy3.wait(10000)); + }); + connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, [=](QString error, const QString &) { + QFAIL("Login failed"); + }); + }); + + connect(conn, &Connection::resolveError, this, [=](QString error) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, this, [=] { + QFAIL("Network error: make sure synapse is running"); + }); + + QSignalSpy spy(conn, &Connection::loginFlowsChanged); + QSignalSpy spy2(conn, &Connection::connected); + QVERIFY(spy.wait(10000)); + QVERIFY(spy2.wait(10000)); + delete conn; +} + +void TestOlmAccount::uploadSignedOneTimeKeys() +{ + auto conn = new Connection(); + conn->resolveServer("@alice:localhost:" + QString::number(443)); + connect(conn, &Connection::loginFlowsChanged, this, [this, conn]() { + conn->loginWithPassword("alice", "secret", "AlicePhone", ""); + connect(conn, &Connection::connected, this, [this, conn] { + auto olmAccount = conn->olmAccount(); + auto nKeys = olmAccount->generateOneTimeKeys(5); + QCOMPARE(nKeys, 5); + + auto oneTimeKeys = olmAccount->oneTimeKeys(); + QHash oneTimeKeysHash; + const auto signedKey = olmAccount->signOneTimeKeys(oneTimeKeys); + for (const auto &[keyId, key] : asKeyValueRange(signedKey)) { + QVariant var; + var.setValue(key); + oneTimeKeysHash[keyId] = var; + } + auto request = new UploadKeysJob(none, oneTimeKeysHash); + connect(request, &BaseJob::result, this, [request, nKeys, conn](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 1); + QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], nKeys); + }); + connect(request, &BaseJob::failure, this, [] { + QFAIL("upload failed"); + }); + conn->run(request); + QSignalSpy spy3(request, &BaseJob::result); + QVERIFY(spy3.wait(10000)); + }); + connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, [=](QString error, const QString &) { + QFAIL("Login failed"); + }); + }); + + connect(conn, &Connection::resolveError, this, [=](QString error) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, this, [=] { + QFAIL("Network error: make sure synapse is running"); + }); + + QSignalSpy spy(conn, &Connection::loginFlowsChanged); + QSignalSpy spy2(conn, &Connection::connected); + QVERIFY(spy.wait(10000)); + QVERIFY(spy2.wait(10000)); + delete conn; +} + +void TestOlmAccount::uploadKeys() +{ + auto conn = new Connection(); + conn->resolveServer("@alice:localhost:" + QString::number(443)); + connect(conn, &Connection::loginFlowsChanged, this, [this, conn]() { + conn->loginWithPassword("alice", "secret", "AlicePhone", ""); + connect(conn, &Connection::connected, this, [this, conn] { + auto olmAccount = conn->olmAccount(); + auto idks = olmAccount->identityKeys(); + olmAccount->generateOneTimeKeys(1); + auto otks = olmAccount->oneTimeKeys(); + auto request = olmAccount->createUploadKeyRequest(otks); + connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 1); + QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], 1); + }); + connect(request, &BaseJob::failure, this, [] { + QFAIL("upload failed"); + }); + conn->run(request); + QSignalSpy spy3(request, &BaseJob::result); + QVERIFY(spy3.wait(10000)); + }); + connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, [=](QString error, const QString &) { + QFAIL("Login failed"); + }); + }); + + connect(conn, &Connection::resolveError, this, [=](QString error) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, this, [=] { + QFAIL("Network error: make sure synapse is running"); + }); + + QSignalSpy spy(conn, &Connection::loginFlowsChanged); + QSignalSpy spy2(conn, &Connection::connected); + QVERIFY(spy.wait(10000)); + QVERIFY(spy2.wait(10000)); + delete conn; +} + QTEST_MAIN(TestOlmAccount) diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index 4e270730..41298957 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -16,4 +16,8 @@ private Q_SLOTS: //void removeOneTimeKeys(); void deviceKeys(); void encryptedFile(); + void uploadIdentityKey(); + void uploadOneTimeKeys(); + void uploadSignedOneTimeKeys(); + void uploadKeys(); }; diff --git a/lib/connection.cpp b/lib/connection.cpp index f96eeb71..704bc1b4 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -108,7 +108,8 @@ public: QVector loginFlows; #ifdef Quotient_E2EE_ENABLED - QScopedPointer encryptionManager; + std::unique_ptr olmAccount; + //QScopedPointer encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -183,6 +184,9 @@ public: EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; + return {}; + /* #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; return {}; @@ -242,6 +246,7 @@ public: return std::move(decryptedEvent); #endif // Quotient_E2EE_ENABLED +*/ } }; @@ -420,8 +425,8 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED - encryptionManager->uploadIdentityKeys(q); - encryptionManager->uploadOneTimeKeys(q); + //encryptionManager->uploadIdentityKeys(q); + //encryptionManager->uploadOneTimeKeys(q); #endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { @@ -442,11 +447,19 @@ void Connection::Private::completeSetup(const QString& mxId) qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED AccountSettings accountSettings(data->userId()); - encryptionManager.reset( - new EncryptionManager(accountSettings.encryptionAccountPickle())); + + // init olmAccount + olmAccount = std::make_unique(data->userId(), data->deviceId()); + if (accountSettings.encryptionAccountPickle().isEmpty()) { - accountSettings.setEncryptionAccountPickle( - encryptionManager->olmAccountPickle()); + // create new account and save unpickle data + olmAccount->createNewAccount(); + accountSettings.setEncryptionAccountPickle(std::get(olmAccount->pickle(Unencrypted{}))); + // TODO handle pickle errors + } else { + // account already existing + auto pickle = accountSettings.encryptionAccountPickle(); + olmAccount->unpickle(pickle, Unencrypted{}); } #endif // Quotient_E2EE_ENABLED emit q->stateChanged(); @@ -608,16 +621,16 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->consumeToDeviceEvents(data.takeToDeviceEvents()); #ifdef Quotient_E2EE_ENABLED // handling device_one_time_keys_count - if (!d->encryptionManager) - { - qCDebug(E2EE) << "Encryption manager is not there yet, updating " - "one-time key counts will be skipped"; - return; - } - if (const auto deviceOneTimeKeysCount = data.deviceOneTimeKeysCount(); - !deviceOneTimeKeysCount.isEmpty()) - d->encryptionManager->updateOneTimeKeyCounts(this, - deviceOneTimeKeysCount); + //if (!d->encryptionManager) + //{ + // qCDebug(E2EE) << "Encryption manager is not there yet, updating " + // "one-time key counts will be skipped"; + // return; + //} + //if (const auto deviceOneTimeKeysCount = data.deviceOneTimeKeysCount(); + // !deviceOneTimeKeysCount.isEmpty()) + // d->encryptionManager->updateOneTimeKeyCounts(this, + // deviceOneTimeKeysCount); #endif // Quotient_E2EE_ENABLED } @@ -745,6 +758,7 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { +/* #ifdef Quotient_E2EE_ENABLED // handling m.room_key to-device encrypted event visitEach(toDeviceEvents, [this](const EncryptedEvent& ee) { @@ -775,6 +789,7 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) }); }); #endif +*/ } void Connection::stopSync() @@ -1228,7 +1243,7 @@ bool Connection::isLoggedIn() const { return !accessToken().isEmpty(); } #ifdef Quotient_E2EE_ENABLED QOlmAccount *Connection::olmAccount() const { - return d->encryptionManager->account(); + return d->olmAccount.get(); //d->encryptionManager->account(); } #endif // Quotient_E2EE_ENABLED diff --git a/lib/converters.cpp b/lib/converters.cpp index 444ca4f6..e6dcd854 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -3,15 +3,26 @@ #include "converters.h" -#include +#include +#include "crypto/e2ee.h" QJsonValue Quotient::JsonConverter::dump(const QVariant& v) { + if (v.canConvert()) { + return toJson(v.value()); + } return QJsonValue::fromVariant(v); } QVariant Quotient::JsonConverter::load(const QJsonValue& jv) { + if (jv.isObject()) { + QJsonObject obj = jv.toObject(); + if (obj.contains("key") && obj.contains("signatures")) { + SignedOneTimeKey signedOneTimeKeys; + signedOneTimeKeys.key = obj["key"].toString(); + } + } return jv.toVariant(); } diff --git a/lib/crypto/e2ee.h b/lib/crypto/e2ee.h index 73dd7f65..2d280185 100644 --- a/lib/crypto/e2ee.h +++ b/lib/crypto/e2ee.h @@ -7,10 +7,13 @@ #include #include +#include "converters.h" #include #include +#include #include +#include #include "util.h" @@ -68,16 +71,56 @@ struct OneTimeKeys }; //! Struct representing the signed one-time keys. -struct SignedOneTimeKey +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. - QMap> signatures; + QHash> signatures; +}; + + +template <> +struct JsonObjectConverter { + 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 +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/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 76b0a263..fb91c906 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -4,6 +4,8 @@ #ifdef Quotient_E2EE_ENABLED #include "qolmaccount.h" +#include "connection.h" +#include "csapi/keys.h" #include "crypto/qolmutils.h" #include #include @@ -138,7 +140,7 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const return olm_account_max_number_of_one_time_keys(m_account); } -void QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const +size_t 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); @@ -147,6 +149,7 @@ void QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const if (error == olm_error()) { throw lastError(m_account); } + return error; } OneTimeKeys QOlmAccount::oneTimeKeys() const @@ -212,6 +215,37 @@ OlmAccount *Quotient::QOlmAccount::data() return m_account; } +UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKeys) +{ + + 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; + + if (oneTimeKeys.curve25519().isEmpty()) { + return new UploadKeysJob(deviceKeys); + } + + // Sign & append the one time keys. + auto temp = signOneTimeKeys(oneTimeKeys); + QHash oneTimeKeysSigned; + for (const auto &[keyId, key] : asKeyValueRange(temp)) { + QVariant keyVar; + keyVar.setValue(key); + oneTimeKeysSigned[keyId] = keyVar; + } + + return new UploadKeysJob(deviceKeys, oneTimeKeysSigned); +} + std::variant, QOlmError> QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index 4398214a..d61c8748 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -4,6 +4,7 @@ #pragma once #ifdef Quotient_E2EE_ENABLED +#include "csapi/keys.h" #include "crypto/e2ee.h" #include "crypto/qolmerrors.h" #include "crypto/qolmmessage.h" @@ -15,6 +16,7 @@ struct OlmAccount; namespace Quotient { class QOlmSession; +class Connection; //! An olm account manages all cryptographic keys used on a device. //! \code{.cpp} @@ -55,7 +57,7 @@ public: size_t maxNumberOfOneTimeKeys() const; //! Generates the supplied number of one time keys. - void generateOneTimeKeys(size_t numberOfKeys) const; + size_t generateOneTimeKeys(size_t numberOfKeys) const; //! Gets the OlmAccount's one time keys formatted as JSON. OneTimeKeys oneTimeKeys() const; @@ -68,6 +70,8 @@ public: SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; + UploadKeysJob *createUploadKeyRequest(const OneTimeKeys &oneTimeKeys); + //! Remove the one time key used to create the supplied session. [[nodiscard]] std::optional removeOneTimeKeys(const std::unique_ptr &session) const; @@ -90,7 +94,7 @@ public: QOlmAccount(OlmAccount *account); OlmAccount *data(); private: - OlmAccount *m_account = nullptr; + OlmAccount *m_account = nullptr; // owning QString m_userId; QString m_deviceId; }; diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 449eb2a3..c8dc6bdd 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -104,7 +104,7 @@ public: { // Try to decrypt message body using one of the known sessions for that // device - bool sessionsPassed = false; + /*bool sessionsPassed = false; // new e2ee TODO: for (auto &senderSession : sessions) { if (senderSession == sessions.last()) { @@ -152,7 +152,7 @@ public: qCDebug(E2EE) << "try to establish new InboundSession with" << senderKey; QOlmMessage preKeyMessage = QOlmMessage(message.toCiphertext(), QOlmMessage::PreKey); // new e2ee TODO: - const auto sessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), preKeyMessage); + //const auto sessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), preKeyMessage); if (const auto error = std::get_if(&sessionResult)) { qCDebug(E2EE) << "Error decrypting pre-key message when trying " @@ -161,7 +161,7 @@ public: return QString(); } - const auto newSession = std::get>(sessionResult); + const auto newSession = std::get>(olmAccount->createInboundSessionFrom(senderKey.toUtf8(), preKeyMessage)); qCDebug(E2EE) << "Created new Olm session" << newSession->sessionId(); @@ -178,9 +178,9 @@ public: << "Error removing one time keys" << error.value(); } - sessions.insert(senderKey, std::move(newSession)); - return std::get(decryptedResult); - } + //sessions.insert(senderKey, std::move(newSession)); TODO + //return std::get(decryptedResult); + }*/ return QString(); } }; diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 57618329..293538ee 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -40,7 +40,11 @@ public: NetworkAccessManager::NetworkAccessManager(QObject* parent) : QNetworkAccessManager(parent), d(std::make_unique(this)) -{} +{ + connect(this, &QNetworkAccessManager::sslErrors, this, [](QNetworkReply *reply, const QList &errors) { + reply->ignoreSslErrors(); + }); +} QList NetworkAccessManager::ignoredSslErrors() const { -- cgit v1.2.3 From f9f7d130e5768d0f69edc8900d37f540b61fa974 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sat, 30 Jan 2021 00:21:10 +0100 Subject: Key verification --- CMakeLists.txt | 1 + autotests/testolmaccount.cpp | 138 +++++++++++++++++++++++++++++++------------ autotests/testolmaccount.h | 1 + lib/crypto/qolmaccount.cpp | 42 +++++++++++++ lib/crypto/qolmaccount.h | 9 +++ lib/crypto/qolmutility.cpp | 58 ++++++++++++++++++ lib/crypto/qolmutility.h | 48 +++++++++++++++ 7 files changed, 258 insertions(+), 39 deletions(-) create mode 100644 lib/crypto/qolmutility.cpp create mode 100644 lib/crypto/qolmutility.h (limited to 'autotests') diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f62af68..fb07fa22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,6 +163,7 @@ list(APPEND lib_SRCS lib/crypto/qolminboundsession.cpp lib/crypto/qolmoutboundsession.cpp lib/crypto/qolmutils.cpp + lib/crypto/qolmutility.cpp lib/crypto/qolmerrors.cpp lib/crypto/qolmsession.cpp lib/crypto/qolmmessage.cpp diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index c764e023..ce51b9ec 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -244,19 +244,6 @@ void TestOlmAccount::uploadOneTimeKeys() QSignalSpy spy3(request, &BaseJob::result); QVERIFY(spy3.wait(10000)); }); - connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, [=](QString error, const QString &) { - QFAIL("Login failed"); - }); - }); - - connect(conn, &Connection::resolveError, this, [=](QString error) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, this, [=] { - QFAIL("Network error: make sure synapse is running"); }); QSignalSpy spy(conn, &Connection::loginFlowsChanged); @@ -298,19 +285,6 @@ void TestOlmAccount::uploadSignedOneTimeKeys() QSignalSpy spy3(request, &BaseJob::result); QVERIFY(spy3.wait(10000)); }); - connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, [=](QString error, const QString &) { - QFAIL("Login failed"); - }); - }); - - connect(conn, &Connection::resolveError, this, [=](QString error) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, this, [=] { - QFAIL("Network error: make sure synapse is running"); }); QSignalSpy spy(conn, &Connection::loginFlowsChanged); @@ -344,19 +318,6 @@ void TestOlmAccount::uploadKeys() QSignalSpy spy3(request, &BaseJob::result); QVERIFY(spy3.wait(10000)); }); - connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, [=](QString error, const QString &) { - QFAIL("Login failed"); - }); - }); - - connect(conn, &Connection::resolveError, this, [=](QString error) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, this, [=] { - QFAIL("Network error: make sure synapse is running"); }); QSignalSpy spy(conn, &Connection::loginFlowsChanged); @@ -366,4 +327,103 @@ void TestOlmAccount::uploadKeys() delete conn; } +inline void sleep() +{ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +} + + +void TestOlmAccount::claimKeys() +{ + auto alice = new Connection(); + alice->resolveServer("@alice:localhost:" + QString::number(443)); + connect(alice, &Connection::loginFlowsChanged, this, [this, alice]() { + alice->loginWithPassword("alice", "secret", "AlicePhone", ""); + connect(alice, &Connection::connected, this, [this, alice] { + qDebug() << "alice->accessToken()" << alice->accessToken(); + QVERIFY(!alice->accessToken().isEmpty()); + }); + }); + + QSignalSpy spy(alice, &Connection::loginFlowsChanged); + QSignalSpy spy2(alice, &Connection::connected); + QVERIFY(spy.wait(10000)); + QVERIFY(spy2.wait(10000)); + + auto bob = new Connection(); + bob->resolveServer("@bob:localhost:" + QString::number(443)); + connect(bob, &Connection::loginFlowsChanged, this, [this, bob]() { + bob->loginWithPassword("bob", "secret", "BobPhone", ""); + connect(bob, &Connection::connected, this, [this, bob] { + qDebug() << "bob->accessToken()" << bob->accessToken(); + QVERIFY(!bob->accessToken().isEmpty()); + }); + }); + + QSignalSpy spy3(bob, &Connection::loginFlowsChanged); + QSignalSpy spy4(bob, &Connection::connected); + QVERIFY(spy3.wait(10000)); + QVERIFY(spy4.wait(10000)); + + // Bob uploads his keys. + auto *bobOlm = bob->olmAccount(); + bobOlm->generateOneTimeKeys(1); + auto request = bobOlm->createUploadKeyRequest(bobOlm->oneTimeKeys()); + + connect(request, &BaseJob::result, this, [request, bob](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 1); + QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], 1); + }); + bob->run(request); + + QSignalSpy requestSpy(request, &BaseJob::result); + QVERIFY(requestSpy.wait(10000)); + + // Alice retrieves bob's keys & claims one signed one-time key. + auto *aliceOlm = alice->olmAccount(); + QHash deviceKeys; + deviceKeys[bob->userId()] = QStringList(); + auto job = alice->callApi(deviceKeys); + connect(job, &BaseJob::result, this, [bob, alice, aliceOlm, job, this] { + auto bobDevices = job->deviceKeys()[bob->userId()]; + QVERIFY(bobDevices.size() > 0); + + auto devices = {bob->deviceId()}; + + // Retrieve the identity key for the current device. + auto bobEd25519 = + bobDevices[bob->deviceId()].keys["ed25519:" + bob->deviceId()]; + + const auto currentDevice = bobDevices[bob->deviceId()]; + + // Verify signature. + QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(), bob->userId())); + + QHash> oneTimeKeys; + for (const auto &d : devices) { + oneTimeKeys[bob->userId()] = QHash(); + oneTimeKeys[bob->userId()][d] = SignedCurve25519Key; + } + auto job = alice->callApi(oneTimeKeys); + connect(job, &BaseJob::result, this, [aliceOlm, bob, bobEd25519, job] { + const auto userId = bob->userId(); + const auto deviceId = bob->deviceId(); + + // The device exists. + QCOMPARE(job->oneTimeKeys().size(), 1); + QCOMPARE(job->oneTimeKeys()[userId].size(), 1); + + // The key is the one bob sent. + auto oneTimeKey = job->oneTimeKeys()[userId][deviceId]; + QVERIFY(oneTimeKey.canConvert()); + + //auto algo = oneTimeKey.begin().key(); + //auto contents = oneTimeKey.begin().value(); + }); + }); + delete bob; + delete alice; +} + QTEST_MAIN(TestOlmAccount) diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index 41298957..8b2d2e09 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -20,4 +20,5 @@ private Q_SLOTS: void uploadOneTimeKeys(); void uploadSignedOneTimeKeys(); void uploadKeys(); + void claimKeys(); }; diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index fb91c906..24fd87f2 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -7,6 +7,7 @@ #include "connection.h" #include "csapi/keys.h" #include "crypto/qolmutils.h" +#include "crypto/qolmutility.h" #include #include #include @@ -263,4 +264,45 @@ std::variant, QOlmError> QOlmAccount::createOutboun return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); } +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(QString signingKey, + QJsonObject obj, + QString signature) +{ + if (signature.isEmpty()) { + return false; + } + + obj.remove("unsigned"); + obj.remove("signatures"); + + QJsonDocument doc; + doc.setObject(obj); + auto canonicalJson = doc.toJson(); + + QByteArray signingKeyBuf = signingKey.toUtf8(); + QOlmUtility utility; + auto signatureBuf = signature.toUtf8(); + auto result = utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf); + if (std::holds_alternative(result)) { + return false; + } + + return std::get(result); +} + #endif diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index d61c8748..09ef623a 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -99,6 +99,15 @@ private: 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(QString signingKey, + QJsonObject obj, + QString signature); + } // namespace Quotient #endif diff --git a/lib/crypto/qolmutility.cpp b/lib/crypto/qolmutility.cpp new file mode 100644 index 00000000..3c6a14c7 --- /dev/null +++ b/lib/crypto/qolmutility.cpp @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/qolmutility.h" +#include "olm/olm.h" + +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(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 QOlmUtility::ed25519Verify(const QByteArray &key, + const QByteArray &message, QByteArray &signature) +{ + const auto error = olm_ed25519_verify(m_utility, key.data(), key.length(), + message.data(), message.length(), signature.data(), signature.length()); + + if (error == olm_error()) { + return lastError(m_utility); + } + return error == 0; +} + + +#endif diff --git a/lib/crypto/qolmutility.h b/lib/crypto/qolmutility.h new file mode 100644 index 00000000..16c330eb --- /dev/null +++ b/lib/crypto/qolmutility.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED +#include +#include +#include "crypto/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 `sha256_bytes()`, returning its output. + QString sha256Utf8Msg(const QString &message) const; + + //! Verify a ed25519 signature. + //! \param any 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 ed25519Verify(const QByteArray &key, + const QByteArray &message, QByteArray &signature); + + +private: + OlmUtility *m_utility; + +}; +} + +#endif -- cgit v1.2.3 From 4593856411a2a8e4b82333abd5684b253daab47c Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sat, 30 Jan 2021 01:26:30 +0100 Subject: Add more test and use macro to remove duplicated code --- autotests/testolmaccount.cpp | 343 +++++++++++++++++++++---------------------- autotests/testolmaccount.h | 7 + lib/crypto/qolmaccount.cpp | 5 +- 3 files changed, 175 insertions(+), 180 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index ce51b9ec..5cb88a99 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -163,207 +163,130 @@ void TestOlmAccount::encryptedFile() QCOMPARE(file.key.kty, "oct"); } +#define CREATE_CONNECTION(VAR, USERNAME, SECRET, DEVICE_NAME) \ + auto VAR = std::make_shared(); \ + (VAR) ->resolveServer("@alice:localhost:" + QString::number(443)); \ + connect( (VAR) .get(), &Connection::loginFlowsChanged, this, [this, VAR ] () { \ + (VAR) ->loginWithPassword( (USERNAME) , SECRET , DEVICE_NAME , ""); \ + }); \ + connect( (VAR) .get(), &Connection::networkError, [=](QString error, const QString &, int, int) { \ + QFAIL("Network error: make sure synapse is running"); \ + }); \ + connect( (VAR) .get(), &Connection::loginError, [=](QString error, const QString &) { \ + QFAIL("Login failed"); \ + }); \ + QSignalSpy spy ## VAR ((VAR).get(), &Connection::loginFlowsChanged); \ + QSignalSpy spy2 ## VAR ((VAR).get(), &Connection::connected); \ + QVERIFY(spy ## VAR .wait(10000)); \ + QVERIFY(spy2 ## VAR .wait(10000)); + void TestOlmAccount::uploadIdentityKey() { - auto conn = new Connection(); - conn->resolveServer("@alice:localhost:" + QString::number(443)); - connect(conn, &Connection::loginFlowsChanged, this, [this, conn]() { - conn->loginWithPassword("alice", "secret", "AlicePhone", ""); - connect(conn, &Connection::connected, this, [this, conn] { - auto olmAccount = conn->olmAccount(); - auto idKeys = olmAccount->identityKeys(); - - QVERIFY(idKeys.curve25519.size() > 10); - QVERIFY(idKeys.curve25519.size() > 10); - - - OneTimeKeys unused; - auto request = olmAccount->createUploadKeyRequest(unused); - connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { - auto job2 = static_cast(job); - QCOMPARE(job2->oneTimeKeyCounts().size(), 0); - }); - connect(request, &BaseJob::failure, this, [] { - QFAIL("upload failed"); - }); - conn->run(request); - QSignalSpy spy3(request, &BaseJob::result); - QVERIFY(spy3.wait(10000)); - }); - connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, [=](QString error, const QString &) { - QFAIL("Login failed"); - }); - }); + CREATE_CONNECTION(conn, "alice", "secret", "AlicePhone") + + auto olmAccount = conn->olmAccount(); + auto idKeys = olmAccount->identityKeys(); - connect(conn, &Connection::resolveError, this, [=](QString error) { - QFAIL("Network error: make sure synapse is running"); + QVERIFY(idKeys.curve25519.size() > 10); + QVERIFY(idKeys.curve25519.size() > 10); + + OneTimeKeys unused; + auto request = olmAccount->createUploadKeyRequest(unused); + connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 0); }); - connect(conn, &Connection::loginError, this, [=] { - QFAIL("Network error: make sure synapse is running"); + connect(request, &BaseJob::failure, this, [] { + QFAIL("upload failed"); }); - - QSignalSpy spy(conn, &Connection::loginFlowsChanged); - QSignalSpy spy2(conn, &Connection::connected); - QVERIFY(spy.wait(10000)); - QVERIFY(spy2.wait(10000)); - delete conn; + conn->run(request); + QSignalSpy spy3(request, &BaseJob::result); + QVERIFY(spy3.wait(10000)); } void TestOlmAccount::uploadOneTimeKeys() { - auto conn = new Connection(); - conn->resolveServer("@alice:localhost:" + QString::number(443)); - connect(conn, &Connection::loginFlowsChanged, this, [this, conn]() { - conn->loginWithPassword("alice", "secret", "AlicePhone", ""); - connect(conn, &Connection::connected, this, [this, conn] { - auto olmAccount = conn->olmAccount(); - - auto nKeys = olmAccount->generateOneTimeKeys(5); - QCOMPARE(nKeys, 5); - - auto oneTimeKeys = olmAccount->oneTimeKeys(); - - QHash oneTimeKeysHash; - const auto curve = oneTimeKeys.curve25519(); - for (const auto &[keyId, key] : asKeyValueRange(curve)) { - oneTimeKeysHash["curve25519:"+keyId] = key; - } - auto request = new UploadKeysJob(none, oneTimeKeysHash); - connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { - auto job2 = static_cast(job); - QCOMPARE(job2->oneTimeKeyCounts().size(), 1); - QCOMPARE(job2->oneTimeKeyCounts()["curve25519"], 5); - }); - connect(request, &BaseJob::failure, this, [] { - QFAIL("upload failed"); - }); - conn->run(request); - QSignalSpy spy3(request, &BaseJob::result); - QVERIFY(spy3.wait(10000)); - }); - }); + CREATE_CONNECTION(conn, "alice", "secret", "AlicePhone") + auto olmAccount = conn->olmAccount(); - QSignalSpy spy(conn, &Connection::loginFlowsChanged); - QSignalSpy spy2(conn, &Connection::connected); - QVERIFY(spy.wait(10000)); - QVERIFY(spy2.wait(10000)); - delete conn; + auto nKeys = olmAccount->generateOneTimeKeys(5); + QCOMPARE(nKeys, 5); + + auto oneTimeKeys = olmAccount->oneTimeKeys(); + + QHash oneTimeKeysHash; + const auto curve = oneTimeKeys.curve25519(); + for (const auto &[keyId, key] : asKeyValueRange(curve)) { + oneTimeKeysHash["curve25519:"+keyId] = key; + } + auto request = new UploadKeysJob(none, oneTimeKeysHash); + connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 1); + QCOMPARE(job2->oneTimeKeyCounts()["curve25519"], 5); + }); + connect(request, &BaseJob::failure, this, [] { + QFAIL("upload failed"); + }); + conn->run(request); + QSignalSpy spy3(request, &BaseJob::result); + QVERIFY(spy3.wait(10000)); } void TestOlmAccount::uploadSignedOneTimeKeys() { - auto conn = new Connection(); - conn->resolveServer("@alice:localhost:" + QString::number(443)); - connect(conn, &Connection::loginFlowsChanged, this, [this, conn]() { - conn->loginWithPassword("alice", "secret", "AlicePhone", ""); - connect(conn, &Connection::connected, this, [this, conn] { - auto olmAccount = conn->olmAccount(); - auto nKeys = olmAccount->generateOneTimeKeys(5); - QCOMPARE(nKeys, 5); - - auto oneTimeKeys = olmAccount->oneTimeKeys(); - QHash oneTimeKeysHash; - const auto signedKey = olmAccount->signOneTimeKeys(oneTimeKeys); - for (const auto &[keyId, key] : asKeyValueRange(signedKey)) { - QVariant var; - var.setValue(key); - oneTimeKeysHash[keyId] = var; - } - auto request = new UploadKeysJob(none, oneTimeKeysHash); - connect(request, &BaseJob::result, this, [request, nKeys, conn](BaseJob *job) { - auto job2 = static_cast(job); - QCOMPARE(job2->oneTimeKeyCounts().size(), 1); - QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], nKeys); - }); - connect(request, &BaseJob::failure, this, [] { - QFAIL("upload failed"); - }); - conn->run(request); - QSignalSpy spy3(request, &BaseJob::result); - QVERIFY(spy3.wait(10000)); - }); + CREATE_CONNECTION(conn, "alice", "secret", "AlicePhone") + auto olmAccount = conn->olmAccount(); + auto nKeys = olmAccount->generateOneTimeKeys(5); + QCOMPARE(nKeys, 5); + + auto oneTimeKeys = olmAccount->oneTimeKeys(); + QHash oneTimeKeysHash; + const auto signedKey = olmAccount->signOneTimeKeys(oneTimeKeys); + for (const auto &[keyId, key] : asKeyValueRange(signedKey)) { + QVariant var; + var.setValue(key); + oneTimeKeysHash[keyId] = var; + } + auto request = new UploadKeysJob(none, oneTimeKeysHash); + connect(request, &BaseJob::result, this, [request, nKeys, conn](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 1); + QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], nKeys); }); - - QSignalSpy spy(conn, &Connection::loginFlowsChanged); - QSignalSpy spy2(conn, &Connection::connected); - QVERIFY(spy.wait(10000)); - QVERIFY(spy2.wait(10000)); - delete conn; + connect(request, &BaseJob::failure, this, [] { + QFAIL("upload failed"); + }); + conn->run(request); + QSignalSpy spy3(request, &BaseJob::result); + QVERIFY(spy3.wait(10000)); } void TestOlmAccount::uploadKeys() { - auto conn = new Connection(); - conn->resolveServer("@alice:localhost:" + QString::number(443)); - connect(conn, &Connection::loginFlowsChanged, this, [this, conn]() { - conn->loginWithPassword("alice", "secret", "AlicePhone", ""); - connect(conn, &Connection::connected, this, [this, conn] { - auto olmAccount = conn->olmAccount(); - auto idks = olmAccount->identityKeys(); - olmAccount->generateOneTimeKeys(1); - auto otks = olmAccount->oneTimeKeys(); - auto request = olmAccount->createUploadKeyRequest(otks); - connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { - auto job2 = static_cast(job); - QCOMPARE(job2->oneTimeKeyCounts().size(), 1); - QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], 1); - }); - connect(request, &BaseJob::failure, this, [] { - QFAIL("upload failed"); - }); - conn->run(request); - QSignalSpy spy3(request, &BaseJob::result); - QVERIFY(spy3.wait(10000)); - }); + CREATE_CONNECTION(conn, "alice", "secret", "AlicePhone") + auto olmAccount = conn->olmAccount(); + auto idks = olmAccount->identityKeys(); + olmAccount->generateOneTimeKeys(1); + auto otks = olmAccount->oneTimeKeys(); + auto request = olmAccount->createUploadKeyRequest(otks); + connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 1); + QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], 1); }); - - QSignalSpy spy(conn, &Connection::loginFlowsChanged); - QSignalSpy spy2(conn, &Connection::connected); - QVERIFY(spy.wait(10000)); - QVERIFY(spy2.wait(10000)); - delete conn; -} - -inline void sleep() -{ - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + connect(request, &BaseJob::failure, this, [] { + QFAIL("upload failed"); + }); + conn->run(request); + QSignalSpy spy3(request, &BaseJob::result); + QVERIFY(spy3.wait(10000)); } - void TestOlmAccount::claimKeys() { - auto alice = new Connection(); - alice->resolveServer("@alice:localhost:" + QString::number(443)); - connect(alice, &Connection::loginFlowsChanged, this, [this, alice]() { - alice->loginWithPassword("alice", "secret", "AlicePhone", ""); - connect(alice, &Connection::connected, this, [this, alice] { - qDebug() << "alice->accessToken()" << alice->accessToken(); - QVERIFY(!alice->accessToken().isEmpty()); - }); - }); - - QSignalSpy spy(alice, &Connection::loginFlowsChanged); - QSignalSpy spy2(alice, &Connection::connected); - QVERIFY(spy.wait(10000)); - QVERIFY(spy2.wait(10000)); - - auto bob = new Connection(); - bob->resolveServer("@bob:localhost:" + QString::number(443)); - connect(bob, &Connection::loginFlowsChanged, this, [this, bob]() { - bob->loginWithPassword("bob", "secret", "BobPhone", ""); - connect(bob, &Connection::connected, this, [this, bob] { - qDebug() << "bob->accessToken()" << bob->accessToken(); - QVERIFY(!bob->accessToken().isEmpty()); - }); - }); - - QSignalSpy spy3(bob, &Connection::loginFlowsChanged); - QSignalSpy spy4(bob, &Connection::connected); - QVERIFY(spy3.wait(10000)); - QVERIFY(spy4.wait(10000)); + CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") + CREATE_CONNECTION(bob, "alice", "secret", "AlicePhone") // Bob uploads his keys. auto *bobOlm = bob->olmAccount(); @@ -422,8 +345,70 @@ void TestOlmAccount::claimKeys() //auto contents = oneTimeKey.begin().value(); }); }); - delete bob; - delete alice; } +void TestOlmAccount::claimMultipleKeys() +{ + // Login with alice multiple times + CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") + CREATE_CONNECTION(alice1, "alice", "secret", "AlicePhone") + CREATE_CONNECTION(alice2, "alice", "secret", "AlicePhone") + + auto olm = alice->olmAccount(); + olm->generateOneTimeKeys(10); + auto res = olm->createUploadKeyRequest(olm->oneTimeKeys()); + alice->run(res); + connect(res, &BaseJob::result, this, [res] { + QCOMPARE(res->oneTimeKeyCounts().size(), 1); + QCOMPARE(res->oneTimeKeyCounts()["signed_curve25519"], 10); + }); + QSignalSpy spy(res, &BaseJob::result); + + auto olm1 = alice1->olmAccount(); + olm1->generateOneTimeKeys(10); + auto res1 = olm1->createUploadKeyRequest(olm1->oneTimeKeys()); + alice1->run(res1); + connect(res1, &BaseJob::result, this, [res1] { + QCOMPARE(res1->oneTimeKeyCounts().size(), 1); + QCOMPARE(res1->oneTimeKeyCounts()["signed_curve25519"], 10); + }); + QSignalSpy spy1(res1, &BaseJob::result); + + auto olm2 = alice2->olmAccount(); + olm2->generateOneTimeKeys(10); + auto res2 = olm2->createUploadKeyRequest(olm2->oneTimeKeys()); + alice2->run(res2); + connect(res2, &BaseJob::result, this, [res2] { + QCOMPARE(res2->oneTimeKeyCounts().size(), 1); + QCOMPARE(res2->oneTimeKeyCounts()["signed_curve25519"], 10); + }); + QSignalSpy spy2(res2, &BaseJob::result); + + QVERIFY(spy.wait(10000)); + QVERIFY(spy1.wait(10000)); + QVERIFY(spy2.wait(10000)); + + // Bob will claim all keys from alice + CREATE_CONNECTION(bob, "bob", "secret", "BobPhone") + + QStringList devices_; + devices_ << alice->deviceId() + << alice1->deviceId() + << alice2->deviceId(); + + QHash> oneTimeKeys; + for (const auto &d : devices_) { + oneTimeKeys[alice->userId()] = QHash(); + oneTimeKeys[alice->userId()][d] = SignedCurve25519Key; + } + auto job = bob->callApi(oneTimeKeys); + connect(job, &BaseJob::result, this, [bob, job] { + const auto userId = bob->userId(); + const auto deviceId = bob->deviceId(); + + // The device exists. + QCOMPARE(job->oneTimeKeys().size(), 1); + QCOMPARE(job->oneTimeKeys()[userId].size(), 3); + }); +} QTEST_MAIN(TestOlmAccount) diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index 8b2d2e09..b6b9cae4 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -3,11 +3,17 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include +#include + +namespace Quotient { + class Connection; +} class TestOlmAccount : public QObject { Q_OBJECT + private Q_SLOTS: void pickleUnpickedTest(); void identityKeysValid(); @@ -21,4 +27,5 @@ private Q_SLOTS: void uploadSignedOneTimeKeys(); void uploadKeys(); void claimKeys(); + void claimMultipleKeys(); }; diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 24fd87f2..750d7318 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -272,6 +272,7 @@ bool Quotient::verifyIdentitySignature(const DeviceKeys &deviceKeys, const auto signingKey = deviceKeys.keys[signKeyId]; const auto signature = deviceKeys.signatures[userId][signKeyId]; + if (signature.isEmpty()) { return false; } @@ -292,7 +293,9 @@ bool Quotient::ed25519VerifySignature(QString signingKey, QJsonDocument doc; doc.setObject(obj); - auto canonicalJson = doc.toJson(); + auto canonicalJson = doc.toJson(QJsonDocument::Compact); + + qDebug() << canonicalJson; QByteArray signingKeyBuf = signingKey.toUtf8(); QOlmUtility utility; -- cgit v1.2.3 From fe9b2f918753d40d93f8aecf182485e75d4b75bb Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 31 Jan 2021 01:18:35 +0100 Subject: More test but still failing in signing/signature verification --- autotests/CMakeLists.txt | 1 + autotests/testolmaccount.cpp | 10 ++-- autotests/testolmutility.cpp | 131 +++++++++++++++++++++++++++++++++++++++++++ autotests/testolmutility.h | 15 +++++ lib/crypto/qolmaccount.cpp | 36 ++++++------ lib/crypto/qolmaccount.h | 8 ++- lib/crypto/qolmutility.cpp | 23 ++++++-- lib/crypto/qolmutility.h | 2 +- 8 files changed, 196 insertions(+), 30 deletions(-) create mode 100644 autotests/testolmutility.cpp create mode 100644 autotests/testolmutility.h (limited to 'autotests') diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 6afdf8cc..0354172b 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -16,4 +16,5 @@ if(${PROJECT_NAME}_ENABLE_E2EE) quotient_add_test(NAME testolmaccount) quotient_add_test(NAME testgroupsession) quotient_add_test(NAME testolmsession) + quotient_add_test(NAME testolmutility) endif() diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 5cb88a99..8d979e0b 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -309,11 +309,10 @@ void TestOlmAccount::claimKeys() deviceKeys[bob->userId()] = QStringList(); auto job = alice->callApi(deviceKeys); connect(job, &BaseJob::result, this, [bob, alice, aliceOlm, job, this] { + qDebug() << job->jsonData(); auto bobDevices = job->deviceKeys()[bob->userId()]; QVERIFY(bobDevices.size() > 0); - auto devices = {bob->deviceId()}; - // Retrieve the identity key for the current device. auto bobEd25519 = bobDevices[bob->deviceId()].keys["ed25519:" + bob->deviceId()]; @@ -324,10 +323,9 @@ void TestOlmAccount::claimKeys() QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(), bob->userId())); QHash> oneTimeKeys; - for (const auto &d : devices) { - oneTimeKeys[bob->userId()] = QHash(); - oneTimeKeys[bob->userId()][d] = SignedCurve25519Key; - } + oneTimeKeys[bob->userId()] = QHash(); + oneTimeKeys[bob->userId()][bob->deviceId()] = SignedCurve25519Key; + auto job = alice->callApi(oneTimeKeys); connect(job, &BaseJob::result, this, [aliceOlm, bob, bobEd25519, job] { const auto userId = bob->userId(); diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp new file mode 100644 index 00000000..cb92a0df --- /dev/null +++ b/autotests/testolmutility.cpp @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "testolmutility.h" +#include "crypto/qolmaccount.h" +#include "crypto/qolmutility.h" + +using namespace Quotient; + +void TestOlmUtility::canonicalJSON() +{ + // Examples taken from + // https://matrix.org/docs/spec/appendices.html#canonical-json + auto data = QJsonDocument::fromJson(QByteArrayLiteral(R"({ + "auth": { + "success": true, + "mxid": "@john.doe:example.com", + "profile": { + "display_name": "John Doe", + "three_pids": [{ + "medium": "email", + "address": "john.doe@example.org" + }, { + "medium": "msisdn", + "address": "123456789" + }] + }}})")); + + QCOMPARE(data.toJson(QJsonDocument::Compact), + "{\"auth\":{\"mxid\":\"@john.doe:example.com\",\"profile\":{\"display_name\":\"John " + "Doe\",\"three_pids\":[{\"address\":\"john.doe@example.org\",\"medium\":\"email\"},{" + "\"address\":\"123456789\",\"medium\":\"msisdn\"}]},\"success\":true}}"); + + auto data0 = QJsonDocument::fromJson(QByteArrayLiteral(R"({"b":"2","a":"1"})")); + QCOMPARE(data0.toJson(QJsonDocument::Compact), "{\"a\":\"1\",\"b\":\"2\"}"); + + auto data1 = QJsonDocument::fromJson(QByteArrayLiteral(R"({ "本": 2, "æ—¥": 1 })")); + QCOMPARE(data1.toJson(QJsonDocument::Compact), "{\"æ—¥\":1,\"本\":2}"); + + auto data2 = QJsonDocument::fromJson(QByteArrayLiteral(R"({"a": "\u65E5"})")); + QCOMPARE(data2.toJson(QJsonDocument::Compact), "{\"a\":\"æ—¥\"}"); + + auto data3 = QJsonDocument::fromJson(QByteArrayLiteral(R"({ "a": null })")); + QCOMPARE(data3.toJson(QJsonDocument::Compact), "{\"a\":null}"); +} + +void TestOlmUtility::verifySignedOneTimeKey() +{ + auto aliceOlm = std::make_shared("alice:matrix.org", "aliceDevice"); + aliceOlm->createNewAccount(); + aliceOlm->generateOneTimeKeys(1); + auto keys = aliceOlm->oneTimeKeys(); + + auto firstKey = keys.curve25519().keyValueBegin()->second; + auto msgObj = QJsonObject({{"key", firstKey}}); + auto sig = aliceOlm->sign(msgObj); + + auto msg = QJsonDocument(msgObj).toJson(QJsonDocument::Compact); + + auto utilityBuf = new uint8_t[olm_utility_size()]; + auto utility = olm_utility(utilityBuf); + + qDebug() << "1" << aliceOlm->identityKeys().ed25519 << msg << QString::fromUtf8(sig); + + QByteArray signatureBuf1(sig.length(), '0'); + std::copy(sig.begin(), sig.end(), signatureBuf1.begin()); + + auto res = olm_ed25519_verify(utility, + aliceOlm->identityKeys().ed25519.data(), + aliceOlm->identityKeys().ed25519.size(), + msg.data(), + msg.size(), + (void *)sig.data(), + sig.size()); + qDebug() << "2" << aliceOlm->identityKeys().ed25519 << msg << QString::fromUtf8(signatureBuf1); + + QCOMPARE(std::string(olm_utility_last_error(utility)), "SUCCESS"); + QCOMPARE(res, 0); + + delete[](reinterpret_cast(utility)); + + QOlmUtility utility2; + auto res2 = std::get(utility2.ed25519Verify(aliceOlm->identityKeys().ed25519, msg, signatureBuf1)); + + //QCOMPARE(std::string(olm_utility_last_error(utility)), "SUCCESS"); + QCOMPARE(res2, true); +} + +void TestOlmUtility::validUploadKeysRequest() +{ + const auto userId = QStringLiteral("@alice:matrix.org"); + const auto deviceId = QStringLiteral("FKALSOCCC"); + + auto alice = std::make_shared(userId, deviceId); + alice->createNewAccount(); + alice->generateOneTimeKeys(1); + + auto idSig = alice->signIdentityKeys(); + + QJsonObject body + { + {"algorithms", QJsonArray{"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}}, + {"user_id", userId}, + {"device_id", deviceId}, + {"keys", + QJsonObject{ + {QStringLiteral("curve25519:") + deviceId, QString::fromUtf8(alice->identityKeys().curve25519)}, + {QStringLiteral("ed25519:") + deviceId, QString::fromUtf8(alice->identityKeys().ed25519)} + } + }, + {"signatures", + QJsonObject{ + {userId, + QJsonObject{ + {"ed25519:" + deviceId, QString::fromUtf8(idSig)} + } + } + } + } + }; + + DeviceKeys deviceKeys = alice->getDeviceKeys(); + QCOMPARE(QJsonDocument(toJson(deviceKeys)).toJson(QJsonDocument::Compact), + QJsonDocument(body).toJson(QJsonDocument::Compact)); + + QVERIFY(verifyIdentitySignature(fromJson(body), deviceId, userId)); + QVERIFY(verifyIdentitySignature(deviceKeys, deviceId, userId)); +} + +QTEST_MAIN(TestOlmUtility) diff --git a/autotests/testolmutility.h b/autotests/testolmutility.h new file mode 100644 index 00000000..b30249c8 --- /dev/null +++ b/autotests/testolmutility.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include + +class TestOlmUtility : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void canonicalJSON(); + void verifySignedOneTimeKey(); + void validUploadKeysRequest(); +}; diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 750d7318..e27bbee1 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -110,10 +110,10 @@ IdentityKeys QOlmAccount::identityKeys() const QByteArray QOlmAccount::sign(const QByteArray &message) const { - const size_t signatureLength = olm_account_signature_length(m_account); - QByteArray signatureBuffer(signatureLength, '0'); + QByteArray signatureBuffer(olm_account_signature_length(m_account), '0'); + const auto error = olm_account_sign(m_account, message.data(), message.length(), - signatureBuffer.data(), signatureLength); + signatureBuffer.data(), signatureBuffer.length()); if (error == olm_error()) { throw lastError(m_account); @@ -216,9 +216,8 @@ OlmAccount *Quotient::QOlmAccount::data() return m_account; } -UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKeys) +DeviceKeys QOlmAccount::getDeviceKeys() const { - DeviceKeys deviceKeys; deviceKeys.userId = m_userId; deviceKeys.deviceId = m_deviceId; @@ -231,6 +230,13 @@ UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKey const auto sign = signIdentityKeys(); deviceKeys.signatures[m_userId]["ed25519:" + m_deviceId] = sign; + return deviceKeys; +} + +UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKeys) +{ + auto deviceKeys = getDeviceKeys(); + if (oneTimeKeys.curve25519().isEmpty()) { return new UploadKeysJob(deviceKeys); } @@ -272,36 +278,34 @@ bool Quotient::verifyIdentitySignature(const DeviceKeys &deviceKeys, const auto signingKey = deviceKeys.keys[signKeyId]; const auto signature = deviceKeys.signatures[userId][signKeyId]; - if (signature.isEmpty()) { + qDebug() << "signature empty"; return false; } return ed25519VerifySignature(signingKey, toJson(deviceKeys), signature); } -bool Quotient::ed25519VerifySignature(QString signingKey, - QJsonObject obj, - QString signature) +bool Quotient::ed25519VerifySignature(const QString &signingKey, + const QJsonObject &obj, + const QString &signature) { if (signature.isEmpty()) { return false; } + QJsonObject obj1 = obj; - obj.remove("unsigned"); - obj.remove("signatures"); - - QJsonDocument doc; - doc.setObject(obj); - auto canonicalJson = doc.toJson(QJsonDocument::Compact); + obj1.remove("unsigned"); + obj1.remove("signatures"); - qDebug() << canonicalJson; + 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(result)) { + qDebug() << "error:" << std::get(result); return false; } diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index 09ef623a..de78a8af 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -72,6 +72,8 @@ public: UploadKeysJob *createUploadKeyRequest(const OneTimeKeys &oneTimeKeys); + DeviceKeys getDeviceKeys() const; + //! Remove the one time key used to create the supplied session. [[nodiscard]] std::optional removeOneTimeKeys(const std::unique_ptr &session) const; @@ -104,9 +106,9 @@ bool verifyIdentitySignature(const DeviceKeys &deviceKeys, const QString &userId); //! checks if the signature is signed by the signing_key -bool ed25519VerifySignature(QString signingKey, - QJsonObject obj, - QString signature); +bool ed25519VerifySignature(const QString &signingKey, + const QJsonObject &obj, + const QString &signature); } // namespace Quotient diff --git a/lib/crypto/qolmutility.cpp b/lib/crypto/qolmutility.cpp index 3c6a14c7..ad78a226 100644 --- a/lib/crypto/qolmutility.cpp +++ b/lib/crypto/qolmutility.cpp @@ -5,6 +5,7 @@ #ifdef Quotient_E2EE_ENABLED #include "crypto/qolmutility.h" #include "olm/olm.h" +#include using namespace Quotient; @@ -19,10 +20,12 @@ QOlmUtility::QOlmUtility() { auto utility = new uint8_t[olm_utility_size()]; m_utility = olm_utility(utility); + qDebug() << "created"; } QOlmUtility::~QOlmUtility() { + qDebug() << "deleted"; olm_clear_utility(m_utility); delete[](reinterpret_cast(m_utility)); } @@ -43,15 +46,27 @@ QString QOlmUtility::sha256Utf8Msg(const QString &message) const } std::variant QOlmUtility::ed25519Verify(const QByteArray &key, - const QByteArray &message, QByteArray &signature) + const QByteArray &message, const QByteArray &signature) { - const auto error = olm_ed25519_verify(m_utility, key.data(), key.length(), - message.data(), message.length(), signature.data(), signature.length()); + QByteArray signatureBuf(signature.length(), '0'); + std::copy(signature.begin(), signature.end(), signatureBuf.begin()); + qDebug() << "3" << key << message << signature; + + 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()) { + qDebug() << QString(olm_utility_last_error(m_utility)); return lastError(m_utility); } - return error == 0; + + if (ret != 0) { + qDebug() << "ed25519Verify" << ret; + return false; + } + return true; } diff --git a/lib/crypto/qolmutility.h b/lib/crypto/qolmutility.h index 16c330eb..3de09ab4 100644 --- a/lib/crypto/qolmutility.h +++ b/lib/crypto/qolmutility.h @@ -36,7 +36,7 @@ public: //! \param message QByteArray The message that was signed. //! \param signature QByteArray The signature of the message. std::variant ed25519Verify(const QByteArray &key, - const QByteArray &message, QByteArray &signature); + const QByteArray &message, const QByteArray &signature); private: -- cgit v1.2.3 From d9dc94a8fb59c8590c4aa7cdf773c2825e69d823 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 31 Jan 2021 21:11:23 +0100 Subject: Fix signing keys --- autotests/testolmaccount.cpp | 12 ++++++++++-- autotests/testolmutility.cpp | 2 -- lib/crypto/qolmaccount.cpp | 19 +++++++++++++------ lib/crypto/qolmutility.cpp | 6 ------ 4 files changed, 23 insertions(+), 16 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 8d979e0b..4eed1980 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -309,7 +309,6 @@ void TestOlmAccount::claimKeys() deviceKeys[bob->userId()] = QStringList(); auto job = alice->callApi(deviceKeys); connect(job, &BaseJob::result, this, [bob, alice, aliceOlm, job, this] { - qDebug() << job->jsonData(); auto bobDevices = job->deviceKeys()[bob->userId()]; QVERIFY(bobDevices.size() > 0); @@ -337,7 +336,16 @@ void TestOlmAccount::claimKeys() // The key is the one bob sent. auto oneTimeKey = job->oneTimeKeys()[userId][deviceId]; - QVERIFY(oneTimeKey.canConvert()); + QVERIFY(oneTimeKey.canConvert()); + + QVariantMap varMap = oneTimeKey.toMap(); + bool found = false; + for (const auto key : varMap.keys()) { + if (key.startsWith(QStringLiteral("signed_curve25519"))) { + found = true; + } + } + QVERIFY(found); //auto algo = oneTimeKey.begin().key(); //auto contents = oneTimeKey.begin().value(); diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index cb92a0df..1d9978d3 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -61,7 +61,6 @@ void TestOlmUtility::verifySignedOneTimeKey() auto utilityBuf = new uint8_t[olm_utility_size()]; auto utility = olm_utility(utilityBuf); - qDebug() << "1" << aliceOlm->identityKeys().ed25519 << msg << QString::fromUtf8(sig); QByteArray signatureBuf1(sig.length(), '0'); std::copy(sig.begin(), sig.end(), signatureBuf1.begin()); @@ -73,7 +72,6 @@ void TestOlmUtility::verifySignedOneTimeKey() msg.size(), (void *)sig.data(), sig.size()); - qDebug() << "2" << aliceOlm->identityKeys().ed25519 << msg << QString::fromUtf8(signatureBuf1); QCOMPARE(std::string(olm_utility_last_error(utility)), "SUCCESS"); QCOMPARE(res, 0); diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index e27bbee1..0f354d3e 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -129,10 +129,19 @@ QByteArray QOlmAccount::sign(const QJsonObject &message) const 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()); + 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)); } @@ -279,7 +288,6 @@ bool Quotient::verifyIdentitySignature(const DeviceKeys &deviceKeys, const auto signature = deviceKeys.signatures[userId][signKeyId]; if (signature.isEmpty()) { - qDebug() << "signature empty"; return false; } @@ -305,7 +313,6 @@ bool Quotient::ed25519VerifySignature(const QString &signingKey, auto signatureBuf = signature.toUtf8(); auto result = utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf); if (std::holds_alternative(result)) { - qDebug() << "error:" << std::get(result); return false; } diff --git a/lib/crypto/qolmutility.cpp b/lib/crypto/qolmutility.cpp index ad78a226..87615770 100644 --- a/lib/crypto/qolmutility.cpp +++ b/lib/crypto/qolmutility.cpp @@ -20,12 +20,10 @@ QOlmUtility::QOlmUtility() { auto utility = new uint8_t[olm_utility_size()]; m_utility = olm_utility(utility); - qDebug() << "created"; } QOlmUtility::~QOlmUtility() { - qDebug() << "deleted"; olm_clear_utility(m_utility); delete[](reinterpret_cast(m_utility)); } @@ -51,19 +49,15 @@ std::variant QOlmUtility::ed25519Verify(const QByteArray &key, QByteArray signatureBuf(signature.length(), '0'); std::copy(signature.begin(), signature.end(), signatureBuf.begin()); - qDebug() << "3" << key << message << signature; - 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()) { - qDebug() << QString(olm_utility_last_error(m_utility)); return lastError(m_utility); } if (ret != 0) { - qDebug() << "ed25519Verify" << ret; return false; } return true; -- cgit v1.2.3 From 97f2d162618e7fb2473c184c77875ac9d5e8d1d5 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 15 Feb 2021 18:10:34 +0100 Subject: Apply a few more comments --- autotests/testolmaccount.cpp | 4 ++-- autotests/testolmaccount.h | 2 +- lib/encryptionmanager.cpp | 2 +- lib/room.cpp | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 4eed1980..8129ae5b 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -9,7 +9,7 @@ using namespace Quotient; -void TestOlmAccount::pickleUnpickedTest() +void TestOlmAccount::pickleUnpickledTest() { QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); olmAccount.createNewAccount(); @@ -73,7 +73,7 @@ void TestOlmAccount::deviceKeys() { // copied from mtxclient DeviceKeys device1; - device1.userId = "@alice:example.com"; + device1.userId = "@alice:example.com"; device1.deviceId = "JLAFKJWSCS"; device1.keys = {{"curve25519:JLAFKJWSCS", "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"}, {"ed25519:JLAFKJWSCS", "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"}}; diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index b6b9cae4..97fbca18 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -15,7 +15,7 @@ class TestOlmAccount : public QObject private Q_SLOTS: - void pickleUnpickedTest(); + void pickleUnpickledTest(); void identityKeysValid(); void signatureValid(); void oneTimeKeysValid(); diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 719add1d..3c3103a7 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -69,7 +69,7 @@ public: UploadKeysJob* uploadOneTimeKeysJob = nullptr; QueryKeysJob* queryKeysJob = nullptr; - QScopedPointer olmAccount; + std::unique_ptr olmAccount; float signedKeysProportion; float oneTimeKeyThreshold; diff --git a/lib/room.cpp b/lib/room.cpp index a8a7fe0c..1a7a9911 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -66,8 +66,8 @@ #ifdef Quotient_E2EE_ENABLED #include "crypto/qolmaccount.h" -# include "crypto/qolmerrors.h" -# include "crypto/qolminboundsession.h" +#include "crypto/qolmerrors.h" +#include "crypto/qolminboundsession.h" #endif // Quotient_E2EE_ENABLED using namespace Quotient; -- cgit v1.2.3 From 5c50f0ccadedbcb07d51dbac9b1d59c03a26af2f Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 15 Feb 2021 18:13:27 +0100 Subject: fix typo --- autotests/testolmaccount.cpp | 1 - lib/encryptionmanager.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 8129ae5b..91342241 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -187,7 +187,6 @@ void TestOlmAccount::uploadIdentityKey() auto olmAccount = conn->olmAccount(); auto idKeys = olmAccount->identityKeys(); - QVERIFY(idKeys.curve25519.size() > 10); QVERIFY(idKeys.curve25519.size() > 10); OneTimeKeys unused; diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 3c3103a7..53890fdb 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -350,7 +350,7 @@ QByteArray EncryptionManager::olmAccountPickle() QOlmAccount *EncryptionManager::account() const { - return d->olmAccount.data(); + return d->olmAccount.get(); } void EncryptionManager::Private::updateKeysToUpload() -- cgit v1.2.3 From ea617d31cf3f72f76fd49c0a20f445a78678fe5f Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 19 Apr 2021 16:07:57 +0200 Subject: Apply suggestions from code review Co-authored-by: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> --- autotests/testgroupsession.cpp | 1 - autotests/testgroupsession.h | 2 +- autotests/testolmaccount.h | 3 +-- autotests/testolmsession.cpp | 2 +- autotests/testolmsession.h | 2 +- autotests/testolmutility.h | 2 +- lib/crypto/qolmaccount.cpp | 16 +++++++--------- lib/crypto/qolmaccount.h | 7 ++++--- lib/crypto/qolmerrors.cpp | 1 + lib/crypto/qolminboundsession.cpp | 8 +++----- lib/crypto/qolmoutboundsession.cpp | 8 ++++---- lib/crypto/qolmoutboundsession.h | 5 +++-- lib/crypto/qolmutility.h | 4 ++-- 13 files changed, 29 insertions(+), 32 deletions(-) (limited to 'autotests') diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 858f29d8..ea1bb4a9 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -41,7 +41,6 @@ void TestOlmSession::groupSessionCryptoValid() const auto plainText = QStringLiteral("Hello world!"); const auto ciphertext = std::get(ogs->encrypt(plainText)); - qDebug() << ciphertext; // ciphertext valid base64? QVERIFY(QByteArray::fromBase64Encoding(ciphertext).decodingStatus == QByteArray::Base64DecodingStatus::Ok); diff --git a/autotests/testgroupsession.h b/autotests/testgroupsession.h index 27f34bec..7743295f 100644 --- a/autotests/testgroupsession.h +++ b/autotests/testgroupsession.h @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include +#include class TestOlmSession : public QObject { diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index 97fbca18..8901f095 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include +#include #include namespace Quotient { @@ -13,7 +13,6 @@ class TestOlmAccount : public QObject { Q_OBJECT - private Q_SLOTS: void pickleUnpickledTest(); void identityKeysValid(); diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 72c54174..0803cc6d 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -26,7 +26,7 @@ std::pair, std::unique_ptr> createSess const auto preKey = outbound->encrypt(""); // Payload does not matter for PreKey if (preKey.type() != QOlmMessage::PreKey) { - throw "Wrong first message type received, can't create session"; + QFAIL("Wrong first message type received, can't create session"); } auto inbound = std::get>(accountB.createInboundSession(preKey)); return std::make_pair, std::unique_ptr>(std::move(inbound), std::move(outbound)); diff --git a/autotests/testolmsession.h b/autotests/testolmsession.h index 9a5798fa..bd670c9b 100644 --- a/autotests/testolmsession.h +++ b/autotests/testolmsession.h @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include +#include class TestOlmSession : public QObject { diff --git a/autotests/testolmutility.h b/autotests/testolmutility.h index b30249c8..f2a3ca45 100644 --- a/autotests/testolmutility.h +++ b/autotests/testolmutility.h @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include +#include class TestOlmUtility : public QObject { diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 4f007e2f..8b964c9f 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -151,9 +151,9 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const size_t 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); + 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); @@ -219,12 +219,12 @@ std::optional QOlmAccount::removeOneTimeKeys(const std::unique_ptr oneTimeKeysSigned; for (const auto &[keyId, key] : asKeyValueRange(temp)) { - QVariant keyVar; - keyVar.setValue(key); - oneTimeKeysSigned[keyId] = keyVar; + oneTimeKeysSigned[keyId] = QVariant::fromValue(key); } return new UploadKeysJob(deviceKeys, oneTimeKeysSigned); diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index 1e198687..c93a8354 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once #include "csapi/keys.h" @@ -37,7 +38,7 @@ public: //! 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); + void unpickle(QByteArray &pickled, const PicklingMode &mode); //! Serialises an OlmAccount to encrypted Base64. std::variant pickle(const PicklingMode &mode); @@ -62,7 +63,7 @@ public: //! Gets the OlmAccount's one time keys formatted as JSON. OneTimeKeys oneTimeKeys() const; - //! Sign all time key. + //! Sign all one time keys. QMap signOneTimeKeys(const OneTimeKeys &keys) const; //! Sign one time key. @@ -84,7 +85,7 @@ public: //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. //! - //! \param theirIdentityKey - The identity key of an Olm account that + //! \param theirIdentityKey - The identity key of the Olm account that //! encrypted this Olm message. std::variant, QOlmError> createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage); diff --git a/lib/crypto/qolmerrors.cpp b/lib/crypto/qolmerrors.cpp index 46b2618c..2c3926de 100644 --- a/lib/crypto/qolmerrors.cpp +++ b/lib/crypto/qolmerrors.cpp @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later + #include "qolmerrors.h" Quotient::QOlmError Quotient::fromString(const std::string &error_raw) { diff --git a/lib/crypto/qolminboundsession.cpp b/lib/crypto/qolminboundsession.cpp index e1ced72b..beaf3299 100644 --- a/lib/crypto/qolminboundsession.cpp +++ b/lib/crypto/qolminboundsession.cpp @@ -10,7 +10,6 @@ using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { const std::string error_raw = olm_inbound_group_session_last_error(session); - std::cout << error_raw; return fromString(error_raw); } @@ -39,7 +38,6 @@ std::unique_ptr QOlmInboundGroupSession::create(const Q return std::make_unique(olmInboundGroupSession); } - std::unique_ptr QOlmInboundGroupSession::import(const QByteArray &key) { const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); @@ -123,9 +121,9 @@ std::variant, QOlmError> QOlmInboundGroupSession::d std::variant 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(keyBuf.data()), keyLen, 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(keyBuf.data()), keyLength, messageIndex); if (error == olm_error()) { return lastError(m_groupSession); diff --git a/lib/crypto/qolmoutboundsession.cpp b/lib/crypto/qolmoutboundsession.cpp index bf8dce61..bc572ba5 100644 --- a/lib/crypto/qolmoutboundsession.cpp +++ b/lib/crypto/qolmoutboundsession.cpp @@ -27,8 +27,8 @@ QOlmOutboundGroupSession::~QOlmOutboundGroupSession() std::unique_ptr 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 randomLength = olm_init_outbound_group_session_random_length(olmOutboundGroupSession); + QByteArray randomBuf = getRandom(randomLength); const auto error = olm_init_outbound_group_session(olmOutboundGroupSession, reinterpret_cast(randomBuf.data()), randomBuf.length()); @@ -86,8 +86,8 @@ std::variant, QOlmError> QOlmOutboundG std::variant 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 messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); + QByteArray messageBuf(messageMaxLength, '0'); const auto error = olm_group_encrypt(m_groupSession, reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), reinterpret_cast(messageBuf.data()), messageBuf.length()); diff --git a/lib/crypto/qolmoutboundsession.h b/lib/crypto/qolmoutboundsession.h index f1df0395..201a178a 100644 --- a/lib/crypto/qolmoutboundsession.h +++ b/lib/crypto/qolmoutboundsession.h @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once #include "olm/olm.h" @@ -21,11 +22,11 @@ public: //! Creates a new instance of `QOlmOutboundGroupSession`. //! Throw OlmError on errors static std::unique_ptr create(); - //! Serialises an `QOlmOutboundGroupSession` to encrypted Base64. + //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. std::variant pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. - static std::variant, QOlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); + static std::variant, QOlmError> unpickle(const QByteArray &pickled, const PicklingMode &mode); //! Encrypts a plaintext message using the session. std::variant encrypt(const QString &plaintext); diff --git a/lib/crypto/qolmutility.h b/lib/crypto/qolmutility.h index fc6569f7..5fd28dcc 100644 --- a/lib/crypto/qolmutility.h +++ b/lib/crypto/qolmutility.h @@ -27,11 +27,11 @@ public: QString sha256Bytes(const QByteArray &inputBuf) const; //! Convenience function that converts the UTF-8 message - //! to bytes and then calls `sha256_bytes()`, returning its output. + //! to bytes and then calls `sha256Bytes()`, returning its output. QString sha256Utf8Msg(const QString &message) const; //! Verify a ed25519 signature. - //! \param any QByteArray The public part of the ed25519 key that signed the message. + //! \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 ed25519Verify(const QByteArray &key, -- cgit v1.2.3 From c2836e007e2d46c0c20270b99ede5b78d2c7170b Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 22 Feb 2021 13:51:51 +0100 Subject: ssl --- autotests/testolmaccount.cpp | 1 + lib/connection.cpp | 5 +++++ lib/connection.h | 5 +++++ lib/connectiondata.cpp | 6 ++++++ lib/connectiondata.h | 1 + lib/networkaccessmanager.cpp | 17 ++++++++++++----- lib/networkaccessmanager.h | 1 + 7 files changed, 31 insertions(+), 5 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 91342241..eb44791a 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -165,6 +165,7 @@ void TestOlmAccount::encryptedFile() #define CREATE_CONNECTION(VAR, USERNAME, SECRET, DEVICE_NAME) \ auto VAR = std::make_shared(); \ + VAR->ignoreSslErrors(true); \ (VAR) ->resolveServer("@alice:localhost:" + QString::number(443)); \ connect( (VAR) .get(), &Connection::loginFlowsChanged, this, [this, VAR ] () { \ (VAR) ->loginWithPassword( (USERNAME) , SECRET , DEVICE_NAME , ""); \ diff --git a/lib/connection.cpp b/lib/connection.cpp index 704bc1b4..62427ae1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -572,6 +572,11 @@ void Connection::sync(int timeout) }); } +void Connection::ignoreSslErrors(bool ignore) +{ + connectionData()->ignoreSslErrors(ignore); +} + void Connection::syncLoop(int timeout) { if (d->syncLoopConnection && d->syncTimeout == timeout) { diff --git a/lib/connection.h b/lib/connection.h index 6729b23d..93e22da2 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -476,6 +476,11 @@ public: setUserFactory(defaultUserFactory()); } + /// Ignore ssl errors (usefull for automated testing with local synapse + /// instance). + /// \internal + void ignoreSslErrors(bool ignore); + public Q_SLOTS: /// \brief Set the homeserver base URL and retrieve its login flows /// diff --git a/lib/connectiondata.cpp b/lib/connectiondata.cpp index 87ad4577..672feb06 100644 --- a/lib/connectiondata.cpp +++ b/lib/connectiondata.cpp @@ -128,6 +128,12 @@ bool ConnectionData::needsToken(const QString& requestName) const != d->needToken.cend(); } +void ConnectionData::ignoreSslErrors(bool ignore) const +{ + auto quotientNam = static_cast(nam()); + quotientNam.ignoreSslErrors(ignore); +} + void ConnectionData::setDeviceId(const QString& deviceId) { d->deviceId = deviceId; diff --git a/lib/connectiondata.h b/lib/connectiondata.h index e16a2dac..203dc9e8 100644 --- a/lib/connectiondata.h +++ b/lib/connectiondata.h @@ -29,6 +29,7 @@ public: bool needsToken(const QString& requestName) const; QNetworkAccessManager* nam() const; + void ignoreSslErrors(bool ignore = true) const; void setBaseUrl(QUrl baseUrl); void setToken(QByteArray accessToken); void setDeviceId(const QString& deviceId); diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 293538ee..d0380cec 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -40,17 +40,24 @@ public: NetworkAccessManager::NetworkAccessManager(QObject* parent) : QNetworkAccessManager(parent), d(std::make_unique(this)) -{ - connect(this, &QNetworkAccessManager::sslErrors, this, [](QNetworkReply *reply, const QList &errors) { - reply->ignoreSslErrors(); - }); -} +{} QList NetworkAccessManager::ignoredSslErrors() const { return d->ignoredSslErrors; } +void NetworkAccessManager::ignoreSslErrors(bool ignore) const +{ + if (ignore) { + connect(this, &QNetworkAccessManager::sslErrors, this, [](QNetworkReply *reply, const QList &errors) { + reply->ignoreSslErrors(); + }); + } else { + disconnect(this, &QNetworkAccessManager::sslErrors, this, nullptr); + } +} + void NetworkAccessManager::addIgnoredSslError(const QSslError& error) { d->ignoredSslErrors << error; diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index 87bc12a1..7643302f 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -19,6 +19,7 @@ public: QList ignoredSslErrors() const; void addIgnoredSslError(const QSslError& error); void clearIgnoredSslErrors(); + void ignoreSslErrors(bool ignore = true) const; /** Get a pointer to the singleton */ static NetworkAccessManager* instance(); -- cgit v1.2.3 From 0a75a095665101d4ffcbec10b43633eee5a0d6d3 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 6 May 2021 01:08:53 +0200 Subject: Fix everything --- autotests/testolmaccount.cpp | 3 ++- autotests/testolmaccount.h | 2 +- autotests/testolmsession.cpp | 3 ++- autotests/testolmsession.h | 2 +- autotests/testolmutility.cpp | 2 +- lib/connection.cpp | 5 ----- lib/connection.h | 5 ----- lib/connectiondata.cpp | 6 ------ lib/connectiondata.h | 1 - lib/crypto/qolmaccount.cpp | 6 +++--- lib/crypto/qolmaccount.h | 2 +- lib/crypto/qolmoutboundsession.h | 2 +- 12 files changed, 12 insertions(+), 27 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index eb44791a..1c296db9 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -6,6 +6,7 @@ #include "crypto/qolmaccount.h" #include "connection.h" #include "events/encryptedfile.h" +#include "networkaccessmanager.h" using namespace Quotient; @@ -164,8 +165,8 @@ void TestOlmAccount::encryptedFile() } #define CREATE_CONNECTION(VAR, USERNAME, SECRET, DEVICE_NAME) \ + NetworkAccessManager::instance()->ignoreSslErrors(true); \ auto VAR = std::make_shared(); \ - VAR->ignoreSslErrors(true); \ (VAR) ->resolveServer("@alice:localhost:" + QString::number(443)); \ connect( (VAR) .get(), &Connection::loginFlowsChanged, this, [this, VAR ] () { \ (VAR) ->loginWithPassword( (USERNAME) , SECRET , DEVICE_NAME , ""); \ diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index 8901f095..bab9eed2 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include +#include #include namespace Quotient { diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 0803cc6d..dba78277 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -26,7 +26,8 @@ std::pair, std::unique_ptr> createSess const auto preKey = outbound->encrypt(""); // Payload does not matter for PreKey if (preKey.type() != QOlmMessage::PreKey) { - QFAIL("Wrong first message type received, can't create session"); + // We can't call QFail here because it's an helper function returning a value + throw "Wrong first message type received, can't create session"; } auto inbound = std::get>(accountB.createInboundSession(preKey)); return std::make_pair, std::unique_ptr>(std::move(inbound), std::move(outbound)); diff --git a/autotests/testolmsession.h b/autotests/testolmsession.h index bd670c9b..9a5798fa 100644 --- a/autotests/testolmsession.h +++ b/autotests/testolmsession.h @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include +#include class TestOlmSession : public QObject { diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index 1d9978d3..2eec7e00 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -118,7 +118,7 @@ void TestOlmUtility::validUploadKeysRequest() } }; - DeviceKeys deviceKeys = alice->getDeviceKeys(); + DeviceKeys deviceKeys = alice->deviceKeys(); QCOMPARE(QJsonDocument(toJson(deviceKeys)).toJson(QJsonDocument::Compact), QJsonDocument(body).toJson(QJsonDocument::Compact)); diff --git a/lib/connection.cpp b/lib/connection.cpp index 62427ae1..704bc1b4 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -572,11 +572,6 @@ void Connection::sync(int timeout) }); } -void Connection::ignoreSslErrors(bool ignore) -{ - connectionData()->ignoreSslErrors(ignore); -} - void Connection::syncLoop(int timeout) { if (d->syncLoopConnection && d->syncTimeout == timeout) { diff --git a/lib/connection.h b/lib/connection.h index 93e22da2..6729b23d 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -476,11 +476,6 @@ public: setUserFactory(defaultUserFactory()); } - /// Ignore ssl errors (usefull for automated testing with local synapse - /// instance). - /// \internal - void ignoreSslErrors(bool ignore); - public Q_SLOTS: /// \brief Set the homeserver base URL and retrieve its login flows /// diff --git a/lib/connectiondata.cpp b/lib/connectiondata.cpp index 672feb06..87ad4577 100644 --- a/lib/connectiondata.cpp +++ b/lib/connectiondata.cpp @@ -128,12 +128,6 @@ bool ConnectionData::needsToken(const QString& requestName) const != d->needToken.cend(); } -void ConnectionData::ignoreSslErrors(bool ignore) const -{ - auto quotientNam = static_cast(nam()); - quotientNam.ignoreSslErrors(ignore); -} - void ConnectionData::setDeviceId(const QString& deviceId) { d->deviceId = deviceId; diff --git a/lib/connectiondata.h b/lib/connectiondata.h index 203dc9e8..e16a2dac 100644 --- a/lib/connectiondata.h +++ b/lib/connectiondata.h @@ -29,7 +29,6 @@ public: bool needsToken(const QString& requestName) const; QNetworkAccessManager* nam() const; - void ignoreSslErrors(bool ignore = true) const; void setBaseUrl(QUrl baseUrl); void setToken(QByteArray accessToken); void setDeviceId(const QString& deviceId); diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 8b964c9f..9368de4f 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -243,10 +243,10 @@ DeviceKeys QOlmAccount::deviceKeys() const UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKeys) { - auto deviceKeys = deviceKeys(); + auto keys = deviceKeys(); if (oneTimeKeys.curve25519().isEmpty()) { - return new UploadKeysJob(deviceKeys); + return new UploadKeysJob(keys); } // Sign & append the one time keys. @@ -256,7 +256,7 @@ UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKey oneTimeKeysSigned[keyId] = QVariant::fromValue(key); } - return new UploadKeysJob(deviceKeys, oneTimeKeysSigned); + return new UploadKeysJob(keys, oneTimeKeysSigned); } std::variant, QOlmError> QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index c93a8354..f3ca82f0 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -73,7 +73,7 @@ public: UploadKeysJob *createUploadKeyRequest(const OneTimeKeys &oneTimeKeys); - DeviceKeys getDeviceKeys() const; + DeviceKeys deviceKeys() const; //! Remove the one time key used to create the supplied session. [[nodiscard]] std::optional removeOneTimeKeys(const std::unique_ptr &session) const; diff --git a/lib/crypto/qolmoutboundsession.h b/lib/crypto/qolmoutboundsession.h index 201a178a..4e06561e 100644 --- a/lib/crypto/qolmoutboundsession.h +++ b/lib/crypto/qolmoutboundsession.h @@ -26,7 +26,7 @@ public: std::variant pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. - static std::variant, QOlmError> unpickle(const QByteArray &pickled, const PicklingMode &mode); + static std::variant, QOlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); //! Encrypts a plaintext message using the session. std::variant encrypt(const QString &plaintext); -- cgit v1.2.3 From cddab50ca80944203930255e37e825abb47a272b Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 10 Jun 2021 15:22:15 +0200 Subject: Finish writing TestOlmAccount::signatureValid --- autotests/testolmaccount.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 1c296db9..1bd63a48 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -4,6 +4,7 @@ #include "testolmaccount.h" #include "crypto/qolmaccount.h" +#include "crypto/qolmutility.h" #include "connection.h" #include "events/encryptedfile.h" #include "networkaccessmanager.h" @@ -47,12 +48,12 @@ void TestOlmAccount::signatureValid() const auto signature = olmAccount.sign(message); QVERIFY(QByteArray::fromBase64Encoding(signature).decodingStatus == QByteArray::Base64DecodingStatus::Ok); - //let utility = OlmUtility::new(); - //let identity_keys = olm_account.parsed_identity_keys(); - //let ed25519_key = identity_keys.ed25519(); - //assert!(utility - // .ed25519_verify(&ed25519_key, message, &signature) - // .unwrap()); + QOlmUtility utility; + const auto identityKeys = olmAccount.identityKeys(); + const auto ed25519Key = identityKeys.ed25519; + const auto verify = utility.ed25519Verify(ed25519Key, message, signature); + QVERIFY(std::holds_alternative(verify)); + QVERIFY(std::get(verify) == true); } void TestOlmAccount::oneTimeKeysValid() @@ -341,7 +342,7 @@ void TestOlmAccount::claimKeys() QVariantMap varMap = oneTimeKey.toMap(); bool found = false; - for (const auto key : varMap.keys()) { + for (const auto &key : varMap.keys()) { if (key.startsWith(QStringLiteral("signed_curve25519"))) { found = true; } -- cgit v1.2.3 From 9bf12da8aaa1b2005d9d7d8eae4269c8a9bf1c08 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 10 Jun 2021 17:42:07 +0200 Subject: test: Add QueryKey test (failing) --- autotests/testolmaccount.cpp | 89 ++++++++++++++++++++++++++++++++++++++++---- autotests/testolmaccount.h | 1 + 2 files changed, 83 insertions(+), 7 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 1bd63a48..9195bb62 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-FileCopyrightText: 2020 mtxclient developers // // SPDX-License-Identifier: LGPL-2.1-or-later @@ -285,6 +286,79 @@ void TestOlmAccount::uploadKeys() QVERIFY(spy3.wait(10000)); } +void TestOlmAccount::queryTest() +{ + CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") + CREATE_CONNECTION(bob, "bob", "secret", "BobPhone") + + // Create and upload keys for both users. + auto aliceOlm = alice->olmAccount(); + aliceOlm->generateOneTimeKeys(1); + auto aliceRes = aliceOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys()); + connect(aliceRes, &BaseJob::result, this, [aliceRes] { + QCOMPARE(aliceRes->oneTimeKeyCounts().size(), 1); + QCOMPARE(aliceRes->oneTimeKeyCounts()["signed_curve25519"], 1); + }); + QSignalSpy spy(aliceRes, &BaseJob::result); + bob->run(aliceRes); + QVERIFY(spy.wait(10000)); + + auto bobOlm = bob->olmAccount(); + bobOlm->generateOneTimeKeys(1); + auto bobRes = aliceOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys()); + connect(bobRes, &BaseJob::result, this, [bobRes] { + QCOMPARE(bobRes->oneTimeKeyCounts().size(), 1); + QCOMPARE(bobRes->oneTimeKeyCounts()["signed_curve25519"], 1); + }); + QSignalSpy spy1(bobRes, &BaseJob::result); + bob->run(bobRes); + QVERIFY(spy1.wait(10000)); + + { + // Each user is requests each other's keys. + QHash deviceKeys; + deviceKeys[bob->userId()] = QStringList(); + auto job = alice->callApi(deviceKeys); + QSignalSpy spy(job, &BaseJob::result); + connect(job, &BaseJob::result, this, [job, &bob, &bobOlm] { + QCOMPARE(job->failures().size(), 0); + + auto aliceDevices = job->deviceKeys()[bob->userId()]; + QVERIFY(aliceDevices.size() > 0); + + auto devKeys = aliceDevices[bob->deviceId()]; + QCOMPARE(devKeys.userId, bob->userId()); + QCOMPARE(devKeys.deviceId, bob->deviceId()); + QCOMPARE(devKeys.keys, bobOlm->deviceKeys().keys); + QCOMPARE(devKeys.signatures, bobOlm->deviceKeys().signatures); + }); + QVERIFY(spy.wait(10000)); + } + + { + QHash deviceKeys; + deviceKeys[alice->userId()] = QStringList(); + auto job = bob->callApi(deviceKeys); + QSignalSpy spy(job, &BaseJob::result); + connect(job, &BaseJob::result, this, [job, &alice, &aliceOlm] { + QCOMPARE(job->failures().size(), 0); + + auto bobDevices = job->deviceKeys()[alice->userId()]; + QVERIFY(bobDevices.size() > 0); + + auto devKeys = bobDevices[alice->deviceId()]; + qDebug() << bobDevices.keys(); + QCOMPARE(devKeys.userId, alice->userId()); + QCOMPARE(devKeys.deviceId, alice->deviceId()); + QCOMPARE(devKeys.keys, aliceOlm->deviceKeys().keys); + QCOMPARE(devKeys.signatures, aliceOlm->deviceKeys().signatures); + }); + QVERIFY(spy.wait(10000)); + } +} + + + void TestOlmAccount::claimKeys() { CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") @@ -365,36 +439,37 @@ void TestOlmAccount::claimMultipleKeys() auto olm = alice->olmAccount(); olm->generateOneTimeKeys(10); auto res = olm->createUploadKeyRequest(olm->oneTimeKeys()); - alice->run(res); + QSignalSpy spy(res, &BaseJob::result); connect(res, &BaseJob::result, this, [res] { QCOMPARE(res->oneTimeKeyCounts().size(), 1); QCOMPARE(res->oneTimeKeyCounts()["signed_curve25519"], 10); }); - QSignalSpy spy(res, &BaseJob::result); + alice->run(res); auto olm1 = alice1->olmAccount(); olm1->generateOneTimeKeys(10); auto res1 = olm1->createUploadKeyRequest(olm1->oneTimeKeys()); - alice1->run(res1); + QSignalSpy spy1(res1, &BaseJob::result); connect(res1, &BaseJob::result, this, [res1] { QCOMPARE(res1->oneTimeKeyCounts().size(), 1); QCOMPARE(res1->oneTimeKeyCounts()["signed_curve25519"], 10); }); - QSignalSpy spy1(res1, &BaseJob::result); + alice1->run(res1); auto olm2 = alice2->olmAccount(); olm2->generateOneTimeKeys(10); auto res2 = olm2->createUploadKeyRequest(olm2->oneTimeKeys()); - alice2->run(res2); + QSignalSpy spy2(res2, &BaseJob::result); connect(res2, &BaseJob::result, this, [res2] { QCOMPARE(res2->oneTimeKeyCounts().size(), 1); QCOMPARE(res2->oneTimeKeyCounts()["signed_curve25519"], 10); }); - QSignalSpy spy2(res2, &BaseJob::result); + alice2->run(res2); + QVERIFY(spy.wait(10000)); QVERIFY(spy1.wait(10000)); - QVERIFY(spy2.wait(10000)); + QVERIFY(spy2.wait(1000)); // TODO this is failing even with 10000 // Bob will claim all keys from alice CREATE_CONNECTION(bob, "bob", "secret", "BobPhone") diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index bab9eed2..ee390613 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -25,6 +25,7 @@ private Q_SLOTS: void uploadOneTimeKeys(); void uploadSignedOneTimeKeys(); void uploadKeys(); + void queryTest(); void claimKeys(); void claimMultipleKeys(); }; -- cgit v1.2.3 From fc531b625efa3f0c0ceebed3c23a3d185d398a4d Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 10 Jun 2021 18:41:39 +0200 Subject: Fix tests --- autotests/testolmaccount.cpp | 6 +++--- lib/crypto/qolmaccount.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 9195bb62..192c97ac 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -300,13 +300,14 @@ void TestOlmAccount::queryTest() QCOMPARE(aliceRes->oneTimeKeyCounts()["signed_curve25519"], 1); }); QSignalSpy spy(aliceRes, &BaseJob::result); - bob->run(aliceRes); + alice->run(aliceRes); QVERIFY(spy.wait(10000)); auto bobOlm = bob->olmAccount(); bobOlm->generateOneTimeKeys(1); - auto bobRes = aliceOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys()); + auto bobRes = bobOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys()); connect(bobRes, &BaseJob::result, this, [bobRes] { + QCOMPARE(bobRes->oneTimeKeyCounts().size(), 1); QCOMPARE(bobRes->oneTimeKeyCounts()["signed_curve25519"], 1); }); @@ -347,7 +348,6 @@ void TestOlmAccount::queryTest() QVERIFY(bobDevices.size() > 0); auto devKeys = bobDevices[alice->deviceId()]; - qDebug() << bobDevices.keys(); QCOMPARE(devKeys.userId, alice->userId()); QCOMPARE(devKeys.deviceId, alice->deviceId()); QCOMPARE(devKeys.keys, aliceOlm->deviceKeys().keys); diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 8cf21045..6b7bc9a9 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -253,7 +253,7 @@ UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKey auto temp = signOneTimeKeys(oneTimeKeys); QHash oneTimeKeysSigned; for (const auto &[keyId, key] : asKeyValueRange(temp)) { - oneTimeKeysSigned[keyId] = QVariant::fromValue(key); + oneTimeKeysSigned[keyId] = QVariant::fromValue(toJson(key)); } return new UploadKeysJob(keys, oneTimeKeysSigned); -- cgit v1.2.3 From 8e0e3849da4f024d94c1cbfb053dfb47706e2d16 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 10 Jun 2021 19:14:54 +0200 Subject: Text: Add KeyChange test --- autotests/testolmaccount.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ autotests/testolmaccount.h | 1 + 2 files changed, 44 insertions(+) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 192c97ac..a31f0b98 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -494,4 +494,47 @@ void TestOlmAccount::claimMultipleKeys() QCOMPARE(job->oneTimeKeys()[userId].size(), 3); }); } + +void TestOlmAccount::keyChange() +{ + CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") + + auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), QStringList()); + connect(job, &BaseJob::result, this, [alice, job, this] () { + // Alice syncs to get the first next_batch token. + alice->sync(); + connect(alice.get(), &Connection::syncDone, this, [alice, this] { + const auto nextBatchToken = alice->nextBatchToken(); + + // generate keys and change existing one + auto aliceOlm = alice->olmAccount(); + aliceOlm->generateOneTimeKeys(1); + auto aliceRes = aliceOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys()); + connect(aliceRes, &BaseJob::result, this, [aliceRes] { + QCOMPARE(aliceRes->oneTimeKeyCounts().size(), 1); + QCOMPARE(aliceRes->oneTimeKeyCounts()["signed_curve25519"], 1); + }); + QSignalSpy spy(aliceRes, &BaseJob::result); + + alice->run(aliceRes); + QVERIFY(spy.wait(10000)); + + // The key changes should contain her username + // because of the key uploading. + + auto changeJob = alice->callApi(nextBatchToken, ""); + connect(changeJob, &BaseJob::result, this, [&changeJob, &alice] { + QCOMPARE(changeJob->changed().size(), 1); + QCOMPARE(changeJob->left().size(), 0); + QCOMPARE(changeJob->changed()[0], alice->userId()); + }); + QSignalSpy spy2(changeJob, &BaseJob::result); + QVERIFY(spy2.wait(10000)); + }); + }); + QSignalSpy spy(job, &BaseJob::result); + QVERIFY(spy.wait(10000)); +} + + QTEST_MAIN(TestOlmAccount) diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index ee390613..f6ad119b 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -28,4 +28,5 @@ private Q_SLOTS: void queryTest(); void claimKeys(); void claimMultipleKeys(); + void keyChange(); }; -- cgit v1.2.3 From 429dbc5670b3f9eba44221395a75221b8306c068 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 10 Jun 2021 21:47:46 +0200 Subject: Add a test (now failing) --- autotests/testolmaccount.cpp | 55 ++++++++++++++++++++++++++++++++++++++++---- autotests/testolmaccount.h | 1 + 2 files changed, 51 insertions(+), 5 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index a31f0b98..4ab21a8a 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -4,11 +4,20 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testolmaccount.h" -#include "crypto/qolmaccount.h" -#include "crypto/qolmutility.h" -#include "connection.h" -#include "events/encryptedfile.h" -#include "networkaccessmanager.h" +#include +#include +#include +#include +#include +#include +#include + +// for sleep +#ifdef _WIN32 +#include +#else +#include +#endif using namespace Quotient; @@ -531,10 +540,46 @@ void TestOlmAccount::keyChange() QSignalSpy spy2(changeJob, &BaseJob::result); QVERIFY(spy2.wait(10000)); }); + QSignalSpy spy2(alice.get(), &Connection::syncDone); + QVERIFY(spy2.wait(10000)); }); QSignalSpy spy(job, &BaseJob::result); QVERIFY(spy.wait(10000)); } +void TestOlmAccount::enableEncryption() +{ + CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") + CREATE_CONNECTION(bob, "bob", "secret", "BobPhone") + + QString joinedRoom; + + auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), {"@bob:localhost"}); + connect(alice.get(), &Connection::newRoom, this, [alice, bob, &joinedRoom, this] (Quotient::Room *room) { + room->activateEncryption(); // TODO we should also wait for it + joinedRoom = room->id(); + sleep(1); + auto job = bob->joinRoom(room->id()); + QSignalSpy spy(job, &BaseJob::result); + QVERIFY(spy.wait(10000)); + }); + QSignalSpy spy(job, &BaseJob::result); + QVERIFY(spy.wait(10000)); + + bob->sync(); + connect(bob.get(), &Connection::syncDone, this, [bob, &joinedRoom, this] { + auto &events = bob->room(joinedRoom)->messageEvents(); + bool hasEncryption = false; + for (auto it = events.rbegin(); it != events.rend(); ++it) { + auto event = it->event(); + if (eventCast(event)) { + hasEncryption = true; + } + } + QVERIFY(hasEncryption); + }); + QSignalSpy spy2(bob.get(), &Connection::syncDone); + QVERIFY(spy2.wait(10000)); +} QTEST_MAIN(TestOlmAccount) diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index f6ad119b..f1f80454 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -29,4 +29,5 @@ private Q_SLOTS: void claimKeys(); void claimMultipleKeys(); void keyChange(); + void enableEncryption(); }; -- cgit v1.2.3 From 0ec4df82265c2f796035c0c103b9f6693f62e24a Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 10 Jun 2021 23:57:15 +0200 Subject: Fix setting encrypted flag in rooms --- autotests/testolmaccount.cpp | 17 +++++++++++------ lib/room.cpp | 1 - 2 files changed, 11 insertions(+), 7 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 4ab21a8a..c7edd7ad 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -519,10 +519,6 @@ void TestOlmAccount::keyChange() auto aliceOlm = alice->olmAccount(); aliceOlm->generateOneTimeKeys(1); auto aliceRes = aliceOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys()); - connect(aliceRes, &BaseJob::result, this, [aliceRes] { - QCOMPARE(aliceRes->oneTimeKeyCounts().size(), 1); - QCOMPARE(aliceRes->oneTimeKeyCounts()["signed_curve25519"], 1); - }); QSignalSpy spy(aliceRes, &BaseJob::result); alice->run(aliceRes); @@ -557,12 +553,15 @@ void TestOlmAccount::enableEncryption() auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), {"@bob:localhost"}); connect(alice.get(), &Connection::newRoom, this, [alice, bob, &joinedRoom, this] (Quotient::Room *room) { room->activateEncryption(); // TODO we should also wait for it + QSignalSpy spy(room, &Room::encryption); + joinedRoom = room->id(); - sleep(1); auto job = bob->joinRoom(room->id()); - QSignalSpy spy(job, &BaseJob::result); + QSignalSpy spy1(job, &BaseJob::result); QVERIFY(spy.wait(10000)); + QVERIFY(spy1.wait(10000)); }); + QSignalSpy spy(job, &BaseJob::result); QVERIFY(spy.wait(10000)); @@ -574,8 +573,14 @@ void TestOlmAccount::enableEncryption() auto event = it->event(); if (eventCast(event)) { hasEncryption = true; + } else { + qDebug() << event->matrixType() << typeId() << event->type(); + if ( event->matrixType() == "m.room.encryption") { + qDebug() << event->contentJson(); + } } } + QVERIFY(bob->room(joinedRoom)->usesEncryption()); QVERIFY(hasEncryption); }); QSignalSpy spy2(bob.get(), &Connection::syncDone); diff --git a/lib/room.cpp b/lib/room.cpp index 2707842c..3a894b9b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1913,7 +1913,6 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) return; } it->setDeparted(); - qCDebug(EVENTS) << "Event txn" << txnId << "has departed"; emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); }); Room::connect(call, &BaseJob::failure, q, -- cgit v1.2.3 From 1bc8c3c2a177668af00889ae18f26d81dfef4af1 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 24 Oct 2021 16:43:59 +0200 Subject: Remove outdated comment --- autotests/testolmaccount.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index c7edd7ad..4fd129b5 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -552,7 +552,7 @@ void TestOlmAccount::enableEncryption() auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), {"@bob:localhost"}); connect(alice.get(), &Connection::newRoom, this, [alice, bob, &joinedRoom, this] (Quotient::Room *room) { - room->activateEncryption(); // TODO we should also wait for it + room->activateEncryption(); QSignalSpy spy(room, &Room::encryption); joinedRoom = room->id(); -- cgit v1.2.3 From dcc4556a761f96ae6c71115bf6297feca32581bf Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 27 Nov 2021 01:58:02 +0100 Subject: More improvements --- autotests/testolmsession.cpp | 16 ++++++++-------- lib/connection.cpp | 10 +++++----- lib/crypto/qolmaccount.cpp | 14 +++++++------- lib/crypto/qolmaccount.h | 10 ++++++---- lib/crypto/qolmsession.cpp | 10 +++++----- lib/crypto/qolmsession.h | 3 +-- lib/encryptionmanager.cpp | 12 +++++++----- lib/room.cpp | 4 ++-- 8 files changed, 41 insertions(+), 38 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index dba78277..750b804e 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -7,7 +7,7 @@ using namespace Quotient; -std::pair, std::unique_ptr> createSessionPair() +std::pair createSessionPair() { QByteArray pickledAccountA("eOBXIKivUT6YYowRH031BNv7zNmzqM5B7CpXdyeaPvala5mt7/OeqrG1qVA7vA1SYloFyvJPIy0QNkD3j1HiPl5vtZHN53rtfZ9exXDok03zjmssqn4IJsqcA7Fbo1FZeKafG0NFcWwCPTdmcV7REqxjqGm3I4K8MQFa45AdTGSUu2C12cWeOcbSMlcINiMral+Uyah1sgPmLJ18h1qcnskXUXQvpffZ5DiUw1Iz5zxnwOQF1GVyowPJD7Zdugvj75RQnDxAn6CzyvrY2k2CuedwqDC3fIXM2xdUNWttW4nC2g4InpBhCVvNwhZYxlUb5BUEjmPI2AB3dAL5ry6o9MFncmbN6x5x"); QByteArray pickledAccountB("eModTvoFi9oOIkax4j4nuxw9Tcl/J8mOmUctUWI68Q89HSaaPTqR+tdlKQ85v2GOs5NlZCp7EuycypN9GQ4fFbHUCrS7nspa3GFBWsR8PnM8+wez5PWmfFZLg3drOvT0jbMjpDx0MjGYClHBqcrEpKx9oFaIRGBaX6HXzT4lRaWSJkXxuX92q8iGNrLn96PuAWFNcD+2JXpPcNFntslwLUNgqzpZ04aIFYwL80GmzyOgq3Bz1GO6u3TgCQEAmTIYN2QkO0MQeuSfe7UoMumhlAJ6R8GPcdSSPtmXNk4tdyzzlgpVq1hm7ZLKto+g8/5Aq3PvnvA8wCqno2+Pi1duK1pZFTIlActr"); @@ -20,7 +20,7 @@ std::pair, std::unique_ptr> createSess const QByteArray oneTimeKeyA("WzsbsjD85iB1R32iWxfJdwkgmdz29ClMbJSJziECYwk"); const QByteArray identityKeyB("q/YhJtog/5VHCAS9rM9uUf6AaFk1yPe4GYuyUOXyQCg"); const QByteArray oneTimeKeyB("oWvzryma+B2onYjo3hM6A3Mgo/Yepm8HvgSvwZMTnjQ"); - auto outbound = std::get>(accountA + auto outbound = std::get(accountA .createOutboundSession(identityKeyB, oneTimeKeyB)); const auto preKey = outbound->encrypt(""); // Payload does not matter for PreKey @@ -29,8 +29,8 @@ std::pair, std::unique_ptr> createSess // We can't call QFail here because it's an helper function returning a value throw "Wrong first message type received, can't create session"; } - auto inbound = std::get>(accountB.createInboundSession(preKey)); - return std::make_pair, std::unique_ptr>(std::move(inbound), std::move(outbound)); + auto inbound = std::get(accountB.createInboundSession(preKey)); + return std::make_pair(std::move(inbound), std::move(outbound)); } void TestOlmSession::olmOutboundSessionCreation() @@ -56,17 +56,17 @@ void TestOlmSession::olmEncryptDecrypt() void TestOlmSession::correctSessionOrdering() { // n0W5IJ2ZmaI9FxKRj/wohUQ6WEU0SfoKsgKKHsr4VbM - auto session1 = std::get>(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGvlaV6t/0ihD2/0QGckDIvbmE1aV+PxB0zUtHXh99bI/60N+PWkCLA84jEY4sz3d45ui/TVoFGLDHlymKxvlj7XngXrbtlxSkVntsPzDiNpKEXCa26N2ubKpQ0fbjrV5gbBTYWfU04DXHPXFDTksxpNALYt/h0eVMVhf6hB0ZzpLBsOG0mpwkLufwub0CuDEDGGmRddz3TcNCLq5NnI8R9udDWvHAkTS1UTbHuIf/y6cZg875nJyXpAvd8/XhL8TOo8ot2sE1fElBa4vrH/m9rBQMC1GPkhLBIizmY44C+Sq9PQRnF+uCZ", Unencrypted{})); + auto session1 = std::get(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGvlaV6t/0ihD2/0QGckDIvbmE1aV+PxB0zUtHXh99bI/60N+PWkCLA84jEY4sz3d45ui/TVoFGLDHlymKxvlj7XngXrbtlxSkVntsPzDiNpKEXCa26N2ubKpQ0fbjrV5gbBTYWfU04DXHPXFDTksxpNALYt/h0eVMVhf6hB0ZzpLBsOG0mpwkLufwub0CuDEDGGmRddz3TcNCLq5NnI8R9udDWvHAkTS1UTbHuIf/y6cZg875nJyXpAvd8/XhL8TOo8ot2sE1fElBa4vrH/m9rBQMC1GPkhLBIizmY44C+Sq9PQRnF+uCZ", Unencrypted{})); // +9pHJhP3K4E5/2m8PYBPLh8pS9CJodwUOh8yz3mnmw0 - auto session2 = std::get>(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UFD+q37/WlfTAzQsSjCdD07FcErZ4siEy5vpiB+pyO8i53ptZvb2qRvqNKFzPaXuu33PS2PBTmmnR+kJt+DgDNqWadyaj/WqEAejc7ALqSs5GuhbZtpoLe+lRSRK0rwVX3gzz4qrl8pm0pD5pSZAUWRXDRlieGWMclz68VUvnSaQH7ElTo4S634CJk+xQfFFCD26v0yONPSN6rwouS1cWPuG5jTlnV8vCFVTU2+lduKh54Ko6FUJ/ei4xR8Nk2duBGSc/TdllX9e2lDYHSUkWoD4ti5xsFioB8Blus7JK9BZfcmRmdlxIOD", Unencrypted {})); + auto session2 = std::get(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UFD+q37/WlfTAzQsSjCdD07FcErZ4siEy5vpiB+pyO8i53ptZvb2qRvqNKFzPaXuu33PS2PBTmmnR+kJt+DgDNqWadyaj/WqEAejc7ALqSs5GuhbZtpoLe+lRSRK0rwVX3gzz4qrl8pm0pD5pSZAUWRXDRlieGWMclz68VUvnSaQH7ElTo4S634CJk+xQfFFCD26v0yONPSN6rwouS1cWPuG5jTlnV8vCFVTU2+lduKh54Ko6FUJ/ei4xR8Nk2duBGSc/TdllX9e2lDYHSUkWoD4ti5xsFioB8Blus7JK9BZfcmRmdlxIOD", Unencrypted {})); // MC7n8hX1l7WlC2/WJGHZinMocgiBZa4vwGAOredb/ME - auto session3 = std::get>(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGNk2TmVDJ95K0Nywf24FNklNVtXtFDiFPHFwNSmCbHNCp3hsGtZlt0AHUkMmL48XklLqzwtVk5/v2RRmSKR5LqYdIakrtuK/fY0ENhBZIbI1sRetaJ2KMbY9l6rCJNfFg8VhpZ4KTVvEZVuP9g/eZkCnP5NxzXiBRF6nfY3O/zhcKxa3acIqs6BMhyLsfuJ80t+hQ1HvVyuhBerGujdSDzV9tJ9SPidOwfYATk81LVF9hTmnI0KaZa7qCtFzhG0dU/Z3hIWH9HOaw1aSB/IPmughbwdJOwERyhuo3YHoznlQnJ7X252BlI", Unencrypted{})); + auto session3 = std::get(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGNk2TmVDJ95K0Nywf24FNklNVtXtFDiFPHFwNSmCbHNCp3hsGtZlt0AHUkMmL48XklLqzwtVk5/v2RRmSKR5LqYdIakrtuK/fY0ENhBZIbI1sRetaJ2KMbY9l6rCJNfFg8VhpZ4KTVvEZVuP9g/eZkCnP5NxzXiBRF6nfY3O/zhcKxa3acIqs6BMhyLsfuJ80t+hQ1HvVyuhBerGujdSDzV9tJ9SPidOwfYATk81LVF9hTmnI0KaZa7qCtFzhG0dU/Z3hIWH9HOaw1aSB/IPmughbwdJOwERyhuo3YHoznlQnJ7X252BlI", Unencrypted{})); const auto session1Id = session1->sessionId(); const auto session2Id = session2->sessionId(); const auto session3Id = session3->sessionId(); - std::vector> sessionList; + std::vector sessionList; sessionList.push_back(std::move(session1)); sessionList.push_back(std::move(session2)); sessionList.push_back(std::move(session3)); diff --git a/lib/connection.cpp b/lib/connection.cpp index df9ff445..a7af1477 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -509,7 +509,7 @@ void Connection::Private::completeSetup(const QString& mxId) // create new account and save unpickle data olmAccount->createNewAccount(); auto job = q->callApi(olmAccount->deviceKeys()); - connect(job, &BaseJob::failure, q, [=]{ + connect(job, &BaseJob::failure, q, [job]{ qCWarning(E2EE) << "Failed to upload device keys:" << job->errorString(); }); } else { @@ -677,10 +677,10 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) auto keys = d->olmAccount->oneTimeKeys(); auto job = d->olmAccount->createUploadKeyRequest(keys); run(job, ForegroundRequest); - connect(job, &BaseJob::success, this, [=](){ + connect(job, &BaseJob::success, this, [this](){ d->olmAccount->markKeysAsPublished(); }); - connect(job, &BaseJob::result, this, [=](){ + connect(job, &BaseJob::result, this, [this](){ d->isUploadingKeys = false; }); } @@ -1903,7 +1903,7 @@ void Connection::Private::loadOutdatedUserDevices() } auto queryKeysJob = q->callApi(users); currentQueryKeysJob = queryKeysJob; - connect(queryKeysJob, &BaseJob::success, q, [=](){ + connect(queryKeysJob, &BaseJob::success, q, [this, queryKeysJob](){ currentQueryKeysJob = nullptr; const auto data = queryKeysJob->deviceKeys(); for(const auto &[user, keys] : asKeyValueRange(data)) { @@ -2024,7 +2024,7 @@ void Connection::Private::loadDevicesList() deviceKeys = fromJson>>(json["devices_list"].toObject()); auto oldToken = json["sync_token"].toString(); auto changesJob = q->callApi(oldToken, q->nextBatchToken()); - connect(changesJob, &BaseJob::success, q, [=](){ + connect(changesJob, &BaseJob::success, q, [this, changesJob](){ bool hasNewOutdatedUser = false; for(const auto &user : changesJob->changed()) { outdatedUsers += user; diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 1de8a0dc..5c9f5db4 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -179,13 +179,13 @@ OneTimeKeys QOlmAccount::oneTimeKeys() const const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); OneTimeKeys oneTimeKeys; - for (const QJsonValue &key1 : json.keys()) { - auto oneTimeKeyObject = json[key1.toString()].toObject(); + for (const QString& key1 : json.keys()) { + auto oneTimeKeyObject = json[key1].toObject(); auto keyMap = QMap(); for (const QString &key2 : oneTimeKeyObject.keys()) { keyMap[key2] = oneTimeKeyObject[key2].toString(); } - oneTimeKeys.keys[key1.toString()] = keyMap; + oneTimeKeys.keys[key1] = keyMap; } return oneTimeKeys; } @@ -215,7 +215,7 @@ QByteArray QOlmAccount::signOneTimeKey(const QString &key) const return sign(j.toJson(QJsonDocument::Compact)); } -std::optional QOlmAccount::removeOneTimeKeys(const std::unique_ptr &session) const +std::optional QOlmAccount::removeOneTimeKeys(const QOlmSessionPtr &session) const { const auto error = olm_remove_one_time_keys(m_account, session->raw()); @@ -266,19 +266,19 @@ UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKey return new UploadKeysJob(keys, oneTimeKeysSigned); } -std::variant, QOlmError> QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) +std::variant QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSession(this, preKeyMessage); } -std::variant, QOlmError> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) +std::variant QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); } -std::variant, QOlmError> QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) +std::variant QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) { return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); } diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index 1f94ab2b..dd461e8b 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -19,6 +19,8 @@ namespace Quotient { class QOlmSession; class Connection; +using QOlmSessionPtr = std::unique_ptr; + //! An olm account manages all cryptographic keys used on a device. //! \code{.cpp} //! const auto olmAccount = new QOlmAccount(this); @@ -77,22 +79,22 @@ public: DeviceKeys deviceKeys() const; //! Remove the one time key used to create the supplied session. - [[nodiscard]] std::optional removeOneTimeKeys(const std::unique_ptr &session) const; + [[nodiscard]] std::optional 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, QOlmError> createInboundSession(const QOlmMessage &preKeyMessage); + std::variant 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, QOlmError> createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage); + std::variant createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage); //! Creates an outbound session for sending messages to a specific /// identity and one time key. - std::variant, QOlmError> createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); + std::variant createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); void markKeysAsPublished(); diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp index a327a643..a0386613 100644 --- a/lib/crypto/qolmsession.cpp +++ b/lib/crypto/qolmsession.cpp @@ -27,7 +27,7 @@ OlmSession* QOlmSession::create() return olm_session(new uint8_t[olm_session_size()]); } -std::variant, QOlmError> QOlmSession::createInbound(QOlmAccount *account, const QOlmMessage &preKeyMessage, bool from, const QString &theirIdentityKey) +std::variant 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; @@ -53,17 +53,17 @@ std::variant, QOlmError> QOlmSession::createInbound return std::make_unique(olmSession); } -std::variant, QOlmError> QOlmSession::createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage) +std::variant QOlmSession::createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage) { return createInbound(account, preKeyMessage); } -std::variant, QOlmError> QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) +std::variant QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) { return createInbound(account, preKeyMessage, true, theirIdentityKey); } -std::variant, QOlmError> QOlmSession::createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey) +std::variant QOlmSession::createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey) { auto *olmOutboundSession = create(); const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); @@ -105,7 +105,7 @@ std::variant QOlmSession::pickle(const PicklingMode &mode return pickledBuf; } -std::variant, QOlmError> QOlmSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +std::variant QOlmSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) { QByteArray pickledBuf = pickled; auto *olmSession = create(); diff --git a/lib/crypto/qolmsession.h b/lib/crypto/qolmsession.h index 959c77d0..7a040b3d 100644 --- a/lib/crypto/qolmsession.h +++ b/lib/crypto/qolmsession.h @@ -74,7 +74,6 @@ private: OlmSession* m_session; }; - -//using QOlmSessionPtr = std::unique_ptr; +using QOlmSessionPtr = std::unique_ptr; } //namespace Quotient diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 84282dbf..ed6ad20b 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -38,7 +38,7 @@ public: EncryptionManager* q; // A map from senderKey to InboundSession - UnorderedMap> sessions; // TODO: cache + UnorderedMap sessions; void updateDeviceKeys( const QHash>& deviceKeys) @@ -76,7 +76,7 @@ public: qCWarning(E2EE) << "Failed to unpickle olm session"; continue; } - sessions[senderKey] = std::move(std::get>(sessionResult)); + sessions[senderKey] = std::move(std::get(sessionResult)); } } void saveSessions() { @@ -135,9 +135,11 @@ public: qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); return {}; } - std::unique_ptr newSession = std::move(std::get>(newSessionResult)); - // TODO Error handling? - olmAccount->removeOneTimeKeys(newSession); + auto newSession = std::move(std::get(newSessionResult)); + auto error = olmAccount->removeOneTimeKeys(newSession); + if (error) { + qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); + } const auto result = newSession->decrypt(message); sessions[senderKey] = std::move(newSession); saveSessions(); diff --git a/lib/room.cpp b/lib/room.cpp index 07ffd0cd..e4fe2fb8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -514,10 +514,10 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) return this == r; // loadedRoomState fires only once per room }); #ifdef Quotient_E2EE_ENABLED - connectSingleShot(this, &Room::encryption, this, [=](){ + connectSingleShot(this, &Room::encryption, this, [this, connection](){ connection->encryptionUpdate(this); }); - connect(this, &Room::userAdded, this, [=](){ + connect(this, &Room::userAdded, this, [this, connection](){ if(usesEncryption()) { connection->encryptionUpdate(this); } -- cgit v1.2.3 From 9217026e46d7ac0d761cc5206d7ef00978558c47 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:58:38 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- autotests/testolmsession.cpp | 2 +- lib/connection.cpp | 3 +-- lib/encryptionmanager.cpp | 2 +- lib/events/encryptedevent.cpp | 12 +++++------- lib/jobs/downloadfilejob.cpp | 2 +- 5 files changed, 9 insertions(+), 12 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 750b804e..00d76d4e 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -30,7 +30,7 @@ std::pair createSessionPair() throw "Wrong first message type received, can't create session"; } auto inbound = std::get(accountB.createInboundSession(preKey)); - return std::make_pair(std::move(inbound), std::move(outbound)); + return { std::move(inbound), std::move(outbound) }; } void TestOlmSession::olmOutboundSessionCreation() diff --git a/lib/connection.cpp b/lib/connection.cpp index a7af1477..ac428a62 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -56,7 +56,6 @@ #include #include - #if QT_VERSION_MAJOR >= 6 # include #else @@ -1948,7 +1947,7 @@ void Connection::Private::saveDevicesList() QElapsedTimer et; et.start(); - QFile outFile { q->e2eeDataDir() + QStringLiteral("/deviceslist.json") }; + QFile outFile { q->e2eeDataDir() % "/deviceslist.json" }; if (!outFile.open(QFile::WriteOnly)) { qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index ed6ad20b..5c106e12 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -80,7 +80,7 @@ public: } } void saveSessions() { - QFile outFile { static_cast(q->parent())->e2eeDataDir() + QStringLiteral("/olmsessions.json") }; + QFile outFile { static_cast(q->parent())->e2eeDataDir() % "/olmsessions.json" }; if (!outFile.open(QFile::WriteOnly)) { qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index c9257584..2e0d7387 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -38,17 +38,15 @@ RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const eventObject["event_id"] = id(); eventObject["sender"] = senderId(); eventObject["origin_server_ts"] = originTimestamp().toMSecsSinceEpoch(); - if(contentJson().contains("m.relates_to")) { - auto relates = contentJson()["m.relates_to"].toObject(); + if (const auto relatesToJson = contentPart("m.relates_to"_ls); !relatesToJson.isUndefined()) { auto content = eventObject["content"].toObject(); - content["m.relates_to"] = relates; + content["m.relates_to"] = relatesToJson.toObject(); eventObject["content"] = content; } - if(unsignedJson().contains("redacts")) { - auto redacts = unsignedJson()["redacts"].toString(); + if (const auto redactsJson = unsignedPart("redacts"_ls); !redactsJson.isUndefined()) { auto unsign = eventObject["unsigned"].toObject(); - unsign["redacts"] = redacts; + unsign["redacts"] = redactsJson.toString(); eventObject["unsigned"] = unsign; } - return makeEvent(eventObject); + return loadEvent(eventObject); } diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 0b4cf6d2..2eea9d59 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -8,7 +8,7 @@ #include #ifdef Quotient_E2EE_ENABLED -# include +# include # include "encryptionmanager.h" # include "events/encryptedfile.h" #endif -- cgit v1.2.3 From 2c6fa33ca52842e9dfba0dd3893a9d5526e10e60 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 7 Dec 2021 19:08:29 +0100 Subject: Rename "crypto" -> "e2ee" --- CMakeLists.txt | 18 +- autotests/testgroupsession.cpp | 6 +- autotests/testolmaccount.cpp | 4 +- autotests/testolmsession.cpp | 2 +- autotests/testolmutility.cpp | 4 +- lib/connection.cpp | 4 +- lib/connection.h | 2 +- lib/converters.cpp | 2 +- lib/crypto/e2ee.h | 132 --------------- lib/crypto/qolmaccount.cpp | 330 ------------------------------------- lib/crypto/qolmaccount.h | 123 -------------- lib/crypto/qolmerrors.cpp | 22 --- lib/crypto/qolmerrors.h | 28 ---- lib/crypto/qolminboundsession.cpp | 153 ----------------- lib/crypto/qolminboundsession.h | 48 ------ lib/crypto/qolmmessage.cpp | 35 ---- lib/crypto/qolmmessage.h | 41 ----- lib/crypto/qolmoutboundsession.cpp | 128 -------------- lib/crypto/qolmoutboundsession.h | 54 ------ lib/crypto/qolmsession.cpp | 253 ---------------------------- lib/crypto/qolmsession.h | 76 --------- lib/crypto/qolmutility.cpp | 63 ------- lib/crypto/qolmutility.h | 45 ----- lib/crypto/qolmutils.cpp | 24 --- lib/crypto/qolmutils.h | 15 -- lib/database.cpp | 6 +- lib/database.h | 2 +- lib/e2ee/e2ee.h | 132 +++++++++++++++ lib/e2ee/qolmaccount.cpp | 330 +++++++++++++++++++++++++++++++++++++ lib/e2ee/qolmaccount.h | 123 ++++++++++++++ lib/e2ee/qolmerrors.cpp | 22 +++ lib/e2ee/qolmerrors.h | 28 ++++ lib/e2ee/qolminboundsession.cpp | 153 +++++++++++++++++ lib/e2ee/qolminboundsession.h | 48 ++++++ lib/e2ee/qolmmessage.cpp | 35 ++++ lib/e2ee/qolmmessage.h | 41 +++++ lib/e2ee/qolmoutboundsession.cpp | 128 ++++++++++++++ lib/e2ee/qolmoutboundsession.h | 54 ++++++ lib/e2ee/qolmsession.cpp | 253 ++++++++++++++++++++++++++++ lib/e2ee/qolmsession.h | 76 +++++++++ lib/e2ee/qolmutility.cpp | 63 +++++++ lib/e2ee/qolmutility.h | 45 +++++ lib/e2ee/qolmutils.cpp | 24 +++ lib/e2ee/qolmutils.h | 15 ++ lib/encryptionmanager.cpp | 12 +- lib/events/encryptedevent.h | 2 +- lib/events/encryptionevent.cpp | 2 +- lib/room.cpp | 8 +- 48 files changed, 1607 insertions(+), 1607 deletions(-) delete mode 100644 lib/crypto/e2ee.h delete mode 100644 lib/crypto/qolmaccount.cpp delete mode 100644 lib/crypto/qolmaccount.h delete mode 100644 lib/crypto/qolmerrors.cpp delete mode 100644 lib/crypto/qolmerrors.h delete mode 100644 lib/crypto/qolminboundsession.cpp delete mode 100644 lib/crypto/qolminboundsession.h delete mode 100644 lib/crypto/qolmmessage.cpp delete mode 100644 lib/crypto/qolmmessage.h delete mode 100644 lib/crypto/qolmoutboundsession.cpp delete mode 100644 lib/crypto/qolmoutboundsession.h delete mode 100644 lib/crypto/qolmsession.cpp delete mode 100644 lib/crypto/qolmsession.h delete mode 100644 lib/crypto/qolmutility.cpp delete mode 100644 lib/crypto/qolmutility.h delete mode 100644 lib/crypto/qolmutils.cpp delete mode 100644 lib/crypto/qolmutils.h create mode 100644 lib/e2ee/e2ee.h create mode 100644 lib/e2ee/qolmaccount.cpp create mode 100644 lib/e2ee/qolmaccount.h create mode 100644 lib/e2ee/qolmerrors.cpp create mode 100644 lib/e2ee/qolmerrors.h create mode 100644 lib/e2ee/qolminboundsession.cpp create mode 100644 lib/e2ee/qolminboundsession.h create mode 100644 lib/e2ee/qolmmessage.cpp create mode 100644 lib/e2ee/qolmmessage.h create mode 100644 lib/e2ee/qolmoutboundsession.cpp create mode 100644 lib/e2ee/qolmoutboundsession.h create mode 100644 lib/e2ee/qolmsession.cpp create mode 100644 lib/e2ee/qolmsession.h create mode 100644 lib/e2ee/qolmutility.cpp create mode 100644 lib/e2ee/qolmutility.h create mode 100644 lib/e2ee/qolmutils.cpp create mode 100644 lib/e2ee/qolmutils.h (limited to 'autotests') diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f886094..a84a70fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,15 +164,15 @@ list(APPEND lib_SRCS ) if (${PROJECT_NAME}_ENABLE_E2EE) list(APPEND lib_SRCS - lib/crypto/qolmaccount.cpp - lib/crypto/qolmsession.cpp - lib/crypto/qolminboundsession.cpp - lib/crypto/qolmoutboundsession.cpp - lib/crypto/qolmutils.cpp - lib/crypto/qolmutility.cpp - lib/crypto/qolmerrors.cpp - lib/crypto/qolmsession.cpp - lib/crypto/qolmmessage.cpp + lib/e2ee/qolmaccount.cpp + lib/e2ee/qolmsession.cpp + lib/e2ee/qolminboundsession.cpp + lib/e2ee/qolmoutboundsession.cpp + lib/e2ee/qolmutils.cpp + lib/e2ee/qolmutility.cpp + lib/e2ee/qolmerrors.cpp + lib/e2ee/qolmsession.cpp + lib/e2ee/qolmmessage.cpp lib/encryptionmanager.cpp ) endif() diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index ea1bb4a9..afd5ef81 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -3,9 +3,9 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testgroupsession.h" -#include "crypto/qolminboundsession.h" -#include "crypto/qolmoutboundsession.h" -#include "crypto/qolmutils.h" +#include "e2ee/qolminboundsession.h" +#include "e2ee/qolmoutboundsession.h" +#include "e2ee/qolmutils.h" using namespace Quotient; diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 4fd129b5..22c457aa 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -4,8 +4,8 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testolmaccount.h" -#include -#include +#include +#include #include #include #include diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 00d76d4e..41baf8e3 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include "crypto/qolmsession.h" +#include "e2ee/qolmsession.h" #include "testolmsession.h" using namespace Quotient; diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index 2eec7e00..bbf3a055 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -3,8 +3,8 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testolmutility.h" -#include "crypto/qolmaccount.h" -#include "crypto/qolmutility.h" +#include "e2ee/qolmaccount.h" +#include "e2ee/qolmutility.h" using namespace Quotient; diff --git a/lib/connection.cpp b/lib/connection.cpp index f344807e..c7591e43 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -38,8 +38,8 @@ #include "jobs/syncjob.h" #ifdef Quotient_E2EE_ENABLED -# include "crypto/qolmaccount.h" -# include "crypto/qolmutils.h" +# include "e2ee/qolmaccount.h" +# include "e2ee/qolmutils.h" #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) diff --git a/lib/connection.h b/lib/connection.h index d2347d1d..3a12ec39 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -22,7 +22,7 @@ #include #ifdef Quotient_E2EE_ENABLED -#include "crypto/e2ee.h" +#include "e2ee/e2ee.h" #endif Q_DECLARE_METATYPE(Quotient::GetLoginFlowsJob::LoginFlow) diff --git a/lib/converters.cpp b/lib/converters.cpp index a3ac44c5..4136940f 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -4,7 +4,7 @@ #include "converters.h" #include -#include "crypto/e2ee.h" +#include "e2ee/e2ee.h" QJsonValue Quotient::JsonConverter::dump(const QVariant& v) { diff --git a/lib/crypto/e2ee.h b/lib/crypto/e2ee.h deleted file mode 100644 index 41cd2878..00000000 --- a/lib/crypto/e2ee.h +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-FileCopyrightText: 2021 Carl Schwan -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include -#include -#include "converters.h" -#include - -#include -#include -#include -#include - -#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; - -class QOlmSession; -using QOlmSessionPtr = std::unique_ptr; - -class QOlmInboundGroupSession; -using QOlmInboundGroupSessionPtr = std::unique_ptr; - -template struct overloaded : Ts... { using Ts::operator()...; }; -template overloaded(Ts...) -> overloaded; - -struct IdentityKeys -{ - QByteArray curve25519; - QByteArray ed25519; -}; - -//! Struct representing the one-time keys. -struct OneTimeKeys -{ - QMap> keys; - - //! Get the HashMap containing the curve25519 one-time keys. - QMap curve25519() const; - - //! Get a reference to the hashmap corresponding to given key type. - std::optional> 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> signatures; -}; - - -template <> -struct JsonObjectConverter { - 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 -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/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp deleted file mode 100644 index 5c9f5db4..00000000 --- a/lib/crypto/qolmaccount.cpp +++ /dev/null @@ -1,330 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "qolmaccount.h" -#include "connection.h" -#include "csapi/keys.h" -#include "crypto/qolmutils.h" -#include "crypto/qolmutility.h" -#include -#include -#include -#include - -using namespace Quotient; - -QMap OneTimeKeys::curve25519() const -{ - return keys[QStringLiteral("curve25519")]; -} - -std::optional> 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(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 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(); - for (const QString &key2 : oneTimeKeyObject.keys()) { - keyMap[key2] = oneTimeKeyObject[key2].toString(); - } - oneTimeKeys.keys[key1] = keyMap; - } - return oneTimeKeys; -} - -QMap QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const -{ - QMap 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 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 oneTimeKeysSigned; - for (const auto &[keyId, key] : asKeyValueRange(temp)) { - oneTimeKeysSigned[keyId] = QVariant::fromValue(toJson(key)); - } - - return new UploadKeysJob(keys, oneTimeKeysSigned); -} - -std::variant QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) -{ - Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); - return QOlmSession::createInboundSession(this, preKeyMessage); -} - -std::variant QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) -{ - Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); - return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); -} - -std::variant 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(result)) { - return false; - } - - return std::get(result); -} diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h deleted file mode 100644 index dd461e8b..00000000 --- a/lib/crypto/qolmaccount.h +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - - -#pragma once - -#include "csapi/keys.h" -#include "crypto/e2ee.h" -#include "crypto/qolmerrors.h" -#include "crypto/qolmmessage.h" -#include "crypto/qolmsession.h" -#include - -struct OlmAccount; - -namespace Quotient { - -class QOlmSession; -class Connection; - -using QOlmSessionPtr = std::unique_ptr; - -//! 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 here. - //! 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 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 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 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 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 createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage); - - //! Creates an outbound session for sending messages to a specific - /// identity and one time key. - std::variant 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/crypto/qolmerrors.cpp b/lib/crypto/qolmerrors.cpp deleted file mode 100644 index 6db1803c..00000000 --- a/lib/crypto/qolmerrors.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// 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/crypto/qolmerrors.h b/lib/crypto/qolmerrors.h deleted file mode 100644 index f8390d2a..00000000 --- a/lib/crypto/qolmerrors.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include - -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/crypto/qolminboundsession.cpp b/lib/crypto/qolminboundsession.cpp deleted file mode 100644 index 31d699f1..00000000 --- a/lib/crypto/qolminboundsession.cpp +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "crypto/qolminboundsession.h" -#include -#include -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(m_groupSession)); -} - -std::unique_ptr 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(temp.data()), temp.size()); - - if (error == olm_error()) { - throw lastError(olmInboundGroupSession); - } - - return std::make_unique(olmInboundGroupSession); -} - -std::unique_ptr 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(keyBuf.data()), keyBuf.size()); - if (error == olm_error()) { - throw lastError(olmInboundGroupSession); - } - - return std::make_unique(olmInboundGroupSession); -} - -QByteArray toKey(const PicklingMode &mode) -{ - if (std::holds_alternative(mode)) { - return ""; - } - return std::get(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, 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(groupSession); -} - -std::variant, 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(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(messageBuf.data()), - messageBuf.length(), reinterpret_cast(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(output), messageIndex); -} - -std::variant 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(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(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/crypto/qolminboundsession.h b/lib/crypto/qolminboundsession.h deleted file mode 100644 index 362e42ba..00000000 --- a/lib/crypto/qolminboundsession.h +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include -#include -#include -#include "olm/olm.h" -#include "crypto/qolmerrors.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 create(const QByteArray &key); - //! Import an inbound group session, from a previous export. - static std::unique_ptr 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, QOlmError> unpickle(const QByteArray &picked, const PicklingMode &mode); - //! Decrypts ciphertext received for this group session. - std::variant, 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 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; -using OlmInboundGroupSessionPtr = std::unique_ptr; -} // namespace Quotient diff --git a/lib/crypto/qolmmessage.cpp b/lib/crypto/qolmmessage.cpp deleted file mode 100644 index 15008b75..00000000 --- a/lib/crypto/qolmmessage.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// 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/crypto/qolmmessage.h b/lib/crypto/qolmmessage.h deleted file mode 100644 index 52aba78c..00000000 --- a/lib/crypto/qolmmessage.h +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include -#include - -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/crypto/qolmoutboundsession.cpp b/lib/crypto/qolmoutboundsession.cpp deleted file mode 100644 index bc572ba5..00000000 --- a/lib/crypto/qolmoutboundsession.cpp +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "qolmoutboundsession.h" -#include "crypto/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(m_groupSession)); -} - -std::unique_ptr 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(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(keyBuffer.data()), - keyMaxLength); - - randomBuf.clear(); - - return std::make_unique(olmOutboundGroupSession); -} - -std::variant 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, 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(idBuffer.data()), - idBuffer.length()); - - key.clear(); - return std::make_unique(olmOutboundGroupSession); -} - -std::variant 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(plaintextBuf.data()), - plaintextBuf.length(), reinterpret_cast(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(idBuffer.data()), - idBuffer.length()); - if (error == olm_error()) { - throw lastError(m_groupSession); - } - return idBuffer; -} - -std::variant 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(keyBuffer.data()), - keyMaxLength); - if (error == olm_error()) { - return lastError(m_groupSession); - } - return keyBuffer; -} diff --git a/lib/crypto/qolmoutboundsession.h b/lib/crypto/qolmoutboundsession.h deleted file mode 100644 index 4e06561e..00000000 --- a/lib/crypto/qolmoutboundsession.h +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - - -#pragma once - -#include "olm/olm.h" -#include "crypto/qolmerrors.h" -#include "crypto/e2ee.h" -#include - -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 create(); - //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); - //! Deserialises from encrypted Base64 that was previously obtained by - //! pickling a `QOlmOutboundGroupSession`. - static std::variant, QOlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); - //! Encrypts a plaintext message using the session. - std::variant 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 sessionKey() const; - QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); -private: - OlmOutboundGroupSession *m_groupSession; -}; - -using QOlmOutboundGroupSessionPtr = std::unique_ptr; -using OlmOutboundGroupSessionPtr = std::unique_ptr; -} diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp deleted file mode 100644 index a0386613..00000000 --- a/lib/crypto/qolmsession.cpp +++ /dev/null @@ -1,253 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "qolmsession.h" -#include "crypto/qolmutils.h" -#include "logging.h" -#include -#include - -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(m_session)); -} - -OlmSession* QOlmSession::create() -{ - return olm_session(new uint8_t[olm_session_size()]); -} - -std::variant 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(olmSession); -} - -std::variant QOlmSession::createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage) -{ - return createInbound(account, preKeyMessage); -} - -std::variant QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) -{ - return createInbound(account, preKeyMessage, true, theirIdentityKey); -} - -std::variant 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(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), - reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), - reinterpret_cast(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(olmOutboundSession); -} - -std::variant 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 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(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(plaintextBuf.data()), plaintextBuf.length(), - reinterpret_cast(randomBuf.data()), randomBuf.length(), - reinterpret_cast(messageBuf.data()), messageBuf.length()); - - if (error == olm_error()) { - throw lastError(m_session); - } - - return QOlmMessage(messageBuf, messageType); -} - -std::variant 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(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(messageBuf2.data()), messageBuf2.length(), - reinterpret_cast(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(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 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 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/crypto/qolmsession.h b/lib/crypto/qolmsession.h deleted file mode 100644 index 711ca66b..00000000 --- a/lib/crypto/qolmsession.h +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include -#include // FIXME: OlmSession -#include "crypto/e2ee.h" -#include "crypto/qolmmessage.h" -#include "crypto/qolmerrors.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, QOlmError> createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage); - static std::variant, QOlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage); - static std::variant, QOlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey); - //! Serialises an `QOlmSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); - //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static std::variant, 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 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 matchesInboundSession(const QOlmMessage &preKeyMessage) const; - - //! Checks if the 'prekey' message is for this in-bound session. - std::variant 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 &lhs, const std::unique_ptr &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, QOlmError> createInbound(QOlmAccount *account, const QOlmMessage& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); - OlmSession* m_session; -}; -} //namespace Quotient diff --git a/lib/crypto/qolmutility.cpp b/lib/crypto/qolmutility.cpp deleted file mode 100644 index bb50b4d0..00000000 --- a/lib/crypto/qolmutility.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "crypto/qolmutility.h" -#include "olm/olm.h" -#include - -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(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 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/crypto/qolmutility.h b/lib/crypto/qolmutility.h deleted file mode 100644 index 5fd28dcc..00000000 --- a/lib/crypto/qolmutility.h +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include -#include -#include "crypto/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 ed25519Verify(const QByteArray &key, - const QByteArray &message, const QByteArray &signature); - - -private: - OlmUtility *m_utility; - -}; -} diff --git a/lib/crypto/qolmutils.cpp b/lib/crypto/qolmutils.cpp deleted file mode 100644 index cd5ac83c..00000000 --- a/lib/crypto/qolmutils.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "crypto/qolmutils.h" -#include -#include - -using namespace Quotient; - -QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) -{ - if (std::holds_alternative(mode)) { - return {}; - } - return std::get(mode).key; -} - -QByteArray Quotient::getRandom(size_t bufferSize) -{ - QByteArray buffer(bufferSize, '0'); - RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); - return buffer; -} diff --git a/lib/crypto/qolmutils.h b/lib/crypto/qolmutils.h deleted file mode 100644 index 8b1c01ce..00000000 --- a/lib/crypto/qolmutils.h +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include - -#include "crypto/e2ee.h" - -namespace Quotient { -// Convert PicklingMode to key -QByteArray toKey(const PicklingMode &mode); -QByteArray getRandom(size_t bufferSize); -} diff --git a/lib/database.cpp b/lib/database.cpp index 153aab31..ec285d22 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -10,9 +10,9 @@ #include #include -#include "crypto/e2ee.h" -#include "crypto/qolmsession.h" -#include "crypto/qolminboundsession.h" +#include "e2ee/e2ee.h" +#include "e2ee/qolmsession.h" +#include "e2ee/qolminboundsession.h" //TODO: delete room specific data when leaving room diff --git a/lib/database.h b/lib/database.h index ed356820..8f8cd6cd 100644 --- a/lib/database.h +++ b/lib/database.h @@ -7,7 +7,7 @@ #include #include -#include "crypto/e2ee.h" +#include "e2ee/e2ee.h" namespace Quotient { class Database : public QObject 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 +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include "converters.h" +#include + +#include +#include +#include +#include + +#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; + +class QOlmSession; +using QOlmSessionPtr = std::unique_ptr; + +class QOlmInboundGroupSession; +using QOlmInboundGroupSessionPtr = std::unique_ptr; + +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...) -> overloaded; + +struct IdentityKeys +{ + QByteArray curve25519; + QByteArray ed25519; +}; + +//! Struct representing the one-time keys. +struct OneTimeKeys +{ + QMap> keys; + + //! Get the HashMap containing the curve25519 one-time keys. + QMap curve25519() const; + + //! Get a reference to the hashmap corresponding to given key type. + std::optional> 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> signatures; +}; + + +template <> +struct JsonObjectConverter { + 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 +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 +// +// 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 +#include +#include +#include + +using namespace Quotient; + +QMap OneTimeKeys::curve25519() const +{ + return keys[QStringLiteral("curve25519")]; +} + +std::optional> 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(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 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(); + for (const QString &key2 : oneTimeKeyObject.keys()) { + keyMap[key2] = oneTimeKeyObject[key2].toString(); + } + oneTimeKeys.keys[key1] = keyMap; + } + return oneTimeKeys; +} + +QMap QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const +{ + QMap 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 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 oneTimeKeysSigned; + for (const auto &[keyId, key] : asKeyValueRange(temp)) { + oneTimeKeysSigned[keyId] = QVariant::fromValue(toJson(key)); + } + + return new UploadKeysJob(keys, oneTimeKeysSigned); +} + +std::variant QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) +{ + Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); + return QOlmSession::createInboundSession(this, preKeyMessage); +} + +std::variant QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) +{ + Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); + return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); +} + +std::variant 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(result)) { + return false; + } + + return std::get(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 +// +// 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 + +struct OlmAccount; + +namespace Quotient { + +class QOlmSession; +class Connection; + +using QOlmSessionPtr = std::unique_ptr; + +//! 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 here. + //! 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 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 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 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 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 createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage); + + //! Creates an outbound session for sending messages to a specific + /// identity and one time key. + std::variant 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 +// +// 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include + +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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "e2ee/qolminboundsession.h" +#include +#include +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(m_groupSession)); +} + +std::unique_ptr 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(temp.data()), temp.size()); + + if (error == olm_error()) { + throw lastError(olmInboundGroupSession); + } + + return std::make_unique(olmInboundGroupSession); +} + +std::unique_ptr 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(keyBuf.data()), keyBuf.size()); + if (error == olm_error()) { + throw lastError(olmInboundGroupSession); + } + + return std::make_unique(olmInboundGroupSession); +} + +QByteArray toKey(const PicklingMode &mode) +{ + if (std::holds_alternative(mode)) { + return ""; + } + return std::get(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, 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(groupSession); +} + +std::variant, 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(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(messageBuf.data()), + messageBuf.length(), reinterpret_cast(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(output), messageIndex); +} + +std::variant 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(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(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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include +#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 create(const QByteArray &key); + //! Import an inbound group session, from a previous export. + static std::unique_ptr 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, QOlmError> unpickle(const QByteArray &picked, const PicklingMode &mode); + //! Decrypts ciphertext received for this group session. + std::variant, 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 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; +using OlmInboundGroupSessionPtr = std::unique_ptr; +} // 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 +// +// 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include + +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 +// +// 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(m_groupSession)); +} + +std::unique_ptr 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(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(keyBuffer.data()), + keyMaxLength); + + randomBuf.clear(); + + return std::make_unique(olmOutboundGroupSession); +} + +std::variant 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, 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(idBuffer.data()), + idBuffer.length()); + + key.clear(); + return std::make_unique(olmOutboundGroupSession); +} + +std::variant 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(plaintextBuf.data()), + plaintextBuf.length(), reinterpret_cast(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(idBuffer.data()), + idBuffer.length()); + if (error == olm_error()) { + throw lastError(m_groupSession); + } + return idBuffer; +} + +std::variant 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(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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + + +#pragma once + +#include "olm/olm.h" +#include "e2ee/qolmerrors.h" +#include "e2ee/e2ee.h" +#include + +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 create(); + //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by + //! pickling a `QOlmOutboundGroupSession`. + static std::variant, QOlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + std::variant 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 sessionKey() const; + QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); +private: + OlmOutboundGroupSession *m_groupSession; +}; + +using QOlmOutboundGroupSessionPtr = std::unique_ptr; +using OlmOutboundGroupSessionPtr = std::unique_ptr; +} 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "qolmsession.h" +#include "e2ee/qolmutils.h" +#include "logging.h" +#include +#include + +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(m_session)); +} + +OlmSession* QOlmSession::create() +{ + return olm_session(new uint8_t[olm_session_size()]); +} + +std::variant 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(olmSession); +} + +std::variant QOlmSession::createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage) +{ + return createInbound(account, preKeyMessage); +} + +std::variant QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) +{ + return createInbound(account, preKeyMessage, true, theirIdentityKey); +} + +std::variant 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(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), + reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), + reinterpret_cast(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(olmOutboundSession); +} + +std::variant 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 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(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(plaintextBuf.data()), plaintextBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length(), + reinterpret_cast(messageBuf.data()), messageBuf.length()); + + if (error == olm_error()) { + throw lastError(m_session); + } + + return QOlmMessage(messageBuf, messageType); +} + +std::variant 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(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(messageBuf2.data()), messageBuf2.length(), + reinterpret_cast(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(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 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 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include // 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, QOlmError> createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage); + static std::variant, QOlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage); + static std::variant, QOlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey); + //! Serialises an `QOlmSession` to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. + static std::variant, 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 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 matchesInboundSession(const QOlmMessage &preKeyMessage) const; + + //! Checks if the 'prekey' message is for this in-bound session. + std::variant 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 &lhs, const std::unique_ptr &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, 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "e2ee/qolmutility.h" +#include "olm/olm.h" +#include + +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(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 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#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 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "e2ee/qolmutils.h" +#include +#include + +using namespace Quotient; + +QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) +{ + if (std::holds_alternative(mode)) { + return {}; + } + return std::get(mode).key; +} + +QByteArray Quotient::getRandom(size_t bufferSize) +{ + QByteArray buffer(bufferSize, '0'); + RAND_bytes(reinterpret_cast(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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include + +#include "e2ee/e2ee.h" + +namespace Quotient { +// Convert PicklingMode to key +QByteArray toKey(const PicklingMode &mode); +QByteArray getRandom(size_t bufferSize); +} diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index e5fa978f..3d616965 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -6,7 +6,6 @@ #include "encryptionmanager.h" #include "connection.h" -#include "crypto/e2ee.h" #include "events/encryptedfile.h" #include "database.h" @@ -16,11 +15,12 @@ #include #include -#include "crypto/qolmaccount.h" -#include "crypto/qolmsession.h" -#include "crypto/qolmmessage.h" -#include "crypto/qolmerrors.h" -#include "crypto/qolmutils.h" +#include "e2ee/e2ee.h" +#include "e2ee/qolmaccount.h" +#include "e2ee/qolmsession.h" +#include "e2ee/qolmmessage.h" +#include "e2ee/qolmerrors.h" +#include "e2ee/qolmutils.h" #include #include diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 28398827..4cc3bf8e 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -3,7 +3,7 @@ #pragma once -#include "crypto/e2ee.h" +#include "e2ee/e2ee.h" #include "roomevent.h" namespace Quotient { diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index d7bb953a..6272c668 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -4,7 +4,7 @@ #include "encryptionevent.h" -#include "crypto/e2ee.h" +#include "e2ee/e2ee.h" #include diff --git a/lib/room.cpp b/lib/room.cpp index 8181f16a..755f677a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -12,7 +12,6 @@ #include "avatar.h" #include "connection.h" #include "converters.h" -#include "crypto/e2ee.h" #include "syncdata.h" #include "user.h" #include "eventstats.h" @@ -65,9 +64,10 @@ #include #ifdef Quotient_E2EE_ENABLED -#include "crypto/qolmaccount.h" -#include "crypto/qolmerrors.h" -#include "crypto/qolminboundsession.h" +#include "e2ee/e2ee.h" +#include "e2ee/qolmaccount.h" +#include "e2ee/qolmerrors.h" +#include "e2ee/qolminboundsession.h" #endif // Quotient_E2EE_ENABLED #include "database.h" -- cgit v1.2.3 From 6cec450f1d749936bd51a1471ac0ed74f633ef66 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 10 Dec 2021 00:26:03 +0100 Subject: Fix compilation of tests against older qt --- autotests/testgroupsession.cpp | 6 +++--- autotests/testolmaccount.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'autotests') diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index afd5ef81..5024ccea 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -13,7 +13,7 @@ void TestOlmSession::groupSessionPicklingValid() { auto ogs = QOlmOutboundGroupSession::create(); const auto ogsId = ogs->sessionId(); - QVERIFY(QByteArray::fromBase64Encoding(ogsId).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + QVERIFY(QByteArray::fromBase64(ogsId).size() > 0); QCOMPARE(0, ogs->sessionMessageIndex()); auto ogsPickled = std::get(ogs->pickle(Unencrypted {})); @@ -23,7 +23,7 @@ void TestOlmSession::groupSessionPicklingValid() auto igs = QOlmInboundGroupSession::create(std::get(ogs->sessionKey())); const auto igsId = igs->sessionId(); // ID is valid base64? - QVERIFY(QByteArray::fromBase64Encoding(igsId).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + QVERIFY(QByteArray::fromBase64(igsId).size() > 0); //// no messages have been sent yet QCOMPARE(0, igs->firstKnownIndex()); @@ -42,7 +42,7 @@ void TestOlmSession::groupSessionCryptoValid() const auto plainText = QStringLiteral("Hello world!"); const auto ciphertext = std::get(ogs->encrypt(plainText)); // ciphertext valid base64? - QVERIFY(QByteArray::fromBase64Encoding(ciphertext).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + QVERIFY(QByteArray::fromBase64(ciphertext).size() > 0); const auto decryptionResult = std::get>(igs->decrypt(ciphertext)); diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 22c457aa..f0fcfe58 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -46,8 +46,8 @@ void TestOlmAccount::identityKeysValid() QCOMPARE(ed25519.size(), 43); // encoded as valid base64? - QVERIFY(QByteArray::fromBase64Encoding(curve25519).decodingStatus == QByteArray::Base64DecodingStatus::Ok); - QVERIFY(QByteArray::fromBase64Encoding(ed25519).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + QVERIFY(QByteArray::fromBase64(curve25519).size() > 0); + QVERIFY(QByteArray::fromBase64(ed25519).size() > 0); } void TestOlmAccount::signatureValid() @@ -56,7 +56,7 @@ void TestOlmAccount::signatureValid() olmAccount.createNewAccount(); const auto message = "Hello world!"; const auto signature = olmAccount.sign(message); - QVERIFY(QByteArray::fromBase64Encoding(signature).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + QVERIFY(QByteArray::fromBase64(signature).size() > 0); QOlmUtility utility; const auto identityKeys = olmAccount.identityKeys(); -- cgit v1.2.3 From 60947d610d0ece6943d2c2e385d6c6c2f960853d Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 24 Dec 2021 17:07:29 +0100 Subject: Apply suggestions --- .github/workflows/sonar.yml | 121 --------------------------------------- CMakeLists.txt | 2 +- Makefile | 31 ---------- autotests/testolmaccount.cpp | 7 --- lib/converters.cpp | 2 +- lib/e2ee/qolmaccount.cpp | 2 +- lib/e2ee/qolmerrors.cpp | 13 +++-- lib/e2ee/qolmerrors.h | 2 +- lib/e2ee/qolminboundsession.cpp | 5 +- lib/e2ee/qolmoutboundsession.cpp | 3 +- lib/e2ee/qolmsession.cpp | 2 +- lib/e2ee/qolmutility.cpp | 2 +- run-tests.sh | 23 ++++++++ 13 files changed, 40 insertions(+), 175 deletions(-) delete mode 100644 .github/workflows/sonar.yml delete mode 100644 Makefile create mode 100755 run-tests.sh (limited to 'autotests') diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml deleted file mode 100644 index c987b0cc..00000000 --- a/.github/workflows/sonar.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: Sonar - -on: - push: - pull_request: - types: [opened, reopened] - -defaults: - run: - shell: bash - -jobs: - SonarCloud: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - qt-version: [ '5.12.10' ] - e2ee: [ '', 'e2ee' ] - update-api: [ '', 'update-api' ] - - env: - SONAR_SCANNER_VERSION: 4.6.2.2472 - SONAR_SERVER_URL: "https://sonarcloud.io" - BUILD_WRAPPER_OUT_DIR: build/sonar - - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - submodules: ${{ matrix.e2ee != '' }} - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v2 - with: - path: ${{ runner.workspace }}/Qt - key: ${{ runner.os }}-Qt${{ matrix.qt-version }}-cache - - - name: Install Qt - uses: jurplel/install-qt-action@v2.11.1 - with: - version: ${{ matrix.qt-version }} -# arch: ${{ matrix.qt-arch }} # Only Windows needs that - cached: ${{ steps.cache-qt.outputs.cache-hit }} - - - name: Install Ninja - uses: seanmiddleditch/gha-setup-ninja@v3 - - - name: Setup build environment - run: | - echo "CC=gcc-10" >>$GITHUB_ENV - echo "CXX=g++-10" >>$GITHUB_ENV - mkdir -p $HOME/.sonar - echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ - -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV - cmake -E make_directory ${{ runner.workspace }}/build - - - name: Build and install olm - if: matrix.e2ee - run: | - cd .. - git clone https://gitlab.matrix.org/matrix-org/olm.git - cmake -S olm -B olm/build $CMAKE_ARGS - cmake --build olm/build --target install - - - name: Build and install qtKeychain - if: matrix.e2ee - run: | - cd .. - git clone https://github.com/frankosterfeld/qtkeychain.git - cmake -S qtkeychain -B qtkeychain/build $CMAKE_ARGS - cmake --build qtkeychain/build --target install - - - name: Pull CS API and build GTAD - if: matrix.update-api - run: | - cd .. - git clone https://github.com/quotient-im/matrix-doc.git - git clone --recursive https://github.com/KitsuneRal/gtad.git - cmake -S gtad -B gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF - cmake --build gtad - echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=$GITHUB_WORKSPACE/../matrix-doc \ - -DGTAD_PATH=$GITHUB_WORKSPACE/../gtad/gtad" \ - >>$GITHUB_ENV - - - name: Download and set up Sonar Cloud tools - run: | - pushd $HOME/.sonar - curl -sSLo build-wrapper.zip $SONAR_SERVER_URL/static/cpp/build-wrapper-linux-x86.zip - unzip -o build-wrapper.zip - echo "BUILD_WRAPPER=$HOME/.sonar/build-wrapper-linux-x86/build-wrapper-linux* --out-dir $BUILD_WRAPPER_OUT_DIR" >>$GITHUB_ENV - curl -sSLo sonar-scanner.zip \ - https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip - unzip -o sonar-scanner.zip - popd - - - name: Configure libQuotient - run: | - if [[ '${{ runner.os }}' == 'Windows' ]]; then - BIN_DIR=. - else - BIN_DIR=bin - fi - echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV - cmake -S $GITHUB_WORKSPACE -B build $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} - - - name: Regenerate API code - if: matrix.update-api - run: cmake --build build --target update-api - - - name: Build libQuotient - run: | - $BUILD_WRAPPER cmake --build build --target all - - - name: Run sonar-scanner - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: | - $HOME/.sonar/sonar-scanner*/bin/sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ff65282..9ef3477e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,7 @@ message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") if (${PROJECT_NAME}_ENABLE_E2EE) find_package(${Qt} ${QtMinVersion} REQUIRED Sql) - find_package(Olm 3.2.1 REQUIRED) + find_package(Olm 3.1.3 REQUIRED) set_package_properties(Olm PROPERTIES DESCRIPTION "Implementation of the Olm and Megolm cryptographic ratchets" URL "https://gitlab.matrix.org/matrix-org/olm" diff --git a/Makefile b/Makefile deleted file mode 100644 index 450e7888..00000000 --- a/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -SYNAPSE_IMAGE="matrixdotorg/synapse:v1.24.0" - -test: ## Run the tests - @cd build/ && GTEST_COLOR=1 ctest --verbose - -synapse: ## Start a synapse instance on docker - @mkdir -p data - @chmod 0777 data - @docker run -v `pwd`/data:/data --rm \ - -e SYNAPSE_SERVER_NAME=localhost -e SYNAPSE_REPORT_STATS=no ${SYNAPSE_IMAGE} generate - @./.ci/adjust-config.sh - @docker run -d \ - --name synapse \ - -p 443:8008 \ - -p 8448:8008 \ - -p 8008:8008 \ - -v `pwd`/data:/data ${SYNAPSE_IMAGE} - @echo Waiting for synapse to start... - @until curl -s -f -k https://localhost:443/_matrix/client/versions; do echo "Checking ..."; sleep 2; done - @echo Register alice - @docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice -p secret -c /data/homeserver.yaml https://localhost:8008' - @echo Register bob - @docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob -p secret -c /data/homeserver.yaml https://localhost:8008' - @echo Register carl - @docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008' - -stop-synapse: ## Stop any running instance of synapse - @rm -rf ./data/* - @docker rm -f synapse 2>&1>/dev/null - -restart: stop-synapse synapse diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index f0fcfe58..d547b683 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -12,13 +12,6 @@ #include #include -// for sleep -#ifdef _WIN32 -#include -#else -#include -#endif - using namespace Quotient; void TestOlmAccount::pickleUnpickledTest() diff --git a/lib/converters.cpp b/lib/converters.cpp index 4136940f..6cbb554d 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -3,7 +3,7 @@ #include "converters.h" -#include +#include #include "e2ee/e2ee.h" QJsonValue Quotient::JsonConverter::dump(const QVariant& v) diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index aaf51946..ffb004cc 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -34,7 +34,7 @@ bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) // Convert olm error to enum QOlmError lastError(OlmAccount *account) { - const std::string error_raw = olm_account_last_error(account); + const auto error_raw = olm_account_last_error(account); return fromString(error_raw); } diff --git a/lib/e2ee/qolmerrors.cpp b/lib/e2ee/qolmerrors.cpp index 6db1803c..568cf7fe 100644 --- a/lib/e2ee/qolmerrors.cpp +++ b/lib/e2ee/qolmerrors.cpp @@ -4,17 +4,18 @@ #include "qolmerrors.h" +#include -Quotient::QOlmError Quotient::fromString(const std::string &error_raw) { - if (!error_raw.compare("BAD_ACCOUNT_KEY")) { +Quotient::QOlmError Quotient::fromString(const char* error_raw) { + if (!strncmp(error_raw, "BAD_ACCOUNT_KEY", 15)) { return QOlmError::BadAccountKey; - } else if (!error_raw.compare("BAD_MESSAGE_KEY_ID")) { + } else if (!strncmp(error_raw, "BAD_MESSAGE_KEY_ID", 18)) { return QOlmError::BadMessageKeyId; - } else if (!error_raw.compare("INVALID_BASE64")) { + } else if (!strncmp(error_raw, "INVALID_BASE64", 14)) { return QOlmError::InvalidBase64; - } else if (!error_raw.compare("NOT_ENOUGH_RANDOM")) { + } else if (!strncmp(error_raw, "NOT_ENOUGH_RANDOM", 17)) { return QOlmError::NotEnoughRandom; - } else if (!error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { + } else if (!strncmp(error_raw, "OUTPUT_BUFFER_TOO_SMALL", 23)) { return QOlmError::OutputBufferTooSmall; } else { return QOlmError::Unknown; diff --git a/lib/e2ee/qolmerrors.h b/lib/e2ee/qolmerrors.h index f8390d2a..f2d77851 100644 --- a/lib/e2ee/qolmerrors.h +++ b/lib/e2ee/qolmerrors.h @@ -23,6 +23,6 @@ enum QOlmError Unknown, }; -QOlmError fromString(const std::string &error_raw); +QOlmError fromString(const char* error_raw); } //namespace Quotient diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 9bf56b6c..2c546875 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -8,7 +8,7 @@ using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { - const std::string error_raw = olm_inbound_group_session_last_error(session); + const auto error_raw = olm_inbound_group_session_last_error(session); return fromString(error_raw); } @@ -27,9 +27,8 @@ QOlmInboundGroupSession::~QOlmInboundGroupSession() std::unique_ptr 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(temp.data()), temp.size()); + reinterpret_cast(key.constData()), key.size()); if (error == olm_error()) { throw lastError(olmInboundGroupSession); diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 88e6b2e1..58196412 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -8,7 +8,7 @@ using namespace Quotient; QOlmError lastError(OlmOutboundGroupSession *session) { - const std::string error_raw = olm_outbound_group_session_last_error(session); + const auto error_raw = olm_outbound_group_session_last_error(session); return fromString(error_raw); } @@ -21,6 +21,7 @@ QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *sess QOlmOutboundGroupSession::~QOlmOutboundGroupSession() { olm_clear_outbound_group_session(m_groupSession); + Q_ASSERT(sizeof(m_groupSession) == olm_outbound_group_session_size()); delete[](reinterpret_cast(m_groupSession)); } diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 69d8b431..575019b3 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -11,7 +11,7 @@ using namespace Quotient; QOlmError lastError(OlmSession* session) { - const std::string error_raw = olm_session_last_error(session); + const auto error_raw = olm_session_last_error(session); return fromString(error_raw); } diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index d0684055..13ee695e 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -10,7 +10,7 @@ using namespace Quotient; // Convert olm error to enum QOlmError lastError(OlmUtility *utility) { - const std::string error_raw = olm_utility_last_error(utility); + const auto error_raw = olm_utility_last_error(utility); return fromString(error_raw); } diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 00000000..b49f37a1 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,23 @@ +mkdir -p data +chmod 0777 data +docker run -v `pwd`/data:/data --rm \ + -e SYNAPSE_SERVER_NAME=localhost -e SYNAPSE_REPORT_STATS=no matrixdotorg/synapse:v1.24.0 generate +./.ci/adjust-config.sh +docker run -d \ + --name synapse \ + -p 1234:8008 \ + -p 8448:8008 \ + -p 8008:8008 \ + -v `pwd`/data:/data matrixdotorg/synapse:v1.24.0 +echo Waiting for synapse to start... +until curl -s -f -k https://localhost:1234/_matrix/client/versions; do echo "Checking ..."; sleep 2; done +echo Register alice +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice -p secret -c /data/homeserver.yaml https://localhost:8008' +echo Register bob +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob -p secret -c /data/homeserver.yaml https://localhost:8008' +echo Register carl +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008' + +cd build/ && GTEST_COLOR=1 ctest --verbose +rm -rf ./data/* +docker rm -f synapse 2>&1>/dev/null -- cgit v1.2.3 From 43470ab005512acb5f137aa6b9026ebcf3fcc142 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 25 Dec 2021 16:01:32 +0100 Subject: Move run-tests.sh --- autotests/run-tests.sh | 23 +++++++++++++++++++++++ run-tests.sh | 23 ----------------------- 2 files changed, 23 insertions(+), 23 deletions(-) create mode 100755 autotests/run-tests.sh delete mode 100755 run-tests.sh (limited to 'autotests') diff --git a/autotests/run-tests.sh b/autotests/run-tests.sh new file mode 100755 index 00000000..b49f37a1 --- /dev/null +++ b/autotests/run-tests.sh @@ -0,0 +1,23 @@ +mkdir -p data +chmod 0777 data +docker run -v `pwd`/data:/data --rm \ + -e SYNAPSE_SERVER_NAME=localhost -e SYNAPSE_REPORT_STATS=no matrixdotorg/synapse:v1.24.0 generate +./.ci/adjust-config.sh +docker run -d \ + --name synapse \ + -p 1234:8008 \ + -p 8448:8008 \ + -p 8008:8008 \ + -v `pwd`/data:/data matrixdotorg/synapse:v1.24.0 +echo Waiting for synapse to start... +until curl -s -f -k https://localhost:1234/_matrix/client/versions; do echo "Checking ..."; sleep 2; done +echo Register alice +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice -p secret -c /data/homeserver.yaml https://localhost:8008' +echo Register bob +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob -p secret -c /data/homeserver.yaml https://localhost:8008' +echo Register carl +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008' + +cd build/ && GTEST_COLOR=1 ctest --verbose +rm -rf ./data/* +docker rm -f synapse 2>&1>/dev/null diff --git a/run-tests.sh b/run-tests.sh deleted file mode 100755 index b49f37a1..00000000 --- a/run-tests.sh +++ /dev/null @@ -1,23 +0,0 @@ -mkdir -p data -chmod 0777 data -docker run -v `pwd`/data:/data --rm \ - -e SYNAPSE_SERVER_NAME=localhost -e SYNAPSE_REPORT_STATS=no matrixdotorg/synapse:v1.24.0 generate -./.ci/adjust-config.sh -docker run -d \ - --name synapse \ - -p 1234:8008 \ - -p 8448:8008 \ - -p 8008:8008 \ - -v `pwd`/data:/data matrixdotorg/synapse:v1.24.0 -echo Waiting for synapse to start... -until curl -s -f -k https://localhost:1234/_matrix/client/versions; do echo "Checking ..."; sleep 2; done -echo Register alice -docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice -p secret -c /data/homeserver.yaml https://localhost:8008' -echo Register bob -docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob -p secret -c /data/homeserver.yaml https://localhost:8008' -echo Register carl -docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008' - -cd build/ && GTEST_COLOR=1 ctest --verbose -rm -rf ./data/* -docker rm -f synapse 2>&1>/dev/null -- cgit v1.2.3 From 0be5e13ce90c783ab49ae1f3223e9d84538b9112 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 29 Jan 2022 21:25:27 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- autotests/testolmaccount.cpp | 55 +++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 31 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index d547b683..45d158eb 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -171,14 +171,16 @@ void TestOlmAccount::encryptedFile() #define CREATE_CONNECTION(VAR, USERNAME, SECRET, DEVICE_NAME) \ NetworkAccessManager::instance()->ignoreSslErrors(true); \ auto VAR = std::make_shared(); \ - (VAR) ->resolveServer("@alice:localhost:" + QString::number(443)); \ - connect( (VAR) .get(), &Connection::loginFlowsChanged, this, [this, VAR ] () { \ + (VAR) ->resolveServer("@alice:localhost:443"); \ + connect( (VAR) .get(), &Connection::loginFlowsChanged, this, [=] { \ (VAR) ->loginWithPassword( (USERNAME) , SECRET , DEVICE_NAME , ""); \ }); \ - connect( (VAR) .get(), &Connection::networkError, [=](QString error, const QString &, int, int) { \ + connect( (VAR) .get(), &Connection::networkError, [](QString error) { \ + QWARN(qUtf8Printable(error)); \ QFAIL("Network error: make sure synapse is running"); \ }); \ - connect( (VAR) .get(), &Connection::loginError, [=](QString error, const QString &) { \ + connect( (VAR) .get(), &Connection::loginError, [](QString error) { \ + QWARN(qUtf8Printable(error)); \ QFAIL("Login failed"); \ }); \ QSignalSpy spy ## VAR ((VAR).get(), &Connection::loginFlowsChanged); \ @@ -197,9 +199,8 @@ void TestOlmAccount::uploadIdentityKey() OneTimeKeys unused; auto request = olmAccount->createUploadKeyRequest(unused); - connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { - auto job2 = static_cast(job); - QCOMPARE(job2->oneTimeKeyCounts().size(), 0); + connect(request, &BaseJob::result, this, [request, conn] { + QCOMPARE(request->oneTimeKeyCounts().size(), 0); }); connect(request, &BaseJob::failure, this, [] { QFAIL("upload failed"); @@ -225,10 +226,9 @@ void TestOlmAccount::uploadOneTimeKeys() oneTimeKeysHash["curve25519:"+keyId] = key; } auto request = new UploadKeysJob(none, oneTimeKeysHash); - connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { - auto job2 = static_cast(job); - QCOMPARE(job2->oneTimeKeyCounts().size(), 1); - QCOMPARE(job2->oneTimeKeyCounts()["curve25519"], 5); + connect(request, &BaseJob::result, this, [request, conn] { + QCOMPARE(request->oneTimeKeyCounts().size(), 1); + QCOMPARE(request->oneTimeKeyCounts()["curve25519"], 5); }); connect(request, &BaseJob::failure, this, [] { QFAIL("upload failed"); @@ -254,10 +254,9 @@ void TestOlmAccount::uploadSignedOneTimeKeys() oneTimeKeysHash[keyId] = var; } auto request = new UploadKeysJob(none, oneTimeKeysHash); - connect(request, &BaseJob::result, this, [request, nKeys, conn](BaseJob *job) { - auto job2 = static_cast(job); - QCOMPARE(job2->oneTimeKeyCounts().size(), 1); - QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], nKeys); + connect(request, &BaseJob::result, this, [request, nKeys, conn] { + QCOMPARE(request->oneTimeKeyCounts().size(), 1); + QCOMPARE(request->oneTimeKeyCounts()["signed_curve25519"], nKeys); }); connect(request, &BaseJob::failure, this, [] { QFAIL("upload failed"); @@ -275,10 +274,9 @@ void TestOlmAccount::uploadKeys() olmAccount->generateOneTimeKeys(1); auto otks = olmAccount->oneTimeKeys(); auto request = olmAccount->createUploadKeyRequest(otks); - connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { - auto job2 = static_cast(job); - QCOMPARE(job2->oneTimeKeyCounts().size(), 1); - QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], 1); + connect(request, &BaseJob::result, this, [request, conn] { + QCOMPARE(request->oneTimeKeyCounts().size(), 1); + QCOMPARE(request->oneTimeKeyCounts()["signed_curve25519"], 1); }); connect(request, &BaseJob::failure, this, [] { QFAIL("upload failed"); @@ -309,7 +307,6 @@ void TestOlmAccount::queryTest() bobOlm->generateOneTimeKeys(1); auto bobRes = bobOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys()); connect(bobRes, &BaseJob::result, this, [bobRes] { - QCOMPARE(bobRes->oneTimeKeyCounts().size(), 1); QCOMPARE(bobRes->oneTimeKeyCounts()["signed_curve25519"], 1); }); @@ -359,8 +356,6 @@ void TestOlmAccount::queryTest() } } - - void TestOlmAccount::claimKeys() { CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") @@ -371,10 +366,9 @@ void TestOlmAccount::claimKeys() bobOlm->generateOneTimeKeys(1); auto request = bobOlm->createUploadKeyRequest(bobOlm->oneTimeKeys()); - connect(request, &BaseJob::result, this, [request, bob](BaseJob *job) { - auto job2 = static_cast(job); - QCOMPARE(job2->oneTimeKeyCounts().size(), 1); - QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], 1); + connect(request, &BaseJob::result, this, [request, bob] { + QCOMPARE(request->oneTimeKeyCounts().size(), 1); + QCOMPARE(request->oneTimeKeyCounts()["signed_curve25519"], 1); }); bob->run(request); @@ -468,7 +462,6 @@ void TestOlmAccount::claimMultipleKeys() }); alice2->run(res2); - QVERIFY(spy.wait(10000)); QVERIFY(spy1.wait(10000)); QVERIFY(spy2.wait(1000)); // TODO this is failing even with 10000 @@ -502,7 +495,7 @@ void TestOlmAccount::keyChange() CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), QStringList()); - connect(job, &BaseJob::result, this, [alice, job, this] () { + connect(job, &BaseJob::result, this, [alice, job, this] { // Alice syncs to get the first next_batch token. alice->sync(); connect(alice.get(), &Connection::syncDone, this, [alice, this] { @@ -521,7 +514,7 @@ void TestOlmAccount::keyChange() // because of the key uploading. auto changeJob = alice->callApi(nextBatchToken, ""); - connect(changeJob, &BaseJob::result, this, [&changeJob, &alice] { + connect(changeJob, &BaseJob::result, this, [changeJob, alice] { QCOMPARE(changeJob->changed().size(), 1); QCOMPARE(changeJob->left().size(), 0); QCOMPARE(changeJob->changed()[0], alice->userId()); @@ -544,7 +537,7 @@ void TestOlmAccount::enableEncryption() QString joinedRoom; auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), {"@bob:localhost"}); - connect(alice.get(), &Connection::newRoom, this, [alice, bob, &joinedRoom, this] (Quotient::Room *room) { + connect(alice.get(), &Connection::newRoom, this, [alice, bob, joinedRoom, this] (Quotient::Room *room) { room->activateEncryption(); QSignalSpy spy(room, &Room::encryption); @@ -559,7 +552,7 @@ void TestOlmAccount::enableEncryption() QVERIFY(spy.wait(10000)); bob->sync(); - connect(bob.get(), &Connection::syncDone, this, [bob, &joinedRoom, this] { + connect(bob.get(), &Connection::syncDone, this, [bob, joinedRoom, this] { auto &events = bob->room(joinedRoom)->messageEvents(); bool hasEncryption = false; for (auto it = events.rbegin(); it != events.rend(); ++it) { -- cgit v1.2.3 From 27baabc9b8c9476fc550aef4462c193d5a9997a6 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sun, 30 Jan 2022 23:15:42 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- autotests/testolmutility.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index bbf3a055..d0476af0 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -47,14 +47,14 @@ void TestOlmUtility::canonicalJSON() void TestOlmUtility::verifySignedOneTimeKey() { - auto aliceOlm = std::make_shared("alice:matrix.org", "aliceDevice"); - aliceOlm->createNewAccount(); - aliceOlm->generateOneTimeKeys(1); - auto keys = aliceOlm->oneTimeKeys(); + QOlmAccount aliceOlm { "@alice:matrix.org", "aliceDevice" }; + aliceOlm.createNewAccount(); + aliceOlm.generateOneTimeKeys(1); + auto keys = aliceOlm.oneTimeKeys(); auto firstKey = keys.curve25519().keyValueBegin()->second; auto msgObj = QJsonObject({{"key", firstKey}}); - auto sig = aliceOlm->sign(msgObj); + auto sig = aliceOlm.sign(msgObj); auto msg = QJsonDocument(msgObj).toJson(QJsonDocument::Compact); @@ -66,8 +66,8 @@ void TestOlmUtility::verifySignedOneTimeKey() std::copy(sig.begin(), sig.end(), signatureBuf1.begin()); auto res = olm_ed25519_verify(utility, - aliceOlm->identityKeys().ed25519.data(), - aliceOlm->identityKeys().ed25519.size(), + aliceOlm.identityKeys().ed25519.data(), + aliceOlm.identityKeys().ed25519.size(), msg.data(), msg.size(), (void *)sig.data(), @@ -79,7 +79,7 @@ void TestOlmUtility::verifySignedOneTimeKey() delete[](reinterpret_cast(utility)); QOlmUtility utility2; - auto res2 = std::get(utility2.ed25519Verify(aliceOlm->identityKeys().ed25519, msg, signatureBuf1)); + auto res2 = std::get(utility2.ed25519Verify(aliceOlm.identityKeys().ed25519, msg, signatureBuf1)); //QCOMPARE(std::string(olm_utility_last_error(utility)), "SUCCESS"); QCOMPARE(res2, true); @@ -90,11 +90,11 @@ void TestOlmUtility::validUploadKeysRequest() const auto userId = QStringLiteral("@alice:matrix.org"); const auto deviceId = QStringLiteral("FKALSOCCC"); - auto alice = std::make_shared(userId, deviceId); - alice->createNewAccount(); - alice->generateOneTimeKeys(1); + QOlmAccount alice { userId, deviceId }; + alice.createNewAccount(); + alice.generateOneTimeKeys(1); - auto idSig = alice->signIdentityKeys(); + auto idSig = alice.signIdentityKeys(); QJsonObject body { @@ -103,8 +103,8 @@ void TestOlmUtility::validUploadKeysRequest() {"device_id", deviceId}, {"keys", QJsonObject{ - {QStringLiteral("curve25519:") + deviceId, QString::fromUtf8(alice->identityKeys().curve25519)}, - {QStringLiteral("ed25519:") + deviceId, QString::fromUtf8(alice->identityKeys().ed25519)} + {QStringLiteral("curve25519:") + deviceId, QString::fromUtf8(alice.identityKeys().curve25519)}, + {QStringLiteral("ed25519:") + deviceId, QString::fromUtf8(alice.identityKeys().ed25519)} } }, {"signatures", @@ -118,7 +118,7 @@ void TestOlmUtility::validUploadKeysRequest() } }; - DeviceKeys deviceKeys = alice->deviceKeys(); + DeviceKeys deviceKeys = alice.deviceKeys(); QCOMPARE(QJsonDocument(toJson(deviceKeys)).toJson(QJsonDocument::Compact), QJsonDocument(body).toJson(QJsonDocument::Compact)); -- cgit v1.2.3 From 7b5edb737522b03d4f697e0e09f1771ad5edef89 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 7 Feb 2022 21:48:07 +0100 Subject: Remove encryptionmanager and various fixes --- CMakeLists.txt | 2 +- autotests/testolmaccount.cpp | 2 +- lib/connection.cpp | 108 ++++++++++++++++++++++++----- lib/connection.h | 2 - lib/e2ee/qolminboundsession.cpp | 2 + lib/encryptionmanager.cpp | 149 ---------------------------------------- lib/encryptionmanager.h | 33 --------- lib/events/encryptedfile.cpp | 27 ++++++++ lib/events/encryptedfile.h | 6 ++ lib/jobs/downloadfilejob.cpp | 5 +- lib/mxcreply.cpp | 3 +- 11 files changed, 130 insertions(+), 209 deletions(-) delete mode 100644 lib/encryptionmanager.cpp delete mode 100644 lib/encryptionmanager.h create mode 100644 lib/events/encryptedfile.cpp (limited to 'autotests') diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ef3477e..69ac7e20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,6 +156,7 @@ list(APPEND lib_SRCS lib/events/roomkeyevent.cpp lib/events/stickerevent.cpp lib/events/keyverificationevent.cpp + lib/events/encryptedfile.cpp lib/jobs/requestdata.cpp lib/jobs/basejob.cpp lib/jobs/syncjob.cpp @@ -174,7 +175,6 @@ if (${PROJECT_NAME}_ENABLE_E2EE) lib/e2ee/qolmerrors.cpp lib/e2ee/qolmsession.cpp lib/e2ee/qolmmessage.cpp - lib/encryptionmanager.cpp ) endif() diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 45d158eb..62b786d0 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -537,7 +537,7 @@ void TestOlmAccount::enableEncryption() QString joinedRoom; auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), {"@bob:localhost"}); - connect(alice.get(), &Connection::newRoom, this, [alice, bob, joinedRoom, this] (Quotient::Room *room) { + connect(alice.get(), &Connection::newRoom, this, [alice, bob, &joinedRoom, this] (Quotient::Room *room) { room->activateEncryption(); QSignalSpy spy(room, &Room::encryption); diff --git a/lib/connection.cpp b/lib/connection.cpp index 58e3a9f8..1a1b284d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -37,7 +37,6 @@ #ifdef Quotient_E2EE_ENABLED # include "e2ee/qolmaccount.h" # include "e2ee/qolmutils.h" -# include "encryptionmanager.h" # include "database.h" #if QT_VERSION_MAJOR >= 6 @@ -117,6 +116,10 @@ public: bool encryptionUpdateRequired = false; PicklingMode picklingMode = Unencrypted {}; Database *database = nullptr; + + // A map from SenderKey to vector of InboundSession + UnorderedMap> olmSessions; + #endif GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -127,7 +130,6 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; bool isUploadingKeys = false; - EncryptionManager *encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -201,6 +203,85 @@ public: return q->stateCacheDir().filePath("state.json"); } +#ifdef Quotient_E2EE_ENABLED + void loadSessions() { + olmSessions = q->database()->loadOlmSessions(q->picklingMode()); + } + void saveSession(QOlmSessionPtr& session, const QString &senderKey) { + auto pickleResult = session->pickle(q->picklingMode()); + if (std::holds_alternative(pickleResult)) { + qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); + return; + } + q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult)); + } + QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) + { + Q_ASSERT(message.type() == QOlmMessage::PreKey); + for(auto& session : olmSessions[senderKey]) { + const auto matches = session->matchesInboundSessionFrom(senderKey, message); + if(std::holds_alternative(matches) && std::get(matches)) { + qCDebug(E2EE) << "Found inbound session"; + const auto result = session->decrypt(message); + if(std::holds_alternative(result)) { + return std::get(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message"; + return {}; + } + } + } + qCDebug(E2EE) << "Creating new inbound session"; + auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); + if(std::holds_alternative(newSessionResult)) { + qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); + return {}; + } + auto newSession = std::move(std::get(newSessionResult)); + auto error = olmAccount->removeOneTimeKeys(newSession); + if (error) { + qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); + } + const auto result = newSession->decrypt(message); + saveSession(newSession, senderKey); + olmSessions[senderKey].push_back(std::move(newSession)); + if(std::holds_alternative(result)) { + return std::get(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; + return {}; + } + } + QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) + { + Q_ASSERT(message.type() == QOlmMessage::General); + for(auto& session : olmSessions[senderKey]) { + const auto result = session->decrypt(message); + if(std::holds_alternative(result)) { + return std::get(result); + } + } + qCWarning(E2EE) << "Failed to decrypt message"; + return {}; + } + + QString sessionDecryptMessage( + const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) + { + QString decrypted; + int type = personalCipherObject.value(TypeKeyL).toInt(-1); + QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); + if (type == 0) { + QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); + decrypted = sessionDecryptPrekey(preKeyMessage, senderKey, account); + } else if (type == 1) { + QOlmMessage message(body, QOlmMessage::General); + decrypted = sessionDecryptGeneral(message, senderKey); + } + return decrypted; + } +#endif + EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { #ifndef Quotient_E2EE_ENABLED @@ -217,7 +298,7 @@ public: qCDebug(E2EE) << "Encrypted event is not for the current device"; return {}; } - const auto decrypted = encryptionManager->sessionDecryptMessage( + const auto decrypted = sessionDecryptMessage( personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" @@ -443,6 +524,7 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #endif // Quotient_E2EE_ENABLED + database->clear(); }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -493,13 +575,15 @@ void Connection::Private::completeSetup(const QString& mxId) picklingMode = Encrypted { job.binaryData() }; } + database = new Database(data->userId(), q); + // init olmAccount olmAccount = std::make_unique(data->userId(), data->deviceId(), q); connect(olmAccount.get(), &QOlmAccount::needsSave, q, &Connection::saveOlmAccount); - database = new Database(data->userId(), q); - - encryptionManager = new EncryptionManager(q); +#ifdef Quotient_E2EE_ENABLED + loadSessions(); +#endif if (database->accountPickle().isEmpty()) { // create new account and save unpickle data @@ -2019,18 +2103,6 @@ void Connection::saveOlmAccount() #endif } -QString Connection::e2eeDataDir() const -{ - auto safeUserId = userId(); - safeUserId.replace(':', '_'); - const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) % '/' - % safeUserId % '/'; - QDir dir; - if (!dir.exists(path)) - dir.mkpath(path); - return path; -} - #ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { diff --git a/lib/connection.h b/lib/connection.h index 93ee496e..8dec2a0c 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -401,8 +401,6 @@ public: bool lazyLoading() const; void setLazyLoading(bool newValue); - QString e2eeDataDir() const; - /*! Start a pre-created job object on this connection */ Q_INVOKABLE BaseJob* run(BaseJob* job, RunningPolicy runningPolicy = ForegroundRequest); diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 9bc80eef..2e9cc716 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -4,6 +4,8 @@ #include "e2ee/qolminboundsession.h" #include +#include + using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp deleted file mode 100644 index abdcdcee..00000000 --- a/lib/encryptionmanager.cpp +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "encryptionmanager.h" - -#include "connection.h" -#include "events/encryptedfile.h" -#include "database.h" - -#include "csapi/keys.h" - -#include -#include -#include - -#include "e2ee/e2ee.h" -#include "e2ee/qolmaccount.h" -#include "e2ee/qolmsession.h" -#include "e2ee/qolmmessage.h" -#include "e2ee/qolmerrors.h" -#include "e2ee/qolmutils.h" -#include -#include - -#include - -using namespace Quotient; -using std::move; - -class EncryptionManager::Private { -public: - EncryptionManager* q; - - Connection* connection; - - // A map from SenderKey to vector of InboundSession - UnorderedMap> sessions; - - void loadSessions() { - sessions = connection->database()->loadOlmSessions(connection->picklingMode()); - } - void saveSession(QOlmSessionPtr& session, const QString &senderKey) { - auto pickleResult = session->pickle(connection->picklingMode()); - if (std::holds_alternative(pickleResult)) { - qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); - return; - } - connection->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult)); - } - QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) - { - Q_ASSERT(message.type() == QOlmMessage::PreKey); - for(auto& session : sessions[senderKey]) { - const auto matches = session->matchesInboundSessionFrom(senderKey, message); - if(std::holds_alternative(matches) && std::get(matches)) { - qCDebug(E2EE) << "Found inbound session"; - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - return std::get(result); - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message"; - return {}; - } - } - } - qCDebug(E2EE) << "Creating new inbound session"; - auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); - if(std::holds_alternative(newSessionResult)) { - qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); - return {}; - } - auto newSession = std::move(std::get(newSessionResult)); - auto error = olmAccount->removeOneTimeKeys(newSession); - if (error) { - qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); - } - const auto result = newSession->decrypt(message); - saveSession(newSession, senderKey); - sessions[senderKey].push_back(std::move(newSession)); - if(std::holds_alternative(result)) { - return std::get(result); - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; - return {}; - } - } - QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) - { - Q_ASSERT(message.type() == QOlmMessage::General); - for(auto& session : sessions[senderKey]) { - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - return std::get(result); - } - } - qCWarning(E2EE) << "Failed to decrypt message"; - return {}; - } -}; - -EncryptionManager::EncryptionManager(QObject* parent) - : QObject(parent) - , d(std::make_unique()) -{ - d->q = this; - d->connection = static_cast(parent); - d->loadSessions(); -} - -EncryptionManager::~EncryptionManager() = default; - -QString EncryptionManager::sessionDecryptMessage( - const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) -{ - QString decrypted; - int type = personalCipherObject.value(TypeKeyL).toInt(-1); - QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); - if (type == 0) { - QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); - decrypted = d->sessionDecryptPrekey(preKeyMessage, senderKey, account); - } else if (type == 1) { - QOlmMessage message(body, QOlmMessage::General); - decrypted = d->sessionDecryptGeneral(message, senderKey); - } - return decrypted; -} - -QByteArray EncryptionManager::decryptFile(const QByteArray &ciphertext, EncryptedFile* file) -{ - const auto key = QByteArray::fromBase64(file->key.k.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); - const auto iv = QByteArray::fromBase64(file->iv.toLatin1()); - const auto sha256 = QByteArray::fromBase64(file->hashes["sha256"].toLatin1()); - if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return QByteArray(); - } - QByteArray plaintext(ciphertext.size(), 0); - EVP_CIPHER_CTX *ctx; - int length; - ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)key.data(), (const unsigned char *)iv.data()); - EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); - EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); - EVP_CIPHER_CTX_free(ctx); - return plaintext; -} -#endif // Quotient_E2EE_ENABLED diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h deleted file mode 100644 index 96569980..00000000 --- a/lib/encryptionmanager.h +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#pragma once - -#include - -#include -#include - -namespace Quotient { -class Connection; -class QOlmAccount; -struct EncryptedFile; - -class EncryptionManager : public QObject { - Q_OBJECT - -public: - explicit EncryptionManager(QObject* parent = nullptr); - ~EncryptionManager(); - QString sessionDecryptMessage(const QJsonObject& personalCipherObject, - const QByteArray& senderKey, std::unique_ptr& account); - static QByteArray decryptFile(const QByteArray &ciphertext, EncryptedFile* encryptedFile); - -private: - class Private; - std::unique_ptr d; -}; - -} // namespace Quotient -#endif // Quotient_E2EE_ENABLED diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp new file mode 100644 index 00000000..5ec344bb --- /dev/null +++ b/lib/events/encryptedfile.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "encryptedfile.h" + +using namespace Quotient; + +QByteArray EncryptedFile::decryptFile(const QByteArray &ciphertext) const +{ + QString _key = key.k; + _key = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); + const auto sha256 = QByteArray::fromBase64(hashes["sha256"].toLatin1()); + if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { + qCWarning(E2EE) << "Hash verification failed for file"; + return QByteArray(); + } + QByteArray plaintext(ciphertext.size(), 0); + EVP_CIPHER_CTX *ctx; + int length; + ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)_key.data(), (const unsigned char *)iv.toLatin1().data()); + EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); + EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext; +} diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 24ac9de1..f271d345 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -5,6 +5,10 @@ #pragma once #include "converters.h" +#include "logging.h" + +#include +#include namespace Quotient { /** @@ -44,6 +48,8 @@ public: QString iv; QHash hashes; QString v; + + QByteArray decryptFile(const QByteArray &ciphertext) const; }; template <> diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 2eea9d59..c5280770 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -9,7 +9,6 @@ #ifdef Quotient_E2EE_ENABLED # include -# include "encryptionmanager.h" # include "events/encryptedfile.h" #endif @@ -126,7 +125,7 @@ BaseJob::Status DownloadFileJob::prepareResult() QByteArray encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = EncryptionManager::decryptFile(encrypted, &file); + auto decrypted = file.decryptFile(encrypted); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -151,7 +150,7 @@ BaseJob::Status DownloadFileJob::prepareResult() auto encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = EncryptionManager::decryptFile(encrypted, &file); + auto decrypted = file.decryptFile(encrypted); d->tempFile->write(decrypted); } else { #endif diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index c7f27b0c..c666cce3 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -9,7 +9,6 @@ #include "room.h" #ifdef Quotient_E2EE_ENABLED -#include "encryptionmanager.h" #include "events/encryptedfile.h" #endif @@ -51,7 +50,7 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) } else { EncryptedFile file = *d->m_encryptedFile; auto buffer = new QBuffer(this); - buffer->setData(EncryptionManager::decryptFile(d->m_reply->readAll(), &file)); + buffer->setData(file.decryptFile(d->m_reply->readAll())); buffer->open(ReadOnly); d->m_device = buffer; } -- cgit v1.2.3 From a42fd5d188e58da95169ea21b4cae23cccd26819 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 16 Feb 2022 17:28:29 +0100 Subject: TestOlmUtility: fix building with Qt 5.12 QKeyValueIterator::operator->() only arrived in Qt 5.15. --- autotests/testolmutility.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'autotests') diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index d0476af0..7a55b61e 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -52,7 +52,7 @@ void TestOlmUtility::verifySignedOneTimeKey() aliceOlm.generateOneTimeKeys(1); auto keys = aliceOlm.oneTimeKeys(); - auto firstKey = keys.curve25519().keyValueBegin()->second; + auto firstKey = *keys.curve25519().begin(); auto msgObj = QJsonObject({{"key", firstKey}}); auto sig = aliceOlm.sign(msgObj); -- cgit v1.2.3 From 6a7e21cfc542588d6bcf7efe839439bf9b632600 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 17 Feb 2022 00:29:06 +0100 Subject: Don't create QApplications in tests --- autotests/testgroupsession.cpp | 2 +- autotests/testolmaccount.cpp | 2 +- autotests/testolmsession.cpp | 2 +- autotests/testolmutility.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'autotests') diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 5024ccea..4425a006 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -51,4 +51,4 @@ void TestOlmSession::groupSessionCryptoValid() QCOMPARE(0, decryptionResult.second); } -QTEST_MAIN(TestOlmSession) +QTEST_APPLESS_MAIN(TestOlmSession) diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 62b786d0..37d9f1d1 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -573,4 +573,4 @@ void TestOlmAccount::enableEncryption() QVERIFY(spy2.wait(10000)); } -QTEST_MAIN(TestOlmAccount) +QTEST_APPLESS_MAIN(TestOlmAccount) diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 41baf8e3..12279cb6 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -77,4 +77,4 @@ void TestOlmSession::correctSessionOrdering() QCOMPARE(sessionList[2]->sessionId(), session1Id); } -QTEST_MAIN(TestOlmSession) +QTEST_APPLESS_MAIN(TestOlmSession) diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index 7a55b61e..ca5aa1fd 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -126,4 +126,4 @@ void TestOlmUtility::validUploadKeysRequest() QVERIFY(verifyIdentitySignature(deviceKeys, deviceId, userId)); } -QTEST_MAIN(TestOlmUtility) +QTEST_APPLESS_MAIN(TestOlmUtility) -- cgit v1.2.3 From 120524a139dbac5d55c952de5dba0e23ba1025f8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 18 Feb 2022 10:44:38 +0100 Subject: CI: setup mock Synapse before running ctest To use this in CI required extending/fixing autotests/run-tests.sh: it can now accept arguments that are further passed to ctest invocation, and it no more cd's to the build directory because build directories can be in all kinds of places, expecting the caller to pick the directory upfront. --- .github/workflows/ci.yml | 2 +- autotests/run-tests.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'autotests') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8ea0d95..67ef5ac5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -217,7 +217,7 @@ jobs: QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true;quotient.events.debug=true' QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}' run: | - ctest --test-dir $BUILD_PATH --output-on-failure + autotests/run-tests.sh --test-dir $BUILD_PATH --output-on-failure [[ -z "$TEST_USER" ]] || \ LD_LIBRARY_PATH=$LIB_PATH \ $VALGRIND quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" diff --git a/autotests/run-tests.sh b/autotests/run-tests.sh index b49f37a1..adfb4ec1 100755 --- a/autotests/run-tests.sh +++ b/autotests/run-tests.sh @@ -18,6 +18,6 @@ docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob -p secre echo Register carl docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008' -cd build/ && GTEST_COLOR=1 ctest --verbose +GTEST_COLOR=1 ctest --verbose "$@" rm -rf ./data/* docker rm -f synapse 2>&1>/dev/null -- cgit v1.2.3 From 6be59df7036f8df385da29051c5320563518728a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 18 Feb 2022 11:06:49 +0100 Subject: Use QCoreApplication in autotests QEventLoop refuses to work without an application object instance. --- autotests/testgroupsession.cpp | 2 +- autotests/testolmaccount.cpp | 2 +- autotests/testolmsession.cpp | 2 +- autotests/testolmutility.cpp | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) (limited to 'autotests') diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 4425a006..2b949578 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -51,4 +51,4 @@ void TestOlmSession::groupSessionCryptoValid() QCOMPARE(0, decryptionResult.second); } -QTEST_APPLESS_MAIN(TestOlmSession) +QTEST_GUILESS_MAIN(TestOlmSession) diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 37d9f1d1..760276c8 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -573,4 +573,4 @@ void TestOlmAccount::enableEncryption() QVERIFY(spy2.wait(10000)); } -QTEST_APPLESS_MAIN(TestOlmAccount) +QTEST_GUILESS_MAIN(TestOlmAccount) diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 12279cb6..5436c392 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -77,4 +77,4 @@ void TestOlmSession::correctSessionOrdering() QCOMPARE(sessionList[2]->sessionId(), session1Id); } -QTEST_APPLESS_MAIN(TestOlmSession) +QTEST_GUILESS_MAIN(TestOlmSession) diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index ca5aa1fd..b4532c8d 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -125,5 +125,4 @@ void TestOlmUtility::validUploadKeysRequest() QVERIFY(verifyIdentitySignature(fromJson(body), deviceId, userId)); QVERIFY(verifyIdentitySignature(deviceKeys, deviceId, userId)); } - -QTEST_APPLESS_MAIN(TestOlmUtility) +QTEST_GUILESS_MAIN(TestOlmUtility) -- cgit v1.2.3 From 0a1b8b9f38ddadb32d54884a05f0e7246d7c25e7 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 18 Feb 2022 14:14:15 +0100 Subject: run-tests.sh: use a trap for cleanup This both is more reliable (GHA executes scripts in fail-fast mode) and ensures that the return value is that of ctest. --- autotests/run-tests.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'autotests') diff --git a/autotests/run-tests.sh b/autotests/run-tests.sh index adfb4ec1..0d0a4ca3 100755 --- a/autotests/run-tests.sh +++ b/autotests/run-tests.sh @@ -4,11 +4,13 @@ docker run -v `pwd`/data:/data --rm \ -e SYNAPSE_SERVER_NAME=localhost -e SYNAPSE_REPORT_STATS=no matrixdotorg/synapse:v1.24.0 generate ./.ci/adjust-config.sh docker run -d \ - --name synapse \ - -p 1234:8008 \ - -p 8448:8008 \ - -p 8008:8008 \ - -v `pwd`/data:/data matrixdotorg/synapse:v1.24.0 + --name synapse \ + -p 1234:8008 \ + -p 8448:8008 \ + -p 8008:8008 \ + -v `pwd`/data:/data matrixdotorg/synapse:v1.24.0 +trap "rm -rf ./data/*; docker rm -f synapse 2>&1 >/dev/null; trap - EXIT" EXIT + echo Waiting for synapse to start... until curl -s -f -k https://localhost:1234/_matrix/client/versions; do echo "Checking ..."; sleep 2; done echo Register alice @@ -19,5 +21,3 @@ echo Register carl docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008' GTEST_COLOR=1 ctest --verbose "$@" -rm -rf ./data/* -docker rm -f synapse 2>&1>/dev/null -- cgit v1.2.3 From 4f4c04c53ea27777e8cf4ecab690985ad3f78e1f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 18 Feb 2022 14:18:14 +0100 Subject: testgroupsession.*: fix TestOlmSession copy-pasta --- autotests/testgroupsession.cpp | 6 +++--- autotests/testgroupsession.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'autotests') diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 2b949578..2566669e 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -9,7 +9,7 @@ using namespace Quotient; -void TestOlmSession::groupSessionPicklingValid() +void TestGroupSession::groupSessionPicklingValid() { auto ogs = QOlmOutboundGroupSession::create(); const auto ogsId = ogs->sessionId(); @@ -33,7 +33,7 @@ void TestOlmSession::groupSessionPicklingValid() QCOMPARE(igsId, igs->sessionId()); } -void TestOlmSession::groupSessionCryptoValid() +void TestGroupSession::groupSessionCryptoValid() { auto ogs = QOlmOutboundGroupSession::create(); auto igs = QOlmInboundGroupSession::create(std::get(ogs->sessionKey())); @@ -51,4 +51,4 @@ void TestOlmSession::groupSessionCryptoValid() QCOMPARE(0, decryptionResult.second); } -QTEST_GUILESS_MAIN(TestOlmSession) +QTEST_GUILESS_MAIN(TestGroupSession) diff --git a/autotests/testgroupsession.h b/autotests/testgroupsession.h index 7743295f..6edf0d16 100644 --- a/autotests/testgroupsession.h +++ b/autotests/testgroupsession.h @@ -4,7 +4,7 @@ #include -class TestOlmSession : public QObject +class TestGroupSession : public QObject { Q_OBJECT -- cgit v1.2.3 From d4f1b09476c4bcc5c33cc40e2506d7fd6a731456 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 18 Feb 2022 14:27:24 +0100 Subject: TestOlmAccount: align homeserver address with that in run-tests.sh It would probably be even better to pass the homeserver address in the environment but that's a bigger endeavour. Also: reformatted CREATE_CONNECTION macro. --- autotests/testolmaccount.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 760276c8..0c44c8d7 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -168,25 +168,25 @@ void TestOlmAccount::encryptedFile() QCOMPARE(file.key.kty, "oct"); } -#define CREATE_CONNECTION(VAR, USERNAME, SECRET, DEVICE_NAME) \ - NetworkAccessManager::instance()->ignoreSslErrors(true); \ - auto VAR = std::make_shared(); \ - (VAR) ->resolveServer("@alice:localhost:443"); \ - connect( (VAR) .get(), &Connection::loginFlowsChanged, this, [=] { \ - (VAR) ->loginWithPassword( (USERNAME) , SECRET , DEVICE_NAME , ""); \ - }); \ - connect( (VAR) .get(), &Connection::networkError, [](QString error) { \ - QWARN(qUtf8Printable(error)); \ - QFAIL("Network error: make sure synapse is running"); \ - }); \ - connect( (VAR) .get(), &Connection::loginError, [](QString error) { \ - QWARN(qUtf8Printable(error)); \ - QFAIL("Login failed"); \ - }); \ - QSignalSpy spy ## VAR ((VAR).get(), &Connection::loginFlowsChanged); \ - QSignalSpy spy2 ## VAR ((VAR).get(), &Connection::connected); \ - QVERIFY(spy ## VAR .wait(10000)); \ - QVERIFY(spy2 ## VAR .wait(10000)); +#define CREATE_CONNECTION(VAR, USERNAME, SECRET, DEVICE_NAME) \ + NetworkAccessManager::instance()->ignoreSslErrors(true); \ + auto VAR = std::make_shared(); \ + (VAR)->resolveServer("@" USERNAME ":localhost:1234"); \ + connect((VAR).get(), &Connection::loginFlowsChanged, this, [=] { \ + (VAR)->loginWithPassword((USERNAME), SECRET, DEVICE_NAME, ""); \ + }); \ + connect((VAR).get(), &Connection::networkError, [](QString error) { \ + QWARN(qUtf8Printable(error)); \ + QFAIL("Network error: make sure synapse is running"); \ + }); \ + connect((VAR).get(), &Connection::loginError, [](QString error) { \ + QWARN(qUtf8Printable(error)); \ + QFAIL("Login failed"); \ + }); \ + QSignalSpy spy##VAR((VAR).get(), &Connection::loginFlowsChanged); \ + QSignalSpy spy2##VAR((VAR).get(), &Connection::connected); \ + QVERIFY(spy##VAR.wait(10000)); \ + QVERIFY(spy2##VAR.wait(10000)); void TestOlmAccount::uploadIdentityKey() { -- cgit v1.2.3 From 34d5ed388caf8a39327c2ea4da5de197faf5583d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 18 Feb 2022 16:41:27 +0100 Subject: testolmaccount.cpp: cleanup --- autotests/testolmaccount.cpp | 164 ++++++++++++++++++++++--------------------- 1 file changed, 83 insertions(+), 81 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 0c44c8d7..b877a692 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -4,13 +4,15 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testolmaccount.h" + +#include +#include #include #include -#include +#include #include #include #include -#include using namespace Quotient; @@ -84,8 +86,8 @@ void TestOlmAccount::deviceKeys() {"ed25519:JLAFKJWSCS", "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"}}; // TODO that should be the default value - device1.algorithms = QStringList {"m.olm.v1.curve25519-aes-sha2", - "m.megolm.v1.aes-sha2"}; + device1.algorithms = + QStringList { OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey }; device1.signatures = { {"@alice:example.com", @@ -168,24 +170,24 @@ void TestOlmAccount::encryptedFile() QCOMPARE(file.key.kty, "oct"); } -#define CREATE_CONNECTION(VAR, USERNAME, SECRET, DEVICE_NAME) \ - NetworkAccessManager::instance()->ignoreSslErrors(true); \ - auto VAR = std::make_shared(); \ - (VAR)->resolveServer("@" USERNAME ":localhost:1234"); \ - connect((VAR).get(), &Connection::loginFlowsChanged, this, [=] { \ - (VAR)->loginWithPassword((USERNAME), SECRET, DEVICE_NAME, ""); \ - }); \ - connect((VAR).get(), &Connection::networkError, [](QString error) { \ - QWARN(qUtf8Printable(error)); \ - QFAIL("Network error: make sure synapse is running"); \ - }); \ - connect((VAR).get(), &Connection::loginError, [](QString error) { \ - QWARN(qUtf8Printable(error)); \ - QFAIL("Login failed"); \ - }); \ - QSignalSpy spy##VAR((VAR).get(), &Connection::loginFlowsChanged); \ - QSignalSpy spy2##VAR((VAR).get(), &Connection::connected); \ - QVERIFY(spy##VAR.wait(10000)); \ +#define CREATE_CONNECTION(VAR, USERNAME, SECRET, DEVICE_NAME) \ + NetworkAccessManager::instance()->ignoreSslErrors(true); \ + auto VAR = std::make_shared(); \ + (VAR)->resolveServer("@" USERNAME ":localhost:1234"); \ + connect((VAR).get(), &Connection::loginFlowsChanged, this, [=] { \ + (VAR)->loginWithPassword((USERNAME), SECRET, DEVICE_NAME, ""); \ + }); \ + connect((VAR).get(), &Connection::networkError, [](const QString& error) { \ + QWARN(qUtf8Printable(error)); \ + QFAIL("Network error: make sure synapse is running"); \ + }); \ + connect((VAR).get(), &Connection::loginError, [](const QString& error) { \ + QWARN(qUtf8Printable(error)); \ + QFAIL("Login failed"); \ + }); \ + QSignalSpy spy##VAR((VAR).get(), &Connection::loginFlowsChanged); \ + QSignalSpy spy2##VAR((VAR).get(), &Connection::connected); \ + QVERIFY(spy##VAR.wait(10000)); \ QVERIFY(spy2##VAR.wait(10000)); void TestOlmAccount::uploadIdentityKey() @@ -228,7 +230,7 @@ void TestOlmAccount::uploadOneTimeKeys() auto request = new UploadKeysJob(none, oneTimeKeysHash); connect(request, &BaseJob::result, this, [request, conn] { QCOMPARE(request->oneTimeKeyCounts().size(), 1); - QCOMPARE(request->oneTimeKeyCounts()["curve25519"], 5); + QCOMPARE(request->oneTimeKeyCounts().value(Curve25519Key), 5); }); connect(request, &BaseJob::failure, this, [] { QFAIL("upload failed"); @@ -256,7 +258,7 @@ void TestOlmAccount::uploadSignedOneTimeKeys() auto request = new UploadKeysJob(none, oneTimeKeysHash); connect(request, &BaseJob::result, this, [request, nKeys, conn] { QCOMPARE(request->oneTimeKeyCounts().size(), 1); - QCOMPARE(request->oneTimeKeyCounts()["signed_curve25519"], nKeys); + QCOMPARE(request->oneTimeKeyCounts().value(SignedCurve25519Key), nKeys); }); connect(request, &BaseJob::failure, this, [] { QFAIL("upload failed"); @@ -276,7 +278,7 @@ void TestOlmAccount::uploadKeys() auto request = olmAccount->createUploadKeyRequest(otks); connect(request, &BaseJob::result, this, [request, conn] { QCOMPARE(request->oneTimeKeyCounts().size(), 1); - QCOMPARE(request->oneTimeKeyCounts()["signed_curve25519"], 1); + QCOMPARE(request->oneTimeKeyCounts().value(SignedCurve25519Key), 1); }); connect(request, &BaseJob::failure, this, [] { QFAIL("upload failed"); @@ -297,7 +299,7 @@ void TestOlmAccount::queryTest() auto aliceRes = aliceOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys()); connect(aliceRes, &BaseJob::result, this, [aliceRes] { QCOMPARE(aliceRes->oneTimeKeyCounts().size(), 1); - QCOMPARE(aliceRes->oneTimeKeyCounts()["signed_curve25519"], 1); + QCOMPARE(aliceRes->oneTimeKeyCounts().value(SignedCurve25519Key), 1); }); QSignalSpy spy(aliceRes, &BaseJob::result); alice->run(aliceRes); @@ -308,7 +310,7 @@ void TestOlmAccount::queryTest() auto bobRes = bobOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys()); connect(bobRes, &BaseJob::result, this, [bobRes] { QCOMPARE(bobRes->oneTimeKeyCounts().size(), 1); - QCOMPARE(bobRes->oneTimeKeyCounts()["signed_curve25519"], 1); + QCOMPARE(bobRes->oneTimeKeyCounts().value(SignedCurve25519Key), 1); }); QSignalSpy spy1(bobRes, &BaseJob::result); bob->run(bobRes); @@ -320,13 +322,13 @@ void TestOlmAccount::queryTest() deviceKeys[bob->userId()] = QStringList(); auto job = alice->callApi(deviceKeys); QSignalSpy spy(job, &BaseJob::result); - connect(job, &BaseJob::result, this, [job, &bob, &bobOlm] { + connect(job, &BaseJob::result, this, [job, bob, bobOlm] { QCOMPARE(job->failures().size(), 0); - auto aliceDevices = job->deviceKeys()[bob->userId()]; - QVERIFY(aliceDevices.size() > 0); + const auto& aliceDevices = job->deviceKeys().value(bob->userId()); + QVERIFY(!aliceDevices.empty()); - auto devKeys = aliceDevices[bob->deviceId()]; + const auto& devKeys = aliceDevices.value(bob->deviceId()); QCOMPARE(devKeys.userId, bob->userId()); QCOMPARE(devKeys.deviceId, bob->deviceId()); QCOMPARE(devKeys.keys, bobOlm->deviceKeys().keys); @@ -340,11 +342,11 @@ void TestOlmAccount::queryTest() deviceKeys[alice->userId()] = QStringList(); auto job = bob->callApi(deviceKeys); QSignalSpy spy(job, &BaseJob::result); - connect(job, &BaseJob::result, this, [job, &alice, &aliceOlm] { + connect(job, &BaseJob::result, this, [job, alice, aliceOlm] { QCOMPARE(job->failures().size(), 0); - auto bobDevices = job->deviceKeys()[alice->userId()]; - QVERIFY(bobDevices.size() > 0); + const auto& bobDevices = job->deviceKeys().value(alice->userId()); + QVERIFY(!bobDevices.empty()); auto devKeys = bobDevices[alice->deviceId()]; QCOMPARE(devKeys.userId, alice->userId()); @@ -368,7 +370,7 @@ void TestOlmAccount::claimKeys() connect(request, &BaseJob::result, this, [request, bob] { QCOMPARE(request->oneTimeKeyCounts().size(), 1); - QCOMPARE(request->oneTimeKeyCounts()["signed_curve25519"], 1); + QCOMPARE(request->oneTimeKeyCounts().value(SignedCurve25519Key), 1); }); bob->run(request); @@ -376,51 +378,47 @@ void TestOlmAccount::claimKeys() QVERIFY(requestSpy.wait(10000)); // Alice retrieves bob's keys & claims one signed one-time key. - auto *aliceOlm = alice->olmAccount(); QHash deviceKeys; deviceKeys[bob->userId()] = QStringList(); auto job = alice->callApi(deviceKeys); - connect(job, &BaseJob::result, this, [bob, alice, aliceOlm, job, this] { - auto bobDevices = job->deviceKeys()[bob->userId()]; - QVERIFY(bobDevices.size() > 0); + connect(job, &BaseJob::result, this, [bob, alice, job, this] { + const auto& bobDevices = job->deviceKeys().value(bob->userId()); + QVERIFY(!bobDevices.empty()); // Retrieve the identity key for the current device. - auto bobEd25519 = - bobDevices[bob->deviceId()].keys["ed25519:" + bob->deviceId()]; + const auto& bobEd25519 = + bobDevices.value(bob->deviceId()).keys["ed25519:" + bob->deviceId()]; const auto currentDevice = bobDevices[bob->deviceId()]; // Verify signature. - QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(), bob->userId())); + QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(), + bob->userId())); QHash> oneTimeKeys; oneTimeKeys[bob->userId()] = QHash(); oneTimeKeys[bob->userId()][bob->deviceId()] = SignedCurve25519Key; auto job = alice->callApi(oneTimeKeys); - connect(job, &BaseJob::result, this, [aliceOlm, bob, bobEd25519, job] { + connect(job, &BaseJob::result, this, [bob, bobEd25519, job] { const auto userId = bob->userId(); const auto deviceId = bob->deviceId(); // The device exists. QCOMPARE(job->oneTimeKeys().size(), 1); - QCOMPARE(job->oneTimeKeys()[userId].size(), 1); + QCOMPARE(job->oneTimeKeys().value(userId).size(), 1); // The key is the one bob sent. - auto oneTimeKey = job->oneTimeKeys()[userId][deviceId]; + const auto& oneTimeKey = + job->oneTimeKeys().value(userId).value(deviceId); QVERIFY(oneTimeKey.canConvert()); - QVariantMap varMap = oneTimeKey.toMap(); - bool found = false; - for (const auto &key : varMap.keys()) { - if (key.startsWith(QStringLiteral("signed_curve25519"))) { - found = true; - } - } - QVERIFY(found); - - //auto algo = oneTimeKey.begin().key(); - //auto contents = oneTimeKey.begin().value(); + const auto varMap = oneTimeKey.toMap(); + QVERIFY(std::any_of(varMap.constKeyValueBegin(), + varMap.constKeyValueEnd(), [](const auto& kv) { + return kv.first.startsWith( + SignedCurve25519Key); + })); }); }); } @@ -438,7 +436,7 @@ void TestOlmAccount::claimMultipleKeys() QSignalSpy spy(res, &BaseJob::result); connect(res, &BaseJob::result, this, [res] { QCOMPARE(res->oneTimeKeyCounts().size(), 1); - QCOMPARE(res->oneTimeKeyCounts()["signed_curve25519"], 10); + QCOMPARE(res->oneTimeKeyCounts().value(SignedCurve25519Key), 10); }); alice->run(res); @@ -448,7 +446,7 @@ void TestOlmAccount::claimMultipleKeys() QSignalSpy spy1(res1, &BaseJob::result); connect(res1, &BaseJob::result, this, [res1] { QCOMPARE(res1->oneTimeKeyCounts().size(), 1); - QCOMPARE(res1->oneTimeKeyCounts()["signed_curve25519"], 10); + QCOMPARE(res1->oneTimeKeyCounts().value(SignedCurve25519Key), 10); }); alice1->run(res1); @@ -458,7 +456,7 @@ void TestOlmAccount::claimMultipleKeys() QSignalSpy spy2(res2, &BaseJob::result); connect(res2, &BaseJob::result, this, [res2] { QCOMPARE(res2->oneTimeKeyCounts().size(), 1); - QCOMPARE(res2->oneTimeKeyCounts()["signed_curve25519"], 10); + QCOMPARE(res2->oneTimeKeyCounts().value(SignedCurve25519Key), 10); }); alice2->run(res2); @@ -482,11 +480,10 @@ void TestOlmAccount::claimMultipleKeys() auto job = bob->callApi(oneTimeKeys); connect(job, &BaseJob::result, this, [bob, job] { const auto userId = bob->userId(); - const auto deviceId = bob->deviceId(); // The device exists. QCOMPARE(job->oneTimeKeys().size(), 1); - QCOMPARE(job->oneTimeKeys()[userId].size(), 3); + QCOMPARE(job->oneTimeKeys().value(userId).size(), 3); }); } @@ -494,8 +491,9 @@ void TestOlmAccount::keyChange() { CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") - auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), QStringList()); + auto job = alice->createRoom(Connection::PublishRoom, {}, {}, {}, {}); connect(job, &BaseJob::result, this, [alice, job, this] { + QVERIFY(job->status().good()); // Alice syncs to get the first next_batch token. alice->sync(); connect(alice.get(), &Connection::syncDone, this, [alice, this] { @@ -517,7 +515,7 @@ void TestOlmAccount::keyChange() connect(changeJob, &BaseJob::result, this, [changeJob, alice] { QCOMPARE(changeJob->changed().size(), 1); QCOMPARE(changeJob->left().size(), 0); - QCOMPARE(changeJob->changed()[0], alice->userId()); + QCOMPARE(*changeJob->changed().cbegin(), alice->userId()); }); QSignalSpy spy2(changeJob, &BaseJob::result); QVERIFY(spy2.wait(10000)); @@ -534,39 +532,43 @@ void TestOlmAccount::enableEncryption() CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") CREATE_CONNECTION(bob, "bob", "secret", "BobPhone") - QString joinedRoom; + QString joinedRoomId; - auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), {"@bob:localhost"}); - connect(alice.get(), &Connection::newRoom, this, [alice, bob, &joinedRoom, this] (Quotient::Room *room) { - room->activateEncryption(); - QSignalSpy spy(room, &Room::encryption); + auto job = alice->createRoom(Connection::PublishRoom, {}, {}, {}, + { "@bob:localhost" }); + connect(alice.get(), &Connection::newRoom, this, + [alice, bob, &joinedRoomId](Quotient::Room* room) { + room->activateEncryption(); + QSignalSpy spy(room, &Room::encryption); - joinedRoom = room->id(); - auto job = bob->joinRoom(room->id()); - QSignalSpy spy1(job, &BaseJob::result); - QVERIFY(spy.wait(10000)); - QVERIFY(spy1.wait(10000)); - }); + joinedRoomId = room->id(); + auto job = bob->joinRoom(room->id()); + QSignalSpy spy1(job, &BaseJob::result); + QVERIFY(spy.wait(10000)); + QVERIFY(spy1.wait(10000)); + }); QSignalSpy spy(job, &BaseJob::result); QVERIFY(spy.wait(10000)); bob->sync(); - connect(bob.get(), &Connection::syncDone, this, [bob, joinedRoom, this] { - auto &events = bob->room(joinedRoom)->messageEvents(); + connect(bob.get(), &Connection::syncDone, this, [bob, joinedRoomId] { + const auto* joinedRoom = bob->room(joinedRoomId); + const auto& events = joinedRoom->messageEvents(); bool hasEncryption = false; for (auto it = events.rbegin(); it != events.rend(); ++it) { - auto event = it->event(); + const auto& event = it->event(); if (eventCast(event)) { hasEncryption = true; } else { - qDebug() << event->matrixType() << typeId() << event->type(); - if ( event->matrixType() == "m.room.encryption") { + qDebug() << event->matrixType() << typeId() + << event->type(); + if (is(*event)) { qDebug() << event->contentJson(); - } + } } } - QVERIFY(bob->room(joinedRoom)->usesEncryption()); + QVERIFY(bob->room(joinedRoomId)->usesEncryption()); QVERIFY(hasEncryption); }); QSignalSpy spy2(bob.get(), &Connection::syncDone); -- cgit v1.2.3 From c239efb530d984bcfc19a02c96bfb5481adb0971 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 18 Feb 2022 16:42:42 +0100 Subject: TestOlmAccount::claimKeys(): auth bob as bob, not as alice --- autotests/testolmaccount.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index b877a692..36f10f2e 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -361,7 +361,7 @@ void TestOlmAccount::queryTest() void TestOlmAccount::claimKeys() { CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") - CREATE_CONNECTION(bob, "alice", "secret", "AlicePhone") + CREATE_CONNECTION(bob, "bob", "secret", "BobPhone") // Bob uploads his keys. auto *bobOlm = bob->olmAccount(); -- cgit v1.2.3 From fb406897292184c46432ed0247ca9bdbef666f69 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 23 Feb 2022 01:37:02 +0100 Subject: Fix tests a bit --- autotests/run-tests.sh | 16 +++++++-- autotests/testolmaccount.cpp | 79 ++++++++++---------------------------------- autotests/testolmaccount.h | 1 - 3 files changed, 31 insertions(+), 65 deletions(-) (limited to 'autotests') diff --git a/autotests/run-tests.sh b/autotests/run-tests.sh index 0d0a4ca3..74a8c544 100755 --- a/autotests/run-tests.sh +++ b/autotests/run-tests.sh @@ -1,5 +1,6 @@ mkdir -p data chmod 0777 data +rm ~/.local/share/testolmaccount -rf docker run -v `pwd`/data:/data --rm \ -e SYNAPSE_SERVER_NAME=localhost -e SYNAPSE_REPORT_STATS=no matrixdotorg/synapse:v1.24.0 generate ./.ci/adjust-config.sh @@ -14,9 +15,20 @@ trap "rm -rf ./data/*; docker rm -f synapse 2>&1 >/dev/null; trap - EXIT" EXIT echo Waiting for synapse to start... until curl -s -f -k https://localhost:1234/_matrix/client/versions; do echo "Checking ..."; sleep 2; done echo Register alice -docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice1 -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice2 -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice3 -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice4 -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice5 -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice6 -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice7 -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice8 -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice9 -p secret -c /data/homeserver.yaml https://localhost:8008' echo Register bob -docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob1 -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob2 -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob3 -p secret -c /data/homeserver.yaml https://localhost:8008' +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob4 -p secret -c /data/homeserver.yaml https://localhost:8008' echo Register carl docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008' diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 36f10f2e..033fff32 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -192,7 +192,7 @@ void TestOlmAccount::encryptedFile() void TestOlmAccount::uploadIdentityKey() { - CREATE_CONNECTION(conn, "alice", "secret", "AlicePhone") + CREATE_CONNECTION(conn, "alice1", "secret", "AlicePhone") auto olmAccount = conn->olmAccount(); auto idKeys = olmAccount->identityKeys(); @@ -214,7 +214,7 @@ void TestOlmAccount::uploadIdentityKey() void TestOlmAccount::uploadOneTimeKeys() { - CREATE_CONNECTION(conn, "alice", "secret", "AlicePhone") + CREATE_CONNECTION(conn, "alice2", "secret", "AlicePhone") auto olmAccount = conn->olmAccount(); auto nKeys = olmAccount->generateOneTimeKeys(5); @@ -242,7 +242,7 @@ void TestOlmAccount::uploadOneTimeKeys() void TestOlmAccount::uploadSignedOneTimeKeys() { - CREATE_CONNECTION(conn, "alice", "secret", "AlicePhone") + CREATE_CONNECTION(conn, "alice3", "secret", "AlicePhone") auto olmAccount = conn->olmAccount(); auto nKeys = olmAccount->generateOneTimeKeys(5); QCOMPARE(nKeys, 5); @@ -270,7 +270,7 @@ void TestOlmAccount::uploadSignedOneTimeKeys() void TestOlmAccount::uploadKeys() { - CREATE_CONNECTION(conn, "alice", "secret", "AlicePhone") + CREATE_CONNECTION(conn, "alice4", "secret", "AlicePhone") auto olmAccount = conn->olmAccount(); auto idks = olmAccount->identityKeys(); olmAccount->generateOneTimeKeys(1); @@ -290,8 +290,8 @@ void TestOlmAccount::uploadKeys() void TestOlmAccount::queryTest() { - CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") - CREATE_CONNECTION(bob, "bob", "secret", "BobPhone") + CREATE_CONNECTION(alice, "alice5", "secret", "AlicePhone") + CREATE_CONNECTION(bob, "bob1", "secret", "BobPhone") // Create and upload keys for both users. auto aliceOlm = alice->olmAccount(); @@ -360,8 +360,8 @@ void TestOlmAccount::queryTest() void TestOlmAccount::claimKeys() { - CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") - CREATE_CONNECTION(bob, "bob", "secret", "BobPhone") + CREATE_CONNECTION(alice, "alice6", "secret", "AlicePhone") + CREATE_CONNECTION(bob, "bob2", "secret", "BobPhone") // Bob uploads his keys. auto *bobOlm = bob->olmAccount(); @@ -426,9 +426,9 @@ void TestOlmAccount::claimKeys() void TestOlmAccount::claimMultipleKeys() { // Login with alice multiple times - CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") - CREATE_CONNECTION(alice1, "alice", "secret", "AlicePhone") - CREATE_CONNECTION(alice2, "alice", "secret", "AlicePhone") + CREATE_CONNECTION(alice, "alice7", "secret", "AlicePhone") + CREATE_CONNECTION(alice1, "alice7", "secret", "AlicePhone") + CREATE_CONNECTION(alice2, "alice7", "secret", "AlicePhone") auto olm = alice->olmAccount(); olm->generateOneTimeKeys(10); @@ -465,7 +465,7 @@ void TestOlmAccount::claimMultipleKeys() QVERIFY(spy2.wait(1000)); // TODO this is failing even with 10000 // Bob will claim all keys from alice - CREATE_CONNECTION(bob, "bob", "secret", "BobPhone") + CREATE_CONNECTION(bob, "bob3", "secret", "BobPhone") QStringList devices_; devices_ << alice->deviceId() @@ -487,50 +487,10 @@ void TestOlmAccount::claimMultipleKeys() }); } -void TestOlmAccount::keyChange() -{ - CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") - - auto job = alice->createRoom(Connection::PublishRoom, {}, {}, {}, {}); - connect(job, &BaseJob::result, this, [alice, job, this] { - QVERIFY(job->status().good()); - // Alice syncs to get the first next_batch token. - alice->sync(); - connect(alice.get(), &Connection::syncDone, this, [alice, this] { - const auto nextBatchToken = alice->nextBatchToken(); - - // generate keys and change existing one - auto aliceOlm = alice->olmAccount(); - aliceOlm->generateOneTimeKeys(1); - auto aliceRes = aliceOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys()); - QSignalSpy spy(aliceRes, &BaseJob::result); - - alice->run(aliceRes); - QVERIFY(spy.wait(10000)); - - // The key changes should contain her username - // because of the key uploading. - - auto changeJob = alice->callApi(nextBatchToken, ""); - connect(changeJob, &BaseJob::result, this, [changeJob, alice] { - QCOMPARE(changeJob->changed().size(), 1); - QCOMPARE(changeJob->left().size(), 0); - QCOMPARE(*changeJob->changed().cbegin(), alice->userId()); - }); - QSignalSpy spy2(changeJob, &BaseJob::result); - QVERIFY(spy2.wait(10000)); - }); - QSignalSpy spy2(alice.get(), &Connection::syncDone); - QVERIFY(spy2.wait(10000)); - }); - QSignalSpy spy(job, &BaseJob::result); - QVERIFY(spy.wait(10000)); -} - void TestOlmAccount::enableEncryption() { - CREATE_CONNECTION(alice, "alice", "secret", "AlicePhone") - CREATE_CONNECTION(bob, "bob", "secret", "BobPhone") + CREATE_CONNECTION(alice, "alice9", "secret", "AlicePhone") + CREATE_CONNECTION(bob, "bob4", "secret", "BobPhone") QString joinedRoomId; @@ -547,7 +507,7 @@ void TestOlmAccount::enableEncryption() QVERIFY(spy.wait(10000)); QVERIFY(spy1.wait(10000)); }); - + alice->sync(); QSignalSpy spy(job, &BaseJob::result); QVERIFY(spy.wait(10000)); @@ -558,14 +518,9 @@ void TestOlmAccount::enableEncryption() bool hasEncryption = false; for (auto it = events.rbegin(); it != events.rend(); ++it) { const auto& event = it->event(); - if (eventCast(event)) { + if (is(*event)) { hasEncryption = true; - } else { - qDebug() << event->matrixType() << typeId() - << event->type(); - if (is(*event)) { - qDebug() << event->contentJson(); - } + break; } } QVERIFY(bob->room(joinedRoomId)->usesEncryption()); diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index f1f80454..367092f6 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -28,6 +28,5 @@ private Q_SLOTS: void queryTest(); void claimKeys(); void claimMultipleKeys(); - void keyChange(); void enableEncryption(); }; -- cgit v1.2.3 From 110ca4b01ae86216ee8c03cd2b4eda5ac351df2a Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 24 Feb 2022 15:53:15 +0100 Subject: Fix test --- autotests/run-tests.sh | 1 - autotests/testolmaccount.cpp | 52 ++++++++++++++++---------------------------- autotests/testolmaccount.h | 3 +++ 3 files changed, 22 insertions(+), 34 deletions(-) (limited to 'autotests') diff --git a/autotests/run-tests.sh b/autotests/run-tests.sh index 74a8c544..0d58e460 100755 --- a/autotests/run-tests.sh +++ b/autotests/run-tests.sh @@ -28,7 +28,6 @@ echo Register bob docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob1 -p secret -c /data/homeserver.yaml https://localhost:8008' docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob2 -p secret -c /data/homeserver.yaml https://localhost:8008' docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob3 -p secret -c /data/homeserver.yaml https://localhost:8008' -docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob4 -p secret -c /data/homeserver.yaml https://localhost:8008' echo Register carl docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008' diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 033fff32..04e23db6 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -490,44 +490,30 @@ void TestOlmAccount::claimMultipleKeys() void TestOlmAccount::enableEncryption() { CREATE_CONNECTION(alice, "alice9", "secret", "AlicePhone") - CREATE_CONNECTION(bob, "bob4", "secret", "BobPhone") - QString joinedRoomId; - - auto job = alice->createRoom(Connection::PublishRoom, {}, {}, {}, - { "@bob:localhost" }); + auto job = alice->createRoom(Connection::PublishRoom, {}, {}, {}, {}); + bool encryptedEmitted = false; connect(alice.get(), &Connection::newRoom, this, - [alice, bob, &joinedRoomId](Quotient::Room* room) { - room->activateEncryption(); - QSignalSpy spy(room, &Room::encryption); - - joinedRoomId = room->id(); - auto job = bob->joinRoom(room->id()); - QSignalSpy spy1(job, &BaseJob::result); - QVERIFY(spy.wait(10000)); - QVERIFY(spy1.wait(10000)); + [alice, this, &encryptedEmitted](Quotient::Room* room) { + room->activateEncryption(); + connect(room, &Room::encryption, this, [&encryptedEmitted, this](){ + encryptedEmitted = true; + Q_EMIT enableEncryptionFinished(); + }); + connect(alice.get(), &Connection::syncDone, this, [alice, room](){ + if (!room->usesEncryption()) { + alice->sync(); + } }); + alice->sync(); + }); + QSignalSpy createRoomSpy(job, &BaseJob::success); + QVERIFY(createRoomSpy.wait(10000)); alice->sync(); - QSignalSpy spy(job, &BaseJob::result); - QVERIFY(spy.wait(10000)); - bob->sync(); - connect(bob.get(), &Connection::syncDone, this, [bob, joinedRoomId] { - const auto* joinedRoom = bob->room(joinedRoomId); - const auto& events = joinedRoom->messageEvents(); - bool hasEncryption = false; - for (auto it = events.rbegin(); it != events.rend(); ++it) { - const auto& event = it->event(); - if (is(*event)) { - hasEncryption = true; - break; - } - } - QVERIFY(bob->room(joinedRoomId)->usesEncryption()); - QVERIFY(hasEncryption); - }); - QSignalSpy spy2(bob.get(), &Connection::syncDone); - QVERIFY(spy2.wait(10000)); + QSignalSpy finishedSpy(this, &TestOlmAccount::enableEncryptionFinished); + QVERIFY(finishedSpy.wait(10000)); + QVERIFY(encryptedEmitted); } QTEST_GUILESS_MAIN(TestOlmAccount) diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index 367092f6..1d3da837 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -13,6 +13,9 @@ class TestOlmAccount : public QObject { Q_OBJECT +Q_SIGNALS: + void enableEncryptionFinished(); + private Q_SLOTS: void pickleUnpickledTest(); void identityKeysValid(); -- cgit v1.2.3 From cfa64f86da6fcfe04947a634a208705543824810 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 24 Feb 2022 17:21:46 +0100 Subject: Fix all tests --- autotests/testolmaccount.cpp | 51 ++++++++++++++++++-------------------------- autotests/testolmaccount.h | 3 --- lib/connection.cpp | 2 +- lib/database.cpp | 4 ++-- lib/database.h | 2 +- 5 files changed, 25 insertions(+), 37 deletions(-) (limited to 'autotests') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 04e23db6..9989665a 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -439,6 +439,7 @@ void TestOlmAccount::claimMultipleKeys() QCOMPARE(res->oneTimeKeyCounts().value(SignedCurve25519Key), 10); }); alice->run(res); + QVERIFY(spy.wait(10000)); auto olm1 = alice1->olmAccount(); olm1->generateOneTimeKeys(10); @@ -449,6 +450,7 @@ void TestOlmAccount::claimMultipleKeys() QCOMPARE(res1->oneTimeKeyCounts().value(SignedCurve25519Key), 10); }); alice1->run(res1); + QVERIFY(spy1.wait(10000)); auto olm2 = alice2->olmAccount(); olm2->generateOneTimeKeys(10); @@ -459,10 +461,7 @@ void TestOlmAccount::claimMultipleKeys() QCOMPARE(res2->oneTimeKeyCounts().value(SignedCurve25519Key), 10); }); alice2->run(res2); - - QVERIFY(spy.wait(10000)); - QVERIFY(spy1.wait(10000)); - QVERIFY(spy2.wait(1000)); // TODO this is failing even with 10000 + QVERIFY(spy2.wait(10000)); // Bob will claim all keys from alice CREATE_CONNECTION(bob, "bob3", "secret", "BobPhone") @@ -473,18 +472,17 @@ void TestOlmAccount::claimMultipleKeys() << alice2->deviceId(); QHash> oneTimeKeys; + oneTimeKeys[alice->userId()] = QHash(); for (const auto &d : devices_) { - oneTimeKeys[alice->userId()] = QHash(); oneTimeKeys[alice->userId()][d] = SignedCurve25519Key; } auto job = bob->callApi(oneTimeKeys); - connect(job, &BaseJob::result, this, [bob, job] { - const auto userId = bob->userId(); + QSignalSpy jobSpy(job, &BaseJob::finished); + QVERIFY(jobSpy.wait(10000)); + const auto userId = alice->userId(); - // The device exists. - QCOMPARE(job->oneTimeKeys().size(), 1); - QCOMPARE(job->oneTimeKeys().value(userId).size(), 3); - }); + QCOMPARE(job->oneTimeKeys().size(), 1); + QCOMPARE(job->oneTimeKeys().value(userId).size(), 3); } void TestOlmAccount::enableEncryption() @@ -492,28 +490,21 @@ void TestOlmAccount::enableEncryption() CREATE_CONNECTION(alice, "alice9", "secret", "AlicePhone") auto job = alice->createRoom(Connection::PublishRoom, {}, {}, {}, {}); - bool encryptedEmitted = false; - connect(alice.get(), &Connection::newRoom, this, - [alice, this, &encryptedEmitted](Quotient::Room* room) { - room->activateEncryption(); - connect(room, &Room::encryption, this, [&encryptedEmitted, this](){ - encryptedEmitted = true; - Q_EMIT enableEncryptionFinished(); - }); - connect(alice.get(), &Connection::syncDone, this, [alice, room](){ - if (!room->usesEncryption()) { - alice->sync(); - } - }); - alice->sync(); - }); QSignalSpy createRoomSpy(job, &BaseJob::success); QVERIFY(createRoomSpy.wait(10000)); alice->sync(); - - QSignalSpy finishedSpy(this, &TestOlmAccount::enableEncryptionFinished); - QVERIFY(finishedSpy.wait(10000)); - QVERIFY(encryptedEmitted); + connect(alice.get(), &Connection::syncDone, this, [alice](){ + qDebug() << "foo"; + alice->sync(); + }); + while(alice->roomsCount(JoinState::Join) == 0) { + QThread::sleep(100); + } + auto room = alice->rooms(JoinState::Join)[0]; + room->activateEncryption(); + QSignalSpy encryptionSpy(room, &Room::encryption); + QVERIFY(encryptionSpy.wait(10000)); + QVERIFY(room->usesEncryption()); } QTEST_GUILESS_MAIN(TestOlmAccount) diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index 1d3da837..367092f6 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -13,9 +13,6 @@ class TestOlmAccount : public QObject { Q_OBJECT -Q_SIGNALS: - void enableEncryptionFinished(); - private Q_SLOTS: void pickleUnpickledTest(); void identityKeysValid(); diff --git a/lib/connection.cpp b/lib/connection.cpp index 1ef2495d..0ef27486 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -579,7 +579,7 @@ void Connection::Private::completeSetup(const QString& mxId) picklingMode = Encrypted { job.binaryData() }; } - database = new Database(data->userId(), q); + database = new Database(data->userId(), data->deviceId(), q); // init olmAccount olmAccount = std::make_unique(data->userId(), data->deviceId(), q); diff --git a/lib/database.cpp b/lib/database.cpp index b91b6ef1..84c93046 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -15,7 +15,7 @@ #include "e2ee/qolminboundsession.h" using namespace Quotient; -Database::Database(const QString& matrixId, QObject* parent) +Database::Database(const QString& matrixId, const QString& deviceId, QObject* parent) : QObject(parent) , m_matrixId(matrixId) { @@ -23,7 +23,7 @@ Database::Database(const QString& matrixId, QObject* parent) QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("Quotient_%1").arg(m_matrixId)); QString databasePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/%1").arg(m_matrixId); QDir(databasePath).mkpath(databasePath); - database().setDatabaseName(databasePath + QStringLiteral("/quotient.db3")); + database().setDatabaseName(databasePath + QStringLiteral("/quotient_%1.db3").arg(deviceId)); database().open(); switch(version()) { diff --git a/lib/database.h b/lib/database.h index d244dc0b..d4d5fb56 100644 --- a/lib/database.h +++ b/lib/database.h @@ -14,7 +14,7 @@ class QUOTIENT_API Database : public QObject { Q_OBJECT public: - Database(const QString& matrixId, QObject* parent); + Database(const QString& matrixId, const QString& deviceId, QObject* parent); int version(); void transaction(); -- cgit v1.2.3