blob: 39dec4cac7af0838a7652f081924f043cfe67916 [file] [log] [blame] [edit]
// 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 "featured/store_impl.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <featured/proto_bindings/featured.pb.h>
#include <openssl/crypto.h>
#include <sys/stat.h>
#include "bootlockbox-client/bootlockbox/boot_lockbox_client.h"
#include "featured/hmac.h"
namespace featured {
constexpr char kStorePath[] = "/var/lib/featured/store";
constexpr char kStoreHMACPath[] = "/var/lib/featured/store_hmac";
constexpr char kLockboxKey[] = "featured_early_boot_key";
constexpr mode_t kSystemFeaturedFilesMode = 0760;
namespace {
// Walks the directory tree to make sure we avoid symlinks.
// Creates |path| if it does not exist.
//
// All parent parts must already exist else we return false.
bool ValidatePathAndOpen(const base::FilePath& path,
int* outfd,
int flags = 0) {
std::vector<std::string> components = path.GetComponents();
if (components.empty()) {
LOG(ERROR) << "Cannot open an empty path";
return false;
}
int parentfd = AT_FDCWD;
for (auto it = components.begin(); it != components.end(); ++it) {
std::string component = *it;
int fd;
if (it == components.end() - 1) {
// Check that the last component is a valid file and open it for reading
// and writing.
fd = openat(parentfd, component.c_str(),
O_CREAT | O_RDWR | O_NOFOLLOW | O_CLOEXEC | flags,
kSystemFeaturedFilesMode);
} else {
// Check that all components except the last are a valid directory.
fd = openat(parentfd, component.c_str(),
O_NOFOLLOW | O_CLOEXEC | O_PATH | O_DIRECTORY);
}
if (fd < 0) {
PLOG(ERROR) << "Unable to access path: " << path.value() << " ("
<< component << ")";
if (parentfd != AT_FDCWD) {
close(parentfd);
}
return false;
}
if (parentfd != AT_FDCWD) {
close(parentfd);
}
parentfd = fd;
}
*outfd = parentfd;
return true;
}
// Validates |file_path| according to |ValidatePathAndOpen| and reads the
// contents into |file_content|. Creates |file_path| if it does not exist.
//
// Returns false if validating, opening, or reading |file_path| fail.
//
// NOTE: While |file_path| could be recreated if reading fails, doing so is
// risky since deletion could have unintended consequences (eg. the file is a
// symlink).
bool ValidatePathAndRead(const base::FilePath& file_path,
std::string& file_content) {
int fd;
if (!ValidatePathAndOpen(file_path, &fd)) {
LOG(ERROR) << "Failed to validate and open " << file_path;
return false;
} else {
// Constructing with |fd| instead of |file_path| to avoid potential
// TOCTOU (time-of-check/time-of-use) vulnerabilities between calling
// |ValidatePathAndOpen| and constructing |file|.
base::File file(fd);
std::vector<uint8_t> buffer(file.GetLength());
if (!file.ReadAndCheck(/*offset=*/0, base::make_span(buffer))) {
LOG(ERROR) << "Failed to read file contents";
return false;
}
file_content = std::string(buffer.begin(), buffer.end());
}
return true;
}
// Writes store and hmac to disk.
bool WriteDisk(const Store& store,
const HMAC& hmac_wrapper,
const base::FilePath& store_path,
const base::FilePath& hmac_path) {
std::string serialized_store;
bool serialized = store.SerializeToString(&serialized_store);
if (!serialized) {
LOG(ERROR) << "Could not serialize protobuf";
return false;
}
int store_fd;
if (!ValidatePathAndOpen(store_path, &store_fd, O_TRUNC)) {
PLOG(ERROR) << "Could not reopen " << store_path;
return false;
}
// Write store to disk.
base::File store_file(store_fd);
if (!store_file.WriteAtCurrentPosAndCheck(
base::as_bytes(base::make_span(serialized_store)))) {
PLOG(ERROR) << "Could not write new store to disk";
return false;
}
// Compute store HMAC.
std::optional<std::string> store_hmac = hmac_wrapper.Sign(serialized_store);
if (!store_hmac.has_value()) {
LOG(ERROR) << "Failed to sign store hmac";
return false;
}
int hmac_fd;
if (!ValidatePathAndOpen(hmac_path, &hmac_fd, O_TRUNC)) {
LOG(ERROR) << "Could not reopen " << hmac_path;
return false;
}
// Write store HMAC to disk.
std::string store_hmac_str = store_hmac.value();
base::File hmac_file(hmac_fd);
if (!hmac_file.WriteAtCurrentPosAndCheck(
base::as_bytes(base::make_span(store_hmac_str)))) {
PLOG(ERROR) << "Could not write new store HMAC to disk";
return false;
}
return true;
}
} // namespace
StoreImpl::StoreImpl(const Store& store,
std::unique_ptr<HMAC> hmac_wrapper,
const base::FilePath& store_path,
const base::FilePath& hmac_path)
: store_(store),
hmac_wrapper_(std::move(hmac_wrapper)),
store_path_(std::move(store_path)),
hmac_path_(std::move(hmac_path)) {}
std::unique_ptr<StoreInterface> StoreImpl::Create() {
std::unique_ptr<bootlockbox::BootLockboxClient> boot_lockbox_client =
bootlockbox::BootLockboxClient::CreateBootLockboxClient();
return Create(base::FilePath(kStorePath), base::FilePath(kStoreHMACPath),
std::move(boot_lockbox_client));
}
std::unique_ptr<StoreInterface> StoreImpl::Create(
base::FilePath store_path,
base::FilePath hmac_path,
std::unique_ptr<bootlockbox::BootLockboxClient> boot_lockbox_client) {
// Check validity of the boot lockbox.
if (!boot_lockbox_client) {
LOG(ERROR) << "Invalid bootlockbox client";
return nullptr;
}
// Read the store and HMAC.
// Open store file or create if it does not exist.
std::string store_content;
if (!ValidatePathAndRead(store_path, store_content)) {
LOG(ERROR) << "Failed to validate and read from " << store_path;
return nullptr;
}
// Open hmac file or create if it does not exist.
std::string hmac_content;
if (!ValidatePathAndRead(hmac_path, hmac_content)) {
LOG(ERROR) << "Failed to validate and read from " << hmac_path;
return nullptr;
}
// Verify the HMAC, falling back to an empty proto if it fails to verify
// (or is missing).
std::unique_ptr<HMAC> hmac_wrapper;
std::string hmac_key;
bool key_exists = boot_lockbox_client->Read(kLockboxKey, &hmac_key);
bool verified = false;
if (key_exists) {
hmac_wrapper = std::make_unique<HMAC>(HMAC::SHA256);
if (!hmac_wrapper->Init(hmac_key)) {
LOG(ERROR) << "Failed to initialize HMAC instance";
return nullptr;
}
verified = hmac_wrapper->Verify(store_content, hmac_content);
// Zero out the hmac key after verifying store and hmac.
OPENSSL_cleanse(hmac_key.data(), hmac_key.size());
}
// Deserialize the proto and store it in memory.
Store store;
if (verified) {
bool deserialized_store = store.ParseFromString(store_content);
if (!deserialized_store) {
LOG(ERROR) << "Failed to deserialize store";
store.Clear();
}
// else: Use empty proto.
} // else: Use empty proto.
// Generate a new key, attempt to store it in the boot lockbox, and only if
// that succeeds, re-generate an HMAC of the serialized proto and store it to
// disk.
std::unique_ptr<HMAC> new_hmac_wrapper = std::make_unique<HMAC>(HMAC::SHA256);
if (!new_hmac_wrapper->Init()) {
LOG(ERROR) << "HMAC wrapper failed to generate new key";
return nullptr;
}
std::string new_key = new_hmac_wrapper->GetKey();
if (!boot_lockbox_client->Store(kLockboxKey, new_key)) {
LOG(ERROR) << "Could not store new key";
return nullptr;
}
// Zero out the symmetric key after storing it.
OPENSSL_cleanse(new_key.data(), new_key.size());
if (!WriteDisk(store, *new_hmac_wrapper, store_path, hmac_path)) {
LOG(ERROR) << "Failed to write store and hmac to disk";
return nullptr;
}
return std::unique_ptr<StoreInterface>(
new StoreImpl(store, std::move(new_hmac_wrapper), store_path, hmac_path));
}
uint32_t StoreImpl::GetBootAttemptsSinceLastUpdate() {
return store_.boot_attempts_since_last_seed_update();
}
bool StoreImpl::IncrementBootAttemptsSinceLastUpdate() {
uint32_t boot_attempts = GetBootAttemptsSinceLastUpdate();
store_.set_boot_attempts_since_last_seed_update(boot_attempts + 1);
if (!WriteDisk(store_, *hmac_wrapper_, store_path_, hmac_path_)) {
LOG(ERROR) << "Failed to increment boot attempts to disk.";
return false;
}
return true;
}
bool StoreImpl::ClearBootAttemptsSinceLastUpdate() {
store_.set_boot_attempts_since_last_seed_update(0);
if (!WriteDisk(store_, *hmac_wrapper_, store_path_, hmac_path_)) {
LOG(ERROR) << "Failed to increment boot attempts to disk.";
return false;
}
return true;
}
SeedDetails StoreImpl::GetLastGoodSeed() {
return store_.last_good_seed();
}
bool StoreImpl::SetLastGoodSeed(const SeedDetails& seed) {
*store_.mutable_last_good_seed() = seed;
if (!WriteDisk(store_, *hmac_wrapper_, store_path_, hmac_path_)) {
LOG(ERROR) << "Failed to increment boot attempts to disk.";
return false;
}
return true;
}
// TODO(kendraketsui): implement.
std::vector<FeatureOverride> StoreImpl::GetOverrides() {
return std::vector<FeatureOverride>();
}
// TODO(kendraketsui): implement.
void StoreImpl::AddOverride(const FeatureOverride& override) {}
// TODO(kendraketsui): implement.
void StoreImpl::RemoveOverrideFor(const std::string& name) {}
} // namespace featured