summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorKonstantinos Sideris <sideris.konstantin@gmail.com>2018-09-15 23:52:14 +0300
committerKonstantinos Sideris <sideris.konstantin@gmail.com>2018-09-15 23:52:14 +0300
commita9ddc3b3d32cd0bdce2d88648bf2bd83662c44e0 (patch)
tree6269263f2b03858f3da8ee8128777a083e49c837 /src
parentUpdate mtxclient (diff)
downloadnheko-a9ddc3b3d32cd0bdce2d88648bf2bd83662c44e0.tar.xz
Implement import/export of megolm session keys (#358)
Diffstat (limited to 'src')
-rw-r--r--src/Cache.cpp56
-rw-r--r--src/Cache.h24
-rw-r--r--src/UserSettingsPage.cpp136
-rw-r--r--src/UserSettingsPage.h4
4 files changed, 194 insertions, 26 deletions
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;