coffin

secure lan file storage on a device
git clone git://parazyd.org/coffin.git
Log | Files | Refs | Submodules | README | LICENSE

commit 93560963b2413c846da85efd2596c8a69cff9f77
parent f7a9f52b2508eff9381a644bb13e87f9ed7b6f9c
Author: parazyd <parazyd@dyne.org>
Date:   Mon, 28 Mar 2016 21:13:58 +0200

added makefile, tomb source, doc update

Diffstat:
AMakefile | 10++++++++++
MREADME.md | 19+++++++++++++++----
Asrc/tomb/.gitignore | 11+++++++++++
Asrc/tomb/Makefile | 26++++++++++++++++++++++++++
Asrc/tomb/doc/Makefile.am | 6++++++
Asrc/tomb/doc/tomb.1 | 467+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tomb/kdf-keys/.gitignore | 4++++
Asrc/tomb/kdf-keys/Makefile | 18++++++++++++++++++
Asrc/tomb/kdf-keys/README | 27+++++++++++++++++++++++++++
Asrc/tomb/kdf-keys/benchmark.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tomb/kdf-keys/gen_salt.c | 39+++++++++++++++++++++++++++++++++++++++
Asrc/tomb/kdf-keys/hexencode.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tomb/kdf-keys/pbkdf2.c | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tomb/kdf-keys/test.sh | 22++++++++++++++++++++++
Asrc/tomb/kdf-keys/test.txt | 0
Asrc/tomb/tomb | 2834+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
16 files changed, 3731 insertions(+), 4 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,10 @@ +all: + make -C src/tomb/kdf-keys + +install: + make -C src/tomb install + make -C src/tomb/kdf-keys install + @./config.sh + +clean: + make -C src/tomb/kdf-keys clean diff --git a/README.md b/README.md @@ -11,15 +11,26 @@ Now, you are able to access your files on the LAN. Once you plug in your key again, that same tomb will be closed and your files are once again unreadable. +## Installation +Install the needed dependencies, some from the following list may vary +depending on the distro you are using: +``` +zsh cryptsetup libgcrypt20-dev apache2 wipe sshfs inotify-tools +pinentry-curses pwgen gettext haveged sudo +``` + +Clone this repository to the device you will be using. + +Run `make` in order to compile tomb's KDF modules. + +Run `make install` as root in order to install and configure your +device. It will install all the required dependencies needed as well. + ## Notes * `gmakehook` can be used to create coffin hooks in a more user-friendly manner. It is a GUI (zenity) helper script. ## Usage -## Installation -Clone this git repository to your device, and run -`make` followed by `make install` - ## Troubleshooting diff --git a/src/tomb/.gitignore b/src/tomb/.gitignore @@ -0,0 +1,11 @@ +\#* +.\#* +*~ +*.o +tomb-askpass +tomb-status +doc/web/public +doc/web/dyne +share/gtkrc +.*.sw? +*.mo diff --git a/src/tomb/Makefile b/src/tomb/Makefile @@ -0,0 +1,26 @@ +PROG = tomb +PREFIX ?= /usr/local +MANDIR ?= ${PREFIX}/share/man + +all: + @echo + @echo "Tomb is a script and does not need compilation, it can be simply executed." + @echo + @echo "To install it in /usr/local together with its manpage use 'make install'." + @echo + @echo "To run Tomb one needs to have some tools installed on the system:" + @echo "Sudo, cryptsetup, pinentry and gnupg. Also wipe is recommended." + @echo + +install: + install -Dm755 ${PROG} ${DESTDIR}${PREFIX}/bin/${PROG} + install -Dm644 doc/${PROG}.1 ${DESTDIR}${MANDIR}/man1/${PROG}.1 + @echo + @echo "Tomb is installed succesfully. To install language translations, make sure" + @echo "gettext is also installed, then 'cd extras/translations' and 'make install' there." + @echo + @echo "Look around the extras/ directory, it contains other interesting modules." + @echo + +test: + make -C extras/test diff --git a/src/tomb/doc/Makefile.am b/src/tomb/doc/Makefile.am @@ -0,0 +1,6 @@ + +man_MANS = tomb.1 tomb-open.1 tomb-status.1 + +EXTRA_DIST = tomb.1 tomb-open.1 tomb-status.1 Luks_on_disk_format.pdf \ + New_methods_in_HD_encryption.pdf TKS1-draft.pdf + diff --git a/src/tomb/doc/tomb.1 b/src/tomb/doc/tomb.1 @@ -0,0 +1,467 @@ +.TH tomb 1 "November 26, 2014" "tomb" + +.SH NAME +Tomb \- the Crypto Undertaker + +.SH SYNOPSIS +.B +.IP "tomb [options] command [arguments]" + +.SH DESCRIPTION + +Tomb is an application to manage the creation and access of encrypted +storage files: it can be operated from commandline and it can +integrate with a user's graphical desktop. + +Tomb generates encrypted storage files to be opened and closed using +their associated keys, which are also protected with a password chosen +by the user. To create, open and close tombs a user will need super +user rights to execute the tomb commandline utility. + +A tomb is like a locked folder that can be safely transported and +hidden in a filesystem; it encourages users to keep their keys +separate from tombs, for instance keeping a tomb file on your computer +harddisk and its key file on a USB stick. + + +.SH COMMANDS + +.B +.IP "dig" +Generates a file that can be used as a tomb and will occupy as much +space as its desired initial size, the unlocked \fI.tomb\fR file can +then be locked using a \fIkey\fR. It takes a mandatory \fI-s\fR option which is +the size in megabytes (MiB). Tombs are digged using +low-quality random data (/dev/urandom). + +.B +.IP "forge" +Creates a new \fIkey\fR and prompts the user for a \fIpassword\fR to +protect its usage. This operation requires high quality random data +(/dev/random) which can take quite some time to be gathered on a +server: it works better on a desktop where the mouse can be moved +around for entropy. The default cipher to protect the key is AES256, a +custom one can be specified using the \fI-o\fR option, for a list of +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. + +.B +.IP "lock" +Initializes and locks an empty tomb (made with \fIdig\fR) using a key +(made with \fIforge\fR), making it ready for usage. After this +operation, the tomb can only be opened in possession of the key and +knowing its password. As in any other command requiring a key, the +option \fI-k\fR should be used to specify a key file. The \fI-o\fR +option can be used to specify the cipher specification: default is +"aes-xts-plain64:sha256", old versions of Tomb used "aes-cbc-essiv:sha256". +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. + +.B +.IP "open" +Opens an existing \fI.tomb\fR (first argument) using a key (\fI-k\fR), +if a second argument is given it will 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). + +.B +.IP "list" +List all the tombs found open, including information about the time +they were opened and the hooks that they mounted. If the first +argument is present, then shows only the tomb named that way or +returns an error if it's not found. If the option +\fI--get-mountpoint\fR is used then print a simple list of currently +open tomb mountpoint paths. + +.B +.IP "index" +Creates or updates the search indexes of all tombs currently open: +enables use of the \fIsearch\fR command using simple word patterns on +file names. Indexes are created using mlocate's updatedb(8) and +swish-e(1) if they are found on the system. Indexes allow to search +very fast for filenames and contents inside a tomb, they are stored +inside it and are not accessible if the Tomb is closed. To avoid +indexing a specific tomb simply touch a \fI.noindex\fR file in it. + +.B +.IP "search" +Takes any string as argument and searches for them through all tombs +currently open and previously indexed using the \fIindex\fR command. +The search matches filenames if mlocate is installed and then also +file contents if swish++ is present on the system, results are listed +on the console. + +.B +.IP "close" +Closes a currently open tomb. If more tombs are open, the first +argument should be used to specify the name of the tomb to be closed, +or \fIall\fR to close all currently open tombs. This command fails if +the tomb is in use by running processes (to force close, see +\fIslam\fR below). + +.B +.IP "slam" +Closes a tomb like the command \fIclose\fR does, but it doesn't fail +even if the tomb is in use by other application processes: it looks +for and violently kills \-9 each of them. This command may +provoke unsaved data loss, but assists users to face surprise +situations. + + +.B +.IP "passwd" +Changes the password protecting a key file specified using +\fI-k\fR. The user will need to know the key's current password, then +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. + +.B +.IP "setkey" +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. + +.B +.IP "resize" +Increase the size of a tomb file to the amount specified by the +\fI-s\fR option, which is the new size in megabytes (MiB). Full access to the tomb using +a key (\fI-k\fR) and its password is required. Tombs can only grow and +can never be made smaller. This command makes use of the cryptsetup(8) +resize feature and the resize2fs command: its much more practical than +creating a new tomb and moving everything into it. + +.B +.IP "engrave" +This command transforms a tomb key into an image that can be printed +on paper and physically stored as backup, i.e. hidden in a book. It +Renders a QRCode of the tomb key, still protected by its password: a +PNG image (extension \fI.qr.png\fR) will be created in the current +directory and can be later printed (fits an A4 or Letter format). To +recover an engraved key one can use any QRCode reader on a smartphone: +save it into a file and then use that file as a key (\fI-k\fR). + +.B +.IP "bury" +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. + +.B +.IP "exhume" +This command recovers from jpeg images the keys that were previously +hidden into them using \fIbury\fR. Exhume requires a key filename +(\fI-k\fR) and a \fIjpeg image\fR file (first argument) known to be +containing a key. If the right key password is given, the key will be +exhumed. If the password is not known, it is very hard to verify if a +key is buried in any image or not. + +.SH OPTIONS +.B +.B +.IP "-k \fI<keyfile>\fR" +For all operations requiring a key, this option specifies the location +of the key file to use. Arguments can also be \fIjpeg image\fR files +where keys have been hidden using the \fIbury\fR command, or text +files retrieved from \fIengraved\fR QR codes. If the \fIkeyfile\fR +argument is "-" (dash), Tomb will read the key from stdin (blocking). +.B +.IP "-n" +Skip processing of post-hooks and bind-hooks if found inside the tomb. +See the \fIHOOKS\fR section in this manual for more information. +.B +.IP "-o" +Manually specify mount options to be used when opening a tomb instead +of the default \fIrw,noatime,nodev\fR, i.e. to mount a tomb read-only +(ro) to prevent any modification of its data. Can also be used to +change the symmetric encryption algorithm for keys during \fIforge\fR +operations (default \fIAES256\fR) or the LUKS encryption method during +\fIlock\fR operations (default \fIaes-xts-plain64:sha256\fR). +.B +.IP "-f" +Force flag, currently used to override swap checks, might be +overriding more wimpy behaviours in future, but make sure you know +what you are doing if you force an operation. +.B +.IP "-s \fI<MBytes>\fR" +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 "--kdf \fI<itertime>\fR" +Activate the KDF feature against dictionary attacks when creating a +key: forces a delay of \fI<itertime>\fR seconds every time this key is used. +You should keep in mind that the actual iteration count is calculated based on +the performance of the computer where you forge the key. +The argument must be an integer, so you cannot say \fI--kdf 0.3\fR for 300ms. +.B +.IP "-h" +Display a help text and quit. +.B +.IP "-v" +Display version and quit. +.B +.IP "-q" +Run more quietly +.B +.IP "-D" +Print more information while running, for debugging purposes + +.SH DEV MODE +.B +.IP "--no-color" +Suppress colors in console output (needed for string parsing by +wrappers). +.B +.IP "--unsafe" +Enable using dev-mode arguments, i.e. to pass passwords from +commandline options. This is mostly used needed for execution by +wrappers and testing suite. +.B +.IP "--use-urandom" +Use an inferior quality random source to improve the speed of key +generation at the cost of security (needed for the testing suite). +.B +.IP "--tomb-pwd <string>" +Use string as password when needed on tomb. +.B +.IP "--tomb-old-pwd <string>" +Use string as old password when needed in tomb commands requiring +multiple keys, like \fIpasswd\fR or \fIsetkey\fR. +.B +.IP "-U" +Switch to this user ID when dropping privileges. +.B +.IP "-G" +Switch to this group ID when dropping privileges. +.B +.IP "-T" +Switch to this TTY terminal when dropping privileges. + +.SH HOOKS + +Hooks are special files that can be placed inside the tomb and trigger +actions when it is opened and closed; there are two kinds of such +files: \fIbind-hooks\fR and \fIpost-hooks\fR can be placed in the +base root of the tomb. + +.B +.IP "bind-hooks" +This hook file consists of a simple two column list of files or +directories inside the tomb to be made directly accessible inside the +current user's home directory. Tomb will use the "mount \-o bind" +command to bind locations inside the tomb to locations found in $HOME +so in the first column are indicated paths relative to the tomb and in +the second column are indicated paths relative to $HOME contents, for +example: +.EX + mail mail + .gnupg .gnupg + .fmrc .fetchmailrc + .mozilla .mozilla +.EE + +.B +.IP "post-hooks" +This hook file gets executed as user by tomb right after opening it; +it should be a regular shell script, starting with a shebang. Tomb +executes this hook as user (dropping root privileges) and giving it +two arguments: "$1" is "open" or "close" depending from the tomb +command given, "$2" is the full path to the mountpoint where the tomb +is open. + +.SH PRIVILEGE ESCALATION + +The tomb commandline tool needs to acquire super user rights to +execute most of its operations: to do so it uses sudo(8), while +pinentry(1) is adopted to collect passwords from the user. Tomb +executes as super user only when required. + +To be made available on multi user systems, the superuser execution of +the tomb script can be authorized for users without jeopardizing the +whole system's security: just add such a line to \fI/etc/sudoers\fR: + +.EX + username ALL=NOPASSWD: /usr/local/bin/tomb +.EE + +Password input is handled by the pinentry program: it can be text +based or graphical and is usually configured with a symlink. When +using Tomb in X11 it is better to use a graphical pinentry-gtk2 or +pinentry-qt because it helps preventing keylogging by other X +clients. When using it from a remote ssh connection it might be +necessary to force use of pinentry-curses for instance by unsetting +the DISPLAY environment var. + + +.SH SWAP + +On execution of certain commands Tomb will complain about swap memory +on disk when present and \fIabort if your system has swap +activated\fR. You can disable this behaviour using the +\fI--force\fR. Before doing that, however, you may be interested in +knowing the risks of doing so: +.IP \(bu +During such operations a lack of available memory could cause the swap +to write your secret key on the disk. +.IP \(bu +Even while using an opened tomb, another application could occupy too +much memory so that the swap needs to be used, this way it is possible +that some contents of files contained into the tomb are physically +written on your disk, not encrypted. +.P + +If you don't need swap, execute \fI swapoff -a\fR. If you really need +it, you could make an encrypted swap partition. Tomb doesn't detect if +your swap is encrypted, and will complain anyway. + +.SH EXAMPLES + +.IP \(bu +Create a 128MB large "secret" tomb and its keys, then open it: + +.EX + tomb dig -s 128 secret.tomb + + tomb forge secret.tomb.key + + tomb lock secret.tomb -k secret.tomb.key + + tomb open secret.tomb -k secret.tomb.key +.EE + +.IP \(bu +Open a Tomb using the key from a remote SSH shell, without saving any +local copy of it: + +.EX + ssh user@my.shell.net 'cat .secrets/tomb.key' | tomb open secret.tomb -k - +.EE + +.IP \(bu +Open a Tomb on a remote server passing the unencrypted local key on stdin via SSH, +without saving any remote copy of it: + +.EX + gpg -d .secrets/tomb.key | ssh server tomb open secret.tomb -k cleartext --unsafe +.EE + +.IP \(bu +Create a bind hook that places your GnuPG folder inside the tomb, but +makes it reachable from the standard $HOME/.gnupg location every time +the tomb will be opened: + +.EX + tomb open GPG.tomb -k GPG.tomb.key + echo ".gnupg .gnupg" > /media/GPG.tomb/bind-hooks + mv ~/.gnupg /media/GPG.tomb/.gnupg && mkdir ~/.gnupg + tomb close GPG && tomb open GPG.tomb -k GPG.tomb.key +.EE + +.IP \(bu +Script a tomb to launch the Firefox browser every time is opened, +keeping all its profile data inside it: + +.EX + tomb open FOX.tomb -k FOX.tomb.key + cat <<EOF > /media/FOX.tomb/post-hooks +#!/bin/sh +if [ "$1" = "open" ]; then + firefox -no-remote -profile "$2"/firefox-pro & +fi +EOF + chmod +x /media/FOX.tomb/post-hooks +.EE + +.IP \(bu +Script a tomb to archive Pictures using Shotwell, launching it on open: + +.EX + tomb open Pictures.tomb -k Pictures.tomb.key + cat <<EOF > /media/Pictures.tomb/bind-hooks +Pictures Pictures +EOF + cat <<EOF > /media/Pictures.tomb/post-hooks +#!/bin/sh +if [ "$1" = "open" ]; then + which shotwell > /dev/null + if [ "$?" = "0" ]; then + shotwell -d "$2"/Pictures/.shotwell & + fi +fi +EOF + chmod +x /media/Pictures.tomb/post-hooks +.EE + +.SH BUGS +Please report bugs on the Github issue tracker at +.UR https://github.com/dyne/Tomb/issues +.UE + +One can also try to get in touch with developers via the #dyne chat channel on \fIhttps://irc.dyne.org\fR. + +.SH AUTHORS + +Tomb is designed, written and maintained by Denis Roio aka Jaromil. + +Tomb includes code by Anathema, Boyska, Hellekin O. Wolf and GDrooid. + +Tomb's artwork is contributed by Jordi aka Mon Mort and Logan VanCuren. + +Gettext internationalization and Spanish translation is contributed by +GDrooid, French translation by Hellekin, Russian translation by fsLeg, +German translation by x3nu. + +Testing, reviews and documentation are contributed by Dreamer, Shining +the Translucent, Mancausoft, Asbesto Molesto, Nignux, Vlax, The Grugq, +Reiven, GDrooid, Alphazo, Brian May, TheJH, fsLeg, JoelMon and the +Linux Action Show! + +Cryptsetup was developed by Christophe Saout and Clemens Fruhwirth. + +.SH COPYING + +This manual is Copyright (c) 2011-2015 by Denis Roio <\fIjaromil@dyne.org\fR> + +This manual includes contributions by Boyska and Hellekin O. Wolf. + +Permission is granted to copy, distribute and/or modify this manual +under the terms of the GNU Free Documentation License, Version 1.1 or +any later version published by the Free Software Foundation. +Permission is granted to make and distribute verbatim copies of this +manual page provided the above copyright notice and this permission +notice are preserved on all copies. + +.SH AVAILABILITY + +The most recent version of Tomb sourcecode and up to date +documentation is available for download from its website on +\fIhttps://tomb.dyne.org\fR. + +.SH SEE ALSO + +.B +.IP cryptsetup(8) + +GnuPG website: +.br +https://www.gnupg.org + +DM-Crypt website: +.br +https://gitlab.com/cryptsetup/cryptsetup/wikis/DMCrypt + +LUKS website: +.br +https://gitlab.com/cryptsetup/cryptsetup/wikis/home diff --git a/src/tomb/kdf-keys/.gitignore b/src/tomb/kdf-keys/.gitignore @@ -0,0 +1,4 @@ +tomb-kdf-pbkdf2 +tomb-kdf-pbkdf2-gensalt +tomb-kdf-pbkdf2-getiter +tomb-utils-hexencode diff --git a/src/tomb/kdf-keys/Makefile b/src/tomb/kdf-keys/Makefile @@ -0,0 +1,18 @@ + +PREFIX ?= /usr/local + +all: + $(CC) -O2 -o tomb-kdb-pbkdf2 pbkdf2.c -lgcrypt + $(CC) -O2 -o tomb-kdb-pbkdf2-getiter benchmark.c -lgcrypt + $(CC) -O2 -o tomb-kdb-pbkdf2-gensalt gen_salt.c -lgcrypt + $(CC) -O2 -o tomb-kdb-hexencode hexencode.c + +clean: + rm -f tomb-kdb-pbkdf2 tomb-kdb-pbkdf2-getiter tomb-kdb-pbkdf2-gensalt tomb-kdb-hexencode + +install: + install -Dm755 tomb-kdb-pbkdf2 ${DESTDIR}${PREFIX}/bin/tomb-kdb-pbkdf2 + install -Dm755 tomb-kdb-pbkdf2-getiter ${DESTDIR}${PREFIX}/bin/tomb-kdb-pbkdf2-getiter + install -Dm755 tomb-kdb-pbkdf2-gensalt ${DESTDIR}${PREFIX}/bin/tomb-kdb-pbkdf2-gensalt + install -Dm755 tomb-kdb-hexencode ${DESTDIR}${PREFIX}/bin/tomb-kdb-hexencode + @echo "Tomb-kdb auxiliary binaries installed in ${DESTDIR}${PREFIX}/bin" diff --git a/src/tomb/kdf-keys/README b/src/tomb/kdf-keys/README @@ -0,0 +1,27 @@ + +BUILD +------ + +Just type make. +You need a recent development version of libgcrypt installed. +On Debian 7 (not earlier) the libgcrypt11-dev package works: + # apt-get install libgcrypt11-dev + +PLANS +------ + +While this can be useful for general purpose, it specially fits tomb, and it's designed for easy integration and compilation. + +Binary name will then be: +tomb-kdb-${algo} +tomb-kdb-${algo}-gensalt +tomb-kdb-${algo}-getiter +tomb-kdb-hexencode + +Base64 vs hexencode +------------------- + +While base64 is easier to use (shell command, more compact), pbkdf2 use hex +in its specifications. +This could be solved with an option (-x for hex, defaults to base64) + diff --git a/src/tomb/kdf-keys/benchmark.c b/src/tomb/kdf-keys/benchmark.c @@ -0,0 +1,59 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sys/time.h> + +#include <gcrypt.h> + +static long bench(int ic) { + char *pass = "mypass"; + unsigned char *salt = "abcdefghijklmno"; + int salt_len = strlen(salt); + int result_len = 64; + unsigned char *result = calloc(result_len, sizeof(char)); + struct timeval start, end; + long microtime; + + gettimeofday(&start, NULL); + gcry_kdf_derive( pass, strlen(pass), GCRY_KDF_PBKDF2, GCRY_MD_SHA1, salt, salt_len, ic, result_len, result); + gettimeofday(&end, NULL); + microtime = 1000000*end.tv_sec+end.tv_usec - (1000000*start.tv_sec+start.tv_usec); + + return (long)microtime; +} +int main(int argc, char *argv[]) +{ + long desired_time = 1000000; + long microtime; + int ic=100; + int tries=0; + if(argc >= 2) + sscanf(argv[1], "%ld", &desired_time); + if (!gcry_check_version ("1.5.0")) { + fputs ("libgcrypt version mismatch\n", stderr); + exit (2); + } + /* Allocate a pool of 16k secure memory. This make the secure memory + available and also drops privileges where needed. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + /* It is now okay to let Libgcrypt complain when there was/is + a problem with the secure memory. */ + gcry_control (GCRYCTL_RESUME_SECMEM_WARN); + /* Tell Libgcrypt that initialization has completed. */ + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); + + + microtime = bench(ic); + while( abs(desired_time-microtime) > (desired_time/10) /*little difference */ + && tries++ <= 5) { + float ratio = (float)desired_time/microtime; + if(ratio > 1000) ratio=1000.0; + ic*=ratio; + if(ic<1) ic=1; + microtime = bench(ic); + } + printf("%d\n", ic); + return 0; + +} diff --git a/src/tomb/kdf-keys/gen_salt.c b/src/tomb/kdf-keys/gen_salt.c @@ -0,0 +1,39 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +void print_hex(unsigned char *buf, int len) +{ + int i; + + for(i=0;i<len;i++){ + printf("%02x", buf[i]); + } +} + +int main(int argc, char **argv) { + int len=32; + int res; + unsigned char *buf; + FILE *rand; + if(argc>=2) { + if(sscanf(argv[1], "%d", &len) != 1) { + fprintf(stderr, "Error: len must be an integer\n"); + return 1; + } + } + buf = calloc(len, sizeof(char)); + memset(buf, 9, len); + rand = fopen("/dev/random", "r"); + res = fread(buf, sizeof(char), len, rand); + if( res != len) { + fprintf(stderr, "Error reading /dev/random: %d != %d, \n", res, len); + fclose(rand); + free(buf); + return 2; + } + fclose(rand); + print_hex(buf, len); + free(buf); + return 0; +} diff --git a/src/tomb/kdf-keys/hexencode.c b/src/tomb/kdf-keys/hexencode.c @@ -0,0 +1,49 @@ +/* + * A simple utility that reads from stdin and output the hexencoding (on a single line) of the input + */ + +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> + +static int decode_mode = 0; +int main(int argc, char *argv[]) { + char c; + char buf[3]; + int read_bytes; + int opt; + static struct option long_options[] = + { + {"decode", no_argument, &decode_mode, 1}, + {"encode", no_argument, &decode_mode, 0}, + {0,0,0,0} + }; + int option_index = 0; + + while(1) { + option_index = 0; + opt = getopt_long(argc, argv, "", long_options, &option_index); + if(opt == -1) + break; + switch(opt) { + case 0: + break; + case '?': + return 127; + default: + abort(); + } + } + if(decode_mode == 0) { + while(( c = (char)getchar() ) != EOF) + printf("%02x", c); + return 0; + } else { + while( (read_bytes=fread(buf, sizeof(char), 2, stdin)) != 0) { + if(read_bytes == 1) buf[1]='\0'; + sscanf(buf, "%x", &c); + printf("%c", c); + } + return 0; + } +} diff --git a/src/tomb/kdf-keys/pbkdf2.c b/src/tomb/kdf-keys/pbkdf2.c @@ -0,0 +1,144 @@ +/* +** SYNOPSIS +** echo "passphrase" | pbkdf2 salt_hex count > 48_byte_hex_key_and_iv +** +** DESCRIPTION +** +** Make the "Password-Based Key Derivation Function v2" function found in +** the openssl library available to the command line, as it is not available +** for use from the "openssl" command. At the time of writing the "openssl" +** command only encrypts using the older, 'fast' pbkdf1.5 method. +** +** The 'salt_hex' is the salt to be used, as a hexadecimal string. Typically +** this is 8 bytes (64 bit), and is an assigned randomly during encryption. +** +** The 'count' is iteration count used to make the calculation of the key +** from the passphrase longer so as to take 1/2 to 2 seconds to generate. +** This complexity prevents slows down brute force attacks enormously. +** +** The output of the above is a 48 bytes in hexadeximal, which is typically +** used for 32 byte encryption key KEY and a 16 byte IV as needed by +** Crypt-AES-256 (or some other encryption method). +** +** NOTE: While the "openssl" command can accept a hex encoded 'key' and 'iv' +** it only does so on the command line, which is insecure. As such I +** recommend that the output only be used with API access to the "OpenSSL" +** cryptography libraries. +** +************* +** +** Anthony Thyssen 4 November 2009 A.Thyssen@griffith.edu.au +** +** Based on a test program "pkcs5.c" found on +** http://www.mail-archive.com/openssl-users@openssl.org +** which uses openssl to perform PBKDF2 (RFC2898) iteritive (slow) password +** hashing. +** +** Build +** gcc -o pbkdf2 pbkdf2.c -lcrypto +** +*/ +#include <stdio.h> +#include <string.h> + +#include <gcrypt.h> + +/* TODO: move print_hex and hex_to_binary to utils.h, with separate compiling */ +void print_hex(unsigned char *buf, int len) +{ + int i; + + for(i=0;i<len;i++) + printf("%02x", buf[i]); + printf("\n"); +} + +int hex_to_binary(unsigned char *buf, char *hex) +{ + int ret; + int count=0; + while(*hex) { + if( hex[1] ) { + ret = sscanf( hex, "%2x", (unsigned int*) buf++ ); + hex += 2; + } + else { + ret = sscanf( hex++, "%1x", (unsigned int*)buf++ ); + } + count++; + if( ret != 1) + return -1; + } + *buf = 0; // null terminate -- precaution + return count; +} + +int main(int argc, char *argv[]) +{ + char *pass = NULL; + unsigned char *salt; + int salt_len; // salt length in bytes + int ic=0; // iterative count + int result_len; + unsigned char *result; // result (binary - 32+16 chars) + int i; + + if ( argc != 4 ) { + fprintf(stderr, "usage: %s salt count len <passwd >binary_key_iv\n", argv[0]); + exit(10); + } + + //TODO: move to base64decode + salt=calloc(strlen(argv[1])/2+3, sizeof(char)); + salt_len=hex_to_binary(salt, argv[1]); + if( salt_len <= 0 ) { + fprintf(stderr, "Error: %s is not a valid salt (it must be a hexadecimal string)\n", argv[1]); + exit(1); + } + + if( sscanf(argv[2], "%d", &ic) == 0 || ic<=0) { + fprintf(stderr, "Error: count must be a positive integer\n"); + exit(1); + } + if( sscanf(argv[3], "%d", &result_len) == 0 || result_len<=0) { + fprintf(stderr, "Error: result_len must be a positive integer\n"); + exit(1); + } + + fscanf(stdin, "%ms", &pass); + if ( pass[strlen(pass)-1] == '\n' ) + pass[strlen(pass)-1] = '\0'; + + // PBKDF 2 + result = calloc(result_len, sizeof(unsigned char*)); + if (!gcry_check_version ("1.5.0")) { + fputs ("libgcrypt version mismatch\n", stderr); + exit (2); + } + /* Allocate a pool of 16k secure memory. This make the secure memory + available and also drops privileges where needed. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + /* It is now okay to let Libgcrypt complain when there was/is + a problem with the secure memory. */ + gcry_control (GCRYCTL_RESUME_SECMEM_WARN); + /* Tell Libgcrypt that initialization has completed. */ + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); + + gcry_kdf_derive( pass, strlen(pass), GCRY_KDF_PBKDF2, GCRY_MD_SHA1, salt, salt_len, ic, result_len, result); + print_hex(result, result_len); // Key + IV (as hex string) + + //clear and free everything + for(i=0; i<result_len;i++) + result[i]=0; + free(result); + for(i=0; i<strlen(pass); i++) //blank + pass[i]=0; + free(pass); + for(i=0; i<strlen(argv[1])/2+3; i++) //blank + salt[i]=0; + free(salt); + + return(0); +} + +/* vim: set noexpandtab ts=4 sw=4: */ diff --git a/src/tomb/kdf-keys/test.sh b/src/tomb/kdf-keys/test.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env zsh + +error=0 +while read line; do + pass=`cut -f1 <<<$line` + salt=`cut -f2 <<<$line` + iter=`cut -f3 <<<$line` + keylen=`cut -f4 <<<$line` + expected=`cut -f5 <<<$line` + hexsalt=`cut -f6 <<<$line` + #TODO: check! + derived=`./pbkdf2 $hexsalt $iter $keylen <<<$pass` + if [[ $derived != $expected ]]; then + echo ./pbkdf2 $hexsalt $iter $keylen "<<<$pass" + echo "Expected $expected, got $derived" >&2 + error=$((error + 1)) + fi +done < test.txt + +if [[ $error == 1 ]]; then + exit $error +fi diff --git a/src/tomb/kdf-keys/test.txt b/src/tomb/kdf-keys/test.txt Binary files differ. diff --git a/src/tomb/tomb b/src/tomb/tomb @@ -0,0 +1,2834 @@ +#!/bin/zsh +# +# Tomb, the Crypto Undertaker +# +# A commandline tool to easily operate encryption of secret data +# + +# {{{ License + +# Copyright (C) 2007-2016 Dyne.org Foundation +# +# Tomb is designed, written and maintained by Denis Roio <jaromil@dyne.org> +# +# With contributions by Anathema, Boyska, Hellekin O. Wolf and GDrooid +# +# Gettext internationalization and Spanish translation is contributed by +# GDrooid, French translation by Hellekin, Russian translation by fsLeg, +# German translation by x3nu. +# +# Testing, reviews and documentation are contributed by Dreamer, Shining +# the Translucent, Mancausoft, Asbesto Molesto, Nignux, Vlax, The Grugq, +# Reiven, GDrooid, Alphazo, Brian May, TheJH, fsLeg, JoelMon and the +# Linux Action Show! +# +# Tomb's artwork is contributed by Jordi aka Mon Mort and Logan VanCuren. +# +# Cryptsetup was developed by Christophe Saout and Clemens Fruhwirth. + +# This source code is free software; you can redistribute it and/or +# modify it under the terms of the GNU Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This source code 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. Please refer +# to the GNU Public License for more details. +# +# You should have received a copy of the GNU Public License along with +# this source code; if not, write to: Free Software Foundation, Inc., +# 675 Mass Ave, Cambridge, MA 02139, USA. + +# }}} - License + +# {{{ Global variables + +typeset VERSION="2.2" +typeset DATE="Dec/2015" +typeset TOMBEXEC=$0 +typeset TMPPREFIX=${TMPPREFIX:-/tmp} +# TODO: configure which tmp dir to use from a cli flag + +# Tomb is using some global variables set by the shell: +# TMPPREFIX, UID, GID, PATH, TTY, USERNAME +# You can grep 'global variable' to see where they are used. + +# Keep a reference of the original command line arguments +typeset -a OLDARGS +for arg in "${(@)argv}"; do OLDARGS+=("$arg"); done + +# Special command requirements +typeset -a DD WIPE PINENTRY +DD=(dd) +WIPE=(rm -f) +PINENTRY=(pinentry) + +# load zsh regex module +zmodload zsh/regex +zmodload zsh/mapfile +zmodload -F zsh/stat b:zstat + +# make sure variables aren't exported +unsetopt allexport + +# Flag optional commands if available (see _ensure_dependencies()) +typeset -i KDF=1 +typeset -i STEGHIDE=1 +typeset -i RESIZER=1 +typeset -i SWISH=1 +typeset -i QRENCODE=1 + +# Default mount options +typeset MOUNTOPTS="rw,noatime,nodev" + +# Makes glob matching case insensitive +unsetopt CASE_MATCH + +typeset -AH OPTS # Command line options (see main()) + +# Command context (see _whoami()) +typeset -H _USER # Running username +typeset -Hi _UID # Running user identifier +typeset -Hi _GID # Running user group identifier +typeset -H _TTY # Connected input terminal + +# Tomb context (see _plot()) +typeset -H TOMBPATH # Full path to the tomb +typeset -H TOMBDIR # Directory where the tomb is +typeset -H TOMBFILE # File name of the tomb +typeset -H TOMBNAME # Name of the tomb + +# Tomb secrets +typeset -H TOMBKEY # Encrypted key contents (see forge_key(), recover_key()) +typeset -H TOMBKEYFILE # Key file (ditto) +typeset -H TOMBSECRET # Raw deciphered key (see forge_key(), gpg_decrypt()) +typeset -H TOMBPASSWORD # Raw tomb passphrase (see gen_key(), ask_key_password()) +typeset -H TOMBTMP # Filename of secure temp just created (see _tmp_create()) + +typeset -aH TOMBTMPFILES # Keep track of temporary files +typeset -aH TOMBLOOPDEVS # Keep track of used loop devices + +# Make sure sbin is in PATH (man zshparam) +path+=( /sbin /usr/sbin ) + +# For gettext +export TEXTDOMAIN=tomb + +# }}} + +# {{{ Safety functions + +# Wrap sudo with a more visible message +_sudo() { + local sudo_eng="[sudo] Enter password for user ::1 user:: to gain superuser privileges" + local msg="$(gettext -s "$sudo_eng")" + msg=${(S)msg//::1*::/$USER} + sudo -p " +$msg + +" ${@} +} + +# Cleanup anything sensitive before exiting. +_endgame() { + + # Prepare some random material to overwrite vars + local rr="$RANDOM" + while [[ ${#rr} -lt 500 ]]; do + rr+="$RANDOM" + done + + # Ensure no information is left in unallocated memory + TOMBPATH="$rr"; unset TOMBPATH + TOMBDIR="$rr"; unset TOMBDIR + TOMBFILE="$rr"; unset TOMBFILE + TOMBNAME="$rr"; unset TOMBNAME + TOMBKEY="$rr"; unset TOMBKEY + TOMBKEYFILE="$rr"; unset TOMBKEYFILE + TOMBSECRET="$rr"; unset TOMBSECRET + TOMBPASSWORD="$rr"; unset TOMBPASSWORD + + # Clear temporary files + for f in $TOMBTMPFILES; do + ${=WIPE} "$f" + done + unset TOMBTMPFILES + + # Detach loop devices + for l in $TOMBLOOPDEVS; do + _sudo losetup -d "$l" + done + unset TOMBLOOPDEVS + +} + +# Trap functions for the _endgame event +TRAPINT() { _endgame INT } +TRAPEXIT() { _endgame EXIT } +TRAPHUP() { _endgame HUP } +TRAPQUIT() { _endgame QUIT } +TRAPABRT() { _endgame ABORT } +TRAPKILL() { _endgame KILL } +TRAPPIPE() { _endgame PIPE } +TRAPTERM() { _endgame TERM } +TRAPSTOP() { _endgame STOP } + +_cat() { local -a _arr; + # read file using mapfile, newline fix + _arr=("${(f@)${mapfile[${1}]%$’\n’}}"); print "$_arr" +} + +_is_found() { + # returns 0 if binary is found in path + [[ "$1" = "" ]] && return 1 + command -v "$1" 1>/dev/null 2>/dev/null + return $? +} + +# Identify the running user +# Set global variables _UID, _GID, _TTY, and _USER, either from the +# command line, -U, -G, -T, respectively, or from the environment. +# Also update USERNAME and HOME to maintain consistency. +_whoami() { + + # Set username from UID or environment + _USER=$SUDO_USER + [[ "$_USER" = "" ]] && { _USER=$USERNAME } + [[ "$_USER" = "" ]] && { _USER=$(id -u) } + [[ "$_USER" = "" ]] && { + _failure "Failing to identify the user who is calling us" } + + # Get GID from option -G or the environment + option_is_set -G \ + && _GID=$(option_value -G) || _GID=$(id -g $_USER) + + # Get UID from option -U or the environment + option_is_set -U \ + && _UID=$(option_value -U) || _UID=$(id -u $_USER) + + _verbose "Identified caller: ::1 username:: (::2 UID:::::3 GID::)" $_USER $_UID $_GID + + # Update USERNAME accordingly if possible + [[ $EUID == 0 && $_USER != $USERNAME ]] && { + _verbose "Updating USERNAME from '::1 USERNAME::' to '::2 _USER::')" $USERNAME $_USER + USERNAME=$_USER + } + + # Force HOME to _USER's HOME if necessary + local home=$(awk -F: "/^$_USER:/ { print \$6 }" /etc/passwd 2>/dev/null) + [[ $home == $HOME ]] || { + _verbose "Updating HOME to match user's: ::1 home:: (was ::2 HOME::)" \ + $home $HOME + HOME=$home } + + # Get connecting TTY from option -T or the environment + option_is_set -T && _TTY=$(option_value -T) + [[ -z $_TTY ]] && _TTY=$TTY + +} + +# Define sepulture's plot (setup tomb-related arguments) +# Synopsis: _plot /path/to/the.tomb +# Set TOMB{PATH,DIR,FILE,NAME} +_plot() { + + # We set global variables + typeset -g TOMBPATH TOMBDIR TOMBFILE TOMBNAME + + TOMBPATH="$1" + + TOMBDIR=$(dirname $TOMBPATH) + + TOMBFILE=$(basename $TOMBPATH) + + # The tomb name is TOMBFILE without an extension. + # It can start with dots: ..foo.tomb -> ..foo + TOMBNAME="${TOMBFILE%\.[^\.]*}" + [[ -z $TOMBNAME ]] && { + _failure "Tomb won't work without a TOMBNAME." } + +} + +# Provide a random filename in shared memory +_tmp_create() { + [[ -d "$TMPPREFIX" ]] || { + # we create the tempdir with the sticky bit on + _sudo mkdir -m 1777 "$TMPPREFIX" + [[ $? == 0 ]] || _failure "Fatal error creating the temporary directory: ::1 temp dir::" "$TMPPREFIX" + } + + # We're going to add one more $RANDOM for each time someone complains + # about this being too weak of a random. + tfile="${TMPPREFIX}/$RANDOM$RANDOM$RANDOM$RANDOM" # Temporary file + umask 066 + [[ $? == 0 ]] || { + _failure "Fatal error setting the permission umask for temporary files" } + + [[ -r "$tfile" ]] && { + _failure "Someone is messing up with us trying to hijack temporary files." } + + touch "$tfile" + [[ $? == 0 ]] || { + _failure "Fatal error creating a temporary file: ::1 temp file::" "$tfile" } + + _verbose "Created tempfile: ::1 temp file::" "$tfile" + TOMBTMP="$tfile" + TOMBTMPFILES+=("$tfile") + + return 0 +} + +# Check if a *block* device is encrypted +# Synopsis: _is_encrypted_block /path/to/block/device +# Return 0 if it is an encrypted block device +_is_encrypted_block() { + local b=$1 # Path to a block device + local s="" # lsblk option -s (if available) + + # Issue #163 + # lsblk --inverse appeared in util-linux 2.22 + # but --version is not consistent... + lsblk --help | grep -Fq -- --inverse + [[ $? -eq 0 ]] && s="--inverse" + + sudo lsblk $s -o type -n $b 2>/dev/null \ + | egrep -q '^crypt$' + + return $? +} + +# Check if swap is activated +# Return 0 if NO swap is used, 1 if swap is used. +# Return 1 if any of the swaps is not encrypted. +# Return 2 if swap(s) is(are) used, but ALL encrypted. +# Use _check_swap in functions. It will call this function and +# exit if unsafe swap is present. +_ensure_safe_swap() { + + local -i r=1 # Return code: 0 no swap, 1 unsafe swap, 2 encrypted + local -a swaps # List of swap partitions + local bone is_crypt + + swaps="$(awk '/^\// { print $1 }' /proc/swaps 2>/dev/null)" + [[ -z "$swaps" ]] && return 0 # No swap partition is active + + _message "An active swap partition is detected..." + for s in $=swaps; do + { _is_encrypted_block $s } && { r=2 } || { + # We're dealing with unencrypted stuff. + # Maybe it lives on an encrypted filesystem anyway. + # @todo: verify it's actually on an encrypted FS (see #163 and !189) + # Well, no: bail out. + r=1; break + } + done + + if [[ $r -eq 2 ]]; then + _success "All your swaps are belong to crypt. Good." + else + _warning "This poses a security risk." + _warning "You can deactivate all swap partitions using the command:" + _warning " swapoff -a" + _warning "[#163] I may not detect plain swaps on an encrypted volume." + _warning "But if you want to proceed like this, use the -f (force) flag." + fi + return $r + +} + +# Wrapper to allow encrypted swap and remind the user about possible +# data leaks to disk if swap is on, which shouldn't be ignored. It could +# be run once in main(), but as swap evolves, it's better to run it +# whenever swap may be needed. +# Exit if unencrypted swap is active on the system. +_check_swap() { + if ! option_is_set -f && ! option_is_set --ignore-swap; then + _ensure_safe_swap + case $? in + 0|2) # No, or encrypted swap + return 0 + ;; + *) # Unencrypted swap + _failure "Operation aborted." + ;; + esac + fi +} + +# Ask user for a password +# Wraps around the pinentry command, from the GnuPG project, as it +# provides better security and conveniently use the right toolkit. +ask_password() { + + local description="$1" + local title="${2:-Enter tomb password.}" + local output + local password + local gtkrc + local theme + + # Distributions have broken wrappers for pinentry: they do + # implement fallback, but they disrupt the output somehow. We are + # better off relying on less intermediaries, so we implement our + # own fallback mechanisms. Pinentry supported: curses, gtk-2, qt4 + # and x11. + + # make sure LANG is set, default to C + LANG=${LANG:-C} + + _verbose "asking password with tty=$TTY lc-ctype=$LANG" + + if [[ "$DISPLAY" = "" ]]; then + + if _is_found "pinentry-curses"; then + _verbose "using pinentry-curses" + output=`cat <<EOF | pinentry-curses +OPTION ttyname=$TTY +OPTION lc-ctype=$LANG +SETTITLE $title +SETDESC $description +SETPROMPT Password: +GETPIN +EOF` + else + _failure "Cannot find pinentry-curses and no DISPLAY detected." + fi + + else # a DISPLAY is found to be active + + # customized gtk2 dialog with a skull (if extras are installed) + if _is_found "pinentry-gtk-2"; then + _verbose "using pinentry-gtk2" + + gtkrc="" + theme=/share/themes/tomb/gtk-2.0-key/gtkrc + for i in /usr/local /usr; do + [[ -r $i/$theme ]] && { + gtkrc="$i/$theme" + break + } + done + [[ "$gtkrc" = "" ]] || { + gtkrc_old="$GTK2_RC_FILES" + export GTK2_RC_FILES="$gtkrc" + } + output=`cat <<EOF | pinentry-gtk-2 +OPTION ttyname=$TTY +OPTION lc-ctype=$LANG +SETTITLE $title +SETDESC $description +SETPROMPT Password: +GETPIN +EOF` + [[ "$gtkrc" = "" ]] || export GTK2_RC_FILES="$gtkrc_old" + + # TODO QT4 customization of dialog + elif _is_found "pinentry-qt4"; then + _verbose "using pinentry-qt4" + + output=`cat <<EOF | pinentry-qt4 +OPTION ttyname=$TTY +OPTION lc-ctype=$LANG +SETTITLE $title +SETDESC $description +SETPROMPT Password: +GETPIN +EOF` + + # TODO X11 customization of dialog + elif _is_found "pinentry-x11"; then + _verbose "using pinentry-x11" + + output=`cat <<EOF | pinentry-x11 +OPTION ttyname=$TTY +OPTION lc-ctype=$LANG +SETTITLE $title +SETDESC $description +SETPROMPT Password: +GETPIN +EOF` + + else + + if _is_found "pinentry-curses"; then + _verbose "using pinentry-curses" + + _warning "Detected DISPLAY, but only pinentry-curses is found." + output=`cat <<EOF | pinentry-curses +OPTION ttyname=$TTY +OPTION lc-ctype=$LANG +SETTITLE $title +SETDESC $description +SETPROMPT Password: +GETPIN +EOF` + else + _failure "Cannot find any pinentry: impossible to ask for password." + fi + + fi + + fi # end of DISPLAY block + + # parse the pinentry output + for i in ${(f)output}; do + [[ "$i" =~ "^ERR.*" ]] && { + _warning "Pinentry error: ::1 error::" ${i[(w)3]} + print "canceled" + return 1 } + + # here the password is found + [[ "$i" =~ "^D .*" ]] && password="${i##D }" + done + + [[ "$password" = "" ]] && { + _warning "Empty password" + print "empty" + return 1 } + + print "$password" + return 0 +} + + + +# Check if a filename is a valid tomb +is_valid_tomb() { + _verbose "is_valid_tomb ::1 tomb file::" $1 + + # First argument must be the path to a tomb + [[ -z "$1" ]] && { + _failure "Tomb file is missing from arguments." } + + _fail=0 + # Tomb file must be a readable, writable, non-empty regular file. + [[ ! -w "$1" ]] && { + _warning "Tomb file is not writable: ::1 tomb file::" $1 + _fail=1 + } + [[ ! -f "$1" ]] && { + _warning "Tomb file is not a regular file: ::1 tomb file::" $1 + _fail=1 + } + [[ ! -s "$1" ]] && { + _warning "Tomb file is empty (zero length): ::1 tomb file::" $1 + _fail=1 + } + + _uid="`zstat +uid $1`" + [[ "$_uid" = "$UID" ]] || { + _user="`zstat -s +uid $1`" + _warning "Tomb file is owned by another user: ::1 tomb owner::" $_user + } + [[ $_fail = 1 ]] && { + _failure "Tomb command failed: ::1 command name::" $subcommand + } + + # TODO: split the rest of that function out. + # We already have a valid tomb, now we're checking + # whether we can alter it. + + # Tomb file may be a LUKS FS (or we are creating it) + [[ "`file $1`" =~ "luks encrypted file" ]] || { + _warning "File is not yet a tomb: ::1 tomb file::" $1 } + + _plot $1 # Set TOMB{PATH,DIR,FILE,NAME} + + # Tomb already mounted (or we cannot alter it) + [[ "`mount -l`" -regex-match "${TOMBFILE}.*\[$TOMBNAME\]$" ]] && { + _failure "Tomb is currently in use: ::1 tomb name::" $TOMBNAME + } + + _message "Valid tomb file found: ::1 tomb path::" $TOMBPATH + + return 0 +} + +# $1 is the tomb file to be lomounted +lo_mount() { + tpath="$1" + + # check if we have support for loop mounting + _nstloop=`_sudo losetup -f` + [[ $? = 0 ]] || { + _warning "Loop mount of volumes is not possible on this machine, this error" + _warning "often occurs on VPS and kernels that don't provide the loop module." + _warning "It is impossible to use Tomb on this machine at this conditions." + _failure "Operation aborted." + } + + _sudo losetup -f "$tpath" # allocates the next loopback for our file + + TOMBLOOPDEVS+=("$_nstloop") # add to array of lodevs used + + return 0 +} + +# print out latest loopback mounted +lo_new() { print - "${TOMBLOOPDEVS[${#TOMBLOOPDEVS}]}" } + +# $1 is the path to the lodev to be preserved after quit +lo_preserve() { + _verbose "lo_preserve on ::1 path::" $1 + # remove the lodev from the tomb_lodevs array + TOMBLOOPDEVS=("${(@)TOMBLOOPDEVS:#$1}") +} + +# eventually used for debugging +dump_secrets() { + print "TOMBPATH: $TOMBPATH" + print "TOMBNAME: $TOMBNAME" + + print "TOMBKEY len: ${#TOMBKEY}" + print "TOMBKEYFILE: $TOMBKEYFILE" + print "TOMBSECRET len: ${#TOMBSECRET}" + print "TOMBPASSWORD: $TOMBPASSWORD" + + print "TOMBTMPFILES: ${(@)TOMBTMPFILES}" + print "TOMBLOOPDEVS: ${(@)TOMBLOOPDEVS}" +} + +# }}} + +# {{{ Commandline interaction + +usage() { + _print "Syntax: tomb [options] command [arguments]" + _print "\000" + _print "Commands:" + _print "\000" + _print " // Creation:" + _print " dig create a new empty TOMB file of size -s in MiB" + _print " forge create a new KEY file and set its password" + _print " lock installs a lock on a TOMB to use it with KEY" + _print "\000" + _print " // Operations on tombs:" + _print " open open an existing TOMB (-k KEY file or - for stdin)" + _print " index update the search indexes of tombs" + _print " search looks for filenames matching text patterns" + _print " list list of open TOMBs and information on them" + _print " close close a specific TOMB (or 'all')" + _print " slam slam a TOMB killing all programs using it" + [[ $RESIZER == 1 ]] && { + _print " resize resize a TOMB to a new size -s (can only grow)" + } + _print "\000" + _print " // Operations on keys:" + _print " passwd change the password of a KEY (needs old pass)" + _print " setkey change the KEY locking a TOMB (needs old key and pass)" + _print "\000" + [[ $QRENCODE == 1 ]] && { + _print " // Backup on paper:" + _print " engrave makes a QR code of a KEY to be saved on paper" + } + _print "\000" + [[ $STEGHIDE == 1 ]] && { + _print " // Steganography:" + _print " bury hide a KEY inside a JPEG image (for use with -k)" + _print " exhume extract a KEY from a JPEG image (prints to stdout)" + } + _print "\000" + _print "Options:" + _print "\000" + _print " -s size of the tomb file when creating/resizing one (in MiB)" + _print " -k path to the key to be used ('-k -' to read from stdin)" + _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)" + [[ $KDF == 1 ]] && { + _print " --kdf forge keys armored against dictionary attacks" + } + + _print "\000" + _print " -h print this help" + _print " -v print version, license and list of available ciphers" + _print " -q run quietly without printing informations" + _print " -D print debugging information at runtime" + _print "\000" + _print "For more informations on Tomb read the manual: man tomb" + _print "Please report bugs on <http://github.com/dyne/tomb/issues>." +} + + +# Check whether a commandline option is set. +# +# Synopsis: option_is_set -flag [out] +# +# First argument is the commandline flag (e.g., "-s"). +# If the second argument is present and set to 'out', print out the +# result: either 'set' or 'unset' (useful for if conditions). +# +# Return 0 if is set, 1 otherwise +option_is_set() { + local -i r # the return code (0 = set, 1 = unset) + + [[ -n ${(k)OPTS[$1]} ]]; + r=$? + + [[ $2 == "out" ]] && { + [[ $r == 0 ]] && { print 'set' } || { print 'unset' } + } + + return $r; +} + +# Print the option value matching the given flag +# Unique argument is the commandline flag (e.g., "-s"). +option_value() { + print -n - "${OPTS[$1]}" +} + +# Messaging function with pretty coloring +function _msg() { + local msg="$2" + command -v gettext 1>/dev/null 2>/dev/null && msg="$(gettext -s "$2")" + for i in $(seq 3 ${#}); + do + msg=${(S)msg//::$(($i - 2))*::/$*[$i]} + done + + local command="print -P" + local progname="$fg[magenta]${TOMBEXEC##*/}$reset_color" + local message="$fg_bold[normal]$fg_no_bold[normal]$msg$reset_color" + local -i returncode + + case "$1" in + inline) + command+=" -n"; pchars=" > "; pcolor="yellow" + ;; + message) + pchars=" . "; pcolor="white"; message="$fg_no_bold[$pcolor]$msg$reset_color" + ;; + verbose) + pchars="[D]"; pcolor="blue" + ;; + success) + pchars="(*)"; pcolor="green"; message="$fg_no_bold[$pcolor]$msg$reset_color" + ;; + warning) + pchars="[W]"; pcolor="yellow"; message="$fg_no_bold[$pcolor]$msg$reset_color" + ;; + failure) + pchars="[E]"; pcolor="red"; message="$fg_no_bold[$pcolor]$msg$reset_color" + returncode=1 + ;; + print) + progname="" + ;; + *) + pchars="[F]"; pcolor="red" + message="Developer oops! Usage: _msg MESSAGE_TYPE \"MESSAGE_CONTENT\"" + returncode=127 + ;; + esac + ${=command} "${progname} $fg_bold[$pcolor]$pchars$reset_color ${message}$color[reset_color]" >&2 + return $returncode +} + +function _message say() { + local notice="message" + [[ "$1" = "-n" ]] && shift && notice="inline" + option_is_set -q || _msg "$notice" $@ + return 0 +} + +function _verbose xxx() { + option_is_set -D && _msg verbose $@ + return 0 +} + +function _success yes() { + option_is_set -q || _msg success $@ + return 0 +} + +function _warning no() { + option_is_set -q || _msg warning $@ + return 1 +} + +function _failure die() { + typeset -i exitcode=${exitv:-1} + option_is_set -q || _msg failure $@ + # be sure we forget the secrets we were told + exit $exitcode +} + +function _print() { + option_is_set -q || _msg print $@ + return 0 +} + +_list_optional_tools() { + typeset -a _deps + _deps=(gettext dcfldd wipe steghide) + _deps+=(resize2fs tomb-kdb-pbkdf2 qrencode swish-e unoconv) + for d in $_deps; do + _print "`which $d`" + done + return 0 +} + + +# Check program dependencies +# +# Tomb depends on system utilities that must be present, and other +# functionality that can be provided by various programs according to +# what's available on the system. If some required commands are +# missing, bail out. +_ensure_dependencies() { + + # Check for required programs + for req in cryptsetup pinentry sudo gpg mkfs.ext4 e2fsck; do + command -v $req 1>/dev/null 2>/dev/null || { + _failure "Missing required dependency ::1 command::. Please install it." $req } + done + + # Ensure system binaries are available in the PATH + path+=(/sbin /usr/sbin) # zsh magic + + # Which dd command to use + command -v dcfldd 1>/dev/null 2>/dev/null && DD=(dcfldd statusinterval=1) + + # Which wipe command to use + command -v wipe 1>/dev/null 2>/dev/null && WIPE=(wipe -f -s) + + # Check for steghide + command -v steghide 1>/dev/null 2>/dev/null || STEGHIDE=0 + # Check for resize + command -v resize2fs 1>/dev/null 2>/dev/null || RESIZER=0 + # Check for KDF auxiliary tools + command -v tomb-kdb-pbkdf2 1>/dev/null 2>/dev/null || KDF=0 + # Check for Swish-E file content indexer + command -v swish-e 1>/dev/null 2>/dev/null || SWISH=0 + # Check for QREncode for paper backups of keys + command -v qrencode 1>/dev/null 2>/dev/null || QRENCODE=0 +} + +# }}} - Commandline interaction + +# {{{ Key operations + +# $1 is the encrypted key contents we are checking +is_valid_key() { + local key="$1" # Unique argument is an encrypted key to test + + _verbose "is_valid_key" + + [[ -z $key ]] && key=$TOMBKEY + [[ "$key" = "cleartext" ]] && { + { option_is_set --unsafe } || { + _warning "cleartext key from stdin selected: this is unsafe." + exitv=127 _failure "please use --unsafe if you really want to do this." + } + _warning "received key in cleartext from stdin (unsafe mode)" + return 0 } + + [[ -z $key ]] && { + _warning "is_valid_key() called without an argument." + return 1 + } + + # If the key file is an image don't check file header + [[ -r $TOMBKEYFILE ]] \ + && [[ $(file $TOMBKEYFILE) =~ "JP.G" ]] \ + && { + _message "Key is an image, it might be valid." + return 0 } + + [[ $key =~ "BEGIN PGP" ]] && { + _message "Key is valid." + return 0 } + + return 1 +} + +# $1 is a string containing an encrypted key +_tomb_key_recover recover_key() { + local key="${1}" # Unique argument is an encrypted key + + _warning "Attempting key recovery." + + _head="${key[(f)1]}" # take the first line + + TOMBKEY="" # Reset global variable + + [[ $_head =~ "^_KDF_" ]] && TOMBKEY+="$_head\n" + + TOMBKEY+="-----BEGIN PGP MESSAGE-----\n" + TOMBKEY+="$key\n" + TOMBKEY+="-----END PGP MESSAGE-----\n" + + return 0 +} + +# Retrieve the tomb key from the file specified from the command line, +# or from stdin if -k - was selected. Run validity checks on the +# file. On success, return 0 and print out the full path of the key. +# Set global variables TOMBKEY and TOMBKEYFILE. +_load_key() { + local keyfile="$1" # Unique argument is an optional keyfile + + [[ -z $keyfile ]] && keyfile=$(option_value -k) + [[ -z $keyfile ]] && { + _failure "This operation requires a key file to be specified using the -k option." } + + if [[ $keyfile == "-" ]]; then + _verbose "load_key reading from stdin." + _message "Waiting for the key to be piped from stdin... " + TOMBKEYFILE=stdin + TOMBKEY=$(cat) + elif [[ $keyfile == "cleartext" ]]; then + _verbose "load_key reading SECRET from stdin" + _message "Waiting for the key to be piped from stdin... " + TOMBKEYFILE=cleartext + TOMBKEY=cleartext + TOMBSECRET=$(cat) + else + _verbose "load_key argument: ::1 key file::" $keyfile + [[ -r $keyfile ]] || _failure "Key not found, specify one using -k." + TOMBKEYFILE=$keyfile + TOMBKEY="${mapfile[$TOMBKEYFILE]}" + fi + + _verbose "load_key: ::1 key::" $TOMBKEYFILE + + [[ "$TOMBKEY" = "" ]] && { + # something went wrong, there is no key to load + # this occurs especially when piping from stdin and aborted + _failure "Key not found, specify one using -k." + } + + is_valid_key $TOMBKEY || { + _warning "The key seems invalid or its format is not known by this version of Tomb." + _tomb_key_recover $TOMBKEY + } + + # Declared TOMBKEYFILE (path) + # Declared TOMBKEY (contents) + + 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 ;^/ + local gpgver=$(gpg --version --no-permission-warning | awk '/^gpg/ {print $3}') + local gpgpass="$1\n$TOMBKEY" + local gpgstatus + + [[ $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` + ret=$? + unset gpgpass + + } || { # using status-file in gpg != 1.4.11 + + TOMBSECRET=`print - "$gpgpass" | \ + gpg --batch --passphrase-fd 0 --no-tty --no-options \ + --status-fd 2 --no-mdc-warning --no-permission-warning \ + --no-secmem-warning` |& grep GNUPG: \ + | read -r -d'\n' gpgstatus + + unset gpgpass + + ret=1 + + [[ "${gpgstatus}" =~ "DECRYPTION_OKAY" ]] && { ret=0 } + + + } + 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 + _verbose "get_lukskey" + + _password="$1" + + + firstline="${TOMBKEY[(f)1]}" + + # key is KDF encoded + if [[ $firstline =~ '^_KDF_' ]]; then + kdf_hash="${firstline[(ws:_:)2]}" + _verbose "KDF: ::1 kdf::" "$kdf_hash" + case "$kdf_hash" in + "pbkdf2sha1") + kdf_salt="${firstline[(ws:_:)3]}" + kdf_ic="${firstline[(ws:_:)4]}" + kdf_len="${firstline[(ws:_:)5]}" + _message "Unlocking KDF key protection ($kdf_hash)" + _verbose "KDF salt: $kdf_salt" + _verbose "KDF ic: $kdf_ic" + _verbose "KDF len: $kdf_len" + _password=$(tomb-kdb-pbkdf2 $kdf_salt $kdf_ic $kdf_len 2>/dev/null <<<$_password) + ;; + *) + _failure "No suitable program for KDF ::1 program::." $pbkdf_hash + unset _password + return 1 + ;; + esac + + # key needs to be exhumed from an image + elif [[ -r $TOMBKEYFILE && $(file $TOMBKEYFILE) =~ "JP.G" ]]; then + + exhume_key $TOMBKEYFILE "$_password" + + fi + + gpg_decrypt "$_password" # Save decrypted contents into $TOMBSECRET + + ret="$?" + + _verbose "get_lukskey returns ::1::" $ret + 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 saves +# the password in the global variable $TOMBPASSWORD +ask_key_password() { + [[ -z "$TOMBKEYFILE" ]] && { + _failure "Internal error: ask_key_password() called before _load_key()." } + + [[ "$TOMBKEYFILE" = "cleartext" ]] && { + _verbose "no password needed, using secret bytes from stdin" + return 0 } + + _message "A password is required to use key ::1 key::" $TOMBKEYFILE + passok=0 + tombpass="" + if [[ "$1" = "" ]]; then + + for c in 1 2 3; do + if [[ $c == 1 ]]; then + tombpass=$(ask_password "Insert password to: $TOMBKEYFILE") + else + tombpass=$(ask_password "Insert password to: $TOMBKEYFILE (attempt $c)") + fi + [[ $? = 0 ]] || { + _warning "User aborted password dialog." + return 1 + } + + get_lukskey "$tombpass" + + [[ $? = 0 ]] && { + passok=1; _message "Password OK." + break; + } + done + + else + # if a second argument is present then the password is already known + tombpass="$1" + _verbose "ask_key_password with tombpass: ::1 tomb pass::" $tombpass + + get_lukskey "$tombpass" + + [[ $? = 0 ]] && { + passok=1; _message "Password OK." + } + + fi + [[ $passok == 1 ]] || return 1 + + TOMBPASSWORD=$tombpass + return 0 +} + +# 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 - ${=@} + return $? +} + +# change tomb key password +change_passwd() { + local tmpnewkey lukskey c tombpass tombpasstmp + + _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 + + _tmp_create + tmpnewkey=$TOMBTMP + + if option_is_set --tomb-old-pwd; then + local tomboldpwd="`option_value --tomb-old-pwd`" + _verbose "tomb-old-pwd = ::1 old pass::" $tomboldpwd + ask_key_password "$tomboldpwd" + else + ask_key_password + fi + [[ $? == 0 ]] || _failure "No valid password supplied." + + _success "Changing password for ::1 key file::" $TOMBKEYFILE + + # Here $TOMBSECRET contains the key material in clear + + { option_is_set --tomb-pwd } && { + local tombpwd="`option_value --tomb-pwd`" + _verbose "tomb-pwd = ::1 new pass::" $tombpwd + gen_key "$tombpwd" >> "$tmpnewkey" + } || { + gen_key >> "$tmpnewkey" + } + + { is_valid_key "${mapfile[$tmpnewkey]}" } || { + _failure "Error: the newly generated keyfile does not seem valid." } + + # Copy the new key as the original keyfile name + cp -f "${tmpnewkey}" $TOMBKEYFILE + _success "Your passphrase was successfully updated." + + return 0 +} + + +# takes care to encrypt a key +# honored options: --kdf --tomb-pwd -o +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}" + # 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 + + 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 + _error "Wrong argument for --kdf: must be an integer number (iteration seconds)." + _error "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 + + # TODO: check result of gpg operation + cat <<EOF | gpg --openpgp --force-mdc --cipher-algo ${algo} \ + --batch --no-options --no-tty --passphrase-fd 0 --status-fd 2 \ + -o - -c -a +${tombpass} +$TOMBSECRET +EOF + # print -n "${tombpass}" \ + # | gpg --openpgp --force-mdc --cipher-algo ${algo} \ + # --batch --no-options --no-tty --passphrase-fd 0 --status-fd 2 \ + # -o - -c -a ${lukskey} + + TOMBPASSWORD="$tombpass" # Set global variable + unset tombpass + unset tombpasstmp +} + +# prints an array of ciphers available in gnupg (to encrypt keys) +list_gnupg_ciphers() { + # prints an error if GnuPG is not found + which gpg 2>/dev/null || _failure "gpg (GnuPG) is not found, Tomb cannot function without it." + + ciphers=(`gpg --version | awk ' +BEGIN { ciphers=0 } +/^Cipher:/ { gsub(/,/,""); sub(/^Cipher:/,""); print; ciphers=1; next } +/^Hash:/ { ciphers=0 } +{ if(ciphers==0) { next } else { gsub(/,/,""); print; } } +'`) + print " ${ciphers}" + return 1 +} + +# Steganographic function to bury a key inside an image. +# Requires steghide(1) to be installed +bury_key() { + + _load_key # Try loading key from option -k and set TOMBKEY + + imagefile=$PARAM + + [[ "`file $imagefile`" =~ "JPEG" ]] || { + _warning "Encode failed: ::1 image file:: is not a jpeg image." $imagefile + return 1 + } + + _success "Encoding key ::1 tomb key:: inside image ::2 image file::" $TOMBKEY $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 it isn't strictly necessary (and having different + # password would enhance security). Nevertheless here we prefer + # usability. + + { option_is_set --tomb-pwd } && { + local tombpwd="`option_value --tomb-pwd`" + _verbose "tomb-pwd = ::1 tomb pass::" $tombpwd + ask_key_password "$tombpwd" + } || { + ask_key_password + } + [[ $? != 0 ]] && { + _warning "Wrong password supplied." + _failure "You shall not bury a key whose password is unknown to you." } + + # We omit armor strings since having them as constants can give + # ground to effective attacks on steganography + print - "$TOMBKEY" | awk ' +/^-----/ {next} +/^Version/ {next} +{print $0}' \ + | steghide embed --embedfile - --coverfile ${imagefile} \ + -p $TOMBPASSWORD -z 9 -e serpent cbc + if [ $? != 0 ]; then + _warning "Encoding error: steghide reports problems." + res=1 + else + _success "Tomb key encoded succesfully into image ::1 image file::" $imagefile + res=0 + fi + + 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() { + [[ "$1" = "" ]] && { + _failure "Exhume failed, no image specified" } + + local imagefile="$1" # The image file where to look for the key + local tombpass="$2" # (Optional) the password to use (internal use) + local destkey="$3" # (Optional) the key file where to save the + # result (- for stdout) + local r=1 # Return code (default: fail) + + # Ensure the image file is a readable JPEG + [[ ! -r $imagefile ]] && { + _failure "Exhume failed, image file not found: ::1 image file::" "${imagefile:-none}" } + [[ ! $(file "$imagefile") =~ "JP.G" ]] && { + _failure "Exhume failed: ::1 image file:: is not a jpeg image." $imagefile } + + # When a password is passed as argument then always print out + # the exhumed key on stdout without further checks (internal use) + [[ -n "$tombpass" ]] && { + TOMBKEY=$(steghide extract -sf $imagefile -p $tombpass -xf -) + [[ $? != 0 ]] && { + _failure "Wrong password or no steganographic key found" } + + recover_key $TOMBKEY + + return 0 + } + + # Ensure we have a valid destination for the key + [[ -z $destkey ]] && { option_is_set -k } && destkey=$(option_value -k) + [[ -z $destkey ]] && { + destkey="-" # No key was specified: fallback to stdout + _message "printing exhumed key on stdout" } + + # Bail out if destination exists, unless -f (force) was passed + [[ $destkey != "-" && -s $destkey ]] && { + _warning "File exists: ::1 tomb key::" $destkey + { option_is_set -f } && { + _warning "Use of --force selected: overwriting." + rm -f $destkey + } || { + _warning "Make explicit use of --force to overwrite." + _failure "Refusing to overwrite file. Operation aborted." } + } + + _message "Trying to exhume a key out of image ::1 image file::" $imagefile + { option_is_set --tomb-pwd } && { + tombpass=$(option_value --tomb-pwd) + _verbose "tomb-pwd = ::1 tomb pass::" $tombpass + } || { + [[ -n $TOMBPASSWORD ]] && tombpass=$TOMBPASSWORD + } || { + tombpass=$(ask_password "Insert password to exhume key from $imagefile") + [[ $? != 0 ]] && { + _warning "User aborted password dialog." + return 1 + } + } + + # Extract the key from the image + steghide extract -sf $imagefile -p ${tombpass} -xf $destkey + r=$? + + # Report to the user + [[ "$destkey" = "-" ]] && destkey="stdout" + [[ $r == 0 ]] && { + _success "Key succesfully exhumed to ::1 key::." $destkey + } || { + _warning "Nothing found in ::1 image file::" $imagefile + } + + return $r +} + +# Produces a printable image of the key contents so a backup on paper +# can be made and hidden in books etc. +engrave_key() { + + _load_key # Try loading key from option -k and set TOMBKEYFILE + + local keyname=$(basename $TOMBKEYFILE) + local pngname="$keyname.qr.png" + + _success "Rendering a printable QRCode for key: ::1 tomb key file::" $TOMBKEYFILE + # we omit armor strings to save space + awk '/^-----/ {next}; /^Version/ {next}; {print $0}' $TOMBKEYFILE \ + | qrencode --size 4 --level H --casesensitive -o $pngname + [[ $? != 0 ]] && { + _failure "QREncode reported an error." } + + _success "Operation successful:" + # TODO: only if verbose and/or not silent + ls -lh $pngname + file $pngname +} + +# }}} - Key handling + +# {{{ Create + +# Since version 1.5.3, tomb creation is a three-step process that replaces create_tomb(): +# +# * dig a .tomb (the large file) using /dev/urandom (takes some minutes at least) +# +# * forge a .key (the small file) using /dev/random (good entropy needed) +# +# * lock the .tomb file with the key, binding the key to the tomb (requires dm_crypt format) + +# Step one - Dig a tomb +# +# Synopsis: dig_tomb /path/to/tomb -s sizemebibytes +# +# It will create an empty file to be formatted as a loopback +# filesystem. Initially the file is filled with random data taken +# from /dev/urandom to improve overall tomb's security and prevent +# some attacks aiming at detecting how much data is in the tomb, or +# which blocks in the filesystem contain that data. + +dig_tomb() { + local tombpath="$1" # Path to tomb + # Require the specification of the size of the tomb (-s) in MiB + local -i tombsize=$(option_value -s) + + _message "Commanded to dig tomb ::1 tomb path::" $tombpath + + [[ -n "$tombpath" ]] || _failure "Missing path to tomb" + [[ -n "$tombsize" ]] || _failure "Size argument missing, use -s" + [[ $tombsize == <-> ]] || _failure "Size must be an integer (mebibytes)" + [[ $tombsize -ge 10 ]] || _failure "Tombs can't be smaller than 10 mebibytes" + + _plot $tombpath # Set TOMB{PATH,DIR,FILE,NAME} + + [[ -e $TOMBPATH ]] && { + _warning "A tomb exists already. I'm not digging here:" + ls -lh $TOMBPATH + return 1 + } + + _success "Creating a new tomb in ::1 tomb path::" $TOMBPATH + + _message "Generating ::1 tomb file:: of ::2 size::MiB" $TOMBFILE $tombsize + + # Ensure that file permissions are safe even if interrupted + touch $TOMBPATH + [[ $? = 0 ]] || { + _warning "Error creating the tomb ::1 tomb path::" $TOMBPATH + _failure "Operation aborted." + } + chmod 0600 $TOMBPATH + + _verbose "Data dump using ::1:: from /dev/urandom" ${DD[1]} + ${=DD} if=/dev/urandom bs=1048576 count=$tombsize of=$TOMBPATH + + [[ $? == 0 && -e $TOMBPATH ]] && { + ls -lh $TOMBPATH + } || { + _warning "Error creating the tomb ::1 tomb path::" $TOMBPATH + _failure "Operation aborted." + } + + _success "Done digging ::1 tomb name::" $TOMBNAME + _message "Your tomb is not yet ready, you need to forge a key and lock it:" + _message "tomb forge ::1 tomb path::.key" $TOMBPATH + _message "tomb lock ::1 tomb path:: -k ::1 tomb path::.key" $TOMBPATH + + return 0 +} + +# Step two -- Create a detached key to lock a tomb with +# +# Synopsis: forge_key [destkey|-k destkey] [-o cipher] +# +# Arguments: +# -k path to destination keyfile +# -o Use an alternate algorithm +# +forge_key() { + # can be specified both as simple argument or using -k + local destkey="$1" + { option_is_set -k } && { destkey=$(option_value -k) } + + local algo="AES256" # Default encryption algorithm + + [[ -z "$destkey" ]] && { + _failure "A filename needs to be specified using -k to forge a new key." } + +# _message "Commanded to forge key ::1 key::" $destkey + + _check_swap # Ensure the available memory is safe to use + + # Ensure GnuPG won't exit with an error before first run + [[ -r $HOME/.gnupg/pubring.gpg ]] || { + mkdir -m 0700 $HOME/.gnupg + touch $HOME/.gnupg/pubring.gpg } + + # Do not overwrite any files accidentally + [[ -r "$destkey" ]] && { + ls -lh $destkey + _failure "Forging this key would overwrite an existing file. Operation aborted." } + + touch $destkey + [[ $? == 0 ]] || { + _warning "Cannot generate encryption key." + _failure "Operation aborted." } + chmod 0600 $destkey + + # Update algorithm if it was passed on the command line with -o + { option_is_set -o } && algopt="$(option_value -o)" + [[ -n "$algopt" ]] && algo=$algopt + + _message "Commanded to forge key ::1 key:: with cipher algorithm ::2 algorithm::" \ + $destkey $algo + + [[ $KDF == 1 ]] && { + _message "Using KDF to protect the key password (`option_value --kdf` rounds)" + } + + TOMBKEYFILE="$destkey" # Set global variable + + _warning "This operation takes time, keep using this computer on other tasks," + _warning "once done you will be asked to choose a password for your tomb." + _warning "To make it faster you can move the mouse around." + _warning "If you are on a server, you can use an Entropy Generation Daemon." + + # Use /dev/random as the entropy source, unless --use-urandom is specified + local random_source=/dev/random + { option_is_set --use-urandom } && random_source=/dev/urandom + + _verbose "Data dump using ::1:: from ::2 source::" ${DD[1]} $random_source + TOMBSECRET=$(${=DD} bs=1 count=256 if=$random_source) + [[ $? == 0 ]] || { + _warning "Cannot generate encryption key." + _failure "Operation aborted." } + + # Here the global variable TOMBSECRET contains the naked secret + + _success "Choose the password of your key: ::1 tomb key::" $TOMBKEYFILE + _message "(You can also change it later using 'tomb passwd'.)" + # _user_file $TOMBKEYFILE + + tombname="$TOMBKEYFILE" # XXX ??? + # the gen_key() function takes care of the new key's encryption + { option_is_set --tomb-pwd } && { + local tombpwd="`option_value --tomb-pwd`" + _verbose "tomb-pwd = ::1 new pass::" $tombpwd + gen_key "$tombpwd" >> $TOMBKEYFILE + } || { + gen_key >> $TOMBKEYFILE + } + + # load the key contents (set global variable) + TOMBKEY="${mapfile[$TOMBKEYFILE]}" + + # this does a check on the file header + is_valid_key $TOMBKEY || { + _warning "The key does not seem to be valid." + _warning "Dumping contents to screen:" + print "${mapfile[$TOMBKEY]}" + _warning "--" + _sudo umount ${keytmp} + rm -r $keytmp + _failure "Operation aborted." + } + + _message "Done forging ::1 key file::" $TOMBKEYFILE + _success "Your key is ready:" + ls -lh $TOMBKEYFILE +} + +# Step three -- Lock tomb +# +# Synopsis: tomb_lock file.tomb file.tomb.key [-o cipher] +# +# Lock the given tomb with the given key file, in fact formatting the +# loopback volume as a LUKS device. +# Default cipher 'aes-xts-plain64:sha256'can be overridden with -o +lock_tomb_with_key() { + # old default was aes-cbc-essiv:sha256 + # Override with -o + # for more alternatives refer to cryptsetup(8) + local cipher="aes-xts-plain64:sha256" + + local tombpath="$1" # First argument is the path to the tomb + + [[ -n $tombpath ]] || { + _warning "No tomb specified for locking." + _warning "Usage: tomb lock file.tomb file.tomb.key" + return 1 + } + + _plot $tombpath + + _message "Commanded to lock tomb ::1 tomb file::" $TOMBFILE + + [[ -f $TOMBPATH ]] || { + _failure "There is no tomb here. You have to dig it first." } + + _verbose "Tomb found: ::1 tomb path::" $TOMBPATH + + lo_mount $TOMBPATH + nstloop=`lo_new` + + _verbose "Loop mounted on ::1 mount point::" $nstloop + + _message "Checking if the tomb is empty (we never step on somebody else's bones)." + _sudo cryptsetup isLuks ${nstloop} + if [ $? = 0 ]; then + # is it a LUKS encrypted nest? then bail out and avoid reformatting it + _warning "The tomb was already locked with another key." + _failure "Operation aborted. I cannot lock an already locked tomb. Go dig a new one." + else + _message "Fine, this tomb seems empty." + fi + + _load_key # Try loading key from option -k and set TOMBKEYFILE + + # the encryption cipher for a tomb can be set when locking using -c + { option_is_set -o } && algopt="$(option_value -o)" + [[ -n "$algopt" ]] && cipher=$algopt + _message "Locking using cipher: ::1 cipher::" $cipher + + # get the pass from the user and check it + if option_is_set --tomb-pwd; then + tomb_pwd="`option_value --tomb-pwd`" + _verbose "tomb-pwd = ::1 tomb pass::" $tomb_pwd + ask_key_password "$tomb_pwd" + else + ask_key_password + fi + [[ $? == 0 ]] || _failure "No valid password supplied." + + _success "Locking ::1 tomb file:: with ::2 tomb key file::" $TOMBFILE $TOMBKEYFILE + + _message "Formatting Luks mapped device." + _cryptsetup --batch-mode \ + --cipher ${cipher} --key-size 256 --key-slot 0 \ + luksFormat ${nstloop} + [[ $? == 0 ]] || { + _warning "cryptsetup luksFormat returned an error." + _failure "Operation aborted." } + + _cryptsetup --cipher ${cipher} luksOpen ${nstloop} tomb.tmp + [[ $? == 0 ]] || { + _warning "cryptsetup luksOpen returned an error." + _failure "Operation aborted." } + + _message "Formatting your Tomb with Ext3/Ext4 filesystem." + _sudo mkfs.ext4 -q -F -j -L $TOMBNAME /dev/mapper/tomb.tmp + + [[ $? == 0 ]] || { + _warning "Tomb format returned an error." + _warning "Your tomb ::1 tomb file:: may be corrupted." $TOMBFILE } + + # Sync + _sudo cryptsetup luksClose tomb.tmp + + _message "Done locking ::1 tomb name:: using Luks dm-crypt ::2 cipher::" $TOMBNAME $cipher + _success "Your tomb is ready in ::1 tomb path:: and secured with key ::2 tomb key::" \ + $TOMBPATH $TOMBKEYFILE + +} + +# This function changes the key that locks a tomb +change_tomb_key() { + local tombkey="$1" # Path to the tomb's key file + local tombpath="$2" # Path to the tomb + + _message "Commanded to reset key for tomb ::1 tomb path::" $tombpath + + [[ -z "$tombpath" ]] && { + _warning "Command 'setkey' needs two arguments: the old key file and the tomb." + _warning "I.e: tomb -k new.tomb.key old.tomb.key secret.tomb" + _failure "Execution aborted." + } + + _check_swap + + # this also calls _plot() + is_valid_tomb $tombpath + + lo_mount $TOMBPATH + nstloop=`lo_new` + _sudo cryptsetup isLuks ${nstloop} + # is it a LUKS encrypted nest? we check one more time + [[ $? == 0 ]] || { + _failure "Not a valid LUKS encrypted volume: ::1 volume::" $TOMBPATH } + + _load_key $tombkey # Try loading given key and set TOMBKEY and + # TOMBKEYFILE + local oldkey=$TOMBKEY + local oldkeyfile=$TOMBKEYFILE + + # we have everything, prepare to mount + _success "Changing lock on tomb ::1 tomb name::" $TOMBNAME + _message "Old key: ::1 old key::" $oldkeyfile + + # render the mapper + mapdate=`date +%s` + # save date of mount in minutes since 1970 + mapper="tomb.$TOMBNAME.$mapdate.$(basename $nstloop)" + + # load the old key + if option_is_set --tomb-old-pwd; then + tomb_old_pwd="`option_value --tomb-old-pwd`" + _verbose "tomb-old-pwd = ::1 old pass::" $tomb_old_pwd + ask_key_password "$tomb_old_pwd" + else + ask_key_password + fi + [[ $? == 0 ]] || { + _failure "No valid password supplied for the old key." } + old_secret=$TOMBSECRET + + # luksOpen the tomb (not really mounting, just on the loopback) + print -R -n - "$old_secret" | _sudo cryptsetup --key-file - \ + luksOpen ${nstloop} ${mapper} + [[ $? == 0 ]] || _failure "Unexpected error in luksOpen." + + _load_key # Try loading new key from option -k and set TOMBKEYFILE + + _message "New key: ::1 key file::" $TOMBKEYFILE + + if option_is_set --tomb-pwd; then + tomb_new_pwd="`option_value --tomb-pwd`" + _verbose "tomb-pwd = ::1 tomb pass::" $tomb_new_pwd + ask_key_password "$tomb_new_pwd" + else + ask_key_password + fi + [[ $? == 0 ]] || { + _failure "No valid password supplied for the new key." } + + _tmp_create + tmpnewkey=$TOMBTMP + print -R -n - "$TOMBSECRET" >> $tmpnewkey + + print -R -n - "$old_secret" | _sudo cryptsetup --key-file - \ + luksChangeKey "$nstloop" "$tmpnewkey" + + [[ $? == 0 ]] || _failure "Unexpected error in luksChangeKey." + + _sudo cryptsetup luksClose "${mapper}" || _failure "Unexpected error in luksClose." + + _success "Succesfully changed key for tomb: ::1 tomb file::" $TOMBFILE + _message "The new key is: ::1 new key::" $TOMBKEYFILE + + return 0 +} + +# }}} - Creation + +# {{{ Open + +# $1 = tombfile $2(optional) = mountpoint +mount_tomb() { + local tombpath="$1" # First argument is the path to the tomb + [[ -n "$tombpath" ]] || _failure "No tomb name specified for opening." + + _message "Commanded to open tomb ::1 tomb name::" $tombpath + + _check_swap + + # this also calls _plot() + is_valid_tomb $tombpath + + _load_key # Try loading new key from option -k and set TOMBKEYFILE + + tombmount="$2" + [[ "$tombmount" = "" ]] && { + tombmount=/media/$TOMBNAME + [[ -d /media ]] || { # no /media found, adopting /run/media/$USER (udisks2 compat) + tombmount=/run/media/$_USER/$TOMBNAME + } + _message "Mountpoint not specified, using default: ::1 mount point::" $tombmount + } + + _success "Opening ::1 tomb file:: on ::2 mount point::" $TOMBNAME $tombmount + + lo_mount $TOMBPATH + nstloop=`lo_new` + + _sudo cryptsetup isLuks ${nstloop} || { + # is it a LUKS encrypted nest? see cryptsetup(1) + _failure "::1 tomb file:: is not a valid Luks encrypted storage file." $TOMBFILE } + + _message "This tomb is a valid LUKS encrypted device." + + luksdump="`_sudo cryptsetup luksDump ${nstloop}`" + tombdump=(`print $luksdump | awk ' + /^Cipher name/ {print $3} + /^Cipher mode/ {print $3} + /^Hash spec/ {print $3}'`) + _message "Cipher is \"::1 cipher::\" mode \"::2 mode::\" hash \"::3 hash::\"" $tombdump[1] $tombdump[2] $tombdump[3] + + slotwarn=`print $luksdump | awk ' + BEGIN { zero=0 } + /^Key slot 0/ { zero=1 } + /^Key slot.*ENABLED/ { if(zero==1) print "WARN" }'` + [[ "$slotwarn" == "WARN" ]] && { + _warning "Multiple key slots are enabled on this tomb. Beware: there can be a backdoor." } + + # save date of mount in minutes since 1970 + mapdate=`date +%s` + + mapper="tomb.$TOMBNAME.$mapdate.$(basename $nstloop)" + + _verbose "dev mapper device: ::1 mapper::" $mapper + _verbose "Tomb key: ::1 key file::" $TOMBKEYFILE + + # take the name only, strip extensions + _verbose "Tomb name: ::1 tomb name:: (to be engraved)" $TOMBNAME + + { option_is_set --tomb-pwd } && { + tomb_pwd="`option_value --tomb-pwd`" + _verbose "tomb-pwd = ::1 tomb pass::" $tomb_pwd + ask_key_password "$tomb_pwd" + } || { + ask_key_password + } + [[ $? == 0 ]] || _failure "No valid password supplied." + + _cryptsetup luksOpen ${nstloop} ${mapper} + [[ $? = 0 ]] || { + _failure "Failure mounting the encrypted file." } + + # preserve the loopdev after exit + lo_preserve "$nstloop" + + # array: [ cipher, keysize, loopdevice ] + tombstat=(`_sudo cryptsetup status ${mapper} | awk ' + /cipher:/ {print $2} + /keysize:/ {print $2} + /device:/ {print $2}'`) + _success "Success unlocking tomb ::1 tomb name::" $TOMBNAME + _verbose "Key size is ::1 size:: for cipher ::2 cipher::" $tombstat[2] $tombstat[1] + + _message "Checking filesystem via ::1::" $tombstat[3] + _sudo fsck -p -C0 /dev/mapper/${mapper} + _verbose "Tomb engraved as ::1 tomb name::" $TOMBNAME + _sudo tune2fs -L $TOMBNAME /dev/mapper/${mapper} > /dev/null + + # we need root from here on + _sudo mkdir -p $tombmount + + # Default mount options are overridden with the -o switch + { option_is_set -o } && { + local oldmountopts=$MOUNTOPTS + MOUNTOPTS="$(option_value -o)" } + + # TODO: safety check MOUNTOPTS + # safe_mount_options && \ + _sudo mount -o $MOUNTOPTS /dev/mapper/${mapper} ${tombmount} + # Clean up if the mount failed + [[ $? == 0 ]] || { + _warning "Error mounting ::1 mapper:: on ::2 tombmount::" $mapper $tombmount + [[ $oldmountopts != $MOUNTOPTS ]] && \ + _warning "Are mount options '::1 mount options::' valid?" $MOUNTOPTS + # TODO: move cleanup to _endgame() + [[ -d $tombmount ]] && _sudo rmdir $tombmount + [[ -e /dev/mapper/$mapper ]] && _sudo cryptsetup luksClose $mapper + # The loop is taken care of in _endgame() + _failure "Cannot mount ::1 tomb name::" $TOMBNAME + } + + _sudo chown $UID:$GID ${tombmount} + _sudo chmod 0711 ${tombmount} + + _success "Success opening ::1 tomb file:: on ::2 mount point::" $TOMBFILE $tombmount + + local tombtty tombhost tombuid tombuser + + # print out when it was opened the last time, by whom and where + [[ -r ${tombmount}/.last ]] && { + tombsince=$(_cat ${tombmount}/.last) + tombsince=$(date --date=@$tombsince +%c) + tombtty=$(_cat ${tombmount}/.tty) + tombhost=$(_cat ${tombmount}/.host) + tomblast=$(_cat ${tombmount}/.last) + tombuid=$(_cat ${tombmount}/.uid | tr -d ' ') + + tombuser=$(getent passwd $tombuid) + tombuser=${tombuser[(ws@:@)1]} + + _message "Last visit by ::1 user::(::2 tomb build::) from ::3 tty:: on ::4 host::" $tombuser $tombuid $tombtty $tombhost + _message "on date ::1 date::" $tombsince + } + # write down the UID and TTY that opened the tomb + rm -f ${tombmount}/.uid + print $_UID > ${tombmount}/.uid + rm -f ${tombmount}/.tty + print $_TTY > ${tombmount}/.tty + # also the hostname + rm -f ${tombmount}/.host + hostname > ${tombmount}/.host + # and the "last time opened" information + # in minutes since 1970, this is printed at next open + rm -f ${tombmount}/.last + date +%s > ${tombmount}/.last + # human readable: date --date=@"`cat .last`" +%c + + + # process bind-hooks (mount -o bind of directories) + # and post-hooks (execute on open) + { option_is_set -n } || { + exec_safe_bind_hooks ${tombmount} + exec_safe_post_hooks ${tombmount} open } + + return 0 +} + +## HOOKS EXECUTION +# +# Execution of code inside a tomb may present a security risk, e.g., +# if the tomb is shared or compromised, an attacker could embed +# malicious code. When in doubt, open the tomb with the -n switch in +# order to skip this feature and verify the files mount-hooks and +# bind-hooks inside the tomb yourself before letting them run. + +# Mount files and directories from the tomb to the current user's HOME. +# +# Synopsis: exec_safe_bind_hooks /path/to/mounted/tomb +# +# This can be a security risk if you share tombs with untrusted people. +# In that case, use the -n switch to turn off this feature. +exec_safe_bind_hooks() { + local mnt="$1" # First argument is the mount point of the tomb + + # Default mount options are overridden with the -o switch + [[ -n ${(k)OPTS[-o]} ]] && MOUNTOPTS=${OPTS[-o]} + + # No HOME set? Note: this should never happen again. + [[ -z $HOME ]] && { + _warning "How pitiful! A tomb, and no HOME." + return 1 } + + [[ -z $mnt || ! -d $mnt ]] && { + _warning "Cannot exec bind hooks without a mounted tomb." + return 1 } + + [[ -r "$mnt/bind-hooks" ]] || { + _verbose "bind-hooks not found in ::1 mount point::" $mnt + return 1 } + + typeset -Al maps # Maps of files and directories to mount + typeset -al mounted # Track already mounted files and directories + + # better parsing for bind hooks checks for two separated words on + # each line, using zsh word separator array subscript + _bindhooks="${mapfile[${mnt}/bind-hooks]}" + for h in ${(f)_bindhooks}; do + s="${h[(w)1]}" + d="${h[(w)2]}" + [[ "$s" = "" ]] && { _warning "bind-hooks file is broken"; return 1 } + [[ "$d" = "" ]] && { _warning "bind-hooks file is broken"; return 1 } + maps+=($s $d) + _verbose "bind-hook found: $s -> $d" + done + unset _bindhooks + + for dir in ${(k)maps}; do + [[ "${dir[1]}" == "/" || "${dir[1,2]}" == ".." ]] && { + _warning "bind-hooks map format: local/to/tomb local/to/\$HOME" + continue } + + [[ "${${maps[$dir]}[1]}" == "/" || "${${maps[$dir]}[1,2]}" == ".." ]] && { + _warning "bind-hooks map format: local/to/tomb local/to/\$HOME. Rolling back" + for dir in ${mounted}; do _sudo umount $dir; done + return 1 } + + if [[ ! -r "$HOME/${maps[$dir]}" ]]; then + _warning "bind-hook target not existent, skipping ::1 home::/::2 subdir::" $HOME ${maps[$dir]} + elif [[ ! -r "$mnt/$dir" ]]; then + _warning "bind-hook source not found in tomb, skipping ::1 mount point::/::2 subdir::" $mnt $dir + else + _sudo mount -o bind,$MOUNTOPTS $mnt/$dir $HOME/${maps[$dir]} \ + && mounted+=("$HOME/${maps[$dir]}") + fi + done +} + +# Execute automated actions configured in the tomb. +# +# Synopsis: exec_safe_post_hooks /path/to/mounted/tomb [open|close] +# +# If an executable file named 'post-hooks' is found inside the tomb, +# run it as a user. This might need a dialog for security on what is +# being run, however we expect you know well what is inside your tomb. +# If you're mounting an untrusted tomb, be safe and use the -n switch +# to verify what it would run if you let it. This feature opens the +# possibility to make encrypted executables. +exec_safe_post_hooks() { + local mnt=$1 # First argument is where the tomb is mounted + local act=$2 # Either 'open' or 'close' + + # Only run if post-hooks has the executable bit set + [[ -x $mnt/post-hooks ]] || return + + # If the file starts with a shebang, run it. + cat $mnt/post-hooks | head -n1 | grep '^#!\s*/' &> /dev/null + [[ $? == 0 ]] && { + _success "Post hooks found, executing as user ::1 user name::." $USERNAME + $mnt/post-hooks $act $mnt + } +} + +# }}} - Tomb open + +# {{{ List + +# list all tombs mounted in a readable format +# $1 is optional, to specify a tomb +list_tombs() { + + local tombname tombmount tombfs tombfsopts tombloop + local ts tombtot tombused tombavail tombpercent tombp tombsince + local tombtty tombhost tombuid tombuser + # list all open tombs + mounted_tombs=(`list_tomb_mounts $1`) + [[ ${#mounted_tombs} == 0 ]] && { + _failure "I can't see any ::1 status:: tomb, may they all rest in peace." ${1:-open} } + + for t in ${mounted_tombs}; do + mapper=`basename ${t[(ws:;:)1]}` + tombname=${t[(ws:;:)5]} + tombmount=${t[(ws:;:)2]} + tombfs=${t[(ws:;:)3]} + tombfsopts=${t[(ws:;:)4]} + tombloop=${mapper[(ws:.:)4]} + + # calculate tomb size + ts=`df -hP /dev/mapper/$mapper | +awk "/mapper/"' { print $2 ";" $3 ";" $4 ";" $5 }'` + tombtot=${ts[(ws:;:)1]} + tombused=${ts[(ws:;:)2]} + tombavail=${ts[(ws:;:)3]} + tombpercent=${ts[(ws:;:)4]} + tombp=${tombpercent%%%} + + # obsolete way to get the last open date from /dev/mapper + # which doesn't work when tomb filename contain dots + # tombsince=`date --date=@${mapper[(ws:.:)3]} +%c` + + # find out who opens it from where + [[ -r ${tombmount}/.tty ]] && { + tombsince=$(_cat ${tombmount}/.last) + tombsince=$(date --date=@$tombsince +%c) + tombtty=$(_cat ${tombmount}/.tty) + tombhost=$(_cat ${tombmount}/.host) + tombuid=$(_cat ${tombmount}/.uid | tr -d ' ') + + tombuser=$(getent passwd $tombuid) + tombuser=${tombuser[(ws@:@)1]} + } + + { option_is_set --get-mountpoint } && { print $tombmount; continue } + + _message "::1 tombname:: open on ::2 tombmount:: using ::3 tombfsopts::" \ + $tombname $tombmount $tombfsopts + + _verbose "::1 tombname:: /dev/::2 tombloop:: device mounted (detach with losetup -d)" $tombname $tombloop + + _message "::1 tombname:: open since ::2 tombsince::" $tombname $tombsince + + [[ -z "$tombtty" ]] || { + _message "::1 tombname:: open by ::2 tombuser:: from ::3 tombtty:: on ::4 tombhost::" \ + $tombname $tombuser $tombtty $tombhost + } + + _message "::1 tombname:: size ::2 tombtot:: of which ::3 tombused:: (::5 tombpercent::%) is used: ::4 tombavail:: free " \ + $tombname $tombtot $tombused $tombavail $tombpercent + + [[ ${tombp} -ge 90 ]] && { + _warning "::1 tombname:: warning: your tomb is almost full!" $tombname + } + + # Now check hooks + mounted_hooks=(`list_tomb_binds $tombname $tombmount`) + for h in ${mounted_hooks}; do + _message "::1 tombname:: hooks ::2 hookname:: on ::3 hookdest::" \ + $tombname "`basename ${h[(ws:;:)1]}`" ${h[(ws:;:)2]} + done + done +} + + +# Print out an array of mounted tombs (internal use) +# Format is semi-colon separated list of attributes +# if 1st arg is supplied, then list only that tomb +# +# String positions in the semicolon separated array: +# +# 1. full mapper path +# +# 2. mountpoint +# +# 3. filesystem type +# +# 4. mount options +# +# 5. tomb name +list_tomb_mounts() { + [[ -z "$1" ]] && { + # list all open tombs + mount -l \ + | awk ' +BEGIN { main="" } +/^\/dev\/mapper\/tomb/ { + if(main==$1) next; + print $1 ";" $3 ";" $5 ";" $6 ";" $7 + main=$1 +} +' + } || { + # list a specific tomb + mount -l \ + | awk -vtomb="[$1]" ' +BEGIN { main="" } +/^\/dev\/mapper\/tomb/ { + if($7!=tomb) next; + if(main==$1) next; + print $1 ";" $3 ";" $5 ";" $6 ";" $7 + main=$1 +} +' + } +} + +# list_tomb_binds +# print out an array of mounted bind hooks (internal use) +# format is semi-colon separated list of attributes +# needs two arguments: name of tomb whose hooks belong +# mount tomb +list_tomb_binds() { + [[ -z "$2" ]] && { + _failure "Internal error: list_tomb_binds called without argument." } + + # OK well, prepare for some insanity: parsing the mount table on GNU/Linux + # is like combing a Wookie while he is riding a speedbike down a valley. + + typeset -A tombs + typeset -a binds + for t in "${(f)$(mount -l | grep '/dev/mapper/tomb.*]$')}"; do + len="${(w)#t}" + [[ "${t[(w)$len]}" = "$1" ]] || continue + tombs+=( ${t[(w)1]} ${t[(w)$len]} ) + + done + + for m in ${(k)tombs}; do + for p in "${(f)$(cat /proc/mounts):s/\\040(deleted)/}"; do + # Debian's kernel appends a '\040(deleted)' to the mountpoint in /proc/mounts + # so if we parse the string as-is then this will break the parsing. How nice of them! + # Some bugs related to this are more than 10yrs old. Such Debian! Much stable! Very parsing! + # Bug #711183 umount parser for /proc/mounts broken on stale nfs mount (gets renamed to "/mnt/point (deleted)") + # Bug #711184 mount should not stat mountpoints on mount + # Bug #711187 linux-image-3.2.0-4-amd64: kernel should not rename mountpoint if nfs server is dead/unreachable + [[ "${p[(w)1]}" = "$m" ]] && { + [[ "${(q)p[(w)2]}" != "${(q)2}" ]] && { + # Our output format: + # mapper;mountpoint;fs;flags;name + binds+=("$m;${(q)p[(w)2]};${p[(w)3]};${p[(w)4]};${tombs[$m]}") } + } + done + done + + # print the results out line by line + for b in $binds; do print - "$b"; done +} + +# }}} - Tomb list + +# {{{ Index and search + +# index files in all tombs for search +# $1 is optional, to specify a tomb +index_tombs() { + { command -v updatedb 1>/dev/null 2>/dev/null } || { + _failure "Cannot index tombs on this system: updatedb (mlocate) not installed." } + + updatedbver=`updatedb --version | grep '^updatedb'` + [[ "$updatedbver" =~ "GNU findutils" ]] && { + _warning "Cannot use GNU findutils for index/search commands." } + [[ "$updatedbver" =~ "mlocate" ]] || { + _failure "Index command needs 'mlocate' to be installed." } + + _verbose "$updatedbver" + + mounted_tombs=(`list_tomb_mounts $1`) + [[ ${#mounted_tombs} == 0 ]] && { + # Considering one tomb + [[ -n "$1" ]] && { + _failure "There seems to be no open tomb engraved as [::1::]" $1 } + # Or more + _failure "I can't see any open tomb, may they all rest in peace." } + + _success "Creating and updating search indexes." + + # start the LibreOffice document converter if installed + { command -v unoconv 1>/dev/null 2>/dev/null } && { + unoconv -l 2>/dev/null & + _verbose "unoconv listener launched." + sleep 1 } + + for t in ${mounted_tombs}; do + mapper=`basename ${t[(ws:;:)1]}` + tombname=${t[(ws:;:)5]} + tombmount=${t[(ws:;:)2]} + [[ -r ${tombmount}/.noindex ]] && { + _message "Skipping ::1 tomb name:: (.noindex found)." $tombname + continue } + _message "Indexing ::1 tomb name:: filenames..." $tombname + updatedb -l 0 -o ${tombmount}/.updatedb -U ${tombmount} + + # here we use swish to index file contents + [[ $SWISH == 1 ]] && { + _message "Indexing ::1 tomb name:: contents..." $tombname + rm -f ${tombmount}/.swishrc + _message "Generating a new swish-e configuration file: ::1 swish conf::" ${tombmount}/.swishrc + cat <<EOF > ${tombmount}/.swishrc +# index directives +DefaultContents TXT* +IndexDir $tombmount +IndexFile $tombmount/.swish +# exclude images +FileRules filename regex /\.jp.?g/i +FileRules filename regex /\.png/i +FileRules filename regex /\.gif/i +FileRules filename regex /\.tiff/i +FileRules filename regex /\.svg/i +FileRules filename regex /\.xcf/i +FileRules filename regex /\.eps/i +FileRules filename regex /\.ttf/i +# exclude audio +FileRules filename regex /\.mp3/i +FileRules filename regex /\.ogg/i +FileRules filename regex /\.wav/i +FileRules filename regex /\.mod/i +FileRules filename regex /\.xm/i +# exclude video +FileRules filename regex /\.mp4/i +FileRules filename regex /\.avi/i +FileRules filename regex /\.ogv/i +FileRules filename regex /\.ogm/i +FileRules filename regex /\.mkv/i +FileRules filename regex /\.mov/i +FileRules filename regex /\.flv/i +FileRules filename regex /\.webm/i +# exclude system +FileRules filename is ok +FileRules filename is lock +FileRules filename is control +FileRules filename is status +FileRules filename is proc +FileRules filename is sys +FileRules filename is supervise +FileRules filename regex /\.asc$/i +FileRules filename regex /\.gpg$/i +# pdf and postscript +FileFilter .pdf pdftotext "'%p' -" +FileFilter .ps ps2txt "'%p' -" +# compressed files +FileFilterMatch lesspipe "%p" /\.tgz$/i +FileFilterMatch lesspipe "%p" /\.zip$/i +FileFilterMatch lesspipe "%p" /\.gz$/i +FileFilterMatch lesspipe "%p" /\.bz2$/i +FileFilterMatch lesspipe "%p" /\.Z$/ +# spreadsheets +FileFilterMatch unoconv "-d spreadsheet -f csv --stdout %P" /\.xls.*/i +FileFilterMatch unoconv "-d spreadsheet -f csv --stdout %P" /\.xlt.*/i +FileFilter .ods unoconv "-d spreadsheet -f csv --stdout %P" +FileFilter .ots unoconv "-d spreadsheet -f csv --stdout %P" +FileFilter .dbf unoconv "-d spreadsheet -f csv --stdout %P" +FileFilter .dif unoconv "-d spreadsheet -f csv --stdout %P" +FileFilter .uos unoconv "-d spreadsheet -f csv --stdout %P" +FileFilter .sxc unoconv "-d spreadsheet -f csv --stdout %P" +# word documents +FileFilterMatch unoconv "-d document -f txt --stdout %P" /\.doc.*/i +FileFilterMatch unoconv "-d document -f txt --stdout %P" /\.odt.*/i +FileFilterMatch unoconv "-d document -f txt --stdout %P" /\.rtf.*/i +FileFilterMatch unoconv "-d document -f txt --stdout %P" /\.tex$/i +# native html support +IndexContents HTML* .htm .html .shtml +IndexContents XML* .xml +EOF + + swish-e -c ${tombmount}/.swishrc -S fs -v3 + } + _message "Search index updated." + done +} + +search_tombs() { + { command -v locate 1>/dev/null 2>/dev/null } || { + _failure "Cannot index tombs on this system: updatedb (mlocate) not installed." } + + updatedbver=`updatedb --version | grep '^updatedb'` + [[ "$updatedbver" =~ "GNU findutils" ]] && { + _warning "Cannot use GNU findutils for index/search commands." } + [[ "$updatedbver" =~ "mlocate" ]] || { + _failure "Index command needs 'mlocate' to be installed." } + + _verbose "$updatedbver" + + # list all open tombs + mounted_tombs=(`list_tomb_mounts`) + [[ ${#mounted_tombs} == 0 ]] && { + _failure "I can't see any open tomb, may they all rest in peace." } + + _success "Searching for: ::1::" ${(f)@} + for t in ${mounted_tombs}; do + _verbose "Checking for index: ::1::" ${t} + mapper=`basename ${t[(ws:;:)1]}` + tombname=${t[(ws:;:)5]} + tombmount=${t[(ws:;:)2]} + [[ -r ${tombmount}/.updatedb ]] && { + # Use mlocate to search hits on filenames + _message "Searching filenames in tomb ::1 tomb name::" $tombname + locate -d ${tombmount}/.updatedb -e -i "${(f)@}" + _message "Matches found: ::1 matches::" \ + $(locate -d ${tombmount}/.updatedb -e -i -c ${(f)@}) + + # Use swish-e to search over contents + [[ $SWISH == 1 && -r $tombmount/.swish ]] && { + _message "Searching contents in tomb ::1 tomb name::" $tombname + swish-e -w ${=@} -f $tombmount/.swish -H0 } + } || { + _warning "Skipping tomb ::1 tomb name::: not indexed." $tombname + _warning "Run 'tomb index' to create indexes." } + done + _message "Search completed." +} + +# }}} - Index and search + +# {{{ Resize + +# resize tomb file size +resize_tomb() { + local tombpath="$1" # First argument is the path to the tomb + + _message "Commanded to resize tomb ::1 tomb name:: to ::2 size:: mebibytes." $1 $OPTS[-s] + + [[ -z "$tombpath" ]] && _failure "No tomb name specified for resizing." + [[ ! -r $tombpath ]] && _failure "Cannot find ::1::" $tombpath + + newtombsize="`option_value -s`" + [[ -z "$newtombsize" ]] && { + _failure "Aborting operations: new size was not specified, use -s" } + + # this also calls _plot() + is_valid_tomb $tombpath + + _load_key # Try loading new key from option -k and set TOMBKEYFILE + + local oldtombsize=$(( `stat -c %s "$TOMBPATH" 2>/dev/null` / 1048576 )) + local mounted_tomb=`mount -l | + awk -vtomb="[$TOMBNAME]" '/^\/dev\/mapper\/tomb/ { if($7==tomb) print $1 }'` + + # Tomb must not be open + [[ -z "$mounted_tomb" ]] || { + _failure "Please close the tomb ::1 tomb name:: before trying to resize it." $TOMBNAME } + # New tomb size must be specified + [[ -n "$newtombsize" ]] || { + _failure "You must specify the new size of ::1 tomb name::" $TOMBNAME } + # New tomb size must be an integer + [[ $newtombsize == <-> ]] || _failure "Size is not an integer." + + # Tombs can only grow in size + if [[ "$newtombsize" -gt "$oldtombsize" ]]; then + + delta="$(( $newtombsize - $oldtombsize ))" + + _message "Generating ::1 tomb file:: of ::2 size::MiB" $TOMBFILE $newtombsize + + _verbose "Data dump using ::1:: from /dev/urandom" ${DD[1]} + ${=DD} if=/dev/urandom bs=1048576 count=${delta} >> $TOMBPATH + [[ $? == 0 ]] || { + _failure "Error creating the extra resize ::1 size::, operation aborted." \ + $tmp_resize } + + # If same size this allows to re-launch resize if pinentry expires + # so that it will continue resizing without appending more space. + # Resizing the partition to the file size cannot harm data anyway. + elif [[ "$newtombsize" = "$oldtombsize" ]]; then + _message "Tomb seems resized already, operating filesystem stretch" + else + _failure "The new size must be greater then old tomb size." + fi + + { option_is_set --tomb-pwd } && { + tomb_pwd="`option_value --tomb-pwd`" + _verbose "tomb-pwd = ::1 tomb pass::" $tomb_pwd + ask_key_password "$tomb_pwd" + } || { + ask_key_password + } + [[ $? == 0 ]] || _failure "No valid password supplied." + + lo_mount "$TOMBPATH" + nstloop=`lo_new` + + mapdate=`date +%s` + mapper="tomb.$TOMBNAME.$mapdate.$(basename $nstloop)" + + _message "opening tomb" + _cryptsetup luksOpen ${nstloop} ${mapper} || { + _failure "Failure mounting the encrypted file." } + + _sudo cryptsetup resize "${mapper}" || { + _failure "cryptsetup failed to resize ::1 mapper::" $mapper } + + _sudo e2fsck -p -f /dev/mapper/${mapper} || { + _failure "e2fsck failed to check ::1 mapper::" $mapper } + + _sudo resize2fs /dev/mapper/${mapper} || { + _failure "resize2fs failed to resize ::1 mapper::" $mapper } + + # close and free the loop device + _sudo cryptsetup luksClose "${mapper}" + + return 0 +} + +# }}} + +# {{{ Close + +umount_tomb() { + local tombs how_many_tombs + local pathmap mapper tombname tombmount loopdev + local ans pidk pname + + if [ "$1" = "all" ]; then + mounted_tombs=(`list_tomb_mounts`) + else + mounted_tombs=(`list_tomb_mounts $1`) + fi + + [[ ${#mounted_tombs} == 0 ]] && { + _failure "There is no open tomb to be closed." } + + [[ ${#mounted_tombs} -gt 1 && -z "$1" ]] && { + _warning "Too many tombs mounted, please specify one (see tomb list)" + _warning "or issue the command 'tomb close all' to close them all." + _failure "Operation aborted." } + + for t in ${mounted_tombs}; do + mapper=`basename ${t[(ws:;:)1]}` + + # strip square parens from tombname + tombname=${t[(ws:;:)5]} + tombmount=${t[(ws:;:)2]} + tombfs=${t[(ws:;:)3]} + tombfsopts=${t[(ws:;:)4]} + tombloop=${mapper[(ws:.:)4]} + + _verbose "Name: ::1 tomb name::" $tombname + _verbose "Mount: ::1 mount point::" $tombmount + _verbose "Mapper: ::1 mapper::" $mapper + + [[ -e "$mapper" ]] && { + _warning "Tomb not found: ::1 tomb file::" $1 + _warning "Please specify an existing tomb." + return 0 } + + [[ -n $SLAM ]] && { + _success "Slamming tomb ::1 tomb name:: mounted on ::2 mount point::" \ + $tombname $tombmount + _message "Kill all processes busy inside the tomb." + { slam_tomb "$tombmount" } || { + _failure "Cannot slam the tomb ::1 tomb name::" $tombname } + } || { + _message "Closing tomb ::1 tomb name:: mounted on ::2 mount point::" \ + $tombname $tombmount } + + # check if there are binded dirs and close them + bind_tombs=(`list_tomb_binds $tombname $tombmount`) + for b in ${bind_tombs}; do + bind_mapper="${b[(ws:;:)1]}" + bind_mount="${b[(ws:;:)2]}" + _message "Closing tomb bind hook: ::1 hook::" $bind_mount + _sudo umount "`print - ${bind_mount}`" || { + [[ -n $SLAM ]] && { + _success "Slamming tomb: killing all processes using this hook." + slam_tomb "`print - ${bind_mount}`" || _failure "Cannot slam the bind hook ::1 hook::" $bind_mount + umount "`print - ${bind_mount}`" || _failure "Cannot slam the bind hook ::1 hook::" $bind_mount + } || { + _failure "Tomb bind hook ::1 hook:: is busy, cannot close tomb." $bind_mount + } + } + done + + # Execute post-hooks for eventual cleanup + { option_is_set -n } || { + exec_safe_post_hooks ${tombmount%%/} close } + + _verbose "Performing umount of ::1 mount point::" $tombmount + _sudo umount ${tombmount} + [[ $? = 0 ]] || { _failure "Tomb is busy, cannot umount!" } + + # If we used a default mountpoint and is now empty, delete it + tombname_regex=${tombname//\[/} + tombname_regex=${tombname_regex//\]/} + + [[ "$tombmount" -regex-match "[/run]?/media[/$_USER]?/$tombname_regex" ]] && { + _sudo rmdir $tombmount } + + _sudo cryptsetup luksClose $mapper + [[ $? == 0 ]] || { + _failure "Error occurred in cryptsetup luksClose ::1 mapper::" $mapper } + + # Normally the loopback device is detached when unused + [[ -e "/dev/$tombloop" ]] && _sudo losetup -d "/dev/$tombloop" + [[ $? = 0 ]] || { + _verbose "/dev/$tombloop was already closed." } + + _success "Tomb ::1 tomb name:: closed: your bones will rest in peace." $tombname + + done # loop across mounted tombs + + return 0 +} + +# Kill all processes using the tomb +slam_tomb() { + # $1 = tomb mount point + if [[ -z `fuser -m "$1" 2>/dev/null` ]]; then + return 0 + fi + #Note: shells are NOT killed by INT or TERM, but they are killed by HUP + for s in TERM HUP KILL; do + _verbose "Sending ::1:: to processes inside the tomb:" $s + if option_is_set -D; then + ps -fp `fuser -m /media/a.tomb 2>/dev/null`| + while read line; do + _verbose $line + done + fi + fuser -s -m "$1" -k -M -$s + if [[ -z `fuser -m "$1" 2>/dev/null` ]]; then + return 0 + fi + if ! option_is_set -f; then + sleep 3 + fi + done + return 1 +} + +# }}} - Tomb close + +# {{{ Main routine + +main() { + + _ensure_dependencies # Check dependencies are present or bail out + + local -A subcommands_opts + ### Options configuration + # + # Hi, dear developer! Are you trying to add a new subcommand, or + # to add some options? Well, keep in mind that option names are + # global: they cannot bear a different meaning or behaviour across + # subcommands. The only exception is "-o" which means: "options + # passed to the local subcommand", and thus can bear a different + # meaning for different subcommands. + # + # For example, "-s" means "size" and accepts one argument. If you + # are tempted to add an alternate option "-s" (e.g., to mean + # "silent", and that doesn't accept any argument) DON'T DO IT! + # + # There are two reasons for that: + # I. Usability; users expect that "-s" is "size" + # II. Option parsing WILL EXPLODE if you do this kind of bad + # things (it will complain: "option defined more than once") + # + # If you want to use the same option in multiple commands then you + # 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) + 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[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[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[engrave]="k: " + + subcommands_opts[passwd]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: " + subcommands_opts[close]="" + subcommands_opts[help]="" + subcommands_opts[slam]="" + subcommands_opts[list]="-get-mountpoint " + + subcommands_opts[index]="" + subcommands_opts[search]="" + + subcommands_opts[help]="" + subcommands_opts[bury]="k: -tomb-pwd: " + subcommands_opts[exhume]="k: -tomb-pwd: " + # 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[check]="-ignore-swap " + # subcommands_opts[translate]="" + + ### Detect subcommand + local -aU every_opts #every_opts behave like a set; that is, an array with unique elements + for optspec in $subcommands_opts$main_opts; do + for opt in ${=optspec}; do + every_opts+=${opt} + done + done + local -a oldstar + oldstar=("${(@)argv}") + #### detect early: useful for --option-parsing + zparseopts -M -D -Adiscardme ${every_opts} + if [[ -n ${(k)discardme[--option-parsing]} ]]; then + print $1 + if [[ -n "$1" ]]; then + return 1 + fi + return 0 + fi + unset discardme + if ! zparseopts -M -E -D -Adiscardme ${every_opts}; then + _failure "Error parsing." + return 127 + fi + unset discardme + subcommand=$1 + if [[ -z $subcommand ]]; then + subcommand="__default" + fi + + if [[ -z ${(k)subcommands_opts[$subcommand]} ]]; then + _warning "There's no such command \"::1 subcommand::\"." $subcommand + exitv=127 _failure "Please try -h for help." + fi + argv=("${(@)oldstar}") + unset oldstar + + ### Parsing global + command-specific options + # zsh magic: ${=string} will split to multiple arguments when spaces occur + set -A cmd_opts ${main_opts} ${=subcommands_opts[$subcommand]} + # if there is no option, we don't need parsing + if [[ -n $cmd_opts ]]; then + zparseopts -M -E -D -AOPTS ${cmd_opts} + if [[ $? != 0 ]]; then + _warning "Some error occurred during option processing." + exitv=127 _failure "See \"tomb help\" for more info." + fi + fi + #build PARAM (array of arguments) and check if there are unrecognized options + ok=0 + PARAM=() + for arg in $*; do + if [[ $arg == '--' || $arg == '-' ]]; then + ok=1 + continue #it shouldn't be appended to PARAM + elif [[ $arg[1] == '-' ]]; then + if [[ $ok == 0 ]]; then + exitv=127 _failure "Unrecognized option ::1 arg:: for subcommand ::2 subcommand::" $arg $subcommand + fi + fi + PARAM+=$arg + done + # First parameter actually is the subcommand: delete it and shift + [[ $subcommand != '__default' ]] && { PARAM[1]=(); shift } + + ### End parsing command-specific options + + # Use colors unless told not to + { ! option_is_set --no-color } && { autoload -Uz colors && colors } + # Some options are only available during insecure mode + { ! option_is_set --unsafe } && { + for opt in --tomb-pwd --use-urandom --tomb-old-pwd; do + { option_is_set $opt } && { + exitv=127 _failure "You specified option ::1 option::, which is DANGEROUS and should only be used for testing\nIf you really want so, add --unsafe" $opt } + done + } + # read -t or --tmp flags to set a custom temporary directory + option_is_set --tmp && TMPPREFIX=$(option_value --tmp) + + + # When we run as root, we remember the original uid:gid to set + # permissions for the calling user and drop privileges + _whoami # Reset _UID, _GID, _TTY + + [[ "$PARAM" == "" ]] && { + _verbose "Tomb command: ::1 subcommand::" $subcommand + } || { + _verbose "Tomb command: ::1 subcommand:: ::2 param::" $subcommand $PARAM + } + + [[ -z $_UID ]] || { + _verbose "Caller: uid[::1 uid::], gid[::2 gid::], tty[::3 tty::]." \ + $_UID $_GID $_TTY + } + + _verbose "Temporary directory: $TMPPREFIX" + + # Process subcommand + case "$subcommand" in + + # USAGE + help) + usage + ;; + + # DEPRECATION notice (leave here as 'create' is still present in old docs) + create) + _warning "The create command is deprecated, please use dig, forge and lock instead." + _warning "For more informations see Tomb's manual page (man tomb)." + _failure "Operation aborted." + ;; + + # CREATE Step 1: dig -s NN file.tomb + dig) + dig_tomb ${=PARAM} + ;; + + # CREATE Step 2: forge file.tomb.key + forge) + forge_key ${=PARAM} + ;; + + # CREATE Step 2: lock -k file.tomb.key file.tomb + lock) + lock_tomb_with_key ${=PARAM} + ;; + + # Open the tomb + mount|open) + mount_tomb ${=PARAM} + ;; + + # Close the tomb + # `slam` is used to force closing. + umount|close|slam) + [[ "$subcommand" == "slam" ]] && SLAM=1 + umount_tomb $PARAM[1] + ;; + + # Grow tomb's size + resize) + [[ $RESIZER == 0 ]] && { + _failure "Resize2fs not installed: cannot resize tombs." } + resize_tomb $PARAM[1] + ;; + + ## Contents manipulation + + # Index tomb contents + index) + index_tombs $PARAM[1] + ;; + + # List tombs + list) + list_tombs $PARAM[1] + ;; + + # Search tomb contents + search) + search_tombs ${=PARAM} + ;; + + ## Locking operations + + # Export key to QR Code + engrave) + [[ $QRENCODE == 0 ]] && { + _failure "QREncode not installed: cannot engrave keys on paper." } + engrave_key ${=PARAM} + ;; + + # Change password on existing key + passwd) + change_passwd $PARAM[1] + ;; + + # Change tomb key + setkey) + change_tomb_key ${=PARAM} + ;; + + # STEGANOGRAPHY: hide key inside an image + bury) + [[ $STEGHIDE == 0 ]] && { + _failure "Steghide not installed: cannot bury keys into images." } + bury_key $PARAM[1] + ;; + + # STEGANOGRAPHY: read key hidden in an image + exhume) + [[ $STEGHIDE == 0 ]] && { + _failure "Steghide not installed: cannot exhume keys from images." } + exhume_key $PARAM[1] + ;; + + ## Internal commands useful to developers + + # Make tomb functions available to the calling shell or script + 'source') return 0 ;; + + # Ask user for a password interactively + askpass) ask_password $PARAM[1] $PARAM[2] ;; + + # Default operation: presentation, or version information with -v + __default) + _print "Tomb ::1 version:: - a strong and gentle undertaker for your secrets" $VERSION + _print "\000" + _print " Copyright (C) 2007-2015 Dyne.org Foundation, License GNU GPL v3+" + _print " This is free software: you are free to change and redistribute it" + _print " For the latest sourcecode go to <http://dyne.org/software/tomb>" + _print "\000" + option_is_set -v && { + local langwas=$LANG + LANG=en + _print " This source code is distributed in the hope that it will be useful," + _print " but WITHOUT ANY WARRANTY; without even the implied warranty of" + _print " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + LANG=$langwas + _print " When in need please refer to <http://dyne.org/support>." + _print "\000" + _print "System utils:" + _print "\000" + cat <<EOF + `sudo -V | head -n1` + `cryptsetup --version` + `pinentry --version` + `gpg --version | head -n1` - key forging algorithms (GnuPG symmetric ciphers): + `list_gnupg_ciphers` +EOF + _print "\000" + _print "Optional utils:" + _print "\000" + _list_optional_tools version + return 0 + } + usage + ;; + + # Reject unknown command and suggest help + *) + _warning "Command \"::1 subcommand::\" not recognized." $subcommand + _message "Try -h for help." + return 1 + ;; + esac + return $? +} + +# }}} + +# {{{ Run + +main "$@" || exit $? # Prevent `source tomb source` from exiting + +# }}} + +# -*- tab-width: 4; indent-tabs-mode:nil; -*- +# vim: set shiftwidth=4 expandtab: