From: Lennart Poettering Date: Mon, 13 Jul 2026 13:40:56 +0000 (+0200) Subject: repart: support generating LUKS+Verity partitions X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=02cd3fd879531052dbd5a2bd23293cf3c01ad8c2;p=thirdparty%2Fsystemd.git repart: support generating LUKS+Verity partitions For various cases it is interesting to both sign and encrypted a file system, for example to prepare it on one host and provide it to another. Let's explicitly support preparing this in systemd-repart via setting both Verity= and Encrypt=. --- diff --git a/man/repart.d.xml b/man/repart.d.xml index 8de04357354..eaa83f61713 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -709,6 +709,10 @@ This option has no effect if the partition already exists. + This option may be combined with Verity=data in order to place the + encrypted partition inside a dm-verity protection envelope, see the description of + Verity= below. + @@ -773,7 +777,19 @@ This option has no effect if the partition already exists. - Usage of this option in combination with Encrypt= is not supported. + data may be combined with Encrypt=, in which case the + partition is first encrypted with LUKS2 and the verity hash data is then generated from the + resulting ciphertext, i.e. Verity is the outer protection envelope and LUKS2 the inner. Tools such + as systemd-dissect1 + will then set up the dm-verity device first, and open the LUKS2 volume on top of it, so that all + encrypted data read is also authenticated via the verity root hash. Note that in this case the + verity hashes necessarily cover freshly generated ciphertext, hence the root hash (and thus the + default partition UUIDs derived from it) will change on every build, even if the plaintext contents + are fully reproducible. Encrypt= may not be combined with + Verity=hash or Verity=signature. Since the ciphertext is only + generated while the final image is built, Minimize= cannot be used for the hash + partition of an encrypted data partition; use SizeMaxBytes= on the data partition + to size the hash partition instead. For each unique VerityMatchKey= value, a single verity data partition (Verity=data) and a single verity hash partition (Verity=hash) diff --git a/src/repart/repart.c b/src/repart/repart.c index 002fb782be5..645d22ea646 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -3138,9 +3138,9 @@ static int partition_read_definition( verity_mode_to_string(p->verity)); } - if (p->verity != VERITY_OFF && p->encrypt != ENCRYPT_OFF) + if (IN_SET(p->verity, VERITY_HASH, VERITY_SIG) && p->encrypt != ENCRYPT_OFF) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Encrypting verity hash/data partitions is not supported."); + "Encrypting verity hash/signature partitions is not supported."); if (p->verity == VERITY_SIG && (p->size_min != UINT64_MAX || p->size_max != UINT64_MAX)) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), @@ -3667,6 +3667,13 @@ static int context_read_definitions(Context *context) { if (dp->minimize == MINIMIZE_OFF && !(dp->copy_blocks_path || dp->copy_blocks_auto)) return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), "Minimize= set for verity hash partition but data partition does not set CopyBlocks= or Minimize=."); + + /* The verity hash of an encrypted data partition covers the ciphertext, which is generated + * with a fresh volume key only when the final image is built, hence it cannot be + * precalculated for minimizing purposes. */ + if (dp->encrypt != ENCRYPT_OFF) + return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), + "Minimize= cannot be set for verity hash partitions whose data partition is encrypted, use SizeMaxBytes= on the data partition instead."); } LIST_FOREACH(partitions, p, context->partitions) { @@ -5323,6 +5330,15 @@ static int partition_target_prepare( return 0; } +static void partition_target_drop_decrypted(PartitionTarget *t) { + assert(t); + + /* Deactivate the dm-crypt device again, so that subsequent access to the target reaches the + * encrypted data as it is stored on disk (i.e. the ciphertext). Requires the target to have been + * sync'ed first. */ + t->decrypted = decrypted_partition_target_free(t->decrypted); +} + static int partition_target_grow(PartitionTarget *t, uint64_t size) { int r; @@ -6411,6 +6427,11 @@ static int context_copy_blocks(Context *context) { p->partno, FORMAT_TIMESPAN(time_spent, 0)); if (p->siblings[VERITY_HASH] && !partition_defer(context, p->siblings[VERITY_HASH])) { + /* The verity hash must cover the partition contents as stored on disk, i.e. the + * ciphertext if the partition is encrypted, hence tear down the dm-crypt device + * first. */ + partition_target_drop_decrypted(t); + r = partition_format_verity_hash(context, p->siblings[VERITY_HASH], /* node= */ NULL, partition_target_path(t)); if (r < 0) @@ -7600,6 +7621,11 @@ static int context_mkfs(Context *context) { return r; if (p->siblings[VERITY_HASH] && !partition_defer(context, p->siblings[VERITY_HASH])) { + /* The verity hash must cover the partition contents as stored on disk, i.e. the + * ciphertext if the partition is encrypted, hence tear down the dm-crypt device + * first. */ + partition_target_drop_decrypted(t); + r = partition_format_verity_hash(context, p->siblings[VERITY_HASH], /* node= */ NULL, partition_target_path(t)); if (r < 0)