blob: 3071b39aca6363ca97f3fb7340daac5de659d324 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cryptohome/keyset_management.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/callback.h>
#include <base/check.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/timer/elapsed_timer.h>
#include <brillo/secure_blob.h>
#include <chromeos/constants/cryptohome.h>
#include <dbus/cryptohome/dbus-constants.h>
#include "cryptohome/auth_blocks/auth_block_state.h"
#include "cryptohome/credentials.h"
#include "cryptohome/crypto.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/filesystem_layout.h"
#include "cryptohome/key_objects.h"
#include "cryptohome/platform.h"
#include "cryptohome/storage/homedirs.h"
#include "cryptohome/timestamp.pb.h"
#include "cryptohome/vault_keyset.h"
#include "cryptohome/vault_keyset_factory.h"
using base::FilePath;
namespace cryptohome {
namespace {
// Wraps VaultKeyset::DecryptEx to bind to DecryptVkCallback without object
// reference.
bool DecryptExWrapper(const KeyBlobs& key_blobs,
VaultKeyset* vk,
CryptoError* error) {
return vk->DecryptEx(key_blobs, error);
}
// Wraps VaultKeyset::Decrypt to bind to DecryptVkCallback without object
// reference.
bool DecryptWrapper(const brillo::SecureBlob& key,
bool locked_to_single_user,
VaultKeyset* vk,
CryptoError* error) {
return vk->Decrypt(key, locked_to_single_user, error);
}
// Wraps VaultKeyset::ExncryptEx to bind to EncryptVkCallback without object
// reference.
bool EncryptExWrapper(const KeyBlobs& key_blobs,
std::unique_ptr<AuthBlockState> auth_state,
VaultKeyset* vk) {
return vk->EncryptEx(key_blobs, *auth_state);
}
// Wraps VaultKeyset::Exncryptto bind to EncryptVkCallback without object
// reference.
bool EncryptWrapper(const brillo::SecureBlob& key,
const std::string& obfuscated_username,
VaultKeyset* vk) {
return vk->Encrypt(key, obfuscated_username);
}
} // namespace
KeysetManagement::KeysetManagement(
Platform* platform,
Crypto* crypto,
std::unique_ptr<VaultKeysetFactory> vault_keyset_factory)
: platform_(platform),
crypto_(crypto),
vault_keyset_factory_(std::move(vault_keyset_factory)) {}
bool KeysetManagement::AreCredentialsValid(const Credentials& creds) {
std::unique_ptr<VaultKeyset> vk = GetValidKeyset(creds, nullptr /* error */);
return vk.get() != nullptr;
}
std::unique_ptr<VaultKeyset> KeysetManagement::GetValidKeysetWithKeyBlobs(
const std::string& obfuscated_username,
KeyBlobs key_blobs,
const std::optional<std::string>& label,
MountError* error) {
return GetValidKeysetImpl(
obfuscated_username, label,
base::BindRepeating(&DecryptExWrapper,
base::Passed(std::move(key_blobs))),
error);
}
std::unique_ptr<VaultKeyset> KeysetManagement::GetValidKeyset(
const Credentials& credentials, MountError* error) {
std::string obfuscated_username = credentials.GetObfuscatedUsername();
bool locked_to_single_user =
platform_->FileExists(base::FilePath(kLockedToSingleUserFile));
return GetValidKeysetImpl(
obfuscated_username, credentials.key_data().label(),
base::BindRepeating(&DecryptWrapper, credentials.passkey(),
locked_to_single_user),
error);
}
std::unique_ptr<VaultKeyset> KeysetManagement::GetValidKeysetImpl(
const std::string& obfuscated,
const std::optional<std::string>& label,
DecryptVkCallback decrypt_vk_callback,
MountError* error) {
if (error) {
*error = MOUNT_ERROR_NONE;
}
std::vector<int> key_indices;
if (!GetVaultKeysets(obfuscated, &key_indices)) {
LOG(WARNING) << "No valid keysets on disk for " << obfuscated;
if (error)
*error = MOUNT_ERROR_VAULT_UNRECOVERABLE;
return nullptr;
}
bool any_keyset_exists = false;
CryptoError last_crypto_error = CryptoError::CE_NONE;
for (int index : key_indices) {
std::unique_ptr<VaultKeyset> vk = LoadVaultKeysetForUser(obfuscated, index);
if (!vk) {
continue;
}
any_keyset_exists = true;
// Skip decrypt attempts if the label doesn't match.
// Treat an empty creds label as a wildcard.
if (label.has_value() && !label.value().empty() &&
label != vk->GetLabel()) {
continue;
}
// Skip LE Credentials if not explicitly identified by a label, since we
// don't want unnecessary wrong attempts.
if ((!label.has_value() || label.value().empty()) &&
(vk->GetFlags() & SerializedVaultKeyset::LE_CREDENTIAL)) {
continue;
}
if (decrypt_vk_callback.Run(vk.get(), &last_crypto_error)) {
return vk;
}
}
MountError local_error = MOUNT_ERROR_NONE;
if (!any_keyset_exists) {
LOG(ERROR) << "No parsable keysets found for " << obfuscated;
local_error = MOUNT_ERROR_VAULT_UNRECOVERABLE;
} else if (last_crypto_error == CryptoError::CE_NONE) {
// If we're searching by label, don't let a no-key-found become
// MOUNT_ERROR_FATAL. In the past, no parseable key was a fatal
// error. Just treat it like an invalid key. This allows for
// multiple per-label requests then a wildcard, worst case, before
// the Cryptohome is removed.
if (label.has_value() && !label.value().empty()) {
LOG(ERROR) << "Failed to find the specified keyset for " << obfuscated;
local_error = MOUNT_ERROR_KEY_FAILURE;
} else {
LOG(ERROR) << "Failed to find any suitable keyset for " << obfuscated;
local_error = MOUNT_ERROR_FATAL;
}
} else {
switch (last_crypto_error) {
case CryptoError::CE_TPM_FATAL:
case CryptoError::CE_OTHER_FATAL:
local_error = MOUNT_ERROR_VAULT_UNRECOVERABLE;
break;
case CryptoError::CE_TPM_COMM_ERROR:
local_error = MOUNT_ERROR_TPM_COMM_ERROR;
break;
case CryptoError::CE_TPM_DEFEND_LOCK:
local_error = MOUNT_ERROR_TPM_DEFEND_LOCK;
break;
case CryptoError::CE_TPM_REBOOT:
local_error = MOUNT_ERROR_TPM_NEEDS_REBOOT;
break;
default:
local_error = MOUNT_ERROR_KEY_FAILURE;
break;
}
LOG(ERROR) << "Failed to decrypt any keysets for " << obfuscated
<< ": mount error " << local_error << ", crypto error "
<< last_crypto_error;
}
if (error)
*error = local_error;
return nullptr;
}
std::unique_ptr<VaultKeyset> KeysetManagement::GetVaultKeyset(
const std::string& obfuscated_username,
const std::string& key_label) const {
if (key_label.empty())
return NULL;
// Walk all indices to find a match.
// We should move to label-derived suffixes to be efficient.
std::vector<int> key_indices;
if (!GetVaultKeysets(obfuscated_username, &key_indices)) {
return NULL;
}
for (int index : key_indices) {
std::unique_ptr<VaultKeyset> vk =
LoadVaultKeysetForUser(obfuscated_username, index);
if (!vk) {
continue;
}
if (vk->GetLabel() == key_label) {
return vk;
}
}
return NULL;
}
// TODO(wad) Figure out how this might fit in with vault_keyset.cc
bool KeysetManagement::GetVaultKeysets(const std::string& obfuscated,
std::vector<int>* keysets) const {
CHECK(keysets);
base::FilePath user_dir = UserPath(obfuscated);
std::unique_ptr<FileEnumerator> file_enumerator(platform_->GetFileEnumerator(
user_dir, false, base::FileEnumerator::FILES));
base::FilePath next_path;
while (!(next_path = file_enumerator->Next()).empty()) {
base::FilePath file_name = next_path.BaseName();
// Scan for "master." files. // nocheck
if (file_name.RemoveFinalExtension().value() != kKeyFile) {
continue;
}
std::string index_str = file_name.FinalExtension();
int index;
if (!base::StringToInt(&index_str[1], &index)) {
continue;
}
// The test below will catch all strtol(3) error conditions.
if (index < 0 || index >= kKeyFileMax) {
LOG(ERROR) << "Invalid key file range: " << index;
continue;
}
keysets->push_back(static_cast<int>(index));
}
// Ensure it is sorted numerically and not lexigraphically.
std::sort(keysets->begin(), keysets->end());
return keysets->size() != 0;
}
bool KeysetManagement::GetVaultKeysetLabelsAndData(
const std::string& obfuscated_username,
std::map<std::string, KeyData>* key_label_data) const {
CHECK(key_label_data);
base::FilePath user_dir = UserPath(obfuscated_username);
std::unique_ptr<FileEnumerator> file_enumerator(platform_->GetFileEnumerator(
user_dir, false /* Not recursive. */, base::FileEnumerator::FILES));
base::FilePath next_path;
while (!(next_path = file_enumerator->Next()).empty()) {
base::FilePath file_name = next_path.BaseName();
// Scan for key files.
if (file_name.RemoveFinalExtension().value() != kKeyFile) {
continue;
}
int index = 0;
std::string index_str = file_name.FinalExtension();
// StringToInt will only return true for a perfect conversion.
if (!base::StringToInt(&index_str[1], &index)) {
continue;
}
if (index < 0 || index >= kKeyFileMax) {
LOG(ERROR) << "Invalid key file range: " << index;
continue;
}
// Now parse the keyset to get its label and keydata or skip it. The
// VaultKeyset will not be decrypted during this step.
std::unique_ptr<VaultKeyset> vk =
LoadVaultKeysetForUser(obfuscated_username, index);
if (!vk) {
continue;
}
if (key_label_data->find(vk->GetLabel()) != key_label_data->end()) {
// This is a confirmation check, we do not expect to hit this.
LOG(INFO) << "Found a duplicate label, skipping it: " << vk->GetLabel();
continue;
}
key_label_data->insert({vk->GetLabel(), vk->GetKeyData()});
}
return (key_label_data->size() > 0);
}
bool KeysetManagement::GetVaultKeysetLabels(
const std::string& obfuscated_username,
std::vector<std::string>* labels) const {
CHECK(labels);
base::FilePath user_dir = UserPath(obfuscated_username);
std::unique_ptr<FileEnumerator> file_enumerator(platform_->GetFileEnumerator(
user_dir, false /* Not recursive. */, base::FileEnumerator::FILES));
base::FilePath next_path;
while (!(next_path = file_enumerator->Next()).empty()) {
base::FilePath file_name = next_path.BaseName();
// Scan for "master." files. // nocheck
if (file_name.RemoveFinalExtension().value() != kKeyFile) {
continue;
}
int index = 0;
std::string index_str = file_name.FinalExtension();
// StringToInt will only return true for a perfect conversion.
if (!base::StringToInt(&index_str[1], &index)) {
continue;
}
if (index < 0 || index >= kKeyFileMax) {
LOG(ERROR) << "Invalid key file range: " << index;
continue;
}
// Now parse the keyset to get its label or skip it.
std::unique_ptr<VaultKeyset> vk =
LoadVaultKeysetForUser(obfuscated_username, index);
if (!vk) {
continue;
}
labels->push_back(vk->GetLabel());
}
return (labels->size() > 0);
}
std::unique_ptr<VaultKeyset> KeysetManagement::AddInitialKeysetWithKeyBlobs(
const std::string& obfuscated_username,
const KeyData& key_data,
const SerializedVaultKeyset_SignatureChallengeInfo&
challenge_credentials_keyset_info,
const FileSystemKeyset& file_system_keyset,
KeyBlobs key_blobs,
std::unique_ptr<AuthBlockState> auth_state) {
return AddInitialKeysetImpl(
obfuscated_username, key_data, challenge_credentials_keyset_info,
file_system_keyset,
base::BindOnce(&EncryptExWrapper, std::move(key_blobs),
std::move(auth_state)));
}
std::unique_ptr<VaultKeyset> KeysetManagement::AddInitialKeyset(
const Credentials& credentials,
const FileSystemKeyset& file_system_keyset) {
std::string obfuscated_username = credentials.GetObfuscatedUsername();
SerializedVaultKeyset_SignatureChallengeInfo
challenge_credentials_keyset_info =
credentials.challenge_credentials_keyset_info();
return AddInitialKeysetImpl(
obfuscated_username, credentials.key_data(),
challenge_credentials_keyset_info, file_system_keyset,
base::BindOnce(&EncryptWrapper, credentials.passkey(),
obfuscated_username));
}
std::unique_ptr<VaultKeyset> KeysetManagement::AddInitialKeysetImpl(
const std::string& obfuscated_username,
const KeyData& key_data,
const SerializedVaultKeyset_SignatureChallengeInfo&
challenge_credentials_keyset_info,
const FileSystemKeyset& file_system_keyset,
EncryptVkCallback encrypt_vk_callback) {
std::unique_ptr<VaultKeyset> vk(
vault_keyset_factory_->New(platform_, crypto_));
vk->Initialize(platform_, crypto_);
vk->SetLegacyIndex(kInitialKeysetIndex);
vk->SetKeyData(key_data);
vk->CreateFromFileSystemKeyset(file_system_keyset);
if (key_data.type() == KeyData::KEY_TYPE_CHALLENGE_RESPONSE) {
vk->SetFlags(vk->GetFlags() |
SerializedVaultKeyset::SIGNATURE_CHALLENGE_PROTECTED);
vk->SetSignatureChallengeInfo(challenge_credentials_keyset_info);
}
bool callback_result = std::move(encrypt_vk_callback).Run(vk.get());
if (!callback_result ||
!vk->Save(VaultKeysetPath(obfuscated_username, kInitialKeysetIndex))) {
LOG(ERROR) << "Failed to encrypt and write keyset for the new user.";
return nullptr;
}
return vk;
}
bool KeysetManagement::ShouldReSaveKeyset(VaultKeyset* vault_keyset) const {
// Calling EnsureTpm here handles the case where a user logged in while
// cryptohome was taking TPM ownership. In that case, their vault keyset
// would be scrypt-wrapped and the TPM would not be connected. If we're
// configured to use the TPM, calling EnsureTpm will try to connect, and
// if successful, the call to is_cryptohome_key_loaded() below will succeed,
// allowing re-wrapping (migration) using the TPM.
crypto_->EnsureTpm(false);
if (!vault_keyset->HasWrappedChapsKey()) {
vault_keyset->CreateRandomChapsKey();
LOG(INFO) << "Migrating keyset " << vault_keyset->GetLegacyIndex()
<< " as Cryptohome has taken TPM ownership";
return true;
}
// If the vault keyset's TPM state is not the same as that configured for
// the device, re-save the keyset (this will save in the device's default
// method).
// In the table below: X = true, - = false, * = any value
//
// 1 2 3 4 5 6 7 8 9
// should_tpm X X X X - - - * X
//
// pcr_bound - X * - - * - * -
//
// tpm_wrapped - X X - - X - X *
//
// scrypt_wrapped - - - X - - X X *
//
// scrypt_derived * X - * * * * * *
//
// migrate Y N Y Y Y Y N Y Y
//
// If the vault keyset is signature-challenge protected, we should not
// re-encrypt it at all (that is unnecessary).
const unsigned crypt_flags = vault_keyset->GetFlags();
bool pcr_bound = (crypt_flags & SerializedVaultKeyset::PCR_BOUND) != 0;
bool tpm_wrapped = (crypt_flags & SerializedVaultKeyset::TPM_WRAPPED) != 0;
bool scrypt_wrapped =
(crypt_flags & SerializedVaultKeyset::SCRYPT_WRAPPED) != 0;
bool scrypt_derived =
(crypt_flags & SerializedVaultKeyset::SCRYPT_DERIVED) != 0;
bool is_signature_challenge_protected =
(crypt_flags & SerializedVaultKeyset::SIGNATURE_CHALLENGE_PROTECTED) != 0;
bool should_tpm = (crypto_->is_cryptohome_key_loaded() &&
!is_signature_challenge_protected);
bool can_unseal_with_user_auth = crypto_->CanUnsealWithUserAuth();
bool has_tpm_public_key_hash = vault_keyset->HasTpmPublicKeyHash();
if (is_signature_challenge_protected) {
return false;
}
bool is_le_credential =
(crypt_flags & SerializedVaultKeyset::LE_CREDENTIAL) != 0;
if (is_le_credential &&
!crypto_->NeedsPcrBinding(vault_keyset->GetLELabel())) {
return false;
}
// If the keyset was TPM-wrapped, but there was no public key hash,
// always re-save.
if (tpm_wrapped && !has_tpm_public_key_hash) {
LOG(INFO) << "Migrating keyset " << vault_keyset->GetLegacyIndex()
<< " as there is no public hash";
return true;
}
// Check the table.
if (tpm_wrapped && should_tpm && scrypt_derived && !scrypt_wrapped) {
if ((pcr_bound && can_unseal_with_user_auth) ||
(!pcr_bound && !can_unseal_with_user_auth)) {
return false; // 2
}
}
if (scrypt_wrapped && !should_tpm && !tpm_wrapped)
return false; // 7
LOG(INFO) << "Migrating keyset " << vault_keyset->GetLegacyIndex()
<< ": should_tpm=" << should_tpm
<< ", has_hash=" << has_tpm_public_key_hash
<< ", flags=" << crypt_flags << ", pcr_bound=" << pcr_bound
<< ", can_unseal_with_user_auth=" << can_unseal_with_user_auth;
return true;
}
bool KeysetManagement::ReSaveKeyset(const Credentials& credentials,
VaultKeyset* vault_keyset) const {
std::string obfuscated_username = credentials.GetObfuscatedUsername();
return ReSaveKeysetImpl(*vault_keyset,
base::BindOnce(&EncryptWrapper, credentials.passkey(),
obfuscated_username));
}
bool KeysetManagement::ReSaveKeysetWithKeyBlobs(
VaultKeyset& vault_keyset,
KeyBlobs key_blobs,
std::unique_ptr<AuthBlockState> auth_state) const {
return ReSaveKeysetImpl(
vault_keyset, base::BindOnce(&EncryptExWrapper, std::move(key_blobs),
std::move(auth_state)));
}
bool KeysetManagement::ReSaveKeysetImpl(
VaultKeyset& vault_keyset, EncryptVkCallback encrypt_vk_callback) const {
// Save the initial keyset so we can roll-back any changes if we
// failed to re-save.
VaultKeyset old_keyset = vault_keyset;
// We get the LE Label from keyset before we resave it. Once the keyset
// is re-saved, a new label is generated making the old label obsolete.
// It would be safe to delete that from PinWeaver tree after resave.
std::optional<uint64_t> old_le_label;
if (vault_keyset.HasLELabel()) {
old_le_label = vault_keyset.GetLELabel();
}
bool callback_result = std::move(encrypt_vk_callback).Run(&vault_keyset);
if (!callback_result || !vault_keyset.Save(vault_keyset.GetSourceFile())) {
LOG(ERROR) << "Failed to encrypt and write the vault_keyset.";
vault_keyset = old_keyset;
return false;
}
if ((vault_keyset.GetFlags() & SerializedVaultKeyset::LE_CREDENTIAL) != 0 &&
old_le_label.has_value()) {
CHECK_NE(old_le_label.value(), vault_keyset.GetLELabel());
if (!crypto_->RemoveLECredential(old_le_label.value())) {
// This is non-fatal error.
LOG(ERROR) << "Failed to remove label = " << old_le_label.value();
}
}
return true;
}
bool KeysetManagement::ReSaveKeysetIfNeeded(const Credentials& credentials,
VaultKeyset* vault_keyset) const {
if (ShouldReSaveKeyset(vault_keyset)) {
return ReSaveKeyset(credentials, vault_keyset);
}
return true;
}
CryptohomeErrorCode KeysetManagement::AddKeyset(
const Credentials& new_credentials,
const VaultKeyset& vault_keyset,
bool clobber) {
std::string obfuscated_username = new_credentials.GetObfuscatedUsername();
return AddKeysetImpl(
obfuscated_username, new_credentials.key_data(), vault_keyset,
base::BindOnce(&EncryptWrapper, new_credentials.passkey(),
obfuscated_username),
clobber);
}
CryptohomeErrorCode KeysetManagement::AddKeysetWithKeyBlobs(
const std::string& obfuscated_username_new,
const KeyData& key_data_new,
const VaultKeyset& vault_keyset_old,
KeyBlobs key_blobs_new,
std::unique_ptr<AuthBlockState> auth_state_new,
bool clobber) {
return AddKeysetImpl(
obfuscated_username_new, key_data_new, vault_keyset_old,
base::BindOnce(&EncryptExWrapper, std::move(key_blobs_new),
std::move(auth_state_new)),
clobber);
}
CryptohomeErrorCode KeysetManagement::AddKeysetImpl(
const std::string& obfuscated_username_new,
const KeyData& key_data_new,
const VaultKeyset& vault_keyset_old,
EncryptVkCallback encrypt_vk_callback,
bool clobber) {
// Walk the namespace looking for the first free spot.
// Note, nothing is stopping simultaneous access to these files
// or enforcing mandatory locking.
base::FilePath vk_path;
bool file_found = false;
for (int new_index = 0; new_index < kKeyFileMax; ++new_index) {
vk_path = VaultKeysetPath(obfuscated_username_new, new_index);
// Rely on fopen()'s O_EXCL|O_CREAT behavior to fail
// repeatedly until there is an opening.
base::ScopedFILE vk_file(platform_->OpenFile(vk_path, "wx"));
if (vk_file) { // got one
file_found = true;
break;
}
}
if (!file_found) {
LOG(WARNING) << "Failed to find an available keyset slot";
return CRYPTOHOME_ERROR_KEY_QUOTA_EXCEEDED;
}
// Before persisting, check if there is an existing labeled credential.
std::unique_ptr<VaultKeyset> match =
GetVaultKeyset(obfuscated_username_new, key_data_new.label());
if (match.get()) {
LOG(INFO) << "Label already exists.";
platform_->DeleteFile(vk_path);
if (!clobber) {
return CRYPTOHOME_ERROR_KEY_LABEL_EXISTS;
}
vk_path = match->GetSourceFile();
}
std::unique_ptr<VaultKeyset> keyset_to_add(
vault_keyset_factory_->New(platform_, crypto_));
keyset_to_add->InitializeToAdd(vault_keyset_old);
keyset_to_add->SetKeyData(key_data_new);
// Repersist the VK with the new creds.
CryptohomeErrorCode ret_code = CRYPTOHOME_ERROR_NOT_SET;
if (!std::move(encrypt_vk_callback).Run(keyset_to_add.get()) ||
!keyset_to_add->Save(vk_path)) {
LOG(WARNING) << "Failed to encrypt or write the new keyset";
ret_code = CRYPTOHOME_ERROR_BACKING_STORE_FAILURE;
// If we're clobbering don't delete on error.
if (!clobber || !match.get()) {
platform_->DeleteFile(vk_path);
}
}
return ret_code;
}
CryptohomeErrorCode KeysetManagement::UpdateKeyset(
const Credentials& new_credentials, const VaultKeyset& vault_keyset) {
std::string obfuscated_username = new_credentials.GetObfuscatedUsername();
// Check if there is an existing labeled keyset.
std::unique_ptr<VaultKeyset> match =
GetVaultKeyset(obfuscated_username, new_credentials.key_data().label());
if (!match.get()) {
LOG(ERROR) << "Label does not exist.";
return CRYPTOHOME_ERROR_AUTHORIZATION_KEY_NOT_FOUND;
}
// We set clobber to be true as we are sure that there is an existing keyset.
return AddKeyset(new_credentials, vault_keyset,
true /* we are updating existing keyset */);
}
CryptohomeErrorCode KeysetManagement::UpdateKeysetWithKeyBlobs(
const std::string& obfuscated_username_new,
const KeyData& key_data_new,
const VaultKeyset& vault_keyset,
KeyBlobs key_blobs,
std::unique_ptr<AuthBlockState> auth_state) {
// Check if there is an existing labeled keyset.
std::unique_ptr<VaultKeyset> match =
GetVaultKeyset(obfuscated_username_new, key_data_new.label());
if (!match.get()) {
LOG(ERROR) << "Label does not exist.";
return CRYPTOHOME_ERROR_AUTHORIZATION_KEY_NOT_FOUND;
}
// We set clobber to be true as we are sure that there is an existing keyset.
return AddKeysetWithKeyBlobs(
obfuscated_username_new, key_data_new, vault_keyset, std::move(key_blobs),
std::move(auth_state), true /* we are updating existing keyset */);
}
CryptohomeErrorCode KeysetManagement::AddWrappedResetSeedIfMissing(
VaultKeyset* vault_keyset, const Credentials& credentials) {
if (!AddResetSeedIfMissing(*vault_keyset)) {
return CRYPTOHOME_ERROR_NOT_SET;
}
if (!vault_keyset->Encrypt(credentials.passkey(),
credentials.GetObfuscatedUsername()) ||
!vault_keyset->Save(vault_keyset->GetSourceFile())) {
LOG(WARNING) << "Failed to re-encrypt the old keyset";
return CRYPTOHOME_ERROR_BACKING_STORE_FAILURE;
}
return CRYPTOHOME_ERROR_NOT_SET;
}
CryptohomeErrorCode KeysetManagement::RemoveKeyset(
const Credentials& credentials, const KeyData& key_data) {
// This error condition should be caught by the caller.
if (key_data.label().empty())
return CRYPTOHOME_ERROR_KEY_NOT_FOUND;
const std::string obfuscated = credentials.GetObfuscatedUsername();
std::unique_ptr<VaultKeyset> remove_vk =
GetVaultKeyset(obfuscated, key_data.label());
if (!remove_vk.get()) {
LOG(WARNING) << "RemoveKeyset: key to remove not found";
return CRYPTOHOME_ERROR_KEY_NOT_FOUND;
}
std::unique_ptr<VaultKeyset> vk =
GetValidKeyset(credentials, nullptr /* error */);
if (!vk) {
// Differentiate between failure and non-existent.
if (!credentials.key_data().label().empty()) {
vk = GetVaultKeyset(obfuscated, credentials.key_data().label());
if (!vk.get()) {
LOG(WARNING) << "RemoveKeyset: key not found";
return CRYPTOHOME_ERROR_AUTHORIZATION_KEY_NOT_FOUND;
}
}
LOG(WARNING) << "RemoveKeyset: invalid authentication provided";
return CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED;
}
if (!ForceRemoveKeyset(obfuscated, remove_vk->GetLegacyIndex())) {
LOG(ERROR) << "RemoveKeyset: failed to remove keyset file";
return CRYPTOHOME_ERROR_BACKING_STORE_FAILURE;
}
return CRYPTOHOME_ERROR_NOT_SET;
}
bool KeysetManagement::ForceRemoveKeyset(const std::string& obfuscated,
int index) {
// Note, external callers should check credentials.
if (index < 0 || index >= kKeyFileMax)
return false;
std::unique_ptr<VaultKeyset> vk = LoadVaultKeysetForUser(obfuscated, index);
if (!vk) {
LOG(WARNING) << "ForceRemoveKeyset: keyset " << index << " for "
<< obfuscated << " does not exist";
// Since it doesn't exist, then we're done.
return true;
}
// Try removing the LE credential data, if applicable. But, don't abort if we
// fail. The leaf data will remain, but at least the SerializedVaultKeyset
// will be deleted.
if (vk->IsLECredential()) {
if (!crypto_->RemoveLECredential(vk->GetLELabel())) {
LOG(ERROR)
<< "ForceRemoveKeyset: Failed to remove LE credential metadata.";
}
}
base::FilePath path = VaultKeysetPath(obfuscated, index);
if (platform_->DeleteFileSecurely(path))
return true;
// TODO(wad) Add file zeroing here or centralize with other code.
return platform_->DeleteFile(path);
}
bool KeysetManagement::MoveKeyset(const std::string& obfuscated,
int src,
int dst) {
if (src < 0 || dst < 0 || src >= kKeyFileMax || dst >= kKeyFileMax)
return false;
base::FilePath src_path = VaultKeysetPath(obfuscated, src);
base::FilePath dst_path = VaultKeysetPath(obfuscated, dst);
if (!platform_->FileExists(src_path))
return false;
if (platform_->FileExists(dst_path))
return false;
// Grab the destination exclusively
FILE* vk_file = platform_->OpenFile(dst_path, "wx");
if (!vk_file)
return false;
// The creation occurred so there's no reason to keep the handle.
platform_->CloseFile(vk_file);
if (!platform_->Rename(src_path, dst_path))
return false;
return true;
}
std::unique_ptr<VaultKeyset> KeysetManagement::LoadVaultKeysetForUser(
const std::string& obfuscated_user, int index) const {
std::unique_ptr<VaultKeyset> keyset(
vault_keyset_factory_->New(platform_, crypto_));
// Load the encrypted keyset
base::FilePath user_key_file = VaultKeysetPath(obfuscated_user, index);
// We don't have keys yet, so just load it.
// TODO(wad) Move to passing around keysets and not serialized versions.
if (!keyset->Load(user_key_file)) {
LOG(ERROR) << "Failed to load keyset file for user " << obfuscated_user;
return nullptr;
}
keyset->SetLegacyIndex(index);
return keyset;
}
bool KeysetManagement::Migrate(const VaultKeyset& old_vk,
const Credentials& newcreds) {
int key_index = old_vk.GetLegacyIndex();
if (key_index == -1) {
LOG(ERROR) << "Attempted migration of key-less mount.";
return false;
}
std::string obfuscated_username = newcreds.GetObfuscatedUsername();
// Overwrite the existing keyset.
base::FilePath vk_path = old_vk.GetSourceFile();
std::unique_ptr<VaultKeyset> migrated_vk(
vault_keyset_factory_->New(platform_, crypto_));
migrated_vk->InitializeToAdd(old_vk);
if (old_vk.HasKeyData()) {
migrated_vk->SetKeyData(old_vk.GetKeyData());
}
if (!migrated_vk->Encrypt(newcreds.passkey(), obfuscated_username) ||
!migrated_vk->Save(vk_path)) {
LOG(WARNING) << "Failed to encrypt or write the new keyset to migrate.";
return false;
}
// Remove all other keysets during a "migration".
std::vector<int> key_indices;
if (!GetVaultKeysets(obfuscated_username, &key_indices)) {
LOG(WARNING) << "Failed to enumerate keysets after adding one. Weird.";
// Fallthrough: The user is migrated, but something else changed keys.
}
for (int index : key_indices) {
if (index == key_index)
continue;
LOG(INFO) << "Removing keyset " << index << " due to migration.";
ForceRemoveKeyset(obfuscated_username, index); // Failure is ok.
}
return true;
}
void KeysetManagement::ResetLECredentials(
const std::optional<Credentials>& creds,
const std::optional<VaultKeyset>& validated_vk,
const std::string& obfuscated) {
if (!creds.has_value() && !validated_vk.has_value()) {
LOG(WARNING) << "Neither credentials nor validated keyset is provided "
"for LE credential reset, reset skipped.";
return;
}
std::vector<int> key_indices;
if (!GetVaultKeysets(obfuscated, &key_indices)) {
LOG(WARNING) << "No valid keysets on disk for " << obfuscated;
return;
}
std::unique_ptr<VaultKeyset> vk;
if (validated_vk.has_value()) {
vk = std::make_unique<VaultKeyset>(validated_vk.value());
}
if (!validated_vk.has_value()) {
// Make sure the credential can actually be used for sign-in.
// It is also the easiest way to get a valid keyset.
vk = GetValidKeyset(creds.value(), nullptr /* error */);
if (!vk) {
LOG(WARNING) << "The provided credentials are incorrect or invalid"
" for LE credential reset, reset skipped.";
return;
}
}
for (int index : key_indices) {
std::unique_ptr<VaultKeyset> vk_reset =
LoadVaultKeysetForUser(obfuscated, index);
if (!vk_reset || !vk_reset->IsLECredential() || // Skip non-LE Credentials.
crypto_->GetWrongAuthAttempts(vk_reset->GetLELabel()) == 0) {
continue;
}
CryptoError err;
if (!crypto_->ResetLECredential(*vk_reset, *vk, &err)) {
LOG(WARNING) << "Failed to reset an LE credential: " << err;
continue;
}
vk_reset->SetAuthLocked(false);
if (!vk_reset->Save(vk_reset->GetSourceFile())) {
LOG(WARNING) << "Failed to clear auth_locked in VaultKeyset on disk.";
}
}
}
void KeysetManagement::RemoveLECredentials(
const std::string& obfuscated_username) {
std::vector<int> key_indices;
if (!GetVaultKeysets(obfuscated_username, &key_indices)) {
LOG(WARNING) << "No valid keysets on disk for " << obfuscated_username;
return;
}
for (int index : key_indices) {
std::unique_ptr<VaultKeyset> vk_remove =
LoadVaultKeysetForUser(obfuscated_username, index);
if (!vk_remove ||
!vk_remove->IsLECredential()) { // Skip non-LE Credentials.
continue;
}
uint64_t label = vk_remove->GetLELabel();
if (!crypto_->RemoveLECredential(label)) {
LOG(WARNING) << "Failed to remove an LE credential, label: " << label;
continue;
}
// Remove the cryptohome VaultKeyset data.
base::FilePath vk_path = VaultKeysetPath(obfuscated_username, index);
platform_->DeleteFile(vk_path);
}
return;
}
bool KeysetManagement::UserExists(const std::string& obfuscated_username) {
base::FilePath user_dir = UserPath(obfuscated_username);
return platform_->DirectoryExists(user_dir);
}
brillo::SecureBlob KeysetManagement::GetPublicMountPassKey(
const std::string& account_id) {
brillo::SecureBlob public_mount_salt;
if (!GetPublicMountSalt(platform_, &public_mount_salt)) {
LOG(ERROR) << "Could not get or create public salt from file";
// Ensure that it is empty so that caller can confirm there was an error.
public_mount_salt.clear();
return public_mount_salt;
}
brillo::SecureBlob passkey;
Crypto::PasswordToPasskey(account_id.c_str(), public_mount_salt, &passkey);
return passkey;
}
// TODO(b/205759690, dlunev): can be removed after a stepping stone release.
base::Time KeysetManagement::GetPerIndexTimestampFileData(
const std::string& obfuscated, int index) {
brillo::Blob tcontents;
if (!platform_->ReadFile(UserActivityPerIndexTimestampPath(obfuscated, index),
&tcontents)) {
return base::Time();
}
cryptohome::Timestamp timestamp;
if (!timestamp.ParseFromArray(tcontents.data(), tcontents.size())) {
return base::Time();
}
return base::Time::FromInternalValue(timestamp.timestamp());
}
// TODO(b/205759690, dlunev): can be removed after a stepping stone release.
base::Time KeysetManagement::GetKeysetBoundTimestamp(
const std::string& obfuscated) {
base::Time timestamp = base::Time();
std::vector<int> key_indices;
GetVaultKeysets(obfuscated, &key_indices);
for (int index : key_indices) {
std::unique_ptr<VaultKeyset> keyset =
LoadVaultKeysetForUser(obfuscated, index);
if (keyset.get() && keyset->HasLastActivityTimestamp()) {
const base::Time new_timestamp =
base::Time::FromInternalValue(keyset->GetLastActivityTimestamp());
if (new_timestamp > timestamp) {
timestamp = new_timestamp;
}
}
const base::Time ts_from_index_file =
GetPerIndexTimestampFileData(obfuscated, index);
if (ts_from_index_file > timestamp) {
timestamp = ts_from_index_file;
}
}
return timestamp;
}
void KeysetManagement::RecordAllVaultKeysetMetrics(
const std::string& obfuscated) const {
VaultKeysetMetrics keyset_metrics;
std::vector<int> key_indices;
GetVaultKeysets(obfuscated, &key_indices);
for (int index : key_indices) {
std::unique_ptr<VaultKeyset> vk = LoadVaultKeysetForUser(obfuscated, index);
if (!vk) {
continue;
} else {
if (!RecordVaultKeysetMetrics(*vk.get(), keyset_metrics)) {
LOG(ERROR) << "Metrics not recorded for " << vk->GetLabel();
}
}
}
ReportVaultKeysetMetrics(keyset_metrics);
}
bool KeysetManagement::RecordVaultKeysetMetrics(
const VaultKeyset& vk, VaultKeysetMetrics& keyset_metrics) const {
if (!vk.HasKeyData()) {
LOG(ERROR) << "VaultKeyset doesn't have a valid KeyData field.";
return false;
}
if (vk.GetKeyData().label().empty()) {
// VaultKeyset label is empty.
if (vk.IsLECredential()) {
keyset_metrics.empty_label_le_cred_count++;
} else {
keyset_metrics.empty_label_count++;
}
} else if (vk.IsLECredential()) {
// VaultKeyset is PIN based, label is non-empty.
keyset_metrics.le_cred_count++;
} else {
switch (vk.GetKeyData().type()) {
case KeyData::KEY_TYPE_PASSWORD:
if (vk.GetKeyData().has_provider_data()) {
// VaultKeyset is based on SmartUnlock/EasyUnlock.
keyset_metrics.smart_unlock_count++;
} else {
// VaultKeyset is password based.
keyset_metrics.password_count++;
}
break;
case KeyData::KEY_TYPE_CHALLENGE_RESPONSE:
// VaultKeyset is smartcard/challenge-response based.
keyset_metrics.smartcard_count++;
break;
case KeyData::KEY_TYPE_FINGERPRINT:
// VaultKeyset is fingerprint-based.
keyset_metrics.fingerprint_count++;
break;
case KeyData::KEY_TYPE_KIOSK:
// VaultKeyset is kiosk-based.
keyset_metrics.kiosk_count++;
break;
default:
keyset_metrics.unclassified_count++;
break;
}
}
return true;
}
// TODO(b/205759690, dlunev): can be removed after a stepping stone release.
void KeysetManagement::CleanupPerIndexTimestampFiles(
const std::string& obfuscated) {
for (int i = 0; i < kKeyFileMax; ++i) {
std::ignore = platform_->DeleteFileDurable(
UserActivityPerIndexTimestampPath(obfuscated, i));
}
}
bool KeysetManagement::AddResetSeedIfMissing(VaultKeyset& vault_keyset) {
bool has_reset_seed = vault_keyset.HasWrappedResetSeed();
if (has_reset_seed) {
// No need to update the vault keyset.
return false;
}
ReportUsageOfLegacyCodePath(
LegacyCodePathLocation::kGenerateResetSeedDuringAddKey, has_reset_seed);
LOG(INFO) << "Keyset lacks reset_seed; generating one.";
vault_keyset.CreateRandomResetSeed();
return true;
}
CryptohomeErrorCode KeysetManagement::SaveKeysetWithKeyBlobs(
VaultKeyset& vault_keyset,
const KeyBlobs& key_blobs,
const AuthBlockState& auth_state) {
if (!vault_keyset.EncryptEx(key_blobs, auth_state) ||
!vault_keyset.Save(vault_keyset.GetSourceFile())) {
LOG(WARNING) << "Failed to encrypt the keyset";
return CRYPTOHOME_ERROR_BACKING_STORE_FAILURE;
}
return CRYPTOHOME_ERROR_NOT_SET;
}
} // namespace cryptohome