blob: 77f3a54a15f514c56e7ae7bdcf8c58addb34b49e [file] [log] [blame]
// Copyright 2020 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/le_credential_manager_impl.h"
#include <fcntl.h>
#include <optional>
#include <string>
#include <utility>
#include <base/check.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <libhwsec-foundation/crypto/secure_blob_util.h>
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/error/cryptohome_tpm_error.h"
#include "cryptohome/error/location_utils.h"
namespace {
// Constants used to define the hash tree.
constexpr uint32_t kLengthLabels = 14;
constexpr uint32_t kBitsPerLevel = 2;
} // namespace
using ::cryptohome::error::CryptohomeLECredError;
using ::cryptohome::error::CryptohomeTPMError;
using ::cryptohome::error::ErrorAction;
using ::cryptohome::error::ErrorActionSet;
using ::hwsec_foundation::GetSecureRandom;
using ::hwsec_foundation::status::MakeStatus;
using ::hwsec_foundation::status::OkStatus;
using ::hwsec_foundation::status::StatusChain;
using CredentialTreeResult = hwsec::PinWeaverFrontend::CredentialTreeResult;
using DelaySchedule = hwsec::PinWeaverFrontend::DelaySchedule;
using GetLogResult = hwsec::PinWeaverFrontend::GetLogResult;
using ReplayLogOperationResult =
hwsec::PinWeaverFrontend::ReplayLogOperationResult;
namespace cryptohome {
LECredentialManagerImpl::LECredentialManagerImpl(
hwsec::PinWeaverFrontend* pinweaver, const base::FilePath& le_basedir)
: is_locked_(false), pinweaver_(pinweaver), basedir_(le_basedir) {
CHECK(pinweaver_);
// Check if hash tree already exists.
bool new_hash_tree = !base::PathExists(le_basedir);
hash_tree_ = std::make_unique<SignInHashTree>(kLengthLabels, kBitsPerLevel,
le_basedir);
if (!hash_tree_->IsValid()) {
LOG(ERROR)
<< "Failed to initialize LE credential manager: invalid hash tree";
return;
}
// Reset the root hash in the TPM to its initial value.
if (new_hash_tree) {
hwsec::StatusOr<CredentialTreeResult> result =
pinweaver_->Reset(kBitsPerLevel, kLengthLabels);
if (!result.ok()) {
LOG(ERROR) << "Failed to reset pinweaver: " << result.status();
return;
}
root_hash_ = result->new_root;
hash_tree_->GenerateAndStoreHashCache();
}
}
LECredStatus LECredentialManagerImpl::InsertCredential(
const std::vector<hwsec::OperationPolicySetting>& policies,
const brillo::SecureBlob& le_secret,
const brillo::SecureBlob& he_secret,
const brillo::SecureBlob& reset_secret,
const DelaySchedule& delay_sched,
std::optional<uint32_t> expiration_delay,
uint64_t* ret_label) {
return InsertLeaf(nullptr, policies, &le_secret, &he_secret, reset_secret,
delay_sched, expiration_delay, /*is_rate_limiter=*/false,
ret_label);
}
LECredStatus LECredentialManagerImpl::CheckCredential(
uint64_t label,
const brillo::SecureBlob& le_secret,
brillo::SecureBlob* he_secret,
brillo::SecureBlob* reset_secret) {
return CheckSecret(label, le_secret, he_secret, reset_secret, false, true);
}
LECredStatus LECredentialManagerImpl::ResetCredential(
uint64_t label, const brillo::SecureBlob& reset_secret, bool strong_reset) {
return CheckSecret(label, reset_secret, nullptr, nullptr, strong_reset,
false);
}
LECredStatus LECredentialManagerImpl::RemoveCredential(uint64_t label) {
if (!hash_tree_->IsValid() || !Sync()) {
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidTreeInRemoveCred),
ErrorActionSet({ErrorAction::kReboot}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
SignInHashTree::Label label_object(label, kLengthLabels, kBitsPerLevel);
brillo::Blob orig_cred, orig_mac;
std::vector<brillo::Blob> h_aux;
bool metadata_lost;
LECredStatus ret = RetrieveLabelInfo(label_object, &orig_cred, &orig_mac,
&h_aux, &metadata_lost);
if (!ret.ok()) {
ReportLEResult(kLEOpRemove, kLEActionLoadFromDisk,
ret->local_lecred_error());
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManRetrieveLabelFailedInRemoveCred))
.Wrap(std::move(ret));
}
hwsec::StatusOr<CredentialTreeResult> result =
pinweaver_->RemoveCredential(label, h_aux, orig_mac);
if (!result.ok()) {
ReportLEResult(kLEOpRemove, kLEActionBackend, LE_CRED_ERROR_HASH_TREE);
LOG(ERROR) << "Error executing TPM RemoveCredential command: "
<< result.status();
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManRemoveCredFailedInRemoveCred),
ErrorActionSet({ErrorAction::kReboot}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
root_hash_ = result->new_root;
ReportLEResult(kLEOpRemove, kLEActionBackend, LE_CRED_SUCCESS);
if (!hash_tree_->RemoveLabel(label_object)) {
LOG(ERROR) << "Removed label from TPM but hash tree removal "
"encountered error: "
<< label;
ReportLEResult(kLEOpRemove, kLEActionSaveToDisk, LE_CRED_ERROR_HASH_TREE);
// This is an un-salvageable state. We can't make LE updates anymore,
// since the disk state can't be updated.
// We block further LE operations until at least the next boot.
// The hope is that on reboot, the disk operations start working. In that
// case, we will be able to replay this operation from the TPM log.
is_locked_ = true;
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManRemoveLabelFailedInRemoveCred),
ErrorActionSet({ErrorAction::kReboot}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
ReportLEResult(kLEOpRemove, kLEActionSaveToDisk, LE_CRED_SUCCESS);
return OkStatus<CryptohomeLECredError>();
}
LECredStatus LECredentialManagerImpl::CheckSecret(
uint64_t label,
const brillo::SecureBlob& secret,
brillo::SecureBlob* he_secret,
brillo::SecureBlob* reset_secret,
bool strong_reset,
bool is_le_secret) {
if (!hash_tree_->IsValid() || !Sync()) {
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidTreeInCheckSecret),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
if (he_secret) {
he_secret->clear();
}
if (reset_secret) {
reset_secret->clear();
}
const char* uma_log_op = is_le_secret ? kLEOpCheck : kLEOpReset;
SignInHashTree::Label label_object(label, kLengthLabels, kBitsPerLevel);
brillo::Blob orig_cred, orig_mac;
std::vector<brillo::Blob> h_aux;
bool metadata_lost;
LECredStatus ret = RetrieveLabelInfo(label_object, &orig_cred, &orig_mac,
&h_aux, &metadata_lost);
if (!ret.ok()) {
ReportLEResult(uma_log_op, kLEActionLoadFromDisk,
ret->local_lecred_error());
return ret;
}
if (metadata_lost) {
LOG(ERROR) << "Invalid cred metadata for label: " << label;
ReportLEResult(uma_log_op, kLEActionLoadFromDisk,
LE_CRED_ERROR_INVALID_METADATA);
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidMetadataInCheckSecret),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_INVALID_METADATA);
}
ReportLEResult(uma_log_op, kLEActionLoadFromDisk, LE_CRED_SUCCESS);
hwsec::StatusOr<CredentialTreeResult> result =
is_le_secret
? pinweaver_->CheckCredential(label, h_aux, orig_cred, secret)
: pinweaver_->ResetCredential(label, h_aux, orig_cred, secret,
strong_reset);
if (!result.ok()) {
LOG(ERROR) << "Failed to call pinweaver in check secret: "
<< result.status();
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManPinWeaverFailedInCheckSecret),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE)
.Wrap(MakeStatus<CryptohomeTPMError>(std::move(result).status()));
}
root_hash_ = result->new_root;
ReportLEResult(uma_log_op, kLEActionBackend,
BackendErrorToCredError(result->error));
// Store the new credential meta data and MAC in case the backend performed a
// state change. Note that this might also be needed for some failure cases.
if (result->new_cred_metadata.has_value() && result->new_mac.has_value()) {
if (!hash_tree_->StoreLabel(label_object, result->new_mac.value(),
result->new_cred_metadata.value(), false)) {
ReportLEResult(uma_log_op, kLEActionSaveToDisk, LE_CRED_ERROR_HASH_TREE);
LOG(ERROR) << "Failed to update credential in disk hash tree for label: "
<< label;
// This is an un-salvageable state. We can't make LE updates anymore,
// since the disk state can't be updated.
// We block further LE operations until at least the next boot.
// The hope is that on reboot, the disk operations start working. In that
// case, we will be able to replay this operation from the TPM log.
is_locked_ = true;
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManStoreLabelFailedInCheckSecret),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
}
if (he_secret && result->he_secret.has_value()) {
*he_secret = result->he_secret.value();
}
if (reset_secret && result->reset_secret.has_value()) {
*reset_secret = result->reset_secret.value();
}
ReportLEResult(uma_log_op, kLEActionSaveToDisk, LE_CRED_SUCCESS);
LECredStatus converted = ConvertTpmError(result->error);
if (converted.ok())
return OkStatus<CryptohomeLECredError>();
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManTpmFailedInCheckSecret),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}))
.Wrap(std::move(converted));
}
int LECredentialManagerImpl::GetWrongAuthAttempts(uint64_t label) {
if (!hash_tree_->IsValid()) {
return -1;
}
SignInHashTree::Label label_object(label, kLengthLabels, kBitsPerLevel);
brillo::Blob orig_cred, orig_mac;
std::vector<brillo::Blob> h_aux;
bool metadata_lost;
LECredStatus ret = RetrieveLabelInfo(label_object, &orig_cred, &orig_mac,
&h_aux, &metadata_lost);
if (!ret.ok()) {
LOG(ERROR) << "Failed to retrieve label info: " << std::move(ret);
return -1;
}
hwsec::StatusOr<int> result = pinweaver_->GetWrongAuthAttempts(orig_cred);
if (!result.ok()) {
LOG(ERROR) << "Failed to get wrong auth attempts: "
<< std::move(result).status();
return -1;
}
return result.value();
}
LECredStatusOr<uint32_t> LECredentialManagerImpl::GetDelayInSeconds(
uint64_t label) {
if (!hash_tree_->IsValid()) {
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidTreeInGetDelayInSeconds),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
SignInHashTree::Label label_object(label, kLengthLabels, kBitsPerLevel);
brillo::Blob orig_cred, orig_mac;
std::vector<brillo::Blob> h_aux;
bool metadata_lost;
LECredStatus status = RetrieveLabelInfo(label_object, &orig_cred, &orig_mac,
&h_aux, &metadata_lost);
if (!status.ok()) {
ReportLEResult(kLEOpGetDelayInSeconds, kLEActionLoadFromDisk,
status->local_lecred_error());
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(
kLocLECredManRetrieveLabelFailedInGetDelayInSeconds))
.Wrap(std::move(status));
}
if (metadata_lost) {
LOG(ERROR) << "Invalid cred metadata for label: " << label;
ReportLEResult(kLEOpGetDelayInSeconds, kLEActionLoadFromDisk,
LE_CRED_ERROR_INVALID_METADATA);
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidMetadataInGetDelayInSeconds),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_INVALID_METADATA);
}
ReportLEResult(kLEOpGetDelayInSeconds, kLEActionLoadFromDisk,
LE_CRED_SUCCESS);
hwsec::StatusOr<uint32_t> result = pinweaver_->GetDelayInSeconds(orig_cred);
if (!result.ok()) {
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(
kLocLECredManPinWeaverFailedInGetDelayInSeconds),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE)
.Wrap(MakeStatus<CryptohomeTPMError>(std::move(result).status()));
}
return result.value();
}
LECredStatusOr<std::optional<uint32_t>>
LECredentialManagerImpl::GetExpirationInSeconds(uint64_t label) {
if (!hash_tree_->IsValid()) {
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidTreeInGetExpirationInSeconds),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
SignInHashTree::Label label_object(label, kLengthLabels, kBitsPerLevel);
brillo::Blob orig_cred, orig_mac;
std::vector<brillo::Blob> h_aux;
bool metadata_lost;
LECredStatus status = RetrieveLabelInfo(label_object, &orig_cred, &orig_mac,
&h_aux, &metadata_lost);
if (!status.ok()) {
ReportLEResult(kLEOpGetExpirationInSeconds, kLEActionLoadFromDisk,
status->local_lecred_error());
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(
kLocLECredManRetrieveLabelFailedInGetExpirationInSeconds))
.Wrap(std::move(status));
}
if (metadata_lost) {
LOG(ERROR) << "Invalid cred metadata for label: " << label;
ReportLEResult(kLEOpGetExpirationInSeconds, kLEActionLoadFromDisk,
LE_CRED_ERROR_INVALID_METADATA);
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(
kLocLECredManInvalidMetadataInGetExpirationInSeconds),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_INVALID_METADATA);
}
ReportLEResult(kLEOpGetExpirationInSeconds, kLEActionLoadFromDisk,
LE_CRED_SUCCESS);
hwsec::StatusOr<std::optional<uint32_t>> result =
pinweaver_->GetExpirationInSeconds(orig_cred);
if (!result.ok()) {
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(
kLocLECredManPinWeaverFailedInGetExpirationInSeconds),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE)
.Wrap(MakeStatus<CryptohomeTPMError>(std::move(result).status()));
}
return result.value();
}
LECredStatus LECredentialManagerImpl::InsertRateLimiter(
uint8_t auth_channel,
const std::vector<hwsec::OperationPolicySetting>& policies,
const brillo::SecureBlob& reset_secret,
const DelaySchedule& delay_sched,
std::optional<uint32_t> expiration_delay,
uint64_t* ret_label) {
return InsertLeaf(&auth_channel, policies, nullptr, nullptr, reset_secret,
delay_sched, expiration_delay, /*is_rate_limiter=*/true,
ret_label);
}
LECredStatusOr<LECredentialManager::StartBiometricsAuthReply>
LECredentialManagerImpl::StartBiometricsAuth(
uint8_t auth_channel,
uint64_t label,
const brillo::SecureBlob& client_nonce) {
if (!hash_tree_->IsValid() || !Sync()) {
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidTreeInStartBiometricsAuth),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
SignInHashTree::Label label_object(label, kLengthLabels, kBitsPerLevel);
brillo::Blob orig_cred, orig_mac;
std::vector<brillo::Blob> h_aux;
bool metadata_lost;
LECredStatus ret = RetrieveLabelInfo(label_object, &orig_cred, &orig_mac,
&h_aux, &metadata_lost);
if (!ret.ok()) {
ReportLEResult(kLEOpStartBiometricsAuth, kLEActionLoadFromDisk,
ret->local_lecred_error());
return ret;
}
if (metadata_lost) {
LOG(ERROR) << "Invalid cred metadata for label: " << label;
ReportLEResult(kLEOpStartBiometricsAuth, kLEActionLoadFromDisk,
LE_CRED_ERROR_INVALID_METADATA);
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidMetadataInStartBiometricsAuth),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_INVALID_METADATA);
}
ReportLEResult(kLEOpStartBiometricsAuth, kLEActionLoadFromDisk,
LE_CRED_SUCCESS);
hwsec::StatusOr<CredentialTreeResult> result =
pinweaver_->StartBiometricsAuth(auth_channel, label, h_aux, orig_cred,
client_nonce);
if (!result.ok()) {
LOG(ERROR) << "Failed to call pinweaver in start biometrics auth: "
<< result.status();
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(
kLocLECredManPinWeaverFailedInStartBiometricsAuth),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE)
.Wrap(MakeStatus<CryptohomeTPMError>(std::move(result).status()));
}
root_hash_ = result->new_root;
ReportLEResult(kLEOpStartBiometricsAuth, kLEActionBackend,
BackendErrorToCredError(result->error));
// Store the new credential meta data and MAC in case the backend performed a
// state change. Note that this might also be needed for some failure cases.
if (result->new_cred_metadata.has_value() && result->new_mac.has_value()) {
if (!hash_tree_->StoreLabel(label_object, result->new_mac.value(),
result->new_cred_metadata.value(), false)) {
ReportLEResult(kLEOpStartBiometricsAuth, kLEActionSaveToDisk,
LE_CRED_ERROR_HASH_TREE);
LOG(ERROR) << "Failed to update credential in disk hash tree for label: "
<< label;
// This is an un-salvageable state. We can't make LE updates anymore,
// since the disk state can't be updated.
// We block further LE operations until at least the next boot.
// The hope is that on reboot, the disk operations start working. In that
// case, we will be able to replay this operation from the TPM log.
is_locked_ = true;
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(
kLocLECredManStoreLabelFailedInStartBiometricsAuth),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
}
ReportLEResult(kLEOpStartBiometricsAuth, kLEActionSaveToDisk,
LE_CRED_SUCCESS);
LECredStatus converted = ConvertTpmError(result->error);
if (!converted.ok()) {
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManTpmFailedInStartBiometricsAuth),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}))
.Wrap(std::move(converted));
}
if (!result->server_nonce.has_value() || !result->iv.has_value() ||
!result->encrypted_he_secret.has_value()) {
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidOutputInStartBiometricsAuth),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
LECredentialManager::StartBiometricsAuthReply reply{
.server_nonce = std::move(result->server_nonce.value()),
.iv = std::move(result->iv.value()),
.encrypted_he_secret = std::move(result->encrypted_he_secret.value()),
};
return reply;
}
LECredStatus LECredentialManagerImpl::InsertLeaf(
uint8_t* auth_channel,
const std::vector<hwsec::OperationPolicySetting>& policies,
const brillo::SecureBlob* le_secret,
const brillo::SecureBlob* he_secret,
const brillo::SecureBlob& reset_secret,
const DelaySchedule& delay_sched,
std::optional<uint32_t> expiration_delay,
bool is_rate_limiter,
uint64_t* ret_label) {
if (!hash_tree_->IsValid() || !Sync()) {
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidTreeInInsertCred),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
if ((is_rate_limiter && !auth_channel) ||
(!is_rate_limiter && (!le_secret || !he_secret))) {
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidParamInInsertCred),
error::ErrorActionSet({error::ErrorAction::kDevCheckUnexpectedState}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
const char* uma_log_op =
is_rate_limiter ? kLEOpInsertRateLimiter : kLEOpInsert;
SignInHashTree::Label label = hash_tree_->GetFreeLabel();
if (!label.is_valid()) {
LOG(ERROR) << "No free labels available.";
ReportLEResult(uma_log_op, kLEActionLoadFromDisk,
LE_CRED_ERROR_NO_FREE_LABEL);
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManLabelUnavailableInInsertCred),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_NO_FREE_LABEL);
}
std::vector<brillo::Blob> h_aux = GetAuxHashes(label);
if (h_aux.empty()) {
LOG(ERROR) << "Error getting aux hashes for label: " << label.value();
ReportLEResult(uma_log_op, kLEActionLoadFromDisk, LE_CRED_ERROR_HASH_TREE);
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManEmptyAuxInInsertCred),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
ReportLEResult(uma_log_op, kLEActionLoadFromDisk, LE_CRED_SUCCESS);
hwsec::StatusOr<CredentialTreeResult> result =
is_rate_limiter
? pinweaver_->InsertRateLimiter(*auth_channel, policies,
label.value(), h_aux, reset_secret,
delay_sched, expiration_delay)
: pinweaver_->InsertCredential(policies, label.value(), h_aux,
*le_secret, *he_secret, reset_secret,
delay_sched, expiration_delay);
if (!result.ok()) {
LOG(ERROR) << "Error executing pinweaver InsertCredential command: "
<< result.status();
ReportLEResult(uma_log_op, kLEActionBackend, LE_CRED_ERROR_HASH_TREE);
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManTpmFailedInInsertCred),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE)
.Wrap(MakeStatus<CryptohomeTPMError>(std::move(result).status()));
}
root_hash_ = result->new_root;
ReportLEResult(uma_log_op, kLEActionBackend, LE_CRED_SUCCESS);
if (!hash_tree_->StoreLabel(label, result->new_mac.value(),
result->new_cred_metadata.value(), false)) {
ReportLEResult(uma_log_op, kLEActionSaveToDisk, LE_CRED_ERROR_HASH_TREE);
LOG(ERROR) << "InsertCredential succeeded in PinWeaver but disk updated "
"failed, label: "
<< label.value();
// The insert into the disk hash tree failed, so let us remove
// the credential from the TPM state so that we are back to where
// we started.
hwsec::StatusOr<CredentialTreeResult> remove_result =
pinweaver_->RemoveCredential(label.value(), h_aux,
result->new_mac.value());
if (!remove_result.ok()) {
ReportLEResult(uma_log_op, kLEActionBackendRecoverInsert,
LE_CRED_ERROR_HASH_TREE);
LOG(ERROR)
<< " Failed to rewind aborted InsertCredential in PinWeaver, label: "
<< label.value() << ": " << std::move(remove_result.status());
// The attempt to undo the PinWeaver side operation has also failed, Can't
// do much else now. We block further LE operations until at least the
// next boot.
is_locked_ = true;
} else {
root_hash_ = remove_result->new_root;
}
ReportLEResult(uma_log_op, kLEActionBackendRecoverInsert, LE_CRED_SUCCESS);
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManStoreFailedInInsertCred),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
ReportLEResult(uma_log_op, kLEActionSaveToDisk, LE_CRED_SUCCESS);
*ret_label = label.value();
return OkStatus<CryptohomeLECredError>();
}
LECredStatus LECredentialManagerImpl::RetrieveLabelInfo(
const SignInHashTree::Label& label,
brillo::Blob* cred_metadata,
brillo::Blob* mac,
std::vector<brillo::Blob>* h_aux,
bool* metadata_lost) {
if (!hash_tree_->GetLabelData(label, mac, cred_metadata, metadata_lost)) {
LOG(ERROR) << "Failed to get the credential in disk hash tree for label: "
<< label.value();
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManTreeGetDataFailedInRetrieveLabel),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_INVALID_LABEL);
}
// Any empty |cred_metadata| means the label isn't present in the hash tree.
if (cred_metadata->empty()) {
LOG(ERROR) << "Label doesn't exist in hash tree: " << label.value();
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManNonexistentInRetrieveLabel),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_INVALID_LABEL);
}
*h_aux = GetAuxHashes(label);
if (h_aux->empty()) {
LOG(ERROR) << "Error retrieving aux hashes from hash tree for label: "
<< label.value();
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManEmptyAuxInRetrieveLabel),
ErrorActionSet({ErrorAction::kReboot, ErrorAction::kAuth}),
LECredError::LE_CRED_ERROR_HASH_TREE);
}
return OkStatus<CryptohomeLECredError>();
}
std::vector<brillo::Blob> LECredentialManagerImpl::GetAuxHashes(
const SignInHashTree::Label& label) {
auto aux_labels = hash_tree_->GetAuxiliaryLabels(label);
std::vector<brillo::Blob> h_aux;
if (aux_labels.empty()) {
LOG(ERROR) << "Error getting h_aux for label:" << label.value();
return h_aux;
}
h_aux.reserve(aux_labels.size());
for (auto cur_aux_label : aux_labels) {
brillo::Blob hash, cred_data;
bool metadata_lost;
if (!hash_tree_->GetLabelData(cur_aux_label, &hash, &cred_data,
&metadata_lost)) {
LOG(INFO) << "Error getting aux label :" << cur_aux_label.value()
<< " for label: " << label.value();
h_aux.clear();
break;
}
h_aux.push_back(std::move(hash));
}
return h_aux;
}
LECredStatus LECredentialManagerImpl::ConvertTpmError(
CredentialTreeResult::ErrorCode err) {
LECredError conv_err = BackendErrorToCredError(err);
// We want to split out various cases of LE Credential errors so that their
// statistics are tallied separately from the other LECredential errors.
switch (conv_err) {
case LE_CRED_SUCCESS:
return OkStatus<CryptohomeLECredError>();
case LE_CRED_ERROR_INVALID_LE_SECRET:
// Incorrect password.
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidLESecretInConvertTpmError),
ErrorActionSet({ErrorAction::kIncorrectAuth}), conv_err);
case LE_CRED_ERROR_INVALID_RESET_SECRET:
// The reset secret is invalid. We don't expect this to occur so
// kDevCheckUnexpectedState is specified. The user can always try to use
// non-LE auth methods to get around this, so kAuth is specified.
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManInvalidResetSecretInConvertTpmError),
ErrorActionSet(
{ErrorAction::kDevCheckUnexpectedState, ErrorAction::kAuth}),
conv_err);
case LE_CRED_ERROR_TOO_MANY_ATTEMPTS:
// Too many attempts.
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManTooManyAttemptsInConvertTpmError),
ErrorActionSet({ErrorAction::kTpmLockout}), conv_err);
case LE_CRED_ERROR_PCR_NOT_MATCH:
// In usual operation, we don't expect PCR to be incorrect, so
// kDevCheckUnexpectedState is specified. If the PCR is incorrect, a
// reboot might fix it. Alternatively, the user can try a different auth
// that doesn't require a correct PCR value. If all else fails, powerwash
// might allow us to restart from a correct PCR value.
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManPCRNotMatchInConvertTpmError),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState,
ErrorAction::kAuth, ErrorAction::kReboot,
ErrorAction::kPowerwash}),
conv_err);
case LE_CRED_ERROR_HASH_TREE:
// In usual operation, we don't expect the hash tree to go bad, so
// kDevCheckUnexpectedState is specified. The user can always try to use
// non-LE auth methods to get around this, so kAuth is specified. User can
// delete the vault start fresh so kDeleteVault is specified.
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManHashTreeInConvertTpmError),
ErrorActionSet({ErrorAction::kDevCheckUnexpectedState,
ErrorAction::kAuth, ErrorAction::kDeleteVault}),
conv_err);
default:
ErrorActionSet action_set;
return MakeStatus<CryptohomeLECredError>(
CRYPTOHOME_ERR_LOC(kLocLECredManConvertTpmError),
std::move(action_set), conv_err);
}
}
LECredError LECredentialManagerImpl::BackendErrorToCredError(
CredentialTreeResult::ErrorCode err) {
switch (err) {
case CredentialTreeResult::ErrorCode::kSuccess:
return LE_CRED_SUCCESS;
case CredentialTreeResult::ErrorCode::kInvalidLeSecret:
return LE_CRED_ERROR_INVALID_LE_SECRET;
case CredentialTreeResult::ErrorCode::kInvalidResetSecret:
return LE_CRED_ERROR_INVALID_RESET_SECRET;
case CredentialTreeResult::ErrorCode::kTooManyAttempts:
return LE_CRED_ERROR_TOO_MANY_ATTEMPTS;
case CredentialTreeResult::ErrorCode::kHashTreeOutOfSync:
return LE_CRED_ERROR_HASH_TREE;
case CredentialTreeResult::ErrorCode::kPolicyNotMatch:
return LE_CRED_ERROR_PCR_NOT_MATCH;
default:
return LE_CRED_ERROR_HASH_TREE;
}
}
bool LECredentialManagerImpl::Sync() {
if (is_locked_) {
ReportLESyncOutcome(LE_CRED_ERROR_LE_LOCKED);
return false;
}
brillo::Blob disk_root_hash;
hash_tree_->GetRootHash(&disk_root_hash);
// If we don't have it, get the root hash from the LE Backend.
std::vector<GetLogResult::LogEntry> log;
if (root_hash_.empty()) {
hwsec::StatusOr<GetLogResult> result = pinweaver_->GetLog(disk_root_hash);
if (!result.ok()) {
ReportLEResult(kLEOpSync, kLEActionBackendGetLog,
LE_CRED_ERROR_UNCLASSIFIED);
ReportLESyncOutcome(LE_CRED_ERROR_HASH_TREE);
LOG(ERROR) << "Couldn't get LE Log: " << std::move(result).status();
is_locked_ = true;
return false;
}
root_hash_ = result->root_hash;
log = std::move(result->log_entries);
ReportLEResult(kLEOpSync, kLEActionBackendGetLog, LE_CRED_SUCCESS);
}
if (disk_root_hash == root_hash_) {
ReportLESyncOutcome(LE_CRED_SUCCESS);
return true;
}
LOG(WARNING) << "LE HashCache is stale; reconstructing.";
hash_tree_->GenerateAndStoreHashCache();
disk_root_hash.clear();
hash_tree_->GetRootHash(&disk_root_hash);
if (disk_root_hash == root_hash_) {
ReportLESyncOutcome(LE_CRED_SUCCESS);
return true;
}
LOG(WARNING) << "LE sync loss between OS and GSC, attempting log replay.";
// Get the log again, since |disk_root_hash| may have changed.
log.clear();
hwsec::StatusOr<GetLogResult> result = pinweaver_->GetLog(disk_root_hash);
if (!result.ok()) {
ReportLEResult(kLEOpSync, kLEActionBackendGetLog,
LE_CRED_ERROR_UNCLASSIFIED);
ReportLESyncOutcome(LE_CRED_ERROR_HASH_TREE);
LOG(ERROR) << "Couldn't get LE Log.";
is_locked_ = true;
return false;
}
root_hash_ = result->root_hash;
log = std::move(result->log_entries);
ReportLEResult(kLEOpSync, kLEActionBackendGetLog, LE_CRED_SUCCESS);
if (!ReplayLogEntries(log, disk_root_hash)) {
ReportLESyncOutcome(LE_CRED_ERROR_HASH_TREE);
LOG(ERROR) << "Failed to Synchronize LE disk state after log replay.";
is_locked_ = true;
return false;
}
ReportLESyncOutcome(LE_CRED_SUCCESS);
return true;
}
bool LECredentialManagerImpl::ReplayInsert(uint64_t label,
const brillo::Blob& log_root,
const brillo::Blob& mac) {
LOG(INFO) << "Replaying insert for label " << label;
// Fill cred_metadata with some random data since LECredentialManager
// considers empty cred_metadata as a non-existent label.
brillo::Blob cred_metadata(mac.size());
GetSecureRandom(cred_metadata.data(), cred_metadata.size());
SignInHashTree::Label label_obj(label, kLengthLabels, kBitsPerLevel);
if (!hash_tree_->StoreLabel(label_obj, mac, cred_metadata, true)) {
ReportLEResult(kLEOpReplayInsert, kLEActionSaveToDisk,
LE_CRED_ERROR_HASH_TREE);
LOG(ERROR) << "InsertCredentialReplay disk update "
"failed, label: "
<< label_obj.value();
return false;
}
ReportLEResult(kLEOpReplayInsert, kLEActionSaveToDisk, LE_CRED_SUCCESS);
return true;
}
bool LECredentialManagerImpl::ReplayCheck(uint64_t label,
const brillo::Blob& log_root,
bool is_full_replay) {
LOG(INFO) << "Replaying check for label " << label;
SignInHashTree::Label label_obj(label, kLengthLabels, kBitsPerLevel);
brillo::Blob orig_cred, orig_mac;
std::vector<brillo::Blob> h_aux;
bool metadata_lost;
if (!RetrieveLabelInfo(label_obj, &orig_cred, &orig_mac, &h_aux,
&metadata_lost)
.ok()) {
ReportLEResult(kLEOpReplayCheck, kLEActionLoadFromDisk,
LE_CRED_ERROR_HASH_TREE);
return false;
}
ReportLEResult(kLEOpReplayCheck, kLEActionLoadFromDisk, LE_CRED_SUCCESS);
// We separate the results between a normal replay and a full replay because
// the error distribution in a full replay might be very different (since
// we're just doing a best-effort attempt hoping that we are only 1 entry
// behind the first log entry). Only the replay_log action of ReplayCheck
// needs this separation because other individual actions aren't affected by
// whether the assumed root hash is correct.
const char* replay_log_action = is_full_replay
? kLEActionBackendReplayLogForFullReplay
: kLEActionBackendReplayLog;
hwsec::StatusOr<ReplayLogOperationResult> result =
pinweaver_->ReplayLogOperation(log_root, h_aux, orig_cred);
if (!result.ok()) {
ReportLEResult(kLEOpReplayCheck, replay_log_action,
LE_CRED_ERROR_UNCLASSIFIED);
LOG(ERROR) << "Auth replay failed on LE Backend, label: " << label << ": "
<< std::move(result).status();
return false;
}
ReportLEResult(kLEOpReplayCheck, replay_log_action, LE_CRED_SUCCESS);
// Store the new credential metadata and MAC.
if (!result->new_cred_metadata.empty() && !result->new_mac.empty()) {
if (!hash_tree_->StoreLabel(label_obj, result->new_mac,
result->new_cred_metadata, false)) {
ReportLEResult(kLEOpReplayCheck, kLEActionSaveToDisk,
LE_CRED_ERROR_HASH_TREE);
LOG(ERROR) << "Error in LE auth replay disk hash tree update, label: "
<< label;
return false;
}
ReportLEResult(kLEOpReplayCheck, kLEActionSaveToDisk, LE_CRED_SUCCESS);
}
return true;
}
bool LECredentialManagerImpl::ReplayResetTree() {
LOG(INFO) << "Replaying tree reset";
hash_tree_.reset();
if (!base::DeletePathRecursively(basedir_)) {
PLOG(ERROR) << "Failed to delete disk hash tree during replay.";
ReportLEResult(kLEOpReplayResetTree, kLEActionSaveToDisk,
LE_CRED_ERROR_HASH_TREE);
return false;
}
ReportLEResult(kLEOpReplayResetTree, kLEActionSaveToDisk, LE_CRED_SUCCESS);
auto new_hash_tree =
std::make_unique<SignInHashTree>(kLengthLabels, kBitsPerLevel, basedir_);
if (!new_hash_tree->IsValid()) {
ReportLEResult(kLEOpReplayResetTree, kLEActionSaveToDisk,
LE_CRED_ERROR_HASH_TREE);
return false;
}
hash_tree_ = std::move(new_hash_tree);
hash_tree_->GenerateAndStoreHashCache();
return true;
}
bool LECredentialManagerImpl::ReplayRemove(uint64_t label) {
LOG(INFO) << "Replaying remove for label " << label;
SignInHashTree::Label label_obj(label, kLengthLabels, kBitsPerLevel);
if (!hash_tree_->RemoveLabel(label_obj)) {
ReportLEResult(kLEOpReplayRemove, kLEActionSaveToDisk,
LE_CRED_ERROR_HASH_TREE);
LOG(ERROR) << "RemoveLabel LE Replay failed for label: " << label;
return false;
}
ReportLEResult(kLEOpReplayRemove, kLEActionSaveToDisk, LE_CRED_SUCCESS);
return true;
}
bool LECredentialManagerImpl::ReplayLogEntries(
const std::vector<GetLogResult::LogEntry>& log,
const brillo::Blob& disk_root_hash) {
// The log entries are in reverse chronological order. Because the log entries
// only store the root hash after the operation, the strategy here is:
// - Parse the logs in reverse.
// - First try to find a log entry which matches the on-disk root hash,
// and start with the log entry following that. If you can't, then just
// start from the earliest log.
// - For all other entries, simply attempt to replay the operation.
auto it = log.rbegin();
size_t replay_count = 0;
for (; it != log.rend(); ++it) {
const GetLogResult::LogEntry& log_entry = *it;
if (log_entry.root == disk_root_hash) {
// 1-based count, zero indicates no root hash match.
replay_count = it - log.rbegin() + 1;
LOG(INFO) << "Starting replay at log entry #" << it - log.rbegin();
++it;
break;
}
}
ReportLELogReplayEntryCount(replay_count);
bool is_full_replay = false;
if (it == log.rend()) {
LOG(WARNING) << "No matching root hash, starting replay at oldest entry";
it = log.rbegin();
is_full_replay = true;
}
brillo::Blob cur_root_hash = disk_root_hash;
std::vector<uint64_t> inserted_leaves;
for (; it != log.rend(); ++it) {
const GetLogResult::LogEntry& log_entry = *it;
bool ret;
switch (log_entry.type) {
case GetLogResult::LogEntryType::kInsert:
ret = ReplayInsert(log_entry.label, log_entry.root, log_entry.mac);
if (ret) {
inserted_leaves.push_back(log_entry.label);
}
break;
case GetLogResult::LogEntryType::kRemove:
ret = ReplayRemove(log_entry.label);
break;
case GetLogResult::LogEntryType::kCheck:
ret = ReplayCheck(log_entry.label, log_entry.root, is_full_replay);
break;
case GetLogResult::LogEntryType::kReset:
ret = ReplayResetTree();
break;
case GetLogResult::LogEntryType::kInvalid:
LOG(ERROR) << "Invalid log entry.";
ReportLEReplayResult(is_full_replay, LEReplayError::kInvalidLogEntry);
return false;
}
if (!ret) {
LOG(ERROR) << "Failure to replay LE Cred log entries.";
ReportLEReplayResult(is_full_replay, LEReplayError::kOperationError);
return false;
}
cur_root_hash.clear();
hash_tree_->GetRootHash(&cur_root_hash);
if (cur_root_hash != log_entry.root) {
LOG(ERROR) << "Root hash doesn't match log root after replaying entry.";
ReportLEReplayResult(is_full_replay, LEReplayError::kHashMismatch);
return false;
}
}
// Remove any inserted leaves since they are unusable.
for (const auto& label : inserted_leaves) {
if (!RemoveCredential(label).ok()) {
LOG(ERROR) << "Failed to remove re-inserted label: " << label;
ReportLEReplayResult(is_full_replay,
LEReplayError::kRemoveInsertedCredentialsError);
return false;
}
}
ReportLEReplayResult(is_full_replay, LEReplayError::kSuccess);
return true;
}
} // namespace cryptohome