diff --git a/src/Cache.cpp b/src/Cache.cpp
index ba32cae4..cbff2ca6 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -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();
}
diff --git a/src/Cache.h b/src/Cache.h
index f9a9f9c0..5bdfb113 100644
--- a/src/Cache.h
+++ b/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
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 3b14088e..655a8509 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -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_);
-
- encryptionLayout_->addLayout(deviceIdWidget);
- encryptionLayout_->addLayout(deviceFingerprintWidget);
+ deviceFingerprintLayout->addWidget(deviceFingerprintLabel, 1);
+ deviceFingerprintLayout->addWidget(deviceFingerprintValue_);
+
+ 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());
+ }
+}
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index c6aeb300..501e76d9 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -133,6 +133,10 @@ signals:
void moveBack();
void trayOptionChanged(bool value);
+private slots:
+ void importSessionKeys();
+ void exportSessionKeys();
+
private:
void restoreThemeCombo() const;
void restoreScaleFactor() const;
|