blob: aa1a933db1a7c952febf0bfccc6f9ca406feafa0 [file] [log] [blame]
// Copyright 2016 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.
//
// Unit tests for TpmInit.
#include "cryptohome/tpm_init.h"
#include <map>
#include <string>
#include <base/files/file_path.h>
#include <brillo/secure_blob.h>
#include "cryptohome/mock_platform.h"
#include "cryptohome/mock_tpm.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;
namespace cryptohome {
extern const base::FilePath kTpmOwnedFile;
const base::FilePath kTpmStatusFile("/mnt/stateful_partition/.tpm_status");
const base::FilePath kDefaultCryptohomeKeyFile("/home/.shadow/cryptohome.key");
const TpmKeyHandle kTestKeyHandle = 17; // any non-zero value
// Tests that need to do more setup work before calling Service::Initialize can
// use this instead of ServiceTest.
class TpmInitTest : public ::testing::Test {
public:
TpmInitTest()
: is_tpm_owned_(false),
is_tpm_being_owned_(false),
is_tpm_initialized_(false),
tpm_init_(&tpm_, &platform_) {}
TpmInitTest(const TpmInitTest&) = delete;
TpmInitTest& operator=(const TpmInitTest&) = delete;
~TpmInitTest() override = default;
// Default mock implementations for |tpm_| methods.
// For TPM-related flags: enabled is always true, other flags are settable.
bool IsTpmOwned() const { return is_tpm_owned_; }
void SetIsTpmOwned(bool is_tpm_owned) { is_tpm_owned_ = is_tpm_owned; }
bool IsTpmBeingOwned() const { return is_tpm_being_owned_; }
void SetIsTpmBeingOwned(bool is_tpm_being_owned) {
is_tpm_being_owned_ = is_tpm_being_owned;
}
bool IsTpmInitialized() { return is_tpm_initialized_; }
void SetIsTpmInitialized(bool is_tpm_initialized) {
is_tpm_initialized_ = is_tpm_initialized;
}
bool PerformTpmEnabledOwnedCheck(bool* is_enabled, bool* is_owned) {
*is_enabled = true;
*is_owned = is_tpm_owned_;
return true;
}
bool GetRandomDataBlob(size_t length, brillo::Blob* data) const {
data->resize(length, 0);
return true;
}
bool GetRandomDataSecureBlob(size_t length, brillo::SecureBlob* data) const {
data->resize(length, 0);
return true;
}
// Default mock implementations for |platform_| methods.
// Files are emulated using |files_| map: <file path> -> <file contents>.
bool FileExists(const base::FilePath& path) const {
return files_.count(path) > 0;
}
bool FileMove(const base::FilePath& from, const base::FilePath& to) {
if (!FileExists(from)) {
return false;
}
if (FileExists(to)) {
return false;
}
files_[to] = files_[from];
return FileDelete(from);
}
bool FileDelete(const base::FilePath& path) {
return files_.erase(path) == 1;
}
bool FileTouch(const base::FilePath& path) {
files_.emplace(path, brillo::Blob());
return FileExists(path);
}
bool GetFileSize(const base::FilePath& path, int64_t* size) {
if (!FileExists(path)) {
return false;
}
*size = files_[path].size();
return true;
}
bool FileRead(const base::FilePath& path, brillo::Blob* blob) {
if (!FileExists(path)) {
return false;
}
*blob = files_[path];
return true;
}
bool FileReadToString(const base::FilePath& path, std::string* str) {
brillo::Blob blob;
if (!FileRead(path, &blob)) {
return false;
}
str->assign(reinterpret_cast<char*>(blob.data()), blob.size());
return true;
}
bool FileReadToSecureBlob(const base::FilePath& path,
brillo::SecureBlob* sblob) {
if (!FileExists(path)) {
return false;
}
brillo::Blob temp = files_[path];
sblob->assign(temp.begin(), temp.end());
return true;
}
bool FileWrite(const base::FilePath& path, const brillo::Blob& blob) {
files_[path] = blob;
return true;
}
bool FileWriteFromSecureBlob(const base::FilePath& path,
const brillo::SecureBlob& sblob) {
brillo::Blob blob(sblob.begin(), sblob.end());
files_[path] = blob;
return true;
}
bool FileWriteAtomic(const base::FilePath& path,
const brillo::SecureBlob& sblob,
mode_t /* mode */) {
return FileWriteFromSecureBlob(path, sblob);
}
bool FileWriteString(const base::FilePath& path, const std::string& str) {
brillo::Blob blob(str.begin(), str.end());
return FileWrite(path, blob);
}
void SetTpmStatus(const TpmStatus& tpm_status) {
brillo::Blob file_data(tpm_status.ByteSizeLong());
tpm_status.SerializeWithCachedSizesToArray(file_data.data());
EXPECT_TRUE(FileWrite(kTpmStatusFile, file_data));
}
void GetTpmStatus(TpmStatus* tpm_status) {
brillo::Blob file_data;
tpm_status->Clear();
EXPECT_TRUE(FileRead(kTpmStatusFile, &file_data));
EXPECT_TRUE(tpm_status->ParseFromArray(file_data.data(), file_data.size()));
}
void SetTpmReady() {
SetIsTpmOwned(true);
SetIsTpmBeingOwned(false);
FileTouch(kTpmOwnedFile);
ASSERT_TRUE(tpm_init_.IsTpmReady());
}
void SetUp() override {
ON_CALL(tpm_, IsEnabled()).WillByDefault(Return(true));
ON_CALL(tpm_, IsOwned())
.WillByDefault(Invoke(this, &TpmInitTest::IsTpmOwned));
ON_CALL(tpm_, SetIsOwned(_))
.WillByDefault(Invoke(this, &TpmInitTest::SetIsTpmOwned));
ON_CALL(tpm_, IsBeingOwned())
.WillByDefault(Invoke(this, &TpmInitTest::IsTpmBeingOwned));
ON_CALL(tpm_, SetIsBeingOwned(_))
.WillByDefault(Invoke(this, &TpmInitTest::SetIsTpmBeingOwned));
ON_CALL(tpm_, IsInitialized())
.WillByDefault(Invoke(this, &TpmInitTest::IsTpmInitialized));
ON_CALL(tpm_, SetIsInitialized(_))
.WillByDefault(Invoke(this, &TpmInitTest::SetIsTpmInitialized));
ON_CALL(tpm_, PerformEnabledOwnedCheck(_, _))
.WillByDefault(Invoke(this, &TpmInitTest::PerformTpmEnabledOwnedCheck));
ON_CALL(tpm_, GetRandomDataBlob(_, _))
.WillByDefault(Invoke(this, &TpmInitTest::GetRandomDataBlob));
ON_CALL(tpm_, GetRandomDataSecureBlob(_, _))
.WillByDefault(Invoke(this, &TpmInitTest::GetRandomDataSecureBlob));
ON_CALL(platform_, FileExists(_))
.WillByDefault(Invoke(this, &TpmInitTest::FileExists));
ON_CALL(platform_, Move(_, _))
.WillByDefault(Invoke(this, &TpmInitTest::FileMove));
ON_CALL(platform_, DeleteFile(_))
.WillByDefault(Invoke(this, &TpmInitTest::FileDelete));
ON_CALL(platform_, DeletePathRecursively(_))
.WillByDefault(Invoke(this, &TpmInitTest::FileDelete));
ON_CALL(platform_, DeleteFileDurable(_))
.WillByDefault(Invoke(this, &TpmInitTest::FileDelete));
ON_CALL(platform_, TouchFileDurable(_))
.WillByDefault(Invoke(this, &TpmInitTest::FileTouch));
ON_CALL(platform_, GetFileSize(_, _))
.WillByDefault(Invoke(this, &TpmInitTest::GetFileSize));
ON_CALL(platform_, ReadFile(_, _))
.WillByDefault(Invoke(this, &TpmInitTest::FileRead));
ON_CALL(platform_, ReadFileToSecureBlob(_, _))
.WillByDefault(Invoke(this, &TpmInitTest::FileReadToSecureBlob));
ON_CALL(platform_, WriteSecureBlobToFile(_, _))
.WillByDefault(Invoke(this, &TpmInitTest::FileWriteFromSecureBlob));
ON_CALL(platform_, WriteSecureBlobToFileAtomic(_, _, _))
.WillByDefault(Invoke(this, &TpmInitTest::FileWriteAtomic));
ON_CALL(platform_, WriteSecureBlobToFileAtomicDurable(_, _, _))
.WillByDefault(Invoke(this, &TpmInitTest::FileWriteAtomic));
ON_CALL(platform_, DataSyncFile(_)).WillByDefault(Return(true));
}
void TearDown() override {}
protected:
bool is_tpm_owned_;
bool is_tpm_being_owned_;
bool is_tpm_initialized_;
std::map<base::FilePath, brillo::Blob> files_;
NiceMock<MockTpm> tpm_;
NiceMock<MockPlatform> platform_;
// Declare tpm_init_ last, so it gets destroyed before all the mocks.
TpmInit tpm_init_;
};
MATCHER_P(HasStoredCryptohomeKey, str, "") {
std::string stored_key;
if (!arg->FileReadToString(kDefaultCryptohomeKeyFile, &stored_key)) {
*result_listener << "has no stored cryptohome key";
return false;
}
if (stored_key != str) {
*result_listener << "has stored cryptohome key \"" << stored_key << "\"";
return false;
}
return true;
}
MATCHER_P(HasLoadedCryptohomeKey, handle, "") {
if (!arg->HasCryptohomeKey()) {
*result_listener << "has no loaded cryptohome key";
return false;
}
TpmKeyHandle loaded_handle = arg->GetCryptohomeKey();
if (loaded_handle != handle) {
*result_listener << "has loaded cryptohome key " << loaded_handle;
return false;
}
return true;
}
MATCHER(HasNoLoadedCryptohomeKey, "") {
TpmKeyHandle loaded_handle = arg->GetCryptohomeKey();
*result_listener << "has loaded cryptohome key " << loaded_handle;
return !arg->HasCryptohomeKey() && loaded_handle == kInvalidKeyHandle;
}
ACTION_P(GenerateWrappedKey, wrapped_key) {
*arg2 = brillo::SecureBlob(wrapped_key);
return true;
}
ACTION_P(LoadWrappedKeyToHandle, handle) {
arg1->reset(nullptr, handle);
return Tpm::kTpmRetryNone;
}
TEST_F(TpmInitTest, AlreadyOwnedSuccess) {
bool took_ownership = false;
SetIsTpmOwned(true);
FileTouch(kTpmOwnedFile);
ASSERT_TRUE(tpm_init_.TakeOwnership(&took_ownership));
ASSERT_FALSE(took_ownership);
}
TEST_F(TpmInitTest, TakeOwnershipMonolithicSuccess) {
// Setup TPM.
EXPECT_CALL(tpm_, SetIsOwned(false)).Times(1);
EXPECT_CALL(tpm_, SetIsEnabled(true)).Times(1);
EXPECT_TRUE(tpm_init_.SetupTpm(false));
EXPECT_TRUE(IsTpmInitialized());
EXPECT_FALSE(IsTpmOwned());
EXPECT_FALSE(FileExists(kTpmOwnedFile));
::testing::Mock::VerifyAndClearExpectations(&tpm_);
// Take Ownership.
EXPECT_CALL(tpm_, DoesUseTpmManager()).WillOnce(Return(false));
EXPECT_CALL(tpm_, IsEndorsementKeyAvailable())
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
EXPECT_CALL(tpm_, CreateEndorsementKey()).WillOnce(Return(true));
EXPECT_CALL(tpm_, TakeOwnership(_, _)).WillOnce(Return(true));
EXPECT_CALL(tpm_, TestTpmAuth(_)).WillOnce(Return(true));
EXPECT_CALL(tpm_, InitializeSrk(_)).WillOnce(Return(true));
EXPECT_CALL(tpm_, ChangeOwnerPassword(_, _)).WillOnce(Return(true));
EXPECT_CALL(tpm_, SetOwnerPassword(_)).Times(1);
bool took_ownership = false;
EXPECT_TRUE(tpm_init_.TakeOwnership(&took_ownership));
EXPECT_TRUE(took_ownership);
EXPECT_TRUE(IsTpmOwned());
EXPECT_FALSE(IsTpmBeingOwned());
EXPECT_TRUE(FileExists(kTpmOwnedFile));
}
TEST_F(TpmInitTest, TakeOwnershipDistributedSuccess) {
// Set initial TPM states.
SetIsTpmOwned(false);
SetIsTpmInitialized(true);
SetIsTpmBeingOwned(false);
// Take Ownership.
EXPECT_CALL(tpm_, DoesUseTpmManager()).WillOnce(Return(true));
EXPECT_CALL(tpm_, IsEndorsementKeyAvailable())
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
EXPECT_CALL(tpm_, CreateEndorsementKey()).WillOnce(Return(true));
EXPECT_CALL(tpm_, TakeOwnership(_, _)).WillOnce(Return(true));
EXPECT_CALL(tpm_, TestTpmAuth(_)).Times(0);
EXPECT_CALL(tpm_, InitializeSrk(_)).Times(0);
EXPECT_CALL(tpm_, ChangeOwnerPassword(_, _)).Times(0);
EXPECT_CALL(tpm_, SetOwnerPassword(_)).Times(0);
bool took_ownership = false;
EXPECT_TRUE(tpm_init_.TakeOwnership(&took_ownership));
EXPECT_TRUE(took_ownership);
EXPECT_TRUE(IsTpmOwned());
EXPECT_FALSE(IsTpmBeingOwned());
EXPECT_TRUE(FileExists(kTpmOwnedFile));
}
TEST_F(TpmInitTest, RemoveTpmOwnerDependencySuccess) {
TpmStatus tpm_status;
tpm_status.set_flags(TpmStatus::ATTESTATION_NEEDS_OWNER |
TpmStatus::INSTALL_ATTRIBUTES_NEEDS_OWNER);
SetTpmStatus(tpm_status);
auto dependency = TpmPersistentState::TpmOwnerDependency::kAttestation;
EXPECT_CALL(tpm_, RemoveOwnerDependency(dependency)).WillOnce(Return(true));
EXPECT_CALL(platform_,
WriteSecureBlobToFileAtomicDurable(kTpmStatusFile, _, _))
.Times(1);
tpm_init_.RemoveTpmOwnerDependency(dependency);
GetTpmStatus(&tpm_status);
EXPECT_EQ(TpmStatus::INSTALL_ATTRIBUTES_NEEDS_OWNER, tpm_status.flags());
}
TEST_F(TpmInitTest, RemoveTpmOwnerDependencyAlreadyRemoved) {
TpmStatus tpm_status;
tpm_status.set_flags(TpmStatus::INSTALL_ATTRIBUTES_NEEDS_OWNER);
SetTpmStatus(tpm_status);
auto dependency = TpmPersistentState::TpmOwnerDependency::kAttestation;
EXPECT_CALL(tpm_, RemoveOwnerDependency(dependency)).WillOnce(Return(true));
EXPECT_CALL(platform_,
WriteSecureBlobToFileAtomicDurable(kTpmStatusFile, _, _))
.Times(0);
tpm_init_.RemoveTpmOwnerDependency(dependency);
GetTpmStatus(&tpm_status);
EXPECT_EQ(TpmStatus::INSTALL_ATTRIBUTES_NEEDS_OWNER, tpm_status.flags());
}
TEST_F(TpmInitTest, RemoveTpmOwnerDependencyTpmFailure) {
TpmStatus tpm_status;
tpm_status.set_flags(TpmStatus::ATTESTATION_NEEDS_OWNER);
SetTpmStatus(tpm_status);
auto dependency = TpmPersistentState::TpmOwnerDependency::kAttestation;
EXPECT_CALL(tpm_, RemoveOwnerDependency(dependency)).WillOnce(Return(false));
EXPECT_CALL(platform_,
WriteSecureBlobToFileAtomicDurable(kTpmStatusFile, _, _))
.Times(0);
tpm_init_.RemoveTpmOwnerDependency(dependency);
GetTpmStatus(&tpm_status);
EXPECT_EQ(TpmStatus::ATTESTATION_NEEDS_OWNER, tpm_status.flags());
}
TEST_F(TpmInitTest, RemoveTpmOwnerDependencyNoTpmStatus) {
auto dependency = TpmPersistentState::TpmOwnerDependency::kAttestation;
EXPECT_CALL(tpm_, RemoveOwnerDependency(dependency)).WillOnce(Return(true));
EXPECT_CALL(platform_,
WriteSecureBlobToFileAtomicDurable(kTpmStatusFile, _, _))
.Times(0);
tpm_init_.RemoveTpmOwnerDependency(dependency);
}
TEST_F(TpmInitTest, LoadCryptohomeKeySuccess) {
SetIsTpmInitialized(true);
FileTouch(kDefaultCryptohomeKeyFile);
EXPECT_CALL(tpm_, LoadWrappedKey(_, _))
.WillOnce(LoadWrappedKeyToHandle(kTestKeyHandle));
tpm_init_.SetupTpm(true);
EXPECT_THAT(&tpm_init_, HasLoadedCryptohomeKey(kTestKeyHandle));
}
TEST_F(TpmInitTest, LoadCryptohomeKeyTransientFailure) {
// Transient failure on the first attempt leads to key not being loaded.
// But the key is not re-created. Success on the second attempt loads the
// old key.
SetIsTpmInitialized(true);
FileWriteString(kDefaultCryptohomeKeyFile, "old-key");
EXPECT_CALL(tpm_, LoadWrappedKey(_, _))
.WillOnce(Return(Tpm::kTpmRetryCommFailure))
.WillOnce(LoadWrappedKeyToHandle(kTestKeyHandle));
EXPECT_CALL(tpm_, WrapRsaKey(_, _, _)).Times(0);
tpm_init_.SetupTpm(true);
EXPECT_THAT(&tpm_init_, HasNoLoadedCryptohomeKey());
tpm_init_.SetupTpm(true);
EXPECT_THAT(&tpm_init_, HasLoadedCryptohomeKey(kTestKeyHandle));
EXPECT_THAT(this, HasStoredCryptohomeKey("old-key"));
}
TEST_F(TpmInitTest, ReCreateCryptohomeKeyAfterLoadFailure) {
// Permanent failure while loading the key leads to re-creating, storing
// and loading the new key.
SetTpmReady();
SetIsTpmInitialized(true);
FileWriteString(kDefaultCryptohomeKeyFile, "old-key");
EXPECT_CALL(tpm_, LoadWrappedKey(_, _))
.WillOnce(Return(Tpm::kTpmRetryFailNoRetry))
.WillOnce(LoadWrappedKeyToHandle(kTestKeyHandle));
EXPECT_CALL(tpm_, WrapRsaKey(_, _, _))
.WillOnce(GenerateWrappedKey("new-key"));
tpm_init_.SetupTpm(true);
EXPECT_THAT(&tpm_init_, HasLoadedCryptohomeKey(kTestKeyHandle));
EXPECT_THAT(this, HasStoredCryptohomeKey("new-key"));
}
TEST_F(TpmInitTest, ReCreateCryptohomeKeyFailureDuringKeyCreation) {
// Permanent failure while loading the key leads to an attempt to re-create
// the key. Which fails. So, nothing new is stored or loaded.
SetTpmReady();
SetIsTpmInitialized(true);
FileWriteString(kDefaultCryptohomeKeyFile, "old-key");
EXPECT_CALL(tpm_, LoadWrappedKey(_, _))
.WillOnce(Return(Tpm::kTpmRetryFailNoRetry));
EXPECT_CALL(tpm_, WrapRsaKey(_, _, _)).WillOnce(Return(false));
tpm_init_.SetupTpm(true);
EXPECT_THAT(&tpm_init_, HasNoLoadedCryptohomeKey());
EXPECT_THAT(this, HasStoredCryptohomeKey("old-key"));
}
TEST_F(TpmInitTest, ReCreateCryptohomeKeyFailureDuringKeyLoading) {
// Permanent failure while loading the key leads to re-creating the key.
// It is stored. But then loading fails.
// Still, on the next attempt, the key is loaded, and not re-created again.
SetTpmReady();
SetIsTpmInitialized(true);
FileWriteString(kDefaultCryptohomeKeyFile, "old-key");
EXPECT_CALL(tpm_, LoadWrappedKey(_, _))
.WillOnce(Return(Tpm::kTpmRetryFailNoRetry))
.WillOnce(Return(Tpm::kTpmRetryFailNoRetry))
.WillOnce(LoadWrappedKeyToHandle(kTestKeyHandle));
EXPECT_CALL(tpm_, WrapRsaKey(_, _, _))
.WillOnce(GenerateWrappedKey("new-key"));
tpm_init_.SetupTpm(true);
EXPECT_THAT(&tpm_init_, HasNoLoadedCryptohomeKey());
EXPECT_THAT(this, HasStoredCryptohomeKey("new-key"));
tpm_init_.SetupTpm(true);
EXPECT_THAT(&tpm_init_, HasLoadedCryptohomeKey(kTestKeyHandle));
EXPECT_THAT(this, HasStoredCryptohomeKey("new-key"));
}
TEST_F(TpmInitTest, IsTpmReadyWithOwnedFile) {
FileTouch(kTpmOwnedFile);
SetIsTpmOwned(true);
SetIsTpmBeingOwned(false);
EXPECT_CALL(tpm_, DoesUseTpmManager()).WillOnce(Return(false));
EXPECT_TRUE(tpm_init_.IsTpmReady());
EXPECT_CALL(tpm_, DoesUseTpmManager()).WillOnce(Return(true));
EXPECT_TRUE(tpm_init_.IsTpmReady());
EXPECT_CALL(tpm_, DoesUseTpmManager()).Times(0);
SetIsTpmOwned(false);
EXPECT_FALSE(tpm_init_.IsTpmReady());
SetIsTpmOwned(true);
SetIsTpmBeingOwned(true);
EXPECT_FALSE(tpm_init_.IsTpmReady());
}
TEST_F(TpmInitTest, IsTpmReadyNoOwnedFile) {
FileDelete(kTpmOwnedFile);
SetIsTpmOwned(true);
SetIsTpmBeingOwned(false);
EXPECT_CALL(tpm_, DoesUseTpmManager()).WillOnce(Return(false));
EXPECT_FALSE(tpm_init_.IsTpmReady());
EXPECT_CALL(tpm_, DoesUseTpmManager()).WillOnce(Return(true));
EXPECT_TRUE(tpm_init_.IsTpmReady());
}
} // namespace cryptohome