summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKees Cook <keescook@chromium.org>2012-02-27 12:43:15 -0800
committerGerrit <chrome-bot@google.com>2012-04-04 12:47:23 -0700
commit498977af89510bf5e44af0a0b8171d23fb770f4b (patch)
treed02c241a80410659ca1b3ef603c347edad2f344f
parent27f8838fb4e1f157320c4ec871e57dcfcaea5fab (diff)
downloadvboot-498977af89510bf5e44af0a0b8171d23fb770f4b.tar.gz
mount-encrypted: add encrypted mount helper
This adds the utility needed to mount the encrypted partition at boot time, as defined by the design document: https://docs.google.com/a/google.com/document/d/1VQTDXvNsEFcrUOmNC4OmCfJst49Pd_mxZ41nfKu5EPc/edit This still needs Cryptohome support and chromeos_startup to switch to using it. BUG=chromium-os:22172 TEST=lumpy build & manual testing Change-Id: Ib9f0b4e5ba1a8aeb4737d0c8ec72a8e0dee049da Signed-off-by: Kees Cook <keescook@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/16889 Reviewed-by: Elly Jones <ellyjones@chromium.org>
-rw-r--r--.gitignore2
-rw-r--r--utility/Makefile18
-rw-r--r--utility/mount-encrypted.c815
-rw-r--r--utility/mount-encrypted.h99
-rw-r--r--utility/mount-helpers.c650
-rw-r--r--utility/mount-helpers.h40
6 files changed, 1624 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index db3c8b0d..089802ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
/build
+/build-au
+/build-main
ID
diff --git a/utility/Makefile b/utility/Makefile
index e0a0f221..59d624a5 100644
--- a/utility/Makefile
+++ b/utility/Makefile
@@ -40,6 +40,8 @@ TARGET_NAMES = crossystem \
ifeq ($(MINIMAL),)
TARGET_NAMES += bmpblk_font bmpblk_utility eficompress efidecompress
+else
+TARGET_NAMES += mount-encrypted
endif
TARGET_BINS = $(addprefix ${BUILD_ROOT}/,$(TARGET_NAMES))
@@ -157,6 +159,22 @@ ${BUILD_ROOT}/vbutil_what_keys: vbutil_what_keys
${BUILD_ROOT}/tpmc: tpmc.c $(LIBS)
$(CC) $(CFLAGS) $< -o $@ $(LIBS)
+${BUILD_ROOT}/mount-helpers.o: mount-helpers.c mount-helpers.h mount-encrypted.h
+ $(CC) -Wall -Werror -O2 -D_FORTIFY_SOURCE=2 -fstack-protector \
+ $(shell pkg-config --cflags glib-2.0 openssl) \
+ -c $< -o $@
+
+# The embedded libcrypto includes conflict with the shipped openssl,
+# so this builds without the common CFLAGS (and those includes).
+${BUILD_ROOT}/mount-encrypted: mount-encrypted.c mount-encrypted.h \
+ ${BUILD_ROOT}/mount-helpers.o $(LIBS)
+ $(CC) -Wall -Werror -O2 -D_FORTIFY_SOURCE=2 -fstack-protector \
+ $(shell pkg-config --cflags glib-2.0 openssl) \
+ -I$(FWDIR)/include \
+ -I$(HOSTDIR)/include \
+ $< -o $@ $(shell pkg-config --libs glib-2.0 openssl) \
+ ${BUILD_ROOT}/mount-helpers.o $(LIBS)
+
${BUILD_ROOT}/dev_sign_file: dev_sign_file.c $(LIBS)
$(CC) $(CFLAGS) $< -o $@ $(LIBS) -lcrypto
diff --git a/utility/mount-encrypted.c b/utility/mount-encrypted.c
new file mode 100644
index 00000000..2a1a49d5
--- /dev/null
+++ b/utility/mount-encrypted.c
@@ -0,0 +1,815 @@
+/* Copyright (c) 2012 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.
+ *
+ * This tool will attempt to mount or create the encrypted stateful partition,
+ * and the various bind mountable subdirectories.
+ *
+ */
+#define _GNU_SOURCE
+#define _FILE_OFFSET_BITS 64
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <linux/fs.h>
+
+#include <glib.h>
+
+#include <openssl/rand.h>
+
+#define CHROMEOS_ENVIRONMENT
+#include "tlcl.h"
+#include "crossystem.h"
+
+#include "mount-encrypted.h"
+#include "mount-helpers.h"
+
+#define STATEFUL_MNT "/mnt/stateful_partition"
+#define ENCRYPTED_MNT STATEFUL_MNT "/encrypted"
+#define DMCRYPT_DEV_NAME "encstateful"
+#define BUF_SIZE 1024
+#define PROP_SIZE 64
+
+static const gchar * const kRootDir = "/";
+static const gchar * const kKernelCmdline = "/proc/cmdline";
+static const gchar * const kKernelCmdlineOption = " encrypted-stateful-key=";
+static const gchar * const kStatefulMount = STATEFUL_MNT;
+static const gchar * const kEncryptedKey = STATEFUL_MNT "/encrypted.key";
+static const gchar * const kEncryptedBlock = STATEFUL_MNT "/encrypted.block";
+static const gchar * const kEncryptedMount = ENCRYPTED_MNT;
+static const gchar * const kEncryptedFSType = "ext4";
+static const gchar * const kCryptName = DMCRYPT_DEV_NAME;
+static const gchar * const kCryptDev = "/dev/mapper/" DMCRYPT_DEV_NAME;
+static const gchar * const kTpmDev = "/dev/tpm0";
+static const gchar * const kNullDev = "/dev/null";
+static const float kSizePercent = 0.3;
+static const uint32_t kLockboxIndex = 0x20000004;
+static const uint32_t kLockboxSizeV1 = 0x2c;
+static const uint32_t kLockboxSizeV2 = 0x45;
+static const uint32_t kLockboxSaltOffset = 0x5;
+static const size_t kSectorSize = 512;
+static const size_t kExt4BlockSize = 4096;
+static const size_t kExt4MinBytes = 64 * 1024 * 1024;
+
+static struct bind_mount {
+ const char * const src;
+ const char * const old;
+ const char * const dst;
+ const char * const owner;
+ const char * const group;
+ const mode_t mode;
+ const int submount; /* Submount is bound already. */
+ const int optional; /* Non-fatal if this bind fails. */
+} bind_mounts[] = {
+#if DEBUG_ENABLED == 2
+# define DEBUG_DEST ".new"
+#else
+# define DEBUG_DEST ""
+#endif
+ { ENCRYPTED_MNT "/var", STATEFUL_MNT "/var",
+ "/var" DEBUG_DEST, "root", "root",
+ S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, 0, 0 },
+ { ENCRYPTED_MNT "/chronos", STATEFUL_MNT "/home/chronos",
+ "/home/chronos" DEBUG_DEST, "chronos", "chronos",
+ S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH, 1, 1 },
+ { },
+};
+
+int has_tpm = 0;
+
+static void sha256(char *string, uint8_t *digest)
+{
+ SHA256((unsigned char *)string, strlen(string), digest);
+}
+
+/* Extract the desired system key from the kernel's boot command line. */
+static int get_key_from_cmdline(uint8_t *digest)
+{
+ int result = 0;
+ gchar *buffer;
+ gsize length;
+ char *cmdline, *option_end;
+ /* Option name without the leading space. */
+ const gchar *option = kKernelCmdlineOption + 1;
+
+ if (!g_file_get_contents(kKernelCmdline, &buffer, &length, NULL)) {
+ PERROR(kKernelCmdline);
+ return 0;
+ }
+
+ /* Find a string match either at start of string or following
+ * a space.
+ */
+ cmdline = buffer;
+ if (strncmp(cmdline, option, strlen(option)) == 0 ||
+ (cmdline = strstr(cmdline, kKernelCmdlineOption))) {
+ /* The "=" exists because it is in kKernelCmdlineOption. */
+ cmdline = strstr(cmdline, "=");
+ /* strchrnul() cannot return NULL. */
+ option_end = strchrnul(cmdline, ' ');
+ *option_end = '\0';
+ sha256(cmdline, digest);
+ debug_dump_hex("system key", digest, DIGEST_LENGTH);
+ result = 1;
+ }
+
+ g_free(buffer);
+ return result;
+}
+
+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 (!VbGetSystemPropertyString("mainfw_type", fw, sizeof(fw)))
+ state = 0;
+ else
+ state = strcmp(fw, "nonchrome") != 0;
+ return state;
+}
+
+static int is_cr48(void)
+{
+ static int state = -1;
+ char hwid[PROP_SIZE];
+
+ /* Cache the state so we don't have to perform the query again. */
+ if (state != -1)
+ return state;
+
+ if (!VbGetSystemPropertyString("hwid", hwid, sizeof(hwid)))
+ state = 0;
+ else
+ state = strstr(hwid, "MARIO") != NULL;
+ return state;
+}
+
+static int
+_read_nvram(uint8_t *buffer, size_t len, uint32_t index, uint32_t size)
+{
+ if (size > len) {
+ ERROR("NVRAM size (0x%x > 0x%zx) is too big", size, len);
+ return 0;
+ }
+
+ return TlclRead(index, buffer, size);
+}
+
+/*
+ * Cases:
+ * - no NVRAM area at all (OOBE)
+ * - defined NVRAM area, but TPM not Owned
+ * - defined NVRAM area, but not Finalized
+ * - legacy NVRAM area (migration needed)
+ * - modern NVRAM area (\o/)
+ */
+// TODO(keescook): recovery code needs to wipe NVRAM area to new size?
+static int get_nvram_key(uint8_t *digest, int *old_lockbox)
+{
+ TPM_PERMANENT_FLAGS pflags;
+ uint8_t value[kLockboxSizeV2], bytes_anded, bytes_ored;
+ uint32_t size, result, i;
+ uint8_t *rand_bytes;
+ uint32_t rand_size;
+
+ /* Start by expecting modern NVRAM area. */
+ *old_lockbox = 0;
+ size = kLockboxSizeV2;
+ result = _read_nvram(value, sizeof(value), kLockboxIndex, size);
+ if (result) {
+ size = kLockboxSizeV1;
+ result = _read_nvram(value, sizeof(value), kLockboxIndex, size);
+ if (result) {
+ /* No NVRAM area at all. */
+ INFO("No NVRAM area defined.");
+ return 0;
+ }
+ /* Legacy NVRAM area. */
+ INFO("Legacy NVRAM area found.");
+ *old_lockbox = 1;
+ } else {
+ INFO("NVRAM area found.");
+ }
+
+ debug_dump_hex("nvram", value, size);
+
+ /* Ignore defined but unowned NVRAM area. */
+ result = TlclGetPermanentFlags(&pflags);
+ if (result) {
+ INFO("Could not read TPM Permanent Flags.");
+ return 0;
+ }
+ if (!pflags.ownership) {
+ INFO("TPM not Owned, ignoring NVRAM area.");
+ return 0;
+ }
+
+ /* Ignore defined but unwritten NVRAM area. */
+ bytes_ored = 0x0;
+ bytes_anded = 0xff;
+ for (i = 0; i < size; ++i) {
+ bytes_ored |= value[i];
+ bytes_anded &= value[i];
+ }
+ if (bytes_ored == 0x0 || bytes_anded == 0xff) {
+ INFO("NVRAM area has been defined but not written.");
+ return 0;
+ }
+
+ /* Choose random bytes to use based on NVRAM version. */
+ if (*old_lockbox) {
+ rand_bytes = value;
+ rand_size = size;
+ } else {
+ rand_bytes = value + kLockboxSaltOffset;
+ if (kLockboxSaltOffset + DIGEST_LENGTH > size) {
+ INFO("Impossibly small NVRAM area size (%d).", size);
+ return 0;
+ }
+ rand_size = DIGEST_LENGTH;
+ }
+ if (rand_size < DIGEST_LENGTH) {
+ INFO("Impossibly small rand_size (%d).", rand_size);
+ return 0;
+ }
+ debug_dump_hex("rand_bytes", rand_bytes, rand_size);
+
+ SHA256(rand_bytes, rand_size, digest);
+ debug_dump_hex("system key", digest, DIGEST_LENGTH);
+
+ return 1;
+}
+
+/* Find the system key used for decrypting the stored encryption key.
+ * ChromeOS devices are required to use the NVRAM area (excepting CR-48s),
+ * all the rest will fallback through various places (kernel command line,
+ * BIOS UUID, and finally a static value) for a system key.
+ */
+static int find_system_key(uint8_t *digest, int *migration_allowed)
+{
+ gchar *key;
+ gsize length;
+
+ /* By default, do not allow migration. */
+ *migration_allowed = 0;
+ if (has_chromefw()) {
+ int rc;
+ rc = get_nvram_key(digest, migration_allowed);
+
+ /* Since the CR-48 did not ship with a lockbox area, they
+ * are allowed to fall back to non-NVRAM system keys.
+ */
+ if (rc || !is_cr48()) {
+ INFO("Using NVRAM as system key; %s.",
+ rc ? "already populated"
+ : "needs population");
+ return rc;
+ }
+ }
+
+ if (get_key_from_cmdline(digest)) {
+ INFO("Using kernel command line argument as system key.");
+ return 1;
+ }
+ if (g_file_get_contents("/sys/class/dmi/id/product_uuid",
+ &key, &length, NULL)) {
+ sha256(key, digest);
+ debug_dump_hex("system key", digest, DIGEST_LENGTH);
+ g_free(key);
+ INFO("Using UUID as system key.");
+ return 1;
+ }
+
+ INFO("Using default insecure system key.");
+ sha256("default unsafe static key", digest);
+ debug_dump_hex("system key", digest, DIGEST_LENGTH);
+ return 1;
+}
+
+/* Returns 1 on success, 0 on failure. */
+static int get_random_bytes_tpm(unsigned char *buffer, int wanted)
+{
+ uint32_t remaining = wanted;
+
+ /* Read random bytes from TPM, which can return short reads. */
+ while (remaining) {
+ uint32_t result, size;
+
+ result = TlclGetRandom(buffer + (wanted - remaining),
+ remaining, &size);
+ if (result || size > remaining) {
+ ERROR("TPM GetRandom failed.");
+ return 0;
+ }
+ remaining -= size;
+ }
+
+ return 1;
+}
+
+/* Returns 1 on success, 0 on failure. */
+static int get_random_bytes(unsigned char *buffer, int wanted)
+{
+ if (has_tpm)
+ return get_random_bytes_tpm(buffer, wanted);
+ else
+ return RAND_bytes(buffer, wanted);
+}
+
+static char *choose_encryption_key(void)
+{
+ unsigned char rand_bytes[DIGEST_LENGTH];
+ unsigned char digest[DIGEST_LENGTH];
+
+ get_random_bytes(rand_bytes, sizeof(rand_bytes));
+
+ SHA256(rand_bytes, DIGEST_LENGTH, digest);
+ debug_dump_hex("encryption key", digest, DIGEST_LENGTH);
+
+ return stringify_hex(digest, DIGEST_LENGTH);
+}
+
+static int check_bind_src(struct bind_mount *bind)
+{
+ struct passwd *user;
+ struct group *group;
+
+ if (!(user = getpwnam(bind->owner))) {
+ PERROR("getpwnam(%s)", bind->owner);
+ return -1;
+ }
+ if (!(group = getgrnam(bind->group))) {
+ PERROR("getgrnam(%s)", bind->group);
+ return -1;
+ }
+
+ if (access(bind->src, R_OK) && mkdir(bind->src, bind->mode)) {
+ PERROR("mkdir(%s)", bind->src);
+ return -1;
+ }
+ /* Must do explicit chmod since mkdir()'s mode respects umask. */
+ if (chmod(bind->src, bind->mode)) {
+ PERROR("chmod(%s)", bind->src);
+ return -1;
+ }
+ if (chown(bind->src, user->pw_uid, group->gr_gid)) {
+ PERROR("chown(%s)", bind->src);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void migrate_contents(struct bind_mount *bind)
+{
+ gchar *old;
+
+ /* Skip migration if the old bind src is missing. */
+ if (!bind->old || access(bind->old, R_OK))
+ return;
+
+ INFO("Migrating bind mount contents %s to %s.", bind->old, bind->src);
+ check_bind_src(bind);
+
+ if (!(old = g_strdup_printf("%s/.", bind->old))) {
+ PERROR("g_strdup_printf");
+ goto remove;
+ }
+
+ const gchar *cp[] = {
+ "/bin/cp", "-a",
+ old,
+ bind->src,
+ NULL
+ };
+
+ if (runcmd(cp, NULL) != 0) {
+ /* If the copy failed, it may have partially populated the
+ * new source, so we need to remove the new source and
+ * rebuild it. Regardless, the old source must be removed
+ * as well.
+ */
+ INFO("Failed to migrate %s to %s!", bind->old, bind->src);
+ remove_tree(bind->src);
+ check_bind_src(bind);
+ }
+
+remove:
+ g_free(old);
+
+ /* The removal of the old directory needs to happen at finalize
+ * time, otherwise /var state gets lost on a migration if the
+ * system is powered off before the encryption key is saved.
+ */
+ return;
+}
+
+static void finalize(uint8_t *system_key, char *encryption_key)
+{
+ struct bind_mount *bind;
+
+ INFO("Writing keyfile %s.", kEncryptedKey);
+ if (!keyfile_write(kEncryptedKey, system_key, encryption_key)) {
+ ERROR("Failed to write %s -- aborting.", kEncryptedKey);
+ return;
+ }
+
+ for (bind = bind_mounts; bind->src; ++ bind) {
+ if (access(bind->old, R_OK))
+ continue;
+ INFO("Removing %s.", bind->old);
+#if DEBUG_ENABLED
+ continue;
+#endif
+ remove_tree(bind->old);
+ }
+}
+
+/* This triggers the live encryption key to be written to disk, encrypted
+ * by the system key. It is intended to be called by Cryptohome once the
+ * TPM is done being set up. If the system key is passed as an argument,
+ * use it, otherwise attempt to query the TPM again.
+ */
+static int finalize_from_cmdline(char *key)
+{
+ uint8_t system_key[DIGEST_LENGTH];
+ char *encryption_key;
+ int migrate;
+
+ if (key) {
+ if (strlen(key) != 2 * DIGEST_LENGTH) {
+ ERROR("Invalid key length.");
+ return EXIT_FAILURE;
+ }
+
+ if (!hexify_string(key, system_key, DIGEST_LENGTH)) {
+ ERROR("Failed to convert hex string to byte array");
+ return EXIT_FAILURE;
+ }
+ } else {
+ if (!find_system_key(system_key, &migrate)) {
+ ERROR("Could not locate system key.");
+ return EXIT_FAILURE;
+ }
+ }
+
+ encryption_key = dm_get_key(kCryptDev);
+ if (!encryption_key) {
+ ERROR("Could not locate encryption key for %s.", kCryptDev);
+ return EXIT_FAILURE;
+ }
+
+ finalize(system_key, encryption_key);
+
+ return EXIT_SUCCESS;
+}
+
+static int setup_encrypted(void)
+{
+ int has_system_key;
+ uint8_t system_key[DIGEST_LENGTH];
+ char *encryption_key = NULL;
+ int migrate_allowed = 0, migrate_needed = 0, rebuild = 0;
+ gchar *lodev = NULL;
+ size_t sectors;
+ struct bind_mount *bind;
+ int sparsefd;
+ size_t blocks_min, blocks_max;
+
+ /* Use the "system key" to decrypt the "encryption key" stored in
+ * the stateful partition.
+ */
+ has_system_key = find_system_key(system_key, &migrate_allowed);
+ if (has_system_key) {
+ encryption_key = keyfile_read(kEncryptedKey, system_key);
+ } else {
+ INFO("No usable system key found.");
+ }
+
+ if (encryption_key) {
+ /* If we found a stored encryption key, we've already
+ * finished a complete login and Cryptohome Finalize
+ * so migration is finished.
+ */
+ migrate_allowed = 0;
+ } else {
+ INFO("Generating new encryption key.");
+ encryption_key = choose_encryption_key();
+ if (!encryption_key)
+ return 0;
+ rebuild = 1;
+ }
+
+ if (rebuild) {
+ struct statvfs buf;
+ off_t size;
+
+ /* Wipe out the old files, and ignore errors. */
+ unlink(kEncryptedKey);
+ unlink(kEncryptedBlock);
+
+ /* Calculate the desired size of the new partition. */
+ if (statvfs(kStatefulMount, &buf)) {
+ PERROR(kStatefulMount);
+ return 0;
+ }
+ size = buf.f_blocks;
+ size *= kSizePercent;
+ size *= buf.f_frsize;
+
+ INFO("Creating sparse backing file with size %llu.",
+ (unsigned long long)size);
+
+ /* Create the sparse file. */
+ sparsefd = sparse_create(kEncryptedBlock, size);
+ if (sparsefd < 0) {
+ PERROR(kEncryptedBlock);
+ return 0;
+ }
+ } else {
+ sparsefd = open(kEncryptedBlock, O_RDWR | O_NOFOLLOW);
+ if (sparsefd < 0) {
+ PERROR(kEncryptedBlock);
+ return 0;
+ }
+ }
+
+ /* Set up loopback device. */
+ INFO("Loopback attaching %s.", kEncryptedBlock);
+ lodev = loop_attach(sparsefd, kEncryptedBlock);
+ if (!lodev || strlen(lodev) == 0) {
+ ERROR("loop_attach failed");
+ goto failed;
+ }
+
+ /* Get size as seen by block device. */
+ sectors = get_sectors(lodev);
+ if (!sectors) {
+ ERROR("Failed to read device size");
+ goto lo_cleanup;
+ }
+
+ /* Mount loopback device with dm-crypt using the encryption key. */
+ INFO("Setting up dm-crypt %s as %s.", lodev, kCryptDev);
+ if (!dm_setup(sectors, encryption_key, kCryptName, lodev,
+ kCryptDev)) {
+ ERROR("dm_setup failed");
+ goto lo_cleanup;
+ }
+
+ /* Decide now if any migration will happen. If so, we will not
+ * grow the new filesystem in the background, since we need to
+ * copy the contents over before /var is valid again.
+ */
+ if (!rebuild)
+ migrate_allowed = 0;
+ if (migrate_allowed) {
+ for (bind = bind_mounts; bind->src; ++ bind) {
+ /* Skip mounts that have no prior location defined. */
+ if (!bind->old)
+ continue;
+ /* Skip mounts that have no prior data on disk. */
+ if (access(bind->old, R_OK) != 0)
+ continue;
+
+ migrate_needed = 1;
+ }
+ }
+
+ /* Calculate filesystem min/max size. */
+ blocks_max = sectors / (kExt4BlockSize / kSectorSize);
+ blocks_min = migrate_needed ? blocks_max :
+ kExt4MinBytes / kExt4BlockSize;
+ if (rebuild) {
+ INFO("Building filesystem on %s "
+ "(blocksize:%zu, min:%zu, max:%zu).",
+ kCryptDev, kExt4BlockSize, blocks_min, blocks_max);
+ if (!filesystem_build(kCryptDev, kExt4BlockSize,
+ blocks_min, blocks_max))
+ goto dm_cleanup;
+ }
+
+ /* Mount the dm-crypt partition finally. */
+ INFO("Mounting %s onto %s.", kCryptDev, kEncryptedMount);
+ if (access(kEncryptedMount, R_OK) &&
+ mkdir(kEncryptedMount, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
+ PERROR(kCryptDev);
+ goto dm_cleanup;
+ }
+ if (mount(kCryptDev, kEncryptedMount, kEncryptedFSType,
+ MS_NODEV | MS_NOEXEC | MS_NOSUID | MS_RELATIME,
+ "discard")) {
+ PERROR("mount(%s,%s)", kCryptDev, kEncryptedMount);
+ goto dm_cleanup;
+ }
+
+ /* Always spawn filesystem resizer, in case growth was interrupted. */
+ /* TODO(keescook): if already full size, don't resize. */
+ filesystem_resizer(kCryptDev, blocks_min, blocks_max);
+
+ /* If the legacy lockbox NVRAM area exists, we've rebuilt the
+ * filesystem, and there are old bind sources on disk, attempt
+ * migration.
+ */
+ if (migrate_needed && migrate_allowed) {
+ /* Migration needs to happen before bind mounting because
+ * some partitions were not already on the stateful partition,
+ * and would be over-mounted by the new bind mount.
+ */
+ for (bind = bind_mounts; bind->src; ++ bind)
+ migrate_contents(bind);
+ }
+
+ /* Perform bind mounts. */
+ for (bind = bind_mounts; bind->src; ++ bind) {
+ INFO("Bind mounting %s onto %s.", bind->src, bind->dst);
+ if (check_bind_src(bind) ||
+ mount(bind->src, bind->dst, "none", MS_BIND, NULL)) {
+ PERROR("mount(%s,%s)", bind->src, bind->dst);
+ if (bind->optional)
+ continue;
+ goto unbind;
+ }
+ }
+
+ /* Devices that are not using NVRAM for their system key do not
+ * need to wait for the NVRAM area to be populated by Cryptohome
+ * and a call to "finalize". Devices that already have the NVRAM
+ * area populated and are being rebuilt don't need to wait for
+ * Cryptohome because the NVRAM area isn't going to change.
+ */
+ if (rebuild && has_system_key)
+ finalize(system_key, encryption_key);
+
+ free(lodev);
+ return 1;
+
+unbind:
+ for (bind = bind_mounts; bind->src; ++ bind) {
+ INFO("Unmounting %s.", bind->dst);
+ umount(bind->dst);
+ }
+
+ INFO("Unmounting %s.", kEncryptedMount);
+ umount(kEncryptedMount);
+
+dm_cleanup:
+ INFO("Removing %s.", kCryptDev);
+ /* TODO(keescook): something holds this open briefly on mkfs failure
+ * and I haven't been able to catch it yet. Adding an "fuser" call
+ * here is sufficient to lose the race. Instead, just sleep during
+ * the error path.
+ */
+ sleep(1);
+ dm_teardown(kCryptDev);
+
+lo_cleanup:
+ INFO("Unlooping %s.", lodev);
+ loop_detach(lodev);
+
+failed:
+ free(lodev);
+
+ return 0;
+}
+
+
+static void check_mount_states(void)
+{
+ struct bind_mount *bind;
+
+ /* Verify stateful partition exists and is mounted. */
+ if (access(kStatefulMount, R_OK) ||
+ same_vfs(kStatefulMount, kRootDir)) {
+ INFO("%s is not mounted.", kStatefulMount);
+ exit(1);
+ }
+
+ /* Verify encrypted partition is missing or not already mounted. */
+ if (access(kEncryptedMount, R_OK) == 0 &&
+ !same_vfs(kEncryptedMount, kStatefulMount)) {
+ INFO("%s already appears to be mounted.", kEncryptedMount);
+ exit(0);
+ }
+
+ /* Verify that bind mount targets exist. */
+ for (bind = bind_mounts; bind->src; ++ bind) {
+ if (access(bind->dst, R_OK)) {
+ PERROR("%s mount point is missing.", bind->dst);
+ exit(1);
+ }
+ }
+
+ /* Verify that old bind mounts on stateful haven't happened yet. */
+ for (bind = bind_mounts; bind->src; ++ bind) {
+ if (bind->submount)
+ continue;
+
+ if (same_vfs(bind->dst, kStatefulMount)) {
+ INFO("%s already bind mounted.", bind->dst);
+ exit(1);
+ }
+ }
+
+ INFO("VFS mount state sanity check ok.");
+}
+
+int device_details(void)
+{
+ uint8_t system_key[DIGEST_LENGTH];
+ TPM_PERMANENT_FLAGS pflags;
+ int old_lockbox = -1;
+
+ printf("TPM: %s\n", has_tpm ? "yes" : "no");
+ if (has_tpm) {
+ printf("TPM Owned: %s\n", TlclGetPermanentFlags(&pflags) ?
+ "fail" : (pflags.ownership ? "yes" : "no"));
+ }
+ printf("ChromeOS: %s\n", has_chromefw() ? "yes" : "no");
+ printf("CR48: %s\n", is_cr48() ? "yes" : "no");
+ if (has_chromefw()) {
+ int rc;
+ rc = get_nvram_key(system_key, &old_lockbox);
+ if (!rc)
+ printf("NVRAM: missing\n");
+ else {
+ printf("NVRAM: %s, %s\n",
+ old_lockbox ? "legacy" : "modern",
+ rc ? "available" : "ignored");
+ }
+ }
+ else {
+ printf("NVRAM: not present\n");
+ }
+
+ return EXIT_SUCCESS;
+}
+
+void init_tpm(void)
+{
+ int tpm;
+
+ tpm = open(kTpmDev, O_RDWR);
+ if (tpm >= 0) {
+ has_tpm = 1;
+ close(tpm);
+ }
+ else {
+ /* TlclLibInit does not fail, it exits, so instead,
+ * have it open /dev/null if the TPM is not available.
+ */
+ setenv("TPM_DEVICE_PATH", kNullDev, 1);
+ }
+ TlclLibInit();
+}
+
+int main(int argc, char *argv[])
+{
+ int okay;
+
+ INFO_INIT("Starting.");
+ init_tpm();
+
+ if (argc > 1) {
+ if (!strcmp(argv[1], "device"))
+ return device_details();
+ if (!strcmp(argv[1], "finalize"))
+ return finalize_from_cmdline(argc > 2 ? argv[2] : NULL);
+
+ fprintf(stderr, "Usage: %s [device|finalize]\n",
+ argv[0]);
+ return 1;
+ }
+
+ check_mount_states();
+
+ okay = setup_encrypted();
+ if (!okay) {
+ INFO("Setup failed -- clearing files and retrying.");
+ unlink(kEncryptedKey);
+ unlink(kEncryptedBlock);
+ okay = setup_encrypted();
+ }
+
+ INFO("Done.");
+
+ /* Continue boot. */
+ return !okay;
+}
diff --git a/utility/mount-encrypted.h b/utility/mount-encrypted.h
new file mode 100644
index 00000000..df5b1d2d
--- /dev/null
+++ b/utility/mount-encrypted.h
@@ -0,0 +1,99 @@
+/* Copyright (c) 2012 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.
+ *
+ * Private header file for mount-encrypted helper tool.
+ */
+#ifndef _MOUNT_ENCRYPTED_H_
+#define _MOUNT_ENCRYPTED_H_
+
+/* TODO(keescook): Disable debugging in production. */
+#define DEBUG_ENABLED 1
+
+#include <openssl/err.h>
+#include <openssl/sha.h>
+
+#define DIGEST_LENGTH SHA256_DIGEST_LENGTH
+
+#define _ERROR(f, a...) do { \
+ fprintf(stderr, "ERROR %s (%s, %d): ", \
+ __func__, __FILE__, __LINE__); \
+ fprintf(stderr, f, ## a); \
+} while (0)
+#define ERROR(f, a...) do { \
+ _ERROR(f, ## a); \
+ fprintf(stderr, "\n"); \
+} while (0)
+#define PERROR(f, a...) do { \
+ _ERROR(f, ## a); \
+ fprintf(stderr, ": %s\n", strerror(errno)); \
+} while (0)
+
+#define SSL_ERROR(f, a...) do { \
+ ERR_load_crypto_strings(); \
+ _ERROR(f, ## a); \
+ fprintf(stderr, "%s\n", ERR_error_string(ERR_get_error(), NULL)); \
+} while (0)
+
+#if DEBUG_ENABLED
+static struct timeval tick;
+# define TICK_INIT() gettimeofday(&tick, NULL)
+# ifdef DEBUG_TIME_DELTA
+# define TICK_REPORT() do { \
+ struct timeval now, diff; \
+ gettimeofday(&now, NULL); \
+ diff.tv_sec = now.tv_sec - tick.tv_sec; \
+ if (tick.tv_usec > now.tv_usec) { \
+ diff.tv_sec -= 1; \
+ diff.tv_usec = 1000000 - tick.tv_usec + now.tv_usec; \
+ } else { \
+ diff.tv_usec = now.tv_usec - tick.tv_usec; \
+ } \
+ tick = now; \
+ printf("\tTook: [%2d.%06d]\n", (int)diff.tv_sec, (int)diff.tv_usec); \
+} while (0)
+# else
+# define TICK_REPORT() do { \
+ gettimeofday(&tick, NULL); \
+ printf("[%d:%2d.%06d] ", getpid(), (int)tick.tv_sec, (int)tick.tv_usec); \
+} while (0)
+# endif
+#else
+# define TICK_INIT() do { } while (0)
+# define TICK_REPORT() do { } while (0)
+#endif
+
+#define INFO(f, a...) do { \
+ TICK_REPORT(); \
+ printf(f, ## a); \
+ printf("\n"); \
+} while (0)
+#define INFO_INIT(f, a...) do { \
+ TICK_INIT(); \
+ INFO(f, ## a); \
+} while (0)
+#if DEBUG_ENABLED
+# define DEBUG(f, a...) do { \
+ printf(f, ## a); \
+ printf("\n"); \
+} while (0)
+#else
+# define DEBUG(f, a...) do { } while (0)
+#endif
+
+#if DEBUG_ENABLED
+static inline void debug_dump_hex(const char *name, uint8_t *data,
+ uint32_t size)
+{
+ int i;
+ printf("%s: ", name);
+ for (i = 0; i < size; i++) {
+ printf("%02x ", data[i]);
+ }
+ printf("\n");
+}
+#else
+# define debug_dump_hex(n, d, s) do { } while (0)
+#endif
+
+#endif /* _MOUNT_ENCRYPTED_H_ */
diff --git a/utility/mount-helpers.c b/utility/mount-helpers.c
new file mode 100644
index 00000000..b6f31c30
--- /dev/null
+++ b/utility/mount-helpers.c
@@ -0,0 +1,650 @@
+/* Copyright (c) 2012 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.
+ *
+ * This is a collection of helper utilities for use with the "mount-encrypted"
+ * utility.
+ *
+ */
+#define _GNU_SOURCE
+#define _FILE_OFFSET_BITS 64
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <linux/fs.h>
+#include <linux/loop.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <openssl/evp.h>
+
+#include "mount-encrypted.h"
+#include "mount-helpers.h"
+
+static const gchar * const kRootDir = "/";
+static const gchar * const kLoopTemplate = "/dev/loop%d";
+static const int kLoopMajor = 7;
+static const int kLoopMax = 8;
+static const unsigned int kResizeStepSeconds = 2;
+static const size_t kResizeBlocks = 32768 * 10;
+static const gchar * const kExt4ExtendedOptions = "discard,lazy_itable_init";
+
+int remove_tree(const char *tree)
+{
+ const gchar *rm[] = {
+ "/bin/rm", "-rf", tree,
+ NULL
+ };
+
+ return runcmd(rm, NULL);
+}
+
+size_t get_sectors(const char *device)
+{
+ size_t sectors;
+ int fd;
+ if ((fd = open(device, O_RDONLY | O_NOFOLLOW)) < 0) {
+ PERROR("open(%s)", device);
+ return 0;
+ }
+ if (ioctl(fd, BLKGETSIZE, &sectors)) {
+ PERROR("ioctl(%s, BLKGETSIZE)", device);
+ return 0;
+ }
+ close(fd);
+ return sectors;
+}
+
+int runcmd(const gchar *argv[], gchar **output)
+{
+ gint rc;
+ gchar *out = NULL, *errout = NULL;
+ GError *err = NULL;
+
+ g_spawn_sync(kRootDir, (gchar **)argv, NULL, 0, NULL, NULL,
+ &out, &errout, &rc, &err);
+ if (err) {
+ ERROR("%s: %s", argv[0], err->message);
+ g_error_free(err);
+ return -1;
+ }
+
+ if (rc)
+ ERROR("%s failed (%d)\n%s\n%s", argv[0], rc, out, errout);
+
+ if (output)
+ *output = out;
+ else
+ g_free(out);
+ g_free(errout);
+
+ return rc;
+}
+
+int same_vfs(const char *mnt_a, const char *mnt_b)
+{
+ struct statvfs stat_a, stat_b;
+
+ if (statvfs(mnt_a, &stat_a)) {
+ PERROR("statvfs(%s)", mnt_a);
+ exit(1);
+ }
+ if (statvfs(mnt_b, &stat_b)) {
+ PERROR("statvfs(%s)", mnt_b);
+ exit(1);
+ }
+ return (stat_a.f_fsid == stat_b.f_fsid);
+}
+
+/* Returns allocated string that holds [length]*2 + 1 characters. */
+char *stringify_hex(uint8_t *binary, size_t length)
+{
+ char *string;
+ size_t i;
+
+ string = malloc(length * 2 + 1);
+ if (!string) {
+ PERROR("malloc");
+ return NULL;
+ }
+ for (i = 0; i < length; ++i)
+ sprintf(string + (i * 2), "%02x", binary[i]);
+ string[length * 2] = '\0';
+
+ return string;
+}
+
+/* Returns allocated byte array that holds strlen([string])/2 bytes. */
+uint8_t *hexify_string(char *string, uint8_t *binary, size_t length)
+{
+ size_t bytes, i;
+
+ bytes = strlen(string) / 2;
+ if (bytes > length) {
+ ERROR("Hex string too long (%zu) for byte array (%zu)",
+ bytes, length);
+ return NULL;
+ }
+
+ for (i = 0; i < bytes; ++i) {
+ if (sscanf(&string[i * 2], "%2hhx", &binary[i]) != 1) {
+ ERROR("Invalid hex code at byte %zu.", i);
+ return NULL;
+ }
+ }
+
+ return binary;
+}
+
+static int is_loop_device(int fd)
+{
+ struct stat info;
+
+ return (fstat(fd, &info) == 0 && S_ISBLK(info.st_mode) &&
+ major(info.st_rdev) == kLoopMajor);
+}
+
+static int loop_is_attached(int fd)
+{
+ struct loop_info info;
+
+ errno = 0;
+ if (ioctl(fd, LOOP_GET_STATUS, &info) && errno == ENXIO)
+ return 0;
+
+ return 1;
+}
+
+static int loop_allocate(gchar **loopback)
+{
+ int i, fd;
+
+ *loopback = NULL;
+ for (i = 0; i < kLoopMax; ++i) {
+ g_free(*loopback);
+ *loopback = g_strdup_printf(kLoopTemplate, i);
+ if (!*loopback) {
+ PERROR("g_strdup_printf");
+ return -1;
+ }
+
+ fd = open(*loopback, O_RDONLY | O_NOFOLLOW);
+ if (fd < 0) {
+ PERROR("open(%s)", *loopback);
+ goto failed;
+ }
+ if (is_loop_device(fd) && !loop_is_attached(fd)) {
+ close(fd);
+ fd = open(*loopback, O_RDWR | O_NOFOLLOW);
+ if (is_loop_device(fd) && !loop_is_attached(fd))
+ return fd;
+ }
+ close(fd);
+ }
+ ERROR("Ran out of loopback devices");
+
+failed:
+ g_free(*loopback);
+ *loopback = NULL;
+ return -1;
+}
+
+int loop_detach(const gchar *loopback)
+{
+ int fd;
+
+ fd = open(loopback, O_RDONLY | O_NOFOLLOW);
+ if (fd < 0) {
+ PERROR("open(%s)", loopback);
+ return 0;
+ }
+ if (!is_loop_device(fd) || !loop_is_attached(fd))
+ goto failed;
+ if (ioctl(fd, LOOP_CLR_FD, 0)) {
+ PERROR("LOOP_CLR_FD");
+ goto failed;
+ }
+
+ close (fd);
+ return 1;
+
+failed:
+ close(fd);
+ return 0;
+}
+
+gchar *loop_attach(int fd, const char *name)
+{
+ gchar *loopback = NULL;
+ int loopfd;
+ struct loop_info64 info;
+
+ loopfd = loop_allocate(&loopback);
+ if (loopfd < 0)
+ return NULL;
+ if (ioctl(loopfd, LOOP_SET_FD, fd) < 0) {
+ PERROR("LOOP_SET_FD");
+ goto failed;
+ }
+
+ memset(&info, 0, sizeof(info));
+ strncpy((char*)info.lo_file_name, name, LO_NAME_SIZE);
+ if (ioctl(loopfd, LOOP_SET_STATUS64, &info)) {
+ PERROR("LOOP_SET_STATUS64");
+ goto failed;
+ }
+
+ close(loopfd);
+ close(fd);
+ return loopback;
+failed:
+ close(loopfd);
+ close(fd);
+ g_free(loopback);
+ return 0;
+}
+
+int dm_setup(size_t sectors, const gchar *encryption_key, const char *name,
+ const gchar *device, const char *path)
+{
+ /* Mount loopback device with dm-crypt using the encryption key. */
+ gchar *table = g_strdup_printf("0 %zu crypt " \
+ "aes-cbc-essiv:sha256 %s " \
+ "0 %s 0 " \
+ "1 allow_discards",
+ sectors,
+ encryption_key,
+ device);
+ if (!table) {
+ PERROR("g_strdup_printf");
+ return 0;
+ }
+
+ const gchar *argv[] = {
+ "/sbin/dmsetup",
+ "create", name,
+ "--noudevrules", "--noudevsync",
+ "--table", table,
+ NULL
+ };
+
+ /* TODO(keescook): replace with call to libdevmapper. */
+ if (runcmd(argv, NULL) != 0) {
+ g_free(table);
+ return 0;
+ }
+ g_free(table);
+
+ /* Make sure the dm-crypt device showed up. */
+ if (access(path, R_OK)) {
+ ERROR("%s does not exist", path);
+ return 0;
+ }
+
+ return 1;
+}
+
+void dm_teardown(const gchar *device)
+{
+ const char *argv[] = {
+ "/sbin/dmsetup",
+ "remove", device,
+ "--noudevrules", "--noudevsync",
+ NULL
+ };
+ /* TODO(keescook): replace with call to libdevmapper. */
+ runcmd(argv, NULL);
+}
+
+char *dm_get_key(const gchar *device)
+{
+ gchar *output = NULL;
+ char *key;
+ int i;
+ const char *argv[] = {
+ "/sbin/dmsetup",
+ "table", "--showkeys",
+ device,
+ NULL
+ };
+ /* TODO(keescook): replace with call to libdevmapper. */
+ if (runcmd(argv, &output) != 0)
+ return NULL;
+
+ /* Key is 4th field in the output. */
+ for (i = 0, key = strtok(output, " ");
+ i < 4 && key;
+ ++i, key = strtok(NULL, " ")) { }
+
+ /* Create a copy of the key and free the output buffer. */
+ if (key) {
+ key = strdup(key);
+ g_free(output);
+ }
+
+ return key;
+}
+
+int sparse_create(const char *path, size_t size)
+{
+ int sparsefd;
+
+ sparsefd = open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
+ S_IRUSR | S_IWUSR);
+ if (sparsefd < 0)
+ goto out;
+
+ if (ftruncate(sparsefd, size)) {
+ int saved_errno = errno;
+
+ close(sparsefd);
+ unlink(path);
+ errno = saved_errno;
+
+ sparsefd = -1;
+ }
+
+out:
+ return sparsefd;
+}
+
+int filesystem_build(const char *device, size_t block_bytes, size_t blocks_min,
+ size_t blocks_max)
+{
+ int rc = 0;
+
+ gchar *blocksize = g_strdup_printf("%zu", block_bytes);
+ if (!blocksize) {
+ PERROR("g_strdup_printf");
+ goto out;
+ }
+
+ gchar *blocks_str;
+ blocks_str = g_strdup_printf("%zu", blocks_min);
+ if (!blocks_str) {
+ PERROR("g_strdup_printf");
+ goto free_blocksize;
+ }
+
+ gchar *extended;
+ if (blocks_min < blocks_max) {
+ extended = g_strdup_printf("%s,resize=%zu",
+ kExt4ExtendedOptions, blocks_max);
+ } else {
+ extended = g_strdup_printf("%s", kExt4ExtendedOptions);
+ }
+ if (!extended) {
+ PERROR("g_strdup_printf");
+ goto free_blocks_str;
+ }
+
+ const gchar *mkfs[] = {
+ "/sbin/mkfs.ext4",
+ "-T", "default",
+ "-b", blocksize,
+ "-m", "0",
+ "-O", "^huge_file,^flex_bg",
+ "-E", extended,
+ device,
+ blocks_str,
+ NULL
+ };
+
+ rc = (runcmd(mkfs, NULL) == 0);
+ if (!rc)
+ goto free_extended;
+
+ const gchar *tune2fs[] = {
+ "/sbin/tune2fs",
+ "-c", "0",
+ "-i", "0",
+ device,
+ NULL
+ };
+ rc = (runcmd(tune2fs, NULL) == 0);
+
+free_extended:
+ g_free(extended);
+free_blocks_str:
+ g_free(blocks_str);
+free_blocksize:
+ g_free(blocksize);
+out:
+ return rc;
+}
+
+/* Spawns a filesystem resizing process. */
+void filesystem_resizer(const char *device, size_t blocks, size_t blocks_max)
+{
+ pid_t pid;
+
+ /* Ignore resizing if we know the filesystem was built to max size. */
+ if (blocks >= blocks_max)
+ return;
+
+ fflush(NULL);
+ pid = fork();
+ if (pid < 0) {
+ PERROR("fork");
+ return;
+ }
+ if (pid != 0) {
+ INFO("Started filesystem resizing process %d.", pid);
+ return;
+ }
+
+ if (setsid() < 0) {
+ PERROR("setsid");
+ goto out;
+ }
+
+ /* TODO(keescook): Read superblock to find out the current size of
+ * the filesystem (since statvfs does not report the correct value).
+ * For now, instead of doing multi-step resizing, just resize to the
+ * full size of the block device in one step.
+ */
+ blocks = blocks_max;
+
+ INFO_INIT("Resizing started in %d second steps.", kResizeStepSeconds);
+
+ do {
+ gchar *blocks_str;
+
+ sleep(kResizeStepSeconds);
+
+ blocks += kResizeBlocks;
+ if (blocks > blocks_max)
+ blocks = blocks_max;
+
+ blocks_str = g_strdup_printf("%zu", blocks);
+ if (!blocks_str) {
+ PERROR("g_strdup_printf");
+ goto out;
+ }
+
+ const gchar *resize[] = {
+ "/sbin/resize2fs",
+ "-f",
+ device,
+ blocks_str,
+ NULL
+ };
+
+ INFO("Resizing filesystem on %s to %zu.", device, blocks);
+ if (runcmd(resize, NULL)) {
+ ERROR("resize2fs failed");
+ goto out;
+ }
+ g_free(blocks_str);
+ } while (blocks < blocks_max);
+
+ INFO("Resizing finished.");
+out:
+ exit(0);
+}
+
+char *keyfile_read(const char *keyfile, uint8_t *system_key)
+{
+ char *key = NULL;
+ unsigned char *cipher = NULL;
+ gsize length;
+ uint8_t *plain = NULL;
+ int plain_length, final_len;
+ GError *error = NULL;
+ EVP_CIPHER_CTX ctx;
+ const EVP_CIPHER *algo = EVP_aes_256_cbc();
+
+ DEBUG("Reading keyfile %s", keyfile);
+ if (EVP_CIPHER_key_length(algo) != DIGEST_LENGTH) {
+ ERROR("cipher key size mismatch (got %d, want %d)",
+ EVP_CIPHER_key_length(algo), DIGEST_LENGTH);
+ goto out;
+ }
+
+ if (access(keyfile, R_OK)) {
+ /* This file being missing is handled in caller, so
+ * do not emit error message.
+ */
+ INFO("%s does not exist.", keyfile);
+ goto out;
+ }
+
+ if (!g_file_get_contents(keyfile, (gchar **)&cipher, &length,
+ &error)) {
+ ERROR("Unable to read %s: %s", keyfile, error->message);
+ g_error_free(error);
+ goto out;
+ }
+ plain = malloc(length);
+ if (!plain) {
+ PERROR("malloc");
+ goto free_cipher;
+ }
+
+ DEBUG("Decrypting keyfile %s", keyfile);
+ /* Use the default IV. */
+ if (!EVP_DecryptInit(&ctx, algo, system_key, NULL)) {
+ SSL_ERROR("EVP_DecryptInit");
+ goto free_plain;
+ }
+ /* TODO(keescook): this is a heap overflow -- file size not checked. */
+ if (!EVP_DecryptUpdate(&ctx, plain, &plain_length, cipher, length)) {
+ SSL_ERROR("EVP_DecryptUpdate");
+ goto free_ctx;
+ }
+ if (!EVP_DecryptFinal(&ctx, plain+plain_length, &final_len)) {
+ SSL_ERROR("EVP_DecryptFinal");
+ goto free_ctx;
+ }
+ plain_length += final_len;
+
+ if (plain_length != DIGEST_LENGTH) {
+ ERROR("Decrypted encryption key length (%d) is not %d.",
+ plain_length, DIGEST_LENGTH);
+ goto free_ctx;
+ }
+
+ debug_dump_hex("encryption key", plain, DIGEST_LENGTH);
+
+ key = stringify_hex(plain, DIGEST_LENGTH);
+
+free_ctx:
+ EVP_CIPHER_CTX_cleanup(&ctx);
+free_plain:
+ free(plain);
+free_cipher:
+ g_free(cipher);
+out:
+ DEBUG("key:%p", key);
+ return key;
+}
+
+int keyfile_write(const char *keyfile, uint8_t *system_key, char *string)
+{
+ int rc = 0;
+ size_t length;
+ uint8_t plain[DIGEST_LENGTH];
+ uint8_t *cipher = NULL;
+ int cipher_length, final_len;
+ GError *error = NULL;
+ EVP_CIPHER_CTX ctx;
+ const EVP_CIPHER *algo = EVP_aes_256_cbc();
+
+ DEBUG("Staring to process keyfile %s", keyfile);
+ if (EVP_CIPHER_key_length(algo) != DIGEST_LENGTH) {
+ ERROR("cipher key size mismatch (got %d, want %d)",
+ EVP_CIPHER_key_length(algo), DIGEST_LENGTH);
+ goto out;
+ }
+
+ if (access(keyfile, R_OK) == 0) {
+ ERROR("%s already exists.", keyfile);
+ goto out;
+ }
+
+ length = strlen(string);
+ if (length != sizeof(plain) * 2) {
+ ERROR("Encryption key string length (%zu) is not %zu.",
+ length, sizeof(plain) * 2);
+ goto out;
+ }
+
+ length = sizeof(plain);
+ if (!hexify_string(string, plain, length)) {
+ ERROR("Failed to convert encryption key to byte array");
+ goto out;
+ }
+
+ debug_dump_hex("encryption key", plain, DIGEST_LENGTH);
+
+ cipher = malloc(length + EVP_CIPHER_block_size(algo));
+ if (!cipher) {
+ PERROR("malloc");
+ goto out;
+ }
+
+ DEBUG("Encrypting keyfile %s", keyfile);
+ /* Use the default IV. */
+ if (!EVP_EncryptInit(&ctx, algo, system_key, NULL)) {
+ SSL_ERROR("EVP_EncryptInit");
+ goto free_cipher;
+ }
+ if (!EVP_EncryptUpdate(&ctx, cipher, &cipher_length,
+ (unsigned char *)plain, length)) {
+ SSL_ERROR("EVP_EncryptUpdate");
+ goto free_ctx;
+ }
+ if (!EVP_EncryptFinal(&ctx, cipher+cipher_length, &final_len)) {
+ SSL_ERROR("EVP_EncryptFinal");
+ goto free_ctx;
+ }
+ length = cipher_length + final_len;
+
+ DEBUG("Writing keyfile %s", keyfile);
+ if (!g_file_set_contents(keyfile, (gchar *)cipher, length, &error)) {
+ ERROR("Unable to write %s: %s", keyfile, error->message);
+ g_error_free(error);
+ goto free_ctx;
+ }
+
+ rc = 1;
+
+free_ctx:
+ EVP_CIPHER_CTX_cleanup(&ctx);
+free_cipher:
+ free(cipher);
+out:
+ DEBUG("rc:%d", rc);
+ return rc;
+}
diff --git a/utility/mount-helpers.h b/utility/mount-helpers.h
new file mode 100644
index 00000000..bc50fe54
--- /dev/null
+++ b/utility/mount-helpers.h
@@ -0,0 +1,40 @@
+/* Copyright (c) 2012 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.
+ *
+ * Header file for mount helpers.
+ */
+#ifndef _MOUNT_HELPERS_H_
+#define _MOUNT_HELPERS_H_
+
+/* General utility functions. */
+size_t get_sectors(const char *device);
+int remove_tree(const char *tree);
+int runcmd(const gchar *argv[], gchar **output);
+int same_vfs(const char *mnt_a, const char *mnt_b);
+char *stringify_hex(uint8_t *binary, size_t length);
+uint8_t *hexify_string(char *string, uint8_t *binary, size_t length);
+
+/* Loopback device attach/detach helpers. */
+gchar *loop_attach(int fd, const char *name);
+int loop_detach(const gchar *loopback);
+
+/* Encrypted device mapper setup/teardown. */
+int dm_setup(size_t sectors, const gchar *encryption_key, const char *name,
+ const gchar *device, const char *path);
+void dm_teardown(const gchar *device);
+char *dm_get_key(const gchar *device);
+
+/* Sparse file creation. */
+int sparse_create(const char *path, size_t size);
+
+/* Filesystem creation. */
+int filesystem_build(const char *device, size_t block_bytes, size_t blocks_min,
+ size_t blocks_max);
+void filesystem_resizer(const char *device, size_t blocks, size_t blocks_max);
+
+/* Encrypted keyfile handling. */
+char *keyfile_read(const char *keyfile, uint8_t *system_key);
+int keyfile_write(const char *keyfile, uint8_t *system_key, char *plain);
+
+#endif /* _MOUNT_HELPERS_H_ */