blob: a89c9d96f7d82dbe577b156142cf9a648541f303 [file] [log] [blame] [edit]
// Copyright 2022 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/error/converter.h"
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <base/logging.h>
#include <libhwsec-foundation/error/error.h>
#include "cryptohome/error/action.h"
#include "cryptohome/error/cryptohome_crypto_error.h"
#include "cryptohome/error/cryptohome_error.h"
#include "cryptohome/error/reporting.h"
namespace cryptohome::error {
namespace {
class PrimaryActions : public std::bitset<kPrimaryActionEnumSize> {
public:
PrimaryActions() = default;
PrimaryActions(std::initializer_list<PrimaryAction> init) {
for (PrimaryAction action : init) {
operator[](action) = true;
}
}
using std::bitset<kPrimaryActionEnumSize>::operator[];
bool operator[](PrimaryAction pos) const {
return bitset::operator[](static_cast<size_t>(pos));
}
reference operator[](PrimaryAction pos) {
return bitset::operator[](static_cast<size_t>(pos));
}
};
user_data_auth::PrimaryAction PrimaryActionToProto(PrimaryAction action) {
switch (action) {
case PrimaryAction::kTpmUpdateRequired:
return user_data_auth::PrimaryAction::PRIMARY_TPM_UDPATE_REQUIRED;
case PrimaryAction::kTpmLockout:
return user_data_auth::PrimaryAction::PRIMARY_TPM_LOCKOUT;
case PrimaryAction::kIncorrectAuth:
return user_data_auth::PrimaryAction::PRIMARY_INCORRECT_AUTH;
case PrimaryAction::kFactorLockedOut:
return user_data_auth::PrimaryAction::PRIMARY_FACTOR_LOCKED_OUT;
}
}
user_data_auth::PossibleAction PossibleActionToProto(PossibleAction action) {
switch (action) {
case PossibleAction::kRetry:
return user_data_auth::PossibleAction::POSSIBLY_RETRY;
case PossibleAction::kReboot:
return user_data_auth::PossibleAction::POSSIBLY_REBOOT;
case PossibleAction::kAuth:
return user_data_auth::PossibleAction::POSSIBLY_AUTH;
case PossibleAction::kDeleteVault:
return user_data_auth::PossibleAction::POSSIBLY_DELETE_VAULT;
case PossibleAction::kPowerwash:
return user_data_auth::PossibleAction::POSSIBLY_POWERWASH;
case PossibleAction::kDevCheckUnexpectedState:
return user_data_auth::PossibleAction::
POSSIBLY_DEV_CHECK_UNEXPECTED_STATE;
case PossibleAction::kFatal:
return user_data_auth::PossibleAction::POSSIBLY_FATAL;
}
}
std::string PrimaryActionToString(PrimaryAction action) {
switch (action) {
case PrimaryAction::kTpmUpdateRequired:
return "TPM Update Required";
case PrimaryAction::kTpmLockout:
return "TPM Lockout";
case PrimaryAction::kIncorrectAuth:
return "Incorrect Auth";
case PrimaryAction::kFactorLockedOut:
return "LE Locked Out";
}
}
// Retrieve the ErrorID (aka, the location) from the stack of errors.
// It looks something like this: 5-42-17
std::string ErrorIDFromStack(
const hwsec_foundation::status::StatusChain<CryptohomeError>& stack) {
std::string result;
for (const auto& err : stack.const_range()) {
if (!result.empty()) {
result += "-";
}
result += std::to_string(err.local_location());
}
return result;
}
} // namespace
// Retrieves the recommendation from cryptohome to the caller (Chromium).
// PrimaryAction means that cryptohome is certain that an action will resolve
// the issue, or there's a specific reason why it failed. PossibleAction means
// that cryptohome is uncertain if some actions would resolve the issue but it's
// worth a try anyway. Currently when there are multiple primary actions in the
// stack, the first one (upper-layer) is used. This behavior might be changed in
// the future.
// TODO(b/275676828): Determine best way to choose one primary action from the
// stack.
template <typename ErrorType>
void ActionsFromStack(
const hwsec_foundation::status::StatusChain<ErrorType>& stack,
std::optional<PrimaryAction>& primary,
PossibleActions& possible) {
PrimaryActions primary_actions;
// Collect all actions in the stack.
for (const auto& err : stack.const_range()) {
// NOTE(b/229708597) The underlying StatusChain will prohibit the iteration
// of the stack soon, and therefore other users of StatusChain should avoid
// iterating through the StatusChain without consulting the owner of the
// bug.
if (std::holds_alternative<PrimaryAction>(err.local_actions())) {
primary_actions[std::get<PrimaryAction>(err.local_actions())] = true;
} else {
possible |= std::get<PossibleActions>(err.local_actions());
}
}
if (primary_actions.none()) {
return;
}
// If we are sure, we'll not propose actions that we're not certain about.
possible.reset();
if (primary_actions[PrimaryAction::kIncorrectAuth] &&
primary_actions.count() == 1) {
primary = PrimaryAction::kIncorrectAuth;
return;
}
// If IncorrectAuth isn't the only primary action, it is considered inferior
// and we should remove it.
primary_actions[PrimaryAction::kIncorrectAuth] = false;
bool should_warn = primary_actions.count() > 1;
std::stringstream ss;
ss << "Multiple conflicting primary actions:";
// Other primary actions are considered the same tier, which shouldn't
// co-exist. Return one of them that exists in the stack.
for (size_t i = 0; i < kPrimaryActionEnumSize; ++i) {
PrimaryAction primary_action = static_cast<PrimaryAction>(i);
if (primary_actions[i]) {
primary = primary_action;
primary_actions[i] = false;
ss << " " << PrimaryActionToString(primary_action)
<< (primary_actions.any() ? "," : ".");
}
}
if (should_warn) {
LOG(DFATAL) << ss.str();
}
return;
}
// Instantiate for common types.
template void ActionsFromStack(
const hwsec_foundation::status::StatusChain<CryptohomeError>& stack,
std::optional<PrimaryAction>& primary,
PossibleActions& possible);
template void ActionsFromStack(
const hwsec_foundation::status::StatusChain<CryptohomeCryptoError>& stack,
std::optional<PrimaryAction>& primary,
PossibleActions& possible);
user_data_auth::CryptohomeErrorCode LegacyErrorCodeFromStack(
const hwsec_foundation::status::StatusChain<CryptohomeError>& stack) {
// Traverse down the stack for the first error
for (const auto& err : stack.const_range()) {
// NOTE(b/229708597) The underlying StatusChain will prohibit the iteration
// of the stack soon, and therefore other users of StatusChain should avoid
// iterating through the StatusChain without consulting the owner of the
// bug.
auto current_legacy_err = err.local_legacy_error();
if (current_legacy_err) {
return current_legacy_err.value();
}
}
// There's some form of an error because the original CryptohomeError is not
// nullptr, therefore, we should leave an unknown error here.
return user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_UNKNOWN_LEGACY;
}
user_data_auth::CryptohomeErrorInfo CryptohomeErrorToUserDataAuthError(
const hwsec_foundation::status::StatusChain<CryptohomeError>& err,
user_data_auth::CryptohomeErrorCode* legacy_ec) {
user_data_auth::CryptohomeErrorInfo result;
if (err.ok()) {
// No error.
result.set_primary_action(user_data_auth::PrimaryAction::PRIMARY_NO_ERROR);
if (legacy_ec) {
*legacy_ec =
user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_NOT_SET;
}
return result;
}
// Get the location and recommended actions.
result.set_error_id(ErrorIDFromStack(err));
std::optional<PrimaryAction> primary;
PossibleActions possible;
ActionsFromStack(err, primary, possible);
if (primary.has_value()) {
result.set_primary_action(PrimaryActionToProto(*primary));
} else {
result.set_primary_action(user_data_auth::PrimaryAction::PRIMARY_NONE);
for (size_t i = 0; i < possible.size(); ++i) {
if (possible[i]) {
result.add_possible_actions(
PossibleActionToProto(static_cast<PossibleAction>(i)));
}
}
}
// Get the legacy CryptohomeErrorCode as well.
if (legacy_ec) {
*legacy_ec = LegacyErrorCodeFromStack(err);
if (*legacy_ec ==
user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_UNKNOWN_LEGACY) {
LOG(WARNING) << "No legacy error code in error stack for "
"CryptohomeErrorToUserDataAuthError: "
<< result.error_id();
}
}
return result;
}
} // namespace cryptohome::error