#!/bin/sh # This script sets up an ecryptfs mount in a user's ~/Private # # Originally ecryptfs-setup-pam-wrapped.sh by Michael Halcrow, IBM # # Ported for use on Ubuntu by Dustin Kirkland # Copyright (C) 2008 Canonical Ltd. # Copyright (C) 2007-2008 International Business Machines PRIVATE_DIR="Private" WRAPPING_PASS="LOGIN" PW_ATTEMPTS=3 MESSAGE="Enter your login passphrase" CIPHER="aes" KEYBYTES="16" FNEK= # Zero out user-defined GREP_OPTIONS, such as --line-number GREP_OPTIONS= usage() { echo echo "Usage:" echo " $0 [-f|--force] [-w|--wrapping] [--nopwcheck] [-n|--no-fnek]" echo " [-u|--username USER] [-l|--loginpass LOGINPASS]" echo " [-m|--mountpass MOUNTPASS]" echo echo " -f, --force Force overwriting of an existing setup" echo " -w, --wrapping Use an independent wrapping passphrase," echo " different from the login passphrase" echo " -n, --no-fnek Do not encrypt filenames; If this flag is" echo " omitted, and the kernel supports filename" echo " encryption, then filenames will be encrypted" echo " -u, --username Username for encrypted private mountpoint," echo " defaults to yourself" echo " -l, --loginpass Login/Wrapping passphrase for USER," echo " used to wrap MOUNTPASS" echo " --nopwcheck Do not check the validity of the specified" echo " login password (useful for LDAP user accounts)" echo " --noautomount Setup this user such that the encrypted private" echo " directory is not automatically mounted on login" echo " --noautoumount Setup this user such that the encrypted private" echo " directory is not automatically unmounted at" echo " logout" echo " -m, --mountpass Passphrase for mounting the ecryptfs directory," echo " defaults to randomly generated $KEYBYTES bytes" echo " -b, --bootstrap Bootstrap a new user's entire home directory" echo " Generates a random mount passphrase, which" echo " will be wrapped when the new login passphrase" echo " is set. SHOULD ONLY BE CALLED FROM 'adduser'." echo " --undo Provide instructions on how to undo an" echo " encrypted private setup" echo echo " Be sure to properly escape your parameters according to your" echo " shell's special character nuances, and also surround the" echo " parameters by double quotes, if necessary." echo exit 1 } undo_msg() { echo " In the event that you want to remove your eCryptfs Private Directory setup, you will need to very carefully perform the following actions manually: 1. Obtain your Private directory mountpoint $ PRIVATE=\`cat ~/.ecryptfs/Private.mnt 2>/dev/null || echo \$HOME/Private\` 2. Ensure that you have moved all relevant data out of your \$PRIVATE directory 3. Unmount your encrypted private directory $ ecryptfs-umount-private 4. Make your Private directory writable again $ chmod 700 \$PRIVATE 5. Remove \$PRIVATE, ~/.Private, ~/.ecryptfs Note: THIS IS VERY PERMANENT, BE VERY CAREFUL $ rm -rf \$PRIVATE ~/.Private ~/.ecryptfs 6. Uninstall the utilities (this is specific to your Linux distribution) $ sudo apt-get remove ecryptfs-utils libecryptfs0 " } error() { echo "ERROR: $1" 1>&2 exit 1 } error_testing() { rm -f "$1" >/dev/null /sbin/umount.ecryptfs_private >/dev/null error "$2" exit 1 } random_passphrase () { bytes=$1 # Pull $1 of random data from /dev/urandom, # and convert to a string of hex digits od -x -N $bytes --width=$bytes /dev/urandom | head -n 1 | sed "s/^0000000//" | sed "s/\s*//g" } filename_encryption_available() { version=$(cat /sys/fs/ecryptfs/version 2>/dev/null) [ -z "$version" ] && error "Can't get ecryptfs version, ecryptfs kernel module not loaded?" [ $(($version & 0x100)) -eq 0 ] && return 1 return 0 } filename_encryption_available && FNEK="--fnek" if [ ! -z "$SUDO_USER" ]; then USER="$SUDO_USER" fi while [ ! -z "$1" ]; do case "$1" in -u|--username) USER="$2" shift 2 ;; -l|--loginpass) LOGINPASS="$2" shift 2 ;; -m|--mountpass) MOUNTPASS="$2" shift 2 ;; -w|--wrapping) WRAPPING_PASS="INDEPENDENT" MESSAGE="Enter your wrapping passphrase" shift 1 ;; -f|--force) FORCE=1 shift 1 ;; --nopwcheck) NOPWCHECK=1 shift 1 ;; --noautomount) NOAUTOMOUNT=1 shift 1 ;; --noautoumount) NOAUTOUMOUNT=1 shift 1 ;; --undo) undo_msg exit 0 ;; -b|--bootstrap) [ `whoami` = "root" ] || error "You must be root to bootstrap encrypt a home directory" BOOTSTRAP=1 MOUNTPASS=`random_passphrase $KEYBYTES` RANDOM_MOUNTPASS=1 shift 1 ;; -n|--no-fnek) FNEK= shift 1 ;; *) usage ;; esac done # Prompt for the USER name, if not on the command line and not in the env if [ -z "$USER" ]; then while [ true ]; do echo -n "Enter the username: " USER=`head -n1` echo if [ -z "$USER" ]; then echo "ERROR: You must provide a username" continue else # Verify that the user exists if ! id "$USER" >/dev/null; then echo "ERROR: User [$USER] does not exist" continue fi break fi done else # Verify that the user exists id "$USER" >/dev/null || error "User [$USER] does not exist" fi # Check if user is member of ecryptfs group if ! groups "$USER" | sed -e 's| |\n|g' | grep -q '^ecryptfs$'; then error "User needs to be a member of ecryptfs group" fi # Obtain the user's home directory HOME=`getent passwd "$USER" | awk -F: '{print $6}'` if [ ! -d "$HOME" ]; then error "User home directory [$HOME] does not exist" fi if [ "$BOOTSTRAP" = "1" ]; then # If we want to encrypt the entire homedir, we need the .ecryptfs # config dir elsewhere, but linked into the homedir mkdir -p -m 700 /var/lib/ecryptfs/$USER ln -sf /var/lib/ecryptfs/$USER $HOME/.ecryptfs MOUNTPOINT="$HOME" else mkdir -m 700 $HOME/.ecryptfs MOUNTPOINT="$HOME/$PRIVATE_DIR" fi # Check for previously setup private directory if [ -s "$HOME/.ecryptfs/wrapped-passphrase" -a "$FORCE" != "1" ]; then error "wrapped-passphrase file already exists, use --force to overwrite." fi if [ -s "$HOME/.ecryptfs/$PRIVATE_DIR.sig" -a "$FORCE" != "1" ]; then error "$PRIVATE_DIR.sig file already exists, use --force to overwrite." fi # Check for active mounts CRYPTDIR="$HOME/.$PRIVATE_DIR" grep -qs "$MOUNTPOINT " /proc/mounts && error "[$MOUNTPOINT] is already mounted" grep -qs "$CRYPTDIR " /proc/mounts && error "[$CRYPTDIR] is already mounted" # Check that the mount point and encrypted directory are empty (skip symlinks). # Perhaps one day we could provide a migration mode (using rsync or something), # but this would be VERY hard to do safely. count=`ls -Al "$MOUNTPOINT" 2>/dev/null | egrep -c "^[drwx-]{10}"` if [ "$count" != "0" ]; then error "$MOUNTPOINT must be empty before proceeding" fi count=`ls -Al "$CRYPTDIR" 2>/dev/null | egrep -c "^[dlrwx-]{10}"` if [ "$count" != "0" ]; then error "$CRYPTDIR must be empty before proceeding" fi stty_orig=`stty -g` # Prompt for the LOGINPASS, if not on the command line and not in the env if [ -z "$LOGINPASS" ] && [ "$BOOTSTRAP" != "1" ]; then tries=0 while [ $tries -lt $PW_ATTEMPTS ]; do stty -echo echo -n "$MESSAGE: " LOGINPASS=`head -n1` stty $stty_orig echo if [ $WRAPPING_PASS != "LOGIN" -o ! -x /sbin/unix_chkpwd ]; then # If we can't check the accuracy of the user's entered # passphrase, force them to type it twice (matching) stty -echo echo -n "$MESSAGE (again): " LOGINPASS2=`head -n1` stty $stty_orig echo if [ "$LOGINPASS" != "$LOGINPASS2" ]; then echo "ERROR: Wrapping passphrases must match" else break fi tries=$(($tries + 1)) continue fi if [ -z "$LOGINPASS" ]; then echo "ERROR: You must provide a login passphrase" tries=$(($tries + 1)) else if [ "$NOPWCHECK" = "1" ]; then echo "INFO: Skipping password verification" break else if printf "%s\0" "$LOGINPASS" | /sbin/unix_chkpwd "$USER" nullok; then break else echo "ERROR: Your login passphrase is incorrect" tries=$(($tries + 1)) fi fi fi done if [ $tries -ge $PW_ATTEMPTS ]; then echo "ERROR: Too many incorrect password attempts, exiting" exit 1 fi fi # Prompt for the MOUNTPASS, if not on the command line and not in the env if [ -z "$MOUNTPASS" ]; then tries=0 while [ $tries -lt $PW_ATTEMPTS ]; do stty -echo echo -n "Enter your mount passphrase [leave blank to generate one]: " MOUNTPASS=`head -n1` stty $stty_orig echo if [ -z "$MOUNTPASS" ]; then MOUNTPASS=`random_passphrase $KEYBYTES` RANDOM_MOUNTPASS=1 break else stty -echo echo -n "Enter your mount passphrase (again): " MOUNTPASS2=`head -n1` stty $stty_orig echo if [ "$MOUNTPASS" != "$MOUNTPASS2" ]; then echo "ERROR: Mount passphrases do not match" tries=$(($tries + 1)) else break fi fi done if [ $tries -ge $PW_ATTEMPTS ]; then echo "ERROR: Too many incorrect passphrase attempts, exiting" exit 1 fi fi #echo #echo "Using username [$USER]" #echo "Using mount passphrase [$MOUNTPASS]" #echo "Using login passphrase [$LOGINPASS]" #echo "Using mount point [$MOUNTPOINT]" #echo "Using encrypted dir [$CRYPTDIR]" #echo #echo "This script will attempt to set up your system to mount" #echo "$MOUNTPOINT with eCryptfs automatically on login," #echo "using your login passphrase." echo echo "************************************************************************" if [ "$RANDOM_MOUNTPASS" = "1" ]; then echo "YOU SHOULD RECORD THIS MOUNT PASSPHRASE AND STORE IN A SAFE LOCATION:" echo "$MOUNTPASS" else echo "YOU SHOULD RECORD YOUR MOUNT PASSPHRASE AND STORE IN A SAFE LOCATION:" fi echo "THIS WILL BE REQUIRED IF YOU NEED TO RECOVER YOUR DATA AT A LATER TIME." echo "************************************************************************" echo ############################################################################### # Setup private directory in home mkdir -m 700 -p "$CRYPTDIR" || error "Could not create crypt directory [$CRYPTDIR]" mkdir -m 700 -p "$MOUNTPOINT" || error "Could not create mount directory [$MOUNTPOINT]" ln -sf /usr/share/ecryptfs-utils/ecryptfs-mount-private.txt "$MOUNTPOINT"/README.txt ln -sf /usr/share/ecryptfs-utils/ecryptfs-mount-private.desktop "$MOUNTPOINT"/Access-Your-Private-Data.desktop chmod 500 "$MOUNTPOINT" # Setup ~/.ecryptfs directory if [ "$NOAUTOMOUNT" = "1" ]; then echo "INFO: $HOME/$PRIVATE_DIR will not be mounted on login" else touch $HOME/.ecryptfs/auto-mount || error "Could not setup ecryptfs auto-mount" fi if [ "$NOAUTOUMOUNT" = "1" ]; then echo "INFO: $HOME/$PRIVATE_DIR will not be unmounted on logout" else touch $HOME/.ecryptfs/auto-umount || error "Could not setup ecryptfs auto-umount" fi if [ "$WRAPPING_PASS" = "LOGIN" ]; then rm -f $HOME/.ecryptfs/wrapping-independent || error "Could not remove ecryptfs wrapping-independent" else touch $HOME/.ecryptfs/wrapping-independent || error "Could not setup ecryptfs wrapping-independent" fi # Backup any existing wrapped-passphrase or sig files; we DO NOT destroy this timestamp=`date +%Y%m%d%H%M%S` for i in "$HOME/.ecryptfs/wrapped-passphrase" "$HOME/.ecryptfs/$PRIVATE_DIR.sig"; do if [ -s "$i" ]; then mv -f "$i" "$i.$timestamp" || error "Could not backup existing data [$i]" fi done # Setup wrapped-passphrase file u=`umask` umask 377 if [ "$BOOTSTRAP" = "1" ]; then # This will be wrapped by pam_ecryptfs's chauthtok as soon as the user # chooses a password. Until that happens (hopefully soon), standard # file permissions (600) are all that's protecting it. Write it to # ramdisk, to keep it from leaking to the hard-drive. temp=`mktemp /dev/shm/.ecryptfs-XXXXXX` printf "%s" "$MOUNTPASS" > "$temp" mv "$temp" "/dev/shm/.ecryptfs-$USER" else printf "%s\n%s" "$MOUNTPASS" "$LOGINPASS" | ecryptfs-wrap-passphrase "$HOME/.ecryptfs/wrapped-passphrase" - || error "Could not wrap passphrase" fi umask $u # Add the passphrase to current keyring # On subsequent logins, this should be handled by "pam_ecryptfs.so unwrap" response=`printf "%s" "$MOUNTPASS" | ecryptfs-add-passphrase $FNEK -` if [ $? -ne 0 ]; then error "Could not add passphrase to the current keyring" fi sig=`echo "$response" | grep "Inserted auth tok" | sed "s/^.*\[//" | sed "s/\].*$//"` if ! echo "$sig" | egrep -qs "^[0-9a-fA-F]{$KEYBYTES,$KEYBYTES}$"; then error "Could not obtain the key signature" fi temp=`mktemp` echo "$sig" > "$temp" || error "Could not create signature file [$HOME/.ecryptfs/$PRIVATE_DIR.sig]" mv "$temp" "$HOME/.ecryptfs/$PRIVATE_DIR.sig" temp=`mktemp` echo "$MOUNTPOINT" > "$temp" || error "Could not create mountpoint file [$HOME/.ecryptfs/$PRIVATE_DIR.mnt]" mv "$temp" "$HOME/.ecryptfs/$PRIVATE_DIR.mnt" echo echo "Done configuring." echo # Skip the tests if we're in bootstrap mode, but exit with the encrypted # homedir mounted if [ "$BOOTSTRAP" = "1" ]; then # Force the mount here, since the root user has the key loaded, # and the calling 'adduser' is about to copy over /etc/skel # NOTE: it is the responsibility of 'adduser' to unmount! # And ensure that $USER owns the files/dirs we've created as root chown $USER:$USER "$CRYPTDIR" /dev/shm/.ecryptfs-$USER if [ "$FNEK" = "--fnek" ]; then fnek_sig=`tail -n 1 "$HOME/.ecryptfs/$PRIVATE_DIR.sig"` sig=`head -n 1 "$HOME/.ecryptfs/$PRIVATE_DIR.sig"` sig_opt="ecryptfs_sig=$sig,ecryptfs_fnek_sig=$fnek_sig" else sig_opt="ecryptfs_sig=$sig" fi mount -i -t ecryptfs -o "rw,$sig_opt,ecryptfs_cipher=$CIPHER,ecryptfs_key_bytes=$KEYBYTES" "$CRYPTDIR" "$MOUNTPOINT" || error "Could not mount" ln -sf /var/lib/ecryptfs/$USER $MOUNTPOINT/.ecryptfs for i in auto-mount \ auto-umount \ $PRIVATE_DIR.mnt \ $PRIVATE_DIR.sig \ wrapped-passphrase; do [ -e $HOME/.ecryptfs/$i ] && chown $USER:$USER $MOUNTPOINT/.ecryptfs/$i done chown $USER:$USER /var/lib/ecryptfs/$USER chown -h $USER:$USER $MOUNTPOINT/.ecryptfs exit 0 fi # Now let's perform some basic mount/write/umount/read sanity testing... echo "Testing mount/write/umount/read..." /sbin/mount.ecryptfs_private || error "Could not mount private ecryptfs directory" temp=`mktemp "$MOUNTPOINT/ecryptfs.test.XXXXXX"` || error_testing "$temp" "Could not create empty file" random_data=`head -c 16000 /dev/urandom | od -x` || error_testing "$temp" "Could not generate random data" echo "$random_data" > "$temp" || error_testing "$temp" "Could not write encrypted file" md5sum1=`md5sum "$temp"` || error_testing "$temp" "Could not read encrypted file" /sbin/umount.ecryptfs_private || error_testing "$temp" "Could not unmount private ecryptfs directory" /sbin/mount.ecryptfs_private || error_testing "$temp" "Could not mount private ecryptfs directory (2)" md5sum2=`md5sum "$temp"` || error_testing "$temp" "Could not read encrypted file (2)" rm -f "$temp" # Use ecryptfs-umount-private on the final run, to clear the used keys # out of the keyring ecryptfs-umount-private || error_testing "$temp" "Could not unmount private ecryptfs directory (2)" if [ "$md5sum1" != "$md5sum2" ]; then error "Testing failed." else echo "Testing succeeded." fi echo echo "Logout, and log back in to begin using your encrypted directory." echo exit 0