Programming and Scripting :: Updated tripl script



For your orgastic overstimulation, here is the updated  version of the tripl script referred to here , since it doesn't have a home as yet.

If you tried the earlier version (in the loopaes extension), note the cli switches have changed. Type tripl -h

It tests for correct gnupg and losetup etc which will slow it down on very old machines; if anyone asks I will add a switch to disable these checks.

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))


original here.