feat(OMEMO): QXmppClientExtension for OMEMO

This commit is contained in:
vae 2021-05-12 17:33:34 +03:00
parent b1a8f162ce
commit 006752b31c
Signed by: vae
GPG Key ID: A9A33351400E00E5
9 changed files with 167 additions and 98 deletions

View File

@ -47,7 +47,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
network(p_net), network(p_net),
passwordType(Shared::AccountPassword::plain), passwordType(Shared::AccountPassword::plain),
mh(new MessageHandler(this)), mh(new MessageHandler(this)),
rh(new RosterHandler(this)) rh(new RosterHandler(this)),
omemo(new QXmpp::Omemo::Manager())
{ {
config.setUser(p_login); config.setUser(p_login);
config.setDomain(p_server); config.setDomain(p_server);
@ -90,7 +91,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
client.addExtension(rcpm); client.addExtension(rcpm);
QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived); QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
client.addExtension(omemo.get());
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + name; path += "/" + name;

View File

@ -30,23 +30,24 @@
#include <map> #include <map>
#include <set> #include <set>
#include <QXmppRosterManager.h>
#include <QXmppCarbonManager.h>
#include <QXmppDiscoveryManager.h>
#include <QXmppMamManager.h>
#include <QXmppMucManager.h>
#include <QXmppClient.h>
#include <QXmppBookmarkManager.h> #include <QXmppBookmarkManager.h>
#include <QXmppBookmarkSet.h> #include <QXmppBookmarkSet.h>
#include <QXmppCarbonManager.h>
#include <QXmppClient.h>
#include <QXmppDiscoveryManager.h>
#include <QXmppMamManager.h>
#include <QXmppMessageReceiptManager.h>
#include <QXmppMucManager.h>
#include <QXmppRosterManager.h>
#include <QXmppUploadRequestManager.h> #include <QXmppUploadRequestManager.h>
#include <QXmppVCardIq.h> #include <QXmppVCardIq.h>
#include <QXmppVCardManager.h> #include <QXmppVCardManager.h>
#include <QXmppMessageReceiptManager.h> #include <qomemo/qxmpp_omemo_manager.h>
#include "shared/shared.h"
#include "contact.h"
#include "conference.h" #include "conference.h"
#include "contact.h"
#include "networkaccess.h" #include "networkaccess.h"
#include "shared/shared.h"
#include "handlers/messagehandler.h" #include "handlers/messagehandler.h"
#include "handlers/rosterhandler.h" #include "handlers/rosterhandler.h"
@ -165,6 +166,8 @@ private:
MessageHandler* mh; MessageHandler* mh;
RosterHandler* rh; RosterHandler* rh;
QScopedPointer<QXmpp::Omemo::Manager> omemo;
private slots: private slots:
void onClientStateChange(QXmppClient::State state); void onClientStateChange(QXmppClient::State state);

View File

@ -11,4 +11,6 @@ target_sources(squawk PRIVATE
sce.h sce.h
user_device_list.cpp user_device_list.cpp
user_device_list.h user_device_list.h
qxmpp_omemo_manager.cpp
qxmpp_omemo_manager.h
) )

View File

@ -4,27 +4,12 @@
#include "device_service.h" #include "device_service.h"
#include <QXmppClient.h> QXmpp::Omemo::DeviceService::DeviceService(QObject *parent) : QObject(parent) {}
#include <QXmppPubSubIq.h>
#include <QDebug> void QXmpp::Omemo::DeviceService::onDeviceListReceived(
const QString &jid, const QXmpp::Omemo::DeviceList &list) {
QXmpp::Omemo::DeviceService::DeviceService(QXmppClient &client, QObject *parent) for (const auto &device : list.devices) {
: QObject(parent), client(client) { qInfo() << "Got device for" << jid << ":" << device.id;
connect(&client, &QXmppClient::iqReceived, this, }
&DeviceService::onIqReceived);
}
void QXmpp::Omemo::DeviceService::onIqReceived(const QXmppIq &iq) {
// Update OMEMO device list
}
void QXmpp::Omemo::DeviceService::fetch() {
QXmppPubSubIq fetchOwnDevices{};
fetchOwnDevices.setFrom(client.configuration().jid());
fetchOwnDevices.setTo(client.configuration().jidBare());
fetchOwnDevices.setType(QXmppIq::Get);
fetchOwnDevices.setQueryNode("urn:xmpp:omemo:1:devices");
client.sendPacket(fetchOwnDevices);
} }

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include "qomemo.h"
#include "user_device_list.h" #include "user_device_list.h"
#include <QXmppClient.h> #include <QXmppClient.h>
@ -14,17 +15,12 @@ class DeviceService : public QObject {
Q_OBJECT Q_OBJECT
public: public:
DeviceService(QXmppClient& client, QObject *parent); explicit DeviceService(QObject *parent);
void fetch();
public slots: public slots:
void onIqReceived(const QXmppIq& iq); void onDeviceListReceived(const QString& jid, const DeviceList& list);
private: private:
void announce();
QXmppClient& client;
QMap<QString, UserDeviceList> device_lists{}; QMap<QString, UserDeviceList> device_lists{};
}; };

View File

@ -13,7 +13,7 @@
using namespace QXmpp::Factories; using namespace QXmpp::Factories;
QXmppElement QXmpp::Omemo::DeviceList::toXml() const { QXmppElement QXmpp::Omemo::DeviceList::toXml() const {
auto element = createElement("devices", "urn:xmpp:omemo:1"); auto element = createElement("list", "eu.siacs.conversations.axolotl");
for (const auto &d : devices) { for (const auto &d : devices) {
element.appendChild(d.toXml()); element.appendChild(d.toXml());
@ -28,11 +28,10 @@ QXmppIq QXmpp::Omemo::DeviceList::toIq() const {
iq.setType(QXmppIq::Set); iq.setType(QXmppIq::Set);
auto item = createElement("item"); auto item = createElement("item");
item.setAttribute("id", "current");
item.appendChild(toXml()); item.appendChild(toXml());
auto publish = createElement("publish"); auto publish = createElement("publish");
publish.setAttribute("node", "urn:xmpp:omemo:1:devices"); publish.setAttribute("node", "eu.siacs.conversations.axolotl.devicelist");
publish.appendChild(item); publish.appendChild(item);
auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub"); auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
@ -45,7 +44,7 @@ QXmppIq QXmpp::Omemo::DeviceList::toIq() const {
} }
void QXmpp::Omemo::DeviceList::fromXml(const QXmppElement &element) { void QXmpp::Omemo::DeviceList::fromXml(const QXmppElement &element) {
if (!elementMatches(element, "devices", "urn:xmpp:omemo:1")) if (!elementMatches(element, "list", "eu.siacs.conversations.axolotl"))
return; return;
devices.clear(); devices.clear();
@ -64,10 +63,6 @@ QXmppElement QXmpp::Omemo::Device::toXml() const {
auto result = createElement("device"); auto result = createElement("device");
result.setAttribute("id", QString::number(id)); result.setAttribute("id", QString::number(id));
if (!label.isEmpty()) {
result.setAttribute("label", label);
}
return result; return result;
} }
@ -76,12 +71,11 @@ void QXmpp::Omemo::Device::fromXml(const QXmppElement &element) {
return; return;
id = element.attribute("id").toInt(); id = element.attribute("id").toInt();
label = element.attribute("label");
} }
QXmppElement QXmpp::Omemo::PreKey::toXml() const { QXmppElement QXmpp::Omemo::PreKey::toXml() const {
auto pk = createElement("pk"); auto pk = createElement("preKeyPublic");
pk.setAttribute("id", QString::number(id)); pk.setAttribute("preKeyId", QString::number(id));
// TODO: Base64 // TODO: Base64
pk.setValue(data); pk.setValue(data);
@ -89,23 +83,23 @@ QXmppElement QXmpp::Omemo::PreKey::toXml() const {
} }
void QXmpp::Omemo::PreKey::fromXml(const QXmppElement &element) { void QXmpp::Omemo::PreKey::fromXml(const QXmppElement &element) {
if (!elementMatches(element, "pk")) if (!elementMatches(element, "preKeyPublic"))
return; return;
id = element.attribute("id").toInt(); id = element.attribute("preKeyId").toInt();
// TODO: Base64 // TODO: Base64
data = element.value(); data = element.value();
} }
QXmppElement QXmpp::Omemo::Bundle::toXml() const { QXmppElement QXmpp::Omemo::Bundle::toXml() const {
auto spkNode = createElement("spk"); auto spkNode = createElement("signedPreKeyPublic");
spkNode.setAttribute("id", QString::number(spkId)); spkNode.setAttribute("signedPreKeyId", QString::number(spkId));
spkNode.setValue(spk); spkNode.setValue(spk);
auto spksNode = createElement("spks"); auto spksNode = createElement("signedPreKeySignature");
spksNode.setValue(spks); spksNode.setValue(spks);
auto ikNode = createElement("ik"); auto ikNode = createElement("identityKey");
ikNode.setValue(ik); ikNode.setValue(ik);
auto prekeysNode = createElement("prekeys"); auto prekeysNode = createElement("prekeys");
@ -113,7 +107,7 @@ QXmppElement QXmpp::Omemo::Bundle::toXml() const {
prekeysNode.appendChild(pk.toXml()); prekeysNode.appendChild(pk.toXml());
} }
auto result = createElement("bundle", "urn:xmpp:omemo:1"); auto result = createElement("bundle", "eu.siacs.conversations.axolotl");
result.appendChild(spkNode); result.appendChild(spkNode);
result.appendChild(spksNode); result.appendChild(spksNode);
result.appendChild(ikNode); result.appendChild(ikNode);
@ -122,22 +116,21 @@ QXmppElement QXmpp::Omemo::Bundle::toXml() const {
return result; return result;
} }
QXmppIq QXmpp::Omemo::Bundle::toIq() const { QXmppIq QXmpp::Omemo::Bundle::toIq(int deviceId) const {
QXmppIq iq{}; QXmppIq iq{};
iq.setType(QXmppIq::Set); iq.setType(QXmppIq::Set);
auto item = createElement("item"); auto item = createElement("item");
item.setAttribute("id", QString::number(deviceId));
item.appendChild(toXml()); item.appendChild(toXml());
auto publish = createElement("publish"); auto publish = createElement("publish");
publish.setAttribute("node", "urn:xmpp:omemo:1:bundles"); publish.setAttribute("node", QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
publish.appendChild(item); publish.appendChild(item);
auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub"); auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
pubSub.appendChild(publish); pubSub.appendChild(publish);
pubSub.appendChild(createOpenPublishOptions("max")); pubSub.appendChild(createOpenPublishOptions());
iq.extensions().push_back(pubSub); iq.extensions().push_back(pubSub);
@ -145,7 +138,7 @@ QXmppIq QXmpp::Omemo::Bundle::toIq() const {
} }
void QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) { void QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) {
if (!elementMatches(element, "bundle", "urn:xmpp:omemo:1")) if (!elementMatches(element, "bundle", "eu.siacs.conversations.axolotl"))
return; return;
auto spkNode = element.firstChildElement("spk"); auto spkNode = element.firstChildElement("spk");
@ -184,7 +177,7 @@ void QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) {
QXmppPubSubIq QXmpp::Omemo::Bundle::fetchDeviceBundleIq(int deviceId) { QXmppPubSubIq QXmpp::Omemo::Bundle::fetchDeviceBundleIq(int deviceId) {
QXmppPubSubIq iq{}; QXmppPubSubIq iq{};
iq.setType(QXmppIq::Get); iq.setType(QXmppIq::Get);
iq.setQueryNode("urn:xmpp:omemo:1:bundles"); iq.setQueryNode(QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
QXmppPubSubItem item{}; QXmppPubSubItem item{};
item.setId(QString::number(deviceId)); item.setId(QString::number(deviceId));
@ -197,13 +190,8 @@ QXmppElement QXmpp::Omemo::EncryptedMessage::header() const {
auto result = createElement("header"); auto result = createElement("header");
result.setAttribute("sid", QString::number(fromDeviceId)); result.setAttribute("sid", QString::number(fromDeviceId));
return result; auto ivNode = createElement("iv");
} ivNode.setValue(iv);
QXmppElement QXmpp::Omemo::EncryptedMessage::toXml() const {
auto result = createElement("encrypted", "urn:xmpp:omemo:1");
result.appendChild(header());
for (const auto &key : keys) { for (const auto &key : keys) {
result.appendChild(key.toXml()); result.appendChild(key.toXml());
@ -212,6 +200,16 @@ QXmppElement QXmpp::Omemo::EncryptedMessage::toXml() const {
return result; return result;
} }
QXmppElement QXmpp::Omemo::EncryptedMessage::toXml() const {
auto result = createElement("encrypted", "eu.siacs.conversations.axolotl");
result.appendChild(header());
// TODO: Payload is optional
result.appendChild(payload());
return result;
}
QXmppElement QXmpp::Omemo::EncryptedMessage::payload() const { QXmppElement QXmpp::Omemo::EncryptedMessage::payload() const {
QBuffer buffer; QBuffer buffer;
buffer.open(QIODevice::ReadWrite); buffer.open(QIODevice::ReadWrite);
@ -260,21 +258,11 @@ QXmppElement QXmpp::Omemo::EncryptedMessage::content() const {
QXmppElement QXmpp::Omemo::MessageKey::toXml() const { QXmppElement QXmpp::Omemo::MessageKey::toXml() const {
auto result = createElement("key"); auto result = createElement("key");
if (kex) result.setAttribute("rid", QString::number(receivingDeviceId));
result.setAttribute("kex", "true"); if (prekey)
result.setAttribute("prekey", "true");
result.setValue(key); result.setValue(key);
return result; return result;
} }
QXmppElement QXmpp::Omemo::HeaderKeys::toXml() const {
auto result = createElement("keys");
result.setAttribute("jid", jid);
for (const auto &key : keys) {
result.appendChild(key.toXml());
}
return result;
}

View File

@ -17,7 +17,6 @@ public:
void fromXml(const QXmppElement &element); void fromXml(const QXmppElement &element);
int id; int id;
QString label;
}; };
class DeviceList { class DeviceList {
@ -27,7 +26,6 @@ public:
/// Expects a urn:xmpp:omemo:1:devices node /// Expects a urn:xmpp:omemo:1:devices node
void fromXml(const QXmppElement &element); void fromXml(const QXmppElement &element);
private:
QList<Device> devices; QList<Device> devices;
}; };
@ -46,11 +44,9 @@ public:
[[nodiscard]] static QXmppPubSubIq fetchDeviceBundleIq(int deviceId); [[nodiscard]] static QXmppPubSubIq fetchDeviceBundleIq(int deviceId);
[[nodiscard]] QXmppElement toXml() const; [[nodiscard]] QXmppElement toXml() const;
[[nodiscard]] QXmppIq toIq() const; [[nodiscard]] QXmppIq toIq(int deviceId) const;
void fromXml(const QXmppElement &element); void fromXml(const QXmppElement &element);
int deviceId;
QString spk; QString spk;
int spkId; int spkId;
QString spks; QString spks;
@ -62,18 +58,11 @@ class MessageKey {
public: public:
[[nodiscard]] QXmppElement toXml() const; [[nodiscard]] QXmppElement toXml() const;
bool kex{}; int receivingDeviceId{};
bool prekey{};
QString key{}; QString key{};
}; };
class HeaderKeys {
public:
[[nodiscard]] QXmppElement toXml() const;
QString jid{};
QList<MessageKey> keys{};
};
class EncryptedMessage { class EncryptedMessage {
public: public:
[[nodiscard]] QXmppElement header() const; [[nodiscard]] QXmppElement header() const;
@ -83,11 +72,13 @@ public:
int fromDeviceId{}; int fromDeviceId{};
QList<HeaderKeys> keys{}; QList<MessageKey> keys{};
QString from{}; QString from{};
QString to{}; QString to{};
QDateTime timestamp{}; QDateTime timestamp{};
QString iv{};
QXmppMessage message{}; QXmppMessage message{};
}; };

View File

@ -0,0 +1,66 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "qxmpp_omemo_manager.h"
#include <QDomElement>
#include <QXmppClient.h>
#include <QXmppPubSubIq.h>
#include <iostream>
QXmpp::Omemo::Manager::Manager() : deviceService(new QXmpp::Omemo::DeviceService(this)) {
connect(this, &Manager::deviceListReceived, deviceService.get(), &DeviceService::onDeviceListReceived);
}
bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
QString str{};
QTextStream info(&str);
stanza.save(info, 4);
std::cout << str.toStdString();
if (stanza.tagName() == "iq") {
if (stanza.attribute("type") == "result") {
auto pubsub = stanza.firstChildElement("pubsub");
if (!pubsub.isNull()) {
auto items = pubsub.firstChildElement("items");
if (items.attribute("node") == "eu.siacs.conversations.axolotl.devicelist") {
auto item = items.firstChildElement("item");
if (!item.isNull()) {
auto list = item.firstChildElement("list");
if (!list.isNull()) {
DeviceList deviceList{};
deviceList.fromXml(list);
emit deviceListReceived(stanza.attribute("from"), deviceList);
return true;
}
}
}
}
}
}
return false;
}
void QXmpp::Omemo::Manager::setClient(QXmppClient *client) {
QXmppClientExtension::setClient(client);
if (!client)
return;
QObject::connect(client, &QXmppClient::connected, this, &Manager::fetchOwnDevices);
}
void QXmpp::Omemo::Manager::fetchOwnDevices() {
QXmppPubSubIq iq{};
iq.setFrom(client()->configuration().jid());
iq.setTo(client()->configuration().jidBare());
iq.setType(QXmppIq::Get);
iq.setQueryNode("eu.siacs.conversations.axolotl.devicelist");
client()->sendPacket(iq);
}

View File

@ -0,0 +1,36 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include "device_service.h"
#include "qomemo.h"
#include <QXmppClientExtension.h>
namespace QXmpp::Omemo {
class Manager : public QXmppClientExtension {
Q_OBJECT;
public:
Manager();
~Manager() override = default;
bool handleStanza(const QDomElement &stanza) override;
public slots:
void fetchOwnDevices();
signals:
void deviceListReceived(const QString& jid, const DeviceList& list);
protected:
void setClient(QXmppClient *client) override;
private:
QScopedPointer<DeviceService> deviceService;
};
} // namespace QXmpp::Omemo