mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 11:00:48 +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(
|
||||
MTXCLIENT_URL
|
||||
https://github.com/mujx/mtxclient/archive/3328fdedcb7db0a0bd2921189193504bf3b0c3aa.tar.gz
|
||||
https://github.com/mujx/mtxclient/archive/49a3ffddc13482902b73312a4a6d2e62dddaef64.tar.gz
|
||||
)
|
||||
set(MTXCLIENT_HASH
|
||||
10a60158669001c6367a163b21a251a4e098ee9e3d12c5190104e9940639dc9f)
|
||||
181b5d99cdf4639336f54af369f3a2feef089608716adccff9412440eac1e8b1)
|
||||
|
||||
set(
|
||||
TWEENY_URL
|
||||
|
|
|
@ -211,9 +211,51 @@ Cache::isRoomEncrypted(const std::string &room_id)
|
|||
return res;
|
||||
}
|
||||
|
||||
//
|
||||
// Device Management
|
||||
//
|
||||
mtx::crypto::ExportedSessionKeys
|
||||
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
|
||||
|
@ -224,7 +266,7 @@ Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
|
|||
mtx::crypto::InboundGroupSessionPtr session)
|
||||
{
|
||||
using namespace mtx::crypto;
|
||||
const auto key = index.to_hash();
|
||||
const auto key = json(index).dump();
|
||||
const auto pickled = pickle<InboundSessionObject>(session.get(), SECRET);
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
|
@ -241,14 +283,14 @@ OlmInboundGroupSession *
|
|||
Cache::getInboundMegolmSession(const MegolmSessionIndex &index)
|
||||
{
|
||||
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
|
||||
Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept
|
||||
Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
24
src/Cache.h
24
src/Cache.h
|
@ -235,11 +235,24 @@ struct MegolmSessionIndex
|
|||
std::string session_id;
|
||||
//! The curve25519 public key of the sender.
|
||||
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
|
||||
{
|
||||
// Megolm sessions
|
||||
|
@ -425,13 +438,16 @@ public:
|
|||
bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
|
||||
void updateOutboundMegolmSession(const std::string &room_id, int message_index);
|
||||
|
||||
void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
|
||||
mtx::crypto::ExportedSessionKeys exportSessionKeys();
|
||||
|
||||
//
|
||||
// Inbound Megolm Sessions
|
||||
//
|
||||
void saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||
mtx::crypto::InboundGroupSessionPtr session);
|
||||
OlmInboundGroupSession *getInboundMegolmSession(const MegolmSessionIndex &index);
|
||||
bool inboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept;
|
||||
bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
|
||||
|
||||
//
|
||||
// Olm Sessions
|
||||
|
|
|
@ -18,7 +18,11 @@
|
|||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QDebug>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSettings>
|
||||
|
@ -233,43 +237,60 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
|||
|
||||
auto encryptionLayout_ = new QVBoxLayout;
|
||||
encryptionLayout_->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
||||
encryptionLayout_->setAlignment(Qt::AlignVCenter | Qt::AlignBottom);
|
||||
|
||||
QFont monospaceFont = QFont(font);
|
||||
monospaceFont.setFamily("Courier New");
|
||||
monospaceFont.setStyleHint(QFont::Courier);
|
||||
monospaceFont.setPointSizeF(monospaceFont.pointSizeF() * 0.9);
|
||||
|
||||
auto deviceIdWidget = new QHBoxLayout;
|
||||
deviceIdWidget->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
||||
auto deviceIdLayout = new QHBoxLayout;
|
||||
deviceIdLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
||||
|
||||
auto deviceIdLabel = new QLabel(tr("Device ID"), this);
|
||||
deviceIdLabel->setFont(font);
|
||||
deviceIdValue_ = new QLabel();
|
||||
deviceIdValue_ = new QLabel{this};
|
||||
deviceIdValue_->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
deviceIdValue_->setFont(monospaceFont);
|
||||
deviceIdWidget->addWidget(deviceIdLabel, 1);
|
||||
deviceIdWidget->addWidget(deviceIdValue_);
|
||||
deviceIdLayout->addWidget(deviceIdLabel, 1);
|
||||
deviceIdLayout->addWidget(deviceIdValue_);
|
||||
|
||||
auto deviceFingerprintWidget = new QHBoxLayout;
|
||||
deviceFingerprintWidget->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
||||
auto deviceFingerprintLayout = new QHBoxLayout;
|
||||
deviceFingerprintLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
||||
|
||||
auto deviceFingerprintLabel = new QLabel(tr("Device Fingerprint"), this);
|
||||
deviceFingerprintLabel->setFont(font);
|
||||
deviceFingerprintValue_ = new QLabel();
|
||||
deviceFingerprintValue_ = new QLabel{this};
|
||||
deviceFingerprintValue_->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
deviceFingerprintValue_->setFont(monospaceFont);
|
||||
deviceFingerprintWidget->addWidget(deviceFingerprintLabel, 1);
|
||||
deviceFingerprintWidget->addWidget(deviceFingerprintValue_);
|
||||
deviceFingerprintLayout->addWidget(deviceFingerprintLabel, 1);
|
||||
deviceFingerprintLayout->addWidget(deviceFingerprintValue_);
|
||||
|
||||
encryptionLayout_->addLayout(deviceIdWidget);
|
||||
encryptionLayout_->addLayout(deviceFingerprintWidget);
|
||||
auto sessionKeysLayout = new QHBoxLayout;
|
||||
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);
|
||||
|
||||
auto encryptionLabel_ = new QLabel(tr("ENCRYPTION"), this);
|
||||
encryptionLabel_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
|
||||
encryptionLabel_->setFont(font);
|
||||
// encryptionLabel_->setContentsMargins(0, 50, 0, 0);
|
||||
|
||||
auto general_ = new QLabel(tr("GENERAL"), this);
|
||||
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(new HorizontalLine(this));
|
||||
mainLayout_->addLayout(encryptionLayout_);
|
||||
mainLayout_->addStretch(1);
|
||||
|
||||
auto scrollArea_ = new QScrollArea(this);
|
||||
scrollArea_->setFrameShape(QFrame::NoFrame);
|
||||
|
@ -452,3 +472,89 @@ UserSettingsPage::restoreThemeCombo() const
|
|||
else
|
||||
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 trayOptionChanged(bool value);
|
||||
|
||||
private slots:
|
||||
void importSessionKeys();
|
||||
void exportSessionKeys();
|
||||
|
||||
private:
|
||||
void restoreThemeCombo() const;
|
||||
void restoreScaleFactor() const;
|
||||
|
|
Loading…
Reference in a new issue