diff --git a/conf/layer.conf b/conf/layer.conf index d1dcbd8..f6a2e95 100644 --- a/conf/layer.conf +++ b/conf/layer.conf @@ -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', \ diff --git a/recipes-bsp/bootbin/xilinx-bootbin_%.bbappend b/recipes-bsp/bootbin/xilinx-bootbin_%.bbappend new file mode 100644 index 0000000..66ddc0f --- /dev/null +++ b/recipes-bsp/bootbin/xilinx-bootbin_%.bbappend @@ -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.*" diff --git a/recipes-wolfssl/wolfboot/README.md b/recipes-wolfssl/wolfboot/README.md new file mode 100644 index 0000000..8a489ee --- /dev/null +++ b/recipes-wolfssl/wolfboot/README.md @@ -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 +``` + +Artifacts deployed to `tmp/deploy/images//`: + +- `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_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 `. + +## 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. diff --git a/recipes-wolfssl/wolfboot/wolfboot-keytools-native_git.bb b/recipes-wolfssl/wolfboot/wolfboot-keytools-native_git.bb new file mode 100644 index 0000000..12032f0 --- /dev/null +++ b/recipes-wolfssl/wolfboot/wolfboot-keytools-native_git.bb @@ -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 +} diff --git a/recipes-wolfssl/wolfboot/wolfboot-signed-image.bb b/recipes-wolfssl/wolfboot/wolfboot-signed-image.bb new file mode 100644 index 0000000..21209db --- /dev/null +++ b/recipes-wolfssl/wolfboot/wolfboot-signed-image.bb @@ -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_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 .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 _v_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 diff --git a/recipes-wolfssl/wolfboot/wolfboot.inc b/recipes-wolfssl/wolfboot/wolfboot.inc new file mode 100644 index 0000000..9133704 --- /dev/null +++ b/recipes-wolfssl/wolfboot/wolfboot.inc @@ -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 = "" +# SRCREV_wolfboot:pn-wolfboot-keytools-native = "" +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 ?= "9a667f2a7527da2b8e490ae7923665321af2d3ac" +SRCREV_wolfssl ?= "1d363f3adceba9d1478230ede476a37b0dcdef24" +SRCREV_FORMAT = "wolfboot_wolfssl" + +S = "${WORKDIR}/git" diff --git a/recipes-wolfssl/wolfboot/wolfboot_git.bb b/recipes-wolfssl/wolfboot/wolfboot_git.bb new file mode 100644 index 0000000..210d91a --- /dev/null +++ b/recipes-wolfssl/wolfboot/wolfboot_git.bb @@ -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 .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 .der " + "-inform DER -pubout -outform DER -out .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 .der' " \ + "then extract the public half with 'openssl rsa -in .der " \ + "-inform DER -pubout -outform DER -out .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"