Compare commits

...

22 Commits

Author SHA1 Message Date
vae 6c9f1ab964 feat: wip omemo + key publish 2021-07-22 23:01:30 +03:00
vae 442ad37300 feat: omemo signal lib wip 2021-07-22 20:45:39 +03:00
vae 08fe37bfb2
feat(omemo): basic UI 2021-05-15 12:28:43 +03:00
vae bb2ce750c8
build: add OpenSSL dependency 2021-05-15 00:48:32 +03:00
vae bbeeee4c8a
ref(omemo): reformat qomemo/ 2021-05-13 17:54:37 +03:00
vae 574210f5d9
ref(omemo): bundle separate, add db 2021-05-13 17:51:05 +03:00
vae 2654e38665
feat(omemo): add signal protocol wrappers 2021-05-13 17:50:29 +03:00
vae 12ffe8e8e6
ref(omemo): add qomemo/variant 2021-05-13 00:32:13 +03:00
vae 006752b31c
feat(OMEMO): QXmppClientExtension for OMEMO 2021-05-12 17:33:34 +03:00
vae b1a8f162ce
feat(OMEMO): DeviceKeyStorage, DeviceService, UserDeviceList stubs 2021-05-12 15:50:52 +03:00
vae 6721b62629
feat(OMEMO): qxmppfactories, refactoring 2021-05-12 15:00:41 +03:00
vae b22a4c8ca3
feat(OMEMO): add Device, DeviceList, PreKey, Bundle + XML 2021-05-12 12:33:35 +03:00
vae a6254d88b3
feat: OMEMO devices ui mock 2021-05-12 10:06:14 +03:00
vae 2fbbe1ec22
Merge branch 'build-refactor' into maybe-omemo 2021-05-12 08:30:22 +03:00
vae 140b0fa6b4
feat: add omemo key list entry WIP 2021-05-12 01:44:47 +03:00
vae cb7e2ede75
feat: add OMEMODevices (WIP) 2021-05-12 00:58:39 +03:00
vae bc66ab7e52
build: signal-protocol-c 2021-05-11 23:17:57 +03:00
vae f94c3dac14 Merge branch 'build-refactor' into maybe-omemo 2021-05-11 23:05:50 +03:00
vae 7d648ab081 feat: add omemo buttons 2021-05-11 19:11:02 +03:00
vae 7c1ae4737e
feat(omemo): WIP signal-protocol-c cpp wrapper 2021-05-11 00:32:44 +03:00
vae 04e745fad4
build: add qomemo dir with TODO 2021-05-11 00:12:33 +03:00
vae 9fbbe0c120
build: add signal-protocol-c dependency 2021-05-10 22:42:17 +03:00
79 changed files with 3280 additions and 22 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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})

View File

@ -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;

View File

@ -30,23 +30,24 @@
#include <map>
#include <set>
#include <QXmppRosterManager.h>
#include <QXmppCarbonManager.h>
#include <QXmppDiscoveryManager.h>
#include <QXmppMamManager.h>
#include <QXmppMucManager.h>
#include <QXmppClient.h>
#include <QXmppBookmarkManager.h>
#include <QXmppBookmarkSet.h>
#include <QXmppCarbonManager.h>
#include <QXmppClient.h>
#include <QXmppDiscoveryManager.h>
#include <QXmppMamManager.h>
#include <QXmppMessageReceiptManager.h>
#include <QXmppMucManager.h>
#include <QXmppRosterManager.h>
#include <QXmppUploadRequestManager.h>
#include <QXmppVCardIq.h>
#include <QXmppVCardManager.h>
#include <QXmppMessageReceiptManager.h>
#include <qomemo/qxmpp_omemo_manager.h>
#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<QXmpp::Omemo::Manager> omemo;
private slots:
void onClientStateChange(QXmppClient::State state);

1
external/signal-protocol-c vendored Submodule

@ -0,0 +1 @@
Subproject commit 3a83a4f4ed2302ff6e68ab569c88793b50c22d28

25
qomemo/CMakeLists.txt Normal file
View File

@ -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)

5
qomemo/bundle.cpp Normal file
View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "bundle.h"

34
qomemo/bundle.h Normal file
View File

@ -0,0 +1,34 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QList>
#include <QString>
#include <QBuffer>
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<PreKey> prekeys;
};
} // namespace QXmpp::Omemo

180
qomemo/database.cpp Normal file
View File

@ -0,0 +1,180 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "database.h"
#include "bundle.h"
#include <QDebug>
#include <QDir>
#include <QException>
#include <QStandardPaths>
#include <QString>
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<QByteArray> 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<char *>(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<int> 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<int *>(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<QByteArray> Database::loadIdentityKey(int deviceId) {
return std::nullopt;
}
bool Database::containsPreKey() {
return false;
}
std::optional<KeyPair> Database::loadPreKey(int deviceId, int id) {
return std::nullopt;
}
bool Database::savePreKey(int deviceId, int id, const KeyPair &preKey) {
return false;
}
std::optional<Bundle> Database::loadBundle(int deviceId) {
Bundle result{};
auto ik = loadIdentityKey(deviceId);
result.ik = ik.value();
auto spk = loadSignedPreKey(deviceId);
result.spk = spk->key.publicKey;
result.spks = spk->signature;
result.spkId = spk->id;
// PreKeys
return result;
}
bool Database::saveBundle(int deviceId, const Bundle &bundle) {
return false;
}
std::optional<SignedPreKey> Database::loadSignedPreKey(int deviceId) {
return std::optional<SignedPreKey>();
}
bool Database::saveSignedPreKey(int deviceId, const SignedPreKey &signedPreKey) {
return false;
}

56
qomemo/database.h Normal file
View File

@ -0,0 +1,56 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <QBuffer>
#include <QString>
#include <lmdb.h>
#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<int> loadActiveDeviceId();
bool saveActiveDeviceId(int deviceId);
std::optional<QByteArray> loadIdentityKeySecret(int deviceId);
bool saveIdentityKeySecret(int deviceId, const QByteArray &identityKeySecret);
std::optional<Bundle> loadBundle(int deviceId);
bool saveBundle(int deviceId, const Bundle& bundle);
// For any user
std::optional<QByteArray> loadIdentityKey(int deviceId);
bool saveIdentityKey(int deviceId, const QByteArray &identityKey);
bool containsPreKey();
std::optional<KeyPair> loadPreKey(int deviceId, int id);
bool savePreKey(int deviceId, int id, const KeyPair &preKey);
std::optional<SignedPreKey> loadSignedPreKey(int deviceId);
bool saveSignedPreKey(int deviceId, const SignedPreKey& signedPreKey);
const QString jid;
private:
MDB_env *env{};
MDB_dbi dbiDevices{};
MDB_dbi dbiKeys{};
MDB_dbi dbiPreKeys{};
MDB_dbi dbiIdentityKeys{};
};
} // namespace QXmpp::Omemo

5
qomemo/device.cpp Normal file
View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "device.h"

25
qomemo/device.h Normal file
View File

@ -0,0 +1,25 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <QList>
class QXmppElement;
class QXmppIq;
namespace QXmpp::Omemo {
class Device {
public:
int id;
};
class DeviceList {
public:
QList<Device> devices;
};
} // namespace QXmpp::Omemo

View File

@ -0,0 +1,14 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "device_key_storage.h"
#include <QRandomGenerator>
int QXmpp::Omemo::DeviceKeyStorage::generateDeviceId() {
QRandomGenerator random{};
return 1 + random.bounded(INT32_MAX - 1);
}
QXmpp::Omemo::DeviceKeyStorage::DeviceKeyStorage(int deviceId) : deviceId(deviceId) {}

View File

@ -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

35
qomemo/device_service.cpp Normal file
View File

@ -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<Database> DeviceService::getDatabase(const QString &jid) {
if (!databases.contains(jid)) {
databases.insert(jid, QSharedPointer<Database>::create(jid));
}
return databases[jid];
}
void DeviceService::addIdentity(const QString &jid, int deviceId, const QByteArray& publicKey) {
auto db = getDatabase(jid);
db->saveIdentityKey(deviceId, publicKey);
}
void DeviceService::onDeviceListNotFound(const QString &jid) {
qInfo() << "Device list not found:" << jid;
}

38
qomemo/device_service.h Normal file
View File

@ -0,0 +1,38 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QBuffer>
#include "qomemo.h"
#include "user_device_list.h"
#include "database.h"
#include <QXmppClient.h>
namespace QXmpp::Omemo {
class DeviceList;
class DeviceService : public QObject {
Q_OBJECT
public:
explicit DeviceService(QObject *parent);
QSharedPointer<Database> getDatabase(const QString& jid);
public slots:
void addIdentity(const QString& jid, int deviceId, const QByteArray& publicKey);
void onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list);
void onDeviceListNotFound(const QString &jid);
private:
QMap<QString, QSharedPointer<Database>> databases{};
};
} // namespace QXmpp::Omemo

6
qomemo/key.cpp Normal file
View File

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

27
qomemo/key.h Normal file
View File

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

94
qomemo/qomemo.cpp Normal file
View File

@ -0,0 +1,94 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "qomemo.h"
#include "sce.h"
#include <shared/qxmppfactories.h>
#include <QBuffer>
#include <QDebug>
#include <QDomDocument>
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;
}

42
qomemo/qomemo.h Normal file
View File

@ -0,0 +1,42 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QXmppPubSubIq.h>
#include <QDateTime>
#include <QXmppMessage.h>
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<MessageKey> keys{};
QString from{};
QString to{};
QDateTime timestamp{};
QString iv{};
QXmppMessage message{};
};
} // namespace QXmpp::Omemo

View File

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

View File

@ -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 <memory>
#include <QXmppClientExtension.h>
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<DeviceService> getDeviceService();
Bundle generateAndSaveBundle(int deviceId);
public slots:
void fetchOwnDevices();
void publishDeviceList(const QXmpp::Omemo::DeviceList& deviceList);
void generateDeviceListForSelf();
void generateDeviceForSelfIfNeeded(const QXmpp::Omemo::DeviceList &currentList);
void publishBundle(int deviceId, const QXmpp::Omemo::Bundle& bundle);
signals:
void deviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list);
void deviceListNotFound(const QString &jid);
protected:
void setClient(QXmppClient *client) override;
private:
QSharedPointer<DeviceService> deviceService;
QScopedPointer<Variant::Base> omemoVariant;
std::shared_ptr<Signal::Context> signalContext;
};
} // namespace QXmpp::Omemo

26
qomemo/sce.cpp Normal file
View File

@ -0,0 +1,26 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "sce.h"
#include <QRandomGenerator>
#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;
}

14
qomemo/sce.h Normal file
View File

@ -0,0 +1,14 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QDateTime>
#include <QXmppElement.h>
namespace QXmpp::Sce {
QString generatePadding();
}

View File

@ -0,0 +1,9 @@
target_sources(squawk PRIVATE
context.cpp
context.h
util.cpp
util.h
)
add_subdirectory(crypto)
add_subdirectory(stores)

28
qomemo/signal/context.cpp Normal file
View File

@ -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<Crypto::ECKeyPair> Context::generateCurveKeyPair() {
auto result = std::unique_ptr<Crypto::ECKeyPair>();
// TODO
return result;
}
signal_context *Context::temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped() {
return ctx;
}

32
qomemo/signal/context.h Normal file
View File

@ -0,0 +1,32 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include "crypto/ec.h"
#include <memory>
#include <signal/signal_protocol.h>
namespace Signal {
class Context {
public:
Context();
~Context();
Context(const Context &) = delete;
Context(Context &&) = delete;
Context &operator=(const Context &) = delete;
std::unique_ptr<Crypto::ECKeyPair> generateCurveKeyPair();
signal_context *temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped();
private:
signal_crypto_provider cryptoProvider{};
signal_context *ctx{nullptr};
};
} // namespace Signal

View File

@ -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
)

View File

@ -0,0 +1,187 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "aes_openssl.h"
#include <memory>
extern "C" {
#include <openssl/evp.h>
#include <openssl/opensslv.h>
}
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<uint8_t>(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<uint8_t>(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;
}

View File

@ -0,0 +1,17 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
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

View File

@ -0,0 +1,40 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "crypto.h"
extern "C" {
#include <openssl/rand.h>
}
#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;
}

View File

@ -0,0 +1,13 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Crypto {
signal_crypto_provider createProvider();
}

View File

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

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

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

View File

@ -0,0 +1,71 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "hmac_sha256_openssl.h"
extern "C" {
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
}
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_CTX *>(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_CTX *>(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_CTX *>(hmac_context);
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
HMAC_CTX_free(ctx);
#else
HMAC_CTX_cleanup(ctx);
delete ctx;
#endif
}
}

View File

@ -0,0 +1,16 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
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

View File

@ -0,0 +1,67 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "sha512_digest_openssl.h"
extern "C" {
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
}
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<EVP_MD_CTX *>(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<EVP_MD_CTX *>(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<EVP_MD_CTX *>(digest_context);
EVP_MD_CTX_destroy(ctx);
}

View File

@ -0,0 +1,16 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
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

View File

@ -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
)

View File

@ -0,0 +1,70 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "identity_key_store.h"
#include "qomemo/signal/util.h"
#include <utility>
Signal::Store::IdentityKeyStore::IdentityKeyStore(QXmpp::Omemo::DeviceService &deviceService, QString jid, int deviceId)
: jid(std::move(jid)), deviceId(deviceId), deviceService(deviceService) {
database = deviceService.getDatabase(jid);
}
int Signal::Store::IdentityKeyStore::getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data) {
auto pk = database->loadIdentityKey(deviceId);
auto sk = database->loadIdentityKeySecret(deviceId);
*public_data = signal_buffer_create(reinterpret_cast<const uint8_t *>(pk->data()), pk->size());
*private_data = signal_buffer_create(reinterpret_cast<const uint8_t *>(sk->data()), sk->size());
return 0;
}
int Signal::Store::IdentityKeyStore::getLocalRegistrationId(uint32_t *registration_id) {
// TODO: Figure out what registration id is used for
*registration_id = 1;
return 0;
}
int Signal::Store::IdentityKeyStore::saveIdentity(const signal_protocol_address *address, uint8_t *key_data,
size_t key_len) {
auto identityJid = Signal::Util::jidFromAddress(address);
auto identityKey = Signal::Util::byteArray(key_data, key_len);
deviceService.getDatabase(identityJid)->saveIdentityKey(address->device_id, identityKey);
return 0;
}
int Signal::Store::IdentityKeyStore::isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data,
size_t key_len) {
auto identityJid = Signal::Util::jidFromAddress(address);
auto actualIdentityKey = deviceService.getDatabase(identityJid)->loadIdentityKey(address->device_id);
if (!actualIdentityKey.has_value()) {
return 1;
}
auto givenIdentityKey = Signal::Util::byteArray(key_data, key_len);
return givenIdentityKey == actualIdentityKey ? 1 : 0;
}
void Signal::Store::IdentityKeyStore::fillCallbacks(signal_protocol_identity_key_store &store) {
store.get_identity_key_pair = [](signal_buffer **public_data, signal_buffer **private_data, void *ptr) {
return static_cast<IdentityKeyStore *>(ptr)->getIdentityKeyPair(public_data, private_data);
};
store.get_local_registration_id = [](void *ptr, uint32_t *registrationId) {
return static_cast<IdentityKeyStore *>(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<IdentityKeyStore *>(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<IdentityKeyStore *>(ptr)->saveIdentity(address, key_data, key_len);
};
}

View File

@ -0,0 +1,32 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
#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<QXmpp::Omemo::Database> database;
};
} // namespace Signal::Store

View File

@ -0,0 +1,38 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "pre_key_store.h"
Signal::Store::PreKeyStore::PreKeyStore(QSharedPointer<QXmpp::Omemo::Database> database) : database(
std::move(database)) {}
int Signal::Store::PreKeyStore::containsPreKey(uint32_t pre_key_id) {
return 0;
}
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<PreKeyStore *>(ptr)->containsPreKey(id);
};
store.load_pre_key = [](signal_buffer **record, uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->loadPreKey(record, id);
};
store.remove_pre_key = [](uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->removePreKey(id);
};
store.store_pre_key = [](uint32_t id, uint8_t *record, size_t size,
void *ptr) {
return static_cast<PreKeyStore *>(ptr)->storePreKey(id, record, size);
};
}

View File

@ -0,0 +1,28 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
#include "qomemo/device_service.h"
namespace Signal::Store {
class PreKeyStore {
public:
explicit PreKeyStore(QSharedPointer<QXmpp::Omemo::Database> database);
int containsPreKey(uint32_t pre_key_id);
int loadPreKey(signal_buffer **record, uint32_t pre_key_id);
int storePreKey(uint32_t pre_key_id, uint8_t *record, size_t record_len);
int removePreKey(uint32_t pre_key_id);
void fillCallbacks(signal_protocol_pre_key_store &store);
private:
QSharedPointer<QXmpp::Omemo::Database> database;
};
} // namespace Signal::Store

View File

@ -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<SenderKeyStore *>(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<SenderKeyStore *>(ptr)->storeSenderKey(sender_key_name, record, record_len, user_record,
user_record_len);
};
}

View File

@ -0,0 +1,21 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
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

View File

@ -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<SessionStore *>(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<SessionStore *>(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<SessionStore *>(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<SessionStore *>(ptr)->containsSession(address);
};
store.delete_session_func = [](const signal_protocol_address *address, void *ptr) {
return static_cast<SessionStore *>(ptr)->deleteSession(address);
};
store.delete_all_sessions_func = [](const char *name, size_t name_len, void *ptr) {
return static_cast<SessionStore *>(ptr)->deleteAllSessions(name, name_len);
};
}

View File

@ -0,0 +1,24 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
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

View File

@ -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<SignedPreKeyStore *>(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<SignedPreKeyStore *>(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<SignedPreKeyStore *>(ptr)->containsSignedPreKey(
signed_pre_key_id);
};
store.remove_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) {
return static_cast<SignedPreKeyStore *>(ptr)->removeSignedPreKey(
signed_pre_key_id);
};
}

View File

@ -0,0 +1,21 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
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

View File

@ -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);
}

View File

@ -0,0 +1,42 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
#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> identityKeyStore;
std::unique_ptr<PreKeyStore> preKeyStore;
std::unique_ptr<SenderKeyStore> senderKeyStore;
std::unique_ptr<SessionStore> sessionStore;
std::unique_ptr<SignedPreKeyStore> signedPreKeyStore;
};
} // namespace Signal::Store

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

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

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

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

View File

@ -0,0 +1,9 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "user_device_list.h"
#include <utility>
QXmpp::Omemo::UserDeviceList::UserDeviceList(QString jid) : jid(std::move(jid)) {}

18
qomemo/user_device_list.h Normal file
View File

@ -0,0 +1,18 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QString>
namespace QXmpp::Omemo {
class UserDeviceList {
public:
explicit UserDeviceList(QString jid);
const QString jid;
};
} // namespace QXmpp::Omemo

View File

@ -0,0 +1 @@
target_sources(squawk PRIVATE xep0384.cpp xep0384.h conversations.cpp conversations.h omemo_base.cpp omemo_base.h)

View File

@ -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 <QDebug>
#include <QXmppIq.h>
#include <QXmppPubSubIq.h>
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<DeviceList> Variant::Conversations::latestDeviceListFromPubSubNode(const QXmppElement &items) {
auto item = items.firstChildElement("item");
if (item.isNull())
return std::nullopt;
auto list = item.firstChildElement("list");
if (list.isNull())
return std::nullopt;
if (!elementMatches(list, "list", "eu.siacs.conversations.axolotl"))
return std::nullopt;
DeviceList result{};
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<PreKey> Variant::Conversations::preKeyFromXml(const QXmppElement &xml) {
if (!elementMatches(xml, "preKeyPublic"))
return std::nullopt;
auto pk = PreKey();
pk.id = xml.attribute("preKeyId").toInt();
pk.data = QByteArray::fromBase64Encoding(xml.value().toUtf8()).decoded;
return pk;
}
std::optional<Bundle> Variant::Conversations::bundleFromXml(const QXmppElement &xml) {
if (!elementMatches(xml, "bundle", "eu.siacs.conversations.axolotl"))
return std::nullopt;
auto spkNode = xml.firstChildElement("signedPreKeyPublic");
if (spkNode.isNull()) {
qWarning() << "'bundle': missing 'signedPreKeyPublic'";
return std::nullopt;
}
Bundle result{};
result.spk = QByteArray::fromBase64Encoding(spkNode.value().toUtf8()).decoded;
result.spkId = spkNode.attribute("signedPreKeyId").toInt();
auto spksNode = xml.firstChildElement("signedPreKeySignature");
if (spksNode.isNull()) {
qWarning() << "'bundle': missing 'signedPreKeySignature'";
return std::nullopt;
}
result.spks = QByteArray::fromBase64Encoding(spksNode.value().toUtf8()).decoded;
auto ikNode = xml.firstChildElement("identityKey");
if (ikNode.isNull()) {
qWarning() << "'bundle': missing 'identityKey'";
return std::nullopt;
}
result.ik = QByteArray::fromBase64Encoding(ikNode.value().toUtf8()).decoded;
auto prekeysNode = xml.firstChildElement("prekeys");
auto pkNode = prekeysNode.firstChildElement("preKeyPublic");
result.prekeys.clear();
while (!pkNode.isNull()) {
auto maybePk = preKeyFromXml(pkNode);
if (maybePk)
result.prekeys.push_back(*maybePk);
pkNode = pkNode.nextSiblingElement("preKeyPublic");
}
return result;
}

View File

@ -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<DeviceList> latestDeviceListFromPubSubNode(const QXmppElement &items) override;
QXmppIq deviceListSetIq(const DeviceList &deviceList) override;
QXmppElement preKeyToXml(const PreKey &pk) override;
std::optional<PreKey> preKeyFromXml(const QXmppElement &xml) override;
QXmppElement bundleToXml(const Bundle &bundle) override;
std::optional<Bundle> bundleFromXml(const QXmppElement &xml) override;
QXmppIq bundleSetIq(int deviceId, const Bundle &bundle) override;
};
} // namespace QXmpp::Omemo::Variant

View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "omemo_base.h"

View File

@ -0,0 +1,45 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <optional>
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<DeviceList> latestDeviceListFromPubSubNode(const QXmppElement &xml) = 0;
virtual QXmppIq deviceListSetIq(const DeviceList &deviceList) = 0;
virtual QXmppElement preKeyToXml(const PreKey &pk) = 0;
virtual std::optional<PreKey> preKeyFromXml(const QXmppElement &xml) = 0;
virtual QXmppElement bundleToXml(const Bundle& bundle) = 0;
virtual std::optional<Bundle> bundleFromXml(const QXmppElement& xml) = 0;
virtual QXmppIq bundleSetIq(int deviceId, const Bundle& bundle) = 0;
};
} // namespace Variant
} // namespace QXmpp::Omemo

View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "xep0384.h"

7
qomemo/variant/xep0384.h Normal file
View File

@ -0,0 +1,7 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
namespace QXmpp::Omemo {}

View File

@ -16,4 +16,6 @@ target_sources(squawk PRIVATE
utils.h
vcard.cpp
vcard.h
qxmppfactories.cpp
qxmppfactories.h
)

75
shared/qxmppfactories.cpp Normal file
View File

@ -0,0 +1,75 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "qxmppfactories.h"
#include <QDebug>
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;
}

25
shared/qxmppfactories.h Normal file
View File

@ -0,0 +1,25 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QXmppElement.h>
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

View File

@ -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)

8
ui/omemo/CMakeLists.txt Normal file
View File

@ -0,0 +1,8 @@
target_sources(squawk PRIVATE
contactsettings.cpp
contactsettings.h
contactsettings.ui
omemodevices.cpp
omemodevices.h
omemodevices.ui
)

View File

@ -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();
}

View File

@ -0,0 +1,26 @@
/*
* Created by victoria on 2021-05-15.
*/
#pragma once
#include <QDialog>
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<Ui::ContactSettings> m_ui;
};

144
ui/omemo/contactsettings.ui Normal file
View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ContactSettings</class>
<widget class="QDialog" name="ContactSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Contact Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Settings for foo@example.com</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="useOmemoCheckBox">
<property name="text">
<string>Use OMEMO</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="devicesButton">
<property name="text">
<string>Devices...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ContactSettings</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ContactSettings</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

15
ui/omemo/omemodevices.cpp Normal file
View File

@ -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() {}

23
ui/omemo/omemodevices.h Normal file
View File

@ -0,0 +1,23 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QDialog>
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<Ui::OMEMODevices> m_ui;
};

508
ui/omemo/omemodevices.ui Normal file
View File

@ -0,0 +1,508 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OMEMODevices</class>
<widget class="QDialog" name="OMEMODevices">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>560</width>
<height>357</height>
</rect>
</property>
<property name="windowTitle">
<string>OMEMO Devices</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>List of OMEMO fingerprints for fooooo@bar.com</string>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>540</width>
<height>281</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QFrame" name="frame_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(6, 88, 6)</string>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>240</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Squawk</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="view-barcode-qr">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_9">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="approved">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_10">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="edit-delete-remove">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>240</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Dino</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="view-barcode-qr">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_7">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="approved">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_8">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="edit-delete-remove">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">background: rgb(111, 47, 47)</string>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<property name="lineWidth">
<number>1</number>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>240</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd 1234abcd</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Conversations</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="view-barcode-qr">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="approved">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="edit-delete-remove">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>OMEMODevices</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>OMEMODevices</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -20,6 +20,8 @@
#include "ui_squawk.h"
#include <QDebug>
#include <QIcon>
#include <utility>
#include <ui/omemo/omemodevices.h>
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);

View File

@ -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);

View File

@ -162,7 +162,7 @@
<x>0</x>
<y>0</y>
<width>718</width>
<height>27</height>
<height>23</height>
</rect>
</property>
<widget class="QMenu" name="menuSettings">
@ -224,6 +224,15 @@
<string>Add conference</string>
</property>
</action>
<action name="actionDeviceList">
<property name="icon">
<iconset theme="computer-laptop">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Device list</string>
</property>
</action>
</widget>
<resources/>
<connections/>

View File

@ -27,6 +27,7 @@
#include <unistd.h>
#include <QAbstractTextDocumentLayout>
#include <QCoreApplication>
#include <ui/omemo/contactsettings.h>
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();
}

View File

@ -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;

View File

@ -292,6 +292,20 @@
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="encryptionButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="security-low">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="attachButton">
<property name="text">
@ -400,8 +414,8 @@ background-color: transparent
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Liberation Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-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';&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="acceptRichText">
<bool>false</bool>