Monday, August 15, 2016

Full Disk Encryption (FDE) Including Most Of /boot on Arch Linux

Hey everyone - I have been playing around with full disk encryption on Linux a bit lately, and I see a lot of literature that says /boot cannot be encrypted because the boot loader will not be able to decrypt it.  This is only partially true.  Newer versions of grub support decryption of LUKS containers at boot time.  Today, we will take a look at doing full disk encryption including (most of) /boot with Arch Linux.  Some of the commands we will run (namely mkinitcpio) are Arch specific, but the rest of this post may be useful to you if you are running another distribution.


I recently purchased a laptop because I plan on traveling a bit more in the future, and it would be nice to have something with a larger screen and a bit more power than my tablet.  Since laptops can get lost or stolen, I thought it would be a good idea to employ full disk encryption on my laptop's SSD so that my data is protected if something happens to the laptop.  In Windows, you can achieve full disk encryption using BitLocker if you are using a Pro or Enterprise version of Windows 7+.  You can also use open source utilities like VeraCrypt or LibreCrypt.  In OS X, you can use FileVault 2 which was introduced with OS X 10.7 (Lion).  I am not going to be running Windows or OS X on this laptop, so I needed a solution that would work with Linux.


Full disk encryption sounds awesome right?  Before we dive in, we need to talk about what FDE is and is not.  FDE is only good against attacks where the adversary has physical access to the disk.  If the disk is mounted and being used, then it is decrypted.  Therefore, FDE is not the be all, end all of security for your machine.  It is part of a layered, defense-in-depth approach to system security.  FDE is great for devices that do not always have great physical security around them.  Devices like laptops, phones, and tablets can be stolen or lost.  If your disk is unencrypted, someone only needs access to that disk to steal the data.  FDE adds one or two layers of authentication by asking for a passphrase, requiring a physical device to be attached to the system (like a TPM module or USB flash drive with a special file), or both.  If someone gets a hold of a disk with a strong passphrase, it will be relatively difficult for them to recover data from that disk.  You could also use FDE on your desktop if you are worried that it will be lost or stolen.  Remember that once an adversary has physical access to your computer, they can do whatever they want.  Therefore, the measures we will talk about help mitigate data compromise.  They do not completely prevent it.


When it comes to disk encryption in Linux, the names you will hear the most are dm-crypt and LUKS.  LUKS stands for Linux Unified Key Setup.  LUKS is nice because it is a platform independent on-disk format specification and works in every modern Linux distribution and even Windows with LibreCrypt.  In theory, if you had a file system driver in Windows for your file system, you could use a tool like LibreCrypt to read the contents of the disk.  dm-crypt is the backend for transparent disk encryption in the Linux kernel.  It was introduced with kernel 2.6.  Later, we are going to use cryptsetup which is the tool for utilizing dm-crypt.  So, we will use cryptsetup to set up a LUKS formatted disk.  All of this will use dm-crypt in the backend.

Out of the Box

Fedora and other distributions will offer disk encryption to you when you install them.  On one of my Fedora systems, most of the drive is indeed encrypted (including swap, which is great):
$> lsblk
NAME                                          MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda                                             8:0    0 167.7G  0 disk  
├─sda1                                          8:1    0   500M  0 part  /boot
└─sda2                                          8:2    0 167.2G  0 part  
  └─luks-51a39f9a-f82d-4732-97ee-740583a36df1 253:0    0 167.2G  0 crypt 
    ├─fedora-root                             253:1    0    50G  0 lvm   /
    ├─fedora-swap                             253:2    0   3.9G  0 lvm   [SWAP]
    └─fedora-home                             253:3    0 113.3G  0 lvm   /home

However, you can see that sda1 (/boot) is outside of the LUKS container.  So, why should you care?  Your sensitive data is in /, /home, or perhaps in your swap file.  In theory, that is all that should need to be encrypted.  How true this is depends on your risk tolerance.

Let's take a second to talk about what is in /boot.  On modern Linux systems, /boot contains all of the files that the system needs to start up (including the kernel).  Once you select a kernel image to load in your system's boot loader (be it systemd-boot, LILO, grub, whatever), your boot loader will load that image and pass off execution to the kernel (the kernel image is called vmlinuz in modern Linux distros).  The kernel will load the initial ramdisk (used to be called initrd, now known as initramfs) which contains tools and drivers that the kernel needs to load the full system.  On a system using disk encryption for the system drive (/), cryptsetup is one of these tools.  Cryptsetup handles mounting and umounting encrypted partitions, so for the system to interact with a disk encrypted with dm-crypt, it needs cryptsetup.  So, what if some nefarious person were to modify the cryptsetup in your initramfs to spit out the pass phrase?  Or, what if they were to modify the files in the initramfs to load a rootkit or something similar?  This article talks about those possibilities, and while it is from 2011, the concepts have not changed.

These ideas sound a bit scary because no one checks the integrity of their initramfs file before they boot their machine, so you probably would have no idea that this has happened.  If your laptop is lost or stolen, it will likely be sold for money.  However, if you want to protect yourself as much as you can, we will talk about a way to encrypt most of /boot.  I say most of because on EFI systems, an EFI system partition (known as ESP) is required to boot.  ESP contains EFI bootloaders and applications that the EFI firmware on your computer needs to start.  I am not aware of any EFI firmware that supports an encrypted ESP, so that has to remain unencrypted.  Therefore, many of the problems with unencrypted initramfs files are transferred here.  In theory, someone could modify the Linux EFI boot loader with "bad" code.  You can mitigate this by enabling Secure Boot.  Secure Boot enforces cryptographic signatures for EFI boot loaders, so if your boot loader is modified, the signature check will fail, and you will know about it.

If your system does not use EFI, or you do not care about having an EFI boot loader, then this article (which forms the basis for most of the procedure outlined in this blog post) will work for you.  However, if you want to dual boot on an EFI system (like Windows 8+), you will need to account for EFI.

The Setup

We are going to set up our disk with LVM (logical volume manager) because LVM allows for dynamic resizing of a volume group which could be nice in the future.  You do not have to use LVM.  If you choose not to, the steps are actually easier since you do not have to do all of the LVM-related set up stuff.  We are going to set up LVM inside of our LUKS container (LVM on LUKS).  This does not allow for spanning multiple disks, so if you need to do that, this section in the Arch wiki will help you there.  Since my system only has one disk (and will likely ever only have one disk), I am going for the simpler solution.

For our example, we have a 20GB disk, and we will put two partitions on it.  The first (sda1) will be for the EFI System Partition (ESP) and the second will be for everything else (sda2).  If you already have Windows installed, you are probably not going to be using sda1 and sda2 (since those partitions are already set up by Windows).  In that case, you will need to substitute the partition numbers you will be using.  We will have a separate place for /home, but that will be inside of the volume group as will our swap.  You can use whatever tools you like to partition your disk.  I am going to use gdisk for this example.

Partitioning the Disk

On our 20GB disk, we are going to reserve 512MB (100MB is the minimum) for ESP, and the rest (roughly 19.5GB) for everything else including /, /home, and swap.  Using gdisk, we will create the first partition as EFI (code EF00) and the second partition as standard Linux (8300).  Here is what my partition table looks like:
[root@testbox ~]# gdisk -l /dev/sda
GPT fdisk (gdisk) version 1.0.1

Partition table scan:
  MBR: protective
  BSD: not present
  APM: not present
  GPT: present

Found valid GPT with protective MBR; using GPT.
Disk /dev/sda: 41943040 sectors, 20.0 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): 979137A6-28E0-44B3-84FD-7920716B9BFC
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 41943006
Partitions will be aligned on 2048-sector boundaries
Total free space is 2014 sectors (1007.0 KiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048         1050623   512.0 MiB   EF00  EFI System
   2         1050624        41943006   19.5 GiB    8300  Linux filesystem

Creating the LUKS Container

Now that our partitions are set up, we will set up a new LUKS container on /dev/sda2.  We are not including /dev/sda1 because it must remain unencrypted.  We need to use cryptsetup (the frontend for dm-crypt) to get everything set up:

# We are going to specify a key length of 512 bits (which is an effective key length
# of 256 bits since we are going to keep the default of AES in XTS mode)
[root@testbox ~]# cryptsetup -s 512 luksFormat /dev/sda2

# Now are are going to open the container so we can work with it
# lvm is the name that we will use to refer to the open container.  You can name it whatever you want.
[root@testbox ~]# cryptsetup luksOpen /dev/sda2 lvm

Creating LVM Structure

You can skip this part if you are not using LVM.  If you are interested in using LVM and do not know much about it, here is a quick crash course.  You should read more on the subject elsewhere for a more in depth explanation.

In a computer system, you can have one or more physical volumes (partitions or disks).  They might be labeled as /dev/sda1, /dev/sdb1, and so on.  With LVM, you can put one or more of those physical volumes into what is called a volume group.  That volume group can be split however you like into logical volumes.  A logical volume is like a filesystem (say / or /home).  LVM is nice because it adds an abstraction layer that allows you to add and remove physical disks and adjust accordingly while keeping everything together.

For our example, we will create a physical volume for /dev/sda2.  /dev/sda1 is not part of the volume group because the ESP needs to be separate.  To create a new physical volume, we use the pvcreate command:

[root@testbox ~]# pvcreate /dev/mapper/lvm

Now, we need to create our volume group.  I am going to call mine VolGroup0, but you can call yours whatever you like.  We need to use the vgcreate command for this.

[root@testbox ~]# vgcreate VolGroup0 /dev/mapper/lvm

Finally, we need to create each of the logical volumes.  We are going to have one for swap (4 GB), one for / (12 GB), and the rest will be for /home:

# Create a 4GB (-L 4G) logical volume on VolGroup0 named (-n) swap
[root@testbox ~]# lvcreate -L 4G VolGroup0 -n swap

# Create a 12GB (-L 12G) logical volume on VolGroup0 named (-n) root
[root@testbox ~]# lvcreate -L 12G VolGroup0 -n root

# Create a logical volume named (-n) home on VolGroup0
# from the rest of the space (-l +100%FREE)
[root@testbox ~]# lvcreate -l +100%FREE VolGroup0 -n home

Creating Filesystems

Now, we need to create our file systems.  swap will be swap, and the rest will be ext4.  You can use whatever you like.

# Substitute VolGroup0 for the name of your volume group
[root@testbox ~]# mkswap /dev/mapper/VolGroup0-swap
[root@testbox ~]# swapon /dev/mapper/VolGroup0-swap

[root@testbox ~]# mkfs.ext4 /dev/mapper/VolGroup0-root
[root@testbox ~]# mkfs.ext4 /dev/mapper/VolGroup0-home

Now, you can continue the rest of the installation process until just before you install the boot loader.  We need to tweak things a bit.

Adding a Key File

In the article where I first read this, the author talks about creating a key file so that you only need to type your password once when booting.  When grub unlocks your drive after your type your password in, it passes control to the kernel that then has to unlock your drive again so that it has access to it.  The author's solution was to create a keyfile that could unlock the drive and pass that keyfile to the kernel when grub passes control to the kernel.  This works.  However, the key file is readable when the system is bootable to anyone that has root.  The author tries to mitigate this by chmod-ing the key file to 000 (no permissions for anyone, even the owner), but root can still read the file.  The key file is stored in the encrypted partition, so if someone steals your drive, they need to know the password to boot or mount the disk.  With that said, keep in mind that if your system is compromised while it is booted, all bets are off.

If you want to buy this risk, create the key file like this:

# Use dd to create a 2KB file of random bytes
[root@testbox ~]# dd bs=512 count=4 if=/dev/udrandom of=/etc/key_file

# Add that key file to the keys for the LUKS container
[root@testbox ~]# cryptsetup luksAddKey /dev/sda2 /etc/key_file

Adding Kernel Hooks for Encryption

The kernel needs the right hooks to be able to decrypt the LUKS container we made.  Before we install the boot loader, we should put in those hooks.  In the file /etc/mkinitcpio.conf, change the line that says HOOKS and add lvm2 and encrypt to the list.  If you are using a key file, put the path to the key file between the quotes on the FILES= line.  After this, rebuild the kernel:

[root@testbox ~]# mkinitcpio -p linux

Installing and Configuring GRUB

As of this writing, GRUB is the only boot loader that supports booting from an encrypted disk.  To install it:

# efibootmgr is needed for EFI support in GRUB
[root@testbox ~]# pacman -S grub efibootmgr

Now, we need to configure it.  Before we do that, we need to grab the UUID of the partition for the LUKS container.  We can use UUIDs so that it does not matter what the device is called (like /dev/sda2).  If the device becomes /dev/sdb2, the UUID is the same.  To find the UUID, use lsblk:

[root@testbox ~]# lsblk -f
NAME                 FSTYPE      LABEL UUID                                   MOUNTPOINT
├─sda1               vfat              6FFE-A2F4                              /boot/efi
└─sda2               crypto_LUKS       b9de7df0-4965-40b2-adf2-9db0b57622a4   
  └─lvm              LVM2_member       jSBliM-yHze-UUkV-i39Y-6ZuL-x0v5-JTRLzG 
    ├─VolGroup0-swap swap        swap  96bfb9ce-571e-411e-bf60-b25a2f042760   
    ├─VolGroup0-root ext4             b5064ff5-3113-45f2-85c8-f3737edf8b5a   /
    └─VolGroup0-home ext4             1ae97c3f-b427-417b-871e-4d903c57e30f   /home

We want the UUID from /dev/sda2 (the LUKS container).  In this case, it is b9de7df0-4965-40b2-adf2-9db0b57622a4.  So, in /etc/default/grub, we will add the following to the GRUB_CMDLINE_LINUX line:
# Substitute your UUID after UUID= and before :lvm

Finally, add GRUB_ENABLE_CRYPTODISK=y to the end of /etc/default/grub.  Save the file, and finish installation:

[root@testbox ~]# grub-mkconfig -o /boot/grub/grub.cfg
[root@testbox ~]# grub-install /dev/sda

And that is it!  Finish up the installation, and reboot.  You should get a prompt asking for your password, then boot should proceed normally.


For me, enabling FDE including /boot was an exercise to learn more about the process and how LUKS, GRUB, and the boot process work together.  I cannot say if the outcome is worth all of the effort.  It was an interesting exercise that mitigates a rare form of attack.  The procedure we outlined here needs to be modified for other distributions, because mkinitcpio is specific to Arch-based distributions.  The GRUB configuration steps would work for any distribution running a new enough version of GRUB (> 2.0).

Do you use FDE?  Is this procedure too paranoid or not paranoid enough?  Let me know!  Thanks for reading!

No comments:

Post a Comment