diff options
| author | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2023-08-28 00:20:54 +0200 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2023-09-18 10:35:30 +0200 |
| commit | 5bbe04c9ca091a0626f34afe5e3ba2141e2963de (patch) | |
| tree | 35e76183618fe557b4ded5735fb846e3c6cf3995 /jiddb.cpp | |
| parent | d32b9e93572c5e6999a7323139de38cc1a7197cf (diff) | |
WIP OMEMO TrustDb/JidDb
Diffstat (limited to 'jiddb.cpp')
| -rw-r--r-- | jiddb.cpp | 711 |
1 files changed, 705 insertions, 6 deletions
@@ -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; + } + } +} |
