aboutsummaryrefslogtreecommitdiff
path: root/lib/connection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/connection.cpp')
-rw-r--r--lib/connection.cpp273
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