[2] Keeping Application Data Safe with GnuPG

   |   12 minute read   |   Using 2518 words

by deterenkelt

Many applications store data on the disk unencrypted or in a way that can be easily cracked. Web browsers, IM, email and WebDAV clients do this, but that’s only the beginning of the list. Roughly 80% of a user’s passwords are stored unencrypted. Let’s take a look at how Firefox stores them, for example. If a master password is specified, Firefox uses 3DES, or Triple Data Encryption algorithm. However, if no master password is set, the data is about as hard to crack as any plain text document.1,2 With no master password, files are simply encoded into base 64, which is not meant to encrypt data, but to change its representation to an ASCII string that would be easy to transmit (e.g. embedding small images in HTML files to avoid superfluous server requests).

3DES is considered safe to use until 2020, but it usually requires three different passwords, and 3DES' strength highly depends on the strength of those passwords.3,4 A short password offers the same security as no password, because not only does specialized cracking software exist for 3DES encrypted files, such software is freely available.5 This means that if we want our data to be secured, we have to use a long master password for each program that supports a good type of encryption. And what about the programs that don’t? Moreover, remembering dozens of long master passwords for browsers, IM, and email, as well typing them every time a program starts? That’s a bit inconvenient. Fortunately, keychains exist for dealing with this; however, keychains may lack support and still require logging in – certainly not the most convenient. And what’s the need in a keychain, if GPG is already there? All that is needed is to encrypt some files holding account information, and decrypt them temporarily somewhere outside the disk. This article covers an example of such a setup.

Figure 1 (above) illustrates the Firefox encryption scheme. It uses two files, key3.db and signons. sqlite which reside in the profile folder. The former contains hash and salt, while the latter website logins.

Prerequisites

This article is aimed at people who are experienced with GNU/Linux, do some shell scripting and have already installed and set up GnuPG.6

Autorun Part

Upon the autorun script two tasks are laid: ready gpg-agent, and modify the environment for the application wrapper script. The wrapper doesn’t have to be called from the autorun script. For demonstrative purposes, let’s say it’s called somewhere at the end of the autorun script or right after it.

There are many ways to set something to run after login in a GNU/Linux OS. Most preferred would be with ~/.xinitrc or ~/.Xsession, since they’re executed right after X starts, but before it launches any window manager, hence they’re best suited Figure 1 for altering the environment. These two files and starting routines are well described in the X Windows System Administrator’s Guide.

First of all, we should find and mount our removable drive, which may be a flash stick, smartphone SD card, or any other removable media. Let’s say that the volume on the stick is labeled ‘PHONE_CARD’ and we mount it to a directory named ~/phone_card.

grep -qF "$HOME/phone_card" /proc/mounts && \
    sudo /bin/umount $HOME/phone_card

We must be sure it is not mounted and there is no ‘stuck’ record, while the device is not attached physically. sudo is important, because most will work from an ordinary user environment (and that’s the right way to do it). In order to make sudo run this and some further commands automatically without asking for the root password, several lines should be added to /etc/sudoers.

Cmnd_Alias ALLOW_ME_THESE_COMMANDS = /bin/mount, \
                                     /bin/umount, \
                                     /sbin/findfs
User_Alias ME = your_user_name
Defaults:ME env_reset
Defaults:ME env_keep += SHELL
ME ALL = (root) NOPASSWD: ALLOW_ME_THESE_COMMANDS

Don’t forget to replace your_user_name with an actual name.

rm -f $HOME/phone_card
mkdir -m700 $HOME/phone_card
c=0; until grep -q $HOME/phone_card /proc/mounts || {
    disk=$(sudo /sbin/findfs LABEL=PHONE_CARD) && \
        sudo /bin/mount -t vfat -o users,fmask=0111,dmask=0000,rw,\
codepage=850,iocharset=iso8859-1,utf8 \
        $disk $HOME/phone_card
    }; do
    sleep 1 && [ $((c++)) -gt 300 ] && break
done

A simple until cycle with a counter variable. The cycle body evaluates until $HOME/phone_card is found in /proc/mounts. The body simply checks for a timeout of five minutes (300 s). Now to the condition. There is a subshell call for /sbin/findfs that will attempt to find a volume with label PHONE_CARD, and, if it does, the path to the actual device (e.g. /dev/sdc1) will be placed to a variable named disk. Then this name is used in the mount command, which sets options and filesystem type to vfat. The users option is to make userspace daemons happy, otherwise it won’t be possible to unmount the flash card from Thunar, for example; fmask and dmask are usual parameters forbidding file execution on this medium, finally codepage and iocharset are important for correct name handling on conversion to UTF-8. The list of encodings can be seen in the kernel configuration menu in File Systems –> Native Language Support. The settings above are for Western European languages.

rm -rf /tmp/decrypted &>/dev/null
mkdir -m 700 /tmp/decrypted
cp -R ~/phone_card/.gnupg /tmp/decrypted/
chmod -R a-rwx,u=rwX /tmp/decrypted
sudo /bin/umount $HOME/phone_card && rmdir ~/phone_card

/tmp/decrypted is the directory where .gnupg and all decrypted files are to be placed. It resides in /tmp, which has to be of the temporary filesystem type, tmpfs. That’s the simplest way to keep files in RAM while the computer is running. A symbolic link to .gnupg is placed in the $HOME directory and file permissions are fixed because vfat doesn’t understand UNIX permissions. chmod is done recursively on that folder, first depriving all from any rights, then giving 600 to files and 700 to directories for the user running the script. After all the files are copied, the card is removed and the folder is deleted (that last line is optional, the card can be left mounted, so it’d be possible to unmount it when leaving the computer). Edit the /tmp related line in /etc/fstab to this:

tmpfs /tmp tmpfs defaults 0 0

Somewhere around here, you should set up the keyboard layout, because if the passphrase contains symbols that are not present in some default PC101 layout, you won’t be able to enter them.

export GNUPGHOME=/tmp/decrypted/.gnupg
pgrep -u $UID gpg-agent || eval $(gpg-agent --daemon --use-standard-socket)
export GPG_TTY=`tty`

First, GNUPGHOME is exported, pointing at the directory where it should look for keys. pgrep checks if a daemon is already running – there’s no need to restart if it was left over from a previous login, and if it’s not present, it’s to be launched. Current version of GnuPG (2.1.6) doesn’t rely upon GPG_AGENT_INFO completely, but it seems to be broken, so the code above refers to 2.0.26-r3 as the most recent stable. Don’t forget to put use-agent in the gpg.conf. The recent versions of GnuPG should also start the agent automatically when gpg is called, and they do, but later calls to gpg don’t seem able to connect to the agent, the example above is guaranteed to work for 2.0.26. The output of gpg-agent is eval’d because it exports an important variable containing the path to the agent socket. For instance,

GPG_AGENT_INFO=/tmp/decrypted/.gnupg/S.gpg-agent:19667:1

GnuPG since v2 uses the name $GNUPGHOME/S.gpg-agent instead of a temporary directory with a random name in /tmp, but in 2.0.26 gpg still isn’t satisfied unless it finds the address to the socket in GPG_AGENT_INFO variable.

This variable shall be present in the environment of every program that wants to decrypt anything with keys that gpg-agent holds. At least until GnuPG 2.1 is fixed. However, there is another necessary variable, GPG_TTY, that gets exported to the current environment, too. It should be stressed, that only those programs that are launched from this shell or its descendants will inherit these variables in their environment. This is why the ~/.xinitrc or ~/.Xsession way is preferred: autorun scripts in desktop environments may be run not in the same shell that spawned the DE session, and the environment will get alternated in its own ‘branch’ that other applications won’t know of. In order for GPG_* variables to get into every shell on your workspace, they must be exported in the same shell that spawns the DE or earlier. To think of it, this export may take place even in ~/.bashrc, but it would mean that the whole thing has to be there, and that’d be a very bad idea.

export PATH="$HOME/bin:$PATH"

~/bin folder will contain the wrapper script. Here the autorun part ends, and it’s the last place to set up the keyboard which must be done before the first call to the wrapper, because when pinentry (the program that is used to ask for a passphrase) window pops up… Right, the keyboard will be still in some default Xorg keyboard layout, and if the passphrase is good and contains characters that are not present on it, they will be impossible to enter.

Wrapper

The scheme is as follows: there is a request for a program, let’s say, firefox. Since ~/bin is the first directory in PATH, it is checked before other directories. The shell will look for a file named ‘firefox’ there. And we want to run a wrapper instead of Firefox, so how do we do that? We create a symlink named ‘firefox’ that is pointing to the wrapper in the same folder, and in the wrapper we distinguish between applications by the $0 variable which is the script name. But in this case, it will be the name of the symbolic link the script is called as. How do we avoid recursion while attempting to call actual Firefox, when we’re done with decrypting its files? It just needs to be called with the full path, like /usr/bin/firefox, or whatever ‘which firefox’ says.

In general, to launch several applications in an autostart script, the following code may be employed:

startup_apps=(firefox thunar pidgin)
# ...
for app in "${startup_apps[@]}"; do
    pgrep -u $UID -f "^$app\>" >/dev/null || { (nohup $app) & }
done

pgrep checks whether the application is already running and if it isn’t, it’s launched via a forked subshell with nohup (to prevent it from dying after its parent shell closes). The user id and the regular expression are to distinguish between applications that do not belong to the current user and those whose command line does not coincide with the simple file names as specified in the array above, i.e. to not confuse Bob’s firefox with Alice’s and mpd with mpdscribble.

Let’s look at the wrapper script internals.7

run_app is the function for running the actual application and decryption procedures. It works for every program. It takes at least two arguments: $1 is absolute path to the actual binary and $2..n are absolute paths to the .gpg files with secrets that reside in the same directory as their unencrypted contents.

local app=$1
[[ "$app" =~ ^.*/.*$ ]] || {
    echo "This function must be given an absolute path \
to an actual binary, like /usr/bin/... or so." >&2
    exit 4
}
shift 1

Next, the files are decrypted.

for gpgfile in "$@"; do
    [ -e "$gpgfile" ] && {
        gpgfiles[${#gpgfiles[@]}]="$gpgfile"
        tmpfile="/tmp/decrypted/${gpgfile##*/}"
        tmpfile="${tmpfile%.gpg}"
        tmpfiles[${#tmpfiles[@]}]="$tmpfile"

Clearing these variables is important if you alternate them, e.g. to add SCIM support. Pinentry has a known bug that causes input to be impossible sometimes.

GTK_IM_MODULE= QT_IM_MODULE= gpg -qd --output "$tmpfile" --yes "$gpgfile"

Saving last modification time for decrypted files to know whether they were changed afterwards.

lastmods[${#lastmods[@]}]=`stat -c %Y "$tmpfile"

…and symbolic links placed to where the application expects plain files to be.

        ln -sf "$tmpfile" "${gpgfile%.gpg}"
    }   # end of "test -e"
done # end of "for gpgfile"

If the same wrapper hangs in memory, wait for five seconds and then bail out.

while pgrep -xf "^bash ${0##*/}$"; do
    sleep 1
    [ $((i++)) -gt 5 ] && break
done

Testing if the actual app is still/already running, not allowing a second instance.

pgrep -axf "^$app$" || "$app"

When the application closes, check whether it has modified the files.

for ((i=0; i<${#tmpfiles[@]}; i++)); do
    [ ${lastmods[$i]} -lt "`stat -c %Y "${tmpfiles[i]}"`" ] && \
        modified=t
done
[ -v modified ] && {
    for ((i=0; i<${#gpgfiles[@]}; i++)); do

Sign, encrypt to hidden recipient, and place the updated file where the old one was.

GTK_IM_MODULE= QT_IM_MODULE= gpg --batch -se --yes \
    --output ${tmpfiles[i]}.gpg \
    -R *$USER ${tmpfiles[i]} &>>$elog || \
    echo "GPG couldn't encrypt ${tmpfiles[i]##*/}. See $elog \
for details." >&2

Not mv, because it erases symlinks.

        cp "${tmpfiles[i]}.gpg" "${gpgfiles[i]}"
done } # end of check for modified files

Erasing files from tmpfs. This may be done here, or the whole /tmp/decrypted can be erased on logout (if the computer is not to be switched off).

for ((i=0; i<${#gpgfiles[@]}; i++)); do
    rm ${tmpfiles[i]} ${tmpfiles[i]}.gpg
done
# ... end of run_app()

And here are the actual first lines to be executed when the wrapper starts. They get the name of the app the wrapper should start as and enable logging.

app_name=${0##*/}
elog="/tmp/runapp_$app_name"
echo >$elog
exec &>$elog
set -x

As simple as that. Each application is a case statement, which decides the paths to an actual executable and encrypted files for a program.

case $app_name in
    # NB: only actual binaries with absolute paths here!
    firefox)
        [ -e /usr/bin/firefox-bin ] && \
        firefox=/usr/bin/firefox-bin || firefox=/usr/bin/firefox

$@ are the arguments passed to the command. When some other application runs firefox with arguments, it usually wants to open a link, so if Firefox is already running, don’t start another wrapper and send a command opening the link in a new tab.

if pgrep -u $UID -xf "^$firefox$" &>/dev/null; then
    $firefox -new-tab "$@"
else
    run_app $firefox \
        ~/.mozilla/firefox/profile.xqwzts/key3.db.gpg \
        ~/.mozilla/firefox/profile.xqwzts/signons.sqlite.gpg
fi

Don’t forget to replace profile.xqwzts with an actual profile folder.

*)
cat <<"EOF"
Usage:
Just use symbolic links:
ln -s ~/bin/run_app.sh ~/bin/firefox

Don’t forget to export PATH="$HOME/bin:$PATH"!

EOF
        exit 3 ;;
esac # $app_name

Addendum

This setup isn’t meant to form a bastion of safety, it’s aimed at a compromise between convenience and securing files on workstations other people may have access to. The other purpose is to make it safer to backup on cloud services. This setup is vulnerable to attacks from the code users run by their own will, e.g. if a malicious script wants to run something with GPG, it will decrypt files automatically. Of course, the script must know that the files are encrypted with GPG and where they reside. Or that ~/bin/run_app.sh is being used. If you are truly afraid that somebody has access to your computer when you don’t, use keychains and make them ask for a passphrase each time such a request comes. Or better don’t use them at all and perform all encryption operations manually.

Complete File Listings


  1. How browsers store your passwords (and why you shouldn’t let them). https://raidersec.blogspot.in/2013/06/how-browsers-store-your-passwords-and.html ↩︎

  2. Hacking / Recovering Firefox Saved Passwords. https://realinfosec.com/?p=132 ↩︎

  3. Master password encryption in FireFox and Thunderbird. https://luxsci.com/blog/master-password-encryption-in-firefox-and-thunderbird.html ↩︎

  4. https://en.wikipedia.org/wiki/Triple_DES ↩︎

  5. FireMasterCracker. https://securityxploded.com/firefox-master-password-cracker.php ↩︎

  6. wiki.debian.org/Keysigning ↩︎

  7. The snippets of code that follow were edited and focus on the key parts of the system. A link to the full listing can be found at the end of this article. ↩︎