| // 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 <string> |
| #include <vector> |
| |
| #include <base/logging.h> |
| #include <base/strings/string_split.h> |
| #include <base/threading/platform_thread.h> |
| #include <base/time/time.h> |
| #include <chromeos/secure_blob.h> |
| |
| #include "cryptohome/cryptolib.h" |
| #include "cryptohome/platform.h" |
| |
| using chromeos::SecureBlob; |
| |
| namespace cryptohome { |
| const uint32_t Lockbox::kNvramVersion1 = 1; |
| const uint32_t Lockbox::kNvramVersion2 = 2; |
| const uint32_t Lockbox::kNvramVersionDefault = 2; |
| const uint32_t Lockbox::kReservedSizeBytes = sizeof(uint32_t); |
| const uint32_t Lockbox::kReservedFlagsBytes = sizeof(uint8_t); |
| const uint32_t Lockbox::kReservedSaltBytesV1 = 7; |
| const uint32_t Lockbox::kReservedSaltBytesV2 = CRYPTOHOME_LOCKBOX_SALT_LENGTH; |
| const uint32_t Lockbox::kReservedDigestBytes = SHA256_DIGEST_LENGTH; |
| const uint32_t Lockbox::kReservedNvramBytesV1 = kReservedSizeBytes + |
| kReservedFlagsBytes + |
| kReservedSaltBytesV1 + |
| kReservedDigestBytes; |
| const uint32_t Lockbox::kReservedNvramBytesV2 = kReservedSizeBytes + |
| kReservedFlagsBytes + |
| kReservedSaltBytesV2 + |
| kReservedDigestBytes; |
| const char * const Lockbox::kMountEncrypted = "/usr/sbin/mount-encrypted"; |
| const char * const Lockbox::kMountEncryptedFinalize = "finalize"; |
| |
| |
| Lockbox::Lockbox(Tpm* tpm, uint32_t nvram_index) |
| : tpm_(tpm), |
| nvram_index_(nvram_index), |
| nvram_version_(kNvramVersionDefault), |
| default_process_(new chromeos::ProcessImpl()), |
| process_(default_process_.get()), |
| contents_(new LockboxContents()), |
| default_platform_(new Platform()), |
| platform_(default_platform_.get()) { |
| } |
| |
| Lockbox::~Lockbox() { |
| } |
| |
| bool Lockbox::TpmIsReady() const { |
| if (!tpm_) { |
| LOG(ERROR) << "TpmIsReady: no tpm_ instance."; |
| return false; |
| } |
| if (!tpm_->IsEnabled()) { |
| LOG(ERROR) << "TpmIsReady: is not enabled."; |
| return false; |
| } |
| if (!tpm_->IsOwned()) { |
| LOG(ERROR) << "TpmIsReady: is not owned."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool Lockbox::HasAuthorization() const { |
| if (!TpmIsReady()) { |
| LOG(ERROR) << "HasAuthorization: TPM not ready."; |
| return false; |
| } |
| // If we have a TPM owner password, we can reset. |
| chromeos::Blob owner_password; |
| if (tpm_->GetOwnerPassword(&owner_password) && owner_password.size() != 0) |
| return true; |
| LOG(INFO) << "HasAuthorization: TPM Owner password not available."; |
| return false; |
| } |
| |
| 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) { |
| uint32_t nvram_bytes; |
| 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; |
| } |
| switch (nvram_version_) { |
| case kNvramVersion1: |
| nvram_bytes = kReservedNvramBytesV1; |
| break; |
| case kNvramVersion2: |
| default: |
| nvram_bytes = kReservedNvramBytesV2; |
| break; |
| } |
| if (!tpm_->DefineLockOnceNvram(nvram_index_, nvram_bytes)) { |
| *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 not an expected value, |
| // we've got tampering or an unexpected bug/race during set. |
| switch (nvram_data.size()) { |
| case kReservedNvramBytesV1: |
| contents_->salt_size = kReservedSaltBytesV1; |
| nvram_version_ = kNvramVersion1; |
| break; |
| case kReservedNvramBytesV2: |
| contents_->salt_size = kReservedSaltBytesV2; |
| nvram_version_ = kNvramVersion2; |
| break; |
| default: |
| LOG(ERROR) << "Load() found unexpected NVRAM size: " << nvram_data.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) == kReservedSaltBytesV2); |
| DCHECK(sizeof(contents_->salt) >= contents_->salt_size); |
| memcpy(contents_->salt, nvram_data.data(), contents_->salt_size); |
| nvram_data.erase(nvram_data.begin(), |
| nvram_data.begin() + contents_->salt_size); |
| |
| // Grab the hash. |
| DCHECK(nvram_data.size() == kReservedDigestBytes); |
| DCHECK(sizeof(contents_->hash) == kReservedDigestBytes); |
| memcpy(contents_->hash, nvram_data.data(), 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 + contents_->salt_size); |
| |
| SecureBlob hash = CryptoLib::Sha256(salty_blob); |
| // 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 (chromeos::SecureMemcmp(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) { |
| unsigned int nvram_size; |
| |
| if (!TpmIsReady()) { |
| LOG(ERROR) << "Store() called when TPM was not ready!"; |
| *error = kErrorIdTpmError; |
| return false; |
| } |
| |
| // Ensure we have the space 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; |
| } |
| // Check defined NVRAM size. |
| nvram_size = tpm_->GetNvramSize(nvram_index_); |
| switch (nvram_size) { |
| case kReservedNvramBytesV1: |
| contents_->salt_size = kReservedSaltBytesV1; |
| nvram_version_ = kNvramVersion1; |
| break; |
| case kReservedNvramBytesV2: |
| contents_->salt_size = kReservedSaltBytesV2; |
| nvram_version_ = kNvramVersion2; |
| break; |
| default: |
| LOG(ERROR) << "Store() found unexpected NVRAM size " << nvram_size << "."; |
| *error = kErrorIdNvramInvalid; |
| return false; |
| } |
| |
| // Grab a salt from the TPM. |
| chromeos::Blob salt(0); |
| if (!tpm_->GetRandomData(contents_->salt_size, &salt)) { |
| LOG(ERROR) << "Store() failed to get a salt from the TPM."; |
| *error = kErrorIdTpmError; |
| return false; |
| } |
| // Keep the data locally too. |
| DCHECK(sizeof(contents_->salt) == kReservedSaltBytesV2); |
| DCHECK(sizeof(contents_->salt) >= contents_->salt_size); |
| memcpy(contents_->salt, salt.data(), contents_->salt_size); |
| |
| // 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()); |
| |
| // Insert the hash into the NVRAM. |
| SecureBlob nvram_blob = CryptoLib::Sha256(salty_blob); |
| DCHECK(kReservedDigestBytes == nvram_blob.size()); |
| memcpy(contents_->hash, nvram_blob.data(), 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][hash_blob] |
| |
| // 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 = 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; |
| } |
| |
| // Call out to mount-encrypted now that salt has been written. |
| FinalizeMountEncrypted(nvram_version_ == 1 ? nvram_blob : salt); |
| |
| 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; |
| } |
| |
| // TODO(keescook) Write unittests for this. |
| void Lockbox::FinalizeMountEncrypted(const chromeos::Blob &entropy) const { |
| std::string hex; |
| std::string outfile_path; |
| FILE *outfile; |
| int rc; |
| |
| // Take hash of entropy and convert to hex string for cmdline. |
| SecureBlob hash = CryptoLib::Sha256(entropy); |
| hex = CryptoLib::BlobToHex(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)) { |
| base::SplitString(contents, '\n', &output); |
| for (it = output.begin(); it < output.end(); it++) { |
| LOG(ERROR) << *it; |
| } |
| } |
| } |
| } else { |
| LOG(INFO) << "Encrypted partition finalized."; |
| } |
| |
| if (outfile) |
| platform_->CloseFile(outfile); |
| |
| return; |
| } |
| |
| } // namespace cryptohome |