blob: bf4b71350e1860fea924f1a7db620dec1982607f [file] [log] [blame] [edit]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "init/tpm_encryption/encryption_key.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <utility>
#include <base/check.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <brillo/file_utils.h>
#include <brillo/files/file_util.h>
#include <libhwsec-foundation/crypto/aes.h>
#include <libhwsec-foundation/crypto/hmac.h>
#include <libhwsec-foundation/crypto/secure_blob_util.h>
#include <libhwsec-foundation/crypto/sha.h>
#include <libstorage/platform/platform.h>
#include <openssl/sha.h>
#include "init/tpm_encryption/tpm.h"
namespace encryption {
namespace paths {
const char kKernelCmdline[] = "proc/cmdline";
const char kProductUUID[] = "sys/class/dmi/id/product_uuid";
// Using stateful partition mount point as base.
const char kEncryptedKey[] = "encrypted.key";
const char kNeedsFinalization[] = "encrypted.needs-finalization";
const char kStatefulPreservationRequest[] = "preservation_request";
const char kPreservedPreviousKey[] = "encrypted.key.preserved";
} // namespace paths
namespace {
const char kKernelCmdlineOption[] = "encrypted-stateful-key=";
const char kStaticKeyDefault[] = "default unsafe static key";
const char kStaticKeyFinalizationNeeded[] = "needs finalization";
const size_t kMaxReadSize = 4 * 1024;
bool ReadKeyFile(libstorage::Platform* platform,
const base::FilePath& path,
brillo::SecureBlob* plaintext,
const brillo::SecureBlob& encryption_key) {
brillo::Blob ciphertext;
// Check the file size: we expect to be really small. In case of filesystem
// corruption ignore files that are way too big.
int64_t size;
if (!platform->GetFileSize(path, &size)) {
LOG(ERROR) << "Unable to get file size for " << path;
return false;
}
if (size > kMaxReadSize) {
LOG(ERROR) << "File " << path << " too large: " << size;
return false;
}
if (!platform->ReadFile(path, &ciphertext)) {
LOG(ERROR) << "Data read failed from " << path;
return false;
}
if (!hwsec_foundation::AesDecryptSpecifyBlockMode(
ciphertext, 0, ciphertext.size(), encryption_key,
brillo::Blob(hwsec_foundation::kAesBlockSize),
hwsec_foundation::PaddingScheme::kPaddingStandard,
hwsec_foundation::BlockMode::kCbc, plaintext)) {
LOG(ERROR) << "Decryption failed for data from " << path;
return false;
}
// The decryption is succeed when the plaintext size is correct.
if (plaintext->size() != SHA256_DIGEST_LENGTH) {
LOG(ERROR) << "Decryption result size mismatch for data from " << path
<< ", expected size:" << SHA256_DIGEST_LENGTH
<< ", actual size:" << plaintext->size();
return false;
}
return true;
}
bool WriteKeyFile(libstorage::Platform* platform,
const base::FilePath& path,
const brillo::SecureBlob& plaintext,
const brillo::SecureBlob& encryption_key) {
if (platform->FileExists(path)) {
LOG(ERROR) << path << " already exists.";
return false;
}
// Note that we pass an all-zeros IV. In general, this is dangerous since
// identical plaintext will lead to identical ciphertext, revealing the fact
// that the same message has been encrypted. This can potentially be used in
// chosen plaintext attacks to determine the plaintext for a given ciphertext.
// In the case at hand, we only ever encrypt a single message using the system
// key and don't allow attackers to inject plaintext, so we are good.
//
// Ideally, we'd generate a random IV and stored it to disk as well, but
// switching over to the safer scheme would have to be done in a
// backwards-compatible way, so for now it isn't worth it.
brillo::Blob ciphertext;
if (!hwsec_foundation::AesEncryptSpecifyBlockMode(
plaintext, 0, plaintext.size(), encryption_key,
brillo::Blob(hwsec_foundation::kAesBlockSize),
hwsec_foundation::PaddingScheme::kPaddingStandard,
hwsec_foundation::BlockMode::kCbc, &ciphertext)) {
LOG(ERROR) << "Encryption failed for " << path;
return false;
}
if (!platform->WriteFileAtomicDurable(path, ciphertext, 0600)) {
PLOG(ERROR) << "Unable to write " << path;
return false;
}
return true;
}
brillo::SecureBlob Sha256(const std::string& str) {
brillo::SecureBlob blob(str);
return hwsec_foundation::Sha256(blob);
}
brillo::SecureBlob GetUselessKey() {
return Sha256(kStaticKeyFinalizationNeeded);
}
// Extract the desired system key from the kernel's boot command line.
brillo::SecureBlob GetKeyFromKernelCmdline(libstorage::Platform* platform,
const base::FilePath rootdir) {
std::string cmdline;
if (!platform->ReadFileToString(rootdir.Append(paths::kKernelCmdline),
&cmdline)) {
PLOG(ERROR) << "Failed to read kernel command line";
return brillo::SecureBlob();
}
// Find a string match either at start of string or following a space.
size_t pos = cmdline.find(kKernelCmdlineOption);
if (pos == std::string::npos || !(pos == 0 || cmdline[pos - 1] == ' ')) {
return brillo::SecureBlob();
}
std::string value = cmdline.substr(pos + strlen(kKernelCmdlineOption));
value = value.substr(0, value.find(' '));
brillo::SecureBlob key = Sha256(value);
return key;
}
} // namespace
EncryptionKey::EncryptionKey(libstorage::Platform* platform,
SystemKeyLoader* loader,
const base::FilePath& rootdir,
const base::FilePath& stateful_mount)
: platform_(platform), loader_(loader), rootdir_(rootdir) {
key_path_ = stateful_mount.Append(paths::kEncryptedKey);
needs_finalization_path_ = stateful_mount.Append(paths::kNeedsFinalization);
preservation_request_path_ =
stateful_mount.Append(paths::kStatefulPreservationRequest);
preserved_previous_key_path_ =
stateful_mount.Append(paths::kPreservedPreviousKey);
}
bool EncryptionKey::SetTpmSystemKey() {
bool rc = loader_->Load(&system_key_);
if (rc) {
LOG(INFO) << "Using NVRAM as system key; already populated.";
} else {
LOG(INFO) << "Using NVRAM as system key; finalization needed.";
}
return rc;
}
bool EncryptionKey::SetInsecureFallbackSystemKey() {
system_key_ = GetKeyFromKernelCmdline(platform_, rootdir_);
if (!system_key_.empty()) {
LOG(INFO) << "Using kernel command line argument as system key.";
system_key_status_ = SystemKeyStatus::kKernelCommandLine;
return true;
}
std::string product_uuid;
if (platform_->ReadFileToString(rootdir_.Append(paths::kProductUUID),
&product_uuid)) {
system_key_ = Sha256(base::ToUpperASCII(product_uuid));
LOG(INFO) << "Using UUID as system key.";
system_key_status_ = SystemKeyStatus::kProductUUID;
return true;
}
LOG(INFO) << "Using default insecure system key.";
system_key_ = Sha256(kStaticKeyDefault);
system_key_status_ = SystemKeyStatus::kStaticFallback;
return true;
}
bool EncryptionKey::LoadChromeOSSystemKey(base::FilePath backup) {
SetTpmSystemKey();
// Check and handle potential requests to preserve an already existing
// encryption key in order to retain the existing stateful file system.
if (system_key_.empty() &&
platform_->FileExists(preservation_request_path_)) {
// Move the previous key file to a different path and clear the request
// before changing TPM state. This makes sure that we're not putting the
// system into a state where the old key might get picked up accidentally
// (even by previous versions of mount-encrypted on rollback) if we reboot
// while the preservation process is not completed yet (for example due to
// power loss).
if (!platform_->Rename(key_path_, preserved_previous_key_path_)) {
platform_->DeleteFile(key_path_);
}
platform_->DeleteFile(preservation_request_path_);
}
// Note that we must check for presence of a to-be-preserved key
// unconditionally: If the preservation process doesn't complete on first
// attempt (e.g. due to crash or power loss) but already took TPM ownership,
// we might see a situation where there appears to be a valid system key but
// we still must retry preservation to salvage the previous key.
if (platform_->FileExists(preserved_previous_key_path_)) {
RewrapPreviousEncryptionKey();
// Preservation is done at this point even though it might have bailed or
// failed. The code below will handle the potentially absent system key.
platform_->DeleteFile(preserved_previous_key_path_);
}
// Attempt to generate a fresh system key if we haven't found one.
if (system_key_.empty()) {
LOG(INFO) << "Attempting to generate fresh NVRAM system key.";
const brillo::SecureBlob key_material =
hwsec_foundation::CreateSecureRandomBlob(SHA256_DIGEST_LENGTH);
bool rc = loader_->Initialize(key_material, &system_key_);
if (!rc) {
LOG(ERROR) << "Failed to initialize system key NV space contents.";
return false;
}
if (!system_key_.empty() && !loader_->Persist()) {
LOG(WARNING) << "Unable to persist the key, will retry.";
system_key_.clear();
}
if (!system_key_.empty() && !backup.empty()) {
if (!platform_->WriteSecureBlobToFile(backup, key_material)) {
LOG(WARNING)
<< "Unable to save TPM random seed, TPM tast test will fail.";
}
}
}
// Lock the system key to to prevent subsequent manipulation.
loader_->Lock();
// Determine and record the system key status.
if (system_key_.empty()) {
system_key_status_ = SystemKeyStatus::kFinalizationPending;
} else if (loader_->UsingLockboxKey()) {
system_key_status_ = SystemKeyStatus::kNVRAMLockbox;
} else {
system_key_status_ = SystemKeyStatus::kNVRAMEncstateful;
}
return true;
}
bool EncryptionKey::LoadEncryptionKey() {
if (!system_key_.empty()) {
if (ReadKeyFile(platform_, key_path_, &encryption_key_, system_key_)) {
encryption_key_status_ = EncryptionKeyStatus::kKeyFile;
return true;
}
LOG(INFO) << "Failed to load encryption key from disk.";
} else {
LOG(INFO) << "No usable system key found.";
}
// Delete any stale encryption key files from disk. This is important because
// presence of the key file determines whether finalization requests from
// cryptohome do need to write a key file.
platform_->DeleteFile(key_path_);
encryption_key_.clear();
// Check if there's a to-be-finalized key on disk.
if (!ReadKeyFile(platform_, needs_finalization_path_, &encryption_key_,
GetUselessKey())) {
// This is a brand new system with no keys, so generate a fresh one.
LOG(INFO) << "Generating new encryption key.";
encryption_key_ =
hwsec_foundation::CreateSecureRandomBlob(SHA256_DIGEST_LENGTH);
encryption_key_status_ = EncryptionKeyStatus::kFresh;
} else {
encryption_key_status_ = EncryptionKeyStatus::kNeedsFinalization;
LOG(ERROR) << "Finalization unfinished! Encryption key still on disk!";
}
// At this point, we have an encryption key but it has not been finalized yet
// (i.e. encrypted under the system key and stored on disk in the key file).
//
// However, when we are creating the encrypted mount for the first time, the
// TPM might not be in a state where we have a system key. In this case we
// fall back to writing the obfuscated encryption key to disk (*sigh*).
//
// NB: We'd ideally never write an insufficiently protected key to disk. This
// is already the case for TPM 2.0 devices as they can create system keys as
// needed, and we can improve the situation for TPM 1.2 devices as well by (1)
// using an NVRAM space that doesn't get lost on TPM clear and (2) allowing
// mount-encrypted to take ownership and create the NVRAM space if necessary.
if (system_key_.empty()) {
if (is_fresh()) {
LOG(INFO) << "Writing finalization intent " << needs_finalization_path_;
if (!WriteKeyFile(platform_, needs_finalization_path_, encryption_key_,
GetUselessKey())) {
LOG(ERROR) << "Failed to write " << needs_finalization_path_;
}
}
return true;
}
// We have a system key, so finalize now.
Finalize();
return true;
}
brillo::SecureBlob EncryptionKey::GetDerivedSystemKey(
const std::string& label) const {
if (!system_key_.empty() &&
system_key_status_ == EncryptionKey::SystemKeyStatus::kNVRAMEncstateful) {
return hwsec_foundation::HmacSha256(system_key_, brillo::SecureBlob(label));
}
return brillo::SecureBlob();
}
void EncryptionKey::Finalize() {
CHECK(!system_key_.empty());
CHECK(!encryption_key_.empty());
LOG(INFO) << "Writing keyfile " << key_path_;
if (!WriteKeyFile(platform_, key_path_, encryption_key_, system_key_)) {
LOG(ERROR) << "Failed to write " << key_path_;
return;
}
// Finalization is complete at this point.
did_finalize_ = true;
// Make a best effort attempt to wipe the obfuscated key file from disk.
if (platform_->FileExists(needs_finalization_path_)) {
if (!platform_->DeleteFileSecurely(needs_finalization_path_)) {
// We are unebale to erase the file properly, just do the minimum.
LOG(ERROR) << "Failed to secure erase " << needs_finalization_path_
<< ". Trying simple deletion.";
platform_->DeleteFileDurable(needs_finalization_path_);
}
}
}
bool EncryptionKey::RewrapPreviousEncryptionKey() {
// Key preservation has been requested, but we haven't performed the process
// of carrying over the encryption key yet, or we have started and didn't
// finish the last attempt.
LOG(INFO) << "Attempting to preserve previous encryption key.";
// Load the previous system key and set up a fresh system key to re-wrap the
// encryption key.
brillo::SecureBlob fresh_system_key;
brillo::SecureBlob previous_system_key;
if (!loader_->GenerateForPreservation(&previous_system_key,
&fresh_system_key)) {
return false;
}
brillo::SecureBlob previous_encryption_key;
if (!ReadKeyFile(platform_, preserved_previous_key_path_,
&previous_encryption_key, previous_system_key)) {
LOG(WARNING) << "Failed to decrypt preserved previous key, aborting.";
return false;
}
// We have the previous encryption key at this point, so we're in business.
// Re-wrap the encryption key under the new system key and store it to disk.
platform_->DeleteFile(key_path_);
if (!WriteKeyFile(platform_, key_path_, previous_encryption_key,
fresh_system_key)) {
return false;
}
// Persist the fresh system key. It's important that the fresh system key gets
// written to the NVRAM space as the last step (in particular, only after the
// encryption key has been re-wrapped). Otherwise, a crash would lead to a
// situation where the new system key has already replaced the old one,
// leaving us with no way to recover the preserved encryption key.
if (!loader_->Persist()) {
return false;
}
// Success. Put the keys in place for later usage.
system_key_ = std::move(fresh_system_key);
LOG(INFO) << "Successfully preserved encryption key.";
return true;
}
} // namespace encryption