diff --git a/.gitmodules b/.gitmodules index bbe5364..c205907 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "external/qxmpp"] path = external/qxmpp url = https://github.com/qxmpp-project/qxmpp.git +[submodule "external/signal-protocol-c"] + path = external/signal-protocol-c + url = https://github.com/signalapp/libsignal-protocol-c.git diff --git a/CMakeLists.txt b/CMakeLists.txt index b9349d9..9ca13cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,12 +16,14 @@ add_executable(squawk) target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}) option(SYSTEM_QXMPP "Use system qxmpp lib" ON) +option(SYSTEM_SIGNAL "Use system signal-protocol-c lib" ON) option(WITH_KWALLET "Build KWallet support module" ON) option(WITH_KIO "Build KIO support module" ON) # Dependencies ## Qt find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED) +target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml) ## QXmpp if (SYSTEM_QXMPP) @@ -42,6 +44,16 @@ else () target_link_libraries(squawk PRIVATE QXmpp::QXmpp) endif () +# Signal +if (NOT SYSTEM_SIGNAL) + add_subdirectory(external/signal-protocol-c) + add_dependencies(squawk signal-protocol-c) + target_link_libraries(squawk PRIVATE signal-protocol-c) +else () + find_package(Signal REQUIRED) + target_link_libraries(squawk PRIVATE Signal::Signal) +endif () + ## KIO if (WITH_KIO) find_package(KF5KIO CONFIG) @@ -68,15 +80,15 @@ if (WITH_KWALLET) endif () endif () -## Signal (TODO) -# find_package(Signal REQUIRED) - ## LMDB find_package(LMDB REQUIRED) - -# Linking -target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml) target_link_libraries(squawk PRIVATE lmdb) + +# OpenSSL +find_package(OpenSSL REQUIRED) +target_link_libraries(squawk PRIVATE OpenSSL::Crypto) + +# Misc target_link_libraries(squawk PRIVATE simpleCrypt) target_link_libraries(squawk PRIVATE uuid) @@ -101,6 +113,7 @@ add_subdirectory(resources) add_subdirectory(shared) add_subdirectory(translations) add_subdirectory(ui) +add_subdirectory(qomemo) # Install the executable install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/core/account.cpp b/core/account.cpp index 5ce29ee..d38e889 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -47,7 +47,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& network(p_net), passwordType(Shared::AccountPassword::plain), mh(new MessageHandler(this)), - rh(new RosterHandler(this)) + rh(new RosterHandler(this)), + omemo(new QXmpp::Omemo::Manager()) { config.setUser(p_login); config.setDomain(p_server); @@ -90,7 +91,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& client.addExtension(rcpm); QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived); - + + client.addExtension(omemo.get()); QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); path += "/" + name; diff --git a/core/account.h b/core/account.h index a0db9f9..2a3816e 100644 --- a/core/account.h +++ b/core/account.h @@ -30,23 +30,24 @@ #include #include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include +#include -#include "shared/shared.h" -#include "contact.h" #include "conference.h" +#include "contact.h" #include "networkaccess.h" +#include "shared/shared.h" #include "handlers/messagehandler.h" #include "handlers/rosterhandler.h" @@ -165,6 +166,8 @@ private: MessageHandler* mh; RosterHandler* rh; + + QScopedPointer omemo; private slots: void onClientStateChange(QXmppClient::State state); diff --git a/external/signal-protocol-c b/external/signal-protocol-c new file mode 160000 index 0000000..3a83a4f --- /dev/null +++ b/external/signal-protocol-c @@ -0,0 +1 @@ +Subproject commit 3a83a4f4ed2302ff6e68ab569c88793b50c22d28 diff --git a/qomemo/CMakeLists.txt b/qomemo/CMakeLists.txt new file mode 100644 index 0000000..7c71af1 --- /dev/null +++ b/qomemo/CMakeLists.txt @@ -0,0 +1,25 @@ +target_sources(squawk PRIVATE + bundle.cpp + bundle.h + database.cpp + database.h + device.cpp + device.h + device_key_storage.cpp + device_key_storage.h + device_service.cpp + device_service.h + key.cpp + key.h + qomemo.cpp + qomemo.h + sce.cpp + sce.h + user_device_list.cpp + user_device_list.h + qxmpp_omemo_manager.cpp + qxmpp_omemo_manager.h + ) + +add_subdirectory(signal) +add_subdirectory(variant) diff --git a/qomemo/bundle.cpp b/qomemo/bundle.cpp new file mode 100644 index 0000000..d27cd1e --- /dev/null +++ b/qomemo/bundle.cpp @@ -0,0 +1,5 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#include "bundle.h" diff --git a/qomemo/bundle.h b/qomemo/bundle.h new file mode 100644 index 0000000..61e8185 --- /dev/null +++ b/qomemo/bundle.h @@ -0,0 +1,34 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#pragma once + +#include +#include +#include + +class QXmppPubSubIq; + +class QXmppElement; + +class QXmppIq; + +namespace QXmpp::Omemo { + + class PreKey { + public: + int id; + QByteArray data; + }; + + class Bundle { + public: + QByteArray spk; + int spkId; + QByteArray spks; + QByteArray ik; + QList prekeys; + }; + +} // namespace QXmpp::Omemo diff --git a/qomemo/database.cpp b/qomemo/database.cpp new file mode 100644 index 0000000..2923634 --- /dev/null +++ b/qomemo/database.cpp @@ -0,0 +1,180 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "database.h" + +#include "bundle.h" + +#include +#include +#include +#include +#include + +using namespace QXmpp::Omemo; + +Database::Database(QString jid) : jid(std::move(jid)) { + auto cacheLocation = + QStandardPaths::writableLocation(QStandardPaths::CacheLocation); + auto path = QString("%1/.omemo/%2").arg(cacheLocation, jid); + QDir cache(path); + + if (!cache.exists() && !cache.mkpath(path)) { + qWarning() << "Could not create:" << path; + throw QException(); + } + + mdb_env_create(&env); + + mdb_env_set_maxdbs(env, 5); + mdb_env_set_mapsize(env, 512UL * 1024UL * 1024UL); + mdb_env_open(env, path.toStdString().c_str(), 0, 0664); + + MDB_txn *txn; + mdb_txn_begin(env, nullptr, 0, &txn); + mdb_dbi_open(txn, "keys", MDB_CREATE, &dbiKeys); + mdb_dbi_open(txn, "devices", MDB_CREATE, &dbiDevices); + mdb_dbi_open(txn, "identity_keys", MDB_CREATE, &dbiIdentityKeys); + mdb_txn_commit(txn); +} + +Database::~Database() { + mdb_dbi_close(env, dbiKeys); + mdb_dbi_close(env, dbiDevices); + mdb_dbi_close(env, dbiIdentityKeys); + mdb_env_close(env); +} + +std::optional Database::loadIdentityKeySecret(int deviceId) { return std::nullopt; } + +bool Database::saveIdentityKeySecret(int deviceId, + 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()); + mdbValue.mv_size = identityKeySecret.size(); + + MDB_txn *txn; + mdb_txn_begin(env, nullptr, 0, &txn); + auto err = mdb_put(txn, dbiIdentityKeys, &mdbKey, &mdbValue, MDB_NOOVERWRITE); + if (!err) { + mdb_txn_commit(txn); + return true; + } + + qWarning() << "could not save identity key secret:" << mdb_strerror(err); + mdb_txn_abort(txn); + + return false; +} + +std::optional Database::loadActiveDeviceId() { + MDB_val key, value; + + key.mv_data = (void *) "active"; + key.mv_size = sizeof("active"); + + MDB_txn *txn; + mdb_txn_begin(env, nullptr, 0, &txn); + + auto err = mdb_get(txn, dbiIdentityKeys, &key, &value); + if (err) { + qWarning() << "could not load active device id:" << mdb_strerror(err); + mdb_txn_abort(txn); + return std::nullopt; + } + + if (value.mv_size != sizeof(int)) { + qWarning() << "mv_size is" << value.mv_size << "instead of" << sizeof(int); + mdb_txn_abort(txn); + return std::nullopt; + } + + auto id = *reinterpret_cast(value.mv_data); + + mdb_txn_abort(txn); + + return id; +} + +bool Database::saveActiveDeviceId(int deviceId) { + MDB_val key, value; + + key.mv_data = (void *) "active"; + key.mv_size = sizeof("active"); + + value.mv_data = &deviceId; + value.mv_size = sizeof(deviceId); + + MDB_txn *txn; + mdb_txn_begin(env, nullptr, 0, &txn); + + auto err = mdb_put(txn, dbiIdentityKeys, &key, &value, 0); + if (err) { + qWarning() << "could not save active device id" << mdb_strerror(err); + return false; + } + + err = mdb_txn_commit(txn); + if (err) { + qWarning() << "could not save active device id" << mdb_strerror(err); + return false; + } + + 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 new file mode 100644 index 0000000..1edeea6 --- /dev/null +++ b/qomemo/database.h @@ -0,0 +1,56 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include +#include +#include + +#include "key.h" + +namespace QXmpp::Omemo { + + class Bundle; + + class Database { + public: + explicit Database(QString jid); + ~Database(); + Database(const Database &) = delete; + Database(Database &&) = delete; + Database &operator=(const Database &) = delete; + + // For local user + std::optional loadActiveDeviceId(); + bool saveActiveDeviceId(int deviceId); + + 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; + + private: + MDB_env *env{}; + MDB_dbi dbiDevices{}; + MDB_dbi dbiKeys{}; + MDB_dbi dbiPreKeys{}; + MDB_dbi dbiIdentityKeys{}; + }; + +} // namespace QXmpp::Omemo diff --git a/qomemo/device.cpp b/qomemo/device.cpp new file mode 100644 index 0000000..edfee1c --- /dev/null +++ b/qomemo/device.cpp @@ -0,0 +1,5 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "device.h" diff --git a/qomemo/device.h b/qomemo/device.h new file mode 100644 index 0000000..6a67a19 --- /dev/null +++ b/qomemo/device.h @@ -0,0 +1,25 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include + +class QXmppElement; + +class QXmppIq; + +namespace QXmpp::Omemo { + + class Device { + public: + int id; + }; + + class DeviceList { + public: + QList devices; + }; + +} // namespace QXmpp::Omemo diff --git a/qomemo/device_key_storage.cpp b/qomemo/device_key_storage.cpp new file mode 100644 index 0000000..d8c74df --- /dev/null +++ b/qomemo/device_key_storage.cpp @@ -0,0 +1,14 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#include "device_key_storage.h" +#include + +int QXmpp::Omemo::DeviceKeyStorage::generateDeviceId() { + QRandomGenerator random{}; + + return 1 + random.bounded(INT32_MAX - 1); +} + +QXmpp::Omemo::DeviceKeyStorage::DeviceKeyStorage(int deviceId) : deviceId(deviceId) {} diff --git a/qomemo/device_key_storage.h b/qomemo/device_key_storage.h new file mode 100644 index 0000000..efeade9 --- /dev/null +++ b/qomemo/device_key_storage.h @@ -0,0 +1,18 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#pragma once + +namespace QXmpp::Omemo { + + class DeviceKeyStorage { + public: + static int generateDeviceId(); + + explicit DeviceKeyStorage(int deviceId); + + const int deviceId; + }; + +} // namespace QXmpp::Omemo diff --git a/qomemo/device_service.cpp b/qomemo/device_service.cpp new file mode 100644 index 0000000..653a3a6 --- /dev/null +++ b/qomemo/device_service.cpp @@ -0,0 +1,35 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#include "device_service.h" +#include "device.h" + +using namespace QXmpp::Omemo; + +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 new file mode 100644 index 0000000..2442486 --- /dev/null +++ b/qomemo/device_service.h @@ -0,0 +1,38 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#pragma once + +#include + +#include "qomemo.h" +#include "user_device_list.h" +#include "database.h" + +#include + +namespace QXmpp::Omemo { + + class DeviceList; + + class DeviceService : public QObject { + Q_OBJECT + + 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> 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/qomemo.cpp b/qomemo/qomemo.cpp new file mode 100644 index 0000000..2b67119 --- /dev/null +++ b/qomemo/qomemo.cpp @@ -0,0 +1,94 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#include "qomemo.h" +#include "sce.h" +#include + +#include +#include +#include + +using namespace QXmpp::Factories; + +QXmppElement QXmpp::Omemo::EncryptedMessage::header() const { + auto result = createElement("header"); + result.setAttribute("sid", QString::number(fromDeviceId)); + + auto ivNode = createElement("iv"); + ivNode.setValue(iv); + + for (const auto &key : keys) { + result.appendChild(key.toXml()); + } + + return result; +} + +QXmppElement QXmpp::Omemo::EncryptedMessage::toXml() const { + auto result = createElement("encrypted", "eu.siacs.conversations.axolotl"); + + result.appendChild(header()); + // TODO: Payload is optional + result.appendChild(payload()); + + return result; +} + +QXmppElement QXmpp::Omemo::EncryptedMessage::payload() const { + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + QXmlStreamWriter writer(&buffer); + message.toXml(&writer); + + QDomDocument doc; + doc.setContent(buffer.data(), true); + + QXmppElement root(doc.documentElement()); + root.setTagName("payload"); + + return root; +} + +QXmppElement QXmpp::Omemo::EncryptedMessage::content() const { + auto envelope = createElement("content", "urn:xmpp:sce:0"); + + envelope.appendChild(payload()); + + if (!from.isEmpty()) { + auto fromNode = createElement("from"); + fromNode.setAttribute("jid", from); + envelope.appendChild(fromNode); + } + + if (!to.isEmpty()) { + auto toNode = createElement("to"); + toNode.setAttribute("jid", to); + envelope.appendChild(toNode); + } + + if (!timestamp.isNull()) { + auto timeNode = createElement("time"); + timeNode.setAttribute("stamp", timestamp.toString(Qt::DateFormat::ISODate)); + envelope.appendChild(timeNode); + } + + auto rpad = createElement("rpad"); + rpad.setValue(QXmpp::Sce::generatePadding()); + envelope.appendChild(rpad); + + return envelope; +} + +QXmppElement QXmpp::Omemo::MessageKey::toXml() const { + auto result = createElement("key"); + + result.setAttribute("rid", QString::number(receivingDeviceId)); + if (prekey) + result.setAttribute("prekey", "true"); + + result.setValue(key); + + return result; +} diff --git a/qomemo/qomemo.h b/qomemo/qomemo.h new file mode 100644 index 0000000..1e0c71a --- /dev/null +++ b/qomemo/qomemo.h @@ -0,0 +1,42 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#pragma once + +#include + +#include +#include + +namespace QXmpp::Omemo { + + class MessageKey { + public: + [[nodiscard]] QXmppElement toXml() const; + + int receivingDeviceId{}; + bool prekey{}; + QString key{}; + }; + + class EncryptedMessage { + public: + [[nodiscard]] QXmppElement header() const; + [[nodiscard]] QXmppElement content() const; + [[nodiscard]] QXmppElement toXml() const; + [[nodiscard]] QXmppElement payload() const; + + int fromDeviceId{}; + + QList keys{}; + QString from{}; + QString to{}; + QDateTime timestamp{}; + + QString iv{}; + + QXmppMessage message{}; + }; + +} // namespace QXmpp::Omemo diff --git a/qomemo/qxmpp_omemo_manager.cpp b/qomemo/qxmpp_omemo_manager.cpp new file mode 100644 index 0000000..e70754c --- /dev/null +++ b/qomemo/qxmpp_omemo_manager.cpp @@ -0,0 +1,292 @@ +/* + * 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 + +#include +#include +#include +#include +#include + +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 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; +} diff --git a/qomemo/qxmpp_omemo_manager.h b/qomemo/qxmpp_omemo_manager.h new file mode 100644 index 0000000..03c2aa2 --- /dev/null +++ b/qomemo/qxmpp_omemo_manager.h @@ -0,0 +1,64 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#pragma once + +#include "device_service.h" +#include "qomemo.h" +#include "variant/omemo_base.h" + +#include +#include + +namespace Signal { + class Context; +} + +namespace QXmpp::Omemo { + + class Manager : public QXmppClientExtension { + Q_OBJECT; + + public: + Manager(); + ~Manager() override = default; + + bool handleStanza(const QDomElement &stanza) override; + + bool handleDeviceList(const QDomElement& stanza); + + bool handleMissingDeviceList(const QDomElement& stanza); + + bool handleEncryptedMessage(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: + QSharedPointer deviceService; + QScopedPointer omemoVariant; + std::shared_ptr signalContext; + }; + +} // namespace QXmpp::Omemo diff --git a/qomemo/sce.cpp b/qomemo/sce.cpp new file mode 100644 index 0000000..b94cede --- /dev/null +++ b/qomemo/sce.cpp @@ -0,0 +1,26 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#include "sce.h" + +#include + +#define RPAD_ALPHABET "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + +constexpr int RPAD_MAX_LENGTH = 200; + +QString QXmpp::Sce::generatePadding() { + QRandomGenerator random{}; + QString result{}; + QString alphabet{QStringLiteral(RPAD_ALPHABET)}; + + auto length = random.bounded(RPAD_MAX_LENGTH); + result.resize(length); + + for (auto i = 0; i < length; ++i) { + result[i] = alphabet[random.bounded(alphabet.length())]; + } + + return result; +} diff --git a/qomemo/sce.h b/qomemo/sce.h new file mode 100644 index 0000000..497aa3d --- /dev/null +++ b/qomemo/sce.h @@ -0,0 +1,14 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#pragma once + +#include +#include + +namespace QXmpp::Sce { + + QString generatePadding(); + +} diff --git a/qomemo/signal/CMakeLists.txt b/qomemo/signal/CMakeLists.txt new file mode 100644 index 0000000..7223904 --- /dev/null +++ b/qomemo/signal/CMakeLists.txt @@ -0,0 +1,9 @@ +target_sources(squawk PRIVATE + context.cpp + context.h + util.cpp + util.h + ) + +add_subdirectory(crypto) +add_subdirectory(stores) \ No newline at end of file diff --git a/qomemo/signal/context.cpp b/qomemo/signal/context.cpp new file mode 100644 index 0000000..75d3557 --- /dev/null +++ b/qomemo/signal/context.cpp @@ -0,0 +1,28 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "context.h" +#include "crypto/crypto.h" + +using namespace Signal; + +Context::Context() : cryptoProvider{ Signal::Crypto::createProvider() } { + signal_context_create(&ctx, nullptr); + signal_context_set_crypto_provider(ctx, &cryptoProvider); +} + +Context::~Context() { + signal_context_destroy(ctx); +} + +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 new file mode 100644 index 0000000..30941d9 --- /dev/null +++ b/qomemo/signal/context.h @@ -0,0 +1,32 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include "crypto/ec.h" + +#include + +#include + +namespace Signal { + + class Context { + public: + Context(); + ~Context(); + Context(const Context &) = delete; + Context(Context &&) = delete; + Context &operator=(const Context &) = delete; + + std::unique_ptr generateCurveKeyPair(); + + signal_context *temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(); + + private: + signal_crypto_provider cryptoProvider{}; + signal_context *ctx{nullptr}; + }; + +} // namespace Signal diff --git a/qomemo/signal/crypto/CMakeLists.txt b/qomemo/signal/crypto/CMakeLists.txt new file mode 100644 index 0000000..8f43895 --- /dev/null +++ b/qomemo/signal/crypto/CMakeLists.txt @@ -0,0 +1,12 @@ +target_sources(squawk PRIVATE + aes_openssl.cpp + aes_openssl.h + crypto.cpp + crypto.h + hmac_sha256_openssl.cpp + hmac_sha256_openssl.h + sha512_digest_openssl.cpp + sha512_digest_openssl.h + ec.cpp + ec.h + ) diff --git a/qomemo/signal/crypto/aes_openssl.cpp b/qomemo/signal/crypto/aes_openssl.cpp new file mode 100644 index 0000000..17f2df8 --- /dev/null +++ b/qomemo/signal/crypto/aes_openssl.cpp @@ -0,0 +1,187 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "aes_openssl.h" +#include + +extern "C" { +#include +#include +} + +using namespace Signal::Crypto; + +class EVPCipherCtxWrapper { +public: + EVPCipherCtxWrapper() { +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + ctx = EVP_CIPHER_CTX_new(); +#else + ctx = new EVP_CIPHER_CTX; + EVP_CIPHER_CTX_init(ctx); +#endif + } + + ~EVPCipherCtxWrapper() { + if (good()) { +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + EVP_CIPHER_CTX_free(ctx); +#else + EVP_CIPHER_CTX_cleanup(ctx); + delete ctx; +#endif + } + } + + EVPCipherCtxWrapper(const EVPCipherCtxWrapper &) = delete; + EVPCipherCtxWrapper(EVPCipherCtxWrapper &&) = delete; + EVPCipherCtxWrapper &operator=(const EVPCipherCtxWrapper &) = delete; + + [[nodiscard]] bool good() const { return ctx != nullptr; } + + [[nodiscard]] EVP_CIPHER_CTX *operator*() const { return ctx; } + +private: + EVP_CIPHER_CTX *ctx{nullptr}; +}; + +static const EVP_CIPHER *aes_cipher(int cipher, size_t key_len) { + if (cipher == SG_CIPHER_AES_CBC_PKCS5) { + if (key_len == 16) { + return EVP_aes_128_cbc(); + } else if (key_len == 24) { + return EVP_aes_192_cbc(); + } else if (key_len == 32) { + return EVP_aes_256_cbc(); + } + } else if (cipher == SG_CIPHER_AES_CTR_NOPADDING) { + if (key_len == 16) { + return EVP_aes_128_ctr(); + } else if (key_len == 24) { + return EVP_aes_192_ctr(); + } else if (key_len == 32) { + return EVP_aes_256_ctr(); + } + } + return nullptr; +} + +int Aes::encrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv, + size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *) { + const EVP_CIPHER *evp_cipher = aes_cipher(cipher, key_len); + if (!evp_cipher) { + fprintf(stderr, "invalid AES mode or key size: %zu\n", key_len); + return SG_ERR_UNKNOWN; + } + + if (iv_len != 16) { + fprintf(stderr, "invalid AES IV size: %zu\n", iv_len); + return SG_ERR_UNKNOWN; + } + + if (plaintext_len > INT_MAX - EVP_CIPHER_block_size(evp_cipher)) { + fprintf(stderr, "invalid plaintext length: %zu\n", plaintext_len); + return SG_ERR_UNKNOWN; + } + + EVPCipherCtxWrapper ctx{}; + if (!ctx.good()) { + fprintf(stderr, "could not create context\n"); + return SG_ERR_UNKNOWN; + } + + auto result = EVP_EncryptInit_ex(*ctx, evp_cipher, nullptr, key, iv); + if (!result) { + fprintf(stderr, "cannot initialize cipher\n"); + return SG_ERR_UNKNOWN; + } + + if (cipher == SG_CIPHER_AES_CTR_NOPADDING) { + result = EVP_CIPHER_CTX_set_padding(*ctx, 0); + if (!result) { + fprintf(stderr, "cannot set padding\n"); + return SG_ERR_UNKNOWN; + } + } + + auto out_buf = std::make_unique(plaintext_len + EVP_CIPHER_block_size(evp_cipher)); + + int out_len = 0; + result = EVP_EncryptUpdate(*ctx, out_buf.get(), &out_len, plaintext, + plaintext_len); + if (!result) { + fprintf(stderr, "cannot encrypt plaintext\n"); + return SG_ERR_UNKNOWN; + } + + int final_len = 0; + result = EVP_EncryptFinal_ex(*ctx, out_buf.get() + out_len, &final_len); + if (!result) { + fprintf(stderr, "cannot finish encrypting plaintext\n"); + return SG_ERR_UNKNOWN; + } + + *output = signal_buffer_create(out_buf.get(), out_len + final_len); + + return result; +} + +int Aes::decrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv, + size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *) { + const EVP_CIPHER *evp_cipher = aes_cipher(cipher, key_len); + if (!evp_cipher) { + fprintf(stderr, "invalid AES mode or key size: %zu\n", key_len); + return SG_ERR_INVAL; + } + + if (iv_len != 16) { + fprintf(stderr, "invalid AES IV size: %zu\n", iv_len); + return SG_ERR_INVAL; + } + + if (ciphertext_len > INT_MAX - EVP_CIPHER_block_size(evp_cipher)) { + fprintf(stderr, "invalid ciphertext length: %zu\n", ciphertext_len); + return SG_ERR_UNKNOWN; + } + + EVPCipherCtxWrapper ctx{}; + if (!ctx.good()) { + fprintf(stderr, "could not create context\n"); + return SG_ERR_UNKNOWN; + } + + auto result = EVP_DecryptInit_ex(*ctx, evp_cipher, nullptr, key, iv); + if (!result) { + fprintf(stderr, "cannot initialize cipher\n"); + return SG_ERR_UNKNOWN; + } + + if (cipher == SG_CIPHER_AES_CTR_NOPADDING) { + result = EVP_CIPHER_CTX_set_padding(*ctx, 0); + if (!result) { + fprintf(stderr, "cannot set padding\n"); + return SG_ERR_UNKNOWN; + } + } + + auto out_buf = std::make_unique(ciphertext_len + EVP_CIPHER_block_size(evp_cipher)); + + int out_len = 0; + result = EVP_DecryptUpdate(*ctx, out_buf.get(), &out_len, ciphertext, ciphertext_len); + if (!result) { + fprintf(stderr, "cannot decrypt ciphertext\n"); + return SG_ERR_UNKNOWN; + } + + int final_len = 0; + result = EVP_DecryptFinal_ex(*ctx, out_buf.get() + out_len, &final_len); + if (!result) { + fprintf(stderr, "cannot finish decrypting ciphertext\n"); + return SG_ERR_UNKNOWN; + } + + *output = signal_buffer_create(out_buf.get(), out_len + final_len); + + return result; +} \ No newline at end of file diff --git a/qomemo/signal/crypto/aes_openssl.h b/qomemo/signal/crypto/aes_openssl.h new file mode 100644 index 0000000..63f4635 --- /dev/null +++ b/qomemo/signal/crypto/aes_openssl.h @@ -0,0 +1,17 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include + +namespace Signal::Crypto::Aes { + + int encrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv, + size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *user_data); + + int decrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv, + size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *user_data); + +} // namespace Signal::Crypto::Aes diff --git a/qomemo/signal/crypto/crypto.cpp b/qomemo/signal/crypto/crypto.cpp new file mode 100644 index 0000000..d6a6058 --- /dev/null +++ b/qomemo/signal/crypto/crypto.cpp @@ -0,0 +1,40 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "crypto.h" + +extern "C" { +#include +} + +#include "aes_openssl.h" +#include "hmac_sha256_openssl.h" +#include "sha512_digest_openssl.h" + +int random_func(uint8_t *data, size_t len, void *) { + if (RAND_bytes(data, len)) { + return 0; + } else { + return SG_ERR_UNKNOWN; + } +} + +signal_crypto_provider Signal::Crypto::createProvider() { + signal_crypto_provider result{}; + + result.random_func = random_func; + result.hmac_sha256_init_func = HmacSha256::init; + result.hmac_sha256_update_func = HmacSha256::update; + result.hmac_sha256_final_func = HmacSha256::final; + result.hmac_sha256_cleanup_func = HmacSha256::cleanup; + result.sha512_digest_init_func = Sha512::init; + result.sha512_digest_update_func = Sha512::update; + result.sha512_digest_final_func = Sha512::final; + result.sha512_digest_cleanup_func = Sha512::cleanup; + result.encrypt_func = Aes::encrypt; + result.decrypt_func = Aes::decrypt; + result.user_data = nullptr; + + return result; +} diff --git a/qomemo/signal/crypto/crypto.h b/qomemo/signal/crypto/crypto.h new file mode 100644 index 0000000..2fbff31 --- /dev/null +++ b/qomemo/signal/crypto/crypto.h @@ -0,0 +1,13 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include + +namespace Signal::Crypto { + + signal_crypto_provider createProvider(); + +} 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/crypto/hmac_sha256_openssl.cpp b/qomemo/signal/crypto/hmac_sha256_openssl.cpp new file mode 100644 index 0000000..2a8aba5 --- /dev/null +++ b/qomemo/signal/crypto/hmac_sha256_openssl.cpp @@ -0,0 +1,71 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "hmac_sha256_openssl.h" + +extern "C" { +#include +#include +#include +} + +using namespace Signal::Crypto; + +int HmacSha256::init(void **hmac_context, const uint8_t *key, size_t key_len, void *) { +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + HMAC_CTX *ctx = HMAC_CTX_new(); + if (!ctx) { + return SG_ERR_NOMEM; + } +#else + auto ctx = new HMAC_CTX; + HMAC_CTX_init(ctx); +#endif + + *hmac_context = ctx; + + if (HMAC_Init_ex(ctx, key, key_len, EVP_sha256(), nullptr) != 1) { + return SG_ERR_UNKNOWN; + } + + return SG_SUCCESS; +} + +int HmacSha256::update(void *hmac_context, const uint8_t *data, size_t data_len, void *) { + auto ctx = static_cast(hmac_context); + int result = HMAC_Update(ctx, data, data_len); + + return (result == 1) ? SG_SUCCESS : SG_ERR_UNKNOWN; +} + +int HmacSha256::final(void *hmac_context, signal_buffer **output, void *) { + auto ctx = static_cast(hmac_context); + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int len = 0; + + if (HMAC_Final(ctx, md, &len) != 1) { + return SG_ERR_UNKNOWN; + } + + signal_buffer *output_buffer = signal_buffer_create(md, len); + if (!output_buffer) { + return SG_ERR_NOMEM; + } + + *output = output_buffer; + + return SG_SUCCESS; +} + +void HmacSha256::cleanup(void *hmac_context, void *) { + if (hmac_context) { + auto ctx = static_cast(hmac_context); +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + HMAC_CTX_free(ctx); +#else + HMAC_CTX_cleanup(ctx); + delete ctx; +#endif + } +} \ No newline at end of file diff --git a/qomemo/signal/crypto/hmac_sha256_openssl.h b/qomemo/signal/crypto/hmac_sha256_openssl.h new file mode 100644 index 0000000..03fe8e0 --- /dev/null +++ b/qomemo/signal/crypto/hmac_sha256_openssl.h @@ -0,0 +1,16 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include + +namespace Signal::Crypto::HmacSha256 { + + int init(void **hmac_context, const uint8_t *key, size_t key_len, void *); + int update(void *hmac_context, const uint8_t *data, size_t data_len, void *); + int final(void *hmac_context, signal_buffer **output, void *); + void cleanup(void *hmac_context, void *); + +} // namespace Signal::Crypto::HmacSha256 diff --git a/qomemo/signal/crypto/sha512_digest_openssl.cpp b/qomemo/signal/crypto/sha512_digest_openssl.cpp new file mode 100644 index 0000000..360f11e --- /dev/null +++ b/qomemo/signal/crypto/sha512_digest_openssl.cpp @@ -0,0 +1,67 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "sha512_digest_openssl.h" + +extern "C" { +#include +#include +#include +} + +using namespace Signal::Crypto; + +int Sha512::init(void **digest_context, void *) { + auto ctx = EVP_MD_CTX_create(); + if (!ctx) { + return SG_ERR_NOMEM; + } + + auto result = EVP_DigestInit_ex(ctx, EVP_sha512(), nullptr); + + if (result == 1) { + *digest_context = ctx; + return SG_SUCCESS; + } + + EVP_MD_CTX_destroy(ctx); + return SG_ERR_UNKNOWN; +} + +int Sha512::update(void *digest_context, const uint8_t *data, size_t data_len, void *) { + auto ctx = static_cast(digest_context); + auto result = EVP_DigestUpdate(ctx, data, data_len); + + return (result == 1) ? SG_SUCCESS : SG_ERR_UNKNOWN; +} + +int Sha512::final(void *digest_context, signal_buffer **output, void *) { + auto ctx = static_cast(digest_context); + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int len = 0; + + auto result = EVP_DigestFinal_ex(ctx, md, &len); + if (result != 1) { + return SG_ERR_UNKNOWN; + } + + result = EVP_DigestInit_ex(ctx, EVP_sha512(), nullptr); + if (result != 1) { + return SG_ERR_UNKNOWN; + } + + signal_buffer *output_buffer = signal_buffer_create(md, len); + if (!output_buffer) { + return SG_ERR_NOMEM; + } + + *output = output_buffer; + + return SG_SUCCESS; +} + +void Sha512::cleanup(void *digest_context, void *) { + auto ctx = static_cast(digest_context); + EVP_MD_CTX_destroy(ctx); +} \ No newline at end of file diff --git a/qomemo/signal/crypto/sha512_digest_openssl.h b/qomemo/signal/crypto/sha512_digest_openssl.h new file mode 100644 index 0000000..8b263a2 --- /dev/null +++ b/qomemo/signal/crypto/sha512_digest_openssl.h @@ -0,0 +1,16 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include + +namespace Signal::Crypto::Sha512 { + + int init(void **digest_context, void *); + int update(void *digest_context, const uint8_t *data, size_t data_len, void *); + int final(void *digest_context, signal_buffer **output, void *); + void cleanup(void *digest_context, void *); + +} // namespace Signal::Crypto::Sha512 diff --git a/qomemo/signal/stores/CMakeLists.txt b/qomemo/signal/stores/CMakeLists.txt new file mode 100644 index 0000000..971b87b --- /dev/null +++ b/qomemo/signal/stores/CMakeLists.txt @@ -0,0 +1,14 @@ +target_sources(squawk PRIVATE + identity_key_store.cpp + identity_key_store.h + pre_key_store.cpp + pre_key_store.h + sender_key_store.cpp + sender_key_store.h + session_store.cpp + session_store.h + signed_pre_key_store.cpp + signed_pre_key_store.h + store_context.cpp + store_context.h + ) \ No newline at end of file diff --git a/qomemo/signal/stores/identity_key_store.cpp b/qomemo/signal/stores/identity_key_store.cpp new file mode 100644 index 0000000..e80b7d3 --- /dev/null +++ b/qomemo/signal/stores/identity_key_store.cpp @@ -0,0 +1,70 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "identity_key_store.h" + +#include "qomemo/signal/util.h" +#include + +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); + }; + store.get_local_registration_id = [](void *ptr, uint32_t *registrationId) { + return static_cast(ptr)->getLocalRegistrationId(registrationId); + }; + store.is_trusted_identity = [](const signal_protocol_address *address, uint8_t *key_data, size_t key_len, + void *ptr) { + return static_cast(ptr)->isTrustedIdentity(address, key_data, key_len); + }; + 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); + }; +} diff --git a/qomemo/signal/stores/identity_key_store.h b/qomemo/signal/stores/identity_key_store.h new file mode 100644 index 0000000..017ca42 --- /dev/null +++ b/qomemo/signal/stores/identity_key_store.h @@ -0,0 +1,32 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include + +#include "qomemo/device_service.h" + +namespace Signal::Store { + + class IdentityKeyStore { + public: + 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 new file mode 100644 index 0000000..72bfe72 --- /dev/null +++ b/qomemo/signal/stores/pre_key_store.cpp @@ -0,0 +1,38 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "pre_key_store.h" + +Signal::Store::PreKeyStore::PreKeyStore(QSharedPointer database) : database( + std::move(database)) {} + +int Signal::Store::PreKeyStore::containsPreKey(uint32_t pre_key_id) { + return 0; +} + +int Signal::Store::PreKeyStore::loadPreKey(signal_buffer **record, uint32_t pre_key_id) { + return 0; +} + +int Signal::Store::PreKeyStore::storePreKey(uint32_t pre_key_id, uint8_t *record, size_t record_len) { + return 0; +} + +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 new file mode 100644 index 0000000..0ec1a79 --- /dev/null +++ b/qomemo/signal/stores/pre_key_store.h @@ -0,0 +1,28 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include + +#include "qomemo/device_service.h" + +namespace Signal::Store { + + class PreKeyStore { + public: + 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/stores/sender_key_store.cpp b/qomemo/signal/stores/sender_key_store.cpp new file mode 100644 index 0000000..2db5231 --- /dev/null +++ b/qomemo/signal/stores/sender_key_store.cpp @@ -0,0 +1,30 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "sender_key_store.h" + +int Signal::Store::SenderKeyStore::loadSenderKey(signal_buffer **record, signal_buffer **user_record, + const signal_protocol_sender_key_name *sender_key_name) { + return 0; +} + +int Signal::Store::SenderKeyStore::storeSenderKey(const signal_protocol_sender_key_name *sender_key_name, + uint8_t *record, size_t record_len, uint8_t *user_record, + size_t user_record_len) { + return 0; +} + +void Signal::Store::SenderKeyStore::fillCallbacks(signal_protocol_sender_key_store &store) { + store.user_data = nullptr; + store.destroy_func = nullptr; + store.load_sender_key = [](signal_buffer **record, signal_buffer **user_record, + const signal_protocol_sender_key_name *sender_key_name, void *ptr) { + return static_cast(ptr)->loadSenderKey(record, user_record, sender_key_name); + }; + store.store_sender_key = [](const signal_protocol_sender_key_name *sender_key_name, uint8_t *record, + size_t record_len, uint8_t *user_record, size_t user_record_len, void *ptr) { + return static_cast(ptr)->storeSenderKey(sender_key_name, record, record_len, user_record, + user_record_len); + }; +} diff --git a/qomemo/signal/stores/sender_key_store.h b/qomemo/signal/stores/sender_key_store.h new file mode 100644 index 0000000..a5abcc6 --- /dev/null +++ b/qomemo/signal/stores/sender_key_store.h @@ -0,0 +1,21 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include + +namespace Signal::Store { + + class SenderKeyStore { + public: + int storeSenderKey(const signal_protocol_sender_key_name *sender_key_name, uint8_t *record, size_t record_len, + uint8_t *user_record, size_t user_record_len); + int loadSenderKey(signal_buffer **record, signal_buffer **user_record, + const signal_protocol_sender_key_name *sender_key_name); + + void fillCallbacks(signal_protocol_sender_key_store &store); + }; + +} // namespace Signal::Store diff --git a/qomemo/signal/stores/session_store.cpp b/qomemo/signal/stores/session_store.cpp new file mode 100644 index 0000000..4b9a5f9 --- /dev/null +++ b/qomemo/signal/stores/session_store.cpp @@ -0,0 +1,57 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "session_store.h" + +int Signal::Store::SessionStore::loadSession(signal_buffer **record, signal_buffer **user_record, + const signal_protocol_address *address) { + return 0; +} + +int Signal::Store::SessionStore::getSubDeviceSessions(signal_int_list **sessions, const char *name, size_t name_len) { + return 0; +} + +int Signal::Store::SessionStore::storeSession(const signal_protocol_address *address, uint8_t *record, + size_t record_len, uint8_t *user_record, size_t user_record_len) { + return 0; +} + +int Signal::Store::SessionStore::containsSession(const signal_protocol_address *address) { + return 0; +} + +int Signal::Store::SessionStore::deleteSession(const signal_protocol_address *address) { + return 0; +} + +int Signal::Store::SessionStore::deleteAllSessions(const char *name, size_t name_len) { + return 0; +} + +void Signal::Store::SessionStore::fillCallbacks(signal_protocol_session_store &store) { + store.user_data = nullptr; + store.destroy_func = nullptr; + store.load_session_func = [](signal_buffer **record, signal_buffer **user_record, + const signal_protocol_address *address, void *ptr) { + return static_cast(ptr)->loadSession(record, user_record, address); + }; + store.get_sub_device_sessions_func = [](signal_int_list **sessions, const char *name, size_t name_len, void *ptr) { + return static_cast(ptr)->getSubDeviceSessions(sessions, name, name_len); + }; + store.store_session_func = [](const signal_protocol_address *address, uint8_t *record, size_t record_len, + uint8_t *user_record, size_t user_record_len, void *ptr) { + return static_cast(ptr)->storeSession(address, record, record_len, user_record, + user_record_len); + }; + store.contains_session_func = [](const signal_protocol_address *address, void *ptr) { + return static_cast(ptr)->containsSession(address); + }; + store.delete_session_func = [](const signal_protocol_address *address, void *ptr) { + return static_cast(ptr)->deleteSession(address); + }; + store.delete_all_sessions_func = [](const char *name, size_t name_len, void *ptr) { + return static_cast(ptr)->deleteAllSessions(name, name_len); + }; +} diff --git a/qomemo/signal/stores/session_store.h b/qomemo/signal/stores/session_store.h new file mode 100644 index 0000000..c773c83 --- /dev/null +++ b/qomemo/signal/stores/session_store.h @@ -0,0 +1,24 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include + +namespace Signal::Store { + + class SessionStore { + public: + int loadSession(signal_buffer **record, signal_buffer **user_record, const signal_protocol_address *address); + int getSubDeviceSessions(signal_int_list **sessions, const char *name, size_t name_len); + int storeSession(const signal_protocol_address *address, uint8_t *record, size_t record_len, + uint8_t *user_record, size_t user_record_len); + int containsSession(const signal_protocol_address *address); + int deleteSession(const signal_protocol_address *address); + int deleteAllSessions(const char *name, size_t name_len); + + void fillCallbacks(signal_protocol_session_store &store); + }; + +} // namespace Signal::Store diff --git a/qomemo/signal/stores/signed_pre_key_store.cpp b/qomemo/signal/stores/signed_pre_key_store.cpp new file mode 100644 index 0000000..483465d --- /dev/null +++ b/qomemo/signal/stores/signed_pre_key_store.cpp @@ -0,0 +1,43 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "signed_pre_key_store.h" + +int Signal::Store::SignedPreKeyStore::loadSignedPreKey(signal_buffer **record, uint32_t signed_pre_key_id) { + return 0; +} + +int +Signal::Store::SignedPreKeyStore::storeSignedPreKey(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len) { + return 0; +} + +int Signal::Store::SignedPreKeyStore::containsSignedPreKey(uint32_t signed_pre_key_id) { + return 0; +} + +int Signal::Store::SignedPreKeyStore::removeSignedPreKey(uint32_t signed_pre_key_id) { + return 0; +} + +void Signal::Store::SignedPreKeyStore::fillCallbacks(signal_protocol_signed_pre_key_store &store) { + store.user_data = nullptr; + store.destroy_func = nullptr; + store.load_signed_pre_key = [](signal_buffer **record, uint32_t signed_pre_key_id, void *ptr) { + return static_cast(ptr)->loadSignedPreKey( + record, signed_pre_key_id); + }; + store.store_signed_pre_key = [](uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *ptr) { + return static_cast(ptr)->storeSignedPreKey( + signed_pre_key_id, record, record_len); + }; + store.contains_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) { + return static_cast(ptr)->containsSignedPreKey( + signed_pre_key_id); + }; + store.remove_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) { + return static_cast(ptr)->removeSignedPreKey( + signed_pre_key_id); + }; +} diff --git a/qomemo/signal/stores/signed_pre_key_store.h b/qomemo/signal/stores/signed_pre_key_store.h new file mode 100644 index 0000000..2544741 --- /dev/null +++ b/qomemo/signal/stores/signed_pre_key_store.h @@ -0,0 +1,21 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include + +namespace Signal::Store { + + class SignedPreKeyStore { + public: + int loadSignedPreKey(signal_buffer **record, uint32_t signed_pre_key_id); + int storeSignedPreKey(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len); + int containsSignedPreKey(uint32_t signed_pre_key_id); + int removeSignedPreKey(uint32_t signed_pre_key_id); + + void fillCallbacks(signal_protocol_signed_pre_key_store &store); + }; + +} // namespace Signal::Store diff --git a/qomemo/signal/stores/store_context.cpp b/qomemo/signal/stores/store_context.cpp new file mode 100644 index 0000000..e4dd58a --- /dev/null +++ b/qomemo/signal/stores/store_context.cpp @@ -0,0 +1,25 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "store_context.h" + +Signal::Store::Context::Context(signal_context *global) : identityKeyStore(), preKeyStore(), senderKeyStore(), sessionStore(), signedPreKeyStore() { + signal_protocol_store_context_create(&ctx, global); + + identityKeyStore->fillCallbacks(iks); + preKeyStore->fillCallbacks(pks); + senderKeyStore->fillCallbacks(sks); + sessionStore->fillCallbacks(ss); + signedPreKeyStore->fillCallbacks(spks); + + signal_protocol_store_context_set_identity_key_store(ctx, &iks); + signal_protocol_store_context_set_pre_key_store(ctx, &pks); + signal_protocol_store_context_set_sender_key_store(ctx, &sks); + signal_protocol_store_context_set_session_store(ctx, &ss); + signal_protocol_store_context_set_signed_pre_key_store(ctx, &spks); +} + +Signal::Store::Context::~Context() { + signal_protocol_store_context_destroy(ctx); +} diff --git a/qomemo/signal/stores/store_context.h b/qomemo/signal/stores/store_context.h new file mode 100644 index 0000000..7c1ea55 --- /dev/null +++ b/qomemo/signal/stores/store_context.h @@ -0,0 +1,42 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include + +#include "identity_key_store.h" +#include "pre_key_store.h" +#include "sender_key_store.h" +#include "session_store.h" +#include "signed_pre_key_store.h" + +namespace Signal::Store { + + class Context { + public: + explicit Context(signal_context *global); + ~Context(); + + Context(const Context &) = delete; + Context(Context &&) = delete; + Context &operator=(const Context &) = delete; + + private: + signal_protocol_store_context *ctx{nullptr}; + + signal_protocol_identity_key_store iks{}; + signal_protocol_pre_key_store pks{}; + signal_protocol_sender_key_store sks{}; + signal_protocol_session_store ss{}; + signal_protocol_signed_pre_key_store spks{}; + + std::unique_ptr identityKeyStore; + std::unique_ptr preKeyStore; + std::unique_ptr senderKeyStore; + std::unique_ptr sessionStore; + std::unique_ptr signedPreKeyStore; + }; + +} // 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/user_device_list.cpp b/qomemo/user_device_list.cpp new file mode 100644 index 0000000..5910ed2 --- /dev/null +++ b/qomemo/user_device_list.cpp @@ -0,0 +1,9 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#include "user_device_list.h" + +#include + +QXmpp::Omemo::UserDeviceList::UserDeviceList(QString jid) : jid(std::move(jid)) {} diff --git a/qomemo/user_device_list.h b/qomemo/user_device_list.h new file mode 100644 index 0000000..00465aa --- /dev/null +++ b/qomemo/user_device_list.h @@ -0,0 +1,18 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#pragma once + +#include + +namespace QXmpp::Omemo { + + class UserDeviceList { + public: + explicit UserDeviceList(QString jid); + + const QString jid; + }; + +} // namespace QXmpp::Omemo diff --git a/qomemo/variant/CMakeLists.txt b/qomemo/variant/CMakeLists.txt new file mode 100644 index 0000000..a2b211a --- /dev/null +++ b/qomemo/variant/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(squawk PRIVATE xep0384.cpp xep0384.h conversations.cpp conversations.h omemo_base.cpp omemo_base.h) diff --git a/qomemo/variant/conversations.cpp b/qomemo/variant/conversations.cpp new file mode 100644 index 0000000..1658c37 --- /dev/null +++ b/qomemo/variant/conversations.cpp @@ -0,0 +1,205 @@ +/* + * Created by victoria on 2021-05-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; + +QXmppElement Variant::Conversations::deviceToXml(const Device &device) { + auto result = createElement("device"); + result.setAttribute("id", QString::number(device.id)); + + return result; +} + +Device Variant::Conversations::deviceFromXml(const QXmppElement &xml) { + Device result{}; + + if (!elementMatches(xml, "device")) + return result; + + result.id = xml.attribute("id").toInt(); + + return result; +} + +QXmppElement +Variant::Conversations::deviceListToXml(const DeviceList &deviceList) { + auto element = createElement("list", "eu.siacs.conversations.axolotl"); + + for (const auto &device : deviceList.devices) { + element.appendChild(deviceToXml(device)); + } + + return element; +} + +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{}; + + auto deviceElement = list.firstChildElement("device"); + while (!deviceElement.isNull()) { + result.devices.push_back(deviceFromXml(deviceElement)); + deviceElement = deviceElement.nextSiblingElement("device"); + } + + return result; +} + +QXmppIq Variant::Conversations::deviceListSetIq(const DeviceList &deviceList) { + QXmppIq iq{}; + + iq.setType(QXmppIq::Set); + + auto item = createElement("item"); + item.appendChild(deviceListToXml(deviceList)); + + auto publish = createElement("publish"); + 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:%1").arg(deviceId)); + publish.appendChild(item); + + auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub"); + pubSub.appendChild(publish); + pubSub.appendChild(createOpenPublishOptions()); + + iq.setExtensions({ pubSub }); + + 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 new file mode 100644 index 0000000..5a467f0 --- /dev/null +++ b/qomemo/variant/conversations.h @@ -0,0 +1,32 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include "omemo_base.h" + +namespace QXmpp::Omemo::Variant { + + class Conversations : public Base { + public: + ~Conversations() override = default; + + QXmppElement deviceToXml(const Device &device) override; + Device deviceFromXml(const QXmppElement &xml) override; + + [[nodiscard]] QString getDeviceListNode() const override; + QXmppElement deviceListToXml(const DeviceList &deviceList) 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.cpp b/qomemo/variant/omemo_base.cpp new file mode 100644 index 0000000..a3e8493 --- /dev/null +++ b/qomemo/variant/omemo_base.cpp @@ -0,0 +1,5 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "omemo_base.h" diff --git a/qomemo/variant/omemo_base.h b/qomemo/variant/omemo_base.h new file mode 100644 index 0000000..88d2a9f --- /dev/null +++ b/qomemo/variant/omemo_base.h @@ -0,0 +1,45 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +#include + +class QString; +class QXmppElement; +class QXmppIq; + +namespace QXmpp::Omemo { + + class PreKey; + class Bundle; + class Device; + class DeviceList; + + namespace Variant { + + class Base { + public: + virtual ~Base() = default; + + 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 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 + +} // namespace QXmpp::Omemo diff --git a/qomemo/variant/xep0384.cpp b/qomemo/variant/xep0384.cpp new file mode 100644 index 0000000..8ec2597 --- /dev/null +++ b/qomemo/variant/xep0384.cpp @@ -0,0 +1,5 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#include "xep0384.h" diff --git a/qomemo/variant/xep0384.h b/qomemo/variant/xep0384.h new file mode 100644 index 0000000..9298b24 --- /dev/null +++ b/qomemo/variant/xep0384.h @@ -0,0 +1,7 @@ +/* + * Created by victoria on 2021-05-13. + */ + +#pragma once + +namespace QXmpp::Omemo {} diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt index a36b516..97ed619 100644 --- a/shared/CMakeLists.txt +++ b/shared/CMakeLists.txt @@ -16,4 +16,6 @@ target_sources(squawk PRIVATE utils.h vcard.cpp vcard.h + qxmppfactories.cpp + qxmppfactories.h ) diff --git a/shared/qxmppfactories.cpp b/shared/qxmppfactories.cpp new file mode 100644 index 0000000..dc53388 --- /dev/null +++ b/shared/qxmppfactories.cpp @@ -0,0 +1,75 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#include "qxmppfactories.h" + +#include + +bool QXmpp::Factories::elementMatches(const QXmppElement &element, + const QString &tagName, + const QString &xmlns) { + if (element.tagName() != tagName) { + qWarning() << "tag name: expected = " << tagName + << ", got = " << element.tagName(); + return false; + } + + if (!xmlns.isEmpty() && element.attribute("xmlns") != xmlns) { + qWarning() << "xmlns: expected = " << xmlns + << ", got = " << element.attribute("xmlns"); + return false; + } + + return true; +} + +QXmppElement QXmpp::Factories::createElement(const QString &tagName, + const QString &xmlns) { + QXmppElement el{}; + el.setTagName(tagName); + + if (!xmlns.isEmpty()) + el.setAttribute("xmlns", xmlns); + + return el; +} + +QXmppElement QXmpp::Factories::createValue(const QString &value) { + auto el = createElement("value"); + el.setValue(value); + + return el; +} + +QXmppElement QXmpp::Factories::createField(const QString &key, + const QString &value, bool hidden) { + auto field = createElement("field"); + field.setAttribute("var", key); + if (hidden) + field.setAttribute("type", "hidden"); + field.appendChild(createValue(value)); + + return field; +} + +QXmppElement +QXmpp::Factories::createOpenPublishOptions(const QString &maxItems) { + auto formType = createField( + "FORM_TYPE", "http://jabber.org/protocol/pubsub#publish-options", true); + auto accessModel = createField("pubsub#access_model", "open"); + + auto x = createElement("x", "jabber:x:data"); + x.setAttribute("type", "submit"); + + x.appendChild(formType); + x.appendChild(accessModel); + + if (!maxItems.isEmpty()) + x.appendChild(createField("pubsub#max_items", maxItems)); + + auto publishOptions = createElement("publish-options"); + publishOptions.appendChild(x); + + return publishOptions; +} \ No newline at end of file diff --git a/shared/qxmppfactories.h b/shared/qxmppfactories.h new file mode 100644 index 0000000..d7d78e6 --- /dev/null +++ b/shared/qxmppfactories.h @@ -0,0 +1,25 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#pragma once + +#include + +namespace QXmpp::Factories { + +bool elementMatches(const QXmppElement &element, const QString &tagName, + const QString &xmlns = QStringLiteral("")); + +QXmppElement createElement(const QString &tagName, + const QString &xmlns = QStringLiteral("")); + +QXmppElement createValue(const QString &value); + +QXmppElement createField(const QString &key, const QString &value, + bool hidden = false); + +QXmppElement +createOpenPublishOptions(const QString &maxItems = QStringLiteral("")); + +} // namespace QXmpp::Factories diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 36207b6..222f8d7 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -3,3 +3,4 @@ target_sources(squawk PRIVATE squawk.cpp squawk.h squawk.ui) add_subdirectory(models) add_subdirectory(utils) add_subdirectory(widgets) +add_subdirectory(omemo) \ No newline at end of file diff --git a/ui/omemo/CMakeLists.txt b/ui/omemo/CMakeLists.txt new file mode 100644 index 0000000..b775e83 --- /dev/null +++ b/ui/omemo/CMakeLists.txt @@ -0,0 +1,8 @@ +target_sources(squawk PRIVATE + contactsettings.cpp + contactsettings.h + contactsettings.ui + omemodevices.cpp + omemodevices.h + omemodevices.ui + ) \ No newline at end of file diff --git a/ui/omemo/contactsettings.cpp b/ui/omemo/contactsettings.cpp new file mode 100644 index 0000000..ba9704f --- /dev/null +++ b/ui/omemo/contactsettings.cpp @@ -0,0 +1,25 @@ +/* + * Created by victoria on 2021-05-15. +*/ + +#include "contactsettings.h" +#include "ui_contactsettings.h" +#include "omemodevices.h" + +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(jid, this); + + devices->setAttribute(Qt::WA_DeleteOnClose); + devices->show(); +} diff --git a/ui/omemo/contactsettings.h b/ui/omemo/contactsettings.h new file mode 100644 index 0000000..37dc4c1 --- /dev/null +++ b/ui/omemo/contactsettings.h @@ -0,0 +1,26 @@ +/* + * Created by victoria on 2021-05-15. +*/ + +#pragma once + +#include + +namespace Ui { + class ContactSettings; +} + +class ContactSettings : public QDialog { + Q_OBJECT +public: + explicit ContactSettings(QString jid, QWidget *parent = nullptr); + ~ContactSettings() override; + + const QString jid; + +private slots: + void openDeviceList(); + +private: + QScopedPointer m_ui; +}; diff --git a/ui/omemo/contactsettings.ui b/ui/omemo/contactsettings.ui new file mode 100644 index 0000000..5f7e77a --- /dev/null +++ b/ui/omemo/contactsettings.ui @@ -0,0 +1,144 @@ + + + ContactSettings + + + + 0 + 0 + 400 + 300 + + + + Contact Settings + + + + + + Settings for foo@example.com + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Use OMEMO + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Devices... + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ContactSettings + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ContactSettings + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/ui/omemo/omemodevices.cpp b/ui/omemo/omemodevices.cpp new file mode 100644 index 0000000..5b08565 --- /dev/null +++ b/ui/omemo/omemodevices.cpp @@ -0,0 +1,15 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#include "omemodevices.h" +#include "ui_omemodevices.h" + +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 new file mode 100644 index 0000000..529711e --- /dev/null +++ b/ui/omemo/omemodevices.h @@ -0,0 +1,23 @@ +/* + * Created by victoria on 2021-05-12. + */ + +#pragma once + +#include + +namespace Ui { + class OMEMODevices; +} + +class OMEMODevices : public QDialog { + Q_OBJECT +public: + explicit OMEMODevices(QString jid, QWidget *parent = nullptr); + ~OMEMODevices() override; + + const QString jid; + +private: + QScopedPointer m_ui; +}; diff --git a/ui/omemo/omemodevices.ui b/ui/omemo/omemodevices.ui new file mode 100644 index 0000000..c15dc9d --- /dev/null +++ b/ui/omemo/omemodevices.ui @@ -0,0 +1,508 @@ + + + OMEMODevices + + + + 0 + 0 + 560 + 357 + + + + OMEMO Devices + + + + + + List of OMEMO fingerprints for fooooo@bar.com + + + + + + + true + + + + + 0 + 0 + 540 + 281 + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + background-color: rgb(6, 88, 6) + + + QFrame::Panel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + + 240 + 0 + + + + + Monospace + 8 + + + + 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd + + + Qt::PlainText + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + Squawk + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + + + + + .. + + + true + + + + + + + + 0 + 0 + + + + + + + + .. + + + true + + + + + + + + 0 + 0 + + + + + + + + .. + + + true + + + + + + + + + + + 0 + 0 + + + + QFrame::Panel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + + 240 + 0 + + + + + Monospace + 8 + + + + 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd + + + Qt::PlainText + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + Dino + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + + + + + .. + + + true + + + + + + + + 0 + 0 + + + + + + + + .. + + + true + + + + + + + + 0 + 0 + + + + + + + + .. + + + true + + + + + + + + + + + 0 + 0 + + + + false + + + background: rgb(111, 47, 47) + + + QFrame::Panel + + + QFrame::Raised + + + 1 + + + + + + + 0 + 0 + + + + + 240 + 0 + + + + + Monospace + 8 + + + + 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd + + + Qt::PlainText + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + Conversations + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + + + + + .. + + + true + + + + + + + + 0 + 0 + + + + + + + + .. + + + true + + + + + + + + 0 + 0 + + + + + + + + .. + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + false + + + + + + + + + buttonBox + accepted() + OMEMODevices + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + OMEMODevices + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/ui/squawk.cpp b/ui/squawk.cpp index fb79592..1cd9929 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -20,6 +20,8 @@ #include "ui_squawk.h" #include #include +#include +#include Squawk::Squawk(QWidget *parent) : QMainWindow(parent), @@ -60,7 +62,7 @@ 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(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged); connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive); connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest); @@ -134,6 +136,13 @@ void Squawk::onNewContact() nc->exec(); } +void Squawk::openDeviceList(QString bareJid) { + auto od = new OMEMODevices(std::move(bareJid), this); + + od->setAttribute(Qt::WA_DeleteOnClose); + od->show(); +} + void Squawk::onNewConference() { JoinConference* jc = new JoinConference(rosterModel.accountsModel, this); @@ -530,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 15d3f82..a7318de 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -155,6 +155,7 @@ private slots: void onPasswordPromptRejected(); void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous); void onContextAboutToHide(); + void openDeviceList(QString bareJid); void onUnnoticedMessage(const QString& account, const Shared::Message& msg); diff --git a/ui/squawk.ui b/ui/squawk.ui index f6cb300..a029a44 100644 --- a/ui/squawk.ui +++ b/ui/squawk.ui @@ -162,7 +162,7 @@ 0 0 718 - 27 + 23 @@ -224,6 +224,15 @@ Add conference + + + + .. + + + Device list + + diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 45ce2c5..518847f 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -27,6 +27,7 @@ #include #include #include +#include Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent): QWidget(parent), @@ -80,7 +81,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton); connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, this, &Conversation::onTextEditDocSizeChanged); - + connect(m_ui->encryptionButton, &QToolButton::clicked, this, &Conversation::openEncryptionSettings); + m_ui->messageEditor->installEventFilter(&ker); @@ -433,3 +435,10 @@ void Conversation::onFeedContext(const QPoint& pos) } } } + +void Conversation::openEncryptionSettings() { + auto cs = new ContactSettings(palJid, this); + + cs->setAttribute(Qt::WA_DeleteOnClose); + cs->show(); +} diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 0b0dcb2..5ec7acc 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -107,6 +107,7 @@ protected slots: void onFeedMessage(const Shared::Message& msg); void positionShadow(); void onFeedContext(const QPoint &pos); + void openEncryptionSettings(); public: const bool isMuc; diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui index bb38666..e9bcbf8 100644 --- a/ui/widgets/conversation.ui +++ b/ui/widgets/conversation.ui @@ -292,6 +292,20 @@ + + + + + + + + .. + + + true + + + @@ -400,8 +414,8 @@ background-color: transparent <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Liberation Sans'; font-size:10pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> +</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Liberation Sans';"><br /></p></body></html> false