blob: d1d33d6b517f007c808e76183d1ac058f60d9526 [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 <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 brillo::SecureBlob& 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));
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;
}
brillo::SecureBlob RetrieveKey() {
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)) {
auto encryption_key =
brillo::SecureHexToSecureBlob(brillo::SecureBlob(val));
base::DeleteFile(ramoops_file, false /* recursive */);
// 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(encryption_key))
LOG(WARNING) << "Failed to store key for next reboot.";
return encryption_key;
}
}
return brillo::SecureBlob();
}
} // namespace
EncryptedRebootVault::EncryptedRebootVault()
: vault_path_(base::FilePath(kEncryptedRebootVaultPath)) {
if (dircrypto::CheckFscryptKeyIoctlSupport()) {
key_reference_.policy_version = FSCRYPT_POLICY_V2;
} else {
key_reference_.reference = brillo::SecureBlob(kEncryptionKeyTag);
key_reference_.policy_version = FSCRYPT_POLICY_V1;
}
}
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.
brillo::SecureBlob transient_encryption_key =
cryptohome::CryptoLib::CreateSecureRandomBlob(kEncryptionKeySize);
// The key descriptor needs to be exactly 8 bytes for v1 encryption policies.
if (!dircrypto::AddDirectoryKey(transient_encryption_key, &key_reference_)) {
LOG(ERROR) << "Failed to add pmsg-key";
return false;
}
// 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 (!base::CreateDirectory(vault_path_)) {
LOG(ERROR) << "Failed to create directory";
return false;
}
// Set the fscrypt context for the directory.
if (!dircrypto::SetDirectoryKey(vault_path_, key_reference_)) {
LOG(ERROR) << "Failed to set directory key";
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 (!dircrypto::RemoveDirectoryKey(key_reference_, vault_path_)) {
LOG(WARNING) << "Failed to unlink encryption key from keyring.";
}
return base::DeleteFile(vault_path_, true /* recursively */);
}
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;
}
// If the vault exists, get the policy version from the directory.
switch (dircrypto::GetDirectoryPolicyVersion(vault_path_)) {
case FSCRYPT_POLICY_V1:
key_reference_.policy_version = FSCRYPT_POLICY_V1;
key_reference_.reference = brillo::SecureBlob(kEncryptionKeyTag);
break;
case FSCRYPT_POLICY_V2:
key_reference_.policy_version = FSCRYPT_POLICY_V2;
break;
default:
LOG(ERROR) << "Failed to get policy version for directory";
return false;
}
// Retrieve key.
brillo::SecureBlob transient_encryption_key = RetrieveKey();
if (transient_encryption_key.empty()) {
LOG(INFO) << "No valid key found: the device might have booted up from a "
"shutdown.";
return false;
}
// Unlock vault.
if (!dircrypto::AddDirectoryKey(transient_encryption_key, &key_reference_)) {
LOG(ERROR) << "Failed to add key to keyring.";
return false;
}
ignore_result(reset_vault.Release());
return true;
}