| // 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. |
| |
| // Contains the implementation of class Tpm |
| |
| #include "cryptohome/tpm2_impl.h" |
| |
| #include <cinttypes> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/check.h> |
| #include <base/logging.h> |
| #include <base/message_loop/message_pump_type.h> |
| #include <base/notreached.h> |
| #include <base/numerics/safe_conversions.h> |
| #include <base/strings/stringprintf.h> |
| #include <crypto/libcrypto-compat.h> |
| #include <crypto/scoped_openssl_types.h> |
| #include <libhwsec/error/tpm_retry_handler.h> |
| #include <libhwsec/error/tpm2_error.h> |
| #include <openssl/bn.h> |
| #include <openssl/evp.h> |
| #include <openssl/rsa.h> |
| #include <openssl/x509.h> |
| #include <tpm_manager-client/tpm_manager/dbus-constants.h> |
| #include <trunks/authorization_delegate.h> |
| #include <trunks/blob_parser.h> |
| #include <trunks/error_codes.h> |
| #include <trunks/openssl_utility.h> |
| #include <trunks/policy_session.h> |
| #include <trunks/tpm_alerts.h> |
| #include <trunks/tpm_constants.h> |
| #include <trunks/trunks_dbus_proxy.h> |
| #include <trunks/trunks_factory.h> |
| #include <trunks/trunks_factory_impl.h> |
| |
| #include "cryptohome/crypto/aes.h" |
| #include "cryptohome/crypto/big_num_util.h" |
| #include "cryptohome/crypto/elliptic_curve.h" |
| #include "cryptohome/crypto/elliptic_curve_error.h" |
| #include "cryptohome/crypto/rsa.h" |
| #include "cryptohome/crypto/sha.h" |
| |
| using brillo::Blob; |
| using brillo::BlobFromString; |
| using brillo::BlobToString; |
| using brillo::SecureBlob; |
| using hwsec::StatusChain; |
| using hwsec::TPM2Error; |
| using hwsec::TPMError; |
| using hwsec::TPMErrorBase; |
| using hwsec::TPMRetryAction; |
| using hwsec_foundation::error::CreateError; |
| using hwsec_foundation::error::WrapError; |
| using trunks::GetErrorString; |
| using trunks::TPM_RC_SUCCESS; |
| using trunks::TrunksFactory; |
| |
| namespace cryptohome { |
| |
| namespace { |
| |
| constexpr trunks::TPMI_ECC_CURVE kDefaultTpmCurveId = trunks::TPM_ECC_NIST_P256; |
| |
| constexpr EllipticCurve::CurveType kDefaultCurve = |
| EllipticCurve::CurveType::kPrime256; |
| |
| constexpr int kMinPassBlobSize = 32; |
| |
| // Returns the total number of bits set in the first |size| elements from |
| // |array|. |
| int CountSetBits(const uint8_t* array, size_t size) { |
| int res = 0; |
| for (size_t i = 0; i < size; ++i) { |
| for (int bit_position = 0; bit_position < 8; ++bit_position) { |
| if ((array[i] & (1 << bit_position)) != 0) { |
| ++res; |
| } |
| } |
| } |
| return res; |
| } |
| |
| std::string OwnerDependencyEnumClassToString( |
| Tpm::TpmOwnerDependency dependency) { |
| switch (dependency) { |
| case Tpm::TpmOwnerDependency::kInstallAttributes: |
| return tpm_manager::kTpmOwnerDependency_Nvram; |
| case Tpm::TpmOwnerDependency::kAttestation: |
| return tpm_manager::kTpmOwnerDependency_Attestation; |
| default: |
| NOTREACHED() << __func__ << ": Unexpected enum class value: " |
| << static_cast<int>(dependency); |
| return ""; |
| } |
| } |
| |
| trunks::TpmUtility::AsymmetricKeyUsage ConvertAsymmetricKeyUsage( |
| AsymmetricKeyUsage usage) { |
| switch (usage) { |
| case AsymmetricKeyUsage::kDecryptKey: |
| return trunks::TpmUtility::AsymmetricKeyUsage::kDecryptKey; |
| case AsymmetricKeyUsage::kSignKey: |
| return trunks::TpmUtility::AsymmetricKeyUsage::kSignKey; |
| case AsymmetricKeyUsage::kDecryptAndSignKey: |
| return trunks::TpmUtility::AsymmetricKeyUsage::kDecryptAndSignKey; |
| default: |
| NOTREACHED() << __func__ << ": Unexpected enum class value: " |
| << static_cast<int>(usage); |
| return trunks::TpmUtility::AsymmetricKeyUsage::kDecryptKey; |
| } |
| } |
| |
| StatusChain<TPMErrorBase> DeriveTpmEccPointFromSeed( |
| const SecureBlob& seed, trunks::TPMS_ECC_POINT* out_point) { |
| // Generate an ECC private key (scalar) based on the seed. |
| crypto::ScopedBIGNUM private_key = SecureBlobToBigNum(Sha256(seed)); |
| |
| ScopedBN_CTX context = CreateBigNumContext(); |
| if (!context.get()) { |
| return CreateError<TPMError>("Failed to allocate BN_CTX structure", |
| TPMRetryAction::kNoRetry); |
| } |
| |
| base::Optional<EllipticCurve> ec = |
| EllipticCurve::Create(kDefaultCurve, context.get()); |
| if (!ec) { |
| return CreateError<TPMError>("Failed to create EllipticCurve", |
| TPMRetryAction::kNoRetry); |
| } |
| |
| if (!ec->IsScalarValid(*private_key)) { |
| // Generate another pass_blob may resolve this issue. |
| return CreateError<EllipticCurveError>( |
| EllipticCurveErrorCode::kScalarOutOfRange); |
| } |
| |
| crypto::ScopedEC_POINT public_point = |
| ec->MultiplyWithGenerator(*private_key, context.get()); |
| |
| if (!public_point) { |
| return CreateError<TPMError>("Failed to multiply with generator", |
| TPMRetryAction::kNoRetry); |
| } |
| |
| if (!trunks::OpensslToTpmEccPoint(*ec->GetGroup(), *public_point, |
| ec->AffineCoordinateSizeInBytes(), |
| out_point)) { |
| return CreateError<TPMError>("Error converting OpenSSL to TPM ECC point", |
| TPMRetryAction::kNoRetry); |
| } |
| |
| return nullptr; |
| } |
| |
| std::map<uint32_t, std::string> ToStrPcrMap( |
| const std::map<uint32_t, brillo::Blob>& pcr_map) { |
| std::map<uint32_t, std::string> str_pcr_map; |
| for (const auto& [index, value] : pcr_map) { |
| str_pcr_map[index] = brillo::BlobToString(value); |
| } |
| return str_pcr_map; |
| } |
| } // namespace |
| |
| // Keep it with sync to UMA enum list |
| // https://chromium.googlesource.com/chromium/src/+/HEAD/tools/metrics/histograms/enums.xml |
| // These values are persisted to logs, and should therefore never be renumbered |
| // nor reused. |
| enum TpmAlerts { |
| kCamoBreach = 1, |
| kDmemParity = 2, |
| kDrfParity = 3, |
| kImemParity = 4, |
| kPgmFault = 5, |
| kCpuDIfBusError = 6, |
| kCpuDIfUpdateWatchdog = 7, |
| kCpuIIfBusError = 8, |
| kCpuIIfUpdateWatchdog = 9, |
| kCpuSIfBusError = 10, |
| kCpuSIfUpdateWatchdog = 11, |
| kDmaIfBusErr = 12, |
| kDmaIfUpdateWatchdog = 13, |
| kSpsIfBusErr = 14, |
| kSpsIfUpdateWatchdog = 15, |
| kUsbIfBusErr = 16, |
| kUsbIfUpdateWatchdog = 17, |
| kFuseDefaults = 18, |
| kDiffFail = 19, |
| kSoftwareAlert0 = 20, |
| kSoftwareAlert1 = 21, |
| kSoftwareAlert2 = 22, |
| kSoftwareAlert3 = 23, |
| kHearbitFail = 24, |
| kProcOpcodeHash = 25, |
| kSramParityScrub = 26, |
| kAesExecCtrMax = 27, |
| kAesHkey = 28, |
| kCertLookup = 29, |
| kFlashEntry = 30, |
| kPw = 31, |
| kShaExecCtrMax = 32, |
| kShaFault = 33, |
| kShaHkey = 34, |
| kPmuBatteryMon = 35, |
| kPmuWatchdog = 36, |
| kRtcDead = 37, |
| kTempMax = 38, |
| kTempMaxDiff = 39, |
| kTempMin = 40, |
| kRngOutOfSpec = 41, |
| kRngTimeout = 42, |
| kVoltageError = 43, |
| kXoJitteryTrim = 44, |
| |
| kTPMAlertNumBuckets, // Must be the last entry. |
| }; |
| static_assert(kTPMAlertNumBuckets <= trunks::kAlertsMaxSize + 1, |
| "Number of UMA enums less than alerts set size"); |
| |
| // Maps alerts identifiers received from TMP firmware to UMA identifiers |
| const TpmAlerts h1AlertsMap[trunks::kH1AlertsSize] = { |
| kCamoBreach, |
| kDmemParity, |
| kDrfParity, |
| kImemParity, |
| kPgmFault, |
| kCpuDIfBusError, |
| kCpuDIfUpdateWatchdog, |
| kCpuIIfBusError, |
| kCpuIIfUpdateWatchdog, |
| kCpuSIfBusError, |
| kCpuSIfUpdateWatchdog, |
| kDmaIfBusErr, |
| kDmaIfUpdateWatchdog, |
| kSpsIfBusErr, |
| kSpsIfUpdateWatchdog, |
| kUsbIfBusErr, |
| kUsbIfUpdateWatchdog, |
| kFuseDefaults, |
| kDiffFail, |
| kSoftwareAlert0, |
| kSoftwareAlert1, |
| kSoftwareAlert2, |
| kSoftwareAlert3, |
| kHearbitFail, |
| kProcOpcodeHash, |
| kSramParityScrub, |
| kAesExecCtrMax, |
| kAesHkey, |
| kCertLookup, |
| kFlashEntry, |
| kPw, |
| kShaExecCtrMax, |
| kShaFault, |
| kShaHkey, |
| kPmuBatteryMon, |
| kPmuWatchdog, |
| kRtcDead, |
| kTempMax, |
| kTempMaxDiff, |
| kTempMin, |
| kRngOutOfSpec, |
| kRngTimeout, |
| kVoltageError, |
| kXoJitteryTrim, |
| }; |
| |
| Tpm2Impl::Tpm2Impl(TrunksFactory* factory, |
| tpm_manager::TpmManagerUtility* tpm_manager_utility) |
| : tpm_manager_utility_(tpm_manager_utility), |
| has_external_trunks_context_(true) { |
| external_trunks_context_.factory = factory; |
| external_trunks_context_.tpm_state = factory->GetTpmState(); |
| external_trunks_context_.tpm_utility = factory->GetTpmUtility(); |
| } |
| |
| bool Tpm2Impl::InitializeTpmManagerUtility() { |
| if (!tpm_manager_utility_) { |
| tpm_manager_utility_ = tpm_manager::TpmManagerUtility::GetSingleton(); |
| if (!tpm_manager_utility_) { |
| LOG(ERROR) << __func__ << ": Failed to get TpmManagerUtility singleton!"; |
| } |
| } |
| return tpm_manager_utility_ && tpm_manager_utility_->Initialize(); |
| } |
| |
| bool Tpm2Impl::CacheTpmManagerStatus() { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| return tpm_manager_utility_->GetTpmStatus(&is_enabled_, &is_owned_, |
| &last_tpm_manager_data_); |
| } |
| |
| bool Tpm2Impl::IsEnabled() { |
| if (!is_enabled_) { |
| if (!CacheTpmManagerStatus()) { |
| LOG(ERROR) << __func__ << ": Failed to call |UpdateTpmStatus|."; |
| return false; |
| } |
| } |
| return is_enabled_; |
| } |
| |
| bool Tpm2Impl::IsOwned() { |
| if (!is_owned_) { |
| if (!UpdateTpmStatus(RefreshType::REFRESH_IF_NEEDED)) { |
| LOG(ERROR) << __func__ << ": Failed to call |UpdateTpmStatus|."; |
| return false; |
| } |
| } |
| return is_owned_; |
| } |
| |
| bool Tpm2Impl::IsOwnerPasswordPresent() { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| bool is_owner_password_present = false; |
| if (!tpm_manager_utility_->GetTpmNonsensitiveStatus( |
| nullptr, nullptr, &is_owner_password_present, nullptr)) { |
| LOG(ERROR) << __func__ << ": Failed to get |is_owner_password_present|."; |
| return false; |
| } |
| return is_owner_password_present; |
| } |
| |
| bool Tpm2Impl::HasResetLockPermissions() { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| bool has_reset_lock_permissions = false; |
| if (!tpm_manager_utility_->GetTpmNonsensitiveStatus( |
| nullptr, nullptr, nullptr, &has_reset_lock_permissions)) { |
| LOG(ERROR) << __func__ << ": Failed to get |has_reset_lock_permissions|."; |
| return false; |
| } |
| return has_reset_lock_permissions; |
| } |
| |
| StatusChain<hwsec::TPMErrorBase> Tpm2Impl::GetRandomDataBlob( |
| size_t length, brillo::Blob* data) { |
| brillo::SecureBlob blob(length); |
| if (StatusChain<TPMErrorBase> err = GetRandomDataSecureBlob(length, &blob)) { |
| return WrapError<TPMError>(std::move(err), "GetRandomDataBlob failed"); |
| } |
| data->assign(blob.begin(), blob.end()); |
| return nullptr; |
| } |
| |
| StatusChain<hwsec::TPMErrorBase> Tpm2Impl::GetRandomDataSecureBlob( |
| size_t length, brillo::SecureBlob* data) { |
| CHECK(data); |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return CreateError<TPMError>("Failed to get trunks context", |
| TPMRetryAction::kNoRetry); |
| } |
| std::string random_data; |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->GenerateRandom( |
| length, /* delegate */ nullptr, &random_data)))) { |
| return WrapError<TPMError>(std::move(err), "Error getting random data"); |
| } |
| if (random_data.size() != length) { |
| return CreateError<TPMError>( |
| base::StringPrintf("Error getting random data: requested length %zu" |
| ", received length %zu", |
| length, random_data.size()), |
| TPMRetryAction::kNoRetry); |
| } |
| data->assign(random_data.begin(), random_data.end()); |
| return nullptr; |
| } |
| |
| StatusChain<hwsec::TPMErrorBase> Tpm2Impl::GetAlertsData( |
| Tpm::AlertsData* alerts) { |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return CreateError<TPMError>("Failed to get trunks context", |
| TPMRetryAction::kNoRetry); |
| } |
| |
| trunks::TpmAlertsData trunks_alerts; |
| if (StatusChain<TPMErrorBase> err = |
| HANDLE_TPM_COMM_ERROR(CreateError<TPM2Error>( |
| trunks->tpm_utility->GetAlertsData(&trunks_alerts)))) { |
| if (err.Is<TPM2Error>() && |
| err.Cast<TPM2Error>()->ErrorCode() == trunks::TPM_RC_NO_SUCH_COMMAND) { |
| return WrapError<TPMError>( |
| std::move(err), "TPM GetAlertsData vendor command is not implemented", |
| TPMRetryAction::kNoRetry); |
| } else { |
| memset(alerts, 0, sizeof(Tpm::AlertsData)); |
| return WrapError<TPMError>(std::move(err), "Error getting alerts data"); |
| } |
| } else if (trunks_alerts.chip_family != trunks::kFamilyH1) { |
| // Currently we support only H1 alerts |
| return CreateError<TPMError>( |
| "Unknown alerts family: " + std::to_string(trunks_alerts.chip_family), |
| TPMRetryAction::kNoRetry); |
| } |
| |
| memset(alerts, 0, sizeof(Tpm::AlertsData)); |
| for (int i = 0; i < trunks_alerts.alerts_num; i++) { |
| size_t uma_idx = h1AlertsMap[i]; |
| if (uma_idx <= 0 || uma_idx >= kTPMAlertNumBuckets) { |
| LOG(ERROR) << "Alert index " << i << " maps into invalid UMA enum index " |
| << uma_idx; |
| } else { |
| alerts->counters[uma_idx] = trunks_alerts.counters[i]; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| bool Tpm2Impl::DefineNvram(uint32_t index, size_t length, uint32_t flags) { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| const bool write_define = flags & Tpm::kTpmNvramWriteDefine; |
| const bool bind_to_pcr0 = flags & Tpm::kTpmNvramBindToPCR0; |
| const bool firmware_readable = flags & Tpm::kTpmNvramFirmwareReadable; |
| |
| return tpm_manager_utility_->DefineSpace(index, length, write_define, |
| bind_to_pcr0, firmware_readable); |
| } |
| |
| bool Tpm2Impl::DestroyNvram(uint32_t index) { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| return tpm_manager_utility_->DestroySpace(index); |
| } |
| |
| bool Tpm2Impl::WriteNvram(uint32_t index, const SecureBlob& blob) { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| return tpm_manager_utility_->WriteSpace(index, blob.to_string(), |
| /*use_owner_auth=*/false); |
| } |
| |
| bool Tpm2Impl::OwnerWriteNvram(uint32_t index, const SecureBlob& blob) { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| return tpm_manager_utility_->WriteSpace(index, blob.to_string(), |
| /*use_owner_auth=*/true); |
| } |
| |
| bool Tpm2Impl::ReadNvram(uint32_t index, SecureBlob* blob) { |
| if (!InitializeTpmManagerUtility()) { |
| return false; |
| } |
| |
| std::string output; |
| const bool result = tpm_manager_utility_->ReadSpace(index, false, &output); |
| SecureBlob tmp(output); |
| blob->swap(tmp); |
| return result; |
| } |
| |
| bool Tpm2Impl::IsNvramDefined(uint32_t index) { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| std::vector<uint32_t> spaces; |
| if (!tpm_manager_utility_->ListSpaces(&spaces)) { |
| return false; |
| } |
| for (uint32_t space : spaces) { |
| if (index == space) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool Tpm2Impl::IsNvramLocked(uint32_t index) { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| uint32_t size; |
| bool is_read_locked; |
| bool is_write_locked; |
| if (!tpm_manager_utility_->GetSpaceInfo(index, &size, &is_read_locked, |
| &is_write_locked, |
| /*attributes=*/nullptr)) { |
| return false; |
| } |
| return is_write_locked; |
| } |
| |
| bool Tpm2Impl::WriteLockNvram(uint32_t index) { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| return tpm_manager_utility_->LockSpace(index); |
| } |
| |
| unsigned int Tpm2Impl::GetNvramSize(uint32_t index) { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| uint32_t size; |
| bool is_read_locked; |
| bool is_write_locked; |
| if (!tpm_manager_utility_->GetSpaceInfo(index, &size, &is_read_locked, |
| &is_write_locked, |
| /*attributes=*/nullptr)) { |
| return 0; |
| } |
| return size; |
| } |
| |
| bool Tpm2Impl::CreateDelegate(const std::set<uint32_t>& bound_pcrs, |
| uint8_t delegate_family_label, |
| uint8_t delegate_label, |
| Blob* delegate_blob, |
| Blob* delegate_secret) { |
| LOG(ERROR) << __func__ << ": Not implemented."; |
| return false; |
| } |
| |
| bool Tpm2Impl::Sign(const SecureBlob& key_blob, |
| const SecureBlob& input, |
| uint32_t bound_pcr_index, |
| SecureBlob* signature) { |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return false; |
| } |
| trunks::AuthorizationDelegate* delegate; |
| std::unique_ptr<trunks::PolicySession> policy_session; |
| std::unique_ptr<trunks::HmacSession> hmac_session; |
| if (bound_pcr_index != kNotBoundToPCR) { |
| policy_session = trunks->factory->GetPolicySession(); |
| if (StatusChain<TPMErrorBase> err = |
| HANDLE_TPM_COMM_ERROR(CreateError<TPM2Error>( |
| policy_session->StartUnboundSession(true, false)))) { |
| LOG(ERROR) << "Error starting policy session: " << err; |
| return false; |
| } |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(policy_session->PolicyPCR( |
| std::map<uint32_t, std::string>({{bound_pcr_index, ""}}))))) { |
| LOG(ERROR) << "Error creating PCR policy: " << err; |
| return false; |
| } |
| delegate = policy_session->GetDelegate(); |
| } else { |
| hmac_session = trunks->factory->GetHmacSession(); |
| if (StatusChain<TPMErrorBase> err = |
| HANDLE_TPM_COMM_ERROR(CreateError<TPM2Error>( |
| hmac_session->StartUnboundSession(true, true)))) { |
| LOG(ERROR) << "Error starting hmac session: " << err; |
| return false; |
| } |
| hmac_session->SetEntityAuthorizationValue(""); |
| delegate = hmac_session->GetDelegate(); |
| } |
| |
| ScopedKeyHandle handle; |
| if (StatusChain<TPMErrorBase> err = LoadWrappedKey(key_blob, &handle)) { |
| LOG(ERROR) << "Error loading pcr bound key: " << err; |
| return false; |
| } |
| std::string tpm_signature; |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->Sign( |
| handle.value(), trunks::TPM_ALG_RSASSA, trunks::TPM_ALG_SHA256, |
| input.to_string(), true /* generate_hash */, delegate, |
| &tpm_signature)))) { |
| LOG(ERROR) << "Error signing: " << err; |
| return false; |
| } |
| signature->assign(tpm_signature.begin(), tpm_signature.end()); |
| return true; |
| } |
| |
| bool Tpm2Impl::CreatePCRBoundKey( |
| const std::map<uint32_t, brillo::Blob>& pcr_map, |
| AsymmetricKeyUsage key_type, |
| SecureBlob* key_blob, |
| SecureBlob* public_key_der, |
| SecureBlob* creation_blob) { |
| CHECK(key_blob) << "No key blob argument provided."; |
| CHECK(creation_blob) << "No creation blob argument provided."; |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return false; |
| } |
| std::string policy_digest; |
| std::map<uint32_t, std::string> str_pcr_map = ToStrPcrMap(pcr_map); |
| if (StatusChain<TPMErrorBase> err = |
| HANDLE_TPM_COMM_ERROR(CreateError<TPM2Error>( |
| trunks->tpm_utility->GetPolicyDigestForPcrValues( |
| str_pcr_map, false /* use_auth_value */, &policy_digest)))) { |
| LOG(ERROR) << "Error getting policy digest: " << err; |
| return false; |
| } |
| std::vector<uint32_t> pcr_list; |
| for (const auto& map_pair : pcr_map) { |
| pcr_list.push_back(map_pair.first); |
| } |
| std::string tpm_key_blob; |
| std::string tpm_creation_blob; |
| std::unique_ptr<trunks::AuthorizationDelegate> delegate = |
| trunks->factory->GetPasswordAuthorization(""); |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->CreateRSAKeyPair( |
| ConvertAsymmetricKeyUsage(key_type), kDefaultTpmRsaModulusSize, |
| kDefaultTpmPublicExponent, |
| "", // No authorization |
| policy_digest, |
| true, // use_only_policy_authorization |
| pcr_list, delegate.get(), &tpm_key_blob, |
| &tpm_creation_blob /* No creation_blob */)))) { |
| LOG(ERROR) << "Error creating a pcr bound key: " << err; |
| return false; |
| } |
| key_blob->assign(tpm_key_blob.begin(), tpm_key_blob.end()); |
| creation_blob->assign(tpm_creation_blob.begin(), tpm_creation_blob.end()); |
| |
| // if |public_key_der| is present, create and assign it. |
| if (public_key_der) { |
| trunks::TPM2B_PUBLIC public_data; |
| trunks::TPM2B_PRIVATE private_data; |
| if (!trunks->factory->GetBlobParser()->ParseKeyBlob( |
| key_blob->to_string(), &public_data, &private_data)) { |
| return false; |
| } |
| if (!PublicAreaToPublicKeyDER(public_data.public_area, public_key_der)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool Tpm2Impl::VerifyPCRBoundKey( |
| const std::map<uint32_t, brillo::Blob>& pcr_map, |
| const SecureBlob& key_blob, |
| const SecureBlob& creation_blob) { |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return false; |
| } |
| // First we verify that the PCR were in a known good state at the time of |
| // Key creation. |
| trunks::TPM2B_CREATION_DATA creation_data; |
| trunks::TPM2B_DIGEST creation_hash; |
| trunks::TPMT_TK_CREATION creation_ticket; |
| if (!trunks->factory->GetBlobParser()->ParseCreationBlob( |
| creation_blob.to_string(), &creation_data, &creation_hash, |
| &creation_ticket)) { |
| LOG(ERROR) << "Error parsing creation_blob."; |
| return false; |
| } |
| trunks::TPML_PCR_SELECTION& pcr_select = |
| creation_data.creation_data.pcr_select; |
| if (pcr_select.count != 1) { |
| LOG(ERROR) << "Creation data missing creation PCR value."; |
| return false; |
| } |
| if (pcr_select.pcr_selections[0].hash != trunks::TPM_ALG_SHA256) { |
| LOG(ERROR) << "Creation PCR extended with wrong hash algorithm."; |
| return false; |
| } |
| uint8_t* pcr_selections = pcr_select.pcr_selections[0].pcr_select; |
| if (pcr_map.size() != CountSetBits(pcr_selections, PCR_SELECT_MIN)) { |
| LOG(ERROR) << "Incorrect creation PCR specified."; |
| return false; |
| } |
| brillo::Blob concatenated_pcr_values; |
| for (const auto& map_pair : pcr_map) { |
| uint32_t pcr_index = map_pair.first; |
| const brillo::Blob& pcr_value = map_pair.second; |
| if (pcr_index >= 8 * PCR_SELECT_MIN || |
| (pcr_selections[pcr_index / 8] & (1 << (pcr_index % 8))) == 0) { |
| LOG(ERROR) << "Incorrect creation PCR specified."; |
| return false; |
| } |
| concatenated_pcr_values.insert(concatenated_pcr_values.end(), |
| pcr_value.begin(), pcr_value.end()); |
| } |
| Blob expected_pcr_digest = Sha256(concatenated_pcr_values); |
| if (creation_data.creation_data.pcr_digest.size != |
| expected_pcr_digest.size()) { |
| LOG(ERROR) << "Incorrect PCR digest size."; |
| return false; |
| } |
| if (memcmp(creation_data.creation_data.pcr_digest.buffer, |
| expected_pcr_digest.data(), expected_pcr_digest.size()) != 0) { |
| LOG(ERROR) << "Incorrect PCR digest value."; |
| return false; |
| } |
| // Then we certify that the key was created by the TPM. |
| ScopedKeyHandle scoped_handle; |
| if (StatusChain<TPMErrorBase> err = |
| LoadWrappedKey(key_blob, &scoped_handle)) { |
| LOG(ERROR) << "Failed to load wrapped key: " << err; |
| return false; |
| } |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->CertifyCreation( |
| scoped_handle.value(), creation_blob.to_string())))) { |
| LOG(ERROR) << "Error certifying that key was created by TPM: " << err; |
| return false; |
| } |
| // Finally we verify that the key's policy_digest is the expected value. |
| std::unique_ptr<trunks::PolicySession> trial_session = |
| trunks->factory->GetTrialSession(); |
| if (StatusChain<TPMErrorBase> err = |
| HANDLE_TPM_COMM_ERROR(CreateError<TPM2Error>( |
| trial_session->StartUnboundSession(true, true)))) { |
| LOG(ERROR) << "Error starting a trial session: " << err; |
| return false; |
| } |
| std::map<uint32_t, std::string> str_pcr_map = ToStrPcrMap(pcr_map); |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trial_session->PolicyPCR(str_pcr_map)))) { |
| LOG(ERROR) << "Error restricting trial policy to pcr value: " << err; |
| return false; |
| } |
| std::string policy_digest; |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trial_session->GetDigest(&policy_digest)))) { |
| LOG(ERROR) << "Error getting policy digest: " << err; |
| return false; |
| } |
| trunks::TPMT_PUBLIC public_area; |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->GetKeyPublicArea( |
| scoped_handle.value(), &public_area)))) { |
| LOG(ERROR) << "Error getting key public area: " << err; |
| return false; |
| } |
| if (public_area.auth_policy.size != policy_digest.size()) { |
| LOG(ERROR) << "Key auth policy and policy digest are of different length." |
| << public_area.auth_policy.size << "," << policy_digest.size(); |
| return false; |
| } else if (memcmp(public_area.auth_policy.buffer, policy_digest.data(), |
| policy_digest.size()) != 0) { |
| LOG(ERROR) << "Key auth policy is different from policy digest."; |
| return false; |
| } else if (public_area.object_attributes & trunks::kUserWithAuth) { |
| LOG(ERROR) << "Key authorization is not restricted to policy."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool Tpm2Impl::ExtendPCR(uint32_t pcr_index, const Blob& extension) { |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return false; |
| } |
| std::unique_ptr<trunks::AuthorizationDelegate> delegate = |
| trunks->factory->GetPasswordAuthorization(""); |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->ExtendPCR( |
| pcr_index, BlobToString(extension), delegate.get())))) { |
| LOG(ERROR) << "Error extending PCR: " << err; |
| return false; |
| } |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->ExtendPCRForCSME( |
| pcr_index, BlobToString(extension))))) { |
| LOG(ERROR) << "Error extending PCR for CSME: " << err; |
| return false; |
| } |
| return true; |
| } |
| |
| bool Tpm2Impl::ReadPCR(uint32_t pcr_index, Blob* pcr_value) { |
| CHECK(pcr_value); |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return false; |
| } |
| std::string pcr_digest; |
| if (StatusChain<TPMErrorBase> err = |
| HANDLE_TPM_COMM_ERROR(CreateError<TPM2Error>( |
| trunks->tpm_utility->ReadPCR(pcr_index, &pcr_digest)))) { |
| LOG(ERROR) << "Error reading from PCR: " << err; |
| return false; |
| } |
| *pcr_value = BlobFromString(pcr_digest); |
| return true; |
| } |
| |
| bool Tpm2Impl::WrapRsaKey(const SecureBlob& public_modulus, |
| const SecureBlob& prime_factor, |
| SecureBlob* wrapped_key) { |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return false; |
| } |
| std::string key_blob; |
| std::unique_ptr<trunks::AuthorizationDelegate> delegate = |
| trunks->factory->GetPasswordAuthorization(""); |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->ImportRSAKey( |
| trunks::TpmUtility::AsymmetricKeyUsage::kDecryptKey, |
| public_modulus.to_string(), kDefaultTpmPublicExponent, |
| prime_factor.to_string(), |
| "", // No authorization, |
| delegate.get(), &key_blob)))) { |
| LOG(ERROR) << "Error creating SRK wrapped key: " << err; |
| return false; |
| } |
| wrapped_key->assign(key_blob.begin(), key_blob.end()); |
| return true; |
| } |
| |
| bool Tpm2Impl::CreateWrappedEccKey(SecureBlob* wrapped_key) { |
| CHECK(wrapped_key) << "No key blob argument provided."; |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return false; |
| } |
| std::vector<uint32_t> pcr_list; |
| std::string tpm_key_blob; |
| std::string tpm_creation_blob; |
| std::unique_ptr<trunks::AuthorizationDelegate> delegate = |
| trunks->factory->GetPasswordAuthorization(""); |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->CreateECCKeyPair( |
| trunks::TpmUtility::kDecryptKey, kDefaultTpmCurveId, |
| "", // No authorization |
| "", // No policy digest |
| false, // use_only_policy_authorization |
| pcr_list, delegate.get(), &tpm_key_blob, |
| &tpm_creation_blob /* No creation_blob */)))) { |
| LOG(ERROR) << "Error creating a pcr bound key: " << err; |
| return false; |
| } |
| wrapped_key->assign(tpm_key_blob.begin(), tpm_key_blob.end()); |
| |
| return true; |
| } |
| |
| StatusChain<TPMErrorBase> Tpm2Impl::LoadWrappedKey( |
| const SecureBlob& wrapped_key, ScopedKeyHandle* key_handle) { |
| CHECK(key_handle); |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return CreateError<TPMError>("Failed to get trunks context", |
| TPMRetryAction::kNoRetry); |
| } |
| trunks::TPM_HANDLE handle; |
| std::unique_ptr<trunks::AuthorizationDelegate> delegate = |
| trunks->factory->GetPasswordAuthorization(""); |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->LoadKey( |
| wrapped_key.to_string(), delegate.get(), &handle)))) { |
| return WrapError<TPMError>(std::move(err), "Error loading SRK wrapped key"); |
| } |
| key_handle->reset(this, handle); |
| return nullptr; |
| } |
| |
| bool Tpm2Impl::LegacyLoadCryptohomeKey(ScopedKeyHandle* key_handle, |
| SecureBlob* key_blob) { |
| // This doesn't apply to devices with TPM 2.0. |
| return false; |
| } |
| |
| void Tpm2Impl::CloseHandle(TpmKeyHandle key_handle) { |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return; |
| } |
| trunks->factory->GetTpm()->FlushContext( |
| key_handle, nullptr, |
| base::BindRepeating( |
| [](TpmKeyHandle key_handle, trunks::TPM_RC result) { |
| if (StatusChain<TPMErrorBase> err = |
| CreateError<TPM2Error>(result)) { |
| LOG(WARNING) << "Error flushing tpm handle " << key_handle << ": " |
| << err; |
| } |
| }, |
| key_handle)); |
| } |
| |
| StatusChain<TPMErrorBase> Tpm2Impl::EncryptBlob(TpmKeyHandle key_handle, |
| const SecureBlob& plaintext, |
| const SecureBlob& key, |
| SecureBlob* ciphertext) { |
| CHECK(ciphertext); |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return CreateError<TPMError>("Failed to get trunks context", |
| TPMRetryAction::kNoRetry); |
| } |
| std::string tpm_ciphertext; |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->AsymmetricEncrypt( |
| key_handle, trunks::TPM_ALG_OAEP, trunks::TPM_ALG_SHA256, |
| plaintext.to_string(), nullptr, &tpm_ciphertext)))) { |
| return WrapError<TPMError>(std::move(err), "Error encrypting plaintext"); |
| } |
| if (!ObscureRsaMessage(SecureBlob(tpm_ciphertext), key, ciphertext)) { |
| return CreateError<TPMError>("Error obscuring tpm encrypted blob", |
| TPMRetryAction::kNoRetry); |
| } |
| return nullptr; |
| } |
| |
| StatusChain<TPMErrorBase> Tpm2Impl::DecryptBlob( |
| TpmKeyHandle key_handle, |
| const SecureBlob& ciphertext, |
| const SecureBlob& key, |
| const std::map<uint32_t, brillo::Blob>& pcr_map, |
| SecureBlob* plaintext) { |
| CHECK(plaintext); |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return CreateError<TPMError>("Failed to get trunks context", |
| TPMRetryAction::kNoRetry); |
| } |
| SecureBlob local_data; |
| if (!UnobscureRsaMessage(ciphertext, key, &local_data)) { |
| return CreateError<TPMError>("Error unobscureing message", |
| TPMRetryAction::kNoRetry); |
| } |
| trunks::AuthorizationDelegate* delegate; |
| std::unique_ptr<trunks::PolicySession> policy_session; |
| std::unique_ptr<trunks::AuthorizationDelegate> default_delegate; |
| if (!pcr_map.empty()) { |
| std::map<uint32_t, std::string> str_pcr_map = ToStrPcrMap(pcr_map); |
| policy_session = trunks->factory->GetPolicySession(); |
| if (StatusChain<TPMErrorBase> err = |
| HANDLE_TPM_COMM_ERROR(CreateError<TPM2Error>( |
| policy_session->StartUnboundSession(true, true)))) { |
| return WrapError<TPMError>(std::move(err), |
| "Error starting policy session", |
| TPMRetryAction::kNoRetry); |
| } |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(policy_session->PolicyPCR(str_pcr_map)))) { |
| return WrapError<TPMError>(std::move(err), "Error creating PCR policy", |
| TPMRetryAction::kNoRetry); |
| } |
| delegate = policy_session->GetDelegate(); |
| } else { |
| default_delegate = trunks->factory->GetPasswordAuthorization(""); |
| delegate = default_delegate.get(); |
| } |
| |
| std::string tpm_plaintext; |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->AsymmetricDecrypt( |
| key_handle, trunks::TPM_ALG_OAEP, trunks::TPM_ALG_SHA256, |
| local_data.to_string(), delegate, &tpm_plaintext)))) { |
| return WrapError<TPMError>(std::move(err), "Error decrypting plaintext"); |
| } |
| plaintext->assign(tpm_plaintext.begin(), tpm_plaintext.end()); |
| return nullptr; |
| } |
| |
| StatusChain<TPMErrorBase> Tpm2Impl::SealToPcrWithAuthorization( |
| const SecureBlob& plaintext, |
| const SecureBlob& auth_value, |
| const std::map<uint32_t, brillo::Blob>& pcr_map, |
| SecureBlob* sealed_data) { |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return CreateError<TPMError>("Failed to get trunks context", |
| TPMRetryAction::kNoRetry); |
| } |
| |
| std::map<uint32_t, std::string> str_pcr_map = ToStrPcrMap(pcr_map); |
| |
| // Get the policy digest for PCR. |
| std::string policy_digest; |
| if (StatusChain<TPMErrorBase> err = |
| HANDLE_TPM_COMM_ERROR(CreateError<TPM2Error>( |
| trunks->tpm_utility->GetPolicyDigestForPcrValues( |
| str_pcr_map, true /* use_auth_value */, &policy_digest)))) { |
| return WrapError<TPMError>(std::move(err), "Error getting policy digest"); |
| } |
| |
| std::unique_ptr<trunks::HmacSession> session = |
| trunks->factory->GetHmacSession(); |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(session->StartUnboundSession(true, true)))) { |
| return WrapError<TPMError>(std::move(err), "Error starting hmac session"); |
| } |
| |
| std::string sealed_str; |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->SealData( |
| plaintext.to_string(), policy_digest, auth_value.to_string(), |
| /*require_admin_with_policy=*/true, session->GetDelegate(), |
| &sealed_str)))) { |
| return WrapError<TPMError>(std::move(err), |
| "Error sealing data to PCR with authorization"); |
| } |
| sealed_data->assign(sealed_str.begin(), sealed_str.end()); |
| |
| return nullptr; |
| } |
| |
| StatusChain<TPMErrorBase> Tpm2Impl::PreloadSealedData( |
| const brillo::SecureBlob& sealed_data, ScopedKeyHandle* preload_handle) { |
| if (StatusChain<TPMErrorBase> err = |
| LoadWrappedKey(sealed_data, preload_handle)) { |
| return WrapError<TPMError>(std::move(err), "Failed to load sealed data"); |
| } |
| return nullptr; |
| } |
| |
| StatusChain<TPMErrorBase> Tpm2Impl::UnsealWithAuthorization( |
| base::Optional<TpmKeyHandle> preload_handle, |
| const SecureBlob& sealed_data, |
| const SecureBlob& auth_value, |
| const std::map<uint32_t, brillo::Blob>& pcr_map, |
| SecureBlob* plaintext) { |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return CreateError<TPMError>("Failed to get trunks context", |
| TPMRetryAction::kNoRetry); |
| } |
| |
| std::unique_ptr<trunks::PolicySession> policy_session = |
| trunks->factory->GetPolicySession(); |
| // Use unsalted session here, to unseal faster. |
| if (StatusChain<TPMErrorBase> err = |
| HANDLE_TPM_COMM_ERROR(CreateError<TPM2Error>( |
| policy_session->StartUnboundSession(false, false)))) { |
| return WrapError<TPMError>(std::move(err), "Error starting policy session"); |
| } |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(policy_session->PolicyAuthValue()))) { |
| return WrapError<TPMError>(std::move(err), |
| "Error setting session to use auth_value"); |
| } |
| |
| std::map<uint32_t, std::string> str_pcr_map = ToStrPcrMap(pcr_map); |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(policy_session->PolicyPCR(str_pcr_map)))) { |
| return WrapError<TPMError>(std::move(err), "Error in PolicyPCR"); |
| } |
| policy_session->SetEntityAuthorizationValue(auth_value.to_string()); |
| std::string unsealed_data; |
| if (preload_handle) { |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->UnsealDataWithHandle( |
| *preload_handle, policy_session->GetDelegate(), |
| &unsealed_data)))) { |
| return WrapError<TPMError>(std::move(err), |
| "Error unsealing data with authorization"); |
| } |
| } else { |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->UnsealData( |
| sealed_data.to_string(), policy_session->GetDelegate(), |
| &unsealed_data)))) { |
| return WrapError<TPMError>(std::move(err), |
| "Error unsealing data with authorization"); |
| } |
| } |
| plaintext->assign(unsealed_data.begin(), unsealed_data.end()); |
| |
| return nullptr; |
| } |
| |
| StatusChain<TPMErrorBase> Tpm2Impl::GetPublicKeyHash(TpmKeyHandle key_handle, |
| SecureBlob* hash) { |
| CHECK(hash); |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return CreateError<TPMError>("Failed to get trunks context", |
| TPMRetryAction::kNoRetry); |
| } |
| trunks::TPMT_PUBLIC public_data; |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->GetKeyPublicArea( |
| key_handle, &public_data)))) { |
| return WrapError<TPMError>(std::move(err), "Error getting key public area"); |
| } |
| std::string public_modulus = |
| trunks::StringFrom_TPM2B_PUBLIC_KEY_RSA(public_data.unique.rsa); |
| *hash = Sha256(SecureBlob(public_modulus)); |
| return nullptr; |
| } |
| |
| void Tpm2Impl::GetStatus(base::Optional<TpmKeyHandle> key, |
| TpmStatusInfo* status) { |
| memset(status, 0, sizeof(TpmStatusInfo)); |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return; |
| } |
| status->this_instance_has_context = true; |
| status->this_instance_has_key_handle = key.has_value(); |
| status->last_tpm_error = trunks->tpm_state->Initialize(); |
| if (status->last_tpm_error != TPM_RC_SUCCESS) { |
| return; |
| } |
| status->can_connect = true; |
| trunks::TPMT_PUBLIC public_srk; |
| status->last_tpm_error = trunks->tpm_utility->GetKeyPublicArea( |
| trunks::kStorageRootKey, &public_srk); |
| if (status->last_tpm_error != TPM_RC_SUCCESS) { |
| return; |
| } |
| status->can_load_srk = true; |
| status->can_load_srk_public_key = true; |
| status->srk_vulnerable_roca = false; |
| |
| // Check the Cryptohome key by using what we have been told. |
| status->has_cryptohome_key = key.has_value(); |
| |
| if (status->has_cryptohome_key) { |
| // Check encryption (we don't care about the contents, just whether or not |
| // there was an error) |
| SecureBlob data(16); |
| SecureBlob password(16); |
| SecureBlob salt(8); |
| SecureBlob data_out(16); |
| memset(data.data(), 'A', data.size()); |
| memset(password.data(), 'B', password.size()); |
| memset(salt.data(), 'C', salt.size()); |
| memset(data_out.data(), 'D', data_out.size()); |
| SecureBlob aes_key; |
| PasskeyToAesKey(password, salt, 13, &aes_key, NULL); |
| if (StatusChain<TPMErrorBase> err = |
| EncryptBlob(key.value(), data, aes_key, &data_out)) { |
| LOG(ERROR) << __func__ << ": Failed to encrypt blob: " << err; |
| return; |
| } |
| status->can_encrypt = true; |
| |
| // Check decryption (we don't care about the contents, just whether or not |
| // there was an error) |
| if (StatusChain<TPMErrorBase> err = |
| DecryptBlob(key.value(), data_out, aes_key, |
| std::map<uint32_t, brillo::Blob>(), &data)) { |
| LOG(ERROR) << __func__ << ": Failed to decrypt blob: " << err; |
| return; |
| } |
| status->can_decrypt = true; |
| } |
| } |
| |
| StatusChain<hwsec::TPMErrorBase> Tpm2Impl::IsSrkRocaVulnerable(bool* result) { |
| // This doesn't apply to devices with TPM 2.0. |
| *result = false; |
| return nullptr; |
| } |
| |
| bool Tpm2Impl::GetDictionaryAttackInfo(int* counter, |
| int* threshold, |
| bool* lockout, |
| int* seconds_remaining) { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| return tpm_manager_utility_->GetDictionaryAttackInfo( |
| counter, threshold, lockout, seconds_remaining); |
| } |
| |
| bool Tpm2Impl::ResetDictionaryAttackMitigation( |
| const Blob& /* delegate_blob */, const Blob& /* delegate_secret */) { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| return tpm_manager_utility_->ResetDictionaryAttackLock(); |
| } |
| |
| void Tpm2Impl::DeclareTpmFirmwareStable() { |
| TrunksClientContext* trunks; |
| if (!fw_declared_stable_ && GetTrunksContext(&trunks)) { |
| StatusChain<TPMErrorBase> err = |
| HANDLE_TPM_COMM_ERROR(CreateError<TPM2Error>( |
| trunks->tpm_utility->DeclareTpmFirmwareStable())); |
| fw_declared_stable_ = (err == nullptr); |
| } |
| } |
| |
| bool Tpm2Impl::GetTrunksContext(TrunksClientContext** trunks) { |
| if (has_external_trunks_context_) { |
| *trunks = &external_trunks_context_; |
| return true; |
| } |
| base::PlatformThreadId thread_id = base::PlatformThread::CurrentId(); |
| std::map<base::PlatformThreadId, |
| std::unique_ptr<Tpm2Impl::TrunksClientContext>>::iterator iter; |
| { |
| base::AutoLock lock(trunks_contexts_lock_); |
| iter = trunks_contexts_.find(thread_id); |
| if (iter == trunks_contexts_.end()) { |
| auto result = trunks_contexts_.emplace(thread_id, nullptr); |
| iter = std::move(result.first); |
| } |
| } |
| |
| // Different elements in the same container can be modified concurrently by |
| // different threads, so we don't need to lock this block. |
| if (!iter->second) { |
| std::unique_ptr<TrunksClientContext> new_context(new TrunksClientContext); |
| new_context->factory_impl = std::make_unique<trunks::TrunksFactoryImpl>(); |
| if (!new_context->factory_impl->Initialize()) { |
| LOG(ERROR) << "Failed to initialize trunks factory."; |
| return false; |
| } |
| new_context->factory = new_context->factory_impl.get(); |
| new_context->tpm_state = new_context->factory->GetTpmState(); |
| new_context->tpm_utility = new_context->factory->GetTpmUtility(); |
| iter->second = std::move(new_context); |
| } |
| *trunks = iter->second.get(); |
| return true; |
| } |
| |
| bool Tpm2Impl::LoadPublicKeyFromSpki( |
| const Blob& public_key_spki_der, |
| AsymmetricKeyUsage key_type, |
| trunks::TPM_ALG_ID scheme, |
| trunks::TPM_ALG_ID hash_alg, |
| trunks::AuthorizationDelegate* session_delegate, |
| ScopedKeyHandle* key_handle) { |
| // Parse the SPKI. |
| const unsigned char* asn1_ptr = public_key_spki_der.data(); |
| const crypto::ScopedEVP_PKEY pkey( |
| d2i_PUBKEY(nullptr, &asn1_ptr, public_key_spki_der.size())); |
| if (!pkey) { |
| LOG(ERROR) << "Error parsing Subject Public Key Info DER"; |
| return false; |
| } |
| const crypto::ScopedRSA rsa(EVP_PKEY_get1_RSA(pkey.get())); |
| if (!rsa) { |
| LOG(ERROR) << "Error: non-RSA key was supplied"; |
| return false; |
| } |
| SecureBlob key_modulus(RSA_size(rsa.get())); |
| const BIGNUM* n; |
| const BIGNUM* e; |
| RSA_get0_key(rsa.get(), &n, &e, nullptr); |
| if (BN_bn2bin(n, key_modulus.data()) != key_modulus.size()) { |
| LOG(ERROR) << "Error extracting public key modulus"; |
| return false; |
| } |
| constexpr BN_ULONG kInvalidBnWord = ~static_cast<BN_ULONG>(0); |
| const BN_ULONG exponent_word = BN_get_word(e); |
| if (exponent_word == kInvalidBnWord || |
| !base::IsValueInRangeForNumericType<uint32_t>(exponent_word)) { |
| LOG(ERROR) << "Error extracting public key exponent"; |
| return false; |
| } |
| const uint32_t key_exponent = static_cast<uint32_t>(exponent_word); |
| // Load the key into the TPM. |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) |
| return false; |
| trunks::TPM_HANDLE key_handle_raw = 0; |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->LoadRSAPublicKey( |
| ConvertAsymmetricKeyUsage(key_type), scheme, hash_alg, |
| key_modulus.to_string(), key_exponent, session_delegate, |
| &key_handle_raw)))) { |
| LOG(ERROR) << "Error loading public key: " << err; |
| return false; |
| } |
| key_handle->reset(this, key_handle_raw); |
| return true; |
| } |
| |
| bool Tpm2Impl::PublicAreaToPublicKeyDER(const trunks::TPMT_PUBLIC& public_area, |
| brillo::SecureBlob* public_key_der) { |
| crypto::ScopedRSA rsa(RSA_new()); |
| crypto::ScopedBIGNUM e(BN_new()), n(BN_new()); |
| if (!rsa || !e || !n) { |
| LOG(ERROR) << "Failed to allocate RSA or BIGNUM for public key."; |
| return false; |
| } |
| if (!BN_set_word(e.get(), kDefaultTpmPublicExponent) || |
| !BN_bin2bn(public_area.unique.rsa.buffer, public_area.unique.rsa.size, |
| n.get()) || |
| !RSA_set0_key(rsa.get(), n.release(), e.release(), nullptr)) { |
| LOG(ERROR) << "Failed to set up RSA."; |
| return false; |
| } |
| int der_length = i2d_RSAPublicKey(rsa.get(), nullptr); |
| if (der_length < 0) { |
| LOG(ERROR) << "Failed to get DER-encoded public key length."; |
| return false; |
| } |
| public_key_der->resize(der_length); |
| unsigned char* der_buffer = public_key_der->data(); |
| der_length = i2d_RSAPublicKey(rsa.get(), &der_buffer); |
| if (der_length < 0) { |
| LOG(ERROR) << "Failed to DER-encode public key."; |
| return false; |
| } |
| return true; |
| } |
| |
| StatusChain<TPMErrorBase> Tpm2Impl::GetAuthValue( |
| base::Optional<TpmKeyHandle> key_handle, |
| const SecureBlob& pass_blob, |
| SecureBlob* auth_value) { |
| if (!key_handle) { |
| LOG(DFATAL) << "TPM2.0 needs a key_handle to get auth value."; |
| return CreateError<TPMError>("TPM2.0 needs a key_handle to get auth value", |
| TPMRetryAction::kNoRetry); |
| } |
| if (pass_blob.size() != kDefaultTpmRsaModulusSize / 8) { |
| return CreateError<TPMError>( |
| "Unexpected pass_blob size: " + std::to_string(pass_blob.size()), |
| TPMRetryAction::kNoRetry); |
| } |
| |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return CreateError<TPMError>("Failed to get trunks context", |
| TPMRetryAction::kNoRetry); |
| } |
| |
| // To guarantee that pass_blob is lower that public key modulus, just set the |
| // first byte to 0. |
| std::string value_to_decrypt = pass_blob.to_string(); |
| value_to_decrypt[0] = 0; |
| std::string decrypted_value; |
| std::unique_ptr<trunks::AuthorizationDelegate> delegate = |
| trunks->factory->GetPasswordAuthorization(""); |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->AsymmetricDecrypt( |
| key_handle.value(), trunks::TPM_ALG_NULL, trunks::TPM_ALG_NULL, |
| value_to_decrypt, delegate.get(), &decrypted_value)))) { |
| return WrapError<TPMError>(std::move(err), "Error decrypting pass_blob"); |
| } |
| *auth_value = Sha256(SecureBlob(decrypted_value)); |
| |
| return nullptr; |
| } |
| |
| StatusChain<TPMErrorBase> Tpm2Impl::GetEccAuthValue( |
| base::Optional<TpmKeyHandle> key_handle, |
| const SecureBlob& pass_blob, |
| SecureBlob* auth_value) { |
| if (!key_handle) { |
| LOG(DFATAL) << "TPM2.0 needs a key_handle to get ECC auth value."; |
| return CreateError<TPMError>( |
| "TPM2.0 needs a key_handle to get ECC auth value", |
| TPMRetryAction::kNoRetry); |
| } |
| |
| if (pass_blob.size() < kMinPassBlobSize) { |
| return CreateError<TPMError>( |
| "Unexpected pass_blob size: " + std::to_string(pass_blob.size()), |
| TPMRetryAction::kNoRetry); |
| } |
| |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return CreateError<TPMError>("Failed to get trunks context", |
| TPMRetryAction::kCommunication); |
| } |
| |
| trunks::TPMS_ECC_POINT ecc_point; |
| if (StatusChain<TPMErrorBase> err = |
| DeriveTpmEccPointFromSeed(pass_blob, &ecc_point)) { |
| return WrapError<TPMError>(std::move(err), |
| "Failed to derive TPM ECC point from "); |
| } |
| |
| trunks::TPM2B_ECC_POINT in_point = trunks::Make_TPM2B_ECC_POINT(ecc_point); |
| trunks::TPM2B_ECC_POINT z_point; |
| |
| std::unique_ptr<trunks::AuthorizationDelegate> delegate = |
| trunks->factory->GetPasswordAuthorization(""); |
| |
| if (StatusChain<TPMErrorBase> err = HANDLE_TPM_COMM_ERROR( |
| CreateError<TPM2Error>(trunks->tpm_utility->ECDHZGen( |
| key_handle.value(), in_point, delegate.get(), &z_point)))) { |
| return WrapError<TPMError>(std::move(err), "Error doing ECDH ZGen"); |
| } |
| |
| *auth_value = |
| Sha256(SecureBlob(StringFrom_TPM2B_ECC_PARAMETER(z_point.point.x))); |
| |
| return nullptr; |
| } |
| |
| bool Tpm2Impl::UpdateTpmStatus(RefreshType refresh_type) { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| |
| bool is_successful = false; |
| bool has_received = false; |
| |
| // Repeats data copy into |last_tpm_manager_data_|; reasonable trade-off due |
| // to low ROI to avoid that. |
| const bool is_connected = tpm_manager_utility_->GetOwnershipTakenSignalStatus( |
| &is_successful, &has_received, &last_tpm_manager_data_); |
| |
| // When we need explicitly query tpm status either because the signal is not |
| // ready for any reason, or because the signal is not received yet so we need |
| // to run it once in case the signal is sent by tpm_manager before already. |
| if (refresh_type == RefreshType::FORCE_REFRESH || !is_connected || |
| !is_successful || (!has_received && shall_cache_tpm_manager_status_)) { |
| // Retains |shall_cache_tpm_manager_status_| to be |true| if the signal |
| // cannot be relied on (yet). Actually |!is_successful| suffices to update |
| // |shall_cache_tpm_manager_status_|; by design, uses the redundancy just to |
| // avoid confusion. |
| shall_cache_tpm_manager_status_ &= (!is_connected || !is_successful); |
| return CacheTpmManagerStatus(); |
| } else if (has_received) { |
| is_enabled_ = true; |
| is_owned_ = true; |
| } |
| return true; |
| } |
| |
| bool Tpm2Impl::RemoveOwnerDependency(Tpm::TpmOwnerDependency dependency) { |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": Failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| return tpm_manager_utility_->RemoveOwnerDependency( |
| OwnerDependencyEnumClassToString(dependency)); |
| } |
| |
| bool Tpm2Impl::GetVersionInfo(TpmVersionInfo* version_info) { |
| if (!version_info) { |
| LOG(ERROR) << __func__ << "version_info is not initialized."; |
| return false; |
| } |
| |
| // Version info on a device never changes. Returns from cache directly if we |
| // have the cache. |
| if (version_info_) { |
| *version_info = *version_info_; |
| return true; |
| } |
| |
| if (!InitializeTpmManagerUtility()) { |
| LOG(ERROR) << __func__ << ": failed to initialize |TpmManagerUtility|."; |
| return false; |
| } |
| |
| if (!tpm_manager_utility_->GetVersionInfo( |
| &version_info->family, &version_info->spec_level, |
| &version_info->manufacturer, &version_info->tpm_model, |
| &version_info->firmware_version, &version_info->vendor_specific)) { |
| LOG(ERROR) << __func__ << ": failed to get version info from tpm_manager."; |
| return false; |
| } |
| |
| version_info_ = *version_info; |
| return true; |
| } |
| |
| bool Tpm2Impl::GetIFXFieldUpgradeInfo(IFXFieldUpgradeInfo* info) { |
| return false; |
| } |
| |
| bool Tpm2Impl::GetRsuDeviceId(std::string* device_id) { |
| TrunksClientContext* trunks; |
| if (!GetTrunksContext(&trunks)) { |
| return false; |
| } |
| return trunks->tpm_utility->GetRsuDeviceId(device_id) == TPM_RC_SUCCESS; |
| } |
| |
| LECredentialBackend* Tpm2Impl::GetLECredentialBackend() { |
| #if USE_PINWEAVER |
| return &le_credential_backend_; |
| #else |
| return nullptr; |
| #endif |
| } |
| |
| SignatureSealingBackend* Tpm2Impl::GetSignatureSealingBackend() { |
| return &signature_sealing_backend_; |
| } |
| |
| cryptorecovery::RecoveryCryptoTpmBackend* Tpm2Impl::GetRecoveryCryptoBackend() { |
| return &recovery_crypto_backend_; |
| } |
| |
| bool Tpm2Impl::GetDelegate(brillo::Blob* /*blob*/, |
| brillo::Blob* /*secret*/, |
| bool* has_reset_lock_permissions) { |
| LOG(WARNING) << __func__ << ": No-ops to |blob| and |secret|."; |
| *has_reset_lock_permissions = true; |
| return true; |
| } |
| |
| StatusChain<TPMErrorBase> Tpm2Impl::IsDelegateBoundToPcr(bool* result) { |
| *result = false; |
| return nullptr; |
| } |
| |
| bool Tpm2Impl::DelegateCanResetDACounter() { |
| return true; |
| } |
| |
| std::map<uint32_t, brillo::Blob> Tpm2Impl::GetPcrMap( |
| const std::string& obfuscated_username, bool use_extended_pcr) const { |
| std::map<uint32_t, brillo::Blob> pcr_map; |
| if (use_extended_pcr) { |
| brillo::Blob starting_value(SHA256_DIGEST_LENGTH, 0); |
| brillo::Blob digest_value = Sha256(brillo::CombineBlobs( |
| {starting_value, Sha256(brillo::BlobFromString(obfuscated_username))})); |
| pcr_map[kTpmSingleUserPCR] = digest_value; |
| } else { |
| pcr_map[kTpmSingleUserPCR] = brillo::Blob(SHA256_DIGEST_LENGTH, 0); |
| } |
| |
| return pcr_map; |
| } |
| |
| } // namespace cryptohome |