diff -up rear-2.4/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh.orig rear-2.4/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh --- rear-2.4/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh.orig 2018-06-21 10:40:53.000000000 +0200 +++ rear-2.4/usr/share/rear/layout/prepare/GNU/Linux/160_include_luks_code.sh 2020-11-25 09:21:55.186716041 +0100 @@ -1,35 +1,74 @@ # Code to recreate LUKS volumes. create_crypt() { + # See the create_device() function in lib/layout-functions.sh what "device type" means: + local device_type="$1" + if ! grep -q "^crypt $device_type " "$LAYOUT_FILE" ; then + LogPrintError "Skip recreating LUKS volume $device_type (no 'crypt $device_type' entry in $LAYOUT_FILE)" + # FIXME: The return code is ignored in the create_device() function in lib/layout-functions.sh: + return 1 + fi + local crypt target_device source_device options - read crypt target_device source_device options < <(grep "^crypt $1 " "$LAYOUT_FILE") + local mapping_name option key value + local cryptsetup_options="" keyfile="" password="" - local target_name=${target_device#/dev/mapper/} + read crypt target_device source_device options < <( grep "^crypt $device_type " "$LAYOUT_FILE" ) + + # Careful! One cannot 'test -b $source_device' here at the time when this code is run + # because the source device is usually a disk partition block device like /dev/sda2 + # but disk partition block devices usually do not yet exist (in particular not on a new clean disk) + # because partitions are actually created later when the diskrestore.sh script is run + # but not here when this code is run which only generates the diskrestore.sh script: + if ! test $source_device ; then + LogPrintError "Skip recreating LUKS volume $device_type: No source device (see the 'crypt $device_type' entry in $LAYOUT_FILE)" + # FIXME: The return code is ignored in the create_device() function in lib/layout-functions.sh: + return 1 + fi + + mapping_name=${target_device#/dev/mapper/} + if ! test $mapping_name ; then + LogPrintError "Skip recreating LUKS volume $device_type on $source_device: No /dev/mapper/... mapping name (see the 'crypt $device_type' entry in $LAYOUT_FILE)" + # FIXME: The return code is ignored in the create_device() function in lib/layout-functions.sh: + return 1 + fi - local cryptsetup_options="" keyfile="" password="" - local option key value for option in $options ; do - key=${option%=*} + # $option is of the form keyword=value and + # we assume keyword has no '=' character but value could be anything that may have a '=' character + # so we split keyword=value at the leftmost '=' character so that + # e.g. keyword=foo=bar gets split into key="keyword" and value="foo=bar": + key=${option%%=*} value=${option#*=} - + # The "cryptseup luksFormat" command does not require any of the type, cipher, key-size, hash, uuid option values + # because if omitted a cryptseup default value is used so we treat those values as optional. + # Using plain test to ensure the value is a single non empty and non blank word + # without quoting because test " " would return zero exit code + # cf. "Beware of the emptiness" in https://github.com/rear/rear/wiki/Coding-Style case "$key" in - cipher) - cryptsetup_options+=" --cipher $value" + (type) + test $value && cryptsetup_options+=" --type $value" + ;; + (cipher) + test $value && cryptsetup_options+=" --cipher $value" ;; - key_size) - cryptsetup_options+=" --key-size $value" + (key_size) + test $value && cryptsetup_options+=" --key-size $value" ;; - hash) - cryptsetup_options+=" --hash $value" + (hash) + test $value && cryptsetup_options+=" --hash $value" ;; - uuid) - cryptsetup_options+=" --uuid $value" + (uuid) + test $value && cryptsetup_options+=" --uuid $value" ;; - keyfile) - keyfile=$value + (keyfile) + test $value && keyfile=$value ;; - password) - password=$value + (password) + test $value && password=$value + ;; + (*) + LogPrintError "Skipping unsupported LUKS cryptsetup option '$key' in 'crypt $target_device $source_device' entry in $LAYOUT_FILE" ;; esac done @@ -37,26 +76,25 @@ create_crypt() { cryptsetup_options+=" $LUKS_CRYPTSETUP_OPTIONS" ( - echo "Log \"Creating LUKS device $target_name on $source_device\"" + echo "LogPrint \"Creating LUKS volume $mapping_name on $source_device\"" if [ -n "$keyfile" ] ; then # Assign a temporary keyfile at this stage so that original keyfiles do not leak onto the rescue medium. # The original keyfile will be restored from the backup and then re-assigned to the LUKS device in the # 'finalize' stage. # The scheme for generating a temporary keyfile path must be the same here and in the 'finalize' stage. - keyfile="${TMPDIR:-/tmp}/LUKS-keyfile-$target_name" + keyfile="$TMP_DIR/LUKS-keyfile-$mapping_name" dd bs=512 count=4 if=/dev/urandom of="$keyfile" chmod u=rw,go=- "$keyfile" - echo "cryptsetup luksFormat --batch-mode $cryptsetup_options $source_device $keyfile" - echo "cryptsetup luksOpen --key-file $keyfile $source_device $target_name" + echo "cryptsetup luksOpen --key-file $keyfile $source_device $mapping_name" elif [ -n "$password" ] ; then echo "echo \"$password\" | cryptsetup luksFormat --batch-mode $cryptsetup_options $source_device" - echo "echo \"$password\" | cryptsetup luksOpen $source_device $target_name" + echo "echo \"$password\" | cryptsetup luksOpen $source_device $mapping_name" else - echo "LogPrint \"Please enter the password for LUKS device $target_name ($source_device):\"" + echo "LogUserOutput \"Set the password for LUKS volume $mapping_name (for 'cryptsetup luksFormat' on $source_device):\"" echo "cryptsetup luksFormat --batch-mode $cryptsetup_options $source_device" - echo "LogPrint \"Please re-enter the password for LUKS device $target_name ($source_device):\"" - echo "cryptsetup luksOpen $source_device $target_name" + echo "LogUserOutput \"Enter the password for LUKS volume $mapping_name (for 'cryptsetup luksOpen' on $source_device):\"" + echo "cryptsetup luksOpen $source_device $mapping_name" fi echo "" ) >> "$LAYOUT_CODE" diff -up rear-2.4/usr/share/rear/layout/save/GNU/Linux/260_crypt_layout.sh.orig rear-2.4/usr/share/rear/layout/save/GNU/Linux/260_crypt_layout.sh --- rear-2.4/usr/share/rear/layout/save/GNU/Linux/260_crypt_layout.sh.orig 2018-06-21 10:40:53.000000000 +0200 +++ rear-2.4/usr/share/rear/layout/save/GNU/Linux/260_crypt_layout.sh 2020-11-25 09:19:31.406669210 +0100 @@ -9,6 +9,8 @@ Log "Saving Encrypted volumes." REQUIRED_PROGS=( "${REQUIRED_PROGS[@]}" cryptsetup dmsetup ) COPY_AS_IS=( "${COPY_AS_IS[@]}" /usr/share/cracklib/\* /etc/security/pwquality.conf ) +local invalid_cryptsetup_option_value="no" + while read target_name junk ; do # find the target device we're mapping if ! [ -e /dev/mapper/$target_name ] ; then @@ -30,17 +32,96 @@ while read target_name junk ; do source_device="$(get_device_name ${slave##*/})" done - if ! cryptsetup isLuks $source_device >/dev/null 2>&1; then + if ! blkid -p -o export $source_device >$TMP_DIR/blkid.output ; then + LogPrintError "Error: Cannot get attributes for $target_name ('blkid -p -o export $source_device' failed)" + continue + fi + + if ! grep -q "TYPE=crypto_LUKS" $TMP_DIR/blkid.output ; then + Log "Skipping $target_name (no 'TYPE=crypto_LUKS' in 'blkid -p -o export $source_device' output)" + continue + fi + + # Detect LUKS version: + # Remove all non-digits in particular to avoid leading or trailing spaces in the version string + # cf. "Beware of the emptiness" in https://github.com/rear/rear/wiki/Coding-Style + # that could happen if the blkid output contains "VERSION = 2" so that 'cut -d= -f2' results " 2". + version=$( grep "VERSION" $TMP_DIR/blkid.output | cut -d= -f2 | tr -c -d '[:digit:]' ) + if ! test "$version" = "1" -o "$version" = "2" ; then + LogPrintError "Error: Unsupported LUKS version for $target_name ('blkid -p -o export $source_device' shows 'VERSION=$version')" + continue + fi + luks_type=luks$version + + # 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)" 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 ) + 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. + # 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=$( egrep -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/' ) + fi - # gather crypt information - cipher=$(cryptsetup luksDump $source_device | grep "Cipher name" | sed -r 's/^.+:\s*(.+)$/\1/') - mode=$(cryptsetup luksDump $source_device | grep "Cipher mode" | cut -d: -f2- | awk '{printf("%s",$1)};') - key_size=$(cryptsetup luksDump $source_device | grep "MK bits" | sed -r 's/^.+:\s*(.+)$/\1/') - hash=$(cryptsetup luksDump $source_device | grep "Hash spec" | sed -r 's/^.+:\s*(.+)$/\1/') - uuid=$(cryptsetup luksDump $source_device | grep "UUID" | sed -r 's/^.+:\s*(.+)$/\1/') - keyfile_option=$([ -f /etc/crypttab ] && awk '$1 == "'"$target_name"'" && $3 != "none" && $3 != "-" { print "keyfile=" $3; }' /etc/crypttab) + # Basic checks that the cipher key_size hash uuid values exist + # cf. https://github.com/rear/rear/pull/2504#issuecomment-718729198 + # because some values are needed during "rear recover" + # to set cryptsetup options in layout/prepare/GNU/Linux/160_include_luks_code.sh + # 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 + # 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 + # cf. "Beware of the emptiness" in https://github.com/rear/rear/wiki/Coding-Style + # Do not error out instantly here but only report errors here so the user can see all messages + # and actually error out at the end of this script if there was one actually invalid value: + if ! test $cipher ; then + LogPrint "No 'cipher' value for LUKS$version volume $target_name in $source_device" + fi + if test $key_size ; then + if ! is_positive_integer $key_size ; then + LogPrintError "Error: 'key_size=$key_size' is no positive integer for LUKS$version volume $target_name in $source_device" + invalid_cryptsetup_option_value="yes" + fi + else + LogPrint "No 'key_size' value for LUKS$version volume $target_name in $source_device" + fi + if ! test $hash ; then + LogPrint "No 'hash' value for LUKS$version volume $target_name in $source_device" + fi + if ! test $uuid ; then + # Report a missig uuid value as an error to have the user informed + # but do not error out here because things can be fixed manually during "rear recover" + # cf. https://github.com/rear/rear/pull/2506#issuecomment-721757810 + # and https://github.com/rear/rear/pull/2506#issuecomment-722315498 + # and https://github.com/rear/rear/issues/2509 + 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 "crypt /dev/mapper/$target_name $source_device cipher=$cipher-$mode key_size=$key_size hash=$hash uuid=$uuid $keyfile_option" >> $DISKLAYOUT_FILE done < <( dmsetup ls --target crypt ) + +# Let this script return successfully when invalid_cryptsetup_option_value is not true: +is_true $invalid_cryptsetup_option_value && Error "Invalid or empty LUKS cryptsetup option value(s) in $DISKLAYOUT_FILE" || true