chromium/chromeos/ash/components/tpm/tpm_token_info_getter.cc

// 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