| // 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 "cryptohome/install_attributes.h" |
| |
| #include <sys/types.h> |
| |
| #include <limits> |
| #include <string> |
| |
| #include <base/logging.h> |
| #include <base/time/time.h> |
| |
| #include "cryptohome/lockbox.h" |
| #include "cryptohome/tpm_init.h" |
| |
| #include "install_attributes.pb.h" // NOLINT(build/include) |
| |
| 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"; |
| const mode_t InstallAttributes::kDataFilePermissions = 0644; |
| // This is the default location for the cache file. |
| const char* InstallAttributes::kDefaultCacheFile = |
| "/var/run/lockbox/install_attributes.pb"; |
| const mode_t InstallAttributes::kCacheFilePermissions = 0644; |
| |
| 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. |
| brillo::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, |
| brillo::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, |
| brillo::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 brillo::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.data()), |
| 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.data()), |
| 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 |
| brillo::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_->WriteFileAtomicDurable(data_file_, attr_bytes, |
| kDataFilePermissions)) { |
| LOG(ERROR) << "Finalize() write failed after locking the Lockbox."; |
| SetIsInvalid(true); |
| return false; |
| } |
| |
| // Since the cache file is re-created upon every boot, atomic write is not |
| // required. |
| if (!platform_->WriteFile(cache_file_, attr_bytes) || |
| !platform_->SetPermissions(cache_file_, kCacheFilePermissions)) { |
| 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(brillo::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; |
| brillo::Blob value; |
| for (int i = 0; i < Count(); i++) { |
| GetByIndex(i, &key, &value); |
| std::string value_str(reinterpret_cast<const char*>(value.data())); |
| attrs->SetString(key, value_str); |
| } |
| dv->Set("attrs", attrs); |
| } |
| return dv; |
| } |
| |
| } // namespace cryptohome |