diff --git a/.travis.yml b/.travis.yml index 56e9031..7389acb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,12 @@ -dist: trusty sudo: required language: c -addons: - apt: - packages: - - zsh - - gnupg - - cryptsetup - - pinentry-curses - - gawk - - libgcrypt20-dev - - steghide - - qrencode - - e2fsprogs - - python2.7 +services: + - docker before_script: - - make --directory=extras/kdf-keys - - sudo make --directory=extras/kdf-keys install - - sudo ./extras/install_cloakify.sh + - docker build -t dyne/tomb . + - docker run -it --privileged dyne/tomb /bin/bash -c "oracle & make test" script: - - make --directory=extras/kdf-keys test - - make test + - docker run -it --privileged dyne/tomb /bin/bash -c "make --directory=extras/kdf-keys test" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e42f0e9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM dyne/devuan:beowulf + +RUN apt-get update -y -q && apt-get install -y -q zsh cryptsetup gawk libgcrypt20-dev steghide qrencode python python2.7 python3-pip python3-dev libsodium-dev libssl-dev make gcc g++ sudo gettext file bsdmainutils +RUN pip3 install setuptools wheel + +COPY . /Tomb/ + +WORKDIR /Tomb/extras +RUN ./install_sphinx.sh +RUN cp test/sphinx.cfg /etc/sphinx/config + +WORKDIR /Tomb +RUN make --directory=extras/kdf-keys +RUN make --directory=extras/kdf-keys install \ No newline at end of file diff --git a/extras/install_cloakify.sh b/extras/install_cloakify.sh index 4935154..1d3fec2 100755 --- a/extras/install_cloakify.sh +++ b/extras/install_cloakify.sh @@ -4,8 +4,8 @@ wget https://github.com/TryCatchHCF/Cloakify/archive/v1.0.3.tar.gz -O /tmp/cloak mkdir -p extras/cloakify tar -xvf /tmp/cloakify.tar.gz --strip-components=1 -C $PWD/extras/cloakify echo "#!/bin/sh -python2 $PWD/extras/cloakify/cloakify.py" > /usr/bin/cloakify +python2 $PWD/extras/cloakify/cloakify.py $@" > /usr/bin/cloakify echo "#!/bin/sh -python2 $PWD/extras/cloakify/decloakify.py" > /usr/bin/decloakify +python2 $PWD/extras/cloakify/decloakify.py $@" > /usr/bin/decloakify chmod +x /usr/bin/cloakify chmod +x /usr/bin/decloakify \ No newline at end of file diff --git a/extras/install_sphinx.sh b/extras/install_sphinx.sh new file mode 100755 index 0000000..5cd9f13 --- /dev/null +++ b/extras/install_sphinx.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -ex +git clone https://github.com/stef/libsphinx +cd libsphinx +git submodule update --init --recursive --remote +cd src +sed -i 's|/usr/local|/usr|' makefile +make +sudo make install +ldconfig +pip3 install pwdsphinx +sudo mkdir -p /etc/sphinx \ No newline at end of file diff --git a/extras/test/00_create.sh b/extras/test/00_create.sh index da70774..85b9111 100644 --- a/extras/test/00_create.sh +++ b/extras/test/00_create.sh @@ -15,4 +15,16 @@ test_expect_success 'Testing tomb creation: dig, forge and lock' ' tt_lock --tomb-pwd $DUMMYPASS ' +if test_have_prereq SPHINX ORACLE; then + test_export "sphinx_test" + test_expect_success 'Testing tomb creation: dig, forge and lock (sphinx password handling)' ' + tt_dig -s 20 && + tt_forge --tomb-pwd $DUMMYPASS --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST && + print $(echo $DUMMYPASS | sphinx get $DUMMYUSER $DUMMYHOST) \ + | gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key \ + | hexdump -C && + tt_lock --tomb-pwd $DUMMYPASS --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST + ' +fi + test_done diff --git a/extras/test/10_operations.sh b/extras/test/10_operations.sh index 003be17..7655b86 100644 --- a/extras/test/10_operations.sh +++ b/extras/test/10_operations.sh @@ -44,4 +44,13 @@ if test_have_prereq LSOF; then ' fi +if test_have_prereq SPHINX ORACLE; then + test_export "sphinx_test" # Using already generated tomb + test_expect_success 'Testing open with good password (sphinx)' ' + tt_open --tomb-pwd $DUMMYPASS --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST && + tt_close + ' +fi + + test_done diff --git a/extras/test/60_resize.sh b/extras/test/60_resize.sh index 5d33105..a8f0290 100644 --- a/extras/test/60_resize.sh +++ b/extras/test/60_resize.sh @@ -16,4 +16,11 @@ if test_have_prereq RESIZER; then ' fi +if test_have_prereq RESIZER SPHINX ORACLE; then + test_export "sphinx_test" # Using already generated tomb + test_expect_success 'Testing resize to 30 MB tomb (sphinx)' ' + tt resize -s 30 $tomb -k $tomb_key --unsafe --tomb-pwd $DUMMYPASS --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST + ' +fi + test_done diff --git a/extras/test/65_passwd.sh b/extras/test/65_passwd.sh index 4350de8..b549da8 100644 --- a/extras/test/65_passwd.sh +++ b/extras/test/65_passwd.sh @@ -17,4 +17,16 @@ test_expect_success 'Testing tomb with GnuPG keys: passwd' ' tt passwd -k $tomb_key -g -r $KEY2 ' +if test_have_prereq SPHINX ORACLE; then + test_export "sphinx_test" # Using already generated tomb + test_expect_success 'Testing changing tomb password with sphinx' ' + tt passwd -k $tomb_key --unsafe \ + --tomb-old-pwd $DUMMYPASS --tomb-pwd $DUMMYPASSNEW \ + --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST && + tt passwd -k $tomb_key --unsafe \ + --tomb-old-pwd $DUMMYPASSNEW --tomb-pwd $DUMMYPASS \ + --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST + ' +fi + test_done diff --git a/extras/test/75_hooks.sh b/extras/test/75_hooks.sh index d263934..919a119 100644 --- a/extras/test/75_hooks.sh +++ b/extras/test/75_hooks.sh @@ -2,6 +2,8 @@ export test_description="Testing tomb bind hooks" +TEMPHOME=$HOME + source ./setup test_export "test" # Using already generated tomb @@ -14,9 +16,9 @@ test_expect_success 'Testing bind hooks' ' rm -f "$MEDIA/$testname/bind-hooks" && echo "$bindtest $bindtest" > "$MEDIA/$testname/bind-hooks" && tt_close && - touch "/home/$USER/$bindtest" && + touch "$TEMPHOME/$bindtest" && tt_open --tomb-pwd $DUMMYPASS && - RND2=$(cat "/home/$USER/$bindtest") && + RND2=$(cat "$TEMPHOME/$bindtest") && [[ "$RND" = "$RND2" ]] && tt list $testname && tt_close diff --git a/extras/test/90_setkey.sh b/extras/test/90_setkey.sh index d2d0cc4..b942725 100644 --- a/extras/test/90_setkey.sh +++ b/extras/test/90_setkey.sh @@ -26,4 +26,23 @@ test_expect_success 'Testing tomb with GnuPG keys: setkey' ' tt_close ' +if test_have_prereq SPHINX ORACLE; then + test_export "sphinx_test" # Using already generated tomb + test_expect_success 'Testing set key (sphinx)' ' + tt forge -k $tomb_key_new --tomb-pwd $DUMMYPASS \ + --ignore-swap --unsafe --use-urandom --force \ + --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST && + tt setkey -k $tomb_key_new $tomb_key $tomb \ + --unsafe --tomb-pwd $DUMMYPASS --tomb-old-pwd $DUMMYPASS \ + --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST && + tt open -k $tomb_key_new $tomb \ + --unsafe --tomb-pwd $DUMMYPASS \ + --sphx-user $DUMMYUSER --sphx-host $DUMMYHOST && + print $DUMMYPASS \ + | gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key_new \ + | hexdump -C && + tt_close + ' +fi + test_done diff --git a/extras/test/setup b/extras/test/setup index a622c62..5b4dc83 100644 --- a/extras/test/setup +++ b/extras/test/setup @@ -55,6 +55,8 @@ command -v lsof > /dev/null && test_set_prereq LSOF command -v python2 > /dev/null && test_set_prereq PYTHON2 command -v cloakify > /dev/null && test_set_prereq CLOAKIFY command -v decloakify > /dev/null && test_set_prereq DECLOAKIFY +command -v sphinx > /dev/null && test_set_prereq SPHINX +command -v oracle > /dev/null && test_set_prereq ORACLE # GnuPG config @@ -73,6 +75,10 @@ chmod 700 "$GNUPGHOME" export DUMMYPASS=test export DUMMYPASSNEW=changetest +# Dummy host and username for sphinx +export DUMMYHOST=example.com +export DUMMYUSER=user + # Test helpers diff --git a/extras/test/sphinx.cfg b/extras/test/sphinx.cfg new file mode 100644 index 0000000..6e827c8 --- /dev/null +++ b/extras/test/sphinx.cfg @@ -0,0 +1,16 @@ +[client] +verbose = False +address = 127.0.0.1 +port = 2355 +datadir = /tmp/.sphinx/ + +[server] +verbose = False +address = 127.0.0.1 +port = 2355 +datadir = /tmp/.sphinx/ +keydir = /tmp/.sphinx/ + +[websphinx] +pinentry=/usr/bin/pinentry +log= diff --git a/tomb b/tomb index 2f60155..d116e0b 100755 --- a/tomb +++ b/tomb @@ -76,6 +76,7 @@ typeset -i KDF=1 typeset -i STEGHIDE=1 typeset -i CLOAKIFY=1 typeset -i DECLOAKIFY=1 +typeset -i SPHINX=1 typeset -i RESIZER=1 typeset -i SWISH=1 typeset -i QRENCODE=1 @@ -501,6 +502,12 @@ ask_password() { [[ "$i" =~ "^D .*" ]] && password="${i##D }"; done + # if sphinx mode is chosen, use the provided input + # as master password to retrieve the actual password + { option_is_set --sphx-user } || { option_is_set --sphx-host } && { + password=$(sphinx_get_password "$password") + } + [[ "$password" = "" ]] && { _warning "Empty password" print "empty" @@ -511,6 +518,64 @@ ask_password() { return 0 } +# Retrieve PASSWORD from sphinx +# $1 MASTER password for the password store +# requires --sphx-host and --sphx-user flags to be set +sphinx_get_password() { + local errorfile + local password + { option_is_set --sphx-user && option_is_set --sphx-host } && { + # value error in sphinx doesn't set exit code + # using tempfile as a workaround to notice the error + errorfile=$(mktemp /tmp/tomb_error.XXXXXXXXX) + password=$(echo "$1" | sphinx get $(option_value --sphx-user) $(option_value --sphx-host) 2>$errorfile) + if ! grep -q "ValueError: fail" $errorfile ; then + echo "$password" + rm $errorfile + return 0 + else + _warning "sphinx returns error: ::1 error::" $(cat $errorfile) + rm $errorfile + _failure "Failed to retrieve actual password with sphinx." + fi + } || { + _failure "Both host and user have to be set to use sphinx" + } +} + +# Create PASSWORD in sphinx +# $1 MASTER password for the password store +# requires --sphx-host and --sphx-user flags to be set +sphinx_set_password() { + local errorfile + local password + { option_is_set --sphx-user && option_is_set --sphx-host } && { + # value error in sphinx doesn't set exit code + # using tempfile as a workaround to notice the error + errorfile=$(mktemp /tmp/tomb_error.XXXXXXXXX) + # check first if this host/user combination exists in store + # if yes, there is no need to make a call to create + password=$(echo "$1" | sphinx get $(option_value --sphx-user) $(option_value --sphx-host) 2>$errorfile) + if ! grep -q "ValueError: fail" $errorfile ; then + echo "$password" + rm $errorfile + return 0 + fi + # no such host/user combination in store, create one + password=$(echo "$1" | sphinx create $(option_value --sphx-user) $(option_value --sphx-host) ulsd 0 2>$errorfile) + if ! grep -q "ValueError: fail" $errorfile ; then + echo "$password" + rm $errorfile + return 0 + else + _warning "sphinx returns error: ::1 error::" $(cat $errorfile) + rm $errorfile + _failure "Failed to create password with sphinx" + fi + } || { + _failure "Both host and user have to be set to use sphinx" + } +} # Check if a filename is a valid tomb @@ -682,6 +747,11 @@ usage() { _print " -r provide GnuPG recipients (separated by comma)" _print " -R provide GnuPG hidden recipients (separated by comma)" + [[ $SPHINX == 1 ]] && { + _print " --sphx-user user associated with the key (for use with pitchforkedsphinx)" + _print " --sphx-host host associated with the key (for use with pitchforkedsphinx)" + } + [[ $KDF == 1 ]] && { _print " --kdf forge keys armored against dictionary attacks" } @@ -863,6 +933,8 @@ _ensure_dependencies() { command -v cloakify 1>/dev/null 2>/dev/null || CLOAKIFY=0 # Check for decloakify command -v decloakify 1>/dev/null 2>/dev/null || DECLOAKIFY=0 + # Check for pitchforkedsphinx client + command -v sphinx 1>/dev/null 2>/dev/null || SPHINX=0 # Check for resize command -v resize2fs 1>/dev/null 2>/dev/null || RESIZER=0 # Check for KDF auxiliary tools @@ -1196,6 +1268,12 @@ ask_key_password() { tombpass="$1" _verbose "ask_key_password with tombpass: ::1 tomb pass::" $tombpass + # if sphinx mode is chosen, use the provided input + # as master password to retrieve the actual password + { option_is_set --sphx-user } || { option_is_set --sphx-host } && { + tombpass=$(sphinx_get_password "$tombpass") + } + get_lukskey "$tombpass" [[ $? = 0 ]] && { @@ -1212,7 +1290,7 @@ ask_key_password() { # call cryptsetup with arguments using the currently known secret # echo flags eliminate newline and disable escape (BSD_ECHO) _cryptsetup() { - print -R -n - "$TOMBSECRET" | _sudo cryptsetup --key-file - ${@} + print -R -n - "$TOMBSECRET" | _sudo cryptsetup --type luks1 --key-file - ${@} return $? } @@ -1283,10 +1361,22 @@ gen_key() { local gpgpass opt local recipients_opt typeset -a gpgopt + local sphx_host_tmp + local sphx_user_tmp # here user is prompted for key password tombpass="" tombpasstmp="" + # remove sphinx opts not to mess with initial password prompt + { option_is_set --sphx-user } && { + sphx_user_tmp="$(option_value --sphx-user)" + unset "OPTS[--sphx-user]" + } + { option_is_set --sphx-host } && { + sphx_host_tmp="$(option_value --sphx-host)" + unset "OPTS[--sphx-host]" + } + { option_is_set -g } && { gpgopt=(--encrypt) @@ -1347,6 +1437,19 @@ gen_key() { _verbose "gen_key takes tombpass from CLI argument: ::1 tomb pass::" $tombpass fi + # if sphinx mode is chosen, use the provided input + # as master password to generate the actual password + if [[ $sphx_host_tmp ]] || [[ $sphx_user_tmp ]]; then + OPTS[--sphx-user]=$sphx_user_tmp + OPTS[--sphx-host]=$sphx_host_tmp + unset sphx_user_tmp + unset sphx_host_tmp + tombpass=$(sphinx_set_password "$tombpass") + if [[ $? != 0 ]]; then + _failure "User aborted." + fi + fi + header="" [[ $KDF == 1 ]] && { { option_is_set --kdf } && { @@ -2993,19 +3096,19 @@ main() { main_opts=(q -quiet=q D -debug=D h -help=h v -version=v f -force=f -tmp: U: G: T: -no-color -unsafe g -gpgkey=g) subcommands_opts[__default]="" # -o in open and mount is used to pass alternate mount options - subcommands_opts[open]="n -nohook=n k: -kdf: o: -ignore-swap -tomb-pwd: r: R: p -preserve-ownership=p" + subcommands_opts[open]="n -nohook=n k: -kdf: o: -ignore-swap -tomb-pwd: r: R: -sphx-host: -sphx-user: p -preserve-ownership=p" subcommands_opts[mount]=${subcommands_opts[open]} subcommands_opts[create]="" # deprecated, will issue warning # -o in forge and lock is used to pass an alternate cipher. - subcommands_opts[forge]="-ignore-swap k: -kdf: o: -tomb-pwd: -use-urandom r: R: " + subcommands_opts[forge]="-ignore-swap k: -kdf: o: -tomb-pwd: -use-urandom r: R: -sphx-host: -sphx-user: " subcommands_opts[dig]="-ignore-swap s: -size=s " - subcommands_opts[lock]="-ignore-swap k: -kdf: o: -tomb-pwd: r: R: " - subcommands_opts[setkey]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: r: R: " + subcommands_opts[lock]="-ignore-swap k: -kdf: o: -tomb-pwd: r: R: -sphx-host: -sphx-user: " + subcommands_opts[setkey]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: r: R: -sphx-host: -sphx-user: " subcommands_opts[engrave]="k: " - subcommands_opts[passwd]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: r: R: " + subcommands_opts[passwd]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: r: R: -sphx-host: -sphx-user: " subcommands_opts[close]="" subcommands_opts[help]="" subcommands_opts[slam]="" @@ -3016,8 +3119,8 @@ main() { subcommands_opts[search]="" subcommands_opts[help]="" - subcommands_opts[bury]="k: -tomb-pwd: r: R: " - subcommands_opts[exhume]="k: -tomb-pwd: r: R: " + subcommands_opts[bury]="k: -tomb-pwd: r: R: -sphx-host: -sphx-user: " + subcommands_opts[exhume]="k: -tomb-pwd: r: R: -sphx-host: -sphx-user: " subcommands_opts[cloak]="k: " subcommands_opts[uncloak]="k: " # subcommands_opts[decompose]="" @@ -3025,7 +3128,7 @@ main() { # subcommands_opts[install]="" subcommands_opts[askpass]="" subcommands_opts[source]="" - subcommands_opts[resize]="-ignore-swap s: -size=s k: -tomb-pwd: r: R: " + subcommands_opts[resize]="-ignore-swap s: -size=s k: -tomb-pwd: r: R: -sphx-host: -sphx-user: " subcommands_opts[check]="-ignore-swap " # subcommands_opts[translate]=""