Skip to content

Commit 921931a

Browse files
committed
cm4: add wolfCrypt FIPS 140-3 support (sim + CM4 hardware validated)
1 parent 9dcfd5c commit 921931a

11 files changed

Lines changed: 475 additions & 17 deletions

File tree

config/examples/cm4-fips.config

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Raspberry Pi CM4 (BCM2711) with the wolfCrypt FIPS 140-3 module.
2+
# Point WOLFBOOT_LIB_WOLFSSL at an unpacked FIPS / FIPS-ready wolfSSL tree.
3+
# Entropy comes from the BCM2711 RNG200 hardware TRNG (hal/cm4.c). See
4+
# docs/FIPS.md for the in-core hash-seal procedure (done on-target over UART).
5+
ARCH?=AARCH64
6+
TARGET?=cm4
7+
SIGN?=ECC384
8+
HASH?=SHA384
9+
FIPS?=1
10+
WOLFBOOT_LIB_WOLFSSL?=../wolfssl-5.9.2-gplv3-fips-ready
11+
DEBUG?=1
12+
DEBUG_UART?=1
13+
VTOR?=1
14+
SPMATH?=1
15+
NO_ARM_ASM?=1
16+
PKA?=0
17+
WOLFTPM?=0
18+
NO_XIP?=1
19+
NO_QNX?=1
20+
IMAGE_HEADER_SIZE?=1024
21+
WOLFBOOT_SECTOR_SIZE=0x400
22+
WOLFBOOT_NO_PARTITIONS=1
23+
WOLFBOOT_RAMBOOT_MAX_SIZE=0x20000000
24+
WOLFBOOT_LOAD_ADDRESS?=0x3080000
25+
WOLFBOOT_LOAD_DTS_ADDRESS?=0x400000

config/examples/sim-fips.config

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# wolfBoot simulator build using the wolfCrypt FIPS 140-3 module.
2+
# Point WOLFBOOT_LIB_WOLFSSL at an unpacked FIPS (or FIPS-ready) wolfSSL tree.
3+
# Prototype target for the CM4 FIPS integration (see docs/FIPS.md).
4+
ARCH=sim
5+
TARGET=sim
6+
SIGN?=ECC384
7+
HASH?=SHA384
8+
FIPS?=1
9+
WOLFBOOT_LIB_WOLFSSL?=../wolfssl-5.9.2-gplv3-fips-ready
10+
WOLFBOOT_SMALL_STACK?=0
11+
SPI_FLASH=0
12+
DEBUG=1
13+
14+
# sizes should be multiple of system page size
15+
WOLFBOOT_PARTITION_SIZE=0x40000
16+
WOLFBOOT_SECTOR_SIZE=0x1000
17+
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000
18+
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x100000
19+
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x180000
20+
21+
# required for keytools
22+
WOLFBOOT_FIXED_PARTITIONS=1

docs/FIPS.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# wolfBoot with wolfCrypt FIPS 140-3
2+
3+
This page explains how wolfBoot performs its firmware signature verification with the wolfCrypt FIPS 140-3 module, and what a fully CMVP-validated deployment additionally requires. Read it before making any FIPS claim about a wolfBoot deployment.
4+
5+
## Two distinct things: approved algorithms vs. a validated module
6+
7+
FIPS 140-3 has two separate requirements that are easy to conflate:
8+
9+
1. Using FIPS **approved algorithms** for the security-relevant operations (here: image signature verification and hashing).
10+
2. Performing those operations inside the **CMVP-validated wolfCrypt module** boundary, with the power-on self-test (POST), the in-core integrity check, and status gating (`wolfCrypt_GetStatus_fips`).
11+
12+
A stock wolfBoot build only addresses (1): it compiles individual `wolfcrypt/src/*.c` files selected by `SIGN`/`HASH`, with no POST or in-core integrity check. Building with `FIPS=1` (this page) addresses (2): wolfBoot links the wolfCrypt FIPS module boundary, runs the POST + in-core check at boot, and refuses to boot unless the module is operational.
13+
14+
A production-validated deployment still requires the **licensed, validated** wolfCrypt FIPS bundle at the exact validated revision (not the evaluation "FIPS-ready" drop), the validated module version, and adherence to the module's Security Policy. Contact wolfSSL (facts@wolfssl.com) for the current certificate, validated module version, and integration guidance for a specific target.
15+
16+
## Approved algorithms for image authentication
17+
18+
wolfBoot authenticates images with a public-key signature over a hash of the image. The default wolfBoot signature algorithm, **ED25519, is NOT FIPS approved** and must not be used for a FIPS configuration.
19+
20+
Approved pairs (select in the target `.config`):
21+
22+
- Signature (`SIGN=`): `ECC256` / `ECC384` / `ECC521` (ECDSA P-256/P-384/P-521), or `RSAPSS2048` / `RSAPSS3072` / `RSAPSS4096` (RSA-PSS).
23+
- Hash (`HASH=`): `SHA256`, `SHA384`. Match or exceed the signature strength (e.g. P-384 with SHA-384).
24+
- Set `SPMATH=1` (the single-precision math backend the wolfCrypt FIPS module is validated with).
25+
26+
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.
27+
28+
## Getting the FIPS source
29+
30+
Obtain a FIPS wolfCrypt source tree. For evaluation, the FIPS-ready bundle can be downloaded from wolfSSL:
31+
32+
```
33+
https://www.wolfssl.com/wolfssl-5.9.2-gplv3-fips-ready.zip
34+
```
35+
36+
Production use requires the licensed, validated FIPS bundle. Unpack it and point wolfBoot at it with `WOLFBOOT_LIB_WOLFSSL`.
37+
38+
## Building wolfBoot with FIPS
39+
40+
The `FIPS=1` build option (`options.mk`) rebuilds the wolfcrypt object list as the validated module boundary in link order (`wolfcrypt_first.o` first, `wolfcrypt_last.o` last, with `fips.o`/`fips_test.o` and the boundary crypto between them - the in-core integrity hash on GCC/ELF is enforced by this link order). Point the build at the FIPS tree and select an approved algorithm pair:
41+
42+
```
43+
cp config/examples/sim-fips.config .config # or cm4-fips.config
44+
make FIPS=1 WOLFBOOT_LIB_WOLFSSL=/path/to/wolfssl-5.9.2-gplv3-fips-ready \
45+
SIGN=ECC384 HASH=SHA384 SPMATH=1
46+
```
47+
48+
`-DHAVE_FIPS` is added by the `FIPS=1` block; `settings.h` in the FIPS tree defines the exact version (the FIPS-ready 5.9.2 bundle reports `HAVE_FIPS_VERSION 7`). The `HAVE_FIPS` block in `include/user_settings.h` enables the module's algorithm set, keeps the RNG/DRBG enabled, and wires the entropy seed (below).
49+
50+
## Entropy source (required)
51+
52+
The FIPS DRBG needs a seed. wolfBoot's lean configuration compiles out the OS seed paths, so a seed is provided via `CUSTOM_RAND_GENERATE_SEED` (the `HAVE_FIPS` block in `include/user_settings.h` keeps the RNG enabled by undoing wolfBoot's `WC_NO_RNG`/`WC_NO_HASHDRBG`). The example wiring points it 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`.
53+
54+
## Sealing the in-core integrity hash
55+
56+
The module verifies an in-core integrity hash (HMAC-SHA-256 over the module's code and read-only data) at startup. A fresh build ships with a placeholder, so the first run reports a mismatch; capture the runtime hash and seal it:
57+
58+
1. Build and run with a FIPS callback registered (wolfBoot does this in `src/loader.c`). On a mismatch the module reports the runtime hash; wolfBoot prints it (`FIPS in-core hash = ...`, from `wolfCrypt_GetCoreHash_fips()`), and on the CM4 the test app (`test-app/app_cm4.c`) prints it over UART.
59+
2. Copy the reported 64-hex-character hash into `verifyCore[]` in `wolfcrypt/src/fips_test.c`.
60+
3. Rebuild and re-run. `wolfCrypt_GetStatus_fips()` now returns 0 (operational).
61+
62+
The seal is **specific to the exact binary layout**: any code change that shifts the FIPS module's link addresses changes the in-core hash and requires a re-seal. Re-sealing `verifyCore[]` itself does not shift addresses (same-size rewrite), so once the rest of the build is fixed the seal converges in one pass.
63+
64+
## Bare-metal targets
65+
66+
The FIPS module targets a hosted environment; a few things must be provided on bare-metal (the CM4 does all of these):
67+
68+
- **POST entry.** The module registers its POST via a C constructor (`.init_array`), which a hosted runtime runs before `main()`. wolfBoot's bare-metal startup does not run `.init_array`, so build with `NO_ATTRIBUTE_CONSTRUCTOR` and call `fipsEntry()` explicitly (`src/loader.c`).
69+
- **Normal (cacheable) memory.** wolfBoot's simple startup runs with the MMU off, where all memory is Device-nGnRnE and unaligned / 128-bit SIMD accesses fault (the FIPS module and newlib `printf`/`snprintf` do both). The CM4 HAL enables a minimal identity MMU with DDR mapped Normal cacheable before the POST (`cm4_mmu_enable`), and tears it down (clean D-cache, disable MMU/caches) before the boot handoff (`cm4_mmu_disable`) so the loaded image is coherent and the application starts MMU-off.
70+
- **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`).
71+
72+
Bring the module up on the simulator (`config/examples/sim-fips.config`) first - it exercises the whole flow (module boundary, POST, in-core seal, verify, A/B update) with no hardware.
73+
74+
## Verifying operation
75+
76+
- 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).
77+
- wolfBoot treats a non-zero FIPS status as a hard failure and refuses to boot (`src/loader.c`).
78+
- A deliberately corrupted module boundary (flip a byte) makes the in-core check fail and blocks the boot - the negative test for the integration.
79+
80+
## See also
81+
82+
- [Targets.md](Targets.md) - Raspberry Pi Compute Module 4 (BCM2711) target. FIPS 140-3 authenticated boot (module operational -> SHA-384 integrity -> ECDSA-P384 verify seeded by the BCM2711 hardware TRNG -> handoff) is validated on CM4 hardware.

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ See also: [wolfBoot Product Overview](https://www.wolfssl.com/products/wolfboot/
1111
- [**encrypted_partitions.md**](./encrypted_partitions.md) - Creating and managing encrypted firmware/data partitions.
1212
- [**firmware_image.md**](./firmware_image.md) - wolfBoot firmware image format, layout, and metadata.
1313
- [**firmware_update.md**](./firmware_update.md) - Update flow: slots, verification, rollback, and recovery.
14+
- [**FIPS.md**](./FIPS.md) - Building wolfBoot with the wolfCrypt FIPS 140-3 module: approved algorithms, DRBG entropy, and in-core integrity sealing.
1415
- [**flash-OTP.md**](./flash-OTP.md) - Using One-Time Programmable (OTP) regions in flash for secure data.
1516
- [**flash_partitions.md**](./flash_partitions.md) - Flash partitioning schemes and configuration guidance.
1617
- [**HAL.md**](./HAL.md) - Hardware Abstraction Layer notes and porting considerations.

docs/Targets.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3767,7 +3767,7 @@ Booting at 0x3080000
37673767
37683768
### FIPS 140-3
37693769
3770-
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.
3770+
The CM4 target uses `SIGN=ECC384 HASH=SHA384` (FIPS-approved) and can perform its signature verification with the wolfCrypt FIPS 140-3 module (`config/examples/cm4-fips.config`, `FIPS=1`). At boot the module runs its power-on self-test and in-core integrity check, and wolfBoot refuses to boot unless the module is operational. Entropy for the FIPS DRBG comes from the BCM2711 RNG200 hardware TRNG. This has been validated end to end on CM4 hardware: FIPS module operational -> SHA-384 integrity -> ECDSA-P384 signature verification -> boot handoff. The on-target harness in `test-app/app_cm4.c` prints the runtime in-core hash over the UART for the `verifyCore[]` seal. See [FIPS.md](FIPS.md) for the full build, entropy, and hash-sealing procedure.
37713771
37723772
## Xilinx Zynq UltraScale
37733773

hal/cm4.c

Lines changed: 128 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@
4242
/* Fixed addresses (provided by the linker script) */
4343
extern void *kernel_addr, *update_addr, *dts_addr;
4444

45+
#if defined(HAVE_FIPS)
46+
void cm4_mmu_enable(void); /* defined below; called from hal_init */
47+
void cm4_mmu_disable(void); /* defined below; called from hal_prepare_boot */
48+
#endif
49+
4550
#if defined(DEBUG_UART)
4651
static void uart_tx(char c)
4752
{
@@ -129,12 +134,135 @@ void hal_init(void)
129134
wolfBoot_printf("wolfBoot CM4 (BCM2711 Cortex-A72) hal_init, EL%d\n",
130135
(int)((el >> 2) & 0x3));
131136
#endif
137+
#if defined(HAVE_FIPS)
138+
/* Bring up Normal cacheable memory before the FIPS POST, which uses
139+
* unaligned / SIMD accesses that the MMU-off Device memory rejects. */
140+
cm4_mmu_enable();
141+
#endif
132142
}
133143

134144
void hal_prepare_boot(void)
135145
{
146+
#if defined(HAVE_FIPS)
147+
/* Undo cm4_mmu_enable() before handoff: flush the app out of the D-cache
148+
* and return to the MMU-off state the application expects. */
149+
cm4_mmu_disable();
150+
#endif
136151
}
137152

153+
#if defined(HAVE_FIPS)
154+
/* Minimal identity-mapped MMU + caches for the CM4. wolfBoot's simple startup
155+
* runs with the MMU off, so all memory is Device-nGnRnE, which faults on the
156+
* unaligned / 128-bit SIMD accesses the FIPS module and newlib printf perform.
157+
* Mapping DDR as Normal (cacheable) permits those accesses and speeds up the
158+
* crypto; the peripheral region (incl. 0xFE000000) stays Device.
159+
* Four 1GB block descriptors cover the 32-bit VA space at translation level 1. */
160+
#define MMU_BLOCK_NORMAL 0x0000000000000701ULL /* block, AttrIdx0, AF, SH inner */
161+
#define MMU_BLOCK_DEVICE 0x0000000000000405ULL /* block, AttrIdx1, AF, SH none */
162+
163+
static volatile uint64_t cm4_l1_table[512] __attribute__((aligned(4096)));
164+
165+
void cm4_mmu_enable(void)
166+
{
167+
unsigned long sctlr;
168+
int i;
169+
170+
/* 0-3GB DDR -> Normal; 3-4GB peripherals (0xFE000000) -> Device. */
171+
for (i = 0; i < 4; i++) {
172+
uint64_t base = (uint64_t)i << 30;
173+
cm4_l1_table[i] = base | ((i == 3) ? MMU_BLOCK_DEVICE : MMU_BLOCK_NORMAL);
174+
}
175+
/* MAIR: Attr0 = 0xFF Normal WB write-alloc, Attr1 = 0x00 Device-nGnRnE. */
176+
__asm__ volatile("msr mair_el2, %0" :: "r"(0x00000000000000FFUL));
177+
__asm__ volatile("msr ttbr0_el2, %0"
178+
:: "r"((uint64_t)(uintptr_t)cm4_l1_table));
179+
/* TCR_EL2: T0SZ=32 (32-bit VA), 4KB granule, WB cacheable inner-shareable
180+
* table walks, 36-bit PA. */
181+
__asm__ volatile("msr tcr_el2, %0" :: "r"(0x0000000000013520UL));
182+
__asm__ volatile("isb");
183+
__asm__ volatile("tlbi alle2");
184+
__asm__ volatile("dsb sy");
185+
__asm__ volatile("ic iallu");
186+
__asm__ volatile("dsb sy");
187+
__asm__ volatile("isb");
188+
/* SCTLR_EL2: enable MMU (M), data cache (C), instruction cache (I). */
189+
__asm__ volatile("mrs %0, sctlr_el2" : "=r"(sctlr));
190+
sctlr |= (1UL << 0) | (1UL << 2) | (1UL << 12);
191+
__asm__ volatile("msr sctlr_el2, %0" :: "r"(sctlr));
192+
__asm__ volatile("isb");
193+
}
194+
195+
/* Clean+invalidate all data cache levels by set/way (dc cisw). */
196+
static void cm4_dcache_flush_all(void)
197+
{
198+
uint64_t clidr, ccsidr;
199+
unsigned int level, loc, ctype, linesize, ways, sets, way, set, wayshift;
200+
201+
__asm__ volatile("dsb sy");
202+
__asm__ volatile("mrs %0, clidr_el1" : "=r"(clidr));
203+
loc = (unsigned int)((clidr >> 24) & 0x7); /* Level of Coherency */
204+
for (level = 0; level < loc; level++) {
205+
ctype = (unsigned int)((clidr >> (level * 3)) & 0x7);
206+
if (ctype < 2) /* no data/unified cache at this level */
207+
continue;
208+
__asm__ volatile("msr csselr_el1, %0" :: "r"((uint64_t)(level << 1)));
209+
__asm__ volatile("isb");
210+
__asm__ volatile("mrs %0, ccsidr_el1" : "=r"(ccsidr));
211+
linesize = (unsigned int)(ccsidr & 0x7) + 4; /* log2(bytes) */
212+
ways = (unsigned int)((ccsidr >> 3) & 0x3FF); /* assoc - 1 */
213+
sets = (unsigned int)((ccsidr >> 13) & 0x7FFF); /* sets - 1 */
214+
wayshift = (unsigned int)__builtin_clz(ways);
215+
for (set = 0; set <= sets; set++) {
216+
for (way = 0; way <= ways; way++) {
217+
uint64_t val = ((uint64_t)(level << 1))
218+
| ((uint64_t)way << wayshift)
219+
| ((uint64_t)set << linesize);
220+
__asm__ volatile("dc cisw, %0" :: "r"(val));
221+
}
222+
}
223+
}
224+
__asm__ volatile("dsb sy");
225+
__asm__ volatile("isb");
226+
}
227+
228+
/* Tear down the MMU/caches before boot handoff: clean the freshly-copied app
229+
* out of the D-cache to memory, disable the MMU and caches, and invalidate the
230+
* I-cache/TLB. Returns the CPU to the MMU-off state the application (and the
231+
* ARM64 Linux boot protocol) expects. */
232+
void cm4_mmu_disable(void)
233+
{
234+
unsigned long sctlr;
235+
236+
cm4_dcache_flush_all();
237+
__asm__ volatile("mrs %0, sctlr_el2" : "=r"(sctlr));
238+
sctlr &= ~((1UL << 0) | (1UL << 2) | (1UL << 12)); /* clear M, C, I */
239+
__asm__ volatile("msr sctlr_el2, %0" :: "r"(sctlr));
240+
__asm__ volatile("isb");
241+
__asm__ volatile("ic iallu");
242+
__asm__ volatile("tlbi alle2");
243+
__asm__ volatile("dsb sy");
244+
__asm__ volatile("isb");
245+
}
246+
#endif /* HAVE_FIPS */
247+
248+
#if defined(DEBUG) && defined(DEBUG_UART)
249+
/* CM4 bring-up diagnostic: exception handler invoked from cm4_vectors in
250+
* src/boot_aarch64_start.S. Dumps the fault syndrome so a data/instruction
251+
* abort shows up over UART instead of hanging silently. Built only with
252+
* DEBUG + DEBUG_UART. ESR_EL2[31:26] = exception class. */
253+
void cm4_fault_handler(unsigned long esr, unsigned long elr, unsigned long far);
254+
void cm4_fault_handler(unsigned long esr, unsigned long elr, unsigned long far)
255+
{
256+
wolfBoot_printf("\n*** CM4 EXCEPTION ***\n");
257+
wolfBoot_printf("ESR_EL2=0x%08x EC=0x%02x\n",
258+
(unsigned)esr, (unsigned)((esr >> 26) & 0x3F));
259+
wolfBoot_printf("ELR_EL2=0x%08x%08x\n",
260+
(unsigned)(elr >> 32), (unsigned)elr);
261+
wolfBoot_printf("FAR_EL2=0x%08x%08x\n",
262+
(unsigned)(far >> 32), (unsigned)far);
263+
}
264+
#endif /* DEBUG && DEBUG_UART */
265+
138266
#if defined(HAVE_FIPS)
139267
/* FIPS DRBG entropy seed from the BCM2711 RNG200 hardware TRNG. Registered via
140268
* CUSTOM_RAND_GENERATE_SEED in include/user_settings.h. The RNG200 has NIST
@@ -176,18 +304,6 @@ int wolfBoot_fips_seed(unsigned char* output, unsigned int sz)
176304
for (i = 0; i < n; i++)
177305
output[pos++] = (unsigned char)(word >> (i * 8));
178306
}
179-
180-
#if defined(DEBUG_UART)
181-
{
182-
static int traced = 0;
183-
if (!traced) {
184-
wolfBoot_printf("RNG200 seed %02x %02x %02x %02x %02x %02x %02x %02x\n",
185-
output[0], output[1], output[2], output[3],
186-
output[4], output[5], output[6], output[7]);
187-
traced = 1;
188-
}
189-
}
190-
#endif
191307
return 0;
192308
}
193309
#endif /* HAVE_FIPS */

hal/sim.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,28 @@ static int sim_cryptocb(int devIdArg, wc_CryptoInfo* info, void* ctx)
122122
#include "port/posix/posix_flash_file.h"
123123
#endif /* WOLFBOOT_ENABLE_WOLFHSM_SERVER */
124124

125+
#if defined(HAVE_FIPS)
126+
/* FIPS DRBG entropy seed for the simulator: read from /dev/urandom.
127+
* Registered via CUSTOM_RAND_GENERATE_SEED in include/user_settings.h. */
128+
int wolfBoot_fips_seed(unsigned char* output, unsigned int sz)
129+
{
130+
unsigned int pos = 0;
131+
int fd = open("/dev/urandom", O_RDONLY);
132+
if (fd < 0)
133+
return -1;
134+
while (pos < sz) {
135+
ssize_t r = read(fd, output + pos, sz - pos);
136+
if (r <= 0) {
137+
close(fd);
138+
return -1;
139+
}
140+
pos += (unsigned int)r;
141+
}
142+
close(fd);
143+
return 0;
144+
}
145+
#endif /* HAVE_FIPS */
146+
125147
/* Global pointer to the internal and external flash base */
126148
uint8_t *sim_ram_base;
127149
static uint8_t *flash_base;

0 commit comments

Comments
 (0)