From 5074554cb08599edda4535086d30ae9e766200e5 Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 9 Sep 2026 15:35:28 -0700 Subject: [PATCH] Add Nuvoton NuMicro M2354 crypto callback port --- .github/workflows/nuvoton-m2354-compile.yml | 135 ++ .wolfssl_known_macro_extras | 8 + wolfcrypt/src/asn.c | 30 +- wolfcrypt/src/include.am | 9 + wolfcrypt/src/port/nuvoton/README.md | 285 +++ .../src/port/nuvoton/nuvoton_cb_cipher.c | 360 +++ wolfcrypt/src/port/nuvoton/nuvoton_cb_hash.c | 468 ++++ wolfcrypt/src/port/nuvoton/nuvoton_cb_pk.c | 869 ++++++++ wolfcrypt/src/port/nuvoton/nuvoton_cb_rng.c | 66 + wolfcrypt/src/port/nuvoton/nuvoton_cryptocb.c | 143 ++ wolfcrypt/src/port/nuvoton/nuvoton_hw.c | 1924 +++++++++++++++++ wolfcrypt/src/port/nuvoton/nuvoton_hw.h | 264 +++ wolfcrypt/src/port/nuvoton/nuvoton_key.c | 149 ++ wolfcrypt/src/random.c | 37 + wolfssl/wolfcrypt/asn.h | 18 + wolfssl/wolfcrypt/include.am | 6 + .../wolfcrypt/port/nuvoton/nuvoton_cryptocb.h | 77 + wolfssl/wolfcrypt/port/nuvoton/nuvoton_key.h | 106 + .../wolfcrypt/port/nuvoton/nuvoton_settings.h | 198 ++ wolfssl/wolfcrypt/settings.h | 7 + 20 files changed, 5148 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/nuvoton-m2354-compile.yml create mode 100644 wolfcrypt/src/port/nuvoton/README.md create mode 100644 wolfcrypt/src/port/nuvoton/nuvoton_cb_cipher.c create mode 100644 wolfcrypt/src/port/nuvoton/nuvoton_cb_hash.c create mode 100644 wolfcrypt/src/port/nuvoton/nuvoton_cb_pk.c create mode 100644 wolfcrypt/src/port/nuvoton/nuvoton_cb_rng.c create mode 100644 wolfcrypt/src/port/nuvoton/nuvoton_cryptocb.c create mode 100644 wolfcrypt/src/port/nuvoton/nuvoton_hw.c create mode 100644 wolfcrypt/src/port/nuvoton/nuvoton_hw.h create mode 100644 wolfcrypt/src/port/nuvoton/nuvoton_key.c create mode 100644 wolfssl/wolfcrypt/port/nuvoton/nuvoton_cryptocb.h create mode 100644 wolfssl/wolfcrypt/port/nuvoton/nuvoton_key.h create mode 100644 wolfssl/wolfcrypt/port/nuvoton/nuvoton_settings.h diff --git a/.github/workflows/nuvoton-m2354-compile.yml b/.github/workflows/nuvoton-m2354-compile.yml new file mode 100644 index 00000000000..11531b3927e --- /dev/null +++ b/.github/workflows/nuvoton-m2354-compile.yml @@ -0,0 +1,135 @@ +name: "Nuvoton M2354 port" + +# Keeps the Nuvoton NuMicro M2354 crypto callback port compiling against the +# real vendor BSP. The port has no autotools option and is not built by any +# other job: an application compiles wolfcrypt/src/port/nuvoton/*.c into its own +# project, the same way wolfcrypt/src/port/st/stm32.c is carried. Without this +# guard a header rename in the BSP, or a refactor in aes.c, ecc.c or cryptocb.h, +# would break the port silently until someone next built for the board. +# +# Nothing is faked. arm-none-eabi-gcc is a plain apt package and the BSP is a +# public GitHub repository, so both legs compile against the genuine Nuvoton +# StdDriver headers at a pinned commit. +# +# secure WOLFSSL_NUVOTON_SECURE, the whole port including nuvoton_hw.c +# nonsecure WOLFSSL_NUVOTON_NSC, where nuvoton_hw.c compiles to nothing +# and the cmse_nonsecure_entry veneers supply the symbols +# +# The runnable example, including those veneers, lives in wolfssl-examples +# under embedded/nuvoton_m2354. Functional correctness is validated on a +# NuMaker-M2354, not here; see wolfcrypt/src/port/nuvoton/README.md. + +# START OF COMMON SECTION +on: + push: + branches: [ 'master', 'main', 'release/**' ] + paths: + - 'wolfcrypt/src/port/nuvoton/**' + - 'wolfssl/wolfcrypt/port/nuvoton/**' + - '.github/workflows/nuvoton-m2354-compile.yml' + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '**' ] + paths: + - 'wolfcrypt/src/**' + - 'wolfssl/wolfcrypt/**' + - '.github/workflows/nuvoton-m2354-compile.yml' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +# END OF COMMON SECTION + +env: + # Pinned so a BSP change cannot break a PR that did not touch the port. Bump + # it deliberately. + BSP_REF: 3d423be763edabe1d8b3f27dea363b69e787f8f9 + +jobs: + compile: + name: ${{ matrix.leg }} (Cortex-M23) + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} + runs-on: ubuntu-24.04 + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - leg: secure + world: WOLFSSL_NUVOTON_SECURE + - leg: nonsecure + world: WOLFSSL_NUVOTON_NSC + steps: + - uses: actions/checkout@v4 + name: Checkout wolfSSL + + - name: Install the toolchain + run: | + sudo apt-get update + sudo apt-get install -y gcc-arm-none-eabi + + - name: Checkout the Nuvoton M2354 BSP + run: | + set -e + # The full repository is around 256 MB and almost none of it is + # needed, so take one commit and only the driver tree. + git clone --filter=blob:none --no-checkout --sparse \ + https://github.com/OpenNuvoton/M2354BSP "$GITHUB_WORKSPACE/M2354BSP" + cd "$GITHUB_WORKSPACE/M2354BSP" + git sparse-checkout set \ + Library/StdDriver/inc Library/Device Library/CMSIS + git fetch --depth 1 origin "$BSP_REF" + git checkout "$BSP_REF" + + - name: Compile the port + run: | + set -e + BSP="$GITHUB_WORKSPACE/M2354BSP" + mkdir -p "$GITHUB_WORKSPACE/cfg" + + # Minimal configuration: enough to reach every engine in the port. + # The example in wolfssl-examples carries a realistic one. + cat > "$GITHUB_WORKSPACE/cfg/user_settings.h" <<'EOF' + #ifndef CI_USER_SETTINGS_H + #define CI_USER_SETTINGS_H + #define WOLFSSL_NUVOTON_M2354 + #define SINGLE_THREADED + #define NO_FILESYSTEM + #define WOLFSSL_SMALL_STACK + #define WOLFSSL_SHA224 + #define WOLFSSL_SHA384 + #define WOLFSSL_SHA512 + #define HAVE_HASHDRBG + #define HAVE_AES_CBC + #define HAVE_AES_ECB + #define WOLFSSL_AES_DIRECT + #define WOLFSSL_AES_COUNTER + #define HAVE_AESGCM + #define HAVE_ECC + #define HAVE_ECC_DHE + #define HAVE_ECC_SIGN + #define HAVE_ECC_VERIFY + #define HAVE_ECC384 + #define WOLFSSL_KEY_GEN + #define TFM_TIMING_RESISTANT + #define ECC_TIMING_RESISTANT + #define WC_RSA_BLINDING + #endif + EOF + + CFLAGS="-mcpu=cortex-m23 -mthumb -Os -Wall -Wextra -Werror -c" + CFLAGS="$CFLAGS -I. -I$GITHUB_WORKSPACE/cfg -DWOLFSSL_USER_SETTINGS" + CFLAGS="$CFLAGS -D${{ matrix.world }}" + CFLAGS="$CFLAGS -I$BSP/Library/StdDriver/inc" + CFLAGS="$CFLAGS -I$BSP/Library/Device/Nuvoton/M2354/Include" + CFLAGS="$CFLAGS -I$BSP/Library/CMSIS/Include" + + for f in wolfcrypt/src/port/nuvoton/*.c wolfcrypt/src/random.c; do + echo " $f" + # shellcheck disable=SC2086 + arm-none-eabi-gcc $CFLAGS -o /dev/null "$f" + done diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index b593af15ca0..0d0971bf8bb 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -1038,6 +1038,7 @@ WOLFSSL_NO_OCSP_OPTIONAL_CERTS WOLFSSL_NO_RSA_KEY_CHECK WOLFSSL_NO_SERVER_GROUPS_EXT WOLFSSL_NO_SESSION_STATS +WOLFSSL_NO_SHA512_HASHTYPE WOLFSSL_NO_SIGALG WOLFSSL_NO_SOCKADDR_UN WOLFSSL_NO_STRICT_CIPHER_SUITE @@ -1046,6 +1047,13 @@ WOLFSSL_NO_TRUSTED_CERTS_VERIFY WOLFSSL_NO_WORD64_OPS WOLFSSL_NO_XOR_OPS WOLFSSL_NRF51_AES +WOLFSSL_NUVOTON_NO_AESCCM +WOLFSSL_NUVOTON_NO_AESGCM +WOLFSSL_NUVOTON_NO_HW_MUTEX +WOLFSSL_NUVOTON_NO_SP_DEFAULT +WOLFSSL_NUVOTON_NSC +WOLFSSL_NUVOTON_NSC_IMPL +WOLFSSL_NUVOTON_RNG_OFFLOAD WOLFSSL_NXP_CASPER_ECC_MUL2ADD WOLFSSL_NXP_CASPER_ECC_MULMOD WOLFSSL_NXP_LPC55S6X diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index c8db8fd794f..b7dc97d9441 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -33997,17 +33997,22 @@ int DecodeECC_DSA_Sig_Ex(const byte* sig, word32 sigLen, mp_int* r, mp_int* s, #endif -#ifdef WOLFSSL_ASN_TEMPLATE -#if defined(HAVE_ECC) && defined(WOLFSSL_CUSTOM_CURVES) +#ifdef WOLFSSL_ASN_HEX_STRING /* Convert data to hex string. * * Big-endian byte array is converted to big-endian hexadecimal string. * + * Written for the custom ECC curve parameters, which SEC 1 carries as byte + * arrays and ecc_set_type holds as strings. Hardware ports whose driver takes + * key material the same way reuse it rather than growing their own copy; see + * WOLFSSL_ASN_HEX_STRING in asn.h. Base16_Decode() goes the other way and + * accepts either case, so the two pair up. + * * @param [in] input Buffer containing data. * @param [in] inSz Size of data in buffer. - * @param [out] out Buffer to hold hex string. + * @param [out] out Buffer to hold hex string. Needs inSz * 2 + 1 bytes. */ -static void DataToHexString(const byte* input, word32 inSz, char* out) +void wc_DataToHexString(const byte* input, word32 inSz, char* out) { static const char hexChar[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; @@ -34021,7 +34026,10 @@ static void DataToHexString(const byte* input, word32 inSz, char* out) /* NUL terminate string. */ out[i * 2] = '\0'; } +#endif /* WOLFSSL_ASN_HEX_STRING */ +#ifdef WOLFSSL_ASN_TEMPLATE +#if defined(HAVE_ECC) && defined(WOLFSSL_CUSTOM_CURVES) #ifndef WOLFSSL_ECC_CURVE_STATIC /* Convert data to hex string and place in allocated buffer. * @@ -34048,7 +34056,7 @@ static int DataToHexStringAlloc(const byte* input, word32 inSz, char** out, } else { /* Convert to hex string. */ - DataToHexString(input, inSz, str); + wc_DataToHexString(input, inSz, str); *out = str; } @@ -34251,23 +34259,23 @@ static int EccSpecifiedECDomainDecode(const byte* input, word32 inSz, #else if (ret == 0) { /* Base X-ordinate */ - DataToHexString(base + 1, (word32)curve->size, (char *)curve->Gx); + wc_DataToHexString(base + 1, (word32)curve->size, (char *)curve->Gx); /* Base Y-ordinate */ - DataToHexString(base + 1 + curve->size, (word32)curve->size, (char *)curve->Gy); + wc_DataToHexString(base + 1 + curve->size, (word32)curve->size, (char *)curve->Gy); /* Prime */ - DataToHexString(dataASN[ECCSPECIFIEDASN_IDX_PRIME_P].data.ref.data, + wc_DataToHexString(dataASN[ECCSPECIFIEDASN_IDX_PRIME_P].data.ref.data, dataASN[ECCSPECIFIEDASN_IDX_PRIME_P].data.ref.length, (char *)curve->prime); /* Parameter A */ - DataToHexString(dataASN[ECCSPECIFIEDASN_IDX_PARAM_A].data.ref.data, + wc_DataToHexString(dataASN[ECCSPECIFIEDASN_IDX_PARAM_A].data.ref.data, dataASN[ECCSPECIFIEDASN_IDX_PARAM_A].data.ref.length, (char *)curve->Af); /* Parameter B */ - DataToHexString(dataASN[ECCSPECIFIEDASN_IDX_PARAM_B].data.ref.data, + wc_DataToHexString(dataASN[ECCSPECIFIEDASN_IDX_PARAM_B].data.ref.data, dataASN[ECCSPECIFIEDASN_IDX_PARAM_B].data.ref.length, (char *)curve->Bf); /* Order of curve */ - DataToHexString(dataASN[ECCSPECIFIEDASN_IDX_ORDER].data.ref.data, + wc_DataToHexString(dataASN[ECCSPECIFIEDASN_IDX_ORDER].data.ref.data, dataASN[ECCSPECIFIEDASN_IDX_ORDER].data.ref.length, (char *)curve->order); } diff --git a/wolfcrypt/src/include.am b/wolfcrypt/src/include.am index 53fa2ca0c45..a30e76c38a4 100644 --- a/wolfcrypt/src/include.am +++ b/wolfcrypt/src/include.am @@ -133,6 +133,15 @@ EXTRA_DIST += wolfcrypt/src/port/ti/ti-aes.c \ wolfcrypt/src/port/silabs/silabs_hash.c \ wolfcrypt/src/port/silabs/silabs_random.c \ wolfcrypt/src/port/silabs/README.md \ + wolfcrypt/src/port/nuvoton/nuvoton_hw.h \ + wolfcrypt/src/port/nuvoton/nuvoton_hw.c \ + wolfcrypt/src/port/nuvoton/nuvoton_cryptocb.c \ + wolfcrypt/src/port/nuvoton/nuvoton_cb_rng.c \ + wolfcrypt/src/port/nuvoton/nuvoton_cb_hash.c \ + wolfcrypt/src/port/nuvoton/nuvoton_cb_cipher.c \ + wolfcrypt/src/port/nuvoton/nuvoton_cb_pk.c \ + wolfcrypt/src/port/nuvoton/nuvoton_key.c \ + wolfcrypt/src/port/nuvoton/README.md \ wolfcrypt/src/port/st/stm32.c \ wolfcrypt/src/port/st/README.md \ wolfcrypt/src/port/st/STM32MP13.md \ diff --git a/wolfcrypt/src/port/nuvoton/README.md b/wolfcrypt/src/port/nuvoton/README.md new file mode 100644 index 00000000000..137f6371413 --- /dev/null +++ b/wolfcrypt/src/port/nuvoton/README.md @@ -0,0 +1,285 @@ +# Nuvoton (nuvoton) Port + +Hardware crypto for the Nuvoton NuMicro **M2354** series (Arm Cortex-M23, +Armv8-M baseline, TrustZone). Written against the +[M2354BSP](https://github.com/OpenNuvoton/M2354BSP) StdDriver and developed on +the [NuMaker-M2354](https://www.nuvoton.com/board/numaker-m2354/) +(M2354KJFAE, 1 MB flash, 256 KB SRAM, 96 MHz). + +The part carries three separate blocks, and this port drives all three: + +| Block | What it does | +|---|---| +| CRPT | AES, SHA, ECC and RSA accelerator, plus a PRNG | +| TRNG | Entropy source, separate IP, seeds the CRPT PRNG | +| Key Store | Key slots in SRAM, Flash and OTP, used by handle | + +The port is a **crypto callback device**: software stays compiled in, and +anything the hardware cannot do is declined and runs in software rather than +failing. Nothing in the wolfSSL core grows a struct member for it - a Key Store +handle rides in the standard `devCtx` of the `Aes` or `ecc_key` it belongs to. + +## Enabling + +Define `WOLFSSL_NUVOTON_M2354` in `user_settings.h` and add +`wolfcrypt/src/port/nuvoton/*.c` to your project. With nothing else named, +every engine is offloaded. Name one or more of these instead and only those +are: + +```c +#define WOLFSSL_NUVOTON_M2354 +/* optional, otherwise all of them */ +#define WOLFSSL_NUVOTON_TRNG +#define WOLFSSL_NUVOTON_HASH +#define WOLFSSL_NUVOTON_CIPHER +#define WOLFSSL_NUVOTON_ECC +#define WOLFSSL_NUVOTON_RSA +#define WOLFSSL_NUVOTON_KS +``` + +`wolfssl/wolfcrypt/port/nuvoton/nuvoton_settings.h` carries the full list, +including `WOLFSSL_NUVOTON_DEVID`, `WOLFSSL_NUVOTON_DMA_BUF_SZ` and +`WOLFSSL_NUVOTON_HW_TIMEOUT`. + +Bring the device up once, then route work to it by device id: + +```c +wolfCrypt_Init(); +wc_NuvotonCryptoCb_RegisterDevice(WOLFSSL_NUVOTON_DEVID); + +wc_AesInit(&aes, NULL, WOLFSSL_NUVOTON_DEVID); +wc_ecc_init_ex(&key, NULL, WOLFSSL_NUVOTON_DEVID); +/* or for a whole TLS context: */ +wolfSSL_CTX_SetDevId(ctx, WOLFSSL_NUVOTON_DEVID); +``` + +### The CRPT interrupt is required + +The BSP's ECC and RSA drivers block on a flag that only the CRPT interrupt +sets (`ECC_DriverISR` in `Library/StdDriver/src/crypto.c`). Route it: + +```c +void CRPT_IRQHandler(void) +{ + ECC_DriverISR(CRPT); +} +... +NVIC_EnableIRQ(CRPT_IRQn); +``` + +The port arms `ECCIEN`/`ECCEIEN` during init, so the application supplies only +the handler and the NVIC line. The NVIC line alone is not enough - nothing in +the BSP driver sets `CRPT->INTEN`. Without the handler every public key call +spins to its timeout and returns `WC_HW_E`, deliberately an error rather than a +silent slow fallback that would hide the wiring mistake. + +:warning: **Timeouts with the handler installed mean a misaligned vector +table.** The M2354 implements 132 exceptions, so `VTOR` must be **1024**-byte +aligned, not the 128 or 256 a smaller part needs. Nothing fails at startup and +only the first exception goes astray - usually the CRPT one. An image linked at +`0x00000000` is aligned by construction, so this only bites behind a header, as +a bootloader payload is. The example's `build.sh` rejects a misaligned image. + +## What is offloaded + +| Engine | Operations | Notes | +|---|---|---| +| TRNG | `wc_GenerateSeed`, `WC_ALGO_TYPE_RNG` | Also wired into the `wc_GenerateSeed` chain in `wolfcrypt/src/random.c`, so a `WC_RNG` built with `INVALID_DEVID` still gets entropy | +| SHA | SHA-1, SHA-224, SHA-256, SHA-384, SHA-512 | Streaming, see below | +| AES | ECB, CBC, CTR | 128, 192 and 256 bit, whole blocks | +| AES-GCM | Encrypt, decrypt and GMAC | Any size: one round up to `WOLFSSL_NUVOTON_GCM_BUF_SZ`, DMA cascade above it | +| AES-CCM | Encrypt and decrypt | Nonce 7 to 13 bytes, up to `WOLFSSL_NUVOTON_GCM_BUF_SZ` of packed packet | +| ECC | ECDSA sign and verify, ECDH, key generation | P-192, P-224, P-256, P-384, P-521, Brainpool P-256/384/512 | +| RSA | Public and private modular exponentiation | 1024, 2048, 3072 and 4096 bit | +| Key Store | AES keys by handle | `wolfssl/wolfcrypt/port/nuvoton/nuvoton_key.h` | + +There is one CRPT channel and the staging buffers are static, so every engine +call is serialised with `wolfSSL_CryptHwMutex*`. Those compile to nothing +unless `WOLFSSL_CRYPT_HW_MUTEX` is set, so the port turns it on for any build +that is not `SINGLE_THREADED`. `WOLFSSL_NUVOTON_NO_HW_MUTEX` opts out, which is +only safe if nothing else can enter the port concurrently. + +Declined, and therefore run in software: + +| Case | Why | +|---|---| +| AES-CCM past `WOLFSSL_NUVOTON_GCM_BUF_SZ` | GCM has a DMA cascade for this, CCM does not | +| AES-CTR not starting on a block boundary | The engine cannot resume part way into a key stream block (`aes->left`) | +| SHA-512/224 and SHA-512/256 | Different initial values; the engine does full SHA-512 only | +| The empty message | The SHA result register is only written by a DMA round, and a zero length round does not start one | +| Curves the engine lacks, other RSA sizes | Not implemented in hardware | +| ECC keys held in the Key Store | Vendor bug, see below | + +:warning: `ECC_GenerateSignature_KS()` reports success and returns a well formed +signature that does not verify: `r` comes back as the base point x coordinate - +the answer for `k = 1` - for every slot layout tried, so the engine is not +taking `k` from the slot. The register form is correct on the same vector, so +this is not wolfSSL-side marshalling. Pending an answer from Nuvoton, +`wc_NuvotonKs_SetEccKey()` is not offered. AES keys from the store work. + +## Key Store + +A key written to the store gets a slot, and from then on the application uses +the handle. The key material never enters wolfCrypt memory again: + +```c +wc_NuvotonKsKey ksKey; +Aes aes; + +wc_NuvotonKs_Write(&ksKey, WC_NUVOTON_KS_MEM_SRAM, WC_NUVOTON_KS_OWNER_AES, + 256, keyBytes, sizeof(keyBytes), 0 /* not readable */); + +wc_AesInit(&aes, NULL, WOLFSSL_NUVOTON_DEVID); +wc_NuvotonKs_SetAesKey(&aes, &ksKey); +wc_AesSetIV(&aes, iv); +wc_AesCbcEncrypt(&aes, out, in, sizeof(in)); +``` + +The handle has to outlive the object that points at it. + +:warning: **OTP slots cannot be rewritten,** and `KS_EraseKey()` in the BSP +writes `KS_SRAM` into the metadata itself, so only a volatile slot can be +cleared. Flash and OTP keys are retired with `wc_NuvotonKs_Revoke()`. Both are +permanent. + +## TrustZone + +`CRPT`, `TRNG` and the Key Store are secure-only peripherals in the default SCU +partition, and `M2354.h` has no `KS_NS` alias at all. So where wolfCrypt runs +decides how it reaches them, and the port is built for either: + +| Macro | wolfCrypt runs | How it reaches the hardware | +|---|---|---| +| `WOLFSSL_NUVOTON_SECURE` (default) | Secure world, which is also where a non-TrustZone application runs | `nuvoton_hw.c` calls the BSP drivers directly | +| `WOLFSSL_NUVOTON_NSC` | Non-secure world | `cmse_nonsecure_entry` veneers in a secure partition | + +Everything the port does to the hardware goes through `wc_nuvoton_hw_*` in +`nuvoton_hw.h`, and no other file in the port includes a BSP header, so that is +the only seam between the two and the rest of the port is byte-identical either +way. The secure half lives in wolfssl-examples at +`embedded/nuvoton_m2354/secure/nuvoton_nsc.c`: one veneer per call, copying the +request into secure memory and validating every pointer with +`cmse_check_address_range()` first. + +Two build notes: + +- `NSC_Init()` programs **SAU region 3** from the `__start_NSC`/`__end_NSC` + linker symbols, so a partition header must not also enable a static SAU + region over those addresses. Overlapping SAU regions are UNPREDICTABLE in + Armv8-M and every secure gateway call then faults. +- `system_M2354.c` must be compiled with `-mcmse`, or its SAU and SCU setup + compiles to nothing and the part runs with the SAU disabled. + +## Hashing + +The SHA engine keeps its working state in `HMAC_FDBCK` (1728 bits, readable +*and writable*), and `HMAC_FBADDR` swaps that state to an SRAM buffer by DMA. +Every context gets its own buffer, so nothing of a message stays in the engine +between calls and hashes interleave freely - a TLS transcript hash against the +record MAC, or a `wc_Sha256Copy` fork, are safe by construction. A context +costs a fixed **~350 bytes** regardless of message length. + +:warning: Nuvoton's own mbedTLS layer does not use the feedback registers, so +reading only that layer suggests the engine cannot save state at all. It can; +the mechanism is in the TRM and in `crpt_reg.h`. + +## DMA addressing + +CRPT DMA reads and writes only word-aligned SRAM addresses in the `0x2xxxxxxx` +region, as the BSP's own `aes_alt.c` states. **Both aliases count:** secure code +sees SRAM at `0x2xxxxxxx`, non-secure code sees the same memory at `0x3xxxxxxx`, +and the alias a transfer is given decides the security attribute of the access. +Buffers arriving through the veneers are non-secure, carry `0x3xxxxxxx`, and are +passed through unchanged. + +:warning: Opposite polarity from the NXP TrustZone parts, where `0x3xxxxxxx` is +the secure alias. + +AES bounces anything that does not qualify through a buffer sized by +`WOLFSSL_NUVOTON_DMA_BUF_SZ` (six blocks by default). SHA declines to software +instead, since staging a whole message would need a cascade and so exclusive use +of the engine across calls. In practice a message from `XMALLOC` always +qualifies. + +## Multi-threading + +The CRPT has one usable channel and the port serializes every operation on +`wolfSSL_CryptHwMutexLock()`. A build with `SINGLE_THREADED` gets that for +free. + +## Example project + +A runnable `wolfcrypt_test` and benchmark for the NuMaker-M2354 lives in +wolfssl-examples under `embedded/nuvoton_m2354`, with the non-secure callable +veneers, a `user_settings.h` to start from, and the CRPT interrupt handler. + +## Status + +Validated on a NuMaker-M2354 (M2354KJFAE at 96 MHz, UART0 on PA6/PA7). +`wolfcrypt_test` runs to completion with **no failures**, covering every +offloaded engine. Two things the suite does not reach were covered on the +board separately: the Key Store round trip in the example, and a sweep of +AES-GCM and AES-CCM against software across payload, nonce and tag lengths, +since the suite's GCM vectors use a single payload length and so miss the +boundary between the one-shot and cascade forms. + +A two-image TrustZone build (NSCBA at `0x00080000`) has also been run on the +board, and every algorithm passes from the non-secure side - including **ECC**, +which depends on the CRPT interrupt being taken in the secure world, and the +Key Store round trip. + +Host `--enable-all --enable-cryptocb` `make check` is unchanged by this port, +`check-headers` and `check-source-text` are clean, and both cross-compile legs +build under `-Wall -Wextra -Werror`. + +A run on the non-secure path that stops printing after the RSA test is the +same `WOLFSSL_KEY_GEN` effect described under Status, not a TrustZone problem: +the next thing the suite does is a software 2048-bit DH parameter search. Each +of those tests passes when called directly across the boundary (`rsa 0`, +`dh 0`, `pwdbased 0`, `ecc 0`). + +## Benchmarks + +NuMaker-M2354 at 96 MHz. **Both columns come from the same build**, with +`WOLFSSL_HAVE_SP_ECC`, `WOLFSSL_HAVE_SP_RSA` and the Thumb-1 SP assembly +enabled, so the software column is wolfSSL configured properly for this core +rather than falling back to portable C. Measuring against an unoptimised +software baseline roughly triples the apparent speedup. + +| Algorithm | Software | Hardware | +|---|---|---| +| SHA-1 | 1.850 MiB/s | 8.521 MiB/s | +| SHA-224 | 768.8 KiB/s | 8.740 MiB/s | +| SHA-256 | 771.1 KiB/s | 8.707 MiB/s | +| SHA-384 | 270.7 KiB/s | 13.843 MiB/s | +| SHA-512 | 270.9 KiB/s | 13.951 MiB/s | +| SHA-512/224 | 270.9 KiB/s | 270.4 KiB/s (declined) | +| SHA-512/256 | 270.7 KiB/s | 270.1 KiB/s (declined) | +| HMAC-SHA-1 | 1.824 MiB/s | 8.041 MiB/s | +| HMAC-SHA-256 | 762.0 KiB/s | 8.211 MiB/s | +| HMAC-SHA-512 | 266.5 KiB/s | 12.671 MiB/s | +| AES-128-CBC enc | 411.4 KiB/s | 8.732 MiB/s | +| AES-128-CBC dec | 392.9 KiB/s | 8.838 MiB/s | +| AES-256-CBC enc | 293.5 KiB/s | 7.837 MiB/s | +| AES-256-CBC dec | 287.4 KiB/s | 7.919 MiB/s | +| AES-128-CTR | 435.6 KiB/s | 8.561 MiB/s | +| AES-256-CTR | 306.6 KiB/s | 7.707 MiB/s | +| AES-128-ECB enc | 451.0 KiB/s | 11.494 MiB/s | +| AES-256-ECB enc | 316.2 KiB/s | 9.990 MiB/s | +| AES-128-GCM enc | 285.4 KiB/s | 721.4 KiB/s | +| RNG (Hash-DRBG) | - | 705.9 KiB/s | + +The SHA-512/224 and SHA-512/256 rows matching software is the port correctly +declining those two. The RNG row is wolfCrypt's Hash-DRBG seeded from the TRNG, +the default; see `WOLFSSL_NUVOTON_RNG_OFFLOAD`. Public key timings are omitted +because the benchmark generates an RSA-3072 key first, which takes over seven +minutes on this part. + +# wolfBoot + +wolfBoot has an M2354 target that builds on this port, including the TrustZone +arrangement: https://github.com/wolfSSL/wolfBoot/pull/884 + +# Support + +For questions please email support@wolfssl.com diff --git a/wolfcrypt/src/port/nuvoton/nuvoton_cb_cipher.c b/wolfcrypt/src/port/nuvoton/nuvoton_cb_cipher.c new file mode 100644 index 00000000000..2517f28dde4 --- /dev/null +++ b/wolfcrypt/src/port/nuvoton/nuvoton_cb_cipher.c @@ -0,0 +1,360 @@ +/* nuvoton_cb_cipher.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 + */ + +/* AES on the M2354 CRPT engine: ECB, CBC and CTR. + * + * The key comes from aes->devKey, which wc_AesSetKey fills for any Aes built + * with a crypto callback device id, and the chaining state from aes->reg. The + * engine returns the value it would feed AES_IV for the next block in + * AES_FDBCK, so the hardware layer writes that straight back into aes->reg and + * a later software call picks up the stream where this one left off. + * + * GCM runs on the engine as a single packed packet of IV, AAD and payload, + * which is what the one-shot crypto callback is handed. Its key register + * convention is not the CBC one: the engine wants big-endian key words with + * no KINSWAP. Decrypt runs the same packet with CRPT_AES_CTL_ENCRPT_Msk + * clear, and the engine returns the tag it computed over the ciphertext, + * which this file compares against the received one. + * + * CCM runs on the engine the same way, with its own first block and its own + * counter, and with the key convention the block modes use rather than the + * one GCM wants. */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_NUVOTON_M2354) && defined(WOLFSSL_NUVOTON_CIPHER) && \ + defined(WOLF_CRYPTO_CB) && !defined(NO_AES) + +#include +#include +#include + +#ifdef WOLFSSL_NUVOTON_KS + #include +#endif + +#include "wolfcrypt/src/port/nuvoton/nuvoton_hw.h" + +/* ForceZero() and ConstantCompare() for the GCM tag handling. */ +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +/* Fill in the parts of a request that every mode shares. Returns 0 when the + * engine can take it. */ +static int wc_NuvotonAesSetup(wc_NuvotonAesReq* req, Aes* aes, byte* out, + const byte* in, word32 sz, int mode, int enc) +{ + if (aes == NULL) { + return BAD_FUNC_ARG; + } + if (sz > 0 && (out == NULL || in == NULL)) { + return BAD_FUNC_ARG; + } + + /* ECB, CBC and CTR go to the engine as whole blocks: a partial tail is + * the caller's, and for CTR it is also the case wolfCrypt tracks in + * aes->left. GCM is different - the payload is padded into the packed + * buffer, so any length is fine, including none at all for an + * authenticate-only call. */ + if (mode != WC_NUVOTON_AES_GCM) { + if (sz == 0 || (sz % WC_AES_BLOCK_SIZE) != 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + } + + if (aes->keylen != 16 && aes->keylen != 24 && aes->keylen != 32) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + XMEMSET(req, 0, sizeof(*req)); + req->in = in; + req->out = out; + req->sz = sz; + req->keySz = (word32)aes->keylen; + req->mode = mode; + req->encrypt = enc; + req->keySlot = WC_NUVOTON_NO_SLOT; + +#ifdef WOLFSSL_NUVOTON_KS + /* A Key Store handle in devCtx means the engine fetches the key itself + * and no key material passes through here. */ + if (aes->devCtx != NULL) { + wc_NuvotonKsKey* ksKey = (wc_NuvotonKsKey*)aes->devCtx; + + req->keyMem = ksKey->mem; + req->keySlot = ksKey->slot; + } + else +#endif + { + req->key = (const byte*)aes->devKey; + } + + if (mode != WC_NUVOTON_AES_ECB) { + req->iv = (byte*)aes->reg; + } + + return 0; +} + +/* Anything the engine reports as "cannot take this one" becomes a decline, so + * the operation runs in software instead of failing. */ +#if defined(HAVE_AESGCM) || defined(HAVE_AESCCM) +/* Finish an authenticated decrypt: compare the tag the engine computed with + * the one received. Nothing that fails to authenticate reaches the caller. */ +static int wc_NuvotonAeadDecFinish(int ret, byte* tag, const byte* expected, + word32 tagSz, byte* out, word32 sz, int authErr) +{ + if (ret == 0) { + if (ConstantCompare(tag, expected, (int)tagSz) != 0) { + if (sz > 0) { + ForceZero(out, sz); + } + /* GCM and CCM callers each expect their own code. */ + ret = authErr; + } + } + + ForceZero(tag, WC_AES_BLOCK_SIZE); + + return ret; +} +#endif + +#if defined(HAVE_AESGCM) || defined(HAVE_AESCCM) +/* Build an AEAD request: the IV is the caller's nonce, not aes->reg, and the + * payload may be empty (GMAC). Caller sets the mode; GCM by default. */ +static int wc_NuvotonAesGcmSetup(wc_NuvotonAesReq* req, Aes* aes, byte* out, + const byte* in, word32 sz, byte* iv, word32 ivSz, const byte* aad, + word32 aadSz, word32 tagSz, int enc) +{ + int ret; + + if (iv == NULL || ivSz == 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (tagSz == 0 || tagSz > WC_AES_BLOCK_SIZE) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = wc_NuvotonAesSetup(req, aes, out, in, sz, WC_NUVOTON_AES_GCM, enc); + if (ret != 0) { + return ret; + } + + req->iv = iv; + req->ivSz = ivSz; + req->aad = aad; + req->aadSz = aadSz; + req->tagSz = tagSz; + + return 0; +} +#endif + +static int wc_NuvotonAesRun(wc_NuvotonAesReq* req) +{ + int ret = wc_nuvoton_hw_aes(req); + + if (ret == WC_NO_ERR_TRACE(BAD_LENGTH_E) || + ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + /* Declining sends the operation to software, which is right for a + * plain key. A Key Store key has no software counterpart - the + * material never left the store - so the fallback cannot run and + * wolfCrypt reports MISSING_KEY from somewhere unrelated. Say what + * actually happened instead. */ + if (req->keySlot != WC_NUVOTON_NO_SLOT) { + WOLFSSL_MSG("Nuvoton: stored key, and the engine declined the " + "operation"); + ret = WC_HW_E; + } + else { + ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + } + + return ret; +} + +int wc_NuvotonCb_Cipher(wc_CryptoInfo* info) +{ + wc_NuvotonAesReq req; + int ret; + + if (info == NULL) { + return BAD_FUNC_ARG; + } + + switch (info->cipher.type) { +#ifdef HAVE_AES_CBC + case WC_CIPHER_AES_CBC: + ret = wc_NuvotonAesSetup(&req, info->cipher.aescbc.aes, + info->cipher.aescbc.out, info->cipher.aescbc.in, + info->cipher.aescbc.sz, WC_NUVOTON_AES_CBC, info->cipher.enc); + if (ret != 0) { + return ret; + } + return wc_NuvotonAesRun(&req); +#endif +#ifdef WOLFSSL_AES_COUNTER + case WC_CIPHER_AES_CTR: + /* CTR is a stream cipher to wolfCrypt: aes->left counts the bytes + * of the current key stream block it has not handed out yet. The + * engine has no way to be told to start part way into a block, so + * offload only a call that begins on a block boundary. */ + if (info->cipher.aesctr.aes != NULL && + info->cipher.aesctr.aes->left != 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + ret = wc_NuvotonAesSetup(&req, info->cipher.aesctr.aes, + info->cipher.aesctr.out, info->cipher.aesctr.in, + info->cipher.aesctr.sz, WC_NUVOTON_AES_CTR, 1); + if (ret != 0) { + return ret; + } + return wc_NuvotonAesRun(&req); +#endif +#if defined(HAVE_AES_ECB) || defined(WOLFSSL_AES_DIRECT) || \ + defined(WOLF_CRYPTO_CB_ONLY_AES) + case WC_CIPHER_AES_ECB: + ret = wc_NuvotonAesSetup(&req, info->cipher.aesecb.aes, + info->cipher.aesecb.out, info->cipher.aesecb.in, + info->cipher.aesecb.sz, WC_NUVOTON_AES_ECB, info->cipher.enc); + if (ret != 0) { + return ret; + } + return wc_NuvotonAesRun(&req); +#endif +#ifdef HAVE_AESGCM + case WC_CIPHER_AES_GCM: + if (info->cipher.enc) { + ret = wc_NuvotonAesGcmSetup(&req, + info->cipher.aesgcm_enc.aes, + info->cipher.aesgcm_enc.out, info->cipher.aesgcm_enc.in, + info->cipher.aesgcm_enc.sz, + (byte*)info->cipher.aesgcm_enc.iv, + info->cipher.aesgcm_enc.ivSz, + info->cipher.aesgcm_enc.authIn, + info->cipher.aesgcm_enc.authInSz, + info->cipher.aesgcm_enc.authTagSz, 1); + if (ret != 0) { + return ret; + } + /* On encrypt the tag goes straight to the caller. */ + req.tag = info->cipher.aesgcm_enc.authTag; + + return wc_NuvotonAesRun(&req); + } + else { + byte tag[WC_AES_BLOCK_SIZE]; + + ret = wc_NuvotonAesGcmSetup(&req, + info->cipher.aesgcm_dec.aes, + info->cipher.aesgcm_dec.out, info->cipher.aesgcm_dec.in, + info->cipher.aesgcm_dec.sz, + (byte*)info->cipher.aesgcm_dec.iv, + info->cipher.aesgcm_dec.ivSz, + info->cipher.aesgcm_dec.authIn, + info->cipher.aesgcm_dec.authInSz, + info->cipher.aesgcm_dec.authTagSz, 0); + if (ret != 0) { + return ret; + } + + /* The engine computes the tag over the ciphertext. */ + XMEMSET(tag, 0, sizeof(tag)); + req.tag = tag; + + ret = wc_NuvotonAesRun(&req); + + return wc_NuvotonAeadDecFinish(ret, tag, + info->cipher.aesgcm_dec.authTag, + info->cipher.aesgcm_dec.authTagSz, + info->cipher.aesgcm_dec.out, info->cipher.aesgcm_dec.sz, + WC_NO_ERR_TRACE(AES_GCM_AUTH_E)); + } +#endif +#ifdef HAVE_AESCCM + case WC_CIPHER_AES_CCM: + if (info->cipher.enc) { + ret = wc_NuvotonAesGcmSetup(&req, + info->cipher.aesccm_enc.aes, + info->cipher.aesccm_enc.out, info->cipher.aesccm_enc.in, + info->cipher.aesccm_enc.sz, + (byte*)info->cipher.aesccm_enc.nonce, + info->cipher.aesccm_enc.nonceSz, + info->cipher.aesccm_enc.authIn, + info->cipher.aesccm_enc.authInSz, + info->cipher.aesccm_enc.authTagSz, 1); + if (ret != 0) { + return ret; + } + req.mode = WC_NUVOTON_AES_CCM; + req.tag = info->cipher.aesccm_enc.authTag; + + return wc_NuvotonAesRun(&req); + } + else { + byte tag[WC_AES_BLOCK_SIZE]; + + ret = wc_NuvotonAesGcmSetup(&req, + info->cipher.aesccm_dec.aes, + info->cipher.aesccm_dec.out, info->cipher.aesccm_dec.in, + info->cipher.aesccm_dec.sz, + (byte*)info->cipher.aesccm_dec.nonce, + info->cipher.aesccm_dec.nonceSz, + info->cipher.aesccm_dec.authIn, + info->cipher.aesccm_dec.authInSz, + info->cipher.aesccm_dec.authTagSz, 0); + if (ret != 0) { + return ret; + } + req.mode = WC_NUVOTON_AES_CCM; + + XMEMSET(tag, 0, sizeof(tag)); + req.tag = tag; + + ret = wc_NuvotonAesRun(&req); + + return wc_NuvotonAeadDecFinish(ret, tag, + info->cipher.aesccm_dec.authTag, + info->cipher.aesccm_dec.authTagSz, + info->cipher.aesccm_dec.out, info->cipher.aesccm_dec.sz, + WC_NO_ERR_TRACE(AES_CCM_AUTH_E)); + } +#endif + default: + break; + } + + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} + +#endif /* WOLFSSL_NUVOTON_M2354 && WOLFSSL_NUVOTON_CIPHER && WOLF_CRYPTO_CB && + * !NO_AES */ diff --git a/wolfcrypt/src/port/nuvoton/nuvoton_cb_hash.c b/wolfcrypt/src/port/nuvoton/nuvoton_cb_hash.c new file mode 100644 index 00000000000..68f3bd9ea24 --- /dev/null +++ b/wolfcrypt/src/port/nuvoton/nuvoton_cb_hash.c @@ -0,0 +1,468 @@ +/* nuvoton_cb_hash.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 + */ + +/* Hashing on the M2354 CRPT SHA engine. + * + * Streams. Each hash context owns a feedback buffer, and the engine swaps its + * working state in and out of that buffer by DMA around every chunk - FBIN + * before, FBOUT after, addressed by HMAC_FBADDR. Nothing of a message stays in + * the engine between calls, so hashes may interleave freely: a TLS handshake + * running the transcript hash against the record MAC, or wc_Sha256Copy forking + * the transcript, are all safe by construction. + * + * The context therefore costs a fixed ~350 bytes - 216 bytes of feedback state + * plus one block of buffering - rather than growing with the message. + * + * The engine takes whole blocks for every chunk but the last, so updates + * buffer a partial block here and hand over as soon as a full one is + * available. The buffer is deliberately allowed to fill completely before it + * is flushed, so that a message whose length is an exact multiple of the block + * size still has a final chunk to give the engine with DMALAST set. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_NUVOTON_M2354) && defined(WOLFSSL_NUVOTON_HASH) && \ + defined(WOLF_CRYPTO_CB) + +#include +#include +#include +#include +#include + +#include "wolfcrypt/src/port/nuvoton/nuvoton_hw.h" + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +/* Per hash context streaming state, held in the wolfSSL hash devCtx. + * + * fdbck is what the engine reads and writes by DMA, so it leads the struct to + * keep it word aligned; XMALLOC gives it an address the engine can reach. */ +typedef struct { + word32 fdbck[WC_NUVOTON_SHA_FDBCK_WORDS]; + byte block[WC_NUVOTON_SHA_BLOCK_MAX]; /* partial block between updates */ + word32 used; /* bytes currently in block */ + word32 blockSz; /* 64 or 128, by algorithm */ + int shaMode; + int started; /* has a chunk gone to the engine for this message */ +} NuvotonShaCtx; + +/* Release a hash context's streaming state. It holds a slice of the message + * and the engine's working state, so wipe it. */ +static void wc_NuvotonShaCtxFree(NuvotonShaCtx* ctx) +{ + if (ctx == NULL) { + return; + } + ForceZero(ctx, sizeof(*ctx)); + XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); +} + +/* Hand one chunk to the engine, carrying this context's feedback state. */ +static int wc_NuvotonShaChunk(NuvotonShaCtx* ctx, const byte* in, word32 inSz, + byte* digest, word32 digestSz, int last) +{ + wc_NuvotonShaReq req; + int ret; + + XMEMSET(&req, 0, sizeof(req)); + req.fdbck = ctx->fdbck; + req.in = in; + req.inSz = inSz; + req.digest = digest; + req.digestSz = digestSz; + req.shaMode = ctx->shaMode; + req.first = (ctx->started == 0); + req.last = last; + + ret = wc_nuvoton_hw_sha(&req); + if (ret == 0) { + ctx->started = 1; + } + + return ret; +} + +/* Block size for this algorithm, or 0 if we do not support it. */ +static word32 wc_NuvotonShaBlockSz(int hashType) +{ + switch (hashType) { +#ifndef NO_SHA + case WC_HASH_TYPE_SHA: + return WC_SHA_BLOCK_SIZE; +#endif +#ifdef WOLFSSL_SHA224 + case WC_HASH_TYPE_SHA224: + return WC_SHA224_BLOCK_SIZE; +#endif +#ifndef NO_SHA256 + case WC_HASH_TYPE_SHA256: + return WC_SHA256_BLOCK_SIZE; +#endif +#ifdef WOLFSSL_SHA384 + case WC_HASH_TYPE_SHA384: + return WC_SHA384_BLOCK_SIZE; +#endif +#ifdef WOLFSSL_SHA512 + case WC_HASH_TYPE_SHA512: + return WC_SHA512_BLOCK_SIZE; +#endif + default: + return 0; + } +} + +/* Size of the hash context for this type, or 0 if we do not support it. */ +static word32 wc_NuvotonHashCtxSize(int hashType) +{ + switch (hashType) { +#ifndef NO_SHA + case WC_HASH_TYPE_SHA: + return (word32)sizeof(wc_Sha); +#endif +#ifdef WOLFSSL_SHA224 + case WC_HASH_TYPE_SHA224: + return (word32)sizeof(wc_Sha224); +#endif +#ifndef NO_SHA256 + case WC_HASH_TYPE_SHA256: + return (word32)sizeof(wc_Sha256); +#endif +#ifdef WOLFSSL_SHA384 + case WC_HASH_TYPE_SHA384: + return (word32)sizeof(wc_Sha384); +#endif +#ifdef WOLFSSL_SHA512 + case WC_HASH_TYPE_SHA512: + return (word32)sizeof(wc_Sha512); +#endif + default: + return 0; + } +} + +/* Address of the devCtx field for this hash type, or NULL if unsupported. */ +static void** wc_NuvotonHashDevCtx(void* hashCtx, int hashType) +{ + if (hashCtx == NULL) { + return NULL; + } + + switch (hashType) { +#ifndef NO_SHA + case WC_HASH_TYPE_SHA: + return &((wc_Sha*)hashCtx)->devCtx; +#endif +#ifdef WOLFSSL_SHA224 + case WC_HASH_TYPE_SHA224: + return &((wc_Sha224*)hashCtx)->devCtx; +#endif +#ifndef NO_SHA256 + case WC_HASH_TYPE_SHA256: + return &((wc_Sha256*)hashCtx)->devCtx; +#endif +#ifdef WOLFSSL_SHA384 + case WC_HASH_TYPE_SHA384: + return &((wc_Sha384*)hashCtx)->devCtx; +#endif +#ifdef WOLFSSL_SHA512 + case WC_HASH_TYPE_SHA512: + return &((wc_Sha512*)hashCtx)->devCtx; +#endif + default: + return NULL; + } +} + +/* Work out the engine mode, digest length and devCtx for this hash. Returns 0 + * if the CRPT SHA engine can do it. */ +static int wc_NuvotonHashResolve(wc_CryptoInfo* info, void*** devCtx, + int* shaMode, word32* hashLen) +{ + if (info == NULL || devCtx == NULL || shaMode == NULL || hashLen == NULL) { + return BAD_FUNC_ARG; + } + + switch (info->hash.type) { +#ifndef NO_SHA + case WC_HASH_TYPE_SHA: + *devCtx = wc_NuvotonHashDevCtx(info->hash.sha1, info->hash.type); + *shaMode = WC_NUVOTON_SHA_1; + *hashLen = WC_SHA_DIGEST_SIZE; + break; +#endif +#ifdef WOLFSSL_SHA224 + case WC_HASH_TYPE_SHA224: + *devCtx = wc_NuvotonHashDevCtx(info->hash.sha224, info->hash.type); + *shaMode = WC_NUVOTON_SHA_224; + *hashLen = WC_SHA224_DIGEST_SIZE; + break; +#endif +#ifndef NO_SHA256 + case WC_HASH_TYPE_SHA256: + *devCtx = wc_NuvotonHashDevCtx(info->hash.sha256, info->hash.type); + *shaMode = WC_NUVOTON_SHA_256; + *hashLen = WC_SHA256_DIGEST_SIZE; + break; +#endif +#ifdef WOLFSSL_SHA384 + case WC_HASH_TYPE_SHA384: + *devCtx = wc_NuvotonHashDevCtx(info->hash.sha384, info->hash.type); + *shaMode = WC_NUVOTON_SHA_384; + *hashLen = WC_SHA384_DIGEST_SIZE; + break; +#endif +#ifdef WOLFSSL_SHA512 + case WC_HASH_TYPE_SHA512: + #ifdef WOLFSSL_SHA512_HASHTYPE + /* The engine only does full SHA-512. SHA-512/224 and SHA-512/256 + * start from different initial values, so they must go to + * software. hashType is the only way to tell them apart and it + * exists only with WOLFSSL_SHA512_HASHTYPE, which is why + * nuvoton_settings.h turns that on and refuses a build without + * it: those two variants are gated on the negative + * WOLFSSL_NOSHA512_224 / _256, so they are on by DEFAULT, and + * without the member this check silently disappears. + * + * Caught on hardware. The benchmark showed SHA-512/224 and + * SHA-512/256 running at full SHA-512 speed on the accelerator, + * which meant they were being answered with a full SHA-512 digest + * and no error at all. Wrong results, silently, in the default + * configuration. */ + if (info->hash.sha512 != NULL && + (info->hash.sha512->hashType == WC_HASH_TYPE_SHA512_224 || + info->hash.sha512->hashType == WC_HASH_TYPE_SHA512_256)) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + #endif + *devCtx = wc_NuvotonHashDevCtx(info->hash.sha512, info->hash.type); + *shaMode = WC_NUVOTON_SHA_512; + *hashLen = WC_SHA512_DIGEST_SIZE; + break; +#endif + default: + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + if (*devCtx == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + return 0; +} + +/* Handles update and final for a hash. */ +static int wc_NuvotonHashCompute(wc_CryptoInfo* info) +{ + void** devCtxPtr = NULL; + int shaMode = 0; + word32 hashLen = 0; + word32 blockSz; + NuvotonShaCtx* ctx; + int ret; + + ret = wc_NuvotonHashResolve(info, &devCtxPtr, &shaMode, &hashLen); + if (ret != 0) { + return ret; + } + + blockSz = wc_NuvotonShaBlockSz(info->hash.type); + if (blockSz == 0 || blockSz > WC_NUVOTON_SHA_BLOCK_MAX) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ctx = (NuvotonShaCtx*)(*devCtxPtr); + if (ctx == NULL) { + ctx = (NuvotonShaCtx*)XMALLOC(sizeof(NuvotonShaCtx), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (ctx == NULL) { + return MEMORY_E; + } + XMEMSET(ctx, 0, sizeof(*ctx)); + ctx->blockSz = blockSz; + ctx->shaMode = shaMode; + *devCtxPtr = ctx; + } + + /* update: fill the block buffer and hand over whole blocks. The buffer is + * allowed to fill completely before flushing, so a message that is an + * exact multiple of the block size still leaves a block for the final + * call to send with DMALAST. */ + if (info->hash.in != NULL) { + const byte* in = info->hash.in; + word32 sz = info->hash.inSz; + + while (sz > 0) { + word32 take = ctx->blockSz - ctx->used; + + if (take > sz) { + take = sz; + } + XMEMCPY(ctx->block + ctx->used, in, take); + ctx->used += take; + in += take; + sz -= take; + + if (ctx->used == ctx->blockSz && sz > 0) { + ret = wc_NuvotonShaChunk(ctx, ctx->block, ctx->used, NULL, 0, + 0); + if (ret != 0) { + break; + } + ctx->used = 0; + } + } + + if (ret != 0) { + /* Nothing partial can be handed back to software - it has not seen + * the earlier updates - so a failure here is a failure. An address + * the engine cannot reach is the one exception, and that can only + * happen on the very first chunk with our own buffer, which is + * always reachable. */ + wc_NuvotonShaCtxFree(ctx); + *devCtxPtr = NULL; + return ret; + } + } + + /* final: send whatever is left with DMALAST and let the engine pad. */ + if (info->hash.digest != NULL) { + if (ctx->started == 0 && ctx->used == 0) { + /* The empty message. The result register is only written by a DMA + * round and a zero-length round does not start one, so software + * takes it. */ + wc_NuvotonShaCtxFree(ctx); + *devCtxPtr = NULL; + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = wc_NuvotonShaChunk(ctx, ctx->block, ctx->used, + info->hash.digest, hashLen, 1); + + wc_NuvotonShaCtxFree(ctx); + *devCtxPtr = NULL; + + if (ret != 0) { + return ret; + } + } + + return 0; +} + +int wc_NuvotonCb_Hash(wc_CryptoInfo* info) +{ + if (info == NULL) { + return BAD_FUNC_ARG; + } + + return wc_NuvotonHashCompute(info); +} + +#ifdef WOLF_CRYPTO_CB_COPY +int wc_NuvotonCb_HashCopy(wc_CryptoInfo* info) +{ + void** srcDevCtx; + void** dstDevCtx; + NuvotonShaCtx* srcCtx; + NuvotonShaCtx* dstCtx; + word32 ctxSize; + + if (info == NULL) { + return BAD_FUNC_ARG; + } + if (info->copy.algo != WC_ALGO_TYPE_HASH) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + srcDevCtx = wc_NuvotonHashDevCtx(info->copy.src, info->copy.type); + dstDevCtx = wc_NuvotonHashDevCtx(info->copy.dst, info->copy.type); + ctxSize = wc_NuvotonHashCtxSize(info->copy.type); + if (srcDevCtx == NULL || dstDevCtx == NULL || ctxSize == 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + /* wolfSSL skips its own copy when this succeeds, so do all of it. Free + * anything the destination already holds or it would leak. */ + wc_NuvotonShaCtxFree((NuvotonShaCtx*)(*dstDevCtx)); + + XMEMCPY(info->copy.dst, info->copy.src, ctxSize); + + srcCtx = (NuvotonShaCtx*)(*srcDevCtx); + if (srcCtx == NULL) { + *dstDevCtx = NULL; + return 0; + } + + /* Give the fork its own feedback buffer. Sharing one would have the two + * hashes overwrite each other's state on the next chunk, which is the + * whole thing this design exists to avoid. */ + dstCtx = (NuvotonShaCtx*)XMALLOC(sizeof(NuvotonShaCtx), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (dstCtx == NULL) { + *dstDevCtx = NULL; + return MEMORY_E; + } + XMEMCPY(dstCtx, srcCtx, sizeof(*dstCtx)); + + *dstDevCtx = dstCtx; + return 0; +} +#endif /* WOLF_CRYPTO_CB_COPY */ + +#ifdef WOLF_CRYPTO_CB_FREE +/* Release the saved message a hash context is holding. Called from + * wc_NuvotonCb_Free() for WC_ALGO_TYPE_HASH objects. */ +int wc_NuvotonHashFree(wc_CryptoInfo* info) +{ + void** devCtx; + NuvotonShaCtx* ctx; + + if (info == NULL) { + return BAD_FUNC_ARG; + } + + devCtx = wc_NuvotonHashDevCtx(info->free.obj, info->free.type); + if (devCtx != NULL) { + ctx = (NuvotonShaCtx*)(*devCtx); + if (ctx != NULL) { + wc_NuvotonShaCtxFree(ctx); + *devCtx = NULL; + } + } + + /* Decline so wolfSSL still wipes the context. devCtx is NULL now, so + * nothing gets freed twice. */ + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} +#endif /* WOLF_CRYPTO_CB_FREE */ + +#endif /* WOLFSSL_NUVOTON_M2354 && WOLFSSL_NUVOTON_HASH && WOLF_CRYPTO_CB */ diff --git a/wolfcrypt/src/port/nuvoton/nuvoton_cb_pk.c b/wolfcrypt/src/port/nuvoton/nuvoton_cb_pk.c new file mode 100644 index 00000000000..235d417e694 --- /dev/null +++ b/wolfcrypt/src/port/nuvoton/nuvoton_cb_pk.c @@ -0,0 +1,869 @@ +/* nuvoton_cb_pk.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 + */ + +/* Public key work on the M2354 CRPT engine: ECDSA, ECDH, ECC key generation + * and RSA. + * + * The driver takes and returns every value as a NUL terminated hex string + * rather than as a byte array, so this file spends most of its length + * converting. It converts with what wolfCrypt already has - mp_tohex and + * mp_read_radix for anything that is already an mp_int, wc_DataToHexString + * (asn.c, written for the custom ECC curve parameter strings) for a byte + * array, and an mp_int round trip coming back - rather than growing its own. + * + * Two things about the engine shape the code: + * + * The engine signs a number, not a message: the caller reduces the digest to + * an integer e first, exactly as FIPS 186 describes and as Nuvoton's own + * mbedTLS layer does in derive_mpi(). That truncation is copied from the + * software path in wolfcrypt/src/ecc.c so the two agree. + * + * The BSP public key drivers block on a flag that only the CRPT interrupt + * sets. The application must route CRPT_IRQHandler to ECC_DriverISR(); with + * no handler every call here times out. That is reported as WC_HW_E rather + * than declined to software, because a missing handler is a wiring mistake + * and a silent, very slow fallback would hide it. */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_NUVOTON_M2354) && defined(WOLF_CRYPTO_CB) && \ + (defined(WOLFSSL_NUVOTON_ECC) || defined(WOLFSSL_NUVOTON_RSA)) + +#include +#include +#include +#include +#ifdef HAVE_ECC + #include +#endif +#ifndef NO_RSA + #include +#endif +#ifdef WOLFSSL_NUVOTON_KS + #include +#endif + +#include "wolfcrypt/src/port/nuvoton/nuvoton_hw.h" + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +/* Room for the largest curve the engine takes, P-521: 66 bytes is 132 hex + * characters plus the NUL. Rounded up to the width Nuvoton uses for the same + * strings in ECC_CURVE. */ +#define NUVOTON_ECC_HEX_SZ 144 + +/* Decode one of the driver's hex strings into a fixed width big-endian byte + * array, left padded with zeros. Goes through an mp_int rather than + * Base16_Decode() so that an odd number of digits and a value shorter than + * the field both come out right, and so the port does not need + * WOLFSSL_BASE16 turned on in every build. */ +static int nuvoton_hex_to_bin(const char* hex, byte* out, word32 outSz) +{ + mp_int a[1]; + int ret; + + if (hex == NULL || out == NULL || outSz == 0) { + return BAD_FUNC_ARG; + } + + ret = mp_init(a); + if (ret != MP_OKAY) { + return ret; + } + + ret = mp_read_radix(a, hex, MP_RADIX_HEX); + if (ret == MP_OKAY) { + if ((word32)mp_unsigned_bin_size(a) > outSz) { + ret = BUFFER_E; + } + } + if (ret == MP_OKAY) { + ret = mp_to_unsigned_bin_len(a, out, (int)outSz); + } + + mp_forcezero(a); + mp_free(a); + + return ret; +} + +#ifdef WOLFSSL_NUVOTON_ECC + +/* Every hex string one ECC request needs, in one allocation. Together they + * are about a kilobyte, too much to leave on the stack of a caller that may + * already be deep inside a handshake. */ +typedef struct { + char e[NUVOTON_ECC_HEX_SZ]; + char d[NUVOTON_ECC_HEX_SZ]; + char k[NUVOTON_ECC_HEX_SZ]; + char qx[NUVOTON_ECC_HEX_SZ]; + char qy[NUVOTON_ECC_HEX_SZ]; + char r[NUVOTON_ECC_HEX_SZ]; + char s[NUVOTON_ECC_HEX_SZ]; +} NuvotonEccHex; + +/* Map a wolfCrypt curve id onto the port's selector, or + * WC_NUVOTON_CURVE_NONE when the engine has no equivalent. The engine also + * does the Koblitz and binary field curves and SM2, which wolfCrypt either + * does not carry or routes elsewhere. */ +static int nuvoton_curve_id(int curveId) +{ + switch (curveId) { +#ifdef HAVE_ECC192 + case ECC_SECP192R1: + return WC_NUVOTON_CURVE_P192; +#endif +#ifdef HAVE_ECC224 + case ECC_SECP224R1: + return WC_NUVOTON_CURVE_P224; +#endif +#ifndef NO_ECC256 + case ECC_SECP256R1: + return WC_NUVOTON_CURVE_P256; +#endif +#ifdef HAVE_ECC384 + case ECC_SECP384R1: + return WC_NUVOTON_CURVE_P384; +#endif +#ifdef HAVE_ECC521 + case ECC_SECP521R1: + return WC_NUVOTON_CURVE_P521; +#endif +#ifdef HAVE_ECC_BRAINPOOL + #ifdef HAVE_ECC256 + case ECC_BRAINPOOLP256R1: + return WC_NUVOTON_CURVE_BP256; + #endif + #ifdef HAVE_ECC384 + case ECC_BRAINPOOLP384R1: + return WC_NUVOTON_CURVE_BP384; + #endif + #ifdef HAVE_ECC512 + case ECC_BRAINPOOLP512R1: + return WC_NUVOTON_CURVE_BP512; + #endif +#endif + default: + return WC_NUVOTON_CURVE_NONE; + } +} + +/* Whether an ecc_key is carrying a Key Store handle rather than ordinary key + * material. Those are declined: signing from a stored ECC key produces a + * signature that does not verify, and no slot layout tried on hardware + * changes that, so the path is not offered rather than offered broken. AES + * keys from the store are unaffected and are validated. */ +static int nuvoton_ecc_is_ks(const ecc_key* key) +{ +#ifdef WOLFSSL_NUVOTON_KS + return (key != NULL && key->devCtx != NULL); +#else + (void)key; + return 0; +#endif +} + +/* Reduce a digest to the integer the signature equation uses: take the + * leftmost bytes up to the order size, then shift off the spare bits. Same + * steps as the software path in wolfcrypt/src/ecc.c, so a signature made here + * and one made there agree. */ +static int nuvoton_digest_to_hex(const byte* digest, word32 digestSz, + const ecc_set_type* dp, char* out, word32 outSz) +{ + mp_int e[1]; + mp_int order[1]; + int ret; + int orderBits; + word32 useSz = digestSz; + + if (digest == NULL || dp == NULL || out == NULL) { + return BAD_FUNC_ARG; + } + + ret = mp_init_multi(e, order, NULL, NULL, NULL, NULL); + if (ret != MP_OKAY) { + return ret; + } + + ret = mp_read_radix(order, dp->order, MP_RADIX_HEX); + if (ret == MP_OKAY) { + orderBits = mp_count_bits(order); + + if ((word32)orderBits < useSz * WOLFSSL_BIT_SIZE) { + useSz = ((word32)orderBits + WOLFSSL_BIT_SIZE - 1) / + WOLFSSL_BIT_SIZE; + } + + ret = mp_read_unsigned_bin(e, digest, useSz); + } + if (ret == MP_OKAY) { + if (useSz * WOLFSSL_BIT_SIZE > (word32)orderBits) { + ret = mp_rshb(e, (int)(WOLFSSL_BIT_SIZE - + ((word32)orderBits & 0x7))); + } + } + if (ret == MP_OKAY) { + if ((word32)mp_unsigned_bin_size(e) * 2 + 1 > outSz) { + ret = BUFFER_E; + } + } + if (ret == MP_OKAY) { + ret = mp_tohex(e, out); + } + + mp_free(order); + mp_free(e); + + return ret; +} + +/* An mp_int as a hex string the driver will take. */ +static int nuvoton_mp_to_hex(mp_int* a, char* out, word32 outSz) +{ + if (a == NULL || out == NULL) { + return BAD_FUNC_ARG; + } + if ((word32)mp_unsigned_bin_size(a) * 2 + 1 > outSz) { + return BUFFER_E; + } + + return mp_tohex(a, out); +} + +/* A fresh per-message random in [1, n-1], as a hex string. The engine needs + * one handed to it; it has no internal source for it. */ +static int nuvoton_gen_k_hex(WC_RNG* rng, const ecc_set_type* dp, char* out, + word32 outSz) +{ + mp_int k[1]; + mp_int order[1]; + int ret; + + if (rng == NULL || dp == NULL || out == NULL) { + return BAD_FUNC_ARG; + } + + ret = mp_init_multi(k, order, NULL, NULL, NULL, NULL); + if (ret != MP_OKAY) { + return ret; + } + + ret = mp_read_radix(order, dp->order, MP_RADIX_HEX); + if (ret == MP_OKAY) { + ret = wc_ecc_gen_k(rng, dp->size, k, order); + } + if (ret == MP_OKAY) { + ret = nuvoton_mp_to_hex(k, out, outSz); + } + + mp_forcezero(k); + mp_free(order); + mp_free(k); + + return ret; +} + + +/* wolfCrypt rejects signing or verifying an all-zero digest, and enforces a + * digest length range, in wc_ecc_sign_hash_ex() and wc_ecc_verify_hash_ex(). + * A crypto callback intercepts before either runs, so the port has to apply + * the same contract itself or it silently accepts inputs the software path + * refuses - which wolfcrypt_test checks for directly. The STM32 PKA path in + * wolfcrypt/src/ecc.c re-implements it for the same reason. */ +static int nuvoton_digest_ok(const byte* digest, word32 digestSz) +{ +#ifndef WC_ALLOW_ECC_ZERO_HASH + byte isZero = 0; + word32 i; +#endif + + if (digest == NULL) { + return ECC_BAD_ARG_E; + } + if (digestSz > WC_MAX_DIGEST_SIZE || digestSz < WC_MIN_DIGEST_SIZE) { + return BAD_LENGTH_E; + } +#ifndef WC_ALLOW_ECC_ZERO_HASH + for (i = 0; i < digestSz; i++) { + isZero |= digest[i]; + } + if (isZero == 0) { + return ECC_BAD_ARG_E; + } +#endif + + return 0; +} + +static int nuvoton_ecc_sign(wc_CryptoInfo* info) +{ + NuvotonEccHex* hex; + wc_NuvotonEccReq req; + ecc_key* key = info->pk.eccsign.key; + mp_int r[1]; + mp_int s[1]; + int curveId; + int ret; + + if (key == NULL || key->dp == NULL || info->pk.eccsign.in == NULL || + info->pk.eccsign.out == NULL || info->pk.eccsign.outlen == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (info->pk.eccsign.rng == NULL) { + /* No random source means no k, and the engine will not make one. */ + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + curveId = nuvoton_curve_id(key->dp->id); + if (curveId == WC_NUVOTON_CURVE_NONE) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = nuvoton_digest_ok(info->pk.eccsign.in, info->pk.eccsign.inlen); + if (ret != 0) { + return ret; + } + + if (nuvoton_ecc_is_ks(key) || mp_iszero(wc_ecc_key_get_priv(key))) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + hex = (NuvotonEccHex*)XMALLOC(sizeof(NuvotonEccHex), key->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (hex == NULL) { + return MEMORY_E; + } + XMEMSET(hex, 0, sizeof(*hex)); + + XMEMSET(&req, 0, sizeof(req)); + req.curveId = curveId; + req.msg = hex->e; + req.r = hex->r; + req.s = hex->s; + req.keySlot = WC_NUVOTON_NO_SLOT; + + ret = nuvoton_digest_to_hex(info->pk.eccsign.in, info->pk.eccsign.inlen, + key->dp, hex->e, (word32)sizeof(hex->e)); + + if (ret == 0) { + ret = nuvoton_mp_to_hex(wc_ecc_key_get_priv(key), hex->d, + (word32)sizeof(hex->d)); + if (ret == 0) { + ret = nuvoton_gen_k_hex(info->pk.eccsign.rng, key->dp, hex->k, + (word32)sizeof(hex->k)); + } + req.d = hex->d; + req.k = hex->k; + } + + if (ret == 0) { + ret = wc_nuvoton_hw_ecc_sign(&req); + } + + /* wolfCrypt expects the DER encoded signature back, because this callback + * stands in for the whole of wc_ecc_sign_hash(). */ + if (ret == 0) { + ret = mp_init_multi(r, s, NULL, NULL, NULL, NULL); + if (ret == MP_OKAY) { + ret = mp_read_radix(r, hex->r, MP_RADIX_HEX); + if (ret == MP_OKAY) { + ret = mp_read_radix(s, hex->s, MP_RADIX_HEX); + } + if (ret == MP_OKAY) { + ret = StoreECC_DSA_Sig(info->pk.eccsign.out, + info->pk.eccsign.outlen, r, s); + } + mp_free(s); + mp_free(r); + } + } + + ForceZero(hex, sizeof(*hex)); + XFREE(hex, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +static int nuvoton_ecc_verify(wc_CryptoInfo* info) +{ + NuvotonEccHex* hex; + wc_NuvotonEccReq req; + ecc_key* key = info->pk.eccverify.key; + mp_int r[1]; + mp_int s[1]; + int curveId; + int ret; + + if (key == NULL || key->dp == NULL || info->pk.eccverify.sig == NULL || + info->pk.eccverify.hash == NULL || info->pk.eccverify.res == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + curveId = nuvoton_curve_id(key->dp->id); + if (curveId == WC_NUVOTON_CURVE_NONE) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = nuvoton_digest_ok(info->pk.eccverify.hash, info->pk.eccverify.hashlen); + if (ret != 0) { + return ret; + } + + /* Verifying with a private-only key is legal: wolfCrypt derives the public + * point from the private scalar on the way through, and callers rely on + * that - wolfcrypt_test verifies with such a key and then checks that + * exporting the public part has started working. The engine has no such + * step; it takes Qx and Qy as inputs. Reading them from a key that has not + * had them computed hands it zeros, and the engine dutifully reports the + * signature invalid, which is a wrong answer rather than an error. Leave + * this case to software, which populates the key as a side effect. */ + if (key->type == ECC_PRIVATEKEY_ONLY || + mp_iszero(key->pubkey.x) || mp_iszero(key->pubkey.y)) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + hex = (NuvotonEccHex*)XMALLOC(sizeof(NuvotonEccHex), key->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (hex == NULL) { + return MEMORY_E; + } + XMEMSET(hex, 0, sizeof(*hex)); + + ret = mp_init_multi(r, s, NULL, NULL, NULL, NULL); + if (ret == MP_OKAY) { + ret = DecodeECC_DSA_Sig(info->pk.eccverify.sig, + info->pk.eccverify.siglen, r, s); + if (ret == 0) { + ret = nuvoton_mp_to_hex(r, hex->r, (word32)sizeof(hex->r)); + } + if (ret == 0) { + ret = nuvoton_mp_to_hex(s, hex->s, (word32)sizeof(hex->s)); + } + mp_free(s); + mp_free(r); + } + + if (ret == 0) { + ret = nuvoton_digest_to_hex(info->pk.eccverify.hash, + info->pk.eccverify.hashlen, key->dp, hex->e, + (word32)sizeof(hex->e)); + } + if (ret == 0) { + ret = nuvoton_mp_to_hex(key->pubkey.x, hex->qx, + (word32)sizeof(hex->qx)); + } + if (ret == 0) { + ret = nuvoton_mp_to_hex(key->pubkey.y, hex->qy, + (word32)sizeof(hex->qy)); + } + + if (ret == 0) { + XMEMSET(&req, 0, sizeof(req)); + req.curveId = curveId; + req.msg = hex->e; + req.qx = hex->qx; + req.qy = hex->qy; + req.r = hex->r; + req.s = hex->s; + req.keySlot = WC_NUVOTON_NO_SLOT; + + ret = wc_nuvoton_hw_ecc_verify(&req); + + /* A rejected signature is a result, not an error: report it through + * res and return success, the way the software path does. */ + if (ret == 0) { + *info->pk.eccverify.res = 1; + } + else if (ret == WC_NO_ERR_TRACE(SIG_VERIFY_E)) { + *info->pk.eccverify.res = 0; + ret = 0; + } + } + + ForceZero(hex, sizeof(*hex)); + XFREE(hex, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +#ifdef HAVE_ECC_DHE +static int nuvoton_ecc_shared(wc_CryptoInfo* info) +{ + NuvotonEccHex* hex; + wc_NuvotonEccReq req; + ecc_key* priv = info->pk.ecdh.private_key; + ecc_key* pub = info->pk.ecdh.public_key; + int curveId; + int ret; + word32 outSz; + + if (priv == NULL || pub == NULL || priv->dp == NULL || + info->pk.ecdh.out == NULL || info->pk.ecdh.outlen == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + curveId = nuvoton_curve_id(priv->dp->id); + if (curveId == WC_NUVOTON_CURVE_NONE) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + outSz = (word32)priv->dp->size; + if (*info->pk.ecdh.outlen < outSz) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + /* This callback runs ahead of the checks wc_ecc_shared_secret() would + * make, so anything it would have rejected has to be rejected here too - + * otherwise a mismatched or malformed key reaches the engine and comes + * back as a hardware error, or as a shared secret computed over the wrong + * curve. Declining sends it to software, which reports it properly. */ + if (pub->dp == NULL || pub->dp->id != priv->dp->id || + pub->idx != priv->idx || pub->type == ECC_PRIVATEKEY_ONLY || + priv->type == ECC_PUBLICKEY) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (mp_iszero(wc_ecc_key_get_priv(priv)) || + mp_iszero(pub->pubkey.x) || mp_iszero(pub->pubkey.y)) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + /* A stored private key cannot answer this: ECC_GenerateSecretZ_KS() leaves + * Z in a slot and hands nothing back. */ + if (nuvoton_ecc_is_ks(priv)) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + hex = (NuvotonEccHex*)XMALLOC(sizeof(NuvotonEccHex), priv->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (hex == NULL) { + return MEMORY_E; + } + XMEMSET(hex, 0, sizeof(*hex)); + + ret = nuvoton_mp_to_hex(pub->pubkey.x, hex->qx, (word32)sizeof(hex->qx)); + if (ret == 0) { + ret = nuvoton_mp_to_hex(pub->pubkey.y, hex->qy, + (word32)sizeof(hex->qy)); + } + if (ret == 0) { + ret = nuvoton_mp_to_hex(wc_ecc_key_get_priv(priv), hex->d, + (word32)sizeof(hex->d)); + } + + if (ret == 0) { + XMEMSET(&req, 0, sizeof(req)); + req.curveId = curveId; + req.qx = hex->qx; + req.qy = hex->qy; + req.d = hex->d; + req.out = hex->e; /* reuse a spare buffer of the right width */ + req.keySlot = WC_NUVOTON_NO_SLOT; + + ret = wc_nuvoton_hw_ecc_shared(&req); + } + + if (ret == 0) { + ret = nuvoton_hex_to_bin(hex->e, info->pk.ecdh.out, outSz); + } + if (ret == 0) { + *info->pk.ecdh.outlen = outSz; + } + + ForceZero(hex, sizeof(*hex)); + XFREE(hex, priv->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +static int nuvoton_ecc_keygen(wc_CryptoInfo* info) +{ + NuvotonEccHex* hex; + wc_NuvotonEccReq req; + ecc_key* key = info->pk.eckg.key; + mp_int order[1]; + int curveId; + int ret; + + if (key == NULL || info->pk.eckg.rng == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + /* The caller may name a curve, or leave it to the key it passed in. */ + if (info->pk.eckg.curveId != ECC_CURVE_DEF) { + ret = wc_ecc_set_curve(key, info->pk.eckg.size, + info->pk.eckg.curveId); + if (ret != 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + } + if (key->dp == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + curveId = nuvoton_curve_id(key->dp->id); + if (curveId == WC_NUVOTON_CURVE_NONE) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + hex = (NuvotonEccHex*)XMALLOC(sizeof(NuvotonEccHex), key->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (hex == NULL) { + return MEMORY_E; + } + XMEMSET(hex, 0, sizeof(*hex)); + + /* The private scalar is drawn here and the engine only derives the point: + * the driver has no key generation entry point of its own that returns + * the private half. */ + ret = mp_init(order); + if (ret == MP_OKAY) { + ret = mp_read_radix(order, key->dp->order, MP_RADIX_HEX); + if (ret == MP_OKAY) { + ret = wc_ecc_gen_k(info->pk.eckg.rng, key->dp->size, + wc_ecc_key_get_priv(key), order); + } + mp_free(order); + } + if (ret == 0) { + ret = nuvoton_mp_to_hex(wc_ecc_key_get_priv(key), hex->d, + (word32)sizeof(hex->d)); + } + + if (ret == 0) { + XMEMSET(&req, 0, sizeof(req)); + req.curveId = curveId; + req.d = hex->d; + req.qx = hex->qx; + req.qy = hex->qy; + req.keySlot = WC_NUVOTON_NO_SLOT; + + ret = wc_nuvoton_hw_ecc_pubkey(&req); + } + + if (ret == 0) { + ret = mp_read_radix(key->pubkey.x, hex->qx, MP_RADIX_HEX); + } + if (ret == 0) { + ret = mp_read_radix(key->pubkey.y, hex->qy, MP_RADIX_HEX); + } + if (ret == 0) { + ret = mp_set(key->pubkey.z, 1); + } + if (ret == 0) { + key->type = ECC_PRIVATEKEY; + } + + ForceZero(hex, sizeof(*hex)); + XFREE(hex, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif /* HAVE_ECC_DHE */ + +#endif /* WOLFSSL_NUVOTON_ECC */ + +#if defined(WOLFSSL_NUVOTON_RSA) && !defined(NO_RSA) + +/* Every hex string one RSA request needs. Sized from the key in hand rather + * than from the 4096 bit maximum, because at that size these four strings + * come to four kilobytes. */ +typedef struct { + char* in; + char* n; + char* e; + char* out; + word32 hexSz; /* characters plus the NUL, one buffer */ +} NuvotonRsaHex; + +static void nuvoton_rsa_hex_free(NuvotonRsaHex* hex, void* heap) +{ + if (hex->in != NULL) { + ForceZero(hex->in, hex->hexSz); + XFREE(hex->in, heap, DYNAMIC_TYPE_TMP_BUFFER); + hex->in = NULL; + } + if (hex->n != NULL) { + XFREE(hex->n, heap, DYNAMIC_TYPE_TMP_BUFFER); + hex->n = NULL; + } + if (hex->e != NULL) { + ForceZero(hex->e, hex->hexSz); + XFREE(hex->e, heap, DYNAMIC_TYPE_TMP_BUFFER); + hex->e = NULL; + } + if (hex->out != NULL) { + ForceZero(hex->out, hex->hexSz); + XFREE(hex->out, heap, DYNAMIC_TYPE_TMP_BUFFER); + hex->out = NULL; + } +} + +static int nuvoton_rsa_hex_alloc(NuvotonRsaHex* hex, word32 hexSz, void* heap) +{ + XMEMSET(hex, 0, sizeof(*hex)); + hex->hexSz = hexSz; + + hex->in = (char*)XMALLOC(hexSz, heap, DYNAMIC_TYPE_TMP_BUFFER); + hex->n = (char*)XMALLOC(hexSz, heap, DYNAMIC_TYPE_TMP_BUFFER); + hex->e = (char*)XMALLOC(hexSz, heap, DYNAMIC_TYPE_TMP_BUFFER); + hex->out = (char*)XMALLOC(hexSz, heap, DYNAMIC_TYPE_TMP_BUFFER); + + if (hex->in == NULL || hex->n == NULL || hex->e == NULL || + hex->out == NULL) { + nuvoton_rsa_hex_free(hex, heap); + return MEMORY_E; + } + + return 0; +} + +static int nuvoton_rsa(wc_CryptoInfo* info) +{ + NuvotonRsaHex hex; + wc_NuvotonRsaReq req; + RsaKey* key = info->pk.rsa.key; + void* heap; + mp_int* exp; + word32 modSz; + word32 hexSz; + int keyBits; + int isPrivate; + int ret; + + if (key == NULL || info->pk.rsa.in == NULL || + info->pk.rsa.out == NULL || info->pk.rsa.outLen == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + heap = key->heap; + modSz = (word32)mp_unsigned_bin_size(&key->n); + keyBits = mp_count_bits(&key->n); + + /* The engine takes four sizes and nothing between them. */ + if (keyBits != 1024 && keyBits != 2048 && keyBits != 3072 && + keyBits != 4096) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (info->pk.rsa.inLen > modSz || *info->pk.rsa.outLen < modSz) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + isPrivate = (info->pk.rsa.type == RSA_PRIVATE_DECRYPT || + info->pk.rsa.type == RSA_PRIVATE_ENCRYPT); + if (isPrivate) { + exp = &key->d; + if (mp_iszero(exp)) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + } + else { + exp = &key->e; + } + + /* Two hex characters per byte, plus the NUL. */ + hexSz = modSz * 2 + 1; + + ret = nuvoton_rsa_hex_alloc(&hex, hexSz, heap); + if (ret != 0) { + return ret; + } + + /* The base is a byte array, so it takes the byte array converter; the + * modulus and exponent are already mp_ints. */ + wc_DataToHexString(info->pk.rsa.in, info->pk.rsa.inLen, hex.in); + + ret = mp_tohex(&key->n, hex.n); + if (ret == MP_OKAY) { + ret = mp_tohex(exp, hex.e); + } + + if (ret == 0) { + XMEMSET(&req, 0, sizeof(req)); + req.in = hex.in; + req.n = hex.n; + req.e = hex.e; + req.out = hex.out; + req.outSz = hexSz; + req.keyBits = keyBits; + req.keySlot = WC_NUVOTON_NO_SLOT; + + ret = wc_nuvoton_hw_rsa(&req); + } + + if (ret == 0) { + ret = nuvoton_hex_to_bin(hex.out, info->pk.rsa.out, modSz); + } + if (ret == 0) { + *info->pk.rsa.outLen = modSz; + } + + nuvoton_rsa_hex_free(&hex, heap); + + return ret; +} + +#endif /* WOLFSSL_NUVOTON_RSA && !NO_RSA */ + +int wc_NuvotonCb_Pk(wc_CryptoInfo* info) +{ + if (info == NULL) { + return BAD_FUNC_ARG; + } + + switch (info->pk.type) { +#if defined(WOLFSSL_NUVOTON_ECC) && defined(HAVE_ECC) + #ifdef HAVE_ECC_SIGN + case WC_PK_TYPE_ECDSA_SIGN: + return nuvoton_ecc_sign(info); + #endif + #ifdef HAVE_ECC_VERIFY + case WC_PK_TYPE_ECDSA_VERIFY: + return nuvoton_ecc_verify(info); + #endif + #ifdef HAVE_ECC_DHE + case WC_PK_TYPE_ECDH: + return nuvoton_ecc_shared(info); + case WC_PK_TYPE_EC_KEYGEN: + return nuvoton_ecc_keygen(info); + #endif +#endif +#if defined(WOLFSSL_NUVOTON_RSA) && !defined(NO_RSA) + case WC_PK_TYPE_RSA: + return nuvoton_rsa(info); +#endif + default: + break; + } + + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} + +#endif /* WOLFSSL_NUVOTON_M2354 && WOLF_CRYPTO_CB && + * (WOLFSSL_NUVOTON_ECC || WOLFSSL_NUVOTON_RSA) */ diff --git a/wolfcrypt/src/port/nuvoton/nuvoton_cb_rng.c b/wolfcrypt/src/port/nuvoton/nuvoton_cb_rng.c new file mode 100644 index 00000000000..4a0ead6fa20 --- /dev/null +++ b/wolfcrypt/src/port/nuvoton/nuvoton_cb_rng.c @@ -0,0 +1,66 @@ +/* nuvoton_cb_rng.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 + */ + +/* Random numbers on the M2354. Only the seed comes from here by default; the + * stream is wolfCrypt's Hash-DRBG. WOLFSSL_NUVOTON_RNG_OFFLOAD takes the + * stream from the engine instead. See the dispatcher for why. */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_NUVOTON_M2354) && defined(WOLFSSL_NUVOTON_TRNG) && \ + defined(WOLF_CRYPTO_CB) + +#include +#include + +#include "wolfcrypt/src/port/nuvoton/nuvoton_hw.h" + +#ifdef WOLFSSL_NUVOTON_RNG_OFFLOAD +int wc_NuvotonCb_Rng(wc_CryptoInfo* info) +{ + if (info == NULL) { + return BAD_FUNC_ARG; + } + if (info->rng.out == NULL || info->rng.sz == 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + return wc_nuvoton_hw_trng(info->rng.out, info->rng.sz); +} +#endif /* WOLFSSL_NUVOTON_RNG_OFFLOAD */ + +int wc_NuvotonCb_Seed(wc_CryptoInfo* info) +{ + if (info == NULL) { + return BAD_FUNC_ARG; + } + if (info->seed.seed == NULL || info->seed.sz == 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + return wc_nuvoton_hw_trng(info->seed.seed, info->seed.sz); +} + +#endif /* WOLFSSL_NUVOTON_M2354 && WOLFSSL_NUVOTON_TRNG && WOLF_CRYPTO_CB */ diff --git a/wolfcrypt/src/port/nuvoton/nuvoton_cryptocb.c b/wolfcrypt/src/port/nuvoton/nuvoton_cryptocb.c new file mode 100644 index 00000000000..7d7f265d5b9 --- /dev/null +++ b/wolfcrypt/src/port/nuvoton/nuvoton_cryptocb.c @@ -0,0 +1,143 @@ +/* nuvoton_cryptocb.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 + */ + +/* Crypto callback dispatcher for the Nuvoton NuMicro M2354. Routes each + * wc_AlgoType to the engine file that handles it and turns everything else + * into a decline, so an operation the CRPT accelerator cannot do runs in + * software rather than failing. */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_NUVOTON_M2354) && defined(WOLF_CRYPTO_CB) + +#include +#include +#include +#include + +#include "wolfcrypt/src/port/nuvoton/nuvoton_hw.h" + +int wc_NuvotonCryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + + (void)devId; + (void)ctx; + + if (info == NULL) { + return BAD_FUNC_ARG; + } + + switch (info->algo_type) { +#ifdef WOLFSSL_NUVOTON_TRNG + /* Seed only by default. Offloading the stream benchmarks ~7x faster, + * but the engine PRNG's construction is undocumented while the + * Hash-DRBG is specified and seeded from the same TRNG. Set + * WOLFSSL_NUVOTON_RNG_OFFLOAD to prefer throughput. */ +#ifdef WOLFSSL_NUVOTON_RNG_OFFLOAD + case WC_ALGO_TYPE_RNG: + ret = wc_NuvotonCb_Rng(info); + break; +#endif + case WC_ALGO_TYPE_SEED: + ret = wc_NuvotonCb_Seed(info); + break; +#endif +#ifdef WOLFSSL_NUVOTON_HASH + case WC_ALGO_TYPE_HASH: + ret = wc_NuvotonCb_Hash(info); + break; +#endif +#ifdef WOLFSSL_NUVOTON_CIPHER + case WC_ALGO_TYPE_CIPHER: + ret = wc_NuvotonCb_Cipher(info); + break; +#endif +#if defined(WOLFSSL_NUVOTON_ECC) || defined(WOLFSSL_NUVOTON_RSA) + case WC_ALGO_TYPE_PK: + ret = wc_NuvotonCb_Pk(info); + break; +#endif +#if defined(WOLF_CRYPTO_CB_COPY) && defined(WOLFSSL_NUVOTON_HASH) + case WC_ALGO_TYPE_COPY: + ret = wc_NuvotonCb_HashCopy(info); + break; +#endif +#ifdef WOLF_CRYPTO_CB_FREE + case WC_ALGO_TYPE_FREE: + ret = wc_NuvotonCb_Free(info); + break; +#endif + default: + break; /* not ours; software handles it */ + } + + return ret; +} + +int wc_NuvotonCryptoCb_RegisterDevice(int devId) +{ + int ret; + + ret = wc_nuvoton_hw_init(); + if (ret != 0) { + WOLFSSL_MSG("Nuvoton: hardware init failed"); + return ret; + } + + ret = wc_CryptoCb_RegisterDevice(devId, wc_NuvotonCryptoDevCb, NULL); + if (ret != 0) { + wc_nuvoton_hw_cleanup(); + } + + return ret; +} + +void wc_NuvotonCryptoCb_UnRegisterDevice(int devId) +{ + wc_CryptoCb_UnRegisterDevice(devId); + wc_nuvoton_hw_cleanup(); +} + +#ifdef WOLF_CRYPTO_CB_FREE +int wc_NuvotonCb_Free(wc_CryptoInfo* info) +{ + if (info == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_NUVOTON_HASH + if (info->free.algo == WC_ALGO_TYPE_HASH) { + return wc_NuvotonHashFree(info); + } +#endif + + /* A Key Store handle in devCtx belongs to whoever wrote it. Declining + * leaves wolfCrypt's teardown, which does not touch devCtx. */ + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} +#endif /* WOLF_CRYPTO_CB_FREE */ + +#endif /* WOLFSSL_NUVOTON_M2354 && WOLF_CRYPTO_CB */ diff --git a/wolfcrypt/src/port/nuvoton/nuvoton_hw.c b/wolfcrypt/src/port/nuvoton/nuvoton_hw.c new file mode 100644 index 00000000000..221c08f45c4 --- /dev/null +++ b/wolfcrypt/src/port/nuvoton/nuvoton_hw.c @@ -0,0 +1,1924 @@ +/* nuvoton_hw.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 + */ + +/* The secure-world half of the M2354 port, and the only file in it that + * includes a BSP header. Compiled when wolfCrypt runs secure, and on the + * secure side of a TrustZone split where the veneers forward to it. Under + * WOLFSSL_NUVOTON_NSC nothing here is built. */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_NUVOTON_M2354) && defined(WOLFSSL_NUVOTON_SECURE) + +/* BSP StdDriver. The application puts M2354BSP/Library/StdDriver/inc, + * Library/Device/Nuvoton/M2354/Include and Library/CMSIS/Include on the + * include path. + * + * This has to come before any wolfSSL header other than settings.h. M2354.h + * defines TRUE and FALSE unconditionally as (1L) and (0L), while types.h + * defines them as 1 and 0 behind an #ifndef. Whichever is second wins quietly + * unless it is the BSP, which then redefines them and fails a -Werror build. */ +#include "NuMicro.h" + +#include "wolfcrypt/src/port/nuvoton/nuvoton_hw.h" + +#include +#include +#include +#include +#ifdef WOLFSSL_NUVOTON_CIPHER + #include +#endif + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +/* How many times wc_nuvoton_hw_init() has been called without a matching + * wc_nuvoton_hw_cleanup(). The hardware comes up on the first and goes down on + * the last, so several independent devices or a re-registration do not fight + * over it. Guarded by the wolfCrypt hardware mutex. */ +static int nuvotonRefCount = 0; + +/* CRPT DMA reads and writes only word-aligned SRAM addresses. Nuvoton states + * the requirement in the BSP's own mbedTLS layer + * (Library/CryptoAccelerator/aes_alt.c): "(1) Word-aligned (2) Located in + * 0x2xxxxxxx region." + * + * Both SRAM aliases have to be accepted, not just the one that note mentions. + * On this part SRAM_BASE is 0x20000000 and NS_OFFSET is 0x10000000, so secure + * code sees SRAM at 0x2xxxxxxx and non-secure code sees the same memory at + * 0x3xxxxxxx, and the alias a transfer is given is what decides the security + * attribute of the access. In a TrustZone build the buffers arriving through + * the veneers are non-secure and therefore carry 0x3xxxxxxx addresses, which + * are correct and must be passed to the engine as they are. Accepting only + * 0x2xxxxxxx would make every AES and SHA call from the non-secure world + * decline to software, silently and with no error to point at it. + * + * Note the polarity is the opposite way round from the NXP TrustZone parts, + * where the 0x3xxxxxxx SRAM alias is the secure one. */ +#define NUVOTON_DMA_REGION_MASK 0xF0000000u +#define NUVOTON_DMA_REGION_S 0x20000000u /* SRAM_BASE */ +#define NUVOTON_DMA_REGION_NS 0x30000000u /* SRAM_BASE + NS_OFFSET */ + +static int nuvoton_dma_ok(const void* p, word32 sz) +{ + uint32_t addr = (uint32_t)(uintptr_t)p; + uint32_t region; + + if (sz == 0) { + return 1; + } + if ((addr & 0x3u) != 0) { + return 0; + } + + region = addr & NUVOTON_DMA_REGION_MASK; + if (region != NUVOTON_DMA_REGION_S && region != NUVOTON_DMA_REGION_NS) { + return 0; + } + + return 1; +} + +#ifdef WOLFSSL_NUVOTON_KS + +/* Map the port's Key Store selector onto the BSP KS_MEM_Type. The two enums + * happen to agree, but the mapping is written out so a BSP renumbering shows + * up here as a compile problem rather than as keys landing in the wrong + * store. */ +static int nuvoton_ks_mem(int keyMem, KS_MEM_Type* mem) +{ + switch (keyMem) { + case WC_NUVOTON_KS_SRAM: + *mem = KS_SRAM; + break; + case WC_NUVOTON_KS_FLASH: + *mem = KS_FLASH; + break; + case WC_NUVOTON_KS_OTP: + *mem = KS_OTP; + break; + default: + return BAD_FUNC_ARG; + } + + return 0; +} + +#endif /* WOLFSSL_NUVOTON_KS */ + +int wc_nuvoton_hw_init(void) +{ + int ret = 0; + + ret = wolfSSL_CryptHwMutexInit(); + if (ret != 0) { + return ret; + } + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + + if (nuvotonRefCount == 0) { + SYS_UnlockReg(); + + /* CRPT and the Key Store share the AHB clock domain; the TRNG has its + * own on APB1 and RNG_Open() ungates it. */ + CLK_EnableModuleClock(CRPT_MODULE); + CLK_EnableModuleClock(KS_MODULE); + SYS_ResetModule(CRPT_RST); + + if (KS_Open() != 0) { + WOLFSSL_MSG("Nuvoton: KS_Open failed"); + ret = WC_HW_E; + } + + /* Brings the TRNG up and points the CRPT PRNG seed generator at it. */ + if (ret == 0 && RNG_Open() != 0) { + WOLFSSL_MSG("Nuvoton: RNG_Open failed"); + ret = WC_HW_E; + } + + /* Arm the ECC interrupt sources in the accelerator itself. The BSP's + * public key routines block on a flag that only ECC_DriverISR() sets, + * and nothing in the driver enables ECCIEN/ECCEIEN - enabling the NVIC + * line alone is not enough, because the CRPT never raises it. Without + * this every ECC call spins out TIMEOUT_ECC, which is SystemCoreClock + * iterations, and then fails. Nothing hangs and nothing reports the + * cause; the test suite simply appears to stop making progress. + * + * The AES, SHA and RSA paths here poll INTSTS directly and do not need + * an interrupt, so only ECC is armed. */ + if (ret == 0) { + ECC_ENABLE_INT(CRPT); + } + + SYS_LockReg(); + + if (ret != 0) { + CLK_DisableModuleClock(KS_MODULE); + CLK_DisableModuleClock(CRPT_MODULE); + } + } + + if (ret == 0) { + nuvotonRefCount++; + } + + wolfSSL_CryptHwMutexUnLock(); + + return ret; +} + +void wc_nuvoton_hw_cleanup(void) +{ + if (wolfSSL_CryptHwMutexLock() != 0) { + return; + } + + if (nuvotonRefCount > 0) { + nuvotonRefCount--; + + if (nuvotonRefCount == 0) { + ECC_DISABLE_INT(CRPT); + SYS_UnlockReg(); + SYS_ResetModule(CRPT_RST); + CLK_DisableModuleClock(KS_MODULE); + CLK_DisableModuleClock(CRPT_MODULE); + SYS_LockReg(); + } + } + + wolfSSL_CryptHwMutexUnLock(); +} + +#ifdef WOLFSSL_NUVOTON_TRNG + +int wc_nuvoton_hw_trng(byte* out, word32 sz) +{ + int ret = 0; + int got; + word32 chunk; + /* RNG_Random() fills whole words and hands back at most eight of them per + * call, so a partial word at the tail is taken from a staging word rather + * than written past the caller's buffer. */ + uint32_t buf[8]; + + if (out == NULL || sz == 0) { + return BAD_FUNC_ARG; + } + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + + while (sz > 0) { + chunk = sz; + if (chunk > sizeof(buf)) { + chunk = (word32)sizeof(buf); + } + + /* RNG_Random() returns the number of words it produced, and zero when + * it timed out waiting on the engine. It is not a signed error. */ + got = RNG_Random(buf, (int32_t)((chunk + 3) / 4)); + if (got <= 0) { + WOLFSSL_MSG("Nuvoton: RNG_Random failed"); + ret = RNG_FAILURE_E; + break; + } + + if (chunk > (word32)got * 4) { + chunk = (word32)got * 4; + } + + XMEMCPY(out, buf, chunk); + out += chunk; + sz -= chunk; + } + + ForceZero(buf, sizeof(buf)); + + wolfSSL_CryptHwMutexUnLock(); + + return ret; +} + +#endif /* WOLFSSL_NUVOTON_TRNG */ + + +#ifdef WOLFSSL_NUVOTON_HASH + +/* Map the port's hash selector onto the BSP SHA_MODE_* value. */ +static int nuvoton_sha_mode(int shaMode, uint32_t* opMode) +{ + switch (shaMode) { + case WC_NUVOTON_SHA_1: + *opMode = SHA_MODE_SHA1; + break; + case WC_NUVOTON_SHA_224: + *opMode = SHA_MODE_SHA224; + break; + case WC_NUVOTON_SHA_256: + *opMode = SHA_MODE_SHA256; + break; + case WC_NUVOTON_SHA_384: + *opMode = SHA_MODE_SHA384; + break; + case WC_NUVOTON_SHA_512: + *opMode = SHA_MODE_SHA512; + break; + default: + return BAD_FUNC_ARG; + } + + return 0; +} + +int wc_nuvoton_hw_sha(wc_NuvotonShaReq* req) +{ + int ret; + uint32_t opMode = 0; + uint32_t ctl; + word32 timeout; + /* SHA_Read() writes as many words as the current op mode produces, which + * is sixteen for SHA-512 and fewer for the rest. */ + uint32_t dgst[16]; + + if (req == NULL || req->fdbck == NULL) { + return BAD_FUNC_ARG; + } + if (req->in == NULL && req->inSz > 0) { + return BAD_FUNC_ARG; + } + if (req->last && (req->digest == NULL || req->digestSz == 0 || + req->digestSz > sizeof(dgst))) { + return BAD_FUNC_ARG; + } + + ret = nuvoton_sha_mode(req->shaMode, &opMode); + if (ret != 0) { + return ret; + } + + /* Both the message chunk and the feedback buffer are read and written by + * DMA, so both have to satisfy the engine's addressing rules. */ + if (!nuvoton_dma_ok(req->in, req->inSz) || + !nuvoton_dma_ok(req->fdbck, + WC_NUVOTON_SHA_FDBCK_WORDS * (word32)sizeof(word32))) { + return BAD_ALIGN_E; + } + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + + /* FBIN pulls this context's saved state in before the block and FBOUT + * writes the updated state back out after, both by DMA from FBADDR. That + * pairing is what makes concurrent hash contexts safe: nothing of this + * message stays in the engine between calls. The first chunk has no state + * to read, and the last chunk has none worth writing. */ + ctl = (opMode << CRPT_HMAC_CTL_OPMODE_Pos) | + CRPT_HMAC_CTL_INSWAP_Msk | CRPT_HMAC_CTL_OUTSWAP_Msk | + CRPT_HMAC_CTL_DMACSCAD_Msk | CRPT_HMAC_CTL_DMAEN_Msk; + + if (req->first) { + ctl |= CRPT_HMAC_CTL_DMAFIRST_Msk; + } + else { + ctl |= CRPT_HMAC_CTL_FBIN_Msk; + } + + if (req->last) { + ctl |= CRPT_HMAC_CTL_DMALAST_Msk; + } + else { + ctl |= CRPT_HMAC_CTL_FBOUT_Msk; + } + + CRPT->HMAC_CTL = ctl; + CRPT->HMAC_FBADDR = (uint32_t)(uintptr_t)req->fdbck; + CRPT->HMAC_SADDR = (uint32_t)(uintptr_t)req->in; + CRPT->HMAC_DMACNT = req->inSz; + + /* Clear the completion flag from whatever ran last, or the wait below + * returns immediately. */ + CRPT->INTSTS = CRPT_INTSTS_HMACIF_Msk; + + CRPT->HMAC_CTL = ctl | CRPT_HMAC_CTL_START_Msk; + + /* Both conditions matter: the flag says a DMA round finished, and BUSY + * says the engine has finished with it. */ + timeout = WOLFSSL_NUVOTON_HW_TIMEOUT; + while (((CRPT->INTSTS & CRPT_INTSTS_HMACIF_Msk) == 0) || + ((CRPT->HMAC_STS & CRPT_HMAC_STS_BUSY_Msk) != 0)) { + if (timeout-- == 0) { + WOLFSSL_MSG("Nuvoton: SHA timeout"); + wolfSSL_CryptHwMutexUnLock(); + return WC_TIMEOUT_E; + } + } + + if ((CRPT->HMAC_STS & CRPT_HMAC_STS_DMAERR_Msk) != 0) { + WOLFSSL_MSG("Nuvoton: SHA DMA error"); + CRPT->INTSTS = CRPT_INTSTS_HMACIF_Msk; + wolfSSL_CryptHwMutexUnLock(); + return WC_HW_E; + } + + CRPT->INTSTS = CRPT_INTSTS_HMACIF_Msk; + + if (req->last) { + SHA_Read(CRPT, dgst); + XMEMCPY(req->digest, dgst, req->digestSz); + ForceZero(dgst, sizeof(dgst)); + } + + wolfSSL_CryptHwMutexUnLock(); + + return 0; +} + +#endif /* WOLFSSL_NUVOTON_HASH */ + + +#ifdef WOLFSSL_NUVOTON_CIPHER + +/* The engine reads and writes the key, the IV and the feedback registers as + * words holding the wire bytes in memory order, with KINSWAP and the data + * INSWAP/OUTSWAP bits doing the endianness. Same convention as the BSP's own + * mbedTLS layer, so these two helpers are just an explicit spelling of the + * byte copy it does. */ +static uint32_t nuvoton_get32(const byte* p) +{ + return ((uint32_t)p[0]) | (((uint32_t)p[1]) << 8) | + (((uint32_t)p[2]) << 16) | (((uint32_t)p[3]) << 24); +} + +/* Advance a 128-bit big-endian counter block by n. */ +static void nuvoton_ctr_add(byte* ctr, word32 n) +{ + int i; + word32 carry = n; + + for (i = WC_AES_BLOCK_SIZE - 1; i >= 0 && carry != 0; i--) { + carry += ctr[i]; + ctr[i] = (byte)carry; + carry >>= 8; + } +} + +#ifdef WOLFSSL_NUVOTON_AESGCM +/* GCM loads its key words the other way up. See the KINSWAP note in + * nuvoton_aes_gcm(). */ +static uint32_t nuvoton_get32be(const byte* p) +{ + return (((uint32_t)p[0]) << 24) | (((uint32_t)p[1]) << 16) | + (((uint32_t)p[2]) << 8) | ((uint32_t)p[3]); +} +#endif + +static void nuvoton_set32(byte* p, uint32_t v) +{ + p[0] = (byte)(v ); + p[1] = (byte)(v >> 8); + p[2] = (byte)(v >> 16); + p[3] = (byte)(v >> 24); +} + +/* Staging for a request whose buffers do not meet the DMA addressing rules. + * Placed in the default .bss, which the M2354 linker scripts put in the + * 0x2xxxxxxx SRAM the engine can reach. nuvoton_aes_round() checks that before + * using them, because a linker script that puts .bss somewhere the engine + * cannot address would otherwise fail silently. Guarded by the hardware mutex + * along with the engine itself. */ +static ALIGN16 byte nuvotonDmaIn[WOLFSSL_NUVOTON_DMA_BUF_SZ]; +static ALIGN16 byte nuvotonDmaOut[WOLFSSL_NUVOTON_DMA_BUF_SZ]; + +/* Map the port's AES selector onto the BSP AES_MODE_* value. */ +static int nuvoton_aes_mode(int mode, uint32_t* opMode) +{ + switch (mode) { + case WC_NUVOTON_AES_ECB: + *opMode = AES_MODE_ECB; + break; + case WC_NUVOTON_AES_CBC: + *opMode = AES_MODE_CBC; + break; + case WC_NUVOTON_AES_CTR: + *opMode = AES_MODE_CTR; + break; + default: + /* GCM and CCM do not come through here: they need the GCM packet + * layout and the feedback buffer, not a plain DMA round. */ + return BAD_FUNC_ARG; + } + + return 0; +} + +/* Map a key length in bytes onto the BSP AES_KEY_SIZE_* value. */ +static int nuvoton_aes_keysize(word32 keySz, uint32_t* keySize) +{ + switch (keySz) { + case 16: + *keySize = AES_KEY_SIZE_128; + break; + case 24: + *keySize = AES_KEY_SIZE_192; + break; + case 32: + *keySize = AES_KEY_SIZE_256; + break; + default: + return BAD_FUNC_ARG; + } + + return 0; +} + +/* One DMA round over at most WOLFSSL_NUVOTON_DMA_BUF_SZ bytes. The caller has + * the mutex and has already programmed the key; this sets the IV, runs the + * round and leaves the chained IV in AES_FDBCK for the next one. */ +static int nuvoton_aes_round(uint32_t ctl, const byte* in, byte* out, + word32 sz, const uint32_t iv[4]) +{ + word32 timeout; + const byte* src = in; + byte* dst = out; + + /* Stage anything the engine cannot address itself. The staging buffers + * are only useful if they are themselves reachable, which depends on where + * the linker put .bss. */ + if (!nuvoton_dma_ok(in, sz)) { + if (!nuvoton_dma_ok(nuvotonDmaIn, sizeof(nuvotonDmaIn))) { + WOLFSSL_MSG("Nuvoton: DMA staging buffer is not engine addressable"); + return WC_HW_E; + } + XMEMCPY(nuvotonDmaIn, in, sz); + src = nuvotonDmaIn; + } + if (!nuvoton_dma_ok(out, sz)) { + if (!nuvoton_dma_ok(nuvotonDmaOut, sizeof(nuvotonDmaOut))) { + WOLFSSL_MSG("Nuvoton: DMA staging buffer is not engine addressable"); + return WC_HW_E; + } + dst = nuvotonDmaOut; + } + + CRPT->AES_CTL = ctl; + + AES_SetInitVect(CRPT, 0, iv); + AES_SetDMATransfer(CRPT, 0, (uint32_t)(uintptr_t)src, + (uint32_t)(uintptr_t)dst, sz); + + CRPT->INTSTS = CRPT_INTSTS_AESIF_Msk | CRPT_INTSTS_AESEIF_Msk; + + AES_Start(CRPT, 0, CRYPTO_DMA_ONE_SHOT); + + timeout = WOLFSSL_NUVOTON_HW_TIMEOUT; + while ((CRPT->INTSTS & + (CRPT_INTSTS_AESIF_Msk | CRPT_INTSTS_AESEIF_Msk)) == 0) { + if (timeout-- == 0) { + WOLFSSL_MSG("Nuvoton: AES timeout"); + return WC_TIMEOUT_E; + } + } + + if ((CRPT->INTSTS & CRPT_INTSTS_AESEIF_Msk) != 0) { + WOLFSSL_MSG("Nuvoton: AES error interrupt"); + CRPT->INTSTS = CRPT_INTSTS_AESIF_Msk | CRPT_INTSTS_AESEIF_Msk; + return WC_HW_E; + } + + CRPT->INTSTS = CRPT_INTSTS_AESIF_Msk | CRPT_INTSTS_AESEIF_Msk; + + if (dst != out) { + XMEMCPY(out, nuvotonDmaOut, sz); + } + + return 0; +} + + +#if defined(WOLFSSL_NUVOTON_AESGCM) || defined(WOLFSSL_NUVOTON_AESCCM) + +/* Packed AEAD packet staging, shared by GCM and CCM under the hardware mutex. + * Sets where GCM switches from one round to the DMA cascade; CCM has no + * cascade and is declined above this size. */ +#ifndef WOLFSSL_NUVOTON_GCM_BUF_SZ + #define WOLFSSL_NUVOTON_GCM_BUF_SZ 1088 +#endif + +static ALIGN16 byte nuvotonGcmIn[WOLFSSL_NUVOTON_GCM_BUF_SZ]; +static ALIGN16 byte nuvotonGcmOut[WOLFSSL_NUVOTON_GCM_BUF_SZ]; + +#ifdef WOLFSSL_NUVOTON_AESGCM +/* GCM feedback state between cascade rounds, as the hash path carries + * HMAC_FDBCK. 72 bytes, the size the vendor layer uses. */ +static ALIGN16 byte nuvotonGcmFb[72]; +#endif + +/* DMA targets for the recomputed tag: the GHASH result, and the counter block + * that encrypts it. */ +static ALIGN16 byte nuvotonGcmGhash[WC_AES_BLOCK_SIZE]; +static ALIGN16 byte nuvotonGcmTag[WC_AES_BLOCK_SIZE]; + +/* Round up to the next whole block. */ +static word32 nuvoton_gcm_align(word32 sz) +{ + return (sz + (WC_AES_BLOCK_SIZE - 1)) & ~(word32)(WC_AES_BLOCK_SIZE - 1); +} + +/* One AES round for GCM and CCM. Caller holds the mutex and has set the key + * and the counts. */ +static int nuvoton_gcm_run_dma(uint32_t ctl, const byte* src, byte* dst, + word32 sz, uint32_t dmaMode) +{ + word32 timeout; + + CRPT->AES_CTL = ctl; + AES_SetDMATransfer(CRPT, 0, (uint32_t)(uintptr_t)src, + (uint32_t)(uintptr_t)dst, sz); + + CRPT->INTSTS = CRPT_INTSTS_AESIF_Msk | CRPT_INTSTS_AESEIF_Msk; + AES_Start(CRPT, 0, dmaMode); + + timeout = WOLFSSL_NUVOTON_HW_TIMEOUT; + while ((CRPT->INTSTS & + (CRPT_INTSTS_AESIF_Msk | CRPT_INTSTS_AESEIF_Msk)) == 0) { + if (timeout-- == 0) { + WOLFSSL_MSG("Nuvoton: AES-GCM timeout"); + return WC_TIMEOUT_E; + } + } + + if ((CRPT->INTSTS & CRPT_INTSTS_AESEIF_Msk) != 0) { + WOLFSSL_MSG("Nuvoton: AES-GCM error interrupt"); + CRPT->INTSTS = CRPT_INTSTS_AESIF_Msk | CRPT_INTSTS_AESEIF_Msk; + return WC_HW_E; + } + + CRPT->INTSTS = CRPT_INTSTS_AESIF_Msk | CRPT_INTSTS_AESEIF_Msk; + + return 0; +} + +/* The common case: the whole operation in one round. */ +static int nuvoton_gcm_run(uint32_t ctl, const byte* src, byte* dst, word32 sz) +{ + return nuvoton_gcm_run_dma(ctl, src, dst, sz, CRYPTO_DMA_ONE_SHOT); +} + +#endif /* WOLFSSL_NUVOTON_AESGCM || WOLFSSL_NUVOTON_AESCCM */ + +#ifdef WOLFSSL_NUVOTON_AESGCM + +/* Packed size: IV section, then AAD, then payload, each padded to a block. A + * 96 bit IV takes one block with a trailing 1 (the J0 rule); any other length + * is padded and followed by a block holding its bit count. */ +static word32 nuvoton_gcm_packed_sz(word32 ivSz, word32 aadSz, word32 sz) +{ + word32 packed; + + if (ivSz == GCM_NONCE_MID_SZ) { + packed = WC_AES_BLOCK_SIZE; + } + else { + packed = nuvoton_gcm_align(ivSz) + WC_AES_BLOCK_SIZE; + } + + return packed + nuvoton_gcm_align(aadSz) + nuvoton_gcm_align(sz); +} + +/* Lay the three sections out for the engine. */ +static void nuvoton_gcm_pack(const wc_NuvotonAesReq* req, byte* buf) +{ + word32 off = 0; + word64 bits; + word32 i; + + XMEMCPY(buf, req->iv, req->ivSz); + + if (req->ivSz == GCM_NONCE_MID_SZ) { + XMEMSET(buf + req->ivSz, 0, WC_AES_BLOCK_SIZE - req->ivSz); + buf[WC_AES_BLOCK_SIZE - 1] = 1; + off = WC_AES_BLOCK_SIZE; + } + else { + off = nuvoton_gcm_align(req->ivSz); + XMEMSET(buf + req->ivSz, 0, off - req->ivSz); + + /* 64 bits of zero, then the IV length in bits, big endian. */ + XMEMSET(buf + off, 0, WC_AES_BLOCK_SIZE); + bits = (word64)req->ivSz * 8; + for (i = 0; i < 8; i++) { + buf[off + WC_AES_BLOCK_SIZE - 1 - i] = (byte)(bits >> (i * 8)); + } + off += WC_AES_BLOCK_SIZE; + } + + if (req->aadSz > 0) { + XMEMCPY(buf + off, req->aad, req->aadSz); + XMEMSET(buf + off + req->aadSz, + 0, nuvoton_gcm_align(req->aadSz) - req->aadSz); + off += nuvoton_gcm_align(req->aadSz); + } + + if (req->sz > 0) { + XMEMCPY(buf + off, req->in, req->sz); + XMEMSET(buf + off + req->sz, 0, nuvoton_gcm_align(req->sz) - req->sz); + } +} + +/* Write a 64 bit big-endian bit count. */ +static void nuvoton_gcm_bitlen(byte* p, word32 bytes) +{ + word64 bits = (word64)bytes * 8; + word32 i; + + for (i = 0; i < 8; i++) { + p[7 - i] = (byte)(bits >> (i * 8)); + } +} + +/* T = CTR(J0, GHASH(align(A) || align(C) || bitlen(A) || bitlen(C))), on the + * engine in GHASH and CTR mode with the key already loaded. Used for the two + * payload lengths whose tag the engine gets wrong. The vendor layer does this + * for encrypt only, leaving its decrypt checking a tag it knows to be wrong. + * Caller holds the mutex. */ +static int nuvoton_gcm_tag(wc_NuvotonAesReq* req, const byte* cipher, + uint32_t keySize) +{ + uint32_t ctl; + uint32_t j0[4]; + word32 aadSection; + word32 cSection; + word32 len; + word32 i; + int ret; + + aadSection = nuvoton_gcm_align(req->aadSz); + cSection = nuvoton_gcm_align(req->sz); + len = aadSection + cSection + WC_AES_BLOCK_SIZE; + + if (len > (word32)sizeof(nuvotonGcmIn)) { + return BAD_LENGTH_E; + } + + /* GHASH input: the AAD and the ciphertext, each padded to a block, then + * one block holding both bit counts. */ + XMEMSET(nuvotonGcmIn, 0, len); + if (req->aadSz > 0) { + XMEMCPY(nuvotonGcmIn, req->aad, req->aadSz); + } + if (req->sz > 0) { + XMEMCPY(nuvotonGcmIn + aadSection, cipher, req->sz); + } + nuvoton_gcm_bitlen(nuvotonGcmIn + aadSection + cSection, req->aadSz); + nuvoton_gcm_bitlen(nuvotonGcmIn + aadSection + cSection + 8, req->sz); + + ctl = CRPT_AES_CTL_ENCRPT_Msk | + (keySize << CRPT_AES_CTL_KEYSZ_Pos) | + (AES_IN_OUT_SWAP << CRPT_AES_CTL_OUTSWAP_Pos) | + CRPT_AES_CTL_DMAEN_Msk; + + ret = nuvoton_gcm_run(ctl | (AES_MODE_GHASH << CRPT_AES_CTL_OPMODE_Pos), + nuvotonGcmIn, nuvotonGcmGhash, len); + if (ret != 0) { + return ret; + } + + /* J0: a 96 bit nonce is used directly with a counter of one, anything else + * is itself a GHASH. */ + if (req->ivSz == GCM_NONCE_MID_SZ) { + for (i = 0; i < 3; i++) { + j0[i] = (((uint32_t)req->iv[i * 4 ]) << 24) | + (((uint32_t)req->iv[i * 4 + 1]) << 16) | + (((uint32_t)req->iv[i * 4 + 2]) << 8) | + ((uint32_t)req->iv[i * 4 + 3]); + } + j0[3] = 1; + } + else { + len = nuvoton_gcm_align(req->ivSz) + WC_AES_BLOCK_SIZE; + if (len > (word32)sizeof(nuvotonGcmIn)) { + return BAD_LENGTH_E; + } + + XMEMSET(nuvotonGcmIn, 0, len); + XMEMCPY(nuvotonGcmIn, req->iv, req->ivSz); + nuvoton_gcm_bitlen(nuvotonGcmIn + len - 8, req->ivSz); + + ret = nuvoton_gcm_run( + ctl | (AES_MODE_GHASH << CRPT_AES_CTL_OPMODE_Pos), + nuvotonGcmIn, nuvotonGcmTag, len); + if (ret != 0) { + return ret; + } + + for (i = 0; i < 4; i++) { + j0[i] = (((uint32_t)nuvotonGcmTag[i * 4 ]) << 24) | + (((uint32_t)nuvotonGcmTag[i * 4 + 1]) << 16) | + (((uint32_t)nuvotonGcmTag[i * 4 + 2]) << 8) | + ((uint32_t)nuvotonGcmTag[i * 4 + 3]); + } + } + + AES_SetInitVect(CRPT, 0, j0); + + ret = nuvoton_gcm_run(ctl | (AES_MODE_CTR << CRPT_AES_CTL_OPMODE_Pos), + nuvotonGcmGhash, nuvotonGcmTag, WC_AES_BLOCK_SIZE); + if (ret == 0) { + XMEMCPY(req->tag, nuvotonGcmTag, req->tagSz); + } + + return ret; +} + +/* GCM too large for one round: the IV and AAD first, then payload chunks, with + * the feedback state swapped through AES_FBADDR. The tag follows the last + * chunk's padded output. Caller holds the mutex and has set the key. */ +static int nuvoton_gcm_cascade(wc_NuvotonAesReq* req, uint32_t ctl) +{ + word32 chunkMax; + word32 done; + word32 chunk; + word32 chunkAligned = 0; + word32 headSz; + int ret; + + /* Round one carries the IV and AAD, so the chunk buffer has to hold them + * as well as a payload chunk. */ + headSz = nuvoton_gcm_packed_sz(req->ivSz, req->aadSz, 0); + if (headSz + WC_AES_BLOCK_SIZE > (word32)sizeof(nuvotonGcmIn)) { + return BAD_LENGTH_E; + } + + chunkMax = (word32)sizeof(nuvotonGcmIn); + if (chunkMax > (word32)sizeof(nuvotonGcmOut) - WC_AES_BLOCK_SIZE) { + chunkMax = (word32)sizeof(nuvotonGcmOut) - WC_AES_BLOCK_SIZE; + } + chunkMax &= ~(word32)(WC_AES_BLOCK_SIZE - 1); + + XMEMSET(nuvotonGcmFb, 0, sizeof(nuvotonGcmFb)); + CRPT->AES_FBADDR = (uint32_t)(uintptr_t)nuvotonGcmFb; + + /* IV and AAD first, feedback state out. */ + { + wc_NuvotonAesReq head = *req; + + head.sz = 0; + nuvoton_gcm_pack(&head, nuvotonGcmIn); + } + + ret = nuvoton_gcm_run_dma(ctl | CRPT_AES_CTL_FBOUT_Msk, nuvotonGcmIn, + nuvotonGcmOut, headSz, CRYPTO_DMA_FIRST); + + for (done = 0; ret == 0 && done < req->sz; done += chunk) { + chunk = req->sz - done; + if (chunk > chunkMax) { + chunk = chunkMax; + } + + XMEMCPY(nuvotonGcmIn, req->in + done, chunk); + chunkAligned = nuvoton_gcm_align(chunk); + if (chunkAligned > chunk) { + XMEMSET(nuvotonGcmIn + chunk, 0, chunkAligned - chunk); + } + + ret = nuvoton_gcm_run_dma( + ctl | CRPT_AES_CTL_FBIN_Msk | CRPT_AES_CTL_FBOUT_Msk, + nuvotonGcmIn, nuvotonGcmOut, chunkAligned, + ((done + chunk) >= req->sz) ? CRYPTO_DMA_LAST : + CRYPTO_DMA_CONTINUE); + if (ret == 0) { + XMEMCPY(req->out + done, nuvotonGcmOut, chunk); + } + } + + if (ret == 0) { + /* The tag lands after the last chunk's padded output. */ + XMEMCPY(req->tag, nuvotonGcmOut + chunkAligned, req->tagSz); + } + + return ret; +} + +/* One-shot AES-GCM. Encrypt and decrypt run the identical packet through the + * engine, differing only in CRPT_AES_CTL_ENCRPT_Msk. The tag follows the + * payload either way, and on decrypt the caller compares it. */ +static int nuvoton_aes_gcm(wc_NuvotonAesReq* req) +{ + uint32_t keyWords[8]; + uint32_t keySize = 0; + uint32_t ctl; + word32 packedSz; + word32 outSz; + word32 i; + int oneShot; + int ret; + + if (req->iv == NULL || req->ivSz == 0) { + return BAD_FUNC_ARG; + } + if (req->tag == NULL || req->tagSz == 0 || + req->tagSz > WC_AES_BLOCK_SIZE) { + return BAD_FUNC_ARG; + } + if ((req->aadSz > 0 && req->aad == NULL) || + (req->sz > 0 && (req->in == NULL || req->out == NULL))) { + return BAD_FUNC_ARG; + } + + ret = nuvoton_aes_keysize(req->keySz, &keySize); + if (ret != 0) { + return ret; + } + + packedSz = nuvoton_gcm_packed_sz(req->ivSz, req->aadSz, req->sz); + outSz = nuvoton_gcm_align(req->sz) + WC_AES_BLOCK_SIZE; + + /* Everything that fits goes in one round; anything larger goes through the + * cascade, which has no size limit of its own. */ + oneShot = (packedSz <= (word32)sizeof(nuvotonGcmIn) && + outSz <= (word32)sizeof(nuvotonGcmOut)); + + XMEMSET(keyWords, 0, sizeof(keyWords)); + if (req->key != NULL) { + for (i = 0; i < req->keySz; i += 4) { + keyWords[i / 4] = nuvoton_get32be(req->key + i); + } + } + + /* No KINSWAP, unlike every other mode: the key words above are big endian + * and go in as they are. The wrong pairing gives a plausible ciphertext + * and a wrong tag. */ + ctl = ((uint32_t)req->encrypt << CRPT_AES_CTL_ENCRPT_Pos) | + (AES_MODE_GCM << CRPT_AES_CTL_OPMODE_Pos) | + (keySize << CRPT_AES_CTL_KEYSZ_Pos) | + (AES_IN_OUT_SWAP << CRPT_AES_CTL_OUTSWAP_Pos) | + CRPT_AES_CTL_DMAEN_Msk; + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + ForceZero(keyWords, sizeof(keyWords)); + return ret; + } + + if (oneShot) { + nuvoton_gcm_pack(req, nuvotonGcmIn); + XMEMSET(nuvotonGcmOut, 0, outSz); + } + + /* GCM state survives a mode change, so reset first as the BSP does. The + * reset also clears the ECC interrupt enables set at init, so re-arm + * them or every later ECC call spins out its timeout. */ + SYS_UnlockReg(); + SYS_ResetModule(CRPT_RST); + ECC_ENABLE_INT(CRPT); + SYS_LockReg(); + + if (req->keySlot == WC_NUVOTON_NO_SLOT) { + AES_SetKey(CRPT, 0, keyWords, keySize); + } + else { +#ifdef WOLFSSL_NUVOTON_KS + KS_MEM_Type mem; + + ret = nuvoton_ks_mem(req->keyMem, &mem); + if (ret == 0) { + AES_SetKey_KS(CRPT, mem, (int32_t)req->keySlot); + } +#else + ret = BAD_FUNC_ARG; +#endif + } + + if (ret == 0) { + /* Byte counts, not block counts, and the unpadded ones. */ + CRPT->AES_GCM_IVCNT[0] = req->ivSz; + CRPT->AES_GCM_IVCNT[1] = 0; + CRPT->AES_GCM_ACNT[0] = req->aadSz; + CRPT->AES_GCM_ACNT[1] = 0; + CRPT->AES_GCM_PCNT[0] = req->sz; + CRPT->AES_GCM_PCNT[1] = 0; + + if (oneShot) { + ret = nuvoton_gcm_run(ctl, nuvotonGcmIn, nuvotonGcmOut, packedSz); + } + else { + ret = nuvoton_gcm_cascade(req, ctl); + } + } + + if (ret == 0) { + if (oneShot) { + if (req->sz > 0) { + XMEMCPY(req->out, nuvotonGcmOut, req->sz); + } + /* The tag follows the padded payload. */ + XMEMCPY(req->tag, nuvotonGcmOut + nuvoton_gcm_align(req->sz), + req->tagSz); + } + + /* Except for the two payload lengths the engine gets wrong, where the + * tag is recomputed from the ciphertext - which on encrypt is what was + * just produced, and on decrypt is the input. */ + if ((req->sz % WC_AES_BLOCK_SIZE) == 1 || + (req->sz % WC_AES_BLOCK_SIZE) == 15) { + ret = nuvoton_gcm_tag(req, + req->encrypt ? req->out : req->in, keySize); + } + } + + CRPT->AES_CTL = CRPT_AES_CTL_STOP_Msk; + + ForceZero(keyWords, sizeof(keyWords)); + ForceZero(nuvotonGcmIn, sizeof(nuvotonGcmIn)); + ForceZero(nuvotonGcmOut, sizeof(nuvotonGcmOut)); + ForceZero(nuvotonGcmGhash, sizeof(nuvotonGcmGhash)); + ForceZero(nuvotonGcmTag, sizeof(nuvotonGcmTag)); +#ifdef WOLFSSL_NUVOTON_AESGCM + ForceZero(nuvotonGcmFb, sizeof(nuvotonGcmFb)); +#endif + + wolfSSL_CryptHwMutexUnLock(); + + return ret; +} + +#endif /* WOLFSSL_NUVOTON_AESGCM */ + + +#ifdef WOLFSSL_NUVOTON_AESCCM + +/* CCM shares the GCM staging buffers: both are one packed packet in, payload + * and tag out, and the hardware mutex serialises them. */ + +/* CCM B0: flags | nonce | payload length big endian in the trailing q bytes, + * flags carrying AAD presence, tag length and q. SP 800-38C A.2. */ +static void nuvoton_ccm_b0(const wc_NuvotonAesReq* req, byte q, byte* b0) +{ + word32 len = req->sz; + byte i; + + XMEMSET(b0, 0, WC_AES_BLOCK_SIZE); + + b0[0] = (byte)(((req->aadSz > 0) ? 0x40 : 0x00) | + ((((byte)req->tagSz - 2) / 2) << 3) | + (byte)(q - 1)); + XMEMCPY(b0 + 1, req->iv, req->ivSz); + + for (i = 0; i < q; i++) { + b0[WC_AES_BLOCK_SIZE - 1 - i] = (byte)len; + len >>= 8; + } +} + +/* One-shot AES-CCM. Key words go in little endian with KINSWAP set, as the + * block modes load them - not the GCM convention. */ +static int nuvoton_aes_ccm(wc_NuvotonAesReq* req) +{ + uint32_t keyWords[8]; + uint32_t ctr[4]; + uint32_t keySize = 0; + uint32_t ctl; + byte ctr0[WC_AES_BLOCK_SIZE]; + byte q; + word32 aadSection; + word32 pSection; + word32 packedSz; + word32 i; + int ret; + + if (req->iv == NULL || req->ivSz < 7 || req->ivSz > 13) { + return BAD_FUNC_ARG; + } + if (req->tag == NULL || req->tagSz < 4 || req->tagSz > WC_AES_BLOCK_SIZE || + (req->tagSz % 2) != 0) { + return BAD_FUNC_ARG; + } + if (req->aadSz >= 0xFF00) { + /* The AAD length is encoded in the two bytes ahead of it here, which + * is the short form only. */ + return BAD_LENGTH_E; + } + if ((req->aadSz > 0 && req->aad == NULL) || + (req->sz > 0 && (req->in == NULL || req->out == NULL))) { + return BAD_FUNC_ARG; + } + + ret = nuvoton_aes_keysize(req->keySz, &keySize); + if (ret != 0) { + return ret; + } + + q = (byte)(15 - req->ivSz); + + /* B0, then the AAD with its two length bytes, then the payload. With no + * AAD there is no AAD block at all - B0's flags say so and the MAC covers + * B0 and the payload only. The vendor layer emits one regardless, which + * authenticates a block the standard does not have. */ + aadSection = (req->aadSz > 0) ? nuvoton_gcm_align(req->aadSz + 2) : 0; + pSection = nuvoton_gcm_align(req->sz); + packedSz = WC_AES_BLOCK_SIZE + aadSection + pSection; + + if (packedSz > (word32)sizeof(nuvotonGcmIn) || + (pSection + WC_AES_BLOCK_SIZE) > (word32)sizeof(nuvotonGcmOut)) { + return BAD_LENGTH_E; + } + + XMEMSET(keyWords, 0, sizeof(keyWords)); + if (req->key != NULL) { + for (i = 0; i < req->keySz; i += 4) { + keyWords[i / 4] = nuvoton_get32(req->key + i); + } + } + + /* Ctr0, the counter the tag is masked with: starts at zero, not at the + * one the payload blocks begin from. */ + XMEMSET(ctr0, 0, sizeof(ctr0)); + ctr0[0] = (byte)(q - 1); + XMEMCPY(ctr0 + 1, req->iv, req->ivSz); + for (i = 0; i < WC_AES_BLOCK_SIZE; i += 4) { + ctr[i / 4] = nuvoton_get32(ctr0 + i); + } + + ctl = ((uint32_t)req->encrypt << CRPT_AES_CTL_ENCRPT_Pos) | + (AES_MODE_CCM << CRPT_AES_CTL_OPMODE_Pos) | + (keySize << CRPT_AES_CTL_KEYSZ_Pos) | + (AES_IN_OUT_SWAP << CRPT_AES_CTL_OUTSWAP_Pos) | + CRPT_AES_CTL_KINSWAP_Msk | + CRPT_AES_CTL_DMAEN_Msk; + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + ForceZero(keyWords, sizeof(keyWords)); + return ret; + } + + XMEMSET(nuvotonGcmIn, 0, packedSz); + nuvoton_ccm_b0(req, q, nuvotonGcmIn); + + if (req->aadSz > 0) { + /* The AAD length, big endian, ahead of the AAD itself. */ + nuvotonGcmIn[WC_AES_BLOCK_SIZE] = (byte)(req->aadSz >> 8); + nuvotonGcmIn[WC_AES_BLOCK_SIZE + 1] = (byte)(req->aadSz); + XMEMCPY(nuvotonGcmIn + WC_AES_BLOCK_SIZE + 2, req->aad, req->aadSz); + } + if (req->sz > 0) { + XMEMCPY(nuvotonGcmIn + WC_AES_BLOCK_SIZE + aadSection, req->in, + req->sz); + } + XMEMSET(nuvotonGcmOut, 0, pSection + WC_AES_BLOCK_SIZE); + + SYS_UnlockReg(); + SYS_ResetModule(CRPT_RST); + ECC_ENABLE_INT(CRPT); + SYS_LockReg(); + + if (req->keySlot == WC_NUVOTON_NO_SLOT) { + AES_SetKey(CRPT, 0, keyWords, keySize); + } + else { +#ifdef WOLFSSL_NUVOTON_KS + KS_MEM_Type mem; + + ret = nuvoton_ks_mem(req->keyMem, &mem); + if (ret == 0) { + AES_SetKey_KS(CRPT, mem, (int32_t)req->keySlot); + } +#else + ret = BAD_FUNC_ARG; +#endif + } + + if (ret == 0) { + CRPT->AES_CTL = ctl; + AES_SetInitVect(CRPT, 0, ctr); + + /* B0 and the AAD blocks are authenticated-only data to the engine. */ + CRPT->AES_GCM_ACNT[0] = WC_AES_BLOCK_SIZE + aadSection; + CRPT->AES_GCM_ACNT[1] = 0; + CRPT->AES_GCM_PCNT[0] = req->sz; + CRPT->AES_GCM_PCNT[1] = 0; + + ret = nuvoton_gcm_run(ctl, nuvotonGcmIn, nuvotonGcmOut, packedSz); + } + + if (ret == 0) { + if (req->sz > 0) { + XMEMCPY(req->out, nuvotonGcmOut, req->sz); + } + XMEMCPY(req->tag, nuvotonGcmOut + pSection, req->tagSz); + } + + CRPT->AES_CTL = CRPT_AES_CTL_STOP_Msk; + + ForceZero(keyWords, sizeof(keyWords)); + ForceZero(ctr, sizeof(ctr)); + ForceZero(nuvotonGcmIn, sizeof(nuvotonGcmIn)); + ForceZero(nuvotonGcmOut, sizeof(nuvotonGcmOut)); + + wolfSSL_CryptHwMutexUnLock(); + + return ret; +} + +#endif /* WOLFSSL_NUVOTON_AESCCM */ + +int wc_nuvoton_hw_aes(wc_NuvotonAesReq* req) +{ + int ret; + uint32_t opMode = 0; + uint32_t keySize = 0; + uint32_t ctl; + uint32_t iv[4]; + uint32_t keyWords[8]; + byte nextIv[WC_AES_BLOCK_SIZE]; + word32 done; + word32 chunk; + word32 i; + + if (req == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_NUVOTON_AESGCM + /* GCM is a packed packet and a tag, not a run of whole blocks, and its + * payload may be empty (GMAC). It gets its own path. */ + if (req->mode == WC_NUVOTON_AES_GCM) { + return nuvoton_aes_gcm(req); + } +#endif +#ifdef WOLFSSL_NUVOTON_AESCCM + if (req->mode == WC_NUVOTON_AES_CCM) { + return nuvoton_aes_ccm(req); + } +#endif + + if (req->in == NULL || req->out == NULL) { + return BAD_FUNC_ARG; + } + if (req->sz == 0 || (req->sz % WC_AES_BLOCK_SIZE) != 0) { + return BAD_LENGTH_E; + } + if (req->keySlot == WC_NUVOTON_NO_SLOT && req->key == NULL) { + return BAD_FUNC_ARG; + } + + ret = nuvoton_aes_mode(req->mode, &opMode); + if (ret != 0) { + return ret; + } + ret = nuvoton_aes_keysize(req->keySz, &keySize); + if (ret != 0) { + return ret; + } + if (req->mode != WC_NUVOTON_AES_ECB && req->iv == NULL) { + return BAD_FUNC_ARG; + } + + XMEMSET(keyWords, 0, sizeof(keyWords)); + if (req->key != NULL) { + for (done = 0; done < req->keySz; done += 4) { + keyWords[done / 4] = nuvoton_get32(req->key + done); + } + } + + XMEMSET(iv, 0, sizeof(iv)); + if (req->iv != NULL) { + for (done = 0; done < WC_AES_BLOCK_SIZE; done += 4) { + iv[done / 4] = nuvoton_get32(req->iv + done); + } + } + + /* KINSWAP goes with the key words built above; INSWAP and OUTSWAP say the + * message is little-endian in memory. */ + ctl = ((uint32_t)req->encrypt << CRPT_AES_CTL_ENCRPT_Pos) | + (opMode << CRPT_AES_CTL_OPMODE_Pos) | + (keySize << CRPT_AES_CTL_KEYSZ_Pos) | + (AES_IN_OUT_SWAP << CRPT_AES_CTL_OUTSWAP_Pos) | + CRPT_AES_CTL_KINSWAP_Msk | + CRPT_AES_CTL_DMAEN_Msk; + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + ForceZero(keyWords, sizeof(keyWords)); + return ret; + } + + /* Stop whatever the engine was doing, then load the key once: the CTL + * writes below do not disturb the key registers. */ + CRPT->AES_CTL = CRPT_AES_CTL_STOP_Msk; + + if (req->keySlot == WC_NUVOTON_NO_SLOT) { + AES_SetKey(CRPT, 0, keyWords, keySize); + } + else { +#ifdef WOLFSSL_NUVOTON_KS + KS_MEM_Type mem; + + ret = nuvoton_ks_mem(req->keyMem, &mem); + if (ret == 0) { + /* The engine reads the key straight out of the store, so it never + * appears in memory the CPU can see. */ + AES_SetKey_KS(CRPT, mem, (int32_t)req->keySlot); + } +#else + ret = BAD_FUNC_ARG; +#endif + } + + for (done = 0; ret == 0 && done < req->sz; done += chunk) { + chunk = req->sz - done; + if (chunk > WOLFSSL_NUVOTON_DMA_BUF_SZ) { + chunk = WOLFSSL_NUVOTON_DMA_BUF_SZ; + } + + /* CBC decrypt chains on the ciphertext, which is the input, and in + * and out are allowed to alias - so take the copy before the round + * overwrites it. */ + if (req->mode == WC_NUVOTON_AES_CBC && !req->encrypt) { + XMEMCPY(nextIv, req->in + done + chunk - WC_AES_BLOCK_SIZE, + WC_AES_BLOCK_SIZE); + } + + ret = nuvoton_aes_round(ctl, req->in + done, req->out + done, chunk, + iv); + if (ret != 0) { + break; + } + + /* Carry the chaining state to the next chunk, and to the next call. + * + * Derived from the data rather than read out of AES_FDBCK. FDBCK does + * hold the engine's feedback, but the TRM describes it in terms of DMA + * cascade mode and does not pin down its byte order relative to + * AES_IV, and guessing wrong is not visible in a single-shot test - + * the first block comes out right and only the next call is wrong. + * The ciphertext and the counter are unambiguous and already in hand. */ + switch (req->mode) { + case WC_NUVOTON_AES_CBC: + if (req->encrypt) { + XMEMCPY(nextIv, req->out + done + chunk - + WC_AES_BLOCK_SIZE, WC_AES_BLOCK_SIZE); + } + for (i = 0; i < WC_AES_BLOCK_SIZE; i += 4) { + iv[i / 4] = nuvoton_get32(nextIv + i); + } + break; + + case WC_NUVOTON_AES_CTR: + /* The counter is a big-endian integer over the whole block, + * advanced once per block the engine consumed. */ + for (i = 0; i < WC_AES_BLOCK_SIZE; i += 4) { + nuvoton_set32(nextIv + i, iv[i / 4]); + } + nuvoton_ctr_add(nextIv, chunk / WC_AES_BLOCK_SIZE); + for (i = 0; i < WC_AES_BLOCK_SIZE; i += 4) { + iv[i / 4] = nuvoton_get32(nextIv + i); + } + break; + + default: + /* ECB has no chaining state. */ + break; + } + } + + /* Hand the chained IV back so the next call continues the stream. */ + if (ret == 0 && req->iv != NULL) { + for (done = 0; done < WC_AES_BLOCK_SIZE; done += 4) { + nuvoton_set32(req->iv + done, iv[done / 4]); + } + } + + CRPT->AES_CTL = CRPT_AES_CTL_STOP_Msk; + + ForceZero(keyWords, sizeof(keyWords)); + ForceZero(nuvotonDmaIn, sizeof(nuvotonDmaIn)); + ForceZero(nuvotonDmaOut, sizeof(nuvotonDmaOut)); + + wolfSSL_CryptHwMutexUnLock(); + + return ret; +} + + +#endif /* WOLFSSL_NUVOTON_CIPHER */ + + +#if defined(WOLFSSL_NUVOTON_ECC) || defined(WOLFSSL_NUVOTON_RSA) + +/* The BSP public key drivers block on a flag that only the CRPT interrupt + * sets, so the application has to route that interrupt to ECC_DriverISR(). + * The wait loops carry their own timeout and give up rather than hang, but + * without the handler every public key operation times out. The port reports + * that as an error rather than declining to software, because a missing + * handler is a wiring mistake and a silent slow fallback would hide it. + * the wolfssl-examples project installs the handler; the port README says so too. */ + +#endif /* WOLFSSL_NUVOTON_ECC || WOLFSSL_NUVOTON_RSA */ + +#ifdef WOLFSSL_NUVOTON_ECC + +/* Map the port's curve selector onto the BSP E_ECC_CURVE value. */ +static int nuvoton_ecc_curve(int curveId, E_ECC_CURVE* curve) +{ + switch (curveId) { + case WC_NUVOTON_CURVE_P192: + *curve = CURVE_P_192; + break; + case WC_NUVOTON_CURVE_P224: + *curve = CURVE_P_224; + break; + case WC_NUVOTON_CURVE_P256: + *curve = CURVE_P_256; + break; + case WC_NUVOTON_CURVE_P384: + *curve = CURVE_P_384; + break; + case WC_NUVOTON_CURVE_P521: + *curve = CURVE_P_521; + break; + case WC_NUVOTON_CURVE_BP256: + *curve = CURVE_BP_256; + break; + case WC_NUVOTON_CURVE_BP384: + *curve = CURVE_BP_384; + break; + case WC_NUVOTON_CURVE_BP512: + *curve = CURVE_BP_512; + break; + default: + return BAD_FUNC_ARG; + } + + return 0; +} + +int wc_nuvoton_hw_ecc_sign(wc_NuvotonEccReq* req) +{ + int ret; + int32_t rc; + E_ECC_CURVE curve; + + if (req == NULL || req->msg == NULL || req->r == NULL || + req->s == NULL) { + return BAD_FUNC_ARG; + } + if (req->d == NULL || req->k == NULL) { + return BAD_FUNC_ARG; + } + + ret = nuvoton_ecc_curve(req->curveId, &curve); + if (ret != 0) { + return ret; + } + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + + rc = ECC_GenerateSignature(CRPT, curve, req->msg, req->d, req->k, + req->r, req->s); + + wolfSSL_CryptHwMutexUnLock(); + + if (rc != 0) { + WOLFSSL_MSG("Nuvoton: ECC sign failed"); + return WC_HW_E; + } + + return 0; +} + +int wc_nuvoton_hw_ecc_verify(wc_NuvotonEccReq* req) +{ + int ret; + int32_t rc; + E_ECC_CURVE curve; + + if (req == NULL || req->msg == NULL || req->qx == NULL || + req->qy == NULL || req->r == NULL || req->s == NULL) { + return BAD_FUNC_ARG; + } + + ret = nuvoton_ecc_curve(req->curveId, &curve); + if (ret != 0) { + return ret; + } + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + + rc = ECC_VerifySignature(CRPT, curve, req->msg, req->qx, req->qy, req->r, + req->s); + + wolfSSL_CryptHwMutexUnLock(); + + /* The driver folds "the signature does not check out" and "the engine + * failed" into the same negative return, so the caller cannot tell them + * apart and neither can this. Report it as a failed verify, which is the + * safe reading of the two. */ + if (rc != 0) { + return SIG_VERIFY_E; + } + + return 0; +} + +int wc_nuvoton_hw_ecc_shared(wc_NuvotonEccReq* req) +{ + int ret; + int32_t rc; + E_ECC_CURVE curve; + + if (req == NULL || req->qx == NULL || req->qy == NULL || + req->out == NULL || req->d == NULL) { + return BAD_FUNC_ARG; + } + if (req->keySlot != WC_NUVOTON_NO_SLOT) { + /* ECC_GenerateSecretZ_KS() leaves the shared secret in a Key Store + * slot and hands nothing back, so there is no way to satisfy a caller + * that asked for the bytes. */ + return BAD_FUNC_ARG; + } + + ret = nuvoton_ecc_curve(req->curveId, &curve); + if (ret != 0) { + return ret; + } + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + + rc = ECC_GenerateSecretZ(CRPT, curve, req->d, req->qx, req->qy, req->out); + + wolfSSL_CryptHwMutexUnLock(); + + if (rc != 0) { + WOLFSSL_MSG("Nuvoton: ECDH failed"); + return WC_HW_E; + } + + return 0; +} + +int wc_nuvoton_hw_ecc_pubkey(wc_NuvotonEccReq* req) +{ + int ret; + int32_t rc; + E_ECC_CURVE curve; + + if (req == NULL || req->qx == NULL || req->qy == NULL) { + return BAD_FUNC_ARG; + } + if (req->keySlot == WC_NUVOTON_NO_SLOT && req->d == NULL) { + return BAD_FUNC_ARG; + } + + ret = nuvoton_ecc_curve(req->curveId, &curve); + if (ret != 0) { + return ret; + } + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + + if (req->keySlot == WC_NUVOTON_NO_SLOT) { + rc = ECC_GeneratePublicKey(CRPT, curve, req->d, req->qx, req->qy); + } + else { + KS_MEM_Type mem; + + ret = nuvoton_ks_mem(req->keyMem, &mem); + if (ret != 0) { + wolfSSL_CryptHwMutexUnLock(); + return ret; + } + rc = ECC_GeneratePublicKey_KS(CRPT, curve, mem, + req->keySlot, req->qx, req->qy, 0); + } + + wolfSSL_CryptHwMutexUnLock(); + + if (rc != 0) { + WOLFSSL_MSG("Nuvoton: ECC public key failed"); + return WC_HW_E; + } + + return 0; +} + +#endif /* WOLFSSL_NUVOTON_ECC */ + +#ifdef WOLFSSL_NUVOTON_RSA + +/* Map a modulus size in bits onto the BSP RSA_KEY_SIZE_* value. */ +static int nuvoton_rsa_keysize(int keyBits, uint32_t* keySize) +{ + switch (keyBits) { + case 1024: + *keySize = RSA_KEY_SIZE_1024; + break; + case 2048: + *keySize = RSA_KEY_SIZE_2048; + break; + case 3072: + *keySize = RSA_KEY_SIZE_3072; + break; + case 4096: + *keySize = RSA_KEY_SIZE_4096; + break; + default: + return BAD_FUNC_ARG; + } + + return 0; +} + +int wc_nuvoton_hw_rsa(wc_NuvotonRsaReq* req) +{ + int ret; + int32_t rc; + uint32_t keySize = 0; + uint32_t opMode; + word32 timeout; + RSA_BUF_NORMAL_T* buf; + + if (req == NULL || req->in == NULL || req->n == NULL || req->e == NULL || + req->out == NULL) { + return BAD_FUNC_ARG; + } + if (req->keySlot != WC_NUVOTON_NO_SLOT) { + /* The Key Store form of RSA wants every CRT factor in its own slot, + * which nuvoton_key.c does not provision yet. */ + return BAD_FUNC_ARG; + } + + ret = nuvoton_rsa_keysize(req->keyBits, &keySize); + if (ret != 0) { + return ret; + } + + /* The engine works out of a caller supplied scratch area: two kilobytes + * for the plain mode, and more for CRT. Too big for the stack on a part + * with this much SRAM, so it comes off the heap for the one call. */ + buf = (RSA_BUF_NORMAL_T*)XMALLOC(sizeof(RSA_BUF_NORMAL_T), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (buf == NULL) { + return MEMORY_E; + } + + /* Zero it. RSA_SetKey() and RSA_SetDMATransfer() load the operands with + * Hex2Reg(), which writes only as many words as the hex string covers and + * leaves the rest of each field untouched - unlike the ECC paths, which + * clean_reg() first. So the high words of the modulus, the exponent and + * the base are whatever was in this allocation beforehand, and the engine + * computes against them. The symptom is not an error: the operation + * completes and returns a wrong result, which surfaces much later as + * RSA_BUFFER_E out of the OAEP unpad. */ + XMEMSET(buf, 0, sizeof(RSA_BUF_NORMAL_T)); + + /* Only the plain modular exponentiation is wired up. CRT needs p, q and + * the four precomputed values in the CRT working buffer, and a private + * operation is correct either way, just slower. */ + opMode = RSA_MODE_NORMAL; + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; + } + + rc = RSA_Open(CRPT, opMode, keySize, buf, sizeof(RSA_BUF_NORMAL_T), 0); + if (rc == 0) { + rc = RSA_SetKey(CRPT, req->e); + } + if (rc == 0) { + rc = RSA_SetDMATransfer(CRPT, req->in, req->n, NULL, NULL); + } + if (rc == 0) { + CRPT->INTSTS = CRPT_INTSTS_RSAIF_Msk | CRPT_INTSTS_RSAEIF_Msk; + RSA_Start(CRPT); + + timeout = WOLFSSL_NUVOTON_HW_TIMEOUT; + while ((CRPT->INTSTS & + (CRPT_INTSTS_RSAIF_Msk | CRPT_INTSTS_RSAEIF_Msk)) == 0) { + if (timeout-- == 0) { + WOLFSSL_MSG("Nuvoton: RSA timeout"); + rc = -1; + break; + } + } + + if (rc == 0 && (CRPT->INTSTS & CRPT_INTSTS_RSAEIF_Msk) != 0) { + WOLFSSL_MSG("Nuvoton: RSA error interrupt"); + rc = -1; + } + + CRPT->INTSTS = CRPT_INTSTS_RSAIF_Msk | CRPT_INTSTS_RSAEIF_Msk; + } + if (rc == 0) { + rc = RSA_Read(CRPT, req->out); + } + + wolfSSL_CryptHwMutexUnLock(); + + ForceZero(buf, sizeof(RSA_BUF_NORMAL_T)); + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + if (rc != 0) { + WOLFSSL_MSG("Nuvoton: RSA failed"); + return WC_HW_E; + } + + return 0; +} + +#endif /* WOLFSSL_NUVOTON_RSA */ + + +#ifdef WOLFSSL_NUVOTON_KS + +/* The store records the key size as an index, not a bit count. */ +static int nuvoton_ks_size_meta(word32 bits, uint32_t* meta) +{ + switch (bits) { + case 128: *meta = KS_META_128; break; + case 163: *meta = KS_META_163; break; + case 192: *meta = KS_META_192; break; + case 224: *meta = KS_META_224; break; + case 233: *meta = KS_META_233; break; + case 255: *meta = KS_META_255; break; + case 256: *meta = KS_META_256; break; + case 283: *meta = KS_META_283; break; + case 384: *meta = KS_META_384; break; + case 409: *meta = KS_META_409; break; + case 512: *meta = KS_META_512; break; + case 521: *meta = KS_META_521; break; + case 571: *meta = KS_META_571; break; + case 1024: *meta = KS_META_1024; break; + case 1536: *meta = KS_META_1536; break; + case 2048: *meta = KS_META_2048; break; + case 3072: *meta = KS_META_3072; break; + case 4096: *meta = KS_META_4096; break; + default: + return BAD_FUNC_ARG; + } + + return 0; +} + +/* Which engine is allowed to use the key. The store refuses any other. */ +static int nuvoton_ks_owner_meta(int owner, uint32_t* meta) +{ + switch (owner) { + case 0: *meta = KS_META_AES; break; + case 1: *meta = KS_META_HMAC; break; + case 2: *meta = KS_META_RSA_EXP; break; + case 3: *meta = KS_META_RSA_MID; break; + case 4: *meta = KS_META_ECC; break; + case 5: *meta = KS_META_CPU; break; + default: + return BAD_FUNC_ARG; + } + + return 0; +} + +int wc_nuvoton_hw_ks_write(wc_NuvotonKsWriteReq* req) +{ + int ret; + int32_t slot; + KS_MEM_Type mem; + uint32_t meta; + uint32_t sizeMeta = 0; + uint32_t ownerMeta = 0; + /* The largest key the store holds is 4096 bits. */ + uint32_t words[4096 / 32]; + + if (req == NULL || req->key == NULL || req->keySz == 0 || + req->keySz > sizeof(words)) { + return BAD_FUNC_ARG; + } + + ret = nuvoton_ks_mem(req->keyMem, &mem); + if (ret != 0) { + return ret; + } + ret = nuvoton_ks_size_meta(req->bits, &sizeMeta); + if (ret != 0) { + return ret; + } + ret = nuvoton_ks_owner_meta(req->owner, &ownerMeta); + if (ret != 0) { + return ret; + } + + /* Everything the port writes is a secure key. READABLE is the caller's + * choice and is what separates a key software can get back from one that + * only ever leaves the store into an engine. */ + meta = sizeMeta | ownerMeta | KS_META_SECURE; + if (req->readable) { + meta |= KS_META_READABLE; + } + + XMEMSET(words, 0, sizeof(words)); + XMEMCPY(words, req->key, req->keySz); + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + ForceZero(words, sizeof(words)); + return ret; + } + + slot = KS_Write(mem, meta, words); + + wolfSSL_CryptHwMutexUnLock(); + + ForceZero(words, sizeof(words)); + + if (slot < 0) { + WOLFSSL_MSG("Nuvoton: KS_Write failed"); + return WC_HW_E; + } + + return (int)slot; +} + +int wc_nuvoton_hw_ks_read(int keyMem, int keySlot, byte* out, word32 outSz) +{ + int ret; + KS_MEM_Type mem; + uint32_t words[4096 / 32]; + + if (out == NULL || outSz == 0 || outSz > sizeof(words)) { + return BAD_FUNC_ARG; + } + + ret = nuvoton_ks_mem(keyMem, &mem); + if (ret != 0) { + return ret; + } + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + + if (KS_Read(mem, (int32_t)keySlot, words, (outSz + 3) / 4) != 0) { + WOLFSSL_MSG("Nuvoton: KS_Read failed"); + ret = WC_HW_E; + } + else { + XMEMCPY(out, words, outSz); + } + + wolfSSL_CryptHwMutexUnLock(); + + ForceZero(words, sizeof(words)); + + return ret; +} + +int wc_nuvoton_hw_ks_erase(int keyMem, int keySlot) +{ + int ret; + KS_MEM_Type mem; + + ret = nuvoton_ks_mem(keyMem, &mem); + if (ret != 0) { + return ret; + } + + /* KS_EraseKey() writes KS_SRAM into the metadata itself, so it can only + * clear a volatile slot. A Flash or OTP key is retired with + * wc_nuvoton_hw_ks_revoke() instead; there is no per key erase for + * those. */ + if (mem != KS_SRAM) { + return BAD_FUNC_ARG; + } + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + + if (KS_EraseKey((int32_t)keySlot) != 0) { + WOLFSSL_MSG("Nuvoton: KS_EraseKey failed"); + ret = WC_HW_E; + } + + wolfSSL_CryptHwMutexUnLock(); + + return ret; +} + +int wc_nuvoton_hw_ks_revoke(int keyMem, int keySlot) +{ + int ret; + KS_MEM_Type mem; + + ret = nuvoton_ks_mem(keyMem, &mem); + if (ret != 0) { + return ret; + } + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + + if (KS_RevokeKey(mem, (int32_t)keySlot) != 0) { + WOLFSSL_MSG("Nuvoton: KS_RevokeKey failed"); + ret = WC_HW_E; + } + + wolfSSL_CryptHwMutexUnLock(); + + return ret; +} + +#endif /* WOLFSSL_NUVOTON_KS */ + +#endif /* WOLFSSL_NUVOTON_M2354 && WOLFSSL_NUVOTON_SECURE */ diff --git a/wolfcrypt/src/port/nuvoton/nuvoton_hw.h b/wolfcrypt/src/port/nuvoton/nuvoton_hw.h new file mode 100644 index 00000000000..ffb161cb1cd --- /dev/null +++ b/wolfcrypt/src/port/nuvoton/nuvoton_hw.h @@ -0,0 +1,264 @@ +/* nuvoton_hw.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 + */ + +/* The TrustZone boundary for the M2354 port. Every touch of the CRPT, the + * TRNG and the Key Store goes through a wc_nuvoton_hw_* call declared here, + * and nothing else in the port includes a BSP header, so this is the only + * seam that moves when wolfCrypt changes worlds: + * + * WOLFSSL_NUVOTON_SECURE nuvoton_hw.c implements these against the BSP. + * WOLFSSL_NUVOTON_NSC nuvoton_hw.c is not compiled; cmse_nonsecure_entry + * veneers supply the symbols. See + * embedded/nuvoton_m2354/secure/ in wolfssl-examples. + * + * So the declarations use only wolfSSL types, and anything past a few + * arguments goes by struct pointer: veneers cannot take stack-passed + * arguments, and it gives the secure side one range to validate. + * + * Port private, not installed. */ + +#ifndef WOLF_CRYPT_NUVOTON_HW_H +#define WOLF_CRYPT_NUVOTON_HW_H + +#include + +#ifdef WOLFSSL_NUVOTON_M2354 + +#include + +/* Secure side of a TrustZone split. nuvoton_hw.c is compiled into the secure + * image next to the veneers in embedded/nuvoton_m2354/secure/ in wolfssl-examples, which export these + * same names to the non-secure world. Rename the implementations so both can + * live in one image; each veneer validates its pointers and then calls the + * matching _s function. Nothing else changes, so the secure and non-secure + * builds share one source file. */ +#ifdef WOLFSSL_NUVOTON_NSC_IMPL + #define wc_nuvoton_hw_init wc_nuvoton_hw_init_s + #define wc_nuvoton_hw_cleanup wc_nuvoton_hw_cleanup_s + #define wc_nuvoton_hw_trng wc_nuvoton_hw_trng_s + #define wc_nuvoton_hw_sha wc_nuvoton_hw_sha_s + #define wc_nuvoton_hw_aes wc_nuvoton_hw_aes_s + #define wc_nuvoton_hw_ecc_sign wc_nuvoton_hw_ecc_sign_s + #define wc_nuvoton_hw_ecc_verify wc_nuvoton_hw_ecc_verify_s + #define wc_nuvoton_hw_ecc_shared wc_nuvoton_hw_ecc_shared_s + #define wc_nuvoton_hw_ecc_pubkey wc_nuvoton_hw_ecc_pubkey_s + #define wc_nuvoton_hw_rsa wc_nuvoton_hw_rsa_s + #define wc_nuvoton_hw_ks_write wc_nuvoton_hw_ks_write_s + #define wc_nuvoton_hw_ks_read wc_nuvoton_hw_ks_read_s + #define wc_nuvoton_hw_ks_erase wc_nuvoton_hw_ks_erase_s + #define wc_nuvoton_hw_ks_revoke wc_nuvoton_hw_ks_revoke_s +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +/* Hash algorithms the CRPT SHA engine implements. Mapped to the BSP + * SHA_MODE_* values inside nuvoton_hw.c. */ +enum wc_NuvotonShaMode { + WC_NUVOTON_SHA_1 = 0, + WC_NUVOTON_SHA_224 = 1, + WC_NUVOTON_SHA_256 = 2, + WC_NUVOTON_SHA_384 = 3, + WC_NUVOTON_SHA_512 = 4 +}; + +/* AES modes the CRPT engine implements and this port offers. */ +enum wc_NuvotonAesMode { + WC_NUVOTON_AES_ECB = 0, + WC_NUVOTON_AES_CBC = 1, + WC_NUVOTON_AES_CTR = 2, + WC_NUVOTON_AES_GCM = 3, + WC_NUVOTON_AES_CCM = 4 +}; + +/* Key Store memory types. Mirrors the BSP KS_MEM_Type values so the port never + * has to name that enum, but the mapping is made explicit in nuvoton_hw.c + * rather than assumed. */ +enum wc_NuvotonKsMem { + WC_NUVOTON_KS_SRAM = 0, + WC_NUVOTON_KS_FLASH = 1, + WC_NUVOTON_KS_OTP = 2 +}; + +/* Curves the CRPT ECC engine implements, limited to the ones this port maps + * from a wolfCrypt curve id. Mapped to E_ECC_CURVE inside nuvoton_hw.c. */ +enum wc_NuvotonCurve { + WC_NUVOTON_CURVE_NONE = 0, + WC_NUVOTON_CURVE_P192 = 1, + WC_NUVOTON_CURVE_P224 = 2, + WC_NUVOTON_CURVE_P256 = 3, + WC_NUVOTON_CURVE_P384 = 4, + WC_NUVOTON_CURVE_P521 = 5, + WC_NUVOTON_CURVE_BP256 = 6, + WC_NUVOTON_CURVE_BP384 = 7, + WC_NUVOTON_CURVE_BP512 = 8 +}; + +/* No Key Store slot: the request carries plain key material instead. */ +#define WC_NUVOTON_NO_SLOT (-1) + +/* One AES request. in and out may alias. sz is a whole number of blocks; the + * caller keeps any partial tail. iv is read and written back for the chaining + * modes and ignored for ECB. Either key/keySz or (keyMem, keySlot) supplies the + * key, never both. */ +typedef struct wc_NuvotonAesReq { + const byte* in; + byte* out; + const byte* key; /* NULL when keySlot is not WC_NUVOTON_NO_SLOT */ + byte* iv; /* 16 bytes, in and out, NULL for ECB */ + const byte* aad; /* GCM and CCM only */ + byte* tag; /* GCM and CCM only, out on encrypt, in on decrypt */ + word32 sz; + word32 keySz; /* 16, 24 or 32 */ + word32 aadSz; + word32 tagSz; + word32 ivSz; /* GCM only; CBC and CTR always use a full block */ + int mode; /* enum wc_NuvotonAesMode */ + int encrypt; /* 1 to encrypt, 0 to decrypt */ + int keyMem; /* enum wc_NuvotonKsMem, used when keySlot is set */ + int keySlot; /* WC_NUVOTON_NO_SLOT for plain key material */ +} wc_NuvotonAesReq; + +/* One ECC request. The CRPT driver speaks NUL terminated lowercase hex + * strings, not byte arrays, so that is what crosses this boundary; the + * conversion lives in nuvoton_cb_pk.c, which uses the converters wolfCrypt + * already has. Unused fields are NULL. */ +typedef struct wc_NuvotonEccReq { + char* msg; /* digest, hex */ + char* d; /* private key, hex */ + char* k; /* per-message random, hex, sign only */ + char* qx; /* public key X, hex */ + char* qy; /* public key Y, hex */ + char* r; /* signature R, hex, in or out */ + char* s; /* signature S, hex, in or out */ + char* out; /* shared secret (ECDH), hex */ + int curveId; /* enum wc_NuvotonCurve */ + int keyMem; /* enum wc_NuvotonKsMem, used when keySlot is set */ + int keySlot; /* WC_NUVOTON_NO_SLOT for plain key material */ +} wc_NuvotonEccReq; + +/* Feedback state the SHA engine swaps in and out of a caller-supplied buffer, + * 1728 bits per the TRM. Giving every hash context its own buffer is what + * lets hashes interleave: the engine reads this context's state before the + * block and writes it back after, so no cascade is "open" between calls. */ +#define WC_NUVOTON_SHA_FDBCK_WORDS 54 + +/* Largest SHA block this engine handles, SHA-384 and SHA-512. */ +#define WC_NUVOTON_SHA_BLOCK_MAX 128 + +/* One SHA request. A cmse_nonsecure_entry veneer cannot take arguments that + * spill onto the stack, so anything past four words is packed into a struct + * the same way the other engines do it. + * + * in must be a whole number of blocks unless last is set. fdbck must be word + * aligned and in SRAM, which an allocation from the port satisfies; the engine + * reaches it by DMA. digest is written only when last is set. */ +typedef struct wc_NuvotonShaReq { + word32* fdbck; /* WC_NUVOTON_SHA_FDBCK_WORDS words, in and out */ + const byte* in; + byte* digest; /* written when last is set, NULL otherwise */ + word32 inSz; + word32 digestSz; + int shaMode; /* enum wc_NuvotonShaMode */ + int first; /* 1 for the first chunk of the message */ + int last; /* 1 for the final chunk; the engine pads */ +} wc_NuvotonShaReq; + +/* One Key Store write. Same reason as above. */ +typedef struct wc_NuvotonKsWriteReq { + const byte* key; + word32 keySz; + word32 bits; /* key size the store records */ + int keyMem; /* enum wc_NuvotonKsMem */ + int owner; /* which engine may use it, see nuvoton_key.h */ + int readable; /* 1 to allow reading it back */ +} wc_NuvotonKsWriteReq; + +/* One RSA modular exponentiation. All values are NUL terminated hex strings, + * most significant digit first. p and q are set only for a CRT private + * operation. keyBits is 1024, 2048, 3072 or 4096. */ +typedef struct wc_NuvotonRsaReq { + char* in; /* base, hex */ + char* n; /* modulus, hex */ + char* e; /* exponent, hex; the private exponent for a decrypt */ + char* p; /* CRT factor, hex, or NULL */ + char* q; /* CRT factor, hex, or NULL */ + char* out; /* result, hex */ + word32 outSz; /* size of out in bytes, including the NUL */ + int keyBits; + int keyMem; /* enum wc_NuvotonKsMem, used when keySlot is set */ + int keySlot; /* WC_NUVOTON_NO_SLOT for plain key material */ +} wc_NuvotonRsaReq; + +/* Bring the CRPT, TRNG and Key Store blocks up: ungate their clocks, release + * them from reset and open the drivers. Idempotent, reference counted, and + * safe to call from more than one thread. Returns 0 or a wolfCrypt error. */ +WOLFSSL_LOCAL int wc_nuvoton_hw_init(void); + +/* Undo one wc_nuvoton_hw_init(). The hardware is only shut down when the last + * reference goes away. */ +WOLFSSL_LOCAL void wc_nuvoton_hw_cleanup(void); + +/* Read sz bytes from the TRNG. */ +WOLFSSL_LOCAL int wc_nuvoton_hw_trng(byte* out, word32 sz); + +/* Run one chunk of a SHA message, carrying the context's state through the + * feedback buffer. + * + * BAD_ALIGN_E means the engine cannot address the caller's buffer and the + * operation should fall back to software rather than be treated as failed. */ +WOLFSSL_LOCAL int wc_nuvoton_hw_sha(wc_NuvotonShaReq* req); + +/* Run one AES request. ECB, CBC and CTR go to the engine as a run of whole + * blocks; GCM and CCM go as one packed packet of IV, AAD and payload, and a + * GCM payload too large for the staging buffers runs through the DMA cascade. + * Which of those are built is set by WOLFSSL_NUVOTON_AESGCM and + * WOLFSSL_NUVOTON_AESCCM. */ +WOLFSSL_LOCAL int wc_nuvoton_hw_aes(wc_NuvotonAesReq* req); + +/* ECDSA sign, ECDSA verify, ECDH shared secret, and public key from private. + * Verify returns 0 when the signature is good and SIG_VERIFY_E when it is + * not; a hardware failure returns some other error. */ +WOLFSSL_LOCAL int wc_nuvoton_hw_ecc_sign(wc_NuvotonEccReq* req); +WOLFSSL_LOCAL int wc_nuvoton_hw_ecc_verify(wc_NuvotonEccReq* req); +WOLFSSL_LOCAL int wc_nuvoton_hw_ecc_shared(wc_NuvotonEccReq* req); +WOLFSSL_LOCAL int wc_nuvoton_hw_ecc_pubkey(wc_NuvotonEccReq* req); + +/* One RSA modular exponentiation. */ +WOLFSSL_LOCAL int wc_nuvoton_hw_rsa(wc_NuvotonRsaReq* req); + +/* Key Store. The metadata word the store wants is built on the BSP side from + * these, so no BSP macro has to cross the boundary. ks_write returns the slot + * index it allocated, or a negative wolfCrypt error. */ +WOLFSSL_LOCAL int wc_nuvoton_hw_ks_write(wc_NuvotonKsWriteReq* req); +WOLFSSL_LOCAL int wc_nuvoton_hw_ks_read(int keyMem, int keySlot, byte* out, + word32 outSz); +WOLFSSL_LOCAL int wc_nuvoton_hw_ks_erase(int keyMem, int keySlot); +WOLFSSL_LOCAL int wc_nuvoton_hw_ks_revoke(int keyMem, int keySlot); + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFSSL_NUVOTON_M2354 */ + +#endif /* WOLF_CRYPT_NUVOTON_HW_H */ diff --git a/wolfcrypt/src/port/nuvoton/nuvoton_key.c b/wolfcrypt/src/port/nuvoton/nuvoton_key.c new file mode 100644 index 00000000000..b3d810debd3 --- /dev/null +++ b/wolfcrypt/src/port/nuvoton/nuvoton_key.c @@ -0,0 +1,149 @@ +/* nuvoton_key.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 + */ + +/* Key Store keys on the M2354. See wolfssl/wolfcrypt/port/nuvoton/nuvoton_key.h + * for what the API is for; this is the thin layer between it and the hardware + * calls, plus the two functions that attach a stored key to a wolfCrypt + * object. */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_NUVOTON_M2354) && defined(WOLFSSL_NUVOTON_KS) + +#include +#include +#include + +#include "wolfcrypt/src/port/nuvoton/nuvoton_hw.h" + +/* ForceZero() when a stored-key handle replaces existing key material. */ +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +int wc_NuvotonKs_Write(wc_NuvotonKsKey* ksKey, int mem, int owner, + word32 bits, const byte* key, word32 keySz, int readable) +{ + wc_NuvotonKsWriteReq req; + int slot; + + if (ksKey == NULL || key == NULL || keySz == 0) { + return BAD_FUNC_ARG; + } + /* The store records the size separately from the material, so a mismatch + * would silently write a key of the wrong length. */ + if (bits == 0 || ((bits + 7) / 8) != keySz) { + return BAD_FUNC_ARG; + } + + XMEMSET(&req, 0, sizeof(req)); + req.key = key; + req.keySz = keySz; + req.bits = bits; + req.keyMem = mem; + req.owner = owner; + req.readable = readable; + + slot = wc_nuvoton_hw_ks_write(&req); + if (slot < 0) { + return slot; + } + + ksKey->slot = slot; + ksKey->mem = mem; + ksKey->owner = owner; + ksKey->bits = bits; + + return 0; +} + +int wc_NuvotonKs_Read(const wc_NuvotonKsKey* ksKey, byte* out, word32 outSz) +{ + if (ksKey == NULL || out == NULL || outSz == 0) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_ks_read(ksKey->mem, ksKey->slot, out, outSz); +} + +int wc_NuvotonKs_Erase(const wc_NuvotonKsKey* ksKey) +{ + if (ksKey == NULL) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_ks_erase(ksKey->mem, ksKey->slot); +} + +int wc_NuvotonKs_Revoke(const wc_NuvotonKsKey* ksKey) +{ + if (ksKey == NULL) { + return BAD_FUNC_ARG; + } + + return wc_nuvoton_hw_ks_revoke(ksKey->mem, ksKey->slot); +} + +#ifndef NO_AES +int wc_NuvotonKs_SetAesKey(Aes* aes, wc_NuvotonKsKey* ksKey) +{ + if (aes == NULL || ksKey == NULL) { + return BAD_FUNC_ARG; + } + if (ksKey->owner != WC_NUVOTON_KS_OWNER_AES) { + return BAD_FUNC_ARG; + } + if (ksKey->bits != 128 && ksKey->bits != 192 && ksKey->bits != 256) { + return BAD_FUNC_ARG; + } + + /* Any key this Aes already held has to go. Leaving the software schedule + * and devKey in place means a later decline to software silently encrypts + * with the previous key instead of failing, and keeps that key in memory + * for as long as the object lives. */ + ForceZero(aes->key, sizeof(aes->key)); +#ifdef WOLFSSL_AES_COUNTER + aes->left = 0; +#endif +#ifdef WC_AES_KEY_IS_SET + aes->keyInstalled = 0; +#endif + + /* wc_AesSetKey() is what normally fills keylen in, and it is not called + * for a stored key because there is no key material to give it. The + * cipher callback still needs the length to pick the engine key size. */ + aes->keylen = (int)(ksKey->bits / 8); + aes->rounds = (ksKey->bits / 32) + 6; + aes->devCtx = ksKey; + + return 0; +} +#endif /* !NO_AES */ + + +#endif /* WOLFSSL_NUVOTON_M2354 && WOLFSSL_NUVOTON_KS */ diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index fff02bad256..1859debefdb 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -6132,6 +6132,43 @@ int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) return 0; } +#elif defined(WOLFSSL_NUVOTON_TRNG) + /* Nuvoton NuMicro M2354 TRNG, reached through the port's hardware layer so + * that a TrustZone build gets it through the same veneer as everything + * else. See wolfcrypt/src/port/nuvoton/nuvoton_hw.c. + * + * This arm is needed even though the port is a crypto callback device: a + * WC_RNG created with INVALID_DEVID never reaches the callback, and this + * part has no /dev/random to fall back on. */ + #include "wolfcrypt/src/port/nuvoton/nuvoton_hw.h" + + int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) + { + int ret; + + (void)os; + + if (output == NULL || sz == 0) { + return BAD_FUNC_ARG; + } + + /* This is the path a WC_RNG built with INVALID_DEVID takes, so the + * crypto callback device may never have been registered and the CRPT + * clocks and the TRNG may still be down. Bring them up for the call + * and drop the reference afterwards; the init is reference counted, so + * an application that did register keeps its own. */ + ret = wc_nuvoton_hw_init(); + if (ret != 0) { + return ret; + } + + ret = wc_nuvoton_hw_trng(output, sz); + + wc_nuvoton_hw_cleanup(); + + return ret; + } + #elif defined(WOLFSSL_VA416X0_TRNG) /* Vorago VA416x0 hardware TRNG (an Arm CryptoCell-style entropy block). * Used to seed the SP800-90A Hash-DRBG (keep HAVE_HASHDRBG enabled); the diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 2b4479a7732..f4784df15be 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -97,6 +97,16 @@ that can be serialized and deserialized in a cross-platform way. #endif #include /* public interface */ +/* wc_DataToHexString(): byte array to lowercase hex string. Built for the + * custom ECC curve parameter strings, and reused by hardware ports whose + * driver takes key material as a hex string. Anything that needs it defines + * WOLFSSL_ASN_HEX_STRING; the custom curve code turns it on for itself. */ +#if defined(WOLFSSL_ASN_TEMPLATE) && defined(HAVE_ECC) && \ + defined(WOLFSSL_CUSTOM_CURVES) + #undef WOLFSSL_ASN_HEX_STRING + #define WOLFSSL_ASN_HEX_STRING +#endif + #if defined(NO_SHA) && defined(NO_SHA256) #define WC_SHA256_DIGEST_SIZE 32 #endif @@ -105,6 +115,14 @@ that can be serialized and deserialized in a cross-platform way. extern "C" { #endif +#ifdef WOLFSSL_ASN_HEX_STRING +/* Convert inSz bytes at input into a NUL terminated lowercase hex string. + * out needs room for inSz * 2 + 1 bytes. */ +WOLFSSL_LOCAL void wc_DataToHexString(const byte* input, word32 inSz, + char* out); +#endif + + #ifndef NO_ASN #ifndef EXTERNAL_SERIAL_SIZE diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index 777a9619377..e9f32c3a7ff 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -151,6 +151,12 @@ noinst_HEADERS+= \ wolfssl/wolfcrypt/libwolfssl_sources.h \ wolfssl/wolfcrypt/libwolfssl_sources_asm.h +# Installed unconditionally: settings.h includes nuvoton_settings.h whenever +# WOLFSSL_NUVOTON_M2354 is set, so an installed tree needs it to preprocess. +nobase_include_HEADERS+= wolfssl/wolfcrypt/port/nuvoton/nuvoton_settings.h +nobase_include_HEADERS+= wolfssl/wolfcrypt/port/nuvoton/nuvoton_cryptocb.h +nobase_include_HEADERS+= wolfssl/wolfcrypt/port/nuvoton/nuvoton_key.h + if BUILD_CRYPTOAUTHLIB nobase_include_HEADERS+= wolfssl/wolfcrypt/port/atmel/atmel.h diff --git a/wolfssl/wolfcrypt/port/nuvoton/nuvoton_cryptocb.h b/wolfssl/wolfcrypt/port/nuvoton/nuvoton_cryptocb.h new file mode 100644 index 00000000000..4789256383c --- /dev/null +++ b/wolfssl/wolfcrypt/port/nuvoton/nuvoton_cryptocb.h @@ -0,0 +1,77 @@ +/* nuvoton_cryptocb.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 + */ + +/* Crypto callback device for the Nuvoton NuMicro M2354. + * + * Register once after wolfCrypt_Init(), then pass WOLFSSL_NUVOTON_DEVID as the + * devId of a key or hash, or wolfSSL_CTX_SetDevId() for a whole TLS context: + * + * wolfCrypt_Init(); + * wc_NuvotonCryptoCb_RegisterDevice(WOLFSSL_NUVOTON_DEVID); + * + * Anything the accelerator cannot do is declined and runs in software. */ + +#ifndef WOLF_CRYPT_NUVOTON_CRYPTOCB_H +#define WOLF_CRYPT_NUVOTON_CRYPTOCB_H + +#include + +#if defined(WOLFSSL_NUVOTON_M2354) && defined(WOLF_CRYPTO_CB) + +#include +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/* Bring the hardware up and register the device. */ +WOLFSSL_API int wc_NuvotonCryptoCb_RegisterDevice(int devId); + +/* Unregister the device and release the hardware. */ +WOLFSSL_API void wc_NuvotonCryptoCb_UnRegisterDevice(int devId); + +/* The callback itself, public so it can be wrapped or chained. */ +WOLFSSL_API int wc_NuvotonCryptoDevCb(int devId, wc_CryptoInfo* info, + void* ctx); + +/* Per engine entry points. Each returns 0 when the hardware did the work, + * CRYPTOCB_UNAVAILABLE to fall back to software, or a wolfCrypt error. */ +#ifdef WOLFSSL_NUVOTON_RNG_OFFLOAD +WOLFSSL_LOCAL int wc_NuvotonCb_Rng(wc_CryptoInfo* info); +#endif +WOLFSSL_LOCAL int wc_NuvotonCb_Seed(wc_CryptoInfo* info); +WOLFSSL_LOCAL int wc_NuvotonCb_Hash(wc_CryptoInfo* info); +WOLFSSL_LOCAL int wc_NuvotonCb_HashCopy(wc_CryptoInfo* info); +WOLFSSL_LOCAL int wc_NuvotonCb_Cipher(wc_CryptoInfo* info); +WOLFSSL_LOCAL int wc_NuvotonCb_Pk(wc_CryptoInfo* info); +WOLFSSL_LOCAL int wc_NuvotonCb_Free(wc_CryptoInfo* info); + +/* Called by wc_NuvotonCb_Free(); declines so wolfCrypt still wipes it. */ +WOLFSSL_LOCAL int wc_NuvotonHashFree(wc_CryptoInfo* info); + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFSSL_NUVOTON_M2354 && WOLF_CRYPTO_CB */ + +#endif /* WOLF_CRYPT_NUVOTON_CRYPTOCB_H */ diff --git a/wolfssl/wolfcrypt/port/nuvoton/nuvoton_key.h b/wolfssl/wolfcrypt/port/nuvoton/nuvoton_key.h new file mode 100644 index 00000000000..b2c194425eb --- /dev/null +++ b/wolfssl/wolfcrypt/port/nuvoton/nuvoton_key.h @@ -0,0 +1,106 @@ +/* nuvoton_key.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 + */ + +/* Key Store keys on the Nuvoton NuMicro M2354. + * + * A key written to the store gets a slot; the application then uses the handle + * rather than the key. Attach an AES key with wc_NuvotonKs_SetAesKey() and the + * callback runs the operation through the AES_SetKey_KS driver entry point, so + * the key material never enters wolfCrypt memory. The handle rides in the + * object's devCtx. + * + * AES is the only owner that can be attached. The engine's ECC Key Store path + * returns signatures that do not verify, so it is not offered; see the port + * README. */ + +#ifndef WOLF_CRYPT_NUVOTON_KEY_H +#define WOLF_CRYPT_NUVOTON_KEY_H + +#include + +#if defined(WOLFSSL_NUVOTON_M2354) && defined(WOLFSSL_NUVOTON_KS) + +#include +#ifndef NO_AES + #include +#endif +#ifdef HAVE_ECC + #include +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +/* Named here rather than exposing the BSP KS_MEM_Type. */ +typedef enum { + WC_NUVOTON_KS_MEM_SRAM = 0, /* volatile, cleared by a reset */ + WC_NUVOTON_KS_MEM_FLASH = 1, /* non-volatile */ + WC_NUVOTON_KS_MEM_OTP = 2 /* one time programmable */ +} wc_NuvotonKsMemType; + +/* The store enforces this: an AES key cannot be fed to the ECC engine. */ +typedef enum { + WC_NUVOTON_KS_OWNER_AES = 0, + WC_NUVOTON_KS_OWNER_HMAC = 1, + WC_NUVOTON_KS_OWNER_RSA_EXP = 2, + WC_NUVOTON_KS_OWNER_RSA_MID = 3, + WC_NUVOTON_KS_OWNER_ECC = 4, + WC_NUVOTON_KS_OWNER_CPU = 5 +} wc_NuvotonKsOwner; + +/* Held in the devCtx of the Aes or ecc_key it belongs to. */ +typedef struct wc_NuvotonKsKey { + int slot; /* index the store gave it */ + int mem; /* wc_NuvotonKsMemType */ + int owner; /* wc_NuvotonKsOwner */ + word32 bits; /* key size in bits */ +} wc_NuvotonKsKey; + +/* Write key material and fill in ksKey with the handle. bits must be a size + * the store holds (128, 192, 224, 233, 255, 256, 283, 384, 409, 512, 521, 571, + * 1024, 1536, 2048, 3072, 4096). readable allows wc_NuvotonKs_Read() later; + * leave it 0 for a key that should never come out. OTP is permanent. */ +WOLFSSL_API int wc_NuvotonKs_Write(wc_NuvotonKsKey* ksKey, int mem, int owner, + word32 bits, const byte* key, word32 keySz, int readable); + +/* Only works for a slot written with readable set. */ +WOLFSSL_API int wc_NuvotonKs_Read(const wc_NuvotonKsKey* ksKey, byte* out, + word32 outSz); + +/* Clear a volatile slot. Flash and OTP have no per key erase; revoke. */ +WOLFSSL_API int wc_NuvotonKs_Erase(const wc_NuvotonKsKey* ksKey); + +/* Retire a key permanently. Cannot be undone. */ +WOLFSSL_API int wc_NuvotonKs_Revoke(const wc_NuvotonKsKey* ksKey); + +#ifndef NO_AES +/* The Aes must carry the port's devId, and ksKey must outlive it. */ +WOLFSSL_API int wc_NuvotonKs_SetAesKey(Aes* aes, wc_NuvotonKsKey* ksKey); +#endif + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFSSL_NUVOTON_M2354 && WOLFSSL_NUVOTON_KS */ + +#endif /* WOLF_CRYPT_NUVOTON_KEY_H */ diff --git a/wolfssl/wolfcrypt/port/nuvoton/nuvoton_settings.h b/wolfssl/wolfcrypt/port/nuvoton/nuvoton_settings.h new file mode 100644 index 00000000000..90fc5251448 --- /dev/null +++ b/wolfssl/wolfcrypt/port/nuvoton/nuvoton_settings.h @@ -0,0 +1,198 @@ +/* nuvoton_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 + */ + +/* Build settings for the Nuvoton NuMicro M2354 port. Macros only, no BSP + * headers, so settings.h can include it early. + * + * WOLFSSL_NUVOTON_M2354 turns the port on and offloads every engine. Name any + * of these instead and only those are offloaded: + * WOLFSSL_NUVOTON_TRNG - standalone TRNG (rng.h) + * WOLFSSL_NUVOTON_HASH - CRPT SHA-1/224/256/384/512 + * WOLFSSL_NUVOTON_CIPHER - CRPT AES (ECB/CBC/CTR) + * WOLFSSL_NUVOTON_ECC - CRPT ECC, including ECDH + * WOLFSSL_NUVOTON_RSA - CRPT RSA + * WOLFSSL_NUVOTON_KS - Key Store wrapped keys + * + * Exactly one TrustZone model, defaulting to the secure world: + * WOLFSSL_NUVOTON_SECURE - direct BSP calls + * WOLFSSL_NUVOTON_NSC - through cmse_nonsecure_entry veneers; + * nuvoton_hw.c is not compiled and the + * veneers satisfy wc_nuvoton_hw_* instead + * + * Also: WOLFSSL_NUVOTON_DEVID (default 820), WOLFSSL_NUVOTON_DMA_BUF_SZ, + * WOLFSSL_NUVOTON_HW_TIMEOUT, WOLFSSL_NUVOTON_RNG_OFFLOAD. + */ + +#ifndef WOLFSSL_NUVOTON_SETTINGS_H +#define WOLFSSL_NUVOTON_SETTINGS_H + +#ifdef WOLFSSL_NUVOTON_M2354 + +/* The port works through the wolfSSL crypto callback. */ +#ifndef WOLF_CRYPTO_CB + #define WOLF_CRYPTO_CB +#endif + +/* Hash contexts are copied and freed; keys carry a Key Store handle. */ +#ifndef WOLF_CRYPTO_CB_COPY + #define WOLF_CRYPTO_CB_COPY +#endif +#ifndef WOLF_CRYPTO_CB_FREE + #define WOLF_CRYPTO_CB_FREE +#endif + +/* Which world wolfCrypt is built for. Guessing wrong faults on the first CRPT + * access, so a conflict is an error. Silence means the secure world, where a + * non-TrustZone application also runs. */ +#if defined(WOLFSSL_NUVOTON_SECURE) && defined(WOLFSSL_NUVOTON_NSC) + #error "Name only one of WOLFSSL_NUVOTON_SECURE or WOLFSSL_NUVOTON_NSC" +#endif +#if !defined(WOLFSSL_NUVOTON_SECURE) && !defined(WOLFSSL_NUVOTON_NSC) + #define WOLFSSL_NUVOTON_SECURE +#endif + +/* No engine was named, so turn them all on. */ +#if !defined(WOLFSSL_NUVOTON_TRNG) && \ + !defined(WOLFSSL_NUVOTON_HASH) && \ + !defined(WOLFSSL_NUVOTON_CIPHER) && \ + !defined(WOLFSSL_NUVOTON_ECC) && \ + !defined(WOLFSSL_NUVOTON_RSA) && \ + !defined(WOLFSSL_NUVOTON_KS) + #define WOLFSSL_NUVOTON_TRNG + #define WOLFSSL_NUVOTON_HASH + #define WOLFSSL_NUVOTON_CIPHER + /* Leave RSA off in a build without RSA. */ + #ifndef NO_RSA + #define WOLFSSL_NUVOTON_RSA + #endif + /* HAVE_ECC is decided later, so set this now and let nuvoton_cb_pk.c + * check. */ + #define WOLFSSL_NUVOTON_ECC + #define WOLFSSL_NUVOTON_KS +#endif + +/* KS is secure-world only: M2354.h aliases it to KS_S unconditionally, with + * no KS_NS, unlike CRPT. A non-secure build reaches it through the veneers. + * Key handles ride in devCtx, so no set-key hook and no new struct member. */ + +/* SHA-512/224 and /256 are on by DEFAULT (gated on the negative + * WOLFSSL_NOSHA512_224/_256). The engine does full SHA-512 only, and the + * hashType member that tells them apart exists solely under + * WOLFSSL_SHA512_HASHTYPE. Without it the port cannot decline them and + * answers with a full SHA-512 digest and no error. */ +#if defined(WOLFSSL_NUVOTON_HASH) && !defined(NO_SHA512) + #ifdef WOLFSSL_NO_SHA512_HASHTYPE + #error "WOLFSSL_NUVOTON_HASH needs WOLFSSL_SHA512_HASHTYPE to tell \ +SHA-512 from SHA-512/224 and SHA-512/256" + #endif + #ifndef WOLFSSL_SHA512_HASHTYPE + #define WOLFSSL_SHA512_HASHTYPE + #endif +#endif + +/* The CRPT ECC and RSA drivers take NUL terminated hex strings, so reuse the + * converter asn.c already has for the custom ECC curve parameters. */ +#if (defined(WOLFSSL_NUVOTON_ECC) || defined(WOLFSSL_NUVOTON_RSA)) && \ + !defined(WOLFSSL_ASN_HEX_STRING) + #define WOLFSSL_ASN_HEX_STRING +#endif + +/* Clear of the other ports' defaults (STM32 806-808, RealTek 810-811). */ +#ifndef WOLFSSL_NUVOTON_DEVID + #define WOLFSSL_NUVOTON_DEVID 820 +#endif + +/* So the stock test and benchmark drive the port with no extra argument. */ +/* Every engine call and the static staging buffers are serialised with + * wolfSSL_CryptHwMutex*, which compile to nothing unless this is on. There is + * one CRPT channel, so without it two threads overwrite one another's state + * and their buffers. Define WOLFSSL_NUVOTON_NO_HW_MUTEX to opt out, which is + * only safe single threaded. */ +#if !defined(WOLFSSL_CRYPT_HW_MUTEX) && \ + !defined(WOLFSSL_NUVOTON_NO_HW_MUTEX) && !defined(SINGLE_THREADED) + #define WOLFSSL_CRYPT_HW_MUTEX 1 +#endif + +#ifndef WC_USE_DEVID + #define WC_USE_DEVID WOLFSSL_NUVOTON_DEVID +#endif + +/* AES-CCM on the engine, sharing the GCM staging buffers. */ +#if defined(HAVE_AESCCM) && !defined(NO_AES) && \ + !defined(WOLFSSL_NUVOTON_NO_AESCCM) + #undef WOLFSSL_NUVOTON_AESCCM + #define WOLFSSL_NUVOTON_AESCCM +#endif + +/* AES-GCM on the engine. Needs the packed packet staging buffers in + * nuvoton_hw.c, so it can be turned off on a part where that memory matters; + * GCM then runs in software like CCM does. */ +#if defined(HAVE_AESGCM) && !defined(NO_AES) && \ + !defined(WOLFSSL_NUVOTON_NO_AESGCM) + #undef WOLFSSL_NUVOTON_AESGCM + #define WOLFSSL_NUVOTON_AESGCM +#endif + +/* Bounce buffer for AES operands the engine cannot address. Largest chunk per + * DMA round; must be a multiple of the AES block size. Raising it trades + * static SRAM for fewer rounds. */ +#ifndef WOLFSSL_NUVOTON_DMA_BUF_SZ + #define WOLFSSL_NUVOTON_DMA_BUF_SZ (16 * 6) +#endif +#if (WOLFSSL_NUVOTON_DMA_BUF_SZ % 16) != 0 + #error "WOLFSSL_NUVOTON_DMA_BUF_SZ must be a multiple of the AES block size" +#endif + +/* Busy-wait bound, in loop iterations, so a wedged engine returns an error + * rather than hanging the caller. AES, SHA and RSA poll; only ECC needs the + * interrupt (see wc_nuvoton_hw_init). */ +#ifndef WOLFSSL_NUVOTON_HW_TIMEOUT + #define WOLFSSL_NUVOTON_HW_TIMEOUT 5000000 +#endif + +/* Cortex-M23 is Thumb-1. Both halves matter: the tier without the assembly + * leaves everything that misses the accelerator on portable C, which dominates + * at 96 MHz. sp_armthumb.c is Thumb-1; sp_cortexm.c is Thumb-2 and will not + * assemble here. No Thumb-1 per-algorithm asm exists, so WOLFSSL_ARMASM stays + * off. See doc/ASM_AND_MATH_DEFINES.md. */ +#if !defined(WOLFSSL_SP_ARM_THUMB) && !defined(WOLFSSL_SP_ARM_CORTEX_M) && \ + !defined(WOLFSSL_SP_ARM32) && !defined(WOLFSSL_SP_ARM64) && \ + !defined(WOLFSSL_SP_X86_64) && !defined(WOLFSSL_SP_MATH_ALL) && \ + !defined(WOLFSSL_NUVOTON_NO_SP_DEFAULT) + #define WOLFSSL_SP_ARM_THUMB + #ifndef NO_ASM + #define WOLFSSL_SP_ARM_THUMB_ASM + #endif +#endif + +/* No filesystem and no OS entropy device on this part, so drop /dev/random - + * but only when the TRNG arm in random.c is compiled, or the build would have + * no seed source at all. Naming engines without WOLFSSL_NUVOTON_TRNG means + * supplying wc_GenerateSeed() yourself. */ +#ifdef WOLFSSL_NUVOTON_TRNG + #ifndef NO_DEV_RANDOM + #define NO_DEV_RANDOM + #endif +#endif + +#endif /* WOLFSSL_NUVOTON_M2354 */ + +#endif /* WOLFSSL_NUVOTON_SETTINGS_H */ diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index a57a4c06b7d..7e8c38578ee 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -455,6 +455,13 @@ #include #endif +/* Nuvoton NuMicro M2354 port: enable the crypto callback and map WC_USE_DEVID + * before the rest of settings.h and before the unmodified test and benchmark + * read it. This header is macro only and pulls in no BSP dependencies. */ +#if defined(WOLFSSL_NUVOTON_M2354) + #include +#endif + /* Forward propagation of the legacy parent gate to the canonical name * (HAVE_DILITHIUM -> WOLFSSL_HAVE_MLDSA). Always active: required so that * a user_settings.h or build flag using only the legacy spelling still