From 10b89faeea9e385ea901d45418491cd91dff99b9 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 29 Jan 2021 20:23:42 +0100 Subject: More tests --- .ci/adjust-config.sh | 53 +++++++++++ Makefile | 31 +++++++ autotests/testolmaccount.cpp | 206 ++++++++++++++++++++++++++++++++++++++++++- autotests/testolmaccount.h | 4 + lib/connection.cpp | 51 +++++++---- lib/converters.cpp | 13 ++- lib/crypto/e2ee.h | 47 +++++++++- lib/crypto/qolmaccount.cpp | 36 +++++++- lib/crypto/qolmaccount.h | 8 +- lib/encryptionmanager.cpp | 12 +-- lib/networkaccessmanager.cpp | 6 +- 11 files changed, 435 insertions(+), 32 deletions(-) create mode 100755 .ci/adjust-config.sh create mode 100644 Makefile diff --git a/.ci/adjust-config.sh b/.ci/adjust-config.sh new file mode 100755 index 00000000..b2ca52b2 --- /dev/null +++ b/.ci/adjust-config.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +CMD="" + +$CMD perl -pi -w -e \ + 's/rc_messages_per_second.*/rc_messages_per_second: 1000/g;' data/homeserver.yaml +$CMD perl -pi -w -e \ + 's/rc_message_burst_count.*/rc_message_burst_count: 10000/g;' data/homeserver.yaml + +( +cat <&1>/dev/null + +restart: stop-synapse synapse diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index a4dfd7b5..c764e023 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -4,7 +4,7 @@ #include "testolmaccount.h" #include "crypto/qolmaccount.h" -#include "csapi/definitions/device_keys.h" +#include "connection.h" #include "events/encryptedfile.h" using namespace Quotient; @@ -162,4 +162,208 @@ void TestOlmAccount::encryptedFile() QCOMPARE(file.key.keyOps.count(), 2); QCOMPARE(file.key.kty, "oct"); } + +void TestOlmAccount::uploadIdentityKey() +{ + auto conn = new Connection(); + conn->resolveServer("@alice:localhost:" + QString::number(443)); + connect(conn, &Connection::loginFlowsChanged, this, [this, conn]() { + conn->loginWithPassword("alice", "secret", "AlicePhone", ""); + connect(conn, &Connection::connected, this, [this, conn] { + auto olmAccount = conn->olmAccount(); + auto idKeys = olmAccount->identityKeys(); + + QVERIFY(idKeys.curve25519.size() > 10); + QVERIFY(idKeys.curve25519.size() > 10); + + + OneTimeKeys unused; + auto request = olmAccount->createUploadKeyRequest(unused); + connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 0); + }); + connect(request, &BaseJob::failure, this, [] { + QFAIL("upload failed"); + }); + conn->run(request); + QSignalSpy spy3(request, &BaseJob::result); + QVERIFY(spy3.wait(10000)); + }); + connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, [=](QString error, const QString &) { + QFAIL("Login failed"); + }); + }); + + connect(conn, &Connection::resolveError, this, [=](QString error) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, this, [=] { + QFAIL("Network error: make sure synapse is running"); + }); + + QSignalSpy spy(conn, &Connection::loginFlowsChanged); + QSignalSpy spy2(conn, &Connection::connected); + QVERIFY(spy.wait(10000)); + QVERIFY(spy2.wait(10000)); + delete conn; +} + +void TestOlmAccount::uploadOneTimeKeys() +{ + auto conn = new Connection(); + conn->resolveServer("@alice:localhost:" + QString::number(443)); + connect(conn, &Connection::loginFlowsChanged, this, [this, conn]() { + conn->loginWithPassword("alice", "secret", "AlicePhone", ""); + connect(conn, &Connection::connected, this, [this, conn] { + auto olmAccount = conn->olmAccount(); + + auto nKeys = olmAccount->generateOneTimeKeys(5); + QCOMPARE(nKeys, 5); + + auto oneTimeKeys = olmAccount->oneTimeKeys(); + + QHash oneTimeKeysHash; + const auto curve = oneTimeKeys.curve25519(); + for (const auto &[keyId, key] : asKeyValueRange(curve)) { + oneTimeKeysHash["curve25519:"+keyId] = key; + } + auto request = new UploadKeysJob(none, oneTimeKeysHash); + connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 1); + QCOMPARE(job2->oneTimeKeyCounts()["curve25519"], 5); + }); + connect(request, &BaseJob::failure, this, [] { + QFAIL("upload failed"); + }); + conn->run(request); + QSignalSpy spy3(request, &BaseJob::result); + QVERIFY(spy3.wait(10000)); + }); + connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, [=](QString error, const QString &) { + QFAIL("Login failed"); + }); + }); + + connect(conn, &Connection::resolveError, this, [=](QString error) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, this, [=] { + QFAIL("Network error: make sure synapse is running"); + }); + + QSignalSpy spy(conn, &Connection::loginFlowsChanged); + QSignalSpy spy2(conn, &Connection::connected); + QVERIFY(spy.wait(10000)); + QVERIFY(spy2.wait(10000)); + delete conn; +} + +void TestOlmAccount::uploadSignedOneTimeKeys() +{ + auto conn = new Connection(); + conn->resolveServer("@alice:localhost:" + QString::number(443)); + connect(conn, &Connection::loginFlowsChanged, this, [this, conn]() { + conn->loginWithPassword("alice", "secret", "AlicePhone", ""); + connect(conn, &Connection::connected, this, [this, conn] { + auto olmAccount = conn->olmAccount(); + auto nKeys = olmAccount->generateOneTimeKeys(5); + QCOMPARE(nKeys, 5); + + auto oneTimeKeys = olmAccount->oneTimeKeys(); + QHash oneTimeKeysHash; + const auto signedKey = olmAccount->signOneTimeKeys(oneTimeKeys); + for (const auto &[keyId, key] : asKeyValueRange(signedKey)) { + QVariant var; + var.setValue(key); + oneTimeKeysHash[keyId] = var; + } + auto request = new UploadKeysJob(none, oneTimeKeysHash); + connect(request, &BaseJob::result, this, [request, nKeys, conn](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 1); + QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], nKeys); + }); + connect(request, &BaseJob::failure, this, [] { + QFAIL("upload failed"); + }); + conn->run(request); + QSignalSpy spy3(request, &BaseJob::result); + QVERIFY(spy3.wait(10000)); + }); + connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, [=](QString error, const QString &) { + QFAIL("Login failed"); + }); + }); + + connect(conn, &Connection::resolveError, this, [=](QString error) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, this, [=] { + QFAIL("Network error: make sure synapse is running"); + }); + + QSignalSpy spy(conn, &Connection::loginFlowsChanged); + QSignalSpy spy2(conn, &Connection::connected); + QVERIFY(spy.wait(10000)); + QVERIFY(spy2.wait(10000)); + delete conn; +} + +void TestOlmAccount::uploadKeys() +{ + auto conn = new Connection(); + conn->resolveServer("@alice:localhost:" + QString::number(443)); + connect(conn, &Connection::loginFlowsChanged, this, [this, conn]() { + conn->loginWithPassword("alice", "secret", "AlicePhone", ""); + connect(conn, &Connection::connected, this, [this, conn] { + auto olmAccount = conn->olmAccount(); + auto idks = olmAccount->identityKeys(); + olmAccount->generateOneTimeKeys(1); + auto otks = olmAccount->oneTimeKeys(); + auto request = olmAccount->createUploadKeyRequest(otks); + connect(request, &BaseJob::result, this, [request, conn](BaseJob *job) { + auto job2 = static_cast(job); + QCOMPARE(job2->oneTimeKeyCounts().size(), 1); + QCOMPARE(job2->oneTimeKeyCounts()["signed_curve25519"], 1); + }); + connect(request, &BaseJob::failure, this, [] { + QFAIL("upload failed"); + }); + conn->run(request); + QSignalSpy spy3(request, &BaseJob::result); + QVERIFY(spy3.wait(10000)); + }); + connect(conn, &Connection::networkError, [=](QString error, const QString &, int, int) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, [=](QString error, const QString &) { + QFAIL("Login failed"); + }); + }); + + connect(conn, &Connection::resolveError, this, [=](QString error) { + QFAIL("Network error: make sure synapse is running"); + }); + connect(conn, &Connection::loginError, this, [=] { + QFAIL("Network error: make sure synapse is running"); + }); + + QSignalSpy spy(conn, &Connection::loginFlowsChanged); + QSignalSpy spy2(conn, &Connection::connected); + QVERIFY(spy.wait(10000)); + QVERIFY(spy2.wait(10000)); + delete conn; +} + QTEST_MAIN(TestOlmAccount) diff --git a/autotests/testolmaccount.h b/autotests/testolmaccount.h index 4e270730..41298957 100644 --- a/autotests/testolmaccount.h +++ b/autotests/testolmaccount.h @@ -16,4 +16,8 @@ private Q_SLOTS: //void removeOneTimeKeys(); void deviceKeys(); void encryptedFile(); + void uploadIdentityKey(); + void uploadOneTimeKeys(); + void uploadSignedOneTimeKeys(); + void uploadKeys(); }; diff --git a/lib/connection.cpp b/lib/connection.cpp index f96eeb71..704bc1b4 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -108,7 +108,8 @@ public: QVector loginFlows; #ifdef Quotient_E2EE_ENABLED - QScopedPointer encryptionManager; + std::unique_ptr olmAccount; + //QScopedPointer encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -183,6 +184,9 @@ public: EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; + return {}; + /* #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; return {}; @@ -242,6 +246,7 @@ public: return std::move(decryptedEvent); #endif // Quotient_E2EE_ENABLED +*/ } }; @@ -420,8 +425,8 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED - encryptionManager->uploadIdentityKeys(q); - encryptionManager->uploadOneTimeKeys(q); + //encryptionManager->uploadIdentityKeys(q); + //encryptionManager->uploadOneTimeKeys(q); #endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { @@ -442,11 +447,19 @@ void Connection::Private::completeSetup(const QString& mxId) qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED AccountSettings accountSettings(data->userId()); - encryptionManager.reset( - new EncryptionManager(accountSettings.encryptionAccountPickle())); + + // init olmAccount + olmAccount = std::make_unique(data->userId(), data->deviceId()); + if (accountSettings.encryptionAccountPickle().isEmpty()) { - accountSettings.setEncryptionAccountPickle( - encryptionManager->olmAccountPickle()); + // create new account and save unpickle data + olmAccount->createNewAccount(); + accountSettings.setEncryptionAccountPickle(std::get(olmAccount->pickle(Unencrypted{}))); + // TODO handle pickle errors + } else { + // account already existing + auto pickle = accountSettings.encryptionAccountPickle(); + olmAccount->unpickle(pickle, Unencrypted{}); } #endif // Quotient_E2EE_ENABLED emit q->stateChanged(); @@ -608,16 +621,16 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->consumeToDeviceEvents(data.takeToDeviceEvents()); #ifdef Quotient_E2EE_ENABLED // handling device_one_time_keys_count - if (!d->encryptionManager) - { - qCDebug(E2EE) << "Encryption manager is not there yet, updating " - "one-time key counts will be skipped"; - return; - } - if (const auto deviceOneTimeKeysCount = data.deviceOneTimeKeysCount(); - !deviceOneTimeKeysCount.isEmpty()) - d->encryptionManager->updateOneTimeKeyCounts(this, - deviceOneTimeKeysCount); + //if (!d->encryptionManager) + //{ + // qCDebug(E2EE) << "Encryption manager is not there yet, updating " + // "one-time key counts will be skipped"; + // return; + //} + //if (const auto deviceOneTimeKeysCount = data.deviceOneTimeKeysCount(); + // !deviceOneTimeKeysCount.isEmpty()) + // d->encryptionManager->updateOneTimeKeyCounts(this, + // deviceOneTimeKeysCount); #endif // Quotient_E2EE_ENABLED } @@ -745,6 +758,7 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { +/* #ifdef Quotient_E2EE_ENABLED // handling m.room_key to-device encrypted event visitEach(toDeviceEvents, [this](const EncryptedEvent& ee) { @@ -775,6 +789,7 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) }); }); #endif +*/ } void Connection::stopSync() @@ -1228,7 +1243,7 @@ bool Connection::isLoggedIn() const { return !accessToken().isEmpty(); } #ifdef Quotient_E2EE_ENABLED QOlmAccount *Connection::olmAccount() const { - return d->encryptionManager->account(); + return d->olmAccount.get(); //d->encryptionManager->account(); } #endif // Quotient_E2EE_ENABLED diff --git a/lib/converters.cpp b/lib/converters.cpp index 444ca4f6..e6dcd854 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -3,15 +3,26 @@ #include "converters.h" -#include +#include +#include "crypto/e2ee.h" QJsonValue Quotient::JsonConverter::dump(const QVariant& v) { + if (v.canConvert()) { + return toJson(v.value()); + } return QJsonValue::fromVariant(v); } QVariant Quotient::JsonConverter::load(const QJsonValue& jv) { + if (jv.isObject()) { + QJsonObject obj = jv.toObject(); + if (obj.contains("key") && obj.contains("signatures")) { + SignedOneTimeKey signedOneTimeKeys; + signedOneTimeKeys.key = obj["key"].toString(); + } + } return jv.toVariant(); } diff --git a/lib/crypto/e2ee.h b/lib/crypto/e2ee.h index 73dd7f65..2d280185 100644 --- a/lib/crypto/e2ee.h +++ b/lib/crypto/e2ee.h @@ -7,10 +7,13 @@ #include #include +#include "converters.h" #include #include +#include #include +#include #include "util.h" @@ -68,16 +71,56 @@ struct OneTimeKeys }; //! Struct representing the signed one-time keys. -struct SignedOneTimeKey +class SignedOneTimeKey { +public: + SignedOneTimeKey() = default; + SignedOneTimeKey(const SignedOneTimeKey &) = default; + SignedOneTimeKey &operator=(const SignedOneTimeKey &) = default; //! Required. The unpadded Base64-encoded 32-byte Curve25519 public key. QString key; //! Required. Signatures of the key object. //! The signature is calculated using the process described at Signing JSON. - QMap> signatures; + QHash> signatures; +}; + + +template <> +struct JsonObjectConverter { + static void fillFrom(const QJsonObject& jo, + SignedOneTimeKey& result) + { + fromJson(jo.value("key"_ls), result.key); + fromJson(jo.value("signatures"_ls), result.signatures); + } + + static void dumpTo(QJsonObject &jo, const SignedOneTimeKey &result) + { + addParam<>(jo, QStringLiteral("key"), result.key); + addParam<>(jo, QStringLiteral("signatures"), result.signatures); + } }; bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); +template +class asKeyValueRange +{ +public: + asKeyValueRange(T &data) + : m_data{data} + { + } + + auto begin() { return m_data.keyValueBegin(); } + + auto end() { return m_data.keyValueEnd(); } + +private: + T &m_data; +}; + } // namespace Quotient + +Q_DECLARE_METATYPE(Quotient::SignedOneTimeKey) diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp index 76b0a263..fb91c906 100644 --- a/lib/crypto/qolmaccount.cpp +++ b/lib/crypto/qolmaccount.cpp @@ -4,6 +4,8 @@ #ifdef Quotient_E2EE_ENABLED #include "qolmaccount.h" +#include "connection.h" +#include "csapi/keys.h" #include "crypto/qolmutils.h" #include #include @@ -138,7 +140,7 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const return olm_account_max_number_of_one_time_keys(m_account); } -void QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const +size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const { const size_t randomLen = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); QByteArray randomBuffer = getRandom(randomLen); @@ -147,6 +149,7 @@ void QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const if (error == olm_error()) { throw lastError(m_account); } + return error; } OneTimeKeys QOlmAccount::oneTimeKeys() const @@ -212,6 +215,37 @@ OlmAccount *Quotient::QOlmAccount::data() return m_account; } +UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKeys) +{ + + DeviceKeys deviceKeys; + deviceKeys.userId = m_userId; + deviceKeys.deviceId = m_deviceId; + deviceKeys.algorithms = QStringList {"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}; + + const auto idKeys = identityKeys(); + deviceKeys.keys["curve25519:" + m_deviceId] = idKeys.curve25519; + deviceKeys.keys["ed25519:" + m_deviceId] = idKeys.ed25519; + + const auto sign = signIdentityKeys(); + deviceKeys.signatures[m_userId]["ed25519:" + m_deviceId] = sign; + + if (oneTimeKeys.curve25519().isEmpty()) { + return new UploadKeysJob(deviceKeys); + } + + // Sign & append the one time keys. + auto temp = signOneTimeKeys(oneTimeKeys); + QHash oneTimeKeysSigned; + for (const auto &[keyId, key] : asKeyValueRange(temp)) { + QVariant keyVar; + keyVar.setValue(key); + oneTimeKeysSigned[keyId] = keyVar; + } + + return new UploadKeysJob(deviceKeys, oneTimeKeysSigned); +} + std::variant, QOlmError> QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h index 4398214a..d61c8748 100644 --- a/lib/crypto/qolmaccount.h +++ b/lib/crypto/qolmaccount.h @@ -4,6 +4,7 @@ #pragma once #ifdef Quotient_E2EE_ENABLED +#include "csapi/keys.h" #include "crypto/e2ee.h" #include "crypto/qolmerrors.h" #include "crypto/qolmmessage.h" @@ -15,6 +16,7 @@ struct OlmAccount; namespace Quotient { class QOlmSession; +class Connection; //! An olm account manages all cryptographic keys used on a device. //! \code{.cpp} @@ -55,7 +57,7 @@ public: size_t maxNumberOfOneTimeKeys() const; //! Generates the supplied number of one time keys. - void generateOneTimeKeys(size_t numberOfKeys) const; + size_t generateOneTimeKeys(size_t numberOfKeys) const; //! Gets the OlmAccount's one time keys formatted as JSON. OneTimeKeys oneTimeKeys() const; @@ -68,6 +70,8 @@ public: SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; + UploadKeysJob *createUploadKeyRequest(const OneTimeKeys &oneTimeKeys); + //! Remove the one time key used to create the supplied session. [[nodiscard]] std::optional removeOneTimeKeys(const std::unique_ptr &session) const; @@ -90,7 +94,7 @@ public: QOlmAccount(OlmAccount *account); OlmAccount *data(); private: - OlmAccount *m_account = nullptr; + OlmAccount *m_account = nullptr; // owning QString m_userId; QString m_deviceId; }; diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 449eb2a3..c8dc6bdd 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -104,7 +104,7 @@ public: { // Try to decrypt message body using one of the known sessions for that // device - bool sessionsPassed = false; + /*bool sessionsPassed = false; // new e2ee TODO: for (auto &senderSession : sessions) { if (senderSession == sessions.last()) { @@ -152,7 +152,7 @@ public: qCDebug(E2EE) << "try to establish new InboundSession with" << senderKey; QOlmMessage preKeyMessage = QOlmMessage(message.toCiphertext(), QOlmMessage::PreKey); // new e2ee TODO: - const auto sessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), preKeyMessage); + //const auto sessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), preKeyMessage); if (const auto error = std::get_if(&sessionResult)) { qCDebug(E2EE) << "Error decrypting pre-key message when trying " @@ -161,7 +161,7 @@ public: return QString(); } - const auto newSession = std::get>(sessionResult); + const auto newSession = std::get>(olmAccount->createInboundSessionFrom(senderKey.toUtf8(), preKeyMessage)); qCDebug(E2EE) << "Created new Olm session" << newSession->sessionId(); @@ -178,9 +178,9 @@ public: << "Error removing one time keys" << error.value(); } - sessions.insert(senderKey, std::move(newSession)); - return std::get(decryptedResult); - } + //sessions.insert(senderKey, std::move(newSession)); TODO + //return std::get(decryptedResult); + }*/ return QString(); } }; diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 57618329..293538ee 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -40,7 +40,11 @@ public: NetworkAccessManager::NetworkAccessManager(QObject* parent) : QNetworkAccessManager(parent), d(std::make_unique(this)) -{} +{ + connect(this, &QNetworkAccessManager::sslErrors, this, [](QNetworkReply *reply, const QList &errors) { + reply->ignoreSslErrors(); + }); +} QList NetworkAccessManager::ignoredSslErrors() const { -- cgit v1.2.3