diff options
| -rw-r--r-- | CMakeLists.txt | 28 | ||||
| -rw-r--r-- | atm_db.cpp | 42 | ||||
| -rw-r--r-- | atm_db.h | 37 | ||||
| -rw-r--r-- | client.cpp | 61 | ||||
| -rw-r--r-- | client.h | 40 | ||||
| -rw-r--r-- | credentials.cpp | 218 | ||||
| -rw-r--r-- | credentials.h | 45 | ||||
| -rw-r--r-- | direction.h | 6 | ||||
| -rw-r--r-- | jiddb.cpp | 912 | ||||
| -rw-r--r-- | jiddb.h | 114 | ||||
| -rw-r--r-- | libomemo-c/CMakeLists.txt | 23 | ||||
| -rw-r--r-- | omemo_db.cpp | 93 | ||||
| -rw-r--r-- | omemo_db.h | 42 | ||||
| -rw-r--r-- | qxmpp/CMakeLists.txt | 18 | ||||
| m--------- | qxmpp/qxmpp | 0 | ||||
| -rw-r--r-- | trust_db.cpp | 415 | ||||
| -rw-r--r-- | trust_db.h | 72 |
17 files changed, 2163 insertions, 3 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index e12d803..be9668a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt6 REQUIRED COMPONENTS Quick Sql) +find_package(Qt6 REQUIRED COMPONENTS Core Quick Sql) # QXmpp requires C++17. set(CMAKE_CXX_STANDARD 17) @@ -16,8 +16,21 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) qt_standard_project_setup(REQUIRES 6.2) qt_add_executable(${PROJECT_NAME} + atm_db.cpp + atm_db.h + client.cpp + client.h + credentials.cpp + credentials.h + direction.h + jiddb.cpp + jiddb.h main.cpp + omemo_db.cpp + omemo_db.h test.h + trust_db.cpp + trust_db.h ) qt_add_qml_module(${PROJECT_NAME} @@ -32,6 +45,10 @@ qt_add_qml_module(${PROJECT_NAME} Main.qml ) +find_package(Qt6Keychain REQUIRED) +target_link_libraries(${PROJECT_NAME} PRIVATE + Qt6Keychain::Qt6Keychain) + # Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. # If you are developing for iOS or macOS you should consider setting an # explicit, fixed bundle identifier manually though. @@ -44,10 +61,15 @@ set_target_properties(${PROJECT_NAME} PROPERTIES ) target_link_libraries(${PROJECT_NAME} - PRIVATE Qt6::Core - Qt6::Quick + PRIVATE + Qt6::Core + Qt6::Quick + Qt6::Sql ) +add_subdirectory(libomemo-c) +add_subdirectory(qxmpp) + include(GNUInstallDirs) install(TARGETS ${PROJECT_NAME} BUNDLE DESTINATION . diff --git a/atm_db.cpp b/atm_db.cpp new file mode 100644 index 0000000..f038959 --- /dev/null +++ b/atm_db.cpp @@ -0,0 +1,42 @@ +#include "atm_db.h" +#include <QXmppFutureUtils_p.h> + +AtmDb::AtmDb(const QString &jid, const JidDb &db) : + TrustDb(jid, db) +{} + +QXmppTask<void> AtmDb::addKeysForPostponedTrustDecisions( + const QString &encryption, const QByteArray &senderKeyId, + const QList<QXmppTrustMessageKeyOwner> &keyOwners) +{ + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> AtmDb::removeKeysForPostponedTrustDecisions( + const QString &encryption, + const QList<QByteArray> &keyIdsForAuthentication, + const QList<QByteArray> &keyIdsForDistrusting) +{ + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> AtmDb::removeKeysForPostponedTrustDecisions( + const QString &encryption, + const QList<QByteArray> &senderKeyIds) +{ + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> AtmDb::removeKeysForPostponedTrustDecisions( + const QString &encryption) +{ + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<QHash<bool, QMultiHash<QString, QByteArray>>> + AtmDb::keysForPostponedTrustDecisions(const QString &encryption, + const QList<QByteArray> &senderKeyIds) +{ + return QXmpp::Private::makeReadyTask( + QHash<bool, QMultiHash<QString, QByteArray>>()); +} diff --git a/atm_db.h b/atm_db.h new file mode 100644 index 0000000..0cbc247 --- /dev/null +++ b/atm_db.h @@ -0,0 +1,37 @@ +#ifndef ATM_DB_H +#define ATM_DB_H + +#include "jiddb.h" +#include "trust_db.h" +#include <QXmppAtmTrustStorage.h> +#include <QXmppTask.h> +#include <QXmppTrustMessageKeyOwner.h> +#include <QByteArray> +#include <QHash> +#include <QMultiHash> +#include <QList> +#include <QString> + +class AtmDb : public TrustDb, virtual public QXmppAtmTrustStorage +{ +public: + AtmDb(const QString &jid, const JidDb &db); + + QXmppTask<void> addKeysForPostponedTrustDecisions( + const QString &encryption, const QByteArray &senderKeyId, + const QList<QXmppTrustMessageKeyOwner> &keyOwners) override; + QXmppTask<void> removeKeysForPostponedTrustDecisions( + const QString &encryption, + const QList<QByteArray> &keyIdsForAuthentication, + const QList<QByteArray> &keyIdsForDistrusting) override; + QXmppTask<void> removeKeysForPostponedTrustDecisions( + const QString &encryption, + const QList<QByteArray> &senderKeyIds) override; + QXmppTask<void> removeKeysForPostponedTrustDecisions( + const QString &encryption) override; + QXmppTask<QHash<bool, QMultiHash<QString, QByteArray>>> + keysForPostponedTrustDecisions(const QString &encryption, + const QList<QByteArray> &senderKeyIds = {}) override; +}; + +#endif diff --git a/client.cpp b/client.cpp new file mode 100644 index 0000000..3cb4e86 --- /dev/null +++ b/client.cpp @@ -0,0 +1,61 @@ +#include "client.h" +#include <QXmppTask.h> +#include <QDebug> +#include <iostream> + +Client::Client(const QString &jid, QObject *const parent) : + QXmppClient(parent), + jid(jid), + db(this->jid), + atm_db(this->jid, db), + atm(&atm_db), + omemo_db(db), + omemo(&omemo_db) +{ + addExtension(&atm); + addExtension(&carbon); + addExtension(&mam); + addExtension(&pubsub); + addExtension(&omemo); + + connect(this, &QXmppClient::connected, this, + [this]() + { + omemo.setUp().then(this, + [](const bool &&result) + { + qDebug() << "setUp result: " << result; + }); + }); + + omemo.setSecurityPolicy(QXmpp::TrustSecurityPolicy::Toakafa); + omemo.load().then(this, + [=](const bool &&result) + { + qDebug() << "load result: " << result; + }); + + logger()->setLoggingType(QXmppLogger::SignalLogging); + logger()->setMessageTypes(QXmppLogger::DebugMessage); + + connect(logger(), &QXmppLogger::message, this, + [=] (QXmppLogger::MessageType type, const QString &text) + { + std::cerr << qPrintable(text) << std::endl; + }); +} + +QString Client::jidBare() +{ + return configuration().jidBare(); +} + +const JidDb &Client::database() const +{ + return db; +} + +JidDb &Client::database() +{ + return db; +} diff --git a/client.h b/client.h new file mode 100644 index 0000000..3da53e8 --- /dev/null +++ b/client.h @@ -0,0 +1,40 @@ +#ifndef CLIENT_H +#define CLIENT_H + +#include "atm_db.h" +#include "jiddb.h" +#include "omemo_db.h" +#include "trust_db.h" +#include <QObject> +#include <QString> +#include <QXmppAtmManager.h> +#include <QXmppCarbonManagerV2.h> +#include <QXmppClient.h> +#include <QXmppMamManager.h> +#include <QXmppOmemoManager.h> +#include <QXmppOmemoStorage.h> +#include <QXmppPubSubManager.h> +#include <QXmppTrustManager.h> +#include <QXmppTrustStorage.h> + +class Client : public QXmppClient +{ +public: + Client(const QString &jid, QObject *parent = nullptr); + QString jidBare(); + const JidDb &database() const; + JidDb &database(); + +private: + QXmppCarbonManagerV2 carbon; + QXmppMamManager mam; + const QString jid; + JidDb db; + AtmDb atm_db; + QXmppAtmManager atm; + QXmppPubSubManager pubsub; + OmemoDb omemo_db; + QXmppOmemoManager omemo; +}; + +#endif diff --git a/credentials.cpp b/credentials.cpp new file mode 100644 index 0000000..5cd6e6f --- /dev/null +++ b/credentials.cpp @@ -0,0 +1,218 @@ +#include "credentials.h" +#include <qt5keychain/keychain.h> +#include <QXmppPromise.h> +#include <QXmppConfiguration.h> +#include <iostream> + +static const QString service = "xxcc", sep = ";"; + +QXmppTask<Credentials::PairListResult> Credentials::load( + Credentials::PairList &pairs, + QStringList::const_iterator &it, + QStringList::const_iterator end) +{ + if (it == end) + return QXmpp::Private::makeReadyTask<PairListResult>( + PairListResult(pairs)); + + QXmppPromise<PairListResult> promise; + + load(*it).then(this, + [=] (LoadResult &&result) mutable + { + if (std::holds_alternative<QString>(result)) + { + pairs << Pair(*it, std::get<QString>(result)); + + load(pairs, ++it, end).then(this, + [=] (PairListResult &&result) mutable + { + promise.finish(result); + }); + } + else if (std::holds_alternative<Error>(result)) + promise.finish(Error{std::get<Error>(result)}); + }); + + return promise.task(); +} + +QXmppTask<Credentials::PairListResult> Credentials::load() +{ + QXmppPromise<PairListResult> promise; + + storedUsers().then(this, + [=] (Users &&result) mutable + { + if (std::holds_alternative<QStringList>(result)) + { + PairList pairs; + auto list = new QStringList(std::get<QStringList>(result)); + auto begin = list->cbegin(); + const auto end = list->cend(); + + load(pairs, begin, end).then(this, + [=] (PairListResult &&result) mutable + { + delete list; + promise.finish(result); + }); + } + else if (std::holds_alternative<Error>(result)) + promise.finish(Error{std::get<Error>(result)}); + }); + + return promise.task(); +} + +QXmppTask<Credentials::StoreResult> Credentials::store(Client *c) +{ + QXmppPromise<StoreResult> promise; + const QXmppConfiguration &cfg = c->configuration(); + const QString user = cfg.jidBare(); + + store(user, cfg.password()).then(this, + [=] (StoreResult &&result) mutable + { + store(user).then(this, + [=] (StoreResult &&result) mutable + { + promise.finish(result); + }); + }); + + return promise.task(); +} + +QXmppTask<Credentials::StoreResult> Credentials::store(const QString &user) +{ + QXmppPromise<StoreResult> promise; + + storedUsersList().then(this, + [=] (LoadResult &&result) mutable + { + if (std::holds_alternative<QString>(result)) + { + auto &list = std::get<QString>(result); + + if (!list.isEmpty()) + list += sep; + + list += user; + store("users", list).then(this, + [=] (StoreResult &&result) mutable + { + promise.finish(result); + }); + } + else if (std::holds_alternative<Error>(result)) + { + store("users", user).then(this, + [=] (StoreResult &&result) mutable + { + promise.finish(result); + }); + } + }); + + return promise.task(); +} + +QXmppTask<Credentials::LoadResult> Credentials::storedUsersList() +{ + QXmppPromise<LoadResult> promise; + + load("users").then(this, + [=] (LoadResult &&result) mutable + { + promise.finish(result); + }); + + return promise.task(); + +} + +QXmppTask<Credentials::LoadResult> Credentials::load(const QString &key) +{ + QXmppPromise<LoadResult> promise; + auto job = new QKeychain::ReadPasswordJob(service); + + job->setKey(key); + job->connect(job, &QKeychain::Job::finished, this, + [=] (QKeychain::Job *const job) mutable + { + auto readjob = dynamic_cast<QKeychain::ReadPasswordJob *>(job); + + if (readjob->error()) + { + const auto error = "Failed to load " + key + ": " + + readjob->errorString(); + + std::cerr << qPrintable(error) << std::endl; + promise.finish(Error{error}); + } + else + { + auto users = readjob->textData(); + + promise.finish(QString(users)); + } + }); + + job->start(); + return promise.task(); +} + +QXmppTask<Credentials::StoreResult> Credentials::store(const QString &key, + const QString &value) +{ + QXmppPromise<StoreResult> promise; + auto job = new QKeychain::WritePasswordJob(service); + + job->setKey(key); + job->setTextData(value); + job->connect(job, &QKeychain::Job::finished, this, + [=] (QKeychain::Job *const job) mutable + { + if (job->error()) + { + StoreResult sr; + + const auto error = "Failed to store " + key + ": " + + job->errorString(); + + std::cerr << qPrintable(error) << std::endl; + sr = Error{error}; + promise.finish(StoreResult(sr)); + } + else + promise.finish(Success{}); + }); + + job->start(); + return promise.task(); +} + +QXmppTask<Credentials::Users> Credentials::storedUsers() +{ + QXmppPromise<Users> promise; + + storedUsersList().then(this, + [=] (LoadResult &&result) mutable + { + if (std::holds_alternative<QString>(result)) + { + const auto &value = std::get<QString>(result); + + promise.finish(QStringList(value.split(sep))); + } + else if (std::holds_alternative<Error>(result)) + { + const auto &error = std::get<Error>(result); + + promise.finish(Error{error.description}); + } + }); + + return promise.task(); +} diff --git a/credentials.h b/credentials.h new file mode 100644 index 0000000..100729b --- /dev/null +++ b/credentials.h @@ -0,0 +1,45 @@ +#ifndef CREDENTIALS_H +#define CREDENTIALS_H + +#include "client.h" +#include <QXmppTask.h> +#include <QXmppPromise.h> +#include <QObject> +#include <QList> +#include <QPair> +#include <QStringList> +#include <variant> + +class Credentials : public QObject +{ + Q_OBJECT +public: + struct Success {}; + struct Error + { + QString description; + }; + + using Pair = QPair<QString, QString>; + using PairList = QList<Pair>; + using StoreResult = std::variant<Success, Error>; + using LoadResult = std::variant<QString, Error>; + using PairListResult = std::variant<PairList, Error>; + + QXmppTask<PairListResult> load(); + QXmppTask<StoreResult> store(Client *c); + +private: + using Users = std::variant<QStringList, Error>; + + QXmppTask<StoreResult> store(const QString &key, const QString &value); + QXmppTask<StoreResult> store(const QString &user); + QXmppTask<LoadResult> load(const QString &key); + QXmppTask<LoadResult> storedUsersList(); + QXmppTask<Users> storedUsers(); + QXmppTask<PairListResult> load(PairList &pairs, + QStringList::const_iterator &it, + QStringList::const_iterator end); +}; + +#endif diff --git a/direction.h b/direction.h new file mode 100644 index 0000000..aed3bdf --- /dev/null +++ b/direction.h @@ -0,0 +1,6 @@ +#ifndef DIRECTION_H +#define DIRECTION_H + +enum Direction {In, Out}; + +#endif diff --git a/jiddb.cpp b/jiddb.cpp new file mode 100644 index 0000000..53d77e0 --- /dev/null +++ b/jiddb.cpp @@ -0,0 +1,912 @@ +#include "jiddb.h" +#include <QDir> +#include <QSqlQuery> +#include <QSqlError> +#include <QStandardPaths> +#include <QTimeZone> +#include <QVariant> +#include <iostream> +#include <stdexcept> + +JidDb::JidDb(const QString &jid) : + jid(jid), + db(QSqlDatabase::addDatabase("QSQLITE")) +{ + const auto path = QStandardPaths::writableLocation( + QStandardPaths::AppDataLocation); + const QDir dir; + + if (!dir.exists(path) && !dir.mkdir(path)) + throw std::runtime_error("Failed to create app dir"); + + const auto abspath = path + "/" + jid + ".db"; + + db.setDatabaseName(abspath); + + if (!db.open()) + throw std::runtime_error(qPrintable("database" + abspath + + "could not be created")); + + QSqlQuery q(db); + + if (!q.exec("create table if not exists roster " + "(jid TEXT unique) strict;")) + std::cerr << "JidDb::JidDb: query exec failed" << std::endl; +} + +int JidDb::addToRoster(const QString &jid) +{ + QSqlQuery q(db); + + if (!q.exec("insert or ignore into roster (jid) values ('" + jid + "');")) + { + std::cerr << "JidDb::addToRoster: query exec failed" << std::endl; + return -1; + } + + Q_EMIT addedToRoster(jid); + return 0; +} + +int JidDb::addToRoster(const QStringList &roster) +{ + for (const auto &r : roster) + if (addToRoster(r)) + return -1; + + return 0; +} + +QStringList JidDb::roster() const +{ + QStringList ret; + QSqlQuery q(db); + + if (!q.exec("select jid from roster;")) + std::cerr << "query exec failed" << std::endl; + else + while (q.next()) + ret.append(q.value("jid").toString()); + + return ret; +} + +int JidDb::ensureContactTable(const QString &jid) const +{ + QSqlQuery q(db); + + if (!q.exec("create table if not exists '" + jid + + "' (direction TEXT, time INTEGER, body TEXT) strict;")) + { + std::cerr << "JidDb::storeMessage: query exec failed" << std::endl; + return -1; + } + + return 0; +} + +static QString securityPolicyTableName(const QString &encryption) +{ + return "securityPolicy/" + encryption; +} + +int JidDb::ensureSecurityPolicyTable(const QString &encryption, + QString &table) const +{ + QSqlQuery q(db); + + table = securityPolicyTableName(encryption); + + if (!q.exec("create table if not exists '" + table + + "' (policy TEXT unique) strict;")) + { + std::cerr << "JidDb::ensureSecurityPolicyTable: query exec failed" + << std::endl; + return -1; + } + + return 0; +} + +static QString keysTableName(const QString &encryption) +{ + return "keys/" + encryption; +} + +int JidDb::ensureKeysTable(const QString &encryption, QString &table) const +{ + QSqlQuery q(db); + + table = keysTableName(encryption); + + if (!q.exec("create table if not exists '" + table + + "' (owner TEXT, trustlevel TEXT, key TEXT) strict;")) + { + std::cerr << "JidDb::ensureKeysTable: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return -1; + } + + return 0; +} + +QList<JidDb::Message> JidDb::messages(const QString &jid, + const int tail) const +{ + QSqlQuery q(db); + + if (ensureContactTable(jid)) + return QList<Message>(); + else if (tail < 0) + { + if (!q.exec("select * from '" + jid + "' order by time;")) + { + std::cerr << "JidDb::messages: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return QList<Message>(); + } + } + else if (!q.exec("select * from '" + jid + "' order by time desc limit " + + QString::number(tail) + ";")) + { + std::cerr << "JidDb::messages: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return QList<Message>(); + } + + QList<Message> ret; + + while (q.next()) + { + Message m; + bool ok; + qlonglong t = q.value("time").toLongLong(&ok); + + if (!ok) + { + std::cerr << "JidDb::messages: invalid time" << std::endl; + // Attempt to read other messages. + continue; + } + + m.body = q.value("body").toString(); + m.dt = QDateTime::fromSecsSinceEpoch(t, QTimeZone::systemTimeZone()); + + const auto dirstr = q.value("direction").toString(); + + if (dirstr == "in") + m.direction = Direction::In; + else if (dirstr == "out") + m.direction = Direction::Out; + else + { + std::cerr << "JidDb::messages: invalid direction" << std::endl; + // Attempt to read other messages. + continue; + } + + ret << m; + } + + return ret; +} + +int JidDb::store(const JidDb::Message &msg) const +{ + QSqlQuery q(db); + QString dir; + + switch (msg.direction) + { + case Direction::In: + dir = "in"; + break; + + case Direction::Out: + dir = "out"; + break; + } + + ensureContactTable(msg.contact); + + if (!q.exec("insert into '" + msg.contact + + "' (direction, time, body) values " + "('" + dir + "', " + + QString::number(msg.dt.toSecsSinceEpoch()) + ", " + "'" + msg.body + "')")) + { + std::cerr << "JidDb::storeMessage: query exec 2 failed" << std::endl; + return -1; + } + + return 0; +} + +QStringList JidDb::tables() const +{ + QSqlQuery q(db); + + if (!q.exec("select name from sqlite_schema where " + "type = 'table' and name not like 'sqlite_%'")) + { + std::cerr << "JidDb::conversations: query failed" << std::endl; + return QStringList(); + } + + QStringList ret; + + while (q.next()) + ret << q.value("name").toString(); + + return ret; +} + +QList<JidDb::Conversation> JidDb::conversations() const +{ + const auto conversations = tables(); + QList<Conversation> ret; + + for (const auto &jid : conversations) + if (jid.contains('@')) + { + const auto msgs = messages(jid, 1); + + if (!msgs.isEmpty()) + { + const auto &m = msgs.first(); + + ret << Conversation(jid, m.body, m.dt); + } + } + + return ret; +} + +QString JidDb::securityPolicy(const QString &encryption) const +{ + const auto tb = tables(); + const auto name = securityPolicyTableName(encryption); + + for (const auto &t : tb) + { + if (t == name) + { + QStringList ret; + QSqlQuery q(db); + + if (!q.exec("select policy from '" + name + "';")) + std::cerr << "query exec failed" << std::endl; + else + while (q.next()) + return q.value("policy").toString(); + + break; + } + } + + return QString(); +} + +std::optional<QXmppOmemoStorage::OwnDevice> JidDb::omemoOwnDevice() const +{ + return std::optional<QXmppOmemoStorage::OwnDevice>(); +} + +int JidDb::store(const QString &encryption, const QString &policy) const +{ + QString table; + + if (ensureSecurityPolicyTable(encryption, table)) + return -1; + + QSqlQuery q(db); + + if (!q.exec("insert or ignore into '" + table + + "' (policy) values ('" + policy + "');")) + { + std::cerr << "JidDb::store: query exec failed" + << std::endl; + return -1; + } + + return 0; +} + +QList<JidDb::Keys> JidDb::keys(const QString &encryption) const +{ + QList<Keys> ret; + QString table; + + if (ensureKeysTable(encryption, table)) + { + std::cerr << "JidDb::keys: ensureKeysTable failed" << std::endl; + return QList<Keys>(); + } + + QSqlQuery q(db); + + if (!q.exec("select owner, trustlevel, key from '" + table + "';")) + std::cerr << "JidDb::keys: query exec failed" << std::endl; + else + while (q.next()) + { + const auto owner = q.value("owner").toString(); + const auto trust_level = q.value("trustlevel").toString(); + const auto key_b64 = q.value("key").toString(); + const auto key = QByteArray::fromBase64(key_b64.toLatin1()); + bool found = false; + + for (auto &k : ret) + if (k.owner == owner) + { + k.keys.append(key); + found = true; + break; + } + + if (!found) + { + Keys keys; + + keys.owner = owner; + keys.trust_level = trust_level; + keys.keys.append(key); + ret.append(keys); + } + } + + return ret; +} + +int JidDb::store(const QString &encryption, const JidDb::Keys &keys) const +{ + QString table; + + if (ensureKeysTable(encryption, table)) + { + std::cerr << "JidDb::store: ensureKeysTable failed" << std::endl; + return -1; + } + + for (const auto &k : keys.keys) + { + QSqlQuery q(db); + + const auto kstr = k.isEmpty() ? "\"\"" + : "'" + QString::fromLatin1(k.toBase64()) + "'"; + + if (!q.exec("insert or replace into '" + table + + "' (owner, trustlevel, key) values (" + "'" + keys.owner + "', " + "'" + keys.trust_level + "'," + + kstr + + ");")) + { + std::cerr << "JidDb::store: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return -1; + } + } + + return 0; +} + +int JidDb::ensureOmemoDeviceTable() const +{ + QSqlQuery q(db); + + if (!q.exec("create table if not exists 'omemo/devices' " + "(jid TEXT, deviceID INTEGER, keyID TEXT, session TEXT, " + "unresp_sent INTEGER, unresp_recv INTEGER, removal INTEGER) " + "strict;")) + { + std::cerr << "JidDb::ensureOmemoTable: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return -1; + } + + return 0; +} + +int JidDb::store(const QString &jid, const uint32_t id, + const QXmppOmemoStorage::Device &d) const +{ + if (ensureOmemoDeviceTable()) + { + std::cerr << "JidDb::store: ensureOmemoTable failed" << std::endl; + return -1; + } + + QSqlQuery q(db); + const auto keyId = d.keyId.isEmpty() ? "\"\"" + : "'" + QString::fromLatin1(d.keyId.toBase64()) + "'", + session = d.session.isEmpty() ? "\"\"" + : "'" + QString::fromLatin1(d.session.toBase64()) + "'", + query = "insert or replace into 'omemo/devices' " + "(jid, deviceID, keyID, session, " + "unresp_sent, unresp_recv , removal) values (" + "'" + jid + "', " + + QString::number(id) + ", " + + keyId + ", " + + session + ", " + + QString::number(d.unrespondedSentStanzasCount) + ", " + + QString::number(d.unrespondedReceivedStanzasCount) + ", " + + QString::number(d.removalFromDeviceListDate.toSecsSinceEpoch()) + + ");"; + + if (!q.exec(query)) + { + std::cerr << "JidDb::store(id, jid, d): query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return -1; + } + + return 0; +} + +int JidDb::ensureOmemoSignedPreKeyTable() const +{ + QSqlQuery q(db); + + if (!q.exec("create table if not exists 'omemo/signedprekeys' " + "(id INTEGER, creation INTEGER, data TEXT) strict;")) + { + std::cerr << "JidDb::ensureOmemoSignedPreKeyTable: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return -1; + } + + return 0; +} + +int JidDb::store(uint32_t id, const QXmppOmemoStorage::SignedPreKeyPair &spk) + const +{ + if (ensureOmemoSignedPreKeyTable()) + { + std::cerr << "JidDb::store: ensureOmemoSignedPreKeyTable failed" + << std::endl; + return -1; + } + + QSqlQuery q(db); + const auto data = spk.data.isEmpty() ? "\"\"" + : "'" + QString::fromLatin1(spk.data.toBase64()) + "'", + query = "insert or replace into 'omemo/signedprekeys' " + "(id, creation, data) values (" + + QString::number(id) + ", " + + QString::number(spk.creationDate.toSecsSinceEpoch()) + ", " + + data + + ");"; + + if (!q.exec(query)) + { + std::cerr << "JidDb::store(spk): query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return -1; + } + + return 0; +} + +int JidDb::ensureOmemoPreKeyTable() const +{ + QSqlQuery q(db); + + if (!q.exec("create table if not exists 'omemo/prekeys' " + "(id INTEGER, data TEXT) strict;")) + { + std::cerr << "JidDb::ensureOmemoPreKeyTable: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return -1; + } + + return 0; +} + +int JidDb::store(const QHash<uint32_t, QByteArray> &pairs) const +{ + if (ensureOmemoPreKeyTable()) + { + std::cerr << "JidDb::store: ensureOmemoPreKeyTable failed" + << std::endl; + return -1; + } + + for (const auto id : pairs.keys()) + { + const auto data = pairs.value(id); + const auto datastr = data.isEmpty() ? "\"\"" + : "'" + QString::fromLatin1(data.toBase64()) + "'", + query = "insert or replace into 'omemo/prekeys' " + "(id, data) values (" + + QString::number(id) + ", " + + datastr + + ");"; + + QSqlQuery q(db); + + if (!q.exec(query)) + { + std::cerr << "JidDb::store(pairs): query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return -1; + } + } + + return 0; +} + +int JidDb::ensureOmemoOwnDeviceTable() const +{ + QSqlQuery q(db); + + if (!q.exec("create table if not exists 'omemo/owndevice' " + "(id INTEGER, label TEXT, privkey TEXT, pubkey TEXT, " + "latestSignedPreKeyId INTEGER, latestPreKeyId INTEGER) strict;")) + { + std::cerr << "JidDb::ensureOmemoOwnDeviceTable: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return -1; + } + + return 0; +} + +int JidDb::store(const QXmppOmemoStorage::OwnDevice &own) const +{ + if (ensureOmemoOwnDeviceTable()) + { + std::cerr << "JidDb::store: ensureOmemoOwnDeviceTable failed" + << std::endl; + return -1; + } + + QSqlQuery q(db); + const auto privkey = own.privateIdentityKey.isEmpty() ? "\"\"" + : "'" + QString::fromLatin1(own.privateIdentityKey.toBase64()) + "'", + pubkey = own.publicIdentityKey.isEmpty() ? "\"\"" + : "'" + QString::fromLatin1(own.publicIdentityKey.toBase64()) + "'", + query = "insert or replace into 'omemo/owndevice' " + "(id, label, privkey, pubkey, latestSignedPreKeyId, " + "latestPreKeyId) values (" + + QString::number(own.id) + ", " + "'" + own.label + "', " + + privkey + ", " + + pubkey + ", " + + QString::number(own.latestSignedPreKeyId) + ", " + + QString::number(own.latestPreKeyId) + + ");"; + + if (!q.exec(query)) + { + std::cerr << "JidDb::store(own): query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + std::cerr << "Query was: " << qPrintable(query) << std::endl; + return -1; + } + + return 0; +} + +QList<JidDb::OmemoDevice> JidDb::omemoDevices() const +{ + QList<OmemoDevice> ret; + QSqlQuery q(db); + + if (!q.exec("select * from 'omemo/devices';")) + { + std::cerr << "JidDb::omemoDevices: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return QList<OmemoDevice>(); + } + + while (q.next()) + { + QXmppOmemoStorage::Device device; + bool ok; + const uint32_t deviceId = q.value("deviceID").toULongLong(&ok); + + if (!ok) + { + std::cerr << "JidDb::messages: invalid deviceID" << std::endl; + // Attempt to read other messages. + continue; + } + + device.unrespondedReceivedStanzasCount = + q.value("unresp_recv").toInt(&ok); + + if (!ok) + { + std::cerr << "JidDb::messages: invalid unresp_recv" << std::endl; + // Attempt to read other messages. + continue; + } + + device.unrespondedSentStanzasCount = q.value("unresp_sent").toInt(&ok); + + if (!ok) + { + std::cerr << "JidDb::messages: invalid unresp_sent" << std::endl; + // Attempt to read other messages. + continue; + } + + const QByteArray keyId = q.value("keyID").toByteArray(), + session = q.value("session").toByteArray(); + const auto jid = q.value("jid").toString(); + bool found = false; + + for (auto &r : ret) + if (jid == r.jid) + { + r.devices.insert(deviceId, device); + break; + } + + if (!found) + { + OmemoDevice d; + + d.jid = jid; + d.devices.insert(deviceId, device); + ret << d; + } + } + + return ret; +} + +QHash<uint32_t, QXmppOmemoStorage::SignedPreKeyPair> JidDb::signedPreKeyPairs() + const +{ + QHash<uint32_t, QXmppOmemoStorage::SignedPreKeyPair> ret; + + QSqlQuery q(db); + + if (!q.exec("select * from 'omemo/signedprekeys';")) + { + std::cerr << "JidDb::signedPreKeyPairs: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return QHash<uint32_t, QXmppOmemoStorage::SignedPreKeyPair>(); + } + + while (q.next()) + { + QXmppOmemoStorage::SignedPreKeyPair spk; + bool ok; + const uint32_t id = q.value("id").toULongLong(&ok); + + if (!ok) + { + std::cerr << "JidDb::messages: invalid id" << std::endl; + // Attempt to read other entries. + continue; + } + + const auto creation = q.value("creation").toInt(&ok); + + if (!ok) + { + std::cerr << "JidDb::messages: invalid creation date" << std::endl; + // Attempt to read other entries. + continue; + } + + spk.creationDate = QDateTime::fromSecsSinceEpoch(creation); + spk.data = q.value("data").toString().toLatin1().toBase64(); + ret.insert(id, spk); + } + + return ret; +} + +QHash<uint32_t, QByteArray> JidDb::preKeyPairs() const +{ + QHash<uint32_t, QByteArray> ret; + + QSqlQuery q(db); + + if (!q.exec("select * from 'omemo/signedprekeys';")) + { + std::cerr << "JidDb::signedPreKeyPairs: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return QHash<uint32_t, QByteArray>(); + } + + while (q.next()) + { + bool ok; + const uint32_t id = q.value("id").toULongLong(&ok); + + if (!ok) + { + std::cerr << "JidDb::messages: invalid id" << std::endl; + // Attempt to read other entries. + continue; + } + + const auto data = q.value("data").toString().toLatin1().toBase64(); + ret.insert(id, data); + } + + return ret; +} + +void JidDb::removeKeys(const QString &encryption) const +{ + QString table; + + if (ensureKeysTable(encryption, table)) + std::cerr << "JidDb::removeKeys: ensureKeysTable failed" << std::endl; + else + { + QSqlQuery q(db); + + if (!q.exec("delete from '" + table + "'")) + std::cerr << "JidDb::removeKeys: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + } +} + +void JidDb::removeOmemoDevice(const QString &jid, const uint32_t id) const +{ + if (ensureOmemoDeviceTable()) + std::cerr << "JidDb::removeOmemoDevice: ensureOmemoDeviceTable failed" + << std::endl; + else + { + QSqlQuery q(db); + const auto idstr = QString::number(id); + + if (!q.exec("delete from 'omemo/devices' " + "where (jid = '" + jid + "' " + "and id = " + idstr + ")")) + std::cerr << "JidDb::removeOmemoDevice: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + } +} + +void JidDb::removeOmemoDevices(const QString &jid) const +{ + if (ensureOmemoDeviceTable()) + std::cerr << "JidDb::removeOmemoDevices: ensureOmemoDeviceTable failed" + << std::endl; + else + { + QSqlQuery q(db); + + if (!q.exec("delete from 'omemo/devices' " + "where jid = '" + jid + "'")) + std::cerr << "JidDb::removeOmemoDevices: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + } +} + +int JidDb::ensureOwnIdTable(const QString &encryption, QString &table) + const +{ + QSqlQuery q(db); + + table = encryption + "/ownID"; + + if (!q.exec("create table if not exists '" + table + "' " + "(ID TEXT) strict;")) + { + std::cerr << "JidDb::ensureOwnIdTable: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return -1; + } + + return 0; +} + +QByteArray JidDb::ownKeyId(const QString &encryption) const +{ + QString table; + + if (ensureOwnIdTable(encryption, table)) + { + std::cerr << "JidDb::store: ensureOwnIdTable failed" << std::endl; + return QByteArray(); + } + + QSqlQuery q(db); + + if (!q.exec("select * from '" + table + "';")) + { + std::cerr << "JidDb::ownKeyId: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + return QByteArray(); + } + + while (q.next()) + { + const auto id = q.value("id").toString(); + + return QByteArray::fromBase64(id.toLatin1()); + } + + return QByteArray(); +} + +int JidDb::store(const QString &encryption, const QByteArray &ownId) const +{ + QString table; + + if (ensureOwnIdTable(encryption, table)) + { + std::cerr << "JidDb::store: ensureOmemoOwnDeviceTable failed" + << std::endl; + return -1; + } + + QSqlQuery q(db); + const auto id = ownId.isEmpty() ? "\"\"" + : "'" + QString::fromLatin1(ownId.toBase64()) + "'", + query = "insert or replace into '" + table + "' " + "(id) values (" + + id + + ");"; + + return 0; +} + +void JidDb::removeSignedPreKeyPair(const uint32_t id) const +{ + if (ensureOmemoSignedPreKeyTable()) + std::cerr << "JidDb::removeSignedPreKeyPair: " + "ensureOmemoSignedPreKeyTable failed" << std::endl; + else + { + QSqlQuery q(db); + const auto idstr = QString::number(id); + + if (!q.exec("delete from 'omemo/signedprekeys' where id = " + idstr)) + std::cerr << "JidDb::removeSignedPreKeyPair: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + } +} + +void JidDb::removePreKeyPair(const uint32_t id) const +{ + if (ensureOmemoPreKeyTable()) + std::cerr << "JidDb::removePreKeyPair: " + "ensureOmemoPreKeyTable failed" << std::endl; + else + { + QSqlQuery q(db); + const auto idstr = QString::number(id); + + if (!q.exec("delete from 'omemo/prekeys' where id = " + idstr)) + std::cerr << "JidDb::removePreKeyPair: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + } +} + +void JidDb::removeOmemo() const +{ + if (ensureOmemoSignedPreKeyTable()) + std::cerr << "JidDb::removeOmemo: " + "ensureOmemoSignedPreKeyTable failed" << std::endl; + else if (ensureOmemoPreKeyTable()) + std::cerr << "JidDb::removeOmemo: " + "ensureOmemoPreKeyTable failed" << std::endl; + else + { + QSqlQuery q(db); + + if (!q.exec("delete from 'omemo/signedprekeys'")) + std::cerr << "JidDb::removeOmemo: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + else + { + QSqlQuery q(db); + + if (!q.exec("delete from 'omemo/prekeys'")) + std::cerr << "JidDb::removeOmemo: query exec failed: " + << qPrintable(q.lastError().text()) << std::endl; + } + } +} @@ -0,0 +1,114 @@ +#ifndef JID_DB_H +#define JID_DB_H + +#include "direction.h" +#include <QByteArray> +#include <QDateTime> +#include <QList> +#include <QObject> +#include <QPair> +#include <QString> +#include <QStringList> +#include <QSqlDatabase> +#include <QXmppOmemoStorage.h> +#include <QVariant> +#include <optional> + +class JidDb : public QObject +{ + Q_OBJECT + +public: + struct Message + { + Direction direction; + QDateTime dt; + QString contact, body; + }; + + struct Conversation + { + Conversation(const QString &to, const QString &last_msg, + const QDateTime &dt) : + to(to), last_msg(last_msg), dt(dt) {} + QString to, last_msg; + QDateTime dt; + }; + + struct Trust + { + QString security_policy; + }; + + struct Keys + { + Keys() {} + Keys(const QString &owner, const QString &trust_level, + const QList<QByteArray> &keys) : + owner(owner), + trust_level(trust_level), + keys(keys) + {} + QString owner, trust_level; + QList<QByteArray> keys; + }; + + struct OmemoDevice + { + QString jid; + QHash<uint32_t, QXmppOmemoStorage::Device> devices; + }; + + JidDb(const QString &jid); + QStringList roster() const; + const QString &jid; + +public Q_SLOTS: + QList<Conversation> conversations() const; + QList<Message> messages(const QString &jid, + int tail = -1) const; + QList<Keys> keys(const QString &encryption) const; + QString securityPolicy(const QString &encryption) const; + QList<OmemoDevice> omemoDevices() const; + std::optional<QXmppOmemoStorage::OwnDevice> omemoOwnDevice() const; + QHash<uint32_t, QXmppOmemoStorage::SignedPreKeyPair> signedPreKeyPairs() + const; + QHash<uint32_t, QByteArray> preKeyPairs() const; + QByteArray ownKeyId(const QString &encryption) const; + int addToRoster(const QString &jid); + int addToRoster(const QStringList &roster); + int store(const Message &msg) const; + int store(const QString &encryption, + const QString &securityPolicy) const; + int store(const QString &encryption, const Keys &keys) const; + int store(const QString &jid, uint32_t id, + const QXmppOmemoStorage::Device &d) const; + int store(uint32_t id, const QXmppOmemoStorage::SignedPreKeyPair &spk) const; + int store(const QHash<uint32_t, QByteArray> &pairs) const; + int store(const QXmppOmemoStorage::OwnDevice &own) const; + int store(const QString &encryption, const QByteArray &ownId) const; + void removeKeys(const QString &encryption) const; + void removeOmemoDevice(const QString &jid, uint32_t id) const; + void removeOmemoDevices(const QString &jid) const; + void removeOmemo() const; + void removeSignedPreKeyPair(uint32_t id) const; + void removePreKeyPair(uint32_t id) const; + +Q_SIGNALS: + void addedToRoster(QString jid); + +private: + QSqlDatabase db; + int ensureSecurityPolicyTable(const QString &encryption, + QString &table) const; + int ensureContactTable(const QString &jid) const; + int ensureKeysTable(const QString &encryption, QString &table) const; + int ensureOmemoDeviceTable() const; + int ensureOmemoSignedPreKeyTable() const; + int ensureOmemoPreKeyTable() const; + int ensureOmemoOwnDeviceTable() const; + int ensureOwnIdTable(const QString &encryption, QString &table) const; + QStringList tables() const; +}; + +#endif diff --git a/libomemo-c/CMakeLists.txt b/libomemo-c/CMakeLists.txt new file mode 100644 index 0000000..ba52fed --- /dev/null +++ b/libomemo-c/CMakeLists.txt @@ -0,0 +1,23 @@ +find_package(PkgConfig REQUIRED) +pkg_check_modules(protobuf-c REQUIRED IMPORTED_TARGET libprotobuf-c) +set(CMAKE_POLICY_DEFAULT_CMP0048 NEW) +add_subdirectory(libomemo-c) +target_include_directories(omemo-c PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/libomemo-c/src> + $<INSTALL_INTERFACE:libomemo-c/src>) +target_link_libraries(omemo-c PRIVATE PkgConfig::protobuf-c) +export(TARGETS omemo-c FILE Findomemo-c.cmake) + +install( + TARGETS omemo-c + EXPORT OmemoCTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install( + EXPORT OmemoCTargets + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/libomemo-c" + FILE Findomemo-c.cmake +) diff --git a/omemo_db.cpp b/omemo_db.cpp new file mode 100644 index 0000000..c1949c0 --- /dev/null +++ b/omemo_db.cpp @@ -0,0 +1,93 @@ +#include "omemo_db.h" +#include <QXmppConfiguration.h> +#include <QXmppFutureUtils_p.h> +#include <QString> +#include <iostream> +#include <optional> + +static const QString service = "xxcc", key_namespace = "omemo"; + +OmemoDb::OmemoDb(const JidDb &db) : + db(db) +{ +} + +QXmppTask<QXmppOmemoStorage::OmemoData> OmemoDb::allData() +{ + QXmppOmemoStorage::OmemoData ret; + QHash<QString, QHash<uint32_t, Device>> devices; + + for (const auto &d : db.omemoDevices()) + ret.devices.insert(d.jid, d.devices); + + ret.ownDevice = db.omemoOwnDevice(); + ret.preKeyPairs = db.preKeyPairs(); + ret.signedPreKeyPairs = db.signedPreKeyPairs(); + return QXmpp::Private::makeReadyTask(QXmppOmemoStorage::OmemoData(ret)); +} + +QXmppTask<void> OmemoDb::setOwnDevice(const std::optional<OwnDevice> &device) +{ + if (device.has_value() && db.store(device.value())) + std::cerr << "db.store failed" << std::endl; + + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> OmemoDb::addSignedPreKeyPair(const uint32_t keyId, + const SignedPreKeyPair &keyPair) +{ + if (db.store(keyId, keyPair)) + std::cerr << "db.store failed" << std::endl; + + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> OmemoDb::removeSignedPreKeyPair(uint32_t keyId) +{ + db.removeSignedPreKeyPair(keyId); + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> OmemoDb::addPreKeyPairs( + const QHash<uint32_t, QByteArray> &keyPairs) +{ + if (db.store(keyPairs)) + std::cerr << "db.store failed" << std::endl; + + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> OmemoDb::removePreKeyPair(const uint32_t keyId) +{ + db.removePreKeyPair(keyId); + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> OmemoDb::addDevice(const QString &jid, + const uint32_t deviceId, const Device &d) +{ + if (db.store(jid, deviceId, d)) + std::cerr << "db.store failed" << std::endl; + + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> OmemoDb::removeDevice(const QString &jid, + const uint32_t deviceId) +{ + db.removeOmemoDevice(jid, deviceId); + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> OmemoDb::removeDevices(const QString &jid) +{ + db.removeOmemoDevices(jid); + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> OmemoDb::resetAll() +{ + db.removeOmemo(); + return QXmpp::Private::makeReadyTask(); +} diff --git a/omemo_db.h b/omemo_db.h new file mode 100644 index 0000000..da3fd93 --- /dev/null +++ b/omemo_db.h @@ -0,0 +1,42 @@ +#ifndef OMEMO_DB_H +#define OMEMO_DB_H + +#include "jiddb.h" +#include <QXmppGlobal.h> +#include <QXmppOmemoStorage.h> +#include <QXmppPromise.h> +#include <QXmppTask.h> +#include <qt5keychain/keychain.h> +#include <QDateTime> +#include <QByteArray> +#include <QString> +#include <QSqlDatabase> +#include <cstdint> + +class OmemoDb : public QObject, public QXmppOmemoStorage +{ + Q_OBJECT + +public: + OmemoDb(const JidDb &db); + QXmppTask<OmemoData> allData() override; + QXmppTask<void> setOwnDevice( + const std::optional<OwnDevice> &device) override; + QXmppTask<void> addSignedPreKeyPair(uint32_t keyId, + const SignedPreKeyPair &keyPair) override; + QXmppTask<void> removeSignedPreKeyPair(uint32_t keyId) override; + QXmppTask<void> addPreKeyPairs( + const QHash<uint32_t, QByteArray> &keyPairs) override; + QXmppTask<void> removePreKeyPair(uint32_t keyId) override; + QXmppTask<void> addDevice(const QString &jid, + uint32_t deviceId, const Device &device) override; + QXmppTask<void> removeDevice(const QString &jid, + uint32_t deviceId) override; + QXmppTask<void> removeDevices(const QString &jid) override; + QXmppTask<void> resetAll() override; + +private: + const JidDb &db; +}; + +#endif diff --git a/qxmpp/CMakeLists.txt b/qxmpp/CMakeLists.txt new file mode 100644 index 0000000..0712e3e --- /dev/null +++ b/qxmpp/CMakeLists.txt @@ -0,0 +1,18 @@ +set(BUILD_SHARED OFF) +set(BUILD_OMEMO ON) +set(BUILD_TESTS OFF) +set(WITH_QCA ON) +set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) +set(QT_VERSION_MAJOR 6) +add_subdirectory(qxmpp) +target_link_libraries(${PROJECT_NAME} PRIVATE + QXmppQt${QT_VERSION_MAJOR} + QXmppOmemoQt${QT_VERSION_MAJOR}) +target_include_directories(QXmppQt${QT_VERSION_MAJOR} PRIVATE + ${CMAKE_BINARY_DIR}/qxmpp/qxmpp/src) +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_BINARY_DIR}/qxmpp/qxmpp/src) +target_include_directories(QXmppOmemoQt${QT_VERSION_MAJOR} PRIVATE + ${CMAKE_BINARY_DIR}/qxmpp/qxmpp/src/omemo) +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_BINARY_DIR}/qxmpp/qxmpp/src/omemo) diff --git a/qxmpp/qxmpp b/qxmpp/qxmpp -Subproject 6fe82239fc55b16953f965ea4e20e5fbfe806dd +Subproject d7dae544d07016cb27b07fe1a83dbd90729a131 diff --git a/trust_db.cpp b/trust_db.cpp new file mode 100644 index 0000000..3dfd906 --- /dev/null +++ b/trust_db.cpp @@ -0,0 +1,415 @@ +#include "trust_db.h" +#include <QXmppTrustLevel.h> +#include <QXmppTrustSecurityPolicy.h> +#include <QXmppConfiguration.h> +#include <QXmppFutureUtils_p.h> +#include <QXmppPromise.h> +#include <QEventLoop> +#include <QtConcurrent/QtConcurrent> +#include <iostream> + +TrustDb::TrustDb(const QString &jid, const JidDb &db) : + jid(jid), + db(db) +{ +} + +static const struct +{ + QXmpp::TrustLevel level; + const char *str; +} trustlevels[] = +{ + {QXmpp::TrustLevel::Undecided, "Undecided"}, + {QXmpp::TrustLevel::AutomaticallyDistrusted, "AutomaticallyDistrusted"}, + {QXmpp::TrustLevel::ManuallyDistrusted, "ManuallyDistrusted"}, + {QXmpp::TrustLevel::AutomaticallyTrusted, "AutomaticallyTrusted"}, + {QXmpp::TrustLevel::ManuallyTrusted, "ManuallyTrusted"}, + {QXmpp::TrustLevel::Authenticated, "Authenticated"} +}; + +static const struct +{ + QXmpp::TrustSecurityPolicy policy; + const char *str; +} tsp_levels[] = +{ + {QXmpp::NoSecurityPolicy, "NoSecurityPolicy"}, + {QXmpp::Toakafa, "Toakafa"} +}; + +static QString toString(const QXmpp::TrustSecurityPolicy securityPolicy) +{ + for (const auto &t : tsp_levels) + if (t.policy == securityPolicy) + return t.str; + + return "unknown"; +} + +static QString toString(const QXmpp::TrustLevel &trustLevel) +{ + for (const auto &t : trustlevels) + if (t.level == trustLevel) + return t.str; + + return "unknown"; +} + +static int toSecurityPolicy(const QString &s, + QXmpp::TrustSecurityPolicy &securityPolicy) +{ + for (const auto &t : tsp_levels) + if (t.str == s) + { + securityPolicy = t.policy; + return 0; + } + + return -1; +} + +static int toTrustLevel(const QString &tstr, QXmpp::TrustLevel &trustLevel) +{ + for (const auto &t : trustlevels) + if (t.str == tstr) + { + trustLevel = t.level; + return 0; + } + + return -1; +} + +QString TrustDb::service() const +{ + return "xxcc/trust/" + jid; +} + +QXmppTask<void> TrustDb::setSecurityPolicy(const QString &encryption, + const QXmpp::TrustSecurityPolicy securityPolicy) +{ + if (db.store(encryption, toString(securityPolicy))) + std::cerr << "TrustDb::setSecurityPolicy: store failed" + << std::endl; + + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> TrustDb::resetSecurityPolicy(const QString &encryption) +{ + if (db.store(encryption, QString())) + std::cerr << "TrustDb::resetSecurityPolicy: store failed" + << std::endl; + + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<QXmpp::TrustSecurityPolicy> TrustDb::securityPolicy( + const QString &encryption) +{ + const auto s = db.securityPolicy(encryption); + QXmpp::TrustSecurityPolicy policy; + + if (toSecurityPolicy(s, policy)) + std::cerr << "TrustDb::securityPolicy: toSecurityPolicy failed" + << std::endl; + + return QXmpp::Private::makeReadyTask(QXmpp::TrustSecurityPolicy(policy)); +} + +QXmppTask<void> TrustDb::setOwnKey(const QString &encryption, + const QByteArray &keyId) +{ + if (db.store(encryption, keyId)) + std::cerr << "TrustDb::setOwnKey: db.store failed" << std::endl; + + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> TrustDb::resetOwnKey(const QString &encryption) +{ + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<QByteArray> TrustDb::ownKey(const QString &encryption) +{ + return QXmpp::Private::makeReadyTask(db.ownKeyId(encryption)); +} + +QXmppTask<void> TrustDb::addKeys(const QString &encryption, + const QString &keyOwnerJid, const QList<QByteArray> &keyIds, + const QXmpp::TrustLevel trustLevel) +{ + const struct JidDb::Keys keys(keyOwnerJid, toString(trustLevel), keyIds); + + if (db.store(encryption, keys)) + std::cerr << "TrustDb::addKeys: store failed" << std::endl; + + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> TrustDb::removeKeys(const QString &encryption, + const QList<QByteArray> &keyIds) +{ + auto keys = db.keys(encryption); + + for (const auto &id : keyIds) + for (auto &k : keys) + { + const auto i = k.keys.indexOf(id); + + if (i != -1) + { + k.keys.removeAt(i); + break; + } + } + + for (const auto &k : keys) + if (db.store(encryption, k)) + std::cerr << "TrustDb::removeKeys: store failed" << std::endl; + + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> TrustDb::removeKeys(const QString &encryption, + const QString &keyOwnerJid) +{ + auto keys = db.keys(encryption); + + for (int i = 0; i < keys.count(); i++) + if (keys[i].owner == keyOwnerJid) + { + keys.removeAt(i); + break; + } + + for (const auto &k : keys) + if (db.store(encryption, k)) + std::cerr << "TrustDb::removeKeys: store failed" << std::endl; + + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<void> TrustDb::removeKeys(const QString &encryption) +{ + db.removeKeys(encryption); + return QXmpp::Private::makeReadyTask(); +} + +QXmppTask<QHash<QXmpp::TrustLevel, + QMultiHash<QString, QByteArray>>> TrustDb::keys(const QString &encryption, + const QXmpp::TrustLevels trustLevels) +{ + QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>> ret; + const auto keys = db.keys(encryption); + + for (const auto &k : keys) + { + QXmpp::TrustLevel level; + + if (toTrustLevel(k.trust_level, level)) + { + std::cerr << "TrustDb::keys: invalid trust level: " + << qPrintable(k.trust_level) << std::endl; + continue; + } + else if (!trustLevels.testFlag(level)) + continue; + + QMultiHash<QString, QByteArray> mh; + + for (const auto &key : k.keys) + mh.insert(k.owner, key); + + ret.insert(level, mh); + } + + return QXmpp::Private::makeReadyTask(QHash<QXmpp::TrustLevel, + QMultiHash<QString, QByteArray>>(ret)); +} + +QXmppTask<QHash<QString, + QHash<QByteArray, QXmpp::TrustLevel>>> TrustDb::keys(const QString &encryption, + const QList<QString> &keyOwnerJids, + const QXmpp::TrustLevels trustLevels) +{ + QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>> ret; + const auto keys = db.keys(encryption); + + for (const auto &jid : keyOwnerJids) + for (const auto &k : keys) + { + if (k.owner != jid) + continue; + + QXmpp::TrustLevel level; + + if (toTrustLevel(k.trust_level, level)) + { + std::cerr << "TrustDb::keys: invalid trust level: " + << qPrintable(k.trust_level) << std::endl; + continue; + } + else if (!trustLevels.testFlag(level)) + continue; + + QHash<QByteArray, QXmpp::TrustLevel> h; + + for (const auto &key : k.keys) + h.insert(key, level); + + ret.insert(jid, h); + break; + } + + return QXmpp::Private::makeReadyTask(QHash<QString, + QHash<QByteArray, QXmpp::TrustLevel>>(ret)); +} + +QXmppTask<bool> TrustDb::hasKey(const QString &encryption, + const QString &keyOwnerJid, QXmpp::TrustLevels trustLevels) +{ + bool ret = false; + const auto keys = db.keys(encryption); + + for (const auto &k : keys) + { + if (k.owner != keyOwnerJid) + continue; + + QXmpp::TrustLevel level; + + if (toTrustLevel(k.trust_level, level)) + { + std::cerr << "TrustDb::keys: invalid trust level: " + << qPrintable(k.trust_level) << std::endl; + break; + } + else if (!trustLevels.testFlag(level)) + break; + + ret = true; + break; + } + + return QXmpp::Private::makeReadyTask(bool(ret)); +} + +QXmppTask<QHash<QString, + QMultiHash<QString, QByteArray>>> TrustDb::setTrustLevel( + const QString &encryption, + const QMultiHash<QString, QByteArray> &keyIds, + const QXmpp::TrustLevel trustLevel) +{ + QHash<QString, QMultiHash<QString, QByteArray>> ret; + auto keys = db.keys(encryption); + const auto str = toString(trustLevel); + + for (const auto &owner : keyIds.keys()) + for (auto &k : keys) + { + if (k.owner != owner) + continue; + + k.trust_level = str; + QMultiHash<QString, QByteArray> mh; + + for (const auto &id : k.keys) + mh.insert(owner, id); + + ret.insert(owner, mh); + break; + } + + for (const auto &key : keys) + if (db.store(encryption, key)) + std::cerr << "TrustDb::setTrustLevel: store failed" + << std::endl; + + return QXmpp::Private::makeReadyTask(QHash<QString, + QMultiHash<QString, QByteArray>>(ret)); +} + +QXmppTask<QHash<QString, + QMultiHash<QString, QByteArray>>> TrustDb::setTrustLevel( + const QString &encryption, + const QList<QString> &keyOwnerJids, + const QXmpp::TrustLevel oldTrustLevel, + const QXmpp::TrustLevel newTrustLevel) +{ + QHash<QString, QMultiHash<QString, QByteArray>> ret; + auto keys = db.keys(encryption); + const auto str = toString(newTrustLevel); + + for (const auto &owner : keyOwnerJids) + for (auto &k : keys) + { + QXmpp::TrustLevel trust_level; + + if (k.owner != owner + || toTrustLevel(k.trust_level, trust_level) + || trust_level != oldTrustLevel) + continue; + + k.trust_level = str; + QMultiHash<QString, QByteArray> mh; + + for (const auto &id : k.keys) + mh.insert(owner, id); + + ret.insert(owner, mh); + break; + } + + for (const auto &key : keys) + if (db.store(encryption, key)) + std::cerr << "TrustDb::setTrustLevel: store failed" << std::endl; + + return QXmpp::Private::makeReadyTask(QHash<QString, + QMultiHash<QString, QByteArray>>(ret)); +} + +QXmppTask<QXmpp::TrustLevel> TrustDb::trustLevel(const QString &encryption, + const QString &keyOwnerJid, const QByteArray &keyId) +{ + const auto keys = db.keys(encryption); + + for (const auto &k : keys) + { + if (k.owner != keyOwnerJid) + continue; + + for (const auto &id : k.keys) + if (id == keyId) + { + QXmpp::TrustLevel ret(QXmpp::TrustLevel::Undecided); + + if (toTrustLevel(k.trust_level, ret)) + std::cerr << "toTrustLevel failed" << std::endl; + + return QXmpp::Private::makeReadyTask( QXmpp::TrustLevel(ret)); + } + } + + return QXmpp::Private::makeReadyTask(QXmpp::TrustLevel( + QXmpp::TrustLevel::Undecided)); +} + +QXmppTask<void> TrustDb::resetAll(const QString &encryption) +{ + db.removeKeys(encryption); + + return QXmpp::Private::makeReadyTask(); +} + +namespace QXmpp +{ + +uint qHash(const TrustLevel &key, uint seed) +{ + return qHash(key, seed); +} + +} diff --git a/trust_db.h b/trust_db.h new file mode 100644 index 0000000..7d0e58c --- /dev/null +++ b/trust_db.h @@ -0,0 +1,72 @@ +#ifndef TRUST_DB_H +#define TRUST_DB_H + +#include "jiddb.h" +#include <QXmppClient.h> +#include <QXmppTask.h> +#include <QXmppTrustLevel.h> +#include <QXmppTrustStorage.h> +#include <QByteArray> +#include <QHash> +#include <QList> +#include <QMultiHash> +#include <QString> + +class TrustDb : virtual public QXmppTrustStorage +{ +public: + TrustDb(const QString &jid, const JidDb &db); + + QXmppTask<void> setSecurityPolicy(const QString &encryption, + QXmpp::TrustSecurityPolicy securityPolicy) override; + QXmppTask<void> resetSecurityPolicy(const QString &encryption) override; + QXmppTask<QXmpp::TrustSecurityPolicy> securityPolicy( + const QString &encryption) override; + + QXmppTask<void> setOwnKey(const QString &encryption, + const QByteArray &keyId) override; + QXmppTask<void> resetOwnKey(const QString &encryption) override; + QXmppTask<QByteArray> ownKey(const QString &encryption) override; + + QXmppTask<void> addKeys(const QString &encryption, + const QString &keyOwnerJid, const QList<QByteArray> &keyIds, + QXmpp::TrustLevel trustLevel + = QXmpp::TrustLevel::AutomaticallyDistrusted) override; + QXmppTask<void> removeKeys(const QString &encryption, + const QList<QByteArray> &keyIds) override; + QXmppTask<void> removeKeys(const QString &encryption, + const QString &keyOwnerJid) override; + QXmppTask<void> removeKeys(const QString &encryption) override; + QXmppTask<QHash<QXmpp::TrustLevel, + QMultiHash<QString, QByteArray>>> keys(const QString &encryption, + QXmpp::TrustLevels trustLevels = {}) override; + QXmppTask<QHash<QString, + QHash<QByteArray, QXmpp::TrustLevel>>> keys(const QString &encryption, + const QList<QString> &keyOwnerJids, + QXmpp::TrustLevels trustLevels = {}) override; + QXmppTask<bool> hasKey(const QString &encryption, + const QString &keyOwnerJid, QXmpp::TrustLevels trustLevels) override; + + QXmppTask<QHash<QString, + QMultiHash<QString, QByteArray>>> setTrustLevel( + const QString &encryption, + const QMultiHash<QString, QByteArray> &keyIds, + QXmpp::TrustLevel trustLevel) override; + QXmppTask<QHash<QString, + QMultiHash<QString, QByteArray>>> setTrustLevel( + const QString &encryption, + const QList<QString> &keyOwnerJids, + QXmpp::TrustLevel oldTrustLevel, + QXmpp::TrustLevel newTrustLevel) override; + QXmppTask<QXmpp::TrustLevel> trustLevel(const QString &encryption, + const QString &keyOwnerJid, const QByteArray &keyId) override; + + QXmppTask<void> resetAll(const QString &encryption) override; + +private: + const QString &jid; + const JidDb &db; + QString service() const; +}; + +#endif |
