aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMelvin Keskin <melvo@olomono.de>2021-09-02 23:35:11 +0200
committerLinus Jahn <lnj@kaidan.im>2021-09-03 16:16:14 +0200
commit11db6293ced5c4665630ad3d728aba5751a3f658 (patch)
tree93e8536de8daa881d8ee84b28b7bc5e0a3571572
parent2ccc8d2d7033bb7180638ec8f980b904cfe3d686 (diff)
downloadqxmpp-11db6293ced5c4665630ad3d728aba5751a3f658.tar.gz
Add QXmppOmemoDeviceBundle
Co-authored-by: Germán Márquez Mejía <mancho@olomono.de>
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/base/QXmppOmemoData.cpp234
-rw-r--r--src/base/QXmppOmemoDeviceBundle.h73
-rw-r--r--tests/qxmppomemodata/tst_qxmppomemodata.cpp72
4 files changed, 380 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bba6b704..7a17b027 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -46,6 +46,7 @@ set(INSTALL_HEADER_FILES
base/QXmppMucIq.h
base/QXmppNonza.h
base/QXmppNonSASLAuth.h
+ base/QXmppOmemoDeviceBundle.h
base/QXmppOmemoDeviceList.h
base/QXmppOmemoDeviceElement.h
base/QXmppPingIq.h
diff --git a/src/base/QXmppOmemoData.cpp b/src/base/QXmppOmemoData.cpp
index b095ac24..84170c24 100644
--- a/src/base/QXmppOmemoData.cpp
+++ b/src/base/QXmppOmemoData.cpp
@@ -23,11 +23,13 @@
*/
#include "QXmppConstants_p.h"
+#include "QXmppOmemoDeviceBundle.h"
#include "QXmppOmemoDeviceElement.h"
#include "QXmppOmemoDeviceList.h"
#include "QXmppUtils.h"
#include <QDomElement>
+#include <QMap>
///
/// \class QXmppOmemoDeviceElement
@@ -231,3 +233,235 @@ bool QXmppOmemoDeviceList::isOmemoDeviceList(const QDomElement &element)
return element.tagName() == QStringLiteral("devices") &&
element.namespaceURI() == ns_omemo_1;
}
+
+///
+/// \class QXmppOmemoDeviceBundle
+///
+/// \brief The QXmppOmemoDeviceBundle class represents an OMEMO bundle as
+/// defined by \xep{0384, OMEMO Encryption}.
+///
+/// It is a collection of publicly accessible data used by the X3DH key exchange.
+/// The data is used to build an encrypted session with an OMEMO device.
+///
+/// \since QXmpp 1.5
+///
+
+class QXmppOmemoDeviceBundlePrivate : public QSharedData
+{
+public:
+ QByteArray publicIdentityKey;
+ QByteArray signedPublicPreKey;
+ uint32_t signedPublicPreKeyId = 0;
+ QByteArray signedPublicPreKeySignature;
+ QMap<uint32_t, QByteArray> publicPreKeys;
+};
+
+///
+/// Constructs an OMEMO device bundle.
+///
+QXmppOmemoDeviceBundle::QXmppOmemoDeviceBundle()
+ : d(new QXmppOmemoDeviceBundlePrivate)
+{
+}
+
+///
+/// Constructs a copy of \a other.
+///
+/// \param other
+///
+QXmppOmemoDeviceBundle::QXmppOmemoDeviceBundle(const QXmppOmemoDeviceBundle &other) = default;
+
+QXmppOmemoDeviceBundle::~QXmppOmemoDeviceBundle() = default;
+
+///
+/// Assigns \a other to this OMEMO device bundle.
+///
+/// \param other
+///
+QXmppOmemoDeviceBundle &QXmppOmemoDeviceBundle::operator=(const QXmppOmemoDeviceBundle &other) = default;
+
+///
+/// Returns the public identity key.
+///
+/// The public identity key is the public long-term key which never changes.
+///
+/// \return the public identity key
+///
+QByteArray QXmppOmemoDeviceBundle::publicIdentityKey() const
+{
+ return d->publicIdentityKey;
+}
+
+///
+/// Sets the public identity key.
+///
+/// \param key public identity key
+///
+void QXmppOmemoDeviceBundle::setPublicIdentityKey(const QByteArray &key)
+{
+ d->publicIdentityKey = key;
+}
+
+///
+/// Returns the public pre key that is signed.
+///
+/// \return the signed public pre key
+///
+QByteArray QXmppOmemoDeviceBundle::signedPublicPreKey() const
+{
+ return d->signedPublicPreKey;
+}
+
+///
+/// Sets the public pre key that is signed.
+///
+/// \param key signed public pre key
+///
+void QXmppOmemoDeviceBundle::setSignedPublicPreKey(const QByteArray &key)
+{
+ d->signedPublicPreKey = key;
+}
+
+///
+/// Returns the ID of the public pre key that is signed.
+///
+/// The ID is 0 if it is unset.
+///
+/// \return the ID of the signed public pre key
+///
+uint32_t QXmppOmemoDeviceBundle::signedPublicPreKeyId() const
+{
+ return d->signedPublicPreKeyId;
+}
+
+///
+/// Sets the ID of the public pre key that is signed.
+///
+/// A valid ID must be at least 1 and at most 2^32-1.
+///
+/// \param id ID of the signed public pre key
+///
+void QXmppOmemoDeviceBundle::setSignedPublicPreKeyId(const uint32_t id)
+{
+ d->signedPublicPreKeyId = id;
+}
+
+///
+/// Returns the signature of the public pre key that is signed.
+///
+/// \return the signature of the signed public pre key
+///
+QByteArray QXmppOmemoDeviceBundle::signedPublicPreKeySignature() const
+{
+ return d->signedPublicPreKeySignature;
+}
+
+///
+/// Returns the signature of the public pre key that is signed.
+///
+/// \param signature signature of the signed public pre key
+///
+void QXmppOmemoDeviceBundle::setSignedPublicPreKeySignature(const QByteArray &signature)
+{
+ d->signedPublicPreKeySignature = signature;
+}
+
+///
+/// Returns the public pre keys.
+///
+/// The key of a key-value pair represents the ID of the corresponding public
+/// pre key.
+/// The value of a key-value pair represents the public pre key.
+///
+/// \return the public pre keys
+///
+QMap<uint32_t, QByteArray> QXmppOmemoDeviceBundle::publicPreKeys() const
+{
+ return d->publicPreKeys;
+}
+
+///
+/// Sets the public pre keys.
+///
+/// The key of a key-value pair represents the ID of the corresponding public
+/// pre key.
+/// The ID must be at least 1 and at most 2^32-1, otherwise the corresponding
+/// key-value pair is ignored.
+/// The value of a key-value pair represents the public pre key.
+///
+/// \param keys public pre keys
+///
+void QXmppOmemoDeviceBundle::setPublicPreKeys(const QMap<uint32_t, QByteArray> &keys)
+{
+ for (auto it = keys.cbegin(); it != keys.cend(); it++) {
+ if (it.key() > 0) {
+ d->publicPreKeys.insert(it.key(), it.value());
+ }
+ }
+}
+
+/// \cond
+void QXmppOmemoDeviceBundle::parse(const QDomElement &element)
+{
+ d->publicIdentityKey = QByteArray::fromBase64(element.firstChildElement(QStringLiteral("ik")).text().toLatin1());
+
+ const auto signedPublicPreKeyElement = element.firstChildElement(QStringLiteral("spk"));
+ if (!signedPublicPreKeyElement.isNull()) {
+ d->signedPublicPreKeyId = signedPublicPreKeyElement.attribute(QStringLiteral("id")).toInt();
+ d->signedPublicPreKey = QByteArray::fromBase64(signedPublicPreKeyElement.text().toLatin1());
+ }
+ d->signedPublicPreKeySignature = QByteArray::fromBase64(element.firstChildElement(QStringLiteral("spks")).text().toLatin1());
+
+ const auto publicPreKeysElement = element.firstChildElement(QStringLiteral("prekeys"));
+ if (!publicPreKeysElement.isNull()) {
+ for (QDomElement publicPreKeyElement = publicPreKeysElement.firstChildElement(QStringLiteral("pk"));
+ !publicPreKeyElement.isNull();
+ publicPreKeyElement = publicPreKeyElement.nextSiblingElement(QStringLiteral("pk"))) {
+ d->publicPreKeys.insert(publicPreKeyElement.attribute(QStringLiteral("id")).toInt(), QByteArray::fromBase64(publicPreKeyElement.text().toLatin1()));
+ }
+ }
+}
+
+void QXmppOmemoDeviceBundle::toXml(QXmlStreamWriter *writer) const
+{
+ writer->writeStartElement(QStringLiteral("bundle"));
+ writer->writeDefaultNamespace(ns_omemo_1);
+
+ writer->writeStartElement(QStringLiteral("ik"));
+ writer->writeCharacters(publicIdentityKey().toBase64());
+ writer->writeEndElement();
+
+ writer->writeStartElement(QStringLiteral("spk"));
+ writer->writeAttribute(QStringLiteral("id"), QString::number(signedPublicPreKeyId()));
+ writer->writeCharacters(signedPublicPreKey().toBase64());
+ writer->writeEndElement();
+
+ writer->writeStartElement(QStringLiteral("spks"));
+ writer->writeCharacters(signedPublicPreKeySignature().toBase64());
+ writer->writeEndElement();
+
+ writer->writeStartElement(QStringLiteral("prekeys"));
+ for (auto it = d->publicPreKeys.cbegin(); it != d->publicPreKeys.cend(); it++) {
+ writer->writeStartElement(QStringLiteral("pk"));
+ writer->writeAttribute(QStringLiteral("id"), QString::number(it.key()));
+ writer->writeCharacters(it.value().toBase64());
+ writer->writeEndElement();
+ }
+ writer->writeEndElement(); // prekeys
+
+ writer->writeEndElement(); // bundle
+}
+/// \endcond
+
+///
+/// Determines whether the given DOM element is an OMEMO device bundle.
+///
+/// \param element DOM element being checked
+///
+/// \return true if element is an OMEMO device bundle, otherwise false
+///
+bool QXmppOmemoDeviceBundle::isOmemoDeviceBundle(const QDomElement &element)
+{
+ return element.tagName() == QStringLiteral("bundle") &&
+ element.namespaceURI() == ns_omemo_1;
+}
diff --git a/src/base/QXmppOmemoDeviceBundle.h b/src/base/QXmppOmemoDeviceBundle.h
new file mode 100644
index 00000000..b687c78b
--- /dev/null
+++ b/src/base/QXmppOmemoDeviceBundle.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2008-2021 The QXmpp developers
+ *
+ * Author:
+ * Germán Márquez Mejía
+ * Melvin Keskin
+ *
+ * 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 QXMPPOMEMODEVICEBUNDLE_H
+#define QXMPPOMEMODEVICEBUNDLE_H
+
+#include "QXmppGlobal.h"
+
+#include <QSharedDataPointer>
+
+class QDomElement;
+class QXmppOmemoDeviceBundlePrivate;
+class QXmlStreamWriter;
+
+class QXMPP_EXPORT QXmppOmemoDeviceBundle
+{
+public:
+ QXmppOmemoDeviceBundle();
+ QXmppOmemoDeviceBundle(const QXmppOmemoDeviceBundle &other);
+ ~QXmppOmemoDeviceBundle();
+
+ QXmppOmemoDeviceBundle &operator=(const QXmppOmemoDeviceBundle &other);
+
+ QByteArray publicIdentityKey() const;
+ void setPublicIdentityKey(const QByteArray &key);
+
+ QByteArray signedPublicPreKey() const;
+ void setSignedPublicPreKey(const QByteArray &key);
+
+ uint32_t signedPublicPreKeyId() const;
+ void setSignedPublicPreKeyId(uint32_t id);
+
+ QByteArray signedPublicPreKeySignature() const;
+ void setSignedPublicPreKeySignature(const QByteArray &signature);
+
+ QMap<uint32_t, QByteArray> publicPreKeys() const;
+ void setPublicPreKeys(const QMap<uint32_t, QByteArray> &keys);
+
+ /// \cond
+ void parse(const QDomElement &element);
+ void toXml(QXmlStreamWriter *writer) const;
+ /// \endcond
+
+ static bool isOmemoDeviceBundle(const QDomElement &element);
+
+private:
+ QSharedDataPointer<QXmppOmemoDeviceBundlePrivate> d;
+};
+
+Q_DECLARE_TYPEINFO(QXmppOmemoDeviceBundle, Q_MOVABLE_TYPE);
+
+#endif // QXMPPOMEMODEVICEBUNDLE_H
diff --git a/tests/qxmppomemodata/tst_qxmppomemodata.cpp b/tests/qxmppomemodata/tst_qxmppomemodata.cpp
index 23f65023..46bc2b44 100644
--- a/tests/qxmppomemodata/tst_qxmppomemodata.cpp
+++ b/tests/qxmppomemodata/tst_qxmppomemodata.cpp
@@ -22,6 +22,7 @@
*
*/
+#include "QXmppOmemoDeviceBundle.h"
#include "QXmppOmemoDeviceElement.h"
#include "QXmppOmemoDeviceList.h"
@@ -40,6 +41,9 @@ private slots:
void testIsOmemoDeviceList_data();
void testIsOmemoDeviceList();
void testOmemoDeviceList();
+ void testIsOmemoDeviceBundle_data();
+ void testIsOmemoDeviceBundle();
+ void testOmemoDeviceBundle();
};
void tst_QXmppOmemoData::testIsOmemoDeviceElement_data()
@@ -163,5 +167,73 @@ void tst_QXmppOmemoData::testOmemoDeviceList()
serializePacket(deviceList2, xml);
}
+void tst_QXmppOmemoData::testIsOmemoDeviceBundle_data()
+{
+ QTest::addColumn<QByteArray>("xml");
+ QTest::addColumn<bool>("isValid");
+
+ QTest::newRow("valid")
+ << QByteArrayLiteral("<bundle xmlns=\"urn:xmpp:omemo:1\"/>")
+ << true;
+ QTest::newRow("invalidTag")
+ << QByteArrayLiteral("<invalid xmlns=\"urn:xmpp:omemo:1\"/>")
+ << false;
+ QTest::newRow("invalidNamespace")
+ << QByteArrayLiteral("<bundle xmlns=\"invalid\"/>")
+ << false;
+}
+
+void tst_QXmppOmemoData::testIsOmemoDeviceBundle()
+{
+ QFETCH(QByteArray, xml);
+ QFETCH(bool, isValid);
+
+ QDomDocument doc;
+ QCOMPARE(doc.setContent(xml, true), true);
+ const QDomElement element = doc.documentElement();
+ QCOMPARE(QXmppOmemoDeviceBundle::isOmemoDeviceBundle(element), isValid);
+}
+
+void tst_QXmppOmemoData::testOmemoDeviceBundle()
+{
+ const QByteArray xml(QByteArrayLiteral(
+ "<bundle xmlns=\"urn:xmpp:omemo:1\">"
+ "<ik>a012U0R9WixWKUYhYipucnZOWG06akFOR3Q1NGNOOmUK</ik>"
+ "<spk id=\"1\">Oy5TSG9vVVV4Wz9wUkUvI1lUXiVLIU5bbGIsUV0wRngK</spk>"
+ "<spks>PTEoSk91VnRZSXBzcFlPXy4jZ3NKcGVZZ2d3YVJbVj8K</spks>"
+ "<prekeys>"
+ "<pk id=\"1\">eDM2cnBiTmo4MmRGQ1RYTkZ0YnVwajJtNWdPdzkxZ0gK</pk>"
+ "<pk id=\"2\">aDRHdkcxNDNYUmJSNWVObnNWd0RCSzE1QlVKVGQ1RVEK</pk>"
+ "</prekeys>"
+ "</bundle>"));
+
+ const QMap<uint32_t, QByteArray> expectedPublicPreKeys = {
+ { 1, QByteArray::fromBase64(QByteArrayLiteral("eDM2cnBiTmo4MmRGQ1RYTkZ0YnVwajJtNWdPdzkxZ0gK")) },
+ { 2, QByteArray::fromBase64(QByteArrayLiteral("aDRHdkcxNDNYUmJSNWVObnNWd0RCSzE1QlVKVGQ1RVEK")) }
+ };
+
+ QXmppOmemoDeviceBundle deviceBundle1;
+ parsePacket(deviceBundle1, xml);
+ QCOMPARE(deviceBundle1.publicIdentityKey().toBase64(), QByteArrayLiteral("a012U0R9WixWKUYhYipucnZOWG06akFOR3Q1NGNOOmUK"));
+ QCOMPARE(deviceBundle1.signedPublicPreKeyId(), uint32_t(1));
+ QCOMPARE(deviceBundle1.signedPublicPreKey().toBase64(), QByteArrayLiteral("Oy5TSG9vVVV4Wz9wUkUvI1lUXiVLIU5bbGIsUV0wRngK"));
+ QCOMPARE(deviceBundle1.signedPublicPreKeySignature().toBase64(), QByteArrayLiteral("PTEoSk91VnRZSXBzcFlPXy4jZ3NKcGVZZ2d3YVJbVj8K"));
+ QCOMPARE(deviceBundle1.publicPreKeys(), expectedPublicPreKeys);
+ serializePacket(deviceBundle1, xml);
+
+ QXmppOmemoDeviceBundle deviceBundle2;
+ deviceBundle2.setPublicIdentityKey(QByteArray::fromBase64(QByteArrayLiteral("a012U0R9WixWKUYhYipucnZOWG06akFOR3Q1NGNOOmUK")));
+ deviceBundle2.setSignedPublicPreKeyId(1);
+ deviceBundle2.setSignedPublicPreKey(QByteArray::fromBase64(QByteArrayLiteral("Oy5TSG9vVVV4Wz9wUkUvI1lUXiVLIU5bbGIsUV0wRngK")));
+ deviceBundle2.setSignedPublicPreKeySignature(QByteArray::fromBase64(QByteArrayLiteral("PTEoSk91VnRZSXBzcFlPXy4jZ3NKcGVZZ2d3YVJbVj8K")));
+ deviceBundle2.setPublicPreKeys(expectedPublicPreKeys);
+ QCOMPARE(deviceBundle2.publicIdentityKey().toBase64(), QByteArrayLiteral("a012U0R9WixWKUYhYipucnZOWG06akFOR3Q1NGNOOmUK"));
+ QCOMPARE(deviceBundle2.signedPublicPreKeyId(), uint32_t(1));
+ QCOMPARE(deviceBundle2.signedPublicPreKey().toBase64(), QByteArrayLiteral("Oy5TSG9vVVV4Wz9wUkUvI1lUXiVLIU5bbGIsUV0wRngK"));
+ QCOMPARE(deviceBundle2.signedPublicPreKeySignature().toBase64(), QByteArrayLiteral("PTEoSk91VnRZSXBzcFlPXy4jZ3NKcGVZZ2d3YVJbVj8K"));
+ QCOMPARE(deviceBundle2.publicPreKeys(), expectedPublicPreKeys);
+ serializePacket(deviceBundle2, xml);
+}
+
QTEST_MAIN(tst_QXmppOmemoData)
#include "tst_qxmppomemodata.moc"