Skip to content

Commit a727d86

Browse files
Merge pull request #164 from dgarske/wolfboot_bb
wolfBoot: add Yocto/OE secure bootloader recipes
2 parents 8217d90 + d592d2c commit a727d86

7 files changed

Lines changed: 554 additions & 0 deletions

File tree

conf/layer.conf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ BBFILES += "${LAYERDIR}/recipes-wolfssl/wolfssl/*.bb \
2626
${LAYERDIR}/recipes-wolfssl/wolfprovider/wolfssl*.bbappend \
2727
${LAYERDIR}/recipes-wolfssl/wolfengine/wolfengine*.bb \
2828
${LAYERDIR}/recipes-wolfssl/wolfengine/wolfssl*.bbappend \
29+
${LAYERDIR}/recipes-wolfssl/wolfboot/*.bb \
30+
${LAYERDIR}/recipes-wolfssl/wolfboot/*.bbappend \
2931
${LAYERDIR}/recipes-examples/wolfcrypt/wolfcryptbenchmark/*.bb \
3032
${LAYERDIR}/recipes-examples/wolfcrypt/wolfcryptbenchmark/*.bbappend \
3133
${LAYERDIR}/recipes-examples/wolfcrypt/wolfcrypttest/*.bb \
@@ -45,6 +47,9 @@ BBFILES += "${LAYERDIR}/recipes-wolfssl/wolfssl/*.bb \
4547
${LAYERDIR}/recipes-support/gnutls/*.bbappend \
4648
${LAYERDIR}/recipes-support/gnutls/wolfssl-gnutls-wrapper_git.bb"
4749

50+
# xilinx-bootbin bbappend requires meta-xilinx-tools layer
51+
BBFILES_DYNAMIC += "xilinx-tools:${LAYERDIR}/recipes-bsp/bootbin/*.bbappend"
52+
4853
# Uncomment if building bind with wolfSSL.
4954
#BBFILES += "${LAYERDIR}/recipes-connectivity/bind/*.bbappend"
5055

@@ -99,6 +104,7 @@ PREFERRED_PROVIDER_wolfssl-py ??= "wolfssl-py"
99104
PREFERRED_PROVIDER_wolfcrypt-py ??= "wolfcrypt-py"
100105
PREFERRED_PROVIDER_wolfprovider ??= "wolfprovider"
101106
PREFERRED_PROVIDER_wolfengine ??= "wolfengine"
107+
PREFERRED_PROVIDER_wolfboot ??= "wolfboot"
102108

103109
BBFILES += "${@bb.utils.contains('WOLFSSL_TYPE', \
104110
'fips', \
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Replace U-Boot with wolfBoot as second-stage bootloader in BOOT.BIN
2+
#
3+
# Only activates when WOLFBOOT_ENABLE = "1" is set in configuration (e.g.
4+
# local.conf or a machine .conf). Without that flag this bbappend is a
5+
# no-op and xilinx-bootbin keeps using U-Boot.
6+
#
7+
# A dedicated variable is used here instead of inspecting EXTRA_IMAGEDEPENDS:
8+
# EXTRA_IMAGEDEPENDS is frequently set inside image recipes, which are
9+
# parsed in a different context than xilinx-bootbin, so the anonymous
10+
# python() below would see an empty value and silently stay inert.
11+
#
12+
# Boot chain with wolfBoot:
13+
# FSBL -> PMU FW -> (bitstream) -> ATF (EL3) -> wolfBoot (EL2) -> signed FIT
14+
#
15+
# Default ZynqMP BIF order (machine-xilinx-zynqmp.inc):
16+
# FSBL -> PMU FW -> bitstream (optional) -> ATF -> DTB -> U-Boot
17+
#
18+
# wolfBoot BIF order:
19+
# FSBL -> PMU FW -> bitstream (optional) -> ATF -> wolfBoot
20+
# (DTB is inside the signed FIT image, loaded by wolfBoot at runtime.)
21+
22+
python () {
23+
if (d.getVar('WOLFBOOT_ENABLE') or '') != '1':
24+
# wolfBoot not requested -- this bbappend is inert.
25+
return
26+
# Apply wolfBoot BIF overrides below.
27+
d.setVar('BIF_SSBL_ATTR', 'wolfboot')
28+
d.setVarFlag('BIF_PARTITION_ATTR', 'wolfboot',
29+
'destination_cpu=a53-0,exception_level=el-2')
30+
d.setVarFlag('BIF_PARTITION_IMAGE', 'wolfboot',
31+
d.expand('${RECIPE_SYSROOT}/boot/wolfboot.elf'))
32+
d.setVar('BIF_DEVICETREE_ATTR', '')
33+
# BIF_PARTITION_IMAGE[wolfboot] points at ${RECIPE_SYSROOT}/boot/wolfboot.elf,
34+
# which is populated by do_populate_sysroot, not do_deploy. Depend on
35+
# the right task so xilinx-bootbin blocks until wolfboot.elf is staged.
36+
d.appendVar('DEPENDS', ' wolfboot')
37+
existing = d.getVarFlag('do_compile', 'depends') or ''
38+
d.setVarFlag('do_compile', 'depends', existing + ' wolfboot:do_populate_sysroot')
39+
}
40+
41+
# Only meaningful on ZynqMP (Versal has its own PLM-based flow; not handled here)
42+
COMPATIBLE_MACHINE:append = "|xilinx-zynqmp.*"

recipes-wolfssl/wolfboot/README.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# wolfBoot (Yocto/OE recipes)
2+
3+
Recipes that build [wolfBoot](https://github.com/wolfssl/wolfBoot), wolfSSL's
4+
portable secure bootloader, as part of a Yocto / OpenEmbedded image.
5+
6+
## Recipes
7+
8+
| Recipe | Purpose |
9+
|---|---|
10+
| `wolfboot-keytools-native_git.bb` | Host-side `wolfboot-keygen` and `wolfboot-sign` utilities (RSA4096 key generation, RSA4096+SHA3-384 image signing). |
11+
| `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). |
12+
| `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`. |
13+
14+
A companion `recipes-bsp/bootbin/xilinx-bootbin_%.bbappend` overrides the
15+
AMD/Xilinx `xilinx-bootbin` recipe to swap U-Boot for `wolfboot.elf` in
16+
`BOOT.BIN` on ZynqMP. It only activates when `WOLFBOOT_ENABLE = "1"` is
17+
set in configuration (`local.conf` or a machine `.conf`).
18+
19+
## Signing-key provisioning (required)
20+
21+
`wolfboot_git.bb` and `wolfboot-signed-image.bb` both require you to
22+
supply a pre-generated RSA4096 private key via `WOLFBOOT_SIGNING_KEY`.
23+
The recipes will **not** auto-generate one — that would leak the
24+
private half through sstate and DEPLOY_DIR_IMAGE.
25+
26+
Generate the key pair once on your workstation:
27+
28+
```sh
29+
# One-time host-side build of the keytools (outside Yocto):
30+
make -C /path/to/wolfBoot/tools/keytools keygen sign
31+
32+
# Generate the RSA4096 key pair:
33+
/path/to/wolfBoot/tools/keytools/keygen --rsa4096 \
34+
-g /secure/path/wolfboot_signing_private_key.der
35+
```
36+
37+
Store `wolfboot_signing_private_key.der` **outside** the build tree — a
38+
secrets manager, an encrypted volume, or at minimum a `.gitignore`d
39+
directory. Back it up: losing the private key means you can never sign
40+
another A/B update image that your deployed `wolfboot.elf` will accept.
41+
42+
## Quick start (AMD/Xilinx ZynqMP, SD-card boot)
43+
44+
Set the following in `local.conf` (or a machine `.conf`). These must live
45+
in configuration, not inside an image recipe: the `xilinx-bootbin`
46+
bbappend reads `WOLFBOOT_ENABLE` at parse time, which runs before any
47+
image recipe is evaluated.
48+
49+
```bitbake
50+
# Activate the xilinx-bootbin bbappend (swap U-Boot for wolfBoot in BOOT.BIN)
51+
WOLFBOOT_ENABLE = "1"
52+
53+
# Ensure the signed FIT image is built alongside the image.
54+
EXTRA_IMAGEDEPENDS:append = " wolfboot-signed-image"
55+
56+
# REQUIRED: absolute path to the pre-generated signing private key.
57+
WOLFBOOT_SIGNING_KEY = "/secure/path/wolfboot_signing_private_key.der"
58+
59+
# Pick a config template from wolfBoot/config/examples/
60+
WOLFBOOT_CONFIG = "zynqmp_sdcard.config"
61+
62+
# Override the Linux rootfs partition in the DTB bootargs fixup.
63+
# Default in the ZynqMP SD-card example is /dev/mmcblk0p4.
64+
WOLFBOOT_LINUX_BOOTARGS_ROOT = "/dev/mmcblk0p4"
65+
66+
# Bump to "2" for A/B update images, etc.
67+
WOLFBOOT_IMAGE_VERSION = "1"
68+
```
69+
70+
Build:
71+
72+
```
73+
bitbake <your-image>
74+
```
75+
76+
Artifacts deployed to `tmp/deploy/images/<MACHINE>/`:
77+
78+
- `BOOT.BIN` - FSBL + PMUFW + ATF + wolfBoot.elf
79+
- `wolfboot.elf` - bare-metal bootloader
80+
- `wolfboot_signing_public_key.der` - verifying key (safe to publish; only if `WOLFBOOT_PUBLIC_KEY` is set)
81+
- `image_v1_signed.bin` - signed FIT image (for OFP_A partition)
82+
83+
Note: the private signing key is **not** deployed — it stays on the
84+
workstation / secrets store you pointed `WOLFBOOT_SIGNING_KEY` at.
85+
86+
## SD card layout (wolfBoot A/B scheme)
87+
88+
| Partition | Size | Type | Contents |
89+
|---|---|---|---|
90+
| p1 | 128 MB | FAT32 | `BOOT.BIN` |
91+
| p2 | 200 MB | raw | `image_v1_signed.bin` (OFP_A = primary) |
92+
| p3 | 200 MB | raw | update slot (OFP_B) |
93+
| p4 | rest | ext4 | Linux rootfs |
94+
95+
See `wolfBoot/tools/scripts/` for helper scripts to create the SD card
96+
and flash update images (once upstreamed).
97+
98+
## Key management
99+
100+
The signing key pair is **user-supplied**, not generated inside the build.
101+
`WOLFBOOT_SIGNING_KEY` must point at a pre-generated RSA4096 private key
102+
file (DER format) — see the "Signing-key provisioning" section at the top
103+
of this README. The recipe:
104+
105+
- Stages the private key at `${S}/wolfboot_signing_private_key.der` so the
106+
wolfBoot Makefile can derive the public half and compile it into
107+
`wolfboot.elf` via `src/keystore.c`.
108+
- Does **not** deploy the private key to `DEPLOY_DIR_IMAGE`. Only
109+
`wolfboot.elf` (and optionally the public key, if `WOLFBOOT_PUBLIC_KEY`
110+
is set) is deployed.
111+
- `wolfboot-signed-image.bb` reads the same `WOLFBOOT_SIGNING_KEY`
112+
directly to sign the FIT, so there is no on-disk hand-off of the
113+
private key between recipes.
114+
115+
To rotate keys:
116+
117+
1. Generate a new RSA4096 key pair with `wolfboot-keygen --rsa4096 -g …`.
118+
Prefer a **new filename/path** for each rotation (e.g.
119+
`wolfboot_signing_private_key_v2.der`) rather than overwriting the
120+
existing `.der` in place — see the caveat below.
121+
2. Update `WOLFBOOT_SIGNING_KEY` to the new private key path.
122+
3. Build: `wolfboot.elf` embeds the new public key, and
123+
`image_v<N>_signed.bin` is signed with the new private key.
124+
4. Flash the new `BOOT.BIN` (contains new `wolfboot.elf`) to the device,
125+
followed by a signed update image signed with the new key.
126+
127+
**sstate caveat.** `WOLFBOOT_SIGNING_KEY` points at an absolute path
128+
outside the recipe tree, so BitBake does not checksum the key file's
129+
contents into the task hash — only the path string participates. If you
130+
overwrite the same `.der` file in place with new key material, sstate
131+
and stamps may reuse the previously-built `wolfboot.elf` and signed
132+
image, leaving you with a mismatched public/private pair. To stay safe:
133+
134+
- **Preferred**: rotate to a *new filename/path*. Changing the value of
135+
`WOLFBOOT_SIGNING_KEY` invalidates the task hash naturally.
136+
- **Fallback** (if you must reuse the same path): force a rebuild with
137+
`bitbake -c cleansstate wolfboot wolfboot-signed-image` before the
138+
next `bitbake <your-image>`.
139+
140+
## Swapping U-Boot for wolfBoot
141+
142+
The `recipes-bsp/bootbin/xilinx-bootbin_%.bbappend` rewrites the BIF so
143+
`BOOT.BIN` contains `wolfboot.elf` instead of `u-boot.elf` at EL2, and
144+
drops the standalone DTB partition (wolfBoot loads the DTB out of the
145+
signed FIT image). It activates only when `WOLFBOOT_ENABLE = "1"` is set
146+
in configuration (`local.conf` or a machine `.conf`). Setting it inside
147+
an image recipe is **not** sufficient: the bbappend's anonymous python
148+
runs at parse time, before image recipes are evaluated.
149+
150+
## ZynqMP caveats
151+
152+
See the comments in `wolfBoot/config/examples/zynqmp_sdcard.config` for
153+
two non-obvious points:
154+
155+
1. **Root device**: when the XSA disables `sdhci0` (common on ZCU102
156+
boards where only the external SD slot is populated), SD1 enumerates
157+
as `/dev/mmcblk0`, not `/dev/mmcblk1`. The template default is
158+
`mmcblk0p4`; flip to `mmcblk1p4` only if both sdhci0 + sdhci1 are
159+
enabled. Controlled via `WOLFBOOT_LINUX_BOOTARGS_ROOT`.
160+
161+
2. **BOOT_EL1**: `BOOT_EL1?=1` in the example is currently inert on
162+
ZynqMP because `EL2_HYPERVISOR` is defined in `hal/zynq.h` (included
163+
by `src/boot_aarch64_start.S`) but not in `src/boot_aarch64.c`. wolfBoot
164+
stays at EL2 before jumping to Linux, which matches standard PetaLinux
165+
U-Boot behavior. See the comments in the template.
166+
167+
## Future work
168+
169+
- **`PROVIDES += "virtual/bootloader"`** — On vanilla Yocto where U-Boot
170+
is the sole bootloader, `PREFERRED_PROVIDER_virtual/bootloader = "wolfboot"`
171+
would be a cleaner selector than `WOLFBOOT_ENABLE = "1"` plus the
172+
`xilinx-bootbin` bbappend. We deliberately do not declare that PROVIDES
173+
yet, because on AMD/Xilinx ZynqMP — the only target this layer has been
174+
validated against — it does not actually simplify the integration:
175+
`xilinx-bootbin` references U-Boot by PN (`u-boot-xlnx`) inside its BIF
176+
generation rather than going through `virtual/bootloader`, and
177+
`IMAGE_BOOT_FILES` lists U-Boot artifacts by literal name. The current
178+
bbappend rewrites the BIF entry surgically; that swap would still be
179+
required regardless of who provides `virtual/bootloader`. The right
180+
time to add this PROVIDES is when a non-Xilinx BSP integration lands,
181+
so the contract can be tested end-to-end.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
SUMMARY = "wolfBoot signing and key generation tools (native)"
2+
DESCRIPTION = "Host-side keygen and sign utilities for wolfBoot secure-boot \
3+
image signing. Builds RSA4096 signing keys and signs firmware images with \
4+
SHA3-384 hashes. Uses wolfBoot's bundled wolfCrypt (under lib/wolfssl) -- \
5+
no external wolfSSL dependency."
6+
7+
require wolfboot.inc
8+
9+
inherit native
10+
11+
do_configure[noexec] = "1"
12+
13+
do_compile() {
14+
# Build the keytools (host-side signing/keygen utilities).
15+
oe_runmake -C tools/keytools \
16+
CC="${CC}" \
17+
LD="${CC}" \
18+
WOLFBOOTDIR=${S} \
19+
WOLFBOOT_LIB_WOLFSSL=${S}/lib/wolfssl \
20+
V=1
21+
}
22+
23+
do_install() {
24+
install -d ${D}${bindir}
25+
install -m 0755 ${S}/tools/keytools/sign ${D}${bindir}/wolfboot-sign
26+
install -m 0755 ${S}/tools/keytools/keygen ${D}${bindir}/wolfboot-keygen
27+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
SUMMARY = "wolfBoot signed kernel FIT image"
2+
DESCRIPTION = "Signs the kernel FIT image (fitImage / image.ub) with \
3+
wolfBoot RSA4096+SHA3-384 for verified secure boot. The signed output is \
4+
placed in DEPLOY_DIR_IMAGE as image_v<version>_signed.bin, ready for \
5+
flashing to the OFP_A / OFP_B partitions of an A/B-enabled SD card or QSPI."
6+
7+
HOMEPAGE = "https://github.com/wolfssl/wolfBoot"
8+
SECTION = "bootloaders"
9+
LICENSE = "GPL-3.0-only"
10+
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-3.0-only;md5=c79ff39f19dfec6d293b95dea7b07891"
11+
12+
inherit deploy
13+
14+
DEPENDS = "wolfboot-keytools-native"
15+
16+
# REQUIRED: absolute path to the same wolfBoot signing private key used by
17+
# wolfboot_git.bb to embed the public key into wolfboot.elf. User-supplied
18+
# out-of-band (never sourced from DEPLOY_DIR_IMAGE or sstate).
19+
WOLFBOOT_SIGNING_KEY ?= ""
20+
21+
# Kernel recipe name -- default matches linux-xlnx on ZynqMP / Versal.
22+
# Override in local.conf for non-Xilinx kernels (e.g. KERNEL_PN = "linux-yocto").
23+
KERNEL_PN ?= "linux-xlnx"
24+
25+
# Only depend on the kernel being deployed. The signing key is supplied
26+
# directly by the user, NOT pulled from wolfboot:do_deploy, so there is no
27+
# wolfboot dep here.
28+
do_compile[depends] += "${KERNEL_PN}:do_deploy"
29+
30+
do_configure[noexec] = "1"
31+
32+
# Version stamped into the signed image header. Increment for A/B updates
33+
# (e.g. set to "2" for the next A/B update image).
34+
WOLFBOOT_IMAGE_VERSION ?= "1"
35+
36+
# Name of the FIT image produced by the kernel recipe. Defaults match
37+
# linux-xlnx on ZynqMP / Versal.
38+
WOLFBOOT_FIT_IMAGE ?= "fitImage"
39+
40+
# Validate WOLFBOOT_SIGNING_KEY only when this recipe actually builds
41+
# (see wolfboot_git.bb for the rationale re: parse-time vs task-time).
42+
python check_wolfboot_signing_key() {
43+
key = d.getVar('WOLFBOOT_SIGNING_KEY') or ''
44+
if not key:
45+
bb.fatal("WOLFBOOT_SIGNING_KEY is not set. Generate a signing key "
46+
"with 'wolfboot-keygen --rsa4096 -g <path>.der' and point "
47+
"WOLFBOOT_SIGNING_KEY at it (absolute path). The SAME key "
48+
"must also be set in wolfboot_git.bb so public/private "
49+
"halves match. See recipes-wolfssl/wolfboot/README.md.")
50+
import os
51+
if not os.path.isfile(key):
52+
bb.fatal("WOLFBOOT_SIGNING_KEY='%s' does not exist or is not a "
53+
"regular file." % key)
54+
}
55+
do_compile[prefuncs] += "check_wolfboot_signing_key"
56+
57+
do_compile() {
58+
install -d ${B}
59+
60+
# Validate the upstream FIT image exists before invoking the signing
61+
# tool, so a missing/renamed artifact fails with an actionable message
62+
# instead of a cryptic wolfboot-sign error.
63+
fit_image="${DEPLOY_DIR_IMAGE}/${WOLFBOOT_FIT_IMAGE}"
64+
if [ ! -f "$fit_image" ]; then
65+
bbfatal "FIT image '$fit_image' not found. Check WOLFBOOT_FIT_IMAGE and that ${KERNEL_PN}:do_deploy produced it."
66+
fi
67+
68+
# Sign a local copy under ${B} instead of operating directly on the
69+
# shared DEPLOY_DIR_IMAGE. wolfboot-sign writes its output next to the
70+
# input; keeping both input and output under ${WORKDIR} avoids races
71+
# with the kernel's do_deploy (or parallel multiconfig builds) and
72+
# leaves publishing to do_deploy below.
73+
cp "$fit_image" ${B}/${WOLFBOOT_FIT_IMAGE}
74+
75+
# Sign the FIT image with RSA4096 + SHA3-384 using the user-supplied
76+
# signing key. wolfboot-sign emits the output NEXT TO the input file,
77+
# naming it <input>_v<version>_signed.bin. Run from ${B} with a
78+
# relative path so the output lands inside ${B} predictably.
79+
cd ${B}
80+
wolfboot-sign --rsa4096 --sha3 \
81+
${WOLFBOOT_FIT_IMAGE} \
82+
${WOLFBOOT_SIGNING_KEY} \
83+
${WOLFBOOT_IMAGE_VERSION}
84+
}
85+
86+
do_install[noexec] = "1"
87+
88+
do_deploy() {
89+
install -d ${DEPLOYDIR}
90+
install -m 0644 ${B}/${WOLFBOOT_FIT_IMAGE}_v${WOLFBOOT_IMAGE_VERSION}_signed.bin \
91+
${DEPLOYDIR}/image_v${WOLFBOOT_IMAGE_VERSION}_signed.bin
92+
}
93+
94+
addtask deploy before do_build after do_compile

0 commit comments

Comments
 (0)