Run `make check' to execute the tests. This commit only adds ima_hash test. Signed-off-by: Vitaly Chikunov <vt@xxxxxxxxxxxx> --- .gitignore | 2 +- Makefile.am | 2 +- configure.ac | 1 + tests/.gitignore | 16 +++ tests/Makefile.am | 7 ++ tests/functions.sh | 273 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/ima_hash.test | 80 +++++++++++++++ 7 files changed, 379 insertions(+), 2 deletions(-) create mode 100644 tests/.gitignore create mode 100644 tests/Makefile.am create mode 100755 tests/functions.sh create mode 100755 tests/ima_hash.test diff --git a/.gitignore b/.gitignore index cb82166..c579199 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ missing compile libtool ltmain.sh - +test-driver # Compiled executables *.o diff --git a/Makefile.am b/Makefile.am index dba408d..45c6f82 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = src +SUBDIRS = src tests dist_man_MANS = evmctl.1 doc_DATA = examples/ima-genkey-self.sh examples/ima-genkey.sh examples/ima-gen-local-ca.sh diff --git a/configure.ac b/configure.ac index 099c3b1..f246182 100644 --- a/configure.ac +++ b/configure.ac @@ -72,6 +72,7 @@ EVMCTL_MANPAGE_DOCBOOK_XSL AC_CONFIG_FILES([Makefile src/Makefile + tests/Makefile packaging/ima-evm-utils.spec ]) AC_OUTPUT diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..9ecc984 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,16 @@ +# Generated by test driver +*.log +*.trs + +# Generated by tests +*.txt +*.out +*.sig +*.sig2 + +# Generated certs and keys (by gen-keys.sh) +*.cer +*.pub +*.key +*.conf + diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..e37b958 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,7 @@ +check_SCRIPTS = +TESTS = $(check_SCRIPTS) + +check_SCRIPTS += ima_hash.test + +clean-local: + -rm -f *.txt *.out *.sig *.sig2 diff --git a/tests/functions.sh b/tests/functions.sh new file mode 100755 index 0000000..a04bf36 --- /dev/null +++ b/tests/functions.sh @@ -0,0 +1,273 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# ima-evm-utils tests bash functions +# +# Copyright (C) 2020 Vitaly Chikunov <vt@xxxxxxxxxxxx> +# +# This program 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 2, or (at your option) +# any later version. +# +# This program 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. + +# Tests accounting +declare -i testspass=0 testsfail=0 testsskip=0 + +# Exit codes (compatible with automake) +declare -r OK=0 +declare -r FAIL=1 +declare -r HARDFAIL=99 # hard failure no matter testing mode +declare -r SKIP=77 + +# You can set env VERBOSE=1 to see more output from evmctl +VERBOSE=${VERBOSE:-0} +V=vvvv +V=${V:0:$VERBOSE} +V=${V:+-$V} + +# Exit if env FAILEARLY is defined. +# Used in expect_{pass,fail}. +exit_early() { + if [ "$FAILEARLY" ]; then + exit "$1" + fi +} + +# Require particular executables to be present +_require() { + ret= + for i; do + if ! type $i; then + echo "$i is required for test" + ret=1 + fi + done + [ $ret ] && exit $HARDFAIL +} + +# Non-TTY output is never colored +if [ -t 1 ]; then + RED=$'\e[1;31m' + GREEN=$'\e[1;32m' + YELLOW=$'\e[1;33m' + BLUE=$'\e[1;34m' + CYAN=$'\e[1;36m' + NORM=$'\e[m' +fi + +# Test mode determined by TFAIL variable: +# undefined: to success testing +# defined: failure testing +TFAIL= +TMODE=+ # mode character to prepend running command in log +declare -i TNESTED=0 # just for sanity checking + +# Run positive test (one that should pass) and account its result +expect_pass() { + local ret + + if [ $TNESTED -gt 0 ]; then + echo $RED"expect_pass should not be run nested"$NORM + testsfail+=1 + exit $HARDFAIL + fi + TFAIL= + TMODE=+ + TNESTED+=1 + [ "$VERBOSE" -gt 1 ] && echo "____ START positive test: $*" + "$@" + ret=$? + [ "$VERBOSE" -gt 1 ] && echo "^^^^ STOP ($ret) positive test: $*" + TNESTED+=-1 + case $ret in + 0) testspass+=1 ;; + 77) testsskip+=1 ;; + 99) testsfail+=1; exit_early 1 ;; + *) testsfail+=1; exit_early 2 ;; + esac + return $ret +} + +# Eval negative test (one that should fail) and account its result +expect_fail() { + local ret + + if [ $TNESTED -gt 0 ]; then + echo $RED"expect_fail should not be run nested"$NORM + testsfail+=1 + exit $HARDFAIL + fi + + TFAIL=yes + TMODE=- + TNESTED+=1 + [ "$VERBOSE" -gt 1 ] && echo "____ START negative test: $*" + "$@" + ret=$? + [ "$VERBOSE" -gt 1 ] && echo "^^^^ STOP ($ret) negative test: $*" + TNESTED+=-1 + case $ret in + 0) testsfail+=1; exit_early 3 ;; + 77) testsskip+=1 ;; + 99) testsfail+=1; exit_early 4 ;; + *) testspass+=1 ;; + esac + # Restore defaults (as in positive tests) + # for tests to run without wrappers + TFAIL= + TMODE=+ + return $ret +} + +# return true if current test is positive +_test_expected_to_pass() { + [ ! $TFAIL ] +} + +# return true if current test is negative +_test_expected_to_fail() { + [ $TFAIL ] +} + +# Show blank line and color following text to red +# if it's real error (ie we are in expect_pass mode). +color_red_on_failure() { + if _test_expected_to_pass; then + echo $RED + COLOR_RESTORE=true + fi +} + +# For hard errors +color_red() { + echo $RED + COLOR_RESTORE=true +} + +color_restore() { + [ $COLOR_RESTORE ] && echo $NORM + COLOR_RESTORE= +} + +ADD_DEL= +ADD_TEXT_FOR= +# _evmctl_run should be run as `_evmctl_run ... || return' +_evmctl_run() { + local op=$1 out=$1-$$.out + local text_for=${FOR:+for $ADD_TEXT_FOR} + # Additional parameters: + # ADD_DEL: additional files to rm on failure + # ADD_TEXT_FOR: append to text as 'for $ADD_TEXT_FOR' + + cmd="evmctl $V $EVMCTL_ENGINE $*" + echo $YELLOW$TMODE "$cmd"$NORM + $cmd >"$out" 2>&1 + ret=$? + + # Shell special and signal exit codes (except 255) + if [ $ret -ge 126 ] && [ $ret -lt 255 ]; then + color_red + echo "evmctl $op failed hard with ($ret) $text_for" + sed 's/^/ /' "$out" + color_restore + rm "$out" $ADD_DEL + ADD_DEL= + ADD_TEXT_FOR= + return $HARDFAIL + elif [ $ret -gt 0 ]; then + color_red_on_failure + echo "evmctl $op failed" ${TFAIL:+properly} "with ($ret) $text_for" + # Show evmctl output only in verbose mode or if real failure. + if _test_expected_to_pass || [ "$VERBOSE" ]; then + sed 's/^/ /' "$out" + fi + color_restore + rm "$out" $ADD_DEL + ADD_DEL= + ADD_TEXT_FOR= + return $FAIL + elif _test_expected_to_fail; then + color_red + echo "evmctl $op wrongly succeeded $text_for" + sed 's/^/ /' "$out" + color_restore + else + [ "$VERBOSE" ] && sed 's/^/ /' "$out" + fi + rm "$out" + ADD_DEL= + ADD_TEXT_FOR= + return $OK +} + +# Extract xattr $attr from $file into $out file skipping $pref'ix +_extract_xattr() { + local file=$1 attr=$2 out=$3 pref=$4 + + getfattr -n "$attr" -e hex "$file" \ + | grep "^$attr=" \ + | sed "s/^$attr=$pref//" \ + | xxd -r -p > "$out" +} + +# Test if xattr $attr in $file matches $pref'ix +# Show error and fail otherwise. +_test_xattr() { + local file=$1 attr=$2 pref=$3 + local text_for=${ADD_TEXT_FOR:+ for $ADD_TEXT_FOR} + + if ! getfattr -n "$attr" -e hex "$file" | egrep -qx "$attr=$pref"; then + color_red_on_failure + echo "Did not find expected hash$text_for:" + echo " $attr=$pref" + echo "" + echo "Actual output below:" + getfattr -n "$attr" -e hex "$file" | sed 's/^/ /' + color_restore + rm "$file" + ADD_TEXT_FOR= + return $FAIL + fi + ADD_TEXT_FOR= +} + +# Try to enable gost-engine if needed. +_enable_gost_engine() { + # Do not enable if it's already working (enabled by user) + if ! openssl md_gost12_256 /dev/null >/dev/null 2>&1 \ + && openssl engine gost >/dev/null 2>&1; then + EVMCTL_ENGINE="--engine gost" + OPENSSL_ENGINE="-engine gost" + fi +} + +# Show test stats and exit into automake test system +# with proper exit code (same as ours). +_report_exit() { + if [ $testsfail -gt 0 ]; then + echo "=================================" + echo " Run with FAILEARLY=1 $0 $*" + echo " To stop after first failure" + echo "=================================" + fi + [ $testspass -gt 0 ] && echo -n $GREEN || echo -n $NORM + echo -n "PASS: $testspass" + [ $testsskip -gt 0 ] && echo -n $YELLOW || echo -n $NORM + echo -n " SKIP: $testsskip" + [ $testsfail -gt 0 ] && echo -n $RED || echo -n $NORM + echo " FAIL: $testsfail" + echo $NORM + if [ $testsfail -gt 0 ]; then + exit $FAIL + elif [ $testspass -gt 0 ]; then + exit $OK + else + exit $SKIP + fi +} + diff --git a/tests/ima_hash.test b/tests/ima_hash.test new file mode 100755 index 0000000..a3e81a6 --- /dev/null +++ b/tests/ima_hash.test @@ -0,0 +1,80 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# evmctl ima_hash tests +# +# Copyright (C) 2020 Vitaly Chikunov <vt@xxxxxxxxxxxx> +# +# This program 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 2, or (at your option) +# any later version. +# +# This program 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. + +cd "$(dirname "$0")" || exit 1 +PATH=../src:$PATH +source ./functions.sh +_require evmctl openssl getfattr + +trap _report_exit EXIT +set -f # disable globbing + +check() { + local alg=$1 pref=$2 chash=$3 hash + local file=$alg-hash.txt + + rm -f "$file" + touch "$file" + # Generate hash with openssl, if it failed skip test, + # unless it's negative test, then pass to evmctl + cmd="openssl dgst $OPENSSL_ENGINE -$alg $file" + echo - "$cmd" + hash=$(set -o pipefail; $cmd 2>/dev/null | cut -d' ' -f2) + if [ $? -ne 0 ] && _test_expected_to_pass; then + echo $CYAN"$alg test is skipped"$NORM + rm "$file" + return $SKIP + fi + if [ "$chash" ] && [ "$chash" != "$hash" ]; then + color_red + echo "Invalid hash for $alg from openssl" + echo "Expected: $chash" + echo "Returned: $hash" + color_restore + rm "$file" + return $HARDFAIL + fi + + ADD_TEXT_FOR=$alg ADD_DEL=$file \ + _evmctl_run ima_hash --hashalgo "$alg" --xattr-user "$file" || return + ADD_TEXT_FOR=$alg \ + _test_xattr "$file" user.ima "$pref$hash" || return + rm "$file" + return $OK +} + +# check args: algo hdr-prefix canonic-hash +expect_pass check md4 0x01 31d6cfe0d16ae931b73c59d7e0c089c0 +expect_pass check md5 0x01 d41d8cd98f00b204e9800998ecf8427e +expect_pass check sha1 0x01 da39a3ee5e6b4b0d3255bfef95601890afd80709 +expect_fail check SHA1 0x01 # uppercase +expect_fail check sha512-224 0x01 # valid for pkcs1 +expect_fail check sha512-256 0x01 # valid for pkcs1 +expect_fail check unknown 0x01 # nonexistent +expect_pass check sha224 0x0407 d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f +expect_pass check sha256 0x0404 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +expect_pass check sha384 0x0405 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b +expect_pass check sha512 0x0406 cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e +expect_pass check rmd160 0x0403 9c1185a5c5e9fc54612808977ee8f548b2258d31 +expect_fail check sm3 0x01 +expect_fail check sm3-256 0x01 +_enable_gost_engine +expect_pass check md_gost12_256 0x0412 3f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb +expect_pass check streebog256 0x0412 3f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb +expect_pass check md_gost12_512 0x0413 8e945da209aa869f0455928529bcae4679e9873ab707b55315f56ceb98bef0a7362f715528356ee83cda5f2aac4c6ad2ba3a715c1bcd81cb8e9f90bf4c1c1a8a +expect_pass check streebog512 0x0413 8e945da209aa869f0455928529bcae4679e9873ab707b55315f56ceb98bef0a7362f715528356ee83cda5f2aac4c6ad2ba3a715c1bcd81cb8e9f90bf4c1c1a8a + -- 2.11.0