Store secrets (apart from the pickle key) in the database

pull/1224/head
Nicolas Werner 2 years ago
parent 1f77e1c810
commit 537fa437e2
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
  1. 127
      src/Cache.cpp
  2. 14
      src/Cache_p.h
  3. 3
      src/ChatPage.cpp
  4. 21
      src/encryption/Olm.cpp

@ -39,7 +39,7 @@
//! Should be changed when a breaking change occurs in the cache format. //! Should be changed when a breaking change occurs in the cache format.
//! This will reset client's data. //! This will reset client's data.
static const std::string CURRENT_CACHE_FORMAT_VERSION{"2022.04.08"}; static const std::string CURRENT_CACHE_FORMAT_VERSION{"2022.11.06"};
//! Keys used for the DB //! Keys used for the DB
static const std::string_view NEXT_BATCH_KEY("next_batch"); static const std::string_view NEXT_BATCH_KEY("next_batch");
@ -340,13 +340,13 @@ Cache::setup()
txn.commit(); txn.commit();
loadSecrets({ loadSecretsFromStore(
{mtx::secret_storage::secrets::cross_signing_master, false}, {
{mtx::secret_storage::secrets::cross_signing_self_signing, false}, {"pickle_secret", true},
{mtx::secret_storage::secrets::cross_signing_user_signing, false}, },
{mtx::secret_storage::secrets::megolm_backup_v1, false}, [this](const std::string &, bool, const std::string &value) {
{"pickle_secret", true}, this->pickle_secret_ = value;
}); });
} }
static void static void
@ -380,7 +380,9 @@ secretName(std::string name, bool internal)
} }
void void
Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad) Cache::loadSecretsFromStore(
std::vector<std::pair<std::string, bool>> toLoad,
std::function<void(const std::string &name, bool internal, const std::string &value)> callback)
{ {
auto settings = UserSettings::instance()->qsettings(); auto settings = UserSettings::instance()->qsettings();
@ -398,12 +400,11 @@ Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad)
if (value.isEmpty()) { if (value.isEmpty()) {
nhlog::db()->info("Restored empty secret '{}'.", name.toStdString()); nhlog::db()->info("Restored empty secret '{}'.", name.toStdString());
} else { } else {
std::unique_lock lock(secret_storage.mtx); callback(name_, internal, value.toStdString());
secret_storage.secrets[name.toStdString()] = value.toStdString();
} }
} }
// if we emit the databaseReady signal directly it won't be received // if we emit the databaseReady signal directly it won't be received
QTimer::singleShot(0, this, [this] { loadSecrets({}); }); QTimer::singleShot(0, this, [this, callback] { loadSecretsFromStore({}, callback); });
return; return;
} }
@ -419,7 +420,7 @@ Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad)
connect(job, connect(job,
&QKeychain::ReadPasswordJob::finished, &QKeychain::ReadPasswordJob::finished,
this, this,
[this, name, toLoad, job](QKeychain::Job *) mutable { [this, name, toLoad, job, name_, internal, callback](QKeychain::Job *) mutable {
nhlog::db()->debug("Finished reading '{}'", toLoad.begin()->first); nhlog::db()->debug("Finished reading '{}'", toLoad.begin()->first);
const QString secret = job->textData(); const QString secret = job->textData();
if (job->error() && job->error() != QKeychain::Error::EntryNotFound) { if (job->error() && job->error() != QKeychain::Error::EntryNotFound) {
@ -433,40 +434,72 @@ Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad)
if (secret.isEmpty()) { if (secret.isEmpty()) {
nhlog::db()->debug("Restored empty secret '{}'.", name.toStdString()); nhlog::db()->debug("Restored empty secret '{}'.", name.toStdString());
} else { } else {
std::unique_lock lock(secret_storage.mtx); callback(name_, internal, secret.toStdString());
secret_storage.secrets[name.toStdString()] = secret.toStdString();
} }
// load next secret // load next secret
toLoad.erase(toLoad.begin()); toLoad.erase(toLoad.begin());
// You can't start a job from the finish signal of a job. // You can't start a job from the finish signal of a job.
QTimer::singleShot(0, this, [this, toLoad] { loadSecrets(toLoad); }); QTimer::singleShot(
0, this, [this, toLoad, callback] { loadSecretsFromStore(toLoad, callback); });
}); });
nhlog::db()->debug("Reading '{}'", name_); nhlog::db()->debug("Reading '{}'", name_);
job->start(); job->start();
} }
std::optional<std::string> std::optional<std::string>
Cache::secret(const std::string name_, bool internal) Cache::secret(const std::string &name_, bool internal)
{ {
auto name = secretName(name_, internal); auto name = secretName(name_, internal);
std::unique_lock lock(secret_storage.mtx);
if (auto secret = secret_storage.secrets.find(name.toStdString()); auto txn = ro_txn(env_);
secret != secret_storage.secrets.end()) std::string_view value;
return secret->second; auto db_name = "secret." + name.toStdString();
else if (!syncStateDb_.get(txn, db_name, value))
return std::nullopt; return std::nullopt;
mtx::secret_storage::AesHmacSha2EncryptedData data = nlohmann::json::parse(value);
auto decrypted = mtx::crypto::decrypt(data, mtx::crypto::to_binary_buf(pickle_secret_), name_);
if (decrypted.empty())
return std::nullopt;
else
return decrypted;
} }
void void
Cache::storeSecret(const std::string name_, const std::string secret, bool internal) Cache::storeSecret(const std::string &name_, const std::string &secret, bool internal)
{ {
auto name = secretName(name_, internal); auto name = secretName(name_, internal);
{
std::unique_lock lock(secret_storage.mtx); auto txn = lmdb::txn::begin(env_);
secret_storage.secrets[name.toStdString()] = secret;
} auto encrypted =
mtx::crypto::encrypt(secret, mtx::crypto::to_binary_buf(pickle_secret_), name_);
auto db_name = "secret." + name.toStdString();
syncStateDb_.put(txn, db_name, nlohmann::json(encrypted).dump());
txn.commit();
emit secretChanged(name_);
}
void
Cache::deleteSecret(const std::string &name_, bool internal)
{
auto name = secretName(name_, internal);
auto txn = lmdb::txn::begin(env_);
std::string_view value;
auto db_name = "secret." + name.toStdString();
syncStateDb_.del(txn, db_name, value);
txn.commit();
}
void
Cache::storeSecretInStore(const std::string name_, const std::string secret)
{
auto name = secretName(name_, true);
auto settings = UserSettings::instance()->qsettings(); auto settings = UserSettings::instance()->qsettings();
if (settings->value(QStringLiteral("run_without_secure_secrets_service"), false).toBool()) { if (settings->value(QStringLiteral("run_without_secure_secrets_service"), false).toBool()) {
@ -507,13 +540,9 @@ Cache::storeSecret(const std::string name_, const std::string secret, bool inter
} }
void void
Cache::deleteSecret(const std::string name, bool internal) Cache::deleteSecretFromStore(const std::string name, bool internal)
{ {
auto name_ = secretName(name, internal); auto name_ = secretName(name, internal);
{
std::unique_lock lock(secret_storage.mtx);
secret_storage.secrets.erase(name_.toStdString());
}
auto settings = UserSettings::instance()->qsettings(); auto settings = UserSettings::instance()->qsettings();
if (settings->value(QStringLiteral("run_without_secure_secrets_service"), false).toBool()) { if (settings->value(QStringLiteral("run_without_secure_secrets_service"), false).toBool()) {
@ -539,13 +568,8 @@ std::string
Cache::pickleSecret() Cache::pickleSecret()
{ {
if (pickle_secret_.empty()) { if (pickle_secret_.empty()) {
auto s = secret("pickle_secret", true); this->pickle_secret_ = mtx::client::utils::random_token(64, true);
if (!s) { storeSecretInStore("pickle_secret", pickle_secret_);
this->pickle_secret_ = mtx::client::utils::random_token(64, true);
storeSecret("pickle_secret", pickle_secret_, true);
} else {
this->pickle_secret_ = *s;
}
} }
return pickle_secret_; return pickle_secret_;
@ -1179,11 +1203,7 @@ Cache::deleteData()
nhlog::db()->info("deleted cache files from disk"); nhlog::db()->info("deleted cache files from disk");
} }
deleteSecret(mtx::secret_storage::secrets::megolm_backup_v1); deleteSecretFromStore("pickle_secret", true);
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);
deleteSecret("pickle_secret", true);
} }
} }
@ -1392,7 +1412,8 @@ Cache::runMigrations()
}}, }},
{"2021.08.31", {"2021.08.31",
[this]() { [this]() {
storeSecret("pickle_secret", "secret", true); storeSecretInStore("pickle_secret", "secret");
this->pickle_secret_ = "secret";
return true; return true;
}}, }},
{"2022.04.08", {"2022.04.08",
@ -1448,6 +1469,22 @@ Cache::runMigrations()
return false; return false;
} }
}}, }},
{"2022.11.06",
[this]() {
loadSecretsFromStore(
{
{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},
},
[this](const std::string &name, bool internal, const std::string &value) {
this->storeSecret(name, value, internal);
QTimer::singleShot(
0, this, [this, name, internal] { deleteSecretFromStore(name, internal); });
});
return true;
}},
}; };
nhlog::db()->info("Running migrations, this may take a while!"); nhlog::db()->info("Running migrations, this may take a while!");

@ -291,9 +291,9 @@ public:
void deleteBackupVersion(); void deleteBackupVersion();
std::optional<OnlineBackupVersion> backupVersion(); std::optional<OnlineBackupVersion> backupVersion();
void storeSecret(const std::string name, const std::string secret, bool internal = false); void storeSecret(const std::string &name, const std::string &secret, bool internal = false);
void deleteSecret(const std::string name, bool internal = false); void deleteSecret(const std::string &name, bool internal = false);
std::optional<std::string> secret(const std::string name, bool internal = false); std::optional<std::string> secret(const std::string &name, bool internal = false);
std::string pickleSecret(); std::string pickleSecret();
@ -324,7 +324,12 @@ signals:
void databaseReady(); void databaseReady();
private: private:
void loadSecrets(std::vector<std::pair<std::string, bool>> toLoad); void loadSecretsFromStore(
std::vector<std::pair<std::string, bool>> toLoad,
std::function<void(const std::string &name, bool internal, const std::string &value)>
callback);
void storeSecretInStore(const std::string name, const std::string secret);
void deleteSecretFromStore(const std::string name, bool internal);
//! Save an invited room. //! Save an invited room.
void saveInvite(lmdb::txn &txn, void saveInvite(lmdb::txn &txn,
@ -685,7 +690,6 @@ private:
std::string pickle_secret_; std::string pickle_secret_;
VerificationStorage verification_storage; VerificationStorage verification_storage;
SecretsStorage secret_storage;
bool databaseReady_ = false; bool databaseReady_ = false;
}; };

@ -384,7 +384,8 @@ ChatPage::dropToLoginPage(const QString &msg)
tr("Because of the following reason Nheko wants to drop you to the login page:\n%1\nIf you " tr("Because of the following reason Nheko wants to drop you to the login page:\n%1\nIf you "
"think this is a mistake, you can close Nheko instead to possibly recover your encrpytion " "think this is a mistake, you can close Nheko instead to possibly recover your encrpytion "
"keys. After you have been dropped to the login page, you can sign in again using your " "keys. After you have been dropped to the login page, you can sign in again using your "
"usual methods."), "usual methods.")
.arg(msg),
QMessageBox::StandardButton::Close | QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Close | QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok); QMessageBox::StandardButton::Ok);
if (btn == QMessageBox::StandardButton::Close) { if (btn == QMessageBox::StandardButton::Close) {

@ -6,6 +6,7 @@
#include "Olm.h" #include "Olm.h"
#include <QObject> #include <QObject>
#include <QRandomGenerator>
#include <QTimer> #include <QTimer>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -98,13 +99,19 @@ handle_secret_request(const mtx::events::DeviceEvent<mtx::events::msg::SecretReq
return; return;
secretSend.content.secret = secret.value(); secretSend.content.secret = secret.value();
send_encrypted_to_device_messages( // Randomly delay reply to workaround olm session generation races
{{local_user.to_string(), {{e->content.requesting_device_id}}}}, secretSend); QTimer::singleShot(QRandomGenerator::global()->bounded(0, 3000),
ChatPage::instance(),
nhlog::net()->info("Sent secret '{}' to ({},{})", [local_user, e = *e, secretSend] {
e->content.name, send_encrypted_to_device_messages(
local_user.to_string(), {{local_user.to_string(), {{e.content.requesting_device_id}}}},
e->content.requesting_device_id); secretSend);
nhlog::net()->info("Sent secret '{}' to ({},{})",
e.content.name,
local_user.to_string(),
e.content.requesting_device_id);
});
} }
void void

Loading…
Cancel
Save