|
|
@ -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!"); |
|
|
|