| // Copyright 2019 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cryptohome/userdataauth.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <set> |
| #include <string> |
| #include <unordered_map> |
| #include <unordered_set> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/check.h> |
| #include <base/check_op.h> |
| #include <base/functional/bind.h> |
| #include <base/json/json_writer.h> |
| #include <base/logging.h> |
| #include <base/message_loop/message_pump_type.h> |
| #include <base/notreached.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/threading/thread_task_runner_handle.h> |
| #include <bootlockbox/boot_lockbox_client.h> |
| #include <brillo/cryptohome.h> |
| #include <chaps/isolate.h> |
| #include <chaps/token_manager_client.h> |
| #include <chromeos/constants/cryptohome.h> |
| #include <cryptohome/proto_bindings/auth_factor.pb.h> |
| #include <dbus/cryptohome/dbus-constants.h> |
| #include <featured/feature_library.h> |
| #include <libhwsec/factory/factory_impl.h> |
| #include <libhwsec/status.h> |
| #include <libhwsec-foundation/crypto/secure_blob_util.h> |
| #include <libhwsec-foundation/crypto/sha.h> |
| #include <metrics/timer.h> |
| |
| #include "cryptohome/auth_blocks/auth_block_utility_impl.h" |
| #include "cryptohome/auth_blocks/fp_service.h" |
| #include "cryptohome/auth_factor/auth_factor.h" |
| #include "cryptohome/auth_factor/auth_factor_manager.h" |
| #include "cryptohome/auth_factor/auth_factor_storage_type.h" |
| #include "cryptohome/auth_factor/auth_factor_type.h" |
| #include "cryptohome/auth_factor/auth_factor_utils.h" |
| #include "cryptohome/auth_session.h" |
| #include "cryptohome/auth_session_manager.h" |
| #include "cryptohome/auth_session_proto_utils.h" |
| #include "cryptohome/challenge_credentials/challenge_credentials_helper_impl.h" |
| #include "cryptohome/cleanup/disk_cleanup.h" |
| #include "cryptohome/cleanup/low_disk_space_handler.h" |
| #include "cryptohome/cleanup/user_oldest_activity_timestamp_manager.h" |
| #include "cryptohome/cryptohome_metrics.h" |
| #include "cryptohome/cryptorecovery/recovery_crypto_impl.h" |
| #include "cryptohome/error/converter.h" |
| #include "cryptohome/error/cryptohome_crypto_error.h" |
| #include "cryptohome/error/location_utils.h" |
| #include "cryptohome/error/locations.h" |
| #include "cryptohome/filesystem_layout.h" |
| #include "cryptohome/flatbuffer_schemas/auth_block_state.h" |
| #include "cryptohome/key_challenge_service.h" |
| #include "cryptohome/key_challenge_service_factory.h" |
| #include "cryptohome/keyset_management.h" |
| #include "cryptohome/pkcs11/real_pkcs11_token_factory.h" |
| #include "cryptohome/signature_sealing/structures_proto.h" |
| #include "cryptohome/storage/cryptohome_vault.h" |
| #include "cryptohome/storage/file_system_keyset.h" |
| #include "cryptohome/storage/mount_utils.h" |
| #include "cryptohome/user_secret_stash.h" |
| #include "cryptohome/user_secret_stash_storage.h" |
| #include "cryptohome/user_session/real_user_session_factory.h" |
| #include "cryptohome/uss_experiment_config_fetcher.h" |
| #include "cryptohome/util/proto_enum.h" |
| #include "cryptohome/vault_keyset.h" |
| |
| using base::FilePath; |
| using brillo::Blob; |
| using brillo::SecureBlob; |
| using brillo::cryptohome::home::SanitizeUserName; |
| using cryptohome::error::CryptohomeCryptoError; |
| using cryptohome::error::CryptohomeError; |
| using cryptohome::error::CryptohomeMountError; |
| using cryptohome::error::CryptohomeTPMError; |
| using cryptohome::error::ErrorAction; |
| using cryptohome::error::ErrorActionSet; |
| using hwsec::TPMErrorBase; |
| using hwsec::TPMRetryAction; |
| using hwsec_foundation::Sha1; |
| using hwsec_foundation::status::MakeStatus; |
| using hwsec_foundation::status::OkStatus; |
| using hwsec_foundation::status::StatusChain; |
| |
| namespace cryptohome { |
| |
| constexpr char kMountThreadName[] = "MountThread"; |
| constexpr char kNotFirstBootFilePath[] = "/run/cryptohome/not_first_boot"; |
| constexpr char kDeviceMapperDevicePrefix[] = "/dev/mapper/dmcrypt"; |
| |
| namespace { |
| // Some utility functions used by UserDataAuth. |
| |
| // Wrapper function for the ReplyWithError. |
| template <typename ReplyType> |
| void ReplyWithStatus(base::OnceCallback<void(const ReplyType&)> on_done, |
| CryptohomeStatus status) { |
| ReplyType reply; |
| ReplyWithError(std::move(on_done), std::move(reply), std::move(status)); |
| } |
| |
| // Get the Account ID for an AccountIdentifier proto. |
| Username GetAccountId(const AccountIdentifier& id) { |
| if (id.has_account_id()) { |
| return Username(id.account_id()); |
| } |
| return Username(id.email()); |
| } |
| |
| // Whether the key can be used for lightweight challenge-response authentication |
| // check against the given user session. |
| bool KeyMatchesForLightweightChallengeResponseCheck( |
| const KeyData& key_data, const UserSession& session) { |
| DCHECK_EQ(key_data.type(), KeyData::KEY_TYPE_CHALLENGE_RESPONSE); |
| DCHECK_EQ(key_data.challenge_response_key_size(), 1); |
| if (session.key_data().type() != KeyData::KEY_TYPE_CHALLENGE_RESPONSE || |
| session.key_data().label().empty() || |
| session.key_data().label() != key_data.label()) |
| return false; |
| if (session.key_data().challenge_response_key_size() != 1) { |
| // Using multiple challenge-response keys at once is currently unsupported. |
| return false; |
| } |
| if (session.key_data().challenge_response_key(0).public_key_spki_der() != |
| key_data.challenge_response_key(0).public_key_spki_der()) { |
| LOG(WARNING) << "Public key mismatch for lightweight challenge-response " |
| "authentication check"; |
| return false; |
| } |
| return true; |
| } |
| |
| // Returns true if any of the path in |prefixes| starts with |path| |
| // Note that this function is case insensitive |
| bool PrefixPresent(const std::vector<FilePath>& prefixes, |
| const std::string path) { |
| return std::any_of( |
| prefixes.begin(), prefixes.end(), [&path](const FilePath& prefix) { |
| return base::StartsWith(path, prefix.value(), |
| base::CompareCase::INSENSITIVE_ASCII); |
| }); |
| } |
| |
| // Groups dm-crypt mounts for each user. Mounts for a user may have a source |
| // in either dmcrypt-<>-data or dmcrypt-<>-cache. Strip the application |
| // specific suffix for the device and use <> as the group key. |
| void GroupDmcryptDeviceMounts( |
| std::multimap<const FilePath, const FilePath>* mounts, |
| std::multimap<const FilePath, const FilePath>* grouped_mounts) { |
| for (auto match = mounts->begin(); match != mounts->end(); ++match) { |
| // Group dmcrypt-<>-data and dmcrypt-<>-cache mounts. Strip out last |
| // '-' from the path. |
| size_t last_component_index = match->first.value().find_last_of("-"); |
| |
| if (last_component_index == std::string::npos) { |
| continue; |
| } |
| |
| base::FilePath device_group( |
| match->first.value().substr(0, last_component_index)); |
| if (device_group.ReferencesParent()) { |
| // This should probably never occur in practice, but seems useful from the |
| // security hygiene perspective to explicitly prevent transforming stuff |
| // like "/foo/..-" into "/foo/..". |
| LOG(WARNING) << "Skipping malformed dm-crypt mount point: " |
| << match->first; |
| continue; |
| } |
| grouped_mounts->insert({device_group, match->second}); |
| } |
| } |
| |
| void ReplyWithAuthenticationResult( |
| const AuthSession* auth_session, |
| base::OnceCallback<void(const user_data_auth::AuthenticateAuthFactorReply&)> |
| on_done, |
| CryptohomeStatus status) { |
| DCHECK(auth_session); |
| DCHECK(!on_done.is_null()); |
| user_data_auth::AuthenticateAuthFactorReply reply; |
| reply.set_authenticated(auth_session->status() == |
| AuthStatus::kAuthStatusAuthenticated); |
| for (AuthIntent auth_intent : auth_session->authorized_intents()) { |
| reply.add_authorized_for(AuthIntentToProto(auth_intent)); |
| } |
| |
| if (auth_session->status() == AuthStatus::kAuthStatusAuthenticated) { |
| reply.set_seconds_left(auth_session->GetRemainingTime().InSeconds()); |
| } |
| |
| ReplyWithError(std::move(on_done), std::move(reply), status); |
| } |
| |
| } // namespace |
| |
| UserDataAuth::UserDataAuth() |
| : origin_thread_id_(base::PlatformThread::CurrentId()), |
| mount_thread_(nullptr), |
| hwsec_factory_(nullptr), |
| hwsec_(nullptr), |
| pinweaver_(nullptr), |
| recovery_crypto_(nullptr), |
| default_cryptohome_keys_manager_(nullptr), |
| cryptohome_keys_manager_(nullptr), |
| tpm_manager_util_(nullptr), |
| default_platform_(new Platform()), |
| platform_(default_platform_.get()), |
| default_crypto_(nullptr), |
| crypto_(nullptr), |
| default_chaps_client_(new chaps::TokenManagerClient()), |
| chaps_client_(default_chaps_client_.get()), |
| default_pkcs11_init_(new Pkcs11Init()), |
| pkcs11_init_(default_pkcs11_init_.get()), |
| default_pkcs11_token_factory_(new RealPkcs11TokenFactory()), |
| pkcs11_token_factory_(default_pkcs11_token_factory_.get()), |
| firmware_management_parameters_(nullptr), |
| fingerprint_manager_(nullptr), |
| ownership_callback_has_run_(false), |
| default_install_attrs_(nullptr), |
| install_attrs_(nullptr), |
| enterprise_owned_(false), |
| default_homedirs_(nullptr), |
| homedirs_(nullptr), |
| default_keyset_management_(nullptr), |
| keyset_management_(nullptr), |
| auth_block_utility_(nullptr), |
| default_auth_session_manager_(nullptr), |
| auth_session_manager_(nullptr), |
| default_low_disk_space_handler_(nullptr), |
| low_disk_space_handler_(nullptr), |
| disk_cleanup_threshold_(kFreeSpaceThresholdToTriggerCleanup), |
| disk_cleanup_aggressive_threshold_( |
| kFreeSpaceThresholdToTriggerAggressiveCleanup), |
| disk_cleanup_critical_threshold_( |
| kFreeSpaceThresholdToTriggerCriticalCleanup), |
| disk_cleanup_target_free_space_(kTargetFreeSpaceAfterCleanup), |
| default_user_session_factory_(nullptr), |
| user_session_factory_(nullptr), |
| guest_user_(brillo::cryptohome::home::GetGuestUsername()), |
| force_ecryptfs_(true), |
| fscrypt_v2_(false), |
| legacy_mount_(true), |
| bind_mount_downloads_(true), |
| migrate_to_user_secret_stash_(false), |
| default_arc_disk_quota_(nullptr), |
| arc_disk_quota_(nullptr), |
| default_feature_lib_(nullptr), |
| feature_lib_(nullptr) {} |
| |
| UserDataAuth::~UserDataAuth() { |
| if (low_disk_space_handler_) { |
| low_disk_space_handler_->Stop(); |
| } |
| if (mount_thread_) { |
| mount_thread_->Stop(); |
| } |
| } |
| |
| bool UserDataAuth::Initialize() { |
| AssertOnOriginThread(); |
| |
| // Note that we check to see if |origin_task_runner_| and |mount_task_runner_| |
| // are available here because they may have been set to an overridden value |
| // during unit testing before Initialize() is called. |
| if (!origin_task_runner_) { |
| origin_task_runner_ = base::ThreadTaskRunnerHandle::Get(); |
| } |
| if (!mount_task_runner_) { |
| mount_thread_ = std::make_unique<MountThread>(kMountThreadName, this); |
| } |
| |
| if (!hwsec_factory_) { |
| default_hwsec_factory_ = std::make_unique<hwsec::FactoryImpl>(); |
| hwsec_factory_ = default_hwsec_factory_.get(); |
| } |
| |
| if (!hwsec_) { |
| default_hwsec_ = hwsec_factory_->GetCryptohomeFrontend(); |
| hwsec_ = default_hwsec_.get(); |
| } |
| |
| if (!pinweaver_) { |
| default_pinweaver_ = hwsec_factory_->GetPinWeaverFrontend(); |
| pinweaver_ = default_pinweaver_.get(); |
| } |
| |
| if (!recovery_crypto_) { |
| default_recovery_crypto_ = hwsec_factory_->GetRecoveryCryptoFrontend(); |
| recovery_crypto_ = default_recovery_crypto_.get(); |
| } |
| |
| // Note that we check to see if |cryptohome_keys_manager_| is available here |
| // because it may have been set to an overridden value during unit testing |
| // before Initialize() is called. |
| if (!cryptohome_keys_manager_) { |
| default_cryptohome_keys_manager_.reset( |
| new CryptohomeKeysManager(hwsec_, platform_)); |
| cryptohome_keys_manager_ = default_cryptohome_keys_manager_.get(); |
| } |
| |
| // Initialize Firmware Management Parameters |
| if (!firmware_management_parameters_) { |
| default_firmware_management_params_ = |
| FirmwareManagementParameters::CreateInstance(hwsec_); |
| firmware_management_parameters_ = default_firmware_management_params_.get(); |
| } |
| |
| if (!install_attrs_) { |
| default_install_attrs_ = |
| std::make_unique<InstallAttributes>(platform_, hwsec_); |
| install_attrs_ = default_install_attrs_.get(); |
| } |
| |
| if (!user_activity_timestamp_manager_) { |
| default_user_activity_timestamp_manager_ = |
| std::make_unique<UserOldestActivityTimestampManager>(platform_); |
| user_activity_timestamp_manager_ = |
| default_user_activity_timestamp_manager_.get(); |
| } |
| |
| if (!crypto_) { |
| default_crypto_ = std::make_unique<Crypto>( |
| hwsec_, pinweaver_, cryptohome_keys_manager_, recovery_crypto_); |
| crypto_ = default_crypto_.get(); |
| } |
| crypto_->Init(); |
| |
| if (!InitializeFilesystemLayout(platform_, &system_salt_)) { |
| LOG(ERROR) << "Failed to initialize filesystem layout."; |
| return false; |
| } |
| |
| if (!keyset_management_) { |
| default_keyset_management_ = std::make_unique<KeysetManagement>( |
| platform_, crypto_, std::make_unique<VaultKeysetFactory>()); |
| keyset_management_ = default_keyset_management_.get(); |
| } |
| |
| if (!auth_block_utility_) { |
| default_auth_block_utility_ = std::make_unique<AuthBlockUtilityImpl>( |
| keyset_management_, crypto_, platform_, |
| std::make_unique<FingerprintAuthBlockService>( |
| base::BindRepeating(&UserDataAuth::GetFingerprintManager, |
| base::Unretained(this)), |
| base::BindRepeating(&UserDataAuth::OnFingerprintScanResult, |
| base::Unretained(this)))); |
| auth_block_utility_ = default_auth_block_utility_.get(); |
| } |
| |
| if (!auth_factor_manager_) { |
| default_auth_factor_manager_ = |
| std::make_unique<AuthFactorManager>(platform_); |
| auth_factor_manager_ = default_auth_factor_manager_.get(); |
| } |
| |
| if (!user_secret_stash_storage_) { |
| default_user_secret_stash_storage_ = |
| std::make_unique<UserSecretStashStorage>(platform_); |
| user_secret_stash_storage_ = default_user_secret_stash_storage_.get(); |
| } |
| |
| if (!auth_session_manager_) { |
| default_auth_session_manager_ = std::make_unique<AuthSessionManager>( |
| crypto_, platform_, sessions_, keyset_management_, auth_block_utility_, |
| auth_factor_manager_, user_secret_stash_storage_); |
| auth_session_manager_ = default_auth_session_manager_.get(); |
| } |
| |
| if (!vault_factory_) { |
| auto container_factory = |
| std::make_unique<EncryptedContainerFactory>(platform_); |
| container_factory->set_allow_fscrypt_v2(fscrypt_v2_); |
| default_vault_factory_ = std::make_unique<CryptohomeVaultFactory>( |
| platform_, std::move(container_factory)); |
| default_vault_factory_->set_enable_application_containers( |
| enable_application_containers_); |
| vault_factory_ = default_vault_factory_.get(); |
| |
| if (platform_->IsStatefulLogicalVolumeSupported()) { |
| base::FilePath stateful_device = platform_->GetStatefulDevice(); |
| brillo::LogicalVolumeManager* lvm = platform_->GetLogicalVolumeManager(); |
| brillo::PhysicalVolume pv(stateful_device, |
| std::make_shared<brillo::LvmCommandRunner>()); |
| |
| std::optional<brillo::VolumeGroup> vg; |
| std::optional<brillo::Thinpool> thinpool; |
| |
| vg = lvm->GetVolumeGroup(pv); |
| if (vg && vg->IsValid()) { |
| thinpool = lvm->GetThinpool(*vg, "thinpool"); |
| } |
| |
| if (thinpool && vg) { |
| default_vault_factory_->CacheLogicalVolumeObjects(vg, thinpool); |
| } |
| } |
| } |
| |
| if (!homedirs_) { |
| // This callback runs in HomeDirs::Remove on |this.homedirs_|. Since |
| // |this.keyset_management_| won't be destroyed upon call of Remove(), |
| // base::Unretained(keyset_management_) will be valid when the callback |
| // runs. |
| HomeDirs::RemoveCallback remove_callback = |
| base::BindRepeating(&KeysetManagement::RemoveLECredentials, |
| base::Unretained(keyset_management_)); |
| default_homedirs_ = std::make_unique<HomeDirs>( |
| platform_, std::make_unique<policy::PolicyProvider>(), remove_callback, |
| vault_factory_); |
| homedirs_ = default_homedirs_.get(); |
| } |
| |
| auto homedirs = homedirs_->GetHomeDirs(); |
| for (const auto& dir : homedirs) { |
| // TODO(b/205759690, dlunev): can be changed after a stepping stone release |
| // to `user_activity_timestamp_manager_->LoadTimestamp(dir.obfuscated);` |
| base::Time legacy_timestamp = |
| keyset_management_->GetKeysetBoundTimestamp(dir.obfuscated); |
| user_activity_timestamp_manager_->LoadTimestampWithLegacy(dir.obfuscated, |
| legacy_timestamp); |
| keyset_management_->CleanupPerIndexTimestampFiles(dir.obfuscated); |
| } |
| |
| if (!mount_factory_) { |
| default_mount_factory_ = std::make_unique<MountFactory>(); |
| mount_factory_ = default_mount_factory_.get(); |
| } |
| |
| if (!user_session_factory_) { |
| default_user_session_factory_ = std::make_unique<RealUserSessionFactory>( |
| mount_factory_, platform_, homedirs_, keyset_management_, |
| user_activity_timestamp_manager_, pkcs11_token_factory_); |
| user_session_factory_ = default_user_session_factory_.get(); |
| } |
| |
| if (!low_disk_space_handler_) { |
| default_low_disk_space_handler_ = std::make_unique<LowDiskSpaceHandler>( |
| homedirs_, platform_, user_activity_timestamp_manager_); |
| low_disk_space_handler_ = default_low_disk_space_handler_.get(); |
| } |
| low_disk_space_handler_->disk_cleanup()->set_cleanup_threshold( |
| disk_cleanup_threshold_); |
| low_disk_space_handler_->disk_cleanup()->set_aggressive_cleanup_threshold( |
| disk_cleanup_aggressive_threshold_); |
| low_disk_space_handler_->disk_cleanup()->set_critical_cleanup_threshold( |
| disk_cleanup_critical_threshold_); |
| low_disk_space_handler_->disk_cleanup()->set_target_free_space( |
| disk_cleanup_target_free_space_); |
| |
| if (!arc_disk_quota_) { |
| default_arc_disk_quota_ = std::make_unique<ArcDiskQuota>( |
| homedirs_, platform_, base::FilePath(kArcDiskHome)); |
| arc_disk_quota_ = default_arc_disk_quota_.get(); |
| } |
| // Initialize ARC Disk Quota Service. |
| arc_disk_quota_->Initialize(); |
| |
| if (!mount_task_runner_) { |
| base::Thread::Options options; |
| options.message_pump_type = base::MessagePumpType::IO; |
| mount_thread_->StartWithOptions(std::move(options)); |
| mount_task_runner_ = mount_thread_->task_runner(); |
| } |
| |
| if (platform_->FileExists(base::FilePath(kNotFirstBootFilePath))) { |
| // Clean up any unreferenced mountpoints at startup. |
| PostTaskToMountThread(FROM_HERE, |
| base::BindOnce( |
| [](UserDataAuth* userdataauth) { |
| userdataauth->CleanUpStaleMounts(false); |
| }, |
| base::Unretained(this))); |
| } else { |
| platform_->TouchFileDurable(base::FilePath(kNotFirstBootFilePath)); |
| } |
| |
| low_disk_space_handler_->SetUpdateUserActivityTimestampCallback( |
| base::BindRepeating( |
| base::IgnoreResult(&UserDataAuth::UpdateCurrentUserActivityTimestamp), |
| base::Unretained(this), 0)); |
| |
| low_disk_space_handler_->SetLowDiskSpaceCallback( |
| base::BindRepeating([](uint64_t) {})); |
| |
| if (!low_disk_space_handler_->Init(base::BindRepeating( |
| &UserDataAuth::PostTaskToMountThread, base::Unretained(this)))) |
| return false; |
| |
| return true; |
| } |
| |
| void UserDataAuth::CreateMountThreadDBus() { |
| AssertOnMountThread(); |
| if (!mount_thread_bus_) { |
| // Setup the D-Bus. |
| dbus::Bus::Options options; |
| options.bus_type = dbus::Bus::SYSTEM; |
| mount_thread_bus_ = base::MakeRefCounted<dbus::Bus>(options); |
| CHECK(mount_thread_bus_->Connect()) |
| << "Failed to connect to system D-Bus on mount thread"; |
| } |
| } |
| |
| void UserDataAuth::ShutdownTask() { |
| default_auth_session_manager_.reset(); |
| default_uss_experiment_config_fetcher_.reset(); |
| default_fingerprint_manager_.reset(); |
| default_challenge_credentials_helper_.reset(); |
| if (mount_thread_bus_) { |
| mount_thread_bus_->ShutdownAndBlock(); |
| mount_thread_bus_.reset(); |
| } |
| } |
| |
| bool UserDataAuth::PostDBusInitialize() { |
| AssertOnOriginThread(); |
| CHECK(bus_); |
| |
| if (!tpm_manager_util_) { |
| tpm_manager_util_ = tpm_manager::TpmManagerUtility::GetSingleton(); |
| } |
| |
| if (tpm_manager_util_) { |
| tpm_manager_util_->AddOwnershipCallback(base::BindRepeating( |
| &UserDataAuth::OnOwnershipTakenSignal, base::Unretained(this))); |
| } else { |
| LOG(ERROR) << __func__ << ": Failed to get TpmManagerUtility singleton!"; |
| } |
| |
| // Create a dbus connection on mount thread. |
| PostTaskToMountThread(FROM_HERE, |
| base::BindOnce(&UserDataAuth::CreateMountThreadDBus, |
| base::Unretained(this))); |
| |
| // If the TPM is unowned or doesn't exist, it's safe for |
| // this function to be called again. However, it shouldn't |
| // be called across multiple threads in parallel. |
| |
| PostTaskToMountThread( |
| FROM_HERE, base::BindOnce(&UserDataAuth::InitializeInstallAttributes, |
| base::Unretained(this))); |
| |
| PostTaskToMountThread(FROM_HERE, |
| base::BindOnce(&UserDataAuth::CreateFingerprintManager, |
| base::Unretained(this))); |
| |
| PostTaskToMountThread( |
| FROM_HERE, |
| base::BindOnce(&UserDataAuth::InitializeChallengeCredentialsHelper, |
| base::Unretained(this))); |
| |
| PostTaskToMountThread( |
| FROM_HERE, base::BindOnce(&UserDataAuth::CreateUssExperimentConfigFetcher, |
| base::Unretained(this))); |
| |
| PostTaskToMountThread(FROM_HERE, |
| base::BindOnce(&UserDataAuth::InitializeFeatureLibrary, |
| base::Unretained(this))); |
| |
| return true; |
| } |
| |
| void UserDataAuth::InitializeFeatureLibrary() { |
| AssertOnMountThread(); |
| if (!feature_lib_) { |
| default_feature_lib_ = feature::PlatformFeatures::New(mount_thread_bus_); |
| feature_lib_ = default_feature_lib_.get(); |
| if (!feature_lib_) { |
| LOG(WARNING) << "Failed to determine USS migration experiment flag"; |
| return; |
| } |
| } |
| auth_session_manager_->set_feature_lib(feature_lib_); |
| } |
| |
| void UserDataAuth::InitializeChallengeCredentialsHelper() { |
| AssertOnMountThread(); |
| CryptohomeStatus status = InitForChallengeResponseAuth(); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to initialize challenge_credentials_helper_."; |
| } |
| } |
| |
| void UserDataAuth::CreateUssExperimentConfigFetcher() { |
| AssertOnMountThread(); |
| if (!uss_experiment_config_fetcher_) { |
| if (!default_uss_experiment_config_fetcher_) { |
| default_uss_experiment_config_fetcher_ = |
| UssExperimentConfigFetcher::Create(mount_thread_bus_); |
| } |
| uss_experiment_config_fetcher_ = |
| default_uss_experiment_config_fetcher_.get(); |
| } |
| } |
| |
| void UserDataAuth::CreateFingerprintManager() { |
| AssertOnMountThread(); |
| if (!fingerprint_manager_) { |
| if (!default_fingerprint_manager_) { |
| default_fingerprint_manager_ = FingerprintManager::Create( |
| mount_thread_bus_, |
| dbus::ObjectPath(std::string(biod::kBiodServicePath) |
| .append(kCrosFpBiometricsManagerRelativePath))); |
| } |
| fingerprint_manager_ = default_fingerprint_manager_.get(); |
| } |
| } |
| |
| FingerprintManager* UserDataAuth::GetFingerprintManager() const { |
| AssertOnMountThread(); |
| return fingerprint_manager_; |
| } |
| |
| void UserDataAuth::OnFingerprintScanResult( |
| user_data_auth::FingerprintScanResult result) { |
| AssertOnMountThread(); |
| if (fingerprint_scan_result_callback_) { |
| fingerprint_scan_result_callback_.Run(result); |
| } |
| } |
| |
| void UserDataAuth::OnOwnershipTakenSignal() { |
| PostTaskToMountThread(FROM_HERE, |
| base::BindOnce(&UserDataAuth::OwnershipCallback, |
| base::Unretained(this), true, true)); |
| } |
| |
| bool UserDataAuth::PostTaskToOriginThread(const base::Location& from_here, |
| base::OnceClosure task, |
| const base::TimeDelta& delay) { |
| if (delay.is_zero()) { |
| return origin_task_runner_->PostTask(from_here, std::move(task)); |
| } |
| return origin_task_runner_->PostDelayedTask(from_here, std::move(task), |
| delay); |
| } |
| |
| bool UserDataAuth::PostTaskToMountThread(const base::Location& from_here, |
| base::OnceClosure task, |
| const base::TimeDelta& delay) { |
| CHECK(mount_task_runner_); |
| if (delay.is_zero()) { |
| // Increase and report the parallel task count. |
| parallel_task_count_ += 1; |
| if (parallel_task_count_ > 1) { |
| ReportParallelTasks(parallel_task_count_); |
| } |
| |
| // Reduce the parallel task count after finished the task. |
| auto full_task = base::BindOnce( |
| [](base::OnceClosure task, std::atomic<int>* task_count) { |
| std::move(task).Run(); |
| *task_count -= 1; |
| }, |
| std::move(task), base::Unretained(¶llel_task_count_)); |
| |
| return mount_task_runner_->PostTask(from_here, std::move(full_task)); |
| } |
| return mount_task_runner_->PostDelayedTask(from_here, std::move(task), delay); |
| } |
| |
| bool UserDataAuth::IsMounted(const Username& username, bool* is_ephemeral_out) { |
| // Note: This can only run in mount_thread_ |
| AssertOnMountThread(); |
| |
| bool is_mounted = false; |
| bool is_ephemeral = false; |
| if (username->empty()) { |
| // No username is specified, so we consider "the cryptohome" to be mounted |
| // if any existing cryptohome is mounted. |
| for (const auto& [unused, session] : *sessions_) { |
| if (session.IsActive()) { |
| is_mounted = true; |
| is_ephemeral |= session.IsEphemeral(); |
| } |
| } |
| } else { |
| // A username is specified, check the associated mount object. |
| const UserSession* session = sessions_->Find(username); |
| |
| if (session) { |
| is_mounted = session->IsActive(); |
| is_ephemeral = is_mounted && session->IsEphemeral(); |
| } |
| } |
| |
| if (is_ephemeral_out) { |
| *is_ephemeral_out = is_ephemeral; |
| } |
| |
| return is_mounted; |
| } |
| |
| bool UserDataAuth::RemoveAllMounts() { |
| AssertOnMountThread(); |
| |
| bool success = true; |
| while (!sessions_->empty()) { |
| const auto& [username, session] = *sessions_->begin(); |
| if (session.IsActive() && !session.Unmount()) { |
| success = false; |
| } |
| if (!sessions_->Remove(username)) { |
| NOTREACHED() << "Failed to remove user session on unmount"; |
| } |
| } |
| return success; |
| } |
| |
| bool UserDataAuth::FilterActiveMounts( |
| std::multimap<const FilePath, const FilePath>* mounts, |
| std::multimap<const FilePath, const FilePath>* active_mounts, |
| bool include_busy_mount) { |
| // Note: This can only run in mount_thread_ |
| AssertOnMountThread(); |
| |
| bool skipped = false; |
| std::set<const FilePath> children_to_preserve; |
| |
| for (auto match = mounts->begin(); match != mounts->end();) { |
| // curr->first is the source device of the group that we are processing in |
| // this outer loop. |
| auto curr = match; |
| bool keep = false; |
| |
| // Note that we organize the set of mounts with the same source, then |
| // process them together. That is, say there's /dev/mmcblk0p1 mounted on |
| // /home/user/xxx and /home/chronos/u-xxx/MyFiles/Downloads. They are both |
| // from the same source (/dev/mmcblk0p1, or match->first). In this case, |
| // we'll decide the fate of all mounts with the same source together. For |
| // each such group, the outer loop will run once. The inner loop will |
| // iterate through every mount in the group with |match| variable, looking |
| // to see if it's owned by any active mounts. If it is, the entire group is |
| // kept. Otherwise, (and assuming no open files), the entire group is |
| // discarded, as in, not moved into the active_mounts multimap. |
| |
| // Walk each set of sources as one group since multimaps are key ordered. |
| for (; match != mounts->end() && match->first == curr->first; ++match) { |
| // Ignore known mounts. |
| for (const auto& [unused, session] : *sessions_) { |
| if (session.OwnsMountPoint(match->second)) { |
| keep = true; |
| // If !include_busy_mount, other mount points not owned scanned after |
| // should be preserved as well. |
| if (include_busy_mount) |
| break; |
| } |
| } |
| |
| // Ignore mounts pointing to children of used mounts. |
| if (!include_busy_mount) { |
| if (children_to_preserve.find(match->second) != |
| children_to_preserve.end()) { |
| keep = true; |
| skipped = true; |
| LOG(WARNING) << "Stale mount " << match->second.value() << " from " |
| << match->first.value() << " is a just a child."; |
| } |
| } |
| |
| // Optionally, ignore mounts with open files. |
| if (!keep && !include_busy_mount) { |
| // Mark the mount points that are not in use as 'expired'. Add the mount |
| // points to the |active_mounts| list if they are not expired. |
| ExpireMountResult expire_mount_result = |
| platform_->ExpireMount(match->second); |
| if (expire_mount_result == ExpireMountResult::kBusy) { |
| LOG(WARNING) << "Stale mount " << match->second.value() << " from " |
| << match->first.value() << " has active holders."; |
| keep = true; |
| skipped = true; |
| } else if (expire_mount_result == ExpireMountResult::kError) { |
| // To avoid unloading any pkcs11 token that is in use, add mount point |
| // to the |active_mounts| if it is failed to be expired. |
| LOG(ERROR) << "Stale mount " << match->second.value() << " from " |
| << match->first.value() |
| << " failed to be removed from active mounts list."; |
| keep = true; |
| skipped = true; |
| } |
| } |
| } |
| if (keep) { |
| std::multimap<const FilePath, const FilePath> children; |
| LOG(WARNING) << "Looking for children of " << curr->first; |
| platform_->GetMountsBySourcePrefix(curr->first, &children); |
| for (const auto& child : children) { |
| children_to_preserve.insert(child.second); |
| } |
| |
| active_mounts->insert(curr, match); |
| mounts->erase(curr, match); |
| } |
| } |
| return skipped; |
| } |
| |
| void UserDataAuth::GetEphemeralLoopDevicesMounts( |
| std::multimap<const FilePath, const FilePath>* mounts) { |
| AssertOnMountThread(); |
| std::multimap<const FilePath, const FilePath> loop_mounts; |
| platform_->GetLoopDeviceMounts(&loop_mounts); |
| |
| const FilePath sparse_path = |
| FilePath(kEphemeralCryptohomeDir).Append(kSparseFileDir); |
| for (const auto& device : platform_->GetAttachedLoopDevices()) { |
| // Ephemeral mounts are mounts from a loop device with ephemeral sparse |
| // backing file. |
| if (sparse_path.IsParent(device.backing_file)) { |
| auto range = loop_mounts.equal_range(device.device); |
| mounts->insert(range.first, range.second); |
| } |
| } |
| } |
| |
| bool UserDataAuth::UnloadPkcs11Tokens(const std::vector<FilePath>& exclude) { |
| AssertOnMountThread(); |
| |
| SecureBlob isolate = |
| chaps::IsolateCredentialManager::GetDefaultIsolateCredential(); |
| std::vector<std::string> tokens; |
| if (!chaps_client_->GetTokenList(isolate, &tokens)) |
| return false; |
| for (size_t i = 0; i < tokens.size(); ++i) { |
| if (tokens[i] != chaps::kSystemTokenPath && |
| !PrefixPresent(exclude, tokens[i])) { |
| // It's not a system token and is not under one of the excluded path. |
| LOG(INFO) << "Unloading up PKCS #11 token: " << tokens[i]; |
| chaps_client_->UnloadToken(isolate, FilePath(tokens[i])); |
| } |
| } |
| return true; |
| } |
| |
| bool UserDataAuth::CleanUpStaleMounts(bool force) { |
| AssertOnMountThread(); |
| |
| // This function is meant to aid in a clean recovery from a crashed or |
| // manually restarted cryptohomed. Cryptohomed may restart: |
| // 1. Before any mounts occur |
| // 2. While mounts are active |
| // 3. During an unmount |
| // In case #1, there should be no special work to be done. |
| // The best way to disambiguate #2 and #3 is to determine if there are |
| // any active open files on any stale mounts. If there are open files, |
| // then we've likely(*) resumed an active session. If there are not, |
| // the last cryptohome should have been unmounted. |
| // It's worth noting that a restart during active use doesn't impair |
| // other user session behavior, like CheckKey, because it doesn't rely |
| // exclusively on mount state. |
| // |
| // In the future, it may make sense to attempt to keep the MountMap |
| // persisted to disk which would make resumption much easier. |
| // |
| // (*) Relies on the expectation that all processes have been killed off. |
| |
| // TODO(b:225769250, dlunev): figure out cleanup for non-mounted application |
| // containers. |
| |
| // Stale shadow and ephemeral mounts. |
| std::multimap<const FilePath, const FilePath> shadow_mounts; |
| std::multimap<const FilePath, const FilePath> ephemeral_mounts; |
| std::multimap<const FilePath, const FilePath> dmcrypt_mounts, |
| grouped_dmcrypt_mounts; |
| |
| // Active mounts that we don't intend to unmount. |
| std::multimap<const FilePath, const FilePath> active_mounts; |
| |
| // Retrieve all the mounts that's currently mounted by the kernel and concerns |
| // us |
| platform_->GetMountsBySourcePrefix(ShadowRoot(), &shadow_mounts); |
| platform_->GetMountsByDevicePrefix(kDeviceMapperDevicePrefix, |
| &dmcrypt_mounts); |
| GroupDmcryptDeviceMounts(&dmcrypt_mounts, &grouped_dmcrypt_mounts); |
| GetEphemeralLoopDevicesMounts(&ephemeral_mounts); |
| |
| // Remove mounts that we've a record of or have open files on them |
| bool skipped = |
| FilterActiveMounts(&shadow_mounts, &active_mounts, force) || |
| FilterActiveMounts(&ephemeral_mounts, &active_mounts, force) || |
| FilterActiveMounts(&grouped_dmcrypt_mounts, &active_mounts, force); |
| |
| // Unload PKCS#11 tokens on any mount that we're going to unmount. |
| std::vector<FilePath> excluded_mount_points; |
| for (const auto& mount : active_mounts) { |
| excluded_mount_points.push_back(mount.second); |
| } |
| UnloadPkcs11Tokens(excluded_mount_points); |
| |
| // Unmount anything left. |
| for (const auto& match : grouped_dmcrypt_mounts) { |
| LOG(WARNING) << "Lazily unmounting stale dmcrypt mount: " |
| << match.second.value() << " for " << match.first.value(); |
| // true for lazy unmount, nullptr for us not needing to know if it's really |
| // unmounted. |
| platform_->Unmount(match.second, true, nullptr); |
| } |
| |
| for (const auto& match : shadow_mounts) { |
| LOG(WARNING) << "Lazily unmounting stale shadow mount: " |
| << match.second.value() << " from " << match.first.value(); |
| // true for lazy unmount, nullptr for us not needing to know if it's really |
| // unmounted. |
| platform_->Unmount(match.second, true, nullptr); |
| } |
| |
| // Attempt to clear the encryption key for the shadow directories once |
| // the mount has been unmounted. The encryption key needs to be cleared |
| // after all the unmounts are done to ensure that none of the existing |
| // submounts becomes inaccessible. |
| if (force && !shadow_mounts.empty()) { |
| // Attempt to clear fscrypt encryption keys for the shadow mounts. |
| for (const auto& match : shadow_mounts) { |
| if (!platform_->InvalidateDirCryptoKey(dircrypto::KeyReference(), |
| match.first)) { |
| LOG(WARNING) << "Failed to clear fscrypt keys for stale mount: " |
| << match.first; |
| } |
| } |
| |
| // Clear all keys in the user keyring for ecryptfs mounts. |
| if (!platform_->ClearUserKeyring()) { |
| LOG(WARNING) << "Failed to clear stale user keys."; |
| } |
| } |
| for (const auto& match : ephemeral_mounts) { |
| LOG(WARNING) << "Lazily unmounting stale ephemeral mount: " |
| << match.second.value() << " from " << match.first.value(); |
| // true for lazy unmount, nullptr for us not needing to know if it's really |
| // unmounted. |
| platform_->Unmount(match.second, true, nullptr); |
| // Clean up destination directory for ephemeral mounts under ephemeral |
| // cryptohome dir. |
| if (base::StartsWith(match.first.value(), kLoopPrefix, |
| base::CompareCase::SENSITIVE) && |
| FilePath(kEphemeralCryptohomeDir).IsParent(match.second)) { |
| platform_->DeletePathRecursively(match.second); |
| } |
| } |
| |
| // Clean up all stale sparse files, this is comprised of two stages: |
| // 1. Clean up stale loop devices. |
| // 2. Clean up stale sparse files. |
| // Note that some mounts are backed by loop devices, and loop devices are |
| // backed by sparse files. |
| |
| std::vector<Platform::LoopDevice> loop_devices = |
| platform_->GetAttachedLoopDevices(); |
| const FilePath sparse_dir = |
| FilePath(kEphemeralCryptohomeDir).Append(kSparseFileDir); |
| std::vector<FilePath> stale_sparse_files; |
| platform_->EnumerateDirectoryEntries(sparse_dir, false /* is_recursive */, |
| &stale_sparse_files); |
| |
| // We'll go through all loop devices, and for every of them, we'll see if we |
| // can remove it. Also in the process, we'll get to keep track of which sparse |
| // files are actually used by active loop devices. |
| for (const auto& device : loop_devices) { |
| // Check whether the loop device is created from an ephemeral sparse file. |
| if (!sparse_dir.IsParent(device.backing_file)) { |
| // Nah, it's this loop device is not backed by an ephemeral sparse file |
| // created by cryptohome, so we'll leave it alone. |
| continue; |
| } |
| |
| // Check if any of our active mounts are backed by this loop device. |
| if (active_mounts.count(device.device) == 0) { |
| // Nope, this loop device have nothing to do with our active mounts. |
| LOG(WARNING) << "Detaching stale loop device: " << device.device.value(); |
| if (!platform_->DetachLoop(device.device)) { |
| ReportCryptohomeError(kEphemeralCleanUpFailed); |
| PLOG(ERROR) << "Can't detach stale loop: " << device.device.value(); |
| } |
| } else { |
| // This loop device backs one of our active_mounts, so we can't count it |
| // as stale. Thus removing from the stale_sparse_files list. |
| stale_sparse_files.erase( |
| std::remove(stale_sparse_files.begin(), stale_sparse_files.end(), |
| device.backing_file), |
| stale_sparse_files.end()); |
| } |
| } |
| |
| // Now we clean up the stale sparse files. |
| for (const auto& file : stale_sparse_files) { |
| LOG(WARNING) << "Deleting stale ephemeral backing sparse file: " |
| << file.value(); |
| if (!platform_->DeleteFile(file)) { |
| ReportCryptohomeError(kEphemeralCleanUpFailed); |
| PLOG(ERROR) << "Failed to clean up ephemeral sparse file: " |
| << file.value(); |
| } |
| } |
| |
| // |force| and |skipped| cannot be true at the same time. If |force| is true, |
| // then we'll not skip over any stale mount because there are open files, so |
| // |skipped| must be false. |
| DCHECK(!(force && skipped)); |
| |
| return skipped; |
| } |
| |
| user_data_auth::UnmountReply UserDataAuth::Unmount() { |
| AssertOnMountThread(); |
| |
| bool unmount_ok = RemoveAllMounts(); |
| |
| // If there are any unexpected mounts lingering from a crash/restart, |
| // clean them up now. |
| // Note that we do not care about the return value of CleanUpStaleMounts() |
| // because it doesn't matter if any mount is skipped due to open files, and |
| // additionally, since we've specified force=true, it'll not skip over mounts |
| // with open files. |
| CleanUpStaleMounts(true); |
| |
| if (homedirs_->AreEphemeralUsersEnabled()) { |
| homedirs_->RemoveNonOwnerCryptohomes(); |
| } |
| |
| CryptohomeStatus result; |
| if (!unmount_ok) { |
| result = MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthRemoveAllMountsFailedInUnmount), |
| ErrorActionSet({ErrorAction::kReboot}), |
| user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_MOUNT_FATAL); |
| } |
| user_data_auth::UnmountReply reply; |
| PopulateReplyWithError(result, &reply); |
| return reply; |
| } |
| |
| void UserDataAuth::InitializePkcs11(UserSession* session) { |
| AssertOnMountThread(); |
| |
| // We should not pass nullptr to this method. |
| DCHECK(session); |
| |
| bool still_mounted = false; |
| |
| // The mount has to be mounted, that is, still tracked by cryptohome. |
| // Otherwise there's no point in initializing PKCS#11 for it. The reason for |
| // this check is because it might be possible for Unmount() to be called after |
| // mounting and before getting here. |
| for (const auto& [unused, user_session] : *sessions_) { |
| if (&user_session == session && session->IsActive()) { |
| still_mounted = true; |
| break; |
| } |
| } |
| |
| if (!still_mounted) { |
| LOG(WARNING) |
| << "PKCS#11 initialization requested but cryptohome is not mounted."; |
| return; |
| } |
| |
| // Note that the timer stops in the Mount class' method. |
| ReportTimerStart(kPkcs11InitTimer); |
| |
| if (session->GetPkcs11Token()) { |
| session->GetPkcs11Token()->Insert(); |
| } |
| |
| ReportTimerStop(kPkcs11InitTimer); |
| |
| LOG(INFO) << "PKCS#11 initialization succeeded."; |
| } |
| |
| void UserDataAuth::Pkcs11RestoreTpmTokens() { |
| AssertOnMountThread(); |
| |
| for (const auto& [unused, session] : *sessions_) { |
| InitializePkcs11(&session); |
| } |
| } |
| |
| void UserDataAuth::EnsureCryptohomeKeys() { |
| if (!IsOnMountThread()) { |
| // We are not on mount thread, but to be safe, we'll only access Mount |
| // objects on mount thread, so let's post ourself there. |
| PostTaskToMountThread(FROM_HERE, |
| base::BindOnce(&UserDataAuth::EnsureCryptohomeKeys, |
| base::Unretained(this))); |
| return; |
| } |
| |
| AssertOnMountThread(); |
| |
| if (!cryptohome_keys_manager_->HasAnyCryptohomeKey()) { |
| cryptohome_keys_manager_->Init(); |
| } |
| } |
| |
| void UserDataAuth::set_cleanup_threshold(uint64_t cleanup_threshold) { |
| disk_cleanup_threshold_ = cleanup_threshold; |
| } |
| |
| void UserDataAuth::set_aggressive_cleanup_threshold( |
| uint64_t aggressive_cleanup_threshold) { |
| disk_cleanup_aggressive_threshold_ = aggressive_cleanup_threshold; |
| } |
| |
| void UserDataAuth::set_critical_cleanup_threshold( |
| uint64_t critical_cleanup_threshold) { |
| disk_cleanup_critical_threshold_ = critical_cleanup_threshold; |
| } |
| |
| void UserDataAuth::set_target_free_space(uint64_t target_free_space) { |
| disk_cleanup_target_free_space_ = target_free_space; |
| } |
| |
| void UserDataAuth::SetLowDiskSpaceCallback( |
| const base::RepeatingCallback<void(uint64_t)>& callback) { |
| low_disk_space_handler_->SetLowDiskSpaceCallback(callback); |
| } |
| |
| void UserDataAuth::SetFingerprintScanResultCallback( |
| const base::RepeatingCallback<void(user_data_auth::FingerprintScanResult)>& |
| callback) { |
| fingerprint_scan_result_callback_ = callback; |
| } |
| |
| void UserDataAuth::OwnershipCallback(bool status, bool took_ownership) { |
| AssertOnMountThread(); |
| |
| // Note that this function should only be called once during the lifetime of |
| // this process, extra calls will be dropped. |
| if (ownership_callback_has_run_) { |
| LOG(WARNING) << "Duplicated call to OwnershipCallback."; |
| return; |
| } |
| ownership_callback_has_run_ = true; |
| |
| if (took_ownership) { |
| // Make sure cryptohome keys are loaded and ready for every mount. |
| EnsureCryptohomeKeys(); |
| |
| // Initialize the install-time locked attributes since we can't do it prior |
| // to ownership. |
| InitializeInstallAttributes(); |
| } |
| } |
| |
| void UserDataAuth::SetEnterpriseOwned(bool enterprise_owned) { |
| AssertOnMountThread(); |
| |
| enterprise_owned_ = enterprise_owned; |
| homedirs_->set_enterprise_owned(enterprise_owned); |
| } |
| |
| void UserDataAuth::DetectEnterpriseOwnership() { |
| AssertOnMountThread(); |
| |
| static const std::string true_str = "true"; |
| brillo::Blob true_value(true_str.begin(), true_str.end()); |
| true_value.push_back(0); |
| |
| brillo::Blob value; |
| if (install_attrs_->Get("enterprise.owned", &value) && value == true_value) { |
| // Update any active mounts with the state, have to be done on mount thread. |
| SetEnterpriseOwned(true); |
| } |
| // Note: Right now there's no way to convert an enterprise owned machine to a |
| // non-enterprise owned machine without clearing the TPM, so we don't try |
| // calling SetEnterpriseOwned() with false. |
| } |
| |
| void UserDataAuth::InitializeInstallAttributes() { |
| AssertOnMountThread(); |
| |
| // Don't reinitialize when install attributes are valid or first install. |
| if (install_attrs_->status() == InstallAttributes::Status::kValid || |
| install_attrs_->status() == InstallAttributes::Status::kFirstInstall) { |
| return; |
| } |
| |
| // The TPM owning instance may have changed since initialization. |
| // InstallAttributes can handle a NULL or !IsEnabled Tpm object. |
| std::ignore = install_attrs_->Init(); |
| |
| // Check if the machine is enterprise owned and report to mount_ then. |
| DetectEnterpriseOwnership(); |
| } |
| |
| void UserDataAuth::EnsureBootLockboxFinalized() { |
| AssertOnMountThread(); |
| |
| // Lock NVRamBootLockbox |
| auto nvram_boot_lockbox_client = |
| bootlockbox::BootLockboxClient::CreateBootLockboxClient(); |
| if (!nvram_boot_lockbox_client) { |
| LOG(WARNING) << "Failed to create nvram_boot_lockbox_client"; |
| return; |
| } |
| |
| if (!nvram_boot_lockbox_client->Finalize()) { |
| LOG(WARNING) << "Failed to finalize nvram lockbox."; |
| } |
| } |
| |
| void UserDataAuth::BlockPkEstablishment() { |
| AssertOnMountThread(); |
| |
| if (pk_establishment_blocked_) { |
| return; |
| } |
| |
| hwsec::StatusOr<bool> enabled = pinweaver_->IsEnabled(); |
| if (!enabled.ok() || !*enabled) { |
| return; |
| } |
| |
| // Pk related mechanisms are only added in PW version 2. |
| hwsec::StatusOr<uint8_t> version = pinweaver_->GetVersion(); |
| if (!version.ok() || *version <= 1) { |
| return; |
| } |
| |
| hwsec::Status status = pinweaver_->BlockGeneratePk(); |
| if (!status.ok()) { |
| LOG(WARNING) << "Block biometrics Pk establishment failed: " |
| << status.status(); |
| } else { |
| pk_establishment_blocked_ = true; |
| } |
| } |
| |
| UserSession* UserDataAuth::GetOrCreateUserSession(const Username& username) { |
| // This method touches the |sessions_| object so it needs to run on |
| // |mount_thread_| |
| AssertOnMountThread(); |
| UserSession* session = sessions_->Find(username); |
| if (!session) { |
| // We don't have a mount associated with |username|, let's create one. |
| EnsureBootLockboxFinalized(); |
| // Block biometrics Pk establishment afterwards as we considered the device |
| // becoming more vulnerable to attackers. |
| BlockPkEstablishment(); |
| std::unique_ptr<UserSession> owned_session = user_session_factory_->New( |
| username, legacy_mount_, bind_mount_downloads_); |
| session = owned_session.get(); |
| if (!sessions_->Add(username, std::move(owned_session))) { |
| NOTREACHED() << "Failed to add created user session"; |
| return nullptr; |
| } |
| } |
| return session; |
| } |
| |
| void UserDataAuth::RemoveInactiveUserSession(const Username& username) { |
| AssertOnMountThread(); |
| |
| UserSession* session = sessions_->Find(username); |
| if (!session || session->IsActive()) { |
| return; |
| } |
| |
| if (!sessions_->Remove(username)) { |
| NOTREACHED() << "Failed to remove inactive user session."; |
| } |
| } |
| |
| CryptohomeStatus UserDataAuth::InitForChallengeResponseAuth() { |
| AssertOnMountThread(); |
| if (challenge_credentials_helper_initialized_) { |
| // Already successfully initialized. |
| return OkStatus<CryptohomeError>(); |
| } |
| |
| if (!challenge_credentials_helper_) { |
| // Lazily create the helper object that manages generation/decryption of |
| // credentials for challenge-protected vaults. |
| default_challenge_credentials_helper_ = |
| std::make_unique<ChallengeCredentialsHelperImpl>(hwsec_); |
| challenge_credentials_helper_ = default_challenge_credentials_helper_.get(); |
| } |
| |
| if (!mount_thread_bus_) { |
| LOG(ERROR) << "Cannot do challenge-response mount without system D-Bus bus"; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthNoDBusInInitChalRespAuth), |
| ErrorActionSet( |
| {ErrorAction::kReboot, ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_MOUNT_FATAL); |
| } |
| key_challenge_service_factory_->SetMountThreadBus(mount_thread_bus_); |
| |
| auth_block_utility_->InitializeChallengeCredentialsHelper( |
| challenge_credentials_helper_, key_challenge_service_factory_); |
| |
| challenge_credentials_helper_initialized_ = true; |
| return OkStatus<CryptohomeError>(); |
| } |
| |
| void UserDataAuth::CheckKey( |
| const user_data_auth::CheckKeyRequest& request, |
| base::OnceCallback<void(user_data_auth::CryptohomeErrorCode)> on_done) { |
| AssertOnMountThread(); |
| |
| if (!request.has_account_id() || !request.has_authorization_request()) { |
| LOG(ERROR) |
| << "CheckKeyRequest must have account_id and authorization_request."; |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| return; |
| } |
| |
| Username account_id = GetAccountId(request.account_id()); |
| if (account_id->empty()) { |
| LOG(ERROR) << "CheckKeyRequest must have valid account_id."; |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| return; |
| } |
| |
| // Process challenge-response credentials asynchronously. |
| if (request.authorization_request().key().data().type() == |
| KeyData::KEY_TYPE_CHALLENGE_RESPONSE) { |
| DoChallengeResponseCheckKey(request, std::move(on_done)); |
| return; |
| } |
| |
| // Process fingerprint credentials asynchronously. |
| if (request.authorization_request().key().data().type() == |
| KeyData::KEY_TYPE_FINGERPRINT) { |
| if (!fingerprint_manager_) { |
| // Fingerprint manager failed to initialize, or the device may not |
| // support fingerprint auth at all. |
| std::move(on_done).Run(user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_FINGERPRINT_ERROR_INTERNAL); |
| return; |
| } |
| if (!fingerprint_manager_->HasAuthSessionForUser( |
| SanitizeUserName(account_id))) { |
| std::move(on_done).Run(user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_FINGERPRINT_DENIED); |
| return; |
| } |
| fingerprint_manager_->SetAuthScanDoneCallback( |
| base::BindOnce(&UserDataAuth::CompleteFingerprintCheckKey, |
| base::Unretained(this), std::move(on_done))); |
| return; |
| } |
| |
| // Note that there's no check for empty AuthorizationRequest key label because |
| // such a key will test against all VaultKeysets of a compatible |
| // key().data().type(), and thus is valid. |
| |
| const std::string& auth_secret = |
| request.authorization_request().key().secret(); |
| if (auth_secret.empty()) { |
| LOG(ERROR) << "No key secret in CheckKeyRequest."; |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| return; |
| } |
| |
| Credentials credentials(account_id, SecureBlob(auth_secret)); |
| credentials.set_key_data(request.authorization_request().key().data()); |
| |
| const ObfuscatedUsername obfuscated_username = |
| credentials.GetObfuscatedUsername(); |
| |
| bool found_valid_credentials = false; |
| UserSession* const session = sessions_->Find(account_id); |
| if (session) { |
| if (session->VerifyCredentials(credentials)) { |
| found_valid_credentials = true; |
| } else if (session->IsEphemeral()) { |
| std::move(on_done).Run( |
| user_data_auth::CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED); |
| return; |
| } |
| } |
| |
| if (found_valid_credentials) { |
| MountStatusOr<std::unique_ptr<VaultKeyset>> vk_status = |
| keyset_management_->GetValidKeyset(credentials); |
| std::unique_ptr<VaultKeyset> vk; |
| if (!vk_status.ok()) { |
| // The operation may fail for ephemeral user. |
| LOG(WARNING) << "Failed to get valid keyset in CheckKey: << " |
| << std::move(vk_status).status(); |
| } else { |
| vk = std::move(vk_status).value(); |
| } |
| |
| if (vk) { |
| // Entered the right creds, so reset LE credentials. |
| keyset_management_->ResetLECredentialsWithValidatedVK( |
| *vk, obfuscated_username); |
| } |
| |
| if (request.unlock_webauthn_secret()) { |
| if (vk == nullptr) { |
| std::move(on_done).Run( |
| user_data_auth::CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED); |
| return; |
| } |
| if (!PrepareWebAuthnSecret(account_id, *vk)) { |
| // Failed to prepare WebAuthn secret means there's no active user |
| // session for the account id. |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_KEY_NOT_FOUND); |
| return; |
| } |
| } |
| |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_NOT_SET); |
| return; |
| } |
| |
| // Cover different keys for the same user with homedirs. |
| if (!homedirs_->Exists(obfuscated_username)) { |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND); |
| return; |
| } |
| |
| MountStatusOr<std::unique_ptr<VaultKeyset>> vk_status = |
| keyset_management_->GetValidKeyset(credentials); |
| if (!vk_status.ok()) { |
| // TODO(wad) Should this pass along KEY_NOT_FOUND too? |
| std::move(on_done).Run( |
| user_data_auth::CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED); |
| return; |
| } |
| |
| keyset_management_->ResetLECredentialsWithValidatedVK(*vk_status.value(), |
| obfuscated_username); |
| |
| if (request.unlock_webauthn_secret()) { |
| if (!PrepareWebAuthnSecret(account_id, *vk_status.value().get())) { |
| // Failed to prepare WebAuthn secret means there's no active user |
| // session for the account id. |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_KEY_NOT_FOUND); |
| return; |
| } |
| } |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_NOT_SET); |
| |
| return; |
| } |
| |
| bool UserDataAuth::PrepareWebAuthnSecret(const Username& account_id, |
| const VaultKeyset& vk) { |
| UserSession* const session = sessions_->Find(account_id); |
| if (!session) { |
| return false; |
| } |
| FileSystemKeyset fs_keyset(vk); |
| session->PrepareWebAuthnSecret(fs_keyset.Key().fek, fs_keyset.Key().fnek); |
| return true; |
| } |
| |
| void UserDataAuth::CompleteFingerprintCheckKey( |
| base::OnceCallback<void(user_data_auth::CryptohomeErrorCode)> on_done, |
| FingerprintScanStatus status) { |
| AssertOnMountThread(); |
| if (status == FingerprintScanStatus::FAILED_RETRY_ALLOWED) { |
| std::move(on_done).Run(user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_FINGERPRINT_RETRY_REQUIRED); |
| return; |
| } else if (status == FingerprintScanStatus::FAILED_RETRY_NOT_ALLOWED) { |
| std::move(on_done).Run(user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_FINGERPRINT_DENIED); |
| return; |
| } |
| |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_NOT_SET); |
| } |
| |
| void UserDataAuth::DoChallengeResponseCheckKey( |
| const user_data_auth::CheckKeyRequest& request, |
| base::OnceCallback<void(user_data_auth::CryptohomeErrorCode)> on_done) { |
| AssertOnMountThread(); |
| |
| const auto& authorization = request.authorization_request(); |
| DCHECK_EQ(authorization.key().data().type(), |
| KeyData::KEY_TYPE_CHALLENGE_RESPONSE); |
| |
| CryptohomeStatus status = InitForChallengeResponseAuth(); |
| if (!status.ok()) { |
| std::move(on_done).Run(LegacyErrorCodeFromStack(status)); |
| return; |
| } |
| |
| if (!authorization.has_key_delegate() || |
| !authorization.key_delegate().has_dbus_service_name()) { |
| LOG(ERROR) << "Cannot do challenge-response authentication without key " |
| "delegate information"; |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_MOUNT_FATAL); |
| return; |
| } |
| if (!authorization.key().data().challenge_response_key_size()) { |
| LOG(ERROR) << "Missing challenge-response key information"; |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_MOUNT_FATAL); |
| return; |
| } |
| if (authorization.key().data().challenge_response_key_size() > 1) { |
| LOG(ERROR) |
| << "Using multiple challenge-response keys at once is unsupported"; |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_MOUNT_FATAL); |
| return; |
| } |
| |
| // Begin from attempting a lightweight check that doesn't use the vault keyset |
| // or heavy TPM operations, and therefore is faster than the full check and |
| // also works in case the mount is ephemeral. |
| TryLightweightChallengeResponseCheckKey(request, std::move(on_done)); |
| } |
| |
| void UserDataAuth::TryLightweightChallengeResponseCheckKey( |
| const user_data_auth::CheckKeyRequest& request, |
| base::OnceCallback<void(user_data_auth::CryptohomeErrorCode)> on_done) { |
| AssertOnMountThread(); |
| |
| const auto& authorization = request.authorization_request(); |
| const auto& identifier = request.account_id(); |
| |
| DCHECK_EQ(authorization.key().data().type(), |
| KeyData::KEY_TYPE_CHALLENGE_RESPONSE); |
| DCHECK(challenge_credentials_helper_); |
| |
| const Username account_id = GetAccountId(identifier); |
| const ObfuscatedUsername obfuscated_username = SanitizeUserName(account_id); |
| |
| std::optional<KeyData> found_session_key_data; |
| for (const auto& [unused, session] : *sessions_) { |
| if (session.VerifyUser(obfuscated_username) && |
| KeyMatchesForLightweightChallengeResponseCheck( |
| authorization.key().data(), session)) { |
| found_session_key_data = session.key_data(); |
| break; |
| } |
| } |
| if (!found_session_key_data) { |
| // No matching user session found, so fall back to the full check. |
| OnLightweightChallengeResponseCheckKeyDone( |
| request, std::move(on_done), |
| MakeStatus<CryptohomeTPMError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthNoSessionInTryLiteChalRespCheckKey), |
| ErrorActionSet({ErrorAction::kReboot}), TPMRetryAction::kReboot)); |
| return; |
| } |
| |
| // KeyChallengeService is tasked with contacting the challenge response D-Bus |
| // service that'll provide the response once we send the challenge. |
| std::unique_ptr<KeyChallengeService> key_challenge_service = |
| key_challenge_service_factory_->New( |
| authorization.key_delegate().dbus_service_name()); |
| if (!key_challenge_service) { |
| LOG(ERROR) << "Failed to create key challenge service"; |
| OnLightweightChallengeResponseCheckKeyDone( |
| request, std::move(on_done), |
| MakeStatus<CryptohomeTPMError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthNoServiceInTryLiteChalRespCheckKey), |
| ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}), |
| TPMRetryAction::kReboot)); |
| return; |
| } |
| |
| if (!found_session_key_data->challenge_response_key_size()) { |
| LOG(ERROR) << "Missing challenge-response key information"; |
| OnLightweightChallengeResponseCheckKeyDone( |
| request, std::move(on_done), |
| MakeStatus<CryptohomeTPMError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthNoKeyInfoInTryLiteChalRespCheckKey), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| TPMRetryAction::kNoRetry)); |
| return; |
| } |
| |
| if (found_session_key_data->challenge_response_key_size() > 1) { |
| LOG(ERROR) |
| << "Using multiple challenge-response keys at once is unsupported"; |
| OnLightweightChallengeResponseCheckKeyDone( |
| request, std::move(on_done), |
| MakeStatus<CryptohomeTPMError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthMultipleKeyInTryLiteChalRespCheckKey), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| TPMRetryAction::kNoRetry)); |
| return; |
| } |
| |
| const ChallengePublicKeyInfo& public_key_info = |
| found_session_key_data->challenge_response_key(0); |
| |
| // Attempt the lightweight check against the found user session. |
| challenge_credentials_helper_->VerifyKey( |
| account_id, proto::FromProto(public_key_info), |
| std::move(key_challenge_service), |
| base::BindOnce(&UserDataAuth::OnLightweightChallengeResponseCheckKeyDone, |
| base::Unretained(this), request, std::move(on_done))); |
| } |
| |
| void UserDataAuth::OnLightweightChallengeResponseCheckKeyDone( |
| const user_data_auth::CheckKeyRequest& request, |
| base::OnceCallback<void(user_data_auth::CryptohomeErrorCode)> on_done, |
| TPMStatus status) { |
| AssertOnMountThread(); |
| if (!status.ok()) { |
| DoFullChallengeResponseCheckKey(request, std::move(on_done)); |
| return; |
| } |
| |
| // Note that the LE credentials are not reset here, since we don't have the |
| // full credentials after the lightweight check. |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_NOT_SET); |
| } |
| |
| void UserDataAuth::DoFullChallengeResponseCheckKey( |
| const user_data_auth::CheckKeyRequest& request, |
| base::OnceCallback<void(user_data_auth::CryptohomeErrorCode)> on_done) { |
| AssertOnMountThread(); |
| |
| const auto& authorization = request.authorization_request(); |
| const auto& identifier = request.account_id(); |
| |
| DCHECK_EQ(authorization.key().data().type(), |
| KeyData::KEY_TYPE_CHALLENGE_RESPONSE); |
| DCHECK(challenge_credentials_helper_); |
| |
| const Username account_id = GetAccountId(identifier); |
| const ObfuscatedUsername obfuscated_username = SanitizeUserName(account_id); |
| |
| // KeyChallengeService is tasked with contacting the challenge response D-Bus |
| // service that'll provide the response once we send the challenge. |
| std::unique_ptr<KeyChallengeService> key_challenge_service = |
| key_challenge_service_factory_->New( |
| authorization.key_delegate().dbus_service_name()); |
| if (!key_challenge_service) { |
| LOG(ERROR) << "Failed to create key challenge service"; |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_MOUNT_FATAL); |
| return; |
| } |
| |
| if (!homedirs_->Exists(obfuscated_username)) { |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND); |
| return; |
| } |
| |
| std::unique_ptr<VaultKeyset> vault_keyset(keyset_management_->GetVaultKeyset( |
| obfuscated_username, authorization.key().data().label())); |
| if (!vault_keyset) { |
| LOG(ERROR) << "No existing challenge-response vault keyset found"; |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_MOUNT_FATAL); |
| return; |
| } |
| |
| if (!authorization.key().data().challenge_response_key_size()) { |
| LOG(ERROR) << "Missing challenge-response key information"; |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_MOUNT_FATAL); |
| return; |
| } |
| |
| if (authorization.key().data().challenge_response_key_size() > 1) { |
| LOG(ERROR) |
| << "Using multiple challenge-response keys at once is unsupported"; |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_MOUNT_FATAL); |
| return; |
| } |
| |
| const ChallengePublicKeyInfo& public_key_info = |
| authorization.key().data().challenge_response_key(0); |
| |
| challenge_credentials_helper_->Decrypt( |
| account_id, proto::FromProto(public_key_info), |
| proto::FromProto(vault_keyset->GetSignatureChallengeInfo()), |
| std::move(key_challenge_service), |
| base::BindOnce(&UserDataAuth::OnFullChallengeResponseCheckKeyDone, |
| base::Unretained(this), request, std::move(on_done))); |
| } |
| |
| void UserDataAuth::OnFullChallengeResponseCheckKeyDone( |
| const user_data_auth::CheckKeyRequest& request, |
| base::OnceCallback<void(user_data_auth::CryptohomeErrorCode)> on_done, |
| TPMStatusOr<ChallengeCredentialsHelper::GenerateNewOrDecryptResult> |
| result) { |
| AssertOnMountThread(); |
| if (!result.ok()) { |
| LOG(ERROR) << "Key checking failed due to failure to obtain " |
| "challenge-response credentials"; |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_MOUNT_FATAL); |
| return; |
| } |
| |
| ChallengeCredentialsHelper::GenerateNewOrDecryptResult result_val = |
| std::move(result).value(); |
| std::unique_ptr<brillo::SecureBlob> passkey = result_val.passkey(); |
| |
| const auto& authorization = request.authorization_request(); |
| const auto& identifier = request.account_id(); |
| const Username account_id = GetAccountId(identifier); |
| |
| auto credentials = std::make_unique<Credentials>(account_id, *passkey); |
| credentials->set_key_data(authorization.key().data()); |
| |
| // Entered the right creds, so reset LE credentials. |
| keyset_management_->ResetLECredentials(*credentials, |
| credentials->GetObfuscatedUsername()); |
| |
| std::move(on_done).Run(user_data_auth::CRYPTOHOME_ERROR_NOT_SET); |
| } |
| |
| user_data_auth::ListKeysReply UserDataAuth::ListKeys( |
| const user_data_auth::ListKeysRequest& request) { |
| AssertOnMountThread(); |
| user_data_auth::ListKeysReply reply; |
| |
| if (!request.has_account_id()) { |
| // ListKeysRequest must have account_id. |
| PopulateReplyWithError( |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthNoIDInListKeys), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT), |
| &reply); |
| return reply; |
| } |
| |
| const Username account_id = GetAccountId(request.account_id()); |
| if (account_id->empty()) { |
| // ListKeysRequest must have valid account_id. |
| PopulateReplyWithError( |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthInvalidIDInListKeys), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT), |
| &reply); |
| return reply; |
| } |
| |
| const ObfuscatedUsername obfuscated_username = SanitizeUserName(account_id); |
| if (!homedirs_->Exists(obfuscated_username)) { |
| PopulateReplyWithError( |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthUserNonexistentInListKeys), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND), |
| &reply); |
| return reply; |
| } |
| |
| std::vector<std::string> labels_out; |
| if (!keyset_management_->GetVaultKeysetLabels( |
| obfuscated_username, /*include_le_labels*/ true, &labels_out)) { |
| PopulateReplyWithError( |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthListFailedInListKeys), |
| ErrorActionSet( |
| {ErrorAction::kDevCheckUnexpectedState, ErrorAction::kReboot}), |
| user_data_auth::CRYPTOHOME_ERROR_KEY_NOT_FOUND), |
| &reply); |
| return reply; |
| } |
| *reply.mutable_labels() = {labels_out.begin(), labels_out.end()}; |
| PopulateReplyWithError(OkStatus<CryptohomeError>(), &reply); |
| return reply; |
| } |
| |
| user_data_auth::RemoveReply UserDataAuth::Remove( |
| const user_data_auth::RemoveRequest& request) { |
| AssertOnMountThread(); |
| |
| user_data_auth::RemoveReply reply; |
| if (!request.has_identifier() && request.auth_session_id().empty()) { |
| // RemoveRequest must have identifier or an AuthSession Id |
| PopulateReplyWithError( |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthNoIDInRemove), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT), |
| &reply); |
| return reply; |
| } |
| |
| InUseAuthSession auth_session; |
| if (!request.auth_session_id().empty()) { |
| auth_session = |
| auth_session_manager_->FindAuthSession(request.auth_session_id()); |
| CryptohomeStatus auth_session_status = auth_session.AuthSessionStatus(); |
| if (!auth_session_status.ok()) { |
| PopulateReplyWithError(auth_session_status.status(), &reply); |
| return reply; |
| } |
| } |
| |
| Username account_id = auth_session.AuthSessionStatus().ok() |
| ? auth_session->username() |
| : GetAccountId(request.identifier()); |
| if (account_id->empty()) { |
| // RemoveRequest must have valid account_id. |
| PopulateReplyWithError( |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthNoAccountIdWithAuthSessionInRemove), |
| ErrorActionSet( |
| {ErrorAction::kDevCheckUnexpectedState, ErrorAction::kReboot}), |
| user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT), |
| &reply); |
| return reply; |
| } |
| |
| ObfuscatedUsername obfuscated = SanitizeUserName(account_id); |
| |
| const UserSession* const session = sessions_->Find(account_id); |
| if (session && session->IsActive()) { |
| // Can't remove active user |
| PopulateReplyWithError( |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthUserActiveInRemove), |
| ErrorActionSet({ErrorAction::kReboot}), |
| user_data_auth::CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY), |
| &reply); |
| return reply; |
| } |
| |
| if (!homedirs_->Remove(obfuscated)) { |
| // User vault removal failed. |
| PopulateReplyWithError( |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthRemoveFailedInRemove), |
| ErrorActionSet({ErrorAction::kPowerwash, ErrorAction::kReboot}), |
| user_data_auth::CRYPTOHOME_ERROR_REMOVE_FAILED), |
| &reply); |
| return reply; |
| } |
| |
| // Since the user is now removed, any further operations require a fresh |
| // AuthSession. |
| if (auth_session.AuthSessionStatus().ok()) { |
| if (!auth_session_manager_->RemoveAuthSession(request.auth_session_id())) { |
| NOTREACHED() << "Failed to remove AuthSession when removing user."; |
| } |
| } |
| |
| PopulateReplyWithError(OkStatus<CryptohomeError>(), &reply); |
| return reply; |
| } |
| |
| user_data_auth::ResetApplicationContainerReply |
| UserDataAuth::ResetApplicationContainer( |
| const user_data_auth::ResetApplicationContainerRequest& request) { |
| AssertOnMountThread(); |
| user_data_auth::ResetApplicationContainerReply reply; |
| Username account_id = GetAccountId(request.account_id()); |
| |
| if (account_id->empty() || request.application_name().empty()) { |
| // RemoveRequest must have identifier or an AuthSession Id |
| PopulateReplyWithError( |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthNoIDInResetAppContainer), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT), |
| &reply); |
| return reply; |
| } |
| |
| UserSession* session = sessions_->Find(account_id); |
| if (!session || !session->IsActive()) { |
| // Can't reset container of inactive user. |
| PopulateReplyWithError( |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthUserInactiveInResetAppContainer), |
| ErrorActionSet({ErrorAction::kReboot}), |
| user_data_auth::CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY), |
| &reply); |
| return reply; |
| } |
| |
| if (!session->ResetApplicationContainer(request.application_name())) { |
| PopulateReplyWithError( |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthUserFailedResetAppContainer), |
| ErrorActionSet({ErrorAction::kReboot}), |
| user_data_auth::CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY), |
| &reply); |
| return reply; |
| } |
| |
| PopulateReplyWithError(OkStatus<CryptohomeError>(), &reply); |
| return reply; |
| } |
| |
| void UserDataAuth::StartMigrateToDircrypto( |
| const user_data_auth::StartMigrateToDircryptoRequest& request, |
| Mount::MigrationCallback progress_callback) { |
| AssertOnMountThread(); |
| |
| MigrationType migration_type = request.minimal_migration() |
| ? MigrationType::MINIMAL |
| : MigrationType::FULL; |
| |
| // Note that total_bytes and current_bytes field in |progress| is discarded by |
| // client whenever |progress.status| is not DIRCRYPTO_MIGRATION_IN_PROGRESS, |
| // this is why they are left with the default value of 0 here. |
| user_data_auth::DircryptoMigrationProgress progress; |
| AuthSession* auth_session = nullptr; |
| InUseAuthSession in_use_auth_session; |
| if (!request.auth_session_id().empty()) { |
| CryptohomeStatusOr<InUseAuthSession> auth_session_status = |
| GetAuthenticatedAuthSession(request.auth_session_id()); |
| if (!auth_session_status.ok()) { |
| LOG(ERROR) << "StartMigrateToDircrypto: Invalid auth_session_id."; |
| progress.set_status(user_data_auth::DIRCRYPTO_MIGRATION_FAILED); |
| progress_callback.Run(progress); |
| return; |
| } |
| in_use_auth_session = std::move(auth_session_status.value()); |
| auth_session = in_use_auth_session.Get(); |
| } |
| |
| Username account_id = auth_session ? auth_session->username() |
| : GetAccountId(request.account_id()); |
| UserSession* const session = sessions_->Find(account_id); |
| if (!session) { |
| LOG(ERROR) << "StartMigrateToDircrypto: Failed to get session."; |
| progress.set_status(user_data_auth::DIRCRYPTO_MIGRATION_FAILED); |
| progress_callback.Run(progress); |
| return; |
| } |
| LOG(INFO) << "StartMigrateToDircrypto: Migrating to dircrypto."; |
| if (!session->MigrateVault(progress_callback, migration_type)) { |
| LOG(ERROR) << "StartMigrateToDircrypto: Failed to migrate."; |
| progress.set_status(user_data_auth::DIRCRYPTO_MIGRATION_FAILED); |
| progress_callback.Run(progress); |
| return; |
| } |
| LOG(INFO) << "StartMigrateToDircrypto: Migration done."; |
| progress.set_status(user_data_auth::DIRCRYPTO_MIGRATION_SUCCESS); |
| progress_callback.Run(progress); |
| } |
| |
| user_data_auth::CryptohomeErrorCode UserDataAuth::NeedsDircryptoMigration( |
| const AccountIdentifier& account, bool* result) { |
| AssertOnMountThread(); |
| ObfuscatedUsername obfuscated_username = |
| SanitizeUserName(GetAccountId(account)); |
| if (!homedirs_->Exists(obfuscated_username)) { |
| LOG(ERROR) << "Unknown user in NeedsDircryptoMigration."; |
| return user_data_auth::CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND; |
| } |
| |
| *result = !force_ecryptfs_ && |
| homedirs_->NeedsDircryptoMigration(obfuscated_username); |
| return user_data_auth::CRYPTOHOME_ERROR_NOT_SET; |
| } |
| |
| bool UserDataAuth::IsLowEntropyCredentialSupported() { |
| AssertOnOriginThread(); |
| hwsec::StatusOr<bool> is_enabled = hwsec_->IsPinWeaverEnabled(); |
| if (!is_enabled.ok()) { |
| LOG(ERROR) << "Failed to get pinweaver status"; |
| return false; |
| } |
| return is_enabled.value(); |
| } |
| |
| int64_t UserDataAuth::GetAccountDiskUsage(const AccountIdentifier& account) { |
| AssertOnMountThread(); |
| // Note that if the given |account| is invalid or non-existent, then HomeDirs' |
| // implementation of ComputeDiskUsage is specified to return 0. |
| return homedirs_->ComputeDiskUsage(GetAccountId(account)); |
| } |
| |
| bool UserDataAuth::IsArcQuotaSupported() { |
| AssertOnOriginThread(); |
| return arc_disk_quota_->IsQuotaSupported(); |
| } |
| |
| int64_t UserDataAuth::GetCurrentSpaceForArcUid(uid_t android_uid) { |
| AssertOnOriginThread(); |
| return arc_disk_quota_->GetCurrentSpaceForUid(android_uid); |
| } |
| |
| int64_t UserDataAuth::GetCurrentSpaceForArcGid(uid_t android_gid) { |
| AssertOnOriginThread(); |
| return arc_disk_quota_->GetCurrentSpaceForGid(android_gid); |
| } |
| |
| int64_t UserDataAuth::GetCurrentSpaceForArcProjectId(int project_id) { |
| AssertOnOriginThread(); |
| return arc_disk_quota_->GetCurrentSpaceForProjectId(project_id); |
| } |
| |
| bool UserDataAuth::SetMediaRWDataFileProjectId(int project_id, |
| int fd, |
| int* out_error) { |
| AssertOnOriginThread(); |
| return arc_disk_quota_->SetMediaRWDataFileProjectId(project_id, fd, |
| out_error); |
| } |
| |
| bool UserDataAuth::SetMediaRWDataFileProjectInheritanceFlag(bool enable, |
| int fd, |
| int* out_error) { |
| AssertOnOriginThread(); |
| return arc_disk_quota_->SetMediaRWDataFileProjectInheritanceFlag(enable, fd, |
| out_error); |
| } |
| |
| bool UserDataAuth::Pkcs11IsTpmTokenReady() { |
| AssertOnMountThread(); |
| // We touched the sessions_ object, so we need to be on mount thread. |
| |
| for (const auto& [unused, session] : *sessions_) { |
| if (!session.GetPkcs11Token() || !session.GetPkcs11Token()->IsReady()) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| user_data_auth::TpmTokenInfo UserDataAuth::Pkcs11GetTpmTokenInfo( |
| const Username& username) { |
| AssertOnOriginThread(); |
| user_data_auth::TpmTokenInfo result; |
| std::string label, pin; |
| CK_SLOT_ID slot; |
| FilePath token_path; |
| if (username->empty()) { |
| // We want to get the system token. |
| |
| // Get the label and pin for system token. |
| pkcs11_init_->GetTpmTokenInfo(&label, &pin); |
| |
| token_path = FilePath(chaps::kSystemTokenPath); |
| } else { |
| // We want to get the user token. |
| |
| // Get the label and pin for user token. |
| pkcs11_init_->GetTpmTokenInfoForUser(username, &label, &pin); |
| |
| token_path = homedirs_->GetChapsTokenDir(username); |
| } |
| |
| result.set_label(label); |
| result.set_user_pin(pin); |
| |
| if (!pkcs11_init_->GetTpmTokenSlotForPath(token_path, &slot)) { |
| // Failed to get the slot, let's use -1 for default. |
| slot = -1; |
| } |
| result.set_slot(slot); |
| |
| return result; |
| } |
| |
| void UserDataAuth::Pkcs11Terminate() { |
| AssertOnMountThread(); |
| // We are touching the |sessions_| object so we need to be on mount thread. |
| |
| for (const auto& [unused, session] : *sessions_) { |
| if (session.GetPkcs11Token()) { |
| session.GetPkcs11Token()->Remove(); |
| } |
| } |
| } |
| |
| bool UserDataAuth::InstallAttributesGet(const std::string& name, |
| std::vector<uint8_t>* data_out) { |
| AssertOnMountThread(); |
| return install_attrs_->Get(name, data_out); |
| } |
| |
| bool UserDataAuth::InstallAttributesSet(const std::string& name, |
| const std::vector<uint8_t>& data) { |
| AssertOnMountThread(); |
| return install_attrs_->Set(name, data); |
| } |
| |
| bool UserDataAuth::InstallAttributesFinalize() { |
| AssertOnMountThread(); |
| bool result = install_attrs_->Finalize(); |
| DetectEnterpriseOwnership(); |
| return result; |
| } |
| |
| int UserDataAuth::InstallAttributesCount() { |
| AssertOnMountThread(); |
| return install_attrs_->Count(); |
| } |
| |
| bool UserDataAuth::InstallAttributesIsSecure() { |
| AssertOnMountThread(); |
| return install_attrs_->IsSecure(); |
| } |
| |
| InstallAttributes::Status UserDataAuth::InstallAttributesGetStatus() { |
| AssertOnMountThread(); |
| return install_attrs_->status(); |
| } |
| |
| // static |
| user_data_auth::InstallAttributesState |
| UserDataAuth::InstallAttributesStatusToProtoEnum( |
| InstallAttributes::Status status) { |
| static const std::unordered_map<InstallAttributes::Status, |
| user_data_auth::InstallAttributesState> |
| state_map = {{InstallAttributes::Status::kUnknown, |
| user_data_auth::InstallAttributesState::UNKNOWN}, |
| {InstallAttributes::Status::kTpmNotOwned, |
| user_data_auth::InstallAttributesState::TPM_NOT_OWNED}, |
| {InstallAttributes::Status::kFirstInstall, |
| user_data_auth::InstallAttributesState::FIRST_INSTALL}, |
| {InstallAttributes::Status::kValid, |
| user_data_auth::InstallAttributesState::VALID}, |
| {InstallAttributes::Status::kInvalid, |
| user_data_auth::InstallAttributesState::INVALID}}; |
| if (state_map.count(status) != 0) { |
| return state_map.at(status); |
| } |
| |
| NOTREACHED(); |
| // Return is added so compiler doesn't complain. |
| return user_data_auth::InstallAttributesState::INVALID; |
| } |
| |
| void UserDataAuth::OnFingerprintStartAuthSessionResp( |
| base::OnceCallback< |
| void(const user_data_auth::StartFingerprintAuthSessionReply&)> on_done, |
| bool success) { |
| AssertOnMountThread(); |
| VLOG(1) << "Start fingerprint auth session result: " << success; |
| user_data_auth::StartFingerprintAuthSessionReply reply; |
| if (!success) { |
| reply.set_error(user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_FINGERPRINT_ERROR_INTERNAL); |
| } |
| std::move(on_done).Run(reply); |
| } |
| |
| void UserDataAuth::StartFingerprintAuthSession( |
| const user_data_auth::StartFingerprintAuthSessionRequest& request, |
| base::OnceCallback<void( |
| const user_data_auth::StartFingerprintAuthSessionReply&)> on_done) { |
| AssertOnMountThread(); |
| user_data_auth::StartFingerprintAuthSessionReply reply; |
| |
| if (!request.has_account_id()) { |
| LOG(ERROR) << "StartFingerprintAuthSessionRequest must have account_id"; |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| std::move(on_done).Run(reply); |
| return; |
| } |
| |
| Username account_id = GetAccountId(request.account_id()); |
| if (account_id->empty()) { |
| LOG(ERROR) |
| << "StartFingerprintAuthSessionRequest must have vaid account_id."; |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| std::move(on_done).Run(reply); |
| return; |
| } |
| |
| if (!fingerprint_manager_) { |
| // Fingerprint manager failed to initialize, or the device may not support |
| // fingerprint auth at all. |
| reply.set_error( |
| user_data_auth::CRYPTOHOME_ERROR_FINGERPRINT_ERROR_INTERNAL); |
| std::move(on_done).Run(reply); |
| return; |
| } |
| |
| const ObfuscatedUsername obfuscated_username = SanitizeUserName(account_id); |
| if (!homedirs_->Exists(obfuscated_username)) { |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND); |
| std::move(on_done).Run(reply); |
| return; |
| } |
| |
| fingerprint_manager_->StartAuthSessionAsyncForUser( |
| obfuscated_username, |
| base::BindOnce(&UserDataAuth::OnFingerprintStartAuthSessionResp, |
| base::Unretained(this), std::move(on_done))); |
| } |
| |
| user_data_auth::CryptohomeErrorCode UserDataAuth::EndFingerprintAuthSession() { |
| AssertOnMountThread(); |
| |
| if (!fingerprint_manager_) { |
| // Fingerprint manager failed to initialize, or the device may not support |
| // fingerprint auth at all. |
| return user_data_auth::CRYPTOHOME_ERROR_FINGERPRINT_ERROR_INTERNAL; |
| } |
| |
| fingerprint_manager_->EndAuthSession(); |
| return user_data_auth::CRYPTOHOME_ERROR_NOT_SET; |
| } |
| |
| user_data_auth::GetWebAuthnSecretReply UserDataAuth::GetWebAuthnSecret( |
| const user_data_auth::GetWebAuthnSecretRequest& request) { |
| AssertOnMountThread(); |
| user_data_auth::GetWebAuthnSecretReply reply; |
| |
| if (!request.has_account_id()) { |
| LOG(ERROR) << "GetWebAuthnSecretRequest must have account_id."; |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| return reply; |
| } |
| |
| Username account_id = GetAccountId(request.account_id()); |
| if (account_id->empty()) { |
| LOG(ERROR) << "GetWebAuthnSecretRequest must have valid account_id."; |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| return reply; |
| } |
| |
| UserSession* const session = sessions_->Find(account_id); |
| std::unique_ptr<brillo::SecureBlob> secret; |
| if (session) { |
| secret = session->GetWebAuthnSecret(); |
| } |
| if (!secret) { |
| LOG(ERROR) << "Failed to get WebAuthn secret."; |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_KEY_NOT_FOUND); |
| return reply; |
| } |
| |
| reply.set_webauthn_secret(secret->to_string()); |
| return reply; |
| } |
| |
| user_data_auth::GetWebAuthnSecretHashReply UserDataAuth::GetWebAuthnSecretHash( |
| const user_data_auth::GetWebAuthnSecretHashRequest& request) { |
| AssertOnMountThread(); |
| user_data_auth::GetWebAuthnSecretHashReply reply; |
| |
| if (!request.has_account_id()) { |
| LOG(ERROR) << "GetWebAuthnSecretHashRequest must have account_id."; |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| return reply; |
| } |
| |
| Username account_id = GetAccountId(request.account_id()); |
| if (account_id->empty()) { |
| LOG(ERROR) << "GetWebAuthnSecretHashRequest must have valid account_id."; |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| return reply; |
| } |
| |
| const UserSession* const session = sessions_->Find(account_id); |
| brillo::SecureBlob secret_hash; |
| if (session) { |
| secret_hash = session->GetWebAuthnSecretHash(); |
| } |
| if (secret_hash.empty()) { |
| LOG(ERROR) << "Failed to get WebAuthn secret hash."; |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_KEY_NOT_FOUND); |
| return reply; |
| } |
| |
| reply.set_webauthn_secret_hash(secret_hash.to_string()); |
| return reply; |
| } |
| |
| user_data_auth::GetHibernateSecretReply UserDataAuth::GetHibernateSecret( |
| const user_data_auth::GetHibernateSecretRequest& request) { |
| AssertOnMountThread(); |
| user_data_auth::GetHibernateSecretReply reply; |
| |
| // If there's an auth_session_id, use that to create the hibernate |
| // secret on demand (otherwise it's not available until later). |
| if (!request.auth_session_id().empty()) { |
| CryptohomeStatusOr<InUseAuthSession> auth_session_status = |
| GetAuthenticatedAuthSession(request.auth_session_id()); |
| if (!auth_session_status.ok()) { |
| LOG(ERROR) << "Invalid AuthSession for HibernateSecret."; |
| reply.set_error(user_data_auth::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN); |
| return reply; |
| } |
| |
| std::unique_ptr<brillo::SecureBlob> secret = |
| auth_session_status.value()->GetHibernateSecret(); |
| |
| reply.set_hibernate_secret(secret->to_string()); |
| return reply; |
| } |
| |
| LOG(INFO) << "Getting the hibernate secret via legacy account_id"; |
| if (!request.has_account_id()) { |
| LOG(ERROR) << "GetHibernateSecretRequest must have account_id."; |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| return reply; |
| } |
| |
| Username account_id = GetAccountId(request.account_id()); |
| if (account_id->empty()) { |
| LOG(ERROR) << "GetHibernateSecretRequest must have valid account_id."; |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| return reply; |
| } |
| |
| UserSession* const session = sessions_->Find(account_id); |
| std::unique_ptr<brillo::SecureBlob> secret; |
| if (session) { |
| secret = session->GetHibernateSecret(); |
| } |
| if (!secret) { |
| LOG(ERROR) << "Failed to get hibernate secret hash."; |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_KEY_NOT_FOUND); |
| return reply; |
| } |
| |
| reply.set_hibernate_secret(secret->to_string()); |
| return reply; |
| } |
| |
| user_data_auth::GetEncryptionInfoReply UserDataAuth::GetEncryptionInfo( |
| const user_data_auth::GetEncryptionInfoRequest& request) { |
| AssertOnMountThread(); |
| user_data_auth::GetEncryptionInfoReply reply; |
| |
| const bool state = homedirs_->KeylockerForStorageEncryptionEnabled(); |
| reply.set_error(user_data_auth::CRYPTOHOME_ERROR_NOT_SET); |
| reply.set_keylocker_supported(state); |
| return reply; |
| } |
| |
| user_data_auth::CryptohomeErrorCode |
| UserDataAuth::GetFirmwareManagementParameters( |
| user_data_auth::FirmwareManagementParameters* fwmp) { |
| AssertOnOriginThread(); |
| if (!firmware_management_parameters_->Load()) { |
| return user_data_auth:: |
| CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID; |
| } |
| |
| uint32_t flags; |
| if (firmware_management_parameters_->GetFlags(&flags)) { |
| fwmp->set_flags(flags); |
| } else { |
| LOG(WARNING) |
| << "Failed to GetFlags() for GetFirmwareManagementParameters()."; |
| return user_data_auth:: |
| CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID; |
| } |
| |
| std::vector<uint8_t> hash; |
| if (firmware_management_parameters_->GetDeveloperKeyHash(&hash)) { |
| *fwmp->mutable_developer_key_hash() = {hash.begin(), hash.end()}; |
| } else { |
| LOG(WARNING) << "Failed to GetDeveloperKeyHash() for " |
| "GetFirmwareManagementParameters()."; |
| return user_data_auth:: |
| CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID; |
| } |
| |
| return user_data_auth::CRYPTOHOME_ERROR_NOT_SET; |
| } |
| |
| user_data_auth::CryptohomeErrorCode |
| UserDataAuth::SetFirmwareManagementParameters( |
| const user_data_auth::FirmwareManagementParameters& fwmp) { |
| AssertOnOriginThread(); |
| |
| if (!firmware_management_parameters_->Create()) { |
| return user_data_auth:: |
| CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_CANNOT_STORE; |
| } |
| |
| uint32_t flags = fwmp.flags(); |
| std::unique_ptr<std::vector<uint8_t>> hash; |
| |
| if (!fwmp.developer_key_hash().empty()) { |
| hash.reset(new std::vector<uint8_t>(fwmp.developer_key_hash().begin(), |
| fwmp.developer_key_hash().end())); |
| } |
| |
| if (!firmware_management_parameters_->Store(flags, hash.get())) { |
| return user_data_auth:: |
| CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_CANNOT_STORE; |
| } |
| |
| return user_data_auth::CRYPTOHOME_ERROR_NOT_SET; |
| } |
| |
| bool UserDataAuth::RemoveFirmwareManagementParameters() { |
| AssertOnOriginThread(); |
| return firmware_management_parameters_->Destroy(); |
| } |
| |
| const brillo::SecureBlob& UserDataAuth::GetSystemSalt() { |
| AssertOnOriginThread(); |
| DCHECK_NE(system_salt_.size(), 0) |
| << "Cannot call GetSystemSalt before initialization"; |
| return system_salt_; |
| } |
| |
| bool UserDataAuth::UpdateCurrentUserActivityTimestamp(int time_shift_sec) { |
| AssertOnMountThread(); |
| // We are touching the sessions object, so we'll need to be on mount thread. |
| |
| bool success = true; |
| for (const auto& [username, session] : *sessions_) { |
| const ObfuscatedUsername obfuscated_username = SanitizeUserName(username); |
| // Inactive session is not current and ephemerals should not have ts since |
| // they do not affect disk space use and do not participate in disk |
| // cleaning. |
| if (!session.IsActive() || session.IsEphemeral()) { |
| continue; |
| } |
| success &= user_activity_timestamp_manager_->UpdateTimestamp( |
| obfuscated_username, base::Seconds(time_shift_sec)); |
| } |
| |
| return success; |
| } |
| |
| bool UserDataAuth::GetRsuDeviceId(std::string* rsu_device_id) { |
| AssertOnOriginThread(); |
| |
| hwsec::StatusOr<brillo::Blob> rsu = hwsec_->GetRsuDeviceId(); |
| if (!rsu.ok()) { |
| LOG(INFO) << "Failed to get RSU device ID: " << rsu.status(); |
| return false; |
| } |
| |
| *rsu_device_id = brillo::BlobToString(rsu.value()); |
| return true; |
| } |
| |
| bool UserDataAuth::RequiresPowerwash() { |
| AssertOnOriginThread(); |
| const bool is_powerwash_required = !crypto_->CanUnsealWithUserAuth(); |
| return is_powerwash_required; |
| } |
| |
| user_data_auth::CryptohomeErrorCode |
| UserDataAuth::LockToSingleUserMountUntilReboot( |
| const AccountIdentifier& account_id) { |
| AssertOnOriginThread(); |
| const ObfuscatedUsername obfuscated_username = |
| SanitizeUserName(GetAccountId(account_id)); |
| |
| homedirs_->SetLockedToSingleUser(); |
| brillo::Blob pcr_value; |
| |
| hwsec::StatusOr<bool> is_current_user_set = hwsec_->IsCurrentUserSet(); |
| if (!is_current_user_set.ok()) { |
| LOG(ERROR) << "Failed to get current user status for " |
| "LockToSingleUserMountUntilReboot(): " |
| << is_current_user_set.status(); |
| return user_data_auth::CRYPTOHOME_ERROR_FAILED_TO_READ_PCR; |
| } |
| |
| if (is_current_user_set.value()) { |
| return user_data_auth::CRYPTOHOME_ERROR_PCR_ALREADY_EXTENDED; |
| } |
| |
| if (hwsec::Status status = hwsec_->SetCurrentUser(*obfuscated_username); |
| !status.ok()) { |
| LOG(ERROR) |
| << "Failed to set current user for LockToSingleUserMountUntilReboot(): " |
| << status; |
| return user_data_auth::CRYPTOHOME_ERROR_FAILED_TO_EXTEND_PCR; |
| } |
| |
| return user_data_auth::CRYPTOHOME_ERROR_NOT_SET; |
| } |
| |
| bool UserDataAuth::OwnerUserExists() { |
| AssertOnOriginThread(); |
| Username owner; |
| return homedirs_->GetPlainOwner(&owner); |
| } |
| |
| std::string UserDataAuth::GetStatusString() { |
| AssertOnMountThread(); |
| |
| base::Value mounts(base::Value::Type::LIST); |
| for (const auto& [unused, session] : *sessions_) { |
| mounts.Append(session.GetStatus()); |
| } |
| |
| base::Value dv(base::Value::Type::DICT); |
| dv.SetKey("mounts", std::move(mounts)); |
| std::string json; |
| base::JSONWriter::WriteWithOptions(dv, base::JSONWriter::OPTIONS_PRETTY_PRINT, |
| &json); |
| return json; |
| } |
| |
| void UserDataAuth::StartAuthSession( |
| user_data_auth::StartAuthSessionRequest request, |
| base::OnceCallback<void(const user_data_auth::StartAuthSessionReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| |
| user_data_auth::StartAuthSessionReply reply; |
| |
| if (request.intent() == user_data_auth::AUTH_INTENT_UNSPECIFIED) { |
| // TODO(b/240596931): Stop allowing the UNSPECIFIED value after Chrome's |
| // change to populate this field lives for some time. |
| request.set_intent(user_data_auth::AUTH_INTENT_DECRYPT); |
| } |
| std::optional<AuthIntent> auth_intent = AuthIntentFromProto(request.intent()); |
| if (!auth_intent.has_value()) { |
| ReplyWithError( |
| std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthNoIntentInStartAuthSession), |
| ErrorActionSet( |
| {ErrorAction::kDevCheckUnexpectedState, ErrorAction::kReboot}), |
| user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_MOUNT_FATAL)); |
| return; |
| } |
| |
| CryptohomeStatusOr<InUseAuthSession> auth_session_status = |
| auth_session_manager_->CreateAuthSession( |
| GetAccountId(request.account_id()), request.flags(), |
| auth_intent.value()); |
| if (!auth_session_status.ok()) { |
| ReplyWithError( |
| std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthCreateFailedInStartAuthSession), |
| ErrorActionSet( |
| {ErrorAction::kDevCheckUnexpectedState, ErrorAction::kReboot})) |
| .Wrap(std::move(auth_session_status).status())); |
| return; |
| } |
| AuthSession* auth_session = auth_session_status.value().Get(); |
| |
| reply.set_auth_session_id(auth_session->serialized_token()); |
| reply.set_user_exists(auth_session->user_exists()); |
| |
| if (auth_session->auth_factor_map().empty() && |
| (auth_session->user_exists() && !auth_session->ephemeral_user())) { |
| ReplyWithError( |
| std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthNotConfiguredInStartAuthSession), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kDeleteVault, ErrorAction::kAuth}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_UNUSABLE_VAULT)); |
| return; |
| } |
| |
| // Discover any available auth factors from the AuthSession. |
| std::set<std::string> listed_auth_factor_labels; |
| for (AuthFactorMap::ValueView stored_auth_factor : |
| auth_session->auth_factor_map()) { |
| const AuthFactor& auth_factor = stored_auth_factor.auth_factor(); |
| |
| std::optional<user_data_auth::AuthFactor> proto_factor = GetAuthFactorProto( |
| auth_factor.metadata(), auth_factor.type(), auth_factor.label()); |
| if (proto_factor.has_value()) { |
| // Only output one factor per label. |
| auto [unused, was_inserted] = |
| listed_auth_factor_labels.insert(auth_factor.label()); |
| if (!was_inserted) { |
| continue; |
| } |
| |
| // Only populate reply with AuthFactors that support the intended form of |
| // authentication. |
| auto supported_intents = |
| auth_block_utility_->GetSupportedIntentsFromState( |
| auth_factor.auth_block_state()); |
| std::optional<AuthIntent> requested_intent = |
| AuthIntentFromProto(request.intent()); |
| if (requested_intent && supported_intents.contains(*requested_intent)) { |
| *reply.add_auth_factors() = std::move(proto_factor.value()); |
| } |
| } |
| } |
| |
| // The associated UserSession (if there is one) may also have some factors of |
| // its own, via verifiers. However, these are only available if the request is |
| // for a verify-only session. |
| // |
| // This is done after the persistent factors are looked up because if a |
| // persistent factor also has a verifier then we only want output from the |
| // persistent factor data. |
| if (request.intent() == user_data_auth::AUTH_INTENT_VERIFY_ONLY) { |
| if (UserSession* user_session = |
| sessions_->Find(GetAccountId(request.account_id()))) { |
| for (const CredentialVerifier* verifier : |
| user_session->GetCredentialVerifiers()) { |
| if (auto proto_factor = GetAuthFactorProto( |
| verifier->auth_factor_metadata(), verifier->auth_factor_type(), |
| verifier->auth_factor_label())) { |
| auto [unused, was_inserted] = |
| listed_auth_factor_labels.insert(verifier->auth_factor_label()); |
| if (was_inserted) { |
| *reply.add_auth_factors() = std::move(*proto_factor); |
| } |
| } |
| } |
| } |
| } |
| |
| ReplyWithError(std::move(on_done), reply, OkStatus<CryptohomeError>()); |
| } |
| |
| void UserDataAuth::SetKeyDataForUserSession(AuthSession* auth_session, |
| bool override_existing_data) { |
| DCHECK(auth_session); |
| |
| UserSession* const session = sessions_->Find(auth_session->username()); |
| // Ensure valid session. |
| if (!session) { |
| LOG(WARNING) << "SetCredential failed as user session does not exist"; |
| return; |
| } |
| |
| // Check the user is already mounted. |
| if (!session->IsActive()) { |
| LOG(WARNING) << "SetCredential failed as user session is not active."; |
| return; |
| } |
| |
| // Check if both UserSession and AuthSession match. |
| if (session->IsEphemeral() != auth_session->ephemeral_user()) { |
| LOG(WARNING) << "SetCredential failed as user session does not match " |
| "auth_session ephemeral status user: " |
| << auth_session->obfuscated_username(); |
| return; |
| } |
| |
| // Ensure AuthSession is authenticated. |
| if (auth_session->status() != AuthStatus::kAuthStatusAuthenticated) { |
| LOG(WARNING) << "SetCredential failed as auth session is not authenticated " |
| "for user: " |
| << auth_session->obfuscated_username(); |
| return; |
| } |
| |
| if (!session->HasCredentialVerifier() || override_existing_data) { |
| session->set_key_data(auth_session->current_key_data()); |
| } |
| } |
| |
| void UserDataAuth::OnAddAuthFactorFinished(AuthSession* auth_session, |
| StatusCallback on_done, |
| CryptohomeStatus status) { |
| if (status.ok()) { |
| SetKeyDataForUserSession(auth_session, |
| /*override_existing_data=*/false); |
| } |
| std::move(on_done).Run(std::move(status)); |
| } |
| |
| void UserDataAuth::OnUpdateAuthFactorFinished(AuthSession* auth_session, |
| StatusCallback on_done, |
| CryptohomeStatus status) { |
| if (status.ok()) { |
| SetKeyDataForUserSession(auth_session, |
| /*override_existing_data=*/true); |
| } |
| std::move(on_done).Run(std::move(status)); |
| } |
| |
| void UserDataAuth::InvalidateAuthSession( |
| user_data_auth::InvalidateAuthSessionRequest request, |
| base::OnceCallback<void(const user_data_auth::InvalidateAuthSessionReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| |
| user_data_auth::InvalidateAuthSessionReply reply; |
| if (auth_session_manager_->RemoveAuthSession(request.auth_session_id())) { |
| LOG(INFO) << "AuthSession: invalidated."; |
| } |
| |
| ReplyWithError(std::move(on_done), reply, OkStatus<CryptohomeError>()); |
| } |
| |
| void UserDataAuth::ExtendAuthSession( |
| user_data_auth::ExtendAuthSessionRequest request, |
| base::OnceCallback<void(const user_data_auth::ExtendAuthSessionReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| |
| user_data_auth::ExtendAuthSessionReply reply; |
| // Fetch only authenticated authsession. This is because the timer only runs |
| // AuthSession is authenticated. If the timer is not running, then there is |
| // nothing to extend. |
| CryptohomeStatusOr<InUseAuthSession> auth_session_status = |
| GetAuthenticatedAuthSession(request.auth_session_id()); |
| if (!auth_session_status.ok()) { |
| ReplyWithError(std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthSessionNotFoundInExtendAuthSession)) |
| .Wrap(std::move(auth_session_status).status())); |
| return; |
| } |
| AuthSession* auth_session = auth_session_status.value().Get(); |
| |
| // Extend specified AuthSession. |
| auto timer_extension = base::Seconds(request.extension_duration()); |
| CryptohomeStatus ret = auth_session->ExtendTimeoutTimer(timer_extension); |
| |
| CryptohomeStatus err = OkStatus<CryptohomeError>(); |
| if (!ret.ok()) { |
| // TODO(b/229688435): Wrap the error after AuthSession is migrated to use |
| // CryptohomeError. |
| err = |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthExtendFailedInExtendAuthSession)) |
| .Wrap(std::move(ret)); |
| } |
| reply.set_seconds_left(auth_session->GetRemainingTime().InSeconds()); |
| ReplyWithError(std::move(on_done), reply, std::move(err)); |
| } |
| |
| CryptohomeStatusOr<InUseAuthSession> UserDataAuth::GetAuthenticatedAuthSession( |
| const std::string& auth_session_id) { |
| AssertOnMountThread(); |
| |
| // Check if the token refers to a valid AuthSession. |
| InUseAuthSession auth_session = |
| auth_session_manager_->FindAuthSession(auth_session_id); |
| CryptohomeStatus auth_session_status = auth_session.AuthSessionStatus(); |
| if (!auth_session_status.ok()) { |
| LOG(ERROR) << "AuthSession not found."; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthSessionNotFoundInGetAuthedAS), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kReboot}), |
| user_data_auth::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN) |
| .Wrap(std::move(auth_session_status).status()); |
| } |
| |
| // Check if the AuthSession is properly authenticated. |
| if (auth_session->status() != AuthStatus::kAuthStatusAuthenticated) { |
| LOG(ERROR) << "AuthSession is not authenticated."; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthSessionNotAuthedInGetAuthedAS), |
| ErrorActionSet( |
| {ErrorAction::kDevCheckUnexpectedState, ErrorAction::kReboot}), |
| user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| } |
| |
| return auth_session; |
| } |
| |
| ObfuscatedUsername UserDataAuth::SanitizedUserNameForSession( |
| const std::string& auth_session_id) { |
| InUseAuthSession auth_session = |
| auth_session_manager_->FindAuthSession(auth_session_id); |
| if (!auth_session.AuthSessionStatus().ok()) { |
| return ObfuscatedUsername(); |
| } |
| return auth_session->obfuscated_username(); |
| } |
| |
| CryptohomeStatusOr<UserSession*> UserDataAuth::GetMountableUserSession( |
| AuthSession* auth_session) { |
| AssertOnMountThread(); |
| |
| const ObfuscatedUsername& obfuscated_username = |
| auth_session->obfuscated_username(); |
| |
| // Check no guest is mounted. |
| UserSession* const guest_session = sessions_->Find(guest_user_); |
| if (guest_session && guest_session->IsActive()) { |
| LOG(ERROR) << "Can not mount non-anonymous while guest session is active."; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthGuestAlreadyMountedInGetMountableUS), |
| ErrorActionSet({ErrorAction::kReboot}), |
| user_data_auth::CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY); |
| } |
| |
| // Check the user is not already mounted. |
| UserSession* const session = GetOrCreateUserSession(auth_session->username()); |
| if (session->IsActive()) { |
| LOG(ERROR) << "User is already mounted: " << obfuscated_username; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthSessionAlreadyMountedInGetMountableUS), |
| ErrorActionSet({ErrorAction::kReboot}), |
| user_data_auth::CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY); |
| } |
| |
| return session; |
| } |
| |
| void UserDataAuth::PreMountHook(const ObfuscatedUsername& obfuscated_username) { |
| AssertOnMountThread(); |
| |
| LOG(INFO) << "Started mounting for: " << obfuscated_username; |
| |
| // Any non-guest mount attempt triggers InstallAttributes finalization. |
| // The return value is ignored as it is possible we're pre-ownership. |
| // The next login will assure finalization if possible. |
| if (install_attrs_->status() == InstallAttributes::Status::kFirstInstall) { |
| std::ignore = install_attrs_->Finalize(); |
| } |
| // Remove all existing cryptohomes, except for the owner's one, if the |
| // ephemeral users policy is on. |
| // Note that a fresh policy value is read here, which in theory can conflict |
| // with the one used for calculation of |mount_args.is_ephemeral|. However, |
| // this inconsistency (whose probability is anyway pretty low in practice) |
| // should only lead to insignificant transient glitches, like an attempt to |
| // mount a non existing anymore cryptohome. |
| if (homedirs_->AreEphemeralUsersEnabled()) { |
| homedirs_->RemoveNonOwnerCryptohomes(); |
| } |
| } |
| |
| void UserDataAuth::PostMountHook(UserSession* user_session, |
| const MountStatus& status) { |
| AssertOnMountThread(); |
| |
| if (!status.ok()) { |
| LOG(ERROR) << "Finished mounting with status code: " << status; |
| return; |
| } |
| LOG(INFO) << "Mount succeeded."; |
| InitializePkcs11(user_session); |
| } |
| |
| EncryptedContainerType UserDataAuth::DbusEncryptionTypeToContainerType( |
| user_data_auth::VaultEncryptionType type) { |
| switch (type) { |
| case user_data_auth::VaultEncryptionType::CRYPTOHOME_VAULT_ENCRYPTION_ANY: |
| return EncryptedContainerType::kUnknown; |
| case user_data_auth::VaultEncryptionType:: |
| CRYPTOHOME_VAULT_ENCRYPTION_ECRYPTFS: |
| return EncryptedContainerType::kEcryptfs; |
| case user_data_auth::VaultEncryptionType:: |
| CRYPTOHOME_VAULT_ENCRYPTION_FSCRYPT: |
| return EncryptedContainerType::kFscrypt; |
| case user_data_auth::VaultEncryptionType:: |
| CRYPTOHOME_VAULT_ENCRYPTION_DMCRYPT: |
| return EncryptedContainerType::kDmcrypt; |
| default: |
| // Default cuz proto3 enum sentinels, that's why -_- |
| return EncryptedContainerType::kUnknown; |
| } |
| } |
| |
| void UserDataAuth::PrepareGuestVault( |
| user_data_auth::PrepareGuestVaultRequest request, |
| base::OnceCallback<void(const user_data_auth::PrepareGuestVaultReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| |
| LOG(INFO) << "Preparing guest vault"; |
| user_data_auth::PrepareGuestVaultReply reply; |
| CryptohomeStatus status = PrepareGuestVaultImpl(); |
| reply.set_sanitized_username(*SanitizeUserName(guest_user_)); |
| ReplyWithError(std::move(on_done), reply, status); |
| return; |
| } |
| |
| void UserDataAuth::PrepareEphemeralVault( |
| user_data_auth::PrepareEphemeralVaultRequest request, |
| base::OnceCallback<void(const user_data_auth::PrepareEphemeralVaultReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| |
| LOG(INFO) << "Preparing ephemeral vault"; |
| user_data_auth::PrepareEphemeralVaultReply reply; |
| CryptohomeStatus status = |
| PrepareEphemeralVaultImpl(request.auth_session_id()); |
| reply.set_sanitized_username( |
| *SanitizedUserNameForSession(request.auth_session_id())); |
| ReplyWithError(std::move(on_done), reply, status); |
| } |
| |
| void UserDataAuth::PreparePersistentVault( |
| user_data_auth::PreparePersistentVaultRequest request, |
| base::OnceCallback<void(const user_data_auth::PreparePersistentVaultReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| |
| LOG(INFO) << "Preparing persistent vault"; |
| CryptohomeVault::Options options = { |
| .force_type = |
| DbusEncryptionTypeToContainerType(request.encryption_type()), |
| .block_ecryptfs = request.block_ecryptfs(), |
| }; |
| CryptohomeStatus status = |
| PreparePersistentVaultImpl(request.auth_session_id(), options); |
| |
| const ObfuscatedUsername obfuscated_username = |
| SanitizedUserNameForSession(request.auth_session_id()); |
| if (status.ok() && !obfuscated_username->empty()) { |
| // Send UMA with VK stats once per successful mount operation. |
| keyset_management_->RecordAllVaultKeysetMetrics(obfuscated_username); |
| } |
| user_data_auth::PreparePersistentVaultReply reply; |
| reply.set_sanitized_username(*obfuscated_username); |
| ReplyWithError(std::move(on_done), reply, status); |
| } |
| |
| void UserDataAuth::PrepareVaultForMigration( |
| user_data_auth::PrepareVaultForMigrationRequest request, |
| base::OnceCallback< |
| void(const user_data_auth::PrepareVaultForMigrationReply&)> on_done) { |
| AssertOnMountThread(); |
| |
| LOG(INFO) << "Preparing vault for migration"; |
| CryptohomeVault::Options options = { |
| .migrate = true, |
| }; |
| user_data_auth::PrepareVaultForMigrationReply reply; |
| CryptohomeStatus status = |
| PreparePersistentVaultImpl(request.auth_session_id(), options); |
| reply.set_sanitized_username( |
| *SanitizedUserNameForSession(request.auth_session_id())); |
| ReplyWithError(std::move(on_done), reply, status); |
| } |
| |
| void UserDataAuth::CreatePersistentUser( |
| user_data_auth::CreatePersistentUserRequest request, |
| base::OnceCallback<void(const user_data_auth::CreatePersistentUserReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| |
| LOG(INFO) << "Creating persistent user"; |
| user_data_auth::CreatePersistentUserReply reply; |
| |
| // Record current time for timing how long CreatePersistentUserImpl will |
| // take. |
| auto start_time = base::TimeTicks::Now(); |
| |
| StatusChain<CryptohomeError> ret = |
| CreatePersistentUserImpl(request.auth_session_id()); |
| |
| ReportTimerDuration(kCreatePersistentUserTimer, start_time, ""); |
| |
| reply.set_sanitized_username( |
| *SanitizedUserNameForSession(request.auth_session_id())); |
| ReplyWithError(std::move(on_done), reply, ret); |
| } |
| |
| CryptohomeStatus UserDataAuth::PrepareGuestVaultImpl() { |
| AssertOnMountThread(); |
| |
| // If there are no active sessions, attempt to account for cryptohome restarts |
| // after crashing. |
| if (sessions_->size() != 0 || CleanUpStaleMounts(false)) { |
| LOG(ERROR) << "Can not mount guest while other sessions are active."; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthOtherSessionActiveInPrepareGuestVault), |
| ErrorActionSet({ErrorAction::kReboot}), |
| user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_MOUNT_FATAL); |
| } |
| |
| UserSession* const session = GetOrCreateUserSession(guest_user_); |
| |
| LOG(INFO) << "Started mounting for guest"; |
| ReportTimerStart(kMountGuestExTimer); |
| MountStatus status = session->MountGuest(); |
| ReportTimerStop(kMountGuestExTimer); |
| if (!status.ok()) { |
| DCHECK(status->mount_error() != MOUNT_ERROR_NONE); |
| LOG(ERROR) << "Finished mounting with status code: " |
| << status->mount_error(); |
| RemoveInactiveUserSession(guest_user_); |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthMountFailedInPrepareGuestVault)) |
| .Wrap(std::move(status)); |
| } |
| LOG(INFO) << "Mount succeeded."; |
| return OkStatus<CryptohomeError>(); |
| } |
| |
| CryptohomeStatus UserDataAuth::PrepareEphemeralVaultImpl( |
| const std::string& auth_session_id) { |
| AssertOnMountThread(); |
| |
| // If there are no active sessions, attempt to account for cryptohome restarts |
| // after crashing. |
| if (sessions_->size() != 0 || CleanUpStaleMounts(false)) { |
| LOG(ERROR) << "Can not mount ephemeral while other sessions are active."; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthOtherSessionActiveInPrepareEphemeralVault), |
| ErrorActionSet({ErrorAction::kReboot}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY); |
| } |
| |
| InUseAuthSession auth_session = |
| auth_session_manager_->FindAuthSession(auth_session_id); |
| CryptohomeStatus auth_session_status = auth_session.AuthSessionStatus(); |
| if (!auth_session_status.ok()) { |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthNoAuthSessionInPrepareEphemeralVault), |
| ErrorActionSet( |
| {ErrorAction::kDevCheckUnexpectedState, ErrorAction::kReboot}), |
| user_data_auth::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN); |
| } |
| |
| if (!auth_session->ephemeral_user()) { |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthNonEphemeralAuthSessionInPrepareEphemeralVault), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kReboot, ErrorAction::kPowerwash}), |
| user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| } |
| |
| CryptohomeStatusOr<UserSession*> session_status = |
| GetMountableUserSession(auth_session.Get()); |
| if (!session_status.ok()) { |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthGetSessionFailedInPrepareEphemeralVault)) |
| .Wrap(std::move(session_status).status()); |
| } |
| |
| PreMountHook(auth_session->obfuscated_username()); |
| ReportTimerStart(kMountExTimer); |
| MountStatus mount_status = |
| session_status.value()->MountEphemeral(auth_session->username()); |
| ReportTimerStop(kMountExTimer); |
| PostMountHook(session_status.value(), mount_status); |
| if (!mount_status.ok()) { |
| RemoveInactiveUserSession(auth_session->username()); |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthMountFailedInPrepareEphemeralVault)) |
| .Wrap(std::move(mount_status).status()); |
| } |
| |
| // Let the auth session perform any finalization operations for a newly |
| // created user. |
| CryptohomeStatus ret = auth_session->OnUserCreated(); |
| if (!ret.ok()) { |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthFinalizeFailedInPrepareEphemeralVault)) |
| .Wrap(std::move(ret)); |
| } |
| return OkStatus<CryptohomeError>(); |
| } |
| |
| CryptohomeStatus UserDataAuth::PreparePersistentVaultImpl( |
| const std::string& auth_session_id, |
| const CryptohomeVault::Options& vault_options) { |
| AssertOnMountThread(); |
| |
| // If there are no active sessions, attempt to account for cryptohome restarts |
| // after crashing. |
| if (sessions_->empty()) { |
| CleanUpStaleMounts(false); |
| } |
| |
| CryptohomeStatusOr<InUseAuthSession> auth_session_status = |
| GetAuthenticatedAuthSession(auth_session_id); |
| if (!auth_session_status.ok()) { |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthNoAuthSessionInPreparePersistentVault)) |
| .Wrap(std::move(auth_session_status).status()); |
| } |
| |
| if (auth_session_status.value()->ephemeral_user()) { |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthEphemeralAuthSessionAttemptPreparePersistentVault), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kDeleteVault, ErrorAction::kReboot, |
| ErrorAction::kPowerwash}), |
| user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| } |
| |
| const ObfuscatedUsername& obfuscated_username = |
| auth_session_status.value()->obfuscated_username(); |
| if (!homedirs_->Exists(obfuscated_username)) { |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthNonExistentInPreparePersistentVault), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kDeleteVault, ErrorAction::kReboot, |
| ErrorAction::kPowerwash}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND); |
| } |
| |
| CryptohomeStatusOr<UserSession*> session_status = |
| GetMountableUserSession(auth_session_status.value().Get()); |
| if (!session_status.ok()) { |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthGetSessionFailedInPreparePersistentVault)) |
| .Wrap(std::move(session_status).status()); |
| } |
| |
| PreMountHook(obfuscated_username); |
| if (low_disk_space_handler_) { |
| low_disk_space_handler_->disk_cleanup()->FreeDiskSpaceDuringLogin( |
| obfuscated_username); |
| } |
| ReportTimerStart(kMountExTimer); |
| MountStatus mount_status = session_status.value()->MountVault( |
| auth_session_status.value()->username(), |
| auth_session_status.value()->file_system_keyset(), vault_options); |
| ReportTimerStop(kMountExTimer); |
| PostMountHook(session_status.value(), mount_status); |
| if (!mount_status.ok()) { |
| RemoveInactiveUserSession(auth_session_status.value()->username()); |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthMountFailedInPreparePersistentVault)) |
| .Wrap(std::move(mount_status).status()); |
| } |
| |
| SetKeyDataForUserSession(auth_session_status.value().Get(), |
| /*override_existing_data=*/false); |
| return OkStatus<CryptohomeError>(); |
| } |
| |
| CryptohomeStatus UserDataAuth::CreatePersistentUserImpl( |
| const std::string& auth_session_id) { |
| AssertOnMountThread(); |
| |
| InUseAuthSession auth_session = |
| auth_session_manager_->FindAuthSession(auth_session_id); |
| CryptohomeStatus auth_session_status = auth_session.AuthSessionStatus(); |
| if (!auth_session_status.ok()) { |
| LOG(ERROR) << "AuthSession not found."; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthSessionNotFoundInCreatePersistentUser), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kReboot}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN) |
| .Wrap(std::move(auth_session_status).status()); |
| } |
| |
| if (auth_session->ephemeral_user()) { |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthEphemeralAuthSessionAttemptCreatePersistentUser), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kReboot, ErrorAction::kPowerwash}), |
| user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_INVALID_ARGUMENT); |
| } |
| |
| const ObfuscatedUsername& obfuscated_username = |
| auth_session->obfuscated_username(); |
| |
| // This checks presence of the actual encrypted vault. We fail if Create is |
| // called while actual persistent vault is present. |
| auto exists_or = homedirs_->CryptohomeExists(obfuscated_username); |
| if (exists_or.ok() && exists_or.value()) { |
| LOG(ERROR) << "User already exists: " << obfuscated_username; |
| // TODO(b/208898186, dlunev): replace with a more appropriate error |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthUserExistsInCreatePersistentUser), |
| ErrorActionSet( |
| {ErrorAction::kDevCheckUnexpectedState, ErrorAction::kDeleteVault}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY); |
| } |
| |
| if (!exists_or.ok()) { |
| MountError mount_error = exists_or.status()->error(); |
| LOG(ERROR) << "Failed to query vault existance for: " << obfuscated_username |
| << ", code: " << mount_error; |
| return MakeStatus<CryptohomeMountError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthCheckExistsFailedInCreatePersistentUser), |
| ErrorActionSet( |
| {ErrorAction::kDevCheckUnexpectedState, ErrorAction::kReboot}), |
| mount_error, MountErrorToCryptohomeError(mount_error)); |
| } |
| |
| // This check seems superfluous after the `HomeDirs::CryptohomeExists()` check |
| // above, but it can happen that the user directory exists without any vault |
| // in it. We perform both checks for completeness and also to distinguish |
| // between these two error cases in metrics and logs. |
| if (auth_session->user_exists()) { |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthUserDirExistsInCreatePersistentUser), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kDeleteVault, ErrorAction::kPowerwash}), |
| user_data_auth::CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY); |
| } |
| |
| // This checks and creates if missing the user's directory in shadow root. |
| // We need to disambiguate with vault presence, because it is possible that |
| // we have an empty shadow root directory for the user left behind after |
| // removing a profile (due to a bug or for some other reasons). To avoid weird |
| // failures in the case, just let the creation succeed, since the user is |
| // effectively not there. Eventually |Exists| will check for the presence of |
| // the USS/auth factors to determine if the user is intended to be there. |
| // This call will not create the actual volume (for efficiency, idempotency, |
| // and because that would require going the full sequence of mount and unmount |
| // because of ecryptfs possibility). |
| if (!homedirs_->Exists(obfuscated_username) && |
| !homedirs_->Create(auth_session->username())) { |
| LOG(ERROR) << "Failed to create shadow directory for: " |
| << obfuscated_username; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthCreateFailedInCreatePersistentUser), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kReboot, ErrorAction::kPowerwash}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| |
| // Let the auth session perform any finalization operations for a newly |
| // created user. |
| CryptohomeStatus ret = auth_session->OnUserCreated(); |
| if (!ret.ok()) { |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthFinalizeFailedInCreatePersistentUser)) |
| .Wrap(std::move(ret)); |
| } |
| return OkStatus<CryptohomeError>(); |
| } |
| |
| void UserDataAuth::AddAuthFactor( |
| user_data_auth::AddAuthFactorRequest request, |
| base::OnceCallback<void(const user_data_auth::AddAuthFactorReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| user_data_auth::AddAuthFactorReply reply; |
| CryptohomeStatusOr<InUseAuthSession> auth_session_status = |
| GetAuthenticatedAuthSession(request.auth_session_id()); |
| if (!auth_session_status.ok()) { |
| ReplyWithError( |
| std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthNoAuthSessionInAddAuthFactor)) |
| .Wrap(std::move(auth_session_status).status())); |
| return; |
| } |
| |
| // Populate the request auth factor with accurate sysinfo. |
| PopulateAuthFactorProtoWithSysinfo(*request.mutable_auth_factor()); |
| |
| StatusCallback on_add_auth_factor_finished = base::BindOnce( |
| &UserDataAuth::OnAddAuthFactorFinished, base::Unretained(this), |
| auth_session_status.value().Get(), |
| base::BindOnce(&ReplyWithStatus<user_data_auth::AddAuthFactorReply>, |
| std::move(on_done))); |
| auth_session_status.value()->AddAuthFactor( |
| request, std::move(on_add_auth_factor_finished)); |
| } |
| |
| void UserDataAuth::AuthenticateAuthFactor( |
| user_data_auth::AuthenticateAuthFactorRequest request, |
| base::OnceCallback<void(const user_data_auth::AuthenticateAuthFactorReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| user_data_auth::AuthenticateAuthFactorReply reply; |
| |
| InUseAuthSession auth_session = |
| auth_session_manager_->FindAuthSession(request.auth_session_id()); |
| CryptohomeStatus auth_session_status = auth_session.AuthSessionStatus(); |
| if (!auth_session_status.ok()) { |
| LOG(ERROR) << "Invalid AuthSession token provided."; |
| ReplyWithError( |
| std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthSessionNotFoundInAuthAuthFactor), |
| ErrorActionSet( |
| {ErrorAction::kDevCheckUnexpectedState, ErrorAction::kReboot}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN) |
| .Wrap(std::move(auth_session_status).status())); |
| return; |
| } |
| |
| // |auth_factor_labels| is intended to replace |auth_factor_label|, reject |
| // requests specifying both fields. |
| // TODO(b/265151254): Deprecate |auth_factor_label| and remove this check. |
| if (!request.auth_factor_label().empty() && |
| request.auth_factor_labels_size() > 0) { |
| LOG(ERROR) << "Cannot accept request with both auth_factor_label and " |
| "auth_factor_labels."; |
| ReplyWithError( |
| std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataMalformedRequestInAuthAuthFactor), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_INVALID_ARGUMENT)); |
| return; |
| } |
| |
| std::vector<std::string> auth_factor_labels; |
| if (!request.auth_factor_label().empty()) { |
| auth_factor_labels.push_back(request.auth_factor_label()); |
| } else { |
| for (auto label : request.auth_factor_labels()) { |
| auth_factor_labels.push_back(label); |
| } |
| } |
| auth_session->AuthenticateAuthFactor( |
| auth_factor_labels, request.auth_input(), |
| base::BindOnce(&ReplyWithAuthenticationResult, auth_session.Get(), |
| std::move(on_done))); |
| } |
| |
| void UserDataAuth::UpdateAuthFactor( |
| user_data_auth::UpdateAuthFactorRequest request, |
| base::OnceCallback<void(const user_data_auth::UpdateAuthFactorReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| |
| user_data_auth::UpdateAuthFactorReply reply; |
| |
| CryptohomeStatusOr<InUseAuthSession> auth_session_status = |
| GetAuthenticatedAuthSession(request.auth_session_id()); |
| if (!auth_session_status.ok()) { |
| ReplyWithError( |
| std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthNoAuthSessionInUpdateAuthFactor)) |
| .Wrap(std::move(auth_session_status).status())); |
| return; |
| } |
| |
| // Populate the request auth factor with accurate sysinfo. |
| PopulateAuthFactorProtoWithSysinfo(*request.mutable_auth_factor()); |
| |
| StatusCallback on_update_auth_factor_finished = base::BindOnce( |
| &UserDataAuth::OnUpdateAuthFactorFinished, base::Unretained(this), |
| auth_session_status.value().Get(), |
| base::BindOnce(&ReplyWithStatus<user_data_auth::UpdateAuthFactorReply>, |
| std::move(on_done))); |
| auth_session_status.value()->UpdateAuthFactor( |
| request, std::move(on_update_auth_factor_finished)); |
| } |
| |
| void UserDataAuth::RemoveAuthFactor( |
| user_data_auth::RemoveAuthFactorRequest request, |
| base::OnceCallback<void(const user_data_auth::RemoveAuthFactorReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| user_data_auth::RemoveAuthFactorReply reply; |
| |
| CryptohomeStatusOr<InUseAuthSession> auth_session_status = |
| GetAuthenticatedAuthSession(request.auth_session_id()); |
| if (!auth_session_status.ok()) { |
| ReplyWithError(std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthSessionNotFoundInRemoveAuthFactor)) |
| .Wrap(std::move(auth_session_status).status())); |
| return; |
| } |
| |
| StatusCallback on_remove_auth_factor_finished = |
| base::BindOnce(&ReplyWithStatus<user_data_auth::RemoveAuthFactorReply>, |
| std::move(on_done)); |
| |
| auth_session_status.value()->RemoveAuthFactor( |
| request, std::move(on_remove_auth_factor_finished)); |
| } |
| |
| void UserDataAuth::ListAuthFactors( |
| user_data_auth::ListAuthFactorsRequest request, |
| base::OnceCallback<void(const user_data_auth::ListAuthFactorsReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| user_data_auth::ListAuthFactorsReply reply; |
| |
| // Check whether user exists. |
| // Compute the raw and sanitized user name from the request. |
| Username username = GetAccountId(request.account_id()); |
| ObfuscatedUsername obfuscated_username = SanitizeUserName(username); |
| UserSession* user_session = sessions_->Find(username); // May be null! |
| // If the user does not exist, we cannot return auth factors for it. |
| bool is_persistent_user = (user_session && !user_session->IsEphemeral()) || |
| keyset_management_->UserExists(obfuscated_username); |
| bool is_ephemeral_user = user_session && user_session->IsEphemeral(); |
| if (!is_persistent_user && !is_ephemeral_user) { |
| ReplyWithError(std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthUserNonexistentInListAuthFactors), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_INVALID_ARGUMENT)); |
| return; |
| } |
| |
| if (is_persistent_user) { |
| // Prepare the response for configured AuthFactors (with status) with all of |
| // the auth factors from the disk. |
| // Load the AuthFactorMap. |
| bool migrate_to_user_secret_stash = false; |
| if (feature_lib_) { |
| migrate_to_user_secret_stash = feature_lib_->IsEnabledBlocking( |
| kCrOSLateBootMigrateToUserSecretStash); |
| } |
| AuthFactorVaultKeysetConverter converter(keyset_management_); |
| AuthFactorMap auth_factor_map; |
| auth_factor_map = |
| LoadAuthFactorMap(migrate_to_user_secret_stash, obfuscated_username, |
| *platform_, converter, *auth_factor_manager_); |
| |
| // Populate the response from the items in the AuthFactorMap. |
| for (AuthFactorMap::ValueView item : auth_factor_map) { |
| AuthFactor auth_factor = item.auth_factor(); |
| auto auth_factor_proto = GetAuthFactorProto( |
| auth_factor.metadata(), auth_factor.type(), auth_factor.label()); |
| if (auth_factor_proto) { |
| user_data_auth::AuthFactorWithStatus auth_factor_with_status; |
| *auth_factor_with_status.mutable_auth_factor() = |
| std::move(*auth_factor_proto); |
| auto supported_intents = |
| auth_block_utility_->GetSupportedIntentsFromState( |
| auth_factor.auth_block_state()); |
| for (const auto& auth_intent : supported_intents) { |
| auth_factor_with_status.add_available_for_intents( |
| AuthIntentToProto(auth_intent)); |
| } |
| *reply.add_configured_auth_factors_with_status() = |
| std::move(auth_factor_with_status); |
| } |
| } |
| |
| // Prepare the response for supported AuthFactors for the given user. |
| // Since user is a persistent user this is determined based on the |
| // underlying storage backend and the existing configured factors. |
| |
| // Turn the list of configured types into a set that we can use for |
| // computing the list of supported factors. |
| std::set<AuthFactorType> configured_types; |
| for (const auto& configured_factor_status : |
| reply.configured_auth_factors_with_status()) { |
| if (auto type = AuthFactorTypeFromProto( |
| configured_factor_status.auth_factor().type())) { |
| configured_types.insert(*type); |
| } |
| } |
| |
| // Determine what auth factors are supported by going through the entire set |
| // of auth factor types and checking each one. |
| |
| AuthFactorStorageType storage_type = AuthFactorStorageType::kVaultKeyset; |
| if (!auth_factor_map.HasFactorWithStorage( |
| AuthFactorStorageType::kVaultKeyset) && |
| IsUserSecretStashExperimentEnabled(platform_)) { |
| storage_type = AuthFactorStorageType::kUserSecretStash; |
| } |
| |
| for (auto proto_type : |
| PROTOBUF_ENUM_ALL_VALUES(user_data_auth::AuthFactorType)) { |
| std::optional<AuthFactorType> type = AuthFactorTypeFromProto(proto_type); |
| if (!type) { |
| continue; |
| } |
| if (auth_block_utility_->IsAuthFactorSupported(*type, storage_type, |
| configured_types)) { |
| reply.add_supported_auth_factors(proto_type); |
| } |
| } |
| } else if (is_ephemeral_user) { |
| // Use the credential verifier for the session to determine what types of |
| // factors are configured. |
| if (user_session) { |
| for (const CredentialVerifier* verifier : |
| user_session->GetCredentialVerifiers()) { |
| if (auto proto_factor = GetAuthFactorProto( |
| verifier->auth_factor_metadata(), verifier->auth_factor_type(), |
| verifier->auth_factor_label())) { |
| user_data_auth::AuthFactorWithStatus auth_factor_with_status; |
| *auth_factor_with_status.mutable_auth_factor() = |
| std::move(*proto_factor); |
| // All ephemeral users have light verification only enabled by |
| // default. |
| auth_factor_with_status.add_available_for_intents( |
| AuthIntentToProto(AuthIntent::kVerifyOnly)); |
| *reply.add_configured_auth_factors_with_status() = |
| std::move(auth_factor_with_status); |
| } |
| } |
| } |
| // Determine what auth factors are supported by going through the entire set |
| // of auth factor types and checking each one. |
| for (auto proto_type : |
| PROTOBUF_ENUM_ALL_VALUES(user_data_auth::AuthFactorType)) { |
| std::optional<AuthFactorType> type = AuthFactorTypeFromProto(proto_type); |
| if (!type) { |
| continue; |
| } |
| if (auth_block_utility_->IsVerifyWithAuthFactorSupported( |
| AuthIntent::kVerifyOnly, *type)) { |
| reply.add_supported_auth_factors(proto_type); |
| } |
| } |
| } |
| |
| // TODO(b/247122507): Remove this with configured_auth_factor field once tast |
| // test cleanup is done. |
| for (auto configured_auth_factors_with_status : |
| reply.configured_auth_factors_with_status()) { |
| user_data_auth::AuthFactor auth_factor; |
| auth_factor.CopyFrom(configured_auth_factors_with_status.auth_factor()); |
| *reply.add_configured_auth_factors() = std::move(auth_factor); |
| } |
| |
| // Successfully completed, send the response with OK. |
| ReplyWithError(std::move(on_done), reply, OkStatus<CryptohomeError>()); |
| } |
| |
| void UserDataAuth::GetAuthFactorExtendedInfo( |
| user_data_auth::GetAuthFactorExtendedInfoRequest request, |
| base::OnceCallback< |
| void(const user_data_auth::GetAuthFactorExtendedInfoReply&)> on_done) { |
| AssertOnMountThread(); |
| |
| user_data_auth::GetAuthFactorExtendedInfoReply reply; |
| |
| // Compute the account_id and obfuscated user name from the request. |
| ObfuscatedUsername obfuscated_username = |
| SanitizeUserName(GetAccountId(request.account_id())); |
| user_data_auth::AuthFactor auth_factor_proto; |
| if (LoadUserAuthFactorByLabel( |
| auth_factor_manager_, *auth_block_utility_, obfuscated_username, |
| request.auth_factor_label(), &auth_factor_proto)) { |
| *reply.mutable_auth_factor() = std::move(auth_factor_proto); |
| } |
| std::optional<AuthFactorType> type = |
| AuthFactorTypeFromProto(reply.auth_factor().type()); |
| if (!type) { |
| ReplyWithError( |
| std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUserDataAuthFactorExtendedInfoTypeFailure), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_KEY_NOT_FOUND)); |
| return; |
| } |
| switch (*type) { |
| case AuthFactorType::kCryptohomeRecovery: { |
| if (!request.has_recovery_info_request()) { |
| ReplyWithError( |
| std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthFactorExtendedInfoRecoveryIdFailure), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_INVALID_ARGUMENT)); |
| return; |
| } |
| std::unique_ptr<cryptorecovery::RecoveryCryptoImpl> recovery = |
| cryptorecovery::RecoveryCryptoImpl::Create(recovery_crypto_, |
| platform_); |
| if (!recovery) { |
| ReplyWithError( |
| std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthRecoveryObjectFailureGetRecoveryId), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_ERROR_RECOVERY_FATAL)); |
| return; |
| } |
| std::vector<std::string> recovery_ids = recovery->GetLastRecoveryIds( |
| request.account_id(), request.recovery_info_request().max_depth()); |
| user_data_auth::RecoveryExtendedInfoReply recovery_reply; |
| for (const std::string& recovery_id : recovery_ids) { |
| recovery_reply.add_recovery_ids(recovery_id); |
| } |
| *reply.mutable_recovery_info_reply() = std::move(recovery_reply); |
| break; |
| } |
| default: { |
| LOG(WARNING) << AuthFactorTypeToString(*type) |
| << " factor type does not support extended info."; |
| } |
| } |
| ReplyWithError(std::move(on_done), reply, OkStatus<CryptohomeError>()); |
| } |
| |
| void UserDataAuth::PrepareAuthFactor( |
| user_data_auth::PrepareAuthFactorRequest request, |
| base::OnceCallback<void(const user_data_auth::PrepareAuthFactorReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| user_data_auth::PrepareAuthFactorReply reply; |
| InUseAuthSession auth_session = |
| auth_session_manager_->FindAuthSession(request.auth_session_id()); |
| CryptohomeStatus auth_session_status = auth_session.AuthSessionStatus(); |
| if (!auth_session_status.ok()) { |
| ReplyWithError( |
| std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthPrepareAuthFactorAuthSessionNotFound), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN) |
| .Wrap(std::move(auth_session_status).status())); |
| return; |
| } |
| auth_session->PrepareAuthFactor( |
| request, |
| base::BindOnce(&ReplyWithStatus<user_data_auth::PrepareAuthFactorReply>, |
| std::move(on_done))); |
| } |
| |
| void UserDataAuth::TerminateAuthFactor( |
| user_data_auth::TerminateAuthFactorRequest request, |
| base::OnceCallback<void(const user_data_auth::TerminateAuthFactorReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| user_data_auth::TerminateAuthFactorReply reply; |
| InUseAuthSession auth_session = |
| auth_session_manager_->FindAuthSession(request.auth_session_id()); |
| CryptohomeStatus auth_session_status = auth_session.AuthSessionStatus(); |
| if (!auth_session_status.ok()) { |
| ReplyWithError( |
| std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthTerminateAuthFactorAuthSessionNotFound), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN) |
| .Wrap(std::move(auth_session_status).status())); |
| return; |
| } |
| auth_session->TerminateAuthFactor( |
| request, |
| base::BindOnce(&ReplyWithStatus<user_data_auth::TerminateAuthFactorReply>, |
| std::move(on_done))); |
| } |
| |
| void UserDataAuth::GetAuthSessionStatus( |
| user_data_auth::GetAuthSessionStatusRequest request, |
| base::OnceCallback<void(const user_data_auth::GetAuthSessionStatusReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| user_data_auth::GetAuthSessionStatusReply reply; |
| |
| InUseAuthSession auth_session = |
| auth_session_manager_->FindAuthSession(request.auth_session_id()); |
| CryptohomeStatus auth_session_status = auth_session.AuthSessionStatus(); |
| if (!auth_session_status.ok()) { |
| reply.set_error(user_data_auth::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN); |
| LOG(ERROR) << "GetAuthSessionStatus: AuthSession not found."; |
| return; |
| } |
| GetAuthSessionStatusImpl(auth_session.Get(), reply); |
| } |
| |
| void UserDataAuth::GetAuthSessionStatusImpl( |
| AuthSession* auth_session, |
| user_data_auth::GetAuthSessionStatusReply& reply) { |
| DCHECK(auth_session); |
| // Default is invalid unless there is evidence otherwise. |
| reply.set_status(user_data_auth::AUTH_SESSION_STATUS_INVALID_AUTH_SESSION); |
| |
| if (auth_session->status() == AuthStatus::kAuthStatusFurtherFactorRequired) { |
| reply.set_status( |
| user_data_auth::AUTH_SESSION_STATUS_FURTHER_FACTOR_REQUIRED); |
| } else if (auth_session->status() == AuthStatus::kAuthStatusAuthenticated) { |
| reply.set_time_left(auth_session->GetRemainingTime().InSeconds()); |
| reply.set_status(user_data_auth::AUTH_SESSION_STATUS_AUTHENTICATED); |
| } |
| } |
| |
| void UserDataAuth::GetRecoveryRequest( |
| user_data_auth::GetRecoveryRequestRequest request, |
| base::OnceCallback<void(const user_data_auth::GetRecoveryRequestReply&)> |
| on_done) { |
| AssertOnMountThread(); |
| |
| user_data_auth::GetRecoveryRequestReply reply; |
| InUseAuthSession auth_session = |
| auth_session_manager_->FindAuthSession(request.auth_session_id()); |
| CryptohomeStatus auth_session_status = auth_session.AuthSessionStatus(); |
| if (!auth_session_status.ok()) { |
| LOG(ERROR) << "Invalid AuthSession token provided."; |
| ReplyWithError(std::move(on_done), reply, |
| MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocUserDataAuthSessionNotFoundInGetRecoveryRequest), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kReboot}), |
| user_data_auth::CryptohomeErrorCode:: |
| CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN) |
| .Wrap(std::move(auth_session_status).status())); |
| return; |
| } |
| auth_session->GetRecoveryRequest(request, std::move(on_done)); |
| } |
| |
| } // namespace cryptohome |