diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt index 8b194be..7c71af1 100644 --- a/qomemo/CMakeLists.txt +++ b/qomemo/CMakeLists.txt @@ -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 diff --git a/qomemo/bundle.cpp b/qomemo/bundle.cpp index 691a65e..d27cd1e 100644 --- a/qomemo/bundle.cpp +++ b/qomemo/bundle.cpp @@ -3,127 +3,3 @@ */ #include "bundle.h" - -#include - -#include - -#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; -} \ No newline at end of file diff --git a/qomemo/bundle.h b/qomemo/bundle.h index 428277b..61e8185 100644 --- a/qomemo/bundle.h +++ b/qomemo/bundle.h @@ -6,6 +6,7 @@ #include #include +#include class QXmppPubSubIq; @@ -17,26 +18,16 @@ namespace QXmpp::Omemo { class PreKey { public: - [[nodiscard]] QXmppElement toXml() const; - /// Expects a - 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 prekeys; }; diff --git a/qomemo/database.cpp b/qomemo/database.cpp index 2314a80..2923634 100644 --- a/qomemo/database.cpp +++ b/qomemo/database.cpp @@ -4,6 +4,8 @@ #include "database.h" +#include "bundle.h" + #include #include #include @@ -44,17 +46,17 @@ Database::~Database() { mdb_env_close(env); } -QBuffer Database::loadIdentityKeySecret(int deviceId) { return QBuffer(); } +std::optional 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(identityKeySecret.data().data()); + mdbValue.mv_data = const_cast(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 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(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 Database::loadIdentityKey(int deviceId) { + return std::nullopt; +} + +bool Database::containsPreKey() { + return false; +} + +std::optional Database::loadPreKey(int deviceId, int id) { + return std::nullopt; +} + +bool Database::savePreKey(int deviceId, int id, const KeyPair &preKey) { + return false; +} + +std::optional 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 Database::loadSignedPreKey(int deviceId) { + return std::optional(); +} + +bool Database::saveSignedPreKey(int deviceId, const SignedPreKey &signedPreKey) { + return false; +} diff --git a/qomemo/database.h b/qomemo/database.h index 3f9b933..1edeea6 100644 --- a/qomemo/database.h +++ b/qomemo/database.h @@ -8,8 +8,12 @@ #include #include +#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 loadActiveDeviceId(); bool saveActiveDeviceId(int deviceId); - QBuffer loadIdentityKeySecret(int deviceId); - bool saveIdentityKeySecret(int deviceId, const QBuffer &identityKeySecret); + std::optional loadIdentityKeySecret(int deviceId); + bool saveIdentityKeySecret(int deviceId, const QByteArray &identityKeySecret); + + std::optional loadBundle(int deviceId); + bool saveBundle(int deviceId, const Bundle& bundle); + + // For any user + std::optional loadIdentityKey(int deviceId); + bool saveIdentityKey(int deviceId, const QByteArray &identityKey); + + bool containsPreKey(); + std::optional loadPreKey(int deviceId, int id); + bool savePreKey(int deviceId, int id, const KeyPair &preKey); + + std::optional loadSignedPreKey(int deviceId); + bool saveSignedPreKey(int deviceId, const SignedPreKey& signedPreKey); const QString jid; diff --git a/qomemo/device_service.cpp b/qomemo/device_service.cpp index 306f383..653a3a6 100644 --- a/qomemo/device_service.cpp +++ b/qomemo/device_service.cpp @@ -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 DeviceService::getDatabase(const QString &jid) { + if (!databases.contains(jid)) { + databases.insert(jid, QSharedPointer::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; +} diff --git a/qomemo/device_service.h b/qomemo/device_service.h index a9529c4..2442486 100644 --- a/qomemo/device_service.h +++ b/qomemo/device_service.h @@ -4,8 +4,11 @@ #pragma once +#include + #include "qomemo.h" #include "user_device_list.h" +#include "database.h" #include @@ -19,11 +22,17 @@ namespace QXmpp::Omemo { public: explicit DeviceService(QObject *parent); + QSharedPointer 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 device_lists{}; + QMap> databases{}; }; } // namespace QXmpp::Omemo diff --git a/qomemo/key.cpp b/qomemo/key.cpp new file mode 100644 index 0000000..7e9cc30 --- /dev/null +++ b/qomemo/key.cpp @@ -0,0 +1,6 @@ +/* + * Created by victoria on 2021-05-15. +*/ + +#include "key.h" + \ No newline at end of file diff --git a/qomemo/key.h b/qomemo/key.h new file mode 100644 index 0000000..a0910c7 --- /dev/null +++ b/qomemo/key.h @@ -0,0 +1,27 @@ +/* + * Created by victoria on 2021-05-15. +*/ + +#pragma once + +#include + +#include + +namespace QXmpp::Omemo { + + class KeyPair { + public: + QByteArray publicKey{}; + std::optional secretKey{ std::nullopt }; + }; + + class SignedPreKey { + public: + int id{ 0 }; + + KeyPair key{}; + QByteArray signature{}; + }; + +} diff --git a/qomemo/qxmpp_omemo_manager.cpp b/qomemo/qxmpp_omemo_manager.cpp index 844a189..788fc96 100644 --- a/qomemo/qxmpp_omemo_manager.cpp +++ b/qomemo/qxmpp_omemo_manager.cpp @@ -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 #include #include +#include +#include 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 ¤tList) { + if (jid == client()->configuration().jidBare()) + generateDeviceForSelfIfNeeded(currentList); + }); } bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) { @@ -28,31 +42,62 @@ bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) { std::cout << str.toStdString(); - if (stanza.tagName() == "iq") { - if (stanza.attribute("type") == "result") { - auto pubsub = stanza.firstChildElement("pubsub"); - if (!pubsub.isNull()) { - 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); - - return true; - } - } - } - } - } - } + 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()) + 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; +} + void QXmpp::Omemo::Manager::setClient(QXmppClient *client) { QXmppClientExtension::setClient(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 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{}.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(), ¤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); + + 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); +} diff --git a/qomemo/qxmpp_omemo_manager.h b/qomemo/qxmpp_omemo_manager.h index aae7467..7f04026 100644 --- a/qomemo/qxmpp_omemo_manager.h +++ b/qomemo/qxmpp_omemo_manager.h @@ -10,6 +10,10 @@ #include +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 getDeviceService(); + + Bundle generateAndSaveBundle(int deviceId); + public slots: void fetchOwnDevices(); + void publishDeviceList(const QXmpp::Omemo::DeviceList& deviceList); + + void generateDeviceListForSelf(); + + void generateDeviceForSelfIfNeeded(const QXmpp::Omemo::DeviceList ¤tList); + + 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; + QSharedPointer deviceService; QScopedPointer omemoVariant; + QScopedPointer signalContext; }; } // namespace QXmpp::Omemo diff --git a/qomemo/signal/CMakeLists.txt b/qomemo/signal/CMakeLists.txt index 60cfdd8..7223904 100644 --- a/qomemo/signal/CMakeLists.txt +++ b/qomemo/signal/CMakeLists.txt @@ -1,6 +1,8 @@ target_sources(squawk PRIVATE context.cpp context.h + util.cpp + util.h ) add_subdirectory(crypto) diff --git a/qomemo/signal/context.cpp b/qomemo/signal/context.cpp index bc2322e..6253faa 100644 --- a/qomemo/signal/context.cpp +++ b/qomemo/signal/context.cpp @@ -4,6 +4,19 @@ #include "context.h" -Signal::Context::Context() {} +using namespace Signal; -Signal::Context::~Context() {} +Context::Context() {} + +Context::~Context() {} + +std::unique_ptr Context::generateCurveKeyPair() { + auto result = std::unique_ptr(); + // TODO + + return result; +} + +signal_context *Context::temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped() { + return ctx; +} diff --git a/qomemo/signal/context.h b/qomemo/signal/context.h index c398105..b829aca 100644 --- a/qomemo/signal/context.h +++ b/qomemo/signal/context.h @@ -4,6 +4,10 @@ #pragma once +#include "crypto/ec.h" + +#include + #include namespace Signal { @@ -16,6 +20,10 @@ namespace Signal { Context(Context &&) = delete; Context &operator=(const Context &) = delete; + std::unique_ptr generateCurveKeyPair(); + + signal_context *temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(); + private: signal_context *ctx{nullptr}; }; diff --git a/qomemo/signal/crypto/CMakeLists.txt b/qomemo/signal/crypto/CMakeLists.txt index 6359510..8f43895 100644 --- a/qomemo/signal/crypto/CMakeLists.txt +++ b/qomemo/signal/crypto/CMakeLists.txt @@ -7,4 +7,6 @@ target_sources(squawk PRIVATE hmac_sha256_openssl.h sha512_digest_openssl.cpp sha512_digest_openssl.h + ec.cpp + ec.h ) diff --git a/qomemo/signal/crypto/ec.cpp b/qomemo/signal/crypto/ec.cpp new file mode 100644 index 0000000..875d2ee --- /dev/null +++ b/qomemo/signal/crypto/ec.cpp @@ -0,0 +1,5 @@ +/* + * Created by victoria on 2021-06-17. +*/ + +#include "ec.h" diff --git a/qomemo/signal/crypto/ec.h b/qomemo/signal/crypto/ec.h new file mode 100644 index 0000000..280d00a --- /dev/null +++ b/qomemo/signal/crypto/ec.h @@ -0,0 +1,20 @@ +/* + * Created by victoria on 2021-06-17. +*/ + +#pragma once + +#include + +namespace Signal::Crypto { + + class ECKeyPair { + public: + ECKeyPair(); + ~ECKeyPair(); + + private: + ec_key_pair *ec; + }; + +} diff --git a/qomemo/signal/stores/identity_key_store.cpp b/qomemo/signal/stores/identity_key_store.cpp index a8b28df..e80b7d3 100644 --- a/qomemo/signal/stores/identity_key_store.cpp +++ b/qomemo/signal/stores/identity_key_store.cpp @@ -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 - 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(pk->data()), pk->size()); + *private_data = signal_buffer_create(reinterpret_cast(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(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(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; } diff --git a/qomemo/signal/stores/identity_key_store.h b/qomemo/signal/stores/identity_key_store.h index 4179430..017ca42 100644 --- a/qomemo/signal/stores/identity_key_store.h +++ b/qomemo/signal/stores/identity_key_store.h @@ -6,16 +6,27 @@ #include +#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 database; }; } // namespace Signal::Store diff --git a/qomemo/signal/stores/pre_key_store.cpp b/qomemo/signal/stores/pre_key_store.cpp index fe4cd60..72bfe72 100644 --- a/qomemo/signal/stores/pre_key_store.cpp +++ b/qomemo/signal/stores/pre_key_store.cpp @@ -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(ptr)->containsPreKey(id); - }; - store.load_pre_key = [](signal_buffer **record, uint32_t id, void *ptr) { - return static_cast(ptr)->loadPreKey(record, id); - }; - store.remove_pre_key = [](uint32_t id, void *ptr) { - return static_cast(ptr)->removePreKey(id); - }; - store.store_pre_key = [](uint32_t id, uint8_t *record, size_t size, - void *ptr) { - return static_cast(ptr)->storePreKey(id, record, size); - }; - - signal_protocol_store_context_set_pre_key_store(ctx, &store); -} +Signal::Store::PreKeyStore::PreKeyStore(QSharedPointer 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(ptr)->containsPreKey(id); + }; + store.load_pre_key = [](signal_buffer **record, uint32_t id, void *ptr) { + return static_cast(ptr)->loadPreKey(record, id); + }; + store.remove_pre_key = [](uint32_t id, void *ptr) { + return static_cast(ptr)->removePreKey(id); + }; + store.store_pre_key = [](uint32_t id, uint8_t *record, size_t size, + void *ptr) { + return static_cast(ptr)->storePreKey(id, record, size); + }; +} diff --git a/qomemo/signal/stores/pre_key_store.h b/qomemo/signal/stores/pre_key_store.h index eb70786..0ec1a79 100644 --- a/qomemo/signal/stores/pre_key_store.h +++ b/qomemo/signal/stores/pre_key_store.h @@ -6,16 +6,23 @@ #include +#include "qomemo/device_service.h" + namespace Signal::Store { class PreKeyStore { public: - static void boundToContext(signal_protocol_store_context *ctx); + explicit PreKeyStore(QSharedPointer 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 database; }; } // namespace Signal::Store diff --git a/qomemo/signal/util.cpp b/qomemo/signal/util.cpp new file mode 100644 index 0000000..9c2ed08 --- /dev/null +++ b/qomemo/signal/util.cpp @@ -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(address->name), static_cast(address->name_len)); +} + +QByteArray Signal::Util::byteArray(const uint8_t *data, size_t len) { + return QByteArray::fromRawData(reinterpret_cast(data), static_cast(len)); +} diff --git a/qomemo/signal/util.h b/qomemo/signal/util.h new file mode 100644 index 0000000..e1145ef --- /dev/null +++ b/qomemo/signal/util.h @@ -0,0 +1,18 @@ +/* + * Created by victoria on 2021-05-15. +*/ + +#pragma once + +#include + +#include +#include + +namespace Signal::Util { + + QString jidFromAddress(const signal_protocol_address *address); + + QByteArray byteArray(const uint8_t *data, size_t len); + +} diff --git a/qomemo/variant/conversations.cpp b/qomemo/variant/conversations.cpp index 4fc6af6..84926cb 100644 --- a/qomemo/variant/conversations.cpp +++ b/qomemo/variant/conversations.cpp @@ -4,10 +4,13 @@ #include "conversations.h" +#include "qomemo/bundle.h" #include "qomemo/device.h" #include "shared/qxmppfactories.h" +#include #include +#include 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 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 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 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; +} diff --git a/qomemo/variant/conversations.h b/qomemo/variant/conversations.h index 923e29b..5a467f0 100644 --- a/qomemo/variant/conversations.h +++ b/qomemo/variant/conversations.h @@ -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 latestDeviceListFromPubSubNode(const QXmppElement &items) override; QXmppIq deviceListSetIq(const DeviceList &deviceList) override; + + QXmppElement preKeyToXml(const PreKey &pk) override; + std::optional preKeyFromXml(const QXmppElement &xml) override; + + QXmppElement bundleToXml(const Bundle &bundle) override; + std::optional bundleFromXml(const QXmppElement &xml) override; + + QXmppIq bundleSetIq(int deviceId, const Bundle &bundle) override; }; } // namespace QXmpp::Omemo::Variant diff --git a/qomemo/variant/omemo_base.h b/qomemo/variant/omemo_base.h index 58910fe..88d2a9f 100644 --- a/qomemo/variant/omemo_base.h +++ b/qomemo/variant/omemo_base.h @@ -4,14 +4,17 @@ #pragma once -class QXmppElement; +#include +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 latestDeviceListFromPubSubNode(const QXmppElement &xml) = 0; virtual QXmppIq deviceListSetIq(const DeviceList &deviceList) = 0; + + virtual QXmppElement preKeyToXml(const PreKey &pk) = 0; + virtual std::optional preKeyFromXml(const QXmppElement &xml) = 0; + + virtual QXmppElement bundleToXml(const Bundle& bundle) = 0; + virtual std::optional bundleFromXml(const QXmppElement& xml) = 0; + + virtual QXmppIq bundleSetIq(int deviceId, const Bundle& bundle) = 0; }; } // namespace Variant diff --git a/ui/omemo/contactsettings.cpp b/ui/omemo/contactsettings.cpp index c4d20b9..ba9704f 100644 --- a/ui/omemo/contactsettings.cpp +++ b/ui/omemo/contactsettings.cpp @@ -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(); diff --git a/ui/omemo/contactsettings.h b/ui/omemo/contactsettings.h index 4720c1f..37dc4c1 100644 --- a/ui/omemo/contactsettings.h +++ b/ui/omemo/contactsettings.h @@ -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(); diff --git a/ui/omemo/omemodevices.cpp b/ui/omemo/omemodevices.cpp index 877ff27..5b08565 100644 --- a/ui/omemo/omemodevices.cpp +++ b/ui/omemo/omemodevices.cpp @@ -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() {} diff --git a/ui/omemo/omemodevices.h b/ui/omemo/omemodevices.h index 70dbb0f..529711e 100644 --- a/ui/omemo/omemodevices.h +++ b/ui/omemo/omemodevices.h @@ -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 m_ui; }; diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 13c452a..1cd9929 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -20,6 +20,7 @@ #include "ui_squawk.h" #include #include +#include #include 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(); @@ -539,6 +539,10 @@ void Squawk::onRosterContextMenu(const QPoint& point) emit connectAccount(name); }); } + + 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); diff --git a/ui/squawk.h b/ui/squawk.h index a4c130e..a7318de 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -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); diff --git a/ui/squawk.ui b/ui/squawk.ui index cb48de6..a029a44 100644 --- a/ui/squawk.ui +++ b/ui/squawk.ui @@ -169,17 +169,7 @@ Settings - - - OMEMO - - - - - - - @@ -236,7 +226,8 @@ - + + .. Device list diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 2b2547b..518847f 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -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();