blob: 8080fde412abcb437e7afa7c0f8528b0cf869d63 [file] [log] [blame]
// Copyright 2017 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 TpmPersistentState.
#include "cryptohome/tpm_persistent_state.h"
#include <map>
#include <base/files/file_path.h>
#include <brillo/secure_blob.h>
#include "cryptohome/mock_platform.h"
using brillo::SecureBlob;
using ::testing::_;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;
namespace cryptohome {
using TpmOwnerDependency = TpmPersistentState::TpmOwnerDependency;
extern const base::FilePath kTpmOwnedFile;
const base::FilePath kTpmStatusFile("/mnt/stateful_partition/.tpm_status");
const base::FilePath kShallInitializeFile(
"/home/.shadow/.can_attempt_ownership");
class TpmPersistentStateTest : public ::testing::Test {
public:
// 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 FileDelete(const base::FilePath& path) {
return files_.erase(path) == 1;
}
bool FileTouch(const base::FilePath& path) {
if (!FileExists(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 FileWrite(const base::FilePath& path, const brillo::Blob& blob) {
files_[path] = blob;
return true;
}
bool FileWriteSecureBlob(const base::FilePath& path,
const brillo::SecureBlob& sblob) {
return FileWrite(path, brillo::Blob(sblob.begin(), sblob.end()));
}
bool FileWriteAtomic(const base::FilePath& path,
const brillo::SecureBlob& blob,
mode_t /* mode */) {
return FileWriteSecureBlob(path, blob);
}
void SetUp() override {
ON_CALL(platform_, FileExists(_))
.WillByDefault(Invoke(this, &TpmPersistentStateTest::FileExists));
ON_CALL(platform_, DeleteFileDurable(_))
.WillByDefault(Invoke(this, &TpmPersistentStateTest::FileDelete));
ON_CALL(platform_, TouchFileDurable(_))
.WillByDefault(Invoke(this, &TpmPersistentStateTest::FileTouch));
ON_CALL(platform_, GetFileSize(_, _))
.WillByDefault(Invoke(this, &TpmPersistentStateTest::GetFileSize));
ON_CALL(platform_, ReadFile(_, _))
.WillByDefault(Invoke(this, &TpmPersistentStateTest::FileRead));
ON_CALL(platform_, WriteSecureBlobToFile(_, _))
.WillByDefault(
Invoke(this, &TpmPersistentStateTest::FileWriteSecureBlob));
ON_CALL(platform_, WriteSecureBlobToFileAtomicDurable(_, _, _))
.WillByDefault(Invoke(this, &TpmPersistentStateTest::FileWriteAtomic));
ON_CALL(platform_, DataSyncFile(_)).WillByDefault(Return(true));
}
protected:
std::map<base::FilePath, brillo::Blob> files_;
NiceMock<MockPlatform> platform_;
// Declare tpm_init_ last, so it gets destroyed before all the mocks.
TpmPersistentState tpm_persistent_state_{&platform_};
};
TEST_F(TpmPersistentStateTest, SetPassword) {
// Initially there's no password.
ASSERT_FALSE(FileExists(kTpmStatusFile));
SecureBlob result;
EXPECT_FALSE(tpm_persistent_state_.GetSealedPassword(&result));
// After setting the default password, we get back an empty password.
EXPECT_TRUE(tpm_persistent_state_.SetDefaultPassword());
EXPECT_TRUE(tpm_persistent_state_.GetSealedPassword(&result));
EXPECT_TRUE(result.empty());
// After setting some password, we get it back.
SecureBlob password("password");
EXPECT_TRUE(tpm_persistent_state_.SetSealedPassword(password));
EXPECT_TRUE(tpm_persistent_state_.GetSealedPassword(&result));
EXPECT_EQ(password, result);
// Clearing status clears the password.
EXPECT_TRUE(tpm_persistent_state_.ClearStatus());
EXPECT_FALSE(tpm_persistent_state_.GetSealedPassword(&result));
}
TEST_F(TpmPersistentStateTest, SetDependencies) {
// Initially, there's no password, no dependencies, so clearing suceeds.
ASSERT_FALSE(FileExists(kTpmStatusFile));
EXPECT_TRUE(tpm_persistent_state_.ClearStoredPasswordIfNotNeeded());
// Setting the default password should also set both dependencies to on.
EXPECT_TRUE(tpm_persistent_state_.SetDefaultPassword());
EXPECT_FALSE(tpm_persistent_state_.ClearStoredPasswordIfNotNeeded());
// Clearing the state after setting the password should allow clearing
// the password (which is already clear).
EXPECT_TRUE(tpm_persistent_state_.ClearStatus());
EXPECT_TRUE(tpm_persistent_state_.ClearStoredPasswordIfNotNeeded());
// Setting any password should also set both dependencies to on.
SecureBlob password("password");
EXPECT_TRUE(tpm_persistent_state_.SetSealedPassword(password));
EXPECT_FALSE(tpm_persistent_state_.ClearStoredPasswordIfNotNeeded());
// Clearing one dependency is not sufficient for clearing the password.
EXPECT_TRUE(
tpm_persistent_state_.ClearDependency(TpmOwnerDependency::kAttestation));
EXPECT_FALSE(tpm_persistent_state_.ClearStoredPasswordIfNotNeeded());
SecureBlob result;
EXPECT_TRUE(tpm_persistent_state_.GetSealedPassword(&result));
EXPECT_EQ(password, result);
// Clearing both dependencies should allow clearing the password.
EXPECT_TRUE(tpm_persistent_state_.ClearDependency(
TpmOwnerDependency::kInstallAttributes));
EXPECT_TRUE(tpm_persistent_state_.ClearStoredPasswordIfNotNeeded());
EXPECT_FALSE(tpm_persistent_state_.GetSealedPassword(&result));
// Clearing the state after setting the password should allow clearing
// the password (which is already clear).
EXPECT_TRUE(tpm_persistent_state_.ClearStatus());
EXPECT_TRUE(tpm_persistent_state_.ClearStoredPasswordIfNotNeeded());
}
TEST_F(TpmPersistentStateTest, TpmStatusPreExisting) {
SecureBlob password("password");
TpmStatus status;
status.set_flags(TpmStatus::OWNED_BY_THIS_INSTALL |
TpmStatus::USES_RANDOM_OWNER |
TpmStatus::ATTESTATION_NEEDS_OWNER);
status.set_owner_password(password.to_string());
brillo::Blob status_blob = brillo::BlobFromString(status.SerializeAsString());
FileWrite(kTpmStatusFile, status_blob);
SecureBlob result;
EXPECT_TRUE(tpm_persistent_state_.GetSealedPassword(&result));
EXPECT_EQ(password, result);
EXPECT_FALSE(tpm_persistent_state_.ClearStoredPasswordIfNotNeeded());
EXPECT_TRUE(
tpm_persistent_state_.ClearDependency(TpmOwnerDependency::kAttestation));
EXPECT_TRUE(tpm_persistent_state_.ClearStoredPasswordIfNotNeeded());
}
TEST_F(TpmPersistentStateTest, TpmStatusCached) {
// The TpmStatus file is read only once.
TpmStatus empty_status;
empty_status.set_flags(TpmStatus::NONE);
brillo::Blob empty_status_blob =
brillo::BlobFromString(empty_status.SerializeAsString());
FileWrite(kTpmStatusFile, empty_status_blob);
EXPECT_CALL(platform_, ReadFile(kTpmStatusFile, _)).Times(1);
SecureBlob password("password");
EXPECT_TRUE(tpm_persistent_state_.SetSealedPassword(password));
SecureBlob result;
EXPECT_TRUE(tpm_persistent_state_.GetSealedPassword(&result));
EXPECT_EQ(password, result);
EXPECT_TRUE(tpm_persistent_state_.GetSealedPassword(&result));
EXPECT_EQ(password, result);
// Each change to state leads to write.
EXPECT_CALL(platform_,
WriteSecureBlobToFileAtomicDurable(kTpmStatusFile, _, _))
.Times(2);
EXPECT_TRUE(tpm_persistent_state_.SetSealedPassword(password));
EXPECT_TRUE(tpm_persistent_state_.ClearDependency(
TpmOwnerDependency::kInstallAttributes));
// Clearing the status leads to deleting the file.
EXPECT_TRUE(tpm_persistent_state_.ClearStatus());
EXPECT_FALSE(FileExists(kTpmStatusFile));
}
TEST_F(TpmPersistentStateTest, TpmReady) {
// The file is read only once, after that the flag is cached in memory.
ASSERT_FALSE(FileExists(kTpmOwnedFile));
EXPECT_CALL(platform_, FileExists(kTpmOwnedFile)).Times(1);
// Initially, there's no file, so tpm is not ready.
EXPECT_FALSE(tpm_persistent_state_.IsReady());
EXPECT_FALSE(tpm_persistent_state_.IsReady());
// Saying that it's ready creates the file and returns correct status
// afterwards.
EXPECT_TRUE(tpm_persistent_state_.SetReady(true));
EXPECT_TRUE(tpm_persistent_state_.IsReady());
EXPECT_TRUE(FileExists(kTpmOwnedFile));
// Setting the flag back to false...
EXPECT_TRUE(tpm_persistent_state_.SetReady(false));
EXPECT_FALSE(tpm_persistent_state_.IsReady());
EXPECT_FALSE(FileExists(kTpmOwnedFile));
}
TEST_F(TpmPersistentStateTest, TpmReadyPreExisting) {
// If there's a kTpmOwnedFile at start, IsReady returns true.
FileTouch(kTpmOwnedFile);
EXPECT_CALL(platform_, FileExists(kTpmOwnedFile)).Times(1);
EXPECT_TRUE(tpm_persistent_state_.IsReady());
EXPECT_TRUE(tpm_persistent_state_.IsReady());
}
TEST_F(TpmPersistentStateTest, ShallInitialize) {
// Two requests result in a single file operation, after that it is cached.
EXPECT_FALSE(FileExists(kShallInitializeFile));
EXPECT_CALL(platform_, FileExists(_)).Times(1);
EXPECT_FALSE(tpm_persistent_state_.ShallInitialize());
EXPECT_FALSE(tpm_persistent_state_.ShallInitialize());
// Two identical calls to SetShallInitialize result in one file operation.
// FileExists() is not called again.
EXPECT_CALL(platform_, TouchFileDurable(_)).Times(1);
EXPECT_TRUE(tpm_persistent_state_.SetShallInitialize(true));
EXPECT_TRUE(FileExists(kShallInitializeFile));
EXPECT_TRUE(tpm_persistent_state_.ShallInitialize());
EXPECT_TRUE(tpm_persistent_state_.SetShallInitialize(true));
EXPECT_TRUE(tpm_persistent_state_.ShallInitialize());
// Two identical calls to SetShallInitialize result in one file operation.
// FileExists() is not called again.
EXPECT_CALL(platform_, DeleteFileDurable(_)).Times(1);
EXPECT_TRUE(tpm_persistent_state_.SetShallInitialize(false));
EXPECT_FALSE(FileExists(kShallInitializeFile));
EXPECT_FALSE(tpm_persistent_state_.ShallInitialize());
EXPECT_TRUE(tpm_persistent_state_.SetShallInitialize(false));
EXPECT_FALSE(tpm_persistent_state_.ShallInitialize());
}
} // namespace cryptohome