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=.
This commit is contained in:
Lennart Poettering
2026-07-16 07:05:39 +02:00
parent 07a9d1f929
commit 02cd3fd879
2 changed files with 45 additions and 3 deletions
+17 -1
View File
@@ -709,6 +709,10 @@
<para>This option has no effect if the partition already exists.</para>
<para>This option may be combined with <varname>Verity=data</varname> in order to place the
encrypted partition inside a dm-verity protection envelope, see the description of
<varname>Verity=</varname> below.</para>
<xi:include href="version-info.xml" xpointer="v247"/></listitem>
</varlistentry>
@@ -773,7 +777,19 @@
<para>This option has no effect if the partition already exists.</para>
<para>Usage of this option in combination with <varname>Encrypt=</varname> is not supported.</para>
<para><literal>data</literal> may be combined with <varname>Encrypt=</varname>, 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 <citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry>
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. <varname>Encrypt=</varname> may not be combined with
<literal>Verity=hash</literal> or <literal>Verity=signature</literal>. Since the ciphertext is only
generated while the final image is built, <varname>Minimize=</varname> cannot be used for the hash
partition of an encrypted data partition; use <varname>SizeMaxBytes=</varname> on the data partition
to size the hash partition instead.</para>
<para>For each unique <varname>VerityMatchKey=</varname> value, a single verity data partition
(<literal>Verity=data</literal>) and a single verity hash partition (<literal>Verity=hash</literal>)
+28 -2
View File
@@ -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)