From a480ede91b6356fe62d6f060eba997cb3d510bde Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 9 Jul 2026 11:32:12 -0700 Subject: [PATCH 1/3] Add aarch64_efi target: wolfBoot as an AArch64 UEFI application AArch64 UEFI-application target (sibling of x86_64_efi): builds wolfboot.efi, verifies the next-stage image with wolfCrypt, and boots via UEFI LoadImage/StartImage. Validated on the Jetson Orin Nano through to the Ubuntu login. Includes gnu-efi build and QEMU scripts, CI, a UEFI Secure Boot signing tool (sign-efi-secureboot.sh) with enrollment/fuse docs, and FIPS-approved algorithm guidance (docs/FIPS.md). --- .github/workflows/test-build.yml | 7 + .github/workflows/test-configs.yml | 8 + .gitignore | 9 + Makefile | 9 +- arch.mk | 60 +++- config/examples/aarch64_efi.config | 29 ++ docs/FIPS.md | 52 ++++ docs/README.md | 1 + docs/Targets.md | 168 +++++++++++ hal/aarch64_efi.c | 382 +++++++++++++++++++++++++ hal/aarch64_efi.ld | 3 + src/boot_aarch64_efi.c | 49 ++++ tools/keytools/keygen.c | 2 +- tools/scripts/aarch64-efi-qemu.sh | 45 +++ tools/scripts/build-gnu-efi-aarch64.sh | 44 +++ tools/scripts/sign-efi-secureboot.sh | 104 +++++++ 16 files changed, 968 insertions(+), 4 deletions(-) create mode 100644 config/examples/aarch64_efi.config create mode 100644 docs/FIPS.md create mode 100644 hal/aarch64_efi.c create mode 100644 hal/aarch64_efi.ld create mode 100644 src/boot_aarch64_efi.c create mode 100755 tools/scripts/aarch64-efi-qemu.sh create mode 100755 tools/scripts/build-gnu-efi-aarch64.sh create mode 100755 tools/scripts/sign-efi-secureboot.sh diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 3325566d6a..9b0d078e5c 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -13,6 +13,9 @@ on: make-args: required: false type: string + prep-run: + required: false + type: string jobs: @@ -42,6 +45,10 @@ jobs: run: | make -C tools/keytools && make -C tools/bin-assemble + - name: Pre-build setup + if: inputs.prep-run != '' + run: ${{inputs.prep-run}} + - name: Build wolfboot run: | make ${{inputs.make-args}} diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 54c2309046..7a978caee5 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -738,6 +738,14 @@ jobs: arch: host config-file: ./config/examples/x86_64_efi.config + aarch64_efi_test: + uses: ./.github/workflows/test-build.yml + with: + arch: aarch64 + config-file: ./config/examples/aarch64_efi.config + # Build gnu-efi for aarch64 (pinned in the script) before wolfboot.efi. + prep-run: ./tools/scripts/build-gnu-efi-aarch64.sh + zynqmp_test: uses: ./.github/workflows/test-build-aarch64.yml with: diff --git a/.gitignore b/.gitignore index b1bc7fb6ad..0d4ae0dec9 100644 --- a/.gitignore +++ b/.gitignore @@ -421,3 +421,12 @@ sdcard.img # wolfHSM STM32H5 TZ demo build output port/stmicro/stm32h5-tz-wolfhsm/out/ + +# gnu-efi built for the AArch64 UEFI target and its cloned source +tools/gnu-efi-aarch64/ +tools/gnu-efi-src/ +# aarch64_efi working/staging dirs (build artifacts, not tracked) +aarch64_efi-stage/ +tools/qemu-esp/ +# UEFI Secure Boot keys/certs generated by tools/scripts/sign-efi-secureboot.sh +tools/efi-secureboot-keys/ diff --git a/Makefile b/Makefile index 2051b7900a..22cce80a9f 100644 --- a/Makefile +++ b/Makefile @@ -265,6 +265,9 @@ SIGN_ENV=IMAGE_HEADER_SIZE=$(IMAGE_HEADER_SIZE) \ MAIN_TARGET=factory.bin +# PE/COFF output format for the wolfboot.efi objcopy rule. Overridden per +# target in arch.mk (e.g. pei-aarch64-little for aarch64_efi). +EFI_OBJCOPY_TARGET?=pei-x86-64 TARGET_H_TEMPLATE:=include/target.h.in ifeq ($(TZEN),1) @@ -293,6 +296,10 @@ ifeq ($(TARGET),x86_64_efi) MAIN_TARGET:=wolfboot.efi endif +ifeq ($(TARGET),aarch64_efi) + MAIN_TARGET:=wolfboot.efi +endif + ifeq ($(FSP), 1) MAIN_TARGET:=wolfboot_stage1.bin endif @@ -379,7 +386,7 @@ wolfboot.efi: wolfboot.elf $(Q)$(OBJCOPY) -j .rodata -j .text -j .sdata -j .data \ -j .dynamic -j .dynsym -j .rel \ -j .rela -j .reloc -j .eh_frame \ - -O pei-x86-64 --subsystem=10 $^ $@ + -O $(EFI_OBJCOPY_TARGET) --subsystem=10 $^ $@ @echo @echo "\t[SIZE]" $(Q)$(SIZE) wolfboot.efi diff --git a/arch.mk b/arch.mk index beeffa4122..4e69a3e6a4 100644 --- a/arch.mk +++ b/arch.mk @@ -63,9 +63,20 @@ endif ## ARM Cortex-A ifeq ($(ARCH),AARCH64) - CROSS_COMPILE?=aarch64-none-elf- + ifeq ($(TARGET),aarch64_efi) + # UEFI app: Linux GNU toolchain (freestanding EFI ABI), not aarch64-none-elf- + CROSS_COMPILE?=aarch64-linux-gnu- + else + CROSS_COMPILE?=aarch64-none-elf- + endif CFLAGS+=-DARCH_AARCH64 -DFAST_MEMCPY - OBJS+=src/boot_aarch64.o src/boot_aarch64_start.o + ifeq ($(TARGET),aarch64_efi) + # UEFI app: gnu-efi CRT0 is the entry; do_boot is in boot_aarch64_efi.o. + # Skip the bare-metal reset (boot_aarch64_start.S) and EL2/GIC glue. + OBJS+=src/boot_aarch64_efi.o + else + OBJS+=src/boot_aarch64.o src/boot_aarch64_start.o + endif ifeq ($(TARGET),zynq) ARCH_FLAGS=-march=armv8-a+crypto @@ -1664,6 +1675,37 @@ ifeq ($(TARGET),x86_64_efi) UPDATE_OBJS:=src/update_ram.o endif +ifeq ($(TARGET),aarch64_efi) + # Generic AArch64 UEFI application (validated on NVIDIA Jetson Orin Nano). + # Build gnu-efi for aarch64 first: ./tools/scripts/build-gnu-efi-aarch64.sh + # (override the install path with GNU_EFI_PATH=... if needed). + USE_GCC_HEADLESS=0 + GNU_EFI_PATH?=tools/gnu-efi-aarch64 + GNU_EFI_LIB_PATH?=$(GNU_EFI_PATH)/lib + GNU_EFI_INC_PATH?=$(GNU_EFI_PATH)/include + GNU_EFI_CRT0=$(GNU_EFI_LIB_PATH)/crt0-efi-aarch64.o + GNU_EFI_LSCRIPT=$(GNU_EFI_LIB_PATH)/elf_aarch64_efi.lds + CFLAGS += -fpic -ffreestanding -fno-stack-protector -fno-stack-check \ + -fshort-wchar -mstrict-align + CFLAGS += -I$(GNU_EFI_INC_PATH) -I$(GNU_EFI_INC_PATH)/efi \ + -I$(GNU_EFI_INC_PATH)/efi/aarch64 \ + -DTARGET_aarch64_efi -DWOLFBOOT_DUALBOOT + # avoid using of fixed LOAD_ADDRESS, uefi target uses dynamic location + CFLAGS += -DWOLFBOOT_NO_LOAD_ADDRESS + # AArch64 PE/COFF output format for objcopy (see the wolfboot.efi rule). + # This binutils exposes it as pei-aarch64-little (not efi-app-aarch64). + EFI_OBJCOPY_TARGET=pei-aarch64-little + # --allow-multiple-definition: gnu-efi's libefi init.o (pulled in for + # InitializeLib) also defines memset/memcpy; wolfBoot's src/string.o comes + # first in link order and wins. + LDFLAGS = -shared -Bsymbolic --allow-multiple-definition \ + -L$(GNU_EFI_LIB_PATH) -T$(GNU_EFI_LSCRIPT) + LD_START_GROUP = $(GNU_EFI_CRT0) + LD_END_GROUP = -lgnuefi -lefi + LD = $(CROSS_COMPILE)ld + UPDATE_OBJS:=src/update_ram.o +endif + ifeq ($(ARCH),sim) USE_GCC_HEADLESS=0 LD = gcc @@ -1914,6 +1956,19 @@ endif ## Update mechanism ifeq ($(ARCH),AARCH64) +ifeq ($(TARGET),aarch64_efi) + # UEFI app: UEFI owns MMU/FDT, so skip the -DMMU/-DWOLFBOOT_FDT DTS path and + # fdt.o/gpt.o (like x86_64_efi). update_ram.o is set in the block above. + # DEBUG=1: route wolfBoot_printf to the UEFI console (gnu-efi Print). + ifeq ($(DEBUG),1) + CFLAGS += -DWOLFBOOT_DEBUG_EFI=1 + endif + # Drop -Werror for this target: WOLFBOOT_DEBUG_EFI pulls gnu-efi headers into + # every TU and efidebug.h redefines the -DDEBUG object macro as a function + # macro. That cpp macro-redefinition warning has no -W name, so it can't be + # scoped with -Wno-error=; our own sources are kept warning-clean. + CFLAGS := $(filter-out -Werror,$(CFLAGS)) +else CFLAGS+=-DMMU -DWOLFBOOT_FDT -DWOLFBOOT_DUALBOOT OBJS+=src/fdt.o # src/gpt.c provides the CRC32 helpers reused by update_ram.c's uImage @@ -1934,6 +1989,7 @@ ifeq ($(ARCH),AARCH64) # RAM-based boot from external flash (default) UPDATE_OBJS:=src/update_ram.o endif +endif else ifeq ($(DUALBANK_SWAP),1) CFLAGS+=-DWOLFBOOT_DUALBOOT diff --git a/config/examples/aarch64_efi.config b/config/examples/aarch64_efi.config new file mode 100644 index 0000000000..53f422e751 --- /dev/null +++ b/config/examples/aarch64_efi.config @@ -0,0 +1,29 @@ +# AArch64 UEFI application - wolfBoot as a UEFI app (the AArch64 sibling of the +# x86_64_efi target). Validated on the NVIDIA Jetson Orin Nano (Tegra234). +# +# wolfBoot builds as an AArch64 UEFI application (wolfboot.efi) launched by the +# platform UEFI firmware. It reads kernel.img/update.img from the EFI Simple +# File System, verifies them, and boots via UEFI LoadImage/StartImage. +# Modeled on config/examples/x86_64_efi.config. +# +# Prerequisite: build gnu-efi for AArch64 first (one-time): +# ./tools/scripts/build-gnu-efi-aarch64.sh +# then: cp config/examples/aarch64_efi.config .config && make +# +# Crypto starts on ED25519/SHA256 (matches the proven x86 EFI target) to +# de-risk the first build. Switch to ECC384/SHA384 once the port builds and +# runs: set SIGN?=ECC384, HASH?=SHA384, SPMATH=1 (pulls in SP math objects). +# +# For a FIPS 140-3 deployment use FIPS-approved algorithms: SIGN?=ECC384, +# HASH?=SHA384, SPMATH=1 (ED25519 is NOT FIPS approved). See docs/FIPS.md for +# the approved-algorithm vs. validated-module distinction and build steps. +ARCH=AARCH64 +TARGET=aarch64_efi +WOLFBOOT_SMALL_STACK=1 +SIGN?=ED25519 +HASH?=SHA256 +DEBUG=1 +SPMATH=0 +# required for keytools +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_NO_PARTITIONS=1 diff --git a/docs/FIPS.md b/docs/FIPS.md new file mode 100644 index 0000000000..b8d1be5a0e --- /dev/null +++ b/docs/FIPS.md @@ -0,0 +1,52 @@ +# FIPS 140-3 and wolfBoot + +This page explains what "FIPS-ready" means for wolfBoot, how to build a wolfBoot image that uses only FIPS 140-3 approved algorithms, and what a fully CMVP-validated configuration additionally requires. Read it before making any FIPS claim about a wolfBoot deployment. + +## Two distinct things: approved algorithms vs. a validated module + +FIPS 140-3 compliance has two separate requirements that are easy to conflate: + +1. Using FIPS **approved algorithms** for the security-relevant operations (here: image signature verification and hashing). +2. Performing those operations inside the **CMVP-validated wolfCrypt cryptographic module** boundary, which includes the power-on self test (POST), the in-core integrity check, and the module status gating (`wolfCrypt_GetStatus_fips`). + +wolfBoot addresses (1) with configuration. wolfBoot does **not**, by default, satisfy (2): the build compiles individual wolfCrypt source files (`lib/wolfssl/wolfcrypt/src/*.c`) selected by the chosen `SIGN`/`HASH` options, rather than linking the validated wolfCrypt FIPS module container. There is no POST or in-core integrity check in a stock wolfBoot build. This is why the accurate phrasing is "FIPS-ready through wolfCrypt" (approved algorithms, from a codebase with FIPS validations) rather than "FIPS validated". + +## Approved algorithms for image authentication + +wolfBoot authenticates images with a public-key signature over a hash of the image. For a FIPS deployment select an approved pair. The default wolfBoot signature algorithm, **ED25519, is NOT FIPS approved** and must not be used for a FIPS configuration. + +Approved options already wired into `options.mk`: + +- Signature (`SIGN=`): `ECC256` / `ECC384` / `ECC521` (ECDSA P-256/P-384/P-521), or `RSAPSS2048` / `RSAPSS3072` / `RSAPSS4096` (RSA-PSS). +- Hash (`HASH=`): `SHA256` or `SHA384` (SHA-2). Match or exceed the signature strength (e.g. P-384 with SHA-384). + +Not approved for signing: `ED25519`, `ED448`. (The post-quantum options `LMS`/`XMSS`/`ML-DSA` are governed by separate NIST standards and are out of scope here.) + +## Building a FIPS-ready AArch64 UEFI image + +Select ECDSA P-384 + SHA-384 in the `aarch64_efi` config (edit `config/examples/aarch64_efi.config`: set `SIGN=ECC384`, `HASH=SHA384`, `SPMATH=1`), then build: + +``` +./tools/scripts/build-gnu-efi-aarch64.sh # one-time: AArch64 gnu-efi +cp config/examples/aarch64_efi.config .config # edit: SIGN=ECC384 HASH=SHA384 SPMATH=1 +make keysclean # if switching from another algorithm +make # -> wolfboot.efi (ECC384/SHA384) +``` + +The same algorithm selection applies to any other target: set `SIGN=ECC384 HASH=SHA384` (and `SPMATH=1` for the single-precision math backend the wolfCrypt FIPS module is typically validated with) in that target's config. + +Sign a payload with the matching algorithm, for example: + +``` +./tools/keytools/sign --ecc384 --sha384 kernel.img wolfboot_signing_private_key.der 1 +``` + +## What a fully validated build additionally requires (scope, not yet implemented) + +To move from "approved algorithms" to an actual FIPS 140-3 validated configuration, the crypto must run inside the validated module boundary. That means, in addition to the algorithm selection above: + +- Link the certified wolfCrypt FIPS module bundle (the validated `fips.c` plus the exact validated wolfCrypt sources at the validated revision) instead of cherry-picking `.c` files, so the module boundary and its integrity hash are preserved. +- Run the module POST and in-core integrity check before the first cryptographic operation, and gate verification on `wolfCrypt_GetStatus_fips() == 0`. +- Constrain the build to the validated algorithm set and validated module version. + +This is a wolfCrypt-integration effort (it depends on a wolfSSL FIPS bundle, not a wolfBoot-only change) and is not part of the stock wolfBoot build today. Contact wolfSSL (facts@wolfssl.com) for the current wolfCrypt FIPS 140-3 certificate, validated module version, and integration guidance for a specific target. diff --git a/docs/README.md b/docs/README.md index d86720d9c7..a2ca3ed99a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,6 +11,7 @@ See also: [wolfBoot Product Overview](https://www.wolfssl.com/products/wolfboot/ - [**encrypted_partitions.md**](./encrypted_partitions.md) - Creating and managing encrypted firmware/data partitions. - [**firmware_image.md**](./firmware_image.md) - wolfBoot firmware image format, layout, and metadata. - [**firmware_update.md**](./firmware_update.md) - Update flow: slots, verification, rollback, and recovery. +- [**FIPS.md**](./FIPS.md) - FIPS 140-3: approved algorithm selection vs. a validated module, and how to build a FIPS-ready image. - [**flash-OTP.md**](./flash-OTP.md) - Using One-Time Programmable (OTP) regions in flash for secure data. - [**flash_partitions.md**](./flash_partitions.md) - Flash partitioning schemes and configuration guidance. - [**HAL.md**](./HAL.md) - Hardware Abstraction Layer notes and porting considerations. diff --git a/docs/Targets.md b/docs/Targets.md index 0f2cbb352d..af847aa0fa 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -33,6 +33,7 @@ This README describes configuration of supported targets. * [NXP T10xx PPC (T1024 / T1040)](#nxp-qoriq-t10xx-ppc-t1024--t1040) * [NXP T2080 PPC](#nxp-qoriq-t2080-ppc) * [Qemu x86-64 UEFI](#qemu-x86-64-uefi) +* [NVIDIA Jetson Orin (aarch64_efi)](#nvidia-jetson-orin-aarch64_efi) * [Raspberry Pi pico 2 (rp2350)](#raspberry-pi-pico-rp2350) * [Renesas RA6M4](#renesas-ra6m4) * [Renesas RX65N](#renesas-rx65n) @@ -7785,6 +7786,173 @@ You can `Ctrl-C` or login as `root` and power off qemu with `poweroff` +## NVIDIA Jetson Orin (aarch64_efi) + +The `aarch64_efi` target builds wolfBoot as an AArch64 UEFI application (`wolfboot.efi`), the direct counterpart of the [Qemu x86-64 UEFI](#qemu-x86-64-uefi) target. It uses only UEFI Boot Services (no SoC-specific registers), so the same binary runs on any AArch64 UEFI platform. It has been validated on the NVIDIA Jetson Orin Nano Developer Kit (Tegra234), where the on-module UEFI firmware (edk2-nvidia) launches it after the NVIDIA-signed early boot chain (BootROM -> MB1 -> MB2 -> UEFI). wolfBoot reads the next-stage image from the EFI Simple File System, authenticates it with wolfCrypt, and boots it via the UEFI `LoadImage`/`StartImage` services (an AArch64 Linux `Image` is itself a PE/COFF EFI-stub application). + +### Prerequisites + + * An AArch64 GNU toolchain (`aarch64-linux-gnu-gcc`) + * gnu-efi built for AArch64 (the host distro package is usually x86-only, so build it with the helper script below) + * For emulation: `qemu-system-aarch64` plus the AArch64 UEFI firmware (AAVMF, package `qemu-efi-aarch64`) + +On a debian-like system: + +``` +apt install git make gcc-aarch64-linux-gnu dosfstools mtools +apt install qemu-system-arm qemu-efi-aarch64 # emulation (optional) +``` + +Build the AArch64 gnu-efi runtime once (installs into `tools/gnu-efi-aarch64/`): + +``` +./tools/scripts/build-gnu-efi-aarch64.sh +``` + +### Configuration + +An example configuration is provided in [config/examples/aarch64_efi.config](config/examples/aarch64_efi.config). It selects `ARCH=AARCH64`, `TARGET=aarch64_efi`, and a signature/hash algorithm (ED25519/SHA256 by default). No partition addresses are required -- UEFI provides the storage and dynamic image placement. + +### Building + +``` +cp config/examples/aarch64_efi.config .config +make +``` + +This produces `wolfboot.efi`, a PE32+ AArch64 EFI application (objcopy output format `pei-aarch64-little`). + +### Signing a payload + +Sign the image to boot (an AArch64 Linux `Image`, or any EFI application for testing) with the generated key, tagging it with a version. wolfBoot looks for `kernel.img` and `update.img` on the volume it was launched from and boots the higher valid version: + +``` +./tools/keytools/sign --ed25519 --sha256 Image wolfboot_signing_private_key.der 1 +cp Image_v1_signed.bin kernel.img +``` + +### Running in QEMU + +``` +./tools/scripts/aarch64-efi-qemu.sh # add --gdb to debug with gdb-multiarch +``` + +The script exposes a scratch directory to the UEFI firmware as a FAT ESP, copies `wolfboot.efi` (and `kernel.img` from `aarch64_efi-stage/` if present), and auto-runs it. + +### Deploying on the Jetson Orin Nano + +The Jetson UEFI auto-boots removable media via `\EFI\BOOT\BOOTAA64.EFI`. Place wolfBoot and a signed payload on a FAT32 partition: + +``` +\EFI\BOOT\BOOTAA64.EFI <- wolfboot.efi +\kernel.img <- signed payload (read from the volume root) +\cmdline.txt <- optional Linux kernel command line (see below) +``` + +Insert the card and power on; UEFI auto-launches wolfBoot, which verifies and boots the payload. The debug console on the Orin Nano Developer Kit is the J14 button header (not the 40-pin), 115200 8N1. Example output: + +``` +Image base: 0x25E5D4000 +Opening file: kernel.img, size: 57969 +Checking integrity...done +Verifying signature...done +Successfully selected image in part: 0 +Firmware Valid +Booting at 0x5E254100 +Staging kernel at address 5E254100, size: 57969 +``` + +### Booting Linux + +An AArch64 Linux `Image` carries a PE/COFF EFI stub, so wolfBoot boots it with the same `LoadImage`/`StartImage` path used above -- no initrd or bare-metal handoff is needed when the kernel has built-in MMC/ext4 drivers (the NVIDIA L4T kernel does). This has been validated end to end on the Jetson Orin Nano: wolfBoot verifies the signed kernel and boots NVIDIA Jetson Linux (L4T R36.4.4, `5.15.148-tegra`) all the way to an Ubuntu 22.04 login prompt. + +Obtain a Tegra234-compatible kernel and root filesystem from the [NVIDIA Jetson Linux (L4T)](https://developer.nvidia.com/embedded/jetson-linux) BSP. The driver package (`Jetson_Linux_R36.x.x_aarch64.tbz2`) contains `Linux_for_Tegra/kernel/Image` and `Linux_for_Tegra/kernel/dtb/tegra234-*.dtb`; the matching `Tegra_Linux_Sample-Root-Filesystem_*.tbz2` provides the rootfs. + +Sign the kernel `Image` with the wolfBoot key and name it `kernel.img`: + +``` +./tools/keytools/sign --ed25519 --sha256 Linux_for_Tegra/kernel/Image \ + wolfboot_signing_private_key.der 1 +cp Linux_for_Tegra/kernel/Image_v1_signed.bin kernel.img +``` + +Lay out the microSD as a FAT ESP plus a rootfs partition and place: + +``` +FAT (p1): \EFI\BOOT\BOOTAA64.EFI <- wolfboot.efi (UEFI auto-boots this) + \kernel.img <- signed L4T kernel + \cmdline.txt <- kernel command line (below) +ext4 (p2): the L4T sample root filesystem +``` + +`cmdline.txt` (read by wolfBoot and passed to the kernel via `LoadOptions`): + +``` +root=/dev/mmcblk0p2 rw rootwait console=ttyTCU0,115200 +``` + +On power-up the Jetson UEFI auto-boots `\EFI\BOOT\BOOTAA64.EFI`; wolfBoot verifies `kernel.img` and hands off to the kernel, which receives the real Tegra234 device tree from the UEFI configuration table (`EFI stub: Using DTB from configuration table`), mounts `mmcblk0p2`, and brings up systemd and the login on the J14 debug console (`ttyTCU0`). + +Security note: the plaintext `\cmdline.txt` is not covered by the image signature. For a production trust chain the command line should be authenticated -- baked into the signed image, or supplied via the device-tree `/chosen` bootargs. An initramfs-based flow (rather than a direct `root=` mount) would additionally need initrd support via the `LINUX_EFI_INITRD_MEDIA_GUID` LoadFile2 protocol, which this target does not currently implement. + +For a FIPS 140-3 deployment, set FIPS-approved algorithms in your `.config` (`SIGN=ECC384`, `HASH=SHA384`, `SPMATH=1`) instead of the default ED25519/SHA256, and sign with `--ecc384 --sha384`. See [FIPS.md](FIPS.md) for the approved-algorithm-vs-validated-module distinction. + +### Root of trust: enrolling wolfBoot into UEFI Secure Boot + +The steps above give wolfBoot verifying the kernel. To close the remaining gap -- the firmware verifying `wolfboot.efi` itself -- enroll wolfBoot into UEFI Secure Boot so the platform refuses to launch an unsigned or tampered `wolfboot.efi`. This makes the chain continuous: firmware -> `wolfboot.efi` -> kernel. + +`tools/scripts/sign-efi-secureboot.sh` generates a Platform Key (PK), Key Exchange Key (KEK) and signature-database (db) key/cert, signs `wolfboot.efi` with the db key, and emits the signed variable updates (`.auth`) for enrollment: + +``` +./tools/scripts/sign-efi-secureboot.sh wolfboot.efi +``` + +This produces `wolfboot.efi.signed` and, under `tools/efi-secureboot-keys/`, the `PK`/`KEK`/`db` key and cert plus the signed variable updates (`db.auth`, `KEK.auth`, `PK.auth`). (It requires `sbsigntool`, `efitools`, and `openssl`; `sbsign` may print benign "gaps between PE/COFF sections" warnings for gnu-efi images -- signing and `sbverify` still pass.) + +The Jetson edk2 UEFI Shell `setvar` cannot enroll authenticated variables (it has no file input and cannot set the time-based-authenticated attribute), so enroll from the UEFI **setup menu**, which reads the certificate files from the ESP. The menu's "Enroll ... Using File" expects DER-encoded certificates; convert the generated PEM certs once: + +``` +cd tools/efi-secureboot-keys +for k in PK KEK db; do openssl x509 -in $k.crt -outform DER -out $k.cer; done +``` + +Copy `db.cer`, `KEK.cer`, `PK.cer` and `wolfboot.efi.signed` onto the ESP (deploy the signed image as `\EFI\BOOT\BOOTAA64.EFI`). Name the cert files with a **lowercase** `.cer` extension: the edk2 file explorer decides a file is a certificate by a case-sensitive suffix match against `.cer`/`.der`/`.crt`, so an uppercase `DB.CER` (as FAT stores a plain 8.3 name) is rejected with "Unsupported file type!". Copy them with lowercase names (e.g. `mcopy db.cer ::/db.cer`) so the VFAT lowercase flag is set. Then, on the board (over the serial console -- use `minicom` or `screen`, not a plain pass-through, so the full-screen menu renders): + +1. Reset and press `ESC` at the firmware banner (`ESC to enter Setup`) to enter Setup. +2. `Device Manager` -> `Secure Boot Configuration`. +3. Set `Secure Boot Mode` -> `Custom Mode` (reveals `Custom Secure Boot Options`). +4. Under `Custom Secure Boot Options`, enroll in order -- `db`, then `KEK`, then `PK` (enrolling PK is what turns Secure Boot enforcing and exits Setup Mode): + - `DB Options` -> `Enroll Signature` -> `Enroll Signature Using File` -> select `db.cer` -> accept the default signature-owner GUID -> `Commit Changes and Exit`. + - `KEK Options` -> `Enroll KEK` -> `Enroll KEK Using File` -> `KEK.cer` -> `Commit Changes and Exit`. + - `PK Options` -> `Enroll PK` -> `Enroll PK Using File` -> `PK.cer` -> `Commit Changes and Exit`. +5. Confirm `Current Secure Boot State: Enabled`, then reset. + +On reboot the firmware verifies `\EFI\BOOT\BOOTAA64.EFI` against `db`, so the signed wolfBoot launches (`Verifying signature...done`). To prove enforcement, replace `BOOTAA64.EFI` with an unsigned `wolfboot.efi`; the firmware refuses it with a Security Violation. Secure Boot is fully reversible: in the same menu set `Secure Boot Mode` -> `Standard Mode`, or delete the PK, to return the board to Setup Mode. + +Important -- sign the kernel for Secure Boot too. wolfBoot boots the kernel through the UEFI `LoadImage`/`StartImage` services, and with Secure Boot enforcing, `LoadImage` also verifies the kernel image against `db`. A kernel that is only wolfBoot-signed is rejected by `LoadImage` (`can't load kernel image from memory`), even though wolfBoot's own `Verifying signature...done` passed. Sign the raw `Image` with the db key (sbsign) first, then wolfBoot-sign the result, so the kernel is trusted by both UEFI (`db`, for `LoadImage`) and wolfBoot (wolfCrypt): + +``` +sbsign --key tools/efi-secureboot-keys/db.key --cert tools/efi-secureboot-keys/db.crt \ + --output Image.sb Linux_for_Tegra/kernel/Image +./tools/keytools/sign --ed25519 --sha256 Image.sb wolfboot_signing_private_key.der 1 +cp Image_v1_signed.bin kernel.img # note: sign drops the .sb, output is Image_v1_signed.bin +``` + +The full enforced chain is then firmware -> (Secure Boot) -> wolfBoot -> (wolfCrypt + Secure Boot) -> kernel -> Linux. + +Test this without hardware first: the QEMU + AAVMF helper (`tools/scripts/aarch64-efi-qemu.sh`) uses OVMF/AAVMF variable storage that supports enrolling the same keys, so you can confirm the signed binary launches and a wrong-key binary is refused before touching the board. + +Note on the Jetson dev kit: the OP-TEE console prints "Test UEFI variable auth key is being used" / "UEFI variable protection is not fully enabled", i.e. the variable store is in a development state (`SetupMode=1`), which is why menu enrollment works without a prior platform key. A production device would additionally fuse-lock the variable store (see below). + +### Root of trust: NVIDIA fuse provisioning (production; not performed here) + +UEFI Secure Boot above is enforced by the edk2 firmware. On a production Jetson the firmware chain (BootROM -> MB1 -> MB2 -> cpu-bootloader/UEFI) is itself made tamper-resistant by burning the NVIDIA security fuses from the L4T BSP: + +- PKC (public-key crypto) fuses: burn the SHA of your signing public key so the BootROM only accepts an NVIDIA-signing-chain that you control. +- SBK (secure boot key) fuses: optionally encrypt the boot binaries. + +These are burned with `odmfuse.sh`/`tegrasign` from `Linux_for_Tegra/` and are irreversible. They are the final production step and are intentionally NOT part of this port -- the development board stays in unfused/dev mode. Consult the NVIDIA Jetson Linux "Secure Boot" documentation for the current `odmfuse.sh` procedure for your module before burning anything. Once fused, the fused firmware enforces UEFI Secure Boot, which enforces `wolfboot.efi`, which enforces the kernel -- a complete hardware root of trust. + ## Intel x86_64 with Intel FSP support This setup is more complex than the UEFI approach described earlier, but allows diff --git a/hal/aarch64_efi.c b/hal/aarch64_efi.c new file mode 100644 index 0000000000..33e4680338 --- /dev/null +++ b/hal/aarch64_efi.c @@ -0,0 +1,382 @@ +/* aarch64_efi.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Generic AArch64 UEFI-application HAL (validated on NVIDIA Jetson Orin Nano, + * Tegra234 / Cortex-A78AE). + * + * wolfBoot runs here as an AArch64 UEFI application (wolfboot.efi), launched + * by the platform UEFI firmware. It reads the kernel image from the EFI + * Simple File System, verifies it with wolfCrypt, and hands off to it via + * the UEFI LoadImage/StartImage services (the AArch64 Linux Image is itself + * a PE/COFF EFI-stub application). UEFI owns DRAM/MMU/GIC state, so this HAL + * needs no bare-metal init; the flash ops are stubs and storage/console come + * from UEFI Boot Services. This is the AArch64 sibling of hal/x86_64_efi.c. */ + +#include +#include + +#include "image.h" +#include "loader.h" +#include "printf.h" + +#ifdef TARGET_aarch64_efi + +/* -DDEBUG (object macro from DEBUG=1) collides with gnu-efi efidebug.h's + * DEBUG(a); drop it before the EFI headers (unused here). */ +#undef DEBUG + +#include +#include + +#ifdef __WOLFBOOT +void hal_init(void) +{ +} + +void hal_prepare_boot(void) +{ +} + +#endif + +#define PAGE_SIZE 0x1000 +#define EFI_DEVICE_PATH_PROTOCOL_HW_TYPE 0x01 +#define EFI_DEVICE_PATH_PROTOCOL_MEM_SUBTYPE 0x03 + +static EFI_SYSTEM_TABLE *gSystemTable; +static EFI_HANDLE gImageHandle; +EFI_PHYSICAL_ADDRESS kernel_addr; +EFI_PHYSICAL_ADDRESS update_addr; + +/* Optional Linux kernel command line, read from \cmdline.txt on the ESP and + * handed to the kernel EFI stub via LoadOptions (see aarch64_efi_do_boot). */ +static CHAR16 *kernel_cmdline = NULL; +static UINTN kernel_cmdline_bytes = 0; + +int RAMFUNCTION hal_flash_write(uintptr_t address, const uint8_t *data, int len) +{ + return 0; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ +} + +void RAMFUNCTION hal_flash_lock(void) +{ +} + +int RAMFUNCTION hal_flash_erase(uintptr_t address, int len) +{ + return 0; +} + +void* hal_get_primary_address(void) +{ + return (void*)kernel_addr; +} + +void* hal_get_update_address(void) +{ + return (void*)update_addr; +} + +void *hal_get_dts_address(void) +{ + return NULL; +} + +void *hal_get_dts_update_address(void) +{ + return NULL; +} + +static void panic() +{ + while(1) {} +} + +void RAMFUNCTION aarch64_efi_do_boot(uint32_t *boot_addr) +{ + uint32_t *size; + uint8_t* manifest = ((uint8_t*)boot_addr) - IMAGE_HEADER_SIZE; + MEMMAP_DEVICE_PATH mem_path_device[2]; + EFI_HANDLE kernelImageHandle; + EFI_STATUS status; + EFI_LOADED_IMAGE *kernel_li = NULL; + EFI_GUID lipGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID; + + size = (uint32_t *)(manifest + 4); + + mem_path_device->Header.Type = EFI_DEVICE_PATH_PROTOCOL_HW_TYPE; + mem_path_device->Header.SubType = EFI_DEVICE_PATH_PROTOCOL_MEM_SUBTYPE; + mem_path_device->MemoryType = EfiLoaderData; + mem_path_device->StartingAddress = (EFI_PHYSICAL_ADDRESS)boot_addr; + mem_path_device->EndingAddress = (EFI_PHYSICAL_ADDRESS)((uint8_t*)boot_addr+*size); + SetDevicePathNodeLength(&mem_path_device->Header, + sizeof(MEMMAP_DEVICE_PATH)); + + SetDevicePathEndNode(&mem_path_device[1].Header); + + wolfBoot_printf("Staging kernel at address %p, size: %u\n", (void*)boot_addr, *size); + status = uefi_call_wrapper(gSystemTable->BootServices->LoadImage, + 6, + 0, /* bool */ + gImageHandle, + (EFI_DEVICE_PATH*)mem_path_device, + boot_addr, + *size, + &kernelImageHandle); + if (status != EFI_SUCCESS) { + wolfBoot_printf("can't load kernel image from memory\n"); + panic(); + } + + /* Pass the kernel command line (from \cmdline.txt) to the loaded image via + * LoadOptions for the Linux EFI stub. For a production trust chain the + * cmdline should be authenticated (in the signed image or DT /chosen), not + * a plaintext file. A direct root= boot needs no initrd; an initramfs flow + * would add it via LINUX_EFI_INITRD_MEDIA_GUID (LoadFile2). */ + if (kernel_cmdline != NULL) { + status = uefi_call_wrapper(gSystemTable->BootServices->HandleProtocol, 3, + kernelImageHandle, &lipGuid, + (void**)&kernel_li); + if (status == EFI_SUCCESS && kernel_li != NULL) { + kernel_li->LoadOptions = kernel_cmdline; + kernel_li->LoadOptionsSize = (UINT32)kernel_cmdline_bytes; + } + } + + status = uefi_call_wrapper(gSystemTable->BootServices->StartImage, + 3, + kernelImageHandle, 0, NULL); + if (status != EFI_SUCCESS) { + wolfBoot_printf("can't load kernel image from memory\n"); + panic(); + } +} + +static UINT64 FileSize(EFI_FILE_HANDLE FileHandle) +{ + EFI_FILE_INFO *FileInfo; + UINT64 ret; + + FileInfo = LibFileInfo(FileHandle); + if (FileInfo == NULL) { + panic(); + return 0; /* Never reached, for static analyzer */ + } + + ret = FileInfo->FileSize; + FreePool(FileInfo); + + return ret; +} + +static EFI_FILE_HANDLE GetVolume(EFI_HANDLE image) +{ + EFI_GUID fsGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID; + EFI_GUID lipGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID; + EFI_LOADED_IMAGE *loaded_image = NULL; + EFI_FILE_IO_INTERFACE *IOVolume; + EFI_FILE_HANDLE Volume; + EFI_STATUS status; + + status = uefi_call_wrapper(BS->HandleProtocol, 3, + image, &lipGuid, (void **) &loaded_image); + if (status != EFI_SUCCESS) + panic(); + + status = uefi_call_wrapper(BS->HandleProtocol, 3, + loaded_image->DeviceHandle, + &fsGuid, (VOID*)&IOVolume); + if (status != EFI_SUCCESS) + panic(); + + status = uefi_call_wrapper(IOVolume->OpenVolume, 2, IOVolume, &Volume); + + if (status != EFI_SUCCESS) + panic(); + + return Volume; +} + +static EFI_FILE_HANDLE openFile(CHAR16 *file, EFI_FILE_HANDLE volume) +{ + EFI_FILE_HANDLE file_handle; + EFI_STATUS status; + + status = uefi_call_wrapper(volume->Open, 5, + volume, + &file_handle, + file, + EFI_FILE_MODE_READ, + EFI_FILE_READ_ONLY | EFI_FILE_HIDDEN | EFI_FILE_SYSTEM); + + if (status != EFI_SUCCESS) + file_handle = NULL; + + return file_handle; +} + +static int open_kernel_image(EFI_FILE_HANDLE vol, CHAR16 *filename, + EFI_PHYSICAL_ADDRESS *_addr, uint32_t *sz) +{ + EFI_FILE_HANDLE file; + EFI_STATUS status; + UINTN readsz; + + file = openFile(filename, vol); + if (file == NULL) + return -1; + + *sz = FileSize(file); + wolfBoot_printf("Opening file: %s, size: %u\n", filename, *sz); + status = uefi_call_wrapper(BS->AllocatePages, + 4, + AllocateAnyPages, + EfiLoaderData, + (*sz/PAGE_SIZE) + 1, _addr); + if (status != EFI_SUCCESS) { + wolfBoot_printf("can't get memory at specified address %d\n", status); + uefi_call_wrapper(file->Close, 1, file); + return status; + } + + /* EFI_FILE Read() takes UINTN *BufferSize and VOID *Buffer. On AArch64 + * uefi_call_wrapper is a native passthrough (no arg casting), so pass the + * correct types explicitly rather than a uint32_t pointer and an integer + * address. */ + readsz = (UINTN)*sz; + status = uefi_call_wrapper(file->Read, 3, file, &readsz, + (void*)(uintptr_t)*_addr); + *sz = (uint32_t)readsz; + uefi_call_wrapper(file->Close, 1, file); /* done with the file */ + if (status != EFI_SUCCESS) { + wolfBoot_printf("can't read kernel image %d\n", status); + return status; + } + + if (*sz < IMAGE_HEADER_SIZE) { + wolfBoot_printf("Image smaller than the header\n"); + return -1; + } + + return 0; +} + +/* Read an optional \cmdline.txt (ASCII) from the ESP into a widechar buffer + * for the kernel LoadOptions. No-op (leaves kernel_cmdline NULL) if absent. */ +static void read_cmdline(EFI_FILE_HANDLE vol) +{ + EFI_FILE_HANDLE file; + EFI_STATUS status; + UINT64 sz; + UINTN readsz, i, n; + uint8_t *ascii = NULL; + CHAR16 *wide = NULL; + + file = openFile(L"cmdline.txt", vol); + if (file == NULL) + return; /* optional */ + + sz = FileSize(file); + if (sz == 0 || sz > 4096) { + uefi_call_wrapper(file->Close, 1, file); + return; + } + + status = uefi_call_wrapper(BS->AllocatePool, 3, EfiLoaderData, + (UINTN)sz, (void**)&ascii); + if (status != EFI_SUCCESS || ascii == NULL) { + uefi_call_wrapper(file->Close, 1, file); + return; + } + + readsz = (UINTN)sz; + status = uefi_call_wrapper(file->Read, 3, file, &readsz, ascii); + uefi_call_wrapper(file->Close, 1, file); /* done with the file */ + if (status != EFI_SUCCESS) { + FreePool(ascii); + return; + } + + /* trim trailing CR/LF/whitespace */ + n = (UINTN)readsz; + while (n > 0 && (ascii[n-1] == '\n' || ascii[n-1] == '\r' || + ascii[n-1] == ' ' || ascii[n-1] == '\t')) + n--; + + status = uefi_call_wrapper(BS->AllocatePool, 3, EfiLoaderData, + (n + 1) * sizeof(CHAR16), (void**)&wide); + if (status != EFI_SUCCESS || wide == NULL) { + FreePool(ascii); + return; + } + for (i = 0; i < n; i++) + wide[i] = (CHAR16)ascii[i]; + wide[n] = 0; + FreePool(ascii); + + kernel_cmdline = wide; + kernel_cmdline_bytes = (n + 1) * sizeof(CHAR16); + wolfBoot_printf("Kernel cmdline (%d chars) from cmdline.txt\n", (int)n); +} + +EFI_STATUS +EFIAPI +efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) +{ + CHAR16 *kernel_filename = L"kernel.img"; + CHAR16 *update_filename = L"update.img"; + EFI_LOADED_IMAGE *loaded_image = NULL; + EFI_FILE_HANDLE vol; + EFI_STATUS status; + uint32_t kernel_size, update_size; + + InitializeLib(ImageHandle, SystemTable); + gSystemTable = SystemTable; + gImageHandle = ImageHandle; + + status = uefi_call_wrapper(SystemTable->BootServices->HandleProtocol, + 3, + ImageHandle, + &LoadedImageProtocol, + (void **)&loaded_image); + + if (status == EFI_SUCCESS) + wolfBoot_printf("Image base: 0x%lx\n", loaded_image->ImageBase); + vol = GetVolume(ImageHandle); + read_cmdline(vol); + open_kernel_image(vol, kernel_filename, &kernel_addr, &kernel_size); + open_kernel_image(vol, update_filename, &update_addr, &update_size); + + if (kernel_addr == 0 && update_addr == 0) { + wolfBoot_printf("No image to load\n"); + panic(); + } + + wolfBoot_start(); + + return EFI_SUCCESS; +} + +#endif /* TARGET_aarch64_efi */ diff --git a/hal/aarch64_efi.ld b/hal/aarch64_efi.ld new file mode 100644 index 0000000000..c38b3a4446 --- /dev/null +++ b/hal/aarch64_efi.ld @@ -0,0 +1,3 @@ +# aarch64_efi is a UEFI application target; the link uses the linker script +# provided by gnu-efi (elf_aarch64_efi.lds via GNU_EFI_LSCRIPT). This is an +# empty file to appease the Makefile (mirrors hal/x86_64_efi.ld). diff --git a/src/boot_aarch64_efi.c b/src/boot_aarch64_efi.c new file mode 100644 index 0000000000..fc65c21da7 --- /dev/null +++ b/src/boot_aarch64_efi.c @@ -0,0 +1,49 @@ +/* boot_aarch64_efi.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* AArch64 UEFI-application boot glue (generic AArch64 UEFI target). + * + * When wolfBoot is built as a UEFI application the gnu-efi CRT0 is the entry + * point and efi_main() (hal/aarch64_efi.c) does all the work, so none of the + * bare-metal reset / EL2->EL1 / cache-teardown path in boot_aarch64.c applies. + * This provides the do_boot() the updater (src/update_ram.c) calls; it simply + * forwards the verified image to the UEFI LoadImage/StartImage handoff in + * hal/aarch64_efi.c. Mirrors src/boot_x86_64.c. */ + +#include + +#include "image.h" +#include "loader.h" +#include "wolfboot/wolfboot.h" + +#ifdef TARGET_aarch64_efi + +/* aarch64_efi never defines MMU (UEFI owns MMU/FDT), so do_boot has only the + * app-only form; it forwards the verified image to the LoadImage/StartImage + * handoff in hal/aarch64_efi.c. */ +extern void RAMFUNCTION aarch64_efi_do_boot(uint32_t *boot_addr); + +void RAMFUNCTION do_boot(const uint32_t *app_offset) +{ + aarch64_efi_do_boot((uint32_t *)app_offset); +} + +#endif /* TARGET_aarch64_efi */ diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index 0482305dee..3751f588af 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -156,7 +156,7 @@ const char Store_hdr[] = "\n" "#define KEYSTORE_SECTION __attribute__((section (\"__KEYSTORE,__keystore\")))\n" "#elif defined(__CCRX__) || defined(WOLFBOOT_RENESAS_RSIP) || defined(WOLFBOOT_RENESAS_TSIP) || defined(WOLFBOOT_RENESAS_SCEPROTECT)\n" "#define KEYSTORE_SECTION /* Renesas RX */\n" - "#elif defined(TARGET_x86_64_efi)\n" + "#elif defined(TARGET_x86_64_efi) || defined(TARGET_aarch64_efi)\n" "#define KEYSTORE_SECTION\n" "#else\n" "#define KEYSTORE_SECTION __attribute__((section (\".keystore\")))\n" diff --git a/tools/scripts/aarch64-efi-qemu.sh b/tools/scripts/aarch64-efi-qemu.sh new file mode 100755 index 0000000000..d707ea9e40 --- /dev/null +++ b/tools/scripts/aarch64-efi-qemu.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Run the aarch64_efi wolfboot.efi in QEMU aarch64 UEFI for fast iteration/debug. +# No hardware / USB stick needed: a scratch dir is exposed to UEFI as a FAT ESP. +# +# ./tools/scripts/aarch64-efi-qemu.sh # run, auto-launch wolfboot.efi +# ./tools/scripts/aarch64-efi-qemu.sh --gdb # freeze at reset for gdb (-s -S) +# +# In another shell for --gdb: +# gdb-multiarch wolfboot.elf -ex 'target remote :1234' +set -e + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +ESP="$ROOT/tools/qemu-esp" +CODE=/usr/share/AAVMF/AAVMF_CODE.fd +VARS_SRC=/usr/share/AAVMF/AAVMF_VARS.fd + +for f in "$CODE" "$VARS_SRC"; do + [ -r "$f" ] || { echo "Missing $f -- install qemu-efi-aarch64"; exit 1; } +done +command -v qemu-system-aarch64 >/dev/null || { echo "Install qemu-system-arm"; exit 1; } + +mkdir -p "$ESP" +cp "$ROOT/wolfboot.efi" "$ESP"/ +cp "$ROOT/aarch64_efi-stage/kernel.img" "$ESP"/ 2>/dev/null || true +# optional Linux kernel command line (M3): read by wolfboot as \cmdline.txt +cp "$ROOT/aarch64_efi-stage/cmdline.txt" "$ESP"/ 2>/dev/null || true +# auto-run wolfboot.efi from the UEFI shell +printf 'fs0:\r\nwolfboot.efi\r\n' > "$ESP/startup.nsh" + +VARS="$ROOT/tools/qemu-esp/AAVMF_VARS.fd" +cp "$VARS_SRC" "$VARS" + +GDB_ARGS="" +if [ "$1" = "--gdb" ]; then + GDB_ARGS="-s -S" + echo "GDB mode: qemu frozen. In another shell:" + echo " gdb-multiarch $ROOT/wolfboot.elf -ex 'target remote :1234'" +fi + +exec qemu-system-aarch64 \ + -machine virt -cpu cortex-a72 -m 1024 -smp 1 \ + -drive if=pflash,format=raw,file="$CODE",readonly=on \ + -drive if=pflash,format=raw,file="$VARS" \ + -drive format=raw,file=fat:rw:"$ESP" \ + -net none -nographic $GDB_ARGS diff --git a/tools/scripts/build-gnu-efi-aarch64.sh b/tools/scripts/build-gnu-efi-aarch64.sh new file mode 100755 index 0000000000..5e746d0427 --- /dev/null +++ b/tools/scripts/build-gnu-efi-aarch64.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Build gnu-efi for AArch64 (for the wolfBoot aarch64_efi UEFI target). +# Installs crt0-efi-aarch64.o, elf_aarch64_efi.lds, libgnuefi.a, libefi.a +# and the EFI headers into tools/gnu-efi-aarch64/ inside the wolfBoot tree. +# +# GNU_EFI_REF is pinned to a released tag so the runtime CRT0/linker-script/libs +# are reproducible and auditable (the aarch64_efi link depends on the exact +# revision -- see the --allow-multiple-definition note in arch.mk). Override: +# CROSS_COMPILE=... GNU_EFI_REF=... ./build-gnu-efi-aarch64.sh +set -e + +CROSS_COMPILE="${CROSS_COMPILE:-aarch64-linux-gnu-}" +GNU_EFI_REF="${GNU_EFI_REF:-4.0.4}" + +# Resolve wolfBoot root (this script lives in tools/scripts/) +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +PREFIX="$ROOT/tools/gnu-efi-aarch64" +SRC="$ROOT/tools/gnu-efi-src" + +echo "== gnu-efi aarch64 build ==" +echo " CROSS_COMPILE=$CROSS_COMPILE" +echo " PREFIX=$PREFIX" + +# gnu-efi upstream (Nigel Croxon, the gnu-efi maintainer). Pin to $GNU_EFI_REF; +# if the tree already exists, force it onto that ref (reproducible re-runs). +if [ ! -d "$SRC/.git" ]; then + git clone --depth 1 -b "$GNU_EFI_REF" https://github.com/ncroxon/gnu-efi "$SRC" +else + git -C "$SRC" fetch --depth 1 origin tag "$GNU_EFI_REF" 2>/dev/null \ + || git -C "$SRC" fetch --depth 1 origin "$GNU_EFI_REF" + git -C "$SRC" checkout -q "$GNU_EFI_REF" 2>/dev/null \ + || git -C "$SRC" checkout -q FETCH_HEAD + make -C "$SRC" ARCH=aarch64 clean >/dev/null 2>&1 || true +fi + +make -C "$SRC" ARCH=aarch64 CROSS_COMPILE="$CROSS_COMPILE" -j"$(nproc)" +make -C "$SRC" ARCH=aarch64 CROSS_COMPILE="$CROSS_COMPILE" \ + PREFIX=/ INSTALLROOT="$PREFIX" install + +echo "== installed files ==" +find "$PREFIX" -name 'crt0-efi-aarch64.o' -o -name 'elf_aarch64_efi.lds' \ + -o -name 'libgnuefi.a' -o -name 'libefi.a' | sort +echo "== done ==" diff --git a/tools/scripts/sign-efi-secureboot.sh b/tools/scripts/sign-efi-secureboot.sh new file mode 100755 index 0000000000..75f10f4f9f --- /dev/null +++ b/tools/scripts/sign-efi-secureboot.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# Sign wolfboot.efi for UEFI Secure Boot and generate the PK/KEK/db enrollment +# material, so the platform firmware itself verifies wolfBoot before launching +# it (closing the code-signing gap: firmware -> wolfboot.efi -> kernel). +# +# This is the UEFI Secure Boot layer of the wolfBoot root of trust. It is +# independent of wolfBoot's own wolfCrypt verification of the payload, and of +# any NVIDIA fuse provisioning (documented separately in docs/Targets.md). +# +# Requires: sbsigntool (sbsign, sbverify) and efitools (cert-to-efi-sig-list, +# sign-efi-sig-list) and openssl. On Debian/Ubuntu: +# sudo apt install sbsigntool efitools openssl uuid-runtime +# +# Usage: +# ./tools/scripts/sign-efi-secureboot.sh [wolfboot.efi] +# Overrides (env): +# KEYDIR directory for the generated keys/certs (default tools/efi-secureboot-keys) +# EFI_BIN the PE image to sign (default: first arg, else wolfboot.efi) +# CN common-name prefix for the generated certs (default "wolfBoot") +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +KEYDIR="${KEYDIR:-$ROOT/tools/efi-secureboot-keys}" +EFI_BIN="${EFI_BIN:-${1:-$ROOT/wolfboot.efi}}" +CN="${CN:-wolfBoot}" + +for tool in openssl sbsign sbverify cert-to-efi-sig-list sign-efi-sig-list; do + command -v "$tool" >/dev/null 2>&1 || { + echo "ERROR: required tool '$tool' not found." >&2 + echo " install: sudo apt install sbsigntool efitools openssl uuid-runtime" >&2 + exit 1 + } +done + +if [ ! -f "$EFI_BIN" ]; then + echo "ERROR: EFI image '$EFI_BIN' not found (build wolfboot.efi first)." >&2 + exit 1 +fi +# Resolve to an absolute path: we cd into KEYDIR below, after which a relative +# image path would no longer resolve. +EFI_BIN="$(cd "$(dirname "$EFI_BIN")" && pwd)/$(basename "$EFI_BIN")" + +mkdir -p "$KEYDIR" +# Resolve to an absolute path: we cd into KEYDIR below but still reference +# "$KEYDIR/db.key" etc. afterward, which would break for a relative KEYDIR. +KEYDIR="$(cd "$KEYDIR" && pwd)" +cd "$KEYDIR" + +# A stable GUID identifies the owner of the enrolled variables; persist it so +# repeated runs reuse the same identity. +if [ ! -f guid.txt ]; then + if command -v uuidgen >/dev/null 2>&1; then + uuidgen > guid.txt + else + # Fallback: derive a GUID from openssl if uuid-runtime is absent. + openssl rand -hex 16 | sed 's/\(........\)\(....\)\(....\)\(....\)\(............\)/\1-\2-\3-\4-\5/' > guid.txt + fi +fi +GUID="$(cat guid.txt)" +echo "== UEFI Secure Boot key owner GUID: $GUID ==" + +# Generate the Platform Key (PK), Key Exchange Key (KEK) and signature +# database (db) key/cert if they do not already exist. Self-signed X.509, +# 2048-bit RSA, long validity for lab/dev use. +gen_key() { + name="$1"; subject="$2" + if [ ! -f "$name.key" ] || [ ! -f "$name.crt" ]; then + echo "== generating $name key/cert ($subject) ==" + openssl req -new -x509 -newkey rsa:2048 -nodes -sha256 -days 3650 \ + -subj "/CN=$subject/" -keyout "$name.key" -out "$name.crt" + fi + # ESL (EFI signature list) form of the cert, tagged with our owner GUID. + cert-to-efi-sig-list -g "$GUID" "$name.crt" "$name.esl" +} + +gen_key PK "$CN PK" +gen_key KEK "$CN KEK" +gen_key db "$CN db" + +# Build the signed variable updates (.auth) for enrollment: +# PK is signed by PK (self) +# KEK is signed by PK +# db is signed by KEK +echo "== building signed enrollment variables (.auth) ==" +sign-efi-sig-list -g "$GUID" -k PK.key -c PK.crt PK PK.esl PK.auth +sign-efi-sig-list -g "$GUID" -k PK.key -c PK.crt KEK KEK.esl KEK.auth +sign-efi-sig-list -g "$GUID" -k KEK.key -c KEK.crt db db.esl db.auth + +# Sign the wolfBoot PE image with the db key so the firmware accepts it. +SIGNED="${EFI_BIN}.signed" +echo "== signing $EFI_BIN with db key -> $SIGNED ==" +sbsign --key "$KEYDIR/db.key" --cert "$KEYDIR/db.crt" \ + --output "$SIGNED" "$EFI_BIN" +sbverify --cert "$KEYDIR/db.crt" "$SIGNED" + +echo "== done ==" +echo "Signed image: $SIGNED" +echo "Enroll on the target (UEFI setup / KeyTool / QEMU AAVMF vars) in order:" +echo " db <- $KEYDIR/db.auth" +echo " KEK <- $KEYDIR/KEK.auth" +echo " PK <- $KEYDIR/PK.auth (enabling PK turns Secure Boot enforcing)" +echo "Then deploy $SIGNED as \\EFI\\BOOT\\BOOTAA64.EFI (or your boot path)." From 1963856aec7835e040b0a0852c7873e2991339e3 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 9 Jul 2026 11:32:24 -0700 Subject: [PATCH 2/3] tegra234: add bare-metal skeleton target UART + ARMv8 generic timer HAL on the shared AArch64 startup; builds wolfboot.bin (RAM boot, like raspi3). Groundwork for the Path B firmware-replacement direction; adds a compile-only CI job. --- .github/workflows/test-configs.yml | 9 ++ Makefile | 6 + arch.mk | 9 ++ config/examples/tegra234.config | 30 ++++ hal/tegra234.c | 242 +++++++++++++++++++++++++++++ hal/tegra234.h | 33 ++++ hal/tegra234.ld | 68 ++++++++ src/boot_aarch64_start.S | 4 + test-app/app_tegra234.c | 38 +++++ 9 files changed, 439 insertions(+) create mode 100644 config/examples/tegra234.config create mode 100644 hal/tegra234.c create mode 100644 hal/tegra234.h create mode 100644 hal/tegra234.ld create mode 100644 test-app/app_tegra234.c diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 7a978caee5..94989b32d6 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -359,6 +359,15 @@ jobs: config-file: ./config/examples/raspi3-encrypted.config make-args: wolfboot.bin CROSS_COMPILE=aarch64-linux-gnu- + tegra234_test: + uses: ./.github/workflows/test-build.yml + with: + arch: aarch64 + # Tegra234 bare-metal skeleton: build the bootloader only (RAM boot, + # no factory.bin), mirroring raspi3. + config-file: ./config/examples/tegra234.config + make-args: wolfboot.bin CROSS_COMPILE=aarch64-linux-gnu- + sim_tfm_smallstack_test: uses: ./.github/workflows/test-build.yml with: diff --git a/Makefile b/Makefile index 22cce80a9f..dc165ec640 100644 --- a/Makefile +++ b/Makefile @@ -317,6 +317,12 @@ ifeq ($(TARGET),raspi3) MAIN_TARGET:=wolfboot.bin endif +# Tegra234 bare-metal skeleton boots from RAM (loaded by an earlier stage), +# like raspi3: build the bootloader binary, no contiguous flash factory.bin. +ifeq ($(TARGET),tegra234) + MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin +endif + ifeq ($(TARGET),sim) CFLAGS+=-fno-pie LDFLAGS+=-no-pie diff --git a/arch.mk b/arch.mk index 4e69a3e6a4..a0561ad422 100644 --- a/arch.mk +++ b/arch.mk @@ -128,6 +128,15 @@ ifeq ($(ARCH),AARCH64) SPI_TARGET=nxp endif + ifeq ($(TARGET),tegra234) + # NVIDIA Jetson Orin (Tegra234, Cortex-A78AE) bare-metal skeleton. + # Groundwork for the Path B firmware-replacement direction; the validated + # Orin path is the aarch64_efi UEFI app. See hal/tegra234.c. + ARCH_FLAGS=-mcpu=cortex-a78+crypto -march=armv8.2-a+crypto + CFLAGS+=$(ARCH_FLAGS) -DCORTEX_A78 + CFLAGS+=-DWOLFBOOT_DUALBOOT + endif + # Default ARM ASM setting for unrecognized AARCH64 targets ifeq ($(filter zynq versal nxp_ls1028a,$(TARGET)),) NO_ARM_ASM?=1 diff --git a/config/examples/tegra234.config b/config/examples/tegra234.config new file mode 100644 index 0000000000..3cb5ec15d8 --- /dev/null +++ b/config/examples/tegra234.config @@ -0,0 +1,30 @@ +# NVIDIA Jetson Orin / Tegra234 - BARE-METAL skeleton target. +# +# WARNING: this is groundwork for the "Path B" firmware-replacement direction +# (wolfBoot as a raw DRAM payload with its own drivers), NOT a validated boot +# path. It builds and links the lowest-level Tegra234 drivers (UART console + +# ARMv8 generic timer) on top of the shared AArch64 bare-metal startup, so the +# driver set has a home to grow in. The memory map (hal/tegra234.ld) and the +# earlier-stage clock/pinmux assumptions are PROVISIONAL and must be validated +# on hardware and finalized with the Path B handoff recon. +# +# For a working, validated Orin boot use config/examples/aarch64_efi.config +# (wolfBoot as a UEFI application) instead. +# +# Toolchain: aarch64-none-elf- (bare-metal), not the UEFI aarch64-linux-gnu-. +ARCH=AARCH64 +TARGET=tegra234 +SIGN?=ECC384 +HASH?=SHA384 +DEBUG?=1 +DEBUG_UART?=1 +SPMATH?=1 +# Boot media is external (SD/eMMC/NVMe via ext_flash); no internal flash. +NO_XIP?=1 +# Skip GIC init in the shared bare-metal startup for the skeleton bring-up. +NO_QNX?=1 +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_NO_PARTITIONS=1 +# PROVISIONAL DRAM load addresses (mirror hal/tegra234.ld). +WOLFBOOT_LOAD_ADDRESS?=0x90000000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0x91000000 diff --git a/hal/tegra234.c b/hal/tegra234.c new file mode 100644 index 0000000000..6e0976f973 --- /dev/null +++ b/hal/tegra234.c @@ -0,0 +1,242 @@ +/* tegra234.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* + * Bare-metal HAL skeleton for the NVIDIA Jetson Orin (Tegra234, Cortex-A78AE). + * + * SCOPE: this is the FOUNDATION for a bare-metal Tegra234 target (the "Path B" + * firmware-replacement direction), NOT the validated boot path. The validated, + * shipping way to run wolfBoot on the Orin is the aarch64_efi UEFI application + * (hal/aarch64_efi.c) which needs no Tegra-specific code. This file provides + * the two lowest-level drivers to build on -- UART console and the ARMv8 + * generic timer -- plus the standard HAL stubs, so the target compiles and + * links. Register bases come from the public Tegra234 memory map / upstream + * Linux device tree (tegra234.dtsi). Anything marked PROVISIONAL below must be + * validated on hardware and finalized alongside the Path B handoff recon (the + * DRAM load address, the earlier-stage clock/pinmux contract). + */ + +#include +#include +#include +#include +#include "image.h" +#include "printf.h" + +#ifndef ARCH_AARCH64 +# error "wolfBoot tegra234 HAL: wrong architecture. Compile with ARCH=AARCH64." +#endif + +#if defined(DEBUG_UART) + #define PRINTF_ENABLED +#endif + +/* Linker-provided partition/load addresses (see hal/tegra234.ld). */ +extern uint32_t kernel_addr; +extern uint32_t update_addr; +extern uint32_t dts_addr; + +/* -------------------------------------------------------------------------- + * ARMv8 generic timer (architectural; no SoC register needed) + * -------------------------------------------------------------------------- */ + +/* Fallback if firmware left CNTFRQ_EL0 unprogrammed. Tegra234's timestamp + * counter is nominally 31.25 MHz; PROVISIONAL until confirmed on silicon. */ +#ifndef TIMER_CLK_FREQ +#define TIMER_CLK_FREQ 31250000ULL +#endif + +static inline uint64_t timer_get_count(void) +{ + uint64_t cntpct; + __asm__ volatile("mrs %0, cntpct_el0" : "=r" (cntpct)); + return cntpct; +} + +static inline uint64_t timer_get_freq(void) +{ + uint64_t cntfrq; + __asm__ volatile("mrs %0, cntfrq_el0" : "=r" (cntfrq)); + return cntfrq ? cntfrq : TIMER_CLK_FREQ; +} + +uint64_t hal_get_timer_us(void) +{ + return (timer_get_count() * 1000000ULL) / timer_get_freq(); +} + +void hal_delay_us(uint32_t us) +{ + uint64_t freq = timer_get_freq(); + uint64_t target = timer_get_count() + ((uint64_t)us * freq) / 1000000ULL; + + while (timer_get_count() < target) + ; +} + +/* -------------------------------------------------------------------------- + * UART console (Tegra234 UARTA, NS16550-compatible) + * -------------------------------------------------------------------------- */ + +/* + * UARTA at 0x03100000 (upstream DT node serial@3100000). Tegra wires the + * 16550 registers 32 bits wide with a register shift of 2, so each logical + * register sits at (index << 2). We assume the earlier NVIDIA boot stage + * (MB1/MB2) has already enabled the UART clock and pinmux and set the baud + * divisor; this skeleton only drives TX against the existing configuration + * (the raspi3 model, where the GPU firmware sets clocks). Reprogramming the + * baud rate would require the Tegra CAR/BPMP clock path (PROVISIONAL, not + * implemented here). + */ +#define TEGRA_UARTA_BASE 0x03100000UL +#define UART_REG(idx) ((volatile uint32_t*)(TEGRA_UARTA_BASE + ((idx) << 2))) + +#define UART_THR UART_REG(0) /* Transmit holding (write) */ +#define UART_FCR UART_REG(2) /* FIFO control (write) */ +#define UART_LCR UART_REG(3) /* Line control */ +#define UART_LSR UART_REG(5) /* Line status */ + +#define UART_LSR_THRE (1u << 5) /* TX holding register empty */ +#define UART_FCR_EN (1u << 0) /* Enable FIFOs */ +#define UART_LCR_8N1 (0x03u) /* 8 data bits, 1 stop, no parity */ + +#if defined(DEBUG_UART) +void uart_init(void) +{ + /* Clock/pinmux/baud are owned by the earlier boot stage; set a known + * 8N1 line format and enable the FIFOs against that configuration. */ + *UART_FCR = UART_FCR_EN; + *UART_LCR = UART_LCR_8N1; +} + +static void uart_tx(char c) +{ + while ((*UART_LSR & UART_LSR_THRE) == 0) + ; + *UART_THR = (uint32_t)(uint8_t)c; +} + +void uart_write(const char* buf, unsigned int sz) +{ + unsigned int i; + for (i = 0; i < sz; i++) { + if (buf[i] == '\n') + uart_tx('\r'); + uart_tx(buf[i]); + } +} +#endif /* DEBUG_UART */ + +/* -------------------------------------------------------------------------- + * Partition / load addresses + * -------------------------------------------------------------------------- */ + +void* hal_get_primary_address(void) +{ + return (void*)&kernel_addr; +} + +void* hal_get_update_address(void) +{ + return (void*)&update_addr; +} + +void* hal_get_dts_address(void) +{ + return (void*)&dts_addr; +} + +void* hal_get_dts_update_address(void) +{ + return NULL; /* Not yet supported */ +} + +/* -------------------------------------------------------------------------- + * External storage (boot media). Stubbed: a real Tegra234 bare-metal boot + * needs an SDHCI/MMC driver (model src/sdhci.c + the platform hooks in + * include/sdhci.h, cf. hal/zynq.c Arasan controller). PROVISIONAL / future. + * -------------------------------------------------------------------------- */ + +#ifdef EXT_FLASH +int ext_flash_read(unsigned long address, uint8_t *data, int len) +{ + XMEMCPY(data, (void *)address, len); + return len; +} + +int ext_flash_erase(unsigned long address, int len) +{ + XMEMSET((void *)address, 0xFF, len); + return len; +} + +int ext_flash_write(unsigned long address, const uint8_t *data, int len) +{ + XMEMCPY((void *)address, data, len); + return len; +} + +void ext_flash_lock(void) +{ +} + +void ext_flash_unlock(void) +{ +} +#endif /* EXT_FLASH */ + +/* -------------------------------------------------------------------------- + * Public HAL entry points + * -------------------------------------------------------------------------- */ + +void hal_init(void) +{ +#if defined(DEBUG_UART) + uart_init(); + wolfBoot_printf("wolfBoot Tegra234 (bare-metal skeleton)\n"); +#endif +} + +void hal_prepare_boot(void) +{ +} + +/* Internal flash is not used on this target (boot media is SD/eMMC/NVMe via + * the external-flash path). Provide the required no-op HAL flash surface. */ +int RAMFUNCTION hal_flash_write(uintptr_t address, const uint8_t *data, int len) +{ + (void)address; (void)data; (void)len; + return 0; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ +} + +void RAMFUNCTION hal_flash_lock(void) +{ +} + +int RAMFUNCTION hal_flash_erase(uintptr_t address, int len) +{ + (void)address; (void)len; + return 0; +} diff --git a/hal/tegra234.h b/hal/tegra234.h new file mode 100644 index 0000000000..843d3ff62a --- /dev/null +++ b/hal/tegra234.h @@ -0,0 +1,33 @@ +/* tegra234.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef _TEGRA234_H_ +#define _TEGRA234_H_ + +/* Use the simple AArch64 startup in src/boot_aarch64_start.S: a small .boot + * reset stub that detects the current EL, sets the stack, and calls + * boot_entry_C. The NVIDIA earlier boot stage (MB1/MB2) already brings up + * DRAM/clocks, so wolfBoot does not need the full builtin EL3/MMU init path + * (same model as raspi3). */ +#define USE_BUILTIN_STARTUP +#define USE_SIMPLE_STARTUP + +#endif /* _TEGRA234_H_ */ diff --git a/hal/tegra234.ld b/hal/tegra234.ld new file mode 100644 index 0000000000..56fcb5fff7 --- /dev/null +++ b/hal/tegra234.ld @@ -0,0 +1,68 @@ +/* tegra234.ld - bare-metal link map for the Tegra234 skeleton target. + * + * PROVISIONAL memory map. Modeled on hal/raspi3.ld (a firmware-loaded, + * boot-from-DRAM AArch64 target). The DRAM base and the kernel/update/dts + * placement below are placeholders that must be finalized against the Path B + * handoff contract (where the NVIDIA cpu-bootloader stage leaves wolfBoot in + * DRAM, and the address ranges that are safe to use). Do not treat these as + * validated for on-hardware boot. + */ +MEMORY +{ + DDR_MEM(rwx): ORIGIN = 0x80000000, LENGTH = 0x10000000 +} +ENTRY(_vector_table); + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.boot*)) + *(.text*) + *(.rodata*) + *(.note.*) + . = ALIGN(4); + _end_text = .; + } > DDR_MEM + .edidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > DDR_MEM + + .data : + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > DDR_MEM + + .bss (NOLOAD) : + { + _start_bss = .; + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + __bss_end__ = .; + _end = .; + } > DDR_MEM + . = ALIGN(4); +} + +END_STACK = _start_text; + +/* PROVISIONAL partition/load addresses (offsets within DDR_MEM). */ +kernel_addr = 0x80100000; +update_addr = 0x81100000; +dts_addr = 0x800a0000; +kernel_load_addr = 0x90000000; +dts_load_addr = 0x91000000; + +_wolfboot_partition_boot_address = kernel_addr; +_wolfboot_partition_update_address = update_addr; diff --git a/src/boot_aarch64_start.S b/src/boot_aarch64_start.S index 544b5e8de9..a618a22a28 100644 --- a/src/boot_aarch64_start.S +++ b/src/boot_aarch64_start.S @@ -38,6 +38,10 @@ #include "hal/raspi3.h" #endif +#ifdef TARGET_tegra234 +#include "hal/tegra234.h" +#endif + /* GICv2 Register Offsets */ #ifndef GICD_BASE #define GICD_BASE 0xF9010000 diff --git a/test-app/app_tegra234.c b/test-app/app_tegra234.c new file mode 100644 index 0000000000..4120f31bb2 --- /dev/null +++ b/test-app/app_tegra234.c @@ -0,0 +1,38 @@ +/* app_tegra234.c + * + * Test bare-metal boot application + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include + +#include "wolfboot/wolfboot.h" + +#ifdef TARGET_tegra234 + +void __attribute__((section(".boot"))) main(void) { + + /* Wait for reboot */ + while(1) + ; +} +#endif /** TARGET_tegra234 **/ From d9a062b29c1a8b2bae0d08a6e6aa45623074e7a1 Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 10 Jul 2026 17:17:41 -0700 Subject: [PATCH 3/3] aarch64_efi: measure kernel into firmware TPM via EFI_TCG2 Adds MEASURED_BOOT_TCG2: wolfBoot extends the verified kernel into the platform firmware TPM (PCR MEASURED_PCR_A) via EFI_TCG2_PROTOCOL HashLogExtendEvent before handoff, no wolfTPM transport. Validated on the NVIDIA Orin Nano fTPM (TPM present, SHA-256+SHA-384 banks, measured into PCR 9). --- config/examples/aarch64_efi.config | 7 ++ docs/Targets.md | 13 +++ hal/aarch64_efi.c | 126 +++++++++++++++++++++++++++++ options.mk | 10 +++ 4 files changed, 156 insertions(+) diff --git a/config/examples/aarch64_efi.config b/config/examples/aarch64_efi.config index 53f422e751..a6d2c70fb4 100644 --- a/config/examples/aarch64_efi.config +++ b/config/examples/aarch64_efi.config @@ -27,3 +27,10 @@ SPMATH=0 # required for keytools WOLFBOOT_SECTOR_SIZE?=0x1000 WOLFBOOT_NO_PARTITIONS=1 +# Measured boot: extend the verified kernel into the platform firmware TPM via +# EFI_TCG2_PROTOCOL (PCR MEASURED_PCR_A) before handoff, using the firmware's +# own TPM stack -- no wolfTPM transport. Best-effort: skips cleanly if the +# firmware exposes no TCG2/TPM. Validated on the NVIDIA Orin Nano fTPM +# (TPM present, SHA-256 + SHA-384 PCR banks). +MEASURED_BOOT_TCG2=1 +MEASURED_PCR_A?=9 diff --git a/docs/Targets.md b/docs/Targets.md index af847aa0fa..b92cda63b2 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -7953,6 +7953,19 @@ UEFI Secure Boot above is enforced by the edk2 firmware. On a production Jetson These are burned with `odmfuse.sh`/`tegrasign` from `Linux_for_Tegra/` and are irreversible. They are the final production step and are intentionally NOT part of this port -- the development board stays in unfused/dev mode. Consult the NVIDIA Jetson Linux "Secure Boot" documentation for the current `odmfuse.sh` procedure for your module before burning anything. Once fused, the fused firmware enforces UEFI Secure Boot, which enforces `wolfboot.efi`, which enforces the kernel -- a complete hardware root of trust. +### Measured boot (firmware TPM via EFI_TCG2) + +The Jetson firmware provides a TPM 2.0 (an OP-TEE fTPM) behind `EFI_TCG2_PROTOCOL` and already measures the early boot chain into PCRs. wolfBoot extends that chain to the OS: with `MEASURED_BOOT_TCG2=1` (default in `config/examples/aarch64_efi.config`), it measures the verified kernel image into PCR `MEASURED_PCR_A` (default 9) with `HashLogExtendEvent` just before handoff, appending a `wolfBoot kernel.img` record to the firmware event log. This is the same consumer pattern U-Boot uses -- the firmware / fTPM performs the hashing, PCR extend and log append, so wolfBoot needs no TPM transport driver of its own and pulls in no wolfTPM. + +It is best-effort and does not disturb boot: wolfBoot logs the TPM capability and, if the platform exposes no TCG2 protocol or reports no TPM present, skips the measurement and continues. On the Orin Nano the console shows: + +``` +TCG2: TPM present=1 activeBanks=0x6 banks=2 +TCG2: measured kernel (43091976 bytes) into PCR 9 +``` + +`activeBanks=0x6` is the SHA-256 (0x2) + SHA-384 (0x4) PCR banks; the kernel is extended into PCR 9 in both. An attestation client can then read PCR 9 plus the TCG2 event log to confirm exactly which kernel wolfBoot verified and booted. Choose `MEASURED_PCR_A` to fit the platform's PCR allocation (0-7 are firmware-owned; 8-15 are for OS/loader use). Note the edk2 firmware separately measures the loaded `wolfboot.efi` image itself into its own PCRs via `LoadImage`, so the firmware-verifies-wolfBoot and wolfBoot-measures-kernel events are distinct entries in the log. + ## Intel x86_64 with Intel FSP support This setup is more complex than the UEFI approach described earlier, but allows diff --git a/hal/aarch64_efi.c b/hal/aarch64_efi.c index 33e4680338..6335dade90 100644 --- a/hal/aarch64_efi.c +++ b/hal/aarch64_efi.c @@ -114,6 +114,127 @@ static void panic() while(1) {} } +#ifdef WOLFBOOT_MEASURED_BOOT_EFI_TCG2 +/* EFI TCG2 protocol (TCG EFI Protocol Specification) -- not provided by gnu-efi. + * Used to measure the wolfBoot-verified kernel into a firmware-TPM PCR via + * HashLogExtendEvent before StartImage, extending the platform's measured-boot + * chain to the OS. This is the same consumer pattern U-Boot uses: the platform + * firmware / fTPM performs the hashing, PCR extend and event-log append behind + * EFI_TCG2_PROTOCOL, so wolfBoot needs no TPM transport driver of its own. */ +#define WOLFBOOT_TCG2_PROTOCOL_GUID \ + { 0x607f766c, 0x7455, 0x42be, {0x93,0x0b,0xe4,0xd7,0x6d,0xb2,0x72,0x0f} } + +#define WOLFBOOT_TCG2_EV_IPL 0x0000000DUL /* boot-loader / OS measurement */ + +typedef struct { + UINT8 Major; + UINT8 Minor; +} WOLFBOOT_TCG2_VERSION; + +typedef struct { + UINT8 Size; + WOLFBOOT_TCG2_VERSION StructureVersion; + WOLFBOOT_TCG2_VERSION ProtocolVersion; + UINT32 HashAlgorithmBitmap; + UINT32 SupportedEventLogs; + BOOLEAN TPMPresentFlag; + UINT16 MaxCommandSize; + UINT16 MaxResponseSize; + UINT32 ManufacturerID; + UINT32 NumberOfPcrBanks; + UINT32 ActivePcrBanks; +} WOLFBOOT_TCG2_CAPABILITY; + +typedef struct { + UINT32 HeaderSize; + UINT16 HeaderVersion; + UINT32 PCRIndex; + UINT32 EventType; +} __attribute__((packed)) WOLFBOOT_TCG2_EVENT_HEADER; + +typedef struct { + UINT32 Size; + WOLFBOOT_TCG2_EVENT_HEADER Header; + UINT8 Event[1]; +} __attribute__((packed)) WOLFBOOT_TCG2_EVENT; + +struct wolfboot_tcg2_protocol; +typedef struct wolfboot_tcg2_protocol WOLFBOOT_TCG2_PROTOCOL; +struct wolfboot_tcg2_protocol { + EFI_STATUS (EFIAPI *GetCapability)(WOLFBOOT_TCG2_PROTOCOL *This, + WOLFBOOT_TCG2_CAPABILITY *Cap); + void *GetEventLog; + EFI_STATUS (EFIAPI *HashLogExtendEvent)(WOLFBOOT_TCG2_PROTOCOL *This, + UINT64 Flags, + EFI_PHYSICAL_ADDRESS DataToHash, + UINT64 DataToHashLen, + WOLFBOOT_TCG2_EVENT *Event); + void *SubmitCommand; + void *GetActivePcrBanks; + void *SetActivePcrBanks; + void *GetResultOfSetActivePcrBanks; +}; + +/* Measure the verified kernel image into the firmware TPM (PCR + * WOLFBOOT_MEASURED_PCR_A) via EFI_TCG2_PROTOCOL, before handing off. Best + * effort: if the firmware exposes no TCG2 protocol or reports no TPM present, + * this logs and returns without failing the boot. */ +static void tcg2_measure_kernel(void *addr, uint32_t size) +{ + EFI_GUID guid = WOLFBOOT_TCG2_PROTOCOL_GUID; + WOLFBOOT_TCG2_PROTOCOL *tcg2 = NULL; + WOLFBOOT_TCG2_CAPABILITY cap; + EFI_STATUS status; + static const char desc[] = "wolfBoot kernel.img"; + uint8_t buf[sizeof(WOLFBOOT_TCG2_EVENT) + sizeof(desc)]; + WOLFBOOT_TCG2_EVENT *evt = (WOLFBOOT_TCG2_EVENT *)buf; + + status = uefi_call_wrapper(BS->LocateProtocol, 3, &guid, NULL, + (void **)&tcg2); + if (status != EFI_SUCCESS || tcg2 == NULL) { + wolfBoot_printf("TCG2: protocol not found (0x%lx); measure skipped\n", + (unsigned long)status); + return; + } + + ZeroMem(&cap, sizeof(cap)); + cap.Size = (UINT8)sizeof(cap); + status = uefi_call_wrapper(tcg2->GetCapability, 2, tcg2, &cap); + if (status != EFI_SUCCESS) { + wolfBoot_printf("TCG2: GetCapability failed (0x%lx)\n", + (unsigned long)status); + return; + } + wolfBoot_printf("TCG2: TPM present=%d activeBanks=0x%x banks=%d\n", + (int)cap.TPMPresentFlag, (unsigned)cap.ActivePcrBanks, + (int)cap.NumberOfPcrBanks); + if (!cap.TPMPresentFlag) { + wolfBoot_printf("TCG2: no firmware TPM present; measure skipped\n"); + return; + } + + ZeroMem(buf, sizeof(buf)); + evt->Size = (UINT32)sizeof(buf); + evt->Header.HeaderSize = (UINT32)sizeof(WOLFBOOT_TCG2_EVENT_HEADER); + evt->Header.HeaderVersion = 1; + evt->Header.PCRIndex = (UINT32)WOLFBOOT_MEASURED_PCR_A; + evt->Header.EventType = (UINT32)WOLFBOOT_TCG2_EV_IPL; + CopyMem(evt->Event, (void *)desc, sizeof(desc)); + + status = uefi_call_wrapper(tcg2->HashLogExtendEvent, 5, tcg2, + (UINT64)0, + (EFI_PHYSICAL_ADDRESS)(uintptr_t)addr, + (UINT64)size, evt); + if (status != EFI_SUCCESS) { + wolfBoot_printf("TCG2: HashLogExtendEvent failed (0x%lx)\n", + (unsigned long)status); + return; + } + wolfBoot_printf("TCG2: measured kernel (%u bytes) into PCR %d\n", + size, (int)WOLFBOOT_MEASURED_PCR_A); +} +#endif /* WOLFBOOT_MEASURED_BOOT_EFI_TCG2 */ + void RAMFUNCTION aarch64_efi_do_boot(uint32_t *boot_addr) { uint32_t *size; @@ -136,6 +257,11 @@ void RAMFUNCTION aarch64_efi_do_boot(uint32_t *boot_addr) SetDevicePathEndNode(&mem_path_device[1].Header); +#ifdef WOLFBOOT_MEASURED_BOOT_EFI_TCG2 + /* Extend the verified kernel into the firmware TPM before handoff. */ + tcg2_measure_kernel((void*)boot_addr, *size); +#endif + wolfBoot_printf("Staging kernel at address %p, size: %u\n", (void*)boot_addr, *size); status = uefi_call_wrapper(gSystemTable->BootServices->LoadImage, 6, diff --git a/options.mk b/options.mk index b6adb4a588..c726ff4aaa 100644 --- a/options.mk +++ b/options.mk @@ -102,6 +102,16 @@ ifeq ($(MEASURED_BOOT),1) endif endif +## Measured boot via the platform firmware TPM behind EFI_TCG2_PROTOCOL, for +## UEFI-application targets (e.g. aarch64_efi on NVIDIA Jetson). wolfBoot +## measures the verified next-stage image into a PCR with HashLogExtendEvent; +## the firmware / fTPM owns the TPM, so this pulls in NO wolfTPM transport. +ifeq ($(MEASURED_BOOT_TCG2),1) + MEASURED_PCR_A ?= 9 + CFLAGS+=-D"WOLFBOOT_MEASURED_BOOT_EFI_TCG2" + CFLAGS+=-D"WOLFBOOT_MEASURED_PCR_A=$(MEASURED_PCR_A)" +endif + ## TPM keystore ifeq ($(WOLFBOOT_TPM_KEYSTORE),1) WOLFTPM:=1