Compare commits
22 Commits
master
...
maybe-omem
Author | SHA1 | Date | |
---|---|---|---|
6c9f1ab964 | |||
442ad37300 | |||
08fe37bfb2 | |||
bb2ce750c8 | |||
bbeeee4c8a | |||
574210f5d9 | |||
2654e38665 | |||
12ffe8e8e6 | |||
006752b31c | |||
b1a8f162ce | |||
6721b62629 | |||
b22a4c8ca3 | |||
a6254d88b3 | |||
2fbbe1ec22 | |||
140b0fa6b4 | |||
cb7e2ede75 | |||
bc66ab7e52 | |||
f94c3dac14 | |||
7d648ab081 | |||
7c1ae4737e | |||
04e745fad4 | |||
9fbbe0c120 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||
|
@ -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})
|
||||
|
@ -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;
|
||||
|
@ -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
1
external/signal-protocol-c
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 3a83a4f4ed2302ff6e68ab569c88793b50c22d28
|
25
qomemo/CMakeLists.txt
Normal file
25
qomemo/CMakeLists.txt
Normal 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
5
qomemo/bundle.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
/*
|
||||
* Created by victoria on 2021-05-12.
|
||||
*/
|
||||
|
||||
#include "bundle.h"
|
34
qomemo/bundle.h
Normal file
34
qomemo/bundle.h
Normal 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
180
qomemo/database.cpp
Normal 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
56
qomemo/database.h
Normal 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
5
qomemo/device.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
/*
|
||||
* Created by victoria on 2021-05-13.
|
||||
*/
|
||||
|
||||
#include "device.h"
|
25
qomemo/device.h
Normal file
25
qomemo/device.h
Normal 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
|
14
qomemo/device_key_storage.cpp
Normal file
14
qomemo/device_key_storage.cpp
Normal 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) {}
|
18
qomemo/device_key_storage.h
Normal file
18
qomemo/device_key_storage.h
Normal 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
35
qomemo/device_service.cpp
Normal 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
38
qomemo/device_service.h
Normal 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
6
qomemo/key.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
/*
|
||||
* Created by victoria on 2021-05-15.
|
||||
*/
|
||||
|
||||
#include "key.h"
|
||||
|
27
qomemo/key.h
Normal file
27
qomemo/key.h
Normal 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
94
qomemo/qomemo.cpp
Normal 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
42
qomemo/qomemo.h
Normal 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
|
292
qomemo/qxmpp_omemo_manager.cpp
Normal file
292
qomemo/qxmpp_omemo_manager.cpp
Normal 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 ¤tList) {
|
||||
if (jid == client()->configuration().jidBare())
|
||||
generateDeviceForSelfIfNeeded(currentList);
|
||||
});
|
||||
}
|
||||
|
||||
bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
|
||||
QString str{};
|
||||
QTextStream info(&str);
|
||||
stanza.save(info, 4);
|
||||
|
||||
std::cout << str.toStdString();
|
||||
|
||||
if (handleDeviceList(stanza) || handleMissingDeviceList(stanza) || handleEncryptedMessage(stanza))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Manager::handleDeviceList(const QDomElement &stanza) {
|
||||
if (!(stanza.tagName() == "iq"))
|
||||
return false;
|
||||
|
||||
if (!(stanza.attribute("type") == "result"))
|
||||
return false;
|
||||
|
||||
auto pubsub = stanza.firstChildElement("pubsub");
|
||||
if (pubsub.isNull())
|
||||
return false;
|
||||
|
||||
auto items = pubsub.firstChildElement("items");
|
||||
if (!(items.attribute("node") == omemoVariant->getDeviceListNode()))
|
||||
return false;
|
||||
|
||||
auto deviceList = omemoVariant->latestDeviceListFromPubSubNode(items);
|
||||
if (!deviceList.has_value())
|
||||
return false;
|
||||
|
||||
emit deviceListReceived(stanza.attribute("from"), deviceList.value());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Manager::handleMissingDeviceList(const QDomElement &stanza) {
|
||||
if (stanza.tagName() != "iq")
|
||||
return false;
|
||||
|
||||
if (stanza.attribute("type") != "error")
|
||||
return false;
|
||||
|
||||
auto pubsub = stanza.firstChildElement("pubsub");
|
||||
if (pubsub.isNull())
|
||||
return false;
|
||||
|
||||
auto items = pubsub.firstChildElement("items");
|
||||
if (items.attribute("node") != omemoVariant->getDeviceListNode())
|
||||
return false;
|
||||
|
||||
auto error = stanza.firstChildElement("error");
|
||||
|
||||
if (error.namespaceURI() != "jabber:client" || error.attribute("code") != "404")
|
||||
return false;
|
||||
|
||||
qDebug() << "Got 404 deviceList for" << stanza.attribute("from");
|
||||
emit deviceListNotFound(stanza.attribute("from"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Manager::handleEncryptedMessage(const QDomElement &stanza) {
|
||||
if (stanza.tagName() != "message")
|
||||
return false;
|
||||
|
||||
auto encrypted = stanza.firstChildElement("encrypted");
|
||||
if (encrypted.isNull())
|
||||
return false;
|
||||
|
||||
qDebug() << "!!!! Got encrypted message!!";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QXmpp::Omemo::Manager::setClient(QXmppClient *client) {
|
||||
QXmppClientExtension::setClient(client);
|
||||
|
||||
if (!client)
|
||||
return;
|
||||
|
||||
QObject::connect(client, &QXmppClient::connected, this,
|
||||
&Manager::fetchOwnDevices);
|
||||
}
|
||||
|
||||
void QXmpp::Omemo::Manager::fetchOwnDevices() {
|
||||
QXmppPubSubIq iq{};
|
||||
iq.setFrom(client()->configuration().jid());
|
||||
iq.setTo(client()->configuration().jidBare());
|
||||
iq.setType(QXmppIq::Get);
|
||||
iq.setQueryNode(omemoVariant->getDeviceListNode());
|
||||
|
||||
client()->sendPacket(iq);
|
||||
}
|
||||
|
||||
QSharedPointer<DeviceService> Manager::getDeviceService() {
|
||||
return deviceService;
|
||||
}
|
||||
|
||||
void Manager::publishDeviceList(const DeviceList &deviceList) {
|
||||
// QXmppPubSubIq iq{};
|
||||
// iq.setFrom(client()->configuration().jid());
|
||||
// iq.setType(QXmppIq::Set);
|
||||
// iq.setQueryNode(omemoVariant->getDeviceListNode());
|
||||
// iq.setItems()
|
||||
|
||||
auto iq = omemoVariant->deviceListSetIq(deviceList);
|
||||
iq.setFrom(client()->configuration().jid());
|
||||
|
||||
QString str{};
|
||||
QXmlStreamWriter info(&str);
|
||||
iq.toXml(&info);
|
||||
qInfo() << str;
|
||||
|
||||
client()->sendPacket(iq);
|
||||
}
|
||||
|
||||
void Manager::generateDeviceListForSelf() {
|
||||
qInfo() << "Generate device for self...";
|
||||
|
||||
generateDeviceForSelfIfNeeded(DeviceList());
|
||||
}
|
||||
|
||||
void Manager::publishBundle(int deviceId, const Bundle &bundle) {
|
||||
auto iq = omemoVariant->bundleSetIq(deviceId, bundle);
|
||||
iq.setFrom(client()->configuration().jid());
|
||||
|
||||
QString str{};
|
||||
QXmlStreamWriter info(&str);
|
||||
iq.toXml(&info);
|
||||
qInfo() << str;
|
||||
|
||||
client()->sendPacket(iq);
|
||||
}
|
||||
|
||||
void Manager::generateDeviceForSelfIfNeeded(const DeviceList ¤tList) {
|
||||
auto db = deviceService->getDatabase(client()->configuration().jidBare());
|
||||
auto activeId = db->loadActiveDeviceId();
|
||||
|
||||
if (activeId.has_value()) {
|
||||
qInfo() << "Current device:" << *activeId;
|
||||
|
||||
bool found = false;
|
||||
for (const auto &d : currentList.devices)
|
||||
if (d.id == *activeId)
|
||||
found = true;
|
||||
|
||||
if (found)
|
||||
return;
|
||||
|
||||
qInfo() << "Could not find device" << *activeId << ", generating new one";
|
||||
}
|
||||
|
||||
qInfo() << "Generating device";
|
||||
|
||||
auto deviceId = QRandomGenerator64::system()->bounded(INT32_MAX);
|
||||
db->saveActiveDeviceId(deviceId);
|
||||
|
||||
Device device{};
|
||||
device.id = deviceId;
|
||||
|
||||
auto updatedList = currentList;
|
||||
|
||||
|
||||
updatedList.devices.push_back(device);
|
||||
publishDeviceList(updatedList);
|
||||
|
||||
auto bundle = generateAndSaveBundle(deviceId);
|
||||
publishBundle(deviceId, bundle);
|
||||
}
|
||||
|
||||
Bundle Manager::generateAndSaveBundle(int deviceId) {
|
||||
auto database = deviceService->getDatabase(client()->configuration().jidBare());
|
||||
Bundle result{};
|
||||
|
||||
ec_key_pair *ecKeyPair;
|
||||
curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &ecKeyPair);
|
||||
|
||||
signal_buffer *ecPublic;
|
||||
ec_public_key_serialize(&ecPublic, ec_key_pair_get_public(ecKeyPair));
|
||||
|
||||
signal_buffer *ecSecret;
|
||||
ec_private_key_serialize(&ecSecret, ec_key_pair_get_private(ecKeyPair));
|
||||
|
||||
QByteArray identityKey((const char *) signal_buffer_const_data(ecPublic), (int) signal_buffer_len(ecPublic));
|
||||
QByteArray identityKeySecret((const char *) signal_buffer_const_data(ecSecret), (int) signal_buffer_len(ecSecret));
|
||||
|
||||
database->saveIdentityKey(deviceId, identityKey);
|
||||
database->saveIdentityKeySecret(deviceId, identityKeySecret);
|
||||
|
||||
result.ik = identityKey;
|
||||
|
||||
// Generate SPK
|
||||
ec_key_pair *ecSpk;
|
||||
curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &ecSpk);
|
||||
signal_buffer *spkPublic, *spkSecret;
|
||||
ec_public_key_serialize(&spkPublic, ec_key_pair_get_public(ecSpk));
|
||||
ec_private_key_serialize(&spkSecret, ec_key_pair_get_private(ecSpk));
|
||||
|
||||
// Generate SPKs
|
||||
signal_buffer *signature;
|
||||
curve_calculate_signature(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &signature,
|
||||
ec_key_pair_get_private(ecKeyPair), signal_buffer_const_data(spkPublic),
|
||||
signal_buffer_len(spkPublic));
|
||||
|
||||
SignedPreKey spk{};
|
||||
spk.id = 1;
|
||||
spk.key.publicKey = QByteArray((const char *) signal_buffer_const_data(spkPublic), (int) signal_buffer_len(spkPublic));
|
||||
spk.key.secretKey = QByteArray((const char *) signal_buffer_const_data(spkSecret), (int) signal_buffer_len(spkSecret));
|
||||
spk.signature = QByteArray((const char *) signal_buffer_const_data(signature), (int) signal_buffer_len(signature));
|
||||
|
||||
result.spk = spk.key.publicKey;
|
||||
result.spks = spk.signature;
|
||||
result.spkId = 1;
|
||||
|
||||
// Generate 100 PK
|
||||
for (auto i = 1; i <= 100; ++i) {
|
||||
ec_key_pair *currentPreKey;
|
||||
curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), ¤tPreKey);
|
||||
|
||||
signal_buffer *pkPublic, *pkSecret;
|
||||
ec_public_key_serialize(&pkPublic, ec_key_pair_get_public(currentPreKey));
|
||||
ec_private_key_serialize(&pkSecret, ec_key_pair_get_private(currentPreKey));
|
||||
|
||||
KeyPair preKey{};
|
||||
preKey.publicKey = QByteArray((const char *) signal_buffer_const_data(pkPublic), (int) signal_buffer_len(pkPublic));
|
||||
preKey.secretKey = QByteArray((const char *) signal_buffer_const_data(pkSecret), (int) signal_buffer_len(pkSecret));
|
||||
|
||||
database->savePreKey(deviceId, i, preKey);
|
||||
|
||||
PreKey pk{};
|
||||
pk.data = preKey.publicKey;
|
||||
pk.id = i;
|
||||
|
||||
result.prekeys.append(pk);
|
||||
|
||||
signal_buffer_free(pkPublic);
|
||||
signal_buffer_free(pkSecret);
|
||||
SIGNAL_UNREF(currentPreKey);
|
||||
}
|
||||
|
||||
signal_buffer_free(signature);
|
||||
|
||||
signal_buffer_free(ecPublic);
|
||||
signal_buffer_free(ecSecret);
|
||||
SIGNAL_UNREF(ecKeyPair);
|
||||
|
||||
signal_buffer_free(spkPublic);
|
||||
signal_buffer_free(spkSecret);
|
||||
SIGNAL_UNREF(ecSpk);
|
||||
|
||||
return result;
|
||||
}
|
64
qomemo/qxmpp_omemo_manager.h
Normal file
64
qomemo/qxmpp_omemo_manager.h
Normal 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 ¤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> deviceService;
|
||||
QScopedPointer<Variant::Base> omemoVariant;
|
||||
std::shared_ptr<Signal::Context> signalContext;
|
||||
};
|
||||
|
||||
} // namespace QXmpp::Omemo
|
26
qomemo/sce.cpp
Normal file
26
qomemo/sce.cpp
Normal 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
14
qomemo/sce.h
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Created by victoria on 2021-05-12.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QXmppElement.h>
|
||||
|
||||
namespace QXmpp::Sce {
|
||||
|
||||
QString generatePadding();
|
||||
|
||||
}
|
9
qomemo/signal/CMakeLists.txt
Normal file
9
qomemo/signal/CMakeLists.txt
Normal 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
28
qomemo/signal/context.cpp
Normal 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
32
qomemo/signal/context.h
Normal 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
|
12
qomemo/signal/crypto/CMakeLists.txt
Normal file
12
qomemo/signal/crypto/CMakeLists.txt
Normal 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
|
||||
)
|
187
qomemo/signal/crypto/aes_openssl.cpp
Normal file
187
qomemo/signal/crypto/aes_openssl.cpp
Normal 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;
|
||||
}
|
17
qomemo/signal/crypto/aes_openssl.h
Normal file
17
qomemo/signal/crypto/aes_openssl.h
Normal 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
|
40
qomemo/signal/crypto/crypto.cpp
Normal file
40
qomemo/signal/crypto/crypto.cpp
Normal 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;
|
||||
}
|
13
qomemo/signal/crypto/crypto.h
Normal file
13
qomemo/signal/crypto/crypto.h
Normal 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();
|
||||
|
||||
}
|
5
qomemo/signal/crypto/ec.cpp
Normal file
5
qomemo/signal/crypto/ec.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
/*
|
||||
* Created by victoria on 2021-06-17.
|
||||
*/
|
||||
|
||||
#include "ec.h"
|
20
qomemo/signal/crypto/ec.h
Normal file
20
qomemo/signal/crypto/ec.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
71
qomemo/signal/crypto/hmac_sha256_openssl.cpp
Normal file
71
qomemo/signal/crypto/hmac_sha256_openssl.cpp
Normal 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
|
||||
}
|
||||
}
|
16
qomemo/signal/crypto/hmac_sha256_openssl.h
Normal file
16
qomemo/signal/crypto/hmac_sha256_openssl.h
Normal 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
|
67
qomemo/signal/crypto/sha512_digest_openssl.cpp
Normal file
67
qomemo/signal/crypto/sha512_digest_openssl.cpp
Normal 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);
|
||||
}
|
16
qomemo/signal/crypto/sha512_digest_openssl.h
Normal file
16
qomemo/signal/crypto/sha512_digest_openssl.h
Normal 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
|
14
qomemo/signal/stores/CMakeLists.txt
Normal file
14
qomemo/signal/stores/CMakeLists.txt
Normal 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
|
||||
)
|
70
qomemo/signal/stores/identity_key_store.cpp
Normal file
70
qomemo/signal/stores/identity_key_store.cpp
Normal 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);
|
||||
};
|
||||
}
|
32
qomemo/signal/stores/identity_key_store.h
Normal file
32
qomemo/signal/stores/identity_key_store.h
Normal 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
|
38
qomemo/signal/stores/pre_key_store.cpp
Normal file
38
qomemo/signal/stores/pre_key_store.cpp
Normal 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);
|
||||
};
|
||||
}
|
28
qomemo/signal/stores/pre_key_store.h
Normal file
28
qomemo/signal/stores/pre_key_store.h
Normal 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
|
30
qomemo/signal/stores/sender_key_store.cpp
Normal file
30
qomemo/signal/stores/sender_key_store.cpp
Normal 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);
|
||||
};
|
||||
}
|
21
qomemo/signal/stores/sender_key_store.h
Normal file
21
qomemo/signal/stores/sender_key_store.h
Normal 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
|
57
qomemo/signal/stores/session_store.cpp
Normal file
57
qomemo/signal/stores/session_store.cpp
Normal 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);
|
||||
};
|
||||
}
|
24
qomemo/signal/stores/session_store.h
Normal file
24
qomemo/signal/stores/session_store.h
Normal 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
|
43
qomemo/signal/stores/signed_pre_key_store.cpp
Normal file
43
qomemo/signal/stores/signed_pre_key_store.cpp
Normal 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);
|
||||
};
|
||||
}
|
21
qomemo/signal/stores/signed_pre_key_store.h
Normal file
21
qomemo/signal/stores/signed_pre_key_store.h
Normal 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
|
25
qomemo/signal/stores/store_context.cpp
Normal file
25
qomemo/signal/stores/store_context.cpp
Normal 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);
|
||||
}
|
42
qomemo/signal/stores/store_context.h
Normal file
42
qomemo/signal/stores/store_context.h
Normal 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
14
qomemo/signal/util.cpp
Normal 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
18
qomemo/signal/util.h
Normal 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);
|
||||
|
||||
}
|
9
qomemo/user_device_list.cpp
Normal file
9
qomemo/user_device_list.cpp
Normal 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
18
qomemo/user_device_list.h
Normal 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
|
1
qomemo/variant/CMakeLists.txt
Normal file
1
qomemo/variant/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
target_sources(squawk PRIVATE xep0384.cpp xep0384.h conversations.cpp conversations.h omemo_base.cpp omemo_base.h)
|
205
qomemo/variant/conversations.cpp
Normal file
205
qomemo/variant/conversations.cpp
Normal 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;
|
||||
}
|
32
qomemo/variant/conversations.h
Normal file
32
qomemo/variant/conversations.h
Normal 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
|
5
qomemo/variant/omemo_base.cpp
Normal file
5
qomemo/variant/omemo_base.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
/*
|
||||
* Created by victoria on 2021-05-13.
|
||||
*/
|
||||
|
||||
#include "omemo_base.h"
|
45
qomemo/variant/omemo_base.h
Normal file
45
qomemo/variant/omemo_base.h
Normal 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
|
5
qomemo/variant/xep0384.cpp
Normal file
5
qomemo/variant/xep0384.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
/*
|
||||
* Created by victoria on 2021-05-13.
|
||||
*/
|
||||
|
||||
#include "xep0384.h"
|
7
qomemo/variant/xep0384.h
Normal file
7
qomemo/variant/xep0384.h
Normal file
@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Created by victoria on 2021-05-13.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace QXmpp::Omemo {}
|
@ -16,4 +16,6 @@ target_sources(squawk PRIVATE
|
||||
utils.h
|
||||
vcard.cpp
|
||||
vcard.h
|
||||
qxmppfactories.cpp
|
||||
qxmppfactories.h
|
||||
)
|
||||
|
75
shared/qxmppfactories.cpp
Normal file
75
shared/qxmppfactories.cpp
Normal 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
25
shared/qxmppfactories.h
Normal 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
|
@ -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
8
ui/omemo/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
target_sources(squawk PRIVATE
|
||||
contactsettings.cpp
|
||||
contactsettings.h
|
||||
contactsettings.ui
|
||||
omemodevices.cpp
|
||||
omemodevices.h
|
||||
omemodevices.ui
|
||||
)
|
25
ui/omemo/contactsettings.cpp
Normal file
25
ui/omemo/contactsettings.cpp
Normal 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();
|
||||
}
|
26
ui/omemo/contactsettings.h
Normal file
26
ui/omemo/contactsettings.h
Normal 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
144
ui/omemo/contactsettings.ui
Normal 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
15
ui/omemo/omemodevices.cpp
Normal 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
23
ui/omemo/omemodevices.h
Normal 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
508
ui/omemo/omemodevices.ui
Normal 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>
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
11
ui/squawk.ui
11
ui/squawk.ui
@ -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/>
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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><!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></string>
|
||||
</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></string>
|
||||
</property>
|
||||
<property name="acceptRichText">
|
||||
<bool>false</bool>
|
||||
|
Loading…
Reference in New Issue
Block a user