| // 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 |