From a1fb869da582013f21ec9aa1ac862da3107059e4 Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 9 Sep 2026 16:02:16 -0700 Subject: [PATCH] Add Nuvoton NuMaker-M2354 wolfCrypt test and benchmark example --- embedded/nuvoton_m2354/.gitignore | 2 + embedded/nuvoton_m2354/README.md | 131 ++++++ embedded/nuvoton_m2354/app.c | 352 ++++++++++++++ embedded/nuvoton_m2354/build.sh | 246 ++++++++++ embedded/nuvoton_m2354/keystore_test.c | 161 +++++++ embedded/nuvoton_m2354/run_test.sh | 272 +++++++++++ embedded/nuvoton_m2354/secure/nuvoton_nsc.c | 483 ++++++++++++++++++++ embedded/nuvoton_m2354/user_settings.h | 163 +++++++ 8 files changed, 1810 insertions(+) create mode 100644 embedded/nuvoton_m2354/.gitignore create mode 100644 embedded/nuvoton_m2354/README.md create mode 100644 embedded/nuvoton_m2354/app.c create mode 100755 embedded/nuvoton_m2354/build.sh create mode 100644 embedded/nuvoton_m2354/keystore_test.c create mode 100755 embedded/nuvoton_m2354/run_test.sh create mode 100644 embedded/nuvoton_m2354/secure/nuvoton_nsc.c create mode 100644 embedded/nuvoton_m2354/user_settings.h diff --git a/embedded/nuvoton_m2354/.gitignore b/embedded/nuvoton_m2354/.gitignore new file mode 100644 index 000000000..9ab2398c7 --- /dev/null +++ b/embedded/nuvoton_m2354/.gitignore @@ -0,0 +1,2 @@ +build/ +logs/ diff --git a/embedded/nuvoton_m2354/README.md b/embedded/nuvoton_m2354/README.md new file mode 100644 index 000000000..c15547971 --- /dev/null +++ b/embedded/nuvoton_m2354/README.md @@ -0,0 +1,131 @@ +# wolfCrypt on the NuMaker-M2354 + +Builds `wolfcrypt_test` followed by the wolfCrypt benchmark for the +[NuMaker-M2354](https://www.nuvoton.com/board/numaker-m2354/) (NuMicro +M2354KJFAE, Cortex-M23, 1 MB flash, 256 KB SRAM), with the CRPT accelerator, +the TRNG and the Key Store driven through the crypto callback port in +`wolfcrypt/src/port/nuvoton` in the wolfSSL repository, whose README +covers what each engine does and what it declines. + +## What you need + +- `arm-none-eabi-gcc` (any recent release; developed against 13.2) +- A wolfSSL checkout carrying the Nuvoton port +- The Nuvoton BSP: + +```sh +git clone --depth 1 https://github.com/OpenNuvoton/M2354BSP +``` + +Nothing from the BSP is copied into this tree; the build reads it where you +cloned it. + +## Build + +```sh +./build.sh --bsp /path/to/M2354BSP --wolfssl /path/to/wolfssl +``` + +wolfSSL defaults to `../../../wolfssl` and can also come from `WOLFSSL_ROOT`, +so the `--wolfssl` argument is unnecessary if you keep the two checkouts +side by side. + +That produces `build/wolfcrypt-m2354.elf` and `build/wolfcrypt-m2354.bin` for +the secure world, which is also where a plain non-TrustZone application runs. + +For the TrustZone split: + +```sh +./build.sh --bsp /path/to/M2354BSP --tz +``` + +This compiles both halves - the port as the non-secure image sees it, and +`secure/nuvoton_nsc.c` with `-mcmse` - but does not link. A working TrustZone +application also needs a partition layout and a secure bootstrap, and those +belong to your project rather than to wolfSSL. `--tz` does show that the port +and the veneers agree on every signature, which is the part that is easy to get +wrong. + +## Flash and run + +`run_test.sh` does the whole cycle - find the probe, build, take the shared-bench lock, hand the console to the flasher, program, give the console back and capture: + +```sh +./run_test.sh --bsp /path/to/M2354BSP --seconds 180 +``` + +It takes a lock at `/tmp/claude-bench-locks/numaker-m2354.lock` so two people +cannot drive one board at once, and releases it even if the run fails. + +It flashes with `pyocd`, which ships a **builtin** target for this exact part (`m2354kjfae`), so no CMSIS pack download is needed. That requires the Nu-Link2-Me to be presenting as a CMSIS-DAP probe; see the firmware note below. + +Doing it by hand instead: + +The board carries a detachable Nu-Link2-Me. Either of these works: + +- **NuLink command line** (Nuvoton's tool, Windows and Linux): + `NuLink -w APROM build/wolfcrypt-m2354.bin` +- **OpenOCD**, using Nuvoton's fork + ([OpenNuvoton/OpenOCD-Nuvoton](https://github.com/OpenNuvoton/OpenOCD-Nuvoton)) + with `interface/nulink.cfg`. Nuvoton also publishes a `NuLink2_DAPLink.bin` + adapter firmware that makes the probe a CMSIS-DAP device, after which + `pyocd flash` works. + +Console output goes to UART0 at **115200 8N1**, which the Nu-Link2-Me presents +as a virtual COM port. + +## Expected output + +``` +wolfCrypt on NuMaker-M2354 (96 MHz) +TrustZone: secure world, direct BSP calls +Nuvoton crypto callback device registered as devId 820 + +--- wolfcrypt_test --- +... +--- benchmark --- +... +``` + +## Files + +| File | What it is | +|---|---| +| `user_settings.h` | wolfCrypt configuration; a starting point for your own | +| `app.c` | Clocks, UART console, the CRPT interrupt handler, and `main()` | +| `secure/nuvoton_nsc.c` | Non-secure callable veneers for a TrustZone build | +| `build.sh` | The build described above | +| `run_test.sh` | Flash and capture on a real board, taking the bench lock | + +`app.c` is worth reading for two things a project of your own also needs: the +`CRPT_IRQHandler` that routes the crypto interrupt to `ECC_DriverISR()`, +without which every ECC and RSA call times out, and the `current_time()` and +`LowResTimer()` the benchmark asks for. + +## Status + +`wolfcrypt_test` runs to completion on a NuMaker-M2354 with no failures, the +Key Store round trip in `keystore_test.c` passes, and the benchmark runs. + +`WOLFSSL_KEY_GEN` is left off in `user_settings.h`, behind an `#if 0` that +explains why: it enables RSA key generation and a `wc_DhGenerateParams()` call +for a fresh 2048-bit parameter set, both software primality searches the +accelerator has no part in, and the DH one runs for hours here. A capture that +stops after the RSA test with no further output is usually that, not a fault. The port README in wolfSSL carries the throughput +table. + +**`build.sh --tz` is a compile check**, not a runnable image: it builds both +halves so a change cannot break either, but it does not link them, because a +working TrustZone application also needs a partition layout and a secure +bootstrap that belong to your project rather than to this example. + +The veneers in `secure/nuvoton_nsc.c` have been run on hardware separately, in +a two-image build against a part whose NSCBA is `0x00080000`. Every algorithm +in the port reached the accelerator from the non-secure world through them, +including ECC, which depends on the CRPT interrupt being taken in the secure +world. The wolfSSL port README records that result, and one behaviour in a +combined `wolfcrypt_test()` run that is not yet understood. + +# Support + +For questions please email support@wolfssl.com diff --git a/embedded/nuvoton_m2354/app.c b/embedded/nuvoton_m2354/app.c new file mode 100644 index 000000000..8ac570664 --- /dev/null +++ b/embedded/nuvoton_m2354/app.c @@ -0,0 +1,352 @@ +/* app.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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 + */ + +/* wolfCrypt test and benchmark on the NuMaker-M2354. + * + * Brings the clocks and the console up, registers the crypto callback device, + * then runs the algorithm test followed by the benchmark. Output goes to + * UART0, which the on board Nu-Link2-Me presents as a virtual COM port at + * 115200 baud. + */ + +/* NuMicro.h first: M2354.h defines TRUE and FALSE unconditionally and would + * redefine the ones types.h puts up behind an #ifndef. */ +#include "NuMicro.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Provided by the BSP linker script; used by the fault report and by _sbrk. */ +/* Top of the 256 KB the part implements, for the stacked frame sanity check in + * the fault handler. SRAM_BASE comes from M2354.h. */ +#define APP_SRAM_TOP (SRAM_BASE + 0x40000u) + +extern uint32_t __StackLimit; +extern uint32_t __HeapBase; +extern uint32_t __HeapLimit; + +/* Bounded heap. + * + * The BSP's _syscalls.c does not define _sbrk, so the link would otherwise + * pull in the libnosys one, which moves the break with no bounds check at all + * and happily returns addresses past the end of physical RAM. The failure is + * not a clean out-of-memory: malloc succeeds, the caller writes, and the fault + * lands somewhere unrelated - here it surfaced as memcpy inside _realloc_r + * storing to 0x20040000, one word past the top of SRAM. + * + * Defining _sbrk here means the linker resolves it from this object and never + * looks in the library. Running out of heap now returns ENOMEM, which + * wolfCrypt turns into MEMORY_E and reports against the test that asked. */ +void* _sbrk(ptrdiff_t incr) +{ + static char* heapEnd = NULL; + char* prev; + + if (heapEnd == NULL) { + heapEnd = (char*)&__HeapBase; + } + + if (incr < 0 || (heapEnd + incr) > (char*)&__HeapLimit) { + errno = ENOMEM; + return (void*)-1; + } + + prev = heapEnd; + heapEnd += incr; + + return prev; +} + +/* High-water mark of the heap, for reporting after the test run. */ +static size_t heap_used(void) +{ + char* base = (char*)&__HeapBase; + void* cur = _sbrk(0); + + if (cur == (void*)-1) { + return 0; + } + + return (size_t)((char*)cur - base); +} + +/* Free running millisecond counter, driven by SysTick. */ +static volatile word32 msTicks = 0; + +void SysTick_Handler(void); +void CRPT_IRQHandler(void); + +void SysTick_Handler(void) +{ + msTicks++; +} + +/* The BSP public key drivers block on a flag that only this interrupt sets + * (see ECC_DriverISR in Library/StdDriver/src/crypto.c). Without this handler + * every ECC and RSA call would spin until its timeout and fail. */ +void CRPT_IRQHandler(void) +{ + ECC_DriverISR(CRPT); +} + +/* WOLFSSL_USER_CURRTIME: seconds, used by the benchmark to work out rates. */ +double current_time(int reset) +{ + if (reset) { + msTicks = 0; + } + + return (double)msTicks / 1000.0; +} + +/* USER_TICKS: coarse seconds counter. */ +word32 LowResTimer(void) +{ + return msTicks / 1000; +} + +/* Route printf to UART0. + * + * UART_WRITE() is a bare store to DAT with no flow control, and the UART0 + * transmit FIFO is 16 bytes deep. At 96 MHz the CPU fills that far faster than + * 115200 baud drains it, so writing without checking loses everything after + * the first FIFO-full - which looks like a baud rate or wiring fault rather + * than a software one, because what does arrive is correctly spelled. Wait for + * space before every byte. */ +static void uart_put(char c) +{ + while (UART_IS_TX_FULL(UART0)) { + /* Wait for the transmit FIFO to drain. */ + } + UART_WRITE(UART0, (uint8_t)c); +} + +int _write(int fd, const char* buf, int len) +{ + int i; + + (void)fd; + + for (i = 0; i < len; i++) { + if (buf[i] == '\n') { + uart_put('\r'); + } + uart_put(buf[i]); + } + + return len; +} + +/* The BSP startup calls these two by name. ProcessHardFault gets the stacked + * register frame and returns the address to resume at; parking in a loop keeps + * a fault visible under the debugger instead of silently restarting. + * SH_Return belongs to the BSP's semihosting path, which is not used here. */ +uint32_t ProcessHardFault(uint32_t lr, uint32_t msp, uint32_t psp); +int32_t SH_Return(int32_t n32In_R0, int32_t n32In_R1, int32_t* pn32Out_R0); + +uint32_t ProcessHardFault(uint32_t lr, uint32_t msp, uint32_t psp) +{ + const uint32_t* frame; + + /* EXC_RETURN bit 2 says which stack the exception frame went on. The frame + * is r0, r1, r2, r3, r12, lr, pc, xpsr - so the faulting instruction is at + * index 6. ARMv8-M baseline has no SecureFault and no CFSR to consult, so + * the stacked PC is most of what there is to go on: feed it to + * arm-none-eabi-addr2line against the .elf. */ + frame = (const uint32_t*)((lr & 0x4u) ? psp : msp); + + printf("\nhard fault\n"); + printf(" exc_return 0x%08lx msp 0x%08lx psp 0x%08lx\n", + (unsigned long)lr, (unsigned long)msp, (unsigned long)psp); + printf(" sp 0x%08lx stack limit 0x%08lx\n", + (unsigned long)frame, (unsigned long)&__StackLimit); + + /* A stack that collided with the bottom of RAM leaves the frame pointer + * outside SRAM, and reading it here would fault again - which on ARMv8-M + * baseline escalates to Lockup and prints nothing at all. That is the very + * case the stack limit line above is here to diagnose, so the register + * dump is skipped rather than risked. */ + if ((uint32_t)frame < SRAM_BASE || + (uint32_t)frame > (APP_SRAM_TOP - 8u * sizeof(uint32_t))) { + printf(" stacked frame outside SRAM, not dumping registers\n"); + while (1) { + /* Stop here so the fault state can be read out. */ + } + } + + printf(" pc 0x%08lx\n", (unsigned long)frame[6]); + printf(" lr 0x%08lx\n", (unsigned long)frame[5]); + printf(" xpsr 0x%08lx\n", (unsigned long)frame[7]); + printf(" r0 0x%08lx r1 0x%08lx\n", + (unsigned long)frame[0], (unsigned long)frame[1]); + printf(" r2 0x%08lx r3 0x%08lx\n", + (unsigned long)frame[2], (unsigned long)frame[3]); + while (1) { + /* Stop here so the fault state can be read out. */ + } +} + +int32_t SH_Return(int32_t n32In_R0, int32_t n32In_R1, int32_t* pn32Out_R0) +{ + (void)n32In_R0; + (void)n32In_R1; + (void)pn32Out_R0; + + return 0; +} + +static void SYS_Init(void) +{ + SYS_UnlockReg(); + + /* Only SRAM bank 0 is clocked out of reset: AHBCLK comes up as 0x00108000, + * so SRAM0CKEN (bit 20) is set while SRAM1CKEN (21) and SRAM2CKEN (22) are + * clear. Everything at or above 0x20008000 is dead, and writes to it are + * silently discarded rather than faulting - so the failure is wrong + * results or a garbage return address, not an exception pointing at the + * access. Enable them before anything can allocate. Found on silicon by + * the wolfBoot M2354 session, whose stack was at the top of SRAM and which + * sat in Lockup with a perfectly valid image in flash. */ + CLK->AHBCLK |= CLK_AHBCLK_SRAM1CKEN_Msk | CLK_AHBCLK_SRAM2CKEN_Msk; + + /* 12 MHz crystal into the PLL, out at the part's 96 MHz ceiling. + * + * Go through CLK_SetCoreClock() rather than writing CLKSEL0 directly. It + * calls CLK_SetHCLK(), which does three things in a strict order that a + * hand-rolled sequence has to reproduce exactly: + * + * 1. Parks HCLK on HIRC before touching anything else. + * 2. Raises the power level to PL0 and waits for PLSTS.PLCBUSY to + * clear. Only PL0 supports 96 MHz; PL1 tops out at 84 MHz. + * 3. Sets FMC->CYCCTL CYCLE to 4 flash access cycles, which is what + * 96 MHz needs, and only then switches HCLK to the PLL. + * + * Skipping either the power level or the wait states mostly works from a + * cold reset, because reset leaves the part at PL0 - the failure shows up + * on a warm reset that arrives at a lower level or with fewer wait states, + * as an intermittent hard fault or garbage instruction fetch that points + * nowhere near the clock code. Found in the wolfBoot M2354 HAL, which + * reimplements this against raw registers; noted here so a later + * simplification of this file does not reintroduce it. */ + CLK_EnableXtalRC(CLK_PWRCTL_HXTEN_Msk); + CLK_WaitClockReady(CLK_STATUS_HXTSTB_Msk); + CLK_SetCoreClock(FREQ_96MHZ); + + /* HIRC for UART0, so the console baud rate does not move when the PLL + * engages. Enable HIRC explicitly rather than assuming it is on: + * CLK_SetHCLK() samples the HIRC stable flag on entry and, if it found + * HIRC off, switches it back off before returning (clk.c, "Disable HIRC if + * HIRC is disabled before switching HCLK source"). Pointing UART0 at a + * source that was just switched off gives a console that works from a cold + * reset - where HIRC is on by default - and is silent after any warm reset + * that arrives with it off. That is a whole afternoon lost to suspecting + * the PB12/PB13 pin choice instead. */ + CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk); + CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk); + CLK_EnableModuleClock(UART0_MODULE); + CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL2_UART0SEL_HIRC, + CLK_CLKDIV0_UART0(1)); + + /* The NuMaker-M2354 wires the Nu-Link2-Me virtual COM port to UART0 on + * PA6 (RXD) and PA7 (TXD). Not PB12/PB13: those are also a valid UART0 + * assignment on this part, which is why the BSP offers both and why + * picking from the macro names alone is not enough, but they go nowhere on + * this board. Confirmed on hardware - a PB12/PB13 build transmits happily + * (FIFOSTS reports TXEMPTY) with nothing arriving at the host. */ + SYS->GPA_MFPL &= ~(SYS_GPA_MFPL_PA6MFP_Msk | SYS_GPA_MFPL_PA7MFP_Msk); + SYS->GPA_MFPL |= (SYS_GPA_MFPL_PA6MFP_UART0_RXD | + SYS_GPA_MFPL_PA7MFP_UART0_TXD); + + SystemCoreClockUpdate(); + + SYS_LockReg(); +} + +int keystore_test(void); + +int main(void) +{ + int ret; + + SYS_Init(); + UART_Open(UART0, 115200); + + /* One tick per millisecond for current_time() and LowResTimer(). */ + SysTick_Config(SystemCoreClock / 1000); + + /* The public key engines need this interrupt; see CRPT_IRQHandler. */ + NVIC_EnableIRQ(CRPT_IRQn); + + printf("\nwolfCrypt on NuMaker-M2354 (%u MHz)\n", + (unsigned int)(SystemCoreClock / 1000000)); +#ifdef WOLFSSL_NUVOTON_NSC + printf("TrustZone: non-secure, hardware through NSC veneers\n"); +#else + printf("TrustZone: secure world, direct BSP calls\n"); +#endif + + ret = wolfCrypt_Init(); + if (ret != 0) { + printf("wolfCrypt_Init failed: %d\n", ret); + return ret; + } + + ret = wc_NuvotonCryptoCb_RegisterDevice(WOLFSSL_NUVOTON_DEVID); + if (ret != 0) { + printf("Nuvoton device registration failed: %d\n", ret); + return ret; + } + printf("Nuvoton crypto callback device registered as devId %d\n", + WOLFSSL_NUVOTON_DEVID); + + /* The Key Store is not covered by wolfcrypt_test, so it gets its own + * round trip first. */ + ret = keystore_test(); + printf("keystore_test returned %d\n", ret); + + printf("\n--- wolfcrypt_test ---\n"); + ret = wolfcrypt_test(NULL); + printf("wolfcrypt_test returned %d\n", ret); + printf("heap used %u of %u bytes\n", (unsigned int)heap_used(), + (unsigned int)((char*)&__HeapLimit - (char*)&__HeapBase)); + + printf("\n--- benchmark ---\n"); + ret = benchmark_test(NULL); + printf("benchmark_test returned %d\n", ret); + + wc_NuvotonCryptoCb_UnRegisterDevice(WOLFSSL_NUVOTON_DEVID); + wolfCrypt_Cleanup(); + + printf("\ndone\n"); + + while (1) { + /* Nothing left to do; leave the console output on screen. */ + } +} diff --git a/embedded/nuvoton_m2354/build.sh b/embedded/nuvoton_m2354/build.sh new file mode 100755 index 000000000..c290fbfb3 --- /dev/null +++ b/embedded/nuvoton_m2354/build.sh @@ -0,0 +1,246 @@ +#!/bin/bash +# +# Build the wolfCrypt test and benchmark application for the NuMaker-M2354. +# +# Copyright (C) 2006-2026 wolfSSL Inc. +# +# This file is part of wolfSSL. +# +# Usage: +# ./build.sh --bsp /path/to/M2354BSP [--wolfssl /path/to/wolfssl] +# ./build.sh --bsp /path/to/M2354BSP --tz TrustZone compile check +# +# wolfSSL defaults to ../../../wolfssl, or set WOLFSSL_ROOT. Get the BSP with: +# git clone --depth 1 https://github.com/OpenNuvoton/M2354BSP +# +# The secure build links a complete .elf you can flash. The --tz build is a +# compile check of both halves of the TrustZone split: the port sources as the +# non-secure image sees them, and the secure veneers with -mcmse. It does not +# link, because a working TrustZone application also needs a partition layout +# and a secure bootstrap that belong to your project rather than to wolfSSL. + +set -e + +CC=${CC:-arm-none-eabi-gcc} +OBJCOPY=${OBJCOPY:-arm-none-eabi-objcopy} +SIZE=${SIZE:-arm-none-eabi-size} +NM=${NM:-arm-none-eabi-nm} + +BSP="" +TZ=0 +HERE="$(cd "$(dirname "$0")" && pwd)" +ROOT="${WOLFSSL_ROOT:-$HERE/../../../wolfssl}" +OUT="$HERE/build" + +while [ $# -gt 0 ]; do + case "$1" in + --bsp) BSP="$2"; shift 2 ;; + --wolfssl) ROOT="$2"; shift 2 ;; + --tz) TZ=1; shift ;; + --out) OUT="$2"; shift 2 ;; + *) echo "unknown option: $1" >&2; exit 1 ;; + esac +done + +if [ ! -f "$ROOT/wolfcrypt/src/port/nuvoton/nuvoton_hw.c" ]; then + echo "no wolfSSL with the Nuvoton port at: $ROOT" >&2 + echo "point at one with --wolfssl PATH or WOLFSSL_ROOT." >&2 + exit 1 +fi +ROOT="$(cd "$ROOT" && pwd)" + +if [ -z "$BSP" ]; then + echo "give the BSP location with --bsp /path/to/M2354BSP" >&2 + exit 1 +fi + +DEV="$BSP/Library/Device/Nuvoton/M2354" +STD="$BSP/Library/StdDriver" + +for d in "$DEV/Include" "$STD/inc" "$BSP/Library/CMSIS/Include"; do + if [ ! -d "$d" ]; then + echo "not a M2354BSP checkout: $d is missing" >&2 + exit 1 + fi +done + +# Clean only the object directory this invocation will use, not the whole +# output tree. The two legs share $OUT, so wiping it means a --tz run deletes +# the .elf the secure run just produced - which you discover later when +# something wants it for symbol resolution and it is gone. +if [ "$TZ" -eq 0 ]; then + rm -rf "$OUT/obj" +else + rm -rf "$OUT/obj-ns" "$OUT/tz" +fi +mkdir -p "$OUT" + +INC="-I$ROOT -I$HERE -I$DEV/Include -I$STD/inc -I$BSP/Library/CMSIS/Include" + +CFLAGS="-mcpu=cortex-m23 -mthumb -Os -ffunction-sections -fdata-sections" +CFLAGS="$CFLAGS -Wall -Wextra -Werror -DWOLFSSL_USER_SETTINGS $INC" + +# wolfCrypt, minus the two files that are #included into others rather than +# compiled on their own. +WC_SRC="" +for f in "$ROOT"/wolfcrypt/src/*.c; do + case "$(basename "$f")" in + misc.c|evp.c) continue ;; + esac + WC_SRC="$WC_SRC $f" +done +WC_SRC="$WC_SRC $ROOT/wolfcrypt/test/test.c $ROOT/wolfcrypt/benchmark/benchmark.c" + +PORT_SRC="$ROOT/wolfcrypt/src/port/nuvoton/nuvoton_hw.c + $ROOT/wolfcrypt/src/port/nuvoton/nuvoton_cryptocb.c + $ROOT/wolfcrypt/src/port/nuvoton/nuvoton_cb_rng.c + $ROOT/wolfcrypt/src/port/nuvoton/nuvoton_cb_hash.c + $ROOT/wolfcrypt/src/port/nuvoton/nuvoton_cb_cipher.c + $ROOT/wolfcrypt/src/port/nuvoton/nuvoton_cb_pk.c + $ROOT/wolfcrypt/src/port/nuvoton/nuvoton_key.c" + +BSP_SRC="$DEV/Source/system_M2354.c + $STD/src/clk.c + $STD/src/sys.c + $STD/src/uart.c + $STD/src/crypto.c + $STD/src/keystore.c + $STD/src/rng.c + $STD/src/fmc.c" + +# The BSP is vendor code and does not build clean under our warning set (its +# RSA driver compares a pointer against NULL as an integer), so it gets the +# same flags without -Werror. +BSPFLAGS="-mcpu=cortex-m23 -mthumb -Os -ffunction-sections -fdata-sections" +BSPFLAGS="$BSPFLAGS -DWOLFSSL_USER_SETTINGS $INC" + +compile() { + # compile + local dir="$1"; shift + local flags="$1"; shift + local src obj + + mkdir -p "$dir" + for src in "$@"; do + obj="$dir/$(echo "$src" | md5sum | cut -c1-12)_$(basename "$src" | sed 's/\.[cS]$//').o" + # shellcheck disable=SC2086 + $CC $flags -c "$src" -o "$obj" || exit 1 + echo "$obj" + done +} + +if [ "$TZ" -eq 0 ]; then + echo "=== secure world build ===" + + # shellcheck disable=SC2086 + OBJS=$(compile "$OUT/obj" "$CFLAGS -DWOLFSSL_NUVOTON_SECURE" \ + $WC_SRC $PORT_SRC "$HERE/app.c" "$HERE/keystore_test.c") || exit 1 + # shellcheck disable=SC2086 + OBJS="$OBJS $(compile "$OUT/obj" "$BSPFLAGS" $BSP_SRC)" || exit 1 + + # The BSP startup is assembly and the retarget file wants the BSP's own + # warning level, so neither goes through -Werror. + # startup_M2354.S reserves the heap with an #ifndef-guarded Heap_Size, so + # it can be raised from here. The BSP default is 10 KB, which is thin once + # wolfcrypt_test wants a 2 KB RSA working buffer plus hash accumulation. + # This is only safe because app.c enables the SRAM1/SRAM2 clocks: a heap + # reaching past 0x20008000 with those gated off would take writes and + # discard them silently. 32 KB is ample: the hash path streams through the + # engine with a fixed ~350 bytes of state per context, so heap use no + # longer scales with the message being hashed. + # Stack_Size is #ifndef-guarded in startup_M2354.S too. The BSP default is + # 12 KB, which software ECC overruns: gcc_arm.ld puts the stack at the + # BOTTOM of RAM with __StackLimit at 0x20000000, so an overflow runs off + # the start of SRAM and the fault handler has no stack left to report it. + # The part simply goes quiet, which is indistinguishable from a hang. + $CC -mcpu=cortex-m23 -mthumb $INC -DHeap_Size=${HEAP_SIZE:-0x00010000} \ + -DStack_Size=${STACK_SIZE:-0x00008000} \ + -c "$DEV/Source/GCC/startup_M2354.S" -o "$OUT/obj/startup.o" + $CC -mcpu=cortex-m23 -mthumb -Os $INC -c "$DEV/Source/GCC/_syscalls.c" \ + -o "$OUT/obj/syscalls.o" + + # shellcheck disable=SC2086 + $CC -mcpu=cortex-m23 -mthumb -T"$DEV/Source/GCC/gcc_arm.ld" \ + -Wl,--gc-sections -Wl,-Map="$OUT/wolfcrypt-m2354.map" \ + --specs=nano.specs --specs=nosys.specs -u _printf_float \ + $OBJS "$OUT/obj/startup.o" "$OUT/obj/syscalls.o" \ + -o "$OUT/wolfcrypt-m2354.elf" -lm + + $OBJCOPY -O binary "$OUT/wolfcrypt-m2354.elf" "$OUT/wolfcrypt-m2354.bin" + $SIZE "$OUT/wolfcrypt-m2354.elf" + + # VTOR alignment. The low bits of VTOR are RES0, and how many depends on + # the exception count: the M2354 implements 132 (16 system + 116 external), + # so the table is 528 bytes and VTOR has to be 1024-aligned - not the 128 + # or 256 a smaller part would need. Linking at 0x00000000 satisfies that by + # construction, but a misaligned table does not fail at boot: do_boot-style + # handoff code reads the initial SP and PC out of the image directly, so it + # starts, prints and runs, and then the first exception of any kind vectors + # through the wrong address. Check it here rather than discover it that way. + VEC=$($NM "$OUT/wolfcrypt-m2354.elf" | awk '$3 == "__Vectors" {print $1}') + VEC_END=$($NM "$OUT/wolfcrypt-m2354.elf" | awk '$3 == "__Vectors_End" {print $1}') + if [ -n "$VEC" ]; then + VEC_D=$((0x$VEC)) + if [ $((VEC_D % 1024)) -ne 0 ]; then + echo "vector table at 0x$VEC is not 1024-aligned" >&2 + exit 1 + fi + if [ -n "$VEC_END" ]; then + echo "vectors: 0x$VEC, $(( 0x$VEC_END - VEC_D )) bytes, 1024-aligned" + fi + fi + + echo + echo "built $OUT/wolfcrypt-m2354.elf" + echo "flash with: nuvoton NuLink or OpenOCD, see README.md" +else + echo "=== TrustZone compile check ===" + + # A TrustZone project must put its own partition_M2354.h ahead of the + # BSP's, which errors out if it is the one that gets picked up. wolfSSL + # does not ship a copy of that Nuvoton file, so take the BSP's and drop + # the guard; a real project edits it for its own memory split. + # + # FMC_INIT_NSBA is also forced to 0. The BSP ships it as 1, and with that + # set the FMC_NSBA_Setup() in system_M2354.c does not merely check the + # non-secure boundary: if SCU->FNSADDR disagrees with FMC_SECURE_ROM_SIZE + # it erases the config page, programs the new value and issues a chip + # reset, from SystemInit(), before main(). A header derived here is for a + # compile check and carries the BSP's default 0x80000, which is very + # unlikely to be what a given part is actually set to, so leaving it on + # would mean any image linked from it moved the part's secure boundary at + # every boot. Setting the boundary is a provisioning step, not something a + # build-test header should do. + mkdir -p "$OUT/tz" + sed -e '/# error "Link to default partition_M2354.h in secure mode/d' \ + -e 's/^#define FMC_INIT_NSBA .*/#define FMC_INIT_NSBA 0/' \ + "$STD/inc/partition_M2354.h" > "$OUT/tz/partition_M2354.h" + + grep -q '^#define FMC_INIT_NSBA 0' "$OUT/tz/partition_M2354.h" || { + echo "failed to disable FMC_INIT_NSBA in the derived partition header" >&2 + exit 1 + } + + echo "--- non-secure: the port as wolfCrypt sees it" + # shellcheck disable=SC2086 + compile "$OUT/obj-ns" "$CFLAGS -DWOLFSSL_NUVOTON_NSC" $PORT_SRC >/dev/null + + # The local partition header has to come first on the include path, ahead + # of the BSP's, or the BSP's is the one that gets picked up. + TZFLAGS="-I$OUT/tz -mcmse" + TZFLAGS="$TZFLAGS -DWOLFSSL_NUVOTON_SECURE -DWOLFSSL_NUVOTON_NSC_IMPL" + + echo "--- secure: the NSC veneers" + # shellcheck disable=SC2086 + $CC $TZFLAGS $CFLAGS \ + -c "$HERE/secure/nuvoton_nsc.c" -o "$OUT/obj-ns/nuvoton_nsc.o" + + echo "--- secure: the hardware layer the veneers call" + # shellcheck disable=SC2086 + $CC $TZFLAGS $CFLAGS \ + -c "$ROOT/wolfcrypt/src/port/nuvoton/nuvoton_hw.c" \ + -o "$OUT/obj-ns/nuvoton_hw_s.o" + + echo + echo "both halves compile" +fi diff --git a/embedded/nuvoton_m2354/keystore_test.c b/embedded/nuvoton_m2354/keystore_test.c new file mode 100644 index 000000000..c35aa6f6c --- /dev/null +++ b/embedded/nuvoton_m2354/keystore_test.c @@ -0,0 +1,161 @@ +/* keystore_test.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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 2 of the License, or + * (at your option) any later version. + * + * wolfSSL 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., 02110-1301, USA + */ + +/* Key Store round trip for the Nuvoton M2354 port. + * + * wolfcrypt_test() does not cover wrapped keys, so this exercises a key that + * never leaves the store: written to a slot, used by handle for AES, read + * back where allowed, and erased. ECC keys from the store are not supported + * by the port, so there is nothing to test for them here. + * + * SRAM slots only by default. A Flash slot has no per-key erase, only an + * irreversible revoke, so a run would spend one for good; build with + * -DM2354_KS_TEST_FLASH to include that leg. */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define KS_CHECK(cond, msg) \ + do { \ + if (!(cond)) { \ + printf(" FAIL %s (line %d)\n", (msg), __LINE__); \ + fails++; \ + } \ + } while (0) + +/* AES-256-CBC with the key in a slot, against the same key in software. */ +static int ks_aes_test(int mem, const char* memName) +{ + static const byte key[32] = { + 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, + 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, + 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 + }; + static const byte iv[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + static const byte plain[32] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51 + }; + byte ksOut[32], swOut[32], readBack[32]; + wc_NuvotonKsKey ksKey; + Aes aesKs, aesSw; + int fails = 0; + int ret; + + printf(" AES-256-CBC from a %s slot\n", memName); + + XMEMSET(&ksKey, 0, sizeof(ksKey)); + ret = wc_NuvotonKs_Write(&ksKey, mem, WC_NUVOTON_KS_OWNER_AES, 256, + key, sizeof(key), 1); + KS_CHECK(ret == 0, "wc_NuvotonKs_Write"); + if (ret != 0) { + return fails; + } + printf(" slot %d\n", ksKey.slot); + + /* Readable was asked for, so the material comes back. */ + XMEMSET(readBack, 0, sizeof(readBack)); + ret = wc_NuvotonKs_Read(&ksKey, readBack, sizeof(readBack)); + KS_CHECK(ret == 0, "wc_NuvotonKs_Read"); + KS_CHECK(XMEMCMP(readBack, key, sizeof(key)) == 0, "read back matches"); + + ret = wc_AesInit(&aesKs, NULL, WOLFSSL_NUVOTON_DEVID); + KS_CHECK(ret == 0, "wc_AesInit hw"); + ret = wc_NuvotonKs_SetAesKey(&aesKs, &ksKey); + KS_CHECK(ret == 0, "wc_NuvotonKs_SetAesKey"); + ret = wc_AesSetIV(&aesKs, iv); + KS_CHECK(ret == 0, "wc_AesSetIV hw"); + ret = wc_AesCbcEncrypt(&aesKs, ksOut, plain, sizeof(plain)); + KS_CHECK(ret == 0, "wc_AesCbcEncrypt from slot"); + + ret = wc_AesInit(&aesSw, NULL, INVALID_DEVID); + KS_CHECK(ret == 0, "wc_AesInit sw"); + ret = wc_AesSetKey(&aesSw, key, sizeof(key), iv, AES_ENCRYPTION); + KS_CHECK(ret == 0, "wc_AesSetKey sw"); + ret = wc_AesCbcEncrypt(&aesSw, swOut, plain, sizeof(plain)); + KS_CHECK(ret == 0, "wc_AesCbcEncrypt sw"); + + KS_CHECK(XMEMCMP(ksOut, swOut, sizeof(ksOut)) == 0, + "stored key matches software"); + + wc_AesFree(&aesKs); + wc_AesFree(&aesSw); + + if (mem == WC_NUVOTON_KS_MEM_SRAM) { + wc_NuvotonKsKey sealed; + + ret = wc_NuvotonKs_Erase(&ksKey); + KS_CHECK(ret == 0, "wc_NuvotonKs_Erase"); + + /* An erased slot must not still answer. */ + ret = wc_NuvotonKs_Read(&ksKey, readBack, sizeof(readBack)); + KS_CHECK(ret != 0, "read of an erased slot is refused"); + + /* A slot written without the readable flag is the whole point of the + * store, so check the engine keeps it in rather than assuming. */ + XMEMSET(&sealed, 0, sizeof(sealed)); + ret = wc_NuvotonKs_Write(&sealed, mem, WC_NUVOTON_KS_OWNER_AES, 256, + key, sizeof(key), 0); + KS_CHECK(ret == 0, "write of a non readable key"); + if (ret == 0) { + ret = wc_NuvotonKs_Read(&sealed, readBack, sizeof(readBack)); + KS_CHECK(ret != 0, "read of a non readable slot is refused"); + (void)wc_NuvotonKs_Erase(&sealed); + } + } + + wc_ForceZero(readBack, sizeof(readBack)); + + return fails; +} + +int keystore_test(void); +int keystore_test(void) +{ + int fails = 0; + + printf("\n--- Key Store round trip ---\n"); + + fails += ks_aes_test(WC_NUVOTON_KS_MEM_SRAM, "SRAM"); + +#ifdef M2354_KS_TEST_FLASH + /* Consumes a Flash slot for good: there is no per-key erase, only revoke. + * Off by default so a routine run does not spend the board's slots. */ + fails += ks_aes_test(WC_NUVOTON_KS_MEM_FLASH, "Flash"); +#endif + + printf("Key Store: %d failure(s)\n", fails); + + return fails; +} diff --git a/embedded/nuvoton_m2354/run_test.sh b/embedded/nuvoton_m2354/run_test.sh new file mode 100755 index 000000000..185a8b1b9 --- /dev/null +++ b/embedded/nuvoton_m2354/run_test.sh @@ -0,0 +1,272 @@ +#!/bin/bash +# +# Build, flash and capture the wolfCrypt test/benchmark on a NuMaker-M2354. +# +# Copyright (C) 2006-2026 wolfSSL Inc. +# +# This file is part of wolfSSL. +# +# Usage: +# ./run_test.sh --bsp /path/to/M2354BSP [--wolfssl PATH] [--seconds 900] +# [--no-build] +# +# Set EXPECT_WORLD to the banner's TrustZone line - "secure world, direct BSP +# calls" or "non-secure, hardware through NSC veneers" - to have the run fail +# rather than report results from an image you did not mean to test. +# +# The board has one Nu-Link2-Me carrying both SWD and the VCOM console, so it +# is a singleton on a shared bench. This script takes a lock for the whole +# flash-and-capture cycle and always releases it, including on failure. + +set -e + +HERE="$(cd "$(dirname "$0")" && pwd)" +BSP="" +WOLFSSL_ARG="" +SECONDS_CAPTURE=900 +DO_BUILD=1 +SESSION="${CLAUDE_SESSION_NAME:-$(whoami)@$(hostname)}" + +LOCKDIR=/tmp/claude-bench-locks +LOCK="$LOCKDIR/numaker-m2354.lock" + +while [ $# -gt 0 ]; do + case "$1" in + --bsp) BSP="$2"; shift 2 ;; + --wolfssl) WOLFSSL_ARG="--wolfssl $2"; shift 2 ;; + --seconds) SECONDS_CAPTURE="$2"; shift 2 ;; + --no-build) DO_BUILD=0; shift ;; + *) echo "unknown option: $1" >&2; exit 1 ;; + esac +done + +if [ "$DO_BUILD" -eq 1 ] && [ -z "$BSP" ]; then + echo "give the BSP location with --bsp /path/to/M2354BSP" >&2 + exit 1 +fi + +# ---- find the probe and its VCOM ------------------------------------------ +# Nu-Link2-Me enumerates as Nuvoton (0416) with its own firmware, or as +# ARM mbed DAPLink (0d28) after loading NuLink2_DAPLink.bin. pyocd needs the +# DAPLink flavour; Nuvoton's OpenOCD fork needs the other. +find_probe() { + local d v pr + for d in /sys/bus/usb/devices/*-*; do + [ -f "$d/idVendor" ] || continue + v=$(cat "$d/idVendor") + if [ "$v" = "0416" ] || [ "$v" = "0d28" ]; then + pr=$(cat "$d/product" 2>/dev/null) + echo "$(basename "$d") $v:$(cat "$d/idProduct") $pr" + return 0 + fi + done + return 1 +} + +find_vcom() { + local d dev v + for d in /sys/class/tty/ttyACM*; do + [ -e "$d" ] || continue + dev=/dev/$(basename "$d") + v=$(udevadm info -q property -n "$dev" 2>/dev/null | \ + sed -n 's/^ID_VENDOR_ID=//p') + if [ "$v" = "0416" ] || [ "$v" = "0d28" ]; then + echo "$dev" + return 0 + fi + done + return 1 +} + +PROBE=$(find_probe) || { + echo "No Nu-Link2-Me found (looked for USB VID 0416 or 0d28)." >&2 + echo "Check that the USB cable is in the Nu-Link2-Me connector, that the" >&2 + echo "debugger daughterboard is attached, and that the board has power." >&2 + exit 1 +} +echo "probe: $PROBE" + +VCOM=$(find_vcom) || { + echo "Probe found but it presents no CDC-ACM console." >&2 + exit 1 +} +echo "console: $VCOM" + +# ---- build ---------------------------------------------------------------- +if [ "$DO_BUILD" -eq 1 ]; then + # shellcheck disable=SC2086 + "$HERE/build.sh" --bsp "$BSP" $WOLFSSL_ARG +fi + +ELF="$HERE/build/wolfcrypt-m2354.elf" +test -f "$ELF" || { echo "no $ELF, build first" >&2; exit 1; } + +# ---- take the bench lock -------------------------------------------------- +mkdir -p "$LOCKDIR" +if ! ( set -o noclobber + echo "$(date -Is) $SESSION wolfcrypt_test flash+capture" > "$LOCK" + ) 2>/dev/null; then + echo "NuMaker-M2354 is in use: $(cat "$LOCK")" >&2 + echo "Wait for it to be released, or coordinate with the holder." >&2 + exit 1 +fi +echo "lock: $(cat "$LOCK")" + +RELEASED=0 +cleanup() { + if [ "$RELEASED" -eq 0 ]; then + uart-monitor reclaim "$VCOM" >/dev/null 2>&1 || true + rm -f "$LOCK" + RELEASED=1 + echo "lock released" + fi +} +trap cleanup EXIT INT TERM + +# ---- flash ---------------------------------------------------------------- +# Take the console away from the shared daemon for the whole cycle, and give it +# back in the trap. Reading the daemon's log instead looks tidier and works +# right up until it does not: on long runs the daemon stops capturing this port +# while still reporting it as monitored, and the log simply stops growing with +# no error anywhere. That is indistinguishable from a firmware hang, and it +# cost a full afternoon of chasing one that did not exist. Owning the tty for +# the duration removes the failure mode. +# +# The important part is that the daemon stays yielded until the capture is +# finished. Reclaiming before reading puts two readers on one tty and each +# steals bytes from the other, which shreds the output instead of losing it. +uart-monitor yield "$VCOM" >/dev/null 2>&1 || true + +echo "=== flashing ===" +# This bench has a dozen-plus debug probes attached. Without -u, pyocd prompts +# for which one to use and then dies on EOF in a non-interactive run. +# Match the id by its shape rather than by column position: pyocd's table has +# a trailing target column that is present or absent depending on whether the +# probe's part could be identified, which moves the fields around. +PROBE_UID=$(pyocd list 2>/dev/null | grep -i 'nu-link2' | \ + grep -oE '[0-9A-Fa-f]{16,}' | head -1) +test -n "$PROBE_UID" || { echo "could not determine the Nu-Link2 probe id" >&2; exit 1; } +echo "probe uid: $PROBE_UID" +# Sector erase, not chip erase. A chip erase also clears the user +# configuration area, which on a TrustZone-provisioned part holds NSCBA - the +# secure/non-secure flash boundary. Erasing that silently un-provisions the +# board for anyone else sharing it, and the damage is invisible until their +# next secure build behaves oddly. Sector erase touches only what is being +# programmed, and is faster besides. +pyocd flash --target m2354kjfae -u "$PROBE_UID" --erase sector "$ELF" + +# ---- capture -------------------------------------------------------------- +mkdir -p "$HERE/logs" +OUT="$HERE/logs/console-$(date +%Y%m%d-%H%M%S).log" +echo "=== capturing to $OUT (up to $SECONDS_CAPTURE s) ===" + +stty -F "$VCOM" 115200 raw -echo -echoe -echok -crtscts 2>/dev/null || true + +# Whatever the previous image was still emitting, so it cannot be mistaken for +# this run: the two are hard to tell apart once they are in one file. +timeout 1 cat "$VCOM" >/dev/null 2>&1 || true + +# Read first, reset second. Resetting before the reader is attached loses the +# banner and the first tests, which is what makes a log start mid-run. +# +# Read with a plain cat and poll the file, rather than stopping the reader on a +# sentinel line: a sed -u pattern quit here gave up on the tty a couple of +# minutes in, which truncated the log at whatever test was running and read as +# a hang. cat holds the port for as long as it is given. +cat "$VCOM" > "$OUT" 2>/dev/null & +CAP_PID=$! + +sleep 1 +pyocd reset -t m2354kjfae -u "$PROBE_UID" >/dev/null 2>&1 || true + +# Stop as soon as the application prints its last line. The benchmark's public +# key rows take minutes each on this part, so the window has to be generous and +# waiting it out every time is most of the cycle. +ELAPSED=0 +while [ "$ELAPSED" -lt "$SECONDS_CAPTURE" ]; do + sleep 5 + ELAPSED=$((ELAPSED + 5)) + grep -q "benchmark_test returned" "$OUT" 2>/dev/null && break +done +kill "$CAP_PID" 2>/dev/null || true +wait "$CAP_PID" 2>/dev/null || true + +cat "$OUT" + +echo +echo "=== summary ===" +grep -E "returned|Test complete|FAIL|failed|hard fault" "$OUT" | tail -20 || true + +# Which image produced these results matters, and the application says so in +# its banner. Read it rather than assuming: a partial reflash - a TrustZone +# secure half overwritten by a single-world image, say - leaves a board that +# boots something other than what was just built, and every line below it then +# describes the wrong binary. Treat a missing or unexpected banner as a failed +# run, not a note, because the results are not attributable without it. +if ! grep -q "wolfCrypt on NuMaker" "$OUT"; then + echo "ERROR: no banner - the capture attached after the reset, or the" >&2 + echo " board is running something other than this build. The" >&2 + echo " results above are not attributable; rerun." >&2 + exit 1 +fi + +# The console sends CRLF, so strip the carriage return or every comparison +# below fails against a string that looks identical when printed. +WORLD=$(sed -n 's/^TrustZone: //p' "$OUT" | head -1 | tr -d '\r') +echo "image reported: TrustZone: ${WORLD:-unknown}" +if [ -n "$EXPECT_WORLD" ] && [ "$WORLD" != "$EXPECT_WORLD" ]; then + echo "ERROR: expected \"$EXPECT_WORLD\" but the board reported" >&2 + echo " \"$WORLD\" - wrong image on the part." >&2 + exit 1 +fi +# Check the results, not just that the stage ran. A stage that printed +# "returned 1" is a failed run, and a stage that never printed its result at +# all is a truncated capture - reporting either as a pass is worse than +# reporting nothing. +RUN_FAIL=0 + +check_stage() { + # $1 label as printed, $2 1 if the stage is required + line=$(grep -E "^$1 returned " "$OUT" | tail -1 | tr -d '\r') + if [ -z "$line" ]; then + if [ "$2" -eq 1 ]; then + echo "ERROR: $1 never reported a result - capture truncated?" >&2 + RUN_FAIL=1 + else + echo "NOTE: $1 did not finish inside the window" + fi + return + fi + rc=${line##* } + if [ "$rc" != "0" ]; then + echo "ERROR: $1 returned $rc" >&2 + RUN_FAIL=1 + else + echo "$1: passed" + fi +} + +# Count only what this boot printed. The capture attaches before the reset, so +# a log routinely opens with the tail of the previous image's output, and a +# plain grep over the file adds two boots together - which is how a wrong test +# count once reached a PR description. +BANNER_LINE=$(grep -n "wolfCrypt on NuMaker" "$OUT" | head -1 | cut -d: -f1) +TESTS=$(tail -n +"${BANNER_LINE:-1}" "$OUT" | grep -c "test passed") +echo "tests passed this boot: $TESTS" + +check_stage keystore_test 1 +check_stage wolfcrypt_test 1 +check_stage benchmark_test 0 + +# The suite prints its own per-test failures; catch them even if the summary +# return value was somehow absent. +if grep -qE "test failed|error = " "$OUT"; then + echo "ERROR: the log contains a failed test" >&2 + RUN_FAIL=1 +fi + +echo "full log: $OUT" + +if [ "$RUN_FAIL" -ne 0 ]; then + exit 1 +fi diff --git a/embedded/nuvoton_m2354/secure/nuvoton_nsc.c b/embedded/nuvoton_m2354/secure/nuvoton_nsc.c new file mode 100644 index 000000000..b84f63e13 --- /dev/null +++ b/embedded/nuvoton_m2354/secure/nuvoton_nsc.c @@ -0,0 +1,483 @@ +/* nuvoton_nsc.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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 + */ + +/* Non-secure callable entry points for the M2354 port. + * + * The CRPT accelerator, the TRNG and the Key Store are secure only in the + * default SCU partition, and M2354.h has no KS_NS alias at all, so a + * non-secure wolfCrypt cannot reach any of them directly. This file is the + * secure half: one veneer per wc_nuvoton_hw_* call, each checking that the + * buffers the non-secure side passed really belong to the non-secure world + * before handing them to the driver. + * + * Build it into the secure image together with wolfcrypt/src/port/nuvoton/ + * nuvoton_hw.c, with -mcmse and -DWOLFSSL_NUVOTON_NSC_IMPL. That macro renames + * the implementations to wc_nuvoton_hw_*_s (see nuvoton_hw.h) so the veneers + * below can take the plain names, which is what the non-secure image links + * against through the import library the linker emits. + * + * The non-secure side builds the same port sources with + * -DWOLFSSL_NUVOTON_NSC, which leaves nuvoton_hw.c empty. + */ + +#include "NuMicro.h" + +#include +#include +#include +#ifndef NO_AES + #include +#endif + +#include "wolfcrypt/src/port/nuvoton/nuvoton_hw.h" + +#include + +#ifndef WOLFSSL_NUVOTON_NSC_IMPL + #error "Build the secure side with -DWOLFSSL_NUVOTON_NSC_IMPL" +#endif + +/* nuvoton_hw.h has just declared the implementations under their _s names. + * Drop the macros so the veneers below define the plain names the non-secure + * image links against, while still being able to call the _s ones. */ +#undef wc_nuvoton_hw_init +#undef wc_nuvoton_hw_cleanup +#undef wc_nuvoton_hw_trng +#undef wc_nuvoton_hw_sha +#undef wc_nuvoton_hw_aes +#undef wc_nuvoton_hw_ecc_sign +#undef wc_nuvoton_hw_ecc_verify +#undef wc_nuvoton_hw_ecc_shared +#undef wc_nuvoton_hw_ecc_pubkey +#undef wc_nuvoton_hw_rsa +#undef wc_nuvoton_hw_ks_write +#undef wc_nuvoton_hw_ks_read +#undef wc_nuvoton_hw_ks_erase +#undef wc_nuvoton_hw_ks_revoke + +#define NSC_ENTRY __attribute__((cmse_nonsecure_entry)) + +/* Refuse a buffer that is not entirely non-secure, so a non-secure caller + * cannot talk the secure world into reading or overwriting secure memory. + * + * A NULL or zero-length buffer reports failure, so callers must NULL-test an + * optional buffer before calling this rather than relying on a zero length to + * pass through. */ +static int nsc_check(void* p, word32 sz, int write) +{ + int flags = CMSE_NONSECURE | CMSE_MPU_READ; + + if (p == NULL || sz == 0) { + return 0; + } + if (write) { + flags |= CMSE_MPU_READWRITE; + } + + return (cmse_check_address_range(p, sz, flags) != NULL); +} + +/* Same, for a buffer whose length comes from the request. Nothing is read or + * written when that length is zero, so there is no span to check and no reason + * to reject the call: an AES-GCM operation over AAD alone carries a + * zero-length payload. Every other length still goes through nsc_check(). */ +static int nsc_check_len(void* p, word32 sz, int write) +{ + if (sz == 0) { + return 1; + } + + return nsc_check(p, sz, write); +} + +/* Same, for the NUL terminated hex strings the public key requests carry. The + * length is not known up front, so the string is measured inside a bound and + * then the whole span is checked in one go. */ +static int nsc_check_str(char* s, word32 maxSz, word32* lenOut) +{ + word32 i; + + if (s == NULL) { + return 0; + } + if (!nsc_check(s, 1, 0)) { + return 0; + } + + for (i = 0; i < maxSz; i++) { + if (!nsc_check(s + i, 1, 0)) { + return 0; + } + if (s[i] == '\0') { + if (lenOut != NULL) { + *lenOut = i + 1; + } + return 1; + } + } + + return 0; +} + +NSC_ENTRY int wc_nuvoton_hw_init(void) +{ + return wc_nuvoton_hw_init_s(); +} + +NSC_ENTRY void wc_nuvoton_hw_cleanup(void) +{ + wc_nuvoton_hw_cleanup_s(); +} + +NSC_ENTRY int wc_nuvoton_hw_trng(byte* out, word32 sz) +{ + if (!nsc_check(out, sz, 1)) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_trng_s(out, sz); +} + +NSC_ENTRY int wc_nuvoton_hw_sha(wc_NuvotonShaReq* req) +{ + wc_NuvotonShaReq local; + + if (!nsc_check(req, (word32)sizeof(*req), 1)) { + return BAD_FUNC_ARG; + } + XMEMCPY(&local, req, sizeof(local)); + + /* digest is set only on the final chunk; an update passes NULL. Checking + * it unconditionally rejects every intermediate chunk, and the hash + * callback result is not remapped to CRYPTOCB_UNAVAILABLE, so that fails + * the caller outright rather than falling back. fdbck is read and written + * by the engine over DMA, so it needs checking too. */ + if (!nsc_check_len((void*)local.in, local.inSz, 0)) { + return BAD_FUNC_ARG; + } + if (!nsc_check(local.fdbck, + WC_NUVOTON_SHA_FDBCK_WORDS * (word32)sizeof(word32), 1)) { + return BAD_FUNC_ARG; + } + if (local.digest != NULL && + !nsc_check(local.digest, local.digestSz, 1)) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_sha_s(&local); +} + +#ifndef NO_AES +NSC_ENTRY int wc_nuvoton_hw_aes(wc_NuvotonAesReq* req) +{ + wc_NuvotonAesReq local; + + /* Copy the request into secure memory first, so the non-secure side + * cannot change a length or a pointer after it has been checked. */ + if (!nsc_check(req, (word32)sizeof(*req), 1)) { + return BAD_FUNC_ARG; + } + XMEMCPY(&local, req, sizeof(local)); + + /* Only in and out are always present. iv is absent for ECB, key is absent + * when a Key Store slot supplies it, and aad and tag belong to the + * authenticated modes. nsc_check() reports a NULL or zero-length buffer as + * a failure, so each optional one is guarded by its own NULL test first - + * checking them unconditionally rejects every request the port actually + * makes, and the AES path maps that to a decline, so the accelerator would + * silently never be used. */ + if (!nsc_check_len((void*)local.in, local.sz, 0) || + !nsc_check_len(local.out, local.sz, 1)) { + return BAD_FUNC_ARG; + } + if (local.key != NULL && + !nsc_check((void*)local.key, local.keySz, 0)) { + return BAD_FUNC_ARG; + } + /* GCM and CCM carry a nonce of their own length - 12 bytes for GCM, 7 to + * 13 for CCM - and the engine only reads it. The block modes pass a whole + * block which is read and written back as the chaining state. Probing a + * full block of a 7 byte CCM nonce reaches past the caller's buffer, and + * demanding write access rejects a nonce that legitimately sits in + * read-only memory. */ + if (local.iv != NULL) { + int ok; + + if (local.mode == WC_NUVOTON_AES_GCM || + local.mode == WC_NUVOTON_AES_CCM) { + ok = nsc_check_len(local.iv, local.ivSz, 0); + } + else { + ok = nsc_check_len(local.iv, (word32)WC_AES_BLOCK_SIZE, 1); + } + if (!ok) { + return BAD_FUNC_ARG; + } + } + if (local.aad != NULL && + !nsc_check_len((void*)local.aad, local.aadSz, 0)) { + return BAD_FUNC_ARG; + } + if (local.tag != NULL && !nsc_check(local.tag, local.tagSz, 1)) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_aes_s(&local); +} +#endif /* !NO_AES */ + +#ifdef WOLFSSL_NUVOTON_ECC +/* Longest string the ECC requests carry: P-521 is 132 hex characters. */ +/* The width of one ECC hex field in the port, NUVOTON_ECC_HEX_SZ in + * nuvoton_cb_pk.c. They are packed contiguously in one allocation, so probing + * a wider span than this runs past the object on the last field, and + * cmse_check_address_range() judges exactly the span it is given. Keep the two + * in step. */ +#define NSC_ECC_STR_MAX 144 + +/* Which fields the engine writes depends on the operation, and the checks have + * to follow: an output needs write access across the width the engine will + * fill, an input needs to be a readable NUL terminated string inside the + * bound. Getting it backwards either fails a valid request or, worse, lets the + * secure driver write a span nobody validated. */ +#define NSC_ECC_POINT_OUT 0x1 /* qx, qy written (key generation) */ +#define NSC_ECC_SIG_OUT 0x2 /* r, s written (sign) */ + +static int nsc_ecc_strings_ok(wc_NuvotonEccReq* r, int dir) +{ + char* in[3]; + word32 i; + + in[0] = r->msg; + in[1] = r->d; + in[2] = r->k; + + for (i = 0; i < 3; i++) { + if (in[i] != NULL && !nsc_check_str(in[i], NSC_ECC_STR_MAX, NULL)) { + return 0; + } + } + + /* The public point is an input to verify and to ECDH, and an output of key + * generation, where the engine writes it into buffers that start zeroed. + * Checking those as strings passes on the first NUL without ever probing + * the span the engine is about to write. */ + if ((dir & NSC_ECC_POINT_OUT) != 0) { + if ((r->qx != NULL && !nsc_check(r->qx, NSC_ECC_STR_MAX, 1)) || + (r->qy != NULL && !nsc_check(r->qy, NSC_ECC_STR_MAX, 1))) { + return 0; + } + } + else { + if ((r->qx != NULL && !nsc_check_str(r->qx, NSC_ECC_STR_MAX, NULL)) || + (r->qy != NULL && !nsc_check_str(r->qy, NSC_ECC_STR_MAX, NULL))) { + return 0; + } + } + + /* r and s are the signature: written by sign, read by verify. */ + if ((dir & NSC_ECC_SIG_OUT) != 0) { + if ((r->r != NULL && !nsc_check(r->r, NSC_ECC_STR_MAX, 1)) || + (r->s != NULL && !nsc_check(r->s, NSC_ECC_STR_MAX, 1))) { + return 0; + } + } + else { + if ((r->r != NULL && !nsc_check_str(r->r, NSC_ECC_STR_MAX, NULL)) || + (r->s != NULL && !nsc_check_str(r->s, NSC_ECC_STR_MAX, NULL))) { + return 0; + } + } + + /* The shared secret is always an output. */ + if (r->out != NULL && !nsc_check(r->out, NSC_ECC_STR_MAX, 1)) { + return 0; + } + + + return 1; +} + +NSC_ENTRY int wc_nuvoton_hw_ecc_sign(wc_NuvotonEccReq* req) +{ + wc_NuvotonEccReq local; + + if (!nsc_check(req, (word32)sizeof(*req), 1)) { + return BAD_FUNC_ARG; + } + XMEMCPY(&local, req, sizeof(local)); + + if (!nsc_ecc_strings_ok(&local, NSC_ECC_SIG_OUT)) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_ecc_sign_s(&local); +} + +NSC_ENTRY int wc_nuvoton_hw_ecc_verify(wc_NuvotonEccReq* req) +{ + wc_NuvotonEccReq local; + + if (!nsc_check(req, (word32)sizeof(*req), 1)) { + return BAD_FUNC_ARG; + } + XMEMCPY(&local, req, sizeof(local)); + + if (!nsc_ecc_strings_ok(&local, 0)) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_ecc_verify_s(&local); +} + +NSC_ENTRY int wc_nuvoton_hw_ecc_shared(wc_NuvotonEccReq* req) +{ + wc_NuvotonEccReq local; + + if (!nsc_check(req, (word32)sizeof(*req), 1)) { + return BAD_FUNC_ARG; + } + XMEMCPY(&local, req, sizeof(local)); + + if (!nsc_ecc_strings_ok(&local, 0)) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_ecc_shared_s(&local); +} + +NSC_ENTRY int wc_nuvoton_hw_ecc_pubkey(wc_NuvotonEccReq* req) +{ + wc_NuvotonEccReq local; + + if (!nsc_check(req, (word32)sizeof(*req), 1)) { + return BAD_FUNC_ARG; + } + XMEMCPY(&local, req, sizeof(local)); + + if (!nsc_ecc_strings_ok(&local, NSC_ECC_POINT_OUT)) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_ecc_pubkey_s(&local); +} +#endif /* WOLFSSL_NUVOTON_ECC */ + +#ifdef WOLFSSL_NUVOTON_RSA +NSC_ENTRY int wc_nuvoton_hw_rsa(wc_NuvotonRsaReq* req) +{ + wc_NuvotonRsaReq local; + /* 4096 bits is 1024 hex characters plus the NUL. */ + const word32 strMax = 1088; + + if (!nsc_check(req, (word32)sizeof(*req), 1)) { + return BAD_FUNC_ARG; + } + XMEMCPY(&local, req, sizeof(local)); + + if (!nsc_check_str(local.in, strMax, NULL) || + !nsc_check_str(local.n, strMax, NULL) || + !nsc_check_str(local.e, strMax, NULL)) { + return BAD_FUNC_ARG; + } + if (local.p != NULL && !nsc_check_str(local.p, strMax, NULL)) { + return BAD_FUNC_ARG; + } + if (local.q != NULL && !nsc_check_str(local.q, strMax, NULL)) { + return BAD_FUNC_ARG; + } + if (local.outSz > strMax || !nsc_check(local.out, local.outSz, 1)) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_rsa_s(&local); +} +#endif /* WOLFSSL_NUVOTON_RSA */ + +#ifdef WOLFSSL_NUVOTON_KS +NSC_ENTRY int wc_nuvoton_hw_ks_write(wc_NuvotonKsWriteReq* req) +{ + wc_NuvotonKsWriteReq local; + + if (!nsc_check(req, (word32)sizeof(*req), 1)) { + return BAD_FUNC_ARG; + } + XMEMCPY(&local, req, sizeof(local)); + + if (!nsc_check((void*)local.key, local.keySz, 0)) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_ks_write_s(&local); +} + +NSC_ENTRY int wc_nuvoton_hw_ks_read(int keyMem, int keySlot, byte* out, + word32 outSz) +{ + if (!nsc_check(out, outSz, 1)) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_ks_read_s(keyMem, keySlot, out, outSz); +} + +/* Key Store handles are bare indices, so a veneer that forwards whatever index + * it is handed lets the non-secure world manage keys that belong to the secure + * world. Erase is bounded here to volatile slots in a window the non-secure + * side owns. + * + * Partitioning by index is the weakest form of this: it is a demonstration, + * not an ownership model. A product should record which slots it issued to the + * non-secure world and check membership, because nothing stops non-secure code + * from asking about a slot inside the window that the secure world also uses. */ +#ifndef NSC_KS_NS_SLOT_FIRST + #define NSC_KS_NS_SLOT_FIRST 0 +#endif +#ifndef NSC_KS_NS_SLOT_LAST + #define NSC_KS_NS_SLOT_LAST 31 +#endif + +NSC_ENTRY int wc_nuvoton_hw_ks_erase(int keyMem, int keySlot) +{ + /* Only the volatile store, and only the non-secure world's own window. A + * Flash or OTP slot has no per-key erase anyway. */ + if (keyMem != WC_NUVOTON_KS_SRAM) { + return BAD_FUNC_ARG; + } + if (keySlot < NSC_KS_NS_SLOT_FIRST || keySlot > NSC_KS_NS_SLOT_LAST) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_ks_erase_s(keyMem, keySlot); +} + +NSC_ENTRY int wc_nuvoton_hw_ks_revoke(int keyMem, int keySlot) +{ + /* Revoking is permanent and there is no undo, so the non-secure world does + * not get to do it through this veneer. A design that needs it should + * revoke from the secure side, where the policy for which keys may be + * retired lives. */ + (void)keyMem; + (void)keySlot; + + return BAD_FUNC_ARG; +} +#endif /* WOLFSSL_NUVOTON_KS */ diff --git a/embedded/nuvoton_m2354/user_settings.h b/embedded/nuvoton_m2354/user_settings.h new file mode 100644 index 000000000..61f0334c9 --- /dev/null +++ b/embedded/nuvoton_m2354/user_settings.h @@ -0,0 +1,163 @@ +/* user_settings.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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 + */ + +/* wolfCrypt configuration for the NuMaker-M2354 (M2354KJFAE, Cortex-M23, + * 1 MB flash, 256 KB SRAM). Sized for the wolfcrypt_test and benchmark + * application in this directory. */ + +#ifndef WOLFSSL_USER_SETTINGS_H +#define WOLFSSL_USER_SETTINGS_H + +#ifdef __cplusplus + extern "C" { +#endif + +/* ---- The port ------------------------------------------------------- */ + +/* Turns the port on and, with no engine named, offloads all of them. See + * wolfssl/wolfcrypt/port/nuvoton/nuvoton_settings.h for the switches. */ +#define WOLFSSL_NUVOTON_M2354 + + +/* build.sh sets WOLFSSL_NUVOTON_SECURE or WOLFSSL_NUVOTON_NSC on the command + * line. Left alone, the port defaults to the secure world, which is where a + * non-TrustZone M2354 application runs. */ + +/* ---- Platform ------------------------------------------------------- */ + +#define SINGLE_THREADED +/* app.c owns main(); test.c and benchmark.c are called from it. */ +#define NO_MAIN_DRIVER +#define NO_FILESYSTEM +#define WOLFSSL_USER_IO +#define NO_WRITEV +#define WOLFSSL_NO_SOCK +#define WOLFSSL_GENERAL_ALIGNMENT 4 +#define WOLFSSL_SMALL_STACK +/* The benchmark otherwise asks for a 1 MB block buffer. */ +#define BENCH_EMBEDDED +#define WOLFSSL_IGNORE_FILE_WARN + +/* Entropy comes from the port's wc_GenerateSeed(), which reads the TRNG. + * nuvoton_settings.h sets NO_DEV_RANDOM for the same reason. + * + * There is no RTC set up here, so the wall clock and the low resolution timer + * both come from the SysTick based helpers in app.c. */ +#define WOLFSSL_USER_CURRTIME +#define USER_TICKS +#define NO_ASN_TIME + +/* ---- Crypto --------------------------------------------------------- */ + +#define HAVE_HASHDRBG + +#define WOLFSSL_SHA224 +#define WOLFSSL_SHA384 +#define WOLFSSL_SHA512 +#define WOLFSSL_NO_SHAKE128 +#define WOLFSSL_NO_SHAKE256 +#define NO_MD4 +#define NO_MD5 +#define NO_DES3 +#define NO_RC4 +#define NO_PSK +#define NO_DSA + +#define HAVE_AES_CBC +#define HAVE_AES_ECB +#define WOLFSSL_AES_DIRECT +#define WOLFSSL_AES_COUNTER +#define HAVE_AESGCM +#define HAVE_AESCCM +#define GCM_TABLE_4BIT +#define HAVE_AES_KEYWRAP + +/* Single-precision math for the curves and key sizes in use, with the Thumb-1 + * assembly. This is what the software fallback paths run on: the accelerator + * does not cover every curve or operation, and without these the leftovers go + * through the generic bignum code, which on a 96 MHz Cortex-M23 is slow enough + * to dominate anything it touches. sp_armthumb.c is the correct assembly for + * this core; sp_cortexm.c is Thumb-2 and will not assemble. */ +#define WOLFSSL_HAVE_SP_ECC +#define WOLFSSL_HAVE_SP_RSA +#define WOLFSSL_SP_ARM_THUMB +#define WOLFSSL_SP_ARM_THUMB_ASM + +#define HAVE_ECC +#define ECC_USER_CURVES +#define HAVE_ECC256 +#define HAVE_ECC384 +#define ECC_SHAMIR +#define HAVE_ECC_DHE +#define HAVE_ECC_SIGN +#define HAVE_ECC_VERIFY +#define HAVE_ECC_KEY_EXPORT +#define HAVE_ECC_KEY_IMPORT + +#define WC_RSA_BLINDING +/* Key generation is off by default, and the suite is usable because of it. + * + * WOLFSSL_KEY_GEN turns on RSA key generation and, in wolfcrypt_test, a call + * to wc_DhGenerateParams() for a fresh 2048-bit parameter set. Both are + * software primality searches that no part of this accelerator takes part in, + * and on a 96 MHz Cortex-M23 the DH one runs for hours: a capture that stops + * after the RSA test with no further output is usually this, not a fault. RSA + * key generation alone measures around 450 seconds at 3072 bits here. + * + * Turn it on if you want that coverage and are prepared for the wait. It has + * not been run to completion on this part, so whether those two tests pass + * here is untested rather than known. */ +#if 0 + #define WOLFSSL_KEY_GEN +#endif + +#define HAVE_HKDF +#define WOLFSSL_BASE64_ENCODE + +/* Hardening. On by default in an autotools build, so keep it here too. */ +#define TFM_TIMING_RESISTANT +#define ECC_TIMING_RESISTANT + +/* ---- TLS ------------------------------------------------------------ */ + +/* This application only exercises wolfCrypt, so the TLS layer is left out and + * build.sh compiles wolfcrypt/src alone. For a TLS build, drop WOLFCRYPT_ONLY + * and add the sources under src/ as well; the settings below are already the + * ones TLS 1.3 wants. */ +#define WOLFCRYPT_ONLY + +#define WOLFSSL_TLS13 +#define HAVE_TLS_EXTENSIONS +#define HAVE_SUPPORTED_CURVES +#define HAVE_EXTENDED_MASTER +#define WC_RSA_PSS +#define NO_OLD_TLS + +/* ---- Size ----------------------------------------------------------- */ + +#define NO_ERROR_STRINGS +#define WOLFSSL_NO_CURRDIR + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFSSL_USER_SETTINGS_H */