| // Copyright 2018 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/mount_encrypted/encryption_key.h" |
| |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <utility> |
| |
| #include <base/files/file_util.h> |
| #include <base/strings/string_number_conversions.h> |
| |
| #include <brillo/file_utils.h> |
| |
| #include "cryptohome/cryptolib.h" |
| #include "cryptohome/mount_encrypted/mount_encrypted.h" |
| #include "cryptohome/mount_encrypted/tpm.h" |
| |
| namespace mount_encrypted { |
| namespace paths { |
| const char kStatefulMount[] = "mnt/stateful_partition"; |
| const char kEncryptedKey[] = "encrypted.key"; |
| const char kNeedsFinalization[] = "encrypted.needs-finalization"; |
| const char kKernelCmdline[] = "/proc/cmdline"; |
| const char kProductUUID[] = "/sys/class/dmi/id/product_uuid"; |
| 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(const base::FilePath& path, |
| brillo::SecureBlob* plaintext, |
| const brillo::SecureBlob& encryption_key) { |
| std::string ciphertext; |
| if (!base::ReadFileToStringWithMaxSize(path, &ciphertext, kMaxReadSize)) { |
| LOG(ERROR) << "Data read failed from " << path; |
| return false; |
| } |
| |
| if (!cryptohome::CryptoLib::AesDecryptSpecifyBlockMode( |
| brillo::SecureBlob(ciphertext), 0, ciphertext.size(), encryption_key, |
| brillo::SecureBlob(cryptohome::kAesBlockSize), |
| cryptohome::CryptoLib::kPaddingStandard, cryptohome::CryptoLib::kCbc, |
| plaintext)) { |
| LOG(ERROR) << "Decryption failed for data from " << path; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool WriteKeyFile(const base::FilePath& path, |
| const brillo::SecureBlob& plaintext, |
| const brillo::SecureBlob& encryption_key) { |
| if (base::PathExists(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::SecureBlob ciphertext; |
| if (!cryptohome::CryptoLib::AesEncryptSpecifyBlockMode( |
| plaintext, 0, plaintext.size(), encryption_key, |
| brillo::SecureBlob(cryptohome::kAesBlockSize), |
| cryptohome::CryptoLib::kPaddingStandard, cryptohome::CryptoLib::kCbc, |
| &ciphertext)) { |
| LOG(ERROR) << "Encryption failed for " << path; |
| return false; |
| } |
| |
| if (!brillo::WriteBlobToFileAtomic(path, ciphertext, 0600) || |
| !brillo::SyncFileOrDirectory(path.DirName(), true, false)) { |
| PLOG(ERROR) << "Unable to write " << path; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // ShredFile - Overwrite file contents. Useless on SSD. :( |
| // Currently, if the TPM is not ready, we encrypt the encryption key data |
| // with a static key and write it to disk. This function is a best-attempt to |
| // clear the contents of the key file. |
| // 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. |
| void ShredFile(const base::FilePath& file) { |
| uint8_t patterns[] = {0xA5, 0x5A, 0xFF, 0x00}; |
| FILE* target; |
| struct stat info; |
| uint8_t* pattern; |
| int i; |
| |
| // Give up if we can't safely open or stat the target. |
| base::ScopedFD fd(HANDLE_EINTR( |
| open(file.value().c_str(), O_WRONLY | O_NOFOLLOW | O_CLOEXEC))); |
| if (!fd.is_valid()) { |
| PLOG(ERROR) << file; |
| return; |
| } |
| if (fstat(fd.get(), &info)) { |
| PLOG(ERROR) << file; |
| return; |
| } |
| if (!(target = fdopen(fd.get(), "w"))) { |
| PLOG(ERROR) << file; |
| return; |
| } |
| |
| // Ignore errors here, since there's nothing we can really do. |
| pattern = static_cast<uint8_t*>(malloc(info.st_size)); |
| for (i = 0; i < sizeof(patterns); ++i) { |
| memset(pattern, patterns[i], info.st_size); |
| if (fseek(target, 0, SEEK_SET)) |
| PLOG(ERROR) << file; |
| if (fwrite(pattern, info.st_size, 1, target) != 1) |
| PLOG(ERROR) << file; |
| if (fflush(target)) |
| PLOG(ERROR) << file; |
| if (fdatasync(fd.get())) |
| PLOG(ERROR) << file; |
| } |
| } |
| |
| brillo::SecureBlob Sha256(const std::string& str) { |
| brillo::SecureBlob blob(str); |
| return cryptohome::CryptoLib::Sha256(blob); |
| } |
| |
| brillo::SecureBlob GetUselessKey() { |
| return Sha256(kStaticKeyFinalizationNeeded); |
| } |
| |
| // Extract the desired system key from the kernel's boot command line. |
| brillo::SecureBlob GetKeyFromKernelCmdline() { |
| std::string cmdline; |
| if (!base::ReadFileToStringWithMaxSize(base::FilePath(paths::kKernelCmdline), |
| &cmdline, kMaxReadSize)) { |
| 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(SystemKeyLoader* loader, |
| const base::FilePath& rootdir) |
| : loader_(loader) { |
| base::FilePath stateful_mount = rootdir.AppendASCII(paths::kStatefulMount); |
| key_path_ = stateful_mount.AppendASCII(paths::kEncryptedKey); |
| needs_finalization_path_ = |
| stateful_mount.AppendASCII(paths::kNeedsFinalization); |
| preservation_request_path_ = |
| stateful_mount.AppendASCII(paths::kStatefulPreservationRequest); |
| preserved_previous_key_path_ = |
| stateful_mount.AppendASCII(paths::kPreservedPreviousKey); |
| } |
| |
| result_code EncryptionKey::SetTpmSystemKey() { |
| result_code rc = loader_->Load(&system_key_); |
| if (rc == RESULT_SUCCESS) { |
| LOG(INFO) << "Using NVRAM as system key; already populated."; |
| } else { |
| LOG(INFO) << "Using NVRAM as system key; finalization needed."; |
| } |
| |
| return rc; |
| } |
| |
| result_code EncryptionKey::SetInsecureFallbackSystemKey() { |
| system_key_ = GetKeyFromKernelCmdline(); |
| if (!system_key_.empty()) { |
| LOG(INFO) << "Using kernel command line argument as system key."; |
| system_key_status_ = SystemKeyStatus::kKernelCommandLine; |
| return RESULT_SUCCESS; |
| } |
| |
| std::string product_uuid; |
| if (base::ReadFileToStringWithMaxSize(base::FilePath(paths::kProductUUID), |
| &product_uuid, kMaxReadSize)) { |
| system_key_ = Sha256(product_uuid); |
| LOG(INFO) << "Using UUID as system key."; |
| system_key_status_ = SystemKeyStatus::kProductUUID; |
| return RESULT_SUCCESS; |
| } |
| |
| LOG(INFO) << "Using default insecure system key."; |
| system_key_ = Sha256(kStaticKeyDefault); |
| system_key_status_ = SystemKeyStatus::kStaticFallback; |
| return RESULT_SUCCESS; |
| } |
| |
| result_code EncryptionKey::LoadChromeOSSystemKey() { |
| 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() && base::PathExists(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 (!base::Move(key_path_, preserved_previous_key_path_)) { |
| base::DeleteFile(key_path_, false /* recursive */); |
| } |
| base::DeleteFile(preservation_request_path_, false /* recursive */); |
| } |
| |
| // 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 (base::PathExists(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. |
| base::DeleteFile(preserved_previous_key_path_, false /* recursive */); |
| } |
| |
| // 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."; |
| |
| // TODO(mnissler): Gather data on how costly it is to take TPM 1.2 ownership |
| // in practice and decide whether we can just take ownership to create the |
| // NVRAM space if it isn't valid by calling SetupTpm here. |
| const auto key_material = |
| cryptohome::CryptoLib::CreateSecureRandomBlob(DIGEST_LENGTH); |
| result_code rc = loader_->Initialize(key_material, &system_key_); |
| if (rc != RESULT_SUCCESS) { |
| LOG(ERROR) << "Failed to initialize system key NV space contents."; |
| return rc; |
| } |
| |
| if (!system_key_.empty() && loader_->Persist() != RESULT_SUCCESS) { |
| system_key_.clear(); |
| } |
| } |
| |
| // 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 RESULT_SUCCESS; |
| } |
| |
| result_code EncryptionKey::LoadEncryptionKey() { |
| if (!system_key_.empty()) { |
| if (ReadKeyFile(key_path_, &encryption_key_, system_key_)) { |
| encryption_key_status_ = EncryptionKeyStatus::kKeyFile; |
| return RESULT_SUCCESS; |
| } |
| 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. |
| base::DeleteFile(key_path_, false /* recursive */); |
| encryption_key_.clear(); |
| |
| // Check if there's a to-be-finalized key on disk. |
| if (!ReadKeyFile(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_ = |
| cryptohome::CryptoLib::CreateSecureRandomBlob(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(needs_finalization_path_, encryption_key_, |
| GetUselessKey())) { |
| LOG(ERROR) << "Failed to write " << needs_finalization_path_; |
| } |
| } |
| return RESULT_SUCCESS; |
| } |
| |
| // We have a system key, so finalize now. |
| Finalize(); |
| |
| return RESULT_SUCCESS; |
| } |
| |
| void EncryptionKey::PersistEncryptionKey( |
| const brillo::SecureBlob& encryption_key) { |
| encryption_key_ = encryption_key; |
| base::DeleteFile(key_path_, false /* recursive */); |
| Finalize(); |
| } |
| |
| brillo::SecureBlob EncryptionKey::GetDerivedSystemKey( |
| const std::string& label) const { |
| if (!system_key_.empty() && |
| system_key_status_ == EncryptionKey::SystemKeyStatus::kNVRAMEncstateful) { |
| return cryptohome::CryptoLib::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(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. This |
| // is unreliable on many levels, in particular ext4 doesn't support secure |
| // delete so the data may end up sticking around in the journal. Furthermore, |
| // SSDs may remap flash blocks on write, so the data may physically remain in |
| // the old block. See comment above regarding options to get rid of the |
| // finalization intent file in the long run. |
| if (base::PathExists(needs_finalization_path_)) { |
| ShredFile(needs_finalization_path_); |
| base::DeleteFile(needs_finalization_path_, false /* recursive */); |
| } |
| } |
| |
| 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) != RESULT_SUCCESS) { |
| return false; |
| } |
| |
| brillo::SecureBlob previous_encryption_key; |
| if (!ReadKeyFile(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. |
| base::DeleteFile(key_path_, false /* recursive */); |
| if (!WriteKeyFile(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_->SetupTpm() != RESULT_SUCCESS || |
| loader_->Persist() != RESULT_SUCCESS) { |
| 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 mount_encrypted |