aboutsummaryrefslogtreecommitdiff
path: root/src/client/QXmppOmemoManager.cpp
diff options
context:
space:
mode:
authorLinus Jahn <lnj@kaidan.im>2022-05-31 12:22:48 +0200
committerLinus Jahn <lnj@kaidan.im>2022-08-13 15:55:03 +0200
commitf0e0e1cd227c08ffd97aa42813a479b8c3ca6d23 (patch)
tree12ca6aefdef9195ee8937406db86e73cf1a5f820 /src/client/QXmppOmemoManager.cpp
parent87df8a8dda385558d39639ce09e2717974264ab6 (diff)
downloadqxmpp-f0e0e1cd227c08ffd97aa42813a479b8c3ca6d23.tar.gz
Split up OMEMO into extra module
Diffstat (limited to 'src/client/QXmppOmemoManager.cpp')
-rw-r--r--src/client/QXmppOmemoManager.cpp1282
1 files changed, 0 insertions, 1282 deletions
diff --git a/src/client/QXmppOmemoManager.cpp b/src/client/QXmppOmemoManager.cpp
deleted file mode 100644
index cb66a515..00000000
--- a/src/client/QXmppOmemoManager.cpp
+++ /dev/null
@@ -1,1282 +0,0 @@
-// SPDX-FileCopyrightText: 2022 Melvin Keskin <melvo@olomono.de>
-// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im>
-//
-// SPDX-License-Identifier: LGPL-2.1-or-later
-
-#include "QXmppClient.h"
-#include "QXmppConstants_p.h"
-#include "QXmppOmemoDeviceElement_p.h"
-#include "QXmppOmemoDeviceList_p.h"
-#include "QXmppOmemoElement_p.h"
-#include "QXmppOmemoEnvelope_p.h"
-#include "QXmppOmemoIq_p.h"
-#include "QXmppOmemoItems_p.h"
-#include "QXmppOmemoManager_p.h"
-#include "QXmppPubSubEvent.h"
-#include "QXmppTrustManager.h"
-#include "QXmppUtils.h"
-
-#include <QStringBuilder>
-
-using namespace QXmpp;
-using namespace QXmpp::Private;
-using namespace QXmpp::Omemo::Private;
-
-using Error = QXmppStanza::Error;
-using Manager = QXmppOmemoManager;
-using ManagerPrivate = QXmppOmemoManagerPrivate;
-
-// default label used for the own device
-const auto DEVICE_LABEL = QStringLiteral("QXmpp");
-
-class QXmppOmemoOwnDevicePrivate : public QSharedData
-{
-public:
- QString label;
- QByteArray keyId;
-};
-
-///
-/// \class QXmppOmemoOwnDevice
-///
-/// \brief The QXmppOmemoOwnDevice class represents the \xep{0384, OMEMO Encryption} device of this
-/// client instance.
-///
-
-///
-/// Constructs an OMEMO device for this client instance.
-///
-QXmppOmemoOwnDevice::QXmppOmemoOwnDevice()
- : d(new QXmppOmemoOwnDevicePrivate)
-{
-}
-
-/// Copy-constructor.
-QXmppOmemoOwnDevice::QXmppOmemoOwnDevice(const QXmppOmemoOwnDevice &other) = default;
-/// Move-constructor.
-QXmppOmemoOwnDevice::QXmppOmemoOwnDevice(QXmppOmemoOwnDevice &&) noexcept = default;
-QXmppOmemoOwnDevice::~QXmppOmemoOwnDevice() = default;
-/// Assignment operator.
-QXmppOmemoOwnDevice &QXmppOmemoOwnDevice::operator=(const QXmppOmemoOwnDevice &) = default;
-/// Move-assignment operator.
-QXmppOmemoOwnDevice &QXmppOmemoOwnDevice::operator=(QXmppOmemoOwnDevice &&) = default;
-
-///
-/// Returns the human-readable string used to identify the device by users.
-///
-/// If no label is set, a default-constructed QString is returned.
-///
-/// \return the label to identify the device
-///
-QString QXmppOmemoOwnDevice::label() const
-{
- return d->label;
-}
-
-///
-/// Sets an optional human-readable string used to identify the device by users.
-///
-/// The label should not contain more than 53 characters.
-///
-/// \param label label to identify the device
-///
-void QXmppOmemoOwnDevice::setLabel(const QString &label)
-{
- d->label = label;
-}
-
-///
-/// Returns the ID of the public long-term key which never changes.
-///
-/// \return public long-term key ID
-///
-QByteArray QXmppOmemoOwnDevice::keyId() const
-{
- return d->keyId;
-}
-
-///
-/// Sets the ID of the public long-term key which never changes.
-///
-/// \param keyId public long-term key ID
-///
-void QXmppOmemoOwnDevice::setKeyId(const QByteArray &keyId)
-{
- d->keyId = keyId;
-}
-
-class QXmppOmemoDevicePrivate : public QSharedData
-{
-public:
- QString jid;
- TrustLevel trustLevel = TrustLevel::Undecided;
- QString label;
- QByteArray keyId;
-};
-
-///
-/// \class QXmppOmemoDevice
-///
-/// \brief The QXmppOmemoDevice class represents a \xep{0384, OMEMO Encryption} device.
-///
-
-///
-/// Constructs an OMEMO device.
-///
-QXmppOmemoDevice::QXmppOmemoDevice()
- : d(new QXmppOmemoDevicePrivate)
-{
-}
-
-/// Copy-constructor.
-QXmppOmemoDevice::QXmppOmemoDevice(const QXmppOmemoDevice &other) = default;
-/// Move-constructor.
-QXmppOmemoDevice::QXmppOmemoDevice(QXmppOmemoDevice &&) noexcept = default;
-QXmppOmemoDevice::~QXmppOmemoDevice() = default;
-/// Assignment operator.
-QXmppOmemoDevice &QXmppOmemoDevice::operator=(const QXmppOmemoDevice &) = default;
-/// Move-assignment operator.
-QXmppOmemoDevice &QXmppOmemoDevice::operator=(QXmppOmemoDevice &&) = default;
-
-///
-/// Returns the device owner's bare JID.
-///
-/// \return the bare JID
-///
-QString QXmppOmemoDevice::jid() const
-{
- return d->jid;
-}
-
-///
-/// Sets the device owner's bare JID.
-///
-/// \param jid bare JID of the device owner
-///
-void QXmppOmemoDevice::setJid(const QString &jid)
-{
- d->jid = jid;
-}
-
-///
-/// Returns the human-readable string used to identify the device by users.
-///
-/// If no label is set, a default-constructed QString is returned.
-///
-/// \return the label to identify the device
-///
-QString QXmppOmemoDevice::label() const
-{
- return d->label;
-}
-
-///
-/// Sets an optional human-readable string used to identify the device by users.
-///
-/// The label should not contain more than 53 characters.
-///
-/// \param label label to identify the device
-///
-void QXmppOmemoDevice::setLabel(const QString &label)
-{
- d->label = label;
-}
-
-///
-/// Returns the ID of the public long-term key which never changes.
-///
-/// \return public long-term key ID
-///
-QByteArray QXmppOmemoDevice::keyId() const
-{
- return d->keyId;
-}
-
-///
-/// Sets the ID of the public long-term key which never changes.
-///
-/// \param keyId public long-term key ID
-///
-void QXmppOmemoDevice::setKeyId(const QByteArray &keyId)
-{
- d->keyId = keyId;
-}
-
-///
-/// Returns the trust level of the key.
-///
-/// \return the key's trust level
-///
-TrustLevel QXmppOmemoDevice::trustLevel() const
-{
- return d->trustLevel;
-}
-
-///
-/// Sets the trust level of the key.
-///
-/// \param trustLevel key's trust level
-///
-void QXmppOmemoDevice::setTrustLevel(TrustLevel trustLevel)
-{
- d->trustLevel = trustLevel;
-}
-
-///
-/// \class QXmppOmemoManager
-///
-/// The QXmppOmemoManager class manages OMEMO encryption as defined in \xep{0384,
-/// OMEMO Encryption}.
-///
-/// OMEMO uses \xep{0060, Publish-Subscribe} (PubSub) and \xep{0163, Personal Eventing Protocol}
-/// (PEP).
-/// Thus, they must be supported by the server and the corresponding PubSub manager must be added to
-/// the client:
-/// \code
-/// QXmppPubSubManager *pubSubManager = new QXmppPubSubManager;
-/// client->addExtension(pubSubManager);
-/// \endcode
-///
-/// For interacting with the storage, corresponding implementations of the storage interfaces must
-/// be instantiated.
-/// Those implementations have to be adapted to your storage such as a database.
-/// In case you only need memory and no persistent storage, you can use the existing
-/// implementations:
-/// \code
-/// QXmppOmemoStorage *omemoStorage = new QXmppOmemoMemoryStorage;
-/// QXmppTrustStorage *trustStorage = new QXmppTrustMemoryStorage;
-/// \endcode
-///
-/// A trust manager using its storage must be added to the client:
-/// \code
-/// QXmppTrustManager *trustManager = new QXmppAtmManager(trustStorage);
-/// client->addExtension(trustManager);
-/// \endcode
-///
-/// Afterwards, the OMEMO manager using its storage must be added to the client:
-/// \code
-/// QXmppOmemoManager *manager = new QXmppOmemoManager(omemoStorage);
-/// client->addExtension(manager);
-/// \endcode
-///
-/// You can set a security policy used by OMEMO.
-/// Is is recommended to apply TOAKAFA for good security and usability when using
-/// \xep{0450, Automatic Trust Management (ATM)}:
-/// \code
-/// manager->setSecurityPolicy(QXmpp::Toakafa);
-/// \endcode
-///
-/// \xep{0280, Message Carbons} should be used for delivering messages to all endpoints of a user:
-/// \code
-/// QXmppCarbonManager *carbonManager = new QXmppCarbonManager;
-/// client->addExtension(carbonManager);
-/// connect(client, &QXmppClient::connected, this, [=]() {
-/// carbonManager->setCarbonsEnabled(true);
-/// });
-/// connect(carbonManager, &QXmppCarbonManager::messageSent, manager,
-/// &QXmppOmemoManager::handleMessage);
-/// connect(carbonManager, &QXmppCarbonManager::messageReceived, manager,
-/// &QXmppOmemoManager::handleMessage);
-/// \endcode
-///
-/// The OMEMO data must be loaded before connecting to the server:
-/// \code
-/// manager->load();
-/// });
-/// \endcode
-///
-/// If no OMEMO data could be loaded (i.e., the result of \c load() is "false"), it must be set up
-/// first.
-/// That can be done as soon as the user is logged in to the server:
-/// \code
-/// connect(client, &QXmppClient::connected, this, [=]() {
-/// auto future = manager->start();
-/// });
-/// \endcode
-///
-/// Once the future is finished and the result is "true", the manager is ready for use.
-/// Otherwise, check the logging output for details.
-///
-/// By default, stanzas are only sent to devices having keys with the following trust levels:
-/// \code
-/// QXmpp::TrustLevel::AutomaticallyTrusted | QXmpp::TrustLevel::ManuallyTrusted
-/// | QXmpp::TrustLevel::Authenticated
-/// \endcode
-/// That behavior can be changed for each message being sent by specifying the
-/// accepted trust levels:
-/// \code
-/// QXmppSendStanzaParams params;
-/// params.setAcceptedTrustLevels(QXmpp::TrustLevel::Authenticated)
-/// client->send(stanza, params);
-/// \endcode
-///
-/// Stanzas can be encrypted for multiple JIDs which is needed in group chats:
-/// \code
-/// QXmppSendStanzaParams params;
-/// params.setEncryptionJids({ "alice@example.org", "bob@example.com" })
-/// client->send(stanza, params);
-/// \endcode
-///
-/// \warning THIS API IS NOT FINALIZED YET!
-///
-/// \ingroup Managers
-///
-/// \since QXmpp 1.5
-///
-
-///
-/// \typedef QXmppOmemoManager::Result
-///
-/// Contains QXmpp::Success for success or an QXmppStanza::Error for an error.
-///
-
-///
-/// Constructs an OMEMO manager.
-///
-/// \param omemoStorage storage used to store all OMEMO data
-///
-QXmppOmemoManager::QXmppOmemoManager(QXmppOmemoStorage *omemoStorage)
- : d(new ManagerPrivate(this, omemoStorage))
-{
- d->ownDevice.label = DEVICE_LABEL;
- d->init();
- d->schedulePeriodicTasks();
-}
-
-QXmppOmemoManager::~QXmppOmemoManager() = default;
-
-///
-/// Loads all locally stored OMEMO data.
-///
-/// This should be called after starting the client and before the login.
-/// It must only be called after \c setUp() has been called once for the user
-/// during one of the past login session.
-/// It does not need to be called if setUp() has been called during the current
-/// login session.
-///
-/// \see QXmppOmemoManager::setUp()
-///
-/// \return whether everything is loaded successfully
-///
-QFuture<bool> Manager::load()
-{
- QFutureInterface<bool> interface(QFutureInterfaceBase::Started);
-
- auto future = d->omemoStorage->allData();
- await(future, this, [=](QXmppOmemoStorage::OmemoData omemoData) mutable {
- const auto &optionalOwnDevice = omemoData.ownDevice;
- if (optionalOwnDevice) {
- d->ownDevice = *optionalOwnDevice;
- } else {
- debug("Device could not be loaded because it is not stored");
- reportFinishedResult(interface, false);
- return;
- }
-
- const auto &signedPreKeyPairs = omemoData.signedPreKeyPairs;
- if (signedPreKeyPairs.isEmpty()) {
- warning("Signed Pre keys could not be loaded because none is stored");
- reportFinishedResult(interface, false);
- return;
- } else {
- d->signedPreKeyPairs = signedPreKeyPairs;
- d->renewSignedPreKeyPairs();
- }
-
- const auto &preKeyPairs = omemoData.preKeyPairs;
- if (preKeyPairs.isEmpty()) {
- warning("Pre keys could not be loaded because none is stored");
- reportFinishedResult(interface, false);
- return;
- } else {
- d->preKeyPairs = preKeyPairs;
- }
-
- d->devices = omemoData.devices;
- d->removeDevicesRemovedFromServer();
-
- reportFinishedResult(interface, d->isStarted = true);
- });
-
- return interface.future();
-}
-
-///
-/// Sets up all OMEMO data locally and on the server.
-///
-/// The user must be logged in while calling this.
-///
-/// \return whether everything is set up successfully
-///
-QFuture<bool> Manager::setUp()
-{
- QFutureInterface<bool> interface(QFutureInterfaceBase::Started);
-
- auto future = d->setUpDeviceId();
- await(future, this, [=](bool isDeviceIdSetUp) mutable {
- if (isDeviceIdSetUp) {
- // The identity key pair in its deserialized form is not stored as a
- // member variable because it is only needed by
- // updateSignedPreKeyPair().
- RefCountedPtr<ratchet_identity_key_pair> identityKeyPair;
-
- if (d->setUpIdentityKeyPair(identityKeyPair.ptrRef()) &&
- d->updateSignedPreKeyPair(identityKeyPair.get()) &&
- d->updatePreKeyPairs(PRE_KEY_INITIAL_CREATION_COUNT)) {
- auto future = d->omemoStorage->setOwnDevice(d->ownDevice);
- await(future, this, [=]() mutable {
- auto future = d->publishOmemoData();
- await(future, this, [=](bool isPublished) mutable {
- reportFinishedResult(interface, d->isStarted = isPublished);
- });
- });
- } else {
- reportFinishedResult(interface, false);
- }
- } else {
- reportFinishedResult(interface, false);
- }
- });
-
- return interface.future();
-}
-
-///
-/// Returns the key of this client instance.
-///
-/// \return the own key
-///
-QFuture<QByteArray> Manager::ownKey()
-{
- return d->trustManager->ownKey(ns_omemo_2);
-}
-
-///
-/// Returns the JIDs of all key owners mapped to the IDs of their keys with
-/// specific trust levels.
-///
-/// If no trust levels are passed, all keys are returned.
-///
-/// This should be called in order to get all stored keys which can be more than
-/// the stored devices because of trust decisions made without a published or
-/// received device.
-///
-/// \param trustLevels trust levels of the keys
-///
-/// \return the key owner JIDs mapped to their keys with specific trust levels
-///
-QFuture<QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>>> Manager::keys(QXmpp::TrustLevels trustLevels)
-{
- return d->trustManager->keys(ns_omemo_2, trustLevels);
-}
-
-///
-/// Returns the IDs of keys mapped to their trust levels for specific key
-/// owners.
-///
-/// If no trust levels are passed, all keys for jids are returned.
-///
-/// This should be called in order to get the stored keys which can be more than
-/// the stored devices because of trust decisions made without a published or
-/// received device.
-///
-/// \param jids key owners' bare JIDs
-/// \param trustLevels trust levels of the keys
-///
-/// \return the key IDs mapped to their trust levels for specific key owners
-///
-QFuture<QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>>> Manager::keys(const QList<QString> &jids, QXmpp::TrustLevels trustLevels)
-{
- return d->trustManager->keys(ns_omemo_2, jids, trustLevels);
-}
-
-///
-/// Changes the label of the own (this client instance's current user's) device.
-///
-/// The label is a human-readable string used to identify the device by users.
-///
-/// If the OMEMO manager is not started yet, the device label is only changed
-/// locally in memory.
-/// It is stored persistently in the OMEMO storage and updated on the
-/// server if the OMEMO manager is already started or once it is.
-///
-/// \param deviceLabel own device's label
-///
-/// \return whether the action was successful
-///
-QFuture<bool> Manager::changeDeviceLabel(const QString &deviceLabel)
-{
- return d->changeDeviceLabel(deviceLabel);
-}
-
-///
-/// Returns the maximum count of devices stored per JID.
-///
-/// If more devices than that maximum are received for one JID from a server,
-/// they will not be stored locally and thus not used for encryption.
-///
-/// \return the maximum count of devices stored per JID
-///
-int Manager::maximumDevicesPerJid() const
-{
- return d->maximumDevicesPerJid;
-}
-
-///
-/// Sets the maximum count of devices stored per JID.
-///
-/// If more devices than that maximum are received for one JID from a server,
-/// they will not be stored locally and thus not used for encryption.
-///
-/// \param maximum maximum count of devices stored per JID
-///
-void Manager::setMaximumDevicesPerJid(int maximum)
-{
- d->maximumDevicesPerJid = maximum;
-}
-
-///
-/// Returns the maximum count of devices for whom a stanza is encrypted.
-///
-/// If more devices than that maximum are stored for all addressed recipients of
-/// a stanza, the stanza will only be encrypted for first devices until the
-/// maximum is reached.
-///
-/// \return the maximum count of devices for whom a stanza is encrypted
-///
-int Manager::maximumDevicesPerStanza() const
-{
- return d->maximumDevicesPerStanza;
-}
-
-/// Sets the maximum count of devices for whom a stanza is encrypted.
-///
-/// If more devices than that maximum are stored for all addressed recipients of
-/// a stanza, the stanza will only be encrypted for first devices until the
-/// maximum is reached.
-///
-/// \param maximum maximum count of devices for whom a stanza is encrypted
-///
-void Manager::setMaximumDevicesPerStanza(int maximum)
-{
- d->maximumDevicesPerStanza = maximum;
-}
-
-///
-/// Requests device lists from contacts and stores them locally.
-///
-/// The user must be logged in while calling this.
-/// The JID of the current user must not be passed.
-///
-/// \param jids JIDs of the contacts whose device lists are being requested
-///
-/// \return the results of the requests for each JID
-///
-QFuture<Manager::DevicesResult> Manager::requestDeviceLists(const QList<QString> &jids)
-{
- if (const auto jidsCount = jids.size()) {
- QFutureInterface<Manager::DevicesResult> interface(QFutureInterfaceBase::Started);
- auto processedJidsCount = std::make_shared<int>(0);
-
- for (const auto &jid : jids) {
- Q_ASSERT_X(jid != d->ownBareJid(), "Requesting contact's device list", "Own JID passed");
-
- auto future = d->requestDeviceList(jid);
- await(future, this, [=](auto result) mutable {
- DevicesResult devicesResult {
- jid,
- mapSuccess(std::move(result), [](QXmppOmemoDeviceListItem) { return Success(); })
- };
- interface.reportResult(devicesResult);
-
- if (++(*processedJidsCount) == jidsCount) {
- interface.reportFinished();
- }
- });
- }
- return interface.future();
- }
- return QFutureInterface<DevicesResult>(QFutureInterfaceBase::Finished).future();
-}
-
-///
-/// Subscribes the current user's resource to device lists manually.
-///
-/// This should be called after each login and only for contacts without
-/// presence subscription because their device lists are not automatically
-/// subscribed.
-/// The user must be logged in while calling this.
-///
-/// Call \c QXmppOmemoManager::unsubscribeFromDeviceLists() before logout.
-///
-/// \param jids JIDs of the contacts whose device lists are being subscribed
-///
-/// \return the results of the subscription for each JID
-///
-QFuture<Manager::DevicesResult> Manager::subscribeToDeviceLists(const QList<QString> &jids)
-{
- QFutureInterface<Manager::DevicesResult> interface(QFutureInterfaceBase::Started);
-
- if (const auto jidsCount = jids.size()) {
- auto processedJidsCount = std::make_shared<int>(0);
-
- for (const auto &jid : jids) {
- auto future = d->subscribeToDeviceList(jid);
- await(future, this, [=](QXmppPubSubManager::Result result) mutable {
- Manager::DevicesResult devicesResult;
- devicesResult.jid = jid;
- devicesResult.result = result;
- interface.reportResult(devicesResult);
-
- if (++(*processedJidsCount) == jidsCount) {
- interface.reportFinished();
- }
- });
- }
- } else {
- interface.reportFinished();
- }
-
- return interface.future();
-}
-
-///
-/// Unsubscribes the current user's resource from all device lists that were
-/// manually subscribed by \c QXmppOmemoManager::subscribeToDeviceList().
-///
-/// This should be called before each logout.
-/// The user must be logged in while calling this.
-///
-/// \return the results of the unsubscription for each JID
-///
-QFuture<Manager::DevicesResult> Manager::unsubscribeFromDeviceLists()
-{
- return d->unsubscribeFromDeviceLists(d->jidsOfManuallySubscribedDevices);
-}
-
-///
-/// Returns the device of this client instance's current user.
-///
-/// \return the own device
-///
-QXmppOmemoOwnDevice Manager::ownDevice()
-{
- const auto &ownDevice = d->ownDevice;
-
- QXmppOmemoOwnDevice device;
- device.setLabel(ownDevice.label);
- device.setKeyId(createKeyId(ownDevice.publicIdentityKey));
-
- return device;
-}
-
-/// Returns all locally stored devices except the own device.
-///
-/// Only devices that have been received after subscribing the corresponding
-/// device lists on the server are stored locally.
-/// Thus, only those are returned.
-/// Call \c QXmppOmemoManager::subscribeToDeviceLists() for contacts without
-/// presence subscription before.
-///
-/// /\return all devices except the own device
-///
-QFuture<QVector<QXmppOmemoDevice>> Manager::devices()
-{
- return devices(d->devices.keys());
-}
-
-///
-/// Returns locally stored devices except the own device.
-///
-/// Only devices that have been received after subscribing the corresponding
-/// device lists on the server are stored locally.
-/// Thus, only those are returned.
-/// Call \c QXmppOmemoManager::subscribeToDeviceLists() for contacts without
-/// presence subscription before.
-///
-/// \param jids JIDs whose devices are being retrieved
-///
-/// \return all devices of the passed JIDs
-///
-QFuture<QVector<QXmppOmemoDevice>> Manager::devices(const QList<QString> &jids)
-{
- QFutureInterface<QVector<QXmppOmemoDevice>> interface(QFutureInterfaceBase::Started);
-
- auto future = keys(jids);
- await(future, this, [=](QHash<QString, QHash<QByteArray, TrustLevel>> keys) mutable {
- QVector<QXmppOmemoDevice> devices;
-
- for (const auto &jid : jids) {
- const auto &storedDevices = d->devices.value(jid);
- const auto &storedKeys = keys.value(jid);
-
- for (const auto &storedDevice : storedDevices) {
- const auto &keyId = storedDevice.keyId;
-
- QXmppOmemoDevice device;
- device.setJid(jid);
- device.setLabel(storedDevice.label);
-
- if (!keyId.isEmpty()) {
- device.setKeyId(keyId);
- device.setTrustLevel(storedKeys.value(keyId));
- }
-
- devices.append(device);
- }
- }
-
- reportFinishedResult(interface, devices);
- });
-
- return interface.future();
-}
-
-///
-/// Removes all devices of a contact and the subscription to the contact's
-/// device list.
-///
-/// This should be called after removing a contact.
-/// The JID of the current user must not be passed.
-/// Use \c QXmppOmemoManager::resetAll() in order to remove all devices of the
-/// user.
-///
-/// \param jid JID of the contact whose devices are being removed
-///
-/// \return the result of the contact device removals
-///
-QFuture<QXmppPubSubManager::Result> Manager::removeContactDevices(const QString &jid)
-{
- QFutureInterface<QXmppPubSubManager::Result> interface(QFutureInterfaceBase::Started);
-
- Q_ASSERT_X(jid != d->ownBareJid(), "Removing contact device", "Own JID passed");
-
- auto future = d->unsubscribeFromDeviceList(jid);
- await(future, this, [=](QXmppPubSubManager::Result result) mutable {
- if (std::holds_alternative<QXmppStanza::Error>(result)) {
- warning("Contact '" % jid % "' could not be removed because the device list subscription could not be removed");
- reportFinishedResult(interface, result);
- } else {
- d->devices.remove(jid);
-
- auto future = d->omemoStorage->removeDevices(jid);
- await(future, this, [=]() mutable {
- auto future = d->trustManager->removeKeys(ns_omemo_2, jid);
- await(future, this, [=]() mutable {
- reportFinishedResult(interface, result);
- emit devicesRemoved(jid);
- });
- });
- }
- });
-
- return interface.future();
-}
-
-///
-/// Sets the trust levels keys must have in order to build sessions for their
-/// devices.
-///
-/// \param trustLevels trust levels of the keys used for building sessions
-///
-void Manager::setAcceptedSessionBuildingTrustLevels(QXmpp::TrustLevels trustLevels)
-{
- d->acceptedSessionBuildingTrustLevels = trustLevels;
-}
-
-///
-/// Returns the trust levels keys must have in order to build sessions for their
-/// devices.
-///
-/// \return the trust levels of the keys used for building sessions
-///
-TrustLevels Manager::acceptedSessionBuildingTrustLevels()
-{
- return d->acceptedSessionBuildingTrustLevels;
-}
-
-///
-/// Sets whether sessions are built when new devices are received from the
-/// server.
-///
-/// This can be used to not call \c QXmppOmemoManager::buildMissingSessions
-/// manually.
-/// But it should not be used before the initial setup and storing lots of
-/// devices locally.
-/// Otherwise, it could lead to a massive computation and network load when
-/// there are many devices for whom sessions are built.
-///
-/// \see QXmppOmemoManager::buildMissingSessions
-///
-/// \param isNewDeviceAutoSessionBuildingEnabled whether sessions are built for
-/// incoming devices
-///
-void Manager::setNewDeviceAutoSessionBuildingEnabled(bool isNewDeviceAutoSessionBuildingEnabled)
-{
- d->isNewDeviceAutoSessionBuildingEnabled = isNewDeviceAutoSessionBuildingEnabled;
-}
-
-///
-/// Returns whether sessions are built when new devices are received from the
-/// server.
-///
-/// \see QXmppOmemoManager::setNewDeviceAutoSessionBuildingEnabled
-///
-/// \return whether sessions are built for incoming devices
-///
-bool Manager::isNewDeviceAutoSessionBuildingEnabled()
-{
- return d->isNewDeviceAutoSessionBuildingEnabled;
-}
-
-///
-/// Builds sessions manually with devices for whom no sessions are available.
-///
-/// Usually, sessions are built during sending a first message to a device or
-/// after a first message is received from a device.
-/// This can be called in order to speed up the sending of a message.
-/// If this method is called before sending the first message, all sessions can
-/// be built and when the first message is sent, the message has only be
-/// encrypted.
-/// Especially chats with multiple devices, that can decrease the noticeable
-/// time a user has to wait for sending a message.
-/// Additionally, the keys are automatically retrieved from the server which is
-/// helpful in order to get them when calling \c QXmppOmemoManager::devices().
-///
-/// The user must be logged in while calling this.
-///
-/// \param jids JIDs of the device owners for whom the sessions are built
-///
-QFuture<void> Manager::buildMissingSessions(const QList<QString> &jids)
-{
- QFutureInterface<void> interface(QFutureInterfaceBase::Started);
-
- auto &devices = d->devices;
- auto devicesCount = 0;
-
- for (const auto &jid : jids) {
- // Do not exceed the maximum of manageable devices.
- if (devicesCount > d->maximumDevicesPerStanza - devicesCount) {
- warning("Sessions could not be built for all JIDs because their devices are "
- "altogether more than the maximum of manageable devices " %
- QString::number(d->maximumDevicesPerStanza) %
- u" - Use QXmppOmemoManager::setMaximumDevicesPerStanza() to increase the maximum");
- break;
- } else {
- devicesCount += devices.value(jid).size();
- }
- }
-
- if (devicesCount) {
- auto processedDevicesCount = std::make_shared<int>(0);
-
- for (const auto &jid : jids) {
- auto &processedDevices = devices[jid];
-
- for (auto itr = processedDevices.begin(); itr != processedDevices.end(); ++itr) {
- const auto &deviceId = itr.key();
- auto &device = itr.value();
-
- if (device.session.isEmpty()) {
- auto future = d->buildSessionWithDeviceBundle(jid, deviceId, device);
- await(future, this, [=](auto) mutable {
- if (++(*processedDevicesCount) == devicesCount) {
- interface.reportFinished();
- }
- });
- } else if (++(*processedDevicesCount) == devicesCount) {
- interface.reportFinished();
- }
- }
- }
- } else {
- interface.reportFinished();
- }
-
- return interface.future();
-}
-
-///
-/// Resets all OMEMO data for this device and the trust data used by OMEMO.
-///
-/// ATTENTION: This should only be called when an account is removed locally or
-/// if there are unrecoverable problems with the OMEMO setup of this device.
-///
-/// The data on the server for other own devices is not removed.
-/// Call \c resetAll() for that purpose.
-///
-/// The user must be logged in while calling this.
-///
-/// Call \c setUp() once this method is finished if you want to set up
-/// everything again for this device.
-/// Existing sessions are reset, which might lead to undecryptable incoming
-/// stanzas until everything is set up again.
-///
-QFuture<bool> Manager::resetOwnDevice()
-{
- return d->resetOwnDevice();
-}
-
-///
-/// Resets all OMEMO data for all own devices and the trust data used by OMEMO.
-///
-/// ATTENTION: This should only be called if there is a certain reason for it
-/// since it deletes the data for this device and for other own devices from the
-/// server.
-///
-/// Call \c resetOwnDevice() if you only want to delete the OMEMO data for this
-/// device.
-///
-/// The user must be logged in while calling this.
-///
-/// Call \c setUp() once this method is finished if you want to set up
-/// everything again.
-/// Existing sessions are reset, which might lead to undecryptable incoming
-/// stanzas until everything is set up again.
-///
-QFuture<bool> Manager::resetAll()
-{
- return d->resetAll();
-}
-
-///
-/// \fn QXmppOmemoManager::setSecurityPolicy(QXmpp::TrustSecurityPolicy securityPolicy)
-///
-/// Sets the security policy used by this E2EE extension.
-///
-/// \param securityPolicy security policy being set
-///
-QFuture<void> Manager::setSecurityPolicy(QXmpp::TrustSecurityPolicy securityPolicy)
-{
- return d->trustManager->setSecurityPolicy(ns_omemo_2, securityPolicy);
-}
-
-///
-/// \fn QXmppOmemoManager::securityPolicy()
-///
-/// Returns the security policy used by this E2EE extension.
-///
-/// \return the used security policy
-///
-QFuture<QXmpp::TrustSecurityPolicy> Manager::securityPolicy()
-{
- return d->trustManager->securityPolicy(ns_omemo_2);
-}
-
-///
-/// \fn QXmppOmemoManager::setTrustLevel(const QMultiHash<QString, QByteArray> &keyIds, QXmpp::TrustLevel trustLevel)
-///
-/// Sets the trust level of keys.
-///
-/// If a key is not stored, it is added to the storage.
-///
-/// \param keyIds key owners' bare JIDs mapped to the IDs of their keys
-/// \param trustLevel trust level being set
-///
-QFuture<void> Manager::setTrustLevel(const QMultiHash<QString, QByteArray> &keyIds, QXmpp::TrustLevel trustLevel)
-{
- return d->trustManager->setTrustLevel(ns_omemo_2, keyIds, trustLevel);
-}
-
-///
-/// \fn QXmppOmemoManager::trustLevel(const QString &keyOwnerJid, const QByteArray &keyId)
-///
-/// Returns the trust level of a key.
-///
-/// If the key is not stored, the trust in that key is undecided.
-///
-/// \param keyOwnerJid key owner's bare JID
-/// \param keyId ID of the key
-///
-/// \return the key's trust level
-///
-QFuture<QXmpp::TrustLevel> Manager::trustLevel(const QString &keyOwnerJid, const QByteArray &keyId)
-{
- return d->trustManager->trustLevel(ns_omemo_2, keyOwnerJid, keyId);
-}
-
-/// \cond
-QFuture<QXmppE2eeExtension::MessageEncryptResult> Manager::encryptMessage(QXmppMessage &&message, const std::optional<QXmppSendStanzaParams> &params)
-{
- QVector<QString> recipientJids;
- std::optional<TrustLevels> acceptedTrustLevels;
-
- if (params) {
- recipientJids = params->encryptionJids();
- acceptedTrustLevels = params->acceptedTrustLevels();
- }
-
- if (recipientJids.isEmpty()) {
- recipientJids.append(QXmppUtils::jidToBareJid(message.to()));
- }
-
- if (!acceptedTrustLevels) {
- acceptedTrustLevels = ACCEPTED_TRUST_LEVELS;
- }
-
- return d->encryptMessageForRecipients(std::move(message), recipientJids, *acceptedTrustLevels);
-}
-
-QFuture<QXmppE2eeExtension::IqEncryptResult> Manager::encryptIq(QXmppIq &&iq, const std::optional<QXmppSendStanzaParams> &params)
-{
- QFutureInterface<QXmppE2eeExtension::IqEncryptResult> interface(QFutureInterfaceBase::Started);
-
- if (!d->isStarted) {
- QXmpp::SendError error;
- error.text = QStringLiteral("OMEMO manager must be started before encrypting");
- error.type = QXmpp::SendError::EncryptionError;
- reportFinishedResult(interface, { error });
- } else {
- std::optional<TrustLevels> acceptedTrustLevels;
-
- if (params) {
- acceptedTrustLevels = params->acceptedTrustLevels();
- }
-
- if (!acceptedTrustLevels) {
- acceptedTrustLevels = ACCEPTED_TRUST_LEVELS;
- }
-
- auto future = d->encryptStanza(iq, { QXmppUtils::jidToBareJid(iq.to()) }, *acceptedTrustLevels);
- await(future, this, [=, iq = std::move(iq)](std::optional<QXmppOmemoElement> omemoElement) mutable {
- if (!omemoElement) {
- QXmpp::SendError error;
- error.text = QStringLiteral("OMEMO element could not be created");
- error.type = QXmpp::SendError::EncryptionError;
- reportFinishedResult(interface, { error });
- } else {
- QXmppOmemoIq omemoIq;
- omemoIq.setId(iq.id());
- omemoIq.setType(iq.type());
- omemoIq.setLang(iq.lang());
- omemoIq.setFrom(iq.from());
- omemoIq.setTo(iq.to());
- omemoIq.setOmemoElement(*omemoElement);
-
- QByteArray serializedEncryptedIq;
- QXmlStreamWriter writer(&serializedEncryptedIq);
- omemoIq.toXml(&writer);
-
- reportFinishedResult(interface, { serializedEncryptedIq });
- }
- });
- }
-
- return interface.future();
-}
-
-QFuture<QXmppE2eeExtension::IqDecryptResult> Manager::decryptIq(const QDomElement &element)
-{
- if (!d->isStarted) {
- // TODO: Add decryption queue to avoid this error
- return makeReadyFuture<IqDecryptResult>(SendError {
- QStringLiteral("OMEMO manager must be started before decrypting"),
- SendError::EncryptionError });
- }
-
- if (QXmppOmemoIq::isOmemoIq(element)) {
- // Tag name and iq type are already checked in QXmppClient.
- return chain<IqDecryptResult>(d->decryptIq(element), this, [](auto result) -> IqDecryptResult {
- if (result) {
- return result->iq;
- }
- return SendError {
- QStringLiteral("OMEMO message could not be decrypted"),
- SendError::EncryptionError
- };
- });
- }
-
- return makeReadyFuture<IqDecryptResult>(NotEncrypted());
-}
-
-QStringList Manager::discoveryFeatures() const
-{
- return {
- QString(ns_omemo_2_devices) % "+notify"
- };
-}
-
-bool Manager::handleStanza(const QDomElement &stanza)
-{
- if (stanza.tagName() != "iq" || !QXmppOmemoIq::isOmemoIq(stanza)) {
- return false;
- }
-
- // TODO: Queue incoming IQs until OMEMO is initialized
- if (!d->isStarted) {
- warning("Couldn't decrypt incoming IQ because the manager isn't initialized yet.");
- return false;
- }
-
- auto type = stanza.attribute("type");
- if (type != "get" && type != "set") {
- // ignore incoming result and error IQs (they are handled via Client::sendIq())
- return false;
- }
-
- await(d->decryptIq(stanza), this, [=](auto result) {
- if (result) {
- injectIq(result->iq, result->e2eeMetadata);
- } else {
- warning("Could not decrypt incoming OMEMO IQ.");
- }
- });
- return true;
-}
-
-bool Manager::handleMessage(const QXmppMessage &message)
-{
- if (d->isStarted && message.omemoElement()) {
- auto future = d->decryptMessage(message);
- await(future, this, [=](std::optional<QXmppMessage> optionalDecryptedMessage) mutable {
- if (optionalDecryptedMessage) {
- injectMessage(std::move(*optionalDecryptedMessage));
- }
- });
-
- return true;
- }
-
- return false;
-}
-/// \endcond
-
-///
-/// \fn QXmppOmemoManager::trustLevelsChanged(const QMultiHash<QString, QByteArray> &modifiedKeys)
-///
-/// Emitted when the trust levels of keys changed.
-///
-/// \param modifiedKeys key owners' bare JIDs mapped to their modified keys
-///
-
-///
-/// \fn QXmppOmemoManager::deviceAdded(const QString &jid, uint32_t deviceId)
-///
-/// Emitted when a device is added.
-///
-/// \param jid device owner's bare JID
-/// \param deviceId ID of the device
-///
-
-///
-/// \fn QXmppOmemoManager::deviceChanged(const QString &jid, uint32_t deviceId)
-///
-/// Emitted when a device changed.
-///
-/// \param jid device owner's bare JID
-/// \param deviceId ID of the device
-///
-
-///
-/// \fn QXmppOmemoManager::deviceRemoved(const QString &jid, uint32_t deviceId)
-///
-/// Emitted when a device is removed.
-///
-/// \param jid device owner's bare JID
-/// \param deviceId ID of the device
-///
-
-///
-/// \fn QXmppOmemoManager::devicesRemoved(const QString &jid)
-///
-/// Emitted when all devices of an owner are removed.
-///
-/// \param jid device owner's bare JID
-///
-
-///
-/// \fn QXmppOmemoManager::allDevicesRemoved()
-///
-/// Emitted when all devices are removed.
-///
-
-/// \cond
-void Manager::setClient(QXmppClient *client)
-{
- QXmppClientExtension::setClient(client);
- client->setEncryptionExtension(this);
-
- d->trustManager = client->findExtension<QXmppTrustManager>();
- if (!d->trustManager) {
- qFatal("QXmppTrustManager is not available, it must be added to the client before adding QXmppOmemoManager");
- }
-
- d->pubSubManager = client->findExtension<QXmppPubSubManager>();
- if (!d->pubSubManager) {
- qFatal("QXmppPubSubManager is not available, it must be added to the client before adding QXmppOmemoManager");
- }
-
- connect(d->trustManager, &QXmppTrustManager::trustLevelsChanged, this, [=](const QHash<QString, QMultiHash<QString, QByteArray>> &modifiedKeys) {
- const auto &modifiedOmemoKeys = modifiedKeys.value(ns_omemo_2);
- emit trustLevelsChanged(modifiedOmemoKeys);
-
- for (auto itr = modifiedOmemoKeys.cbegin(); itr != modifiedOmemoKeys.cend(); ++itr) {
- const auto &keyOwnerJid = itr.key();
- const auto &keyId = itr.value();
-
- // Emit 'deviceChanged()' only if there is a device with the key.
- const auto &devices = d->devices.value(keyOwnerJid);
- for (auto itr = devices.cbegin(); itr != devices.cend(); ++itr) {
- if (itr->keyId == keyId) {
- emit deviceChanged(keyOwnerJid, itr.key());
- return;
- }
- }
- }
- });
-}
-
-bool Manager::handlePubSubEvent(const QDomElement &element, const QString &pubSubService, const QString &nodeName)
-{
- if (nodeName == ns_omemo_2_devices && QXmppPubSubEvent<QXmppOmemoDeviceListItem>::isPubSubEvent(element)) {
- QXmppPubSubEvent<QXmppOmemoDeviceListItem> event;
- event.parse(element);
-
- switch (event.eventType()) {
- // Items are published or deleted.
- case QXmppPubSubEventBase::Items: {
- // If there are IDs of deleted items, check for an inconsistency.
- // Otherwise, check for published items.
- if (const auto retractIds = event.retractIds(); !retractIds.isEmpty()) {
- // Specific items are deleted.
- const auto &retractedItem = event.retractIds().constFirst();
- if (retractedItem == QXmppPubSubManager::standardItemIdToString(QXmppPubSubManager::Current)) {
- d->handleIrregularDeviceListChanges(pubSubService);
- }
- } else {
- const auto items = event.items();
-
- // Only process items if the event notification contains one.
- // That is necessary because PubSub allows publishing without
- // items leading to notification-only events.
- if (!items.isEmpty()) {
- const auto &deviceListItem = items.constFirst();
- if (deviceListItem.id() == QXmppPubSubManager::standardItemIdToString(QXmppPubSubManager::Current)) {
- d->updateDevices(pubSubService, event.items().constFirst());
- } else {
- d->handleIrregularDeviceListChanges(pubSubService);
- }
- }
- }
-
- break;
- }
-
- // All items are deleted.
- case QXmppPubSubEventBase::Purge:
- // The whole node is deleted.
- case QXmppPubSubEventBase::Delete:
- d->handleIrregularDeviceListChanges(pubSubService);
- break;
- case QXmppPubSubEventBase::Configuration:
- case QXmppPubSubEventBase::Subscription:
- break;
- }
-
- return true;
- }
-
- return false;
-}
-/// \endcond