| // Copyright (c) 2012 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 InstallAttributes. |
| |
| #include "cryptohome/install_attributes.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include <algorithm> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <brillo/secure_blob.h> |
| #include <gtest/gtest.h> |
| |
| #include "cryptohome/lockbox.h" |
| #include "cryptohome/mock_lockbox.h" |
| #include "cryptohome/mock_platform.h" |
| #include "cryptohome/mock_tpm.h" |
| #include "cryptohome/mock_tpm_init.h" |
| |
| using base::FilePath; |
| using ::testing::_; |
| using ::testing::DoAll; |
| using ::testing::Mock; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::SetArgPointee; |
| |
| namespace cryptohome { |
| |
| namespace { |
| static constexpr char kTestName[] = "Shuffle"; |
| static constexpr char kTestData[] = "Duffle"; |
| } // namespace |
| |
| // Provides a test fixture for ensuring Lockbox-flows work as expected. |
| // |
| // Multiple helpers are included to ensure tests are starting from the same |
| // baseline for difference scenarios, such as first boot or all-other-normal |
| // boots. |
| class InstallAttributesTest : public ::testing::Test { |
| public: |
| InstallAttributesTest() : install_attrs_(nullptr) {} |
| ~InstallAttributesTest() override = default; |
| |
| void SetUp() override { |
| ON_CALL(tpm_init_, IsTpmReady()).WillByDefault(Return(true)); |
| ON_CALL(tpm_init_, IsTpmEnabled()).WillByDefault(Return(true)); |
| ON_CALL(tpm_init_, IsTpmOwned()).WillByDefault(Return(true)); |
| |
| install_attrs_.set_lockbox(&lockbox_); |
| install_attrs_.set_platform(&platform_); |
| // No pre-existing data and no TPM auth. |
| EXPECT_CALL(lockbox_, set_tpm(&tpm_)).Times(1); |
| EXPECT_CALL(tpm_, IsEnabled()).WillOnce(Return(true)); |
| install_attrs_.SetTpm(&tpm_); |
| Mock::VerifyAndClearExpectations(&lockbox_); |
| Mock::VerifyAndClearExpectations(&tpm_); |
| } |
| |
| void GetAndCheck() { |
| EXPECT_EQ(1, install_attrs_.Count()); |
| brillo::Blob data; |
| EXPECT_TRUE(install_attrs_.Get(kTestName, &data)); |
| std::string data_str(reinterpret_cast<const char*>(data.data()), |
| data.size()); |
| EXPECT_STREQ(data_str.c_str(), kTestData); |
| } |
| |
| // Generate the data we'll need to load from. |
| brillo::Blob GenerateTestDataFileContents() { |
| brillo::Blob data; |
| SerializedInstallAttributes proto; |
| proto.set_version(proto.version()); |
| SerializedInstallAttributes::Attribute* attr = proto.add_attributes(); |
| attr->set_name(kTestName); |
| attr->set_value(std::string(reinterpret_cast<const char*>(kTestData), |
| strlen(kTestData))); |
| data.resize(proto.ByteSize()); |
| CHECK(proto.SerializeWithCachedSizesToArray(data.data())); |
| return data; |
| } |
| |
| void ExpectRemovingOwnerDependency() { |
| EXPECT_CALL(tpm_init_, |
| RemoveTpmOwnerDependency( |
| TpmPersistentState::TpmOwnerDependency::kInstallAttributes)) |
| .Times(1); |
| } |
| |
| void ExpectNotRemovingOwnerDependency() { |
| EXPECT_CALL(tpm_init_, RemoveTpmOwnerDependency(_)).Times(0); |
| } |
| |
| NiceMock<MockLockbox> lockbox_; |
| brillo::Blob lockbox_data_; |
| InstallAttributes install_attrs_; |
| NiceMock<MockPlatform> platform_; |
| NiceMock<MockTpm> tpm_; |
| NiceMock<MockTpmInit> tpm_init_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(InstallAttributesTest); |
| }; |
| |
| TEST_F(InstallAttributesTest, OobeWithTpm) { |
| EXPECT_EQ(InstallAttributes::Status::kUnknown, install_attrs_.status()); |
| EXPECT_TRUE(install_attrs_.is_secure()); |
| |
| // The first Init() call finds no data file and an unowned TPM. |
| EXPECT_CALL(platform_, |
| ReadFile(FilePath(InstallAttributes::kDefaultCacheFile), _)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(tpm_init_, IsTpmReady()).WillRepeatedly(Return(false)); |
| EXPECT_CALL(tpm_init_, IsTpmOwned()).WillRepeatedly(Return(false)); |
| EXPECT_FALSE(install_attrs_.Init(&tpm_init_)); |
| Mock::VerifyAndClearExpectations(&tpm_init_); |
| Mock::VerifyAndClearExpectations(&platform_); |
| EXPECT_EQ(InstallAttributes::Status::kTpmNotOwned, install_attrs_.status()); |
| |
| // After taking ownership, TPM is ready and Init creates the lockbox. |
| EXPECT_CALL(platform_, |
| ReadFile(FilePath(InstallAttributes::kDefaultCacheFile), _)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(lockbox_, Reset(_)).WillOnce(Return(true)); |
| ExpectRemovingOwnerDependency(); |
| EXPECT_TRUE(install_attrs_.Init(&tpm_init_)); |
| Mock::VerifyAndClearExpectations(&lockbox_); |
| Mock::VerifyAndClearExpectations(&platform_); |
| EXPECT_EQ(InstallAttributes::Status::kFirstInstall, install_attrs_.status()); |
| |
| // Set the test attribute. |
| brillo::Blob data(kTestData, kTestData + sizeof(kTestData)); |
| data.assign(kTestData, kTestData + strlen(kTestData)); |
| EXPECT_TRUE(install_attrs_.Set(kTestName, data)); |
| |
| // Finalize. |
| EXPECT_CALL(lockbox_, Store(_, _)).WillOnce(Return(true)); |
| brillo::Blob serialized_data; |
| EXPECT_CALL(platform_, |
| WriteFileAtomicDurable( |
| FilePath(InstallAttributes::kDefaultDataFile), _, _)) |
| .WillOnce(DoAll(SaveArg<1>(&serialized_data), Return(true))); |
| brillo::Blob cached_data; |
| EXPECT_CALL( |
| platform_, |
| WriteFileAtomic(FilePath(InstallAttributes::kDefaultCacheFile), _, _)) |
| .WillOnce(DoAll(SaveArg<1>(&cached_data), Return(true))); |
| |
| EXPECT_TRUE(install_attrs_.Finalize()); |
| Mock::VerifyAndClearExpectations(&lockbox_); |
| Mock::VerifyAndClearExpectations(&platform_); |
| EXPECT_EQ(InstallAttributes::Status::kValid, install_attrs_.status()); |
| |
| brillo::Blob expected_data = GenerateTestDataFileContents(); |
| EXPECT_EQ(expected_data, serialized_data); |
| EXPECT_EQ(expected_data, cached_data); |
| } |
| |
| TEST_F(InstallAttributesTest, OobeWithoutTpm) { |
| EXPECT_CALL(lockbox_, set_tpm(nullptr)).Times(1); |
| install_attrs_.SetTpm(nullptr); |
| |
| EXPECT_EQ(InstallAttributes::Status::kUnknown, install_attrs_.status()); |
| EXPECT_FALSE(install_attrs_.is_secure()); |
| |
| EXPECT_CALL(platform_, |
| ReadFile(FilePath(InstallAttributes::kDefaultCacheFile), _)) |
| .WillOnce(Return(false)); |
| ExpectNotRemovingOwnerDependency(); |
| |
| EXPECT_TRUE(install_attrs_.Init(&tpm_init_)); |
| |
| EXPECT_EQ(InstallAttributes::Status::kFirstInstall, install_attrs_.status()); |
| } |
| |
| TEST_F(InstallAttributesTest, OobeWithTpmBadWrite) { |
| EXPECT_EQ(InstallAttributes::Status::kUnknown, install_attrs_.status()); |
| EXPECT_TRUE(install_attrs_.is_secure()); |
| |
| // Assume authorization and working tpm. |
| EXPECT_CALL(lockbox_, tpm()).WillRepeatedly(Return(&tpm_)); |
| EXPECT_CALL(lockbox_, Reset(_)).WillOnce(Return(true)); |
| ExpectRemovingOwnerDependency(); |
| |
| EXPECT_TRUE(install_attrs_.Init(&tpm_init_)); |
| Mock::VerifyAndClearExpectations(&lockbox_); |
| |
| brillo::Blob data; |
| data.assign(kTestData, kTestData + strlen(kTestData)); |
| EXPECT_TRUE(install_attrs_.Set(kTestName, data)); |
| |
| EXPECT_CALL(lockbox_, Store(_, _)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, WriteFileAtomicDurable(_, _, _)) |
| .WillOnce(Return(false)); |
| |
| EXPECT_FALSE(install_attrs_.Finalize()); |
| |
| EXPECT_EQ(InstallAttributes::Status::kInvalid, install_attrs_.status()); |
| } |
| |
| TEST_F(InstallAttributesTest, NormalBootWithTpm) { |
| EXPECT_EQ(InstallAttributes::Status::kUnknown, install_attrs_.status()); |
| EXPECT_TRUE(install_attrs_.is_secure()); |
| |
| brillo::Blob serialized_data = GenerateTestDataFileContents(); |
| EXPECT_CALL(platform_, |
| ReadFile(FilePath(InstallAttributes::kDefaultCacheFile), _)) |
| .WillOnce(DoAll(SetArgPointee<1>(serialized_data), Return(true))); |
| ExpectRemovingOwnerDependency(); |
| |
| EXPECT_TRUE(install_attrs_.Init(&tpm_init_)); |
| |
| EXPECT_EQ(InstallAttributes::Status::kValid, install_attrs_.status()); |
| |
| // Make sure the data was parsed correctly. |
| GetAndCheck(); |
| } |
| |
| TEST_F(InstallAttributesTest, NormalBootWithoutTpm) { |
| EXPECT_CALL(lockbox_, set_tpm(nullptr)).Times(1); |
| install_attrs_.SetTpm(nullptr); |
| |
| EXPECT_EQ(InstallAttributes::Status::kUnknown, install_attrs_.status()); |
| EXPECT_FALSE(install_attrs_.is_secure()); |
| |
| brillo::Blob serialized_data = GenerateTestDataFileContents(); |
| EXPECT_CALL(platform_, |
| ReadFile(FilePath(InstallAttributes::kDefaultCacheFile), _)) |
| .WillOnce(DoAll(SetArgPointee<1>(serialized_data), Return(true))); |
| ExpectRemovingOwnerDependency(); |
| |
| EXPECT_TRUE(install_attrs_.Init(&tpm_init_)); |
| |
| EXPECT_EQ(InstallAttributes::Status::kValid, install_attrs_.status()); |
| |
| // Make sure the data was parsed correctly. |
| GetAndCheck(); |
| } |
| |
| // Represents that the OOBE process was interrupted by a reboot or crash prior |
| // to Finalize() being called, but after the Lockbox was reset. |
| // Since InstallAttributes Set/Finalize is not atomic, there is always the risk |
| // of data loss due to failure of the device. It will fail-safe however (by |
| // failing empty). |
| TEST_F(InstallAttributesTest, NormalBootUnlocked) { |
| EXPECT_EQ(InstallAttributes::Status::kUnknown, install_attrs_.status()); |
| EXPECT_TRUE(install_attrs_.is_secure()); |
| |
| EXPECT_CALL(platform_, |
| ReadFile(FilePath(InstallAttributes::kDefaultCacheFile), _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(lockbox_, Reset(_)).WillOnce(Return(true)); |
| ExpectRemovingOwnerDependency(); |
| |
| EXPECT_TRUE(install_attrs_.Init(&tpm_init_)); |
| |
| EXPECT_EQ(InstallAttributes::Status::kFirstInstall, install_attrs_.status()); |
| EXPECT_EQ(0, install_attrs_.Count()); |
| } |
| |
| // Represents that the OOBE process was interrupted by a reboot or crash prior |
| // to Finalize() being called, and before the Lockbox was Created. |
| TEST_F(InstallAttributesTest, NormalBootNoSpace) { |
| EXPECT_EQ(InstallAttributes::Status::kUnknown, install_attrs_.status()); |
| EXPECT_TRUE(install_attrs_.is_secure()); |
| |
| EXPECT_CALL(lockbox_, Reset(_)).WillOnce(Return(true)); |
| ExpectRemovingOwnerDependency(); |
| |
| EXPECT_TRUE(install_attrs_.Init(&tpm_init_)); |
| |
| EXPECT_EQ(InstallAttributes::Status::kFirstInstall, install_attrs_.status()); |
| EXPECT_EQ(0, install_attrs_.Count()); |
| } |
| |
| TEST_F(InstallAttributesTest, NormalBootReadFileError) { |
| EXPECT_EQ(InstallAttributes::Status::kUnknown, install_attrs_.status()); |
| EXPECT_TRUE(install_attrs_.is_secure()); |
| |
| EXPECT_CALL(platform_, |
| ReadFile(FilePath(InstallAttributes::kDefaultCacheFile), _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(lockbox_, Reset(_)) |
| .WillOnce( |
| DoAll(SetArgPointee<0>(LockboxError::kNvramInvalid), Return(false))); |
| ExpectNotRemovingOwnerDependency(); |
| EXPECT_CALL(platform_, DeleteFile(_, _)).Times(0); |
| |
| EXPECT_FALSE(install_attrs_.Init(&tpm_init_)); |
| |
| EXPECT_EQ(InstallAttributes::Status::kInvalid, install_attrs_.status()); |
| EXPECT_EQ(0, install_attrs_.Count()); |
| } |
| |
| TEST_F(InstallAttributesTest, LegacyBoot) { |
| EXPECT_EQ(InstallAttributes::Status::kUnknown, install_attrs_.status()); |
| EXPECT_TRUE(install_attrs_.is_secure()); |
| |
| EXPECT_CALL(platform_, |
| ReadFile(FilePath(InstallAttributes::kDefaultCacheFile), _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(lockbox_, Reset(_)) |
| .WillOnce(DoAll(SetArgPointee<0>(LockboxError::kNvramSpaceAbsent), |
| Return(false))); |
| ExpectRemovingOwnerDependency(); |
| |
| EXPECT_TRUE(install_attrs_.Init(&tpm_init_)); |
| |
| EXPECT_EQ(InstallAttributes::Status::kValid, install_attrs_.status()); |
| EXPECT_EQ(0, install_attrs_.Count()); |
| } |
| |
| // If the Lockbox Reset fails for reasons other than bad password, it should |
| // still be treated as if locked without any attributes set. |
| TEST_F(InstallAttributesTest, LegacyBootUnexpected) { |
| EXPECT_EQ(InstallAttributes::Status::kUnknown, install_attrs_.status()); |
| EXPECT_TRUE(install_attrs_.is_secure()); |
| |
| EXPECT_CALL(platform_, |
| ReadFile(FilePath(InstallAttributes::kDefaultCacheFile), _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(lockbox_, Reset(_)) |
| .WillOnce( |
| DoAll(SetArgPointee<0>(LockboxError::kTpmError), Return(false))); |
| |
| EXPECT_FALSE(install_attrs_.Init(&tpm_init_)); |
| |
| EXPECT_EQ(InstallAttributes::Status::kInvalid, install_attrs_.status()); |
| EXPECT_EQ(0, install_attrs_.Count()); |
| } |
| |
| // If initializing with an unowned TPM, the old data file should be deleted to |
| // make sure that we don't accidentally pick it up as valid after taking |
| // ownership. |
| TEST_F(InstallAttributesTest, ClearPreviousDataFile) { |
| EXPECT_EQ(InstallAttributes::Status::kUnknown, install_attrs_.status()); |
| EXPECT_TRUE(install_attrs_.is_secure()); |
| |
| EXPECT_CALL(tpm_init_, IsTpmReady()).WillRepeatedly(Return(false)); |
| EXPECT_CALL(tpm_init_, IsTpmOwned()).WillRepeatedly(Return(false)); |
| |
| // The cache file isn't present because lockbox-cache won't receive a dump of |
| // the lockbox space if the TPM isn't owned. |
| EXPECT_CALL(platform_, |
| ReadFile(FilePath(InstallAttributes::kDefaultCacheFile), _)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, |
| FileExists(FilePath(InstallAttributes::kDefaultDataFile))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| DeleteFile(FilePath(InstallAttributes::kDefaultDataFile), _)) |
| .WillOnce(Return(true)); |
| |
| EXPECT_FALSE(install_attrs_.Init(&tpm_init_)); |
| |
| EXPECT_EQ(InstallAttributes::Status::kTpmNotOwned, install_attrs_.status()); |
| EXPECT_EQ(0, install_attrs_.Count()); |
| } |
| |
| // Check that if the TPM is out for lunch and inoperable in this boot cycle, we |
| // do keep around the data file as to not irrevocably invalidate install |
| // attributes should the TPM start functioning again after reboot. |
| TEST_F(InstallAttributesTest, KeepDataFileOnTpmFailure) { |
| EXPECT_EQ(InstallAttributes::Status::kUnknown, install_attrs_.status()); |
| EXPECT_TRUE(install_attrs_.is_secure()); |
| |
| EXPECT_CALL(tpm_init_, IsTpmReady()).WillRepeatedly(Return(false)); |
| EXPECT_CALL(tpm_init_, IsTpmEnabled()).WillRepeatedly(Return(false)); |
| EXPECT_CALL(tpm_init_, IsTpmOwned()).WillRepeatedly(Return(false)); |
| |
| // The cache file isn't present because lockbox-cache won't receive a dump of |
| // the lockbox space if the TPM isn't owned. |
| EXPECT_CALL(platform_, |
| ReadFile(FilePath(InstallAttributes::kDefaultCacheFile), _)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(platform_, |
| FileExists(FilePath(InstallAttributes::kDefaultDataFile))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, |
| DeleteFile(FilePath(InstallAttributes::kDefaultDataFile), _)) |
| .Times(0); |
| |
| EXPECT_FALSE(install_attrs_.Init(&tpm_init_)); |
| |
| EXPECT_EQ(InstallAttributes::Status::kInvalid, install_attrs_.status()); |
| EXPECT_EQ(0, install_attrs_.Count()); |
| } |
| |
| } // namespace cryptohome |