diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 30d96647..57d37a61 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -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 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(value, SECRET); + auto index = nlohmann::json::parse(key).get(); + + 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(session.get(), SECRET); auto txn = lmdb::txn::begin(env_); @@ -241,14 +283,14 @@ OlmInboundGroupSession * Cache::getInboundMegolmSession(const MegolmSessionIndex &index) { std::unique_lock 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 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 #include #include +#include +#include #include +#include +#include #include #include #include @@ -233,43 +237,60 @@ UserSettingsPage::UserSettingsPage(QSharedPointer 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 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;