Locking and Encrypting Apps with Encfs

Published: Thu Aug 16 2018
This is part of the se­cu­rity col­lec­tion.

For those who pre­fer it, this post is also on Medium.

Introduction

One of the prob­lems of a shared sys­tem, is that some­times mul­ti­ple ap­pli­ca­tions are used by mul­ti­ple peo­ple. Normally this would be solved by the ex­cel­lent multi-user sup­port in­her­ent to Linux sys­tems. After all, Unix and de­riv­a­tives were de­signed to be used by mul­ti­ple users at the same time.

However, in some cases, it just makes sense for a sin­gle ap­pli­ca­tion to stay locked un­til a pass­word is sup­plied. In any case, encfs is one of the bet­ter en­cryp­tion meth­ods (as long as us­abil­ity over­shad­ows se­cu­rity) and it’s a good prac­tice to en­crypt ap­pli­ca­tion data any­way. The case for en­cryp­tion varies, but weather it’s to make life harder for hack­ers, or just to stop cloud stor­age providers from sniff­ing around, it’s a good idea. Even if mul­ti­ple users will not be shar­ing an ac­count, en­cryp­tion pre­vents the root users or admins from get­ting too frisky with your data.

The pop­u­lar gnome-keyring and other se­cu­rity au­then­ti­ca­tion meth­ods are not a good fit for this since most of them are un­locked all at once and with­out be­ing linked to a par­tic­u­lar pro­gram.

Here we will cre­ate a few sim­ple bash scripts to gener­i­cally and flex­i­bly lock and en­crypt ap­pli­ca­tions in a way which al­lows for mul­ti­ple users to have their own pri­vate en­crypted in­stances of shared apps. The com­plete scripts are lo­cated in my Dotfiles.

Requirements

The ba­sic re­quire­ments are com­monly found across most UNIX sys­tems and it’s de­riv­a­tives in­clud­ing MacOS, Ubuntu and other Debian dis­tros, RPM based sys­tems etc.

I per­son­ally run Arch Linux but that’s just a bi­ased en­dorse­ment.

The re­quire­ments are:

Bash

This is needed for the shell sub­sti­tu­tions in the shell script. Found al­most every­where.

EncFS

This han­dles the en­cryp­tion por­tion. Though every­thing cov­ered here will re­quire only the base pro­gram it­self, new users would prob­a­bly ben­e­fit from hav­ing one of the GUI in­ter­faces to encfs as well. I pre­fer Gnome Encfs Manager.

An ASKPASS pro­gram

These are most fa­mously known for weird ssh er­rors. However, here we will fo­cus on zen­ity and git as a fall­back.

And that’s it. Other vari­a­tions of this method might use shoes for more GUI good­ness. Other askpass pro­grams and helpers may also be used, like the pop­u­lar x11-ssh-askpass pro­gram.

Program Structure

Our ba­sic struc­ture is sim­ple.

  1. An en­crypted folder is mounted
  2. The ap­pli­ca­tion is run

Additionally we would like the fol­low­ing fea­tures:

  1. Execution with­out the ter­mi­nal (GUI, no ter­mi­nal user queries)
  2. An au­to­mated way of gen­er­at­ing a new stash for ap­pli­ca­tions
  3. A san­i­tized name for the mount points

Preliminaries

Before get­ting to the cre­ation of a script, I like to ex­per­i­ment with the na­tive shell. In this case this sim­ply in­volved check­ing the fol­low­ing:

# Testing the mount
encfs ~/.cryptTest ~/cryptTest
  • This prompted me to cre­ate the di­rec­tory if it did­n’t ex­ist, which would not be han­dled prop­erly from within a shell script.

Additionally the MAN page for encfs showed me that sup­port for ex­ter­nal au­then­ti­ca­tion man­agers is granted via the --extpass flag.

Implementation

With those pre­lim­i­nar­ies out of the way, it is time to start script­ing. Portions which re­quire the bash shell specif­i­cally will have the she­bang in­cluded.

Always re­mem­ber to start the script with it and to only put it once, right at the top of the file.

#!/usr/bin/bash

Setting Variables

Initially we might sim­ply set an un­lock string as fol­lows:

unlockString="Unlock $1"

Choosing an ASKPASS pro­gram

Because scripts can quickly get clunky with­out in­tend­ing too, we will first add a sim­ple vari­able which is suit­able for run­ning the ex­ter­nal au­then­ti­ca­tion.

Zenity Prompt
Zenity Prompt
askPass="zenity --password --title=$unlockString"

As men­tioned pre­vi­ously, zen­ity is the pret­tier choice, how­ever, it may not be in­stalled every­where. So we need a fall­back.

Git is more or less avail­able every­where, and it just so hap­pens to have a pretty neat askpass tool as well.

Git Askpass Prompt
Git Askpass Prompt
askPass="/usr/lib/git-core/git-gui--askpass $unlockString"

However, it would be bet­ter to wrap them both up in a way to pick one or the other based on the avail­abil­ity. So, we write a sim­ple test.

if which zenity >/dev/null 2>&1; then
    askPass="zenity --password --title=$unlockString"
elif which git >/dev/null 2>&1; then
    askPass="/usr/lib/git-core/git-gui--askpass $unlockString"
else echo "ERROR: No valid (zenity or git) askpass program available\n"
     fi

Honestly the us­age of which in­stead of command -v is a bit con­tro­ver­sial. However, here I went with which sim­ply be­cause it seemed faster. The more portable (POSIX com­pli­ant) ver­sion of the above would use command -v. For more de­tails check this stack ex­change ques­tion.

Creating Mount-points

Gnome Encfs Manager de­faults to re­mov­ing the mount point when the stash is un­mounted, how­ever, this causes a ter­mi­nal in­put de­mand which needed to be sup­pressed, hence the di­rec­to­ries are cre­ated prior to run­ning Encfs.

cryptDir="$HOME/Encfs/.$1"
appDir="$HOME/.decrypt/$1"
if [ ! -d $appDir ]; then
    mkdir -p $appDir
fi

Caveats

The above snip­pet does not deal with sit­u­a­tions where:

  • The stash is al­ready mounted
  • The stash does not ex­ist

These are dealt with in the Improvements sec­tion of this doc­u­ment.

Mount and Run

Now we are in a po­si­tion to sim­ply mount our stash and run the pro­gram.

encfs --extpass="$askPass" $cryptDir $appDir
$1

Caveats

At this stage the script is not equipped to deal with sit­u­a­tions where:

  • The mount op­er­a­tion fails (wrong pass­word)
  • The con­fig files are en­crypted

The script runs the pro­gram with­out test­ing the re­sult of the mount, which will lead to much frus­tra­tion and weird er­rors. These are fixed in Handling Authentication.

Improvements

Several im­prove­ments to the ba­sic script cre­ated above are dis­cussed in this sec­tion.

Naming di­rec­to­ries

This is ac­tu­ally not a re­ally im­por­tant bit, how­ever, I wanted the app di­rec­to­ries to start with cap­i­tal let­ters. Also I wanted the en­crypted data to be stored in a hid­den folder.

In any case, this por­tion of the script uses a bash spe­cific ex­pan­sion. At this point we can also make the unlockString a lit­tle neater.

#!/usr/bin/bash
# bash specific
tempName=( $1 )
appName=$(echo "${tempName[@]^}")
unlockString="Unlock $appName"

Now that we have the name, we sim­ply mod­ify the di­rec­to­ries.

cryptDir="$HOME/Encfs/.$appName"
appDir="$HOME/.decrypt/$appName"

Handling mounted di­rec­to­ries

To en­sure that the script is able to even­tu­ally deal with sit­u­a­tions where the com­mand is run in suc­ces­sion, a check is re­quired to fig­ure out if the mount point is cur­rently mounted.

If it is mounted, we will un­mount it.

if [ ! -d $appDir ]; then
    mkdir -p $appDir
elif [[ $(findmnt -M "$appDir") ]]; then
    echo "Mounted"
    encfs -u $appDir
else
    echo "Not mounted"
fi

Stash cre­ation

Additionally, for cases where the stash does not yet ex­ist, we will need to cre­ate the other di­rec­tory as well. We shall also kill the ap­pli­ca­tion if the stash is to be mounted (for se­cu­rity). This por­tion was aided by this stack ex­change thread.

if [ ! -d $appDir ]; then
    mkdir -p $appDir
elif [[ $(findmnt -M "$appDir") ]]; then
    echo "Mounted"
    # Added here
    killall -9 $1
    encfs -u $appDir
    exit 1
else
    echo "Not mounted"
fi
if [ ! -d $cryptDir ]; then
    mkdir -p $cryptDir 
fi

Handling Authentication

Finally we shall deal with cases where the script ex­e­cutes and the stashes ex­ist, but the pass­word is in­cor­rect. Additionally, we shall deal with man­ag­ing the flow of con­trol via the $? vari­able.

Quite sim­ply, the $? vari­able holds the re­sult of the pre­vi­ous com­mand. Hence it can be used to con­trol the flow. This was in­spired by the an­swers here.

# Run the program is the stash was mounted
RESULT=$?
if [ $RESULT -eq 0 ]; then
  echo success
  $1
else
  echo failed
  rm -rf $appDir
fi

Putting it all to­gether

For the lat­est re­vi­sions check my Dotfiles.

It is also re­pro­duced here.

#!/usr/bin/bash

# Usage
# unlockEncfsApp.sh $appname
# Copyright (c) 2018 Rohit Goswami <rohit dot goswami at yahoo dot com>
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Program Implementation
#
# Get a capitalized app name
# bash specific
tempName=( $1 )
appName=$(echo "${tempName[@]^}")
unlockString="Unlock $appName"
# Determine the appropriate ASKPASS program
if which zenity >/dev/null 2>&1; then
    askPass="zenity --password --title=$unlockString"
elif which git >/dev/null 2>&1; then
    askPass="/usr/lib/git-core/git-gui--askpass $unlockString"
else echo "ERROR: No valid (zenity or git) askpass program available\n"
     fi
#
# TODO work on the logic when the folder is mounted
#
# Create the directories
cryptDir="$HOME/Encfs/.$appName"
appDir="$HOME/.decrypt/$appName"
if [ ! -d $appDir ]; then
    mkdir -p $appDir
elif [[ $(findmnt -M "$appDir") ]]; then
    echo "Mounted"
    # Added later
    killall -9 $1
    encfs -u $appDir
    exit 1
else
    echo "Not mounted"
fi
if [ ! -d $cryptDir ]; then
    mkdir -p $cryptDir
fi
# Mount the stash
# TODO handle cases where the stash is created for the first time
encfs --extpass="$askPass" $cryptDir $appDir
# Run the program is the stash was mounted
RESULT=$?
if [ $RESULT -eq 0 ]; then
  echo success
  $1
else
  echo failed
  rm -rf $appDir
fi

Future Directions

There ought to be a non-ter­mi­nal way of cre­at­ing the stash for the first time. Also, it may be in­ter­est­ing to work on the rules and con­fig­u­ra­tion schemes for a va­ri­ety of ap­pli­ca­tions.