]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: support generating LUKS+Verity partitions
authorLennart Poettering <lennart@amutable.com>
Mon, 13 Jul 2026 13:40:56 +0000 (15:40 +0200)
committerLennart Poettering <lennart@amutable.com>
Thu, 16 Jul 2026 05:05:39 +0000 (07:05 +0200)
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=.

man/repart.d.xml
src/repart/repart.c

index 8de04357354d55f5e0f0d930f5abb8fa18bd0844..eaa83f617139b9d68e2edfd416a4b57faa056b60 100644 (file)
 
         <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>
 
 
         <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>)
index 002fb782be53f5eb1ec8219cf03550ba7c72785c..645d22ea6469d23d252960c153990dbe9679332f 100644 (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)