← Back to team overview

ecryptfs-devel team mailing list archive

[PATCH] Support migrating an user's home

 

This patch adds support for migrating (encrypting) a users home in
place.

Two scenarios are supported.

First: encrypt a home directory. This is the most straightforward
method. The home directory must not be in use, i.e. no file in it is
opened. A lsof check will be done if it's installed. Nor could it be a
mount point. This mode can be used when a user boots from a LiveCD (or
runlevel 1) and can encrypt any home directory on disk.

To do this, run as root:

    $ ecryptfs-migrate-home -e USER


Second: a non-root user can run "ecryptfs-migrate-home -s" to initiate
a migration. The user's home directory will be encrypted on next
reboot. By this a non-root user can opt to encrypt the home directory
when the system is already deployed. Now a home-encryption-tag-file is
created in the user's home.

To enable this feature, a sysop should modify a boot-up script to run
"ecryptfs-migrate-home -b" on each boot, which will do nothing if no
home-encryption-tag-file is found. This should be done before the user
login attempt.

In this mode, a mount password will be generated randomly, and stored
in /dev/shm temporarily.

When the "ecryptfs-migrate-home -b" finishes, and the user's home
encrypted successfully, the system proceeds to login. After the user
types in the correct login password, the pam_ecryptfs will wrap the
mount password by using the user's login password and the temporary
file holding the mount password is removed.

And the migration is done.

Files modified:

  src/pam_ecryptfs/pam_ecryptfs.c

    Wrapping passphrase file when needed during authentication, this
    was only done when changing a password.

    Also removed checking for wrapped passphrase file in
    ecryptfs_pam_automount_set(), since checking .ecryptfs/auto-mount
    file is enough and the wrapped passphrase file is not created
    before we wrapped it after an user-initiated migration.

  src/utils/ecryptfs-migrate-home

    The new script that does the migration work.

  src/utils/Makefile.am

    To include ecryptfs-migrate-home


TODO:

  1. free space check before migration

  2. prompt the user to remove the /home/USER.old after he checks and
     is sure that the migration is successful

---
diff -Nur ecryptfs-utils-82.orig/src/pam_ecryptfs/pam_ecryptfs.c ecryptfs-utils-82/src/pam_ecryptfs/pam_ecryptfs.c
--- ecryptfs-utils-82.orig/src/pam_ecryptfs/pam_ecryptfs.c	2009-10-21 02:49:55.000000000 +0800
+++ ecryptfs-utils-82/src/pam_ecryptfs/pam_ecryptfs.c	2010-01-19 11:39:29.000000000 +0800
@@ -1,4 +1,5 @@
-/**
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*-
+ *
  * pam_ecryptfs.c: PAM module that sends the user's authentication
  * tokens into the kernel keyring.
  *
@@ -73,17 +74,6 @@
 	char *file_path;
 	int rc = 0;
 	struct stat s;
-	if (asprintf(
-		&file_path, "%s/.ecryptfs/%s",
-		homedir,
-		ECRYPTFS_DEFAULT_WRAPPED_PASSPHRASE_FILENAME) == -1)
-		return -ENOMEM;
-	if (stat(file_path, &s) != 0) {
-		if (errno != ENOENT)
-			rc = -errno;
-		goto out;
-	}
-	free(file_path);
 	if (asprintf(&file_path, "%s/.ecryptfs/auto-mount", homedir) == -1)
 		return -ENOMEM;
 	if (stat(file_path, &s) != 0) {
@@ -133,10 +123,13 @@
 	if (!ecryptfs_pam_automount_set(homedir))
 		goto out;
 	private_mnt = ecryptfs_fetch_private_mnt(homedir);
-	if (ecryptfs_private_is_mounted(NULL, private_mnt, NULL, 1))
+	if (ecryptfs_private_is_mounted(NULL, private_mnt, NULL, 1)) {
+		syslog(LOG_INFO, "%s: %s is already mounted\n", __FUNCTION__,
+		       homedir);
 		/* If private/home is already mounted, then we can skip
 		   costly loading of keys */
 		goto out;
+	}
 	/* we need side effect of this check:
 	   load ecryptfs module if not loaded already */
 	if (ecryptfs_get_version(&version) != 0)
@@ -186,6 +179,33 @@
 				rc = -ENOMEM;
 				goto out_child;
 			}
+
+			/* If /dev/shm/.ecryptfs-$USER exists and owned by the user
+			   and ~/.ecryptfs/wrapped-passphrase does not exist
+			   and a new_passphrase is set:
+			   wrap the unwrapped passphrase file */
+			char *unwrapped_pw_filename;
+			struct stat s;
+			rc = asprintf(&unwrapped_pw_filename, "/dev/shm/.ecryptfs-%s", username);
+			if (rc == -1) {
+				syslog(LOG_ERR, "Unable to allocate memory\n");
+				rc = -ENOMEM;
+				goto out_child;
+			}
+			if (stat(unwrapped_pw_filename, &s) == 0 && (s.st_uid == uid) &&
+				stat(wrapped_pw_filename, &s) != 0	&&
+				passphrase != NULL && *passphrase != '\0') {
+				syslog(LOG_INFO, "Founded unwrapped passphrase file, attempt to wrap");
+				rc = ecryptfs_wrap_passphrase_file(wrapped_pw_filename,
+												   passphrase, salt, unwrapped_pw_filename);
+				if (rc != 0) {
+					syslog(LOG_ERR, "Error wrapping cleartext password; "
+						   "rc = [%d]\n", rc);
+					goto out_child;
+				}
+				syslog(LOG_INFO, "Passphrase file wrapped");
+			}
+
 			rc = ecryptfs_insert_wrapped_passphrase_into_keyring(
 				auth_tok_sig, wrapped_pw_filename, passphrase,
 				salt);
diff -Nur ecryptfs-utils-82.orig/src/utils/ecryptfs-migrate-home ecryptfs-utils-82/src/utils/ecryptfs-migrate-home
--- ecryptfs-utils-82.orig/src/utils/ecryptfs-migrate-home	1970-01-01 08:00:00.000000000 +0800
+++ ecryptfs-utils-82/src/utils/ecryptfs-migrate-home	2010-01-15 17:04:44.000000000 +0800
@@ -0,0 +1,310 @@
+#!/bin/sh
+# -*- sh-basic-offset: 4; sh-indentation: 4; tab-width: 4; indent-tabs-mode: t; sh-indent-comment: t; -*-
+# This script encrypts an user's home
+#
+# Written by Yan Li <yan.i.li@xxxxxxxxx>, <yanli@xxxxxxxxx>
+# Copyright (C) 2010 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program 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.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
+
+set -e -u
+
+# consts
+# ENCRYPT_TAG_FILE_NAME can't contain spaces
+ENCRYPT_TAG_FILE_NAME=.ENCRYPT_ME_ON_NEXT_BOOT
+PRIVATE_DIR="Private"
+
+usage() {
+	echo "
+This script encrypts an user's data in home in place.
+
+There are two ways to use this script:
+
+ a. you can run this script as root, to encrypt any directory by using
+    the -e option. You have to make sure that directory is not in use
+    (the script will also check for this)
+
+ b. if your distro supports this, you can also run this script a a
+    normal user with -s (or --setup) to setup encryption to run on
+    next reboot, and your home will be automatically encrypted on next
+    boot. If your distro didn't support this, running with -s is no
+    use
+
+WARNING: Make a complete backup copy of your non-encrypted data to
+another system or external media. This script is dangerous and, in
+case of an error, could result in data lost, or lock you out of your
+system!
+
+Usage:
+
+$0 -s   or
+$0 -b   or
+$0 -e USER [-l|--loginpass LOGINPASS] [-m|--mountpass MOUNTPASS]
+
+ -e, --encrypt    Encrypt USER's home in place
+ -s, --setup      Set up encryption for current user to run on next boot
+ -l, --loginpass  Login/Wrapping passphrase for USER,
+                  used to wrap MOUNTPASS
+ -m, --mountpass  Passphrase for mounting the ecryptfs directory,
+                  defaults to randomly generated
+ -v, --verbose    Output debug messages
+ -b, --boot       Boot mode. Use this option on boot to do encryption on
+                  demand. SHOULD ONLY BE CALLED from a boot script. You
+                  should NOT use this option directly
+
+"
+	exit 1
+}
+
+error() {
+	echo "$(gettext 'ERROR: ')" "$@" 1>&2
+	exit 1
+}
+
+warning() {
+	echo "$(gettext 'WARNING: ')" "$@" 1>&2
+}
+
+debug() {
+	if [ $VERBOSE -eq 1 ]; then
+		echo "$(gettext 'DEBUG: ')" "$@" 1>&2
+	fi
+}
+
+assert_dir_empty() {
+	# arguments:
+	local DIR=${1}
+
+	if [ -e "${DIR}" ]; then
+		# if ${DIR} is a directory, make sure it's empty
+		if [ -d "${DIR}" ]; then
+			if [ `ls -A "${DIR}" | wc -l` -ne 0 ]; then
+				echo 1>&2 "If you already have some data in directory ${DIR},"
+				echo 1>&2 "please move all of these files and directories out of the way, and"
+				echo 1>&2 "follow the instructions in:"
+				echo 1>&2 "    ecryptfs-setup-private --undo"
+				echo 1>&2 
+				error "${DIR} is not empty, can't continue"
+			fi
+		else
+			error "${DIR} exists but is not an empty directory, can't continue"
+		fi
+	fi
+}
+
+# get user home by id
+get_user_home () {
+	local USER_ID=$1
+
+	local USER_HOME=`grep "^${USER_ID}:" /etc/passwd | cut -d":" -f 6`
+	if [ -z "$USER_HOME" ]; then
+		error "can't find user home for $USER_ID"
+	fi
+
+	echo $USER_HOME
+}
+
+# get user id by home
+get_user_id () {
+	local USER_HOME=$1
+
+	local USER_ID=`grep -F ":${USER_HOME}:" /etc/passwd | cut -d":" -f 1`
+	if [ -z "$USER_ID" ]; then
+		error "can't find user home for $USER_ID"
+	fi
+
+	echo $USER_ID
+}
+
+sanity_check () {
+	# arguments:
+	# remove trailing "/"
+	local USER_ID=${1}
+	local USER_HOME=${2%/}
+
+	# Is $USER_HOME a mountpoint?
+	if mount | grep -q -F " ${USER_HOME} "; then
+		error "${USER_HOME} is a mountpoint or it's already encrypted, we can't migrate it for now"
+	fi
+
+	# Check for rsync
+	if ! which rsync >/dev/null 2>&1; then
+		error "can't find rsync, please install the rsync package"
+	fi
+
+	# Check free space: make sure we have sufficient disk space
+	# available. To make a full copy, we will need at least 2x the
+	# disk usage of the target home directory.
+
+	# TODO: not implemented yet
+	# if DO_NOT_HAVE_ENOUGH_SPACE; then
+	# 	error "not enough free space, I need at least 2x the disk usage of your current home directory"
+	# fi
+
+	# Check directories
+	assert_dir_empty "${USER_HOME}.old" && rm -rf "${USER_HOME}.old"
+	assert_dir_empty "${USER_HOME}/.${PRIVATE_DIR}"
+	assert_dir_empty "${USER_HOME}/.ecryptfs"
+	assert_dir_empty "/home/.ecryptfs/${USER_ID}"
+}
+
+encrypt_dir ()
+{
+	# argument:
+	# remove trailing "/"
+	local USER_ID=$1
+	local USER_HOME=${2%/}
+	local LOGINPASS=${3:-}
+	local MOUNTPASS=${4:-}
+
+	# check whether USER_HOME is in use
+	if ! which lsof >/dev/null 2>&1; then
+		warning "lsof not found, I don't know whether your home is in use or not"
+	else
+		debug "checking for open files in $USER_HOME"
+		if [ `lsof +D "$USER_HOME" | wc -l` -ne 0 ]; then
+			lsof +D "$USER_HOME"
+			echo
+			error "user ${USER_ID} has opened the files above, can't proceed"
+		fi
+	fi
+
+	# start encryption
+	mv "${USER_HOME}" "${USER_HOME}.old"
+	mkdir -p -m 700 "${USER_HOME}"
+	chown -R ${USER_ID}.${USER_ID} "${USER_HOME}"
+	ECRYPTFS_SETUP_PRIVATE_ARGS=""
+	if [ -n "$LOGINPASS" ]; then
+		ECRYPTFS_SETUP_PRIVATE_ARGS="-l ${LOGINPASS}"
+	fi
+	if [ -n "$MOUNTPASS" ]; then
+		ECRYPTFS_SETUP_PRIVATE_ARGS="$ECRYPTFS_SETUP_PRIVATE_ARGS -m ${MOUNTPASS}"
+	fi
+	if ! ecryptfs-setup-private -u "$USER_ID" -b $ECRYPTFS_SETUP_PRIVATE_ARGS; then
+		# too bad, something went wrong, we'll try to recover
+		rm -rf "${USER_HOME}"
+		mv "${USER_HOME}.old" "${USER_HOME}"
+		exit 1
+	fi
+	debug "encrypt home has been set up, encrypting files now..."
+	rsync -a "${USER_HOME}.old/" "${USER_HOME}/"
+	umount "${USER_HOME}/"
+}
+
+DO_SETUP=0
+DO_BOOT=0
+DO_ENCRYPT=0
+VERBOSE=0
+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="$(gettext 'Enter your wrapping passphrase')"
+			shift 1
+			;;
+		-s|--setup)
+			DO_SETUP=1
+			shift 1
+			;;
+		-b|--boot)
+			DO_BOOT=1
+			shift 1
+			;;
+		-v|--verbose)
+			VERBOSE=1
+			shift 1
+			;;
+		-e|--encrypt)
+			DO_ENCRYPT=1
+			USER_ID=$2
+			USER_HOME=`get_user_home ${USER_ID}`
+			shift 2
+			;;
+		*)
+			usage
+			;;
+	esac
+done
+
+if [ `expr $DO_SETUP + $DO_BOOT + $DO_ENCRYPT` -ne 1 ]; then
+	usage
+fi
+
+if [ $DO_SETUP -eq 1 ]; then
+	# we can't encrypt root's home
+	if [ `id -u` -eq 0 ]; then
+		error "can't encrypt root's home, you should use setup as a normal user"
+	fi
+
+	# LOGINPASS and MOUNTPASS will just be ignored here
+	if [ -n "${LOGINPASS:-}" -o -n "${MOUNTPASS:-}" ]; then
+		warning "Setting up encryption on next boot, and LOGINPASS/MOUNTPASS will just be ignored. A random MOUNTPASS will be generated automatically"
+	fi
+
+	sanity_check $HOME
+
+	# set up a tag
+	touch ~/${ENCRYPT_TAG_FILE_NAME}
+
+	echo "I have set the encryption tag, now you should reboot"
+	exit 0
+fi
+
+if [ $DO_BOOT -eq 1 ]; then
+	# I must be run as root now
+	if [ `id -u` -ne 0 ]; then
+		error "boot mode can only be used as root"
+	fi
+
+	# search for ENCRYPT_TAG_FILE_NAME, FIXME: now we handle the first
+	# one only fow now
+	USER_HOME=`ls /home/*/${ENCRYPT_TAG_FILE_NAME} 2>/dev/null | head -1`
+	USER_HOME=${USER_HOME%/*}
+	if [ -z "$USER_HOME" ]; then
+		debug "no encrypt tag file found, bye"
+		exit 0
+	fi
+	USER_ID=`get_user_id "${USER_HOME}"`
+
+	rm -f "${USER_HOME}/${ENCRYPT_TAG_FILE_NAME}"
+
+	# check for ecryptfs kernel module
+	lsmod | grep -q ecryptfs || {
+		modprobe ecryptfs
+	}	
+	
+	sanity_check "$USER_ID" "$USER_HOME"
+	encrypt_dir "$USER_ID" "$USER_HOME"
+	exit 0
+fi
+
+if [ $DO_ENCRYPT -eq 1 ]; then
+	sanity_check "$USER_ID" "$USER_HOME"
+	encrypt_dir "$USER_ID" "$USER_HOME" "${LOGINPASS:-}" "${MOUNTPASS:-}"
+	exit 0
+fi
diff -Nur ecryptfs-utils-82.orig/src/utils/Makefile.am ecryptfs-utils-82/src/utils/Makefile.am
--- ecryptfs-utils-82.orig/src/utils/Makefile.am	2009-10-21 02:49:55.000000000 +0800
+++ ecryptfs-utils-82/src/utils/Makefile.am	2010-01-19 10:59:18.000000000 +0800
@@ -1,6 +1,6 @@
 MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
 
-EXTRA_DIST=ecryptfsrc ecryptfs-rewrite-file ecryptfs-setup-private ecryptfs-setup-swap ecryptfs-mount-private ecryptfs-umount-private
+EXTRA_DIST=ecryptfsrc ecryptfs-rewrite-file ecryptfs-setup-private ecryptfs-setup-swap ecryptfs-mount-private ecryptfs-umount-private ecryptfs-migrate-home
 
 rootsbin_PROGRAMS=mount.ecryptfs \
 		  umount.ecryptfs \
@@ -15,7 +15,8 @@
 	      ecryptfs-setup-swap \
 	      ecryptfs-mount-private \
 	      ecryptfs-umount-private \
-	      ecryptfs-rewrite-file
+	      ecryptfs-rewrite-file \
+	      ecryptfs-migrate-home
 bin2dir = $(bindir)
 
 noinst_PROGRAMS=test

-- 
Best regards,
Li, Yan

Moblin Team, Opensource Technology Center, SSG, Intel
Office tel.: +86-10-82171695 (inet: 8-758-1695)
OpenPGP key: 5C6C31EF
IRC: yanli on network irc.freenode.net

Attachment: signature.asc
Description: Digital signature


Follow ups