aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorLinus Jahn <lnj@kaidan.im>2021-08-22 22:00:46 +0200
committerLinus Jahn <lnj@kaidan.im>2021-09-28 17:08:08 +0200
commit5849459af181d686c6e0b8ccca3a685e44a81582 (patch)
tree821308f9c162ec9127ddf51d1406ab8e0283d136 /src/client
parent48d3eb28ab8f115ce999c2264303925f9c7ce2a7 (diff)
downloadqxmpp-5849459af181d686c6e0b8ccca3a685e44a81582.tar.gz
QXmppClient: Add encryption hooks
Diffstat (limited to 'src/client')
-rw-r--r--src/client/QXmppClient.cpp160
-rw-r--r--src/client/QXmppClient.h5
-rw-r--r--src/client/QXmppClient_p.h3
-rw-r--r--src/client/QXmppE2eeExtension.cpp93
-rw-r--r--src/client/QXmppE2eeExtension.h50
5 files changed, 308 insertions, 3 deletions
diff --git a/src/client/QXmppClient.cpp b/src/client/QXmppClient.cpp
index 7034fbb7..c872c45f 100644
--- a/src/client/QXmppClient.cpp
+++ b/src/client/QXmppClient.cpp
@@ -28,11 +28,13 @@
#include "QXmppConstants_p.h"
#include "QXmppDiscoveryIq.h"
#include "QXmppDiscoveryManager.h"
+#include "QXmppE2eeExtension.h"
#include "QXmppEntityTimeManager.h"
#include "QXmppFutureUtils_p.h"
#include "QXmppLogger.h"
#include "QXmppMessage.h"
#include "QXmppOutgoingClient.h"
+#include "QXmppPacket_p.h"
#include "QXmppRosterManager.h"
#include "QXmppTlsManager_p.h"
#include "QXmppUtils.h"
@@ -44,9 +46,19 @@
#include <QSslSocket>
#include <QTimer>
+using namespace QXmpp::Private;
+
/// \cond
QXmppClientPrivate::QXmppClientPrivate(QXmppClient* qq)
- : clientPresence(QXmppPresence::Available), logger(nullptr), stream(nullptr), receivedConflict(false), reconnectionTries(0), reconnectionTimer(nullptr), isActive(true), q(qq)
+ : clientPresence(QXmppPresence::Available),
+ logger(nullptr),
+ stream(nullptr),
+ encryptionExtension(nullptr),
+ receivedConflict(false),
+ reconnectionTries(0),
+ reconnectionTimer(nullptr),
+ isActive(true),
+ q(qq)
{
}
@@ -249,6 +261,26 @@ bool QXmppClient::removeExtension(QXmppClientExtension* extension)
}
}
+///
+/// Returns the currently used encryption extension.
+///
+/// \since QXmpp 1.5
+///
+QXmppE2eeExtension *QXmppClient::encryptionExtension() const
+{
+ return d->encryptionExtension;
+}
+
+///
+/// Sets the extension to be used for end-to-end-encryption.
+///
+/// \since QXmpp 1.5
+///
+void QXmppClient::setEncryptionExtension(QXmppE2eeExtension *extension)
+{
+ d->encryptionExtension = extension;
+}
+
/// Returns a list containing all the client's extensions.
///
@@ -302,12 +334,15 @@ void QXmppClient::connectToServer(const QString& jid, const QString& password)
connectToServer(config);
}
+///
/// After successfully connecting to the server use this function to send
/// stanzas to the server. This function can solely be used to send various kind
/// of stanzas to the server. QXmppStanza is a parent class of all the stanzas
/// QXmppMessage, QXmppPresence, QXmppIq, QXmppBind, QXmppRosterIq, QXmppSession
/// and QXmppVCard.
///
+/// This function does not end-to-end encrypt the packets.
+///
/// \return Returns true if the packet was sent, false otherwise.
///
/// Following code snippet illustrates how to send a message using this function:
@@ -339,9 +374,52 @@ bool QXmppClient::sendPacket(const QXmppNonza &packet)
/// You can use QFutureWatcher in Qt 5 and QFuture::then() in Qt 6 to handle the
/// results.
///
+/// \since QXmpp 1.5
+///
QFuture<QXmpp::SendResult> QXmppClient::send(QXmppStanza &&stanza)
{
- return d->stream->send(std::move(stanza));
+ const auto sendEncrypted = [this](QFuture<std::variant<QByteArray, QXmpp::SendError>> &&future) {
+ auto interface = std::make_shared<QFutureInterface<QXmpp::SendResult>>(QFutureInterfaceBase::Started);
+
+ await(future, this, [this, interface](std::variant<QByteArray, QXmpp::SendError> result) {
+ if (const auto *xml = std::get_if<QByteArray>(&result)) {
+ d->stream->send(QXmppPacket(*xml, true, interface));
+ } else {
+ interface->reportResult(std::get<QXmpp::SendError>(result));
+ interface->reportFinished();
+ }
+ });
+
+ return interface->future();
+ };
+
+ if (d->encryptionExtension) {
+ if (dynamic_cast<QXmppMessage *>(&stanza)) {
+ return sendEncrypted(d->encryptionExtension->encryptMessage(std::move(dynamic_cast<QXmppMessage &&>(stanza))));
+ } else if (dynamic_cast<QXmppIq *>(&stanza)) {
+ return sendEncrypted(d->encryptionExtension->encryptIq(std::move(dynamic_cast<QXmppIq &&>(stanza))));
+ }
+ }
+ return d->stream->send(stanza);
+}
+
+///
+/// Sends a packet always without end-to-end-encryption.
+///
+/// This does the same as send(), but does not do any end-to-end encryption on
+/// the stanza.
+///
+/// \warning THIS API IS NOT FINALIZED YET!
+///
+/// \returns A QFuture that makes it possible to track the state of the packet.
+/// You can use QFutureWatcher in Qt 5 and QFuture::then() in Qt 6 to handle the
+/// results.
+///
+/// \since QXmpp 1.5
+///
+QFuture<QXmpp::SendResult> QXmppClient::sendUnencrypted(QXmppStanza &&stanza)
+{
+ return d->stream->send(stanza);
}
///
@@ -351,6 +429,10 @@ QFuture<QXmpp::SendResult> QXmppClient::send(QXmppStanza &&stanza)
/// QDomElement. If you don't expect a special response, you may want use
/// sendGenericIq().
///
+/// This does not do any end-to-encryption on the IQ.
+///
+/// \sa sendSensitiveIq()
+///
/// \warning THIS API IS NOT FINALIZED YET!
///
/// \since QXmpp 1.5
@@ -361,6 +443,79 @@ QFuture<QXmppClient::IqResult> QXmppClient::sendIq(QXmppIq &&iq)
}
///
+/// Tries to encrypt and send an IQ packet and returns the response
+/// asynchronously.
+///
+/// This can be used for sensitive IQ requests performed from client to client.
+/// Most IQ requests like service discovery requests cannot be end-to-end
+/// encrypted or it only makes little sense to do so. This is why the default
+/// sendIq() does not do any additional end-to-end encryption.
+///
+/// \warning THIS API IS NOT FINALIZED YET!
+///
+/// \since QXmpp 1.5
+///
+QFuture<QXmppClient::IqResult> QXmppClient::sendSensitiveIq(QXmppIq &&iq)
+{
+ const auto sendEncrypted = [this](QFuture<QXmppE2eeExtension::IqEncryptResult> &&future, const QString &id) {
+ auto interface = std::make_shared<QFutureInterface<IqResult>>(QFutureInterfaceBase::Started);
+
+ await(future, this, [this, interface, id](QXmppE2eeExtension::IqEncryptResult result) {
+ if (const auto *xml = std::get_if<QByteArray>(&result)) {
+ // encrypted successfully
+ auto future = d->stream->sendIq(QXmppPacket(*xml, true, std::make_shared<QFutureInterface<QXmpp::SendResult>>()), id);
+ await(future, this, [this, interface](QXmppStream::IqResult result) {
+ if (const auto encryptedDom = std::get_if<QDomElement>(&result)) {
+ // received result (should be encrypted)
+ if (d->encryptionExtension) {
+ // decrypt
+ auto future = d->encryptionExtension->decryptIq(*encryptedDom);
+ await(future, this, [interface, encryptedDom = *encryptedDom](QXmppE2eeExtension::IqDecryptResult result) {
+ if (const auto dom = std::get_if<QDomElement>(&result)) {
+ // decrypted result
+ interface->reportResult(*dom);
+ } else if (std::holds_alternative<QXmppE2eeExtension::NotEncrypted>(result)) {
+ // the IQ response from the other entity was not encrypted
+ // then report IQ response without modifications
+ interface->reportResult(encryptedDom);
+ } else if (const auto error = std::get_if<QXmpp::SendError>(&result)) {
+ interface->reportResult(*error);
+ }
+ interface->reportFinished();
+ });
+ } else {
+ interface->reportResult(QXmpp::SendError {
+ QStringLiteral("No decryption extension found."),
+ QXmpp::SendError::EncryptionError
+ });
+ interface->reportFinished();
+ }
+ } else {
+ interface->reportResult(std::get<QXmpp::SendError>(result));
+ interface->reportFinished();
+ }
+ });
+ } else {
+ interface->reportResult(std::get<QXmpp::SendError>(result));
+ interface->reportFinished();
+ }
+ });
+
+ return interface->future();
+ };
+
+ if (iq.id().isEmpty() || d->stream->hasIqId(iq.id())) {
+ iq.setId(QXmppUtils::generateStanzaUuid());
+ }
+
+ if (d->encryptionExtension) {
+ const auto id = iq.id();
+ return sendEncrypted(d->encryptionExtension->encryptIq(std::move(iq)), id);
+ }
+ return d->stream->sendIq(std::move(iq));
+}
+
+///
/// Sends an IQ and returns possible stanza errors.
///
/// If you want to parse a special IQ response in the result case, you can use
@@ -375,7 +530,6 @@ QFuture<QXmppClient::IqResult> QXmppClient::sendIq(QXmppIq &&iq)
///
QFuture<QXmppClient::EmptyResult> QXmppClient::sendGenericIq(QXmppIq &&iq)
{
- using namespace QXmpp::Private;
return chainIq(sendIq(std::move(iq)), this, [](const QXmppIq &) -> EmptyResult {
return QXmpp::Success();
});
diff --git a/src/client/QXmppClient.h b/src/client/QXmppClient.h
index 6f98acec..e4ff0d5c 100644
--- a/src/client/QXmppClient.h
+++ b/src/client/QXmppClient.h
@@ -38,6 +38,7 @@
template<typename T>
class QFuture;
+class QXmppE2eeExtension;
class QXmppClientExtension;
class QXmppClientPrivate;
class QXmppPresence;
@@ -151,6 +152,8 @@ public:
}
bool insertExtension(int index, QXmppClientExtension *extension);
bool removeExtension(QXmppClientExtension *extension);
+ QXmppE2eeExtension *encryptionExtension() const;
+ void setEncryptionExtension(QXmppE2eeExtension *);
QList<QXmppClientExtension *> extensions();
@@ -232,7 +235,9 @@ public:
QXmppStanza::Error::Condition xmppStreamError();
QFuture<QXmpp::SendResult> send(QXmppStanza &&);
+ QFuture<QXmpp::SendResult> sendUnencrypted(QXmppStanza &&);
QFuture<IqResult> sendIq(QXmppIq &&);
+ QFuture<IqResult> sendSensitiveIq(QXmppIq &&);
QFuture<EmptyResult> sendGenericIq(QXmppIq &&);
#if QXMPP_DEPRECATED_SINCE(1, 1)
diff --git a/src/client/QXmppClient_p.h b/src/client/QXmppClient_p.h
index 9ef105b5..a62082b3 100644
--- a/src/client/QXmppClient_p.h
+++ b/src/client/QXmppClient_p.h
@@ -41,6 +41,7 @@
class QXmppClient;
class QXmppClientExtension;
+class QXmppE2eeExtension;
class QXmppLogger;
class QXmppOutgoingClient;
class QTimer;
@@ -57,6 +58,8 @@ public:
/// Pointer to the XMPP stream
QXmppOutgoingClient *stream;
+ QXmppE2eeExtension *encryptionExtension;
+
// reconnection
bool receivedConflict;
int reconnectionTries;
diff --git a/src/client/QXmppE2eeExtension.cpp b/src/client/QXmppE2eeExtension.cpp
new file mode 100644
index 00000000..d4e9f9ca
--- /dev/null
+++ b/src/client/QXmppE2eeExtension.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2008-2021 The QXmpp developers
+ *
+ * Authors:
+ * Linus Jahn
+ *
+ * Source:
+ * https://github.com/qxmpp-project/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include "QXmppE2eeExtension.h"
+
+///
+/// \class QXmppE2eeExtension
+///
+/// Abstract client extension for end-to-end-encryption protocols.
+///
+/// \warning THIS API IS NOT FINALIZED YET!
+///
+/// \since QXmpp 1.5
+///
+
+///
+/// \struct QXmppE2eeExtension::NotEncrypted
+///
+/// Indicates that the input was not encrypted and so nothing could be decrypted.
+///
+/// \since QXmpp 1.5
+///
+
+///
+/// \typedef QXmppE2eeExtension::EncryptMessageResult
+///
+/// Contains the XML serialized message stanza with encrypted contents or a
+/// QXmpp::SendError in case the message couldn't be encrypted.
+///
+
+///
+/// \typedef QXmppE2eeExtension::IqEncryptResult
+///
+/// Contains the XML serialized IQ stanza with encrypted contents or a
+/// QXmpp::SendError in case the IQ couldn't be encrypted.
+///
+
+///
+/// \typedef QXmppE2eeExtension::IqDecryptResult
+///
+/// Contains a deserialized IQ stanza in form of a DOM element with decrypted
+/// contents or a QXmpp::SendError in case the IQ couldn't be decrypted.
+///
+
+///
+/// \fn QXmppE2eeExtension::encryptMessage
+///
+/// Encrypts a QXmppMessage and returns the serialized XML stanza with encrypted
+/// contents via QFuture.
+///
+/// If the message cannot be encrypted for whatever reason you can either
+/// serialize the message unencrypted and return that or return a SendError with
+/// an error message.
+///
+
+///
+/// \fn QXmppE2eeExtension::encryptIq
+///
+/// Encrypts a QXmppIq and returns the serialized XML stanza with encrypted
+/// contents via QFuture.
+///
+/// If the IQ cannot be encrypted for whatever reason you can either serialize
+/// the IQ unencrypted and return that or return a SendError with an error
+/// message.
+///
+
+///
+/// \fn QXmppE2eeExtension::decryptIq
+///
+/// Decrypts an IQ from a DOM element and returns a fully decrypted IQ as a DOM
+/// element via QFuture. If the input was not encrypted,
+/// QXmppE2eeExtension::NotEncrypted should be returned.
+///
diff --git a/src/client/QXmppE2eeExtension.h b/src/client/QXmppE2eeExtension.h
new file mode 100644
index 00000000..763a9b28
--- /dev/null
+++ b/src/client/QXmppE2eeExtension.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008-2021 The QXmpp developers
+ *
+ * Authors:
+ * Linus Jahn
+ *
+ * Source:
+ * https://github.com/qxmpp-project/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ */
+
+#ifndef QXMPPE2EEEXTENSION_H
+#define QXMPPE2EEEXTENSION_H
+
+#include "QXmppSendResult.h"
+
+class QDomElement;
+class QXmppMessage;
+class QXmppIq;
+template<typename T>
+class QFuture;
+
+class QXmppE2eeExtension
+{
+public:
+ struct NotEncrypted {};
+
+ using EncryptMessageResult = std::variant<QByteArray, QXmpp::SendError>;
+ using IqEncryptResult = std::variant<QByteArray, QXmpp::SendError>;
+ using IqDecryptResult = std::variant<QDomElement, NotEncrypted, QXmpp::SendError>;
+
+ virtual QFuture<EncryptMessageResult> encryptMessage(QXmppMessage &&) = 0;
+
+ virtual QFuture<IqEncryptResult> encryptIq(QXmppIq &&) = 0;
+ virtual QFuture<IqDecryptResult> decryptIq(const QDomElement &) = 0;
+};
+
+#endif // QXMPPE2EEEXTENSION_H