// Copyright 2024 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/recovery_key_provider_ash.h"
#include <cstdint>
#include <optional>
#include <vector>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "base/sequence_checker.h"
#include "base/system/sys_info.h"
#include "base/task/bind_post_task.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/cryptohome/UserDataAuth.pb.h"
#include "chromeos/ash/components/dbus/cryptohome/recoverable_key_store.pb.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
#include "components/trusted_vault/proto/recovery_key_store.pb.h"
#include "components/trusted_vault/recovery_key_store_connection.h"
#include "components/trusted_vault/trusted_vault_access_token_fetcher.h"
#include "components/trusted_vault/trusted_vault_access_token_fetcher_impl.h"
#include "components/trusted_vault/trusted_vault_request.h"
#include "components/trusted_vault/trusted_vault_server_constants.h"
namespace trusted_vault {
// Currently only a single application key (for recovering the passkeys security
// domain) is supported.
constexpr char kApplicationKeyName[] =
"security_domain_member_key_encrypted_locally";
RecoveryKeyProviderAsh::RecoveryKeyProviderAsh(
scoped_refptr<base::SequencedTaskRunner> user_data_auth_client_task_runner,
AccountId account_id,
std::string device_id)
: user_data_auth_client_task_runner_(user_data_auth_client_task_runner),
account_id_(std::move(account_id)),
device_id_(std::move(device_id)) {
CHECK(user_data_auth_client_task_runner_);
// The instance is created on the main thread, but lives on the
// `StandadloneTrustedVaultBackend` utility thread.
DETACH_FROM_SEQUENCE(sequence_checker_);
}
RecoveryKeyProviderAsh::~RecoveryKeyProviderAsh() = default;
void RecoveryKeyProviderAsh::GetCurrentRecoveryKeyStoreData(
RecoveryKeyStoreDataCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto completion_callback_on_current_sequence =
base::BindPostTaskToCurrentDefault(
base::BindOnce(&RecoveryKeyProviderAsh::OnUserDataAuthClientAvailable,
weak_factory_.GetWeakPtr(), std::move(callback)));
user_data_auth_client_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ash::UserDataAuthClient::WaitForServiceToBeAvailable,
base::Unretained(ash::UserDataAuthClient::Get()),
std::move(completion_callback_on_current_sequence)));
}
void RecoveryKeyProviderAsh::OnUserDataAuthClientAvailable(
RecoveryKeyStoreDataCallback callback,
bool is_available) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!is_available) {
// We can't recover if cryptohome is unavailable since there is no recovery
// factor to upload.
std::move(callback).Run(std::nullopt);
return;
}
user_data_auth::GetRecoverableKeyStoresRequest request;
*request.mutable_account_id() =
cryptohome::CreateAccountIdentifierFromAccountId(account_id_);
auto completion_callback_on_current_sequence =
base::BindPostTaskToCurrentDefault(base::BindOnce(
&RecoveryKeyProviderAsh::OnGetRecoverableKeyStoresReply,
weak_factory_.GetWeakPtr(), std::move(callback)));
user_data_auth_client_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ash::UserDataAuthClient::GetRecoverableKeyStores,
base::Unretained(ash::UserDataAuthClient::Get()),
std::move(request),
std::move(completion_callback_on_current_sequence)));
}
void RecoveryKeyProviderAsh::OnGetRecoverableKeyStoresReply(
RecoveryKeyStoreDataCallback callback,
std::optional<user_data_auth::GetRecoverableKeyStoresReply> reply) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!reply || reply->error()) {
DVLOG(1) << "Invalid GetRecoverableKeyStoresReply "
<< (reply ? static_cast<int>(reply->error()) : -1);
std::move(callback).Run(std::nullopt);
return;
}
// We can only upload a single recovery factor. Pick the PIN if available, and
// device password otherwise.
auto chosen_key_store = base::ranges::find_if(
reply->key_stores(), [](const ::cryptohome::RecoverableKeyStore& pb) {
return pb.key_store_metadata().knowledge_factor_type() ==
cryptohome::KNOWLEDGE_FACTOR_TYPE_PIN;
});
if (chosen_key_store == reply->key_stores().end()) {
chosen_key_store = base::ranges::find_if(
reply->key_stores(), [](const ::cryptohome::RecoverableKeyStore& pb) {
return pb.key_store_metadata().knowledge_factor_type() ==
cryptohome::KNOWLEDGE_FACTOR_TYPE_PASSWORD;
});
}
if (chosen_key_store == reply->key_stores().end()) {
DVLOG(1) << "No applicable key store";
std::move(callback).Run(std::nullopt);
return;
}
if (chosen_key_store->wrapped_security_domain_key().key_name() !=
kApplicationKeyName) {
// Invalid response from cryptohome.
DVLOG(1) << "No matching application key";
std::move(callback).Run(std::nullopt);
return;
}
trusted_vault_pb::Vault vault;
vault.mutable_vault_parameters()->set_backend_public_key(
chosen_key_store->key_store_parameters().backend_public_key());
vault.mutable_vault_parameters()->set_counter_id(
chosen_key_store->key_store_parameters().counter_id());
vault.mutable_vault_parameters()->set_max_attempts(
chosen_key_store->key_store_parameters().max_attempts());
vault.mutable_vault_parameters()->set_vault_handle(
chosen_key_store->key_store_parameters().key_store_handle());
// The RecoverableKeyStoreMetadata and VaultMetadata protos are not
// binary-compatible so we need to translate field by field before we can
// serialize it into the `vault_metadata` proto string field.
trusted_vault_pb::VaultMetadata vault_metadata;
const ::cryptohome::RecoverableKeyStoreMetadata& key_store_metadata =
chosen_key_store->key_store_metadata();
switch (key_store_metadata.knowledge_factor_type()) {
case ::cryptohome::KNOWLEDGE_FACTOR_TYPE_PASSWORD:
vault_metadata.set_lskf_type(
trusted_vault_pb::VaultMetadata_LskfType_PASSWORD);
break;
case ::cryptohome::KNOWLEDGE_FACTOR_TYPE_PIN:
vault_metadata.set_lskf_type(
trusted_vault_pb::VaultMetadata_LskfType_PIN);
break;
default:
// Default is necessary because this proto compiles with sentinels such as
// `INT_MIN_SENTINEL_DO_NOT_USE_`.
DVLOG(1) << "Unknown knowledge factor type: "
<< static_cast<int>(key_store_metadata.knowledge_factor_type());
std::move(callback).Run(std::nullopt);
return;
}
switch (key_store_metadata.hash_type()) {
case ::cryptohome::HASH_TYPE_PBKDF2_AES256_1234:
vault_metadata.set_hash_type(
trusted_vault_pb::VaultMetadata_HashType_PBKDF2_AES256_1234);
break;
case ::cryptohome::HASH_TYPE_SHA256_TOP_HALF:
vault_metadata.set_hash_type(
trusted_vault_pb::VaultMetadata_HashType_SHA256_TOP_HALF);
break;
default:
// Default is necessary because this proto compiles with sentinels such as
// `INT_MIN_SENTINEL_DO_NOT_USE_`.
DVLOG(1) << "Unknown hash type: "
<< static_cast<int>(key_store_metadata.hash_type());
std::move(callback).Run(std::nullopt);
return;
}
vault_metadata.set_hash_salt(key_store_metadata.hash_salt());
vault_metadata.set_cert_path(key_store_metadata.cert_path());
vault_metadata.SerializeToString(vault.mutable_vault_metadata());
vault.set_recovery_key(chosen_key_store->wrapped_recovery_key());
const cryptohome::WrappedSecurityDomainKey& wrapped_security_domain_key =
chosen_key_store->wrapped_security_domain_key();
trusted_vault_pb::ApplicationKey* application_key =
vault.add_application_keys();
application_key->set_key_name(wrapped_security_domain_key.key_name());
application_key->mutable_asymmetric_key_pair()->set_public_key(
wrapped_security_domain_key.public_key());
application_key->mutable_asymmetric_key_pair()->set_wrapped_private_key(
wrapped_security_domain_key.wrapped_private_key());
application_key->mutable_asymmetric_key_pair()->set_wrapping_key(
wrapped_security_domain_key.wrapped_wrapping_key());
vault.mutable_chrome_os_metadata()->set_device_id(device_id_);
std::move(callback).Run(std::move(vault));
}
} // namespace trusted_vault