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" --- 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 ++ 17 files changed, 1570 insertions(+) create mode 100644 lib/e2ee/e2ee.h create mode 100644 lib/e2ee/qolmaccount.cpp create mode 100644 lib/e2ee/qolmaccount.h create mode 100644 lib/e2ee/qolmerrors.cpp create mode 100644 lib/e2ee/qolmerrors.h create mode 100644 lib/e2ee/qolminboundsession.cpp create mode 100644 lib/e2ee/qolminboundsession.h create mode 100644 lib/e2ee/qolmmessage.cpp create mode 100644 lib/e2ee/qolmmessage.h create mode 100644 lib/e2ee/qolmoutboundsession.cpp create mode 100644 lib/e2ee/qolmoutboundsession.h create mode 100644 lib/e2ee/qolmsession.cpp create mode 100644 lib/e2ee/qolmsession.h create mode 100644 lib/e2ee/qolmutility.cpp create mode 100644 lib/e2ee/qolmutility.h create mode 100644 lib/e2ee/qolmutils.cpp create mode 100644 lib/e2ee/qolmutils.h (limited to 'lib/e2ee') 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); +} -- 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 'lib/e2ee') diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml deleted file mode 100644 index c987b0cc..00000000 --- a/.github/workflows/sonar.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: Sonar - -on: - push: - pull_request: - types: [opened, reopened] - -defaults: - run: - shell: bash - -jobs: - SonarCloud: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - qt-version: [ '5.12.10' ] - e2ee: [ '', 'e2ee' ] - update-api: [ '', 'update-api' ] - - env: - SONAR_SCANNER_VERSION: 4.6.2.2472 - SONAR_SERVER_URL: "https://sonarcloud.io" - BUILD_WRAPPER_OUT_DIR: build/sonar - - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - submodules: ${{ matrix.e2ee != '' }} - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v2 - with: - path: ${{ runner.workspace }}/Qt - key: ${{ runner.os }}-Qt${{ matrix.qt-version }}-cache - - - name: Install Qt - uses: jurplel/install-qt-action@v2.11.1 - with: - version: ${{ matrix.qt-version }} -# arch: ${{ matrix.qt-arch }} # Only Windows needs that - cached: ${{ steps.cache-qt.outputs.cache-hit }} - - - name: Install Ninja - uses: seanmiddleditch/gha-setup-ninja@v3 - - - name: Setup build environment - run: | - echo "CC=gcc-10" >>$GITHUB_ENV - echo "CXX=g++-10" >>$GITHUB_ENV - mkdir -p $HOME/.sonar - echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ - -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV - cmake -E make_directory ${{ runner.workspace }}/build - - - name: Build and install olm - if: matrix.e2ee - run: | - cd .. - git clone https://gitlab.matrix.org/matrix-org/olm.git - cmake -S olm -B olm/build $CMAKE_ARGS - cmake --build olm/build --target install - - - name: Build and install qtKeychain - if: matrix.e2ee - run: | - cd .. - git clone https://github.com/frankosterfeld/qtkeychain.git - cmake -S qtkeychain -B qtkeychain/build $CMAKE_ARGS - cmake --build qtkeychain/build --target install - - - name: Pull CS API and build GTAD - if: matrix.update-api - run: | - cd .. - git clone https://github.com/quotient-im/matrix-doc.git - git clone --recursive https://github.com/KitsuneRal/gtad.git - cmake -S gtad -B gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF - cmake --build gtad - echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=$GITHUB_WORKSPACE/../matrix-doc \ - -DGTAD_PATH=$GITHUB_WORKSPACE/../gtad/gtad" \ - >>$GITHUB_ENV - - - name: Download and set up Sonar Cloud tools - run: | - pushd $HOME/.sonar - curl -sSLo build-wrapper.zip $SONAR_SERVER_URL/static/cpp/build-wrapper-linux-x86.zip - unzip -o build-wrapper.zip - echo "BUILD_WRAPPER=$HOME/.sonar/build-wrapper-linux-x86/build-wrapper-linux* --out-dir $BUILD_WRAPPER_OUT_DIR" >>$GITHUB_ENV - curl -sSLo sonar-scanner.zip \ - https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip - unzip -o sonar-scanner.zip - popd - - - name: Configure libQuotient - run: | - if [[ '${{ runner.os }}' == 'Windows' ]]; then - BIN_DIR=. - else - BIN_DIR=bin - fi - echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV - cmake -S $GITHUB_WORKSPACE -B build $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} - - - name: Regenerate API code - if: matrix.update-api - run: cmake --build build --target update-api - - - name: Build libQuotient - run: | - $BUILD_WRAPPER cmake --build build --target all - - - name: Run sonar-scanner - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: | - $HOME/.sonar/sonar-scanner*/bin/sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ff65282..9ef3477e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,7 @@ message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") if (${PROJECT_NAME}_ENABLE_E2EE) find_package(${Qt} ${QtMinVersion} REQUIRED Sql) - find_package(Olm 3.2.1 REQUIRED) + find_package(Olm 3.1.3 REQUIRED) set_package_properties(Olm PROPERTIES DESCRIPTION "Implementation of the Olm and Megolm cryptographic ratchets" URL "https://gitlab.matrix.org/matrix-org/olm" diff --git a/Makefile b/Makefile deleted file mode 100644 index 450e7888..00000000 --- a/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -SYNAPSE_IMAGE="matrixdotorg/synapse:v1.24.0" - -test: ## Run the tests - @cd build/ && GTEST_COLOR=1 ctest --verbose - -synapse: ## Start a synapse instance on docker - @mkdir -p data - @chmod 0777 data - @docker run -v `pwd`/data:/data --rm \ - -e SYNAPSE_SERVER_NAME=localhost -e SYNAPSE_REPORT_STATS=no ${SYNAPSE_IMAGE} generate - @./.ci/adjust-config.sh - @docker run -d \ - --name synapse \ - -p 443:8008 \ - -p 8448:8008 \ - -p 8008:8008 \ - -v `pwd`/data:/data ${SYNAPSE_IMAGE} - @echo Waiting for synapse to start... - @until curl -s -f -k https://localhost:443/_matrix/client/versions; do echo "Checking ..."; sleep 2; done - @echo Register alice - @docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice -p secret -c /data/homeserver.yaml https://localhost:8008' - @echo Register bob - @docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob -p secret -c /data/homeserver.yaml https://localhost:8008' - @echo Register carl - @docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008' - -stop-synapse: ## Stop any running instance of synapse - @rm -rf ./data/* - @docker rm -f synapse 2>&1>/dev/null - -restart: stop-synapse synapse diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index f0fcfe58..d547b683 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -12,13 +12,6 @@ #include #include -// for sleep -#ifdef _WIN32 -#include -#else -#include -#endif - using namespace Quotient; void TestOlmAccount::pickleUnpickledTest() diff --git a/lib/converters.cpp b/lib/converters.cpp index 4136940f..6cbb554d 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -3,7 +3,7 @@ #include "converters.h" -#include +#include #include "e2ee/e2ee.h" QJsonValue Quotient::JsonConverter::dump(const QVariant& v) diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index aaf51946..ffb004cc 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -34,7 +34,7 @@ bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) // Convert olm error to enum QOlmError lastError(OlmAccount *account) { - const std::string error_raw = olm_account_last_error(account); + const auto error_raw = olm_account_last_error(account); return fromString(error_raw); } diff --git a/lib/e2ee/qolmerrors.cpp b/lib/e2ee/qolmerrors.cpp index 6db1803c..568cf7fe 100644 --- a/lib/e2ee/qolmerrors.cpp +++ b/lib/e2ee/qolmerrors.cpp @@ -4,17 +4,18 @@ #include "qolmerrors.h" +#include -Quotient::QOlmError Quotient::fromString(const std::string &error_raw) { - if (!error_raw.compare("BAD_ACCOUNT_KEY")) { +Quotient::QOlmError Quotient::fromString(const char* error_raw) { + if (!strncmp(error_raw, "BAD_ACCOUNT_KEY", 15)) { return QOlmError::BadAccountKey; - } else if (!error_raw.compare("BAD_MESSAGE_KEY_ID")) { + } else if (!strncmp(error_raw, "BAD_MESSAGE_KEY_ID", 18)) { return QOlmError::BadMessageKeyId; - } else if (!error_raw.compare("INVALID_BASE64")) { + } else if (!strncmp(error_raw, "INVALID_BASE64", 14)) { return QOlmError::InvalidBase64; - } else if (!error_raw.compare("NOT_ENOUGH_RANDOM")) { + } else if (!strncmp(error_raw, "NOT_ENOUGH_RANDOM", 17)) { return QOlmError::NotEnoughRandom; - } else if (!error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { + } else if (!strncmp(error_raw, "OUTPUT_BUFFER_TOO_SMALL", 23)) { return QOlmError::OutputBufferTooSmall; } else { return QOlmError::Unknown; diff --git a/lib/e2ee/qolmerrors.h b/lib/e2ee/qolmerrors.h index f8390d2a..f2d77851 100644 --- a/lib/e2ee/qolmerrors.h +++ b/lib/e2ee/qolmerrors.h @@ -23,6 +23,6 @@ enum QOlmError Unknown, }; -QOlmError fromString(const std::string &error_raw); +QOlmError fromString(const char* error_raw); } //namespace Quotient diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 9bf56b6c..2c546875 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -8,7 +8,7 @@ using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { - const std::string error_raw = olm_inbound_group_session_last_error(session); + const auto error_raw = olm_inbound_group_session_last_error(session); return fromString(error_raw); } @@ -27,9 +27,8 @@ QOlmInboundGroupSession::~QOlmInboundGroupSession() std::unique_ptr QOlmInboundGroupSession::create(const QByteArray &key) { const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); - const auto temp = key; const auto error = olm_init_inbound_group_session(olmInboundGroupSession, - reinterpret_cast(temp.data()), temp.size()); + reinterpret_cast(key.constData()), key.size()); if (error == olm_error()) { throw lastError(olmInboundGroupSession); diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 88e6b2e1..58196412 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -8,7 +8,7 @@ using namespace Quotient; QOlmError lastError(OlmOutboundGroupSession *session) { - const std::string error_raw = olm_outbound_group_session_last_error(session); + const auto error_raw = olm_outbound_group_session_last_error(session); return fromString(error_raw); } @@ -21,6 +21,7 @@ QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *sess QOlmOutboundGroupSession::~QOlmOutboundGroupSession() { olm_clear_outbound_group_session(m_groupSession); + Q_ASSERT(sizeof(m_groupSession) == olm_outbound_group_session_size()); delete[](reinterpret_cast(m_groupSession)); } diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 69d8b431..575019b3 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -11,7 +11,7 @@ using namespace Quotient; QOlmError lastError(OlmSession* session) { - const std::string error_raw = olm_session_last_error(session); + const auto error_raw = olm_session_last_error(session); return fromString(error_raw); } diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index d0684055..13ee695e 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -10,7 +10,7 @@ using namespace Quotient; // Convert olm error to enum QOlmError lastError(OlmUtility *utility) { - const std::string error_raw = olm_utility_last_error(utility); + const auto error_raw = olm_utility_last_error(utility); return fromString(error_raw); } diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 00000000..b49f37a1 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,23 @@ +mkdir -p data +chmod 0777 data +docker run -v `pwd`/data:/data --rm \ + -e SYNAPSE_SERVER_NAME=localhost -e SYNAPSE_REPORT_STATS=no matrixdotorg/synapse:v1.24.0 generate +./.ci/adjust-config.sh +docker run -d \ + --name synapse \ + -p 1234:8008 \ + -p 8448:8008 \ + -p 8008:8008 \ + -v `pwd`/data:/data matrixdotorg/synapse:v1.24.0 +echo Waiting for synapse to start... +until curl -s -f -k https://localhost:1234/_matrix/client/versions; do echo "Checking ..."; sleep 2; done +echo Register alice +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u alice -p secret -c /data/homeserver.yaml https://localhost:8008' +echo Register bob +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u bob -p secret -c /data/homeserver.yaml https://localhost:8008' +echo Register carl +docker exec synapse /bin/sh -c 'register_new_matrix_user --admin -u carl -p secret -c /data/homeserver.yaml https://localhost:8008' + +cd build/ && GTEST_COLOR=1 ctest --verbose +rm -rf ./data/* +docker rm -f synapse 2>&1>/dev/null -- cgit v1.2.3 From f01427c307c825feaa6c45888c259903605c9796 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 25 Dec 2021 15:59:03 +0100 Subject: Update lib/e2ee/qolmoutboundsession.cpp Co-authored-by: Alexey Rusakov --- lib/e2ee/qolmoutboundsession.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 58196412..8494efdd 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -64,7 +64,6 @@ std::variant QOlmOutboundGroupSession::pickle(const Pickl return pickledBuf; } - std::variant, QOlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) { QByteArray pickledBuf = pickled; -- cgit v1.2.3 From f4d6a08811b0bf3a10a23ff703fc5a8ff1fcc624 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 25 Dec 2021 16:00:09 +0100 Subject: Apply suggestions --- lib/e2ee/qolmaccount.cpp | 4 +--- lib/e2ee/qolmerrors.cpp | 14 ++++++++------ lib/e2ee/qolmerrors.h | 2 -- lib/e2ee/qolminboundsession.cpp | 4 +--- lib/e2ee/qolmoutboundsession.cpp | 4 +--- lib/e2ee/qolmsession.cpp | 4 +--- lib/e2ee/qolmutility.cpp | 4 +--- 7 files changed, 13 insertions(+), 23 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index ffb004cc..a984f884 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -34,9 +34,7 @@ bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) // Convert olm error to enum QOlmError lastError(OlmAccount *account) { - const auto error_raw = olm_account_last_error(account); - - return fromString(error_raw); + return fromString(olm_account_last_error(account)); } QByteArray getRandom(size_t bufferSize) diff --git a/lib/e2ee/qolmerrors.cpp b/lib/e2ee/qolmerrors.cpp index 568cf7fe..5a60b7e6 100644 --- a/lib/e2ee/qolmerrors.cpp +++ b/lib/e2ee/qolmerrors.cpp @@ -4,18 +4,20 @@ #include "qolmerrors.h" -#include +#include "util.h" +#include Quotient::QOlmError Quotient::fromString(const char* error_raw) { - if (!strncmp(error_raw, "BAD_ACCOUNT_KEY", 15)) { + const QLatin1String error { error_raw }; + if (error_raw == "BAD_ACCOUNT_KEY"_ls) { return QOlmError::BadAccountKey; - } else if (!strncmp(error_raw, "BAD_MESSAGE_KEY_ID", 18)) { + } else if (error_raw == "BAD_MESSAGE_KEY_ID"_ls) { return QOlmError::BadMessageKeyId; - } else if (!strncmp(error_raw, "INVALID_BASE64", 14)) { + } else if (error_raw == "INVALID_BASE64"_ls) { return QOlmError::InvalidBase64; - } else if (!strncmp(error_raw, "NOT_ENOUGH_RANDOM", 17)) { + } else if (error_raw == "NOT_ENOUGH_RANDOM"_ls) { return QOlmError::NotEnoughRandom; - } else if (!strncmp(error_raw, "OUTPUT_BUFFER_TOO_SMALL", 23)) { + } else if (error_raw == "OUTPUT_BUFFER_TOO_SMALL"_ls) { return QOlmError::OutputBufferTooSmall; } else { return QOlmError::Unknown; diff --git a/lib/e2ee/qolmerrors.h b/lib/e2ee/qolmerrors.h index f2d77851..24e87d95 100644 --- a/lib/e2ee/qolmerrors.h +++ b/lib/e2ee/qolmerrors.h @@ -4,8 +4,6 @@ #pragma once -#include - namespace Quotient { //! All errors that could be caused by an operation regarding Olm //! Errors are named exactly like the ones in libolm. diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 2c546875..9729c02d 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -8,9 +8,7 @@ using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { - const auto error_raw = olm_inbound_group_session_last_error(session); - - return fromString(error_raw); + return fromString(olm_inbound_group_session_last_error(session)); } QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session) diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 8494efdd..e75ab427 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -8,9 +8,7 @@ using namespace Quotient; QOlmError lastError(OlmOutboundGroupSession *session) { - const auto error_raw = olm_outbound_group_session_last_error(session); - - return fromString(error_raw); + return fromString(olm_outbound_group_session_last_error(session)); } QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session) diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 575019b3..e575ff39 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -11,9 +11,7 @@ using namespace Quotient; QOlmError lastError(OlmSession* session) { - const auto error_raw = olm_session_last_error(session); - - return fromString(error_raw); + return fromString(olm_session_last_error(session)); } Quotient::QOlmSession::~QOlmSession() diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index 13ee695e..303f6d75 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -10,9 +10,7 @@ using namespace Quotient; // Convert olm error to enum QOlmError lastError(OlmUtility *utility) { - const auto error_raw = olm_utility_last_error(utility); - - return fromString(error_raw); + return fromString(olm_utility_last_error(utility)); } QOlmUtility::QOlmUtility() -- cgit v1.2.3 From 9ef0c672595f47532966566ec08f16424fcf1abb Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 25 Dec 2021 18:22:25 +0100 Subject: Update lib/e2ee/qolmoutboundsession.h Co-authored-by: Alexey Rusakov --- lib/e2ee/qolmoutboundsession.h | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 967f563f..72d87035 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -12,7 +12,6 @@ namespace Quotient { - //! An out-bound group session is responsible for encrypting outgoing //! communication in a Megolm session. class QOlmOutboundGroupSession -- cgit v1.2.3 From b465e785aac47bab8f4b8f4dac9672c9c32ea020 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 25 Dec 2021 18:22:31 +0100 Subject: Update lib/e2ee/qolmoutboundsession.h Co-authored-by: Alexey Rusakov --- lib/e2ee/qolmoutboundsession.h | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 72d87035..39263c77 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later - #pragma once #include "olm/olm.h" -- cgit v1.2.3 From 3164daa6d6cbfb75a1528b2671f5c78422a5539e Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 25 Dec 2021 19:57:26 +0100 Subject: Remove assert --- lib/e2ee/qolmoutboundsession.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index e75ab427..da32417b 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -19,7 +19,6 @@ QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *sess QOlmOutboundGroupSession::~QOlmOutboundGroupSession() { olm_clear_outbound_group_session(m_groupSession); - Q_ASSERT(sizeof(m_groupSession) == olm_outbound_group_session_size()); delete[](reinterpret_cast(m_groupSession)); } -- cgit v1.2.3 From ac53741920b1e92b8ac61bb7c11afcae5722b241 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sun, 6 Feb 2022 20:00:38 +0100 Subject: Update lib/e2ee/qolminboundsession.cpp Co-authored-by: Alexey Rusakov --- lib/e2ee/qolminboundsession.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 9729c02d..9bc80eef 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -4,7 +4,6 @@ #include "e2ee/qolminboundsession.h" #include -#include using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { -- cgit v1.2.3 From 9960d33e0c2bbe4dd8f305cae3b01c2d704d28ff Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 6 Feb 2022 20:00:01 +0100 Subject: Port to QRandomGenerator --- lib/e2ee/qolmutils.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmutils.cpp b/lib/e2ee/qolmutils.cpp index ce27710d..6f7937e8 100644 --- a/lib/e2ee/qolmutils.cpp +++ b/lib/e2ee/qolmutils.cpp @@ -3,8 +3,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "e2ee/qolmutils.h" -#include -#include +#include using namespace Quotient; @@ -19,6 +18,6 @@ QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) QByteArray Quotient::getRandom(size_t bufferSize) { QByteArray buffer(bufferSize, '0'); - RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); + QRandomGenerator::system()->generate(buffer.begin(), buffer.end()); return buffer; } -- cgit v1.2.3 From 4837fd6de188b849a5d11de6791ba844398ce415 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sun, 6 Feb 2022 20:10:40 +0100 Subject: Update lib/e2ee/qolmaccount.cpp Co-authored-by: Alexey Rusakov --- lib/e2ee/qolmaccount.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index a984f884..34ee7ea0 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -29,7 +29,7 @@ std::optional> OneTimeKeys::get(QString keyType) const bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) { - return lhs.curve25519 == rhs.curve25519 &&& lhs.ed25519 == rhs.ed25519; + return lhs.curve25519 == rhs.curve25519 && lhs.ed25519 == rhs.ed25519; } // Convert olm error to enum -- cgit v1.2.3 From 7b5edb737522b03d4f697e0e09f1771ad5edef89 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 7 Feb 2022 21:48:07 +0100 Subject: Remove encryptionmanager and various fixes --- CMakeLists.txt | 2 +- autotests/testolmaccount.cpp | 2 +- lib/connection.cpp | 108 ++++++++++++++++++++++++----- lib/connection.h | 2 - lib/e2ee/qolminboundsession.cpp | 2 + lib/encryptionmanager.cpp | 149 ---------------------------------------- lib/encryptionmanager.h | 33 --------- lib/events/encryptedfile.cpp | 27 ++++++++ lib/events/encryptedfile.h | 6 ++ lib/jobs/downloadfilejob.cpp | 5 +- lib/mxcreply.cpp | 3 +- 11 files changed, 130 insertions(+), 209 deletions(-) delete mode 100644 lib/encryptionmanager.cpp delete mode 100644 lib/encryptionmanager.h create mode 100644 lib/events/encryptedfile.cpp (limited to 'lib/e2ee') diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ef3477e..69ac7e20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,6 +156,7 @@ list(APPEND lib_SRCS lib/events/roomkeyevent.cpp lib/events/stickerevent.cpp lib/events/keyverificationevent.cpp + lib/events/encryptedfile.cpp lib/jobs/requestdata.cpp lib/jobs/basejob.cpp lib/jobs/syncjob.cpp @@ -174,7 +175,6 @@ if (${PROJECT_NAME}_ENABLE_E2EE) lib/e2ee/qolmerrors.cpp lib/e2ee/qolmsession.cpp lib/e2ee/qolmmessage.cpp - lib/encryptionmanager.cpp ) endif() diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 45d158eb..62b786d0 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -537,7 +537,7 @@ void TestOlmAccount::enableEncryption() QString joinedRoom; auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), {"@bob:localhost"}); - connect(alice.get(), &Connection::newRoom, this, [alice, bob, joinedRoom, this] (Quotient::Room *room) { + connect(alice.get(), &Connection::newRoom, this, [alice, bob, &joinedRoom, this] (Quotient::Room *room) { room->activateEncryption(); QSignalSpy spy(room, &Room::encryption); diff --git a/lib/connection.cpp b/lib/connection.cpp index 58e3a9f8..1a1b284d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -37,7 +37,6 @@ #ifdef Quotient_E2EE_ENABLED # include "e2ee/qolmaccount.h" # include "e2ee/qolmutils.h" -# include "encryptionmanager.h" # include "database.h" #if QT_VERSION_MAJOR >= 6 @@ -117,6 +116,10 @@ public: bool encryptionUpdateRequired = false; PicklingMode picklingMode = Unencrypted {}; Database *database = nullptr; + + // A map from SenderKey to vector of InboundSession + UnorderedMap> olmSessions; + #endif GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -127,7 +130,6 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; bool isUploadingKeys = false; - EncryptionManager *encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -201,6 +203,85 @@ public: return q->stateCacheDir().filePath("state.json"); } +#ifdef Quotient_E2EE_ENABLED + void loadSessions() { + olmSessions = q->database()->loadOlmSessions(q->picklingMode()); + } + void saveSession(QOlmSessionPtr& session, const QString &senderKey) { + auto pickleResult = session->pickle(q->picklingMode()); + if (std::holds_alternative(pickleResult)) { + qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); + return; + } + q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult)); + } + QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) + { + Q_ASSERT(message.type() == QOlmMessage::PreKey); + for(auto& session : olmSessions[senderKey]) { + const auto matches = session->matchesInboundSessionFrom(senderKey, message); + if(std::holds_alternative(matches) && std::get(matches)) { + qCDebug(E2EE) << "Found inbound session"; + const auto result = session->decrypt(message); + if(std::holds_alternative(result)) { + return std::get(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message"; + return {}; + } + } + } + qCDebug(E2EE) << "Creating new inbound session"; + auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); + if(std::holds_alternative(newSessionResult)) { + qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); + return {}; + } + auto newSession = std::move(std::get(newSessionResult)); + auto error = olmAccount->removeOneTimeKeys(newSession); + if (error) { + qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); + } + const auto result = newSession->decrypt(message); + saveSession(newSession, senderKey); + olmSessions[senderKey].push_back(std::move(newSession)); + if(std::holds_alternative(result)) { + return std::get(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; + return {}; + } + } + QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) + { + Q_ASSERT(message.type() == QOlmMessage::General); + for(auto& session : olmSessions[senderKey]) { + const auto result = session->decrypt(message); + if(std::holds_alternative(result)) { + return std::get(result); + } + } + qCWarning(E2EE) << "Failed to decrypt message"; + return {}; + } + + QString sessionDecryptMessage( + const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) + { + QString decrypted; + int type = personalCipherObject.value(TypeKeyL).toInt(-1); + QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); + if (type == 0) { + QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); + decrypted = sessionDecryptPrekey(preKeyMessage, senderKey, account); + } else if (type == 1) { + QOlmMessage message(body, QOlmMessage::General); + decrypted = sessionDecryptGeneral(message, senderKey); + } + return decrypted; + } +#endif + EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { #ifndef Quotient_E2EE_ENABLED @@ -217,7 +298,7 @@ public: qCDebug(E2EE) << "Encrypted event is not for the current device"; return {}; } - const auto decrypted = encryptionManager->sessionDecryptMessage( + const auto decrypted = sessionDecryptMessage( personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" @@ -443,6 +524,7 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #endif // Quotient_E2EE_ENABLED + database->clear(); }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -493,13 +575,15 @@ void Connection::Private::completeSetup(const QString& mxId) picklingMode = Encrypted { job.binaryData() }; } + database = new Database(data->userId(), q); + // init olmAccount olmAccount = std::make_unique(data->userId(), data->deviceId(), q); connect(olmAccount.get(), &QOlmAccount::needsSave, q, &Connection::saveOlmAccount); - database = new Database(data->userId(), q); - - encryptionManager = new EncryptionManager(q); +#ifdef Quotient_E2EE_ENABLED + loadSessions(); +#endif if (database->accountPickle().isEmpty()) { // create new account and save unpickle data @@ -2019,18 +2103,6 @@ void Connection::saveOlmAccount() #endif } -QString Connection::e2eeDataDir() const -{ - auto safeUserId = userId(); - safeUserId.replace(':', '_'); - const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) % '/' - % safeUserId % '/'; - QDir dir; - if (!dir.exists(path)) - dir.mkpath(path); - return path; -} - #ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { diff --git a/lib/connection.h b/lib/connection.h index 93ee496e..8dec2a0c 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -401,8 +401,6 @@ public: bool lazyLoading() const; void setLazyLoading(bool newValue); - QString e2eeDataDir() const; - /*! Start a pre-created job object on this connection */ Q_INVOKABLE BaseJob* run(BaseJob* job, RunningPolicy runningPolicy = ForegroundRequest); diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 9bc80eef..2e9cc716 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -4,6 +4,8 @@ #include "e2ee/qolminboundsession.h" #include +#include + using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp deleted file mode 100644 index abdcdcee..00000000 --- a/lib/encryptionmanager.cpp +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "encryptionmanager.h" - -#include "connection.h" -#include "events/encryptedfile.h" -#include "database.h" - -#include "csapi/keys.h" - -#include -#include -#include - -#include "e2ee/e2ee.h" -#include "e2ee/qolmaccount.h" -#include "e2ee/qolmsession.h" -#include "e2ee/qolmmessage.h" -#include "e2ee/qolmerrors.h" -#include "e2ee/qolmutils.h" -#include -#include - -#include - -using namespace Quotient; -using std::move; - -class EncryptionManager::Private { -public: - EncryptionManager* q; - - Connection* connection; - - // A map from SenderKey to vector of InboundSession - UnorderedMap> sessions; - - void loadSessions() { - sessions = connection->database()->loadOlmSessions(connection->picklingMode()); - } - void saveSession(QOlmSessionPtr& session, const QString &senderKey) { - auto pickleResult = session->pickle(connection->picklingMode()); - if (std::holds_alternative(pickleResult)) { - qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); - return; - } - connection->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult)); - } - QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) - { - Q_ASSERT(message.type() == QOlmMessage::PreKey); - for(auto& session : sessions[senderKey]) { - const auto matches = session->matchesInboundSessionFrom(senderKey, message); - if(std::holds_alternative(matches) && std::get(matches)) { - qCDebug(E2EE) << "Found inbound session"; - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - return std::get(result); - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message"; - return {}; - } - } - } - qCDebug(E2EE) << "Creating new inbound session"; - auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); - if(std::holds_alternative(newSessionResult)) { - qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); - return {}; - } - auto newSession = std::move(std::get(newSessionResult)); - auto error = olmAccount->removeOneTimeKeys(newSession); - if (error) { - qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); - } - const auto result = newSession->decrypt(message); - saveSession(newSession, senderKey); - sessions[senderKey].push_back(std::move(newSession)); - if(std::holds_alternative(result)) { - return std::get(result); - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; - return {}; - } - } - QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) - { - Q_ASSERT(message.type() == QOlmMessage::General); - for(auto& session : sessions[senderKey]) { - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - return std::get(result); - } - } - qCWarning(E2EE) << "Failed to decrypt message"; - return {}; - } -}; - -EncryptionManager::EncryptionManager(QObject* parent) - : QObject(parent) - , d(std::make_unique()) -{ - d->q = this; - d->connection = static_cast(parent); - d->loadSessions(); -} - -EncryptionManager::~EncryptionManager() = default; - -QString EncryptionManager::sessionDecryptMessage( - const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) -{ - QString decrypted; - int type = personalCipherObject.value(TypeKeyL).toInt(-1); - QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); - if (type == 0) { - QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); - decrypted = d->sessionDecryptPrekey(preKeyMessage, senderKey, account); - } else if (type == 1) { - QOlmMessage message(body, QOlmMessage::General); - decrypted = d->sessionDecryptGeneral(message, senderKey); - } - return decrypted; -} - -QByteArray EncryptionManager::decryptFile(const QByteArray &ciphertext, EncryptedFile* file) -{ - const auto key = QByteArray::fromBase64(file->key.k.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); - const auto iv = QByteArray::fromBase64(file->iv.toLatin1()); - const auto sha256 = QByteArray::fromBase64(file->hashes["sha256"].toLatin1()); - if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return QByteArray(); - } - QByteArray plaintext(ciphertext.size(), 0); - EVP_CIPHER_CTX *ctx; - int length; - ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)key.data(), (const unsigned char *)iv.data()); - EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); - EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); - EVP_CIPHER_CTX_free(ctx); - return plaintext; -} -#endif // Quotient_E2EE_ENABLED diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h deleted file mode 100644 index 96569980..00000000 --- a/lib/encryptionmanager.h +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#pragma once - -#include - -#include -#include - -namespace Quotient { -class Connection; -class QOlmAccount; -struct EncryptedFile; - -class EncryptionManager : public QObject { - Q_OBJECT - -public: - explicit EncryptionManager(QObject* parent = nullptr); - ~EncryptionManager(); - QString sessionDecryptMessage(const QJsonObject& personalCipherObject, - const QByteArray& senderKey, std::unique_ptr& account); - static QByteArray decryptFile(const QByteArray &ciphertext, EncryptedFile* encryptedFile); - -private: - class Private; - std::unique_ptr d; -}; - -} // namespace Quotient -#endif // Quotient_E2EE_ENABLED diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp new file mode 100644 index 00000000..5ec344bb --- /dev/null +++ b/lib/events/encryptedfile.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "encryptedfile.h" + +using namespace Quotient; + +QByteArray EncryptedFile::decryptFile(const QByteArray &ciphertext) const +{ + QString _key = key.k; + _key = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); + const auto sha256 = QByteArray::fromBase64(hashes["sha256"].toLatin1()); + if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { + qCWarning(E2EE) << "Hash verification failed for file"; + return QByteArray(); + } + QByteArray plaintext(ciphertext.size(), 0); + EVP_CIPHER_CTX *ctx; + int length; + ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)_key.data(), (const unsigned char *)iv.toLatin1().data()); + EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); + EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext; +} diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 24ac9de1..f271d345 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -5,6 +5,10 @@ #pragma once #include "converters.h" +#include "logging.h" + +#include +#include namespace Quotient { /** @@ -44,6 +48,8 @@ public: QString iv; QHash hashes; QString v; + + QByteArray decryptFile(const QByteArray &ciphertext) const; }; template <> diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 2eea9d59..c5280770 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -9,7 +9,6 @@ #ifdef Quotient_E2EE_ENABLED # include -# include "encryptionmanager.h" # include "events/encryptedfile.h" #endif @@ -126,7 +125,7 @@ BaseJob::Status DownloadFileJob::prepareResult() QByteArray encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = EncryptionManager::decryptFile(encrypted, &file); + auto decrypted = file.decryptFile(encrypted); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -151,7 +150,7 @@ BaseJob::Status DownloadFileJob::prepareResult() auto encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = EncryptionManager::decryptFile(encrypted, &file); + auto decrypted = file.decryptFile(encrypted); d->tempFile->write(decrypted); } else { #endif diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index c7f27b0c..c666cce3 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -9,7 +9,6 @@ #include "room.h" #ifdef Quotient_E2EE_ENABLED -#include "encryptionmanager.h" #include "events/encryptedfile.h" #endif @@ -51,7 +50,7 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) } else { EncryptedFile file = *d->m_encryptedFile; auto buffer = new QBuffer(this); - buffer->setData(EncryptionManager::decryptFile(d->m_reply->readAll(), &file)); + buffer->setData(file.decryptFile(d->m_reply->readAll())); buffer->open(ReadOnly); d->m_device = buffer; } -- cgit v1.2.3 From 52a787eefb3fb3d147648d08fc439a4b8a966fd3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 16 Feb 2022 17:57:30 +0100 Subject: Add a few missing QUOTIENT_API stanzas Also, removed Q_GADGET macros from key verification events as those don't seem to do anything (no Q_ENUM/Q_FLAG things, namely). --- lib/database.h | 3 +-- lib/e2ee/e2ee.h | 2 +- lib/e2ee/qolmaccount.h | 14 +++++++------- lib/e2ee/qolmerrors.h | 4 +++- lib/e2ee/qolminboundsession.h | 2 +- lib/e2ee/qolmmessage.h | 4 +++- lib/e2ee/qolmoutboundsession.h | 2 +- lib/e2ee/qolmsession.h | 2 +- lib/e2ee/qolmutility.h | 2 +- lib/e2ee/qolmutils.h | 4 ++-- lib/events/encryptedfile.h | 2 +- lib/events/keyverificationevent.h | 16 +++++----------- 12 files changed, 27 insertions(+), 30 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/database.h b/lib/database.h index 96256a55..d244dc0b 100644 --- a/lib/database.h +++ b/lib/database.h @@ -10,10 +10,9 @@ #include "e2ee/e2ee.h" namespace Quotient { -class Database : public QObject +class QUOTIENT_API Database : public QObject { Q_OBJECT - public: Database(const QString& matrixId, QObject* parent); diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 41cd2878..4c825376 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -65,7 +65,7 @@ struct IdentityKeys }; //! Struct representing the one-time keys. -struct OneTimeKeys +struct QUOTIENT_API OneTimeKeys { QMap> keys; diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 00afc0e6..17aca8aa 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -25,7 +25,7 @@ using QOlmSessionPtr = std::unique_ptr; //! \code{.cpp} //! const auto olmAccount = new QOlmAccount(this); //! \endcode -class QOlmAccount : public QObject +class QUOTIENT_API QOlmAccount : public QObject { Q_OBJECT public: @@ -111,13 +111,13 @@ private: QString m_deviceId; }; -bool verifyIdentitySignature(const DeviceKeys &deviceKeys, - const QString &deviceId, - const QString &userId); +QUOTIENT_API bool verifyIdentitySignature(const DeviceKeys& deviceKeys, + const QString& deviceId, + const QString& userId); //! checks if the signature is signed by the signing_key -bool ed25519VerifySignature(const QString &signingKey, - const QJsonObject &obj, - const QString &signature); +QUOTIENT_API bool ed25519VerifySignature(const QString& signingKey, + const QJsonObject& obj, + const QString& signature); } // namespace Quotient diff --git a/lib/e2ee/qolmerrors.h b/lib/e2ee/qolmerrors.h index 24e87d95..20e61c12 100644 --- a/lib/e2ee/qolmerrors.h +++ b/lib/e2ee/qolmerrors.h @@ -4,6 +4,8 @@ #pragma once +#include "quotient_export.h" + namespace Quotient { //! All errors that could be caused by an operation regarding Olm //! Errors are named exactly like the ones in libolm. @@ -21,6 +23,6 @@ enum QOlmError Unknown, }; -QOlmError fromString(const char* error_raw); +QUOTIENT_API QOlmError fromString(const char* error_raw); } //namespace Quotient diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 7d52991c..1f5dadd3 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -15,7 +15,7 @@ namespace Quotient { //! An in-bound group session is responsible for decrypting incoming //! communication in a Megolm session. -struct QOlmInboundGroupSession +class QUOTIENT_API QOlmInboundGroupSession { public: ~QOlmInboundGroupSession(); diff --git a/lib/e2ee/qolmmessage.h b/lib/e2ee/qolmmessage.h index 52aba78c..557c02b1 100644 --- a/lib/e2ee/qolmmessage.h +++ b/lib/e2ee/qolmmessage.h @@ -4,6 +4,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -16,7 +18,7 @@ namespace Quotient { * * The class provides functions to get a type and the ciphertext. */ -class QOlmMessage : public QByteArray { +class QUOTIENT_API QOlmMessage : public QByteArray { Q_GADGET public: enum Type { diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 39263c77..0122bbfd 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -13,7 +13,7 @@ namespace Quotient { //! An out-bound group session is responsible for encrypting outgoing //! communication in a Megolm session. -class QOlmOutboundGroupSession +class QUOTIENT_API QOlmOutboundGroupSession { public: ~QOlmOutboundGroupSession(); diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index 1febfa0f..889a606d 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -18,7 +18,7 @@ class QOlmSession; //! Either an outbound or inbound session for secure communication. -class QOlmSession +class QUOTIENT_API QOlmSession { public: ~QOlmSession(); diff --git a/lib/e2ee/qolmutility.h b/lib/e2ee/qolmutility.h index b360d625..b2e79e29 100644 --- a/lib/e2ee/qolmutility.h +++ b/lib/e2ee/qolmutility.h @@ -17,7 +17,7 @@ class Connection; //! Allows you to make use of crytographic hashing via SHA-2 and //! verifying ed25519 signatures. -class QOlmUtility +class QUOTIENT_API QOlmUtility { public: QOlmUtility(); diff --git a/lib/e2ee/qolmutils.h b/lib/e2ee/qolmutils.h index bbd71332..f218e628 100644 --- a/lib/e2ee/qolmutils.h +++ b/lib/e2ee/qolmutils.h @@ -10,6 +10,6 @@ namespace Quotient { // Convert PicklingMode to key -QByteArray toKey(const PicklingMode &mode); -QByteArray getRandom(size_t bufferSize); +QUOTIENT_API QByteArray toKey(const PicklingMode &mode); +QUOTIENT_API QByteArray getRandom(size_t bufferSize); } diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 6199be8e..43bafc49 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -29,7 +29,7 @@ public: bool ext; }; -struct EncryptedFile +struct QUOTIENT_API EncryptedFile { Q_GADGET Q_PROPERTY(QUrl url MEMBER url CONSTANT) diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h index 13e7dcdd..497e56a2 100644 --- a/lib/events/keyverificationevent.h +++ b/lib/events/keyverificationevent.h @@ -7,8 +7,7 @@ namespace Quotient { /// Requests a key verification with another user's devices. /// Typically sent as a to-device event. -class KeyVerificationRequestEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationRequestEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.request", KeyVerificationRequestEvent) @@ -33,8 +32,7 @@ public: REGISTER_EVENT_TYPE(KeyVerificationRequestEvent) /// Begins a key verification process. -class KeyVerificationStartEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationStartEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.start", KeyVerificationStartEvent) @@ -76,8 +74,7 @@ REGISTER_EVENT_TYPE(KeyVerificationStartEvent) /// Accepts a previously sent m.key.verification.start message. /// Typically sent as a to-device event. -class KeyVerificationAcceptEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationAcceptEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.accept", KeyVerificationAcceptEvent) @@ -111,8 +108,7 @@ public: }; REGISTER_EVENT_TYPE(KeyVerificationAcceptEvent) -class KeyVerificationCancelEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationCancelEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.cancel", KeyVerificationCancelEvent) @@ -133,7 +129,6 @@ REGISTER_EVENT_TYPE(KeyVerificationCancelEvent) /// Sends the ephemeral public key for a device to the partner device. /// Typically sent as a to-device event. class KeyVerificationKeyEvent : public Event { - Q_GADGET public: DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent) @@ -148,8 +143,7 @@ public: REGISTER_EVENT_TYPE(KeyVerificationKeyEvent) /// Sends the MAC of a device's key to the partner device. -class KeyVerificationMacEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationMacEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.mac", KeyVerificationMacEvent) -- cgit v1.2.3 From 53dfa70601b2d27a6be12d52e86af123d0b26b79 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 15 Feb 2022 20:51:32 +0100 Subject: Cleanup A note on switching to QLatin1String for JSON key constants - this is more concise and barely affects (if at all) runtime performance (padding each QChar with zeros is trivial for assignment; and comparison can be done directly with the same performance as for two QStrings). --- lib/connection.cpp | 22 +++++++----- lib/e2ee/e2ee.h | 76 ++++++++++++++++------------------------ lib/e2ee/qolmaccount.cpp | 79 ++++++++++++++++-------------------------- lib/e2ee/qolminboundsession.h | 26 +++++++------- lib/e2ee/qolmmessage.cpp | 4 +-- lib/e2ee/qolmmessage.h | 3 +- lib/e2ee/qolmoutboundsession.h | 4 +-- lib/e2ee/qolmsession.h | 41 +++++++++++++++------- lib/e2ee/qolmutility.h | 3 -- lib/events/encryptedevent.cpp | 13 +++++++ lib/events/encryptedevent.h | 14 +++----- lib/syncdata.h | 2 +- 12 files changed, 138 insertions(+), 149 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 14188ace..3b8da6d1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -754,18 +754,20 @@ QJsonObject toJson(const DirectChatsMap& directChats) void Connection::onSyncSuccess(SyncData&& data, bool fromCache) { #ifdef Quotient_E2EE_ENABLED - if(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() && !d->isUploadingKeys) { + const auto oneTimeKeyCount = + static_cast(data.deviceOneTimeKeysCount()[SignedCurve25519Key]); + if (oneTimeKeyCount < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() + && !d->isUploadingKeys) { d->isUploadingKeys = true; - d->olmAccount->generateOneTimeKeys(d->olmAccount->maxNumberOfOneTimeKeys() / 2 - data.deviceOneTimeKeysCount()["signed_curve25519"]); + d->olmAccount->generateOneTimeKeys( + d->olmAccount->maxNumberOfOneTimeKeys() / 2 - oneTimeKeyCount); auto keys = d->olmAccount->oneTimeKeys(); auto job = d->olmAccount->createUploadKeyRequest(keys); run(job, ForegroundRequest); - connect(job, &BaseJob::success, this, [this](){ - d->olmAccount->markKeysAsPublished(); - }); - connect(job, &BaseJob::result, this, [this](){ - d->isUploadingKeys = false; - }); + connect(job, &BaseJob::success, this, + [this] { d->olmAccount->markKeysAsPublished(); }); + connect(job, &BaseJob::result, this, + [this] { d->isUploadingKeys = false; }); } static bool first = true; if(first) { @@ -1993,7 +1995,9 @@ void Connection::Private::loadOutdatedUserDevices() deviceKeys[user].clear(); for(const auto &device : keys) { if(device.userId != user) { - qCWarning(E2EE) << "mxId mismatch during device key verification:" << device.userId << user; + qCWarning(E2EE) + << "mxId mismatch during device key verification:" + << device.userId << user; continue; } if(!device.algorithms.contains("m.olm.v1.curve25519-aes-sha2") || !device.algorithms.contains("m.megolm.v1.aes-sha2")) { diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 4c825376..e21aa87b 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -5,43 +5,34 @@ #pragma once -#include -#include #include "converters.h" +#include "quotient_common.h" + +#include #include -#include -#include -#include -#include +namespace Quotient { -#include "util.h" +constexpr auto CiphertextKeyL = "ciphertext"_ls; +constexpr auto SenderKeyKeyL = "sender_key"_ls; +constexpr auto DeviceIdKeyL = "device_id"_ls; +constexpr auto SessionIdKeyL = "session_id"_ls; -namespace Quotient { +constexpr auto AlgorithmKeyL = "algorithm"_ls; +constexpr auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; +constexpr auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls; + +constexpr auto AlgorithmKey = "algorithm"_ls; +constexpr auto RotationPeriodMsKey = "rotation_period_ms"_ls; +constexpr auto RotationPeriodMsgsKey = "rotation_period_msgs"_ls; + +constexpr auto Ed25519Key = "ed25519"_ls; +constexpr auto Curve25519Key = "curve25519"_ls; +constexpr auto SignedCurve25519Key = "signed_curve25519"_ls; + +constexpr auto OlmV1Curve25519AesSha2AlgoKey = "m.olm.v1.curve25519-aes-sha2"_ls; +constexpr auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls; -inline const auto CiphertextKeyL = "ciphertext"_ls; -inline const auto SenderKeyKeyL = "sender_key"_ls; -inline const auto DeviceIdKeyL = "device_id"_ls; -inline const auto SessionIdKeyL = "session_id"_ls; - -inline const auto AlgorithmKeyL = "algorithm"_ls; -inline const auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; -inline const auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls; - -inline const auto AlgorithmKey = QStringLiteral("algorithm"); -inline const auto RotationPeriodMsKey = QStringLiteral("rotation_period_ms"); -inline const auto RotationPeriodMsgsKey = - QStringLiteral("rotation_period_msgs"); - -inline const auto Ed25519Key = QStringLiteral("ed25519"); -inline const auto Curve25519Key = QStringLiteral("curve25519"); -inline const auto SignedCurve25519Key = QStringLiteral("signed_curve25519"); -inline const auto OlmV1Curve25519AesSha2AlgoKey = - QStringLiteral("m.olm.v1.curve25519-aes-sha2"); -inline const auto MegolmV1AesSha2AlgoKey = - QStringLiteral("m.megolm.v1.aes-sha2"); -inline const QStringList SupportedAlgorithms = { OlmV1Curve25519AesSha2AlgoKey, - MegolmV1AesSha2AlgoKey }; struct Unencrypted {}; struct Encrypted { QByteArray key; @@ -55,9 +46,6 @@ using QOlmSessionPtr = std::unique_ptr; class QOlmInboundGroupSession; using QOlmInboundGroupSessionPtr = std::unique_ptr; -template struct overloaded : Ts... { using Ts::operator()...; }; -template overloaded(Ts...) -> overloaded; - struct IdentityKeys { QByteArray curve25519; @@ -73,16 +61,13 @@ struct QUOTIENT_API OneTimeKeys QMap curve25519() const; //! Get a reference to the hashmap corresponding to given key type. - std::optional> get(QString keyType) const; +// std::optional> get(QString keyType) const; }; //! Struct representing the signed one-time keys. class SignedOneTimeKey { public: - SignedOneTimeKey() = default; - SignedOneTimeKey(const SignedOneTimeKey &) = default; - SignedOneTimeKey &operator=(const SignedOneTimeKey &) = default; //! Required. The unpadded Base64-encoded 32-byte Curve25519 public key. QString key; @@ -94,8 +79,7 @@ public: template <> struct JsonObjectConverter { - static void fillFrom(const QJsonObject& jo, - SignedOneTimeKey& result) + static void fillFrom(const QJsonObject& jo, SignedOneTimeKey& result) { fromJson(jo.value("key"_ls), result.key); fromJson(jo.value("signatures"_ls), result.signatures); @@ -108,24 +92,22 @@ struct JsonObjectConverter { } }; -bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); - template class asKeyValueRange { public: - asKeyValueRange(T &data) - : m_data{data} - { - } + asKeyValueRange(T& data) + : m_data { data } + {} auto begin() { return m_data.keyValueBegin(); } - auto end() { return m_data.keyValueEnd(); } private: T &m_data; }; +template +asKeyValueRange(T&) -> asKeyValueRange; } // namespace Quotient diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 34ee7ea0..9cbb14f5 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -3,53 +3,41 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "qolmaccount.h" + #include "connection.h" -#include "csapi/keys.h" -#include "e2ee/qolmutils.h" #include "e2ee/qolmutility.h" -#include -#include -#include -#include +#include "e2ee/qolmutils.h" + +#include "csapi/keys.h" + +#include using namespace Quotient; QMap OneTimeKeys::curve25519() const { - return keys[QStringLiteral("curve25519")]; + return keys[Curve25519Key]; } -std::optional> OneTimeKeys::get(QString keyType) const -{ - if (!keys.contains(keyType)) { - return std::nullopt; - } - return keys[keyType]; -} - -bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) -{ - return lhs.curve25519 == rhs.curve25519 && lhs.ed25519 == rhs.ed25519; -} +//std::optional> OneTimeKeys::get(QString keyType) const +//{ +// if (!keys.contains(keyType)) { +// return std::nullopt; +// } +// return keys[keyType]; +//} // Convert olm error to enum QOlmError lastError(OlmAccount *account) { return fromString(olm_account_last_error(account)); } -QByteArray getRandom(size_t bufferSize) -{ - QByteArray buffer(bufferSize, '0'); - std::generate(buffer.begin(), buffer.end(), std::rand); - return buffer; -} - -QOlmAccount::QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent) +QOlmAccount::QOlmAccount(const QString& userId, const QString& deviceId, + QObject* parent) : QObject(parent) , m_userId(userId) , m_deviceId(deviceId) -{ -} +{} QOlmAccount::~QOlmAccount() { @@ -66,7 +54,7 @@ void QOlmAccount::createNewAccount() if (error == olm_error()) { throw lastError(m_account); } - Q_EMIT needsSave(); + emit needsSave(); } void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) @@ -161,7 +149,7 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const if (error == olm_error()) { throw lastError(m_account); } - Q_EMIT needsSave(); + emit needsSave(); return error; } @@ -220,14 +208,11 @@ std::optional QOlmAccount::removeOneTimeKeys(const QOlmSessionPtr &se if (error == olm_error()) { return lastError(m_account); } - Q_EMIT needsSave(); + emit needsSave(); return std::nullopt; } -OlmAccount *QOlmAccount::data() -{ - return m_account; -} +OlmAccount* QOlmAccount::data() { return m_account; } DeviceKeys QOlmAccount::deviceKeys() const { @@ -284,31 +269,27 @@ std::variant QOlmAccount::createOutboundSession(const void QOlmAccount::markKeysAsPublished() { olm_account_mark_keys_as_published(m_account); - Q_EMIT needsSave(); + emit needsSave(); } -bool Quotient::verifyIdentitySignature(const DeviceKeys &deviceKeys, - const QString &deviceId, - const QString &userId) +bool Quotient::verifyIdentitySignature(const DeviceKeys& deviceKeys, + const QString& deviceId, + const QString& userId) { const auto signKeyId = "ed25519:" + deviceId; const auto signingKey = deviceKeys.keys[signKeyId]; const auto signature = deviceKeys.signatures[userId][signKeyId]; - if (signature.isEmpty()) { - return false; - } - return ed25519VerifySignature(signingKey, toJson(deviceKeys), signature); } -bool Quotient::ed25519VerifySignature(const QString &signingKey, - const QJsonObject &obj, - const QString &signature) +bool Quotient::ed25519VerifySignature(const QString& signingKey, + const QJsonObject& obj, + const QString& signature) { - if (signature.isEmpty()) { + if (signature.isEmpty()) return false; - } + QJsonObject obj1 = obj; obj1.remove("unsigned"); diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 1f5dadd3..437f753d 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -4,12 +4,12 @@ #pragma once -#include -#include -#include -#include "olm/olm.h" -#include "e2ee/qolmerrors.h" #include "e2ee/e2ee.h" +#include "e2ee/qolmerrors.h" +#include "olm/olm.h" + +#include +#include namespace Quotient { @@ -20,16 +20,18 @@ class QUOTIENT_API QOlmInboundGroupSession public: ~QOlmInboundGroupSession(); //! Creates a new instance of `OlmInboundGroupSession`. - static std::unique_ptr create(const QByteArray &key); + static std::unique_ptr create(const QByteArray& key); //! Import an inbound group session, from a previous export. - static std::unique_ptr import(const QByteArray &key); + static std::unique_ptr import(const QByteArray& key); //! Serialises an `OlmInboundGroupSession` to encrypted Base64. QByteArray pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. - static std::variant, QOlmError> unpickle(const QByteArray &picked, const PicklingMode &mode); + static std::variant, QOlmError> + unpickle(const QByteArray& picked, const PicklingMode& mode); //! Decrypts ciphertext received for this group session. - std::variant, QOlmError> decrypt(const QByteArray &message); + std::variant, QOlmError> decrypt( + const QByteArray& message); //! Export the base64-encoded ratchet key for this session, at the given index, //! in a format which can be used by import. std::variant exportSession(uint32_t messageIndex); @@ -38,11 +40,11 @@ public: //! Get a base64-encoded identifier for this session. QByteArray sessionId() const; bool isVerified() const; - QOlmInboundGroupSession(OlmInboundGroupSession *session); + + QOlmInboundGroupSession(OlmInboundGroupSession* session); private: - OlmInboundGroupSession *m_groupSession; + OlmInboundGroupSession* m_groupSession; }; using QOlmInboundGroupSessionPtr = std::unique_ptr; -using OlmInboundGroupSessionPtr = std::unique_ptr; } // namespace Quotient diff --git a/lib/e2ee/qolmmessage.cpp b/lib/e2ee/qolmmessage.cpp index 15008b75..81b166b0 100644 --- a/lib/e2ee/qolmmessage.cpp +++ b/lib/e2ee/qolmmessage.cpp @@ -6,11 +6,11 @@ using namespace Quotient; -QOlmMessage::QOlmMessage(const QByteArray &ciphertext, QOlmMessage::Type type) +QOlmMessage::QOlmMessage(QByteArray ciphertext, QOlmMessage::Type type) : QByteArray(std::move(ciphertext)) , m_messageType(type) { - Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); + Q_ASSERT_X(!isEmpty(), "olm message", "Ciphertext is empty"); } QOlmMessage::QOlmMessage(const QOlmMessage &message) diff --git a/lib/e2ee/qolmmessage.h b/lib/e2ee/qolmmessage.h index 557c02b1..5d5db636 100644 --- a/lib/e2ee/qolmmessage.h +++ b/lib/e2ee/qolmmessage.h @@ -28,8 +28,9 @@ public: Q_ENUM(Type) QOlmMessage() = default; - explicit QOlmMessage(const QByteArray &ciphertext, Type type = General); + explicit QOlmMessage(QByteArray ciphertext, Type type = General); explicit QOlmMessage(const QOlmMessage &message); + ~QOlmMessage() = default; static QOlmMessage fromCiphertext(const QByteArray &ciphertext); diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 0122bbfd..32ba2b3b 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -24,7 +24,8 @@ public: std::variant pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. - static std::variant, QOlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); + static std::variant, QOlmError> + unpickle(QByteArray& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. std::variant encrypt(const QString &plaintext); @@ -48,5 +49,4 @@ private: }; using QOlmOutboundGroupSessionPtr = std::unique_ptr; -using OlmOutboundGroupSessionPtr = std::unique_ptr; } diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index 889a606d..f20c9837 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -16,20 +16,31 @@ namespace Quotient { class QOlmAccount; class QOlmSession; - //! Either an outbound or inbound session for secure communication. class QUOTIENT_API QOlmSession { public: ~QOlmSession(); //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. - static std::variant, QOlmError> createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage); - static std::variant, QOlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage); - static std::variant, QOlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey); + static std::variant, QOlmError> + createInboundSession(QOlmAccount* account, const QOlmMessage& preKeyMessage); + + static std::variant, QOlmError> + createInboundSessionFrom(QOlmAccount* account, + const QString& theirIdentityKey, + const QOlmMessage& preKeyMessage); + + static std::variant, QOlmError> + createOutboundSession(QOlmAccount* account, const QString& theirIdentityKey, + const QString& theirOneTimeKey); + //! Serialises an `QOlmSession` to encrypted Base64. std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static std::variant, QOlmError> unpickle(const QByteArray &pickled, const PicklingMode &mode); + static std::variant, QOlmError> unpickle( + const QByteArray& pickled, const PicklingMode& mode); + //! Encrypts a plaintext message using the session. QOlmMessage encrypt(const QString &plaintext); @@ -48,29 +59,33 @@ public: bool hasReceivedMessage() const; //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSession(const QOlmMessage &preKeyMessage) const; + std::variant matchesInboundSession( + const QOlmMessage& preKeyMessage) const; //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSessionFrom(const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) const; + std::variant matchesInboundSessionFrom( + const QString& theirIdentityKey, const QOlmMessage& preKeyMessage) const; friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) { return lhs.sessionId() < rhs.sessionId(); } - friend bool operator<(const std::unique_ptr &lhs, const std::unique_ptr &rhs) { + friend bool operator<(const std::unique_ptr& lhs, + const std::unique_ptr& rhs) + { return *lhs < *rhs; } - OlmSession *raw() const - { - return m_session; - } + OlmSession* raw() const { return m_session; } + QOlmSession(OlmSession* session); private: //! Helper function for creating new sessions and handling errors. static OlmSession* create(); - static std::variant, QOlmError> createInbound(QOlmAccount *account, const QOlmMessage& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); + static std::variant, QOlmError> createInbound( + QOlmAccount* account, const QOlmMessage& preKeyMessage, + bool from = false, const QString& theirIdentityKey = ""); OlmSession* m_session; }; } //namespace Quotient diff --git a/lib/e2ee/qolmutility.h b/lib/e2ee/qolmutility.h index b2e79e29..a12af49a 100644 --- a/lib/e2ee/qolmutility.h +++ b/lib/e2ee/qolmutility.h @@ -4,7 +4,6 @@ #pragma once -#include #include #include "e2ee/qolmerrors.h" @@ -13,7 +12,6 @@ struct OlmUtility; namespace Quotient { class QOlmSession; -class Connection; //! Allows you to make use of crytographic hashing via SHA-2 and //! verifying ed25519 signatures. @@ -37,7 +35,6 @@ public: std::variant ed25519Verify(const QByteArray &key, const QByteArray &message, const QByteArray &signature); - private: OlmUtility *m_utility; diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 1b5e4441..ba4dd154 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -33,6 +33,19 @@ EncryptedEvent::EncryptedEvent(const QJsonObject& obj) qCDebug(E2EE) << "Encrypted event from" << senderId(); } +QString EncryptedEvent::algorithm() const +{ + auto algo = contentPart(AlgorithmKeyL); + static constexpr auto SupportedAlgorithms = + make_array(OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey); + if (std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), + algo) == SupportedAlgorithms.cend()) { + qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo + << "is not supported"; + } + return algo; +} + RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const { auto eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index c838bbd8..72efffd4 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -39,22 +39,16 @@ public: const QString& deviceId, const QString& sessionId); explicit EncryptedEvent(const QJsonObject& obj); - QString algorithm() const - { - QString algo = contentPart(AlgorithmKeyL); - if (!SupportedAlgorithms.contains(algo)) { - qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo - << "is not supported"; - } - return algo; - } + QString algorithm() const; QByteArray ciphertext() const { return contentPart(CiphertextKeyL).toLatin1(); } QJsonObject ciphertext(const QString& identityKey) const { - return contentPart(CiphertextKeyL).value(identityKey).toObject(); + return contentPart(CiphertextKeyL) + .value(identityKey) + .toObject(); } QString senderKey() const { return contentPart(SenderKeyKeyL); } diff --git a/lib/syncdata.h b/lib/syncdata.h index 633f4b52..6b70140d 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -54,7 +54,7 @@ struct DevicesList { QStringList left; }; -QDebug operator<<(QDebug dhg, const DevicesList &devicesList); +QDebug operator<<(QDebug dhg, const DevicesList& devicesList); template <> struct JsonObjectConverter { -- cgit v1.2.3 From 2178ca994c4b33197239155f7f6715e0451b9172 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 14 Feb 2022 14:06:14 +0100 Subject: Use QHash instead of QMap We don't seem to need sorted associative containers in those cases. --- lib/e2ee/e2ee.h | 4 ++-- lib/e2ee/qolmaccount.cpp | 16 ++++------------ lib/e2ee/qolmaccount.h | 2 +- 3 files changed, 7 insertions(+), 15 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index e21aa87b..361c48ff 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -55,10 +55,10 @@ struct IdentityKeys //! Struct representing the one-time keys. struct QUOTIENT_API OneTimeKeys { - QMap> keys; + QHash> keys; //! Get the HashMap containing the curve25519 one-time keys. - QMap curve25519() const; + QHash curve25519() const; //! Get a reference to the hashmap corresponding to given key type. // std::optional> get(QString keyType) const; diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 9cbb14f5..476a60bd 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -14,7 +14,7 @@ using namespace Quotient; -QMap OneTimeKeys::curve25519() const +QHash OneTimeKeys::curve25519() const { return keys[Curve25519Key]; } @@ -164,21 +164,13 @@ OneTimeKeys QOlmAccount::oneTimeKeys() const } const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); OneTimeKeys oneTimeKeys; - - for (const QString& key1 : json.keys()) { - auto oneTimeKeyObject = json[key1].toObject(); - auto keyMap = QMap(); - for (const QString &key2 : oneTimeKeyObject.keys()) { - keyMap[key2] = oneTimeKeyObject[key2].toString(); - } - oneTimeKeys.keys[key1] = keyMap; - } + fromJson(json, oneTimeKeys.keys); return oneTimeKeys; } -QMap QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const +QHash QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const { - QMap signedOneTimeKeys; + QHash signedOneTimeKeys; for (const auto &keyid : keys.curve25519().keys()) { const auto oneTimeKey = keys.curve25519()[keyid]; QByteArray sign = signOneTimeKey(oneTimeKey); diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 17aca8aa..17f43f1a 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -67,7 +67,7 @@ public: OneTimeKeys oneTimeKeys() const; //! Sign all one time keys. - QMap signOneTimeKeys(const OneTimeKeys &keys) const; + QHash signOneTimeKeys(const OneTimeKeys &keys) const; //! Sign one time key. QByteArray signOneTimeKey(const QString &key) const; -- cgit v1.2.3 From 0a43c023b94e12b3130572f2dd0d6ac8bb4ed110 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 14 Feb 2022 15:25:24 +0100 Subject: isSupportedAlgorithm() That's a better primitive than just exposing SupportedAlgorithms list. --- lib/connection.cpp | 7 +++++-- lib/e2ee/e2ee.h | 9 +++++++++ lib/events/encryptedevent.cpp | 9 +++------ 3 files changed, 17 insertions(+), 8 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 0562d416..87fc8e2c 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2000,8 +2000,11 @@ void Connection::Private::loadOutdatedUserDevices() << device.userId << user; continue; } - if(!device.algorithms.contains("m.olm.v1.curve25519-aes-sha2") || !device.algorithms.contains("m.megolm.v1.aes-sha2")) { - qCWarning(E2EE) << "Unsupported encryption algorithms found" << device.algorithms; + if (!std::all_of(device.algorithms.cbegin(), + device.algorithms.cend(), + isSupportedAlgorithm)) { + qCWarning(E2EE) << "Unsupported encryption algorithms found" + << device.algorithms; continue; } if(!verifyIdentitySignature(device, device.deviceId, device.userId)) { diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 361c48ff..268cb525 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -33,6 +33,15 @@ constexpr auto SignedCurve25519Key = "signed_curve25519"_ls; constexpr auto OlmV1Curve25519AesSha2AlgoKey = "m.olm.v1.curve25519-aes-sha2"_ls; constexpr auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls; +inline bool isSupportedAlgorithm(const QString& algorithm) +{ + static constexpr auto SupportedAlgorithms = + make_array(OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey); + return std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), + algorithm) + != SupportedAlgorithms.cend(); +} + struct Unencrypted {}; struct Encrypted { QByteArray key; diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index ba4dd154..9d07a35f 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -35,14 +35,11 @@ EncryptedEvent::EncryptedEvent(const QJsonObject& obj) QString EncryptedEvent::algorithm() const { - auto algo = contentPart(AlgorithmKeyL); - static constexpr auto SupportedAlgorithms = - make_array(OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey); - if (std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), - algo) == SupportedAlgorithms.cend()) { + const auto algo = contentPart(AlgorithmKeyL); + if (!isSupportedAlgorithm(algo)) qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo << "is not supported"; - } + return algo; } -- cgit v1.2.3 From 29510632780f695eefc9d5107e223aa02caac2c6 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 26 Feb 2022 19:40:13 +0100 Subject: Use QOlmMessage::Type in more places Make sure that the enum values correspond to the values used in the spec and use them instead of magic constants --- lib/connection.cpp | 4 ++-- lib/e2ee/qolmmessage.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 66df1e43..b06c35c2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -274,10 +274,10 @@ public: QString decrypted; int type = personalCipherObject.value(TypeKeyL).toInt(-1); QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); - if (type == 0) { + if (type == QOlmMessage::PreKey) { QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); decrypted = sessionDecryptPrekey(preKeyMessage, senderKey, account); - } else if (type == 1) { + } else if (type == QOlmMessage::General) { QOlmMessage message(body, QOlmMessage::General); decrypted = sessionDecryptGeneral(message, senderKey); } diff --git a/lib/e2ee/qolmmessage.h b/lib/e2ee/qolmmessage.h index 5d5db636..b4285a93 100644 --- a/lib/e2ee/qolmmessage.h +++ b/lib/e2ee/qolmmessage.h @@ -22,8 +22,8 @@ class QUOTIENT_API QOlmMessage : public QByteArray { Q_GADGET public: enum Type { + PreKey = 0, General, - PreKey, }; Q_ENUM(Type) -- cgit v1.2.3 From fc2ffecd22d6008426c2305dbeb31fc2caab6163 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 27 Feb 2022 17:43:17 +0100 Subject: Return false instead of error for failed signature checks --- lib/e2ee/qolmutility.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index 303f6d75..5d1bc18d 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -49,8 +49,11 @@ std::variant QOlmUtility::ed25519Verify(const QByteArray &key, 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()) { + if (ret == olm_error()) { + auto error = lastError(m_utility); + if (error == QOlmError::BadMessageMac) { + return false; + } return lastError(m_utility); } -- cgit v1.2.3 From 0928bd405f4958d3a12277614d2715cfac1f7ac0 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sun, 27 Feb 2022 17:47:20 +0100 Subject: Update lib/e2ee/qolmutility.cpp Co-authored-by: Carl Schwan --- lib/e2ee/qolmutility.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index 5d1bc18d..9f09a37f 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -54,7 +54,7 @@ std::variant QOlmUtility::ed25519Verify(const QByteArray &key, if (error == QOlmError::BadMessageMac) { return false; } - return lastError(m_utility); + return error; } if (ret != 0) { -- cgit v1.2.3 From 2af8d83526ed7a24c18b185e1d64d97632e10f1e Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 9 Apr 2022 02:04:39 +0200 Subject: Prepare for MSC 3700 --- lib/connection.cpp | 42 +++++++++++++++++++++++----------------- lib/connection.h | 4 ++-- lib/database.cpp | 32 +++++++++++++++++++++++------- lib/database.h | 7 ++++--- lib/e2ee/qolminboundsession.cpp | 18 +++++++++++++++++ lib/e2ee/qolminboundsession.h | 11 +++++++++++ lib/room.cpp | 43 +++++++++++++++++++++++------------------ lib/room.h | 2 +- 8 files changed, 109 insertions(+), 50 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index d7460f08..0bdc0489 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -220,7 +220,7 @@ public: } q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult), QDateTime::currentDateTime()); } - QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) + std::pair sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) { Q_ASSERT(message.type() == QOlmMessage::PreKey); for(auto& session : olmSessions[senderKey]) { @@ -230,7 +230,7 @@ public: const auto result = session->decrypt(message); if(std::holds_alternative(result)) { q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); - return std::get(result); + return { std::get(result), session->sessionId() }; } else { qCDebug(E2EE) << "Failed to decrypt prekey message"; return {}; @@ -249,47 +249,53 @@ public: qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); } const auto result = newSession->decrypt(message); + QString sessionId = newSession->sessionId(); saveSession(newSession, senderKey); olmSessions[senderKey].push_back(std::move(newSession)); if(std::holds_alternative(result)) { - return std::get(result); + return { std::get(result), sessionId }; } else { qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; return {}; } } - QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) + std::pair 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)) { q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); - return std::get(result); + return { std::get(result), session->sessionId() }; } } qCWarning(E2EE) << "Failed to decrypt message"; return {}; } - QString sessionDecryptMessage( + std::pair sessionDecryptMessage( const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) { QString decrypted; + QString olmSessionId; int type = personalCipherObject.value(TypeKeyL).toInt(-1); QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); if (type == QOlmMessage::PreKey) { QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); - decrypted = sessionDecryptPrekey(preKeyMessage, senderKey, account); + auto result = sessionDecryptPrekey(preKeyMessage, senderKey, account); + decrypted = result.first; + olmSessionId = result.second; } else if (type == QOlmMessage::General) { QOlmMessage message(body, QOlmMessage::General); - decrypted = sessionDecryptGeneral(message, senderKey); + auto result = sessionDecryptGeneral(message, senderKey); + decrypted = result.first; + olmSessionId = result.second; } - return decrypted; + return { decrypted, olmSessionId }; } #endif - EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) + std::pair sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; @@ -305,7 +311,7 @@ public: qCDebug(E2EE) << "Encrypted event is not for the current device"; return {}; } - const auto decrypted = sessionDecryptMessage( + const auto [decrypted, olmSessionId] = sessionDecryptMessage( personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" @@ -356,7 +362,7 @@ public: return {}; } - return std::move(decryptedEvent); + return { std::move(decryptedEvent), olmSessionId }; #endif // Quotient_E2EE_ENABLED } #ifdef Quotient_E2EE_ENABLED @@ -956,16 +962,16 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& event) { - const auto decryptedEvent = sessionDecryptMessage(event); + const auto [decryptedEvent, olmSessionId] = sessionDecryptMessage(event); if(!decryptedEvent) { qCWarning(E2EE) << "Failed to decrypt event" << event.id(); return; } switchOnType(*decryptedEvent, - [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { + [this, senderKey = event.senderKey(), &event, olmSessionId](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { - detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); + detectedRoom->handleRoomKeyEvent(roomKeyEvent, event.senderId(), olmSessionId); } else { qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() << "is not found at the connection" << q->objectName(); @@ -2192,14 +2198,14 @@ Database* Connection::database() return d->database; } -UnorderedMap, QOlmInboundGroupSessionPtr> Connection::loadRoomMegolmSessions(Room* room) +UnorderedMap Connection::loadRoomMegolmSessions(Room* room) { return database()->loadMegolmSessions(room->id(), picklingMode()); } -void Connection::saveMegolmSession(Room* room, const QString& senderKey, QOlmInboundGroupSession* session, const QString& ed25519Key) +void Connection::saveMegolmSession(Room* room, QOlmInboundGroupSession* session) { - database()->saveMegolmSession(room->id(), senderKey, session->sessionId(), ed25519Key, session->pickle(picklingMode())); + database()->saveMegolmSession(room->id(), session->sessionId(), session->pickle(picklingMode()), session->senderId(), session->olmSessionId()); } QStringList Connection::devicesForUser(User* user) const diff --git a/lib/connection.h b/lib/connection.h index 9f23902b..29731593 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -317,8 +317,8 @@ public: #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; Database* database(); - UnorderedMap, QOlmInboundGroupSessionPtr> loadRoomMegolmSessions(Room* room); - void saveMegolmSession(Room* room, const QString& senderKey, QOlmInboundGroupSession* session, const QString& ed25519Key); + UnorderedMap loadRoomMegolmSessions(Room* room); + void saveMegolmSession(Room* room, QOlmInboundGroupSession* session); #endif // Quotient_E2EE_ENABLED Q_INVOKABLE Quotient::SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; diff --git a/lib/database.cpp b/lib/database.cpp index d719d027..3189b3f1 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -29,6 +29,7 @@ Database::Database(const QString& matrixId, const QString& deviceId, QObject* pa switch(version()) { case 0: migrateTo1(); case 1: migrateTo2(); + case 2: migrateTo3(); } } @@ -112,6 +113,20 @@ void Database::migrateTo2() commit(); } +void Database::migrateTo3() +{ + qCDebug(DATABASE) << "Migrating database to version 3"; + transaction(); + + execute(QStringLiteral("CREATE TABLE inbound_megolm_sessions_temp AS SELECT roomId, sessionId, pickle FROM inbound_megolm_sessions;")); + execute(QStringLiteral("DROP TABLE inbound_megolm_sessions;")); + execute(QStringLiteral("ALTER TABLE inbound_megolm_sessions_temp RENAME TO inbound_megolm_sessions;")); + execute(QStringLiteral("ALTER TABLE inbound_megolm_sessions ADD olmSessionId TEXT;")); + execute(QStringLiteral("ALTER TABLE inbound_megolm_sessions ADD senderId TEXT;")); + execute(QStringLiteral("PRAGMA user_version = 3;")); + commit(); +} + QByteArray Database::accountPickle() { auto query = prepareQuery(QStringLiteral("SELECT pickle FROM accounts;")); @@ -179,33 +194,36 @@ UnorderedMap> Database::loadOlmSessions(con return sessions; } -UnorderedMap, QOlmInboundGroupSessionPtr> Database::loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode) +UnorderedMap Database::loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode) { auto query = prepareQuery(QStringLiteral("SELECT * FROM inbound_megolm_sessions WHERE roomId=:roomId;")); query.bindValue(":roomId", roomId); transaction(); execute(query); commit(); - UnorderedMap, QOlmInboundGroupSessionPtr> sessions; + UnorderedMap 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)); + + sessions[query.value("sessionId").toString()] = std::move(std::get(session)); + sessions[query.value("sessionId").toString()]->setOlmSessionId(query.value("olmSessionId").toString()); + sessions[query.value("sessionId").toString()]->setSenderId(query.value("senderId").toString()); } return sessions; } -void Database::saveMegolmSession(const QString& roomId, const QString& senderKey, const QString& sessionId, const QString& ed25519Key, const QByteArray& pickle) +void Database::saveMegolmSession(const QString& roomId, const QString& sessionId, const QByteArray& pickle, const QString& senderId, const QString& olmSessionId) { - auto query = prepareQuery(QStringLiteral("INSERT INTO inbound_megolm_sessions(roomId, senderKey, sessionId, ed25519Key, pickle) VALUES(:roomId, :senderKey, :sessionId, :ed25519Key, :pickle);")); + auto query = prepareQuery(QStringLiteral("INSERT INTO inbound_megolm_sessions(roomId, sessionId, pickle, senderId, olmSessionId) VALUES(:roomId, :sessionId, :pickle, :senderId, :olmSessionId);")); query.bindValue(":roomId", roomId); - query.bindValue(":senderKey", senderKey); query.bindValue(":sessionId", sessionId); - query.bindValue(":ed25519Key", ed25519Key); query.bindValue(":pickle", pickle); + query.bindValue(":senderId", senderId); + query.bindValue(":olmSessionId", olmSessionId); transaction(); execute(query); commit(); diff --git a/lib/database.h b/lib/database.h index cf241dbc..08fe49f3 100644 --- a/lib/database.h +++ b/lib/database.h @@ -8,7 +8,6 @@ #include #include "e2ee/e2ee.h" - namespace Quotient { class QUOTIENT_API Database : public QObject { @@ -29,8 +28,8 @@ public: void clear(); void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray &pickle, const QDateTime& timestamp); UnorderedMap> loadOlmSessions(const PicklingMode& picklingMode); - UnorderedMap, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode); - void saveMegolmSession(const QString& roomId, const QString& senderKey, const QString& sessionKey, const QString& ed25519Key, const QByteArray& pickle); + UnorderedMap loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode); + void saveMegolmSession(const QString& roomId, const QString& sessionId, const QByteArray& pickle, const QString& senderId, const QString& olmSessionId); void addGroupSessionIndexRecord(const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts); std::pair groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index); void clearRoomData(const QString& roomId); @@ -39,6 +38,8 @@ public: private: void migrateTo1(); void migrateTo2(); + void migrateTo3(); + QString m_matrixId; }; } diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 2e9cc716..60d871ef 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -149,3 +149,21 @@ bool QOlmInboundGroupSession::isVerified() const { return olm_inbound_group_session_is_verified(m_groupSession) != 0; } + +QString QOlmInboundGroupSession::olmSessionId() const +{ + return m_olmSessionId; +} +void QOlmInboundGroupSession::setOlmSessionId(const QString& olmSessionId) +{ + m_olmSessionId = olmSessionId; +} + +QString QOlmInboundGroupSession::senderId() const +{ + return m_senderId; +} +void QOlmInboundGroupSession::setSenderId(const QString& senderId) +{ + m_senderId = senderId; +} diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 437f753d..32112b97 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -41,9 +41,20 @@ public: QByteArray sessionId() const; bool isVerified() const; + //! The olm session that this session was received from. + //! Required to get the device this session is from. + QString olmSessionId() const; + void setOlmSessionId(const QString& setOlmSessionId); + + //! The sender of this session. + QString senderId() const; + void setSenderId(const QString& senderId); + QOlmInboundGroupSession(OlmInboundGroupSession* session); private: OlmInboundGroupSession* m_groupSession; + QString m_olmSessionId; + QString m_senderId; }; using QOlmInboundGroupSessionPtr = std::unique_ptr; diff --git a/lib/room.cpp b/lib/room.cpp index 3ba6890a..041d88c8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -337,27 +337,25 @@ public: bool isLocalUser(const User* u) const { return u == q->localUser(); } #ifdef Quotient_E2EE_ENABLED - // A map from (senderKey, sessionId) to InboundGroupSession - UnorderedMap, QOlmInboundGroupSessionPtr> groupSessions; + UnorderedMap groupSessions; - bool addInboundGroupSession(QString senderKey, QString sessionId, - QString sessionKey, QString ed25519Key) + bool addInboundGroupSession(QString sessionId, QString sessionKey, const QString& senderId, const QString& olmSessionId) { - if (groupSessions.find({senderKey, sessionId}) != groupSessions.end()) { - qCWarning(E2EE) << "Inbound Megolm session" << sessionId - << "with senderKey" << senderKey << "already exists"; + if (groupSessions.find(sessionId) != groupSessions.end()) { + qCWarning(E2EE) << "Inbound Megolm session" << sessionId << "already exists"; return false; } auto megolmSession = QOlmInboundGroupSession::create(sessionKey.toLatin1()); if (megolmSession->sessionId() != sessionId) { - qCWarning(E2EE) << "Session ID mismatch in m.room_key event sent " - "from sender with key" << senderKey; + qCWarning(E2EE) << "Session ID mismatch in m.room_key event"; return false; } + megolmSession->setSenderId(senderId); + megolmSession->setOlmSessionId(olmSessionId); qCWarning(E2EE) << "Adding inbound session"; - connection->saveMegolmSession(q, senderKey, megolmSession.get(), ed25519Key); - groupSessions[{senderKey, sessionId}] = std::move(megolmSession); + connection->saveMegolmSession(q, megolmSession.get()); + groupSessions[sessionId] = std::move(megolmSession); return true; } @@ -365,9 +363,10 @@ public: const QString& senderKey, const QString& sessionId, const QString& eventId, - QDateTime timestamp) + QDateTime timestamp, + const QString& senderId) { - auto groupSessionIt = groupSessions.find({ senderKey, sessionId }); + auto groupSessionIt = groupSessions.find(sessionId); if (groupSessionIt == groupSessions.end()) { // qCWarning(E2EE) << "Unable to decrypt event" << eventId // << "The sender's device has not sent us the keys for " @@ -375,6 +374,10 @@ public: return QString(); } auto& senderSession = groupSessionIt->second; + if (senderSession->senderId() != senderId) { + qCWarning(E2EE) << "Sender from event does not match sender from session"; + return {}; + } auto decryptResult = senderSession->decrypt(cipher); if(std::holds_alternative(decryptResult)) { qCWarning(E2EE) << "Unable to decrypt event" << eventId @@ -1482,9 +1485,9 @@ RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) QString decrypted = d->groupSessionDecryptMessage( encryptedEvent.ciphertext(), encryptedEvent.senderKey(), encryptedEvent.sessionId(), encryptedEvent.id(), - encryptedEvent.originTimestamp()); + encryptedEvent.originTimestamp(), encryptedEvent.senderId()); if (decrypted.isEmpty()) { - // qCWarning(E2EE) << "Encrypted message is empty"; + qCWarning(E2EE) << "Encrypted message is empty"; return {}; } auto decryptedEvent = encryptedEvent.createDecrypted(decrypted); @@ -1497,19 +1500,21 @@ RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) } void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, - const QString& senderKey) + const QString& senderId, + const QString& olmSessionId) { #ifndef Quotient_E2EE_ENABLED Q_UNUSED(roomKeyEvent) - Q_UNUSED(senderKey) + Q_UNUSED(senderId) + Q_UNUSED(olmSessionId) qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED if (roomKeyEvent.algorithm() != MegolmV1AesSha2AlgoKey) { qCWarning(E2EE) << "Ignoring unsupported algorithm" << roomKeyEvent.algorithm() << "in m.room_key event"; } - if (d->addInboundGroupSession(senderKey, roomKeyEvent.sessionId(), - roomKeyEvent.sessionKey(), roomKeyEvent.fullJson()["keys"]["ed25519"].toString())) { + if (d->addInboundGroupSession(roomKeyEvent.sessionId(), + roomKeyEvent.sessionKey(), senderId, olmSessionId)) { qCWarning(E2EE) << "added new inboundGroupSession:" << d->groupSessions.size(); auto undecryptedEvents = d->undecryptedEvents[roomKeyEvent.sessionId()]; diff --git a/lib/room.h b/lib/room.h index 9f70d77a..6ba7feac 100644 --- a/lib/room.h +++ b/lib/room.h @@ -277,7 +277,7 @@ public: int timelineSize() const; bool usesEncryption() const; RoomEventPtr decryptMessage(const EncryptedEvent& encryptedEvent); - void handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, const QString& senderKey); + void handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, const QString& senderId, const QString& olmSessionId); int joinedCount() const; int invitedCount() const; int totalMemberCount() const; -- cgit v1.2.3 From 534b6f3fb3a4bd14d78942cab7eb430dd98e471c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 23 Apr 2022 19:46:58 +0200 Subject: SLICE() Add a macro to make slicing clear in the code and quiet for static analysis. --- lib/connection.cpp | 2 +- lib/e2ee/qolmmessage.cpp | 4 +++- lib/uri.cpp | 2 +- lib/util.h | 16 ++++++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index d99ab64d..a969b3b9 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2065,7 +2065,7 @@ void Connection::Private::loadOutdatedUserDevices() continue; } } - deviceKeys[user][device.deviceId] = device; + deviceKeys[user][device.deviceId] = SLICE(device, DeviceKeys); } outdatedUsers -= user; } diff --git a/lib/e2ee/qolmmessage.cpp b/lib/e2ee/qolmmessage.cpp index 81b166b0..f9b4a5c2 100644 --- a/lib/e2ee/qolmmessage.cpp +++ b/lib/e2ee/qolmmessage.cpp @@ -4,6 +4,8 @@ #include "qolmmessage.h" +#include "util.h" + using namespace Quotient; QOlmMessage::QOlmMessage(QByteArray ciphertext, QOlmMessage::Type type) @@ -26,7 +28,7 @@ QOlmMessage::Type QOlmMessage::type() const QByteArray QOlmMessage::toCiphertext() const { - return QByteArray(*this); + return SLICE(*this, QByteArray); } QOlmMessage QOlmMessage::fromCiphertext(const QByteArray &ciphertext) diff --git a/lib/uri.cpp b/lib/uri.cpp index 6b7d1d20..91751df0 100644 --- a/lib/uri.cpp +++ b/lib/uri.cpp @@ -171,7 +171,7 @@ QUrl Uri::toUrl(UriForm form) const return {}; if (form == CanonicalUri || type() == NonMatrix) - return *this; // NOLINT(cppcoreguidelines-slicing): It's intentional + return SLICE(*this, QUrl); QUrl url; url.setScheme("https"); diff --git a/lib/util.h b/lib/util.h index 753eb1ea..b14e1648 100644 --- a/lib/util.h +++ b/lib/util.h @@ -37,6 +37,22 @@ static_assert(false, "Use Q_DISABLE_MOVE instead; Quotient enables it across all QT_WARNING_POP #endif +/// \brief Copy an object with slicing +/// +/// Unintended slicing is bad, which why there's a C++ Core Guideline that +/// basically says "don't slice, or if you do, make it explicit". Sonar and +/// clang-tidy have warnings matching this guideline; unfortunately, those +/// warnings trigger even when you have a dedicated method (as the guideline +/// recommends) that makes a slicing copy. +/// +/// This macro is meant for cases when slicing is intended: the static cast +/// silences the static analysis warning, and the macro appearance itself makes +/// it very clear that slicing is wanted here. It is made as a macro +/// (not as a function template) to support the case of private inheritance +/// in which a function template would not be able to cast to the private base +/// (see Uri::toUrl() for an example of just that situation). +#define SLICE(Object, ToType) ToType{static_cast(Object)} + namespace Quotient { /// An equivalent of std::hash for QTypes to enable std::unordered_map template -- cgit v1.2.3 From 572b727b22d66a79431326c924236ef431fd972b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 14 May 2022 11:20:43 +0200 Subject: Cleanup across the board Mainly driven by clang-tidy and SonarCloud warnings (sadly, SonarCloud doesn't store historical reports so no link can be provided here). --- lib/connection.cpp | 72 ++++++++++++++++++++--------------------- lib/connection.h | 6 ++-- lib/converters.h | 8 +++++ lib/e2ee/qolminboundsession.cpp | 4 +-- lib/e2ee/qolminboundsession.h | 2 +- lib/events/callinviteevent.cpp | 2 +- lib/events/callinviteevent.h | 4 +-- lib/events/roomkeyevent.h | 5 ++- lib/room.cpp | 20 ++++++------ lib/room.h | 4 +-- 10 files changed, 70 insertions(+), 57 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4418958e..bd4d9251 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -219,7 +219,9 @@ public: } q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult), QDateTime::currentDateTime()); } - std::pair sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) + + std::pair sessionDecryptPrekey(const QOlmMessage& message, + const QString& senderKey) { Q_ASSERT(message.type() == QOlmMessage::PreKey); for(auto& session : olmSessions[senderKey]) { @@ -273,24 +275,19 @@ public: } std::pair sessionDecryptMessage( - const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) + const QJsonObject& personalCipherObject, const QByteArray& senderKey) { - QString decrypted; - QString olmSessionId; - int type = personalCipherObject.value(TypeKeyL).toInt(-1); - QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); - if (type == QOlmMessage::PreKey) { - QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); - auto result = sessionDecryptPrekey(preKeyMessage, senderKey, account); - decrypted = result.first; - olmSessionId = result.second; - } else if (type == QOlmMessage::General) { - QOlmMessage message(body, QOlmMessage::General); - auto result = sessionDecryptGeneral(message, senderKey); - decrypted = result.first; - olmSessionId = result.second; - } - return { decrypted, olmSessionId }; + QOlmMessage message { + personalCipherObject.value(BodyKeyL).toString().toLatin1(), + static_cast( + personalCipherObject.value(TypeKeyL).toInt(-1)) + }; + if (message.type() == QOlmMessage::PreKey) + return sessionDecryptPrekey(message, senderKey); + if (message.type() == QOlmMessage::General) + return sessionDecryptGeneral(message, senderKey); + qCWarning(E2EE) << "Olm message has incorrect type" << message.type(); + return {}; } #endif @@ -310,8 +307,9 @@ public: qCDebug(E2EE) << "Encrypted event is not for the current device"; return {}; } - const auto [decrypted, olmSessionId] = sessionDecryptMessage( - personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); + const auto [decrypted, olmSessionId] = + sessionDecryptMessage(personalCipherObject, + encryptedEvent.senderKey().toLatin1()); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" << encryptedEvent.senderKey() @@ -950,8 +948,6 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) outdatedUsers += event.senderId(); encryptionUpdateRequired = true; pendingEncryptedEvents.push_back(std::make_unique(event.fullJson())); - }, [](const Event& e){ - // Unhandled }); } #endif @@ -967,7 +963,7 @@ void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& eve } switchOnType(*decryptedEvent, - [this, senderKey = event.senderKey(), &event, olmSessionId = olmSessionId](const RoomKeyEvent& roomKeyEvent) { + [this, &event, olmSessionId = olmSessionId](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, event.senderId(), olmSessionId); } else { @@ -2074,12 +2070,13 @@ void Connection::Private::loadOutdatedUserDevices() saveDevicesList(); for(size_t i = 0; i < pendingEncryptedEvents.size();) { - if (q->isKnownCurveKey(pendingEncryptedEvents[i]->fullJson()[SenderKeyL].toString(), pendingEncryptedEvents[i]->contentJson()["sender_key"].toString())) { - handleEncryptedToDeviceEvent(*(pendingEncryptedEvents[i].get())); + if (q->isKnownCurveKey( + pendingEncryptedEvents[i]->fullJson()[SenderKeyL].toString(), + pendingEncryptedEvents[i]->contentPart("sender_key"_ls))) { + handleEncryptedToDeviceEvent(*pendingEncryptedEvents[i]); pendingEncryptedEvents.erase(pendingEncryptedEvents.begin() + i); - } else { - i++; - } + } else + ++i; } }); } @@ -2188,13 +2185,10 @@ void Connection::saveOlmAccount() #ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { - auto room = this->room(notification["room_id"].toString()); + auto r = room(notification["room_id"].toString()); auto event = makeEvent(notification["event"].toObject()); - auto decrypted = room->decryptMessage(*event); - if(!decrypted) { - return QJsonObject(); - } - return decrypted->fullJson(); + const auto decrypted = r->decryptMessage(*event); + return decrypted ? decrypted->fullJson() : QJsonObject(); } Database* Connection::database() @@ -2202,14 +2196,18 @@ Database* Connection::database() return d->database; } -UnorderedMap Connection::loadRoomMegolmSessions(Room* room) +UnorderedMap +Connection::loadRoomMegolmSessions(const Room* room) { return database()->loadMegolmSessions(room->id(), picklingMode()); } -void Connection::saveMegolmSession(Room* room, QOlmInboundGroupSession* session) +void Connection::saveMegolmSession(const Room* room, + const QOlmInboundGroupSession& session) { - database()->saveMegolmSession(room->id(), session->sessionId(), session->pickle(picklingMode()), session->senderId(), session->olmSessionId()); + database()->saveMegolmSession(room->id(), session.sessionId(), + session.pickle(picklingMode()), + session.senderId(), session.olmSessionId()); } QStringList Connection::devicesForUser(User* user) const diff --git a/lib/connection.h b/lib/connection.h index 29731593..b75bd5b5 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -317,8 +317,10 @@ public: #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; Database* database(); - UnorderedMap loadRoomMegolmSessions(Room* room); - void saveMegolmSession(Room* room, QOlmInboundGroupSession* session); + UnorderedMap loadRoomMegolmSessions( + const Room* room); + void saveMegolmSession(const Room* room, + const QOlmInboundGroupSession& session); #endif // Quotient_E2EE_ENABLED Q_INVOKABLE Quotient::SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; diff --git a/lib/converters.h b/lib/converters.h index 6515310a..5e3becb8 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -163,6 +163,14 @@ inline qint64 fromJson(const QJsonValue& jv) { return qint64(jv.toDouble()); } template <> inline QString fromJson(const QJsonValue& jv) { return jv.toString(); } +//! Use fromJson and use toLatin1()/toUtf8()/... to make QByteArray +//! +//! QJsonValue can only convert to QString and there's ambiguity whether +//! conversion to QByteArray should use (fast but very limited) toLatin1() or +//! (all encompassing and conforming to the JSON spec but slow) toUtf8(). +template <> +inline QByteArray fromJson(const QJsonValue& jv) = delete; + template <> inline QJsonArray fromJson(const QJsonValue& jv) { return jv.toArray(); } diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 60d871ef..62856831 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -154,9 +154,9 @@ QString QOlmInboundGroupSession::olmSessionId() const { return m_olmSessionId; } -void QOlmInboundGroupSession::setOlmSessionId(const QString& olmSessionId) +void QOlmInboundGroupSession::setOlmSessionId(const QString& newOlmSessionId) { - m_olmSessionId = olmSessionId; + m_olmSessionId = newOlmSessionId; } QString QOlmInboundGroupSession::senderId() const diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 32112b97..13515434 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -44,7 +44,7 @@ public: //! The olm session that this session was received from. //! Required to get the device this session is from. QString olmSessionId() const; - void setOlmSessionId(const QString& setOlmSessionId); + void setOlmSessionId(const QString& newOlmSessionId); //! The sender of this session. QString senderId() const; diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index 11d50768..2f26a1cb 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -33,7 +33,7 @@ CallInviteEvent::CallInviteEvent(const QJsonObject& obj) qCDebug(EVENTS) << "Call Invite event"; } -CallInviteEvent::CallInviteEvent(const QString& callId, const int lifetime, +CallInviteEvent::CallInviteEvent(const QString& callId, int lifetime, const QString& sdp) : CallEventBase( typeId(), matrixTypeId(), callId, 0, diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index 1b1f4f0f..5b4ca0df 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -13,10 +13,10 @@ public: explicit CallInviteEvent(const QJsonObject& obj); - explicit CallInviteEvent(const QString& callId, const int lifetime, + explicit CallInviteEvent(const QString& callId, int lifetime, const QString& sdp); - QUO_CONTENT_GETTER(int, lifetime) // FIXME: Omittable<>? + QUO_CONTENT_GETTER(int, lifetime) QString sdp() const { return contentPart("offer"_ls).value("sdp"_ls).toString(); diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index c4df7936..ed4c9440 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -16,7 +16,10 @@ public: QString algorithm() const { return contentPart("algorithm"_ls); } QString roomId() const { return contentPart(RoomIdKeyL); } QString sessionId() const { return contentPart("session_id"_ls); } - QString sessionKey() const { return contentPart("session_key"_ls); } + QByteArray sessionKey() const + { + return contentPart("session_key"_ls).toLatin1(); + } }; REGISTER_EVENT_TYPE(RoomKeyEvent) } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index 993455be..0a997b9c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -339,14 +339,16 @@ public: #ifdef Quotient_E2EE_ENABLED UnorderedMap groupSessions; - bool addInboundGroupSession(QString sessionId, QString sessionKey, const QString& senderId, const QString& olmSessionId) + bool addInboundGroupSession(QString sessionId, QByteArray sessionKey, + const QString& senderId, + const QString& olmSessionId) { - if (groupSessions.find(sessionId) != groupSessions.end()) { + if (groupSessions.contains(sessionId)) { qCWarning(E2EE) << "Inbound Megolm session" << sessionId << "already exists"; return false; } - auto megolmSession = QOlmInboundGroupSession::create(sessionKey.toLatin1()); + auto megolmSession = QOlmInboundGroupSession::create(sessionKey); if (megolmSession->sessionId() != sessionId) { qCWarning(E2EE) << "Session ID mismatch in m.room_key event"; return false; @@ -354,13 +356,12 @@ public: megolmSession->setSenderId(senderId); megolmSession->setOlmSessionId(olmSessionId); qCWarning(E2EE) << "Adding inbound session"; - connection->saveMegolmSession(q, megolmSession.get()); + connection->saveMegolmSession(q, *megolmSession); groupSessions[sessionId] = std::move(megolmSession); return true; } QString groupSessionDecryptMessage(QByteArray cipher, - const QString& senderKey, const QString& sessionId, const QString& eventId, QDateTime timestamp, @@ -1478,9 +1479,9 @@ RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) return {}; } QString decrypted = d->groupSessionDecryptMessage( - encryptedEvent.ciphertext(), encryptedEvent.senderKey(), - encryptedEvent.sessionId(), encryptedEvent.id(), - encryptedEvent.originTimestamp(), encryptedEvent.senderId()); + encryptedEvent.ciphertext(), encryptedEvent.sessionId(), + encryptedEvent.id(), encryptedEvent.originTimestamp(), + encryptedEvent.senderId()); if (decrypted.isEmpty()) { // qCWarning(E2EE) << "Encrypted message is empty"; return {}; @@ -1509,7 +1510,8 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, << roomKeyEvent.algorithm() << "in m.room_key event"; } if (d->addInboundGroupSession(roomKeyEvent.sessionId(), - roomKeyEvent.sessionKey(), senderId, olmSessionId)) { + roomKeyEvent.sessionKey(), senderId, + olmSessionId)) { qCWarning(E2EE) << "added new inboundGroupSession:" << d->groupSessions.size(); auto undecryptedEvents = d->undecryptedEvents[roomKeyEvent.sessionId()]; diff --git a/lib/room.h b/lib/room.h index 2e2ebf9a..6e6071f0 100644 --- a/lib/room.h +++ b/lib/room.h @@ -871,8 +871,8 @@ public Q_SLOTS: void inviteCall(const QString& callId, const int lifetime, const QString& sdp); void sendCallCandidates(const QString& callId, const QJsonArray& candidates); - [[deprecated("Lifetime argument is no more in use here; " - "use 2-arg Room::answerCall() instead")]] + //! \deprecated Lifetime argument is no more passed; use 2-arg + //! Room::answerCall() instead void answerCall(const QString& callId, int lifetime, const QString& sdp); void answerCall(const QString& callId, const QString& sdp); void hangupCall(const QString& callId); -- cgit v1.2.3 From 3fcc0c5acb160364e819688cc67a8aaf814fafef Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 14 May 2022 20:51:05 +0200 Subject: Optimise #includes for QOlm* classes --- autotests/testolmutility.cpp | 2 ++ lib/connection.cpp | 15 ++++++++------- lib/e2ee/qolmaccount.cpp | 3 +++ lib/e2ee/qolmaccount.h | 6 ------ lib/e2ee/qolmsession.cpp | 7 ++++--- lib/e2ee/qolmsession.h | 7 ++----- 6 files changed, 19 insertions(+), 21 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index b4532c8d..92b8b5b2 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -6,6 +6,8 @@ #include "e2ee/qolmaccount.h" #include "e2ee/qolmutility.h" +#include + using namespace Quotient; void TestOlmUtility::canonicalJSON() diff --git a/lib/connection.cpp b/lib/connection.cpp index bd4d9251..511b64e2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -35,16 +35,17 @@ #include "jobs/syncjob.h" #ifdef Quotient_E2EE_ENABLED -# include "e2ee/qolmaccount.h" -# include "e2ee/qolmutils.h" # include "database.h" +# include "e2ee/qolmaccount.h" # include "e2ee/qolminboundsession.h" +# include "e2ee/qolmsession.h" +# include "e2ee/qolmutils.h" -#if QT_VERSION_MAJOR >= 6 -# include -#else -# include -#endif +# if QT_VERSION_MAJOR >= 6 +# include +# else +# include +# endif #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 476a60bd..af9eb1bf 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -5,6 +5,7 @@ #include "qolmaccount.h" #include "connection.h" +#include "e2ee/qolmsession.h" #include "e2ee/qolmutility.h" #include "e2ee/qolmutils.h" @@ -12,6 +13,8 @@ #include +#include + using namespace Quotient; QHash OneTimeKeys::curve25519() const diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 17f43f1a..08301998 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -9,18 +9,12 @@ #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); diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index e575ff39..531a6696 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -3,10 +3,12 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "qolmsession.h" + #include "e2ee/qolmutils.h" #include "logging.h" + #include -#include +#include using namespace Quotient; @@ -247,5 +249,4 @@ std::variant QOlmSession::matchesInboundSessionFrom(const QStri QOlmSession::QOlmSession(OlmSession *session) : m_session(session) -{ -} +{} diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index f20c9837..4eb151b6 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -4,17 +4,14 @@ #pragma once -#include -#include // FIXME: OlmSession #include "e2ee/e2ee.h" #include "e2ee/qolmmessage.h" #include "e2ee/qolmerrors.h" #include "e2ee/qolmaccount.h" -namespace Quotient { +struct OlmSession; -class QOlmAccount; -class QOlmSession; +namespace Quotient { //! Either an outbound or inbound session for secure communication. class QUOTIENT_API QOlmSession -- cgit v1.2.3 From 1fe8dc00de4d9bb7072ec9677ec7f8e73e4fc769 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 15 May 2022 21:52:31 +0200 Subject: QOlmAccount::needsSave() shouldn't be const Making Qt signals const is an impossible commitment - once the signal is out, you can't control if any called slot will change the emitting class or not. The code compiles but const-ness is not preserved. --- lib/e2ee/qolmaccount.cpp | 5 +++-- lib/e2ee/qolmaccount.h | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index af9eb1bf..3339ce46 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -143,7 +143,7 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const return olm_account_max_number_of_one_time_keys(m_account); } -size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const +size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) { const size_t randomLength = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); QByteArray randomBuffer = getRandom(randomLength); @@ -196,7 +196,8 @@ QByteArray QOlmAccount::signOneTimeKey(const QString &key) const return sign(j.toJson(QJsonDocument::Compact)); } -std::optional QOlmAccount::removeOneTimeKeys(const QOlmSessionPtr &session) const +std::optional QOlmAccount::removeOneTimeKeys( + const QOlmSessionPtr& session) { const auto error = olm_remove_one_time_keys(m_account, session->raw()); diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 08301998..1f36919a 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -24,7 +24,7 @@ class QUOTIENT_API QOlmAccount : public QObject Q_OBJECT public: QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent = nullptr); - ~QOlmAccount(); + ~QOlmAccount() override; //! Creates a new instance of OlmAccount. During the instantiation //! the Ed25519 fingerprint key pair and the Curve25519 identity key @@ -55,7 +55,7 @@ public: size_t maxNumberOfOneTimeKeys() const; //! Generates the supplied number of one time keys. - size_t generateOneTimeKeys(size_t numberOfKeys) const; + size_t generateOneTimeKeys(size_t numberOfKeys); //! Gets the OlmAccount's one time keys formatted as JSON. OneTimeKeys oneTimeKeys() const; @@ -97,7 +97,7 @@ public: OlmAccount *data(); Q_SIGNALS: - void needsSave() const; + void needsSave(); private: OlmAccount *m_account = nullptr; // owning -- cgit v1.2.3 From a3486fd0e9786c47564ce8173a2e4039135b10f9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 15 May 2022 22:08:09 +0200 Subject: Simplify QOlmSession::matchesInboundSession*() There's no particular use in letting `QOlmError` out, only to confirm that, well, `QOlmError` is just another form of no-match. --- autotests/testolmsession.cpp | 2 +- lib/connection.cpp | 3 +-- lib/e2ee/qolmsession.cpp | 43 +++++++++++++++++-------------------------- 3 files changed, 19 insertions(+), 29 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 5436c392..2674b60b 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -45,7 +45,7 @@ void TestOlmSession::olmEncryptDecrypt() const auto encrypted = outboundSession->encrypt("Hello world!"); if (encrypted.type() == QOlmMessage::PreKey) { QOlmMessage m(encrypted); // clone - QVERIFY(std::get(inboundSession->matchesInboundSession(m))); + QVERIFY(inboundSession->matchesInboundSession(m)); } const auto decrypted = std::get(inboundSession->decrypt(encrypted)); diff --git a/lib/connection.cpp b/lib/connection.cpp index 511b64e2..955b5b1a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -226,8 +226,7 @@ public: { 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)) { + if (session->matchesInboundSessionFrom(senderKey, message)) { qCDebug(E2EE) << "Found inbound session"; const auto result = session->decrypt(message); if(std::holds_alternative(result)) { diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 531a6696..5ccbfd40 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -209,42 +209,33 @@ bool QOlmSession::hasReceivedMessage() const return olm_session_has_received_message(m_session); } -std::variant QOlmSession::matchesInboundSession(const QOlmMessage &preKeyMessage) const +bool 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()); + const auto maybeMatches = + olm_matches_inbound_session(m_session, oneTimeKeyBuf.data(), + oneTimeKeyBuf.length()); - if (matchesResult == olm_error()) { + if (maybeMatches == olm_error()) { return lastError(m_session); } - switch (matchesResult) { - case 0: - return false; - case 1: - return true; - default: - return QOlmError::Unknown; - } + return maybeMatches == 1; } -std::variant QOlmSession::matchesInboundSessionFrom(const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) const + +bool 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; - } + const auto maybeMatches = olm_matches_inbound_session_from( + m_session, theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), + oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); + + if (maybeMatches == olm_error()) + qCWarning(E2EE) << "Error matching an inbound session:" + << olm_session_last_error(m_session); + return maybeMatches == 1; } QOlmSession::QOlmSession(OlmSession *session) -- cgit v1.2.3 From 79b3dba1ed4b6870c4e989ada88e33b1ce0ddc21 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 16 May 2022 10:41:54 +0200 Subject: QOlmExpected and associated refactoring As mentioned in the commit introducing `Expected`, `QOlmExpected` is simply an alias for `Expected`. This simplifies quite a few function signatures in `QOlm*` classes and collapses unwieldy `std::holds_alternative<>`/`std::get<>` constructs into a neat contextual bool cast and an invocation of `operator*` or `value()`/`error()` accessors that don't need to specify the type. While refactoring the code, I found a couple of cases of mismatching `uint32_t` and `qint32_t` in return values; a couple of cases where `decrypt()` returns `QString` which is in fact `QByteArray` (e.g., in `QOlmSession::decrypt()`); there's a repetitive algorithm in `Connection::Private::sessionDecryptPrekey()` and `sessionDecryptGeneral()` --- autotests/testgroupsession.cpp | 14 ++-- autotests/testolmaccount.cpp | 5 +- autotests/testolmsession.cpp | 14 ++-- autotests/testolmutility.cpp | 5 +- lib/connection.cpp | 136 ++++++++++++++++++++------------------- lib/database.cpp | 32 ++++----- lib/e2ee/e2ee.h | 8 +++ lib/e2ee/qolmaccount.cpp | 20 +++--- lib/e2ee/qolmaccount.h | 21 +++--- lib/e2ee/qolminboundsession.cpp | 10 +-- lib/e2ee/qolminboundsession.h | 14 ++-- lib/e2ee/qolmoutboundsession.cpp | 18 +++--- lib/e2ee/qolmoutboundsession.h | 19 +++--- lib/e2ee/qolmsession.cpp | 22 +++++-- lib/e2ee/qolmsession.h | 35 +++++----- lib/e2ee/qolmutility.cpp | 14 ++-- lib/e2ee/qolmutility.h | 7 +- lib/room.cpp | 17 +++-- 18 files changed, 215 insertions(+), 196 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 2566669e..3c329a8a 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -16,11 +16,11 @@ void TestGroupSession::groupSessionPicklingValid() QVERIFY(QByteArray::fromBase64(ogsId).size() > 0); QCOMPARE(0, ogs->sessionMessageIndex()); - auto ogsPickled = std::get(ogs->pickle(Unencrypted {})); - auto ogs2 = std::get(QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {})); + auto ogsPickled = ogs->pickle(Unencrypted {}).value(); + auto ogs2 = QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {}).value(); QCOMPARE(ogsId, ogs2->sessionId()); - auto igs = QOlmInboundGroupSession::create(std::get(ogs->sessionKey())); + auto igs = QOlmInboundGroupSession::create(ogs->sessionKey().value()); const auto igsId = igs->sessionId(); // ID is valid base64? QVERIFY(QByteArray::fromBase64(igsId).size() > 0); @@ -29,22 +29,22 @@ void TestGroupSession::groupSessionPicklingValid() QCOMPARE(0, igs->firstKnownIndex()); auto igsPickled = igs->pickle(Unencrypted {}); - igs = std::get(QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {})); + igs = QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {}).value(); QCOMPARE(igsId, igs->sessionId()); } void TestGroupSession::groupSessionCryptoValid() { auto ogs = QOlmOutboundGroupSession::create(); - auto igs = QOlmInboundGroupSession::create(std::get(ogs->sessionKey())); + auto igs = QOlmInboundGroupSession::create(ogs->sessionKey().value()); QCOMPARE(ogs->sessionId(), igs->sessionId()); const auto plainText = QStringLiteral("Hello world!"); - const auto ciphertext = std::get(ogs->encrypt(plainText)); + const auto ciphertext = ogs->encrypt(plainText).value(); // ciphertext valid base64? QVERIFY(QByteArray::fromBase64(ciphertext).size() > 0); - const auto decryptionResult = std::get>(igs->decrypt(ciphertext)); + const auto decryptionResult = igs->decrypt(ciphertext).value(); //// correct plaintext? QCOMPARE(plainText, decryptionResult.first); diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 9989665a..e31ff6d3 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -21,7 +21,7 @@ void TestOlmAccount::pickleUnpickledTest() QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); olmAccount.createNewAccount(); auto identityKeys = olmAccount.identityKeys(); - auto pickled = std::get(olmAccount.pickle(Unencrypted{})); + auto pickled = olmAccount.pickle(Unencrypted{}).value(); QOlmAccount olmAccount2(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); olmAccount2.unpickle(pickled, Unencrypted{}); auto identityKeys2 = olmAccount2.identityKeys(); @@ -57,8 +57,7 @@ void TestOlmAccount::signatureValid() const auto identityKeys = olmAccount.identityKeys(); const auto ed25519Key = identityKeys.ed25519; const auto verify = utility.ed25519Verify(ed25519Key, message, signature); - QVERIFY(std::holds_alternative(verify)); - QVERIFY(std::get(verify) == true); + QVERIFY(verify.value_or(false)); } void TestOlmAccount::oneTimeKeysValid() diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 2674b60b..182659e7 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -20,8 +20,8 @@ std::pair createSessionPair() const QByteArray oneTimeKeyA("WzsbsjD85iB1R32iWxfJdwkgmdz29ClMbJSJziECYwk"); const QByteArray identityKeyB("q/YhJtog/5VHCAS9rM9uUf6AaFk1yPe4GYuyUOXyQCg"); const QByteArray oneTimeKeyB("oWvzryma+B2onYjo3hM6A3Mgo/Yepm8HvgSvwZMTnjQ"); - auto outbound = std::get(accountA - .createOutboundSession(identityKeyB, oneTimeKeyB)); + auto outbound = + accountA.createOutboundSession(identityKeyB, oneTimeKeyB).value(); const auto preKey = outbound->encrypt(""); // Payload does not matter for PreKey @@ -29,7 +29,7 @@ std::pair createSessionPair() // We can't call QFail here because it's an helper function returning a value throw "Wrong first message type received, can't create session"; } - auto inbound = std::get(accountB.createInboundSession(preKey)); + auto inbound = accountB.createInboundSession(preKey).value(); return { std::move(inbound), std::move(outbound) }; } @@ -48,7 +48,7 @@ void TestOlmSession::olmEncryptDecrypt() QVERIFY(inboundSession->matchesInboundSession(m)); } - const auto decrypted = std::get(inboundSession->decrypt(encrypted)); + const auto decrypted = inboundSession->decrypt(encrypted).value(); QCOMPARE(decrypted, "Hello world!"); } @@ -56,11 +56,11 @@ void TestOlmSession::olmEncryptDecrypt() void TestOlmSession::correctSessionOrdering() { // n0W5IJ2ZmaI9FxKRj/wohUQ6WEU0SfoKsgKKHsr4VbM - auto session1 = std::get(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGvlaV6t/0ihD2/0QGckDIvbmE1aV+PxB0zUtHXh99bI/60N+PWkCLA84jEY4sz3d45ui/TVoFGLDHlymKxvlj7XngXrbtlxSkVntsPzDiNpKEXCa26N2ubKpQ0fbjrV5gbBTYWfU04DXHPXFDTksxpNALYt/h0eVMVhf6hB0ZzpLBsOG0mpwkLufwub0CuDEDGGmRddz3TcNCLq5NnI8R9udDWvHAkTS1UTbHuIf/y6cZg875nJyXpAvd8/XhL8TOo8ot2sE1fElBa4vrH/m9rBQMC1GPkhLBIizmY44C+Sq9PQRnF+uCZ", Unencrypted{})); + auto session1 = QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGvlaV6t/0ihD2/0QGckDIvbmE1aV+PxB0zUtHXh99bI/60N+PWkCLA84jEY4sz3d45ui/TVoFGLDHlymKxvlj7XngXrbtlxSkVntsPzDiNpKEXCa26N2ubKpQ0fbjrV5gbBTYWfU04DXHPXFDTksxpNALYt/h0eVMVhf6hB0ZzpLBsOG0mpwkLufwub0CuDEDGGmRddz3TcNCLq5NnI8R9udDWvHAkTS1UTbHuIf/y6cZg875nJyXpAvd8/XhL8TOo8ot2sE1fElBa4vrH/m9rBQMC1GPkhLBIizmY44C+Sq9PQRnF+uCZ", Unencrypted{}).value(); // +9pHJhP3K4E5/2m8PYBPLh8pS9CJodwUOh8yz3mnmw0 - auto session2 = std::get(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UFD+q37/WlfTAzQsSjCdD07FcErZ4siEy5vpiB+pyO8i53ptZvb2qRvqNKFzPaXuu33PS2PBTmmnR+kJt+DgDNqWadyaj/WqEAejc7ALqSs5GuhbZtpoLe+lRSRK0rwVX3gzz4qrl8pm0pD5pSZAUWRXDRlieGWMclz68VUvnSaQH7ElTo4S634CJk+xQfFFCD26v0yONPSN6rwouS1cWPuG5jTlnV8vCFVTU2+lduKh54Ko6FUJ/ei4xR8Nk2duBGSc/TdllX9e2lDYHSUkWoD4ti5xsFioB8Blus7JK9BZfcmRmdlxIOD", Unencrypted {})); + auto session2 = QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UFD+q37/WlfTAzQsSjCdD07FcErZ4siEy5vpiB+pyO8i53ptZvb2qRvqNKFzPaXuu33PS2PBTmmnR+kJt+DgDNqWadyaj/WqEAejc7ALqSs5GuhbZtpoLe+lRSRK0rwVX3gzz4qrl8pm0pD5pSZAUWRXDRlieGWMclz68VUvnSaQH7ElTo4S634CJk+xQfFFCD26v0yONPSN6rwouS1cWPuG5jTlnV8vCFVTU2+lduKh54Ko6FUJ/ei4xR8Nk2duBGSc/TdllX9e2lDYHSUkWoD4ti5xsFioB8Blus7JK9BZfcmRmdlxIOD", Unencrypted {}).value(); // MC7n8hX1l7WlC2/WJGHZinMocgiBZa4vwGAOredb/ME - auto session3 = std::get(QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGNk2TmVDJ95K0Nywf24FNklNVtXtFDiFPHFwNSmCbHNCp3hsGtZlt0AHUkMmL48XklLqzwtVk5/v2RRmSKR5LqYdIakrtuK/fY0ENhBZIbI1sRetaJ2KMbY9l6rCJNfFg8VhpZ4KTVvEZVuP9g/eZkCnP5NxzXiBRF6nfY3O/zhcKxa3acIqs6BMhyLsfuJ80t+hQ1HvVyuhBerGujdSDzV9tJ9SPidOwfYATk81LVF9hTmnI0KaZa7qCtFzhG0dU/Z3hIWH9HOaw1aSB/IPmughbwdJOwERyhuo3YHoznlQnJ7X252BlI", Unencrypted{})); + auto session3 = QOlmSession::unpickle("7g5cfQRsDk2ROXf9S01n2leZiFRon+EbvXcMOADU0UGNk2TmVDJ95K0Nywf24FNklNVtXtFDiFPHFwNSmCbHNCp3hsGtZlt0AHUkMmL48XklLqzwtVk5/v2RRmSKR5LqYdIakrtuK/fY0ENhBZIbI1sRetaJ2KMbY9l6rCJNfFg8VhpZ4KTVvEZVuP9g/eZkCnP5NxzXiBRF6nfY3O/zhcKxa3acIqs6BMhyLsfuJ80t+hQ1HvVyuhBerGujdSDzV9tJ9SPidOwfYATk81LVF9hTmnI0KaZa7qCtFzhG0dU/Z3hIWH9HOaw1aSB/IPmughbwdJOwERyhuo3YHoznlQnJ7X252BlI", Unencrypted{}).value(); const auto session1Id = session1->sessionId(); const auto session2Id = session2->sessionId(); diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index 92b8b5b2..c1323533 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -81,7 +81,10 @@ void TestOlmUtility::verifySignedOneTimeKey() delete[](reinterpret_cast(utility)); QOlmUtility utility2; - auto res2 = std::get(utility2.ed25519Verify(aliceOlm.identityKeys().ed25519, msg, signatureBuf1)); + auto res2 = + utility2 + .ed25519Verify(aliceOlm.identityKeys().ed25519, msg, signatureBuf1) + .value_or(false); //QCOMPARE(std::string(olm_utility_last_error(utility)), "SUCCESS"); QCOMPARE(res2, true); diff --git a/lib/connection.cpp b/lib/connection.cpp index 955b5b1a..2528b70b 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -212,82 +212,83 @@ public: 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), QDateTime::currentDateTime()); - } - - std::pair sessionDecryptPrekey(const QOlmMessage& message, - const QString& senderKey) + void saveSession(QOlmSession& session, const QString& senderKey) { - Q_ASSERT(message.type() == QOlmMessage::PreKey); - for(auto& session : olmSessions[senderKey]) { - if (session->matchesInboundSessionFrom(senderKey, message)) { - qCDebug(E2EE) << "Found inbound session"; - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); - return { std::get(result), session->sessionId() }; - } 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); - QString sessionId = newSession->sessionId(); - saveSession(newSession, senderKey); - olmSessions[senderKey].push_back(std::move(newSession)); - if(std::holds_alternative(result)) { - return { std::get(result), sessionId }; - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; - return {}; - } + if (auto pickleResult = session.pickle(q->picklingMode())) + q->database()->saveOlmSession(senderKey, session.sessionId(), + *pickleResult, + QDateTime::currentDateTime()); + else + qCWarning(E2EE) << "Failed to pickle olm session. Error" + << pickleResult.error(); } - std::pair sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) + + template + std::pair doDecryptMessage(const QOlmSession& session, + const QOlmMessage& message, + FnT&& andThen) const { - Q_ASSERT(message.type() == QOlmMessage::General); - for(auto& session : olmSessions[senderKey]) { - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); - return { std::get(result), session->sessionId() }; - } + const auto expectedMessage = session.decrypt(message); + if (expectedMessage) { + const auto result = + std::make_pair(*expectedMessage, session.sessionId()); + andThen(); + return result; } - qCWarning(E2EE) << "Failed to decrypt message"; + const auto errorLine = message.type() == QOlmMessage::PreKey + ? "Failed to decrypt prekey message:" + : "Failed to decrypt message:"; + qCDebug(E2EE) << errorLine << expectedMessage.error(); return {}; } std::pair sessionDecryptMessage( const QJsonObject& personalCipherObject, const QByteArray& senderKey) { + const auto msgType = static_cast( + personalCipherObject.value(TypeKeyL).toInt(-1)); + if (msgType != QOlmMessage::General && msgType != QOlmMessage::PreKey) { + qCWarning(E2EE) << "Olm message has incorrect type" << msgType; + return {}; + } QOlmMessage message { - personalCipherObject.value(BodyKeyL).toString().toLatin1(), - static_cast( - personalCipherObject.value(TypeKeyL).toInt(-1)) + personalCipherObject.value(BodyKeyL).toString().toLatin1(), msgType }; - if (message.type() == QOlmMessage::PreKey) - return sessionDecryptPrekey(message, senderKey); - if (message.type() == QOlmMessage::General) - return sessionDecryptGeneral(message, senderKey); - qCWarning(E2EE) << "Olm message has incorrect type" << message.type(); - return {}; + for (const auto& session : olmSessions[senderKey]) + if (msgType == QOlmMessage::General + || session->matchesInboundSessionFrom(senderKey, message)) { + return doDecryptMessage(*session, message, [this, &session] { + q->database()->setOlmSessionLastReceived( + session->sessionId(), QDateTime::currentDateTime()); + }); + } + + if (msgType == QOlmMessage::General) { + qCWarning(E2EE) << "Failed to decrypt message"; + return {}; + } + + qCDebug(E2EE) << "Creating new inbound session"; // Pre-key messages only + auto newSessionResult = + olmAccount->createInboundSessionFrom(senderKey, message); + if (!newSessionResult) { + qCWarning(E2EE) + << "Failed to create inbound session for" << senderKey + << "with error" << newSessionResult.error(); + return {}; + } + auto newSession = std::move(*newSessionResult); + auto error = olmAccount->removeOneTimeKeys(*newSession); + if (error) { + qWarning(E2EE) << "Failed to remove one time key for session" + << newSession->sessionId(); + // Keep going though + } + return doDecryptMessage( + *newSession, message, [this, &senderKey, &newSession] { + saveSession(*newSession, senderKey); + olmSessions[senderKey].push_back(std::move(newSession)); + }); } #endif @@ -2177,8 +2178,11 @@ void Connection::saveOlmAccount() { qCDebug(E2EE) << "Saving olm account"; #ifdef Quotient_E2EE_ENABLED - auto pickle = d->olmAccount->pickle(d->picklingMode); - d->database->setAccountPickle(std::get(pickle)); + if (const auto expectedPickle = d->olmAccount->pickle(d->picklingMode)) + d->database->setAccountPickle(*expectedPickle); + else + qCWarning(E2EE) << "Couldn't save Olm account pickle:" + << expectedPickle.error(); #endif } diff --git a/lib/database.cpp b/lib/database.cpp index 3189b3f1..e2e7acc9 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -184,12 +184,14 @@ UnorderedMap> Database::loadOlmSessions(con 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))); + if (auto expectedSession = + QOlmSession::unpickle(query.value("pickle").toByteArray(), + picklingMode)) { + sessions[query.value("senderKey").toString()].emplace_back( + std::move(*expectedSession)); + } else + qCWarning(E2EE) + << "Failed to unpickle olm session:" << expectedSession.error(); } return sessions; } @@ -203,15 +205,15 @@ UnorderedMap Database::loadMegolmSessions(c commit(); UnorderedMap 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("sessionId").toString()] = std::move(std::get(session)); - sessions[query.value("sessionId").toString()]->setOlmSessionId(query.value("olmSessionId").toString()); - sessions[query.value("sessionId").toString()]->setSenderId(query.value("senderId").toString()); + if (auto expectedSession = QOlmInboundGroupSession::unpickle( + query.value("pickle").toByteArray(), picklingMode)) { + auto& sessionPtr = sessions[query.value("sessionId").toString()] = + std::move(*expectedSession); + sessionPtr->setOlmSessionId(query.value("olmSessionId").toString()); + sessionPtr->setSenderId(query.value("senderId").toString()); + } else + qCWarning(E2EE) << "Failed to unpickle megolm session:" + << expectedSession.error(); } return sessions; } diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 268cb525..8e433d60 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -6,6 +6,8 @@ #pragma once #include "converters.h" +#include "expected.h" +#include "qolmerrors.h" #include "quotient_common.h" #include @@ -55,6 +57,12 @@ using QOlmSessionPtr = std::unique_ptr; class QOlmInboundGroupSession; using QOlmInboundGroupSessionPtr = std::unique_ptr; +class QOlmOutboundGroupSession; +using QOlmOutboundGroupSessionPtr = std::unique_ptr; + +template +using QOlmExpected = Expected; + struct IdentityKeys { QByteArray curve25519; diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 3339ce46..72dddafb 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -73,7 +73,7 @@ void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) } } -std::variant QOlmAccount::pickle(const PicklingMode &mode) +QOlmExpected QOlmAccount::pickle(const PicklingMode &mode) { const QByteArray key = toKey(mode); const size_t pickleLength = olm_pickle_account_length(m_account); @@ -197,9 +197,9 @@ QByteArray QOlmAccount::signOneTimeKey(const QString &key) const } std::optional QOlmAccount::removeOneTimeKeys( - const QOlmSessionPtr& session) + const QOlmSession& session) { - const auto error = olm_remove_one_time_keys(m_account, session->raw()); + const auto error = olm_remove_one_time_keys(m_account, session.raw()); if (error == olm_error()) { return lastError(m_account); @@ -245,19 +245,19 @@ UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKey return new UploadKeysJob(keys, oneTimeKeysSigned); } -std::variant QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) +QOlmExpected 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) +QOlmExpected 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) +QOlmExpected QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) { return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); } @@ -296,10 +296,6 @@ bool Quotient::ed25519VerifySignature(const QString& signingKey, 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); + return utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf) + .value_or(false); } diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 1f36919a..ee2aa69d 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -5,11 +5,12 @@ #pragma once -#include "csapi/keys.h" #include "e2ee/e2ee.h" -#include "e2ee/qolmerrors.h" #include "e2ee/qolmmessage.h" -#include + +#include "csapi/keys.h" + +#include struct OlmAccount; @@ -38,7 +39,7 @@ public: void unpickle(QByteArray &pickled, const PicklingMode &mode); //! Serialises an OlmAccount to encrypted Base64. - std::variant pickle(const PicklingMode &mode); + QOlmExpected pickle(const PicklingMode &mode); //! Returns the account's public identity keys already formatted as JSON IdentityKeys identityKeys() const; @@ -73,22 +74,26 @@ public: DeviceKeys deviceKeys() const; //! Remove the one time key used to create the supplied session. - [[nodiscard]] std::optional removeOneTimeKeys(const QOlmSessionPtr &session) const; + [[nodiscard]] std::optional removeOneTimeKeys( + const QOlmSession& session); //! 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); + QOlmExpected 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); + QOlmExpected 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); + QOlmExpected createOutboundSession( + const QByteArray& theirIdentityKey, const QByteArray& theirOneTimeKey); void markKeysAsPublished(); diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 62856831..17f06205 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -70,7 +70,8 @@ QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const return pickledBuf; } -std::variant, QOlmError> QOlmInboundGroupSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +QOlmExpected 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()]); @@ -85,7 +86,8 @@ std::variant, QOlmError> QOlmInboundGro return std::make_unique(groupSession); } -std::variant, QOlmError> QOlmInboundGroupSession::decrypt(const QByteArray &message) +QOlmExpected> QOlmInboundGroupSession::decrypt( + const QByteArray& message) { // This is for capturing the output of olm_group_decrypt uint32_t messageIndex = 0; @@ -114,10 +116,10 @@ std::variant, QOlmError> QOlmInboundGroupSession::d QByteArray output(plaintextLen, '0'); std::memcpy(output.data(), plaintextBuf.data(), plaintextLen); - return std::make_pair(QString(output), messageIndex); + return std::make_pair(output, messageIndex); } -std::variant QOlmInboundGroupSession::exportSession(uint32_t messageIndex) +QOlmExpected QOlmInboundGroupSession::exportSession(uint32_t messageIndex) { const auto keyLength = olm_export_inbound_group_session_length(m_groupSession); QByteArray keyBuf(keyLength, '0'); diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 13515434..1a9b4415 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -5,11 +5,8 @@ #pragma once #include "e2ee/e2ee.h" -#include "e2ee/qolmerrors.h" -#include "olm/olm.h" -#include -#include +#include namespace Quotient { @@ -27,14 +24,13 @@ public: QByteArray pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. - static std::variant, QOlmError> - unpickle(const QByteArray& picked, const PicklingMode& mode); + static QOlmExpected unpickle( + const QByteArray& pickled, const PicklingMode& mode); //! Decrypts ciphertext received for this group session. - std::variant, QOlmError> decrypt( - const QByteArray& message); + QOlmExpected > 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); + QOlmExpected 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/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index da32417b..96bad344 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -13,8 +13,7 @@ QOlmError lastError(OlmOutboundGroupSession *session) { QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session) : m_groupSession(session) -{ -} +{} QOlmOutboundGroupSession::~QOlmOutboundGroupSession() { @@ -22,7 +21,7 @@ QOlmOutboundGroupSession::~QOlmOutboundGroupSession() delete[](reinterpret_cast(m_groupSession)); } -std::unique_ptr QOlmOutboundGroupSession::create() +QOlmOutboundGroupSessionPtr 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); @@ -45,7 +44,7 @@ std::unique_ptr QOlmOutboundGroupSession::create() return std::make_unique(olmOutboundGroupSession); } -std::variant QOlmOutboundGroupSession::pickle(const PicklingMode &mode) +QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mode) { QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); QByteArray key = toKey(mode); @@ -61,7 +60,7 @@ std::variant QOlmOutboundGroupSession::pickle(const Pickl return pickledBuf; } -std::variant, QOlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +QOlmExpected QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) { QByteArray pickledBuf = pickled; auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); @@ -80,7 +79,7 @@ std::variant, QOlmError> QOlmOutboundG return std::make_unique(olmOutboundGroupSession); } -std::variant QOlmOutboundGroupSession::encrypt(const QString &plaintext) +QOlmExpected QOlmOutboundGroupSession::encrypt(const QString &plaintext) { QByteArray plaintextBuf = plaintext.toUtf8(); const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); @@ -112,12 +111,13 @@ QByteArray QOlmOutboundGroupSession::sessionId() const return idBuffer; } -std::variant QOlmOutboundGroupSession::sessionKey() const +QOlmExpected 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); + const auto error = olm_outbound_group_session_key( + m_groupSession, reinterpret_cast(keyBuffer.data()), + keyMaxLength); if (error == olm_error()) { return lastError(m_groupSession); } diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 32ba2b3b..8058bbb1 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -4,10 +4,10 @@ #pragma once -#include "olm/olm.h" -#include "e2ee/qolmerrors.h" #include "e2ee/e2ee.h" + #include +#include namespace Quotient { @@ -19,15 +19,15 @@ public: ~QOlmOutboundGroupSession(); //! Creates a new instance of `QOlmOutboundGroupSession`. //! Throw OlmError on errors - static std::unique_ptr create(); + static QOlmOutboundGroupSessionPtr create(); //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); + QOlmExpected pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. - static std::variant, QOlmError> - unpickle(QByteArray& pickled, const PicklingMode& mode); + static QOlmExpected unpickle( + QByteArray& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. - std::variant encrypt(const QString &plaintext); + QOlmExpected encrypt(const QString& plaintext); //! Get the current message index for this session. //! @@ -42,11 +42,10 @@ 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; + QOlmExpected sessionKey() const; QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); private: OlmOutboundGroupSession *m_groupSession; }; -using QOlmOutboundGroupSessionPtr = std::unique_ptr; -} +} // namespace Quotient diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 5ccbfd40..2b149aac 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -27,7 +27,9 @@ 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) +QOlmExpected QOlmSession::createInbound( + QOlmAccount* account, const QOlmMessage& preKeyMessage, bool from, + const QString& theirIdentityKey) { if (preKeyMessage.type() != QOlmMessage::PreKey) { qCCritical(E2EE) << "The message is not a pre-key in when creating inbound session" << BadMessageFormat; @@ -53,17 +55,22 @@ std::variant QOlmSession::createInbound(QOlmAccount * return std::make_unique(olmSession); } -std::variant QOlmSession::createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage) +QOlmExpected QOlmSession::createInboundSession( + QOlmAccount* account, const QOlmMessage& preKeyMessage) { return createInbound(account, preKeyMessage); } -std::variant QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) +QOlmExpected 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) +QOlmExpected QOlmSession::createOutboundSession( + QOlmAccount* account, const QString& theirIdentityKey, + const QString& theirOneTimeKey) { auto *olmOutboundSession = create(); const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); @@ -89,7 +96,7 @@ std::variant QOlmSession::createOutboundSession(QOlmA return std::make_unique(olmOutboundSession); } -std::variant QOlmSession::pickle(const PicklingMode &mode) +QOlmExpected QOlmSession::pickle(const PicklingMode &mode) { QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); QByteArray key = toKey(mode); @@ -105,7 +112,8 @@ std::variant QOlmSession::pickle(const PicklingMode &mode return pickledBuf; } -std::variant QOlmSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +QOlmExpected QOlmSession::unpickle(const QByteArray& pickled, + const PicklingMode& mode) { QByteArray pickledBuf = pickled; auto *olmSession = create(); @@ -140,7 +148,7 @@ QOlmMessage QOlmSession::encrypt(const QString &plaintext) return QOlmMessage(messageBuf, messageType); } -std::variant QOlmSession::decrypt(const QOlmMessage &message) const +QOlmExpected QOlmSession::decrypt(const QOlmMessage &message) const { const auto messageType = message.type(); const auto ciphertext = message.toCiphertext(); diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index 4eb151b6..faae16ef 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -19,32 +19,31 @@ class QUOTIENT_API QOlmSession public: ~QOlmSession(); //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. - static std::variant, QOlmError> - createInboundSession(QOlmAccount* account, const QOlmMessage& preKeyMessage); + static QOlmExpected createInboundSession( + QOlmAccount* account, const QOlmMessage& preKeyMessage); - static std::variant, QOlmError> - createInboundSessionFrom(QOlmAccount* account, - const QString& theirIdentityKey, - const QOlmMessage& preKeyMessage); + static QOlmExpected createInboundSessionFrom( + QOlmAccount* account, const QString& theirIdentityKey, + const QOlmMessage& preKeyMessage); - static std::variant, QOlmError> - createOutboundSession(QOlmAccount* account, const QString& theirIdentityKey, - const QString& theirOneTimeKey); + static QOlmExpected createOutboundSession( + QOlmAccount* account, const QString& theirIdentityKey, + const QString& theirOneTimeKey); //! Serialises an `QOlmSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); + QOlmExpected pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static std::variant, QOlmError> unpickle( + static QOlmExpected 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 + //! Decrypts a message using this session. Decoding is lossy, meaning if //! the decrypted plaintext contains invalid UTF-8 symbols, they will //! be returned as `U+FFFD` (�). - std::variant decrypt(const QOlmMessage &message) const; + QOlmExpected decrypt(const QOlmMessage &message) const; //! Get a base64-encoded identifier for this session. QByteArray sessionId() const; @@ -56,11 +55,10 @@ public: bool hasReceivedMessage() const; //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSession( - const QOlmMessage& preKeyMessage) const; + bool matchesInboundSession(const QOlmMessage& preKeyMessage) const; //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSessionFrom( + bool matchesInboundSessionFrom( const QString& theirIdentityKey, const QOlmMessage& preKeyMessage) const; friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) @@ -68,8 +66,7 @@ public: return lhs.sessionId() < rhs.sessionId(); } - friend bool operator<(const std::unique_ptr& lhs, - const std::unique_ptr& rhs) + friend bool operator<(const QOlmSessionPtr& lhs, const QOlmSessionPtr& rhs) { return *lhs < *rhs; } @@ -80,7 +77,7 @@ public: private: //! Helper function for creating new sessions and handling errors. static OlmSession* create(); - static std::variant, QOlmError> createInbound( + static QOlmExpected createInbound( QOlmAccount* account, const QOlmMessage& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); OlmSession* m_session; diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index 9f09a37f..84559085 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -3,8 +3,8 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "e2ee/qolmutility.h" -#include "olm/olm.h" -#include + +#include using namespace Quotient; @@ -40,8 +40,9 @@ QString QOlmUtility::sha256Utf8Msg(const QString &message) const return sha256Bytes(message.toUtf8()); } -std::variant QOlmUtility::ed25519Verify(const QByteArray &key, - const QByteArray &message, const QByteArray &signature) +QOlmExpected QOlmUtility::ed25519Verify(const QByteArray& key, + const QByteArray& message, + const QByteArray& signature) { QByteArray signatureBuf(signature.length(), '0'); std::copy(signature.begin(), signature.end(), signatureBuf.begin()); @@ -57,8 +58,5 @@ std::variant QOlmUtility::ed25519Verify(const QByteArray &key, return error; } - if (ret != 0) { - return false; - } - return true; + return !ret; // ret == 0 means success } diff --git a/lib/e2ee/qolmutility.h b/lib/e2ee/qolmutility.h index a12af49a..5f6bcdc5 100644 --- a/lib/e2ee/qolmutility.h +++ b/lib/e2ee/qolmutility.h @@ -4,15 +4,12 @@ #pragma once -#include -#include "e2ee/qolmerrors.h" +#include "e2ee/e2ee.h" struct OlmUtility; namespace Quotient { -class QOlmSession; - //! Allows you to make use of crytographic hashing via SHA-2 and //! verifying ed25519 signatures. class QUOTIENT_API QOlmUtility @@ -32,7 +29,7 @@ public: //! \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, + QOlmExpected ed25519Verify(const QByteArray &key, const QByteArray &message, const QByteArray &signature); private: diff --git a/lib/room.cpp b/lib/room.cpp index db49e80f..1314803e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -380,17 +380,22 @@ public: return {}; } auto decryptResult = senderSession->decrypt(cipher); - if(std::holds_alternative(decryptResult)) { + if(!decryptResult) { qCWarning(E2EE) << "Unable to decrypt event" << eventId - << "with matching megolm session:" << std::get(decryptResult); + << "with matching megolm session:" << decryptResult.error(); return {}; } - const auto& [content, index] = std::get>(decryptResult); - const auto& [recordEventId, ts] = q->connection()->database()->groupSessionIndexRecord(q->id(), senderSession->sessionId(), index); + const auto& [content, index] = *decryptResult; + const auto& [recordEventId, ts] = + q->connection()->database()->groupSessionIndexRecord( + q->id(), senderSession->sessionId(), index); if (recordEventId.isEmpty()) { - q->connection()->database()->addGroupSessionIndexRecord(q->id(), senderSession->sessionId(), index, eventId, timestamp.toMSecsSinceEpoch()); + q->connection()->database()->addGroupSessionIndexRecord( + q->id(), senderSession->sessionId(), index, eventId, + timestamp.toMSecsSinceEpoch()); } else { - if ((eventId != recordEventId) || (ts != timestamp.toMSecsSinceEpoch())) { + if ((eventId != recordEventId) + || (ts != timestamp.toMSecsSinceEpoch())) { qCWarning(E2EE) << "Detected a replay attack on event" << eventId; return {}; } -- cgit v1.2.3 From 3eb7ad8b0a1ac0f6f9cda679108937a01268f184 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 16 May 2022 20:46:34 +0200 Subject: Save and load outgoing megolm session --- lib/connection.cpp | 11 ++++++++++- lib/connection.h | 5 +++++ lib/database.cpp | 41 +++++++++++++++++++++++++++++++++++++++- lib/database.h | 3 +++ lib/e2ee/qolmoutboundsession.cpp | 22 ++++++++++++++++++++- lib/e2ee/qolmoutboundsession.h | 9 +++++++++ lib/room.cpp | 23 +++++++++++----------- 7 files changed, 99 insertions(+), 15 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index a66a4168..b11ec731 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2247,7 +2247,6 @@ bool Connection::hasOlmSession(User* user, const QString& deviceId) const QPair Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) { //TODO be smarter about choosing a session; see e2ee impl guide - //TODO create session? const auto& curveKey = curveKeyForUserDevice(user->id(), device); QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); auto result = d->olmSessions[curveKey][0]->encrypt(message); @@ -2266,4 +2265,14 @@ void Connection::createOlmSession(const QString& theirIdentityKey, const QString d->olmSessions[theirIdentityKey].push_back(std::move(std::get>(session))); } +QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(Room* room) +{ + return d->database->loadCurrentOutboundMegolmSession(room->id(), d->picklingMode); +} + +void Connection::saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data) +{ + d->database->saveCurrentOutboundMegolmSession(room->id(), d->picklingMode, data); +} + #endif diff --git a/lib/connection.h b/lib/connection.h index afa4a657..8bed55da 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -25,6 +25,7 @@ #ifdef Quotient_E2EE_ENABLED #include "e2ee/e2ee.h" #include "e2ee/qolmmessage.h" +#include "e2ee/qolmoutboundsession.h" #endif Q_DECLARE_METATYPE(Quotient::GetLoginFlowsJob::LoginFlow) @@ -324,6 +325,10 @@ public: const QOlmInboundGroupSession& session); bool hasOlmSession(User* user, const QString& deviceId) const; + QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(Room* room); + void saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data); + + //This currently assumes that an olm session with (user, device) exists //TODO make this return an event? QPair olmEncryptMessage(User* user, const QString& device, const QByteArray& message); diff --git a/lib/database.cpp b/lib/database.cpp index e2e7acc9..8cb3a9d1 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -86,7 +86,7 @@ void Database::migrateTo1() execute(QStringLiteral("CREATE TABLE accounts (pickle TEXT);")); execute(QStringLiteral("CREATE TABLE olm_sessions (senderKey TEXT, sessionId TEXT, pickle TEXT);")); execute(QStringLiteral("CREATE TABLE inbound_megolm_sessions (roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); - execute(QStringLiteral("CREATE TABLE outbound_megolm_sessions (roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE outbound_megolm_sessions (roomId TEXT, sessionId TEXT, pickle TEXT, creationTime TEXT, messageCount INTEGER);")); execute(QStringLiteral("CREATE TABLE group_session_record_index (roomId TEXT, sessionId TEXT, i INTEGER, eventId TEXT, ts INTEGER);")); execute(QStringLiteral("CREATE TABLE tracked_users (matrixId TEXT);")); execute(QStringLiteral("CREATE TABLE outdated_users (matrixId TEXT);")); @@ -292,3 +292,42 @@ void Database::setOlmSessionLastReceived(const QString& sessionId, const QDateTi execute(query); commit(); } + +void Database::saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& session) +{ + const auto pickle = session->pickle(picklingMode); + if (std::holds_alternative(pickle)) { + auto deleteQuery = prepareQuery(QStringLiteral("DELETE FROM outbound_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId;")); + deleteQuery.bindValue(":roomId", roomId); + deleteQuery.bindValue(":sessionId", session->sessionId()); + + auto insertQuery = prepareQuery(QStringLiteral("INSERT INTO outbound_megolm_sessions(roomId, sessionId, pickle, creationTime, messageCount) VALUES(:roomId, :sessionId, :pickle, :creationTime, :messageCount);")); + insertQuery.bindValue(":roomId", roomId); + insertQuery.bindValue(":sessionId", session->sessionId()); + insertQuery.bindValue(":pickle", std::get(pickle)); + insertQuery.bindValue(":creationTime", session->creationTime()); + insertQuery.bindValue(":messageCount", session->messageCount()); + + transaction(); + execute(deleteQuery); + execute(insertQuery); + commit(); + } +} + +QOlmOutboundGroupSessionPtr Database::loadCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode) +{ + auto query = prepareQuery(QStringLiteral("SELECT * FROM outbound_megolm_sessions WHERE roomId=:roomId ORDER BY creationTime DESC;")); + query.bindValue(":roomId", roomId); + execute(query); + if (query.next()) { + auto sessionResult = QOlmOutboundGroupSession::unpickle(query.value("pickle").toByteArray(), picklingMode); + if (std::holds_alternative(sessionResult)) { + auto session = std::move(std::get(sessionResult)); + session->setCreationTime(query.value("creationTime").toDateTime()); + session->setMessageCount(query.value("messageCount").toInt()); + return session; + } + } + return nullptr; +} diff --git a/lib/database.h b/lib/database.h index 08fe49f3..751ebd1d 100644 --- a/lib/database.h +++ b/lib/database.h @@ -8,6 +8,7 @@ #include #include "e2ee/e2ee.h" + namespace Quotient { class QUOTIENT_API Database : public QObject { @@ -34,6 +35,8 @@ public: std::pair groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index); void clearRoomData(const QString& roomId); void setOlmSessionLastReceived(const QString& sessionId, const QDateTime& timestamp); + QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode); + void saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& data); private: void migrateTo1(); diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 96bad344..10b0c4de 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -66,7 +66,7 @@ QOlmExpected QOlmOutboundGroupSession::unpickle(QBy 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()); + pickledBuf.data(), pickledBuf.length()); if (error == olm_error()) { return lastError(olmOutboundGroupSession); } @@ -123,3 +123,23 @@ QOlmExpected QOlmOutboundGroupSession::sessionKey() const } return keyBuffer; } + +int QOlmOutboundGroupSession::messageCount() const +{ + return m_messageCount; +} + +void QOlmOutboundGroupSession::setMessageCount(int messageCount) +{ + m_messageCount = messageCount; +} + +QDateTime QOlmOutboundGroupSession::creationTime() const +{ + return m_creationTime; +} + +void QOlmOutboundGroupSession::setCreationTime(const QDateTime& creationTime) +{ + m_creationTime = creationTime; +} diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 8058bbb1..56b25974 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -26,6 +26,7 @@ public: //! pickling a `QOlmOutboundGroupSession`. static QOlmExpected unpickle( QByteArray& pickled, const PicklingMode& mode); + //! Encrypts a plaintext message using the session. QOlmExpected encrypt(const QString& plaintext); @@ -44,8 +45,16 @@ public: //! ratchet key that will be used for the next message. QOlmExpected sessionKey() const; QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); + + int messageCount() const; + void setMessageCount(int messageCount); + + QDateTime creationTime() const; + void setCreationTime(const QDateTime& creationTime); private: OlmOutboundGroupSession *m_groupSession; + int m_messageCount = 0; + QDateTime m_creationTime = QDateTime::currentDateTime(); }; } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index e8b63235..9e2bd7dd 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -344,7 +344,7 @@ public: int currentMegolmSessionMessageCount = 0; //TODO save this to database unsigned long long currentMegolmSessionCreationTimestamp = 0; - std::unique_ptr currentOutboundMegolmSession = nullptr; + QOlmOutboundGroupSessionPtr currentOutboundMegolmSession = nullptr; bool addInboundGroupSession(QString sessionId, QByteArray sessionKey, const QString& senderId, @@ -415,7 +415,7 @@ public: if (!q->usesEncryption()) { return false; } - return currentMegolmSessionMessageCount >= rotationMessageCount() || (currentMegolmSessionCreationTimestamp + rotationInterval()) < QDateTime::currentMSecsSinceEpoch(); + return currentOutboundMegolmSession->messageCount() >= rotationMessageCount() || currentOutboundMegolmSession->creationTime().addMSecs(rotationInterval()) < QDateTime::currentDateTime(); } bool hasValidMegolmSession() const @@ -446,9 +446,7 @@ public: void createMegolmSession() { qCDebug(E2EE) << "Creating new outbound megolm session for room " << q->id(); currentOutboundMegolmSession = QOlmOutboundGroupSession::create(); - currentMegolmSessionMessageCount = 0; - currentMegolmSessionCreationTimestamp = QDateTime::currentMSecsSinceEpoch(); - //TODO store megolm session to database + connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); } std::unique_ptr payloadForUserDevice(User* user, const QString& device, const QByteArray& sessionId, const QByteArray& sessionKey) @@ -476,7 +474,7 @@ public: void sendRoomKeyToDevices(const QByteArray& sessionId, const QByteArray& sessionKey) { - qWarning() << "Sending room key to devices" << sessionId, sessionKey.toHex(); + qCDebug(E2EE) << "Sending room key to devices" << sessionId, sessionKey.toHex(); QHash> hash; for (const auto& user : q->users()) { QHash u; @@ -493,7 +491,7 @@ public: auto job = connection->callApi(hash); connect(job, &BaseJob::success, q, [job, this, sessionId, sessionKey](){ Connection::UsersToDevicesToEvents usersToDevicesToEvents; - auto data = job->jsonData(); + const auto data = job->jsonData(); for(const auto &user : q->users()) { for(const auto &device : connection->devicesForUser(user)) { const auto recipientCurveKey = connection->curveKeyForUserDevice(user->id(), device); @@ -527,8 +525,6 @@ public: }); } - //TODO load outbound megolm sessions from database - void sendMegolmSession() { // Save the session to this device const auto sessionId = currentOutboundMegolmSession->sessionId(); @@ -577,14 +573,16 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) } }); d->groupSessions = connection->loadRoomMegolmSessions(this); - //TODO load outbound session + d->currentOutboundMegolmSession = connection->loadCurrentOutboundMegolmSession(this); + if (d->shouldRotateMegolmSession()) { + d->currentOutboundMegolmSession = nullptr; + } connect(this, &Room::userRemoved, this, [this](){ if (!usesEncryption()) { return; } d->currentOutboundMegolmSession = nullptr; qCDebug(E2EE) << "Invalidating current megolm session because user left"; - //TODO save old session probably }); @@ -2074,6 +2072,8 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) //TODO check if we increment the sent message count event->setRoomId(id); const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(event->fullJson()).toJson()); + currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); + connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); if(std::holds_alternative(encrypted)) { //TODO something qWarning(E2EE) << "Error encrypting message" << std::get(encrypted); @@ -2084,7 +2084,6 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) encryptedEvent->setRoomId(id); encryptedEvent->setSender(connection->userId()); event->setTransactionId(encryptedEvent->transactionId()); - currentMegolmSessionMessageCount++; // We show the unencrypted event locally while pending. The echo check will throw the encrypted version out addAsPending(std::move(event)); return doSendEvent(encryptedEvent, true); -- cgit v1.2.3 From 89d8f6c44f86a27df28b1d89a80564fb0d4d89fc Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 16 May 2022 21:26:14 +0200 Subject: Fix build failures --- lib/connection.cpp | 12 ++++++------ lib/database.cpp | 10 +++++----- lib/e2ee/qolmoutboundsession.cpp | 2 +- lib/e2ee/qolmoutboundsession.h | 2 +- lib/room.cpp | 16 ++++++++-------- 5 files changed, 21 insertions(+), 21 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 82046d53..a5615f64 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2251,8 +2251,8 @@ QPair Connection::olmEncryptMessage(User* user, c QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); auto result = d->olmSessions[curveKey][0]->encrypt(message); auto pickle = d->olmSessions[curveKey][0]->pickle(picklingMode()); - if (std::holds_alternative(pickle)) { - database()->updateOlmSession(curveKey, d->olmSessions[curveKey][0]->sessionId(), std::get(pickle)); + if (pickle) { + database()->updateOlmSession(curveKey, d->olmSessions[curveKey][0]->sessionId(), *pickle); } else { qCWarning(E2EE) << "Failed to pickle olm session."; } @@ -2262,12 +2262,12 @@ QPair Connection::olmEncryptMessage(User* user, c void Connection::createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey) { auto session = QOlmSession::createOutboundSession(olmAccount(), theirIdentityKey, theirOneTimeKey); - if (std::holds_alternative(session)) { - qCWarning(E2EE) << "Failed to create olm session for " << theirIdentityKey << std::get(session); + if (!session) { + qCWarning(E2EE) << "Failed to create olm session for " << theirIdentityKey << session.error(); return; } - d->saveSession(std::get>(session), theirIdentityKey); - d->olmSessions[theirIdentityKey].push_back(std::move(std::get>(session))); + d->saveSession(**session, theirIdentityKey); + d->olmSessions[theirIdentityKey].push_back(std::move(*session)); } QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(Room* room) diff --git a/lib/database.cpp b/lib/database.cpp index d2d33006..87275e1f 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -132,7 +132,7 @@ void Database::migrateTo3() commit(); } -void Database::migrateTo3() +void Database::migrateTo4() { qCDebug(DATABASE) << "Migrating database to version 4"; transaction(); @@ -313,7 +313,7 @@ void Database::setOlmSessionLastReceived(const QString& sessionId, const QDateTi void Database::saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& session) { const auto pickle = session->pickle(picklingMode); - if (std::holds_alternative(pickle)) { + if (pickle) { auto deleteQuery = prepareQuery(QStringLiteral("DELETE FROM outbound_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId;")); deleteQuery.bindValue(":roomId", roomId); deleteQuery.bindValue(":sessionId", session->sessionId()); @@ -321,7 +321,7 @@ void Database::saveCurrentOutboundMegolmSession(const QString& roomId, const Pic auto insertQuery = prepareQuery(QStringLiteral("INSERT INTO outbound_megolm_sessions(roomId, sessionId, pickle, creationTime, messageCount) VALUES(:roomId, :sessionId, :pickle, :creationTime, :messageCount);")); insertQuery.bindValue(":roomId", roomId); insertQuery.bindValue(":sessionId", session->sessionId()); - insertQuery.bindValue(":pickle", std::get(pickle)); + insertQuery.bindValue(":pickle", pickle.value()); insertQuery.bindValue(":creationTime", session->creationTime()); insertQuery.bindValue(":messageCount", session->messageCount()); @@ -339,8 +339,8 @@ QOlmOutboundGroupSessionPtr Database::loadCurrentOutboundMegolmSession(const QSt execute(query); if (query.next()) { auto sessionResult = QOlmOutboundGroupSession::unpickle(query.value("pickle").toByteArray(), picklingMode); - if (std::holds_alternative(sessionResult)) { - auto session = std::move(std::get(sessionResult)); + if (sessionResult) { + auto session = std::move(*sessionResult); session->setCreationTime(query.value("creationTime").toDateTime()); session->setMessageCount(query.value("messageCount").toInt()); return session; diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 10b0c4de..76188d08 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -60,7 +60,7 @@ QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mo return pickledBuf; } -QOlmExpected QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +QOlmExpected QOlmOutboundGroupSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) { QByteArray pickledBuf = pickled; auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 56b25974..c20613d3 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -25,7 +25,7 @@ public: //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. static QOlmExpected unpickle( - QByteArray& pickled, const PicklingMode& mode); + const QByteArray& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. QOlmExpected encrypt(const QString& plaintext); diff --git a/lib/room.cpp b/lib/room.cpp index 35de59ed..d77bf9ef 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -448,11 +448,11 @@ public: connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); const auto sessionKey = currentOutboundMegolmSession->sessionKey(); - if(std::holds_alternative(sessionKey)) { + if(!sessionKey) { qCWarning(E2EE) << "Failed to load key for new megolm session"; return; } - addInboundGroupSession(currentOutboundMegolmSession->sessionId(), std::get(sessionKey), q->localUser()->id(), "SELF"_ls); + addInboundGroupSession(currentOutboundMegolmSession->sessionId(), *sessionKey, q->localUser()->id(), "SELF"_ls); } std::unique_ptr payloadForUserDevice(User* user, const QString& device, const QByteArray& sessionId, const QByteArray& sessionKey) @@ -526,7 +526,7 @@ public: signedData.remove("unsigned"); signedData.remove("signatures"); auto signatureMatch = QOlmUtility().ed25519Verify(connection->edKeyForUserDevice(user->id(), device).toLatin1(), QJsonDocument(signedData).toJson(QJsonDocument::Compact), signature); - if (std::holds_alternative(signatureMatch)) { + if (!signatureMatch) { qCWarning(E2EE) << "Failed to verify one-time-key signature for" << user->id() << device << ". Skipping this device."; continue; } else { @@ -547,11 +547,11 @@ public: // Save the session to this device const auto sessionId = currentOutboundMegolmSession->sessionId(); const auto _sessionKey = currentOutboundMegolmSession->sessionKey(); - if(std::holds_alternative(_sessionKey)) { + if(!_sessionKey) { qCWarning(E2EE) << "Error loading session key"; return; } - const auto sessionKey = std::get(_sessionKey); + const auto sessionKey = *_sessionKey; const auto senderKey = q->connection()->olmAccount()->identityKeys().curve25519; // Send the session to other people @@ -2101,11 +2101,11 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(pEvent->fullJson()).toJson()); currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); - if(std::holds_alternative(encrypted)) { - qWarning(E2EE) << "Error encrypting message" << std::get(encrypted); + if(!encrypted) { + qWarning(E2EE) << "Error encrypting message" << encrypted.error(); return {}; } - auto encryptedEvent = new EncryptedEvent(std::get(encrypted), q->connection()->olmAccount()->identityKeys().curve25519, q->connection()->deviceId(), currentOutboundMegolmSession->sessionId()); + auto encryptedEvent = new EncryptedEvent(*encrypted, q->connection()->olmAccount()->identityKeys().curve25519, q->connection()->deviceId(), currentOutboundMegolmSession->sessionId()); encryptedEvent->setTransactionId(connection->generateTxnId()); encryptedEvent->setRoomId(id); encryptedEvent->setSender(connection->userId()); -- cgit v1.2.3 From 64797165f04a16d290dd27c2f962060b40f85be3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 25 May 2022 22:48:53 +0200 Subject: Refactor creation of Megolm sessions in Room Notably, replace a multi-level hash map with QMultiHash and factor out Room::P::createOlmSession(). --- lib/connection.cpp | 14 ++-- lib/connection.h | 8 +- lib/database.cpp | 21 ++++-- lib/database.h | 42 ++++++++--- lib/e2ee/qolmoutboundsession.cpp | 4 +- lib/e2ee/qolmoutboundsession.h | 4 +- lib/room.cpp | 156 +++++++++++++++++++++++---------------- 7 files changed, 151 insertions(+), 98 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 8fd2d6cf..1193eb75 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2187,7 +2187,7 @@ QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) return decrypted ? decrypted->fullJson() : QJsonObject(); } -Database* Connection::database() +Database* Connection::database() const { return d->database; } @@ -2271,14 +2271,18 @@ void Connection::createOlmSession(const QString& theirIdentityKey, d->olmSessions[theirIdentityKey].push_back(std::move(*session)); } -QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(Room* room) +QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession( + const QString& roomId) const { - return d->database->loadCurrentOutboundMegolmSession(room->id(), d->picklingMode); + return d->database->loadCurrentOutboundMegolmSession(roomId, + d->picklingMode); } -void Connection::saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data) +void Connection::saveCurrentOutboundMegolmSession( + const QString& roomId, const QOlmOutboundGroupSession& session) const { - d->database->saveCurrentOutboundMegolmSession(room->id(), d->picklingMode, data); + d->database->saveCurrentOutboundMegolmSession(roomId, d->picklingMode, + session); } #endif diff --git a/lib/connection.h b/lib/connection.h index 43e285c1..a2824744 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -318,16 +318,16 @@ public: bool isLoggedIn() const; #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; - Database* database(); + Database* database() const; UnorderedMap loadRoomMegolmSessions( const Room* room); void saveMegolmSession(const Room* room, const QOlmInboundGroupSession& session); bool hasOlmSession(const QString& user, const QString& deviceId) const; - QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(Room* room); - void saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data); - + QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession( + const QString& roomId) const; + void saveCurrentOutboundMegolmSession(const QString& roomId, const QOlmOutboundGroupSession &session) const; //This assumes that an olm session with (user, device) exists std::pair olmEncryptMessage( diff --git a/lib/database.cpp b/lib/database.cpp index 0119b35c..193ff54e 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -307,20 +307,22 @@ void Database::setOlmSessionLastReceived(const QString& sessionId, const QDateTi commit(); } -void Database::saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& session) +void Database::saveCurrentOutboundMegolmSession( + const QString& roomId, const PicklingMode& picklingMode, + const QOlmOutboundGroupSession& session) { - const auto pickle = session->pickle(picklingMode); + const auto pickle = session.pickle(picklingMode); if (pickle) { auto deleteQuery = prepareQuery(QStringLiteral("DELETE FROM outbound_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId;")); deleteQuery.bindValue(":roomId", roomId); - deleteQuery.bindValue(":sessionId", session->sessionId()); + deleteQuery.bindValue(":sessionId", session.sessionId()); auto insertQuery = prepareQuery(QStringLiteral("INSERT INTO outbound_megolm_sessions(roomId, sessionId, pickle, creationTime, messageCount) VALUES(:roomId, :sessionId, :pickle, :creationTime, :messageCount);")); insertQuery.bindValue(":roomId", roomId); - insertQuery.bindValue(":sessionId", session->sessionId()); + insertQuery.bindValue(":sessionId", session.sessionId()); insertQuery.bindValue(":pickle", pickle.value()); - insertQuery.bindValue(":creationTime", session->creationTime()); - insertQuery.bindValue(":messageCount", session->messageCount()); + insertQuery.bindValue(":creationTime", session.creationTime()); + insertQuery.bindValue(":messageCount", session.messageCount()); transaction(); execute(deleteQuery); @@ -362,7 +364,9 @@ void Database::setDevicesReceivedKey(const QString& roomId, const QVector Database::devicesWithoutKey(const QString& roomId, QHash& devices, const QString &sessionId) +QMultiHash Database::devicesWithoutKey( + const QString& roomId, QMultiHash devices, + const QString& sessionId) { auto query = prepareQuery(QStringLiteral("SELECT userId, deviceId FROM sent_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId")); query.bindValue(":roomId", roomId); @@ -371,7 +375,8 @@ QHash Database::devicesWithoutKey(const QString& roomId, Q execute(query); commit(); while (query.next()) { - devices[query.value("userId").toString()].removeAll(query.value("deviceId").toString()); + devices.remove(query.value("userId").toString(), + query.value("deviceId").toString()); } return devices; } diff --git a/lib/database.h b/lib/database.h index 45348c8d..4091d61b 100644 --- a/lib/database.h +++ b/lib/database.h @@ -32,22 +32,40 @@ public: QByteArray accountPickle(); void setAccountPickle(const QByteArray &pickle); void clear(); - void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle, const QDateTime& timestamp); - UnorderedMap> loadOlmSessions(const PicklingMode& picklingMode); - UnorderedMap loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode); - void saveMegolmSession(const QString& roomId, const QString& sessionId, const QByteArray& pickle, const QString& senderId, const QString& olmSessionId); - void addGroupSessionIndexRecord(const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts); - std::pair groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index); + void saveOlmSession(const QString& senderKey, const QString& sessionId, + const QByteArray& pickle, const QDateTime& timestamp); + UnorderedMap> loadOlmSessions( + const PicklingMode& picklingMode); + UnorderedMap loadMegolmSessions( + const QString& roomId, const PicklingMode& picklingMode); + void saveMegolmSession(const QString& roomId, const QString& sessionId, + const QByteArray& pickle, const QString& senderId, + const QString& olmSessionId); + void addGroupSessionIndexRecord(const QString& roomId, + const QString& sessionId, uint32_t index, + const QString& eventId, qint64 ts); + std::pair groupSessionIndexRecord(const QString& roomId, + const QString& sessionId, + qint64 index); void clearRoomData(const QString& roomId); - void setOlmSessionLastReceived(const QString& sessionId, const QDateTime& timestamp); - QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode); - void saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& data); - void updateOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle); + void setOlmSessionLastReceived(const QString& sessionId, + const QDateTime& timestamp); + QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession( + const QString& roomId, const PicklingMode& picklingMode); + void saveCurrentOutboundMegolmSession( + const QString& roomId, const PicklingMode& picklingMode, + const QOlmOutboundGroupSession& session); + void updateOlmSession(const QString& senderKey, const QString& sessionId, + const QByteArray& pickle); // Returns a map UserId -> [DeviceId] that have not received key yet - QHash devicesWithoutKey(const QString& roomId, QHash& devices, const QString &sessionId); + QMultiHash devicesWithoutKey(const QString& roomId, QMultiHash devices, + const QString& sessionId); // 'devices' contains tuples {userId, deviceId, curveKey} - void setDevicesReceivedKey(const QString& roomId, const QVector>& devices, const QString& sessionId, int index); + void setDevicesReceivedKey( + const QString& roomId, + const QVector>& devices, + const QString& sessionId, int index); private: void migrateTo1(); diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 76188d08..a2eff2c8 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -44,7 +44,7 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() return std::make_unique(olmOutboundGroupSession); } -QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mode) +QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mode) const { QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); QByteArray key = toKey(mode); @@ -79,7 +79,7 @@ QOlmExpected QOlmOutboundGroupSession::unpickle(con return std::make_unique(olmOutboundGroupSession); } -QOlmExpected QOlmOutboundGroupSession::encrypt(const QString &plaintext) +QOlmExpected QOlmOutboundGroupSession::encrypt(const QString &plaintext) const { QByteArray plaintextBuf = plaintext.toUtf8(); const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index c20613d3..9a82d22a 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -21,14 +21,14 @@ public: //! Throw OlmError on errors static QOlmOutboundGroupSessionPtr create(); //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. - QOlmExpected pickle(const PicklingMode &mode); + QOlmExpected pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. static QOlmExpected unpickle( const QByteArray& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. - QOlmExpected encrypt(const QString& plaintext); + QOlmExpected encrypt(const QString& plaintext) const; //! Get the current message index for this session. //! diff --git a/lib/room.cpp b/lib/room.cpp index 26fe80e3..07d03467 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -443,9 +443,11 @@ public: return q->getCurrentState()->rotationPeriodMsgs(); } void createMegolmSession() { - qCDebug(E2EE) << "Creating new outbound megolm session for room " << q->id(); + qCDebug(E2EE) << "Creating new outbound megolm session for room " + << q->objectName(); currentOutboundMegolmSession = QOlmOutboundGroupSession::create(); - connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); + connection->saveCurrentOutboundMegolmSession( + id, *currentOutboundMegolmSession); const auto sessionKey = currentOutboundMegolmSession->sessionKey(); if(!sessionKey) { @@ -477,90 +479,113 @@ public: return makeEvent(encrypted, connection->olmAccount()->identityKeys().curve25519); } - QHash getDevicesWithoutKey() const + QMultiHash getDevicesWithoutKey() const { - QHash devices; - for (const auto& user : q->users()) { - devices[user->id()] = q->connection()->devicesForUser(user->id()); + QMultiHash devices; + for (const auto& user : q->users()) + for (const auto& deviceId : connection->devicesForUser(user->id())) + devices.insert(user->id(), deviceId); + + return connection->database()->devicesWithoutKey( + id, devices, currentOutboundMegolmSession->sessionId()); + } + + bool createOlmSession(const QString& user, const QString& device, + const QJsonObject& oneTimeKeyObject) const + { + static QOlmUtility verifier; + qDebug(E2EE) << "Creating a new session for" << user << device; + if (oneTimeKeyObject.isEmpty()) { + qWarning(E2EE) << "No one time key for" << user << device; + return false; } - return q->connection()->database()->devicesWithoutKey(q->id(), devices, QString(currentOutboundMegolmSession->sessionId())); + const auto oneTimeKeyForId = *oneTimeKeyObject.constBegin(); + const auto signature = + oneTimeKeyForId["signatures"][user]["ed25519:"_ls % device] + .toString() + .toLatin1(); + auto signedObject = oneTimeKeyForId.toObject(); + signedObject.remove("unsigned"_ls); + signedObject.remove("signatures"_ls); + const auto signedData = + QJsonDocument(signedObject).toJson(QJsonDocument::Compact); + if (!verifier.ed25519Verify( + connection->edKeyForUserDevice(user, device).toLatin1(), + signedData, signature)) { + qWarning(E2EE) << "Failed to verify one-time-key signature for" + << user << device << ". Skipping this device."; + return false; + } + const auto recipientCurveKey = + connection->curveKeyForUserDevice(user, device); + connection->createOlmSession(recipientCurveKey, + oneTimeKeyForId["key"].toString()); + return true; } - void sendRoomKeyToDevices(const QByteArray& sessionId, const QByteArray& sessionKey, const QHash devices, int index) + void sendRoomKeyToDevices(const QByteArray& sessionId, + const QByteArray& sessionKey, + const QMultiHash& devices, + int index) { - qCDebug(E2EE) << "Sending room key to devices" << sessionId, sessionKey.toHex(); + qDebug(E2EE) << "Sending room key to devices:" << sessionId + << sessionKey.toHex(); QHash> hash; - for (const auto& user : devices.keys()) { - QHash u; - for(const auto &device : devices[user]) { - if (!connection->hasOlmSession(user, device)) { - u[device] = "signed_curve25519"_ls; - qCDebug(E2EE) << "Adding" << user << device << "to keys to claim"; - } + for (const auto& [userId, deviceId] : asKeyValueRange(devices)) + if (!connection->hasOlmSession(userId, deviceId)) { + hash[userId].insert(deviceId, "signed_curve25519"_ls); + qDebug(E2EE) + << "Adding" << userId << deviceId << "to keys to claim"; } - if (!u.isEmpty()) { - hash[user] = u; - } - } - if (hash.isEmpty()) { + + if (hash.isEmpty()) return; - } + auto job = connection->callApi(hash); - connect(job, &BaseJob::success, q, [job, this, sessionId, sessionKey, devices, index](){ + connect(job, &BaseJob::success, q, + [job, this, sessionId, sessionKey, devices, index] { Connection::UsersToDevicesToEvents usersToDevicesToEvents; const auto data = job->jsonData(); - for(const auto &user : devices.keys()) { - for(const auto &device : devices[user]) { - const auto recipientCurveKey = connection->curveKeyForUserDevice(user, device); - if (!connection->hasOlmSession(user, device)) { - qCDebug(E2EE) << "Creating a new session for" << user << device; - if(data["one_time_keys"][user][device].toObject().isEmpty()) { - qWarning() << "No one time key for" << user << device; - continue; - } - const auto keyId = data["one_time_keys"][user][device].toObject().keys()[0]; - const auto oneTimeKey = data["one_time_keys"][user][device][keyId]["key"].toString(); - const auto signature = data["one_time_keys"][user][device][keyId]["signatures"][user][QStringLiteral("ed25519:") + device].toString().toLatin1(); - auto signedData = data["one_time_keys"][user][device][keyId].toObject(); - signedData.remove("unsigned"); - signedData.remove("signatures"); - auto signatureMatch = QOlmUtility().ed25519Verify(connection->edKeyForUserDevice(user, device).toLatin1(), QJsonDocument(signedData).toJson(QJsonDocument::Compact), signature); - if (!signatureMatch) { - qCWarning(E2EE) << "Failed to verify one-time-key signature for" << user << device << ". Skipping this device."; - continue; - } else { - } - connection->createOlmSession(recipientCurveKey, oneTimeKey); - } - usersToDevicesToEvents[user][device] = payloadForUserDevice(user, device, sessionId, sessionKey); - } + for (const auto& [user, device] : asKeyValueRange(devices)) { + if (!connection->hasOlmSession(user, device) + && !createOlmSession( + user, device, + data["one_time_keys"][user][device].toObject())) + continue; + + usersToDevicesToEvents[user][device] = + payloadForUserDevice(user, device, sessionId, + sessionKey); } if (!usersToDevicesToEvents.empty()) { - connection->sendToDevices("m.room.encrypted", usersToDevicesToEvents); + connection->sendToDevices("m.room.encrypted"_ls, + usersToDevicesToEvents); QVector> receivedDevices; - for (const auto& user : devices.keys()) { - for (const auto& device : devices[user]) { - receivedDevices += {user, device, q->connection()->curveKeyForUserDevice(user, device) }; - } - } - connection->database()->setDevicesReceivedKey(q->id(), receivedDevices, sessionId, index); + receivedDevices.reserve(devices.size()); + for (const auto& [user, device] : asKeyValueRange(devices)) + receivedDevices.push_back( + { user, device, + connection->curveKeyForUserDevice(user, device) }); + + connection->database()->setDevicesReceivedKey(id, + receivedDevices, + sessionId, index); } }); } - void sendMegolmSession(const QHash& devices) { + void sendMegolmSession(const QMultiHash& devices) { // Save the session to this device const auto sessionId = currentOutboundMegolmSession->sessionId(); - const auto _sessionKey = currentOutboundMegolmSession->sessionKey(); - if(!_sessionKey) { + const auto sessionKey = currentOutboundMegolmSession->sessionKey(); + if(!sessionKey) { qCWarning(E2EE) << "Error loading session key"; return; } - const auto sessionKey = *_sessionKey; - const auto senderKey = q->connection()->olmAccount()->identityKeys().curve25519; // Send the session to other people - sendRoomKeyToDevices(sessionId, sessionKey, devices, currentOutboundMegolmSession->sessionMessageIndex()); + sendRoomKeyToDevices(sessionId, *sessionKey, devices, + currentOutboundMegolmSession->sessionMessageIndex()); } #endif // Quotient_E2EE_ENABLED @@ -592,7 +617,8 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) } }); d->groupSessions = connection->loadRoomMegolmSessions(this); - d->currentOutboundMegolmSession = connection->loadCurrentOutboundMegolmSession(this); + d->currentOutboundMegolmSession = + connection->loadCurrentOutboundMegolmSession(this->id()); if (d->shouldRotateMegolmSession()) { d->currentOutboundMegolmSession = nullptr; } @@ -2101,12 +2127,12 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) if (!hasValidMegolmSession() || shouldRotateMegolmSession()) { createMegolmSession(); } - const auto devicesWithoutKey = getDevicesWithoutKey(); - sendMegolmSession(devicesWithoutKey); + sendMegolmSession(getDevicesWithoutKey()); const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(pEvent->fullJson()).toJson()); currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); - connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); + connection->saveCurrentOutboundMegolmSession( + id, *currentOutboundMegolmSession); if(!encrypted) { qWarning(E2EE) << "Error encrypting message" << encrypted.error(); return {}; -- cgit v1.2.3 From 0f8335a32debc4c61d9fc9875c79c0ba6ba05357 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 27 May 2022 19:09:26 +0200 Subject: Move some Meg/Olm session logic from Room::Private to Connection::Private Functions (Room::Private::)createOlmSession, payloadForUserDevice and sendRoomKeyToDevices don't have a lot to do with the given Room object but deal with quite a few things stored in Connection. This commit moves them to Connection::Private, exposing sendSessionKeyToDevices (the new name for sendRoomKeyToDevices) in Connection so that Room could call it from Room::P::sendMegolmSession(). While moving these over, a few additional things were adjusted: - more functions marked as const - a few functions could be moved now from Connection to Connection::Private - false slots in Connection (such as picklingMode) are moved out of the slots block - keys.yml in Matrix CS API definitions has been adjusted to match the real structure of `/claim` response (see quotient-im/matrix-spec repo); csapi/keys.h has been regenerated accordingly. --- autotests/testolmaccount.cpp | 80 +++++++-------- lib/connection.cpp | 227 ++++++++++++++++++++++++++++++++++--------- lib/connection.h | 34 +++---- lib/csapi/keys.h | 4 +- lib/e2ee/qolmsession.cpp | 5 +- lib/e2ee/qolmsession.h | 2 +- lib/events/roomkeyevent.h | 4 +- lib/room.cpp | 113 +-------------------- 8 files changed, 249 insertions(+), 220 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index b509d12f..3fb8ac24 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -378,47 +378,47 @@ void TestOlmAccount::claimKeys() // Alice retrieves bob's keys & claims one signed one-time key. QHash deviceKeys; deviceKeys[bob->userId()] = QStringList(); - auto job = alice->callApi(deviceKeys); - connect(job, &BaseJob::result, this, [bob, alice, job, this] { - const auto& bobDevices = job->deviceKeys().value(bob->userId()); - QVERIFY(!bobDevices.empty()); - - // Retrieve the identity key for the current device. - const auto& bobEd25519 = - bobDevices.value(bob->deviceId()).keys["ed25519:" + bob->deviceId()]; - - const auto currentDevice = bobDevices[bob->deviceId()]; - - // Verify signature. - QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(), - bob->userId())); - - QHash> oneTimeKeys; - oneTimeKeys[bob->userId()] = QHash(); - oneTimeKeys[bob->userId()][bob->deviceId()] = SignedCurve25519Key; - - auto job = alice->callApi(oneTimeKeys); - connect(job, &BaseJob::result, this, [bob, bobEd25519, job] { - const auto userId = bob->userId(); - const auto deviceId = bob->deviceId(); - - // The device exists. - QCOMPARE(job->oneTimeKeys().size(), 1); - QCOMPARE(job->oneTimeKeys().value(userId).size(), 1); - - // The key is the one bob sent. - const auto& oneTimeKey = - job->oneTimeKeys().value(userId).value(deviceId); - QVERIFY(oneTimeKey.canConvert()); - - const auto varMap = oneTimeKey.toMap(); - QVERIFY(std::any_of(varMap.constKeyValueBegin(), - varMap.constKeyValueEnd(), [](const auto& kv) { - return kv.first.startsWith( - SignedCurve25519Key); - })); - }); + auto queryKeysJob = alice->callApi(deviceKeys); + QSignalSpy requestSpy2(queryKeysJob, &BaseJob::result); + QVERIFY(requestSpy2.wait(10000)); + + const auto& bobDevices = queryKeysJob->deviceKeys().value(bob->userId()); + QVERIFY(!bobDevices.empty()); + + const auto currentDevice = bobDevices[bob->deviceId()]; + + // Verify signature. + QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(), + bob->userId())); + // Retrieve the identity key for the current device. + const auto& bobEd25519 = + bobDevices.value(bob->deviceId()).keys["ed25519:" + bob->deviceId()]; + + QHash> oneTimeKeys; + oneTimeKeys[bob->userId()] = QHash(); + oneTimeKeys[bob->userId()][bob->deviceId()] = SignedCurve25519Key; + + auto claimKeysJob = alice->callApi(oneTimeKeys); + connect(claimKeysJob, &BaseJob::result, this, [bob, bobEd25519, claimKeysJob] { + const auto userId = bob->userId(); + const auto deviceId = bob->deviceId(); + + // The device exists. + QCOMPARE(claimKeysJob->oneTimeKeys().size(), 1); + QCOMPARE(claimKeysJob->oneTimeKeys().value(userId).size(), 1); + + // The key is the one bob sent. + const auto& oneTimeKeys = + claimKeysJob->oneTimeKeys().value(userId).value(deviceId); + for (auto it = oneTimeKeys.begin(); it != oneTimeKeys.end(); ++it) { + if (it.key().startsWith(SignedCurve25519Key) + && it.value().isObject()) + return; + } + QFAIL("The claimed one time key is not in /claim response"); }); + QSignalSpy completionSpy(claimKeysJob, &BaseJob::result); + QVERIFY(completionSpy.wait(10000)); } void TestOlmAccount::claimMultipleKeys() diff --git a/lib/connection.cpp b/lib/connection.cpp index 1193eb75..ab4a7dea 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -39,6 +39,7 @@ # include "e2ee/qolmaccount.h" # include "e2ee/qolminboundsession.h" # include "e2ee/qolmsession.h" +# include "e2ee/qolmutility.h" # include "e2ee/qolmutils.h" # if QT_VERSION_MAJOR >= 6 @@ -62,7 +63,6 @@ #include #include - using namespace Quotient; // This is very much Qt-specific; STL iterators don't have key() and value() @@ -210,11 +210,11 @@ public: #ifdef Quotient_E2EE_ENABLED void loadSessions() { - olmSessions = q->database()->loadOlmSessions(q->picklingMode()); + olmSessions = q->database()->loadOlmSessions(picklingMode); } - void saveSession(QOlmSession& session, const QString& senderKey) + void saveSession(const QOlmSession& session, const QString& senderKey) const { - if (auto pickleResult = session.pickle(q->picklingMode())) + if (auto pickleResult = session.pickle(picklingMode)) q->database()->saveOlmSession(senderKey, session.sessionId(), *pickleResult, QDateTime::currentDateTime()); @@ -364,9 +364,27 @@ public: #endif // Quotient_E2EE_ENABLED } #ifdef Quotient_E2EE_ENABLED + bool isKnownCurveKey(const QString& userId, const QString& curveKey) const; + void loadOutdatedUserDevices(); void saveDevicesList(); void loadDevicesList(); + + // This function assumes that an olm session with (user, device) exists + std::pair olmEncryptMessage( + const QString& userId, const QString& device, + const QByteArray& message) const; + bool createOlmSession(const QString& targetUserId, + const QString& targetDeviceId, + const QJsonObject& oneTimeKeyObject); + QString curveKeyForUserDevice(const QString& userId, + const QString& device) const; + QString edKeyForUserDevice(const QString& userId, + const QString& device) const; + std::unique_ptr makeEventForSessionKey( + const QString& roomId, const QString& targetUserId, + const QString& targetDeviceId, const QByteArray& sessionId, + const QByteArray& sessionKey) const; #endif }; @@ -935,20 +953,23 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { #ifdef Quotient_E2EE_ENABLED if (!toDeviceEvents.empty()) { - qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events"; + qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() + << "to-device events"; visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { - qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); + qCDebug(E2EE) << "Unsupported algorithm" << event.id() + << "for event" << event.algorithm(); return; } - if (q->isKnownCurveKey(event.senderId(), event.senderKey())) { + if (isKnownCurveKey(event.senderId(), event.senderKey())) { handleEncryptedToDeviceEvent(event); return; } trackedUsers += event.senderId(); outdatedUsers += event.senderId(); encryptionUpdateRequired = true; - pendingEncryptedEvents.push_back(std::make_unique(event.fullJson())); + pendingEncryptedEvents.push_back( + makeEvent(event.fullJson())); }); } #endif @@ -1316,9 +1337,8 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) return forgetJob; } -SendToDeviceJob* -Connection::sendToDevices(const QString& eventType, - const UsersToDevicesToEvents& eventsMap) +SendToDeviceJob* Connection::sendToDevices( + const QString& eventType, const UsersToDevicesToEvents& eventsMap) { QHash> json; json.reserve(int(eventsMap.size())); @@ -2063,7 +2083,7 @@ void Connection::Private::loadOutdatedUserDevices() saveDevicesList(); for(size_t i = 0; i < pendingEncryptedEvents.size();) { - if (q->isKnownCurveKey( + if (isKnownCurveKey( pendingEncryptedEvents[i]->fullJson()[SenderKeyL].toString(), pendingEncryptedEvents[i]->contentPart("sender_key"_ls))) { handleEncryptedToDeviceEvent(*pendingEncryptedEvents[i]); @@ -2193,13 +2213,13 @@ Database* Connection::database() const } UnorderedMap -Connection::loadRoomMegolmSessions(const Room* room) +Connection::loadRoomMegolmSessions(const Room* room) const { return database()->loadMegolmSessions(room->id(), picklingMode()); } void Connection::saveMegolmSession(const Room* room, - const QOlmInboundGroupSession& session) + const QOlmInboundGroupSession& session) const { database()->saveMegolmSession(room->id(), session.sessionId(), session.pickle(picklingMode()), @@ -2211,64 +2231,179 @@ QStringList Connection::devicesForUser(const QString& userId) const return d->deviceKeys[userId].keys(); } -QString Connection::curveKeyForUserDevice(const QString& userId, - const QString& device) const +QString Connection::Private::curveKeyForUserDevice(const QString& userId, + const QString& device) const { - return d->deviceKeys[userId][device].keys["curve25519:" % device]; + return deviceKeys[userId][device].keys["curve25519:" % device]; } -QString Connection::edKeyForUserDevice(const QString& userId, - const QString& device) const +QString Connection::Private::edKeyForUserDevice(const QString& userId, + const QString& device) const { - return d->deviceKeys[userId][device].keys["ed25519:" % device]; + return deviceKeys[userId][device].keys["ed25519:" % device]; } -bool Connection::isKnownCurveKey(const QString& userId, - const QString& curveKey) const +bool Connection::Private::isKnownCurveKey(const QString& userId, + const QString& curveKey) const { - auto query = database()->prepareQuery(QStringLiteral("SELECT * FROM tracked_devices WHERE matrixId=:matrixId AND curveKey=:curveKey")); + auto query = database->prepareQuery( + QStringLiteral("SELECT * FROM tracked_devices WHERE matrixId=:matrixId " + "AND curveKey=:curveKey")); query.bindValue(":matrixId", userId); query.bindValue(":curveKey", curveKey); - database()->execute(query); + database->execute(query); return query.next(); } -bool Connection::hasOlmSession(const QString& user, const QString& deviceId) const +bool Connection::hasOlmSession(const QString& user, + const QString& deviceId) const { - const auto& curveKey = curveKeyForUserDevice(user, deviceId); + const auto& curveKey = d->curveKeyForUserDevice(user, deviceId); return d->olmSessions.contains(curveKey) && !d->olmSessions[curveKey].empty(); } -std::pair Connection::olmEncryptMessage( - const QString& userId, const QString& device, const QByteArray& message) const +std::pair Connection::Private::olmEncryptMessage( + const QString& userId, const QString& device, + const QByteArray& message) const { const auto& curveKey = curveKeyForUserDevice(userId, device); - QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); - const auto result = d->olmSessions[curveKey][0]->encrypt(message); - if (const auto pickle = - d->olmSessions[curveKey][0]->pickle(picklingMode())) { - database()->updateOlmSession(curveKey, - d->olmSessions[curveKey][0]->sessionId(), - *pickle); + const auto& olmSession = olmSessions.at(curveKey).front(); + QOlmMessage::Type type = olmSession->encryptMessageType(); + const auto result = olmSession->encrypt(message); + if (const auto pickle = olmSession->pickle(picklingMode)) { + database->updateOlmSession(curveKey, olmSession->sessionId(), *pickle); } else { - qCWarning(E2EE) << "Failed to pickle olm session: " << pickle.error(); + qWarning(E2EE) << "Failed to pickle olm session: " << pickle.error(); } return { type, result.toCiphertext() }; } -void Connection::createOlmSession(const QString& theirIdentityKey, - const QString& theirOneTimeKey) const -{ - auto session = QOlmSession::createOutboundSession(olmAccount(), - theirIdentityKey, - theirOneTimeKey); +bool Connection::Private::createOlmSession(const QString& targetUserId, + const QString& targetDeviceId, + const QJsonObject& oneTimeKeyObject) +{ + static QOlmUtility verifier; + qDebug(E2EE) << "Creating a new session for" << targetUserId + << targetDeviceId; + if (oneTimeKeyObject.isEmpty()) { + qWarning(E2EE) << "No one time key for" << targetUserId + << targetDeviceId; + return false; + } + auto signedOneTimeKey = oneTimeKeyObject.constBegin()->toObject(); + // Verify contents of signedOneTimeKey - for that, drop `signatures` and + // `unsigned` and then verify the object against the respective signature + const auto signature = + signedOneTimeKey.take("signatures"_ls)[targetUserId]["ed25519:"_ls % targetDeviceId] + .toString() + .toLatin1(); + signedOneTimeKey.remove("unsigned"_ls); + if (!verifier.ed25519Verify( + edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), + QJsonDocument(signedOneTimeKey).toJson(QJsonDocument::Compact), + signature)) { + qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId + << targetDeviceId << ". Skipping this device."; + return false; + } + const auto recipientCurveKey = + curveKeyForUserDevice(targetUserId, targetDeviceId); + auto session = + QOlmSession::createOutboundSession(olmAccount.get(), recipientCurveKey, + signedOneTimeKey["key"].toString()); if (!session) { qCWarning(E2EE) << "Failed to create olm session for " - << theirIdentityKey << session.error(); + << recipientCurveKey << session.error(); + return false; + } + saveSession(**session, recipientCurveKey); + olmSessions[recipientCurveKey].push_back(std::move(*session)); + return true; +} + +std::unique_ptr Connection::Private::makeEventForSessionKey( + const QString& roomId, const QString& targetUserId, + const QString& targetDeviceId, const QByteArray& sessionId, + const QByteArray& sessionKey) const +{ + // Noisy but nice for debugging + // qDebug(E2EE) << "Creating the payload for" << data->userId() << device << + // sessionId << sessionKey.toHex(); + const auto event = makeEvent("m.megolm.v1.aes-sha2", roomId, + sessionId, sessionKey, + data->userId()); + auto payloadJson = event->fullJson(); + payloadJson.insert("recipient"_ls, targetUserId); + payloadJson.insert(SenderKeyL, data->userId()); + payloadJson.insert("recipient_keys"_ls, + QJsonObject { { Ed25519Key, + edKeyForUserDevice(targetUserId, + targetDeviceId) } }); + payloadJson.insert("keys"_ls, + QJsonObject { + { Ed25519Key, + QString(olmAccount->identityKeys().ed25519) } }); + payloadJson.insert("sender_device"_ls, data->deviceId()); + + const auto [type, cipherText] = olmEncryptMessage( + targetUserId, targetDeviceId, + QJsonDocument(payloadJson).toJson(QJsonDocument::Compact)); + QJsonObject encrypted { + { curveKeyForUserDevice(targetUserId, targetDeviceId), + QJsonObject { { "type"_ls, type }, + { "body"_ls, QString(cipherText) } } } + }; + + return makeEvent(encrypted, + olmAccount->identityKeys().curve25519); +} + +void Connection::sendSessionKeyToDevices( + const QString& roomId, const QByteArray& sessionId, + const QByteArray& sessionKey, const QMultiHash& devices, + int index) +{ + qDebug(E2EE) << "Sending room key to devices:" << sessionId + << sessionKey.toHex(); + QHash> hash; + for (const auto& [userId, deviceId] : asKeyValueRange(devices)) + if (!hasOlmSession(userId, deviceId)) { + hash[userId].insert(deviceId, "signed_curve25519"_ls); + qDebug(E2EE) << "Adding" << userId << deviceId + << "to keys to claim"; + } + + if (hash.isEmpty()) return; - } - d->saveSession(**session, theirIdentityKey); - d->olmSessions[theirIdentityKey].push_back(std::move(*session)); + + auto job = callApi(hash); + connect(job, &BaseJob::success, this, [job, this, roomId, sessionId, sessionKey, devices, index] { + UsersToDevicesToEvents usersToDevicesToEvents; + const auto oneTimeKeys = job->oneTimeKeys(); + for (const auto& [targetUserId, targetDeviceId] : + asKeyValueRange(devices)) { + if (!hasOlmSession(targetUserId, targetDeviceId) + && !d->createOlmSession( + targetUserId, targetDeviceId, + oneTimeKeys[targetUserId][targetDeviceId])) + continue; + + usersToDevicesToEvents[targetUserId][targetDeviceId] = + d->makeEventForSessionKey(roomId, targetUserId, targetDeviceId, + sessionId, sessionKey); + } + if (!usersToDevicesToEvents.empty()) { + sendToDevices(EncryptedEvent::TypeId, usersToDevicesToEvents); + QVector> receivedDevices; + receivedDevices.reserve(devices.size()); + for (const auto& [user, device] : asKeyValueRange(devices)) + receivedDevices.push_back( + { user, device, d->curveKeyForUserDevice(user, device) }); + + database()->setDevicesReceivedKey(roomId, receivedDevices, + sessionId, index); + } + }); } QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession( diff --git a/lib/connection.h b/lib/connection.h index a2824744..5b806350 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -319,22 +319,26 @@ public: #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; Database* database() const; + PicklingMode picklingMode() const; UnorderedMap loadRoomMegolmSessions( - const Room* room); + const Room* room) const; void saveMegolmSession(const Room* room, - const QOlmInboundGroupSession& session); + const QOlmInboundGroupSession& session) const; bool hasOlmSession(const QString& user, const QString& deviceId) const; QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession( const QString& roomId) const; - void saveCurrentOutboundMegolmSession(const QString& roomId, const QOlmOutboundGroupSession &session) const; - - //This assumes that an olm session with (user, device) exists - std::pair olmEncryptMessage( - const QString& userId, const QString& device, - const QByteArray& message) const; - void createOlmSession(const QString& theirIdentityKey, - const QString& theirOneTimeKey) const; + void saveCurrentOutboundMegolmSession( + const QString& roomId, const QOlmOutboundGroupSession& session) const; + + void sendSessionKeyToDevices(const QString& roomId, + const QByteArray& sessionId, + const QByteArray& sessionKey, + const QMultiHash& devices, + int index); + + QJsonObject decryptNotification(const QJsonObject ¬ification); + QStringList devicesForUser(const QString& userId) const; #endif // Quotient_E2EE_ENABLED Q_INVOKABLE Quotient::SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; @@ -695,16 +699,8 @@ public Q_SLOTS: #ifdef Quotient_E2EE_ENABLED void encryptionUpdate(Room *room); - PicklingMode picklingMode() const; - QJsonObject decryptNotification(const QJsonObject ¬ification); - - QStringList devicesForUser(const QString& userId) const; - QString curveKeyForUserDevice(const QString& userId, - const QString& device) const; - QString edKeyForUserDevice(const QString& userId, - const QString& device) const; - bool isKnownCurveKey(const QString& userId, const QString& curveKey) const; #endif + Q_SIGNALS: /// \brief Initial server resolution has failed /// diff --git a/lib/csapi/keys.h b/lib/csapi/keys.h index ce1ca9ed..bcf1ad41 100644 --- a/lib/csapi/keys.h +++ b/lib/csapi/keys.h @@ -207,9 +207,9 @@ public: /// /// See the [key algorithms](/client-server-api/#key-algorithms) section for /// information on the Key Object format. - QHash> oneTimeKeys() const + QHash> oneTimeKeys() const { - return loadFromJson>>( + return loadFromJson>>( "one_time_keys"_ls); } }; diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 2b149aac..2a98d5d8 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -96,12 +96,13 @@ QOlmExpected QOlmSession::createOutboundSession( return std::make_unique(olmOutboundSession); } -QOlmExpected QOlmSession::pickle(const PicklingMode &mode) +QOlmExpected QOlmSession::pickle(const PicklingMode &mode) const { 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()); + pickledBuf.data(), + pickledBuf.length()); if (error == olm_error()) { return lastError(m_session); diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index faae16ef..021092c7 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -31,7 +31,7 @@ public: const QString& theirOneTimeKey); //! Serialises an `QOlmSession` to encrypted Base64. - QOlmExpected pickle(const PicklingMode &mode); + QOlmExpected pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. static QOlmExpected unpickle( diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 2bda3086..3093db41 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -12,7 +12,9 @@ public: DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent) explicit RoomKeyEvent(const QJsonObject& obj); - explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, const QString &sessionId, const QString& sessionKey, const QString& senderId); + explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, + const QString& sessionId, const QString& sessionKey, + const QString& senderId); QString algorithm() const { return contentPart("algorithm"_ls); } QString roomId() const { return contentPart(RoomIdKeyL); } diff --git a/lib/room.cpp b/lib/room.cpp index 07d03467..6ec06aa8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -457,28 +457,6 @@ public: addInboundGroupSession(currentOutboundMegolmSession->sessionId(), *sessionKey, q->localUser()->id(), "SELF"_ls); } - std::unique_ptr payloadForUserDevice(QString user, const QString& device, const QByteArray& sessionId, const QByteArray& sessionKey) - { - // Noisy but nice for debugging - //qCDebug(E2EE) << "Creating the payload for" << user->id() << device << sessionId << sessionKey.toHex(); - const auto event = makeEvent("m.megolm.v1.aes-sha2", q->id(), sessionId, sessionKey, q->localUser()->id()); - QJsonObject payloadJson = event->fullJson(); - payloadJson["recipient"] = user; - payloadJson["sender"] = connection->user()->id(); - QJsonObject recipientObject; - recipientObject["ed25519"] = connection->edKeyForUserDevice(user, device); - payloadJson["recipient_keys"] = recipientObject; - QJsonObject senderObject; - senderObject["ed25519"] = QString(connection->olmAccount()->identityKeys().ed25519); - payloadJson["keys"] = senderObject; - payloadJson["sender_device"] = connection->deviceId(); - auto cipherText = connection->olmEncryptMessage(user, device, QJsonDocument(payloadJson).toJson(QJsonDocument::Compact)); - QJsonObject encrypted; - encrypted[connection->curveKeyForUserDevice(user, device)] = QJsonObject{{"type", cipherText.first}, {"body", QString(cipherText.second)}}; - - return makeEvent(encrypted, connection->olmAccount()->identityKeys().curve25519); - } - QMultiHash getDevicesWithoutKey() const { QMultiHash devices; @@ -490,91 +468,7 @@ public: id, devices, currentOutboundMegolmSession->sessionId()); } - bool createOlmSession(const QString& user, const QString& device, - const QJsonObject& oneTimeKeyObject) const - { - static QOlmUtility verifier; - qDebug(E2EE) << "Creating a new session for" << user << device; - if (oneTimeKeyObject.isEmpty()) { - qWarning(E2EE) << "No one time key for" << user << device; - return false; - } - const auto oneTimeKeyForId = *oneTimeKeyObject.constBegin(); - const auto signature = - oneTimeKeyForId["signatures"][user]["ed25519:"_ls % device] - .toString() - .toLatin1(); - auto signedObject = oneTimeKeyForId.toObject(); - signedObject.remove("unsigned"_ls); - signedObject.remove("signatures"_ls); - const auto signedData = - QJsonDocument(signedObject).toJson(QJsonDocument::Compact); - if (!verifier.ed25519Verify( - connection->edKeyForUserDevice(user, device).toLatin1(), - signedData, signature)) { - qWarning(E2EE) << "Failed to verify one-time-key signature for" - << user << device << ". Skipping this device."; - return false; - } - const auto recipientCurveKey = - connection->curveKeyForUserDevice(user, device); - connection->createOlmSession(recipientCurveKey, - oneTimeKeyForId["key"].toString()); - return true; - } - - void sendRoomKeyToDevices(const QByteArray& sessionId, - const QByteArray& sessionKey, - const QMultiHash& devices, - int index) - { - qDebug(E2EE) << "Sending room key to devices:" << sessionId - << sessionKey.toHex(); - QHash> hash; - for (const auto& [userId, deviceId] : asKeyValueRange(devices)) - if (!connection->hasOlmSession(userId, deviceId)) { - hash[userId].insert(deviceId, "signed_curve25519"_ls); - qDebug(E2EE) - << "Adding" << userId << deviceId << "to keys to claim"; - } - - if (hash.isEmpty()) - return; - - auto job = connection->callApi(hash); - connect(job, &BaseJob::success, q, - [job, this, sessionId, sessionKey, devices, index] { - Connection::UsersToDevicesToEvents usersToDevicesToEvents; - const auto data = job->jsonData(); - for (const auto& [user, device] : asKeyValueRange(devices)) { - if (!connection->hasOlmSession(user, device) - && !createOlmSession( - user, device, - data["one_time_keys"][user][device].toObject())) - continue; - - usersToDevicesToEvents[user][device] = - payloadForUserDevice(user, device, sessionId, - sessionKey); - } - if (!usersToDevicesToEvents.empty()) { - connection->sendToDevices("m.room.encrypted"_ls, - usersToDevicesToEvents); - QVector> receivedDevices; - receivedDevices.reserve(devices.size()); - for (const auto& [user, device] : asKeyValueRange(devices)) - receivedDevices.push_back( - { user, device, - connection->curveKeyForUserDevice(user, device) }); - - connection->database()->setDevicesReceivedKey(id, - receivedDevices, - sessionId, index); - } - }); - } - - void sendMegolmSession(const QMultiHash& devices) { + void sendMegolmSession(const QMultiHash& devices) const { // Save the session to this device const auto sessionId = currentOutboundMegolmSession->sessionId(); const auto sessionKey = currentOutboundMegolmSession->sessionKey(); @@ -584,8 +478,9 @@ public: } // Send the session to other people - sendRoomKeyToDevices(sessionId, *sessionKey, devices, - currentOutboundMegolmSession->sessionMessageIndex()); + connection->sendSessionKeyToDevices( + id, sessionId, *sessionKey, devices, + currentOutboundMegolmSession->sessionMessageIndex()); } #endif // Quotient_E2EE_ENABLED -- cgit v1.2.3 From 21ae4eca4c06e500ec04a52ad42772bf8e8e9b6f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 31 May 2022 18:58:27 +0200 Subject: Tweak QOlmAccount and data structures around This is mainly to plug the definition of a string-to-variant map for one-time keys (see https://spec.matrix.org/v1.2/client-server-api/#key-algorithms) into the CS API generated code (see the "shortcut OneTimeKeys" commit for gtad.yaml); but along with it came considerable streamlining of code in qolmaccount.cpp. Using std::variant to store that map also warranted converters.h to gain support for that type (even wider than toJson() that is already in dev - a non-trivial merge from dev is in order). --- autotests/testolmaccount.cpp | 18 +++----- lib/converters.h | 20 ++++++++ lib/e2ee/e2ee.h | 10 ++-- lib/e2ee/qolmaccount.cpp | 108 ++++++++++++++++--------------------------- lib/e2ee/qolmaccount.h | 9 ++-- 5 files changed, 73 insertions(+), 92 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index e31ff6d3..c85718dd 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -198,7 +198,7 @@ void TestOlmAccount::uploadIdentityKey() QVERIFY(idKeys.curve25519.size() > 10); - OneTimeKeys unused; + UnsignedOneTimeKeys unused; auto request = olmAccount->createUploadKeyRequest(unused); connect(request, &BaseJob::result, this, [request, conn] { QCOMPARE(request->oneTimeKeyCounts().size(), 0); @@ -221,7 +221,7 @@ void TestOlmAccount::uploadOneTimeKeys() auto oneTimeKeys = olmAccount->oneTimeKeys(); - QHash oneTimeKeysHash; + OneTimeKeys oneTimeKeysHash; const auto curve = oneTimeKeys.curve25519(); for (const auto &[keyId, key] : asKeyValueRange(curve)) { oneTimeKeysHash["curve25519:"+keyId] = key; @@ -247,12 +247,10 @@ void TestOlmAccount::uploadSignedOneTimeKeys() QCOMPARE(nKeys, 5); auto oneTimeKeys = olmAccount->oneTimeKeys(); - QHash oneTimeKeysHash; + OneTimeKeys oneTimeKeysHash; const auto signedKey = olmAccount->signOneTimeKeys(oneTimeKeys); for (const auto &[keyId, key] : asKeyValueRange(signedKey)) { - QVariant var; - var.setValue(key); - oneTimeKeysHash[keyId] = var; + oneTimeKeysHash[keyId] = key; } auto request = new UploadKeysJob(none, oneTimeKeysHash); connect(request, &BaseJob::result, this, [request, nKeys, conn] { @@ -410,11 +408,9 @@ void TestOlmAccount::claimKeys() // The key is the one bob sent. const auto& oneTimeKey = job->oneTimeKeys().value(userId).value(deviceId); - QVERIFY(oneTimeKey.canConvert()); - - const auto varMap = oneTimeKey.toMap(); - QVERIFY(std::any_of(varMap.constKeyValueBegin(), - varMap.constKeyValueEnd(), [](const auto& kv) { + QVERIFY(std::any_of(oneTimeKey.constKeyValueBegin(), + oneTimeKey.constKeyValueEnd(), + [](const auto& kv) { return kv.first.startsWith( SignedCurve25519Key); })); diff --git a/lib/converters.h b/lib/converters.h index 5e3becb8..64a5cfb6 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -224,6 +224,26 @@ struct QUOTIENT_API JsonConverter { static QVariant load(const QJsonValue& jv); }; +template +inline QJsonValue toJson(const std::variant& v) +{ + // std::visit requires all overloads to return the same type - and + // QJsonValue is a perfect candidate for that same type (assuming that + // variants never occur on the top level in Matrix API) + return std::visit( + [](const auto& value) { return QJsonValue { toJson(value) }; }, v); +} + +template +struct QUOTIENT_API JsonConverter> { + static std::variant load(const QJsonValue& jv) + { + if (jv.isString()) + return fromJson(jv); + return fromJson(jv); + } +}; + template struct JsonConverter> { static QJsonValue dump(const Omittable& from) diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 8e433d60..f97eb27a 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -70,15 +70,12 @@ struct IdentityKeys }; //! Struct representing the one-time keys. -struct QUOTIENT_API OneTimeKeys +struct QUOTIENT_API UnsignedOneTimeKeys { QHash> keys; //! Get the HashMap containing the curve25519 one-time keys. - QHash curve25519() const; - - //! Get a reference to the hashmap corresponding to given key type. -// std::optional> get(QString keyType) const; + QHash curve25519() const { return keys[Curve25519Key]; } }; //! Struct representing the signed one-time keys. @@ -93,7 +90,6 @@ public: QHash> signatures; }; - template <> struct JsonObjectConverter { static void fillFrom(const QJsonObject& jo, SignedOneTimeKey& result) @@ -109,6 +105,8 @@ struct JsonObjectConverter { } }; +using OneTimeKeys = QHash>; + template class asKeyValueRange { diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 72dddafb..4cfc6151 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -17,19 +17,6 @@ using namespace Quotient; -QHash OneTimeKeys::curve25519() const -{ - return keys[Curve25519Key]; -} - -//std::optional> OneTimeKeys::get(QString keyType) const -//{ -// if (!keys.contains(keyType)) { -// return std::nullopt; -// } -// return keys[keyType]; -//} - // Convert olm error to enum QOlmError lastError(OlmAccount *account) { return fromString(olm_account_last_error(account)); @@ -122,20 +109,15 @@ QByteArray QOlmAccount::sign(const QJsonObject &message) const 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)); - + return sign(QJsonObject { + { "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) } } } }); } size_t QOlmAccount::maxNumberOfOneTimeKeys() const @@ -145,9 +127,13 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) { - const size_t randomLength = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); + 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); + const auto error = + olm_account_generate_one_time_keys(m_account, numberOfKeys, + randomBuffer.data(), randomLength); if (error == olm_error()) { throw lastError(m_account); @@ -156,7 +142,7 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) return error; } -OneTimeKeys QOlmAccount::oneTimeKeys() const +UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const { const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0'); @@ -166,34 +152,25 @@ OneTimeKeys QOlmAccount::oneTimeKeys() const throw lastError(m_account); } const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); - OneTimeKeys oneTimeKeys; + UnsignedOneTimeKeys oneTimeKeys; fromJson(json, oneTimeKeys.keys); return oneTimeKeys; } -QHash QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const +OneTimeKeys QOlmAccount::signOneTimeKeys(const UnsignedOneTimeKeys &keys) const { - QHash 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); - } + OneTimeKeys signedOneTimeKeys; + const auto& curveKeys = keys.curve25519(); + for (const auto& [keyId, key] : asKeyValueRange(curveKeys)) + signedOneTimeKeys["signed_curve25519:" % keyId] = + signedOneTimeKey(key.toUtf8(), sign(QJsonObject{{"key", key}})); return signedOneTimeKeys; } -SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray &key, const QString &signature) const +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)); + return { key, { { m_userId, { { "ed25519:" + m_deviceId, signature } } } } }; } std::optional QOlmAccount::removeOneTimeKeys( @@ -227,39 +204,32 @@ DeviceKeys QOlmAccount::deviceKeys() const return deviceKeys; } -UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKeys) +UploadKeysJob* QOlmAccount::createUploadKeyRequest( + const UnsignedOneTimeKeys& oneTimeKeys) const { - 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); + return new UploadKeysJob(deviceKeys(), signOneTimeKeys(oneTimeKeys)); } -QOlmExpected QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) +QOlmExpected QOlmAccount::createInboundSession( + const QOlmMessage& preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSession(this, preKeyMessage); } -QOlmExpected QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) +QOlmExpected QOlmAccount::createInboundSessionFrom( + const QByteArray& theirIdentityKey, const QOlmMessage& preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); - return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); + return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, + preKeyMessage); } -QOlmExpected QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) +QOlmExpected QOlmAccount::createOutboundSession( + const QByteArray& theirIdentityKey, const QByteArray& theirOneTimeKey) { - return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); + return QOlmSession::createOutboundSession(this, theirIdentityKey, + theirOneTimeKey); } void QOlmAccount::markKeysAsPublished() diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index ee2aa69d..23fe58dd 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -59,17 +59,14 @@ public: size_t generateOneTimeKeys(size_t numberOfKeys); //! Gets the OlmAccount's one time keys formatted as JSON. - OneTimeKeys oneTimeKeys() const; + UnsignedOneTimeKeys oneTimeKeys() const; //! Sign all one time keys. - QHash signOneTimeKeys(const OneTimeKeys &keys) const; - - //! Sign one time key. - QByteArray signOneTimeKey(const QString &key) const; + OneTimeKeys signOneTimeKeys(const UnsignedOneTimeKeys &keys) const; SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; - UploadKeysJob *createUploadKeyRequest(const OneTimeKeys &oneTimeKeys); + UploadKeysJob* createUploadKeyRequest(const UnsignedOneTimeKeys& oneTimeKeys) const; DeviceKeys deviceKeys() const; -- cgit v1.2.3 From 2ecc08357fab7d22947b9cb5251d2f29be2ec55b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 4 Jun 2022 22:35:06 +0200 Subject: Address Sonar warnings --- lib/e2ee/qolmaccount.cpp | 4 ++-- lib/room.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 4cfc6151..241ae750 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -160,8 +160,8 @@ UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const OneTimeKeys QOlmAccount::signOneTimeKeys(const UnsignedOneTimeKeys &keys) const { OneTimeKeys signedOneTimeKeys; - const auto& curveKeys = keys.curve25519(); - for (const auto& [keyId, key] : asKeyValueRange(curveKeys)) + for (const auto& curveKeys = keys.curve25519(); + const auto& [keyId, key] : asKeyValueRange(curveKeys)) signedOneTimeKeys["signed_curve25519:" % keyId] = signedOneTimeKey(key.toUtf8(), sign(QJsonObject{{"key", key}})); return signedOneTimeKeys; diff --git a/lib/room.cpp b/lib/room.cpp index 9e2a5053..284d19df 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1531,7 +1531,7 @@ QStringList Room::safeMemberNames() const { QStringList res; res.reserve(d->membersMap.size()); - for (auto u: std::as_const(d->membersMap)) + for (const auto* u: std::as_const(d->membersMap)) res.append(safeMemberName(u->id())); return res; @@ -1541,7 +1541,7 @@ QStringList Room::htmlSafeMemberNames() const { QStringList res; res.reserve(d->membersMap.size()); - for (auto u: std::as_const(d->membersMap)) + for (const auto* u: std::as_const(d->membersMap)) res.append(htmlSafeMemberName(u->id())); return res; @@ -3378,7 +3378,7 @@ QString Room::Private::calculateDisplayname() const shortlist = buildShortlist(membersLeft); QStringList names; - for (auto u : shortlist) { + for (const auto* u : shortlist) { if (u == nullptr || isLocalUser(u)) break; // Only disambiguate if the room is not empty -- cgit v1.2.3 From 5a63f8a18645d612decdcc853335df0682c41d03 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 18 Jun 2022 21:29:27 +0200 Subject: Drop make_array(); use std::to_array() where needed make_array() has been introduced to cover for shortcomings on macOS and Windows. These shortcomings are no more there, so we can just use the standardrlibrary. --- lib/e2ee/e2ee.h | 5 +++-- lib/events/encryptionevent.cpp | 4 +--- lib/jobs/basejob.cpp | 20 ++++++++++---------- lib/quotient_common.h | 27 +++++++-------------------- 4 files changed, 21 insertions(+), 35 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index f97eb27a..1efd0f16 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -37,8 +37,9 @@ constexpr auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls; inline bool isSupportedAlgorithm(const QString& algorithm) { - static constexpr auto SupportedAlgorithms = - make_array(OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey); + static constexpr std::array SupportedAlgorithms { + OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey + }; return std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), algorithm) != SupportedAlgorithms.cend(); diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 6e994cd4..eb15f38e 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -9,9 +9,7 @@ #include namespace Quotient { -static const std::array encryptionStrings = { - { MegolmV1AesSha2AlgoKey } -}; +static constexpr std::array encryptionStrings { MegolmV1AesSha2AlgoKey }; template <> struct JsonConverter { diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index fe70911e..da645a2d 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -138,9 +138,8 @@ public: QTimer timer; QTimer retryTimer; - static constexpr std::array errorStrategy { - { { 90s, 5s }, { 90s, 10s }, { 120s, 30s } } - }; + static constexpr auto errorStrategy = std::to_array( + { { 90s, 5s }, { 90s, 10s }, { 120s, 30s } }); int maxRetries = int(errorStrategy.size()); int retriesTaken = 0; @@ -152,10 +151,8 @@ public: [[nodiscard]] QString dumpRequest() const { - // FIXME: use std::array {} when Apple stdlib gets deduction guides for it - static const auto verbs = - make_array(QStringLiteral("GET"), QStringLiteral("PUT"), - QStringLiteral("POST"), QStringLiteral("DELETE")); + static const std::array verbs { "GET"_ls, "PUT"_ls, "POST"_ls, + "DELETE"_ls }; const auto verbWord = verbs.at(size_t(verb)); return verbWord % ' ' % (reply ? reply->url().toString(QUrl::RemoveQuery) @@ -748,11 +745,14 @@ QString BaseJob::statusCaption() const } } -int BaseJob::error() const { return d->status.code; } +int BaseJob::error() const { + return d->status.code; } -QString BaseJob::errorString() const { return d->status.message; } +QString BaseJob::errorString() const { + return d->status.message; } -QUrl BaseJob::errorUrl() const { return d->errorUrl; } +QUrl BaseJob::errorUrl() const { + return d->errorUrl; } void BaseJob::setStatus(Status s) { diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 8bcd5ca6..136e9f79 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -41,7 +41,6 @@ Q_ENUM_NS_IMPL(Enum) \ Q_FLAG_NS(Flags) -// Apple Clang hasn't caught up with explicit(bool) yet #if __cpp_conditional_explicit >= 201806L #define QUO_IMPLICIT explicit(false) #else @@ -54,19 +53,6 @@ namespace Quotient { Q_NAMESPACE_EXPORT(QUOTIENT_API) -// std::array {} needs explicit template parameters on macOS because -// Apple stdlib doesn't have deduction guides for std::array. C++20 has -// to_array() but that can't be borrowed, this time because of MSVC: -// https://developercommunity.visualstudio.com/t/vc-ice-p1-initc-line-3652-from-stdto-array/1464038 -// Therefore a simpler (but also slightly more wobbly - it resolves the element -// type using std::common_type<>) make_array facility is implemented here. -template -constexpr auto make_array(Ts&&... items) -{ - return std::array, sizeof...(items)>( - { std::forward(items)... }); -} - // TODO: code like this should be generated from the CS API definition //! \brief Membership states @@ -87,9 +73,10 @@ enum class Membership : unsigned int { }; QUO_DECLARE_FLAGS_NS(MembershipMask, Membership) -constexpr auto MembershipStrings = make_array( - // The order MUST be the same as the order in the original enum - "join", "leave", "invite", "knock", "ban"); +constexpr std::array MembershipStrings { + // The order MUST be the same as the order in the Membership enum + "join", "leave", "invite", "knock", "ban" +}; //! \brief Local user join-state names //! @@ -105,10 +92,10 @@ enum class JoinState : std::underlying_type_t { }; QUO_DECLARE_FLAGS_NS(JoinStates, JoinState) -[[maybe_unused]] constexpr auto JoinStateStrings = make_array( +[[maybe_unused]] constexpr std::array JoinStateStrings { MembershipStrings[0], MembershipStrings[1], MembershipStrings[2], MembershipStrings[3] /* same as MembershipStrings, sans "ban" */ -); +}; //! \brief Network job running policy flags //! @@ -135,7 +122,7 @@ enum RoomType { }; Q_ENUM_NS(RoomType) -[[maybe_unused]] constexpr auto RoomTypeStrings = make_array("m.space"); +[[maybe_unused]] constexpr std::array RoomTypeStrings { "m.space" }; } // namespace Quotient Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask) -- cgit v1.2.3 From 7ef84728ab3744192583eb587a4585c576f5a176 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 18 Jun 2022 21:39:42 +0200 Subject: Move C++-only macros to util.h This pertains to QUO_IMPLICIT and DECL_DEPRECATED_ENUMERATOR - both can be used with no connection to Qt meta-type system (which is what quotient_common.h is for). --- lib/e2ee/e2ee.h | 3 +-- lib/events/encryptionevent.cpp | 2 -- lib/events/encryptionevent.h | 1 - lib/quotient_common.h | 9 --------- lib/util.h | 9 +++++++++ 5 files changed, 10 insertions(+), 14 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 1efd0f16..9501b263 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -8,7 +8,6 @@ #include "converters.h" #include "expected.h" #include "qolmerrors.h" -#include "quotient_common.h" #include #include @@ -71,7 +70,7 @@ struct IdentityKeys }; //! Struct representing the one-time keys. -struct QUOTIENT_API UnsignedOneTimeKeys +struct UnsignedOneTimeKeys { QHash> keys; diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index eb15f38e..1654d6f3 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -6,8 +6,6 @@ #include "e2ee/e2ee.h" -#include - namespace Quotient { static constexpr std::array encryptionStrings { MegolmV1AesSha2AlgoKey }; diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 5b5420ec..c73e5598 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -5,7 +5,6 @@ #pragma once #include "stateevent.h" -#include "quotient_common.h" namespace Quotient { class QUOTIENT_API EncryptionEventContent { diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 136e9f79..e087e7d3 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -41,15 +41,6 @@ Q_ENUM_NS_IMPL(Enum) \ Q_FLAG_NS(Flags) -#if __cpp_conditional_explicit >= 201806L -#define QUO_IMPLICIT explicit(false) -#else -#define QUO_IMPLICIT -#endif - -#define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \ - Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended - namespace Quotient { Q_NAMESPACE_EXPORT(QUOTIENT_API) diff --git a/lib/util.h b/lib/util.h index 5dd69d74..d1623881 100644 --- a/lib/util.h +++ b/lib/util.h @@ -37,6 +37,15 @@ static_assert(false, "Use Q_DISABLE_MOVE instead; Quotient enables it across all QT_WARNING_POP #endif +#if __cpp_conditional_explicit >= 201806L +#define QUO_IMPLICIT explicit(false) +#else +#define QUO_IMPLICIT +#endif + +#define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \ + Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended + /// \brief Copy an object with slicing /// /// Unintended slicing is bad, which why there's a C++ Core Guideline that -- cgit v1.2.3 From 7c1125cdd146227320aa7eb082225c4051ea0563 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 19 Jun 2022 20:55:00 +0200 Subject: Add a missing #include --- lib/e2ee/e2ee.h | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 9501b263..234d4bcb 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -11,6 +11,7 @@ #include #include +#include namespace Quotient { -- cgit v1.2.3 From 5de8d0a4bc9744327703c1613fc5ac3f232f44a8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Jun 2022 21:36:43 +0200 Subject: Fix signature verification toJson(SignedOneTimeKey) incorrectly generated a "signatures" key mapped to an empty object when no signatures were in the C++ value. Also: fallback keys have an additional flag that also has to be taken into account when verifying signatures. --- lib/e2ee/e2ee.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 234d4bcb..aba795a4 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -89,6 +89,8 @@ public: //! Required. Signatures of the key object. //! The signature is calculated using the process described at Signing JSON. QHash> signatures; + + bool fallback = false; }; template <> @@ -97,12 +99,14 @@ struct JsonObjectConverter { { fromJson(jo.value("key"_ls), result.key); fromJson(jo.value("signatures"_ls), result.signatures); + fromJson(jo.value("fallback"_ls), result.fallback); } static void dumpTo(QJsonObject &jo, const SignedOneTimeKey &result) { - addParam<>(jo, QStringLiteral("key"), result.key); - addParam<>(jo, QStringLiteral("signatures"), result.signatures); + addParam<>(jo, "key"_ls, result.key); + addParam(jo, "signatures"_ls, result.signatures); + addParam(jo, "key"_ls, result.fallback); } }; -- cgit v1.2.3 From 9f7a65b04c246de4c27b205ece778ede1ad7df7e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 09:18:09 +0200 Subject: Fix copy-pasta in signed one-time key JSON dumper --- lib/connection.cpp | 5 +++-- lib/e2ee/e2ee.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 2319a38a..13a35684 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2306,10 +2306,11 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, signedOneTimeKey ->signatures[targetUserId]["ed25519:"_ls % targetDeviceId] .toLatin1(); + const auto payloadObject = + toJson(SignedOneTimeKey { signedOneTimeKey->key, {} }); if (!verifier.ed25519Verify( edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), - QJsonDocument(toJson(SignedOneTimeKey { signedOneTimeKey->key, {} })) - .toJson(QJsonDocument::Compact), + QJsonDocument(payloadObject).toJson(QJsonDocument::Compact), signature)) { qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId << targetDeviceId << ". Skipping this device."; diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index aba795a4..17c87f53 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -106,7 +106,7 @@ struct JsonObjectConverter { { addParam<>(jo, "key"_ls, result.key); addParam(jo, "signatures"_ls, result.signatures); - addParam(jo, "key"_ls, result.fallback); + addParam(jo, "fallback"_ls, result.fallback); } }; -- cgit v1.2.3 From 6ae41d68dcdb91e5ec4a3ea48a151daaa0765765 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 15:10:33 +0200 Subject: Rework SignedOneTimeKey as a QJsonObject wrapper Since this object has to be verified against a signature it also carries there's a rather specific procedure described in The Spec for that. That procedure basically assumes handling the signed one-time key object as a JSON object, not as a C++ object. And originally Quotient E2EE code was exactly like that (obtaining the right QJsonObject from the job result and handling it as specced) but then one enthusiastic developer (me) decided it's better to use a proper C++ structure - breaking the verification logic along the way. After a couple attempts to fix it, here we are again: SignedOneTimeKey is a proper QJsonObject, and even provides a method returning its JSON in the form prepared for verification (according to the spec). --- lib/connection.cpp | 10 +++----- lib/e2ee/e2ee.h | 62 ++++++++++++++++++++++++++++++++---------------- lib/e2ee/qolmaccount.cpp | 12 ++++------ 3 files changed, 48 insertions(+), 36 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 13a35684..690b3f6a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2303,14 +2303,10 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, // Verify contents of signedOneTimeKey - for that, drop `signatures` and // `unsigned` and then verify the object against the respective signature const auto signature = - signedOneTimeKey - ->signatures[targetUserId]["ed25519:"_ls % targetDeviceId] - .toLatin1(); - const auto payloadObject = - toJson(SignedOneTimeKey { signedOneTimeKey->key, {} }); + signedOneTimeKey->signature(targetUserId, targetDeviceId); if (!verifier.ed25519Verify( edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), - QJsonDocument(payloadObject).toJson(QJsonDocument::Compact), + signedOneTimeKey->toJsonForVerification(), signature)) { qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId << targetDeviceId << ". Skipping this device."; @@ -2320,7 +2316,7 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, curveKeyForUserDevice(targetUserId, targetDeviceId); auto session = QOlmSession::createOutboundSession(olmAccount.get(), recipientCurveKey, - signedOneTimeKey->key); + signedOneTimeKey->key()); if (!session) { qCWarning(E2EE) << "Failed to create olm session for " << recipientCurveKey << session.error(); diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 17c87f53..7b9b5820 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -10,8 +10,10 @@ #include "qolmerrors.h" #include -#include +#include + #include +#include namespace Quotient { @@ -79,35 +81,53 @@ struct UnsignedOneTimeKeys QHash curve25519() const { return keys[Curve25519Key]; } }; -//! Struct representing the signed one-time keys. -class SignedOneTimeKey -{ +class SignedOneTimeKey { public: - //! Required. The unpadded Base64-encoded 32-byte Curve25519 public key. - QString key; + explicit SignedOneTimeKey(const QString& unsignedKey, const QString& userId, + const QString& deviceId, const QString& signature) + : payload { { "key"_ls, unsignedKey }, + { "signatures"_ls, + QJsonObject { + { userId, QJsonObject { { "ed25519:"_ls % deviceId, + signature } } } } } } + {} + explicit SignedOneTimeKey(const QJsonObject& jo = {}) + : payload(jo) + {} - //! Required. Signatures of the key object. - //! The signature is calculated using the process described at Signing JSON. - QHash> signatures; + //! Unpadded Base64-encoded 32-byte Curve25519 public key + QString key() const { return payload["key"_ls].toString(); } - bool fallback = false; -}; + //! \brief Signatures of the key object + //! + //! The signature is calculated using the process described at + //! https://spec.matrix.org/v1.3/appendices/#signing-json + auto signatures() const + { + return fromJson>>( + payload["signatures"_ls]); + } -template <> -struct JsonObjectConverter { - static void fillFrom(const QJsonObject& jo, SignedOneTimeKey& result) + QByteArray signature(QStringView userId, QStringView deviceId) const { - fromJson(jo.value("key"_ls), result.key); - fromJson(jo.value("signatures"_ls), result.signatures); - fromJson(jo.value("fallback"_ls), result.fallback); + return payload["signatures"_ls][userId]["ed25519:"_ls % deviceId] + .toString() + .toLatin1(); } - static void dumpTo(QJsonObject &jo, const SignedOneTimeKey &result) + //! Whether the key is a fallback key + bool isFallback() const { return payload["fallback"_ls].toBool(); } + auto toJson() const { return payload; } + auto toJsonForVerification() const { - addParam<>(jo, "key"_ls, result.key); - addParam(jo, "signatures"_ls, result.signatures); - addParam(jo, "fallback"_ls, result.fallback); + auto json = payload; + json.remove("signatures"_ls); + json.remove("unsigned"_ls); + return QJsonDocument(json).toJson(QJsonDocument::Compact); } + +private: + QJsonObject payload; }; using OneTimeKeys = QHash>; diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 241ae750..c3714363 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -162,17 +162,13 @@ OneTimeKeys QOlmAccount::signOneTimeKeys(const UnsignedOneTimeKeys &keys) const OneTimeKeys signedOneTimeKeys; for (const auto& curveKeys = keys.curve25519(); const auto& [keyId, key] : asKeyValueRange(curveKeys)) - signedOneTimeKeys["signed_curve25519:" % keyId] = - signedOneTimeKey(key.toUtf8(), sign(QJsonObject{{"key", key}})); + signedOneTimeKeys.insert("signed_curve25519:" % keyId, + SignedOneTimeKey { + key, m_userId, m_deviceId, + sign(QJsonObject { { "key", key } }) }); return signedOneTimeKeys; } -SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray& key, - const QString& signature) const -{ - return { key, { { m_userId, { { "ed25519:" + m_deviceId, signature } } } } }; -} - std::optional QOlmAccount::removeOneTimeKeys( const QOlmSession& session) { -- cgit v1.2.3 From 7fdb1a8653863f580b2672faefc08fb372258df8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 15:56:03 +0200 Subject: Code cleanup and reformatting --- lib/connection.cpp | 5 +++-- lib/converters.h | 4 ++-- lib/e2ee/qolmaccount.cpp | 6 ++++-- lib/e2ee/qolmaccount.h | 2 -- 4 files changed, 9 insertions(+), 8 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 690b3f6a..701f78c2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2308,8 +2308,9 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), signedOneTimeKey->toJsonForVerification(), signature)) { - qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId - << targetDeviceId << ". Skipping this device."; + qWarning(E2EE) << "Failed to verify one-time-key signature for" + << targetUserId << targetDeviceId + << ". Skipping this device."; return false; } const auto recipientCurveKey = diff --git a/lib/converters.h b/lib/converters.h index 385982ab..c445442c 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -86,8 +86,8 @@ struct JsonConverter : _impl::JsonExporter { static T load(const QJsonDocument& jd) { return doLoad(jd.object()); } }; -template >> +template + requires (!std::is_constructible_v) inline auto toJson(const T& pod) // -> can return anything from which QJsonValue or, in some cases, QJsonDocument // is constructible diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index c3714363..cd10f165 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -145,9 +145,11 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const { const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); - QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0'); + QByteArray oneTimeKeysBuffer(static_cast(oneTimeKeyLength), '0'); - const auto error = olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), oneTimeKeyLength); + const auto error = olm_account_one_time_keys(m_account, + oneTimeKeysBuffer.data(), + oneTimeKeyLength); if (error == olm_error()) { throw lastError(m_account); } diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 23fe58dd..f2a31314 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -64,8 +64,6 @@ public: //! Sign all one time keys. OneTimeKeys signOneTimeKeys(const UnsignedOneTimeKeys &keys) const; - SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; - UploadKeysJob* createUploadKeyRequest(const UnsignedOneTimeKeys& oneTimeKeys) const; DeviceKeys deviceKeys() const; -- cgit v1.2.3 From 4d4d363b29ff4e471511ff454a58d7d8b88d215d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 15:54:36 +0200 Subject: Start using C++20's designated initializers --- lib/e2ee/e2ee.h | 6 +++--- lib/e2ee/qolmaccount.cpp | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 7b9b5820..449e6ef7 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -37,11 +37,11 @@ constexpr auto SignedCurve25519Key = "signed_curve25519"_ls; constexpr auto OlmV1Curve25519AesSha2AlgoKey = "m.olm.v1.curve25519-aes-sha2"_ls; constexpr auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls; +constexpr std::array SupportedAlgorithms { OlmV1Curve25519AesSha2AlgoKey, + MegolmV1AesSha2AlgoKey }; + inline bool isSupportedAlgorithm(const QString& algorithm) { - static constexpr std::array SupportedAlgorithms { - OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey - }; return std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), algorithm) != SupportedAlgorithms.cend(); diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index cd10f165..ccb191f4 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -187,19 +187,19 @@ 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"}; + static QStringList Algorithms(SupportedAlgorithms.cbegin(), + SupportedAlgorithms.cend()); 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; + return DeviceKeys { + .userId = m_userId, + .deviceId = m_deviceId, + .algorithms = Algorithms, + .keys { { "curve25519:" + m_deviceId, idKeys.curve25519 }, + { "ed25519:" + m_deviceId, idKeys.ed25519 } }, + .signatures { + { m_userId, { { "ed25519:" + m_deviceId, signIdentityKeys() } } } } + }; } UploadKeysJob* QOlmAccount::createUploadKeyRequest( -- cgit v1.2.3 From 63e4cce8cc32af9bd92ead9876a3642d7cbdfb31 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 22:41:15 +0200 Subject: Fix the just introduced Sonar warning Too many parameters of the same type in a row. --- lib/e2ee/e2ee.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 449e6ef7..0772b70a 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -84,12 +84,13 @@ struct UnsignedOneTimeKeys class SignedOneTimeKey { public: explicit SignedOneTimeKey(const QString& unsignedKey, const QString& userId, - const QString& deviceId, const QString& signature) + const QString& deviceId, + const QByteArray& signature) : payload { { "key"_ls, unsignedKey }, { "signatures"_ls, QJsonObject { { userId, QJsonObject { { "ed25519:"_ls % deviceId, - signature } } } } } } + QString(signature) } } } } } } {} explicit SignedOneTimeKey(const QJsonObject& jo = {}) : payload(jo) -- cgit v1.2.3 From 363a7e40e8aa12cb780b076cca8db4f47b70f4fa Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 26 Sep 2022 09:44:20 +0200 Subject: Replace QOlmError with OlmErrorCode QOlmError represents a subset of OlmErrorCode, and the associated fromString() function uses undocumented strings produced inside Olm; meanwhile OlmErrorCode is documented in its own header file. Each QOlm* class now has lastErrorCode() next to lastError() (that, from now, returns a textual representation straight from Olm, not QOlmError enum). Also: including olm/error.h in e2ee/e2ee.h required some rearrangement of the code to make sure non-E2EE configuration still builds. --- CMakeLists.txt | 2 +- autotests/testolmaccount.cpp | 3 +- autotests/testolmsession.cpp | 8 +++- lib/connection.cpp | 3 +- lib/e2ee/e2ee.h | 36 ++++----------- lib/e2ee/qolmaccount.cpp | 82 +++++++++++++++++---------------- lib/e2ee/qolmaccount.h | 9 ++-- lib/e2ee/qolmerrors.cpp | 25 ---------- lib/e2ee/qolmerrors.h | 28 ------------ lib/e2ee/qolminboundsession.cpp | 84 +++++++++++++++++++--------------- lib/e2ee/qolminboundsession.h | 5 +- lib/e2ee/qolmoutboundsession.cpp | 81 +++++++++++++++++++-------------- lib/e2ee/qolmoutboundsession.h | 7 ++- lib/e2ee/qolmsession.cpp | 98 +++++++++++++++++++++------------------- lib/e2ee/qolmsession.h | 4 +- lib/e2ee/qolmutility.cpp | 22 +++++---- lib/e2ee/qolmutility.h | 4 +- lib/events/encryptedevent.cpp | 1 + lib/events/encryptedevent.h | 7 ++- lib/room.cpp | 1 - lib/util.h | 17 +++++++ 21 files changed, 266 insertions(+), 261 deletions(-) delete mode 100644 lib/e2ee/qolmerrors.cpp delete mode 100644 lib/e2ee/qolmerrors.h (limited to 'lib/e2ee') diff --git a/CMakeLists.txt b/CMakeLists.txt index a56115e3..b021411c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,6 +150,7 @@ list(APPEND lib_SRCS lib/eventitem.h lib/eventitem.cpp lib/accountregistry.h lib/accountregistry.cpp lib/mxcreply.h lib/mxcreply.cpp + lib/e2ee/e2ee.h # because it's used by generated API lib/events/event.h lib/events/event.cpp lib/events/eventloader.h lib/events/roomevent.h lib/events/roomevent.cpp @@ -193,7 +194,6 @@ if (${PROJECT_NAME}_ENABLE_E2EE) lib/e2ee/qolmoutboundsession.h lib/e2ee/qolmoutboundsession.cpp lib/e2ee/qolmutils.h lib/e2ee/qolmutils.cpp lib/e2ee/qolmutility.h lib/e2ee/qolmutility.cpp - lib/e2ee/qolmerrors.h lib/e2ee/qolmerrors.cpp lib/e2ee/qolmsession.h lib/e2ee/qolmsession.cpp lib/e2ee/qolmmessage.h lib/e2ee/qolmmessage.cpp lib/events/keyverificationevent.h diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 280705d0..eb428661 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -25,7 +25,8 @@ void TestOlmAccount::pickleUnpickledTest() auto identityKeys = olmAccount.identityKeys(); auto pickled = olmAccount.pickle(Unencrypted{}).value(); QOlmAccount olmAccount2(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); - olmAccount2.unpickle(pickled, Unencrypted{}); + auto unpickleResult = olmAccount2.unpickle(pickled, Unencrypted{}); + QCOMPARE(unpickleResult, 0); auto identityKeys2 = olmAccount2.identityKeys(); QCOMPARE(identityKeys.curve25519, identityKeys2.curve25519); QCOMPARE(identityKeys.ed25519, identityKeys2.ed25519); diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 182659e7..66a04241 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -12,9 +12,13 @@ std::pair createSessionPair() QByteArray pickledAccountA("eOBXIKivUT6YYowRH031BNv7zNmzqM5B7CpXdyeaPvala5mt7/OeqrG1qVA7vA1SYloFyvJPIy0QNkD3j1HiPl5vtZHN53rtfZ9exXDok03zjmssqn4IJsqcA7Fbo1FZeKafG0NFcWwCPTdmcV7REqxjqGm3I4K8MQFa45AdTGSUu2C12cWeOcbSMlcINiMral+Uyah1sgPmLJ18h1qcnskXUXQvpffZ5DiUw1Iz5zxnwOQF1GVyowPJD7Zdugvj75RQnDxAn6CzyvrY2k2CuedwqDC3fIXM2xdUNWttW4nC2g4InpBhCVvNwhZYxlUb5BUEjmPI2AB3dAL5ry6o9MFncmbN6x5x"); QByteArray pickledAccountB("eModTvoFi9oOIkax4j4nuxw9Tcl/J8mOmUctUWI68Q89HSaaPTqR+tdlKQ85v2GOs5NlZCp7EuycypN9GQ4fFbHUCrS7nspa3GFBWsR8PnM8+wez5PWmfFZLg3drOvT0jbMjpDx0MjGYClHBqcrEpKx9oFaIRGBaX6HXzT4lRaWSJkXxuX92q8iGNrLn96PuAWFNcD+2JXpPcNFntslwLUNgqzpZ04aIFYwL80GmzyOgq3Bz1GO6u3TgCQEAmTIYN2QkO0MQeuSfe7UoMumhlAJ6R8GPcdSSPtmXNk4tdyzzlgpVq1hm7ZLKto+g8/5Aq3PvnvA8wCqno2+Pi1duK1pZFTIlActr"); auto accountA = QOlmAccount("accountA:foo.com", "Device1UserA"); - accountA.unpickle(pickledAccountA, Unencrypted{}); + if (accountA.unpickle(pickledAccountA, Unencrypted{}) != OLM_SUCCESS) + qFatal("Failed to unpickle account A: %s", accountA.lastError()); + auto accountB = QOlmAccount("accountB:foo.com", "Device1UserB"); - accountB.unpickle(pickledAccountB, Unencrypted{}); + if (accountB.unpickle(pickledAccountB, Unencrypted{}) != OLM_SUCCESS) + qFatal("Failed to unpickle account B: %s", accountB.lastError()); + const QByteArray identityKeyA("qIEr3TWcJQt4CP8QoKKJcCaukByIOpgh6erBkhLEa2o"); const QByteArray oneTimeKeyA("WzsbsjD85iB1R32iWxfJdwkgmdz29ClMbJSJziECYwk"); diff --git a/lib/connection.cpp b/lib/connection.cpp index 8ca76ceb..865dff79 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -270,8 +270,7 @@ public: return {}; } auto newSession = std::move(*newSessionResult); - auto error = olmAccount->removeOneTimeKeys(*newSession); - if (error) { + if (olmAccount->removeOneTimeKeys(*newSession) != OLM_SUCCESS) { qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); // Keep going though diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 0772b70a..51ceff67 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -6,21 +6,20 @@ #pragma once #include "converters.h" -#include "expected.h" -#include "qolmerrors.h" #include #include #include -#include -namespace Quotient { +#ifdef Quotient_E2EE_ENABLED +# include "expected.h" + +# include +# include +#endif -constexpr auto CiphertextKeyL = "ciphertext"_ls; -constexpr auto SenderKeyKeyL = "sender_key"_ls; -constexpr auto DeviceIdKeyL = "device_id"_ls; -constexpr auto SessionIdKeyL = "session_id"_ls; +namespace Quotient { constexpr auto AlgorithmKeyL = "algorithm"_ls; constexpr auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; @@ -47,6 +46,7 @@ inline bool isSupportedAlgorithm(const QString& algorithm) != SupportedAlgorithms.cend(); } +#ifdef Quotient_E2EE_ENABLED struct Unencrypted {}; struct Encrypted { QByteArray key; @@ -64,7 +64,8 @@ class QOlmOutboundGroupSession; using QOlmOutboundGroupSessionPtr = std::unique_ptr; template -using QOlmExpected = Expected; +using QOlmExpected = Expected; +#endif struct IdentityKeys { @@ -133,23 +134,6 @@ private: using OneTimeKeys = QHash>; -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; -}; -template -asKeyValueRange(T&) -> asKeyValueRange; - } // namespace Quotient Q_DECLARE_METATYPE(Quotient::SignedOneTimeKey) diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index ccb191f4..d33db8e3 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -18,8 +18,13 @@ using namespace Quotient; // Convert olm error to enum -QOlmError lastError(OlmAccount *account) { - return fromString(olm_account_last_error(account)); +OlmErrorCode QOlmAccount::lastErrorCode() const { + return olm_account_last_error_code(m_account); +} + +const char* QOlmAccount::lastError() const +{ + return olm_account_last_error(m_account); } QOlmAccount::QOlmAccount(const QString& userId, const QString& deviceId, @@ -40,24 +45,24 @@ 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); + if (olm_create_account(m_account, randomData.data(), randomSize) + == olm_error()) { + throw lastError(); } emit needsSave(); } -void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) +OlmErrorCode 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 + if (olm_unpickle_account(m_account, key.data(), key.length(), + pickled.data(), pickled.size()) + == olm_error()) { // Probably log the user out since we have no way of getting to the keys - //throw lastError(m_account); + return lastErrorCode(); } + return OLM_SUCCESS; } QOlmExpected QOlmAccount::pickle(const PicklingMode &mode) @@ -65,11 +70,10 @@ QOlmExpected 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); - } + if (olm_pickle_account(m_account, key.data(), key.length(), + pickleBuffer.data(), pickleLength) + == olm_error()) + return lastErrorCode(); return pickleBuffer; } @@ -77,9 +81,9 @@ 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); + if (olm_account_identity_keys(m_account, keyBuffer.data(), keyLength) + == olm_error()) { + throw lastError(); } const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object(); return IdentityKeys { @@ -92,11 +96,10 @@ 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); + if (olm_account_sign(m_account, message.data(), message.length(), + signatureBuffer.data(), signatureBuffer.length()) + == olm_error()) { + throw lastError(); } return signatureBuffer; } @@ -131,15 +134,15 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); QByteArray randomBuffer = getRandom(randomLength); - const auto error = + const auto result = olm_account_generate_one_time_keys(m_account, numberOfKeys, randomBuffer.data(), randomLength); - if (error == olm_error()) { - throw lastError(m_account); + if (result == olm_error()) { + throw lastError(); } emit needsSave(); - return error; + return result; } UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const @@ -147,11 +150,10 @@ UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); QByteArray oneTimeKeysBuffer(static_cast(oneTimeKeyLength), '0'); - const auto error = olm_account_one_time_keys(m_account, - oneTimeKeysBuffer.data(), - oneTimeKeyLength); - if (error == olm_error()) { - throw lastError(m_account); + if (olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), + oneTimeKeyLength) + == olm_error()) { + throw lastError(); } const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); UnsignedOneTimeKeys oneTimeKeys; @@ -171,16 +173,16 @@ OneTimeKeys QOlmAccount::signOneTimeKeys(const UnsignedOneTimeKeys &keys) const return signedOneTimeKeys; } -std::optional QOlmAccount::removeOneTimeKeys( - const QOlmSession& session) +OlmErrorCode QOlmAccount::removeOneTimeKeys(const QOlmSession& session) { - const auto error = olm_remove_one_time_keys(m_account, session.raw()); - - if (error == olm_error()) { - return lastError(m_account); + if (olm_remove_one_time_keys(m_account, session.raw()) == olm_error()) { + qWarning(E2EE).nospace() + << "Failed to remove one-time keys for session " + << session.sessionId() << ": " << lastError(); + return lastErrorCode(); } emit needsSave(); - return std::nullopt; + return OLM_SUCCESS; } OlmAccount* QOlmAccount::data() { return m_account; } diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index f2a31314..5ad98e47 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -36,7 +36,8 @@ public: //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmAccount`. //! This needs to be called before any other action or use createNewAccount() instead. - void unpickle(QByteArray &pickled, const PicklingMode &mode); + [[nodiscard]] OlmErrorCode unpickle(QByteArray& pickled, + const PicklingMode& mode); //! Serialises an OlmAccount to encrypted Base64. QOlmExpected pickle(const PicklingMode &mode); @@ -69,8 +70,7 @@ public: DeviceKeys deviceKeys() const; //! Remove the one time key used to create the supplied session. - [[nodiscard]] std::optional removeOneTimeKeys( - const QOlmSession& session); + [[nodiscard]] OlmErrorCode removeOneTimeKeys(const QOlmSession& session); //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. //! @@ -92,6 +92,9 @@ public: void markKeysAsPublished(); + OlmErrorCode lastErrorCode() const; + const char *lastError() const; + // HACK do not use directly QOlmAccount(OlmAccount *account); OlmAccount *data(); diff --git a/lib/e2ee/qolmerrors.cpp b/lib/e2ee/qolmerrors.cpp deleted file mode 100644 index 5a60b7e6..00000000 --- a/lib/e2ee/qolmerrors.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - - -#include "qolmerrors.h" -#include "util.h" -#include - -Quotient::QOlmError Quotient::fromString(const char* error_raw) { - const QLatin1String error { error_raw }; - if (error_raw == "BAD_ACCOUNT_KEY"_ls) { - return QOlmError::BadAccountKey; - } else if (error_raw == "BAD_MESSAGE_KEY_ID"_ls) { - return QOlmError::BadMessageKeyId; - } else if (error_raw == "INVALID_BASE64"_ls) { - return QOlmError::InvalidBase64; - } else if (error_raw == "NOT_ENOUGH_RANDOM"_ls) { - return QOlmError::NotEnoughRandom; - } else if (error_raw == "OUTPUT_BUFFER_TOO_SMALL"_ls) { - return QOlmError::OutputBufferTooSmall; - } else { - return QOlmError::Unknown; - } -} diff --git a/lib/e2ee/qolmerrors.h b/lib/e2ee/qolmerrors.h deleted file mode 100644 index 20e61c12..00000000 --- a/lib/e2ee/qolmerrors.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "quotient_export.h" - -namespace Quotient { -//! All errors that could be caused by an operation regarding Olm -//! Errors are named exactly like the ones in libolm. -enum QOlmError -{ - BadAccountKey, - BadMessageFormat, - BadMessageKeyId, - BadMessageMac, - BadMessageVersion, - InvalidBase64, - NotEnoughRandom, - OutputBufferTooSmall, - UnknownMessageIndex, - Unknown, -}; - -QUOTIENT_API QOlmError fromString(const char* error_raw); - -} //namespace Quotient diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 17f06205..870070c2 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -3,20 +3,27 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "e2ee/qolminboundsession.h" -#include +#include "logging.h" + #include +#include +#include using namespace Quotient; -QOlmError lastError(OlmInboundGroupSession *session) { - return fromString(olm_inbound_group_session_last_error(session)); +OlmErrorCode QOlmInboundGroupSession::lastErrorCode() const { + return olm_inbound_group_session_last_error_code(m_groupSession); } -QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session) - : m_groupSession(session) +const char* QOlmInboundGroupSession::lastError() const { + return olm_inbound_group_session_last_error(m_groupSession); } +QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session) + : m_groupSession(session) +{} + QOlmInboundGroupSession::~QOlmInboundGroupSession() { olm_clear_inbound_group_session(m_groupSession); @@ -26,11 +33,12 @@ 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 error = olm_init_inbound_group_session(olmInboundGroupSession, - reinterpret_cast(key.constData()), key.size()); - - if (error == olm_error()) { - throw lastError(olmInboundGroupSession); + if (olm_init_inbound_group_session( + olmInboundGroupSession, + reinterpret_cast(key.constData()), key.size()) + == olm_error()) { + // FIXME: create QOlmInboundGroupSession earlier and use lastError() + throw olm_inbound_group_session_last_error_code(olmInboundGroupSession); } return std::make_unique(olmInboundGroupSession); @@ -41,10 +49,12 @@ std::unique_ptr QOlmInboundGroupSession::import(const Q 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); + if (olm_import_inbound_group_session( + olmInboundGroupSession, + reinterpret_cast(keyBuf.data()), keyBuf.size()) + == olm_error()) { + // FIXME: create QOlmInboundGroupSession earlier and use lastError() + throw olm_inbound_group_session_last_error_code(olmInboundGroupSession); } return std::make_unique(olmInboundGroupSession); @@ -62,10 +72,11 @@ 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); + if (olm_pickle_inbound_group_session(m_groupSession, key.data(), + key.length(), pickledBuf.data(), + pickledBuf.length()) + == olm_error()) { + throw lastError(); } return pickledBuf; } @@ -76,10 +87,12 @@ QOlmExpected QOlmInboundGroupSession::unpickle( 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); + if (olm_unpickle_inbound_group_session(groupSession, key.data(), + key.length(), pickledBuf.data(), + pickledBuf.size()) + == olm_error()) { + // FIXME: create QOlmInboundGroupSession earlier and use lastError() + return olm_inbound_group_session_last_error_code(groupSession); } key.clear(); @@ -105,12 +118,9 @@ QOlmExpected> QOlmInboundGroupSession::decrypt( 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); + if (plaintextLen == olm_error()) { + qWarning(E2EE) << "Failed to decrypt the message:" << lastError(); + return lastErrorCode(); } QByteArray output(plaintextLen, '0'); @@ -123,10 +133,11 @@ QOlmExpected QOlmInboundGroupSession::exportSession(uint32_t message { 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); + if (olm_export_inbound_group_session( + m_groupSession, reinterpret_cast(keyBuf.data()), + keyLength, messageIndex) + == olm_error()) { + return lastErrorCode(); } return keyBuf; } @@ -139,10 +150,11 @@ uint32_t QOlmInboundGroupSession::firstKnownIndex() const QByteArray QOlmInboundGroupSession::sessionId() const { QByteArray sessionIdBuf(olm_inbound_group_session_id_length(m_groupSession), '0'); - const auto error = olm_inbound_group_session_id(m_groupSession, reinterpret_cast(sessionIdBuf.data()), - sessionIdBuf.length()); - if (error == olm_error()) { - throw lastError(m_groupSession); + if (olm_inbound_group_session_id( + m_groupSession, reinterpret_cast(sessionIdBuf.data()), + sessionIdBuf.length()) + == olm_error()) { + throw lastError(); } return sessionIdBuf; } diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 1a9b4415..e8da6355 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -6,7 +6,7 @@ #include "e2ee/e2ee.h" -#include +struct OlmInboundGroupSession; namespace Quotient { @@ -46,6 +46,9 @@ public: QString senderId() const; void setSenderId(const QString& senderId); + OlmErrorCode lastErrorCode() const; + const char* lastError() const; + QOlmInboundGroupSession(OlmInboundGroupSession* session); private: OlmInboundGroupSession* m_groupSession; diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index a2eff2c8..79c16e01 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -3,12 +3,20 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "e2ee/qolmoutboundsession.h" + #include "e2ee/qolmutils.h" +#include + using namespace Quotient; -QOlmError lastError(OlmOutboundGroupSession *session) { - return fromString(olm_outbound_group_session_last_error(session)); +OlmErrorCode QOlmOutboundGroupSession::lastErrorCode() const { + return olm_outbound_group_session_last_error_code(m_groupSession); +} + +const char* QOlmOutboundGroupSession::lastError() const +{ + return olm_outbound_group_session_last_error(m_groupSession); } QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session) @@ -27,11 +35,12 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() 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); + if (olm_init_outbound_group_session( + olmOutboundGroupSession, + reinterpret_cast(randomBuf.data()), randomBuf.length()) + == olm_error()) { + // FIXME: create the session object earlier and use lastError() + throw olm_outbound_group_session_last_error_code(olmOutboundGroupSession); } const auto keyMaxLength = olm_outbound_group_session_key_length(olmOutboundGroupSession); @@ -48,12 +57,11 @@ QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mo { 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); - } + if (olm_pickle_outbound_group_session(m_groupSession, key.data(), + key.length(), pickledBuf.data(), + pickledBuf.length()) + == olm_error()) + return lastErrorCode(); key.clear(); @@ -65,10 +73,13 @@ QOlmExpected QOlmOutboundGroupSession::unpickle(con 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(), - pickledBuf.data(), pickledBuf.length()); - if (error == olm_error()) { - return lastError(olmOutboundGroupSession); + if (olm_unpickle_outbound_group_session(olmOutboundGroupSession, key.data(), + key.length(), pickledBuf.data(), + pickledBuf.length()) + == olm_error()) { + // FIXME: create the session object earlier and use lastError() + return olm_outbound_group_session_last_error_code( + olmOutboundGroupSession); } const auto idMaxLength = olm_outbound_group_session_id_length(olmOutboundGroupSession); QByteArray idBuffer(idMaxLength, '0'); @@ -84,12 +95,13 @@ QOlmExpected QOlmOutboundGroupSession::encrypt(const QString &plaint 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); - } + if (olm_group_encrypt(m_groupSession, + reinterpret_cast(plaintextBuf.data()), + plaintextBuf.length(), + reinterpret_cast(messageBuf.data()), + messageBuf.length()) + == olm_error()) + return lastErrorCode(); return messageBuf; } @@ -103,11 +115,12 @@ 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); - } + if (olm_outbound_group_session_id( + m_groupSession, reinterpret_cast(idBuffer.data()), + idBuffer.length()) + == olm_error()) + throw lastError(); + return idBuffer; } @@ -115,12 +128,12 @@ QOlmExpected 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); - } + if (olm_outbound_group_session_key( + m_groupSession, reinterpret_cast(keyBuffer.data()), + keyMaxLength) + == olm_error()) + return lastErrorCode(); + return keyBuffer; } diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 9a82d22a..cd26fc67 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -6,8 +6,7 @@ #include "e2ee/e2ee.h" -#include -#include +struct OlmOutboundGroupSession; namespace Quotient { @@ -51,6 +50,10 @@ public: QDateTime creationTime() const; void setCreationTime(const QDateTime& creationTime); + + OlmErrorCode lastErrorCode() const; + const char* lastError() const; + private: OlmOutboundGroupSession *m_groupSession; int m_messageCount = 0; diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 2a98d5d8..771d310d 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -12,8 +12,13 @@ using namespace Quotient; -QOlmError lastError(OlmSession* session) { - return fromString(olm_session_last_error(session)); +OlmErrorCode QOlmSession::lastErrorCode() const { + return olm_session_last_error_code(m_session); +} + +const char* QOlmSession::lastError() const +{ + return olm_session_last_error(m_session); } Quotient::QOlmSession::~QOlmSession() @@ -32,7 +37,8 @@ QOlmExpected QOlmSession::createInbound( const QString& theirIdentityKey) { if (preKeyMessage.type() != QOlmMessage::PreKey) { - qCCritical(E2EE) << "The message is not a pre-key in when creating inbound session" << BadMessageFormat; + qCCritical(E2EE) << "The message is not a pre-key; will try to create " + "the inbound session anyway"; } const auto olmSession = create(); @@ -47,7 +53,8 @@ QOlmExpected QOlmSession::createInbound( } if (error == olm_error()) { - const auto lastErr = lastError(olmSession); + // FIXME: the QOlmSession object should be created earlier + const auto lastErr = olm_session_last_error_code(olmSession); qCWarning(E2EE) << "Error when creating inbound session" << lastErr; return lastErr; } @@ -78,15 +85,17 @@ QOlmExpected QOlmSession::createOutboundSession( 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) { + if (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()) + == olm_error()) { + // FIXME: the QOlmSession object should be created earlier + const auto lastErr = olm_session_last_error_code(olmOutboundSession); + if (lastErr == OLM_NOT_ENOUGH_RANDOM) { throw lastErr; } return lastErr; @@ -100,16 +109,12 @@ QOlmExpected QOlmSession::pickle(const PicklingMode &mode) const { 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); - } + if (olm_pickle_session(m_session, key.data(), key.length(), + pickledBuf.data(), pickledBuf.length()) + == olm_error()) + return lastErrorCode(); key.clear(); - return pickledBuf; } @@ -119,10 +124,11 @@ QOlmExpected QOlmSession::unpickle(const QByteArray& pickled, 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); + if (olm_unpickle_session(olmSession, key.data(), key.length(), + pickledBuf.data(), pickledBuf.length()) + == olm_error()) { + // FIXME: the QOlmSession object should be created earlier + return olm_session_last_error_code(olmSession); } key.clear(); @@ -137,13 +143,14 @@ QOlmMessage QOlmSession::encrypt(const QString &plaintext) 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); + if (olm_encrypt(m_session, reinterpret_cast(plaintextBuf.data()), + plaintextBuf.length(), + reinterpret_cast(randomBuf.data()), + randomBuf.length(), + reinterpret_cast(messageBuf.data()), + messageBuf.length()) + == olm_error()) { + throw lastError(); } return QOlmMessage(messageBuf, messageType); @@ -163,9 +170,8 @@ QOlmExpected QOlmSession::decrypt(const QOlmMessage &message) const 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); + return lastError(); } QByteArray plaintextBuf(plaintextMaxLen, '0'); @@ -175,10 +181,9 @@ QOlmExpected QOlmSession::decrypt(const QOlmMessage &message) const 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) { + const auto lastErr = lastErrorCode(); + if (lastErr == OLM_OUTPUT_BUFFER_TOO_SMALL) { throw lastErr; } return lastErr; @@ -193,7 +198,7 @@ QOlmMessage::Type QOlmSession::encryptMessageType() { const auto messageTypeResult = olm_encrypt_message_type(m_session); if (messageTypeResult == olm_error()) { - throw lastError(m_session); + throw lastError(); } if (messageTypeResult == OLM_MESSAGE_TYPE_PRE_KEY) { return QOlmMessage::PreKey; @@ -205,10 +210,10 @@ 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); + if (olm_session_id(m_session, reinterpret_cast(idBuffer.data()), + idBuffer.length()) + == olm_error()) { + throw lastError(); } return idBuffer; } @@ -225,11 +230,10 @@ bool QOlmSession::matchesInboundSession(const QOlmMessage& preKeyMessage) const const auto maybeMatches = olm_matches_inbound_session(m_session, oneTimeKeyBuf.data(), oneTimeKeyBuf.length()); - - if (maybeMatches == olm_error()) { - return lastError(m_session); - } - return maybeMatches == 1; + if (maybeMatches == olm_error()) + qWarning(E2EE) << "Error matching an inbound session:" + << olm_session_last_error(m_session); + return maybeMatches == 1; // Any errors are treated as non-match } bool QOlmSession::matchesInboundSessionFrom( diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index 021092c7..cc988a03 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -6,7 +6,6 @@ #include "e2ee/e2ee.h" #include "e2ee/qolmmessage.h" -#include "e2ee/qolmerrors.h" #include "e2ee/qolmaccount.h" struct OlmSession; @@ -71,6 +70,9 @@ public: return *lhs < *rhs; } + OlmErrorCode lastErrorCode() const; + const char* lastError() const; + OlmSession* raw() const { return m_session; } QOlmSession(OlmSession* session); diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index 84559085..15c875c0 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -8,9 +8,13 @@ using namespace Quotient; -// Convert olm error to enum -QOlmError lastError(OlmUtility *utility) { - return fromString(olm_utility_last_error(utility)); +OlmErrorCode QOlmUtility::lastErrorCode() const { + return olm_utility_last_error_code(m_utility); +} + +const char* QOlmUtility::lastError() const +{ + return olm_utility_last_error(m_utility); } QOlmUtility::QOlmUtility() @@ -48,15 +52,15 @@ QOlmExpected QOlmUtility::ed25519Verify(const QByteArray& key, 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()); - + message.data(), message.size(), + (void*)signatureBuf.data(), + signatureBuf.size()); if (ret == olm_error()) { - auto error = lastError(m_utility); - if (error == QOlmError::BadMessageMac) { + auto error = lastErrorCode(); + if (error == OLM_BAD_MESSAGE_MAC) return false; - } return error; } - return !ret; // ret == 0 means success + return ret == 0; } diff --git a/lib/e2ee/qolmutility.h b/lib/e2ee/qolmutility.h index 5f6bcdc5..89277385 100644 --- a/lib/e2ee/qolmutility.h +++ b/lib/e2ee/qolmutility.h @@ -32,8 +32,10 @@ public: QOlmExpected ed25519Verify(const QByteArray &key, const QByteArray &message, const QByteArray &signature); + OlmErrorCode lastErrorCode() const; + const char* lastError() const; + private: OlmUtility *m_utility; - }; } diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 94b44901..114addb5 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedevent.h" +#include "e2ee/e2ee.h" #include "logging.h" using namespace Quotient; diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 02d4c7aa..e24e5745 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -3,10 +3,15 @@ #pragma once -#include "e2ee/e2ee.h" #include "roomevent.h" namespace Quotient { + +constexpr auto CiphertextKeyL = "ciphertext"_ls; +constexpr auto SenderKeyKeyL = "sender_key"_ls; +constexpr auto DeviceIdKeyL = "device_id"_ls; +constexpr auto SessionIdKeyL = "session_id"_ls; + /* * While the specification states: * diff --git a/lib/room.cpp b/lib/room.cpp index 95dd0ab7..382d0243 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -65,7 +65,6 @@ #ifdef Quotient_E2EE_ENABLED #include "e2ee/e2ee.h" #include "e2ee/qolmaccount.h" -#include "e2ee/qolmerrors.h" #include "e2ee/qolminboundsession.h" #include "e2ee/qolmutility.h" #include "database.h" diff --git a/lib/util.h b/lib/util.h index 9efda5d1..ab219488 100644 --- a/lib/util.h +++ b/lib/util.h @@ -111,6 +111,23 @@ private: iterator to; }; +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; +}; +template +asKeyValueRange(T&) -> asKeyValueRange; + /** A replica of std::find_first_of that returns a pair of iterators * * Convenient for cases when you need to know which particular "first of" -- cgit v1.2.3 From bcc05aa1d52cae2b6d8e70bb6cf04fa49904687a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 21 Sep 2022 15:45:59 +0200 Subject: Cleanup across E2EE code Notably: - simplified unnecessarily verbose constructs; - formally aligned (no re-numeration was necessary) QOlmMessage::Type with corresponding OLM_ constants; - dropped QOlmSession::encryptMessageType() because it's very sensitive to the order of calling with QOlmSession::encrypt() (and encrypt() itself already calls it and returns the message type); - simplify the return type of pickle() calls that can only fail due to an internal error; - replace const QString& with QStringView or const QByteArray& where appropriate; - use '\0' where it was meant to be instead of '0'. --- autotests/testgroupsession.cpp | 9 ++-- autotests/testolmaccount.cpp | 3 +- autotests/testolmsession.cpp | 10 +++-- autotests/testolmutility.cpp | 15 +++---- lib/connection.cpp | 8 ++-- lib/e2ee/e2ee.h | 2 +- lib/e2ee/qolmaccount.cpp | 54 +++++++++++----------- lib/e2ee/qolmaccount.h | 9 ++-- lib/e2ee/qolminboundsession.cpp | 57 +++++++++++------------- lib/e2ee/qolminboundsession.h | 4 +- lib/e2ee/qolmmessage.cpp | 6 --- lib/e2ee/qolmmessage.h | 12 +++-- lib/e2ee/qolmoutboundsession.cpp | 44 +++++++++--------- lib/e2ee/qolmoutboundsession.h | 4 +- lib/e2ee/qolmsession.cpp | 96 +++++++++++++++------------------------- lib/e2ee/qolmsession.h | 15 +++---- lib/e2ee/qolmutility.cpp | 24 +++++----- lib/e2ee/qolmutils.h | 1 + lib/events/filesourceinfo.cpp | 35 ++++++--------- lib/keyverificationsession.cpp | 6 +-- 20 files changed, 186 insertions(+), 228 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 3c329a8a..18ace73b 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -17,7 +17,9 @@ void TestGroupSession::groupSessionPicklingValid() QCOMPARE(0, ogs->sessionMessageIndex()); auto ogsPickled = ogs->pickle(Unencrypted {}).value(); - auto ogs2 = QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {}).value(); + auto ogs2 = + QOlmOutboundGroupSession::unpickle(std::move(ogsPickled), Unencrypted{}) + .value(); QCOMPARE(ogsId, ogs2->sessionId()); auto igs = QOlmInboundGroupSession::create(ogs->sessionKey().value()); @@ -29,7 +31,8 @@ void TestGroupSession::groupSessionPicklingValid() QCOMPARE(0, igs->firstKnownIndex()); auto igsPickled = igs->pickle(Unencrypted {}); - igs = QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {}).value(); + igs = QOlmInboundGroupSession::unpickle(std::move(igsPickled), Unencrypted{}) + .value(); QCOMPARE(igsId, igs->sessionId()); } @@ -39,7 +42,7 @@ void TestGroupSession::groupSessionCryptoValid() auto igs = QOlmInboundGroupSession::create(ogs->sessionKey().value()); QCOMPARE(ogs->sessionId(), igs->sessionId()); - const auto plainText = QStringLiteral("Hello world!"); + const auto plainText = "Hello world!"; const auto ciphertext = ogs->encrypt(plainText).value(); // ciphertext valid base64? QVERIFY(QByteArray::fromBase64(ciphertext).size() > 0); diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index eb428661..0e1eab84 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -25,7 +25,8 @@ void TestOlmAccount::pickleUnpickledTest() auto identityKeys = olmAccount.identityKeys(); auto pickled = olmAccount.pickle(Unencrypted{}).value(); QOlmAccount olmAccount2(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); - auto unpickleResult = olmAccount2.unpickle(pickled, Unencrypted{}); + auto unpickleResult = olmAccount2.unpickle(std::move(pickled), + Unencrypted{}); QCOMPARE(unpickleResult, 0); auto identityKeys2 = olmAccount2.identityKeys(); QCOMPARE(identityKeys.curve25519, identityKeys2.curve25519); diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 66a04241..18b0d5f2 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -11,12 +11,14 @@ std::pair createSessionPair() { QByteArray pickledAccountA("eOBXIKivUT6YYowRH031BNv7zNmzqM5B7CpXdyeaPvala5mt7/OeqrG1qVA7vA1SYloFyvJPIy0QNkD3j1HiPl5vtZHN53rtfZ9exXDok03zjmssqn4IJsqcA7Fbo1FZeKafG0NFcWwCPTdmcV7REqxjqGm3I4K8MQFa45AdTGSUu2C12cWeOcbSMlcINiMral+Uyah1sgPmLJ18h1qcnskXUXQvpffZ5DiUw1Iz5zxnwOQF1GVyowPJD7Zdugvj75RQnDxAn6CzyvrY2k2CuedwqDC3fIXM2xdUNWttW4nC2g4InpBhCVvNwhZYxlUb5BUEjmPI2AB3dAL5ry6o9MFncmbN6x5x"); QByteArray pickledAccountB("eModTvoFi9oOIkax4j4nuxw9Tcl/J8mOmUctUWI68Q89HSaaPTqR+tdlKQ85v2GOs5NlZCp7EuycypN9GQ4fFbHUCrS7nspa3GFBWsR8PnM8+wez5PWmfFZLg3drOvT0jbMjpDx0MjGYClHBqcrEpKx9oFaIRGBaX6HXzT4lRaWSJkXxuX92q8iGNrLn96PuAWFNcD+2JXpPcNFntslwLUNgqzpZ04aIFYwL80GmzyOgq3Bz1GO6u3TgCQEAmTIYN2QkO0MQeuSfe7UoMumhlAJ6R8GPcdSSPtmXNk4tdyzzlgpVq1hm7ZLKto+g8/5Aq3PvnvA8wCqno2+Pi1duK1pZFTIlActr"); - auto accountA = QOlmAccount("accountA:foo.com", "Device1UserA"); - if (accountA.unpickle(pickledAccountA, Unencrypted{}) != OLM_SUCCESS) + auto accountA = QOlmAccount(u"accountA:foo.com", u"Device1UserA"); + if (accountA.unpickle(std::move(pickledAccountA), Unencrypted{}) + != OLM_SUCCESS) qFatal("Failed to unpickle account A: %s", accountA.lastError()); - auto accountB = QOlmAccount("accountB:foo.com", "Device1UserB"); - if (accountB.unpickle(pickledAccountB, Unencrypted{}) != OLM_SUCCESS) + auto accountB = QOlmAccount(u"accountB:foo.com", u"Device1UserB"); + if (accountB.unpickle(std::move(pickledAccountB), Unencrypted{}) + != OLM_SUCCESS) qFatal("Failed to unpickle account B: %s", accountB.lastError()); diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index 5b67c805..64ceb3e7 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -49,7 +49,7 @@ void TestOlmUtility::canonicalJSON() void TestOlmUtility::verifySignedOneTimeKey() { - QOlmAccount aliceOlm { "@alice:matrix.org", "aliceDevice" }; + QOlmAccount aliceOlm { u"@alice:matrix.org", u"aliceDevice" }; aliceOlm.createNewAccount(); aliceOlm.generateOneTimeKeys(1); auto keys = aliceOlm.oneTimeKeys(); @@ -64,16 +64,13 @@ void TestOlmUtility::verifySignedOneTimeKey() auto utility = olm_utility(utilityBuf); - QByteArray signatureBuf1(sig.length(), '0'); + QByteArray signatureBuf1(sig.length(), '\0'); std::copy(sig.begin(), sig.end(), signatureBuf1.begin()); - auto res = olm_ed25519_verify(utility, - aliceOlm.identityKeys().ed25519.data(), - aliceOlm.identityKeys().ed25519.size(), - msg.data(), - msg.size(), - (void *)sig.data(), - sig.size()); + auto res = + olm_ed25519_verify(utility, aliceOlm.identityKeys().ed25519.data(), + aliceOlm.identityKeys().ed25519.size(), msg.data(), + msg.size(), sig.data(), sig.size()); QCOMPARE(std::string(olm_utility_last_error(utility)), "SUCCESS"); QCOMPARE(res, 0); diff --git a/lib/connection.cpp b/lib/connection.cpp index 865dff79..f38bb751 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -652,8 +652,7 @@ void Connection::Private::completeSetup(const QString& mxId) }); } else { // account already existing - auto pickle = database->accountPickle(); - olmAccount->unpickle(pickle, picklingMode); + olmAccount->unpickle(database->accountPickle(), picklingMode); } #endif // Quotient_E2EE_ENABLED emit q->stateChanged(); @@ -2300,14 +2299,13 @@ std::pair Connection::Private::olmEncryptMessage( { const auto& curveKey = curveKeyForUserDevice(userId, device); const auto& olmSession = olmSessions.at(curveKey).front(); - QOlmMessage::Type type = olmSession->encryptMessageType(); const auto result = olmSession->encrypt(message); if (const auto pickle = olmSession->pickle(picklingMode)) { database->updateOlmSession(curveKey, olmSession->sessionId(), *pickle); } else { qWarning(E2EE) << "Failed to pickle olm session: " << pickle.error(); } - return { type, result.toCiphertext() }; + return { result.type(), result.toCiphertext() }; } bool Connection::Private::createOlmSession(const QString& targetUserId, @@ -2343,7 +2341,7 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, return false; } const auto recipientCurveKey = - curveKeyForUserDevice(targetUserId, targetDeviceId); + curveKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(); auto session = QOlmSession::createOutboundSession(olmAccount.get(), recipientCurveKey, signedOneTimeKey->key()); diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 51ceff67..5999c0be 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -98,7 +98,7 @@ public: {} //! Unpadded Base64-encoded 32-byte Curve25519 public key - QString key() const { return payload["key"_ls].toString(); } + QByteArray key() const { return payload["key"_ls].toString().toLatin1(); } //! \brief Signatures of the key object //! diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index d33db8e3..556a8274 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -27,11 +27,11 @@ const char* QOlmAccount::lastError() const return olm_account_last_error(m_account); } -QOlmAccount::QOlmAccount(const QString& userId, const QString& deviceId, +QOlmAccount::QOlmAccount(QStringView userId, QStringView deviceId, QObject* parent) : QObject(parent) - , m_userId(userId) - , m_deviceId(deviceId) + , m_userId(userId.toString()) + , m_deviceId(deviceId.toString()) {} QOlmAccount::~QOlmAccount() @@ -43,20 +43,20 @@ QOlmAccount::~QOlmAccount() 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); - if (olm_create_account(m_account, randomData.data(), randomSize) + const auto randomLength = olm_create_account_random_length(m_account); + QByteArray randomData = getRandom(randomLength); + if (olm_create_account(m_account, randomData.data(), randomLength) == olm_error()) { throw lastError(); } emit needsSave(); } -OlmErrorCode QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) +OlmErrorCode QOlmAccount::unpickle(QByteArray&& pickled, const PicklingMode &mode) { m_account = olm_account(new uint8_t[olm_account_size()]); - const QByteArray key = toKey(mode); - if (olm_unpickle_account(m_account, key.data(), key.length(), + if (const auto key = toKey(mode); + olm_unpickle_account(m_account, key.data(), key.length(), pickled.data(), pickled.size()) == olm_error()) { // Probably log the user out since we have no way of getting to the keys @@ -69,7 +69,7 @@ QOlmExpected QOlmAccount::pickle(const PicklingMode &mode) { const QByteArray key = toKey(mode); const size_t pickleLength = olm_pickle_account_length(m_account); - QByteArray pickleBuffer(pickleLength, '0'); + QByteArray pickleBuffer(pickleLength, '\0'); if (olm_pickle_account(m_account, key.data(), key.length(), pickleBuffer.data(), pickleLength) == olm_error()) @@ -80,12 +80,12 @@ QOlmExpected QOlmAccount::pickle(const PicklingMode &mode) IdentityKeys QOlmAccount::identityKeys() const { const size_t keyLength = olm_account_identity_keys_length(m_account); - QByteArray keyBuffer(keyLength, '0'); + QByteArray keyBuffer(keyLength, '\0'); if (olm_account_identity_keys(m_account, keyBuffer.data(), keyLength) == olm_error()) { throw lastError(); } - const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object(); + const auto key = QJsonDocument::fromJson(keyBuffer).object(); return IdentityKeys { key.value(QStringLiteral("curve25519")).toString().toUtf8(), key.value(QStringLiteral("ed25519")).toString().toUtf8() @@ -94,7 +94,7 @@ IdentityKeys QOlmAccount::identityKeys() const QByteArray QOlmAccount::sign(const QByteArray &message) const { - QByteArray signatureBuffer(olm_account_signature_length(m_account), '0'); + QByteArray signatureBuffer(olm_account_signature_length(m_account), '\0'); if (olm_account_sign(m_account, message.data(), message.length(), signatureBuffer.data(), signatureBuffer.length()) @@ -112,15 +112,15 @@ QByteArray QOlmAccount::sign(const QJsonObject &message) const QByteArray QOlmAccount::signIdentityKeys() const { const auto keys = identityKeys(); - return sign(QJsonObject { - { "algorithms", QJsonArray { "m.olm.v1.curve25519-aes-sha2", - "m.megolm.v1.aes-sha2" } }, + return sign(QJsonObject{ + { "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) } } } }); + { "keys", QJsonObject{ { QStringLiteral("curve25519:") + m_deviceId, + QString::fromUtf8(keys.curve25519) }, + { QStringLiteral("ed25519:") + m_deviceId, + QString::fromUtf8(keys.ed25519) } } } }); } size_t QOlmAccount::maxNumberOfOneTimeKeys() const @@ -130,7 +130,7 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) { - const size_t randomLength = + const auto randomLength = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); QByteArray randomBuffer = getRandom(randomLength); @@ -147,8 +147,8 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const { - const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); - QByteArray oneTimeKeysBuffer(static_cast(oneTimeKeyLength), '0'); + const auto oneTimeKeyLength = olm_account_one_time_keys_length(m_account); + QByteArray oneTimeKeysBuffer(static_cast(oneTimeKeyLength), '\0'); if (olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), oneTimeKeyLength) @@ -193,13 +193,13 @@ DeviceKeys QOlmAccount::deviceKeys() const SupportedAlgorithms.cend()); const auto idKeys = identityKeys(); - return DeviceKeys { + return DeviceKeys{ .userId = m_userId, .deviceId = m_deviceId, .algorithms = Algorithms, - .keys { { "curve25519:" + m_deviceId, idKeys.curve25519 }, - { "ed25519:" + m_deviceId, idKeys.ed25519 } }, - .signatures { + .keys{ { "curve25519:" + m_deviceId, idKeys.curve25519 }, + { "ed25519:" + m_deviceId, idKeys.ed25519 } }, + .signatures{ { m_userId, { { "ed25519:" + m_deviceId, signIdentityKeys() } } } } }; } diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 5ad98e47..0fb9803f 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -24,7 +24,8 @@ class QUOTIENT_API QOlmAccount : public QObject { Q_OBJECT public: - QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent = nullptr); + QOlmAccount(QStringView userId, QStringView deviceId, + QObject* parent = nullptr); ~QOlmAccount() override; //! Creates a new instance of OlmAccount. During the instantiation @@ -36,7 +37,7 @@ public: //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmAccount`. //! This needs to be called before any other action or use createNewAccount() instead. - [[nodiscard]] OlmErrorCode unpickle(QByteArray& pickled, + [[nodiscard]] OlmErrorCode unpickle(QByteArray&& pickled, const PicklingMode& mode); //! Serialises an OlmAccount to encrypted Base64. @@ -74,7 +75,7 @@ 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. + //! \param preKeyMessage An Olm pre-key message that was encrypted for this account. QOlmExpected createInboundSession( const QOlmMessage& preKeyMessage); @@ -93,7 +94,7 @@ public: void markKeysAsPublished(); OlmErrorCode lastErrorCode() const; - const char *lastError() const; + const char* lastError() const; // HACK do not use directly QOlmAccount(OlmAccount *account); diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 870070c2..a05ddf62 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -2,8 +2,9 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include "e2ee/qolminboundsession.h" -#include "logging.h" +#include "qolminboundsession.h" +#include "qolmutils.h" +#include "../logging.h" #include #include @@ -37,7 +38,7 @@ std::unique_ptr QOlmInboundGroupSession::create(const Q olmInboundGroupSession, reinterpret_cast(key.constData()), key.size()) == olm_error()) { - // FIXME: create QOlmInboundGroupSession earlier and use lastError() + // FIXME: create QOlmInboundGroupSession earlier and use lastErrorCode() throw olm_inbound_group_session_last_error_code(olmInboundGroupSession); } @@ -47,11 +48,10 @@ std::unique_ptr QOlmInboundGroupSession::create(const Q 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; if (olm_import_inbound_group_session( olmInboundGroupSession, - reinterpret_cast(keyBuf.data()), keyBuf.size()) + reinterpret_cast(key.data()), key.size()) == olm_error()) { // FIXME: create QOlmInboundGroupSession earlier and use lastError() throw olm_inbound_group_session_last_error_code(olmInboundGroupSession); @@ -60,19 +60,12 @@ std::unique_ptr QOlmInboundGroupSession::import(const Q 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 QOlmInboundGroupSession::pickle(const PicklingMode& mode) const { - QByteArray pickledBuf(olm_pickle_inbound_group_session_length(m_groupSession), '0'); - const QByteArray key = toKey(mode); - if (olm_pickle_inbound_group_session(m_groupSession, key.data(), + QByteArray pickledBuf( + olm_pickle_inbound_group_session_length(m_groupSession), '\0'); + if (const auto key = toKey(mode); + olm_pickle_inbound_group_session(m_groupSession, key.data(), key.length(), pickledBuf.data(), pickledBuf.length()) == olm_error()) { @@ -82,14 +75,13 @@ QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const } QOlmExpected QOlmInboundGroupSession::unpickle( - const QByteArray& pickled, const PicklingMode& mode) + 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); + auto key = toKey(mode); if (olm_unpickle_inbound_group_session(groupSession, key.data(), - key.length(), pickledBuf.data(), - pickledBuf.size()) + key.length(), pickled.data(), + pickled.size()) == olm_error()) { // FIXME: create QOlmInboundGroupSession earlier and use lastError() return olm_inbound_group_session_last_error_code(groupSession); @@ -107,13 +99,16 @@ QOlmExpected> QOlmInboundGroupSession::decrypt( // We need to clone the message because // olm_decrypt_max_plaintext_length destroys the input buffer - QByteArray messageBuf(message.length(), '0'); + 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'); + QByteArray plaintextBuf(olm_group_decrypt_max_plaintext_length( + m_groupSession, + reinterpret_cast(messageBuf.data()), + messageBuf.length()), + '\0'); - messageBuf = QByteArray(message.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()), @@ -123,16 +118,17 @@ QOlmExpected> QOlmInboundGroupSession::decrypt( return lastErrorCode(); } - QByteArray output(plaintextLen, '0'); + QByteArray output(plaintextLen, '\0'); std::memcpy(output.data(), plaintextBuf.data(), plaintextLen); return std::make_pair(output, messageIndex); } -QOlmExpected QOlmInboundGroupSession::exportSession(uint32_t messageIndex) +QOlmExpected QOlmInboundGroupSession::exportSession( + uint32_t messageIndex) { const auto keyLength = olm_export_inbound_group_session_length(m_groupSession); - QByteArray keyBuf(keyLength, '0'); + QByteArray keyBuf(keyLength, '\0'); if (olm_export_inbound_group_session( m_groupSession, reinterpret_cast(keyBuf.data()), keyLength, messageIndex) @@ -149,7 +145,8 @@ uint32_t QOlmInboundGroupSession::firstKnownIndex() const QByteArray QOlmInboundGroupSession::sessionId() const { - QByteArray sessionIdBuf(olm_inbound_group_session_id_length(m_groupSession), '0'); + QByteArray sessionIdBuf(olm_inbound_group_session_id_length(m_groupSession), + '\0'); if (olm_inbound_group_session_id( m_groupSession, reinterpret_cast(sessionIdBuf.data()), sessionIdBuf.length()) diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index e8da6355..0b89349a 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -21,11 +21,11 @@ public: //! 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; + QByteArray pickle(const PicklingMode& mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. static QOlmExpected unpickle( - const QByteArray& pickled, const PicklingMode& mode); + QByteArray&& pickled, const PicklingMode& mode); //! Decrypts ciphertext received for this group session. QOlmExpected > decrypt(const QByteArray& message); //! Export the base64-encoded ratchet key for this session, at the given index, diff --git a/lib/e2ee/qolmmessage.cpp b/lib/e2ee/qolmmessage.cpp index f9b4a5c2..b9cb8bd2 100644 --- a/lib/e2ee/qolmmessage.cpp +++ b/lib/e2ee/qolmmessage.cpp @@ -15,12 +15,6 @@ QOlmMessage::QOlmMessage(QByteArray ciphertext, QOlmMessage::Type type) Q_ASSERT_X(!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; diff --git a/lib/e2ee/qolmmessage.h b/lib/e2ee/qolmmessage.h index b4285a93..ea73b3e3 100644 --- a/lib/e2ee/qolmmessage.h +++ b/lib/e2ee/qolmmessage.h @@ -6,8 +6,9 @@ #include "quotient_export.h" -#include -#include +#include +#include +#include namespace Quotient { @@ -22,15 +23,12 @@ class QUOTIENT_API QOlmMessage : public QByteArray { Q_GADGET public: enum Type { - PreKey = 0, - General, + PreKey = OLM_MESSAGE_TYPE_PRE_KEY, + General = OLM_MESSAGE_TYPE_MESSAGE, }; Q_ENUM(Type) - QOlmMessage() = default; explicit QOlmMessage(QByteArray ciphertext, Type type = General); - explicit QOlmMessage(const QOlmMessage &message); - ~QOlmMessage() = default; static QOlmMessage fromCiphertext(const QByteArray &ciphertext); diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 79c16e01..22107a21 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -2,9 +2,10 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include "e2ee/qolmoutboundsession.h" +#include "qolmoutboundsession.h" -#include "e2ee/qolmutils.h" +#include "logging.h" +#include "qolmutils.h" #include @@ -43,8 +44,9 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() throw olm_outbound_group_session_last_error_code(olmOutboundGroupSession); } + // FIXME: is it used anywhere? const auto keyMaxLength = olm_outbound_group_session_key_length(olmOutboundGroupSession); - QByteArray keyBuffer(keyMaxLength, '0'); + QByteArray keyBuffer(keyMaxLength, '\0'); olm_outbound_group_session_key(olmOutboundGroupSession, reinterpret_cast(keyBuffer.data()), keyMaxLength); @@ -55,8 +57,9 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mode) const { - QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); - QByteArray key = toKey(mode); + QByteArray pickledBuf( + olm_pickle_outbound_group_session_length(m_groupSession), '\0'); + auto key = toKey(mode); if (olm_pickle_outbound_group_session(m_groupSession, key.data(), key.length(), pickledBuf.data(), pickledBuf.length()) @@ -64,40 +67,41 @@ QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mo return lastErrorCode(); key.clear(); - return pickledBuf; } -QOlmExpected QOlmOutboundGroupSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +QOlmExpected 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); + auto key = toKey(mode); if (olm_unpickle_outbound_group_session(olmOutboundGroupSession, key.data(), - key.length(), pickledBuf.data(), - pickledBuf.length()) + key.length(), pickled.data(), + pickled.length()) == olm_error()) { // FIXME: create the session object earlier and use lastError() return olm_outbound_group_session_last_error_code( olmOutboundGroupSession); } const auto idMaxLength = olm_outbound_group_session_id_length(olmOutboundGroupSession); - QByteArray idBuffer(idMaxLength, '0'); + QByteArray idBuffer(idMaxLength, '\0'); olm_outbound_group_session_id(olmOutboundGroupSession, reinterpret_cast(idBuffer.data()), idBuffer.length()); + // FIXME: idBuffer doesn't seem to be used, is it needed here? key.clear(); return std::make_unique(olmOutboundGroupSession); } -QOlmExpected QOlmOutboundGroupSession::encrypt(const QString &plaintext) const +QOlmExpected QOlmOutboundGroupSession::encrypt( + const QByteArray& plaintext) const { - QByteArray plaintextBuf = plaintext.toUtf8(); - const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); - QByteArray messageBuf(messageMaxLength, '0'); + const auto messageMaxLength = + olm_group_encrypt_message_length(m_groupSession, plaintext.length()); + QByteArray messageBuf(messageMaxLength, '\0'); if (olm_group_encrypt(m_groupSession, - reinterpret_cast(plaintextBuf.data()), - plaintextBuf.length(), + reinterpret_cast(plaintext.data()), + plaintext.length(), reinterpret_cast(messageBuf.data()), messageBuf.length()) == olm_error()) @@ -114,7 +118,7 @@ uint32_t QOlmOutboundGroupSession::sessionMessageIndex() const QByteArray QOlmOutboundGroupSession::sessionId() const { const auto idMaxLength = olm_outbound_group_session_id_length(m_groupSession); - QByteArray idBuffer(idMaxLength, '0'); + QByteArray idBuffer(idMaxLength, '\0'); if (olm_outbound_group_session_id( m_groupSession, reinterpret_cast(idBuffer.data()), idBuffer.length()) @@ -127,7 +131,7 @@ QByteArray QOlmOutboundGroupSession::sessionId() const QOlmExpected QOlmOutboundGroupSession::sessionKey() const { const auto keyMaxLength = olm_outbound_group_session_key_length(m_groupSession); - QByteArray keyBuffer(keyMaxLength, '0'); + QByteArray keyBuffer(keyMaxLength, '\0'); if (olm_outbound_group_session_key( m_groupSession, reinterpret_cast(keyBuffer.data()), keyMaxLength) diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index cd26fc67..e5e73ddf 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -24,10 +24,10 @@ public: //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. static QOlmExpected unpickle( - const QByteArray& pickled, const PicklingMode& mode); + QByteArray&& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. - QOlmExpected encrypt(const QString& plaintext) const; + QOlmExpected encrypt(const QByteArray& plaintext) const; //! Get the current message index for this session. //! diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 771d310d..e252c37f 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -76,22 +76,17 @@ QOlmExpected QOlmSession::createInboundSessionFrom( } QOlmExpected QOlmSession::createOutboundSession( - QOlmAccount* account, const QString& theirIdentityKey, - const QString& theirOneTimeKey) + QOlmAccount* account, const QByteArray& theirIdentityKey, + const QByteArray& theirOneTimeKey) { - auto *olmOutboundSession = create(); - const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); - QByteArray randomBuf = getRandom(randomLen); + auto* olmOutboundSession = create(); + auto randomBuf = getRandom( + olm_create_outbound_session_random_length(olmOutboundSession)); - QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); - QByteArray theirOneTimeKeyBuf = theirOneTimeKey.toUtf8(); if (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()) + olmOutboundSession, account->data(), theirIdentityKey.data(), + theirIdentityKey.length(), theirOneTimeKey.data(), + theirOneTimeKey.length(), randomBuf.data(), randomBuf.length()) == olm_error()) { // FIXME: the QOlmSession object should be created earlier const auto lastErr = olm_session_last_error_code(olmOutboundSession); @@ -107,7 +102,7 @@ QOlmExpected QOlmSession::createOutboundSession( QOlmExpected QOlmSession::pickle(const PicklingMode &mode) const { - QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); + QByteArray pickledBuf(olm_pickle_session_length(m_session), '\0'); QByteArray key = toKey(mode); if (olm_pickle_session(m_session, key.data(), key.length(), pickledBuf.data(), pickledBuf.length()) @@ -118,14 +113,13 @@ QOlmExpected QOlmSession::pickle(const PicklingMode &mode) const return pickledBuf; } -QOlmExpected QOlmSession::unpickle(const QByteArray& pickled, +QOlmExpected QOlmSession::unpickle(QByteArray&& pickled, const PicklingMode& mode) { - QByteArray pickledBuf = pickled; auto *olmSession = create(); - QByteArray key = toKey(mode); + auto key = toKey(mode); if (olm_unpickle_session(olmSession, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()) + pickled.data(), pickled.length()) == olm_error()) { // FIXME: the QOlmSession object should be created earlier return olm_session_last_error_code(olmSession); @@ -135,52 +129,48 @@ QOlmExpected QOlmSession::unpickle(const QByteArray& pickled, return std::make_unique(olmSession); } -QOlmMessage QOlmSession::encrypt(const QString &plaintext) +QOlmMessage QOlmSession::encrypt(const QByteArray& 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); - if (olm_encrypt(m_session, reinterpret_cast(plaintextBuf.data()), - plaintextBuf.length(), - reinterpret_cast(randomBuf.data()), - randomBuf.length(), - reinterpret_cast(messageBuf.data()), + const auto messageMaxLength = + olm_encrypt_message_length(m_session, plaintext.length()); + QByteArray messageBuf(messageMaxLength, '0'); + // NB: The type has to be calculated before calling olm_encrypt() + const auto messageType = olm_encrypt_message_type(m_session); + auto randomBuf = getRandom(olm_encrypt_random_length(m_session)); + if (olm_encrypt(m_session, plaintext.data(), plaintext.length(), + randomBuf.data(), randomBuf.length(), messageBuf.data(), messageBuf.length()) == olm_error()) { throw lastError(); } - return QOlmMessage(messageBuf, messageType); + randomBuf.clear(); + return QOlmMessage(messageBuf, QOlmMessage::Type(messageType)); } QOlmExpected 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; + const auto messageTypeValue = message.type(); // We need to clone the message because // olm_decrypt_max_plaintext_length destroys the input buffer - QByteArray messageBuf(ciphertext.length(), '0'); + 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()); + const auto plaintextMaxLen = olm_decrypt_max_plaintext_length( + m_session, messageTypeValue, messageBuf.data(), messageBuf.length()); if (plaintextMaxLen == olm_error()) { return lastError(); } - QByteArray plaintextBuf(plaintextMaxLen, '0'); - QByteArray messageBuf2(ciphertext.length(), '0'); + 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); + const auto plaintextResultLen = + olm_decrypt(m_session, messageTypeValue, messageBuf2.data(), + messageBuf2.length(), plaintextBuf.data(), plaintextMaxLen); if (plaintextResultLen == olm_error()) { const auto lastErr = lastErrorCode(); if (lastErr == OLM_OUTPUT_BUFFER_TOO_SMALL) { @@ -188,31 +178,15 @@ QOlmExpected QOlmSession::decrypt(const QOlmMessage &message) const } 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(); - } - if (messageTypeResult == OLM_MESSAGE_TYPE_PRE_KEY) { - return QOlmMessage::PreKey; - } - return QOlmMessage::General; + plaintextBuf.truncate(plaintextResultLen); + return plaintextBuf; } QByteArray QOlmSession::sessionId() const { const auto idMaxLength = olm_session_id_length(m_session); QByteArray idBuffer(idMaxLength, '0'); - if (olm_session_id(m_session, reinterpret_cast(idBuffer.data()), - idBuffer.length()) - == olm_error()) { + if (olm_session_id(m_session, idBuffer.data(), idMaxLength) == olm_error()) { throw lastError(); } return idBuffer; diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index cc988a03..174e98ef 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -26,18 +26,18 @@ public: const QOlmMessage& preKeyMessage); static QOlmExpected createOutboundSession( - QOlmAccount* account, const QString& theirIdentityKey, - const QString& theirOneTimeKey); + QOlmAccount* account, const QByteArray& theirIdentityKey, + const QByteArray& theirOneTimeKey); //! Serialises an `QOlmSession` to encrypted Base64. QOlmExpected pickle(const PicklingMode &mode) const; - //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static QOlmExpected unpickle( - const QByteArray& pickled, const PicklingMode& mode); + //! Deserialises from encrypted Base64 previously made with pickle() + static QOlmExpected unpickle(QByteArray&& pickled, + const PicklingMode& mode); //! Encrypts a plaintext message using the session. - QOlmMessage encrypt(const QString &plaintext); + QOlmMessage encrypt(const QByteArray& plaintext); //! Decrypts a message using this session. Decoding is lossy, meaning if //! the decrypted plaintext contains invalid UTF-8 symbols, they will @@ -47,9 +47,6 @@ public: //! 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; diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index 15c875c0..08c2b699 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -32,7 +32,7 @@ QOlmUtility::~QOlmUtility() QString QOlmUtility::sha256Bytes(const QByteArray &inputBuf) const { const auto outputLen = olm_sha256_length(m_utility); - QByteArray outputBuf(outputLen, '0'); + QByteArray outputBuf(outputLen, '\0'); olm_sha256(m_utility, inputBuf.data(), inputBuf.length(), outputBuf.data(), outputBuf.length()); @@ -48,19 +48,17 @@ QOlmExpected QOlmUtility::ed25519Verify(const QByteArray& key, const QByteArray& message, const QByteArray& signature) { - QByteArray signatureBuf(signature.length(), '0'); + 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()); - if (ret == olm_error()) { - auto error = lastErrorCode(); - if (error == OLM_BAD_MESSAGE_MAC) - return false; - return error; - } + if (olm_ed25519_verify(m_utility, key.data(), key.size(), message.data(), + message.size(), signatureBuf.data(), + signatureBuf.size()) + == 0) + return true; - return ret == 0; + auto error = lastErrorCode(); + if (error == OLM_BAD_MESSAGE_MAC) + return false; + return error; } diff --git a/lib/e2ee/qolmutils.h b/lib/e2ee/qolmutils.h index f218e628..7a8511c3 100644 --- a/lib/e2ee/qolmutils.h +++ b/lib/e2ee/qolmutils.h @@ -9,6 +9,7 @@ #include "e2ee/e2ee.h" namespace Quotient { + // Convert PicklingMode to key QUOTIENT_API QByteArray toKey(const PicklingMode &mode); QUOTIENT_API QByteArray getRandom(size_t bufferSize); diff --git a/lib/events/filesourceinfo.cpp b/lib/events/filesourceinfo.cpp index e8b6794b..6abe6a08 100644 --- a/lib/events/filesourceinfo.cpp +++ b/lib/events/filesourceinfo.cpp @@ -59,19 +59,15 @@ std::pair Quotient::encryptFile( const QByteArray& plainText) { #ifdef Quotient_E2EE_ENABLED - QByteArray k = getRandom(32); - auto kBase64 = k.toBase64(); - QByteArray iv = getRandom(16); - JWK key = { "oct"_ls, - { "encrypt"_ls, "decrypt"_ls }, - "A256CTR"_ls, - QString(k.toBase64()) - .replace(u'/', u'_') - .replace(u'+', u'-') - .left(kBase64.indexOf('=')), - true }; - - int length; + auto k = getRandom(32); + auto kBase64 = k.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals); + auto iv = getRandom(16); + JWK key = { + "oct"_ls, { "encrypt"_ls, "decrypt"_ls }, "A256CTR"_ls, kBase64, true + }; + + int length = -1; auto* ctx = EVP_CIPHER_CTX_new(); EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, reinterpret_cast(k.data()), @@ -89,14 +85,11 @@ std::pair Quotient::encryptFile( EVP_CIPHER_CTX_free(ctx); auto hash = QCryptographicHash::hash(cipherText, QCryptographicHash::Sha256) - .toBase64(); - auto ivBase64 = iv.toBase64(); - EncryptedFileMetadata efm = { {}, - key, - ivBase64.left(ivBase64.indexOf('=')), - { { QStringLiteral("sha256"), - hash.left(hash.indexOf('=')) } }, - "v2"_ls }; + .toBase64(QByteArray::OmitTrailingEquals); + auto ivBase64 = iv.toBase64(QByteArray::OmitTrailingEquals); + EncryptedFileMetadata efm = { + {}, key, ivBase64, { { QStringLiteral("sha256"), hash } }, "v2"_ls + }; return { efm, cipherText }; #else return {}; diff --git a/lib/keyverificationsession.cpp b/lib/keyverificationsession.cpp index 3f76eac1..0f24c743 100644 --- a/lib/keyverificationsession.cpp +++ b/lib/keyverificationsession.cpp @@ -71,9 +71,9 @@ void KeyVerificationSession::init(milliseconds timeout) QTimer::singleShot(timeout, this, [this] { cancelVerification(TIMEOUT); }); m_sas = olm_sas(new std::byte[olm_sas_size()]); - auto randomSize = olm_create_sas_random_length(m_sas); - auto random = getRandom(randomSize); - olm_create_sas(m_sas, random.data(), randomSize); + const auto randomLength = olm_create_sas_random_length(m_sas); + auto random = getRandom(randomLength); + olm_create_sas(m_sas, random.data(), randomLength); } KeyVerificationSession::~KeyVerificationSession() -- cgit v1.2.3 From bc1ded73bedf593acda80b00eb7da32f688c4843 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 21 Sep 2022 16:11:39 +0200 Subject: RandomBuffer A convenient abstraction swallowing all the type casts and, more importantly, cleanup on destruction (previous code only cleaned up the buffer upon a successful call to Olm API but not upon an error). --- lib/connection.cpp | 2 +- lib/e2ee/qolmaccount.cpp | 9 +++------ lib/e2ee/qolmoutboundsession.cpp | 9 +++------ lib/e2ee/qolmsession.cpp | 12 +++++------- lib/e2ee/qolmutils.cpp | 7 +++---- lib/e2ee/qolmutils.h | 23 ++++++++++++++++++++++- lib/events/filesourceinfo.cpp | 8 +++----- lib/keyverificationsession.cpp | 3 +-- 8 files changed, 41 insertions(+), 32 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index f38bb751..cd8ee727 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -614,7 +614,7 @@ void Connection::Private::completeSetup(const QString& mxId) loop.exec(); if (job.error() == QKeychain::Error::EntryNotFound) { - picklingMode = Encrypted { getRandom(128) }; + picklingMode = Encrypted { RandomBuffer(128) }; QKeychain::WritePasswordJob job(qAppName()); job.setAutoDelete(false); job.setKey(accountSettings.userId() + QStringLiteral("-Pickle")); diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 556a8274..b56272ef 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -44,8 +44,7 @@ void QOlmAccount::createNewAccount() { m_account = olm_account(new uint8_t[olm_account_size()]); const auto randomLength = olm_create_account_random_length(m_account); - QByteArray randomData = getRandom(randomLength); - if (olm_create_account(m_account, randomData.data(), randomLength) + if (olm_create_account(m_account, RandomBuffer(randomLength), randomLength) == olm_error()) { throw lastError(); } @@ -133,10 +132,8 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const auto randomLength = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); - QByteArray randomBuffer = getRandom(randomLength); - const auto result = - olm_account_generate_one_time_keys(m_account, numberOfKeys, - randomBuffer.data(), randomLength); + const auto result = olm_account_generate_one_time_keys( + m_account, numberOfKeys, RandomBuffer(randomLength), randomLength); if (result == olm_error()) { throw lastError(); diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 22107a21..3d176274 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -34,11 +34,10 @@ QOlmOutboundGroupSessionPtr 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); - if (olm_init_outbound_group_session( - olmOutboundGroupSession, - reinterpret_cast(randomBuf.data()), randomBuf.length()) + if (olm_init_outbound_group_session(olmOutboundGroupSession, + RandomBuffer(randomLength).bytes(), + randomLength) == olm_error()) { // FIXME: create the session object earlier and use lastError() throw olm_outbound_group_session_last_error_code(olmOutboundGroupSession); @@ -50,8 +49,6 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() olm_outbound_group_session_key(olmOutboundGroupSession, reinterpret_cast(keyBuffer.data()), keyMaxLength); - randomBuf.clear(); - return std::make_unique(olmOutboundGroupSession); } diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index e252c37f..7c102a96 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -80,13 +80,13 @@ QOlmExpected QOlmSession::createOutboundSession( const QByteArray& theirOneTimeKey) { auto* olmOutboundSession = create(); - auto randomBuf = getRandom( - olm_create_outbound_session_random_length(olmOutboundSession)); + const auto randomLength = + olm_create_outbound_session_random_length(olmOutboundSession); if (olm_create_outbound_session( olmOutboundSession, account->data(), theirIdentityKey.data(), theirIdentityKey.length(), theirOneTimeKey.data(), - theirOneTimeKey.length(), randomBuf.data(), randomBuf.length()) + theirOneTimeKey.length(), RandomBuffer(randomLength), randomLength) == olm_error()) { // FIXME: the QOlmSession object should be created earlier const auto lastErr = olm_session_last_error_code(olmOutboundSession); @@ -96,7 +96,6 @@ QOlmExpected QOlmSession::createOutboundSession( return lastErr; } - randomBuf.clear(); return std::make_unique(olmOutboundSession); } @@ -136,15 +135,14 @@ QOlmMessage QOlmSession::encrypt(const QByteArray& plaintext) QByteArray messageBuf(messageMaxLength, '0'); // NB: The type has to be calculated before calling olm_encrypt() const auto messageType = olm_encrypt_message_type(m_session); - auto randomBuf = getRandom(olm_encrypt_random_length(m_session)); + const auto randomLength = olm_encrypt_random_length(m_session); if (olm_encrypt(m_session, plaintext.data(), plaintext.length(), - randomBuf.data(), randomBuf.length(), messageBuf.data(), + RandomBuffer(randomLength), randomLength, messageBuf.data(), messageBuf.length()) == olm_error()) { throw lastError(); } - randomBuf.clear(); return QOlmMessage(messageBuf, QOlmMessage::Type(messageType)); } diff --git a/lib/e2ee/qolmutils.cpp b/lib/e2ee/qolmutils.cpp index 6f7937e8..c6e51bcd 100644 --- a/lib/e2ee/qolmutils.cpp +++ b/lib/e2ee/qolmutils.cpp @@ -15,9 +15,8 @@ QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) return std::get(mode).key; } -QByteArray Quotient::getRandom(size_t bufferSize) +RandomBuffer::RandomBuffer(size_t size) + : QByteArray(static_cast(size), '\0') { - QByteArray buffer(bufferSize, '0'); - QRandomGenerator::system()->generate(buffer.begin(), buffer.end()); - return buffer; + QRandomGenerator::system()->generate(begin(), end()); } diff --git a/lib/e2ee/qolmutils.h b/lib/e2ee/qolmutils.h index 7a8511c3..da9d2d18 100644 --- a/lib/e2ee/qolmutils.h +++ b/lib/e2ee/qolmutils.h @@ -12,5 +12,26 @@ namespace Quotient { // Convert PicklingMode to key QUOTIENT_API QByteArray toKey(const PicklingMode &mode); -QUOTIENT_API QByteArray getRandom(size_t bufferSize); + +class QUOTIENT_API RandomBuffer : public QByteArray { +public: + explicit RandomBuffer(size_t size); + ~RandomBuffer() { clear(); } + + // NOLINTNEXTLINE(google-explicit-constructor) + QUO_IMPLICIT operator void*() { return data(); } + char* chars() { return data(); } + uint8_t* bytes() { return reinterpret_cast(data()); } + + Q_DISABLE_COPY(RandomBuffer) + RandomBuffer(RandomBuffer&&) = default; + void operator=(RandomBuffer&&) = delete; +}; + +[[deprecated("Create RandomBuffer directly")]] inline auto getRandom( + size_t bufferSize) +{ + return RandomBuffer(bufferSize); } + +} // namespace Quotient diff --git a/lib/events/filesourceinfo.cpp b/lib/events/filesourceinfo.cpp index 6abe6a08..a60d86d2 100644 --- a/lib/events/filesourceinfo.cpp +++ b/lib/events/filesourceinfo.cpp @@ -59,19 +59,17 @@ std::pair Quotient::encryptFile( const QByteArray& plainText) { #ifdef Quotient_E2EE_ENABLED - auto k = getRandom(32); + auto k = RandomBuffer(32); auto kBase64 = k.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - auto iv = getRandom(16); + auto iv = RandomBuffer(16); JWK key = { "oct"_ls, { "encrypt"_ls, "decrypt"_ls }, "A256CTR"_ls, kBase64, true }; int length = -1; auto* ctx = EVP_CIPHER_CTX_new(); - EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, - reinterpret_cast(k.data()), - reinterpret_cast(iv.data())); + EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, k.bytes(), iv.bytes()); const auto blockSize = EVP_CIPHER_CTX_block_size(ctx); QByteArray cipherText(plainText.size() + blockSize - 1, '\0'); EVP_EncryptUpdate(ctx, reinterpret_cast(cipherText.data()), diff --git a/lib/keyverificationsession.cpp b/lib/keyverificationsession.cpp index 0f24c743..171596c0 100644 --- a/lib/keyverificationsession.cpp +++ b/lib/keyverificationsession.cpp @@ -72,8 +72,7 @@ void KeyVerificationSession::init(milliseconds timeout) m_sas = olm_sas(new std::byte[olm_sas_size()]); const auto randomLength = olm_create_sas_random_length(m_sas); - auto random = getRandom(randomLength); - olm_create_sas(m_sas, random.data(), randomLength); + olm_create_sas(m_sas, RandomBuffer(randomLength), randomLength); } KeyVerificationSession::~KeyVerificationSession() -- cgit v1.2.3 From 2ff045d8b381bfbd64100d083f81b61c5fe87b23 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 21 Sep 2022 21:09:49 +0200 Subject: Wrap error reporting into facility macros Facility macros to report Olm errors: QOLM_INTERNAL_ERROR[_X], QOLM_FAIL_OR_LOG[_X] --- autotests/testgroupsession.cpp | 8 +++---- autotests/testolmaccount.cpp | 2 +- lib/connection.cpp | 23 ++++++-------------- lib/database.cpp | 32 +++++++++++++--------------- lib/e2ee/qolmaccount.cpp | 37 +++++++++++++++++++------------- lib/e2ee/qolmaccount.h | 4 +++- lib/e2ee/qolminboundsession.cpp | 28 +++++++++++++++++------- lib/e2ee/qolminboundsession.h | 4 ++-- lib/e2ee/qolmoutboundsession.cpp | 24 ++++++++++++--------- lib/e2ee/qolmoutboundsession.h | 6 +++--- lib/e2ee/qolmsession.cpp | 46 ++++++++++++++++++++++------------------ lib/e2ee/qolmsession.h | 2 +- lib/e2ee/qolmutils.h | 18 ++++++++++++++++ lib/room.cpp | 45 ++++++++++++++------------------------- 14 files changed, 150 insertions(+), 129 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 18ace73b..1054a160 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -16,13 +16,13 @@ void TestGroupSession::groupSessionPicklingValid() QVERIFY(QByteArray::fromBase64(ogsId).size() > 0); QCOMPARE(0, ogs->sessionMessageIndex()); - auto ogsPickled = ogs->pickle(Unencrypted {}).value(); + auto&& ogsPickled = ogs->pickle(Unencrypted {}); auto ogs2 = QOlmOutboundGroupSession::unpickle(std::move(ogsPickled), Unencrypted{}) .value(); QCOMPARE(ogsId, ogs2->sessionId()); - auto igs = QOlmInboundGroupSession::create(ogs->sessionKey().value()); + auto igs = QOlmInboundGroupSession::create(ogs->sessionKey()).value(); const auto igsId = igs->sessionId(); // ID is valid base64? QVERIFY(QByteArray::fromBase64(igsId).size() > 0); @@ -39,11 +39,11 @@ void TestGroupSession::groupSessionPicklingValid() void TestGroupSession::groupSessionCryptoValid() { auto ogs = QOlmOutboundGroupSession::create(); - auto igs = QOlmInboundGroupSession::create(ogs->sessionKey().value()); + auto igs = QOlmInboundGroupSession::create(ogs->sessionKey()).value(); QCOMPARE(ogs->sessionId(), igs->sessionId()); const auto plainText = "Hello world!"; - const auto ciphertext = ogs->encrypt(plainText).value(); + const auto ciphertext = ogs->encrypt(plainText); // ciphertext valid base64? QVERIFY(QByteArray::fromBase64(ciphertext).size() > 0); diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 0e1eab84..56532698 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -23,7 +23,7 @@ void TestOlmAccount::pickleUnpickledTest() QOlmAccount olmAccount(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); olmAccount.createNewAccount(); auto identityKeys = olmAccount.identityKeys(); - auto pickled = olmAccount.pickle(Unencrypted{}).value(); + auto pickled = olmAccount.pickle(Unencrypted{}); QOlmAccount olmAccount2(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); auto unpickleResult = olmAccount2.unpickle(std::move(pickled), Unencrypted{}); diff --git a/lib/connection.cpp b/lib/connection.cpp index cd8ee727..0afbffbc 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -206,13 +206,9 @@ public: } void saveSession(const QOlmSession& session, const QString& senderKey) const { - if (auto pickleResult = session.pickle(picklingMode)) - q->database()->saveOlmSession(senderKey, session.sessionId(), - *pickleResult, - QDateTime::currentDateTime()); - else - qCWarning(E2EE) << "Failed to pickle olm session. Error" - << pickleResult.error(); + q->database()->saveOlmSession(senderKey, session.sessionId(), + session.pickle(picklingMode), + QDateTime::currentDateTime()); } template @@ -2219,11 +2215,7 @@ void Connection::saveOlmAccount() { #ifdef Quotient_E2EE_ENABLED qCDebug(E2EE) << "Saving olm account"; - if (const auto expectedPickle = d->olmAccount->pickle(d->picklingMode)) - d->database->setAccountPickle(*expectedPickle); - else - qCWarning(E2EE) << "Couldn't save Olm account pickle:" - << expectedPickle.error(); + d->database->setAccountPickle(d->olmAccount->pickle(d->picklingMode)); #endif } @@ -2300,11 +2292,8 @@ std::pair Connection::Private::olmEncryptMessage( const auto& curveKey = curveKeyForUserDevice(userId, device); const auto& olmSession = olmSessions.at(curveKey).front(); const auto result = olmSession->encrypt(message); - if (const auto pickle = olmSession->pickle(picklingMode)) { - database->updateOlmSession(curveKey, olmSession->sessionId(), *pickle); - } else { - qWarning(E2EE) << "Failed to pickle olm session: " << pickle.error(); - } + database->updateOlmSession(curveKey, olmSession->sessionId(), + olmSession->pickle(picklingMode)); return { result.type(), result.toCiphertext() }; } diff --git a/lib/database.cpp b/lib/database.cpp index 4eb82cf5..2b472648 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -323,23 +323,21 @@ void Database::saveCurrentOutboundMegolmSession( const QOlmOutboundGroupSession& session) { const auto pickle = session.pickle(picklingMode); - if (pickle) { - auto deleteQuery = prepareQuery(QStringLiteral("DELETE FROM outbound_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId;")); - deleteQuery.bindValue(":roomId", roomId); - deleteQuery.bindValue(":sessionId", session.sessionId()); - - auto insertQuery = prepareQuery(QStringLiteral("INSERT INTO outbound_megolm_sessions(roomId, sessionId, pickle, creationTime, messageCount) VALUES(:roomId, :sessionId, :pickle, :creationTime, :messageCount);")); - insertQuery.bindValue(":roomId", roomId); - insertQuery.bindValue(":sessionId", session.sessionId()); - insertQuery.bindValue(":pickle", pickle.value()); - insertQuery.bindValue(":creationTime", session.creationTime()); - insertQuery.bindValue(":messageCount", session.messageCount()); - - transaction(); - execute(deleteQuery); - execute(insertQuery); - commit(); - } + auto deleteQuery = prepareQuery(QStringLiteral("DELETE FROM outbound_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId;")); + deleteQuery.bindValue(":roomId", roomId); + deleteQuery.bindValue(":sessionId", session.sessionId()); + + auto insertQuery = prepareQuery(QStringLiteral("INSERT INTO outbound_megolm_sessions(roomId, sessionId, pickle, creationTime, messageCount) VALUES(:roomId, :sessionId, :pickle, :creationTime, :messageCount);")); + insertQuery.bindValue(":roomId", roomId); + insertQuery.bindValue(":sessionId", session.sessionId()); + insertQuery.bindValue(":pickle", pickle); + insertQuery.bindValue(":creationTime", session.creationTime()); + insertQuery.bindValue(":messageCount", session.messageCount()); + + transaction(); + execute(deleteQuery); + execute(insertQuery); + commit(); } QOlmOutboundGroupSessionPtr Database::loadCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode) diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index b56272ef..d03bcb3b 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -45,9 +45,9 @@ void QOlmAccount::createNewAccount() m_account = olm_account(new uint8_t[olm_account_size()]); const auto randomLength = olm_create_account_random_length(m_account); if (olm_create_account(m_account, RandomBuffer(randomLength), randomLength) - == olm_error()) { - throw lastError(); - } + == olm_error()) + QOLM_INTERNAL_ERROR("Failed to create a new account"); + emit needsSave(); } @@ -64,7 +64,7 @@ OlmErrorCode QOlmAccount::unpickle(QByteArray&& pickled, const PicklingMode &mod return OLM_SUCCESS; } -QOlmExpected QOlmAccount::pickle(const PicklingMode &mode) +QByteArray QOlmAccount::pickle(const PicklingMode &mode) { const QByteArray key = toKey(mode); const size_t pickleLength = olm_pickle_account_length(m_account); @@ -72,7 +72,9 @@ QOlmExpected QOlmAccount::pickle(const PicklingMode &mode) if (olm_pickle_account(m_account, key.data(), key.length(), pickleBuffer.data(), pickleLength) == olm_error()) - return lastErrorCode(); + QOLM_INTERNAL_ERROR(qPrintable("Failed to pickle Olm account " + + accountId())); + return pickleBuffer; } @@ -82,7 +84,8 @@ IdentityKeys QOlmAccount::identityKeys() const QByteArray keyBuffer(keyLength, '\0'); if (olm_account_identity_keys(m_account, keyBuffer.data(), keyLength) == olm_error()) { - throw lastError(); + QOLM_INTERNAL_ERROR( + qPrintable("Failed to get " % accountId() % " identity keys")); } const auto key = QJsonDocument::fromJson(keyBuffer).object(); return IdentityKeys { @@ -97,9 +100,9 @@ QByteArray QOlmAccount::sign(const QByteArray &message) const if (olm_account_sign(m_account, message.data(), message.length(), signatureBuffer.data(), signatureBuffer.length()) - == olm_error()) { - throw lastError(); - } + == olm_error()) + QOLM_INTERNAL_ERROR("Failed to sign a message"); + return signatureBuffer; } @@ -135,9 +138,10 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const auto result = olm_account_generate_one_time_keys( m_account, numberOfKeys, RandomBuffer(randomLength), randomLength); - if (result == olm_error()) { - throw lastError(); - } + if (result == olm_error()) + QOLM_INTERNAL_ERROR(qPrintable( + "Failed to generate one-time keys for account " + accountId())); + emit needsSave(); return result; } @@ -149,9 +153,10 @@ UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const if (olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), oneTimeKeyLength) - == olm_error()) { - throw lastError(); - } + == olm_error()) + QOLM_INTERNAL_ERROR(qPrintable( + "Failed to obtain one-time keys for account" % accountId())); + const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); UnsignedOneTimeKeys oneTimeKeys; fromJson(json, oneTimeKeys.keys); @@ -266,3 +271,5 @@ bool Quotient::ed25519VerifySignature(const QString& signingKey, return utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf) .value_or(false); } + +QString QOlmAccount::accountId() const { return m_userId % '/' % m_deviceId; } diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 0fb9803f..a5faa82a 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -41,7 +41,7 @@ public: const PicklingMode& mode); //! Serialises an OlmAccount to encrypted Base64. - QOlmExpected pickle(const PicklingMode &mode); + QByteArray pickle(const PicklingMode &mode); //! Returns the account's public identity keys already formatted as JSON IdentityKeys identityKeys() const; @@ -107,6 +107,8 @@ private: OlmAccount *m_account = nullptr; // owning QString m_userId; QString m_deviceId; + + QString accountId() const; }; QUOTIENT_API bool verifyIdentitySignature(const DeviceKeys& deviceKeys, diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index a05ddf62..1e3803f2 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -31,7 +31,8 @@ QOlmInboundGroupSession::~QOlmInboundGroupSession() //delete[](reinterpret_cast(m_groupSession)); } -std::unique_ptr QOlmInboundGroupSession::create(const QByteArray &key) +QOlmExpected QOlmInboundGroupSession::create( + const QByteArray& key) { const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); if (olm_init_inbound_group_session( @@ -39,13 +40,17 @@ std::unique_ptr QOlmInboundGroupSession::create(const Q reinterpret_cast(key.constData()), key.size()) == olm_error()) { // FIXME: create QOlmInboundGroupSession earlier and use lastErrorCode() - throw olm_inbound_group_session_last_error_code(olmInboundGroupSession); + qWarning(E2EE) << "Failed to create an inbound group session:" + << olm_inbound_group_session_last_error( + olmInboundGroupSession); + return olm_inbound_group_session_last_error_code(olmInboundGroupSession); } return std::make_unique(olmInboundGroupSession); } -std::unique_ptr QOlmInboundGroupSession::import(const QByteArray &key) +QOlmExpected QOlmInboundGroupSession::import( + const QByteArray& key) { const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); @@ -54,7 +59,10 @@ std::unique_ptr QOlmInboundGroupSession::import(const Q reinterpret_cast(key.data()), key.size()) == olm_error()) { // FIXME: create QOlmInboundGroupSession earlier and use lastError() - throw olm_inbound_group_session_last_error_code(olmInboundGroupSession); + qWarning(E2EE) << "Failed to import an inbound group session:" + << olm_inbound_group_session_last_error( + olmInboundGroupSession); + return olm_inbound_group_session_last_error_code(olmInboundGroupSession); } return std::make_unique(olmInboundGroupSession); @@ -69,7 +77,7 @@ QByteArray QOlmInboundGroupSession::pickle(const PicklingMode& mode) const key.length(), pickledBuf.data(), pickledBuf.length()) == olm_error()) { - throw lastError(); + QOLM_INTERNAL_ERROR("Failed to pickle the inbound group session"); } return pickledBuf; } @@ -84,6 +92,8 @@ QOlmExpected QOlmInboundGroupSession::unpickle( pickled.size()) == olm_error()) { // FIXME: create QOlmInboundGroupSession earlier and use lastError() + qWarning(E2EE) << "Failed to unpickle an inbound group session:" + << olm_inbound_group_session_last_error(groupSession); return olm_inbound_group_session_last_error_code(groupSession); } key.clear(); @@ -133,6 +143,8 @@ QOlmExpected QOlmInboundGroupSession::exportSession( m_groupSession, reinterpret_cast(keyBuf.data()), keyLength, messageIndex) == olm_error()) { + QOLM_FAIL_OR_LOG(OLM_OUTPUT_BUFFER_TOO_SMALL, + "Failed to export the inbound group session"); return lastErrorCode(); } return keyBuf; @@ -150,9 +162,9 @@ QByteArray QOlmInboundGroupSession::sessionId() const if (olm_inbound_group_session_id( m_groupSession, reinterpret_cast(sessionIdBuf.data()), sessionIdBuf.length()) - == olm_error()) { - throw lastError(); - } + == olm_error()) + QOLM_INTERNAL_ERROR("Failed to obtain the group session id"); + return sessionIdBuf; } diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 0b89349a..15979a05 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -17,9 +17,9 @@ class QUOTIENT_API QOlmInboundGroupSession public: ~QOlmInboundGroupSession(); //! Creates a new instance of `OlmInboundGroupSession`. - static std::unique_ptr create(const QByteArray& key); + static QOlmExpected create(const QByteArray& key); //! Import an inbound group session, from a previous export. - static std::unique_ptr import(const QByteArray& key); + static QOlmExpected 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 diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 3d176274..18af5569 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -39,8 +39,10 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() RandomBuffer(randomLength).bytes(), randomLength) == olm_error()) { - // FIXME: create the session object earlier and use lastError() - throw olm_outbound_group_session_last_error_code(olmOutboundGroupSession); + // FIXME: create the session object earlier + QOLM_INTERNAL_ERROR_X("Failed to initialise an outbound group session", + olm_outbound_group_session_last_error( + olmOutboundGroupSession)); } // FIXME: is it used anywhere? @@ -52,7 +54,7 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() return std::make_unique(olmOutboundGroupSession); } -QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mode) const +QByteArray QOlmOutboundGroupSession::pickle(const PicklingMode &mode) const { QByteArray pickledBuf( olm_pickle_outbound_group_session_length(m_groupSession), '\0'); @@ -61,7 +63,7 @@ QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mo key.length(), pickledBuf.data(), pickledBuf.length()) == olm_error()) - return lastErrorCode(); + QOLM_INTERNAL_ERROR("Failed to pickle the outbound group session"); key.clear(); return pickledBuf; @@ -77,6 +79,9 @@ QOlmExpected QOlmOutboundGroupSession::unpickle( pickled.length()) == olm_error()) { // FIXME: create the session object earlier and use lastError() + qWarning(E2EE) << "Failed to unpickle an outbound group session:" + << olm_outbound_group_session_last_error( + olmOutboundGroupSession); return olm_outbound_group_session_last_error_code( olmOutboundGroupSession); } @@ -90,8 +95,7 @@ QOlmExpected QOlmOutboundGroupSession::unpickle( return std::make_unique(olmOutboundGroupSession); } -QOlmExpected QOlmOutboundGroupSession::encrypt( - const QByteArray& plaintext) const +QByteArray QOlmOutboundGroupSession::encrypt(const QByteArray& plaintext) const { const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintext.length()); @@ -102,7 +106,7 @@ QOlmExpected QOlmOutboundGroupSession::encrypt( reinterpret_cast(messageBuf.data()), messageBuf.length()) == olm_error()) - return lastErrorCode(); + QOLM_INTERNAL_ERROR("Failed to encrypt a message"); return messageBuf; } @@ -120,12 +124,12 @@ QByteArray QOlmOutboundGroupSession::sessionId() const m_groupSession, reinterpret_cast(idBuffer.data()), idBuffer.length()) == olm_error()) - throw lastError(); + QOLM_INTERNAL_ERROR("Failed to obtain group session id"); return idBuffer; } -QOlmExpected QOlmOutboundGroupSession::sessionKey() const +QByteArray QOlmOutboundGroupSession::sessionKey() const { const auto keyMaxLength = olm_outbound_group_session_key_length(m_groupSession); QByteArray keyBuffer(keyMaxLength, '\0'); @@ -133,7 +137,7 @@ QOlmExpected QOlmOutboundGroupSession::sessionKey() const m_groupSession, reinterpret_cast(keyBuffer.data()), keyMaxLength) == olm_error()) - return lastErrorCode(); + QOLM_INTERNAL_ERROR("Failed to obtain group session key"); return keyBuffer; } diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index e5e73ddf..d36fbf69 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -20,14 +20,14 @@ public: //! Throw OlmError on errors static QOlmOutboundGroupSessionPtr create(); //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. - QOlmExpected pickle(const PicklingMode &mode) const; + QByteArray pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. static QOlmExpected unpickle( QByteArray&& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. - QOlmExpected encrypt(const QByteArray& plaintext) const; + QByteArray encrypt(const QByteArray& plaintext) const; //! 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. - QOlmExpected sessionKey() const; + QByteArray sessionKey() const; QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); int messageCount() const; diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 7c102a96..0d6edd21 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -90,23 +90,23 @@ QOlmExpected QOlmSession::createOutboundSession( == olm_error()) { // FIXME: the QOlmSession object should be created earlier const auto lastErr = olm_session_last_error_code(olmOutboundSession); - if (lastErr == OLM_NOT_ENOUGH_RANDOM) { - throw lastErr; - } + QOLM_FAIL_OR_LOG_X(lastErr == OLM_NOT_ENOUGH_RANDOM, + "Failed to create an outbound Olm session", + olm_session_last_error(olmOutboundSession)); return lastErr; } return std::make_unique(olmOutboundSession); } -QOlmExpected QOlmSession::pickle(const PicklingMode &mode) const +QByteArray QOlmSession::pickle(const PicklingMode &mode) const { QByteArray pickledBuf(olm_pickle_session_length(m_session), '\0'); QByteArray key = toKey(mode); if (olm_pickle_session(m_session, key.data(), key.length(), pickledBuf.data(), pickledBuf.length()) == olm_error()) - return lastErrorCode(); + QOLM_INTERNAL_ERROR("Failed to pickle an Olm session"); key.clear(); return pickledBuf; @@ -121,7 +121,11 @@ QOlmExpected QOlmSession::unpickle(QByteArray&& pickled, pickled.data(), pickled.length()) == olm_error()) { // FIXME: the QOlmSession object should be created earlier - return olm_session_last_error_code(olmSession); + const auto errorCode = olm_session_last_error_code(olmSession); + QOLM_FAIL_OR_LOG_X(errorCode == OLM_OUTPUT_BUFFER_TOO_SMALL, + "Failed to unpickle an Olm session", + olm_session_last_error(olmSession)); + return errorCode; } key.clear(); @@ -140,7 +144,7 @@ QOlmMessage QOlmSession::encrypt(const QByteArray& plaintext) RandomBuffer(randomLength), randomLength, messageBuf.data(), messageBuf.length()) == olm_error()) { - throw lastError(); + QOLM_INTERNAL_ERROR("Failed to encrypt the message"); } return QOlmMessage(messageBuf, QOlmMessage::Type(messageType)); @@ -159,7 +163,9 @@ QOlmExpected QOlmSession::decrypt(const QOlmMessage &message) const const auto plaintextMaxLen = olm_decrypt_max_plaintext_length( m_session, messageTypeValue, messageBuf.data(), messageBuf.length()); if (plaintextMaxLen == olm_error()) { - return lastError(); + qWarning(E2EE) << "Couldn't calculate decrypted message length:" + << lastError(); + return lastErrorCode(); } QByteArray plaintextBuf(plaintextMaxLen, '\0'); @@ -170,11 +176,9 @@ QOlmExpected QOlmSession::decrypt(const QOlmMessage &message) const olm_decrypt(m_session, messageTypeValue, messageBuf2.data(), messageBuf2.length(), plaintextBuf.data(), plaintextMaxLen); if (plaintextResultLen == olm_error()) { - const auto lastErr = lastErrorCode(); - if (lastErr == OLM_OUTPUT_BUFFER_TOO_SMALL) { - throw lastErr; - } - return lastErr; + QOLM_FAIL_OR_LOG(OLM_OUTPUT_BUFFER_TOO_SMALL, + "Failed to decrypt the message"); + return lastErrorCode(); } plaintextBuf.truncate(plaintextResultLen); return plaintextBuf; @@ -183,10 +187,10 @@ QOlmExpected QOlmSession::decrypt(const QOlmMessage &message) const QByteArray QOlmSession::sessionId() const { const auto idMaxLength = olm_session_id_length(m_session); - QByteArray idBuffer(idMaxLength, '0'); - if (olm_session_id(m_session, idBuffer.data(), idMaxLength) == olm_error()) { - throw lastError(); - } + QByteArray idBuffer(idMaxLength, '\0'); + if (olm_session_id(m_session, idBuffer.data(), idMaxLength) == olm_error()) + QOLM_INTERNAL_ERROR("Failed to obtain Olm session id"); + return idBuffer; } @@ -203,8 +207,8 @@ bool QOlmSession::matchesInboundSession(const QOlmMessage& preKeyMessage) const olm_matches_inbound_session(m_session, oneTimeKeyBuf.data(), oneTimeKeyBuf.length()); if (maybeMatches == olm_error()) - qWarning(E2EE) << "Error matching an inbound session:" - << olm_session_last_error(m_session); + qWarning(E2EE) << "Error matching an inbound session:" << lastError(); + return maybeMatches == 1; // Any errors are treated as non-match } @@ -218,8 +222,8 @@ bool QOlmSession::matchesInboundSessionFrom( oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); if (maybeMatches == olm_error()) - qCWarning(E2EE) << "Error matching an inbound session:" - << olm_session_last_error(m_session); + qCWarning(E2EE) << "Error matching an inbound session:" << lastError(); + return maybeMatches == 1; } diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index 174e98ef..400fb854 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -30,7 +30,7 @@ public: const QByteArray& theirOneTimeKey); //! Serialises an `QOlmSession` to encrypted Base64. - QOlmExpected pickle(const PicklingMode &mode) const; + QByteArray pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 previously made with pickle() static QOlmExpected unpickle(QByteArray&& pickled, diff --git a/lib/e2ee/qolmutils.h b/lib/e2ee/qolmutils.h index da9d2d18..17eee7a3 100644 --- a/lib/e2ee/qolmutils.h +++ b/lib/e2ee/qolmutils.h @@ -34,4 +34,22 @@ public: return RandomBuffer(bufferSize); } +#define QOLM_INTERNAL_ERROR_X(Message_, LastError_) \ + qFatal("%s, internal error: %s", Message_, LastError_) + +#define QOLM_INTERNAL_ERROR(Message_) \ + QOLM_INTERNAL_ERROR_X(Message_, lastError()) + +#define QOLM_FAIL_OR_LOG_X(InternalCondition_, Message_, LastErrorText_) \ + do { \ + const QString errorMsg{ (Message_) }; \ + if (InternalCondition_) \ + QOLM_INTERNAL_ERROR_X(qPrintable(errorMsg), (LastErrorText_)); \ + qWarning(E2EE).nospace() << errorMsg << ": " << (LastErrorText_); \ + } while (false) /* End of macro */ + +#define QOLM_FAIL_OR_LOG(InternalFailureValue_, Message_) \ + QOLM_FAIL_OR_LOG_X(lastErrorCode() == (InternalFailureValue_), (Message_), \ + lastError()) + } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index 382d0243..5fdff036 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -357,7 +357,9 @@ public: return false; } - auto megolmSession = QOlmInboundGroupSession::create(sessionKey); + auto expectedMegolmSession = QOlmInboundGroupSession::create(sessionKey); + Q_ASSERT(expectedMegolmSession.has_value()); + auto&& megolmSession = *expectedMegolmSession; if (megolmSession->sessionId() != sessionId) { qCWarning(E2EE) << "Session ID mismatch in m.room_key event"; return false; @@ -442,12 +444,10 @@ public: connection->saveCurrentOutboundMegolmSession( id, *currentOutboundMegolmSession); - const auto sessionKey = currentOutboundMegolmSession->sessionKey(); - if(!sessionKey) { - qCWarning(E2EE) << "Failed to load key for new megolm session"; - return; - } - addInboundGroupSession(currentOutboundMegolmSession->sessionId(), *sessionKey, q->localUser()->id(), "SELF"_ls); + currentOutboundMegolmSession->sessionKey(); + addInboundGroupSession(currentOutboundMegolmSession->sessionId(), + currentOutboundMegolmSession->sessionKey(), + q->localUser()->id(), "SELF"_ls); } QMultiHash getDevicesWithoutKey() const @@ -460,22 +460,6 @@ public: return connection->database()->devicesWithoutKey( id, devices, currentOutboundMegolmSession->sessionId()); } - - void sendMegolmSession(const QMultiHash& devices) const { - // Save the session to this device - const auto sessionId = currentOutboundMegolmSession->sessionId(); - const auto sessionKey = currentOutboundMegolmSession->sessionKey(); - if(!sessionKey) { - qCWarning(E2EE) << "Error loading session key"; - return; - } - - // Send the session to other people - connection->sendSessionKeyToDevices( - id, sessionId, *sessionKey, devices, - currentOutboundMegolmSession->sessionMessageIndex()); - } - #endif // Quotient_E2EE_ENABLED private: @@ -2029,17 +2013,20 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) if (!hasValidMegolmSession() || shouldRotateMegolmSession()) { createMegolmSession(); } - sendMegolmSession(getDevicesWithoutKey()); + // Send the session to other people + connection->sendSessionKeyToDevices( + id, currentOutboundMegolmSession->sessionId(), + currentOutboundMegolmSession->sessionKey(), getDevicesWithoutKey(), + currentOutboundMegolmSession->sessionMessageIndex()); const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(pEvent->fullJson()).toJson()); currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); connection->saveCurrentOutboundMegolmSession( id, *currentOutboundMegolmSession); - if(!encrypted) { - qWarning(E2EE) << "Error encrypting message" << encrypted.error(); - return {}; - } - encryptedEvent = makeEvent(*encrypted, q->connection()->olmAccount()->identityKeys().curve25519, q->connection()->deviceId(), currentOutboundMegolmSession->sessionId()); + encryptedEvent = makeEvent( + encrypted, q->connection()->olmAccount()->identityKeys().curve25519, + q->connection()->deviceId(), + currentOutboundMegolmSession->sessionId()); encryptedEvent->setTransactionId(connection->generateTxnId()); encryptedEvent->setRoomId(id); encryptedEvent->setSender(connection->userId()); -- cgit v1.2.3 From d5c1afc536dde87f460ccadfcfd51444b5e9bb82 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 22 Sep 2022 14:01:01 +0200 Subject: Trample Sonar warnings --- lib/connection.cpp | 4 +++- lib/e2ee/qolmaccount.cpp | 7 ++++--- lib/e2ee/qolminboundsession.cpp | 2 +- lib/e2ee/qolminboundsession.h | 2 +- lib/e2ee/qolmoutboundsession.cpp | 6 +++--- lib/e2ee/qolmsession.cpp | 13 ++++++------- 6 files changed, 18 insertions(+), 16 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/connection.cpp b/lib/connection.cpp index 0afbffbc..51c2062f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -648,7 +648,9 @@ void Connection::Private::completeSetup(const QString& mxId) }); } else { // account already existing - olmAccount->unpickle(database->accountPickle(), picklingMode); + if (!olmAccount->unpickle(database->accountPickle(), picklingMode)) + qWarning(E2EE) + << "Could not unpickle Olm account, E2EE won't be available"; } #endif // Quotient_E2EE_ENABLED emit q->stateChanged(); diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index d03bcb3b..7392a9f5 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -43,15 +43,16 @@ QOlmAccount::~QOlmAccount() void QOlmAccount::createNewAccount() { m_account = olm_account(new uint8_t[olm_account_size()]); - const auto randomLength = olm_create_account_random_length(m_account); - if (olm_create_account(m_account, RandomBuffer(randomLength), randomLength) + if (const auto randomLength = olm_create_account_random_length(m_account); + olm_create_account(m_account, RandomBuffer(randomLength), randomLength) == olm_error()) QOLM_INTERNAL_ERROR("Failed to create a new account"); emit needsSave(); } -OlmErrorCode QOlmAccount::unpickle(QByteArray&& pickled, const PicklingMode &mode) +OlmErrorCode QOlmAccount::unpickle(QByteArray&& pickled, + const PicklingMode& mode) { m_account = olm_account(new uint8_t[olm_account_size()]); if (const auto key = toKey(mode); diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 1e3803f2..18275dc0 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -49,7 +49,7 @@ QOlmExpected QOlmInboundGroupSession::create( return std::make_unique(olmInboundGroupSession); } -QOlmExpected QOlmInboundGroupSession::import( +QOlmExpected QOlmInboundGroupSession::importSession( const QByteArray& key) { const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 15979a05..b9710354 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -19,7 +19,7 @@ public: //! Creates a new instance of `OlmInboundGroupSession`. static QOlmExpected create(const QByteArray& key); //! Import an inbound group session, from a previous export. - static QOlmExpected import(const QByteArray& key); + static QOlmExpected importSession(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 diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 18af5569..56f78906 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -33,9 +33,9 @@ QOlmOutboundGroupSession::~QOlmOutboundGroupSession() QOlmOutboundGroupSessionPtr 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); - - if (olm_init_outbound_group_session(olmOutboundGroupSession, + if (const auto randomLength = olm_init_outbound_group_session_random_length( + olmOutboundGroupSession); + olm_init_outbound_group_session(olmOutboundGroupSession, RandomBuffer(randomLength).bytes(), randomLength) == olm_error()) { diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 0d6edd21..e3f69132 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -80,10 +80,9 @@ QOlmExpected QOlmSession::createOutboundSession( const QByteArray& theirOneTimeKey) { auto* olmOutboundSession = create(); - const auto randomLength = - olm_create_outbound_session_random_length(olmOutboundSession); - - if (olm_create_outbound_session( + if (const auto randomLength = + olm_create_outbound_session_random_length(olmOutboundSession); + olm_create_outbound_session( olmOutboundSession, account->data(), theirIdentityKey.data(), theirIdentityKey.length(), theirOneTimeKey.data(), theirOneTimeKey.length(), RandomBuffer(randomLength), randomLength) @@ -136,11 +135,11 @@ QOlmMessage QOlmSession::encrypt(const QByteArray& plaintext) { const auto messageMaxLength = olm_encrypt_message_length(m_session, plaintext.length()); - QByteArray messageBuf(messageMaxLength, '0'); + QByteArray messageBuf(messageMaxLength, '\0'); // NB: The type has to be calculated before calling olm_encrypt() const auto messageType = olm_encrypt_message_type(m_session); - const auto randomLength = olm_encrypt_random_length(m_session); - if (olm_encrypt(m_session, plaintext.data(), plaintext.length(), + if (const auto randomLength = olm_encrypt_random_length(m_session); + olm_encrypt(m_session, plaintext.data(), plaintext.length(), RandomBuffer(randomLength), randomLength, messageBuf.data(), messageBuf.length()) == olm_error()) { -- cgit v1.2.3 From 388c301222586583a0144f7229a564cf07b597d4 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 24 Sep 2022 20:51:00 +0200 Subject: Remove no-op code --- lib/e2ee/qolmoutboundsession.cpp | 11 ----------- lib/room.cpp | 1 - 2 files changed, 12 deletions(-) (limited to 'lib/e2ee') diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 56f78906..1176d790 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -45,12 +45,6 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() olmOutboundGroupSession)); } - // FIXME: is it used anywhere? - 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); - return std::make_unique(olmOutboundGroupSession); } @@ -85,11 +79,6 @@ QOlmExpected QOlmOutboundGroupSession::unpickle( return olm_outbound_group_session_last_error_code( 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()); - // FIXME: idBuffer doesn't seem to be used, is it needed here? key.clear(); return std::make_unique(olmOutboundGroupSession); diff --git a/lib/room.cpp b/lib/room.cpp index 5fdff036..0cf818ce 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -444,7 +444,6 @@ public: connection->saveCurrentOutboundMegolmSession( id, *currentOutboundMegolmSession); - currentOutboundMegolmSession->sessionKey(); addInboundGroupSession(currentOutboundMegolmSession->sessionId(), currentOutboundMegolmSession->sessionKey(), q->localUser()->id(), "SELF"_ls); -- cgit v1.2.3 From 72e14cb1bdff68dfe0fb61fff0defd6c50dff43c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 25 Sep 2022 21:37:49 +0200 Subject: QOlmUtility::ed25519Verify: just return bool It's too easy to incorrectly test the previous return type. (cherry picked from commit 5904a61c59f0eef00aef07ef998658fd791ff139) --- autotests/testolmaccount.cpp | 3 +-- autotests/testolmutility.cpp | 6 ++---- lib/e2ee/qolmaccount.cpp | 3 +-- lib/e2ee/qolmutility.cpp | 21 +++++---------------- lib/e2ee/qolmutility.h | 4 ++-- 5 files changed, 11 insertions(+), 26 deletions(-) (limited to 'lib/e2ee') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 56532698..53a0c955 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -60,8 +60,7 @@ void TestOlmAccount::signatureValid() QOlmUtility utility; const auto identityKeys = olmAccount.identityKeys(); const auto ed25519Key = identityKeys.ed25519; - const auto verify = utility.ed25519Verify(ed25519Key, message, signature); - QVERIFY(verify.value_or(false)); + QVERIFY(utility.ed25519Verify(ed25519Key, message, signature)); } void TestOlmAccount::oneTimeKeysValid() diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index 64ceb3e7..4de5afdf 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -78,10 +78,8 @@ void TestOlmUtility::verifySignedOneTimeKey() delete[](reinterpret_cast(utility)); QOlmUtility utility2; - auto res2 = - utility2 - .ed25519Verify(aliceOlm.identityKeys().ed25519, msg, signatureBuf1) - .value_or(false); + auto res2 = utility2.ed25519Verify(aliceOlm.identityKeys().ed25519, msg, + signatureBuf1); //QCOMPARE(std::string(olm_utility_last_error(utility)), "SUCCESS"); QVERIFY(res2); diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 7392a9f5..345ab16b 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -269,8 +269,7 @@ bool Quotient::ed25519VerifySignature(const QString& signingKey, QByteArray signingKeyBuf = signingKey.toUtf8(); QOlmUtility utility; auto signatureBuf = signature.toUtf8(); - return utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf) - .value_or(false); + return utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf); } QString QOlmAccount::accountId() const { return m_userId % '/' % m_deviceId; } diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index 08c2b699..46f7f4f3 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -44,21 +44,10 @@ QString QOlmUtility::sha256Utf8Msg(const QString &message) const return sha256Bytes(message.toUtf8()); } -QOlmExpected QOlmUtility::ed25519Verify(const QByteArray& key, - const QByteArray& message, - const QByteArray& signature) +bool QOlmUtility::ed25519Verify(const QByteArray& key, const QByteArray& message, + QByteArray signature) { - QByteArray signatureBuf(signature.length(), '\0'); - std::copy(signature.begin(), signature.end(), signatureBuf.begin()); - - if (olm_ed25519_verify(m_utility, key.data(), key.size(), message.data(), - message.size(), signatureBuf.data(), - signatureBuf.size()) - == 0) - return true; - - auto error = lastErrorCode(); - if (error == OLM_BAD_MESSAGE_MAC) - return false; - return error; + return olm_ed25519_verify(m_utility, key.data(), key.size(), message.data(), + message.size(), signature.data(), signature.size()) + == 0; } diff --git a/lib/e2ee/qolmutility.h b/lib/e2ee/qolmutility.h index 89277385..508767bf 100644 --- a/lib/e2ee/qolmutility.h +++ b/lib/e2ee/qolmutility.h @@ -29,8 +29,8 @@ public: //! \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. - QOlmExpected ed25519Verify(const QByteArray &key, - const QByteArray &message, const QByteArray &signature); + bool ed25519Verify(const QByteArray &key, + const QByteArray &message, QByteArray signature); OlmErrorCode lastErrorCode() const; const char* lastError() const; -- cgit v1.2.3