[2] Keeping Application Data Safe with GnuPG
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
andsignons
.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
- https://github.com/deterenkelt/dotfiles/blob/3ae623d/.preload.sh
- https://github.com/deterenkelt/dotfiles/blob/f035f3d/bin/run_app.sh
-
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 ↩︎
-
Hacking / Recovering Firefox Saved Passwords. https://realinfosec.com/?p=132 ↩︎
-
Master password encryption in FireFox and Thunderbird. https://luxsci.com/blog/master-password-encryption-in-firefox-and-thunderbird.html ↩︎
-
FireMasterCracker. https://securityxploded.com/firefox-master-password-cracker.php ↩︎
-
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. ↩︎