tfixes to piping keys stdin/out in image steganography - tomb - the crypto undertaker HTML git clone git://parazyd.org/tomb.git DIR Log DIR Files DIR Refs DIR README DIR LICENSE --- DIR commit 534476a84988dc08ac1c7678d74651b880306aba DIR parent b7e89e52466a444a2e5c199217733f0bcf38e197 HTML Author: Jaromil <jaromil@dyne.org> Date: Tue, 5 Aug 2014 17:05:49 +0200 fixes to piping keys stdin/out in image steganography Also started refactoring code around key handling and added some documentation. Diffstat: M tomb | 264 ++++++++++++++++++++++--------- 1 file changed, 192 insertions(+), 72 deletions(-) --- DIR diff --git a/tomb b/tomb t@@ -539,30 +539,34 @@ check_bin() { # operations. On success returns 0 and prints out the full path to # the key. load_key() { - # take the name of a tomb file as argument - if option_is_set -k ; then - if [[ "`option_value -k`" == "-" ]]; then - _verbose "load_key reading from stdin." - # take key from stdin - tombkeydir=`safe_dir load_key_stdin` # global used to check if key from stdin - _verbose "tempdir is $tombkeydir" - act "Waiting for the key to be piped from stdin... " - cat > ${tombkeydir}/stdin.tmp.key - print ok >&2 - tombdir=${tombkeydir} - tombfile=stdin.tmp.key - tombname="stdin" - elif [[ "`option_value -k`" != "" ]]; then - _verbose "load_key argument: `option_value -k`" - # take key from a file - tombkey=`option_value -k` - tombdir=`dirname $tombkey` - tombfile=`basename $tombkey` - fi + # take the name of a tomb file as argument to option -k + # if no argument is given, tomb{key|dir|file} are set by caller - else # no -k specified + { option_is_set -k } || { _failure "This operation requires a key file to be specified using the -k option." - return 1 + return 1 } + + local keyopt + keyopt="`option_value -k`" + + if [[ "$keyopt" == "-" ]]; then + _verbose "load_key reading from stdin." + # take key from stdin + tombkeydir=`safe_dir load_key_stdin` + # tombkeydir is a global used to check if key from stdin + _verbose "tempdir is $tombkeydir" + act "Waiting for the key to be piped from stdin... " + cat > ${tombkeydir}/stdin.tmp.key + print ok >&2 + tombdir=${tombkeydir} + tombfile=stdin.tmp.key + tombname="stdin" + elif [[ "$keyopt" != "" ]]; then + _verbose "load_key argument: `option_value -k`" + # take key from a file + tombkey=`option_value -k` + tombdir=`dirname $tombkey` + tombfile=`basename $tombkey` fi tombkey=${tombdir}/${tombfile} t@@ -573,16 +577,59 @@ load_key() { drop_key return 1 } - # this does a check on the file header - if ! is_valid_key ${tombkey}; then - _warning "The key seems invalid, the application/pgp header is missing." - drop_key - return 1 - fi + # TODO: move this condition for JPEG steg into is_valid_key + [[ `file "$tombkey"` =~ "JP.G" ]] || { + # if the key file is an image don't check file header + if ! is_valid_key ${tombkey}; then + _warning "The key seems invalid or its format is not known by this version of Tomb." + drop_key + return 1 + fi + } + print "$tombkey" return 0 } +# takes two args just like get_lukskey +# prints out the decrypted content +# contains tweaks for different gpg versions +gpg_decrypt() { + # fix for gpg 1.4.11 where the --status-* options don't work ;^/ + gpgver=`gpg --version --no-permission-warning | awk '/^gpg/ {print $3}'` + local lukspass="$1" + local keyfile="$2" + + if [ "$gpgver" = "1.4.11" ]; then + _verbose "GnuPG is version 1.4.11 - adopting status fix." + + print "$lukspass" | \ + gpg --batch --passphrase-fd 0 --no-tty --no-options \ + -d "${keyfile}" + ret=$? + unset lukspass + + else # using status-file in gpg != 1.4.11 + + res=`safe_filename lukskey` + [[ $? = 0 ]] || { + unset lukspass; + _failure "Fatal error creating temp file." } + + print "$lukspass" | \ + gpg --batch --passphrase-fd 0 --no-tty --no-options \ + --status-fd 2 --no-mdc-warning --no-permission-warning \ + --no-secmem-warning -d "${keyfile}" 2> $res + + unset lukspass + grep 'DECRYPTION_OKAY' $res > /dev/null + ret=$?; rm -f $res + + fi + return $ret + +} + # This function asks the user for the password to use the key it tests # it against the return code of gpg on success returns 0 and prints # the password (be careful about where you save it!) t@@ -604,7 +651,7 @@ ask_key_password() { return 1 fi - get_lukskey "$tombpass" "$tombkey" >/dev/null + check_lukskey "$tombpass" "$tombkey" if [ $? = 0 ]; then passok=1; _message "Password OK." t@@ -617,7 +664,7 @@ ask_key_password() { tombpass="$2" _verbose "ask_key_password with tombpass: $tombpass" - get_lukskey "$tombpass" "$tombkey" >/dev/null + check_lukskey "$tombpass" "$tombkey" if [ $? = 0 ]; then passok=1; _message "Password OK."; fi t@@ -742,55 +789,100 @@ print "-----END PGP MESSAGE-----" } +# This function checks if the password effectively works to decrypt +# the key. It is used by the password prompt to verify validity and +# it resembles get_lukskey(). 1st arg the password, 2nd the keyfile +check_lukskey() { + local lukspass="$1" + local keyfile="$2" + local exhumedkey + + firstline=`head -n1 $keyfile` + _verbose "check_lukskey XXX $keyfile" + + + # key is KDF encoded + if [[ $firstline =~ '^_KDF_' ]]; then + _verbose "KDF: `cut -d_ -f 3 <<<$firstline`" + case `cut -d_ -f 3 <<<$firstline` in + pbkdf2sha1) + pbkdf2_param=`cut -d_ -f 4- <<<$firstline | tr '_' ' '` + lukspass=$(tomb-kdb-pbkdf2 ${=pbkdf2_param} 2> /dev/null <<<$lukspass) + ;; + *) + _failure "No suitable program for KDF `cut -f 3 <<<$firstline`." + unset lukspass + return 1 + ;; + esac + + # key needs to be exhumed from an image + elif [[ `file "$keyfile"` =~ "JP.G" ]]; then + exhumedkey="`safe_filename exhumedkey`" + _verbose "lukspass in check_lukskey: $lukspass" + + exhume_key "$keyfile" "$lukspass" "$exhumedkey" + keyfile="$exhumedkey" + fi + _verbose "lukspass in check_lukskey: $lukspass" + + # check validity, eventually repair adding headers + is_valid_key "$keyfile" || { + _failure "This key is unusable: $keyfile" } + + # prints out decrypted content to stdout + gpg_decrypt "$lukspass" "$keyfile" > /dev/null + ret="$?" + _verbose "check_lukskey returns $ret" + return $ret +} + + # Gets a key file and a password, prints out the decoded contents to # be used directly by Luks as a cryptographic key get_lukskey() { # $1 is the password, $2 is the keyfile - local tombpass=$1 - local keyfile=$2 + local lukspass="$1" + local keyfile="$2" + local exhumedkey + firstline=`head -n1 $keyfile` _verbose "get_lukskey XXX $keyfile" + + # key is KDF encoded if [[ $firstline =~ '^_KDF_' ]]; then _verbose "KDF: `cut -d_ -f 3 <<<$firstline`" case `cut -d_ -f 3 <<<$firstline` in pbkdf2sha1) pbkdf2_param=`cut -d_ -f 4- <<<$firstline | tr '_' ' '` - tombpass=$(tomb-kdb-pbkdf2 ${=pbkdf2_param} 2> /dev/null <<<$tombpass) + lukspass=$(tomb-kdb-pbkdf2 ${=pbkdf2_param} 2> /dev/null <<<$lukspass) ;; *) _failure "No suitable program for KDF `cut -f 3 <<<$firstline`." - unset tombpass + unset lukspass return 1 ;; esac - fi - # fix for gpg 1.4.11 where the --status-* options don't work ;^/ - gpgver=`gpg --version | awk '/^gpg/ {print $3}'` - if [ "$gpgver" = "1.4.11" ]; then - _verbose "GnuPG is version 1.4.11 - adopting status fix." + # key needs to be exhumed from an image + elif [[ `file "$keyfile"` =~ "JP.G" ]]; then + exhumedkey="`safe_filename exhumedkey`" + _verbose "lukspass in get_lukskey: $lukspass" - print ${tombpass} | \ - gpg --batch --passphrase-fd 0 --no-tty --no-options -d "${keyfile}" - ret=$? - unset tombpass - - else # using status-file in gpg != 1.4.11 - - res=`safe_filename lukskey` - { test $? = 0 } || { unset tombpass; _failure "Fatal error creating temp file." } + exhume_key "$keyfile" "$lukspass" "$exhumedkey" + keyfile="$exhumedkey" + fi + _verbose "lukspass in get_lukskey: $lukspass" - print ${tombpass} | \ - gpg --batch --passphrase-fd 0 --no-tty --no-options --status-fd 2 \ - --no-mdc-warning --no-permission-warning --no-secmem-warning \ - -d "${keyfile}" 2> $res + # check validity, eventually repair adding headers + is_valid_key "$keyfile" || { + _failure "This key is unusable: $keyfile" } - unset tombpass - grep 'DECRYPTION_OKAY' $res > /dev/null - ret=$?; rm -f $res + # prints out decrypted content to stdout + gpg_decrypt "$lukspass" "$keyfile" - fi + ret="$?" _verbose "get_lukskey returns $ret" return $ret } t@@ -849,10 +941,13 @@ gen_key() { fi # --kdf takes one parameter: iter time (on present machine) in seconds local -i microseconds - microseconds=$((itertime*10000)) + microseconds=$(( itertime * 10000 )) _success "Using KDF, iterations: $microseconds" + _message "generating salt" pbkdf2_salt=`tomb-kdb-pbkdf2-gensalt` + _message "calculating iterations" pbkdf2_iter=`tomb-kdb-pbkdf2-getiter $microseconds` + _message "encoding the password" # We use a length of 64bytes = 512bits (more than needed!?) tombpass=`tomb-kdb-pbkdf2 $pbkdf2_salt $pbkdf2_iter 64 <<<"${tombpass}"` t@@ -904,6 +999,12 @@ bury_key() { _success "Encoding key $tombkey inside image $imagefile" _message "Please confirm the key password for the encoding" + # We ask the password and test if it is the same encoding the + # base key, to insure that the same password is used for the + # encryption and the steganography. This is a standard enforced + # by Tomb, but its not strictly necessary (and having different + # password would enhance security). Nevertheless here we prefer + # usability. if option_is_set --tomb-pwd; then tomb_pwd="`option_value --tomb-pwd`" t@@ -940,20 +1041,36 @@ bury_key() { return $res } + +# mandatory 1st arg: the image file where key is supposed to be +# optional 2nd arg: the password to use (same as key, internal use) +# optional 3rd arg: the key where to save the result (- for stdout) exhume_key() { - tombkey="`option_value -k`" - { test "$tombkey" = "" } && { - tombkey="-" - _message "printing exhumed key on stdout" - } imagefile="$1" res=1 - file $imagefile | grep -i JPEG > /dev/null - if [ $? != 0 ]; then - _failure "Encode failed: $imagefile is not a jpeg image." - fi + knownpass="$2" + + tombkey="$3" + [[ "$tombkey" = "" ]] && { + tombkey="`option_value -k`" + { test "$tombkey" = "" } && { + # no key output specified: fallback to stdout + tombkey="-" + _message "printing exhumed key on stdout" } + } + + [[ `file "$imagefile"` =~ "JP.G" ]] || { + _failure "Encode failed: $imagefile is not a jpeg image." } + + # when a password is passed as argument then always print out + # the exhumed key on stdout without further checks (internal use) + { test "$knownpass" = "" } || { + steghide extract -sf "$imagefile" -p "$knownpass" -xf - + { test $? = 0 } || { + _failure "Wrong password or no steganographic key found" } + } { test "$tombkey" = "-" } || { if [[ -e "$tombkey" ]]; then t@@ -1448,6 +1565,7 @@ mount_tomb() { local tombfile local tombdir local tombname + local tombpass tombfile=`basename ${1}` tombdir=`dirname ${1}` # check file type (if its a Luks fs) t@@ -1460,7 +1578,7 @@ mount_tomb() { _verbose "Tomb found: ${tombdir}/${tombfile}" # load_key called here - tombkey=`load_key ${tombdir}/${tombfile}` + tombkey=`load_key` { test $? = 0 } || { _failure "Aborting operations: error loading key $tombkey" } t@@ -1519,7 +1637,10 @@ mount_tomb() { mapper="tomb.${tombname}.${mapdate}.`basename $nstloop`" _verbose "dev mapper device: $mapper" _verbose "Tomb key: $tombkey" - keyname=`basename $tombkey | cut -d. -f1` + + # take the name only, strip extensions + keyname=${tombkey%%.*} + _verbose "Tomb name: $keyname (to be engraved)" if option_is_set --tomb-pwd; then tomb_pwd="`option_value --tomb-pwd`" t@@ -1535,7 +1656,6 @@ mount_tomb() { get_lukskey "${tombpass}" ${tombkey} | \ cryptsetup --key-file - luksOpen ${nstloop} ${mapper} - # key dropped here drop_key unset tombpass t@@ -1748,8 +1868,8 @@ list_tombs() { for h in ${mounted_hooks}; do print -n "$fg_no_bold[green]$tombname" print -n "$fg_no_bold[white] hooks " -# print -n "$fg_bold[white]`basename ${h[(ws:;:)1]}`" -# print -n "$fg_no_bold[white] on " +# print -n "$fg_bold[white]`basename ${h[(ws:;:)1]}`" +# print -n "$fg_no_bold[white] on " print "$fg_bold[white]${h[(ws:;:)2]}$fg_no_bold[white]" done done t@@ -2019,7 +2139,7 @@ resize_tomb() { tombname=${tombfile%%\.*} # load key from options or file - local tombkey="`load_key ${tombdir}/${tombfile}`" + local tombkey="`load_key`" # make sure to call drop_key later { test -r "$tombkey" } || { _failure "Aborting operations: key not found, use -k" }