remote authentication for whole disk encryption

the (skippable) background

Update 31August2019: Ugh. It all gets rather more complicated than I like by the time you get WiFi and the Tor hidden service running. I was thinking of creating an SD card image, but then I’d have to maintain it for each new release of Raspbian. Josh suggested that, instead, I create a bash script to do the whole install to a vanilla Raspbian. If you’ve tried creating this and got lost, stay tuned and I’ll do the installer as time allows. Email me if you want to encourage me to do it sooner.

I am a fan of whole-disk encryption. It is just about the only way to insure that a lost, stolen, confiscated, or discarded machine doesn’t leak information.

There is a problem, though, with servers. Mine is a very small company with servers scattered around the US (and one overseas) at locations that are not staffed 24/7. What if a server needs restarting, and there is nobody on premises to enter the password to start it back up? It would become impossible to recover from a power failure, a premises alarm event (some of my servers shut down automatically when the premises alarm system detects an intrusion), or even software updates that require a restart.

I have addressed the problem in the past by creating an unencrypted boot partition and an encrypted virtual partition (using LUKS on Linux) for holding sensitive data. It works well, but there is a very real risk of information leaking onto the bootable partition in the form of log and temp files, and requires some extra care with backups. (I generally back up the encrypted partition by backing up the virtual partition file, which can add a lot of time to an rsync backup if your partition file is tens of gigabytes.)

For reference, I have put a copy of the code I use to manage encrypted virtual partitions at the end of this post. The code itself is trivially simple and only relies on widely-vetted, maintained, open source tools.

In fact, let’s enshrine those things as key principles of this project:

  1. Minimal custom code. Preferably only scripts so short and obvious that even I won’t be able to infest them with compromising bugs.
  2. Use only widely-deployed, well-vetted open source packages. My threat model is such that, should someone discover serious vulnerabilities in popular packages like Tor, Raspbian, ssh, or LUKS they’ll be using them against much more visible targets long before I would have to worry about it.
  3. The boot partition is encrypted, full stop. No solutions requiring that “just a little” bootable partition. Okay, so the Master Boot Record (or UEFI equivalent) has to be plaintext: there’s no way around that one without custom motherboard firmware. But no plaintext bootable partitions.

I am also aware that many people feel that virtualization is the answer, or various commercial solutions that support bypasses (!!) of the encryption “just for emergencies” or a solution that boots a tiny system that can then pass the remotely-entered encryption key on to the primary partition to boot. All of these solutions increase the servers’ attack surfaces; some increase it almost infinitely <cough>Symantec</cough>.

the password problem

So the difficulty comes about because any system that meets my criteria will have to get a decryption key from somewhere before any networking connectivity can be created. I want to use LUKS because of it meets the criteria above. But LUKS just enables a minimal USB keyboard interface and text video display output so that the decryption key (passphrase) can be entered. How do I type into a system with no network connectivity when I’m potentially thousands of miles away?

My first thought was that I have multiple servers, many of which have no need of whole disk encryption. Why not use an unencrypted server’s USB port to “type” the password into the encrypted server’s USB port? Easy, I thought, but I couldn’t find any solutions that would serve my purposes. I know recent Linux kernel versions support USB gadgetry, but it looked as if I would have to write significant code to make it work. That not only violates Criterion 1, it offends my lazy sensibilities. I put the “write a Linux USB keyboard emulator” project on the maybe-someday-but-probably-not stack and continued using encrypted virtual partitions.

raspberry pi zero w

My son Joshua was fiddling around with a Raspberry Pi Zero W and implemented a P4wnP1, a “USB attack platform.” It takes the Pi0w (a tiny, $10 single board computer) and turns it into a… virtual keyboard! The P4wnP1 isn’t what I needed, but had the necessary feature of keyboard emulation. A little searching led to “Composite USB Gadgets On The Raspberry Pi Zero“, a page on the iSticktoit.net site with the necessary info for making a virtual keyboard.

the simple version

  • Install a minimal Raspbian image
  • Follow the instructions on iSticktoit.net to enable USB gadget mode
  • Shell into your Raspberry Pi Zero W when you need to restart your server
  • Run this script (as root) to allow you to type your password (I paste it into an executable file called keythru):
#!/bin/bash

# echo keystrokes to the USB port as a keyboard emulator
# ron@risley.net 20190824
#
# far from complete; supports only USASCII input and, even
# then, some characters won't work due to bash quoting
# issues that I haven't been motivated to address
# terminates with ^c ; ^b is mapped to send ^c

SELECT=""

# initialize an array of HID codes
declare -a HIDCD
HIDCD=( 
"\001\000\037\000\000\000\000\000" # NULL
"\001\000\004\000\000\000\000\000" # ^a
"\001\000\006\000\000\000\000\000" # ^b (but sent as ^c)
"\001\000\006\000\000\000\000\000" # ^c
"\001\000\007\000\000\000\000\000" # ^d
"\001\000\010\000\000\000\000\000" # ^e
"\001\000\011\000\000\000\000\000" # ^f
"\001\000\012\000\000\000\000\000" # ^g
"\000\000\052\000\000\000\000\000" # backspace
"\000\000\053\000\000\000\000\000" # tab
"\000\000\050\000\000\000\000\000" # newline/return
"\001\000\016\000\000\000\000\000" # ^k
"\001\000\017\000\000\000\000\000" # ^l
"\001\000\020\000\000\000\000\000" # return
"\001\000\021\000\000\000\000\000" # ^n
"\001\000\022\000\000\000\000\000" # ^o
"\001\000\023\000\000\000\000\000" # ^p
"\001\000\024\000\000\000\000\000" # ^q
"\001\000\025\000\000\000\000\000" # ^r
"\001\000\026\000\000\000\000\000" # ^s
"\001\000\027\000\000\000\000\000" # ^t
"\001\000\030\000\000\000\000\000" # ^u
"\001\000\031\000\000\000\000\000" # ^v
"\001\000\032\000\000\000\000\000" # ^w
"\001\000\033\000\000\000\000\000" # ^x
"\001\000\034\000\000\000\000\000" # ^y
"\001\000\035\000\000\000\000\000" # ^z
"\001\000\021\000\000\000\000\000" # esc
"\001\000\062\000\000\000\000\000" # ^|
"\001\000\060\000\000\000\000\000" # ^]
"\001\000\046\000\000\000\000\000" # ^^
"\001\000\055\000\000\000\000\000" # ^_
"\000\000\054\000\000\000\000\000" # space
"\002\000\036\000\000\000\000\000" # !
"\002\000\037\000\000\000\000\000" # @
"\002\000\040\000\000\000\000\000" # #
"\002\000\041\000\000\000\000\000" # $
"\002\000\042\000\000\000\000\000" # %
"\002\000\043\000\000\000\000\000" # circumflex
"\002\000\044\000\000\000\000\000" # &
"\002\000\045\000\000\000\000\000" # asterisk
"\002\000\046\000\000\000\000\000" # (
"\002\000\047\000\000\000\000\000" # )
"\002\000\056\000\000\000\000\000" # +
"\000\000\066\000\000\000\000\000" # ,
"\000\000\055\000\000\000\000\000" # -
"\000\000\067\000\000\000\000\000" # .
"\000\000\070\000\000\000\000\000" # /
"\000\000\047\000\000\000\000\000" # 0
"\000\000\036\000\000\000\000\000" # 1
"\000\000\037\000\000\000\000\000" # 2
"\000\000\040\000\000\000\000\000" # 3
"\000\000\041\000\000\000\000\000" # 4
"\000\000\042\000\000\000\000\000" # 5
"\000\000\043\000\000\000\000\000" # 6
"\000\000\044\000\000\000\000\000" # 7
"\000\000\045\000\000\000\000\000" # 8
"\000\000\046\000\000\000\000\000" # 9
"\002\000\063\000\000\000\000\000" # :
"\000\000\063\000\000\000\000\000" # ;
"\002\000\066\000\000\000\000\000" # <
"\000\000\056\000\000\000\000\000" # =
"\002\000\067\000\000\000\000\000" # >
"\002\000\070\000\000\000\000\000" # ?
"\002\000\037\000\000\000\000\000" # @
"\002\000\004\000\000\000\000\000" # A
"\002\000\005\000\000\000\000\000" # B
"\002\000\006\000\000\000\000\000" # C
"\002\000\007\000\000\000\000\000" # D
"\002\000\010\000\000\000\000\000" # E
"\002\000\011\000\000\000\000\000" # F
"\002\000\012\000\000\000\000\000" # G
"\002\000\013\000\000\000\000\000" # H
"\002\000\014\000\000\000\000\000" # I
"\002\000\015\000\000\000\000\000" # J
"\002\000\016\000\000\000\000\000" # K
"\002\000\017\000\000\000\000\000" # L
"\002\000\020\000\000\000\000\000" # M
"\002\000\021\000\000\000\000\000" # N
"\002\000\022\000\000\000\000\000" # O
"\002\000\023\000\000\000\000\000" # P
"\002\000\024\000\000\000\000\000" # Q
"\002\000\025\000\000\000\000\000" # R
"\002\000\026\000\000\000\000\000" # S
"\002\000\027\000\000\000\000\000" # T
"\002\000\030\000\000\000\000\000" # U
"\002\000\031\000\000\000\000\000" # V
"\002\000\032\000\000\000\000\000" # W
"\002\000\033\000\000\000\000\000" # X
"\002\000\034\000\000\000\000\000" # Y
"\002\000\035\000\000\000\000\000" # Z
"\000\000\057\000\000\000\000\000" # [
"\000\000\061\000\000\000\000\000" # \
"\000\000\060\000\000\000\000\000" # ]
"\002\000\043\000\000\000\000\000" # ^
"\002\000\055\000\000\000\000\000" # _
"\000\000\065\000\000\000\000\000" # `
"\000\000\004\000\000\000\000\000" # a
"\000\000\005\000\000\000\000\000" # b
"\000\000\006\000\000\000\000\000" # c
"\000\000\007\000\000\000\000\000" # d
"\000\000\010\000\000\000\000\000" # e
"\000\000\011\000\000\000\000\000" # f
"\000\000\012\000\000\000\000\000" # g
"\000\000\013\000\000\000\000\000" # h
"\000\000\014\000\000\000\000\000" # i
"\000\000\015\000\000\000\000\000" # j
"\000\000\016\000\000\000\000\000" # k
"\000\000\017\000\000\000\000\000" # l
"\000\000\020\000\000\000\000\000" # m
"\000\000\021\000\000\000\000\000" # n
"\000\000\022\000\000\000\000\000" # o
"\000\000\023\000\000\000\000\000" # p
"\000\000\024\000\000\000\000\000" # q
"\000\000\025\000\000\000\000\000" # r
"\000\000\026\000\000\000\000\000" # s
"\000\000\027\000\000\000\000\000" # t
"\000\000\030\000\000\000\000\000" # u
"\000\000\031\000\000\000\000\000" # v
"\000\000\032\000\000\000\000\000" # w
"\000\000\033\000\000\000\000\000" # x
"\000\000\034\000\000\000\000\000" # y
"\000\000\035\000\000\000\000\000" # z
"\002\000\057\000\000\000\000\000" # {
"\002\000\061\000\000\000\000\000" # |
"\002\000\060\000\000\000\000\000" # }
"\002\000\065\000\000\000\000\000" # ~
"\000\000\052\000\000\000\000\000" # del
)

IFS='' # prevent parsing of the input line

while true; do # until user types ^c
  read -s -N 1 SELECT
  echo -n $SELECT
  printf -v STROKE '%d' "'$SELECT"
  echo -ne ${HIDCD[$STROKE]} > /dev/hidg0
  sleep 0.1
  echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0
done

It looks long, but it’s mostly the HID translation table. The actual executable code is only 9 lines, and I’m not sure that the sleep statement is even necessary. The script was a quickie and certainly isn’t perfect, but it should be more than adequate for entering passwords as long as you eschew exotic characters. (After all, it’s generally a good idea to rely on length, not complexity, for good passphrases.)

more details

It’s possible to set up a completely headless pi0w. It’s what I do since I have very little desk space at my workstation, no KVM, and I don’t like messing with cables.

I am a big fan of using Tor location hidden services for applications like this. Specifically, by making ssh a v3 hidden service with public key authentication, you get robust NAT traversal which is probably crucial for this application (after all, if you’re already on the local subnet, you’re probably physically close enough to attach a keyboard) and a high degree of security. So to complete this project, you should install Tor, configure a v3 hidden service on port 22, enable public-key authentication and install your public key on sshd, disable password logins, and restrict ssh access to localhost only. I was going to add all this to the tutorial, but it’s rather off-topic and Josh assures me that “both the people who read your blog” probably already know how to do it.

It’s also worth noting that you’ll be entering passwords blindly, but you pretty much do anyway. I’ve found that it works quite reliably to just wait at least three minutes after a reboot, then just shell into the pi0w, launch the keyboard emulator script, and type the password. If you have a huge need to know what’s going on, you could always attach a monitor and then aim a $20 Wyze Cam at it (that’s what I did for testing, as the server is in a different room from my workstation).

appendix: the encrypted virtual partition

For reference, here are the bare bones of the current code I’ve been using to create, mount, and unmount an encrypted virtual partition. This is still a decent solution if you don’t want to buy and configure a pi0w or you need some services to be available after a completely unattended reboot.

Create:

# create a (40GB) zeroed file
dd if=/dev/zero of=apachecrypt.raw bs=1M count=40000

# make a partition
parted apachecrypt.raw mklabel msdos

# create a loop device
sudo losetup -f apachecrypt.raw

# create an encrypted partition and verify it
sudo cryptsetup luksFormat -c aes-cbc-essiv:sha256 /dev/loop0
sudo cryptsetup luksOpen /dev/loop0 apachecrypt
sudo dmsetup ls

# make the filesystem
sudo mkfs.ext3 /dev/mapper/apachecrypt

Mount:

#!/bin/bash

# create a loop device
sudo losetup /dev/loop0 /home/ron/vdisk/apachecrypt.raw

# open encrypted partition and verify it
sudo cryptsetup luksOpen /dev/loop0 apachecrypt

# mount it up
sudo mount /dev/mapper/apachecrypt /media/apachecrypt

Unmount:

#!/bin/bash

sudo umount /media/apachecrypt
sudo cryptsetup luksClose apachecrypt
sudo losetup -d /dev/loop0