| // Copyright 2018 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 "cryptohome/mount_encrypted/tpm.h" |
| |
| #include <inttypes.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <type_traits> |
| |
| #include <base/files/file.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/macros.h> |
| #include <base/strings/string_number_conversions.h> |
| |
| #include <brillo/file_utils.h> |
| #include <brillo/process/process.h> |
| |
| #include "cryptohome/cryptolib.h" |
| #include "cryptohome/mount_encrypted/mount_encrypted.h" |
| |
| namespace mount_encrypted { |
| namespace { |
| |
| const uint32_t kLockboxSaltOffset = 0x5; |
| |
| // Attributes for the encstatful NVRAM space. Ideally, we'd set |
| // TPM_NV_PER_OWNERWRITE so the space gets automatically destroyed when the TPM |
| // gets cleared. That'd mean we'd have to recreate the NVRAM space on next boot |
| // though, which requires TPM ownership. Taking ownership is notoriously slow, |
| // so we can't afford to do this. Instead, we keep the space allocated and |
| // detect TPM clear to regenerate the system key. |
| const uint32_t kAttributes = TPM_NV_PER_WRITE_STCLEAR | TPM_NV_PER_READ_STCLEAR; |
| |
| const uint32_t kAttributesMask = |
| TPM_NV_PER_READ_STCLEAR | TPM_NV_PER_AUTHREAD | TPM_NV_PER_OWNERREAD | |
| TPM_NV_PER_PPREAD | TPM_NV_PER_GLOBALLOCK | TPM_NV_PER_WRITE_STCLEAR | |
| TPM_NV_PER_WRITEDEFINE | TPM_NV_PER_WRITEALL | TPM_NV_PER_AUTHWRITE | |
| TPM_NV_PER_OWNERWRITE | TPM_NV_PER_PPWRITE; |
| |
| // Key derivation labels. |
| const char kLabelSystemKey[] = "system_key"; |
| const char kLabelLockboxMAC[] = "lockbox_mac"; |
| |
| // This is the well-known secret (SHA-1 hash of 20 zero bytes) that TrouSerS |
| // sets by default when taking ownership. We use the same value here to simplify |
| // the logic in cryptohomed. |
| const uint8_t kWellKnownSecret[TPM_AUTH_DATA_LEN] = { |
| 0x67, 0x68, 0x03, 0x3e, 0x21, 0x64, 0x68, 0x24, 0x7b, 0xd0, |
| 0x31, 0xa0, 0xa2, 0xd9, 0x87, 0x6d, 0x79, 0x81, 0x8f, 0x8f, |
| }; |
| |
| // This struct defines the memory layout of the NVRAM area. Member sizes are |
| // chosen taking layout into consideration. |
| struct EncStatefulArea { |
| enum class Flag { |
| // The |lockbox_mac| field is valid and contains a MAC of the lockbox NVRAM |
| // area contents. |
| kLockboxMacValid = 0, |
| // We are expecting another TPM clear to take place for which preservation |
| // will be allowed. This is used to handle the TPM clear following a TPM |
| // firmware update. |
| kAnticipatingTPMClear = 1, |
| }; |
| |
| static constexpr uint32_t kMagic = 0x54504d31; |
| |
| static constexpr size_t kVersionShift = 8; |
| static constexpr uint32_t kVersionMask = (1 << kVersionShift) - 1; |
| |
| static constexpr uint32_t kCurrentVersion = 1; |
| |
| uint32_t magic; |
| uint32_t ver_flags; |
| uint8_t key_material[DIGEST_LENGTH]; |
| uint8_t lockbox_mac[DIGEST_LENGTH]; |
| |
| bool is_valid() const { |
| return magic == kMagic && (ver_flags & kVersionMask) == kCurrentVersion; |
| } |
| |
| static uint32_t flag_value(Flag flag) { |
| return (1 << (static_cast<size_t>(flag) + kVersionShift)); |
| } |
| |
| bool test_flag(Flag flag) const { |
| return ver_flags & flag_value(flag); |
| } |
| void set_flag(Flag flag) { |
| ver_flags |= flag_value(flag); |
| } |
| void clear_flag(Flag flag) { |
| ver_flags &= ~flag_value(flag); |
| } |
| |
| result_code Init(const brillo::SecureBlob& new_key_material) { |
| magic = kMagic; |
| ver_flags = kCurrentVersion; |
| |
| size_t key_material_size = new_key_material.size(); |
| if (key_material_size != sizeof(key_material)) { |
| LOG(ERROR) << "Invalid key material size " << key_material_size; |
| return RESULT_FAIL_FATAL; |
| } |
| memcpy(key_material, new_key_material.data(), key_material_size); |
| |
| memset(lockbox_mac, 0, sizeof(lockbox_mac)); |
| return RESULT_SUCCESS; |
| } |
| |
| brillo::SecureBlob DeriveKey(const std::string& label) const { |
| return cryptohome::CryptoLib::HmacSha256( |
| brillo::SecureBlob(key_material, key_material + sizeof(key_material)), |
| brillo::Blob(label.data(), label.data() + label.size())); |
| } |
| }; |
| |
| // We're using casts to map the EncStatefulArea struct to NVRAM contents, so |
| // make sure to keep this a POD type. |
| static_assert(std::is_pod<EncStatefulArea>(), |
| "EncStatefulArea must be a POD type"); |
| |
| // Make sure that the EncStatefulArea struct fits the encstateful NVRAM space. |
| static_assert(kEncStatefulSize >= sizeof(EncStatefulArea), |
| "EncStatefulArea must fit within encstateful NVRAM space"); |
| |
| } // namespace |
| |
| const uint8_t* kOwnerSecret = kWellKnownSecret; |
| const size_t kOwnerSecretSize = sizeof(kWellKnownSecret); |
| |
| // System key loader implementation for TPM1 systems. This supports two |
| // different sources of obtaining system key material: A dedicated NVRAM space |
| // (called the "encstateful NVRAM space" below) and the "salt" in the lockbox |
| // space. We prefer the former if it is available. |
| class Tpm1SystemKeyLoader : public SystemKeyLoader { |
| public: |
| Tpm1SystemKeyLoader(Tpm* tpm, const base::FilePath& rootdir) |
| : tpm_(tpm), rootdir_(rootdir) {} |
| |
| result_code Load(brillo::SecureBlob* key) override; |
| result_code Initialize(const brillo::SecureBlob& key_material, |
| brillo::SecureBlob* derived_system_key) override; |
| result_code Persist() override; |
| void Lock() override; |
| result_code SetupTpm() override; |
| result_code GenerateForPreservation(brillo::SecureBlob* previous_key, |
| brillo::SecureBlob* fresh_key) override; |
| result_code CheckLockbox(bool* valid) override; |
| bool UsingLockboxKey() override; |
| |
| private: |
| // Gets a pointer to the EncStatefulArea structure backed by NVRAM. |
| result_code LoadEncStatefulArea(const EncStatefulArea** area); |
| |
| // Loads the key from the encstateful NVRAM space. |
| result_code LoadEncStatefulKey(brillo::SecureBlob* key); |
| |
| // Loads the key from the lockbox NVRAM space. |
| result_code LoadLockboxKey(brillo::SecureBlob* key); |
| |
| // Validates the encstateful space is defined with correct parameters. |
| result_code IsEncStatefulSpaceProperlyDefined(bool* result); |
| |
| // Obtains and formats TPM version info as key-value pairs. |
| std::string FormatVersionInfo(); |
| |
| // Obtains and formats IFX field upgrade status as key-value pairs. |
| std::string FormatIFXFieldUpgradeInfo(); |
| |
| // Check whether a TPM firmware update is pending. Returns true if there is an |
| // update, false if no pending update and on errors. |
| bool IsTPMFirmwareUpdatePending(); |
| |
| Tpm* tpm_ = nullptr; |
| base::FilePath rootdir_; |
| |
| // Provisional space contents that get initialized by Generate() and written |
| // to the NVRAM space by Persist(); |
| std::unique_ptr<brillo::SecureBlob> provisional_contents_; |
| |
| // Whether we're using the lockbox salt as system key. |
| bool using_lockbox_key_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(Tpm1SystemKeyLoader); |
| }; |
| |
| // TPM cases: |
| // - does not exist at all (disabled in test firmware or non-chrome device). |
| // - exists (below). |
| // |
| // TPM ownership cases: |
| // - unowned (OOBE): |
| // - expect modern lockbox. |
| // - owned: depends on NVRAM area (below). |
| // |
| // NVRAM area cases: |
| // - no NVRAM area at all: |
| // - interrupted install (cryptohome has the TPM password) |
| // - ancient device (cr48, cryptohome has thrown away TPM password) |
| // - broken device (cryptohome has thrown away/never had TPM password) |
| // - must expect worst-case: no lockbox ever. |
| // - defined NVRAM area, but not written to ("Finalized"); interrupted OOBE. |
| // - written ("Finalized") NVRAM area. |
| // |
| // In case of success: (NVRAM area found and used) |
| // - *system_key populated with NVRAM area entropy. |
| // In case of failure: (NVRAM missing or error) |
| // - *system_key untouched. |
| result_code Tpm1SystemKeyLoader::Load(brillo::SecureBlob* system_key) { |
| bool space_properly_defined = false; |
| result_code rc = IsEncStatefulSpaceProperlyDefined(&space_properly_defined); |
| if (rc != RESULT_SUCCESS) { |
| return rc; |
| } |
| |
| // Prefer the encstateful space if it is set up correctly. |
| if (space_properly_defined) { |
| // Only load the key if we are sure that we have generated a fresh key after |
| // the last TPM clear. After a clear, the TPM has no owner. In unowned state |
| // we rely on a flag we store persistently in the TPM to indicate whether we |
| // have generated a key already (note that the TPM automatically clears the |
| // flag on TPM clear). |
| bool system_key_initialized = false; |
| rc = tpm_->HasSystemKeyInitializedFlag(&system_key_initialized); |
| if (rc != RESULT_SUCCESS) { |
| return rc; |
| } |
| |
| if (system_key_initialized) { |
| rc = LoadEncStatefulKey(system_key); |
| if (rc == RESULT_SUCCESS) { |
| return RESULT_SUCCESS; |
| } |
| } |
| } else { |
| // The lockbox NVRAM space is created by cryptohomed and only valid after |
| // TPM ownership has been established. |
| bool owned = false; |
| result_code rc = tpm_->IsOwned(&owned); |
| if (rc != RESULT_SUCCESS) { |
| LOG(ERROR) << "Failed to determine TPM ownership."; |
| return rc; |
| } |
| |
| if (owned) { |
| rc = LoadLockboxKey(system_key); |
| if (rc == RESULT_SUCCESS) { |
| using_lockbox_key_ = true; |
| return RESULT_SUCCESS; |
| } |
| } |
| } |
| |
| return RESULT_FAIL_FATAL; |
| } |
| |
| result_code Tpm1SystemKeyLoader::Initialize( |
| const brillo::SecureBlob& key_material, |
| brillo::SecureBlob* derived_system_key) { |
| provisional_contents_ = |
| std::make_unique<brillo::SecureBlob>(sizeof(EncStatefulArea)); |
| EncStatefulArea* area = |
| reinterpret_cast<EncStatefulArea*>(provisional_contents_->data()); |
| |
| result_code rc = area->Init(key_material); |
| if (rc != RESULT_SUCCESS) { |
| return rc; |
| } |
| |
| if (derived_system_key) { |
| *derived_system_key = area->DeriveKey(kLabelSystemKey); |
| } |
| |
| return RESULT_SUCCESS; |
| } |
| |
| result_code Tpm1SystemKeyLoader::Persist() { |
| CHECK(provisional_contents_); |
| |
| bool space_defined_properly = false; |
| result_code rc = IsEncStatefulSpaceProperlyDefined(&space_defined_properly); |
| if (rc != RESULT_SUCCESS) { |
| return rc; |
| } |
| |
| if (!space_defined_properly) { |
| return RESULT_FAIL_FATAL; |
| } |
| |
| NvramSpace* encstateful_space = tpm_->GetEncStatefulSpace(); |
| rc = encstateful_space->Write(*provisional_contents_); |
| if (rc != RESULT_SUCCESS) { |
| LOG(ERROR) << "Failed to write NVRAM area"; |
| return rc; |
| } |
| |
| rc = tpm_->SetSystemKeyInitializedFlag(); |
| if (rc != RESULT_SUCCESS) { |
| LOG(ERROR) << "Failed to create dummy delegation entry."; |
| return rc; |
| } |
| |
| using_lockbox_key_ = false; |
| return RESULT_SUCCESS; |
| } |
| |
| void Tpm1SystemKeyLoader::Lock() { |
| NvramSpace* encstateful_space = tpm_->GetEncStatefulSpace(); |
| if (!encstateful_space->is_valid()) { |
| return; |
| } |
| |
| if (encstateful_space->WriteLock() != RESULT_SUCCESS) { |
| LOG(ERROR) << "Failed to write-lock NVRAM area."; |
| } |
| if (encstateful_space->ReadLock() != RESULT_SUCCESS) { |
| LOG(ERROR) << "Failed to read-lock NVRAM area."; |
| } |
| } |
| |
| result_code Tpm1SystemKeyLoader::SetupTpm() { |
| bool is_properly_defined = false; |
| result_code rc = IsEncStatefulSpaceProperlyDefined(&is_properly_defined); |
| if (rc != RESULT_SUCCESS) { |
| return rc; |
| } |
| |
| if (is_properly_defined) { |
| return RESULT_SUCCESS; |
| } |
| |
| // We need to take ownership and redefine the space. |
| LOG(INFO) << "Redefining encrypted stateful space."; |
| |
| bool owned = false; |
| rc = tpm_->IsOwned(&owned); |
| if (rc != RESULT_SUCCESS) { |
| LOG(ERROR) << "Can't determine TPM ownership."; |
| return RESULT_FAIL_FATAL; |
| } |
| |
| if (!owned) { |
| // Reset cryptohomed state so it re-initializes the TPM. |
| base::FilePath tpm_status_path = |
| rootdir_.AppendASCII(paths::cryptohome::kTpmStatus); |
| base::FilePath tpm_owned_path = |
| rootdir_.AppendASCII(paths::cryptohome::kTpmOwned); |
| base::FilePath shall_initialize_path = |
| rootdir_.AppendASCII(paths::cryptohome::kShallInitialize); |
| base::FilePath attestation_database_path = |
| rootdir_.AppendASCII(paths::cryptohome::kAttestationDatabase); |
| if (!base::DeleteFile(tpm_status_path, false /* recursive */) || |
| !base::DeleteFile(tpm_owned_path, false /* recursive */) || |
| !brillo::SyncFileOrDirectory(tpm_status_path.DirName(), true, false) || |
| !brillo::WriteToFileAtomic(shall_initialize_path, nullptr, 0, 0644) || |
| !brillo::SyncFileOrDirectory(shall_initialize_path.DirName(), true, |
| false) || |
| !base::DeleteFile(attestation_database_path, false /* recursive */)) { |
| PLOG(ERROR) << "Failed to update cryptohomed state."; |
| return RESULT_FAIL_FATAL; |
| } |
| |
| result_code rc = tpm_->TakeOwnership(); |
| if (rc != RESULT_SUCCESS) { |
| LOG(ERROR) << "Failed to ensure TPM ownership."; |
| return rc; |
| } |
| } |
| |
| uint32_t pcr_selection = (1 << kPCRBootMode); |
| rc = tpm_->GetEncStatefulSpace()->Define(kAttributes, sizeof(EncStatefulArea), |
| pcr_selection); |
| if (rc != RESULT_SUCCESS) { |
| LOG(ERROR) << "Failed to define encrypted stateful NVRAM space."; |
| return rc; |
| } |
| |
| return RESULT_SUCCESS; |
| } |
| |
| result_code Tpm1SystemKeyLoader::GenerateForPreservation( |
| brillo::SecureBlob* previous_key, brillo::SecureBlob* fresh_key) { |
| // Determine whether we may preserve the encryption key that was in use before |
| // the TPM got cleared. Preservation is allowed if either (1) a TPM firmware |
| // update is pending and has been requested for installation or (2) we've |
| // taken a note in NVRAM space flags to anticipate a TPM clear. Condition (2) |
| // covers the TPM clear that follows installation of the firmware update. We'd |
| // prefer to handle that case by testing whether we actually just went through |
| // an update, but there's no trustworthy post-factum signal to tell us. |
| const EncStatefulArea* area = nullptr; |
| bool tpm_firmware_update_pending = false; |
| result_code rc = LoadEncStatefulArea(&area); |
| if (rc != RESULT_SUCCESS || |
| !area->test_flag(EncStatefulArea::Flag::kAnticipatingTPMClear)) { |
| tpm_firmware_update_pending = IsTPMFirmwareUpdatePending(); |
| if (!tpm_firmware_update_pending) { |
| return RESULT_FAIL_FATAL; |
| } |
| } |
| |
| // Load the previous system key. |
| rc = LoadEncStatefulKey(previous_key); |
| if (rc != RESULT_SUCCESS) { |
| rc = LoadLockboxKey(previous_key); |
| if (rc != RESULT_SUCCESS) { |
| return RESULT_FAIL_FATAL; |
| } |
| } |
| |
| // Generate new encstateful contents. |
| provisional_contents_ = |
| std::make_unique<brillo::SecureBlob>(sizeof(EncStatefulArea)); |
| EncStatefulArea* provisional_area = |
| reinterpret_cast<EncStatefulArea*>(provisional_contents_->data()); |
| |
| const auto key_material = |
| cryptohome::CryptoLib::CreateSecureRandomBlob(DIGEST_LENGTH); |
| rc = provisional_area->Init(key_material); |
| if (rc != RESULT_SUCCESS) { |
| return rc; |
| } |
| |
| // Set the flag to anticipate another TPM clear for the case where we're |
| // preserving for the installation of a TPM firmware update. |
| if (tpm_firmware_update_pending) { |
| provisional_area->set_flag(EncStatefulArea::Flag::kAnticipatingTPMClear); |
| } |
| |
| // We need to leave the TPM in a state with owner auth available. However, |
| // when preserving the state of the system, we must guarantee lockbox |
| // integrity. To achieve lockbox tamper evidence, we store a MAC of the |
| // lockbox space in the encstateful space, which gets locked to prevent |
| // further manipulation in Lock(). We can thus re-check lockbox contents are |
| // legit at next reboot by verifying the MAC. |
| provisional_area->set_flag(EncStatefulArea::Flag::kLockboxMacValid); |
| NvramSpace* lockbox_space = tpm_->GetLockboxSpace(); |
| if (lockbox_space->is_valid()) { |
| brillo::SecureBlob mac = cryptohome::CryptoLib::HmacSha256( |
| provisional_area->DeriveKey(kLabelLockboxMAC), |
| lockbox_space->contents()); |
| memcpy(provisional_area->lockbox_mac, mac.data(), mac.size()); |
| } |
| |
| *fresh_key = provisional_area->DeriveKey(kLabelSystemKey); |
| using_lockbox_key_ = false; |
| return RESULT_SUCCESS; |
| } |
| |
| result_code Tpm1SystemKeyLoader::LoadEncStatefulArea( |
| const EncStatefulArea** area) { |
| NvramSpace* space = tpm_->GetEncStatefulSpace(); |
| if (!space->is_valid()) { |
| LOG(ERROR) << "Invalid encstateful space."; |
| return RESULT_FAIL_FATAL; |
| } |
| |
| *area = reinterpret_cast<const EncStatefulArea*>(space->contents().data()); |
| if (!(*area)->is_valid()) { |
| LOG(ERROR) << "Invalid encstateful contents."; |
| return RESULT_FAIL_FATAL; |
| } |
| |
| return RESULT_SUCCESS; |
| } |
| |
| result_code Tpm1SystemKeyLoader::LoadEncStatefulKey( |
| brillo::SecureBlob* system_key) { |
| const EncStatefulArea* area = nullptr; |
| result_code rc = LoadEncStatefulArea(&area); |
| if (rc != RESULT_SUCCESS) { |
| return rc; |
| } |
| |
| *system_key = area->DeriveKey(kLabelSystemKey); |
| return RESULT_SUCCESS; |
| } |
| |
| result_code Tpm1SystemKeyLoader::LoadLockboxKey( |
| brillo::SecureBlob* system_key) { |
| brillo::SecureBlob key_material; |
| NvramSpace* lockbox_space = tpm_->GetLockboxSpace(); |
| const brillo::SecureBlob& lockbox_contents = lockbox_space->contents(); |
| if (!lockbox_space->is_valid()) { |
| return RESULT_FAIL_FATAL; |
| } else if (lockbox_contents.size() == kLockboxSizeV1) { |
| key_material = lockbox_contents; |
| } else if (kLockboxSaltOffset + DIGEST_LENGTH <= lockbox_contents.size()) { |
| const uint8_t* begin = lockbox_contents.data() + kLockboxSaltOffset; |
| key_material.assign(begin, begin + DIGEST_LENGTH); |
| } else { |
| LOG(INFO) << "Impossibly small NVRAM area size (" |
| << lockbox_contents.size() << ")."; |
| return RESULT_FAIL_FATAL; |
| } |
| |
| *system_key = cryptohome::CryptoLib::Sha256(key_material); |
| return RESULT_SUCCESS; |
| } |
| |
| result_code Tpm1SystemKeyLoader::IsEncStatefulSpaceProperlyDefined( |
| bool* result) { |
| *result = false; |
| |
| NvramSpace* encstateful_space = tpm_->GetEncStatefulSpace(); |
| if (!encstateful_space->is_valid() || |
| encstateful_space->contents().size() < sizeof(EncStatefulArea)) { |
| LOG(ERROR) << "encstateful space contents absent or too short."; |
| return RESULT_SUCCESS; |
| } |
| |
| uint32_t attributes; |
| result_code rc = encstateful_space->GetAttributes(&attributes); |
| if (rc != RESULT_SUCCESS) { |
| return rc; |
| } |
| |
| if ((attributes & kAttributesMask) != kAttributes) { |
| LOG(ERROR) << "Bad encstateful space attributes."; |
| return rc; |
| } |
| |
| uint32_t pcr_selection = (1 << kPCRBootMode); |
| bool pcr_binding_correct = false; |
| rc = encstateful_space->CheckPCRBinding(pcr_selection, &pcr_binding_correct); |
| if (rc != RESULT_SUCCESS) { |
| LOG(ERROR) << "Bad encstateful PCR binding."; |
| return rc; |
| } |
| |
| *result = pcr_binding_correct; |
| return RESULT_SUCCESS; |
| } |
| |
| std::string Tpm1SystemKeyLoader::FormatVersionInfo() { |
| uint32_t vendor; |
| uint64_t firmware_version; |
| std::vector<uint8_t> vendor_specific; |
| if (!tpm_->GetVersionInfo(&vendor, &firmware_version, &vendor_specific)) { |
| return std::string(); |
| } |
| |
| return base::StringPrintf( |
| "vendor %08x\nfirmware_version %016" PRIx64 "\nvendor_specific %s", |
| vendor, firmware_version, |
| base::HexEncode(vendor_specific.data(), vendor_specific.size()).c_str()); |
| } |
| |
| std::string Tpm1SystemKeyLoader::FormatIFXFieldUpgradeInfo() { |
| TPM_IFX_FIELDUPGRADEINFO info; |
| if (!tpm_->GetIFXFieldUpgradeInfo(&info)) { |
| return std::string(); |
| } |
| |
| auto format_fw_pkg = [](const TPM_IFX_FIRMWAREPACKAGE& firmware_package, |
| const char* prefix) { |
| return base::StringPrintf( |
| "%s_package_id %08x\n" |
| "%s_version %08x\n" |
| "%s_stale_version %08x\n", |
| prefix, firmware_package.FwPackageIdentifier, prefix, |
| firmware_package.Version, prefix, firmware_package.StaleVersion); |
| }; |
| |
| return base::StringPrintf( |
| "max_data_size %u\n" |
| "%s" |
| "%s" |
| "%s" |
| "status %04x\n" |
| "%s" |
| "field_upgrade_counter %u\n", |
| info.wMaxDataSize, |
| format_fw_pkg(info.sBootloaderFirmwarePackage, "bootloader").c_str(), |
| format_fw_pkg(info.sFirmwarePackages[0], "fw0").c_str(), |
| format_fw_pkg(info.sFirmwarePackages[1], "fw1").c_str(), |
| info.wSecurityModuleStatus, |
| format_fw_pkg(info.sProcessFirmwarePackage, "process_fw").c_str(), |
| info.wFieldUpgradeCounter); |
| } |
| |
| bool Tpm1SystemKeyLoader::IsTPMFirmwareUpdatePending() { |
| // Make sure a TPM firmware upgrade has been requested. |
| if (!base::PathExists(rootdir_.AppendASCII(paths::kFirmwareUpdateRequest))) { |
| LOG(ERROR) << "TPM firmware update wasn't requested."; |
| return false; |
| } |
| |
| // Obtain version and upgrade status information to pass to the locator tool. |
| std::string version_info = FormatVersionInfo(); |
| std::string ifx_field_upgrade_info = FormatIFXFieldUpgradeInfo(); |
| if (version_info.empty() || ifx_field_upgrade_info.empty()) { |
| return false; |
| } |
| |
| // Launch the update locator script. |
| brillo::ProcessImpl tpm_firmware_update_locator; |
| tpm_firmware_update_locator.SetCloseUnusedFileDescriptors(true); |
| tpm_firmware_update_locator.RedirectUsingPipe(STDOUT_FILENO, false); |
| tpm_firmware_update_locator.AddArg( |
| rootdir_.AppendASCII(paths::kFirmwareUpdateLocator).value()); |
| tpm_firmware_update_locator.AddArg(version_info); |
| tpm_firmware_update_locator.AddArg(ifx_field_upgrade_info); |
| if (!tpm_firmware_update_locator.Start()) { |
| LOG(ERROR) << "Failed to start update locator child process"; |
| return false; |
| } |
| |
| // Read the output. |
| { |
| base::File pipe(tpm_firmware_update_locator.GetPipe(STDOUT_FILENO)); |
| char update_location[PATH_MAX]; |
| int bytes_read = |
| pipe.ReadAtCurrentPos(update_location, sizeof(update_location)); |
| if (bytes_read <= 0) { |
| LOG(ERROR) << "Failed to read update location from pipe."; |
| return false; |
| } |
| |
| // Check that the update location file exists. |
| char* newline_pos = strchr(update_location, '\n'); |
| if (newline_pos) { |
| *newline_pos = '\0'; |
| } |
| base::FilePath update_path(update_location); |
| LOG(INFO) << "Checking whether " |
| << rootdir_.AppendASCII(paths::kFirmwareDir) << " is a parent of " |
| << update_path; |
| if (!rootdir_.AppendASCII(paths::kFirmwareDir).IsParent(update_path) || |
| !base::PathExists(update_path)) { |
| LOG(ERROR) << "Failure locating TPM firmware update file."; |
| return false; |
| } |
| } |
| |
| // Make sure the locator script terminated cleanly. |
| if (tpm_firmware_update_locator.Wait() != 0) { |
| LOG(ERROR) << "TPM firmware update locator utility failed."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| result_code Tpm1SystemKeyLoader::CheckLockbox(bool* valid) { |
| *valid = false; |
| |
| bool space_properly_defined = false; |
| result_code rc = IsEncStatefulSpaceProperlyDefined(&space_properly_defined); |
| if (rc != RESULT_SUCCESS) { |
| return rc; |
| } |
| |
| if (space_properly_defined) { |
| // Check whether the encstateful space contains a valid lockbox MAC. Check |
| // the actual lockbox contents against the MAC, reset the lockbox space to |
| // invalid so subsequent code won't use it (specifically, the lockbox space |
| // won't get exported for OS consumption). |
| // |
| // This addresses the scenario where the TPM is left in unowned state or |
| // owned with the well-known password after preservation. The requirement is |
| // that the lockbox contents may only change at full device reset (e.g. |
| // implying stateful file system loss). However, stateful preservation |
| // carries over state, so it needs to ensure the lockbox stays locked. Due |
| // to the TPM state, the lockbox space could get redefined and thus written |
| // to after preservation. The MAC check here doesn't disallow this, but it |
| // ensures tamper-evidence: Modified lockbox contents will cause MAC |
| // validation failure, so the lockbox will be considered invalid. Note that |
| // attempts at adjusting the MAC to match tampered lockbox contents are |
| // prevented by locking the encstateful space after boot. |
| const EncStatefulArea* area = nullptr; |
| rc = LoadEncStatefulArea(&area); |
| switch (rc) { |
| case RESULT_SUCCESS: |
| if (area->test_flag(EncStatefulArea::Flag::kLockboxMacValid)) { |
| NvramSpace* lockbox_space = tpm_->GetLockboxSpace(); |
| if (lockbox_space->is_valid()) { |
| brillo::SecureBlob mac = cryptohome::CryptoLib::HmacSha256( |
| area->DeriveKey(kLabelLockboxMAC), lockbox_space->contents()); |
| *valid = brillo::SecureMemcmp(area->lockbox_mac, mac.data(), |
| mac.size()) == 0; |
| return RESULT_SUCCESS; |
| } |
| } |
| break; |
| case RESULT_FAIL_FATAL: |
| // Encstateful contents invalid, so lockbox MAC doesn't apply. |
| break; |
| default: |
| return rc; |
| } |
| } |
| |
| // In case there is no encstateful space, the lockbox space is only valid once |
| // cryptohomed has taken TPM ownership and recreated the space. |
| return tpm_->IsOwned(valid); |
| } |
| |
| bool Tpm1SystemKeyLoader::UsingLockboxKey() { |
| return using_lockbox_key_; |
| } |
| |
| std::unique_ptr<SystemKeyLoader> SystemKeyLoader::Create( |
| Tpm* tpm, const base::FilePath& rootdir) { |
| return std::make_unique<Tpm1SystemKeyLoader>(tpm, rootdir); |
| } |
| |
| } // namespace mount_encrypted |