blob: 08c6918e0533080eefe73a3a332c674ddad9515b [file] [log] [blame] [edit]
// Copyright 2021 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/auth_factor/manager.h"
#include <sys/stat.h>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <brillo/secure_blob.h>
#include <flatbuffers/flatbuffers.h>
#include <libhwsec-foundation/flatbuffers/basic_objects.h>
#include <libhwsec-foundation/flatbuffers/flatbuffer_secure_allocator_bridge.h>
#include <libhwsec-foundation/status/status_chain.h>
#include <libstorage/platform/platform.h>
#include "cryptohome/auth_blocks/auth_block_utility.h"
#include "cryptohome/auth_factor/auth_factor.h"
#include "cryptohome/auth_factor/label.h"
#include "cryptohome/auth_factor/metadata.h"
#include "cryptohome/auth_factor/type.h"
#include "cryptohome/error/cryptohome_error.h"
#include "cryptohome/error/locations.h"
#include "cryptohome/filesystem_layout.h"
#include "cryptohome/flatbuffer_schemas/auth_factor.h"
namespace cryptohome {
namespace {
using ::cryptohome::error::CryptohomeError;
using ::cryptohome::error::ErrorActionSet;
using ::cryptohome::error::PossibleAction;
using ::cryptohome::error::PrimaryAction;
using ::hwsec_foundation::status::MakeStatus;
using ::hwsec_foundation::status::OkStatus;
using ::hwsec_foundation::status::StatusChain;
// Use rw------- for the auth factor files.
constexpr mode_t kAuthFactorFilePermissions = 0600;
// Checks if the provided `auth_factor_label` is valid and on success returns
// `AuthFactorPath()`.
CryptohomeStatusOr<base::FilePath> GetAuthFactorPathFromStringType(
const ObfuscatedUsername& obfuscated_username,
const std::string& auth_factor_type_string,
const std::string& auth_factor_label) {
if (!IsValidAuthFactorLabel(auth_factor_label)) {
LOG(ERROR) << "Invalid auth factor label " << auth_factor_label
<< " of type " << auth_factor_type_string;
return MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocGetAuthFactorPathInvalidLabel),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT);
}
return AuthFactorPath(obfuscated_username, auth_factor_type_string,
auth_factor_label);
}
// Converts `auth_factor_type` to string and on success calls
// `GetAuthFactorPathFromStringType()` method above.
CryptohomeStatusOr<base::FilePath> GetAuthFactorPath(
const ObfuscatedUsername& obfuscated_username,
const AuthFactorType auth_factor_type,
const std::string& auth_factor_label) {
const std::string type_string = AuthFactorTypeToString(auth_factor_type);
if (type_string.empty()) {
LOG(ERROR) << "Failed to convert auth factor type "
<< static_cast<int>(auth_factor_type) << " for factor called "
<< auth_factor_label;
return MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocGetAuthFactorPathWrongTypeString),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT);
}
return GetAuthFactorPathFromStringType(obfuscated_username, type_string,
auth_factor_label);
}
} // namespace
AuthFactorManager::AuthFactorManager(libstorage::Platform* platform,
KeysetManagement* keyset_management,
UssManager* uss_manager)
: platform_(platform),
uss_manager_(uss_manager),
converter_(keyset_management) {
CHECK(platform_);
CHECK(uss_manager_);
}
AuthFactorMap& AuthFactorManager::GetAuthFactorMap(
const ObfuscatedUsername& username) {
auto iter = map_of_af_maps_.lower_bound(username);
if (iter == map_of_af_maps_.end() || iter->first != username) {
iter = map_of_af_maps_.emplace_hint(iter, username,
LoadAllAuthFactors(username));
}
return iter->second;
}
void AuthFactorManager::DiscardAuthFactorMap(
const ObfuscatedUsername& username) {
map_of_af_maps_.erase(username);
}
void AuthFactorManager::DiscardAllAuthFactorMaps() {
map_of_af_maps_.clear();
}
CryptohomeStatus AuthFactorManager::SaveAuthFactorFile(
const ObfuscatedUsername& obfuscated_username,
const AuthFactor& auth_factor) {
CryptohomeStatusOr<base::FilePath> file_path = GetAuthFactorPath(
obfuscated_username, auth_factor.type(), auth_factor.label());
if (!file_path.ok()) {
LOG(ERROR) << "Failed to get auth factor path in Save.";
return MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocAuthFactorManagerGetPathFailedInSave),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT);
}
// Create a flatbuffer to be persisted.
std::optional<brillo::Blob> flatbuffer =
SerializedAuthFactor{.auth_block_state = auth_factor.auth_block_state(),
.metadata = auth_factor.metadata().metadata,
.common_metadata = auth_factor.metadata().common}
.Serialize();
if (!flatbuffer.has_value()) {
LOG(ERROR) << "Failed to serialize auth factor " << auth_factor.label()
<< " of type " << AuthFactorTypeToString(auth_factor.type());
return MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocAuthFactorManagerSerializeFailedInSave),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE);
}
// Write the file.
if (!platform_->WriteFileAtomicDurable(*file_path, *flatbuffer,
kAuthFactorFilePermissions)) {
LOG(ERROR) << "Failed to persist auth factor " << auth_factor.label()
<< " of type " << AuthFactorTypeToString(auth_factor.type())
<< " for " << obfuscated_username;
return MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocAuthFactorManagerWriteFailedInSave),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE);
}
return OkStatus<CryptohomeError>();
}
CryptohomeStatus AuthFactorManager::DeleteAuthFactorFile(
const ObfuscatedUsername& obfuscated_username,
const AuthFactor& auth_factor) {
CryptohomeStatusOr<base::FilePath> file_path = GetAuthFactorPath(
obfuscated_username, auth_factor.type(), auth_factor.label());
if (!file_path.ok()) {
LOG(ERROR) << "Failed to get auth factor path in Save.";
return MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocAuthFactorManagerGetPathFailedInDelete),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT);
}
// Remove the file.
if (!platform_->DeleteFileSecurely(*file_path)) {
LOG(WARNING) << "Failed to securely delete from disk auth factor "
<< auth_factor.label() << " of type "
<< AuthFactorTypeToString(auth_factor.type()) << " for "
<< obfuscated_username
<< ". Attempting to delete without zeroization.";
if (!platform_->DeleteFile(*file_path)) {
LOG(ERROR) << "Failed to delete from disk auth factor "
<< auth_factor.label() << " of type "
<< AuthFactorTypeToString(auth_factor.type()) << " for "
<< obfuscated_username;
return MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocAuthFactorManagerDeleteFailedInDelete),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState,
PossibleAction::kRetry, PossibleAction::kReboot}),
user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE);
}
}
LOG(INFO) << "Deleted from disk auth factor label: " << auth_factor.label();
// Remove the checksum file and only log warnings if the removal failed.
base::FilePath auth_factor_checksum_path =
file_path->AddExtension(libstorage::kChecksumExtension);
if (!platform_->DeleteFileSecurely(auth_factor_checksum_path)) {
LOG(WARNING)
<< "Failed to securely delete checksum file from disk for auth factor "
<< auth_factor.label() << " of type "
<< AuthFactorTypeToString(auth_factor.type()) << " for "
<< obfuscated_username << ". Attempting to delete without zeroization.";
if (!platform_->DeleteFile(auth_factor_checksum_path)) {
LOG(WARNING)
<< "Failed to delete checksum file from disk for auth factor "
<< auth_factor.label() << " of type "
<< AuthFactorTypeToString(auth_factor.type()) << " for "
<< obfuscated_username;
}
}
return OkStatus<CryptohomeError>();
}
CryptohomeStatusOr<AuthFactor> AuthFactorManager::LoadAuthFactor(
const ObfuscatedUsername& obfuscated_username,
AuthFactorType auth_factor_type,
const std::string& auth_factor_label) {
CryptohomeStatusOr<base::FilePath> file_path = GetAuthFactorPath(
obfuscated_username, auth_factor_type, auth_factor_label);
if (!file_path.ok()) {
LOG(ERROR) << "Failed to get auth factor path in Load.";
return MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocAuthFactorManagerGetPathFailedInLoad),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT);
}
brillo::Blob file_contents;
if (!platform_->ReadFile(file_path.value(), &file_contents)) {
LOG(ERROR) << "Failed to load persisted auth factor " << auth_factor_label
<< " of type " << AuthFactorTypeToString(auth_factor_type)
<< " for " << obfuscated_username;
return MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocAuthFactorManagerReadFailedInLoad),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE);
}
// This check is redundant to the flatbuffer parsing below, but we check it
// here in order to distinguish "empty file" from "corrupted file" in metrics
// and logs.
if (file_contents.empty()) {
return MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocAuthFactorManagerEmptyReadInLoad),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE);
}
auto serialized_factor = SerializedAuthFactor::Deserialize(file_contents);
if (!serialized_factor.has_value()) {
LOG(ERROR) << "Failed to parse persisted auth factor " << auth_factor_label
<< " of type " << AuthFactorTypeToString(auth_factor_type)
<< " for " << obfuscated_username;
return MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocAuthFactorManagerParseFailedInLoad),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE);
}
return AuthFactor(
auth_factor_type, auth_factor_label,
AuthFactorMetadata{.common = serialized_factor.value().common_metadata,
.metadata = serialized_factor.value().metadata},
serialized_factor.value().auth_block_state);
}
AuthFactorManager::LabelToTypeMap AuthFactorManager::ListAuthFactors(
const ObfuscatedUsername& obfuscated_username) {
LabelToTypeMap label_to_type_map;
std::unique_ptr<libstorage::FileEnumerator> file_enumerator(
platform_->GetFileEnumerator(AuthFactorsDirPath(obfuscated_username),
/*recursive=*/false,
base::FileEnumerator::FILES));
base::FilePath next_path;
while (!(next_path = file_enumerator->Next()).empty()) {
const base::FilePath base_name = next_path.BaseName();
if (!base_name.RemoveFinalExtension().FinalExtension().empty()) {
// Silently ignore files that have multiple extensions; to note, a
// legitimate case of such files is the checksum file
// ("<type>.<label>.sum").
continue;
}
// Parse and sanitize the type.
const std::string auth_factor_type_string =
base_name.RemoveExtension().value();
const std::optional<AuthFactorType> auth_factor_type =
AuthFactorTypeFromString(auth_factor_type_string);
if (!auth_factor_type.has_value()) {
LOG(WARNING) << "Unknown auth factor type: file name = "
<< base_name.value();
continue;
}
// Parse and sanitize the label. Note that `FilePath::Extension()` returns a
// string with a leading dot.
const std::string extension = base_name.Extension();
if (extension.length() <= 1 ||
extension[0] != base::FilePath::kExtensionSeparator) {
LOG(WARNING) << "Missing auth factor label: file name = "
<< base_name.value();
continue;
}
const std::string auth_factor_label = extension.substr(1);
if (!IsValidAuthFactorLabel(auth_factor_label)) {
LOG(WARNING) << "Invalid auth factor label: file name = "
<< base_name.value();
continue;
}
// Check for label clashes.
if (label_to_type_map.count(auth_factor_label)) {
const AuthFactorType previous_type = label_to_type_map[auth_factor_label];
LOG(WARNING) << "Ignoring duplicate auth factor: label = "
<< auth_factor_label << " type = " << auth_factor_type_string
<< " previous type = "
<< AuthFactorTypeToString(previous_type);
continue;
}
// All checks passed - add the factor.
label_to_type_map.insert({auth_factor_label, auth_factor_type.value()});
}
return label_to_type_map;
}
void AuthFactorManager::RemoveAuthFactor(
const ObfuscatedUsername& obfuscated_username,
const AuthFactor& auth_factor,
AuthBlockUtility* auth_block_utility,
StatusCallback callback) {
auth_block_utility->PrepareAuthBlockForRemoval(
obfuscated_username, auth_factor.auth_block_state(),
base::BindOnce(&AuthFactorManager::RemoveAuthFactorFiles,
weak_factory_.GetWeakPtr(), obfuscated_username,
auth_factor, std::move(callback)));
}
void AuthFactorManager::UpdateAuthFactor(
const ObfuscatedUsername& obfuscated_username,
const std::string& auth_factor_label,
AuthFactor& auth_factor,
AuthBlockUtility* auth_block_utility,
StatusCallback callback) {
// 1. Load the old auth factor state from disk.
CryptohomeStatusOr<AuthFactor> existing_auth_factor = LoadAuthFactor(
obfuscated_username, auth_factor.type(), auth_factor_label);
if (!existing_auth_factor.ok()) {
LOG(ERROR) << "Failed to load persisted auth factor " << auth_factor_label
<< " of type " << AuthFactorTypeToString(auth_factor.type())
<< " for " << obfuscated_username << " in Update.";
std::move(callback).Run(
MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocAuthFactorManagerLoadFailedInUpdate),
user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE)
.Wrap(std::move(existing_auth_factor).err_status()));
return;
}
// 2. Save auth factor to disk - the old auth factor state will be overridden
// and accessible only from `existing_auth_factor` object.
CryptohomeStatus save_result =
SaveAuthFactorFile(obfuscated_username, auth_factor);
if (!save_result.ok()) {
LOG(ERROR) << "Failed to save auth factor " << auth_factor.label()
<< " of type " << AuthFactorTypeToString(auth_factor.type())
<< " for " << obfuscated_username << " in Update.";
std::move(callback).Run(
MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(kLocAuthFactorManagerSaveFailedInUpdate),
user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE)
.Wrap(std::move(save_result)));
return;
}
// 3. The old auth factor state was removed from disk. Call
// `PrepareForRemoval()` to complete the removal.
auth_block_utility->PrepareAuthBlockForRemoval(
obfuscated_username, existing_auth_factor->auth_block_state(),
base::BindOnce(&AuthFactorManager::LogPrepareForRemovalStatus,
weak_factory_.GetWeakPtr(), obfuscated_username,
auth_factor, std::move(callback)));
}
void AuthFactorManager::RemoveMigratedFingerprintAuthFactors(
const ObfuscatedUsername& obfuscated_username,
AuthBlockUtility* auth_block_utility,
StatusCallback callback) {
const AuthFactorMap& auth_factor_map = GetAuthFactorMap(obfuscated_username);
std::optional<AuthFactorMap::ValueView> found_migrated_factor = std::nullopt;
for (auto factor_view : auth_factor_map) {
if (factor_view.auth_factor().type() == AuthFactorType::kFingerprint) {
const auto* fp_metadata = std::get_if<FingerprintMetadata>(
&(factor_view.auth_factor().metadata().metadata));
if (!fp_metadata) {
std::move(callback).Run(MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(
kLocAuthFactorManagerMalformedFactorInRemoveMigratedFpFactors),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}),
user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT));
return;
}
if (fp_metadata->was_migrated) {
found_migrated_factor = factor_view;
break;
}
}
}
if (!found_migrated_factor) {
std::move(callback).Run(OkStatus<CryptohomeError>());
return;
}
auto& auth_factor = found_migrated_factor->auth_factor();
auto migrate_more = base::BindOnce(
&AuthFactorManager::ContinueRemoveFpAuthFactors,
weak_factory_.GetWeakPtr(), obfuscated_username, auth_factor.label(),
auth_block_utility, std::move(callback));
auth_block_utility->PrepareAuthBlockForRemoval(
obfuscated_username, auth_factor.auth_block_state(),
base::BindOnce(&AuthFactorManager::RemoveAuthFactorFiles,
weak_factory_.GetWeakPtr(), obfuscated_username,
auth_factor, std::move(migrate_more)));
}
AuthFactorMap AuthFactorManager::LoadAllAuthFactors(
const ObfuscatedUsername& obfuscated_username) {
AuthFactorMap auth_factor_map;
// Load labels for auth factors in the USS. If the USS cannot be loaded then
// tere are no factors listed in the USS.
std::set<std::string_view> uss_labels;
auto encrypted_uss = uss_manager_->LoadEncrypted(obfuscated_username);
if (encrypted_uss.ok()) {
uss_labels = (*encrypted_uss)->WrappedMainKeyIds();
}
// Load all of the USS-based auth factors.
for (const auto& [label, auth_factor_type] :
ListAuthFactors(obfuscated_username)) {
if (!uss_labels.contains(label)) {
LOG(WARNING) << "Skipping auth factor which has no key in USS " << label;
continue;
}
CryptohomeStatusOr<AuthFactor> auth_factor =
LoadAuthFactor(obfuscated_username, auth_factor_type, label);
if (!auth_factor.ok()) {
LOG(WARNING) << "Skipping malformed auth factor " << label;
continue;
}
auth_factor_map.Add(std::move(*auth_factor),
AuthFactorStorageType::kUserSecretStash);
}
// Load all the VaultKeysets and backup VaultKeysets in disk and convert
// them to AuthFactor format.
std::vector<std::string> migrated_labels;
std::map<std::string, AuthFactor> vk_factor_map;
std::map<std::string, AuthFactor> backup_factor_map;
converter_.VaultKeysetsToAuthFactorsAndKeyLabelData(
obfuscated_username, migrated_labels, vk_factor_map, backup_factor_map);
// Duplicate labels are not expected on any use case. However in very rare
// edge cases where an interrupted USS migration results in having both
// regular VaultKeyset and USS factor in disk it is safer to use the original
// VaultKeyset. In that case regular VaultKeyset overrides the existing
// label in the map.
for (auto& [unused, factor] : vk_factor_map) {
if (auth_factor_map.Find(factor.label())) {
LOG(WARNING) << "Unexpected duplication of label: " << factor.label()
<< ". Regular VaultKeyset will override the AuthFactor.";
}
auth_factor_map.Add(std::move(factor), AuthFactorStorageType::kVaultKeyset);
}
return auth_factor_map;
}
void AuthFactorManager::RemoveAuthFactorFiles(
const ObfuscatedUsername& obfuscated_username,
const AuthFactor& auth_factor,
base::OnceCallback<void(CryptohomeStatus)> callback,
CryptohomeStatus status) {
if (!status.ok()) {
LOG(WARNING) << "Failed to prepare for removal for auth factor "
<< auth_factor.label() << " of type "
<< AuthFactorTypeToString(auth_factor.type()) << " for "
<< obfuscated_username;
std::move(callback).Run(
MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(
kLocAuthFactorManagerPrepareForRemovalFailedInRemove),
ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}))
.Wrap(std::move(status)));
return;
}
std::move(callback).Run(
DeleteAuthFactorFile(obfuscated_username, auth_factor));
}
void AuthFactorManager::LogPrepareForRemovalStatus(
const ObfuscatedUsername& obfuscated_username,
const AuthFactor& auth_factor,
StatusCallback callback,
CryptohomeStatus status) {
if (!status.ok()) {
LOG(WARNING) << "PrepareForRemoval failed for auth factor "
<< auth_factor.label() << " of type "
<< AuthFactorTypeToString(auth_factor.type()) << " for "
<< obfuscated_username << " in Update.";
std::move(callback).Run(
MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(
kLocAuthFactorManagerPrepareForRemovalFailedInUpdate),
user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT)
.Wrap(std::move(status)));
return;
}
std::move(callback).Run(OkStatus<CryptohomeError>());
}
void AuthFactorManager::ContinueRemoveFpAuthFactors(
const ObfuscatedUsername& obfuscated_username,
const std::string& auth_factor_label,
AuthBlockUtility* auth_block_utility,
StatusCallback callback,
CryptohomeStatus status) {
if (!status.ok()) {
std::move(callback).Run(
MakeStatus<CryptohomeError>(
CRYPTOHOME_ERR_LOC(
kLocAuthFactorManagerFailedInRemoveOneMigratedFpFactors),
user_data_auth::CRYPTOHOME_ERROR_INVALID_ARGUMENT)
.Wrap(std::move(status)));
return;
}
AuthFactorMap& auth_factor_map = GetAuthFactorMap(obfuscated_username);
auth_factor_map.Remove(auth_factor_label);
RemoveMigratedFingerprintAuthFactors(obfuscated_username, auth_block_utility,
std::move(callback));
}
} // namespace cryptohome