diff options
| author | Linus Jahn <lnj@kaidan.im> | 2021-08-22 22:00:46 +0200 |
|---|---|---|
| committer | Linus Jahn <lnj@kaidan.im> | 2021-09-28 17:08:08 +0200 |
| commit | 5849459af181d686c6e0b8ccca3a685e44a81582 (patch) | |
| tree | 821308f9c162ec9127ddf51d1406ab8e0283d136 /src | |
| parent | 48d3eb28ab8f115ce999c2264303925f9c7ce2a7 (diff) | |
| download | qxmpp-5849459af181d686c6e0b8ccca3a685e44a81582.tar.gz | |
QXmppClient: Add encryption hooks
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/client/QXmppClient.cpp | 160 | ||||
| -rw-r--r-- | src/client/QXmppClient.h | 5 | ||||
| -rw-r--r-- | src/client/QXmppClient_p.h | 3 | ||||
| -rw-r--r-- | src/client/QXmppE2eeExtension.cpp | 93 | ||||
| -rw-r--r-- | src/client/QXmppE2eeExtension.h | 50 |
6 files changed, 310 insertions, 3 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 86849223..1f7b869a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -93,6 +93,7 @@ set(INSTALL_HEADER_FILES client/QXmppClientExtension.h client/QXmppConfiguration.h client/QXmppDiscoveryManager.h + client/QXmppE2eeExtension.h client/QXmppEntityTimeManager.h client/QXmppInvokable.h client/QXmppMamManager.h @@ -198,6 +199,7 @@ set(SOURCE_FILES client/QXmppClientExtension.cpp client/QXmppConfiguration.cpp client/QXmppDiscoveryManager.cpp + client/QXmppE2eeExtension.cpp client/QXmppEntityTimeManager.cpp client/QXmppInternalClientExtension.cpp client/QXmppInvokable.cpp 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 |
