| // Copyright 2015 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 "tpm_manager/server/tpm_status_impl.h" |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include <base/check.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/optional.h> |
| #include <brillo/file_utils.h> |
| #include <tpm_manager/server/tpm_util.h> |
| #include <trousers/trousers.h> |
| #include <trousers/tss.h> |
| |
| namespace { |
| |
| // Minimum size of TPM_DA_INFO struct. |
| constexpr size_t kMinimumDaInfoSize = 21; |
| |
| // Minimum size of TPM_CAP_VERSION_INFO struct. |
| constexpr size_t kMinimumVersionInfoSize = 17; |
| |
| // The TPM manufacturer code of Infineon. |
| constexpr uint32_t kInfineonManufacturerCode = 0x49465800; |
| |
| // The Infineon-specific DA info sub-capability flag. |
| constexpr uint32_t kInfineonMfrSubCapability = 0x00000802; |
| |
| // The offset of DA counter in the Infineon-specific DA info data. |
| constexpr size_t kInfineonDACounterOffset = 9; |
| |
| // The flag that tells if the tpm is full initialized. |
| constexpr char kTpmFullyInitializedPath[] = |
| "/mnt/stateful_partition/.tpm_owned"; |
| |
| bool TouchTpmFullyInitializedPath() { |
| return brillo::WriteBlobToFile<std::vector<char>>( |
| base::FilePath(kTpmFullyInitializedPath), {}); |
| } |
| |
| bool SetNoSrkAuth(tpm_manager::LocalDataStore* local_data_store, bool value) { |
| tpm_manager::LocalData local_data; |
| if (!local_data_store->Read(&local_data)) { |
| LOG(ERROR) << __func__ << ": Failed to read local data."; |
| return false; |
| } |
| local_data.set_no_srk_auth(value); |
| if (!local_data_store->Write(local_data)) { |
| LOG(ERROR) << __func__ << ": Failed to write local data change."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool GetNoSrkAuth(tpm_manager::LocalDataStore* local_data_store) { |
| tpm_manager::LocalData local_data; |
| if (!local_data_store->Read(&local_data)) { |
| LOG(ERROR) << __func__ << ": Failed to read local data."; |
| return false; |
| } |
| return local_data.no_srk_auth(); |
| } |
| |
| } // namespace |
| |
| namespace tpm_manager { |
| |
| TpmStatusImpl::TpmStatusImpl(LocalDataStore* local_data_store) |
| : local_data_store_(local_data_store) {} |
| |
| bool TpmStatusImpl::IsTpmEnabled() { |
| if (!is_enable_initialized_) { |
| RefreshOwnedEnabledInfo(); |
| } |
| return is_enabled_; |
| } |
| |
| bool TpmStatusImpl::GetTpmOwned(TpmStatus::TpmOwnershipStatus* status) { |
| CHECK(status); |
| if (kTpmOwned == ownership_status_) { |
| *status = ownership_status_; |
| return true; |
| } |
| |
| if (!is_owned_) { |
| // update is_owned_ |
| RefreshOwnedEnabledInfo(); |
| } |
| |
| if (!is_owned_) { |
| if (!base::DeleteFile(base::FilePath(kTpmFullyInitializedPath))) { |
| LOG(WARNING) << __func__ << ": Failed to delete " |
| << kTpmFullyInitializedPath; |
| } |
| if (!SetNoSrkAuth(local_data_store_, false)) { |
| LOG(WARNING) << __func__ << ": Failed to set no_srk_auth"; |
| } |
| // We even haven't tried to take ownership yet. |
| ownership_status_ = kTpmUnowned; |
| *status = ownership_status_; |
| return true; |
| } |
| |
| const base::Optional<bool> is_default_owner_password = |
| TestTpmWithDefaultOwnerPassword(); |
| if (!is_default_owner_password.has_value()) { |
| LOG(ERROR) << __func__ << ": Failed to test default owner password."; |
| return false; |
| } |
| if (*is_default_owner_password) { |
| ownership_status_ = kTpmPreOwned; |
| *status = ownership_status_; |
| return true; |
| } |
| |
| const base::Optional<bool> is_default_srk_auth = TestTpmSrkWithDefaultAuth(); |
| if (!is_default_srk_auth.has_value()) { |
| LOG(ERROR) << __func__ << ": Failed to test default SRK auth."; |
| return false; |
| } |
| if (!*is_default_srk_auth) { |
| LOG(WARNING) << __func__ << ": Failed to use SRK with default auth."; |
| ownership_status_ = kTpmSrkNoAuth; |
| *status = ownership_status_; |
| return true; |
| } |
| |
| ownership_status_ = kTpmOwned; |
| |
| *status = ownership_status_; |
| return true; |
| } |
| |
| bool TpmStatusImpl::GetDictionaryAttackInfo(uint32_t* counter, |
| uint32_t* threshold, |
| bool* lockout, |
| uint32_t* seconds_remaining) { |
| CHECK(counter); |
| CHECK(threshold); |
| CHECK(lockout); |
| CHECK(seconds_remaining); |
| std::vector<uint8_t> capability_data; |
| if (!GetCapability(TSS_TPMCAP_DA_LOGIC, TPM_ET_KEYHANDLE, &capability_data, |
| nullptr) || |
| capability_data.size() < kMinimumDaInfoSize) { |
| LOG(ERROR) << "Error getting tpm capability data for DA info."; |
| return false; |
| } |
| if (static_cast<uint16_t>(capability_data[1]) == TPM_TAG_DA_INFO) { |
| TPM_DA_INFO da_info; |
| uint64_t offset = 0; |
| Trspi_UnloadBlob_DA_INFO(&offset, capability_data.data(), &da_info); |
| *counter = da_info.currentCount; |
| *threshold = da_info.thresholdCount; |
| *lockout = (da_info.state == TPM_DA_STATE_ACTIVE); |
| *seconds_remaining = da_info.actionDependValue; |
| } |
| |
| // For Infineon, pulls the counter out of vendor-specific data and checks if |
| // it matches the value in DA_INFO. |
| |
| if (!GetCapability(TSS_TPMCAP_PROPERTY, TSS_TPMCAP_PROP_MANUFACTURER, |
| &capability_data, nullptr) || |
| capability_data.size() != sizeof(uint32_t)) { |
| LOG(WARNING) << "Failed to query TSS_TPMCAP_PROP_MANUFACTURER. " |
| "Using the DA info from TSS_TPMCAP_DA_LOGIC."; |
| return true; |
| } |
| |
| uint32_t manufacturer; |
| uint64_t offset = 0; |
| Trspi_UnloadBlob_UINT32(&offset, &manufacturer, capability_data.data()); |
| if (manufacturer != kInfineonManufacturerCode) { |
| return true; |
| } |
| |
| if (!GetCapability(TSS_TPMCAP_MFR, kInfineonMfrSubCapability, |
| &capability_data, nullptr)) { |
| LOG(WARNING) << "Failed to query Infineon MFR capability. " |
| "Using the DA info from TSS_TPMCAP_DA_LOGIC."; |
| return true; |
| } |
| |
| if (capability_data.size() <= kInfineonDACounterOffset) { |
| LOG(WARNING) << "Couldn't read DA counter from Infineon's MFR " |
| "capability. Using the DA info from TSS_TPMCAP_DA_LOGIC."; |
| return true; |
| } |
| |
| uint32_t vendor_da_counter = |
| static_cast<uint32_t>(capability_data[kInfineonDACounterOffset]); |
| if (*counter != vendor_da_counter) { |
| LOG(WARNING) << "DA counter mismatch for Infineon: " << *counter << " vs. " |
| << vendor_da_counter << ". Using the larger one."; |
| *counter = std::max(*counter, vendor_da_counter); |
| } |
| return true; |
| } |
| |
| bool TpmStatusImpl::IsDictionaryAttackMitigationEnabled(bool* is_enabled) { |
| // For TPM1.2, it is always enabled. |
| *is_enabled = true; |
| return true; |
| } |
| |
| bool TpmStatusImpl::GetVersionInfo(uint32_t* family, |
| uint64_t* spec_level, |
| uint32_t* manufacturer, |
| uint32_t* tpm_model, |
| uint64_t* firmware_version, |
| std::vector<uint8_t>* vendor_specific) { |
| CHECK(family); |
| CHECK(spec_level); |
| CHECK(manufacturer); |
| CHECK(tpm_model); |
| CHECK(firmware_version); |
| CHECK(vendor_specific); |
| |
| std::vector<uint8_t> capability_data; |
| if (!GetCapability(TSS_TPMCAP_VERSION_VAL, 0, &capability_data, nullptr) || |
| capability_data.size() < kMinimumVersionInfoSize || |
| static_cast<uint16_t>(capability_data[1]) != TPM_TAG_CAP_VERSION_INFO) { |
| LOG(ERROR) << "Error getting TPM version capability data."; |
| return false; |
| } |
| |
| TPM_CAP_VERSION_INFO tpm_version; |
| uint64_t offset = 0; |
| Trspi_UnloadBlob_CAP_VERSION_INFO(&offset, capability_data.data(), |
| &tpm_version); |
| *family = 0x312e3200; |
| *spec_level = (static_cast<uint64_t>(tpm_version.specLevel) << 32) | |
| tpm_version.errataRev; |
| *manufacturer = |
| (tpm_version.tpmVendorID[0] << 24) | (tpm_version.tpmVendorID[1] << 16) | |
| (tpm_version.tpmVendorID[2] << 8) | (tpm_version.tpmVendorID[3] << 0); |
| // There's no generic model field in the spec. Model information might be |
| // present in the vendor-specific data returned by CAP_VERSION_INFO, so if |
| // we ever require to know the model, we'll need to check with hardware |
| // vendors for the best way to determine it. |
| *tpm_model = ~0; |
| *firmware_version = |
| (tpm_version.version.revMajor << 8) | tpm_version.version.revMinor; |
| const uint8_t* data = |
| reinterpret_cast<const uint8_t*>(tpm_version.vendorSpecific); |
| vendor_specific->assign(data, data + tpm_version.vendorSpecificSize); |
| free(tpm_version.vendorSpecific); |
| return true; |
| } |
| |
| base::Optional<bool> TpmStatusImpl::TestTpmWithDefaultOwnerPassword() { |
| if (base::PathExists(base::FilePath(kTpmFullyInitializedPath))) { |
| is_owner_password_default_ = false; |
| } |
| |
| if (is_owner_password_default_.has_value()) { |
| return is_owner_password_default_; |
| } |
| |
| TpmConnection connection(GetDefaultOwnerPassword()); |
| TSS_HTPM tpm_handle = connection.GetTpm(); |
| if (tpm_handle == 0) { |
| return base::nullopt; |
| } |
| |
| // Call Tspi_TPM_GetStatus to test the default owner password. |
| TSS_BOOL current_status = false; |
| TSS_RESULT result = |
| Tspi_TPM_GetStatus(tpm_handle, TSS_TPMSTATUS_DISABLED, ¤t_status); |
| |
| if (result == TPM_SUCCESS) { |
| is_owner_password_default_ = true; |
| } else if (result == TPM_ERROR(TPM_E_AUTHFAIL)) { |
| is_owner_password_default_ = false; |
| if (!TouchTpmFullyInitializedPath()) { |
| LOG(WARNING) << __func__ << ": Failed to touch " |
| << kTpmFullyInitializedPath; |
| } |
| } else { |
| TPM_LOG(ERROR, result) << "Unexpected error calling |Tspi_TPM_GetStatus|."; |
| } |
| return is_owner_password_default_; |
| } |
| |
| base::Optional<bool> TpmStatusImpl::TestTpmSrkWithDefaultAuth() { |
| if (GetNoSrkAuth(local_data_store_)) { |
| is_srk_auth_default_ = false; |
| } |
| |
| if (is_srk_auth_default_.has_value()) { |
| return is_srk_auth_default_; |
| } |
| |
| TpmConnection connection; |
| TSS_RESULT result; |
| trousers::ScopedTssKey srk_handle(connection.GetContext()); |
| TSS_UUID SRK_UUID = TSS_UUID_SRK; |
| if (TPM_ERROR(result = Tspi_Context_LoadKeyByUUID( |
| connection.GetContext(), TSS_PS_TYPE_SYSTEM, SRK_UUID, |
| srk_handle.ptr()))) { |
| TPM_LOG(ERROR, result) << "Error calling Tspi_Context_LoadKeyByUUID"; |
| return base::nullopt; |
| } |
| |
| // Check if the SRK wants a password |
| UINT32 srk_authusage; |
| if (TPM_ERROR(result = Tspi_GetAttribUint32( |
| srk_handle, TSS_TSPATTRIB_KEY_INFO, |
| TSS_TSPATTRIB_KEYINFO_AUTHUSAGE, &srk_authusage))) { |
| TPM_LOG(ERROR, result) << "Error calling Tspi_GetAttribUint32"; |
| return base::nullopt; |
| } |
| |
| if (!srk_authusage) { |
| is_srk_auth_default_ = true; |
| return is_srk_auth_default_; |
| } |
| |
| // Give it the password if needed |
| TSS_HPOLICY srk_usage_policy; |
| if (TPM_ERROR(result = Tspi_GetPolicyObject(srk_handle, TSS_POLICY_USAGE, |
| &srk_usage_policy))) { |
| TPM_LOG(ERROR, result) << "Error calling Tspi_GetPolicyObject"; |
| return base::nullopt; |
| } |
| BYTE default_auth[0]; |
| result = Tspi_Policy_SetSecret(srk_usage_policy, TSS_SECRET_MODE_PLAIN, 0, |
| default_auth); |
| if (result != TPM_SUCCESS) { |
| TPM_LOG(ERROR, result) |
| << "Unexpected error calling |Tspi_Policy_SetSecret|."; |
| return base::nullopt; |
| } |
| |
| unsigned public_srk_size; |
| trousers::ScopedTssMemory public_srk_bytes(connection.GetContext()); |
| result = |
| Tspi_Key_GetPubKey(srk_handle, &public_srk_size, public_srk_bytes.ptr()); |
| if (result == TPM_SUCCESS) { |
| is_srk_auth_default_ = true; |
| } else if (result == TPM_ERROR(TPM_E_AUTHFAIL)) { |
| is_srk_auth_default_ = false; |
| if (!SetNoSrkAuth(local_data_store_, true)) { |
| LOG(WARNING) << __func__ << ": Failed to set no_srk_auth"; |
| } |
| } else { |
| TPM_LOG(ERROR, result) << "Unexpected error calling |Tspi_Key_GetPubKey|."; |
| return base::nullopt; |
| } |
| return is_srk_auth_default_; |
| } |
| |
| void TpmStatusImpl::RefreshOwnedEnabledInfo() { |
| TSS_RESULT result; |
| std::vector<uint8_t> capability_data; |
| if (!GetCapability(TSS_TPMCAP_PROPERTY, TSS_TPMCAP_PROP_OWNER, |
| &capability_data, &result)) { |
| if (ERROR_CODE(result) == TPM_E_DISABLED) { |
| is_enable_initialized_ = true; |
| is_enabled_ = false; |
| return; |
| } |
| } else { |
| // |capability_data| should be populated with a TSS_BOOL which is true iff |
| // the Tpm is owned. |
| if (capability_data.size() != sizeof(TSS_BOOL)) { |
| LOG(ERROR) << "Error refreshing Tpm ownership information."; |
| is_enable_initialized_ = true; |
| is_enabled_ = true; |
| is_owned_ = false; |
| return; |
| } |
| is_owned_ = (capability_data[0] != 0); |
| if (!is_owned_) { |
| trousers::ScopedTssKey local_key_handle(tpm_connection_.GetContext()); |
| TSS_RESULT result = Tspi_TPM_GetPubEndorsementKey( |
| tpm_connection_.GetTpm(), false, nullptr, local_key_handle.ptr()); |
| if (TPM_ERROR(result) == TPM_E_DISABLED) { |
| is_enable_initialized_ = true; |
| is_enabled_ = false; |
| return; |
| } |
| } |
| is_enable_initialized_ = true; |
| is_enabled_ = true; |
| return; |
| } |
| } |
| |
| bool TpmStatusImpl::GetCapability(uint32_t capability, |
| uint32_t sub_capability, |
| std::vector<uint8_t>* data, |
| TSS_RESULT* tpm_result) { |
| CHECK(data); |
| TSS_HTPM tpm_handle = tpm_connection_.GetTpm(); |
| if (tpm_handle == 0) { |
| if (tpm_result) { |
| *tpm_result = TSS_E_COMM_FAILURE; |
| } |
| return false; |
| } |
| uint32_t length = 0; |
| trousers::ScopedTssMemory buf(tpm_connection_.GetContext()); |
| TSS_RESULT result = Tspi_TPM_GetCapability( |
| tpm_handle, capability, sizeof(uint32_t), |
| reinterpret_cast<BYTE*>(&sub_capability), &length, buf.ptr()); |
| if (tpm_result) { |
| *tpm_result = result; |
| } |
| if (TPM_ERROR(result)) { |
| LOG(ERROR) << "Error getting TPM capability data."; |
| return false; |
| } |
| data->assign(buf.value(), buf.value() + length); |
| return true; |
| } |
| |
| void TpmStatusImpl::MarkRandomOwnerPasswordSet() { |
| // Also makes sure the state machine is consistent. |
| is_enable_initialized_ = is_enabled_ = is_owned_ = true; |
| ownership_status_ = kTpmOwned; |
| is_owner_password_default_ = false; |
| if (!TouchTpmFullyInitializedPath()) { |
| LOG(WARNING) << __func__ << ": Failed to touch " |
| << kTpmFullyInitializedPath; |
| } |
| } |
| |
| bool TpmStatusImpl::SupportU2f() { |
| // For TPM1.2, we doesn't support u2f. |
| return false; |
| } |
| |
| bool TpmStatusImpl::SupportPinweaver() { |
| // For TPM1.2, we doesn't support pinweaver. |
| return false; |
| } |
| |
| GscVersion TpmStatusImpl::GetGscVersion() { |
| // TPM1.2 is not a GSC. |
| return GscVersion::GSC_VERSION_NOT_GSC; |
| } |
| |
| bool TpmStatusImpl::GetRoVerificationStatus( |
| tpm_manager::RoVerificationStatus* status) { |
| *status = tpm_manager::RO_STATUS_UNSUPPORTED; |
| return true; |
| } |
| |
| } // namespace tpm_manager |