diff --git a/rear-support-multi-keyslot-luks-RHEL-83776.patch b/rear-support-multi-keyslot-luks-RHEL-83776.patch new file mode 100644 index 0000000..d35c56b --- /dev/null +++ b/rear-support-multi-keyslot-luks-RHEL-83776.patch @@ -0,0 +1,165 @@ +From e9ce93f096e505968cc728a7eb5a06e25dc8d88b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Renaud=20M=C3=A9trich?= + <1163635+rmetrich@users.noreply.github.com> +Date: Fri, 27 Jun 2025 14:39:01 +0200 +Subject: [PATCH] LUKS2: try to handle multiple key slots (#3430) + +LUKS2: try to handle multiple key slots: +The current code doesn't deal properly with multiple key slots nor +extracts the Hash that was used at LUKS creation properly. +With current code, when multiple key slots are found or PBKDF algorithm +is "pbkdf2" (instead of default "argon2id"), the disk layout file +contains 2 lines for 'crypt' parameters, causing havoc during recovery. +This new code tries to do better: +It searches for the Hash in Keyslots section only +(and falls back to Digests section if not found). +It warns the admin if multiple keyslots are in use +(e.g. because of multiple passphrases or Clevis binding). +It handles the PBKDF algorithm. +It makes sure that the 'crypt' parameters are always on one line. +--- + doc/user-guide/06-layout-configuration.adoc | 2 +- + .../GNU/Linux/160_include_luks_code.sh | 3 + + .../layout/save/GNU/Linux/260_crypt_layout.sh | 69 ++++++++++++++++--- + 3 files changed, 62 insertions(+), 12 deletions(-) + +diff --git a/doc/user-guide/06-layout-configuration.adoc b/doc/user-guide/06-layout-configuration.adoc +index d2a0a4c8b9..f1f4a47326 100644 +--- a/doc/user-guide/06-layout-configuration.adoc ++++ b/doc/user-guide/06-layout-configuration.adoc +@@ -666,7 +666,7 @@ lvmvol [key:value ...] + + === LUKS Devices === + ---------------------------------- +-crypt /dev/mapper/ [type=] [cipher=] [key_size=] [hash=] [uuid=] [keyfile=] [password=] ++crypt /dev/mapper/ [type=] [cipher=] [key_size=] [hash=] [uuid=] [pbkdf= ] [keyfile=] [password=] + ---------------------------------- + + === DRBD === +diff --git a/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh b/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh +index 0c662f6770..3b0b34cd12 100644 +--- a/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh ++++ b/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh +@@ -62,6 +62,9 @@ create_crypt() { + (uuid) + test $value && cryptsetup_options+=" --uuid $value" + ;; ++ (pbkdf) ++ test $value && cryptsetup_options+=" --pbkdf $value" ++ ;; + (keyfile) + test $value && keyfile=$value + ;; +diff --git a/usr/share/rear/layout/save/GNU/Linux/260_crypt_layout.sh b/usr/share/rear/layout/save/GNU/Linux/260_crypt_layout.sh +index 7410165a5e..c3470f3244 100644 +--- a/usr/share/rear/layout/save/GNU/Linux/260_crypt_layout.sh ++++ b/usr/share/rear/layout/save/GNU/Linux/260_crypt_layout.sh +@@ -61,31 +61,73 @@ while read target_name junk ; do + fi + luks_type=luks$version + ++ luksDump_cmd="cryptsetup luksDump $source_device" ++ + # Gather crypt information: +- if ! cryptsetup luksDump $source_device >$TMP_DIR/cryptsetup.luksDump ; then +- LogPrintError "Error: Cannot get LUKS$version values for $target_name ('cryptsetup luksDump $source_device' failed)" ++ if ! $luksDump_cmd >$TMP_DIR/cryptsetup.luksDump ; then ++ LogPrintError "Error: Cannot get LUKS$version values for $target_name ('$luksDump_cmd' failed)" + continue + fi ++ + uuid=$( grep "UUID" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+)$/\1/' ) + keyfile_option=$( [ -f /etc/crypttab ] && awk '$1 == "'"$target_name"'" && $3 != "none" && $3 != "-" && $3 != "" { print "keyfile=" $3; }' /etc/crypttab ) ++ pbkdf_option="" ++ + if test $luks_type = "luks1" ; then ++ + cipher_name=$( grep "Cipher name" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+)$/\1/' ) + cipher_mode=$( grep "Cipher mode" $TMP_DIR/cryptsetup.luksDump | cut -d: -f2- | awk '{printf("%s",$1)};' ) + cipher=$cipher_name-$cipher_mode + key_size=$( grep "MK bits" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+)$/\1/' ) + hash=$( grep "Hash spec" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+)$/\1/' ) ++ + elif test $luks_type = "luks2" ; then +- cipher=$( grep "cipher:" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+)$/\1/' ) +- # More than one keyslot may be defined - use key_size from the first slot. ++ ++ keyslots_section=$( awk '/^Keyslots:/ {include=1;next} /^[[:upper:]]/ && include {exit} include' $TMP_DIR/cryptsetup.luksDump ) ++ if [ -z "$keyslots_section" ]; then ++ LogPrintError "Error: No Keyslots section found in '$luksDump_cmd' output" ++ continue ++ fi ++ if [ $( grep -c -P '^\s+\d+: luks2' <<< "$keyslots_section" ) -gt 1 ]; then ++ LogPrintError "Warning: More than one luks2 keyslot found in '$luksDump_cmd' output, will only consider the first keyslot during recovery" ++ fi ++ luks2_section=$( awk '/^[[:blank:]]+[[:digit:]]+:/ && include {exit} /^[[:blank:]]+[[:digit:]]+: luks2/{include=1} include' <<< "$keyslots_section") ++ ++ cipher=$( grep "Cipher:" <<< "$luks2_section" | sed -r 's/^.+:\s*(.+)$/\1/' ) ++ pbkdf=$( grep "PBKDF:" <<< "$luks2_section" | sed -r 's/^.+:\s*(.+)$/\1/' ) ++ if [ -z "$pbkdf" ]; then ++ LogPrintError "Warning: no PBKDF found in luks2 keyslot of '$luksDump_cmd' output, will use defaults during recovery" ++ else ++ pbkdf_option="pbkdf=$pbkdf" ++ fi ++ + # Depending on the version the "cryptsetup luksDump" command outputs the key_size value + # as a line like + # Key: 512 bits + # and/or as a line like + # Cipher key: 512 bits + # cf. https://github.com/rear/rear/pull/2504#issuecomment-718729198 and subsequent comments +- # so we grep for both lines but use only the first match from the first slot: +- key_size=$( grep -E -m 1 "Key:|Cipher key:" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+) bits$/\1/' ) +- hash=$( grep "Hash" $TMP_DIR/cryptsetup.luksDump | sed -r 's/^.+:\s*(.+)$/\1/' ) ++ key_size=$( grep -E "Key:|Cipher key:" <<< "$luks2_section" | sed -r 's/^.+:\s*(.+) bits$/\1/' | sort -u ) ++ if [ -z "$key_size" ]; then ++ LogPrintError "Error: No key size found in luks2 keyslot of '$luksDump_cmd' output" ++ elif [ $( wc -w <<< "$key_size" ) -gt 1 ]; then ++ LogPrintError "Error: Too many key sizes found in luks2 keyslot of '$luksDump_cmd' output" ++ key_size="" ++ fi ++ ++ hash=$( grep "AF hash:" <<< "$luks2_section" | sed -r 's/^.+:\s*(.+)$/\1/' ) ++ if [ -z "$hash" ]; then ++ # Fallback to using Hash field found in Digests section ++ digests_section=$( awk '/^Digests:/ {include=1;next} /^[[:upper:]]/ && include {exit} include' $TMP_DIR/cryptsetup.luksDump ) ++ hash=$( grep "Hash:" <<< "$digests_section" | sed -r 's/^.+:\s*(.+)$/\1/' | sort -u ) ++ if [ -z "$hash" ]; then ++ LogPrintError "Warning: No Hash found in Digests section of '$luksDump_cmd' output, will use default type during recovery" ++ elif [ $( wc -w <<< "$hash" ) -gt 1 ]; then ++ hash=$( head -1 <<< "$hash" ) ++ LogPrintError "Warning: Too many Hash found in Digests section of '$luksDump_cmd' output, will use '$hash' during recovery" ++ fi ++ fi ++ + fi + + # Basic checks that the cipher key_size hash uuid values exist +@@ -95,9 +137,9 @@ while read target_name junk ; do + # and it seems cryptsetup fails when options with empty values are specified + # cf. https://github.com/rear/rear/pull/2504#issuecomment-719479724 + # For example a LUKS1 crypt entry in disklayout.conf looks like +- # crypt /dev/mapper/luks1test /dev/sda7 type=luks1 cipher=aes-xts-plain64 key_size=256 hash=sha256 uuid=1b4198c9-d9b0-4c57-b9a3-3433e391e706 +- # and a LUKS1 crypt entry in disklayout.conf looks like +- # crypt /dev/mapper/luks2test /dev/sda8 type=luks2 cipher=aes-xts-plain64 key_size=256 hash=sha256 uuid=3e874a28-7415-4f8c-9757-b3f28a96c4d2 ++ # crypt /dev/mapper/luks1test /dev/sda7 type=luks1 cipher=aes-xts-plain64 key_size=256 hash=sha256 uuid=1b4198c9-d9b0-4c57-b9a3-3433e391e706 ++ # and a LUKS2 crypt entry in disklayout.conf looks like ++ # crypt /dev/mapper/luks2test /dev/sda8 type=luks2 cipher=aes-xts-plain64 key_size=256 hash=sha256 uuid=3e874a28-7415-4f8c-9757-b3f28a96c4d2 pbkdf=argon2id + # Only the keyfile_option value is optional and the luks_type value is already tested above. + # Using plain test to ensure a value is a single non empty and non blank word + # without quoting because test " " would return zero exit code +@@ -127,7 +169,12 @@ while read target_name junk ; do + LogPrintError "Error: No 'uuid' value for LUKS$version volume $target_name in $source_device (mounting it or booting the recreated system may fail)" + fi + +- echo "crypt /dev/mapper/$target_name $source_device type=$luks_type cipher=$cipher key_size=$key_size hash=$hash uuid=$uuid $keyfile_option" >> $DISKLAYOUT_FILE ++ { ++ echo -n "crypt /dev/mapper/$target_name $source_device type=$luks_type cipher=$cipher key_size=$key_size hash=$hash uuid=$uuid" ++ [ -n "$keyfile_option" ] && echo -n " $keyfile_option" ++ [ -n "$pbkdf_option" ] && echo -n " $pbkdf_option" ++ echo ++ } >> $DISKLAYOUT_FILE + + done < <( dmsetup ls --target crypt ) + + diff --git a/rear.spec b/rear.spec index ba1a0ac..bab2c01 100644 --- a/rear.spec +++ b/rear.spec @@ -34,6 +34,10 @@ Patch122: rear-skip-longhorn-iscsi-RHEL-83551.patch # https://github.com/rear/rear/commit/1ca518c2a0e675ace956ef71bc79d67e4990562b Patch123: rear-detect-prep-boot-on-gpt-RHEL-82098.patch +# fix recovery of LUKS encrypted systems with multiple keyslots +# https://github.com/rear/rear/commit/e9ce93f096e505968cc728a7eb5a06e25dc8d88b +Patch124: rear-support-multi-keyslot-luks-RHEL-83776.patch + ###################### # downstream patches # ######################