|
| 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. |
0 commit comments