repart: optionally lock encrypted partitions to TPM2
This useful for bootstrapping encrypted systems: on first boot let's create a /var/ partition that is locked to the local TPM2.
This commit is contained in:
parent
5f0ab16198
commit
889914ef6c
|
@ -34,6 +34,7 @@
|
||||||
#include "format-util.h"
|
#include "format-util.h"
|
||||||
#include "fs-util.h"
|
#include "fs-util.h"
|
||||||
#include "gpt.h"
|
#include "gpt.h"
|
||||||
|
#include "hexdecoct.h"
|
||||||
#include "id128-util.h"
|
#include "id128-util.h"
|
||||||
#include "json.h"
|
#include "json.h"
|
||||||
#include "list.h"
|
#include "list.h"
|
||||||
|
@ -54,9 +55,11 @@
|
||||||
#include "specifier.h"
|
#include "specifier.h"
|
||||||
#include "stat-util.h"
|
#include "stat-util.h"
|
||||||
#include "stdio-util.h"
|
#include "stdio-util.h"
|
||||||
|
#include "string-table.h"
|
||||||
#include "string-util.h"
|
#include "string-util.h"
|
||||||
#include "strv.h"
|
#include "strv.h"
|
||||||
#include "terminal-util.h"
|
#include "terminal-util.h"
|
||||||
|
#include "tpm2-util.h"
|
||||||
#include "user-util.h"
|
#include "user-util.h"
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
|
|
||||||
|
@ -107,15 +110,27 @@ static bool arg_json = false;
|
||||||
static JsonFormatFlags arg_json_format_flags = 0;
|
static JsonFormatFlags arg_json_format_flags = 0;
|
||||||
static void *arg_key = NULL;
|
static void *arg_key = NULL;
|
||||||
static size_t arg_key_size = 0;
|
static size_t arg_key_size = 0;
|
||||||
|
static char *arg_tpm2_device = NULL;
|
||||||
|
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
|
||||||
|
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep);
|
||||||
|
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
|
||||||
|
|
||||||
typedef struct Partition Partition;
|
typedef struct Partition Partition;
|
||||||
typedef struct FreeArea FreeArea;
|
typedef struct FreeArea FreeArea;
|
||||||
typedef struct Context Context;
|
typedef struct Context Context;
|
||||||
|
|
||||||
|
typedef enum EncryptMode {
|
||||||
|
ENCRYPT_OFF,
|
||||||
|
ENCRYPT_KEY_FILE,
|
||||||
|
ENCRYPT_TPM2,
|
||||||
|
ENCRYPT_KEY_FILE_TPM2,
|
||||||
|
_ENCRYPT_MODE_MAX,
|
||||||
|
_ENCRYPT_MODE_INVALID = -1,
|
||||||
|
} EncryptMode;
|
||||||
|
|
||||||
struct Partition {
|
struct Partition {
|
||||||
char *definition_path;
|
char *definition_path;
|
||||||
|
|
||||||
|
@ -149,7 +164,7 @@ struct Partition {
|
||||||
|
|
||||||
char *format;
|
char *format;
|
||||||
char **copy_files;
|
char **copy_files;
|
||||||
bool encrypt;
|
EncryptMode encrypt;
|
||||||
|
|
||||||
LIST_FIELDS(Partition, partitions);
|
LIST_FIELDS(Partition, partitions);
|
||||||
};
|
};
|
||||||
|
@ -177,6 +192,15 @@ struct Context {
|
||||||
sd_id128_t seed;
|
sd_id128_t seed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const char *encrypt_mode_table[_ENCRYPT_MODE_MAX] = {
|
||||||
|
[ENCRYPT_OFF] = "off",
|
||||||
|
[ENCRYPT_KEY_FILE] = "key-file",
|
||||||
|
[ENCRYPT_TPM2] = "tpm2",
|
||||||
|
[ENCRYPT_KEY_FILE_TPM2] = "key-file+tpm2",
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE);
|
||||||
|
|
||||||
static uint64_t round_down_size(uint64_t v, uint64_t p) {
|
static uint64_t round_down_size(uint64_t v, uint64_t p) {
|
||||||
return (v / p) * p;
|
return (v / p) * p;
|
||||||
}
|
}
|
||||||
|
@ -388,12 +412,12 @@ static uint64_t partition_min_size(const Partition *p) {
|
||||||
if (!PARTITION_EXISTS(p)) {
|
if (!PARTITION_EXISTS(p)) {
|
||||||
uint64_t d = 0;
|
uint64_t d = 0;
|
||||||
|
|
||||||
if (p->encrypt)
|
if (p->encrypt != ENCRYPT_OFF)
|
||||||
d += round_up_size(LUKS2_METADATA_SIZE, 4096);
|
d += round_up_size(LUKS2_METADATA_SIZE, 4096);
|
||||||
|
|
||||||
if (p->copy_blocks_size != UINT64_MAX)
|
if (p->copy_blocks_size != UINT64_MAX)
|
||||||
d += round_up_size(p->copy_blocks_size, 4096);
|
d += round_up_size(p->copy_blocks_size, 4096);
|
||||||
else if (p->format || p->encrypt) {
|
else if (p->format || p->encrypt != ENCRYPT_OFF) {
|
||||||
uint64_t f;
|
uint64_t f;
|
||||||
|
|
||||||
/* If we shall synthesize a file system, take minimal fs size into account (assumed to be 4K if not known) */
|
/* If we shall synthesize a file system, take minimal fs size into account (assumed to be 4K if not known) */
|
||||||
|
@ -1137,6 +1161,8 @@ static int config_parse_copy_files(
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_encrypt, encrypt_mode, EncryptMode, ENCRYPT_OFF, "Invalid encryption mode");
|
||||||
|
|
||||||
static int partition_read_definition(Partition *p, const char *path) {
|
static int partition_read_definition(Partition *p, const char *path) {
|
||||||
|
|
||||||
ConfigTableItem table[] = {
|
ConfigTableItem table[] = {
|
||||||
|
@ -1154,7 +1180,7 @@ static int partition_read_definition(Partition *p, const char *path) {
|
||||||
{ "Partition", "CopyBlocks", config_parse_path, 0, &p->copy_blocks_path },
|
{ "Partition", "CopyBlocks", config_parse_path, 0, &p->copy_blocks_path },
|
||||||
{ "Partition", "Format", config_parse_fstype, 0, &p->format },
|
{ "Partition", "Format", config_parse_fstype, 0, &p->format },
|
||||||
{ "Partition", "CopyFiles", config_parse_copy_files, 0, p },
|
{ "Partition", "CopyFiles", config_parse_copy_files, 0, p },
|
||||||
{ "Partition", "Encrypt", config_parse_bool, 0, &p->encrypt },
|
{ "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt },
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
int r;
|
int r;
|
||||||
|
@ -1188,7 +1214,7 @@ static int partition_read_definition(Partition *p, const char *path) {
|
||||||
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
|
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
|
||||||
"Format=swap and CopyFiles= cannot be combined, refusing.");
|
"Format=swap and CopyFiles= cannot be combined, refusing.");
|
||||||
|
|
||||||
if (!p->format && (!strv_isempty(p->copy_files) || (p->encrypt && !p->copy_blocks_path))) {
|
if (!p->format && (!strv_isempty(p->copy_files) || (p->encrypt != ENCRYPT_OFF && !p->copy_blocks_path))) {
|
||||||
/* Pick "ext4" as file system if we are configured to copy files or encrypt the device */
|
/* Pick "ext4" as file system if we are configured to copy files or encrypt the device */
|
||||||
p->format = strdup("ext4");
|
p->format = strdup("ext4");
|
||||||
if (!p->format)
|
if (!p->format)
|
||||||
|
@ -2376,7 +2402,9 @@ static int partition_encrypt(
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(p);
|
assert(p);
|
||||||
assert(p->encrypt);
|
assert(p->encrypt != ENCRYPT_OFF);
|
||||||
|
|
||||||
|
log_debug("Encryption mode for partition %" PRIu64 ": %s", p->partno, encrypt_mode_to_string(p->encrypt));
|
||||||
|
|
||||||
r = dlopen_cryptsetup();
|
r = dlopen_cryptsetup();
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
|
@ -2425,15 +2453,61 @@ static int partition_encrypt(
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to LUKS2 format future partition: %m");
|
return log_error_errno(r, "Failed to LUKS2 format future partition: %m");
|
||||||
|
|
||||||
r = sym_crypt_keyslot_add_by_volume_key(
|
if (IN_SET(p->encrypt, ENCRYPT_KEY_FILE, ENCRYPT_KEY_FILE_TPM2)) {
|
||||||
cd,
|
r = sym_crypt_keyslot_add_by_volume_key(
|
||||||
CRYPT_ANY_SLOT,
|
cd,
|
||||||
volume_key,
|
CRYPT_ANY_SLOT,
|
||||||
volume_key_size,
|
volume_key,
|
||||||
strempty(arg_key),
|
volume_key_size,
|
||||||
arg_key_size);
|
strempty(arg_key),
|
||||||
if (r < 0)
|
arg_key_size);
|
||||||
return log_error_errno(r, "Failed to add LUKS2 key: %m");
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to add LUKS2 key: %m");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) {
|
||||||
|
#if HAVE_TPM2
|
||||||
|
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
|
||||||
|
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||||
|
_cleanup_(erase_and_freep) void *secret = NULL;
|
||||||
|
_cleanup_free_ void *blob = NULL, *hash = NULL;
|
||||||
|
size_t secret_size, blob_size, hash_size;
|
||||||
|
int keyslot;
|
||||||
|
|
||||||
|
r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to seal to TPM2: %m");
|
||||||
|
|
||||||
|
r = base64mem(secret, secret_size, &base64_encoded);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to base64 encode secret key: %m");
|
||||||
|
|
||||||
|
r = cryptsetup_set_minimal_pbkdf(cd);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to set minimal PBKDF: %m");
|
||||||
|
|
||||||
|
keyslot = sym_crypt_keyslot_add_by_volume_key(
|
||||||
|
cd,
|
||||||
|
CRYPT_ANY_SLOT,
|
||||||
|
volume_key,
|
||||||
|
volume_key_size,
|
||||||
|
base64_encoded,
|
||||||
|
strlen(base64_encoded));
|
||||||
|
if (keyslot < 0)
|
||||||
|
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
|
||||||
|
|
||||||
|
r = tpm2_make_luks2_json(keyslot, arg_tpm2_pcr_mask, blob, blob_size, hash, hash_size, &v);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");
|
||||||
|
|
||||||
|
r = cryptsetup_add_token_json(cd, v);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to add TPM2 JSON token to LUKS2 header: %m");
|
||||||
|
#else
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||||
|
"Support for TPM2 enrollment not enabled.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
r = sym_crypt_activate_by_volume_key(
|
r = sym_crypt_activate_by_volume_key(
|
||||||
cd,
|
cd,
|
||||||
|
@ -2521,7 +2595,7 @@ static int context_copy_blocks(Context *context) {
|
||||||
if (whole_fd < 0)
|
if (whole_fd < 0)
|
||||||
assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
|
assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
|
||||||
|
|
||||||
if (p->encrypt) {
|
if (p->encrypt != ENCRYPT_OFF) {
|
||||||
r = loop_device_make(whole_fd, O_RDWR, p->offset, p->new_size, 0, &d);
|
r = loop_device_make(whole_fd, O_RDWR, p->offset, p->new_size, 0, &d);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno);
|
return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno);
|
||||||
|
@ -2554,7 +2628,7 @@ static int context_copy_blocks(Context *context) {
|
||||||
if (fsync(target_fd) < 0)
|
if (fsync(target_fd) < 0)
|
||||||
return log_error_errno(r, "Failed to synchronize copied data blocks: %m");
|
return log_error_errno(r, "Failed to synchronize copied data blocks: %m");
|
||||||
|
|
||||||
if (p->encrypt) {
|
if (p->encrypt != ENCRYPT_OFF) {
|
||||||
encrypted_dev_fd = safe_close(encrypted_dev_fd);
|
encrypted_dev_fd = safe_close(encrypted_dev_fd);
|
||||||
|
|
||||||
r = deactivate_luks(cd, encrypted);
|
r = deactivate_luks(cd, encrypted);
|
||||||
|
@ -2743,7 +2817,7 @@ static int context_mkfs(Context *context) {
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to lock loopback device: %m");
|
return log_error_errno(r, "Failed to lock loopback device: %m");
|
||||||
|
|
||||||
if (p->encrypt) {
|
if (p->encrypt != ENCRYPT_OFF) {
|
||||||
r = partition_encrypt(p, d->node, &cd, &encrypted, &encrypted_dev_fd);
|
r = partition_encrypt(p, d->node, &cd, &encrypted, &encrypted_dev_fd);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to encrypt device: %m");
|
return log_error_errno(r, "Failed to encrypt device: %m");
|
||||||
|
@ -2773,7 +2847,7 @@ static int context_mkfs(Context *context) {
|
||||||
log_info("Successfully formatted future partition %" PRIu64 ".", p->partno);
|
log_info("Successfully formatted future partition %" PRIu64 ".", p->partno);
|
||||||
|
|
||||||
/* The file system is now created, no need to delay udev further */
|
/* The file system is now created, no need to delay udev further */
|
||||||
if (p->encrypt)
|
if (p->encrypt != ENCRYPT_OFF)
|
||||||
if (flock(encrypted_dev_fd, LOCK_UN) < 0)
|
if (flock(encrypted_dev_fd, LOCK_UN) < 0)
|
||||||
return log_error_errno(errno, "Failed to unlock LUKS device: %m");
|
return log_error_errno(errno, "Failed to unlock LUKS device: %m");
|
||||||
|
|
||||||
|
@ -2788,7 +2862,7 @@ static int context_mkfs(Context *context) {
|
||||||
* if we don't sync before detaching a block device the in-flight sectors possibly won't hit
|
* if we don't sync before detaching a block device the in-flight sectors possibly won't hit
|
||||||
* the disk. */
|
* the disk. */
|
||||||
|
|
||||||
if (p->encrypt) {
|
if (p->encrypt != ENCRYPT_OFF) {
|
||||||
if (fsync(encrypted_dev_fd) < 0)
|
if (fsync(encrypted_dev_fd) < 0)
|
||||||
return log_error_errno(r, "Failed to synchronize LUKS volume: %m");
|
return log_error_errno(r, "Failed to synchronize LUKS volume: %m");
|
||||||
encrypted_dev_fd = safe_close(encrypted_dev_fd);
|
encrypted_dev_fd = safe_close(encrypted_dev_fd);
|
||||||
|
@ -3420,6 +3494,9 @@ static int help(void) {
|
||||||
" --root=PATH Operate relative to root path\n"
|
" --root=PATH Operate relative to root path\n"
|
||||||
" --definitions=DIR Find partitions in specified directory\n"
|
" --definitions=DIR Find partitions in specified directory\n"
|
||||||
" --key-file=PATH Key to use when encrypting partitions\n"
|
" --key-file=PATH Key to use when encrypting partitions\n"
|
||||||
|
" --tpm2-device=PATH Path to TPM2 device node to use\n"
|
||||||
|
" --tpm2-pcrs=PCR1,PCR2,…\n"
|
||||||
|
" TPM2 PCR indexes to use for TPM2 enrollment\n"
|
||||||
" --seed=UUID 128bit seed UUID to derive all UUIDs from\n"
|
" --seed=UUID 128bit seed UUID to derive all UUIDs from\n"
|
||||||
" --size=BYTES Grow loopback file to specified size\n"
|
" --size=BYTES Grow loopback file to specified size\n"
|
||||||
" --json=pretty|short|off\n"
|
" --json=pretty|short|off\n"
|
||||||
|
@ -3449,6 +3526,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
ARG_SIZE,
|
ARG_SIZE,
|
||||||
ARG_JSON,
|
ARG_JSON,
|
||||||
ARG_KEY_FILE,
|
ARG_KEY_FILE,
|
||||||
|
ARG_TPM2_DEVICE,
|
||||||
|
ARG_TPM2_PCRS,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct option options[] = {
|
static const struct option options[] = {
|
||||||
|
@ -3466,6 +3545,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
{ "size", required_argument, NULL, ARG_SIZE },
|
{ "size", required_argument, NULL, ARG_SIZE },
|
||||||
{ "json", required_argument, NULL, ARG_JSON },
|
{ "json", required_argument, NULL, ARG_JSON },
|
||||||
{ "key-file", required_argument, NULL, ARG_KEY_FILE },
|
{ "key-file", required_argument, NULL, ARG_KEY_FILE },
|
||||||
|
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
|
||||||
|
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3635,6 +3716,43 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ARG_TPM2_DEVICE: {
|
||||||
|
_cleanup_free_ char *device = NULL;
|
||||||
|
|
||||||
|
if (streq(optarg, "list"))
|
||||||
|
return tpm2_list_devices();
|
||||||
|
|
||||||
|
if (!streq(optarg, "auto")) {
|
||||||
|
device = strdup(optarg);
|
||||||
|
if (!device)
|
||||||
|
return log_oom();
|
||||||
|
}
|
||||||
|
|
||||||
|
free(arg_tpm2_device);
|
||||||
|
arg_tpm2_device = TAKE_PTR(device);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ARG_TPM2_PCRS: {
|
||||||
|
uint32_t mask;
|
||||||
|
|
||||||
|
if (isempty(optarg)) {
|
||||||
|
arg_tpm2_pcr_mask = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = tpm2_parse_pcrs(optarg, &mask);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (arg_tpm2_pcr_mask == UINT32_MAX)
|
||||||
|
arg_tpm2_pcr_mask = mask;
|
||||||
|
else
|
||||||
|
arg_tpm2_pcr_mask |= mask;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
@ -3667,6 +3785,9 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||||
"A path to a device node or loopback file must be specified when --empty=force, --empty=require or --empty=create are used.");
|
"A path to a device node or loopback file must be specified when --empty=force, --empty=require or --empty=create are used.");
|
||||||
|
|
||||||
|
if (arg_tpm2_pcr_mask == UINT32_MAX)
|
||||||
|
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue