aboutsummaryrefslogtreecommitdiff
path: root/jiddb.cpp
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2023-08-28 00:20:54 +0200
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2023-09-18 10:35:30 +0200
commit5bbe04c9ca091a0626f34afe5e3ba2141e2963de (patch)
tree35e76183618fe557b4ded5735fb846e3c6cf3995 /jiddb.cpp
parentd32b9e93572c5e6999a7323139de38cc1a7197cf (diff)
WIP OMEMO TrustDb/JidDb
Diffstat (limited to 'jiddb.cpp')
-rw-r--r--jiddb.cpp711
1 files changed, 705 insertions, 6 deletions
diff --git a/jiddb.cpp b/jiddb.cpp
index b555f87..f66d242 100644
--- a/jiddb.cpp
+++ b/jiddb.cpp
@@ -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;
+ }
+ }
+}