blob: d688ac973e3d4f74aa5c693036a6e9d7912b2965 [file] [log] [blame]
// Copyright (c) 2011 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.
// This macro must be defined before stdint.h is included explicitly or
// implicitly. It ensures UINT32_MAX is available.
#define __STDC_LIMIT_MACROS
#include "lockbox.h"
#include <arpa/inet.h>
#include <openssl/sha.h>
#include <limits.h>
#include <stdint.h>
#include <base/logging.h>
#include <base/platform_thread.h>
#include <base/time.h>
namespace cryptohome {
const uint32_t Lockbox::kReservedSizeBytes = sizeof(uint32_t);
const uint32_t Lockbox::kReservedFlagsBytes = sizeof(uint8_t);
const uint32_t Lockbox::kReservedSaltBytes = CRYPTOHOME_LOCKBOX_SALT_LENGTH;
const uint32_t Lockbox::kReservedDigestBytes = SHA256_DIGEST_LENGTH;
const uint32_t Lockbox::kReservedNvramBytes = kReservedDigestBytes +
kReservedSaltBytes +
kReservedFlagsBytes +
kReservedSizeBytes;
Lockbox::Lockbox(Tpm* tpm, uint32_t nvram_index)
: tpm_(tpm),
nvram_index_(nvram_index),
default_crypto_(new Crypto()),
crypto_(default_crypto_.get()),
contents_(new LockboxContents()) {
}
Lockbox::~Lockbox() {
}
bool Lockbox::TpmIsReady() const {
return tpm_ && tpm_->IsConnected() && tpm_->IsOwned();
}
bool Lockbox::HasAuthorization() const {
if (!TpmIsReady())
return false;
// If we have a TPM owner password, we can reset.
chromeos::Blob owner_password;
return tpm_->GetOwnerPassword(&owner_password) && owner_password.size() != 0;
}
bool Lockbox::Destroy(ErrorId* error) {
CHECK(error);
*error = kErrorIdNone;
if (!HasAuthorization())
return false;
// This is only an error if authorization is supplied.
if (tpm_->IsNvramDefined(nvram_index_) &&
!tpm_->DestroyNvram(nvram_index_)) {
*error = kErrorIdTpmError;
return false;
}
return true;
}
bool Lockbox::Create(ErrorId* error) {
CHECK(error);
*error = kErrorIdNone;
// Make sure we have what we need now.
if (!HasAuthorization()) {
*error = kErrorIdInsufficientAuthorization;
LOG(ERROR) << "Create() called with insufficient authorization.";
return false;
}
if (!Destroy(error)) {
LOG(ERROR) << "Failed to destroy lockbox data before creation.";
return false;
}
if (!tpm_->DefineLockOnceNvram(nvram_index_, kReservedNvramBytes, 0)) {
*error = kErrorIdTpmError;
LOG(ERROR) << "Create() failed to defined NVRAM space.";
return false;
}
LOG(INFO) << "Lockbox created.";
return true;
}
bool Lockbox::Load(ErrorId* error) {
CHECK(error);
// TODO(wad) Determine if we want to allow reloading later.
if (contents_->loaded)
return true;
if (!TpmIsReady()) {
LOG(ERROR) << "Load() TPM is not ready.";
*error = kErrorIdTpmNotReady;
return false;
}
if (!tpm_->IsNvramDefined(nvram_index_)) {
LOG(INFO) << "Load() called with no NVRAM space defined.";
*error = kErrorIdNoNvramSpace;
return false;
}
if (!tpm_->IsNvramLocked(nvram_index_)) {
LOG(INFO) << "Load() called prior to a successful Store()";
*error = kErrorIdNoNvramData;
return false;
}
SecureBlob nvram_data(0);
if (!tpm_->ReadNvram(nvram_index_, &nvram_data)) {
LOG(ERROR) << "Load() could not read from NVRAM space.";
*error = kErrorIdTpmError;
return false;
}
// If the read is successful, but the size is wrong, we've got tampering
// or an unexpected bug/race during set.
if (nvram_data.size() != kReservedNvramBytes) {
LOG(ERROR) << "Load() unexpected NVRAM size.";
*error = kErrorIdNvramInvalid;
return false;
}
// Extract the expected data size from the NVRAM.
if (!ParseSizeBlob(nvram_data, &contents_->size)) {
LOG(ERROR) << "Load() unable to parse NVRAM data.";
*error = kErrorIdNvramInvalid;
return false;
}
// Drop the size bytes
nvram_data.erase(nvram_data.begin(),
nvram_data.begin() + kReservedSizeBytes);
contents_->flags = nvram_data.front();
// Erase the reserved flags byte(s).
nvram_data.erase(nvram_data.begin(),
nvram_data.begin() + kReservedFlagsBytes);
// Grab the salt.
DCHECK(sizeof(contents_->salt) == kReservedSaltBytes);
memcpy(contents_->salt, &nvram_data[0], sizeof(contents_->salt));
nvram_data.erase(nvram_data.begin(),
nvram_data.begin() + kReservedSaltBytes);
DCHECK(nvram_data.size() == kReservedDigestBytes);
DCHECK(sizeof(contents_->hash) == kReservedDigestBytes);
memcpy(contents_->hash, &nvram_data[0], sizeof(contents_->hash));
DLOG(INFO) << "Load() successfully loaded NVRAM data.";
contents_->loaded = true;
return true;
}
bool Lockbox::Verify(const chromeos::Blob& blob, ErrorId* error) {
CHECK(error);
// It's not possible to verify without a locked space.
if (!contents_->loaded) {
*error = kErrorIdNoNvramData;
return false;
}
// Make sure that the file size matches what was stored in nvram.
if (blob.size() != contents_->size) {
LOG(ERROR) << "Verify() expected " << contents_->size
<< " , but read " << blob.size() << " bytes.";
*error = kErrorIdSizeMismatch;
return false;
}
// Append the salt to the data.
chromeos::Blob salty_blob(blob);
salty_blob.insert(salty_blob.end(),
contents_->salt,
contents_->salt + sizeof(contents_->salt));
SecureBlob hash(0);
crypto_->GetSha256(salty_blob, 0, salty_blob.size(), &hash);
// Maybe release the duplicate blob data.
// TODO(wad) Add Crypto::GatherSha256 which takes a vector of blobs.
salty_blob.resize(0);
DCHECK(hash.size() == kReservedDigestBytes);
// Validate the data hash versus the stored hash.
if (memcmp(contents_->hash,
hash.data(),
sizeof(contents_->hash))) {
LOG(ERROR) << "Verify() hash mismatch!";
*error = kErrorIdHashMismatch;
return false;
}
DLOG(INFO) << "Verify() verified "
<< blob.size() << " of " << contents_->size << " bytes.";
return true;
}
bool Lockbox::Store(const chromeos::Blob& blob, ErrorId* error) {
if (!TpmIsReady()) {
LOG(ERROR) << "Store() called when TPM was not ready!";
*error = kErrorIdTpmError;
return false;
}
// Grab a salt from the TPM.
chromeos::Blob salt(0);
if (!tpm_->GetRandomData(kReservedSaltBytes, &salt)) {
LOG(ERROR) << "Store() failed to get a seed from the TPM.";
*error = kErrorIdTpmError;
return false;
}
// Keep the data locally too.
DCHECK(kReservedSaltBytes == sizeof(contents_->salt));
memcpy(contents_->salt, &salt[0], sizeof(contents_->salt));
// Get the size of the data blob
chromeos::Blob size_blob;
if (!GetSizeBlob(blob, &size_blob)) {
LOG(ERROR) << "Store() data blob is too large.";
*error = kErrorIdTooLarge;
return false;
}
contents_->size = blob.size();
// Append the salt to the data and hash.
chromeos::Blob salty_blob(blob);
salty_blob.insert(salty_blob.end(), salt.begin(), salt.end());
SecureBlob nvram_blob(0);
crypto_->GetSha256(salty_blob, 0, salty_blob.size(), &nvram_blob);
memcpy(contents_->hash, &nvram_blob[0], sizeof(contents_->hash));
// Insert the salt into the NVRAM.
nvram_blob.insert(nvram_blob.begin(), salt.begin(), salt.end());
// Insert the flags byte. At present, this is always 0. It exists
// to allow for future format changes that can be indicated without relying
// on untrusted-file parsing-based detection, such as digest algorithm
// changes or the addition of encryption.
nvram_blob.insert(nvram_blob.begin(), static_cast<unsigned char>(0));
contents_->flags = 0;
// Insert size prefix.
nvram_blob.insert(nvram_blob.begin(), size_blob.begin(), size_blob.end());
// The resulting NVRAM space should look like:
// [size_blob][flags][salt][nvram_blob]
// Ensure we have the spaces ready.
if (!tpm_->IsNvramDefined(nvram_index_)) {
LOG(ERROR) << "Store() called with no NVRAM space.";
*error = kErrorIdNoNvramSpace;
return false;
}
if (tpm_->IsNvramLocked(nvram_index_)) {
LOG(ERROR) << "Store() called with a locked NVRAM space.";
*error = kErrorIdNvramInvalid;
return false;
}
// Write the hash to nvram
if (!tpm_->WriteNvram(nvram_index_, nvram_blob)) {
LOG(ERROR) << "Store() failed to write the attribute hash to NVRAM";
*error = kErrorIdTpmError;
return false;
}
SecureBlob lock(0);
// Write 0 to the nvram
if (!tpm_->WriteNvram(nvram_index_, lock)) {
LOG(ERROR) << "Store() failed to lock the NVRAM space";
*error = kErrorIdTpmError;
return false;
}
// Ensure the space is now locked.
if (!tpm_->IsNvramLocked(nvram_index_)) {
LOG(ERROR) << "NVRAM space did not lock as expected.";
*error = kErrorIdNvramFailedToLock;
return false;
}
return true;
LOG(INFO) << "Store()'d " << blob.size() << " bytes";
return true;
}
bool Lockbox::GetSizeBlob(const chromeos::Blob& data,
chromeos::Blob* size_bytes) const {
uint32_t serializable_size = 0;
if (data.size() > UINT32_MAX)
return false;
// Use network byte order prior to marshalling to ensure safe
// unmarshalling if we somehow change endianness.
serializable_size = htonl(static_cast<uint32_t>(data.size()));
// Push it back starting with the first byte.
size_bytes->resize(0);
for (uint32_t bytes = 0; bytes < kReservedSizeBytes; ++bytes) {
size_bytes->push_back(
static_cast<char>((serializable_size >> (CHAR_BIT * bytes)) & 0xff));
}
return true;
}
bool Lockbox::ParseSizeBlob(const chromeos::Blob& blob, uint32_t* size) const {
CHECK(size);
*size = 0;
if (blob.size() < kReservedSizeBytes)
return false;
// Unmarshal it from NVRAM based on how it was written.
uint32_t stored_size = 0;
for (uint32_t bytes = 0; bytes < kReservedSizeBytes; ++bytes) {
stored_size |= static_cast<uint32_t>(blob.at(bytes)) << (bytes * CHAR_BIT);
}
// Now convert back from network byte order.
*size = ntohl(stored_size);
return true;
}
} // namespace cryptohome