blob: 041bcd63abc7252f2d553ff9c799872b91f8438d [file] [log] [blame]
// Copyright (c) 2012 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.
#include "install_attributes.h"
#include <limits.h>
#include <base/logging.h>
#include <base/time/time.h>
#include "lockbox.h"
#include "install_attributes.pb.h"
#include "tpm_init.h"
namespace cryptohome {
// See README.lockbox for information on how this was selected.
const uint32_t InstallAttributes::kLockboxIndex = 0x20000004;
// By default, we store this with other cryptohome state.
const char* InstallAttributes::kDefaultDataFile =
"/home/.shadow/install_attributes.pb";
// This is the default location for the cache file.
const char* InstallAttributes::kDefaultCacheFile =
"/var/run/lockbox/install_attributes.pb";
InstallAttributes::InstallAttributes(Tpm* tpm)
: is_first_install_(false),
is_invalid_(false),
is_initialized_(false),
data_file_(kDefaultDataFile),
cache_file_(kDefaultCacheFile),
default_attributes_(new SerializedInstallAttributes()),
default_lockbox_(new Lockbox(tpm, kLockboxIndex)),
default_platform_(new Platform()),
attributes_(default_attributes_.get()),
lockbox_(default_lockbox_.get()),
platform_(default_platform_.get()) {
SetTpm(tpm); // make sure to check TPM status if needed.
version_ = attributes_->version(); // versioning controlled by pb default.
}
InstallAttributes::~InstallAttributes() {
}
bool InstallAttributes::PrepareSystem() {
set_is_first_install(true);
Lockbox::ErrorId error_id;
// Delete the attributes file if it exists.
if (platform_->FileExists(data_file_) &&
!platform_->DeleteFile(data_file_, false)) {
LOG(ERROR) << "PrepareSystem() unable to delete old data!";
return false;
}
// If a TPM is in use, let's clean up the lockbox.
if (is_secure())
return lockbox()->Destroy(&error_id);
return true;
}
void InstallAttributes::SetIsInvalid(bool is_invalid) {
// If a store is invalid, make sure it is forced to be empty.
is_invalid_ = is_invalid;
if (is_invalid) {
set_is_first_install(false);
attributes_->Clear();
}
}
void InstallAttributes::SetTpm(Tpm* tpm) {
// Technically, it is safe to call SetTpm, then Init() again, but it could
// also cause weirdness and report that data is TPM-backed when it isn't.
DCHECK(!is_initialized()) << "SetTpm used after a successful Init().";
if (tpm && !tpm->IsEnabled()) {
LOG(WARNING) << "set_tpm() missing or disabled TPM provided.";
tpm = NULL; // Don't give it to Lockbox.
}
set_is_secure(tpm != NULL);
lockbox_->set_tpm(tpm);
}
bool InstallAttributes::Init(TpmInit* tpm_init) {
Lockbox::ErrorId error_id;
// Insure that if Init() was called and it failed, we can retry cleanly.
attributes_->Clear();
SetIsInvalid(false);
set_is_initialized(false);
if (is_first_install()) {
if (!is_secure()) {
LOG(WARNING) << "InstallAttributes are insecure without a TPM.";
set_is_initialized(true);
return true;
}
if (!lockbox()->Create(&error_id)) {
if (error_id == Lockbox::kErrorIdInsufficientAuthorization)
LOG(ERROR) << "First install, but no TPM credentials provided.";
SetIsInvalid(true);
return false;
}
set_is_initialized(true);
tpm_init->RemoveTpmOwnerDependency(TpmInit::kInstallAttributes);
return true;
}
if (is_secure() && !lockbox()->Load(&error_id)) {
// There are two non-terminal error cases:
// 1. No NVRAM space is defined. This will occur on systems that
// are autoupdated with this code but never went through the OOBE,
// or if creation was interrupted after TPM Ownership happened.
// 2. NVRAM space exists and is unlocked. It means the system was powered
// off before any data was stored.
switch (error_id) {
case Lockbox::kErrorIdNoNvramSpace:
LOG(INFO) << "Resuming interrupted InstallAttributes. (Create needed.)";
if (!lockbox()->Create(&error_id)) {
if (error_id == Lockbox::kErrorIdInsufficientAuthorization)
DLOG(INFO) << "Legacy install. (Can never create NVRAM space.)";
else
LOG(ERROR) << "Create failed, Lockbox error: " << error_id;
} else {
// Create worked, so act like the kErrorIdNoNvramData path now.
set_is_first_install(true);
}
set_is_initialized(true);
// No data.
return true;
break;
case Lockbox::kErrorIdNoNvramData:
LOG(INFO) << "Resuming interrupted InstallAttributes. (Store needed.)";
set_is_first_install(true);
set_is_initialized(true);
// Since we write when we finalize, we don't try to reparse.
return true;
break;
default:
LOG(ERROR) << "InstallAttributes failed to initialize.";
SetIsInvalid(true);
return false;
}
}
// Load the file from disk.
chromeos::Blob blob;
if (!platform_->ReadFile(data_file_, &blob)) {
LOG(WARNING) << "Init() failed to read attributes file.";
// If this is an insecure install, then we can just start the
// file fresh, otherwise it signifies tampering.
if (is_secure()) {
SetIsInvalid(true);
return false;
}
LOG(INFO) << "Init() assuming first-time install for TPM-less system.";
set_is_first_install(true);
set_is_initialized(true);
return true;
}
// Prior to attempting to deserialize the data, ensure it has not
// been tampered with.
if (is_secure() && !lockbox()->Verify(blob, &error_id)) {
LOG(ERROR) << "Init() could not verify attribute data!";
SetIsInvalid(true);
return false;
}
if (!attributes_->ParseFromArray(
static_cast<google::protobuf::uint8*>(blob.data()),
blob.size())) {
LOG(ERROR) << "Failed to parse data file (" << blob.size() << " bytes)";
SetIsInvalid(true);
return false;
}
set_is_initialized(true);
return true;
}
int InstallAttributes::FindIndexByName(const std::string& name) const {
std::string name_str(name);
for (int i = 0; i < attributes_->attributes_size(); ++i) {
if (attributes_->attributes(i).name().compare(name_str) == 0)
return i;
}
return -1;
}
bool InstallAttributes::Get(const std::string& name,
chromeos::Blob* value) const {
int index = FindIndexByName(name);
if (index == -1)
return false;
return GetByIndex(index, NULL, value);
}
bool InstallAttributes::GetByIndex(int index,
std::string* name,
chromeos::Blob* value) const {
if (index < 0 || index >= attributes_->attributes_size()) {
LOG(ERROR) << "GetByIndex called with invalid index.";
return false;
}
const SerializedInstallAttributes::Attribute* const attr =
&attributes_->attributes(index);
if (name) {
name->assign(attr->name());
}
if (value) {
value->resize(attr->value().length());
memcpy(&value->at(0), attr->value().c_str(), value->size());
}
return true;
}
bool InstallAttributes::Set(const std::string& name,
const chromeos::Blob& value) {
if (!is_first_install()) {
LOG(ERROR) << "Set() called on immutable attributes.";
return false;
}
if (Count() == INT_MAX) {
LOG(ERROR) << "Set() cannot insert into full attribute store.";
return false;
}
// Clobber an existing entry if it exists.
int index = FindIndexByName(name);
if (index != -1) {
SerializedInstallAttributes::Attribute* attr =
attributes_->mutable_attributes(index);
attr->set_value(std::string(reinterpret_cast<const char*>(&value[0]),
value.size()));
return true;
}
SerializedInstallAttributes::Attribute* attr = attributes_->add_attributes();
if (!attr) {
LOG(ERROR) << "Failed to add a new attribute.";
return false;
}
attr->set_name(name);
attr->set_value(std::string(reinterpret_cast<const char*>(&value[0]),
value.size()));
return true;
}
bool InstallAttributes::Finalize() {
if (!IsReady()) {
LOG(ERROR) << "Finalize() called with invalid/uninitialized data.";
return false;
}
// Repeated calls to Finalize() are idempotent.
if (!is_first_install())
return true;
// Restamp the version.
attributes_->set_version(version_);
// Serialize the bytestream
chromeos::Blob attr_bytes;
if (!SerializeAttributes(&attr_bytes)) {
LOG(ERROR) << "Finalize() failed to serialize the attributes.";
return false;
}
Lockbox::ErrorId error;
DLOG(INFO) << "Finalizing() " << attr_bytes.size() << " bytes.";
if (is_secure() && !lockbox()->Store(attr_bytes, &error)) {
LOG(ERROR) << "Finalize() failed with Lockbox error: " << error;
// It may be possible to recover from a failed NVRAM store. So the
// instance is not marked invalid.
return false;
}
if (!platform_->WriteFile(data_file_, attr_bytes)) {
LOG(ERROR) << "Finalize() write failed after locking the Lockbox.";
SetIsInvalid(true);
return false;
}
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
if (!platform_->WriteFile(cache_file_, attr_bytes) ||
!platform_->SetPermissions(cache_file_, mode)) {
LOG(WARNING) << "Finalize() failed to create cache file.";
}
LOG(INFO) << "InstallAttributes have been finalized.";
set_is_first_install(false);
NotifyFinalized();
return true;
}
int InstallAttributes::Count() const {
return attributes_->attributes_size();
}
bool InstallAttributes::SerializeAttributes(chromeos::Blob* out_bytes) {
out_bytes->resize(attributes_->ByteSize());
attributes_->SerializeWithCachedSizesToArray(
static_cast<google::protobuf::uint8*>(out_bytes->data()));
return true;
}
base::Value* InstallAttributes::GetStatus() {
base::DictionaryValue* dv = new base::DictionaryValue();
dv->SetBoolean("initialized", is_initialized());
dv->SetInteger("version", version());
dv->SetInteger("lockbox_index", lockbox()->nvram_index());
dv->SetInteger("lockbox_nvram_version", lockbox()->nvram_version());
dv->SetBoolean("secure", is_secure());
dv->SetBoolean("invalid", is_invalid());
dv->SetBoolean("first_install", is_first_install());
dv->SetInteger("size", Count());
if (Count()) {
base::DictionaryValue* attrs = new base::DictionaryValue();
std::string key;
chromeos::Blob value;
for (int i = 0; i < Count(); i++) {
GetByIndex(i, &key, &value);
std::string value_str(reinterpret_cast<const char*>(&value[0]));
attrs->SetString(key, value_str);
}
dv->Set("attrs", attrs);
}
return dv;
}
} // namespace cryptohome