Building a Better Screen Locker for GNU/Linux
by idk
As vendors begin to realize that shipping proprietary firmware only makes devices less competitive and less secure, and heroic reverse-engineering efforts make progress in Freedreno, etnaviV, OpenFWWF, and Lima, software freedom is finally closer than ever on mobile devices.
This makes 2017 and beyond a much more exciting time, with the ability to run a few devices in full freedom, if you are willing to make a few sacrifices in terms of hardware. Unfortunately, this fifth(ish) userspace/middleware in the mobile space means that even more basic components will have to be re-produced in the new environment. One of the most illustrative examples is the screen locker.
Deficiencies of Modern Screen Lockers on Desktop GNU/Linux
What do you want from a screen locker for a mobile device in 2017 and beyond?
I, for one, want something out of a screen locker that no GNU/Linux screen locker I am aware of can give.
That is a transparent, reasonably secure ability to encrypt files on a running device to mitigate the effect of exfiltration by physical means, i.e., someone grabbing my device and walking away with it.
On iOS, the device manages multiple keys, many of which are managed by the screen lock. When the screen lock engages, the user's personal folders are encrypted until the passphrase is re-entered to the lock screen. Actually, that's something I'd like on the desktop, too.
So what do we need to build a sufficient screen locker?
Goals
1.) Delay access by a physical attacker with easy-to-obtain resources, such as malicious HID emulators, physical keyloggers, and attackers who compromise a device by obvious theft.
2.) Hamper the installation of malware by a physical attacker which may be used to log and exfiltrate the screen locker passphrase by disabling channels that may be used to install it.
3.) Give the user of the device a datastore which can be transparently and unobtrusively encrypted and decrypted when the user locks and unlocks the screen, which, if exfiltrated, will be untfeasibly difficult to decrypt.
4.) Have different disk encryption, user login/$HOME decryption, screen lock, and Encrypted Data Store (EDS) passphrases. Never physically enter EDS passphrase. Instead, entering the screen lock passphrase causes it to be unlocked and the EDS locks itself automatically after timing out, or with the screen lock.
Materials
Slock: github.com/fyrix/slock
We use Slock for this project because Slock doesn't do things that suck, like create unnecessarily confusing code. This makes it very easy to modify for our purposes, and there is example code available that can assist us to this effect. You could, in theory, do this with any screensaver, but I did it with Slock. I encourage you to do it with your screensaver of choice.
(Christopher Jeffrey) chjj's Slock: github.com/chjj/slock
Original Slock: git.suckless.org/slock
Xssstate: git.suckless.org/xssstate
We use Xssstate to monitor the X screensaver state. This is because it also sucks a lot less than other ways to do it, and works nicely with Slock.
GPG: www.gnupg.org
For reasons that are perfectly obvious, we use GPG for encrypting the password to the encrypted data store. It's pretty much the only reasonable tool for this. We will be writing a wrapper to help us make sure things are done in a consistent way.
EncFS: vgough.github.io/encfs
Finally, we'll be using EncFS as the way we guard the encrypted data store. Make sure you get the latest version! EncFS is undergoing significant improvements.
GRsecurity: grsecurity.org
We use GRsecurity in a slightly custom configuration in order to make it possible to prevent USB attacks that attempt to brute-force our screensaver by emulating a Human Interface Device (HID). The configuration available to Debian Sid/Jessie-Backports works well with one config-only modification.
Other Things to Note
- Strictly speaking, you need Sudo. Hopefully you already have it.
- It makes good sense to just plain disable IEEE 1394 (FireWire on approved Apple devices) and its descendants, and anything else that you can plug in externally that you don't use could be disabled too.
Creating Our Password and Data Store
To create and manage our data store as best we reasonably can, we should make some preparations. First, we're going to need a passphrase-protected keypair to use with the data in the classic, asymmetric fashion. Just do this with the regular: gpg --gen-key
But generate a random, long passphrase for it and write it down before you finish. Mine is 128 random characters long.
Then, we're going to need a (short) passphrase-protected, symmetrically-encrypted file that contains nothing but the generated data store passphrase. For example:
$ LONG_RANDOM_PASSWORD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 128 | head -n 1) $ echo $LONG_RANDOM_PASSWORD vSn7WFrYDA96Cvbd9A4YIb73vVVM3trwI8lZmZ3oSanaUpHmtNfIRmXKw66iEwQQlTwuDLBY8AexJw7YyCSOhIfuuIGkWRZaD65SnFd0tU8nIZS4LoCzvPgYeq7y0MtzLastly, create an "encrypted" folder in your $HOME directory using EncFS.
$ mkdir -p ~/.crypt ~/crypt $ echo $LONG_RANDOM_PASSWORD | encfs --stdinpass ~/.crypt ~/cryptExample Wrappers for GPG
Now we need to create some wrappers for GPG which will help us when we call out to it from the screensaver to check the password.
This is just a shell script, and on my system it's at /usr/bin/masterscreen:
#!/bin/sh basic_gpg_decrypt() { [ ! -z "$1" ] && VAL=$(gpg --passphrase "$1" -d $HOME/.masterscreen.gpg) echo "$VAL" } generate_gpg_pwfile() { PASS=$(whiptail --passwordbox "please enter your secret password" 8 78 --title "password dialog" 3>&1 1>&2 2>&3) PASSC=$(whiptail --passwordbox "please confirm your secret password" 8 78 --title "password dialog" 3>&1 1>&2 2>&3) LONG_RANDOM_PASSWORD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 128 | head -n 1) [ "$PASS" = "$PASSC" ] && echo "$LONG_RANDOM_PASSWORD" | gpg --cipher-algo AES256 --passphrase "$PASS" --output "$HOME/.masterscreen.gpg" --symmetric unset PASS; unset PASSC; echo "%echo Generating a basic OpenPGP key Key-Type: RSA Key-Length: 4096 Name-Real: masterscreen Name-Email: masterscreen@localhost Expire-Date: 1y Passphrase: $PASSS %commit %echo done" | gpg --gen-key --batch - mkdir -p $HOME/crypt $HOME/.crypt echo $PASSS | encfs --stdinpass ~/.crypt ~/crypt unset PASSS } unload_gpg_datask() { fusermount -u ~/crypt gpg-connect-agent reloadagent /bye } load_gpg_datask() { VAL=basic_gpg_decrypt "$1" gpg-agent --add "$2" --passphrase "$VAL" || echo "failure" && unload_gpg_datask echo $VAL | encfs $HOME/.crypt $HOME/crypt --stdinpass || echo "failure" && unload_gpg_datask } if [ -f "$HOME/.masterscreen.gpg" ]; then [ -z "$2" ] && [ -z "$1" ] && load_gpg_datask "$2" "$1" [ ! -z "$1" ] && unload_gpg_datask else generate_gpg_pwfile fiInit Scripts
If you're going to want to start Slock under its own username, you will probably want to use your init system to start it.
If you use System V init, or if your systemd supports /etc/rc.local, then you can simply start it there and get pretty decent results.
Create this simple wrapper script (called xsidle.sh and is from the Xssstate examples) for Slock, place it into /bin, and run it from your init system.
#!/bin/sh # # Use xset s $time to control the timeout when this will run. # if [ $# -lt 1 ]; then printf "usage: %s cmd\n" "$(basename $0)" 2>&1 exit 1 fi cmd="$@" while true do if [ $(xssstate -s) != "disabled" ]; then tosleep=$(($(xssstate -t) / 1000)) if [ $tosleep -le 0 ]; then $cmd else sleep $tosleep fi else sleep 10 fi doneIf you can use /etc/rc.local, it's as simple as:
$ su $USER /bin/xsidle.sh /usr/bin/slock &YMMV, though. Or, you can run it as your own user by starting it with your .bashrc.
Modifying Slock
First, add the following pre-processing options:
#define USBOFF 1 #define GPGOFF 1 #define STRICT_USBOFF 0Next, create the USB lock functions:
// Turn off USB if we're in danger. static void usboff(void) { #if USBOFF // Needs sudo privileges - alter your /etc/sudoers file: // sysctl: [username] [hostname] =NOPASSWD: /sbin/sysctl kernel.grsecurity.deny_new_usb=0 char *args[] = { "sudo", "sysctl", "kernel.grsecurity.deny_new_usb=1", NULL }; #if STRICT_USBOFF char *argst[] = { "sudo", "sysctl", "kernel.grsecurity.grsec_lock=1", NULL }; execvp(argst[0], argst); #endif execvp(args[0], args); #else return; #endif } // Turn on USB when the correct password is entered. static void usbon(void) { #if USBOFF // Needs sudo privileges - alter your /etc/sudoers file: // sysctl: [username] [hostname] =NOPASSWD: /sbin/sysctl kernel.grsecurity.deny_new_usb=0 char *args[] = { "sudo", "sysctl", "kernel.grsecurity.deny_new_usb=0", NULL }; execvp(args[0], args); #else return; #endif }Now, add the GPG/EncFS lock functions:
// Release the gpg keys and unmount the encfs data store static void gpgon(void) { #if GPGOFF // This resets the GPG agent when the screen is locked. char *args[] = { "masterscreen", NULL }; execvp(args[0], args); #else return; #endif } // Re-add the GPG keys and re-mount the encfs encrypted store. static int gpgoff(const char *password) { #if GPGOFF // This function checks the password from the Screen Locker against the symmetric file. // If it succeeds, then the screen will be unlocked and the key will be added to the gpg-agent. char buf[128]; int i = 0; char *args[] = { "masterscreen", "masterscreen@localhost", &password, NULL }; FILE *p = popen(&args, "r"); if (p != NULL) { while (!feof(p) && (i < 128)) { fread(&buf[i++], 1, 1, p); } buf[i] = 0; if (strstr(buf, "failure")) { return -1; } pclose(p); return 0; } else { return -1; } #else return; #endif }Finally, add the appropriate triggers in the main screenlocker loop, at the bottom of lockscreen (around line 600):
usboff(); gpgon(); return lock; }
at the top of unlockscreen (around line 480):
static void unlockscreen(Display * dpy, Lock * lock) { usbon();and in the middle of readpw() (around line 350):
#if GPGOFF if (gpgoff(passwd) == 0) { running = 0; #elseConfigure Settings
Some of our commands require root access for the Slock user. Since Slock is runnable by the logged-in user without root, you need to make some exceptions to your sudoers policy to make it do what it needs to. I prefer to make the commands totally explicit.
For automatic shutdown after five password attempts (part of the pre-existing mods by chjj):
Modify /etc/sudoers:
systemd: $USER $HOST =NOPASSWD: /usr/bin/systemctl poweroff sysvinit: $USER $HOST =NOPASSWD: /usr/bin/shutdown -h nowFor USB enabling/disabling:
$USER $HOST =NOPASSWD: /sbin/sysctl kernel.grsecurity.deny_new_usb=0 $USER $HOST =NOPASSWD: /sbin/sysctl kernel.grsecurity.deny_new_usb=1In order to change the USB connectivity on-the-fly, we'll have to leave GRsecurity sysctl available to the root user by disabling grsecurity.grsec_lock. You'll need to modify /etc/sysctl.d/grsec.conf.
Simply change: kernel.grsecurity.grsec_lock = 1 to kernel.grsecurity.grsec_lock = 0
In Conclusion
It's possible to have a screen locker for GNU/Linux which is reasonably resilient to local attackers who attempt to brute force the password or install malware while the lock is engaged to log and exfiltrate the password. While a clever attacker will just find another way to install keylogging malware, shoulder surfers, physical keyloggers, or even TEMPEST-style EM keylogging will fail to exfiltrate the real password to the encrypted data store.
ACK
The Suckless Community, chjj on GitHub (whose fork of Slock I in turn forked), Brad Spengler of GRsecurity, Luc Verhaegen for kickstarting ARM GPU freedom, GPG, and all the cypherpunks who came before. And in general, to RMS and the Free Software movement.
Code: masterscreen.sh
Code: xsidle.sh
Code: modify-slock1
Code: modify-slock2
Code: modify-slock3
Code: modify-slock4
Code: modify-slock5
Code: modify-slock6