blog

2020-12-29

Paranoid password printing with a Raspberry Pi

Motivation and method

My password manager runs on my macOS desktop computer. I wanted to print a plain text copy of my password list for storing in a safe deposit box.

I wanted to do this without having any files that could contain unencrypted sensitive information ever being written to disk by the password manager or the print spooler, or travelling over any network.

This is the method I used:

  1. Create a RAM disk on the macOS system.
  2. Apply a sandbox to the password manager to restrict file output to the RAM disk.
  3. Export a plain-text copy of the password list to the RAM disk.
  4. Use AESCrypt to encrypt the text file.
  5. Copy the encrypted text file to the Mac hard drive.
  6. Unmount the RAM disk.
  7. Modify a Raspberry Pi to allow booting from a flash drive.
  8. Prepare a "Live boot" version of Raspbian OS on a flash drive with a hardware write-lock switch.
  9. Boot the flash drive on a Raspberry Pi in writeable mode. (Do not use an SD card in the Raspberry Pi.)
  10. Copy the encrypted text file to the flash drive.
  11. Shut down and reboot the flash drive in write-locked mode.
  12. Create a RAM disk on the running Raspbian system.
  13. Decrypt the encrypted text file to the RAM disk.
  14. Connect a simple USB printer to Raspberry Pi.
  15. Print the unencrypted text.

RAM disk on macOS

Following Create APFS RAM disk on macOS High Sierra at stackoverflow, I created a RAM disk:

sizeInMB=100
diskutil partitionDisk $(hdiutil attach -nomount ram://$((2048*sizeInMB))) 1 GPTFormat APFS 'RAMdisk' '100%'

Sandbox app and export passwords

Use of this unofficial sandboxing technique is described in more detail in the previous post, Sandboxing a third-party macOS app to restrict writing to one folder.

In the following, MyPasswordProgram is a stand-in for the name of the actual password manager in use.

  1. Make a copy of MyPasswordProgram and put it in a work directory, e.g., ~/Documents/Projects/Sandbox.
  2. Overwrite the entitlements.
    cd ~/Documents/Projects/Sandbox
    codesign --force -s - MyPasswordProgram.app/Contents/MacOS/MyPasswordProgram
    
    
  3. At this point, when I double-clicked the modified program it thought I was a new user with no data. I had to restore my data from a backup. (MyPasswordProgram has a feature allowing backing up data to a separate file.)
  4. Create a sandbox profile, mypasswordprogram.sb to restrict file output:
    (version 1)
    (allow default)
    (deny network*)
    (deny file-write*)
    (allow file-write* (subpath "/Volumes/RAMdisk/") )
    
  5. Launch MyPasswordProgram with sandboxing:
    sandbox-exec -f mypasswordprogram.sb MyPasswordProgram.app/Contents/MacOS/MyPasswordProgram
    
  6. Export the plain-text password file to /Volumes/RAMdisk/clear.txt

My password manager offered a few options for the format of the exported file. Experimentation revealed that some options used the technique of writing a temporary file (on the hard drive!) first. Fortunately, the plain-text option wrote directly (and only) to the selected output location.

Get AESCrypt command-line tool for macOS

Although the aescrypt.org website has macOS binaries available for download here, I opted to get the source and build it myself.

(I used aescrypt_mac_v314_1_source.tgz, although there might be a newer version by the time you read this.)

tar zxvf aescrypt_mac_v314_1_source.tgz
cd aescrypt_mac_v314_1_source
cd src
make

Using sudo make install after this did not work for me (on macOS High Sierra 10.13), so I installed it by hand.

sudo cp aescrypt /usr/local/bin
sudo cp aescrypt_keygen /usr/local/bin 
sudo cp ../man/aescrypt.1 /usr/local/share/man/man1/

Since the theme is paranoia and because the AESCrypt folks make hash values available, I did verify the source code package before I built it.

$ shasum -a 256 aescrypt_mac_v314_1_source.tgz
736cc8247dde220e553af35a0afd5708d20de4a7c40127b0d1069e5473e3a0df  aescrypt_mac_v314_1_source.tgz

One of the authors says, however, in response to a request for a way to verify the files:

You do put too much faith in code signing and hash values. One can argue that it's a better measure than nothing, but I still argue that it's bad practice to rely on them. If the site was compromised, a person could get a code signing certificate in very short order. A person could also modify any published hash information.

I confess I did not go so far as to verify the GnuGP signature of the file of hashes itself. I didn't read the source code, either.

Encrypt with AESCrypt

This asks for a password, encrypts the clear-text file from the RAM disk, and writes the encrypted result to a work directory (on the hard drive).

cd ~/Documents/Projects/Sandbox
aescrypt -e -o encrypted.dat /Volumes/RAMdisk/clear.txt

Unmount the RAM disk after this is done. (In a Finder window that lists all your devices in the sidebar, this can be accomplished by clicking the Eject ⏏ icon to the right of the RAM disk's name.)

Raspberry Pi booting from flash drive

Flash drive

The Raspberry Pi 3 needs an operation to allow for booting from a USB flash drive, in addition to usual ability to boot from an SD card. That operation is documented in a previous post, Solution for a Broken Raspberry Pi SD Card Reader.

The next step calls for a flash drive with a physical write-lock switch. I got this 8GB one from Kanguru:

Kanguru FlashBlu30 8GB flash drive      Closeup of write-protect switch

This brings up the question, "[StackExchange] How reliable is a write protection switch on a USB flash drive?"

The consensus is that it's pretty good.

Note, however, the following gotcha from Kanguru's support site regarding another one of their flash drives with a write-protect switch:

The SS3's write-protection switch must be set in either the locked or unlocked position BEFORE connecting it to your computer. Once the device is connected to a computer, it will remain in whichever state it is set in regardless of whether you change the switch position. In order to change the device's state from read/write to read-only or vice versa, you would need to safely remove the SS3 from the computer, set the write-protect switch to the desired position, and then reconnect it to the computer.

The above is consistent with comments on the StackExchange link.

Install system

Install Raspberry Pi OS (formerly known as Raspbian) on the flash drive, and prepare it for headless operation from first boot:

  1. Download Raspberry Pi OS Lite. As of date current version was 2020-12-02-raspios-buster-armhf-lite.zip.
  2. Using Balena Etcher, write image to flash drive drive.
  3. If Etcher has unmounted the flash drive, unplug and replug it.
  4. The flash drive appears as a volume named "boot". Create an empty file named "ssh" on it.
    touch /Volumes/boot/ssh
    
  5. Create a file on the volume named wpa_supplicant.conf
    vi /Volumes/boot/wpa_supplicant.conf
    
    with contents similar to this, replacing MyAccessPointName and password1234:
    ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
    update_config=1
    country=US
    
    network={
     ssid="MyAccessPointName"
     psk="password1234"
    }
    
    
  6. To specify a static IP address, it is necessary to edit a file on the rootfs volume on the flash drive, which is not accessible from macOS. Either use a physical Linux machine, or install VirtualBox (along with the "VirtualBox Extension Pack" to get USB 3.0 access) and use a virtual Linux system.
  7. Mount the flash drive on the Linux machine. When using Debian GUI, just plugging it mounts everything automatically. From the command line, I used commands similar to following to identify the device and mount it:
    $ sudo blkid
    [...]
    /dev/sdc1: LABEL_FATBOOT="boot" LABEL="boot" UUID="EBBA-157F" TYPE="vfat" PARTUUID="067e19d7-01"
    /dev/sdc2: LABEL="rootfs" UUID="b3ce35cd-ade9-4755-a4bb-1571e37fc1b9" TYPE="ext4" PARTUUID="067e19d7-02"
    $ sudo mkdir /media/michael/boot
    $ sudo mkdir /media/michael/rootfs
    $ sudo mount /dev/sdc1 /media/michael/boot
    $ sudo mount /dev/sdc2 /media/michael/rootfs
    
  8. Edit the file /etc/dhcpcd.conf using the appropriate path, e.g.:
    sudo vi /media/michael/rootfs/etc/dhcpcd.conf
    
    and append the following (making the appropriate substitutions):
    interface eth0
    static ip_address=172.16.1.21/24    
    static routers=172.16.1.10
    static domain_name_servers=8.8.8.8 8.8.4.4
    
    interface wlan0
    SSID MyAccessPointName
    static ip_address=172.16.1.121/24
    noipv6
    static routers=172.16.1.10
    static domain_name_servers=8.8.8.8 8.8.4.4
    
    
  9. Unmount the flash drive. If using the command line, use commands similar to
    $ sudo umount /dev/sdc1
    $ sudo umount /dev/sdc2
    
  10. Boot the Raspberry Pi with the flash drive. (There should be no SD card installed.) The first time it boots the flash drive, it may take a several minutes before it is accessible for remote login.
  11. Log in via ssh with the default user name pi and the default password raspberry, e.g.,
    ssh pi@172.16.1.21
    
  12. Change the password.
    passwd
    

Live boot Raspbian OS

Configure the new system for read-only operation. This follows the method given in Make your Raspberry Pi file system read-only (Raspbian Buster), which explains the instructions which follow in summary only:

  1. Create a temp directory for user pi.
    mkdir /home/pi/tmp
    
  2. Replace packages.
    sudo apt-get remove -y --purge triggerhappy logrotate dphys-swapfile
    sudo apt-get autoremove -y --purge
    
    sudo apt-get install -y busybox-syslogd
    sudo apt-get remove -y --purge rsyslog
    
    
  3. Edit /boot/cmdline.txt
    sudo vi /boot/cmdline.txt
    
    and append at the end of the line
     fastboot noswap ro
    
  4. Edit /etc/fstab
    sudo vi /etc/fstab
    
    add the ,ro flag to all block devices, for example:
    proc            /proc           proc    defaults          0       0
    PARTUUID=2b404624-01  /boot           vfat    defaults,ro          0       2
    PARTUUID=2b404624-02  /               ext4    defaults,noatime,ro  0       1
    
    and append the following
    tmpfs        /tmp            tmpfs   nosuid,nodev         0       0
    tmpfs        /var/log        tmpfs   nosuid,nodev         0       0
    tmpfs        /var/tmp        tmpfs   nosuid,nodev         0       0
    tmpfs        /home/pi/tmp    tmpfs   nosuid,nodev,uid=pi,gid=pi         0       0
    
    
  5. Delete/create files and create symlinks.
    sudo rm -rf /var/lib/dhcp /var/lib/dhcpcd5 /var/spool /etc/resolv.conf
    sudo ln -s /tmp /var/lib/dhcp
    sudo ln -s /tmp /var/lib/dhcpcd5
    sudo ln -s /tmp /var/spool
    sudo touch /tmp/dhcpcd.resolv.conf
    sudo ln -s /tmp/dhcpcd.resolv.conf /etc/resolv.conf
    
    sudo rm /var/lib/systemd/random-seed
    sudo ln -s /tmp/random-seed /var/lib/systemd/random-seed
    
    
  6. Edit /lib/systemd/system/systemd-random-seed.service
    sudo vi /lib/systemd/system/systemd-random-seed.service
    
    so the [Service] section looks like
    [Service]
    Type=oneshot
    RemainAfterExit=yes
    ExecStartPre=/bin/echo "" >/tmp/random-seed
    ExecStart=/lib/systemd/systemd-random-seed load
    ExecStop=/lib/systemd/systemd-random-seed save
    TimeoutSec=30s
    
  7. Edit /etc/bash.bashrc
    sudo vi /etc/bash.bashrc
    
    and append
    set_bash_prompt() {
        fs_mode=$(mount | sed -n -e "s/^\/dev\/.* on \/ .*(\(r[w|o]\).*/\1/p")
        PS1='\[\033[01;32m\]\u@\h${fs_mode:+($fs_mode)}\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
    }
    
    alias ro='sudo mount -o remount,ro / ; sudo mount -o remount,ro /boot'
    alias rw='sudo mount -o remount,rw / ; sudo mount -o remount,rw /boot'
    
    PROMPT_COMMAND=set_bash_prompt
    
    
  8. Edit /etc/bash.bash_logout
    sudo vi /etc/bash.bash_logout
    
    and insert or append
    mount -o remount,ro /
    mount -o remount,ro /boot
    
    
  9. Reboot
    sudo reboot
    

After rebooting, type

rw
to switch to software-controlled read-write mode, and type
ro
to switch back to software-controlled read-only mode.

To switch the hardware-controlled read-only mode, power off the Raspberry Pi, unplug the flash drive, toggle the write-protect switch, then replug the flash drive and power on the Raspberry Pi.

Encrypted file to the flash drive

In one macOS Terminal window, ssh to the Raspberry Pi and set it to read-write mode.

ssh pi@172.16.1.21
rw

In another macOS Terminal window, use sftp to transfer the encrypted text file.

sftp pi@172.16.1.21
lcd ~/Documents/Projects/Sandbox
put encrypted.dat
exit

Install software on Raspberry Pi

Make sure clock is up-to-date


rw
sudo apt-get install ntpdate
sudo ntpdate pool.ntp.org

Install printing software


sudo apt-get update
sudo apt-get install cups
sudo usermod -a -G lpadmin pi

Edit cupsd.conf

sudo vi /etc/cups/cupsd.conf

Search for the line Listen localhost:631 and change nearby lines to look like this:

# Only listen for connections from the local machine
# Listen localhost:631
Port 631

Search for the lines beginning with Restrict access to the server... and change to look like this:

# Restrict access to the server...

  Order allow,deny
  Allow @local


# Restrict access to the admin pages...

  Order allow,deny
  Allow @local


# Restrict access to configuration files...

  AuthType Default
  Require user @SYSTEM
  Order allow,deny
  Allow @local

Restart cups:

sudo /etc/init.d/cups restart

Plug a simple USB printer (i.e., not multi-function, and having no non-volatile memory) into the Raspberry Pi, and turn on the printer.

From another computer on the local network, log in to the print web server by visiting port 631 on the Raspberry Pi, e.g., https://172.16.1.21:631. (Ignore complaints about an invalid https certificate.) Go to the Printers tab.

The details of the next step, setting up the printer, depend on the printer in use. Mine is an elderly Samsung Xpress SL-M2625D Laser Printer. The model was not listed among the options for Samsung printers. The driver I downloaded from Samsung didn't work. In the end, I had best luck with the Samsung ML-2550 - CUPS+Gutenprint v5.3.1 (grayscale, 2-sided printing) driver.

Make a plain text file, e.g., foo.txt, to test printing.

lp foo.txt

My printer can print double-sided pages this way:

lp -o sides=two-sided-long-edge foo.txt

or a range this way:

lp -o page-ranges=1-2  foo.txt

Install AESCrypt


rw
curl -OL https://www.aescrypt.com/download/v3/linux/aescrypt-3.14.tgz
tar zxvf aescrypt-3.14.tgz 
cd aescrypt-3.14/src
make

install -o root -g root -m 755 aescrypt /usr/bin
install -o root -g root -m 755 aescrypt_keygen /usr/bin
sudo cp ../man/aescrypt.1 /usr/share/man/man1/

Custom formatting tool

The basic lp command prints a plain-text file in a monospaced font. For my printer, this comes out in a size that yields 80 characters per line and 64 lines per page.

I would have liked to be able to use command-line tools such as groff to format the output, but that would have required the ability to send a postscript file to the printer. With the driver difficulties I encountered, I was unable to accomplish that without spending more debugging time.

The plain text password list is a single column of information that looks somewhat like this:

Title : Acme Store
Username : Moe
Password : 12345678


Title : Example.com
Username : Larry
Email : larry@example.com
Password : password1


Title : Bank
Account Number : 1234 567890
PIN : 1234


Title : Foo.com
Username : Curley
Email : curley@foo.com
Password : password1

In order to have a more compact and elegant printout, I wanted to have two columns per page, and to avoid having a column or page break in the middle of an item group.

I wrote a quick program for this (https://github.com/7402/columnizer). This is rough, uncommented code - just as an example of what can be done, rather than something to copied and reused. If anyone in the universe wants it polished, send email.

To build on the Raspberry Pi, just transfer the file columnizer.cpp and then do:

g++ -std=c++0x -o columnizer columnizer.cpp

Decrypt file with AESCrypt

Format text and print

Format.

ro
./columnizer -i tmp/clear.txt -o tmp/formatted.txt

Plug in printer to USB port on Raspberry Pi and power on. Verify printer name. (Make sure printing does not get sent to a networked printer!)

lpstat -d -p

And print (double-sided, if available):

lp -d Samsung_M262x_282x_Series -o sides=two-sided-long-edge formatted.txt

Or single-sided:

lp -d Samsung_M262x_282x_Series formatted.txt

Then shut down the Raspberry Pi.

sudo poweroff

Other references


To contact the author, send email.