The grub_cmd_cryptomount make check test performs some functional testing of cryptomount and by extension the underlying cryptodisk infrastructure.
A utility test script named grub-shell-luks-tester is created to handle the complexities of the testing, making it simpler to add new test cases in grub_cmd_cryptomount. Signed-off-by: Glenn Washburn <developm...@efficientek.com> --- Makefile.util.def | 12 + tests/grub_cmd_cryptomount.in | 185 ++++++++++++++ tests/util/grub-shell-luks-tester.in | 366 +++++++++++++++++++++++++++ 3 files changed, 563 insertions(+) create mode 100644 tests/grub_cmd_cryptomount.in create mode 100644 tests/util/grub-shell-luks-tester.in diff --git a/Makefile.util.def b/Makefile.util.def index d919c562c4..22dabba358 100644 --- a/Makefile.util.def +++ b/Makefile.util.def @@ -743,6 +743,12 @@ script = { installdir = noinst; }; +script = { + name = grub-shell-luks-tester; + common = tests/util/grub-shell-luks-tester.in; + installdir = noinst; +}; + script = { name = grub-fs-tester; common = tests/util/grub-fs-tester.in; @@ -1039,6 +1045,12 @@ script = { common = tests/grub_script_return.in; }; +script = { + testcase = nonnative; + name = grub_cmd_cryptomount; + common = tests/grub_cmd_cryptomount.in; +}; + script = { testcase = nonnative; name = grub_cmd_regexp; diff --git a/tests/grub_cmd_cryptomount.in b/tests/grub_cmd_cryptomount.in new file mode 100644 index 0000000000..b05cd3800e --- /dev/null +++ b/tests/grub_cmd_cryptomount.in @@ -0,0 +1,185 @@ +#! @BUILD_SHEBANG@ -e + +# Run all grub cryptomount tests in a Qemu instance +# Copyright (C) 2023 Free Software Foundation, Inc. +# +# GRUB is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GRUB is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GRUB. If not, see <http://www.gnu.org/licenses/>. + +if [ "x$EUID" = "x" ] ; then + EUID=`id -u` +fi + +if [ "$EUID" != 0 ] ; then + echo "not root; cannot test cryptomount." + exit 77 +fi + +if ! which cryptsetup >/dev/null 2>&1; then + echo "cryptsetup not installed; cannot test cryptomount." + exit 77 +fi + +if ! which mkfs.vfat >/dev/null 2>&1; then + echo "mkfs.vfat not installed; cannot test cryptomount." + exit 77 +fi + +COMMON_OPTS='${V:+--debug=$V} --cs-opts="--pbkdf-force-iterations 1000"' + +_testcase() { + local EXPECTEDRES=$1 + local LOGPREFIX=$2 + local res=0 + local output + shift 2 + + # Create a subdir in TMPDIR for each testcase + _TMPDIR=$TMPDIR + TMPDIR=$TMPDIR/`echo -n "$(date +%s).$LOGPREFIX" | sed -e 's,[ /],_,g' -e 's,:$,,g'` + mkdir -p "$TMPDIR" + + output=`"$@" 2>&1` || res=$? + TMPDIR=$_TMPDIR + + if [ "$res" -eq "$EXPECTEDRES" ]; then + if [ "$res" -eq 0 ]; then + echo $LOGPREFIX PASS + else + echo $LOGPREFIX XFAIL + fi + else + echo "Error[$res]: $output" + if [ "$res" -eq 0 ]; then + echo $LOGPREFIX XPASS + elif [ "$res" -eq 1 ]; then + echo $LOGPREFIX FAIL + else + # Any exit code other than 1 or 0, indicates a hard error, + # not a test error + echo $LOGPREFIX ERROR + return 99 + fi + return 1 + fi +} + +testcase() { _testcase 0 "$@"; } +testcase_fail() { _testcase 1 "$@"; } + +### LUKS1 tests +eval testcase "'LUKS1 test cryptsetup defaults:'" \ + @builddir@/grub-shell-luks-tester --luks=1 $COMMON_OPTS + +eval testcase "'LUKS1 test with twofish cipher:'" \ + @builddir@/grub-shell-luks-tester --luks=1 $COMMON_OPTS \ + "--cs-opts='--cipher twofish-xts-plain64'" + +eval testcase "'LUKS1 test key file support:'" \ + @builddir@/grub-shell-luks-tester --luks=1 $COMMON_OPTS \ + --keyfile + +eval testcase "'LUKS1 test key file with offset:'" \ + @builddir@/grub-shell-luks-tester --luks=1 $COMMON_OPTS \ + --keyfile --cs-opts="--keyfile-offset=237" + +eval testcase "'LUKS1 test key file with offset and size:'" \ + @builddir@/grub-shell-luks-tester --luks=1 $COMMON_OPTS \ + --keyfile "--cs-opts='--keyfile-offset=237 --keyfile-size=1023'" + +eval testcase "'LUKS1 test detached header support:'" \ + @builddir@/grub-shell-luks-tester --luks=1 $COMMON_OPTS \ + --detached-header + +eval testcase "'LUKS1 test both detached header and key file:'" \ + @builddir@/grub-shell-luks-tester --luks=1 $COMMON_OPTS \ + --keyfile --detached-header + +### LUKS2 tests (mirroring the LUKS1 tests above) +LUKS2_COMMON_OPTS="--luks=2 --cs-opts=--pbkdf=pbkdf2" +eval testcase "'LUKS2 test cryptsetup defaults:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS + +eval testcase "'LUKS2 test with twofish cipher:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS \ + "--cs-opts='--cipher twofish-xts-plain64'" + +eval testcase "'LUKS2 test key file support:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS \ + --keyfile + +eval testcase "'LUKS2 test key file with offset:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS \ + --keyfile --cs-opts="--keyfile-offset=237" + +eval testcase "'LUKS2 test key file with offset and size:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS \ + --keyfile "--cs-opts='--keyfile-offset=237 --keyfile-size=1023'" + +eval testcase "'LUKS2 test detached header support:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS \ + --detached-header + +eval testcase "'LUKS2 test both detached header and key file:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS \ + --keyfile --detached-header + +### LUKS1 specific tests +# Tests for xts-plain and xts-plain64 modes +eval testcase "'LUKS1 test cryptsetup xts-plain:'" \ + @builddir@/grub-shell-luks-tester --luks=1 $COMMON_OPTS \ + "--cs-opts='--cipher aes-xts-plain'" + +eval testcase "'LUKS1 test cryptsetup xts-plain64:'" \ + @builddir@/grub-shell-luks-tester --luks=1 $COMMON_OPTS \ + "--cs-opts='--cipher aes-xts-plain64'" + +### LUKS2 specific tests +eval testcase "'LUKS2 test with 1k sector size:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS \ + "--cs-opts='--sector-size 1024'" + +eval testcase "'LUKS2 test with 2k sector size:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS \ + "--cs-opts='--sector-size 2048'" + +eval testcase "'LUKS2 test with 4k sector size:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS \ + "--cs-opts='--sector-size 4096'" + +eval testcase "'LUKS2 test with non-default key slot:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS \ + "--cs-opts='--key-slot 5'" + +eval testcase "'LUKS2 test with different metadata size:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS \ + "--cs-opts='--luks2-metadata-size 512k'" + +# TODO: Expect a failure with LUKS2 volumes with argon2 key derivation +eval testcase_fail "'LUKS2 test with argon2 pbkdf:'" \ + @builddir@/grub-shell-luks-tester --luks=2 $COMMON_OPTS \ + "--cs-opts='--pbkdf-memory 32'" "--cs-opts='--pbkdf-parallel 1'" + +# Add good password to second slot and change first slot to unchecked password +csscript=`mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX"` || exit 99 +cat >$csscript <<'EOF' + CSOPTS="--pbkdf-force-iterations 1000 --pbkdf=pbkdf2" + cryptsetup $CSOPTS --key-file $lukskeyfile luksAddKey $luksdiskfile $lukskeyfile + echo "newpass" | cryptsetup $CSOPTS --key-file $lukskeyfile --key-slot 0 luksChangeKey $luksdiskfile +EOF + +eval testcase "'LUKS2 test with second key slot and first slot using different password:'" \ + @builddir@/grub-shell-luks-tester $LUKS2_COMMON_OPTS $COMMON_OPTS \ + "--cs-script='$csscript'" + +exit 0 diff --git a/tests/util/grub-shell-luks-tester.in b/tests/util/grub-shell-luks-tester.in new file mode 100644 index 0000000000..258f043a68 --- /dev/null +++ b/tests/util/grub-shell-luks-tester.in @@ -0,0 +1,366 @@ +#! @BUILD_SHEBANG@ -e + +# Test GRUBs ability to read various LUKS containers +# Copyright (C) 2023 Free Software Foundation, Inc. +# +# GRUB is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GRUB is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GRUB. If not, see <http://www.gnu.org/licenses/>. + +# Initialize some variables. +prefix="@prefix@" +exec_prefix="@exec_prefix@" +datarootdir="@datarootdir@" +builddir="@builddir@" +PACKAGE_NAME=@PACKAGE_NAME@ +PACKAGE_TARNAME=@PACKAGE_TARNAME@ +PACKAGE_VERSION=@PACKAGE_VERSION@ + +# Force build directory components +PATH="${builddir}:$PATH" +export PATH + +grub_shell_opts= +disksize=20M +detached_header= +keyfile= +keyfile_offset= +keyfile_size= +KEYFILE_SIZE_MAX=4096 + +# Usage: usage +# Print the usage. +usage () { + cat <<EOF +Usage: $0 [OPTION] [SOURCE] +Create a LUKS disk with cryptsetup, then verify that it is accessible by grub +running in a QEMU instance. + + -h, --help print this message and exit + -v, --version print the version information and exit + --modules=MODULES pre-load specified modules MODULES + --qemu-opts=OPTIONS extra options to pass to Qemu instance + --cs-opts=OPTIONS extra options to pass to cryptsetup instance + --cs-script=FILE script of cryptsetup commands to be run after format + --luks=1|2 Use LUKS1 or LUKS2 volumes + --detached-header Use a detached header + --keyfile[=FILE] Use a randomly generated key file of size $KEYFILE_SIZE_MAX if not + given a FILE to use as the key file. + +$0 creates a LUKS disk with cryptsetup, then verify that it is accessible by +grub running in a QEMU instance. + +Report bugs to <bug-g...@gnu.org>. +EOF +} + +. "${builddir}/grub-core/modinfo.sh" + +# TODO: We should be selecting the drive based on disk id, change this once +# grub support searching by disk id. +disk="hd0" +case "${grub_modinfo_target_cpu}-${grub_modinfo_platform}" in + i386-qemu) + disk="ata0" + ;; +esac + +# Check the arguments. +for option in "$@"; do + case "$option" in + -h | --help) + usage + exit 0 ;; + -v | --version) + echo "$0 (GNU GRUB ${PACKAGE_VERSION})" + exit 0 ;; + -d | --debug) + debug=$((${debug:-0}+1)) ;; + --debug=*) + debug=$((`echo "$option" | sed -e 's/--debug=//'`)) ;; + --modules=*) + ms=`echo "$option" | sed -e 's/--modules=//'` + modules="$modules,$ms" ;; + --qemu-opts=*) + qs=`echo "$option" | sed -e 's/--qemu-opts=//'` + qemuopts="$qemuopts $qs" ;; + --cs-opts=*) + qs=`echo "$option" | sed -e 's/--cs-opts=//'` + csopts="$csopts $qs" ;; + --cs-script=*) + qs=`echo "$option" | sed -e 's/--cs-script=//'` + csscripts="$csscripts $qs" ;; + --luks=*) + qs=`echo "$option" | sed -e 's/--luks=//'` + csopts="$csopts --type luks$qs" ;; + --detached-header) + detached_header=1 ;; + --keyfile=*) + qs=`echo "$option" | sed -e 's/--keyfile=//'` + keyfile="$qs" ;; + --keyfile) + keyfile=1 ;; + --disksize=*) + qs=`echo "$option" | sed -e 's/--disksize=//'` + disksize="$qs" ;; + -*) + echo "Unrecognized option \`$option'" 1>&2 + usage + exit 3 + ;; + *) + if [ "x${source}" != x ] ; then + echo "too many parameters at the end" 1>&2 + usage + exit 4 + fi + source="${option}" ;; + esac +done + +[ "${debug:-0}" -gt 1 ] && set -x + +grub_shell_opts="$grub_shell_opts --timeout=600s" + +if [ "${debug:-0}" -gt 2 ]; then + grub_shell_opts="$grub_shell_opts --qemu-opts=-nographic" +fi + +# Make sure that the dm-crypto device is shutdown +cleanup() { + if [ -e "$luksdev" ]; then + cryptsetup close "$luksdev" + fi + [ -z "$debug" ] && rm -rf "$lukstestdir" || : +} +trap cleanup EXIT INT TERM KILL QUIT + +get_random_bytes() { + local NUM_BYTES=$1 + dd if=/dev/urandom bs=512 count=$((($NUM_BYTES / 512)+2)) 2>/dev/null \ + | tr -d '\0' | dd bs=1 count=$(($NUM_BYTES)) 2>/dev/null +} + +# create a random directory to be hold generated files +lukstestdir="`mktemp -d "${TMPDIR:-/tmp}/$(basename "$0").XXXXXXXXXX"`" || exit 20 +luksfile=$lukstestdir/luks.disk +lukshdrfile=$lukstestdir/luks.header +lukskeyfile=$lukstestdir/luks.key +vfile=$lukstestdir/mnt/test.verify +vtext="TEST VERIFIED" +testvars=$lukstestdir/testvars +testcase=$lukstestdir/testcase.cfg +testoutput=$lukstestdir/testoutput +password=testpass + +[ -n "$debug" ] && echo "LUKS TEST directory: $lukstestdir" >&2 + +# If testing keyfiles, create a big one. +if [ -e "$keyfile" ]; then + password=`cat "$keyfile"` +elif [ -n "$keyfile" ]; then + password=`get_random_bytes $KEYFILE_SIZE_MAX` +fi + +if [ -n "$detached_header" ]; then + csopts="$csopts --header $lukshdrfile" +fi + +# create the key file +echo -n "$password" > $lukskeyfile + +# Create a very small LUKS container for the test +truncate -s $disksize $luksfile || exit 21 + +# Format the luks disk file +cryptsetup luksFormat -q $csopts $luksfile $lukskeyfile || exit 22 + +# Run any cryptsetup scripts +export luksdiskfile=${detached_header:+$lukshdrfile}${detached_header:-$luksfile} +export lukskeyfile +for csscript in $csscripts; do + [ -f "$csscript" ] && . $csscript +done + +# Look for --keyfile-offset and --keyfile-size options in the cryptsetup +# options, and process them specially. +csopen_opts= +get_args=0 +varname= +for option in $csopts; do + if [ "$get_args" -gt 0 ]; then + csopen_opts=" $csopen_opts $option" + get_args=$(($get_args - 1)) + eval ${varname}=$option + continue + fi + + case "$option" in + --keyfile-offset) + varname=keyfile_offset + get_args=1 ;; + --keyfile-offset=*) + keyfile_offset=`echo "$option" | sed -e 's/--keyfile-offset=//'` ;; + --keyfile-size | -l) + varname=keyfile_size + get_args=1 ;; + --keyfile-size=*) + keyfile_size=`echo "$option" | sed -e 's/--keyfile-size=//'` ;; + *) + continue ;; + esac + + csopen_opts=" $csopen_opts $option" +done + +# Open LUKS device +luksdev=/dev/mapper/`basename $lukstestdir` +cryptsetup open ${detached_header:+--header $lukshdrfile} $csopen_opts \ + --key-file $lukskeyfile $luksfile `basename $luksdev` || exit 23 + +# Make filesystem on the luks disk +mkfs.vfat $luksdev >/dev/null 2>&1 || exit 24 + +# Add verification file to filesystem +mkdir $lukstestdir/mnt +mount $luksdev $lukstestdir/mnt || exit 25 +echo "$vtext" > $vfile + +# Unmount filesystem +umount $lukstestdir/mnt || exit 26 + +. "@builddir@/grub-core/modinfo.sh" + +if [ x"${grub_modinfo_platform}" = xemu ]; then + grub_testvars="(host)$testvars" + grub_key_file="(host)$lukskeyfile" + grub_lukshdr_file="(host)$lukshdrfile" +else + grub_testvars="/testvars" + grub_key_file="/keyfile" + grub_lukshdr_file="/luks.header" +fi + + +# Can not use --disk with a raw LUKS container because it appears qemu +# tries to convert the image to and is failing with: +# "Parameter 'key-secret' is required for cipher" +qemuopts="$qemuopts -drive file=$luksfile,index=0,media=disk,format=raw" + +# Add crypto modules +modules="$modules cryptodisk luks luks2 fat" + +# Create randomly generated trim line +trim_line=`mktemp -u XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` + +# Create vars to import into grub script +cat >$testvars <<EOF +grub_debug="$debug" +grub_lukshdr_file="$grub_lukshdr_file" +grub_key_file="$grub_key_file" +grub_keyfile_offset="$keyfile_offset" +grub_keyfile_size="$keyfile_size" +vfilename="`basename $vfile`" +vtext="$vtext" +trim_line="$trim_line" +disk="$disk" +EOF + +# If testing keyfiles, do not use password variable +if [ -z "$keyfile" ]; then + echo "grub_password=\"$password\"" >>$testvars +fi + +# Create testcase script +cat >$testcase <<'EOF' +set debug=all +search -n -f --set=testvarsdev /testvars +if [ "$?" -ne 0 ]; then + echo; echo "$trim_line" + echo "Could not find testvars file." + ${halt_cmd} +fi +set debug= + +. ($testvarsdev)/testvars + +# If key file exists, use it instead of password +if [ -e "$grub_key_file" ]; then + cryptomount_opts="$cryptomount_opts -k $grub_key_file" +else + cryptomount_opts="$cryptomount_opts -p $grub_password" +fi + +if [ -n "$grub_keyfile_offset" ]; then + cryptomount_opts="$cryptomount_opts -O $grub_keyfile_offset" +fi + +if [ -n "$grub_keyfile_size" ]; then + cryptomount_opts="$cryptomount_opts -S $grub_keyfile_size" +fi + +if [ -e "$grub_lukshdr_file" ]; then + cryptomount_opts="$cryptomount_opts -H $grub_lukshdr_file" +fi + +cdisk=crypto0 + +if test -n "$grub_debug" -a "$grub_debug" -gt 0; then + echo cmd: cryptomount $cryptomount_opts ($disk) + echo -n "devices: " + ls +fi + +if test -n "$grub_debug" -a "$grub_debug" -gt 1; then + set debug=all +fi +cryptomount $cryptomount_opts ($disk) +ret="$?" +if test -n "$grub_debug" -a "$grub_debug" -eq 2; then + set debug= +fi + +echo; echo "$trim_line" +if test $ret -eq 0; then + cat ($cdisk)/$vfilename +else + echo "cryptomount failed: $ret" +fi +EOF + +grub_shell_opts="$grub_shell_opts --trim=${trim_line}" +if [ -n "$keyfile" ]; then + grub_shell_opts="$grub_shell_opts --files=${keyfile:+${grub_key_file}=${lukskeyfile}}" +fi + +if [ -n "$detached_header" ]; then + grub_shell_opts="$grub_shell_opts --files=${detached_header:+${grub_lukshdr_file}=${lukshdrfile}}" +fi + +# Run the test in grub-shell +@builddir@/grub-shell ${debug:+--debug} $grub_shell_opts \ + --modules="$modules" --qemu-opts="$qemuopts" \ + --files="${grub_testvars}=${testvars}" "$testcase" \ + >$testoutput +ret=$? + +if [ "$ret" -eq 0 ]; then + if ! grep -q "^${vtext}$" "$testoutput"; then + echo "error: test not verified [`cat $testoutput`]" >&2 + exit 1 + fi +else + echo "grub-shell exited with error: $ret" >&2 + exit 27 +fi + +exit $ret -- 2.34.1 _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel