| // 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 <base/notreached.h> |
| #include <brillo/secure_blob.h> |
| #include <gtest/gtest.h> |
| #include <libhwsec-foundation/crypto/secure_blob_util.h> |
| #include <libhwsec-foundation/crypto/sha.h> |
| #include <libhwsec-foundation/error/testing_helper.h> |
| |
| #include "cryptohome/mock_lockbox.h" |
| #include "cryptohome/mock_platform.h" |
| #include "cryptohome/mock_tpm.h" |
| |
| namespace cryptohome { |
| using ::brillo::SecureBlob; |
| using ::hwsec::TPMError; |
| using ::hwsec::TPMErrorBase; |
| using ::hwsec::TPMRetryAction; |
| using ::hwsec_foundation::SecureBlobToHex; |
| using ::hwsec_foundation::Sha256; |
| using ::hwsec_foundation::error::testing::ReturnError; |
| 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(const LockboxTest&) = delete; |
| LockboxTest& operator=(const LockboxTest&) = delete; |
| |
| ~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_); |
| } |
| |
| 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_; |
| brillo::Blob file_data_; |
| }; |
| |
| 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. |
| EXPECT_CALL(tpm_, IsOwnerPasswordPresent()) |
| .WillRepeatedly(DoAll(Return(true))); |
| |
| // No pre-existing space. |
| EXPECT_CALL(tpm_, IsNvramDefined(0xdeadbeef)).WillOnce(Return(false)); |
| |
| // Create the new space. |
| EXPECT_CALL(tpm_, DefineNvram(0xdeadbeef, LockboxContents::kNvramSize, |
| 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. |
| EXPECT_CALL(tpm_, IsOwnerPasswordPresent()) |
| .WillRepeatedly(DoAll(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::kNvramSize, |
| 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_, IsOwnerPasswordPresent()).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(LockboxContents::kKeyMaterialSize, 'A'); |
| break; |
| case Tpm::TpmVersion::TPM_2_0: |
| key_material.assign(LockboxContents::kKeyMaterialSize, 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::kNvramSize)); |
| EXPECT_CALL(tpm_, GetRandomDataSecureBlob(key_material.size(), _)) |
| .WillRepeatedly( |
| DoAll(SetArgPointee<1>(key_material), ReturnError<TPMErrorBase>())); |
| EXPECT_CALL(tpm_, WriteNvram(0xdeadbeef, _)).WillOnce(Return(true)); |
| EXPECT_CALL(tpm_, WriteLockNvram(0xdeadbeef)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsNvramLocked(0xdeadbeef)).WillOnce(Return(true)); |
| } |
| |
| 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_SUITE_P(LockboxTestTpm12, |
| LockboxTest, |
| testing::Values(Tpm::TpmVersion::TPM_1_2)); |
| INSTANTIATE_TEST_SUITE_P(LockboxTestTpm20, |
| LockboxTest, |
| testing::Values(Tpm::TpmVersion::TPM_2_0)); |
| |
| class LockboxContentsTest : public testing::Test { |
| public: |
| LockboxContentsTest() = default; |
| |
| void GenerateNvramData(brillo::SecureBlob* nvram_data) { |
| std::unique_ptr<LockboxContents> contents = LockboxContents::New(); |
| 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(); |
| ASSERT_TRUE(contents); |
| ASSERT_TRUE(contents->Decode(nvram_data)); |
| EXPECT_EQ(expected_result, contents->Verify(data)); |
| } |
| }; |
| |
| TEST_F(LockboxContentsTest, LoadAndVerifyOk) { |
| brillo::SecureBlob nvram_data; |
| ASSERT_NO_FATAL_FAILURE(GenerateNvramData(&nvram_data)); |
| LoadAndVerify(nvram_data, {42}, LockboxContents::VerificationResult::kValid); |
| } |
| |
| TEST_F(LockboxContentsTest, LoadAndVerifyBadSize) { |
| SecureBlob nvram_data; |
| ASSERT_NO_FATAL_FAILURE(GenerateNvramData(&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(&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(&nvram_data)); |
| LoadAndVerify(nvram_data, {17}, |
| LockboxContents::VerificationResult::kHashMismatch); |
| } |
| |
| } // namespace cryptohome |