Ubuntu, Secure Boot and ESP32

I bought an ESP32 development kit and some sensors to start monitoring air quality in my house. I had no experience with any of this, so the first obstacle was dealing with Secure Boot. After some Linux magic and throwing enough MicroPython at it, eventually some LEDs started to blink. Nice.

Connecting to ESP32

Programming an ESP32 chip requires a way to transfer your MicroPython codes to the chip itself. Unlike a RaspberryPi, this thing doesn't come with a full-blown Linux operating system and SSH access. ESP32 is more lightweight, and runs code that is uploaded from your laptop through a micro USB cable.

Ubuntu 22.04 does not recognise this ESP32 chip out of the box. It requires a new kernel module to be installed. Modules are loaded into the kernel at run time, so it is not necessary to compile an entirely new kernel for this.

The challenge is, modern computers are equipped with a future called Secure Boot. This prevents unauthorised software, including all kernel modules, from being loaded at boot time.

This means it is not so straightforward anymore to just load a new unsigned ESP32 Kernel Module.

I wanted to understand what this is and how it works.

Secure Boot

Basically Secure Boot ensures all software loaded during boot, from the system's firmware to the operating system's kernel and modules, are signed by a trusted key. This is a good thing, because malware installed in the boot process can be harder to fight.

Secure Boot is just an additional measure to prevent injection of unknown software into the boot process. Any kernel modules loaded after boot also have to be signed, because they are directly related to the kernel itself.

Essentially the boot process now forms a chain of trust. Every step retrieves the signature of the next step, and validates it against a list of trusted public keys.

In this diagram, UEFI is the first component executed after pressing the power button. This component comes with preconfigured keys when you buy the hardware. It is not easy to change these, because it is controlled by a strict process at Microsoft.

That's why the Linux community came up with a new component called the Shim, which the End user has more control of. Here, it is allowed to add and remove our own trusted keys in the Machine Owned Key (MOK) database. Now control is regained over the rest of the trust chain, without help of Microsoft.

So at the start, UEFI retrieves the signature of the next component, the Shim, and verifies it against all public keys in a trust store called DB, also known as the Signature Database. If all is well, it hands over control to the Shim.

Shim does the same trick, it verifies the signature of the bootloader (Grub) against public keys in the MOK trust store.

Grub and the kernel make use of the same MOK trust store and verification code, so this capability does not have to be replicated across the entire chain. This is okay because components down the chain trust components up the chain by definition.

UEFI

UEFI has a special section called Secure Variables where all keys are stored.

  • PK: The Platform Key, an X509 certificate that controls updates to PK and KEK
  • KEK: The Key Exchange Keys, an X509 or RSA2048 certificate that controls updates to DB
  • DB: The Signature Database, contains keys, signatures or hashes for determining trusted EFI binaries, e.g. the Shim
  • DBX: The Forbidden Signatures Database, contains keys, signatures or hashes that are blacklisted

So to connect an ESP32 chip, the kernel module has to be signed with a trusted private/public key-pair first.

Signing and loading kernel modules

A typical workflow would look something like this:

# Install build tools and kernel headers
sudo apt-get install build-essential linux-headers-$(uname -r)

# Compile and install the kernel module
make
sudo make install

# Sign the compiled module
kmodsign sha512 /var/lib/shim-signed/mok/MOK.priv /var/lib/shim-signed/mok/MOK.der [module_file]

# Load the module
sudo modprobe [module_name]

Modprobe also loads any required kernel module dependencies.

The MOK keys are automatically generated if 3rd party drivers or modules have been installed before. On my laptop this was already done during the installation of the Ubuntu OS.

If the keys in /var/lib/shim-signed/mok don't exist yet, they can be generated with:

sudo update-secureboot-policy --new-key

That's it!

So, where are the LEDs?

The ESP32 chip came with an SD card containing the source file of the kernel module. Unfortunately, this contains non-compiling code on modern systems.

The ESP32 chip was only connecting to my laptop after a few additional steps.

  1. Uninstall brltty first, as this clashes with interface driver for ch34x
  2. Clone this Github repository with updated Kernel Module sources
  3. Run make
  4. Sign the modules with kmodsign
  5. Load the module with make load

After hooking up the ESP32 chip with a MicroUSB cable, my IDE (Thonny) was able to load the MicroPython interpreter and execute code.

References

  1. https://oofhours.com/2021/01/19/uefi-secure-boot-who-controls-what-can-run/
  2. https://blog.hansenpartnership.com/the-meaning-of-all-the-uefi-keys/
  3. https://joonas.fi/2021/02/uefi-pc-boot-process-and-uefi-with-qemu/
  4. https://www.linux-magazine.com/Issues/2018/206/Linux-Secure-Boot-with-Shim
  5. https://wiki.ubuntu.com/UEFI/SecureBoot
  6. https://github.com/juliagoda/CH341SER