blob: 237851aafd25c4a1a3d359f288dbeb58ccfa5591 [file] [log] [blame]
// Copyright (c) 2013 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/lockbox.h"
#include <arpa/inet.h>
#include <limits.h>
#include <openssl/sha.h>
#include <stdint.h>
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/strings/string_split.h>
#include <base/sys_byteorder.h>
#include <base/threading/platform_thread.h>
#include <base/time/time.h>
#include <brillo/secure_blob.h>
#include "cryptohome/cryptolib.h"
#include "cryptohome/platform.h"
using base::FilePath;
using brillo::SecureBlob;
namespace cryptohome {
namespace {
// Literals for running mount-encrypted helper.
constexpr char kMountEncrypted[] = "/usr/sbin/mount-encrypted";
constexpr char kMountEncryptedFinalize[] = "finalize";
} // namespace
std::ostream& operator<<(std::ostream& out, LockboxError error) {
return out << static_cast<int>(error);
}
int GetNvramVersionNumber(NvramVersion version) {
switch (version) {
case NvramVersion::kVersion1:
return 1;
case NvramVersion::kVersion2:
return 2;
}
NOTREACHED();
return 0;
}
Lockbox::Lockbox(Tpm* tpm, uint32_t nvram_index)
: tpm_(tpm),
nvram_index_(nvram_index),
default_process_(new brillo::ProcessImpl()),
process_(default_process_.get()),
default_platform_(new Platform()),
platform_(default_platform_.get()) {}
Lockbox::~Lockbox() {}
bool Lockbox::Reset(LockboxError* error) {
if (!tpm_ || !tpm_->IsEnabled() || !tpm_->IsOwned()) {
*error = LockboxError::kTpmUnavailable;
LOG(ERROR) << "TPM unavailable";
return false;
}
// If we have authorization, recreate the lockbox space.
brillo::SecureBlob owner_password;
if (tpm_->GetOwnerPassword(&owner_password) && owner_password.size() != 0) {
if (tpm_->IsNvramDefined(nvram_index_) &&
!tpm_->DestroyNvram(nvram_index_)) {
LOG(ERROR) << "Failed to destroy lockbox data before creation.";
*error = LockboxError::kTpmError;
return false;
}
// If we store the encryption salt in lockbox, protect it from reading
// in non-verified boot mode.
uint32_t nvram_perm =
Tpm::kTpmNvramWriteDefine |
(IsKeyMaterialInLockbox() ? Tpm::kTpmNvramBindToPCR0 : 0);
uint32_t nvram_bytes = LockboxContents::GetNvramSize(nvram_version_);
if (!tpm_->DefineNvram(nvram_index_, nvram_bytes, nvram_perm)) {
*error = LockboxError::kTpmError;
LOG(ERROR) << "Failed to define NVRAM space.";
return false;
}
LOG(INFO) << "Lockbox created.";
return true;
} else {
LOG(WARNING) << "No owner password when trying to reset LockBox.";
}
// Check if the space is already set up correctly.
if (!tpm_->IsNvramDefined(nvram_index_)) {
LOG(ERROR) << "NVRAM space absent when resetting LockBox.";
*error = LockboxError::kNvramSpaceAbsent;
return false;
}
if (tpm_->IsNvramLocked(nvram_index_)) {
LOG(ERROR) << "NVRAM space locked after resetting LockBox.";
*error = LockboxError::kNvramInvalid;
return false;
}
// The NVRAM space that we are looking at is not created by us, and it is too
// expensive to extensively inspect it. Given the above, we aren't sure about
// all its attributes, all we know is that:
// 1. It's not locked.
// 2. It exists (is defined).
// Therefore, it is highly likely that the NVRAM space is writable, and
// suitable for our use case. The most probable scenario is that this NVRAM
// index is created by previous installations of Chromium OS, so we'll just
// continue to use it.
LOG(INFO) << "Existing Lockbox seems writable.";
return true;
}
bool Lockbox::Store(const brillo::Blob& blob, LockboxError* error) {
if (!tpm_ || !tpm_->IsEnabled()) {
*error = LockboxError::kTpmUnavailable;
LOG(ERROR) << "TPM unavailable";
return false;
}
if (!tpm_->IsNvramDefined(nvram_index_) ||
tpm_->IsNvramLocked(nvram_index_)) {
*error = LockboxError::kNvramInvalid;
return false;
}
// Check defined NVRAM size and construct a suitable LockboxContents instance.
unsigned int nvram_size = tpm_->GetNvramSize(nvram_index_);
std::unique_ptr<LockboxContents> contents = LockboxContents::New(nvram_size);
if (!contents) {
LOG(ERROR) << "Unsupported NVRAM space size " << nvram_size << ".";
*error = LockboxError::kNvramInvalid;
return false;
}
// Grab key material from the TPM.
brillo::SecureBlob key_material(contents->key_material_size());
if (IsKeyMaterialInLockbox()) {
if (!tpm_->GetRandomDataSecureBlob(key_material.size(), &key_material)) {
LOG(ERROR) << "Failed to get key material from the TPM.";
*error = LockboxError::kTpmError;
return false;
}
} else {
// Save a TPM command, and just fill the salt field with zeroes.
LOG(INFO) << "Skipping random salt generation.";
}
brillo::SecureBlob nvram_blob;
if (!contents->SetKeyMaterial(key_material) || !contents->Protect(blob) ||
!contents->Encode(&nvram_blob)) {
LOG(ERROR) << "Failed to set up lockbox contents.";
*error = LockboxError::kNvramInvalid;
return false;
}
// Write the hash to nvram
if (!tpm_->WriteNvram(nvram_index_,
SecureBlob(nvram_blob.begin(), nvram_blob.end()))) {
LOG(ERROR) << "Store() failed to write the attribute hash to NVRAM";
*error = LockboxError::kTpmError;
return false;
}
// Lock nvram index for writing.
if (!tpm_->WriteLockNvram(nvram_index_)) {
LOG(ERROR) << "Store() failed to lock the NVRAM space";
*error = LockboxError::kTpmError;
return false;
}
// Ensure the space is now locked.
if (!tpm_->IsNvramLocked(nvram_index_)) {
LOG(ERROR) << "NVRAM space did not lock as expected.";
*error = LockboxError::kTpmError;
return false;
}
// Call out to mount-encrypted now that salt has been written.
FinalizeMountEncrypted(contents->version() == NvramVersion::kVersion1
? nvram_blob
: key_material);
return true;
}
// TODO(keescook) Write unittests for this.
void Lockbox::FinalizeMountEncrypted(const brillo::SecureBlob& entropy) const {
std::string hex;
FilePath outfile_path;
FILE* outfile;
int rc;
// Take hash of entropy and convert to hex string for cmdline.
SecureBlob hash = CryptoLib::Sha256(entropy);
hex = CryptoLib::SecureBlobToHex(hash);
process_->Reset(0);
process_->AddArg(kMountEncrypted);
process_->AddArg(kMountEncryptedFinalize);
process_->AddArg(hex);
// Redirect stdout/stderr somewhere useful for error reporting.
outfile = platform_->CreateAndOpenTemporaryFile(&outfile_path);
if (outfile) {
process_->BindFd(fileno(outfile), STDOUT_FILENO);
process_->BindFd(fileno(outfile), STDERR_FILENO);
}
rc = process_->Run();
if (rc) {
LOG(ERROR) << "Request to finalize encrypted mount failed ('"
<< kMountEncrypted << " " << kMountEncryptedFinalize << " "
<< hex << "', rc:" << rc << ")";
if (outfile) {
std::vector<std::string> output;
std::vector<std::string>::iterator it;
std::string contents;
if (platform_->ReadFileToString(outfile_path, &contents)) {
output = base::SplitString(contents, "\n", base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
for (it = output.begin(); it < output.end(); it++) {
LOG(ERROR) << *it;
}
}
}
} else {
LOG(INFO) << "Encrypted partition finalized.";
}
if (outfile)
platform_->CloseFile(outfile);
return;
}
// static
std::unique_ptr<LockboxContents> LockboxContents::New(size_t nvram_size) {
// Make sure |nvram_size| corresponds to one of the encoding versions.
if (GetNvramSize(NvramVersion::kVersion1) != nvram_size &&
GetNvramSize(NvramVersion::kVersion2) != nvram_size) {
return nullptr;
}
std::unique_ptr<LockboxContents> result(new LockboxContents());
result->key_material_.resize(nvram_size - kFixedPartSize);
return result;
}
bool LockboxContents::Decode(const brillo::SecureBlob& nvram_data) {
// Reject data of incorrect size.
if (nvram_data.size() != GetNvramSize(version())) {
return false;
}
brillo::SecureBlob::const_iterator cursor = nvram_data.begin();
// Extract the expected data size from the NVRAM. For historic reasons, this
// is encoded in reverse host byte order (!).
uint32_t reversed_size;
uint8_t* reversed_size_ptr = reinterpret_cast<uint8_t*>(&reversed_size);
std::copy(cursor, cursor + sizeof(reversed_size), reversed_size_ptr);
cursor += sizeof(reversed_size);
size_ = base::ByteSwap(reversed_size);
// Grab the flags.
flags_ = *cursor++;
// Grab the key material.
key_material_.assign(cursor, cursor + key_material_size());
cursor += key_material_size();
// Grab the hash.
std::copy(cursor, cursor + sizeof(hash_), hash_);
cursor += sizeof(hash_);
// Per the checks at function entry we should have exactly reached the end.
CHECK(cursor == nvram_data.end());
return true;
}
bool LockboxContents::Encode(brillo::SecureBlob* blob) {
// Encode the data size. For historic reasons, this is encoded in reverse host
// byte order (!).
uint32_t reversed_size = base::ByteSwap(size_);
uint8_t* reversed_size_ptr = reinterpret_cast<uint8_t*>(&reversed_size);
blob->insert(blob->end(), reversed_size_ptr,
reversed_size_ptr + sizeof(reversed_size));
// Append the flags byte.
blob->push_back(flags_);
// Append the key material.
blob->insert(blob->end(), std::begin(key_material_), std::end(key_material_));
// Append the hash.
blob->insert(blob->end(), std::begin(hash_), std::end(hash_));
return true;
}
bool LockboxContents::SetKeyMaterial(const brillo::SecureBlob& key_material) {
if (key_material.size() != key_material_size()) {
return false;
}
key_material_ = key_material;
return true;
}
bool LockboxContents::Protect(const brillo::Blob& blob) {
brillo::SecureBlob salty_blob(blob);
salty_blob.insert(salty_blob.end(), key_material_.begin(),
key_material_.end());
SecureBlob salty_blob_hash = CryptoLib::Sha256(salty_blob);
CHECK_EQ(sizeof(hash_), salty_blob_hash.size());
std::copy(salty_blob_hash.begin(), salty_blob_hash.end(), std::begin(hash_));
size_ = blob.size();
return true;
}
LockboxContents::VerificationResult LockboxContents::Verify(
const brillo::Blob& blob) {
// Make sure that the file size matches what was stored in nvram.
if (blob.size() != size_) {
LOG(ERROR) << "Verify() expected " << size_ << " , but received "
<< blob.size() << " bytes.";
return VerificationResult::kSizeMismatch;
}
// Compute the hash of the blob to verify.
brillo::SecureBlob salty_blob(blob);
salty_blob.insert(salty_blob.end(), key_material_.begin(),
key_material_.end());
SecureBlob salty_blob_hash = CryptoLib::Sha256(salty_blob);
// Validate the blob hash versus the stored hash.
if (sizeof(hash_) != salty_blob_hash.size() ||
brillo::SecureMemcmp(hash_, salty_blob_hash.data(), sizeof(hash_))) {
LOG(ERROR) << "Verify() hash mismatch!";
return VerificationResult::kHashMismatch;
}
return VerificationResult::kValid;
}
} // namespace cryptohome