| // 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 <utility> |
| |
| #include <base/files/file_path.h> |
| #include <base/logging.h> |
| #include <base/time/time.h> |
| |
| #include "cryptohome/cryptohome_metrics.h" |
| #include "cryptohome/lockbox.h" |
| #include "cryptohome/tpm_init.h" |
| |
| #include "install_attributes.pb.h" // NOLINT(build/include) |
| |
| using base::FilePath; |
| |
| namespace cryptohome { |
| |
| // 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[] = |
| "/run/lockbox/install_attributes.pb"; |
| const mode_t InstallAttributes::kCacheFilePermissions = 0644; |
| |
| InstallAttributes::InstallAttributes(Tpm* tpm) |
| : data_file_(kDefaultDataFile), |
| cache_file_(kDefaultCacheFile), |
| default_attributes_(new SerializedInstallAttributes()), |
| default_lockbox_(new Lockbox(tpm, 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() {} |
| |
| 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(status_ != Status::kValid && status_ != Status::kFirstInstall) |
| << "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) { |
| // Ensure that if Init() was called and it failed, we can retry cleanly. |
| attributes_->Clear(); |
| status_ = Status::kUnknown; |
| |
| // Read the cache file. If it exists, lockbox-cache has successfully |
| // verified install attributes during early boot, so use them. |
| brillo::Blob blob; |
| if (platform_->ReadFile(cache_file_, &blob)) { |
| if (!attributes_->ParseFromArray( |
| static_cast<google::protobuf::uint8*>(blob.data()), |
| blob.size())) { |
| LOG(ERROR) << "Failed to parse data file (" << blob.size() << " bytes)"; |
| ReportInstallAttributesUsage( |
| InstallAttributesUsageEvent::kCacheParseFailed); |
| attributes_->Clear(); |
| status_ = Status::kInvalid; |
| return false; |
| } |
| |
| status_ = Status::kValid; |
| // Install attributes are valid, no need to hold owner dependency. So, |
| // repeat removing owner dependency in case it didn't succeed during the |
| // first boot. |
| tpm_init->RemoveTpmOwnerDependency( |
| TpmPersistentState::TpmOwnerDependency::kInstallAttributes); |
| ReportInstallAttributesUsage( |
| InstallAttributesUsageEvent::kCacheReadSucceeded); |
| return true; |
| } |
| |
| // No cache file, so TPM lockbox is either not yet set up or data is invalid. |
| if (!is_secure()) { |
| LOG(INFO) << "Init() assuming first-time install for TPM-less system."; |
| status_ = Status::kFirstInstall; |
| ReportInstallAttributesUsage(InstallAttributesUsageEvent::kFirstInstall); |
| return true; |
| } |
| |
| // TPM not ready yet, i.e. setup after ownership not completed. Init() is |
| // supposed to get invoked again once the TPM is owned and configured. |
| if (!tpm_init->IsTpmReady()) { |
| // To ensure that we get a fresh start after taking ownership, remove the |
| // data file when we boot up after a TPM clear. If we didn't do so, the |
| // previous data file might validate against a left-around NVRAM space, |
| // incorrectly indicating that install attributes are already initialized |
| // and locked. |
| // |
| // Note that we theoretically could delete the file unconditionally here, |
| // since IsTpmReady() should always return true after the TPM has been set |
| // up correctly. However, previous experience tells us that there might be |
| // hardware glitches or driver/firmware bugs that could make the TPM look |
| // disabled. If we accidentally delete the data file in such a situation, |
| // we'd make install attributes inconsistent until next TPM clear, which has |
| // far-reaching consequences (enterprise enrollment would be lost for |
| // example). Thus, be careful and only clear data if the TPM looks |
| // positively unowned. |
| if (tpm_init->IsTpmEnabled() && !tpm_init->IsTpmOwned()) { |
| ClearData(); |
| // Don't flag invalid here - Chrome verifies that install attributes |
| // aren't invalid before locking them as part of enterprise enrollment. |
| status_ = Status::kTpmNotOwned; |
| ReportInstallAttributesUsage(InstallAttributesUsageEvent::kTpmNotOwned); |
| return false; |
| } |
| |
| // Cases that don't look like a cleared TPM get flagged invalid. |
| status_ = Status::kInvalid; |
| ReportInstallAttributesUsage(InstallAttributesUsageEvent::kTpmInvalidState); |
| return false; |
| } |
| |
| // The TPM is ready and we haven't found valid install attributes. This |
| // usually means that we haven't written and locked the lockbox yet, so |
| // attempt a reset. The reset may fail in various other edge cases and the |
| // error code lets us identify and handle these edge cases correctly. |
| LockboxError error_id; |
| if (!lockbox()->Reset(&error_id)) { |
| switch (error_id) { |
| case LockboxError::kNvramSpaceAbsent: |
| // Legacy install that didn't create space at OOBE. |
| status_ = Status::kValid; |
| tpm_init->RemoveTpmOwnerDependency( |
| TpmPersistentState::TpmOwnerDependency::kInstallAttributes); |
| ReportInstallAttributesUsage( |
| InstallAttributesUsageEvent::kNvramSpaceAbsent); |
| return true; |
| case LockboxError::kNvramInvalid: |
| LOG(ERROR) << "Inconsistent install attributes state."; |
| status_ = Status::kInvalid; |
| ReportInstallAttributesUsage( |
| InstallAttributesUsageEvent::kNvramInvalid); |
| return false; |
| case LockboxError::kTpmUnavailable: |
| NOTREACHED() << "Should never call lockbox when TPM is unavailable."; |
| status_ = Status::kInvalid; |
| ReportInstallAttributesUsage( |
| InstallAttributesUsageEvent::kTpmUnavailable); |
| return false; |
| case LockboxError::kTpmError: |
| LOG(ERROR) << "TPM error on install attributes initialization."; |
| status_ = Status::kInvalid; |
| ReportInstallAttributesUsage(InstallAttributesUsageEvent::kTpmError); |
| return false; |
| } |
| } |
| |
| // Reset succeeded, so we have a writable lockbox now. |
| // Delete data file potentially left around from previous installation. |
| if (!ClearData()) { |
| return false; |
| } |
| |
| status_ = Status::kFirstInstall; |
| tpm_init->RemoveTpmOwnerDependency( |
| TpmPersistentState::TpmOwnerDependency::kInstallAttributes); |
| ReportInstallAttributesUsage(InstallAttributesUsageEvent::kFirstInstall); |
| 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 (status_ != Status::kFirstInstall) { |
| LOG(ERROR) << "Set() called on immutable attributes."; |
| return false; |
| } |
| |
| if (Count() == std::numeric_limits<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() { |
| switch (status_) { |
| case Status::kUnknown: |
| case Status::kTpmNotOwned: |
| case Status::kInvalid: |
| LOG(ERROR) << "Finalize() called with invalid/uninitialized data."; |
| return false; |
| case Status::kValid: |
| // Repeated calls to Finalize() are idempotent. |
| return true; |
| case Status::kFirstInstall: |
| break; |
| case Status::COUNT: |
| NOTREACHED(); |
| } |
| |
| // 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; |
| } |
| |
| LockboxError 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."; |
| attributes_->Clear(); |
| status_ = Status::kInvalid; |
| return false; |
| } |
| |
| // As the cache file is stored on tmpfs, durable write is not required but we |
| // need atomicity to be safe in case of concurrent reads. |
| if (!platform_->WriteFileAtomic(cache_file_, attr_bytes, |
| kCacheFilePermissions)) { |
| LOG(WARNING) << "Finalize() failed to create cache file."; |
| } |
| |
| LOG(INFO) << "InstallAttributes have been finalized."; |
| status_ = Status::kValid; |
| 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; |
| } |
| |
| bool InstallAttributes::ClearData() { |
| if (platform_->FileExists(data_file_) && |
| !platform_->DeleteFile(data_file_, false)) { |
| LOG(ERROR) << "Failed to delete install attributes data file!"; |
| return false; |
| } |
| return true; |
| } |
| |
| std::unique_ptr<base::Value> InstallAttributes::GetStatus() { |
| auto dv = std::make_unique<base::DictionaryValue>(); |
| dv->SetBoolean("initialized", |
| status_ == Status::kFirstInstall || status_ == Status::kValid); |
| dv->SetInteger("version", version()); |
| dv->SetInteger("lockbox_index", lockbox()->nvram_index()); |
| dv->SetInteger("lockbox_nvram_version", |
| GetNvramVersionNumber(lockbox()->nvram_version())); |
| dv->SetBoolean("secure", is_secure()); |
| dv->SetBoolean("invalid", status_ == Status::kInvalid); |
| dv->SetBoolean("first_install", status_ == Status::kFirstInstall); |
| dv->SetInteger("size", Count()); |
| if (Count()) { |
| auto attrs = std::make_unique<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", std::move(attrs)); |
| } |
| return std::move(dv); |
| } |
| |
| } // namespace cryptohome |