Motivation:
To unlock my system hardrive with a removable usb drive as the key.
Prerequisites:
1.) You must already have installed a luks encrypted system via the alternate cd. (or for the brave, manually)
2.) a usb key. any size, to hold the key. (sd card support is experimental)
Procedure:
1.) First we must backup all the files we are going to edit, so we have somewhere to restore from:
Code:
sudo cp /boot/initrd.img-$(uname -r) /boot/initrd.img-$(uname -r).orig
sudo cp /etc/crypttab /etc/crypttab.orig
sudo cp /etc/initramfs-tools/modules /etc/initramfs-tools/modules.orig
2.) mount the usb drive the new key is going to reside on, and create a new key. I use a partition that is labeled 'quays' in my example, and I store the key in a file called 'crypt.0.key', in the directory '.keys' .
Code:
cd /media/quays/.keys/
dd if=/dev/random of=crypt.0.key bs=1c count=32
3.) Now that we have a new random key, we have to add it to the luks encrypted partition. My luks encrypted partition is /dev/sda2 in the example. This may ask for 2 passwords, one for sudo, and the luks encryption password. We assume the default encryption install, since the installer puts the password in keyslot 0, and this new key will be in keyslot 1 by default. note the keyslot number that is created, as this will be important for reverting.
Code:
sudo cryptsetup luksAddKey /dev/sda2 /media/quays/.keys/crypt.0.key
4.) This is where we have done some magic for you, crypt setup includes an option in the /etc/crypttab to specifiy a script to retrieve the key for an encrytpted drive. We have written such a script, and we use this to scan for a specified usb drive, and obtain the specified key. and return that to cryptsetup to unlock the partition. Please feel free to look over this script, so you can see the options available to you (as not all are documented here) Save this in the file /usr/local/sbin/crypt-usb:
Code:
#!/bin/busybox ash
# File:crypt-usb
################################################################################
# Variables / config:
################################################################################
myname=$(basename $0) # script name.
default_timeout='15' # timeout (s)
prompt="Enter passphrase to unlock the disk $cryptsource ($crypttarget): " # passphrase prompt
prompt_msg="Searching for removable keys ..." # startup msg
mnt="/keymount" # temp mount location
memory="/key_memory"
#
################################################################################
################################################################################
# Functions:
################################################################################
####
# Return true if usplash is running, otherwise return false.
[ -x /sbin/usplash_write ] && usplash_exists='1'
usplash_running()
{
[ -z "$usplash_exists" ] && return 1
pidof "usplash" >/dev/null
return $?
}
####
# Write output to the console. (usplash not running)
write_to_console()
{
read system_uptime no_var < /proc/uptime
printf '[%8s0000] %s: %s\n' "$system_uptime" "$myname" "$@" >&2
}
####
# Write output to usplash
write()
{
usplash_running && ( /sbin/usplash_write "TEXT $@" )
write_to_console "$@"
return 0
}
####
# Set the verbosity according to what the kernel says.
fixup_verbosity()
{
if [ "$(expr match "$(cat /proc/cmdline)" '.*quiet')" -gt "0" ]; then
/sbin/usplash_write "VERBOSE off" 2>/dev/null
else
/sbin/usplash_write "VERBOSE on" 2>/dev/null
fi
}
####
# Ask for the password from with in usplash.
usplash_askpass()
{ # askpass for usplash (return 1 if failure of usplash)
FAIL_NO_USPLASH=1 /sbin/usplash_write "VERBOSE on" || return 1
/sbin/usplash_write "CLEAR"
/sbin/usplash_write "TEXT $1"
until [ -n "$BREAK" ]; do
# Read Character From Input
if ! FAIL_NO_USPLASH=1 /sbin/usplash_write "INPUTCHAR"; then
fixup_verbosity
return 1
fi
ch=$( awk 'BEGIN { RS = "\x04"; FS = "" } ;
{
if ($0 ~ /[\x0A\x0C\x0D]/) { printf "ENTER" }
else if ($0 ~ /[\x7F\x08]/) { printf "DEL" }
else { printf $0 }
}' /dev/.initramfs/usplash_outfifo )
# Handle Characters (break on enter, delete 1 on backspace/delete)
if [ "$ch" = "ENTER" ]; then
BREAK="1"
/sbin/usplash_write "CLEAR"
elif [ "$ch" = "DEL" ]; then
text=$(expr substr "$text" "1" $( expr "${#text}" "-" "1" ))
shown=$(expr substr "$shown" "1" $( expr "${#shown}" "-" "1" ))
else
if [ -n "$ch" ]; then
text="$text$ch"
shown="$shown*"
else
/bin/sleep 0.2
continue
fi
fi
if [ -n "$usplash_showpass" ]; then
/sbin/usplash_write "CLEAR"
/sbin/usplash_write "TEXT $prompt $shown"
fi
done
if usplash_running && [ "$(expr match "$(cat /proc/cmdline)" '.*quiet')" -gt "0" ]; then
/sbin/usplash_write "CLEAR"
/sbin/usplash_write "VERBOSE off"
fi
printf '%s' "$text"
return 0
}
####
# Ask for a password on the console.
ask_pass()
{
if usplash_running; then
if usplash_askpass "$1"; then
return 0
else
exec <$(tty) >$(tty) 2>$(tty)
fi
fi
echo -n "$1" >$(tty)
read -rs text <$(tty)
printf '%s' "$text"
return 0
}
####
# handle the exit conditions, success, we found the key. or fail, and take the
# user provided fail action.
cleanup()
{
if [ -n "$2" ]; then
write "$2"
fi
case "$1" in
askpass|askpassQuiet)
[ ! "$1" = "askpassQuiet" ] && write "DEFERRED to askpass"
echo -n "1" >$memory 2>/dev/null
ask_pass "$prompt"
exit 0
;;
fail|retry)
write "Key retreival failed. Retrying ..."
;;
success)
write "SUCCESS key found"
;;
halt|poweroff|power-off)
case "$1" in
halt)
exit_prompt="Encryption key not found. System Halted"
exit_action=/bin/halt
;;
poweroff|power-off)
exit_prompt="Encryption key not found. System will Power Down"
exit_action=/bin/poweroff
;;
esac
# clear usplash progress bar
if usplash_running ; then
/sbin/usplash_write "PROGRESS 0"
/sbin/usplash_write "TEXT-URGENT $exit_prompt"
else
write "$exit_prompt"
fi
/bin/sleep 2
( $exit_action >/dev/null 2>/dev/null & )
/bin/sleep 30
exit 0
;;
reboot)
if usplash_running ; then
/sbin/usplash_write "PROGRESS 0"
FAIL_NO_USPLASH=1 /sbin/usplash_write \
"INPUTENTER Encryption key not found. Press ENTER to Reboot" \
&& cat /dev/.initramfs/usplash_outfifo
else
read system_uptime no_var < /proc/uptime
printf '[%8s0000] %s: %s' "$system_uptime" "$myname" \
"Encryption key not found. Press ENTER to Reboot" >&2
read -rst600 no_var <$(tty)
fi
( /bin/reboot >/dev/null 2>/dev/null & )
/bin/sleep 30
exit 0
;;
initramfs)
/sbin/usplash_write "QUIT"
chvt 1
modprobe i8042
modprobe atkbd
PS1='(initramfs) ' /bin/sh -i </dev/console >/dev/console 2>&1
;;
esac
exit 0
}
####
# Parse and sanity check the options we were supplied.
parse_opts()
{
[ -z "$1" ] && exit 1
param="$1"
crypt_dev="$( expr match ${param%%:*} '\(\/dev\/.\+\)$' 2>/dev/null)"
param="${param#*:}"
crypt_key="$( expr match ${param%%:*} '\([^/\].*\)$' 2>/dev/null)"
param="${param#*:}"
# Fail on unusable crypttab
[ -z "$crypt_dev" -o -z "$crypt_key" ] && \
cleanup $crypt_fail "bad crypttab"
crypt_fail=$( expr match ":$param:" \
".*:\(askpass\|fail\|retry\|power-\?off\|reboot\|halt\|initramfs\):" )
fstype=$( expr match ":$param:" '.*:\(auto\|vfat\|ext[234]\|xfs\):' )
timeout=$( expr match ":$param:" '.*:\([0-9]\+\):' )
usplash_showpass=$( expr match ":$param:" '.*:\(showpass\):' )
# Set defaults
timeout="${timeout:-15}"
crypt_fail="${crypt_fail:-askpass}"
fstype="${fstype:-auto}"
}
####
# Unmount and remove the mount directory.
unmount()
{
if umount -f "$mnt" ; then
rm -rf "$mnt"
fi
}
####
# Search either for the specified block device, or for the user to hit ESC.
search_wait_escape()
{
num="0"
time=$(expr "$timeout" "*" "4")
ESC_KEY=$(echo -ne "\x1B")
# Loop and check for block device or ESC key
until [ "$time" -lt "1" ] ; do
# Check for block device $crypt_dev
[ -b "$crypt_dev" ] && break
# read 1 character from input in 1 second
if ! usplash_running ; then # For console interaction
read -rst0.25 -n1 response <$(tty) || unset response
[ "$response" = "$ESC_KEY" ] && return 1
else
/bin/sleep 0.25
while FAIL_NO_USPLASH=1 usplash_write "INPUTCHAR"; do
response=$( awk '
BEGIN { RS = "\x04" } ; { FS = "" } ; { printf "%s", $0 }
' /dev/.initramfs/usplash_outfifo )
[ -z "$response" ] && break
[ "$response" = "$ESC_KEY" ] && return 1
done
fi
# end read 1 response character
if [ -n "$2" ]; then
write "$2"
fi
time=$(expr "$time" "-" "1")
done
return 0
}
#
################################################################################
################################################################################
# MAIN:
################################################################################
parse_opts "$1"
if [ ! -b "$crypt_dev" ] ; then
[ "$(cat $memory 2>/dev/null)" -eq "1" ] && cleanup 'askpassQuiet'
fi
write "ALIVE"
if [ "$crypt_fail" = "askpass" ]; then
prompt_msg="$prompt_msg press ESC to cancel"
fi
if usplash_running ; then
/sbin/usplash_write "TIMEOUT 60000"
/sbin/usplash_write "VERBOSE on"
/sbin/usplash_write "CLEAR"
write "$prompt_msg"
fixup_verbosity
else
write "$prompt_msg"
fi
search_wait_escape
if [ "$?" -ne "0" ] ; then # ESC pressed
cleanup $crypt_fail
elif [ ! -b "$crypt_dev" ] ; then # timed out no block device found
cleanup $crypt_fail "no block device found"
else # block device found
write "block device found"
fi
if ! mkdir $mnt 2>/dev/null ; then
cleanup $crypt_fail "unable to make directory $mnt"
fi
fsopts="noexec,sync"
[ "$fstype" = "auto" ] && fstype=$(/lib/udev/vol_id -t $crypt_dev)
[ -z "$fstype" ] && cleanup $crypt_fail "unable to detect the file system type"
if ! mount -rt $fstype -o $fsopts $crypt_dev $mnt 2>/dev/null ; then
rm -rf $mnt
cleanup $crypt_fail "unable to mount $crypt_dev as $fstype with $fsopts"
else
write "mounted block device"
fi
if [ ! -e "$mnt/$crypt_key" ] ; then
unmount
cleanup $crypt_fail "keyfile not found"
else
write "key file found"
fi
key=$( cat $mnt/$crypt_key 2>/dev/null )
unmount
[ -z "$key" ] && cleanup $crypt_fail "keyfile is blank"
printf '%s' "$key"
if usplash_running; then
/sbin/usplash_write "VERBOSE on"
/sbin/usplash_write "STATUS ok"
if [ "$(expr match "$(cat /proc/cmdline)" '.*quiet')" -gt "0" ]; then
/bin/sleep 0.5
/sbin/usplash_write "CLEAR"
/sbin/usplash_write "VERBOSE off"
fi
fi
cleanup success
exit 0
#
################################################################################
5.) Make sure the file is owned by root, and is executeable:
Code:
sudo chown root:root /usr/local/sbin/crypt-usb
sudo chmod 755 /usr/local/sbin/crypt-usb
6.) edit /etc/crypttab to use the keyscript:
Original:
Code:
sda2_crypt /dev/disk/by-uuid/595899a3-eee5-4106-ba7a-737bd3a21189 none luks
We replace the keyword 'none' with the parameters taken by crypt-usb, namely the device file of the usbkey that we are using, followed by a colon, and followed by the path to the key, relative to the root of the removable media. And we add the option 'keyscript' to point to our key script.
Edited:
Code:
sda2_crypt /dev/disk/by-uuid/595899a3-eee5-4106-ba7a-737bd3a21189 /dev/disk/by-label/quays:.keys/crypt0.key luks,keyscript=/usr/local/sbin/crypt-usb
7.) We need to add the usb modueles to the initrd for it to recognize and mount usb drives. edit the file /etc/initramfs-tools/modules, and add:
Code:
sd_mod
scsi_mod
usb-storage
8.) Rebuild the initrd, and reboot
Code:
sudo update-initramfs -u
sudo reboot
Now when you boot your system it will wait 15 seconds to find the key, and if it doesn't, it will fall back to asking you for the password.
Recovery:
If for some reason your system does not boot, and goes to initramfs, then reboot, and edit the initrd line in your kernel, and append .orig to it.
When booting, hit esc to see the menu, (enter a password if required), e to edit the selected entry, select the line that refers to the initrd, and hit e to edit it. add '.orig' and hit esc to go back, and then b to boot.
Reverting:
So you've tried it, and don't like it.... going back is easy.
1.) first we should remove the key from the encrypted partion. This will stop the key we created from unlocking the drive. This is where we will need the keyslot number you noted in step 3 of the installation, by default this will be keyslot 1.
Code:
sudo cryptsetup luksDelKey /dev/sda2 1
2.) then we need to copy the original files we edited back, and remove the keyscript we created.
Code:
sudo cp /boot/initrd.img-$(uname -r).orig /boot/initrd.img-$(uname -r)
sudo cp /etc/crypttab.orig /etc/crypttab
sudo cp /etc/initramfs-tools/modules.orig /etc/initramfs-tools/modules
sudo rm /usr/local/sbin/crypt-usb
3.) Then we update the initramfs, and reboot, and all is back to normal.
Code:
sudo update-initramfs -u
sudo reboot
Conclusion:
There is much much more that can be done with this, one direction we are looking into is making this use an sdcard as the storage for the key ( which should just be a matter of adding the right modules to the initramfs. ) We encourage anyone to take a look at the script we wrote, and see how it can be improved. This has been tested on hardy(i386 and amd64) and intrepid (amd64 only, but it should work on i386). If it works on other releases, we would like to know, but we haven't tested it, so we can't garuntee it.
Bookmarks