| // 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 |