| // Copyright (c) 2013 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 Lockbox. |
| |
| #include "cryptohome/lockbox.h" |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <brillo/process_mock.h> |
| #include <brillo/secure_blob.h> |
| #include <gtest/gtest.h> |
| |
| #include "cryptohome/cryptolib.h" |
| #include "cryptohome/mock_lockbox.h" |
| #include "cryptohome/mock_platform.h" |
| #include "cryptohome/mock_tpm.h" |
| |
| namespace cryptohome { |
| using brillo::SecureBlob; |
| using ::testing::_; |
| using ::testing::DoAll; |
| using ::testing::Eq; |
| using ::testing::InSequence; |
| using ::testing::Mock; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::SetArgPointee; |
| |
| // 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 LockboxTest : public ::testing::TestWithParam<Tpm::TpmVersion> { |
| public: |
| LockboxTest() : lockbox_(nullptr, 0xdeadbeef) {} |
| ~LockboxTest() override = default; |
| |
| void SetUp() override { |
| ON_CALL(tpm_, GetVersion()).WillByDefault(Return(GetParam())); |
| |
| // Create the OOBE data to reuse for post-boot tests. |
| // This generates the expected NVRAM value and serialized file data. |
| file_data_.assign(kFileData, kFileData + strlen(kFileData)); |
| lockbox_.set_tpm(&tpm_); |
| lockbox_.set_process(&process_); |
| } |
| |
| |
| uint32_t GetExpectedNvramSpaceFlags() { |
| switch (GetParam()) { |
| case Tpm::TpmVersion::TPM_1_2: |
| return Tpm::kTpmNvramWriteDefine | Tpm::kTpmNvramBindToPCR0; |
| case Tpm::TpmVersion::TPM_2_0: |
| return Tpm::kTpmNvramWriteDefine; |
| case Tpm::TpmVersion::TPM_UNKNOWN: |
| break; |
| } |
| NOTREACHED(); |
| return 0; |
| } |
| |
| static const char* kFileData; |
| Lockbox lockbox_; |
| NiceMock<MockTpm> tpm_; |
| NiceMock<brillo::ProcessMock> process_; |
| brillo::Blob file_data_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(LockboxTest); |
| }; |
| |
| const char* LockboxTest::kFileData = "42"; |
| |
| TEST_P(LockboxTest, ResetTpmUnavailable) { |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(false)); |
| |
| LockboxError error; |
| EXPECT_FALSE(lockbox_.Reset(&error)); |
| EXPECT_EQ(error, LockboxError::kTpmUnavailable); |
| } |
| |
| TEST_P(LockboxTest, ResetNoNvramSpace) { |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsNvramDefined(0xdeadbeef)).WillOnce(Return(false)); |
| |
| LockboxError error; |
| EXPECT_FALSE(lockbox_.Reset(&error)); |
| EXPECT_EQ(error, LockboxError::kNvramSpaceAbsent); |
| } |
| |
| TEST_P(LockboxTest, ResetExistingSpaceUnlocked) { |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsNvramDefined(0xdeadbeef)).WillOnce(Return(true)); |
| EXPECT_CALL(tpm_, IsNvramLocked(0xdeadbeef)).WillOnce(Return(false)); |
| |
| LockboxError error; |
| EXPECT_TRUE(lockbox_.Reset(&error)); |
| } |
| |
| TEST_P(LockboxTest, ResetExistingSpaceLocked) { |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsNvramDefined(0xdeadbeef)).WillOnce(Return(true)); |
| EXPECT_CALL(tpm_, IsNvramLocked(0xdeadbeef)).WillOnce(Return(true)); |
| |
| LockboxError error; |
| EXPECT_FALSE(lockbox_.Reset(&error)); |
| EXPECT_EQ(error, LockboxError::kNvramInvalid); |
| } |
| |
| TEST_P(LockboxTest, ResetCreateSpace) { |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| |
| // Make the owner password available. |
| static const char* kOwnerPassword = "sup"; |
| brillo::SecureBlob pw; |
| pw.assign(kOwnerPassword, kOwnerPassword + strlen(kOwnerPassword)); |
| EXPECT_CALL(tpm_, GetOwnerPassword(_)) |
| .WillRepeatedly(DoAll(SetArgPointee<0>(pw), Return(true))); |
| |
| // No pre-existing space. |
| EXPECT_CALL(tpm_, IsNvramDefined(0xdeadbeef)).WillOnce(Return(false)); |
| |
| // Create the new space. |
| EXPECT_CALL(tpm_, |
| DefineNvram(0xdeadbeef, |
| LockboxContents::GetNvramSize(NvramVersion::kDefault), |
| GetExpectedNvramSpaceFlags())) |
| .WillOnce(Return(true)); |
| |
| LockboxError error; |
| EXPECT_TRUE(lockbox_.Reset(&error)); |
| } |
| |
| TEST_P(LockboxTest, ResetCreateSpacePreexisting) { |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| |
| // Make the owner password available. |
| static const char* kOwnerPassword = "sup"; |
| brillo::SecureBlob pw; |
| pw.assign(kOwnerPassword, kOwnerPassword + strlen(kOwnerPassword)); |
| EXPECT_CALL(tpm_, GetOwnerPassword(_)) |
| .WillRepeatedly(DoAll(SetArgPointee<0>(pw), Return(true))); |
| |
| // Pre-existing space, expect the space to get destroyed. |
| EXPECT_CALL(tpm_, IsNvramDefined(0xdeadbeef)).WillOnce(Return(true)); |
| EXPECT_CALL(tpm_, DestroyNvram(0xdeadbeef)).WillOnce(Return(true)); |
| |
| // Create the new space. |
| EXPECT_CALL(tpm_, |
| DefineNvram(0xdeadbeef, |
| LockboxContents::GetNvramSize(NvramVersion::kDefault), |
| GetExpectedNvramSpaceFlags())) |
| .WillOnce(Return(true)); |
| |
| LockboxError error; |
| EXPECT_TRUE(lockbox_.Reset(&error)); |
| } |
| |
| TEST_P(LockboxTest, ResetNoOwnerAuth) { |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, GetOwnerPassword(_)).WillRepeatedly(Return(false)); |
| |
| LockboxError error; |
| EXPECT_FALSE(lockbox_.Reset(&error)); |
| EXPECT_EQ(error, LockboxError::kNvramSpaceAbsent); |
| } |
| |
| TEST_P(LockboxTest, StoreOk) { |
| brillo::SecureBlob key_material; |
| switch (GetParam()) { |
| case Tpm::TpmVersion::TPM_1_2: |
| key_material.assign(static_cast<size_t>(NvramVersion::kDefault), 'A'); |
| break; |
| case Tpm::TpmVersion::TPM_2_0: |
| key_material.assign(static_cast<size_t>(NvramVersion::kDefault), 0); |
| break; |
| case Tpm::TpmVersion::TPM_UNKNOWN: |
| NOTREACHED(); |
| break; |
| } |
| |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| |
| // Destroy calls with no file or existing NVRAM space. |
| EXPECT_CALL(tpm_, IsNvramDefined(0xdeadbeef)).WillOnce(Return(true)); |
| { |
| InSequence s; |
| EXPECT_CALL(tpm_, IsNvramLocked(0xdeadbeef)).WillOnce(Return(false)); |
| EXPECT_CALL(tpm_, GetNvramSize(0xdeadbeef)) |
| .WillOnce( |
| Return(LockboxContents::GetNvramSize(NvramVersion::kDefault))); |
| EXPECT_CALL(tpm_, GetRandomDataSecureBlob(key_material.size(), _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(key_material), Return(true))); |
| EXPECT_CALL(tpm_, WriteNvram(0xdeadbeef, _)).WillOnce(Return(true)); |
| EXPECT_CALL(tpm_, WriteLockNvram(0xdeadbeef)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsNvramLocked(0xdeadbeef)).WillOnce(Return(true)); |
| } |
| EXPECT_CALL(process_, Reset(0)).Times(1); |
| EXPECT_CALL(process_, AddArg("/usr/sbin/mount-encrypted")).Times(1); |
| EXPECT_CALL(process_, AddArg("finalize")).Times(1); |
| EXPECT_CALL(process_, |
| AddArg(CryptoLib::SecureBlobToHex( |
| CryptoLib::Sha256(key_material)))) |
| .Times(1); |
| EXPECT_CALL(process_, BindFd(_, 1)).Times(1); |
| EXPECT_CALL(process_, BindFd(_, 2)).Times(1); |
| EXPECT_CALL(process_, Run()).WillOnce(Return(0)); |
| |
| LockboxError error; |
| EXPECT_TRUE(lockbox_.Store(file_data_, &error)); |
| } |
| |
| TEST_P(LockboxTest, StoreLockedNvram) { |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsNvramDefined(0xdeadbeef)).WillOnce(Return(true)); |
| EXPECT_CALL(tpm_, IsNvramLocked(0xdeadbeef)).WillOnce(Return(true)); |
| |
| LockboxError error; |
| EXPECT_FALSE(lockbox_.Store(file_data_, &error)); |
| EXPECT_EQ(error, LockboxError::kNvramInvalid); |
| } |
| |
| TEST_P(LockboxTest, StoreUnlockedNvramSizeBad) { |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(tpm_, IsNvramDefined(0xdeadbeef)).WillOnce(Return(true)); |
| EXPECT_CALL(tpm_, IsNvramLocked(0xdeadbeef)).WillOnce(Return(false)); |
| EXPECT_CALL(tpm_, GetNvramSize(0xdeadbeef)).WillOnce(Return(0)); |
| |
| LockboxError error; |
| EXPECT_FALSE(lockbox_.Store(file_data_, &error)); |
| EXPECT_EQ(error, LockboxError::kNvramInvalid); |
| } |
| |
| TEST_P(LockboxTest, StoreNoNvram) { |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| |
| EXPECT_CALL(tpm_, IsNvramDefined(0xdeadbeef)).WillOnce(Return(false)); |
| |
| LockboxError error; |
| EXPECT_FALSE(lockbox_.Store(file_data_, &error)); |
| EXPECT_EQ(error, LockboxError::kNvramInvalid); |
| } |
| |
| TEST_P(LockboxTest, StoreTpmNotReady) { |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(false)); |
| |
| LockboxError error; |
| EXPECT_FALSE(lockbox_.Store(file_data_, &error)); |
| EXPECT_EQ(error, LockboxError::kNvramInvalid); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(LockboxTestTpm12, |
| LockboxTest, |
| testing::Values(Tpm::TpmVersion::TPM_1_2)); |
| INSTANTIATE_TEST_CASE_P(LockboxTestTpm20, |
| LockboxTest, |
| testing::Values(Tpm::TpmVersion::TPM_2_0)); |
| |
| class LockboxContentsTest : public testing::Test { |
| public: |
| LockboxContentsTest() = default; |
| |
| void GenerateNvramData(NvramVersion version, brillo::SecureBlob* nvram_data) { |
| std::unique_ptr<LockboxContents> contents = |
| LockboxContents::New(LockboxContents::GetNvramSize(version)); |
| ASSERT_TRUE(contents); |
| ASSERT_TRUE(contents->SetKeyMaterial( |
| brillo::SecureBlob(contents->key_material_size(), 'A'))); |
| ASSERT_TRUE(contents->Protect({42})); |
| ASSERT_TRUE(contents->Encode(nvram_data)); |
| } |
| |
| void LoadAndVerify(const brillo::SecureBlob& nvram_data, |
| const brillo::Blob& data, |
| LockboxContents::VerificationResult expected_result) { |
| std::unique_ptr<LockboxContents> contents = |
| LockboxContents::New(nvram_data.size()); |
| ASSERT_TRUE(contents); |
| ASSERT_TRUE(contents->Decode(nvram_data)); |
| EXPECT_EQ(expected_result, contents->Verify(data)); |
| } |
| }; |
| |
| TEST_F(LockboxContentsTest, LoadAndVerifyOkTpmDefault) { |
| brillo::SecureBlob nvram_data; |
| ASSERT_NO_FATAL_FAILURE( |
| GenerateNvramData(NvramVersion::kDefault, &nvram_data)); |
| LoadAndVerify(nvram_data, {42}, LockboxContents::VerificationResult::kValid); |
| } |
| |
| TEST_F(LockboxContentsTest, LoadAndVerifyOkTpmV1) { |
| brillo::SecureBlob nvram_data; |
| ASSERT_NO_FATAL_FAILURE( |
| GenerateNvramData(NvramVersion::kVersion1, &nvram_data)); |
| LoadAndVerify(nvram_data, {42}, LockboxContents::VerificationResult::kValid); |
| } |
| |
| TEST_F(LockboxContentsTest, LoadAndVerifyOkTpmV2) { |
| brillo::SecureBlob nvram_data; |
| ASSERT_NO_FATAL_FAILURE( |
| GenerateNvramData(NvramVersion::kVersion2, &nvram_data)); |
| LoadAndVerify(nvram_data, {42}, LockboxContents::VerificationResult::kValid); |
| } |
| |
| TEST_F(LockboxContentsTest, LoadAndVerifyBadSize) { |
| SecureBlob nvram_data; |
| ASSERT_NO_FATAL_FAILURE( |
| GenerateNvramData(NvramVersion::kVersion2, &nvram_data)); |
| |
| // Change the expected file size to 0. |
| nvram_data[0] = 0; |
| nvram_data[1] = 0; |
| nvram_data[2] = 0; |
| nvram_data[3] = 0; |
| |
| LoadAndVerify(nvram_data, {42}, |
| LockboxContents::VerificationResult::kSizeMismatch); |
| } |
| |
| TEST_F(LockboxContentsTest, LoadAndVerifyBadHash) { |
| SecureBlob nvram_data; |
| ASSERT_NO_FATAL_FAILURE( |
| GenerateNvramData(NvramVersion::kVersion2, &nvram_data)); |
| |
| // Invalidate the hash. |
| nvram_data.back() ^= 0xff; |
| |
| LoadAndVerify(nvram_data, {42}, |
| LockboxContents::VerificationResult::kHashMismatch); |
| } |
| |
| TEST_F(LockboxContentsTest, LoadAndVerifyBadData) { |
| SecureBlob nvram_data; |
| ASSERT_NO_FATAL_FAILURE( |
| GenerateNvramData(NvramVersion::kVersion2, &nvram_data)); |
| LoadAndVerify(nvram_data, {17}, |
| LockboxContents::VerificationResult::kHashMismatch); |
| } |
| |
| } // namespace cryptohome |