| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/ash/components/tpm/tpm_token_info_getter.h" |
| |
| #include <stdint.h> |
| |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/task/task_runner.h" |
| #include "chromeos/ash/components/cryptohome/cryptohome_parameters.h" |
| #include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h" |
| #include "chromeos/ash/components/tpm/buildflags.h" |
| #include "chromeos/dbus/tpm_manager/tpm_manager.pb.h" |
| #include "chromeos/dbus/tpm_manager/tpm_manager_client.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| const int64_t kInitialRequestDelayMs = 100; |
| const int64_t kMaxRequestDelayMs = 300000; // 5 minutes |
| |
| #if BUILDFLAG(NSS_SLOTS_SOFTWARE_FALLBACK) |
| constexpr bool kIsSystemSlotSoftwareFallbackAllowed = true; |
| #else |
| constexpr bool kIsSystemSlotSoftwareFallbackAllowed = false; |
| #endif |
| |
| // Calculates the delay before running next attempt to initiatialize the TPM |
| // token, if |last_delay| was the last or initial delay. |
| base::TimeDelta GetNextRequestDelayMs(base::TimeDelta last_delay) { |
| // This implements an exponential backoff, as we don't know in which order of |
| // magnitude the TPM token changes it's state. |
| base::TimeDelta next_delay = last_delay * 2; |
| |
| // Cap the delay to prevent an overflow. This threshold is arbitrarily chosen. |
| const base::TimeDelta max_delay = base::Milliseconds(kMaxRequestDelayMs); |
| if (next_delay > max_delay) |
| next_delay = max_delay; |
| return next_delay; |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<TPMTokenInfoGetter> TPMTokenInfoGetter::CreateForUserToken( |
| const AccountId& account_id, |
| CryptohomePkcs11Client* userdataauth_client, |
| const scoped_refptr<base::TaskRunner>& delayed_task_runner) { |
| CHECK(account_id.is_valid()); |
| return base::WrapUnique(new TPMTokenInfoGetter( |
| TYPE_USER, account_id, userdataauth_client, delayed_task_runner)); |
| } |
| |
| // static |
| std::unique_ptr<TPMTokenInfoGetter> TPMTokenInfoGetter::CreateForSystemToken( |
| CryptohomePkcs11Client* userdataauth_client, |
| const scoped_refptr<base::TaskRunner>& delayed_task_runner) { |
| return base::WrapUnique(new TPMTokenInfoGetter( |
| TYPE_SYSTEM, EmptyAccountId(), userdataauth_client, delayed_task_runner)); |
| } |
| |
| TPMTokenInfoGetter::~TPMTokenInfoGetter() = default; |
| |
| void TPMTokenInfoGetter::Start(TpmTokenInfoCallback callback) { |
| CHECK(state_ == STATE_INITIAL); |
| CHECK(!callback.is_null()); |
| |
| callback_ = std::move(callback); |
| |
| state_ = STATE_STARTED; |
| Continue(); |
| } |
| |
| TPMTokenInfoGetter::TPMTokenInfoGetter( |
| TPMTokenInfoGetter::Type type, |
| const AccountId& account_id, |
| CryptohomePkcs11Client* cryptohome_pkcs11_client, |
| const scoped_refptr<base::TaskRunner>& delayed_task_runner) |
| : delayed_task_runner_(delayed_task_runner), |
| type_(type), |
| state_(TPMTokenInfoGetter::STATE_INITIAL), |
| account_id_(account_id), |
| use_nss_slots_software_fallback_(kIsSystemSlotSoftwareFallbackAllowed), |
| tpm_request_delay_(base::Milliseconds(kInitialRequestDelayMs)), |
| cryptohome_pkcs11_client_(cryptohome_pkcs11_client) {} |
| |
| void TPMTokenInfoGetter::Continue() { |
| user_data_auth::Pkcs11GetTpmTokenInfoRequest request; |
| switch (state_) { |
| case STATE_INITIAL: |
| NOTREACHED_IN_MIGRATION(); |
| break; |
| case STATE_STARTED: |
| chromeos::TpmManagerClient::Get()->GetTpmNonsensitiveStatus( |
| ::tpm_manager::GetTpmNonsensitiveStatusRequest(), |
| base::BindOnce(&TPMTokenInfoGetter::OnGetTpmStatus, |
| weak_factory_.GetWeakPtr())); |
| break; |
| case STATE_TPM_ENABLED: |
| case STATE_NSS_SLOTS_SOFTWARE_FALLBACK: |
| // For system token, we don't need to supply the username, and with an |
| // empty username, cryptohomed will return the system token information. |
| if (type_ == TYPE_USER) { |
| request.set_username( |
| cryptohome::CreateAccountIdentifierFromAccountId(account_id_) |
| .account_id()); |
| } |
| cryptohome_pkcs11_client_->Pkcs11GetTpmTokenInfo( |
| request, base::BindOnce(&TPMTokenInfoGetter::OnPkcs11GetTpmTokenInfo, |
| weak_factory_.GetWeakPtr())); |
| break; |
| case STATE_DONE: |
| NOTREACHED_IN_MIGRATION(); |
| } |
| } |
| |
| void TPMTokenInfoGetter::RetryLater() { |
| delayed_task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&TPMTokenInfoGetter::Continue, weak_factory_.GetWeakPtr()), |
| tpm_request_delay_); |
| tpm_request_delay_ = GetNextRequestDelayMs(tpm_request_delay_); |
| } |
| |
| void TPMTokenInfoGetter::OnGetTpmStatus( |
| const ::tpm_manager::GetTpmNonsensitiveStatusReply& reply) { |
| if (reply.status() != ::tpm_manager::STATUS_SUCCESS) { |
| LOG(WARNING) << "Failed to get tpm status; status: " << reply.status(); |
| RetryLater(); |
| return; |
| } |
| |
| // In case the use_nss_slots_software_fallback_ is true and the TPM is not |
| // owned, we continue the token info retrieval for the nss slots in order to |
| // fall back to a software-backed initialization. |
| if (use_nss_slots_software_fallback_ && !reply.is_owned()) { |
| state_ = STATE_NSS_SLOTS_SOFTWARE_FALLBACK; |
| Continue(); |
| return; |
| } |
| |
| if (!reply.is_enabled()) { |
| state_ = STATE_DONE; |
| std::move(callback_).Run(std::nullopt); |
| return; |
| } |
| |
| state_ = STATE_TPM_ENABLED; |
| Continue(); |
| } |
| |
| void TPMTokenInfoGetter::OnPkcs11GetTpmTokenInfo( |
| std::optional<user_data_auth::Pkcs11GetTpmTokenInfoReply> token_info) { |
| if (!token_info.has_value() || !token_info->has_token_info() || |
| token_info->token_info().slot() == -1) { |
| RetryLater(); |
| return; |
| } |
| |
| state_ = STATE_DONE; |
| std::move(callback_).Run(token_info->token_info()); |
| } |
| |
| } // namespace ash |