feat(OMEMO): qxmppfactories, refactoring

This commit is contained in:
vae 2021-05-12 15:00:41 +03:00
parent b22a4c8ca3
commit 6721b62629
Signed by: vae
GPG Key ID: A9A33351400E00E5
8 changed files with 294 additions and 77 deletions

View File

@ -3,4 +3,6 @@ target_sources(squawk PRIVATE
signal.cpp
qomemo.cpp
qomemo.h
sce.cpp
sce.h
)

View File

@ -3,77 +3,16 @@
*/
#include "qomemo.h"
#include "sce.h"
#include <shared/qxmppfactories.h>
#include <QBuffer>
#include <QDebug>
#include <QDomDocument>
static bool elementMatches(const QXmppElement &element, const QString &tagName,
const QString &xmlns = QStringLiteral("")) {
if (element.tagName() != tagName) {
qWarning() << "tag name: expected = " << tagName
<< ", got = " << element.tagName();
return false;
}
using namespace QXmpp::Factories;
if (!xmlns.isEmpty() && element.attribute("xmlns") != xmlns) {
qWarning() << "xmlns: expected = " << xmlns
<< ", got = " << element.attribute("xmlns");
return false;
}
return true;
}
static QXmppElement createElement(const QString &tagName,
const QString &xmlns = QStringLiteral("")) {
QXmppElement el{};
el.setTagName(tagName);
if (!xmlns.isEmpty())
el.setAttribute("xmlns", xmlns);
return el;
}
static QXmppElement createValue(const QString &value) {
auto el = createElement("value");
el.setValue(value);
return el;
}
static QXmppElement createField(const QString &key, const QString &value,
bool hidden = false) {
auto field = createElement("field");
field.setAttribute("var", key);
if (hidden)
field.setAttribute("type", "hidden");
field.appendChild(createValue(value));
return field;
}
static QXmppElement
createOpenPublishOptions(const QString &maxItems = QStringLiteral("")) {
auto formType = createField(
"FORM_TYPE", "http://jabber.org/protocol/pubsub#publish-options", true);
auto accessModel = createField("pubsub#access_model", "open");
auto x = createElement("x", "jabber:x:data");
x.setAttribute("type", "submit");
x.appendChild(formType);
x.appendChild(accessModel);
if (!maxItems.isEmpty())
x.appendChild(createField("pubsub#max_items", maxItems));
auto publishOptions = createElement("publish-options");
publishOptions.appendChild(x);
return publishOptions;
}
QXmppElement QOmemo::DeviceList::toXml() const {
QXmppElement QXmpp::Omemo::DeviceList::toXml() const {
auto element = createElement("devices", "urn:xmpp:omemo:1");
for (const auto &d : devices) {
@ -83,7 +22,7 @@ QXmppElement QOmemo::DeviceList::toXml() const {
return element;
}
QXmppIq QOmemo::DeviceList::toIq() const {
QXmppIq QXmpp::Omemo::DeviceList::toIq() const {
QXmppIq iq{};
iq.setType(QXmppIq::Set);
@ -105,7 +44,7 @@ QXmppIq QOmemo::DeviceList::toIq() const {
return iq;
}
void QOmemo::DeviceList::fromXml(const QXmppElement &element) {
void QXmpp::Omemo::DeviceList::fromXml(const QXmppElement &element) {
if (!elementMatches(element, "devices", "urn:xmpp:omemo:1"))
return;
@ -121,7 +60,7 @@ void QOmemo::DeviceList::fromXml(const QXmppElement &element) {
}
}
QXmppElement QOmemo::Device::toXml() const {
QXmppElement QXmpp::Omemo::Device::toXml() const {
auto result = createElement("device");
result.setAttribute("id", QString::number(id));
@ -132,7 +71,7 @@ QXmppElement QOmemo::Device::toXml() const {
return result;
}
void QOmemo::Device::fromXml(const QXmppElement &element) {
void QXmpp::Omemo::Device::fromXml(const QXmppElement &element) {
if (!elementMatches(element, "device"))
return;
@ -140,7 +79,7 @@ void QOmemo::Device::fromXml(const QXmppElement &element) {
label = element.attribute("label");
}
QXmppElement QOmemo::PreKey::toXml() const {
QXmppElement QXmpp::Omemo::PreKey::toXml() const {
auto pk = createElement("pk");
pk.setAttribute("id", QString::number(id));
// TODO: Base64
@ -149,7 +88,7 @@ QXmppElement QOmemo::PreKey::toXml() const {
return pk;
}
void QOmemo::PreKey::fromXml(const QXmppElement &element) {
void QXmpp::Omemo::PreKey::fromXml(const QXmppElement &element) {
if (!elementMatches(element, "pk"))
return;
@ -158,7 +97,7 @@ void QOmemo::PreKey::fromXml(const QXmppElement &element) {
data = element.value();
}
QXmppElement QOmemo::Bundle::toXml() const {
QXmppElement QXmpp::Omemo::Bundle::toXml() const {
auto spkNode = createElement("spk");
spkNode.setAttribute("id", QString::number(spkId));
spkNode.setValue(spk);
@ -183,7 +122,7 @@ QXmppElement QOmemo::Bundle::toXml() const {
return result;
}
QXmppIq QOmemo::Bundle::toIq() const {
QXmppIq QXmpp::Omemo::Bundle::toIq() const {
QXmppIq iq{};
iq.setType(QXmppIq::Set);
@ -205,7 +144,7 @@ QXmppIq QOmemo::Bundle::toIq() const {
return iq;
}
void QOmemo::Bundle::fromXml(const QXmppElement &element) {
void QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) {
if (!elementMatches(element, "bundle", "urn:xmpp:omemo:1"))
return;
@ -241,3 +180,101 @@ void QOmemo::Bundle::fromXml(const QXmppElement &element) {
prekeys.push_back(pk);
}
}
QXmppPubSubIq QXmpp::Omemo::Bundle::fetchDeviceBundleIq(int deviceId) {
QXmppPubSubIq iq{};
iq.setType(QXmppIq::Get);
iq.setQueryNode("urn:xmpp:omemo:1:bundles");
QXmppPubSubItem item{};
item.setId(QString::number(deviceId));
iq.setItems({item});
return iq;
}
QXmppElement QXmpp::Omemo::EncryptedMessage::header() const {
auto result = createElement("header");
result.setAttribute("sid", QString::number(fromDeviceId));
return result;
}
QXmppElement QXmpp::Omemo::EncryptedMessage::toXml() const {
auto result = createElement("encrypted", "urn:xmpp:omemo:1");
result.appendChild(header());
for (const auto &key : keys) {
result.appendChild(key.toXml());
}
return result;
}
QXmppElement QXmpp::Omemo::EncryptedMessage::payload() const {
QBuffer buffer;
buffer.open(QIODevice::ReadWrite);
QXmlStreamWriter writer(&buffer);
message.toXml(&writer);
QDomDocument doc;
doc.setContent(buffer.data(), true);
QXmppElement root(doc.documentElement());
root.setTagName("payload");
return root;
}
QXmppElement QXmpp::Omemo::EncryptedMessage::content() const {
auto envelope = createElement("content", "urn:xmpp:sce:0");
envelope.appendChild(payload());
if (!from.isEmpty()) {
auto fromNode = createElement("from");
fromNode.setAttribute("jid", from);
envelope.appendChild(fromNode);
}
if (!to.isEmpty()) {
auto toNode = createElement("to");
toNode.setAttribute("jid", to);
envelope.appendChild(toNode);
}
if (!timestamp.isNull()) {
auto timeNode = createElement("time");
timeNode.setAttribute("stamp", timestamp.toString(Qt::DateFormat::ISODate));
envelope.appendChild(timeNode);
}
auto rpad = createElement("rpad");
rpad.setValue(QXmpp::Sce::generatePadding());
envelope.appendChild(rpad);
return envelope;
}
QXmppElement QXmpp::Omemo::MessageKey::toXml() const {
auto result = createElement("key");
if (kex)
result.setAttribute("kex", "true");
result.setValue(key);
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

@ -6,7 +6,10 @@
#include <QXmppPubSubIq.h>
namespace QOmemo {
#include <QDateTime>
#include <QXmppMessage.h>
namespace QXmpp::Omemo {
class Device {
public:
@ -40,6 +43,8 @@ public:
class Bundle {
public:
[[nodiscard]] static QXmppPubSubIq fetchDeviceBundleIq(int deviceId);
[[nodiscard]] QXmppElement toXml() const;
[[nodiscard]] QXmppIq toIq() const;
void fromXml(const QXmppElement &element);
@ -53,4 +58,37 @@ public:
QList<PreKey> prekeys;
};
class MessageKey {
public:
[[nodiscard]] QXmppElement toXml() const;
bool kex{};
QString key{};
};
class HeaderKeys {
public:
[[nodiscard]] QXmppElement toXml() const;
QString jid{};
QList<MessageKey> keys{};
};
class EncryptedMessage {
public:
[[nodiscard]] QXmppElement header() const;
[[nodiscard]] QXmppElement content() const;
[[nodiscard]] QXmppElement toXml() const;
[[nodiscard]] QXmppElement payload() const;
int fromDeviceId{};
QList<HeaderKeys> keys{};
QString from{};
QString to{};
QDateTime timestamp{};
QXmppMessage message{};
};
} // namespace QOmemo

24
qomemo/sce.cpp Normal file
View File

@ -0,0 +1,24 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "sce.h"
#include <QRandomGenerator>
constexpr int RPAD_MAX_LENGTH = 200;
QString QXmpp::Sce::generatePadding() {
QRandomGenerator random{};
QString result{};
QString alphabet{ QStringLiteral("!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~") };
auto length = random.bounded(RPAD_MAX_LENGTH);
result.resize(length);
for (auto i = 0; i < length; ++i) {
result[i] = alphabet[random.bounded(alphabet.length())];
}
return result;
}

14
qomemo/sce.h Normal file
View File

@ -0,0 +1,14 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QXmppElement.h>
#include <QDateTime>
namespace QXmpp::Sce {
QString generatePadding();
}

View File

@ -16,4 +16,6 @@ target_sources(squawk PRIVATE
utils.h
vcard.cpp
vcard.h
qxmppfactories.cpp
qxmppfactories.h
)

75
shared/qxmppfactories.cpp Normal file
View File

@ -0,0 +1,75 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "qxmppfactories.h"
#include <QDebug>
bool QXmpp::Factories::elementMatches(const QXmppElement &element,
const QString &tagName,
const QString &xmlns) {
if (element.tagName() != tagName) {
qWarning() << "tag name: expected = " << tagName
<< ", got = " << element.tagName();
return false;
}
if (!xmlns.isEmpty() && element.attribute("xmlns") != xmlns) {
qWarning() << "xmlns: expected = " << xmlns
<< ", got = " << element.attribute("xmlns");
return false;
}
return true;
}
QXmppElement QXmpp::Factories::createElement(const QString &tagName,
const QString &xmlns) {
QXmppElement el{};
el.setTagName(tagName);
if (!xmlns.isEmpty())
el.setAttribute("xmlns", xmlns);
return el;
}
QXmppElement QXmpp::Factories::createValue(const QString &value) {
auto el = createElement("value");
el.setValue(value);
return el;
}
QXmppElement QXmpp::Factories::createField(const QString &key,
const QString &value, bool hidden) {
auto field = createElement("field");
field.setAttribute("var", key);
if (hidden)
field.setAttribute("type", "hidden");
field.appendChild(createValue(value));
return field;
}
QXmppElement
QXmpp::Factories::createOpenPublishOptions(const QString &maxItems) {
auto formType = createField(
"FORM_TYPE", "http://jabber.org/protocol/pubsub#publish-options", true);
auto accessModel = createField("pubsub#access_model", "open");
auto x = createElement("x", "jabber:x:data");
x.setAttribute("type", "submit");
x.appendChild(formType);
x.appendChild(accessModel);
if (!maxItems.isEmpty())
x.appendChild(createField("pubsub#max_items", maxItems));
auto publishOptions = createElement("publish-options");
publishOptions.appendChild(x);
return publishOptions;
}

25
shared/qxmppfactories.h Normal file
View File

@ -0,0 +1,25 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QXmppElement.h>
namespace QXmpp::Factories {
bool elementMatches(const QXmppElement &element, const QString &tagName,
const QString &xmlns = QStringLiteral(""));
QXmppElement createElement(const QString &tagName,
const QString &xmlns = QStringLiteral(""));
QXmppElement createValue(const QString &value);
QXmppElement createField(const QString &key, const QString &value,
bool hidden = false);
QXmppElement
createOpenPublishOptions(const QString &maxItems = QStringLiteral(""));
} // namespace QXmpp::Factories