Code Sample |
#!/bin/bash # $Id: tripl,v0.56 4/23/2008 01:55:01 wdef Exp $ # tripl - single or multiple encryption with loop-aes-ciphers # (c) 2006-2008 wdef v0.54 <email suppressed for forum post> # Wrapper to manage single or multiple encrypted partitions using loop-aes v3.x (multikey). # Automates gpg key generation, allocation and setup/pulldown of loops, layered embedding of keys, # filesystem checking etc. Does not write to /etc/fstab. Note: multiple encryption can use a lot of # cpu at times. Single or double encryption with a good password is sufficient (really!). # Script skips loops that are already in use and only pulls down encrypted loops chained to device # on umounting. Blowfish is not supported since it does not work with multiline keychains in loopaes # and is not recommended for large amounts of data. # Tries hard to prevent loop collisions if you are also using some loops for other things. # This is free software. No warranty. Use AYOR. #=========================================================== ### Changelog v.3 # Set example .config in ${HOME} on first run # - modules settings and MAXLOOPS removed from config, now automatic. # - improved loopfree function # - explicit error messages on failed umount or loop pulldown, fixed exit values from pull_down. # - added option for manually setting paths to utils. # - should work on systems without /sbin in PATH. # ## v0.4 # Added test function for increased iterations with gpg # - Now only pulls down loops chained to device (function dechain_loops) # ## v0.5 # Switches changed to use getopts: -f -muknrh # Try very hard to prevent loop collisions: # - fixed issue with wrong PREVIOUSLOOP when more than one encrypted device mounted. # - fixed dechain_loops # - loopfree now looks in any field of losetup output for loop numbers # Checks that device is there.. # Allow 3 tries to get password right # Clearer messages. # VERBOSE setting. # -f option allows pointing at alternate config file. # ## v0.54 # Fixed loop pulldown after making new partition. # Checks device free before allowing new setup # Other stuff # ## v0.55 # Gentle option for forced umount # ## v0.56 # Added ext3 # To Do: # add signal trap to pulldown loop if device overwrite interrupted #===================// FUNCTIONS //============================ help(){ cat <<"EOF" tripl - single or multiple encryption with loop-aes (c) 2006-2008 wdef v0.56 Usage: tripl [-f <file> ] [-muknrh ] Option: -m = mount -u = unmount -k = make key -n = set up encrypted partition (destroys data!) -r = check, repair encrypted filesystem -f = use <file> as triplrc instead of ~/.triplrc -h = this Set partition name, keys, order of ciphers, mountpoint and MODE (1,2,3 for single,double,triple encryption) in triplrc EOF } set_example_config(){ cat <<EOF # Make these settings in $HOME/.triplrc, where $HOME is root's home dir. DEVICE=/dev/your_device_here MOUNTPT=/mnt/your_mountpoint_here #MODE=3 # Triple encryption MODE=2 # Double encryption #MODE=1 # Single encryption # You can rearrange the order of ciphers, but only for a new setup: #- you'll have to destroy data by runnning tripl -n again if you change this (so back up data first). CIPHER[1]=serpent128 CIPHER[2]=twofish128 CIPHER[3]=AES128 # Run tripl -k to make a key, then set it here: key[1]=/path_to_your_key1 key[2]=/path_to_your_key2 key[3]=/path_to_your_key3 FS=ext2 # Filesystem: ext2, ext3 #FS=ext3 #EMBED=no EMBED=yes # If enabled, the user only need specify one external gpg-encrypted key (key[1]) in user settings. # Other keys will be automatically generated for each encryption layer by tripl -n (prompts user # for passwords) and successively embedded in the encryption layer previous to that being set up. # This also means an attacker has to crack the first encryption layer just to get the # encrypted key for the second, and so on. No effect if MODE=1 # If disabled, a seperate external (detached) key should be created with tripl -k for each # encryption layer and set in this config file prior to running tripl -n #FORCE=OFF # No attempt to interrupt processes to do umount FORCE=GENTLE # Flush buffers and signal politely before resorting to sigkill #FORCE=BRUTE # Only to enable an emergency rapid umount. VERBOSE=yes # Show what is being done. GPGHOME="${HOME}/.gnupg" DISABLE_NEW=yes # Disable new encrypted paritition switch as a double failsafe measure to prevent partition destruction # Enable when you're sure you need it. # If non-standard /path/to/utils, set here: # losetup= # mount= # umount= # fsck= # modprobe= # fdisk= EOF } check_losetup(){ if $(which strings &>/dev/null); then strings $losetup | grep -q -s multi-key-v3 return $? elif grep --version | grep -q 'GNU grep' 2>/dev/null; then grep -q -a -s multi-key-v3 $losetup return $? else echo "Error: can't check losetup compatibility" echo "Install strings or GNU grep before proceeding." exit 1 fi } run_checks(){ if [ $EUID -ne 0 ]; then echo "You're not root."; exit 1; fi if [ $# -eq 0 ]; then help; exit 1; fi if ! which gpg &>/dev/null; then echo "Can't find GnuPG"; exit 1; fi if [ $MAXLOOPS -eq 0 ]; then echo "Can't find any loop devices"; exit 1;fi } set_modules(){ # Set modules to match ciphers for p in $(seq 1 $MODE); do case ${CIPHER[p]} in aes128|AES128) MODULE[$p]=loop;; serpent128|SERPENT128) MODULE[$p]=loop_serpent;; twofish128|TWOFISH128) MODULE[$p]=loop_twofish;; *) echo "Error: unsupported cipher ${CIPHER[p]}"; exit 1;; esac done } check_device_free(){ if $losetup -a | grep -wq ${DEVICE} || grep -wq ${DEVICE} /proc/mounts; then echo "Error: ${DEVICE} is in use." exit 1 fi } conf_checks(){ if [ "$CLI_CONFIG" != yes ]; then if [ -f "${HOME}/.triplrc" ]; then . ${HOME}/.triplrc else set_example_config >${HOME}/.triplrc echo "Edit ${HOME}/.triplrc for your desired setup" echo "Then run tripl again." exit 0 fi fi # Default utils locations if not set or in PATH losetup=${losetup:=/sbin/losetup} mount=${mount:=/bin/mount} umount=${umount:=/bin/umount} fsck=${fsck:=/sbin/fsck} modprobe=${modprobe:=/sbin/modprobe} fdisk=${fdisk:=/sbin/fdisk} # Do some basic checks if [ -z "${DEVICE}" ]; then "DEVICE is unset"; exit 1; fi if [ -z "${MOUNTPT}" ]; then "MOUNTPT is unset"; exit 1; fi if [ ! -d "${MOUNTPT}" ]; then echo "${MOUNTPT} does not exist."; exit 1;fi if [ ! -b "${DEVICE}" ]; then echo "$DEVICE is not a valid block device"; exit 1; fi if ! $fdisk -l | awk '/^\/dev/{print $1}'| grep -wq ${DEVICE}; then echo "Can't find device ${DEVICE}. Is it connected?" exit 1 fi case $FS in ext2|ext3);; *) echo "Invalid filesystem setting \"$FS\""; exit 1;; esac if ! check_losetup; then echo "Error: your $losetup is incompatible with loop-aes v3.x" echo "See the loop-aes README for details." exit 1 fi set_modules } loopfree(){ # Could(?) replace this whole fn with: # perl -e 'map($t[$_]=1, map(/loop(\d)/,`losetup -a`)); $i=0; $i++ until !$t[$i]; ($i==8 and exit 1) || print $i' # Safest - just get all the used loop numbers, regardless of what field they appear in # This should prevent evil from somehow managing something silly like absent loop on one # loop of chain, leaving next loop in chain orphaned, and loopfree would then report the first (busy) # loop as free => overwrite encrypted loop (it happened). A=$($losetup -a | perl -lane 'while (/\/dev\/loop(\d)/g) { print $1 }' | sort | uniq) if [ -z "$A" ]; then # Loop 0 is free NEXTLP=0 return 0 fi Y=0 for i in $A; do if [ $i -eq $Y ]; then (( Y = i + 1 )) continue else # Loop $Y is free NEXTLP=$Y return 0 fi done if [ $Y -eq $MAXLOOPS ]; then # No free loops return 1 else # loop $Y is free NEXTLP=$Y return 0 fi } dechain_loops(){ # Outputs encrypted loop devices chained to device $1 export A=$1 NUMLOOPS=0 until [ -z "${A}" ]; do LOOP=$($losetup -a | awk -F: '$3 ~ ENVIRON["A"] {print $1}') if [ -n "${LOOP}" ]; then echo "${LOOP}" # important: output includes newline (( NUMLOOPS++ )) fi export A=${LOOP} done } pull_down(){ X=$1 if grep -wq "${MOUNTPT}" /proc/mounts; then case $FORCE in BRUTE) # just kill 'em. Could damage filesystem and/or lose some data fuser -m -k ${MOUNTPT};; GENTLE) # Be gentle with me s[0]=-SIGHUP; s[1]=-SIGTERM; s[2]=-SIGKILL sync; sync q=0 for SIG in ${s[*]}; do fuser -m -k $SIG ${MOUNTPT} sleep 1 if fuser -ms ${MOUNTPT}; then echo -n "$SIG failed, " if [ $q -eq 2 ]; then echo "Error: processes still running on ${MOUNTPT}" exit fi echo "trying ${s[q+1]} .." else break fi (( q = q + 1 )) done;; OFF);; *) echo "Error: unrecognized FORCE configuration \"$FORCE\"";; esac $umount ${MOUNTPT} grep -wq "${MOUNTPT}" /proc/mounts && echo "Error: umount failed!" else [ "$VERBOSE" = yes ] && echo "${MOUNTPT} is not in use" fi # Pull down our encrypted loops chained on top of device dechain_loops ${DEVICE} | sort -r | while read D; do [ "$VERBOSE" = yes ] && echo "Pulling down ${D} .." $losetup -d $D X=$? if [ $X -ne 0 ]; then echo "Error: free up ${D} and try again." exit $X fi done && exit $X } ask_user(){ while true; do echo -n "$1? (y/N) " read case $REPLY in y|Y|y*|Y*) if [ "$1" = Abort ]; then exit 1; fi;; n|N|n*|N*) if [ "$1" = Abort ]; then break; fi;; *) echo "Invalid response";; esac done } check_gpg_iterations(){ echo echo x | gpg --no-tty --passphrase-fd 3 3< <(echo whatever) --symmetric >${gpgtest} TESTSTR=$(gpg --no-tty --passphrase-fd 3 3< <(echo whatever) --decrypt -v -v <${gpgtest} 2>&1 | grep -E "salt.+count") ITERFLAG=${TESTSTR##*[ ]} rm -f ${gpgtest} case $ITERFLAG in # newer gnupg versions put () around iteration flag 208|\(208\)) echo "Good, GnuPG is using increased iterations flag = $ITERFLAG";; 96|\(96\)) echo "GnuPG is using only standard iterations flag = $ITERFLAG" echo "See loop-aes README for details." ask_user Abort;; *) echo "WTF?? unknown gpg iteration counts flag $ITERFLAG"; exit 1;; esac } makekey(){ check_gpg_iterations &>/dev/tty head -c 2925 /dev/random | uuencode -m - | head -n 66 | tail -n 65 \ | gpg --symmetric -a return } checkloops(){ NUMBUSY=$($losetup -a|wc -l) (( NUMFREELOOPS = MAXLOOPS - NUMBUSY )) if [ $NUMFREELOOPS -lt $MODE ]; then echo "Insufficient loops free ($NUMFREELOOPS)" echo "Require $MODE free loop device(s)." exit 1 fi } overwrite_partition(){ checkloops loopfree || pull_down 1 LP=/dev/loop$NEXTLP head -c 15 /dev/urandom | uuencode -m - | head -n 2 | tail -n 1 | losetup -p 0 -e AES128 $LP ${DEVICE} dd if=/dev/zero of=$LP bs=4k conv=notrunc 2>/dev/null losetup -d $LP } setup_loops(){ [ "${1}" != new ] && check_device_free # for new setup, this has already been checked checkloops echo if [ $EMBED = yes ]; then if [ ! -e "${key[1]}" ]; then echo "Error: Can't find key ${key[1]} - need one external key for embedded mode." exit 1 fi if [ "${1}" = new ]; then [ "$VERBOSE" = yes ] && echo ">>>>>> Using key ${key[1]} for loop1 <<<<<<" else echo "Enter $MODE password(s):" fi else echo "Enter $MODE password(s):" fi for k in $(seq 1 $MODE); do $modprobe ${MODULE[k]} || pull_down 1 loopfree || pull_down 1 TOPLOOP="/dev/loop$NEXTLP" if [ $k -gt 1 ]; then # Sanity check: PL=$(dechain_loops ${DEVICE}| tail -n 1) if [ ${PREVIOUSLOOP} != ${PL} ]; then echo "[$k] Error! dechain_loops says previous loop is ${PL}" echo "But here previousloop = ${PREVIOUSLOOP}" exit 1 fi fi if [ $EMBED = yes ]; then if [ $k -eq 1 ]; then # No offset key on first loop [ "$VERBOSE" = yes ] && echo "[$k] Setting up ${TOPLOOP} on ${DEVICE} .." p=0 while ! $losetup -e ${CIPHER[1]} -K ${key[1]} -G ${GPGHOME} ${TOPLOOP} ${DEVICE}; do (( p++ )) if [ $p -eq 3 ]; then echo "3rd failed attempt, exiting .."; pull_down 1; fi echo "Try again ($p) .." done else if [ "${1}" = new ]; then echo echo ">>>>>> [$k] Making embedded key on ${PREVIOUSLOOP} <<<<<<" echo "Enter new password at three prompts .." yes "" | dd of=${PREVIOUSLOOP} bs=512 count=16 makekey | dd of=${PREVIOUSLOOP} conv=notrunc fi [ "$VERBOSE" = yes ] && echo "[$k] Setting up ${TOPLOOP} on ${PREVIOUSLOOP} .." p=0 while ! $losetup -e ${CIPHER[k]} -K ${PREVIOUSLOOP} -o 8192 -G ${GPGHOME} ${TOPLOOP} ${PREVIOUSLOOP}; do (( p++ )) if [ $p -eq 3 ]; then echo "3rd failed attempt, exiting .."; pull_down 1; fi echo "Try again ($p) .." done fi else if [ ! -e "${key[k]}" ]; then echo "[$k] Error: Can't find key ${key[k]}" pull_down 1 fi [ $k -eq 1 ] && PREVIOUSLOOP=${DEVICE} [ "$VERBOSE" = yes ] && echo "[$k] Setting up ${TOPLOOP} on ${PREVIOUSLOOP} .." p=0 while ! $losetup -e ${CIPHER[k]} -K ${key[k]} -G ${GPGHOME} ${TOPLOOP} ${PREVIOUSLOOP}; do (( p++ )) if [ $p -eq 3 ]; then echo "3rd failed attempt, exiting .."; pull_down 1; fi echo "Try again ($p) .." done fi PREVIOUSLOOP=${TOPLOOP} done } #=====================// MAIN //================================== gpgtest=/tmp/gpgtest.$RANDOM.$$ MAXLOOPS=$(ls /dev/loop* | wc -l) run_checks $* # Parse cli options ARGS="$@" # Catch malformed options not having 2 chars for P in $ARGS; do case $P in -*) if [ ${#P} -ne 2 ]; then echo "Unknown option $P. Try tripl -h"; exit 1 fi;; esac done if echo $ARGS | grep -q 'f'; then case $ARGS in -f*):;; *f*) echo "Error: -f option must be first" exit 1;; esac fi CLI_CONFIG="" while getopts ":f:nmukrh" Option; do case $Option in f) . ${OPTARG} || exit 1 [ "$VERBOSE" = yes ] && echo "Using config file ${OPTARG} .." CLI_CONFIG=yes;; n) conf_checks if [ $DISABLE_NEW = yes ]; then echo "New partition switch disabled in user settings" exit 0 fi check_device_free echo echo "WARNING: this will destroy all data on $DEVICE !" echo "~~~~~~~" while true; do echo -n "Last chance to exit. Are you sure you want to proceed? (YeS/n) " read case $REPLY in YeS) break;; n|N|n*|N*) exit 0;; y|y*|Y|Yes|YE*) echo "You must type YeS to proceed.";; *) echo "Invalid response.";; esac done echo "Preparing device ${DEVICE} .. pls wait .." overwrite_partition setup_loops new echo echo ">>>>>> Making filesystem on ${TOPLOOP} <<<<<<" mkfs -t $FS ${TOPLOOP} pull_down 0;; m) conf_checks if grep -wq "${MOUNTPT}" /proc/mounts; then echo "${MOUNTPT} in use."; exit 1; fi setup_loops $mount -t $FS ${TOPLOOP} ${MOUNTPT} if grep -wq "${TOPLOOP}" /proc/mounts; then echo "OK"; else pull_down 1; fi;; u) conf_checks pull_down 0;; k) conf_checks while true; do echo -n "Enter path and filename of new key (CNTRL-C = quit): " read DIR=$(dirname ${REPLY}) if [ -e "${REPLY}" ]; then echo "File $REPLY already exists."; continue fi if [ ! -d "${DIR}" ]; then echo "Invalid path ${DIR}"; continue fi if [ ! -w "${DIR}" ]; then echo "${DIR} not writeable"; continue fi break done echo "Making key ${REPLY} .." makekey >${REPLY} if [ $? -eq 0 ]; then echo "Done." echo "Set your new key location at top of the script." else rm -f ${REPLY} # gpg will provide error messages if it fails. exit 1 fi;; r) conf_checks if grep -wq "${MOUNTPT}" /proc/mounts; then echo "${MOUNTPT} in use."; exit 1; fi echo "Repairing encrypted filesystem on ${DEVICE} .." setup_loops $fsck -t $FS -C -f -y ${TOPLOOP} echo "Done." pull_down 0;; h) help; exit 0;; *) A=( echo "$@" ) BADOPT=${A[OPTIND-1]} echo "Unknown option $BADOPT. Try tripl -h"; exit 1;; esac done shift $(($OPTIND - 1)) |