#!/bin/zsh # # Tomb # # a simple commandline tool to create and operate encrypted storage # # Copyleft (C) 2007-2010 Denis Roio # # 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 # 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 } # 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 } # checks if a file is writable # differs from -w coz returns true if does not exist but can be created is_writable() { # arg: filename file=$1 writable=false if [ -r $file ]; then # file exists if [ -w $file ]; then writable=true; fi else # file does not exist touch $file 1>/dev/null 2>/dev/null if [ $? = 0 ]; then writable=true rm $file fi fi if [ x$writable = xtrue ]; then echo "true" else echo "false" fi } # appends a new line to a text file, if not duplicate # it sorts alphabetically the original order of line entries # defines the APPEND_FILE_CHANGED variable if file changes append_line() { # args: file new-line # first check if the file is writable # this also creates the file if doesn't exists if [ `is_writable $1` = false ]; then error "file $1 is not writable" error "can't insert line: $2" return fi tempfile="`basename $1`.append.tmp" # create a temporary file and add the line there cp $1 /tmp/$tempfile echo "$2" >> /tmp/$tempfile # sort and uniq the temp file to temp.2 cat /tmp/$tempfile | sort | uniq > /tmp/${tempfile}.2 SIZE1="`ls -l /tmp/$tempfile | awk '{print $5}'`" SIZE2="`ls -l /tmp/${tempfile}.2 | awk '{print $5}'`" if [ $SIZE != $SIZE ]; then # delete the original rm -f $1 # replace it cp -f /tmp/${tempfile}.2 $1 # signal the change APPEND_FILE_CHANGED=true fi # remove the temporary files rm -f /tmp/$tempfile rm -f /tmp/${tempfile}.2 # and we are done } PATH=/usr/bin:/usr/sbin:/bin:/sbin ############################ ### 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]" 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 key to be used for decryption (defaults in ~/.tomb)" act "" notice "Commands:" act "format format a PARTITION with NAME and generate keys" 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 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 format_tomb() { notice "Formatting partition $FILE as an encrypted storage" act "give it a name:" read -s fsname act " `fdisk -l | grep ${FILE}`" mkdir -p /tmp/tomb modprobe dm-crypt modprobe aes-i586 act "Generating secret key..." key="`basename ${FILE}`" mkdir -p ${HOME}/.tomb cat /dev/urandom | strings | dd bs=1 count=256 of=/tmp/tomb/secret notice "Setup your secret key file ${key}.gpg" # here user is prompted for password gpg -o "${HOME}/.tomb/${key}.gpg" --no-options --openpgp -c -a /tmp/tomb/secret while [ $? = 2 ]; do gpg -o "${HOME}/.tomb/${key}.gpg" --no-options --openpgp -c -a /tmp/tomb/secret done act "formatting Luks partition" # dm-crypt only supports sha1 # but we can use aes-cbc-essiv with sha256 for better security # see http://clemens.endorphin.org/LinuxHDEncSettings cryptsetup --cipher aes-cbc-essiv:sha256 --key-size 256 luksFormat ${FILE} /tmp/tomb/secret if ! [ $? = 0 ]; then act "operation aborted." exit 0 fi cryptsetup --key-file /tmp/tomb/secret --batch-mode --cipher aes luksOpen ${FILE} tomb.tmp rm -f /tmp/tomb/secret cryptsetup luksDump ${FILE} mkfs.ext3 -F -L "${fsname}" -j /dev/mapper/tomb.tmp if [ $? = 0 ]; then act "OK, encrypted partition succesfully formatted with Ext3 filesystem" else act "error formatting ${FILE} Ext3 filesystem" fi cryptsetup luksClose tomb.tmp notice "done formatting $FILE encrypted partition (using Luks dm-crypt AES/SHA256)" act "encrypted key stored in file ${tombdir}/${key}.gpg" append_line ${tombtab} \ "${FILE} ${tombdir}/`basename ${FILE}` aes-cbc-essiv:sha256 none ${tombdir}/${key}.gpg" } create_tomb() { if [ -z $SIZE ]; then error "size is not specified, please use -s option when creating a storage file" exit 0 else act "size set to $SIZE MB" fi SIZE_4k=`expr \( $SIZE \* 1000 \) / 4` notice "generating file of ${SIZE}Mb (${SIZE_4k} blocks of 4Kb)" act "dd if=/dev/zero of=${FILE} bs=4k count=$SIZE_4k" # now with progress bar! dd if=/dev/zero bs=4k count=${SIZE_4k} of=${FILE} if [ $? = 0 -a -e ${FILE} ]; then act "OK: `ls -l ${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 act "Generating secret key..." cat /dev/urandom | strings | dd bs=1 count=256 of=/tmp/tomb/secret clear notice "Setup your secret key file ${FILE}.gpg" # here user is prompted for password gpg -o "${FILE}.gpg" --no-options --openpgp -c -a /tmp/tomb/secret while [ $? = 2 ]; do gpg -o "${FILE}.gpg" --no-options --openpgp -c -a /tmp/tomb/secret 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 --cipher aes-cbc-essiv:sha256 --key-size 256 luksFormat ${nstloop} /tmp/tomb/secret if ! [ $? = 0 ]; then act "operation aborted." exit 0 fi act "formatting Ext3 filesystem" cryptsetup --key-file /tmp/tomb/secret --batch-mode --cipher aes luksOpen ${nstloop} tomb.tmp rm -f /tmp/tomb/secret cryptsetup luksDump ${nstloop} mkfs.ext3 -F -j -L "dyne:nest" /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 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="~/.tomb/`basename $FILE`" 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" error "use -k option to specify which key to use" losetup -d ${nstloop} sleep 5 return 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" append_line /var/run/tombs "${MOUNT} ${mapper} ${nstloop}" 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}'` fi else mapper=`mount | grep $FILE | awk '{print $1}'` 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