blob: c6cfb06fdd200ac65462ca739c14484fd477b4ad [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/trusted_vault/standalone_trusted_vault_backend.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/sequence_checker.h"
#include "base/stl_util.h"
#include "base/task/sequenced_task_runner.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
#include "components/trusted_vault/features.h"
#include "components/trusted_vault/local_recovery_factor.h"
#include "components/trusted_vault/physical_device_recovery_factor.h"
#include "components/trusted_vault/proto/local_trusted_vault.pb.h"
#include "components/trusted_vault/proto_string_bytes_conversion.h"
#include "components/trusted_vault/proto_time_conversion.h"
#include "components/trusted_vault/securebox.h"
#include "components/trusted_vault/standalone_trusted_vault_server_constants.h"
#include "components/trusted_vault/standalone_trusted_vault_storage.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "components/trusted_vault/trusted_vault_histograms.h"
#include "components/trusted_vault/trusted_vault_server_constants.h"
#include "components/trusted_vault/trusted_vault_throttling_connection_impl.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "google_apis/gaia/gaia_id.h"
#include "google_apis/gaia/google_service_auth_error.h"
#if BUILDFLAG(IS_MAC)
#include "components/trusted_vault/icloud_keychain_recovery_factor.h"
#endif
namespace trusted_vault {
namespace {
base::flat_set<GaiaId> GetGaiaIDs(
const std::vector<gaia::ListedAccount>& listed_accounts) {
base::flat_set<GaiaId> result;
for (const gaia::ListedAccount& listed_account : listed_accounts) {
result.insert(listed_account.gaia_id);
}
return result;
}
// Note that it returns false upon transition from kUnknown to
// kNoPersistentAuthErrors.
bool PersistentAuthErrorWasResolved(
StandaloneTrustedVaultBackend::RefreshTokenErrorState
previous_refresh_token_error_state,
StandaloneTrustedVaultBackend::RefreshTokenErrorState
current_refresh_token_error_state) {
return previous_refresh_token_error_state ==
StandaloneTrustedVaultBackend::RefreshTokenErrorState::
kPersistentAuthError &&
current_refresh_token_error_state ==
StandaloneTrustedVaultBackend::RefreshTokenErrorState::
kNoPersistentAuthErrors;
}
TrustedVaultRecoverKeysOutcomeForUMA
GetRecoverKeysOutcomeForUMAFromRecoveryStatus(
LocalRecoveryFactor::RecoveryStatus recovery_status) {
switch (recovery_status) {
case LocalRecoveryFactor::RecoveryStatus::kSuccess:
return TrustedVaultRecoverKeysOutcomeForUMA::kSuccess;
case LocalRecoveryFactor::RecoveryStatus::kNoNewKeys:
return TrustedVaultRecoverKeysOutcomeForUMA::kNoNewKeys;
case LocalRecoveryFactor::RecoveryStatus::kFailure:
return TrustedVaultRecoverKeysOutcomeForUMA::kFailure;
}
NOTREACHED();
}
TrustedVaultRecoveryFactorRegistrationOutcomeForUMA
GetRecoveryFactorRegistrationOutcomeForUMAFromResponse(
TrustedVaultRegistrationStatus response_status) {
switch (response_status) {
case TrustedVaultRegistrationStatus::kSuccess:
return TrustedVaultRecoveryFactorRegistrationOutcomeForUMA::kSuccess;
case TrustedVaultRegistrationStatus::kAlreadyRegistered:
return TrustedVaultRecoveryFactorRegistrationOutcomeForUMA::
kAlreadyRegistered;
case TrustedVaultRegistrationStatus::kLocalDataObsolete:
return TrustedVaultRecoveryFactorRegistrationOutcomeForUMA::
kLocalDataObsolete;
case TrustedVaultRegistrationStatus::kTransientAccessTokenFetchError:
return TrustedVaultRecoveryFactorRegistrationOutcomeForUMA::
kTransientAccessTokenFetchError;
case TrustedVaultRegistrationStatus::kPersistentAccessTokenFetchError:
return TrustedVaultRecoveryFactorRegistrationOutcomeForUMA::
kPersistentAccessTokenFetchError;
case TrustedVaultRegistrationStatus::
kPrimaryAccountChangeAccessTokenFetchError:
return TrustedVaultRecoveryFactorRegistrationOutcomeForUMA::
kPrimaryAccountChangeAccessTokenFetchError;
case TrustedVaultRegistrationStatus::kNetworkError:
return TrustedVaultRecoveryFactorRegistrationOutcomeForUMA::kNetworkError;
case TrustedVaultRegistrationStatus::kOtherError:
return TrustedVaultRecoveryFactorRegistrationOutcomeForUMA::kOtherError;
}
NOTREACHED();
}
class LocalRecoveryFactorsFactoryImpl
: public StandaloneTrustedVaultBackend::LocalRecoveryFactorsFactory {
public:
#if BUILDFLAG(IS_MAC)
explicit LocalRecoveryFactorsFactoryImpl(
const std::string& icloud_keychain_access_group_prefix)
: icloud_keychain_access_group_prefix_(
icloud_keychain_access_group_prefix) {}
#else
LocalRecoveryFactorsFactoryImpl() = default;
#endif
LocalRecoveryFactorsFactoryImpl(const LocalRecoveryFactorsFactoryImpl&) =
delete;
~LocalRecoveryFactorsFactoryImpl() override = default;
LocalRecoveryFactorsFactoryImpl& operator=(
const LocalRecoveryFactorsFactoryImpl&) = delete;
std::vector<std::unique_ptr<LocalRecoveryFactor>> CreateLocalRecoveryFactors(
SecurityDomainId security_domain_id,
StandaloneTrustedVaultStorage* storage,
TrustedVaultThrottlingConnection* connection,
const CoreAccountInfo& primary_account) override {
std::vector<std::unique_ptr<LocalRecoveryFactor>> local_recovery_factors;
local_recovery_factors.emplace_back(
std::make_unique<PhysicalDeviceRecoveryFactor>(
security_domain_id, storage, connection, primary_account));
#if BUILDFLAG(IS_MAC)
if (base::FeatureList::IsEnabled(kEnableICloudKeychainRecoveryFactor)) {
// Note: The iCloud Keychain recovery factor needs to come after the
// physical device recovery factor.
// Retrieval attempts are performed in order, and since retrieving using
// the iCloud Keychain is significantly more heavy weight than from the
// physical device recovery factor, we want to make sure that the latter
// is attempted first.
local_recovery_factors.emplace_back(
std::make_unique<ICloudKeychainRecoveryFactor>(
icloud_keychain_access_group_prefix_, security_domain_id, storage,
connection, primary_account));
}
#endif
return local_recovery_factors;
}
private:
#if BUILDFLAG(IS_MAC)
const std::string icloud_keychain_access_group_prefix_;
#endif
};
} // namespace
StandaloneTrustedVaultBackend::PendingTrustedRecoveryMethod::
PendingTrustedRecoveryMethod() = default;
StandaloneTrustedVaultBackend::PendingTrustedRecoveryMethod::
PendingTrustedRecoveryMethod(PendingTrustedRecoveryMethod&&) = default;
StandaloneTrustedVaultBackend::PendingTrustedRecoveryMethod&
StandaloneTrustedVaultBackend::PendingTrustedRecoveryMethod::operator=(
PendingTrustedRecoveryMethod&&) = default;
StandaloneTrustedVaultBackend::PendingTrustedRecoveryMethod::
~PendingTrustedRecoveryMethod() = default;
StandaloneTrustedVaultBackend::PendingGetIsRecoverabilityDegraded::
PendingGetIsRecoverabilityDegraded() = default;
StandaloneTrustedVaultBackend::PendingGetIsRecoverabilityDegraded::
PendingGetIsRecoverabilityDegraded(PendingGetIsRecoverabilityDegraded&&) =
default;
StandaloneTrustedVaultBackend::PendingGetIsRecoverabilityDegraded&
StandaloneTrustedVaultBackend::PendingGetIsRecoverabilityDegraded::operator=(
PendingGetIsRecoverabilityDegraded&&) = default;
StandaloneTrustedVaultBackend::PendingGetIsRecoverabilityDegraded::
~PendingGetIsRecoverabilityDegraded() = default;
StandaloneTrustedVaultBackend::OngoingFetchKeys::OngoingFetchKeys() = default;
StandaloneTrustedVaultBackend::OngoingFetchKeys::OngoingFetchKeys(
OngoingFetchKeys&&) = default;
StandaloneTrustedVaultBackend::OngoingFetchKeys&
StandaloneTrustedVaultBackend::OngoingFetchKeys::operator=(OngoingFetchKeys&&) =
default;
StandaloneTrustedVaultBackend::OngoingFetchKeys::~OngoingFetchKeys() = default;
StandaloneTrustedVaultBackend::StandaloneTrustedVaultBackend(
#if BUILDFLAG(IS_MAC)
const std::string& icloud_keychain_access_group_prefix,
#endif
SecurityDomainId security_domain_id,
std::unique_ptr<StandaloneTrustedVaultStorage> storage,
std::unique_ptr<Delegate> delegate,
std::unique_ptr<TrustedVaultConnection> connection)
: security_domain_id_(security_domain_id),
storage_(std::move(storage)),
delegate_(std::move(delegate)),
connection_(connection
? std::make_unique<TrustedVaultThrottlingConnectionImpl>(
std::move(connection),
storage_.get())
: nullptr),
#if BUILDFLAG(IS_MAC)
local_recovery_factors_factory_(
std::make_unique<LocalRecoveryFactorsFactoryImpl>(
icloud_keychain_access_group_prefix))
#else
local_recovery_factors_factory_(
std::make_unique<LocalRecoveryFactorsFactoryImpl>())
#endif
{
}
StandaloneTrustedVaultBackend::StandaloneTrustedVaultBackend(
SecurityDomainId security_domain_id,
std::unique_ptr<StandaloneTrustedVaultStorage> storage,
std::unique_ptr<Delegate> delegate,
std::unique_ptr<TrustedVaultThrottlingConnection> connection,
std::unique_ptr<LocalRecoveryFactorsFactory> local_recovery_factors_factory)
: security_domain_id_(security_domain_id),
storage_(std::move(storage)),
delegate_(std::move(delegate)),
connection_(std::move(connection)),
local_recovery_factors_factory_(
std::move(local_recovery_factors_factory)) {}
StandaloneTrustedVaultBackend::~StandaloneTrustedVaultBackend() = default;
// static
scoped_refptr<StandaloneTrustedVaultBackend>
StandaloneTrustedVaultBackend::CreateForTesting(
SecurityDomainId security_domain_id,
std::unique_ptr<StandaloneTrustedVaultStorage> storage,
std::unique_ptr<StandaloneTrustedVaultBackend::Delegate> delegate,
std::unique_ptr<TrustedVaultThrottlingConnection> connection,
std::unique_ptr<LocalRecoveryFactorsFactory>
local_recovery_factors_factory) {
return base::WrapRefCounted(new StandaloneTrustedVaultBackend(
security_domain_id, std::move(storage), std::move(delegate),
std::move(connection), std::move(local_recovery_factors_factory)));
}
void StandaloneTrustedVaultBackend::WriteDegradedRecoverabilityState(
const trusted_vault_pb::LocalTrustedVaultDegradedRecoverabilityState&
degraded_recoverability_state) {
DCHECK(primary_account_.has_value());
trusted_vault_pb::LocalTrustedVaultPerUser* per_user_vault =
storage_->FindUserVault(primary_account_->gaia);
*per_user_vault->mutable_degraded_recoverability_state() =
degraded_recoverability_state;
storage_->WriteDataToDisk();
}
void StandaloneTrustedVaultBackend::OnDegradedRecoverabilityChanged() {
delegate_->NotifyRecoverabilityDegradedChanged();
}
void StandaloneTrustedVaultBackend::ReadDataFromDisk() {
storage_->ReadDataFromDisk();
}
void StandaloneTrustedVaultBackend::FetchKeys(
const CoreAccountInfo& account_info,
FetchKeysCallback callback) {
DCHECK(!callback.is_null());
const trusted_vault_pb::LocalTrustedVaultPerUser* per_user_vault =
storage_->FindUserVault(account_info.gaia);
if (per_user_vault &&
StandaloneTrustedVaultStorage::HasNonConstantKey(*per_user_vault) &&
!per_user_vault->keys_marked_as_stale_by_consumer()) {
// There are locally available keys, which weren't marked as stale. Keys
// download attempt is not needed.
FulfillFetchKeys(account_info.gaia, std::move(callback),
/*status_for_uma=*/std::nullopt);
return;
}
if (!connection_) {
// Keys downloading is disabled.
FulfillFetchKeys(account_info.gaia, std::move(callback),
/*status_for_uma=*/std::nullopt);
return;
}
if (!primary_account_.has_value() ||
primary_account_->gaia != account_info.gaia) {
// Keys download attempt is not possible because there is no primary
// account.
FulfillFetchKeys(account_info.gaia, std::move(callback),
TrustedVaultRecoverKeysOutcomeForUMA::kNoPrimaryAccount);
return;
}
if (ongoing_fetch_keys_.has_value()) {
// Keys downloading is only supported for primary account, thus gaia_id
// should be the same for |ongoing_fetch_keys_| and |account_info|.
CHECK_EQ(ongoing_fetch_keys_->gaia_id, primary_account_->gaia);
CHECK_EQ(ongoing_fetch_keys_->gaia_id, account_info.gaia);
// Download keys request is in progress already, |callback| will be invoked
// upon its completion.
ongoing_fetch_keys_->callbacks.emplace_back(std::move(callback));
return;
}
CHECK(per_user_vault);
ongoing_fetch_keys_ = OngoingFetchKeys();
ongoing_fetch_keys_->gaia_id = account_info.gaia;
ongoing_fetch_keys_->callbacks.emplace_back(std::move(callback));
// |connection_| and |primary_account_| are checked to be present above, so
// |local_recovery_factors| can't be empty.
CHECK(!local_recovery_factors_.empty());
AttemptRecoveryFactor(0);
}
void StandaloneTrustedVaultBackend::AttemptRecoveryFactor(
size_t local_recovery_factor) {
CHECK(local_recovery_factor >= 0 &&
local_recovery_factor < local_recovery_factors_.size());
local_recovery_factors_[local_recovery_factor]->AttemptRecovery(
// |this| outlives |local_recovery_factors_|, and destroying
// |local_recovery_factors_| guarantees cancellation of all callbacks.
base::BindOnce(&StandaloneTrustedVaultBackend::OnKeysRecovered,
base::Unretained(this), local_recovery_factor));
}
void StandaloneTrustedVaultBackend::StoreKeys(
const GaiaId& gaia_id,
const std::vector<std::vector<uint8_t>>& keys,
int last_key_version) {
// Find or create user for |gaid_id|.
trusted_vault_pb::LocalTrustedVaultPerUser* per_user_vault =
storage_->FindUserVault(gaia_id);
if (!per_user_vault) {
per_user_vault = storage_->AddUserVault(gaia_id);
}
// Having retrieved (or downloaded) new keys indicates that information about
// past registration attempts (and probably failures) may no longer be
// relevant.
per_user_vault->set_last_registration_returned_local_data_obsolete(false);
// Replace all keys.
per_user_vault->set_last_vault_key_version(last_key_version);
per_user_vault->set_keys_marked_as_stale_by_consumer(false);
per_user_vault->clear_vault_key();
for (const std::vector<uint8_t>& key : keys) {
AssignBytesToProtoString(
key, per_user_vault->add_vault_key()->mutable_key_material());
}
storage_->WriteDataToDisk();
MaybeRegisterLocalRecoveryFactors();
}
void StandaloneTrustedVaultBackend::SetPrimaryAccount(
const std::optional<CoreAccountInfo>& primary_account,
RefreshTokenErrorState refresh_token_error_state) {
const RefreshTokenErrorState previous_refresh_token_error_state =
refresh_token_error_state_;
refresh_token_error_state_ = refresh_token_error_state;
if (primary_account == primary_account_) {
// Still need to complete deferred deletion, e.g. if primary account was
// cleared before browser shutdown but not handled here.
RemoveNonPrimaryAccountKeysIfMarkedForDeletion();
// A persistent auth error could have just been resolved.
if (PersistentAuthErrorWasResolved(previous_refresh_token_error_state,
refresh_token_error_state_)) {
MaybeProcessPendingTrustedRecoveryMethod();
MaybeRegisterLocalRecoveryFactors();
CHECK(degraded_recoverability_handler_);
degraded_recoverability_handler_->HintDegradedRecoverabilityChanged(
TrustedVaultHintDegradedRecoverabilityChangedReasonForUMA::
kPersistentAuthErrorResolved);
}
return;
}
primary_account_ = primary_account;
degraded_recoverability_handler_ = nullptr;
ongoing_add_recovery_method_request_.reset();
// This aborts all ongoing recoveries / registrations.
local_recovery_factors_.clear();
RemoveNonPrimaryAccountKeysIfMarkedForDeletion();
// Make sure to call pending callbacks, now that ongoing recoveries were
// aborted.
FulfillOngoingFetchKeys(TrustedVaultRecoverKeysOutcomeForUMA::kAborted);
if (!primary_account_.has_value()) {
return;
}
trusted_vault_pb::LocalTrustedVaultPerUser* per_user_vault =
storage_->FindUserVault(primary_account->gaia);
if (!per_user_vault) {
per_user_vault = storage_->AddUserVault(primary_account->gaia);
}
if (connection_) {
// |storage_| and |connection_| outlive |local_recovery_factors_|, so
// passing raw pointers is ok.
local_recovery_factors_ =
local_recovery_factors_factory_->CreateLocalRecoveryFactors(
security_domain_id_, storage_.get(), connection_.get(),
*primary_account_);
}
degraded_recoverability_handler_ =
std::make_unique<TrustedVaultDegradedRecoverabilityHandler>(
connection_.get(), /*delegate=*/this, primary_account_.value(),
per_user_vault->degraded_recoverability_state());
// Should process `pending_get_is_recoverability_degraded_` if it belongs to
// the current primary account.
// TODO(crbug.com/40255601): |pending_get_is_recoverability_degraded_| should
// be redundant now. GetRecoverabilityIsDegraded() should be called after
// SetPrimaryAccount(). This logic is similar to FetchKeys() reporting
// kNoPrimaryAccount, once there is data confirming that this bucked is not
// recorded, it should be safe to remove.
if (pending_get_is_recoverability_degraded_.has_value() &&
pending_get_is_recoverability_degraded_->account_info ==
primary_account_) {
degraded_recoverability_handler_->GetIsRecoverabilityDegraded(std::move(
pending_get_is_recoverability_degraded_->completion_callback));
}
pending_get_is_recoverability_degraded_.reset();
MaybeRegisterLocalRecoveryFactors();
MaybeProcessPendingTrustedRecoveryMethod();
}
void StandaloneTrustedVaultBackend::UpdateAccountsInCookieJarInfo(
const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info) {
const base::flat_set<GaiaId> gaia_ids_in_cookie_jar =
base::STLSetUnion<base::flat_set<GaiaId>>(
GetGaiaIDs(accounts_in_cookie_jar_info
.GetPotentiallyInvalidSignedInAccounts()),
GetGaiaIDs(accounts_in_cookie_jar_info.GetSignedOutAccounts()));
// Primary account data shouldn't be removed immediately, but it needs to be
// removed once account become non-primary if it was ever removed from cookie
// jar.
if (primary_account_.has_value() &&
!gaia_ids_in_cookie_jar.contains(primary_account_->gaia)) {
trusted_vault_pb::LocalTrustedVaultPerUser* primary_account_data_ =
storage_->FindUserVault(primary_account_->gaia);
primary_account_data_->set_should_delete_keys_when_non_primary(true);
}
auto should_remove_user_data =
[&gaia_ids_in_cookie_jar, &primary_account = primary_account_](
const trusted_vault_pb::LocalTrustedVaultPerUser& per_user_data) {
const GaiaId gaia_id(per_user_data.gaia_id());
if (primary_account.has_value() && gaia_id == primary_account->gaia) {
// Don't delete primary account data.
return false;
}
// Delete data if account isn't in cookie jar.
return !gaia_ids_in_cookie_jar.contains(gaia_id);
};
storage_->RemoveUserVaults(should_remove_user_data);
storage_->WriteDataToDisk();
}
bool StandaloneTrustedVaultBackend::MarkLocalKeysAsStale(
const CoreAccountInfo& account_info) {
trusted_vault_pb::LocalTrustedVaultPerUser* per_user_vault =
storage_->FindUserVault(account_info.gaia);
if (!per_user_vault || per_user_vault->keys_marked_as_stale_by_consumer()) {
// No keys available for |account_info| or they are already marked as stale.
return false;
}
per_user_vault->set_keys_marked_as_stale_by_consumer(true);
storage_->WriteDataToDisk();
return true;
}
void StandaloneTrustedVaultBackend::GetIsRecoverabilityDegraded(
const CoreAccountInfo& account_info,
base::OnceCallback<void(bool)> cb) {
if (account_info == primary_account_) {
degraded_recoverability_handler_->GetIsRecoverabilityDegraded(
std::move(cb));
return;
}
pending_get_is_recoverability_degraded_ =
PendingGetIsRecoverabilityDegraded();
pending_get_is_recoverability_degraded_->account_info = account_info;
pending_get_is_recoverability_degraded_->completion_callback = std::move(cb);
}
void StandaloneTrustedVaultBackend::AddTrustedRecoveryMethod(
const GaiaId& gaia_id,
const std::vector<uint8_t>& public_key,
int method_type_hint,
base::OnceClosure cb) {
if (public_key.empty()) {
std::move(cb).Run();
return;
}
if (!primary_account_.has_value() ||
refresh_token_error_state_ ==
RefreshTokenErrorState::kPersistentAuthError) {
// Defer until SetPrimaryAccount() gets called and there are no persistent
// auth errors. Note that the latter is important, because this method can
// be called while the auth error is being resolved and there is no order
// guarantee.
pending_trusted_recovery_method_ = PendingTrustedRecoveryMethod();
pending_trusted_recovery_method_->gaia_id = gaia_id;
pending_trusted_recovery_method_->public_key = public_key;
pending_trusted_recovery_method_->method_type_hint = method_type_hint;
pending_trusted_recovery_method_->completion_callback = std::move(cb);
return;
}
DCHECK(!pending_trusted_recovery_method_.has_value());
if (primary_account_->gaia != gaia_id) {
std::move(cb).Run();
return;
}
trusted_vault_pb::LocalTrustedVaultPerUser* per_user_vault =
storage_->FindUserVault(gaia_id);
DCHECK(per_user_vault);
if (per_user_vault->vault_key().empty()) {
// Can't add recovery method while there are no local keys.
std::move(cb).Run();
return;
}
std::unique_ptr<SecureBoxPublicKey> imported_public_key =
SecureBoxPublicKey::CreateByImport(public_key);
if (!imported_public_key) {
// Invalid public key.
std::move(cb).Run();
return;
}
last_added_recovery_method_public_key_for_testing_ = public_key;
if (!connection_) {
// Feature disabled.
std::move(cb).Run();
return;
}
// |this| outlives |connection_| and
// |ongoing_add_recovery_method_request_|, so it's safe to use
// base::Unretained() here.
ongoing_add_recovery_method_request_ =
connection_->RegisterAuthenticationFactor(
*primary_account_,
GetTrustedVaultKeysWithVersions(
StandaloneTrustedVaultStorage::GetAllVaultKeys(*per_user_vault),
per_user_vault->last_vault_key_version()),
*imported_public_key,
UnspecifiedAuthenticationFactorType(method_type_hint),
base::IgnoreArgs<TrustedVaultRegistrationStatus, int>(base::BindOnce(
&StandaloneTrustedVaultBackend::OnTrustedRecoveryMethodAdded,
base::Unretained(this), std::move(cb))));
}
void StandaloneTrustedVaultBackend::ClearLocalDataForAccount(
const CoreAccountInfo& account_info) {
trusted_vault_pb::LocalTrustedVaultPerUser* per_user_vault =
storage_->FindUserVault(account_info.gaia);
if (!per_user_vault) {
return;
}
*per_user_vault = trusted_vault_pb::LocalTrustedVaultPerUser();
per_user_vault->set_gaia_id(account_info.gaia.ToString());
storage_->WriteDataToDisk();
// This codepath invoked as part of sync reset. While sync reset can cause
// resetting primary account, this is not the case for Chrome OS and Butter
// mode. Trigger recovery factor registration attempt immediately as it can
// succeed in these cases.
MaybeRegisterLocalRecoveryFactors();
}
std::optional<CoreAccountInfo>
StandaloneTrustedVaultBackend::GetPrimaryAccountForTesting() const {
return primary_account_;
}
trusted_vault_pb::LocalDeviceRegistrationInfo
StandaloneTrustedVaultBackend::GetDeviceRegistrationInfoForTesting(
const GaiaId& gaia_id) {
trusted_vault_pb::LocalTrustedVaultPerUser* per_user_vault =
storage_->FindUserVault(gaia_id);
if (!per_user_vault) {
return trusted_vault_pb::LocalDeviceRegistrationInfo();
}
return per_user_vault->local_device_registration_info();
}
std::vector<uint8_t>
StandaloneTrustedVaultBackend::GetLastAddedRecoveryMethodPublicKeyForTesting()
const {
return last_added_recovery_method_public_key_for_testing_;
}
int StandaloneTrustedVaultBackend::GetLastKeyVersionForTesting(
const GaiaId& gaia_id) {
trusted_vault_pb::LocalTrustedVaultPerUser* per_user_vault =
storage_->FindUserVault(gaia_id);
if (!per_user_vault) {
return -1;
}
return per_user_vault->last_vault_key_version();
}
bool StandaloneTrustedVaultBackend::HasPendingTrustedRecoveryMethodForTesting()
const {
return pending_trusted_recovery_method_.has_value();
}
void StandaloneTrustedVaultBackend::MaybeRegisterLocalRecoveryFactors() {
// TODO(crbug.com/40255601): in case of transient failure this function is
// likely to be not called until the browser restart; implement retry logic.
const bool should_record_metrics =
!recovery_factor_registration_state_recorded_to_uma_;
for (auto& factor : local_recovery_factors_) {
// Unretained because |this| outlives |local_recovery_factors_| (and
// destroying |local_recovery_factors_| cancels all callbacks).
const std::optional<TrustedVaultRecoveryFactorRegistrationStateForUMA>
registration_state = factor->MaybeRegister(base::BindOnce(
&StandaloneTrustedVaultBackend::OnRecoveryFactorRegistered,
base::Unretained(this), factor->GetRecoveryFactorType()));
if (registration_state.has_value() && should_record_metrics) {
recovery_factor_registration_state_recorded_to_uma_ = true;
base::UmaHistogramBoolean(
base::StrCat({"TrustedVault.RecoveryFactorRegistered.",
GetLocalRecoveryFactorNameForUma(
factor->GetRecoveryFactorType()),
".", GetSecurityDomainNameForUma(security_domain_id_)}),
factor->IsRegistered());
RecordTrustedVaultRecoveryFactorRegistrationState(
factor->GetRecoveryFactorType(), security_domain_id_,
*registration_state);
}
}
}
void StandaloneTrustedVaultBackend::MaybeProcessPendingTrustedRecoveryMethod() {
if (!primary_account_.has_value() ||
refresh_token_error_state_ ==
RefreshTokenErrorState::kPersistentAuthError ||
!pending_trusted_recovery_method_.has_value() ||
pending_trusted_recovery_method_->gaia_id != primary_account_->gaia) {
return;
}
PendingTrustedRecoveryMethod recovery_method =
std::move(*pending_trusted_recovery_method_);
pending_trusted_recovery_method_.reset();
AddTrustedRecoveryMethod(recovery_method.gaia_id, recovery_method.public_key,
recovery_method.method_type_hint,
std::move(recovery_method.completion_callback));
DCHECK(!pending_trusted_recovery_method_.has_value());
}
void StandaloneTrustedVaultBackend::OnRecoveryFactorRegistered(
LocalRecoveryFactorType local_recovery_factor_type,
TrustedVaultRegistrationStatus status,
int key_version,
bool had_local_keys) {
// If |primary_account_| was changed meanwhile, this callback must be
// cancelled.
DCHECK(primary_account_.has_value());
trusted_vault_pb::LocalTrustedVaultPerUser* per_user_vault =
storage_->FindUserVault(primary_account_->gaia);
DCHECK(per_user_vault);
RecordTrustedVaultRecoveryFactorRegistrationOutcome(
local_recovery_factor_type, security_domain_id_,
GetRecoveryFactorRegistrationOutcomeForUMAFromResponse(status));
switch (status) {
case TrustedVaultRegistrationStatus::kSuccess:
case TrustedVaultRegistrationStatus::kAlreadyRegistered:
if (!had_local_keys) {
// Recover factor registration was triggered while no local non-constant
// keys were available. Detected server-side key should be stored upon
// successful completion (or if recovery factor was already registered,
// e.g. previous response wasn't handled properly), but absence of
// keys (non-constant or constant) still needs to be checked before that
// - there might be StoreKeys() call during handling the request.
if (per_user_vault->vault_key_size() == 0) {
AssignBytesToProtoString(
GetConstantTrustedVaultKey(),
per_user_vault->add_vault_key()->mutable_key_material());
per_user_vault->set_last_vault_key_version(key_version);
storage_->WriteDataToDisk();
}
}
break;
case TrustedVaultRegistrationStatus::kLocalDataObsolete:
case TrustedVaultRegistrationStatus::kTransientAccessTokenFetchError:
case TrustedVaultRegistrationStatus::kPersistentAccessTokenFetchError:
case TrustedVaultRegistrationStatus::
kPrimaryAccountChangeAccessTokenFetchError:
case TrustedVaultRegistrationStatus::kNetworkError:
// Request wasn't sent to the server, so there is no need for throttling.
return;
case TrustedVaultRegistrationStatus::kOtherError:
connection_->RecordFailedRequestForThrottling(*primary_account_);
return;
}
}
void StandaloneTrustedVaultBackend::OnKeysRecovered(
size_t current_local_recovery_factor,
LocalRecoveryFactor::RecoveryStatus recovery_status,
const std::vector<std::vector<uint8_t>>& downloaded_vault_keys,
int last_vault_key_version) {
CHECK(primary_account_.has_value());
// This method should be called only as a result of fetching keys attributed
// to current |ongoing_fetch_keys_|.
CHECK(ongoing_fetch_keys_);
CHECK_EQ(ongoing_fetch_keys_->gaia_id, primary_account_->gaia);
bool should_attempt_next_recovery_factor = true;
switch (recovery_status) {
case LocalRecoveryFactor::RecoveryStatus::kSuccess: {
// |downloaded_vault_keys| doesn't necessary have all keys known to the
// backend, because some old keys may have been deleted from the server
// already. Not preserving old keys is acceptable and desired here, since
// the opposite can make some operations (such as registering
// authentication factors) impossible.
StoreKeys(primary_account_->gaia, downloaded_vault_keys,
last_vault_key_version);
should_attempt_next_recovery_factor = false;
break;
}
case LocalRecoveryFactor::RecoveryStatus::kNoNewKeys: {
// Persist the keys even though there are no new ones, since some old keys
// could be removed from the server.
StoreKeys(primary_account_->gaia, downloaded_vault_keys,
last_vault_key_version);
// The server state for different recovery factors is guaranteed to be the
// same (i.e. they'd return the same keys). So there's no point in trying
// other recovery factors in this case.
should_attempt_next_recovery_factor = false;
break;
}
case LocalRecoveryFactor::RecoveryStatus::kFailure:
break;
}
if (should_attempt_next_recovery_factor) {
const size_t next_local_recovery_factor = current_local_recovery_factor + 1;
if (next_local_recovery_factor < local_recovery_factors_.size()) {
AttemptRecoveryFactor(next_local_recovery_factor);
return;
}
}
// We don't want to attempt the next recovery factor, or we ran out of local
// recovery factors to try. Give up with the status from the last recovery
// factor.
FulfillOngoingFetchKeys(
GetRecoverKeysOutcomeForUMAFromRecoveryStatus(recovery_status));
}
void StandaloneTrustedVaultBackend::OnTrustedRecoveryMethodAdded(
base::OnceClosure cb) {
DCHECK(ongoing_add_recovery_method_request_);
ongoing_add_recovery_method_request_ = nullptr;
std::move(cb).Run();
degraded_recoverability_handler_->HintDegradedRecoverabilityChanged(
TrustedVaultHintDegradedRecoverabilityChangedReasonForUMA::
kRecoveryMethodAdded);
}
void StandaloneTrustedVaultBackend::FulfillOngoingFetchKeys(
std::optional<TrustedVaultRecoverKeysOutcomeForUMA> status_for_uma) {
if (!ongoing_fetch_keys_.has_value()) {
return;
}
// Invoking callbacks may in theory cause side effects (like changing
// |ongoing_fetch_keys_|), making a local copy to avoid them.
auto ongoing_fetch_keys = std::move(*ongoing_fetch_keys_);
ongoing_fetch_keys_ = std::nullopt;
for (auto& callback : ongoing_fetch_keys.callbacks) {
FulfillFetchKeys(ongoing_fetch_keys.gaia_id, std::move(callback),
status_for_uma);
}
}
void StandaloneTrustedVaultBackend::FulfillFetchKeys(
const GaiaId& gaia_id,
FetchKeysCallback callback,
std::optional<TrustedVaultRecoverKeysOutcomeForUMA> status_for_uma) {
const trusted_vault_pb::LocalTrustedVaultPerUser* per_user_vault =
storage_->FindUserVault(gaia_id);
if (status_for_uma.has_value()) {
RecordTrustedVaultRecoverKeysOutcome(security_domain_id_, *status_for_uma);
}
std::vector<std::vector<uint8_t>> vault_keys;
if (per_user_vault) {
vault_keys =
StandaloneTrustedVaultStorage::GetAllVaultKeys(*per_user_vault);
std::erase_if(vault_keys, [](const std::vector<uint8_t>& key) {
return key == GetConstantTrustedVaultKey();
});
}
std::move(callback).Run(vault_keys);
}
void StandaloneTrustedVaultBackend::
RemoveNonPrimaryAccountKeysIfMarkedForDeletion() {
auto should_remove_user_data =
[&primary_account = primary_account_](
const trusted_vault_pb::LocalTrustedVaultPerUser& per_user_data) {
return per_user_data.should_delete_keys_when_non_primary() &&
(!primary_account.has_value() ||
primary_account->gaia != GaiaId(per_user_data.gaia_id()));
};
storage_->RemoveUserVaults(should_remove_user_data);
storage_->WriteDataToDisk();
}
} // namespace trusted_vault