From 3849a2466208a741165385f20150bddad59d2b8c Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 22 Jan 2021 23:36:47 +0100 Subject: Start implementing Qt olm binding --- CMakeLists.txt | 3 +- lib/olm/qolmaccount.cpp | 173 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/olm/qolmaccount.h | 89 +++++++++++++++++++++++++ 3 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 lib/olm/qolmaccount.cpp create mode 100644 lib/olm/qolmaccount.h diff --git a/CMakeLists.txt b/CMakeLists.txt index aa3b9c98..b4d4ef8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,6 +171,7 @@ list(APPEND lib_SRCS lib/jobs/syncjob.cpp lib/jobs/mediathumbnailjob.cpp lib/jobs/downloadfilejob.cpp + lib/olm/qolmaccount.cpp ) # Configure API files generation @@ -317,7 +318,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC $ ) if (${PROJECT_NAME}_ENABLE_E2EE) - target_link_libraries(${PROJECT_NAME} QtOlm) + target_link_libraries(${PROJECT_NAME} Olm::Olm QtOlm) set(FIND_DEPS "find_dependency(QtOlm)") # For QuotientConfig.cmake.in endif() target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp new file mode 100644 index 00000000..673f613b --- /dev/null +++ b/lib/olm/qolmaccount.cpp @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "qolmaccount.h" +#include +#include +#include +#include + +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]; +} + +// Convert PicklingMode to key +QByteArray toKey(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; +} + +// Conver olm error to enum +QOlmAccount::OlmAccountError lastError(OlmAccount *account) { + const std::string error_raw = olm_account_last_error(account); + + if (error_raw.compare("BAD_ACCOUNT_KEY")) { + return QOlmAccount::OlmAccountError::BadAccountKey; + } else if (error_raw.compare("BAD_MESSAGE_KEY_ID")) { + return QOlmAccount::OlmAccountError::BadMessageKeyId; + } else if (error_raw.compare("INVALID_BASE64")) { + return QOlmAccount::OlmAccountError::InvalidBase64; + } else if (error_raw.compare("NOT_ENOUGH_RANDOM")) { + return QOlmAccount::OlmAccountError::NotEnoughRandom; + } else if (error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { + return QOlmAccount::OlmAccountError::OutputBufferTooSmall; + } else { + return QOlmAccount::OlmAccountError::Unknown; + } +} + +QByteArray getRandom(size_t bufferSize) +{ + QByteArray buffer(bufferSize, '0'); + std::generate(buffer.begin(), buffer.end(), std::rand); + return buffer; +} + +QOlmAccount::QOlmAccount(OlmAccount *account) + : m_account(account) +{} + +std::optional QOlmAccount::create() +{ + auto account = olm_account(new uint8_t[olm_account_size()]); + size_t randomSize = olm_create_account_random_length(account); + QByteArray randomData = getRandom(randomSize); + const auto error = olm_create_account(account, randomData.data(), randomSize); + if (error == olm_error()) { + return std::nullopt; + } + return std::make_optional(account); +} + +std::variant QOlmAccount::unpickle(QByteArray pickled, PicklingMode mode) +{ + auto 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()); + if (error == olm_error()) { + return lastError(account); + } + return QOlmAccount(account); +} + +std::variant QOlmAccount::pickle(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; +} + +std::variant QOlmAccount::identityKeys() +{ + 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); + } + const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object(); + return IdentityKeys { + key.value(QStringLiteral("curve25519")).toString().toUtf8(), + key.value(QStringLiteral("ed25519")).toString().toUtf8() + }; +} + +std::variant QOlmAccount::sign(QString 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()) { + return lastError(m_account); + } + return QString::fromUtf8(signatureBuffer); +} + +size_t QOlmAccount::maxNumberOfOneTimeKeys() const +{ + return olm_account_max_number_of_one_time_keys(m_account); +} + +std::optional 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); + } + return std::nullopt; +} + +std::variant 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); + } + 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; +} + +#endif diff --git a/lib/olm/qolmaccount.h b/lib/olm/qolmaccount.h new file mode 100644 index 00000000..219d7e48 --- /dev/null +++ b/lib/olm/qolmaccount.h @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later +#pragma once + +#include +#include +#include +#include +#include +#include "olm/olm.h" + +struct OlmAccount; + +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 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; +}; + +bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); + +//! An olm account manages all cryptographic keys used on a device. +//! \code{.cpp} +//! const auto olmAccount = new QOlmAccount(this); +//! \endcode +class QOlmAccount +{ +public: + enum OlmAccountError { + BadAccountKey, + BadMessageKeyId, + InvalidBase64, + NotEnoughRandom, + OutputBufferTooSmall, + Unknown, + }; + + //! 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, PicklingMode mode); + + //! Serialises an OlmAccount to encrypted Base64. + std::variant pickle(PicklingMode mode); + std::variant identityKeys(); + + //! Returns the signature of the supplied message. + std::variant sign(QString message) 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; + + //! Gets the OlmAccount's one time keys formatted as JSON. + std::variant oneTimeKeys() const; + + // HACK do not use directly + QOlmAccount(OlmAccount *account); +private: + OlmAccount *m_account; +}; -- cgit v1.2.3 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 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 c794d61b161ad48312bf079f1e5f483cfac1dc38 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 22 Jan 2021 23:45:34 +0100 Subject: Add destructor --- lib/olm/qolmaccount.cpp | 5 +++++ lib/olm/qolmaccount.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp index 673f613b..a6a07962 100644 --- a/lib/olm/qolmaccount.cpp +++ b/lib/olm/qolmaccount.cpp @@ -66,6 +66,11 @@ QOlmAccount::QOlmAccount(OlmAccount *account) : m_account(account) {} +QOlmAccount::~QOlmAccount() +{ + olm_clear_account(m_account); +} + std::optional QOlmAccount::create() { auto account = olm_account(new uint8_t[olm_account_size()]); diff --git a/lib/olm/qolmaccount.h b/lib/olm/qolmaccount.h index 219d7e48..268cd5d5 100644 --- a/lib/olm/qolmaccount.h +++ b/lib/olm/qolmaccount.h @@ -49,6 +49,8 @@ bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); class QOlmAccount { public: + ~QOlmAccount(); + enum OlmAccountError { BadAccountKey, BadMessageKeyId, -- cgit v1.2.3 From e2075a1f33f7987385fc61338ce1756715fdaf6a Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sat, 23 Jan 2021 15:57:39 +0100 Subject: Start inboundsession wrapper --- lib/olm/errors.h | 46 ++++++++++++++++++++++++++++++++++++++++++ lib/olm/qolminboundsession.cpp | 30 +++++++++++++++++++++++++++ lib/olm/qolminboundsession.h | 21 +++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 lib/olm/errors.h create mode 100644 lib/olm/qolminboundsession.cpp create mode 100644 lib/olm/qolminboundsession.h diff --git a/lib/olm/errors.h b/lib/olm/errors.h new file mode 100644 index 00000000..e51400ef --- /dev/null +++ b/lib/olm/errors.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +//! All errors that could be caused by an operation regarding an `QOlmAccount`. +//! Errors are named exactly like the ones in libolm. +enum OlmAccountError { + BadAccountKey, + BadMessageKeyId, + InvalidBase64, + NotEnoughRandom, + OutputBufferTooSmall, + Unknown, +}; + +//! All errors that could be caused by an operation regarding an `QOlmSession`. +//! Errors are named exactly like the ones in libolm. +enum OlmSessionError { + BadAccountKey, + BadMessageFormat, + BadMessageKeyId, + BadMessageMac, + BadMessageVersion, + InvalidBase64, + NotEnoughRandom, + OutputBufferTooSmall, + Unknown, +}; + +//! All errors that could be caused by an operation +//! regarding QOlmOutboundGroupSession and QOlmInboundGroupSession. +//! Errors are named exactly like the ones in libolm. +enum OlmGroupSessionError { + BadAccountKey, + BadMessageFormat, + BadMessageMac, + BadMessageVersion, + BadSessionKey, + InvalidBase64, + NotEnoughRandom, + OutputBufferTooSmall, + UnknownMessageIndex, + Unknown, +}; diff --git a/lib/olm/qolminboundsession.cpp b/lib/olm/qolminboundsession.cpp new file mode 100644 index 00000000..fbcaa802 --- /dev/null +++ b/lib/olm/qolminboundsession.cpp @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include + +std::variant QOlmInboundGroupSession::create(const QString &key) +{ + auto olmInboundGroupSessionBuf = QByteArray(olm_inbound_group_session_size(), '0'); + + const auto olmInboundGroupSession = olm_inbound_group_session(olmInboundGroupSessionBuf.data()); + + QByteArray keyBuf = key.toUtf8(); + + const auto error = olm_init_inbound_group_session(olmInboundGroupSession, keyBuf.data(), keyBuf.size()); + + if (error == olm_error()) { + return + } + + if create_error == errors::olm_error() { + Err(Self::last_error(olm_inbound_group_session_ptr)) + } else { + Ok(OlmInboundGroupSession { + group_session_ptr: olm_inbound_group_session_ptr, + group_session_buf: olm_inbound_group_session_buf, + }) + } + +} diff --git a/lib/olm/qolminboundsession.h b/lib/olm/qolminboundsession.h new file mode 100644 index 00000000..520f8b68 --- /dev/null +++ b/lib/olm/qolminboundsession.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include + +//! An in-bound group session is responsible for decrypting incoming +//! communication in a Megolm session. +struct QOlmInboundGroupSession +{ +public: + //! Creates a new instance of `OlmInboundGroupSession`. + static std::variant create(const QString &key); +private: + OlmInboundGroupSession *m_groupSession + QByteArray m_buffer; +}; + -- cgit v1.2.3 From c8d67f737e84bbec98a54fc19a8aa56dbc39d542 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sat, 23 Jan 2021 21:46:26 +0100 Subject: Implement Inboundsession --- lib/olm/e2ee.h | 71 +++++++++++++++++++ lib/olm/errors.cpp | 17 +++++ lib/olm/errors.h | 39 +++------- lib/olm/qolmaccount.cpp | 41 ++++++----- lib/olm/qolmaccount.h | 61 ++++------------ lib/olm/qolminboundsession.cpp | 157 ++++++++++++++++++++++++++++++++++++++--- lib/olm/qolminboundsession.h | 29 +++++++- 7 files changed, 307 insertions(+), 108 deletions(-) create mode 100644 lib/olm/e2ee.h create mode 100644 lib/olm/errors.cpp diff --git a/lib/olm/e2ee.h b/lib/olm/e2ee.h new file mode 100644 index 00000000..40ab56c7 --- /dev/null +++ b/lib/olm/e2ee.h @@ -0,0 +1,71 @@ +// 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 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; +}; + +bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); + +} // namespace Quotient diff --git a/lib/olm/errors.cpp b/lib/olm/errors.cpp new file mode 100644 index 00000000..fce177c6 --- /dev/null +++ b/lib/olm/errors.cpp @@ -0,0 +1,17 @@ +#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; + } +} diff --git a/lib/olm/errors.h b/lib/olm/errors.h index e51400ef..fc2ae2e9 100644 --- a/lib/olm/errors.h +++ b/lib/olm/errors.h @@ -2,22 +2,16 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#pragma once +#ifndef QUOTIENT_OLM_ERROR_H +#define QUOTIENT_OLM_ERROR_H -//! All errors that could be caused by an operation regarding an `QOlmAccount`. -//! Errors are named exactly like the ones in libolm. -enum OlmAccountError { - BadAccountKey, - BadMessageKeyId, - InvalidBase64, - NotEnoughRandom, - OutputBufferTooSmall, - Unknown, -}; +#include -//! All errors that could be caused by an operation regarding an `QOlmSession`. +namespace Quotient { +//! All errors that could be caused by an operation regarding Olm //! Errors are named exactly like the ones in libolm. -enum OlmSessionError { +enum OlmError +{ BadAccountKey, BadMessageFormat, BadMessageKeyId, @@ -26,21 +20,10 @@ enum OlmSessionError { InvalidBase64, NotEnoughRandom, OutputBufferTooSmall, - Unknown, -}; - -//! All errors that could be caused by an operation -//! regarding QOlmOutboundGroupSession and QOlmInboundGroupSession. -//! Errors are named exactly like the ones in libolm. -enum OlmGroupSessionError { - BadAccountKey, - BadMessageFormat, - BadMessageMac, - BadMessageVersion, - BadSessionKey, - InvalidBase64, - NotEnoughRandom, - OutputBufferTooSmall, UnknownMessageIndex, Unknown, }; + +} //namespace Quotient + +#endif diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp index a6a07962..bde9b712 100644 --- a/lib/olm/qolmaccount.cpp +++ b/lib/olm/qolmaccount.cpp @@ -9,6 +9,8 @@ #include #include +using namespace Quotient; + QMap OneTimeKeys::curve25519() const { return keys[QStringLiteral("curve25519")]; @@ -23,7 +25,7 @@ std::optional> OneTimeKeys::get(QString keyType) const } // Convert PicklingMode to key -QByteArray toKey(PicklingMode mode) +QByteArray toKey(const PicklingMode &mode) { if (std::holds_alternative(mode)) { return ""; @@ -36,25 +38,30 @@ bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) return lhs.curve25519 == rhs.curve25519 &&& lhs.ed25519 == rhs.ed25519; } -// Conver olm error to enum -QOlmAccount::OlmAccountError lastError(OlmAccount *account) { - const std::string error_raw = olm_account_last_error(account); - +// TODO use impl from errors.cpp +OlmError fromString(const std::string &error_raw) { if (error_raw.compare("BAD_ACCOUNT_KEY")) { - return QOlmAccount::OlmAccountError::BadAccountKey; + return OlmError::BadAccountKey; } else if (error_raw.compare("BAD_MESSAGE_KEY_ID")) { - return QOlmAccount::OlmAccountError::BadMessageKeyId; + return OlmError::BadMessageKeyId; } else if (error_raw.compare("INVALID_BASE64")) { - return QOlmAccount::OlmAccountError::InvalidBase64; + return OlmError::InvalidBase64; } else if (error_raw.compare("NOT_ENOUGH_RANDOM")) { - return QOlmAccount::OlmAccountError::NotEnoughRandom; + return OlmError::NotEnoughRandom; } else if (error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { - return QOlmAccount::OlmAccountError::OutputBufferTooSmall; + return OlmError::OutputBufferTooSmall; } else { - return QOlmAccount::OlmAccountError::Unknown; + return OlmError::Unknown; } } +// Conver 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'); @@ -83,7 +90,7 @@ std::optional QOlmAccount::create() return std::make_optional(account); } -std::variant QOlmAccount::unpickle(QByteArray pickled, PicklingMode mode) +std::variant QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) { auto account = olm_account(new uint8_t[olm_account_size()]); const QByteArray key = toKey(mode); @@ -94,7 +101,7 @@ std::variant QOlmAccount::unpickle(QB return QOlmAccount(account); } -std::variant QOlmAccount::pickle(PicklingMode mode) +std::variant QOlmAccount::pickle(const PicklingMode &mode) { const QByteArray key = toKey(mode); const size_t pickleLength = olm_pickle_account_length(m_account); @@ -107,7 +114,7 @@ std::variant QOlmAccount::pickle(Pickl return pickleBuffer; } -std::variant QOlmAccount::identityKeys() +std::variant QOlmAccount::identityKeys() { const size_t keyLength = olm_account_identity_keys_length(m_account); QByteArray keyBuffer(keyLength, '0'); @@ -122,7 +129,7 @@ std::variant QOlmAccount::identityKe }; } -std::variant QOlmAccount::sign(QString message) const +std::variant QOlmAccount::sign(const QString &message) const { const size_t signatureLength = olm_account_signature_length(m_account); QByteArray signatureBuffer(signatureLength, '0'); @@ -140,7 +147,7 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const return olm_account_max_number_of_one_time_keys(m_account); } -std::optional QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const +std::optional 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); @@ -152,7 +159,7 @@ std::optional QOlmAccount::generateOneTimeKeys(siz return std::nullopt; } -std::variant QOlmAccount::oneTimeKeys() const +std::variant QOlmAccount::oneTimeKeys() const { const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0'); diff --git a/lib/olm/qolmaccount.h b/lib/olm/qolmaccount.h index 268cd5d5..3ce1fb9a 100644 --- a/lib/olm/qolmaccount.h +++ b/lib/olm/qolmaccount.h @@ -3,44 +3,14 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once -#include -#include -#include -#include -#include +#include "olm/e2ee.h" +#include "olm/errors.h" #include "olm/olm.h" +#include struct OlmAccount; -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 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; -}; - -bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); +namespace Quotient { //! An olm account manages all cryptographic keys used on a device. //! \code{.cpp} @@ -51,41 +21,34 @@ class QOlmAccount public: ~QOlmAccount(); - enum OlmAccountError { - BadAccountKey, - BadMessageKeyId, - InvalidBase64, - NotEnoughRandom, - OutputBufferTooSmall, - Unknown, - }; - //! 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, PicklingMode mode); + static std::variant unpickle(QByteArray &picked, const PicklingMode &mode); //! Serialises an OlmAccount to encrypted Base64. - std::variant pickle(PicklingMode mode); - std::variant identityKeys(); + std::variant pickle(const PicklingMode &mode); + std::variant identityKeys(); //! Returns the signature of the supplied message. - std::variant sign(QString message) const; + std::variant sign(const QString &message) 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; + std::optional generateOneTimeKeys(size_t numberOfKeys) const; //! Gets the OlmAccount's one time keys formatted as JSON. - std::variant oneTimeKeys() const; + std::variant oneTimeKeys() const; // HACK do not use directly QOlmAccount(OlmAccount *account); private: OlmAccount *m_account; }; + +} // namespace Quotient diff --git a/lib/olm/qolminboundsession.cpp b/lib/olm/qolminboundsession.cpp index fbcaa802..62de138f 100644 --- a/lib/olm/qolminboundsession.cpp +++ b/lib/olm/qolminboundsession.cpp @@ -2,9 +2,42 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include +#include "olm/qolminboundsession.h" +#include +#include -std::variant QOlmInboundGroupSession::create(const QString &key) +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); + + return fromString(error_raw); +} + +QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session, const QByteArray &buffer) + : m_groupSession(session) + , m_buffer(buffer) +{ +} + +std::variant QOlmInboundGroupSession::create(const QString &key) { auto olmInboundGroupSessionBuf = QByteArray(olm_inbound_group_session_size(), '0'); @@ -12,19 +45,121 @@ std::variant QOlmInboundGroupSe QByteArray keyBuf = key.toUtf8(); - const auto error = olm_init_inbound_group_session(olmInboundGroupSession, keyBuf.data(), keyBuf.size()); + const auto error = olm_init_inbound_group_session(olmInboundGroupSession, + reinterpret_cast(keyBuf.data()), keyBuf.size()); if (error == olm_error()) { - return + return lastError(olmInboundGroupSession); } - if create_error == errors::olm_error() { - Err(Self::last_error(olm_inbound_group_session_ptr)) - } else { - Ok(OlmInboundGroupSession { - group_session_ptr: olm_inbound_group_session_ptr, - group_session_buf: olm_inbound_group_session_buf, - }) + return QOlmInboundGroupSession(olmInboundGroupSession, olmInboundGroupSessionBuf); +} + + +std::variant QOlmInboundGroupSession::import(const QString &key) +{ + auto olmInboundGroupSessionBuf = QByteArray(olm_inbound_group_session_size(), '0'); + const auto olmInboundGroupSession = olm_inbound_group_session(olmInboundGroupSessionBuf.data()); + QByteArray keyBuf = key.toUtf8(); + + const auto error = olm_import_inbound_group_session(olmInboundGroupSession, + reinterpret_cast(keyBuf.data()), keyBuf.size()); + if (error == olm_error()) { + return lastError(olmInboundGroupSession); + } + + return QOlmInboundGroupSession(olmInboundGroupSession, olmInboundGroupSessionBuf); +} + +QByteArray toKey(const PicklingMode &mode) +{ + if (std::holds_alternative(mode)) { + return ""; + } + return std::get(mode).key; +} + +std::variant 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()) { + return lastError(m_groupSession); } + return pickledBuf; +} + +std::variant QOlmInboundGroupSession::unpicke(QByteArray &picked, const PicklingMode &mode) +{ + QByteArray groupSessionBuf(olm_inbound_group_session_size(), '0'); + auto groupSession = olm_inbound_group_session(groupSessionBuf.data()); + const QByteArray key = toKey(mode); + const auto error = olm_unpickle_inbound_group_session(groupSession, key.data(), key.length(), picked.data(), picked.size()); + if (error == olm_error()) { + return lastError(groupSession); + } + return QOlmInboundGroupSession(groupSession, groupSessionBuf); +} + +std::variant, OlmError> QOlmInboundGroupSession::decrypt(QString &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 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(); + + const auto plaintextLen = olm_group_decrypt(m_groupSession, reinterpret_cast(messageBuf.data()), + messageLen, reinterpret_cast(plaintextBuf.data()), plaintextMaxLen, &messageIndex); + + // Error code or plaintext length is returned + const auto decryptError = plaintextLen; + + if (decryptError == olm_error()) { + return lastError(m_groupSession); + } + + plaintextBuf.truncate(plaintextLen); + return std::make_pair(QString(plaintextBuf), 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); +} + +std::variant 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()) { + return lastError(m_groupSession); + } + return sessionIdBuf; +} + +bool QOlmInboundGroupSession::isVerified() const +{ + return olm_inbound_group_session_is_verified(m_groupSession) != 0; } diff --git a/lib/olm/qolminboundsession.h b/lib/olm/qolminboundsession.h index 520f8b68..c75b7285 100644 --- a/lib/olm/qolminboundsession.h +++ b/lib/olm/qolminboundsession.h @@ -6,6 +6,11 @@ #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. @@ -13,9 +18,27 @@ struct QOlmInboundGroupSession { public: //! Creates a new instance of `OlmInboundGroupSession`. - static std::variant create(const QString &key); + static std::variant create(const QString &key); + //! Import an inbound group session, from a previous export. + static std::variant import(const QString &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); + //! 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, + //! 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. + std::variant sessionId() const; + bool isVerified() const; private: - OlmInboundGroupSession *m_groupSession + QOlmInboundGroupSession(OlmInboundGroupSession *session, const QByteArray &buffer); + OlmInboundGroupSession *m_groupSession; QByteArray m_buffer; }; - +} // namespace Quotient -- 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 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 5b072f7d2519df4c60cc6ae4ce728e99a09d8d44 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 24 Jan 2021 17:59:31 +0100 Subject: ifdef everything --- lib/olm/errors.cpp | 4 ++++ lib/olm/errors.h | 4 ++-- lib/olm/qolmaccount.h | 3 +++ lib/olm/qolminboundsession.cpp | 2 ++ lib/olm/qolminboundsession.h | 3 +++ lib/olm/qolmoutboundsession.cpp | 5 ++++- lib/olm/qolmoutboundsession.h | 2 ++ lib/olm/utils.cpp | 2 ++ lib/olm/utils.h | 2 ++ 9 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/olm/errors.cpp b/lib/olm/errors.cpp index fce177c6..a687e807 100644 --- a/lib/olm/errors.cpp +++ b/lib/olm/errors.cpp @@ -1,3 +1,6 @@ +// 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) { @@ -15,3 +18,4 @@ Quotient::OlmError Quotient::fromString(const std::string &error_raw) { return OlmError::Unknown; } } +#endif diff --git a/lib/olm/errors.h b/lib/olm/errors.h index 3dc4c796..09d2a989 100644 --- a/lib/olm/errors.h +++ b/lib/olm/errors.h @@ -2,9 +2,9 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifndef QUOTIENT_OLM_ERROR_H -#define QUOTIENT_OLM_ERROR_H +#pragma once +#ifdef Quotient_E2EE_ENABLED #include namespace Quotient { diff --git a/lib/olm/qolmaccount.h b/lib/olm/qolmaccount.h index 3ce1fb9a..c478c781 100644 --- a/lib/olm/qolmaccount.h +++ b/lib/olm/qolmaccount.h @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once +#ifdef Quotient_E2EE_ENABLED #include "olm/e2ee.h" #include "olm/errors.h" @@ -52,3 +53,5 @@ private: }; } // namespace Quotient + +#endif diff --git a/lib/olm/qolminboundsession.cpp b/lib/olm/qolminboundsession.cpp index 37dd60f8..f0ca73c4 100644 --- a/lib/olm/qolminboundsession.cpp +++ b/lib/olm/qolminboundsession.cpp @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later +#ifdef Quotient_E2EE_ENABLED #include "olm/qolminboundsession.h" #include #include @@ -150,3 +151,4 @@ 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 index 82802520..85807821 100644 --- a/lib/olm/qolminboundsession.h +++ b/lib/olm/qolminboundsession.h @@ -4,6 +4,8 @@ #pragma once +#ifdef Quotient_E2EE_ENABLED + #include #include #include "olm/olm.h" @@ -43,3 +45,4 @@ private: QByteArray m_buffer; }; } // namespace Quotient +#endif diff --git a/lib/olm/qolmoutboundsession.cpp b/lib/olm/qolmoutboundsession.cpp index 8a6b966b..60126469 100644 --- a/lib/olm/qolmoutboundsession.cpp +++ b/lib/olm/qolmoutboundsession.cpp @@ -1,7 +1,8 @@ // 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" @@ -119,3 +120,5 @@ std::variant QOlmOutboundGroupSession::sessionKey() const } return keyBuffer; } + +#endif diff --git a/lib/olm/qolmoutboundsession.h b/lib/olm/qolmoutboundsession.h index 147c0e03..41eb874a 100644 --- a/lib/olm/qolmoutboundsession.h +++ b/lib/olm/qolmoutboundsession.h @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once +#ifdef Quotient_E2EE_ENABLED #include "olm/olm.h" // from Olm #include "olm/errors.h" @@ -45,3 +46,4 @@ private: QByteArray m_buffer; }; } +#endif diff --git a/lib/olm/utils.cpp b/lib/olm/utils.cpp index 4966af15..15def1d7 100644 --- a/lib/olm/utils.cpp +++ b/lib/olm/utils.cpp @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later +#ifdef Quotient_E2EE_ENABLED #include "olm/utils.h" using namespace Quotient; @@ -20,3 +21,4 @@ QByteArray Quotient::getRandom(size_t bufferSize) std::generate(buffer.begin(), buffer.end(), std::rand); return buffer; } +#endif diff --git a/lib/olm/utils.h b/lib/olm/utils.h index ec0da784..85d4605b 100644 --- a/lib/olm/utils.h +++ b/lib/olm/utils.h @@ -3,6 +3,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once +#ifdef Quotient_E2EE_ENABLED #include "olm/e2ee.h" @@ -11,3 +12,4 @@ namespace Quotient { QByteArray toKey(const PicklingMode &mode); QByteArray getRandom(size_t bufferSize); } +#endif -- cgit v1.2.3 From 0b2b70ac815f48fd83424c282fcf50ef97b08a7b Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 24 Jan 2021 18:02:09 +0100 Subject: Fix documentation typos Co-authored-by: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> --- lib/olm/e2ee.h | 2 +- lib/olm/qolmaccount.cpp | 2 +- lib/olm/qolmoutboundsession.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/olm/e2ee.h b/lib/olm/e2ee.h index 40ab56c7..1dee0e42 100644 --- a/lib/olm/e2ee.h +++ b/lib/olm/e2ee.h @@ -54,7 +54,7 @@ struct IdentityKeys QByteArray ed25519; }; -//! Struct representing the the one-time keys. +//! Struct representing the one-time keys. struct OneTimeKeys { QMap> keys; diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp index 89d82832..742d7d18 100644 --- a/lib/olm/qolmaccount.cpp +++ b/lib/olm/qolmaccount.cpp @@ -30,7 +30,7 @@ bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) return lhs.curve25519 == rhs.curve25519 &&& lhs.ed25519 == rhs.ed25519; } -// Conver olm error to enum +// Convert olm error to enum OlmError lastError(OlmAccount *account) { const std::string error_raw = olm_account_last_error(account); diff --git a/lib/olm/qolmoutboundsession.h b/lib/olm/qolmoutboundsession.h index 41eb874a..2e1439d3 100644 --- a/lib/olm/qolmoutboundsession.h +++ b/lib/olm/qolmoutboundsession.h @@ -21,7 +21,7 @@ public: //! Serialises an `QOlmOutboundGroupSession` to encrypted Base64. std::variant pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by - //! pickling an `QOlmOutboundGroupSession`. + //! pickling a `QOlmOutboundGroupSession`. static std::variant unpickle(QByteArray &pickled, const PicklingMode &mode); //! Encrypts a plaintext message using the session. std::variant encrypt(QString &plaintext); -- cgit v1.2.3 From faaea2eb2a090787f365eb12a42f8452bb0f07e2 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 24 Jan 2021 20:12:39 +0100 Subject: Make it work --- lib/olm/qolminboundsession.cpp | 48 ++++++++++++++++++++--------------------- lib/olm/qolminboundsession.h | 14 ++++++------ lib/olm/qolmoutboundsession.cpp | 31 +++++++++++++++----------- lib/olm/qolmoutboundsession.h | 9 ++++---- lib/olm/utils.cpp | 4 +++- 5 files changed, 57 insertions(+), 49 deletions(-) diff --git a/lib/olm/qolminboundsession.cpp b/lib/olm/qolminboundsession.cpp index f0ca73c4..d3b98a63 100644 --- a/lib/olm/qolminboundsession.cpp +++ b/lib/olm/qolminboundsession.cpp @@ -4,7 +4,6 @@ #ifdef Quotient_E2EE_ENABLED #include "olm/qolminboundsession.h" -#include #include using namespace Quotient; @@ -15,48 +14,44 @@ OlmError lastError(OlmInboundGroupSession *session) { return fromString(error_raw); } -QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session, QByteArray buffer) +QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session) : m_groupSession(session) - , m_buffer(buffer) { } QOlmInboundGroupSession::~QOlmInboundGroupSession() { olm_clear_inbound_group_session(m_groupSession); + //delete[](reinterpret_cast(m_groupSession)); } -std::variant QOlmInboundGroupSession::create(const QByteArray &key) +std::unique_ptr QOlmInboundGroupSession::create(const QByteArray &key) { - QByteArray olmInboundGroupSessionBuf(olm_inbound_group_session_size(), '0'); - const auto olmInboundGroupSession = olm_inbound_group_session(olmInboundGroupSessionBuf.data()); - + 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()) { - return lastError(olmInboundGroupSession); + throw lastError(olmInboundGroupSession); } - return QOlmInboundGroupSession(olmInboundGroupSession, std::move(olmInboundGroupSessionBuf)); + return std::make_unique(olmInboundGroupSession); } -std::variant QOlmInboundGroupSession::import(const QByteArray &key) +std::unique_ptr QOlmInboundGroupSession::import(const QByteArray &key) { - QByteArray olmInboundGroupSessionBuf(olm_inbound_group_session_size(), '0'); - const auto olmInboundGroupSession = olm_inbound_group_session(olmInboundGroupSessionBuf.data()); + 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()) { - return lastError(olmInboundGroupSession); + throw lastError(olmInboundGroupSession); } - return QOlmInboundGroupSession(olmInboundGroupSession, std::move(olmInboundGroupSessionBuf)); + return std::make_unique(olmInboundGroupSession); } QByteArray toKey(const PicklingMode &mode) @@ -67,28 +62,31 @@ QByteArray toKey(const PicklingMode &mode) return std::get(mode).key; } -std::variant QOlmInboundGroupSession::pickle(const PicklingMode &mode) const +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()) { - return lastError(m_groupSession); + throw lastError(m_groupSession); } return pickledBuf; } -std::variant QOlmInboundGroupSession::unpickle(QByteArray &picked, const PicklingMode &mode) +std::variant, OlmError> QOlmInboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) { - QByteArray groupSessionBuf(olm_inbound_group_session_size(), '0'); - auto groupSession = olm_inbound_group_session(groupSessionBuf.data()); - const QByteArray key = toKey(mode); - const auto error = olm_unpickle_inbound_group_session(groupSession, key.data(), key.length(), picked.data(), picked.size()); + 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); } - return QOlmInboundGroupSession(groupSession, std::move(groupSessionBuf)); + key.clear(); + + return std::make_unique(groupSession); } std::variant, OlmError> QOlmInboundGroupSession::decrypt(QString &message) @@ -136,13 +134,13 @@ uint32_t QOlmInboundGroupSession::firstKnownIndex() const return olm_inbound_group_session_first_known_index(m_groupSession); } -std::variant QOlmInboundGroupSession::sessionId() const +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()) { - return lastError(m_groupSession); + throw lastError(m_groupSession); } return sessionIdBuf; } diff --git a/lib/olm/qolminboundsession.h b/lib/olm/qolminboundsession.h index 85807821..ccc53ba8 100644 --- a/lib/olm/qolminboundsession.h +++ b/lib/olm/qolminboundsession.h @@ -8,6 +8,7 @@ #include #include +#include #include "olm/olm.h" #include "olm/errors.h" #include "olm/e2ee.h" @@ -21,14 +22,14 @@ struct QOlmInboundGroupSession public: ~QOlmInboundGroupSession(); //! Creates a new instance of `OlmInboundGroupSession`. - static std::variant create(const QByteArray &key); + static std::unique_ptr create(const QByteArray &key); //! Import an inbound group session, from a previous export. - static std::variant import(const QByteArray &key); + static std::unique_ptr import(const QByteArray &key); //! Serialises an `OlmInboundGroupSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode) const; + QByteArray pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. - static std::variant unpickle(QByteArray &picked, const PicklingMode &mode); + static std::variant, OlmError> 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,12 +38,11 @@ public: //! Get the first message index we know how to decrypt. uint32_t firstKnownIndex() const; //! Get a base64-encoded identifier for this session. - std::variant sessionId() const; + QByteArray sessionId() const; bool isVerified() const; + QOlmInboundGroupSession(OlmInboundGroupSession *session); private: - QOlmInboundGroupSession(OlmInboundGroupSession *session, QByteArray buffer); OlmInboundGroupSession *m_groupSession; - QByteArray m_buffer; }; } // namespace Quotient #endif diff --git a/lib/olm/qolmoutboundsession.cpp b/lib/olm/qolmoutboundsession.cpp index 60126469..ba8be4f6 100644 --- a/lib/olm/qolmoutboundsession.cpp +++ b/lib/olm/qolmoutboundsession.cpp @@ -14,34 +14,38 @@ OlmError lastError(OlmOutboundGroupSession *session) { return fromString(error_raw); } -QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session, const QByteArray &buffer) +QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session) : m_groupSession(session) - , m_buffer(buffer) { } QOlmOutboundGroupSession::~QOlmOutboundGroupSession() { olm_clear_outbound_group_session(m_groupSession); + //delete[](reinterpret_cast(m_groupSession)); } -std::variant QOlmOutboundGroupSession::create() +std::unique_ptr QOlmOutboundGroupSession::create() { - QByteArray sessionBuffer(olm_outbound_group_session_size(), '0'); - auto *olmOutboundGroupSession = olm_outbound_group_session(sessionBuffer.data()); + 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, + const auto error = olm_init_outbound_group_session(olmOutboundGroupSession, reinterpret_cast(randomBuf.data()), randomBuf.length()); if (error == olm_error()) { - return lastError(olmOutboundGroupSession); + 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 QOlmOutboundGroupSession(olmOutboundGroupSession, sessionBuffer); + return std::make_unique(olmOutboundGroupSession); } std::variant QOlmOutboundGroupSession::pickle(const PicklingMode &mode) @@ -61,20 +65,23 @@ std::variant QOlmOutboundGroupSession::pickle(const Pickli } -std::variant QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +std::variant, OlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) { QByteArray pickledBuf = pickled; - QByteArray olmOutboundGroupSessionBuf(olm_outbound_group_session_size(), '0'); + auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); 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); } + 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 QOlmOutboundGroupSession(olmOutboundGroupSession, olmOutboundGroupSessionBuf); + return std::make_unique(olmOutboundGroupSession); } std::variant QOlmOutboundGroupSession::encrypt(QString &plaintext) diff --git a/lib/olm/qolmoutboundsession.h b/lib/olm/qolmoutboundsession.h index 2e1439d3..29776a3d 100644 --- a/lib/olm/qolmoutboundsession.h +++ b/lib/olm/qolmoutboundsession.h @@ -7,6 +7,7 @@ #include "olm/olm.h" // from Olm #include "olm/errors.h" #include "olm/e2ee.h" +#include namespace Quotient { @@ -17,12 +18,13 @@ class QOlmOutboundGroupSession public: ~QOlmOutboundGroupSession(); //! Creates a new instance of `QOlmOutboundGroupSession`. - static std::variant create(); + //! 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 unpickle(QByteArray &pickled, const PicklingMode &mode); + static std::variant, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); //! Encrypts a plaintext message using the session. std::variant encrypt(QString &plaintext); @@ -40,10 +42,9 @@ 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; + QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); private: - QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession, const QByteArray &groupSessionBuf); OlmOutboundGroupSession *m_groupSession; - QByteArray m_buffer; }; } #endif diff --git a/lib/olm/utils.cpp b/lib/olm/utils.cpp index 15def1d7..227e6d84 100644 --- a/lib/olm/utils.cpp +++ b/lib/olm/utils.cpp @@ -4,6 +4,8 @@ #ifdef Quotient_E2EE_ENABLED #include "olm/utils.h" +#include +#include using namespace Quotient; @@ -18,7 +20,7 @@ QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) QByteArray Quotient::getRandom(size_t bufferSize) { QByteArray buffer(bufferSize, '0'); - std::generate(buffer.begin(), buffer.end(), std::rand); + RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); return buffer; } #endif -- 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(-) 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 723038cb3fe16c3f0078d00362fcb53c10f9eb4a Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 24 Jan 2021 20:14:08 +0100 Subject: Depends on OpenSSL for crypo rand --- CMakeLists.txt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 18a7b622..a74d2b7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,13 @@ find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModul get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") +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 (${PROJECT_NAME}_ENABLE_E2EE) if ((NOT DEFINED USE_INTREE_LIBQOLM OR USE_INTREE_LIBQOLM) AND EXISTS ${PROJECT_SOURCE_DIR}/3rdparty/libQtOlm/lib/utils.h) @@ -325,7 +332,14 @@ if (${PROJECT_NAME}_ENABLE_E2EE) target_link_libraries(${PROJECT_NAME} Olm::Olm QtOlm) set(FIND_DEPS "find_dependency(QtOlm)") # For QuotientConfig.cmake.in endif() -target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) + +target_link_libraries(${PROJECT_NAME} + ${Qt}::Core + ${Qt}::Network + ${Qt}::Gui + OpenSSL::Crypto + OpenSSL::SSL) + if (Qt STREQUAL Qt5) # See #483 target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) endif() -- 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(-) 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 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 799008ddbe2414ca0bce060a4d7f6a77c04c8d10 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Tue, 26 Jan 2021 04:39:04 +0300 Subject: E2EE: Introduce session (WiP) --- CMakeLists.txt | 2 + lib/olm/message.cpp | 35 ++++++++++ lib/olm/message.h | 46 +++++++++++++ lib/olm/qolmaccount.cpp | 5 ++ lib/olm/qolmaccount.h | 4 ++ lib/olm/qolminboundsession.h | 1 + lib/olm/qolmoutboundsession.h | 1 + lib/olm/session.cpp | 155 ++++++++++++++++++++++++++++++++++++++++++ lib/olm/session.h | 46 +++++++++++++ 9 files changed, 295 insertions(+) create mode 100644 lib/olm/message.cpp create mode 100644 lib/olm/message.h create mode 100644 lib/olm/session.cpp create mode 100644 lib/olm/session.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a359ae07..476b7d81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,6 +184,8 @@ list(APPEND lib_SRCS lib/olm/qolmoutboundsession.cpp lib/olm/utils.cpp lib/olm/errors.cpp + lib/olm/session.cpp + lib/olm/message.cpp ) # Configure API files generation diff --git a/lib/olm/message.cpp b/lib/olm/message.cpp new file mode 100644 index 00000000..0998a66b --- /dev/null +++ b/lib/olm/message.cpp @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "olm/message.h" + +using namespace Quotient; + +Message::Message(const QByteArray &ciphertext, Message::Type type) + : QByteArray(std::move(ciphertext)), _messageType(type) +{ + Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); +} + +Message::Message(QByteArray ciphertext) : QByteArray(std::move(ciphertext)) +{ + Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); +} + +Message::Type Message::type() const +{ + return _messageType; +} + +QByteArray Message::toCiphertext() const +{ + return QByteArray(*this); +} + + +#endif // Quotient_E2EE_ENABLED + + + diff --git a/lib/olm/message.h b/lib/olm/message.h new file mode 100644 index 00000000..6c8ab485 --- /dev/null +++ b/lib/olm/message.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// 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 : private QByteArray { + Q_GADGET +public: + enum Type { + General, + PreKey, + }; + Q_ENUM(Type) + + Message() = default; + explicit Message(const QByteArray& ciphertext, Type type = General); + explicit Message(QByteArray ciphertext); + + static Message fromCiphertext(QByteArray ciphertext); + + Q_INVOKABLE Type type() const; + Q_INVOKABLE QByteArray toCiphertext() const; + +private: + Type _messageType = General; +}; + + +} //namespace Quotient + +#endif // Quotient_E2EE_ENABLED diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp index 8872f66e..9530d675 100644 --- a/lib/olm/qolmaccount.cpp +++ b/lib/olm/qolmaccount.cpp @@ -192,4 +192,9 @@ QByteArray QOlmAccount::signOneTimeKey(const QString &key) const return sign(j.toJson()); } +OlmAccount *Quotient::QOlmAccount::data() +{ + return m_account; +} + #endif diff --git a/lib/olm/qolmaccount.h b/lib/olm/qolmaccount.h index 3b55212d..3260ca71 100644 --- a/lib/olm/qolmaccount.h +++ b/lib/olm/qolmaccount.h @@ -63,6 +63,10 @@ public: QByteArray signOneTimeKey(const QString &key) const; SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; + OlmAccount *data(); + + // HACK do not use directly + QOlmAccount(OlmAccount *account); private: OlmAccount *m_account = nullptr; QString m_userId; diff --git a/lib/olm/qolminboundsession.h b/lib/olm/qolminboundsession.h index eb698868..739a8411 100644 --- a/lib/olm/qolminboundsession.h +++ b/lib/olm/qolminboundsession.h @@ -46,5 +46,6 @@ private: }; using QOlmInboundGroupSessionPtr = std::unique_ptr; +using OlmInboundGroupSessionPtr = std::unique_ptr; } // namespace Quotient #endif diff --git a/lib/olm/qolmoutboundsession.h b/lib/olm/qolmoutboundsession.h index a642f581..70c4d27f 100644 --- a/lib/olm/qolmoutboundsession.h +++ b/lib/olm/qolmoutboundsession.h @@ -49,5 +49,6 @@ private: }; using QOlmOutboundGroupSessionPtr = std::unique_ptr; +using OlmOutboundGroupSessionPtr = std::unique_ptr; } #endif diff --git a/lib/olm/session.cpp b/lib/olm/session.cpp new file mode 100644 index 00000000..a2a7d28a --- /dev/null +++ b/lib/olm/session.cpp @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "olm/session.h" +#include "olm/utils.h" +#include "logging.h" + +using namespace Quotient; + +OlmError lastError(OlmSession* session) { + const std::string error_raw = olm_session_last_error(session); + + return fromString(error_raw); +} + +Quotient::QOlmSession::~QOlmSession() +{ + olm_clear_session(m_session); +} + +OlmSession* QOlmSession::create() +{ + return olm_session(new uint8_t[olm_session_size()]); +} + +std::unique_ptr QOlmSession::createInbound(QOlmAccount &account, const Message &preKeyMessage, bool from, const QString &theirIdentityKey) +{ + if (preKeyMessage.type() != Message::PreKey) { + qCDebug(E2EE) << "The message is not a pre-key"; + throw BadMessageFormat; + } + + const auto olmSession = create(); + + QByteArray oneTimeKeyMessageBuf = preKeyMessage.toCiphertext(); + QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); + size_t error = 0; + if (from) { + error = olm_create_inbound_session_from(olmSession, account.data(), theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); + } else { + error = olm_create_inbound_session(olmSession, account.data(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); + } + + if (error == olm_error()) { + throw lastError(olmSession); + } + + return std::make_unique(olmSession); +} + +std::unique_ptr QOlmSession::createInboundSession(QOlmAccount& account, const Message &preKeyMessage) +{ + return createInbound(account, preKeyMessage); +} + +std::unique_ptr QOlmSession::createInboundSessionFrom(QOlmAccount &account, const QString &theirIdentityKey, const Message &preKeyMessage) +{ + return createInbound(account, preKeyMessage, true, theirIdentityKey); +} + +std::unique_ptr QOlmSession::createOutboundSession(QOlmAccount &account, const QString &theirIdentityKey, const QString &theirOneTimeKey) +{ + auto *olmOutboundSession = create(); + const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); + QByteArray randomBuf = getRandom(randomLen); + + QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); + QByteArray theirOneTimeKeyBuf = theirOneTimeKey.toUtf8(); + const auto error = olm_create_outbound_session(olmOutboundSession, + account.data(), + theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), + theirOneTimeKeyBuf.data(), theirOneTimeKeyBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length()); + + if (error == olm_error()) { + throw lastError(olmOutboundSession); + } + + 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(QByteArray &pickled, const PicklingMode &mode) +{ + QByteArray pickledBuf = pickled; + auto *olmSession = create(); + QByteArray key = toKey(mode); + const auto error = olm_unpickle_session(olmSession, key.data(), key.length(), + pickled.data(), pickled.length()); + if (error == olm_error()) { + return lastError(olmSession); + } + + key.clear(); + return std::make_unique(olmSession); +} + +std::variant QOlmSession::encrypt(const QString &plaintext) +{ + QByteArray plaintextBuf = plaintext.toUtf8(); + const auto messageMaxLen = olm_encrypt_message_length(m_session, plaintextBuf.length()); + QByteArray messageBuf(messageMaxLen, '0'); + const auto randomLen = olm_encrypt_random_length(m_session); + QByteArray randomBuf = getRandom(randomLen); + const auto error = olm_encrypt(m_session, + reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length(), + reinterpret_cast(messageBuf.data()), messageBuf.length()); + + if (error == olm_error()) { + return lastError(m_session); + } + + return Message::fromCiphertext(messageBuf); +} + +QByteArray QOlmSession::sessionId() const +{ + const auto idMaxLength = olm_session_id_length(m_session); + QByteArray idBuffer(idMaxLength, '0'); + const auto error = olm_session_id(m_session, reinterpret_cast(idBuffer.data()), + idBuffer.length()); + if (error == olm_error()) { + throw lastError(m_session); + } + return idBuffer; +} + +QOlmSession::QOlmSession(OlmSession *session): m_session(session) +{ + +} + +#endif // Quotient_E2EE_ENABLED + + + diff --git a/lib/olm/session.h b/lib/olm/session.h new file mode 100644 index 00000000..76c1df29 --- /dev/null +++ b/lib/olm/session.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED + +#include "olm/e2ee.h" +#include "olm/message.h" +#include "olm/errors.h" +#include "olm/qolmaccount.h" + +namespace Quotient { + +//! Either an outbound or inbound session for secure communication. +class QOlmSession +{ +public: + ~QOlmSession(); + //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. + static std::unique_ptr createInboundSession(QOlmAccount& account, const Message& preKeyMessage); + static std::unique_ptr createInboundSessionFrom(QOlmAccount& account, const QString& theirIdentityKey, const Message& preKeyMessage); + static std::unique_ptr 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(QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + std::variant encrypt(const QString &plaintext); + // TODO: WiP + + //! Get a base64-encoded identifier for this session. + QByteArray sessionId() const; + + QOlmSession(OlmSession* session); +private: + //! Helper function for creating new sessions and handling errors. + static OlmSession* create(); + static std::unique_ptr createInbound(QOlmAccount& account, const Message& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); + OlmSession* m_session; +}; + +} //namespace Quotient + +#endif // Quotient_E2EE_ENABLED -- cgit v1.2.3 From d0d8b267753792d0310dd964b0b688d6262e6eb4 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Tue, 26 Jan 2021 21:55:13 +0300 Subject: Add missing reinterpret_cast for session data --- lib/olm/session.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/olm/session.cpp b/lib/olm/session.cpp index a2a7d28a..e7a57677 100644 --- a/lib/olm/session.cpp +++ b/lib/olm/session.cpp @@ -70,8 +70,8 @@ std::unique_ptr QOlmSession::createOutboundSession(QOlmAccount &acc QByteArray theirOneTimeKeyBuf = theirOneTimeKey.toUtf8(); const auto error = olm_create_outbound_session(olmOutboundSession, account.data(), - theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), - theirOneTimeKeyBuf.data(), theirOneTimeKeyBuf.length(), + reinterpret_cast(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), + reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), reinterpret_cast(randomBuf.data()), randomBuf.length()); if (error == olm_error()) { -- cgit v1.2.3 From 57a218086d3c687cd26580ee2a0d2135646411dc Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 27 Jan 2021 00:50:27 +0100 Subject: Add hehlper functions --- lib/olm/qolmaccount.cpp | 15 +++++++++++++++ lib/olm/qolmaccount.h | 20 +++++++++++++++++++- lib/olm/session.cpp | 26 +++++++++++++++++--------- lib/olm/session.h | 10 ++++++---- 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp index 9530d675..9c47bc87 100644 --- a/lib/olm/qolmaccount.cpp +++ b/lib/olm/qolmaccount.cpp @@ -197,4 +197,19 @@ OlmAccount *Quotient::QOlmAccount::data() return m_account; } +std::variant, OlmError> QOlmAccount::createInboundSession(const Message &preKeyMessage) +{ + return QOlmSession::createInboundSession(this, preKeyMessage); +} + +std::variant, OlmError> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const Message &preKeyMessage) +{ + 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 index 3260ca71..df5e1be2 100644 --- a/lib/olm/qolmaccount.h +++ b/lib/olm/qolmaccount.h @@ -7,12 +7,15 @@ #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); @@ -63,10 +66,25 @@ public: QByteArray signOneTimeKey(const QString &key) const; SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; - OlmAccount *data(); + + //! 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; diff --git a/lib/olm/session.cpp b/lib/olm/session.cpp index e7a57677..b5cd7b81 100644 --- a/lib/olm/session.cpp +++ b/lib/olm/session.cpp @@ -25,7 +25,7 @@ OlmSession* QOlmSession::create() return olm_session(new uint8_t[olm_session_size()]); } -std::unique_ptr QOlmSession::createInbound(QOlmAccount &account, const Message &preKeyMessage, bool from, const QString &theirIdentityKey) +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"; @@ -38,29 +38,33 @@ std::unique_ptr QOlmSession::createInbound(QOlmAccount &account, co 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()); + 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()); + error = olm_create_inbound_session(olmSession, account->data(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); } if (error == olm_error()) { - throw lastError(olmSession); + const auto lastErr = lastError(olmSession); + if (lastErr == OlmError::NotEnoughRandom) { + throw lastErr; + } + return lastErr; } return std::make_unique(olmSession); } -std::unique_ptr QOlmSession::createInboundSession(QOlmAccount& account, const Message &preKeyMessage) +std::variant, OlmError> QOlmSession::createInboundSession(QOlmAccount *account, const Message &preKeyMessage) { return createInbound(account, preKeyMessage); } -std::unique_ptr QOlmSession::createInboundSessionFrom(QOlmAccount &account, const QString &theirIdentityKey, const Message &preKeyMessage) +std::variant, OlmError> QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage) { return createInbound(account, preKeyMessage, true, theirIdentityKey); } -std::unique_ptr QOlmSession::createOutboundSession(QOlmAccount &account, const QString &theirIdentityKey, const QString &theirOneTimeKey) +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); @@ -69,13 +73,17 @@ std::unique_ptr QOlmSession::createOutboundSession(QOlmAccount &acc QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); QByteArray theirOneTimeKeyBuf = theirOneTimeKey.toUtf8(); const auto error = olm_create_outbound_session(olmOutboundSession, - account.data(), + account->data(), reinterpret_cast(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), reinterpret_cast(randomBuf.data()), randomBuf.length()); if (error == olm_error()) { - throw lastError(olmOutboundSession); + const auto lastErr = lastError(olmOutboundSession); + if (lastErr == OlmError::NotEnoughRandom) { + throw lastErr; + } + return lastErr; } randomBuf.clear(); diff --git a/lib/olm/session.h b/lib/olm/session.h index 76c1df29..e3a52c88 100644 --- a/lib/olm/session.h +++ b/lib/olm/session.h @@ -13,15 +13,17 @@ namespace Quotient { +class QOlmAccount; + //! Either an outbound or inbound session for secure communication. class QOlmSession { public: ~QOlmSession(); //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. - static std::unique_ptr createInboundSession(QOlmAccount& account, const Message& preKeyMessage); - static std::unique_ptr createInboundSessionFrom(QOlmAccount& account, const QString& theirIdentityKey, const Message& preKeyMessage); - static std::unique_ptr createOutboundSession(QOlmAccount& account, const QString& theirIdentityKey, const QString& theirOneTimeKey); + 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`. @@ -37,7 +39,7 @@ public: private: //! Helper function for creating new sessions and handling errors. static OlmSession* create(); - static std::unique_ptr createInbound(QOlmAccount& account, const Message& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); + static std::variant, OlmError> createInbound(QOlmAccount *account, const Message& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); OlmSession* m_session; }; -- 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 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 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(-) 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(-) 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(-) 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(-) 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(+) 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 7ad805492f8b42a4bc854313695a912c89019957 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 27 Jan 2021 17:54:27 +0100 Subject: Fix CI --- .github/workflows/ci.yml | 6 ++++++ CMakeLists.txt | 14 +++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47e55421..0b707236 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,6 +106,12 @@ jobs: with: arch: ${{ matrix.platform }} + - name: Install OpenSSL + if: contains(matrix.os, 'ubuntu') and matrix.e2ee + run: | + sudo apt-get install libssl-dev + echo "openssl version" >>$GITHUB_ENV + - name: Build and install olm if: matrix.e2ee run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 476b7d81..a04da04d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,14 +87,14 @@ find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModul get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") -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 (${PROJECT_NAME}_ENABLE_E2EE) + 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) -- 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(+) 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 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 9f71b2a79fba7c5d5ce09ebfdd482c8c470203d9 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 28 Jan 2021 21:59:20 +0100 Subject: Remove duplicated file --- lib/e2ee.h | 35 ----------------------------------- lib/encryptionmanager.cpp | 2 +- lib/events/encryptedevent.h | 2 +- lib/events/encryptionevent.cpp | 2 +- lib/room.cpp | 2 +- 5 files changed, 4 insertions(+), 39 deletions(-) delete mode 100644 lib/e2ee.h diff --git a/lib/e2ee.h b/lib/e2ee.h deleted file mode 100644 index 4044aa02..00000000 --- a/lib/e2ee.h +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "util.h" - -#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 }; -} // namespace Quotient diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 37f3b7c3..569d369a 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -6,7 +6,7 @@ #include "encryptionmanager.h" #include "connection.h" -#include "e2ee.h" +#include "crypto/e2ee.h" #include "csapi/keys.h" diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index de89a7c6..1d7ea913 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -3,7 +3,7 @@ #pragma once -#include "e2ee.h" +#include "crypto/e2ee.h" #include "roomevent.h" namespace Quotient { diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index aa05a96e..d7bb953a 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -4,7 +4,7 @@ #include "encryptionevent.h" -#include "e2ee.h" +#include "crypto/e2ee.h" #include diff --git a/lib/room.cpp b/lib/room.cpp index 6e6d7f11..0c9af2b9 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -12,7 +12,7 @@ #include "avatar.h" #include "connection.h" #include "converters.h" -#include "e2ee.h" +#include "crypto/e2ee.h" #include "syncdata.h" #include "user.h" #include "eventstats.h" -- 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 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(-) 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 0769764249e10f2f6d1a84ac87e93b2fa3b6c61a Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 29 Jan 2021 02:53:01 +0100 Subject: More porting to new API --- lib/crypto/qolmaccount.cpp | 10 +++++ lib/crypto/qolmaccount.h | 3 ++ lib/crypto/qolmsession.cpp | 21 +++++++++- lib/crypto/qolmsession.h | 9 ++++- lib/encryptionmanager.cpp | 95 ++++++++++++++++++++++++---------------------- 5 files changed, 90 insertions(+), 48 deletions(-) diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index fc0fc1cf..76b0a263 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -197,6 +197,16 @@ QByteArray QOlmAccount::signOneTimeKey(const QString &key) const return sign(j.toJson()); } +std::optional QOlmAccount::removeOneTimeKeys(const std::unique_ptr &session) const +{ + const auto error = olm_remove_one_time_keys(m_account, session->raw()); + + if (error == olm_error()) { + return lastError(m_account); + } + return std::nullopt; +} + OlmAccount *Quotient::QOlmAccount::data() { return m_account; diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index b33e3768..4398214a 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -68,6 +68,9 @@ public: SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; + //! Remove the one time key used to create the supplied session. + [[nodiscard]] std::optional removeOneTimeKeys(const std::unique_ptr &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. diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp index cfe21650..b901a440 100644 --- a/lib/crypto/qolmsession.cpp +++ b/lib/crypto/qolmsession.cpp @@ -213,7 +213,7 @@ bool QOlmSession::hasReceivedMessage() const return olm_session_has_received_message(m_session); } -std::variant QOlmSession::matchesInboundSession(QOlmMessage &preKeyMessage) +std::variant QOlmSession::matchesInboundSession(const QOlmMessage &preKeyMessage) const { Q_ASSERT(preKeyMessage.type() == QOlmMessage::Type::PreKey); QByteArray oneTimeKeyBuf(preKeyMessage.data()); @@ -231,6 +231,25 @@ std::variant QOlmSession::matchesInboundSession(QOlmMessage &pr 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 index 6e13801e..0fc59e9e 100644 --- a/lib/crypto/qolmsession.h +++ b/lib/crypto/qolmsession.h @@ -50,7 +50,10 @@ public: bool hasReceivedMessage() const; //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSession(QOlmMessage &preKeyMessage); + 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) { @@ -61,6 +64,10 @@ public: return *lhs < *rhs; } + OlmSession *raw() const + { + return m_session; + } QOlmSession(OlmSession* session); private: //! Helper function for creating new sessions and handling errors. diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 8081f788..449eb2a3 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -88,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) @@ -100,44 +100,45 @@ public: } } } - QString sessionDecrypt(QOlmMessage* message, const QString& senderKey) + QString sessionDecrypt(const QOlmMessage& message, const QString& senderKey) { - QString decrypted; - 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()) { + for (auto &senderSession : sessions) { + if (senderSession == sessions.last()) { sessionsPassed = true; } - try { - decrypted = senderSession->decrypt(message); + + const auto decryptedResult = senderSession->decrypt(message); + if (std::holds_alternative(decryptedResult)) { qCDebug(E2EE) << "Success decrypting Olm event using existing session" - << senderSession->id(); - break; - } 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. - qCDebug(E2EE) - << "Error decrypting pre-key message with existing " - "Olm session" - << senderSession->id() << "reason:" << e->what(); - return QString(); + << senderSession->sessionId(); + return std::get(decryptedResult); + } else { + const auto error = std::get(decryptedResult); + if (message.type() == QOlmMessage::PreKey) { + const auto matches = senderSession->matchesInboundSessionFrom(senderKey, message); + if (auto hasMatch = std::get_if(&matches)) { + if (hasMatch) { + // We had a matching session for a pre-key message, but + // it didn't work. This means something is wrong, so we + // fail now. + qCDebug(E2EE) + << "Error decrypting pre-key message with existing " + "Olm session" + << senderSession->sessionId() << "reason:" << error; + return QString(); + } } } // Simply keep trying otherwise } } - */ - if (sessionsPassed || senderSessions.empty()) { - if (message->type() != QOlmMessage::PreKey) { + if (sessionsPassed || sessions.empty()) { + 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"; @@ -148,36 +149,39 @@ public: } // We have a pre-key message without any matching session, in this // case we should try to create one. - QOlmSession* newSession; qCDebug(E2EE) << "try to establish new InboundSession with" << senderKey; - QOlmMessage preKeyMessage = QOlmMessage(message->toCiphertext(),QOlmMessage::PreKey); + QOlmMessage preKeyMessage = QOlmMessage(message.toCiphertext(), QOlmMessage::PreKey); // new e2ee TODO: - /* - try { - newSession = new InboundSession(olmAccount.data(), - &preKeyMessage, - senderKey.toLatin1(), q); - } catch (OlmError* e) { + 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 " "to establish a new session:" - << e->what(); + << error; return QString(); } - qCDebug(E2EE) << "Created new Olm session" << newSession->id(); - try { - decrypted = newSession->decrypt(message); - } catch (OlmError* e) { + + const auto newSession = std::get>(sessionResult); + + qCDebug(E2EE) << "Created new Olm session" << newSession->sessionId(); + + const auto decryptedResult = newSession->decrypt(message); + if (const auto error = std::get_if(&decryptedResult)) { qCDebug(E2EE) << "Error decrypting pre-key message with new session" - << e->what(); + << error; return QString(); } - olmAccount->removeOneTimeKeys(newSession); - */ - sessions.insert(senderKey, newSession); + if (auto error = olmAccount->removeOneTimeKeys(newSession)) { + qCDebug(E2EE) + << "Error removing one time keys" + << error.value(); + } + sessions.insert(senderKey, std::move(newSession)); + return std::get(decryptedResult); } - return decrypted; + return QString(); } }; @@ -330,11 +334,10 @@ QString EncryptionManager::sessionDecryptMessage( QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); if (type == 0) { QOlmMessage preKeyMessage = QOlmMessage(body, QOlmMessage::PreKey); - decrypted = d->sessionDecrypt(reinterpret_cast(&preKeyMessage), - senderKey); + decrypted = d->sessionDecrypt(preKeyMessage, senderKey); } else if (type == 1) { QOlmMessage message = QOlmMessage(body, QOlmMessage::PreKey); - decrypted = d->sessionDecrypt(&message, senderKey); + decrypted = d->sessionDecrypt(message, senderKey); } return decrypted; } -- 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 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 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(-) 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 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 a5e84c51e9c89021edc8aaade8c751fb6d39cb89 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 31 Jan 2021 01:20:57 +0100 Subject: remove old files --- lib/crypto/qolmsession.cpp.back | 29 ------------------------ lib/crypto/qolmsession.h.back | 49 ----------------------------------------- 2 files changed, 78 deletions(-) delete mode 100644 lib/crypto/qolmsession.cpp.back delete mode 100644 lib/crypto/qolmsession.h.back diff --git a/lib/crypto/qolmsession.cpp.back b/lib/crypto/qolmsession.cpp.back deleted file mode 100644 index ee8b2a7f..00000000 --- a/lib/crypto/qolmsession.cpp.back +++ /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 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.back b/lib/crypto/qolmsession.h.back deleted file mode 100644 index cbba5cef..00000000 --- a/lib/crypto/qolmsession.h.back +++ /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 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); -//} - -} -- 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(-) 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 38547289d56cf66b4f1384ae789cf5b6cd71763e Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 5 Feb 2021 00:03:27 +0100 Subject: Fix cmake code --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb07fa22..ba6c8cd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -316,7 +316,8 @@ if (${PROJECT_NAME}_ENABLE_E2EE) target_link_libraries(${PROJECT_NAME} Olm::Olm OpenSSL::Crypto OpenSSL::SSL) - set(FIND_DEPS "find_dependency(Olm OpenSSL)") # For QuotientConfig.cmake.in + set(FIND_DEPS "find_dependency(Olm) + find_dependency(OpenSSL)") # For QuotientConfig.cmake.in endif() target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) -- cgit v1.2.3 From e9527012622497b0c418df9442180df58401d394 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 15 Feb 2021 18:03:33 +0100 Subject: Apply suggestions from code review Co-authored-by: Nicolas Fella <6377822+nicolasfella@users.noreply.github.com> --- lib/converters.cpp | 6 +++--- lib/crypto/qolmutils.cpp | 2 +- lib/encryptionmanager.cpp | 4 ++-- lib/room.cpp | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/converters.cpp b/lib/converters.cpp index e6dcd854..a3ac44c5 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -17,10 +17,10 @@ QJsonValue Quotient::JsonConverter::dump(const QVariant& v) QVariant Quotient::JsonConverter::load(const QJsonValue& jv) { if (jv.isObject()) { - QJsonObject obj = jv.toObject(); - if (obj.contains("key") && obj.contains("signatures")) { + const QJsonObject obj = jv.toObject(); + if (obj.contains(QLatin1String("key")) && obj.contains(QLatin1String("signatures"))) { SignedOneTimeKey signedOneTimeKeys; - signedOneTimeKeys.key = obj["key"].toString(); + signedOneTimeKeys.key = obj[QLatin1String("key")].toString(); } } return jv.toVariant(); diff --git a/lib/crypto/qolmutils.cpp b/lib/crypto/qolmutils.cpp index a486ea0f..4479932e 100644 --- a/lib/crypto/qolmutils.cpp +++ b/lib/crypto/qolmutils.cpp @@ -12,7 +12,7 @@ using namespace Quotient; QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) { if (std::holds_alternative(mode)) { - return ""; + return {}; } return std::get(mode).key; } diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index c8dc6bdd..719add1d 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -333,10 +333,10 @@ QString EncryptionManager::sessionDecryptMessage( int type = personalCipherObject.value(TypeKeyL).toInt(-1); QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); if (type == 0) { - QOlmMessage preKeyMessage = QOlmMessage(body, QOlmMessage::PreKey); + QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); decrypted = d->sessionDecrypt(preKeyMessage, senderKey); } else if (type == 1) { - QOlmMessage message = QOlmMessage(body, QOlmMessage::PreKey); + QOlmMessage message(body, QOlmMessage::PreKey); decrypted = d->sessionDecrypt(message, senderKey); } return decrypted; diff --git a/lib/room.cpp b/lib/room.cpp index d86b2813..a8a7fe0c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -65,7 +65,7 @@ #include #ifdef Quotient_E2EE_ENABLED -# include "crypto/qolmaccount.h" +#include "crypto/qolmaccount.h" # include "crypto/qolmerrors.h" # include "crypto/qolminboundsession.h" #endif // Quotient_E2EE_ENABLED -- 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(-) 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(-) 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 bc4ef60c29709a6f782f6e75e1f040f250fb8bd7 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 22 Mar 2021 16:32:21 +0100 Subject: Ifdef E2EE out at cmake level --- CMakeLists.txt | 22 +++++++++++++--------- lib/crypto/qolmaccount.cpp | 3 --- lib/crypto/qolmaccount.h | 4 +--- lib/crypto/qolmerrors.cpp | 4 ++-- lib/crypto/qolmerrors.h | 3 --- lib/crypto/qolminboundsession.cpp | 2 -- lib/crypto/qolminboundsession.h | 3 --- lib/crypto/qolmmessage.cpp | 7 ------- lib/crypto/qolmmessage.h | 5 ----- lib/crypto/qolmoutboundsession.cpp | 3 --- lib/crypto/qolmoutboundsession.h | 3 +-- lib/crypto/qolmsession.cpp | 6 ------ lib/crypto/qolmsession.h | 4 ---- lib/crypto/qolmutility.cpp | 4 ---- lib/crypto/qolmutility.h | 3 --- lib/crypto/qolmutils.cpp | 2 -- lib/crypto/qolmutils.h | 2 -- 17 files changed, 17 insertions(+), 63 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba6c8cd1..5601a281 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,16 +158,20 @@ list(APPEND lib_SRCS lib/jobs/syncjob.cpp lib/jobs/mediathumbnailjob.cpp lib/jobs/downloadfilejob.cpp - 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 ) +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 + ) +endif() # Configure API files generation diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 0f354d3e..4f007e2f 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "qolmaccount.h" #include "connection.h" #include "csapi/keys.h" @@ -318,5 +317,3 @@ bool Quotient::ed25519VerifySignature(const QString &signingKey, return std::get(result); } - -#endif diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index de78a8af..1e198687 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: 2021 Carl Schwan // // SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once -#ifdef Quotient_E2EE_ENABLED #include "csapi/keys.h" #include "crypto/e2ee.h" @@ -111,5 +111,3 @@ bool ed25519VerifySignature(const QString &signingKey, const QString &signature); } // namespace Quotient - -#endif diff --git a/lib/crypto/qolmerrors.cpp b/lib/crypto/qolmerrors.cpp index f407383e..46b2618c 100644 --- a/lib/crypto/qolmerrors.cpp +++ b/lib/crypto/qolmerrors.cpp @@ -1,6 +1,7 @@ // 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) { @@ -18,4 +19,3 @@ Quotient::QOlmError Quotient::fromString(const std::string &error_raw) { return QOlmError::Unknown; } } -#endif diff --git a/lib/crypto/qolmerrors.h b/lib/crypto/qolmerrors.h index 400573c6..f8390d2a 100644 --- a/lib/crypto/qolmerrors.h +++ b/lib/crypto/qolmerrors.h @@ -4,7 +4,6 @@ #pragma once -#ifdef Quotient_E2EE_ENABLED #include namespace Quotient { @@ -27,5 +26,3 @@ enum QOlmError QOlmError fromString(const std::string &error_raw); } //namespace Quotient - -#endif diff --git a/lib/crypto/qolminboundsession.cpp b/lib/crypto/qolminboundsession.cpp index 8f5056d8..e1ced72b 100644 --- a/lib/crypto/qolminboundsession.cpp +++ b/lib/crypto/qolminboundsession.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "crypto/qolminboundsession.h" #include #include @@ -154,4 +153,3 @@ 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 index 6af71cbd..36ab4942 100644 --- a/lib/crypto/qolminboundsession.h +++ b/lib/crypto/qolminboundsession.h @@ -4,8 +4,6 @@ #pragma once -#ifdef Quotient_E2EE_ENABLED - #include #include #include @@ -48,4 +46,3 @@ private: using QOlmInboundGroupSessionPtr = std::unique_ptr; using OlmInboundGroupSessionPtr = std::unique_ptr; } // namespace Quotient -#endif diff --git a/lib/crypto/qolmmessage.cpp b/lib/crypto/qolmmessage.cpp index ae98d52f..15008b75 100644 --- a/lib/crypto/qolmmessage.cpp +++ b/lib/crypto/qolmmessage.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "qolmmessage.h" using namespace Quotient; @@ -34,9 +33,3 @@ 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 index d203364d..52aba78c 100644 --- a/lib/crypto/qolmmessage.h +++ b/lib/crypto/qolmmessage.h @@ -4,8 +4,6 @@ #pragma once -#ifdef Quotient_E2EE_ENABLED - #include #include @@ -40,7 +38,4 @@ private: Type m_messageType = General; }; - } //namespace Quotient - -#endif // Quotient_E2EE_ENABLED diff --git a/lib/crypto/qolmoutboundsession.cpp b/lib/crypto/qolmoutboundsession.cpp index 14b7368e..bf8dce61 100644 --- a/lib/crypto/qolmoutboundsession.cpp +++ b/lib/crypto/qolmoutboundsession.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "qolmoutboundsession.h" #include "crypto/qolmutils.h" @@ -127,5 +126,3 @@ std::variant QOlmOutboundGroupSession::sessionKey() const } return keyBuffer; } - -#endif diff --git a/lib/crypto/qolmoutboundsession.h b/lib/crypto/qolmoutboundsession.h index 6b4fd30b..f1df0395 100644 --- a/lib/crypto/qolmoutboundsession.h +++ b/lib/crypto/qolmoutboundsession.h @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: 2021 Carl Schwan // // SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once -#ifdef Quotient_E2EE_ENABLED #include "olm/olm.h" #include "crypto/qolmerrors.h" @@ -51,4 +51,3 @@ private: using QOlmOutboundGroupSessionPtr = std::unique_ptr; using OlmOutboundGroupSessionPtr = std::unique_ptr; } -#endif diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp index b901a440..a1f6ab71 100644 --- a/lib/crypto/qolmsession.cpp +++ b/lib/crypto/qolmsession.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "qolmsession.h" #include "crypto/qolmutils.h" #include "logging.h" @@ -255,8 +254,3 @@ QOlmSession::QOlmSession(OlmSession *session) : m_session(session) { } - -#endif // Quotient_E2EE_ENABLED - - - diff --git a/lib/crypto/qolmsession.h b/lib/crypto/qolmsession.h index 0fc59e9e..959c77d0 100644 --- a/lib/crypto/qolmsession.h +++ b/lib/crypto/qolmsession.h @@ -4,8 +4,6 @@ #pragma once -#ifdef Quotient_E2EE_ENABLED - #include #include // FIXME: OlmSession #include "crypto/e2ee.h" @@ -80,5 +78,3 @@ private: //using QOlmSessionPtr = std::unique_ptr; } //namespace Quotient - -#endif // Quotient_E2EE_ENABLED diff --git a/lib/crypto/qolmutility.cpp b/lib/crypto/qolmutility.cpp index 87615770..bb50b4d0 100644 --- a/lib/crypto/qolmutility.cpp +++ b/lib/crypto/qolmutility.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "crypto/qolmutility.h" #include "olm/olm.h" #include @@ -62,6 +61,3 @@ std::variant QOlmUtility::ed25519Verify(const QByteArray &key, } return true; } - - -#endif diff --git a/lib/crypto/qolmutility.h b/lib/crypto/qolmutility.h index 3de09ab4..fc6569f7 100644 --- a/lib/crypto/qolmutility.h +++ b/lib/crypto/qolmutility.h @@ -4,7 +4,6 @@ #pragma once -#ifdef Quotient_E2EE_ENABLED #include #include #include "crypto/qolmerrors.h" @@ -44,5 +43,3 @@ private: }; } - -#endif diff --git a/lib/crypto/qolmutils.cpp b/lib/crypto/qolmutils.cpp index 4479932e..cd5ac83c 100644 --- a/lib/crypto/qolmutils.cpp +++ b/lib/crypto/qolmutils.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "crypto/qolmutils.h" #include #include @@ -23,4 +22,3 @@ QByteArray Quotient::getRandom(size_t bufferSize) RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); return buffer; } -#endif diff --git a/lib/crypto/qolmutils.h b/lib/crypto/qolmutils.h index 11e9f3cc..8b1c01ce 100644 --- a/lib/crypto/qolmutils.h +++ b/lib/crypto/qolmutils.h @@ -3,7 +3,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once -#ifdef Quotient_E2EE_ENABLED #include @@ -14,4 +13,3 @@ namespace Quotient { QByteArray toKey(const PicklingMode &mode); QByteArray getRandom(size_t bufferSize); } -#endif -- 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(-) 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(-) 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(-) 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 65877dc9fb6e024d456343d42ef55e0c5c8b67b3 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 24 May 2021 16:52:45 +0200 Subject: Upload one-time keys when their count is low --- lib/connection.cpp | 20 +++++++++----------- lib/crypto/qolmaccount.cpp | 5 +++++ lib/crypto/qolmaccount.h | 2 ++ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 704bc1b4..9883b8f3 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -620,17 +620,15 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->consumePresenceData(data.takePresenceData()); 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(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys()) { + d->olmAccount->generateOneTimeKeys(d->olmAccount->maxNumberOfOneTimeKeys() - data.deviceOneTimeKeysCount()["signed_curve25519"]); + auto keys = d->olmAccount->oneTimeKeys(); + auto job = d->olmAccount->createUploadKeyRequest(keys); + run(job, ForegroundRequest); + connect(job, &BaseJob::success, this, [=](){ + d->olmAccount->markKeysAsPublished(); + }); + } #endif // Quotient_E2EE_ENABLED } diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 9368de4f..8cf21045 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -276,6 +276,11 @@ std::variant, QOlmError> QOlmAccount::createOutboun return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); } +void QOlmAccount::markKeysAsPublished() +{ + olm_account_mark_keys_as_published(m_account); +} + bool Quotient::verifyIdentitySignature(const DeviceKeys &deviceKeys, const QString &deviceId, const QString &userId) diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index f3ca82f0..54d8506c 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -93,6 +93,8 @@ public: /// identity and one time key. std::variant, QOlmError> createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); + void markKeysAsPublished(); + // HACK do not use directly QOlmAccount(OlmAccount *account); OlmAccount *data(); -- cgit v1.2.3 From 1186c9fc980f9659191df4b3f5b540befe946dfa Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 24 May 2021 18:01:27 +0200 Subject: Make sure that only one upload is running --- lib/connection.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 9883b8f3..b91a1a90 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -110,6 +110,7 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; //QScopedPointer encryptionManager; + bool isUploadingKeys = false; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -620,7 +621,8 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->consumePresenceData(data.takePresenceData()); d->consumeToDeviceEvents(data.takeToDeviceEvents()); #ifdef Quotient_E2EE_ENABLED - if(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys()) { + if(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() && !d->isUploadingKeys) { + d->isUploadingKeys = true; d->olmAccount->generateOneTimeKeys(d->olmAccount->maxNumberOfOneTimeKeys() - data.deviceOneTimeKeysCount()["signed_curve25519"]); auto keys = d->olmAccount->oneTimeKeys(); auto job = d->olmAccount->createUploadKeyRequest(keys); @@ -628,6 +630,9 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) connect(job, &BaseJob::success, this, [=](){ d->olmAccount->markKeysAsPublished(); }); + connect(job, &BaseJob::result, this, [=](){ + d->isUploadingKeys = false; + }); } #endif // Quotient_E2EE_ENABLED } -- cgit v1.2.3 From 211d0c1b96c13f949f50799f5a4412ae31586546 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 24 May 2021 00:40:30 +0200 Subject: Uncomment some stuff --- lib/connection.cpp | 17 ++++------------- lib/crypto/qolmsession.cpp | 5 ++--- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index b91a1a90..0c0bada6 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -109,8 +109,8 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; - //QScopedPointer encryptionManager; bool isUploadingKeys = false; + QScopedPointer encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -187,7 +187,6 @@ public: { 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 {}; @@ -247,7 +246,6 @@ public: return std::move(decryptedEvent); #endif // Quotient_E2EE_ENABLED -*/ } }; @@ -761,7 +759,6 @@ 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) { @@ -771,20 +768,15 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) return; } - // TODO: full maintaining of the device keys - // with device_lists sync extention and /keys/query - qCDebug(E2EE) << "Getting device keys for the m.room_key sender:" - << ee.senderId(); - // encryptionManager->updateDeviceKeys(); - visit(*sessionDecryptMessage(ee), [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { - if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) + if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); - else + } else { qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() << "is not found at the connection" << q->objectName(); + } }, [](const Event& evt) { qCDebug(E2EE) << "Skipping encrypted to_device event, type" @@ -792,7 +784,6 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) }); }); #endif -*/ } void Connection::stopSync() diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp index a1f6ab71..2068a7d9 100644 --- a/lib/crypto/qolmsession.cpp +++ b/lib/crypto/qolmsession.cpp @@ -30,8 +30,7 @@ OlmSession* QOlmSession::create() 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; + qCCritical(E2EE) << "The message is not a pre-key in when creating inbound session" << BadMessageFormat; } const auto olmSession = create(); @@ -48,7 +47,7 @@ std::variant, QOlmError> QOlmSession::createInbound if (error == olm_error()) { const auto lastErr = lastError(olmSession); if (lastErr == QOlmError::NotEnoughRandom) { - throw lastErr; + qCCritical(E2EE) << "Error when creating inbound session" << lastErr; } return lastErr; } -- cgit v1.2.3 From 1d851e7b2e5e0c937413b8fd4bcdb35c8492430b Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 24 May 2021 01:51:15 +0200 Subject: Upload device keys when creating a new olm account --- lib/connection.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/connection.cpp b/lib/connection.cpp index 0c0bada6..1485a347 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -455,6 +455,10 @@ void Connection::Private::completeSetup(const QString& mxId) olmAccount->createNewAccount(); accountSettings.setEncryptionAccountPickle(std::get(olmAccount->pickle(Unencrypted{}))); // TODO handle pickle errors + auto job = q->callApi(olmAccount->deviceKeys()); + connect(job, &BaseJob::failure, q, [=]{ + qCWarning(E2EE) << "Failed to upload device keys:" << job->errorString(); + }); } else { // account already existing auto pickle = accountSettings.encryptionAccountPickle(); -- cgit v1.2.3 From 00b6103ceeed63238cb8c691163ed8489dd72ddb Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 15 May 2021 22:44:42 +0200 Subject: Start implementing device key tracking --- lib/connection.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/syncdata.cpp | 30 ++++++++++++++++++++++ lib/syncdata.h | 23 +++++++++++++++++ 3 files changed, 126 insertions(+) diff --git a/lib/connection.cpp b/lib/connection.cpp index 1485a347..66590bd8 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -102,6 +102,13 @@ public: QMetaObject::Connection syncLoopConnection {}; int syncTimeout = -1; +#ifdef Quotient_E2EE_ENABLED + QSet trackedUsers; + QSet outdatedUsers; + QHash> deviceKeys; + QueryKeysJob *currentQueryKeysJob = nullptr; +#endif + GetCapabilitiesJob* capabilitiesJob = nullptr; GetCapabilitiesJob::Capabilities capabilities; @@ -153,6 +160,7 @@ public: void consumeAccountData(Events&& accountDataEvents); void consumePresenceData(Events&& presenceData); void consumeToDeviceEvents(Events&& toDeviceEvents); + void consumeDevicesList(DevicesList&& devicesList); template EventT* unpackAccountData() const @@ -247,6 +255,10 @@ public: return std::move(decryptedEvent); #endif // Quotient_E2EE_ENABLED } +#ifdef Quotient_E2EE_ENABLED + void loadOutdatedUserDevices(); + void createDevicesList(); +#endif }; Connection::Connection(const QUrl& server, QObject* parent) @@ -468,6 +480,11 @@ void Connection::Private::completeSetup(const QString& mxId) emit q->stateChanged(); emit q->connected(); q->reloadCapabilities(); +#ifdef Quotient_E2EE_ENABLED + connectSingleShot(q, &Connection::syncDone, q, [=](){ + createDevicesList(); + }); +#endif } void Connection::Private::checkAndConnect(const QString& userId, @@ -637,6 +654,7 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) }); } #endif // Quotient_E2EE_ENABLED + d->consumeDevicesList(data.takeDevicesList()); } void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, @@ -790,6 +808,21 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) #endif } +void Connection::Private::consumeDevicesList(DevicesList&& devicesList) +{ +#ifdef Quotient_E2EE_ENABLED + for(const auto &changed : devicesList.changed) { + outdatedUsers += changed; + } + for(const auto &left : devicesList.left) { + trackedUsers -= left; + outdatedUsers -= left; + deviceKeys.remove(left); + } + loadOutdatedUserDevices(); +#endif +} + void Connection::stopSync() { // If there's a sync loop, break it @@ -1784,3 +1817,43 @@ QVector Connection::availableRoomVersions() co } return result; } + +#ifdef Quotient_E2EE_ENABLED +void Connection::Private::createDevicesList() +{ + for(const auto &room : q->allRooms()) { + if(!room->usesEncryption()) { + continue; + } + for(const auto &user : room->users()) { + if(user->id() != q->userId()) { + trackedUsers += user->id(); + } + } + } + outdatedUsers += trackedUsers; + loadOutdatedUserDevices(); +} + +void Connection::Private::loadOutdatedUserDevices() +{ + QHash users; + for(const auto &user : outdatedUsers) { + users[user] += QStringList(); + } + if(currentQueryKeysJob) { + currentQueryKeysJob->abandon(); + currentQueryKeysJob = nullptr; + } + auto queryKeysJob = q->callApi(users); + currentQueryKeysJob = queryKeysJob; + connect(queryKeysJob, &BaseJob::success, q, [=](){ + const auto data = queryKeysJob->deviceKeys(); + for(const auto &[user, keys] : asKeyValueRange(data)) { + //TODO Check key signature + deviceKeys[user] = keys; + outdatedUsers -= user; + } + }); +} +#endif diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index 396e77eb..9c54888c 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -99,6 +99,34 @@ SyncRoomData::SyncRoomData(QString roomId_, JoinState joinState, fromJson(unreadJson.value(HighlightCountKey), highlightCount); } +QDebug Quotient::operator<<(QDebug dbg, const DevicesList& devicesList) +{ + QDebugStateSaver _(dbg); + QStringList sl; + if (!devicesList.changed.isEmpty()) + sl << QStringLiteral("changed: %1").arg(devicesList.changed.join(", ")); + if (!devicesList.left.isEmpty()) + sl << QStringLiteral("left %1").arg(devicesList.left.join(", ")); + dbg.nospace().noquote() << sl.join(QStringLiteral("; ")); + return dbg; +} + +void JsonObjectConverter::dumpTo(QJsonObject& jo, + const DevicesList& rs) +{ + addParam(jo, QStringLiteral("changed"), + rs.changed); + addParam(jo, QStringLiteral("left"), + rs.left); +} + +void JsonObjectConverter::fillFrom(const QJsonObject& jo, + DevicesList& rs) +{ + fromJson(jo["changed"_ls], rs.changed); + fromJson(jo["left"_ls], rs.left); +} + SyncData::SyncData(const QString& cacheFileName) { QFileInfo cacheFileInfo { cacheFileName }; @@ -133,6 +161,8 @@ std::pair SyncData::cacheVersion() return { MajorCacheVersion, 2 }; } +DevicesList&& SyncData::takeDevicesList() { return std::move(devicesList); } + QJsonObject SyncData::loadJson(const QString& fileName) { QFile roomFile { fileName }; diff --git a/lib/syncdata.h b/lib/syncdata.h index 36d2e0bf..7fa77eda 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -41,6 +41,27 @@ struct JsonObjectConverter { static void fillFrom(const QJsonObject& jo, RoomSummary& rs); }; +/// Information on e2e device updates. Note: only present on an +/// incremental sync. +struct DevicesList { + /// List of users who have updated their device identity keys, or who + /// now share an encrypted room with the client since the previous + /// sync response. + QStringList changed; + + /// List of users with whom we do not share any encrypted rooms + /// anymore since the previous sync response. + QStringList left; +}; + +QDebug operator<<(QDebug dhg, const DevicesList &devicesList); + +template <> +struct JsonObjectConverter { + static void dumpTo(QJsonObject &jo, const DevicesList &dev); + static void fillFrom(const QJsonObject& jo, DevicesList& rs); +}; + class SyncRoomData { public: QString roomId; @@ -85,6 +106,7 @@ public: return deviceOneTimeKeysCount_; } SyncDataList&& takeRoomData(); + DevicesList&& takeDevicesList(); QString nextBatch() const { return nextBatch_; } @@ -102,6 +124,7 @@ private: SyncDataList roomData; QStringList unresolvedRoomIds; QHash deviceOneTimeKeysCount_; + DevicesList devicesList; static QJsonObject loadJson(const QString& fileName); }; -- cgit v1.2.3 From 1f2ae094b29aa3fc237919659ced66c6d236d068 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 16 May 2021 19:58:00 +0200 Subject: Clear current query job when it finished --- lib/connection.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/connection.cpp b/lib/connection.cpp index 66590bd8..41dd71f6 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1848,6 +1848,7 @@ void Connection::Private::loadOutdatedUserDevices() auto queryKeysJob = q->callApi(users); currentQueryKeysJob = queryKeysJob; connect(queryKeysJob, &BaseJob::success, q, [=](){ + currentQueryKeysJob = nullptr; const auto data = queryKeysJob->deviceKeys(); for(const auto &[user, keys] : asKeyValueRange(data)) { //TODO Check key signature -- cgit v1.2.3 From e0945db3c4c539040f07ff7683efa9dc4e6b9e6a Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 18 May 2021 21:44:09 +0200 Subject: Actually load devices from sync data and filter relevant users --- lib/connection.cpp | 8 ++++++-- lib/syncdata.cpp | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 41dd71f6..8ceb2a44 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -812,14 +812,18 @@ void Connection::Private::consumeDevicesList(DevicesList&& devicesList) { #ifdef Quotient_E2EE_ENABLED for(const auto &changed : devicesList.changed) { - outdatedUsers += changed; + if(trackedUsers.contains(changed)) { + outdatedUsers += changed; + } } for(const auto &left : devicesList.left) { trackedUsers -= left; outdatedUsers -= left; deviceKeys.remove(left); } - loadOutdatedUserDevices(); + if(!outdatedUsers.isEmpty()) { + loadOutdatedUserDevices(); + } #endif } diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index 9c54888c..d0533fc9 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -205,6 +205,10 @@ void SyncData::parseJson(const QJsonObject& json, const QString& baseDir) fromJson(json.value("device_one_time_keys_count"_ls), deviceOneTimeKeysCount_); + if(json.contains("device_lists")) { + fromJson(json.value("device_lists"), devicesList); + } + auto rooms = json.value("rooms"_ls).toObject(); auto totalRooms = 0; auto totalEvents = 0; -- cgit v1.2.3 From c408b460bea2010c6745e03c549e136d6b1d9ec6 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 19 May 2021 00:35:53 +0200 Subject: Start tracking user's devices when a a room starts being encrypted --- lib/connection.cpp | 13 +++++++++++++ lib/room.cpp | 3 +++ 2 files changed, 16 insertions(+) diff --git a/lib/connection.cpp b/lib/connection.cpp index 8ceb2a44..2864f3b3 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1861,4 +1861,17 @@ void Connection::Private::loadOutdatedUserDevices() } }); } + +void Connection::newEncryptedRoom(Room *room) +{ + for(const auto &user : room->users()) { + if(!d->trackedUsers.contains(user->id())) { + d->trackedUsers += user->id(); + d->outdatedUsers += user->id(); + } + } + if(!d->outdatedUsers.isEmpty()) { + d->loadOutdatedUserDevices(); + } +} #endif diff --git a/lib/room.cpp b/lib/room.cpp index 1a7a9911..b6022f1b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -475,6 +475,9 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) emit baseStateLoaded(); return this == r; // loadedRoomState fires only once per room }); + connectSingleShot(this, &Room::encryption, this, [=](){ + connection->newEncryptedRoom(this); + }); qCDebug(STATE) << "New" << terse << initialJoinState << "Room:" << id; } -- cgit v1.2.3 From f451813f21a76e8c011bbd27f4ded1d31044a572 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 19 May 2021 22:25:50 +0200 Subject: Update tracked users list when new user joins encrypted room --- lib/connection.cpp | 2 +- lib/room.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 2864f3b3..b87610b7 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1862,7 +1862,7 @@ void Connection::Private::loadOutdatedUserDevices() }); } -void Connection::newEncryptedRoom(Room *room) +void Connection::encryptionUpdate(Room *room) { for(const auto &user : room->users()) { if(!d->trackedUsers.contains(user->id())) { diff --git a/lib/room.cpp b/lib/room.cpp index b6022f1b..2707842c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -476,7 +476,12 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) return this == r; // loadedRoomState fires only once per room }); connectSingleShot(this, &Room::encryption, this, [=](){ - connection->newEncryptedRoom(this); + connection->encryptionUpdate(this); + }); + connect(this, &Room::userAdded, this, [=](){ + if(usesEncryption()) { + connection->encryptionUpdate(this); + } }); qCDebug(STATE) << "New" << terse << initialJoinState << "Room:" << id; } -- cgit v1.2.3 From 6449f66152396ed539904b0e89d41601aeadf30d Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 19 May 2021 23:23:46 +0200 Subject: Verify deviceKeys signatures --- lib/connection.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index b87610b7..06b9bcbc 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1855,8 +1855,22 @@ void Connection::Private::loadOutdatedUserDevices() currentQueryKeysJob = nullptr; const auto data = queryKeysJob->deviceKeys(); for(const auto &[user, keys] : asKeyValueRange(data)) { - //TODO Check key signature - deviceKeys[user] = keys; + deviceKeys[user].clear(); + for(const auto &device : keys) { + if(device.userId != user) { + qCWarning(E2EE) << "mxId mismatch during device key verification:" << device.userId << user; + continue; + } + if(!device.algorithms.contains("m.olm.v1.curve25519-aes-sha2") || !device.algorithms.contains("m.megolm.v1.aes-sha2")) { + qCWarning(E2EE) << "Unsupported encryption algorithms found" << device.algorithms; + continue; + } + if(verifyIdentitySignature(device, device.deviceId, device.userId)) { + qCWarning(E2EE) << "Failed to verify devicekeys signature. Skipping this device"; + continue; + } + deviceKeys[user][device.deviceId] = device; + } outdatedUsers -= user; } }); -- cgit v1.2.3 From 5d3fe54fdfae4b74272a80c4bbe8f5d8a3e4c5cb Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 20 May 2021 15:44:02 +0200 Subject: Fix signature verification logic --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 06b9bcbc..6facd316 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1865,7 +1865,7 @@ void Connection::Private::loadOutdatedUserDevices() qCWarning(E2EE) << "Unsupported encryption algorithms found" << device.algorithms; continue; } - if(verifyIdentitySignature(device, device.deviceId, device.userId)) { + if(!verifyIdentitySignature(device, device.deviceId, device.userId)) { qCWarning(E2EE) << "Failed to verify devicekeys signature. Skipping this device"; continue; } -- cgit v1.2.3 From 265f105d77bf91c127c363b0c880357f91df7db4 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 21 May 2021 00:29:08 +0200 Subject: Cache deviceslist across restarts --- lib/connection.cpp | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++- lib/csapi/keys.h | 5 +++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 6facd316..e7a26f4b 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -258,6 +258,8 @@ public: #ifdef Quotient_E2EE_ENABLED void loadOutdatedUserDevices(); void createDevicesList(); + void saveDevicesList(); + void loadDevicesList(); #endif }; @@ -482,7 +484,7 @@ void Connection::Private::completeSetup(const QString& mxId) q->reloadCapabilities(); #ifdef Quotient_E2EE_ENABLED connectSingleShot(q, &Connection::syncDone, q, [=](){ - createDevicesList(); + loadDevicesList(); }); #endif } @@ -1873,6 +1875,7 @@ void Connection::Private::loadOutdatedUserDevices() } outdatedUsers -= user; } + saveDevicesList(); }); } @@ -1888,4 +1891,98 @@ void Connection::encryptionUpdate(Room *room) d->loadOutdatedUserDevices(); } } + +void Connection::Private::saveDevicesList() +{ + if (!cacheState) + return; + + QElapsedTimer et; + et.start(); + + QFile outFile { q->stateCacheDir().filePath("deviceslist.json") }; + if (!outFile.open(QFile::WriteOnly)) { + qCWarning(MAIN) << "Error opening" << outFile.fileName() << ":" + << outFile.errorString(); + qCWarning(MAIN) << "Caching the rooms state disabled"; + cacheState = false; + return; + } + + QJsonObject rootObj { + { QStringLiteral("cache_version"), + QJsonObject { + { QStringLiteral("major"), SyncData::cacheVersion().first }, + { QStringLiteral("minor"), SyncData::cacheVersion().second } } } + }; + { + QJsonObject trackedUsersJson; + QJsonObject outdatedUsersJson; + for (const auto &user : trackedUsers) { + trackedUsersJson.insert(user, QJsonValue::Null); + } + for (const auto &user : outdatedUsers) { + outdatedUsersJson.insert(user, QJsonValue::Null); + } + rootObj.insert(QStringLiteral("tracked_users"), trackedUsersJson); + rootObj.insert(QStringLiteral("outdated_users"), outdatedUsersJson); + QJsonObject devicesList = toJson>>(deviceKeys); + rootObj.insert(QStringLiteral("devices_list"), devicesList); + rootObj.insert(QStringLiteral("sync_token"), q->nextBatchToken()); + } + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + const auto data = + cacheToBinary ? QCborValue::fromJsonValue(rootObj).toCbor() + : QJsonDocument(rootObj).toJson(QJsonDocument::Compact); +#else + QJsonDocument json { rootObj }; + const auto data = cacheToBinary ? json.toBinaryData() + : json.toJson(QJsonDocument::Compact); +#endif + qCDebug(PROFILER) << "DeviceList generated in" << et; + + outFile.write(data.data(), data.size()); + qCDebug(E2EE) << "DevicesList saved to" << outFile.fileName(); +} + +void Connection::Private::loadDevicesList() +{ + QFile file { q->stateCacheDir().filePath("deviceslist.json") }; + if(!file.exists() || !file.open(QIODevice::ReadOnly)) { + qCDebug(E2EE) << "No devicesList cache exists. Creating new"; + createDevicesList(); + return; + } + auto data = file.readAll(); + const auto json = data.startsWith('{') + ? QJsonDocument::fromJson(data).object() +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + : QCborValue::fromCbor(data).toJsonValue().toObject() +#else + : QJsonDocument::fromBinaryData(data).object() +#endif + ; + if (json.isEmpty()) { + qCWarning(MAIN) << "DevicesList cache is broken or empty, discarding"; + createDevicesList(); + return; + } + for(const auto &user : json["tracked_users"].toArray()) { + trackedUsers += user.toString(); + } + for(const auto &user : json["outdated_users"].toArray()) { + outdatedUsers += user.toString(); + } + + deviceKeys = fromJson>>(json["devices_list"].toObject()); + auto oldToken = json["sync_token"].toString(); + auto changesJob = q->callApi(oldToken, q->nextBatchToken()); + connect(changesJob, &BaseJob::success, q, [=](){ + for(const auto &user : changesJob->changed()) { + outdatedUsers += user; + } + loadOutdatedUserDevices(); + }); +} #endif diff --git a/lib/csapi/keys.h b/lib/csapi/keys.h index 7db09e8d..b1cc640c 100644 --- a/lib/csapi/keys.h +++ b/lib/csapi/keys.h @@ -166,6 +166,11 @@ struct JsonObjectConverter { fillFromJson(jo, result); fromJson(jo.value("unsigned"_ls), result.unsignedData); } + static void dumpTo(QJsonObject& jo, const QueryKeysJob::DeviceInformation& deviceInformation) + { + jo = toJson(deviceInformation); + //addParam<>(jo, "unsigned"_ls, deviceInformation.unsignedData); + } }; /*! \brief Claim one-time encryption keys. -- cgit v1.2.3 From 5f3e33e1c15be19f09d83a0d6f44d551021a9d44 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 5 Feb 2021 18:45:30 +0100 Subject: Implement key verification events --- CMakeLists.txt | 1 + lib/events/keyverificationevent.cpp | 193 ++++++++++++++++++++++++++++++++++++ lib/events/keyverificationevent.h | 167 +++++++++++++++++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 lib/events/keyverificationevent.cpp create mode 100644 lib/events/keyverificationevent.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5601a281..92a9b213 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,7 @@ list(APPEND lib_SRCS lib/events/encryptedevent.cpp lib/events/roomkeyevent.cpp lib/events/stickerevent.cpp + lib/events/keyverificationevent.cpp lib/jobs/requestdata.cpp lib/jobs/basejob.cpp lib/jobs/syncjob.cpp diff --git a/lib/events/keyverificationevent.cpp b/lib/events/keyverificationevent.cpp new file mode 100644 index 00000000..938b3bde --- /dev/null +++ b/lib/events/keyverificationevent.cpp @@ -0,0 +1,193 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "keyverificationevent.h" + +using namespace Quotient; + +KeyVerificationRequestEvent::KeyVerificationRequestEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationRequestEvent::fromDevice() const +{ + return contentJson()["from_device"_ls].toString(); +} + +QString KeyVerificationRequestEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QStringList KeyVerificationRequestEvent::methods() const +{ + QStringList methods; + for (const auto &method : contentJson()["methods"].toArray()) { + methods.append(method.toString()); + } + return methods; +} + +uint64_t KeyVerificationRequestEvent::timestamp() const +{ + return contentJson()["timestamp"_ls].toDouble(); +} + +KeyVerificationStartEvent::KeyVerificationStartEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationStartEvent::fromDevice() const +{ + return contentJson()["from_device"_ls].toString(); +} + +QString KeyVerificationStartEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QString KeyVerificationStartEvent::method() const +{ + return contentJson()["method"_ls].toString(); +} + +Omittable KeyVerificationStartEvent::nextMethod() const +{ + auto next = contentJson()["method"_ls]; + if (next.isUndefined()) { + return std::nullopt; + } + return next.toString(); +} + +QStringList KeyVerificationStartEvent::keyAgreementProtocols() const +{ + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + QStringList protocols; + for (const auto &proto : contentJson()["key_agreement_protocols"_ls].toArray()) { + protocols.append(proto.toString()); + } + return protocols; +} + +QStringList KeyVerificationStartEvent::hashes() const +{ + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + QStringList hashes; + for (const auto &hashItem : contentJson()["hashes"_ls].toArray()) { + hashes.append(hashItem.toString()); + } + return hashes; +} + +QStringList KeyVerificationStartEvent::messageAuthenticationCodes() const +{ + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + + QStringList codes; + for (const auto &code : contentJson()["message_authentication_codes"_ls].toArray()) { + codes.append(code.toString()); + } + return codes; +} + +QString KeyVerificationStartEvent::shortAuthenticationString() const +{ + return contentJson()["short_authentification_string"_ls].toString(); +} + +KeyVerificationAcceptEvent::KeyVerificationAcceptEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationAcceptEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QString KeyVerificationAcceptEvent::method() const +{ + return contentJson()["method"_ls].toString(); +} + +QString KeyVerificationAcceptEvent::keyAgreementProtocol() const +{ + return contentJson()["key_agreement_protocol"_ls].toString(); +} + +QString KeyVerificationAcceptEvent::hashData() const +{ + return contentJson()["hash"_ls].toString(); +} + +QStringList KeyVerificationAcceptEvent::shortAuthenticationString() const +{ + QStringList strings; + for (const auto &authenticationString : contentJson()["short_authentification_string"].toArray()) { + strings.append(authenticationString.toString()); + } + return strings; +} + +QString KeyVerificationAcceptEvent::commitement() const +{ + return contentJson()["commitement"].toString(); +} + +KeyVerificationCancelEvent::KeyVerificationCancelEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationCancelEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QString KeyVerificationCancelEvent::reason() const +{ + return contentJson()["reason"_ls].toString(); +} + +QString KeyVerificationCancelEvent::code() const +{ + return contentJson()["code"_ls].toString(); +} + +KeyVerificationKeyEvent::KeyVerificationKeyEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationKeyEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QString KeyVerificationKeyEvent::key() const +{ + return contentJson()["key"_ls].toString(); +} + +KeyVerificationMacEvent::KeyVerificationMacEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationMacEvent::transactionId() const +{ + return contentJson()["transaction_id"].toString(); +} + +QString KeyVerificationMacEvent::keys() const +{ + return contentJson()["keys"].toString(); +} + +QHash KeyVerificationMacEvent::mac() const +{ + QHash macs; + const auto macObj = contentJson()["mac"_ls].toObject(); + for (auto mac = macObj.constBegin(); mac != macObj.constEnd(); mac++) { + macs.insert(mac.key(), mac.value().toString()); + } + return macs; +} diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h new file mode 100644 index 00000000..13e7dcdd --- /dev/null +++ b/lib/events/keyverificationevent.h @@ -0,0 +1,167 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "event.h" + +namespace Quotient { + +/// Requests a key verification with another user's devices. +/// Typically sent as a to-device event. +class KeyVerificationRequestEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.request", KeyVerificationRequestEvent) + + explicit KeyVerificationRequestEvent(const QJsonObject& obj); + + /// The device ID which is initiating the request. + QString fromDevice() const; + + /// An opaque identifier for the verification request. Must + /// be unique with respect to the devices involved. + QString transactionId() const; + + /// The verification methods supported by the sender. + QStringList methods() const; + + /// The POSIX timestamp in milliseconds for when the request was + /// made. If the request is in the future by more than 5 minutes or + /// more than 10 minutes in the past, the message should be ignored + /// by the receiver. + uint64_t timestamp() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationRequestEvent) + +/// Begins a key verification process. +class KeyVerificationStartEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.start", KeyVerificationStartEvent) + + explicit KeyVerificationStartEvent(const QJsonObject &obj); + + /// The device ID which is initiating the process. + QString fromDevice() const; + + /// An opaque identifier for the verification request. Must + /// be unique with respect to the devices involved. + QString transactionId() const; + + /// The verification method to use. + QString method() const; + + /// Optional method to use to verify the other user's key with. + Omittable nextMethod() const; + + // SAS.V1 methods + + /// The key agreement protocols the sending device understands. + /// \note Only exist if method is m.sas.v1 + QStringList keyAgreementProtocols() const; + + /// The hash methods the sending device understands. + /// \note Only exist if method is m.sas.v1 + QStringList hashes() const; + + /// The message authentication codes that the sending device understands. + /// \note Only exist if method is m.sas.v1 + QStringList messageAuthenticationCodes() const; + + /// The SAS methods the sending device (and the sending device's + /// user) understands. + /// \note Only exist if method is m.sas.v1 + QString shortAuthenticationString() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationStartEvent) + +/// Accepts a previously sent m.key.verification.start message. +/// Typically sent as a to-device event. +class KeyVerificationAcceptEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.accept", KeyVerificationAcceptEvent) + + explicit KeyVerificationAcceptEvent(const QJsonObject& obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// The verification method to use. Must be 'm.sas.v1'. + QString method() const; + + /// The key agreement protocol the device is choosing to use, out of + /// the options in the m.key.verification.start message. + QString keyAgreementProtocol() const; + + /// The hash method the device is choosing to use, out of the + /// options in the m.key.verification.start message. + QString hashData() const; + + /// The message authentication code the device is choosing to use, out + /// of the options in the m.key.verification.start message. + QString messageAuthenticationCode() const; + + /// The SAS methods both devices involved in the verification process understand. + QStringList shortAuthenticationString() const; + + /// The hash (encoded as unpadded base64) of the concatenation of the + /// device's ephemeral public key (encoded as unpadded base64) and the + /// canonical JSON representation of the m.key.verification.start message. + QString commitement() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationAcceptEvent) + +class KeyVerificationCancelEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.cancel", KeyVerificationCancelEvent) + + explicit KeyVerificationCancelEvent(const QJsonObject &obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// A human readable description of the code. The client should only + /// rely on this string if it does not understand the code. + QString reason() const; + + /// The error code for why the process/request was cancelled by the user. + QString code() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationCancelEvent) + +/// Sends the ephemeral public key for a device to the partner device. +/// Typically sent as a to-device event. +class KeyVerificationKeyEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent) + + explicit KeyVerificationKeyEvent(const QJsonObject &obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// The device's ephemeral public key, encoded as unpadded base64. + QString key() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationKeyEvent) + +/// Sends the MAC of a device's key to the partner device. +class KeyVerificationMacEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.mac", KeyVerificationMacEvent) + + explicit KeyVerificationMacEvent(const QJsonObject &obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// The device's ephemeral public key, encoded as unpadded base64. + QString keys() const; + + QHash mac() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationMacEvent) +} // namespace Quotient -- cgit v1.2.3 From a2b65a3abb635a478555b61de33cb5257d8dd34e Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 8 Jun 2021 21:38:26 +0200 Subject: Query for keys less and actually load users from cache --- lib/connection.cpp | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index e7a26f4b..35b2f7fc 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -482,11 +482,6 @@ void Connection::Private::completeSetup(const QString& mxId) emit q->stateChanged(); emit q->connected(); q->reloadCapabilities(); -#ifdef Quotient_E2EE_ENABLED - connectSingleShot(q, &Connection::syncDone, q, [=](){ - loadDevicesList(); - }); -#endif } void Connection::Private::checkAndConnect(const QString& userId, @@ -636,11 +631,6 @@ QJsonObject toJson(const DirectChatsMap& directChats) void Connection::onSyncSuccess(SyncData&& data, bool fromCache) { - d->data->setLastEvent(data.nextBatch()); - d->consumeRoomData(data.takeRoomData(), fromCache); - d->consumeAccountData(data.takeAccountData()); - d->consumePresenceData(data.takePresenceData()); - d->consumeToDeviceEvents(data.takeToDeviceEvents()); #ifdef Quotient_E2EE_ENABLED if(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() && !d->isUploadingKeys) { d->isUploadingKeys = true; @@ -655,8 +645,19 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->isUploadingKeys = false; }); } -#endif // Quotient_E2EE_ENABLED + static bool first = true; + if(first) { + d->loadDevicesList(); + first = false; + } + d->consumeDevicesList(data.takeDevicesList()); +#endif // Quotient_E2EE_ENABLED + d->data->setLastEvent(data.nextBatch()); + d->consumeRoomData(data.takeRoomData(), fromCache); + d->consumeAccountData(data.takeAccountData()); + d->consumePresenceData(data.takePresenceData()); + d->consumeToDeviceEvents(data.takeToDeviceEvents()); } void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, @@ -813,9 +814,11 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) void Connection::Private::consumeDevicesList(DevicesList&& devicesList) { #ifdef Quotient_E2EE_ENABLED + bool hasNewOutdatedUser = false; for(const auto &changed : devicesList.changed) { if(trackedUsers.contains(changed)) { outdatedUsers += changed; + hasNewOutdatedUser = true; } } for(const auto &left : devicesList.left) { @@ -823,7 +826,7 @@ void Connection::Private::consumeDevicesList(DevicesList&& devicesList) outdatedUsers -= left; deviceKeys.remove(left); } - if(!outdatedUsers.isEmpty()) { + if(hasNewOutdatedUser) { loadOutdatedUserDevices(); } #endif @@ -1881,13 +1884,15 @@ void Connection::Private::loadOutdatedUserDevices() void Connection::encryptionUpdate(Room *room) { + bool hasNewOutdatedUser = false; for(const auto &user : room->users()) { if(!d->trackedUsers.contains(user->id())) { d->trackedUsers += user->id(); d->outdatedUsers += user->id(); + hasNewOutdatedUser = true; } } - if(!d->outdatedUsers.isEmpty()) { + if(hasNewOutdatedUser) { d->loadOutdatedUserDevices(); } } @@ -1916,13 +1921,13 @@ void Connection::Private::saveDevicesList() { QStringLiteral("minor"), SyncData::cacheVersion().second } } } }; { - QJsonObject trackedUsersJson; - QJsonObject outdatedUsersJson; + QJsonArray trackedUsersJson; + QJsonArray outdatedUsersJson; for (const auto &user : trackedUsers) { - trackedUsersJson.insert(user, QJsonValue::Null); + trackedUsersJson += user; } for (const auto &user : outdatedUsers) { - outdatedUsersJson.insert(user, QJsonValue::Null); + outdatedUsersJson += user; } rootObj.insert(QStringLiteral("tracked_users"), trackedUsersJson); rootObj.insert(QStringLiteral("outdated_users"), outdatedUsersJson); @@ -1979,10 +1984,14 @@ void Connection::Private::loadDevicesList() auto oldToken = json["sync_token"].toString(); auto changesJob = q->callApi(oldToken, q->nextBatchToken()); connect(changesJob, &BaseJob::success, q, [=](){ + bool hasNewOutdatedUser = false; for(const auto &user : changesJob->changed()) { outdatedUsers += user; + hasNewOutdatedUser = true; + } + if(hasNewOutdatedUser) { + loadOutdatedUserDevices(); } - loadOutdatedUserDevices(); }); } #endif -- cgit v1.2.3 From d997bbf54e755c42b62fadca8ee63b27aa0e7480 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 9 Jun 2021 15:59:10 +0200 Subject: More fixes --- lib/connection.cpp | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 35b2f7fc..9ea09258 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -107,6 +107,7 @@ public: QSet outdatedUsers; QHash> deviceKeys; QueryKeysJob *currentQueryKeysJob = nullptr; + bool encryptionUpdateRequired = false; #endif GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -257,7 +258,6 @@ public: } #ifdef Quotient_E2EE_ENABLED void loadOutdatedUserDevices(); - void createDevicesList(); void saveDevicesList(); void loadDevicesList(); #endif @@ -658,6 +658,10 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->consumeAccountData(data.takeAccountData()); d->consumePresenceData(data.takePresenceData()); d->consumeToDeviceEvents(data.takeToDeviceEvents()); + if(d->encryptionUpdateRequired) { + d->loadOutdatedUserDevices(); + d->encryptionUpdateRequired = false; + } } void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, @@ -1828,22 +1832,6 @@ QVector Connection::availableRoomVersions() co } #ifdef Quotient_E2EE_ENABLED -void Connection::Private::createDevicesList() -{ - for(const auto &room : q->allRooms()) { - if(!room->usesEncryption()) { - continue; - } - for(const auto &user : room->users()) { - if(user->id() != q->userId()) { - trackedUsers += user->id(); - } - } - } - outdatedUsers += trackedUsers; - loadOutdatedUserDevices(); -} - void Connection::Private::loadOutdatedUserDevices() { QHash users; @@ -1884,17 +1872,13 @@ void Connection::Private::loadOutdatedUserDevices() void Connection::encryptionUpdate(Room *room) { - bool hasNewOutdatedUser = false; for(const auto &user : room->users()) { if(!d->trackedUsers.contains(user->id())) { d->trackedUsers += user->id(); d->outdatedUsers += user->id(); - hasNewOutdatedUser = true; + d->encryptionUpdateRequired = true; } } - if(hasNewOutdatedUser) { - d->loadOutdatedUserDevices(); - } } void Connection::Private::saveDevicesList() @@ -1956,7 +1940,6 @@ void Connection::Private::loadDevicesList() QFile file { q->stateCacheDir().filePath("deviceslist.json") }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No devicesList cache exists. Creating new"; - createDevicesList(); return; } auto data = file.readAll(); @@ -1970,7 +1953,6 @@ void Connection::Private::loadDevicesList() ; if (json.isEmpty()) { qCWarning(MAIN) << "DevicesList cache is broken or empty, discarding"; - createDevicesList(); return; } for(const auto &user : json["tracked_users"].toArray()) { -- cgit v1.2.3 From 8b573fc9f9f4b65ace0fdc5b4598f1974e01f7e3 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 9 Jun 2021 16:05:16 +0200 Subject: Fix compilation without E2EE --- lib/connection.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/connection.cpp b/lib/connection.cpp index 9ea09258..10256d9c 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -658,10 +658,12 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->consumeAccountData(data.takeAccountData()); d->consumePresenceData(data.takePresenceData()); d->consumeToDeviceEvents(data.takeToDeviceEvents()); +#ifdef Quotient_E2EE_ENABLED if(d->encryptionUpdateRequired) { d->loadOutdatedUserDevices(); d->encryptionUpdateRequired = false; } +#endif } void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, -- 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(-) 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(-) 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(-) 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(+) 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(-) 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 d30a5153cca335ff6f23cc5a3019001913df0edd Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 10 Jun 2021 23:05:46 +0200 Subject: Fix JSON formatting for one-time-key signature creation --- lib/crypto/qolmaccount.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 6b7bc9a9..22b1faef 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -206,7 +206,7 @@ SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray &key, const QStr QByteArray QOlmAccount::signOneTimeKey(const QString &key) const { QJsonDocument j(QJsonObject{{"key", key}}); - return sign(j.toJson()); + return sign(j.toJson(QJsonDocument::Compact)); } std::optional QOlmAccount::removeOneTimeKeys(const std::unique_ptr &session) const -- 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(-) 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 40d6616ef0c4a9be20d5fe5e50f4b9959d0ab3d1 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 11 Jun 2021 18:37:58 +0200 Subject: Cleanup and Refactor EncryptionManager --- lib/connection.cpp | 45 +++--- lib/encryptionmanager.cpp | 352 +++++++--------------------------------------- lib/encryptionmanager.h | 20 +-- 3 files changed, 69 insertions(+), 348 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 10256d9c..2d040e8a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -118,7 +118,7 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; bool isUploadingKeys = false; - QScopedPointer encryptionManager; + EncryptionManager *encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -194,17 +194,14 @@ 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 {}; -#else // Quotient_E2EE_ENABLED +#else if (encryptedEvent.algorithm() != OlmV1Curve25519AesSha2AlgoKey) return {}; - const auto identityKey = - encryptionManager->account()->identityKeys().curve25519; + const auto identityKey = olmAccount->identityKeys().curve25519; const auto personalCipherObject = encryptedEvent.ciphertext(identityKey); if (personalCipherObject.isEmpty()) { @@ -212,11 +209,11 @@ public: return {}; } const auto decrypted = encryptionManager->sessionDecryptMessage( - personalCipherObject, encryptedEvent.senderKey().toLatin1()); + personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" << encryptedEvent.senderKey() - << encryptionManager->account()->oneTimeKeys().keys; + << olmAccount->oneTimeKeys().keys; return {}; } @@ -233,22 +230,18 @@ public: // TODO: keys to constants const auto decryptedEventObject = decryptedEvent->fullJson(); - const auto recipient = - decryptedEventObject.value("recipient"_ls).toString(); + const auto recipient = decryptedEventObject.value("recipient"_ls).toString(); if (recipient != data->userId()) { qCDebug(E2EE) << "Found user" << recipient << "instead of us" << data->userId() << "in Olm plaintext"; return {}; } - const auto ourKey = - decryptedEventObject.value("recipient_keys"_ls).toObject() - .value(Ed25519Key).toString(); - if (ourKey - != QString::fromUtf8( - encryptionManager->account()->identityKeys().ed25519)) { + const auto ourKey = decryptedEventObject.value("recipient_keys"_ls).toObject() + .value(Ed25519Key).toString(); + if (ourKey != QString::fromUtf8(olmAccount->identityKeys().ed25519)) { qCDebug(E2EE) << "Found key" << ourKey << "instead of ours own ed25519 key" - << encryptionManager->account()->identityKeys().ed25519 + << olmAccount->identityKeys().ed25519 << "in Olm plaintext"; return {}; } @@ -266,6 +259,7 @@ public: Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent), d(new Private(std::make_unique(server))) { + d->encryptionManager = new EncryptionManager(this); d->q = this; // All d initialization should occur before this line } @@ -791,21 +785,20 @@ 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) { - if (ee.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { - qCDebug(E2EE) << "Encrypted event" << ee.id() << "algorithm" - << ee.algorithm() << "is not supported"; + qWarning() << "Consuming to device events" << toDeviceEvents.size(); + if(toDeviceEvents.size() > 0) + visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { + if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { + qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); return; } - visit(*sessionDecryptMessage(ee), - [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { + visit(*sessionDecryptMessage(event), + [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); } else { - qCDebug(E2EE) - << "Encrypted event room id" << roomKeyEvent.roomId() + qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() << "is not found at the connection" << q->objectName(); } }, diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 53890fdb..b9bd6646 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -26,69 +26,16 @@ using std::move; class EncryptionManager::Private { public: - explicit Private(const QByteArray& encryptionAccountPickle, - float signedKeysProportion, float oneTimeKeyThreshold) + explicit Private() : q(nullptr) - , signedKeysProportion(move(signedKeysProportion)) - , oneTimeKeyThreshold(move(oneTimeKeyThreshold)) { - Q_ASSERT((0 <= signedKeysProportion) && (signedKeysProportion <= 1)); - Q_ASSERT((0 <= oneTimeKeyThreshold) && (oneTimeKeyThreshold <= 1)); - if (encryptionAccountPickle.isEmpty()) { - // new e2ee TODO: olmAccount.reset(new QOlmAccount()); - } else { - // new e2ee TODO: olmAccount.reset(new QOlmAccount(encryptionAccountPickle)); // TODO: passphrase even with qtkeychain? - } - /* - * Note about targetKeysNumber: - * - * From: https://github.com/Zil0/matrix-python-sdk/ - * File: matrix_client/crypto/olm_device.py - * - * Try to maintain half the number of one-time keys libolm can hold - * uploaded on the HS. This is because some keys will be claimed by - * peers but not used instantly, and we want them to stay in libolm, - * until the limit is reached and it starts discarding keys, starting by - * the oldest. - */ - targetKeysNumber = olmAccount->maxNumberOfOneTimeKeys() / 2; - targetOneTimeKeyCounts = { - { SignedCurve25519Key, - qRound(signedKeysProportion * targetKeysNumber) }, - { Curve25519Key, - qRound((1 - signedKeysProportion) * targetKeysNumber) } - }; - updateKeysToUpload(); } ~Private() = default; EncryptionManager* q; - UploadKeysJob* uploadIdentityKeysJob = nullptr; - UploadKeysJob* uploadOneTimeKeysInitJob = nullptr; - UploadKeysJob* uploadOneTimeKeysJob = nullptr; - QueryKeysJob* queryKeysJob = nullptr; - - std::unique_ptr olmAccount; - - float signedKeysProportion; - float oneTimeKeyThreshold; - int targetKeysNumber; - - void updateKeysToUpload(); - bool oneTimeKeyShouldUpload(); - - QHash oneTimeKeyCounts; - void setOneTimeKeyCounts(const QHash oneTimeKeyCountsNewValue) - { - oneTimeKeyCounts = oneTimeKeyCountsNewValue; - updateKeysToUpload(); - } - QHash oneTimeKeysToUploadCounts; - QHash targetOneTimeKeyCounts; - // A map from senderKey to InboundSession - QMap> sessions; // TODO: cache + std::map> sessions; // TODO: cache void updateDeviceKeys( const QHash>& deviceKeys) @@ -100,279 +47,76 @@ public: } } } - QString sessionDecrypt(const QOlmMessage& message, const QString& senderKey) + QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) { - // Try to decrypt message body using one of the known sessions for that - // device - /*bool sessionsPassed = false; - // new e2ee TODO: - for (auto &senderSession : sessions) { - if (senderSession == sessions.last()) { - sessionsPassed = true; - } - - const auto decryptedResult = senderSession->decrypt(message); - if (std::holds_alternative(decryptedResult)) { - qCDebug(E2EE) - << "Success decrypting Olm event using existing session" - << senderSession->sessionId(); - return std::get(decryptedResult); - } else { - const auto error = std::get(decryptedResult); - if (message.type() == QOlmMessage::PreKey) { - const auto matches = senderSession->matchesInboundSessionFrom(senderKey, message); - if (auto hasMatch = std::get_if(&matches)) { - if (hasMatch) { - // We had a matching session for a pre-key message, but - // it didn't work. This means something is wrong, so we - // fail now. - qCDebug(E2EE) - << "Error decrypting pre-key message with existing " - "Olm session" - << senderSession->sessionId() << "reason:" << error; - return QString(); - } - } + Q_ASSERT(message.type() == QOlmMessage::PreKey); + for(auto& session : sessions) { + const auto matches = session.second->matchesInboundSessionFrom(senderKey, message); + if(std::holds_alternative(matches) && std::get(matches)) { + qCDebug(E2EE) << "Found inbound session"; + const auto result = session.second->decrypt(message); + if(std::holds_alternative(result)) { + return std::get(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message"; + return {}; } - // Simply keep trying otherwise } } - if (sessionsPassed || sessions.empty()) { - 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"; - return QString(); - } - qCDebug(E2EE) << "No existing sessions"; - return QString(); - } - // We have a pre-key message without any matching session, in this - // case we should try to create one. - 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); - - if (const auto error = std::get_if(&sessionResult)) { - qCDebug(E2EE) << "Error decrypting pre-key message when trying " - "to establish a new session:" - << error; - return QString(); - } - - const auto newSession = std::get>(olmAccount->createInboundSessionFrom(senderKey.toUtf8(), preKeyMessage)); - - qCDebug(E2EE) << "Created new Olm session" << newSession->sessionId(); - - const auto decryptedResult = newSession->decrypt(message); - if (const auto error = std::get_if(&decryptedResult)) { - qCDebug(E2EE) - << "Error decrypting pre-key message with new session" - << error; - return QString(); - } - - if (auto error = olmAccount->removeOneTimeKeys(newSession)) { - qCDebug(E2EE) - << "Error removing one time keys" - << error.value(); + 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; + return {}; + } + std::unique_ptr newSession = std::move(std::get>(newSessionResult)); + // TODO Error handling? + olmAccount->removeOneTimeKeys(newSession); + const auto result = newSession->decrypt(message); + sessions[senderKey] = 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) { + const auto result = session.second->decrypt(message); + if(std::holds_alternative(result)) { + return std::get(result); } - //sessions.insert(senderKey, std::move(newSession)); TODO - //return std::get(decryptedResult); - }*/ - return QString(); + } + qCWarning(E2EE) << "Failed to decrypt message"; + return {}; } }; -EncryptionManager::EncryptionManager(const QByteArray& encryptionAccountPickle, - float signedKeysProportion, - float oneTimeKeyThreshold, QObject* parent) +EncryptionManager::EncryptionManager(QObject* parent) : QObject(parent) - , d(std::make_unique(std::move(encryptionAccountPickle), - std::move(signedKeysProportion), - std::move(oneTimeKeyThreshold))) + , d(std::make_unique()) { d->q = this; } EncryptionManager::~EncryptionManager() = default; -void EncryptionManager::uploadIdentityKeys(Connection* connection) -{ - // https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-keys-upload - DeviceKeys deviceKeys { - /* - * The ID of the user the device belongs to. Must match the user ID used - * when logging in. The ID of the device these keys belong to. Must - * match the device ID used when logging in. The encryption algorithms - * supported by this device. - */ - connection->userId(), - connection->deviceId(), - SupportedAlgorithms, - /* - * Public identity keys. The names of the properties should be in the - * format :. The keys themselves should be encoded - * as specified by the key algorithm. - */ - { { Curve25519Key + QStringLiteral(":") + connection->deviceId(), - d->olmAccount->identityKeys().curve25519 }, - { Ed25519Key + QStringLiteral(":") + connection->deviceId(), - d->olmAccount->identityKeys().curve25519 } }, - /* signatures should be provided after the unsigned deviceKeys - generation */ - {} - }; - - QJsonObject deviceKeysJsonObject = toJson(deviceKeys); - /* additionally removing signatures key, - * since we could not initialize deviceKeys - * without an empty signatures value: - */ - deviceKeysJsonObject.remove(QStringLiteral("signatures")); - /* - * Signatures for the device key object. - * A map from user ID, to a map from : to the - * signature. The signature is calculated using the process called Signing - * JSON. - */ - deviceKeys.signatures = { - { connection->userId(), - { { Ed25519Key + QStringLiteral(":") + connection->deviceId(), - d->olmAccount->sign(deviceKeysJsonObject) } } } - }; - - d->uploadIdentityKeysJob = connection->callApi(deviceKeys); - connect(d->uploadIdentityKeysJob, &BaseJob::success, this, [this] { - d->setOneTimeKeyCounts(d->uploadIdentityKeysJob->oneTimeKeyCounts()); - }); -} - -void EncryptionManager::uploadOneTimeKeys(Connection* connection, - bool forceUpdate) -{ - if (forceUpdate || d->oneTimeKeyCounts.isEmpty()) { - d->uploadOneTimeKeysInitJob = connection->callApi(); - connect(d->uploadOneTimeKeysInitJob, &BaseJob::success, this, [this] { - d->setOneTimeKeyCounts(d->uploadOneTimeKeysInitJob->oneTimeKeyCounts()); - }); - } - - int signedKeysToUploadCount = - d->oneTimeKeysToUploadCounts.value(SignedCurve25519Key, 0); - int unsignedKeysToUploadCount = - d->oneTimeKeysToUploadCounts.value(Curve25519Key, 0); - - d->olmAccount->generateOneTimeKeys(signedKeysToUploadCount - + unsignedKeysToUploadCount); - - QHash oneTimeKeys = {}; - const auto& olmAccountCurve25519OneTimeKeys = d->olmAccount->oneTimeKeys().curve25519(); - - int oneTimeKeysCounter = 0; - for (auto it = olmAccountCurve25519OneTimeKeys.cbegin(); - it != olmAccountCurve25519OneTimeKeys.cend(); ++it) { - QString keyId = it.key(); - QString keyType; - QVariant key; - if (oneTimeKeysCounter < signedKeysToUploadCount) { - QJsonObject message { { QStringLiteral("key"), - it.value() } }; - - QByteArray signedMessage = d->olmAccount->sign(message); - QJsonObject signatures { - { connection->userId(), - QJsonObject { { Ed25519Key + QStringLiteral(":") - + connection->deviceId(), - QString::fromUtf8(signedMessage) } } } - }; - message.insert(QStringLiteral("signatures"), signatures); - key = message; - keyType = SignedCurve25519Key; - } else { - key = it.value(); - keyType = Curve25519Key; - } - ++oneTimeKeysCounter; - oneTimeKeys.insert(QString("%1:%2").arg(keyType).arg(keyId), key); - } - d->uploadOneTimeKeysJob = - connection->callApi(none, oneTimeKeys); - connect(d->uploadOneTimeKeysJob, &BaseJob::success, this, [this] { - d->setOneTimeKeyCounts(d->uploadOneTimeKeysJob->oneTimeKeyCounts()); - }); - // new e2ee TODO: d->olmAccount->markKeysAsPublished(); - qCDebug(E2EE) << QString("Uploaded new one-time keys: %1 signed, %2 unsigned.") - .arg(signedKeysToUploadCount) - .arg(unsignedKeysToUploadCount); -} - -void EncryptionManager::updateOneTimeKeyCounts( - Connection* connection, const QHash& deviceOneTimeKeysCount) -{ - d->oneTimeKeyCounts = deviceOneTimeKeysCount; - if (d->oneTimeKeyShouldUpload()) { - qCDebug(E2EE) << "Uploading new one-time keys."; - uploadOneTimeKeys(connection); - } -} - -void Quotient::EncryptionManager::updateDeviceKeys( - Connection* connection, const QHash& deviceKeys) -{ - d->queryKeysJob = connection->callApi(deviceKeys); - connect(d->queryKeysJob, &BaseJob::success, this, - [this] { d->updateDeviceKeys(d->queryKeysJob->deviceKeys()); }); -} - QString EncryptionManager::sessionDecryptMessage( - const QJsonObject& personalCipherObject, const QByteArray& senderKey) + 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->sessionDecrypt(preKeyMessage, senderKey); + decrypted = d->sessionDecryptPrekey(preKeyMessage, senderKey, account); } else if (type == 1) { - QOlmMessage message(body, QOlmMessage::PreKey); - decrypted = d->sessionDecrypt(message, senderKey); + QOlmMessage message(body, QOlmMessage::General); + decrypted = d->sessionDecryptGeneral(message, senderKey); } return decrypted; } - -QByteArray EncryptionManager::olmAccountPickle() -{ - // new e2ee TODO: return d->olmAccount->pickle(); // TODO: passphrase even with qtkeychain? - return {}; -} - -QOlmAccount *EncryptionManager::account() const -{ - return d->olmAccount.get(); -} - -void EncryptionManager::Private::updateKeysToUpload() -{ - for (auto it = targetOneTimeKeyCounts.cbegin(); - it != targetOneTimeKeyCounts.cend(); ++it) { - int numKeys = oneTimeKeyCounts.value(it.key(), 0); - int numToCreate = qMax(it.value() - numKeys, 0); - oneTimeKeysToUploadCounts.insert(it.key(), numToCreate); - } -} - -bool EncryptionManager::Private::oneTimeKeyShouldUpload() -{ - if (oneTimeKeyCounts.empty()) - return true; - for (auto it = targetOneTimeKeyCounts.cbegin(); - it != targetOneTimeKeyCounts.cend(); ++it) { - if (oneTimeKeyCounts.value(it.key(), 0) - < it.value() * oneTimeKeyThreshold) - return true; - } - return false; -} #endif // Quotient_E2EE_ENABLED diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h index 9d2c8138..17f4f853 100644 --- a/lib/encryptionmanager.h +++ b/lib/encryptionmanager.h @@ -17,26 +17,10 @@ class EncryptionManager : public QObject { Q_OBJECT public: - // TODO: store constats separately? - // TODO: 0.5 oneTimeKeyThreshold instead of 0.1? - explicit EncryptionManager( - const QByteArray& encryptionAccountPickle = QByteArray(), - float signedKeysProportion = 1, float oneTimeKeyThreshold = float(0.1), - QObject* parent = nullptr); + explicit EncryptionManager(QObject* parent = nullptr); ~EncryptionManager(); - - void uploadIdentityKeys(Connection* connection); - void uploadOneTimeKeys(Connection* connection, bool forceUpdate = false); - void - updateOneTimeKeyCounts(Connection* connection, - const QHash& deviceOneTimeKeysCount); - void updateDeviceKeys(Connection* connection, - const QHash& deviceKeys); QString sessionDecryptMessage(const QJsonObject& personalCipherObject, - const QByteArray& senderKey); - QByteArray olmAccountPickle(); - - QOlmAccount* account() const; + const QByteArray& senderKey, std::unique_ptr& account); private: class Private; -- cgit v1.2.3 From 3e51359d1b7891b9a6e4611662c753653c3618bf Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 2 Jun 2021 19:39:34 +0200 Subject: Things --- lib/connection.cpp | 1 + lib/encryptionmanager.h | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/connection.cpp b/lib/connection.cpp index 2d040e8a..494db170 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -796,6 +796,7 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) visit(*sessionDecryptMessage(event), [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { + qWarning() << "IT'S A ROOMKEY EVENT, RUUUUUUUUUUUUUUUUUN"; detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); } else { qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h index 17f4f853..db9bff07 100644 --- a/lib/encryptionmanager.h +++ b/lib/encryptionmanager.h @@ -19,6 +19,7 @@ class EncryptionManager : public QObject { public: explicit EncryptionManager(QObject* parent = nullptr); ~EncryptionManager(); + QString sessionDecryptMessage(const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account); -- cgit v1.2.3 From 1ce9d98cc3957af5a81f6672f3fb86a4dd170ed9 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 24 May 2021 00:40:30 +0200 Subject: Uncomment some stuff --- lib/connection.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 494db170..d652c113 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -793,13 +793,13 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) return; } - visit(*sessionDecryptMessage(event), - [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { + visit(*sessionDecryptMessage(ee), + [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { - qWarning() << "IT'S A ROOMKEY EVENT, RUUUUUUUUUUUUUUUUUN"; detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); } else { - qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() + qCDebug(E2EE) + << "Encrypted event room id" << roomKeyEvent.roomId() << "is not found at the connection" << q->objectName(); } }, -- cgit v1.2.3 From 2c5c990f4248a3112d26c3a92e011655064f3fcf Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 24 May 2021 18:04:30 +0200 Subject: Update the OlmAccountPickle in the accountsettings when the olmaccount changes --- lib/connection.cpp | 8 ++++++-- lib/crypto/qolmaccount.cpp | 6 ++++-- lib/crypto/qolmaccount.h | 9 +++++++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index d652c113..d7115885 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -456,7 +456,11 @@ void Connection::Private::completeSetup(const QString& mxId) AccountSettings accountSettings(data->userId()); // init olmAccount - olmAccount = std::make_unique(data->userId(), data->deviceId()); + olmAccount = std::make_unique(data->userId(), data->deviceId(), q); + connect(olmAccount.get(), &QOlmAccount::needsSave, q, [=](){ + auto pickle = olmAccount->pickle(Unencrypted{}); + AccountSettings(data->userId()).setEncryptionAccountPickle(std::get(pickle)); + }); if (accountSettings.encryptionAccountPickle().isEmpty()) { // create new account and save unpickle data @@ -1283,7 +1287,7 @@ bool Connection::isLoggedIn() const { return !accessToken().isEmpty(); } #ifdef Quotient_E2EE_ENABLED QOlmAccount *Connection::olmAccount() const { - return d->olmAccount.get(); //d->encryptionManager->account(); + return d->olmAccount.get(); } #endif // Quotient_E2EE_ENABLED diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 22b1faef..44959ac2 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -46,8 +46,9 @@ QByteArray getRandom(size_t bufferSize) return buffer; } -QOlmAccount::QOlmAccount(const QString &userId, const QString &deviceId) - : m_userId(userId) +QOlmAccount::QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent) + : QObject(parent) + , m_userId(userId) , m_deviceId(deviceId) { } @@ -158,6 +159,7 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const if (error == olm_error()) { throw lastError(m_account); } + Q_EMIT needsSave(); return error; } diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index 54d8506c..1f94ab2b 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -23,10 +23,11 @@ class Connection; //! \code{.cpp} //! const auto olmAccount = new QOlmAccount(this); //! \endcode -class QOlmAccount +class QOlmAccount : public QObject { + Q_OBJECT public: - QOlmAccount(const QString &userId, const QString &deviceId); + QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent = nullptr); ~QOlmAccount(); //! Creates a new instance of OlmAccount. During the instantiation @@ -98,6 +99,10 @@ public: // HACK do not use directly QOlmAccount(OlmAccount *account); OlmAccount *data(); + +Q_SIGNALS: + void needsSave() const; + private: OlmAccount *m_account = nullptr; // owning QString m_userId; -- cgit v1.2.3 From f89c05408bc9a4bf59366f92c50f055d527b9a28 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 12 Jun 2021 18:58:54 +0200 Subject: Remove unrelated changes --- lib/connection.cpp | 7 +++---- lib/encryptionmanager.h | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index d7115885..ab5e030b 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -797,13 +797,12 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) return; } - visit(*sessionDecryptMessage(ee), - [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { + visit(*sessionDecryptMessage(event), + [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); } else { - qCDebug(E2EE) - << "Encrypted event room id" << roomKeyEvent.roomId() + qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() << "is not found at the connection" << q->objectName(); } }, diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h index db9bff07..17f4f853 100644 --- a/lib/encryptionmanager.h +++ b/lib/encryptionmanager.h @@ -19,7 +19,6 @@ class EncryptionManager : public QObject { public: explicit EncryptionManager(QObject* parent = nullptr); ~EncryptionManager(); - QString sessionDecryptMessage(const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account); -- cgit v1.2.3 From 46e7f0d69db376cf45b354fef69ecba1e4636805 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 12 Jun 2021 19:57:20 +0200 Subject: Emit needsSave when required --- lib/connection.cpp | 3 +-- lib/crypto/qolmaccount.cpp | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index ab5e030b..aaa17cdd 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -460,13 +460,12 @@ void Connection::Private::completeSetup(const QString& mxId) connect(olmAccount.get(), &QOlmAccount::needsSave, q, [=](){ auto pickle = olmAccount->pickle(Unencrypted{}); AccountSettings(data->userId()).setEncryptionAccountPickle(std::get(pickle)); + //TODO handle errors }); if (accountSettings.encryptionAccountPickle().isEmpty()) { // create new account and save unpickle data olmAccount->createNewAccount(); - accountSettings.setEncryptionAccountPickle(std::get(olmAccount->pickle(Unencrypted{}))); - // TODO handle pickle errors auto job = q->callApi(olmAccount->deviceKeys()); connect(job, &BaseJob::failure, q, [=]{ qCWarning(E2EE) << "Failed to upload device keys:" << job->errorString(); diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 44959ac2..3c1f4bd3 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -68,6 +68,7 @@ void QOlmAccount::createNewAccount() if (error == olm_error()) { throw lastError(m_account); } + Q_EMIT needsSave(); } void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) @@ -218,6 +219,7 @@ std::optional QOlmAccount::removeOneTimeKeys(const std::unique_ptr, QOlmError> QOlmAccount::createOutboun void QOlmAccount::markKeysAsPublished() { olm_account_mark_keys_as_published(m_account); + Q_EMIT needsSave(); } bool Quotient::verifyIdentitySignature(const DeviceKeys &deviceKeys, -- cgit v1.2.3 From cdd21a33eabebbad33a610cbe189116caba3ac89 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 31 Aug 2021 00:30:42 +0200 Subject: Fix FTBFS --- lib/connection.h | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/connection.h b/lib/connection.h index 6729b23d..6b1ecdae 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -648,6 +648,7 @@ public Q_SLOTS: /** \deprecated Do not use this directly, use Room::leaveRoom() instead */ virtual LeaveRoomJob* leaveRoom(Room* room); + void encryptionUpdate(Room *room); Q_SIGNALS: /// \brief Initial server resolution has failed /// -- cgit v1.2.3 From d04543ba00335d87a872b0f0c8ff35c85301e096 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 31 Aug 2021 00:42:56 +0200 Subject: Try fixing the CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b707236..a1c8bf1d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,7 +107,7 @@ jobs: arch: ${{ matrix.platform }} - name: Install OpenSSL - if: contains(matrix.os, 'ubuntu') and matrix.e2ee + if: ${{ contains(matrix.os, 'ubuntu') && matrix.e2ee }} run: | sudo apt-get install libssl-dev echo "openssl version" >>$GITHUB_ENV -- cgit v1.2.3 From a30d457161fcaadfe944e4411d4b0e487e856178 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 31 Aug 2021 00:48:23 +0200 Subject: Fix build without E2EE --- lib/connection.cpp | 2 ++ lib/connection.h | 2 ++ lib/room.cpp | 2 ++ 3 files changed, 6 insertions(+) diff --git a/lib/connection.cpp b/lib/connection.cpp index aaa17cdd..98686ed0 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -259,7 +259,9 @@ public: Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent), d(new Private(std::make_unique(server))) { +#ifdef Quotient_E2EE_ENABLED d->encryptionManager = new EncryptionManager(this); +#endif d->q = this; // All d initialization should occur before this line } diff --git a/lib/connection.h b/lib/connection.h index 6b1ecdae..c351f93e 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -648,7 +648,9 @@ public Q_SLOTS: /** \deprecated Do not use this directly, use Room::leaveRoom() instead */ virtual LeaveRoomJob* leaveRoom(Room* room); +#ifdef Quotient_E2EE_ENABLED void encryptionUpdate(Room *room); +#endif Q_SIGNALS: /// \brief Initial server resolution has failed /// diff --git a/lib/room.cpp b/lib/room.cpp index 3a894b9b..57914db4 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -475,6 +475,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) emit baseStateLoaded(); return this == r; // loadedRoomState fires only once per room }); +#ifdef Quotient_E2EE_ENABLED connectSingleShot(this, &Room::encryption, this, [=](){ connection->encryptionUpdate(this); }); @@ -483,6 +484,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) connection->encryptionUpdate(this); } }); +#endif qCDebug(STATE) << "New" << terse << initialJoinState << "Room:" << id; } -- cgit v1.2.3 From 9f08e5d865a5500d2926f10de85da2d5dcd063f7 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 8 Sep 2021 16:07:45 +0200 Subject: Add a function for decrypting E2EE files/images --- lib/encryptionmanager.cpp | 24 ++++++++++++++++++++++++ lib/encryptionmanager.h | 2 ++ 2 files changed, 26 insertions(+) diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index b9bd6646..48e6701c 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -7,11 +7,13 @@ #include "connection.h" #include "crypto/e2ee.h" +#include "events/encryptedfile.h" #include "csapi/keys.h" #include #include +#include #include "crypto/qolmaccount.h" #include "crypto/qolmsession.h" @@ -21,6 +23,8 @@ #include #include +#include + using namespace Quotient; using std::move; @@ -119,4 +123,24 @@ QString EncryptionManager::sessionDecryptMessage( } 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 index 17f4f853..96569980 100644 --- a/lib/encryptionmanager.h +++ b/lib/encryptionmanager.h @@ -12,6 +12,7 @@ namespace Quotient { class Connection; class QOlmAccount; +struct EncryptedFile; class EncryptionManager : public QObject { Q_OBJECT @@ -21,6 +22,7 @@ public: ~EncryptionManager(); QString sessionDecryptMessage(const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account); + static QByteArray decryptFile(const QByteArray &ciphertext, EncryptedFile* encryptedFile); private: class Private; -- 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(-) 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 0bafc33d70ddfdc8c4015a7f330623c726fe7ef7 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 24 Oct 2021 16:47:27 +0200 Subject: Only build the encryptionmanager when encryption is enabled --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92a9b213..8d5f08af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,7 +128,6 @@ list(APPEND lib_SRCS lib/networksettings.cpp lib/converters.cpp lib/util.cpp - lib/encryptionmanager.cpp lib/eventitem.cpp lib/accountregistry.cpp lib/mxcreply.cpp @@ -171,6 +170,7 @@ if (${PROJECT_NAME}_ENABLE_E2EE) lib/crypto/qolmerrors.cpp lib/crypto/qolmsession.cpp lib/crypto/qolmmessage.cpp + lib/encryptionmanager.cpp ) endif() -- cgit v1.2.3 From 6190e2b23a5ef62530a90fc03e264605e716ad2e Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 22 Nov 2021 21:44:22 +0100 Subject: Fix olm error parsing --- lib/crypto/qolmerrors.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/crypto/qolmerrors.cpp b/lib/crypto/qolmerrors.cpp index 2c3926de..6db1803c 100644 --- a/lib/crypto/qolmerrors.cpp +++ b/lib/crypto/qolmerrors.cpp @@ -6,15 +6,15 @@ #include "qolmerrors.h" Quotient::QOlmError Quotient::fromString(const std::string &error_raw) { - if (error_raw.compare("BAD_ACCOUNT_KEY")) { + if (!error_raw.compare("BAD_ACCOUNT_KEY")) { return QOlmError::BadAccountKey; - } else if (error_raw.compare("BAD_MESSAGE_KEY_ID")) { + } else if (!error_raw.compare("BAD_MESSAGE_KEY_ID")) { return QOlmError::BadMessageKeyId; - } else if (error_raw.compare("INVALID_BASE64")) { + } else if (!error_raw.compare("INVALID_BASE64")) { return QOlmError::InvalidBase64; - } else if (error_raw.compare("NOT_ENOUGH_RANDOM")) { + } else if (!error_raw.compare("NOT_ENOUGH_RANDOM")) { return QOlmError::NotEnoughRandom; - } else if (error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { + } else if (!error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { return QOlmError::OutputBufferTooSmall; } else { return QOlmError::Unknown; -- cgit v1.2.3 From eaf23c0f6bb97fdf16631296ab7016447a99c4f2 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 29 Jan 2021 20:23:42 +0100 Subject: Don't die on broken olm account --- lib/crypto/qolmaccount.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 3c1f4bd3..1de8a0dc 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -77,7 +77,10 @@ void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) 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); + 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); } } -- cgit v1.2.3 From 703b3f89ef54d9d40c9117788d0920b6b745bd62 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 12 Jun 2021 23:04:17 +0200 Subject: Implement (meg)olm key caching, megolm decrypting, EncryptedEvent decryption, handling of encrypted redactions and replies --- lib/connection.cpp | 5 +- lib/encryptionmanager.cpp | 72 ++++++++++++++++- lib/room.cpp | 195 +++++++++++++++++++++++++++++++++------------- 3 files changed, 215 insertions(+), 57 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 98686ed0..4a220e0d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -464,6 +464,7 @@ void Connection::Private::completeSetup(const QString& mxId) AccountSettings(data->userId()).setEncryptionAccountPickle(std::get(pickle)); //TODO handle errors }); + encryptionManager = new EncryptionManager(q); if (accountSettings.encryptionAccountPickle().isEmpty()) { // create new account and save unpickle data @@ -1891,9 +1892,9 @@ void Connection::Private::saveDevicesList() QFile outFile { q->stateCacheDir().filePath("deviceslist.json") }; if (!outFile.open(QFile::WriteOnly)) { - qCWarning(MAIN) << "Error opening" << outFile.fileName() << ":" + qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); - qCWarning(MAIN) << "Caching the rooms state disabled"; + qCWarning(E2EE) << "Caching the rooms state disabled"; cacheState = false; return; } diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 48e6701c..d36d5a7a 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -31,7 +31,6 @@ using std::move; class EncryptionManager::Private { public: explicit Private() - : q(nullptr) { } ~Private() = default; @@ -51,6 +50,73 @@ public: } } } + void loadSessions() { + QFile file { static_cast(q->parent())->stateCacheDir().filePath("olmsessions.json") }; + if(!file.exists() || !file.open(QIODevice::ReadOnly)) { + qCDebug(E2EE) << "No sessions cache exists."; + return; + } + auto data = file.readAll(); + const auto json = data.startsWith('{') + ? QJsonDocument::fromJson(data).object() +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + : QCborValue::fromCbor(data).toJsonValue().toObject() +#else + : QJsonDocument::fromBinaryData(data).object() +#endif + ; + if (json.isEmpty()) { + qCWarning(MAIN) << "Sessions cache is empty"; + return; + } + for(const auto &senderKey : json["sessions"].toObject().keys()) { + auto pickle = json["sessions"].toObject()[senderKey].toString(); + auto sessionResult = QOlmSession::unpickle(pickle.toLatin1(), Unencrypted{}); + if(std::holds_alternative(sessionResult)) { + qCWarning(E2EE) << "Failed to unpickle olm session"; + continue; + } + sessions[senderKey] = std::move(std::get>(sessionResult)); + } + } + void saveSessions() { + QFile outFile { static_cast(q->parent())->stateCacheDir().filePath("olmsessions.json") }; + if (!outFile.open(QFile::WriteOnly)) { + qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" + << outFile.errorString(); + qCWarning(E2EE) << "Failed to write olm sessions"; + return; + } + + QJsonObject rootObj { + { QStringLiteral("cache_version"), + QJsonObject { + { QStringLiteral("major"), 1 }, + { QStringLiteral("minor"), 0 } } } + }; + { + QJsonObject sessionsJson; + for (const auto &session : sessions) { + auto pickleResult = session.second->pickle(Unencrypted{}); + if(std::holds_alternative(pickleResult)) { + qCWarning(E2EE) << "Failed to pickle session"; + continue; + } + sessionsJson[session.first] = QString(std::get(pickleResult)); + } + rootObj.insert(QStringLiteral("sessions"), sessionsJson); + } + + #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + const auto data = QJsonDocument(rootObj).toJson(QJsonDocument::Compact); + #else + QJsonDocument json { rootObj }; + const auto data = json.toJson(QJsonDocument::Compact); + #endif + + outFile.write(data.data(), data.size()); + qCDebug(E2EE) << "Sessions saved to" << outFile.fileName(); + } QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) { Q_ASSERT(message.type() == QOlmMessage::PreKey); @@ -60,6 +126,7 @@ public: qCDebug(E2EE) << "Found inbound session"; const auto result = session.second->decrypt(message); if(std::holds_alternative(result)) { + saveSessions(); return std::get(result); } else { qCDebug(E2EE) << "Failed to decrypt prekey message"; @@ -79,6 +146,7 @@ public: const auto result = newSession->decrypt(message); sessions[senderKey] = std::move(newSession); if(std::holds_alternative(result)) { + saveSessions(); return std::get(result); } else { qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; @@ -91,6 +159,7 @@ public: for(auto& session : sessions) { const auto result = session.second->decrypt(message); if(std::holds_alternative(result)) { + saveSessions(); return std::get(result); } } @@ -104,6 +173,7 @@ EncryptionManager::EncryptionManager(QObject* parent) , d(std::make_unique()) { d->q = this; + d->loadSessions(); } EncryptionManager::~EncryptionManager() = default; diff --git a/lib/room.cpp b/lib/room.cpp index 57914db4..5fedd861 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -369,37 +369,95 @@ 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, QOlmInboundGroupSession*> groupSessions; // TODO: - // cache + std::map, std::unique_ptr> groupSessions; + + void loadMegOlmSessions() { + QFile file { connection->stateCacheDir().filePath("megolmsessions.json") }; + if(!file.exists() || !file.open(QIODevice::ReadOnly)) { + qCDebug(E2EE) << "No megolm sessions cache exists."; + return; + } + auto data = file.readAll(); + const auto json = data.startsWith('{') + ? QJsonDocument::fromJson(data).object() +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + : QCborValue::fromCbor(data).toJsonValue().toObject() +#else + : QJsonDocument::fromBinaryData(data).object() +#endif + ; + if (json.isEmpty()) { + qCWarning(MAIN) << "Megolm sessions cache is empty"; + return; + } + for(const auto &s : json["sessions"].toArray()) { + auto pickle = s.toObject()["pickle"].toString().toLatin1(); + auto senderKey = s.toObject()["sender_key"].toString(); + auto sessionId = s.toObject()["session_id"].toString(); + auto sessionResult = QOlmInboundGroupSession::unpickle(pickle, Unencrypted{}); + if(std::holds_alternative(sessionResult)) { + qCWarning(E2EE) << "Failed to unpickle olm session"; + continue; + } + groupSessions[qMakePair(senderKey, sessionId)] = std::move(std::get>(sessionResult)); + } + } + void saveMegOlmSessions() { + QFile outFile { connection->stateCacheDir().filePath("megolmsessions.json") }; + if (!outFile.open(QFile::WriteOnly)) { + qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" + << outFile.errorString(); + qCWarning(E2EE) << "Failed to write megolm sessions"; + return; + } + + QJsonObject rootObj { + { QStringLiteral("cache_version"), + QJsonObject { + { QStringLiteral("major"), 1 }, + { QStringLiteral("minor"), 0 } } } + }; + { + QJsonArray sessionsJson; + for (const auto &session : groupSessions) { + auto pickleResult = session.second->pickle(Unencrypted{}); + sessionsJson += QJsonObject { + {QStringLiteral("sender_key"), session.first.first}, + {QStringLiteral("session_id"), session.first.second}, + {QStringLiteral("pickle"), QString(pickleResult)} + }; + } + rootObj.insert(QStringLiteral("sessions"), sessionsJson); + } + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + const auto data = QJsonDocument(rootObj).toJson(QJsonDocument::Compact); +#else + QJsonDocument json { rootObj }; + const auto data = json.toJson(QJsonDocument::Compact); +#endif + + outFile.write(data.data(), data.size()); + qCDebug(E2EE) << "Megolm sessions saved to" << outFile.fileName(); + } bool addInboundGroupSession(QString senderKey, QString sessionId, QString sessionKey) { - // new e2ee TODO: - /* - if (groupSessions.contains({ senderKey, sessionId })) { - qCDebug(E2EE) << "Inbound Megolm session" << sessionId + if (groupSessions.find(qMakePair(senderKey, sessionId)) != groupSessions.end()) { + qCWarning(E2EE) << "Inbound Megolm session" << sessionId << "with senderKey" << senderKey << "already exists"; return false; } - QOlmInboundGroupSession* megolmSession; - try { - megolmSession = new QOlmInboundGroupSession(sessionKey.toLatin1(), - InboundGroupSession::Init, - q); - } catch (QOlmError* e) { - qCDebug(E2EE) << "Unable to create new InboundGroupSession" - << e->what(); + std::unique_ptr megolmSession = QOlmInboundGroupSession::create(sessionKey.toLatin1()); + if (megolmSession->sessionId() != sessionId) { + qCWarning(E2EE) << "Session ID mismatch in m.room_key event sent " + "from sender with key" << senderKey; return false; } - if (megolmSession->id() != sessionId) { - qCDebug(E2EE) << "Session ID mismatch in m.room_key event sent " - "from sender with key" - << senderKey; - return false; - } - groupSessions.insert({ senderKey, sessionId }, megolmSession); - */ + qCWarning(E2EE) << "Adding inbound session"; + groupSessions[qMakePair(senderKey, sessionId)] = std::move(megolmSession); + saveMegOlmSessions(); return true; } @@ -409,46 +467,33 @@ public: const QString& eventId, QDateTime timestamp) { - std::pair decrypted; - // new e2ee TODO: - /* QPair senderSessionPairKey = qMakePair(senderKey, sessionId); - if (!groupSessions.contains(senderSessionPairKey)) { - qCDebug(E2EE) << "Unable to decrypt event" << eventId + if (groupSessions.find(senderSessionPairKey) == groupSessions.end()) { + qCWarning(E2EE) << "Unable to decrypt event" << eventId << "The sender's device has not sent us the keys for " "this message"; return QString(); } - QOlmInboundGroupSession* senderSession = - groupSessions.value(senderSessionPairKey); - if (!senderSession) { - qCDebug(E2EE) << "Unable to decrypt event" << eventId - << "senderSessionPairKey:" << senderSessionPairKey; + auto& senderSession = groupSessions[senderSessionPairKey]; + auto decryptResult = senderSession->decrypt(cipher); + if(std::holds_alternative(decryptResult)) { + qCWarning(E2EE) << "Unable to decrypt event" << eventId + << "with matching megolm session:" << std::get(decryptResult); return QString(); } - try { - decrypted = senderSession->decrypt(cipher); - } catch (QOlmError* e) { - qCDebug(E2EE) << "Unable to decrypt event" << eventId - << "with matching megolm session:" << e->what(); - return QString(); - } - QPair properties = groupSessionIndexRecord.value( - qMakePair(senderSession->id(), decrypted.second)); + std::pair decrypted = std::get>(decryptResult); + QPair properties = groupSessionIndexRecord.value(qMakePair(senderSession->sessionId(), decrypted.second)); if (properties.first.isEmpty()) { - groupSessionIndexRecord.insert(qMakePair(senderSession->id(), - decrypted.second), - qMakePair(eventId, timestamp)); + groupSessionIndexRecord.insert(qMakePair(senderSession->sessionId(), decrypted.second), qMakePair(eventId, timestamp)); } else { - if ((properties.first != eventId) - || (properties.second != timestamp)) { - qCDebug(E2EE) << "Detected a replay attack on event" << eventId; + if ((properties.first != eventId) || (properties.second != timestamp)) { + qCWarning(E2EE) << "Detected a replay attack on event" << eventId; return QString(); } } - */ - + //TODO is this necessary? + saveMegOlmSessions(); return decrypted.first; } #endif // Quotient_E2EE_ENABLED @@ -475,6 +520,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) emit baseStateLoaded(); return this == r; // loadedRoomState fires only once per room }); + qCDebug(STATE) << "New" << initialJoinState << "Room:" << id; #ifdef Quotient_E2EE_ENABLED connectSingleShot(this, &Room::encryption, this, [=](){ connection->encryptionUpdate(this); @@ -484,6 +530,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) connection->encryptionUpdate(this); } }); + d->loadMegOlmSessions(); #endif qCDebug(STATE) << "New" << terse << initialJoinState << "Room:" << id; } @@ -1504,13 +1551,29 @@ RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) encryptedEvent.sessionId(), encryptedEvent.id(), encryptedEvent.originTimestamp()); if (decrypted.isEmpty()) { + qCWarning(E2EE) << "Encrypted message is empty"; return {}; } - return makeEvent( - QJsonDocument::fromJson(decrypted.toUtf8()).object()); + QJsonObject eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); + eventObject["event_id"] = encryptedEvent.id(); + eventObject["sender"] = encryptedEvent.senderId(); + eventObject["origin_server_ts"] = encryptedEvent.originTimestamp().toMSecsSinceEpoch(); + if(encryptedEvent.contentJson().contains("m.relates_to")) { + auto relates = encryptedEvent.contentJson()["m.relates_to"].toObject(); + auto content = eventObject["content"].toObject(); + content["m.relates_to"] = relates; + eventObject["content"] = content; + } + if(encryptedEvent.unsignedJson().contains("redacts")) { + auto redacts = encryptedEvent.unsignedJson()["redacts"].toString(); + auto unsign = eventObject["unsigned"].toObject(); + unsign["redacts"] = redacts; + eventObject["unsigned"] = unsign; + } + return makeEvent(eventObject); } qCDebug(E2EE) << "Algorithm of the encrypted event with id" - << encryptedEvent.id() << "is not for the current device"; + << encryptedEvent.id() << "is not decryptable by the current device"; return {}; #endif // Quotient_E2EE_ENABLED } @@ -1529,8 +1592,8 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, } if (d->addInboundGroupSession(senderKey, roomKeyEvent.sessionId(), roomKeyEvent.sessionKey())) { - qCDebug(E2EE) << "added new inboundGroupSession:" - << d->groupSessions.count(); + qCWarning(E2EE) << "added new inboundGroupSession:" + << d->groupSessions.size(); } #endif // Quotient_E2EE_ENABLED } @@ -2590,6 +2653,18 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) QElapsedTimer et; et.start(); + + //TODO should this be done before dropDuplicateEvents? + for(long unsigned int i = 0; i < events.size(); i++) { + if(auto* encrypted = eventCast(events[i])) { + qDebug() << "Encrypted Event"; + auto decrypted = q->decryptMessage(*encrypted); + if(decrypted) { + events[i] = std::move(decrypted); + } + } + } + { // Pre-process redactions and edits so that events that get // redacted/replaced in the same batch landed in the timeline already @@ -2742,6 +2817,18 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) return; Changes changes {}; + + //TODO should this be done before dropDuplicateEvents? + for(long unsigned int i = 0; i < events.size(); i++) { + if(auto* encrypted = eventCast(events[i])) { + qDebug() << "Encrypted Event"; + auto decrypted = q->decryptMessage(*encrypted); + if(decrypted) { + events[i] = std::move(decrypted); + } + } + } + // In case of lazy-loading new members may be loaded with historical // messages. Also, the cache doesn't store events with empty content; // so when such events show up in the timeline they should be properly -- cgit v1.2.3 From 60bd11426e941f9d349962b8b2ea4bddd9488965 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 17 Aug 2021 18:10:58 +0200 Subject: Don't crash when ToDeviceEvent decryption fails --- lib/connection.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 4a220e0d..60ffed09 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -798,8 +798,13 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); return; } + const auto decryptedEvent = sessionDecryptMessage(event); + if(!decryptedEvent) { + qCWarning(E2EE) << "Failed to decrypt event" << event.id(); + return; + } - visit(*sessionDecryptMessage(event), + visit(*decryptedEvent, [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); -- cgit v1.2.3 From 244938d2c99674ba09f3c1f92b2a4f8507ac5e58 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 17 Aug 2021 20:51:29 +0200 Subject: Various fixes --- lib/connection.cpp | 2 +- lib/encryptionmanager.cpp | 4 ++-- lib/room.cpp | 8 +++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 60ffed09..7a96bc50 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -634,7 +634,7 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) #ifdef Quotient_E2EE_ENABLED if(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() && !d->isUploadingKeys) { d->isUploadingKeys = true; - d->olmAccount->generateOneTimeKeys(d->olmAccount->maxNumberOfOneTimeKeys() - data.deviceOneTimeKeysCount()["signed_curve25519"]); + d->olmAccount->generateOneTimeKeys(d->olmAccount->maxNumberOfOneTimeKeys() / 2 - data.deviceOneTimeKeysCount()["signed_curve25519"]); auto keys = d->olmAccount->oneTimeKeys(); auto job = d->olmAccount->createUploadKeyRequest(keys); run(job, ForegroundRequest); diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index d36d5a7a..e8cc7b3a 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -125,8 +125,8 @@ public: if(std::holds_alternative(matches) && std::get(matches)) { qCDebug(E2EE) << "Found inbound session"; const auto result = session.second->decrypt(message); + saveSessions(); if(std::holds_alternative(result)) { - saveSessions(); return std::get(result); } else { qCDebug(E2EE) << "Failed to decrypt prekey message"; @@ -145,8 +145,8 @@ public: olmAccount->removeOneTimeKeys(newSession); const auto result = newSession->decrypt(message); sessions[senderKey] = std::move(newSession); + saveSessions(); if(std::holds_alternative(result)) { - saveSessions(); return std::get(result); } else { qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; diff --git a/lib/room.cpp b/lib/room.cpp index 5fedd861..a1354fc5 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -372,7 +372,7 @@ public: std::map, std::unique_ptr> groupSessions; void loadMegOlmSessions() { - QFile file { connection->stateCacheDir().filePath("megolmsessions.json") }; + QFile file { connection->stateCacheDir().filePath(QStringLiteral("megolm/%1.json").arg(id)) }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No megolm sessions cache exists."; return; @@ -387,7 +387,7 @@ public: #endif ; if (json.isEmpty()) { - qCWarning(MAIN) << "Megolm sessions cache is empty"; + qCWarning(E2EE) << "Megolm sessions cache is empty"; return; } for(const auto &s : json["sessions"].toArray()) { @@ -403,7 +403,8 @@ public: } } void saveMegOlmSessions() { - QFile outFile { connection->stateCacheDir().filePath("megolmsessions.json") }; + connection->stateCacheDir().mkdir("megolm"); + QFile outFile { connection->stateCacheDir().filePath(QStringLiteral("megolm/%1.json").arg(id))}; if (!outFile.open(QFile::WriteOnly)) { qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); @@ -521,6 +522,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) return this == r; // loadedRoomState fires only once per room }); qCDebug(STATE) << "New" << initialJoinState << "Room:" << id; + #ifdef Quotient_E2EE_ENABLED connectSingleShot(this, &Room::encryption, this, [=](){ connection->encryptionUpdate(this); -- cgit v1.2.3 From 0583534d83f902235b46ef6761d6698ddb6e6aba Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 18 Aug 2021 02:00:15 +0200 Subject: Store pickling key in qtkeychain and pickle encrypted --- CMakeLists.txt | 4 +++- lib/connection.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++-- lib/connection.h | 5 +++++ lib/encryptionmanager.cpp | 4 ++-- lib/events/eventcontent.cpp | 1 + lib/room.cpp | 5 ++--- 6 files changed, 59 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d5f08af..3977a9d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,8 @@ if (${PROJECT_NAME}_ENABLE_E2EE) endif() endif() +find_package(Qt${QT_MAJOR_VERSION}Keychain REQUIRED) + # Set up source files list(APPEND lib_SRCS lib/quotient_common.h @@ -325,7 +327,7 @@ if (${PROJECT_NAME}_ENABLE_E2EE) find_dependency(OpenSSL)") # For QuotientConfig.cmake.in endif() -target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) +target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui ${QTKEYCHAIN_LIBRARIES}) if (Qt STREQUAL Qt5) # See #483 target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) diff --git a/lib/connection.cpp b/lib/connection.cpp index 7a96bc50..77ab3b72 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -39,6 +39,7 @@ #ifdef Quotient_E2EE_ENABLED # include "crypto/qolmaccount.h" +# include "crypto/qolmutils.h" #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -55,6 +56,13 @@ #include #include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +# include +#else +# include +#endif + using namespace Quotient; // This is very much Qt-specific; STL iterators don't have key() and value() @@ -108,6 +116,7 @@ public: QHash> deviceKeys; QueryKeysJob *currentQueryKeysJob = nullptr; bool encryptionUpdateRequired = false; + PicklingMode picklingMode = Unencrypted {}; #endif GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -457,10 +466,40 @@ void Connection::Private::completeSetup(const QString& mxId) #else // Quotient_E2EE_ENABLED AccountSettings accountSettings(data->userId()); + QKeychain::ReadPasswordJob job(qAppName()); + job.setAutoDelete(false); + job.setKey(accountSettings.userId() + QStringLiteral("-Pickle")); + QEventLoop loop; + QKeychain::ReadPasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); + job.start(); + loop.exec(); + + if (job.error() == QKeychain::Error::EntryNotFound) { + picklingMode = Encrypted { getRandom(128) }; + QKeychain::WritePasswordJob job(qAppName()); + job.setAutoDelete(false); + job.setKey(accountSettings.userId() + QStringLiteral("-Pickle")); + job.setBinaryData(std::get(picklingMode).key); + QEventLoop loop; + QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); + job.start(); + loop.exec(); + + if (job.error()) { + qCWarning(E2EE) << "Could not save pickling key to keychain: " << job.errorString(); + } + } else if(job.error() != QKeychain::Error::NoError) { + //TODO Error, do something + qCWarning(E2EE) << "Error loading pickling key from keychain:" << job.error(); + } else { + qCDebug(E2EE) << "Successfully loaded pickling key from keychain"; + picklingMode = Encrypted { job.binaryData() }; + } + // init olmAccount olmAccount = std::make_unique(data->userId(), data->deviceId(), q); connect(olmAccount.get(), &QOlmAccount::needsSave, q, [=](){ - auto pickle = olmAccount->pickle(Unencrypted{}); + auto pickle = olmAccount->pickle(picklingMode); AccountSettings(data->userId()).setEncryptionAccountPickle(std::get(pickle)); //TODO handle errors }); @@ -476,7 +515,7 @@ void Connection::Private::completeSetup(const QString& mxId) } else { // account already existing auto pickle = accountSettings.encryptionAccountPickle(); - olmAccount->unpickle(pickle, Unencrypted{}); + olmAccount->unpickle(pickle, picklingMode); } #endif // Quotient_E2EE_ENABLED emit q->stateChanged(); @@ -1982,4 +2021,9 @@ void Connection::Private::loadDevicesList() } }); } + +PicklingMode Connection::picklingMode() const +{ + return d->picklingMode; +} #endif diff --git a/lib/connection.h b/lib/connection.h index c351f93e..e5cec34b 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -21,6 +21,10 @@ #include +#ifdef Quotient_E2EE_ENABLED +#include "crypto/e2ee.h" +#endif + Q_DECLARE_METATYPE(Quotient::GetLoginFlowsJob::LoginFlow) namespace Quotient { @@ -650,6 +654,7 @@ public Q_SLOTS: #ifdef Quotient_E2EE_ENABLED void encryptionUpdate(Room *room); + PicklingMode picklingMode() const; #endif Q_SIGNALS: /// \brief Initial server resolution has failed diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index e8cc7b3a..5c1750c9 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -71,7 +71,7 @@ public: } for(const auto &senderKey : json["sessions"].toObject().keys()) { auto pickle = json["sessions"].toObject()[senderKey].toString(); - auto sessionResult = QOlmSession::unpickle(pickle.toLatin1(), Unencrypted{}); + auto sessionResult = QOlmSession::unpickle(pickle.toLatin1(), static_cast(q->parent())->picklingMode()); if(std::holds_alternative(sessionResult)) { qCWarning(E2EE) << "Failed to unpickle olm session"; continue; @@ -97,7 +97,7 @@ public: { QJsonObject sessionsJson; for (const auto &session : sessions) { - auto pickleResult = session.second->pickle(Unencrypted{}); + auto pickleResult = session.second->pickle(static_cast(q->parent())->picklingMode()); if(std::holds_alternative(pickleResult)) { qCWarning(E2EE) << "Failed to pickle session"; continue; diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 22878d4c..d4cb43ff 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -75,6 +75,7 @@ void FileInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert(QStringLiteral("size"), payloadSize); if (mimeType.isValid()) infoJson->insert(QStringLiteral("mimetype"), mimeType.name()); + //TODO add encryptedfile } ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize) diff --git a/lib/room.cpp b/lib/room.cpp index a1354fc5..b60a23f2 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -394,7 +394,7 @@ public: auto pickle = s.toObject()["pickle"].toString().toLatin1(); auto senderKey = s.toObject()["sender_key"].toString(); auto sessionId = s.toObject()["session_id"].toString(); - auto sessionResult = QOlmInboundGroupSession::unpickle(pickle, Unencrypted{}); + auto sessionResult = QOlmInboundGroupSession::unpickle(pickle, connection->picklingMode()); if(std::holds_alternative(sessionResult)) { qCWarning(E2EE) << "Failed to unpickle olm session"; continue; @@ -421,7 +421,7 @@ public: { QJsonArray sessionsJson; for (const auto &session : groupSessions) { - auto pickleResult = session.second->pickle(Unencrypted{}); + auto pickleResult = session.second->pickle(connection->picklingMode()); sessionsJson += QJsonObject { {QStringLiteral("sender_key"), session.first.first}, {QStringLiteral("session_id"), session.first.second}, @@ -2659,7 +2659,6 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) //TODO should this be done before dropDuplicateEvents? for(long unsigned int i = 0; i < events.size(); i++) { if(auto* encrypted = eventCast(events[i])) { - qDebug() << "Encrypted Event"; auto decrypted = q->decryptMessage(*encrypted); if(decrypted) { events[i] = std::move(decrypted); -- cgit v1.2.3 From 77a13cfdace5cb27adb52b3a644a155aee522b12 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 28 Aug 2021 01:03:49 +0200 Subject: Implement download and decryption of encrypted files --- lib/connection.cpp | 15 +++++++ lib/connection.h | 5 +++ lib/jobs/downloadfilejob.cpp | 97 ++++++++++++++++++++++++++++++++++++++------ lib/jobs/downloadfilejob.h | 3 ++ lib/room.cpp | 59 +++++++++++++++++++++++++++ lib/room.h | 3 ++ 6 files changed, 170 insertions(+), 12 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 77ab3b72..4a1130ae 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1017,6 +1017,21 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, return job; } +#ifdef Quotient_E2EE_ENABLED +DownloadFileJob* Connection::downloadFile(const QUrl& url, + const QString& key, + const QString& iv, + const QString& sha256, + const QString& localFilename) +{ + auto mediaId = url.authority() + url.path(); + auto idParts = splitMediaId(mediaId); + auto* job = + callApi(idParts.front(), idParts.back(), key, iv, sha256, localFilename); + return job; +} +#endif + CreateRoomJob* Connection::createRoom(RoomVisibility visibility, const QString& alias, const QString& name, const QString& topic, diff --git a/lib/connection.h b/lib/connection.h index e5cec34b..f9143d3e 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -567,6 +567,11 @@ public Q_SLOTS: DownloadFileJob* downloadFile(const QUrl& url, const QString& localFilename = {}); +#ifdef Quotient_E2EE_ENABLED + DownloadFileJob* downloadFile(const QUrl& url, const QString &key, + const QString& iv, const QString& sha256, + const QString& localFilename = {}); +#endif /** * \brief Create a room (generic method) * This method allows to customize room entirely to your liking, diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 0b0531ad..7a7a46f6 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -7,8 +7,25 @@ #include #include -using namespace Quotient; +#ifdef Quotient_E2EE_ENABLED +# include +# include + +QByteArray decrypt(const QByteArray &ciphertext, const QByteArray &key, const QByteArray &iv) +{ + 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 +using namespace Quotient; class DownloadFileJob::Private { public: Private() : tempFile(new QTemporaryFile()) {} @@ -20,6 +37,12 @@ public: QScopedPointer targetFile; QScopedPointer tempFile; + +#ifdef Quotient_E2EE_ENABLED + QByteArray key; + QByteArray iv; + QByteArray sha256; +#endif }; QUrl DownloadFileJob::makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri) @@ -37,6 +60,23 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, setObjectName(QStringLiteral("DownloadFileJob")); } +#ifdef Quotient_E2EE_ENABLED +DownloadFileJob::DownloadFileJob(const QString& serverName, + const QString& mediaId, + const QString& key, + const QString& iv, + const QString& sha256, + const QString& localFilename) + : GetContentJob(serverName, mediaId) + , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) +{ + setObjectName(QStringLiteral("DownloadFileJob")); + auto _key = key; + d->key = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); + d->iv = QByteArray::fromBase64(iv.toLatin1()); + d->sha256 = QByteArray::fromBase64(sha256.toLatin1()); +} +#endif QString DownloadFileJob::targetFileName() const { return (d->targetFile ? d->targetFile : d->tempFile)->fileName(); @@ -51,7 +91,7 @@ void DownloadFileJob::doPrepare() setStatus(FileError, "Could not open the target file for writing"); return; } - if (!d->tempFile->isReadable() && !d->tempFile->open(QIODevice::WriteOnly)) { + if (!d->tempFile->isReadable() && !d->tempFile->open(QIODevice::ReadWrite)) { qCWarning(JOBS) << "Couldn't open the temporary file" << d->tempFile->fileName() << "for writing"; setStatus(FileError, "Could not open the temporary download file"); @@ -99,18 +139,51 @@ void DownloadFileJob::beforeAbandon() BaseJob::Status DownloadFileJob::prepareResult() { if (d->targetFile) { - d->targetFile->close(); - if (!d->targetFile->remove()) { - qCWarning(JOBS) << "Failed to remove the target file placeholder"; - return { FileError, "Couldn't finalise the download" }; +#ifdef Quotient_E2EE_ENABLED + if(d->key.size() != 0) { + d->tempFile->seek(0); + QByteArray encrypted = d->tempFile->readAll(); + if(d->sha256 != QCryptographicHash::hash(encrypted, QCryptographicHash::Sha256)) { + qCWarning(E2EE) << "Hash verification failed for file"; + return IncorrectResponse; + } + auto decrypted = decrypt(encrypted, d->key, d->iv); + d->targetFile->write(decrypted); + d->targetFile->remove(); + } else { +#endif + d->targetFile->close(); + if (!d->targetFile->remove()) { + qCWarning(JOBS) << "Failed to remove the target file placeholder"; + return { FileError, "Couldn't finalise the download" }; + } + if (!d->tempFile->rename(d->targetFile->fileName())) { + qCWarning(JOBS) << "Failed to rename" << d->tempFile->fileName() + << "to" << d->targetFile->fileName(); + return { FileError, "Couldn't finalise the download" }; + } +#ifdef Quotient_E2EE_ENABLED } - if (!d->tempFile->rename(d->targetFile->fileName())) { - qCWarning(JOBS) << "Failed to rename" << d->tempFile->fileName() - << "to" << d->targetFile->fileName(); - return { FileError, "Couldn't finalise the download" }; +#endif + } else { +#ifdef Quotient_E2EE_ENABLED + if(d->key.size() != 0) { + d->tempFile->seek(0); + auto encrypted = d->tempFile->readAll(); + + if(d->sha256 != QCryptographicHash::hash(encrypted, QCryptographicHash::Sha256)) { + qCWarning(E2EE) << "Hash verification failed for file"; + return IncorrectResponse; + } + auto decrypted = decrypt(encrypted, d->key, d->iv); + d->tempFile->write(decrypted); + } else { +#endif + d->tempFile->close(); +#ifdef Quotient_E2EE_ENABLED } - } else - d->tempFile->close(); +#endif + } qCDebug(JOBS) << "Saved a file as" << targetFileName(); return Success; } diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index 0752af89..f000b991 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -14,6 +14,9 @@ public: DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& localFilename = {}); +#ifdef Quotient_E2EE_ENABLED + DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& key, const QString& iv, const QString& sha256, const QString& localFilename = {}); +#endif QString targetFileName() const; private: diff --git a/lib/room.cpp b/lib/room.cpp index b60a23f2..a4dfcb8f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2426,6 +2426,65 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) d->failedTransfer(eventId); } +#ifdef Quotient_E2EE_ENABLED +void Room::downloadFile(const QString& eventId, const QString& key, const QString& iv, const QString& sha256, const QUrl& localFilename) +{ + if (auto ongoingTransfer = d->fileTransfers.constFind(eventId); + ongoingTransfer != d->fileTransfers.cend() + && ongoingTransfer->status == FileTransferInfo::Started) { + qCWarning(MAIN) << "Transfer for" << eventId + << "is ongoing; download won't start"; + return; + } + + Q_ASSERT_X(localFilename.isEmpty() || localFilename.isLocalFile(), + __FUNCTION__, "localFilename should point at a local file"); + const auto* event = d->getEventWithFile(eventId); + if (!event) { + qCCritical(MAIN) + << eventId << "is not in the local timeline or has no file content"; + Q_ASSERT(false); + return; + } + if (!event->contentJson().contains(QStringLiteral("file"))) { + qCWarning(MAIN) << "Event" << eventId + << "has an empty or malformed mxc URL; won't download"; + return; + } + const auto fileUrl = QUrl(event->contentJson()["file"]["url"].toString()); + auto filePath = localFilename.toLocalFile(); + if (filePath.isEmpty()) { // Setup default file path + filePath = + fileUrl.path().mid(1) % '_' % d->fileNameToDownload(event); + + if (filePath.size() > 200) // If too long, elide in the middle + filePath.replace(128, filePath.size() - 192, "---"); + + filePath = QDir::tempPath() % '/' % filePath; + qDebug(MAIN) << "File path:" << filePath; + } + auto job = connection()->downloadFile(fileUrl, key, iv, sha256, filePath); + if (isJobPending(job)) { + // If there was a previous transfer (completed or failed), overwrite it. + d->fileTransfers[eventId] = { job, job->targetFileName() }; + connect(job, &BaseJob::downloadProgress, this, + [this, eventId](qint64 received, qint64 total) { + d->fileTransfers[eventId].update(received, total); + emit fileTransferProgress(eventId, received, total); + }); + connect(job, &BaseJob::success, this, [this, eventId, fileUrl, job] { + d->fileTransfers[eventId].status = FileTransferInfo::Completed; + emit fileTransferCompleted( + eventId, fileUrl, QUrl::fromLocalFile(job->targetFileName())); + }); + connect(job, &BaseJob::failure, this, + std::bind(&Private::failedTransfer, d, eventId, + job->errorString())); + } else + d->failedTransfer(eventId); +} +#endif + void Room::cancelFileTransfer(const QString& id) { const auto it = d->fileTransfers.find(id); diff --git a/lib/room.h b/lib/room.h index 85c51a87..ac21d08c 100644 --- a/lib/room.h +++ b/lib/room.h @@ -852,6 +852,9 @@ public Q_SLOTS: const QString& overrideContentType = {}); // If localFilename is empty a temporary file is created void downloadFile(const QString& eventId, const QUrl& localFilename = {}); +#ifdef Quotient_E2EE_ENABLED + void downloadFile(const QString& eventId, const QString& key, const QString &iv, const QString &sha256, const QUrl& localFilename = {}); +#endif void cancelFileTransfer(const QString& id); //! \brief Set a given event as last read and post a read receipt on it -- cgit v1.2.3 From b35a736da2b09fe5cc0091f9fbd370d057503a54 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 30 Aug 2021 00:53:29 +0200 Subject: Handle encrypted file download through existing API --- lib/jobs/downloadfilejob.cpp | 2 +- lib/room.cpp | 67 ++++++-------------------------------------- lib/room.h | 3 -- 3 files changed, 10 insertions(+), 62 deletions(-) diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 7a7a46f6..e82271eb 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -149,7 +149,7 @@ BaseJob::Status DownloadFileJob::prepareResult() } auto decrypted = decrypt(encrypted, d->key, d->iv); d->targetFile->write(decrypted); - d->targetFile->remove(); + d->tempFile->remove(); } else { #endif d->targetFile->close(); diff --git a/lib/room.cpp b/lib/room.cpp index a4dfcb8f..d7ebe021 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2405,65 +2405,17 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) filePath = QDir::tempPath() % '/' % filePath; qDebug(MAIN) << "File path:" << filePath; } - auto job = connection()->downloadFile(fileUrl, filePath); - if (isJobPending(job)) { - // If there was a previous transfer (completed or failed), overwrite it. - d->fileTransfers[eventId] = { job, job->targetFileName() }; - connect(job, &BaseJob::downloadProgress, this, - [this, eventId](qint64 received, qint64 total) { - d->fileTransfers[eventId].update(received, total); - emit fileTransferProgress(eventId, received, total); - }); - connect(job, &BaseJob::success, this, [this, eventId, fileUrl, job] { - d->fileTransfers[eventId].status = FileTransferInfo::Completed; - emit fileTransferCompleted( - eventId, fileUrl, QUrl::fromLocalFile(job->targetFileName())); - }); - connect(job, &BaseJob::failure, this, - std::bind(&Private::failedTransfer, d, eventId, - job->errorString())); - } else - d->failedTransfer(eventId); -} - + DownloadFileJob *job = nullptr; +#ifdef Quotient_E2EE_ENABLED + if(fileInfo->file.has_value()) { + auto file = *fileInfo->file; + job = connection()->downloadFile(fileUrl, file.key.k, file.iv, file.hashes["sha256"], filePath); + } else { +#endif + job = connection()->downloadFile(fileUrl, filePath); #ifdef Quotient_E2EE_ENABLED -void Room::downloadFile(const QString& eventId, const QString& key, const QString& iv, const QString& sha256, const QUrl& localFilename) -{ - if (auto ongoingTransfer = d->fileTransfers.constFind(eventId); - ongoingTransfer != d->fileTransfers.cend() - && ongoingTransfer->status == FileTransferInfo::Started) { - qCWarning(MAIN) << "Transfer for" << eventId - << "is ongoing; download won't start"; - return; - } - - Q_ASSERT_X(localFilename.isEmpty() || localFilename.isLocalFile(), - __FUNCTION__, "localFilename should point at a local file"); - const auto* event = d->getEventWithFile(eventId); - if (!event) { - qCCritical(MAIN) - << eventId << "is not in the local timeline or has no file content"; - Q_ASSERT(false); - return; - } - if (!event->contentJson().contains(QStringLiteral("file"))) { - qCWarning(MAIN) << "Event" << eventId - << "has an empty or malformed mxc URL; won't download"; - return; - } - const auto fileUrl = QUrl(event->contentJson()["file"]["url"].toString()); - auto filePath = localFilename.toLocalFile(); - if (filePath.isEmpty()) { // Setup default file path - filePath = - fileUrl.path().mid(1) % '_' % d->fileNameToDownload(event); - - if (filePath.size() > 200) // If too long, elide in the middle - filePath.replace(128, filePath.size() - 192, "---"); - - filePath = QDir::tempPath() % '/' % filePath; - qDebug(MAIN) << "File path:" << filePath; } - auto job = connection()->downloadFile(fileUrl, key, iv, sha256, filePath); +#endif if (isJobPending(job)) { // If there was a previous transfer (completed or failed), overwrite it. d->fileTransfers[eventId] = { job, job->targetFileName() }; @@ -2483,7 +2435,6 @@ void Room::downloadFile(const QString& eventId, const QString& key, const QStrin } else d->failedTransfer(eventId); } -#endif void Room::cancelFileTransfer(const QString& id) { diff --git a/lib/room.h b/lib/room.h index ac21d08c..85c51a87 100644 --- a/lib/room.h +++ b/lib/room.h @@ -852,9 +852,6 @@ public Q_SLOTS: const QString& overrideContentType = {}); // If localFilename is empty a temporary file is created void downloadFile(const QString& eventId, const QUrl& localFilename = {}); -#ifdef Quotient_E2EE_ENABLED - void downloadFile(const QString& eventId, const QString& key, const QString &iv, const QString &sha256, const QUrl& localFilename = {}); -#endif void cancelFileTransfer(const QString& id); //! \brief Set a given event as last read and post a read receipt on it -- cgit v1.2.3 From 8636c7028b45ee8de3125bcf4df40ad60ed949a0 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 31 Aug 2021 00:09:59 +0200 Subject: Add mxc protocol to the networkaccessmanager --- lib/connection.cpp | 6 ++---- lib/connection.h | 3 +-- lib/jobs/downloadfilejob.cpp | 48 +++++++++++--------------------------------- lib/jobs/downloadfilejob.h | 3 ++- lib/mxcreply.cpp | 36 +++++++++++++++++++++++++++++++-- lib/networkaccessmanager.cpp | 6 ++++++ lib/networkaccessmanager.h | 2 +- lib/room.cpp | 2 +- 8 files changed, 59 insertions(+), 47 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 4a1130ae..d8e98bb0 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1019,15 +1019,13 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, #ifdef Quotient_E2EE_ENABLED DownloadFileJob* Connection::downloadFile(const QUrl& url, - const QString& key, - const QString& iv, - const QString& sha256, + const EncryptedFile file, const QString& localFilename) { auto mediaId = url.authority() + url.path(); auto idParts = splitMediaId(mediaId); auto* job = - callApi(idParts.front(), idParts.back(), key, iv, sha256, localFilename); + callApi(idParts.front(), idParts.back(), file, localFilename); return job; } #endif diff --git a/lib/connection.h b/lib/connection.h index f9143d3e..d0945aa4 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -568,8 +568,7 @@ public Q_SLOTS: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob* downloadFile(const QUrl& url, const QString &key, - const QString& iv, const QString& sha256, + DownloadFileJob* downloadFile(const QUrl& url, const EncryptedFile file, const QString& localFilename = {}); #endif /** diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index e82271eb..2fba1973 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -9,20 +9,8 @@ #ifdef Quotient_E2EE_ENABLED # include -# include - -QByteArray decrypt(const QByteArray &ciphertext, const QByteArray &key, const QByteArray &iv) -{ - 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; -} +# include "encryptionmanager.h" +# include "events/encryptedfile.h" #endif using namespace Quotient; @@ -39,9 +27,7 @@ public: QScopedPointer tempFile; #ifdef Quotient_E2EE_ENABLED - QByteArray key; - QByteArray iv; - QByteArray sha256; + Omittable encryptedFile; #endif }; @@ -63,18 +49,13 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, #ifdef Quotient_E2EE_ENABLED DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, - const QString& key, - const QString& iv, - const QString& sha256, + const EncryptedFile file, const QString& localFilename) : GetContentJob(serverName, mediaId) , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) { setObjectName(QStringLiteral("DownloadFileJob")); - auto _key = key; - d->key = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); - d->iv = QByteArray::fromBase64(iv.toLatin1()); - d->sha256 = QByteArray::fromBase64(sha256.toLatin1()); + d->encryptedFile = file; } #endif QString DownloadFileJob::targetFileName() const @@ -140,14 +121,12 @@ BaseJob::Status DownloadFileJob::prepareResult() { if (d->targetFile) { #ifdef Quotient_E2EE_ENABLED - if(d->key.size() != 0) { + if (d->encryptedFile.has_value()) { d->tempFile->seek(0); QByteArray encrypted = d->tempFile->readAll(); - if(d->sha256 != QCryptographicHash::hash(encrypted, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return IncorrectResponse; - } - auto decrypted = decrypt(encrypted, d->key, d->iv); + + EncryptedFile file = *d->encryptedFile; + auto decrypted = EncryptionManager::decryptFile(encrypted, &file); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -167,15 +146,12 @@ BaseJob::Status DownloadFileJob::prepareResult() #endif } else { #ifdef Quotient_E2EE_ENABLED - if(d->key.size() != 0) { + if (d->encryptedFile.has_value()) { d->tempFile->seek(0); auto encrypted = d->tempFile->readAll(); - if(d->sha256 != QCryptographicHash::hash(encrypted, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return IncorrectResponse; - } - auto decrypted = decrypt(encrypted, d->key, d->iv); + EncryptedFile file = *d->encryptedFile; + auto decrypted = EncryptionManager::decryptFile(encrypted, &file); d->tempFile->write(decrypted); } else { #endif diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index f000b991..67a3e95f 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -4,6 +4,7 @@ #pragma once #include "csapi/content-repo.h" +#include "events/encryptedfile.h" namespace Quotient { class DownloadFileJob : public GetContentJob { @@ -15,7 +16,7 @@ public: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& key, const QString& iv, const QString& sha256, const QString& localFilename = {}); + DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFile file, const QString& localFilename = {}); #endif QString targetFileName() const; diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 0b6643fc..65078301 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -3,8 +3,17 @@ #include "mxcreply.h" +#include +#include +#include "accountregistry.h" +#include "connection.h" #include "room.h" +#ifdef Quotient_E2EE_ENABLED +#include "encryptionmanager.h" +#include "events/encryptedfile.h" +#endif + using namespace Quotient; class MxcReply::Private @@ -14,6 +23,8 @@ public: : m_reply(r) {} QNetworkReply* m_reply; + Omittable m_encryptedFile; + QIODevice* m_device = nullptr; }; MxcReply::MxcReply(QNetworkReply* reply) @@ -31,11 +42,32 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) : d(std::make_unique(reply)) { reply->setParent(this); - connect(d->m_reply, &QNetworkReply::finished, this, [this, room, eventId]() { + connect(d->m_reply, &QNetworkReply::finished, this, [this]() { setError(d->m_reply->error(), d->m_reply->errorString()); + +#ifdef Quotient_E2EE_ENABLED + if(!d->m_encryptedFile.has_value()) { + d->m_device = d->m_reply; + } else { + EncryptedFile file = *d->m_encryptedFile; + auto buffer = new QBuffer(this); + buffer->setData(EncryptionManager::decryptFile(d->m_reply->readAll(), &file)); + d->m_device = buffer; + } setOpenMode(ReadOnly); emit finished(); +#else + d->m_device = d->m_reply; +#endif }); + +#ifdef Quotient_E2EE_ENABLED + auto eventIt = room->findInTimeline(eventId); + if(eventIt != room->historyEdge()) { + auto event = eventIt->viewAs(); + d->m_encryptedFile = event->content()->fileInfo()->file; + } +#endif } #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -61,7 +93,7 @@ MxcReply::MxcReply() qint64 MxcReply::readData(char *data, qint64 maxSize) { - return d->m_reply->read(data, maxSize); + return d->m_device->read(data, maxSize); } void MxcReply::abort() diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index d0380cec..c660cff8 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -12,6 +12,12 @@ #include #include #include +#include "accountregistry.h" +#include "mxcreply.h" +#include "connection.h" +#include "events/eventcontent.h" + +#include "room.h" using namespace Quotient; diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index 7643302f..efa41994 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -9,7 +9,7 @@ namespace Quotient { class Room; -class Connection; + class NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: diff --git a/lib/room.cpp b/lib/room.cpp index d7ebe021..688ba5d4 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2409,7 +2409,7 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) #ifdef Quotient_E2EE_ENABLED if(fileInfo->file.has_value()) { auto file = *fileInfo->file; - job = connection()->downloadFile(fileUrl, file.key.k, file.iv, file.hashes["sha256"], filePath); + job = connection()->downloadFile(fileUrl, file, filePath); } else { #endif job = connection()->downloadFile(fileUrl, filePath); -- cgit v1.2.3 From f42b91eb52ea408c6aedd4954cac7eb02e7b0df4 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 31 Aug 2021 00:14:35 +0200 Subject: Try adding qtkeychain to github CI --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1c8bf1d..6f1db8ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,6 +121,14 @@ jobs: cmake --build olm/build --target install echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN with E2EE" >>$GITHUB_ENV + - name: Build and install QtKeychain + if: matrix.e2ee + run: | + cd ${{ runner.workspace }} + 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: | -- cgit v1.2.3 From 82cffec29937e4449a75040485d5188f429b7b1e Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 10 Nov 2021 16:29:11 +0100 Subject: Try decrypting existing messages when a new key is added --- lib/room.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/room.cpp b/lib/room.cpp index 688ba5d4..6c5a9d33 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1596,6 +1596,14 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, roomKeyEvent.sessionKey())) { qCWarning(E2EE) << "added new inboundGroupSession:" << d->groupSessions.size(); + for (unsigned long int i = 0; i < d->timeline.size(); i++) { + if (auto encryptedEvent = d->timeline[i].viewAs()) { + auto decrypted = decryptMessage(*encryptedEvent); + if(decrypted) { + d->timeline[i].replaceEvent(std::move(decrypted)); + } + } + } } #endif // Quotient_E2EE_ENABLED } -- cgit v1.2.3 From 34db4fd1294e41765a5db58ee1a0c59712af62c6 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 15 Nov 2021 21:26:47 +0100 Subject: Various improvements and fixes --- lib/connection.cpp | 54 ++++++++++++++++++++++++---------------------- lib/crypto/qolmsession.cpp | 4 +--- lib/encryptionmanager.cpp | 2 +- lib/mxcreply.cpp | 1 + lib/room.cpp | 4 +++- lib/settings.cpp | 6 ------ 6 files changed, 34 insertions(+), 37 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index d8e98bb0..f36166ff 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -830,33 +830,35 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { #ifdef Quotient_E2EE_ENABLED - qWarning() << "Consuming to device events" << toDeviceEvents.size(); - if(toDeviceEvents.size() > 0) - visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { - if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { - qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); - return; - } - const auto decryptedEvent = sessionDecryptMessage(event); - if(!decryptedEvent) { - qCWarning(E2EE) << "Failed to decrypt event" << event.id(); - return; - } + if(toDeviceEvents.size() > 0) { + qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events"; + visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { + if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { + qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); + return; + } + qWarning() << event.fullJson(); + const auto decryptedEvent = sessionDecryptMessage(event); + if(!decryptedEvent) { + qCWarning(E2EE) << "Failed to decrypt event" << event.id(); + return; + } - visit(*decryptedEvent, - [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { - if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { - detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); - } else { - qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() - << "is not found at the connection" << q->objectName(); - } - }, - [](const Event& evt) { - qCDebug(E2EE) << "Skipping encrypted to_device event, type" - << evt.matrixType(); - }); - }); + visit(*decryptedEvent, + [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { + if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { + detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); + } else { + qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() + << "is not found at the connection" << q->objectName(); + } + }, + [](const Event& evt) { + qCDebug(E2EE) << "Skipping encrypted to_device event, type" + << evt.matrixType(); + }); + }); + } #endif } diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp index 2068a7d9..a327a643 100644 --- a/lib/crypto/qolmsession.cpp +++ b/lib/crypto/qolmsession.cpp @@ -46,9 +46,7 @@ std::variant, QOlmError> QOlmSession::createInbound if (error == olm_error()) { const auto lastErr = lastError(olmSession); - if (lastErr == QOlmError::NotEnoughRandom) { - qCCritical(E2EE) << "Error when creating inbound session" << lastErr; - } + qCWarning(E2EE) << "Error when creating inbound session" << lastErr; return lastErr; } diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 5c1750c9..81c13e50 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -137,7 +137,7 @@ public: 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; + qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); return {}; } std::unique_ptr newSession = std::move(std::get>(newSessionResult)); diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 65078301..639c1324 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -52,6 +52,7 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) EncryptedFile file = *d->m_encryptedFile; auto buffer = new QBuffer(this); buffer->setData(EncryptionManager::decryptFile(d->m_reply->readAll(), &file)); + buffer->open(ReadOnly); d->m_device = buffer; } setOpenMode(ReadOnly); diff --git a/lib/room.cpp b/lib/room.cpp index 6c5a9d33..94f0c9eb 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1600,7 +1600,9 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, if (auto encryptedEvent = d->timeline[i].viewAs()) { auto decrypted = decryptMessage(*encryptedEvent); if(decrypted) { - d->timeline[i].replaceEvent(std::move(decrypted)); + qWarning() << "decrypted" << decrypted->fullJson(); + auto oldEvent = d->timeline[i].replaceEvent(std::move(decrypted)); + emit replacedEvent(d->timeline[i].event(), rawPtr(oldEvent)); } } } diff --git a/lib/settings.cpp b/lib/settings.cpp index ed9082b0..f9b4f471 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -136,18 +136,12 @@ void AccountSettings::clearAccessToken() QByteArray AccountSettings::encryptionAccountPickle() { - QString passphrase = ""; // FIXME: add QtKeychain return value("encryption_account_pickle", "").toByteArray(); } void AccountSettings::setEncryptionAccountPickle( const QByteArray& encryptionAccountPickle) { - qCWarning(MAIN) - << "Saving encryption_account_pickle to QSettings is insecure." - " Developers, do it manually or contribute to share QtKeychain " - "logic to libQuotient."; - QString passphrase = ""; // FIXME: add QtKeychain setValue("encryption_account_pickle", encryptionAccountPickle); } -- cgit v1.2.3 From cab1c772abaf380f30a504231fc06b070feb09ec Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 15 Nov 2021 21:35:11 +0100 Subject: Save olm account on shutdown --- lib/connection.cpp | 18 +++++++++++++----- lib/connection.h | 3 +++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index f36166ff..20b4a113 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -270,6 +270,9 @@ Connection::Connection(const QUrl& server, QObject* parent) { #ifdef Quotient_E2EE_ENABLED d->encryptionManager = new EncryptionManager(this); + connect(qApp, &QCoreApplication::aboutToQuit, this, [this](){ + saveOlmAccount(); + }); #endif d->q = this; // All d initialization should occur before this line } @@ -498,11 +501,8 @@ void Connection::Private::completeSetup(const QString& mxId) // init olmAccount olmAccount = std::make_unique(data->userId(), data->deviceId(), q); - connect(olmAccount.get(), &QOlmAccount::needsSave, q, [=](){ - auto pickle = olmAccount->pickle(picklingMode); - AccountSettings(data->userId()).setEncryptionAccountPickle(std::get(pickle)); - //TODO handle errors - }); + connect(olmAccount.get(), &QOlmAccount::needsSave, q, &Connection::saveOlmAccount); + encryptionManager = new EncryptionManager(q); if (accountSettings.encryptionAccountPickle().isEmpty()) { @@ -2042,3 +2042,11 @@ PicklingMode Connection::picklingMode() const return d->picklingMode; } #endif + +void Connection::saveOlmAccount() +{ + qCDebug(E2EE) << "Saving olm account"; + auto pickle = d->olmAccount->pickle(d->picklingMode); + AccountSettings(d->data->userId()).setEncryptionAccountPickle(std::get(pickle)); + //TODO handle errors +} diff --git a/lib/connection.h b/lib/connection.h index d0945aa4..f5f06471 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -480,6 +480,9 @@ public: setUserFactory(defaultUserFactory()); } + /// Saves the olm account data to disk. Usually doesn't need to be called manually. + void saveOlmAccount(); + public Q_SLOTS: /// \brief Set the homeserver base URL and retrieve its login flows /// -- cgit v1.2.3 From 06facdb1179e2e6789d7263541294fb427f649e5 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 15 Nov 2021 21:57:59 +0100 Subject: Move non-cache data to a non-cache location --- lib/connection.cpp | 16 ++++++++++++++-- lib/connection.h | 2 ++ lib/encryptionmanager.cpp | 4 ++-- lib/room.cpp | 5 ++--- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 20b4a113..cd4c9838 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1949,7 +1949,7 @@ void Connection::Private::saveDevicesList() QElapsedTimer et; et.start(); - QFile outFile { q->stateCacheDir().filePath("deviceslist.json") }; + QFile outFile { q->e2eeDataDir() + QStringLiteral("/deviceslist.json") }; if (!outFile.open(QFile::WriteOnly)) { qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); @@ -1997,7 +1997,7 @@ void Connection::Private::saveDevicesList() void Connection::Private::loadDevicesList() { - QFile file { q->stateCacheDir().filePath("deviceslist.json") }; + QFile file { q->e2eeDataDir() + QStringLiteral("/deviceslist.json") }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No devicesList cache exists. Creating new"; return; @@ -2050,3 +2050,15 @@ void Connection::saveOlmAccount() AccountSettings(d->data->userId()).setEncryptionAccountPickle(std::get(pickle)); //TODO handle errors } + +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; +} diff --git a/lib/connection.h b/lib/connection.h index f5f06471..d1e3a992 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -399,6 +399,8 @@ 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/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 81c13e50..36cfb34c 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -51,7 +51,7 @@ public: } } void loadSessions() { - QFile file { static_cast(q->parent())->stateCacheDir().filePath("olmsessions.json") }; + QFile file { static_cast(q->parent())->e2eeDataDir() + QStringLiteral("/olmsessions.json") }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No sessions cache exists."; return; @@ -80,7 +80,7 @@ public: } } void saveSessions() { - QFile outFile { static_cast(q->parent())->stateCacheDir().filePath("olmsessions.json") }; + QFile outFile { static_cast(q->parent())->e2eeDataDir() + QStringLiteral("/olmsessions.json") }; if (!outFile.open(QFile::WriteOnly)) { qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); diff --git a/lib/room.cpp b/lib/room.cpp index 94f0c9eb..963b9f88 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -372,7 +372,7 @@ public: std::map, std::unique_ptr> groupSessions; void loadMegOlmSessions() { - QFile file { connection->stateCacheDir().filePath(QStringLiteral("megolm/%1.json").arg(id)) }; + QFile file { connection->e2eeDataDir() + QStringLiteral("/%1.json").arg(id) }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No megolm sessions cache exists."; return; @@ -403,8 +403,7 @@ public: } } void saveMegOlmSessions() { - connection->stateCacheDir().mkdir("megolm"); - QFile outFile { connection->stateCacheDir().filePath(QStringLiteral("megolm/%1.json").arg(id))}; + QFile outFile { connection->e2eeDataDir() + QStringLiteral("/%1.json").arg(id)}; if (!outFile.open(QFile::WriteOnly)) { qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); -- cgit v1.2.3 From 15e75b20d5bb9339a8b769b717db00fb5c16b050 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 16 Nov 2021 23:23:26 +0100 Subject: Add function to decrypt notifications --- lib/connection.cpp | 12 +++++++++++- lib/connection.h | 1 + lib/room.cpp | 1 - 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index cd4c9838..6ed116c4 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -837,7 +837,6 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); return; } - qWarning() << event.fullJson(); const auto decryptedEvent = sessionDecryptMessage(event); if(!decryptedEvent) { qCWarning(E2EE) << "Failed to decrypt event" << event.id(); @@ -2062,3 +2061,14 @@ QString Connection::e2eeDataDir() const dir.mkpath(path); return path; } + +QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) +{ + auto room = provideRoom(notification["room_id"].toString()); + auto event = makeEvent(notification["event"].toObject()); + auto decrypted = room->decryptMessage(*event); + if(!decrypted) { + return QJsonObject(); + } + return decrypted->fullJson(); +} diff --git a/lib/connection.h b/lib/connection.h index d1e3a992..824c7fb4 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -664,6 +664,7 @@ public Q_SLOTS: #ifdef Quotient_E2EE_ENABLED void encryptionUpdate(Room *room); PicklingMode picklingMode() const; + QJsonObject decryptNotification(const QJsonObject ¬ification); #endif Q_SIGNALS: /// \brief Initial server resolution has failed diff --git a/lib/room.cpp b/lib/room.cpp index 963b9f88..d755f8eb 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1599,7 +1599,6 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, if (auto encryptedEvent = d->timeline[i].viewAs()) { auto decrypted = decryptMessage(*encryptedEvent); if(decrypted) { - qWarning() << "decrypted" << decrypted->fullJson(); auto oldEvent = d->timeline[i].replaceEvent(std::move(decrypted)); emit replacedEvent(d->timeline[i].event(), rawPtr(oldEvent)); } -- cgit v1.2.3 From 545852ca45fadb3ee43072763e81cbfba0366e25 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 19 Nov 2021 21:49:46 +0100 Subject: Fix compilation --- CMakeLists.txt | 2 +- lib/connection.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3977a9d0..dbb43f89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,7 +109,7 @@ if (${PROJECT_NAME}_ENABLE_E2EE) endif() endif() -find_package(Qt${QT_MAJOR_VERSION}Keychain REQUIRED) +find_package(${Qt}Keychain REQUIRED) # Set up source files list(APPEND lib_SRCS diff --git a/lib/connection.cpp b/lib/connection.cpp index 6ed116c4..95ed1eb6 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2045,9 +2045,11 @@ PicklingMode Connection::picklingMode() const void Connection::saveOlmAccount() { qCDebug(E2EE) << "Saving olm account"; +#ifdef Quotient_E2EE_ENABLED auto pickle = d->olmAccount->pickle(d->picklingMode); AccountSettings(d->data->userId()).setEncryptionAccountPickle(std::get(pickle)); //TODO handle errors +#endif } QString Connection::e2eeDataDir() const @@ -2062,6 +2064,7 @@ QString Connection::e2eeDataDir() const return path; } +#ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { auto room = provideRoom(notification["room_id"].toString()); @@ -2072,3 +2075,4 @@ QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) } return decrypted->fullJson(); } +#endif -- cgit v1.2.3 From 6896d3e1bd57c398bd4e1ee9badac87dc66ccea7 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sat, 20 Nov 2021 00:16:14 +0100 Subject: We always need qtkeychain --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f1db8ef..c2076dea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,7 +122,6 @@ jobs: echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN with E2EE" >>$GITHUB_ENV - name: Build and install QtKeychain - if: matrix.e2ee run: | cd ${{ runner.workspace }} git clone https://github.com/frankosterfeld/qtkeychain.git -- cgit v1.2.3 From 877591582f07b5c5c104370e80c858b951c0757f Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 20 Nov 2021 16:58:40 +0100 Subject: Use UnorderedMap instead of std::map --- lib/encryptionmanager.cpp | 2 +- lib/room.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 36cfb34c..b09e5260 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -38,7 +38,7 @@ public: EncryptionManager* q; // A map from senderKey to InboundSession - std::map> sessions; // TODO: cache + UnorderedMap> sessions; // TODO: cache void updateDeviceKeys( const QHash>& deviceKeys) diff --git a/lib/room.cpp b/lib/room.cpp index d755f8eb..65ce82ac 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -369,7 +369,7 @@ 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. - std::map, std::unique_ptr> groupSessions; + UnorderedMap, std::unique_ptr> groupSessions; void loadMegOlmSessions() { QFile file { connection->e2eeDataDir() + QStringLiteral("/%1.json").arg(id) }; -- cgit v1.2.3 From e4802dc967c640ebac1d0bc7db55b47b79233544 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 20 Nov 2021 20:54:34 +0100 Subject: Try fixing windows CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2076dea..d0c391be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,7 +123,7 @@ jobs: - name: Build and install QtKeychain run: | - cd ${{ runner.workspace }} + cd .. git clone https://github.com/frankosterfeld/qtkeychain.git cmake -S qtkeychain -B qtkeychain/build $CMAKE_ARGS cmake --build qtkeychain/build --target install -- cgit v1.2.3 From e99802772ebab9802e2f35d83ce1de9f83691d90 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 27 Nov 2021 00:11:36 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- lib/connection.cpp | 8 ++++---- lib/encryptionmanager.cpp | 2 +- lib/jobs/downloadfilejob.cpp | 2 +- lib/jobs/downloadfilejob.h | 2 +- lib/mxcreply.cpp | 3 +-- lib/room.cpp | 11 +++++------ 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 95ed1eb6..df9ff445 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -57,7 +57,7 @@ #include -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#if QT_VERSION_MAJOR >= 6 # include #else # include @@ -830,7 +830,7 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { #ifdef Quotient_E2EE_ENABLED - if(toDeviceEvents.size() > 0) { + if (!toDeviceEvents.empty()) { qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events"; visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { @@ -1020,7 +1020,7 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, #ifdef Quotient_E2EE_ENABLED DownloadFileJob* Connection::downloadFile(const QUrl& url, - const EncryptedFile file, + const EncryptedFile& file, const QString& localFilename) { auto mediaId = url.authority() + url.path(); @@ -1996,7 +1996,7 @@ void Connection::Private::saveDevicesList() void Connection::Private::loadDevicesList() { - QFile file { q->e2eeDataDir() + QStringLiteral("/deviceslist.json") }; + QFile file { q->e2eeDataDir() % "/deviceslist.json" }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No devicesList cache exists. Creating new"; return; diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index b09e5260..c816eda7 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -51,7 +51,7 @@ public: } } void loadSessions() { - QFile file { static_cast(q->parent())->e2eeDataDir() + QStringLiteral("/olmsessions.json") }; + QFile file { static_cast(q->parent())->e2eeDataDir() % "/olmsessions.json" }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No sessions cache exists."; return; diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 2fba1973..0b4cf6d2 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -49,7 +49,7 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, #ifdef Quotient_E2EE_ENABLED DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, - const EncryptedFile file, + const EncryptedFile& file, const QString& localFilename) : GetContentJob(serverName, mediaId) , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index 67a3e95f..90d80478 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -16,7 +16,7 @@ public: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFile file, const QString& localFilename = {}); + DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFile& file, const QString& localFilename = {}); #endif QString targetFileName() const; diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 639c1324..2ad49c2c 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -3,8 +3,7 @@ #include "mxcreply.h" -#include -#include +#include #include "accountregistry.h" #include "connection.h" #include "room.h" diff --git a/lib/room.cpp b/lib/room.cpp index 65ce82ac..fca1912f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -456,7 +456,7 @@ public: return false; } qCWarning(E2EE) << "Adding inbound session"; - groupSessions[qMakePair(senderKey, sessionId)] = std::move(megolmSession); + groupSessions[{senderKey, sessionId}] = std::move(megolmSession); saveMegOlmSessions(); return true; } @@ -469,13 +469,14 @@ public: { QPair senderSessionPairKey = qMakePair(senderKey, sessionId); - if (groupSessions.find(senderSessionPairKey) == groupSessions.end()) { + auto groupSessionIt = groupSessions.find(senderSessionPairKey); + if (groupSessionIt == groupSessions.end()) { qCWarning(E2EE) << "Unable to decrypt event" << eventId << "The sender's device has not sent us the keys for " "this message"; return QString(); } - auto& senderSession = groupSessions[senderSessionPairKey]; + auto& senderSession = *groupSessionIt; auto decryptResult = senderSession->decrypt(cipher); if(std::holds_alternative(decryptResult)) { qCWarning(E2EE) << "Unable to decrypt event" << eventId @@ -520,8 +521,6 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) emit baseStateLoaded(); return this == r; // loadedRoomState fires only once per room }); - qCDebug(STATE) << "New" << initialJoinState << "Room:" << id; - #ifdef Quotient_E2EE_ENABLED connectSingleShot(this, &Room::encryption, this, [=](){ connection->encryptionUpdate(this); @@ -1555,7 +1554,7 @@ RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) qCWarning(E2EE) << "Encrypted message is empty"; return {}; } - QJsonObject eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); + auto eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); eventObject["event_id"] = encryptedEvent.id(); eventObject["sender"] = encryptedEvent.senderId(); eventObject["origin_server_ts"] = encryptedEvent.originTimestamp().toMSecsSinceEpoch(); -- cgit v1.2.3 From 8020505eb582479cf62d0157fe866f63a888f1a9 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 27 Nov 2021 00:52:24 +0100 Subject: Apply more suggestions --- lib/connection.h | 2 +- lib/encryptionmanager.cpp | 5 ----- lib/networkaccessmanager.cpp | 6 ------ lib/room.cpp | 21 +++++++-------------- 4 files changed, 8 insertions(+), 26 deletions(-) diff --git a/lib/connection.h b/lib/connection.h index 824c7fb4..d2347d1d 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -573,7 +573,7 @@ public Q_SLOTS: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob* downloadFile(const QUrl& url, const EncryptedFile file, + DownloadFileJob* downloadFile(const QUrl& url, const EncryptedFile& file, const QString& localFilename = {}); #endif /** diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index c816eda7..84282dbf 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -107,12 +107,7 @@ public: rootObj.insert(QStringLiteral("sessions"), sessionsJson); } - #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) const auto data = QJsonDocument(rootObj).toJson(QJsonDocument::Compact); - #else - QJsonDocument json { rootObj }; - const auto data = json.toJson(QJsonDocument::Compact); - #endif outFile.write(data.data(), data.size()); qCDebug(E2EE) << "Sessions saved to" << outFile.fileName(); diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index c660cff8..d0380cec 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -12,12 +12,6 @@ #include #include #include -#include "accountregistry.h" -#include "mxcreply.h" -#include "connection.h" -#include "events/eventcontent.h" - -#include "room.h" using namespace Quotient; diff --git a/lib/room.cpp b/lib/room.cpp index fca1912f..e143747b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -430,12 +430,7 @@ public: rootObj.insert(QStringLiteral("sessions"), sessionsJson); } -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) const auto data = QJsonDocument(rootObj).toJson(QJsonDocument::Compact); -#else - QJsonDocument json { rootObj }; - const auto data = json.toJson(QJsonDocument::Compact); -#endif outFile.write(data.data(), data.size()); qCDebug(E2EE) << "Megolm sessions saved to" << outFile.fileName(); @@ -476,26 +471,26 @@ public: "this message"; return QString(); } - auto& senderSession = *groupSessionIt; + auto& senderSession = groupSessionIt->second; auto decryptResult = senderSession->decrypt(cipher); if(std::holds_alternative(decryptResult)) { qCWarning(E2EE) << "Unable to decrypt event" << eventId << "with matching megolm session:" << std::get(decryptResult); return QString(); } - std::pair decrypted = std::get>(decryptResult); - QPair properties = groupSessionIndexRecord.value(qMakePair(senderSession->sessionId(), decrypted.second)); - if (properties.first.isEmpty()) { - groupSessionIndexRecord.insert(qMakePair(senderSession->sessionId(), decrypted.second), qMakePair(eventId, timestamp)); + const auto& [content, index] = std::get>(decryptResult); + const auto& [recordEventId, ts] = groupSessionIndexRecord.value({senderSession->sessionId(), index}); + if (eventId.isEmpty()) { + groupSessionIndexRecord.insert({senderSession->sessionId(), index}, {recordEventId, timestamp}); } else { - if ((properties.first != eventId) || (properties.second != timestamp)) { + if ((eventId != recordEventId) || (ts != timestamp)) { qCWarning(E2EE) << "Detected a replay attack on event" << eventId; return QString(); } } //TODO is this necessary? saveMegOlmSessions(); - return decrypted.first; + return content; } #endif // Quotient_E2EE_ENABLED @@ -2673,7 +2668,6 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) QElapsedTimer et; et.start(); - //TODO should this be done before dropDuplicateEvents? for(long unsigned int i = 0; i < events.size(); i++) { if(auto* encrypted = eventCast(events[i])) { auto decrypted = q->decryptMessage(*encrypted); @@ -2836,7 +2830,6 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) Changes changes {}; - //TODO should this be done before dropDuplicateEvents? for(long unsigned int i = 0; i < events.size(); i++) { if(auto* encrypted = eventCast(events[i])) { qDebug() << "Encrypted Event"; -- cgit v1.2.3 From 3128df9daa196b2cf3cdb8e029e22d79c397ff66 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 27 Nov 2021 01:34:44 +0100 Subject: Apply even more suggestions --- lib/events/encryptedevent.cpp | 22 ++++++++++++++++++ lib/events/encryptedevent.h | 1 + lib/room.cpp | 53 ++++++++++++++----------------------------- 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 0290f973..c9257584 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedevent.h" +#include "roommessageevent.h" using namespace Quotient; @@ -30,3 +31,24 @@ EncryptedEvent::EncryptedEvent(const QJsonObject& obj) { qCDebug(E2EE) << "Encrypted event from" << senderId(); } + +RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const +{ + auto eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); + 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(); + auto content = eventObject["content"].toObject(); + content["m.relates_to"] = relates; + eventObject["content"] = content; + } + if(unsignedJson().contains("redacts")) { + auto redacts = unsignedJson()["redacts"].toString(); + auto unsign = eventObject["unsigned"].toObject(); + unsign["redacts"] = redacts; + eventObject["unsigned"] = unsign; + } + return makeEvent(eventObject); +} diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 1d7ea913..28398827 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -61,6 +61,7 @@ public: /* device_id and session_id are required with Megolm */ QString deviceId() const { return contentPart(DeviceIdKeyL); } QString sessionId() const { return contentPart(SessionIdKeyL); } + RoomEventPtr createDecrypted(const QString &decrypted) const; }; REGISTER_EVENT_TYPE(EncryptedEvent) diff --git a/lib/room.cpp b/lib/room.cpp index e143747b..07ffd0cd 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -366,9 +366,7 @@ public: // A map from to QHash, QPair> groupSessionIndexRecord; // TODO: cache - // 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. + // A map from (senderKey, sessionId) to InboundGroupSession UnorderedMap, std::unique_ptr> groupSessions; void loadMegOlmSessions() { @@ -399,7 +397,7 @@ public: qCWarning(E2EE) << "Failed to unpickle olm session"; continue; } - groupSessions[qMakePair(senderKey, sessionId)] = std::move(std::get>(sessionResult)); + groupSessions[{senderKey, sessionId}] = std::move(std::get>(sessionResult)); } } void saveMegOlmSessions() { @@ -438,7 +436,7 @@ public: bool addInboundGroupSession(QString senderKey, QString sessionId, QString sessionKey) { - if (groupSessions.find(qMakePair(senderKey, sessionId)) != groupSessions.end()) { + if (groupSessions.find({senderKey, sessionId}) != groupSessions.end()) { qCWarning(E2EE) << "Inbound Megolm session" << sessionId << "with senderKey" << senderKey << "already exists"; return false; @@ -462,8 +460,7 @@ public: const QString& eventId, QDateTime timestamp) { - QPair senderSessionPairKey = - qMakePair(senderKey, sessionId); + const auto senderSessionPairKey = qMakePair(senderKey, sessionId); auto groupSessionIt = groupSessions.find(senderSessionPairKey); if (groupSessionIt == groupSessions.end()) { qCWarning(E2EE) << "Unable to decrypt event" << eventId @@ -1540,36 +1537,20 @@ RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; return {}; #else // Quotient_E2EE_ENABLED - if (encryptedEvent.algorithm() == MegolmV1AesSha2AlgoKey) { - QString decrypted = d->groupSessionDecryptMessage( - encryptedEvent.ciphertext(), encryptedEvent.senderKey(), - encryptedEvent.sessionId(), encryptedEvent.id(), - encryptedEvent.originTimestamp()); - if (decrypted.isEmpty()) { - qCWarning(E2EE) << "Encrypted message is empty"; - return {}; - } - auto eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); - eventObject["event_id"] = encryptedEvent.id(); - eventObject["sender"] = encryptedEvent.senderId(); - eventObject["origin_server_ts"] = encryptedEvent.originTimestamp().toMSecsSinceEpoch(); - if(encryptedEvent.contentJson().contains("m.relates_to")) { - auto relates = encryptedEvent.contentJson()["m.relates_to"].toObject(); - auto content = eventObject["content"].toObject(); - content["m.relates_to"] = relates; - eventObject["content"] = content; - } - if(encryptedEvent.unsignedJson().contains("redacts")) { - auto redacts = encryptedEvent.unsignedJson()["redacts"].toString(); - auto unsign = eventObject["unsigned"].toObject(); - unsign["redacts"] = redacts; - eventObject["unsigned"] = unsign; - } - return makeEvent(eventObject); + if (encryptedEvent.algorithm() != MegolmV1AesSha2AlgoKey) { + qWarning(E2EE) << "Algorithm of the encrypted event with id" + << encryptedEvent.id() << "is not decryptable by the current device"; + return {}; } - qCDebug(E2EE) << "Algorithm of the encrypted event with id" - << encryptedEvent.id() << "is not decryptable by the current device"; - return {}; + QString decrypted = d->groupSessionDecryptMessage( + encryptedEvent.ciphertext(), encryptedEvent.senderKey(), + encryptedEvent.sessionId(), encryptedEvent.id(), + encryptedEvent.originTimestamp()); + if (decrypted.isEmpty()) { + qCWarning(E2EE) << "Encrypted message is empty"; + return {}; + } + return encryptedEvent.createDecrypted(decrypted); #endif // Quotient_E2EE_ENABLED } -- 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(-) 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(-) 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 47bd4dfb2bc720d2b5919b93985f87d918af572a Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 7 Dec 2021 00:25:05 +0100 Subject: Port E2EE to database instead of JSON files --- CMakeLists.txt | 5 +- lib/connection.cpp | 23 ++-- lib/crypto/e2ee.h | 6 + lib/crypto/qolminboundsession.cpp | 2 +- lib/crypto/qolminboundsession.h | 2 +- lib/crypto/qolmsession.h | 3 - lib/database.cpp | 240 ++++++++++++++++++++++++++++++++++++++ lib/database.h | 46 ++++++++ lib/encryptionmanager.cpp | 96 +++------------ lib/events/encryptedevent.cpp | 1 + lib/logging.h | 1 + lib/room.cpp | 82 ++----------- 12 files changed, 335 insertions(+), 172 deletions(-) create mode 100644 lib/database.cpp create mode 100644 lib/database.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dbb43f89..9f886094 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,7 +83,7 @@ else() set(QtExtraModules "Multimedia") # See #483 endif() string(REGEX REPLACE "^(.).*" "Qt\\1" Qt ${QtMinVersion}) # makes "Qt5" or "Qt6" -find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModules}) +find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test Sql ${QtExtraModules}) get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") @@ -133,6 +133,7 @@ list(APPEND lib_SRCS lib/eventitem.cpp lib/accountregistry.cpp lib/mxcreply.cpp + lib/database.cpp lib/events/event.cpp lib/events/roomevent.cpp lib/events/stateevent.cpp @@ -327,7 +328,7 @@ if (${PROJECT_NAME}_ENABLE_E2EE) find_dependency(OpenSSL)") # For QuotientConfig.cmake.in endif() -target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui ${QTKEYCHAIN_LIBRARIES}) +target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui ${Qt}::Sql ${QTKEYCHAIN_LIBRARIES}) if (Qt STREQUAL Qt5) # See #483 target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) diff --git a/lib/connection.cpp b/lib/connection.cpp index ac428a62..f344807e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -62,6 +62,8 @@ # include #endif +#include "database.h" + using namespace Quotient; // This is very much Qt-specific; STL iterators don't have key() and value() @@ -274,6 +276,7 @@ Connection::Connection(const QUrl& server, QObject* parent) }); #endif d->q = this; // All d initialization should occur before this line + Database::instance(); } Connection::Connection(QObject* parent) : Connection({}, parent) {} @@ -439,6 +442,7 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) auto loginJob = q->callApi(std::forward(loginArgs)...); connect(loginJob, &BaseJob::success, q, [this, loginJob] { + Database::instance().clear(loginJob->userId()); data->setToken(loginJob->accessToken().toLatin1()); data->setDeviceId(loginJob->deviceId()); completeSetup(loginJob->userId()); @@ -504,7 +508,7 @@ void Connection::Private::completeSetup(const QString& mxId) encryptionManager = new EncryptionManager(q); - if (accountSettings.encryptionAccountPickle().isEmpty()) { + if (Database::instance().accountPickle(data->userId()).isEmpty()) { // create new account and save unpickle data olmAccount->createNewAccount(); auto job = q->callApi(olmAccount->deviceKeys()); @@ -513,7 +517,7 @@ void Connection::Private::completeSetup(const QString& mxId) }); } else { // account already existing - auto pickle = accountSettings.encryptionAccountPickle(); + auto pickle = Database::instance().accountPickle(data->userId()); olmAccount->unpickle(pickle, picklingMode); } #endif // Quotient_E2EE_ENABLED @@ -1978,15 +1982,9 @@ void Connection::Private::saveDevicesList() rootObj.insert(QStringLiteral("sync_token"), q->nextBatchToken()); } -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - const auto data = - cacheToBinary ? QCborValue::fromJsonValue(rootObj).toCbor() - : QJsonDocument(rootObj).toJson(QJsonDocument::Compact); -#else + QJsonDocument json { rootObj }; - const auto data = cacheToBinary ? json.toBinaryData() - : json.toJson(QJsonDocument::Compact); -#endif + const auto data = json.toJson(); qCDebug(PROFILER) << "DeviceList generated in" << et; outFile.write(data.data(), data.size()); @@ -2043,11 +2041,10 @@ PicklingMode Connection::picklingMode() const void Connection::saveOlmAccount() { - qCDebug(E2EE) << "Saving olm account"; + qDebug() << "Saving olm account"; #ifdef Quotient_E2EE_ENABLED auto pickle = d->olmAccount->pickle(d->picklingMode); - AccountSettings(d->data->userId()).setEncryptionAccountPickle(std::get(pickle)); - //TODO handle errors + Database::instance().setAccountPickle(userId(), std::get(pickle)); #endif } diff --git a/lib/crypto/e2ee.h b/lib/crypto/e2ee.h index 2d280185..41cd2878 100644 --- a/lib/crypto/e2ee.h +++ b/lib/crypto/e2ee.h @@ -49,6 +49,12 @@ struct Encrypted { 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; diff --git a/lib/crypto/qolminboundsession.cpp b/lib/crypto/qolminboundsession.cpp index beaf3299..31d699f1 100644 --- a/lib/crypto/qolminboundsession.cpp +++ b/lib/crypto/qolminboundsession.cpp @@ -72,7 +72,7 @@ QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const return pickledBuf; } -std::variant, QOlmError> QOlmInboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +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()]); diff --git a/lib/crypto/qolminboundsession.h b/lib/crypto/qolminboundsession.h index 36ab4942..362e42ba 100644 --- a/lib/crypto/qolminboundsession.h +++ b/lib/crypto/qolminboundsession.h @@ -27,7 +27,7 @@ public: QByteArray pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. - static std::variant, QOlmError> unpickle(QByteArray &picked, const PicklingMode &mode); + 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, diff --git a/lib/crypto/qolmsession.h b/lib/crypto/qolmsession.h index 7a040b3d..711ca66b 100644 --- a/lib/crypto/qolmsession.h +++ b/lib/crypto/qolmsession.h @@ -73,7 +73,4 @@ private: 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 diff --git a/lib/database.cpp b/lib/database.cpp new file mode 100644 index 00000000..153aab31 --- /dev/null +++ b/lib/database.cpp @@ -0,0 +1,240 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "database.h" + +#include +#include +#include +#include +#include +#include + +#include "crypto/e2ee.h" +#include "crypto/qolmsession.h" +#include "crypto/qolminboundsession.h" + +//TODO: delete room specific data when leaving room + +using namespace Quotient; +Database::Database() +{ + QSqlDatabase::addDatabase(QStringLiteral("QSQLITE")); + QString databasePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QDir(databasePath).mkpath(databasePath); + QSqlDatabase::database().setDatabaseName(databasePath + QStringLiteral("/database.db3")); + QSqlDatabase::database().open(); + + switch(version()) { + case 0: migrateTo1(); + } +} + +int Database::version() +{ + auto query = execute(QStringLiteral("PRAGMA user_version;")); + if (query.next()) { + bool ok; + int value = query.value(0).toInt(&ok); + qDebug() << "Database version" << value; + if (ok) + return value; + } else { + qCritical() << "Failed to check database version"; + } + return -1; +} + +QSqlQuery Database::execute(const QString &queryString) +{ + auto query = QSqlDatabase::database().exec(queryString); + if (query.lastError().type() != QSqlError::NoError) { + qCritical() << "Failed to execute query"; + qCritical() << query.lastQuery(); + qCritical() << query.lastError(); + } + return query; +} + +QSqlQuery Database::execute(QSqlQuery &query) +{ + if (!query.exec()) { + qCritical() << "Failed to execute query"; + qCritical() << query.lastQuery(); + qCritical() << query.lastError(); + } + return query; +} + +void Database::transaction() +{ + QSqlDatabase::database().transaction(); +} + +void Database::commit() +{ + QSqlDatabase::database().commit(); +} + +void Database::migrateTo1() +{ + qDebug() << "Migrating database to version 1"; + transaction(); + execute(QStringLiteral("CREATE TABLE Accounts (matrixId TEXT UNIQUE, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE OlmSessions (matrixId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE InboundMegolmSessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE OutboundMegolmSessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE GroupSessionIndexRecord (matrixId TEXT, roomId TEXT, sessionId TEXT, i INTEGER, eventId TEXT, ts INTEGER);")); + execute(QStringLiteral("PRAGMA user_version = 1;")); + commit(); +} + +QByteArray Database::accountPickle(const QString &id) +{ + QSqlQuery query; + query.prepare(QStringLiteral("SELECT pickle FROM Accounts WHERE matrixId=:matrixId;")); + query.bindValue(":matrixId", id); + execute(query); + if (query.next()) { + return query.value(QStringLiteral("pickle")).toByteArray(); + } + return {}; +} + +void Database::setAccountPickle(const QString &id, const QByteArray &pickle) +{ + QSqlQuery query; + query.prepare(QStringLiteral("INSERT INTO Accounts(matrixId, pickle) VALUES(:matrixId, :pickle) ON CONFLICT (matrixId) DO UPDATE SET pickle=:pickle WHERE matrixId=:matrixId;")); + query.bindValue(":matrixId", id); + query.bindValue(":pickle", pickle); + transaction(); + execute(query); + commit(); +} + +void Database::clear(const QString &id) +{ + QSqlQuery query; + query.prepare(QStringLiteral("DELETE FROM Accounts(matrixId, pickle) WHERE matrixId=:matrixId;")); + query.bindValue(":matrixId", id); + + QSqlQuery sessionsQuery; + sessionsQuery.prepare(QStringLiteral("DELETE FROM OlmSessions WHERE matrixId=:matrixId;")); + sessionsQuery.bindValue(":matrixId", id); + + QSqlQuery megolmSessionsQuery; + megolmSessionsQuery.prepare(QStringLiteral("DELETE FROM InboundMegolmSessions WHERE matrixId=:matrixId;")); + megolmSessionsQuery.bindValue(":matrixId", id); + + QSqlQuery groupSessionIndexRecordQuery; + groupSessionIndexRecordQuery.prepare(QStringLiteral("DELETE FROM GroupSessionIndexRecord WHERE matrixId=:matrixId;")); + groupSessionIndexRecordQuery.bindValue(":matrixId", matrixId); + + transaction(); + execute(query); + execute(sessionsQuery); + execute(megolmSessionsQuery); + execute(groupSessionIndexRecordQuery); + commit(); + +} + +void Database::saveOlmSession(const QString& matrixId, const QString& senderKey, const QString& sessionId, const QByteArray &pickle) +{ + QSqlQuery query; + query.prepare(QStringLiteral("INSERT INTO OlmSessions(matrixId, senderKey, sessionId, pickle) VALUES(:matrixId, :senderKey, :sessionId, :pickle);")); + query.bindValue(":matrixId", matrixId); + query.bindValue(":senderKey", senderKey); + query.bindValue(":sessionId", sessionId); + query.bindValue(":pickle", pickle); + transaction(); + execute(query); + commit(); +} + +UnorderedMap> Database::loadOlmSessions(const QString& matrixId, const PicklingMode& picklingMode) +{ + QSqlQuery query; + query.prepare(QStringLiteral("SELECT * FROM OlmSessions WHERE matrixId=:matrixId;")); + query.bindValue(":matrixId", matrixId); + transaction(); + execute(query); + commit(); + UnorderedMap> sessions; + while (query.next()) { + auto session = QOlmSession::unpickle(query.value("pickle").toByteArray(), picklingMode); + if (std::holds_alternative(session)) { + qCWarning(E2EE) << "Failed to unpickle olm session"; + continue; + } + sessions[query.value("senderKey").toString()].push_back(std::move(std::get(session))); + } + return sessions; +} + +UnorderedMap, QOlmInboundGroupSessionPtr> Database::loadMegolmSessions(const QString& matrixId, const QString& roomId, const PicklingMode& picklingMode) +{ + QSqlQuery query; + query.prepare(QStringLiteral("SELECT * FROM InboundMegolmSessions WHERE matrixId=:matrixId AND roomId=:roomId;")); + query.bindValue(":matrixId", matrixId); + query.bindValue(":roomId", roomId); + transaction(); + execute(query); + commit(); + UnorderedMap, QOlmInboundGroupSessionPtr> sessions; + while (query.next()) { + auto session = QOlmInboundGroupSession::unpickle(query.value("pickle").toByteArray(), picklingMode); + if (std::holds_alternative(session)) { + qCWarning(E2EE) << "Failed to unpickle megolm session"; + continue; + } + sessions[{query.value("senderKey").toString(), query.value("sessionId").toString()}] = std::move(std::get(session)); + } + return sessions; +} + +void Database::saveMegolmSession(const QString& matrixId, const QString& roomId, const QString& senderKey, const QString& sessionId, const QByteArray& pickle) +{ + QSqlQuery query; + query.prepare(QStringLiteral("INSERT INTO InboundMegolmSessions(matrixId, roomId, senderKey, sessionId, pickle) VALUES(:matrixId, :roomId, :senderKey, :sessionId, :pickle);")); + query.bindValue(":matrixId", matrixId); + query.bindValue(":roomId", roomId); + query.bindValue(":senderKey", senderKey); + query.bindValue(":sessionId", sessionId); + query.bindValue(":pickle", pickle); + transaction(); + execute(query); + commit(); +} + +void Database::addGroupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts) +{ + QSqlQuery query; + query.prepare("INSERT INTO GroupSessionIndexRecord(matrixId, roomId, sessionId, i, eventId, ts) VALUES(:matrixId, :roomId, :sessionId, :index, :eventId, :ts);"); + query.bindValue(":matrixId", matrixId); + query.bindValue(":roomId", roomId); + query.bindValue(":sessionId", sessionId); + query.bindValue(":index", index); + query.bindValue(":eventId", eventId); + query.bindValue(":ts", ts); + transaction(); + execute(query); + commit(); +} + +QPair Database::groupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, qint64 index) +{ + QSqlQuery query; + query.prepare(QStringLiteral("SELECT * FROM GroupSessionIndexRecord WHERE matrixId=:matrixId AND roomId=:roomId AND sessionId=:sessionId AND i=:index;")); + query.bindValue(":matrixId", matrixId); + query.bindValue(":roomId", roomId); + query.bindValue(":sessionId", sessionId); + query.bindValue(":index", index); + transaction(); + execute(query); + commit(); + if (!query.next()) { + return {}; + } + return {query.value("eventId").toString(), query.value("ts").toLongLong()}; +} diff --git a/lib/database.h b/lib/database.h new file mode 100644 index 00000000..ed356820 --- /dev/null +++ b/lib/database.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include + +#include "crypto/e2ee.h" + +namespace Quotient { +class Database : public QObject +{ + Q_OBJECT + +public: + static Database &instance() + { + static Database _instance; + return _instance; + } + + int version(); + void transaction(); + void commit(); + QSqlQuery execute(const QString &queryString); + QSqlQuery execute(QSqlQuery &query); + + QByteArray accountPickle(const QString &id); + void setAccountPickle(const QString &id, const QByteArray &pickle); + void clear(const QString &id); + void saveOlmSession(const QString& matrixId, const QString& senderKey, const QString& sessionId, const QByteArray &pickle); + UnorderedMap> loadOlmSessions(const QString& matrixId, const PicklingMode& picklingMode); + UnorderedMap, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& matrixId, const QString& roomId, const PicklingMode& picklingMode); + void saveMegolmSession(const QString& matrixId, const QString& roomId, const QString& senderKey, const QString& sessionKey, const QByteArray& pickle); + void addGroupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts); + QPair groupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, qint64 index); + + +private: + Database(); + + void migrateTo1(); +}; +} diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 5c106e12..e5fa978f 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -8,6 +8,7 @@ #include "connection.h" #include "crypto/e2ee.h" #include "events/encryptedfile.h" +#include "database.h" #include "csapi/keys.h" @@ -37,90 +38,28 @@ public: EncryptionManager* q; - // A map from senderKey to InboundSession - UnorderedMap sessions; - void updateDeviceKeys( - const QHash>& deviceKeys) - { - for (auto userId : deviceKeys.keys()) { - for (auto deviceId : deviceKeys.value(userId).keys()) { - auto info = deviceKeys.value(userId).value(deviceId); - // TODO: ed25519Verify, etc - } - } - } + // A map from SenderKey to vector of InboundSession + UnorderedMap> sessions; + void loadSessions() { - QFile file { static_cast(q->parent())->e2eeDataDir() % "/olmsessions.json" }; - if(!file.exists() || !file.open(QIODevice::ReadOnly)) { - qCDebug(E2EE) << "No sessions cache exists."; - return; - } - auto data = file.readAll(); - const auto json = data.startsWith('{') - ? QJsonDocument::fromJson(data).object() -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - : QCborValue::fromCbor(data).toJsonValue().toObject() -#else - : QJsonDocument::fromBinaryData(data).object() -#endif - ; - if (json.isEmpty()) { - qCWarning(MAIN) << "Sessions cache is empty"; - return; - } - for(const auto &senderKey : json["sessions"].toObject().keys()) { - auto pickle = json["sessions"].toObject()[senderKey].toString(); - auto sessionResult = QOlmSession::unpickle(pickle.toLatin1(), static_cast(q->parent())->picklingMode()); - if(std::holds_alternative(sessionResult)) { - qCWarning(E2EE) << "Failed to unpickle olm session"; - continue; - } - sessions[senderKey] = std::move(std::get(sessionResult)); - } + sessions = Database::instance().loadOlmSessions(static_cast(q->parent())->userId(), static_cast(q->parent())->picklingMode()); } - void saveSessions() { - QFile outFile { static_cast(q->parent())->e2eeDataDir() % "/olmsessions.json" }; - if (!outFile.open(QFile::WriteOnly)) { - qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" - << outFile.errorString(); - qCWarning(E2EE) << "Failed to write olm sessions"; + void saveSession(QOlmSessionPtr& session, const QString &senderKey) { + auto pickleResult = session->pickle(static_cast(q->parent())->picklingMode()); + if (std::holds_alternative(pickleResult)) { + qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); return; } - - QJsonObject rootObj { - { QStringLiteral("cache_version"), - QJsonObject { - { QStringLiteral("major"), 1 }, - { QStringLiteral("minor"), 0 } } } - }; - { - QJsonObject sessionsJson; - for (const auto &session : sessions) { - auto pickleResult = session.second->pickle(static_cast(q->parent())->picklingMode()); - if(std::holds_alternative(pickleResult)) { - qCWarning(E2EE) << "Failed to pickle session"; - continue; - } - sessionsJson[session.first] = QString(std::get(pickleResult)); - } - rootObj.insert(QStringLiteral("sessions"), sessionsJson); - } - - const auto data = QJsonDocument(rootObj).toJson(QJsonDocument::Compact); - - outFile.write(data.data(), data.size()); - qCDebug(E2EE) << "Sessions saved to" << outFile.fileName(); + Database::instance().saveOlmSession(static_cast(q->parent())->userId(), 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) { - const auto matches = session.second->matchesInboundSessionFrom(senderKey, message); + 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.second->decrypt(message); - saveSessions(); + const auto result = session->decrypt(message); if(std::holds_alternative(result)) { return std::get(result); } else { @@ -141,8 +80,8 @@ public: qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); } const auto result = newSession->decrypt(message); - sessions[senderKey] = std::move(newSession); - saveSessions(); + saveSession(newSession, senderKey); + sessions[senderKey].push_back(std::move(newSession)); if(std::holds_alternative(result)) { return std::get(result); } else { @@ -153,10 +92,9 @@ public: QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) { Q_ASSERT(message.type() == QOlmMessage::General); - for(auto& session : sessions) { - const auto result = session.second->decrypt(message); + for(auto& session : sessions[senderKey]) { + const auto result = session->decrypt(message); if(std::holds_alternative(result)) { - saveSessions(); return std::get(result); } } diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 2e0d7387..1b5e4441 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -3,6 +3,7 @@ #include "encryptedevent.h" #include "roommessageevent.h" +#include "events/eventloader.h" using namespace Quotient; diff --git a/lib/logging.h b/lib/logging.h index 5bf050a9..fc0a4c99 100644 --- a/lib/logging.h +++ b/lib/logging.h @@ -19,6 +19,7 @@ Q_DECLARE_LOGGING_CATEGORY(SYNCJOB) Q_DECLARE_LOGGING_CATEGORY(THUMBNAILJOB) Q_DECLARE_LOGGING_CATEGORY(NETWORK) Q_DECLARE_LOGGING_CATEGORY(PROFILER) +Q_DECLARE_LOGGING_CATEGORY(DATABASE) namespace Quotient { // QDebug manipulators diff --git a/lib/room.cpp b/lib/room.cpp index e4fe2fb8..8181f16a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -70,6 +70,8 @@ #include "crypto/qolminboundsession.h" #endif // Quotient_E2EE_ENABLED +#include "database.h" + using namespace Quotient; using namespace std::placeholders; using std::move; @@ -363,75 +365,11 @@ public: bool isLocalUser(const User* u) const { return u == q->localUser(); } #ifdef Quotient_E2EE_ENABLED - // A map from to - QHash, QPair> - groupSessionIndexRecord; // TODO: cache // A map from (senderKey, sessionId) to InboundGroupSession - UnorderedMap, std::unique_ptr> groupSessions; + UnorderedMap, QOlmInboundGroupSessionPtr> groupSessions; void loadMegOlmSessions() { - QFile file { connection->e2eeDataDir() + QStringLiteral("/%1.json").arg(id) }; - if(!file.exists() || !file.open(QIODevice::ReadOnly)) { - qCDebug(E2EE) << "No megolm sessions cache exists."; - return; - } - auto data = file.readAll(); - const auto json = data.startsWith('{') - ? QJsonDocument::fromJson(data).object() -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - : QCborValue::fromCbor(data).toJsonValue().toObject() -#else - : QJsonDocument::fromBinaryData(data).object() -#endif - ; - if (json.isEmpty()) { - qCWarning(E2EE) << "Megolm sessions cache is empty"; - return; - } - for(const auto &s : json["sessions"].toArray()) { - auto pickle = s.toObject()["pickle"].toString().toLatin1(); - auto senderKey = s.toObject()["sender_key"].toString(); - auto sessionId = s.toObject()["session_id"].toString(); - auto sessionResult = QOlmInboundGroupSession::unpickle(pickle, connection->picklingMode()); - if(std::holds_alternative(sessionResult)) { - qCWarning(E2EE) << "Failed to unpickle olm session"; - continue; - } - groupSessions[{senderKey, sessionId}] = std::move(std::get>(sessionResult)); - } - } - void saveMegOlmSessions() { - QFile outFile { connection->e2eeDataDir() + QStringLiteral("/%1.json").arg(id)}; - if (!outFile.open(QFile::WriteOnly)) { - qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" - << outFile.errorString(); - qCWarning(E2EE) << "Failed to write megolm sessions"; - return; - } - - QJsonObject rootObj { - { QStringLiteral("cache_version"), - QJsonObject { - { QStringLiteral("major"), 1 }, - { QStringLiteral("minor"), 0 } } } - }; - { - QJsonArray sessionsJson; - for (const auto &session : groupSessions) { - auto pickleResult = session.second->pickle(connection->picklingMode()); - sessionsJson += QJsonObject { - {QStringLiteral("sender_key"), session.first.first}, - {QStringLiteral("session_id"), session.first.second}, - {QStringLiteral("pickle"), QString(pickleResult)} - }; - } - rootObj.insert(QStringLiteral("sessions"), sessionsJson); - } - - const auto data = QJsonDocument(rootObj).toJson(QJsonDocument::Compact); - - outFile.write(data.data(), data.size()); - qCDebug(E2EE) << "Megolm sessions saved to" << outFile.fileName(); + groupSessions = Database::instance().loadMegolmSessions(q->localUser()->id(), q->id(), q->connection()->picklingMode()); } bool addInboundGroupSession(QString senderKey, QString sessionId, QString sessionKey) @@ -449,8 +387,8 @@ public: return false; } qCWarning(E2EE) << "Adding inbound session"; + Database::instance().saveMegolmSession(q->localUser()->id(), q->id(), senderKey, sessionId, megolmSession->pickle(q->connection()->picklingMode())); groupSessions[{senderKey, sessionId}] = std::move(megolmSession); - saveMegOlmSessions(); return true; } @@ -476,17 +414,15 @@ public: return QString(); } const auto& [content, index] = std::get>(decryptResult); - const auto& [recordEventId, ts] = groupSessionIndexRecord.value({senderSession->sessionId(), index}); - if (eventId.isEmpty()) { - groupSessionIndexRecord.insert({senderSession->sessionId(), index}, {recordEventId, timestamp}); + const auto& [recordEventId, ts] = Database::instance().groupSessionIndexRecord(q->localUser()->id(), q->id(), senderSession->sessionId(), index); + if (recordEventId.isEmpty()) { + Database::instance().addGroupSessionIndexRecord(q->localUser()->id(), q->id(), senderSession->sessionId(), index, eventId, timestamp.toMSecsSinceEpoch()); } else { - if ((eventId != recordEventId) || (ts != timestamp)) { + if ((eventId != recordEventId) || (ts != timestamp.toMSecsSinceEpoch())) { qCWarning(E2EE) << "Detected a replay attack on event" << eventId; return QString(); } } - //TODO is this necessary? - saveMegOlmSessions(); return content; } #endif // Quotient_E2EE_ENABLED -- 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 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 0f20fb028602016c718f4fd05cdb18b80442b3ca Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 7 Dec 2021 19:09:14 +0100 Subject: id -> matrixId --- lib/database.cpp | 16 ++++++++-------- lib/database.h | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/database.cpp b/lib/database.cpp index ec285d22..e115b6c3 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -89,11 +89,11 @@ void Database::migrateTo1() commit(); } -QByteArray Database::accountPickle(const QString &id) +QByteArray Database::accountPickle(const QString &matrixId) { QSqlQuery query; query.prepare(QStringLiteral("SELECT pickle FROM Accounts WHERE matrixId=:matrixId;")); - query.bindValue(":matrixId", id); + query.bindValue(":matrixId", matrixId); execute(query); if (query.next()) { return query.value(QStringLiteral("pickle")).toByteArray(); @@ -101,30 +101,30 @@ QByteArray Database::accountPickle(const QString &id) return {}; } -void Database::setAccountPickle(const QString &id, const QByteArray &pickle) +void Database::setAccountPickle(const QString &matrixId, const QByteArray &pickle) { QSqlQuery query; query.prepare(QStringLiteral("INSERT INTO Accounts(matrixId, pickle) VALUES(:matrixId, :pickle) ON CONFLICT (matrixId) DO UPDATE SET pickle=:pickle WHERE matrixId=:matrixId;")); - query.bindValue(":matrixId", id); + query.bindValue(":matrixId", matrixId); query.bindValue(":pickle", pickle); transaction(); execute(query); commit(); } -void Database::clear(const QString &id) +void Database::clear(const QString &matrixId) { QSqlQuery query; query.prepare(QStringLiteral("DELETE FROM Accounts(matrixId, pickle) WHERE matrixId=:matrixId;")); - query.bindValue(":matrixId", id); + query.bindValue(":matrixId", matrixId); QSqlQuery sessionsQuery; sessionsQuery.prepare(QStringLiteral("DELETE FROM OlmSessions WHERE matrixId=:matrixId;")); - sessionsQuery.bindValue(":matrixId", id); + sessionsQuery.bindValue(":matrixId", matrixId); QSqlQuery megolmSessionsQuery; megolmSessionsQuery.prepare(QStringLiteral("DELETE FROM InboundMegolmSessions WHERE matrixId=:matrixId;")); - megolmSessionsQuery.bindValue(":matrixId", id); + megolmSessionsQuery.bindValue(":matrixId", matrixId); QSqlQuery groupSessionIndexRecordQuery; groupSessionIndexRecordQuery.prepare(QStringLiteral("DELETE FROM GroupSessionIndexRecord WHERE matrixId=:matrixId;")); diff --git a/lib/database.h b/lib/database.h index 8f8cd6cd..25af2833 100644 --- a/lib/database.h +++ b/lib/database.h @@ -27,9 +27,9 @@ public: QSqlQuery execute(const QString &queryString); QSqlQuery execute(QSqlQuery &query); - QByteArray accountPickle(const QString &id); - void setAccountPickle(const QString &id, const QByteArray &pickle); - void clear(const QString &id); + QByteArray accountPickle(const QString &matrixId); + void setAccountPickle(const QString &matrixId, const QByteArray &pickle); + void clear(const QString &matrixId); void saveOlmSession(const QString& matrixId, const QString& senderKey, const QString& sessionId, const QByteArray &pickle); UnorderedMap> loadOlmSessions(const QString& matrixId, const PicklingMode& picklingMode); UnorderedMap, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& matrixId, const QString& roomId, const PicklingMode& picklingMode); -- cgit v1.2.3 From 5cf182fd4fed95e1a16936f400e8ff6fcf991d7c Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 7 Dec 2021 19:18:35 +0100 Subject: Fixes --- lib/database.cpp | 2 +- lib/room.cpp | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/database.cpp b/lib/database.cpp index e115b6c3..6acfbc74 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -115,7 +115,7 @@ void Database::setAccountPickle(const QString &matrixId, const QByteArray &pickl void Database::clear(const QString &matrixId) { QSqlQuery query; - query.prepare(QStringLiteral("DELETE FROM Accounts(matrixId, pickle) WHERE matrixId=:matrixId;")); + query.prepare(QStringLiteral("DELETE FROM Accounts WHERE matrixId=:matrixId;")); query.bindValue(":matrixId", matrixId); QSqlQuery sessionsQuery; diff --git a/lib/room.cpp b/lib/room.cpp index 755f677a..15cbac28 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -401,9 +401,9 @@ public: const auto senderSessionPairKey = qMakePair(senderKey, sessionId); auto groupSessionIt = groupSessions.find(senderSessionPairKey); if (groupSessionIt == groupSessions.end()) { - qCWarning(E2EE) << "Unable to decrypt event" << eventId - << "The sender's device has not sent us the keys for " - "this message"; + // qCWarning(E2EE) << "Unable to decrypt event" << eventId + // << "The sender's device has not sent us the keys for " + // "this message"; return QString(); } auto& senderSession = groupSessionIt->second; @@ -1483,7 +1483,7 @@ RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) encryptedEvent.sessionId(), encryptedEvent.id(), encryptedEvent.originTimestamp()); if (decrypted.isEmpty()) { - qCWarning(E2EE) << "Encrypted message is empty"; + // qCWarning(E2EE) << "Encrypted message is empty"; return {}; } return encryptedEvent.createDecrypted(decrypted); @@ -2749,7 +2749,6 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) for(long unsigned int i = 0; i < events.size(); i++) { if(auto* encrypted = eventCast(events[i])) { - qDebug() << "Encrypted Event"; auto decrypted = q->decryptMessage(*encrypted); if(decrypted) { events[i] = std::move(decrypted); -- cgit v1.2.3 From 1cf9b67e3586888e5f72a30b82bb9541c026d672 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 7 Dec 2021 22:05:12 +0100 Subject: snake_case table names --- lib/database.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/database.cpp b/lib/database.cpp index 6acfbc74..5372ad7e 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -80,11 +80,11 @@ void Database::migrateTo1() { qDebug() << "Migrating database to version 1"; transaction(); - execute(QStringLiteral("CREATE TABLE Accounts (matrixId TEXT UNIQUE, pickle TEXT);")); - execute(QStringLiteral("CREATE TABLE OlmSessions (matrixId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); - execute(QStringLiteral("CREATE TABLE InboundMegolmSessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE accounts (matrixId TEXT UNIQUE, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE olm_sessions (matrixId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE inbound_megolm_sessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); execute(QStringLiteral("CREATE TABLE OutboundMegolmSessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); - execute(QStringLiteral("CREATE TABLE GroupSessionIndexRecord (matrixId TEXT, roomId TEXT, sessionId TEXT, i INTEGER, eventId TEXT, ts INTEGER);")); + execute(QStringLiteral("CREATE TABLE group_session_record_index (matrixId TEXT, roomId TEXT, sessionId TEXT, i INTEGER, eventId TEXT, ts INTEGER);")); execute(QStringLiteral("PRAGMA user_version = 1;")); commit(); } @@ -92,7 +92,7 @@ void Database::migrateTo1() QByteArray Database::accountPickle(const QString &matrixId) { QSqlQuery query; - query.prepare(QStringLiteral("SELECT pickle FROM Accounts WHERE matrixId=:matrixId;")); + query.prepare(QStringLiteral("SELECT pickle FROM accounts WHERE matrixId=:matrixId;")); query.bindValue(":matrixId", matrixId); execute(query); if (query.next()) { @@ -104,7 +104,7 @@ QByteArray Database::accountPickle(const QString &matrixId) void Database::setAccountPickle(const QString &matrixId, const QByteArray &pickle) { QSqlQuery query; - query.prepare(QStringLiteral("INSERT INTO Accounts(matrixId, pickle) VALUES(:matrixId, :pickle) ON CONFLICT (matrixId) DO UPDATE SET pickle=:pickle WHERE matrixId=:matrixId;")); + query.prepare(QStringLiteral("INSERT INTO accounts(matrixId, pickle) VALUES(:matrixId, :pickle) ON CONFLICT (matrixId) DO UPDATE SET pickle=:pickle WHERE matrixId=:matrixId;")); query.bindValue(":matrixId", matrixId); query.bindValue(":pickle", pickle); transaction(); @@ -115,19 +115,19 @@ void Database::setAccountPickle(const QString &matrixId, const QByteArray &pickl void Database::clear(const QString &matrixId) { QSqlQuery query; - query.prepare(QStringLiteral("DELETE FROM Accounts WHERE matrixId=:matrixId;")); + query.prepare(QStringLiteral("DELETE FROM accounts WHERE matrixId=:matrixId;")); query.bindValue(":matrixId", matrixId); QSqlQuery sessionsQuery; - sessionsQuery.prepare(QStringLiteral("DELETE FROM OlmSessions WHERE matrixId=:matrixId;")); + sessionsQuery.prepare(QStringLiteral("DELETE FROM olm_sessions WHERE matrixId=:matrixId;")); sessionsQuery.bindValue(":matrixId", matrixId); QSqlQuery megolmSessionsQuery; - megolmSessionsQuery.prepare(QStringLiteral("DELETE FROM InboundMegolmSessions WHERE matrixId=:matrixId;")); + megolmSessionsQuery.prepare(QStringLiteral("DELETE FROM inbound_megolm_sessions WHERE matrixId=:matrixId;")); megolmSessionsQuery.bindValue(":matrixId", matrixId); QSqlQuery groupSessionIndexRecordQuery; - groupSessionIndexRecordQuery.prepare(QStringLiteral("DELETE FROM GroupSessionIndexRecord WHERE matrixId=:matrixId;")); + groupSessionIndexRecordQuery.prepare(QStringLiteral("DELETE FROM group_session_record_index WHERE matrixId=:matrixId;")); groupSessionIndexRecordQuery.bindValue(":matrixId", matrixId); transaction(); @@ -142,7 +142,7 @@ void Database::clear(const QString &matrixId) void Database::saveOlmSession(const QString& matrixId, const QString& senderKey, const QString& sessionId, const QByteArray &pickle) { QSqlQuery query; - query.prepare(QStringLiteral("INSERT INTO OlmSessions(matrixId, senderKey, sessionId, pickle) VALUES(:matrixId, :senderKey, :sessionId, :pickle);")); + query.prepare(QStringLiteral("INSERT INTO olm_sessions(matrixId, senderKey, sessionId, pickle) VALUES(:matrixId, :senderKey, :sessionId, :pickle);")); query.bindValue(":matrixId", matrixId); query.bindValue(":senderKey", senderKey); query.bindValue(":sessionId", sessionId); @@ -155,7 +155,7 @@ void Database::saveOlmSession(const QString& matrixId, const QString& senderKey, UnorderedMap> Database::loadOlmSessions(const QString& matrixId, const PicklingMode& picklingMode) { QSqlQuery query; - query.prepare(QStringLiteral("SELECT * FROM OlmSessions WHERE matrixId=:matrixId;")); + query.prepare(QStringLiteral("SELECT * FROM olm_sessions WHERE matrixId=:matrixId;")); query.bindValue(":matrixId", matrixId); transaction(); execute(query); @@ -175,7 +175,7 @@ UnorderedMap> Database::loadOlmSessions(con UnorderedMap, QOlmInboundGroupSessionPtr> Database::loadMegolmSessions(const QString& matrixId, const QString& roomId, const PicklingMode& picklingMode) { QSqlQuery query; - query.prepare(QStringLiteral("SELECT * FROM InboundMegolmSessions WHERE matrixId=:matrixId AND roomId=:roomId;")); + query.prepare(QStringLiteral("SELECT * FROM inbound_megolm_sessions WHERE matrixId=:matrixId AND roomId=:roomId;")); query.bindValue(":matrixId", matrixId); query.bindValue(":roomId", roomId); transaction(); @@ -196,7 +196,7 @@ UnorderedMap, QOlmInboundGroupSessionPtr> Database::load void Database::saveMegolmSession(const QString& matrixId, const QString& roomId, const QString& senderKey, const QString& sessionId, const QByteArray& pickle) { QSqlQuery query; - query.prepare(QStringLiteral("INSERT INTO InboundMegolmSessions(matrixId, roomId, senderKey, sessionId, pickle) VALUES(:matrixId, :roomId, :senderKey, :sessionId, :pickle);")); + query.prepare(QStringLiteral("INSERT INTO inbound_megolm_sessions(matrixId, roomId, senderKey, sessionId, pickle) VALUES(:matrixId, :roomId, :senderKey, :sessionId, :pickle);")); query.bindValue(":matrixId", matrixId); query.bindValue(":roomId", roomId); query.bindValue(":senderKey", senderKey); @@ -210,7 +210,7 @@ void Database::saveMegolmSession(const QString& matrixId, const QString& roomId, void Database::addGroupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts) { QSqlQuery query; - query.prepare("INSERT INTO GroupSessionIndexRecord(matrixId, roomId, sessionId, i, eventId, ts) VALUES(:matrixId, :roomId, :sessionId, :index, :eventId, :ts);"); + query.prepare("INSERT INTO group_session_record_index(matrixId, roomId, sessionId, i, eventId, ts) VALUES(:matrixId, :roomId, :sessionId, :index, :eventId, :ts);"); query.bindValue(":matrixId", matrixId); query.bindValue(":roomId", roomId); query.bindValue(":sessionId", sessionId); @@ -225,7 +225,7 @@ void Database::addGroupSessionIndexRecord(const QString& matrixId, const QString QPair Database::groupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, qint64 index) { QSqlQuery query; - query.prepare(QStringLiteral("SELECT * FROM GroupSessionIndexRecord WHERE matrixId=:matrixId AND roomId=:roomId AND sessionId=:sessionId AND i=:index;")); + query.prepare(QStringLiteral("SELECT * FROM group_session_record_index WHERE matrixId=:matrixId AND roomId=:roomId AND sessionId=:sessionId AND i=:index;")); query.bindValue(":matrixId", matrixId); query.bindValue(":roomId", roomId); query.bindValue(":sessionId", sessionId); -- cgit v1.2.3 From 18695131e056b86e38d7b43f787014fbd1516240 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 7 Dec 2021 22:10:42 +0100 Subject: Remove default constructor --- lib/encryptionmanager.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 3d616965..c0e44f70 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -31,11 +31,6 @@ using std::move; class EncryptionManager::Private { public: - explicit Private() - { - } - ~Private() = default; - EncryptionManager* q; // A map from SenderKey to vector of InboundSession -- cgit v1.2.3 From 6cf625779fe270fd4192639e30acc45687270246 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 7 Dec 2021 23:07:57 +0100 Subject: Add clangd files to .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 769bdf45..d414f49f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ Makefile Quotient_autogen/ .cmake/ tests/.cmake/ + +# clangd +.cache/ +compile_commands.json -- cgit v1.2.3 From 9fb07da8451f024085061e2985e9be384e7beb5c Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 7 Dec 2021 23:12:04 +0100 Subject: Maintain list of undecrypted events to speed up decryption of old messages --- lib/room.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 15cbac28..8e348089 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -138,6 +138,8 @@ public: QString prevBatch; QPointer eventsHistoryJob; QPointer allMembersJob; + // Map from megolm sessionId to set of eventIds + UnorderedMap> undecryptedEvents; struct FileTransferPrivateInfo { FileTransferPrivateInfo() = default; @@ -1506,12 +1508,17 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, roomKeyEvent.sessionKey())) { qCWarning(E2EE) << "added new inboundGroupSession:" << d->groupSessions.size(); - for (unsigned long int i = 0; i < d->timeline.size(); i++) { - if (auto encryptedEvent = d->timeline[i].viewAs()) { + for (const auto& eventId : d->undecryptedEvents[roomKeyEvent.sessionId()]) { + if (!d->eventsIndex.contains(eventId)) { + continue; + } + auto event = d->timeline.rend() - (d->eventsIndex.value(eventId) - minTimelineIndex() + 1); + if (auto encryptedEvent = event->viewAs()) { auto decrypted = decryptMessage(*encryptedEvent); if(decrypted) { - auto oldEvent = d->timeline[i].replaceEvent(std::move(decrypted)); - emit replacedEvent(d->timeline[i].event(), rawPtr(oldEvent)); + auto oldEvent = event->replaceEvent(std::move(decrypted)); + emit replacedEvent(event->event(), rawPtr(oldEvent)); + d->undecryptedEvents[roomKeyEvent.sessionId()] -= eventId; } } } @@ -2590,6 +2597,8 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) auto decrypted = q->decryptMessage(*encrypted); if(decrypted) { events[i] = std::move(decrypted); + } else { + undecryptedEvents[encrypted->sessionId()] += encrypted->id(); } } } @@ -2752,6 +2761,8 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) auto decrypted = q->decryptMessage(*encrypted); if(decrypted) { events[i] = std::move(decrypted); + } else { + undecryptedEvents[encrypted->sessionId()] += encrypted->id(); } } } -- cgit v1.2.3 From a0ce17dfe793c924205b449c026f2f776b032ff3 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 8 Dec 2021 23:07:14 +0100 Subject: Store encryptedevent in decrypted roomevents --- lib/events/roomevent.cpp | 13 +++++++++++++ lib/events/roomevent.h | 4 ++++ lib/room.cpp | 7 +++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index fb921af6..b99d1381 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -126,3 +126,16 @@ CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) if (callId().isEmpty()) qCWarning(EVENTS) << id() << "is a call event with an empty call id"; } + +void RoomEvent::setOriginalEvent(event_ptr_tt originalEvent) +{ + _originalEvent = std::move(originalEvent); +} + +const QJsonObject RoomEvent::encryptedJson() const +{ + if(!_originalEvent) { + return {}; + } + return _originalEvent->fullJson(); +} diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 7f13f6f2..35527a62 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -60,11 +60,15 @@ public: //! callback for that in RoomEvent. void addId(const QString& newId); + void setOriginalEvent(event_ptr_tt originalEvent); + const QJsonObject encryptedJson() const; + protected: void dumpTo(QDebug dbg) const override; private: event_ptr_tt _redactedBecause; + event_ptr_tt _originalEvent; }; using RoomEventPtr = event_ptr_tt; using RoomEvents = EventsArray; diff --git a/lib/room.cpp b/lib/room.cpp index 8e348089..b3a092f3 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1517,6 +1517,7 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, auto decrypted = decryptMessage(*encryptedEvent); if(decrypted) { auto oldEvent = event->replaceEvent(std::move(decrypted)); + decrypted->setOriginalEvent(std::move(oldEvent)); emit replacedEvent(event->event(), rawPtr(oldEvent)); d->undecryptedEvents[roomKeyEvent.sessionId()] -= eventId; } @@ -2596,7 +2597,8 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) if(auto* encrypted = eventCast(events[i])) { auto decrypted = q->decryptMessage(*encrypted); if(decrypted) { - events[i] = std::move(decrypted); + auto oldEvent = std::exchange(events[i], std::move(decrypted)); + events[i]->setOriginalEvent(std::move(oldEvent)); } else { undecryptedEvents[encrypted->sessionId()] += encrypted->id(); } @@ -2760,7 +2762,8 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) if(auto* encrypted = eventCast(events[i])) { auto decrypted = q->decryptMessage(*encrypted); if(decrypted) { - events[i] = std::move(decrypted); + auto oldEvent = std::exchange(events[i], std::move(decrypted)); + events[i]->setOriginalEvent(std::move(oldEvent)); } else { undecryptedEvents[encrypted->sessionId()] += encrypted->id(); } -- cgit v1.2.3 From 3054255cba206c91e3bdf0ea42fde39d51261e6a Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 8 Dec 2021 23:14:29 +0100 Subject: Update logging categories --- lib/database.cpp | 4 ++-- lib/logging.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/database.cpp b/lib/database.cpp index 5372ad7e..9bdcd9e6 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -36,7 +36,7 @@ int Database::version() if (query.next()) { bool ok; int value = query.value(0).toInt(&ok); - qDebug() << "Database version" << value; + qCDebug(DATABASE) << "Database version" << value; if (ok) return value; } else { @@ -78,7 +78,7 @@ void Database::commit() void Database::migrateTo1() { - qDebug() << "Migrating database to version 1"; + qCDebug(DATABASE) << "Migrating database to version 1"; transaction(); execute(QStringLiteral("CREATE TABLE accounts (matrixId TEXT UNIQUE, pickle TEXT);")); execute(QStringLiteral("CREATE TABLE olm_sessions (matrixId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); diff --git a/lib/logging.cpp b/lib/logging.cpp index 15eac69d..460caced 100644 --- a/lib/logging.cpp +++ b/lib/logging.cpp @@ -19,3 +19,4 @@ LOGGING_CATEGORY(SYNCJOB, "quotient.jobs.sync") LOGGING_CATEGORY(THUMBNAILJOB, "quotient.jobs.thumbnail") LOGGING_CATEGORY(NETWORK, "quotient.network") LOGGING_CATEGORY(PROFILER, "quotient.profiler") +LOGGING_CATEGORY(DATABASE, "quotient.database") -- cgit v1.2.3 From 39ca00755d91c5e608bfe2a67c84ceb29ed49976 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 8 Dec 2021 23:18:27 +0100 Subject: Try fixing sonar --- .github/workflows/sonar.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 76db59c9..c987b0cc 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -64,6 +64,14 @@ jobs: 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: | -- cgit v1.2.3 From 1f6771c3b14453ae9b6651a9edb1f7778d3f71f3 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Thu, 9 Dec 2021 23:59:38 +0100 Subject: Update lib/connection.cpp Co-authored-by: Alexey Rusakov --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index c7591e43..b7aaca86 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2041,7 +2041,7 @@ PicklingMode Connection::picklingMode() const void Connection::saveOlmAccount() { - qDebug() << "Saving olm account"; + qCDebug(E2EE) << "Saving olm account"; #ifdef Quotient_E2EE_ENABLED auto pickle = d->olmAccount->pickle(d->picklingMode); Database::instance().setAccountPickle(userId(), std::get(pickle)); -- cgit v1.2.3 From 58798ce15f0f235d64f9c34b3f8c013678ebf25f Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 9 Dec 2021 23:26:24 +0100 Subject: Ifdef all the things --- CMakeLists.txt | 13 ++++++++----- lib/connection.cpp | 12 ++++-------- lib/events/roomevent.cpp | 2 ++ lib/events/roomevent.h | 5 +++++ lib/room.cpp | 6 +++++- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a84a70fb..43fed3e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,11 +83,12 @@ else() set(QtExtraModules "Multimedia") # See #483 endif() string(REGEX REPLACE "^(.).*" "Qt\\1" Qt ${QtMinVersion}) # makes "Qt5" or "Qt6" -find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test Sql ${QtExtraModules}) +find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModules}) get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) 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) set_package_properties(Olm PROPERTIES DESCRIPTION "Implementation of the Olm and Megolm cryptographic ratchets" @@ -107,9 +108,9 @@ if (${PROJECT_NAME}_ENABLE_E2EE) if (OpenSSL_FOUND) message(STATUS "Using OpenSSL ${OpenSSL_VERSION} at ${OpenSSL_DIR}") endif() + find_package(${Qt}Keychain REQUIRED) endif() -find_package(${Qt}Keychain REQUIRED) # Set up source files list(APPEND lib_SRCS @@ -133,7 +134,6 @@ list(APPEND lib_SRCS lib/eventitem.cpp lib/accountregistry.cpp lib/mxcreply.cpp - lib/database.cpp lib/events/event.cpp lib/events/roomevent.cpp lib/events/stateevent.cpp @@ -164,6 +164,7 @@ list(APPEND lib_SRCS ) if (${PROJECT_NAME}_ENABLE_E2EE) list(APPEND lib_SRCS + lib/database.cpp lib/e2ee/qolmaccount.cpp lib/e2ee/qolmsession.cpp lib/e2ee/qolminboundsession.cpp @@ -323,12 +324,14 @@ target_include_directories(${PROJECT_NAME} PUBLIC if (${PROJECT_NAME}_ENABLE_E2EE) target_link_libraries(${PROJECT_NAME} Olm::Olm OpenSSL::Crypto - OpenSSL::SSL) + OpenSSL::SSL + ${Qt}::Sql + ${QTKEYCHAIN_LIBRARIES}) set(FIND_DEPS "find_dependency(Olm) find_dependency(OpenSSL)") # For QuotientConfig.cmake.in endif() -target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui ${Qt}::Sql ${QTKEYCHAIN_LIBRARIES}) +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/lib/connection.cpp b/lib/connection.cpp index b7aaca86..433dd942 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -7,9 +7,6 @@ #include "connection.h" #include "connectiondata.h" -#ifdef Quotient_E2EE_ENABLED -# include "encryptionmanager.h" -#endif // Quotient_E2EE_ENABLED #include "room.h" #include "settings.h" #include "user.h" @@ -40,6 +37,8 @@ #ifdef Quotient_E2EE_ENABLED # include "e2ee/qolmaccount.h" # include "e2ee/qolmutils.h" +# include "encryptionmanager.h" +# include "database.h" #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -62,7 +61,6 @@ # include #endif -#include "database.h" using namespace Quotient; @@ -274,9 +272,9 @@ Connection::Connection(const QUrl& server, QObject* parent) connect(qApp, &QCoreApplication::aboutToQuit, this, [this](){ saveOlmAccount(); }); + Database::instance(); #endif d->q = this; // All d initialization should occur before this line - Database::instance(); } Connection::Connection(QObject* parent) : Connection({}, parent) {} @@ -442,15 +440,13 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) auto loginJob = q->callApi(std::forward(loginArgs)...); connect(loginJob, &BaseJob::success, q, [this, loginJob] { - Database::instance().clear(loginJob->userId()); data->setToken(loginJob->accessToken().toLatin1()); data->setDeviceId(loginJob->deviceId()); completeSetup(loginJob->userId()); #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); + Database::instance().clear(loginJob->userId()); #endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index b99d1381..dbce2255 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -127,6 +127,7 @@ CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) qCWarning(EVENTS) << id() << "is a call event with an empty call id"; } +#ifdef Quotient_E2EE_ENABLED void RoomEvent::setOriginalEvent(event_ptr_tt originalEvent) { _originalEvent = std::move(originalEvent); @@ -139,3 +140,4 @@ const QJsonObject RoomEvent::encryptedJson() const } return _originalEvent->fullJson(); } +#endif diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 35527a62..36b45f09 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -60,15 +60,20 @@ public: //! callback for that in RoomEvent. void addId(const QString& newId); +#ifdef Quotient_E2EE_ENABLED void setOriginalEvent(event_ptr_tt originalEvent); const QJsonObject encryptedJson() const; +#endif protected: void dumpTo(QDebug dbg) const override; private: event_ptr_tt _redactedBecause; + +#ifdef Quotient_E2EE_ENABLED event_ptr_tt _originalEvent; +#endif }; using RoomEventPtr = event_ptr_tt; using RoomEvents = EventsArray; diff --git a/lib/room.cpp b/lib/room.cpp index b3a092f3..7d608520 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -68,9 +68,9 @@ #include "e2ee/qolmaccount.h" #include "e2ee/qolmerrors.h" #include "e2ee/qolminboundsession.h" +#include "database.h" #endif // Quotient_E2EE_ENABLED -#include "database.h" using namespace Quotient; using namespace std::placeholders; @@ -2593,6 +2593,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) QElapsedTimer et; et.start(); +#ifdef Quotient_E2EE_ENABLED for(long unsigned int i = 0; i < events.size(); i++) { if(auto* encrypted = eventCast(events[i])) { auto decrypted = q->decryptMessage(*encrypted); @@ -2604,6 +2605,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) } } } +#endif { // Pre-process redactions and edits so that events that get @@ -2758,6 +2760,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) Changes changes {}; +#ifdef Quotient_E2EE_ENABLED for(long unsigned int i = 0; i < events.size(); i++) { if(auto* encrypted = eventCast(events[i])) { auto decrypted = q->decryptMessage(*encrypted); @@ -2769,6 +2772,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) } } } +#endif // In case of lazy-loading new members may be loaded with historical // messages. Also, the cache doesn't store events with empty content; -- cgit v1.2.3 From b3be614b71b12e729d1bf3d6ca7d7068a0786fc8 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 9 Dec 2021 23:56:58 +0100 Subject: Rename database --- lib/database.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/database.cpp b/lib/database.cpp index 9bdcd9e6..01015d3c 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -19,11 +19,11 @@ using namespace Quotient; Database::Database() { - QSqlDatabase::addDatabase(QStringLiteral("QSQLITE")); + QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("Quotient")); QString databasePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QDir(databasePath).mkpath(databasePath); - QSqlDatabase::database().setDatabaseName(databasePath + QStringLiteral("/database.db3")); - QSqlDatabase::database().open(); + QSqlDatabase::database(QStringLiteral("Quotient")).setDatabaseName(databasePath + QStringLiteral("/quotient.db3")); + QSqlDatabase::database(QStringLiteral("Quotient")).open(); switch(version()) { case 0: migrateTo1(); @@ -47,7 +47,7 @@ int Database::version() QSqlQuery Database::execute(const QString &queryString) { - auto query = QSqlDatabase::database().exec(queryString); + auto query = QSqlDatabase::database(QStringLiteral("Quotient")).exec(queryString); if (query.lastError().type() != QSqlError::NoError) { qCritical() << "Failed to execute query"; qCritical() << query.lastQuery(); @@ -68,12 +68,12 @@ QSqlQuery Database::execute(QSqlQuery &query) void Database::transaction() { - QSqlDatabase::database().transaction(); + QSqlDatabase::database(QStringLiteral("Quotient")).transaction(); } void Database::commit() { - QSqlDatabase::database().commit(); + QSqlDatabase::database(QStringLiteral("Quotient")).commit(); } void Database::migrateTo1() @@ -83,7 +83,7 @@ void Database::migrateTo1() execute(QStringLiteral("CREATE TABLE accounts (matrixId TEXT UNIQUE, pickle TEXT);")); execute(QStringLiteral("CREATE TABLE olm_sessions (matrixId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); execute(QStringLiteral("CREATE TABLE inbound_megolm_sessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); - execute(QStringLiteral("CREATE TABLE OutboundMegolmSessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE outbound_megolm_sessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); execute(QStringLiteral("CREATE TABLE group_session_record_index (matrixId TEXT, roomId TEXT, sessionId TEXT, i INTEGER, eventId TEXT, ts INTEGER);")); execute(QStringLiteral("PRAGMA user_version = 1;")); commit(); @@ -91,7 +91,7 @@ void Database::migrateTo1() QByteArray Database::accountPickle(const QString &matrixId) { - QSqlQuery query; + QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); query.prepare(QStringLiteral("SELECT pickle FROM accounts WHERE matrixId=:matrixId;")); query.bindValue(":matrixId", matrixId); execute(query); @@ -103,7 +103,7 @@ QByteArray Database::accountPickle(const QString &matrixId) void Database::setAccountPickle(const QString &matrixId, const QByteArray &pickle) { - QSqlQuery query; + QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); query.prepare(QStringLiteral("INSERT INTO accounts(matrixId, pickle) VALUES(:matrixId, :pickle) ON CONFLICT (matrixId) DO UPDATE SET pickle=:pickle WHERE matrixId=:matrixId;")); query.bindValue(":matrixId", matrixId); query.bindValue(":pickle", pickle); @@ -114,19 +114,19 @@ void Database::setAccountPickle(const QString &matrixId, const QByteArray &pickl void Database::clear(const QString &matrixId) { - QSqlQuery query; + QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); query.prepare(QStringLiteral("DELETE FROM accounts WHERE matrixId=:matrixId;")); query.bindValue(":matrixId", matrixId); - QSqlQuery sessionsQuery; + QSqlQuery sessionsQuery(QSqlDatabase::database(QStringLiteral("Quotient"))); sessionsQuery.prepare(QStringLiteral("DELETE FROM olm_sessions WHERE matrixId=:matrixId;")); sessionsQuery.bindValue(":matrixId", matrixId); - QSqlQuery megolmSessionsQuery; + QSqlQuery megolmSessionsQuery(QSqlDatabase::database(QStringLiteral("Quotient"))); megolmSessionsQuery.prepare(QStringLiteral("DELETE FROM inbound_megolm_sessions WHERE matrixId=:matrixId;")); megolmSessionsQuery.bindValue(":matrixId", matrixId); - QSqlQuery groupSessionIndexRecordQuery; + QSqlQuery groupSessionIndexRecordQuery(QSqlDatabase::database(QStringLiteral("Quotient"))); groupSessionIndexRecordQuery.prepare(QStringLiteral("DELETE FROM group_session_record_index WHERE matrixId=:matrixId;")); groupSessionIndexRecordQuery.bindValue(":matrixId", matrixId); @@ -141,7 +141,7 @@ void Database::clear(const QString &matrixId) void Database::saveOlmSession(const QString& matrixId, const QString& senderKey, const QString& sessionId, const QByteArray &pickle) { - QSqlQuery query; + QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); query.prepare(QStringLiteral("INSERT INTO olm_sessions(matrixId, senderKey, sessionId, pickle) VALUES(:matrixId, :senderKey, :sessionId, :pickle);")); query.bindValue(":matrixId", matrixId); query.bindValue(":senderKey", senderKey); @@ -154,7 +154,7 @@ void Database::saveOlmSession(const QString& matrixId, const QString& senderKey, UnorderedMap> Database::loadOlmSessions(const QString& matrixId, const PicklingMode& picklingMode) { - QSqlQuery query; + QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); query.prepare(QStringLiteral("SELECT * FROM olm_sessions WHERE matrixId=:matrixId;")); query.bindValue(":matrixId", matrixId); transaction(); @@ -174,7 +174,7 @@ UnorderedMap> Database::loadOlmSessions(con UnorderedMap, QOlmInboundGroupSessionPtr> Database::loadMegolmSessions(const QString& matrixId, const QString& roomId, const PicklingMode& picklingMode) { - QSqlQuery query; + QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); query.prepare(QStringLiteral("SELECT * FROM inbound_megolm_sessions WHERE matrixId=:matrixId AND roomId=:roomId;")); query.bindValue(":matrixId", matrixId); query.bindValue(":roomId", roomId); @@ -195,7 +195,7 @@ UnorderedMap, QOlmInboundGroupSessionPtr> Database::load void Database::saveMegolmSession(const QString& matrixId, const QString& roomId, const QString& senderKey, const QString& sessionId, const QByteArray& pickle) { - QSqlQuery query; + QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); query.prepare(QStringLiteral("INSERT INTO inbound_megolm_sessions(matrixId, roomId, senderKey, sessionId, pickle) VALUES(:matrixId, :roomId, :senderKey, :sessionId, :pickle);")); query.bindValue(":matrixId", matrixId); query.bindValue(":roomId", roomId); @@ -209,7 +209,7 @@ void Database::saveMegolmSession(const QString& matrixId, const QString& roomId, void Database::addGroupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts) { - QSqlQuery query; + QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); query.prepare("INSERT INTO group_session_record_index(matrixId, roomId, sessionId, i, eventId, ts) VALUES(:matrixId, :roomId, :sessionId, :index, :eventId, :ts);"); query.bindValue(":matrixId", matrixId); query.bindValue(":roomId", roomId); @@ -224,7 +224,7 @@ void Database::addGroupSessionIndexRecord(const QString& matrixId, const QString QPair Database::groupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, qint64 index) { - QSqlQuery query; + QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); query.prepare(QStringLiteral("SELECT * FROM group_session_record_index WHERE matrixId=:matrixId AND roomId=:roomId AND sessionId=:sessionId AND i=:index;")); query.bindValue(":matrixId", matrixId); query.bindValue(":roomId", roomId); -- cgit v1.2.3 From 7129118a7735a13af0db7d71efd60a330feac877 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 10 Dec 2021 00:12:39 +0100 Subject: ifdef more things --- lib/connection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 433dd942..d1a29a7d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -39,6 +39,12 @@ # include "e2ee/qolmutils.h" # include "encryptionmanager.h" # include "database.h" + +#if QT_VERSION_MAJOR >= 6 +# include +#else +# include +#endif #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -55,12 +61,6 @@ #include #include -#if QT_VERSION_MAJOR >= 6 -# include -#else -# include -#endif - using namespace Quotient; -- 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(-) 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 b4cc38fc7c2c63d8122106a2451aec2c60176a4b Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 10 Dec 2021 16:10:10 +0100 Subject: Use individual databases for each connection --- lib/connection.cpp | 21 ++++++--- lib/connection.h | 2 + lib/database.cpp | 113 +++++++++++++++++++++------------------------- lib/database.h | 32 ++++++------- lib/encryptionmanager.cpp | 9 ++-- lib/room.cpp | 8 ++-- 6 files changed, 93 insertions(+), 92 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index d1a29a7d..8b9f9688 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -116,6 +116,7 @@ public: QueryKeysJob *currentQueryKeysJob = nullptr; bool encryptionUpdateRequired = false; PicklingMode picklingMode = Unencrypted {}; + Database *database = nullptr; #endif GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -268,11 +269,9 @@ Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent), d(new Private(std::make_unique(server))) { #ifdef Quotient_E2EE_ENABLED - d->encryptionManager = new EncryptionManager(this); connect(qApp, &QCoreApplication::aboutToQuit, this, [this](){ saveOlmAccount(); }); - Database::instance(); #endif d->q = this; // All d initialization should occur before this line } @@ -446,7 +445,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 - Database::instance().clear(loginJob->userId()); + database = new Database(loginJob->userId(), q); + database->clear(); #endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { @@ -502,9 +502,13 @@ void Connection::Private::completeSetup(const QString& mxId) olmAccount = std::make_unique(data->userId(), data->deviceId(), q); connect(olmAccount.get(), &QOlmAccount::needsSave, q, &Connection::saveOlmAccount); + if (!database) { + database = new Database(data->userId(), q); + } + encryptionManager = new EncryptionManager(q); - if (Database::instance().accountPickle(data->userId()).isEmpty()) { + if (database->accountPickle().isEmpty()) { // create new account and save unpickle data olmAccount->createNewAccount(); auto job = q->callApi(olmAccount->deviceKeys()); @@ -513,7 +517,7 @@ void Connection::Private::completeSetup(const QString& mxId) }); } else { // account already existing - auto pickle = Database::instance().accountPickle(data->userId()); + auto pickle = database->accountPickle(); olmAccount->unpickle(pickle, picklingMode); } #endif // Quotient_E2EE_ENABLED @@ -2040,7 +2044,7 @@ void Connection::saveOlmAccount() qCDebug(E2EE) << "Saving olm account"; #ifdef Quotient_E2EE_ENABLED auto pickle = d->olmAccount->pickle(d->picklingMode); - Database::instance().setAccountPickle(userId(), std::get(pickle)); + d->database->setAccountPickle(std::get(pickle)); #endif } @@ -2067,4 +2071,9 @@ QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) } return decrypted->fullJson(); } + +Database* Connection::database() +{ + return d->database; +} #endif diff --git a/lib/connection.h b/lib/connection.h index 3a12ec39..93ee496e 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -47,6 +47,7 @@ class DownloadFileJob; class SendToDeviceJob; class SendMessageJob; class LeaveRoomJob; +class Database; class QOlmAccount; @@ -313,6 +314,7 @@ public: bool isLoggedIn() const; #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; + Database* database(); #endif // Quotient_E2EE_ENABLED Q_INVOKABLE Quotient::SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; diff --git a/lib/database.cpp b/lib/database.cpp index 01015d3c..41e62935 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -17,13 +17,16 @@ //TODO: delete room specific data when leaving room using namespace Quotient; -Database::Database() +Database::Database(const QString& matrixId, QObject* parent) + : QObject(parent) + , m_matrixId(matrixId) { - QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("Quotient")); - QString databasePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + m_matrixId.replace(':', '_'); + 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); - QSqlDatabase::database(QStringLiteral("Quotient")).setDatabaseName(databasePath + QStringLiteral("/quotient.db3")); - QSqlDatabase::database(QStringLiteral("Quotient")).open(); + database().setDatabaseName(databasePath + QStringLiteral("/quotient.db3")); + database().open(); switch(version()) { case 0: migrateTo1(); @@ -47,7 +50,7 @@ int Database::version() QSqlQuery Database::execute(const QString &queryString) { - auto query = QSqlDatabase::database(QStringLiteral("Quotient")).exec(queryString); + auto query = database().exec(queryString); if (query.lastError().type() != QSqlError::NoError) { qCritical() << "Failed to execute query"; qCritical() << query.lastQuery(); @@ -68,32 +71,30 @@ QSqlQuery Database::execute(QSqlQuery &query) void Database::transaction() { - QSqlDatabase::database(QStringLiteral("Quotient")).transaction(); + database().transaction(); } void Database::commit() { - QSqlDatabase::database(QStringLiteral("Quotient")).commit(); + database().commit(); } void Database::migrateTo1() { qCDebug(DATABASE) << "Migrating database to version 1"; transaction(); - execute(QStringLiteral("CREATE TABLE accounts (matrixId TEXT UNIQUE, pickle TEXT);")); - execute(QStringLiteral("CREATE TABLE olm_sessions (matrixId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); - execute(QStringLiteral("CREATE TABLE inbound_megolm_sessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); - execute(QStringLiteral("CREATE TABLE outbound_megolm_sessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); - execute(QStringLiteral("CREATE TABLE group_session_record_index (matrixId TEXT, roomId TEXT, sessionId TEXT, i INTEGER, eventId TEXT, ts INTEGER);")); + execute(QStringLiteral("CREATE TABLE accounts (pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE olm_sessions (senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE inbound_megolm_sessions (roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE outbound_megolm_sessions (roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE group_session_record_index (roomId TEXT, sessionId TEXT, i INTEGER, eventId TEXT, ts INTEGER);")); execute(QStringLiteral("PRAGMA user_version = 1;")); commit(); } -QByteArray Database::accountPickle(const QString &matrixId) +QByteArray Database::accountPickle() { - QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); - query.prepare(QStringLiteral("SELECT pickle FROM accounts WHERE matrixId=:matrixId;")); - query.bindValue(":matrixId", matrixId); + auto query = prepareQuery(QStringLiteral("SELECT pickle FROM accounts;")); execute(query); if (query.next()) { return query.value(QStringLiteral("pickle")).toByteArray(); @@ -101,34 +102,23 @@ QByteArray Database::accountPickle(const QString &matrixId) return {}; } -void Database::setAccountPickle(const QString &matrixId, const QByteArray &pickle) +void Database::setAccountPickle(const QByteArray &pickle) { - QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); - query.prepare(QStringLiteral("INSERT INTO accounts(matrixId, pickle) VALUES(:matrixId, :pickle) ON CONFLICT (matrixId) DO UPDATE SET pickle=:pickle WHERE matrixId=:matrixId;")); - query.bindValue(":matrixId", matrixId); + auto deleteQuery = prepareQuery(QStringLiteral("DELETE FROM accounts;")); + auto query = prepareQuery(QStringLiteral("INSERT INTO accounts(pickle) VALUES(:pickle);")); query.bindValue(":pickle", pickle); transaction(); + execute(deleteQuery); execute(query); commit(); } -void Database::clear(const QString &matrixId) +void Database::clear() { - QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); - query.prepare(QStringLiteral("DELETE FROM accounts WHERE matrixId=:matrixId;")); - query.bindValue(":matrixId", matrixId); - - QSqlQuery sessionsQuery(QSqlDatabase::database(QStringLiteral("Quotient"))); - sessionsQuery.prepare(QStringLiteral("DELETE FROM olm_sessions WHERE matrixId=:matrixId;")); - sessionsQuery.bindValue(":matrixId", matrixId); - - QSqlQuery megolmSessionsQuery(QSqlDatabase::database(QStringLiteral("Quotient"))); - megolmSessionsQuery.prepare(QStringLiteral("DELETE FROM inbound_megolm_sessions WHERE matrixId=:matrixId;")); - megolmSessionsQuery.bindValue(":matrixId", matrixId); - - QSqlQuery groupSessionIndexRecordQuery(QSqlDatabase::database(QStringLiteral("Quotient"))); - groupSessionIndexRecordQuery.prepare(QStringLiteral("DELETE FROM group_session_record_index WHERE matrixId=:matrixId;")); - groupSessionIndexRecordQuery.bindValue(":matrixId", matrixId); + auto query = prepareQuery(QStringLiteral("DELETE FROM accounts;")); + auto sessionsQuery = prepareQuery(QStringLiteral("DELETE FROM olm_sessions;")); + auto megolmSessionsQuery = prepareQuery(QStringLiteral("DELETE FROM inbound_megolm_sessions;")); + auto groupSessionIndexRecordQuery = prepareQuery(QStringLiteral("DELETE FROM group_session_record_index;")); transaction(); execute(query); @@ -139,11 +129,9 @@ void Database::clear(const QString &matrixId) } -void Database::saveOlmSession(const QString& matrixId, const QString& senderKey, const QString& sessionId, const QByteArray &pickle) +void Database::saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray &pickle) { - QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); - query.prepare(QStringLiteral("INSERT INTO olm_sessions(matrixId, senderKey, sessionId, pickle) VALUES(:matrixId, :senderKey, :sessionId, :pickle);")); - query.bindValue(":matrixId", matrixId); + auto query = prepareQuery(QStringLiteral("INSERT INTO olm_sessions(senderKey, sessionId, pickle) VALUES(:senderKey, :sessionId, :pickle);")); query.bindValue(":senderKey", senderKey); query.bindValue(":sessionId", sessionId); query.bindValue(":pickle", pickle); @@ -152,11 +140,9 @@ void Database::saveOlmSession(const QString& matrixId, const QString& senderKey, commit(); } -UnorderedMap> Database::loadOlmSessions(const QString& matrixId, const PicklingMode& picklingMode) +UnorderedMap> Database::loadOlmSessions(const PicklingMode& picklingMode) { - QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); - query.prepare(QStringLiteral("SELECT * FROM olm_sessions WHERE matrixId=:matrixId;")); - query.bindValue(":matrixId", matrixId); + QSqlQuery query = prepareQuery(QStringLiteral("SELECT * FROM olm_sessions;")); transaction(); execute(query); commit(); @@ -172,11 +158,9 @@ UnorderedMap> Database::loadOlmSessions(con return sessions; } -UnorderedMap, QOlmInboundGroupSessionPtr> Database::loadMegolmSessions(const QString& matrixId, const QString& roomId, const PicklingMode& picklingMode) +UnorderedMap, QOlmInboundGroupSessionPtr> Database::loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode) { - QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); - query.prepare(QStringLiteral("SELECT * FROM inbound_megolm_sessions WHERE matrixId=:matrixId AND roomId=:roomId;")); - query.bindValue(":matrixId", matrixId); + auto query = prepareQuery(QStringLiteral("SELECT * FROM inbound_megolm_sessions WHERE roomId=:roomId;")); query.bindValue(":roomId", roomId); transaction(); execute(query); @@ -193,11 +177,9 @@ UnorderedMap, QOlmInboundGroupSessionPtr> Database::load return sessions; } -void Database::saveMegolmSession(const QString& matrixId, const QString& roomId, const QString& senderKey, const QString& sessionId, const QByteArray& pickle) +void Database::saveMegolmSession(const QString& roomId, const QString& senderKey, const QString& sessionId, const QByteArray& pickle) { - QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); - query.prepare(QStringLiteral("INSERT INTO inbound_megolm_sessions(matrixId, roomId, senderKey, sessionId, pickle) VALUES(:matrixId, :roomId, :senderKey, :sessionId, :pickle);")); - query.bindValue(":matrixId", matrixId); + auto query = prepareQuery(QStringLiteral("INSERT INTO inbound_megolm_sessions(roomId, senderKey, sessionId, pickle) VALUES(:roomId, :senderKey, :sessionId, :pickle);")); query.bindValue(":roomId", roomId); query.bindValue(":senderKey", senderKey); query.bindValue(":sessionId", sessionId); @@ -207,11 +189,9 @@ void Database::saveMegolmSession(const QString& matrixId, const QString& roomId, commit(); } -void Database::addGroupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts) +void Database::addGroupSessionIndexRecord(const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts) { - QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); - query.prepare("INSERT INTO group_session_record_index(matrixId, roomId, sessionId, i, eventId, ts) VALUES(:matrixId, :roomId, :sessionId, :index, :eventId, :ts);"); - query.bindValue(":matrixId", matrixId); + QSqlQuery query = prepareQuery("INSERT INTO group_session_record_index(roomId, sessionId, i, eventId, ts) VALUES(:roomId, :sessionId, :index, :eventId, :ts);"); query.bindValue(":roomId", roomId); query.bindValue(":sessionId", sessionId); query.bindValue(":index", index); @@ -222,11 +202,10 @@ void Database::addGroupSessionIndexRecord(const QString& matrixId, const QString commit(); } -QPair Database::groupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, qint64 index) +QPair Database::groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index) { - QSqlQuery query(QSqlDatabase::database(QStringLiteral("Quotient"))); - query.prepare(QStringLiteral("SELECT * FROM group_session_record_index WHERE matrixId=:matrixId AND roomId=:roomId AND sessionId=:sessionId AND i=:index;")); - query.bindValue(":matrixId", matrixId); + QSqlQuery query(database()); + query.prepare(QStringLiteral("SELECT * FROM group_session_record_index WHERE roomId=:roomId AND sessionId=:sessionId AND i=:index;")); query.bindValue(":roomId", roomId); query.bindValue(":sessionId", sessionId); query.bindValue(":index", index); @@ -238,3 +217,15 @@ QPair Database::groupSessionIndexRecord(const QString& matrixId } return {query.value("eventId").toString(), query.value("ts").toLongLong()}; } + +QSqlDatabase Database::database() +{ + return QSqlDatabase::database(QStringLiteral("Quotient_%1").arg(m_matrixId)); +} + +QSqlQuery Database::prepareQuery(const QString& queryString) +{ + QSqlQuery query(database()); + query.prepare(queryString); + return query; +} diff --git a/lib/database.h b/lib/database.h index 25af2833..fbb940c8 100644 --- a/lib/database.h +++ b/lib/database.h @@ -15,32 +15,28 @@ class Database : public QObject Q_OBJECT public: - static Database &instance() - { - static Database _instance; - return _instance; - } + Database(const QString& matrixId, QObject* parent); int version(); void transaction(); void commit(); QSqlQuery execute(const QString &queryString); QSqlQuery execute(QSqlQuery &query); - - QByteArray accountPickle(const QString &matrixId); - void setAccountPickle(const QString &matrixId, const QByteArray &pickle); - void clear(const QString &matrixId); - void saveOlmSession(const QString& matrixId, const QString& senderKey, const QString& sessionId, const QByteArray &pickle); - UnorderedMap> loadOlmSessions(const QString& matrixId, const PicklingMode& picklingMode); - UnorderedMap, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& matrixId, const QString& roomId, const PicklingMode& picklingMode); - void saveMegolmSession(const QString& matrixId, const QString& roomId, const QString& senderKey, const QString& sessionKey, const QByteArray& pickle); - void addGroupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts); - QPair groupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, qint64 index); - + QSqlDatabase database(); + QSqlQuery prepareQuery(const QString& quaryString); + + QByteArray accountPickle(); + void setAccountPickle(const QByteArray &pickle); + void clear(); + void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray &pickle); + UnorderedMap> loadOlmSessions(const PicklingMode& picklingMode); + UnorderedMap, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode); + void saveMegolmSession(const QString& roomId, const QString& senderKey, const QString& sessionKey, const QByteArray& pickle); + void addGroupSessionIndexRecord(const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts); + QPair groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index); private: - Database(); - void migrateTo1(); + QString m_matrixId; }; } diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index c0e44f70..abdcdcee 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -33,19 +33,21 @@ class EncryptionManager::Private { public: EncryptionManager* q; + Connection* connection; + // A map from SenderKey to vector of InboundSession UnorderedMap> sessions; void loadSessions() { - sessions = Database::instance().loadOlmSessions(static_cast(q->parent())->userId(), static_cast(q->parent())->picklingMode()); + sessions = connection->database()->loadOlmSessions(connection->picklingMode()); } void saveSession(QOlmSessionPtr& session, const QString &senderKey) { - auto pickleResult = session->pickle(static_cast(q->parent())->picklingMode()); + auto pickleResult = session->pickle(connection->picklingMode()); if (std::holds_alternative(pickleResult)) { qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); return; } - Database::instance().saveOlmSession(static_cast(q->parent())->userId(), senderKey, session->sessionId(), std::get(pickleResult)); + connection->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult)); } QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) { @@ -103,6 +105,7 @@ EncryptionManager::EncryptionManager(QObject* parent) , d(std::make_unique()) { d->q = this; + d->connection = static_cast(parent); d->loadSessions(); } diff --git a/lib/room.cpp b/lib/room.cpp index 7d608520..458f870d 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -371,7 +371,7 @@ public: UnorderedMap, QOlmInboundGroupSessionPtr> groupSessions; void loadMegOlmSessions() { - groupSessions = Database::instance().loadMegolmSessions(q->localUser()->id(), q->id(), q->connection()->picklingMode()); + groupSessions = q->connection()->database()->loadMegolmSessions(q->id(), q->connection()->picklingMode()); } bool addInboundGroupSession(QString senderKey, QString sessionId, QString sessionKey) @@ -389,7 +389,7 @@ public: return false; } qCWarning(E2EE) << "Adding inbound session"; - Database::instance().saveMegolmSession(q->localUser()->id(), q->id(), senderKey, sessionId, megolmSession->pickle(q->connection()->picklingMode())); + q->connection()->database()->saveMegolmSession(q->id(), senderKey, sessionId, megolmSession->pickle(q->connection()->picklingMode())); groupSessions[{senderKey, sessionId}] = std::move(megolmSession); return true; } @@ -416,9 +416,9 @@ public: return QString(); } const auto& [content, index] = std::get>(decryptResult); - const auto& [recordEventId, ts] = Database::instance().groupSessionIndexRecord(q->localUser()->id(), q->id(), senderSession->sessionId(), index); + const auto& [recordEventId, ts] = q->connection()->database()->groupSessionIndexRecord(q->id(), senderSession->sessionId(), index); if (recordEventId.isEmpty()) { - Database::instance().addGroupSessionIndexRecord(q->localUser()->id(), q->id(), senderSession->sessionId(), index, eventId, timestamp.toMSecsSinceEpoch()); + q->connection()->database()->addGroupSessionIndexRecord(q->id(), senderSession->sessionId(), index, eventId, timestamp.toMSecsSinceEpoch()); } else { if ((eventId != recordEventId) || (ts != timestamp.toMSecsSinceEpoch())) { qCWarning(E2EE) << "Detected a replay attack on event" << eventId; -- cgit v1.2.3 From b4a6070d44140a3cbc931b18530721e31f069455 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Fri, 10 Dec 2021 16:15:50 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- lib/events/roomevent.cpp | 2 +- lib/events/roomevent.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index dbce2255..eb5d0485 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -128,7 +128,7 @@ CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) } #ifdef Quotient_E2EE_ENABLED -void RoomEvent::setOriginalEvent(event_ptr_tt originalEvent) +void RoomEvent::setOriginalEvent(event_ptr_tt&& originalEvent) { _originalEvent = std::move(originalEvent); } diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 36b45f09..3d46bf9b 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -61,7 +61,8 @@ public: void addId(const QString& newId); #ifdef Quotient_E2EE_ENABLED - void setOriginalEvent(event_ptr_tt originalEvent); + void setOriginalEvent(event_ptr_tt&& originalEvent); + const RoomEvent* originalEvent() { return _originalEvent.get(); } const QJsonObject encryptedJson() const; #endif -- cgit v1.2.3 From 4a17403f9adad9b4390f7e0010c0f7e23a718f7b Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Fri, 10 Dec 2021 16:25:18 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- lib/room.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 458f870d..0a4fcc68 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1509,16 +1509,18 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, qCWarning(E2EE) << "added new inboundGroupSession:" << d->groupSessions.size(); for (const auto& eventId : d->undecryptedEvents[roomKeyEvent.sessionId()]) { - if (!d->eventsIndex.contains(eventId)) { + const auto pIdx = d->eventsIndex.constFind(eventId); + if (pIdx == d->eventsIndex.cend()) continue; - } - auto event = d->timeline.rend() - (d->eventsIndex.value(eventId) - minTimelineIndex() + 1); - if (auto encryptedEvent = event->viewAs()) { + auto& ti = d->timeline[Timeline::size_type(*pIdx - minTimelineIndex())]; + if (auto encryptedEvent = ti.viewAs()) { auto decrypted = decryptMessage(*encryptedEvent); if(decrypted) { - auto oldEvent = event->replaceEvent(std::move(decrypted)); - decrypted->setOriginalEvent(std::move(oldEvent)); - emit replacedEvent(event->event(), rawPtr(oldEvent)); + // The reference will survive the pointer being moved + auto& decryptedEvent = *decrypted; + auto oldEvent = ti.replaceEvent(std::move(decrypted)); + decryptedEvent.setOriginalEvent(std::move(oldEvent)); + emit replacedEvent(ti.event(), decrypted->originalEvent()); d->undecryptedEvents[roomKeyEvent.sessionId()] -= eventId; } } -- cgit v1.2.3 From 6b29d759a47012eef74948e72c0d0395eb6bf282 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 10 Dec 2021 17:01:05 +0100 Subject: Remove data from database when leaving room --- lib/database.cpp | 14 ++++++++++++-- lib/database.h | 1 + lib/room.cpp | 4 ++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/database.cpp b/lib/database.cpp index 41e62935..665b931a 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -14,8 +14,6 @@ #include "e2ee/qolmsession.h" #include "e2ee/qolminboundsession.h" -//TODO: delete room specific data when leaving room - using namespace Quotient; Database::Database(const QString& matrixId, QObject* parent) : QObject(parent) @@ -229,3 +227,15 @@ QSqlQuery Database::prepareQuery(const QString& queryString) query.prepare(queryString); return query; } + +void Database::clearRoomData(const QString& roomId) +{ + auto query = prepareQuery(QStringLiteral("DELETE FROM inbound_megolm_sessions WHERE roomId=:roomId;")); + auto query2 = prepareQuery(QStringLiteral("DELETE FROM outbound_megolm_sessions WHERE roomId=:roomId;")); + auto query3 = prepareQuery(QStringLiteral("DELETE FROM group_session_record_index WHERE roomId=:roomId;")); + transaction(); + execute(query); + execute(query2); + execute(query3); + commit(); +} diff --git a/lib/database.h b/lib/database.h index fbb940c8..b2187ba4 100644 --- a/lib/database.h +++ b/lib/database.h @@ -34,6 +34,7 @@ public: void saveMegolmSession(const QString& roomId, const QString& senderKey, const QString& sessionKey, const QByteArray& pickle); void addGroupSessionIndexRecord(const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts); QPair groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index); + void clearRoomData(const QString& roomId); private: void migrateTo1(); diff --git a/lib/room.cpp b/lib/room.cpp index 0a4fcc68..a46892f3 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -461,6 +461,10 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) } }); d->loadMegOlmSessions(); + + connect(this, &Room::beforeDestruction, this, [=](){ + connection->database()->clearRoomData(id); + }); #endif qCDebug(STATE) << "New" << terse << initialJoinState << "Room:" << id; } -- cgit v1.2.3 From 79841d6add9e60716ec6690cde3bccf952cceada Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Wed, 22 Dec 2021 19:13:36 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- lib/database.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/database.cpp b/lib/database.cpp index 665b931a..d4365647 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -140,7 +140,7 @@ void Database::saveOlmSession(const QString& senderKey, const QString& sessionId UnorderedMap> Database::loadOlmSessions(const PicklingMode& picklingMode) { - QSqlQuery query = prepareQuery(QStringLiteral("SELECT * FROM olm_sessions;")); + auto query = prepareQuery(QStringLiteral("SELECT * FROM olm_sessions;")); transaction(); execute(query); commit(); @@ -189,7 +189,7 @@ void Database::saveMegolmSession(const QString& roomId, const QString& senderKey void Database::addGroupSessionIndexRecord(const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts) { - QSqlQuery query = prepareQuery("INSERT INTO group_session_record_index(roomId, sessionId, i, eventId, ts) VALUES(:roomId, :sessionId, :index, :eventId, :ts);"); + auto query = prepareQuery("INSERT INTO group_session_record_index(roomId, sessionId, i, eventId, ts) VALUES(:roomId, :sessionId, :index, :eventId, :ts);"); query.bindValue(":roomId", roomId); query.bindValue(":sessionId", sessionId); query.bindValue(":index", index); -- cgit v1.2.3 From 5920f8cf64b60a07ddf73852d6d4f724ab3bb03a Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 22 Dec 2021 19:16:49 +0100 Subject: Another improvement --- lib/database.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/database.cpp b/lib/database.cpp index d4365647..a5df22af 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -202,8 +202,7 @@ void Database::addGroupSessionIndexRecord(const QString& roomId, const QString& QPair Database::groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index) { - QSqlQuery query(database()); - query.prepare(QStringLiteral("SELECT * FROM group_session_record_index WHERE roomId=:roomId AND sessionId=:sessionId AND i=:index;")); + auto query = prepareQuery(QStringLiteral("SELECT * FROM group_session_record_index WHERE roomId=:roomId AND sessionId=:sessionId AND i=:index;")); query.bindValue(":roomId", roomId); query.bindValue(":sessionId", sessionId); query.bindValue(":index", index); -- cgit v1.2.3 From 1176ec1eedb749e81e3d446733c267a971feefa4 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 24 Dec 2021 00:03:32 +0100 Subject: Find sql when using libquotient --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 43fed3e9..1ff65282 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -328,7 +328,8 @@ if (${PROJECT_NAME}_ENABLE_E2EE) ${Qt}::Sql ${QTKEYCHAIN_LIBRARIES}) set(FIND_DEPS "find_dependency(Olm) - find_dependency(OpenSSL)") # For QuotientConfig.cmake.in + find_dependency(OpenSSL) + find_dependency(${Qt}Sql)") # For QuotientConfig.cmake.in endif() target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) -- cgit v1.2.3 From 31bb962f36c31621b311f1aee654e36ea09e8d77 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 24 Dec 2021 01:47:52 +0100 Subject: Fix reading unencrypted images --- lib/mxcreply.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 2ad49c2c..c7f27b0c 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -29,6 +29,7 @@ public: MxcReply::MxcReply(QNetworkReply* reply) : d(std::make_unique(reply)) { + d->m_device = d->m_reply; reply->setParent(this); connect(d->m_reply, &QNetworkReply::finished, this, [this]() { setError(d->m_reply->error(), d->m_reply->errorString()); -- cgit v1.2.3 From aeed7f3bada2cefcb52d4418b5ed76f19980d702 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 24 Dec 2021 14:29:07 +0100 Subject: Cache deviceslist to binary when possible --- lib/connection.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 8b9f9688..e28ffb22 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1982,9 +1982,16 @@ void Connection::Private::saveDevicesList() rootObj.insert(QStringLiteral("sync_token"), q->nextBatchToken()); } - - QJsonDocument json { rootObj }; - const auto data = json.toJson(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + const auto data = + cacheToBinary + ? QCborValue::fromJsonValue(rootObj).toCbor() + : QJsonDocument(rootObj).toJson(QJsonDocument::Compact); +#else + QJsonDocument json { rootObj }; + const auto data = cacheToBinary ? json.toBinaryData() + : json.toJson(QJsonDocument::Compact); +#endif qCDebug(PROFILER) << "DeviceList generated in" << et; outFile.write(data.data(), data.size()); -- 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 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 f01427c307c825feaa6c45888c259903605c9796 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 25 Dec 2021 15:59:03 +0100 Subject: Update lib/e2ee/qolmoutboundsession.cpp Co-authored-by: Alexey Rusakov --- lib/e2ee/qolmoutboundsession.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 58196412..8494efdd 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -64,7 +64,6 @@ std::variant QOlmOutboundGroupSession::pickle(const Pickl return pickledBuf; } - std::variant, QOlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) { QByteArray pickledBuf = pickled; -- cgit v1.2.3 From f4d6a08811b0bf3a10a23ff703fc5a8ff1fcc624 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 25 Dec 2021 16:00:09 +0100 Subject: Apply suggestions --- lib/e2ee/qolmaccount.cpp | 4 +--- lib/e2ee/qolmerrors.cpp | 14 ++++++++------ lib/e2ee/qolmerrors.h | 2 -- lib/e2ee/qolminboundsession.cpp | 4 +--- lib/e2ee/qolmoutboundsession.cpp | 4 +--- lib/e2ee/qolmsession.cpp | 4 +--- lib/e2ee/qolmutility.cpp | 4 +--- 7 files changed, 13 insertions(+), 23 deletions(-) diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index ffb004cc..a984f884 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -34,9 +34,7 @@ bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) // Convert olm error to enum QOlmError lastError(OlmAccount *account) { - const auto error_raw = olm_account_last_error(account); - - return fromString(error_raw); + return fromString(olm_account_last_error(account)); } QByteArray getRandom(size_t bufferSize) diff --git a/lib/e2ee/qolmerrors.cpp b/lib/e2ee/qolmerrors.cpp index 568cf7fe..5a60b7e6 100644 --- a/lib/e2ee/qolmerrors.cpp +++ b/lib/e2ee/qolmerrors.cpp @@ -4,18 +4,20 @@ #include "qolmerrors.h" -#include +#include "util.h" +#include Quotient::QOlmError Quotient::fromString(const char* error_raw) { - if (!strncmp(error_raw, "BAD_ACCOUNT_KEY", 15)) { + const QLatin1String error { error_raw }; + if (error_raw == "BAD_ACCOUNT_KEY"_ls) { return QOlmError::BadAccountKey; - } else if (!strncmp(error_raw, "BAD_MESSAGE_KEY_ID", 18)) { + } else if (error_raw == "BAD_MESSAGE_KEY_ID"_ls) { return QOlmError::BadMessageKeyId; - } else if (!strncmp(error_raw, "INVALID_BASE64", 14)) { + } else if (error_raw == "INVALID_BASE64"_ls) { return QOlmError::InvalidBase64; - } else if (!strncmp(error_raw, "NOT_ENOUGH_RANDOM", 17)) { + } else if (error_raw == "NOT_ENOUGH_RANDOM"_ls) { return QOlmError::NotEnoughRandom; - } else if (!strncmp(error_raw, "OUTPUT_BUFFER_TOO_SMALL", 23)) { + } else if (error_raw == "OUTPUT_BUFFER_TOO_SMALL"_ls) { return QOlmError::OutputBufferTooSmall; } else { return QOlmError::Unknown; diff --git a/lib/e2ee/qolmerrors.h b/lib/e2ee/qolmerrors.h index f2d77851..24e87d95 100644 --- a/lib/e2ee/qolmerrors.h +++ b/lib/e2ee/qolmerrors.h @@ -4,8 +4,6 @@ #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. diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 2c546875..9729c02d 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -8,9 +8,7 @@ using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { - const auto error_raw = olm_inbound_group_session_last_error(session); - - return fromString(error_raw); + return fromString(olm_inbound_group_session_last_error(session)); } QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session) diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 8494efdd..e75ab427 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -8,9 +8,7 @@ using namespace Quotient; QOlmError lastError(OlmOutboundGroupSession *session) { - const auto error_raw = olm_outbound_group_session_last_error(session); - - return fromString(error_raw); + return fromString(olm_outbound_group_session_last_error(session)); } QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session) diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 575019b3..e575ff39 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -11,9 +11,7 @@ using namespace Quotient; QOlmError lastError(OlmSession* session) { - const auto error_raw = olm_session_last_error(session); - - return fromString(error_raw); + return fromString(olm_session_last_error(session)); } Quotient::QOlmSession::~QOlmSession() diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index 13ee695e..303f6d75 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -10,9 +10,7 @@ using namespace Quotient; // Convert olm error to enum QOlmError lastError(OlmUtility *utility) { - const auto error_raw = olm_utility_last_error(utility); - - return fromString(error_raw); + return fromString(olm_utility_last_error(utility)); } QOlmUtility::QOlmUtility() -- 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 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 5af76cb4bedaea85bf8e2a8538fabe4e47d40e1a Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 25 Dec 2021 16:56:17 +0100 Subject: Remove unneeded code --- lib/converters.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/converters.cpp b/lib/converters.cpp index 6cbb554d..b80f5985 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -8,21 +8,11 @@ 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()) { - const QJsonObject obj = jv.toObject(); - if (obj.contains(QLatin1String("key")) && obj.contains(QLatin1String("signatures"))) { - SignedOneTimeKey signedOneTimeKeys; - signedOneTimeKeys.key = obj[QLatin1String("key")].toString(); - } - } return jv.toVariant(); } -- cgit v1.2.3 From 586332cda3d786ccbe74b21bd70d290fee722719 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 25 Dec 2021 17:03:21 +0100 Subject: Remove irrelevant include --- lib/converters.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/converters.cpp b/lib/converters.cpp index b80f5985..444ca4f6 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -4,7 +4,6 @@ #include "converters.h" #include -#include "e2ee/e2ee.h" QJsonValue Quotient::JsonConverter::dump(const QVariant& v) { -- cgit v1.2.3 From d56bb69f06c70bb7cf659d5fdde4e2306d7fb2f2 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 25 Dec 2021 18:19:46 +0100 Subject: Don't save olm account ón shutdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It causes the program to crash for some reason --- lib/connection.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index e28ffb22..138f968f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -269,9 +269,7 @@ Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent), d(new Private(std::make_unique(server))) { #ifdef Quotient_E2EE_ENABLED - connect(qApp, &QCoreApplication::aboutToQuit, this, [this](){ - saveOlmAccount(); - }); + //connect(qApp, &QCoreApplication::aboutToQuit, this, &Connection::saveOlmAccount); #endif d->q = this; // All d initialization should occur before this line } -- cgit v1.2.3 From 9ef0c672595f47532966566ec08f16424fcf1abb Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 25 Dec 2021 18:22:25 +0100 Subject: Update lib/e2ee/qolmoutboundsession.h Co-authored-by: Alexey Rusakov --- lib/e2ee/qolmoutboundsession.h | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 967f563f..72d87035 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -12,7 +12,6 @@ namespace Quotient { - //! An out-bound group session is responsible for encrypting outgoing //! communication in a Megolm session. class QOlmOutboundGroupSession -- cgit v1.2.3 From b465e785aac47bab8f4b8f4dac9672c9c32ea020 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 25 Dec 2021 18:22:31 +0100 Subject: Update lib/e2ee/qolmoutboundsession.h Co-authored-by: Alexey Rusakov --- lib/e2ee/qolmoutboundsession.h | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 72d87035..39263c77 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later - #pragma once #include "olm/olm.h" -- cgit v1.2.3 From 3164daa6d6cbfb75a1528b2671f5c78422a5539e Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 25 Dec 2021 19:57:26 +0100 Subject: Remove assert --- lib/e2ee/qolmoutboundsession.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index e75ab427..da32417b 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -19,7 +19,6 @@ 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)); } -- cgit v1.2.3 From ff415143b0d953f04a207125e265111b925ce763 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 25 Dec 2021 21:51:17 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- lib/connection.cpp | 6 +++--- lib/csapi/keys.h | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 138f968f..89b80909 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -112,7 +112,7 @@ public: #ifdef Quotient_E2EE_ENABLED QSet trackedUsers; QSet outdatedUsers; - QHash> deviceKeys; + QHash> deviceKeys; QueryKeysJob *currentQueryKeysJob = nullptr; bool encryptionUpdateRequired = false; PicklingMode picklingMode = Unencrypted {}; @@ -1975,7 +1975,7 @@ void Connection::Private::saveDevicesList() } rootObj.insert(QStringLiteral("tracked_users"), trackedUsersJson); rootObj.insert(QStringLiteral("outdated_users"), outdatedUsersJson); - QJsonObject devicesList = toJson>>(deviceKeys); + const auto devicesList = toJson(deviceKeys); rootObj.insert(QStringLiteral("devices_list"), devicesList); rootObj.insert(QStringLiteral("sync_token"), q->nextBatchToken()); } @@ -2023,7 +2023,7 @@ void Connection::Private::loadDevicesList() outdatedUsers += user.toString(); } - deviceKeys = fromJson>>(json["devices_list"].toObject()); + fromJson(json["devices_list"], deviceKeys); auto oldToken = json["sync_token"].toString(); auto changesJob = q->callApi(oldToken, q->nextBatchToken()); connect(changesJob, &BaseJob::success, q, [this, changesJob](){ diff --git a/lib/csapi/keys.h b/lib/csapi/keys.h index b1cc640c..7db09e8d 100644 --- a/lib/csapi/keys.h +++ b/lib/csapi/keys.h @@ -166,11 +166,6 @@ struct JsonObjectConverter { fillFromJson(jo, result); fromJson(jo.value("unsigned"_ls), result.unsignedData); } - static void dumpTo(QJsonObject& jo, const QueryKeysJob::DeviceInformation& deviceInformation) - { - jo = toJson(deviceInformation); - //addParam<>(jo, "unsigned"_ls, deviceInformation.unsignedData); - } }; /*! \brief Claim one-time encryption keys. -- 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(-) 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 2fc62d38e8f748d1a78baca8b6f2df40e7cfa1a9 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 30 Jan 2022 21:20:00 +0100 Subject: Use room() instead of provideRoom() --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 89b80909..dbc6261d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2068,7 +2068,7 @@ QString Connection::e2eeDataDir() const #ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { - auto room = provideRoom(notification["room_id"].toString()); + auto room = this->room(notification["room_id"].toString()); auto event = makeEvent(notification["event"].toObject()); auto decrypted = room->decryptMessage(*event); if(!decrypted) { -- cgit v1.2.3 From 023ef3005d3fae80637c6ce140e84db26250d564 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 30 Jan 2022 23:13:01 +0100 Subject: Port devices list to database --- lib/connection.cpp | 156 ++++++++++++++++++++++------------------------------- lib/database.cpp | 4 ++ 2 files changed, 67 insertions(+), 93 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index dbc6261d..54d79674 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -442,10 +442,9 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) completeSetup(loginJob->userId()); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; -#else // Quotient_E2EE_ENABLED +#endif // Quotient_E2EE_ENABLED database = new Database(loginJob->userId(), q); database->clear(); -#endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -1891,7 +1890,6 @@ QVector Connection::availableRoomVersions() co return result; } -#ifdef Quotient_E2EE_ENABLED void Connection::Private::loadOutdatedUserDevices() { QHash users; @@ -1930,112 +1928,84 @@ void Connection::Private::loadOutdatedUserDevices() }); } -void Connection::encryptionUpdate(Room *room) -{ - for(const auto &user : room->users()) { - if(!d->trackedUsers.contains(user->id())) { - d->trackedUsers += user->id(); - d->outdatedUsers += user->id(); - d->encryptionUpdateRequired = true; - } - } -} - void Connection::Private::saveDevicesList() { - if (!cacheState) - return; - - QElapsedTimer et; - et.start(); + q->database()->transaction(); + auto query = q->database()->prepareQuery(QStringLiteral("DELETE FROM tracked_users")); + q->database()->execute(query); + query.prepare(QStringLiteral("INSERT INTO tracked_users(matrixId) VALUES(:matrixId);")); + for (const auto& user : trackedUsers) { + query.bindValue(":matrixId", user); + q->database()->execute(query); + } - QFile outFile { q->e2eeDataDir() % "/deviceslist.json" }; - if (!outFile.open(QFile::WriteOnly)) { - qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" - << outFile.errorString(); - qCWarning(E2EE) << "Caching the rooms state disabled"; - cacheState = false; - return; + query.prepare(QStringLiteral("DELETE FROM outdated_users")); + q->database()->execute(query); + query.prepare(QStringLiteral("INSERT INTO outdated_users(matrixId) VALUES(:matrixId);")); + for (const auto& user : outdatedUsers) { + query.bindValue(":matrixId", user); + q->database()->execute(query); } - QJsonObject rootObj { - { QStringLiteral("cache_version"), - QJsonObject { - { QStringLiteral("major"), SyncData::cacheVersion().first }, - { QStringLiteral("minor"), SyncData::cacheVersion().second } } } - }; - { - QJsonArray trackedUsersJson; - QJsonArray outdatedUsersJson; - for (const auto &user : trackedUsers) { - trackedUsersJson += user; - } - for (const auto &user : outdatedUsers) { - outdatedUsersJson += user; + query.prepare(QStringLiteral("INSERT INTO tracked_devices(matrixId, deviceId, curveKeyId, curveKey, edKeyId, edKey) VALUES(:matrixId, :deviceId, :curveKeyId, :curveKey, :edKeyId, :edKey);")); + for (const auto& user : deviceKeys.keys()) { + for (const auto& device : deviceKeys[user]) { + auto keys = device.keys.keys(); + auto curveKeyId = keys[0].startsWith(QLatin1String("curve")) ? keys[0] : keys[1]; + auto edKeyId = keys[0].startsWith(QLatin1String("ed")) ? keys[0] : keys[1]; + + query.bindValue(":matrixId", user); + query.bindValue(":deviceId", device.deviceId); + query.bindValue(":curveKeyId", curveKeyId); + query.bindValue(":curveKey", device.keys[curveKeyId]); + query.bindValue(":edKeyId", edKeyId); + query.bindValue(":edKey", device.keys[edKeyId]); + + q->database()->execute(query); } - rootObj.insert(QStringLiteral("tracked_users"), trackedUsersJson); - rootObj.insert(QStringLiteral("outdated_users"), outdatedUsersJson); - const auto devicesList = toJson(deviceKeys); - rootObj.insert(QStringLiteral("devices_list"), devicesList); - rootObj.insert(QStringLiteral("sync_token"), q->nextBatchToken()); } - -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - const auto data = - cacheToBinary - ? QCborValue::fromJsonValue(rootObj).toCbor() - : QJsonDocument(rootObj).toJson(QJsonDocument::Compact); -#else - QJsonDocument json { rootObj }; - const auto data = cacheToBinary ? json.toBinaryData() - : json.toJson(QJsonDocument::Compact); -#endif - qCDebug(PROFILER) << "DeviceList generated in" << et; - - outFile.write(data.data(), data.size()); - qCDebug(E2EE) << "DevicesList saved to" << outFile.fileName(); + q->database()->commit(); } void Connection::Private::loadDevicesList() { - QFile file { q->e2eeDataDir() % "/deviceslist.json" }; - if(!file.exists() || !file.open(QIODevice::ReadOnly)) { - qCDebug(E2EE) << "No devicesList cache exists. Creating new"; - return; - } - auto data = file.readAll(); - const auto json = data.startsWith('{') - ? QJsonDocument::fromJson(data).object() -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - : QCborValue::fromCbor(data).toJsonValue().toObject() -#else - : QJsonDocument::fromBinaryData(data).object() -#endif - ; - if (json.isEmpty()) { - qCWarning(MAIN) << "DevicesList cache is broken or empty, discarding"; - return; + auto query = q->database()->prepareQuery(QStringLiteral("SELECT * FROM tracked_users;")); + q->database()->execute(query); + while(query.next()) { + trackedUsers += query.value(0).toString(); } - for(const auto &user : json["tracked_users"].toArray()) { - trackedUsers += user.toString(); + + query = q->database()->prepareQuery(QStringLiteral("SELECT * FROM outdated_users;")); + q->database()->execute(query); + while(query.next()) { + outdatedUsers += query.value(0).toString(); } - for(const auto &user : json["outdated_users"].toArray()) { - outdatedUsers += user.toString(); + + query = q->database()->prepareQuery(QStringLiteral("SELECT * FROM tracked_devices;")); + q->database()->execute(query); + while(query.next()) { + deviceKeys[query.value("matrixId").toString()][query.value("deviceId").toString()] = DeviceKeys { + query.value("matrixId").toString(), + query.value("deviceId").toString(), + { "m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}, + {{query.value("curveKeyId").toString(), query.value("curveKey").toString()}, + {query.value("edKeyId").toString(), query.value("edKey").toString()}}, + {} // Signatures are not saved/loaded as they are not needed after initial validation + }; } - fromJson(json["devices_list"], deviceKeys); - auto oldToken = json["sync_token"].toString(); - auto changesJob = q->callApi(oldToken, q->nextBatchToken()); - connect(changesJob, &BaseJob::success, q, [this, changesJob](){ - bool hasNewOutdatedUser = false; - for(const auto &user : changesJob->changed()) { - outdatedUsers += user; - hasNewOutdatedUser = true; - } - if(hasNewOutdatedUser) { - loadOutdatedUserDevices(); +} + +#ifdef Quotient_E2EE_ENABLED +void Connection::encryptionUpdate(Room *room) +{ + for(const auto &user : room->users()) { + if(!d->trackedUsers.contains(user->id())) { + d->trackedUsers += user->id(); + d->outdatedUsers += user->id(); + d->encryptionUpdateRequired = true; } - }); + } } PicklingMode Connection::picklingMode() const diff --git a/lib/database.cpp b/lib/database.cpp index a5df22af..535920e2 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -86,6 +86,10 @@ void Database::migrateTo1() execute(QStringLiteral("CREATE TABLE inbound_megolm_sessions (roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); execute(QStringLiteral("CREATE TABLE outbound_megolm_sessions (roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); execute(QStringLiteral("CREATE TABLE group_session_record_index (roomId TEXT, sessionId TEXT, i INTEGER, eventId TEXT, ts INTEGER);")); + execute(QStringLiteral("CREATE TABLE tracked_users (matrixId TEXT);")); + execute(QStringLiteral("CREATE TABLE outdated_users (matrixId TEXT);")); + execute(QStringLiteral("CREATE TABLE tracked_devices (matrixId TEXT, deviceId TEXT, curveKeyId TEXT, curveKey TEXT, edKeyId TEXT, edKey TEXT);")); + execute(QStringLiteral("PRAGMA user_version = 1;")); commit(); } -- 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(-) 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 ac53741920b1e92b8ac61bb7c11afcae5722b241 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sun, 6 Feb 2022 20:00:38 +0100 Subject: Update lib/e2ee/qolminboundsession.cpp Co-authored-by: Alexey Rusakov --- lib/e2ee/qolminboundsession.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 9729c02d..9bc80eef 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -4,7 +4,6 @@ #include "e2ee/qolminboundsession.h" #include -#include using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { -- cgit v1.2.3 From 9960d33e0c2bbe4dd8f305cae3b01c2d704d28ff Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 6 Feb 2022 20:00:01 +0100 Subject: Port to QRandomGenerator --- lib/e2ee/qolmutils.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/e2ee/qolmutils.cpp b/lib/e2ee/qolmutils.cpp index ce27710d..6f7937e8 100644 --- a/lib/e2ee/qolmutils.cpp +++ b/lib/e2ee/qolmutils.cpp @@ -3,8 +3,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "e2ee/qolmutils.h" -#include -#include +#include using namespace Quotient; @@ -19,6 +18,6 @@ QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) QByteArray Quotient::getRandom(size_t bufferSize) { QByteArray buffer(bufferSize, '0'); - RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); + QRandomGenerator::system()->generate(buffer.begin(), buffer.end()); return buffer; } -- cgit v1.2.3 From ef64359505778913235666f0e759dd0758f7f4ac Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 6 Feb 2022 20:06:19 +0100 Subject: Only create one database --- lib/connection.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 54d79674..58e3a9f8 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -443,8 +443,6 @@ 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 = new Database(loginJob->userId(), q); - database->clear(); }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -499,9 +497,7 @@ void Connection::Private::completeSetup(const QString& mxId) olmAccount = std::make_unique(data->userId(), data->deviceId(), q); connect(olmAccount.get(), &QOlmAccount::needsSave, q, &Connection::saveOlmAccount); - if (!database) { - database = new Database(data->userId(), q); - } + database = new Database(data->userId(), q); encryptionManager = new EncryptionManager(q); -- cgit v1.2.3 From 4837fd6de188b849a5d11de6791ba844398ce415 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sun, 6 Feb 2022 20:10:40 +0100 Subject: Update lib/e2ee/qolmaccount.cpp Co-authored-by: Alexey Rusakov --- lib/e2ee/qolmaccount.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index a984f884..34ee7ea0 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -29,7 +29,7 @@ std::optional> OneTimeKeys::get(QString keyType) const bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) { - return lhs.curve25519 == rhs.curve25519 &&& lhs.ed25519 == rhs.ed25519; + return lhs.curve25519 == rhs.curve25519 && lhs.ed25519 == rhs.ed25519; } // Convert olm error to enum -- 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 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 840aead6e77a7ab8605bd2f70820ddd2219bdad5 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 7 Feb 2022 22:04:38 +0100 Subject: Refactor KeyVerificationEvents --- lib/events/keyverificationevent.cpp | 83 ++++++++++++------------------------- 1 file changed, 27 insertions(+), 56 deletions(-) diff --git a/lib/events/keyverificationevent.cpp b/lib/events/keyverificationevent.cpp index 938b3bde..4803955d 100644 --- a/lib/events/keyverificationevent.cpp +++ b/lib/events/keyverificationevent.cpp @@ -11,26 +11,22 @@ KeyVerificationRequestEvent::KeyVerificationRequestEvent(const QJsonObject &obj) QString KeyVerificationRequestEvent::fromDevice() const { - return contentJson()["from_device"_ls].toString(); + return contentPart("from_device"_ls); } QString KeyVerificationRequestEvent::transactionId() const { - return contentJson()["transaction_id"_ls].toString(); + return contentPart("transaction_id"_ls); } QStringList KeyVerificationRequestEvent::methods() const { - QStringList methods; - for (const auto &method : contentJson()["methods"].toArray()) { - methods.append(method.toString()); - } - return methods; + return contentPart("methods"_ls); } uint64_t KeyVerificationRequestEvent::timestamp() const { - return contentJson()["timestamp"_ls].toDouble(); + return contentPart("timestamp"_ls); } KeyVerificationStartEvent::KeyVerificationStartEvent(const QJsonObject &obj) @@ -39,62 +35,46 @@ KeyVerificationStartEvent::KeyVerificationStartEvent(const QJsonObject &obj) QString KeyVerificationStartEvent::fromDevice() const { - return contentJson()["from_device"_ls].toString(); + return contentPart("from_device"_ls); } QString KeyVerificationStartEvent::transactionId() const { - return contentJson()["transaction_id"_ls].toString(); + return contentPart("transaction_id"_ls); } QString KeyVerificationStartEvent::method() const { - return contentJson()["method"_ls].toString(); + return contentPart("method"_ls); } Omittable KeyVerificationStartEvent::nextMethod() const { - auto next = contentJson()["method"_ls]; - if (next.isUndefined()) { - return std::nullopt; - } - return next.toString(); + return contentPart>("method_ls"); } QStringList KeyVerificationStartEvent::keyAgreementProtocols() const { Q_ASSERT(method() == QStringLiteral("m.sas.v1")); - QStringList protocols; - for (const auto &proto : contentJson()["key_agreement_protocols"_ls].toArray()) { - protocols.append(proto.toString()); - } - return protocols; + return contentPart("key_agreement_protocols"_ls); } QStringList KeyVerificationStartEvent::hashes() const { Q_ASSERT(method() == QStringLiteral("m.sas.v1")); - QStringList hashes; - for (const auto &hashItem : contentJson()["hashes"_ls].toArray()) { - hashes.append(hashItem.toString()); - } - return hashes; + return contentPart("hashes"_ls); + } QStringList KeyVerificationStartEvent::messageAuthenticationCodes() const { Q_ASSERT(method() == QStringLiteral("m.sas.v1")); - - QStringList codes; - for (const auto &code : contentJson()["message_authentication_codes"_ls].toArray()) { - codes.append(code.toString()); - } - return codes; + return contentPart("message_authentication_codes"_ls); } QString KeyVerificationStartEvent::shortAuthenticationString() const { - return contentJson()["short_authentification_string"_ls].toString(); + return contentPart("short_authentification_string"_ls); } KeyVerificationAcceptEvent::KeyVerificationAcceptEvent(const QJsonObject &obj) @@ -103,36 +83,32 @@ KeyVerificationAcceptEvent::KeyVerificationAcceptEvent(const QJsonObject &obj) QString KeyVerificationAcceptEvent::transactionId() const { - return contentJson()["transaction_id"_ls].toString(); + return contentPart("transaction_id"_ls); } QString KeyVerificationAcceptEvent::method() const { - return contentJson()["method"_ls].toString(); + return contentPart("method"_ls); } QString KeyVerificationAcceptEvent::keyAgreementProtocol() const { - return contentJson()["key_agreement_protocol"_ls].toString(); + return contentPart("key_agreement_protocol"_ls); } QString KeyVerificationAcceptEvent::hashData() const { - return contentJson()["hash"_ls].toString(); + return contentPart("hash"_ls); } QStringList KeyVerificationAcceptEvent::shortAuthenticationString() const { - QStringList strings; - for (const auto &authenticationString : contentJson()["short_authentification_string"].toArray()) { - strings.append(authenticationString.toString()); - } - return strings; + return contentPart("short_authentification_string"_ls); } QString KeyVerificationAcceptEvent::commitement() const { - return contentJson()["commitement"].toString(); + return contentPart("commitment"_ls); } KeyVerificationCancelEvent::KeyVerificationCancelEvent(const QJsonObject &obj) @@ -141,17 +117,17 @@ KeyVerificationCancelEvent::KeyVerificationCancelEvent(const QJsonObject &obj) QString KeyVerificationCancelEvent::transactionId() const { - return contentJson()["transaction_id"_ls].toString(); + return contentPart("transaction_id"_ls); } QString KeyVerificationCancelEvent::reason() const { - return contentJson()["reason"_ls].toString(); + return contentPart("reason"_ls); } QString KeyVerificationCancelEvent::code() const { - return contentJson()["code"_ls].toString(); + return contentPart("code"_ls); } KeyVerificationKeyEvent::KeyVerificationKeyEvent(const QJsonObject &obj) @@ -160,12 +136,12 @@ KeyVerificationKeyEvent::KeyVerificationKeyEvent(const QJsonObject &obj) QString KeyVerificationKeyEvent::transactionId() const { - return contentJson()["transaction_id"_ls].toString(); + return contentPart("transaction_id"_ls); } QString KeyVerificationKeyEvent::key() const { - return contentJson()["key"_ls].toString(); + return contentPart("key"_ls); } KeyVerificationMacEvent::KeyVerificationMacEvent(const QJsonObject &obj) @@ -174,20 +150,15 @@ KeyVerificationMacEvent::KeyVerificationMacEvent(const QJsonObject &obj) QString KeyVerificationMacEvent::transactionId() const { - return contentJson()["transaction_id"].toString(); + return contentPart("transaction_id"_ls); } QString KeyVerificationMacEvent::keys() const { - return contentJson()["keys"].toString(); + return contentPart("keys"_ls); } QHash KeyVerificationMacEvent::mac() const { - QHash macs; - const auto macObj = contentJson()["mac"_ls].toObject(); - for (auto mac = macObj.constBegin(); mac != macObj.constEnd(); mac++) { - macs.insert(mac.key(), mac.value().toString()); - } - return macs; + return contentPart>("mac"_ls); } -- cgit v1.2.3 From 94f34099b8a4c8a40cc99496ceaf9ad5b285c08f Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 9 Feb 2022 22:18:42 +0100 Subject: Move includes to .cpp file --- lib/events/encryptedfile.cpp | 4 ++++ lib/events/encryptedfile.h | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index 5ec344bb..74119127 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -3,6 +3,10 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedfile.h" +#include "logging.h" + +#include +#include using namespace Quotient; diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index f271d345..6199be8e 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -5,10 +5,6 @@ #pragma once #include "converters.h" -#include "logging.h" - -#include -#include namespace Quotient { /** -- cgit v1.2.3 From 6415d6fb194799870eb89cbaff4ba07939aa6ccb Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 9 Feb 2022 22:25:24 +0100 Subject: Fix compilation without E2EE --- lib/connection.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 1a1b284d..04ce1dc2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -523,8 +523,9 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) completeSetup(loginJob->userId()); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; -#endif // Quotient_E2EE_ENABLED +#else // Quotient_E2EE_ENABLED database->clear(); +#endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -1970,6 +1971,7 @@ QVector Connection::availableRoomVersions() co return result; } +#ifdef Quotient_E2EE_ENABLED void Connection::Private::loadOutdatedUserDevices() { QHash users; @@ -2076,7 +2078,6 @@ void Connection::Private::loadDevicesList() } -#ifdef Quotient_E2EE_ENABLED void Connection::encryptionUpdate(Room *room) { for(const auto &user : room->users()) { -- cgit v1.2.3 From 148bb1256fb15c73c605c5301da2b9602f859660 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 11 Feb 2022 23:07:35 +0100 Subject: Implement more suggestions --- lib/connection.cpp | 11 +++++++++++ lib/connection.h | 3 +++ lib/room.cpp | 9 +++------ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 04ce1dc2..cc5d8739 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -38,6 +38,7 @@ # include "e2ee/qolmaccount.h" # include "e2ee/qolmutils.h" # include "database.h" +# include "e2ee/qolminboundsession.h" #if QT_VERSION_MAJOR >= 6 # include @@ -2120,4 +2121,14 @@ Database* Connection::database() { return d->database; } + +UnorderedMap, QOlmInboundGroupSessionPtr> Connection::loadRoomMegolmSessions(Room* room) +{ + return database()->loadMegolmSessions(room->id(), picklingMode()); +} + +void Connection::saveMegolmSession(Room* room, const QString& senderKey, QOlmInboundGroupSession* session) +{ + database()->saveMegolmSession(room->id(), senderKey, session->sessionId(), session->pickle(picklingMode())); +} #endif diff --git a/lib/connection.h b/lib/connection.h index 8dec2a0c..13aa15c0 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -50,6 +50,7 @@ class LeaveRoomJob; class Database; class QOlmAccount; +class QOlmInboundGroupSession; using LoginFlow = GetLoginFlowsJob::LoginFlow; @@ -315,6 +316,8 @@ public: #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; Database* database(); + UnorderedMap, QOlmInboundGroupSessionPtr> loadRoomMegolmSessions(Room* room); + void saveMegolmSession(Room* room, const QString& senderKey, QOlmInboundGroupSession* session); #endif // Quotient_E2EE_ENABLED Q_INVOKABLE Quotient::SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; diff --git a/lib/room.cpp b/lib/room.cpp index a46892f3..492845d7 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -370,9 +370,6 @@ public: // A map from (senderKey, sessionId) to InboundGroupSession UnorderedMap, QOlmInboundGroupSessionPtr> groupSessions; - void loadMegOlmSessions() { - groupSessions = q->connection()->database()->loadMegolmSessions(q->id(), q->connection()->picklingMode()); - } bool addInboundGroupSession(QString senderKey, QString sessionId, QString sessionKey) { @@ -382,14 +379,14 @@ public: return false; } - std::unique_ptr megolmSession = QOlmInboundGroupSession::create(sessionKey.toLatin1()); + auto megolmSession = QOlmInboundGroupSession::create(sessionKey.toLatin1()); if (megolmSession->sessionId() != sessionId) { qCWarning(E2EE) << "Session ID mismatch in m.room_key event sent " "from sender with key" << senderKey; return false; } qCWarning(E2EE) << "Adding inbound session"; - q->connection()->database()->saveMegolmSession(q->id(), senderKey, sessionId, megolmSession->pickle(q->connection()->picklingMode())); + connection->saveMegolmSession(q, senderKey, megolmSession.get()); groupSessions[{senderKey, sessionId}] = std::move(megolmSession); return true; } @@ -460,7 +457,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) connection->encryptionUpdate(this); } }); - d->loadMegOlmSessions(); + d->groupSessions = connection->loadRoomMegolmSessions(this); connect(this, &Room::beforeDestruction, this, [=](){ connection->database()->clearRoomData(id); -- cgit v1.2.3 From b0aef4af9cbf00755c7b70c71d77f0bf7ce0d200 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 12 Feb 2022 12:12:24 +0100 Subject: Replace QPair with std::pair --- lib/connection.cpp | 2 +- lib/connection.h | 2 +- lib/database.cpp | 6 +++--- lib/database.h | 4 ++-- lib/room.cpp | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index cc5d8739..a8de4030 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2122,7 +2122,7 @@ Database* Connection::database() return d->database; } -UnorderedMap, QOlmInboundGroupSessionPtr> Connection::loadRoomMegolmSessions(Room* room) +UnorderedMap, QOlmInboundGroupSessionPtr> Connection::loadRoomMegolmSessions(Room* room) { return database()->loadMegolmSessions(room->id(), picklingMode()); } diff --git a/lib/connection.h b/lib/connection.h index 13aa15c0..28ea6ff3 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -316,7 +316,7 @@ public: #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; Database* database(); - UnorderedMap, QOlmInboundGroupSessionPtr> loadRoomMegolmSessions(Room* room); + UnorderedMap, QOlmInboundGroupSessionPtr> loadRoomMegolmSessions(Room* room); void saveMegolmSession(Room* room, const QString& senderKey, QOlmInboundGroupSession* session); #endif // Quotient_E2EE_ENABLED Q_INVOKABLE Quotient::SyncJob* syncJob() const; diff --git a/lib/database.cpp b/lib/database.cpp index 535920e2..b91b6ef1 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -160,14 +160,14 @@ UnorderedMap> Database::loadOlmSessions(con return sessions; } -UnorderedMap, QOlmInboundGroupSessionPtr> Database::loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode) +UnorderedMap, QOlmInboundGroupSessionPtr> Database::loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode) { auto query = prepareQuery(QStringLiteral("SELECT * FROM inbound_megolm_sessions WHERE roomId=:roomId;")); query.bindValue(":roomId", roomId); transaction(); execute(query); commit(); - UnorderedMap, QOlmInboundGroupSessionPtr> sessions; + UnorderedMap, QOlmInboundGroupSessionPtr> sessions; while (query.next()) { auto session = QOlmInboundGroupSession::unpickle(query.value("pickle").toByteArray(), picklingMode); if (std::holds_alternative(session)) { @@ -204,7 +204,7 @@ void Database::addGroupSessionIndexRecord(const QString& roomId, const QString& commit(); } -QPair Database::groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index) +std::pair Database::groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index) { auto query = prepareQuery(QStringLiteral("SELECT * FROM group_session_record_index WHERE roomId=:roomId AND sessionId=:sessionId AND i=:index;")); query.bindValue(":roomId", roomId); diff --git a/lib/database.h b/lib/database.h index b2187ba4..96256a55 100644 --- a/lib/database.h +++ b/lib/database.h @@ -30,10 +30,10 @@ public: void clear(); void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray &pickle); UnorderedMap> loadOlmSessions(const PicklingMode& picklingMode); - UnorderedMap, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode); + UnorderedMap, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode); void saveMegolmSession(const QString& roomId, const QString& senderKey, const QString& sessionKey, const QByteArray& pickle); void addGroupSessionIndexRecord(const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts); - QPair groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index); + std::pair groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index); void clearRoomData(const QString& roomId); private: diff --git a/lib/room.cpp b/lib/room.cpp index 492845d7..0fc7d23e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -368,7 +368,7 @@ public: #ifdef Quotient_E2EE_ENABLED // A map from (senderKey, sessionId) to InboundGroupSession - UnorderedMap, QOlmInboundGroupSessionPtr> groupSessions; + UnorderedMap, QOlmInboundGroupSessionPtr> groupSessions; bool addInboundGroupSession(QString senderKey, QString sessionId, QString sessionKey) @@ -397,7 +397,7 @@ public: const QString& eventId, QDateTime timestamp) { - const auto senderSessionPairKey = qMakePair(senderKey, sessionId); + const auto senderSessionPairKey = make_pair(senderKey, sessionId); auto groupSessionIt = groupSessions.find(senderSessionPairKey); if (groupSessionIt == groupSessions.end()) { // qCWarning(E2EE) << "Unable to decrypt event" << eventId -- cgit v1.2.3 From 52a787eefb3fb3d147648d08fc439a4b8a966fd3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 16 Feb 2022 17:57:30 +0100 Subject: Add a few missing QUOTIENT_API stanzas Also, removed Q_GADGET macros from key verification events as those don't seem to do anything (no Q_ENUM/Q_FLAG things, namely). --- lib/database.h | 3 +-- lib/e2ee/e2ee.h | 2 +- lib/e2ee/qolmaccount.h | 14 +++++++------- lib/e2ee/qolmerrors.h | 4 +++- lib/e2ee/qolminboundsession.h | 2 +- lib/e2ee/qolmmessage.h | 4 +++- lib/e2ee/qolmoutboundsession.h | 2 +- lib/e2ee/qolmsession.h | 2 +- lib/e2ee/qolmutility.h | 2 +- lib/e2ee/qolmutils.h | 4 ++-- lib/events/encryptedfile.h | 2 +- lib/events/keyverificationevent.h | 16 +++++----------- 12 files changed, 27 insertions(+), 30 deletions(-) diff --git a/lib/database.h b/lib/database.h index 96256a55..d244dc0b 100644 --- a/lib/database.h +++ b/lib/database.h @@ -10,10 +10,9 @@ #include "e2ee/e2ee.h" namespace Quotient { -class Database : public QObject +class QUOTIENT_API Database : public QObject { Q_OBJECT - public: Database(const QString& matrixId, QObject* parent); diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 41cd2878..4c825376 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -65,7 +65,7 @@ struct IdentityKeys }; //! Struct representing the one-time keys. -struct OneTimeKeys +struct QUOTIENT_API OneTimeKeys { QMap> keys; diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 00afc0e6..17aca8aa 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -25,7 +25,7 @@ using QOlmSessionPtr = std::unique_ptr; //! \code{.cpp} //! const auto olmAccount = new QOlmAccount(this); //! \endcode -class QOlmAccount : public QObject +class QUOTIENT_API QOlmAccount : public QObject { Q_OBJECT public: @@ -111,13 +111,13 @@ private: QString m_deviceId; }; -bool verifyIdentitySignature(const DeviceKeys &deviceKeys, - const QString &deviceId, - const QString &userId); +QUOTIENT_API 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); +QUOTIENT_API bool ed25519VerifySignature(const QString& signingKey, + const QJsonObject& obj, + const QString& signature); } // namespace Quotient diff --git a/lib/e2ee/qolmerrors.h b/lib/e2ee/qolmerrors.h index 24e87d95..20e61c12 100644 --- a/lib/e2ee/qolmerrors.h +++ b/lib/e2ee/qolmerrors.h @@ -4,6 +4,8 @@ #pragma once +#include "quotient_export.h" + namespace Quotient { //! All errors that could be caused by an operation regarding Olm //! Errors are named exactly like the ones in libolm. @@ -21,6 +23,6 @@ enum QOlmError Unknown, }; -QOlmError fromString(const char* error_raw); +QUOTIENT_API QOlmError fromString(const char* error_raw); } //namespace Quotient diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 7d52991c..1f5dadd3 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -15,7 +15,7 @@ namespace Quotient { //! An in-bound group session is responsible for decrypting incoming //! communication in a Megolm session. -struct QOlmInboundGroupSession +class QUOTIENT_API QOlmInboundGroupSession { public: ~QOlmInboundGroupSession(); diff --git a/lib/e2ee/qolmmessage.h b/lib/e2ee/qolmmessage.h index 52aba78c..557c02b1 100644 --- a/lib/e2ee/qolmmessage.h +++ b/lib/e2ee/qolmmessage.h @@ -4,6 +4,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -16,7 +18,7 @@ namespace Quotient { * * The class provides functions to get a type and the ciphertext. */ -class QOlmMessage : public QByteArray { +class QUOTIENT_API QOlmMessage : public QByteArray { Q_GADGET public: enum Type { diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 39263c77..0122bbfd 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -13,7 +13,7 @@ namespace Quotient { //! An out-bound group session is responsible for encrypting outgoing //! communication in a Megolm session. -class QOlmOutboundGroupSession +class QUOTIENT_API QOlmOutboundGroupSession { public: ~QOlmOutboundGroupSession(); diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index 1febfa0f..889a606d 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -18,7 +18,7 @@ class QOlmSession; //! Either an outbound or inbound session for secure communication. -class QOlmSession +class QUOTIENT_API QOlmSession { public: ~QOlmSession(); diff --git a/lib/e2ee/qolmutility.h b/lib/e2ee/qolmutility.h index b360d625..b2e79e29 100644 --- a/lib/e2ee/qolmutility.h +++ b/lib/e2ee/qolmutility.h @@ -17,7 +17,7 @@ class Connection; //! Allows you to make use of crytographic hashing via SHA-2 and //! verifying ed25519 signatures. -class QOlmUtility +class QUOTIENT_API QOlmUtility { public: QOlmUtility(); diff --git a/lib/e2ee/qolmutils.h b/lib/e2ee/qolmutils.h index bbd71332..f218e628 100644 --- a/lib/e2ee/qolmutils.h +++ b/lib/e2ee/qolmutils.h @@ -10,6 +10,6 @@ namespace Quotient { // Convert PicklingMode to key -QByteArray toKey(const PicklingMode &mode); -QByteArray getRandom(size_t bufferSize); +QUOTIENT_API QByteArray toKey(const PicklingMode &mode); +QUOTIENT_API QByteArray getRandom(size_t bufferSize); } diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 6199be8e..43bafc49 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -29,7 +29,7 @@ public: bool ext; }; -struct EncryptedFile +struct QUOTIENT_API EncryptedFile { Q_GADGET Q_PROPERTY(QUrl url MEMBER url CONSTANT) diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h index 13e7dcdd..497e56a2 100644 --- a/lib/events/keyverificationevent.h +++ b/lib/events/keyverificationevent.h @@ -7,8 +7,7 @@ namespace Quotient { /// Requests a key verification with another user's devices. /// Typically sent as a to-device event. -class KeyVerificationRequestEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationRequestEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.request", KeyVerificationRequestEvent) @@ -33,8 +32,7 @@ public: REGISTER_EVENT_TYPE(KeyVerificationRequestEvent) /// Begins a key verification process. -class KeyVerificationStartEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationStartEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.start", KeyVerificationStartEvent) @@ -76,8 +74,7 @@ REGISTER_EVENT_TYPE(KeyVerificationStartEvent) /// Accepts a previously sent m.key.verification.start message. /// Typically sent as a to-device event. -class KeyVerificationAcceptEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationAcceptEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.accept", KeyVerificationAcceptEvent) @@ -111,8 +108,7 @@ public: }; REGISTER_EVENT_TYPE(KeyVerificationAcceptEvent) -class KeyVerificationCancelEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationCancelEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.cancel", KeyVerificationCancelEvent) @@ -133,7 +129,6 @@ REGISTER_EVENT_TYPE(KeyVerificationCancelEvent) /// Sends the ephemeral public key for a device to the partner device. /// Typically sent as a to-device event. class KeyVerificationKeyEvent : public Event { - Q_GADGET public: DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent) @@ -148,8 +143,7 @@ public: REGISTER_EVENT_TYPE(KeyVerificationKeyEvent) /// Sends the MAC of a device's key to the partner device. -class KeyVerificationMacEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationMacEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.mac", KeyVerificationMacEvent) -- cgit v1.2.3 From 53dfa70601b2d27a6be12d52e86af123d0b26b79 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 15 Feb 2022 20:51:32 +0100 Subject: Cleanup A note on switching to QLatin1String for JSON key constants - this is more concise and barely affects (if at all) runtime performance (padding each QChar with zeros is trivial for assignment; and comparison can be done directly with the same performance as for two QStrings). --- lib/connection.cpp | 22 +++++++----- lib/e2ee/e2ee.h | 76 ++++++++++++++++------------------------ lib/e2ee/qolmaccount.cpp | 79 ++++++++++++++++-------------------------- lib/e2ee/qolminboundsession.h | 26 +++++++------- lib/e2ee/qolmmessage.cpp | 4 +-- lib/e2ee/qolmmessage.h | 3 +- lib/e2ee/qolmoutboundsession.h | 4 +-- lib/e2ee/qolmsession.h | 41 +++++++++++++++------- lib/e2ee/qolmutility.h | 3 -- lib/events/encryptedevent.cpp | 13 +++++++ lib/events/encryptedevent.h | 14 +++----- lib/syncdata.h | 2 +- 12 files changed, 138 insertions(+), 149 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 14188ace..3b8da6d1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -754,18 +754,20 @@ QJsonObject toJson(const DirectChatsMap& directChats) void Connection::onSyncSuccess(SyncData&& data, bool fromCache) { #ifdef Quotient_E2EE_ENABLED - if(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() && !d->isUploadingKeys) { + const auto oneTimeKeyCount = + static_cast(data.deviceOneTimeKeysCount()[SignedCurve25519Key]); + if (oneTimeKeyCount < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() + && !d->isUploadingKeys) { d->isUploadingKeys = true; - d->olmAccount->generateOneTimeKeys(d->olmAccount->maxNumberOfOneTimeKeys() / 2 - data.deviceOneTimeKeysCount()["signed_curve25519"]); + d->olmAccount->generateOneTimeKeys( + d->olmAccount->maxNumberOfOneTimeKeys() / 2 - oneTimeKeyCount); auto keys = d->olmAccount->oneTimeKeys(); auto job = d->olmAccount->createUploadKeyRequest(keys); run(job, ForegroundRequest); - connect(job, &BaseJob::success, this, [this](){ - d->olmAccount->markKeysAsPublished(); - }); - connect(job, &BaseJob::result, this, [this](){ - d->isUploadingKeys = false; - }); + connect(job, &BaseJob::success, this, + [this] { d->olmAccount->markKeysAsPublished(); }); + connect(job, &BaseJob::result, this, + [this] { d->isUploadingKeys = false; }); } static bool first = true; if(first) { @@ -1993,7 +1995,9 @@ void Connection::Private::loadOutdatedUserDevices() deviceKeys[user].clear(); for(const auto &device : keys) { if(device.userId != user) { - qCWarning(E2EE) << "mxId mismatch during device key verification:" << device.userId << user; + qCWarning(E2EE) + << "mxId mismatch during device key verification:" + << device.userId << user; continue; } if(!device.algorithms.contains("m.olm.v1.curve25519-aes-sha2") || !device.algorithms.contains("m.megolm.v1.aes-sha2")) { diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 4c825376..e21aa87b 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -5,43 +5,34 @@ #pragma once -#include -#include #include "converters.h" +#include "quotient_common.h" + +#include #include -#include -#include -#include -#include +namespace Quotient { -#include "util.h" +constexpr auto CiphertextKeyL = "ciphertext"_ls; +constexpr auto SenderKeyKeyL = "sender_key"_ls; +constexpr auto DeviceIdKeyL = "device_id"_ls; +constexpr auto SessionIdKeyL = "session_id"_ls; -namespace Quotient { +constexpr auto AlgorithmKeyL = "algorithm"_ls; +constexpr auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; +constexpr auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls; + +constexpr auto AlgorithmKey = "algorithm"_ls; +constexpr auto RotationPeriodMsKey = "rotation_period_ms"_ls; +constexpr auto RotationPeriodMsgsKey = "rotation_period_msgs"_ls; + +constexpr auto Ed25519Key = "ed25519"_ls; +constexpr auto Curve25519Key = "curve25519"_ls; +constexpr auto SignedCurve25519Key = "signed_curve25519"_ls; + +constexpr auto OlmV1Curve25519AesSha2AlgoKey = "m.olm.v1.curve25519-aes-sha2"_ls; +constexpr auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls; -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; @@ -55,9 +46,6 @@ 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; @@ -73,16 +61,13 @@ struct QUOTIENT_API OneTimeKeys QMap curve25519() const; //! Get a reference to the hashmap corresponding to given key type. - std::optional> get(QString keyType) const; +// 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; @@ -94,8 +79,7 @@ public: template <> struct JsonObjectConverter { - static void fillFrom(const QJsonObject& jo, - SignedOneTimeKey& result) + static void fillFrom(const QJsonObject& jo, SignedOneTimeKey& result) { fromJson(jo.value("key"_ls), result.key); fromJson(jo.value("signatures"_ls), result.signatures); @@ -108,24 +92,22 @@ struct JsonObjectConverter { } }; -bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); - template class asKeyValueRange { public: - asKeyValueRange(T &data) - : m_data{data} - { - } + asKeyValueRange(T& data) + : m_data { data } + {} auto begin() { return m_data.keyValueBegin(); } - auto end() { return m_data.keyValueEnd(); } private: T &m_data; }; +template +asKeyValueRange(T&) -> asKeyValueRange; } // namespace Quotient diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 34ee7ea0..9cbb14f5 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -3,53 +3,41 @@ // 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 +#include "e2ee/qolmutils.h" + +#include "csapi/keys.h" + +#include using namespace Quotient; QMap OneTimeKeys::curve25519() const { - return keys[QStringLiteral("curve25519")]; + return keys[Curve25519Key]; } -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; -} +//std::optional> OneTimeKeys::get(QString keyType) const +//{ +// if (!keys.contains(keyType)) { +// return std::nullopt; +// } +// return keys[keyType]; +//} // Convert olm error to enum QOlmError lastError(OlmAccount *account) { return fromString(olm_account_last_error(account)); } -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) +QOlmAccount::QOlmAccount(const QString& userId, const QString& deviceId, + QObject* parent) : QObject(parent) , m_userId(userId) , m_deviceId(deviceId) -{ -} +{} QOlmAccount::~QOlmAccount() { @@ -66,7 +54,7 @@ void QOlmAccount::createNewAccount() if (error == olm_error()) { throw lastError(m_account); } - Q_EMIT needsSave(); + emit needsSave(); } void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) @@ -161,7 +149,7 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const if (error == olm_error()) { throw lastError(m_account); } - Q_EMIT needsSave(); + emit needsSave(); return error; } @@ -220,14 +208,11 @@ std::optional QOlmAccount::removeOneTimeKeys(const QOlmSessionPtr &se if (error == olm_error()) { return lastError(m_account); } - Q_EMIT needsSave(); + emit needsSave(); return std::nullopt; } -OlmAccount *QOlmAccount::data() -{ - return m_account; -} +OlmAccount* QOlmAccount::data() { return m_account; } DeviceKeys QOlmAccount::deviceKeys() const { @@ -284,31 +269,27 @@ std::variant QOlmAccount::createOutboundSession(const void QOlmAccount::markKeysAsPublished() { olm_account_mark_keys_as_published(m_account); - Q_EMIT needsSave(); + emit needsSave(); } -bool Quotient::verifyIdentitySignature(const DeviceKeys &deviceKeys, - const QString &deviceId, - const QString &userId) +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) +bool Quotient::ed25519VerifySignature(const QString& signingKey, + const QJsonObject& obj, + const QString& signature) { - if (signature.isEmpty()) { + if (signature.isEmpty()) return false; - } + QJsonObject obj1 = obj; obj1.remove("unsigned"); diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 1f5dadd3..437f753d 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -4,12 +4,12 @@ #pragma once -#include -#include -#include -#include "olm/olm.h" -#include "e2ee/qolmerrors.h" #include "e2ee/e2ee.h" +#include "e2ee/qolmerrors.h" +#include "olm/olm.h" + +#include +#include namespace Quotient { @@ -20,16 +20,18 @@ class QUOTIENT_API QOlmInboundGroupSession public: ~QOlmInboundGroupSession(); //! Creates a new instance of `OlmInboundGroupSession`. - static std::unique_ptr create(const QByteArray &key); + 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); + 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); + 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); + 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); @@ -38,11 +40,11 @@ public: //! Get a base64-encoded identifier for this session. QByteArray sessionId() const; bool isVerified() const; - QOlmInboundGroupSession(OlmInboundGroupSession *session); + + QOlmInboundGroupSession(OlmInboundGroupSession* session); private: - OlmInboundGroupSession *m_groupSession; + 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 index 15008b75..81b166b0 100644 --- a/lib/e2ee/qolmmessage.cpp +++ b/lib/e2ee/qolmmessage.cpp @@ -6,11 +6,11 @@ using namespace Quotient; -QOlmMessage::QOlmMessage(const QByteArray &ciphertext, QOlmMessage::Type type) +QOlmMessage::QOlmMessage(QByteArray ciphertext, QOlmMessage::Type type) : QByteArray(std::move(ciphertext)) , m_messageType(type) { - Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); + Q_ASSERT_X(!isEmpty(), "olm message", "Ciphertext is empty"); } QOlmMessage::QOlmMessage(const QOlmMessage &message) diff --git a/lib/e2ee/qolmmessage.h b/lib/e2ee/qolmmessage.h index 557c02b1..5d5db636 100644 --- a/lib/e2ee/qolmmessage.h +++ b/lib/e2ee/qolmmessage.h @@ -28,8 +28,9 @@ public: Q_ENUM(Type) QOlmMessage() = default; - explicit QOlmMessage(const QByteArray &ciphertext, Type type = General); + explicit QOlmMessage(QByteArray ciphertext, Type type = General); explicit QOlmMessage(const QOlmMessage &message); + ~QOlmMessage() = default; static QOlmMessage fromCiphertext(const QByteArray &ciphertext); diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 0122bbfd..32ba2b3b 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -24,7 +24,8 @@ public: 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(QByteArray& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. std::variant encrypt(const QString &plaintext); @@ -48,5 +49,4 @@ private: }; using QOlmOutboundGroupSessionPtr = std::unique_ptr; -using OlmOutboundGroupSessionPtr = std::unique_ptr; } diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index 889a606d..f20c9837 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -16,20 +16,31 @@ namespace Quotient { class QOlmAccount; class QOlmSession; - //! Either an outbound or inbound session for secure communication. class QUOTIENT_API 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); + 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); + static std::variant, QOlmError> unpickle( + const QByteArray& pickled, const PicklingMode& mode); + //! Encrypts a plaintext message using the session. QOlmMessage encrypt(const QString &plaintext); @@ -48,29 +59,33 @@ public: bool hasReceivedMessage() const; //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSession(const QOlmMessage &preKeyMessage) const; + 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; + 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) { + friend bool operator<(const std::unique_ptr& lhs, + const std::unique_ptr& rhs) + { return *lhs < *rhs; } - OlmSession *raw() const - { - return m_session; - } + 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 = ""); + 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.h b/lib/e2ee/qolmutility.h index b2e79e29..a12af49a 100644 --- a/lib/e2ee/qolmutility.h +++ b/lib/e2ee/qolmutility.h @@ -4,7 +4,6 @@ #pragma once -#include #include #include "e2ee/qolmerrors.h" @@ -13,7 +12,6 @@ struct OlmUtility; namespace Quotient { class QOlmSession; -class Connection; //! Allows you to make use of crytographic hashing via SHA-2 and //! verifying ed25519 signatures. @@ -37,7 +35,6 @@ public: std::variant ed25519Verify(const QByteArray &key, const QByteArray &message, const QByteArray &signature); - private: OlmUtility *m_utility; diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 1b5e4441..ba4dd154 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -33,6 +33,19 @@ EncryptedEvent::EncryptedEvent(const QJsonObject& obj) qCDebug(E2EE) << "Encrypted event from" << senderId(); } +QString EncryptedEvent::algorithm() const +{ + auto algo = contentPart(AlgorithmKeyL); + static constexpr auto SupportedAlgorithms = + make_array(OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey); + if (std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), + algo) == SupportedAlgorithms.cend()) { + qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo + << "is not supported"; + } + return algo; +} + RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const { auto eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index c838bbd8..72efffd4 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -39,22 +39,16 @@ public: const QString& deviceId, const QString& sessionId); explicit EncryptedEvent(const QJsonObject& obj); - QString algorithm() const - { - QString algo = contentPart(AlgorithmKeyL); - if (!SupportedAlgorithms.contains(algo)) { - qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo - << "is not supported"; - } - return algo; - } + QString algorithm() const; QByteArray ciphertext() const { return contentPart(CiphertextKeyL).toLatin1(); } QJsonObject ciphertext(const QString& identityKey) const { - return contentPart(CiphertextKeyL).value(identityKey).toObject(); + return contentPart(CiphertextKeyL) + .value(identityKey) + .toObject(); } QString senderKey() const { return contentPart(SenderKeyKeyL); } diff --git a/lib/syncdata.h b/lib/syncdata.h index 633f4b52..6b70140d 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -54,7 +54,7 @@ struct DevicesList { QStringList left; }; -QDebug operator<<(QDebug dhg, const DevicesList &devicesList); +QDebug operator<<(QDebug dhg, const DevicesList& devicesList); template <> struct JsonObjectConverter { -- cgit v1.2.3 From 0f6506c022ff1ccaa648ff50b81ae29f5a6f2176 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 14 Feb 2022 08:55:50 +0100 Subject: Connection: guard device loading per-object Using a static variable is incorrect as it doesn't load the device list for any subsequent created Connection object. --- lib/connection.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 3b8da6d1..0562d416 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -131,6 +131,7 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; bool isUploadingKeys = false; + bool firstSync = true; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -769,10 +770,9 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) connect(job, &BaseJob::result, this, [this] { d->isUploadingKeys = false; }); } - static bool first = true; - if(first) { + if(d->firstSync) { d->loadDevicesList(); - first = false; + d->firstSync = false; } d->consumeDevicesList(data.takeDevicesList()); -- cgit v1.2.3 From 445e34f26450cf8262a65b74e1294579d9cd56be Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 13 Feb 2022 22:11:52 +0100 Subject: Fix file decryption --- lib/events/encryptedfile.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index 74119127..dbb72af8 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -13,7 +13,7 @@ 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()); + auto keyBytes = 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"; @@ -23,7 +23,7 @@ QByteArray EncryptedFile::decryptFile(const QByteArray &ciphertext) const 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_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)keyBytes.data(), (const unsigned char *)QByteArray::fromBase64(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); -- cgit v1.2.3 From 2178ca994c4b33197239155f7f6715e0451b9172 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 14 Feb 2022 14:06:14 +0100 Subject: Use QHash instead of QMap We don't seem to need sorted associative containers in those cases. --- lib/e2ee/e2ee.h | 4 ++-- lib/e2ee/qolmaccount.cpp | 16 ++++------------ lib/e2ee/qolmaccount.h | 2 +- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index e21aa87b..361c48ff 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -55,10 +55,10 @@ struct IdentityKeys //! Struct representing the one-time keys. struct QUOTIENT_API OneTimeKeys { - QMap> keys; + QHash> keys; //! Get the HashMap containing the curve25519 one-time keys. - QMap curve25519() const; + QHash curve25519() const; //! Get a reference to the hashmap corresponding to given key type. // std::optional> get(QString keyType) const; diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 9cbb14f5..476a60bd 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -14,7 +14,7 @@ using namespace Quotient; -QMap OneTimeKeys::curve25519() const +QHash OneTimeKeys::curve25519() const { return keys[Curve25519Key]; } @@ -164,21 +164,13 @@ OneTimeKeys QOlmAccount::oneTimeKeys() const } 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; - } + fromJson(json, oneTimeKeys.keys); return oneTimeKeys; } -QMap QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const +QHash QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const { - QMap signedOneTimeKeys; + QHash signedOneTimeKeys; for (const auto &keyid : keys.curve25519().keys()) { const auto oneTimeKey = keys.curve25519()[keyid]; QByteArray sign = signOneTimeKey(oneTimeKey); diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 17aca8aa..17f43f1a 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -67,7 +67,7 @@ public: OneTimeKeys oneTimeKeys() const; //! Sign all one time keys. - QMap signOneTimeKeys(const OneTimeKeys &keys) const; + QHash signOneTimeKeys(const OneTimeKeys &keys) const; //! Sign one time key. QByteArray signOneTimeKey(const QString &key) const; -- cgit v1.2.3 From 0a43c023b94e12b3130572f2dd0d6ac8bb4ed110 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 14 Feb 2022 15:25:24 +0100 Subject: isSupportedAlgorithm() That's a better primitive than just exposing SupportedAlgorithms list. --- lib/connection.cpp | 7 +++++-- lib/e2ee/e2ee.h | 9 +++++++++ lib/events/encryptedevent.cpp | 9 +++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 0562d416..87fc8e2c 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2000,8 +2000,11 @@ void Connection::Private::loadOutdatedUserDevices() << device.userId << user; continue; } - if(!device.algorithms.contains("m.olm.v1.curve25519-aes-sha2") || !device.algorithms.contains("m.megolm.v1.aes-sha2")) { - qCWarning(E2EE) << "Unsupported encryption algorithms found" << device.algorithms; + if (!std::all_of(device.algorithms.cbegin(), + device.algorithms.cend(), + isSupportedAlgorithm)) { + qCWarning(E2EE) << "Unsupported encryption algorithms found" + << device.algorithms; continue; } if(!verifyIdentitySignature(device, device.deviceId, device.userId)) { diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 361c48ff..268cb525 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -33,6 +33,15 @@ constexpr auto SignedCurve25519Key = "signed_curve25519"_ls; constexpr auto OlmV1Curve25519AesSha2AlgoKey = "m.olm.v1.curve25519-aes-sha2"_ls; constexpr auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls; +inline bool isSupportedAlgorithm(const QString& algorithm) +{ + static constexpr auto SupportedAlgorithms = + make_array(OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey); + return std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), + algorithm) + != SupportedAlgorithms.cend(); +} + struct Unencrypted {}; struct Encrypted { QByteArray key; diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index ba4dd154..9d07a35f 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -35,14 +35,11 @@ EncryptedEvent::EncryptedEvent(const QJsonObject& obj) QString EncryptedEvent::algorithm() const { - auto algo = contentPart(AlgorithmKeyL); - static constexpr auto SupportedAlgorithms = - make_array(OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey); - if (std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), - algo) == SupportedAlgorithms.cend()) { + const auto algo = contentPart(AlgorithmKeyL); + if (!isSupportedAlgorithm(algo)) qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo << "is not supported"; - } + return algo; } -- cgit v1.2.3 From b5e1fc7d8fcf9336db0dfb351403aa06dcb226a0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 16 Feb 2022 08:40:56 +0100 Subject: More cleanup, especially in EncryptedFile For EncryptedFile: - JSON converter bodies moved away to .cpp; - instead of C-style casts, reinterpret_cast is used to convert from (const) char* to (const) unsigned char*; - the size for the target plain text takes into account the case where the cipher block size can be larger than 1 (after reading https://www.openssl.org/docs/man1.1.1/man3/EVP_DecryptUpdate.html). - file decryption is wrapped in #ifdef Quotient_E2EE_ENABLED, to avoid OpenSSL linking errors when compiling without E2EE. --- lib/connection.cpp | 21 ++++++++--- lib/events/encryptedfile.cpp | 89 +++++++++++++++++++++++++++++++++++++------- lib/events/encryptedfile.h | 40 +++----------------- lib/jobs/downloadfilejob.cpp | 6 +-- 4 files changed, 99 insertions(+), 57 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 87fc8e2c..1ef2495d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2007,8 +2007,10 @@ void Connection::Private::loadOutdatedUserDevices() << device.algorithms; continue; } - if(!verifyIdentitySignature(device, device.deviceId, device.userId)) { - qCWarning(E2EE) << "Failed to verify devicekeys signature. Skipping this device"; + if (!verifyIdentitySignature(device, device.deviceId, + device.userId)) { + qCWarning(E2EE) << "Failed to verify devicekeys signature. " + "Skipping this device"; continue; } deviceKeys[user][device.deviceId] = device; @@ -2022,9 +2024,11 @@ void Connection::Private::loadOutdatedUserDevices() void Connection::Private::saveDevicesList() { q->database()->transaction(); - auto query = q->database()->prepareQuery(QStringLiteral("DELETE FROM tracked_users")); + auto query = q->database()->prepareQuery( + QStringLiteral("DELETE FROM tracked_users")); q->database()->execute(query); - query.prepare(QStringLiteral("INSERT INTO tracked_users(matrixId) VALUES(:matrixId);")); + query.prepare(QStringLiteral( + "INSERT INTO tracked_users(matrixId) VALUES(:matrixId);")); for (const auto& user : trackedUsers) { query.bindValue(":matrixId", user); q->database()->execute(query); @@ -2032,13 +2036,18 @@ void Connection::Private::saveDevicesList() query.prepare(QStringLiteral("DELETE FROM outdated_users")); q->database()->execute(query); - query.prepare(QStringLiteral("INSERT INTO outdated_users(matrixId) VALUES(:matrixId);")); + query.prepare(QStringLiteral( + "INSERT INTO outdated_users(matrixId) VALUES(:matrixId);")); for (const auto& user : outdatedUsers) { query.bindValue(":matrixId", user); q->database()->execute(query); } - query.prepare(QStringLiteral("INSERT INTO tracked_devices(matrixId, deviceId, curveKeyId, curveKey, edKeyId, edKey) VALUES(:matrixId, :deviceId, :curveKeyId, :curveKey, :edKeyId, :edKey);")); + query.prepare(QStringLiteral( + "INSERT INTO tracked_devices" + "(matrixId, deviceId, curveKeyId, curveKey, edKeyId, edKey) " + "VALUES(:matrixId, :deviceId, :curveKeyId, :curveKey, :edKeyId, :edKey);" + )); for (const auto& user : deviceKeys.keys()) { for (const auto& device : deviceKeys[user]) { auto keys = device.keys.keys(); diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index dbb72af8..d4a517bd 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -5,27 +5,88 @@ #include "encryptedfile.h" #include "logging.h" +#ifdef Quotient_E2EE_ENABLED #include #include +#endif using namespace Quotient; -QByteArray EncryptedFile::decryptFile(const QByteArray &ciphertext) const +QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const { - QString _key = key.k; - auto keyBytes = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); +#ifdef Quotient_E2EE_ENABLED + auto _key = key.k; + const auto keyBytes = QByteArray::fromBase64( + _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1()); const auto sha256 = QByteArray::fromBase64(hashes["sha256"].toLatin1()); - if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { + if (sha256 + != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { qCWarning(E2EE) << "Hash verification failed for file"; - return QByteArray(); + return {}; } - 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 *)keyBytes.data(), (const unsigned char *)QByteArray::fromBase64(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; + { + int length; + auto* ctx = EVP_CIPHER_CTX_new(); + QByteArray plaintext(ciphertext.size() + EVP_CIPHER_CTX_block_size(ctx) + - 1, + '\0'); + EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, + reinterpret_cast( + keyBytes.data()), + reinterpret_cast( + QByteArray::fromBase64(iv.toLatin1()).data())); + EVP_DecryptUpdate( + ctx, reinterpret_cast(plaintext.data()), &length, + reinterpret_cast(ciphertext.data()), + ciphertext.size()); + EVP_DecryptFinal_ex(ctx, + reinterpret_cast(plaintext.data()) + + length, + &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext; + } +#else + qWarning(MAIN) << "This build of libQuotient doesn't support E2EE, " + "cannot decrypt the file"; + return ciphertext; +#endif +} + +void JsonObjectConverter::dumpTo(QJsonObject& jo, + const EncryptedFile& pod) +{ + addParam<>(jo, QStringLiteral("url"), pod.url); + addParam<>(jo, QStringLiteral("key"), pod.key); + addParam<>(jo, QStringLiteral("iv"), pod.iv); + addParam<>(jo, QStringLiteral("hashes"), pod.hashes); + addParam<>(jo, QStringLiteral("v"), pod.v); +} + +void JsonObjectConverter::fillFrom(const QJsonObject& jo, + EncryptedFile& pod) +{ + fromJson(jo.value("url"_ls), pod.url); + fromJson(jo.value("key"_ls), pod.key); + fromJson(jo.value("iv"_ls), pod.iv); + fromJson(jo.value("hashes"_ls), pod.hashes); + fromJson(jo.value("v"_ls), pod.v); +} + +void JsonObjectConverter::dumpTo(QJsonObject &jo, const JWK &pod) +{ + addParam<>(jo, QStringLiteral("kty"), pod.kty); + addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); + addParam<>(jo, QStringLiteral("alg"), pod.alg); + addParam<>(jo, QStringLiteral("k"), pod.k); + addParam<>(jo, QStringLiteral("ext"), pod.ext); +} + +void JsonObjectConverter::fillFrom(const QJsonObject &jo, JWK &pod) +{ + fromJson(jo.value("kty"_ls), pod.kty); + fromJson(jo.value("key_ops"_ls), pod.keyOps); + fromJson(jo.value("alg"_ls), pod.alg); + fromJson(jo.value("k"_ls), pod.k); + fromJson(jo.value("ext"_ls), pod.ext); } diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 43bafc49..0558563f 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -49,42 +49,14 @@ public: }; template <> -struct JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const EncryptedFile& pod) - { - addParam<>(jo, QStringLiteral("url"), pod.url); - addParam<>(jo, QStringLiteral("key"), pod.key); - addParam<>(jo, QStringLiteral("iv"), pod.iv); - addParam<>(jo, QStringLiteral("hashes"), pod.hashes); - addParam<>(jo, QStringLiteral("v"), pod.v); - } - static void fillFrom(const QJsonObject& jo, EncryptedFile& pod) - { - fromJson(jo.value("url"_ls), pod.url); - fromJson(jo.value("key"_ls), pod.key); - fromJson(jo.value("iv"_ls), pod.iv); - fromJson(jo.value("hashes"_ls), pod.hashes); - fromJson(jo.value("v"_ls), pod.v); - } +struct QUOTIENT_API JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const EncryptedFile& pod); + static void fillFrom(const QJsonObject& jo, EncryptedFile& pod); }; template <> -struct JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const JWK& pod) - { - addParam<>(jo, QStringLiteral("kty"), pod.kty); - addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); - addParam<>(jo, QStringLiteral("alg"), pod.alg); - addParam<>(jo, QStringLiteral("k"), pod.k); - addParam<>(jo, QStringLiteral("ext"), pod.ext); - } - static void fillFrom(const QJsonObject& jo, JWK& pod) - { - fromJson(jo.value("kty"_ls), pod.kty); - fromJson(jo.value("key_ops"_ls), pod.keyOps); - fromJson(jo.value("alg"_ls), pod.alg); - fromJson(jo.value("k"_ls), pod.k); - fromJson(jo.value("ext"_ls), pod.ext); - } +struct QUOTIENT_API JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const JWK& pod); + static void fillFrom(const QJsonObject& jo, JWK& pod); }; } // namespace Quotient diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 634e5fb9..d00fc5f4 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -127,7 +127,7 @@ BaseJob::Status DownloadFileJob::prepareResult() QByteArray encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = file.decryptFile(encrypted); + const auto decrypted = file.decryptFile(encrypted); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -149,10 +149,10 @@ BaseJob::Status DownloadFileJob::prepareResult() #ifdef Quotient_E2EE_ENABLED if (d->encryptedFile.has_value()) { d->tempFile->seek(0); - auto encrypted = d->tempFile->readAll(); + const auto encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = file.decryptFile(encrypted); + const auto decrypted = file.decryptFile(encrypted); d->tempFile->write(decrypted); } else { #endif -- cgit v1.2.3 From ba2dc2a1aa23c91a52bcd0d9f3feb29ea9ecfcd2 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 16 Feb 2022 10:31:37 +0100 Subject: ci.yml: enable E2EE pipelines again The whole cycle will get terribly long again; looking forward to parallel quotest... --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d902bd3..c8ea0d95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,18 +19,20 @@ jobs: fail-fast: false max-parallel: 1 matrix: - os: [ubuntu-20.04, macos-10.15] + os: [ ubuntu-20.04, macos-10.15 ] compiler: [ GCC, Clang ] qt-version: [ '5.12.12' ] # Not using binary values here, to make the job captions more readable - e2ee: [ '' ] - update-api: [ '', 'update-api' ] + e2ee: [ '', e2ee ] + update-api: [ '', update-api ] sonar: [ '' ] platform: [ '' ] qt-arch: [ '' ] exclude: - os: macos-10.15 compiler: GCC + - os: windows-2019 + e2ee: e2ee # Not supported by the current CI script include: - os: ubuntu-latest compiler: GCC @@ -55,7 +57,6 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 - submodules: ${{ matrix.e2ee != '' }} - name: Cache Qt id: cache-qt @@ -154,7 +155,6 @@ jobs: if: ${{ contains(matrix.os, 'ubuntu') && matrix.e2ee }} run: | sudo apt-get install libssl-dev - echo "openssl version" >>$GITHUB_ENV - name: Build and install olm if: matrix.e2ee -- cgit v1.2.3 From e93260a6b68279518270d0aec2c55f826375fa8a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 16 Feb 2022 12:25:10 +0100 Subject: quotient_export.h: #include To ensure Q_DECL_EXPORT/Q_DECL_IMPORT macros are defined. --- lib/quotient_export.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/quotient_export.h b/lib/quotient_export.h index 5a6edb0e..56767443 100644 --- a/lib/quotient_export.h +++ b/lib/quotient_export.h @@ -3,6 +3,8 @@ #pragma once +#include + #ifdef QUOTIENT_STATIC # define QUOTIENT_API # define QUOTIENT_HIDDEN -- 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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 d7ecf9b839b841ca22a3611b3608801a811e7b00 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 18 Feb 2022 23:10:34 +0100 Subject: Only testolmaccount on Linux On other platforms Docker fails to stand up the container. --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67ef5ac5..f622662b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -217,7 +217,12 @@ 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: | - autotests/run-tests.sh --test-dir $BUILD_PATH --output-on-failure + CTEST_ARGS="--test-dir $BUILD_PATH --output-on-failure" + if [[ -z '${{ matrix.e2ee }}' || '${{ runner.os }}' != 'Linux' ]]; then + ctest $CTEST_ARGS -E testolmaccount + else + autotests/run-tests.sh $CTEST_ARGS + fi [[ -z "$TEST_USER" ]] || \ LD_LIBRARY_PATH=$LIB_PATH \ $VALGRIND quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" -- 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(-) 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(-) 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(-) 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 From 1458d1fc856c33b25c07ac16a86bbc2096110ba1 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Fri, 25 Feb 2022 15:15:34 +0100 Subject: Don't run e2ee on macos --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f622662b..0bbbc06c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,8 @@ jobs: compiler: GCC - os: windows-2019 e2ee: e2ee # Not supported by the current CI script + - os: macos-10.15 + e2ee: e2ee # Missing OpenSSL include: - os: ubuntu-latest compiler: GCC -- cgit v1.2.3 From 9815e9a0a27f0c4a493ad96e9d865ee489ca9404 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 25 Feb 2022 15:35:07 +0100 Subject: Save key counts to state Otherwise new one time keys will be uploaded on every start --- lib/connection.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 0ef27486..4c614176 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -117,6 +117,7 @@ public: bool encryptionUpdateRequired = false; PicklingMode picklingMode = Unencrypted {}; Database *database = nullptr; + QHash oneTimeKeysCount; // A map from SenderKey to vector of InboundSession UnorderedMap> olmSessions; @@ -755,13 +756,12 @@ QJsonObject toJson(const DirectChatsMap& directChats) void Connection::onSyncSuccess(SyncData&& data, bool fromCache) { #ifdef Quotient_E2EE_ENABLED - const auto oneTimeKeyCount = - static_cast(data.deviceOneTimeKeysCount()[SignedCurve25519Key]); - if (oneTimeKeyCount < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() + d->oneTimeKeysCount = data.deviceOneTimeKeysCount(); + if (d->oneTimeKeysCount[SignedCurve25519Key] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() && !d->isUploadingKeys) { d->isUploadingKeys = true; d->olmAccount->generateOneTimeKeys( - d->olmAccount->maxNumberOfOneTimeKeys() / 2 - oneTimeKeyCount); + d->olmAccount->maxNumberOfOneTimeKeys() / 2 - d->oneTimeKeysCount[SignedCurve25519Key]); auto keys = d->olmAccount->oneTimeKeys(); auto job = d->olmAccount->createUploadKeyRequest(keys); run(job, ForegroundRequest); @@ -1829,6 +1829,10 @@ void Connection::saveState() const QJsonObject { { QStringLiteral("events"), accountDataEvents } }); } + { + QJsonObject keysJson = toJson(d->oneTimeKeysCount); + rootObj.insert(QStringLiteral("device_one_time_keys_count"), keysJson); + } #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) const auto data = -- cgit v1.2.3 From b0e1455989405ef46eb6d9ed2cd559a1164d04f4 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 25 Feb 2022 16:03:59 +0100 Subject: Ifdef --- lib/connection.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/connection.cpp b/lib/connection.cpp index 4c614176..66df1e43 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1829,10 +1829,12 @@ void Connection::saveState() const QJsonObject { { QStringLiteral("events"), accountDataEvents } }); } +#ifdef Quotient_E2EE_ENABLED { QJsonObject keysJson = toJson(d->oneTimeKeysCount); rootObj.insert(QStringLiteral("device_one_time_keys_count"), keysJson); } +#endif #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) const auto data = -- cgit v1.2.3