293 lines
8.9 KiB
C++
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 ¤tList) {
|
|
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 ¤tList) {
|
|
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(), ¤tPreKey);
|
|
|
|
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;
|
|
}
|