summary refs log tree commit diff
path: root/src/Cache.cpp
diff options
context:
space:
mode:
authorDeepBlueV7.X <nicolas.werner@hotmail.de>2021-11-07 12:42:40 +0100
committerGitHub <noreply@github.com>2021-11-07 12:42:40 +0100
commit1bdf4ebd2102eafb2afcf50a0a18ec43d896056c (patch)
treee21a55f41b76b6048e0cc88dd689dd41c4de8a63 /src/Cache.cpp
parentMerge pull request #790 from LorenDB/fixRRDialog (diff)
parentMove away from using an event loop to access secrets (diff)
downloadnheko-1bdf4ebd2102eafb2afcf50a0a18ec43d896056c.tar.xz
Merge pull request #791 from Nheko-Reborn/secret-storage-fixes
Move away from using an event loop to access secrets
Diffstat (limited to 'src/Cache.cpp')
-rw-r--r--src/Cache.cpp313
1 files changed, 173 insertions, 140 deletions
diff --git a/src/Cache.cpp b/src/Cache.cpp

index b2db9edd..280e00ed 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp
@@ -199,7 +199,6 @@ Cache::Cache(const QString &userId, QObject *parent) , env_{nullptr} , localUserId_{userId} { - setup(); connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection); connect( this, @@ -212,6 +211,7 @@ Cache::Cache(const QString &userId, QObject *parent) } }, Qt::QueuedConnection); + setup(); } void @@ -308,7 +308,178 @@ Cache::setup() txn.commit(); - databaseReady_ = true; + loadSecrets({ + {mtx::secret_storage::secrets::cross_signing_master, false}, + {mtx::secret_storage::secrets::cross_signing_self_signing, false}, + {mtx::secret_storage::secrets::cross_signing_user_signing, false}, + {mtx::secret_storage::secrets::megolm_backup_v1, false}, + {"pickle_secret", true}, + }); +} + +static void +fatalSecretError() +{ + QMessageBox::critical( + ChatPage::instance(), + QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"), + QCoreApplication::translate( + "SecretStorage", + "Nheko could not connect to the secure storage to save encryption secrets to. This can " + "have multiple reasons. Check if your D-Bus service is running and you have configured a " + "service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If " + "you are having trouble, feel free to open an issue here: " + "https://github.com/Nheko-Reborn/nheko/issues")); + + QCoreApplication::exit(1); + exit(1); +} + +static QString +secretName(std::string name, bool internal) +{ + auto settings = UserSettings::instance(); + return (internal ? "nheko." : "matrix.") + + QString( + QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) + .toBase64()) + + "." + QString::fromStdString(name); +} + +void +Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad) +{ + if (toLoad.empty()) { + this->databaseReady_ = true; + emit databaseReady(); + return; + } + + auto [name_, internal] = toLoad.front(); + + auto job = new QKeychain::ReadPasswordJob(QCoreApplication::applicationName()); + job->setAutoDelete(true); + job->setInsecureFallback(true); + job->setSettings(UserSettings::instance()->qsettings()); + auto name = secretName(name_, internal); + job->setKey(name); + + connect(job, + &QKeychain::ReadPasswordJob::finished, + this, + [this, name, toLoad, job](QKeychain::Job *) mutable { + const QString secret = job->textData(); + if (job->error() && job->error() != QKeychain::Error::EntryNotFound) { + nhlog::db()->error("Restoring secret '{}' failed ({}): {}", + name.toStdString(), + job->error(), + job->errorString().toStdString()); + + fatalSecretError(); + } + if (secret.isEmpty()) { + nhlog::db()->debug("Restored empty secret '{}'.", name.toStdString()); + } else { + std::unique_lock lock(secret_storage.mtx); + secret_storage.secrets[name.toStdString()] = secret.toStdString(); + } + + // load next secret + toLoad.erase(toLoad.begin()); + + // You can't start a job from the finish signal of a job. + QTimer::singleShot(0, [this, toLoad] { loadSecrets(toLoad); }); + }); + job->start(); +} + +std::optional<std::string> +Cache::secret(const std::string name_, bool internal) +{ + auto name = secretName(name_, internal); + std::unique_lock lock(secret_storage.mtx); + if (auto secret = secret_storage.secrets.find(name.toStdString()); + secret != secret_storage.secrets.end()) + return secret->second; + else + return std::nullopt; +} + +void +Cache::storeSecret(const std::string name_, const std::string secret, bool internal) +{ + auto name = secretName(name_, internal); + { + std::unique_lock lock(secret_storage.mtx); + secret_storage.secrets[name.toStdString()] = secret; + } + + auto settings = UserSettings::instance(); + auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName()); + job->setAutoDelete(true); + job->setInsecureFallback(true); + job->setSettings(UserSettings::instance()->qsettings()); + + job->setKey(name); + + job->setTextData(QString::fromStdString(secret)); + + QObject::connect( + job, + &QKeychain::WritePasswordJob::finished, + this, + [name_, this](QKeychain::Job *job) { + if (job->error()) { + nhlog::db()->warn( + "Storing secret '{}' failed: {}", name_, job->errorString().toStdString()); + fatalSecretError(); + } else { + // if we emit the signal directly, qtkeychain breaks and won't execute new + // jobs. You can't start a job from the finish signal of a job. + QTimer::singleShot(0, [this, name_] { emit secretChanged(name_); }); + nhlog::db()->info("Storing secret '{}' successful", name_); + } + }, + Qt::ConnectionType::DirectConnection); + job->start(); +} + +void +Cache::deleteSecret(const std::string name, bool internal) +{ + auto name_ = secretName(name, internal); + { + std::unique_lock lock(secret_storage.mtx); + secret_storage.secrets.erase(name_.toStdString()); + } + + auto settings = UserSettings::instance(); + auto job = new QKeychain::DeletePasswordJob(QCoreApplication::applicationName()); + job->setAutoDelete(true); + job->setInsecureFallback(true); + job->setSettings(UserSettings::instance()->qsettings()); + + job->setKey(name_); + + job->connect( + job, &QKeychain::Job::finished, this, [this, name]() { emit secretChanged(name); }); + job->start(); +} + +std::string +Cache::pickleSecret() +{ + if (pickle_secret_.empty()) { + auto s = secret("pickle_secret", true); + if (!s) { + this->pickle_secret_ = mtx::client::utils::random_token(64, true); + storeSecret("pickle_secret", pickle_secret_, true); + } else { + this->pickle_secret_ = *s; + } + } + + return pickle_secret_; } void @@ -758,144 +929,6 @@ Cache::backupVersion() } } -static void -fatalSecretError() -{ - QMessageBox::critical( - ChatPage::instance(), - QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"), - QCoreApplication::translate( - "SecretStorage", - "Nheko could not connect to the secure storage to save encryption secrets to. This can " - "have multiple reasons. Check if your D-Bus service is running and you have configured a " - "service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If " - "you are having trouble, feel free to open an issue here: " - "https://github.com/Nheko-Reborn/nheko/issues")); - - QCoreApplication::exit(1); - exit(1); -} - -void -Cache::storeSecret(const std::string name, const std::string secret, bool internal) -{ - auto settings = UserSettings::instance(); - auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName()); - job->setAutoDelete(true); - job->setInsecureFallback(true); - job->setSettings(UserSettings::instance()->qsettings()); - - job->setKey( - (internal ? "nheko." : "matrix.") + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) - .toBase64()) + - "." + QString::fromStdString(name)); - - job->setTextData(QString::fromStdString(secret)); - - QObject::connect( - job, - &QKeychain::WritePasswordJob::finished, - this, - [name, this](QKeychain::Job *job) { - if (job->error()) { - nhlog::db()->warn( - "Storing secret '{}' failed: {}", name, job->errorString().toStdString()); - fatalSecretError(); - } else { - // if we emit the signal directly, qtkeychain breaks and won't execute new - // jobs. You can't start a job from the finish signal of a job. - QTimer::singleShot(100, [this, name] { emit secretChanged(name); }); - nhlog::db()->info("Storing secret '{}' successful", name); - } - }, - Qt::ConnectionType::DirectConnection); - job->start(); -} - -void -Cache::deleteSecret(const std::string name, bool internal) -{ - auto settings = UserSettings::instance(); - QKeychain::DeletePasswordJob job(QCoreApplication::applicationName()); - job.setAutoDelete(false); - job.setInsecureFallback(true); - job.setSettings(UserSettings::instance()->qsettings()); - - job.setKey( - (internal ? "nheko." : "matrix.") + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) - .toBase64()) + - "." + QString::fromStdString(name)); - - // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean - // time! - 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, bool internal) -{ - auto settings = UserSettings::instance(); - QKeychain::ReadPasswordJob job(QCoreApplication::applicationName()); - job.setAutoDelete(false); - job.setInsecureFallback(true); - job.setSettings(UserSettings::instance()->qsettings()); - - job.setKey( - (internal ? "nheko." : "matrix.") + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) - .toBase64()) + - "." + QString::fromStdString(name)); - - // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean - // time! - QEventLoop loop; - job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); - job.start(); - loop.exec(); - - const QString secret = job.textData(); - if (job.error()) { - if (job.error() == QKeychain::Error::EntryNotFound) - return std::nullopt; - nhlog::db()->error("Restoring secret '{}' failed ({}): {}", - name, - job.error(), - job.errorString().toStdString()); - - fatalSecretError(); - return std::nullopt; - } - if (secret.isEmpty()) { - nhlog::db()->debug("Restored empty secret '{}'.", name); - return std::nullopt; - } - - return secret.toStdString(); -} - -std::string -Cache::pickleSecret() -{ - if (pickle_secret_.empty()) { - auto s = secret("pickle_secret", true); - if (!s) { - this->pickle_secret_ = mtx::client::utils::random_token(64, true); - storeSecret("pickle_secret", pickle_secret_, true); - } else { - this->pickle_secret_ = *s; - } - } - - return pickle_secret_; -} - void Cache::removeInvite(lmdb::txn &txn, const std::string &room_id) {