blob: f53c6792e93bda721093429eee7c64702703fe66 [file] [log] [blame]
// Copyright 2020 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/encrypted_reboot_vault/encrypted_reboot_vault.h"
#include <cryptohome/cryptolib.h>
#include <cryptohome/dircrypto_util.h>
#include <cryptohome/platform.h>
#include <cryptohome/storage/encrypted_container/encrypted_container.h>
#include <cryptohome/storage/encrypted_container/filesystem_key.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <brillo/key_value_store.h>
namespace {
// Pstore-pmsg path.
const char kPmsgDevicePath[] = "/dev/pmsg0";
// There can be multiple pmsg ramoops entries.
const char kPmsgKeystoreRamoopsPathDesc[] = "pmsg-ramoops-*";
const char kExt4DircryptoSupportedPath[] = "/sys/fs/ext4/features/encryption";
const char kEncryptedRebootVaultPath[] = "/mnt/stateful_partition/reboot_vault";
// Pstore path.
const char kPstorePath[] = "/sys/fs/pstore";
// Key tag to retrieve the key from pstore-pmsg.
const char kEncryptionKeyTag[] = "pmsg-key";
// Encryption key size.
const size_t kEncryptionKeySize = 64;
bool IsSupported() {
if (!base::PathExists(base::FilePath(kPmsgDevicePath))) {
LOG(ERROR) << "pmsg0 not enabled.";
return false;
}
// Check if we can create an encrypted vault.
if (!base::PathExists(base::FilePath(kExt4DircryptoSupportedPath))) {
LOG(ERROR) << "ext4 directory encryption not supported.";
return false;
}
return true;
}
bool SaveKey(const cryptohome::FileSystemKey& key) {
// Do not use store.Save() since it uses WriteFileAtomically() which will
// fail on /dev/pmsg0.
brillo::KeyValueStore store;
store.SetString(kEncryptionKeyTag,
cryptohome::CryptoLib::SecureBlobToHex(key.fek));
std::string store_contents = store.SaveToString();
if (store_contents.empty() ||
!base::WriteFile(base::FilePath(kPmsgDevicePath), store_contents.data(),
store_contents.size())) {
return false;
}
return true;
}
cryptohome::FileSystemKey RetrieveKey() {
cryptohome::FileSystemKey key;
base::FileEnumerator pmsg_ramoops_enumerator(
base::FilePath(kPstorePath), true /* recursive */,
base::FileEnumerator::FILES, kPmsgKeystoreRamoopsPathDesc);
for (base::FilePath ramoops_file = pmsg_ramoops_enumerator.Next();
!ramoops_file.empty(); ramoops_file = pmsg_ramoops_enumerator.Next()) {
brillo::KeyValueStore store;
std::string val;
if (store.Load(ramoops_file) && store.GetString(kEncryptionKeyTag, &val)) {
key.fek = brillo::SecureHexToSecureBlob(brillo::SecureBlob(val));
base::DeleteFile(ramoops_file);
// SaveKey stores the key again into pstore-pmsg on every boot since the
// pstore object isn't persistent. Since the pstore object is always
// stored in RAM on ChromiumOS, it is cleared the next time the device
// shuts down or loses power.
if (!SaveKey(key))
LOG(WARNING) << "Failed to store key for next reboot.";
return key;
}
}
return key;
}
} // namespace
EncryptedRebootVault::EncryptedRebootVault()
: vault_path_(base::FilePath(kEncryptedRebootVaultPath)) {
cryptohome::FileSystemKeyReference key_reference;
key_reference.fek_sig = brillo::SecureBlob(kEncryptionKeyTag);
encrypted_container_ = cryptohome::EncryptedContainer::Generate(
cryptohome::EncryptedContainerType::kFscrypt, vault_path_, key_reference,
&platform_);
}
bool EncryptedRebootVault::CreateVault() {
if (!IsSupported()) {
LOG(ERROR) << "EncryptedRebootVault not supported";
return false;
}
base::ScopedClosureRunner reset_vault(
base::Bind(base::IgnoreResult(&EncryptedRebootVault::PurgeVault),
base::Unretained(this)));
// Remove the existing vault.
PurgeVault();
// Generate encryption key.
cryptohome::FileSystemKey transient_encryption_key;
transient_encryption_key.fek =
cryptohome::CryptoLib::CreateSecureRandomBlob(kEncryptionKeySize);
// Store key into pmsg. If it fails, we bail out.
if (!SaveKey(transient_encryption_key)) {
LOG(ERROR) << "Failed to store transient encryption key to pmsg.";
return false;
}
// Set up the encrypted reboot vault.
if (!encrypted_container_->Setup(transient_encryption_key, /*create=*/true)) {
LOG(ERROR) << "Failed to setup encrypted container";
return false;
}
ignore_result(reset_vault.Release());
return true;
}
bool EncryptedRebootVault::Validate() {
return base::PathExists(vault_path_) &&
dircrypto::GetDirectoryKeyState(vault_path_) ==
dircrypto::KeyState::ENCRYPTED;
}
bool EncryptedRebootVault::PurgeVault() {
if (!encrypted_container_->Teardown()) {
LOG(WARNING) << "Failed to unlink encryption key from keyring.";
}
return encrypted_container_->Purge();
}
bool EncryptedRebootVault::UnlockVault() {
if (!IsSupported()) {
LOG(ERROR) << "EncryptedRebootVault depends on pstore-pmsg to pass the "
"encryption key. Enable CONFIG_PSTORE_PMSG";
return false;
}
// We reset the vault if we fail to unlock it for any reason.
base::ScopedClosureRunner reset_vault(
base::Bind(base::IgnoreResult(&EncryptedRebootVault::PurgeVault),
base::Unretained(this)));
if (!Validate()) {
LOG(ERROR) << "Invalid vault; purging.";
return false;
}
// Retrieve key.
cryptohome::FileSystemKey transient_encryption_key = RetrieveKey();
if (transient_encryption_key.fek.empty()) {
LOG(INFO) << "No valid key found: the device might have booted up from a "
"shutdown.";
return false;
}
// Unlock vault.
if (!encrypted_container_->Setup(transient_encryption_key,
/*create=*/false)) {
LOG(ERROR) << "Failed to add key to keyring.";
return false;
}
ignore_result(reset_vault.Release());
return true;
}