diff --git a/Makefile b/Makefile index 2051b7900a..9601155bb2 100644 --- a/Makefile +++ b/Makefile @@ -310,6 +310,10 @@ ifeq ($(TARGET),raspi3) MAIN_TARGET:=wolfboot.bin endif +ifeq ($(TARGET),cm4) + MAIN_TARGET:=wolfboot.bin +endif + ifeq ($(TARGET),sim) CFLAGS+=-fno-pie LDFLAGS+=-no-pie diff --git a/arch.mk b/arch.mk index beeffa4122..6180e90677 100644 --- a/arch.mk +++ b/arch.mk @@ -117,8 +117,18 @@ ifeq ($(ARCH),AARCH64) SPI_TARGET=nxp endif + ifeq ($(TARGET),cm4) + # Raspberry Pi Compute Module 4 - Broadcom BCM2711, Cortex-A72 + ARCH_FLAGS=-mcpu=cortex-a72+crypto -march=armv8-a+crypto -mtune=cortex-a72 + # -mstrict-align: wolfBoot runs with the MMU off (simple startup), so data + # accesses are Device memory where unaligned access faults. Required for the + # FIPS in-core HMAC over the code region (NO_ARM_ASM drops the asm path that + # otherwise sets this). + CFLAGS+=$(ARCH_FLAGS) -DCORTEX_A72 -mstrict-align + endif + # Default ARM ASM setting for unrecognized AARCH64 targets - ifeq ($(filter zynq versal nxp_ls1028a,$(TARGET)),) + ifeq ($(filter zynq versal nxp_ls1028a cm4,$(TARGET)),) NO_ARM_ASM?=1 endif diff --git a/config/examples/cm4-fips.config b/config/examples/cm4-fips.config new file mode 100644 index 0000000000..b489ee2082 --- /dev/null +++ b/config/examples/cm4-fips.config @@ -0,0 +1,25 @@ +# Raspberry Pi CM4 (BCM2711) with the wolfCrypt FIPS 140-3 module. +# Point WOLFBOOT_LIB_WOLFSSL at an unpacked FIPS / FIPS-ready wolfSSL tree. +# Entropy comes from the BCM2711 RNG200 hardware TRNG (hal/cm4.c). See +# docs/FIPS.md for the in-core hash-seal procedure (done on-target over UART). +ARCH?=AARCH64 +TARGET?=cm4 +SIGN?=ECC384 +HASH?=SHA384 +FIPS?=1 +WOLFBOOT_LIB_WOLFSSL?=../wolfssl-5.9.2-gplv3-fips-ready +DEBUG?=1 +DEBUG_UART?=1 +VTOR?=1 +SPMATH?=1 +NO_ARM_ASM?=1 +PKA?=0 +WOLFTPM?=0 +NO_XIP?=1 +NO_QNX?=1 +IMAGE_HEADER_SIZE?=1024 +WOLFBOOT_SECTOR_SIZE=0x400 +WOLFBOOT_NO_PARTITIONS=1 +WOLFBOOT_RAMBOOT_MAX_SIZE=0x20000000 +WOLFBOOT_LOAD_ADDRESS?=0x3080000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0x400000 diff --git a/config/examples/cm4.config b/config/examples/cm4.config new file mode 100644 index 0000000000..ea9ff84e5b --- /dev/null +++ b/config/examples/cm4.config @@ -0,0 +1,18 @@ +ARCH?=AARCH64 +TARGET?=cm4 +SIGN?=ECC384 +HASH?=SHA384 +DEBUG?=1 +VTOR?=1 +SPMATH?=1 +IMAGE_HEADER_SIZE?=1024 +PKA?=0 +WOLFTPM?=0 +DEBUG_UART?=0 +NO_XIP?=1 +NO_QNX?=1 +WOLFBOOT_SECTOR_SIZE=0x400 +WOLFBOOT_NO_PARTITIONS=1 +WOLFBOOT_RAMBOOT_MAX_SIZE=0x20000000 +WOLFBOOT_LOAD_ADDRESS?=0x3080000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0x400000 diff --git a/config/examples/cm4_sdcard.config b/config/examples/cm4_sdcard.config new file mode 100644 index 0000000000..3f230c30ee --- /dev/null +++ b/config/examples/cm4_sdcard.config @@ -0,0 +1,39 @@ +# Raspberry Pi CM4 (BCM2711) - eMMC/SD A/B disk boot (Phase 2) +# +# Boots from the BCM2711 EMMC2 controller (Arasan SDHCI @ 0xFE340000), which +# drives the onboard eMMC (eMMC variants) or the microSD (CM4 Lite). wolfBoot +# reads GPT A/B image partitions via update_disk.c and boots the highest valid +# version with rollback. +# +# NOTE: the EMMC2 platform glue in hal/cm4.c is bring-up scaffolding pending +# on-hardware validation (controller clock/pinmux state left by the GPU +# firmware, card-detect wiring on the carrier). Use SDHCI_FORCE_CARD_DETECT +# for the embedded eMMC path. +ARCH?=AARCH64 +TARGET?=cm4 +SIGN?=ECC384 +HASH?=SHA384 +IMAGE_HEADER_SIZE?=1024 +DEBUG?=0 +DEBUG_UART?=1 +DISK_SDCARD?=1 +DISK_EMMC?=0 +CFLAGS_EXTRA+=-DSDHCI_FORCE_CARD_DETECT +EXT_FLASH?=0 +NO_XIP=1 +NO_QNX?=1 +ELF?=1 +VTOR?=1 +SPMATH?=1 +PKA?=0 +WOLFTPM?=0 +WOLFBOOT_NO_PARTITIONS=1 +CFLAGS_EXTRA+=-DBOOT_PART_A=1 +CFLAGS_EXTRA+=-DBOOT_PART_B=2 +CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x80000 +WOLFBOOT_LOAD_ADDRESS?=0x10000000 +WOLFBOOT_RAMBOOT_MAX_SIZE=0x2BC00000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0x1000 +WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80200000 +WOLFBOOT_PARTITION_SIZE=0x4000000 +WOLFBOOT_SECTOR_SIZE=0x1000 diff --git a/config/examples/sim-fips.config b/config/examples/sim-fips.config new file mode 100644 index 0000000000..360331ad51 --- /dev/null +++ b/config/examples/sim-fips.config @@ -0,0 +1,22 @@ +# wolfBoot simulator build using the wolfCrypt FIPS 140-3 module. +# Point WOLFBOOT_LIB_WOLFSSL at an unpacked FIPS (or FIPS-ready) wolfSSL tree. +# Prototype target for the CM4 FIPS integration (see docs/FIPS.md). +ARCH=sim +TARGET=sim +SIGN?=ECC384 +HASH?=SHA384 +FIPS?=1 +WOLFBOOT_LIB_WOLFSSL?=../wolfssl-5.9.2-gplv3-fips-ready +WOLFBOOT_SMALL_STACK?=0 +SPI_FLASH=0 +DEBUG=1 + +# sizes should be multiple of system page size +WOLFBOOT_PARTITION_SIZE=0x40000 +WOLFBOOT_SECTOR_SIZE=0x1000 +WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x100000 +WOLFBOOT_PARTITION_SWAP_ADDRESS=0x180000 + +# required for keytools +WOLFBOOT_FIXED_PARTITIONS=1 diff --git a/docs/FIPS.md b/docs/FIPS.md new file mode 100644 index 0000000000..5cfdfde131 --- /dev/null +++ b/docs/FIPS.md @@ -0,0 +1,112 @@ +# wolfBoot with wolfCrypt FIPS 140-3 + +wolfBoot can perform its firmware signature verification using the wolfCrypt +FIPS 140-3 validated cryptographic module. In this configuration the boot-time +integrity and authenticity checks (ECDSA/RSA signature, SHA hashing) run inside +the validated module boundary, and the module runs its power-on self-tests +(POST) and conditional algorithm self-tests (CASTs) before it is used. + +This document describes how to build wolfBoot against a FIPS wolfCrypt source +tree and how to seal the module's in-core integrity hash. + +## Background + +A standard wolfBoot build compiles a curated list of individual +`wolfcrypt/src/*.o` files (see `arch.mk` and `options.mk`) with +`WOLFCRYPT_ONLY`. A FIPS build instead compiles the whole validated module +boundary together, adds the boundary sources (`fips.c`, `fips_test.c`), and +verifies an in-core integrity hash (HMAC-SHA-256 over the module's code and +read-only data) at initialization. The signature/hash algorithms wolfBoot uses +for verification are unchanged - only their implementation moves into the +validated boundary. + +Approved algorithms for firmware verification include ECDSA (P-256/P-384/P-521) +and RSA with SHA-256/384/512. EdDSA (Ed25519/Ed448) is outside the module +boundary; use `SIGN=ECC384 HASH=SHA384` (or an RSA/SHA variant) for FIPS. + +## Getting the FIPS source + +Obtain a FIPS wolfCrypt source tree. For evaluation, the FIPS-ready bundle can +be downloaded from wolfSSL: + +``` +https://www.wolfssl.com/wolfssl-5.9.2-gplv3-fips-ready.zip +``` + +Production use requires the licensed, validated FIPS bundle. Unpack it and +point wolfBoot at it with `WOLFBOOT_LIB_WOLFSSL`. + +## Building wolfBoot with FIPS + +1. Point the build at the FIPS wolfSSL tree (default is `lib/wolfssl`): + + ``` + make WOLFBOOT_LIB_WOLFSSL=/path/to/wolfssl-fips-ready ... + ``` + +2. Enable FIPS in `include/user_settings.h` (gated so non-FIPS builds are + unaffected). `settings.h` in the FIPS tree defines the exact version, so + just enable FIPS: + + ```c + #define HAVE_FIPS + ``` + + The FIPS-ready 5.9.2 bundle reports `HAVE_FIPS_VERSION 7` + (`HAVE_FIPS_VERSION_MINOR 0`). + +3. Add the FIPS boundary sources (`fips.c`, `fips_test.c`) and the in-boundary + crypto objects to the wolfcrypt object list, and add the FIPS in-core memory + sections to the target linker script so the integrity hash covers exactly + the module boundary. + +## Sealing the in-core integrity hash + +The validated module verifies an in-core integrity hash at startup. A fresh +build ships with a placeholder, so the first run reports a mismatch. Capture +the runtime hash and seal it: + +1. Build and run. Register a FIPS callback so a mismatch reports the runtime + hash. On the CM4 target the test application (`test-app/app_cm4.c`) does this + over the UART: it calls `wolfCrypt_SetCb_fips()` and, on `IN_CORE_FIPS_E`, + prints the hash. + +2. Copy the reported hash into `verifyCore[]` in + `wolfcrypt/src/fips_test.c` (the build macro / array that seals the hash). + +3. Rebuild (and re-flash). The in-core check now passes and the module reaches + the operational state; `wolfCrypt_GetStatus_fips()` returns 0. + +## Entropy source (required) + +The FIPS DRBG needs a seed source. wolfBoot's lean configuration compiles out the OS seed paths, so a seed must be provided via `CUSTOM_RAND_GENERATE_SEED` (the RNG is kept enabled in FIPS mode - the `HAVE_FIPS` block in `include/user_settings.h` undoes wolfBoot's `WC_NO_RNG`/`WC_NO_HASHDRBG`). The example wiring points `CUSTOM_RAND_GENERATE_SEED` at `wolfBoot_fips_seed()`, implemented per target: `/dev/urandom` on the simulator (`hal/sim.c`) and the BCM2711 RNG200 hardware TRNG on the CM4 (`hal/cm4.c`). Without a working seed, the ECDSA power-on self-test (which performs a sign) fails with `ECDSA_KAT_FIPS_E` because `wc_GenerateSeed()` returns `NOT_COMPILED_IN`. + +## Sealing note + +The in-core hash excludes `verifyCore[]`, but the placeholder-to-real-hash transition can perturb the computed hash, so sealing may take two or three passes. Repeat build -> capture reported hash -> update `verifyCore[]` -> rebuild until `wolfCrypt_GetStatus_fips()` returns 0. + +## Bare-metal targets + +The FIPS module is written for a hosted environment; a few things must be provided on bare-metal (e.g. the CM4): + +- **Power-on self-test entry.** The module registers its POST via a C constructor (`.init_array`), which a hosted C runtime runs before `main()`. wolfBoot's bare-metal startup does not run `.init_array`, so build with `NO_ATTRIBUTE_CONSTRUCTOR` and call `fipsEntry()` explicitly at startup (wolfBoot does this in `src/loader.c`). +- **Aligned access.** wolfBoot runs with the MMU off, where AArch64 data accesses are Device memory and faulting on unaligned access; build with `-mstrict-align` so the in-core integrity HMAC over the code region does not fault. +- **libc.** The module uses malloc/printf; provide a heap (`src/store_sbrk.c` + a linker `end` symbol) and stub the remaining newlib syscalls (`--specs=nosys.specs`). + +The simulator target (`config/examples/sim-fips.config`) is the recommended place to bring the module up first. The CM4 FIPS target (`config/examples/cm4-fips.config`) is a work in progress. + +## Verifying operation + +- POST/CASTs run at module initialization; `wc_RunAllCast_fips()` runs the + conditional algorithm self-tests and `wolfCrypt_GetStatus_fips()` reports the + module status (0 = operational). +- wolfBoot must treat a non-zero FIPS status as a hard failure and refuse to + boot (no OS handoff). +- A deliberately corrupted module boundary (flip a byte) must make the in-core + check fail and block the boot - the negative test for the integration. + +## See also + +- [Targets.md](Targets.md) - Raspberry Pi Compute Module 4 (BCM2711) target, + which uses `SIGN=ECC384 HASH=SHA384` and carries the on-target FIPS test + harness in `test-app/app_cm4.c`. diff --git a/docs/Targets.md b/docs/Targets.md index 0f2cbb352d..0aa371f5fa 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -6,6 +6,7 @@ This README describes configuration of supported targets. * [Simulated](#simulated) * [Cortex-A53 / Raspberry PI 3](#cortex-a53--raspberry-pi-3-experimental) +* [Cortex-A72 / Raspberry Pi Compute Module 4](#cortex-a72--raspberry-pi-compute-module-4-bcm2711) * [Cypress PSoC-6](#cypress-psoc-6) * [Infineon AURIX TC3xx](#infineon-aurix-tc3xx) * [Intel x86-64 Intel FSP](#intel-x86_64-with-intel-fsp-support) @@ -3694,6 +3695,79 @@ qemu-system-aarch64 -M raspi3b -m 1024 -serial stdio -kernel wolfboot_linux_rasp ``` +## Cortex-A72 / Raspberry Pi Compute Module 4 (BCM2711) + +wolfBoot runs on the Raspberry Pi Compute Module 4 (CM4), a Broadcom BCM2711 with a quad-core Cortex-A72 (AArch64). wolfBoot takes the place of the second-stage OS loader: the BCM2711 boot ROM loads the VideoCore firmware, the firmware loads `kernel8.img` from the boot partition, and that `kernel8.img` is wolfBoot. wolfBoot then verifies the signed application image and boots it, extending the platform root of trust into the OS. + +``` +BCM2711 boot ROM -> SPI EEPROM bootloader -> VideoCore firmware (start4.elf) + -> kernel8.img (wolfBoot) -> verify (ECDSA/SHA) -> application +``` + +On CM4 modules with onboard eMMC the boot files live on the eMMC FAT boot partition; on CM4 Lite they live on a microSD. Either way the medium hangs off the BCM2711 EMMC2 controller (a standard SDHCI v3.0 Arasan block at `0xFE340000`). + +### Building + +``` +cp config/examples/cm4.config .config +make CROSS_COMPILE=aarch64-none-elf- +``` + +The example uses `SIGN=ECC384 HASH=SHA384` (both FIPS-approved). wolfBoot is entered by the firmware at `0x80000` at EL2, matching `hal/cm4.ld`. The image is loaded from RAM: wolfBoot reads the signed application at `kernel_addr` (`0x140000`), verifies it, copies it to `WOLFBOOT_LOAD_ADDRESS`, and boots. + +### Signing and assembling the boot image + +Sign the application, then concatenate wolfBoot and the signed image so the signed image lands at `kernel_addr` (`0x140000` = `0x80000` load + `0xC0000`): + +``` +make keytools tools/bin-assemble/bin-assemble +IMAGE_HEADER_SIZE=1024 ./tools/keytools/sign --ecc384 --sha384 \ + app.bin wolfboot_signing_private_key.der 1 +tools/bin-assemble/bin-assemble kernel8.img \ + 0x0 wolfboot.bin \ + 0xC0000 app_v1_signed.bin +``` + +### config.txt + +The CM4 UART on GPIO14/15 defaults to the mini-UART because the PL011 is used by Bluetooth. wolfBoot drives the PL011, so route it to the header and fix a known UART clock: + +``` +arm_64bit=1 +kernel=kernel8.img +enable_uart=1 +uart_2ndstage=1 +dtoverlay=disable-bt +init_uart_clock=48000000 +init_uart_baud=115200 +``` + +### Flashing + +- CM4 Lite: write `kernel8.img` + the RPi firmware (`start4.elf`, `fixup4.dat`) + `config.txt` to the microSD FAT boot partition. +- CM4 with eMMC: put the module in USB boot mode (nRPIBOOT), run `rpiboot` to expose the eMMC as USB mass storage, and write the same files to its FAT boot partition. See https://github.com/raspberrypi/usbboot . + +### Boot output + +With `DEBUG_UART=1`, a successful authenticated boot prints (115200 8N1): + +``` +wolfBoot CM4 (BCM2711 Cortex-A72) hal_init, EL2 +Trying partition 0 at 0x140000 +Checking integrity...done +Verifying signature...done +Firmware Valid +Booting at 0x3080000 +``` + +### Optional: eMMC/SD A/B updates + +`config/examples/cm4_sdcard.config` enables the disk updater (`DISK_SDCARD`/`DISK_EMMC`), driving the BCM2711 EMMC2 controller through the generic SDHCI driver (`src/sdhci.c`) for A/B image partitions on the boot medium. This path is provided as bring-up scaffolding and is not yet hardware-validated. + +### FIPS 140-3 + +The CM4 target uses `SIGN=ECC384 HASH=SHA384` (FIPS-approved) and can perform its signature verification with the wolfCrypt FIPS 140-3 validated module. The on-target harness in `test-app/app_cm4.c` registers a FIPS callback and prints the runtime in-core integrity hash over the UART for the `verifyCore[]` bootstrap. See [FIPS.md](FIPS.md) for the full build and hash-sealing procedure. + ## Xilinx Zynq UltraScale AMD Zynq UltraScale+ MPSoC ZCU102 Evaluation Kit - Quad-core ARM Cortex-A53 (plus dual Cortex-R5). diff --git a/hal/cm4.c b/hal/cm4.c new file mode 100644 index 0000000000..2a041ff051 --- /dev/null +++ b/hal/cm4.c @@ -0,0 +1,314 @@ +/* cm4.c + * + * HAL for the Raspberry Pi Compute Module 4 (CM4): Broadcom BCM2711, + * quad-core Cortex-A72 (ARMv8-A). + * + * The VideoCore GPU firmware loads wolfBoot (as kernel8.img) to 0x80000 and + * releases the A72 cores; wolfBoot verifies the appended signed payload and + * boots it from RAM. hal_flash_* are no-ops (no in-place flash in this mode). + * Optional eMMC/SD A/B via the generic SDHCI driver is at the end of the file. + * + * 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 +#include "image.h" +#include "printf.h" +#include "hal/cm4.h" +#ifndef ARCH_AARCH64 +# error "wolfBoot cm4 HAL: wrong architecture selected. Please compile with ARCH=AARCH64." +#endif + +/* Hardware register map (bases, UART, EMMC2/SDHCI) is in hal/cm4.h */ + +/* Fixed addresses (provided by the linker script) */ +extern void *kernel_addr, *update_addr, *dts_addr; + +#if defined(DEBUG_UART) +static void uart_tx(char c) +{ + while (*UART0_FR & 0x20) /* TXFF: wait while FIFO full */ + ; + *UART0_DR = c; +} + +void uart_write(const char* buf, uint32_t sz) +{ + while (sz-- > 0 && *buf) + uart_tx(*buf++); +} + +void uart_init(void) +{ + /* The VideoCore firmware has already routed the PL011 to GPIO14/15 + * (dtoverlay=disable-bt) and set init_uart_clock=48MHz. Program the PL011 + * for 115200 8N1 directly, without the VideoCore mailbox (a mailbox poll + * that never returns post-handoff would hang before any output). + * 48MHz UARTCLK: BAUDDIV = 48e6/(16*115200) = 26.04 -> IBRD 26, FBRD 3. */ + *UART0_CR = 0; + *UART0_ICR = 0x7FF; + *UART0_IBRD = 26; + *UART0_FBRD = 3; + *UART0_LCRH = (1 << 4) | (1 << 5) | (1 << 6); /* FIFO, 8-bit */ + *UART0_CR = (1 << 0) | (1 << 8) | (1 << 9); /* enable UART, TX, RX */ +} +#endif /* DEBUG_UART */ + +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 */ +} + +#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 */ + +void hal_init(void) +{ +#if defined(DEBUG_UART) + unsigned long el; + uart_init(); + __asm__ volatile("mrs %0, CurrentEL" : "=r"(el)); + wolfBoot_printf("wolfBoot CM4 (BCM2711 Cortex-A72) hal_init, EL%d\n", + (int)((el >> 2) & 0x3)); +#endif +} + +void hal_prepare_boot(void) +{ +} + +#if defined(HAVE_FIPS) +/* FIPS DRBG entropy seed from the BCM2711 RNG200 hardware TRNG. Registered via + * CUSTOM_RAND_GENERATE_SEED in include/user_settings.h. The RNG200 has NIST + * SP800-90B startup/continuous health tests in hardware. */ +int wolfBoot_fips_seed(unsigned char* output, unsigned int sz) +{ + static int rng_inited = 0; + unsigned int pos = 0; + unsigned int guard; + + if (!rng_inited) { + /* iproc-rng200 bring-up: disable RBG, soft-reset the RBG then RNG + * cores, clear pending interrupt status, then re-enable the RBG. */ + *RNG_CTRL = 0; + *RNG_RBG_SOFT_RESET = 1; + *RNG_RBG_SOFT_RESET = 0; + *RNG_SOFT_RESET = 1; + *RNG_SOFT_RESET = 0; + *RNG_INT_STATUS = 0xFFFFFFFF; /* write-1-to-clear all pending status */ + *RNG_CTRL = RNG200_CTRL_RBGEN; + rng_inited = 1; + } + + while (pos < sz) { + unsigned int word, n, i; + /* wait for at least one 32-bit word in the FIFO (bounded) */ + guard = 0; + while ((*RNG_FIFO_COUNT & 0xFF) == 0) { + if (++guard > 200000000U) { +#if defined(DEBUG_UART) + wolfBoot_printf("RNG200 FIFO timeout int=0x%08x ctrl=0x%08x\n", + (unsigned)*RNG_INT_STATUS, (unsigned)*RNG_CTRL); +#endif + return -1; + } + } + word = *RNG_FIFO_DATA; + n = (sz - pos) < 4 ? (sz - pos) : 4; + for (i = 0; i < n; i++) + output[pos++] = (unsigned char)(word >> (i * 8)); + } + +#if defined(DEBUG_UART) + { + static int traced = 0; + if (!traced) { + wolfBoot_printf("RNG200 seed %02x %02x %02x %02x %02x %02x %02x %02x\n", + output[0], output[1], output[2], output[3], + output[4], output[5], output[6], output[7]); + traced = 1; + } + } +#endif + return 0; +} +#endif /* HAVE_FIPS */ + +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; +} + +#if defined(DISK_SDCARD) || defined(DISK_EMMC) +/* BCM2711 EMMC2 platform glue for the generic SDHCI driver (src/sdhci.c). + * EMMC2 is a standard SDHCI v3.0 Arasan block at 0xFE340000. The driver uses + * Cadence-style SRS offsets (0x200 + std); translate them to the standard + * Arasan layout, mirroring the ZynqMP path in hal/zynq.c. NOTE: not yet + * hardware-validated; clock/caps/card-detect quirks may be required once + * validated on hardware. */ +#include "sdhci.h" + +uint32_t sdhci_reg_read(uint32_t offset) +{ + volatile uint8_t *base = (volatile uint8_t *)BCM2711_EMMC2_BASE; + + if (offset >= CADENCE_SRS_OFFSET) { + uint32_t std_off = offset - CADENCE_SRS_OFFSET; + uint32_t val; + + if (std_off == 0x58) /* SRS22 -> legacy SDMA address (SRS00) */ + return *((volatile uint32_t *)(base + STD_SDHCI_SDMA_ADDR)); + if (std_off == 0x5C) /* SRS23: no 64-bit addressing on v3.0 */ + return 0; + val = *((volatile uint32_t *)(base + std_off)); + if (std_off == 0x40) /* SRS16 Capabilities: mask A64S (no HV4E) */ + val &= ~SDHCI_SRS16_A64S; + return val; + } + return 0; /* HRS region not present on this Arasan block */ +} + +void sdhci_reg_write(uint32_t offset, uint32_t val) +{ + volatile uint8_t *base = (volatile uint8_t *)BCM2711_EMMC2_BASE; + uint32_t std_off; + + if (offset < CADENCE_SRS_OFFSET) + return; /* HRS region not present */ + std_off = offset - CADENCE_SRS_OFFSET; + + /* SRS10 (0x28): Host Control 1 / Power / Block Gap / Wakeup (8-bit each) */ + if (std_off == 0x28) { + *((volatile uint8_t *)(base + STD_SDHCI_HOST_CTRL1)) = (uint8_t)val; + *((volatile uint8_t *)(base + STD_SDHCI_POWER_CTRL)) = (uint8_t)(val >> 8); + *((volatile uint8_t *)(base + STD_SDHCI_BLKGAP_CTRL)) = (uint8_t)(val >> 16); + *((volatile uint8_t *)(base + STD_SDHCI_WAKEUP_CTRL)) = (uint8_t)(val >> 24); + return; + } + /* SRS11 (0x2C): Clock Control (16-bit) / Timeout / Software Reset */ + if (std_off == 0x2C) { + *((volatile uint16_t *)(base + STD_SDHCI_CLK_CTRL)) = (uint16_t)val; + *((volatile uint8_t *)(base + STD_SDHCI_TIMEOUT_CTRL)) = (uint8_t)(val >> 16); + *((volatile uint8_t *)(base + STD_SDHCI_SW_RESET)) = (uint8_t)(val >> 24); + return; + } + if (std_off == 0x58) { /* SRS22 -> legacy SDMA address; write restarts DMA */ + *((volatile uint32_t *)(base + STD_SDHCI_SDMA_ADDR)) = val; + return; + } + if (std_off == 0x5C) /* SRS23: no 64-bit addressing on v3.0 */ + return; + if (std_off == STD_SDHCI_HOST_CTRL2) /* SRS15: mask unsupported HV4E/A64 */ + val &= ~(SDHCI_SRS15_HV4E | SDHCI_SRS15_A64); + *((volatile uint32_t *)(base + std_off)) = val; +} + +void sdhci_platform_init(void) +{ + /* The GPU firmware already brought up the EMMC2 clock/pinmux; just issue a + * controller soft reset and wait for it to clear. */ + volatile uint8_t *base = (volatile uint8_t *)BCM2711_EMMC2_BASE; + volatile int i; + + *((volatile uint8_t *)(base + STD_SDHCI_SW_RESET)) = STD_SDHCI_SRA; + for (i = 0; i < 100000; i++) { + if ((*((volatile uint8_t *)(base + STD_SDHCI_SW_RESET)) & STD_SDHCI_SRA) == 0) + break; + } +} + +void sdhci_platform_irq_init(void) +{ + /* Polled mode; no IRQ wiring needed for the boot path. */ +} + +void sdhci_platform_set_bus_mode(int is_emmc) +{ + (void)is_emmc; /* handled by the generic driver via Host Control 1 */ +} + +/* Microseconds from the ARMv8 generic timer (SDHCI udelay + disk updater). + * Falls back to the BCM2711 system counter frequency if CNTFRQ_EL0 is 0. */ +uint64_t hal_get_timer_us(void) +{ + uint64_t count, freq; + __asm__ volatile("mrs %0, CNTPCT_EL0" : "=r"(count)); + __asm__ volatile("mrs %0, CNTFRQ_EL0" : "=r"(freq)); + if (freq == 0) + freq = BCM2711_TIMER_CLK_FREQ; + return (uint64_t)(((__uint128_t)count * 1000000ULL) / freq); +} +#endif /* DISK_SDCARD || DISK_EMMC */ diff --git a/hal/cm4.h b/hal/cm4.h new file mode 100644 index 0000000000..7b5d650364 --- /dev/null +++ b/hal/cm4.h @@ -0,0 +1,88 @@ +/* cm4.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 + */ + +/* Raspberry Pi Compute Module 4 (CM4) - Broadcom BCM2711, Cortex-A72. + * Hardware register map (bases, UART, EMMC2/SDHCI) shared by the HAL and the + * test application. This header is also included from boot_aarch64_start.S, so + * pointer-cast accessors are guarded with __ASSEMBLER__. */ + +#ifndef _CM4_H_ +#define _CM4_H_ + +/* Select the compact AArch64 startup path in boot_aarch64_start.S */ +#define USE_BUILTIN_STARTUP +#define USE_SIMPLE_STARTUP + +/* BCM2711 peripheral base addresses (low-peripheral 0xFE000000 view). + * Integer literals - safe to include from assembly. */ +#define BCM2711_MMIO_BASE 0xFE000000 +#define BCM2711_GPIO_BASE (BCM2711_MMIO_BASE + 0x200000) +#define BCM2711_UART0_BASE (BCM2711_GPIO_BASE + 0x1000) /* PL011 */ +#define BCM2711_EMMC2_BASE (BCM2711_MMIO_BASE + 0x340000) /* Arasan SDHCI */ +#define BCM2711_RNG_BASE (BCM2711_MMIO_BASE + 0x104000) /* RNG200 TRNG */ + +/* RNG200 (iproc-rng200) register offsets */ +#define RNG200_CTRL 0x00 /* bit0 RBGEN = enable */ +#define RNG200_SOFT_RESET 0x04 +#define RNG200_RBG_SOFT_RESET 0x08 +#define RNG200_INT_STATUS 0x18 +#define RNG200_FIFO_DATA 0x20 +#define RNG200_FIFO_COUNT 0x24 /* [7:0] = words available */ +#define RNG200_CTRL_RBGEN 0x00000001 + +/* BCM2711 system counter frequency (fallback if CNTFRQ_EL0 reads 0) */ +#define BCM2711_TIMER_CLK_FREQ 54000000ULL + +/* SDHCI register offsets used by the EMMC2 glue. The generic SDHCI driver + * addresses registers with Cadence-style SRS offsets (0x200 + std); the glue + * translates to the standard Arasan layout below. Integers, assembler-safe. */ +#define CADENCE_SRS_OFFSET 0x200 +#define STD_SDHCI_SDMA_ADDR 0x00 /* SDMA System Address (32-bit) */ +#define STD_SDHCI_HOST_CTRL1 0x28 +#define STD_SDHCI_POWER_CTRL 0x29 +#define STD_SDHCI_BLKGAP_CTRL 0x2A +#define STD_SDHCI_WAKEUP_CTRL 0x2B +#define STD_SDHCI_CLK_CTRL 0x2C +#define STD_SDHCI_TIMEOUT_CTRL 0x2E +#define STD_SDHCI_SW_RESET 0x2F +#define STD_SDHCI_HOST_CTRL2 0x3C +#define STD_SDHCI_SRA 0x01 /* Software Reset for All */ + +#ifndef __ASSEMBLER__ +/* PL011 UART0 register accessors */ +#define UART0_DR ((volatile unsigned int*)(BCM2711_UART0_BASE+0x00)) +#define UART0_FR ((volatile unsigned int*)(BCM2711_UART0_BASE+0x18)) +#define UART0_IBRD ((volatile unsigned int*)(BCM2711_UART0_BASE+0x24)) +#define UART0_FBRD ((volatile unsigned int*)(BCM2711_UART0_BASE+0x28)) +#define UART0_LCRH ((volatile unsigned int*)(BCM2711_UART0_BASE+0x2C)) +#define UART0_CR ((volatile unsigned int*)(BCM2711_UART0_BASE+0x30)) +#define UART0_ICR ((volatile unsigned int*)(BCM2711_UART0_BASE+0x44)) + +/* RNG200 hardware TRNG register accessors */ +#define RNG_CTRL ((volatile unsigned int*)(BCM2711_RNG_BASE+RNG200_CTRL)) +#define RNG_SOFT_RESET ((volatile unsigned int*)(BCM2711_RNG_BASE+RNG200_SOFT_RESET)) +#define RNG_RBG_SOFT_RESET ((volatile unsigned int*)(BCM2711_RNG_BASE+RNG200_RBG_SOFT_RESET)) +#define RNG_INT_STATUS ((volatile unsigned int*)(BCM2711_RNG_BASE+RNG200_INT_STATUS)) +#define RNG_FIFO_DATA ((volatile unsigned int*)(BCM2711_RNG_BASE+RNG200_FIFO_DATA)) +#define RNG_FIFO_COUNT ((volatile unsigned int*)(BCM2711_RNG_BASE+RNG200_FIFO_COUNT)) +#endif /* !__ASSEMBLER__ */ + +#endif /* _CM4_H_ */ diff --git a/hal/cm4.ld b/hal/cm4.ld new file mode 100644 index 0000000000..aff7e3dc6e --- /dev/null +++ b/hal/cm4.ld @@ -0,0 +1,71 @@ +/* cm4.ld + * + * Linker script for the Raspberry Pi Compute Module 4 (BCM2711, Cortex-A72). + * + * The VideoCore GPU firmware loads kernel8.img to 0x80000 and releases the + * A72 cores there. DDR_MEM length covers the 1GB low-peripheral view common + * to all CM4 RAM variants (1/2/4/8 GB); the peripheral block sits above at + * 0xFE000000 and is not mapped here. + */ +MEMORY +{ + DDR_MEM(rwx): ORIGIN = 0x00080000, LENGTH = 0x3c000000 +} +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(8); + /* Heap start for the newlib sbrk (FIPS module malloc). Grows up into free + * DDR; the stack lives at END_STACK (0x80000) and grows down. */ + PROVIDE(end = .); + PROVIDE(__end__ = .); + PROVIDE(_heap_start = .); +} + +END_STACK = _start_text; +kernel_addr = 0x0140000; +update_addr = 0x1140000; +dts_addr = 0x00a0000; +kernel_load_addr = 0x20000000; +dts_load_addr = 0x21000000; + +_wolfboot_partition_boot_address = kernel_addr; +_wolfboot_partition_update_address = update_addr; diff --git a/include/user_settings.h b/include/user_settings.h index 7886a1bcc2..bd96f9667c 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -43,6 +43,53 @@ #define HAVE_EMPTY_AGGREGATES 0 #define HAVE_ANONYMOUS_INLINE_AGGREGATES 0 +#ifdef HAVE_FIPS + /* wolfCrypt FIPS 140-3 module boundary algorithm set. FIPS requires the + * whole validated module (all approved algorithms) to be present so the + * power-on self-tests and in-core integrity check operate. See + * docs/FIPS.md. Enabled via FIPS=1 (-DHAVE_FIPS in options.mk). */ + #define WOLFSSL_FIPS_READY + /* Single-threaded: the FIPS POST runs at init before any other access, so + * the module's thread-local state is a plain global (no pthread TLS). */ + #define NO_THREAD_LS + /* wolfBoot's bare-metal startup does not run C constructors (.init_array), + * so the FIPS module's power-on self-test entry (fipsEntry) must be a + * normal callable function that wolfBoot invokes explicitly at startup. */ + #define NO_ATTRIBUTE_CONSTRUCTOR + #define WOLFSSL_BASE16 /* fips_test.c hash hex encode/decode */ + #define WOLFSSL_BASE64_ENCODE + #define WOLFSSL_SHA224 + #define WOLFSSL_SHA384 + #define WOLFSSL_SHA512 + #define WOLFSSL_SHA3 + #define HAVE_AESGCM + #define HAVE_AESCCM + #define HAVE_AES_ECB /* AES CAST uses wc_AesEcbEncrypt */ + #define HAVE_AES_CBC /* AES-CBC CAST */ + #define WOLFSSL_AES_COUNTER + #define WOLFSSL_AES_DIRECT + #define WOLFSSL_AES_CFB + #define WOLFSSL_AES_OFB + #define WOLFSSL_AES_XTS + #define WOLFSSL_CMAC + #define HAVE_HKDF + #define HAVE_ECC + #define WOLFSSL_ECDSA_SET_K + #define WOLFSSL_VALIDATE_ECC_IMPORT + #define WOLFSSL_VALIDATE_ECC_KEYGEN + #define WOLFSSL_KEY_GEN + #define WOLFSSL_PUBLIC_MP + #define WOLFSSL_SP_MATH_ALL + /* FIPS DRBG entropy seed source. The RNG must NOT be disabled in FIPS mode + * (see the undef block below which removes WC_NO_RNG/WC_NO_HASHDRBG). The + * seed comes from a HAL-provided source: /dev/urandom on sim, the BCM2711 + * RNG200 hardware TRNG on CM4. */ + #if defined(ARCH_SIM) || defined(TARGET_cm4) + #define CUSTOM_RAND_GENERATE_SEED wolfBoot_fips_seed + extern int wolfBoot_fips_seed(unsigned char* output, unsigned int sz); + #endif +#endif /* HAVE_FIPS */ + /* Stdlib Types */ #define CTYPE_USER /* don't let wolfCrypt types.h include ctype.h */ @@ -126,7 +173,7 @@ extern int tolower(int c); !defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK) && \ !defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) && \ !defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) -# if !defined(WOLFBOOT_TPM) +# if !defined(WOLFBOOT_TPM) && !defined(HAVE_FIPS) # define NO_ECC_SIGN # define NO_ECC_DHE /* For Renesas RX do not enable the misc.c constant time code @@ -346,7 +393,7 @@ extern int tolower(int c); #ifdef WOLFBOOT_HASH_SHA3_384 # define WOLFSSL_SHA3 # if defined(NO_RSA) && !defined(WOLFBOOT_TPM) && \ - !defined(WOLFCRYPT_SECURE_MODE) && \ + !defined(WOLFCRYPT_SECURE_MODE) && !defined(HAVE_FIPS) && \ !defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK) # define NO_SHA256 # endif @@ -355,7 +402,7 @@ extern int tolower(int c); #ifdef WOLFBOOT_HASH_SHA384 # define WOLFSSL_SHA384 # if defined(NO_RSA) && !defined(WOLFBOOT_TPM) && \ - !defined(WOLFCRYPT_SECURE_MODE) && \ + !defined(WOLFCRYPT_SECURE_MODE) && !defined(HAVE_FIPS) && \ !defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK) # define NO_SHA256 # endif @@ -513,7 +560,8 @@ extern int tolower(int c); #endif #if !defined(WOLFCRYPT_SECURE_MODE) && !defined(WOLFBOOT_TPM_PARMENC) && \ - !defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK) + !defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK) && \ + !defined(HAVE_FIPS) #if !(defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) && \ defined(WOLFBOOT_SIGN_ML_DSA)) && \ !defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) @@ -560,7 +608,8 @@ extern int tolower(int c); #if !defined(ENCRYPT_WITH_AES128) && !defined(ENCRYPT_WITH_AES256) && \ !defined(WOLFBOOT_TPM_PARMENC) && !defined(WOLFCRYPT_SECURE_MODE) && \ !defined(SECURE_PKCS11) && !defined(WOLFCRYPT_TZ_PSA) && \ - !defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK) + !defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK) && \ + !defined(HAVE_FIPS) #define NO_AES #endif @@ -623,6 +672,25 @@ extern int tolower(int c); #define NO_CHECK_PRIVATE_KEY #define NO_KDF +#ifdef HAVE_FIPS + /* The FIPS validated module requires its whole boundary present; undo the + * lean verify-only disables above so the module's approved algorithms and + * their power-on self-tests operate (see docs/FIPS.md). */ + #undef NO_HMAC + #undef NO_CMAC + #undef NO_SHA + #undef NO_KDF + #undef NO_ASN + #undef NO_DEV_RANDOM + #undef NO_ECC_KEY_EXPORT + #undef WC_NO_RNG + #undef WC_NO_HASHDRBG + #undef NO_PWDBASED + #undef NO_CODING + #undef NO_AES_CBC /* FIPS AES-CBC CAST needs CBC mode */ + #define HAVE_PBKDF2 +#endif /* HAVE_FIPS */ + /* wolfCrypt Test/Benchmark Configuration */ #ifdef WOLFCRYPT_TEST /* Skip extended tests to save memory */ diff --git a/options.mk b/options.mk index b6adb4a588..eda3a1720c 100644 --- a/options.mk +++ b/options.mk @@ -1681,3 +1681,50 @@ endif ifeq ($(TZEN),1) CFLAGS+=-DTZEN endif + +# --------------------------------------------------------------------------- +# wolfCrypt FIPS 140-3 module (FIPS=1) +# +# Point WOLFBOOT_LIB_WOLFSSL at an unpacked FIPS / FIPS-ready wolfSSL tree. +# The in-core integrity hash on GCC/ELF is enforced by LINK ORDER: +# wolfcrypt_first.o must be first and wolfcrypt_last.o last, with the FIPS +# boundary (crypto + fips.o + fips_test.o) between them. We therefore rebuild +# WOLFCRYPT_OBJS from scratch in that order, replacing the piecemeal per-SIGN +# selection above (the SIGN/HASH CFLAGS remain in effect). See docs/FIPS.md. +ifeq ($(FIPS),1) + CFLAGS+=-DHAVE_FIPS + # The FIPS module pulls in libc malloc/printf. On bare-metal targets provide + # a heap (store_sbrk) and stub the remaining newlib syscalls. store_sbrk.o is + # NOT part of the FIPS boundary, so it goes in OBJS, not WOLFCRYPT_OBJS. + ifneq ($(ARCH),sim) + LDFLAGS += --specs=nosys.specs + OBJS += src/store_sbrk.o + endif + WCDIR=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src + WOLFCRYPT_OBJS := \ + $(WCDIR)/wolfcrypt_first.o \ + $(WCDIR)/hash.o \ + $(WCDIR)/hmac.o \ + $(WCDIR)/kdf.o \ + $(WCDIR)/pwdbased.o \ + $(WCDIR)/random.o \ + $(WCDIR)/sha.o \ + $(WCDIR)/sha256.o \ + $(WCDIR)/sha512.o \ + $(WCDIR)/sha3.o \ + $(WCDIR)/aes.o \ + $(WCDIR)/cmac.o \ + $(WCDIR)/ecc.o \ + $(WCDIR)/sp_int.o \ + $(WCDIR)/wolfmath.o \ + $(WCDIR)/memory.o \ + $(WCDIR)/wc_port.o \ + $(WCDIR)/logging.o \ + $(WCDIR)/error.o \ + $(WCDIR)/coding.o \ + $(WCDIR)/asn.o \ + $(WCDIR)/wc_encrypt.o \ + $(WCDIR)/fips.o \ + $(WCDIR)/fips_test.o \ + $(WCDIR)/wolfcrypt_last.o +endif diff --git a/src/boot_aarch64_start.S b/src/boot_aarch64_start.S index 544b5e8de9..21156e0c3f 100644 --- a/src/boot_aarch64_start.S +++ b/src/boot_aarch64_start.S @@ -38,6 +38,10 @@ #include "hal/raspi3.h" #endif +#ifdef TARGET_cm4 +#include "hal/cm4.h" +#endif + /* GICv2 Register Offsets */ #ifndef GICD_BASE #define GICD_BASE 0xF9010000 diff --git a/src/loader.c b/src/loader.c index 3c26f4db42..211b58cf93 100644 --- a/src/loader.c +++ b/src/loader.c @@ -58,6 +58,22 @@ static volatile const uint32_t __attribute__((used)) wolfboot_version = WOLFBOOT extern void (** const IV_RAM)(void); #endif +#ifdef HAVE_FIPS +#include "printf.h" +#include +/* FIPS module power-on self-test entry. With NO_ATTRIBUTE_CONSTRUCTOR it is a + * plain function (not an .init_array constructor) that wolfBoot calls at boot. */ +extern void fipsEntry(void); +/* wolfCrypt FIPS in-core integrity / CAST callback. On a hash mismatch + * (IN_CORE_FIPS_E) the module reports the runtime hash here; copy it into + * verifyCore[] in wolfcrypt/src/fips_test.c and rebuild (see docs/FIPS.md). */ +static void wolfBoot_fipsCb(int ok, int err, const char* hash) +{ + wolfBoot_printf("FIPS callback: ok=%d err=%d\n", ok, err); + wolfBoot_printf("hash = %s\n", hash != NULL ? hash : "(null)"); +} +#endif + #ifdef TARGET_sim /** * @brief Command line arguments for the test-app in sim mode. @@ -106,6 +122,21 @@ int main(void) wolfBoot_hook_preinit(); #endif hal_init(); +#ifdef HAVE_FIPS + /* Run the FIPS power-on self-test (in-core integrity + CASTs) and refuse + * to boot unless the module is operational. See docs/FIPS.md. */ + wolfCrypt_SetCb_fips(wolfBoot_fipsCb); + fipsEntry(); + if (wolfCrypt_GetStatus_fips() != 0) { + wolfBoot_printf("FIPS 140-3 module NOT operational (status=%d); halting\n", + wolfCrypt_GetStatus_fips()); + /* On self-test failure this returns the runtime in-core hash to seal + * into verifyCore[] in fips_test.c. */ + wolfBoot_printf("FIPS in-core hash = %s\n", wolfCrypt_GetCoreHash_fips()); + wolfBoot_panic(); + } + wolfBoot_printf("FIPS 140-3 module operational\n"); +#endif #ifdef TEST_FLASH hal_flash_test(); #endif diff --git a/test-app/app_cm4.c b/test-app/app_cm4.c new file mode 100644 index 0000000000..865479cd8f --- /dev/null +++ b/test-app/app_cm4.c @@ -0,0 +1,128 @@ +/* app_cm4.c + * + * Test application for Raspberry Pi CM4 (BCM2711). Prints a banner over the + * PL011 UART and, when built against wolfCrypt FIPS (HAVE_FIPS), runs the + * power-on self-tests and registers a FIPS callback that reports the runtime + * in-core integrity hash for the verifyCore[] bootstrap. + * + * 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 "wolfboot/wolfboot.h" +#include "hal/cm4.h" /* BCM2711 UART register map */ + +#ifdef HAVE_FIPS +#include +#include +#include +#endif + +#ifdef TARGET_cm4 + +static void uart_init(void) +{ + /* PL011 for 115200 8N1 from the 48 MHz UART clock (see hal/cm4.c) */ + *UART0_CR = 0; + *UART0_ICR = 0x7FF; + *UART0_IBRD = 26; + *UART0_FBRD = 3; + *UART0_LCRH = (1 << 4) | (1 << 5) | (1 << 6); + *UART0_CR = (1 << 0) | (1 << 8) | (1 << 9); +} + +static void uart_putc(char c) +{ + while (*UART0_FR & 0x20) /* wait while TX FIFO full */ + ; + *UART0_DR = (unsigned int)c; +} + +static void uart_puts(const char* s) +{ + while (*s) { + if (*s == '\n') + uart_putc('\r'); + uart_putc(*s++); + } +} + +static void uart_putdec(int v) +{ + char buf[12]; + unsigned int u; + int i = 0; + + if (v < 0) { + uart_putc('-'); + u = (unsigned int)(-v); + } + else { + u = (unsigned int)v; + } + do { + buf[i++] = (char)('0' + (u % 10)); + u /= 10; + } while (u != 0); + while (i > 0) + uart_putc(buf[--i]); +} + +#ifdef HAVE_FIPS +/* wolfCrypt FIPS callback. On an in-core integrity mismatch (IN_CORE_FIPS_E) + * the module reports the runtime hash here; copy it into verifyCore[] in + * wolfcrypt/src/fips_test.c and rebuild to seal the module boundary. */ +static void cm4_fipsCb(int ok, int err, const char* hash) +{ + uart_puts("FIPS callback: ok="); + uart_putdec(ok); + uart_puts(" err="); + uart_putdec(err); + uart_puts("\nhash = "); + uart_puts(hash != NULL ? hash : "(null)"); + uart_puts("\n"); + if (err == IN_CORE_FIPS_E) { + uart_puts("In-core integrity mismatch: copy the hash above into\n"); + uart_puts("verifyCore[] in wolfcrypt/src/fips_test.c and rebuild.\n"); + } +} +#endif /* HAVE_FIPS */ + +void main(void) +{ + uart_init(); + uart_puts("\n=== wolfBoot CM4 test-app ===\n"); + +#ifdef HAVE_FIPS + uart_puts("wolfCrypt FIPS 140-3 power-on self-test\n"); + wolfCrypt_SetCb_fips(cm4_fipsCb); + if (wc_RunAllCast_fips() == 0) + uart_puts("FIPS CASTs: PASS\n"); + else + uart_puts("FIPS CASTs: FAIL (see callback output above)\n"); + uart_puts("FIPS status: "); + uart_putdec(wolfCrypt_GetStatus_fips()); + uart_puts("\n"); +#endif /* HAVE_FIPS */ + + uart_puts("test-app done; halting.\n"); + while (1) + ; +} +#endif /* TARGET_cm4 */