From 6721b62629ab036df7230fc98ac70486e3d1825c Mon Sep 17 00:00:00 2001 From: vae Date: Wed, 12 May 2021 15:00:41 +0300 Subject: [PATCH] feat(OMEMO): qxmppfactories, refactoring --- qomemo/CMakeLists.txt | 2 + qomemo/qomemo.cpp | 189 +++++++++++++++++++++++--------------- qomemo/qomemo.h | 40 +++++++- qomemo/sce.cpp | 24 +++++ qomemo/sce.h | 14 +++ shared/CMakeLists.txt | 2 + shared/qxmppfactories.cpp | 75 +++++++++++++++ shared/qxmppfactories.h | 25 +++++ 8 files changed, 294 insertions(+), 77 deletions(-) create mode 100644 qomemo/sce.cpp create mode 100644 qomemo/sce.h create mode 100644 shared/qxmppfactories.cpp create mode 100644 shared/qxmppfactories.h diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt index f17475c..e8e4502 100644 --- a/qomemo/CMakeLists.txt +++ b/qomemo/CMakeLists.txt @@ -3,4 +3,6 @@ target_sources(squawk PRIVATE signal.cpp qomemo.cpp qomemo.h + sce.cpp + sce.h ) \ No newline at end of file diff --git a/qomemo/qomemo.cpp b/qomemo/qomemo.cpp index 1695c04..9d4266a 100644 --- a/qomemo/qomemo.cpp +++ b/qomemo/qomemo.cpp @@ -3,77 +3,16 @@ */ #include "qomemo.h" +#include "sce.h" +#include +#include #include +#include -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; +} diff --git a/qomemo/qomemo.h b/qomemo/qomemo.h index 8955d1e..906d0ba 100644 --- a/qomemo/qomemo.h +++ b/qomemo/qomemo.h @@ -6,7 +6,10 @@ #include -namespace QOmemo { +#include +#include + +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 prekeys; }; +class MessageKey { +public: + [[nodiscard]] QXmppElement toXml() const; + + bool kex{}; + QString key{}; +}; + +class HeaderKeys { +public: + [[nodiscard]] QXmppElement toXml() const; + + QString jid{}; + QList keys{}; +}; + +class EncryptedMessage { +public: + [[nodiscard]] QXmppElement header() const; + [[nodiscard]] QXmppElement content() const; + [[nodiscard]] QXmppElement toXml() const; + [[nodiscard]] QXmppElement payload() const; + + int fromDeviceId{}; + + QList keys{}; + QString from{}; + QString to{}; + QDateTime timestamp{}; + + QXmppMessage message{}; +}; + } // namespace QOmemo diff --git a/qomemo/sce.cpp b/qomemo/sce.cpp new file mode 100644 index 0000000..decb8b3 --- /dev/null +++ b/qomemo/sce.cpp @@ -0,0 +1,24 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#include "sce.h" + +#include + +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; +} diff --git a/qomemo/sce.h b/qomemo/sce.h new file mode 100644 index 0000000..2cea049 --- /dev/null +++ b/qomemo/sce.h @@ -0,0 +1,14 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#pragma once + +#include +#include + +namespace QXmpp::Sce { + +QString generatePadding(); + +} diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt index a36b516..97ed619 100644 --- a/shared/CMakeLists.txt +++ b/shared/CMakeLists.txt @@ -16,4 +16,6 @@ target_sources(squawk PRIVATE utils.h vcard.cpp vcard.h + qxmppfactories.cpp + qxmppfactories.h ) diff --git a/shared/qxmppfactories.cpp b/shared/qxmppfactories.cpp new file mode 100644 index 0000000..dc53388 --- /dev/null +++ b/shared/qxmppfactories.cpp @@ -0,0 +1,75 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#include "qxmppfactories.h" + +#include + +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; +} \ No newline at end of file diff --git a/shared/qxmppfactories.h b/shared/qxmppfactories.h new file mode 100644 index 0000000..d7d78e6 --- /dev/null +++ b/shared/qxmppfactories.h @@ -0,0 +1,25 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#pragma once + +#include + +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