Implementing Secureboot with Yocto Wrynose and Grub
Note
TL;DR - Step-by-step guide to implementing UEFI Secureboot on x64 embedded Linux using Yocto Wrynose, meta-secure-core (Wind River), GRUB, and kas — covering custom signing keys, TPM-based LUKS auto-decryption, SELinux, read-only rootfs, and A/B updates with Mender. - Includes a complete kas YAML configuration, custom key generation script, and reference implementation at meta-amarula-solutions-demo-images on GitHub.
A customer recently came to Amarula Solutions with the task of creating a custom Yocto X64 image with the following features:
A/B updates integrating Mender (https://mender.io/)
LUKS with auto-decryption using an onboard TPM
Read-only file system
SELinux enabled
uEFI Secureboot
This was a job that fit our skill-set perfectly, and we were happy to help! I decided to attack the project in the following order:
Create a custom layer for the company
Get the image booting with the following partition schema:
EFI (Fat32)
BootA (ext4)
BootB (ext4)
RootfsA (ext4)
RootfsB (ext4)
/factory (ext4)
/data (ext4)
Add SELinux to the image and set it in permissive mode
Integrate Mender and ensure updates work
Add uEFI Secureboot
Add LUKS with auto-decryption
Perhaps the order of operations wasn’t perfect, but in the end, the image works and updates properly.
Also, there is a link at the bottom of the post for our meta-amarula-solutions-demo-images github page if you want to dive right in and start building images immediately! [1]
Why should you implement Secureboot in your embedded Linux image?
What challenges does Secureboot integration present?
The documentation for meta-secure-core assumes that you already know a lot about Secureboot, module signing, making your own user keys, what SELoader/Grub/MokUtil/etc are. For a new user, this is daunting, and leads to a lot of frustration.
Blog posts and examples of how to implement meta-secure-core are non-existent or out of date.
For security purposes, the customer needs to provide their own signing keys that we never created.
How do we solve these challenges?
Let’s start with the basics:
What is uEFI Secureboot?
uEFI Secureboot is a setting in the uEFI. When enabled, the uEFI verifies firmware images (such as bootloaders) with certificates stored in the uEFI. If the firmware image isn’t signed against a certificate stored in the uEFI, then the image fails to load, and the boot process stops!
How do we load certificates into the uEFI?
Set Secureboot in the uEFI to enforcing
Remove all installed certificates This step is crucial! When all certificates are removed AND uEFI Secureboot is set to enforcing, then the uEFI Secureboot is et to a special “SetupMode” where it is possible to enroll Secureboot certificates into the uEFI!
Using grub with meta-secure-core, a small amount of logic is automatically prepended before the first entry to check if “SetupMode” is active, and if so, automatically provisions the certificates and reboots the device.[2]
How do you build a Yocto image with Secureboot enabled?
The image requires the following:
Bitbake (https://git.openembedded.org/bitbake)
openembedded-core (https://git.openembedded.org/openembedded-core)
meta-openembedded (https://git.openembedded.org/meta-openembedded)
meta-oe
meta-perl
meta-secure-core (https://github.com/Wind-River/meta-secure-core.git)
meta-efi-secure-boot
meta-signing-key
With the above layers checked out and in the same directory, enabling secureboot is straightforward:
source openembedded-core/oe-init-build-envSetup BBLAYERS in conf/bblayers.conf as such:
BBLAYERS ?= “${TOPDIR}/../openembedded-core/meta${TOPDIR}/../meta-openembedded/meta-oe${TOPDIR}/../meta-openembedded/meta-perl${TOPDIR}/../meta-secure-core/meta-efi-secure-boot${TOPDIR}/../meta-secure-core/meta-secure-core-common${TOPDIR}/../meta-secure-core/meta-signing-key“Add the following to
conf/local.conf:UEFI secureboot settings
PACKAGE_CLASSES = “package_rpm” DISTRO_FEATURES:remove = “efi” DISTRO_FEATURES:append = “ efi-secure-boot modsign” GRUB_SIGN_VERIFY = “1” UEFI_SELOADER = “0” UEFI_SB = “1” USER_KEY_SHOW_VERBOSE ?= “1” SIGNING_MODEL = “sample”
See [3] for why UEFI_SELOADER is diabled.
secure-core-image uses rpm’s for signature/security purposes
Run
bitbake secure-core-image
However, doing all of these steps manually can be a pain, and reproducibility makes everything much nicer. The go-to solution for setting all of this up is the kas utility.
Below is an example of a kas-file that will do all of the above for you in one simple step:
header:
version: 20
distro: nodistro
target:
- secure-core-image
repos:
bitbake:
url: "https://git.openembedded.org/bitbake"
branch: 2.18
signed: false
layers:
.: disabled
openembedded-core:
url: "https://git.openembedded.org/openembedded-core"
branch: wrynose
signed: false
layers:
meta:
meta-selftest:
meta-openembedded:
url: "https://git.openembedded.org/meta-openembedded"
branch: wrynose
signed: false
layers:
meta-oe:
meta-perl:
meta-secure-core:
url: "https://github.com/Wind-River/meta-secure-core.git"
branch: wrynose
signed: false
layers:
meta-efi-secure-boot:
meta-secure-core-common:
meta-signing-key:
local_conf_header:
uefi-secure-boot: |
DISTRO_FEATURES:append = " efi-secure-boot modsign"
DISTRO_FEATURES:remove = "efi"
EFI_PROVIDER = "grub-efi"
MACHINE_FEATURES_NATIVE:append = " efi"
MACHINE_FEATURES:append = " efi"
GRUB_SIGN_VERIFY = "1"
INITRAMFS_IMAGE = "secure-core-image-initramfs"
PACKAGE_CLASSES = "package_rpm"
IMAGE_INSTALL:append = " kernel-image-bzimage"
UEFI_SB = "1"
UEFI_SELOADER = "0"
qemu-fixups: |
IMAGE_INSTALL:append = " ovmf-shell-efi"
IMAGE_FSTYPES += " wic"
QB_DEFAULT_FSTYPE = "wic"
Save the file, in our example, we will name it secure-core-image.yml and place the file in a top-level kas directory for oganization purposes.
After installing kas, an image can be built with a single command:
kas build kas/secure-core-image.yml
Images are found in build/tmp/deploy/images/qemux86-64/
Running the image in a qemu environment is as easy as
runqemu secure-core-image ovmf.secboot kvm
Notes:
meta-secure-core auto-sets the root password to “toor”
Once logged in as root,
mokutil --sb-stateshould output “SecureBoot enabled”
How do you set up a professional, production-grade configuration?
Building the demo image is as easy as:
git clone https://github.com/amarula/meta-amarula-solutions-demo-images.git
cd meta-amarula-solutions-demo-images
kas build kas/meta-amarula-solutions-demo-images.yaml
runqemu secureboot-demo-image ovmf.secboot kvm
How do you replace sample signing keys with production keys?
#!/usr/bin/env bash
set -e
EMAIL="support@mycompany.com"
PASSWORD="mycompanysupersecretpassword"
create_user_key_store() {
./create-user-key-store.sh \
-d ./${1}/secureboot \
-c "demo ${1} Signing Key" \
-n "demo-${1}-SecureCore" \
-m "${EMAIL}" \
-rp "${PASSWORD}" \
-bgp "${PASSWORD}" \
-bp "${PASSWORD}" \
-bn "demo-${1}-Bootloader" \
-bm "${EMAIL}" \
-ip "${PASSWORD}" \
--days 9125
}
create_user_key_store "secureboot-demo"
It is advised to move the new directory to your custom meta layer and do the following:
Adjust the MASTER_KEYS_DIR path to match the new directory
Include the keys.conf file in your image.bb or build/conf/local.conf file
Change
SIGNING_MODEL = "sample"toSIGNING_MODEL = "user"
Now you have the basics of secureboot with grub2 and Yocto Wrynose! Congratulations!
1: https://github.com/amarula/meta-amarula-solutions-demo-images
3: https://github.com/Wind-River/meta-secure-core/commit/6d83fbf86fee43ca6510208f7470b04419d4333b
Tip
Need help implementing secure boot or CRA compliance for your embedded Linux product? Amarula Solutions provides Yocto security consulting, custom signing key infrastructure, TPM/LUKS integration, and secure boot implementation for x64, ARM, and RISC-V platforms. Contact our security team