Invisible Arch Linux

13 minute read Enclosure Published

Encrypted system using detached LUKS header with air gapped install.
Table of Contents
Caveat lector: This information is intended for entertainment purposes only.

When individuals speak of doing “big fist pumps” after their Arch installs successfully boot it can be hard to contain one’s curiosity about the path that led them there. But it’s hard to understand until you try it yourself.

This was my journey to first install. It was an encrypted one.

In this tutorial I will show you how I repurposed an old MacBook Pro to double-down on privacy using deniable encryption and how you can too.

In cryptography and steganography, deniable encryption is encryption that allows its users to convincingly deny the fact that the data is encrypted or, assuming that the data is obviously encrypted, its users can convincingly deny that they are able to decrypt it. Source: cryptography.fandom.com.

While you could deniably encrypt any system the mid-2014 Mac is special because it had a battery recall. The recall repair work gave owners the perfect excuse to shred their devices using crypto-randomness prior to service. And in that randomness it’s possible to install and run an invisible operating system.

Here’s a gist of the setup:

  • Deniable encryption on Apple SSD[1,2,3,4]
  • Arch Linux full offline installation
  • Ciphertext and unlock keys kept separate
  • 2FA via pre-boot password authentication
  • Large, easy to read font for the console
  • Support for multiple encrypted disks

And some technical details:

  • LUKS2 with detached LUKS header
  • LVM on LUKS with 2 logical volumes:
    • 1 logical /root (ext4)
    • 1 logical /home (ext4)
  • UEFI boot using systemd-boot
  • Dedicated USB header backup partition

Are there reasons not to use deniable encryption? Gentoo mentions technical ones. What they don’t stay, however, is that it’s possible you may be breaking laws in some countries. But I won’t tell if you don’t.

Requirements

You will need the following hardware to complete setup:

  • 1 2GB+ USB flash drive with archiso installed usb1
  • 1 256MB+ USB flash drive to boot the system usb2
  • 1 mid-2014 MacBook Pro (MacBookPro11,5) sda

Expect a deep discount on the Mac if its battery has yet to be replaced or if the previous owner cannot prove the recall work was performed on the machine.

Breaks

During this tutorial you may power down your machine after completing any section, reboot and pick up where you left off using this script:

Expand to view script
test -b /dev/sdd1 && \
mkdir -p /mnt/{usb2,vg1/home} && \
mount -r /dev/sdd1 /mnt/usb2 && \
test -f /mnt/usb2/ob1 && \
cryptsetup open --header /mnt/usb2/ob1 --type luks /dev/sda c1 && \
sleep 1 && \
mount /dev/mapper/vg1-root /mnt/vg1 && \
mount /dev/mapper/vg1-home /mnt/vg1/home && \
test -b /dev/sdd2 && \
mkdir -p /mnt/boot && \
mount /dev/sdd2 /mnt/boot

Verify with lsblk -o NAME,FSTYPE,TYPE,MOUNTPOINT. You should see output like:

Expand to view output
NAME          FSTYPE      TYPE  MOUNTPOINT
loop0         squashfs    loop
sda                       disk
└─c1          LVM2_member crypt
  ├──vg1-root ext4        lvm   /mnt/vg1
  └──vg1-home ext4        lvm   /mnt/vg1/home
sdb           iso9660     disk
├──sdb1       iso9660     part  /run/archiso/bootmnt
└──sdb2       vfat        part
sdd                       disk
├──sdd1       vfat        part  /mnt/usb2
└──sdd2       vfat        part  /mnt/boot

Note: Partition named sdd2 is created during Booting and should not appear beforehand unless usb2 already contained it prior to starting tutorial.

Assumes you have completed Setup and reinserted usb2 after booting Archiso.

Setup

First boot from usb1 and erase the Apple SSD. One of the fastest, more secure ways to do this is to perform a zero-write inside a DMCrypt container.

Tip: setfont sun12x22 and setfont latarcyrheb-sun32 increase font size.

Then connect and mount usb2, and use cryptsetup to prepare a LUKS container where sdd1 is a writable partition on usb2 for detached LUKS header file ob1:

mkdir /mnt/usb2 && mount /dev/sdd1 /mnt/usb2 && \
cryptsetup --header /mnt/usb2/ob1 luksFormat /dev/sda

Now run lsblk -I 8 -o NAME,FSTYPE,SIZE,FSUSED. You should see output like:

Expand to view output
NAME    FSTYPE    SIZE FSUSED
sda             465.9G
sdb     iso9660   7.5G
├──sdb1 iso9660   639M   639M
└──sdb2 vfat       64M
sdd               250M
└──sdd1 vfat      250M    16M

Confirm FSTYPE of sda is not crypto_LUKS and FSUSED of usb2 is 16M.

If everything looks good, continue. Otherwise, run wipefs -a /dev/sda to wipe the SSD file system info, rm /mnt/usb2/ob1 and restart the setup.

Map LUKS container using detached header and prepare logical volumes:

cryptsetup open --header /mnt/usb2/ob1 --type luks /dev/sda c1 && \
pvcreate /dev/mapper/c1 && vgcreate vg1 /dev/mapper/c1 && \
lvcreate -L 32G vg1 -n root && lvcreate -l 100%FREE vg1 -n home

Then format and mount root and home filesystems:

mkfs.ext4 /dev/vg1/root && mkfs.ext4 /dev/vg1/home && \
mkdir /mnt/vg1 && mount /dev/vg1/root /mnt/vg1 && \
mkdir /mnt/vg1/home && mount /dev/vg1/home /mnt/vg1/home

And run lsblk -I 8 -o NAME,FSTYPE,MOUNTPOINT. You should see output like:

Expand to view output
NAME          FSTYPE      MOUNTPOINT
sda
└─c1          LVM2_member
  ├──vg1-root ext4        /mnt/vg1
  └──vg1-home ext4        /mnt/vg1/home
sdb           iso9660
├──sdb1       iso9660     /run/archiso/bootmnt
└──sdb2       vfat
sdd
└──sdd1       vfat        /mnt/usb2

Confirm FSTYPE of c1 of sda is LVM2_member, FSTYPE of vg1-root and vg1-home of c1 is ext4, and MOUNTPOINT of vg1-root and vg1-home are /mnt/vg1 and /mnt/vg1/home.

(Optional) If everything checks out, unplug usb2 and follow the steps in Breaks so you can be confident you won’t lose any work and have to start over again.

Installation

Everything needed to install and run Arch is already available on Archiso. Steps in this section from Offline Installation and licensed under GFDL-1.3-or-later.

Install Archiso to new root:

cp -ax / /mnt/vg1 && \
cp -vaT /run/archiso/bootmount/arch/boot/x86_64/vmlinuz \
  /mnt/vg1/boot/vmlinuz-linux && \
genfstab -L /mnt/vg1 >> /mnt/vg1/etc/fstab

Configure base system:

arch-chroot /mnt/vg1 /bin/bash -c "\
    sed -i 's/Storage=volatile/#Storage=auto/' /etc/systemd/journald.conf && \
    rm /etc/udev/rules.d/81-dhcpcd.rules && \
    systemctl disable pacman-init.service choose-mirror.service && \
    rm -r /etc/systemd/system/{choose-mirror.service,pacman-init.service,etc-pacman.d-gnupg.mount,getty@tty1.service.d} && \
    rm /etc/systemd/scripts/choose-mirror && \
    rm /root/{.automated_script.sh,.zlogin} && \
    rm /etc/mkinitcpio-archiso.conf && \
    rm -r /etc/initcpio && \
    pacman-key --init && pacman-key --populate archlinux"

This is a scripted approach and it omits one redundant step listed in the ArchWiki as retrieved on 21 Dec 2019. Tested and known to work with archiso 5.3.13-arch1-1. Modifications may be required for other Archiso versions.

Now run lsblk -I 8 -o NAME,SIZE,FSUSED,FSUSE%. You should see output like:

Expand to view output
NAME            SIZE FSUSED FSUSE%
sda           465.9G
└─c1          465.9G
  ├──vg1-root    32G   1.7G     5%
  └──vg1-home 433.9G    72M     0%
sdb             7.5G
├──sdb1         639M   639M   100%
└──sdb2          64M
sdd             250M
└──sdd1         250M    16M     6%

Confirm FSUSE% of vg1-root is 5% and FSUSED of vg1-home is 72M.

Booting

With Arch installed you need a way to boot it. Assuming only one partition exists on usb2 repurpose it for both detached LUKS header and bootability.

Start by reformating usb2 with 16MiB hidden partition:

test -f /mnt/usb2/ob1 && \
cp /mnt/usb2/ob1 /mnt/vg1/home && \
umount /mnt/usb2 && \
parted /dev/sdd rm 1 mkpart primary fat16 2048s 17463KiB \
  set 1 lba off set 1 hidden on && \
mkfs.fat /dev/sdd1 && \
mount /dev/sdd1 /mnt/usb2 && \
mv /mnt/vg1/home/ob1 /mnt/usb2

Check the partition size with df /dev/sdd. You should see output like:

Expand to view output
Filesystem     1K-blocks  Used Available Use% Mounted on
/dev/sdd1          16384 16384         0 100% /mnt/usb2

Confirm Use% is 100% and Available is 0.

Prepare your boot partition and update the file system table:

parted mkpart /dev/sdd primary fat16 36864s 228720000B \
  set 2 esp on && \
mkfs.fat /dev/sdd2 && \
mkdir -p /mnt/boot && \
mount /dev/sdd2 /mnt/boot && \
genfstab -U /mnt/boot >> /mnt/vg1/etc/fstab
Warning: That last genfstab appended one entry to the bottom of file fstab but it’s incorrect. Edit the fstab file so the last item references /boot and not /. There should be three items in total: /, /home and /boot.

Show partitions with parted /dev/sdd print. You should see output like:

Expand to view output
Number  Start   End     Size    Type     File system  Flags
 1      1049kB  17.9MB  16.8MB  primary  fat16        hidden
 2      18.9MB  229MB   210MB   primary  fat16        esp

Confirm Size of 2 is greater than 209.72MB, Type of 1 and 2 is primary with File system of fat16, and Flags of 1 and 2 are hidden and esp, respectively.

Make USB bootable, relaxing validation for boot from non-GPT partition table:

export SYSTEMD_RELAX_ESP_CHECKS=1 && \
test "yes" != "$(bootctl --esp-path=/mnt/boot is-installed)" && \
bootctl --esp-path=/mnt/boot install

You should see output like:

Expand to view output
Created "/mnt/boot/EFI".
Created "/mnt/boot/EFI/systemd".
Created "/mnt/boot/EFI/BOOT".
Created "/mnt/boot/loader".
Created "/mnt/boot/loader/entries".
Created "/mnt/boot/EFI/Linux".
Copied "/usr/lib/systemd/boot/efi/systemd-bootx64.efi" to "/mnt/boot/EFI/systemd/systemd-bootx64.efi".
Copied "/usr/lib/systemd/boot/efi/systemd-bootx64.efi" to "/mnt/boot/EFI/BOOT/BOOTX64.EFI".
Created "/mnt/boot/01234567890abcdef1234567890abdf0".
Random seed file /mnt/boot/loader/random-seed successfully written (512 bytes).
Created EFI boot entry "Linux Boot Manager".

Where 01234567890abcdef1234567890abdf0 is a pseudo-random default boot loader and currently specified in the loader config generated by bootctl in the last step.

Create an entry for the default loader using a subshell:

(
  default="$(grep '^default' /mnt/boot/loader/loader.conf)"
  prefix="${default:8:33}"
  version=1
  cp "/usr/share/systemd/bootctl/arch.conf \
    /mnt/boot/loader/entries/$prefix$version.conf" && \
  sed -in '/options\|##\^$/d' /mnt/boot/loader/entries/$prefix$version.conf && \
  echo "options root=/dev/vg1/root" >> /mnt/boot/loader/entries/$prefix$version.conf
)
Note: Using a subshell prevents assigned variables from lingering.

Update mkinitcpio.conf to use systemd and configure suggested settings:

(
  conf="$(</mnt/vg1/etc/mkinitcpio.conf)"
  hooks="$(grep '^HOOKS' <<< $conf)"
  files="$(grep '^FILES' <<< $conf)"
  conf="${conf//$hooks/#$hooks\\nHOOKS=(base systemd autodetect keyboard sd-vconsole modconf block sd-encrypt sd-lvm2 filesystems fsck)}"
  conf="${conf//$files/#$files\\nFILES=(/boot/ob1)}"
  echo "$conf" > /mnt/vg1/etc/mkinitcpio.conf
)
Tip: Type mkinitcpio -H then tab tab to list hooks & view hook help docs.

Compare the updated file against the Archiso original:

diff -u0 /etc/mkinitcpio.conf /mnt/vg1/etc/mkinitcpio.conf

You should see output like:

Expand to view output
--- /etc/mkinitcpio.conf        1970-01-01 00:32:50.000000000 +0000
+++ /mnt/vg1/etc/mkinitcpio.conf         1970-01-01 00:32:50.000000000 +0000
@@ -19 +19,2 @@
-FILES=()
+#FILES=()
+FILES=(/boot/ob1)
@@ -52 +53,2 @@
-HOOKS=(base udev autodetect modconf block filesystems keyboard fsck)
+#HOOKS=(base udev autodetect modconf block filesystems keyboard fsck)
+HOOKS=(base systemd autodetect keyboard sd-vconsole modconf block sd-encrypt sd-lvm2 filesystems keyboard fsck)

Verify new FILES and HOOKS were added and their previous values commented out.

Afterwards create the vconsole.conf and crypttab.initramfs files for the sd-vconsole and sd-encrypt hooks, respectively, as noted in their help docs:

echo "FONT=latarcyrheb-sun32\nKEYMAP=us" > /mnt/vg1/etc/vconsole.conf && \
echo "c1 $(find /dev/disk/by-id -name '*APPLE_SSD*') - header=/boot/ob1" \
  > /mnt/vg1/etc/crypttab.initramfs

Copy LUKS header and mount the ESP inside the chroot jail:

cp /mnt/usb2/ob1 /mnt/vg1/boot && \
mkdir -p /mnt/vg1/boot/efi && \
arch-chroot /mnt/vg1 /bin/bash -c "\
  findmnt /boot/efi || mount /dev/sdd2 /boot/efi"

Confirm mountpoint exists and ESP installed:

arch-chroot /mnt/vg1 /bin/bash -c "\
  export SYSTEMD_RELAX_ESP_CHECKS=1 && \
  bootctl is-installed || findmnt /boot/efi || lsblk"

Verify result is yes otherwise use the findmnt or lsblk output to help debug. And with the ESP installed generate the image required to boot the system:

arch-chroot /mnt/vg1 /bin/bash -c "mkinitcpio -P"

You should see output like:

Expand to view output
==> Buildiing image from preset: /etc/mkinitcpio.d/linux.preset: 'default'
  -> -k /boot/vmlinux-linux -c /etc/mkinitcpio.conf -g /boot/initramfs-linux.img
==> Starting build: 5.3.13-arch1-1
  -> Running build hook: [base]
  -> Running build hook: [systemd]
  -> Running build hook: [autodetect]
  -> Running build hook: [keyboard]
  -> Running build hook: [sd-vconsole]
  -> Running build hook: [modconf]
  -> Running build hook: [block]
  -> Running build hook: [sd-encrypt]
  -> Running build hook: [sd-lvm2]
  -> Running build hook: [filesystems]
  -> Running build hook: [fsck]
==> Generating module dependencies
==> Creating gzip-compressed initcpio image: /boot/initramfs-linux.img
==> Image generation successful
==> Buildiing image from preset: /etc/mkinitcpio.d/linux.preset: 'fallback'
  -> -k /boot/vmlinux-linux -c /etc/mkinitcpio.conf -g /boot/initramfs-linux-fallback.img -S autodetect
==> Starting build: 5.3.13-arch1-1
  -> Running build hook: [base]
  -> Running build hook: [systemd]
  -> Running build hook: [autodetect]
  -> Running build hook: [keyboard]
  -> Running build hook: [sd-vconsole]
  -> Running build hook: [modconf]
  -> Running build hook: [block]
==> WARNING: Possibly missing firmware for module: wd719x
==> WARNING: Possibly missing firmware for module: aic94xx
  -> Running build hook: [sd-encrypt]
  -> Running build hook: [sd-lvm2]
  -> Running build hook: [filesystems]
  -> Running build hook: [fsck]
==> Generating module dependencies
==> Creating gzip-compressed initcpio image: /boot/initramfs-linux-fallback.img
==> Image generation successful
arch-chroot /mnt/vg1 /bin/bash -c "mkinitcpio -P 20.24s user 4.82s system 107% cpu 23.390 total

Copy both initcpio image files generated in the last step to /mnt/boot and then remove the duplicate LUKS header now embedded in the images:

cp -ai /mnt/vg1/boot/initramfs-linux* /mnt/boot && \
cp -ai /mnt/vg1/boot/vmlinuz-linux /mnt/boot && \
rm /mnt/vg1/boot/ob1

Finally, set a root password and power down the machine:

arch-chroot /mnt/vg1 /bin/bash -c "passwd" && shutdown -P now

Reboot

Unplug both usb1 and usb2 then power on the machine.

As the computer boots you should see a blinking folder icon with a question mark inside as described in Troubleshooting. If you do, go ahead and insert usb2 into one of the USB ports on the machine.

At this point the folder will stop blinking and the boot process will begin. Once authenticated and booted you will be presented with a login prompt:

Arch Linux 5.3.13-arch1-1 (tty1)

archlinux login: _

Type root for the login and press Enter. A password prompt will then appear where you can enter the password created in the last step of Booting. Once logged in you are finished and may begin using your invisible arch.

Summary

In this tutorial you learned how to use a mid-2014 Mac to achieve a form of deniable encryption by installing Arch Linux offline. For improved deniability bear in mind UEFI boot loaders are stored in firmware at /sys/firmware/efi and you may wish to use efibootmgr to clear some of those out at some point.

Once the Mac has reached EOL it is important you dispose of it properly. I have prearranged a drop-off location where you may safely destroy the device:

08°20'34.6"S, 115°30'26.32"E

Just be sure to tip the local Banjar appropriately.

Troubleshooting

If during system boot, all you see is a blinking folder with a question mark:

  • You need to install an operating system
  • You need to boot to your invisible operating system

Note the blinking folder is the expected behavior after after performing a zero-write as described in the first step of Setup. It is also the expected behavior when attempting to start your machine without usb2 attached. If you decide to bail on your Arch install there’s always Manjaro.

If during Reboot you do not receive a password prompt and see:

Expand to view output
[ TIME ] Timed out waiting for device /dev/disk/by-id/dm-uuid/CRYPT-LUKS2-fe06f149a69e844682323674ee70d4bf-c1.
[DEPEND] Dependency failed for Cryptography Setup for c1.
[DEPEND] Dependency failed for Local Encrypted Volumes.
...
[FAILED] Failed to start Switch Root.

You likely need to modify crypttab.initramfs to point to your physical SSD. When finished, regenerate your RAM disk and try booting from usb2 again.

If during Reboot you do receive a password prompt and see:

Expand to view output
[  OK  ] Created slice system-lvm2\x2dpvscan.slice.
         Starting LVM2 PV scan on device 254:0...
[  OK  ] Started Cryptography Setup for c1.
[  OK  ] ...
[  OK  ] Create list of static device nodes in /dev.
[  OK  ] ...
[FAILED] Failed to start Switch Root.

You likely need to modify the root in your ESP loader entry like options root=/dev/vg1/root, regenerate your RAM disk and try booting from usb2 again.

To confirm the expected mappings open the emergency shell and then run command lvm vgscan and lvm lvscan and review the output. The expected device node for root should be listed in the output.


  1. A detached header affords “a system on an unpartitioned, encrypted disk that will be indistinguishable from a disk filled with random data, which could allow deniable encryption”. ArchWiki, Encrypting an entire system, 6 (Plain dm-crypt). Retrieved 2019-12-24. ↩︎

  2. “By using a detached header the encrypted blockdevice itself only carries encrypted data, which gives deniable encryption as long as the existence of a header is unknown to the attackers.” ArchWiki, dm-crypt/Specialties, 6 (Encrypted system usng a detached LUKS header). Retrieved 2019-12-24. ↩︎

  3. Solid-state drives with detached LUKS header afford “the same security as on a magnetic disk.” DMCrypt FAQ 5.19 (Security Aspects). Retrieved 2019-12-24. ↩︎

  4. “If plausible deniability is required, TRIM should never be used […] or the use of encryption will be given away.” ArchWiki, dm-crypt/Specialties, 4 (Discard/TRIM support for solid state drives (SSD)). Retrieved 2019-12-24. ↩︎