squawk/qomemo/qxmpp_omemo_manager.cpp

293 lines
8.9 KiB
C++

/*
* Created by victoria on 2021-05-12.
*/
#include "qxmpp_omemo_manager.h"
#include "qomemo/signal/context.h"
#include "bundle.h"
#include "device.h"
#include "variant/conversations.h"
#include <QDomElement>
#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)},
omemoVariant(new Variant::Conversations),
signalContext(new Signal::Context) {
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) {
QString str{};
QTextStream info(&str);
stanza.save(info, 4);
std::cout << str.toStdString();
if (handleDeviceList(stanza) || handleMissingDeviceList(stanza) || handleEncryptedMessage(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())
return false;
auto items = pubsub.firstChildElement("items");
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;
}
bool Manager::handleEncryptedMessage(const QDomElement &stanza) {
if (stanza.tagName() != "message")
return false;
auto encrypted = stanza.firstChildElement("encrypted");
if (encrypted.isNull())
return false;
qDebug() << "!!!! Got encrypted message!!";
return true;
}
void QXmpp::Omemo::Manager::setClient(QXmppClient *client) {
QXmppClientExtension::setClient(client);
if (!client)
return;
QObject::connect(client, &QXmppClient::connected, this,
&Manager::fetchOwnDevices);
}
void QXmpp::Omemo::Manager::fetchOwnDevices() {
QXmppPubSubIq iq{};
iq.setFrom(client()->configuration().jid());
iq.setTo(client()->configuration().jidBare());
iq.setType(QXmppIq::Get);
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::system()->bounded(INT32_MAX);
db->saveActiveDeviceId(deviceId);
Device device{};
device.id = deviceId;
auto updatedList = currentList;
updatedList.devices.push_back(device);
publishDeviceList(updatedList);
auto bundle = generateAndSaveBundle(deviceId);
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);
result.ik = identityKey;
// 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 = 1;
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));
result.spk = spk.key.publicKey;
result.spks = spk.signature;
result.spkId = 1;
// 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);
PreKey pk{};
pk.data = preKey.publicKey;
pk.id = i;
result.prekeys.append(pk);
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);
return result;
}