diff options
Diffstat (limited to 'lib/connection.cpp')
-rw-r--r-- | lib/connection.cpp | 273 |
1 files changed, 231 insertions, 42 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp index f3d31d2d..7400c82d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -19,7 +19,9 @@ #include "connection.h" #include "connectiondata.h" +#ifdef Quotient_E2EE_ENABLED #include "encryptionmanager.h" +#endif // Quotient_E2EE_ENABLED #include "room.h" #include "settings.h" #include "user.h" @@ -28,7 +30,6 @@ #include "csapi/capabilities.h" #include "csapi/joining.h" #include "csapi/leaving.h" -#include "csapi/login.h" #include "csapi/logout.h" #include "csapi/receipts.h" #include "csapi/room_send.h" @@ -43,6 +44,10 @@ #include "jobs/mediathumbnailjob.h" #include "jobs/syncjob.h" +#ifdef Quotient_E2EE_ENABLED +#include "account.h" // QtOlm +#endif // Quotient_E2EE_ENABLED + #include <QtCore/QCoreApplication> #include <QtCore/QDir> #include <QtCore/QElapsedTimer> @@ -105,7 +110,11 @@ public: GetCapabilitiesJob* capabilitiesJob = nullptr; GetCapabilitiesJob::Capabilities capabilities; + QVector<GetLoginFlowsJob::LoginFlow> loginFlows; + +#ifdef Quotient_E2EE_ENABLED QScopedPointer<EncryptionManager> encryptionManager; +#endif // Quotient_E2EE_ENABLED SyncJob* syncJob = nullptr; @@ -116,8 +125,10 @@ public: != "json"; bool lazyLoading = false; - void connectWithToken(const QString& userId, const QString& accessToken, - const QString& deviceId); + template <typename... LoginArgTs> + void loginToServer(LoginArgTs&&... loginArgs); + void assumeIdentity(const QString& userId, const QString& accessToken, + const QString& deviceId); void removeRoom(const QString& roomId); template <typename EventT> @@ -148,6 +159,70 @@ public: { return q->stateCacheDir().filePath("state.json"); } + + RoomEventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) + { +#ifndef Quotient_E2EE_ENABLED + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; + return {}; +#else // Quotient_E2EE_ENABLED + if (encryptedEvent.algorithm() != OlmV1Curve25519AesSha2AlgoKey) + { + return {}; + } + QString identityKey = + encryptionManager->account()->curve25519IdentityKey(); + QJsonObject personalCipherObject = + encryptedEvent.ciphertext(identityKey); + if (personalCipherObject.isEmpty()) { + qCDebug(E2EE) << "Encrypted event is not for the current device"; + return {}; + } + QString decrypted = encryptionManager->sessionDecryptMessage( + personalCipherObject, encryptedEvent.senderKey().toLatin1()); + if (decrypted.isEmpty()) { + qCDebug(E2EE) << "Problem with new session from senderKey:" + << encryptedEvent.senderKey() + << encryptionManager->account()->oneTimeKeys(); + return {}; + } + + RoomEventPtr decryptedEvent = makeEvent<RoomMessageEvent>( + QJsonDocument::fromJson(decrypted.toUtf8()).object()); + + if (decryptedEvent->senderId() != encryptedEvent.senderId()) { + qCDebug(E2EE) << "Found user" << decryptedEvent->senderId() + << "instead of sender" << encryptedEvent.senderId() + << "in Olm plaintext"; + return {}; + } + + // TODO: keys to constants + QJsonObject decryptedEventObject = decryptedEvent->fullJson(); + QString recipient = + decryptedEventObject.value("recipient"_ls).toString(); + if (recipient != data->userId()) { + qCDebug(E2EE) << "Found user" << recipient << "instead of us" + << data->userId() << "in Olm plaintext"; + return {}; + } + QString ourKey = decryptedEventObject.value("recipient_keys"_ls) + .toObject() + .value(Ed25519Key) + .toString(); + if (ourKey + != QString::fromUtf8( + encryptionManager->account()->ed25519IdentityKey())) { + qCDebug(E2EE) << "Found key" << ourKey + << "instead of ours own ed25519 key" + << encryptionManager->account()->ed25519IdentityKey() + << "in Olm plaintext"; + return {}; + } + + return decryptedEvent; +#endif // Quotient_E2EE_ENABLED + } }; Connection::Connection(const QUrl& server, QObject* parent) @@ -223,49 +298,50 @@ void Connection::resolveServer(const QString& mxid) }); } -void Connection::connectToServer(const QString& user, const QString& password, +inline UserIdentifier makeUserIdentifier(const QString& id) +{ + return { QStringLiteral("m.id.user"), { { QStringLiteral("user"), id } } }; +} + +inline UserIdentifier make3rdPartyIdentifier(const QString& medium, + const QString& address) +{ + return { QStringLiteral("m.id.thirdparty"), + { { QStringLiteral("medium"), medium }, + { QStringLiteral("address"), address } } }; +} + +void Connection::connectToServer(const QString& userId, const QString& password, const QString& initialDeviceName, const QString& deviceId) { - checkAndConnect(user, [=] { - doConnectToServer(user, password, initialDeviceName, deviceId); + checkAndConnect(userId, [=] { + d->loginToServer(LoginFlows::Password.type, makeUserIdentifier(userId), + password, /*token*/ "", deviceId, initialDeviceName); }); } -void Connection::doConnectToServer(const QString& user, const QString& password, - const QString& initialDeviceName, - const QString& deviceId) + +SsoSession* Connection::prepareForSso(const QString& initialDeviceName, + const QString& deviceId) { - auto loginJob = - callApi<LoginJob>(QStringLiteral("m.login.password"), - UserIdentifier { QStringLiteral("m.id.user"), - { { QStringLiteral("user"), user } } }, - password, /*token*/ "", deviceId, initialDeviceName); - connect(loginJob, &BaseJob::success, this, [this, loginJob] { - d->connectWithToken(loginJob->userId(), loginJob->accessToken(), - loginJob->deviceId()); - - AccountSettings accountSettings(loginJob->userId()); - d->encryptionManager.reset( - new EncryptionManager(accountSettings.encryptionAccountPickle())); - if (accountSettings.encryptionAccountPickle().isEmpty()) { - accountSettings.setEncryptionAccountPickle( - d->encryptionManager->olmAccountPickle()); - } + return new SsoSession(this, initialDeviceName, deviceId); +} - d->encryptionManager->uploadIdentityKeys(this); - d->encryptionManager->uploadOneTimeKeys(this); - }); - connect(loginJob, &BaseJob::failure, this, [this, loginJob] { - emit loginError(loginJob->errorString(), loginJob->rawDataSample()); - }); +void Connection::loginWithToken(const QByteArray& loginToken, + const QString& initialDeviceName, + const QString& deviceId) +{ + d->loginToServer(LoginFlows::Token.type, + makeUserIdentifier(/*user is encoded in loginToken*/ {}), + /*password*/ "", loginToken, deviceId, initialDeviceName); } -void Connection::connectWithToken(const QString& userId, - const QString& accessToken, - const QString& deviceId) +void Connection::assumeIdentity(const QString& userId, + const QString& accessToken, + const QString& deviceId) { checkAndConnect(userId, - [=] { d->connectWithToken(userId, accessToken, deviceId); }); + [=] { d->assumeIdentity(userId, accessToken, deviceId); }); } void Connection::reloadCapabilities() @@ -298,9 +374,29 @@ bool Connection::loadingCapabilities() const return !d->capabilities.roomVersions; } -void Connection::Private::connectWithToken(const QString& userId, - const QString& accessToken, - const QString& deviceId) +template <typename... LoginArgTs> +void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) +{ + auto loginJob = + q->callApi<LoginJob>(std::forward<LoginArgTs>(loginArgs)...); + connect(loginJob, &BaseJob::success, q, [this, loginJob] { + assumeIdentity(loginJob->userId(), loginJob->accessToken(), + loginJob->deviceId()); +#ifndef Quotient_E2EE_ENABLED + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; +#else // Quotient_E2EE_ENABLED + encryptionManager->uploadIdentityKeys(this); + encryptionManager->uploadOneTimeKeys(this); +#endif // Quotient_E2EE_ENABLED + }); + connect(loginJob, &BaseJob::failure, q, [this, loginJob] { + emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); + }); +} + +void Connection::Private::assumeIdentity(const QString& userId, + const QString& accessToken, + const QString& deviceId) { data->setUserId(userId); q->user(); // Creates a User object for the local user @@ -309,6 +405,17 @@ void Connection::Private::connectWithToken(const QString& userId, q->setObjectName(userId % '/' % deviceId); qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << userId << "from device" << deviceId; +#ifndef Quotient_E2EE_ENABLED + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; +#else // Quotient_E2EE_ENABLED + AccountSettings accountSettings(userId); + encryptionManager.reset( + new EncryptionManager(accountSettings.encryptionAccountPickle())); + if (accountSettings.encryptionAccountPickle().isEmpty()) { + accountSettings.setEncryptionAccountPickle( + encryptionManager->olmAccountPickle()); + } +#endif // Quotient_E2EE_ENABLED emit q->stateChanged(); emit q->connected(); q->reloadCapabilities(); @@ -535,6 +642,61 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->dcLocalAdditions.clear(); d->dcLocalRemovals.clear(); } +#ifndef Quotient_E2EE_ENABLED + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; +#else // Quotient_E2EE_ENABLED + // handling m.room_key to-device encrypted event + for (auto&& toDeviceEvent : data.takeToDeviceEvents()) { + if (toDeviceEvent->type() == EncryptedEvent::typeId()) { + event_ptr_tt<EncryptedEvent> encryptedEvent = + makeEvent<EncryptedEvent>(toDeviceEvent->fullJson()); + if (encryptedEvent->algorithm() != OlmV1Curve25519AesSha2AlgoKey) { + qCDebug(E2EE) + << "Encrypted event" << encryptedEvent->id() << "algorithm" + << encryptedEvent->algorithm() << "is not supported"; + return; + } + + // TODO: full maintaining of the device keys + // with device_lists sync extention and /keys/query + qCDebug(E2EE) << "Getting device keys for the m.room_key sender:" + << encryptedEvent->senderId(); + // d->encryptionManager->updateDeviceKeys(); + + RoomEventPtr decryptedEvent = + d->sessionDecryptMessage(*encryptedEvent.get()); + // since we are waiting for the RoomKeyEvent: + event_ptr_tt<RoomKeyEvent> roomKeyEvent = + makeEvent<RoomKeyEvent>(decryptedEvent->fullJson()); + if (!roomKeyEvent) { + qCDebug(E2EE) << "Failed to decrypt olm event from user" + << encryptedEvent->senderId(); + return; + } + Room* detectedRoom = room(roomKeyEvent->roomId()); + if (!detectedRoom) { + qCDebug(E2EE) + << "Encrypted event room id" << encryptedEvent->roomId() + << "is not found at the connection"; + return; + } + detectedRoom->handleRoomKeyEvent(roomKeyEvent.get(), + encryptedEvent->senderKey()); + } + } + // handling device_one_time_keys_count + auto deviceOneTimeKeysCount = data.deviceOneTimeKeysCount(); + if (!d->encryptionManager) + { + qCDebug(E2EE) << "Encryption manager is not there yet"; + return; + } + if (!deviceOneTimeKeysCount.isEmpty()) + { + d->encryptionManager->updateOneTimeKeyCounts(this, + deviceOneTimeKeysCount); + } +#endif // Quotient_E2EE_ENABLED } void Connection::stopSync() @@ -871,6 +1033,21 @@ QUrl Connection::homeserver() const { return d->data->baseUrl(); } QString Connection::domain() const { return userId().section(':', 1); } +QVector<GetLoginFlowsJob::LoginFlow> Connection::loginFlows() const +{ + return d->loginFlows; +} + +bool Connection::supportsPasswordAuth() const +{ + return d->loginFlows.contains(LoginFlows::Password); +} + +bool Connection::supportsSso() const +{ + return d->loginFlows.contains(LoginFlows::SSO); +} + Room* Connection::room(const QString& roomId, JoinStates states) const { Room* room = d->roomMap.value({ roomId, false }, nullptr); @@ -956,10 +1133,12 @@ QString Connection::deviceId() const { return d->data->deviceId(); } QByteArray Connection::accessToken() const { return d->data->accessToken(); } +#ifdef Quotient_E2EE_ENABLED QtOlm::Account* Connection::olmAccount() const { return d->encryptionManager->account(); } +#endif // Quotient_E2EE_ENABLED SyncJob* Connection::syncJob() const { return d->syncJob; } @@ -1263,11 +1442,21 @@ QByteArray Connection::generateTxnId() const void Connection::setHomeserver(const QUrl& url) { - if (homeserver() == url) - return; + if (homeserver() != url) { + d->data->setBaseUrl(url); + d->loginFlows.clear(); + emit homeserverChanged(homeserver()); + } - d->data->setBaseUrl(url); - emit homeserverChanged(homeserver()); + // Whenever a homeserver is updated, retrieve available login flows from it + auto* j = callApi<GetLoginFlowsJob>(BackgroundRequest); + connect(j, &BaseJob::finished, this, [this, j] { + if (j->status().good()) + d->loginFlows = j->flows(); + else + d->loginFlows.clear(); + emit loginFlowsChanged(); + }); } void Connection::saveRoomState(Room* r) const |