From 3849a2466208a741165385f20150bddad59d2b8c Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 22 Jan 2021 23:36:47 +0100 Subject: Start implementing Qt olm binding --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index aa3b9c98..b4d4ef8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,6 +171,7 @@ list(APPEND lib_SRCS lib/jobs/syncjob.cpp lib/jobs/mediathumbnailjob.cpp lib/jobs/downloadfilejob.cpp + lib/olm/qolmaccount.cpp ) # Configure API files generation @@ -317,7 +318,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC $ ) if (${PROJECT_NAME}_ENABLE_E2EE) - target_link_libraries(${PROJECT_NAME} QtOlm) + target_link_libraries(${PROJECT_NAME} Olm::Olm QtOlm) set(FIND_DEPS "find_dependency(QtOlm)") # For QuotientConfig.cmake.in endif() target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) -- cgit v1.2.3 From 8706c055e69b01097b702403aaa0d222e5ab0d29 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 24 Jan 2021 01:45:43 +0100 Subject: Implement outboundsession --- CMakeLists.txt | 4 ++ autotests/CMakeLists.txt | 1 + autotests/testgroupsession.cpp | 39 +++++++++++++ autotests/testgroupsession.h | 15 +++++ autotests/testolmaccount.cpp | 2 + lib/olm/errors.h | 2 + lib/olm/qolmaccount.cpp | 27 +-------- lib/olm/qolminboundsession.cpp | 51 +++++++---------- lib/olm/qolminboundsession.h | 9 +-- lib/olm/qolmoutboundsession.cpp | 121 ++++++++++++++++++++++++++++++++++++++++ lib/olm/qolmoutboundsession.h | 47 ++++++++++++++++ lib/olm/utils.cpp | 22 ++++++++ lib/olm/utils.h | 13 +++++ 13 files changed, 291 insertions(+), 62 deletions(-) create mode 100644 autotests/testgroupsession.cpp create mode 100644 autotests/testgroupsession.h create mode 100644 lib/olm/qolmoutboundsession.cpp create mode 100644 lib/olm/qolmoutboundsession.h create mode 100644 lib/olm/utils.cpp create mode 100644 lib/olm/utils.h (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index b4d4ef8b..18a7b622 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,10 @@ list(APPEND lib_SRCS lib/jobs/mediathumbnailjob.cpp lib/jobs/downloadfilejob.cpp lib/olm/qolmaccount.cpp + lib/olm/qolminboundsession.cpp + lib/olm/qolmoutboundsession.cpp + lib/olm/utils.cpp + lib/olm/errors.cpp ) # Configure API files generation diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 07c22ad6..31cdb446 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -13,3 +13,4 @@ endfunction() quotient_add_test(NAME callcandidateseventtest) quotient_add_test(NAME testolmaccount) +quotient_add_test(NAME testgroupsession) diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp new file mode 100644 index 00000000..02892366 --- /dev/null +++ b/autotests/testgroupsession.cpp @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "testgroupsession.h" +#include "olm/qolminboundsession.h" +#include "olm/qolmoutboundsession.h" +#include "olm/utils.h" + +using namespace Quotient; + +void TestOlmSession::groupSessionPicklingValid() +{ + auto ogs = std::get(QOlmOutboundGroupSession::create()); + const auto ogsId = std::get(ogs.sessionId()); + QVERIFY(QByteArray::fromBase64Encoding(ogsId).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + QCOMPARE(0, ogs.sessionMessageIndex()); + + auto ogsPickled = std::get(ogs.pickle(Unencrypted {})); + ogs = std::get(QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {})); + QCOMPARE(ogsId, std::get(ogs.sessionId())); + + qDebug() << std::get(ogs.sessionKey()); + auto igs = std::get(QOlmInboundGroupSession::create(std::get(ogs.sessionKey()))); + const auto igsId = std::get(igs.sessionId()); + // ID is valid base64? + QVERIFY(QByteArray::fromBase64Encoding(igsId).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + + //// no messages have been sent yet + QCOMPARE(0, igs.firstKnownIndex()); + + auto igsPickled = std::get(igs.pickle(Unencrypted {})); + igs = std::get(QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {})); + QCOMPARE(igsId, std::get(igs.sessionId())); +} + +QTEST_MAIN(TestOlmSession) +#endif diff --git a/autotests/testgroupsession.h b/autotests/testgroupsession.h new file mode 100644 index 00000000..28ebf4c9 --- /dev/null +++ b/autotests/testgroupsession.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include + +class TestOlmSession : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void groupSessionPicklingValid(); +}; +#endif diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 549f07ea..45a7e3a5 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -6,6 +6,8 @@ #include "testolmaccount.h" #include "olm/qolmaccount.h" +using namespace Quotient; + void TestOlmAccount::pickleUnpickedTest() { auto olmAccount = QOlmAccount::create().value(); diff --git a/lib/olm/errors.h b/lib/olm/errors.h index fc2ae2e9..3dc4c796 100644 --- a/lib/olm/errors.h +++ b/lib/olm/errors.h @@ -24,6 +24,8 @@ enum OlmError Unknown, }; +OlmError fromString(const std::string &error_raw); + } //namespace Quotient #endif diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp index bde9b712..89d82832 100644 --- a/lib/olm/qolmaccount.cpp +++ b/lib/olm/qolmaccount.cpp @@ -4,6 +4,7 @@ #ifdef Quotient_E2EE_ENABLED #include "qolmaccount.h" +#include "olm/utils.h" #include #include #include @@ -24,37 +25,11 @@ std::optional> OneTimeKeys::get(QString keyType) const return keys[keyType]; } -// Convert PicklingMode to key -QByteArray toKey(const PicklingMode &mode) -{ - if (std::holds_alternative(mode)) { - return ""; - } - return std::get(mode).key; -} - bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) { return lhs.curve25519 == rhs.curve25519 &&& lhs.ed25519 == rhs.ed25519; } -// TODO use impl from errors.cpp -OlmError fromString(const std::string &error_raw) { - if (error_raw.compare("BAD_ACCOUNT_KEY")) { - return OlmError::BadAccountKey; - } else if (error_raw.compare("BAD_MESSAGE_KEY_ID")) { - return OlmError::BadMessageKeyId; - } else if (error_raw.compare("INVALID_BASE64")) { - return OlmError::InvalidBase64; - } else if (error_raw.compare("NOT_ENOUGH_RANDOM")) { - return OlmError::NotEnoughRandom; - } else if (error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { - return OlmError::OutputBufferTooSmall; - } else { - return OlmError::Unknown; - } -} - // Conver olm error to enum OlmError lastError(OlmAccount *account) { const std::string error_raw = olm_account_last_error(account); diff --git a/lib/olm/qolminboundsession.cpp b/lib/olm/qolminboundsession.cpp index 62de138f..37dd60f8 100644 --- a/lib/olm/qolminboundsession.cpp +++ b/lib/olm/qolminboundsession.cpp @@ -3,64 +3,51 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "olm/qolminboundsession.h" -#include -#include - +#include +#include using namespace Quotient; -// TODO move to errors.cpp -OlmError fromString(const std::string &error_raw) { - if (error_raw.compare("BAD_ACCOUNT_KEY")) { - return OlmError::BadAccountKey; - } else if (error_raw.compare("BAD_MESSAGE_KEY_ID")) { - return OlmError::BadMessageKeyId; - } else if (error_raw.compare("INVALID_BASE64")) { - return OlmError::InvalidBase64; - } else if (error_raw.compare("NOT_ENOUGH_RANDOM")) { - return OlmError::NotEnoughRandom; - } else if (error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { - return OlmError::OutputBufferTooSmall; - } else { - return OlmError::Unknown; - } -} - OlmError lastError(OlmInboundGroupSession *session) { const std::string error_raw = olm_inbound_group_session_last_error(session); + std::cout << error_raw; return fromString(error_raw); } -QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session, const QByteArray &buffer) +QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session, QByteArray buffer) : m_groupSession(session) , m_buffer(buffer) { } -std::variant QOlmInboundGroupSession::create(const QString &key) +QOlmInboundGroupSession::~QOlmInboundGroupSession() { - auto olmInboundGroupSessionBuf = QByteArray(olm_inbound_group_session_size(), '0'); + olm_clear_inbound_group_session(m_groupSession); +} +std::variant QOlmInboundGroupSession::create(const QByteArray &key) +{ + QByteArray olmInboundGroupSessionBuf(olm_inbound_group_session_size(), '0'); const auto olmInboundGroupSession = olm_inbound_group_session(olmInboundGroupSessionBuf.data()); - QByteArray keyBuf = key.toUtf8(); + const auto temp = key; const auto error = olm_init_inbound_group_session(olmInboundGroupSession, - reinterpret_cast(keyBuf.data()), keyBuf.size()); + reinterpret_cast(temp.data()), temp.size()); if (error == olm_error()) { return lastError(olmInboundGroupSession); } - return QOlmInboundGroupSession(olmInboundGroupSession, olmInboundGroupSessionBuf); + return QOlmInboundGroupSession(olmInboundGroupSession, std::move(olmInboundGroupSessionBuf)); } -std::variant QOlmInboundGroupSession::import(const QString &key) +std::variant QOlmInboundGroupSession::import(const QByteArray &key) { - auto olmInboundGroupSessionBuf = QByteArray(olm_inbound_group_session_size(), '0'); + QByteArray olmInboundGroupSessionBuf(olm_inbound_group_session_size(), '0'); const auto olmInboundGroupSession = olm_inbound_group_session(olmInboundGroupSessionBuf.data()); - QByteArray keyBuf = key.toUtf8(); + QByteArray keyBuf = key; const auto error = olm_import_inbound_group_session(olmInboundGroupSession, reinterpret_cast(keyBuf.data()), keyBuf.size()); @@ -68,7 +55,7 @@ std::variant QOlmInboundGroupSession::import( return lastError(olmInboundGroupSession); } - return QOlmInboundGroupSession(olmInboundGroupSession, olmInboundGroupSessionBuf); + return QOlmInboundGroupSession(olmInboundGroupSession, std::move(olmInboundGroupSessionBuf)); } QByteArray toKey(const PicklingMode &mode) @@ -91,7 +78,7 @@ std::variant QOlmInboundGroupSession::pickle(const Picklin return pickledBuf; } -std::variant QOlmInboundGroupSession::unpicke(QByteArray &picked, const PicklingMode &mode) +std::variant QOlmInboundGroupSession::unpickle(QByteArray &picked, const PicklingMode &mode) { QByteArray groupSessionBuf(olm_inbound_group_session_size(), '0'); auto groupSession = olm_inbound_group_session(groupSessionBuf.data()); @@ -100,7 +87,7 @@ std::variant QOlmInboundGroupSession::unpicke if (error == olm_error()) { return lastError(groupSession); } - return QOlmInboundGroupSession(groupSession, groupSessionBuf); + return QOlmInboundGroupSession(groupSession, std::move(groupSessionBuf)); } std::variant, OlmError> QOlmInboundGroupSession::decrypt(QString &message) diff --git a/lib/olm/qolminboundsession.h b/lib/olm/qolminboundsession.h index c75b7285..82802520 100644 --- a/lib/olm/qolminboundsession.h +++ b/lib/olm/qolminboundsession.h @@ -17,15 +17,16 @@ namespace Quotient { struct QOlmInboundGroupSession { public: + ~QOlmInboundGroupSession(); //! Creates a new instance of `OlmInboundGroupSession`. - static std::variant create(const QString &key); + static std::variant create(const QByteArray &key); //! Import an inbound group session, from a previous export. - static std::variant import(const QString &key); + static std::variant import(const QByteArray &key); //! Serialises an `OlmInboundGroupSession` to encrypted Base64. std::variant pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. - static std::variant unpicke(QByteArray &picked, const PicklingMode &mode); + static std::variant unpickle(QByteArray &picked, const PicklingMode &mode); //! Decrypts ciphertext received for this group session. std::variant, OlmError> decrypt(QString &message); //! Export the base64-encoded ratchet key for this session, at the given index, @@ -37,7 +38,7 @@ public: std::variant sessionId() const; bool isVerified() const; private: - QOlmInboundGroupSession(OlmInboundGroupSession *session, const QByteArray &buffer); + QOlmInboundGroupSession(OlmInboundGroupSession *session, QByteArray buffer); OlmInboundGroupSession *m_groupSession; QByteArray m_buffer; }; diff --git a/lib/olm/qolmoutboundsession.cpp b/lib/olm/qolmoutboundsession.cpp new file mode 100644 index 00000000..8a6b966b --- /dev/null +++ b/lib/olm/qolmoutboundsession.cpp @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later +// +#include "olm/qolmoutboundsession.h" +#include "olm/utils.h" + +using namespace Quotient; + +OlmError lastError(OlmOutboundGroupSession *session) { + const std::string error_raw = olm_outbound_group_session_last_error(session); + + return fromString(error_raw); +} + +QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session, const QByteArray &buffer) + : m_groupSession(session) + , m_buffer(buffer) +{ +} + +QOlmOutboundGroupSession::~QOlmOutboundGroupSession() +{ + olm_clear_outbound_group_session(m_groupSession); +} + +std::variant QOlmOutboundGroupSession::create() +{ + QByteArray sessionBuffer(olm_outbound_group_session_size(), '0'); + auto *olmOutboundGroupSession = olm_outbound_group_session(sessionBuffer.data()); + const auto randomLen = olm_init_outbound_group_session_random_length(olmOutboundGroupSession); + QByteArray randomBuf = getRandom(randomLen); + + const auto error = olm_init_outbound_group_session(olmOutboundGroupSession, + reinterpret_cast(randomBuf.data()), randomBuf.length()); + + if (error == olm_error()) { + return lastError(olmOutboundGroupSession); + } + + randomBuf.clear(); + + return QOlmOutboundGroupSession(olmOutboundGroupSession, sessionBuffer); +} + +std::variant QOlmOutboundGroupSession::pickle(const PicklingMode &mode) +{ + QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); + QByteArray key = toKey(mode); + const auto error = olm_pickle_outbound_group_session(m_groupSession, key.data(), key.length(), + pickledBuf.data(), pickledBuf.length()); + + if (error == olm_error()) { + return lastError(m_groupSession); + } + + key.clear(); + + return pickledBuf; +} + + +std::variant QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +{ + QByteArray pickledBuf = pickled; + QByteArray olmOutboundGroupSessionBuf(olm_outbound_group_session_size(), '0'); + QByteArray key = toKey(mode); + auto olmOutboundGroupSession = olm_outbound_group_session(reinterpret_cast(olmOutboundGroupSessionBuf.data())); + const auto error = olm_unpickle_outbound_group_session(olmOutboundGroupSession, key.data(), key.length(), + pickled.data(), pickled.length()); + if (error == olm_error()) { + return lastError(olmOutboundGroupSession); + } + + key.clear(); + return QOlmOutboundGroupSession(olmOutboundGroupSession, olmOutboundGroupSessionBuf); +} + +std::variant QOlmOutboundGroupSession::encrypt(QString &plaintext) +{ + QByteArray plaintextBuf = plaintext.toUtf8(); + const auto messageMaxLen = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); + QByteArray messageBuf(messageMaxLen, '0'); + const auto error = olm_group_encrypt(m_groupSession, reinterpret_cast(plaintextBuf.data()), + plaintextBuf.length(), reinterpret_cast(messageBuf.data()), messageBuf.length()); + + if (error == olm_error()) { + return lastError(m_groupSession); + } + + return messageBuf; +} + +uint32_t QOlmOutboundGroupSession::sessionMessageIndex() const +{ + return olm_outbound_group_session_message_index(m_groupSession); +} + +std::variant QOlmOutboundGroupSession::sessionId() const +{ + const auto idMaxLength = olm_outbound_group_session_id_length(m_groupSession); + QByteArray idBuffer(idMaxLength, '0'); + const auto error = olm_outbound_group_session_id(m_groupSession, reinterpret_cast(idBuffer.data()), + idBuffer.length()); + if (error == olm_error()) { + return lastError(m_groupSession); + } + return idBuffer; +} + +std::variant QOlmOutboundGroupSession::sessionKey() const +{ + const auto keyMaxLength = olm_outbound_group_session_key_length(m_groupSession); + QByteArray keyBuffer(keyMaxLength, '0'); + const auto error = olm_outbound_group_session_key(m_groupSession, reinterpret_cast(keyBuffer.data()), + keyMaxLength); + if (error == olm_error()) { + return lastError(m_groupSession); + } + return keyBuffer; +} diff --git a/lib/olm/qolmoutboundsession.h b/lib/olm/qolmoutboundsession.h new file mode 100644 index 00000000..147c0e03 --- /dev/null +++ b/lib/olm/qolmoutboundsession.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later +#pragma once + +#include "olm/olm.h" // from Olm +#include "olm/errors.h" +#include "olm/e2ee.h" + +namespace Quotient { + +//! An out-bound group session is responsible for encrypting outgoing +//! communication in a Megolm session. +class QOlmOutboundGroupSession +{ +public: + ~QOlmOutboundGroupSession(); + //! Creates a new instance of `QOlmOutboundGroupSession`. + static std::variant create(); + //! Serialises an `QOlmOutboundGroupSession` to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by + //! pickling an `QOlmOutboundGroupSession`. + static std::variant unpickle(QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + std::variant encrypt(QString &plaintext); + + //! Get the current message index for this session. + //! + //! Each message is sent with an increasing index; this returns the + //! index for the next message. + uint32_t sessionMessageIndex() const; + + //! Get a base64-encoded identifier for this session. + std::variant sessionId() const; + + //! Get the base64-encoded current ratchet key for this session. + //! + //! Each message is sent with a different ratchet key. This function returns the + //! ratchet key that will be used for the next message. + std::variant sessionKey() const; +private: + QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession, const QByteArray &groupSessionBuf); + OlmOutboundGroupSession *m_groupSession; + QByteArray m_buffer; +}; +} diff --git a/lib/olm/utils.cpp b/lib/olm/utils.cpp new file mode 100644 index 00000000..4966af15 --- /dev/null +++ b/lib/olm/utils.cpp @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "olm/utils.h" + +using namespace Quotient; + +QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) +{ + if (std::holds_alternative(mode)) { + return ""; + } + return std::get(mode).key; +} + +QByteArray Quotient::getRandom(size_t bufferSize) +{ + QByteArray buffer(bufferSize, '0'); + std::generate(buffer.begin(), buffer.end(), std::rand); + return buffer; +} diff --git a/lib/olm/utils.h b/lib/olm/utils.h new file mode 100644 index 00000000..ec0da784 --- /dev/null +++ b/lib/olm/utils.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "olm/e2ee.h" + +namespace Quotient { +// Convert PicklingMode to key +QByteArray toKey(const PicklingMode &mode); +QByteArray getRandom(size_t bufferSize); +} -- cgit v1.2.3 From 723038cb3fe16c3f0078d00362fcb53c10f9eb4a Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 24 Jan 2021 20:14:08 +0100 Subject: Depends on OpenSSL for crypo rand --- CMakeLists.txt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index 18a7b622..a74d2b7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,13 @@ find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModul get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") +find_package(OpenSSL 1.1.0 REQUIRED) +set_package_properties(OpenSSL PROPERTIES + DESCRIPTION "Open source SSL and TLS implementation and cryptographic library" + URL "https://www.openssl.org/" + TYPE REQUIRED +) + if (${PROJECT_NAME}_ENABLE_E2EE) if ((NOT DEFINED USE_INTREE_LIBQOLM OR USE_INTREE_LIBQOLM) AND EXISTS ${PROJECT_SOURCE_DIR}/3rdparty/libQtOlm/lib/utils.h) @@ -325,7 +332,14 @@ if (${PROJECT_NAME}_ENABLE_E2EE) target_link_libraries(${PROJECT_NAME} Olm::Olm QtOlm) set(FIND_DEPS "find_dependency(QtOlm)") # For QuotientConfig.cmake.in endif() -target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) + +target_link_libraries(${PROJECT_NAME} + ${Qt}::Core + ${Qt}::Network + ${Qt}::Gui + OpenSSL::Crypto + OpenSSL::SSL) + if (Qt STREQUAL Qt5) # See #483 target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) endif() -- cgit v1.2.3 From 987d399bc9ce628c376d505e3ebb78ae703d7c68 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Tue, 26 Jan 2021 20:13:20 +0100 Subject: Improve API --- CMakeLists.txt | 1 + autotests/testolmaccount.cpp | 29 +++++++++------- lib/olm/e2ee.h | 11 ++++++ lib/olm/qolmaccount.cpp | 83 +++++++++++++++++++++++++++++++------------- lib/olm/qolmaccount.h | 35 ++++++++++++++----- lib/olm/qolmsession.cpp | 29 ++++++++++++++++ lib/olm/qolmsession.h | 49 ++++++++++++++++++++++++++ 7 files changed, 192 insertions(+), 45 deletions(-) create mode 100644 lib/olm/qolmsession.cpp create mode 100644 lib/olm/qolmsession.h (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index a74d2b7b..a359ae07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,6 +179,7 @@ list(APPEND lib_SRCS lib/jobs/mediathumbnailjob.cpp lib/jobs/downloadfilejob.cpp lib/olm/qolmaccount.cpp + lib/olm/qolmsession.cpp lib/olm/qolminboundsession.cpp lib/olm/qolmoutboundsession.cpp lib/olm/utils.cpp diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 45a7e3a5..75102c32 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -10,19 +10,22 @@ using namespace Quotient; void TestOlmAccount::pickleUnpickedTest() { - auto olmAccount = QOlmAccount::create().value(); - auto identityKeys = std::get(olmAccount.identityKeys()); + QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); + olmAccount.createNewAccount(); + auto identityKeys = olmAccount.identityKeys(); auto pickled = std::get(olmAccount.pickle(Unencrypted{})); - auto olmAccount2 = std::get(QOlmAccount::unpickle(pickled, Unencrypted{})); - auto identityKeys2 = std::get(olmAccount2.identityKeys()); + QOlmAccount olmAccount2(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); + olmAccount2.unpickle(pickled, Unencrypted{}); + auto identityKeys2 = olmAccount2.identityKeys(); QCOMPARE(identityKeys.curve25519, identityKeys2.curve25519); QCOMPARE(identityKeys.ed25519, identityKeys2.ed25519); } void TestOlmAccount::identityKeysValid() { - auto olmAccount = QOlmAccount::create().value(); - const auto identityKeys = std::get(olmAccount.identityKeys()); + QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); + olmAccount.createNewAccount(); + const auto identityKeys = olmAccount.identityKeys(); const auto curve25519 = identityKeys.curve25519; const auto ed25519 = identityKeys.ed25519; // verify encoded keys length @@ -36,10 +39,11 @@ void TestOlmAccount::identityKeysValid() void TestOlmAccount::signatureValid() { - const auto olmAccount = QOlmAccount::create().value(); + QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); + olmAccount.createNewAccount(); const auto message = "Hello world!"; - const auto signature = std::get(olmAccount.sign(message)); - QVERIFY(QByteArray::fromBase64Encoding(signature.toUtf8()).decodingStatus == QByteArray::Base64DecodingStatus::Ok); + const auto signature = olmAccount.sign(message); + QVERIFY(QByteArray::fromBase64Encoding(signature).decodingStatus == QByteArray::Base64DecodingStatus::Ok); //let utility = OlmUtility::new(); //let identity_keys = olm_account.parsed_identity_keys(); @@ -51,15 +55,16 @@ void TestOlmAccount::signatureValid() void TestOlmAccount::oneTimeKeysValid() { - const auto olmAccount = QOlmAccount::create().value(); + QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); + olmAccount.createNewAccount(); const auto maxNumberOfOneTimeKeys = olmAccount.maxNumberOfOneTimeKeys(); QCOMPARE(100, maxNumberOfOneTimeKeys); - const auto oneTimeKeysEmpty = std::get(olmAccount.oneTimeKeys()); + const auto oneTimeKeysEmpty = olmAccount.oneTimeKeys(); QVERIFY(oneTimeKeysEmpty.curve25519().isEmpty()); olmAccount.generateOneTimeKeys(20); - const auto oneTimeKeysFilled = std::get(olmAccount.oneTimeKeys()); + const auto oneTimeKeysFilled = olmAccount.oneTimeKeys(); QCOMPARE(20, oneTimeKeysFilled.curve25519().count()); } diff --git a/lib/olm/e2ee.h b/lib/olm/e2ee.h index 1dee0e42..74f876e4 100644 --- a/lib/olm/e2ee.h +++ b/lib/olm/e2ee.h @@ -66,6 +66,17 @@ struct OneTimeKeys std::optional> get(QString keyType) const; }; +//! Struct representing the signed one-time keys. +struct SignedOneTimeKey +{ + //! Required. The unpadded Base64-encoded 32-byte Curve25519 public key. + QString key; + + //! Required. Signatures of the key object. + //! The signature is calculated using the process described at Signing JSON. + QMap> signatures; +}; + bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); } // namespace Quotient diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp index 742d7d18..8872f66e 100644 --- a/lib/olm/qolmaccount.cpp +++ b/lib/olm/qolmaccount.cpp @@ -44,36 +44,37 @@ QByteArray getRandom(size_t bufferSize) return buffer; } -QOlmAccount::QOlmAccount(OlmAccount *account) - : m_account(account) -{} +QOlmAccount::QOlmAccount(const QString &userId, const QString &deviceId) + : m_userId(userId) + , m_deviceId(deviceId) +{ +} QOlmAccount::~QOlmAccount() { olm_clear_account(m_account); + delete[](reinterpret_cast(m_account)); } -std::optional QOlmAccount::create() +void QOlmAccount::createNewAccount() { - auto account = olm_account(new uint8_t[olm_account_size()]); - size_t randomSize = olm_create_account_random_length(account); + m_account = olm_account(new uint8_t[olm_account_size()]); + size_t randomSize = olm_create_account_random_length(m_account); QByteArray randomData = getRandom(randomSize); - const auto error = olm_create_account(account, randomData.data(), randomSize); + const auto error = olm_create_account(m_account, randomData.data(), randomSize); if (error == olm_error()) { - return std::nullopt; + throw lastError(m_account); } - return std::make_optional(account); } -std::variant QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) +void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) { - auto account = olm_account(new uint8_t[olm_account_size()]); + m_account = olm_account(new uint8_t[olm_account_size()]); const QByteArray key = toKey(mode); - const auto error = olm_unpickle_account(account, key.data(), key.length(), pickled.data(), pickled.size()); + const auto error = olm_unpickle_account(m_account, key.data(), key.length(), pickled.data(), pickled.size()); if (error == olm_error()) { - return lastError(account); + throw lastError(m_account); } - return QOlmAccount(account); } std::variant QOlmAccount::pickle(const PicklingMode &mode) @@ -89,13 +90,13 @@ std::variant QOlmAccount::pickle(const PicklingMode &mode) return pickleBuffer; } -std::variant QOlmAccount::identityKeys() +IdentityKeys QOlmAccount::identityKeys() const { const size_t keyLength = olm_account_identity_keys_length(m_account); QByteArray keyBuffer(keyLength, '0'); const auto error = olm_account_identity_keys(m_account, keyBuffer.data(), keyLength); if (error == olm_error()) { - return lastError(m_account); + throw lastError(m_account); } const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object(); return IdentityKeys { @@ -104,7 +105,7 @@ std::variant QOlmAccount::identityKeys() }; } -std::variant QOlmAccount::sign(const QString &message) const +QByteArray QOlmAccount::sign(const QByteArray &message) const { const size_t signatureLength = olm_account_signature_length(m_account); QByteArray signatureBuffer(signatureLength, '0'); @@ -112,9 +113,19 @@ std::variant QOlmAccount::sign(const QString &message) const signatureBuffer.data(), signatureLength); if (error == olm_error()) { - return lastError(m_account); + throw lastError(m_account); } - return QString::fromUtf8(signatureBuffer); + return signatureBuffer; +} + +QByteArray QOlmAccount::signIdentityKeys() const +{ + const auto keys = identityKeys(); + const QJsonObject j{ {Curve25519Key, QString(keys.curve25519)}, {Ed25519Key, QString(keys.ed25519)} }; + QJsonDocument doc; + doc.setObject(j); + return sign(doc.toJson()); + } size_t QOlmAccount::maxNumberOfOneTimeKeys() const @@ -122,26 +133,25 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const return olm_account_max_number_of_one_time_keys(m_account); } -std::optional QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const +void QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const { const size_t randomLen = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); QByteArray randomBuffer = getRandom(randomLen); const auto error = olm_account_generate_one_time_keys(m_account, numberOfKeys, randomBuffer.data(), randomLen); if (error == olm_error()) { - return lastError(m_account); + throw lastError(m_account); } - return std::nullopt; } -std::variant QOlmAccount::oneTimeKeys() const +OneTimeKeys QOlmAccount::oneTimeKeys() const { const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0'); const auto error = olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), oneTimeKeyLength); if (error == olm_error()) { - return lastError(m_account); + throw lastError(m_account); } const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); OneTimeKeys oneTimeKeys; @@ -157,4 +167,29 @@ std::variant QOlmAccount::oneTimeKeys() const return oneTimeKeys; } +QMap QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const +{ + QMap signedOneTimeKeys; + for (const auto &keyid : keys.curve25519().keys()) { + const auto oneTimeKey = keys.curve25519()[keyid]; + QByteArray sign = signOneTimeKey(oneTimeKey); + signedOneTimeKeys["signed_curve25519:" + keyid] = signedOneTimeKey(oneTimeKey.toUtf8(), sign); + } + return signedOneTimeKeys; +} + +SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray &key, const QString &signature) const +{ + SignedOneTimeKey sign{}; + sign.key = key; + sign.signatures = {{m_userId, {{"ed25519:" + m_deviceId, signature}}}}; + return sign; +} + +QByteArray QOlmAccount::signOneTimeKey(const QString &key) const +{ + QJsonDocument j(QJsonObject{{"key", key}}); + return sign(j.toJson()); +} + #endif diff --git a/lib/olm/qolmaccount.h b/lib/olm/qolmaccount.h index c478c781..3b55212d 100644 --- a/lib/olm/qolmaccount.h +++ b/lib/olm/qolmaccount.h @@ -20,36 +20,53 @@ namespace Quotient { class QOlmAccount { public: + QOlmAccount(const QString &userId, const QString &deviceId); ~QOlmAccount(); //! Creates a new instance of OlmAccount. During the instantiation //! the Ed25519 fingerprint key pair and the Curve25519 identity key //! pair are generated. For more information see here. - static std::optional create(); - static std::variant unpickle(QByteArray &picked, const PicklingMode &mode); + //! This needs to be called before any other action or use unpickle() instead. + void createNewAccount(); + + //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmAccount`. + //! This needs to be called before any other action or use createNewAccount() instead. + void unpickle(QByteArray &picked, const PicklingMode &mode); //! Serialises an OlmAccount to encrypted Base64. std::variant pickle(const PicklingMode &mode); - std::variant identityKeys(); + + //! Returns the account's public identity keys already formatted as JSON + IdentityKeys identityKeys() const; //! Returns the signature of the supplied message. - std::variant sign(const QString &message) const; + QByteArray sign(const QByteArray &message) const; + + //! Sign identity keys. + QByteArray signIdentityKeys() const; //! Maximum number of one time keys that this OlmAccount can //! currently hold. size_t maxNumberOfOneTimeKeys() const; //! Generates the supplied number of one time keys. - std::optional generateOneTimeKeys(size_t numberOfKeys) const; + void generateOneTimeKeys(size_t numberOfKeys) const; //! Gets the OlmAccount's one time keys formatted as JSON. - std::variant oneTimeKeys() const; + OneTimeKeys oneTimeKeys() const; + + //! Sign all time key. + QMap signOneTimeKeys(const OneTimeKeys &keys) const; + + //! Sign one time key. + QByteArray signOneTimeKey(const QString &key) const; - // HACK do not use directly - QOlmAccount(OlmAccount *account); + SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; private: - OlmAccount *m_account; + OlmAccount *m_account = nullptr; + QString m_userId; + QString m_deviceId; }; } // namespace Quotient diff --git a/lib/olm/qolmsession.cpp b/lib/olm/qolmsession.cpp new file mode 100644 index 00000000..32a108a8 --- /dev/null +++ b/lib/olm/qolmsession.cpp @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "olm/qolmsession.h" + +using namespace Quotient; + +std::optional fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext) +{ + if (messageType == OLM_MESSAGE_TYPE_PRE_KEY) { + return PreKeyMessage { ciphertext }; + } else if (messageType == OLM_MESSAGE_TYPE_MESSAGE) { + return Message { ciphertext }; + } + return std::nullopt; +} + +std::pair toPair(const OlmMessage &message) +{ + return std::visit([](auto &arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return std::make_pair(MessageType, QByteArray(arg.message)); + } else if constexpr (std::is_same_v) { + return std::make_pair(PreKeyType, QByteArray(arg.message)); + } + }, message); +} diff --git a/lib/olm/qolmsession.h b/lib/olm/qolmsession.h new file mode 100644 index 00000000..08f47331 --- /dev/null +++ b/lib/olm/qolmsession.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "olm/e2ee.h" +#include "olm/olm.h" +#include "olm/errors.h" +#include + +namespace Quotient { + +//! An encrypted Olm message. +struct Message { + QByteArray message; +}; + +//! A encrypted Olm pre-key message. +//! +//! This message, unlike a normal Message, can be used to create new Olm sessions. +struct PreKeyMessage +{ + QByteArray message; +}; + +enum OlmMessageType +{ + PreKeyType, + MessageType, +}; + +using OlmMessage = std::variant; + +std::optional fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext); + +std::pair toPair(const OlmMessage &message); + +//class QOlmSession +//{ +// /// Creates an inbound session for sending/receiving messages from a received 'prekey' message. +// static std::variant, OlmError> createInboundSession(const QOlmAccount &account, +// PreKeyMessage &message); +// +////private: +// //static std::variant, OlmError> createSessionWith(std::function> func); +//} + +} -- cgit v1.2.3 From 799008ddbe2414ca0bce060a4d7f6a77c04c8d10 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Tue, 26 Jan 2021 04:39:04 +0300 Subject: E2EE: Introduce session (WiP) --- CMakeLists.txt | 2 + lib/olm/message.cpp | 35 ++++++++++ lib/olm/message.h | 46 +++++++++++++ lib/olm/qolmaccount.cpp | 5 ++ lib/olm/qolmaccount.h | 4 ++ lib/olm/qolminboundsession.h | 1 + lib/olm/qolmoutboundsession.h | 1 + lib/olm/session.cpp | 155 ++++++++++++++++++++++++++++++++++++++++++ lib/olm/session.h | 46 +++++++++++++ 9 files changed, 295 insertions(+) create mode 100644 lib/olm/message.cpp create mode 100644 lib/olm/message.h create mode 100644 lib/olm/session.cpp create mode 100644 lib/olm/session.h (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index a359ae07..476b7d81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,6 +184,8 @@ list(APPEND lib_SRCS lib/olm/qolmoutboundsession.cpp lib/olm/utils.cpp lib/olm/errors.cpp + lib/olm/session.cpp + lib/olm/message.cpp ) # Configure API files generation diff --git a/lib/olm/message.cpp b/lib/olm/message.cpp new file mode 100644 index 00000000..0998a66b --- /dev/null +++ b/lib/olm/message.cpp @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "olm/message.h" + +using namespace Quotient; + +Message::Message(const QByteArray &ciphertext, Message::Type type) + : QByteArray(std::move(ciphertext)), _messageType(type) +{ + Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); +} + +Message::Message(QByteArray ciphertext) : QByteArray(std::move(ciphertext)) +{ + Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); +} + +Message::Type Message::type() const +{ + return _messageType; +} + +QByteArray Message::toCiphertext() const +{ + return QByteArray(*this); +} + + +#endif // Quotient_E2EE_ENABLED + + + diff --git a/lib/olm/message.h b/lib/olm/message.h new file mode 100644 index 00000000..6c8ab485 --- /dev/null +++ b/lib/olm/message.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED + +#include +#include + +namespace Quotient { + +/*! \brief A wrapper around an olm encrypted message + * + * This class encapsulates a Matrix olm encrypted message, + * passed in either of 2 forms: a general message or a pre-key message. + * + * The class provides functions to get a type and the ciphertext. + */ +class Message : private QByteArray { + Q_GADGET +public: + enum Type { + General, + PreKey, + }; + Q_ENUM(Type) + + Message() = default; + explicit Message(const QByteArray& ciphertext, Type type = General); + explicit Message(QByteArray ciphertext); + + static Message fromCiphertext(QByteArray ciphertext); + + Q_INVOKABLE Type type() const; + Q_INVOKABLE QByteArray toCiphertext() const; + +private: + Type _messageType = General; +}; + + +} //namespace Quotient + +#endif // Quotient_E2EE_ENABLED diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp index 8872f66e..9530d675 100644 --- a/lib/olm/qolmaccount.cpp +++ b/lib/olm/qolmaccount.cpp @@ -192,4 +192,9 @@ QByteArray QOlmAccount::signOneTimeKey(const QString &key) const return sign(j.toJson()); } +OlmAccount *Quotient::QOlmAccount::data() +{ + return m_account; +} + #endif diff --git a/lib/olm/qolmaccount.h b/lib/olm/qolmaccount.h index 3b55212d..3260ca71 100644 --- a/lib/olm/qolmaccount.h +++ b/lib/olm/qolmaccount.h @@ -63,6 +63,10 @@ public: QByteArray signOneTimeKey(const QString &key) const; SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; + OlmAccount *data(); + + // HACK do not use directly + QOlmAccount(OlmAccount *account); private: OlmAccount *m_account = nullptr; QString m_userId; diff --git a/lib/olm/qolminboundsession.h b/lib/olm/qolminboundsession.h index eb698868..739a8411 100644 --- a/lib/olm/qolminboundsession.h +++ b/lib/olm/qolminboundsession.h @@ -46,5 +46,6 @@ private: }; using QOlmInboundGroupSessionPtr = std::unique_ptr; +using OlmInboundGroupSessionPtr = std::unique_ptr; } // namespace Quotient #endif diff --git a/lib/olm/qolmoutboundsession.h b/lib/olm/qolmoutboundsession.h index a642f581..70c4d27f 100644 --- a/lib/olm/qolmoutboundsession.h +++ b/lib/olm/qolmoutboundsession.h @@ -49,5 +49,6 @@ private: }; using QOlmOutboundGroupSessionPtr = std::unique_ptr; +using OlmOutboundGroupSessionPtr = std::unique_ptr; } #endif diff --git a/lib/olm/session.cpp b/lib/olm/session.cpp new file mode 100644 index 00000000..a2a7d28a --- /dev/null +++ b/lib/olm/session.cpp @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "olm/session.h" +#include "olm/utils.h" +#include "logging.h" + +using namespace Quotient; + +OlmError lastError(OlmSession* session) { + const std::string error_raw = olm_session_last_error(session); + + return fromString(error_raw); +} + +Quotient::QOlmSession::~QOlmSession() +{ + olm_clear_session(m_session); +} + +OlmSession* QOlmSession::create() +{ + return olm_session(new uint8_t[olm_session_size()]); +} + +std::unique_ptr QOlmSession::createInbound(QOlmAccount &account, const Message &preKeyMessage, bool from, const QString &theirIdentityKey) +{ + if (preKeyMessage.type() != Message::PreKey) { + qCDebug(E2EE) << "The message is not a pre-key"; + throw BadMessageFormat; + } + + const auto olmSession = create(); + + QByteArray oneTimeKeyMessageBuf = preKeyMessage.toCiphertext(); + QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); + size_t error = 0; + if (from) { + error = olm_create_inbound_session_from(olmSession, account.data(), theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); + } else { + error = olm_create_inbound_session(olmSession, account.data(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); + } + + if (error == olm_error()) { + throw lastError(olmSession); + } + + return std::make_unique(olmSession); +} + +std::unique_ptr QOlmSession::createInboundSession(QOlmAccount& account, const Message &preKeyMessage) +{ + return createInbound(account, preKeyMessage); +} + +std::unique_ptr QOlmSession::createInboundSessionFrom(QOlmAccount &account, const QString &theirIdentityKey, const Message &preKeyMessage) +{ + return createInbound(account, preKeyMessage, true, theirIdentityKey); +} + +std::unique_ptr QOlmSession::createOutboundSession(QOlmAccount &account, const QString &theirIdentityKey, const QString &theirOneTimeKey) +{ + auto *olmOutboundSession = create(); + const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); + QByteArray randomBuf = getRandom(randomLen); + + QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); + QByteArray theirOneTimeKeyBuf = theirOneTimeKey.toUtf8(); + const auto error = olm_create_outbound_session(olmOutboundSession, + account.data(), + theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), + theirOneTimeKeyBuf.data(), theirOneTimeKeyBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length()); + + if (error == olm_error()) { + throw lastError(olmOutboundSession); + } + + randomBuf.clear(); + return std::make_unique(olmOutboundSession); +} + +std::variant QOlmSession::pickle(const PicklingMode &mode) +{ + QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); + QByteArray key = toKey(mode); + const auto error = olm_pickle_session(m_session, key.data(), key.length(), + pickledBuf.data(), pickledBuf.length()); + + if (error == olm_error()) { + return lastError(m_session); + } + + key.clear(); + + return pickledBuf; +} + +std::variant, OlmError> QOlmSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +{ + QByteArray pickledBuf = pickled; + auto *olmSession = create(); + QByteArray key = toKey(mode); + const auto error = olm_unpickle_session(olmSession, key.data(), key.length(), + pickled.data(), pickled.length()); + if (error == olm_error()) { + return lastError(olmSession); + } + + key.clear(); + return std::make_unique(olmSession); +} + +std::variant QOlmSession::encrypt(const QString &plaintext) +{ + QByteArray plaintextBuf = plaintext.toUtf8(); + const auto messageMaxLen = olm_encrypt_message_length(m_session, plaintextBuf.length()); + QByteArray messageBuf(messageMaxLen, '0'); + const auto randomLen = olm_encrypt_random_length(m_session); + QByteArray randomBuf = getRandom(randomLen); + const auto error = olm_encrypt(m_session, + reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length(), + reinterpret_cast(messageBuf.data()), messageBuf.length()); + + if (error == olm_error()) { + return lastError(m_session); + } + + return Message::fromCiphertext(messageBuf); +} + +QByteArray QOlmSession::sessionId() const +{ + const auto idMaxLength = olm_session_id_length(m_session); + QByteArray idBuffer(idMaxLength, '0'); + const auto error = olm_session_id(m_session, reinterpret_cast(idBuffer.data()), + idBuffer.length()); + if (error == olm_error()) { + throw lastError(m_session); + } + return idBuffer; +} + +QOlmSession::QOlmSession(OlmSession *session): m_session(session) +{ + +} + +#endif // Quotient_E2EE_ENABLED + + + diff --git a/lib/olm/session.h b/lib/olm/session.h new file mode 100644 index 00000000..76c1df29 --- /dev/null +++ b/lib/olm/session.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED + +#include "olm/e2ee.h" +#include "olm/message.h" +#include "olm/errors.h" +#include "olm/qolmaccount.h" + +namespace Quotient { + +//! Either an outbound or inbound session for secure communication. +class QOlmSession +{ +public: + ~QOlmSession(); + //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. + static std::unique_ptr createInboundSession(QOlmAccount& account, const Message& preKeyMessage); + static std::unique_ptr createInboundSessionFrom(QOlmAccount& account, const QString& theirIdentityKey, const Message& preKeyMessage); + static std::unique_ptr createOutboundSession(QOlmAccount& account, const QString& theirIdentityKey, const QString& theirOneTimeKey); + //! Serialises an `QOlmSession` to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. + static std::variant, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + std::variant encrypt(const QString &plaintext); + // TODO: WiP + + //! Get a base64-encoded identifier for this session. + QByteArray sessionId() const; + + QOlmSession(OlmSession* session); +private: + //! Helper function for creating new sessions and handling errors. + static OlmSession* create(); + static std::unique_ptr createInbound(QOlmAccount& account, const Message& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); + OlmSession* m_session; +}; + +} //namespace Quotient + +#endif // Quotient_E2EE_ENABLED -- cgit v1.2.3 From 7ad805492f8b42a4bc854313695a912c89019957 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 27 Jan 2021 17:54:27 +0100 Subject: Fix CI --- .github/workflows/ci.yml | 6 ++++++ CMakeLists.txt | 14 +++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) (limited to 'CMakeLists.txt') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47e55421..0b707236 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,6 +106,12 @@ jobs: with: arch: ${{ matrix.platform }} + - name: Install OpenSSL + if: contains(matrix.os, 'ubuntu') and matrix.e2ee + run: | + sudo apt-get install libssl-dev + echo "openssl version" >>$GITHUB_ENV + - name: Build and install olm if: matrix.e2ee run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 476b7d81..a04da04d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,14 +87,14 @@ find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModul get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") -find_package(OpenSSL 1.1.0 REQUIRED) -set_package_properties(OpenSSL PROPERTIES - DESCRIPTION "Open source SSL and TLS implementation and cryptographic library" - URL "https://www.openssl.org/" - TYPE REQUIRED -) - if (${PROJECT_NAME}_ENABLE_E2EE) + find_package(OpenSSL 1.1.0 REQUIRED) + set_package_properties(OpenSSL PROPERTIES + DESCRIPTION "Open source SSL and TLS implementation and cryptographic library" + URL "https://www.openssl.org/" + TYPE REQUIRED + ) + if ((NOT DEFINED USE_INTREE_LIBQOLM OR USE_INTREE_LIBQOLM) AND EXISTS ${PROJECT_SOURCE_DIR}/3rdparty/libQtOlm/lib/utils.h) add_subdirectory(3rdparty/libQtOlm) -- cgit v1.2.3 From dd0316ce57bd9256a093d66845e1d40cd9426ba4 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 28 Jan 2021 21:54:37 +0100 Subject: Move files --- CMakeLists.txt | 16 +-- autotests/testgroupsession.cpp | 6 +- autotests/testolmaccount.cpp | 6 +- autotests/testolmsession.cpp | 2 +- lib/crypto/e2ee.h | 82 +++++++++++++ lib/crypto/errors.cpp | 21 ++++ lib/crypto/errors.h | 31 +++++ lib/crypto/message.cpp | 42 +++++++ lib/crypto/message.h | 46 +++++++ lib/crypto/qolmaccount.cpp | 217 +++++++++++++++++++++++++++++++++ lib/crypto/qolmaccount.h | 96 +++++++++++++++ lib/crypto/qolminboundsession.cpp | 157 ++++++++++++++++++++++++ lib/crypto/qolminboundsession.h | 51 ++++++++ lib/crypto/qolmoutboundsession.cpp | 131 ++++++++++++++++++++ lib/crypto/qolmoutboundsession.h | 54 +++++++++ lib/crypto/qolmsession.cpp | 29 +++++ lib/crypto/qolmsession.h | 49 ++++++++ lib/crypto/session.cpp | 242 +++++++++++++++++++++++++++++++++++++ lib/crypto/session.h | 77 ++++++++++++ lib/crypto/utils.cpp | 26 ++++ lib/crypto/utils.h | 15 +++ lib/olm/e2ee.h | 82 ------------- lib/olm/errors.cpp | 21 ---- lib/olm/errors.h | 31 ----- lib/olm/message.cpp | 42 ------- lib/olm/message.h | 46 ------- lib/olm/qolmaccount.cpp | 217 --------------------------------- lib/olm/qolmaccount.h | 96 --------------- lib/olm/qolminboundsession.cpp | 157 ------------------------ lib/olm/qolminboundsession.h | 51 -------- lib/olm/qolmoutboundsession.cpp | 131 -------------------- lib/olm/qolmoutboundsession.h | 54 --------- lib/olm/qolmsession.cpp | 29 ----- lib/olm/qolmsession.h | 49 -------- lib/olm/session.cpp | 242 ------------------------------------- lib/olm/session.h | 76 ------------ lib/olm/utils.cpp | 26 ---- lib/olm/utils.h | 15 --- 38 files changed, 1381 insertions(+), 1380 deletions(-) create mode 100644 lib/crypto/e2ee.h create mode 100644 lib/crypto/errors.cpp create mode 100644 lib/crypto/errors.h create mode 100644 lib/crypto/message.cpp create mode 100644 lib/crypto/message.h create mode 100644 lib/crypto/qolmaccount.cpp create mode 100644 lib/crypto/qolmaccount.h create mode 100644 lib/crypto/qolminboundsession.cpp create mode 100644 lib/crypto/qolminboundsession.h create mode 100644 lib/crypto/qolmoutboundsession.cpp create mode 100644 lib/crypto/qolmoutboundsession.h create mode 100644 lib/crypto/qolmsession.cpp create mode 100644 lib/crypto/qolmsession.h create mode 100644 lib/crypto/session.cpp create mode 100644 lib/crypto/session.h create mode 100644 lib/crypto/utils.cpp create mode 100644 lib/crypto/utils.h delete mode 100644 lib/olm/e2ee.h delete mode 100644 lib/olm/errors.cpp delete mode 100644 lib/olm/errors.h delete mode 100644 lib/olm/message.cpp delete mode 100644 lib/olm/message.h delete mode 100644 lib/olm/qolmaccount.cpp delete mode 100644 lib/olm/qolmaccount.h delete mode 100644 lib/olm/qolminboundsession.cpp delete mode 100644 lib/olm/qolminboundsession.h delete mode 100644 lib/olm/qolmoutboundsession.cpp delete mode 100644 lib/olm/qolmoutboundsession.h delete mode 100644 lib/olm/qolmsession.cpp delete mode 100644 lib/olm/qolmsession.h delete mode 100644 lib/olm/session.cpp delete mode 100644 lib/olm/session.h delete mode 100644 lib/olm/utils.cpp delete mode 100644 lib/olm/utils.h (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index a04da04d..40767573 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -178,14 +178,14 @@ list(APPEND lib_SRCS lib/jobs/syncjob.cpp lib/jobs/mediathumbnailjob.cpp lib/jobs/downloadfilejob.cpp - lib/olm/qolmaccount.cpp - lib/olm/qolmsession.cpp - lib/olm/qolminboundsession.cpp - lib/olm/qolmoutboundsession.cpp - lib/olm/utils.cpp - lib/olm/errors.cpp - lib/olm/session.cpp - lib/olm/message.cpp + lib/crypto/qolmaccount.cpp + lib/crypto/qolmsession.cpp + lib/crypto/qolminboundsession.cpp + lib/crypto/qolmoutboundsession.cpp + lib/crypto/utils.cpp + lib/crypto/errors.cpp + lib/crypto/session.cpp + lib/crypto/message.cpp ) # Configure API files generation diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 23c5bf8f..325ca2ec 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -3,9 +3,9 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testgroupsession.h" -#include "olm/qolminboundsession.h" -#include "olm/qolmoutboundsession.h" -#include "olm/utils.h" +#include +#include +#include using namespace Quotient; diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 2fac53bd..cbce845a 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -3,9 +3,9 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testolmaccount.h" -#include "olm/qolmaccount.h" -#include "csapi/definitions/device_keys.h" -#include "events/encryptedfile.h" +#include +#include +#include using namespace Quotient; diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index da0e36e3..462c8213 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include "olm/session.h" +#include #include "testolmsession.h" using namespace Quotient; diff --git a/lib/crypto/e2ee.h b/lib/crypto/e2ee.h new file mode 100644 index 00000000..74f876e4 --- /dev/null +++ b/lib/crypto/e2ee.h @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "util.h" +#include +#include +#include +#include + +#include + +namespace Quotient { +inline const auto CiphertextKeyL = "ciphertext"_ls; +inline const auto SenderKeyKeyL = "sender_key"_ls; +inline const auto DeviceIdKeyL = "device_id"_ls; +inline const auto SessionIdKeyL = "session_id"_ls; + +inline const auto AlgorithmKeyL = "algorithm"_ls; +inline const auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; +inline const auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls; + +inline const auto AlgorithmKey = QStringLiteral("algorithm"); +inline const auto RotationPeriodMsKey = QStringLiteral("rotation_period_ms"); +inline const auto RotationPeriodMsgsKey = + QStringLiteral("rotation_period_msgs"); + +inline const auto Ed25519Key = QStringLiteral("ed25519"); +inline const auto Curve25519Key = QStringLiteral("curve25519"); +inline const auto SignedCurve25519Key = QStringLiteral("signed_curve25519"); +inline const auto OlmV1Curve25519AesSha2AlgoKey = + QStringLiteral("m.olm.v1.curve25519-aes-sha2"); +inline const auto MegolmV1AesSha2AlgoKey = + QStringLiteral("m.megolm.v1.aes-sha2"); +inline const QStringList SupportedAlgorithms = { OlmV1Curve25519AesSha2AlgoKey, + MegolmV1AesSha2AlgoKey }; + +struct Unencrypted {}; +struct Encrypted { + QByteArray key; +}; + +using PicklingMode = std::variant; + +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...) -> overloaded; + +struct IdentityKeys +{ + QByteArray curve25519; + QByteArray ed25519; +}; + +//! Struct representing the one-time keys. +struct OneTimeKeys +{ + QMap> keys; + + //! Get the HashMap containing the curve25519 one-time keys. + QMap curve25519() const; + + //! Get a reference to the hashmap corresponding to given key type. + std::optional> get(QString keyType) const; +}; + +//! Struct representing the signed one-time keys. +struct SignedOneTimeKey +{ + //! Required. The unpadded Base64-encoded 32-byte Curve25519 public key. + QString key; + + //! Required. Signatures of the key object. + //! The signature is calculated using the process described at Signing JSON. + QMap> signatures; +}; + +bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); + +} // namespace Quotient diff --git a/lib/crypto/errors.cpp b/lib/crypto/errors.cpp new file mode 100644 index 00000000..00ff962d --- /dev/null +++ b/lib/crypto/errors.cpp @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later +#ifdef Quotient_E2EE_ENABLED +#include "crypto/errors.h" + +Quotient::OlmError Quotient::fromString(const std::string &error_raw) { + if (error_raw.compare("BAD_ACCOUNT_KEY")) { + return OlmError::BadAccountKey; + } else if (error_raw.compare("BAD_MESSAGE_KEY_ID")) { + return OlmError::BadMessageKeyId; + } else if (error_raw.compare("INVALID_BASE64")) { + return OlmError::InvalidBase64; + } else if (error_raw.compare("NOT_ENOUGH_RANDOM")) { + return OlmError::NotEnoughRandom; + } else if (error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { + return OlmError::OutputBufferTooSmall; + } else { + return OlmError::Unknown; + } +} +#endif diff --git a/lib/crypto/errors.h b/lib/crypto/errors.h new file mode 100644 index 00000000..09d2a989 --- /dev/null +++ b/lib/crypto/errors.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED +#include + +namespace Quotient { +//! All errors that could be caused by an operation regarding Olm +//! Errors are named exactly like the ones in libolm. +enum OlmError +{ + BadAccountKey, + BadMessageFormat, + BadMessageKeyId, + BadMessageMac, + BadMessageVersion, + InvalidBase64, + NotEnoughRandom, + OutputBufferTooSmall, + UnknownMessageIndex, + Unknown, +}; + +OlmError fromString(const std::string &error_raw); + +} //namespace Quotient + +#endif diff --git a/lib/crypto/message.cpp b/lib/crypto/message.cpp new file mode 100644 index 00000000..830633bf --- /dev/null +++ b/lib/crypto/message.cpp @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/message.h" + +using namespace Quotient; + +Message::Message(const QByteArray &ciphertext, Message::Type type) + : QByteArray(std::move(ciphertext)) + , m_messageType(type) +{ + Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); +} + +Message::Message(const Message &message) + : QByteArray(message) + , m_messageType(message.type()) +{ +} + +Message::Type Message::type() const +{ + return m_messageType; +} + +QByteArray Message::toCiphertext() const +{ + return QByteArray(*this); +} + +Message Message::fromCiphertext(const QByteArray &ciphertext) +{ + return Message(ciphertext, Message::General); +} + + +#endif // Quotient_E2EE_ENABLED + + + diff --git a/lib/crypto/message.h b/lib/crypto/message.h new file mode 100644 index 00000000..1ae19ba8 --- /dev/null +++ b/lib/crypto/message.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED + +#include +#include + +namespace Quotient { + +/*! \brief A wrapper around an olm encrypted message + * + * This class encapsulates a Matrix olm encrypted message, + * passed in either of 2 forms: a general message or a pre-key message. + * + * The class provides functions to get a type and the ciphertext. + */ +class Message : public QByteArray { + Q_GADGET +public: + enum Type { + General, + PreKey, + }; + Q_ENUM(Type) + + Message() = default; + explicit Message(const QByteArray &ciphertext, Type type = General); + explicit Message(const Message &message); + + static Message fromCiphertext(const QByteArray &ciphertext); + + Q_INVOKABLE Type type() const; + Q_INVOKABLE QByteArray toCiphertext() const; + +private: + Type m_messageType = General; +}; + + +} //namespace Quotient + +#endif // Quotient_E2EE_ENABLED diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp new file mode 100644 index 00000000..8824e7ef --- /dev/null +++ b/lib/crypto/qolmaccount.cpp @@ -0,0 +1,217 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/qolmaccount.h" +#include "crypto/utils.h" +#include +#include +#include +#include + +using namespace Quotient; + +QMap OneTimeKeys::curve25519() const +{ + return keys[QStringLiteral("curve25519")]; +} + +std::optional> OneTimeKeys::get(QString keyType) const +{ + if (!keys.contains(keyType)) { + return std::nullopt; + } + return keys[keyType]; +} + +bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) +{ + return lhs.curve25519 == rhs.curve25519 &&& lhs.ed25519 == rhs.ed25519; +} + +// Convert olm error to enum +OlmError lastError(OlmAccount *account) { + const std::string error_raw = olm_account_last_error(account); + + return fromString(error_raw); +} + +QByteArray getRandom(size_t bufferSize) +{ + QByteArray buffer(bufferSize, '0'); + std::generate(buffer.begin(), buffer.end(), std::rand); + return buffer; +} + +QOlmAccount::QOlmAccount(const QString &userId, const QString &deviceId) + : m_userId(userId) + , m_deviceId(deviceId) +{ +} + +QOlmAccount::~QOlmAccount() +{ + olm_clear_account(m_account); + delete[](reinterpret_cast(m_account)); +} + +void QOlmAccount::createNewAccount() +{ + m_account = olm_account(new uint8_t[olm_account_size()]); + size_t randomSize = olm_create_account_random_length(m_account); + QByteArray randomData = getRandom(randomSize); + const auto error = olm_create_account(m_account, randomData.data(), randomSize); + if (error == olm_error()) { + throw lastError(m_account); + } +} + +void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) +{ + m_account = olm_account(new uint8_t[olm_account_size()]); + const QByteArray key = toKey(mode); + const auto error = olm_unpickle_account(m_account, key.data(), key.length(), pickled.data(), pickled.size()); + if (error == olm_error()) { + throw lastError(m_account); + } +} + +std::variant QOlmAccount::pickle(const PicklingMode &mode) +{ + const QByteArray key = toKey(mode); + const size_t pickleLength = olm_pickle_account_length(m_account); + QByteArray pickleBuffer(pickleLength, '0'); + const auto error = olm_pickle_account(m_account, key.data(), + key.length(), pickleBuffer.data(), pickleLength); + if (error == olm_error()) { + return lastError(m_account); + } + return pickleBuffer; +} + +IdentityKeys QOlmAccount::identityKeys() const +{ + const size_t keyLength = olm_account_identity_keys_length(m_account); + QByteArray keyBuffer(keyLength, '0'); + const auto error = olm_account_identity_keys(m_account, keyBuffer.data(), keyLength); + if (error == olm_error()) { + throw lastError(m_account); + } + const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object(); + return IdentityKeys { + key.value(QStringLiteral("curve25519")).toString().toUtf8(), + key.value(QStringLiteral("ed25519")).toString().toUtf8() + }; +} + +QByteArray QOlmAccount::sign(const QByteArray &message) const +{ + const size_t signatureLength = olm_account_signature_length(m_account); + QByteArray signatureBuffer(signatureLength, '0'); + const auto error = olm_account_sign(m_account, message.data(), message.length(), + signatureBuffer.data(), signatureLength); + + if (error == olm_error()) { + throw lastError(m_account); + } + return signatureBuffer; +} + +QByteArray QOlmAccount::signIdentityKeys() const +{ + const auto keys = identityKeys(); + const QJsonObject j{ {Curve25519Key, QString(keys.curve25519)}, {Ed25519Key, QString(keys.ed25519)} }; + QJsonDocument doc; + doc.setObject(j); + return sign(doc.toJson()); + +} + +size_t QOlmAccount::maxNumberOfOneTimeKeys() const +{ + return olm_account_max_number_of_one_time_keys(m_account); +} + +void QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const +{ + const size_t randomLen = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); + QByteArray randomBuffer = getRandom(randomLen); + const auto error = olm_account_generate_one_time_keys(m_account, numberOfKeys, randomBuffer.data(), randomLen); + + if (error == olm_error()) { + throw lastError(m_account); + } +} + +OneTimeKeys QOlmAccount::oneTimeKeys() const +{ + const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); + QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0'); + + const auto error = olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), oneTimeKeyLength); + if (error == olm_error()) { + throw lastError(m_account); + } + const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); + OneTimeKeys oneTimeKeys; + + for (const QJsonValue &key1 : json.keys()) { + auto oneTimeKeyObject = json[key1.toString()].toObject(); + auto keyMap = QMap(); + for (const QString &key2 : oneTimeKeyObject.keys()) { + keyMap[key2] = oneTimeKeyObject[key2].toString(); + } + oneTimeKeys.keys[key1.toString()] = keyMap; + } + return oneTimeKeys; +} + +QMap QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const +{ + QMap signedOneTimeKeys; + for (const auto &keyid : keys.curve25519().keys()) { + const auto oneTimeKey = keys.curve25519()[keyid]; + QByteArray sign = signOneTimeKey(oneTimeKey); + signedOneTimeKeys["signed_curve25519:" + keyid] = signedOneTimeKey(oneTimeKey.toUtf8(), sign); + } + return signedOneTimeKeys; +} + +SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray &key, const QString &signature) const +{ + SignedOneTimeKey sign{}; + sign.key = key; + sign.signatures = {{m_userId, {{"ed25519:" + m_deviceId, signature}}}}; + return sign; +} + +QByteArray QOlmAccount::signOneTimeKey(const QString &key) const +{ + QJsonDocument j(QJsonObject{{"key", key}}); + return sign(j.toJson()); +} + +OlmAccount *Quotient::QOlmAccount::data() +{ + return m_account; +} + +std::variant, OlmError> QOlmAccount::createInboundSession(const Message &preKeyMessage) +{ + Q_ASSERT(preKeyMessage.type() == Message::PreKey); + return QOlmSession::createInboundSession(this, preKeyMessage); +} + +std::variant, OlmError> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const Message &preKeyMessage) +{ + Q_ASSERT(preKeyMessage.type() == Message::PreKey); + return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); +} + +std::variant, OlmError> QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) +{ + return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); +} + +#endif diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h new file mode 100644 index 00000000..f98d78ba --- /dev/null +++ b/lib/crypto/qolmaccount.h @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later +#pragma once +#ifdef Quotient_E2EE_ENABLED + +#include "crypto/e2ee.h" +#include "crypto/errors.h" +#include "crypto/session.h" +#include +#include + +struct OlmAccount; + +namespace Quotient { + +class QOlmSession; + +//! An olm account manages all cryptographic keys used on a device. +//! \code{.cpp} +//! const auto olmAccount = new QOlmAccount(this); +//! \endcode +class QOlmAccount +{ +public: + QOlmAccount(const QString &userId, const QString &deviceId); + ~QOlmAccount(); + + //! Creates a new instance of OlmAccount. During the instantiation + //! the Ed25519 fingerprint key pair and the Curve25519 identity key + //! pair are generated. For more information see here. + //! This needs to be called before any other action or use unpickle() instead. + void createNewAccount(); + + //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmAccount`. + //! This needs to be called before any other action or use createNewAccount() instead. + void unpickle(QByteArray &picked, const PicklingMode &mode); + + //! Serialises an OlmAccount to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + + //! Returns the account's public identity keys already formatted as JSON + IdentityKeys identityKeys() const; + + //! Returns the signature of the supplied message. + QByteArray sign(const QByteArray &message) const; + + //! Sign identity keys. + QByteArray signIdentityKeys() const; + + //! Maximum number of one time keys that this OlmAccount can + //! currently hold. + size_t maxNumberOfOneTimeKeys() const; + + //! Generates the supplied number of one time keys. + void generateOneTimeKeys(size_t numberOfKeys) const; + + //! Gets the OlmAccount's one time keys formatted as JSON. + OneTimeKeys oneTimeKeys() const; + + //! Sign all time key. + QMap signOneTimeKeys(const OneTimeKeys &keys) const; + + //! Sign one time key. + QByteArray signOneTimeKey(const QString &key) const; + + SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; + + //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. + //! + //! \param message An Olm pre-key message that was encrypted for this account. + std::variant, OlmError> createInboundSession(const Message &preKeyMessage); + + //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. + //! + //! \param theirIdentityKey - The identity key of an Olm account that + //! encrypted this Olm message. + std::variant, OlmError> createInboundSessionFrom(const QByteArray &theirIdentityKey, const Message &preKeyMessage); + + //! Creates an outbound session for sending messages to a specific + /// identity and one time key. + std::variant, OlmError> createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); + + // HACK do not use directly + QOlmAccount(OlmAccount *account); + OlmAccount *data(); +private: + OlmAccount *m_account = nullptr; + QString m_userId; + QString m_deviceId; +}; + +} // namespace Quotient + +#endif diff --git a/lib/crypto/qolminboundsession.cpp b/lib/crypto/qolminboundsession.cpp new file mode 100644 index 00000000..539fdc51 --- /dev/null +++ b/lib/crypto/qolminboundsession.cpp @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/qolminboundsession.h" +#include +#include +using namespace Quotient; + +OlmError lastError(OlmInboundGroupSession *session) { + const std::string error_raw = olm_inbound_group_session_last_error(session); + + std::cout << error_raw; + return fromString(error_raw); +} + +QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session) + : m_groupSession(session) +{ +} + +QOlmInboundGroupSession::~QOlmInboundGroupSession() +{ + olm_clear_inbound_group_session(m_groupSession); + //delete[](reinterpret_cast(m_groupSession)); +} + +std::unique_ptr QOlmInboundGroupSession::create(const QByteArray &key) +{ + const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); + const auto temp = key; + const auto error = olm_init_inbound_group_session(olmInboundGroupSession, + reinterpret_cast(temp.data()), temp.size()); + + if (error == olm_error()) { + throw lastError(olmInboundGroupSession); + } + + return std::make_unique(olmInboundGroupSession); +} + + +std::unique_ptr QOlmInboundGroupSession::import(const QByteArray &key) +{ + const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); + QByteArray keyBuf = key; + + const auto error = olm_import_inbound_group_session(olmInboundGroupSession, + reinterpret_cast(keyBuf.data()), keyBuf.size()); + if (error == olm_error()) { + throw lastError(olmInboundGroupSession); + } + + return std::make_unique(olmInboundGroupSession); +} + +QByteArray toKey(const PicklingMode &mode) +{ + if (std::holds_alternative(mode)) { + return ""; + } + return std::get(mode).key; +} + +QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const +{ + QByteArray pickledBuf(olm_pickle_inbound_group_session_length(m_groupSession), '0'); + const QByteArray key = toKey(mode); + const auto error = olm_pickle_inbound_group_session(m_groupSession, key.data(), key.length(), pickledBuf.data(), + pickledBuf.length()); + if (error == olm_error()) { + throw lastError(m_groupSession); + } + return pickledBuf; +} + +std::variant, OlmError> QOlmInboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +{ + QByteArray pickledBuf = pickled; + const auto groupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); + QByteArray key = toKey(mode); + const auto error = olm_unpickle_inbound_group_session(groupSession, key.data(), key.length(), + pickledBuf.data(), pickledBuf.size()); + if (error == olm_error()) { + return lastError(groupSession); + } + key.clear(); + + return std::make_unique(groupSession); +} + +std::variant, OlmError> QOlmInboundGroupSession::decrypt(const QByteArray &message) +{ + // This is for capturing the output of olm_group_decrypt + uint32_t messageIndex = 0; + + // We need to clone the message because + // olm_decrypt_max_plaintext_length destroys the input buffer + QByteArray messageBuf(message.length(), '0'); + std::copy(message.begin(), message.end(), messageBuf.begin()); + + QByteArray plaintextBuf(olm_group_decrypt_max_plaintext_length(m_groupSession, + reinterpret_cast(messageBuf.data()), messageBuf.length()), '0'); + + messageBuf = QByteArray(message.length(), '0'); + std::copy(message.begin(), message.end(), messageBuf.begin()); + + const auto plaintextLen = olm_group_decrypt(m_groupSession, reinterpret_cast(messageBuf.data()), + messageBuf.length(), reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), &messageIndex); + + // Error code or plaintext length is returned + const auto decryptError = plaintextLen; + + if (decryptError == olm_error()) { + return lastError(m_groupSession); + } + + QByteArray output(plaintextLen, '0'); + std::memcpy(output.data(), plaintextBuf.data(), plaintextLen); + + return std::make_pair(QString(output), messageIndex); +} + +std::variant QOlmInboundGroupSession::exportSession(uint32_t messageIndex) +{ + const auto keyLen = olm_export_inbound_group_session_length(m_groupSession); + QByteArray keyBuf(keyLen, '0'); + const auto error = olm_export_inbound_group_session(m_groupSession, reinterpret_cast(keyBuf.data()), keyLen, messageIndex); + + if (error == olm_error()) { + return lastError(m_groupSession); + } + return keyBuf; +} + +uint32_t QOlmInboundGroupSession::firstKnownIndex() const +{ + return olm_inbound_group_session_first_known_index(m_groupSession); +} + +QByteArray QOlmInboundGroupSession::sessionId() const +{ + QByteArray sessionIdBuf(olm_inbound_group_session_id_length(m_groupSession), '0'); + const auto error = olm_inbound_group_session_id(m_groupSession, reinterpret_cast(sessionIdBuf.data()), + sessionIdBuf.length()); + if (error == olm_error()) { + throw lastError(m_groupSession); + } + return sessionIdBuf; +} + +bool QOlmInboundGroupSession::isVerified() const +{ + return olm_inbound_group_session_is_verified(m_groupSession) != 0; +} +#endif diff --git a/lib/crypto/qolminboundsession.h b/lib/crypto/qolminboundsession.h new file mode 100644 index 00000000..a58fbbbc --- /dev/null +++ b/lib/crypto/qolminboundsession.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED + +#include +#include +#include +#include "olm/olm.h" +#include "crypto/errors.h" +#include "crypto/e2ee.h" + +namespace Quotient { + +//! An in-bound group session is responsible for decrypting incoming +//! communication in a Megolm session. +struct QOlmInboundGroupSession +{ +public: + ~QOlmInboundGroupSession(); + //! Creates a new instance of `OlmInboundGroupSession`. + static std::unique_ptr create(const QByteArray &key); + //! Import an inbound group session, from a previous export. + static std::unique_ptr import(const QByteArray &key); + //! Serialises an `OlmInboundGroupSession` to encrypted Base64. + QByteArray pickle(const PicklingMode &mode) const; + //! Deserialises from encrypted Base64 that was previously obtained by pickling + //! an `OlmInboundGroupSession`. + static std::variant, OlmError> unpickle(QByteArray &picked, const PicklingMode &mode); + //! Decrypts ciphertext received for this group session. + std::variant, OlmError> decrypt(const QByteArray &message); + //! Export the base64-encoded ratchet key for this session, at the given index, + //! in a format which can be used by import. + std::variant exportSession(uint32_t messageIndex); + //! Get the first message index we know how to decrypt. + uint32_t firstKnownIndex() const; + //! Get a base64-encoded identifier for this session. + QByteArray sessionId() const; + bool isVerified() const; + QOlmInboundGroupSession(OlmInboundGroupSession *session); +private: + OlmInboundGroupSession *m_groupSession; +}; + +using QOlmInboundGroupSessionPtr = std::unique_ptr; +using OlmInboundGroupSessionPtr = std::unique_ptr; +} // namespace Quotient +#endif diff --git a/lib/crypto/qolmoutboundsession.cpp b/lib/crypto/qolmoutboundsession.cpp new file mode 100644 index 00000000..3bfb0187 --- /dev/null +++ b/lib/crypto/qolmoutboundsession.cpp @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/qolmoutboundsession.h" +#include "crypto/utils.h" + +using namespace Quotient; + +OlmError lastError(OlmOutboundGroupSession *session) { + const std::string error_raw = olm_outbound_group_session_last_error(session); + + return fromString(error_raw); +} + +QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session) + : m_groupSession(session) +{ +} + +QOlmOutboundGroupSession::~QOlmOutboundGroupSession() +{ + olm_clear_outbound_group_session(m_groupSession); + delete[](reinterpret_cast(m_groupSession)); +} + +std::unique_ptr QOlmOutboundGroupSession::create() +{ + auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); + const auto randomLen = olm_init_outbound_group_session_random_length(olmOutboundGroupSession); + QByteArray randomBuf = getRandom(randomLen); + + const auto error = olm_init_outbound_group_session(olmOutboundGroupSession, + reinterpret_cast(randomBuf.data()), randomBuf.length()); + + if (error == olm_error()) { + throw lastError(olmOutboundGroupSession); + } + + const auto keyMaxLength = olm_outbound_group_session_key_length(olmOutboundGroupSession); + QByteArray keyBuffer(keyMaxLength, '0'); + olm_outbound_group_session_key(olmOutboundGroupSession, reinterpret_cast(keyBuffer.data()), + keyMaxLength); + + randomBuf.clear(); + + return std::make_unique(olmOutboundGroupSession); +} + +std::variant QOlmOutboundGroupSession::pickle(const PicklingMode &mode) +{ + QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); + QByteArray key = toKey(mode); + const auto error = olm_pickle_outbound_group_session(m_groupSession, key.data(), key.length(), + pickledBuf.data(), pickledBuf.length()); + + if (error == olm_error()) { + return lastError(m_groupSession); + } + + key.clear(); + + return pickledBuf; +} + + +std::variant, OlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +{ + QByteArray pickledBuf = pickled; + auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); + QByteArray key = toKey(mode); + const auto error = olm_unpickle_outbound_group_session(olmOutboundGroupSession, key.data(), key.length(), + pickled.data(), pickled.length()); + if (error == olm_error()) { + return lastError(olmOutboundGroupSession); + } + const auto idMaxLength = olm_outbound_group_session_id_length(olmOutboundGroupSession); + QByteArray idBuffer(idMaxLength, '0'); + olm_outbound_group_session_id(olmOutboundGroupSession, reinterpret_cast(idBuffer.data()), + idBuffer.length()); + + key.clear(); + return std::make_unique(olmOutboundGroupSession); +} + +std::variant QOlmOutboundGroupSession::encrypt(const QString &plaintext) +{ + QByteArray plaintextBuf = plaintext.toUtf8(); + const auto messageMaxLen = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); + QByteArray messageBuf(messageMaxLen, '0'); + const auto error = olm_group_encrypt(m_groupSession, reinterpret_cast(plaintextBuf.data()), + plaintextBuf.length(), reinterpret_cast(messageBuf.data()), messageBuf.length()); + + if (error == olm_error()) { + return lastError(m_groupSession); + } + + return messageBuf; +} + +uint32_t QOlmOutboundGroupSession::sessionMessageIndex() const +{ + return olm_outbound_group_session_message_index(m_groupSession); +} + +QByteArray QOlmOutboundGroupSession::sessionId() const +{ + const auto idMaxLength = olm_outbound_group_session_id_length(m_groupSession); + QByteArray idBuffer(idMaxLength, '0'); + const auto error = olm_outbound_group_session_id(m_groupSession, reinterpret_cast(idBuffer.data()), + idBuffer.length()); + if (error == olm_error()) { + throw lastError(m_groupSession); + } + return idBuffer; +} + +std::variant QOlmOutboundGroupSession::sessionKey() const +{ + const auto keyMaxLength = olm_outbound_group_session_key_length(m_groupSession); + QByteArray keyBuffer(keyMaxLength, '0'); + const auto error = olm_outbound_group_session_key(m_groupSession, reinterpret_cast(keyBuffer.data()), + keyMaxLength); + if (error == olm_error()) { + return lastError(m_groupSession); + } + return keyBuffer; +} + +#endif diff --git a/lib/crypto/qolmoutboundsession.h b/lib/crypto/qolmoutboundsession.h new file mode 100644 index 00000000..6c6c635c --- /dev/null +++ b/lib/crypto/qolmoutboundsession.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later +#pragma once +#ifdef Quotient_E2EE_ENABLED + +#include "olm/olm.h" // from Olm +#include "crypto/errors.h" +#include "crypto/e2ee.h" +#include + +namespace Quotient { + + +//! An out-bound group session is responsible for encrypting outgoing +//! communication in a Megolm session. +class QOlmOutboundGroupSession +{ +public: + ~QOlmOutboundGroupSession(); + //! Creates a new instance of `QOlmOutboundGroupSession`. + //! Throw OlmError on errors + static std::unique_ptr create(); + //! Serialises an `QOlmOutboundGroupSession` to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by + //! pickling a `QOlmOutboundGroupSession`. + static std::variant, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + std::variant encrypt(const QString &plaintext); + + //! Get the current message index for this session. + //! + //! Each message is sent with an increasing index; this returns the + //! index for the next message. + uint32_t sessionMessageIndex() const; + + //! Get a base64-encoded identifier for this session. + QByteArray sessionId() const; + + //! Get the base64-encoded current ratchet key for this session. + //! + //! Each message is sent with a different ratchet key. This function returns the + //! ratchet key that will be used for the next message. + std::variant sessionKey() const; + QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); +private: + OlmOutboundGroupSession *m_groupSession; +}; + +using QOlmOutboundGroupSessionPtr = std::unique_ptr; +using OlmOutboundGroupSessionPtr = std::unique_ptr; +} +#endif diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp new file mode 100644 index 00000000..afa42728 --- /dev/null +++ b/lib/crypto/qolmsession.cpp @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "crypto/qolmsession.h" + +using namespace Quotient; + +std::optional fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext) +{ + if (messageType == OLM_MESSAGE_TYPE_PRE_KEY) { + return PreKeyMessage { ciphertext }; + } else if (messageType == OLM_MESSAGE_TYPE_MESSAGE) { + return Message { ciphertext }; + } + return std::nullopt; +} + +std::pair toPair(const OlmMessage &message) +{ + return std::visit([](auto &arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return std::make_pair(MessageType, QByteArray(arg.message)); + } else if constexpr (std::is_same_v) { + return std::make_pair(PreKeyType, QByteArray(arg.message)); + } + }, message); +} diff --git a/lib/crypto/qolmsession.h b/lib/crypto/qolmsession.h new file mode 100644 index 00000000..3be3c7fc --- /dev/null +++ b/lib/crypto/qolmsession.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include "crypto/e2ee.h" +#include "crypto/errors.h" + +namespace Quotient { + +//! An encrypted Olm message. +struct Message { + QByteArray message; +}; + +//! A encrypted Olm pre-key message. +//! +//! This message, unlike a normal Message, can be used to create new Olm sessions. +struct PreKeyMessage +{ + QByteArray message; +}; + +enum OlmMessageType +{ + PreKeyType, + MessageType, +}; + +using OlmMessage = std::variant; + +std::optional fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext); + +std::pair toPair(const OlmMessage &message); + +//class QOlmSession +//{ +// /// Creates an inbound session for sending/receiving messages from a received 'prekey' message. +// static std::variant, OlmError> createInboundSession(const QOlmAccount &account, +// PreKeyMessage &message); +// +////private: +// //static std::variant, OlmError> createSessionWith(std::function> func); +//} + +} diff --git a/lib/crypto/session.cpp b/lib/crypto/session.cpp new file mode 100644 index 00000000..8b2cb022 --- /dev/null +++ b/lib/crypto/session.cpp @@ -0,0 +1,242 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/session.h" +#include "crypto/utils.h" +#include "logging.h" +#include + +using namespace Quotient; + +OlmError lastError(OlmSession* session) { + const std::string error_raw = olm_session_last_error(session); + + return fromString(error_raw); +} + +Quotient::QOlmSession::~QOlmSession() +{ + olm_clear_session(m_session); + delete[](reinterpret_cast(m_session)); +} + +OlmSession* QOlmSession::create() +{ + return olm_session(new uint8_t[olm_session_size()]); +} + +std::variant, OlmError> QOlmSession::createInbound(QOlmAccount *account, const Message &preKeyMessage, bool from, const QString &theirIdentityKey) +{ + if (preKeyMessage.type() != Message::PreKey) { + qCDebug(E2EE) << "The message is not a pre-key"; + throw BadMessageFormat; + } + + const auto olmSession = create(); + + QByteArray oneTimeKeyMessageBuf = preKeyMessage.toCiphertext(); + QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); + size_t error = 0; + if (from) { + error = olm_create_inbound_session_from(olmSession, account->data(), theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); + } else { + error = olm_create_inbound_session(olmSession, account->data(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); + } + + if (error == olm_error()) { + const auto lastErr = lastError(olmSession); + if (lastErr == OlmError::NotEnoughRandom) { + throw lastErr; + } + return lastErr; + } + + return std::make_unique(olmSession); +} + +std::variant, OlmError> QOlmSession::createInboundSession(QOlmAccount *account, const Message &preKeyMessage) +{ + return createInbound(account, preKeyMessage); +} + +std::variant, OlmError> QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage) +{ + return createInbound(account, preKeyMessage, true, theirIdentityKey); +} + +std::variant, OlmError> QOlmSession::createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey) +{ + auto *olmOutboundSession = create(); + const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); + QByteArray randomBuf = getRandom(randomLen); + + QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); + QByteArray theirOneTimeKeyBuf = theirOneTimeKey.toUtf8(); + const auto error = olm_create_outbound_session(olmOutboundSession, + account->data(), + reinterpret_cast(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), + reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length()); + + if (error == olm_error()) { + const auto lastErr = lastError(olmOutboundSession); + if (lastErr == OlmError::NotEnoughRandom) { + throw lastErr; + } + return lastErr; + } + + randomBuf.clear(); + return std::make_unique(olmOutboundSession); +} + +std::variant QOlmSession::pickle(const PicklingMode &mode) +{ + QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); + QByteArray key = toKey(mode); + const auto error = olm_pickle_session(m_session, key.data(), key.length(), + pickledBuf.data(), pickledBuf.length()); + + if (error == olm_error()) { + return lastError(m_session); + } + + key.clear(); + + return pickledBuf; +} + +std::variant, OlmError> QOlmSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +{ + QByteArray pickledBuf = pickled; + auto *olmSession = create(); + QByteArray key = toKey(mode); + const auto error = olm_unpickle_session(olmSession, key.data(), key.length(), + pickledBuf.data(), pickledBuf.length()); + if (error == olm_error()) { + return lastError(olmSession); + } + + key.clear(); + return std::make_unique(olmSession); +} + +Message QOlmSession::encrypt(const QString &plaintext) +{ + QByteArray plaintextBuf = plaintext.toUtf8(); + const auto messageMaxLen = olm_encrypt_message_length(m_session, plaintextBuf.length()); + QByteArray messageBuf(messageMaxLen, '0'); + const auto messageType = encryptMessageType(); + const auto randomLen = olm_encrypt_random_length(m_session); + QByteArray randomBuf = getRandom(randomLen); + const auto error = olm_encrypt(m_session, + reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length(), + reinterpret_cast(messageBuf.data()), messageBuf.length()); + + if (error == olm_error()) { + throw lastError(m_session); + } + + return Message(messageBuf, messageType); +} + +std::variant QOlmSession::decrypt(const Message &message) const +{ + const auto messageType = message.type(); + const auto ciphertext = message.toCiphertext(); + const auto messageTypeValue = messageType == Message::Type::General + ? OLM_MESSAGE_TYPE_MESSAGE : OLM_MESSAGE_TYPE_PRE_KEY; + + // We need to clone the message because + // olm_decrypt_max_plaintext_length destroys the input buffer + QByteArray messageBuf(ciphertext.length(), '0'); + std::copy(message.begin(), message.end(), messageBuf.begin()); + + const auto plaintextMaxLen = olm_decrypt_max_plaintext_length(m_session, messageTypeValue, + reinterpret_cast(messageBuf.data()), messageBuf.length()); + + if (plaintextMaxLen == olm_error()) { + return lastError(m_session); + } + + QByteArray plaintextBuf(plaintextMaxLen, '0'); + QByteArray messageBuf2(ciphertext.length(), '0'); + std::copy(message.begin(), message.end(), messageBuf2.begin()); + + const auto plaintextResultLen = olm_decrypt(m_session, messageTypeValue, + reinterpret_cast(messageBuf2.data()), messageBuf2.length(), + reinterpret_cast(plaintextBuf.data()), plaintextMaxLen); + + if (plaintextResultLen == olm_error()) { + const auto lastErr = lastError(m_session); + if (lastErr == OlmError::OutputBufferTooSmall) { + throw lastErr; + } + return lastErr; + } + QByteArray output(plaintextResultLen, '0'); + std::memcpy(output.data(), plaintextBuf.data(), plaintextResultLen); + plaintextBuf.clear(); + return output; +} + +Message::Type QOlmSession::encryptMessageType() +{ + const auto messageTypeResult = olm_encrypt_message_type(m_session); + if (messageTypeResult == olm_error()) { + throw lastError(m_session); + } + if (messageTypeResult == OLM_MESSAGE_TYPE_PRE_KEY) { + return Message::PreKey; + } + return Message::General; +} + +QByteArray QOlmSession::sessionId() const +{ + const auto idMaxLength = olm_session_id_length(m_session); + QByteArray idBuffer(idMaxLength, '0'); + const auto error = olm_session_id(m_session, reinterpret_cast(idBuffer.data()), + idBuffer.length()); + if (error == olm_error()) { + throw lastError(m_session); + } + return idBuffer; +} + +bool QOlmSession::hasReceivedMessage() const +{ + return olm_session_has_received_message(m_session); +} + +std::variant QOlmSession::matchesInboundSession(Message &preKeyMessage) +{ + Q_ASSERT(preKeyMessage.type() == Message::Type::PreKey); + QByteArray oneTimeKeyBuf(preKeyMessage.data()); + const auto matchesResult = olm_matches_inbound_session(m_session, oneTimeKeyBuf.data(), oneTimeKeyBuf.length()); + + if (matchesResult == olm_error()) { + return lastError(m_session); + } + switch (matchesResult) { + case 0: + return false; + case 1: + return true; + default: + return OlmError::Unknown; + } +} + +QOlmSession::QOlmSession(OlmSession *session) + : m_session(session) +{ +} + +#endif // Quotient_E2EE_ENABLED + + + diff --git a/lib/crypto/session.h b/lib/crypto/session.h new file mode 100644 index 00000000..24702564 --- /dev/null +++ b/lib/crypto/session.h @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED + +#include +#include +#include "crypto/e2ee.h" +#include "crypto/message.h" +#include "crypto/errors.h" +#include "crypto/qolmaccount.h" + +namespace Quotient { + +class QOlmAccount; +class QOlmSession; + + +//! Either an outbound or inbound session for secure communication. +class QOlmSession +{ +public: + ~QOlmSession(); + //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. + static std::variant, OlmError> createInboundSession(QOlmAccount *account, const Message &preKeyMessage); + static std::variant, OlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage); + static std::variant, OlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey); + //! Serialises an `QOlmSession` to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. + static std::variant, OlmError> unpickle(const QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + Message encrypt(const QString &plaintext); + + //! Decrypts a message using this session. Decoding is lossy, meaing if + //! the decrypted plaintext contains invalid UTF-8 symbols, they will + //! be returned as `U+FFFD` (�). + std::variant decrypt(const Message &message) const; + + //! Get a base64-encoded identifier for this session. + QByteArray sessionId() const; + + //! The type of the next message that will be returned from encryption. + Message::Type encryptMessageType(); + + //! Checker for any received messages for this session. + bool hasReceivedMessage() const; + + //! Checks if the 'prekey' message is for this in-bound session. + std::variant matchesInboundSession(Message &preKeyMessage); + + friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) + { + return lhs.sessionId() < rhs.sessionId(); + } + + friend bool operator<(const std::unique_ptr &lhs, const std::unique_ptr &rhs) { + return *lhs < *rhs; + } + + QOlmSession(OlmSession* session); +private: + //! Helper function for creating new sessions and handling errors. + static OlmSession* create(); + static std::variant, OlmError> createInbound(QOlmAccount *account, const Message& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); + OlmSession* m_session; +}; + + +//using QOlmSessionPtr = std::unique_ptr; + +} //namespace Quotient + +#endif // Quotient_E2EE_ENABLED diff --git a/lib/crypto/utils.cpp b/lib/crypto/utils.cpp new file mode 100644 index 00000000..cb20abf8 --- /dev/null +++ b/lib/crypto/utils.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/utils.h" +#include +#include + +using namespace Quotient; + +QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) +{ + if (std::holds_alternative(mode)) { + return ""; + } + return std::get(mode).key; +} + +QByteArray Quotient::getRandom(size_t bufferSize) +{ + QByteArray buffer(bufferSize, '0'); + RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); + return buffer; +} +#endif diff --git a/lib/crypto/utils.h b/lib/crypto/utils.h new file mode 100644 index 00000000..cea87144 --- /dev/null +++ b/lib/crypto/utils.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once +#ifdef Quotient_E2EE_ENABLED + +#include "crypto/e2ee.h" + +namespace Quotient { +// Convert PicklingMode to key +QByteArray toKey(const PicklingMode &mode); +QByteArray getRandom(size_t bufferSize); +} +#endif diff --git a/lib/olm/e2ee.h b/lib/olm/e2ee.h deleted file mode 100644 index 74f876e4..00000000 --- a/lib/olm/e2ee.h +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-FileCopyrightText: 2021 Carl Schwan -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "util.h" -#include -#include -#include -#include - -#include - -namespace Quotient { -inline const auto CiphertextKeyL = "ciphertext"_ls; -inline const auto SenderKeyKeyL = "sender_key"_ls; -inline const auto DeviceIdKeyL = "device_id"_ls; -inline const auto SessionIdKeyL = "session_id"_ls; - -inline const auto AlgorithmKeyL = "algorithm"_ls; -inline const auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; -inline const auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls; - -inline const auto AlgorithmKey = QStringLiteral("algorithm"); -inline const auto RotationPeriodMsKey = QStringLiteral("rotation_period_ms"); -inline const auto RotationPeriodMsgsKey = - QStringLiteral("rotation_period_msgs"); - -inline const auto Ed25519Key = QStringLiteral("ed25519"); -inline const auto Curve25519Key = QStringLiteral("curve25519"); -inline const auto SignedCurve25519Key = QStringLiteral("signed_curve25519"); -inline const auto OlmV1Curve25519AesSha2AlgoKey = - QStringLiteral("m.olm.v1.curve25519-aes-sha2"); -inline const auto MegolmV1AesSha2AlgoKey = - QStringLiteral("m.megolm.v1.aes-sha2"); -inline const QStringList SupportedAlgorithms = { OlmV1Curve25519AesSha2AlgoKey, - MegolmV1AesSha2AlgoKey }; - -struct Unencrypted {}; -struct Encrypted { - QByteArray key; -}; - -using PicklingMode = std::variant; - -template struct overloaded : Ts... { using Ts::operator()...; }; -template overloaded(Ts...) -> overloaded; - -struct IdentityKeys -{ - QByteArray curve25519; - QByteArray ed25519; -}; - -//! Struct representing the one-time keys. -struct OneTimeKeys -{ - QMap> keys; - - //! Get the HashMap containing the curve25519 one-time keys. - QMap curve25519() const; - - //! Get a reference to the hashmap corresponding to given key type. - std::optional> get(QString keyType) const; -}; - -//! Struct representing the signed one-time keys. -struct SignedOneTimeKey -{ - //! Required. The unpadded Base64-encoded 32-byte Curve25519 public key. - QString key; - - //! Required. Signatures of the key object. - //! The signature is calculated using the process described at Signing JSON. - QMap> signatures; -}; - -bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); - -} // namespace Quotient diff --git a/lib/olm/errors.cpp b/lib/olm/errors.cpp deleted file mode 100644 index a687e807..00000000 --- a/lib/olm/errors.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED -#include "olm/errors.h" - -Quotient::OlmError Quotient::fromString(const std::string &error_raw) { - if (error_raw.compare("BAD_ACCOUNT_KEY")) { - return OlmError::BadAccountKey; - } else if (error_raw.compare("BAD_MESSAGE_KEY_ID")) { - return OlmError::BadMessageKeyId; - } else if (error_raw.compare("INVALID_BASE64")) { - return OlmError::InvalidBase64; - } else if (error_raw.compare("NOT_ENOUGH_RANDOM")) { - return OlmError::NotEnoughRandom; - } else if (error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { - return OlmError::OutputBufferTooSmall; - } else { - return OlmError::Unknown; - } -} -#endif diff --git a/lib/olm/errors.h b/lib/olm/errors.h deleted file mode 100644 index 09d2a989..00000000 --- a/lib/olm/errors.h +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#ifdef Quotient_E2EE_ENABLED -#include - -namespace Quotient { -//! All errors that could be caused by an operation regarding Olm -//! Errors are named exactly like the ones in libolm. -enum OlmError -{ - BadAccountKey, - BadMessageFormat, - BadMessageKeyId, - BadMessageMac, - BadMessageVersion, - InvalidBase64, - NotEnoughRandom, - OutputBufferTooSmall, - UnknownMessageIndex, - Unknown, -}; - -OlmError fromString(const std::string &error_raw); - -} //namespace Quotient - -#endif diff --git a/lib/olm/message.cpp b/lib/olm/message.cpp deleted file mode 100644 index ac7038ae..00000000 --- a/lib/olm/message.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "olm/message.h" - -using namespace Quotient; - -Message::Message(const QByteArray &ciphertext, Message::Type type) - : QByteArray(std::move(ciphertext)) - , m_messageType(type) -{ - Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); -} - -Message::Message(const Message &message) - : QByteArray(message) - , m_messageType(message.type()) -{ -} - -Message::Type Message::type() const -{ - return m_messageType; -} - -QByteArray Message::toCiphertext() const -{ - return QByteArray(*this); -} - -Message Message::fromCiphertext(const QByteArray &ciphertext) -{ - return Message(ciphertext, Message::General); -} - - -#endif // Quotient_E2EE_ENABLED - - - diff --git a/lib/olm/message.h b/lib/olm/message.h deleted file mode 100644 index d2fe871e..00000000 --- a/lib/olm/message.h +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#ifdef Quotient_E2EE_ENABLED - -#include -#include - -namespace Quotient { - -/*! \brief A wrapper around an olm encrypted message - * - * This class encapsulates a Matrix olm encrypted message, - * passed in either of 2 forms: a general message or a pre-key message. - * - * The class provides functions to get a type and the ciphertext. - */ -class Message : public QByteArray { - Q_GADGET -public: - enum Type { - General, - PreKey, - }; - Q_ENUM(Type) - - Message() = default; - explicit Message(const QByteArray &ciphertext, Type type = General); - explicit Message(const Message &message); - - static Message fromCiphertext(const QByteArray &ciphertext); - - Q_INVOKABLE Type type() const; - Q_INVOKABLE QByteArray toCiphertext() const; - -private: - Type m_messageType = General; -}; - - -} //namespace Quotient - -#endif // Quotient_E2EE_ENABLED diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp deleted file mode 100644 index ef51a395..00000000 --- a/lib/olm/qolmaccount.cpp +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "qolmaccount.h" -#include "olm/utils.h" -#include -#include -#include -#include - -using namespace Quotient; - -QMap OneTimeKeys::curve25519() const -{ - return keys[QStringLiteral("curve25519")]; -} - -std::optional> OneTimeKeys::get(QString keyType) const -{ - if (!keys.contains(keyType)) { - return std::nullopt; - } - return keys[keyType]; -} - -bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) -{ - return lhs.curve25519 == rhs.curve25519 &&& lhs.ed25519 == rhs.ed25519; -} - -// Convert olm error to enum -OlmError lastError(OlmAccount *account) { - const std::string error_raw = olm_account_last_error(account); - - return fromString(error_raw); -} - -QByteArray getRandom(size_t bufferSize) -{ - QByteArray buffer(bufferSize, '0'); - std::generate(buffer.begin(), buffer.end(), std::rand); - return buffer; -} - -QOlmAccount::QOlmAccount(const QString &userId, const QString &deviceId) - : m_userId(userId) - , m_deviceId(deviceId) -{ -} - -QOlmAccount::~QOlmAccount() -{ - olm_clear_account(m_account); - delete[](reinterpret_cast(m_account)); -} - -void QOlmAccount::createNewAccount() -{ - m_account = olm_account(new uint8_t[olm_account_size()]); - size_t randomSize = olm_create_account_random_length(m_account); - QByteArray randomData = getRandom(randomSize); - const auto error = olm_create_account(m_account, randomData.data(), randomSize); - if (error == olm_error()) { - throw lastError(m_account); - } -} - -void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) -{ - m_account = olm_account(new uint8_t[olm_account_size()]); - const QByteArray key = toKey(mode); - const auto error = olm_unpickle_account(m_account, key.data(), key.length(), pickled.data(), pickled.size()); - if (error == olm_error()) { - throw lastError(m_account); - } -} - -std::variant QOlmAccount::pickle(const PicklingMode &mode) -{ - const QByteArray key = toKey(mode); - const size_t pickleLength = olm_pickle_account_length(m_account); - QByteArray pickleBuffer(pickleLength, '0'); - const auto error = olm_pickle_account(m_account, key.data(), - key.length(), pickleBuffer.data(), pickleLength); - if (error == olm_error()) { - return lastError(m_account); - } - return pickleBuffer; -} - -IdentityKeys QOlmAccount::identityKeys() const -{ - const size_t keyLength = olm_account_identity_keys_length(m_account); - QByteArray keyBuffer(keyLength, '0'); - const auto error = olm_account_identity_keys(m_account, keyBuffer.data(), keyLength); - if (error == olm_error()) { - throw lastError(m_account); - } - const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object(); - return IdentityKeys { - key.value(QStringLiteral("curve25519")).toString().toUtf8(), - key.value(QStringLiteral("ed25519")).toString().toUtf8() - }; -} - -QByteArray QOlmAccount::sign(const QByteArray &message) const -{ - const size_t signatureLength = olm_account_signature_length(m_account); - QByteArray signatureBuffer(signatureLength, '0'); - const auto error = olm_account_sign(m_account, message.data(), message.length(), - signatureBuffer.data(), signatureLength); - - if (error == olm_error()) { - throw lastError(m_account); - } - return signatureBuffer; -} - -QByteArray QOlmAccount::signIdentityKeys() const -{ - const auto keys = identityKeys(); - const QJsonObject j{ {Curve25519Key, QString(keys.curve25519)}, {Ed25519Key, QString(keys.ed25519)} }; - QJsonDocument doc; - doc.setObject(j); - return sign(doc.toJson()); - -} - -size_t QOlmAccount::maxNumberOfOneTimeKeys() const -{ - return olm_account_max_number_of_one_time_keys(m_account); -} - -void QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const -{ - const size_t randomLen = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); - QByteArray randomBuffer = getRandom(randomLen); - const auto error = olm_account_generate_one_time_keys(m_account, numberOfKeys, randomBuffer.data(), randomLen); - - if (error == olm_error()) { - throw lastError(m_account); - } -} - -OneTimeKeys QOlmAccount::oneTimeKeys() const -{ - const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); - QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0'); - - const auto error = olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), oneTimeKeyLength); - if (error == olm_error()) { - throw lastError(m_account); - } - const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); - OneTimeKeys oneTimeKeys; - - for (const QJsonValue &key1 : json.keys()) { - auto oneTimeKeyObject = json[key1.toString()].toObject(); - auto keyMap = QMap(); - for (const QString &key2 : oneTimeKeyObject.keys()) { - keyMap[key2] = oneTimeKeyObject[key2].toString(); - } - oneTimeKeys.keys[key1.toString()] = keyMap; - } - return oneTimeKeys; -} - -QMap QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const -{ - QMap signedOneTimeKeys; - for (const auto &keyid : keys.curve25519().keys()) { - const auto oneTimeKey = keys.curve25519()[keyid]; - QByteArray sign = signOneTimeKey(oneTimeKey); - signedOneTimeKeys["signed_curve25519:" + keyid] = signedOneTimeKey(oneTimeKey.toUtf8(), sign); - } - return signedOneTimeKeys; -} - -SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray &key, const QString &signature) const -{ - SignedOneTimeKey sign{}; - sign.key = key; - sign.signatures = {{m_userId, {{"ed25519:" + m_deviceId, signature}}}}; - return sign; -} - -QByteArray QOlmAccount::signOneTimeKey(const QString &key) const -{ - QJsonDocument j(QJsonObject{{"key", key}}); - return sign(j.toJson()); -} - -OlmAccount *Quotient::QOlmAccount::data() -{ - return m_account; -} - -std::variant, OlmError> QOlmAccount::createInboundSession(const Message &preKeyMessage) -{ - Q_ASSERT(preKeyMessage.type() == Message::PreKey); - return QOlmSession::createInboundSession(this, preKeyMessage); -} - -std::variant, OlmError> QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const Message &preKeyMessage) -{ - Q_ASSERT(preKeyMessage.type() == Message::PreKey); - return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); -} - -std::variant, OlmError> QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) -{ - return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); -} - -#endif diff --git a/lib/olm/qolmaccount.h b/lib/olm/qolmaccount.h deleted file mode 100644 index df5e1be2..00000000 --- a/lib/olm/qolmaccount.h +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later -#pragma once -#ifdef Quotient_E2EE_ENABLED - -#include "olm/e2ee.h" -#include "olm/errors.h" -#include "olm/olm.h" -#include "olm/session.h" -#include - -struct OlmAccount; - -namespace Quotient { - -class QOlmSession; - -//! An olm account manages all cryptographic keys used on a device. -//! \code{.cpp} -//! const auto olmAccount = new QOlmAccount(this); -//! \endcode -class QOlmAccount -{ -public: - QOlmAccount(const QString &userId, const QString &deviceId); - ~QOlmAccount(); - - //! Creates a new instance of OlmAccount. During the instantiation - //! the Ed25519 fingerprint key pair and the Curve25519 identity key - //! pair are generated. For more information see here. - //! This needs to be called before any other action or use unpickle() instead. - void createNewAccount(); - - //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmAccount`. - //! This needs to be called before any other action or use createNewAccount() instead. - void unpickle(QByteArray &picked, const PicklingMode &mode); - - //! Serialises an OlmAccount to encrypted Base64. - std::variant pickle(const PicklingMode &mode); - - //! Returns the account's public identity keys already formatted as JSON - IdentityKeys identityKeys() const; - - //! Returns the signature of the supplied message. - QByteArray sign(const QByteArray &message) const; - - //! Sign identity keys. - QByteArray signIdentityKeys() const; - - //! Maximum number of one time keys that this OlmAccount can - //! currently hold. - size_t maxNumberOfOneTimeKeys() const; - - //! Generates the supplied number of one time keys. - void generateOneTimeKeys(size_t numberOfKeys) const; - - //! Gets the OlmAccount's one time keys formatted as JSON. - OneTimeKeys oneTimeKeys() const; - - //! Sign all time key. - QMap signOneTimeKeys(const OneTimeKeys &keys) const; - - //! Sign one time key. - QByteArray signOneTimeKey(const QString &key) const; - - SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; - - //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. - //! - //! \param message An Olm pre-key message that was encrypted for this account. - std::variant, OlmError> createInboundSession(const Message &preKeyMessage); - - //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. - //! - //! \param theirIdentityKey - The identity key of an Olm account that - //! encrypted this Olm message. - std::variant, OlmError> createInboundSessionFrom(const QByteArray &theirIdentityKey, const Message &preKeyMessage); - - //! Creates an outbound session for sending messages to a specific - /// identity and one time key. - std::variant, OlmError> createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); - - // HACK do not use directly - QOlmAccount(OlmAccount *account); - OlmAccount *data(); -private: - OlmAccount *m_account = nullptr; - QString m_userId; - QString m_deviceId; -}; - -} // namespace Quotient - -#endif diff --git a/lib/olm/qolminboundsession.cpp b/lib/olm/qolminboundsession.cpp deleted file mode 100644 index 11558f51..00000000 --- a/lib/olm/qolminboundsession.cpp +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "olm/qolminboundsession.h" -#include -#include -using namespace Quotient; - -OlmError lastError(OlmInboundGroupSession *session) { - const std::string error_raw = olm_inbound_group_session_last_error(session); - - std::cout << error_raw; - return fromString(error_raw); -} - -QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session) - : m_groupSession(session) -{ -} - -QOlmInboundGroupSession::~QOlmInboundGroupSession() -{ - olm_clear_inbound_group_session(m_groupSession); - //delete[](reinterpret_cast(m_groupSession)); -} - -std::unique_ptr QOlmInboundGroupSession::create(const QByteArray &key) -{ - const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); - const auto temp = key; - const auto error = olm_init_inbound_group_session(olmInboundGroupSession, - reinterpret_cast(temp.data()), temp.size()); - - if (error == olm_error()) { - throw lastError(olmInboundGroupSession); - } - - return std::make_unique(olmInboundGroupSession); -} - - -std::unique_ptr QOlmInboundGroupSession::import(const QByteArray &key) -{ - const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); - QByteArray keyBuf = key; - - const auto error = olm_import_inbound_group_session(olmInboundGroupSession, - reinterpret_cast(keyBuf.data()), keyBuf.size()); - if (error == olm_error()) { - throw lastError(olmInboundGroupSession); - } - - return std::make_unique(olmInboundGroupSession); -} - -QByteArray toKey(const PicklingMode &mode) -{ - if (std::holds_alternative(mode)) { - return ""; - } - return std::get(mode).key; -} - -QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const -{ - QByteArray pickledBuf(olm_pickle_inbound_group_session_length(m_groupSession), '0'); - const QByteArray key = toKey(mode); - const auto error = olm_pickle_inbound_group_session(m_groupSession, key.data(), key.length(), pickledBuf.data(), - pickledBuf.length()); - if (error == olm_error()) { - throw lastError(m_groupSession); - } - return pickledBuf; -} - -std::variant, OlmError> QOlmInboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) -{ - QByteArray pickledBuf = pickled; - const auto groupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); - QByteArray key = toKey(mode); - const auto error = olm_unpickle_inbound_group_session(groupSession, key.data(), key.length(), - pickledBuf.data(), pickledBuf.size()); - if (error == olm_error()) { - return lastError(groupSession); - } - key.clear(); - - return std::make_unique(groupSession); -} - -std::variant, OlmError> QOlmInboundGroupSession::decrypt(const QByteArray &message) -{ - // This is for capturing the output of olm_group_decrypt - uint32_t messageIndex = 0; - - // We need to clone the message because - // olm_decrypt_max_plaintext_length destroys the input buffer - QByteArray messageBuf(message.length(), '0'); - std::copy(message.begin(), message.end(), messageBuf.begin()); - - QByteArray plaintextBuf(olm_group_decrypt_max_plaintext_length(m_groupSession, - reinterpret_cast(messageBuf.data()), messageBuf.length()), '0'); - - messageBuf = QByteArray(message.length(), '0'); - std::copy(message.begin(), message.end(), messageBuf.begin()); - - const auto plaintextLen = olm_group_decrypt(m_groupSession, reinterpret_cast(messageBuf.data()), - messageBuf.length(), reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), &messageIndex); - - // Error code or plaintext length is returned - const auto decryptError = plaintextLen; - - if (decryptError == olm_error()) { - return lastError(m_groupSession); - } - - QByteArray output(plaintextLen, '0'); - std::memcpy(output.data(), plaintextBuf.data(), plaintextLen); - - return std::make_pair(QString(output), messageIndex); -} - -std::variant QOlmInboundGroupSession::exportSession(uint32_t messageIndex) -{ - const auto keyLen = olm_export_inbound_group_session_length(m_groupSession); - QByteArray keyBuf(keyLen, '0'); - const auto error = olm_export_inbound_group_session(m_groupSession, reinterpret_cast(keyBuf.data()), keyLen, messageIndex); - - if (error == olm_error()) { - return lastError(m_groupSession); - } - return keyBuf; -} - -uint32_t QOlmInboundGroupSession::firstKnownIndex() const -{ - return olm_inbound_group_session_first_known_index(m_groupSession); -} - -QByteArray QOlmInboundGroupSession::sessionId() const -{ - QByteArray sessionIdBuf(olm_inbound_group_session_id_length(m_groupSession), '0'); - const auto error = olm_inbound_group_session_id(m_groupSession, reinterpret_cast(sessionIdBuf.data()), - sessionIdBuf.length()); - if (error == olm_error()) { - throw lastError(m_groupSession); - } - return sessionIdBuf; -} - -bool QOlmInboundGroupSession::isVerified() const -{ - return olm_inbound_group_session_is_verified(m_groupSession) != 0; -} -#endif diff --git a/lib/olm/qolminboundsession.h b/lib/olm/qolminboundsession.h deleted file mode 100644 index 739a8411..00000000 --- a/lib/olm/qolminboundsession.h +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#ifdef Quotient_E2EE_ENABLED - -#include -#include -#include -#include "olm/olm.h" -#include "olm/errors.h" -#include "olm/e2ee.h" - -namespace Quotient { - -//! An in-bound group session is responsible for decrypting incoming -//! communication in a Megolm session. -struct QOlmInboundGroupSession -{ -public: - ~QOlmInboundGroupSession(); - //! Creates a new instance of `OlmInboundGroupSession`. - static std::unique_ptr create(const QByteArray &key); - //! Import an inbound group session, from a previous export. - static std::unique_ptr import(const QByteArray &key); - //! Serialises an `OlmInboundGroupSession` to encrypted Base64. - QByteArray pickle(const PicklingMode &mode) const; - //! Deserialises from encrypted Base64 that was previously obtained by pickling - //! an `OlmInboundGroupSession`. - static std::variant, OlmError> unpickle(QByteArray &picked, const PicklingMode &mode); - //! Decrypts ciphertext received for this group session. - std::variant, OlmError> decrypt(const QByteArray &message); - //! Export the base64-encoded ratchet key for this session, at the given index, - //! in a format which can be used by import. - std::variant exportSession(uint32_t messageIndex); - //! Get the first message index we know how to decrypt. - uint32_t firstKnownIndex() const; - //! Get a base64-encoded identifier for this session. - QByteArray sessionId() const; - bool isVerified() const; - QOlmInboundGroupSession(OlmInboundGroupSession *session); -private: - OlmInboundGroupSession *m_groupSession; -}; - -using QOlmInboundGroupSessionPtr = std::unique_ptr; -using OlmInboundGroupSessionPtr = std::unique_ptr; -} // namespace Quotient -#endif diff --git a/lib/olm/qolmoutboundsession.cpp b/lib/olm/qolmoutboundsession.cpp deleted file mode 100644 index e5c43495..00000000 --- a/lib/olm/qolmoutboundsession.cpp +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "olm/qolmoutboundsession.h" -#include "olm/utils.h" - -using namespace Quotient; - -OlmError lastError(OlmOutboundGroupSession *session) { - const std::string error_raw = olm_outbound_group_session_last_error(session); - - return fromString(error_raw); -} - -QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session) - : m_groupSession(session) -{ -} - -QOlmOutboundGroupSession::~QOlmOutboundGroupSession() -{ - olm_clear_outbound_group_session(m_groupSession); - delete[](reinterpret_cast(m_groupSession)); -} - -std::unique_ptr QOlmOutboundGroupSession::create() -{ - auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); - const auto randomLen = olm_init_outbound_group_session_random_length(olmOutboundGroupSession); - QByteArray randomBuf = getRandom(randomLen); - - const auto error = olm_init_outbound_group_session(olmOutboundGroupSession, - reinterpret_cast(randomBuf.data()), randomBuf.length()); - - if (error == olm_error()) { - throw lastError(olmOutboundGroupSession); - } - - const auto keyMaxLength = olm_outbound_group_session_key_length(olmOutboundGroupSession); - QByteArray keyBuffer(keyMaxLength, '0'); - olm_outbound_group_session_key(olmOutboundGroupSession, reinterpret_cast(keyBuffer.data()), - keyMaxLength); - - randomBuf.clear(); - - return std::make_unique(olmOutboundGroupSession); -} - -std::variant QOlmOutboundGroupSession::pickle(const PicklingMode &mode) -{ - QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); - QByteArray key = toKey(mode); - const auto error = olm_pickle_outbound_group_session(m_groupSession, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()); - - if (error == olm_error()) { - return lastError(m_groupSession); - } - - key.clear(); - - return pickledBuf; -} - - -std::variant, OlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) -{ - QByteArray pickledBuf = pickled; - auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); - QByteArray key = toKey(mode); - const auto error = olm_unpickle_outbound_group_session(olmOutboundGroupSession, key.data(), key.length(), - pickled.data(), pickled.length()); - if (error == olm_error()) { - return lastError(olmOutboundGroupSession); - } - const auto idMaxLength = olm_outbound_group_session_id_length(olmOutboundGroupSession); - QByteArray idBuffer(idMaxLength, '0'); - olm_outbound_group_session_id(olmOutboundGroupSession, reinterpret_cast(idBuffer.data()), - idBuffer.length()); - - key.clear(); - return std::make_unique(olmOutboundGroupSession); -} - -std::variant QOlmOutboundGroupSession::encrypt(const QString &plaintext) -{ - QByteArray plaintextBuf = plaintext.toUtf8(); - const auto messageMaxLen = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); - QByteArray messageBuf(messageMaxLen, '0'); - const auto error = olm_group_encrypt(m_groupSession, reinterpret_cast(plaintextBuf.data()), - plaintextBuf.length(), reinterpret_cast(messageBuf.data()), messageBuf.length()); - - if (error == olm_error()) { - return lastError(m_groupSession); - } - - return messageBuf; -} - -uint32_t QOlmOutboundGroupSession::sessionMessageIndex() const -{ - return olm_outbound_group_session_message_index(m_groupSession); -} - -QByteArray QOlmOutboundGroupSession::sessionId() const -{ - const auto idMaxLength = olm_outbound_group_session_id_length(m_groupSession); - QByteArray idBuffer(idMaxLength, '0'); - const auto error = olm_outbound_group_session_id(m_groupSession, reinterpret_cast(idBuffer.data()), - idBuffer.length()); - if (error == olm_error()) { - throw lastError(m_groupSession); - } - return idBuffer; -} - -std::variant QOlmOutboundGroupSession::sessionKey() const -{ - const auto keyMaxLength = olm_outbound_group_session_key_length(m_groupSession); - QByteArray keyBuffer(keyMaxLength, '0'); - const auto error = olm_outbound_group_session_key(m_groupSession, reinterpret_cast(keyBuffer.data()), - keyMaxLength); - if (error == olm_error()) { - return lastError(m_groupSession); - } - return keyBuffer; -} - -#endif diff --git a/lib/olm/qolmoutboundsession.h b/lib/olm/qolmoutboundsession.h deleted file mode 100644 index 70c4d27f..00000000 --- a/lib/olm/qolmoutboundsession.h +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later -#pragma once -#ifdef Quotient_E2EE_ENABLED - -#include "olm/olm.h" // from Olm -#include "olm/errors.h" -#include "olm/e2ee.h" -#include - -namespace Quotient { - - -//! An out-bound group session is responsible for encrypting outgoing -//! communication in a Megolm session. -class QOlmOutboundGroupSession -{ -public: - ~QOlmOutboundGroupSession(); - //! Creates a new instance of `QOlmOutboundGroupSession`. - //! Throw OlmError on errors - static std::unique_ptr create(); - //! Serialises an `QOlmOutboundGroupSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); - //! Deserialises from encrypted Base64 that was previously obtained by - //! pickling a `QOlmOutboundGroupSession`. - static std::variant, OlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); - //! Encrypts a plaintext message using the session. - std::variant encrypt(const QString &plaintext); - - //! Get the current message index for this session. - //! - //! Each message is sent with an increasing index; this returns the - //! index for the next message. - uint32_t sessionMessageIndex() const; - - //! Get a base64-encoded identifier for this session. - QByteArray sessionId() const; - - //! Get the base64-encoded current ratchet key for this session. - //! - //! Each message is sent with a different ratchet key. This function returns the - //! ratchet key that will be used for the next message. - std::variant sessionKey() const; - QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); -private: - OlmOutboundGroupSession *m_groupSession; -}; - -using QOlmOutboundGroupSessionPtr = std::unique_ptr; -using OlmOutboundGroupSessionPtr = std::unique_ptr; -} -#endif diff --git a/lib/olm/qolmsession.cpp b/lib/olm/qolmsession.cpp deleted file mode 100644 index 32a108a8..00000000 --- a/lib/olm/qolmsession.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "olm/qolmsession.h" - -using namespace Quotient; - -std::optional fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext) -{ - if (messageType == OLM_MESSAGE_TYPE_PRE_KEY) { - return PreKeyMessage { ciphertext }; - } else if (messageType == OLM_MESSAGE_TYPE_MESSAGE) { - return Message { ciphertext }; - } - return std::nullopt; -} - -std::pair toPair(const OlmMessage &message) -{ - return std::visit([](auto &arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - return std::make_pair(MessageType, QByteArray(arg.message)); - } else if constexpr (std::is_same_v) { - return std::make_pair(PreKeyType, QByteArray(arg.message)); - } - }, message); -} diff --git a/lib/olm/qolmsession.h b/lib/olm/qolmsession.h deleted file mode 100644 index 08f47331..00000000 --- a/lib/olm/qolmsession.h +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "olm/e2ee.h" -#include "olm/olm.h" -#include "olm/errors.h" -#include - -namespace Quotient { - -//! An encrypted Olm message. -struct Message { - QByteArray message; -}; - -//! A encrypted Olm pre-key message. -//! -//! This message, unlike a normal Message, can be used to create new Olm sessions. -struct PreKeyMessage -{ - QByteArray message; -}; - -enum OlmMessageType -{ - PreKeyType, - MessageType, -}; - -using OlmMessage = std::variant; - -std::optional fromTypeAndCipthertext(size_t messageType, const QByteArray &ciphertext); - -std::pair toPair(const OlmMessage &message); - -//class QOlmSession -//{ -// /// Creates an inbound session for sending/receiving messages from a received 'prekey' message. -// static std::variant, OlmError> createInboundSession(const QOlmAccount &account, -// PreKeyMessage &message); -// -////private: -// //static std::variant, OlmError> createSessionWith(std::function> func); -//} - -} diff --git a/lib/olm/session.cpp b/lib/olm/session.cpp deleted file mode 100644 index 94f12db6..00000000 --- a/lib/olm/session.cpp +++ /dev/null @@ -1,242 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "olm/session.h" -#include "olm/utils.h" -#include "logging.h" -#include - -using namespace Quotient; - -OlmError lastError(OlmSession* session) { - const std::string error_raw = olm_session_last_error(session); - - return fromString(error_raw); -} - -Quotient::QOlmSession::~QOlmSession() -{ - olm_clear_session(m_session); - delete[](reinterpret_cast(m_session)); -} - -OlmSession* QOlmSession::create() -{ - return olm_session(new uint8_t[olm_session_size()]); -} - -std::variant, OlmError> QOlmSession::createInbound(QOlmAccount *account, const Message &preKeyMessage, bool from, const QString &theirIdentityKey) -{ - if (preKeyMessage.type() != Message::PreKey) { - qCDebug(E2EE) << "The message is not a pre-key"; - throw BadMessageFormat; - } - - const auto olmSession = create(); - - QByteArray oneTimeKeyMessageBuf = preKeyMessage.toCiphertext(); - QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); - size_t error = 0; - if (from) { - error = olm_create_inbound_session_from(olmSession, account->data(), theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); - } else { - error = olm_create_inbound_session(olmSession, account->data(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); - } - - if (error == olm_error()) { - const auto lastErr = lastError(olmSession); - if (lastErr == OlmError::NotEnoughRandom) { - throw lastErr; - } - return lastErr; - } - - return std::make_unique(olmSession); -} - -std::variant, OlmError> QOlmSession::createInboundSession(QOlmAccount *account, const Message &preKeyMessage) -{ - return createInbound(account, preKeyMessage); -} - -std::variant, OlmError> QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage) -{ - return createInbound(account, preKeyMessage, true, theirIdentityKey); -} - -std::variant, OlmError> QOlmSession::createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey) -{ - auto *olmOutboundSession = create(); - const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); - QByteArray randomBuf = getRandom(randomLen); - - QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); - QByteArray theirOneTimeKeyBuf = theirOneTimeKey.toUtf8(); - const auto error = olm_create_outbound_session(olmOutboundSession, - account->data(), - reinterpret_cast(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), - reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), - reinterpret_cast(randomBuf.data()), randomBuf.length()); - - if (error == olm_error()) { - const auto lastErr = lastError(olmOutboundSession); - if (lastErr == OlmError::NotEnoughRandom) { - throw lastErr; - } - return lastErr; - } - - randomBuf.clear(); - return std::make_unique(olmOutboundSession); -} - -std::variant QOlmSession::pickle(const PicklingMode &mode) -{ - QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); - QByteArray key = toKey(mode); - const auto error = olm_pickle_session(m_session, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()); - - if (error == olm_error()) { - return lastError(m_session); - } - - key.clear(); - - return pickledBuf; -} - -std::variant, OlmError> QOlmSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) -{ - QByteArray pickledBuf = pickled; - auto *olmSession = create(); - QByteArray key = toKey(mode); - const auto error = olm_unpickle_session(olmSession, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()); - if (error == olm_error()) { - return lastError(olmSession); - } - - key.clear(); - return std::make_unique(olmSession); -} - -Message QOlmSession::encrypt(const QString &plaintext) -{ - QByteArray plaintextBuf = plaintext.toUtf8(); - const auto messageMaxLen = olm_encrypt_message_length(m_session, plaintextBuf.length()); - QByteArray messageBuf(messageMaxLen, '0'); - const auto messageType = encryptMessageType(); - const auto randomLen = olm_encrypt_random_length(m_session); - QByteArray randomBuf = getRandom(randomLen); - const auto error = olm_encrypt(m_session, - reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), - reinterpret_cast(randomBuf.data()), randomBuf.length(), - reinterpret_cast(messageBuf.data()), messageBuf.length()); - - if (error == olm_error()) { - throw lastError(m_session); - } - - return Message(messageBuf, messageType); -} - -std::variant QOlmSession::decrypt(const Message &message) const -{ - const auto messageType = message.type(); - const auto ciphertext = message.toCiphertext(); - const auto messageTypeValue = messageType == Message::Type::General - ? OLM_MESSAGE_TYPE_MESSAGE : OLM_MESSAGE_TYPE_PRE_KEY; - - // We need to clone the message because - // olm_decrypt_max_plaintext_length destroys the input buffer - QByteArray messageBuf(ciphertext.length(), '0'); - std::copy(message.begin(), message.end(), messageBuf.begin()); - - const auto plaintextMaxLen = olm_decrypt_max_plaintext_length(m_session, messageTypeValue, - reinterpret_cast(messageBuf.data()), messageBuf.length()); - - if (plaintextMaxLen == olm_error()) { - return lastError(m_session); - } - - QByteArray plaintextBuf(plaintextMaxLen, '0'); - QByteArray messageBuf2(ciphertext.length(), '0'); - std::copy(message.begin(), message.end(), messageBuf2.begin()); - - const auto plaintextResultLen = olm_decrypt(m_session, messageTypeValue, - reinterpret_cast(messageBuf2.data()), messageBuf2.length(), - reinterpret_cast(plaintextBuf.data()), plaintextMaxLen); - - if (plaintextResultLen == olm_error()) { - const auto lastErr = lastError(m_session); - if (lastErr == OlmError::OutputBufferTooSmall) { - throw lastErr; - } - return lastErr; - } - QByteArray output(plaintextResultLen, '0'); - std::memcpy(output.data(), plaintextBuf.data(), plaintextResultLen); - plaintextBuf.clear(); - return output; -} - -Message::Type QOlmSession::encryptMessageType() -{ - const auto messageTypeResult = olm_encrypt_message_type(m_session); - if (messageTypeResult == olm_error()) { - throw lastError(m_session); - } - if (messageTypeResult == OLM_MESSAGE_TYPE_PRE_KEY) { - return Message::PreKey; - } - return Message::General; -} - -QByteArray QOlmSession::sessionId() const -{ - const auto idMaxLength = olm_session_id_length(m_session); - QByteArray idBuffer(idMaxLength, '0'); - const auto error = olm_session_id(m_session, reinterpret_cast(idBuffer.data()), - idBuffer.length()); - if (error == olm_error()) { - throw lastError(m_session); - } - return idBuffer; -} - -bool QOlmSession::hasReceivedMessage() const -{ - return olm_session_has_received_message(m_session); -} - -std::variant QOlmSession::matchesInboundSession(Message &preKeyMessage) -{ - Q_ASSERT(preKeyMessage.type() == Message::Type::PreKey); - QByteArray oneTimeKeyBuf(preKeyMessage.data()); - const auto matchesResult = olm_matches_inbound_session(m_session, oneTimeKeyBuf.data(), oneTimeKeyBuf.length()); - - if (matchesResult == olm_error()) { - return lastError(m_session); - } - switch (matchesResult) { - case 0: - return false; - case 1: - return true; - default: - return OlmError::Unknown; - } -} - -QOlmSession::QOlmSession(OlmSession *session) - : m_session(session) -{ -} - -#endif // Quotient_E2EE_ENABLED - - - diff --git a/lib/olm/session.h b/lib/olm/session.h deleted file mode 100644 index 03b3514e..00000000 --- a/lib/olm/session.h +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#ifdef Quotient_E2EE_ENABLED - -#include "olm/e2ee.h" -#include "olm/message.h" -#include "olm/errors.h" -#include -#include "olm/qolmaccount.h" - -namespace Quotient { - -class QOlmAccount; -class QOlmSession; - - -//! Either an outbound or inbound session for secure communication. -class QOlmSession -{ -public: - ~QOlmSession(); - //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. - static std::variant, OlmError> createInboundSession(QOlmAccount *account, const Message &preKeyMessage); - static std::variant, OlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const Message &preKeyMessage); - static std::variant, OlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey); - //! Serialises an `QOlmSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); - //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static std::variant, OlmError> unpickle(const QByteArray &pickled, const PicklingMode &mode); - //! Encrypts a plaintext message using the session. - Message encrypt(const QString &plaintext); - - //! Decrypts a message using this session. Decoding is lossy, meaing if - //! the decrypted plaintext contains invalid UTF-8 symbols, they will - //! be returned as `U+FFFD` (�). - std::variant decrypt(const Message &message) const; - - //! Get a base64-encoded identifier for this session. - QByteArray sessionId() const; - - //! The type of the next message that will be returned from encryption. - Message::Type encryptMessageType(); - - //! Checker for any received messages for this session. - bool hasReceivedMessage() const; - - //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSession(Message &preKeyMessage); - - friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) - { - return lhs.sessionId() < rhs.sessionId(); - } - - friend bool operator<(const std::unique_ptr &lhs, const std::unique_ptr &rhs) { - return *lhs < *rhs; - } - - QOlmSession(OlmSession* session); -private: - //! Helper function for creating new sessions and handling errors. - static OlmSession* create(); - static std::variant, OlmError> createInbound(QOlmAccount *account, const Message& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); - OlmSession* m_session; -}; - - -//using QOlmSessionPtr = std::unique_ptr; - -} //namespace Quotient - -#endif // Quotient_E2EE_ENABLED diff --git a/lib/olm/utils.cpp b/lib/olm/utils.cpp deleted file mode 100644 index 227e6d84..00000000 --- a/lib/olm/utils.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "olm/utils.h" -#include -#include - -using namespace Quotient; - -QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) -{ - if (std::holds_alternative(mode)) { - return ""; - } - return std::get(mode).key; -} - -QByteArray Quotient::getRandom(size_t bufferSize) -{ - QByteArray buffer(bufferSize, '0'); - RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); - return buffer; -} -#endif diff --git a/lib/olm/utils.h b/lib/olm/utils.h deleted file mode 100644 index 85d4605b..00000000 --- a/lib/olm/utils.h +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once -#ifdef Quotient_E2EE_ENABLED - -#include "olm/e2ee.h" - -namespace Quotient { -// Convert PicklingMode to key -QByteArray toKey(const PicklingMode &mode); -QByteArray getRandom(size_t bufferSize); -} -#endif -- cgit v1.2.3 From d72f220e3e3a3b243fdafd93d1405f8207dc516a Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Thu, 28 Jan 2021 23:51:56 +0300 Subject: E2EE: initial port to internal olm wrapper Remove qtolm git module. Update CMakeLists.txt. Rename olm to crypto subdir to prevent disambiguation. Rename internal files accordingly. Comment out not ported E2EE API usage. --- .gitmodules | 3 - 3rdparty/libQtOlm | 1 - CMakeLists.txt | 69 ++++------- autotests/testgroupsession.cpp | 6 +- autotests/testolmaccount.cpp | 6 +- autotests/testolmsession.cpp | 2 +- lib/connection.cpp | 12 +- lib/connection.h | 8 +- lib/crypto/e2ee.h | 7 +- lib/crypto/errors.cpp | 21 ---- lib/crypto/errors.h | 31 ----- lib/crypto/message.cpp | 42 ------- lib/crypto/message.h | 46 ------- lib/crypto/qolmaccount.cpp | 23 ++-- lib/crypto/qolmaccount.h | 15 +-- lib/crypto/qolmerrors.cpp | 21 ++++ lib/crypto/qolmerrors.h | 31 +++++ lib/crypto/qolminboundsession.cpp | 8 +- lib/crypto/qolminboundsession.h | 8 +- lib/crypto/qolmmessage.cpp | 42 +++++++ lib/crypto/qolmmessage.h | 46 +++++++ lib/crypto/qolmoutboundsession.cpp | 14 +-- lib/crypto/qolmoutboundsession.h | 12 +- lib/crypto/qolmsession.cpp | 246 ++++++++++++++++++++++++++++++++++--- lib/crypto/qolmsession.cpp.back | 29 +++++ lib/crypto/qolmsession.h | 92 +++++++++----- lib/crypto/qolmsession.h.back | 49 ++++++++ lib/crypto/qolmutils.cpp | 26 ++++ lib/crypto/qolmutils.h | 17 +++ lib/crypto/session.cpp | 242 ------------------------------------ lib/crypto/session.h | 77 ------------ lib/crypto/utils.cpp | 26 ---- lib/crypto/utils.h | 15 --- lib/encryptionmanager.cpp | 70 ++++++----- lib/encryptionmanager.h | 7 +- lib/room.cpp | 25 ++-- 36 files changed, 690 insertions(+), 705 deletions(-) delete mode 160000 3rdparty/libQtOlm delete mode 100644 lib/crypto/errors.cpp delete mode 100644 lib/crypto/errors.h delete mode 100644 lib/crypto/message.cpp delete mode 100644 lib/crypto/message.h create mode 100644 lib/crypto/qolmerrors.cpp create mode 100644 lib/crypto/qolmerrors.h create mode 100644 lib/crypto/qolmmessage.cpp create mode 100644 lib/crypto/qolmmessage.h create mode 100644 lib/crypto/qolmsession.cpp.back create mode 100644 lib/crypto/qolmsession.h.back create mode 100644 lib/crypto/qolmutils.cpp create mode 100644 lib/crypto/qolmutils.h delete mode 100644 lib/crypto/session.cpp delete mode 100644 lib/crypto/session.h delete mode 100644 lib/crypto/utils.cpp delete mode 100644 lib/crypto/utils.h (limited to 'CMakeLists.txt') 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 f9f7d130e5768d0f69edc8900d37f540b61fa974 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sat, 30 Jan 2021 00:21:10 +0100 Subject: Key verification --- CMakeLists.txt | 1 + autotests/testolmaccount.cpp | 138 +++++++++++++++++++++++++++++++------------ autotests/testolmaccount.h | 1 + lib/crypto/qolmaccount.cpp | 42 +++++++++++++ lib/crypto/qolmaccount.h | 9 +++ lib/crypto/qolmutility.cpp | 58 ++++++++++++++++++ lib/crypto/qolmutility.h | 48 +++++++++++++++ 7 files changed, 258 insertions(+), 39 deletions(-) create mode 100644 lib/crypto/qolmutility.cpp create mode 100644 lib/crypto/qolmutility.h (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f62af68..fb07fa22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,6 +163,7 @@ list(APPEND lib_SRCS lib/crypto/qolminboundsession.cpp lib/crypto/qolmoutboundsession.cpp lib/crypto/qolmutils.cpp + lib/crypto/qolmutility.cpp lib/crypto/qolmerrors.cpp lib/crypto/qolmsession.cpp lib/crypto/qolmmessage.cpp diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index c764e023..ce51b9ec 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -244,19 +244,6 @@ void TestOlmAccount::uploadOneTimeKeys() QSignalSpy spy3(request, &BaseJob::result); QVERIFY(spy3.wait(10000)); }); - connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, [=](QString error, const QString &) { - QFAIL("Login failed"); - }); - }); - - connect(conn, &Connection::resolveError, this, [=](QString error) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, this, [=] { - QFAIL("Network error: make sure synapse is running"); }); QSignalSpy spy(conn, &Connection::loginFlowsChanged); @@ -298,19 +285,6 @@ void TestOlmAccount::uploadSignedOneTimeKeys() QSignalSpy spy3(request, &BaseJob::result); QVERIFY(spy3.wait(10000)); }); - connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, [=](QString error, const QString &) { - QFAIL("Login failed"); - }); - }); - - connect(conn, &Connection::resolveError, this, [=](QString error) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, this, [=] { - QFAIL("Network error: make sure synapse is running"); }); QSignalSpy spy(conn, &Connection::loginFlowsChanged); @@ -344,19 +318,6 @@ void TestOlmAccount::uploadKeys() QSignalSpy spy3(request, &BaseJob::result); QVERIFY(spy3.wait(10000)); }); - connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, [=](QString error, const QString &) { - QFAIL("Login failed"); - }); - }); - - connect(conn, &Connection::resolveError, this, [=](QString error) { - QFAIL("Network error: make sure synapse is running"); - }); - connect(conn, &Connection::loginError, this, [=] { - QFAIL("Network error: make sure synapse is running"); }); QSignalSpy spy(conn, &Connection::loginFlowsChanged); @@ -366,4 +327,103 @@ void TestOlmAccount::uploadKeys() delete conn; } +inline void sleep() +{ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +} + + +void TestOlmAccount::claimKeys() +{ + auto alice = new Connection(); + alice->resolveServer("@alice:localhost:" + QString::number(443)); + connect(alice, &Connection::loginFlowsChanged, this, [this, alice]() { + alice->loginWithPassword("alice", "secret", "AlicePhone", ""); + connect(alice, &Connection::connected, this, [this, alice] { + qDebug() << "alice->accessToken()" << alice->accessToken(); + QVERIFY(!alice->accessToken().isEmpty()); + }); + }); + + QSignalSpy spy(alice, &Connection::loginFlowsChanged); + QSignalSpy spy2(alice, &Connection::connected); + QVERIFY(spy.wait(10000)); + QVERIFY(spy2.wait(10000)); + + auto bob = new Connection(); + bob->resolveServer("@bob:localhost:" + QString::number(443)); + connect(bob, &Connection::loginFlowsChanged, this, [this, bob]() { + bob->loginWithPassword("bob", "secret", "BobPhone", ""); + connect(bob, &Connection::connected, this, [this, bob] { + qDebug() << "bob->accessToken()" << bob->accessToken(); + QVERIFY(!bob->accessToken().isEmpty()); + }); + }); + + QSignalSpy spy3(bob, &Connection::loginFlowsChanged); + QSignalSpy spy4(bob, &Connection::connected); + QVERIFY(spy3.wait(10000)); + QVERIFY(spy4.wait(10000)); + + // Bob uploads his keys. + auto *bobOlm = bob->olmAccount(); + bobOlm->generateOneTimeKeys(1); + auto request = bobOlm->createUploadKeyRequest(bobOlm->oneTimeKeys()); + + connect(request, &BaseJob::result, this, [request, bob](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 1); + QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], 1); + }); + bob->run(request); + + QSignalSpy requestSpy(request, &BaseJob::result); + QVERIFY(requestSpy.wait(10000)); + + // Alice retrieves bob's keys & claims one signed one-time key. + auto *aliceOlm = alice->olmAccount(); + QHash deviceKeys; + deviceKeys[bob->userId()] = QStringList(); + auto job = alice->callApi(deviceKeys); + connect(job, &BaseJob::result, this, [bob, alice, aliceOlm, job, this] { + auto bobDevices = job->deviceKeys()[bob->userId()]; + QVERIFY(bobDevices.size() > 0); + + auto devices = {bob->deviceId()}; + + // Retrieve the identity key for the current device. + auto bobEd25519 = + bobDevices[bob->deviceId()].keys["ed25519:" + bob->deviceId()]; + + const auto currentDevice = bobDevices[bob->deviceId()]; + + // Verify signature. + QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(), bob->userId())); + + QHash> oneTimeKeys; + for (const auto &d : devices) { + oneTimeKeys[bob->userId()] = QHash(); + oneTimeKeys[bob->userId()][d] = SignedCurve25519Key; + } + auto job = alice->callApi(oneTimeKeys); + connect(job, &BaseJob::result, this, [aliceOlm, bob, bobEd25519, job] { + const auto userId = bob->userId(); + const auto deviceId = bob->deviceId(); + + // The device exists. + QCOMPARE(job->oneTimeKeys().size(), 1); + QCOMPARE(job->oneTimeKeys()[userId].size(), 1); + + // The key is the one bob sent. + auto oneTimeKey = job->oneTimeKeys()[userId][deviceId]; + QVERIFY(oneTimeKey.canConvert()); + + //auto algo = oneTimeKey.begin().key(); + //auto contents = oneTimeKey.begin().value(); + }); + }); + delete bob; + delete alice; +} + QTEST_MAIN(TestOlmAccount) diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index 41298957..8b2d2e09 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -20,4 +20,5 @@ private Q_SLOTS: void uploadOneTimeKeys(); void uploadSignedOneTimeKeys(); void uploadKeys(); + void claimKeys(); }; diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index fb91c906..24fd87f2 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -7,6 +7,7 @@ #include "connection.h" #include "csapi/keys.h" #include "crypto/qolmutils.h" +#include "crypto/qolmutility.h" #include #include #include @@ -263,4 +264,45 @@ std::variant, QOlmError> QOlmAccount::createOutboun return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); } +bool Quotient::verifyIdentitySignature(const DeviceKeys &deviceKeys, + const QString &deviceId, + const QString &userId) +{ + const auto signKeyId = "ed25519:" + deviceId; + const auto signingKey = deviceKeys.keys[signKeyId]; + const auto signature = deviceKeys.signatures[userId][signKeyId]; + + if (signature.isEmpty()) { + return false; + } + + return ed25519VerifySignature(signingKey, toJson(deviceKeys), signature); +} + +bool Quotient::ed25519VerifySignature(QString signingKey, + QJsonObject obj, + QString signature) +{ + if (signature.isEmpty()) { + return false; + } + + obj.remove("unsigned"); + obj.remove("signatures"); + + QJsonDocument doc; + doc.setObject(obj); + auto canonicalJson = doc.toJson(); + + QByteArray signingKeyBuf = signingKey.toUtf8(); + QOlmUtility utility; + auto signatureBuf = signature.toUtf8(); + auto result = utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf); + if (std::holds_alternative(result)) { + return false; + } + + return std::get(result); +} + #endif diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index d61c8748..09ef623a 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -99,6 +99,15 @@ private: QString m_deviceId; }; +bool verifyIdentitySignature(const DeviceKeys &deviceKeys, + const QString &deviceId, + const QString &userId); + +//! checks if the signature is signed by the signing_key +bool ed25519VerifySignature(QString signingKey, + QJsonObject obj, + QString signature); + } // namespace Quotient #endif diff --git a/lib/crypto/qolmutility.cpp b/lib/crypto/qolmutility.cpp new file mode 100644 index 00000000..3c6a14c7 --- /dev/null +++ b/lib/crypto/qolmutility.cpp @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "crypto/qolmutility.h" +#include "olm/olm.h" + +using namespace Quotient; + +// Convert olm error to enum +QOlmError lastError(OlmUtility *utility) { + const std::string error_raw = olm_utility_last_error(utility); + + return fromString(error_raw); +} + +QOlmUtility::QOlmUtility() +{ + auto utility = new uint8_t[olm_utility_size()]; + m_utility = olm_utility(utility); +} + +QOlmUtility::~QOlmUtility() +{ + olm_clear_utility(m_utility); + delete[](reinterpret_cast(m_utility)); +} + +QString QOlmUtility::sha256Bytes(const QByteArray &inputBuf) const +{ + const auto outputLen = olm_sha256_length(m_utility); + QByteArray outputBuf(outputLen, '0'); + olm_sha256(m_utility, inputBuf.data(), inputBuf.length(), + outputBuf.data(), outputBuf.length()); + + return QString::fromUtf8(outputBuf); +} + +QString QOlmUtility::sha256Utf8Msg(const QString &message) const +{ + return sha256Bytes(message.toUtf8()); +} + +std::variant QOlmUtility::ed25519Verify(const QByteArray &key, + const QByteArray &message, QByteArray &signature) +{ + const auto error = olm_ed25519_verify(m_utility, key.data(), key.length(), + message.data(), message.length(), signature.data(), signature.length()); + + if (error == olm_error()) { + return lastError(m_utility); + } + return error == 0; +} + + +#endif diff --git a/lib/crypto/qolmutility.h b/lib/crypto/qolmutility.h new file mode 100644 index 00000000..16c330eb --- /dev/null +++ b/lib/crypto/qolmutility.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef Quotient_E2EE_ENABLED +#include +#include +#include "crypto/qolmerrors.h" + +struct OlmUtility; + +namespace Quotient { + +class QOlmSession; +class Connection; + +//! Allows you to make use of crytographic hashing via SHA-2 and +//! verifying ed25519 signatures. +class QOlmUtility +{ +public: + QOlmUtility(); + ~QOlmUtility(); + + //! Returns a sha256 of the supplied byte slice. + QString sha256Bytes(const QByteArray &inputBuf) const; + + //! Convenience function that converts the UTF-8 message + //! to bytes and then calls `sha256_bytes()`, returning its output. + QString sha256Utf8Msg(const QString &message) const; + + //! Verify a ed25519 signature. + //! \param any QByteArray The public part of the ed25519 key that signed the message. + //! \param message QByteArray The message that was signed. + //! \param signature QByteArray The signature of the message. + std::variant ed25519Verify(const QByteArray &key, + const QByteArray &message, QByteArray &signature); + + +private: + OlmUtility *m_utility; + +}; +} + +#endif -- cgit v1.2.3 From 38547289d56cf66b4f1384ae789cf5b6cd71763e Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 5 Feb 2021 00:03:27 +0100 Subject: Fix cmake code --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index fb07fa22..ba6c8cd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -316,7 +316,8 @@ if (${PROJECT_NAME}_ENABLE_E2EE) target_link_libraries(${PROJECT_NAME} Olm::Olm OpenSSL::Crypto OpenSSL::SSL) - set(FIND_DEPS "find_dependency(Olm OpenSSL)") # For QuotientConfig.cmake.in + set(FIND_DEPS "find_dependency(Olm) + find_dependency(OpenSSL)") # For QuotientConfig.cmake.in endif() target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) -- cgit v1.2.3 From bc4ef60c29709a6f782f6e75e1f040f250fb8bd7 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 22 Mar 2021 16:32:21 +0100 Subject: Ifdef E2EE out at cmake level --- CMakeLists.txt | 22 +++++++++++++--------- lib/crypto/qolmaccount.cpp | 3 --- lib/crypto/qolmaccount.h | 4 +--- lib/crypto/qolmerrors.cpp | 4 ++-- lib/crypto/qolmerrors.h | 3 --- lib/crypto/qolminboundsession.cpp | 2 -- lib/crypto/qolminboundsession.h | 3 --- lib/crypto/qolmmessage.cpp | 7 ------- lib/crypto/qolmmessage.h | 5 ----- lib/crypto/qolmoutboundsession.cpp | 3 --- lib/crypto/qolmoutboundsession.h | 3 +-- lib/crypto/qolmsession.cpp | 6 ------ lib/crypto/qolmsession.h | 4 ---- lib/crypto/qolmutility.cpp | 4 ---- lib/crypto/qolmutility.h | 3 --- lib/crypto/qolmutils.cpp | 2 -- lib/crypto/qolmutils.h | 2 -- 17 files changed, 17 insertions(+), 63 deletions(-) (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index ba6c8cd1..5601a281 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,16 +158,20 @@ list(APPEND lib_SRCS lib/jobs/syncjob.cpp lib/jobs/mediathumbnailjob.cpp lib/jobs/downloadfilejob.cpp - lib/crypto/qolmaccount.cpp - lib/crypto/qolmsession.cpp - lib/crypto/qolminboundsession.cpp - lib/crypto/qolmoutboundsession.cpp - lib/crypto/qolmutils.cpp - lib/crypto/qolmutility.cpp - lib/crypto/qolmerrors.cpp - lib/crypto/qolmsession.cpp - lib/crypto/qolmmessage.cpp ) +if (${PROJECT_NAME}_ENABLE_E2EE) + list(APPEND lib_SRCS + lib/crypto/qolmaccount.cpp + lib/crypto/qolmsession.cpp + lib/crypto/qolminboundsession.cpp + lib/crypto/qolmoutboundsession.cpp + lib/crypto/qolmutils.cpp + lib/crypto/qolmutility.cpp + lib/crypto/qolmerrors.cpp + lib/crypto/qolmsession.cpp + lib/crypto/qolmmessage.cpp + ) +endif() # Configure API files generation diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 0f354d3e..4f007e2f 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "qolmaccount.h" #include "connection.h" #include "csapi/keys.h" @@ -318,5 +317,3 @@ bool Quotient::ed25519VerifySignature(const QString &signingKey, return std::get(result); } - -#endif diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index de78a8af..1e198687 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: 2021 Carl Schwan // // SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once -#ifdef Quotient_E2EE_ENABLED #include "csapi/keys.h" #include "crypto/e2ee.h" @@ -111,5 +111,3 @@ bool ed25519VerifySignature(const QString &signingKey, const QString &signature); } // namespace Quotient - -#endif diff --git a/lib/crypto/qolmerrors.cpp b/lib/crypto/qolmerrors.cpp index f407383e..46b2618c 100644 --- a/lib/crypto/qolmerrors.cpp +++ b/lib/crypto/qolmerrors.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2021 Carl Schwan +// // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED + #include "qolmerrors.h" Quotient::QOlmError Quotient::fromString(const std::string &error_raw) { @@ -18,4 +19,3 @@ Quotient::QOlmError Quotient::fromString(const std::string &error_raw) { return QOlmError::Unknown; } } -#endif diff --git a/lib/crypto/qolmerrors.h b/lib/crypto/qolmerrors.h index 400573c6..f8390d2a 100644 --- a/lib/crypto/qolmerrors.h +++ b/lib/crypto/qolmerrors.h @@ -4,7 +4,6 @@ #pragma once -#ifdef Quotient_E2EE_ENABLED #include namespace Quotient { @@ -27,5 +26,3 @@ enum QOlmError QOlmError fromString(const std::string &error_raw); } //namespace Quotient - -#endif diff --git a/lib/crypto/qolminboundsession.cpp b/lib/crypto/qolminboundsession.cpp index 8f5056d8..e1ced72b 100644 --- a/lib/crypto/qolminboundsession.cpp +++ b/lib/crypto/qolminboundsession.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "crypto/qolminboundsession.h" #include #include @@ -154,4 +153,3 @@ bool QOlmInboundGroupSession::isVerified() const { return olm_inbound_group_session_is_verified(m_groupSession) != 0; } -#endif diff --git a/lib/crypto/qolminboundsession.h b/lib/crypto/qolminboundsession.h index 6af71cbd..36ab4942 100644 --- a/lib/crypto/qolminboundsession.h +++ b/lib/crypto/qolminboundsession.h @@ -4,8 +4,6 @@ #pragma once -#ifdef Quotient_E2EE_ENABLED - #include #include #include @@ -48,4 +46,3 @@ private: using QOlmInboundGroupSessionPtr = std::unique_ptr; using OlmInboundGroupSessionPtr = std::unique_ptr; } // namespace Quotient -#endif diff --git a/lib/crypto/qolmmessage.cpp b/lib/crypto/qolmmessage.cpp index ae98d52f..15008b75 100644 --- a/lib/crypto/qolmmessage.cpp +++ b/lib/crypto/qolmmessage.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "qolmmessage.h" using namespace Quotient; @@ -34,9 +33,3 @@ QOlmMessage QOlmMessage::fromCiphertext(const QByteArray &ciphertext) { return QOlmMessage(ciphertext, QOlmMessage::General); } - - -#endif // Quotient_E2EE_ENABLED - - - diff --git a/lib/crypto/qolmmessage.h b/lib/crypto/qolmmessage.h index d203364d..52aba78c 100644 --- a/lib/crypto/qolmmessage.h +++ b/lib/crypto/qolmmessage.h @@ -4,8 +4,6 @@ #pragma once -#ifdef Quotient_E2EE_ENABLED - #include #include @@ -40,7 +38,4 @@ private: Type m_messageType = General; }; - } //namespace Quotient - -#endif // Quotient_E2EE_ENABLED diff --git a/lib/crypto/qolmoutboundsession.cpp b/lib/crypto/qolmoutboundsession.cpp index 14b7368e..bf8dce61 100644 --- a/lib/crypto/qolmoutboundsession.cpp +++ b/lib/crypto/qolmoutboundsession.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "qolmoutboundsession.h" #include "crypto/qolmutils.h" @@ -127,5 +126,3 @@ std::variant QOlmOutboundGroupSession::sessionKey() const } return keyBuffer; } - -#endif diff --git a/lib/crypto/qolmoutboundsession.h b/lib/crypto/qolmoutboundsession.h index 6b4fd30b..f1df0395 100644 --- a/lib/crypto/qolmoutboundsession.h +++ b/lib/crypto/qolmoutboundsession.h @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: 2021 Carl Schwan // // SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once -#ifdef Quotient_E2EE_ENABLED #include "olm/olm.h" #include "crypto/qolmerrors.h" @@ -51,4 +51,3 @@ private: using QOlmOutboundGroupSessionPtr = std::unique_ptr; using OlmOutboundGroupSessionPtr = std::unique_ptr; } -#endif diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp index b901a440..a1f6ab71 100644 --- a/lib/crypto/qolmsession.cpp +++ b/lib/crypto/qolmsession.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "qolmsession.h" #include "crypto/qolmutils.h" #include "logging.h" @@ -255,8 +254,3 @@ QOlmSession::QOlmSession(OlmSession *session) : m_session(session) { } - -#endif // Quotient_E2EE_ENABLED - - - diff --git a/lib/crypto/qolmsession.h b/lib/crypto/qolmsession.h index 0fc59e9e..959c77d0 100644 --- a/lib/crypto/qolmsession.h +++ b/lib/crypto/qolmsession.h @@ -4,8 +4,6 @@ #pragma once -#ifdef Quotient_E2EE_ENABLED - #include #include // FIXME: OlmSession #include "crypto/e2ee.h" @@ -80,5 +78,3 @@ private: //using QOlmSessionPtr = std::unique_ptr; } //namespace Quotient - -#endif // Quotient_E2EE_ENABLED diff --git a/lib/crypto/qolmutility.cpp b/lib/crypto/qolmutility.cpp index 87615770..bb50b4d0 100644 --- a/lib/crypto/qolmutility.cpp +++ b/lib/crypto/qolmutility.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "crypto/qolmutility.h" #include "olm/olm.h" #include @@ -62,6 +61,3 @@ std::variant QOlmUtility::ed25519Verify(const QByteArray &key, } return true; } - - -#endif diff --git a/lib/crypto/qolmutility.h b/lib/crypto/qolmutility.h index 3de09ab4..fc6569f7 100644 --- a/lib/crypto/qolmutility.h +++ b/lib/crypto/qolmutility.h @@ -4,7 +4,6 @@ #pragma once -#ifdef Quotient_E2EE_ENABLED #include #include #include "crypto/qolmerrors.h" @@ -44,5 +43,3 @@ private: }; } - -#endif diff --git a/lib/crypto/qolmutils.cpp b/lib/crypto/qolmutils.cpp index 4479932e..cd5ac83c 100644 --- a/lib/crypto/qolmutils.cpp +++ b/lib/crypto/qolmutils.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifdef Quotient_E2EE_ENABLED #include "crypto/qolmutils.h" #include #include @@ -23,4 +22,3 @@ QByteArray Quotient::getRandom(size_t bufferSize) RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); return buffer; } -#endif diff --git a/lib/crypto/qolmutils.h b/lib/crypto/qolmutils.h index 11e9f3cc..8b1c01ce 100644 --- a/lib/crypto/qolmutils.h +++ b/lib/crypto/qolmutils.h @@ -3,7 +3,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once -#ifdef Quotient_E2EE_ENABLED #include @@ -14,4 +13,3 @@ namespace Quotient { QByteArray toKey(const PicklingMode &mode); QByteArray getRandom(size_t bufferSize); } -#endif -- cgit v1.2.3 From 5f3e33e1c15be19f09d83a0d6f44d551021a9d44 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 5 Feb 2021 18:45:30 +0100 Subject: Implement key verification events --- CMakeLists.txt | 1 + lib/events/keyverificationevent.cpp | 193 ++++++++++++++++++++++++++++++++++++ lib/events/keyverificationevent.h | 167 +++++++++++++++++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 lib/events/keyverificationevent.cpp create mode 100644 lib/events/keyverificationevent.h (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index 5601a281..92a9b213 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,7 @@ list(APPEND lib_SRCS lib/events/encryptedevent.cpp lib/events/roomkeyevent.cpp lib/events/stickerevent.cpp + lib/events/keyverificationevent.cpp lib/jobs/requestdata.cpp lib/jobs/basejob.cpp lib/jobs/syncjob.cpp diff --git a/lib/events/keyverificationevent.cpp b/lib/events/keyverificationevent.cpp new file mode 100644 index 00000000..938b3bde --- /dev/null +++ b/lib/events/keyverificationevent.cpp @@ -0,0 +1,193 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "keyverificationevent.h" + +using namespace Quotient; + +KeyVerificationRequestEvent::KeyVerificationRequestEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationRequestEvent::fromDevice() const +{ + return contentJson()["from_device"_ls].toString(); +} + +QString KeyVerificationRequestEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QStringList KeyVerificationRequestEvent::methods() const +{ + QStringList methods; + for (const auto &method : contentJson()["methods"].toArray()) { + methods.append(method.toString()); + } + return methods; +} + +uint64_t KeyVerificationRequestEvent::timestamp() const +{ + return contentJson()["timestamp"_ls].toDouble(); +} + +KeyVerificationStartEvent::KeyVerificationStartEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationStartEvent::fromDevice() const +{ + return contentJson()["from_device"_ls].toString(); +} + +QString KeyVerificationStartEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QString KeyVerificationStartEvent::method() const +{ + return contentJson()["method"_ls].toString(); +} + +Omittable KeyVerificationStartEvent::nextMethod() const +{ + auto next = contentJson()["method"_ls]; + if (next.isUndefined()) { + return std::nullopt; + } + return next.toString(); +} + +QStringList KeyVerificationStartEvent::keyAgreementProtocols() const +{ + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + QStringList protocols; + for (const auto &proto : contentJson()["key_agreement_protocols"_ls].toArray()) { + protocols.append(proto.toString()); + } + return protocols; +} + +QStringList KeyVerificationStartEvent::hashes() const +{ + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + QStringList hashes; + for (const auto &hashItem : contentJson()["hashes"_ls].toArray()) { + hashes.append(hashItem.toString()); + } + return hashes; +} + +QStringList KeyVerificationStartEvent::messageAuthenticationCodes() const +{ + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + + QStringList codes; + for (const auto &code : contentJson()["message_authentication_codes"_ls].toArray()) { + codes.append(code.toString()); + } + return codes; +} + +QString KeyVerificationStartEvent::shortAuthenticationString() const +{ + return contentJson()["short_authentification_string"_ls].toString(); +} + +KeyVerificationAcceptEvent::KeyVerificationAcceptEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationAcceptEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QString KeyVerificationAcceptEvent::method() const +{ + return contentJson()["method"_ls].toString(); +} + +QString KeyVerificationAcceptEvent::keyAgreementProtocol() const +{ + return contentJson()["key_agreement_protocol"_ls].toString(); +} + +QString KeyVerificationAcceptEvent::hashData() const +{ + return contentJson()["hash"_ls].toString(); +} + +QStringList KeyVerificationAcceptEvent::shortAuthenticationString() const +{ + QStringList strings; + for (const auto &authenticationString : contentJson()["short_authentification_string"].toArray()) { + strings.append(authenticationString.toString()); + } + return strings; +} + +QString KeyVerificationAcceptEvent::commitement() const +{ + return contentJson()["commitement"].toString(); +} + +KeyVerificationCancelEvent::KeyVerificationCancelEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationCancelEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QString KeyVerificationCancelEvent::reason() const +{ + return contentJson()["reason"_ls].toString(); +} + +QString KeyVerificationCancelEvent::code() const +{ + return contentJson()["code"_ls].toString(); +} + +KeyVerificationKeyEvent::KeyVerificationKeyEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationKeyEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QString KeyVerificationKeyEvent::key() const +{ + return contentJson()["key"_ls].toString(); +} + +KeyVerificationMacEvent::KeyVerificationMacEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationMacEvent::transactionId() const +{ + return contentJson()["transaction_id"].toString(); +} + +QString KeyVerificationMacEvent::keys() const +{ + return contentJson()["keys"].toString(); +} + +QHash KeyVerificationMacEvent::mac() const +{ + QHash macs; + const auto macObj = contentJson()["mac"_ls].toObject(); + for (auto mac = macObj.constBegin(); mac != macObj.constEnd(); mac++) { + macs.insert(mac.key(), mac.value().toString()); + } + return macs; +} diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h new file mode 100644 index 00000000..13e7dcdd --- /dev/null +++ b/lib/events/keyverificationevent.h @@ -0,0 +1,167 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "event.h" + +namespace Quotient { + +/// Requests a key verification with another user's devices. +/// Typically sent as a to-device event. +class KeyVerificationRequestEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.request", KeyVerificationRequestEvent) + + explicit KeyVerificationRequestEvent(const QJsonObject& obj); + + /// The device ID which is initiating the request. + QString fromDevice() const; + + /// An opaque identifier for the verification request. Must + /// be unique with respect to the devices involved. + QString transactionId() const; + + /// The verification methods supported by the sender. + QStringList methods() const; + + /// The POSIX timestamp in milliseconds for when the request was + /// made. If the request is in the future by more than 5 minutes or + /// more than 10 minutes in the past, the message should be ignored + /// by the receiver. + uint64_t timestamp() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationRequestEvent) + +/// Begins a key verification process. +class KeyVerificationStartEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.start", KeyVerificationStartEvent) + + explicit KeyVerificationStartEvent(const QJsonObject &obj); + + /// The device ID which is initiating the process. + QString fromDevice() const; + + /// An opaque identifier for the verification request. Must + /// be unique with respect to the devices involved. + QString transactionId() const; + + /// The verification method to use. + QString method() const; + + /// Optional method to use to verify the other user's key with. + Omittable nextMethod() const; + + // SAS.V1 methods + + /// The key agreement protocols the sending device understands. + /// \note Only exist if method is m.sas.v1 + QStringList keyAgreementProtocols() const; + + /// The hash methods the sending device understands. + /// \note Only exist if method is m.sas.v1 + QStringList hashes() const; + + /// The message authentication codes that the sending device understands. + /// \note Only exist if method is m.sas.v1 + QStringList messageAuthenticationCodes() const; + + /// The SAS methods the sending device (and the sending device's + /// user) understands. + /// \note Only exist if method is m.sas.v1 + QString shortAuthenticationString() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationStartEvent) + +/// Accepts a previously sent m.key.verification.start message. +/// Typically sent as a to-device event. +class KeyVerificationAcceptEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.accept", KeyVerificationAcceptEvent) + + explicit KeyVerificationAcceptEvent(const QJsonObject& obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// The verification method to use. Must be 'm.sas.v1'. + QString method() const; + + /// The key agreement protocol the device is choosing to use, out of + /// the options in the m.key.verification.start message. + QString keyAgreementProtocol() const; + + /// The hash method the device is choosing to use, out of the + /// options in the m.key.verification.start message. + QString hashData() const; + + /// The message authentication code the device is choosing to use, out + /// of the options in the m.key.verification.start message. + QString messageAuthenticationCode() const; + + /// The SAS methods both devices involved in the verification process understand. + QStringList shortAuthenticationString() const; + + /// The hash (encoded as unpadded base64) of the concatenation of the + /// device's ephemeral public key (encoded as unpadded base64) and the + /// canonical JSON representation of the m.key.verification.start message. + QString commitement() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationAcceptEvent) + +class KeyVerificationCancelEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.cancel", KeyVerificationCancelEvent) + + explicit KeyVerificationCancelEvent(const QJsonObject &obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// A human readable description of the code. The client should only + /// rely on this string if it does not understand the code. + QString reason() const; + + /// The error code for why the process/request was cancelled by the user. + QString code() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationCancelEvent) + +/// Sends the ephemeral public key for a device to the partner device. +/// Typically sent as a to-device event. +class KeyVerificationKeyEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent) + + explicit KeyVerificationKeyEvent(const QJsonObject &obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// The device's ephemeral public key, encoded as unpadded base64. + QString key() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationKeyEvent) + +/// Sends the MAC of a device's key to the partner device. +class KeyVerificationMacEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.mac", KeyVerificationMacEvent) + + explicit KeyVerificationMacEvent(const QJsonObject &obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// The device's ephemeral public key, encoded as unpadded base64. + QString keys() const; + + QHash mac() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationMacEvent) +} // namespace Quotient -- cgit v1.2.3 From 0bafc33d70ddfdc8c4015a7f330623c726fe7ef7 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 24 Oct 2021 16:47:27 +0200 Subject: Only build the encryptionmanager when encryption is enabled --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index 92a9b213..8d5f08af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,7 +128,6 @@ list(APPEND lib_SRCS lib/networksettings.cpp lib/converters.cpp lib/util.cpp - lib/encryptionmanager.cpp lib/eventitem.cpp lib/accountregistry.cpp lib/mxcreply.cpp @@ -171,6 +170,7 @@ if (${PROJECT_NAME}_ENABLE_E2EE) lib/crypto/qolmerrors.cpp lib/crypto/qolmsession.cpp lib/crypto/qolmmessage.cpp + lib/encryptionmanager.cpp ) endif() -- cgit v1.2.3 From 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 'CMakeLists.txt') 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 545852ca45fadb3ee43072763e81cbfba0366e25 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 19 Nov 2021 21:49:46 +0100 Subject: Fix compilation --- CMakeLists.txt | 2 +- lib/connection.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index 3977a9d0..dbb43f89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,7 +109,7 @@ if (${PROJECT_NAME}_ENABLE_E2EE) endif() endif() -find_package(Qt${QT_MAJOR_VERSION}Keychain REQUIRED) +find_package(${Qt}Keychain REQUIRED) # Set up source files list(APPEND lib_SRCS diff --git a/lib/connection.cpp b/lib/connection.cpp index 6ed116c4..95ed1eb6 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2045,9 +2045,11 @@ PicklingMode Connection::picklingMode() const void Connection::saveOlmAccount() { qCDebug(E2EE) << "Saving olm account"; +#ifdef Quotient_E2EE_ENABLED auto pickle = d->olmAccount->pickle(d->picklingMode); AccountSettings(d->data->userId()).setEncryptionAccountPickle(std::get(pickle)); //TODO handle errors +#endif } QString Connection::e2eeDataDir() const @@ -2062,6 +2064,7 @@ QString Connection::e2eeDataDir() const return path; } +#ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { auto room = provideRoom(notification["room_id"].toString()); @@ -2072,3 +2075,4 @@ QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) } return decrypted->fullJson(); } +#endif -- cgit v1.2.3 From 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 'CMakeLists.txt') 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 'CMakeLists.txt') 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 58798ce15f0f235d64f9c34b3f8c013678ebf25f Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 9 Dec 2021 23:26:24 +0100 Subject: Ifdef all the things --- CMakeLists.txt | 13 ++++++++----- lib/connection.cpp | 12 ++++-------- lib/events/roomevent.cpp | 2 ++ lib/events/roomevent.h | 5 +++++ lib/room.cpp | 6 +++++- 5 files changed, 24 insertions(+), 14 deletions(-) (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index a84a70fb..43fed3e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,11 +83,12 @@ else() set(QtExtraModules "Multimedia") # See #483 endif() string(REGEX REPLACE "^(.).*" "Qt\\1" Qt ${QtMinVersion}) # makes "Qt5" or "Qt6" -find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test Sql ${QtExtraModules}) +find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModules}) get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") if (${PROJECT_NAME}_ENABLE_E2EE) + find_package(${Qt} ${QtMinVersion} REQUIRED Sql) find_package(Olm 3.2.1 REQUIRED) set_package_properties(Olm PROPERTIES DESCRIPTION "Implementation of the Olm and Megolm cryptographic ratchets" @@ -107,9 +108,9 @@ if (${PROJECT_NAME}_ENABLE_E2EE) if (OpenSSL_FOUND) message(STATUS "Using OpenSSL ${OpenSSL_VERSION} at ${OpenSSL_DIR}") endif() + find_package(${Qt}Keychain REQUIRED) endif() -find_package(${Qt}Keychain REQUIRED) # Set up source files list(APPEND lib_SRCS @@ -133,7 +134,6 @@ list(APPEND lib_SRCS lib/eventitem.cpp lib/accountregistry.cpp lib/mxcreply.cpp - lib/database.cpp lib/events/event.cpp lib/events/roomevent.cpp lib/events/stateevent.cpp @@ -164,6 +164,7 @@ list(APPEND lib_SRCS ) if (${PROJECT_NAME}_ENABLE_E2EE) list(APPEND lib_SRCS + lib/database.cpp lib/e2ee/qolmaccount.cpp lib/e2ee/qolmsession.cpp lib/e2ee/qolminboundsession.cpp @@ -323,12 +324,14 @@ target_include_directories(${PROJECT_NAME} PUBLIC if (${PROJECT_NAME}_ENABLE_E2EE) target_link_libraries(${PROJECT_NAME} Olm::Olm OpenSSL::Crypto - OpenSSL::SSL) + OpenSSL::SSL + ${Qt}::Sql + ${QTKEYCHAIN_LIBRARIES}) set(FIND_DEPS "find_dependency(Olm) find_dependency(OpenSSL)") # For QuotientConfig.cmake.in endif() -target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui ${Qt}::Sql ${QTKEYCHAIN_LIBRARIES}) +target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) if (Qt STREQUAL Qt5) # See #483 target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) diff --git a/lib/connection.cpp b/lib/connection.cpp index b7aaca86..433dd942 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -7,9 +7,6 @@ #include "connection.h" #include "connectiondata.h" -#ifdef Quotient_E2EE_ENABLED -# include "encryptionmanager.h" -#endif // Quotient_E2EE_ENABLED #include "room.h" #include "settings.h" #include "user.h" @@ -40,6 +37,8 @@ #ifdef Quotient_E2EE_ENABLED # include "e2ee/qolmaccount.h" # include "e2ee/qolmutils.h" +# include "encryptionmanager.h" +# include "database.h" #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -62,7 +61,6 @@ # include #endif -#include "database.h" using namespace Quotient; @@ -274,9 +272,9 @@ Connection::Connection(const QUrl& server, QObject* parent) connect(qApp, &QCoreApplication::aboutToQuit, this, [this](){ saveOlmAccount(); }); + Database::instance(); #endif d->q = this; // All d initialization should occur before this line - Database::instance(); } Connection::Connection(QObject* parent) : Connection({}, parent) {} @@ -442,15 +440,13 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) auto loginJob = q->callApi(std::forward(loginArgs)...); connect(loginJob, &BaseJob::success, q, [this, loginJob] { - Database::instance().clear(loginJob->userId()); data->setToken(loginJob->accessToken().toLatin1()); data->setDeviceId(loginJob->deviceId()); completeSetup(loginJob->userId()); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED - //encryptionManager->uploadIdentityKeys(q); - //encryptionManager->uploadOneTimeKeys(q); + Database::instance().clear(loginJob->userId()); #endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index b99d1381..dbce2255 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -127,6 +127,7 @@ CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) qCWarning(EVENTS) << id() << "is a call event with an empty call id"; } +#ifdef Quotient_E2EE_ENABLED void RoomEvent::setOriginalEvent(event_ptr_tt originalEvent) { _originalEvent = std::move(originalEvent); @@ -139,3 +140,4 @@ const QJsonObject RoomEvent::encryptedJson() const } return _originalEvent->fullJson(); } +#endif diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 35527a62..36b45f09 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -60,15 +60,20 @@ public: //! callback for that in RoomEvent. void addId(const QString& newId); +#ifdef Quotient_E2EE_ENABLED void setOriginalEvent(event_ptr_tt originalEvent); const QJsonObject encryptedJson() const; +#endif protected: void dumpTo(QDebug dbg) const override; private: event_ptr_tt _redactedBecause; + +#ifdef Quotient_E2EE_ENABLED event_ptr_tt _originalEvent; +#endif }; using RoomEventPtr = event_ptr_tt; using RoomEvents = EventsArray; diff --git a/lib/room.cpp b/lib/room.cpp index b3a092f3..7d608520 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -68,9 +68,9 @@ #include "e2ee/qolmaccount.h" #include "e2ee/qolmerrors.h" #include "e2ee/qolminboundsession.h" +#include "database.h" #endif // Quotient_E2EE_ENABLED -#include "database.h" using namespace Quotient; using namespace std::placeholders; @@ -2593,6 +2593,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) QElapsedTimer et; et.start(); +#ifdef Quotient_E2EE_ENABLED for(long unsigned int i = 0; i < events.size(); i++) { if(auto* encrypted = eventCast(events[i])) { auto decrypted = q->decryptMessage(*encrypted); @@ -2604,6 +2605,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) } } } +#endif { // Pre-process redactions and edits so that events that get @@ -2758,6 +2760,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) Changes changes {}; +#ifdef Quotient_E2EE_ENABLED for(long unsigned int i = 0; i < events.size(); i++) { if(auto* encrypted = eventCast(events[i])) { auto decrypted = q->decryptMessage(*encrypted); @@ -2769,6 +2772,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) } } } +#endif // In case of lazy-loading new members may be loaded with historical // messages. Also, the cache doesn't store events with empty content; -- cgit v1.2.3 From 1176ec1eedb749e81e3d446733c267a971feefa4 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 24 Dec 2021 00:03:32 +0100 Subject: Find sql when using libquotient --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index 43fed3e9..1ff65282 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -328,7 +328,8 @@ if (${PROJECT_NAME}_ENABLE_E2EE) ${Qt}::Sql ${QTKEYCHAIN_LIBRARIES}) set(FIND_DEPS "find_dependency(Olm) - find_dependency(OpenSSL)") # For QuotientConfig.cmake.in + find_dependency(OpenSSL) + find_dependency(${Qt}Sql)") # For QuotientConfig.cmake.in endif() target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) -- cgit v1.2.3 From 60947d610d0ece6943d2c2e385d6c6c2f960853d Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 24 Dec 2021 17:07:29 +0100 Subject: Apply suggestions --- .github/workflows/sonar.yml | 121 --------------------------------------- CMakeLists.txt | 2 +- Makefile | 31 ---------- autotests/testolmaccount.cpp | 7 --- lib/converters.cpp | 2 +- lib/e2ee/qolmaccount.cpp | 2 +- lib/e2ee/qolmerrors.cpp | 13 +++-- lib/e2ee/qolmerrors.h | 2 +- lib/e2ee/qolminboundsession.cpp | 5 +- lib/e2ee/qolmoutboundsession.cpp | 3 +- lib/e2ee/qolmsession.cpp | 2 +- lib/e2ee/qolmutility.cpp | 2 +- run-tests.sh | 23 ++++++++ 13 files changed, 40 insertions(+), 175 deletions(-) delete mode 100644 .github/workflows/sonar.yml delete mode 100644 Makefile create mode 100755 run-tests.sh (limited to 'CMakeLists.txt') diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml deleted file mode 100644 index c987b0cc..00000000 --- a/.github/workflows/sonar.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: Sonar - -on: - push: - pull_request: - types: [opened, reopened] - -defaults: - run: - shell: bash - -jobs: - SonarCloud: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - qt-version: [ '5.12.10' ] - e2ee: [ '', 'e2ee' ] - update-api: [ '', 'update-api' ] - - env: - SONAR_SCANNER_VERSION: 4.6.2.2472 - SONAR_SERVER_URL: "https://sonarcloud.io" - BUILD_WRAPPER_OUT_DIR: build/sonar - - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - submodules: ${{ matrix.e2ee != '' }} - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v2 - with: - path: ${{ runner.workspace }}/Qt - key: ${{ runner.os }}-Qt${{ matrix.qt-version }}-cache - - - name: Install Qt - uses: jurplel/install-qt-action@v2.11.1 - with: - version: ${{ matrix.qt-version }} -# arch: ${{ matrix.qt-arch }} # Only Windows needs that - cached: ${{ steps.cache-qt.outputs.cache-hit }} - - - name: Install Ninja - uses: seanmiddleditch/gha-setup-ninja@v3 - - - name: Setup build environment - run: | - echo "CC=gcc-10" >>$GITHUB_ENV - echo "CXX=g++-10" >>$GITHUB_ENV - mkdir -p $HOME/.sonar - echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ - -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV - cmake -E make_directory ${{ runner.workspace }}/build - - - name: Build and install olm - if: matrix.e2ee - run: | - cd .. - git clone https://gitlab.matrix.org/matrix-org/olm.git - cmake -S olm -B olm/build $CMAKE_ARGS - cmake --build olm/build --target install - - - name: Build and install qtKeychain - if: matrix.e2ee - run: | - cd .. - git clone https://github.com/frankosterfeld/qtkeychain.git - cmake -S qtkeychain -B qtkeychain/build $CMAKE_ARGS - cmake --build qtkeychain/build --target install - - - name: Pull CS API and build GTAD - if: matrix.update-api - run: | - cd .. - git clone https://github.com/quotient-im/matrix-doc.git - git clone --recursive https://github.com/KitsuneRal/gtad.git - cmake -S gtad -B gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF - cmake --build gtad - echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=$GITHUB_WORKSPACE/../matrix-doc \ - -DGTAD_PATH=$GITHUB_WORKSPACE/../gtad/gtad" \ - >>$GITHUB_ENV - - - name: Download and set up Sonar Cloud tools - run: | - pushd $HOME/.sonar - curl -sSLo build-wrapper.zip $SONAR_SERVER_URL/static/cpp/build-wrapper-linux-x86.zip - unzip -o build-wrapper.zip - echo "BUILD_WRAPPER=$HOME/.sonar/build-wrapper-linux-x86/build-wrapper-linux* --out-dir $BUILD_WRAPPER_OUT_DIR" >>$GITHUB_ENV - curl -sSLo sonar-scanner.zip \ - https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip - unzip -o sonar-scanner.zip - popd - - - name: Configure libQuotient - run: | - if [[ '${{ runner.os }}' == 'Windows' ]]; then - BIN_DIR=. - else - BIN_DIR=bin - fi - echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV - cmake -S $GITHUB_WORKSPACE -B build $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} - - - name: Regenerate API code - if: matrix.update-api - run: cmake --build build --target update-api - - - name: Build libQuotient - run: | - $BUILD_WRAPPER cmake --build build --target all - - - name: Run sonar-scanner - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: | - $HOME/.sonar/sonar-scanner*/bin/sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ff65282..9ef3477e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,7 @@ message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") if (${PROJECT_NAME}_ENABLE_E2EE) find_package(${Qt} ${QtMinVersion} REQUIRED Sql) - find_package(Olm 3.2.1 REQUIRED) + find_package(Olm 3.1.3 REQUIRED) set_package_properties(Olm PROPERTIES DESCRIPTION "Implementation of the Olm and Megolm cryptographic ratchets" URL "https://gitlab.matrix.org/matrix-org/olm" diff --git a/Makefile b/Makefile deleted file mode 100644 index 450e7888..00000000 --- a/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -SYNAPSE_IMAGE="matrixdotorg/synapse:v1.24.0" - -test: ## Run the tests - @cd build/ && GTEST_COLOR=1 ctest --verbose - -synapse: ## Start a synapse instance on docker - @mkdir -p data - @chmod 0777 data - @docker run -v `pwd`/data:/data --rm \ - -e SYNAPSE_SERVER_NAME=localhost -e SYNAPSE_REPORT_STATS=no ${SYNAPSE_IMAGE} generate - @./.ci/adjust-config.sh - @docker run -d \ - --name synapse \ - -p 443:8008 \ - -p 8448:8008 \ - -p 8008:8008 \ - -v `pwd`/data:/data ${SYNAPSE_IMAGE} - @echo Waiting for synapse to start... - @until curl -s -f -k https://localhost:443/_matrix/client/versions; do echo "Checking ..."; sleep 2; done - @echo Register alice - @docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice -p secret -c /data/homeserver.yaml https://localhost:8008' - @echo Register bob - @docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob -p secret -c /data/homeserver.yaml https://localhost:8008' - @echo Register carl - @docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008' - -stop-synapse: ## Stop any running instance of synapse - @rm -rf ./data/* - @docker rm -f synapse 2>&1>/dev/null - -restart: stop-synapse synapse diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index f0fcfe58..d547b683 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -12,13 +12,6 @@ #include #include -// for sleep -#ifdef _WIN32 -#include -#else -#include -#endif - using namespace Quotient; void TestOlmAccount::pickleUnpickledTest() diff --git a/lib/converters.cpp b/lib/converters.cpp index 4136940f..6cbb554d 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -3,7 +3,7 @@ #include "converters.h" -#include +#include #include "e2ee/e2ee.h" QJsonValue Quotient::JsonConverter::dump(const QVariant& v) diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index aaf51946..ffb004cc 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -34,7 +34,7 @@ bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) // Convert olm error to enum QOlmError lastError(OlmAccount *account) { - const std::string error_raw = olm_account_last_error(account); + const auto error_raw = olm_account_last_error(account); return fromString(error_raw); } diff --git a/lib/e2ee/qolmerrors.cpp b/lib/e2ee/qolmerrors.cpp index 6db1803c..568cf7fe 100644 --- a/lib/e2ee/qolmerrors.cpp +++ b/lib/e2ee/qolmerrors.cpp @@ -4,17 +4,18 @@ #include "qolmerrors.h" +#include -Quotient::QOlmError Quotient::fromString(const std::string &error_raw) { - if (!error_raw.compare("BAD_ACCOUNT_KEY")) { +Quotient::QOlmError Quotient::fromString(const char* error_raw) { + if (!strncmp(error_raw, "BAD_ACCOUNT_KEY", 15)) { return QOlmError::BadAccountKey; - } else if (!error_raw.compare("BAD_MESSAGE_KEY_ID")) { + } else if (!strncmp(error_raw, "BAD_MESSAGE_KEY_ID", 18)) { return QOlmError::BadMessageKeyId; - } else if (!error_raw.compare("INVALID_BASE64")) { + } else if (!strncmp(error_raw, "INVALID_BASE64", 14)) { return QOlmError::InvalidBase64; - } else if (!error_raw.compare("NOT_ENOUGH_RANDOM")) { + } else if (!strncmp(error_raw, "NOT_ENOUGH_RANDOM", 17)) { return QOlmError::NotEnoughRandom; - } else if (!error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { + } else if (!strncmp(error_raw, "OUTPUT_BUFFER_TOO_SMALL", 23)) { return QOlmError::OutputBufferTooSmall; } else { return QOlmError::Unknown; diff --git a/lib/e2ee/qolmerrors.h b/lib/e2ee/qolmerrors.h index f8390d2a..f2d77851 100644 --- a/lib/e2ee/qolmerrors.h +++ b/lib/e2ee/qolmerrors.h @@ -23,6 +23,6 @@ enum QOlmError Unknown, }; -QOlmError fromString(const std::string &error_raw); +QOlmError fromString(const char* error_raw); } //namespace Quotient diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 9bf56b6c..2c546875 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -8,7 +8,7 @@ using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { - const std::string error_raw = olm_inbound_group_session_last_error(session); + const auto error_raw = olm_inbound_group_session_last_error(session); return fromString(error_raw); } @@ -27,9 +27,8 @@ QOlmInboundGroupSession::~QOlmInboundGroupSession() std::unique_ptr QOlmInboundGroupSession::create(const QByteArray &key) { const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); - const auto temp = key; const auto error = olm_init_inbound_group_session(olmInboundGroupSession, - reinterpret_cast(temp.data()), temp.size()); + reinterpret_cast(key.constData()), key.size()); if (error == olm_error()) { throw lastError(olmInboundGroupSession); diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 88e6b2e1..58196412 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -8,7 +8,7 @@ using namespace Quotient; QOlmError lastError(OlmOutboundGroupSession *session) { - const std::string error_raw = olm_outbound_group_session_last_error(session); + const auto error_raw = olm_outbound_group_session_last_error(session); return fromString(error_raw); } @@ -21,6 +21,7 @@ QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *sess QOlmOutboundGroupSession::~QOlmOutboundGroupSession() { olm_clear_outbound_group_session(m_groupSession); + Q_ASSERT(sizeof(m_groupSession) == olm_outbound_group_session_size()); delete[](reinterpret_cast(m_groupSession)); } diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 69d8b431..575019b3 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -11,7 +11,7 @@ using namespace Quotient; QOlmError lastError(OlmSession* session) { - const std::string error_raw = olm_session_last_error(session); + const auto error_raw = olm_session_last_error(session); return fromString(error_raw); } diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index d0684055..13ee695e 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -10,7 +10,7 @@ using namespace Quotient; // Convert olm error to enum QOlmError lastError(OlmUtility *utility) { - const std::string error_raw = olm_utility_last_error(utility); + const auto error_raw = olm_utility_last_error(utility); return fromString(error_raw); } diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 00000000..b49f37a1 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,23 @@ +mkdir -p data +chmod 0777 data +docker run -v `pwd`/data:/data --rm \ + -e SYNAPSE_SERVER_NAME=localhost -e SYNAPSE_REPORT_STATS=no matrixdotorg/synapse:v1.24.0 generate +./.ci/adjust-config.sh +docker run -d \ + --name synapse \ + -p 1234:8008 \ + -p 8448:8008 \ + -p 8008:8008 \ + -v `pwd`/data:/data matrixdotorg/synapse:v1.24.0 +echo Waiting for synapse to start... +until curl -s -f -k https://localhost:1234/_matrix/client/versions; do echo "Checking ..."; sleep 2; done +echo Register alice +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice -p secret -c /data/homeserver.yaml https://localhost:8008' +echo Register bob +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob -p secret -c /data/homeserver.yaml https://localhost:8008' +echo Register carl +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008' + +cd build/ && GTEST_COLOR=1 ctest --verbose +rm -rf ./data/* +docker rm -f synapse 2>&1>/dev/null -- cgit v1.2.3 From 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 'CMakeLists.txt') 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