blob: bf24152f0b32636e97cc3ba43ff2082afaf1813b [file] [log] [blame]
// Copyright 2012 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chaps/slot_manager_impl.h"
#include <iterator>
#include <map>
#include <memory>
#include <string>
#include <base/functional/bind.h>
#include <base/functional/callback.h>
#include <base/strings/stringprintf.h>
#include <base/test/task_environment.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libhwsec/frontend/chaps/mock_frontend.h>
#include <libhwsec-foundation/error/testing_helper.h>
#include <metrics/metrics_library_mock.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include "chaps/chaps_factory_mock.h"
#include "chaps/chaps_utility.h"
#include "chaps/isolate.h"
#include "chaps/object_pool_mock.h"
#include "chaps/object_store_mock.h"
#include "chaps/session_mock.h"
#include "chaps/slot_policy_mock.h"
using base::FilePath;
using brillo::SecureBlob;
using hwsec::TPMError;
using hwsec_foundation::error::testing::IsOk;
using hwsec_foundation::error::testing::ReturnError;
using hwsec_foundation::error::testing::ReturnOk;
using hwsec_foundation::error::testing::ReturnValue;
using hwsec_foundation::status::MakeStatus;
using hwsec_foundation::status::OkStatus;
using hwsec_foundation::status::StatusChain;
using std::string;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::InvokeWithoutArgs;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
using TPMRetryAction = ::hwsec::TPMRetryAction;
namespace chaps {
namespace {
const char kAuthData[] = "000000";
const char kTokenLabel[] = "test_label";
const char kChapsTokenManagerLoadToken[] =
"Platform.Chaps.TokenManager.LoadToken";
const char kChapsTokenManagerUnloadToken[] =
"Platform.Chaps.TokenManager.UnloadToken";
SecureBlob MakeBlob(const char* auth_data_str) {
return Sha1(SecureBlob(auth_data_str));
}
// Creates and sets default expectations on a ObjectPoolMock instance. Returns
// a pointer to the new object.
ObjectPool* CreateObjectPoolMock(bool* is_pool_valid) {
ObjectPoolMock* object_pool = new ObjectPoolMock();
EXPECT_CALL(*object_pool, GetInternalBlob(kEncryptedAuthKey, _))
.WillRepeatedly(
DoAll(SetArgPointee<1>(string("auth_key_blob")), Return(true)));
EXPECT_CALL(*object_pool, GetInternalBlob(kEncryptedRootKey, _))
.WillRepeatedly(
DoAll(SetArgPointee<1>(string("encrypted_root_key")), Return(true)));
EXPECT_CALL(*object_pool, GetInternalBlob(kImportedTracker, _))
.WillRepeatedly(DoAll(SetArgPointee<1>(string()), Return(false)));
EXPECT_CALL(*object_pool, GetInternalBlob(kAuthDataHash, _))
.WillRepeatedly(
DoAll(SetArgPointee<1>(string("\x01\xCE")), Return(true)));
EXPECT_CALL(*object_pool,
SetInternalBlob(kEncryptedAuthKey, string("auth_key_blob")))
.WillRepeatedly(Return(true));
EXPECT_CALL(*object_pool,
SetInternalBlob(kEncryptedAuthKey, string("new_auth_key_blob")))
.WillRepeatedly(Return(true));
EXPECT_CALL(*object_pool,
SetInternalBlob(kEncryptedRootKey, string("encrypted_root_key")))
.WillRepeatedly(Return(true));
EXPECT_CALL(*object_pool, SetInternalBlob(kImportedTracker, string()))
.WillRepeatedly(Return(true));
EXPECT_CALL(*object_pool, SetInternalBlob(kAuthDataHash, _))
.WillRepeatedly(Return(true));
EXPECT_CALL(*object_pool, SetEncryptionKey(_)).WillRepeatedly(Return(true));
EXPECT_CALL(*object_pool, IsValid()).WillRepeatedly([is_pool_valid]() {
return *is_pool_valid;
});
EXPECT_CALL(*object_pool, Invalidate()).WillRepeatedly([is_pool_valid]() {
*is_pool_valid = false;
});
return object_pool;
}
// Sets default expectations on a hwsec::MockChapsFrontend.
void ConfigureHwsec(hwsec::MockChapsFrontend* hwsec) {
EXPECT_CALL(*hwsec, GetRandomBlob(_)).WillRepeatedly([](size_t size) {
brillo::Blob blob(size);
RAND_bytes(blob.data(), size);
return blob;
});
EXPECT_CALL(*hwsec, GetRandomSecureBlob(_)).WillRepeatedly([](size_t size) {
brillo::SecureBlob blob(size);
RAND_bytes(blob.data(), size);
return blob;
});
EXPECT_CALL(*hwsec, IsEnabled()).WillRepeatedly(ReturnValue(true));
EXPECT_CALL(*hwsec, IsReady()).WillRepeatedly(ReturnValue(true));
EXPECT_CALL(*hwsec, GetFamily()).WillRepeatedly(ReturnValue(0x322E3000));
EXPECT_CALL(*hwsec, UnsealDataAsync(_, Sha1(MakeBlob(kAuthData)), _))
.WillRepeatedly([](auto&&, auto&&, auto&& callback) {
std::move(callback).Run(brillo::SecureBlob());
});
EXPECT_CALL(*hwsec, SealDataAsync(_, Sha1(MakeBlob(kAuthData)), _))
.WillRepeatedly([](auto&&, auto&&, auto&& callback) {
std::move(callback).Run(hwsec::ChapsSealedData{});
});
}
// Creates and returns a mock Session instance.
Session* CreateNewSession() {
return new SessionMock();
}
} // namespace
// A test fixture for an initialized SlotManagerImpl instance.
class TestSlotManager : public ::testing::Test {
public:
TestSlotManager() {
EXPECT_CALL(factory_, CreateSession(_, _, _, _, _))
.WillRepeatedly(InvokeWithoutArgs(CreateNewSession));
ObjectStore* null_store = nullptr;
EXPECT_CALL(factory_, CreateObjectStore(_))
.WillRepeatedly(Return(null_store));
ic_ = IsolateCredentialManager::GetDefaultIsolateCredential();
}
void SetUp() {
// The default style "fast" does not support multi-threaded death tests.
testing::FLAGS_gtest_death_test_style = "threadsafe";
EXPECT_CALL(factory_, CreateObjectPool(_, _, _)).WillRepeatedly([this]() {
return CreateObjectPoolMock(&is_pool_valid_);
});
ConfigureHwsec(&hwsec_);
chaps_metrics_.set_metrics_library_for_testing(&mock_metrics_library_);
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kTPMAvailability,
static_cast<int>(TPMAvailabilityStatus::kTPMAvailable),
static_cast<int>(TPMAvailabilityStatus::kMaxValue)))
.WillOnce(Return(true));
slot_manager_.reset(new SlotManagerImpl(&factory_, &hwsec_, false, nullptr,
&chaps_metrics_));
ASSERT_TRUE(slot_manager_->Init());
}
void TearDown() {
// Destroy the slot manager before its dependencies.
slot_manager_.reset();
}
int InsertToken() {
int slot_id = 0;
slot_manager_->LoadToken(ic_, FilePath("/var/lib/chaps"),
MakeBlob(kAuthData), kTokenLabel, &slot_id);
return slot_id;
}
protected:
base::test::TaskEnvironment task_environment_;
bool is_pool_valid_ = true;
ChapsFactoryMock factory_;
hwsec::MockChapsFrontend hwsec_;
std::unique_ptr<SlotManagerImpl> slot_manager_;
SecureBlob ic_;
StrictMock<MetricsLibraryMock> mock_metrics_library_;
ChapsMetrics chaps_metrics_;
};
typedef TestSlotManager TestSlotManager_DeathTest;
TEST(DeathTest, InvalidInit) {
// The default style "fast" does not support multi-threaded death tests.
testing::FLAGS_gtest_death_test_style = "threadsafe";
hwsec::MockChapsFrontend hwsec;
ConfigureHwsec(&hwsec);
ChapsFactoryMock factory;
StrictMock<MetricsLibraryMock> mock_metrics_library;
ChapsMetrics chaps_metrics;
chaps_metrics.set_metrics_library_for_testing(&mock_metrics_library);
EXPECT_DEATH_IF_SUPPORTED(
new SlotManagerImpl(&factory, nullptr, false, nullptr, &chaps_metrics),
"Check failed");
EXPECT_DEATH_IF_SUPPORTED(
new SlotManagerImpl(nullptr, &hwsec, false, nullptr, &chaps_metrics),
"Check failed");
}
TEST_F(TestSlotManager_DeathTest, InvalidArgs) {
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->IsTokenPresent(ic_, 3),
"Check failed");
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->GetSlotInfo(ic_, 0, nullptr),
"Check failed");
CK_SLOT_INFO slot_info;
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->GetSlotInfo(ic_, 3, &slot_info),
"Check failed");
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->GetTokenInfo(ic_, 0, nullptr),
"Check failed");
CK_TOKEN_INFO token_info;
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->GetTokenInfo(ic_, 3, &token_info),
"Check failed");
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->GetMechanismInfo(ic_, 3),
"Check failed");
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->OpenSession(ic_, 3, false),
"Check failed");
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->CloseAllSessions(ic_, 3),
"Check failed");
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->GetSession(ic_, 0, nullptr),
"Check failed");
}
TEST_F(TestSlotManager_DeathTest, OutOfMemorySession) {
Session* null_session = nullptr;
EXPECT_CALL(factory_, CreateSession(_, _, _, _, _))
.WillRepeatedly(Return(null_session));
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->OpenSession(ic_, 0, false),
"Check failed");
}
TEST_F(TestSlotManager_DeathTest, NoToken) {
CK_TOKEN_INFO token_info;
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->GetTokenInfo(ic_, 1, &token_info),
"Check failed");
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->GetMechanismInfo(ic_, 1),
"Check failed");
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->OpenSession(ic_, 1, false),
"Check failed");
}
TEST_F(TestSlotManager, DefaultSlotSetup) {
EXPECT_EQ(2, slot_manager_->GetSlotCount());
EXPECT_FALSE(slot_manager_->IsTokenAccessible(ic_, 0));
EXPECT_FALSE(slot_manager_->IsTokenAccessible(ic_, 1));
}
TEST(DeathTest, OutOfMemoryInit) {
// The default style "fast" does not support multi-threaded death tests.
testing::FLAGS_gtest_death_test_style = "threadsafe";
hwsec::MockChapsFrontend hwsec;
ConfigureHwsec(&hwsec);
ChapsFactoryMock factory;
ObjectPool* null_pool = nullptr;
StrictMock<MetricsLibraryMock> mock_metrics_library;
ChapsMetrics chaps_metrics;
chaps_metrics.set_metrics_library_for_testing(&mock_metrics_library);
EXPECT_CALL(factory, CreateObjectPool(_, _, _))
.WillRepeatedly(Return(null_pool));
ObjectStore* null_store = nullptr;
EXPECT_CALL(factory, CreateObjectStore(_)).WillRepeatedly(Return(null_store));
SlotManagerImpl sm(&factory, &hwsec, false, nullptr, &chaps_metrics);
EXPECT_CALL(
mock_metrics_library,
SendEnumToUMA(kTPMAvailability,
static_cast<int>(TPMAvailabilityStatus::kTPMAvailable),
static_cast<int>(TPMAvailabilityStatus::kMaxValue)))
.WillOnce(Return(true));
ASSERT_TRUE(sm.Init());
int slot_id;
EXPECT_DEATH_IF_SUPPORTED(
sm.LoadToken(IsolateCredentialManager::GetDefaultIsolateCredential(),
FilePath("/var/lib/chaps"), MakeBlob(kAuthData), kTokenLabel,
&slot_id),
"Check failed");
LOG_CK_RV(CKR_OK);
}
TEST_F(TestSlotManager, QueryInfo) {
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
InsertToken();
CK_SLOT_INFO slot_info;
memset(&slot_info, 0xEE, sizeof(slot_info));
slot_manager_->GetSlotInfo(ic_, 0, &slot_info);
// Check if all bytes have been set by the call.
EXPECT_EQ(nullptr, memchr(&slot_info, 0xEE, sizeof(slot_info)));
CK_TOKEN_INFO token_info;
memset(&token_info, 0xEE, sizeof(token_info));
slot_manager_->GetTokenInfo(ic_, 0, &token_info);
EXPECT_EQ(nullptr, memchr(&token_info, 0xEE, sizeof(token_info)));
string expected_label(kTokenLabel);
expected_label.resize(std::size(token_info.label), ' ');
string actual_label(reinterpret_cast<char*>(token_info.label),
std::size(token_info.label));
EXPECT_EQ(expected_label, actual_label);
const MechanismMap* mechanisms = slot_manager_->GetMechanismInfo(ic_, 0);
ASSERT_TRUE(mechanisms != nullptr);
// Sanity check - we don't want to be strict on the mechanism list.
EXPECT_TRUE(mechanisms->end() != mechanisms->find(CKM_RSA_PKCS));
EXPECT_TRUE(mechanisms->end() != mechanisms->find(CKM_AES_CBC));
}
TEST_F(TestSlotManager, TestSessions) {
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
InsertToken();
int id1 = slot_manager_->OpenSession(ic_, 0, false);
int id2 = slot_manager_->OpenSession(ic_, 0, true);
EXPECT_NE(id1, id2);
Session* s1 = nullptr;
EXPECT_TRUE(slot_manager_->GetSession(ic_, id1, &s1));
EXPECT_TRUE(s1 != nullptr);
Session* s2 = nullptr;
EXPECT_TRUE(slot_manager_->GetSession(ic_, id2, &s2));
EXPECT_TRUE(s2 != nullptr);
EXPECT_NE(s1, s2);
EXPECT_TRUE(slot_manager_->CloseSession(ic_, id1));
EXPECT_FALSE(slot_manager_->CloseSession(ic_, id1));
slot_manager_->CloseAllSessions(ic_, 0);
EXPECT_FALSE(slot_manager_->CloseSession(ic_, id2));
}
TEST_F(TestSlotManager, TestLoadTokenEvents) {
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.Times(4);
InsertToken();
int slot_id;
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kLoadExistingToken),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_TRUE(slot_manager_->LoadToken(
ic_, FilePath("some_path"), MakeBlob(kAuthData), kTokenLabel, &slot_id));
EXPECT_TRUE(slot_manager_->IsTokenPresent(ic_, 1));
// Load token with an existing path - should not result in a new slot.
int slot_id2;
EXPECT_TRUE(slot_manager_->LoadToken(
ic_, FilePath("some_path"), MakeBlob(kAuthData), kTokenLabel, &slot_id2));
EXPECT_EQ(slot_id, slot_id2);
EXPECT_TRUE(slot_manager_->LoadToken(ic_, FilePath("another_path"),
MakeBlob(kAuthData), kTokenLabel,
&slot_id));
EXPECT_TRUE(slot_manager_->IsTokenPresent(ic_, 2));
// Logout with an unknown path.
EXPECT_CALL(mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerUnloadToken,
static_cast<int>(TokenManagerStatus::kUnknownPath),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_FALSE(
slot_manager_->UnloadToken(ic_, FilePath("still_yet_another_path")));
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerUnloadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.Times(2);
EXPECT_TRUE(slot_manager_->UnloadToken(ic_, FilePath("some_path")));
EXPECT_FALSE(slot_manager_->IsTokenAccessible(ic_, 1));
EXPECT_TRUE(slot_manager_->IsTokenPresent(ic_, 2));
EXPECT_TRUE(slot_manager_->LoadToken(ic_, FilePath("one_more_path"),
MakeBlob(kAuthData), kTokenLabel,
&slot_id));
EXPECT_TRUE(slot_manager_->IsTokenPresent(ic_, 1));
EXPECT_TRUE(slot_manager_->UnloadToken(ic_, FilePath("another_path")));
}
TEST_F(TestSlotManager, ManyLoadToken) {
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.Times(101);
InsertToken();
for (int i = 0; i < 100; ++i) {
string path = base::StringPrintf("test%d", i);
int slot_id = 0;
EXPECT_TRUE(slot_manager_->LoadToken(
ic_, FilePath(path), MakeBlob(kAuthData), kTokenLabel, &slot_id));
}
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerUnloadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.Times(100);
for (int i = 0; i < 100; ++i) {
string path = base::StringPrintf("test%d", i);
EXPECT_TRUE(slot_manager_->UnloadToken(ic_, FilePath(path)));
}
}
TEST_F(TestSlotManager, LoadUnloadToken) {
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.Times(2);
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerUnloadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.Times(1);
InsertToken();
int slot_id = 0;
std::string path = "test0";
EXPECT_TRUE(slot_manager_->LoadToken(ic_, FilePath(path), MakeBlob(kAuthData),
kTokenLabel, &slot_id));
EXPECT_TRUE(is_pool_valid_);
task_environment_.RunUntilIdle();
EXPECT_TRUE(is_pool_valid_);
EXPECT_TRUE(slot_manager_->UnloadToken(ic_, FilePath(path)));
EXPECT_FALSE(is_pool_valid_);
}
TEST_F(TestSlotManager, LoadUnloadTokenEarly) {
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.Times(2);
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerUnloadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.Times(1);
InsertToken();
int slot_id = 0;
std::string path = "test0";
EXPECT_TRUE(slot_manager_->LoadToken(ic_, FilePath(path), MakeBlob(kAuthData),
kTokenLabel, &slot_id));
EXPECT_TRUE(is_pool_valid_);
EXPECT_TRUE(slot_manager_->UnloadToken(ic_, FilePath(path)));
EXPECT_FALSE(is_pool_valid_);
}
TEST_F(TestSlotManager, TestDefaultIsolate) {
// Check default isolate is there by default.
SecureBlob defaultIsolate =
IsolateCredentialManager::GetDefaultIsolateCredential();
bool new_isolate = true;
EXPECT_TRUE(slot_manager_->OpenIsolate(&defaultIsolate, &new_isolate));
EXPECT_FALSE(new_isolate);
EXPECT_EQ(IsolateCredentialManager::GetDefaultIsolateCredential(),
defaultIsolate);
}
TEST_F(TestSlotManager, TestOpenIsolate) {
EXPECT_CALL(hwsec_, GetRandomSecureBlob(_))
.WillOnce(ReturnValue(brillo::SecureBlob(kIsolateCredentialBytes, 'A')));
// Check that trying to open an invalid isolate creates new isolate.
SecureBlob isolate("invalid");
bool new_isolate_created = false;
EXPECT_TRUE(slot_manager_->OpenIsolate(&isolate, &new_isolate_created));
EXPECT_TRUE(new_isolate_created);
EXPECT_EQ(brillo::SecureBlob(kIsolateCredentialBytes, 'A'), isolate);
// Check opening an existing isolate.
EXPECT_TRUE(slot_manager_->OpenIsolate(&isolate, &new_isolate_created));
EXPECT_FALSE(new_isolate_created);
EXPECT_EQ(brillo::SecureBlob(kIsolateCredentialBytes, 'A'), isolate);
}
TEST_F(TestSlotManager, TestCloseIsolate) {
EXPECT_CALL(hwsec_, GetRandomSecureBlob(_))
.WillOnce(ReturnValue(brillo::SecureBlob(kIsolateCredentialBytes, 'A')))
.WillOnce(ReturnValue(brillo::SecureBlob(kIsolateCredentialBytes, 'B')));
SecureBlob isolate;
bool new_isolate_created;
EXPECT_TRUE(slot_manager_->OpenIsolate(&isolate, &new_isolate_created));
EXPECT_TRUE(new_isolate_created);
EXPECT_EQ(brillo::SecureBlob(kIsolateCredentialBytes, 'A'), isolate);
EXPECT_TRUE(slot_manager_->OpenIsolate(&isolate, &new_isolate_created));
EXPECT_FALSE(new_isolate_created);
EXPECT_EQ(brillo::SecureBlob(kIsolateCredentialBytes, 'A'), isolate);
slot_manager_->CloseIsolate(isolate);
slot_manager_->CloseIsolate(isolate);
// Final logout, isolate should now be destroyed.
EXPECT_TRUE(slot_manager_->OpenIsolate(&isolate, &new_isolate_created));
EXPECT_TRUE(new_isolate_created);
EXPECT_EQ(brillo::SecureBlob(kIsolateCredentialBytes, 'B'), isolate);
}
TEST_F(TestSlotManager, TestCloseIsolateUnloadToken) {
SecureBlob isolate;
bool new_isolate_created;
EXPECT_TRUE(slot_manager_->OpenIsolate(&isolate, &new_isolate_created));
EXPECT_TRUE(new_isolate_created);
EXPECT_FALSE(slot_manager_->IsTokenAccessible(isolate, 0));
int slot_id;
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_TRUE(slot_manager_->LoadToken(isolate, FilePath("some_path"),
MakeBlob(kAuthData), kTokenLabel,
&slot_id));
EXPECT_TRUE(slot_manager_->IsTokenPresent(isolate, 0));
// Token should be unloaded by CloseIsolate call.
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerUnloadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
slot_manager_->CloseIsolate(isolate);
EXPECT_FALSE(slot_manager_->IsTokenAccessible(isolate, 0));
}
TEST_F(TestSlotManager_DeathTest, TestIsolateTokens) {
CK_SLOT_INFO slot_info;
CK_TOKEN_INFO token_info;
Session* session;
SecureBlob new_isolate_0, new_isolate_1;
SecureBlob defaultIsolate =
IsolateCredentialManager::GetDefaultIsolateCredential();
// Ensure different credentials are created for each isolate.
EXPECT_CALL(hwsec_, GetRandomSecureBlob(_))
.WillOnce(ReturnValue(brillo::SecureBlob(kIsolateCredentialBytes, 'A')))
.WillOnce(ReturnValue(brillo::SecureBlob(kIsolateCredentialBytes, 'B')));
bool new_isolate_created;
int slot_id;
ASSERT_TRUE(slot_manager_->OpenIsolate(&new_isolate_0, &new_isolate_created));
ASSERT_TRUE(new_isolate_created);
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.Times(2);
ASSERT_TRUE(slot_manager_->LoadToken(new_isolate_0, FilePath("new_isolate"),
MakeBlob(kAuthData), kTokenLabel,
&slot_id));
ASSERT_TRUE(slot_manager_->OpenIsolate(&new_isolate_1, &new_isolate_created));
ASSERT_TRUE(new_isolate_created);
ASSERT_TRUE(
slot_manager_->LoadToken(new_isolate_1, FilePath("another_new_isolate"),
MakeBlob(kAuthData), kTokenLabel, &slot_id));
// Ensure tokens are only accessible with the valid isolate cred.
ASSERT_TRUE(slot_manager_->IsTokenAccessible(new_isolate_0, 0));
ASSERT_TRUE(slot_manager_->IsTokenAccessible(new_isolate_1, 1));
ASSERT_FALSE(slot_manager_->IsTokenAccessible(new_isolate_1, 0));
ASSERT_FALSE(slot_manager_->IsTokenAccessible(new_isolate_0, 1));
ASSERT_FALSE(slot_manager_->IsTokenAccessible(defaultIsolate, 0));
ASSERT_FALSE(slot_manager_->IsTokenAccessible(defaultIsolate, 1));
// Check all public methods perform isolate checks.
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->IsTokenPresent(new_isolate_0, 1),
"Check failed");
EXPECT_DEATH_IF_SUPPORTED(
slot_manager_->GetSlotInfo(new_isolate_0, 1, &slot_info), "Check failed");
EXPECT_DEATH_IF_SUPPORTED(
slot_manager_->GetTokenInfo(new_isolate_0, 1, &token_info),
"Check failed");
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->GetMechanismInfo(new_isolate_0, 1),
"Check failed");
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->OpenSession(new_isolate_0, 1, false),
"Check failed");
int slot_1_session = slot_manager_->OpenSession(new_isolate_1, 1, false);
EXPECT_TRUE(
slot_manager_->GetSession(new_isolate_1, slot_1_session, &session));
EXPECT_FALSE(
slot_manager_->GetSession(new_isolate_0, slot_1_session, &session));
EXPECT_FALSE(slot_manager_->CloseSession(new_isolate_0, slot_1_session));
EXPECT_DEATH_IF_SUPPORTED(slot_manager_->CloseAllSessions(new_isolate_0, 1),
"Check failed");
}
TEST_F(TestSlotManager, HWSecNotReady) {
StrictMock<MetricsLibraryMock> mock_metrics_library;
ChapsMetrics chaps_metrics;
chaps_metrics.set_metrics_library_for_testing(&mock_metrics_library);
EXPECT_CALL(hwsec_, IsReady()).WillRepeatedly(ReturnValue(false));
EXPECT_CALL(
mock_metrics_library,
SendEnumToUMA(kTPMAvailability,
static_cast<int>(TPMAvailabilityStatus::kTPMAvailable),
static_cast<int>(TPMAvailabilityStatus::kMaxValue)))
.WillOnce(Return(true));
slot_manager_.reset(
new SlotManagerImpl(&factory_, &hwsec_, false, nullptr, &chaps_metrics));
ASSERT_TRUE(slot_manager_->Init());
EXPECT_FALSE(slot_manager_->IsTokenAccessible(ic_, 0));
EXPECT_FALSE(slot_manager_->IsTokenAccessible(ic_, 1));
int slot_id = 0;
chaps_metrics.set_metrics_library_for_testing(&mock_metrics_library);
EXPECT_CALL(
mock_metrics_library,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kInitStage2Failed),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_FALSE(slot_manager_->LoadToken(
ic_, FilePath("test_token"), MakeBlob(kAuthData), kTokenLabel, &slot_id));
EXPECT_FALSE(slot_manager_->IsTokenAccessible(ic_, 0));
EXPECT_FALSE(slot_manager_->IsTokenAccessible(ic_, 1));
}
TEST_F(TestSlotManager, DelayedHWSecReady) {
StrictMock<MetricsLibraryMock> mock_metrics_library;
ChapsMetrics chaps_metrics;
chaps_metrics.set_metrics_library_for_testing(&mock_metrics_library);
EXPECT_CALL(hwsec_, IsEnabled()).WillRepeatedly(ReturnValue(true));
EXPECT_CALL(hwsec_, IsReady()).WillRepeatedly(ReturnValue(false));
EXPECT_CALL(
mock_metrics_library,
SendEnumToUMA(kTPMAvailability,
static_cast<int>(TPMAvailabilityStatus::kTPMAvailable),
static_cast<int>(TPMAvailabilityStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_CALL(
mock_metrics_library,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
slot_manager_.reset(
new SlotManagerImpl(&factory_, &hwsec_, false, nullptr, &chaps_metrics));
ASSERT_TRUE(slot_manager_->Init());
EXPECT_CALL(hwsec_, IsReady()).WillRepeatedly(ReturnValue(true));
int slot_id = 0;
EXPECT_TRUE(slot_manager_->LoadToken(
ic_, FilePath("test_token"), MakeBlob(kAuthData), kTokenLabel, &slot_id));
}
class SoftwareOnlyTest : public TestSlotManager {
public:
SoftwareOnlyTest()
: kTestTokenPath("sw_test_token"),
set_encryption_key_num_calls_(0),
delete_all_num_calls_(0),
pool_write_result_(true) {}
~SoftwareOnlyTest() override {}
void SetUp() override {
// The default style "fast" does not support multi-threaded death tests.
testing::FLAGS_gtest_death_test_style = "threadsafe";
// Use our own SlotPolicyFactory and ObjectPoolFactory.
EXPECT_CALL(factory_, CreateSlotPolicy(false))
.WillRepeatedly(
InvokeWithoutArgs(this, &SoftwareOnlyTest::SlotPolicyFactory));
EXPECT_CALL(factory_, CreateObjectPool(_, _, _))
.WillRepeatedly(
InvokeWithoutArgs(this, &SoftwareOnlyTest::ObjectPoolFactory));
chaps_metrics_.set_metrics_library_for_testing(&mock_metrics_library_);
EXPECT_CALL(hwsec_, IsEnabled()).WillRepeatedly(ReturnValue(false));
EXPECT_CALL(hwsec_, IsReady()).WillRepeatedly(ReturnValue(false));
EXPECT_CALL(hwsec_, GetFamily())
.WillRepeatedly(
ReturnError<TPMError>("Not supported", TPMRetryAction::kNoRetry));
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kTPMAvailability,
static_cast<int>(TPMAvailabilityStatus::kTPMUnavailable),
static_cast<int>(TPMAvailabilityStatus::kMaxValue)))
.WillOnce(Return(true));
slot_manager_.reset(new SlotManagerImpl(&factory_, &hwsec_, false, nullptr,
&chaps_metrics_));
ASSERT_TRUE(slot_manager_->Init());
}
void TearDown() override {
// Destroy the slot manager before its dependencies.
slot_manager_.reset();
}
SlotPolicyMock* SlotPolicyFactory() {
// Redirect internal blob stuff to fake methods.
SlotPolicyMock* slot_policy = new SlotPolicyMock();
EXPECT_CALL(*slot_policy, IsObjectClassAllowedForNewObject(_))
.WillRepeatedly(Return(true));
EXPECT_CALL(*slot_policy, IsObjectClassAllowedForImportedObject(_))
.WillRepeatedly(Return(true));
return slot_policy;
}
ObjectPoolMock* ObjectPoolFactory() {
// Redirect internal blob stuff to fake methods.
ObjectPoolMock* object_pool = new ObjectPoolMock();
EXPECT_CALL(*object_pool, GetInternalBlob(_, _))
.WillRepeatedly(Invoke(this, &SoftwareOnlyTest::FakeGetInternalBlob));
EXPECT_CALL(*object_pool, SetInternalBlob(_, _))
.WillRepeatedly(Invoke(this, &SoftwareOnlyTest::FakeSetInternalBlob));
EXPECT_CALL(*object_pool, SetEncryptionKey(_))
.WillRepeatedly(Invoke(this, &SoftwareOnlyTest::FakeSetEncryptionKey));
EXPECT_CALL(*object_pool, DeleteAll())
.WillRepeatedly(Invoke(this, &SoftwareOnlyTest::FakeDeleteAll));
return object_pool;
}
void InitializeObjectPoolBlobs() {
// The easiest way is to load / unload a token and let the SlotManager do
// the crypto.
pool_blobs_.clear();
int slot_id = 0;
ASSERT_TRUE(slot_manager_->LoadToken(
ic_, kTestTokenPath, MakeBlob(kAuthData), kTokenLabel, &slot_id));
ASSERT_TRUE(slot_manager_->UnloadToken(ic_, kTestTokenPath));
set_encryption_key_num_calls_ = 0;
delete_all_num_calls_ = 0;
}
bool FakeGetInternalBlob(int blob_id, std::string* blob) {
std::map<int, string>::iterator iter = pool_blobs_.find(blob_id);
if (iter == pool_blobs_.end())
return false;
*blob = iter->second;
return true;
}
bool FakeSetInternalBlob(int blob_id, const std::string& blob) {
if (pool_write_result_) {
pool_blobs_[blob_id] = blob;
}
return pool_write_result_;
}
bool FakeSetEncryptionKey(const brillo::SecureBlob& key) {
set_encryption_key_num_calls_++;
return pool_write_result_;
}
ObjectPool::Result FakeDeleteAll() {
delete_all_num_calls_++;
return pool_write_result_ ? ObjectPool::Result::Success
: ObjectPool::Result::Failure;
}
protected:
const FilePath kTestTokenPath;
// Strict so that we get an error if this gets called.
hwsec::MockChapsFrontend hwsec_;
std::map<int, string> pool_blobs_;
int set_encryption_key_num_calls_;
int delete_all_num_calls_;
bool pool_write_result_;
};
TEST_F(SoftwareOnlyTest, CreateNew) {
int slot_id = 0;
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_TRUE(slot_manager_->LoadToken(ic_, kTestTokenPath, MakeBlob(kAuthData),
kTokenLabel, &slot_id));
EXPECT_TRUE(slot_manager_->IsTokenAccessible(ic_, slot_id));
// Check that an encryption key gets set for a load.
EXPECT_EQ(1, set_encryption_key_num_calls_);
// Check that there was no attempt to destroy a previous token.
EXPECT_EQ(0, delete_all_num_calls_);
}
TEST_F(SoftwareOnlyTest, CreateNewShared) {
int slot_id = 0;
EXPECT_CALL(factory_, CreateSlotPolicy(true))
.WillOnce(InvokeWithoutArgs(this, &SoftwareOnlyTest::SlotPolicyFactory));
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_TRUE(slot_manager_->LoadToken(ic_, base::FilePath(kSystemTokenPath),
MakeBlob(kAuthData), kTokenLabel,
&slot_id));
EXPECT_TRUE(slot_manager_->IsTokenAccessible(ic_, slot_id));
// Check that an encryption key gets set for a load.
EXPECT_EQ(1, set_encryption_key_num_calls_);
// Check that there was no attempt to destroy a previous token.
EXPECT_EQ(0, delete_all_num_calls_);
}
TEST_F(SoftwareOnlyTest, TestOpenIsolate) {
// Check that trying to open an invalid isolate creates new isolate.
SecureBlob isolate("invalid");
bool new_isolate_created = false;
EXPECT_TRUE(slot_manager_->OpenIsolate(&isolate, &new_isolate_created));
EXPECT_TRUE(new_isolate_created);
// Check opening an existing isolate.
EXPECT_TRUE(slot_manager_->OpenIsolate(&isolate, &new_isolate_created));
EXPECT_FALSE(new_isolate_created);
}
TEST_F(SoftwareOnlyTest, LoadExisting) {
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.Times(2);
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerUnloadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
InitializeObjectPoolBlobs();
int slot_id = 0;
EXPECT_TRUE(slot_manager_->LoadToken(ic_, kTestTokenPath, MakeBlob(kAuthData),
kTokenLabel, &slot_id));
EXPECT_TRUE(slot_manager_->IsTokenAccessible(ic_, slot_id));
EXPECT_EQ(1, set_encryption_key_num_calls_);
EXPECT_EQ(0, delete_all_num_calls_);
}
TEST_F(SoftwareOnlyTest, BadAuth) {
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.Times(2);
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerUnloadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
InitializeObjectPoolBlobs();
// We expect the token to be successfully recreated with the new auth value.
int slot_id = 0;
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(
kReinitializingToken,
static_cast<int>(ReinitializingTokenStatus::kBadAuthorizationData),
static_cast<int>(ReinitializingTokenStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_TRUE(slot_manager_->LoadToken(ic_, kTestTokenPath, MakeBlob("bad"),
kTokenLabel, &slot_id));
EXPECT_TRUE(slot_manager_->IsTokenAccessible(ic_, slot_id));
EXPECT_EQ(1, set_encryption_key_num_calls_);
EXPECT_EQ(1, delete_all_num_calls_);
}
TEST_F(SoftwareOnlyTest, CorruptRootKey) {
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.Times(2);
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerUnloadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
InitializeObjectPoolBlobs();
pool_blobs_[kEncryptedRootKey] = "bad";
// We expect the token to be successfully recreated.
int slot_id = 0;
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(
kReinitializingToken,
static_cast<int>(ReinitializingTokenStatus::kFailedToDecryptRootKey),
static_cast<int>(ReinitializingTokenStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_TRUE(slot_manager_->LoadToken(ic_, kTestTokenPath, MakeBlob(kAuthData),
kTokenLabel, &slot_id));
EXPECT_TRUE(slot_manager_->IsTokenAccessible(ic_, slot_id));
EXPECT_EQ(1, set_encryption_key_num_calls_);
EXPECT_EQ(1, delete_all_num_calls_);
}
TEST_F(SoftwareOnlyTest, CreateNewWriteFailure) {
pool_write_result_ = false;
int slot_id = 0;
EXPECT_CALL(mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(
TokenManagerStatus::kFailedToLoadSoftwareToken),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_FALSE(slot_manager_->LoadToken(
ic_, kTestTokenPath, MakeBlob(kAuthData), kTokenLabel, &slot_id));
EXPECT_FALSE(slot_manager_->IsTokenAccessible(ic_, slot_id));
}
TEST_F(SoftwareOnlyTest, LoadExistingWriteFailure) {
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerUnloadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
InitializeObjectPoolBlobs();
pool_write_result_ = false;
int slot_id = 0;
EXPECT_CALL(mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(
TokenManagerStatus::kFailedToLoadSoftwareToken),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_FALSE(slot_manager_->LoadToken(
ic_, kTestTokenPath, MakeBlob(kAuthData), kTokenLabel, &slot_id));
EXPECT_FALSE(slot_manager_->IsTokenAccessible(ic_, slot_id));
EXPECT_EQ(1, set_encryption_key_num_calls_);
}
TEST_F(SoftwareOnlyTest, Unload) {
int slot_id = 0;
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerLoadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_TRUE(slot_manager_->LoadToken(ic_, kTestTokenPath, MakeBlob(kAuthData),
kTokenLabel, &slot_id));
EXPECT_TRUE(slot_manager_->IsTokenAccessible(ic_, slot_id));
EXPECT_CALL(
mock_metrics_library_,
SendEnumToUMA(kChapsTokenManagerUnloadToken,
static_cast<int>(TokenManagerStatus::kCommandSuccess),
static_cast<int>(TokenManagerStatus::kMaxValue)))
.WillOnce(Return(true));
EXPECT_TRUE(slot_manager_->UnloadToken(ic_, kTestTokenPath));
EXPECT_FALSE(slot_manager_->IsTokenAccessible(ic_, slot_id));
}
} // namespace chaps