blob: 3b199e460338ce2da545fe94b56fbe26fa074a81 [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 "libhwsec/backend/tpm2/config.h"
#include <cstdint>
#include <map>
#include <string>
#include <vector>
#include <base/containers/contains.h>
#include <base/no_destructor.h>
#include <base/strings/string_number_conversions.h>
#include <crypto/sha2.h>
#include <libhwsec-foundation/crypto/sha.h>
#include <libhwsec-foundation/status/status_chain_macros.h>
#include <openssl/sha.h>
#include <trunks/openssl_utility.h>
#include <trunks/tpm_utility.h>
#include "libhwsec/backend/tpm2/static_utils.h"
#include "libhwsec/error/tpm2_error.h"
#include "libhwsec/status.h"
#include "libhwsec/structures/operation_policy.h"
using brillo::BlobFromString;
using brillo::BlobToString;
using hwsec_foundation::Sha256;
using hwsec_foundation::status::MakeStatus;
using Mode = hwsec::DeviceConfigSettings::BootModeSetting::Mode;
namespace hwsec {
namespace {
constexpr int kBootModePcr = 0;
constexpr int kDeviceModelPcr = 1;
constexpr int kCurrentUserPcr = USE_TPM_DYNAMIC ? 11 : 4;
constexpr int kBootCmdlinePcr = 13;
constexpr DeviceConfig kSupportConfigs[] = {
DeviceConfig::kBootMode,
DeviceConfig::kDeviceModel,
DeviceConfig::kCurrentUser,
DeviceConfig::kBootCmdline,
};
StatusOr<int> DeviceConfigToPcr(DeviceConfig config) {
switch (config) {
case DeviceConfig::kBootMode:
return kBootModePcr;
case DeviceConfig::kDeviceModel:
return kDeviceModelPcr;
case DeviceConfig::kCurrentUser:
return kCurrentUserPcr;
case DeviceConfig::kBootCmdline:
return kBootCmdlinePcr;
}
return MakeStatus<TPMError>("Unknown device config",
TPMRetryAction::kNoRetry);
}
Status AddToPolicySession(trunks::PolicySession& policy_session,
const ConfigTpm2::PcrMap& pcr_map,
const Permission& permission) {
if (permission.auth_value.has_value()) {
if (permission.type == PermissionType::kAuthValue) {
RETURN_IF_ERROR(MakeStatus<TPM2Error>(policy_session.PolicyAuthValue()))
.WithStatus<TPMError>("Failed to create auth value policy");
} else if (permission.type == PermissionType::kPolicyOR) {
// Doing the PolicyOR with the zero digest and auth_value.
// The initaial policy digest is zero, so we will always match the first
// section. But we still need the correct auth_value to generate correct
// the final policy digest.
std::vector<std::string> policy_or_digests = {
std::string(SHA256_DIGEST_LENGTH, 0),
Sha256(permission.auth_value.value()).to_string()};
RETURN_IF_ERROR(
MakeStatus<TPM2Error>(policy_session.PolicyOR(policy_or_digests)))
.WithStatus<TPMError>("Failed to call PolicyOR");
} else {
return MakeStatus<TPMError>("Unknown policy permission type",
TPMRetryAction::kNoRetry);
}
}
if (!pcr_map.empty()) {
RETURN_IF_ERROR(MakeStatus<TPM2Error>(policy_session.PolicyPCR(pcr_map)))
.WithStatus<TPMError>("Failed to create PCR policy");
}
return OkStatus();
}
// The mapping that maps pcr value to corresponding boot mode.
const std::map<std::string, Mode>& BootModeMapping() {
static const base::NoDestructor<std::map<std::string, Mode>> mapping([] {
std::map<std::string, Mode> mapping;
// 3-byte boot mode:
// - byte 0: 1 if in developer mode, 0 otherwise,
// - byte 1: 1 if in recovery mode, 0 otherwise,
// - byte 2: 1 if verified firmware, 0 if developer firmware.
// Iterating through all possible combination of modes.
for (int i = 0; i < (1 << 3); ++i) {
Mode mode = {
.developer_mode = i & 1,
.recovery_mode = i & 2,
.verified_firmware = i & 4,
};
mapping.emplace(GetTpm2PCRValueForMode(mode), mode);
}
return mapping;
}());
return *mapping;
}
} // namespace
StatusOr<OperationPolicy> ConfigTpm2::ToOperationPolicy(
const OperationPolicySetting& policy) {
DeviceConfigs configs;
const DeviceConfigSettings& settings = policy.device_config_settings;
if (settings.boot_mode.has_value()) {
configs[DeviceConfig::kBootMode] = true;
}
if (settings.device_model.has_value()) {
configs[DeviceConfig::kDeviceModel] = true;
}
if (settings.current_user.has_value()) {
configs[DeviceConfig::kCurrentUser] = true;
}
return OperationPolicy{
.device_configs = configs,
.permission = policy.permission,
};
}
Status ConfigTpm2::SetCurrentUser(const std::string& current_user) {
std::unique_ptr<trunks::AuthorizationDelegate> delegate =
context_.GetTrunksFactory().GetPasswordAuthorization("");
RETURN_IF_ERROR(MakeStatus<TPM2Error>(context_.GetTpmUtility().ExtendPCR(
kCurrentUserPcr, current_user, delegate.get())))
.WithStatus<TPMError>("Failed to extend current user PCR");
return OkStatus();
}
StatusOr<bool> ConfigTpm2::IsCurrentUserSet() {
ASSIGN_OR_RETURN(std::string && value, ReadPcr(kCurrentUserPcr),
_.WithStatus<TPMError>("Failed to read current user PCR"));
return value != std::string(SHA256_DIGEST_LENGTH, 0);
}
StatusOr<Mode> ConfigTpm2::GetCurrentBootMode() {
ASSIGN_OR_RETURN(const std::string& value, ReadPcr(kBootModePcr),
_.WithStatus<TPMError>("Failed to read boot mode PCR"));
return ToBootMode(value);
}
StatusOr<Mode> ConfigTpm2::ToBootMode(const std::string& value) {
const std::map<std::string, Mode>& mapping = BootModeMapping();
if (auto it = mapping.find(value); it != mapping.end()) {
return it->second;
}
return MakeStatus<TPMError>("Encountered invalid boot mode value: " +
base::HexEncode(value.data(), value.size()),
TPMRetryAction::kNoRetry);
}
StatusOr<ConfigTpm2::PcrMap> ConfigTpm2::ToPcrMap(
const DeviceConfigs& device_config) {
PcrMap result;
for (DeviceConfig config : kSupportConfigs) {
if (device_config[config]) {
ASSIGN_OR_RETURN(int pcr, DeviceConfigToPcr(config),
_.WithStatus<TPMError>("Failed to convert to PCR"));
result[pcr] = std::string();
}
}
return result;
}
StatusOr<ConfigTpm2::PcrMap> ConfigTpm2::ToSettingsPcrMap(
const DeviceConfigSettings& settings) {
PcrMap result;
if (settings.boot_mode.has_value()) {
const auto& mode = settings.boot_mode->mode;
if (mode.has_value()) {
result[kBootModePcr] = GetTpm2PCRValueForMode(*mode);
} else {
ASSIGN_OR_RETURN(std::string && value, ReadPcr(kBootModePcr),
_.WithStatus<TPMError>("Failed to read boot mode PCR"));
result[kBootModePcr] = std::move(value);
}
}
if (settings.device_model.has_value()) {
const auto& hardware_id = settings.device_model->hardware_id;
if (hardware_id.has_value()) {
return MakeStatus<TPMError>("Unsupported settings",
TPMRetryAction::kNoRetry);
} else {
ASSIGN_OR_RETURN(
std::string && value, ReadPcr(kDeviceModelPcr),
_.WithStatus<TPMError>("Failed to read device model PCR"));
result[kDeviceModelPcr] = std::move(value);
}
}
if (settings.current_user.has_value()) {
const auto& username = settings.current_user->username;
brillo::Blob digest_value(SHA256_DIGEST_LENGTH, 0);
if (username.has_value()) {
digest_value = Sha256(brillo::CombineBlobs(
{digest_value, Sha256(BlobFromString(username.value()))}));
}
result[kCurrentUserPcr] = BlobToString(digest_value);
}
return result;
}
StatusOr<std::unique_ptr<trunks::PolicySession>>
ConfigTpm2::GetTrunksPolicySession(
const OperationPolicy& policy,
const std::vector<std::string>& extra_policy_digests,
bool salted,
bool enable_encryption) {
std::unique_ptr<trunks::PolicySession> policy_session =
context_.GetTrunksFactory().GetPolicySession();
RETURN_IF_ERROR(MakeStatus<TPM2Error>(policy_session->StartUnboundSession(
salted, enable_encryption)))
.WithStatus<TPMError>("Failed to start policy session");
ASSIGN_OR_RETURN(const PcrMap& pcr_map, ToPcrMap(policy.device_configs),
_.WithStatus<TPMError>("Failed to get PCR map"));
RETURN_IF_ERROR(
AddToPolicySession(*policy_session, pcr_map, policy.permission))
.WithStatus<TPMError>("Failed to add policy to policy session");
if (!extra_policy_digests.empty()) {
RETURN_IF_ERROR(
MakeStatus<TPM2Error>(policy_session->PolicyOR(extra_policy_digests)))
.WithStatus<TPMError>("Failed to call PolicyOR");
}
if (policy.permission.auth_value.has_value() &&
policy.permission.type == PermissionType::kAuthValue) {
std::string auth_value = policy.permission.auth_value.value().to_string();
policy_session->SetEntityAuthorizationValue(auth_value);
brillo::SecureClearContainer(auth_value);
}
return policy_session;
}
StatusOr<ConfigTpm2::TrunksSession> ConfigTpm2::GetTrunksSession(
const OperationPolicy& policy, SessionSecuritySetting setting) {
if (policy.device_configs.any() ||
policy.permission.type != PermissionType::kAuthValue) {
SessionSecurityDetail detail = ToSessionSecurityDetail(setting);
std::vector<std::string> no_extra_policy_digest = {};
ASSIGN_OR_RETURN(
std::unique_ptr<trunks::PolicySession> session,
GetTrunksPolicySession(policy, no_extra_policy_digest, detail.salted,
detail.enable_encryption),
_.WithStatus<TPMError>("Failed to get policy session"));
trunks::AuthorizationDelegate* delegate = session->GetDelegate();
return TrunksSession{
.session = std::move(session),
.delegate = delegate,
};
} else {
ASSIGN_OR_RETURN(trunks::HmacSession & hmac_session,
session_management_.GetOrCreateHmacSession(setting),
_.WithStatus<TPMError>("Failed to get hmac session"));
if (policy.permission.auth_value.has_value()) {
std::string auth_value = policy.permission.auth_value.value().to_string();
hmac_session.SetEntityAuthorizationValue(auth_value);
brillo::SecureClearContainer(auth_value);
}
return TrunksSession{
// The hmac session doesn't owned by the return value.
.session = nullptr,
.delegate = hmac_session.GetDelegate(),
};
}
}
StatusOr<std::string> ConfigTpm2::ReadPcr(uint32_t pcr_index) {
std::string pcr_digest;
RETURN_IF_ERROR(MakeStatus<TPM2Error>(
context_.GetTpmUtility().ReadPCR(pcr_index, &pcr_digest)))
.WithStatus<TPMError>("Failed to read PCR");
return pcr_digest;
}
StatusOr<ConfigTpm2::PcrValue> ConfigTpm2::ToPcrValue(
const DeviceConfigSettings& settings) {
ASSIGN_OR_RETURN(const PcrMap& pcr_map, ToSettingsPcrMap(settings));
// Zero initialize.
ConfigTpm2::PcrValue pcr_value = {};
std::string digest;
for (const PcrMap::value_type& pcr : pcr_map) {
pcr_value.bitmask[pcr.first / 8] |= 1u << (pcr.first % 8);
digest += pcr.second;
}
pcr_value.digest = crypto::SHA256HashString(digest);
return pcr_value;
}
StatusOr<trunks::TPMS_PCR_SELECTION> ConfigTpm2::ToPcrSelection(
const DeviceConfigs& device_configs) {
ASSIGN_OR_RETURN(const PcrMap& pcr_map, ToPcrMap(device_configs));
trunks::TPMS_PCR_SELECTION pcr_selection;
pcr_selection.hash = trunks::TPM_ALG_SHA256;
pcr_selection.sizeof_select = PCR_SELECT_MIN;
memset(pcr_selection.pcr_select, 0, PCR_SELECT_MIN);
for (const PcrMap::value_type& pcr : pcr_map) {
pcr_selection.pcr_select[pcr.first / 8] |= 1u << (pcr.first % 8);
}
return pcr_selection;
}
StatusOr<std::string> ConfigTpm2::GetPolicyDigest(
const OperationPolicySetting& policy) {
if (policy.device_config_settings.use_endorsement_auth) {
// Use the auth policy templates for endorsement key.
return trunks::GetEkTemplateAuthPolicy();
}
ASSIGN_OR_RETURN(const PcrMap& pcr_map,
ToSettingsPcrMap(policy.device_config_settings),
_.WithStatus<TPMError>("Failed to get PCR map"));
if (pcr_map.empty() && policy.permission.type == PermissionType::kAuthValue) {
// We will use hmac session, no policy digest for this case.
return std::string();
}
// Start a trial policy session.
std::unique_ptr<trunks::PolicySession> policy_session =
context_.GetTrunksFactory().GetTrialSession();
RETURN_IF_ERROR(
MakeStatus<TPM2Error>(policy_session->StartUnboundSession(false, false)))
.WithStatus<TPMError>("Failed to start trial session");
RETURN_IF_ERROR(
AddToPolicySession(*policy_session, pcr_map, policy.permission))
.WithStatus<TPMError>("Failed to add policy to policy session");
std::string policy_digest;
RETURN_IF_ERROR(
MakeStatus<TPM2Error>(policy_session->GetDigest(&policy_digest)))
.WithStatus<TPMError>("Failed to get policy digest");
return policy_digest;
}
StatusOr<std::string> ConfigTpm2::GetHardwareID() {
auto property = crossystem_.VbGetSystemPropertyString("hwid");
if (!property.has_value()) {
return MakeStatus<TPMError>("Failed to read hwid property",
TPMRetryAction::kNoRetry);
}
return *property;
}
} // namespace hwsec