| // 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. |
| |
| // Contains the implementation of class TpmInit |
| |
| #include "cryptohome/tpm_init.h" |
| |
| #include <stdint.h> |
| |
| #include <string> |
| |
| #include <base/logging.h> |
| #include <base/threading/platform_thread.h> |
| #include <base/time/time.h> |
| #include <trousers/scoped_tss_type.h> |
| |
| #include "cryptohome/attestation.h" |
| #include "cryptohome/cryptolib.h" |
| #include "cryptohome/interface.h" |
| |
| using base::PlatformThread; |
| using base::PlatformThreadHandle; |
| using brillo::SecureBlob; |
| |
| namespace cryptohome { |
| |
| const int kMaxTimeoutRetries = 5; |
| |
| const char* kTpmCheckEnabledFile = "/sys/class/misc/tpm0/device/enabled"; |
| const char* kTpmCheckOwnedFile = "/sys/class/misc/tpm0/device/owned"; |
| const char* kTpmOwnedFileOld = "/var/lib/.tpm_owned"; |
| const char* kTpmStatusFileOld = "/var/lib/.tpm_status"; |
| const char* kTpmOwnedFile = "/mnt/stateful_partition/.tpm_owned"; |
| const char* kTpmStatusFile = "/mnt/stateful_partition/.tpm_status"; |
| const char* kOpenCryptokiPath = "/var/lib/opencryptoki"; |
| const char kDefaultCryptohomeKeyFile[] = "/home/.shadow/cryptohome.key"; |
| |
| const int kOwnerPasswordLength = 12; |
| const unsigned int kDefaultTpmRsaKeyBits = 2048; |
| const char kTpmWellKnownPassword[] = TSS_WELL_KNOWN_SECRET; |
| |
| // TpmInitTask is a private class used to handle asynchronous initialization of |
| // the TPM. |
| class TpmInitTask : public PlatformThread::Delegate { |
| public: |
| TpmInitTask() : tpm_(NULL), init_(NULL) { |
| } |
| |
| virtual ~TpmInitTask() { |
| } |
| |
| void Init(TpmInit* init) { |
| init_ = init; |
| if (tpm_) { |
| init->SetupTpm(false); |
| } |
| } |
| |
| virtual void ThreadMain() { |
| if (init_) { |
| init_->ThreadMain(); |
| } |
| } |
| |
| void set_tpm(Tpm* tpm) { |
| tpm_ = tpm; |
| } |
| |
| Tpm* get_tpm() { |
| return tpm_; |
| } |
| |
| private: |
| Tpm* tpm_; |
| TpmInit* init_; |
| }; |
| |
| TpmInit::TpmInit(Tpm* tpm, Platform* platform) |
| : tpm_init_task_(new TpmInitTask()), |
| notify_callback_(NULL), |
| initialize_called_(false), |
| initialize_took_ownership_(false), |
| initialization_time_(0), |
| platform_(platform) { |
| set_tpm(tpm); |
| } |
| |
| TpmInit::~TpmInit() { |
| if (!init_thread_.is_null()) { |
| // Must wait for tpm init thread to complete, because when the main thread |
| // exits some libtspi data structures are freed. |
| PlatformThread::Join(init_thread_); |
| init_thread_ = PlatformThreadHandle(); |
| } |
| } |
| |
| void TpmInit::set_tpm(Tpm* value) { |
| if (tpm_init_task_.get()) |
| tpm_init_task_->set_tpm(value); |
| } |
| |
| Tpm* TpmInit::get_tpm() { |
| if (tpm_init_task_.get()) |
| return tpm_init_task_->get_tpm(); |
| return NULL; |
| } |
| |
| void TpmInit::Init(TpmInitCallback* notify_callback) { |
| notify_callback_ = notify_callback; |
| tpm_init_task_->Init(this); |
| } |
| |
| bool TpmInit::AsyncInitializeTpm() { |
| initialize_called_ = true; |
| if (!PlatformThread::Create(0, tpm_init_task_.get(), &init_thread_)) { |
| LOG(ERROR) << "Unable to create TPM initialization background thread."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool TpmInit::IsTpmReady() { |
| // The TPM is "ready" if it is enabled, owned, and not being owned. |
| return (tpm_init_task_->get_tpm()->IsEnabled() && |
| tpm_init_task_->get_tpm()->IsOwned() && |
| !tpm_init_task_->get_tpm()->IsBeingOwned()); |
| } |
| |
| bool TpmInit::IsTpmEnabled() { |
| return tpm_init_task_->get_tpm()->IsEnabled(); |
| } |
| |
| bool TpmInit::IsTpmOwned() { |
| return tpm_init_task_->get_tpm()->IsOwned(); |
| } |
| |
| void TpmInit::SetTpmOwned(bool owned) { |
| tpm_init_task_->get_tpm()->SetIsOwned(owned); |
| } |
| |
| bool TpmInit::IsTpmBeingOwned() { |
| return tpm_init_task_->get_tpm()->IsBeingOwned(); |
| } |
| |
| void TpmInit::SetTpmBeingOwned(bool being_owned) { |
| tpm_init_task_->get_tpm()->SetIsBeingOwned(being_owned); |
| } |
| |
| bool TpmInit::HasInitializeBeenCalled() { |
| return initialize_called_; |
| } |
| |
| bool TpmInit::GetTpmPassword(brillo::Blob* password) { |
| return tpm_init_task_->get_tpm()->GetOwnerPassword(password); |
| } |
| |
| void TpmInit::ClearStoredTpmPassword() { |
| TpmStatus tpm_status; |
| if (LoadTpmStatus(&tpm_status)) { |
| int32_t dependency_flags = TpmStatus::INSTALL_ATTRIBUTES_NEEDS_OWNER | |
| TpmStatus::ATTESTATION_NEEDS_OWNER; |
| if (tpm_status.flags() & dependency_flags) { |
| // The password is still needed, do not clear. |
| return; |
| } |
| if (tpm_status.has_owner_password()) { |
| tpm_status.clear_owner_password(); |
| StoreTpmStatus(tpm_status); |
| } |
| } |
| SecureBlob empty; |
| get_tpm()->SetOwnerPassword(empty); |
| } |
| |
| void TpmInit::ThreadMain() { |
| base::TimeTicks start = base::TimeTicks::Now(); |
| bool initialize_result = InitializeTpm( |
| &initialize_took_ownership_); |
| base::TimeDelta delta = (base::TimeTicks::Now() - start); |
| initialization_time_ = delta.InMilliseconds(); |
| if (initialize_took_ownership_) { |
| LOG(ERROR) << "TPM initialization took " << initialization_time_ << "ms"; |
| } |
| if (notify_callback_) { |
| notify_callback_->InitializeTpmComplete(initialize_result, |
| initialize_took_ownership_); |
| } |
| } |
| |
| void TpmInit::MigrateStatusFiles() { |
| if (!platform_->FileExists(kTpmOwnedFile) && |
| platform_->FileExists(kTpmOwnedFileOld)) { |
| platform_->Move(kTpmOwnedFileOld, kTpmOwnedFile); |
| } |
| if (!platform_->FileExists(kTpmStatusFile) && |
| platform_->FileExists(kTpmStatusFileOld)) { |
| platform_->Move(kTpmStatusFileOld, kTpmStatusFile); |
| } |
| } |
| |
| bool TpmInit::SetupTpm(bool load_key) { |
| if (get_tpm()->IsInitialized()) { |
| return false; |
| } |
| get_tpm()->SetIsInitialized(true); |
| |
| MigrateStatusFiles(); |
| |
| // Checking disabled and owned either via sysfs or via TSS calls will block if |
| // ownership is being taken by another thread or process. So for this to work |
| // well, SetupTpm() needs to be called before InitializeTpm() is called. At |
| // that point, the public API for Tpm only checks these booleans, so other |
| // threads can check without being blocked. InitializeTpm() will reset the |
| // TPM's is_owned_ bit on success. |
| bool is_enabled = false; |
| bool is_owned = false; |
| bool successful_check = false; |
| if (platform_->FileExists(kTpmCheckEnabledFile)) { |
| is_enabled = IsEnabledCheckViaSysfs(); |
| is_owned = IsOwnedCheckViaSysfs(); |
| successful_check = true; |
| } else { |
| if (get_tpm()->PerformEnabledOwnedCheck(&is_enabled, &is_owned)) { |
| successful_check = true; |
| } |
| } |
| |
| get_tpm()->SetIsOwned(is_owned); |
| get_tpm()->SetIsEnabled(is_enabled); |
| |
| if (successful_check && !is_owned) { |
| platform_->DeleteFileDurable(kOpenCryptokiPath, true); |
| platform_->DeleteFileDurable(kTpmOwnedFile, false); |
| platform_->DeleteFileDurable(kTpmStatusFile, false); |
| } |
| if (successful_check && is_owned) { |
| if (!platform_->FileExists(kTpmOwnedFile)) { |
| platform_->TouchFileDurable(kTpmOwnedFile); |
| } |
| } |
| |
| TpmStatus tpm_status; |
| if (LoadTpmStatus(&tpm_status)) { |
| if (tpm_status.has_owner_password()) { |
| SecureBlob local_owner_password; |
| if (LoadOwnerPassword(tpm_status, &local_owner_password)) { |
| get_tpm()->SetOwnerPassword(local_owner_password); |
| } |
| } |
| } |
| |
| if (load_key) { |
| // load cryptohome key |
| LoadOrCreateCryptohomeKey(&cryptohome_key_); |
| } |
| return true; |
| } |
| |
| bool TpmInit::InitializeTpm(bool* OUT_took_ownership) { |
| TpmStatus tpm_status; |
| |
| if (!LoadTpmStatus(&tpm_status)) { |
| tpm_status.Clear(); |
| tpm_status.set_flags(TpmStatus::NONE); |
| } |
| |
| if (OUT_took_ownership) { |
| *OUT_took_ownership = false; |
| } |
| |
| if (!IsTpmEnabled()) { |
| return false; |
| } |
| |
| SecureBlob default_owner_password(sizeof(kTpmWellKnownPassword)); |
| memcpy(default_owner_password.data(), kTpmWellKnownPassword, |
| sizeof(kTpmWellKnownPassword)); |
| |
| bool took_ownership = false; |
| if (!IsTpmOwned()) { |
| SetTpmBeingOwned(true); |
| platform_->DeleteFileDurable(kOpenCryptokiPath, true); |
| platform_->DeleteFileDurable(kTpmOwnedFile, false); |
| platform_->DeleteFileDurable(kTpmStatusFile, false); |
| |
| if (!get_tpm()->IsEndorsementKeyAvailable()) { |
| if (!get_tpm()->CreateEndorsementKey()) { |
| LOG(ERROR) << "Failed to create endorsement key"; |
| SetTpmBeingOwned(false); |
| return false; |
| } |
| } |
| |
| if (!get_tpm()->IsEndorsementKeyAvailable()) { |
| LOG(ERROR) << "Endorsement key is not available"; |
| SetTpmBeingOwned(false); |
| return false; |
| } |
| |
| if (!get_tpm()->TakeOwnership(kMaxTimeoutRetries, default_owner_password)) { |
| LOG(ERROR) << "Take Ownership failed"; |
| SetTpmBeingOwned(false); |
| return false; |
| } |
| |
| SetTpmOwned(true); |
| took_ownership = true; |
| |
| tpm_status.set_flags(TpmStatus::OWNED_BY_THIS_INSTALL | |
| TpmStatus::USES_WELL_KNOWN_OWNER | |
| TpmStatus::INSTALL_ATTRIBUTES_NEEDS_OWNER | |
| TpmStatus::ATTESTATION_NEEDS_OWNER); |
| tpm_status.clear_owner_password(); |
| StoreTpmStatus(tpm_status); |
| } |
| |
| if (OUT_took_ownership) { |
| *OUT_took_ownership = took_ownership; |
| } |
| |
| // If we can open the TPM with the default password, then we still need to |
| // zero the SRK password and unrestrict it, then change the owner password. |
| if (!platform_->FileExists(kTpmOwnedFile) && |
| get_tpm()->TestTpmAuth(default_owner_password)) { |
| if (!get_tpm()->InitializeSrk(default_owner_password)) { |
| LOG(ERROR) << "Couldn't initialize the SRK"; |
| SetTpmBeingOwned(false); |
| return false; |
| } |
| |
| SecureBlob owner_password; |
| CreateOwnerPassword(&owner_password); |
| |
| tpm_status.set_flags(TpmStatus::OWNED_BY_THIS_INSTALL | |
| TpmStatus::USES_RANDOM_OWNER | |
| TpmStatus::INSTALL_ATTRIBUTES_NEEDS_OWNER | |
| TpmStatus::ATTESTATION_NEEDS_OWNER); |
| if (!StoreOwnerPassword(owner_password, &tpm_status)) { |
| tpm_status.clear_owner_password(); |
| } |
| StoreTpmStatus(tpm_status); |
| |
| if ((get_tpm()->ChangeOwnerPassword(default_owner_password, |
| owner_password))) { |
| get_tpm()->SetOwnerPassword(owner_password); |
| } |
| platform_->TouchFileDurable(kTpmOwnedFile); |
| } else { |
| // If we fall through here, then the TPM owned file doesn't exist, but we |
| // couldn't auth with the well-known password. In this case, we must assume |
| // that the TPM has already been owned and set to a random password, so |
| // touch the TPM owned file. |
| if (!platform_->FileExists(kTpmOwnedFile)) { |
| platform_->TouchFileDurable(kTpmOwnedFile); |
| } |
| } |
| |
| SetTpmBeingOwned(false); |
| return true; |
| } |
| |
| bool TpmInit::LoadTpmStatus(TpmStatus* serialized) { |
| if (!platform_->FileExists(kTpmStatusFile)) { |
| return false; |
| } |
| SecureBlob file_data; |
| if (!platform_->ReadFile(kTpmStatusFile, &file_data)) { |
| return false; |
| } |
| if (!serialized->ParseFromArray(file_data.data(), file_data.size())) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool TpmInit::StoreTpmStatus(const TpmStatus& serialized) { |
| if (platform_->FileExists(kTpmStatusFile)) { |
| // Shred old status file, not very useful on SSD. :( |
| do { |
| int64_t file_size; |
| if (!platform_->GetFileSize(kTpmStatusFile, &file_size)) { |
| break; |
| } |
| SecureBlob random; |
| if (!get_tpm()->GetRandomData(file_size, &random)) { |
| break; |
| } |
| platform_->WriteFile(kTpmStatusFile, random); |
| platform_->DataSyncFile(kTpmStatusFile); |
| } while (false); |
| platform_->DeleteFile(kTpmStatusFile, false); |
| } |
| |
| SecureBlob final_blob(serialized.ByteSize()); |
| serialized.SerializeWithCachedSizesToArray( |
| static_cast<google::protobuf::uint8*>(final_blob.data())); |
| bool ok = platform_->WriteFileAtomicDurable(kTpmStatusFile, final_blob, 0600); |
| return ok; |
| } |
| |
| void TpmInit::CreateOwnerPassword(SecureBlob* password) { |
| // Generate a random owner password. The default is a 12-character, |
| // hex-encoded password created from 6 bytes of random data. |
| SecureBlob random(kOwnerPasswordLength / 2); |
| CryptoLib::GetSecureRandom(random.data(), random.size()); |
| SecureBlob tpm_password(kOwnerPasswordLength); |
| CryptoLib::BlobToHexToBuffer(random, |
| tpm_password.data(), |
| tpm_password.size()); |
| password->swap(tpm_password); |
| } |
| |
| bool TpmInit::LoadOwnerPassword(const TpmStatus& tpm_status, |
| brillo::Blob* owner_password) { |
| if (!(tpm_status.flags() & TpmStatus::OWNED_BY_THIS_INSTALL)) { |
| return false; |
| } |
| if ((tpm_status.flags() & TpmStatus::USES_WELL_KNOWN_OWNER)) { |
| SecureBlob default_owner_password(sizeof(kTpmWellKnownPassword)); |
| memcpy(default_owner_password.data(), kTpmWellKnownPassword, |
| sizeof(kTpmWellKnownPassword)); |
| owner_password->swap(default_owner_password); |
| return true; |
| } |
| if (!(tpm_status.flags() & TpmStatus::USES_RANDOM_OWNER) || |
| !tpm_status.has_owner_password()) { |
| return false; |
| } |
| |
| SecureBlob local_owner_password(tpm_status.owner_password().length()); |
| tpm_status.owner_password().copy( |
| local_owner_password.char_data(), |
| tpm_status.owner_password().length(), 0); |
| if (!get_tpm()->Unseal(local_owner_password, owner_password)) { |
| LOG(ERROR) << "Failed to unseal the owner password."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool TpmInit::StoreOwnerPassword(const brillo::Blob& owner_password, |
| TpmStatus* tpm_status) { |
| // Use PCR0 when sealing the data so that the owner password is only |
| // available in the current boot mode. This helps protect the password from |
| // offline attacks until it has been presented and cleared. |
| SecureBlob sealed_password; |
| if (!get_tpm()->SealToPCR0(owner_password, &sealed_password)) { |
| LOG(ERROR) << "StoreOwnerPassword: Failed to seal owner password."; |
| return false; |
| } |
| tpm_status->set_owner_password(sealed_password.data(), |
| sealed_password.size()); |
| return true; |
| } |
| |
| void TpmInit::RemoveTpmOwnerDependency(TpmOwnerDependency dependency) { |
| int32_t flag_to_clear = TpmStatus::NONE; |
| switch (dependency) { |
| case kInstallAttributes: |
| flag_to_clear = TpmStatus::INSTALL_ATTRIBUTES_NEEDS_OWNER; |
| break; |
| case kAttestation: |
| flag_to_clear = TpmStatus::ATTESTATION_NEEDS_OWNER; |
| break; |
| default: |
| CHECK(false); |
| } |
| TpmStatus tpm_status; |
| if (!LoadTpmStatus(&tpm_status)) |
| return; |
| tpm_status.set_flags(tpm_status.flags() & ~flag_to_clear); |
| StoreTpmStatus(tpm_status); |
| } |
| |
| bool TpmInit::CheckSysfsForOne(const char* file_name) const { |
| std::string contents; |
| if (!platform_->ReadFileToString(file_name, &contents)) { |
| return false; |
| } |
| if (contents.size() < 1) { |
| return false; |
| } |
| return (contents[0] == '1'); |
| } |
| |
| bool TpmInit::IsEnabledCheckViaSysfs() { |
| return CheckSysfsForOne(kTpmCheckEnabledFile); |
| } |
| |
| bool TpmInit::IsOwnedCheckViaSysfs() { |
| return CheckSysfsForOne(kTpmCheckOwnedFile); |
| } |
| |
| bool TpmInit::CreateCryptohomeKey() { |
| SecureBlob n; |
| SecureBlob p; |
| if (!CryptoLib::CreateRsaKey(kDefaultTpmRsaKeyBits, &n, &p)) { |
| LOG(ERROR) << "Error creating RSA key"; |
| return false; |
| } |
| brillo::SecureBlob wrapped_key; |
| if (!get_tpm()->WrapRsaKey(n, p, &wrapped_key)) { |
| LOG(ERROR) << "Couldn't wrap cryptohome key"; |
| return false; |
| } |
| |
| if (!SaveCryptohomeKey(wrapped_key)) { |
| LOG(ERROR) << "Couldn't save cryptohome key"; |
| return false; |
| } |
| |
| LOG(INFO) << "Created new cryptohome key."; |
| return true; |
| } |
| |
| bool TpmInit::SaveCryptohomeKey(const brillo::SecureBlob& raw_key) { |
| bool ok = platform_->WriteFileAtomicDurable(kDefaultCryptohomeKeyFile, |
| raw_key, 0600); |
| if (!ok) |
| LOG(ERROR) << "Error writing key file of desired size: " << raw_key.size(); |
| return ok; |
| } |
| |
| bool TpmInit::LoadCryptohomeKey(ScopedKeyHandle* key_handle) { |
| CHECK(key_handle); |
| // First, try loading the key from the key file |
| { |
| SecureBlob raw_key; |
| if (platform_->ReadFile(kDefaultCryptohomeKeyFile, &raw_key)) { |
| Tpm::TpmRetryAction retry_action = get_tpm()->LoadWrappedKey( |
| raw_key, key_handle); |
| if (retry_action == Tpm::kTpmRetryNone) { |
| return true; |
| } |
| if (get_tpm()->IsTransient(retry_action)) { |
| return false; |
| } |
| } |
| } |
| |
| // Then try loading the key by the UUID (this is a legacy upgrade path) |
| SecureBlob raw_key; |
| if (!get_tpm()->LegacyLoadCryptohomeKey(key_handle, |
| &raw_key)) { |
| return false; |
| } |
| |
| // Save the cryptohome key to the well-known location |
| if (!SaveCryptohomeKey(raw_key)) { |
| LOG(ERROR) << "Couldn't save cryptohome key"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool TpmInit::LoadOrCreateCryptohomeKey(ScopedKeyHandle* key_handle) { |
| CHECK(key_handle); |
| // Try to load the cryptohome key. |
| if (LoadCryptohomeKey(key_handle)) { |
| return true; |
| } |
| |
| // Otherwise, the key couldn't be loaded, and it wasn't due to a transient |
| // error, so we must create the key. |
| if (CreateCryptohomeKey()) { |
| if (LoadCryptohomeKey(key_handle)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool TpmInit::HasCryptohomeKey() { |
| return (cryptohome_key_.value() != kInvalidKeyHandle); |
| } |
| |
| TpmKeyHandle TpmInit::GetCryptohomeKey() { |
| return cryptohome_key_.value(); |
| } |
| |
| bool TpmInit::ReloadCryptohomeKey() { |
| CHECK(HasCryptohomeKey()); |
| if (!LoadCryptohomeKey(&cryptohome_key_)) { |
| LOG(ERROR) << "Error reloading Cryptohome key."; |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace cryptohome |