diff --git a/src/Cache.cpp b/src/Cache.cpp
index 05c2e486..9da0d87d 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -24,9 +24,10 @@
#include <QFile>
#include <QHash>
#include <QMap>
-#include <QSettings>
#include <QStandardPaths>
+#include <qt5keychain/keychain.h>
+
#include <mtx/responses/common.hpp>
#include "Cache.h"
@@ -569,6 +570,64 @@ Cache::restoreOlmAccount()
return std::string(pickled.data(), pickled.size());
}
+void
+Cache::storeSecret(const std::string &name, const std::string &secret)
+{
+ QKeychain::WritePasswordJob job(QCoreApplication::applicationName());
+ job.setAutoDelete(false);
+ job.setInsecureFallback(true);
+ job.setKey(QString::fromStdString(name));
+ job.setTextData(QString::fromStdString(secret));
+ QEventLoop loop;
+ job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
+ job.start();
+ loop.exec();
+
+ if (job.error()) {
+ nhlog::db()->warn(
+ "Storing secret '{}' failed: {}", name, job.errorString().toStdString());
+ } else {
+ emit secretChanged(name);
+ }
+}
+
+void
+Cache::deleteSecret(const std::string &name)
+{
+ QKeychain::DeletePasswordJob job(QCoreApplication::applicationName());
+ job.setAutoDelete(false);
+ job.setInsecureFallback(true);
+ job.setKey(QString::fromStdString(name));
+ QEventLoop loop;
+ job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
+ job.start();
+ loop.exec();
+
+ emit secretChanged(name);
+}
+
+std::optional<std::string>
+Cache::secret(const std::string &name)
+{
+ QKeychain::ReadPasswordJob job(QCoreApplication::applicationName());
+ job.setAutoDelete(false);
+ job.setInsecureFallback(true);
+ job.setKey(QString::fromStdString(name));
+ QEventLoop loop;
+ job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
+ job.start();
+ loop.exec();
+
+ const QString secret = job.textData();
+ if (job.error()) {
+ nhlog::db()->debug(
+ "Restoring secret '{}' failed: {}", name, job.errorString().toStdString());
+ return std::nullopt;
+ }
+
+ return secret.toStdString();
+}
+
//
// Media Management
//
@@ -726,10 +785,32 @@ void
Cache::deleteData()
{
// TODO: We need to remove the env_ while not accepting new requests.
+ lmdb::dbi_close(env_, syncStateDb_);
+ lmdb::dbi_close(env_, roomsDb_);
+ lmdb::dbi_close(env_, invitesDb_);
+ lmdb::dbi_close(env_, mediaDb_);
+ lmdb::dbi_close(env_, readReceiptsDb_);
+ lmdb::dbi_close(env_, notificationsDb_);
+
+ lmdb::dbi_close(env_, devicesDb_);
+ lmdb::dbi_close(env_, deviceKeysDb_);
+
+ lmdb::dbi_close(env_, inboundMegolmSessionDb_);
+ lmdb::dbi_close(env_, outboundMegolmSessionDb_);
+
+ env_.close();
+
+ verification_storage.status.clear();
+
if (!cacheDirectory_.isEmpty()) {
QDir(cacheDirectory_).removeRecursively();
nhlog::db()->info("deleted cache files from disk");
}
+
+ deleteSecret(mtx::secret_storage::secrets::megolm_backup_v1);
+ deleteSecret(mtx::secret_storage::secrets::cross_signing_master);
+ deleteSecret(mtx::secret_storage::secrets::cross_signing_user_signing);
+ deleteSecret(mtx::secret_storage::secrets::cross_signing_self_signing);
}
//! migrates db to the current format
@@ -4262,4 +4343,15 @@ restoreOlmAccount()
{
return instance_->restoreOlmAccount();
}
+
+void
+storeSecret(const std::string &name, const std::string &secret)
+{
+ instance_->storeSecret(name, secret);
+}
+std::optional<std::string>
+secret(const std::string &name)
+{
+ return instance_->secret(name);
+}
} // namespace cache
diff --git a/src/Cache.h b/src/Cache.h
index f38f1960..91956725 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -282,4 +282,9 @@ saveOlmAccount(const std::string &pickled);
std::string
restoreOlmAccount();
+
+void
+storeSecret(const std::string &name, const std::string &secret);
+std::optional<std::string>
+secret(const std::string &name);
}
diff --git a/src/Cache_p.h b/src/Cache_p.h
index fab2d964..059c1461 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -269,6 +269,10 @@ public:
void saveOlmAccount(const std::string &pickled);
std::string restoreOlmAccount();
+ void storeSecret(const std::string &name, const std::string &secret);
+ void deleteSecret(const std::string &name);
+ std::optional<std::string> secret(const std::string &name);
+
signals:
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void roomReadStatus(const std::map<QString, bool> &status);
@@ -276,6 +280,7 @@ signals:
void userKeysUpdate(const std::string &sync_token,
const mtx::responses::QueryKeys &keyQuery);
void verificationStatusChanged(const std::string &userid);
+ void secretChanged(const std::string name);
private:
//! Save an invited room.
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index dab414a9..2d223584 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -372,9 +372,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
void
ChatPage::logout()
{
- deleteConfigs();
-
resetUI();
+ deleteConfigs();
emit closing();
connectivityTimer_.stop();
@@ -385,12 +384,12 @@ ChatPage::dropToLoginPage(const QString &msg)
{
nhlog::ui()->info("dropping to the login page: {}", msg.toStdString());
- deleteConfigs();
- resetUI();
-
http::client()->shutdown();
connectivityTimer_.stop();
+ resetUI();
+ deleteConfigs();
+
emit showLoginPage(msg);
}
@@ -418,8 +417,8 @@ ChatPage::deleteConfigs()
settings.remove("");
settings.endGroup();
+ http::client()->shutdown();
cache::deleteData();
- http::client()->clear();
}
void
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 60b5168b..d056aca6 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -26,6 +26,7 @@
#include <mtx/responses/login.hpp>
#include "Cache.h"
+#include "Cache_p.h"
#include "ChatPage.h"
#include "Config.h"
#include "Logging.h"
@@ -294,6 +295,10 @@ MainWindow::showChatPage()
login_page_->reset();
chat_page_->bootstrap(userid, homeserver, token);
+ connect(cache::client(),
+ &Cache::secretChanged,
+ userSettingsPage_,
+ &UserSettingsPage::updateSecretStatus);
instance_ = this;
}
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 22df3911..9dd4705e 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -18,13 +18,13 @@
#include "UserSettingsPage.h"
#include "Utils.h"
-static const std::string STORAGE_SECRET_KEY("secret");
-constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2";
-
namespace {
auto client_ = std::make_unique<mtx::crypto::OlmClient>();
std::map<std::string, std::string> request_id_to_secret_name;
+
+const std::string STORAGE_SECRET_KEY("secret");
+constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2";
}
namespace olm {
@@ -221,6 +221,133 @@ handle_olm_message(const OlmMessage &msg)
} else if (auto roomKey = std::get_if<DeviceEvent<msg::ForwardedRoomKey>>(
&device_event)) {
import_inbound_megolm_session(*roomKey);
+ } else if (auto e =
+ std::get_if<DeviceEvent<msg::SecretSend>>(&device_event)) {
+ auto local_user = http::client()->user_id();
+
+ if (msg.sender != local_user.to_string())
+ continue;
+
+ auto secret_name =
+ request_id_to_secret_name.find(e->content.request_id);
+
+ if (secret_name != request_id_to_secret_name.end()) {
+ nhlog::crypto()->info("Received secret: {}",
+ secret_name->second);
+
+ mtx::events::msg::SecretRequest secretRequest{};
+ secretRequest.action =
+ mtx::events::msg::RequestAction::Cancellation;
+ secretRequest.requesting_device_id =
+ http::client()->device_id();
+ secretRequest.request_id = e->content.request_id;
+
+ auto verificationStatus =
+ cache::verificationStatus(local_user.to_string());
+
+ if (!verificationStatus)
+ continue;
+
+ auto deviceKeys = cache::userKeys(local_user.to_string());
+ std::string sender_device_id;
+ if (deviceKeys) {
+ for (auto &[dev, key] : deviceKeys->device_keys) {
+ if (key.keys["curve25519:" + dev] ==
+ msg.sender_key) {
+ sender_device_id = dev;
+ break;
+ }
+ }
+ }
+
+ std::map<
+ mtx::identifiers::User,
+ std::map<std::string, mtx::events::msg::SecretRequest>>
+ body;
+
+ for (const auto &dev :
+ verificationStatus->verified_devices) {
+ if (dev != secretRequest.requesting_device_id &&
+ dev != sender_device_id)
+ body[local_user][dev] = secretRequest;
+ }
+
+ http::client()
+ ->send_to_device<mtx::events::msg::SecretRequest>(
+ http::client()->generate_txn_id(),
+ body,
+ [name =
+ secret_name->second](mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->error(
+ "Failed to send request cancellation "
+ "for secrect "
+ "'{}'",
+ name);
+ return;
+ }
+ });
+
+ cache::client()->storeSecret(secret_name->second,
+ e->content.secret);
+
+ request_id_to_secret_name.erase(secret_name);
+ }
+
+ } else if (auto e =
+ std::get_if<DeviceEvent<msg::SecretRequest>>(&device_event)) {
+ if (e->content.action != mtx::events::msg::RequestAction::Request)
+ continue;
+
+ auto local_user = http::client()->user_id();
+
+ if (msg.sender != local_user.to_string())
+ continue;
+
+ auto verificationStatus =
+ cache::verificationStatus(local_user.to_string());
+
+ if (!verificationStatus)
+ continue;
+
+ auto deviceKeys = cache::userKeys(local_user.to_string());
+ if (!deviceKeys)
+ continue;
+
+ for (auto &[dev, key] : deviceKeys->device_keys) {
+ if (key.keys["curve25519:" + dev] == msg.sender_key) {
+ if (std::find(
+ verificationStatus->verified_devices.begin(),
+ verificationStatus->verified_devices.end(),
+ dev) ==
+ verificationStatus->verified_devices.end())
+ break;
+
+ // this is a verified device
+ mtx::events::DeviceEvent<
+ mtx::events::msg::SecretSend>
+ secretSend;
+ secretSend.type = EventType::SecretSend;
+ secretSend.content.request_id =
+ e->content.request_id;
+
+ auto secret =
+ cache::client()->secret(e->content.name);
+ if (!secret)
+ break;
+
+ secretSend.content.secret = secret.value();
+
+ send_encrypted_to_device_messages(
+ {{local_user.to_string(), {{dev}}}}, secretSend);
+
+ nhlog::net()->info("Sent secret to ({},{})",
+ local_user.to_string(),
+ dev);
+
+ break;
+ }
+ }
}
return;
diff --git a/src/RoomList.h b/src/RoomList.h
index d50c7de1..02aac869 100644
--- a/src/RoomList.h
+++ b/src/RoomList.h
@@ -43,7 +43,11 @@ public:
void initialize(const QMap<QString, RoomInfo> &info);
void sync(const std::map<QString, RoomInfo> &info);
- void clear() { rooms_.clear(); };
+ void clear()
+ {
+ rooms_.clear();
+ rooms_sort_cache_.clear();
+ };
void updateAvatar(const QString &room_id, const QString &url);
void addRoom(const QString &room_id, const RoomInfo &info);
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index a773af1c..fe0145fe 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -637,6 +637,15 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
deviceFingerprintValue_->setText(utils::humanReadableFingerprint(QString(44, 'X')));
+ backupSecretCached = new QLabel{this};
+ masterSecretCached = new QLabel{this};
+ selfSigningSecretCached = new QLabel{this};
+ userSigningSecretCached = new QLabel{this};
+ backupSecretCached->setFont(monospaceFont);
+ masterSecretCached->setFont(monospaceFont);
+ selfSigningSecretCached->setFont(monospaceFont);
+ userSigningSecretCached->setFont(monospaceFont);
+
auto sessionKeysLabel = new QLabel{tr("Session Keys"), this};
sessionKeysLabel->setFont(font);
sessionKeysLabel->setMargin(OptionMargin);
@@ -801,6 +810,27 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
formLayout_->addRow(sessionKeysLabel, sessionKeysLayout);
formLayout_->addRow(crossSigningKeysLabel, crossSigningKeysLayout);
+ boxWrap(tr("Master signing key"),
+ masterSecretCached,
+ tr("Your most important key. You don't need to have it cached, since not caching "
+ "it makes it less likely it can be stolen and it is only needed to rotate your "
+ "other signing keys."));
+ boxWrap(tr("User signing key"),
+ userSigningSecretCached,
+ tr("The key to verify other users. If it is cached, verifying a user will verify "
+ "all their devices."));
+ boxWrap(
+ tr("Self signing key"),
+ selfSigningSecretCached,
+ tr("The key to verify your own devices. If it is cached, verifying one of your devices "
+ "will mark it verified for all your other devices and for users, that have verified "
+ "you."));
+ boxWrap(tr("Backup key"),
+ backupSecretCached,
+ tr("The key to decrypt online key backups. If it is cached, you can enable online "
+ "key backup to store encryption keys securely encrypted on the server."));
+ updateSecretStatus();
+
auto scrollArea_ = new QScrollArea{this};
scrollArea_->setFrameShape(QFrame::NoFrame);
scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@@ -1154,3 +1184,30 @@ UserSettingsPage::exportSessionKeys()
QMessageBox::warning(this, tr("Error"), e.what());
}
}
+
+void
+UserSettingsPage::updateSecretStatus()
+{
+ QString ok = "QLabel { color : #00cc66; }";
+ QString notSoOk = "QLabel { color : #ff9933; }";
+
+ auto updateLabel = [&, this](QLabel *label, const std::string &secretName) {
+ if (cache::secret(secretName)) {
+ label->setStyleSheet(ok);
+ label->setText(tr("CACHED"));
+ } else {
+ if (secretName == mtx::secret_storage::secrets::cross_signing_master)
+ label->setStyleSheet(ok);
+ else
+ label->setStyleSheet(notSoOk);
+ label->setText(tr("NOT CACHED"));
+ }
+ };
+
+ updateLabel(masterSecretCached, mtx::secret_storage::secrets::cross_signing_master);
+ updateLabel(userSigningSecretCached,
+ mtx::secret_storage::secrets::cross_signing_user_signing);
+ updateLabel(selfSigningSecretCached,
+ mtx::secret_storage::secrets::cross_signing_self_signing);
+ updateLabel(backupSecretCached, mtx::secret_storage::secrets::megolm_backup_v1);
+}
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index d1ae93f0..c699fd59 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -253,6 +253,9 @@ signals:
void themeChanged();
void decryptSidebarChanged();
+public slots:
+ void updateSecretStatus();
+
private slots:
void importSessionKeys();
void exportSessionKeys();
@@ -285,6 +288,10 @@ private:
Toggle *mobileMode_;
QLabel *deviceFingerprintValue_;
QLabel *deviceIdValue_;
+ QLabel *backupSecretCached;
+ QLabel *masterSecretCached;
+ QLabel *selfSigningSecretCached;
+ QLabel *userSigningSecretCached;
QComboBox *themeCombo_;
QComboBox *scaleFactorCombo_;
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index b9febf75..f346acf8 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -51,7 +51,12 @@ public:
void sync(const mtx::responses::Rooms &rooms);
void addRoom(const QString &room_id);
- void clearAll() { models.clear(); }
+ void clearAll()
+ {
+ timeline_ = nullptr;
+ emit activeTimelineChanged(nullptr);
+ models.clear();
+ }
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
|