| // 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/vendor.h" | |
| #include <array> | |
| #include <cinttypes> | |
| #include <cstdint> | |
| #include <string> | |
| #include <utility> | |
| #include <base/strings/string_number_conversions.h> | |
| #include <base/strings/string_split.h> | |
| #include <base/strings/stringprintf.h> | |
| #include <base/sys_byteorder.h> | |
| #include <libhwsec-foundation/crypto/sha.h> | |
| #include <libhwsec-foundation/status/status_chain_macros.h> | |
| #include <tpm_manager/proto_bindings/tpm_manager.pb.h> | |
| #include <tpm_manager-client/tpm_manager/dbus-proxies.h> | |
| #include <trunks/tpm_generated.h> | |
| #include <trunks/tpm_utility.h> | |
| #include "libhwsec/error/tpm2_error.h" | |
| #include "libhwsec/error/tpm_manager_error.h" | |
| using brillo::BlobFromString; | |
| using brillo::BlobToString; | |
| using hwsec_foundation::Sha256; | |
| using hwsec_foundation::status::MakeStatus; | |
| namespace hwsec { | |
| namespace { | |
| Status GetResponseStatus(const std::string& response) { | |
| std::string buffer(response); | |
| trunks::TPM_ST tag; | |
| RETURN_IF_ERROR( | |
| MakeStatus<TPM2Error>(trunks::Parse_TPM_ST(&buffer, &tag, nullptr))) | |
| .WithStatus<TPMError>("Failed to parse response tag"); | |
| trunks::UINT32 response_size; | |
| RETURN_IF_ERROR(MakeStatus<TPM2Error>( | |
| trunks::Parse_UINT32(&buffer, &response_size, nullptr))) | |
| .WithStatus<TPMError>("Failed to parse response size"); | |
| if (response_size != response.size()) { | |
| return MakeStatus<TPMError>("Mismatch response size", | |
| TPMRetryAction::kNoRetry); | |
| } | |
| trunks::TPM_RC rc; | |
| RETURN_IF_ERROR( | |
| MakeStatus<TPM2Error>(trunks::Parse_TPM_RC(&buffer, &rc, nullptr))) | |
| .WithStatus<TPMError>("Failed to parse TPM_RC"); | |
| Status status = MakeStatus<TPM2Error>(rc); | |
| if (status.ok()) { | |
| return OkStatus(); | |
| } | |
| if (status.err_status()->ToTPMRetryAction() != | |
| TPMRetryAction::kCommunication) { | |
| // Communication error is the only valid retry action for vendor commands. | |
| LOG(WARNING) << "TPM vendor command error: " << status; | |
| return OkStatus(); | |
| } | |
| return status; | |
| } | |
| } // namespace | |
| Status VendorTpm2::EnsureVersionInfo() { | |
| if (version_info_.has_value()) { | |
| return OkStatus(); | |
| } | |
| tpm_manager::GetVersionInfoRequest request; | |
| tpm_manager::GetVersionInfoReply reply; | |
| if (brillo::ErrorPtr err; !tpm_manager_.GetVersionInfo( | |
| request, &reply, &err, Proxy::kDefaultDBusTimeoutMs)) { | |
| return MakeStatus<TPMError>(TPMRetryAction::kCommunication) | |
| .Wrap(std::move(err)); | |
| } | |
| RETURN_IF_ERROR(MakeStatus<TPMManagerError>(reply.status())); | |
| version_info_ = std::move(reply); | |
| return OkStatus(); | |
| } | |
| StatusOr<uint32_t> VendorTpm2::GetFamily() { | |
| RETURN_IF_ERROR(EnsureVersionInfo()); | |
| return version_info_->family(); | |
| } | |
| StatusOr<uint64_t> VendorTpm2::GetSpecLevel() { | |
| RETURN_IF_ERROR(EnsureVersionInfo()); | |
| return version_info_->spec_level(); | |
| } | |
| StatusOr<uint32_t> VendorTpm2::GetManufacturer() { | |
| RETURN_IF_ERROR(EnsureVersionInfo()); | |
| return version_info_->manufacturer(); | |
| } | |
| StatusOr<uint32_t> VendorTpm2::GetTpmModel() { | |
| RETURN_IF_ERROR(EnsureVersionInfo()); | |
| return version_info_->tpm_model(); | |
| } | |
| StatusOr<uint64_t> VendorTpm2::GetFirmwareVersion() { | |
| RETURN_IF_ERROR(EnsureVersionInfo()); | |
| return version_info_->firmware_version(); | |
| } | |
| StatusOr<brillo::Blob> VendorTpm2::GetVendorSpecific() { | |
| RETURN_IF_ERROR(EnsureVersionInfo()); | |
| return brillo::BlobFromString(version_info_->vendor_specific()); | |
| } | |
| StatusOr<int32_t> VendorTpm2::GetFingerprint() { | |
| RETURN_IF_ERROR(EnsureVersionInfo()); | |
| // The exact encoding doesn't matter as long as its unambiguous, stable and | |
| // contains all information present in the version fields. | |
| std::string encoded_parameters = base::StringPrintf( | |
| "%08" PRIx32 "%016" PRIx64 "%08" PRIx32 "%08" PRIx32 "%016" PRIx64 | |
| "%016zx", | |
| version_info_->family(), version_info_->spec_level(), | |
| version_info_->manufacturer(), version_info_->tpm_model(), | |
| version_info_->firmware_version(), | |
| version_info_->vendor_specific().size()); | |
| encoded_parameters.append(version_info_->vendor_specific()); | |
| brillo::Blob hash = Sha256(brillo::BlobFromString(encoded_parameters)); | |
| // Return the first 31 bits from |hash|. | |
| uint32_t result = static_cast<uint32_t>(hash[0]) | | |
| static_cast<uint32_t>(hash[1]) << 8 | | |
| static_cast<uint32_t>(hash[2]) << 16 | | |
| static_cast<uint32_t>(hash[3]) << 24; | |
| return result & 0x7fffffff; | |
| } | |
| StatusOr<VendorTpm2::GscType> VendorTpm2::GetGscType() { | |
| RETURN_IF_ERROR(EnsureVersionInfo()); | |
| switch (version_info_->gsc_version()) { | |
| case tpm_manager::GscVersion::GSC_VERSION_CR50: | |
| return GscType::kCr50; | |
| case tpm_manager::GscVersion::GSC_VERSION_TI50: | |
| return GscType::kTi50; | |
| default: | |
| return GscType::kNotGsc; | |
| } | |
| } | |
| StatusOr<bool> VendorTpm2::IsSrkRocaVulnerable() { | |
| return false; | |
| } | |
| StatusOr<brillo::Blob> VendorTpm2::GetLegacyRsuDeviceId() { | |
| std::string device_id; | |
| RETURN_IF_ERROR(MakeStatus<TPM2Error>( | |
| context_.GetTpmUtility().GetRsuDeviceId(&device_id))) | |
| .WithStatus<TPMError>("Failed to get the RSU device ID"); | |
| return BlobFromString(device_id); | |
| } | |
| StatusOr<brillo::Blob> VendorTpm2::GetRsuDeviceId() { | |
| // Try to read the virtual NV RSU device ID first. | |
| StatusOr<brillo::Blob> device_id = ro_data_.Read(RoSpace::kRsuDeviceId); | |
| if (device_id.ok()) { | |
| if (!device_id->empty()) { | |
| return device_id; | |
| } else { | |
| LOG(WARNING) << "Empty virtual NV device ID."; | |
| } | |
| } else { | |
| LOG(WARNING) << "Failed to get virtual NV device ID: " | |
| << device_id.err_status(); | |
| } | |
| // Some older version of cr50 don't support virtual NV RSU device ID, | |
| // fallback to the legacy method. | |
| return GetLegacyRsuDeviceId(); | |
| } | |
| StatusOr<IFXFieldUpgradeInfo> VendorTpm2::GetIFXFieldUpgradeInfo() { | |
| return MakeStatus<TPMError>("Unsupported command", TPMRetryAction::kNoRetry); | |
| } | |
| Status VendorTpm2::DeclareTpmFirmwareStable() { | |
| if (fw_declared_stable_) { | |
| return OkStatus(); | |
| } | |
| RETURN_IF_ERROR(MakeStatus<TPM2Error>( | |
| context_.GetTpmUtility().DeclareTpmFirmwareStable())) | |
| .WithStatus<TPMError>("Failed to declare TPM firmware stable"); | |
| fw_declared_stable_ = true; | |
| return OkStatus(); | |
| } | |
| StatusOr<VendorTpm2::RwVersion> VendorTpm2::GetRwVersion() { | |
| RETURN_IF_ERROR(EnsureVersionInfo()); | |
| // The rw_version format is x.y.z | |
| auto ver = base::SplitString(version_info_->rw_version(), ".", | |
| base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); | |
| if (ver.size() != 3) { | |
| return MakeStatus<TPMError>("Incorrect response size", | |
| TPMRetryAction::kNoRetry); | |
| } | |
| VendorTpm2::RwVersion rw_version; | |
| base::StringToUint(ver[0], &rw_version.epoch); | |
| base::StringToUint(ver[1], &rw_version.major); | |
| base::StringToUint(ver[2], &rw_version.minor); | |
| return rw_version; | |
| } | |
| StatusOr<brillo::Blob> VendorTpm2::SendRawCommand(const brillo::Blob& command) { | |
| std::string response = context_.GetCommandTransceiver().SendCommandAndWait( | |
| BlobToString(command)); | |
| RETURN_IF_ERROR(GetResponseStatus(response)); | |
| return BlobFromString(response); | |
| } | |
| } // namespace hwsec |