// 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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chromeos/ash/components/tpm/tpm_token_info_getter.h"
#include <stdint.h>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_runner.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/userdataauth/cryptohome_pkcs11_client.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_cryptohome_pkcs11_client.h"
#include "chromeos/dbus/tpm_manager/tpm_manager_client.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash {
namespace {
using ::user_data_auth::TpmTokenInfo;
// On invocation, set |called| to true, and store the result |token_info|
// to the |result|.
void OnTpmTokenInfoGetterCompleted(bool* called,
std::optional<TpmTokenInfo>* result,
std::optional<TpmTokenInfo> token_info) {
DCHECK(called);
DCHECK(result);
*called = true;
*result = std::move(token_info);
}
// Task runner for handling delayed tasks posted by TPMTokenInfoGetter when
// retrying failed cryptohome method calls. It just records the requested
// delay and immediately runs the task. The task is run asynchronously to be
// closer to what's actually happening in production.
// The delays used by TPMTokenGetter should be monotonically increasing, so
// the fake task runner does not handle task reordering based on the delays.
class FakeTaskRunner : public base::TaskRunner {
public:
// |delays|: Vector to which the dalays seen by the task runner are saved.
explicit FakeTaskRunner(std::vector<int64_t>* delays) : delays_(delays) {}
FakeTaskRunner(const FakeTaskRunner&) = delete;
FakeTaskRunner& operator=(const FakeTaskRunner&) = delete;
// base::TaskRunner overrides:
bool PostDelayedTask(const base::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay) override {
delays_->push_back(delay.InMilliseconds());
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
from_here, std::move(task));
return true;
}
protected:
~FakeTaskRunner() override = default;
private:
// The vector of delays.
raw_ptr<std::vector<int64_t>> delays_;
};
// Implementation of CryptohomePkcs11Client used in these tests.
// TestCryptohomePkcs11Client overrides all CryptohomePkcs11Client methods used
// in TPMTokenInfoGetter tests.
class TestCryptohomePkcs11Client : public FakeCryptohomePkcs11Client {
public:
// |account_id|: The user associated with the TPMTokenInfoGetter that will be
// using the TestCryptohomePkcs11Client. Should be empty for system token.
explicit TestCryptohomePkcs11Client(const AccountId& account_id)
: account_id_(account_id),
get_tpm_token_info_failure_count_(0),
get_tpm_token_info_not_set_count_(0),
get_tpm_token_info_succeeded_(false) {}
TestCryptohomePkcs11Client(const TestCryptohomePkcs11Client&) = delete;
TestCryptohomePkcs11Client& operator=(const TestCryptohomePkcs11Client&) =
delete;
~TestCryptohomePkcs11Client() override = default;
void set_get_tpm_token_info_failure_count(int value) {
ASSERT_GT(value, 0);
get_tpm_token_info_failure_count_ = value;
}
void set_get_tpm_token_info_not_set_count(int value) {
ASSERT_GT(value, 0);
get_tpm_token_info_not_set_count_ = value;
}
// Sets the tpm tpken info to be reported by the test CryptohomePkcs11Client.
// If there is |Pkcs11GetTpmTokenInfo| in progress, runs the pending
// callback with the set tpm token info.
void SetTpmTokenInfo(const TpmTokenInfo& token_info) {
tpm_token_info_ = token_info;
ASSERT_NE(-1, tpm_token_info_->slot());
InvokeGetTpmTokenInfoCallbackIfReady();
}
private:
// FakeCryptohomePkcs11Client override.
void Pkcs11GetTpmTokenInfo(
const ::user_data_auth::Pkcs11GetTpmTokenInfoRequest& request,
Pkcs11GetTpmTokenInfoCallback callback) override {
if (request.username().empty()) {
ASSERT_TRUE(account_id_.empty());
} else {
ASSERT_EQ(cryptohome::CreateAccountIdentifierFromAccountId(account_id_)
.account_id(),
request.username());
}
HandleGetTpmTokenInfo(std::move(callback));
}
// Handles Pkcs11GetTpmTokenInfo calls (both for system and user token). The
// CryptohomePkcs11Client method overrides should make sure that |account_id_|
// is properly set before calling this.
void HandleGetTpmTokenInfo(
chromeos::DBusMethodCallback<::user_data_auth::Pkcs11GetTpmTokenInfoReply>
callback) {
ASSERT_FALSE(get_tpm_token_info_succeeded_);
ASSERT_TRUE(pending_get_tpm_token_info_callback_.is_null());
if (get_tpm_token_info_failure_count_ > 0) {
--get_tpm_token_info_failure_count_;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::nullopt));
return;
}
if (get_tpm_token_info_not_set_count_ > 0) {
--get_tpm_token_info_not_set_count_;
::user_data_auth::Pkcs11GetTpmTokenInfoReply reply;
reply.mutable_token_info()->set_slot(-1);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), reply));
return;
}
pending_get_tpm_token_info_callback_ = std::move(callback);
InvokeGetTpmTokenInfoCallbackIfReady();
}
void InvokeGetTpmTokenInfoCallbackIfReady() {
if (!tpm_token_info_.has_value() || tpm_token_info_->slot() == -1 ||
pending_get_tpm_token_info_callback_.is_null())
return;
get_tpm_token_info_succeeded_ = true;
// Called synchronously for convenience (to avoid using extra RunLoop in
// tests). Unlike with other Cryptohome callbacks, TPMTokenInfoGetter does
// not rely on this callback being called asynchronously.
::user_data_auth::Pkcs11GetTpmTokenInfoReply reply;
reply.mutable_token_info()->CopyFrom(tpm_token_info_.value());
std::move(pending_get_tpm_token_info_callback_).Run(reply);
}
AccountId account_id_;
int get_tpm_token_info_failure_count_;
int get_tpm_token_info_not_set_count_;
bool get_tpm_token_info_succeeded_;
chromeos::DBusMethodCallback<::user_data_auth::Pkcs11GetTpmTokenInfoReply>
pending_get_tpm_token_info_callback_;
std::optional<TpmTokenInfo> tpm_token_info_;
};
class SystemTPMTokenInfoGetterTest : public testing::Test {
public:
SystemTPMTokenInfoGetterTest() {
chromeos::TpmManagerClient::Get()->InitializeFake();
}
SystemTPMTokenInfoGetterTest(const SystemTPMTokenInfoGetterTest&) = delete;
SystemTPMTokenInfoGetterTest& operator=(const SystemTPMTokenInfoGetterTest&) =
delete;
~SystemTPMTokenInfoGetterTest() override {
chromeos::TpmManagerClient::Get()->Shutdown();
}
void SetUp() override {
cryptohome_client_ =
std::make_unique<TestCryptohomePkcs11Client>(EmptyAccountId());
tpm_token_info_getter_ = TPMTokenInfoGetter::CreateForSystemToken(
cryptohome_client_.get(),
scoped_refptr<base::TaskRunner>(new FakeTaskRunner(&delays_)));
}
protected:
std::unique_ptr<TestCryptohomePkcs11Client> cryptohome_client_;
std::unique_ptr<TPMTokenInfoGetter> tpm_token_info_getter_;
std::vector<int64_t> delays_;
private:
base::test::SingleThreadTaskEnvironment task_environment_;
};
class UserTPMTokenInfoGetterTest : public testing::Test {
public:
UserTPMTokenInfoGetterTest()
: account_id_(AccountId::FromUserEmail("[email protected]")) {
chromeos::TpmManagerClient::Get()->InitializeFake();
}
UserTPMTokenInfoGetterTest(const UserTPMTokenInfoGetterTest&) = delete;
UserTPMTokenInfoGetterTest& operator=(const UserTPMTokenInfoGetterTest&) =
delete;
~UserTPMTokenInfoGetterTest() override {
chromeos::TpmManagerClient::Get()->Shutdown();
}
void SetUp() override {
cryptohome_client_ =
std::make_unique<TestCryptohomePkcs11Client>(account_id_);
tpm_token_info_getter_ = TPMTokenInfoGetter::CreateForUserToken(
account_id_, cryptohome_client_.get(),
scoped_refptr<base::TaskRunner>(new FakeTaskRunner(&delays_)));
tpm_token_info_getter_->set_nss_slots_software_fallback_for_testing(false);
}
protected:
std::unique_ptr<TestCryptohomePkcs11Client> cryptohome_client_;
std::unique_ptr<TPMTokenInfoGetter> tpm_token_info_getter_;
const AccountId account_id_;
std::vector<int64_t> delays_;
private:
base::test::SingleThreadTaskEnvironment task_environment_;
};
TEST_F(SystemTPMTokenInfoGetterTest, BasicFlow) {
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(completed);
TpmTokenInfo fake_token_info;
fake_token_info.set_label("TOKEN_1");
fake_token_info.set_user_pin("2222");
fake_token_info.set_slot(1);
cryptohome_client_->SetTpmTokenInfo(fake_token_info);
EXPECT_TRUE(completed);
ASSERT_TRUE(result.has_value());
EXPECT_EQ("TOKEN_1", result->label());
EXPECT_EQ("2222", result->user_pin());
EXPECT_EQ(1, result->slot());
EXPECT_EQ(std::vector<int64_t>(), delays_);
}
TEST_F(SystemTPMTokenInfoGetterTest, TokenSlotIdEqualsZero) {
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(completed);
TpmTokenInfo fake_token_info;
fake_token_info.set_label("TOKEN_0");
fake_token_info.set_user_pin("2222");
fake_token_info.set_slot(0);
cryptohome_client_->SetTpmTokenInfo(fake_token_info);
EXPECT_TRUE(completed);
ASSERT_TRUE(result.has_value());
EXPECT_EQ("TOKEN_0", result->label());
EXPECT_EQ("2222", result->user_pin());
EXPECT_EQ(0, result->slot());
EXPECT_EQ(std::vector<int64_t>(), delays_);
}
TEST_F(SystemTPMTokenInfoGetterTest, TPMNotEnabled) {
chromeos::TpmManagerClient::Get()
->GetTestInterface()
->mutable_nonsensitive_status_reply()
->set_is_enabled(false);
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(completed);
EXPECT_EQ(std::vector<int64_t>(), delays_);
}
TEST_F(SystemTPMTokenInfoGetterTest, TPMNotOwnedSystemSlotFallbackEnabled) {
chromeos::TpmManagerClient::Get()
->GetTestInterface()
->mutable_nonsensitive_status_reply()
->set_is_enabled(false);
chromeos::TpmManagerClient::Get()
->GetTestInterface()
->mutable_nonsensitive_status_reply()
->set_is_owned(false);
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->set_nss_slots_software_fallback_for_testing(true);
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(completed);
TpmTokenInfo fake_token_info;
fake_token_info.set_label("TOKEN_1");
fake_token_info.set_user_pin("2222");
fake_token_info.set_slot(1);
cryptohome_client_->SetTpmTokenInfo(fake_token_info);
EXPECT_TRUE(completed);
ASSERT_TRUE(result.has_value());
EXPECT_EQ("TOKEN_1", result->label());
EXPECT_EQ("2222", result->user_pin());
EXPECT_EQ(1, result->slot());
EXPECT_EQ(std::vector<int64_t>(), delays_);
}
TEST_F(SystemTPMTokenInfoGetterTest, TPMOwnedSystemSlotFallbackEnabled) {
chromeos::TpmManagerClient::Get()
->GetTestInterface()
->mutable_nonsensitive_status_reply()
->set_is_enabled(true);
chromeos::TpmManagerClient::Get()
->GetTestInterface()
->mutable_nonsensitive_status_reply()
->set_is_owned(true);
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->set_nss_slots_software_fallback_for_testing(true);
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(completed);
TpmTokenInfo fake_token_info;
fake_token_info.set_label("TOKEN_1");
fake_token_info.set_user_pin("2222");
fake_token_info.set_slot(1);
cryptohome_client_->SetTpmTokenInfo(fake_token_info);
EXPECT_TRUE(completed);
ASSERT_TRUE(result.has_value());
EXPECT_EQ("TOKEN_1", result->label());
EXPECT_EQ("2222", result->user_pin());
EXPECT_EQ(1, result->slot());
EXPECT_EQ(std::vector<int64_t>(), delays_);
}
TEST_F(SystemTPMTokenInfoGetterTest, TpmEnabledCallFails) {
chromeos::TpmManagerClient::Get()
->GetTestInterface()
->set_non_nonsensitive_status_dbus_error_count(1);
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(completed);
TpmTokenInfo fake_token_info;
fake_token_info.set_label("TOKEN_1");
fake_token_info.set_user_pin("2222");
fake_token_info.set_slot(1);
cryptohome_client_->SetTpmTokenInfo(fake_token_info);
EXPECT_TRUE(completed);
ASSERT_TRUE(result.has_value());
EXPECT_EQ("TOKEN_1", result->label());
EXPECT_EQ("2222", result->user_pin());
EXPECT_EQ(1, result->slot());
const int64_t kExpectedDelays[] = {100};
EXPECT_EQ(std::vector<int64_t>(kExpectedDelays,
kExpectedDelays + std::size(kExpectedDelays)),
delays_);
}
TEST_F(SystemTPMTokenInfoGetterTest, GetTpmTokenInfoInitiallyNotReady) {
cryptohome_client_->set_get_tpm_token_info_not_set_count(1);
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(completed);
TpmTokenInfo fake_token_info;
fake_token_info.set_label("TOKEN_1");
fake_token_info.set_user_pin("2222");
fake_token_info.set_slot(1);
cryptohome_client_->SetTpmTokenInfo(fake_token_info);
EXPECT_TRUE(completed);
ASSERT_TRUE(result.has_value());
EXPECT_EQ("TOKEN_1", result->label());
EXPECT_EQ("2222", result->user_pin());
EXPECT_EQ(1, result->slot());
const int64_t kExpectedDelays[] = {100};
EXPECT_EQ(std::vector<int64_t>(kExpectedDelays,
kExpectedDelays + std::size(kExpectedDelays)),
delays_);
}
TEST_F(SystemTPMTokenInfoGetterTest, GetTpmTokenInfoInitiallyFails) {
cryptohome_client_->set_get_tpm_token_info_failure_count(1);
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(completed);
TpmTokenInfo fake_token_info;
fake_token_info.set_label("TOKEN_1");
fake_token_info.set_user_pin("2222");
fake_token_info.set_slot(1);
cryptohome_client_->SetTpmTokenInfo(fake_token_info);
EXPECT_TRUE(completed);
ASSERT_TRUE(result.has_value());
EXPECT_EQ("TOKEN_1", result->label());
EXPECT_EQ("2222", result->user_pin());
EXPECT_EQ(1, result->slot());
const int64_t kExpectedDelays[] = {100};
EXPECT_EQ(std::vector<int64_t>(kExpectedDelays,
kExpectedDelays + std::size(kExpectedDelays)),
delays_);
}
TEST_F(SystemTPMTokenInfoGetterTest, RetryDelaysIncreaseExponentially) {
chromeos::TpmManagerClient::Get()
->GetTestInterface()
->set_non_nonsensitive_status_dbus_error_count(2);
cryptohome_client_->set_get_tpm_token_info_failure_count(1);
cryptohome_client_->set_get_tpm_token_info_not_set_count(3);
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(completed);
TpmTokenInfo fake_token_info;
fake_token_info.set_label("TOKEN_1");
fake_token_info.set_user_pin("2222");
fake_token_info.set_slot(1);
cryptohome_client_->SetTpmTokenInfo(fake_token_info);
EXPECT_TRUE(completed);
ASSERT_TRUE(result.has_value());
EXPECT_EQ("TOKEN_1", result->label());
EXPECT_EQ("2222", result->user_pin());
EXPECT_EQ(1, result->slot());
int64_t kExpectedDelays[] = {100, 200, 400, 800, 1600, 3200};
ASSERT_EQ(std::vector<int64_t>(kExpectedDelays,
kExpectedDelays + std::size(kExpectedDelays)),
delays_);
}
TEST_F(SystemTPMTokenInfoGetterTest, RetryDelayBounded) {
chromeos::TpmManagerClient::Get()
->GetTestInterface()
->set_non_nonsensitive_status_dbus_error_count(4);
cryptohome_client_->set_get_tpm_token_info_failure_count(5);
cryptohome_client_->set_get_tpm_token_info_not_set_count(6);
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(completed);
TpmTokenInfo fake_token_info;
fake_token_info.set_label("TOKEN_1");
fake_token_info.set_user_pin("2222");
fake_token_info.set_slot(1);
cryptohome_client_->SetTpmTokenInfo(fake_token_info);
EXPECT_TRUE(completed);
ASSERT_TRUE(result.has_value());
EXPECT_EQ("TOKEN_1", result->label());
EXPECT_EQ("2222", result->user_pin());
EXPECT_EQ(1, result->slot());
int64_t kExpectedDelays[] = {100, 200, 400, 800, 1600,
3200, 6400, 12800, 25600, 51200,
102400, 204800, 300000, 300000, 300000};
ASSERT_EQ(std::vector<int64_t>(kExpectedDelays,
kExpectedDelays + std::size(kExpectedDelays)),
delays_);
}
TEST_F(UserTPMTokenInfoGetterTest, BasicFlow) {
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(completed);
TpmTokenInfo fake_token_info;
fake_token_info.set_label("TOKEN_1");
fake_token_info.set_user_pin("2222");
fake_token_info.set_slot(1);
cryptohome_client_->SetTpmTokenInfo(fake_token_info);
EXPECT_TRUE(completed);
ASSERT_TRUE(result.has_value());
EXPECT_EQ("TOKEN_1", result->label());
EXPECT_EQ("2222", result->user_pin());
EXPECT_EQ(1, result->slot());
EXPECT_EQ(std::vector<int64_t>(), delays_);
}
TEST_F(UserTPMTokenInfoGetterTest, GetTpmTokenInfoInitiallyFails) {
cryptohome_client_->set_get_tpm_token_info_failure_count(1);
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(completed);
TpmTokenInfo fake_token_info;
fake_token_info.set_label("TOKEN_1");
fake_token_info.set_user_pin("2222");
fake_token_info.set_slot(1);
cryptohome_client_->SetTpmTokenInfo(fake_token_info);
EXPECT_TRUE(completed);
ASSERT_TRUE(result.has_value());
EXPECT_EQ("TOKEN_1", result->label());
EXPECT_EQ("2222", result->user_pin());
EXPECT_EQ(1, result->slot());
const int64_t kExpectedDelays[] = {100};
EXPECT_EQ(std::vector<int64_t>(kExpectedDelays,
kExpectedDelays + std::size(kExpectedDelays)),
delays_);
}
TEST_F(UserTPMTokenInfoGetterTest, GetTpmTokenInfoInitiallyNotReady) {
cryptohome_client_->set_get_tpm_token_info_not_set_count(1);
bool completed = false;
std::optional<TpmTokenInfo> result;
tpm_token_info_getter_->Start(
base::BindOnce(&OnTpmTokenInfoGetterCompleted, &completed, &result));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(completed);
TpmTokenInfo fake_token_info;
fake_token_info.set_label("TOKEN_1");
fake_token_info.set_user_pin("2222");
fake_token_info.set_slot(1);
cryptohome_client_->SetTpmTokenInfo(fake_token_info);
EXPECT_TRUE(completed);
ASSERT_TRUE(result.has_value());
EXPECT_EQ("TOKEN_1", result->label());
EXPECT_EQ("2222", result->user_pin());
EXPECT_EQ(1, result->slot());
const int64_t kExpectedDelays[] = {100};
EXPECT_EQ(std::vector<int64_t>(kExpectedDelays,
kExpectedDelays + std::size(kExpectedDelays)),
delays_);
}
} // namespace
} // namespace ash