tomb

the crypto undertaker
git clone git://parazyd.org/tomb.git
Log | Files | Refs | README | LICENSE

commit e37982d1144356fe592e67d46add97b88297be1d
parent 1f022d10f1a1fab564ba960679ec1460535d2a4e
Author: Jaromil <jaromil@dyne.org>
Date:   Mon, 20 Feb 2017 22:09:48 +0100

Merge pull request #244 from roddhjav/gnupg-key-support

GnuPG Key Support
Diffstat:
Mdoc/tomb.1 | 43+++++++++++++++++++++++++++++++++++++------
Aextras/test/.gitignore | 1+
Aextras/test/gnupg/pubring.gpg | 0
Aextras/test/gnupg/secring.gpg | 0
Aextras/test/gnupg/trustdb.gpg | 0
Mextras/test/runtests | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mtomb | 370+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
7 files changed, 452 insertions(+), 106 deletions(-)

diff --git a/doc/tomb.1 b/doc/tomb.1 @@ -46,7 +46,8 @@ supported ciphers use \fI-v\fR. For additional protection against dictionary attacks on keys, the (experimental) \fI--kdf\fR option can be used when forging a key, making sure that the \fItomb-kdb-pbkdf2\fR binaries in \fIextras/kdf\fR were compiled and installed on the -system. +system. Use the \fI-r\fR option to encrypt the key with a GPG key +instead of a password. .B .IP "lock" @@ -60,7 +61,8 @@ option can be used to specify the cipher specification: default is If you are looking for something exotic, also try "serpent-xts-plain64". More options may be found in cryptsetup(8) and Linux documentation. This operation requires root privileges to loopback mount, format the tomb (using -LUKS and Ext4), then set the key in its first LUKS slot. +LUKS and Ext4), then set the key in its first LUKS slot. Use the \fI-r\fR +option to lock the tomb using a GPG key. .B .IP "open" @@ -70,7 +72,8 @@ which can also be an \fIjpeg image\fR (see indicate the \fImountpoint\fR where the tomb should be made accessible, else the tomb is mounted in a directory inside /media (if not available it uses /run/media/$USER). The option \fI-o\fR can be -used to pass mount(8) options (default: rw,noatime,nodev). +used to pass mount(8) options (default: rw,noatime,nodev). Use the +\fI-r\fR option to open the tomb using a GPG key. .B .IP "list" @@ -123,7 +126,8 @@ Changes the password protecting a key file specified using its content will be decoded and reencoded using the new one. This action can't be forced if the current password is not known. If the key file is broken (missing headers) this function also attempts its -recovery. +recovery. Use the \fI-r\fR option to unlock the tomb using your old +GPG key and the \fI-R\fR option to provide the new GPG key. .B .IP "setkey" @@ -131,7 +135,8 @@ Changes the key file that locks a tomb, substituting the old one with a new one. Both the old and the new key files are needed for this operation and their passwords must be known. The new key must be specified using the \fI-k\fR option, the first argument should be the old -key and the second and last argument the tomb file. +key and the second and last argument the tomb file. Use the \fI-r\fR +option to unlock the tomb with a GPG key. .B .IP "resize" @@ -158,7 +163,8 @@ Hides a tomb key (\fI-k\fR) inside a \fIjpeg image\fR (first argument) using \fIsteganography\fR: the image will change in a way that cannot be noticed by human eye and hardly detected by data analysis. This option is useful to backup tomb keys in unsuspected places; it depends -from the availability of \fIsteghide\fR. +from the availability of \fIsteghide\fR. Use the \fI-r\fR +option to unlock the tomb with a GPG key. .B .IP "exhume" @@ -200,6 +206,21 @@ what you are doing if you force an operation. When digging or resizing a tomb, this option must be used to specify the \fIsize\fR of the new file to be created. Units are megabytes (MiB). .B +.IP "-r \fI<gpg_id>[,<gpg_id2>]\fR" +Tell tomb to use a asymmetric GnuPG key instead of a passphrase to +encrypt a tomb key. \fIgpg_id\fR is the key recipient in your GPG +database, you must hold both the public and the private key. If more +than one recipient is present the --shared flag must be present. +The recipients are separed by a ','. +.B +.IP "-R \fI<gpg_id>[,<gpg_id2>]\fR" +Provide a new set of recipient to encrypt a tomb key. This option is +only used in the \fIpasswd\fR command. +.B +.IP "--shared" +Activate the capability to share a tomb. This flag must be enabled +when using the \fI-r\fR option with more than one recipient. +.B .IP "--kdf \fI<itertime>\fR" Activate the KDF feature against dictionary attacks when creating a key: forces a delay of \fI<itertime>\fR times every time this key is @@ -357,6 +378,16 @@ eval $(gpg-agent --daemon --write-env-file "${HOME}/.gpg-agent-info") In the future it may become mandatory to run gpg-agent when using tomb. +.SH SHARE A TOMB +A tomb key can be encrypted with more than one recipient. Therefore, +a tomb can be shared between different user. The multiple recipients +are given using the \fI-r\fR (or/and \fI-R\fR) option and must be +separated by a coma: \fI,\fR. It is a very sensitive action, and the user +needs to trust all the GPG public keys it is going to share its tomb. +This is why this feature needs to be explicitly activated using in +more the flag \fI--shared\fR. The \fI--shared\fR option can be used +in the tomb commands: \fIforge\fR \fIsetkey\fR and \fIpasswd\fR. + .SH EXAMPLES .IP \(bu diff --git a/extras/test/.gitignore b/extras/test/.gitignore @@ -0,0 +1 @@ +gnupg/ diff --git a/extras/test/gnupg/pubring.gpg b/extras/test/gnupg/pubring.gpg Binary files differ. diff --git a/extras/test/gnupg/secring.gpg b/extras/test/gnupg/secring.gpg Binary files differ. diff --git a/extras/test/gnupg/trustdb.gpg b/extras/test/gnupg/trustdb.gpg Binary files differ. diff --git a/extras/test/runtests b/extras/test/runtests @@ -56,12 +56,24 @@ command -v qrencode > /dev/null || QRENCODE=0 typeset -A results -tests=(dig forge lock badpass open close openro passwd chksum bind setkey) + +tests=(dig forge lock badpass open close passwd chksum bind setkey recip-dig + recip-forge recip-lock recip-open recip-close recip-passwd recip-resize + recip-setkey shared shared-passwd shared-setkey) + { test $RESIZER = 1 } && { tests+=(resize) } { test $KDF = 1 } && { tests+=(kdforge kdfpass kdflock kdfopen) } -{ test $STEGHIDE = 1 } && { tests+=(stgin stgout stgopen stgpipe stgimpl) } +{ test $STEGHIDE = 1 } && { tests+=(stgin stgout stgopen stgpipe stgimpl +recip-stgin recip-stgout recip-stgopen recip-stgimpl) } { test $QRENCODE = 1 } && { tests+=(qrenc) } +# GnuPG Conf. +# Note: the assumption is the test keys are unencrypted. +export GNUPGHOME="gnupg/" +chmod 700 "$GNUPGHOME" +gpgid_1="A4857CD176B31435F9709D25F0E573B8289439CD" +gpgid_2="0B2235E660753AB0475FB3E23DC836481F44B31E" + notice "Loading test suite" # functions that can be called singularly @@ -100,6 +112,124 @@ test-tomb-create() { { test $? = 0 } && { results+=(lock SUCCESS) } } +test-tomb-recip() { + + notice "wiping all recip.tomb* in /tmp" + local tomb=/tmp/recip.tomb + local tomb_key=/tmp/recip.tomb.key + sudo rm -f "$tomb" "$tomb_key" + + notice "Testing tomb with recipient creation: dig" + tt dig -s 20 $tomb + { test $? = 0 } && { results+=(recip-dig SUCCESS) } + + notice "Testing tomb with recipient creation: forge" + tt forge $tomb_key -g -r $gpgid_1 --ignore-swap --unsafe --use-urandom + { test $? = 0 } && { results+=(recip-forge SUCCESS) } + + notice "Testing tomb with recipient creation: lock" + tt lock $tomb -k $tomb_key -g -r $gpgid_1 --ignore-swap --unsafe + { test $? = 0 } && { results+=(recip-lock SUCCESS) } + + notice "Testing tomb with recipient opening: open" + tt open $tomb -k $tomb_key -g + { test $? = 0 } && { results+=(recip-open SUCCESS) } + + notice "Testing tomb with recipient closing: close" + tt close recip + { test $? = 0 } && { results+=(recip-close SUCCESS) } + + { test $STEGHIDE = 1 } && { + notice "Testing tomb with recipient steganographic hiding of keys" + + cp -f arditi.jpg /tmp/recip.jpg + sudo rm -f /tmp/recip.steg.key + + tt --unsafe --tomb-pwd ${dummypass} bury -k /tmp/recip.tomb.key \ + /tmp/recip.jpg -g -r "$gpgid_1" + { test $? = 0 } && { results+=(recip-stgin SUCCESS) } + + tt --unsafe --tomb-pwd ${dummypass} exhume -k /tmp/recip.steg.key \ + /tmp/recip.jpg + { test $? = 0 } && { results+=(recip-stgout SUCCESS) } + + tt --unsafe --tomb-pwd ${dummypass} open -k /tmp/recip.steg.key \ + /tmp/recip.tomb -g + { test $? = 0 } && { results+=(recip-stgopen SUCCESS) } + ${T} close recip + + notice "test using open -k image.jpeg" + tt --unsafe --tomb-pwd ${dummypass} open -k /tmp/recip.jpg \ + /tmp/recip.tomb -g + { test $? = 0 } && { results+=(recip-stgimpl SUCCESS) } + tt close recip + } + + notice "Testing tomb with recipient changing gpg key: passwd" + res=0 + tt passwd -k $tomb_key -g -r $gpgid_2 + { test $? = 0 } || { res=1 } + tt open $tomb -k $tomb_key -g + { test $? = 0 } || { res=1 } + tt close recip + { test $? = 0 } || { res=1 } + { test $res = 0 } && { results+=(recip-passwd SUCCESS) } + + notice "Testing tomb with recipient resizing a tomb: resize" + tt resize -s 30 $tomb -k $tomb_key -g -r $gpgid_2 + { test $? = 0 } && { results+=(recip-resize SUCCESS) } + + notice "Testing tomb with recipient setting a new key: setkey" + sudo rm -f /tmp/new.recip.tomb.key + res=0 + tt forge /tmp/new.recip.tomb.key -g -r $gpgid_2 \ + --ignore-swap --unsafe --use-urandom + { test $? = 0 } || { res=1 } + tt setkey -k /tmp/new.recip.tomb.key $tomb_key $tomb -g -r $gpgid_2 + { test $? = 0 } || { res=1 } + tt open -k /tmp/new.recip.tomb.key $tomb -g + { test $? = 0 } || { res=1 } + { test $res = 0 } && { results+=(recip-setkey SUCCESS) } + tt close recip +} + +test-tomb-shared() { + + notice "wiping all shared.tomb* in /tmp" + rm -f /tmp/shared.tomb /tmp/shared.tomb.key + + notice "Testing sharing a tomb" + res=0 + tt dig -s 20 /tmp/shared.tomb + { test $? = 0 } || { res=1 } + tt forge /tmp/shared.tomb.key -g -r $gpgid_1,$gpgid_2 --shared \ + --ignore-swap --unsafe --use-urandom + { test $? = 0 } || { res=1 } + tt lock /tmp/shared.tomb -k /tmp/shared.tomb.key \ + --ignore-swap --unsafe -g -r $gpgid_1 + { test $? = 0 } || { res=1 } + tt open /tmp/shared.tomb -k /tmp/shared.tomb.key -g + { test $? = 0 } || { res=1 } + tt close shared + { test $? = 0 } || { res=1 } + { test $res = 0 } && { results+=(shared SUCCESS) } + + notice "Testing changing recipients on a shared Tomb" + tt passwd -k /tmp/shared.tomb.key -g -r $gpgid_2,$gpgid_1 --shared + { test $? = 0 } && { results+=(shared-passwd SUCCESS) } + + notice "Testing setkey on a shared Tomb" + rm -f /tmp/new.shared.tomb.key + res=0 + tt forge /tmp/new.shared.tomb.key -g -r $gpgid_1,$gpgid_2 --shared\ + --ignore-swap --unsafe --use-urandom + { test $? = 0 } || { res=1 } + tt setkey -k /tmp/new.shared.tomb.key /tmp/shared.tomb.key /tmp/shared.tomb \ + -g -r $gpgid_2,$gpgid_1 --shared + { test $? = 0 } || { res=1 } + { test $res = 0 } && { results+=(shared-setkey SUCCESS) } +} + test-bind-hooks() { notice "Testing bind hooks" @@ -233,6 +363,8 @@ startloops=(`sudo losetup -a |cut -d: -f1`) # isolated function (also called with source) test-tomb-create +test-tomb-recip +test-tomb-shared notice "Testing open with wrong password" @@ -414,11 +546,9 @@ notice "Test results summary" print "${#startloops} loop devices busy at start" for t in $tests; do - echo "$t\t${results[$t]:-FAIL}" -done - -for r in ${(v)results}; do - [[ "$r" == "SUCCESS" ]] || GLOBAL_RESULT=1 + res=${results[$t]:-FAIL} + [[ "$res" == "SUCCESS" ]] || GLOBAL_RESULT=1 + echo "$t\t$res" done print "${#endloops} loop devices busy at end" diff --git a/tomb b/tomb @@ -652,6 +652,9 @@ usage() { _print " -n don't process the hooks found in tomb" _print " -o options passed to commands: open, lock, forge (see man)" _print " -f force operation (i.e. even if swap is active)" + _print " -g use a GnuPG key to encrypt a tomb key" + _print " -r provide GnuPG recipients (separated by coma)" + _print " --shared active sharing feature" [[ $KDF == 1 ]] && { _print " --kdf forge keys armored against dictionary attacks" } @@ -828,6 +831,54 @@ _ensure_dependencies() { # {{{ Key operations +# $@ is the list of all the recipient used to encrypt a tomb key +is_valid_recipients() { + typeset -a recipients + recipients=($@) + + _verbose "is_valid_recipients" + + # All the keys ID must be valid (the public keys must be present in the database) + for gpg_id in ${recipients[@]}; do + gpg --list-keys "$gpg_id" &> /dev/null + [[ $? != 0 ]] && { + _warning "$gpg_id is not a valid key ID." + return 1 + } + done + + # At least one private key must be present + for gpg_id in ${recipients[@]}; do + gpg --list-secret-keys "$gpg_id" &> /dev/null + [[ $? = 0 ]] && { + return 0 + } + done + + return 1 +} + +# $@ is the list of all the recipient used to encrypt a tomb key +# Print the recipient arg to be used in gpg. +_recipients_arg() { + local arg="$1"; shift + typeset -a recipients + recipients=($@) + + for gpg_id in ${recipients[@]}; do + print -R -n "$arg $gpg_id " + done + return 0 +} + +# $1 is a GPG key recipient +# Print the fingerprint of the GPG key +_fingerprint() { + local recipient="$1" + gpg --with-colons --fingerprint "$recipient" | grep fpr | head -1 | cut -d ':' -f 10 | sed 's/.\{4\}/& /g' +} + + # $1 is the encrypted key contents we are checking is_valid_key() { local key="$1" # Unique argument is an encrypted key to test @@ -892,7 +943,12 @@ _load_key() { [[ -z $keyfile ]] && { _failure "This operation requires a key file to be specified using the -k option." } - if [[ $keyfile == "-" ]]; then + if option_is_set -g; then + _verbose "load_key key encrypted with a GnuPG Key" + _message "Key encrypted with a GnuPG Key" + TOMBKEYFILE=$keyfile + TOMBKEY="${mapfile[$TOMBKEYFILE]}" + elif [[ $keyfile == "-" ]]; then _verbose "load_key reading from stdin." _message "Waiting for the key to be piped from stdin... " TOMBKEYFILE=stdin @@ -932,18 +988,34 @@ _load_key() { # takes two args just like get_lukskey # prints out the decrypted content # contains tweaks for different gpg versions +# support both symmetric and asymmetric encryption gpg_decrypt() { # fix for gpg 1.4.11 where the --status-* options don't work ;^/ local gpgver=$(gpg --version --no-permission-warning | awk '/^gpg/ {print $3}') local gpgpass="$1\n$TOMBKEY" - local gpgstatus - local tmpres - + local tmpres ret + typeset -a gpgopt + gpgpopt=(--passphrase-fd 0) + + { option_is_set -g } && { + gpgpass="$TOMBKEY" + gpgpopt=() + + # GPG option '--try-secret-key' exist since GPG 2.1 + { option_is_set -r } && [[ $gpgver =~ "2.1." ]] && { + typeset -a recipients + recipients=(${(s:,:)$(option_value -r)}) + { ! is_valid_recipients $recipients } && { + _failure "You set an invalid GPG ID." + } + gpgpopt=(`_recipients_arg "--try-secret-key" $recipients`) + } + } + [[ $gpgver == "1.4.11" ]] && { _verbose "GnuPG is version 1.4.11 - adopting status fix." - TOMBSECRET=`print - "$gpgpass" | \ - gpg --batch --passphrase-fd 0 --no-tty --no-options` + gpg --batch ${gpgpopt[@]} --no-tty --no-options` ret=$? unset gpgpass return $ret @@ -952,12 +1024,10 @@ gpg_decrypt() { _tmp_create tmpres=$TOMBTMP TOMBSECRET=`print - "$gpgpass" | \ - gpg --batch --passphrase-fd 0 --no-tty --no-options \ + gpg --batch ${gpgpopt[@]} --no-tty --no-options \ --status-fd 2 --no-mdc-warning --no-permission-warning \ --no-secmem-warning 2> $tmpres` - unset gpgpass - ret=1 for i in ${(f)"$(cat $tmpres)"}; do _verbose "$i" @@ -1003,9 +1073,23 @@ get_lukskey() { # key needs to be exhumed from an image elif [[ -r $TOMBKEYFILE && $(file $TOMBKEYFILE) =~ "JP.G" ]]; then - - exhume_key $TOMBKEYFILE "$_password" - + if option_is_set -g; then + # When using a GPG key, the tomb key is buried using a steganography password + if option_is_set --tomb-pwd; then + _password="`option_value --tomb-pwd`" + _verbose "tomb-pwd = ::1 tomb pass::" $_password + else + _password=$(ask_password "Insert password to exhume key from $imagefile") + [[ $? != 0 ]] && { + _warning "User aborted password dialog." + return 1 + } + fi + exhume_key $TOMBKEYFILE "$_password" + unset _password + else + exhume_key $TOMBKEYFILE "$_password" + fi fi gpg_decrypt "$_password" # Save decrypted contents into $TOMBSECRET @@ -1027,6 +1111,12 @@ ask_key_password() { _verbose "no password needed, using secret bytes from stdin" return 0 } + if option_is_set -g; then + _verbose "no password needed, using GPG key" + get_lukskey + return $? + fi + _message "A password is required to use key ::1 key::" $TOMBKEYFILE passok=0 tombpass="" @@ -1083,7 +1173,11 @@ change_passwd() { _check_swap # Ensure swap is secure, if any _load_key # Try loading key from option -k and set TOMBKEYFILE - _message "Commanded to change password for tomb key ::1 key::" $TOMBKEYFILE + { option_is_set -g } && { + _message "Commanded to change GnuPG key for tomb key ::1 key::" $TOMBKEYFILE + } || { + _message "Commanded to change password for tomb key ::1 key::" $TOMBKEYFILE + } _tmp_create tmpnewkey=$TOMBTMP @@ -1097,7 +1191,11 @@ change_passwd() { fi [[ $? == 0 ]] || _failure "No valid password supplied." - _success "Changing password for ::1 key file::" $TOMBKEYFILE + { option_is_set -g } && { + _success "Changing GnuPG key for ::1 key file::" $TOMBKEYFILE + } || { + _success "Changing password for ::1 key file::" $TOMBKEYFILE + } # Here $TOMBSECRET contains the key material in clear @@ -1114,89 +1212,128 @@ change_passwd() { # Copy the new key as the original keyfile name cp -f "${tmpnewkey}" $TOMBKEYFILE - _success "Your passphrase was successfully updated." + { option_is_set -g } && { + _success "Your GnuPG key was successfully changed" + } || { + _success "Your passphrase was successfully updated." + } return 0 } # takes care to encrypt a key -# honored options: --kdf --tomb-pwd -o +# honored options: --kdf --tomb-pwd -o -g -r gen_key() { # $1 the password to use; if not set ask user # -o is the --cipher-algo to use (string taken by GnuPG) local algopt="`option_value -o`" local algo="${algopt:-AES256}" + local gpgpass opt + typeset -a gpgopt # here user is prompted for key password tombpass="" tombpasstmp="" - if [ "$1" = "" ]; then - while true; do - # 3 tries to write two times a matching password - tombpass=`ask_password "Type the new password to secure your key"` - if [[ $? != 0 ]]; then - _failure "User aborted." - fi - if [ -z $tombpass ]; then - _failure "You set empty password, which is not possible." - fi - tombpasstmp=$tombpass - tombpass=`ask_password "Type the new password to secure your key (again)"` - if [[ $? != 0 ]]; then - _failure "User aborted." - fi - if [ "$tombpasstmp" = "$tombpass" ]; then - break; - fi - unset tombpasstmp - unset tombpass - done - else - tombpass="$1" - _verbose "gen_key takes tombpass from CLI argument: ::1 tomb pass::" $tombpass - fi + { option_is_set -g } && { + { option_is_set -r } || { + _failure "A GPG recipient needs to be specified using -r." + } - header="" - [[ $KDF == 1 ]] && { - { option_is_set --kdf } && { - # KDF is a new key strenghtening technique against brute forcing - # see: https://github.com/dyne/Tomb/issues/82 - itertime="`option_value --kdf`" - # removing support of floating points because they can't be type checked well - if [[ "$itertime" != <-> ]]; then - unset tombpass - unset tombpasstmp - _warning "Wrong argument for --kdf: must be an integer number (iteration seconds)." - _failure "Depending on the speed of machines using this tomb, use 1 to 10, or more" - return 1 + typeset -a recipients + recipients=(${(s:,:)$(option_value -r)}) + [ "${#recipients}" -gt 1 ] && { + if option_is_set --shared; then + _warning "You are going to encrypt a tomb key with ${#recipients} recipients." + _warning "It is your responsibility to check the fingerprint of these recipients." + _warning "The fingerprints are:" + for gpg_id in ${recipients[@]}; do + _warning " `_fingerprint "$gpg_id"`" + done + else + _failure "You need to use the option '--shared' to enable sharing support" fi - # --kdf takes one parameter: iter time (on present machine) in seconds - local -i microseconds - microseconds=$(( itertime * 1000000 )) - _success "Using KDF, iteration time: ::1 microseconds::" $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}"` - - header="_KDF_pbkdf2sha1_${pbkdf2_salt}_${pbkdf2_iter}_64\n" } - } - + + { is_valid_recipients $recipients } || { + _failure "You set an invalid GPG ID." + } + + # Set gpg inputs and options + gpgpass="$TOMBSECRET" + gpgopt=(--encrypt `_recipients_arg "--hidden-recipient" $recipients`) + opt='' + } || { + if [ "$1" = "" ]; then + while true; do + # 3 tries to write two times a matching password + tombpass=`ask_password "Type the new password to secure your key"` + if [[ $? != 0 ]]; then + _failure "User aborted." + fi + if [ -z $tombpass ]; then + _failure "You set empty password, which is not possible." + fi + tombpasstmp=$tombpass + tombpass=`ask_password "Type the new password to secure your key (again)"` + if [[ $? != 0 ]]; then + _failure "User aborted." + fi + if [ "$tombpasstmp" = "$tombpass" ]; then + break; + fi + unset tombpasstmp + unset tombpass + done + else + tombpass="$1" + _verbose "gen_key takes tombpass from CLI argument: ::1 tomb pass::" $tombpass + fi - print $header + header="" + [[ $KDF == 1 ]] && { + { option_is_set --kdf } && { + # KDF is a new key strenghtening technique against brute forcing + # see: https://github.com/dyne/Tomb/issues/82 + itertime="`option_value --kdf`" + # removing support of floating points because they can't be type checked well + if [[ "$itertime" != <-> ]]; then + unset tombpass + unset tombpasstmp + _warning "Wrong argument for --kdf: must be an integer number (iteration seconds)." + _failure "Depending on the speed of machines using this tomb, use 1 to 10, or more" + return 1 + fi + # --kdf takes one parameter: iter time (on present machine) in seconds + local -i microseconds + microseconds=$(( itertime * 1000000 )) + _success "Using KDF, iteration time: ::1 microseconds::" $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}"` + + header="_KDF_pbkdf2sha1_${pbkdf2_salt}_${pbkdf2_iter}_64\n" + } + } + print $header - _tmp_create - local tmpres=$TOMBTMP + # Set gpg inputs and options + gpgpass="${tombpass}\n$TOMBSECRET" + gpgopt=(--passphrase-fd 0 --symmetric) + opt='-n' + } - print -n - "${tombpass}\n$TOMBSECRET" \ - | gpg --openpgp --force-mdc --cipher-algo ${algo} --batch \ - --no-options --no-tty --passphrase-fd 0 \ - --status-fd 2 -o - -c -a 2> $tmpres + _tmp_create + local tmpres=$TOMBTMP + print $opt - "$gpgpass" \ + | gpg --openpgp --force-mdc --cipher-algo ${algo} --batch \ + --no-options --no-tty ${gpgopt[@]} \ + --status-fd 2 -o - --armor 2> $tmpres + unset gpgpass # check result of gpg operation for i in ${(f)"$(cat $tmpres)"}; do _verbose "$i" @@ -1241,15 +1378,23 @@ bury_key() { } _success "Encoding key ::1 tomb key:: inside image ::2 image file::" $TOMBKEY $imagefile - _message "Please confirm the key password for the encoding" + { option_is_set -g } && { + _message "Using GnuPG Key ID" + } || { + _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 it isn't strictly necessary (and having different # password would enhance security). Nevertheless here we prefer # usability. + # However, steganography cannot be done with GPG key. Therefore, + # if using a GPG key, we test if the user can decrypt the tomb + # with its key and we ask for a steganography password. - { option_is_set --tomb-pwd } && { + { option_is_set --tomb-pwd } && { ! option_is_set -g } && { local tombpwd="`option_value --tomb-pwd`" _verbose "tomb-pwd = ::1 tomb pass::" $tombpwd ask_key_password "$tombpwd" @@ -1257,9 +1402,38 @@ bury_key() { ask_key_password } [[ $? != 0 ]] && { - _warning "Wrong password supplied." + _warning "Wrong password/GnuPG ID supplied." _failure "You shall not bury a key whose password is unknown to you." } + if option_is_set -g && option_is_set --tomb-pwd; then + TOMBPASSWORD="`option_value --tomb-pwd`" + _verbose "tomb-pwd = ::1 tomb pass::" $TOMBPASSWORD + elif option_is_set -g; then + tombpass="" + tombpasstmp="" + while true; do + # 3 tries to write two times a matching password + tombpass=`ask_password "Type a password to bury your key"` + if [[ $? != 0 ]]; then + _failure "User aborted." + fi + if [ -z $tombpass ]; then + _failure "You set empty password, which is not possible." + fi + tombpasstmp=$tombpass + tombpass=`ask_password "Type a password to bury your key (again)"` + if [[ $? != 0 ]]; then + _failure "User aborted." + fi + if [ "$tombpasstmp" = "$tombpass" ]; then + break; + fi + unset tombpasstmp + unset tombpass + done + TOMBPASSWORD="$tombpass" + fi + # We omit armor strings since having them as constants can give # ground to effective attacks on steganography print - "$TOMBKEY" | awk ' @@ -1452,11 +1626,13 @@ dig_tomb() { # Step two -- Create a detached key to lock a tomb with # -# Synopsis: forge_key [destkey|-k destkey] [-o cipher] +# Synopsis: forge_key [destkey|-k destkey] [-o cipher] [-r gpgid] [--shared] # # Arguments: # -k path to destination keyfile # -o Use an alternate algorithm +# -r GPG recipients to be used +# --shared Activate sharing capability # forge_key() { # can be specified both as simple argument or using -k @@ -1495,7 +1671,7 @@ forge_key() { _message "Commanded to forge key ::1 key:: with cipher algorithm ::2 algorithm::" \ $destkey $algo - [[ $KDF == 1 ]] && { + [[ $KDF == 1 ]] && { ! option_is_set -g } && { _message "Using KDF to protect the key password (`option_value --kdf` rounds)" } @@ -1518,7 +1694,15 @@ forge_key() { # Here the global variable TOMBSECRET contains the naked secret - _success "Choose the password of your key: ::1 tomb key::" $TOMBKEYFILE + { option_is_set -g } && { + { option_is_set --shared } && { + _success "Using GnuPG keys to encrypt and share your key: ::1 tomb key::" $TOMBKEYFILE + } || { + _success "Using the GnuPG key ::1:: to encrypt the key: ::2 tomb key::" `option_value -r` $TOMBKEYFILE + } + } || { + _success "Choose the password of your key: ::1 tomb key::" $TOMBKEYFILE + } _message "(You can also change it later using 'tomb passwd'.)" # _user_file $TOMBKEYFILE @@ -1553,7 +1737,7 @@ forge_key() { # Step three -- Lock tomb # -# Synopsis: tomb_lock file.tomb file.tomb.key [-o cipher] +# Synopsis: tomb_lock file.tomb file.tomb.key [-o cipher] [-r gpgid] # # Lock the given tomb with the given key file, in fact formatting the # loopback volume as a LUKS device. @@ -1794,7 +1978,7 @@ mount_tomb() { # take the name only, strip extensions _verbose "Tomb name: ::1 tomb name:: (to be engraved)" $TOMBNAME - { option_is_set --tomb-pwd } && { + { option_is_set --tomb-pwd } && { ! option_is_set -g } && { tomb_pwd="`option_value --tomb-pwd`" _verbose "tomb-pwd = ::1 tomb pass::" $tomb_pwd ask_key_password "$tomb_pwd" @@ -2563,22 +2747,22 @@ main() { # can only use the non-abbreviated long-option version like: # -force and NOT -f # - main_opts=(q -quiet=q D -debug=D h -help=h v -version=v f -force=f -tmp: U: G: T: -no-color -unsafe) + 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: " + subcommands_opts[open]="n -nohook=n k: -kdf: o: -ignore-swap -tomb-pwd: r: " 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 " + subcommands_opts[forge]="-ignore-swap k: -kdf: o: -tomb-pwd: -use-urandom r: -shared " subcommands_opts[dig]="-ignore-swap s: -size=s " - subcommands_opts[lock]="-ignore-swap k: -kdf: o: -tomb-pwd: " - subcommands_opts[setkey]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: " + subcommands_opts[lock]="-ignore-swap k: -kdf: o: -tomb-pwd: r: " + subcommands_opts[setkey]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: r: -shared " subcommands_opts[engrave]="k: " - subcommands_opts[passwd]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: " + subcommands_opts[passwd]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: r: -shared " subcommands_opts[close]="" subcommands_opts[help]="" subcommands_opts[slam]="" @@ -2588,14 +2772,14 @@ main() { subcommands_opts[search]="" subcommands_opts[help]="" - subcommands_opts[bury]="k: -tomb-pwd: " - subcommands_opts[exhume]="k: -tomb-pwd: " + subcommands_opts[bury]="k: -tomb-pwd: r: " + subcommands_opts[exhume]="k: -tomb-pwd: r: " # subcommands_opts[decompose]="" # subcommands_opts[recompose]="" # subcommands_opts[install]="" subcommands_opts[askpass]="" subcommands_opts[source]="" - subcommands_opts[resize]="-ignore-swap s: -size=s k: -tomb-pwd: " + subcommands_opts[resize]="-ignore-swap s: -size=s k: -tomb-pwd: r: " subcommands_opts[check]="-ignore-swap " # subcommands_opts[translate]=""