The header and keyfile are necessary only for opening the device, not for closing, so it is not necessary to deactivate the generated cryptsetup unit when the header or keyfile backing store are removed. This is especially useful in the case of softreboot, when the new mount root is setup under /run/nextroot/ but we don't want to close the cryptsetup devices for encrypted /var/ or so, and we simply mount it directly on /run/nextroot/var/ before the soft-reboot.
240 lines
10 KiB
Bash
Executable File
240 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
|
set -eux
|
|
set -o pipefail
|
|
|
|
# TODO:
|
|
# - /proc/cmdline parsing
|
|
# - figure out token support (apart from TPM2, as that's covered by TEST-70-TPM2)
|
|
# - this might help https://www.qemu.org/docs/master/system/devices/ccid.html
|
|
# - expect + interactive auth?
|
|
|
|
# We set up an encrypted /var partition which should get mounted automatically
|
|
# on boot
|
|
mountpoint /var
|
|
|
|
systemctl --state=failed --no-legend --no-pager | tee /failed
|
|
if [[ -s /failed ]]; then
|
|
echo >&2 "Found units in failed state"
|
|
exit 1
|
|
fi
|
|
|
|
at_exit() {
|
|
set +e
|
|
|
|
mountpoint -q /proc/cmdline && umount /proc/cmdline
|
|
rm -f /etc/crypttab
|
|
[[ -e /tmp/crypttab.bak ]] && cp -fv /tmp/crypttab.bak /etc/crypttab
|
|
[[ -n "${STORE_LOOP:-}" ]] && losetup -d "$STORE_LOOP"
|
|
[[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR"
|
|
|
|
systemctl daemon-reload
|
|
}
|
|
|
|
trap at_exit EXIT
|
|
|
|
cryptsetup_start_and_check() {
|
|
local expect_fail=0
|
|
local umount_header_and_key=0
|
|
local ec volume unit
|
|
|
|
if [[ "${1:?}" == "-f" ]]; then
|
|
expect_fail=1
|
|
shift
|
|
fi
|
|
|
|
if [[ "${1:?}" == "-u" ]]; then
|
|
umount_header_and_key=1
|
|
shift
|
|
fi
|
|
|
|
for volume in "$@"; do
|
|
unit="systemd-cryptsetup@$volume.service"
|
|
|
|
# The unit existence check should always pass
|
|
[[ "$(systemctl show -P LoadState "$unit")" == loaded ]]
|
|
systemctl list-unit-files "$unit"
|
|
|
|
systemctl start "$unit" && ec=0 || ec=$?
|
|
if [[ "$expect_fail" -ne 0 ]]; then
|
|
if [[ "$ec" -eq 0 ]]; then
|
|
echo >&2 "Unexpected pass when starting $unit"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$ec" -ne 0 ]]; then
|
|
echo >&2 "Unexpected fail when starting $unit"
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$umount_header_and_key" -ne 0 ]]; then
|
|
umount "$TMPFS_DETACHED_KEYFILE"
|
|
umount "$TMPFS_DETACHED_HEADER"
|
|
udevadm settle --timeout=30
|
|
fi
|
|
|
|
systemctl status "$unit"
|
|
test -e "/dev/mapper/$volume"
|
|
systemctl stop "$unit"
|
|
test ! -e "/dev/mapper/$volume"
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
# Note: some stuff (especially TPM-related) is already tested by TEST-70-TPM2,
|
|
# so focus more on other areas instead
|
|
|
|
# Use a common workdir to make the cleanup easier
|
|
WORKDIR="$(mktemp -d)"
|
|
|
|
# Prepare a couple of LUKS2-encrypted disk images
|
|
#
|
|
# 1) Image with an empty password
|
|
IMAGE_EMPTY="$WORKDIR/empty.img)"
|
|
IMAGE_EMPTY_KEYFILE="$WORKDIR/empty.keyfile"
|
|
IMAGE_EMPTY_KEYFILE_ERASE="$WORKDIR/empty-erase.keyfile"
|
|
IMAGE_EMPTY_KEYFILE_ERASE_FAIL="$WORKDIR/empty-erase-fail.keyfile)"
|
|
truncate -s 32M "$IMAGE_EMPTY"
|
|
echo -n passphrase >"$IMAGE_EMPTY_KEYFILE"
|
|
chmod 0600 "$IMAGE_EMPTY_KEYFILE"
|
|
cryptsetup luksFormat --batch-mode \
|
|
--pbkdf pbkdf2 \
|
|
--pbkdf-force-iterations 1000 \
|
|
--use-urandom \
|
|
"$IMAGE_EMPTY" "$IMAGE_EMPTY_KEYFILE"
|
|
PASSWORD=passphrase NEWPASSWORD="" systemd-cryptenroll --password "$IMAGE_EMPTY"
|
|
# Duplicate the key file to test keyfile-erase as well
|
|
cp -v "$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY_KEYFILE_ERASE"
|
|
# The key should get erased even on a failed attempt, so test that too
|
|
cp -v "$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY_KEYFILE_ERASE_FAIL"
|
|
|
|
# 2) Image with a detached header and a key file offset + size
|
|
IMAGE_DETACHED="$WORKDIR/detached.img"
|
|
IMAGE_DETACHED_KEYFILE="$WORKDIR/detached.keyfile"
|
|
IMAGE_DETACHED_KEYFILE2="$WORKDIR/detached.keyfile2"
|
|
IMAGE_DETACHED_HEADER="$WORKDIR/detached.header"
|
|
truncate -s 32M "$IMAGE_DETACHED"
|
|
dd if=/dev/urandom of="$IMAGE_DETACHED_KEYFILE" count=64 bs=1
|
|
dd if=/dev/urandom of="$IMAGE_DETACHED_KEYFILE2" count=32 bs=1
|
|
chmod 0600 "$IMAGE_DETACHED_KEYFILE" "$IMAGE_DETACHED_KEYFILE2"
|
|
cryptsetup luksFormat --batch-mode \
|
|
--pbkdf pbkdf2 \
|
|
--pbkdf-force-iterations 1000 \
|
|
--use-urandom \
|
|
--header "$IMAGE_DETACHED_HEADER" \
|
|
--keyfile-offset 32 \
|
|
--keyfile-size 16 \
|
|
"$IMAGE_DETACHED" "$IMAGE_DETACHED_KEYFILE"
|
|
# Also, add a second key file to key slot 8
|
|
# Note: --key-slot= behaves as --new-key-slot= when used alone for backwards compatibility
|
|
cryptsetup luksAddKey --batch-mode \
|
|
--header "$IMAGE_DETACHED_HEADER" \
|
|
--key-file "$IMAGE_DETACHED_KEYFILE" \
|
|
--keyfile-offset 32 \
|
|
--keyfile-size 16 \
|
|
--key-slot 8 \
|
|
"$IMAGE_DETACHED" "$IMAGE_DETACHED_KEYFILE2"
|
|
|
|
# Prepare a couple of dummy devices we'll store a copy of the detached header
|
|
# and one of the keys on to test if systemd-cryptsetup correctly mounts them
|
|
# when necessary
|
|
STORE_IMAGE="$WORKDIR/store.img"
|
|
truncate -s 64M "$STORE_IMAGE"
|
|
STORE_LOOP="$(losetup --show --find --partscan "$STORE_IMAGE")"
|
|
sfdisk "$STORE_LOOP" <<EOF
|
|
label: gpt
|
|
type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 name=header_store size=32M
|
|
type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 name=keyfile_store
|
|
EOF
|
|
udevadm settle --timeout=30
|
|
mkdir -p /mnt
|
|
mkfs.ext4 -L header_store "/dev/disk/by-partlabel/header_store"
|
|
mount "/dev/disk/by-partlabel/header_store" /mnt
|
|
cp "$IMAGE_DETACHED_HEADER" /mnt/header
|
|
umount /mnt
|
|
mkfs.ext4 -L keyfile_store "/dev/disk/by-partlabel/keyfile_store"
|
|
mount "/dev/disk/by-partlabel/keyfile_store" /mnt
|
|
cp "$IMAGE_DETACHED_KEYFILE2" /mnt/keyfile
|
|
umount /mnt
|
|
|
|
# Also copy the key and header on a tmpfs that we will umount after unlocking
|
|
TMPFS_DETACHED_KEYFILE="$(mktemp -d)"
|
|
TMPFS_DETACHED_HEADER="$(mktemp -d)"
|
|
mount -t tmpfs -o size=32M tmpfs "$TMPFS_DETACHED_KEYFILE"
|
|
mount -t tmpfs -o size=32M tmpfs "$TMPFS_DETACHED_HEADER"
|
|
cp "$IMAGE_DETACHED_KEYFILE" "$TMPFS_DETACHED_KEYFILE/keyfile"
|
|
cp "$IMAGE_DETACHED_HEADER" "$TMPFS_DETACHED_HEADER/header"
|
|
|
|
udevadm settle --timeout=30
|
|
|
|
# Prepare our test crypttab
|
|
[[ -e /etc/crypttab ]] && cp -fv /etc/crypttab /tmp/crypttab.bak
|
|
cat >/etc/crypttab <<EOF
|
|
# headless should translate to headless=1
|
|
empty_key $IMAGE_EMPTY $IMAGE_EMPTY_KEYFILE headless,x-systemd.device-timeout=1m
|
|
empty_key_erase $IMAGE_EMPTY $IMAGE_EMPTY_KEYFILE_ERASE headless=1,keyfile-erase=1
|
|
empty_key_erase_fail $IMAGE_EMPTY $IMAGE_EMPTY_KEYFILE_ERASE_FAIL headless=1,keyfile-erase=1,keyfile-offset=4
|
|
# Empty passphrase without try-empty-password(=yes) shouldn't work
|
|
empty_fail0 $IMAGE_EMPTY - headless=1
|
|
empty_fail1 $IMAGE_EMPTY - headless=1,try-empty-password=0
|
|
empty0 $IMAGE_EMPTY - headless=1,try-empty-password
|
|
empty1 $IMAGE_EMPTY - headless=1,try-empty-password=1
|
|
# This one expects the key to be under /{etc,run}/cryptsetup-keys.d/empty_nokey.key
|
|
empty_nokey $IMAGE_EMPTY - headless=1
|
|
|
|
detached $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=$IMAGE_DETACHED_HEADER,keyfile-offset=32,keyfile-size=16
|
|
detached_store0 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=/header:LABEL=header_store,keyfile-offset=32,keyfile-size=16
|
|
detached_store1 $IMAGE_DETACHED /keyfile:LABEL=keyfile_store headless=1,header=$IMAGE_DETACHED_HEADER
|
|
detached_store2 $IMAGE_DETACHED /keyfile:LABEL=keyfile_store headless=1,header=/header:LABEL=header_store
|
|
detached_fail0 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=$IMAGE_DETACHED_HEADER,keyfile-offset=32
|
|
detached_fail1 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=$IMAGE_DETACHED_HEADER
|
|
detached_fail2 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1
|
|
detached_fail3 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=$IMAGE_DETACHED_HEADER,keyfile-offset=16,keyfile-size=16
|
|
detached_fail4 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=$IMAGE_DETACHED_HEADER,keyfile-offset=32,keyfile-size=8
|
|
detached_slot0 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE2 headless=1,header=$IMAGE_DETACHED_HEADER
|
|
detached_slot1 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE2 headless=1,header=$IMAGE_DETACHED_HEADER,key-slot=8
|
|
detached_slot_fail $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE2 headless=1,header=$IMAGE_DETACHED_HEADER,key-slot=0
|
|
detached_nofail $IMAGE_DETACHED $TMPFS_DETACHED_KEYFILE/keyfile headless=1,header=$TMPFS_DETACHED_HEADER/header,keyfile-offset=32,keyfile-size=16,nofail
|
|
EOF
|
|
|
|
# Temporarily drop luks.name=/luks.uuid= from the kernel command line, as it makes
|
|
# systemd-cryptsetup-generator ignore mounts from /etc/crypttab that are not also
|
|
# specified on the kernel command line
|
|
sed -r 's/luks.(name|uuid)=[^[:space:]+]//' /proc/cmdline >/tmp/cmdline.tmp
|
|
mount --bind /tmp/cmdline.tmp /proc/cmdline
|
|
# Run the systemd-cryptsetup-generator once explicitly, to collect coverage,
|
|
# as during daemon-reload we run generators in a sandbox
|
|
mkdir -p /tmp/systemd-cryptsetup-generator.out
|
|
/usr/lib/systemd/system-generators/systemd-cryptsetup-generator /tmp/systemd-cryptsetup-generator.out/
|
|
systemctl daemon-reload
|
|
systemctl list-unit-files "systemd-cryptsetup@*"
|
|
|
|
cryptsetup_start_and_check empty_key
|
|
test -e "$IMAGE_EMPTY_KEYFILE_ERASE"
|
|
cryptsetup_start_and_check empty_key_erase
|
|
test ! -e "$IMAGE_EMPTY_KEYFILE_ERASE"
|
|
test -e "$IMAGE_EMPTY_KEYFILE_ERASE_FAIL"
|
|
cryptsetup_start_and_check -f empty_key_erase_fail
|
|
test ! -e "$IMAGE_EMPTY_KEYFILE_ERASE_FAIL"
|
|
cryptsetup_start_and_check -f empty_fail{0..1}
|
|
cryptsetup_start_and_check empty{0..1}
|
|
# First, check if we correctly fail without any key
|
|
cryptsetup_start_and_check -f empty_nokey
|
|
# And now provide the key via /{etc,run}/cryptsetup-keys.d/
|
|
mkdir -p /run/cryptsetup-keys.d
|
|
cp "$IMAGE_EMPTY_KEYFILE" /run/cryptsetup-keys.d/empty_nokey.key
|
|
cryptsetup_start_and_check empty_nokey
|
|
|
|
cryptsetup_start_and_check detached
|
|
cryptsetup_start_and_check detached_store{0..2}
|
|
cryptsetup_start_and_check -f detached_fail{0..4}
|
|
cryptsetup_start_and_check detached_slot{0..1}
|
|
cryptsetup_start_and_check -f detached_slot_fail
|
|
cryptsetup_start_and_check -u detached_nofail
|
|
|
|
touch /testok
|