How I secured my data on my laptop


2021.03.19

By encrypting the root partition, detaching the LUKS header into a USB key, signing my own UEFI kernel, enabling secure boot, booting directly from firmware without any bootloader and locking the BIOS setup interface behind a password.

Introduction

Why ? You may ask. If like me, you like to work on your laptop from different places, then you may want to make sure that your sensitives data stay protected in case someone as a physical access to your computer.

I'm not an expert in computer security. So please, be aware that my solution is not perfect and many vulnerabilities could still be exploited. Also, there's many different way to achieve this but I'll focus on only one method to avoid confusion.

I have a 5 years old Dell XPS 15 and I'm using Arch Linux. It should works with another GNU/Linux distribution as the features used are mainly kernel related and the other packages are pretty common. However, the computer need to support EFI secure boot and being able to lock the BIOS setup interface behind a password.

Credits and sources

Root encryption

Don't forget to verify the signature of your installation image as to avoid any malware from the start, then proceed with the installation normally until you need to prepare your disk.

Create a new partition table

Using fdisk, look for the name of your disk.

fdisk -l

In my case, having a NVMe SSD, my disk is named nvme0n1 but otherwise, it's commonly named sda.

Name Type Size
/dev/sda1 EFI 512MB
/dev/sda2 Linux filesystem Remainder of the device

That's how the partition table will look like if you follow my example. Normally, I like to use LVM as it offers more flexibility but I removed it to simplify. Also, I don't have a swap partition.

Start fdisk.

fdisk /dev/sda

Inside the prompt:

  1. Type g to create a new empty GPT partition table.
  2. Type n to add a first partition.
  3. Type Enter a first time to accept the default partition number, then Enter a second time to accept the default first sector and finally +512M for the last sector.
  4. Type t then efi to change the type to 'EFI system'.
  5. Type n to add a second partition.
  6. Type Enter 3 times to accept all default values.
  7. Type w to save the changes and exit.

Setup LUKS

I chose to detach the header into a USB key as a simple way to get a physical authentication factor. If your passphrase somehow leaked, your partition cannot be decrypted without the salt contained in the header. Also, even if someone had the computational power to break the encryption, it would make the process a bit harder.

mount /dev/sdb1 /mnt # mount the usb
cryptsetup luksFormat /dev/sda2 --header /mnt/header.img
cryptsetup open /dev/sda2 root --header /mnt/header.img
umount /mnt

I strongly recommend making a backup of the header file somewhere safe. You cannot decrypt the root partition without it. So if your USB key fails, you'll lose all your data.

Format both partitions

mkfs.vfat -F32 /dev/sda1
mkfs.ext4 /dev/mapper/root

Overwrite previous data

This is a very long process depending of the size of your partition. If you don't do this, some old data could still be potentially retrieved.

dd if=/dev/zero of=/dev/mapper/root status=progress

No need to use /dev/random because the encryption already turn the zeroes into random data.

Proceed with the rest of the installation and chroot into your new installation. Don't install any bootloader, the kernel will boot directly from the firmware.

Decrypt partition at boot

In order to decrypt your root partition at boot with a detached LUKS header on a usb key, add a custom hook to initcpio.

I chose to use labels instead of GUIDs so I can use a backup USB key if the main one fails. So be aware that if there's another external drive connected to your laptop while booting, you may not be able to decrypt your root partition because of that.

/etc/initcpio/hooks/customencrypt

#!/usr/bin/ash

run_hook() {
    modprobe -a -q dm-crypt >/dev/null 2>&1

    while [ ! -b '/dev/sdb1' ]; do
     sleep 1
    done

    mkdir -p /mnt
    mount /dev/sdb1 /mnt
    cryptsetup --header /mnt/header.img open /dev/sda2 root
    umount /mnt
}

/etc/initcpio/install/customencrypt

#!/bin/bash

build() {
    local mod

    add_module "dm-crypt"
    add_module "dm-integrity"
    if [[ $CRYPTO_MODULES ]]; then
        for mod in $CRYPTO_MODULES; do
            add_module "$mod"
        done
    else
        add_all_modules "/crypto/"
    fi

    add_binary "cryptsetup"
    add_binary "dmsetup"
    add_file "/usr/lib/udev/rules.d/10-dm.rules"
    add_file "/usr/lib/udev/rules.d/13-dm-disk.rules"
    add_file "/usr/lib/udev/rules.d/95-dm-notify.rules"
    add_file "/usr/lib/initcpio/udev/11-dm-initramfs.rules" "/usr/lib/udev/rules.d/11-dm-initramfs.rules"

    # cryptsetup calls pthread_create(), which dlopen()s libgcc_s.so.1
    add_binary "/usr/lib/libgcc_s.so.1"

    add_runscript
}

And finally, modify your mkinitcpio configuration file to add the custom module.

/etc/mkinitcpio.conf

HOOKS=(base udev autodetect keyboard keymap modconf block filesystems fsck customencrypt)

Use this mkinitcpio command to create your new initial ramdisk image.

mkinitcpio -P

Secure boot

Why going through the hassle of enabling secure boot ? Well, your root partition is safe but your kernel is not. Imagine you let your laptop unsupervised a few minutes, someone could boot from a usb and replace your kernel with a custom one without you noticing. It's something you really don't want and I don't really need to explain why.

Install the sbsigntools package.

pacman -S sbsigntools

Create a new directory in a secure location like /root/secureboot.

mkdir /root/secureboot
cd /root/secureboot

Generate your own keys

Create a script named mkkeys.sh that will automate the process.

#!/bin/bash

echo -n "Enter a Common Name to embed in the keys: "
read name

#GUID
uuidgen --random > GUID.txt

#PLATFORM KEY
openssl req -newkey rsa:4096 -nodes -keyout PK.key -new -x509 -sha256 -days 3650 -subj "/CN=$name PK/" -out PK.crt
openssl x509 -outform DER -in PK.crt -out PK.cer
cert-to-efi-sig-list -g "$(< GUID.txt)" PK.crt PK.esl
sign-efi-sig-list -g "$(< GUID.txt)" -k PK.key -c PK.crt PK PK.esl PK.auth
sign-efi-sig-list -g "$(< GUID.txt)" -c PK.crt -k PK.key PK /dev/null rm_PK.auth

#KEY EXCHANGE KEY
openssl req -newkey rsa:4096 -nodes -keyout KEK.key -new -x509 -sha256 -days 3650 -subj "/CN=$name KEK/" -out KEK.crt
openssl x509 -outform DER -in KEK.crt -out KEK.cer
cert-to-efi-sig-list -g "$(< GUID.txt)" KEK.crt KEK.esl
sign-efi-sig-list -g "$(< GUID.txt)" -k PK.key -c PK.crt KEK KEK.esl KEK.auth

#SIGNATURE DATABASE KEY
openssl req -newkey rsa:4096 -nodes -keyout db.key -new -x509 -sha256 -days 3650 -subj "/CN=$name db/" -out db.crt
openssl x509 -outform DER -in db.crt -out db.cer
cert-to-efi-sig-list -g "$(< GUID.txt)" db.crt db.esl
sign-efi-sig-list -g "$(< GUID.txt)" -k KEK.key -c KEK.crt db db.esl db.auth

Then execute the script.

touch mkkeys.sh
chmod +x mkkeys.sh
./mkkeys.sh

Generate signed unified kernel

Create a file named cmdline.txt containing the kernel parameters.

BOOT_IMAGE=/vmlinuz-linux initrd=/intel-ucode.img initrd=/initramfs-linux.img root=/dev/mapper/root

Notice that I'm using the Intel microcode. If you don't have a Intel CPU or just don't want to use it, some modifications are required.

Create a script named mkkernel.sh.

#!/bin/sh
cd /root/secureboot

#COMBINE FILES
cat /boot/intel-ucode.img /boot/initramfs-linux.img > initramfs-linux.img
cp /boot/vmlinuz-linux .

objcopy \
    --add-section .osrel=/etc/os-release --change-section-vma .osrel=0x20000 \
    --add-section .cmdline="cmdline.txt" --change-section-vma .cmdline=0x30000 \
    --add-section .linux="vmlinuz-linux" --change-section-vma .linux=0x40000 \
    --add-section .initrd="initramfs-linux.img" --change-section-vma .initrd=0x3000000 \
    /usr/lib/systemd/boot/efi/linuxx64.efi.stub kernel.efi

#SIGN EFI
sbsign --key db.key --cert db.crt --output kernel.efi.signed kernel.efi

#COPY KERNEL
cp kernel.efi.signed /boot/kernel.efi

Make it executable.

touch mkkernel.sh
chmod +x mkkernel.sh

You can manually execute the script each time you want to update the kernel but I recommend to automate the process with a pacman hook.

Create a file at /etc/pacman.d/hooks/91-secureboot.hook.

[Trigger]
Type = Path
Operation = Install
Operation = Upgrade
Target = usr/lib/modules/*/vmlinuz
Target = usr/lib/initcpio/*

[Action]
Description = Making signed kernel UEFI
When = PostTransaction
Exec = /root/secureboot/mkkernel.sh
NeedsTargets

Enroll keys in firmware

I tried to use sbkeysync but despite being in setup mode, I was unable to enroll my keys so I had to enroll them manually, using my BIOS setup interface. Don't forget to enable secure boot mode in your BIOS setup interface and to lock it behind a password because if someone is able to simply turn it off, it would make all this pointless.

Bootloader

I did not install any bootloader because it's simply useless in this case. Instead go into your BIOS setup interface and make a new boot entry for kernel.efi.

Recommendations

  • I like to use a passphrase instead of password. It may be a bit longer to enter each time but it's easier to remember and save you the trouble of having to distinguish similar letters. Also a 8 word passphrase from a dictionary of 3600 words is stronger than a 8 long password from 60 different characters (3600^8 > 60^8).
  • Use a idle screen locker and always lock your laptop before leaving it.
  • Be aware of who might be looking at your screen/keyboard while you enter your passphrase.
  • Don't let the usb key containing the LUKS header on your laptop. Remove it after the boot process and keep it with you.