WIP OMEMO TrustDb/JidDb

This commit is contained in:
Xavier Del Campo Romero 2023-08-28 00:20:54 +02:00
parent d32b9e9357
commit 5bbe04c9ca
Signed by: xavi
GPG Key ID: 84FF3612A9BF43F2
15 changed files with 1488 additions and 552 deletions

View File

@ -23,6 +23,7 @@ set(PROJECT_SOURCES
account.ui
accounts.cpp
accounts.ui
atm_db.cpp
client.cpp
contact.cpp
contact.ui

42
atm_db.cpp Normal file
View File

@ -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>>());
}

37
atm_db.h Normal file
View File

@ -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

View File

@ -1,18 +1,48 @@
#include "client.h"
#include <QXmppTask.h>
#include <QDebug>
#include <iostream>
Client::Client(const QString &jid, QObject *const parent) :
QXmppClient(parent),
jid(jid),
db(jid),
trust_db(jid, db),
trust(&trust_db),
omemo_db(jid),
db(this->jid),
atm_db(this->jid, db),
atm(&atm_db),
omemo_db(db),
omemo(&omemo_db)
{
addExtension(&trust);
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()

View File

@ -1,12 +1,16 @@
#ifndef CLIENT_H
#define CLIENT_H
#include "omemo_db.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>
@ -22,10 +26,12 @@ public:
JidDb &database();
private:
QXmppCarbonManagerV2 carbon;
QXmppMamManager mam;
const QString jid;
JidDb db;
TrustDb trust_db;
QXmppTrustManager trust;
AtmDb atm_db;
QXmppAtmManager atm;
QXmppPubSubManager pubsub;
OmemoDb omemo_db;
QXmppOmemoManager omemo;

View File

@ -1,103 +1,218 @@
#include "credentials.h"
#include <QEventLoop>
#include <qt5keychain/keychain.h>
#include <QXmppPromise.h>
#include <QXmppConfiguration.h>
#include <iostream>
static const QString service = "xxcc", sep = ";";
QList<Credentials::Pair> Credentials::load()
QXmppTask<Credentials::PairListResult> Credentials::load(
Credentials::PairList &pairs,
QStringList::const_iterator &it,
QStringList::const_iterator end)
{
const QStringList users = storedUsers();
QList<Pair> ret;
if (it == end)
return QXmpp::Private::makeReadyTask<PairListResult>(
PairListResult(pairs));
for (const auto &user : users)
QXmppPromise<PairListResult> promise;
load(*it).then(this,
[=] (LoadResult &&result) mutable
{
QKeychain::ReadPasswordJob job(service);
QEventLoop loop;
if (std::holds_alternative<QString>(result))
{
pairs << Pair(*it, std::get<QString>(result));
job.setKey(user);
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
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)});
});
const QString pwd = job.textData();
if (job.error())
std::cerr << "Failed to retrieve password for " << qPrintable(user)
<< ": " << qPrintable(job.errorString()) << std::endl;
else
ret.append(Pair(user, pwd));
}
return ret;
return promise.task();
}
void Credentials::store(Client *c)
QXmppTask<Credentials::PairListResult> Credentials::load()
{
QKeychain::WritePasswordJob job(service);
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();
QEventLoop loop;
job.setKey(user);
job.setTextData(cfg.password());
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
store(user, cfg.password()).then(this,
[=] (StoreResult &&result) mutable
{
store(user).then(this,
[=] (StoreResult &&result) mutable
{
promise.finish(result);
});
});
if (job.error())
std::cerr << "Failed to store password: "
<< qPrintable(job.errorString()) << std::endl;
else
storeUser(user);
return promise.task();
}
void Credentials::storeUser(const QString &user)
QXmppTask<Credentials::StoreResult> Credentials::store(const QString &user)
{
QString list = storedUsersList();
QKeychain::WritePasswordJob job(service);
QEventLoop loop;
QXmppPromise<StoreResult> promise;
if (!list.isEmpty())
list += sep;
storedUsersList().then(this,
[=] (LoadResult &&result) mutable
{
if (std::holds_alternative<QString>(result))
{
auto &list = std::get<QString>(result);
list += user;
if (!list.isEmpty())
list += sep;
job.setKey("users");
job.setTextData(list);
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
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);
});
}
});
if (job.error())
std::cerr << "Failed to store user: "
<< qPrintable(job.errorString()) << std::endl;
return promise.task();
}
QString Credentials::storedUsersList()
QXmppTask<Credentials::LoadResult> Credentials::storedUsersList()
{
QKeychain::ReadPasswordJob job(service);
QString ret;
QEventLoop loop;
QXmppPromise<LoadResult> promise;
job.setKey("users");
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
load("users").then(this,
[=] (LoadResult &&result) mutable
{
promise.finish(result);
});
const QString users = job.textData();
return promise.task();
if (job.error())
std::cerr << "Failed to retrieve users: "
<< qPrintable(job.errorString()) << std::endl;
else
ret = users;
return ret;
}
QStringList Credentials::storedUsers()
QXmppTask<Credentials::LoadResult> Credentials::load(const QString &key)
{
return storedUsersList().split(sep);
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();
}

View File

@ -2,25 +2,44 @@
#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:
using Pair = QPair<QString, QString>;
QList<Pair> load();
struct Success {};
struct Error
{
QString description;
};
public Q_SLOTS:
void store(Client *c);
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:
static void storeUser(const QString &user);
static QString storedUsersList();
static QStringList storedUsers();
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

711
jiddb.cpp
View File

@ -1,6 +1,7 @@
#include "jiddb.h"
#include <QDir>
#include <QSqlQuery>
#include <QSqlError>
#include <QStandardPaths>
#include <QTimeZone>
#include <QVariant>
@ -70,7 +71,7 @@ QStringList JidDb::roster() const
return ret;
}
int JidDb::ensureContactDb(const QString &jid) const
int JidDb::ensureContactTable(const QString &jid) const
{
QSqlQuery q(db);
@ -84,25 +85,72 @@ int JidDb::ensureContactDb(const QString &jid) const
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 (ensureContactDb(jid))
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";
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" << std::endl;
std::cerr << "JidDb::messages: query exec failed: "
<< qPrintable(q.lastError().text()) << std::endl;
return QList<Message>();
}
@ -143,7 +191,7 @@ QList<JidDb::Message> JidDb::messages(const QString &jid,
return ret;
}
int JidDb::storeMessage(const JidDb::Message &msg) const
int JidDb::store(const JidDb::Message &msg) const
{
QSqlQuery q(db);
QString dir;
@ -159,7 +207,7 @@ int JidDb::storeMessage(const JidDb::Message &msg) const
break;
}
ensureContactDb(msg.contact);
ensureContactTable(msg.contact);
if (!q.exec("insert into '" + msg.contact
+ "' (direction, time, body) values "
@ -213,3 +261,654 @@ QList<JidDb::Conversation> JidDb::conversations() const
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 = " + id))
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 = " + id))
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;
}
}
}

64
jiddb.h
View File

@ -2,12 +2,17 @@
#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
{
@ -30,6 +35,30 @@ public:
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;
@ -38,16 +67,47 @@ public Q_SLOTS:
QList<Conversation> conversations() const;
QList<Message> messages(const QString &jid,
int tail = -1) const;
int storeMessage(const Message &msg) 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 ensureContactDb(const QString &jid) const;
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;
};

View File

@ -1,374 +1,93 @@
#include "omemo_db.h"
#include <QXmppConfiguration.h>
#include <QXmppFutureUtils_p.h>
#include <QXmppPromise.h>
#include <qt5keychain/keychain.h>
#include <QEventLoop>
#include <QtConcurrent>
#include <QString>
#include <iostream>
#include <optional>
static const QString service_ns = "xxcc/omemo/";
static const QString service = "xxcc", key_namespace = "omemo";
OmemoDb::OmemoDb(const QString &jid) :
jid(jid)
OmemoDb::OmemoDb(const JidDb &db) :
db(db)
{
}
QString OmemoDb::service() const
{
return service_ns + jid;
}
QXmppTask<QXmppOmemoStorage::OmemoData> OmemoDb::allData()
{
return QXmpp::Private::makeReadyTask(QXmppOmemoStorage::OmemoData());
}
QXmppOmemoStorage::OmemoData ret;
QHash<QString, QHash<uint32_t, Device>> devices;
int OmemoDb::storeOwnKeyId(const QString &service, const uint32_t id)
{
QKeychain::WritePasswordJob job(service);
QEventLoop loop;
for (const auto &d : db.omemoDevices())
ret.devices.insert(d.jid, d.devices);
loop.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.setKey("device/keyid");
job.setTextData(QString::number(id, 16));
job.start();
loop.exec();
if (job.error())
{
std::cerr << "Failed to store own key ID: "
<< qPrintable(job.errorString()) << std::endl;
return -1;
}
return 0;
}
int OmemoDb::storePrivateIdentityKey(const QString &service,
const QByteArray &privateIdentityKey)
{
QKeychain::WritePasswordJob job(service);
QEventLoop loop;
loop.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.setKey("device/privateIdentityKey");
job.setBinaryData(privateIdentityKey);
job.start();
loop.exec();
if (job.error())
{
std::cerr << "Failed to store private identity key: "
<< qPrintable(job.errorString()) << std::endl;
return -1;
}
return 0;
}
int OmemoDb::storePublicIdentityKey(const QString &service,
const QByteArray &publicIdentityKey)
{
QKeychain::WritePasswordJob job(service);
QEventLoop loop;
loop.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.setKey("device/publicIdentityKey");
job.setBinaryData(publicIdentityKey);
job.start();
loop.exec();
if (job.error())
{
std::cerr << "Failed to store public identity key: "
<< qPrintable(job.errorString()) << std::endl;
return -1;
}
return 0;
}
int OmemoDb::storeLatestSignedPreKeyId(const QString &service,
const uint32_t latestSignedPreKeyId)
{
QKeychain::WritePasswordJob job(service);
QEventLoop loop;
loop.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.setKey("device/latestSignedPreKeyId");
job.setTextData(QString::number(latestSignedPreKeyId, 16));
job.start();
loop.exec();
if (job.error())
{
std::cerr << "Failed to store latest signed prekey ID: "
<< qPrintable(job.errorString()) << std::endl;
return -1;
}
return 0;
}
int OmemoDb::storeLatestPreKeyId(const QString &service,
const uint32_t latestPreKeyId)
{
QKeychain::WritePasswordJob job(service);
QEventLoop loop;
loop.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.setKey("device/latestPreKeyId");
job.setTextData(QString::number(latestPreKeyId, 16));
job.start();
loop.exec();
if (job.error())
{
std::cerr << "Failed to store latest prekey ID: "
<< qPrintable(job.errorString()) << std::endl;
return -1;
}
return 0;
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())
return QXmpp::Private::makeReadyTask();
if (device.has_value() && db.store(device.value()))
std::cerr << "db.store failed" << std::endl;
QXmppPromise<void> promise;
auto future = QtConcurrent::run(
[=] () mutable
{
const auto &d = device.value();
const auto srv = service();
storeDeviceLabel(srv, d.label)
|| storeOwnKeyId(srv, d.id)
|| storePrivateIdentityKey(srv, d.privateIdentityKey)
|| storePublicIdentityKey(srv, d.publicIdentityKey)
|| storeLatestSignedPreKeyId(srv, d.latestSignedPreKeyId)
|| storeLatestPreKeyId(srv, d.latestPreKeyId);
promise.finish();
});
return promise.task();
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();
}
int OmemoDb::storeDeviceLabel(const QString &service, const QString &label)
{
QKeychain::WritePasswordJob job(service);
QEventLoop loop;
loop.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.setKey("device/label");
job.setTextData(label);
job.start();
loop.exec();
if (job.error())
{
std::cerr << "Failed to store device label: "
<< qPrintable(job.errorString()) << std::endl;
return -1;
}
return 0;
}
int OmemoDb::storeKeyId(const QString &service, const QByteArray &keyId)
{
QKeychain::WritePasswordJob job(service);
QEventLoop loop;
loop.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.setKey("device/key_id");
job.setBinaryData(keyId);
job.start();
loop.exec();
if (job.error())
{
std::cerr << "Failed to store device keyID: "
<< qPrintable(job.errorString()) << std::endl;
return -1;
}
return 0;
}
int OmemoDb::storeSession(const QString &service, const QByteArray &session)
{
QKeychain::WritePasswordJob job(service);
QEventLoop loop;
loop.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.setKey("device/session");
job.setBinaryData(session);
job.start();
loop.exec();
if (job.error())
{
std::cerr << "Failed to store device session: "
<< qPrintable(job.errorString()) << std::endl;
return -1;
}
return 0;
}
int OmemoDb::storeUnrespondedSentStanzasCount(const QString &service,
const int count)
{
QKeychain::WritePasswordJob job(service);
QEventLoop loop;
loop.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.setKey("device/unresponded_sent_stanzas");
job.setTextData(QString::number(count));
job.start();
loop.exec();
if (job.error())
{
std::cerr << "Failed to store device unresponded sent stanzas: "
<< qPrintable(job.errorString()) << std::endl;
return -1;
}
return 0;
}
int OmemoDb::storeUnrespondedReceivedStanzasCount(const QString &service,
const int count)
{
QKeychain::WritePasswordJob job(service);
QEventLoop loop;
loop.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.setKey("device/unresponded_received_stanzas");
job.setTextData(QString::number(count));
job.start();
loop.exec();
if (job.error())
{
std::cerr << "Failed to store device unresponded received stanzas: "
<< qPrintable(job.errorString()) << std::endl;
return -1;
}
return 0;
}
int OmemoDb::storeRemovalFromDeviceListDate(const QString &service,
const QDateTime &dt)
{
QKeychain::WritePasswordJob job(service);
QEventLoop loop;
loop.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.setKey("device/unresponded_received_stanzas");
job.setTextData(QString::number(dt.currentMSecsSinceEpoch()));
job.start();
loop.exec();
if (job.error())
{
std::cerr << "Failed to store device unresponded received stanzas: "
<< qPrintable(job.errorString()) << std::endl;
return -1;
}
return 0;
}
QXmppTask<void> OmemoDb::addDevice(const QString &jid,
const uint32_t deviceId, const Device &device)
const uint32_t deviceId, const Device &d)
{
const QString fullservice = service_ns + jid;
QXmppPromise<void> promise;
if (db.store(jid, deviceId, d))
std::cerr << "db.store failed" << std::endl;
auto future = QtConcurrent::run(
[=] () mutable
{
storeDeviceLabel(fullservice, device.label)
|| storeKeyId(fullservice, device.keyId)
|| storeSession(fullservice, device.session)
|| storeUnrespondedSentStanzasCount(fullservice,
device.unrespondedSentStanzasCount)
|| storeUnrespondedReceivedStanzasCount(fullservice,
device.unrespondedSentStanzasCount)
|| storeRemovalFromDeviceListDate(fullservice,
device.removalFromDeviceListDate);
promise.finish();
});
return promise.task();
return QXmpp::Private::makeReadyTask();
}
QXmppTask<void> OmemoDb::removeDevice(const QString &jid,
const uint32_t deviceId)
{
#if 0
const QString fullservice = service_ns + jid;
QKeychain::DeletePasswordJob job(fullservice);
QEventLoop loop;
loop.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.setKey("device/unresponded_received_stanzas");
job.setTextData(QString::number(dt.currentMSecsSinceEpoch()));
job.start();
loop.exec();
if (job.error())
{
std::cerr << "Failed to store device unresponded received stanzas: "
<< qPrintable(job.errorString()) << std::endl;
return -1;
}
return 0;
#endif
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();
}

View File

@ -1,17 +1,24 @@
#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 QXmppOmemoStorage
class OmemoDb : public QObject, public QXmppOmemoStorage
{
Q_OBJECT
public:
OmemoDb(const QString &jid);
OmemoDb(const JidDb &db);
QXmppTask<OmemoData> allData() override;
QXmppTask<void> setOwnDevice(
const std::optional<OwnDevice> &device) override;
@ -29,26 +36,7 @@ public:
QXmppTask<void> resetAll() override;
private:
const QString &jid;
QString service() const;
static int storeOwnKeyId(const QString &service, uint32_t id);
static int storePrivateIdentityKey(const QString &service,
const QByteArray &privateIdentityKey);
static int storePublicIdentityKey(const QString &service,
const QByteArray &publicIdentityKey);
static int storeLatestSignedPreKeyId(const QString &service,
uint32_t latestSignedPreKeyId);
static int storeLatestPreKeyId(const QString &service,
uint32_t latestPreKeyId);
static int storeDeviceLabel(const QString &service, const QString &label);
static int storeKeyId(const QString &service, const QByteArray &keyId);
static int storeSession(const QString &service, const QByteArray &session);
static int storeUnrespondedSentStanzasCount(const QString &service,
int count);
static int storeUnrespondedReceivedStanzasCount(const QString &service,
int count);
static int storeRemovalFromDeviceListDate(const QString &service,
const QDateTime &dt);
const JidDb &db;
};
#endif

View File

@ -1,8 +1,9 @@
#include "trust_db.h"
#include <QXmppTrustLevel.h>
#include <QXmppTrustSecurityPolicy.h>
#include <QXmppConfiguration.h>
#include <QXmppFutureUtils_p.h>
#include <QXmppPromise.h>
#include <qt5keychain/keychain.h>
#include <QEventLoop>
#include <QtConcurrent>
#include <iostream>
@ -13,16 +14,44 @@ TrustDb::TrustDb(const QString &jid, const JidDb &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)
{
switch (securityPolicy)
{
case QXmpp::NoSecurityPolicy:
return "NoSecurityPolicy";
for (const auto &t : tsp_levels)
if (t.policy == securityPolicy)
return t.str;
case QXmpp::Toakafa:
return "Toakafa";
}
return "unknown";
}
static QString toString(const QXmpp::TrustLevel &trustLevel)
{
for (const auto &t : trustlevels)
if (t.level == trustLevel)
return t.str;
return "unknown";
}
@ -30,16 +59,24 @@ static QString toString(const QXmpp::TrustSecurityPolicy securityPolicy)
static int toSecurityPolicy(const QString &s,
QXmpp::TrustSecurityPolicy &securityPolicy)
{
if (s == "NoSecurityPolicy")
{
securityPolicy = QXmpp::NoSecurityPolicy;
return 0;
}
else if (s == "Toakafa")
{
securityPolicy = QXmpp::Toakafa;
return 0;
}
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;
}
@ -52,35 +89,18 @@ QString TrustDb::service() const
QXmppTask<void> TrustDb::setSecurityPolicy(const QString &encryption,
const QXmpp::TrustSecurityPolicy securityPolicy)
{
QKeychain::WritePasswordJob job(service());
QEventLoop loop;
job.setKey("securityPolicy/" + encryption);
job.setTextData(toString(securityPolicy));
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
if (job.error())
std::cerr << "Failed to store security policy: "
<< qPrintable(job.errorString()) << std::endl;
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)
{
QKeychain::DeletePasswordJob job(service());
QEventLoop loop;
job.setKey("securityPolicy/" + encryption);
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
if (job.error())
std::cerr << "Failed to reset security policy: "
<< qPrintable(job.errorString()) << std::endl;
if (db.store(encryption, QString()))
std::cerr << "TrustDb::resetSecurityPolicy: store failed"
<< std::endl;
return QXmpp::Private::makeReadyTask();
}
@ -88,23 +108,11 @@ QXmppTask<void> TrustDb::resetSecurityPolicy(const QString &encryption)
QXmppTask<QXmpp::TrustSecurityPolicy> TrustDb::securityPolicy(
const QString &encryption)
{
QKeychain::ReadPasswordJob job(service());
QEventLoop loop;
const auto s = db.securityPolicy(encryption);
QXmpp::TrustSecurityPolicy policy;
job.setKey("securityPolicy/" + encryption);
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
if (job.error())
std::cerr << "Failed to read security policy: "
<< qPrintable(job.errorString()) << std::endl;
QXmpp::TrustSecurityPolicy policy = QXmpp::Toakafa;
const QString data = job.textData();
if (toSecurityPolicy(data, policy))
std::cerr << "Invalid security policy " << qPrintable(data)
if (toSecurityPolicy(s, policy))
std::cerr << "TrustDb::securityPolicy: toSecurityPolicy failed"
<< std::endl;
return QXmpp::Private::makeReadyTask(QXmpp::TrustSecurityPolicy(policy));
@ -113,81 +121,80 @@ QXmppTask<QXmpp::TrustSecurityPolicy> TrustDb::securityPolicy(
QXmppTask<void> TrustDb::setOwnKey(const QString &encryption,
const QByteArray &keyId)
{
QKeychain::WritePasswordJob job(service());
QEventLoop loop;
job.setKey("key/" + encryption);
job.setBinaryData(keyId);
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
if (job.error())
std::cerr << "Failed to store own key: "
<< qPrintable(job.errorString()) << std::endl;
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)
{
QKeychain::DeletePasswordJob job(service());
QEventLoop loop;
job.setKey("key/" + encryption);
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
if (job.error())
std::cerr << "Failed to reset own key: "
<< qPrintable(job.errorString()) << std::endl;
return QXmpp::Private::makeReadyTask();
}
QXmppTask<QByteArray> TrustDb::ownKey(const QString &encryption)
{
QKeychain::ReadPasswordJob job(service());
QEventLoop loop;
job.setKey("key/" + encryption);
job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
QByteArray ret;
if (job.error())
std::cerr << "Failed to read own key: "
<< qPrintable(job.errorString()) << std::endl;
else
ret = job.binaryData();
return QXmpp::Private::makeReadyTask(QByteArray(ret));
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();
}
@ -195,8 +202,32 @@ 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>>());
QMultiHash<QString, QByteArray>>(ret));
}
QXmppTask<QHash<QString,
@ -204,14 +235,66 @@ QXmppTask<QHash<QString,
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>>());
QHash<QByteArray, QXmpp::TrustLevel>>(ret));
}
QXmppTask<bool> TrustDb::hasKey(const QString &encryption,
const QString &keyOwnerJid, QXmpp::TrustLevels trustLevels)
{
return QXmpp::Private::makeReadyTask(bool());
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,
@ -220,8 +303,33 @@ QXmppTask<QHash<QString,
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>>());
QMultiHash<QString, QByteArray>>(ret));
}
QXmppTask<QHash<QString,
@ -231,17 +339,77 @@ QXmppTask<QHash<QString,
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>>());
QMultiHash<QString, QByteArray>>(ret));
}
QXmppTask<QXmpp::TrustLevel> TrustDb::trustLevel(const QString &encryption,
const QString &keyOwnerJid, const QByteArray &keyId)
{
return QXmpp::Private::makeReadyTask(QXmpp::TrustLevel());
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);
}
}

View File

@ -6,32 +6,56 @@
#include "conversation.h"
#include "message.h"
#include <QXmppMessage.h>
#include <QXmppOmemoElement_p.h>
#include <QXmppRosterManager.h>
#include <QXmppUtils.h>
#include <QKeyEvent>
#include <QPushButton>
#include <QScroller>
#include <iostream>
#include <stdexcept>
#include <utility>
#include <variant>
xxcc::xxcc(QWidget *const parent) :
QWidget(parent),
selected(nullptr)
{
const auto pairs = creds.load();
ui.setupUi(this);
QScroller::grabGesture(ui.conversations_list, QScroller::TouchGesture);
QScroller::grabGesture(ui.messages, QScroller::TouchGesture);
connectAccounts(pairs);
retrieveConversations();
creds.load().then(this,
[=] (Credentials::PairListResult &&result)
{
if (std::holds_alternative<Credentials::PairList>(result))
{
const auto &pairs = std::get<Credentials::PairList>(result);
connectAccounts(pairs);
retrieveConversations();
}
else if (std::holds_alternative<Credentials::Error>(result))
{
const auto &error = std::get<Credentials::Error>(result);
std::cerr << qPrintable(error.description) << std::endl;
}
});
connect(ui.accounts, &QPushButton::released, this,
[this]
{
Accounts a(clients, this);
a.connect(&a, &Accounts::new_account, this, &xxcc::addAccount);
a.connect(&a, &Accounts::new_account, &creds, &Credentials::store);
a.connect(&a, &Accounts::new_account, this,
[&] (Client *c)
{
creds.store(c);
});
a.exec();
});
@ -157,9 +181,9 @@ void xxcc::addInMessage(const QXmppMessage &msg)
ui.messages);
}
void xxcc::addOutMessage(const QXmppMessage &msg)
void xxcc::addOutMessage(const QString &msg, const QDateTime &dt)
{
new Message(msg.body(), msg.stamp().toLocalTime(), Direction::Out,
new Message(msg, dt.toLocalTime(), Direction::Out,
ui.messages);
}
@ -194,6 +218,8 @@ void xxcc::addAccount(Client *const c)
throw std::runtime_error("Expected non-null QXmppRosterManager");
}
#include <QDebug>
void xxcc::send(void)
{
if (!selected)
@ -201,30 +227,52 @@ void xxcc::send(void)
const auto from = selected->jidBare(),
to = ui.jid->text(), msg = ui.chatinput->toPlainText();
const bool enc = ui.omemo->isChecked();
QXmppMessage out(from, to, msg);
const auto dt = QDateTime::currentDateTimeUtc();
out.setStamp(QDateTime::currentDateTimeUtc());
selected->sendPacket(out);
addOutMessage(out);
storeMessage(out, Direction::Out);
ui.chatinput->clear();
out.setE2eeFallbackBody("[xxcc: This is an OMEMO-encrypted message]");
out.setStamp(dt);
// TODO: QXmpp forces OMEMO 2 (XEP-0384 version >= 0.8.0).
// This breaks compatibility with Dino and Gajim, who still use 0.1.0.
// out.setEncryptionMethod(QXmpp::Omemo0);
selected->sendSensitive(std::move(out)).then(this,
[=](const QXmpp::SendResult &&result) mutable
{
qDebug() << "result.index(): " << result.index();
if (std::holds_alternative<QXmpp::SendSuccess>(result))
{
const auto &success = std::get<QXmpp::SendSuccess>(result);
qDebug() << "acknowledged: " << success.acknowledged;
addOutMessage(msg, dt);
storeMessage(from, to, msg, dt, Direction::Out);
ui.chatinput->clear();
}
else if (std::holds_alternative<QXmppError>(result))
{
const auto &error = std::get<QXmppError>(result);
qDebug() << error.description;
}
});
}
void xxcc::storeMessage(const QXmppMessage &msg, const Direction dir) const
void xxcc::storeMessage(const QString &from, const QString &to,
const QString &msg, const QDateTime &dt, const Direction dir) const
{
QString jid, contact;
switch (dir)
{
case Direction::In:
jid = QXmppUtils::jidToBareJid(msg.to());
contact = QXmppUtils::jidToBareJid(msg.from());
jid = QXmppUtils::jidToBareJid(to);
contact = QXmppUtils::jidToBareJid(from);
break;
case Direction::Out:
jid = msg.from();
contact = msg.to();
jid = from;
contact = to;
break;
}
@ -235,15 +283,20 @@ void xxcc::storeMessage(const QXmppMessage &msg, const Direction dir) const
const auto &db = c->database();
JidDb::Message m;
m.body = msg.body();
m.dt = msg.stamp();
m.body = msg;
m.dt = dt;
m.direction = dir;
m.contact = contact;
db.storeMessage(m);
db.store(m);
}
}
}
void xxcc::storeMessage(const QXmppMessage &msg, const Direction dir) const
{
storeMessage(msg.from(), msg.to(), msg.body(), msg.stamp(), dir);
}
void xxcc::retrieveConversations()
{
for (const auto c : clients)

4
xxcc.h
View File

@ -28,12 +28,14 @@ private:
Client *selected;
void connectAccounts(const QList<Credentials::Pair> &pairs);
void storeMessage(const QXmppMessage &msg, Direction dir) const;
void storeMessage(const QString &from, const QString &to,
const QString &msg, const QDateTime &dt, Direction dir) const;
void retrieveConversations();
private Q_SLOTS:
void startChat(QString from, QString to);
void addInMessage(const QXmppMessage &msg);
void addOutMessage(const QXmppMessage &msg);
void addOutMessage(const QString &msg, const QDateTime &dt);
void addAccount(Client *c);
void send(void);
};

View File

@ -226,10 +226,7 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox">
<property name="enabled">
<bool>false</bool>
</property>
<widget class="QCheckBox" name="omemo">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>