Keeping my OpenPGP key safe

In order to keep it reasonably secure I do not keep the secret part of my PGP key on internet connected computers. I keep my signing,encryption and authentication keys on my FSFE membership card which doubles up as an OpenPGP card. The certifying key is kept, encrypted, on a small non-networked computer (and backed up elsewhere).

Signing OpenPGP keys

To simplify signing other people's keys I use caff from the Debian signing party package. The way caff works is to sign each uid on a key individually and send that signed key, encrypted to itself, to the e-mail address embedded in the uid. If the recipient can decrypt the message they demonstrate control of the key and thereby verify, to some degree, that the e-mail address and key are controlled by the same person.

Combing the two

Modern MTAs are designed to work in an Internet environment and by default send messages via SMTP. On my disconnected key signing computer there is no Internet and therefore no SMTP. On the face of it this presents a problem for caff .

The retro-computing solution

My first proper job was with a small, and now defunct ISP that, in addition to the then usual dial-up SLIP/PPP Internet connections offered a BBS and UUCP: a system for copying files and remote command execution that works in batched mode . My operating system of choice still supports this. UUCP normally works over serial-ports, phone lines, TCP or even ssh. However as I want an air-gap between my internet-connected computer and my key-signing machine none of these are suitable. My solution then is to run UUCP over Sneakernet.


My solution uses the usbmount,uucpand openssh packages and a thumb drive. On the thumbdrive I create spool, log and pub directories to server as the data directories for a virtual UUCP system. I chown them appropriately. Fortunately UUCP is an old enough part of unix that it has a fixed uid and gid assigned to it meaning that it is the same across all my systems.

I created three directories on each node to support the sneakernet: /etc/opt/uusbcp, /var/opt/uusbcp and /opt/uusbcp/bin . In /etc/opt/uusbcp are most of the usual uucp files plus a file uuid that holds the uuid of the thumbdrive. The config file contains a few unusual entries that ensure the virtual machine can be distinguished from the host machine and stores its data on the thumbdrive when it is plugged in:

nodename        epistle
spool           /var/opt/uusbcp/spool/
pubdir          /var/opt/uusbcp/pub/
logfile         /var/opt/uusbcp/log/Log
statfile        /var/opt/uusbcp/log/Stats
debugfile       /var/opt/uusbcp/log/Debug

Rather than a sys file I created a sys.head that contains only defaults and a pointer to a special port for contacting the system into which the drive is plugged:

chat ""
port TCP
command-path /bin /usr/bin /usr/sbin
commands true
callback true
forward ANY
remote-send ~
remote-receive ~
local-send ~ 
local-receive ~

system dumain
time any

The port file defines that port:

port TCP
type tcp

type pipe
command /usr/bin/ssh -C -x -o batchmode=yes uucp@localhost

I use the uucp user's *~/.ssh configuration to force running uucico with the appropriate userid for the thumbdrive. Password logins are disabled.

I created a script /opt/uusbcp/bin/docall to be run when the thumb drive is plugged in to initiate the UUCP connection to the local machine:

set -e
mount --make-private --bind /etc/opt/uusbcp /etc/uucp
mount --make-private --bind "$2" /var/opt/uusbcp
while  fuser -m /var/opt/uusbcp; do sleep 1 ;done
su - uucp -c "/usr/lib/uucp/uucico -z -x 2 -D -q -S $1"
while fuser -m /var/opt/uusbcp; do sleep 1 ;done

On my signing machine this is a little simpler:

set -e
mount --make-private --bind /etc/opt/uusbcp /etc/uucp
mount --make-private --bind "$2" /var/opt/uusbcp
su - uucp -c "/usr/lib/uucp/uucico -z -x 2 -D -q -S $1"

Finally to ensure my script is called when needed I add a script under /etc/usbmount/mount.d/99_uusbcp:

 UUNAME="$(uuname -l)"
 UUID=$(cat ${UUSBETC}/uuid)
 SNEAKERNET=$(findmnt -n -o TARGET UUID="${UUID}")
 if [  "${SNEAKERNET}" = "${UM_MOUNTPOINT}" -a -n "${SNEAKERNET}" ] 
    set -e
    ls  -1 "${SNEAKERNET}/spool" |grep '^[a-z][a-z0-9-]*[a-z0-9]$'|grep -v -- "^${UUNAME}"'$'|sed -e 's/^/system /' -e 's/$/\nforward ANY/'|cat "${UUSBSYS}.head" - >"${UUSBSYS}.new"
    mv "${UUSBSYS}.new" "${UUSBSYS}"
    mount -o remount --make-rprivate /
    unshare -m /opt/uusbcp/bin/docall "${UUNAME}" "${SNEAKERNET}"
    umount "${SNEAKERNET}"
    e2fsck /dev/disk/by-uuid/${UUID}

The long line beginning with ls -1 generates the sys file used for the thumbdrive based on the sys.head file and the contents of the spool directory on the thumbdrive.

Those of you familiar with UUCP may be wondering why I used unshare and private mounts to mount /etc/opt/uusbcp over /etc/opt/uucp rather than just using the -I option to uuxqt and uucico to specify an alternate config file. The reason is that uuxqt does not pass this option on to uucp when executing multi-hop copies.

On each host in the sneakernet there is an entry in the sys file for the thumb drive uucp host (epistle)with appropriate permissions (essentially only copying into uucppublic for the key-signing machine).


My signing machine's MTA sends mail by piping it into a double hop uux command that will execute rsmtp on my desktop machine the next time I move the thumb drive from the first to the second.

uux epistle!dumain!rsmtp  

Apart from the double hop this is fairly standard for sending mail via UUCP to a smarthost.

Getting keys onto the signing host

The signing machine is a pure satellite system from an e-mail perspective so I can't use e-mail to get keys there. However I can use a double hop uucp command to copy a keyring from my desktop to the /var/spool/uucppublic directory on the signing machine from whence I can pick them up and sign if appropriate.

Future Improvements

I may look into making one uucico call the other directly rather than using ssh and possibly mounting the regular filesystems read-only in the thumb drive environment to improve the protection against hostile thumb-drives. At the moment protection relies on checking the uuid and restricted command execution via UUCP.