wolfBoot: add Yocto/OE secure bootloader recipes
Add wolfBoot secure boot support to meta-wolfssl. Four new recipes
cross-compile wolfboot.elf, build host-side signing/keygen tools,
and sign kernel FIT images with RSA4096+SHA3-384 for verified boot.
New recipes:
wolfboot.inc - shared SRC_URI, LICENSE, SRCREVs
wolfboot_git.bb - cross-compiles wolfboot.elf from a
user-selected config/examples/ template;
embeds a user-supplied RSA4096 public key
wolfboot-keytools-native_git.bb - native wolfboot-keygen / wolfboot-sign
wolfboot-signed-image.bb - signs kernel FIT with RSA4096+SHA3-384
Supporting files:
xilinx-bootbin_%.bbappend - replaces U-Boot with wolfBoot in BOOT.BIN
on ZynqMP (gated by WOLFBOOT_ENABLE="1");
uses BBFILES_DYNAMIC for meta-xilinx-tools
conf/layer.conf - registers wolfboot BBFILES globs and
PREFERRED_PROVIDER
README.md - usage guide, key provisioning, ZynqMP notes
Design decisions:
- Signing key is user-supplied out-of-band (WOLFBOOT_SIGNING_KEY) to
avoid leaking private key material through sstate or DEPLOY_DIR_IMAGE.
- wolfboot_git.bb uses raw make (not oe_runmake) because wolfBoot is a
bare-metal bootloader with its own -nostdlib/-ffreestanding toolchain
flags that conflict with Yocto's CC/CFLAGS/LDFLAGS injection.
- SRCREVs use weak assignment (?=) so downstream users can override via
local.conf to track unreleased upstream fixes.
Tested on AMD/Xilinx ZCU102 hardware with PetaLinux 2025.2:
FSBL -> PMU FW -> ATF -> wolfBoot (EL2) -> signed FIT -> Linux
WOLFBOOT_CONFIG="zynqmp_sdcard.config"
WOLFBOOT_LINUX_BOOTARGS_ROOT="/dev/mmcblk0p4"
pull/164/head
parent
e710f093ac
commit
1b58000aa4
|
|
@ -26,6 +26,8 @@ BBFILES += "${LAYERDIR}/recipes-wolfssl/wolfssl/*.bb \
|
|||
${LAYERDIR}/recipes-wolfssl/wolfprovider/wolfssl*.bbappend \
|
||||
${LAYERDIR}/recipes-wolfssl/wolfengine/wolfengine*.bb \
|
||||
${LAYERDIR}/recipes-wolfssl/wolfengine/wolfssl*.bbappend \
|
||||
${LAYERDIR}/recipes-wolfssl/wolfboot/*.bb \
|
||||
${LAYERDIR}/recipes-wolfssl/wolfboot/*.bbappend \
|
||||
${LAYERDIR}/recipes-examples/wolfcrypt/wolfcryptbenchmark/*.bb \
|
||||
${LAYERDIR}/recipes-examples/wolfcrypt/wolfcryptbenchmark/*.bbappend \
|
||||
${LAYERDIR}/recipes-examples/wolfcrypt/wolfcrypttest/*.bb \
|
||||
|
|
@ -45,6 +47,9 @@ BBFILES += "${LAYERDIR}/recipes-wolfssl/wolfssl/*.bb \
|
|||
${LAYERDIR}/recipes-support/gnutls/*.bbappend \
|
||||
${LAYERDIR}/recipes-support/gnutls/wolfssl-gnutls-wrapper_git.bb"
|
||||
|
||||
# xilinx-bootbin bbappend requires meta-xilinx-tools layer
|
||||
BBFILES_DYNAMIC += "xilinx-tools:${LAYERDIR}/recipes-bsp/bootbin/*.bbappend"
|
||||
|
||||
# Uncomment if building bind with wolfSSL.
|
||||
#BBFILES += "${LAYERDIR}/recipes-connectivity/bind/*.bbappend"
|
||||
|
||||
|
|
@ -99,6 +104,7 @@ PREFERRED_PROVIDER_wolfssl-py ??= "wolfssl-py"
|
|||
PREFERRED_PROVIDER_wolfcrypt-py ??= "wolfcrypt-py"
|
||||
PREFERRED_PROVIDER_wolfprovider ??= "wolfprovider"
|
||||
PREFERRED_PROVIDER_wolfengine ??= "wolfengine"
|
||||
PREFERRED_PROVIDER_wolfboot ??= "wolfboot"
|
||||
|
||||
BBFILES += "${@bb.utils.contains('WOLFSSL_TYPE', \
|
||||
'fips', \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
# Replace U-Boot with wolfBoot as second-stage bootloader in BOOT.BIN
|
||||
#
|
||||
# Only activates when WOLFBOOT_ENABLE = "1" is set in configuration (e.g.
|
||||
# local.conf or a machine .conf). Without that flag this bbappend is a
|
||||
# no-op and xilinx-bootbin keeps using U-Boot.
|
||||
#
|
||||
# A dedicated variable is used here instead of inspecting EXTRA_IMAGEDEPENDS:
|
||||
# EXTRA_IMAGEDEPENDS is frequently set inside image recipes, which are
|
||||
# parsed in a different context than xilinx-bootbin, so the anonymous
|
||||
# python() below would see an empty value and silently stay inert.
|
||||
#
|
||||
# Boot chain with wolfBoot:
|
||||
# FSBL -> PMU FW -> (bitstream) -> ATF (EL3) -> wolfBoot (EL2) -> signed FIT
|
||||
#
|
||||
# Default ZynqMP BIF order (machine-xilinx-zynqmp.inc):
|
||||
# FSBL -> PMU FW -> bitstream (optional) -> ATF -> DTB -> U-Boot
|
||||
#
|
||||
# wolfBoot BIF order:
|
||||
# FSBL -> PMU FW -> bitstream (optional) -> ATF -> wolfBoot
|
||||
# (DTB is inside the signed FIT image, loaded by wolfBoot at runtime.)
|
||||
|
||||
python () {
|
||||
if (d.getVar('WOLFBOOT_ENABLE') or '') != '1':
|
||||
# wolfBoot not requested -- this bbappend is inert.
|
||||
return
|
||||
# Apply wolfBoot BIF overrides below.
|
||||
d.setVar('BIF_SSBL_ATTR', 'wolfboot')
|
||||
d.setVarFlag('BIF_PARTITION_ATTR', 'wolfboot',
|
||||
'destination_cpu=a53-0,exception_level=el-2')
|
||||
d.setVarFlag('BIF_PARTITION_IMAGE', 'wolfboot',
|
||||
d.expand('${RECIPE_SYSROOT}/boot/wolfboot.elf'))
|
||||
d.setVar('BIF_DEVICETREE_ATTR', '')
|
||||
# BIF_PARTITION_IMAGE[wolfboot] points at ${RECIPE_SYSROOT}/boot/wolfboot.elf,
|
||||
# which is populated by do_populate_sysroot, not do_deploy. Depend on
|
||||
# the right task so xilinx-bootbin blocks until wolfboot.elf is staged.
|
||||
d.appendVar('DEPENDS', ' wolfboot')
|
||||
existing = d.getVarFlag('do_compile', 'depends') or ''
|
||||
d.setVarFlag('do_compile', 'depends', existing + ' wolfboot:do_populate_sysroot')
|
||||
}
|
||||
|
||||
# Only meaningful on ZynqMP (Versal has its own PLM-based flow; not handled here)
|
||||
COMPATIBLE_MACHINE:append = "|xilinx-zynqmp.*"
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
# wolfBoot (Yocto/OE recipes)
|
||||
|
||||
Recipes that build [wolfBoot](https://github.com/wolfssl/wolfBoot), wolfSSL's
|
||||
portable secure bootloader, as part of a Yocto / OpenEmbedded image.
|
||||
|
||||
## Recipes
|
||||
|
||||
| Recipe | Purpose |
|
||||
|---|---|
|
||||
| `wolfboot-keytools-native_git.bb` | Host-side `wolfboot-keygen` and `wolfboot-sign` utilities (RSA4096 key generation, RSA4096+SHA3-384 image signing). |
|
||||
| `wolfboot_git.bb` | Cross-compiles `wolfboot.elf` (bare-metal, AArch64 / AArch32 / RISC-V). Picks a config from `${S}/config/examples/` per the `WOLFBOOT_CONFIG` variable. Consumes a user-supplied signing key via `WOLFBOOT_SIGNING_KEY` (see "Signing-key provisioning" below). |
|
||||
| `wolfboot-signed-image.bb` | Signs the kernel FIT image (default `fitImage`) with RSA4096+SHA3-384 and emits `image_v${WOLFBOOT_IMAGE_VERSION}_signed.bin` into `DEPLOY_DIR_IMAGE`. |
|
||||
|
||||
A companion `recipes-bsp/bootbin/xilinx-bootbin_%.bbappend` overrides the
|
||||
AMD/Xilinx `xilinx-bootbin` recipe to swap U-Boot for `wolfboot.elf` in
|
||||
`BOOT.BIN` on ZynqMP. It only activates when `WOLFBOOT_ENABLE = "1"` is
|
||||
set in configuration (`local.conf` or a machine `.conf`).
|
||||
|
||||
## Signing-key provisioning (required)
|
||||
|
||||
`wolfboot_git.bb` and `wolfboot-signed-image.bb` both require you to
|
||||
supply a pre-generated RSA4096 private key via `WOLFBOOT_SIGNING_KEY`.
|
||||
The recipes will **not** auto-generate one — that would leak the
|
||||
private half through sstate and DEPLOY_DIR_IMAGE.
|
||||
|
||||
Generate the key pair once on your workstation:
|
||||
|
||||
```sh
|
||||
# One-time host-side build of the keytools (outside Yocto):
|
||||
make -C /path/to/wolfBoot/tools/keytools keygen sign
|
||||
|
||||
# Generate the RSA4096 key pair:
|
||||
/path/to/wolfBoot/tools/keytools/keygen --rsa4096 \
|
||||
-g /secure/path/wolfboot_signing_private_key.der
|
||||
```
|
||||
|
||||
Store `wolfboot_signing_private_key.der` **outside** the build tree — a
|
||||
secrets manager, an encrypted volume, or at minimum a `.gitignore`d
|
||||
directory. Back it up: losing the private key means you can never sign
|
||||
another A/B update image that your deployed `wolfboot.elf` will accept.
|
||||
|
||||
## Quick start (AMD/Xilinx ZynqMP, SD-card boot)
|
||||
|
||||
Set the following in `local.conf` (or a machine `.conf`). These must live
|
||||
in configuration, not inside an image recipe: the `xilinx-bootbin`
|
||||
bbappend reads `WOLFBOOT_ENABLE` at parse time, which runs before any
|
||||
image recipe is evaluated.
|
||||
|
||||
```bitbake
|
||||
# Activate the xilinx-bootbin bbappend (swap U-Boot for wolfBoot in BOOT.BIN)
|
||||
WOLFBOOT_ENABLE = "1"
|
||||
|
||||
# Ensure the signed FIT image is built alongside the image.
|
||||
EXTRA_IMAGEDEPENDS:append = " wolfboot-signed-image"
|
||||
|
||||
# REQUIRED: absolute path to the pre-generated signing private key.
|
||||
WOLFBOOT_SIGNING_KEY = "/secure/path/wolfboot_signing_private_key.der"
|
||||
|
||||
# Pick a config template from wolfBoot/config/examples/
|
||||
WOLFBOOT_CONFIG = "zynqmp_sdcard.config"
|
||||
|
||||
# Override the Linux rootfs partition in the DTB bootargs fixup.
|
||||
# Default in the ZynqMP SD-card example is /dev/mmcblk0p4.
|
||||
WOLFBOOT_LINUX_BOOTARGS_ROOT = "/dev/mmcblk0p4"
|
||||
|
||||
# Bump to "2" for A/B update images, etc.
|
||||
WOLFBOOT_IMAGE_VERSION = "1"
|
||||
```
|
||||
|
||||
Build:
|
||||
|
||||
```
|
||||
bitbake <your-image>
|
||||
```
|
||||
|
||||
Artifacts deployed to `tmp/deploy/images/<MACHINE>/`:
|
||||
|
||||
- `BOOT.BIN` - FSBL + PMUFW + ATF + wolfBoot.elf
|
||||
- `wolfboot.elf` - bare-metal bootloader
|
||||
- `wolfboot_signing_public_key.der` - verifying key (safe to publish; only if `WOLFBOOT_PUBLIC_KEY` is set)
|
||||
- `image_v1_signed.bin` - signed FIT image (for OFP_A partition)
|
||||
|
||||
Note: the private signing key is **not** deployed — it stays on the
|
||||
workstation / secrets store you pointed `WOLFBOOT_SIGNING_KEY` at.
|
||||
|
||||
## SD card layout (wolfBoot A/B scheme)
|
||||
|
||||
| Partition | Size | Type | Contents |
|
||||
|---|---|---|---|
|
||||
| p1 | 128 MB | FAT32 | `BOOT.BIN` |
|
||||
| p2 | 200 MB | raw | `image_v1_signed.bin` (OFP_A = primary) |
|
||||
| p3 | 200 MB | raw | update slot (OFP_B) |
|
||||
| p4 | rest | ext4 | Linux rootfs |
|
||||
|
||||
See `wolfBoot/tools/scripts/` for helper scripts to create the SD card
|
||||
and flash update images (once upstreamed).
|
||||
|
||||
## Key management
|
||||
|
||||
The signing key pair is **user-supplied**, not generated inside the build.
|
||||
`WOLFBOOT_SIGNING_KEY` must point at a pre-generated RSA4096 private key
|
||||
file (DER format) — see the "Signing-key provisioning" section at the top
|
||||
of this README. The recipe:
|
||||
|
||||
- Stages the private key at `${S}/wolfboot_signing_private_key.der` so the
|
||||
wolfBoot Makefile can derive the public half and compile it into
|
||||
`wolfboot.elf` via `src/keystore.c`.
|
||||
- Does **not** deploy the private key to `DEPLOY_DIR_IMAGE`. Only
|
||||
`wolfboot.elf` (and optionally the public key, if `WOLFBOOT_PUBLIC_KEY`
|
||||
is set) is deployed.
|
||||
- `wolfboot-signed-image.bb` reads the same `WOLFBOOT_SIGNING_KEY`
|
||||
directly to sign the FIT, so there is no on-disk hand-off of the
|
||||
private key between recipes.
|
||||
|
||||
To rotate keys:
|
||||
|
||||
1. Generate a new RSA4096 key pair with `wolfboot-keygen --rsa4096 -g …`.
|
||||
Prefer a **new filename/path** for each rotation (e.g.
|
||||
`wolfboot_signing_private_key_v2.der`) rather than overwriting the
|
||||
existing `.der` in place — see the caveat below.
|
||||
2. Update `WOLFBOOT_SIGNING_KEY` to the new private key path.
|
||||
3. Build: `wolfboot.elf` embeds the new public key, and
|
||||
`image_v<N>_signed.bin` is signed with the new private key.
|
||||
4. Flash the new `BOOT.BIN` (contains new `wolfboot.elf`) to the device,
|
||||
followed by a signed update image signed with the new key.
|
||||
|
||||
**sstate caveat.** `WOLFBOOT_SIGNING_KEY` points at an absolute path
|
||||
outside the recipe tree, so BitBake does not checksum the key file's
|
||||
contents into the task hash — only the path string participates. If you
|
||||
overwrite the same `.der` file in place with new key material, sstate
|
||||
and stamps may reuse the previously-built `wolfboot.elf` and signed
|
||||
image, leaving you with a mismatched public/private pair. To stay safe:
|
||||
|
||||
- **Preferred**: rotate to a *new filename/path*. Changing the value of
|
||||
`WOLFBOOT_SIGNING_KEY` invalidates the task hash naturally.
|
||||
- **Fallback** (if you must reuse the same path): force a rebuild with
|
||||
`bitbake -c cleansstate wolfboot wolfboot-signed-image` before the
|
||||
next `bitbake <your-image>`.
|
||||
|
||||
## Swapping U-Boot for wolfBoot
|
||||
|
||||
The `recipes-bsp/bootbin/xilinx-bootbin_%.bbappend` rewrites the BIF so
|
||||
`BOOT.BIN` contains `wolfboot.elf` instead of `u-boot.elf` at EL2, and
|
||||
drops the standalone DTB partition (wolfBoot loads the DTB out of the
|
||||
signed FIT image). It activates only when `WOLFBOOT_ENABLE = "1"` is set
|
||||
in configuration (`local.conf` or a machine `.conf`). Setting it inside
|
||||
an image recipe is **not** sufficient: the bbappend's anonymous python
|
||||
runs at parse time, before image recipes are evaluated.
|
||||
|
||||
## ZynqMP caveats
|
||||
|
||||
See the comments in `wolfBoot/config/examples/zynqmp_sdcard.config` for
|
||||
two non-obvious points:
|
||||
|
||||
1. **Root device**: when the XSA disables `sdhci0` (common on ZCU102
|
||||
boards where only the external SD slot is populated), SD1 enumerates
|
||||
as `/dev/mmcblk0`, not `/dev/mmcblk1`. The template default is
|
||||
`mmcblk0p4`; flip to `mmcblk1p4` only if both sdhci0 + sdhci1 are
|
||||
enabled. Controlled via `WOLFBOOT_LINUX_BOOTARGS_ROOT`.
|
||||
|
||||
2. **BOOT_EL1**: `BOOT_EL1?=1` in the example is currently inert on
|
||||
ZynqMP because `EL2_HYPERVISOR` is defined in `hal/zynq.h` (included
|
||||
by `src/boot_aarch64_start.S`) but not in `src/boot_aarch64.c`. wolfBoot
|
||||
stays at EL2 before jumping to Linux, which matches standard PetaLinux
|
||||
U-Boot behavior. See the comments in the template.
|
||||
|
||||
## Future work
|
||||
|
||||
- **`PROVIDES += "virtual/bootloader"`** — On vanilla Yocto where U-Boot
|
||||
is the sole bootloader, `PREFERRED_PROVIDER_virtual/bootloader = "wolfboot"`
|
||||
would be a cleaner selector than `WOLFBOOT_ENABLE = "1"` plus the
|
||||
`xilinx-bootbin` bbappend. We deliberately do not declare that PROVIDES
|
||||
yet, because on AMD/Xilinx ZynqMP — the only target this layer has been
|
||||
validated against — it does not actually simplify the integration:
|
||||
`xilinx-bootbin` references U-Boot by PN (`u-boot-xlnx`) inside its BIF
|
||||
generation rather than going through `virtual/bootloader`, and
|
||||
`IMAGE_BOOT_FILES` lists U-Boot artifacts by literal name. The current
|
||||
bbappend rewrites the BIF entry surgically; that swap would still be
|
||||
required regardless of who provides `virtual/bootloader`. The right
|
||||
time to add this PROVIDES is when a non-Xilinx BSP integration lands,
|
||||
so the contract can be tested end-to-end.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
SUMMARY = "wolfBoot signing and key generation tools (native)"
|
||||
DESCRIPTION = "Host-side keygen and sign utilities for wolfBoot secure-boot \
|
||||
image signing. Builds RSA4096 signing keys and signs firmware images with \
|
||||
SHA3-384 hashes. Uses wolfBoot's bundled wolfCrypt (under lib/wolfssl) -- \
|
||||
no external wolfSSL dependency."
|
||||
|
||||
require wolfboot.inc
|
||||
|
||||
inherit native
|
||||
|
||||
do_configure[noexec] = "1"
|
||||
|
||||
do_compile() {
|
||||
# Build the keytools (host-side signing/keygen utilities).
|
||||
oe_runmake -C tools/keytools \
|
||||
CC="${CC}" \
|
||||
LD="${CC}" \
|
||||
WOLFBOOTDIR=${S} \
|
||||
WOLFBOOT_LIB_WOLFSSL=${S}/lib/wolfssl \
|
||||
V=1
|
||||
}
|
||||
|
||||
do_install() {
|
||||
install -d ${D}${bindir}
|
||||
install -m 0755 ${S}/tools/keytools/sign ${D}${bindir}/wolfboot-sign
|
||||
install -m 0755 ${S}/tools/keytools/keygen ${D}${bindir}/wolfboot-keygen
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
SUMMARY = "wolfBoot signed kernel FIT image"
|
||||
DESCRIPTION = "Signs the kernel FIT image (fitImage / image.ub) with \
|
||||
wolfBoot RSA4096+SHA3-384 for verified secure boot. The signed output is \
|
||||
placed in DEPLOY_DIR_IMAGE as image_v<version>_signed.bin, ready for \
|
||||
flashing to the OFP_A / OFP_B partitions of an A/B-enabled SD card or QSPI."
|
||||
|
||||
HOMEPAGE = "https://github.com/wolfssl/wolfBoot"
|
||||
SECTION = "bootloaders"
|
||||
LICENSE = "GPL-3.0-only"
|
||||
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-3.0-only;md5=c79ff39f19dfec6d293b95dea7b07891"
|
||||
|
||||
inherit deploy
|
||||
|
||||
DEPENDS = "wolfboot-keytools-native"
|
||||
|
||||
# REQUIRED: absolute path to the same wolfBoot signing private key used by
|
||||
# wolfboot_git.bb to embed the public key into wolfboot.elf. User-supplied
|
||||
# out-of-band (never sourced from DEPLOY_DIR_IMAGE or sstate).
|
||||
WOLFBOOT_SIGNING_KEY ?= ""
|
||||
|
||||
# Kernel recipe name -- default matches linux-xlnx on ZynqMP / Versal.
|
||||
# Override in local.conf for non-Xilinx kernels (e.g. KERNEL_PN = "linux-yocto").
|
||||
KERNEL_PN ?= "linux-xlnx"
|
||||
|
||||
# Only depend on the kernel being deployed. The signing key is supplied
|
||||
# directly by the user, NOT pulled from wolfboot:do_deploy, so there is no
|
||||
# wolfboot dep here.
|
||||
do_compile[depends] += "${KERNEL_PN}:do_deploy"
|
||||
|
||||
do_configure[noexec] = "1"
|
||||
|
||||
# Version stamped into the signed image header. Increment for A/B updates
|
||||
# (e.g. set to "2" for the next A/B update image).
|
||||
WOLFBOOT_IMAGE_VERSION ?= "1"
|
||||
|
||||
# Name of the FIT image produced by the kernel recipe. Defaults match
|
||||
# linux-xlnx on ZynqMP / Versal.
|
||||
WOLFBOOT_FIT_IMAGE ?= "fitImage"
|
||||
|
||||
# Validate WOLFBOOT_SIGNING_KEY only when this recipe actually builds
|
||||
# (see wolfboot_git.bb for the rationale re: parse-time vs task-time).
|
||||
python check_wolfboot_signing_key() {
|
||||
key = d.getVar('WOLFBOOT_SIGNING_KEY') or ''
|
||||
if not key:
|
||||
bb.fatal("WOLFBOOT_SIGNING_KEY is not set. Generate a signing key "
|
||||
"with 'wolfboot-keygen --rsa4096 -g <path>.der' and point "
|
||||
"WOLFBOOT_SIGNING_KEY at it (absolute path). The SAME key "
|
||||
"must also be set in wolfboot_git.bb so public/private "
|
||||
"halves match. See recipes-wolfssl/wolfboot/README.md.")
|
||||
import os
|
||||
if not os.path.isfile(key):
|
||||
bb.fatal("WOLFBOOT_SIGNING_KEY='%s' does not exist or is not a "
|
||||
"regular file." % key)
|
||||
}
|
||||
do_compile[prefuncs] += "check_wolfboot_signing_key"
|
||||
|
||||
do_compile() {
|
||||
install -d ${B}
|
||||
|
||||
# Validate the upstream FIT image exists before invoking the signing
|
||||
# tool, so a missing/renamed artifact fails with an actionable message
|
||||
# instead of a cryptic wolfboot-sign error.
|
||||
fit_image="${DEPLOY_DIR_IMAGE}/${WOLFBOOT_FIT_IMAGE}"
|
||||
if [ ! -f "$fit_image" ]; then
|
||||
bbfatal "FIT image '$fit_image' not found. Check WOLFBOOT_FIT_IMAGE and that ${KERNEL_PN}:do_deploy produced it."
|
||||
fi
|
||||
|
||||
# Sign a local copy under ${B} instead of operating directly on the
|
||||
# shared DEPLOY_DIR_IMAGE. wolfboot-sign writes its output next to the
|
||||
# input; keeping both input and output under ${WORKDIR} avoids races
|
||||
# with the kernel's do_deploy (or parallel multiconfig builds) and
|
||||
# leaves publishing to do_deploy below.
|
||||
cp "$fit_image" ${B}/${WOLFBOOT_FIT_IMAGE}
|
||||
|
||||
# Sign the FIT image with RSA4096 + SHA3-384 using the user-supplied
|
||||
# signing key. wolfboot-sign emits the output NEXT TO the input file,
|
||||
# naming it <input>_v<version>_signed.bin. Run from ${B} with a
|
||||
# relative path so the output lands inside ${B} predictably.
|
||||
cd ${B}
|
||||
wolfboot-sign --rsa4096 --sha3 \
|
||||
${WOLFBOOT_FIT_IMAGE} \
|
||||
${WOLFBOOT_SIGNING_KEY} \
|
||||
${WOLFBOOT_IMAGE_VERSION}
|
||||
}
|
||||
|
||||
do_install[noexec] = "1"
|
||||
|
||||
do_deploy() {
|
||||
install -d ${DEPLOYDIR}
|
||||
install -m 0644 ${B}/${WOLFBOOT_FIT_IMAGE}_v${WOLFBOOT_IMAGE_VERSION}_signed.bin \
|
||||
${DEPLOYDIR}/image_v${WOLFBOOT_IMAGE_VERSION}_signed.bin
|
||||
}
|
||||
|
||||
addtask deploy before do_build after do_compile
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# Shared include for wolfBoot recipes
|
||||
#
|
||||
# Pulls the wolfBoot source tree and the wolfSSL submodule source side-by-side.
|
||||
# wolfBoot bundles wolfSSL under lib/wolfssl, so we stage wolfSSL there instead
|
||||
# of fetching it from the wolfBoot submodule pointer (keeps the two SRCREVs
|
||||
# explicit and greppable).
|
||||
|
||||
HOMEPAGE = "https://github.com/wolfssl/wolfBoot"
|
||||
BUGTRACKER = "https://github.com/wolfssl/wolfBoot/issues"
|
||||
SECTION = "bootloaders"
|
||||
LICENSE = "GPL-3.0-only"
|
||||
LIC_FILES_CHKSUM = "file://LICENSE;md5=1ebbd3e34237af26da5dc08a4e440464"
|
||||
|
||||
# NOTE: SRCREVs below are pinned to wolfSSL/wolfBoot master and
|
||||
# wolfSSL/wolfssl master tips at the time of writing. Bump these as
|
||||
# upstream evolves. Downstream users can override via local.conf:
|
||||
# SRCREV_wolfboot:pn-wolfboot = "<sha>"
|
||||
# SRCREV_wolfboot:pn-wolfboot-keytools-native = "<sha>"
|
||||
SRC_URI = " \
|
||||
git://github.com/wolfssl/wolfBoot.git;protocol=https;branch=master;name=wolfboot;destsuffix=git \
|
||||
git://github.com/wolfssl/wolfssl.git;protocol=https;branch=master;name=wolfssl;destsuffix=git/lib/wolfssl \
|
||||
"
|
||||
SRCREV_wolfboot ?= "25c0aaa8bd5a64f85f4d1eef9fab698275d32f20"
|
||||
SRCREV_wolfssl ?= "c4c71eece3e6eae7042a16fffdebf9bd80a4f2a1"
|
||||
SRCREV_FORMAT = "wolfboot_wolfssl"
|
||||
|
||||
S = "${WORKDIR}/git"
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
SUMMARY = "wolfBoot secure bootloader"
|
||||
DESCRIPTION = "wolfBoot is a portable, OS-agnostic secure bootloader for \
|
||||
32-bit and 64-bit targets. It provides verified secure boot with A/B \
|
||||
update / rollback support. On AMD/Xilinx ZynqMP it replaces U-Boot as \
|
||||
the second-stage bootloader (FSBL -> PMU FW -> ATF (EL3) -> wolfBoot \
|
||||
(EL2) -> signed Linux kernel)."
|
||||
|
||||
require wolfboot.inc
|
||||
|
||||
inherit deploy
|
||||
|
||||
# Which config/examples/*.config template to build against. Override in
|
||||
# local.conf / image recipe to target a different board or boot medium.
|
||||
# Examples: zynqmp_sdcard.config, zynqmp.config (QSPI), versal_sdcard.config
|
||||
WOLFBOOT_CONFIG ??= "zynqmp_sdcard.config"
|
||||
|
||||
# Linux rootfs device passed via the DTB /chosen/bootargs root= patch in
|
||||
# src/fdt.c. Override to match your partition layout. The wolfBoot ZynqMP
|
||||
# example configs hard-code a sensible default but it is far easier to
|
||||
# flip this single variable than to ship a custom .config fork.
|
||||
WOLFBOOT_LINUX_BOOTARGS_ROOT ??= ""
|
||||
|
||||
# REQUIRED: absolute path to a pre-generated wolfBoot signing private key
|
||||
# (DER format, RSA4096 by default). Generate once via
|
||||
# `wolfboot-keygen --rsa4096 -g wolfboot_signing_private_key.der` and store
|
||||
# outside the build tree (never inside DEPLOY_DIR_IMAGE or sstate).
|
||||
# See README.md for the full provisioning workflow.
|
||||
WOLFBOOT_SIGNING_KEY ?= ""
|
||||
|
||||
# OPTIONAL: absolute path to a pre-generated wolfBoot signing public key
|
||||
# (DER format). If unset, wolfBoot's keygen derives the public key from the
|
||||
# private key in-memory to populate src/keystore.c. Only set this if you
|
||||
# want to pin a public key independent of the private key file (e.g. HSM).
|
||||
WOLFBOOT_PUBLIC_KEY ?= ""
|
||||
|
||||
# keytools-native provides wolfboot-keygen (used only when deriving the
|
||||
# public half from a supplied private key) and wolfboot-sign (used by
|
||||
# wolfboot-signed-image.bb for FIT image signing).
|
||||
DEPENDS = "wolfboot-keytools-native"
|
||||
|
||||
# Additional flags passed to 'make wolfboot.elf'. These are command-line
|
||||
# overrides, so they take precedence over .config assignments. WARNING:
|
||||
# do NOT use this for CFLAGS_EXTRA — command-line CFLAGS_EXTRA+= would
|
||||
# replace ALL file-level CFLAGS_EXTRA lines, wiping out critical defines
|
||||
# like SDHCI_FORCE_CARD_DETECT. Use WOLFBOOT_EXTRA_CONFIG_LINES instead.
|
||||
WOLFBOOT_EXTRA_MAKE_FLAGS ?= ""
|
||||
|
||||
# Extra lines appended to the .config after the template is copied.
|
||||
# Use this for CFLAGS_EXTRA additions (e.g. "CFLAGS_EXTRA+=-O2") so they
|
||||
# coexist with the template's own CFLAGS_EXTRA lines.
|
||||
WOLFBOOT_EXTRA_CONFIG_LINES ?= ""
|
||||
|
||||
COMPATIBLE_MACHINE = ".*"
|
||||
PACKAGE_ARCH = "${MACHINE_ARCH}"
|
||||
|
||||
do_configure[noexec] = "1"
|
||||
|
||||
# Validate WOLFBOOT_SIGNING_KEY only when this recipe actually builds.
|
||||
# A parse-time anonymous python () {} would fail any bitbake invocation
|
||||
# that merely parses meta-wolfssl (e.g. unrelated CI running
|
||||
# `bitbake -c cleanall wolfssl`); a named prefunc runs only when
|
||||
# do_compile is scheduled.
|
||||
python check_wolfboot_signing_key() {
|
||||
import os
|
||||
key = d.getVar('WOLFBOOT_SIGNING_KEY') or ''
|
||||
if not key:
|
||||
bb.fatal("WOLFBOOT_SIGNING_KEY is not set. Generate a signing key "
|
||||
"with 'wolfboot-keygen --rsa4096 -g <path>.der' and point "
|
||||
"WOLFBOOT_SIGNING_KEY at it (absolute path). See "
|
||||
"recipes-wolfssl/wolfboot/README.md for the full workflow.")
|
||||
if not os.path.isfile(key):
|
||||
bb.fatal("WOLFBOOT_SIGNING_KEY='%s' does not exist or is not a "
|
||||
"regular file." % key)
|
||||
pub = d.getVar('WOLFBOOT_PUBLIC_KEY') or ''
|
||||
if not pub:
|
||||
bb.fatal("WOLFBOOT_PUBLIC_KEY is not set. Extract the public half "
|
||||
"from the private key with: openssl rsa -in <privkey>.der "
|
||||
"-inform DER -pubout -outform DER -out <pubkey>.der")
|
||||
if not os.path.isfile(pub):
|
||||
bb.fatal("WOLFBOOT_PUBLIC_KEY='%s' does not exist or is not a "
|
||||
"regular file." % pub)
|
||||
}
|
||||
do_compile[prefuncs] += "check_wolfboot_signing_key"
|
||||
|
||||
do_compile() {
|
||||
# Seed wolfBoot's Makefile with the requested example .config.
|
||||
if [ ! -f ${S}/config/examples/${WOLFBOOT_CONFIG} ]; then
|
||||
bbfatal "WOLFBOOT_CONFIG='${WOLFBOOT_CONFIG}' not found under ${S}/config/examples/"
|
||||
fi
|
||||
cp ${S}/config/examples/${WOLFBOOT_CONFIG} ${S}/.config
|
||||
|
||||
# Append any extra config lines (e.g. CFLAGS_EXTRA+=-O2 from a bbappend).
|
||||
if [ -n "${WOLFBOOT_EXTRA_CONFIG_LINES}" ]; then
|
||||
echo "${WOLFBOOT_EXTRA_CONFIG_LINES}" >> ${S}/.config
|
||||
fi
|
||||
|
||||
# Optionally override the Linux rootfs device in the compiled-in bootargs.
|
||||
if [ -n "${WOLFBOOT_LINUX_BOOTARGS_ROOT}" ]; then
|
||||
sed -i -e 's|-DLINUX_BOOTARGS_ROOT=\\"[^"]*\\"|-DLINUX_BOOTARGS_ROOT=\\"${WOLFBOOT_LINUX_BOOTARGS_ROOT}\\"|' \
|
||||
${S}/.config
|
||||
fi
|
||||
|
||||
# Cross-compile wolfboot.elf.
|
||||
# wolfBoot is a bare-metal bootloader (-nostdlib -ffreestanding), so we
|
||||
# use raw make (not oe_runmake) to prevent Yocto's CC/CFLAGS/LDFLAGS
|
||||
# from overriding wolfBoot's own toolchain settings. The Yocto cross
|
||||
# compiler still needs --sysroot to find headers and libgcc; we embed
|
||||
# it in CC so it applies to both compilation and linking.
|
||||
#
|
||||
# USER_PRIVATE_KEY + USER_PUBLIC_KEY tell wolfBoot's Makefile to use a
|
||||
# pre-generated key pair instead of regenerating one inside the build
|
||||
# tree (upstream contract, see wolfBoot/Makefile:371). This sidesteps
|
||||
# the fact that wolfBoot's in-tree keytools target would otherwise try
|
||||
# to cross-compile keygen for AArch64 and then run the resulting
|
||||
# AArch64 binary on the x86_64 build host -- which fails under qemu
|
||||
# inside Docker if AArch64 binfmt isn't set up.
|
||||
#
|
||||
# wolfBoot's Makefile requires both USER_PRIVATE_KEY and USER_PUBLIC_KEY.
|
||||
# Both must be supplied by the user (generate via wolfboot-keygen).
|
||||
if [ -z "${WOLFBOOT_PUBLIC_KEY}" ] || [ ! -f "${WOLFBOOT_PUBLIC_KEY}" ]; then
|
||||
bbfatal "WOLFBOOT_PUBLIC_KEY is not set or the file does not exist. " \
|
||||
"Generate both keys with 'wolfboot-keygen --rsa4096 -g <privkey>.der' " \
|
||||
"then extract the public half with 'openssl rsa -in <privkey>.der " \
|
||||
"-inform DER -pubout -outform DER -out <pubkey>.der'. Point both " \
|
||||
"WOLFBOOT_SIGNING_KEY and WOLFBOOT_PUBLIC_KEY at the respective files."
|
||||
fi
|
||||
PUBKEY_FOR_MAKE=${WOLFBOOT_PUBLIC_KEY}
|
||||
|
||||
unset CFLAGS CPPFLAGS CXXFLAGS LDFLAGS
|
||||
SYSROOT_FLAG="--sysroot=${RECIPE_SYSROOT}"
|
||||
# KEYGEN_TOOL override: wolfBoot's Makefile otherwise tries to build
|
||||
# tools/keytools/keygen using the target cross-compiler and then run
|
||||
# the resulting AArch64 binary on the x86_64 build host. Point it at
|
||||
# the native keygen from wolfboot-keytools-native instead.
|
||||
NATIVE_KEYGEN="$(command -v wolfboot-keygen)"
|
||||
make wolfboot.elf \
|
||||
CROSS_COMPILE=${TARGET_PREFIX} \
|
||||
CC="${TARGET_PREFIX}gcc $SYSROOT_FLAG" \
|
||||
LD="${TARGET_PREFIX}gcc $SYSROOT_FLAG" \
|
||||
USER_PRIVATE_KEY="${WOLFBOOT_SIGNING_KEY}" \
|
||||
USER_PUBLIC_KEY="$PUBKEY_FOR_MAKE" \
|
||||
KEYGEN_TOOL="$NATIVE_KEYGEN" \
|
||||
${WOLFBOOT_EXTRA_MAKE_FLAGS} \
|
||||
V=1
|
||||
}
|
||||
|
||||
do_install() {
|
||||
# Install wolfboot.elf into sysroot for xilinx-bootbin BIF consumption
|
||||
# (BIF_PARTITION_IMAGE[wolfboot] points at ${RECIPE_SYSROOT}/boot/wolfboot.elf).
|
||||
install -d ${D}/boot
|
||||
install -m 0644 ${S}/wolfboot.elf ${D}/boot/wolfboot.elf
|
||||
}
|
||||
|
||||
do_deploy() {
|
||||
install -d ${DEPLOYDIR}
|
||||
install -m 0644 ${S}/wolfboot.elf ${DEPLOYDIR}/wolfboot.elf
|
||||
# Optionally deploy the public signing key (safe to publish). This is
|
||||
# the verifying key embedded in wolfboot.elf; having it in DEPLOYDIR
|
||||
# lets CI and downstream tooling verify signed images without access
|
||||
# to the private key.
|
||||
if [ -n "${WOLFBOOT_PUBLIC_KEY}" ] && [ -f "${WOLFBOOT_PUBLIC_KEY}" ]; then
|
||||
install -m 0644 ${WOLFBOOT_PUBLIC_KEY} ${DEPLOYDIR}/wolfboot_signing_public_key.der
|
||||
fi
|
||||
# NOTE: the private key (wolfboot_signing_private_key.der) is
|
||||
# intentionally NOT deployed. User-supplied out-of-band via
|
||||
# WOLFBOOT_SIGNING_KEY; publishing it would defeat the point of signed
|
||||
# boot.
|
||||
}
|
||||
|
||||
addtask deploy before do_build after do_compile
|
||||
|
||||
# wolfBoot is a bare-metal bootloader -- skip QA checks that assume a
|
||||
# hosted Linux binary.
|
||||
INSANE_SKIP:${PN} = "ldflags textrel buildpaths"
|
||||
|
||||
FILES:${PN} = "/boot/wolfboot.elf"
|
||||
SYSROOT_DIRS += "/boot"
|
||||
Loading…
Reference in New Issue