feat: omemo signal lib wip

This commit is contained in:
vae 2021-07-22 20:45:39 +03:00
parent 08fe37bfb2
commit 442ad37300
34 changed files with 745 additions and 262 deletions

View File

@ -9,6 +9,8 @@ target_sources(squawk PRIVATE
device_key_storage.h
device_service.cpp
device_service.h
key.cpp
key.h
qomemo.cpp
qomemo.h
sce.cpp

View File

@ -3,127 +3,3 @@
*/
#include "bundle.h"
#include <QXmppPubSubIq.h>
#include <QDebug>
#include "shared/qxmppfactories.h"
using namespace QXmpp::Factories;
QXmppElement QXmpp::Omemo::PreKey::toXml() const {
auto pk = createElement("preKeyPublic");
pk.setAttribute("preKeyId", QString::number(id));
// TODO: Base64
pk.setValue(data);
return pk;
}
void QXmpp::Omemo::PreKey::fromXml(const QXmppElement &element) {
if (!elementMatches(element, "preKeyPublic"))
return;
id = element.attribute("preKeyId").toInt();
// TODO: Base64
data = element.value();
}
QXmppElement QXmpp::Omemo::Bundle::toXml() const {
auto spkNode = createElement("signedPreKeyPublic");
spkNode.setAttribute("signedPreKeyId", QString::number(spkId));
spkNode.setValue(spk);
auto spksNode = createElement("signedPreKeySignature");
spksNode.setValue(spks);
auto ikNode = createElement("identityKey");
ikNode.setValue(ik);
auto prekeysNode = createElement("prekeys");
for (const auto &pk : prekeys) {
prekeysNode.appendChild(pk.toXml());
}
auto result = createElement("bundle", "eu.siacs.conversations.axolotl");
result.appendChild(spkNode);
result.appendChild(spksNode);
result.appendChild(ikNode);
result.appendChild(prekeysNode);
return result;
}
QXmppIq QXmpp::Omemo::Bundle::toIq(int deviceId) const {
QXmppIq iq{};
iq.setType(QXmppIq::Set);
auto item = createElement("item");
item.appendChild(toXml());
auto publish = createElement("publish");
publish.setAttribute(
"node",
QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
publish.appendChild(item);
auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
pubSub.appendChild(publish);
pubSub.appendChild(createOpenPublishOptions());
iq.extensions().push_back(pubSub);
return iq;
}
void QXmpp::Omemo::Bundle::fromXml(const QXmppElement &element) {
if (!elementMatches(element, "bundle", "eu.siacs.conversations.axolotl"))
return;
auto spkNode = element.firstChildElement("spk");
if (spkNode.isNull()) {
qWarning() << "'bundle': missing 'spk'";
return;
}
spk = spkNode.value();
spkId = spkNode.attribute("id").toInt();
auto spksNode = element.firstChildElement("spks");
if (spksNode.isNull()) {
qWarning() << "'bundle': missing 'spks'";
return;
}
spks = spksNode.value();
auto ikNode = element.firstChildElement("ik");
if (ikNode.isNull()) {
qWarning() << "'bundle': missing 'ik'";
return;
}
ik = ikNode.value();
auto prekeysNode = element.firstChildElement("prekeys");
auto pkNode = prekeysNode.firstChildElement("pk");
prekeys.clear();
while (!pkNode.isNull()) {
PreKey pk{};
pk.fromXml(pkNode);
prekeys.push_back(pk);
}
}
QXmppPubSubIq QXmpp::Omemo::Bundle::fetchDeviceBundleIq(int deviceId) {
QXmppPubSubIq iq{};
iq.setType(QXmppIq::Get);
iq.setQueryNode(
QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
QXmppPubSubItem item{};
item.setId(QString::number(deviceId));
iq.setItems({item});
return iq;
}

View File

@ -6,6 +6,7 @@
#include <QList>
#include <QString>
#include <QBuffer>
class QXmppPubSubIq;
@ -17,26 +18,16 @@ namespace QXmpp::Omemo {
class PreKey {
public:
[[nodiscard]] QXmppElement toXml() const;
/// Expects a <pk>
void fromXml(const QXmppElement &element);
int id;
QString data;
QByteArray data;
};
class Bundle {
public:
[[nodiscard]] static QXmppPubSubIq fetchDeviceBundleIq(int deviceId);
[[nodiscard]] QXmppElement toXml() const;
[[nodiscard]] QXmppIq toIq(int deviceId) const;
void fromXml(const QXmppElement &element);
QString spk;
QByteArray spk;
int spkId;
QString spks;
QString ik;
QByteArray spks;
QByteArray ik;
QList<PreKey> prekeys;
};

View File

@ -4,6 +4,8 @@
#include "database.h"
#include "bundle.h"
#include <QDebug>
#include <QDir>
#include <QException>
@ -44,17 +46,17 @@ Database::~Database() {
mdb_env_close(env);
}
QBuffer Database::loadIdentityKeySecret(int deviceId) { return QBuffer(); }
std::optional<QByteArray> Database::loadIdentityKeySecret(int deviceId) { return std::nullopt; }
bool Database::saveIdentityKeySecret(int deviceId,
const QBuffer &identityKeySecret) {
const QByteArray &identityKeySecret) {
MDB_val mdbKey, mdbValue;
auto key = QString("%1/secret").arg(QString::number(deviceId)).toStdString();
mdbKey.mv_data = key.data();
mdbKey.mv_size = key.size();
mdbValue.mv_data = const_cast<char *>(identityKeySecret.data().data());
mdbValue.mv_data = const_cast<char *>(identityKeySecret.data());
mdbValue.mv_size = identityKeySecret.size();
MDB_txn *txn;
@ -71,7 +73,7 @@ bool Database::saveIdentityKeySecret(int deviceId,
return false;
}
int Database::loadActiveDeviceId() {
std::optional<int> Database::loadActiveDeviceId() {
MDB_val key, value;
key.mv_data = (void *) "active";
@ -83,12 +85,14 @@ int Database::loadActiveDeviceId() {
auto err = mdb_get(txn, dbiIdentityKeys, &key, &value);
if (err) {
qWarning() << "could not load active device id:" << mdb_strerror(err);
return 0;
mdb_txn_abort(txn);
return std::nullopt;
}
if (value.mv_size != sizeof(int)) {
qWarning() << "mv_size is" << value.mv_size << "instead of" << sizeof(int);
return 0;
mdb_txn_abort(txn);
return std::nullopt;
}
auto id = *reinterpret_cast<int *>(value.mv_data);
@ -124,3 +128,53 @@ bool Database::saveActiveDeviceId(int deviceId) {
return true;
}
bool Database::saveIdentityKey(int deviceId, const QByteArray &identityKey) {
return false;
}
std::optional<QByteArray> Database::loadIdentityKey(int deviceId) {
return std::nullopt;
}
bool Database::containsPreKey() {
return false;
}
std::optional<KeyPair> Database::loadPreKey(int deviceId, int id) {
return std::nullopt;
}
bool Database::savePreKey(int deviceId, int id, const KeyPair &preKey) {
return false;
}
std::optional<Bundle> Database::loadBundle(int deviceId) {
Bundle result{};
auto ik = loadIdentityKey(deviceId);
result.ik = ik.value();
auto spk = loadSignedPreKey(deviceId);
result.spk = spk->key.publicKey;
result.spks = spk->signature;
result.spkId = spk->id;
// PreKeys
return result;
}
bool Database::saveBundle(int deviceId, const Bundle &bundle) {
return false;
}
std::optional<SignedPreKey> Database::loadSignedPreKey(int deviceId) {
return std::optional<SignedPreKey>();
}
bool Database::saveSignedPreKey(int deviceId, const SignedPreKey &signedPreKey) {
return false;
}

View File

@ -8,8 +8,12 @@
#include <QString>
#include <lmdb.h>
#include "key.h"
namespace QXmpp::Omemo {
class Bundle;
class Database {
public:
explicit Database(QString jid);
@ -18,14 +22,26 @@ namespace QXmpp::Omemo {
Database(Database &&) = delete;
Database &operator=(const Database &) = delete;
QBuffer loadIdentityKey();
bool saveIdentityKey(const QBuffer &identityKey);
int loadActiveDeviceId();
// For local user
std::optional<int> loadActiveDeviceId();
bool saveActiveDeviceId(int deviceId);
QBuffer loadIdentityKeySecret(int deviceId);
bool saveIdentityKeySecret(int deviceId, const QBuffer &identityKeySecret);
std::optional<QByteArray> loadIdentityKeySecret(int deviceId);
bool saveIdentityKeySecret(int deviceId, const QByteArray &identityKeySecret);
std::optional<Bundle> loadBundle(int deviceId);
bool saveBundle(int deviceId, const Bundle& bundle);
// For any user
std::optional<QByteArray> loadIdentityKey(int deviceId);
bool saveIdentityKey(int deviceId, const QByteArray &identityKey);
bool containsPreKey();
std::optional<KeyPair> loadPreKey(int deviceId, int id);
bool savePreKey(int deviceId, int id, const KeyPair &preKey);
std::optional<SignedPreKey> loadSignedPreKey(int deviceId);
bool saveSignedPreKey(int deviceId, const SignedPreKey& signedPreKey);
const QString jid;

View File

@ -5,11 +5,31 @@
#include "device_service.h"
#include "device.h"
QXmpp::Omemo::DeviceService::DeviceService(QObject *parent) : QObject(parent) {}
using namespace QXmpp::Omemo;
void QXmpp::Omemo::DeviceService::onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list) {
DeviceService::DeviceService(QObject *parent) : QObject(parent) {}
void DeviceService::onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list) {
for (const auto &device : list.devices) {
qInfo() << "Got device for" << jid << ":" << device.id;
}
}
QSharedPointer<Database> DeviceService::getDatabase(const QString &jid) {
if (!databases.contains(jid)) {
databases.insert(jid, QSharedPointer<Database>::create(jid));
}
return databases[jid];
}
void DeviceService::addIdentity(const QString &jid, int deviceId, const QByteArray& publicKey) {
auto db = getDatabase(jid);
db->saveIdentityKey(deviceId, publicKey);
}
void DeviceService::onDeviceListNotFound(const QString &jid) {
qInfo() << "Device list not found:" << jid;
}

View File

@ -4,8 +4,11 @@
#pragma once
#include <QBuffer>
#include "qomemo.h"
#include "user_device_list.h"
#include "database.h"
#include <QXmppClient.h>
@ -19,11 +22,17 @@ namespace QXmpp::Omemo {
public:
explicit DeviceService(QObject *parent);
QSharedPointer<Database> getDatabase(const QString& jid);
public slots:
void addIdentity(const QString& jid, int deviceId, const QByteArray& publicKey);
void onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list);
void onDeviceListNotFound(const QString &jid);
private:
QMap<QString, UserDeviceList> device_lists{};
QMap<QString, QSharedPointer<Database>> databases{};
};
} // namespace QXmpp::Omemo

6
qomemo/key.cpp Normal file
View File

@ -0,0 +1,6 @@
/*
* Created by victoria on 2021-05-15.
*/
#include "key.h"

27
qomemo/key.h Normal file
View File

@ -0,0 +1,27 @@
/*
* Created by victoria on 2021-05-15.
*/
#pragma once
#include <optional>
#include <QByteArray>
namespace QXmpp::Omemo {
class KeyPair {
public:
QByteArray publicKey{};
std::optional<QByteArray> secretKey{ std::nullopt };
};
class SignedPreKey {
public:
int id{ 0 };
KeyPair key{};
QByteArray signature{};
};
}

View File

@ -4,6 +4,9 @@
#include "qxmpp_omemo_manager.h"
#include "qomemo/signal/context.h"
#include "bundle.h"
#include "device.h"
#include "variant/conversations.h"
@ -12,13 +15,24 @@
#include <QXmppClient.h>
#include <QXmppPubSubIq.h>
#include <iostream>
#include <QRandomGenerator64>
#include <external/signal-protocol-c/src/signal_protocol_internal.h>
using namespace QXmpp::Omemo;
Manager::Manager()
: deviceService(new DeviceService(this)),
: deviceService{new DeviceService(this)},
omemoVariant(new Variant::Conversations) {
connect(this, &Manager::deviceListReceived, deviceService.get(), &DeviceService::onDeviceListReceived);
connect(this, &Manager::deviceListNotFound, deviceService.get(), &DeviceService::onDeviceListNotFound);
connect(this, &Manager::deviceListNotFound, this, [this](const QString &jid) {
if (jid == client()->configuration().jidBare())
generateDeviceListForSelf();
});
connect(this, &Manager::deviceListReceived, this, [this](const QString &jid, const DeviceList &currentList) {
if (jid == client()->configuration().jidBare())
generateDeviceForSelfIfNeeded(currentList);
});
}
bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
@ -28,29 +42,60 @@ bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
std::cout << str.toStdString();
if (stanza.tagName() == "iq") {
if (stanza.attribute("type") == "result") {
if (handleDeviceList(stanza) || handleMissingDeviceList(stanza))
return true;
return false;
}
bool Manager::handleDeviceList(const QDomElement &stanza) {
if (!(stanza.tagName() == "iq"))
return false;
if (!(stanza.attribute("type") == "result"))
return false;
auto pubsub = stanza.firstChildElement("pubsub");
if (!pubsub.isNull()) {
if (pubsub.isNull())
return false;
auto items = pubsub.firstChildElement("items");
if (items.attribute("node") ==
"eu.siacs.conversations.axolotl.devicelist") {
auto item = items.firstChildElement("item");
if (!item.isNull()) {
auto list = item.firstChildElement("list");
if (!list.isNull()) {
DeviceList deviceList = omemoVariant->deviceListFromXml(list);
emit deviceListReceived(stanza.attribute("from"), deviceList);
if (!(items.attribute("node") == omemoVariant->getDeviceListNode()))
return false;
auto deviceList = omemoVariant->latestDeviceListFromPubSubNode(items);
if (!deviceList.has_value())
return false;
emit deviceListReceived(stanza.attribute("from"), deviceList.value());
return true;
}
}
}
}
}
}
bool Manager::handleMissingDeviceList(const QDomElement &stanza) {
if (stanza.tagName() != "iq")
return false;
if (stanza.attribute("type") != "error")
return false;
auto pubsub = stanza.firstChildElement("pubsub");
if (pubsub.isNull())
return false;
auto items = pubsub.firstChildElement("items");
if (items.attribute("node") != omemoVariant->getDeviceListNode())
return false;
auto error = stanza.firstChildElement("error");
if (error.namespaceURI() != "jabber:client" || error.attribute("code") != "404")
return false;
qDebug() << "Got 404 deviceList for" << stanza.attribute("from");
emit deviceListNotFound(stanza.attribute("from"));
return true;
}
void QXmpp::Omemo::Manager::setClient(QXmppClient *client) {
@ -68,7 +113,152 @@ void QXmpp::Omemo::Manager::fetchOwnDevices() {
iq.setFrom(client()->configuration().jid());
iq.setTo(client()->configuration().jidBare());
iq.setType(QXmppIq::Get);
iq.setQueryNode("eu.siacs.conversations.axolotl.devicelist");
iq.setQueryNode(omemoVariant->getDeviceListNode());
client()->sendPacket(iq);
}
QSharedPointer<DeviceService> Manager::getDeviceService() {
return deviceService;
}
void Manager::publishDeviceList(const DeviceList &deviceList) {
// QXmppPubSubIq iq{};
// iq.setFrom(client()->configuration().jid());
// iq.setType(QXmppIq::Set);
// iq.setQueryNode(omemoVariant->getDeviceListNode());
// iq.setItems()
auto iq = omemoVariant->deviceListSetIq(deviceList);
iq.setFrom(client()->configuration().jid());
QString str{};
QXmlStreamWriter info(&str);
iq.toXml(&info);
qInfo() << str;
client()->sendPacket(iq);
}
void Manager::generateDeviceListForSelf() {
qInfo() << "Generate device for self...";
generateDeviceForSelfIfNeeded(DeviceList());
}
void Manager::publishBundle(int deviceId, const Bundle &bundle) {
auto iq = omemoVariant->bundleSetIq(deviceId, bundle);
iq.setFrom(client()->configuration().jid());
QString str{};
QXmlStreamWriter info(&str);
iq.toXml(&info);
qInfo() << str;
client()->sendPacket(iq);
}
void Manager::generateDeviceForSelfIfNeeded(const DeviceList &currentList) {
auto db = deviceService->getDatabase(client()->configuration().jidBare());
auto activeId = db->loadActiveDeviceId();
if (activeId.has_value()) {
qInfo() << "Current device:" << *activeId;
bool found = false;
for (const auto &d : currentList.devices)
if (d.id == *activeId)
found = true;
if (found)
return;
qInfo() << "Could not find device" << *activeId << ", generating new one";
}
qInfo() << "Generating device";
auto deviceId = QRandomGenerator64{}.bounded(INT32_MAX);
db->saveActiveDeviceId(deviceId);
Device device{};
device.id = deviceId;
auto updatedList = currentList;
auto bundle = Bundle();
updatedList.devices.push_back(device);
publishDeviceList(updatedList);
publishBundle(deviceId, bundle);
}
Bundle Manager::generateAndSaveBundle(int deviceId) {
auto database = deviceService->getDatabase(client()->configuration().jidBare());
Bundle result{};
ec_key_pair *ecKeyPair;
curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &ecKeyPair);
signal_buffer *ecPublic;
ec_public_key_serialize(&ecPublic, ec_key_pair_get_public(ecKeyPair));
signal_buffer *ecSecret;
ec_private_key_serialize(&ecSecret, ec_key_pair_get_private(ecKeyPair));
QByteArray identityKey((const char *) signal_buffer_const_data(ecPublic), (int) signal_buffer_len(ecPublic));
QByteArray identityKeySecret((const char *) signal_buffer_const_data(ecSecret), (int) signal_buffer_len(ecSecret));
database->saveIdentityKey(deviceId, identityKey);
database->saveIdentityKeySecret(deviceId, identityKeySecret);
// Generate SPK
ec_key_pair *ecSpk;
curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &ecSpk);
signal_buffer *spkPublic, *spkSecret;
ec_public_key_serialize(&spkPublic, ec_key_pair_get_public(ecSpk));
ec_private_key_serialize(&spkSecret, ec_key_pair_get_private(ecSpk));
// Generate SPKs
signal_buffer *signature;
curve_calculate_signature(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &signature,
ec_key_pair_get_private(ecKeyPair), signal_buffer_const_data(spkPublic),
signal_buffer_len(spkPublic));
SignedPreKey spk{};
spk.id = 0;
spk.key.publicKey = QByteArray((const char *) signal_buffer_const_data(spkPublic), (int) signal_buffer_len(spkPublic));
spk.key.secretKey = QByteArray((const char *) signal_buffer_const_data(spkSecret), (int) signal_buffer_len(spkSecret));
spk.signature = QByteArray((const char *) signal_buffer_const_data(signature), (int) signal_buffer_len(signature));
// Generate 100 PK
for (auto i = 1; i <= 100; ++i) {
ec_key_pair *currentPreKey;
curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &currentPreKey);
signal_buffer *pkPublic, *pkSecret;
ec_public_key_serialize(&pkPublic, ec_key_pair_get_public(currentPreKey));
ec_private_key_serialize(&pkSecret, ec_key_pair_get_private(currentPreKey));
KeyPair preKey{};
preKey.publicKey = QByteArray((const char *) signal_buffer_const_data(pkPublic), (int) signal_buffer_len(pkPublic));
preKey.secretKey = QByteArray((const char *) signal_buffer_const_data(pkSecret), (int) signal_buffer_len(pkSecret));
database->savePreKey(deviceId, i, preKey);
signal_buffer_free(pkPublic);
signal_buffer_free(pkSecret);
SIGNAL_UNREF(currentPreKey);
}
signal_buffer_free(signature);
signal_buffer_free(ecPublic);
signal_buffer_free(ecSecret);
SIGNAL_UNREF(ecKeyPair);
signal_buffer_free(spkPublic);
signal_buffer_free(spkSecret);
SIGNAL_UNREF(ecSpk);
}

View File

@ -10,6 +10,10 @@
#include <QXmppClientExtension.h>
namespace Signal {
class Context;
}
namespace QXmpp::Omemo {
class Manager : public QXmppClientExtension {
@ -21,18 +25,37 @@ namespace QXmpp::Omemo {
bool handleStanza(const QDomElement &stanza) override;
bool handleDeviceList(const QDomElement& stanza);
bool handleMissingDeviceList(const QDomElement& stanza);
QSharedPointer<DeviceService> getDeviceService();
Bundle generateAndSaveBundle(int deviceId);
public slots:
void fetchOwnDevices();
void publishDeviceList(const QXmpp::Omemo::DeviceList& deviceList);
void generateDeviceListForSelf();
void generateDeviceForSelfIfNeeded(const QXmpp::Omemo::DeviceList &currentList);
void publishBundle(int deviceId, const QXmpp::Omemo::Bundle& bundle);
signals:
void deviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list);
void deviceListNotFound(const QString &jid);
protected:
void setClient(QXmppClient *client) override;
private:
QScopedPointer<DeviceService> deviceService;
QSharedPointer<DeviceService> deviceService;
QScopedPointer<Variant::Base> omemoVariant;
QScopedPointer<Signal::Context> signalContext;
};
} // namespace QXmpp::Omemo

View File

@ -1,6 +1,8 @@
target_sources(squawk PRIVATE
context.cpp
context.h
util.cpp
util.h
)
add_subdirectory(crypto)

View File

@ -4,6 +4,19 @@
#include "context.h"
Signal::Context::Context() {}
using namespace Signal;
Signal::Context::~Context() {}
Context::Context() {}
Context::~Context() {}
std::unique_ptr<Crypto::ECKeyPair> Context::generateCurveKeyPair() {
auto result = std::unique_ptr<Crypto::ECKeyPair>();
// TODO
return result;
}
signal_context *Context::temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped() {
return ctx;
}

View File

@ -4,6 +4,10 @@
#pragma once
#include "crypto/ec.h"
#include <memory>
#include <signal/signal_protocol.h>
namespace Signal {
@ -16,6 +20,10 @@ namespace Signal {
Context(Context &&) = delete;
Context &operator=(const Context &) = delete;
std::unique_ptr<Crypto::ECKeyPair> generateCurveKeyPair();
signal_context *temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped();
private:
signal_context *ctx{nullptr};
};

View File

@ -7,4 +7,6 @@ target_sources(squawk PRIVATE
hmac_sha256_openssl.h
sha512_digest_openssl.cpp
sha512_digest_openssl.h
ec.cpp
ec.h
)

View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-06-17.
*/
#include "ec.h"

20
qomemo/signal/crypto/ec.h Normal file
View File

@ -0,0 +1,20 @@
/*
* Created by victoria on 2021-06-17.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Crypto {
class ECKeyPair {
public:
ECKeyPair();
~ECKeyPair();
private:
ec_key_pair *ec;
};
}

View File

@ -4,13 +4,56 @@
#include "identity_key_store.h"
void Signal::Store::IdentityKeyStore::boundToContext(
signal_protocol_store_context *ctx) {
signal_protocol_identity_key_store store{};
#include "qomemo/signal/util.h"
#include <utility>
store.user_data = nullptr;
store.destroy_func = nullptr;
Signal::Store::IdentityKeyStore::IdentityKeyStore(QXmpp::Omemo::DeviceService &deviceService, QString jid, int deviceId)
: jid(std::move(jid)), deviceId(deviceId), deviceService(deviceService) {
database = deviceService.getDatabase(jid);
}
int Signal::Store::IdentityKeyStore::getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data) {
auto pk = database->loadIdentityKey(deviceId);
auto sk = database->loadIdentityKeySecret(deviceId);
*public_data = signal_buffer_create(reinterpret_cast<const uint8_t *>(pk->data()), pk->size());
*private_data = signal_buffer_create(reinterpret_cast<const uint8_t *>(sk->data()), sk->size());
return 0;
}
int Signal::Store::IdentityKeyStore::getLocalRegistrationId(uint32_t *registration_id) {
// TODO: Figure out what registration id is used for
*registration_id = 1;
return 0;
}
int Signal::Store::IdentityKeyStore::saveIdentity(const signal_protocol_address *address, uint8_t *key_data,
size_t key_len) {
auto identityJid = Signal::Util::jidFromAddress(address);
auto identityKey = Signal::Util::byteArray(key_data, key_len);
deviceService.getDatabase(identityJid)->saveIdentityKey(address->device_id, identityKey);
return 0;
}
int Signal::Store::IdentityKeyStore::isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data,
size_t key_len) {
auto identityJid = Signal::Util::jidFromAddress(address);
auto actualIdentityKey = deviceService.getDatabase(identityJid)->loadIdentityKey(address->device_id);
if (!actualIdentityKey.has_value()) {
return 1;
}
auto givenIdentityKey = Signal::Util::byteArray(key_data, key_len);
return givenIdentityKey == actualIdentityKey ? 1 : 0;
}
void Signal::Store::IdentityKeyStore::fillCallbacks(signal_protocol_identity_key_store &store) {
store.get_identity_key_pair = [](signal_buffer **public_data, signal_buffer **private_data, void *ptr) {
return static_cast<IdentityKeyStore *>(ptr)->getIdentityKeyPair(public_data, private_data);
};
@ -24,24 +67,4 @@ void Signal::Store::IdentityKeyStore::boundToContext(
store.save_identity = [](const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *ptr) {
return static_cast<IdentityKeyStore *>(ptr)->saveIdentity(address, key_data, key_len);
};
signal_protocol_store_context_set_identity_key_store(ctx, &store);
}
int Signal::Store::IdentityKeyStore::getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data) {
return 0;
}
int Signal::Store::IdentityKeyStore::getLocalRegistrationId(uint32_t *registration_id) {
return 0;
}
int Signal::Store::IdentityKeyStore::saveIdentity(const signal_protocol_address *address, uint8_t *key_data,
size_t key_len) {
return 0;
}
int Signal::Store::IdentityKeyStore::isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data,
size_t key_len) {
return 0;
}

View File

@ -6,16 +6,27 @@
#include <signal/signal_protocol.h>
#include "qomemo/device_service.h"
namespace Signal::Store {
class IdentityKeyStore {
public:
static void boundToContext(signal_protocol_store_context *ctx);
IdentityKeyStore(QXmpp::Omemo::DeviceService &deviceService, QString jid, int deviceId);
int getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data);
int getLocalRegistrationId(uint32_t *registration_id);
int saveIdentity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len);
int isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len);
void fillCallbacks(signal_protocol_identity_key_store &store);
const QString jid;
const int deviceId;
private:
QXmpp::Omemo::DeviceService &deviceService;
QSharedPointer<QXmpp::Omemo::Database> database;
};
} // namespace Signal::Store

View File

@ -4,29 +4,8 @@
#include "pre_key_store.h"
void Signal::Store::PreKeyStore::boundToContext(
signal_protocol_store_context *ctx) {
signal_protocol_pre_key_store store{};
store.destroy_func = nullptr;
store.user_data = nullptr;
store.contains_pre_key = [](uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->containsPreKey(id);
};
store.load_pre_key = [](signal_buffer **record, uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->loadPreKey(record, id);
};
store.remove_pre_key = [](uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->removePreKey(id);
};
store.store_pre_key = [](uint32_t id, uint8_t *record, size_t size,
void *ptr) {
return static_cast<PreKeyStore *>(ptr)->storePreKey(id, record, size);
};
signal_protocol_store_context_set_pre_key_store(ctx, &store);
}
Signal::Store::PreKeyStore::PreKeyStore(QSharedPointer<QXmpp::Omemo::Database> database) : database(
std::move(database)) {}
int Signal::Store::PreKeyStore::containsPreKey(uint32_t pre_key_id) {
return 0;
@ -41,3 +20,19 @@ int Signal::Store::PreKeyStore::storePreKey(uint32_t pre_key_id, uint8_t *record
}
int Signal::Store::PreKeyStore::removePreKey(uint32_t pre_key_id) { return 0; }
void Signal::Store::PreKeyStore::fillCallbacks(signal_protocol_pre_key_store &store) {
store.contains_pre_key = [](uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->containsPreKey(id);
};
store.load_pre_key = [](signal_buffer **record, uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->loadPreKey(record, id);
};
store.remove_pre_key = [](uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->removePreKey(id);
};
store.store_pre_key = [](uint32_t id, uint8_t *record, size_t size,
void *ptr) {
return static_cast<PreKeyStore *>(ptr)->storePreKey(id, record, size);
};
}

View File

@ -6,16 +6,23 @@
#include <signal/signal_protocol.h>
#include "qomemo/device_service.h"
namespace Signal::Store {
class PreKeyStore {
public:
static void boundToContext(signal_protocol_store_context *ctx);
explicit PreKeyStore(QSharedPointer<QXmpp::Omemo::Database> database);
int containsPreKey(uint32_t pre_key_id);
int loadPreKey(signal_buffer **record, uint32_t pre_key_id);
int storePreKey(uint32_t pre_key_id, uint8_t *record, size_t record_len);
int removePreKey(uint32_t pre_key_id);
void fillCallbacks(signal_protocol_pre_key_store &store);
private:
QSharedPointer<QXmpp::Omemo::Database> database;
};
} // namespace Signal::Store

14
qomemo/signal/util.cpp Normal file
View File

@ -0,0 +1,14 @@
/*
* Created by victoria on 2021-05-15.
*/
#include "util.h"
QString Signal::Util::jidFromAddress(const signal_protocol_address *address) {
// TODO: Validate this
return QString::fromRawData(reinterpret_cast<const QChar *>(address->name), static_cast<int>(address->name_len));
}
QByteArray Signal::Util::byteArray(const uint8_t *data, size_t len) {
return QByteArray::fromRawData(reinterpret_cast<const char *>(data), static_cast<int>(len));
}

18
qomemo/signal/util.h Normal file
View File

@ -0,0 +1,18 @@
/*
* Created by victoria on 2021-05-15.
*/
#pragma once
#include <signal/signal_protocol.h>
#include <QString>
#include <QByteArray>
namespace Signal::Util {
QString jidFromAddress(const signal_protocol_address *address);
QByteArray byteArray(const uint8_t *data, size_t len);
}

View File

@ -4,10 +4,13 @@
#include "conversations.h"
#include "qomemo/bundle.h"
#include "qomemo/device.h"
#include "shared/qxmppfactories.h"
#include <QDebug>
#include <QXmppIq.h>
#include <QXmppPubSubIq.h>
using namespace QXmpp::Omemo;
using namespace QXmpp::Factories;
@ -41,13 +44,21 @@ Variant::Conversations::deviceListToXml(const DeviceList &deviceList) {
return element;
}
DeviceList Variant::Conversations::deviceListFromXml(const QXmppElement &xml) {
std::optional<DeviceList> Variant::Conversations::latestDeviceListFromPubSubNode(const QXmppElement &items) {
auto item = items.firstChildElement("item");
if (item.isNull())
return std::nullopt;
auto list = item.firstChildElement("list");
if (list.isNull())
return std::nullopt;
if (!elementMatches(list, "list", "eu.siacs.conversations.axolotl"))
return std::nullopt;
DeviceList result{};
if (!elementMatches(xml, "list", "eu.siacs.conversations.axolotl"))
return result;
auto deviceElement = xml.firstChildElement("device");
auto deviceElement = list.firstChildElement("device");
while (!deviceElement.isNull()) {
result.devices.push_back(deviceFromXml(deviceElement));
deviceElement = deviceElement.nextSiblingElement("device");
@ -65,7 +76,34 @@ QXmppIq Variant::Conversations::deviceListSetIq(const DeviceList &deviceList) {
item.appendChild(deviceListToXml(deviceList));
auto publish = createElement("publish");
publish.setAttribute("node", "eu.siacs.conversations.axolotl.devicelist");
publish.setAttribute("node", getDeviceListNode());
publish.appendChild(item);
auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
pubSub.appendChild(publish);
pubSub.appendChild(createOpenPublishOptions());
iq.setExtensions({ pubSub });
return iq;
}
QString Variant::Conversations::getDeviceListNode() const {
return "eu.siacs.conversations.axolotl.devicelist";
}
QXmppIq Variant::Conversations::bundleSetIq(int deviceId, const Bundle &bundle) {
QXmppIq iq{};
iq.setType(QXmppIq::Set);
auto item = createElement("item");
item.appendChild(bundleToXml(bundle));
auto publish = createElement("publish");
publish.setAttribute(
"node",
QString("eu.siacs.conversations.axolotl.bundles:%s").arg(deviceId));
publish.appendChild(item);
auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
@ -76,3 +114,92 @@ QXmppIq Variant::Conversations::deviceListSetIq(const DeviceList &deviceList) {
return iq;
}
QXmppElement Variant::Conversations::bundleToXml(const Bundle &bundle) {
auto spkNode = createElement("signedPreKeyPublic");
spkNode.setAttribute("signedPreKeyId", QString::number(bundle.spkId));
spkNode.setValue(bundle.spk.toBase64());
auto spksNode = createElement("signedPreKeySignature");
spksNode.setValue(bundle.spks.toBase64());
auto ikNode = createElement("identityKey");
ikNode.setValue(bundle.ik.toBase64());
auto prekeysNode = createElement("prekeys");
for (const auto &pk : bundle.prekeys) {
prekeysNode.appendChild(preKeyToXml(pk));
}
auto result = createElement("bundle", "eu.siacs.conversations.axolotl");
result.appendChild(spkNode);
result.appendChild(spksNode);
result.appendChild(ikNode);
result.appendChild(prekeysNode);
return result;
}
QXmppElement Variant::Conversations::preKeyToXml(const PreKey &pk) {
auto x = createElement("preKeyPublic");
x.setAttribute("preKeyId", QString::number(pk.id));
x.setValue(pk.data.toBase64());
return x;
}
std::optional<PreKey> Variant::Conversations::preKeyFromXml(const QXmppElement &xml) {
if (!elementMatches(xml, "preKeyPublic"))
return std::nullopt;
auto pk = PreKey();
pk.id = xml.attribute("preKeyId").toInt();
pk.data = QByteArray::fromBase64Encoding(xml.value().toUtf8()).decoded;
return pk;
}
std::optional<Bundle> Variant::Conversations::bundleFromXml(const QXmppElement &xml) {
if (!elementMatches(xml, "bundle", "eu.siacs.conversations.axolotl"))
return std::nullopt;
auto spkNode = xml.firstChildElement("signedPreKeyPublic");
if (spkNode.isNull()) {
qWarning() << "'bundle': missing 'signedPreKeyPublic'";
return std::nullopt;
}
Bundle result{};
result.spk = QByteArray::fromBase64Encoding(spkNode.value().toUtf8()).decoded;
result.spkId = spkNode.attribute("signedPreKeyId").toInt();
auto spksNode = xml.firstChildElement("signedPreKeySignature");
if (spksNode.isNull()) {
qWarning() << "'bundle': missing 'signedPreKeySignature'";
return std::nullopt;
}
result.spks = QByteArray::fromBase64Encoding(spksNode.value().toUtf8()).decoded;
auto ikNode = xml.firstChildElement("identityKey");
if (ikNode.isNull()) {
qWarning() << "'bundle': missing 'identityKey'";
return std::nullopt;
}
result.ik = QByteArray::fromBase64Encoding(ikNode.value().toUtf8()).decoded;
auto prekeysNode = xml.firstChildElement("prekeys");
auto pkNode = prekeysNode.firstChildElement("preKeyPublic");
result.prekeys.clear();
while (!pkNode.isNull()) {
auto maybePk = preKeyFromXml(pkNode);
if (maybePk)
result.prekeys.push_back(*maybePk);
pkNode = pkNode.nextSiblingElement("preKeyPublic");
}
return result;
}

View File

@ -15,9 +15,18 @@ namespace QXmpp::Omemo::Variant {
QXmppElement deviceToXml(const Device &device) override;
Device deviceFromXml(const QXmppElement &xml) override;
[[nodiscard]] QString getDeviceListNode() const override;
QXmppElement deviceListToXml(const DeviceList &deviceList) override;
DeviceList deviceListFromXml(const QXmppElement &xml) override;
std::optional<DeviceList> latestDeviceListFromPubSubNode(const QXmppElement &items) override;
QXmppIq deviceListSetIq(const DeviceList &deviceList) override;
QXmppElement preKeyToXml(const PreKey &pk) override;
std::optional<PreKey> preKeyFromXml(const QXmppElement &xml) override;
QXmppElement bundleToXml(const Bundle &bundle) override;
std::optional<Bundle> bundleFromXml(const QXmppElement &xml) override;
QXmppIq bundleSetIq(int deviceId, const Bundle &bundle) override;
};
} // namespace QXmpp::Omemo::Variant

View File

@ -4,14 +4,17 @@
#pragma once
class QXmppElement;
#include <optional>
class QString;
class QXmppElement;
class QXmppIq;
namespace QXmpp::Omemo {
class PreKey;
class Bundle;
class Device;
class DeviceList;
namespace Variant {
@ -23,9 +26,18 @@ namespace QXmpp::Omemo {
virtual QXmppElement deviceToXml(const Device &device) = 0;
virtual Device deviceFromXml(const QXmppElement &xml) = 0;
[[nodiscard]] virtual QString getDeviceListNode() const = 0;
virtual QXmppElement deviceListToXml(const DeviceList &deviceList) = 0;
virtual DeviceList deviceListFromXml(const QXmppElement &xml) = 0;
virtual std::optional<DeviceList> latestDeviceListFromPubSubNode(const QXmppElement &xml) = 0;
virtual QXmppIq deviceListSetIq(const DeviceList &deviceList) = 0;
virtual QXmppElement preKeyToXml(const PreKey &pk) = 0;
virtual std::optional<PreKey> preKeyFromXml(const QXmppElement &xml) = 0;
virtual QXmppElement bundleToXml(const Bundle& bundle) = 0;
virtual std::optional<Bundle> bundleFromXml(const QXmppElement& xml) = 0;
virtual QXmppIq bundleSetIq(int deviceId, const Bundle& bundle) = 0;
};
} // namespace Variant

View File

@ -6,17 +6,19 @@
#include "ui_contactsettings.h"
#include "omemodevices.h"
ContactSettings::ContactSettings(QWidget *parent)
: QDialog(parent), m_ui(new Ui::ContactSettings()) {
ContactSettings::ContactSettings(QString jid, QWidget *parent)
: QDialog(parent), jid(std::move(jid)), m_ui(new Ui::ContactSettings()) {
m_ui->setupUi(this);
connect(m_ui->devicesButton, &QPushButton::clicked, this, &ContactSettings::openDeviceList);
setWindowTitle(QString("Encryption settings for %1").arg(this->jid));
}
ContactSettings::~ContactSettings() {}
void ContactSettings::openDeviceList() {
auto devices = new OMEMODevices(this);
auto devices = new OMEMODevices(jid, this);
devices->setAttribute(Qt::WA_DeleteOnClose);
devices->show();

View File

@ -13,9 +13,11 @@ namespace Ui {
class ContactSettings : public QDialog {
Q_OBJECT
public:
explicit ContactSettings(QWidget *parent = nullptr);
explicit ContactSettings(QString jid, QWidget *parent = nullptr);
~ContactSettings() override;
const QString jid;
private slots:
void openDeviceList();

View File

@ -5,9 +5,11 @@
#include "omemodevices.h"
#include "ui_omemodevices.h"
OMEMODevices::OMEMODevices(QWidget *parent)
: QDialog(parent), m_ui(new Ui::OMEMODevices()) {
OMEMODevices::OMEMODevices(QString jid, QWidget *parent)
: QDialog(parent), jid(std::move(jid)), m_ui(new Ui::OMEMODevices()) {
m_ui->setupUi(this);
setWindowTitle(QString("%1's OMEMO devices").arg(this->jid));
}
OMEMODevices::~OMEMODevices() {}

View File

@ -13,9 +13,11 @@ namespace Ui {
class OMEMODevices : public QDialog {
Q_OBJECT
public:
explicit OMEMODevices(QWidget *parent = nullptr);
explicit OMEMODevices(QString jid, QWidget *parent = nullptr);
~OMEMODevices() override;
const QString jid;
private:
QScopedPointer<Ui::OMEMODevices> m_ui;
};

View File

@ -20,6 +20,7 @@
#include "ui_squawk.h"
#include <QDebug>
#include <QIcon>
#include <utility>
#include <ui/omemo/omemodevices.h>
Squawk::Squawk(QWidget *parent) :
@ -61,7 +62,6 @@ Squawk::Squawk(QWidget *parent) :
connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
connect(&rosterModel, &Models::Roster::unnoticedMessage, this, &Squawk::onUnnoticedMessage);
connect(m_ui->actionDeviceList, &QAction::triggered, this, &Squawk::onOMEMODevices);
connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
@ -136,8 +136,8 @@ void Squawk::onNewContact()
nc->exec();
}
void Squawk::onOMEMODevices() {
auto od = new OMEMODevices(this);
void Squawk::openDeviceList(QString bareJid) {
auto od = new OMEMODevices(std::move(bareJid), this);
od->setAttribute(Qt::WA_DeleteOnClose);
od->show();
@ -540,6 +540,10 @@ void Squawk::onRosterContextMenu(const QPoint& point)
});
}
QAction* devices = contextMenu->addAction(Shared::icon("security-high"), tr("Devices"));
devices->setEnabled(active);
connect(devices, &QAction::triggered, [this, acc]() { openDeviceList(acc->getBareJid()); });
QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
card->setEnabled(active);
connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true));

View File

@ -155,7 +155,7 @@ private slots:
void onPasswordPromptRejected();
void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
void onContextAboutToHide();
void onOMEMODevices();
void openDeviceList(QString bareJid);
void onUnnoticedMessage(const QString& account, const Shared::Message& msg);

View File

@ -169,17 +169,7 @@
<property name="title">
<string>Settings</string>
</property>
<widget class="QMenu" name="menuOMEMO">
<property name="title">
<string>OMEMO</string>
</property>
<property name="icon">
<iconset theme="security-high"/>
</property>
<addaction name="actionDeviceList"/>
</widget>
<addaction name="actionAccounts"/>
<addaction name="menuOMEMO"/>
</widget>
<widget class="QMenu" name="menuFile">
<property name="title">
@ -236,7 +226,8 @@
</action>
<action name="actionDeviceList">
<property name="icon">
<iconset theme="computer-laptop"/>
<iconset theme="computer-laptop">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Device list</string>

View File

@ -437,7 +437,7 @@ void Conversation::onFeedContext(const QPoint& pos)
}
void Conversation::openEncryptionSettings() {
auto cs = new ContactSettings(this);
auto cs = new ContactSettings(palJid, this);
cs->setAttribute(Qt::WA_DeleteOnClose);
cs->show();