tomb

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

commit 1a6fd48def3ed490c5435bfdf4d5317a319479bd
parent 5290fd9e8d0643cbc37a1089eadb1fdb687b88b6
Author: Jaromil <jaromil@dyne.org>
Date:   Mon, 14 Feb 2011 10:24:31 +0100

cleanup of password entry mechanism

using pinentry (with Assuan protocol) instead of our own askpass
a bit less cooler but much more secure.
this also includes partial normalization of variable names
and the redirection of tomb operational output to stderr.

Diffstat:
Mdoc/Makefile.am | 2--
Msrc/Makefile.am | 6+-----
Msrc/tomb | 146++++++++++++++++++++++++++++++++++++++++---------------------------------------
Dsrc/tomb-askpass.c | 332-------------------------------------------------------------------------------
4 files changed, 75 insertions(+), 411 deletions(-)

diff --git a/doc/Makefile.am b/doc/Makefile.am @@ -6,4 +6,3 @@ EXTRA_DIST = Luks_on_disk_format.pdf New_methods_in_HD_encryption.pdf TKS1-draft install-data-hook: ln -sf $(mandir)/man1/tomb.1 $(mandir)/man1/tomb-open.1 ln -sf $(mandir)/man1/tomb.1 $(mandir)/man1/tomb-status.1 - ln -sf $(mandir)/man1/tomb.1 $(mandir)/man1/tomb-askpass.1- \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am @@ -1,16 +1,12 @@ bin_SCRIPTS = tomb tomb-open -bin_PROGRAMS = tomb-status tomb-askpass +bin_PROGRAMS = tomb-status tomb_status_SOURCES = tomb-status.c tomb_status_LDADD = @GTK2_LIBS@ @NOTIFY_LIBS@ tomb_status_CFLAGS = @GTK2_CFLAGS@ @NOTIFY_CFLAGS@ -tomb_askpass_SOURCES = tomb-askpass.c -tomb_askpass_LDADD = @GTK2_LIBS@ -tomb_askpass_CFLAGS = @GTK2_CFLAGS@ - EXTRA_DIST = monmort.xpm pixmapdir = $(datadir)/pixmaps pixmap_DATA = monmort.xpm diff --git a/src/tomb b/src/tomb @@ -27,10 +27,10 @@ DATE=Feb/2011 # standard output message routines # it's always useful to wrap them, in case we change behaviour later -notice() { if ! [ $QUIET ]; then echo "[*] $1"; fi } -act() { if ! [ $QUIET ]; then echo " . $1"; fi } -error() { if ! [ $QUIET ]; then echo "[!] $1"; fi } -func() { if [ $DEBUG ]; then echo "[D] $1"; fi } +notice() { if ! [ $QUIET ]; then echo "[*] $1" >&2; fi } +act() { if ! [ $QUIET ]; then echo " . $1" >&2; fi } +error() { if ! [ $QUIET ]; then echo "[!] $1" >&2; fi } +func() { if [ $DEBUG ]; then echo "[D] $1" >&2; fi } # which dd command to use which dcfldd > /dev/null @@ -157,37 +157,31 @@ ask_usbkey() { return 0 } -# user interface (just to ask the password) +# we use pinentry now +# comes from gpg project and is much more secure +# it also conveniently uses the right toolkit ask_password() { - xhost 2>&1 >/dev/null - if [ $? = 0 ]; then # we have access to the X display - - which tomb-askpass > /dev/null - if [ $? = 0 ]; then - export scolopendro="`tomb-askpass ${1} 2>/dev/null`" - return - fi - which ssh-askpass # 2>&1 > /dev/null - if [ $? = 0 ]; then - export scolopendro="`ssh-askpass "Tomb: provide the password to unlock"`" - return - fi - - else # we'll collect the password from commandline - - act "Tomb: provide the password to unlock" - echo -n " > " - read -s scolopendro - export scolopendro + # pinentry has no custom icon setting + # so we need to temporary modify the gtk theme + cp ~/.gtkrc-2.0 ~/.gtkrc-2.0.bak + cat <<EOF >> ~/.gtkrc-2.0 + pixmap_path "/usr/local/share/pixmaps" + style "normal" { stock["gtk-dialog-authentication"] = {{"monmort.xpm"}} } + widget "*" style "normal" +EOF - fi + cat <<EOF | pinentry | awk '/^D/ { print $2 }' +SETTITLE Opening Tomb $1 +SETDESC You need a password to use its key +SETPROMPT Password: +GETPIN +EOF + + # restore gtk as it was + cp ~/.gtkrc-2.0.bak ~/.gtkrc-2.0 + rm ~/.gtkrc-2.0.bak - # just in case we'd like to have dialog supported too: - # dialog --backtitle "This file is encrypted for privacy protection" \ - # --title "Security check" --insecure \ - # --passwordbox "Enter password:" 10 30 2> /var/run/.scolopendro - } # popup notification @@ -247,18 +241,21 @@ check_priv() { if [ $? = 0 ]; then func "Using sudo for root execution of 'tomb ${(f)ARGS}'" # check if sudo has a timestamp active - sudo -n true 2> /dev/null - if [ $? != 0 ]; then - # if not then ask a password - echo "SETDESC Sudo execution of Tomb ${ARGS[@]} + sudok=false + sudo -n tomb 2> /dev/null + if [ $? != 0 ]; then # if not then ask a password + cat <<EOF | pinentry | awk '/^D/ { print $2 }' | sudo -S -v +SETTITLE Super user privileges required +SETDESC Sudo execution of Tomb ${ARGS[@]} SETPROMPT Insert your USER password: -GETPIN" | pinentry | awk '/^D/ { print $2 }' | sudo -S -v +GETPIN +EOF fi sudo "tomb" ${(s: :)ARGS} exit $? - fi + fi # have sudo return 1 - fi + fi # are we root already return 0 } @@ -409,17 +406,17 @@ create_tomb() { # here user is prompted for key password for c in 1 2 3; do # 3 tries to write two times a matching password - ask_password ${FILE} - scolotemp=$scolopendro - ask_password "${FILE} (again)" - if [ "$scolotemp" = "$scolopendro" ]; then + tombpass=`exec_as_user tomb -q askpass ${FILE}` + tombpasstmp=$tombpass + tombpass=`exec_as_user tomb -q askpass "${FILE} (again)"` + if [ "$tombpasstmp" = "$tombpass" ]; then break; fi - unset $scolotemp - unset $scolopendro + unset tombpasstmp + unset tombpass done - if [ -z $scolopendro ]; then + if [ -z $tombpass ]; then error "passwords don't match, aborting operation" umount ${keytmp} losetup -d $nstloop @@ -427,8 +424,9 @@ create_tomb() { exit 1 fi - echo "${scolopendro}" | gpg --batch --no-options --no-tty --passphrase-fd 0 \ + echo "${tombpass}" | gpg --batch --no-options --no-tty --passphrase-fd 0 \ -o "${FILE}.gpg" -c -a ${keytmp}/tomb.tmp + if [ $? = 2 ]; then error "setting password failed: gnupg returns 2" umount ${keytmp} @@ -491,7 +489,7 @@ create_tomb() { cp -v ${FILE}.gpg ${usbkey_mount}/.tomb/ chmod -R go-rwx ${usbkey_mount}/.tomb umount ${usbkey_mount} - unset ${usbkey_mount} + unset usbkey_mount notice "Key ${FILE}.gpg succesfully saved on your USB" act "now we proceed opening your new tomb" KEY=${FILE}.gpg @@ -503,6 +501,8 @@ create_tomb() { else # kept besides (deprecated behaviour) act "now we proceed opening your new tomb" KEY=${FILE}.gpg + unset CMD2 + unset CMD3 mount_tomb ${FILE} fi @@ -515,7 +515,6 @@ mount_tomb() { return 1 elif [ -r $CMD2 ]; then tombfile=`basename $CMD2` - tombdir=`dirname $CMD2` else # try also adding a .tomb extension tombfile=${tombfile%%\.*}.tomb @@ -525,6 +524,8 @@ mount_tomb() { fi fi + tombdir=`dirname $CMD2` + file ${tombdir}/${tombfile} | grep -i 'luks encrypted.*cbc-essiv' 2>&1 >/dev/null if [ $? != 0 ]; then error "$CMD2 is not a valid tomb file, operation aborted" @@ -533,7 +534,7 @@ mount_tomb() { fi tombname=${tombfile%%\.*} - act "mounting tomb named after $tombname" + act "mounting tomb named $tombname" if [ $KEY ]; then tombkey="`basename $KEY`" @@ -564,14 +565,14 @@ mount_tomb() { fi if ! [ $CMD3 ]; then - tombmount=/media/`basename ${tombfile}` + tombmount=/media/${tombfile} act "mountpoint not specified, using default: $tombmount" elif ! [ -x $CMD3 ]; then error "mountpoint $CMD3 doesn't exist, operation aborted." if [ -n "$usbkey_mount" ]; then umount $usbkey_mount rmdir $usbkey_mount - unset $usbkey_mount + unset usbkey_mount fi return 1 else @@ -611,16 +612,16 @@ mount_tomb() { for c in 1 2 3; do if [ $c = 1 ]; then - ask_password ${keyname} + tombpass=`exec_as_user tomb -q askpass ${keyname}` else - ask_password "$keyname (retry $c)" + tombpass=`exec_as_user tomb -q askpass "$keyname (retry $c)"` fi - echo "${scolopendro}" \ + echo "${tombpass}" \ | gpg --batch --passphrase-fd 0 --no-tty --no-options \ - -d "${tombkeypath}" 2>/dev/null \ + -d "${tombkeypath}" \ | cryptsetup --key-file - luksOpen ${nstloop} ${mapper} - unset scolopendro + unset tombpass if [ -r /dev/mapper/${mapper} ]; then break; # password was correct @@ -631,7 +632,7 @@ mount_tomb() { if [ -r ${usbkey_mount}/.tomb/${tombkey} ]; then umount ${usbkey_mount} rmdir ${usbkey_mount} - unset ${usbkey_mount} + unset usbkey_mount fi if ! [ -r /dev/mapper/${mapper} ]; then @@ -644,7 +645,7 @@ mount_tomb() { act "encrypted storage filesystem check" fsck -p -C0 /dev/mapper/${mapper} act "tomb engraved as $tombname" - tune2fs -L ${tombname} /dev/mapper/${mapper} + tune2fs -L ${tombname} /dev/mapper/${mapper} > /dev/null mount -o rw,noatime,nodev /dev/mapper/${mapper} ${tombmount} @@ -681,17 +682,17 @@ encode_key() { # here user is prompted for key password for c in 1 2 3; do # 3 tries to write two times a matching password - ask_password ${FILE} - scolotemp=$scolopendro - ask_password "${FILE} (again)" - if [ "$scolotemp" = "$scolopendro" ]; then + tombpass=`exec_as_user tomb -q askpass ${FILE}` + tombpasstmp=$tombpass + tombpass=`exec_as_user tomb -q askpass "${FILE} (again)"` + if [ "$tombpasstmp" = "$tombpass" ]; then break; fi - unset $scolotemp - unset $scolopendro + unset tombpasstmp + unset tombpass done - if [ -z $scolopendro ]; then + if [ -z $tombpass ]; then error "passwords don't match, aborting operation." return 1 fi @@ -702,7 +703,7 @@ encode_key() { /^Comment/ {next} {print $0}' ${tombkey} \ | steghide embed --embedfile - --coverfile ${imagefile} \ - -p ${scolopendro} -z 9 -e serpent cbc + -p ${tombpass} -z 9 -e serpent cbc if [ $? != 0 ]; then error "encoding error: steghide reports problems" res=1 @@ -711,7 +712,7 @@ encode_key() { res=0 fi - unset scolopendro + unset tombpass return $res } @@ -731,11 +732,11 @@ decode_key() { notice "Decoding a key out of image $imagefile" for c in 1 2 3; do if [ $c = 1 ]; then - ask_password ${keyname} + tombpass=`exec_as_user tomb -q askpass ${keyname}` else - ask_password "$keyname (retry $c)" + tombpass=`exec_as_user tomb -q askpass "$keyname (retry $c)"` fi - steghide extract -sf ${imagefile} -p ${scolopendro} -xf - \ + steghide extract -sf ${imagefile} -p ${tombpass} -xf - \ | awk ' BEGIN { print "-----BEGIN PGP MESSAGE-----" @@ -753,7 +754,7 @@ print "-----END PGP MESSAGE-----" fi done - unset scolopendro + unset tombpass if [ $res != 0 ]; then error "nothing found." @@ -1030,6 +1031,7 @@ case "$CMD" in install) check_priv ; install_tomb ;; + askpass) ask_password $CMD2 $CMD3 ;; status) tomb-status ;; notify) tomb-notify $CMD2 $CMD3 ;; @@ -1039,4 +1041,4 @@ case "$CMD" in ;; esac # return codes from called functions -return $? +# return $? diff --git a/src/tomb-askpass.c b/src/tomb-askpass.c @@ -1,332 +0,0 @@ -/* Tomb askpass - - Derived from gtk-led-askpass.c version 0.9 - by Dafydd Harries <daf@muse.19inch.net>, 2003 2004 - (An ssh-askpass alike software) - - Based on ideas from ssh-askpass-gnome, by Damien Miller and Nalin Dahyabhai, - and on Jim Knoble's x11-ssh-askpass. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program 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. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, Inc., - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - See also: - - http://www.cgabriel.org/sw/gtk2-ssh-askpass/ - -- Jim Knoble's x11-ssh-askpass - http://www.cgabriel.org/sw/gtk2-ssh-askpass/ - -- Christopher Gabriel's gtk2-ssh-askpass - - Todo: - - - Internationalise. Probably entails autotoolising. - - Add more eye candy. - - Implement optional mouse/server grabbing. - - Alow overriding the title on the command line. - - Make the LED box a proper GTK+ widget. - -*/ - -#include <gdk/gdk.h> -#include <gtk/gtk.h> -#include <gdk/gdkkeysyms.h> - -/* The Tomb icon is an artwork by Jordi aka MonMort - a nomadic graffiti artist from Barcelona */ -#include <monmort.xpm> - -/* Title for the dialog. */ -#define TITLE "Unlocking tomb" - -/* Width of each LED. */ -#define LED_WIDTH 8 -/* Height of each LED. */ -#define LED_HEIGHT 16 -/* Space around and between LEDs. */ -#define LED_MARGIN 5 -/* Number of LEDs to have. */ -#define LED_COUNT 12 - -/* How many times to attempt to grab the keyboard before giving up. */ -#define GRAB_TRIES_MAX 10 -/* How long to sleep, in microseconds, in between keyboard grab attempts. */ -#define GRAB_SLEEP 100000 -/* Sleep length, in milliseconds, after Control-U press. */ -#define CLEAR_SLEEP 800 - -enum { - LED_STATE_OFF, - LED_STATE_GREEN, - LED_STATE_RED, - LED_STATES -}; - -GdkPixbuf *pb_monmort; - -GdkColor colours[LED_STATES] = { - /* LED_STATE_OFF */ - { 0, 0x3333, 0x6666, 0x3333 }, - /* LED_STATE_GREEN */ - { 0, 0x6666, 0xFFFF, 0x6666 }, - /* LED_STATE_RED */ - { 0, 0xDDDD, 0x3333, 0x3333 } -}; - - -void draw_led(GtkWidget *widget, gint state, guint offset) -{ - GdkGC *gc = gdk_gc_new(widget->window); - - /* Draw the border. */ - gdk_draw_rectangle(widget->window, - widget->style->fg_gc[GTK_WIDGET_STATE(widget)], - FALSE, - LED_MARGIN + offset * (LED_WIDTH + LED_MARGIN), - LED_MARGIN, - LED_WIDTH, - LED_HEIGHT); - - gdk_gc_set_rgb_fg_color(gc, &(colours[state])); - - /* Draw the inside rectangle. */ - gdk_draw_rectangle(widget->window, - gc, - TRUE, - LED_MARGIN + offset * (LED_WIDTH + LED_MARGIN) + 1, - LED_MARGIN + 1, - LED_WIDTH - 1, - LED_HEIGHT - 1); -} - -gboolean led_area_expose_handler(GtkWidget *led_area, GdkEventExpose *event, - GString *passphrase) -{ - gint i, width, height; - gint length = passphrase->len; - - gdk_drawable_get_size(GDK_DRAWABLE(led_area->window), &width, &height); - - /* Draw a focus indicator if appropriate. */ - if (GTK_WIDGET_HAS_FOCUS(led_area)) - gtk_paint_focus(led_area->style, led_area->window, - GTK_WIDGET_STATE(led_area), &(event->area), led_area, - "", 0, 0, width, height); - - /* Draw each LED. */ - for (i = 0; i < LED_COUNT; i++) { - /* This is the complicated bit. */ - gboolean on = ((length / LED_COUNT) % 2 == 0) ? - (length % LED_COUNT > i) : - (length % LED_COUNT <= i); - - draw_led(led_area, on ? LED_STATE_GREEN : LED_STATE_OFF, i); - } - - /* TRUE means not to propagate the event. */ - return TRUE; -} - -gboolean timeout_handler(GtkWidget *led_area) -{ - gtk_widget_queue_draw(led_area); - - return FALSE; -} - -void clear(GString *passphrase, GtkWidget *led_area) -{ - gint i; - - /* - Delete bit by bit to ensure erasure. g_string_erase() will overwrite - the last character with 0, so we shouldn't need to worry about leaving - sensitive data in memory. Note that the string may be empty. This is - so that the interface responds consistently. - */ - while (passphrase->len > 0) - g_string_erase(passphrase, passphrase->len - 1, 1); - - for (i = 0; i < LED_COUNT; i++) - draw_led(led_area, LED_STATE_RED, i); - - /* - Remove the redness after a delay. If an exposure is triggered, such as - by a key getting pressed, then the redraw will simply happen early. - */ - gtk_timeout_add(CLEAR_SLEEP, (GtkFunction)timeout_handler, led_area); -} - -gboolean led_area_key_press_handler(GtkWidget *led_area, GdkEventKey *event, - GString *passphrase) { - /* Obtain Unicode representation of key released. */ - gunichar c = gdk_keyval_to_unicode(event->keyval); - /* Determine whether the key released was printable. */ - gint isprint = g_unichar_isprint(c); - /* Obtain default modifier mask. */ - guint modifiers = gtk_accelerator_get_default_mod_mask(); - - if (event->keyval == GDK_Delete) { - clear(passphrase, led_area); - return TRUE; - } - - if ((event->state & modifiers) == GDK_CONTROL_MASK) { - - if (event->keyval == GDK_u) { - /* C-u -- delete everything. */ - clear(passphrase, led_area); - /* Return early in order to avoid the redraw. */ - return TRUE; - - } else { - - /* Unrecognised keypress. */ - return FALSE; - } - - } else if (event->keyval == GDK_BackSpace && passphrase->len > 0) { - /* - Backspace -- remove last character. See the comment above - about g_string_erase. - */ - g_string_erase(passphrase, passphrase->len - 1, 1); - } else if (isprint) { - /* Printable character. */ - g_string_append_unichar(passphrase, c); - } else { - /* Unrecognized keypress, propagate. */ - return FALSE; - } - - /* Trigger a redraw of the LED area. */ - gtk_widget_queue_draw(led_area); - - /* TRUE means not to propagate the event. */ - return TRUE; -} - -gboolean led_area_button_press_handler(GtkWidget *led_area, - GdkEventButton *event, gpointer data) -{ - gtk_widget_grab_focus(led_area); - - return TRUE; -} - -int main(int argc, char *argv[]) -{ - gint response, grab_tries, i; - char keyname[256]; - GString *passphrase = g_string_new(""); - GtkWidget *dialog, *alignment, *led_area; - GList tmplist; - - gtk_set_locale(); - gtk_init(&argc, &argv); - - if (argc > 1) { - snprintf(keyname,255,"%s",argv[1]); - } else { - sprintf(keyname,"unknown"); - } - /* - dialog - `- vbox (implicit) - `- aligment - `- led_area - */ - - /* Question dialog with no parent; OK and Cancel buttons. */ - dialog = gtk_message_dialog_new_with_markup - (NULL, 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK, - "Enter the password to unlock\n" - "<span font=\"Times 24\">%s</span>", keyname); - gtk_window_set_title(GTK_WINDOW(dialog), TITLE); - - // set and show the image icon - pb_monmort = gdk_pixbuf_new_from_xpm_data(monmort); - tmplist.data = (gpointer*)pb_monmort; - tmplist.prev = tmplist.next = NULL; - gtk_window_set_icon_list(GTK_WINDOW(dialog), &tmplist); - gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog), - gtk_image_new_from_pixbuf(pb_monmort)); - - /* Place the dialog in the middle of the screen. */ - gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); - /* OK is the default action. */ - gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); - - /* Add some spacing within the dialog's vbox. */ - gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 3); - - /* The alignment widget containing the drawing area. */ - alignment = gtk_alignment_new(0.5, 0.5, 0, 0); - gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), alignment); - - /* The drawing area for the LEDs. */ - led_area = gtk_drawing_area_new(); - gtk_container_add(GTK_CONTAINER(alignment), led_area); - /* Make the LED widget focusable. */ - GTK_WIDGET_SET_FLAGS(led_area, GTK_CAN_FOCUS); - /* Make the LED widget focused. */ - gtk_widget_grab_focus(led_area); - /* Make the LED widget receive key press and button press events. */ - gtk_widget_add_events(led_area, - GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK); - /* Set a size request. */ - gtk_widget_set_size_request(led_area, - LED_MARGIN + (LED_WIDTH + LED_MARGIN) * LED_COUNT, - LED_HEIGHT + LED_MARGIN * 2); - /* Set up handler for key releases. */ - g_signal_connect(G_OBJECT(led_area), "key_press_event", - G_CALLBACK(led_area_key_press_handler), passphrase); - /* Set up handler for button releases. */ - g_signal_connect(G_OBJECT(led_area), "button_press_event", - G_CALLBACK(led_area_button_press_handler), NULL); - /* Set up handler for expose events. */ - g_signal_connect(G_OBJECT(led_area), "expose_event", - G_CALLBACK(led_area_expose_handler), passphrase); - - - /* Show all the widgets. */ - gtk_widget_show_all(dialog); - /* Put the dialog on the screen now for the grab to work. */ - gtk_widget_show_now(dialog); - - /* Grab the keyboard */ - gdk_keyboard_grab(GTK_WIDGET(dialog)->window, FALSE, GDK_CURRENT_TIME); - - /* Make the dialog stay on top. */ - gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE); - - /* Run the dialog. */ - response = gtk_dialog_run(GTK_DIALOG(dialog)); - - /* Ungrab the keyboard. */ - gdk_keyboard_ungrab(GDK_CURRENT_TIME); - - /* If the OK button was pressed, print the passphrase. */ - if (response == GTK_RESPONSE_OK) - g_print("%s\n", passphrase->str); - - /* Scrub the passphrase, if any. */ - for (i = 0; i < passphrase->len; i++) - passphrase->str[i] = '\0'; - - if (response == GTK_RESPONSE_OK) - return 0; - - return 1; -} -