#!/bin/zsh # # Tomb # # a simple commandline tool to create and operate encrypted storage # # Copyleft (C) 2007-2010 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.8 DATE=Aug/2010 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 # usb auto detect # tested on ubuntu 10.04 - please test and patch on other systems if you can ask_usbkey() { echo "looking for key $1 on usb" 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, attaching " 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 4 |awk '/ sd.:/ { print $3 }'` echo echo -n "usb key attached, mounting " # what that it is mounted mounted=false while [ "$mounted" != "true" ]; do cat /proc/mounts | tail -n2 | grep -q "^/dev/$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 echo "usb key mounted on $usbmount" # check if the key is there if [ -r ${usbmount}/$1 ]; then echo "key found!" export enc_key="${usbmount}/${1}" return 0 else echo "key not found on usb" fi return 1 } # 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 } ############################ ### main() ### notice "Tomb - simple commandline tool for encrypted storage" act "version $VERSION ($DATE) by Denis J. Roio " act "" func "invoked with args \"$*\" " func "running on `date`" OPTS=`getopt -o hvs:k: -n 'tomb' -- "$@"` while true; do case "$1" in -h) 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 "" 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 cat $0 | awk ' BEGIN { license=0 } /^# This source/ { license=1 } { if(license==1) print " " $0 } /MA 02139, USA.$/ { license=0 } ' act "" exit 0 ;; -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 id | grep root > /dev/null if [ $? != 0 ]; then error "This program must be run as root to produce results" exit 1 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" > ${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 act "formatting Ext3 filesystem" cryptsetup --key-file ${keytmp} --cipher aes luksOpen ${nstloop} tomb.tmp rm -f ${keytmp} # cryptsetup luksDump ${nstloop} mkfs.ext3 -q -F -j -L "`hostname`-`date +%s`" /dev/mapper/tomb.tmp if [ $? = 0 ]; then act "OK, encrypted storage succesfully formatted with Ext3 filesystem" else act "error formatting storage file with Ext3 filesystem" fi sync cryptsetup luksClose tomb.tmp losetup -d ${nstloop} notice "done creating $FILE encrypted storage (using Luks dm-crypt AES/SHA256)" } # mount_crypto() { # if ! [ -r $FILE ]; then # error "file or partition $FILE does not exists" # exit 0 # fi # # check if its a file or partition # file ${FILE} | grep block > /dev/null # if [ $? = 0 ]; then # act "$FILE is a partition" # mount_crypto_partition # else # act "$FILE is a loopback file" # mount_crypto_file # fi # } 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 error "you need to specify a MOUNTPOINT for the mount command" exit 0 fi if ! [ -x $MOUNT ]; then error "mountpoint $MOUNT doesn't exist" exit 0 fi nstloop=`losetup -f` losetup -f ${FILE} act "check for a valid LUKS encrypted device" cryptsetup isLuks ${nstloop} if [ $? = 0 ]; then # it's a LUKS encrypted nest, see cryptsetup(1) # 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 ".tomb/$enc_key" if ! [ -r "${enc_key}" ]; then losetup -d ${nstloop} sleep 5 return fi 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.ext3 -p -C0 /dev/mapper/${mapper} mount -t ext3 /dev/mapper/${mapper} ${MOUNT} # TODO: possible mount options to try out: # -o rw,noatime,nodev,data=writeback,commit=30 # -o rw,noatime,nodev notice "encrypted storage $FILE succesfully mounted on $MOUNT" else error "$FILE is not a valid Luks encrypted storage file" 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 tomb found to be mounted" 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" } case "$CMD" in create) create_tomb ;; format) format_tomb ;; mount) mount_tomb ;; umount) umount_tomb ;; unmount) umount_tomb ;; *) error "command \"$CMD\" not recognized" act "try -h for help" ;; esac