mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 19:08:58 +03:00
Implement import/export of megolm session keys (#358)
This commit is contained in:
parent
cf71a5858c
commit
a9ddc3b3d3
5 changed files with 195 additions and 27 deletions
4
deps/CMakeLists.txt
vendored
4
deps/CMakeLists.txt
vendored
|
@ -39,10 +39,10 @@ set(BOOST_SHA256
|
||||||
|
|
||||||
set(
|
set(
|
||||||
MTXCLIENT_URL
|
MTXCLIENT_URL
|
||||||
https://github.com/mujx/mtxclient/archive/3328fdedcb7db0a0bd2921189193504bf3b0c3aa.tar.gz
|
https://github.com/mujx/mtxclient/archive/49a3ffddc13482902b73312a4a6d2e62dddaef64.tar.gz
|
||||||
)
|
)
|
||||||
set(MTXCLIENT_HASH
|
set(MTXCLIENT_HASH
|
||||||
10a60158669001c6367a163b21a251a4e098ee9e3d12c5190104e9940639dc9f)
|
181b5d99cdf4639336f54af369f3a2feef089608716adccff9412440eac1e8b1)
|
||||||
|
|
||||||
set(
|
set(
|
||||||
TWEENY_URL
|
TWEENY_URL
|
||||||
|
|
|
@ -211,9 +211,51 @@ Cache::isRoomEncrypted(const std::string &room_id)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
mtx::crypto::ExportedSessionKeys
|
||||||
// Device Management
|
Cache::exportSessionKeys()
|
||||||
//
|
{
|
||||||
|
using namespace mtx::crypto;
|
||||||
|
|
||||||
|
ExportedSessionKeys keys;
|
||||||
|
|
||||||
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||||
|
auto cursor = lmdb::cursor::open(txn, inboundMegolmSessionDb_);
|
||||||
|
|
||||||
|
std::string key, value;
|
||||||
|
while (cursor.get(key, value, MDB_NEXT)) {
|
||||||
|
ExportedSession exported;
|
||||||
|
|
||||||
|
auto saved_session = unpickle<InboundSessionObject>(value, SECRET);
|
||||||
|
auto index = nlohmann::json::parse(key).get<MegolmSessionIndex>();
|
||||||
|
|
||||||
|
exported.room_id = index.room_id;
|
||||||
|
exported.sender_key = index.sender_key;
|
||||||
|
exported.session_id = index.session_id;
|
||||||
|
exported.session_key = export_session(saved_session.get());
|
||||||
|
|
||||||
|
keys.sessions.push_back(exported);
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.close();
|
||||||
|
txn.commit();
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Cache::importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys)
|
||||||
|
{
|
||||||
|
for (const auto &s : keys.sessions) {
|
||||||
|
MegolmSessionIndex index;
|
||||||
|
index.room_id = s.room_id;
|
||||||
|
index.session_id = s.session_id;
|
||||||
|
index.sender_key = s.sender_key;
|
||||||
|
|
||||||
|
auto exported_session = mtx::crypto::import_session(s.session_key);
|
||||||
|
|
||||||
|
saveInboundMegolmSession(index, std::move(exported_session));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Session Management
|
// Session Management
|
||||||
|
@ -224,7 +266,7 @@ Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||||
mtx::crypto::InboundGroupSessionPtr session)
|
mtx::crypto::InboundGroupSessionPtr session)
|
||||||
{
|
{
|
||||||
using namespace mtx::crypto;
|
using namespace mtx::crypto;
|
||||||
const auto key = index.to_hash();
|
const auto key = json(index).dump();
|
||||||
const auto pickled = pickle<InboundSessionObject>(session.get(), SECRET);
|
const auto pickled = pickle<InboundSessionObject>(session.get(), SECRET);
|
||||||
|
|
||||||
auto txn = lmdb::txn::begin(env_);
|
auto txn = lmdb::txn::begin(env_);
|
||||||
|
@ -241,14 +283,14 @@ OlmInboundGroupSession *
|
||||||
Cache::getInboundMegolmSession(const MegolmSessionIndex &index)
|
Cache::getInboundMegolmSession(const MegolmSessionIndex &index)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
|
std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
|
||||||
return session_storage.group_inbound_sessions[index.to_hash()].get();
|
return session_storage.group_inbound_sessions[json(index).dump()].get();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept
|
Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
|
std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx);
|
||||||
return session_storage.group_inbound_sessions.find(index.to_hash()) !=
|
return session_storage.group_inbound_sessions.find(json(index).dump()) !=
|
||||||
session_storage.group_inbound_sessions.end();
|
session_storage.group_inbound_sessions.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
24
src/Cache.h
24
src/Cache.h
|
@ -235,11 +235,24 @@ struct MegolmSessionIndex
|
||||||
std::string session_id;
|
std::string session_id;
|
||||||
//! The curve25519 public key of the sender.
|
//! The curve25519 public key of the sender.
|
||||||
std::string sender_key;
|
std::string sender_key;
|
||||||
|
|
||||||
//! Representation to be used in a hash map.
|
|
||||||
std::string to_hash() const { return room_id + session_id + sender_key; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline void
|
||||||
|
to_json(nlohmann::json &obj, const MegolmSessionIndex &msg)
|
||||||
|
{
|
||||||
|
obj["room_id"] = msg.room_id;
|
||||||
|
obj["session_id"] = msg.session_id;
|
||||||
|
obj["sender_key"] = msg.sender_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
from_json(const nlohmann::json &obj, MegolmSessionIndex &msg)
|
||||||
|
{
|
||||||
|
msg.room_id = obj.at("room_id");
|
||||||
|
msg.session_id = obj.at("session_id");
|
||||||
|
msg.sender_key = obj.at("sender_key");
|
||||||
|
}
|
||||||
|
|
||||||
struct OlmSessionStorage
|
struct OlmSessionStorage
|
||||||
{
|
{
|
||||||
// Megolm sessions
|
// Megolm sessions
|
||||||
|
@ -425,13 +438,16 @@ public:
|
||||||
bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
|
bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
|
||||||
void updateOutboundMegolmSession(const std::string &room_id, int message_index);
|
void updateOutboundMegolmSession(const std::string &room_id, int message_index);
|
||||||
|
|
||||||
|
void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
|
||||||
|
mtx::crypto::ExportedSessionKeys exportSessionKeys();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Inbound Megolm Sessions
|
// Inbound Megolm Sessions
|
||||||
//
|
//
|
||||||
void saveInboundMegolmSession(const MegolmSessionIndex &index,
|
void saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||||
mtx::crypto::InboundGroupSessionPtr session);
|
mtx::crypto::InboundGroupSessionPtr session);
|
||||||
OlmInboundGroupSession *getInboundMegolmSession(const MegolmSessionIndex &index);
|
OlmInboundGroupSession *getInboundMegolmSession(const MegolmSessionIndex &index);
|
||||||
bool inboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept;
|
bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Olm Sessions
|
// Olm Sessions
|
||||||
|
|
|
@ -18,7 +18,11 @@
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QInputDialog>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
@ -233,43 +237,60 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
||||||
|
|
||||||
auto encryptionLayout_ = new QVBoxLayout;
|
auto encryptionLayout_ = new QVBoxLayout;
|
||||||
encryptionLayout_->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
encryptionLayout_->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
||||||
|
encryptionLayout_->setAlignment(Qt::AlignVCenter | Qt::AlignBottom);
|
||||||
|
|
||||||
QFont monospaceFont = QFont(font);
|
QFont monospaceFont = QFont(font);
|
||||||
monospaceFont.setFamily("Courier New");
|
monospaceFont.setFamily("Courier New");
|
||||||
monospaceFont.setStyleHint(QFont::Courier);
|
monospaceFont.setStyleHint(QFont::Courier);
|
||||||
monospaceFont.setPointSizeF(monospaceFont.pointSizeF() * 0.9);
|
monospaceFont.setPointSizeF(monospaceFont.pointSizeF() * 0.9);
|
||||||
|
|
||||||
auto deviceIdWidget = new QHBoxLayout;
|
auto deviceIdLayout = new QHBoxLayout;
|
||||||
deviceIdWidget->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
deviceIdLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
||||||
|
|
||||||
auto deviceIdLabel = new QLabel(tr("Device ID"), this);
|
auto deviceIdLabel = new QLabel(tr("Device ID"), this);
|
||||||
deviceIdLabel->setFont(font);
|
deviceIdLabel->setFont(font);
|
||||||
deviceIdValue_ = new QLabel();
|
deviceIdValue_ = new QLabel{this};
|
||||||
deviceIdValue_->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
deviceIdValue_->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
deviceIdValue_->setFont(monospaceFont);
|
deviceIdValue_->setFont(monospaceFont);
|
||||||
deviceIdWidget->addWidget(deviceIdLabel, 1);
|
deviceIdLayout->addWidget(deviceIdLabel, 1);
|
||||||
deviceIdWidget->addWidget(deviceIdValue_);
|
deviceIdLayout->addWidget(deviceIdValue_);
|
||||||
|
|
||||||
auto deviceFingerprintWidget = new QHBoxLayout;
|
auto deviceFingerprintLayout = new QHBoxLayout;
|
||||||
deviceFingerprintWidget->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
deviceFingerprintLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
||||||
|
|
||||||
auto deviceFingerprintLabel = new QLabel(tr("Device Fingerprint"), this);
|
auto deviceFingerprintLabel = new QLabel(tr("Device Fingerprint"), this);
|
||||||
deviceFingerprintLabel->setFont(font);
|
deviceFingerprintLabel->setFont(font);
|
||||||
deviceFingerprintValue_ = new QLabel();
|
deviceFingerprintValue_ = new QLabel{this};
|
||||||
deviceFingerprintValue_->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
deviceFingerprintValue_->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
deviceFingerprintValue_->setFont(monospaceFont);
|
deviceFingerprintValue_->setFont(monospaceFont);
|
||||||
deviceFingerprintWidget->addWidget(deviceFingerprintLabel, 1);
|
deviceFingerprintLayout->addWidget(deviceFingerprintLabel, 1);
|
||||||
deviceFingerprintWidget->addWidget(deviceFingerprintValue_);
|
deviceFingerprintLayout->addWidget(deviceFingerprintValue_);
|
||||||
|
|
||||||
encryptionLayout_->addLayout(deviceIdWidget);
|
auto sessionKeysLayout = new QHBoxLayout;
|
||||||
encryptionLayout_->addLayout(deviceFingerprintWidget);
|
sessionKeysLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
||||||
|
auto sessionKeysLabel = new QLabel(tr("Session Keys"), this);
|
||||||
|
sessionKeysLabel->setFont(font);
|
||||||
|
sessionKeysLayout->addWidget(sessionKeysLabel, 1);
|
||||||
|
|
||||||
|
auto sessionKeysImportBtn = new FlatButton(tr("IMPORT"), this);
|
||||||
|
connect(
|
||||||
|
sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys);
|
||||||
|
auto sessionKeysExportBtn = new FlatButton(tr("EXPORT"), this);
|
||||||
|
connect(
|
||||||
|
sessionKeysExportBtn, &QPushButton::clicked, this, &UserSettingsPage::exportSessionKeys);
|
||||||
|
sessionKeysLayout->addWidget(sessionKeysExportBtn);
|
||||||
|
sessionKeysLayout->addWidget(sessionKeysImportBtn);
|
||||||
|
|
||||||
|
encryptionLayout_->addLayout(deviceIdLayout);
|
||||||
|
encryptionLayout_->addLayout(deviceFingerprintLayout);
|
||||||
|
encryptionLayout_->addWidget(new HorizontalLine(this));
|
||||||
|
encryptionLayout_->addLayout(sessionKeysLayout);
|
||||||
|
|
||||||
font.setWeight(65);
|
font.setWeight(65);
|
||||||
|
|
||||||
auto encryptionLabel_ = new QLabel(tr("ENCRYPTION"), this);
|
auto encryptionLabel_ = new QLabel(tr("ENCRYPTION"), this);
|
||||||
encryptionLabel_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
|
encryptionLabel_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
|
||||||
encryptionLabel_->setFont(font);
|
encryptionLabel_->setFont(font);
|
||||||
// encryptionLabel_->setContentsMargins(0, 50, 0, 0);
|
|
||||||
|
|
||||||
auto general_ = new QLabel(tr("GENERAL"), this);
|
auto general_ = new QLabel(tr("GENERAL"), this);
|
||||||
general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
|
general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
|
||||||
|
@ -310,7 +331,6 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
||||||
mainLayout_->addWidget(encryptionLabel_, 1, Qt::AlignLeft | Qt::AlignBottom);
|
mainLayout_->addWidget(encryptionLabel_, 1, Qt::AlignLeft | Qt::AlignBottom);
|
||||||
mainLayout_->addWidget(new HorizontalLine(this));
|
mainLayout_->addWidget(new HorizontalLine(this));
|
||||||
mainLayout_->addLayout(encryptionLayout_);
|
mainLayout_->addLayout(encryptionLayout_);
|
||||||
mainLayout_->addStretch(1);
|
|
||||||
|
|
||||||
auto scrollArea_ = new QScrollArea(this);
|
auto scrollArea_ = new QScrollArea(this);
|
||||||
scrollArea_->setFrameShape(QFrame::NoFrame);
|
scrollArea_->setFrameShape(QFrame::NoFrame);
|
||||||
|
@ -452,3 +472,89 @@ UserSettingsPage::restoreThemeCombo() const
|
||||||
else
|
else
|
||||||
themeCombo_->setCurrentIndex(2);
|
themeCombo_->setCurrentIndex(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UserSettingsPage::importSessionKeys()
|
||||||
|
{
|
||||||
|
auto fileName = QFileDialog::getOpenFileName(this, tr("Open Sessions File"), "", "");
|
||||||
|
|
||||||
|
QFile file(fileName);
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
QMessageBox::warning(this, tr("Error"), file.errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bin = file.peek(file.size());
|
||||||
|
auto payload = std::string(bin.data(), bin.size());
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
auto password = QInputDialog::getText(this,
|
||||||
|
tr("File Password"),
|
||||||
|
tr("Enter the passphrase to decrypt the file:"),
|
||||||
|
QLineEdit::Password,
|
||||||
|
"",
|
||||||
|
&ok);
|
||||||
|
|
||||||
|
if (!ok || password.isEmpty()) {
|
||||||
|
QMessageBox::warning(this, tr("Error"), tr("The password cannot be empty"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto sessions = mtx::crypto::decrypt_exported_sessions(
|
||||||
|
mtx::crypto::base642bin(payload), password.toStdString());
|
||||||
|
cache::client()->importSessionKeys(std::move(sessions));
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
QMessageBox::warning(this, tr("Error"), e.what());
|
||||||
|
} catch (const lmdb::error &e) {
|
||||||
|
QMessageBox::warning(this, tr("Error"), e.what());
|
||||||
|
} catch (const nlohmann::json::exception &e) {
|
||||||
|
QMessageBox::warning(this, tr("Error"), e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UserSettingsPage::exportSessionKeys()
|
||||||
|
{
|
||||||
|
qDebug() << "exporting session keys";
|
||||||
|
|
||||||
|
// Open password dialog.
|
||||||
|
bool ok;
|
||||||
|
auto password = QInputDialog::getText(this,
|
||||||
|
tr("File Password"),
|
||||||
|
tr("Enter passphrase to encrypt your session keys:"),
|
||||||
|
QLineEdit::Password,
|
||||||
|
"",
|
||||||
|
&ok);
|
||||||
|
|
||||||
|
if (!ok || password.isEmpty()) {
|
||||||
|
QMessageBox::warning(this, tr("Error"), tr("The password cannot be empty"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open file dialog to save the file.
|
||||||
|
auto fileName =
|
||||||
|
QFileDialog::getSaveFileName(this, tr("File to save the exported session keys"), "", "");
|
||||||
|
|
||||||
|
QFile file(fileName);
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
|
QMessageBox::warning(this, tr("Error"), file.errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export sessions & save to file.
|
||||||
|
try {
|
||||||
|
auto encrypted_blob = mtx::crypto::encrypt_exported_sessions(
|
||||||
|
cache::client()->exportSessionKeys(), password.toStdString());
|
||||||
|
|
||||||
|
auto b64 = mtx::crypto::bin2base64(encrypted_blob);
|
||||||
|
|
||||||
|
file.write(b64.data(), b64.size());
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
QMessageBox::warning(this, tr("Error"), e.what());
|
||||||
|
} catch (const lmdb::error &e) {
|
||||||
|
QMessageBox::warning(this, tr("Error"), e.what());
|
||||||
|
} catch (const nlohmann::json::exception &e) {
|
||||||
|
QMessageBox::warning(this, tr("Error"), e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -133,6 +133,10 @@ signals:
|
||||||
void moveBack();
|
void moveBack();
|
||||||
void trayOptionChanged(bool value);
|
void trayOptionChanged(bool value);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void importSessionKeys();
|
||||||
|
void exportSessionKeys();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void restoreThemeCombo() const;
|
void restoreThemeCombo() const;
|
||||||
void restoreScaleFactor() const;
|
void restoreScaleFactor() const;
|
||||||
|
|
Loading…
Reference in a new issue