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/encryptionmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/encryptionmanager.cpp') 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" -- cgit v1.2.3 From d72f220e3e3a3b243fdafd93d1405f8207dc516a Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Thu, 28 Jan 2021 23:51:56 +0300 Subject: E2EE: initial port to internal olm wrapper Remove qtolm git module. Update CMakeLists.txt. Rename olm to crypto subdir to prevent disambiguation. Rename internal files accordingly. Comment out not ported E2EE API usage. --- .gitmodules | 3 - 3rdparty/libQtOlm | 1 - CMakeLists.txt | 69 ++++------- autotests/testgroupsession.cpp | 6 +- autotests/testolmaccount.cpp | 6 +- autotests/testolmsession.cpp | 2 +- lib/connection.cpp | 12 +- lib/connection.h | 8 +- lib/crypto/e2ee.h | 7 +- lib/crypto/errors.cpp | 21 ---- lib/crypto/errors.h | 31 ----- lib/crypto/message.cpp | 42 ------- lib/crypto/message.h | 46 ------- lib/crypto/qolmaccount.cpp | 23 ++-- lib/crypto/qolmaccount.h | 15 +-- lib/crypto/qolmerrors.cpp | 21 ++++ lib/crypto/qolmerrors.h | 31 +++++ lib/crypto/qolminboundsession.cpp | 8 +- lib/crypto/qolminboundsession.h | 8 +- lib/crypto/qolmmessage.cpp | 42 +++++++ lib/crypto/qolmmessage.h | 46 +++++++ lib/crypto/qolmoutboundsession.cpp | 14 +-- lib/crypto/qolmoutboundsession.h | 12 +- lib/crypto/qolmsession.cpp | 246 ++++++++++++++++++++++++++++++++++--- lib/crypto/qolmsession.cpp.back | 29 +++++ lib/crypto/qolmsession.h | 92 +++++++++----- lib/crypto/qolmsession.h.back | 49 ++++++++ lib/crypto/qolmutils.cpp | 26 ++++ lib/crypto/qolmutils.h | 17 +++ lib/crypto/session.cpp | 242 ------------------------------------ lib/crypto/session.h | 77 ------------ lib/crypto/utils.cpp | 26 ---- lib/crypto/utils.h | 15 --- lib/encryptionmanager.cpp | 70 ++++++----- lib/encryptionmanager.h | 7 +- lib/room.cpp | 25 ++-- 36 files changed, 690 insertions(+), 705 deletions(-) delete mode 160000 3rdparty/libQtOlm delete mode 100644 lib/crypto/errors.cpp delete mode 100644 lib/crypto/errors.h delete mode 100644 lib/crypto/message.cpp delete mode 100644 lib/crypto/message.h create mode 100644 lib/crypto/qolmerrors.cpp create mode 100644 lib/crypto/qolmerrors.h create mode 100644 lib/crypto/qolmmessage.cpp create mode 100644 lib/crypto/qolmmessage.h create mode 100644 lib/crypto/qolmsession.cpp.back create mode 100644 lib/crypto/qolmsession.h.back create mode 100644 lib/crypto/qolmutils.cpp create mode 100644 lib/crypto/qolmutils.h delete mode 100644 lib/crypto/session.cpp delete mode 100644 lib/crypto/session.h delete mode 100644 lib/crypto/utils.cpp delete mode 100644 lib/crypto/utils.h (limited to 'lib/encryptionmanager.cpp') 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 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(-) (limited to 'lib/encryptionmanager.cpp') 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 (limited to 'lib/encryptionmanager.cpp') 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 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(-) (limited to 'lib/encryptionmanager.cpp') 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(-) (limited to 'lib/encryptionmanager.cpp') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 4eed1980..8129ae5b 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -9,7 +9,7 @@ using namespace Quotient; -void TestOlmAccount::pickleUnpickedTest() +void TestOlmAccount::pickleUnpickledTest() { QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); olmAccount.createNewAccount(); @@ -73,7 +73,7 @@ void TestOlmAccount::deviceKeys() { // copied from mtxclient DeviceKeys device1; - device1.userId = "@alice:example.com"; + device1.userId = "@alice:example.com"; device1.deviceId = "JLAFKJWSCS"; device1.keys = {{"curve25519:JLAFKJWSCS", "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"}, {"ed25519:JLAFKJWSCS", "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"}}; diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index b6b9cae4..97fbca18 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -15,7 +15,7 @@ class TestOlmAccount : public QObject private Q_SLOTS: - void pickleUnpickedTest(); + void pickleUnpickledTest(); void identityKeysValid(); void signatureValid(); void oneTimeKeysValid(); diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 719add1d..3c3103a7 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -69,7 +69,7 @@ public: UploadKeysJob* uploadOneTimeKeysJob = nullptr; QueryKeysJob* queryKeysJob = nullptr; - QScopedPointer olmAccount; + std::unique_ptr olmAccount; float signedKeysProportion; float oneTimeKeyThreshold; diff --git a/lib/room.cpp b/lib/room.cpp index a8a7fe0c..1a7a9911 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -66,8 +66,8 @@ #ifdef Quotient_E2EE_ENABLED #include "crypto/qolmaccount.h" -# include "crypto/qolmerrors.h" -# include "crypto/qolminboundsession.h" +#include "crypto/qolmerrors.h" +#include "crypto/qolminboundsession.h" #endif // Quotient_E2EE_ENABLED using namespace Quotient; -- cgit v1.2.3 From 5c50f0ccadedbcb07d51dbac9b1d59c03a26af2f Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 15 Feb 2021 18:13:27 +0100 Subject: fix typo --- autotests/testolmaccount.cpp | 1 - lib/encryptionmanager.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/encryptionmanager.cpp') 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 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(-) (limited to 'lib/encryptionmanager.cpp') 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 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(+) (limited to 'lib/encryptionmanager.cpp') 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 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(-) (limited to 'lib/encryptionmanager.cpp') 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 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(-) (limited to 'lib/encryptionmanager.cpp') 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(-) (limited to 'lib/encryptionmanager.cpp') 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 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(-) (limited to 'lib/encryptionmanager.cpp') 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 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(-) (limited to 'lib/encryptionmanager.cpp') 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 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(-) (limited to 'lib/encryptionmanager.cpp') 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 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(-) (limited to 'lib/encryptionmanager.cpp') 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(-) (limited to 'lib/encryptionmanager.cpp') 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 dcc4556a761f96ae6c71115bf6297feca32581bf Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 27 Nov 2021 01:58:02 +0100 Subject: More improvements --- autotests/testolmsession.cpp | 16 ++++++++-------- lib/connection.cpp | 10 +++++----- lib/crypto/qolmaccount.cpp | 14 +++++++------- lib/crypto/qolmaccount.h | 10 ++++++---- lib/crypto/qolmsession.cpp | 10 +++++----- lib/crypto/qolmsession.h | 3 +-- lib/encryptionmanager.cpp | 12 +++++++----- lib/room.cpp | 4 ++-- 8 files changed, 41 insertions(+), 38 deletions(-) (limited to 'lib/encryptionmanager.cpp') diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index dba78277..750b804e 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -7,7 +7,7 @@ using namespace Quotient; -std::pair, std::unique_ptr> createSessionPair() +std::pair createSessionPair() { QByteArray pickledAccountA("eOBXIKivUT6YYowRH031BNv7zNmzqM5B7CpXdyeaPvala5mt7/OeqrG1qVA7vA1SYloFyvJPIy0QNkD3j1HiPl5vtZHN53rtfZ9exXDok03zjmssqn4IJsqcA7Fbo1FZeKafG0NFcWwCPTdmcV7REqxjqGm3I4K8MQFa45AdTGSUu2C12cWeOcbSMlcINiMral+Uyah1sgPmLJ18h1qcnskXUXQvpffZ5DiUw1Iz5zxnwOQF1GVyowPJD7Zdugvj75RQnDxAn6CzyvrY2k2CuedwqDC3fIXM2xdUNWttW4nC2g4InpBhCVvNwhZYxlUb5BUEjmPI2AB3dAL5ry6o9MFncmbN6x5x"); QByteArray pickledAccountB("eModTvoFi9oOIkax4j4nuxw9Tcl/J8mOmUctUWI68Q89HSaaPTqR+tdlKQ85v2GOs5NlZCp7EuycypN9GQ4fFbHUCrS7nspa3GFBWsR8PnM8+wez5PWmfFZLg3drOvT0jbMjpDx0MjGYClHBqcrEpKx9oFaIRGBaX6HXzT4lRaWSJkXxuX92q8iGNrLn96PuAWFNcD+2JXpPcNFntslwLUNgqzpZ04aIFYwL80GmzyOgq3Bz1GO6u3TgCQEAmTIYN2QkO0MQeuSfe7UoMumhlAJ6R8GPcdSSPtmXNk4tdyzzlgpVq1hm7ZLKto+g8/5Aq3PvnvA8wCqno2+Pi1duK1pZFTIlActr"); @@ -20,7 +20,7 @@ std::pair, std::unique_ptr> createSess const QByteArray oneTimeKeyA("WzsbsjD85iB1R32iWxfJdwkgmdz29ClMbJSJziECYwk"); const QByteArray identityKeyB("q/YhJtog/5VHCAS9rM9uUf6AaFk1yPe4GYuyUOXyQCg"); const QByteArray oneTimeKeyB("oWvzryma+B2onYjo3hM6A3Mgo/Yepm8HvgSvwZMTnjQ"); - auto outbound = std::get>(accountA + auto outbound = std::get(accountA .createOutboundSession(identityKeyB, oneTimeKeyB)); const auto preKey = outbound->encrypt(""); // Payload does not matter for PreKey @@ -29,8 +29,8 @@ std::pair, std::unique_ptr> createSess // We can't call QFail here because it's an helper function returning a value throw "Wrong first message type received, can't create session"; } - auto inbound = std::get>(accountB.createInboundSession(preKey)); - return std::make_pair, std::unique_ptr>(std::move(inbound), std::move(outbound)); + auto inbound = std::get(accountB.createInboundSession(preKey)); + return std::make_pair(std::move(inbound), std::move(outbound)); } void TestOlmSession::olmOutboundSessionCreation() @@ -56,17 +56,17 @@ void TestOlmSession::olmEncryptDecrypt() void TestOlmSession::correctSessionOrdering() { // n0W5IJ2ZmaI9FxKRj/wohUQ6WEU0SfoKsgKKHsr4VbM - auto session1 = std::get>(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGvlaV6t/0ihD2/0QGckDIvbmE1aV+PxB0zUtHXh99bI/60N+PWkCLA84jEY4sz3d45ui/TVoFGLDHlymKxvlj7XngXrbtlxSkVntsPzDiNpKEXCa26N2ubKpQ0fbjrV5gbBTYWfU04DXHPXFDTksxpNALYt/h0eVMVhf6hB0ZzpLBsOG0mpwkLufwub0CuDEDGGmRddz3TcNCLq5NnI8R9udDWvHAkTS1UTbHuIf/y6cZg875nJyXpAvd8/XhL8TOo8ot2sE1fElBa4vrH/m9rBQMC1GPkhLBIizmY44C+Sq9PQRnF+uCZ", Unencrypted{})); + auto session1 = std::get(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGvlaV6t/0ihD2/0QGckDIvbmE1aV+PxB0zUtHXh99bI/60N+PWkCLA84jEY4sz3d45ui/TVoFGLDHlymKxvlj7XngXrbtlxSkVntsPzDiNpKEXCa26N2ubKpQ0fbjrV5gbBTYWfU04DXHPXFDTksxpNALYt/h0eVMVhf6hB0ZzpLBsOG0mpwkLufwub0CuDEDGGmRddz3TcNCLq5NnI8R9udDWvHAkTS1UTbHuIf/y6cZg875nJyXpAvd8/XhL8TOo8ot2sE1fElBa4vrH/m9rBQMC1GPkhLBIizmY44C+Sq9PQRnF+uCZ", Unencrypted{})); // +9pHJhP3K4E5/2m8PYBPLh8pS9CJodwUOh8yz3mnmw0 - auto session2 = std::get>(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UFD+q37/WlfTAzQsSjCdD07FcErZ4siEy5vpiB+pyO8i53ptZvb2qRvqNKFzPaXuu33PS2PBTmmnR+kJt+DgDNqWadyaj/WqEAejc7ALqSs5GuhbZtpoLe+lRSRK0rwVX3gzz4qrl8pm0pD5pSZAUWRXDRlieGWMclz68VUvnSaQH7ElTo4S634CJk+xQfFFCD26v0yONPSN6rwouS1cWPuG5jTlnV8vCFVTU2+lduKh54Ko6FUJ/ei4xR8Nk2duBGSc/TdllX9e2lDYHSUkWoD4ti5xsFioB8Blus7JK9BZfcmRmdlxIOD", Unencrypted {})); + auto session2 = std::get(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UFD+q37/WlfTAzQsSjCdD07FcErZ4siEy5vpiB+pyO8i53ptZvb2qRvqNKFzPaXuu33PS2PBTmmnR+kJt+DgDNqWadyaj/WqEAejc7ALqSs5GuhbZtpoLe+lRSRK0rwVX3gzz4qrl8pm0pD5pSZAUWRXDRlieGWMclz68VUvnSaQH7ElTo4S634CJk+xQfFFCD26v0yONPSN6rwouS1cWPuG5jTlnV8vCFVTU2+lduKh54Ko6FUJ/ei4xR8Nk2duBGSc/TdllX9e2lDYHSUkWoD4ti5xsFioB8Blus7JK9BZfcmRmdlxIOD", Unencrypted {})); // MC7n8hX1l7WlC2/WJGHZinMocgiBZa4vwGAOredb/ME - auto session3 = std::get>(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGNk2TmVDJ95K0Nywf24FNklNVtXtFDiFPHFwNSmCbHNCp3hsGtZlt0AHUkMmL48XklLqzwtVk5/v2RRmSKR5LqYdIakrtuK/fY0ENhBZIbI1sRetaJ2KMbY9l6rCJNfFg8VhpZ4KTVvEZVuP9g/eZkCnP5NxzXiBRF6nfY3O/zhcKxa3acIqs6BMhyLsfuJ80t+hQ1HvVyuhBerGujdSDzV9tJ9SPidOwfYATk81LVF9hTmnI0KaZa7qCtFzhG0dU/Z3hIWH9HOaw1aSB/IPmughbwdJOwERyhuo3YHoznlQnJ7X252BlI", Unencrypted{})); + auto session3 = std::get(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGNk2TmVDJ95K0Nywf24FNklNVtXtFDiFPHFwNSmCbHNCp3hsGtZlt0AHUkMmL48XklLqzwtVk5/v2RRmSKR5LqYdIakrtuK/fY0ENhBZIbI1sRetaJ2KMbY9l6rCJNfFg8VhpZ4KTVvEZVuP9g/eZkCnP5NxzXiBRF6nfY3O/zhcKxa3acIqs6BMhyLsfuJ80t+hQ1HvVyuhBerGujdSDzV9tJ9SPidOwfYATk81LVF9hTmnI0KaZa7qCtFzhG0dU/Z3hIWH9HOaw1aSB/IPmughbwdJOwERyhuo3YHoznlQnJ7X252BlI", Unencrypted{})); const auto session1Id = session1->sessionId(); const auto session2Id = session2->sessionId(); const auto session3Id = session3->sessionId(); - std::vector> sessionList; + std::vector sessionList; sessionList.push_back(std::move(session1)); sessionList.push_back(std::move(session2)); sessionList.push_back(std::move(session3)); diff --git a/lib/connection.cpp b/lib/connection.cpp index df9ff445..a7af1477 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -509,7 +509,7 @@ void Connection::Private::completeSetup(const QString& mxId) // create new account and save unpickle data olmAccount->createNewAccount(); auto job = q->callApi(olmAccount->deviceKeys()); - connect(job, &BaseJob::failure, q, [=]{ + connect(job, &BaseJob::failure, q, [job]{ qCWarning(E2EE) << "Failed to upload device keys:" << job->errorString(); }); } else { @@ -677,10 +677,10 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) auto keys = d->olmAccount->oneTimeKeys(); auto job = d->olmAccount->createUploadKeyRequest(keys); run(job, ForegroundRequest); - connect(job, &BaseJob::success, this, [=](){ + connect(job, &BaseJob::success, this, [this](){ d->olmAccount->markKeysAsPublished(); }); - connect(job, &BaseJob::result, this, [=](){ + connect(job, &BaseJob::result, this, [this](){ d->isUploadingKeys = false; }); } @@ -1903,7 +1903,7 @@ void Connection::Private::loadOutdatedUserDevices() } auto queryKeysJob = q->callApi(users); currentQueryKeysJob = queryKeysJob; - connect(queryKeysJob, &BaseJob::success, q, [=](){ + connect(queryKeysJob, &BaseJob::success, q, [this, queryKeysJob](){ currentQueryKeysJob = nullptr; const auto data = queryKeysJob->deviceKeys(); for(const auto &[user, keys] : asKeyValueRange(data)) { @@ -2024,7 +2024,7 @@ void Connection::Private::loadDevicesList() deviceKeys = fromJson>>(json["devices_list"].toObject()); auto oldToken = json["sync_token"].toString(); auto changesJob = q->callApi(oldToken, q->nextBatchToken()); - connect(changesJob, &BaseJob::success, q, [=](){ + connect(changesJob, &BaseJob::success, q, [this, changesJob](){ bool hasNewOutdatedUser = false; for(const auto &user : changesJob->changed()) { outdatedUsers += user; diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 1de8a0dc..5c9f5db4 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -179,13 +179,13 @@ OneTimeKeys QOlmAccount::oneTimeKeys() const const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); OneTimeKeys oneTimeKeys; - for (const QJsonValue &key1 : json.keys()) { - auto oneTimeKeyObject = json[key1.toString()].toObject(); + for (const QString& key1 : json.keys()) { + auto oneTimeKeyObject = json[key1].toObject(); auto keyMap = QMap(); for (const QString &key2 : oneTimeKeyObject.keys()) { keyMap[key2] = oneTimeKeyObject[key2].toString(); } - oneTimeKeys.keys[key1.toString()] = keyMap; + oneTimeKeys.keys[key1] = keyMap; } return oneTimeKeys; } @@ -215,7 +215,7 @@ QByteArray QOlmAccount::signOneTimeKey(const QString &key) const return sign(j.toJson(QJsonDocument::Compact)); } -std::optional QOlmAccount::removeOneTimeKeys(const std::unique_ptr &session) const +std::optional QOlmAccount::removeOneTimeKeys(const QOlmSessionPtr &session) const { const auto error = olm_remove_one_time_keys(m_account, session->raw()); @@ -266,19 +266,19 @@ UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKey return new UploadKeysJob(keys, oneTimeKeysSigned); } -std::variant, QOlmError> QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) +std::variant QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSession(this, preKeyMessage); } -std::variant, QOlmError> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) +std::variant QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); } -std::variant, QOlmError> QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) +std::variant QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) { return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); } diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index 1f94ab2b..dd461e8b 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -19,6 +19,8 @@ namespace Quotient { class QOlmSession; class Connection; +using QOlmSessionPtr = std::unique_ptr; + //! An olm account manages all cryptographic keys used on a device. //! \code{.cpp} //! const auto olmAccount = new QOlmAccount(this); @@ -77,22 +79,22 @@ public: DeviceKeys deviceKeys() const; //! Remove the one time key used to create the supplied session. - [[nodiscard]] std::optional removeOneTimeKeys(const std::unique_ptr &session) const; + [[nodiscard]] std::optional removeOneTimeKeys(const QOlmSessionPtr &session) const; //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. //! //! \param message An Olm pre-key message that was encrypted for this account. - std::variant, QOlmError> createInboundSession(const QOlmMessage &preKeyMessage); + std::variant createInboundSession(const QOlmMessage &preKeyMessage); //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. //! //! \param theirIdentityKey - The identity key of the Olm account that //! encrypted this Olm message. - std::variant, QOlmError> createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage); + std::variant createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage); //! Creates an outbound session for sending messages to a specific /// identity and one time key. - std::variant, QOlmError> createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); + std::variant createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); void markKeysAsPublished(); diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp index a327a643..a0386613 100644 --- a/lib/crypto/qolmsession.cpp +++ b/lib/crypto/qolmsession.cpp @@ -27,7 +27,7 @@ OlmSession* QOlmSession::create() return olm_session(new uint8_t[olm_session_size()]); } -std::variant, QOlmError> QOlmSession::createInbound(QOlmAccount *account, const QOlmMessage &preKeyMessage, bool from, const QString &theirIdentityKey) +std::variant QOlmSession::createInbound(QOlmAccount *account, const QOlmMessage &preKeyMessage, bool from, const QString &theirIdentityKey) { if (preKeyMessage.type() != QOlmMessage::PreKey) { qCCritical(E2EE) << "The message is not a pre-key in when creating inbound session" << BadMessageFormat; @@ -53,17 +53,17 @@ std::variant, QOlmError> QOlmSession::createInbound return std::make_unique(olmSession); } -std::variant, QOlmError> QOlmSession::createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage) +std::variant QOlmSession::createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage) { return createInbound(account, preKeyMessage); } -std::variant, QOlmError> QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) +std::variant QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) { return createInbound(account, preKeyMessage, true, theirIdentityKey); } -std::variant, QOlmError> QOlmSession::createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey) +std::variant QOlmSession::createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey) { auto *olmOutboundSession = create(); const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); @@ -105,7 +105,7 @@ std::variant QOlmSession::pickle(const PicklingMode &mode return pickledBuf; } -std::variant, QOlmError> QOlmSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +std::variant QOlmSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) { QByteArray pickledBuf = pickled; auto *olmSession = create(); diff --git a/lib/crypto/qolmsession.h b/lib/crypto/qolmsession.h index 959c77d0..7a040b3d 100644 --- a/lib/crypto/qolmsession.h +++ b/lib/crypto/qolmsession.h @@ -74,7 +74,6 @@ private: OlmSession* m_session; }; - -//using QOlmSessionPtr = std::unique_ptr; +using QOlmSessionPtr = std::unique_ptr; } //namespace Quotient diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 84282dbf..ed6ad20b 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -38,7 +38,7 @@ public: EncryptionManager* q; // A map from senderKey to InboundSession - UnorderedMap> sessions; // TODO: cache + UnorderedMap sessions; void updateDeviceKeys( const QHash>& deviceKeys) @@ -76,7 +76,7 @@ public: qCWarning(E2EE) << "Failed to unpickle olm session"; continue; } - sessions[senderKey] = std::move(std::get>(sessionResult)); + sessions[senderKey] = std::move(std::get(sessionResult)); } } void saveSessions() { @@ -135,9 +135,11 @@ public: qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); return {}; } - std::unique_ptr newSession = std::move(std::get>(newSessionResult)); - // TODO Error handling? - olmAccount->removeOneTimeKeys(newSession); + auto newSession = std::move(std::get(newSessionResult)); + auto error = olmAccount->removeOneTimeKeys(newSession); + if (error) { + qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); + } const auto result = newSession->decrypt(message); sessions[senderKey] = std::move(newSession); saveSessions(); diff --git a/lib/room.cpp b/lib/room.cpp index 07ffd0cd..e4fe2fb8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -514,10 +514,10 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) return this == r; // loadedRoomState fires only once per room }); #ifdef Quotient_E2EE_ENABLED - connectSingleShot(this, &Room::encryption, this, [=](){ + connectSingleShot(this, &Room::encryption, this, [this, connection](){ connection->encryptionUpdate(this); }); - connect(this, &Room::userAdded, this, [=](){ + connect(this, &Room::userAdded, this, [this, connection](){ if(usesEncryption()) { connection->encryptionUpdate(this); } -- cgit v1.2.3 From 9217026e46d7ac0d761cc5206d7ef00978558c47 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:58:38 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- autotests/testolmsession.cpp | 2 +- lib/connection.cpp | 3 +-- lib/encryptionmanager.cpp | 2 +- lib/events/encryptedevent.cpp | 12 +++++------- lib/jobs/downloadfilejob.cpp | 2 +- 5 files changed, 9 insertions(+), 12 deletions(-) (limited to 'lib/encryptionmanager.cpp') 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 (limited to 'lib/encryptionmanager.cpp') 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 (limited to 'lib/encryptionmanager.cpp') 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 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(-) (limited to 'lib/encryptionmanager.cpp') 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 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(-) (limited to 'lib/encryptionmanager.cpp') 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 7b5edb737522b03d4f697e0e09f1771ad5edef89 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 7 Feb 2022 21:48:07 +0100 Subject: Remove encryptionmanager and various fixes --- CMakeLists.txt | 2 +- autotests/testolmaccount.cpp | 2 +- lib/connection.cpp | 108 ++++++++++++++++++++++++----- lib/connection.h | 2 - lib/e2ee/qolminboundsession.cpp | 2 + lib/encryptionmanager.cpp | 149 ---------------------------------------- lib/encryptionmanager.h | 33 --------- lib/events/encryptedfile.cpp | 27 ++++++++ lib/events/encryptedfile.h | 6 ++ lib/jobs/downloadfilejob.cpp | 5 +- lib/mxcreply.cpp | 3 +- 11 files changed, 130 insertions(+), 209 deletions(-) delete mode 100644 lib/encryptionmanager.cpp delete mode 100644 lib/encryptionmanager.h create mode 100644 lib/events/encryptedfile.cpp (limited to 'lib/encryptionmanager.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