repart: Do offline encryption instead of online

Offline encryption can be done without mounting the luks device. For
now we still use loop devices to split out the partition we want to
write to but in a later commit we'll replace this with a regular file.

For offline encryption, we need to keep 2x the luks header size space
free at the end of the partition, so this means our encrypted partitions
will be 16M larger than before.
This commit is contained in:
Daan De Meyer 2022-10-09 20:46:59 +02:00
parent 98e0456ec0
commit 48a09a8fff
4 changed files with 171 additions and 150 deletions

View file

@ -1325,7 +1325,10 @@ if want_libcryptsetup != 'false' and not skip_deps
foreach ident : ['crypt_set_metadata_size',
'crypt_activate_by_signed_key',
'crypt_token_max']
'crypt_token_max',
'crypt_reencrypt_init_by_passphrase',
'crypt_reencrypt',
'crypt_set_data_offset']
have_ident = have and cc.has_function(
ident,
prefix : '#include <libcryptsetup.h>',

View file

@ -90,6 +90,9 @@
/* LUKS2 takes off 16M of the partition size with its metadata by default */
#define LUKS2_METADATA_SIZE (16ULL*1024ULL*1024ULL)
/* To do LUKS2 offline encryption, we need to keep some extra free space at the end of the partition. */
#define LUKS2_METADATA_KEEP_FREE (LUKS2_METADATA_SIZE*2ULL)
/* LUKS2 volume key size. */
#define VOLUME_KEY_SIZE (512ULL/8ULL)
@ -274,12 +277,7 @@ static const char *verity_mode_table[_VERITY_MODE_MAX] = {
[VERITY_SIG] = "signature",
};
#if HAVE_LIBCRYPTSETUP
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE);
#else
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE);
#endif
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(verity_mode, VerityMode);
static uint64_t round_down_size(uint64_t v, uint64_t p) {
@ -566,7 +564,7 @@ static uint64_t partition_min_size(const Context *context, const Partition *p) {
uint64_t d = 0;
if (p->encrypt != ENCRYPT_OFF)
d += round_up_size(LUKS2_METADATA_SIZE, context->grain_size);
d += round_up_size(LUKS2_METADATA_KEEP_FREE, context->grain_size);
if (p->copy_blocks_size != UINT64_MAX)
d += round_up_size(p->copy_blocks_size, context->grain_size);
@ -2985,16 +2983,27 @@ static int context_wipe_and_discard(Context *context, bool from_scratch) {
return 0;
}
static int partition_encrypt(
Context *context,
Partition *p,
const char *node,
struct crypt_device **ret_cd,
char **ret_volume,
int *ret_fd) {
#if HAVE_LIBCRYPTSETUP
static int partition_encrypt(Context *context, Partition *p, const char *node) {
#if HAVE_LIBCRYPTSETUP && HAVE_CRYPT_SET_DATA_OFFSET && HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE && HAVE_CRYPT_REENCRYPT
struct crypt_params_luks2 luks_params = {
.label = strempty(p->new_label),
.sector_size = context->sector_size,
.data_device = node,
};
struct crypt_params_reencrypt reencrypt_params = {
.mode = CRYPT_REENCRYPT_ENCRYPT,
.direction = CRYPT_REENCRYPT_BACKWARD,
.resilience = "datashift",
.data_shift = LUKS2_METADATA_SIZE / 512,
.luks2 = &luks_params,
.flags = CRYPT_REENCRYPT_INITIALIZE_ONLY|CRYPT_REENCRYPT_MOVE_FIRST_SEGMENT,
};
_cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
_cleanup_free_ char *dm_name = NULL, *vol = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_fclose_ FILE *h = NULL;
_cleanup_free_ char *hp = NULL;
const char *passphrase = NULL;
size_t passphrase_size = 0;
sd_id128_t uuid;
int r;
@ -3002,33 +3011,46 @@ static int partition_encrypt(
assert(p);
assert(p->encrypt != ENCRYPT_OFF);
log_debug("Encryption mode for partition %" PRIu64 ": %s", p->partno, encrypt_mode_to_string(p->encrypt));
r = dlopen_cryptsetup();
if (r < 0)
return log_error_errno(r, "libcryptsetup not found, cannot encrypt: %m");
if (asprintf(&dm_name, "luks-repart-%08" PRIx64, random_u64()) < 0)
return log_oom();
if (ret_volume) {
vol = path_join("/dev/mapper/", dm_name);
if (!vol)
return log_oom();
}
r = derive_uuid(p->new_uuid, "luks-uuid", &uuid);
if (r < 0)
return r;
log_info("Encrypting future partition %" PRIu64 "...", p->partno);
r = sym_crypt_init(&cd, node);
r = fopen_temporary(NULL, &h, &hp);
if (r < 0)
return log_error_errno(r, "Failed to allocate libcryptsetup context: %m");
return log_error_errno(r, "Failed to create temporary LUKS header file: %m");
/* Weird cryptsetup requirement which requires the header file to be the size of at least one sector. */
r = posix_fallocate(fileno(h), 0, context->sector_size);
if (r < 0)
return log_error_errno(r, "Failed to grow temporary LUKS header file: %m");
r = sym_crypt_init(&cd, hp);
if (r < 0)
return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", hp);
cryptsetup_enable_logging(cd);
/* Disable kernel keyring usage by libcryptsetup as a workaround for
* https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/273. This makes sure that we can do
* offline encryption even when repart is running in a container. */
r = sym_crypt_volume_key_keyring(cd, false);
if (r < 0)
return log_error_errno(r, "Failed to disable kernel keyring: %m");
r = sym_crypt_metadata_locking(cd, false);
if (r < 0)
return log_error_errno(r, "Failed to disable metadata locking: %m");
r = sym_crypt_set_data_offset(cd, LUKS2_METADATA_SIZE / 512);
if (r < 0)
return log_error_errno(r, "Failed to set data offset: %m");
r = sym_crypt_format(cd,
CRYPT_LUKS2,
"aes",
@ -3036,10 +3058,7 @@ static int partition_encrypt(
SD_ID128_TO_UUID_STRING(uuid),
NULL,
VOLUME_KEY_SIZE,
&(struct crypt_params_luks2) {
.label = strempty(p->new_label),
.sector_size = context->sector_size,
});
&luks_params);
if (r < 0)
return log_error_errno(r, "Failed to LUKS2 format future partition: %m");
@ -3053,11 +3072,13 @@ static int partition_encrypt(
arg_key_size);
if (r < 0)
return log_error_errno(r, "Failed to add LUKS2 key: %m");
passphrase = strempty(arg_key);
passphrase_size = arg_key_size;
}
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 *pubkey = NULL;
@ -3106,7 +3127,7 @@ static int partition_encrypt(
base64_encoded,
strlen(base64_encoded));
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
return log_error_errno(keyslot, "Failed to add new TPM2 key: %m");
r = tpm2_make_luks2_json(
keyslot,
@ -3125,63 +3146,67 @@ static int partition_encrypt(
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");
passphrase = base64_encoded;
passphrase_size = strlen(base64_encoded);
#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_reencrypt_init_by_passphrase(
cd,
dm_name,
NULL,
VOLUME_KEY_SIZE,
arg_discard ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0);
passphrase,
passphrase_size,
CRYPT_ANY_SLOT,
0,
sym_crypt_get_cipher(cd),
sym_crypt_get_cipher_mode(cd),
&reencrypt_params);
if (r < 0)
return log_error_errno(r, "Failed to activate LUKS superblock: %m");
return log_error_errno(r, "Failed to prepare for reencryption: %m");
/* crypt_reencrypt_init_by_passphrase() doesn't actually put the LUKS header at the front, we have
* to do that ourselves. */
sym_crypt_free(cd);
cd = NULL;
r = sym_crypt_init(&cd, node);
if (r < 0)
return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", node);
r = sym_crypt_header_restore(cd, CRYPT_LUKS2, hp);
if (r < 0)
return log_error_errno(r, "Failed to place new LUKS header at head of %s: %m", node);
reencrypt_params.flags &= ~CRYPT_REENCRYPT_INITIALIZE_ONLY;
r = sym_crypt_reencrypt_init_by_passphrase(
cd,
NULL,
passphrase,
passphrase_size,
CRYPT_ANY_SLOT,
0,
NULL,
NULL,
&reencrypt_params);
if (r < 0)
return log_error_errno(r, "Failed to load reencryption context: %m");
r = sym_crypt_reencrypt(cd, NULL);
if (r < 0)
return log_error_errno(r, "Failed to encrypt %s: %m", node);
log_info("Successfully encrypted future partition %" PRIu64 ".", p->partno);
if (ret_fd) {
_cleanup_close_ int dev_fd = -1;
dev_fd = open(vol, O_RDWR|O_CLOEXEC|O_NOCTTY);
if (dev_fd < 0)
return log_error_errno(errno, "Failed to open LUKS volume '%s': %m", vol);
*ret_fd = TAKE_FD(dev_fd);
}
if (ret_cd)
*ret_cd = TAKE_PTR(cd);
if (ret_volume)
*ret_volume = TAKE_PTR(vol);
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot encrypt: %m");
#endif
}
static int deactivate_luks(struct crypt_device *cd, const char *node) {
#if HAVE_LIBCRYPTSETUP
int r;
if (!cd)
return 0;
assert(node);
/* udev or so might access out block device in the background while we are done. Let's hence force
* detach the volume. We sync'ed before, hence this should be safe. */
r = sym_crypt_deactivate_by_name(cd, basename(node), CRYPT_DEACTIVATE_FORCE);
if (r < 0)
return log_error_errno(r, "Failed to deactivate LUKS device: %m");
return 1;
#else
return 0;
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"libcryptsetup is not supported or is missing required symbols, cannot encrypt: %m");
#endif
}
@ -3193,10 +3218,7 @@ static int context_copy_blocks(Context *context) {
/* Copy in file systems on the block level */
LIST_FOREACH(partitions, p, context->partitions) {
_cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
_cleanup_free_ char *encrypted = NULL;
_cleanup_close_ int encrypted_dev_fd = -1;
int target_fd;
if (p->copy_blocks_fd < 0)
@ -3213,7 +3235,7 @@ static int context_copy_blocks(Context *context) {
assert(p->new_size != UINT64_MAX);
assert(p->copy_blocks_size != UINT64_MAX);
assert(p->new_size >= p->copy_blocks_size);
assert(p->new_size >= p->copy_blocks_size + (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0));
if (whole_fd < 0)
assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
@ -3223,14 +3245,7 @@ static int context_copy_blocks(Context *context) {
if (r < 0)
return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno);
r = partition_encrypt(context, p, d->node, &cd, &encrypted, &encrypted_dev_fd);
if (r < 0)
return log_error_errno(r, "Failed to encrypt device: %m");
if (flock(encrypted_dev_fd, LOCK_EX) < 0)
return log_error_errno(errno, "Failed to lock LUKS device: %m");
target_fd = encrypted_dev_fd;
target_fd = d->fd;
} else {
if (lseek(whole_fd, p->offset, SEEK_SET) == (off_t) -1)
return log_error_errno(errno, "Failed to seek to partition offset: %m");
@ -3245,24 +3260,15 @@ static int context_copy_blocks(Context *context) {
if (r < 0)
return log_error_errno(r, "Failed to copy in data from '%s': %m", p->copy_blocks_path);
if (fsync(target_fd) < 0)
return log_error_errno(errno, "Failed to synchronize copied data blocks: %m");
if (p->encrypt != ENCRYPT_OFF) {
encrypted_dev_fd = safe_close(encrypted_dev_fd);
r = deactivate_luks(cd, encrypted);
r = partition_encrypt(context, p, d->node);
if (r < 0)
return r;
sym_crypt_free(cd);
cd = NULL;
r = loop_device_sync(d);
if (r < 0)
return log_error_errno(r, "Failed to sync loopback device: %m");
}
if (fsync(target_fd) < 0)
return log_error_errno(errno, "Failed to synchronize copied data blocks: %m");
log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path);
}
@ -3541,12 +3547,9 @@ static int context_mkfs(Context *context) {
return r;
LIST_FOREACH(partitions, p, context->partitions) {
_cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
_cleanup_(rm_rf_physical_and_freep) char *tmp_root = NULL;
_cleanup_free_ char *encrypted = NULL, *root = NULL;
_cleanup_close_ int encrypted_dev_fd = -1;
const char *fsdev;
_cleanup_free_ char *root = NULL;
if (p->dropped)
continue;
@ -3566,29 +3569,22 @@ static int context_mkfs(Context *context) {
assert(p->offset != UINT64_MAX);
assert(p->new_size != UINT64_MAX);
assert(p->new_size >= (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0));
if (fd < 0)
assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
/* Loopback block devices are not only useful to turn regular files into block devices, but
* also to cut out sections of block devices into new block devices. */
* also to cut out sections of block devices into new block devices. If we're doing
* encryption, we make sure we keep free space at the end which is required for cryptsetup's
* offline encryption. */
r = loop_device_make(fd, O_RDWR, p->offset, p->new_size, 0, 0, LOCK_EX, &d);
r = loop_device_make(fd, O_RDWR, p->offset,
p->new_size - (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0),
0, 0, LOCK_EX, &d);
if (r < 0)
return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno);
if (p->encrypt != ENCRYPT_OFF) {
r = partition_encrypt(context, p, d->node, &cd, &encrypted, &encrypted_dev_fd);
if (r < 0)
return log_error_errno(r, "Failed to encrypt device: %m");
if (flock(encrypted_dev_fd, LOCK_EX) < 0)
return log_error_errno(errno, "Failed to lock LUKS device: %m");
fsdev = encrypted;
} else
fsdev = d->node;
log_info("Formatting future partition %" PRIu64 ".", p->partno);
/* Ideally, we populate filesystems using our own code after creating the filesystem to
@ -3603,47 +3599,33 @@ static int context_mkfs(Context *context) {
return r;
}
r = make_filesystem(fsdev, p->format, strempty(p->new_label), root ?: tmp_root, p->fs_uuid, arg_discard);
if (r < 0) {
encrypted_dev_fd = safe_close(encrypted_dev_fd);
(void) deactivate_luks(cd, encrypted);
r = make_filesystem(d->node, p->format, strempty(p->new_label), root ?: tmp_root, p->fs_uuid, arg_discard);
if (r < 0)
return r;
}
log_info("Successfully formatted future partition %" PRIu64 ".", p->partno);
/* The file system is now created, no need to delay udev further */
if (p->encrypt != ENCRYPT_OFF)
if (flock(encrypted_dev_fd, LOCK_UN) < 0)
return log_error_errno(errno, "Failed to unlock LUKS device: %m");
/* Now, we can populate all the other filesystems that aren't read-only. */
if (!mkfs_supports_root_option(p->format)) {
r = partition_populate_filesystem(p, fsdev, denylist);
if (r < 0) {
encrypted_dev_fd = safe_close(encrypted_dev_fd);
(void) deactivate_luks(cd, encrypted);
r = partition_populate_filesystem(p, d->node, denylist);
if (r < 0)
return r;
}
}
if (p->encrypt != ENCRYPT_OFF) {
r = loop_device_refresh_size(d, UINT64_MAX, p->new_size);
if (r < 0)
return log_error_errno(r, "Failed to refresh loopback device size: %m");
r = partition_encrypt(context, p, d->node);
if (r < 0)
return log_error_errno(r, "Failed to encrypt device: %m");
}
/* Note that we always sync explicitly here, since mkfs.fat doesn't do that on its own, and
* if we don't sync before detaching a block device the in-flight sectors possibly won't hit
* the disk. */
if (p->encrypt != ENCRYPT_OFF) {
if (fsync(encrypted_dev_fd) < 0)
return log_error_errno(errno, "Failed to synchronize LUKS volume: %m");
encrypted_dev_fd = safe_close(encrypted_dev_fd);
r = deactivate_luks(cd, encrypted);
if (r < 0)
return r;
sym_crypt_free(cd);
cd = NULL;
}
r = loop_device_sync(d);
if (r < 0)
return log_error_errno(r, "Failed to sync loopback device: %m");

View file

@ -49,6 +49,18 @@ int (*sym_crypt_token_max)(const char *type);
#endif
crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type);
int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size);
#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params);
#endif
#if HAVE_CRYPT_REENCRYPT
int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr));
#endif
int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable);
#if HAVE_CRYPT_SET_DATA_OFFSET
int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset);
#endif
int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file);
int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable);
static void cryptsetup_log_glue(int level, const char *msg, void *usrptr) {
@ -234,7 +246,19 @@ int dlopen_cryptsetup(void) {
DLSYM_ARG(crypt_token_max),
#endif
DLSYM_ARG(crypt_token_status),
DLSYM_ARG(crypt_volume_key_get));
DLSYM_ARG(crypt_volume_key_get),
#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
DLSYM_ARG(crypt_reencrypt_init_by_passphrase),
#endif
#if HAVE_CRYPT_REENCRYPT
DLSYM_ARG(crypt_reencrypt),
#endif
DLSYM_ARG(crypt_metadata_locking),
#if HAVE_CRYPT_SET_DATA_OFFSET
DLSYM_ARG(crypt_set_data_offset),
#endif
DLSYM_ARG(crypt_header_restore),
DLSYM_ARG(crypt_volume_key_keyring));
if (r <= 0)
return r;

View file

@ -64,6 +64,18 @@ static inline int crypt_token_max(_unused_ const char *type) {
#endif
extern crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type);
extern int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size);
#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
extern int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params);
#endif
#if HAVE_CRYPT_REENCRYPT
extern int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr));
#endif
extern int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable);
#if HAVE_CRYPT_SET_DATA_OFFSET
extern int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset);
#endif
extern int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file);
extern int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL);