Last updated on 8 February 2018

Posted by YesDay on 23 September 2017 Tags: odroid-c2 archlinux alarm luks fde encryption

1. Introduction

Full Disk Encryption (FDE) protects our data against unauthorised access in case someone gains physical access to the storage media. This document describes how to install ArchLinux with Full Disk Encryption on ODROID-C2. The encryption method is LUKS with XTS key-size 512 bit (AES-256).

In a nutshell, Full Disk Encryption requires

  • Encrypting a partition and copying the root filesystem to it.

  • The kernel to include the dm_crypt kernel module. In our case this is already included by default therefore we won’t need to re-compile the kernel.

  • The initramfs to include the dm_crypt kernel module and the cryptsetup binary. We use a tool called dracut to generate the required initramfs. Dracut supports the required functionality via the additional modules crypt and lvm.

  • Passing the dracut options for LUKS to the initramfs via the bootargs property inside boot.ini. For example, say that in our case we want the initramfs to unlock a LUKS volume with UUID ae51db2d-0890-4b1b-abc5-8c10f01da353 and load the root filesystem from the device mapper /dev/mapper/vg-root. To pass these dracut options we configure the following

    sudo nano /boot/boot.ini
    setenv bootargs "rd.luks.uuid=ae51db2d-0890-4b1b-abc5-8c10f01da353 root=/dev/mapper/vg-root rootwait <leave the rest as is>"
    Note

    A lot of the steps throughout this document involve editing configuration files. To keep the words to the minimum, we use the above notation as a very concise way to describe such file editing steps. The above notation means

    • You need to edit the file /boot/boot.ini with root privileges (hence sudo nano /boot/boot.ini). Nano is the command line editor, however feel free to use another editor of your choice.

    • Find the line starting with setenv bootargs and add or edit the configuration options rd.luks.uuid=ae51db2d-0890-4b1b-abc5-8c10f01da353 root=/dev/mapper/vg-root rootwait. Some files mentioned throughout this document might have the corresponding line being commented out or not present at all. If that’s the case you will need to uncomment or append the line into the file, respectively.

    • Leave the rest of the line after rootwait as is.

Additionally, for a headless setup you will need to enable remote unlocking via SSH as described in Remotely unlock the LUKS rootfs during boot using Dropbear sshd.

Last but not least, if you prefer to use the described functionality out of the box, simply download this OS image. Either way, the current document will provide more technical details in regards to the underlying components and how they work together in a Full Disk Encryption environment.

2. Hardware requirements

  • ODROID-C2

  • A Linux box from which you will flash the OS image and interact with the ODROID-C2

  • USB disk with at least 4G capacity

  • microSD card or eMMC module with at least 4G capacity

  • (Optional) USB-UART Module Kit for connecting with the ODROID-C2’s serial console. Refer to this post for instructions on how to connect along with explanation why the serial console is highly recommended in this case.

3. Flash the OS image and boot ODROID-C2

Flash the OS image to the USB disk by following the instructions from https://archlinuxarm.org/platforms/armv8/amlogic/odroid-c2

Replace /dev/mmcblk0 in the following instructions with the device name for the microSD card as it appears on your computer.

If mounted, unmount the partitions of the microSD card

lsblk
umount /dev/mmcblk0p1
umount /dev/mmcblk0p2

Zero the beginning of the microSD card

sudo dd if=/dev/zero bs=1M count=8 of=/dev/mmcblk0
sync

Using a tool like GParted, create an MBR/msdos partition table and two partitions on the microSD card

  • ext4 partition with 128M size

  • lvm2 partition occupying the rest of the space (no need to format yet)

Copy the contents of the /boot directory from the USB disk into the first partition of the microSD card

sudo cp -R /media/user/usb-disk/boot/* /media/user/micro-sd-card-part1/

Create a symbolic link as a workaround for the hardcoded boot.ini path of the alarm/uboot-odroid-c2

cd /media/user/micro-sd-card-part1
sudo ln -s . boot

Flash the bootloader files

sudo ./sd_fusing.sh /dev/mmcblk0

Determine the UUID of the USB disk

$ sudo lsblk -o name,uuid,mountpoint
NAME                    UUID                                   MOUNTPOINT
sdb
└─sdb1                  2b53696c-2e8e-4e61-a164-1a7463fd3785   /media/user/usb-disk
Caution
If there are duplicate UUIDs among the partitions of the USB disk and the microSD card then deduplicate them (e.g. sudo tune2fs /dev/sda2 -U $(uuidgen)) to avoid future conflicts.

Configure the boot.ini to boot from the USB disk. To do so, use the UUID from the previous step to configure the boot.ini of the microSD card.

sudo nano /media/user/micro-sd-card-part1/boot.ini
setenv bootargs "root=UUID=2b53696c-2e8e-4e61-a164-1a7463fd3785 rootwait <leave the rest as is>"

Unmount, run sync few times, and remove the microSD card and the USB disk from the Linux box.

Plug the microSD card and the USB disk to the ODROID-C2.

Boot the ODROID-C2 and connect to its serial console. If you need instructions on how to connect to the serial console, refer here.

If all goes well you should boot into the USB disk.

Caution
If root=UUID=2b53696c-2e8e-4e61-a164-1a7463fd3785 doesn’t work then try root=/dev/sda1, root=/dev/sdb1 or whatever device name you see in the console prior to the failed boot (e.g. [ 14.812393] sd 1:0:0:0: [sda] Attached SCSI removable disk). If still having issues try restarting some times and/or repositioning the USB disk into a different USB port on the ODROID-C2. Don’t worry if it seems to be giving you trouble, as you won’t have to boot to the USB disk again after the first successful boot.

The default credentials are alarm/alarm and root/root

Verify the root filesystem is mounted from the USB disk

df -h

4. Change passwords

Change the passwords for the alarm and the root user. The default credentials are alarm/alarm and root/root

passwd
su
passwd

5. Install required packages

su
pacman -Syu
pacman -S --needed sudo python git rsync lvm2 cryptsetup

(Optional) Setup passwordless sudo for the user alarm

echo 'alarm ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/010_alarm-nopasswd

6. Install dracut

Install trizen (AUR helper)

sudo pacman -S --needed base-devel
mkdir ~/aur && cd $_
git clone https://aur.archlinux.org/trizen.git
cd trizen
makepkg -si --needed --noconfirm

Install dracut using the trizen tool

trizen -S dracut

Verify the dracut installation by listing modules

dracut --list-modules

6.1. ERROR: aarch64 architecture is not supported by the package

If trizen -S dracut reports an error that aarch64 architecture is not supported by the package, then follow these steps to configure support for aarch64

cd /tmp/trizen-alarm/dracut/
nano PKGBUILD # replace `arch=("i686" "x86_64")` with `arch=("aarch64")`
makepkg -si --needed --noconfirm

6.2. ERROR: makepkg FAILED (unknown public key)

If the makepkg reports an error like dracut-046.tar …​ FAILED (unknown public key 340F12141EA0994D), then type these commands and try again

gpg --full-gen-key
gpg --recv-key 340F12141EA0994D

Refer to Makepkg signature checking for more details.

6.3. ERROR: GnuPG Key generation failed: No pinentry

If gpg --full-gen-key reports the error Key generation failed: No pinentry, then follow the below steps to configure gpg as described in https://wiki.archlinux.org/index.php/GnuPG#gpg-agent, and try again.

The gpg-agent needs to know how to ask the user for the password.

nano ~/.gnupg/gpg-agent.conf
pinentry-program /usr/bin/pinentry-curses

Reload the gpg-agent

gpg-connect-agent reloadagent /bye

6.4. ERROR: 'pacman' failed to install missing dependencies

If makepkg reports missing dependencies error, then upgrade the packages and try again.

sudo pacman -Syu
trizen -Syua

6.5. ERROR: dracut: command not found

If after running trizen -S dracut the dracut command is not installed, then try installing it manually without using the trizen AUR helper.

cd /tmp/trizen-alarm/dracut/
makepkg -si --needed --noconfirm

7. Prepare the LUKS rootfs

Encrypt the second partition of the microSD card (see also Recommended options for LUKS)

sudo cryptsetup -v -y -c aes-xts-plain64 -s 512 -h sha512 -i 5000 --use-random luksFormat /dev/mmcblk0p2
-v = verbose
-y = verify passphrase, ask twice, and complain if they don't match
-c = specify the cipher used
-s = specify the key size used
-h = specify the hash used
-i = number of milliseconds to spend passphrase processing (if using anything more than sha1, must be great than 1000)
--use-random = which random number generator to use
luksFormat = to initialize the partition and set a passphrase
/dev/mmcblk0p2 = the partition to encrypt

Unlock the LUKS device and mount it at /dev/mapper/lvm

sudo cryptsetup luksOpen /dev/mmcblk0p2 lvm

Create primary volume, volume group, and logical volume

sudo pvcreate /dev/mapper/lvm
sudo vgcreate vg /dev/mapper/lvm
sudo lvcreate -l 100%FREE -n root vg

Create filesystem

sudo mkfs.ext4 -O ^metadata_csum,^64bit /dev/mapper/vg-root

Mount the new encrypted root volume (logical volume)

sudo mount /dev/mapper/vg-root /mnt

Copy the existing root volume to the new, encrypted root volume (with a 1.5G installation, completes in about 6 min on an average microSD)

sudo rsync -av \
--exclude=/boot \
--exclude=/mnt \
--exclude=/proc \
--exclude=/dev \
--exclude=/sys \
--exclude=/tmp \
--exclude=/run \
--exclude=/media \
--exclude=/var/log \
--exclude=/var/cache/pacman/pkg \
--exclude=/usr/src/linux-headers* \
--exclude=/home/*/.gvfs \
--exclude=/home/*/.local/share/Trash \
/ /mnt

If the SSH host keys are empty, remove them so that they will be regenerated the next time the sshd starts. This will prevent the memory leak issue as described here.

sudo rm /mnt/etc/ssh/ssh_host*key*

Create some directories and mount the boot partition

sudo mkdir -p /mnt/boot /mnt/mnt /mnt/proc /mnt/dev /mnt/sys /mnt/tmp
sudo mount -t ext4 /dev/mmcblk0p1 /mnt/boot

Register the encrypted volume in crypttab

sudo bash -c 'echo lvm UUID=$(cryptsetup luksUUID /dev/mmcblk0p2) none luks>> /mnt/etc/crypttab'

Configure fstab

sudo nano /mnt/etc/fstab
/dev/mapper/vg-root / ext4 errors=remount-ro,noatime,discard 0 1
/dev/mmcblk0p1 /boot ext4 noatime,discard 0 2

8. Generate new initramfs and reboot into the LUKS rootfs

Generate new initramfs using dracut. The below will add the dracut modules crypt and lvm to the initramfs. These modules will prompt for LUKS password during boot and unlock the LUKS volume. Mind that the order of the modules is important.

sudo dracut --force --hostonly -a "crypt lvm" /mnt/boot/initramfs-linux.img

Determine the LUKS UUID

sudo cryptsetup luksUUID /dev/mmcblk0p2
470cc9eb-f36b-40a2-98d8-7fce3285bb89

Configure the rd.luks.uuid and root dracut options in bootargs. These will unlock the LUKS volume and load the rootfs from it during boot.

sudo nano /mnt/boot/boot.ini
setenv bootargs "rd.luks.uuid=470cc9eb-f36b-40a2-98d8-7fce3285bb89 root=/dev/mapper/vg-root rootwait <leave the rest as is>"
Caution
In the above step, do NOT delete the rest of bootargs, essentially replace root=UUID=2b53696c-2e8e-4e61-a164-1a7463fd3785 with rd.luks.uuid=470cc9eb-f36b-40a2-98d8-7fce3285bb89 root=/dev/mapper/vg-root and leave the rest of bootargs untouched.

Unmount and reboot into the LUKS rootfs

sudo umount /mnt/boot
sudo umount /mnt
sudo reboot

If all goes well you will be prompted to enter the LUKS password during boot.

Verify the LUKS rootfs

df -h
output
Filesystem           Size  Used Avail Use% Mounted on
devtmpfs             714M     0  714M   0% /dev
tmpfs                859M     0  859M   0% /dev/shm
tmpfs                859M  8.3M  851M   1% /run
tmpfs                859M     0  859M   0% /sys/fs/cgroup
/dev/mapper/vg-root  1.7G  1.4G  256M  85% /
tmpfs                859M     0  859M   0% /tmp
/dev/mmcblk0p1       120M   26M   86M  23% /boot
tmpfs                172M     0  172M   0% /run/user/1000

9. Remotely unlock the LUKS rootfs during boot using Dropbear sshd

Replace 10.0.0.100 in the following instructions with the IP address assigned to the ODROID-C2 by your local DHCP server. Use the fing tool to find the assigned IP address (e.g. sudo fing 10.0.0.1/24).

9.1. Make sure the SSH daemon is running

sudo systemctl status sshd
journalctl -u sshd -n 100

If the above commands report that sshd fails with memory allocation error, then

sudo rm /etc/ssh/ssh_host*key*
sudo systemctl start sshd

9.2. Install and configure Dropbear

Install the dracut module crypt-ssh

trizen -S dracut-crypt-ssh-git

From your Linux box, copy the public SSH key to the appconf/dracut-crypt-ssh/authorized_keys file on the remote ODROID-C2 server

cat ~/.ssh/*.pub | ssh alarm@10.0.0.100 'umask 077; mkdir -p appconf/dracut-crypt-ssh; touch appconf/dracut-crypt-ssh/authorized_keys; cat >>appconf/dracut-crypt-ssh/authorized_keys'

Configure the crypt-ssh module

sudo nano /etc/dracut.conf.d/crypt-ssh.conf
dropbear_acl="/home/alarm/appconf/dracut-crypt-ssh/authorized_keys"

Generate new initramfs using dracut. The below will add the dracut modules network and crypt-ssh to the initramfs. Mind that the order of the modules is important.

sudo dracut --force --hostonly -a "network crypt lvm crypt-ssh" /boot/initramfs-linux.img

Enable network access during boot by adding rd.neednet and ip dracut options to bootargs

sudo nano /boot/boot.ini
setenv bootargs "rd.neednet=1 ip=10.0.0.100::10.0.0.1:255.255.255.0:archlinux-luks-host:eth0:off rd.luks.uuid=ae51db2d-0890-4b1b-abc5-8c10f01da353 root=/dev/mapper/vg-root rootwait <leave the rest as is>"

If you prefer DHCP instead of static ip, simply replace with ip=dhcp.

Refer to network documentation of dracut and dracut options for more options (man dracut.cmdline).

Reboot so that Dropbear starts, allowing for remote unlocking

sudo reboot

9.3. Unlocking the volume interactively

From your Linux box, connect to the remote Dropbear SSH server running on the ODROID-C2

ssh -p 222 root@10.0.0.100

Unlock the volume (asks you for the passphrase and sends it to console)

console_auth
Passphrase:

If unlocking the device succeeded, the initramfs will clean up itself and Dropbear terminates itself and your connection.

Additional commands
  • console_peek Prints what’s on the console

Note
There is also the unlock command but we encountered this issue when tested it at the time of this writing with all the latest updates installed.

9.4. Unlocking the volume using a password file

Some use cases require to feed input automatically to the interactive command console_auth.

From your Linux box, unlock the volume

ssh -p 222 root@10.0.0.100 console_auth < password-file

or

gpg2 --decrypt password-file.gpg | ssh -p 222 root@10.0.0.100 console_auth

9.5. OPTIONAL: Restrict access to the console_auth command

For additional security, you might want to only allow the execution of the command console_auth and nothing else. To achieve this you need to configure the SSH key with restricting options in the authorized_keys file.

From your Linux box, copy the public SSH key, with restricting options, to the appconf/dracut-crypt-ssh/authorized_keys file on the remote ODROID-C2 server

(printf 'command="console_auth",no-agent-forwarding,no-port-forwarding,no-pty,no-X11-forwarding ' && cat ~/.ssh/*.pub) | ssh alarm@10.0.0.100 'umask 077; mkdir -p appconf/dracut-crypt-ssh; touch appconf/dracut-crypt-ssh/authorized_keys; cat >appconf/dracut-crypt-ssh/authorized_keys'

Refer to the Dropbear documentation for a full list of restricting options. Prior to continuing, it might be a good idea to create a copy of the initramfs

sudo cp /boot/initramfs-linux.img /boot/initramfs-linux.img-`date +%y%m%d-%H%M%S`
Caution
In headless setup, carefuly examine the restricting options to avoid locking yourself out.

Generate new initramfs using dracut

sudo dracut --force --hostonly -a "network crypt lvm crypt-ssh" /boot/initramfs-linux.img

In this case, you can unlock the volume interactively by simply typing

ssh -p 222 root@10.0.0.100

Note that when typing the above command

  • The console_auth command is automatically invoked on the remote server and immediately prompts for password, as if you just typed ssh -p 222 root@10.0.0.100 console_auth.

  • While you type the password, it will be displayed on the screen in plain text. Therefore, you should avoid unlocking interactively when the access is restricted to the console_auth command.

  • When you press enter you will be disconnected no matter whether the password was correct or not. Whereas with the non-restricted login (see Unlocking the volume interactively) you would only be disconnected if the password was correct, meaning that you would have feedback for whether the unlocking was successful or not.

On the other hand, to unlock the volume using a password file, from your Linux box, type

ssh -p 222 root@10.0.0.100 < password-file

or

gpg2 --decrypt password-file.gpg | ssh -p 222 root@10.0.0.100