Transparent Encryption For the User's Home Folder
The following steps have been completed on a fresh CentOS v6.5 install to allow users on a desktop to encrypt their Home directory.
ref: http://www.howtoforge.com/encrypting_encfs_pam_script
Introduction
Many organisations are requesting users to encrypt their laptop to protect confidential information (customer names, internal contact details, etc.). On many Windows systems this is problematic and intrusive. This page is meant to help CentOS (and alike) users in configuring their systems to encrypt/decrypt their Home folder auto-magically using EncFS.
Install CentOS
The steps described in this page are based on an install of CentOS from the "Minimal" install CD. Any installation media should do.
Dummy User Account
When setting up the first user (during the installation process), I suggest you create a dummy account (e.g. "Administrator"). This will allow the configuration to be completed for the 'real' user.
Disk Setup
We are NOT setting up encryption using the installer method.
The recommendation is to select "All files in one partition" and accept the defaults.
When the installation is complete, reboot to complete the configuration and log in with the dummy user created during the installation. This user will be able to get root privileges for the rest of the process.
After installing CentOS always make sure your system is up to date so run the following:
yum update
Ensure /tmp is tmpfs
As we will be storing transient encoded information in the /tmp directory (the information is only stored for the duration of the login) it is prudent to ensure an attack by powering off the machine during login won't expose this information. It is easiest to set this up in single user mode. Therefore boot into single user mode by temporarily modifying the grub boot entry on boot.
- Press space when the grub boot loader laucnhes.
Press e to edit the current boot stanza.
Go down to the line starting with kernel.
Press e to edit the line.
- Add a " -s" (without the quotes, so that's a [space][-][s]).
- Press [Enter].
- Press [b] to boot.
When booted into single user mode, delete all the files in /tmp and then add the following into the /etc/fstab file.
tmpfs /tmp tmpfs defaults 0 0
Then save the file and reboot.
Install the required packages
pam_script(5)
pam-script.so is a pam module that implements session management. It optionally runs a session open script (/etc/security/onsessionopen), a session close script (/etc/security/onsessionclose) or an authentication script (/etc/security/onauth) if they exist. Alternatively any other script can be executed using the options onsessionopen=/path/to/script, onsessionclose=/path/to/script and onauth=/path/to/script.
The pam_script utility comes from RepoForge, so you'll need to ensure that is installed from RepoForge.
encfs(1)
EncFS creates a virtual encrypted filesystem which stores encrypted data in the rootdir directory and makes the unencrypted data visible at the mountPoint directory. The user must supply a password which is used to (indirectly) encrypt both filenames and file contents.
The encfs utility comes from EPEL, so you'll need to ensure that is installed from EPEL.
After setting up the repositories you can then install the packages.
# yum install pam_script fuse-encfs
Test encfs works
Add the user to the 'fuse' group (logout and log back in again to pick up group memberships).
After installation you can test if it works:
$ mkdir -p ~/test/encrypted $ mkdir -p ~/test/decrypted $ encfs ~/test/encrypted ~/test/decrypted $ mount (should show the just created mount) $ echo "This is very secret." > ~/test/decrypted/testfile $ cat ~/test/decrypted/testfile This is very secret. $ fusermount -u ~/test/decrypted $ ls -al ~/test/decrypted
No file should exist
$ ls -al ~/test/encrypted . .. .encfs6.xml PoJa3a5ZO0iiZoTKmuNYZsLp
The filename is arbitrary, as it's hashed by encfs.
Remount the directory to view the contents of the file.
$ encfs ~/test/encrypted ~/test/decrypted $ cat ~/test/decrypted/testfile This is very secret.
The directory encrypted contains the encrypted files, and remains on the harddisk after unmounting and/or shutting down. The directory decrypted is the interface to it, and disappears when unmounted and/or shutting down.
The file testfile should appear in the decrypted directory, and in encrypted form in the encrypted directory. Name and content of the file are encrypted.
Now setup the users encrypted $HOME
The configuration will setup encfs for a new "real" user.
Now create your "real" user and ensure they're added to the fuse group.
# adduser <username> # usermod -aG fuse <username>
Move their home directory/files out of the way:
# mkdir /var/backups/<username_home> # mv /home/<username>/.* /var/backups/<username_home>/. # rmdir /home/<username> # mkdir -p /home/.encfs/<username> /home/<username> # chown <username>.<username> /home/.encfs/<username> /home/<username>
To create the user's encrypted directory, we will use their login via the su utility, but without pulling in their environment (e.g. do NOT use the - switch to the su command).
Create the encrypted directory (If you encounter problems with permissions, see the Section on permissions):
# su <username> $ encfs -v /home/.encfs/<username> /home/<username>
Accept the default options, or tinker with the encryption settings. Paranoia mode works, but won't support hard links. The author has used paranoia mode successfully.
SET THE PASSWORD TO BE THE SAME AS YOUR LOGIN PASSWORD
Check that encrypted filesystem is mounted correctly:
$ mount
This should output something like the following:
encfs on /home/<username> type fuse.encfs (rw,nosuid,nodev,default_permissions)
Move the home directory/files back:
$ mv /var/backups/<username_home>/* /home/<username>/.
Create the scripts to autmount on login and unmount on logout
There is a single script /etc/security/onauth which is symlinked to from /etc/security/onsessionopen and /etc/security/onsessionclose. This was chosen to centralise setup and configuration.
$ cat /etc/security/onauth #!/bin/sh # # What: onauth, onsessionopen, onsessionclose # When: 5-Aug-2013 # Who: Philip Jensen # Why: To capture login credentials to transparently fusermount an encrypted # home directory for a user. # # Setup vars USER=$1 PASSWORD=$PAM_AUTHTOK USER_FILE=/tmp/__u PASSWORD_FILE=/tmp/__p LOG_FILE=/var/log/pam_script.log /bin/echo "------------------------------" >> ${LOG_FILE} date >> ${LOG_FILE} /bin/echo "Run as `whoami`" >> ${LOG_FILE} #echo "Params passed to this script" >> ${LOG_FILE} #echo "\$0 = $0" >> ${LOG_FILE} #echo "\$1 = $1" >> ${LOG_FILE} #echo "\$2 = $2" >> ${LOG_FILE} #echo "\$3 = $3" >> ${LOG_FILE} #echo "\$PAM_AUTHTOK = ${PAM_AUTHTOK}" >> ${LOG_FILE} #echo "" >> ${LOG_FILE} capture_credentials() { # umask 277 /bin/echo "${USER}" | base64 > ${USER_FILE} /bin/echo "${PASSWORD}" | base64 > ${PASSWORD_FILE} exit 0 } mount_encfs_home() { USER=`cat ${USER_FILE} | base64 -d` PASSWORD=`cat ${PASSWORD_FILE} | base64 -d` #echo ${PASSWORD} | su - ${USER} -c "/usr/bin/encfs -v -S /home/.encfs/${USER} /home/${USER} -- -o nonempty" >> ${LOG_FILE} 2>&1 echo ${PASSWORD} | su - ${USER} -c "/usr/bin/encfs -v -S /home/.encfs/${USER} /home/${USER} -- -o nonempty" >> /dev/null 2>&1 rm ${USER_FILE} ${PASSWORD_FILE} } umount_encfs_home() { echo "Unmounting encrypted home dir /home/.encfs/${USER} from /home/${USER}" >> ${LOG_FILE} # need to do a lazy unmount to wait until the filesystem is clean. #umount -l /home/${USER} >> ${LOG_FILE} 2>&1 umount -l /home/${USER} >> /dev/null 2>&1 } case "$0" in *onauth) echo "Capturing credentials" >> ${LOG_FILE} capture_credentials ;; *onsessionopen) echo "Trying to mount encfs home" >> ${LOG_FILE} mount_encfs_home ;; *onsessionclose) echo "Trying to un-mount encfs home" >> ${LOG_FILE} umount_encfs_home ;; esac echo "------------------------------" >> ${LOG_FILE} exit 0
Make sure the script is executable.
# chmod +x /etc/security/onauth
Now create the symlinks
# ln -s /etc/security/onauth /etc/security/onsessionopen # ln -s /etc/security/onauth /etc/security/onsessionclose
Setup PAM
As we're capturing the user's password to unlock the EncFS keys to mount and decrypt the encrypted filesystem, the changes are made to /etc/pam.d/password-auth
# cat /etc/pam.d/password-auth #%PAM-1.0 # This file is auto-generated. # User changes will be destroyed the next time authconfig is run. auth required pam_env.so # pam-script for EncFS mounting ...... auth optional pam_script.so expose=1 runas=root auth sufficient pam_unix.so nullok try_first_pass auth requisite pam_succeed_if.so uid >= 500 quiet auth required pam_deny.so account required pam_unix.so account sufficient pam_localuser.so account sufficient pam_succeed_if.so uid < 500 quiet account required pam_permit.so password requisite pam_cracklib.so try_first_pass retry=3 type= password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok password required pam_deny.so session optional pam_keyinit.so revoke session required pam_limits.so session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid # pam-script for EncFS mounting ...... session optional pam_script.so runas=root session required pam_unix.so
This is automatically regenerated by authconfig, so the file attributes could be modified to make it immutable.
password-auth is typically a symlink to password-auth-ac, so set the immutable bit chattr +i password-auth-ac.
Check the attributes:-
# lsattr password-auth-ac ----i--------e- password-auth-ac
Fix ICEauthority issue
When X starts for the user it wants to write a file, but the file doesn't yet exist in a writable form as it's have a fuse mount laid over the top. The following resolves this issue.
See the last two lines of the file.
/etc/security/pam_env.conf
This change is required to remove the annoying message "Cannot update ICEauthority /home/(user)/.ICEauthority. The last line of this example contains the change.
# cat /etc/security/pam_env.conf # # This is the configuration file for pam_env, a PAM module to load in # a configurable list of environment variables for a # # The original idea for this came from Andrew G. Morgan ... #<quote> # Mmm. Perhaps you might like to write a pam_env module that reads a # default environment from a file? I can see that as REALLY # useful... Note it would be an "auth" module that returns PAM_IGNORE # for the auth part and sets the environment returning PAM_SUCCESS in # the setcred function... #</quote> # # What I wanted was the REMOTEHOST variable set, purely for selfish # reasons, and AGM didn't want it added to the SimpleApps login # program (which is where I added the patch). So, my first concern is # that variable, from there there are numerous others that might/would # be useful to be set: NNTPSERVER, LESS, PATH, PAGER, MANPAGER ..... # # Of course, these are a different kind of variable than REMOTEHOST in # that they are things that are likely to be configured by # administrators rather than set by logging in, how to treat them both # in the same config file? # # Here is my idea: # # Each line starts with the variable name, there are then two possible # options for each variable DEFAULT and OVERRIDE. # DEFAULT allows and administrator to set the value of the # variable to some default value, if none is supplied then the empty # string is assumed. The OVERRIDE option tells pam_env that it should # enter in its value (overriding the default value) if there is one # to use. OVERRIDE is not used, "" is assumed and no override will be # done. # # VARIABLE [DEFAULT=[value]] [OVERRIDE=[value]] # # (Possibly non-existent) environment variables may be used in values # using the ${string} syntax and (possibly non-existent) PAM_ITEMs may # be used in values using the @{string} syntax. Both the $ and @ # characters can be backslash escaped to be used as literal values # values can be delimited with "", escaped " not supported. # Note that many environment variables that you would like to use # may not be set by the time the module is called. # For example, HOME is used below several times, but # many PAM applications don't make it available by the time you need it. # # # First, some special variables # # Set the REMOTEHOST variable for any hosts that are remote, default # to "localhost" rather than not being set at all #REMOTEHOST DEFAULT=localhost OVERRIDE=@{PAM_RHOST} # # Set the DISPLAY variable if it seems reasonable #DISPLAY DEFAULT=${REMOTEHOST}:0.0 OVERRIDE=${DISPLAY} # # # Now some simple variables # #PAGER DEFAULT=less #MANPAGER DEFAULT=less #LESS DEFAULT="M q e h15 z23 b80" #NNTPSERVER DEFAULT=localhost #PATH DEFAULT=${HOME}/bin:/usr/local/bin:/bin\ #:/usr/bin:/usr/local/bin/X11:/usr/bin/X11 # # silly examples of escaped variables, just to show how they work. # #DOLLAR DEFAULT=\$ #DOLLARDOLLAR DEFAULT= OVERRIDE=\$${DOLLAR} #DOLLARPLUS DEFAULT=\${REMOTEHOST}${REMOTEHOST} #ATSIGN DEFAULT="" OVERRIDE=\@ # # set the ICEAUTHORITY file location to allow GNOME to start on encfs $HOME ICEAUTHORITY DEFAULT=/tmp/.ICEauthority_@{PAM_USER}
Password Utility
There are some significant challenges in automating this process as the user must be able to change their own password, this requires the passwd utility to be used (as it's the only suid binary). However, it's possible to overcome this issue with the use of an expect script. Therefore install the expect package:
yum install expect
Then copy the following code to /usr/local/bin/passwd. The normal user's PATH is set to execute this passwd utility before the system one in /usr/bin.
# # What: /usr/local/bin/passwd # When: 5/Aug/2013 # Who: Philip Jensen (partially generated by autoexpect - refer expect-dev) # Why: To keep a user's password in sync with their encfs password # which needs to be unlocked when the user logs in. # # set force_conservative 0 ;# set to 1 to force conservative mode even if ;# script wasn't run conservatively originally if {$force_conservative} { set send_slow {1 .1} proc send {ignore arg} { sleep .1 exp_send -s -- $arg } } # Tell the user they aren't using the real passwd utility. puts "###############################################################" puts "# #" puts "# This 'passwd' utility overrides the original. #" puts "# #" puts "# It is used to change the user's UNIX password #" puts "# as well as their encrypted file system password. #" puts "# #" puts "# The original password changing utility is /usr/bin/passwd #" puts "# #" puts "###############################################################" # Who is the user we are changing the password for. # (actually this is needed for encfsctl - see below) set user [exec whoami] puts "\nChanging password for $user. If this is incorrect press Ctrl + C\r" # Get the user's current password. send_user "\nCurrent password: " stty -echo expect_user -re "(.*)\n" set curr_password $expect_out(1,string) stty echo # Get their new password send_user "\nNew password: " stty -echo expect_user -re "(.*)\n" set new_password_one $expect_out(1,string) stty echo send_user "\nRe-enter new password: " stty -echo expect_user -re "(.*)\n" set new_password_two $expect_out(1,string) stty echo puts "\n" # Do the *NEW* passwords match? if {$new_password_one!=$new_password_two} { puts "Passwords don't match, exiting!\n" exit 2 } else { set new_password "$new_password_one" } # Debug output #puts "Changing password from: $curr_password\nto: $new_password\n" # Hide output from the screen (operate in silent mode). # Of course this may not hide any potential output from the command line. # Beware of background process watchers. log_user 0 set timeout -1 spawn /bin/sh match_max 100000 # begin changing passwords puts "\nChanging user's password." send -- "/usr/bin/passwd\r" #expect -exact "/usr/bin/passwd\r #Changing password for $user.\r #(current) UNIX password: " expect "UNIX password: " send -- "$curr_password\r" # we need to handle the user entering an incorrect current password. expect { "Authentication token manipulation error" {puts "Current User password incorrect!" ; exit } "New password: " { send -- "$new_password\r" } } # we need to handle the user's proposed password being too simple. expect { "BAD PASSWORD:" {puts "\nNew password doesn't meet password rules for complexity.\nExiting without changing passwords!" ; exit } "Retype new password: " { send -- "$new_password\r" } } #expect -exact "Retype new password: " { send -- "$new_password\r" } expect -exact "\r passwd: all authentication tokens updated successfully.\r" send_user "User password changed successfully." # If we get this far, changing the user's password succeeded, # therefore we can proceed to change the encfs password. puts "\nChanging encfs password." expect -exact "\$ " send -- "/usr/bin/encfsctl passwd /home/.encfs/$user\r" expect -exact "\r Enter current Encfs password\r EncFS Password: " send -- "$curr_password\r" # what if the current encfs password didn't match the current user's password # Of course now we have a very BIG problem as the transparent login won't work. # But if they've used this utility, then they should be fine. ;-) expect { "Invalid password\r" { puts "\nCurrent encfs password incorrect.\nLogin and EncFS passwords not in sync.\nAutomatic mounting of encrypted home filesystem will fail!!\n\nUse /usr/bin/passwd and /usr/bin/encfsctl manually." ; exit } "New Encfs Password: " { send -- "$new_password\r" } } expect -exact "\r Verify Encfs Password: " send -- "$new_password\r" expect "Volume Key successfully updated." puts "Password change complete!" send -- "exit\r" expect eof
Make the file executable.
chmod +x /usr/local/bin/passwd
Now su to the user and test they can change their password.
su - <encrypted_home_user> passwd
This should run the new password changing utility and change the user's password, and their encfs mount password.
Now you should be able to logout, and login (via GDM) as the user with an encrypted home.
An interesting side benefit of having a fuse mount, is that no other user will be able to view the contents of the encrypted directory - not event root!
Try it for yourself.