Skip to content

Truly Secure boot

TL;DR

We are going to sign everything pertaining to the boot process (including grub.cfg and initrd) with your own keys.

You can jump directly to Step-by-step instructions.

Boot process — digest

CPU firmware
  1. Loosely speaking, a “CPU inside CPU” starts first. More precisely, Firmware inside PCH (1) or BMC (2) is executed first. Intel Management Engine runs in the PCH and starts before the CPU.

    1. platform controller hub
    2. baseboard management controller
  2. It performs basic hardware configuration and allows the CPU to come out of reset.

  3. The first instruction that CPU executes is the instruction from 16-bytes below 4GB.

    What is there?
    • SPI flash memory is mapped to the top of the 32-bit address space.
    • Reads are forwarded by the chipset to the flash device.
    • The instruction at 0xFFFFFFF0 is a JMP instruction to the start of the UEFI platform initialization code (SEC phase)

    Other relevant low-level things and keywords:

    • The Boot Block: SEC and PEI phase code.
    • Variable store for UEFI Variables and default settings.
    • SEC Phase sets up the UEFI environment and passes control to PEI Core.
    • PEI Dispatcher dispatches PEI Modules (PEIMs) that perform early hardware and memory initialization.
    • PEI Core passes control to DXE Core.
    • DXE drivers perform additional hardware initialization and configuration.
UEFI firmware

UEFI firmware allows users to enroll (add) their own SSL certificates (this should be protected with a password). So, given certificates it verifies everything up to (and including) first-stage bootloader (shim).

  1. UEFI firmware mounts first partition (1). UEFI-bootable binaries are then stored as files in the /EFI/BOOT/ directory.

    1. The first partition is expected to have partition ID of 0xEF00 and be formatted as a FAT32 filesystem
  2. UEFI firmware verifies signatures of the EFI executables using SSL certificates and hashsums.

    • There are several types of certificates and hashsum stores:
      • Platform Key (PK) stores (usually) a single certificate. Does not sign executables directly, but signs other keys.
      • Key Exchange Key (KEK) can store multiple certificates.
      • Blacklist (dbx) and whitelist (db). They can store both certificates and hashsums.
    What is certificate and how UEFI verifies signatures?

    A certificate is a public key. Below is how signing and verification works. Remember that only you have private key, and everyone else (including UEFI firmware) - public key.

    • Signing: You calculate a hash of a module and encrypt it with your private key. Then you attach the result (signature) to a file (UEFI module in our case).
    • Verification: The idea is to get (presumably) the same hash in two independent ways and compare them. The 1st way: decrypt the attached signature with the public key (which UEFI has). The 2nd way: calculate hash ourselves (given the module file). If they are equal UEFI knows that the module was created and placed there by whoever poses and controls private key.

    A certificates has certain capabilities. For example, certificates generated by update-secureboot-policy can only sign kernel modules (KeyUsage OID (1.3.6.1.4.1.2312.16.1.2)). They are stored at local file-system.

  3. (Unless UEFI firmware is configured for multi-boot), UEFI executes a EFI binary with the default name, which is BOOTX64.EFI. In case of Linux, the binary is shim.

    • To add a new EFI binary to your firmware’s list of available binaries, you should use the efibootmgr utility.

At this point, there are two alternatives: (1) control can be passed to shim, which passes control to grub, which passes control to kernel or (2) control can be passed directly to kernel (EFI Stub & Systemd-boot):

Shim

In addition to the aforementioned PK/KEK/DB/DBX, shim uses its own storage of keys and certificates called MOK (1). Shim is supposed to verify everything else: second-stage bootloader (GRUB), kernel, kernel drivers. Shim can:

  1. machine owner keys
  • start MokManager. MokManager allows a user to enroll keys, remove trusted keys, enroll binary hashes and toggle Secure Boot validation at the shim level. Unfortunately, MokManager does not have any protection from an adversary — it allows anyone to enroll new certificates, and does not require any protection — it is too helpful. So, in this guide we will remove it completely.
  • or shim
    1. verifies GRUB and Linux kernel.
    2. and then passes execution to GRUB.
GRUB

This is the point where UEFI’s SecureBoot starts to fade away. While GRUB performs checks of some files (linux kernel, vmlinuz) using UEFI’s certificates, it does not check everything that it loads and uses with UEFI certificates. Luckily, GRUB has an option to check everything that it loads with GPG keys (a mechanism that is completely unrelated to UEFI certificates, but conceptually similar to what UEFI does). So, GRUB:

  1. Reads and loads its own config from /boot/grub/ (which means that the the configs has to be protected from tampering — we are going to sign GRUB config with GPG).
  2. Provides user with a bunch of options (including rescue shell) as to how to proceed (so, this is another thing that has to be protected — we will create a special GRUB superuser + password).
  3. Loads initrd (1) and kernel in memory and passes execution to the kernel (we will sign both initrd and kernel with GPG). Only kernel is verified with UEFI certificates in default configuration. So, Initrd should be signed with GPG.

    1. initrd is a tiny part of user-space of a real OS, the only purposes of which is to figure-out how to proceed booting (mounting disks, decrypting, loading from NFS, handling DHCP and so on…). The init script is the main entry point. More info here.
Kernel & Initrd

Kernel then:

  1. Executes init script from initrd.
  2. The init scripts does whatever it needs to mount real FS.
  3. And finally, replaces itself with real /sbin/init from real FS.

There is another option, however. Linux kernel is provided as a EFI binary, and therefore can be verified and loaded directly by UEFI firmware. This has a number of advantages:

  • This approach is much simpler
  • fewer moving parts
  • faster boots (1)

    1. in qemu VM 57s in case of shim/GRUB vs 11s in case of unified kernel image
  • decreased surface for helpful “features” (such as MokManager)

  • Fewer files to sign:
    • surface for mistake is less.
    • no need to create GPG keys.

The only thing that needs to be addressed in this scenario is initrd. Luckily, it is possible to create a “unified kernel image” that includes everything that is needed to start kernel (kernel itself, initrd, kernel start options). The image then can be signed as usual and loaded by UEFI firmware.

To give you a taste of how simpler this option is, below a complete example of making non-unified kernel boot: (in case of unified kernel image, we need one invocation of objcopy command before):

$ efibootmgr -c -L MyEFIStubLinux -l /vmlinuz.efi -u 'initrd=\initrd.img root=/dev/mapper/vgkubuntu-root ro'
  • Where the value of the -u option can be copied from /proc/cmdline.
  • And vmlinuz.efi and initrd.img should be copied from /boot to /boot/efi.
Other info

Note, that some TPMs (Titan M) can start even before (1), checking integrity of itself and UEFI, and only then starting CPU.

There are also several types of boot:

  • Thorough or Full Boot provides the maximum amount of protection by using Secure Boot throughout the boot process. Integrity, signature, and hash checks are performed.
  • Fast Boot or Minimal Boot minimizes boot time by skipping numerous checks, which may or may not include Secure Boot checks. Boot speed is prioritized over some security features and/or additional features and peripheral support at boot time. Malware like LoJax can slip by on some systems (Schlej 2018). When Fast Boot is a configurable toggle, disabling Fast Boot typically results in Thorough Boot.
  • Automatic Boot attempts to detect when changes have occurred to the early stages of UEFI boot. Automatic Boot invokes Fast/Minimal Boot when no changes are detected. Thorough/Full Boot is invoked once after each significant change is detected.

The plan

Below we are going to:

  • Generate our own SSL keys and certificates. The keys can be stored on filesystem or Yubikey.
  • Only if you chose to use GRUB Generate our own GPG keys. The keys can be stored on filesystem or Yubikey.
  • Remove existing signatures from UEFI binaries (in /boot/efi) and sign them and Linux kernel (vmlinuz-) with SSL.
  • Only if you chose to use GRUB Sign Linux kernel, initrd, grub configs with GPG.
  • Only if you chose to use GRUB Configure GRUB in such a way that it verifies everything it loads, and create a password-protected root user for GRUB.
  • Only if you chose to use EFI stub / unified kernel Create a unified kernel image.
  • Reboot and enroll SSL certificates in UEFI.

We also want to simplify and automate the process because we will need to run it repeatedly (potentially, on each system update).


Customisation points & Low-level Tools

We are going to use a helper script, but it is always useful to understand how to accomplish each step yourself. You might need it in case something goes wrong and you need to fix it from a live usb.

SSL Certificates in UEFI

Get the list

Get the list of existing and enrolled certificates in UEFI:

Using efi-readvar (Install via: sudo apt install efitools)

  • efi-readvar — will print everything.
  • Extract PK/KEK/db via: efi-readvar -v PK -o PK.old.esl and save the files somewhere.
  • Extract der certificates from ESL with: sig-list-to-certs sec_out/KEK.old.esl KEK

Using mokutil: mokutil --pk, mokutil --kek, mokutil --db, mokutil --list-enrolled.

View details
  • openssl x509 -inform pem -in PK.crt -text
  • openssl x509 -inform der -in /var/lib/shim-signed/mok/MOK.der -text
Generate a new SSL key

On filesystem:

  • Generate private key and PEM-encoded certificate:
    openssl req –new -x509 –newkey rsa:2048 –subj "/CN=Custom PK/" –keyout PK.key –out PK.crt –days 3650 –nodes –sha256
    
    It will generate a new 2048 bit key, PK.key is a private key, and PK.crt is the corresponding certificate (public key).
  • Convert certificate from PEM encoding to DER: openssl x509 -outform der -in PK.crt -out PK.cer . You will need to install it into the UEFI firmware.
  • Also, you might want to generate ESL (EFI Sig List) from your certificates.

On Yubikey:

  • You can generate certificate as above and then import it to Yubikey. More Info.
  • Or you can generate them directly on Yubikey. More Info.

Note, the above will generate only one key/certificates (PK). You need to repeat the process for KEK and db.

Enrolling the certificates

This is the most reliable method.

Reboot, go to SecureBoot settings in UEFI, remove existing ones and add new ones.

  • sudo update-secureboot-policy --new-key — generates a new key/certificate. It will be stored in /var/lib/shim-signed/mok/MOK.der, /var/lib/shim-signed/mok/MOK.priv (check it with sudo locate MOK).
  • sudo update-secureboot-policy --enroll-key adds the key in the list.

Reboot, disable Secure Boot, and then:

  • db: efi-updatevar -e -f dsk1.esl db
  • hashes: efi-updatevar -a -e -f hashes.esl db
  • KEK: efi-updatevar -e -f KEK.esl KEK
  • PK: efi-updatevar -e -f PK.esl PK

SSL Signatures: Listing & Signing & Verification

Get list of signatures from a file

List signatures of shimx64.efi:

  • pesign -S -i /usr/lib/shim/shimx64.efi.signed (install via sudo apt install pesign)
  • sbverify --list sec_out/shimx64.efi.dualsigned.byme

See who signed your files (if you are still not using fish shell, replace brackets: () with backticks: `` below):

$ echo (sudo find /boot -maxdepth 1 -type f -iname 'vmlinuz*generic' && sudo find /boot/efi/ -type f -iname '*.efi') | tr ' ' '\n' | xargs -I {} sudo bash -c "echo {}; pesign -S -i {} | grep \"common name\""

/boot/vmlinuz-5.11.0-14-generic
The signer's common name is Canonical Ltd. Secure Boot Signing (2017)
/boot/vmlinuz-5.11.0-16-generic
The signer's common name is Canonical Ltd. Secure Boot Signing (2017)
/boot/efi/EFI/ubuntu/grubx64.efi
The signer's common name is Canonical Ltd. Secure Boot Signing (2017)
/boot/efi/EFI/ubuntu/shimx64.efi
The signer's common name is Microsoft Windows UEFI Driver Publisher
/boot/efi/EFI/ubuntu/mmx64.efi
The signer's common name is Canonical Ltd. Secure Boot Signing (2017)
/boot/efi/EFI/BOOT/BOOTX64.EFI
The signer's common name is Microsoft Windows UEFI Driver Publisher # (1)!
/boot/efi/EFI/BOOT/fbx64.efi
The signer's common name is Canonical Ltd. Secure Boot Signing (2017)
/boot/efi/EFI/BOOT/mmx64.efi
The signer's common name is Canonical Ltd. Secure Boot Signing (2017)
  1. ❗ note that shim is signed by M$ ❗
$ echo `sudo find /boot -maxdepth 1 -type f -iname 'vmlinuz*generic' && sudo find /boot/efi/ -type f -iname '*.efi'` | tr ' ' '\n' | xargs -I {} sudo bash -c "echo {}; pesign -S -i {} | grep \"common name\""

/boot/vmlinuz-5.11.0-14-generic
The signer's common name is Canonical Ltd. Secure Boot Signing (2017)
/boot/vmlinuz-5.11.0-16-generic
The signer's common name is Canonical Ltd. Secure Boot Signing (2017)
/boot/efi/EFI/ubuntu/grubx64.efi
The signer's common name is Canonical Ltd. Secure Boot Signing (2017)
/boot/efi/EFI/ubuntu/shimx64.efi
The signer's common name is Microsoft Windows UEFI Driver Publisher
/boot/efi/EFI/ubuntu/mmx64.efi
The signer's common name is Canonical Ltd. Secure Boot Signing (2017)
/boot/efi/EFI/BOOT/BOOTX64.EFI
The signer's common name is Microsoft Windows UEFI Driver Publisher # (1)!
/boot/efi/EFI/BOOT/fbx64.efi
The signer's common name is Canonical Ltd. Secure Boot Signing (2017)
/boot/efi/EFI/BOOT/mmx64.efi
The signer's common name is Canonical Ltd. Secure Boot Signing (2017)
  1. ❗ note that shim is signed by M$ ❗
Remove signatures

It is useful if you want to sign an executable yourself and you have only signed version of it:

pesign --signature-number 0 --remove-signature -i shimx64.efi -o shimx64.efi
Sign
  • Signing EFI executables (bootloaders) with keys on FS using sbsign from sbsigntool:
    • sbsign --key sec_out/KEK.key --cert sec_out/KEK.crt sec_out/shimx64.efi.dualsigned
  • Signing with Yubikey. See here.
  • Signing kernel modules using kmodsign:
    • kmodsign sha512 DBK.key DBK.der antimalware.ko
Verify

You can verify signed file by:

  • inspecting list of signatures attached, see above
  • or by verifying against the certificate: sbverify --cert sec_out/KEK.crt sec_out/shimx64.efi.dualsigned.byme

Hashes

Whitelist of checksums

  • Get the list: mokutil --db

  • Add: mokutil --import-hash(?)

  • Delete: mokutil --delete-hash(?)

Blacklist of checksums

  • Get the list: mokutil --dbx

  • kmodsign - signs a module.

GRUB

Protecting GRUB configs on disk

In order to protect GRUB’s config from tampering we create a standalone version of GRUB:

grub-mkstandalone -d /usr/lib/grub/x86_64-efi/ -O x86_64-efi --modules="part_gpt part_msdos" --fonts="unicode" --locales="en@quot" --themes="" -o "/home/user/grub-standalone.efi" "boot/grub/grub.cfg=/boot/grub/grub.cfg".

The resulting standalone GRUB binary is directly bootable and contains a memdisk, which holds the configuration file and modules, as well as the configuration file. Note, it still requires shim, so you cannot remove everything from /boot/efi except from grub-standalone.efi.

This GRUB binary now can be signed and used to boot the system.

Protecting GRUB configs from within GRUB

There is GRUB shell as well as an option to modify settings from GRUB itself. To prevent a malicious user from modifying the kernel cmdline of your system (for example, to point to a different init binary), a GRUB password should be set.

  • grub-mkpasswd-pbkdf2 — generate hashed version of the password

  • We add this in its config:

    /etc/grub.d/40_custom
    set superusers="root"
    password_pbkdf2 root <generated password hash>
    
  • Remove the requirement for the superuser password to be entered on a normal boot-up:

    /etc/grub.d/10-linux
    1
    2
    3
    4
    5
    echo "menuentry '$(echo "$title" | grub_quote)'
        ${CLASS} \$menuentry_id_option
        'gnulinux-$version-$type-$boot_device_id'
        --unrestricted            ### <--- Add this option
        {" | sed "s/^/$submenu_indentation/"
    

Step-by-step instructions

First of all, you need to:

  • choose how your system will boot: (1) via shim -> grub -> (grub.cfg, initrd, kernel) or (2) unified kernel directly.
  • choose where you will store keys: filesystem or Yubikey.

We are going to use the helper script:

$ ./secboot.py
usage: secboot.py [-h] [--dry-run] [--log-level {DEBUG,INFO,ERROR,DISABLED}] [--run-tests]
                  [--no-run-tests]

This is a script for managing Secure boot. Many operations require root. Commands below with prefix
"ot/" (stands for "one-time") are supposed to be run once, during initial setup.

    ot/generate-gpg-keys
                        Generates new GPG keys. Example of usage: "./secboot.py --log-level INFO
                        ot/generate-gpg-keys --GNUPGHOME sec_out/gpg --id Popl --gpg/pass """
    ot/generate-uefi-keys
                        Generates new (your own!) UEFI (PK/KEK/db) keys and certificates on filesystem.
                        You might want to create a temporary memory-backed fs for the task: "sudo mount
                        -t tmpfs -o size=10G,uid=(id -u) tmpfs sec_out" and move them then to a
                        Yubikey, see ot/enroll-ssl-to-yubikey. Example of usage: "./secboot.py --log-
                        level INFO ot/generate-uefi-keys -o sec_out/ --id Popl"
    ot/enroll-ssl-to-yubikey
                        Enrolls PK/KEK/DB keys and certificates to Yubikey. Example of usage:
                        "./secboot.py --log-level INFO ot/enroll-ssl-to-yubikey --keys-dir sec_out/
                        --uefi/nfc-reader "HID Global OMNIKEY 5422 Smartcard Reader [OMNIKEY 5422CL""
    ot/enroll-certs-to-uefi
                        Enrolls PK/KEK/DB certificates to UEFI. Assumes that UEFI is in "Setup Mode"
                        (temporarily disable secure boot, and delete all preexisting certificates).
                        Example of usage: "./secboot.py --log-level INFO ot/enroll-certs-to-uefi
                        --uefi/engine fs --uefi/keys-dir sec_out/" or "./secboot.py --log-level INFO
                        ot/enroll-certs-to-uefi --uefi/engine yu --uefi/nfc-reader "HID Global OMNIKEY
                        5422 Smartcard Reader [OMNIKEY 5422CL""
    re-sign-efi-file    [The command is not supposed to be used on its own. You can skip it.] Removes
                        all existing signatures from a given EFI executable file and signs it with a
                        provided (your own!) certificate. Example of usage: "./secboot.py --log-level
                        INFO re-sign-efi-file --file-to-sign sec_out/shimx64.efi.signed --uefi/engine
                        fs --uefi/keys-dir sec_out/" or "./secboot.py --log-level INFO re-sign-efi-file
                        --file-to-sign sec_out/shimx64.efi.signed --uefi/engine yu --uefi/nfc-reader
                        "HID Global OMNIKEY 5422 Smartcard Reader [OMNIKEY 5422CL""
    re-sign-file-with-gpg
                        [The command is not supposed to be used on its own. You can skip it.] Produces
                        a detached GPG signature using GPG. The command can be useful if you wish to
                        make manual changes to grub.cfg and then re-sign it. Example of usage:
                        "./secboot.py --log-level INFO re-sign-file-with-gpg --file-to-sign
                        sec_out/shimx64.efi --gpg/engine fs --gpg/key-id ADDB2... --GNUPGHOME
                        sec_out/gpg" or "./secboot.py --log-level INFO re-sign-file-with-gpg --file-to-
                        sign sec_out/shimx64.efi --gpg/engine yu --gpg/pub-key ~/devel/gpg*"
    danger/grub/make-new-boot
                        Prepares new content of /boot/efi. For testing, you might want to specify "--
                        boot-dir ./testing-boot". Example of usage: "sudo ./secboot.py --log-level INFO
                        danger/grub/make-new-boot --boot-dir /boot --uefi/engine fs --uefi/keys-dir
                        sec_out/ --gpg/engine fs --gpg/key-id ADDB2... --GNUPGHOME sec_out/gpg
                        --gpg/pass "" --pbkdf-grub-pass-path ~/grub_pbkdf2_pass" or "sudo ./secboot.py
                        --log-level INFO danger/grub/make-new-boot --boot-dir /boot --uefi/engine yu
                        --uefi/nfc-reader "HID Global OMNIKEY 5422 Smartcard Reader [OMNIKEY 5422CL"
                        --gpg/engine yu --gpg/pub-key ~/devel/gpg* --pbkdf-grub-pass-path
                        ~/grub_pbkdf2_pass"
    danger/efistub/make-new-boot
                        Prepares new content of /boot/efi. For testing, you might want to specify "--
                        boot-dir ./testing-boot". Example of usage: "sudo ./secboot.py --log-level INFO
                        danger/efistub/make-new-boot --boot-dir /boot --disk /dev/nvme0n1 --partition 1
                        --uefi/engine fs --uefi/keys-dir sec_out/" or "sudo ./secboot.py --log-level
                        INFO danger/efistub/make-new-boot --boot-dir /boot --disk /dev/nvme0n1
                        --partition 1 --uefi/engine yu --uefi/nfc-reader "HID Global OMNIKEY 5422
                        Smartcard Reader [OMNIKEY 5422CL""
    qemu/efistub/initialise
                        Guides you through the process of (1) creation of a new VM in QEMU and (2)
                        making it interactable from host console. After this is done, it uses the VM
                        for testing itself: it (a) copies itself in the VM, (b) generates SSL/UEFI keys
                        using ot/generate-uefi-keys, (c) creates new boot via danger/efistub/make-new-
                        boot and finally (d) ensures the VM can successfully reboot. See also
                        qemu/efistub/run-tests. Example of usage: ./secboot.py --log-level INFO
                        qemu/efistub/initialise --vm-dir ~/devel/secboot_efistub_testing
    qemu/efistub/run-tests
                        Runs "integration" tests in QEMU VM. The tests verify that (1) the OS loads
                        after all modifications, (2) the OS does NOT load if anything (unified kernel
                        image) was changed. Example of usage: "./secboot.py --log-level INFO
                        qemu/efistub/run-tests --vm-dir ~/devel/secboot_efistub_testing"
    qemu/grub/initialise
                        Guides you through the process of (1) creation of a new VM in QEMU and (2)
                        making it interactable from host console. After this is done, it uses the VM
                        for testing itself: it (a) copies itself in the VM, (b) generates GPG keys in
                        the VM using ot/generate-gpg-keys, (c) generates SSL/UEFI keys using
                        ot/generate-uefi-keys, (d) creates new boot via danger/grub/make-new-boot and
                        finally (d) ensures the VM can successfully reboot. See also qemu/grub/run-
                        tests. Example of usage: ./secboot.py --log-level INFO qemu/grub/initialise
                        --vm-dir ~/devel/secboot_grub_testing
    qemu/grub/run-tests
                        Runs "integration" tests in QEMU VM. The tests verify that (1) the OS loads
                        after all modifications, (2) the OS does NOT load if anything (kernel, initrd,
                        GRUB, GRUB config, shim) was changed. Example of usage: "./secboot.py --log-
                        level INFO qemu/grub/run-tests --vm-dir ~/devel/secboot_grub_testing"

optional arguments:
  -h, --help            show this help message and exit
  --dry-run
  --log-level {DEBUG,INFO,ERROR,DISABLED}
                        Log level
  --run-tests
  --no-run-tests

Its output is pretty self-explanatory. If you need more details specify --log-level DEBUG, --dry-run is also supported. Also, you might want to check help of specific sub-commands, for example:

./secboot.py --log-level INFO danger/grub/make-new-boot --help
usage: secboot.py danger/grub/make-new-boot [-h] [--boot-dir BOOT_DIR] [--work-dir WORK_DIR]
                                            [--backups-dir BACKUPS_DIR] --pbkdf-grub-pass-path
                                            PBKDF_GRUB_PASS_PATH --uefi/engine {fs,yu}
                                            [--uefi/key {KEK,db}] [--uefi/keys-dir UEFI/KEYS_DIR]
                                            [--uefi/pass UEFI/PASS] [--uefi/nfc-reader UEFI/NFC_READER]
                                            --gpg/engine {fs,yu} [--gpg/pub-key GPG/PUB_KEY]
                                            [--gpg/key-id GPG/KEY_ID] [--GNUPGHOME GNUPGHOME]
                                            [--use-gpg-agent-for-pass] [--gpg/pass GPG/PASS]

optional arguments:
  -h, --help            show this help message and exit
  --boot-dir BOOT_DIR   Boot dir.
  --work-dir WORK_DIR   Work dir.
  --backups-dir BACKUPS_DIR
                        Directory where to backup existing boot
  --uefi/engine {fs,yu}
                        Defines source of key info: "fs" - a directory on filesystem, "yu" - Yubikey
  --uefi/nfc-reader UEFI/NFC_READER
                        (Only if "yu" engine is used and only if it is used over NFC interface.) An
                        id(or a prefix thereof) of NFC reader. It is needed for Yubikey soft to be able
                        to correctly talk to Yubikey. You can obtain one by running: "yubico-piv-tool
                        -a status -v2", see more info here https://github.com/Yubico/yubico-piv-
                        tool/issues/298
  --gpg/engine {fs,yu}  Defines source of key info: "fs" - a directory on filesystem, "yu" - Yubikey
  --gpg/pub-key GPG/PUB_KEY
                        (Only if "yu" engine is used) Path to a public GPG key

Buy equipment

Storing your keys in Yubikey has a number of advantages:

  • Keys are never stored on filesystem of your PC.
  • It is nearly impossible to extract (private) keys from Yubikey (and impossible without your noticing it).
  • Yubikey has a hardware-backed max PIN entry counter (for example, your LUKS password can be brute-forced arbitrarily long, but Yubikey limits the number of attempts to 3).
  • Yubikey can be hideen/stored in a safe place.
  • You do not need to insert it physically in a USB because there are NFC versions.

So, you might want to buy Yubikey NFC as well as NFC reader: something like HID Identity OMNIKEY 5422 will work, while NFC ACR122 ACR122U will NOT because it does not support “extended APDU” / “TPDU”.

Generate SSL/UEFI keys and certificates

This will genrate (PK, KEK, db) x (cer, key) in a directory (sec_out in the example below):

$ ./secboot.py --log-level INFO ot/generate-uefi-keys -o sec_out/ --id Popl

2021-04-27 09:00:10,939 INFO generate:370: Generated private key in sec_out/PK.key, cert in sec_out/PK.crt. You might want to check that everything is fine: "openssl x509 -inform pem -in sec_out/PK.crt -text"
2021-04-27 09:00:11,043 INFO generate:378: Converted PEM-encoded .crt to DER-encoded .cer at: sec_out/PK.cer
2021-04-27 09:00:11,201 INFO generate:370: Generated private key in sec_out/KEK.key, cert in sec_out/KEK.crt. You might want to check that everything is fine: "openssl x509 -inform pem -in sec_out/KEK.crt -text"
2021-04-27 09:00:11,304 INFO generate:378: Converted PEM-encoded .crt to DER-encoded .cer at: sec_out/KEK.cer
2021-04-27 09:00:11,455 INFO generate:370: Generated private key in sec_out/db.key, cert in sec_out/db.crt. You might want to check that everything is fine: "openssl x509 -inform pem -in sec_out/db.crt -text"
2021-04-27 09:00:11,558 INFO generate:378: Converted PEM-encoded .crt to DER-encoded .cer at: sec_out/db.cer

If you have a Yubikey, move keys to it:

$ ./secboot.py --log-level INFO ot/enroll-ssl-to-yubikey --keys-dir sec_out/ --uefi/nfc-reader "HID Global OMNIKEY 5422 Smartcard Reader [OMNIKEY 5422CL"

2021-04-27 09:18:00,161 WARNING enroll_to_yubikey:470: You might want to setup your Yubikey: (1) Set PIN: "yubico-piv-tool -r 'HID Global OMNIKEY 5422 Smartcard Reader [OMNIKEY 5422CL' --action change-pin -P 123456", (2) Set PUK: "yubico-piv-tool -r 'HID Global OMNIKEY 5422 Smartcard Reader [OMNIKEY 5422CL' --action change-puk -P 12345678", (3) Enable PIV over NFC: "ykman config nfc -f -e PIV". Other action see here: https://developers.yubico.com/yubico-piv-tool/Actions/
2021-04-27 09:18:00,655 INFO enable_retired_slots:483: Retired slots enabled
2021-04-27 09:18:00,843 INFO enroll:496: Added key: sec_out/PK.key in slot: 82
2021-04-27 09:18:01,060 INFO enroll:502: Added certificate: sec_out/PK.crt in slot: 82
2021-04-27 09:18:01,246 INFO enroll:496: Added key: sec_out/KEK.key in slot: 9d
2021-04-27 09:18:01,463 INFO enroll:502: Added certificate: sec_out/KEK.crt in slot: 9d
2021-04-27 09:18:01,651 INFO enroll:496: Added key: sec_out/db.key in slot: 9c
2021-04-27 09:18:01,867 INFO enroll:502: Added certificate: sec_out/db.crt in slot: 9c

Refer to help of “ot/enroll-ssl-to-yubikey” to get more info about --uefi/nfc-reader option and its value.

For GRUB users

Generate GPG keys

The best guide for generating GPG keys and enrolling them to Yubikey is here. But the script also can generate them:

./secboot.py --log-level INFO ot/generate-gpg-keys --GNUPGHOME sec_out/gpg1 --id Popl --gpg/pass ""

2021-05-01 19:46:31,582 INFO generate_keys:818: Generated primary rsa4096 cert key with keyid: C16D9DAAE7E7CF8667EBF64CE18101E9A3EAB78F
2021-05-01 19:46:32,269 INFO add_sub_key:826: Generated sub-key: rsa4096 sign
2021-05-01 19:46:32,983 INFO add_sub_key:826: Generated sub-key: rsa4096 encrypt
2021-05-01 19:46:34,425 INFO add_sub_key:826: Generated sub-key: rsa4096 auth

Done

Refer to the guide if you have a Yubikey and wish to move the key to it (note, it will remove keys on filesystem). In short:

gpg --edit-key $KEYID
   key 1
   keytocard
   key 1
   key 2
   keytocard
   key 2
   key 3
   keytocard
   key 3
   save
GRUB password hash

As described above, various advanced options (such as rescue shell) must be protected with GRUB’s superuser+password. The script creates GRUB config with username root. But you will need to come up with a password and create a file containing pbkdf2 hash of it:

$ grub-mkpasswd-pbkdf2 -c 50000000 # (1)!
Enter password: <test>
Reenter password: <test>
PBKDF2 hash of your password is grub.pbkdf2.sha512.10000.ED9363D6FFDC719D047BC8B4544DC36001DE400D26E2E040613D7503BBE80EE5C14FB39535BD7DBAB95851A612EC49F3A8213B57EE1C27B7DC34DE1ABF79E502.327C2AB81E227E26D11DE8D291DFFEFC026339F51FF46EE321F57B2D3397788E55C3D144F5BCA13BAE0AB01B2C28D28B8B8E2EACE8E70BF58466B7A5F03CC137
  1. -c 50000000 means that when an adversary will brute-force your password on a huge cluster of machines, it will take just ~2 minutes for one try, so, feel free to increase the iteration count (until you feel comfortable with the time required for one attempt).

Add the hash to a file:

echo grub.pbkdf2.sha512.10000.ED9363D6F... > ~/grub_pbkdf2_pass

We will then specify the file for the script. The script will read the hash and embed it in a standalone version of GRUB, which then will be signed with UEFI certificate.

Generate new boot

This is the only command that you will need to run repeatedly (potentially with each system update) (1).

  1. You can create an alias/function: sysupgrade: sudo apt update && sudo apt upgrade && sudo ./secboot.py --log-level INFO danger/make-new-boot ... to simplify the task.
$ sudo ./secboot.py --log-level INFO danger/make-new-boot \
                    --boot-dir /boot \
                    --uefi/engine fs --uefi/keys-dir sec_out/ \
                    --gpg/engine fs --gpg/key-id 8C0FE2CEAA13563813D6239504AF0F899E6DF636 --GNUPGHOME sec_out/gpg --gpg/pass "" \
                    --pbkdf-grub-pass-path ~/grub_pbkdf2_pass

2021-05-01 18:46:20,301 INFO make_new_boot:1222: Backing up /boot...
2021-05-01 18:46:20,301 INFO exec:82: executing: cp -r /boot /bb/boot.bak.1619891180.3016443

2021-05-01 18:46:20,784 INFO make_new_boot:1235: Preparing standalone GRUB, its config and signing it with GPG...
2021-05-01 18:46:21,080 INFO _create_trampoline_config:1099: Created grub trampoline config at: /tmp/secboot_eex1v1g3/grub.init.cfg
2021-05-01 18:46:21,422 INFO create_standalone_and_config:1193: Signed grub trampoline config. Signature: /tmp/secboot_eex1v1g3/grub.init.cfg.sig, you can verify it yourself: "gpg --verify /tmp/secboot_eex1v1g3/grub.init.cfg.sig /tmp/secboot_eex1v1g3/grub.init.cfg"
2021-05-01 18:46:21,848 INFO _create_standalone:1120: Created standalone grub EFI binary at: /tmp/secboot_eex1v1g3/grubx64.efi
2021-05-01 18:46:21,848 INFO _create_main_config:1125: Creating main GRUB config for kernels: [KernelVersion(p1=5, p2=8, p3=0, p4=50, suffix='generic'), KernelVersion(p1=5, p2=8, p3=0, p4=25, suffix='generic')]...
2021-05-01 18:46:23,872 INFO generate_custom_menu_entries:1143: Generating menuentry for kernel version: 5.8.0-50-generic, cmdline: root=/dev/mapper/vgkubuntu-root ro console=tty0 console=ttyS0,38400n8 and OS: Ubuntu 20.10
2021-05-01 18:46:23,872 INFO generate_custom_menu_entries:1143: Generating menuentry for kernel version: 5.8.0-25-generic, cmdline: root=/dev/mapper/vgkubuntu-root ro console=tty0 console=ttyS0,38400n8 and OS: Ubuntu 20.10
2021-05-01 18:46:23,872 INFO _create_main_config:1176: Created main GRUB config at: /tmp/secboot_eex1v1g3/grub.cfg
2021-05-01 18:46:24,112 INFO create_standalone_and_config:1204: Signed grub main config. Signature: /tmp/secboot_eex1v1g3/grub.cfg.sig, you can verify it yourself: "gpg --verify /tmp/secboot_eex1v1g3/grub.cfg.sig /tmp/secboot_eex1v1g3/grub.cfg"

2021-05-01 18:46:24,114 INFO make_new_boot:1243: Coping results to the right place...
2021-05-01 18:46:24,115 INFO exec:82: executing: cp /tmp/secboot_eex1v1g3/grub.cfg /boot/efi
2021-05-01 18:46:24,218 INFO exec:82: executing: cp /tmp/secboot_eex1v1g3/grub.cfg.sig /boot/efi
2021-05-01 18:46:24,321 INFO exec:82: executing: cp /tmp/secboot_eex1v1g3/grubx64.efi /boot/efi/EFI/ubuntu/grubx64.efi
2021-05-01 18:46:24,429 INFO exec:82: executing: cp /boot/vmlinuz-5.8.0-50-generic /boot/efi
2021-05-01 18:46:24,549 INFO exec:82: executing: cp /boot/initrd.img-5.8.0-50-generic /boot/efi
2021-05-01 18:46:24,741 INFO exec:82: executing: cp /boot/vmlinuz-5.8.0-25-generic /boot/efi
2021-05-01 18:46:24,860 INFO exec:82: executing: cp /boot/initrd.img-5.8.0-25-generic /boot/efi

2021-05-01 18:46:25,115 INFO make_new_boot:1260: (Re-)signing all EFI binaries with SSL...
2021-05-01 18:46:25,219 WARNING install_if_program_does_not_exist:134: Program: pesign does not exist => installing it via package: pesign
2021-05-01 18:46:32,095 INFO re_sign_efi_file:626: (Re-)signed and verified /boot/efi/vmlinuz-5.8.0-50-generic with FsUefi(/home/test/sec_out/db). You can verify it yourself with "pesign -S -i /boot/efi/vmlinuz-5.8.0-50-generic" and/or "sbverify --cert /home/test/sec_out/db.crt /boot/efi/vmlinuz-5.8.0-50-generic"
2021-05-01 18:46:33,276 INFO re_sign_efi_file:626: (Re-)signed and verified /boot/efi/vmlinuz-5.8.0-25-generic with FsUefi(/home/test/sec_out/db). You can verify it yourself with "pesign -S -i /boot/efi/vmlinuz-5.8.0-25-generic" and/or "sbverify --cert /home/test/sec_out/db.crt /boot/efi/vmlinuz-5.8.0-25-generic"
2021-05-01 18:46:33,660 INFO re_sign_efi_file:626: (Re-)signed and verified /boot/efi/EFI/ubuntu/grubx64.efi with FsUefi(/home/test/sec_out/db). You can verify it yourself with "pesign -S -i /boot/efi/EFI/ubuntu/grubx64.efi" and/or "sbverify --cert /home/test/sec_out/db.crt /boot/efi/EFI/ubuntu/grubx64.efi"
2021-05-01 18:46:34,110 INFO re_sign_efi_file:626: (Re-)signed and verified /boot/efi/EFI/ubuntu/shimx64.efi with FsUefi(/home/test/sec_out/db). You can verify it yourself with "pesign -S -i /boot/efi/EFI/ubuntu/shimx64.efi" and/or "sbverify --cert /home/test/sec_out/db.crt /boot/efi/EFI/ubuntu/shimx64.efi"
2021-05-01 18:46:34,555 INFO re_sign_efi_file:626: (Re-)signed and verified /boot/efi/EFI/ubuntu/mmx64.efi with FsUefi(/home/test/sec_out/db). You can verify it yourself with "pesign -S -i /boot/efi/EFI/ubuntu/mmx64.efi" and/or "sbverify --cert /home/test/sec_out/db.crt /boot/efi/EFI/ubuntu/mmx64.efi"
2021-05-01 18:46:35,007 INFO re_sign_efi_file:626: (Re-)signed and verified /boot/efi/EFI/BOOT/BOOTX64.EFI with FsUefi(/home/test/sec_out/db). You can verify it yourself with "pesign -S -i /boot/efi/EFI/BOOT/BOOTX64.EFI" and/or "sbverify --cert /home/test/sec_out/db.crt /boot/efi/EFI/BOOT/BOOTX64.EFI"

2021-05-01 18:46:35,906 INFO make_new_boot:1275: (Re-)signing files with GPG...
2021-05-01 18:46:36,265 INFO re_sign_file_with_gpg:868: Signed and verified /boot/vmlinuz-5.8.0-50-generic with Gpg(/home/test/sec_out/gpg, key_id: 8C0FE2CEAA13563813D6239504AF0F899E6DF636). Signature: /boot/vmlinuz-5.8.0-50-generic.sig. You can verify it yourself with "gpg --verify /boot/vmlinuz-5.8.0-50-generic.sig /boot/vmlinuz-5.8.0-50-generic"
2021-05-01 18:46:37,150 INFO re_sign_file_with_gpg:868: Signed and verified /boot/initrd.img-5.8.0-50-generic with Gpg(/home/test/sec_out/gpg, key_id: 8C0FE2CEAA13563813D6239504AF0F899E6DF636). Signature: /boot/initrd.img-5.8.0-50-generic.sig. You can verify it yourself with "gpg --verify /boot/initrd.img-5.8.0-50-generic.sig /boot/initrd.img-5.8.0-50-generic"

2021-05-01 18:46:40,765 INFO make_new_boot:1284: Removing backdoor-ish MokManager...
2021-05-01 18:46:40,766 INFO exec:82: executing: rm /boot/efi/EFI/ubuntu/mmx64.efi
2021-05-01 18:46:40,869 INFO exec:82: executing: rm /boot/efi/EFI/BOOT/mmx64.efi

For EFI Stub Users

$ sudo ./secboot.py --log-level INFO danger/efistub/make-new-boot \
                    --boot-dir /boot --disk /dev/sda --partition 1 \
                    --uefi/engine fs --uefi/keys-dir sec_out/

2021-05-04 17:46:11,174 INFO create_unified_kernel_from:1496: Creating unified kernel image from vmlinuz and initrd 5.11.0-16-generic in /boot
2021-05-04 17:46:11,277 WARNING install_if_program_does_not_exist:144: Program: objcopy does not exist => installing it via package: binutils
2021-05-04 17:46:15,764 WARNING install_if_program_does_not_exist:144: Program: pesign does not exist => installing it via package: pesign
2021-05-04 17:46:22,283 INFO re_sign_efi_file:636: (Re-)signed and verified /tmp/secboot_yqx1ll9c/secboot-linux-latest.efi with FsUefi(/home/test/sec_out/db). You can verify it yourself with "pesign -S -i /tmp/secboot_yqx1ll9c/secboot-linux-latest.efi" and/or "sbverify --cert /home/test/sec_out/db.crt /tmp/secboot_yqx1ll9c/secboot-linux-latest.efi"

2021-05-04 17:46:22,283 INFO create_unified_kernel_from:1496: Creating unified kernel image from vmlinuz and initrd 5.8.0-50-generic in /boot
2021-05-04 17:46:24,581 INFO re_sign_efi_file:636: (Re-)signed and verified /tmp/secboot_yqx1ll9c/secboot-linux-prev.efi with FsUefi(/home/test/sec_out/db). You can verify it yourself with "pesign -S -i /tmp/secboot_yqx1ll9c/secboot-linux-prev.efi" and/or "sbverify --cert /home/test/sec_out/db.crt /tmp/secboot_yqx1ll9c/secboot-linux-prev.efi"

2021-05-04 17:46:24,581 INFO make_boot_backup:984: Backing up /boot...
2021-05-04 17:46:24,581 INFO exec:92: executing: cp -r /boot /bb/boot.bak.1620146784.581392

2021-05-04 17:46:24,944 INFO exec:92: executing: find /boot/efi -mindepth 1 ! -name "*.cer" -delete
2021-05-04 17:46:25,047 INFO exec:92: executing: mkdir -p /boot/efi
2021-05-04 17:46:25,150 INFO exec:92: executing: mkdir -p /boot/efi
2021-05-04 17:46:25,252 INFO exec:92: executing: cp /tmp/secboot_yqx1ll9c/secboot-linux-prev.efi /boot/efi/secboot-linux-prev.efi
2021-05-04 17:46:25,463 INFO exec:92: executing: cp /tmp/secboot_yqx1ll9c/secboot-linux-latest.efi /boot/efi/secboot-linux-latest.efi

2021-05-04 17:46:25,865 INFO repopulate_efibootmgr_if_needed:1522: Need to repopulate efibootmgr entries because number of entries (which is 7) is not 2
2021-05-04 17:46:25,866 INFO repopulate_efibootmgr_if_needed:1539: Removing boot: 0000
2021-05-04 17:46:25,980 INFO repopulate_efibootmgr_if_needed:1539: Removing boot: 0001
2021-05-04 17:46:26,092 INFO repopulate_efibootmgr_if_needed:1539: Removing boot: 0002
2021-05-04 17:46:26,205 INFO repopulate_efibootmgr_if_needed:1539: Removing boot: 0003
2021-05-04 17:46:26,316 INFO repopulate_efibootmgr_if_needed:1539: Removing boot: 0004
2021-05-04 17:46:26,429 INFO repopulate_efibootmgr_if_needed:1539: Removing boot: 0005
2021-05-04 17:46:26,540 INFO repopulate_efibootmgr_if_needed:1539: Removing boot: 0006
2021-05-04 17:46:26,647 INFO add_entry:1547: Adding boot entry for "SecBoot Previous Linux", /secboot-linux-prev.efi
2021-05-04 17:46:26,764 INFO add_entry:1547: Adding boot entry for "SecBoot Latest Linux", /secboot-linux-latest.efi

Enroll your UEFI keys

There are many ways of enrolling your keys, but since you will need to reboot your PC anyway in order to delete existing keys from UEFI, I will consider enrolling certificates using UEFI.

Get your certificates
  • If they are on filesystem it is trivial.

  • If you moved them to a Yubikey, run this:

    ./secboot.py --log-level INFO ot/enroll-certs-to-uefi --uefi/engine yu --uefi/nfc-reader "HID Global OMNIKEY 5422 Smartcard Reader [OMNIKEY 5422CL"
    
    Not implemented yet.
    You will need to do it manually:
        - Copy *.cer files from /tmp/secboot_certs_for_uefi_dfd29wtd to a FAT-formatted flash drive, _or_ /boot/efi directory
        - Reboot and enter UEFI setup => SecureBoot settings
        - In the settings, remove all existing keys (PK, KEK, db)
        - Enroll your own (db.cer, KEK.cer, PK.cer)
    
    Press any key to exit...
    

    The command above will extract all certificates in a temporary directory and will wait until you press a key, after which it will erase the extracted certificates. So copy the certificates somewhere.

Move certificates to media accessible from UEFI

You have two options:

  • Simplest Copy *.cer files to /boot/efi — this is visible from UEFI.

  • Alternatively, you can create a USB flash and copy them there:

    1
    2
    3
    4
    sudo parted /dev/sdX mklabel gpt # or msdos
    sudo parted -a opt /dev/sdX mkpart primary 0% 100%
    sudo mkfs.ext4 -L TempStorage /dev/sdX1
    sudo mount /dev/sda1 /mnt/
    
Reboot and enroll

Finally, reboot, remove all existing certificates, and enroll your own. If something goes wrong, you should be able to fix it by disabling SecureBoot.

Note that you will probably need to switch Mode from “Windows UEFI” to “Custom”, otherwise, shim will not be able to validate GRUB signed by us.

Lock-down UEFI

Protect UEFI settings / certificates by setting a password. Without this step, all UEFI settings can be disabled.

Always prefer the thorough or full boot option when unsure of the vendor implementation.

Disable Compatibility Support Module (CSM) Legacy Mode to prevent legacy fallback mode from bypassing Secure Boot protections. Reason: Some systems fall back to Legacy Mode when a Secure Boot check fails.

Reboot and check it works

  • Check that secure boot is enabled: sudo dmesg | grep -i "uefi\|secure boot"
  • Double-check that secure boot is enabled: mokutil --sb-state
  • Triple-check that secure boot is enabled: od -An -t u1 --read-bytes=1 --skip-bytes=4 /sys/firmware/efi/efivars/SecureBoot-* must output 1.
  • Check that there is only your certificates: efi-readvar
  • Check you keys are in the output of sudo cat /proc/keys
  • Ideally, you should check that boot will fail if you modify something (shim, grub, grub.cfg, initrd, kernel). Obviously, be prepared to restore it yourself.
    • You can place a binary (such as an unsigned GRUB EFI binary) at EFI/BOOT/BOOTX64.EFI on a FAT32-formatted USB Flash drive. Use the UEFI firmware interface to set this drive as the current boot drive, and ensure that a security warning appears, which halts the boot process.
    • You also should verify that an image signed with the default UEFI secure boot keys does not boot — an Ubuntu CD or bootable USB stick should allow you to verify this.

In case of an error

You can always find an up-to-date (and presumably working) version of the above description in the script. create_new_boot_in_fresh_vm does exactly what is described above.

qemu: Qemu = Qemu(dry_run, image)
qemu.start()
qemu.disable_sudo_pass()
# Avoid race condition between us and system-update runnin in the background
qemu.exec_and_wait(f'sudo apt-get -y remove unattended-upgrades')
qemu.copy(pl.Path(__file__).resolve(), script) # Copy the script in the VM

# Generate SSL/UEFI
qemu.exec_and_wait(f'sudo {GENERATE_UEFI_KEYS_EXAMPLE} && sudo cp -r ./sec_out/*.cer /boot/efi')

# Generate GPG
gpg_gen_out: str = qemu.exec_and_wait(f'sudo {GENERATE_GPG_KEYS_EXAMPLE}')
gpg_keyid: str = gpg_keyid_regex.match(gpg_gen_out).group('g1') if not dry_run else ""

# Generate GRUB PBKDF2 pass
qemu.exec_and_wait(f'grub-mkpasswd-pbkdf2 | tee ~/grub_pbkdf2_pass_out && cat ~/grub_pbkdf2_pass_out | '
                f'grep "PBKDF2 hash of your password is" | sed \'s/PBKDF2 hash of your password is //g\' > '
                f'~/grub_pbkdf2_pass', resp="Enter password:", use_regex=False)
qemu.exec_and_wait('test', resp="Reenter password:", use_regex=False, wait_for_echo=False)
qemu.exec_and_wait('test', wait_for_echo=False) # Repeat it

# Create new /boot
make_new_boot_cmd: str = MAKE_NEW_BOOT_EXAMPLE_FS
make_new_boot_cmd = make_new_boot_cmd.replace("--boot-dir ./boot", "--boot-dir /boot")
make_new_boot_cmd = make_new_boot_cmd.replace("--gpg/key-id ADDB2...", f"--gpg/key-id {gpg_keyid}")
qemu.exec_and_wait(f'sudo {make_new_boot_cmd}')

qemu.shutdown()
qemu: Qemu = Qemu(dry_run, image)
qemu.start()
qemu.disable_sudo_pass()
qemu.exec_and_wait(f'sudo apt-get -y remove unattended-upgrades')
qemu.copy(pl.Path(__file__).resolve(), script) # Copy the script in the VM

qemu.exec_and_wait(f'sudo {GENERATE_UEFI_KEYS_EXAMPLE} && sudo cp -r ./sec_out/*.cer /boot/efi')

make_new_boot_cmd: str = MAKE_NEW_EFISTUB_BOOT_EXAMPLE_FS
make_new_boot_cmd = make_new_boot_cmd.replace("--disk /dev/nvme0n1", f"--disk /dev/sda")
qemu.exec_and_wait(f'sudo {make_new_boot_cmd}')

qemu.shutdown()

Optional Proper testing in a VM

So, how do you know that everything described above really works? How do you know I do not lie? How do I know that I am right?

Well, there is only one way to check — test in a VM. In this section:

  • we will install our favourite distro in VM (needs to be done manually)
  • set it up similarly to our main PC (needs to be done manually)
  • create new /boot with SecureBoot
  • and then we will be tampering/modifying random and sensitive parts of the OS in order to ensure that boot stops working.

Setup VM (semi-manually)

Just follow instructions of qemu/efistub/initialise (or qemu/grub/initialise):

./secboot.py --no-run-tests  --log-level INFO qemu/initialise --vm-dir ~/devel/secboot_testing

You should install OS in VM:
  - Create disk: qemu-img create -f qcow2 secboot_testing/without-secboot.img 20G
  - Start installer: qemu-system-x86_64 -m 3G -drive file=secboot_testing/OVMF-without-secboot.fd,format=raw,if=pflash -boot d -enable-kvm -smp 2 -net nic -net user -cdrom ~/Downloads/kubuntu-20.10-desktop-amd64.iso -hda secboot_testing/without-secboot.img
  - Create user "test" with password "test"

Make the VM interactable from host console:
  - sudo nano /etc/default/grub
    # GRUB_TIMEOUT_STYLE=hidden
    GRUB_TIMEOUT=3
    GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=ttyS0,38400n8"
    GRUB_TERMINAL=serial
    GRUB_SERIAL_COMMAND="serial --speed=38400 --unit=0 --word=8 --parity=no --stop=1"
  - Re-generate grub.cfg: grub-mkconfig -o /boot/grub/grub.cfg

Shutdown the OS

Press any key when ready...

2021-05-01 18:45:15,361 INFO recreate_new_snapshot:1389: Removed write permissions from secboot_testing/without-secboot.img and secboot_testing/OVMF-without-secboot.fd
2021-05-01 18:45:15,731 INFO recreate_new_snapshot:1403: Created new snapshot QemuImage(image=PosixPath('secboot_testing/with-secboot.img'), uefi=PosixPath('secboot_testing/OVMF-with-secboot.fd')) from QemuImage(image=PosixPath('secboot_testing/without-secboot.img'), uefi=PosixPath('secboot_testing/OVMF-without-secboot.fd')) and made the latter immutable

test@test-Standard-PC-i440FX-PIIX-1996:~$
test@test-Standard-PC-i440FX-PIIX-1996:~$ echo "test ALL=(ALL) NOPASSWD: ALL" | sudo tee -a /etc/sudoers
[sudo] password for test:
test ALL=(ALL) NOPASSWD: ALL
test@test-Standard-PC-i440FX-PIIX-1996:~$ sudo apt-get -y remove unattended-upgrades

...

Now you have to enroll new keys in UEFI:
  - press and hold <Esc> to enter UEFI settings
  - Device Manager
  - Secure Boot configuration
  - Secure Boot Mode -> Custom Mode
  - Custom Secure Boot Options
  - DB Options -> Enroll Signature -> Enter -> db.cer -> Commit changes and exit
  - Repeat for KEK
  - Repeat for PK
  - Esc -> Esc -> Esc -> On the main menu: Reset


Press any key when you are ready...

...

2021-05-01 18:48:51,060 INFO qemu_initialise:1660: SecureBoot has been successfully enabled. Everything works fine. Shuttig down...

Run automated test

Use option qemu/efistub/run-tests (or qemu/grub/run-tests). The script will create a temporary snapshot, modify various parts of the system (by writing random byte in the middle of a file, so that signature be invalid), reboot the system, and verify that it does not load (and verify that it did not load due to signature verification check, not due to the garbage we wrote — this is achieved by switching of UEFI in VM, and ensuring that it loads fine).

Example of one such modification:

qemu: Qemu = Qemu(dry_run, disposable)
assert qemu.start() == BootStatuses.Ok
qemu.overwrite_middle_of(file=pl.Path("/boot/efi/EFI/BOOT/BOOTX64.EFI"))
qemu.overwrite_middle_of(file=pl.Path("/boot/efi/EFI/ubuntu/shimx64.efi"))
qemu.shutdown()
assert qemu.start() == BootStatuses.UefiFailedToLoadShim

# Check that OS failed to boot not because we broke its binaries by writing garbage into them, but
# because of signature verification. We do it, by trying to boot the same OS with initial UEFI (without keys):
qemu: Qemu = Qemu(dry_run, QemuImage(image=disposable.image, uefi=orig_uefi))
assert qemu.start() == BootStatuses.Ok

Useful QEMU commands

  • QEMU images:
    • Create an image with max size 20G: qemu-img create -f qcow2 secboot-initial.img 20G
    • In case you need later to resize it: qemu-img resize secboot-initial.img +10G
  • Bring UEFI: sudo apt install ovmf && cp /usr/share/ovmf/OVMF.fd ./OVMF.fd
  • Install it: qemu-system-x86_64 -m 4G -drive file=./OVMF.fd,format=raw,if=pflash -boot d -enable-kvm -smp 4 -net nic -net user -cdrom ~/Downloads/kubuntu-20.10-desktop-amd64.iso -hda secboot-initial.img
  • Create a snapshot: qemu-img create -f qcow2 -b sb-image.img trying_to_flip_a_bit.img
  • Mount a folder from host
    • -virtfs local,path=~/devel/,mount_tag=host0,security_model=passthrough,id=host0
    • Add entry in guest /etc/fstab: TODO
  • Better screen resolution: -vga virtio
  • Modify binary file (to check that the modification will be caught):

    (export f=file-to-change.txt; cp $f $f.bak; printf "\x56" | dd of=$f bs=1 seek=10 count=1 conv=notrunc; echo; cmp -b -c $f $f.bak; md5sum $f $f.bak)
    

Resources