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.
This commit is contained in:
Jaromil 2011-02-14 10:24:31 +01:00
parent 5290fd9e8d
commit 1a6fd48def
4 changed files with 75 additions and 410 deletions

View File

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

View File

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

146
src/tomb
View File

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

View File

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