#!/bin/zsh # # Tomb # # a simple commandline tool to create and operate encrypted storage # # Copyleft (C) 2007-2011 Denis Roio # # Tomb development is supported by: NOONE. # Would you like to support it and engrave your name on this software? # Contact me! # # thanks to Gabriele "Asbesto Molesto" Zaverio # for suggesting the perfect name for this tool. # # 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 "looking for key $1 on usb" exec_as_user tomb-notify "Tomb needs a key." "Plug your usb key in the computer to open $1" echo -n " . please insert your usb key " plugged=false while [ "$plugged" != "true" ]; do dmesg | tail -n 12 | grep -q 'new.*USB device' if [ $? = 0 ]; then plugged=true; fi echo -n "." sleep .5 done echo echo -n " . usb key inserted, opening " attached=false while [ "$attached" != "true" ]; do dmesg | tail -n 3| grep -q 'Attached.*removable disk' if [ $? = 0 ]; then attached=true; fi echo -n "." sleep .5 done # get the first partition usbpart=`dmesg |tail -n 8 | grep ' sd.:' |cut -d: -f2 |tr -d ' '` # wait that is mounted 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 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() { xhost 2>/dev/null if [ $? = 0 ]; then # we have access to the X display if [ -x /usr/bin/ssh-askpass ]; then # debian has this 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 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 } # 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 "mount mount an existing storage FILE on MOUNTPOINT" act "umount unmounts a mounted storage 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; break ;; esac done if [ -z $CMD ]; then error "first argument missing, use -h for help" 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 in ${FILE}" if [ -z $SIZE ]; then if [ $MOUNT ]; then SIZE=$MOUNT else error "size is not specified, please use -s option when creating a tomb" exit 0 fi fi SIZE_4k=`expr \( $SIZE \* 1000 \) / 4` act "Generating file of ${SIZE}Mb (${SIZE_4k} blocks of 4Kb)" # TODO: use dd_rescue and/or dcfldd $DD if=/dev/urandom bs=4k count=${SIZE_4k} of=${FILE} # 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 nest file ${FILE} : (dd if=/dev/zero of=${FILE} bs=4k count=$SIZE_4k)" sleep 4 exit 0 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, computer use helps to gather more entropy." cat /dev/random | dd bs=1 count=256 of=${keytmp} notice "Setup your secret key file ${FILE}.gpg" # 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 read on ${FILE} and secured with key ${FILE}.gpg" act "now plug an external usb device to save the key separately:" ask_usbkey if ! [ -w ${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 rm -rf ${FILE}.gpg fi # cryptsetup luksDump ${nstloop} act "formatting your Tomb with Ext4 filesystem" mkfs.ext4 -q -F -j -L "`hostname`-`date +%s`" /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)" } 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 act "mountpoint not specified, using default: /media/$FILE" MOUNT=/media/${FILE} mkdir -p $MOUNT elif ! [ -x $MOUNT ]; then error "mountpoint $MOUNT doesn't exist" exit 0 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 0 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" tail /var/log/messages losetup -d ${nstloop} return fi act "encrypted storage filesystem check" fsck.ext4 -p -C0 /dev/mapper/${mapper} mount -t ext4 -o rw,noatime,nodev /dev/mapper/${mapper} ${MOUNT} notice "encrypted storage $FILE succesfully mounted on $MOUNT" if [ $DISPLAY ]; then exec_as_user tomb-status ${FILE} ${MOUNT} fi } 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" return 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* echo return fi else if ! [ -r $FILE ]; then error "tomb not found: $FILE" error "please specify the full /dev/mapper/tomb* path" return fi mapper=$FILE 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 umount ${FILE} if ! [ $? = 0 ]; then error "error occurred in umount ${FILE}" exit 0 fi cryptsetup luksClose ${mapper} if ! [ $? = 0 ]; then error "error occurred in cryptsetup luksClose ${mapper}" exit 0 fi losetup -d "/dev/`echo $mapper | 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" exec_as_user tomb-notify "Tomb closed:" "${FILE} Rest In Peace." } # install mime-types, bells and whistles for the desktop # see http://developers.sun.com/solaris/articles/integrating_gnome.html # and freedesktop specs install() { notice "Installing Tomb on your desktop" 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=true 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 '%s'; needsterminal; 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=true mime-types=application/x-tomb-volume,application/x-tomb-key EOF } 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 ;; *) error "command \"$CMD\" not recognized" act "try -h for help" ;; esac