Pitchforked sphinx integration for remote password storage

This commit is contained in:
heat-wave 2019-03-03 16:56:26 +00:00 committed by Artur Malimonov
parent a0c74985ca
commit b054a83ee5
13 changed files with 230 additions and 32 deletions

View File

@ -1,26 +1,12 @@
dist: trusty
sudo: required sudo: required
language: c language: c
addons: services:
apt: - docker
packages:
- zsh
- gnupg
- cryptsetup
- pinentry-curses
- gawk
- libgcrypt20-dev
- steghide
- qrencode
- e2fsprogs
- python2.7
before_script: before_script:
- make --directory=extras/kdf-keys - docker build -t dyne/tomb .
- sudo make --directory=extras/kdf-keys install - docker run -it --privileged dyne/tomb /bin/bash -c "oracle & make test"
- sudo ./extras/install_cloakify.sh
script: script:
- make --directory=extras/kdf-keys test - docker run -it --privileged dyne/tomb /bin/bash -c "make --directory=extras/kdf-keys test"
- make test

14
Dockerfile Normal file
View File

@ -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

View File

@ -4,8 +4,8 @@ wget https://github.com/TryCatchHCF/Cloakify/archive/v1.0.3.tar.gz -O /tmp/cloak
mkdir -p extras/cloakify mkdir -p extras/cloakify
tar -xvf /tmp/cloakify.tar.gz --strip-components=1 -C $PWD/extras/cloakify tar -xvf /tmp/cloakify.tar.gz --strip-components=1 -C $PWD/extras/cloakify
echo "#!/bin/sh echo "#!/bin/sh
python2 $PWD/extras/cloakify/cloakify.py" > /usr/bin/cloakify python2 $PWD/extras/cloakify/cloakify.py $@" > /usr/bin/cloakify
echo "#!/bin/sh 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/cloakify
chmod +x /usr/bin/decloakify chmod +x /usr/bin/decloakify

12
extras/install_sphinx.sh Executable file
View File

@ -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

View File

@ -15,4 +15,16 @@ test_expect_success 'Testing tomb creation: dig, forge and lock' '
tt_lock --tomb-pwd $DUMMYPASS 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 test_done

View File

@ -44,4 +44,13 @@ if test_have_prereq LSOF; then
' '
fi 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 test_done

View File

@ -16,4 +16,11 @@ if test_have_prereq RESIZER; then
' '
fi 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 test_done

View File

@ -17,4 +17,16 @@ test_expect_success 'Testing tomb with GnuPG keys: passwd' '
tt passwd -k $tomb_key -g -r $KEY2 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 test_done

View File

@ -2,6 +2,8 @@
export test_description="Testing tomb bind hooks" export test_description="Testing tomb bind hooks"
TEMPHOME=$HOME
source ./setup source ./setup
test_export "test" # Using already generated tomb test_export "test" # Using already generated tomb
@ -14,9 +16,9 @@ test_expect_success 'Testing bind hooks' '
rm -f "$MEDIA/$testname/bind-hooks" && rm -f "$MEDIA/$testname/bind-hooks" &&
echo "$bindtest $bindtest" > "$MEDIA/$testname/bind-hooks" && echo "$bindtest $bindtest" > "$MEDIA/$testname/bind-hooks" &&
tt_close && tt_close &&
touch "/home/$USER/$bindtest" && touch "$TEMPHOME/$bindtest" &&
tt_open --tomb-pwd $DUMMYPASS && tt_open --tomb-pwd $DUMMYPASS &&
RND2=$(cat "/home/$USER/$bindtest") && RND2=$(cat "$TEMPHOME/$bindtest") &&
[[ "$RND" = "$RND2" ]] && [[ "$RND" = "$RND2" ]] &&
tt list $testname && tt list $testname &&
tt_close tt_close

View File

@ -26,4 +26,23 @@ test_expect_success 'Testing tomb with GnuPG keys: setkey' '
tt_close 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 test_done

View File

@ -55,6 +55,8 @@ command -v lsof > /dev/null && test_set_prereq LSOF
command -v python2 > /dev/null && test_set_prereq PYTHON2 command -v python2 > /dev/null && test_set_prereq PYTHON2
command -v cloakify > /dev/null && test_set_prereq CLOAKIFY command -v cloakify > /dev/null && test_set_prereq CLOAKIFY
command -v decloakify > /dev/null && test_set_prereq DECLOAKIFY 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 # GnuPG config
@ -73,6 +75,10 @@ chmod 700 "$GNUPGHOME"
export DUMMYPASS=test export DUMMYPASS=test
export DUMMYPASSNEW=changetest export DUMMYPASSNEW=changetest
# Dummy host and username for sphinx
export DUMMYHOST=example.com
export DUMMYUSER=user
# Test helpers # Test helpers

16
extras/test/sphinx.cfg Normal file
View File

@ -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=

121
tomb
View File

@ -76,6 +76,7 @@ typeset -i KDF=1
typeset -i STEGHIDE=1 typeset -i STEGHIDE=1
typeset -i CLOAKIFY=1 typeset -i CLOAKIFY=1
typeset -i DECLOAKIFY=1 typeset -i DECLOAKIFY=1
typeset -i SPHINX=1
typeset -i RESIZER=1 typeset -i RESIZER=1
typeset -i SWISH=1 typeset -i SWISH=1
typeset -i QRENCODE=1 typeset -i QRENCODE=1
@ -501,6 +502,12 @@ ask_password() {
[[ "$i" =~ "^D .*" ]] && password="${i##D }"; [[ "$i" =~ "^D .*" ]] && password="${i##D }";
done 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" = "" ]] && { [[ "$password" = "" ]] && {
_warning "Empty password" _warning "Empty password"
print "empty" print "empty"
@ -511,6 +518,64 @@ ask_password() {
return 0 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 # 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 recipients (separated by comma)"
_print " -R provide GnuPG hidden 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 ]] && { [[ $KDF == 1 ]] && {
_print " --kdf forge keys armored against dictionary attacks" _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 command -v cloakify 1>/dev/null 2>/dev/null || CLOAKIFY=0
# Check for decloakify # Check for decloakify
command -v decloakify 1>/dev/null 2>/dev/null || DECLOAKIFY=0 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 # Check for resize
command -v resize2fs 1>/dev/null 2>/dev/null || RESIZER=0 command -v resize2fs 1>/dev/null 2>/dev/null || RESIZER=0
# Check for KDF auxiliary tools # Check for KDF auxiliary tools
@ -1196,6 +1268,12 @@ ask_key_password() {
tombpass="$1" tombpass="$1"
_verbose "ask_key_password with tombpass: ::1 tomb pass::" $tombpass _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" get_lukskey "$tombpass"
[[ $? = 0 ]] && { [[ $? = 0 ]] && {
@ -1212,7 +1290,7 @@ ask_key_password() {
# call cryptsetup with arguments using the currently known secret # call cryptsetup with arguments using the currently known secret
# echo flags eliminate newline and disable escape (BSD_ECHO) # echo flags eliminate newline and disable escape (BSD_ECHO)
_cryptsetup() { _cryptsetup() {
print -R -n - "$TOMBSECRET" | _sudo cryptsetup --key-file - ${@} print -R -n - "$TOMBSECRET" | _sudo cryptsetup --type luks1 --key-file - ${@}
return $? return $?
} }
@ -1283,10 +1361,22 @@ gen_key() {
local gpgpass opt local gpgpass opt
local recipients_opt local recipients_opt
typeset -a gpgopt typeset -a gpgopt
local sphx_host_tmp
local sphx_user_tmp
# here user is prompted for key password # here user is prompted for key password
tombpass="" tombpass=""
tombpasstmp="" 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 } && { { option_is_set -g } && {
gpgopt=(--encrypt) gpgopt=(--encrypt)
@ -1347,6 +1437,19 @@ gen_key() {
_verbose "gen_key takes tombpass from CLI argument: ::1 tomb pass::" $tombpass _verbose "gen_key takes tombpass from CLI argument: ::1 tomb pass::" $tombpass
fi 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="" header=""
[[ $KDF == 1 ]] && { [[ $KDF == 1 ]] && {
{ option_is_set --kdf } && { { 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) 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]="" subcommands_opts[__default]=""
# -o in open and mount is used to pass alternate mount options # -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[mount]=${subcommands_opts[open]}
subcommands_opts[create]="" # deprecated, will issue warning subcommands_opts[create]="" # deprecated, will issue warning
# -o in forge and lock is used to pass an alternate cipher. # -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[dig]="-ignore-swap s: -size=s "
subcommands_opts[lock]="-ignore-swap k: -kdf: o: -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: " subcommands_opts[setkey]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: r: R: -sphx-host: -sphx-user: "
subcommands_opts[engrave]="k: " 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[close]=""
subcommands_opts[help]="" subcommands_opts[help]=""
subcommands_opts[slam]="" subcommands_opts[slam]=""
@ -3016,8 +3119,8 @@ main() {
subcommands_opts[search]="" subcommands_opts[search]=""
subcommands_opts[help]="" subcommands_opts[help]=""
subcommands_opts[bury]="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: " subcommands_opts[exhume]="k: -tomb-pwd: r: R: -sphx-host: -sphx-user: "
subcommands_opts[cloak]="k: " subcommands_opts[cloak]="k: "
subcommands_opts[uncloak]="k: " subcommands_opts[uncloak]="k: "
# subcommands_opts[decompose]="" # subcommands_opts[decompose]=""
@ -3025,7 +3128,7 @@ main() {
# subcommands_opts[install]="" # subcommands_opts[install]=""
subcommands_opts[askpass]="" subcommands_opts[askpass]=""
subcommands_opts[source]="" 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[check]="-ignore-swap "
# subcommands_opts[translate]="" # subcommands_opts[translate]=""