blob: f83f3cb41246d88d3887c647b435036ad60b9113 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <utility>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <brillo/files/file_util.h>
#include <dbus/mock_bus.h>
#include <featured/proto_bindings/featured.pb.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libhwsec-foundation/crypto/hmac.h>
#include "featured/store_impl.h"
namespace featured {
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::StrictMock;
MATCHER_P(EqualsProto,
message,
"Match a proto Message equal to the matcher's argument.") {
std::string expected_serialized, actual_serialized;
message.SerializeToString(&expected_serialized);
arg.SerializeToString(&actual_serialized);
return expected_serialized == actual_serialized;
}
// Arbitrary 32-byte string.
constexpr char kInitialBootKey[] = "01234567890123456789012345678901";
constexpr char kCorruptFileContent[] = "test_corrupted_data";
class StoreImplTest : public testing::Test {
public:
StoreImplTest()
: mock_bus_(base::MakeRefCounted<dbus::MockBus>(dbus::Bus::Options{})) {
EXPECT_TRUE(dir_.CreateUniqueTempDir());
store_path_ = get_dir().Append("store");
tpm_seed_path_ = get_dir().Append("tpm_seed");
}
const base::FilePath& get_dir() { return dir_.GetPath(); }
protected:
void SetUp() {
ASSERT_TRUE(base::WriteFile(tpm_seed_path_, std::string(kInitialBootKey)));
}
void ComputeHmac(Store& store) {
// Compute overrides HMAC.
brillo::SecureBlob hash = hwsec_foundation::HmacSha256(
brillo::SecureBlob(std::string(kInitialBootKey)),
brillo::BlobFromString(store.overrides()));
store.set_overrides_hmac(hash.to_string());
}
void InitializeStore(Store& store) {
store.set_boot_attempts_since_last_seed_update(0);
SeedDetails* seed = store.mutable_last_good_seed();
seed->set_compressed_data("test_compressed_data");
seed->set_date(1);
seed->set_fetch_time(1);
seed->set_locale("test_locale");
seed->set_milestone(110);
seed->set_permanent_consistency_country("us");
seed->set_session_consistency_country("us");
seed->set_signature("test_signature");
OverridesSet set;
FeatureOverride* feature = set.add_overrides();
feature->set_name("CrOSEarlyBootTest");
feature->set_enabled(true);
std::string set_serialized;
ASSERT_TRUE(set.SerializeToString(&set_serialized));
store.set_overrides(set_serialized);
ComputeHmac(store);
}
base::FilePath store_path_;
base::FilePath tpm_seed_path_;
private:
base::ScopedTempDir dir_;
scoped_refptr<dbus::MockBus> mock_bus_;
};
// Check that StoreImpl creation succeeds when the seed is too short.
TEST_F(StoreImplTest, KeyRead_BadSize) {
// Create key that's too short.
ASSERT_TRUE(base::WriteFile(tpm_seed_path_, "bad_length"));
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
EXPECT_NE(store_interface, nullptr);
}
// Check that StoreImpl creation succeeds when reading the seed file fails.
TEST_F(StoreImplTest, KeyRead_NonExistent) {
ASSERT_TRUE(brillo::DeleteFile(tpm_seed_path_));
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
EXPECT_NE(store_interface, nullptr);
}
// Check that StoreImpl creation fails when reading the store file fails.
TEST_F(StoreImplTest, StoreRead_Failure) {
// Create store with only write permission.
base::File store_file(
store_path_, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
ASSERT_TRUE(store_file.IsValid());
ASSERT_TRUE(SetPosixFilePermissions(store_path_,
base::FILE_PERMISSION_WRITE_BY_USER));
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
EXPECT_EQ(store_interface, nullptr);
}
// Check that StoreImpl creation fails when store file creation fails due to
// incorrect permissions.
TEST_F(StoreImplTest, StoreCreate_Failure_WrongPermissions) {
// Modify directory to have only read permission.
ASSERT_TRUE(
SetPosixFilePermissions(get_dir(), base::FILE_PERMISSION_READ_BY_USER));
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
EXPECT_EQ(store_interface, nullptr);
// Add execute and write permissions back to more likely to clean up the
// directory correctly.
ASSERT_TRUE(SetPosixFilePermissions(get_dir(),
base::FILE_PERMISSION_EXECUTE_BY_USER |
base::FILE_PERMISSION_WRITE_BY_USER));
}
// Check that StoreImpl creation fails when store file path is invalid (eg.
// empty path).
TEST_F(StoreImplTest, StoreCreate_Failure_InvalidPath) {
std::unique_ptr<StoreInterface> store_interface = StoreImpl::Create(
/*store_path=*/base::FilePath(""), tpm_seed_path_);
EXPECT_EQ(store_interface, nullptr);
}
// Check that StoreImpl creation fails when writing to the store file fails.
TEST_F(StoreImplTest, StoreWrite_Failure) {
// Create store with only read permission.
base::File store_file(
store_path_, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
ASSERT_TRUE(store_file.IsValid());
ASSERT_TRUE(
SetPosixFilePermissions(store_path_, base::FILE_PERMISSION_READ_BY_USER));
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
EXPECT_EQ(store_interface, nullptr);
}
// Check that StoreImpl object is created when the store is successfully
// verified. This means reading the key, the store file, and the sig file from
// disk succeed as well.
//
// Verifies the store on disk is not modified.
TEST_F(StoreImplTest, StoreVerified_Success) {
// Create store.
Store store;
InitializeStore(store);
// Serialize store.
std::string serialized_store;
ASSERT_TRUE(store.SerializeToString(&serialized_store));
// Write serialized store to disk.
ASSERT_TRUE(base::WriteFile(store_path_, serialized_store));
// Create StoreImpl object.
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
EXPECT_NE(store_interface, nullptr);
std::string store_content;
ASSERT_TRUE(ReadFileToString(store_path_, &store_content));
EXPECT_EQ(store_content, serialized_store);
}
// Check that StoreImpl object is created when store verification fails due to
// corrupt store file. Verifies an empty overrides and associated HMAC are
// written to disk.
TEST_F(StoreImplTest, StoreCorruption_Verification_Failure) {
// Create store.
Store store;
InitializeStore(store);
// Serialize store.
std::string serialized_store;
ASSERT_TRUE(store.SerializeToString(&serialized_store));
// Corrupt store.
store.set_overrides("junk");
std::string corrupted_store;
ASSERT_TRUE(store.SerializeToString(&corrupted_store));
// Write corrupted store to disk.
ASSERT_TRUE(base::WriteFile(store_path_, corrupted_store));
// Create StoreImpl object.
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
EXPECT_NE(store_interface, nullptr);
// Verify that the on-disk contents are valid.
std::string store_content;
ASSERT_TRUE(ReadFileToString(store_path_, &store_content));
Store actual_store;
ASSERT_TRUE(actual_store.ParseFromString(store_content));
EXPECT_TRUE(actual_store.overrides().empty());
store.clear_overrides();
ComputeHmac(store);
EXPECT_EQ(actual_store.overrides_hmac(), store.overrides_hmac());
}
// Check that StoreImpl object is created when store verification fails due to
// corrupt HMAC field. Verifies an empty overrides and associated HMAC are
// written to disk.
TEST_F(StoreImplTest, HMACCorruption_Verification_Failure) {
// Create store.
Store store;
InitializeStore(store);
store.set_overrides_hmac("bad hmac");
// Serialize store.
std::string serialized_store;
ASSERT_TRUE(store.SerializeToString(&serialized_store));
// Write serialized store to disk.
ASSERT_TRUE(base::WriteFile(store_path_, serialized_store));
// Create StoreImpl object.
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
EXPECT_NE(store_interface, nullptr);
// Verify that the on-disk contents are valid.
std::string store_content;
ASSERT_TRUE(ReadFileToString(store_path_, &store_content));
Store actual_store;
ASSERT_TRUE(actual_store.ParseFromString(store_content));
EXPECT_TRUE(actual_store.overrides().empty());
store.clear_overrides();
ComputeHmac(store);
EXPECT_EQ(actual_store.overrides_hmac(), store.overrides_hmac());
}
// Check that StoreImpl object is created when store deserialization fails due
// to corrupt store.
//
// Verifies an empty store and associated HMAC are written to disk.
TEST_F(StoreImplTest, StoreCorruption_Deserialize_Failure) {
// Write corrupted store to disk.
std::string corrupted_store = kCorruptFileContent;
ASSERT_TRUE(base::WriteFile(store_path_, corrupted_store));
// Create StoreImpl object.
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
EXPECT_NE(store_interface, nullptr);
Store expected;
ComputeHmac(expected);
std::string expected_serialized;
ASSERT_TRUE(expected.SerializeToString(&expected_serialized));
std::string store_content;
ASSERT_TRUE(ReadFileToString(store_path_, &store_content));
EXPECT_EQ(store_content, expected_serialized);
}
// Check correctness of incrementing the boot attempts field in the store.
TEST_F(StoreImplTest, IncrementBootAttempts_Success) {
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
ASSERT_NE(store_interface, nullptr);
// Check boot attempts field.
EXPECT_EQ(store_interface->GetBootAttemptsSinceLastUpdate(), 0);
// Increment boot attempts.
EXPECT_TRUE(store_interface->IncrementBootAttemptsSinceLastUpdate());
EXPECT_EQ(store_interface->GetBootAttemptsSinceLastUpdate(), 1);
// Verify boot attempts update is reflected on disk.
std::string store_content;
ASSERT_TRUE(ReadFileToString(store_path_, &store_content));
Store store;
ASSERT_TRUE(store.ParseFromString(store_content));
EXPECT_EQ(store.boot_attempts_since_last_seed_update(), 1);
}
// Check that incrementing the boot attempts field fails when StoreImpl does not
// have permission to update the store file on disk.
TEST_F(StoreImplTest, IncrementBootAttempts_Failure_StoreWrongPermissions) {
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
ASSERT_NE(store_interface, nullptr);
// Remove write permissions for store file.
ASSERT_TRUE(
SetPosixFilePermissions(store_path_, base::FILE_PERMISSION_READ_BY_USER));
// Increment boot attempts.
EXPECT_FALSE(store_interface->IncrementBootAttemptsSinceLastUpdate());
}
// Check correctness of clearing the boot attempts field in the store.
TEST_F(StoreImplTest, ClearBootAttempts_Success) {
// Create store.
Store store;
store.set_boot_attempts_since_last_seed_update(1);
ComputeHmac(store);
// Serialize store.
std::string serialized_store;
ASSERT_TRUE(store.SerializeToString(&serialized_store));
// Write serialized store to disk.
ASSERT_TRUE(base::WriteFile(store_path_, serialized_store));
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
ASSERT_NE(store_interface, nullptr);
// Check boot attempts field.
EXPECT_EQ(store_interface->GetBootAttemptsSinceLastUpdate(), 1);
// Clear boot attempts.
EXPECT_TRUE(store_interface->ClearBootAttemptsSinceLastUpdate());
EXPECT_EQ(store_interface->GetBootAttemptsSinceLastUpdate(), 0);
// Verify boot attempts update is reflected on disk.
std::string store_content;
ASSERT_TRUE(ReadFileToString(store_path_, &store_content));
ASSERT_TRUE(store.ParseFromString(store_content));
EXPECT_EQ(store.boot_attempts_since_last_seed_update(), 0);
}
// Check that clearing the boot attempts field fails when StoreImpl does not
// have permission to update the store file on disk.
TEST_F(StoreImplTest, ClearBootAttempts_Failure_StoreWrongPermissions) {
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
ASSERT_NE(store_interface, nullptr);
// Remove write permissions for store file.
ASSERT_TRUE(
SetPosixFilePermissions(store_path_, base::FILE_PERMISSION_READ_BY_USER));
// Clear boot attempts.
EXPECT_FALSE(store_interface->ClearBootAttemptsSinceLastUpdate());
}
// Check correctness of updating the seed field in the store.
TEST_F(StoreImplTest, UpdateSeed_Success) {
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
ASSERT_NE(store_interface, nullptr);
// Check seed field.
SeedDetails seed;
EXPECT_THAT(store_interface->GetLastGoodSeed(),
EqualsProto(seed)); // Check for empty seed.
// Update seed.
seed.set_compressed_data("test_compressed_data");
EXPECT_TRUE(store_interface->SetLastGoodSeed(seed));
EXPECT_THAT(store_interface->GetLastGoodSeed(), EqualsProto(seed));
// Verify seed update is reflected on disk.
Store store;
std::string store_content;
ASSERT_TRUE(ReadFileToString(store_path_, &store_content));
ASSERT_TRUE(store.ParseFromString(store_content));
EXPECT_THAT(store.last_good_seed(), EqualsProto(seed));
}
// Check that updating the seed field fails when StoreImpl does not
// have permission to update the store file on disk.
TEST_F(StoreImplTest, UpdateSeed_Failure_StoreWrongPermissions) {
std::unique_ptr<StoreInterface> store_interface =
StoreImpl::Create(store_path_, tpm_seed_path_);
ASSERT_NE(store_interface, nullptr);
// Remove write permissions for store file.
ASSERT_TRUE(
SetPosixFilePermissions(store_path_, base::FILE_PERMISSION_READ_BY_USER));
// Update seed.
SeedDetails seed;
seed.set_compressed_data("test_compressed_data");
EXPECT_FALSE(store_interface->SetLastGoodSeed(seed));
}
} // namespace featured