Linux: Secure Boot

From fit-PC wiki
Jump to: navigation, search

Secure Boot and Linux

The idea is to create a signed GRUB EFI binary with required modules built-in. Secure Boot verifies this binary during boot. GRUB then reads the signed grub.cfg which contains the list of available kernels and then loads the signed kernel and initrd. GRUB's verification is based on GPG which is independent of Secure Boot.

The guide was tested on a Debian system with the specs listed below, but should be easily adaptable.

  • Device: fitlet2
  • OS: Debian GNU/Linux testing (buster)
  • Kernel: 4.13.0-1-amd64
  • BIOS: 05/14/2018 American Megatrends Inc. FLT2.
  • GRUB: 2.02-2
  • efitools: 1.8.1

Installation of required tools

# super-user mode required
su -
apt update
apt install -y openssl gnupg gpg sbsigntool uuid-runtime tree
# Package efitools must to be compiled and installed manually because Debian repository contains old 1.4.2 version:
apt build-dep -y efitools
tar -xvzf efitools-1.8.1.tar.gz
cd efitools-1.8.1
make install
cd ..

Secure Boot setup

  • Generate your own keys for Secure Boot: PK, KEK, db
CN="Your Name"
O="Your Organisation Name"
openssl req -new -x509 -newkey rsa:2048 -subj "/CN=$CN PK,  O=$O/"  -keyout PK.key  -out PK.crt  -days 7300 -nodes -sha256
openssl req -new -x509 -newkey rsa:2048 -subj "/CN=$CN KEK, O=$O/"  -keyout KEK.key -out KEK.crt -days 7300 -nodes -sha256
openssl req -new -x509 -newkey rsa:2048 -subj "/CN=$CN db,  O=$O/"  -keyout db.key  -out db.crt  -days 7300 -nodes -sha256

  • Convert open part of the keys to the ESL format understood for UEFI
UUID=$(uuidgen --random)
cert-to-efi-sig-list -g $UUID PK.crt PK.esl
cert-to-efi-sig-list -g $UUID KEK.crt KEK.esl
cert-to-efi-sig-list -g $UUID db.crt db.esl

  • Sign ESL files
sign-efi-sig-list -k PK.key -c PK.crt PK PK.esl PK.auth
sign-efi-sig-list -k PK.key -c PK.crt KEK KEK.esl KEK.auth
sign-efi-sig-list -k KEK.key -c KEK.crt db db.esl db.auth

  • At this stage your are ready to sign GRUB EFI binary and add it to the list of binaries allowed by Secure Boot

GRUB EFI setup

GRUB EFI supports loading of GPG signed files only e.g. config or kernels through the verify module. The grub-mkstandalone command can be used to create a single GRUB binary with built-in modules and initial configuration script. The GRUB password is required to restrict access to the GRUB shell which allows running arbitrary commands. Your grub.cfg, kernel and initrd and their signatures would be placed on the EFI partition.

  • Create initial GRUB configuration script grub.init.cfg
GRUB_PASSWORD="Your GRUB password"
GRUB_PASSWORD_HASH=$(echo -e "$GRUB_PASSWORD\n$GRUB_PASSWORD" | grub-mkpasswd-pbkdf2 | grep -o "grub.*")
EFI_UUID=$(lsblk -f | grep -i efi | grep -E -o "[A-Z0-9]{4}-[A-Z0-9]{4}")

cat > grub.init.cfg << EOF
set check_signatures=enforce
export check_signatures
set superusers=root
export superusers
password_pbkdf2 root $GRUB_PASSWORD_HASH
search --no-floppy --fs-uuid --set=root $EFI_UUID
configfile /grub.cfg
echo /grub.cfg did not boot the system, rebooting the system in 10 seconds..
sleep 10

### as result grub.init.cfg will be created
  • Generate your GPG key
GPG_PASSWORD="Your GPG password"
cat > gpg.batch.file << EOF
%echo Generating a basic OpenPGP key
Key-Type: DSA
Key-Length: 1024
Subkey-Type: ELG-E
Subkey-Length: 1024
Name-Real: Your Name
Expire-Date: 0
Passphrase: $GPG_PASSWORD
# Do a commit here, so that we can later print "done" :-)
%echo done

gpg --batch --gen-key gpg.batch.file 2>&1 | tee gpg.log
GPG_KEY=$(cat gpg.log | grep -o "gpg: key [0-9A-Z]* marked as ultimately trusted" | awk '{ print $3 }')
gpg --export $GPG_KEY > gpg.key

  • Sing grub.init.cfg with your GPG key
# you will be required to enter value of $GPG_PASSWORD manually
gpg --yes --default-key $GPG_KEY --detach-sign grub.init.cfg

### as result grub.init.cfg.sig will be created
  • Creating single GRUB EFI binary with buit-in modules and signed grub.init.cfg
MODULES="all_video archelp boot bufio configfile crypto echo efi_gop efi_uga ext2 extcmd  \
fat font fshelp gcry_dsa gcry_rsa gcry_sha1 gcry_sha512 gettext gfxterm linux linuxefi ls \
memdisk minicmd mmap mpi normal part_gpt part_msdos password_pbkdf2 pbkdf2 reboot relocator \
search search_fs_file search_fs_uuid search_label sleep tar terminal verify video_fb"
grub-mkstandalone -d /usr/lib/grub/x86_64-efi -O x86_64-efi --modules "$MODULES" --pubkey gpg.key --output grubx64.efi  boot/grub/grub.cfg=grub.init.cfg boot/grub/grub.cfg.sig=grub.init.cfg.sig -v

### as result grubx64.efi will be created
  • Sign grubx64.efi with your db key
sbsign --key db.key --cert db.crt grubx64.efi

### as result grubx64.efi.signed will be created, it will be your bootloader

Prepare signed grub.cfg kernel and initrd on the EFI partition

  • Mount your EFI partition to /boot/efi if not mounted yet
[ "$(mount | grep /boot/efi)" ] || mount /dev/disk/by-uuid/$EFI_UUID /boot/efi

  • Create grub.cfg on the EFI partition
KERNEL_VERSION=$(uname -r)
KERNEL_CMDLINE=$(cat /proc/cmdline | awk '{ $1=""; print $0 }')
cat > /boot/efi/grub.cfg << EOF
set timeout_style=menu
set timeout=2
set gfxmode=auto
set gfxpayload=keep
terminal_output gfxterm
menuentry 'Debian GNU/Linux, with Linux $KERNEL_VERSION' --unrestricted {
   echo   'Loading Linux $KERNEL_VERSION ...'
   echo   'Loading initial ramdisk ...'
   initrd /initrd.img-$KERNEL_VERSION

### as result grub.cfg will be created, please review it
  • Copy your existent kernel and initrd to the EFI partition
cp /boot/vmlinuz-$KERNEL_VERSION /boot/efi/
cp /boot/initrd.img-$KERNEL_VERSION /boot/efi/

  • Sign grub.cfg, kernel and initrd with your GPG key
gpg --default-key "$GPG_KEY" --detach-sign /boot/efi/grub.cfg
gpg --default-key "$GPG_KEY" --detach-sign /boot/efi/vmlinuz-$KERNEL_VERSION
gpg --default-key "$GPG_KEY" --detach-sign /boot/efi/initrd.img-$KERNEL_VERSION

### as result signatures of these files will be created

  • Replace your existent bootloader with signed one
# find your existent bootloader and make a copy of it
BOOTLOADER=$(find /boot/efi/EFI/ -name "*x64.efi")
# place grubx64.efi.signed you've created before as default bootloader
cp grubx64.efi.signed $BOOTLOADER


  • At this stage you should see the following in your /boot/efi
tree /boot/efi/

# You should see the following:
├── EFI
│   └── debian
│       ├── grubx64.efi
│       └── grubx64.efi.orig
├── grub.cfg
├── grub.cfg.sig
├── initrd.img-4.13.0-1-amd64
├── initrd.img-4.13.0-1-amd64.sig
├── vmlinuz-4.13.0-1-amd64
└── vmlinuz-4.13.0-1-amd64.sig
 2 directories, 8 files
  • Reboot once to see if chain from bootloader to the OS works correctly
  • Login as super-user again
su -

Enable Secure Boot

  • Install keys into EFI (PK last as it will enable Custom Mode locking out further unsigned changes):
efi-updatevar -f db.auth db
efi-updatevar -f KEK.auth KEK
efi-updatevar -f PK.auth PK
  • The EFI variables may be immutable (i-flag in lsattr output) in recent kernels (e.g. 4.5.4). Use chattr -i to make them mutable again if you can’t update the variables with the commands above:
chattr -i /sys/firmware/efi/efivars/{PK,KEK,db,dbx}-*
  • Review installed certificates

# You should see the following:
Variable PK, length 874
PK: List 0, type X509
    Signature 0, size 846, owner 101f003f-a976-4802-bc12-bbb2238fe111
            CN=Your Name PK,  O=Your Organisation Name
            CN=Your Name PK,  O=Your Organisation Name
Variable KEK, length 874
KEK: List 0, type X509
    Signature 0, size 846, owner 101f003f-a976-4802-bc12-bbb2238fe111
            CN=Your Name KEK, O=Your Organisation Name
            CN=Your Name KEK, O=Your Organisation Name
Variable db, length 874
db: List 0, type X509
    Signature 0, size 846, owner 101f003f-a976-4802-bc12-bbb2238fe111
            CN=Your Name db,  O=Your Organisation Name
            CN=Your Name db,  O=Your Organisation Name
Variable dbx has no entries
Variable MokList has no entries

  • From now on only EFI binaries signed with any db key can be loaded

See also