blob: 3a408e0e6e062af0c590409b3a1dee8a433571de [file] [log] [blame]
// 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/process/process_mock.h>
#include <brillo/secure_blob.h>
#include <gtest/gtest.h>
#include <libhwsec-foundation/error/testing_helper.h>
#include "cryptohome/crypto/secure_blob_util.h"
#include "cryptohome/crypto/sha.h"
#include "cryptohome/mock_lockbox.h"
#include "cryptohome/mock_platform.h"
#include "cryptohome/mock_tpm.h"
namespace cryptohome {
using brillo::SecureBlob;
using ::hwsec::error::TPMError;
using ::hwsec::error::TPMErrorBase;
using ::hwsec::error::TPMRetryAction;
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_);
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_;
};
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::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.
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::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_, 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(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), 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));
}
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(SecureBlobToHex(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_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(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