Implement import/export of megolm session keys (#358)

This commit is contained in:
Konstantinos Sideris 2018-09-15 23:52:14 +03:00
parent cf71a5858c
commit a9ddc3b3d3
5 changed files with 195 additions and 27 deletions

4
deps/CMakeLists.txt vendored
View file

@ -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

View file

@ -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();
} }

View file

@ -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

View file

@ -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());
}
}

View file

@ -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;