#!/bin/zsh # # Tomb, the Crypto Undertaker # # a tool to easily operate file encryption of private and secret data # # Copyleft (C) 2007-2011 Denis Roio # # 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. VERSION=0.9 DATE=Jan/2011 # PATH=/usr/bin:/usr/sbin:/bin:/sbin # standard output message routines # it's always useful to wrap them, in case we change behaviour later notice() { echo "[*] $1"; } act() { echo " . $1"; } error() { echo "[!] $1"; } func() { if [ $DEBUG ]; then echo "[D] $1"; fi } # which dd command to use which dcfldd > /dev/null if [ $? = 0 ]; then DD="dcfldd" else DD=dd fi # which wipe command to use which wipe > /dev/null if [ $? = 0 ]; then WIPE=(wipe -f -s -q) else WIPE=(rm -f) fi # usb auto detect using dmesg # tested on ubuntu 10.04 - please test and patch on other systems if you can ask_usbkey() { notice "Waiting 1 minute for a usb key to connect" echo -n " . please insert your usb key " exec_as_user notify-send -i monmort \ -u normal -h string:App:Tomb \ -h double:Version:${VERSION} \ -t 60 \ "Insert your USB KEY" \ "Tomb is waiting 1 minute for you to insert an external key." plugged=false c=0 while [ "$plugged" != "true" ]; do dmesg | tail -n 12 | grep -q 'new.*USB device' if [ $? = 0 ]; then plugged=true; fi echo -n "." sleep 1 c=`expr $c + 1` if [ $c -gt 60 ]; then echo error "timeout." export usbkey_mount=none return 1; fi done echo echo -n " . usb key inserted, opening " c=0 attached=false while [ "$attached" != "true" ]; do dmesg | tail -n 3| grep -q 'Attached.*removable disk' if [ $? = 0 ]; then attached=true; fi echo -n "." sleep 1 c=`expr $c + 1` if [ $c -gt 15 ]; then echo error "timeout." export usbkey_mount=none return 1; fi done # get the first partition usbpart=`dmesg |tail -n 8 | grep ' sd.:' |cut -d: -f2 |tr -d ' '` # wait that is mounted c=0 mounted=false while [ "$mounted" != "true" ]; do cat /proc/mounts | tail -n 2 | grep -q $usbpart if [ $? = 0 ]; then mounted=true; fi echo -n "." sleep .5 c=`expr $c + 1` if [ $c -gt 30 ]; then echo error "timeout." export usbkey_mount=none return 1; fi done # check where it is mounted usbmount=`cat /proc/mounts | awk -v p=$usbpart '{ if( $1 == "/dev/" p) print $2 }'` echo act "usb key mounted on $usbmount" export usbkey_mount=$usbmount return 0 } # user interface (just to ask the password) ask_password() { exec_as_user xhost 2>/dev/null if [ $? = 0 ]; then # we have access to the X display exec_as_user which tomb-askpass if [ $? = 0 ]; then keyname=`basename $enc_key | cut -d. -f1` export scolopendro="`exec_as_user tomb-askpass $keyname`" return elif [ -x /usr/bin/ssh-askpass ]; then # debian has this export scolopendro="`exec_as_user 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 fi # 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 tomb-notify() { if [ -z $1 ]; then exec_as_user notify-send -i monmort \ -u low -h string:App:Tomb \ -h double:Version:${VERSION} \ "Tomb version $VERSION" \ "Hi, I'm the Undertaker. Let's start setting your Crypt?" else exec_as_user notify-send -i monmort ${@} fi } # drop privileges exec_as_user() { func "executing as user '$SUDO_USER': ${(f)@}" sudo -u $SUDO_USER ${@} } ############################ ### main() ### notice "Tomb - simple commandline tool for encrypted storage" act "version $VERSION ($DATE) by Jaromil @ dyne.org" func "invoked with args \"${(f)@}\" " func "running on `date`" OPTS=`getopt -o hvs:k:S -n 'tomb' -- "$@"` while true; do case "$1" in -h) act "" notice "Syntax: tomb [options] command [file] [mountpoint | size]" act "" notice "Options:" act "-h print this help" act "-v print out the version information for this tool" act "-s size of the storage file when creating one (in MBytes)" act "-k path to the key to use for decryption" act "-S acquire super user rights if possible" act "" notice "Commands:" act "create create a new encrypted storage FILE and keys" act "open open an existing tomb FILE on MOUNTPOINT" act "close closes the tomb on MOUNTPOINT" echo; exit 2 ;; -v) # print out the GPL license in this file act "" cat $0 | awk ' BEGIN { license=0 } /^# This source/ { license=1 } { if(license==1) print " " $0 } /MA 02139, USA.$/ { license=0 } ' act "" exit 0 ;; -S) GETPRIV=true; shift 1 ;; *) break ;; esac done id | grep root > /dev/null if [ $? != 0 ]; then error "This program must be run as root to produce results" if [ "$GETPRIV" = "true" ]; then which gksu > /dev/null if [ $? = 0 ]; then act "Using gksu for root execution of 'tomb ${(f)@}'" gksu "tomb ${(f)@}" exit $? fi which sudo > /dev/null if [ $? = 0 ]; then act "Using sudo for root execution of 'tomb ${(f)@}'" sudo "tomb ${(f)@}" exit $? fi exit 1 else exit 1 fi fi # now process the real options OPTS=`getopt -o hvs:k:S -n 'tomb' -- "$@"` while true; do case "$1" in -s) SIZE=$2; shift 2 ;; -k) KEY=$2; shift 2 ;; --) shift; break ;; *) CMD=$1; FILE=$2; MOUNT=$3; # compat with old args CMD2=${2}; CMD3=${3}; break ;; esac done if [ -z $CMD ]; then error "first argument missing, use -h for help" tomb-notify exit 0 fi func "command: $CMD for file $FILE" tombdir=${HOME}/.tomb tombtab=${tombdir}/fstab if ! [ -r ${tombtab} ]; then act "creating tomb filesystem tab in your home" mkdir -p ${HOME}/.tomb echo "# entombed filesystem information, see man tomb (TODO)" > ${tombtab} echo "# format here is similar to the system wide fstab" >> ${tombtab} echo "# " >> ${tombtab} fi create_tomb() { notice "Creating a new tomb" if [ -z $SIZE ]; then if [ $MOUNT ]; then SIZE=$MOUNT else act "No size specified, summoning the Tomb Undertaker to guide us in the creation." tomb-open & disown exit 0 fi fi # make sure the file has a .tomb extension FILE="${FILE%\.*}.tomb" SIZE_4k=`expr \( $SIZE \* 1000 \) / 4` act "Generating ${FILE} of ${SIZE}Mb (${SIZE_4k} blocks of 4Kb)" # TODO: use dd_rescue $DD if=/dev/urandom bs=4k count=${SIZE_4k} of=${FILE} if [ $? = 0 -a -e ${FILE} ]; then act "OK: `ls -lh ${FILE}`" else error "Error creating the tomb ${FILE}, operation aborted." exit 1 fi mkdir -p /tmp/tomb modprobe dm-crypt modprobe aes-i586 nstloop=`losetup -f` # get the number for next loopback device losetup -f ${FILE} # allocates the next loopback for our file keytmp=`tempfile` act "Generating secret key..." act "this operation takes time, keep using this computer on other tasks," act "once done you will be asked to choose a password for your tomb." cat /dev/urandom | dd bs=1 count=256 of=${keytmp} notice "Setup your secret key file ${FILE}.gpg" tomb-notify "The Tomb key is being forged:" "please set your password." # here user is prompted for key password gpg -o "${FILE}.gpg" --no-options --openpgp -c -a ${keytmp} while [ $? = 2 ]; do gpg -o "${FILE}.gpg" --no-options --openpgp -c -a ${keytmp} done act "formatting Luks mapped device" # dm-crypt only supports sha1 # but we can use aes-cbc-essiv with sha256 for better security # see http://clemens.endorphin.org/LinuxHDEncSettings cryptsetup --batch-mode \ --cipher aes-cbc-essiv:sha256 --key-size 256 \ luksFormat ${nstloop} ${keytmp} if ! [ $? = 0 ]; then act "operation aborted." exit 0 fi cryptsetup --key-file ${keytmp} --cipher aes luksOpen ${nstloop} tomb.tmp ${WIPE[@]} ${keytmp} notice "Your tomb is ready on ${FILE} and secured with key ${FILE}.gpg" act "Would you like to save the key on an external usb device?" act "This is recommended for safety:" act "always keep the key in a different place than the door!" act "If you answer yes, you'll need a USB KEY now: (yes/no)" tomb-notify "Tomb has forged a key." "Would you like to save it on USB?" echo -n " > " read -q if [ $? = 0 ]; then ask_usbkey if ! [ -e ${usbkey_mount} ]; then error "cannot save the key in a separate place, move it yourself later." else mkdir -p ${usbkey_mount}/.tomb cp -v ${FILE}.gpg ${usbkey_mount}/.tomb/ chmod -R go-rwx ${usbkey_mount}/.tomb ${WIPE[@]} ${FILE}.gpg fi fi # cryptsetup luksDump ${nstloop} act "formatting your Tomb with Ext4 filesystem" mkfs.ext4 -q -F -j -L "${FILE%\.*}-`hostname`" /dev/mapper/tomb.tmp if [ $? = 0 ]; then act "OK, encrypted storage succesfully formatted" else act "error formatting Tomb" fi sync cryptsetup luksClose tomb.tmp losetup -d ${nstloop} notice "done creating $FILE encrypted storage (using Luks dm-crypt AES/SHA256)" tomb-notify "The Tomb is ready!" "We will now open your new Tomb for the first time." tomb mount $FILE } mount_tomb() { if [ -z $KEY ]; then enc_key="`basename ${FILE}.gpg`" else enc_key="$KEY" fi notice "mounting $FILE on mountpoint $MOUNT" if [ -z $MOUNT ]; then MOUNT=/media/`basename ${FILE}` act "mountpoint not specified, using default: $MOUNT" mkdir -p $MOUNT elif ! [ -x $MOUNT ]; then error "mountpoint $MOUNT doesn't exist" exit 1 fi # check if key file is present if ! [ -r "${enc_key}" ]; then error "encryption key ${enc_key} not found on disk" error "use -k option to specify which key to use" error "or provide a usb key, or press ctrl-c to abort" ask_usbkey # returns usbkey_mount, now check if the key is there if [ -r ${usbkey_mount}/.tomb/${enc_key} ]; then enc_key=${usbkey_mount}/.tomb/${enc_key} notice "key found on ${enc_key}" else error "key is missing." exit 1 fi fi nstloop=`losetup -f` losetup -f ${FILE} act "check for a valid LUKS encrypted device" cryptsetup isLuks ${nstloop} if [ $? != 0 ]; then # is it a LUKS encrypted nest? see cryptsetup(1) error "$FILE is not a valid Luks encrypted storage file" exit 1 fi modprobe dm-crypt modprobe aes-i586 # save date of mount in minutes since 1970 mapdate="`date +%s`" mapdate="`echo ${mapdate}/60 | bc -l | cut -d. -f1`" mapper="tomb.`basename $FILE | cut -d. -f1`.$mapdate.`basename $nstloop`" notice "Password is required for key ${enc_key}" for c in 1 2 3; do ask_password echo "${scolopendro}" \ | gpg --passphrase-fd 0 --no-tty --no-options \ -d "${enc_key}" 2>/dev/null \ | cryptsetup --key-file - luksOpen ${nstloop} ${mapper} unset scolopendro if [ -r /dev/mapper/${mapper} ]; then break; # password was correct fi done if ! [ -r /dev/mapper/${mapper} ]; then error "failure mounting the encrypted file" losetup -d ${nstloop} exit 1 fi act "encrypted storage filesystem check" fsck -p -C0 /dev/mapper/${mapper} mount -o rw,noatime,nodev /dev/mapper/${mapper} ${MOUNT} notice "encrypted storage $FILE succesfully mounted on $MOUNT" ( exec_as_user tomb-status ${mapper} ${FILE} ${MOUNT} ) & disown exit 0 } umount_tomb() { if [ -z $FILE ]; then how_many_tombs="`ls /dev/mapper/tomb* 2>/dev/null | wc -w`" if [ $how_many_tombs = 0 ]; then error "there is no open tomb to be closed" exit 0 elif [ $how_many_tombs = 1 ]; then mapper=`ls /dev/mapper/tomb* 2>/dev/null` FILE=`mount | grep $mapper | awk '{print $3}'` else error "too many tombs mounted, please specify which to unmount:" ls /dev/mapper/tomb* exit 1 fi else if [ -r $FILE ]; then mapper=$FILE elif [ -r /dev/mapper/${FILE} ]; then mapper=/dev/mapper/${FILE} else error "tomb not found: $FILE" error "please specify an existing /dev/mapper/tomb*" exit 1 fi # FILE=`mount | grep $mapper | awk '{print $3}'` fi # if [ "$mapper" = "" ]; then # error "$FILE is not mounted" # return # fi # mapper=`basename $mapper` # if ! [ -r /dev/mapper/${mapper} ]; then # error "tomb doesn't seems to be mounted:" # error "${mapper} is not present in /dev/mapper" # exit 1 # fi basemap=`basename $mapper` tombname=`echo ${basemap} | cut -d. -f2` errno=`umount ${mapper}` if ! [ $? = 0 ]; then tomb-notify "Tomb '$tombname' is too busy." \ "Close all applications and file managers, then try again." exit 1 fi cryptsetup luksClose $basemap if ! [ $? = 0 ]; then error "error occurred in cryptsetup luksClose ${basemap}" exit 1 fi losetup -d "/dev/`echo $basemap | cut -d. -f4`" # echo ${nstloop} | grep loop 1>/dev/null 2>/dev/null # # if it's a loopback then we need to do losetup -d # if [ $? = 0 ]; then # losetup -d ${nstloop} # if ! [ $? = 0 ]; then # error "error occurred in losetup -d ${nstloop}" # exit 0 # fi # fi notice "crypt storage ${mapper} unmounted" tomb-notify "Tomb closed: $tombname" "Your bones will Rest In Peace." exit 0 } # install mime-types, bells and whistles for the desktop # see http://developers.sun.com/solaris/articles/integrating_gnome.html # and freedesktop specs install() { # TODO: distro package deps (for binary) # debian: zsh, cryptsetup, libgtk2.0-0, libnotify-bin act "updating mimetypes..." cat < /tmp/dyne-tomb.xml Tomb encrypted volume Tomb crypto key EOF xdg-mime install /tmp/dyne-tomb.xml xdg-icon-resource install --context mimetypes --size 32 monmort.xpm monmort xdg-icon-resource install --size 32 monmort.xpm dyne-monmort rm /tmp/dyne-tomb.xml act "updating desktop..." cat < /usr/share/applications/tomb.desktop [Desktop Entry] Version=1.0 Type=Application Name=Tomb crypto undertaker GenericName=Crypto undertaker Comment=Keep your bones safe Exec=tomb-open %U TryExec=tomb-open Icon=monmort.xpm Terminal=false Categories=Utility;Security;Archiving;Filesystem; MimeType=application/x-tomb-volume; EOF update-desktop-database act "updating menus..." cat < /etc/menu/tomb ?package(tomb):command="tomb" icon="/usr/share/pixmaps/monmort.xpm" needs="cryptsetup" \ section="Applications/Accessories" title="Tomb" hints="Crypto" \ hotkey="Tomb" EOF update-menus act "updating mime info..." cat < /usr/share/mime-info/tomb.keys # actions for encrypted tomb storage application/x-tomb-volume: open=tomb-open %f view=tomb-open %f icon-filename=monmort.xpm short_list_application_ids_for_novice_user_level=tomb EOF cat < /usr/share/mime-info/tomb.mime # mime type for encrypted tomb storage application/x-tomb-volume ext: tomb application/x-tomb-key ext: tomb.gpg EOF cat < /usr/lib/mime/packages/tomb application/x-tomb-volume; tomb-open '%s'; priority=8 EOF update-mime act "updating application entry..." cat < /usr/share/application-registry/tomb.applications tomb command=tomb-open name=Tomb - Crypto Undertaker can_open_multiple_files=false expects_uris=false requires_terminal=false mime-types=application/x-tomb-volume,application/x-tomb-key EOF act "Tomb is now installed." } case "$CMD" in create) create_tomb ;; mount) mount_tomb ;; open) mount_tomb ;; umount) umount_tomb ;; unmount) umount_tomb ;; close) umount_tomb ;; install) install ;; status) tomb-status ;; notify) tomb-notify $CMD2 $CMD3 ;; *) error "command \"$CMD\" not recognized" act "try -h for help" break ;; esac exit 0