Creating A Safe Directory With PAM And EncFS

Want to support HowtoForge? Become a subscriber!
 
Submitted by stefbon (Contact Author) (Forums) on Mon, 2006-06-05 18:18. :: Security

Creating A Safe Directory With PAM And EncFS 

Contents

. Introduction
. Installation of encfs
. Installation of pam_script
. Adjusting PAM configuration
. The result

Introduction

I do work a lot with programs with require credentials
Examples of those programs are:
. mount.cifs
. fusesmb (look for details here)

Now, in my network (and others) the credentials provided at login could (and should) be used by those programs. How can you retrieve these credentials, providing enough security?
With a the PAM modules pam_script it's possible to store the password in a file, which will be used by fusemb and mount.cifs to read the password from.

To achieve security, one could make the user logging in owner and deny read/write for anybody else. Remove this file when the user ends his/her session.
This is enough, for runtime. But I was wondering, but what if the system crashes, and the file with the credentials remains on the harddrive? Anybody who is able to mount this harddrive with for example a lifecd, can read this file!

That's why I was looking for a was to encrypt this file.

With encfs this is very possible! At run time it gives an interface to encrypted files and directories, which does only exist at runtime! When the system is not running, there are only encrypted files, useless when you do not know the key to it. And this key is exactly the (encrypted) password! That's why I've chosen for a combination of PAM and Encfs.

Look for the website of Encfs: http://freshmeat.net/projects/encfs

This construction is intended to give enough security for run- and downtime (after a crash) to store sensitive information, not for creating a permanent safe directory on your harddrive to store documents.

Installation of encfs


Of course FUSE should be installed.
Installation is very straight forward:
(look at the website for rlog which is required by EncFs)

tar -xzf encfs-*.tar.gz
cd encfs-*
configure --prefix=/usr --sysconfdir=/etc --libexecdir=/usr/sbin
make
make install


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

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.

I've chosen for a seperate map in the local machine where for every user logging in a encrypted (and an interface to it) will be stored:


install -m777 -o root -g root /var/lib/encfs


Note the permissions: everybody must be able to create a directory here. Later in this document is explained why.

Installation of pam_script


Installation is very simple. After a make command move the library to the proper directory, /lib/security.

tar -xzf pam-script-*.tar.gz
cd pam-script-*
make
mv pam_script.so /lib/security
chown root:root /lib/security/pam_script.so
chmod 755 /lib/security/pam_script.so

Pam_script.so uses some parameters. All of them are described in the README in the source directory. Important are:

common parameters



- runas=#user# : makes the script called run as user #user#

parameters only in the auth part



- onauth=/path/to/onauth/script : path to script which is run when in auth part
default is /etc/security/onauth

only in the session part


- onsessionopen=/path/to/onsessionopen/script : path to script which is run when a (valid)session is started
default is /etc/security/onsessionopen

- onsessionclose=/path/to/onsessionclose/script : path to script which is run when a session is closed
default is /etc/security/onsessionclose

After installation the more complex thing is to configure the system to make use of this module.


Adjusting PAM configuration

I've used pam_script in the auth and in the session part of the pam (login,kde) service file.
First I describe howto adjust the auth part, where pam_script is used more than once.

The auth part

Pam_script has the ability (from version 0.1.5) to get the password provided at login, and make this via an evironmentvariable PAM_AUTHTOK available to scripts.

The purpose here is to create a safe directory where confidential information (like credentials) are stored.

The module is stacked in the authpart:

cat /etc/pam.d/login

-- snip --
auth                required        pam_shells.so
auth                required        pam_script.so expose=1
auth                sufficient        pam_unix.so use_first_pass
auth                sufficient        pam_ldap.so use_first_pass
auth                required        pam_script.so onauth=/etc/security/onauth.failed
auth                required        pam_deny.so

As you can see I use pam_scripts.so multiple times:

- The first time to run a script which creates an encrypted directory (with encfs) and to write the secret password to a file in this directory for use by credential sensitive programs like fusesmb and mount.cifs. This is done just before the first auth module following, pam_unix.

- The last time to run a script when authentication is not succesfull. When the preceding authmodules fail (pam_unix and pam_ldap) (and only then) this module is reached. It's neccasary to unmount the encrypted directory and to remove temporary files.

- Note that the last module of all is pam_deny, is really neccasary. Without it everybody is able to login. This is because pam_script always returns "PAM_SUCCESS", no matter what the return value is of the scripts.

Do not forget to add the flag "use_first_pass" to the existing module pam_unix.so.

The scripts

cat /etc/security/onauth

#!/bin/bash 
 
retcode=0; 
 
userid=$1 
service=$2 
authtok=$3 
 
if [ -z "$authtok" ]; then 
 
    authtok=$PAM_AUTHTOK 
 
fi; 
 
userproperties=$(getent passwd | grep -m 1 -E "^$userid") 
 
if [ -z "$userproperties" ]; then 
 
    # 
    # userproperties not found: something wrong 
    # 
 
    echo "User not found." 
    exit 
 
fi; 
 
homedir=$(echo $userproperties | cut -d ":" -f 6); 
gidnr=$(echo $userproperties | cut -d ":" -f 4); 
uidnr=$(echo $userproperties | cut -d ":" -f 3); 
 
if [ -d /var/lib/encfs ]; then 
 
    # create a safe 
 
    if [ ! -d /var/lib/encfs/$userid ]; then 
 
        install -m700 -o $uidnr -g $gidnr -d /var/lib/encfs/$userid 
 
    fi; 
 
    if [ ! -d /var/lib/encfs/$userid/encrypted ]; then 
 
        install -m700 -o $uidnr -g $gidnr -d /var/lib/encfs/$userid/encrypted 
 
    fi; 
 
    if [ ! -d /var/lib/encfs/$userid/unencrypted ]; then 
 
        install -m700 -o $uidnr -g $gidnr -d /var/lib/encfs/$userid/unencrypted 
 
    fi; 
 
    if [ ! -d /var/lib/encfs/$userid/run ]; then 
 
        install -m700 -o $uidnr -g $gidnr -d /var/lib/encfs/$userid/run 
        
    fi; 
 
    #
    # test the encrypted directory is not already mounted
    #
 
    if [ -z "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then 
 
        # 
        # create a password-provide-program 
        # 
 
        cd /var/lib/encfs/$userid 
 
        md5authsum=$(echo $authtok | md5sum | cut -d " " -f 1) 
 
        echo "$md5authsum" > run/tmp 
        echo "$md5authsum" >> run/tmp 
 
        chown $uidnr:$gidnr run/tmp 
 
        rm -rf encrypted/* 
        rm -f encrypted/.encfs* 
 
        cat run/tmp | encfs -S /var/lib/encfs/$userid/encrypted /var/lib/encfs/$userid/unencrypted -- -o allow_root 1>>/dev/null 
 
    fi; 
 
    # 
    # this is what's all about: storing the credentials in a file 
    # in this case the password 
    #  
 
    if [ -n "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then 
 
        cd /var/lib/encfs/$userid/unencrypted/ 
 
        if [ -f password.tmp ]; then 
 
                rm password.tmp 
 
        fi; 
 
        echo $authtok > password.tmp 
 
    fi; 
 
fi; 

Some remarks:

This scripts creates a encrypted directory (if it does not already exists) with encfs. Note the -S option at encfs: the password for this encrypted directory is read from stdin and not propmted for.
This password is the same as used at login.
The encrypted directory is at /var/lib/encfs/$userid.

The password is written to a file, password.tmp. This is a temporary file: the password does not have to be the correct one. Later in the process, depending on the success of the validation this file is renamed (to password) or removed.

Very important to notice is that the script is executed as the user logging in, not as root! And because programs like mount.cifs later need to have access to this directory to read the password file, the common fuseoption allow_root is added.


#!/bin/bash
userid=$1
service=$2
userproperties=$(getent passwd | grep -m 1 -E "^$userid")
if [ -z "$userproperties" ]; then
    #
    # userproperties not found: something wrong
    #
    echo "User not found."
    exit
fi;
homedir=$(echo $userproperties | cut -d ":" -f 6);
gidnr=$(echo $userproperties | cut -d ":" -f 4);
uidnr=$(echo $userproperties | cut -d ":" -f 3);
#
# this script is run when the authentication failed
# a safe encrypted is still created
# so it's important to remove this safe again
# 
if [ -d /var/lib/encfs ]; then
    if [ -d /var/lib/encfs/$userid/unencrypted ]; then
        #
        # test the encrypted directory is not already mounted
        #
        if [ -n "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
            if [ $(w -h $userid | wc -l) -eq 0 ]; then
                
                #
                # this user is not logged in on more tty's
                # just remove everything and umount the encrypted directory
                #
                
                rm -rf /var/lib/encfs/$userid/unencrypted/*
                fusermount -u /var/lib/encfs/$userid/unencrypted
                
                rm -rf /var/lib/encfs/$userid/encrypted/*
                rm -f /var/lib/encfs/$userid/encrypted/.encfs*
            else
                #
                # this user is still logged in
                #
                
                rm -f /var/lib/encfs/$userid/unencrypted/password.tmp
                
            fi;
        
        fi;
        if [ -z "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
        
                rm -rf /var/lib/encfs/$userid/encrypted/*
                rm -f /var/lib/encfs/$userid/encrypted/.encfs*
                rm -rf /var/lib/encfs/$userid/unencrypted/*
        fi;
        
    fi;
fi;

Some remarks:

This script is executed when the credentials provided are not valid. The encrypted directory, which has just been setup, wil be removed (unmounted) again and cleared. This is of course only when this user is not logged on any other tty.

The session part

When the session part is reached, it's sure that the credentials provided (password) are correct. This means that the password - in the the auth fase stored in a temporary file - is correct. So one thing to do in the session fase is to move the contents of the password.tmp to the permanent one, password.

A second thing is running scripts which need these credentials for own use, like mounting CIFS shares or fusesmb.
It's logical that any confidential information stays inside the safe directory. That's what it was all about in the first place!!

Note that this modules runs two scripts on default:
. /etc/security/onsessionopen : when a session start/opens;
. /etc/security/onsessionclose : when a session ends/closes.

I follow the default, there is no reason to do otherwise.

My /etc/pam.d/login file (the sessionpart) looks like:

cat /etc/pam.d/login

-- snip --
session                required        pam_mkhomedir.so
session                required        pam_motd.so
session                 optional        pam_mail.so empty dir=/var/mail
session                optional        pam_lastlog.so
session                required        pam_env.so
session                required        pam_script.so
session          required               pam_unix.so
session                required        pam_ldap.so

The scripts

On session open

cat /etc/security/onsessionopen

#!/bin/bash
retcode=0;
userid=$1
service=$2
userproperties=$(getent passwd | grep -E "^$userid")
if [ -z "$userproperties" ]; then
    #
    # userproperties not found: something wrong
    #
    
    echo "User not found."
    exit
    
fi;
homedir=$(echo $userproperties | cut -d ":" -f 6);
gidnr=$(echo $userproperties | cut -d ":" -f 4);
uidnr=$(echo $userproperties | cut -d ":" -f 3);
if [ -d /var/lib/encfs/$userid/encrypted ]; then
    #
    # test the encrypted directory is mounted
    #
    
    if [ -n "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
    
        if [ -f /var/lib/encfs/$userid/unencrypted/password.tmp ]; then
        
            if [ -f /var/lib/encfs/$userid/unencrypted/password ]; then
            
                #
                # an old passwordfile found 
                #
                
                if [ -z "(diff /var/lib/encfs/$userid/unencrypted/password /var/lib/encfs/$userid/unencrypted/password.tmp)" ]; then
                
                    #
                    # new password and old one are the same
                    #
                    
                    rm /var/lib/encfs/$userid/unencrypted/password.tmp
                    
                else
                
                    mv /var/lib/encfs/$userid/unencrypted/password.tmp /var/lib/encfs/$userid/unencrypted/password
                
                fi;
            else
            
                #
                # password not found : it's the first login.
                # just move the temporary passwordfile to the remaining
                #
                
                mv /var/lib/encfs/$userid/unencrypted/password.tmp /var/lib/encfs/$userid/unencrypted/password
            
            fi;        
        
        fi;
        
        if [ -d /etc/session.d/pam/onsessionopen ]; then
        
            for script in /etc/session.d/pam/onsessionopen/*.sh; do
            
                if [ -x $script ]; then
            
                    eval $script $userid $service
                    
                fi
                
            done;
        
        fi;
        
    fi;
    
fi;

On session close

cat >> /etc/security/onsessionclose

#!/bin/bash
userid=$1
service=$2
userproperties=$(getent passwd | grep -E "^$userid")
if [ -z "$userproperties" ]; then
    #
    # userproperties not found: something wrong
    #
    echo "User not found."
    exit
fi;
homedir=$(echo $userproperties | cut -d ":" -f 6);
gidnr=$(echo $userproperties | cut -d ":" -f 4);
uidnr=$(echo $userproperties | cut -d ":" -f 3);
#
# this script is run when the authentication failed
# a safe encrypted is still created
# so it's important to remove this safe again
# 
if [ -d /var/lib/encfs ]; then
    if [ -d /var/lib/encfs/$userid/unencrypted ]; then
        #
        # test the encrypted directory is already mounted
        #
        if [ -n "$(mount | grep -w /var/lib/encfs/$userid/unencrypted )" ]; then
            if [ $(w -h $userid | wc -l) -eq 0 ]; then
        
                rm -rf /var/lib/encfs/$service.$userid/unencrypted/*
                fusermount -u /var/lib/encfs/$userid/unencrypted
                
                rm -rf /var/lib/encfs/$userid/encrypted/*
                rm -f /var/lib/encfs/$userid/encrypted/.encfs*
                
            fi;
        else
        
            rm -rf /var/lib/encfs/$userid/encrypted/*
        fi;
        
        if [ -d /etc/session.d/pam/onsessionclose ]; then
        
            for script in /etc/session.d/pam/onsessionclose/*.sh; do
                if [ -x $script ]; then
                    eval $script $userid $service
                fi
                
            done;
        
        fi;
    fi;
fi;

Some remarks:
. important: early versions of shadow (where the login program is a part of) did not close sessions on default (versions prior to 4.0.12). You'll have to add :

    CLOSE_SESSIONS yes

to the /etc/login.defs file.
This option is not documented, and not present in the login.defs file installed by the Shadow package. You'll have to add it yourself.
In newer versions this option is removed: the session is always closed.

The result

The encrypted directory contains now the credentials, which are only available to the owner and root. By creating a file mount.cifs.conf with these values:

touch /var/lib/encfs/$userid/unencrypted/mount.cifs.conf
chmod 600 /var/lib/encfs/$userid/unencrypted/mount.cifs.conf

echo "username=$userid" > /var/lib/encfs/$userid/unencrypted/mount.cifs.conf
echo -n "password=" >> /var/lib/encfs/$userid/unencrypted/mount.cifs.conf
cat /var/lib/encfs/$userid/unencrypted/password >> /var/lib/encfs/$userid/unencrypted/mount.cifs.conf

Now mounting a cifs share is possible with:

/sbin/mount.cifs //fileserver/public /home/sbon/netshares/fileserver/public -o credentails=/var/lib/encfs/sbon/unencrypted/mount.cifs.conf,ip=192.168.0.2


where the directory /home/sbon/netshares/fileserver/public does exist, and therse is a share "public" available on "fileserver", a smb/cifs server with ipnumber 192.168.0.2.
This command must be run as root. Root needs to have access to the encrypted directory.


An other example is fusesmb, which can use credentials to browse the smb networkneighbourhood. This is not done by creating a seperate file for the credentials only, but in the global-per-user configurationfile of fusesmb in ~/.smb/fusesmb.conf:
(here I create a simple configuationfile with credentials only)

touch /var/lib/encfs/sbon/unencrypted/fusesmb.conf
chmod 600 /var/lib/encfs/sbon/unencrypted/fusesmb.conf

echo "[global]" > /var/lib/encfs/sbon/unencrypted/fusesmb.conf
echo "username = sbon" >> /var/lib/encfs/sbon/unencrypted/fusesmb.conf
echo -n "password = " >> /var/lib/encfs/sbon/unencrypted/fusesmb.conf
cat /var/lib/encfs/sbon/unencrypted/password >> /var/lib/encfs/sbon/unencrypted/fusesmb.conf

ln -sf /var/lib/encfs/sbon/unencrypted/fusesmb.conf /home/sbon/.smb/fusesmb.conf

The last link is because fusesmb (started by sbon) expects the configuration file there.
Now start it with:

fusesmb /home/sbon/network

when I'm logged in as myself (sbon). The directory /home/sbon/network has to exist.


Please do not use the comment function to ask for help! If you need help, please use our forum.
Comments will be published after administrator approval.
Submitted by Anonymous (not registered) on Tue, 2006-06-06 11:19.

This is a great howto, thanks.

But it misses one important information, which is what happens when the user changes his password. As the login password is used to decrypt the encfs files, it will no more possible to decrypt them at the next login.

To avoid that, we should use encfsctl, which is provided with encfs, to reencrypt the files with the new password. This can not be done with pam_script without modifying it to manage event like onpasswdchange for example, but we can rename the binary passwd in passwd.bin and add a new shell passwd which will ask twice the password, call passwd.bin to update the user password and if the last call succeed, call encfsctl to reencrypt the files as well as update the reference to the password file password.tmp.

Submitted by Anonymous (not registered) on Fri, 2006-06-09 12:19.

Sorry, I forgot some things.

First of all, thank you for the compliment.

Very important to know, I've meant this safe only for security during a session (and an occasional crash), After a session it can be destroyed (read: cleaned, unmounted and removed). Because it purpose it to store only information like passwords, (and maybe krb5 tickets?????) , nothing else!!

So there is no reason to keep the encrypted between two logins and try to open it again with (the changed) credentials. My idea is to create a brand new safe directory, every login again. And no encrypted file of directory needs te be kept untill the next login.

So, every login a brand new safe can be created, any old one may be forgotten (removed).

Stef Bon

Submitted by Anonymous (not registered) on Mon, 2006-06-26 22:20.
Given that you explicitly don't want the safe to survive a reboot, is there any reason to use a persistent filesystem at all? The advantage of EncFS is that the files continue to exist after a reboot; a tmpfs would work for your session system.

The only disadvantage of a tmpfs is that if your swap isn't encrypted, your session data isn't encrypted if it's swapped out; this is already a risk if the programs you're using the session data with aren't carefully written to lock the data into memory. In any case, if you're worried about what could happen if I hit your machine with a LiveCD, you're already encrypting swap.

Submitted by Prompt (registered user) on Tue, 2006-06-27 16:20.
Thanks for the suggestion, I haven't been thinking of this. I understand that this is making things easier. How can I achieve this? Just create a tmpfs by mounting? And what about encryption? Can I use encfs on top off tmpfs? Stef
Submitted by Anonymous (not registered) on Fri, 2006-06-09 07:23.
You're absolutely right. When a user in a session changes his/her password, the key to the encrypted directory is not valid anymore. But I would like to make some remarks about this issue:
  1. I've chosen the password as key, but this isn't necessary. Any unique key will do! When I look around there are more systems creating unique keys to identify sessions with. I know these keys are used, but I do not have enough knowledge how this works. Examples of these (of which I think are good) are FreeNX (the server creates a unique id for a user session). Maybe a PHP session is also a good example. According to me these sessions are also identified with a unique session id.
  2. The purpose of a key is because Encfs needs one! It has nothing to do with the security I'm using it for. I'm using it to ensure that files containing sensitive information like passwords can not be read when a these files accidently remain on the harddrive (a crash fe). If this could be done without a key: great. Maybe I'm saying something stupid here, maybe this key is really necessary to enforce unique encryption I don't know.
Stef Bon