blob: 70a7a1e3907fc9fcfedab4c34e338f73c243284d [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.
// Contains the implementation of class TpmInit
#include "cryptohome/tpm_init.h"
#include <stdint.h>
#include <string>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/threading/platform_thread.h>
#include <base/time/time.h>
#include "cryptohome/attestation.h"
#include "cryptohome/cryptolib.h"
#include "cryptohome/interface.h"
using base::FilePath;
using base::PlatformThread;
using base::PlatformThreadHandle;
using brillo::SecureBlob;
namespace cryptohome {
const int kMaxTimeoutRetries = 5;
const FilePath kMiscTpmCheckEnabledFile("/sys/class/misc/tpm0/device/enabled");
const FilePath kMiscTpmCheckOwnedFile("/sys/class/misc/tpm0/device/owned");
const FilePath kTpmTpmCheckEnabledFile("/sys/class/tpm/tpm0/device/enabled");
const FilePath kTpmTpmCheckOwnedFile("/sys/class/tpm/tpm0/device/owned");
const FilePath kTpmOwnedFileOld("/var/lib/.tpm_owned");
const FilePath kTpmStatusFileOld("/var/lib/.tpm_status");
extern const FilePath kTpmOwnedFile("/mnt/stateful_partition/.tpm_owned");
const FilePath kTpmStatusFile("/mnt/stateful_partition/.tpm_status");
const FilePath kOpenCryptokiPath("/var/lib/opencryptoki");
const FilePath kDefaultCryptohomeKeyFile("/home/.shadow/cryptohome.key");
const int kOwnerPasswordLength = 12;
const unsigned int kDefaultTpmRsaKeyBits = 2048;
// 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) {
const bool was_initialized = get_tpm()->IsInitialized();
if (!was_initialized) {
get_tpm()->SetIsInitialized(true);
RestoreTpmStateFromStorage();
}
if (load_key) {
// load cryptohome key
LoadOrCreateCryptohomeKey(&cryptohome_key_);
}
return !was_initialized;
}
void TpmInit::RestoreTpmStateFromStorage() {
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(kTpmTpmCheckEnabledFile)) {
is_enabled = IsEnabledCheckViaSysfs(kTpmTpmCheckEnabledFile);
is_owned = IsOwnedCheckViaSysfs(kTpmTpmCheckOwnedFile);
successful_check = true;
} else if (platform_->FileExists(kMiscTpmCheckEnabledFile)) {
is_enabled = IsEnabledCheckViaSysfs(kMiscTpmCheckEnabledFile);
is_owned = IsOwnedCheckViaSysfs(kMiscTpmCheckOwnedFile);
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);
}
}
}
}
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 FilePath& 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(const FilePath& enabled_file) {
return CheckSysfsForOne(enabled_file);
}
bool TpmInit::IsOwnedCheckViaSysfs(const FilePath& owned_file) {
return CheckSysfsForOne(owned_file);
}
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