#! @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 99
fi

if ! which cryptsetup >/dev/null 2>&1; then
   echo "cryptsetup not installed; cannot test cryptomount."
   exit 99
fi

if ! which mkfs.vfat >/dev/null 2>&1; then
   echo "mkfs.vfat not installed; cannot test cryptomount."
   exit 99
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
