#!/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 ec volume unit if [[ "${1:?}" == "-f" ]]; then expect_fail=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 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" </etc/crypttab </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 touch /testok