blob: 21c5bd75d3770a3f7d7f59247d76633062537e87 [file] [edit]
/* Copyright 2012 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* This tool will attempt to mount or create the encrypted stateful partition,
* and the various bind mountable subdirectories.
*
*/
#define _FILE_OFFSET_BITS 64
#define CHROMEOS_ENVIRONMENT
#include <fcntl.h>
#include <sys/time.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <brillo/blkdev_utils/lvm.h>
#include <brillo/flag_helper.h>
#include <brillo/secure_blob.h>
#include <brillo/syslog_logging.h>
#include <vboot/crossystem.h>
#include <vboot/tlcl.h>
#include "cryptohome/mount_encrypted/encrypted_fs.h"
#include "cryptohome/mount_encrypted/encryption_key.h"
#include "cryptohome/mount_encrypted/mount_encrypted.h"
#include "cryptohome/mount_encrypted/mount_encrypted_metrics.h"
#include "cryptohome/mount_encrypted/tpm.h"
#include "cryptohome/platform.h"
#include "cryptohome/storage/encrypted_container/encrypted_container_factory.h"
#include "cryptohome/storage/encrypted_container/filesystem_key.h"
#define PROP_SIZE 64
#if DEBUG_ENABLED
struct timeval tick = {};
struct timeval tick_start = {};
#endif
using mount_encrypted::paths::cryptohome::kTpmOwned;
namespace {
constexpr char kBioCryptoInitPath[] = "/usr/bin/bio_crypto_init";
constexpr char kBioTpmSeedSalt[] = "biod";
constexpr char kBioTpmSeedTmpDir[] = "/run/bio_crypto_init";
constexpr char kBioTpmSeedFile[] = "seed";
constexpr char kHibermanPath[] = "/usr/sbin/hiberman";
constexpr char kHibermanTpmSeedSalt[] = "hiberman";
constexpr char kHibermanTpmSeedTmpDir[] = "/run/hiberman";
constexpr char kHibermanTpmSeedFile[] = "tpm_seed";
constexpr char kOldTpmOwnershipStateFile[] =
"mnt/stateful_partition/.tpm_owned";
static const uid_t kBiodUid = 282;
static const gid_t kBiodGid = 282;
static const uid_t kHibermanUid = 20184;
static const gid_t kHibermanGid = 20184;
constexpr char kNvramExport[] = "/tmp/lockbox.nvram";
constexpr char kMountEncryptedMetricsPath[] =
"/run/mount_encrypted/metrics.mount-encrypted";
} // namespace
static result_code get_system_property(const char* prop,
char* buf,
size_t length) {
const char* rc;
rc = VbGetSystemPropertyString(prop, buf, length);
LOG(INFO) << "Got System Property '" << prop << "': " << (rc ? buf : "FAIL");
return rc != NULL ? RESULT_SUCCESS : RESULT_FAIL_FATAL;
}
static int has_chromefw(void) {
static int state = -1;
char fw[PROP_SIZE];
/* Cache the state so we don't have to perform the query again. */
if (state != -1)
return state;
if (get_system_property("mainfw_type", fw, sizeof(fw)) != RESULT_SUCCESS)
state = 0;
else
state = strcmp(fw, "nonchrome") != 0;
return state;
}
static bool shall_use_tpm_for_system_key() {
if (!USE_TPM_INSECURE_FALLBACK) {
return true;
}
if (has_chromefw()) {
return true;
}
/* Don't use tpm for system key if we are using runtime TPM selection. */
if (USE_TPM_DYNAMIC) {
return false;
}
/* Assume we have tpm for system_key when we are using vtpm tpm2 simulator. */
return USE_TPM2_SIMULATOR && USE_VTPM_PROXY;
}
static result_code report_info(mount_encrypted::EncryptedFs* encrypted_fs,
const base::FilePath& rootdir) {
mount_encrypted::Tpm tpm;
printf("TPM: %s\n", tpm.available() ? "yes" : "no");
if (tpm.available()) {
bool owned = false;
printf("TPM Owned: %s\n", tpm.IsOwned(&owned) == RESULT_SUCCESS
? (owned ? "yes" : "no")
: "fail");
}
printf("ChromeOS: %s\n", has_chromefw() ? "yes" : "no");
printf("TPM2: %s\n", tpm.is_tpm2() ? "yes" : "no");
if (shall_use_tpm_for_system_key()) {
brillo::SecureBlob system_key;
auto loader = mount_encrypted::SystemKeyLoader::Create(&tpm, rootdir);
result_code rc = loader->Load(&system_key);
if (rc != RESULT_SUCCESS) {
printf("NVRAM: missing.\n");
} else {
printf("NVRAM: available.\n");
}
} else {
printf("NVRAM: not present\n");
}
// Report info from the encrypted mount.
encrypted_fs->ReportInfo();
return RESULT_SUCCESS;
}
// Reads key material from the file |key_material_file|, creates a system key
// using the material, and persists the system key in NVRAM.
//
// This function only supports TPM 2.0 and should be called ONLY for testing
// purposes.
//
// Doesn't take ownership of |platform|.
// Return code indicates if every thing is successful.
static result_code set_system_key(const base::FilePath& rootdir,
const char* key_material_file,
cryptohome::Platform* platform) {
if (!key_material_file) {
LOG(ERROR) << "Key material file not provided.";
return RESULT_FAIL_FATAL;
}
mount_encrypted::Tpm tpm;
if (!tpm.is_tpm2()) {
LOG(WARNING) << "Custom system key is not supported in TPM 1.2.";
return RESULT_FAIL_FATAL;
}
brillo::SecureBlob key_material;
if (!platform->ReadFileToSecureBlob(base::FilePath(key_material_file),
&key_material)) {
LOG(ERROR) << "Failed to read custom system key material from file "
<< key_material_file;
return RESULT_FAIL_FATAL;
}
auto loader = mount_encrypted::SystemKeyLoader::Create(&tpm, rootdir);
result_code rc = loader->Initialize(key_material, nullptr);
if (rc != RESULT_SUCCESS) {
LOG(ERROR) << "Failed to initialize system key NV space contents.";
return rc;
}
rc = loader->Persist();
if (rc != RESULT_SUCCESS) {
LOG(ERROR) << "Failed to persist custom system key material in NVRAM.";
return rc;
}
return RESULT_SUCCESS;
}
/* Exports NVRAM contents to tmpfs for use by install attributes */
void nvram_export(const brillo::SecureBlob& contents) {
int fd;
LOG(INFO) << "Export NVRAM contents";
fd = open(kNvramExport, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
if (fd < 0) {
PLOG(ERROR) << "open(nvram_export)";
return;
}
if (write(fd, contents.data(), contents.size()) != contents.size()) {
// Don't leave broken files around
unlink(kNvramExport);
}
close(fd);
}
bool SendSecretToTmpFile(const mount_encrypted::EncryptionKey& key,
const std::string& salt,
const base::FilePath& tmp_dir,
const std::string& filename,
uid_t user_id,
gid_t group_id,
cryptohome::Platform* platform) {
brillo::SecureBlob tpm_seed = key.GetDerivedSystemKey(salt);
if (tpm_seed.empty()) {
LOG(ERROR) << "TPM Seed provided for " << filename << " is empty";
return false;
}
if (!platform->SafeCreateDirAndSetOwnershipAndPermissions(
tmp_dir,
/* mode=700 */ S_IRUSR | S_IWUSR | S_IXUSR, user_id, group_id)) {
PLOG(ERROR) << "Failed to CreateDir or SetOwnershipAndPermissions of "
<< tmp_dir;
return false;
}
auto file = tmp_dir.Append(filename);
if (!platform->WriteStringToFileAtomic(file, tpm_seed.to_string(),
/* mode=600 */ S_IRUSR | S_IWUSR)) {
PLOG(ERROR) << "Failed to write TPM seed to tmpfs file " << filename;
return false;
}
if (!platform->SetOwnership(file, user_id, group_id, true)) {
PLOG(ERROR) << "Failed to change ownership/perms of tmpfs file "
<< filename;
// Remove the file as it contains the tpm seed with incorrect owner.
PLOG_IF(ERROR, !base::DeleteFile(file)) << "Unable to remove file!";
return false;
}
return true;
}
// Send a secret derived from the system key to the biometric managers, if
// available, via a tmpfs file which will be read by bio_crypto_init.
bool SendSecretToBiodTmpFile(const mount_encrypted::EncryptionKey& key,
cryptohome::Platform* platform) {
// If there isn't a bio-sensor, don't bother.
if (!base::PathExists(base::FilePath(kBioCryptoInitPath))) {
LOG(INFO)
<< "There is no bio_crypto_init binary, so skip sending TPM seed.";
return true;
}
return SendSecretToTmpFile(key, std::string(kBioTpmSeedSalt),
base::FilePath(kBioTpmSeedTmpDir), kBioTpmSeedFile,
kBiodUid, kBiodGid, platform);
}
// Send a secret derived from the system key to hiberman, if available.
// available, via a tmpfs file which will be read by hiberman.
bool SendSecretToHibermanTmpFile(const mount_encrypted::EncryptionKey& key,
cryptohome::Platform* platform) {
if (!base::PathExists(base::FilePath(kHibermanPath))) {
LOG(INFO) << "There is no hiberman binary, so skip sending TPM seed.";
return true;
}
return SendSecretToTmpFile(key, std::string(kHibermanTpmSeedSalt),
base::FilePath(kHibermanTpmSeedTmpDir),
kHibermanTpmSeedFile, kHibermanUid, kHibermanGid,
platform);
}
// Originally .tpm_owned file is located in /mnt/stateful_partition. Since the
// directory can only be written by root, .tpm_owned won't be able to get
// touched by tpm_managerd if we run it in minijail. Therefore, we need to
// migrate the files from /mnt/stateful_partition to the files into
// /mnt/stateful_partition/unencrypted/tpm_manager. The migration is written
// here since mount-encrypted is started before tpm_managerd.
bool migrate_tpm_owership_state_file() {
auto dirname = base::FilePath(kTpmOwned).DirName();
if (!base::CreateDirectory(dirname)) {
LOG(ERROR) << "Failed to create dir for TPM pwnership state file.";
return false;
}
if (base::PathExists(base::FilePath(kOldTpmOwnershipStateFile))) {
LOG(INFO) << kOldTpmOwnershipStateFile << " exists. "
<< "Moving it to " << kTpmOwned;
return base::Move(base::FilePath(kOldTpmOwnershipStateFile),
base::FilePath((kTpmOwned)));
}
return true;
}
static result_code mount_encrypted_partition(
mount_encrypted::EncryptedFs* encrypted_fs,
const base::FilePath& rootdir,
cryptohome::Platform* platform,
bool safe_mount) {
result_code rc;
mount_encrypted::ScopedMountEncryptedMetricsSingleton scoped_metrics(
kMountEncryptedMetricsPath);
// For the mount operation at boot, return RESULT_FAIL_FATAL to trigger
// chromeos_startup do the stateful wipe.
rc = encrypted_fs->CheckStates();
if (rc != RESULT_SUCCESS)
return rc;
if (!migrate_tpm_owership_state_file()) {
LOG(ERROR) << "Failed to migrate tpm owership state file to" << kTpmOwned;
}
mount_encrypted::Tpm tpm;
auto loader = mount_encrypted::SystemKeyLoader::Create(&tpm, rootdir);
mount_encrypted::EncryptionKey key(loader.get(), rootdir);
if (shall_use_tpm_for_system_key() && safe_mount) {
if (!tpm.available()) {
// The TPM should be available before we load the system_key.
LOG(ERROR) << "TPM not available.";
// We shouldn't continue to load the system_key.
return RESULT_FAIL_FATAL;
}
rc = key.LoadChromeOSSystemKey();
} else {
rc = key.SetInsecureFallbackSystemKey();
}
mount_encrypted::MountEncryptedMetrics::Get()->ReportSystemKeyStatus(
key.system_key_status());
if (rc != RESULT_SUCCESS) {
return rc;
}
rc = key.LoadEncryptionKey();
mount_encrypted::MountEncryptedMetrics::Get()->ReportEncryptionKeyStatus(
key.encryption_key_status());
if (rc != RESULT_SUCCESS) {
return rc;
}
/* Log errors during sending seed to biod, but don't stop execution. */
if (has_chromefw()) {
LOG_IF(ERROR, !SendSecretToBiodTmpFile(key, platform))
<< "Failed to send TPM secret to biod.";
LOG_IF(ERROR, !SendSecretToHibermanTmpFile(key, platform))
<< "Failed to send TPM secret to hiberman.";
} else {
LOG(ERROR)
<< "Failed to load system key, biod and hiberman won't get a TPM seed.";
}
cryptohome::FileSystemKey encryption_key;
encryption_key.fek = key.encryption_key();
rc = encrypted_fs->Setup(encryption_key, key.is_fresh());
if (rc == RESULT_SUCCESS) {
/* Only check the lockbox when we are using TPM for system key. */
if (shall_use_tpm_for_system_key()) {
bool lockbox_valid = false;
if (loader->CheckLockbox(&lockbox_valid) == RESULT_SUCCESS) {
mount_encrypted::NvramSpace* lockbox_space = tpm.GetLockboxSpace();
if (lockbox_valid && lockbox_space->is_valid()) {
LOG(INFO) << "Lockbox is valid, exporting.";
nvram_export(lockbox_space->contents());
}
} else {
LOG(ERROR) << "Lockbox validity check error.";
}
}
}
LOG(INFO) << "Done.";
// Continue boot.
return rc;
}
static void print_usage(const char process_name[]) {
fprintf(stderr, "Usage: %s [info|finalize|umount|set|mount]\n", process_name);
}
int main(int argc, const char* argv[]) {
DEFINE_bool(unsafe, false, "mount encrypt partition with well known secret.");
brillo::FlagHelper::Init(argc, argv, "mount-encrypted");
brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderr);
logging::SetLogItems(false, // process ID
false, // thread ID
true, // timestamp
false); // tickcount
auto commandline = base::CommandLine::ForCurrentProcess();
auto args = commandline->GetArgs();
char* rootdir_env = getenv("MOUNT_ENCRYPTED_ROOT");
base::FilePath rootdir = base::FilePath(rootdir_env ? rootdir_env : "/");
cryptohome::Platform platform;
cryptohome::EncryptedContainerFactory encrypted_container_factory(&platform);
brillo::DeviceMapper device_mapper;
brillo::LogicalVolumeManager lvm;
auto encrypted_fs = mount_encrypted::EncryptedFs::Generate(
rootdir, &platform, &device_mapper, &lvm, &encrypted_container_factory);
if (!encrypted_fs) {
LOG(ERROR) << "Failed to create encrypted fs handler.";
return RESULT_FAIL_FATAL;
}
LOG(INFO) << "Starting.";
if (args.size() >= 1) {
if (args[0] == "umount") {
return encrypted_fs->Teardown();
} else if (args[0] == "info") {
// Report info from the encrypted mount.
return report_info(encrypted_fs.get(), rootdir);
} else if (args[0] == "set") {
return set_system_key(rootdir, args.size() >= 2 ? args[1].c_str() : NULL,
&platform);
} else if (args[0] == "mount") {
return mount_encrypted_partition(encrypted_fs.get(), rootdir, &platform,
!FLAGS_unsafe);
} else {
print_usage(argv[0]);
return RESULT_FAIL_FATAL;
}
}
// default operation is mount encrypted partition.
return mount_encrypted_partition(encrypted_fs.get(), rootdir, &platform,
!FLAGS_unsafe);
}