From 380dc59f92dc8f708ab0714d90d64f47246f8d3a Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 16 Jun 2026 16:18:00 -0700 Subject: [PATCH] Add RealTek AmebaPro2 (RTL8735B) HUK crypto-callback port --- configure.ac | 23 +- wolfcrypt/src/include.am | 7 + wolfcrypt/src/port/realtek/README.md | 258 +++++++ wolfcrypt/src/port/realtek/amebapro2.c | 731 ++++++++++++++++++++ wolfcrypt/src/port/realtek/amebapro2_shim.h | 89 +++ wolfssl/wolfcrypt/include.am | 1 + wolfssl/wolfcrypt/port/realtek/amebapro2.h | 117 ++++ zephyr/CMakeLists.txt | 1 + 8 files changed, 1226 insertions(+), 1 deletion(-) create mode 100644 wolfcrypt/src/port/realtek/README.md create mode 100644 wolfcrypt/src/port/realtek/amebapro2.c create mode 100644 wolfcrypt/src/port/realtek/amebapro2_shim.h create mode 100644 wolfssl/wolfcrypt/port/realtek/amebapro2.h diff --git a/configure.ac b/configure.ac index bdf5d3df294..ce108235e02 100644 --- a/configure.ac +++ b/configure.ac @@ -3212,6 +3212,25 @@ case "$ENABLED_STSAFE" in esac +# RealTek AmebaPro2 (RTL8735B) HUK crypto-callback port. +# On-target the application supplies the AmebaPro2 HAL include path. This option +# is a host compile-test of the port: it swaps the HAL headers for a shim +# (WOLFSSL_AMEBAPRO2_HOST_TEST) so the cryptocb dispatch and wiring build without +# the vendor SDK. It forces crypto callbacks on (see the cryptocb block). +# Example: "./configure --enable-amebapro2" +ENABLED_AMEBAPRO2="no" +AC_ARG_ENABLE([amebapro2], + [AS_HELP_STRING([--enable-amebapro2], + [Enable RealTek AmebaPro2 (RTL8735B) HUK crypto-callback port (host compile-test).])], + [ ENABLED_AMEBAPRO2=$enableval ], + [ ENABLED_AMEBAPRO2=no ]) + +if test "x$ENABLED_AMEBAPRO2" != "xno" +then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_REALTEK_HUK -DWOLFSSL_AMEBAPRO2_HOST_TEST -DHAVE_AES_ECB" +fi + + # NXP SE050 # Example: "./configure --with-se050=/home/pi/simw_top" ENABLED_SE050="no" @@ -10680,7 +10699,7 @@ AC_ARG_ENABLE([cryptocb-sw-test], [ ENABLED_CRYPTOCB_SW_TEST=yes ] ) -if test "x$ENABLED_PKCS11" = "xyes" || test "x$ENABLED_WOLFTPM" = "xyes" || test "$ENABLED_CAAM" != "no" +if test "x$ENABLED_PKCS11" = "xyes" || test "x$ENABLED_WOLFTPM" = "xyes" || test "$ENABLED_CAAM" != "no" || test "x$ENABLED_AMEBAPRO2" != "xno" then ENABLED_CRYPTOCB=yes fi @@ -12429,6 +12448,7 @@ AM_CONDITIONAL([BUILD_IOTSAFE],[test "x$ENABLED_IOTSAFE" = "xyes"]) AM_CONDITIONAL([BUILD_IOTSAFE_HWRNG],[test "x$ENABLED_IOTSAFE_HWRNG" = "xyes"]) AM_CONDITIONAL([BUILD_SE050],[test "x$ENABLED_SE050" = "xyes"]) AM_CONDITIONAL([BUILD_STSAFE],[test "x$ENABLED_STSAFE" != "xno"]) +AM_CONDITIONAL([BUILD_AMEBAPRO2],[test "x$ENABLED_AMEBAPRO2" != "xno"]) AM_CONDITIONAL([BUILD_TROPIC01],[test "x$ENABLED_TROPIC01" = "xyes"]) AM_CONDITIONAL([BUILD_KDF],[test "x$ENABLED_KDF" = "xyes"]) AM_CONDITIONAL([BUILD_HMAC],[test "x$ENABLED_HMAC" = "xyes"]) @@ -13008,6 +13028,7 @@ echo " * IoT-Safe: $ENABLED_IOTSAFE" echo " * IoT-Safe HWRNG: $ENABLED_IOTSAFE_HWRNG" echo " * NXP SE050: $ENABLED_SE050" echo " * STMicro STSAFE: $ENABLED_STSAFE" +echo " * RealTek AmebaPro2 HUK: $ENABLED_AMEBAPRO2" echo " * TROPIC01: $ENABLED_TROPIC01" echo " * Maxim Integrated MAXQ10XX: $ENABLED_MAXQ10XX" echo " * PSA: $ENABLED_PSA" diff --git a/wolfcrypt/src/include.am b/wolfcrypt/src/include.am index 18d7a339cd5..7077e3bafbd 100644 --- a/wolfcrypt/src/include.am +++ b/wolfcrypt/src/include.am @@ -105,6 +105,9 @@ EXTRA_DIST += wolfcrypt/src/port/ti/ti-aes.c \ wolfcrypt/src/port/st/README.md \ wolfcrypt/src/port/st/STM32MP13.md \ wolfcrypt/src/port/st/STM32MP25.md \ + wolfcrypt/src/port/realtek/amebapro2.c \ + wolfcrypt/src/port/realtek/amebapro2_shim.h \ + wolfcrypt/src/port/realtek/README.md \ wolfcrypt/src/port/tropicsquare/tropic01.c \ wolfcrypt/src/port/tropicsquare/README.md \ wolfcrypt/src/port/af_alg/afalg_aes.c \ @@ -244,6 +247,10 @@ if BUILD_TROPIC01 src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/tropicsquare/tropic01.c endif +if BUILD_AMEBAPRO2 +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/realtek/amebapro2.c +endif + if BUILD_PSA src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/psa/psa.c src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/psa/psa_hash.c diff --git a/wolfcrypt/src/port/realtek/README.md b/wolfcrypt/src/port/realtek/README.md new file mode 100644 index 00000000000..c993a11d3e4 --- /dev/null +++ b/wolfcrypt/src/port/realtek/README.md @@ -0,0 +1,258 @@ +# RealTek AmebaPro2 (RTL8735B) HUK Port + +Binds wolfCrypt keys to the RTL8735B silicon Hardware Unique Key (HUK) through +the AmebaPro2 HAL crypto engine, via the wolfCrypt crypto-callback (CryptoCb) +framework. A 256-bit "seed" is run through the HAL HKDF key-ladder against the +HUK to land a device-bound working key in a secure key-storage slot; AES +(GCM/ECB/CBC/CTR) then runs from that slot and the working key never enters +software. It is a pure crypto-callback device and adds no wolfSSL core API or +struct fields: AES reads its seed from the standard `aes->devKey`, and ECDSA +reads a `wc_AmebaPro2_EccKey` (the HUK-wrapped scalar + seed) the caller attaches +via the standard `ecc_key->devCtx`. This mirrors the device pattern the STM32 +DHUK port (`wc_Stm32_DhukRegister`) also uses. + +## Hardware + +RTL8735B / AmebaPro2 security blocks used by this port (from the +`Ameba-AIoT/nuwa_hal_realtek` SDK, `rtl8735b` branch, headers under +`ameba/amebapro2/source/fwlib/rtl8735b/include/`): + +- HUK in OTP: `SB_OTP_HIGH_VAL_HUK1` (0x21), `HUK2` (0x22), `HUK_RMA` (0x2F). +- HKDF key-ladder in secure RAM: `hal_hkdf_hmac_sha256_secure_init`, + `hal_hkdf_extract_secure_all`, `hal_hkdf_expand_secure_all` -- derive the HUK + into a secure key-storage slot without exposing the key to software. +- AES secure-key ops that reference the derived slot by number: + `hal_crypto_aes_ecb_sk_init`, `hal_crypto_aes_gcm_sk_init` (key never leaves + hardware). +- The HUK-bound ECDSA sign path reuses the AES secure-key engine above to unwrap + the wrapped scalar, then signs in software. The HW ECDSA engine (`hal_ecdsa.h`) + and OTP-resident ECDSA keys (`hal_otp_ecdsa_key_*`) are follow-ons, not yet + used. +- TRNG (`hal_trng.h`); the `ameba-zephyr-pro2-platform` repo provides a Zephyr + entropy driver (`entropy_amebapro2.c`, DT `realtek,amebapro2-trng`) that feeds + wolfCrypt's `wc_GenerateSeed` via `sys_rand_get`. + +## Enabling + +```c +#define WOLFSSL_REALTEK_HUK /* enable the AmebaPro2 HUK device */ +#define WOLF_CRYPTO_CB /* required -- HUK routes through crypto callbacks */ +``` + +Set these in `user_settings.h`. The application/board CMake must add +the AmebaPro2 HAL include directory (e.g. +`.../fwlib/rtl8735b/include/`) to the wolfSSL library include path so this port +can include `hal_crypto.h` and `hal_hkdf.h`. + +Configurable (override in `user_settings.h` before including wolfSSL): + +| Macro | Default | Meaning | +|--------------------------------|---------|--------------------------------------| +| `WC_HUK_DEVID` | 809 | CryptoCb device id (STM32 DHUK is 808) | +| `WC_AMEBAPRO2_HUK_SK_IDX` | 1 | Secure-key slot holding the HUK (HUK1) | +| `WC_AMEBAPRO2_HKDF_PRK_IDX` | 3 | Intermediate HKDF PRK slot | +| `WC_AMEBAPRO2_DERIVED_WB_IDX` | 4 | Derived working-key slot (AES uses it) | +| `WC_AMEBAPRO2_HKDF_CRYPTO_SEL` | 0 | `crypto_sel` for the secure HKDF init | +| `WC_AMEBAPRO2_MAX_WRAPPED` | 96 | Max wrapped-scalar blob the ECDSA sign path unwraps | + +## API + +```c +#include + +/* One-time: register the AmebaPro2 HUK crypto-callback device. */ +wc_AmebaPro2_HukRegister(WC_HUK_DEVID); + +/* AES / GCM: enable via devId at init, then pass the 256-bit seed as the key. + * The seed is HKDF input that diversifies the HUK -- it is NOT the AES key. */ +Aes aes; +byte seed[32]; /* per-purpose derivation seed (need not be secret) */ +wc_AesInit(&aes, NULL, WC_HUK_DEVID); +wc_AesGcmSetKey(&aes, seed, 32); +wc_AesGcmEncrypt(&aes, ct, pt, ptSz, iv, 12, tag, tagSz, aad, aadSz); /* full GCM */ +wc_AesFree(&aes); + +/* AES-ECB / AES-CBC follow the same pattern (wc_AesSetKey + wc_AesEcb*/ +/* wc_AesCbc* with devId = WC_HUK_DEVID). */ + +wc_AmebaPro2_HukUnRegister(WC_HUK_DEVID); +``` + +The seed maps to a device-bound working key as: +HUK (slot `WC_AMEBAPRO2_HUK_SK_IDX`) -> `hal_hkdf_extract_secure_all` -> PRK slot +-> `hal_hkdf_expand_secure_all` -> working key in `WC_AMEBAPRO2_DERIVED_WB_IDX` +-> `hal_crypto_aes_gcm_sk_init` / `hal_crypto_aes_ecb_sk_init`. The derive and +the AES op run under one crypto-mutex hold; the working key never enters +software. Identical seed -> identical working key (deterministic, so GMAC +verifies and AES round-trips); a wrong seed yields a different key (GCM decrypt +returns `AES_GCM_AUTH_E`). + +HUK-bound ECDSA sign (Stage 3, wrapped-scalar): point the key's crypto-callback +context at a `wc_AmebaPro2_EccKey` (the scalar AES-wrapped under a HUK-derived +key, plus its 32-byte seed) -- no dedicated wolfSSL import API: + +```c +#include +wc_AmebaPro2_EccKey hk = { seed, 32, wrapped, wrappedLen, plainLen }; +ecc_key key; +wc_ecc_init_ex(&key, NULL, WC_HUK_DEVID); +wc_ecc_set_curve(&key, plainLen, ECC_SECP256R1); +key.devCtx = &hk; /* borrowed; must outlive the key */ +wc_ecc_sign_hash(hash, hashSz, sig, &sigSz, rng, &key); +``` + +At sign time the port derives the slot key from the seed, ECB-unwraps the scalar +into a short-lived buffer, signs, and scrubs it. The wrapped blob is device-bound +(it only unwraps on the silicon whose HUK produced the slot key). The scalar is +briefly in software during the sign; an OTP-resident model (`hal_ecdsa_select_prk`, +scalar never in software) and routing the sign itself through the HW ECDSA engine +(`hal_ecdsa`) are follow-ons. + +## Notes / limitations + +- The HAL GCM path assumes a 96-bit (12-byte) IV (standard J0). A non-12-byte + IV returns a hard error (not a software fallback, which would key off the seed + rather than the device-bound key). +- AES-CBC and AES-CTR chain in software over single-block + `hal_crypto_aes_ecb_sk_*` calls because the HAL exposes no CBC/CTR secure-key + variant; the key still stays in hardware. CTR maintains the wolfCrypt counter + state (`aes->reg`/`tmp`/`left`) so partial blocks continue across calls. +- The HAL crypto engine DMAs its buffers on 32-byte (cache-line) boundaries and + rejects an unaligned GCM iv/aad. The port stages key/iv/aad/tag on aligned + temporaries and bounces unaligned in/out through aligned buffers, so callers + need not align. +- Each operation derives the working key from the Aes' own `devKey` seed under + the crypto mutex (no shared port global), so concurrent `Aes` objects are + safe. +- `--enable-amebapro2` builds a host compile-test only: it swaps the HAL headers + for `amebapro2_shim.h` (sentinel stubs, no real crypto) to exercise the + crypto-callback dispatch and build wiring without the vendor SDK. All + functional validation requires RTL8735B hardware. + +## Status + +Validated on RTL8735B silicon (both the RealTek FreeRTOS SDK app and a Zephyr +image): registration; AES-GCM (encrypt / deterministic tag / decrypt-verify / +round-trip / wrong-seed -> `AES_GCM_AUTH_E` / unaligned buffers / non-12-byte-IV +reject); AES-ECB; AES-CBC (incl. in-place, multi-call); AES-CTR; and HUK-bound +ECDSA (P-256) -- all pass. + +- Stage 0 (skeleton, build wiring, host compile-test): done. +- Stage 1 (HUK key-ladder + full AES-GCM): done, validated on hardware. +- Stage 2 (AES-ECB / AES-CBC / AES-CTR): done, validated on hardware. +- Stage 3 (HUK-bound ECDSA sign, wrapped-scalar): done, validated on RTL8735B + (P-256 sign verifies against the original public key; tampered hash fails). + OTP-resident keys and HW-ECDSA-engine signing are follow-ons. + +## Benchmarks (software crypto baseline) + +`wolfcrypt_test` (full self-test, all PASS) and `wolfcrypt_benchmark` were run on +the RTL8735B EVB to validate the core library and toolchain on this target. The +figures below are **pure software wolfCrypt** -- they are NOT the HUK device +(which routes AES through the silicon engine for HUK-derived keys); they serve as +a reference baseline and to size the benefit of hardware offload. + +- Target: RTL8735B "KM4" Arm Cortex-M33 (ARMv8-M Mainline, TrustZone + DSP) at + 500 MHz (`CPU_CLK`); DDR at 533 MHz. +- Toolchain / build: RealTek ASDK 10.3.0 (GCC 10.3.0), SDK default `-Os`, + FreeRTOS, `WOLFCRYPT_ONLY`, `SINGLE_THREADED`, big-integer math via the generic + `WOLFSSL_SP_MATH_ALL` (portable C, no Cortex-M assembly), `BENCH_EMBEDDED`. +- Build options live with the SDK example (not in the wolfSSL tree): + `component/example/wolfcrypt_test/{user_settings.h, wolfcrypt_test.cmake, + main.c}` of the AmebaPro2 FreeRTOS SDK. The RNG is seeded from the SDK + `rtw_get_random_bytes`; `current_time()` uses `hal_read_systime_us()`. + +Symmetric / hash (higher is better): + +| Algorithm | Throughput | +|---------------------|------------| +| AES-128-CBC enc/dec | 9.55 / 9.67 MiB/s | +| AES-256-CBC enc/dec | 7.25 / 7.02 MiB/s | +| AES-128-GCM enc/dec | 5.35 / 5.33 MiB/s | +| AES-256-GCM enc/dec | 4.53 / 4.52 MiB/s | +| AES-128-CTR | 9.75 MiB/s | +| AES-128-ECB enc/dec | 10.42 / 10.56 MiB/s | +| AES-CCM enc/dec | 4.73 / 4.65 MiB/s | +| GMAC (4-bit table) | 13.43 MiB/s | +| AES-128-CMAC | 8.84 MiB/s | +| ChaCha20 | 24.79 MiB/s | +| ChaCha20-Poly1305 | 15.83 MiB/s | +| Poly1305 | 64.77 MiB/s | +| SHA-1 | 29.19 MiB/s | +| SHA-256 | 10.94 MiB/s | +| SHA-512 | 7.29 MiB/s | +| SHA3-256 | 6.61 MiB/s | +| HMAC-SHA256 | 10.85 MiB/s | + +Public key (higher is better): + +| Operation | Rate | +|-----------------------|------| +| RSA-2048 public | 214.7 ops/s | +| RSA-2048 private | 6.14 ops/s | +| RSA-2048 key gen | 0.40 ops/s | +| DH-2048 key gen/agree | 17.67 / 15.23 ops/s | +| ECDSA P-256 sign/verify | 40.03 / 29.81 ops/s | +| ECDHE P-256 agree | 40.69 ops/s | +| Curve25519 key gen/agree | 414.8 / 419.4 ops/s | +| Ed25519 sign/verify | 788.3 / 397.0 ops/s | + +The tables above are the portable-C baseline. The assembly backends below raise +these substantially. Curve25519/Ed25519 already use the dedicated +`curve25519.c`/`ed25519.c` fast code. + +## Optimizations (measured on RTL8735B @ 500 MHz, -Os) + +Two wolfCrypt assembly backends apply to this Cortex-M33 and were validated on +hardware (both keep `wolfcrypt_test` all-PASS). Neither needs wolfSSL source +changes -- they are build-config selections plus adding the relevant asm files. + +### 1. Public key -- `sp_cortexm.c` (Thumb-2/DSP single-precision) + +Enable with `WOLFSSL_SP_ARM_CORTEX_M_ASM` + `WOLFSSL_HAVE_SP_RSA` + +`WOLFSSL_HAVE_SP_ECC` + `WOLFSSL_HAVE_SP_DH`, and add `wolfcrypt/src/sp_cortexm.c` +to the build (alongside the generic `sp_int.c` for sizes without an asm path). + +| Operation | Generic C | sp_cortexm | Speedup | +|------------------------|-----------|------------|---------| +| ECC P-256 key gen | 40.7 | 541.2 ops/s | 13.3x | +| ECDSA P-256 sign | 40.0 | 427.6 ops/s | 10.7x | +| ECDSA P-256 verify | 29.8 | 292.7 ops/s | 9.8x | +| ECDHE P-256 agree | 40.7 | 318.1 ops/s | 7.8x | +| RSA-2048 public | 214.7 | 618.4 ops/s | 2.9x | +| RSA-2048 private | 6.14 | 19.0 ops/s | 3.1x | +| DH-2048 agree | 15.2 | 38.3 ops/s | 2.5x | + +### 2. Symmetric -- Thumb-2 asm (`port/arm/thumb2-*-asm.S`) + +Enable with `WOLFSSL_ARMASM` + `WOLFSSL_ARMASM_THUMB2` + +`WOLFSSL_ARMASM_NO_HW_CRYPTO` + `WOLFSSL_ARMASM_NO_NEON` + `WOLFSSL_ARM_ARCH=7`, +and add `thumb2-aes-asm.S`, `thumb2-sha256-asm.S`, `thumb2-sha512-asm.S`, +`thumb2-sha3-asm.S`, `thumb2-chacha-asm.S`, `thumb2-poly1305-asm.S`. +`WOLFSSL_ARMASM` is a global switch, so provide the `.S` for every covered +module. (Curve25519/Ed25519 also have Thumb-2 asm but their `ge_operations.c` +integration assumes 64-bit and was left on the C path here.) + +| Algorithm | Generic C | Thumb-2 asm | Speedup | +|---------------------|-----------|-------------|---------| +| AES-128-CBC enc | 9.55 | 20.85 MiB/s | 2.2x | +| AES-128-ECB enc | 10.42 | 20.82 MiB/s | 2.0x | +| AES-128-CTR | 9.75 | 20.47 MiB/s | 2.1x | +| AES-128-GCM enc | 5.35 | 10.30 MiB/s | 1.9x | +| GMAC | 13.43 | 20.81 MiB/s | 1.5x | +| AES-128-CMAC | 8.84 | 14.67 MiB/s | 1.7x | +| ChaCha20 | 24.79 | 46.44 MiB/s | 1.9x | +| ChaCha20-Poly1305 | 15.83 | 25.38 MiB/s | 1.6x | +| SHA-256 | 10.94 | 17.83 MiB/s | 1.6x | +| SHA3-256 | 6.61 | 8.64 MiB/s | 1.3x | +| HMAC-SHA256 | 10.85 | 17.66 MiB/s | 1.6x | + +### Note on hardware offload + +For AES, hashing and ECDSA the RTL8735B has a dedicated crypto engine (the HAL +`hal_crypto_*` / `hal_ecdsa` blocks this HUK port already uses for HUK-derived +keys). A general (any-key) HW crypto-callback port over that engine would beat +the Thumb-2 software figures above and is the recommended production path for +symmetric throughput; the Thumb-2 asm is the portable software fallback. The +`sp_cortexm.c` PK speedup is worth taking regardless, since it needs no silicon +support. diff --git a/wolfcrypt/src/port/realtek/amebapro2.c b/wolfcrypt/src/port/realtek/amebapro2.c new file mode 100644 index 00000000000..c27d528e14e --- /dev/null +++ b/wolfcrypt/src/port/realtek/amebapro2.c @@ -0,0 +1,731 @@ +/* amebapro2.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef WOLFSSL_REALTEK_HUK + +#include +#include +#include +#include +#include + +#ifdef WOLF_CRYPTO_CB + #include +#endif +#ifndef NO_AES + #include +#endif +#ifdef HAVE_ECC + #include +#endif + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +/* Vendor HAL surface: the real SDK headers on target, the host-test shim under + * --enable-amebapro2 (see amebapro2_shim.h). The on-target include path is + * supplied by the application / board CMake (see this port's README). */ +#ifdef WOLFSSL_AMEBAPRO2_HOST_TEST + #include "amebapro2_shim.h" +#else + #include "hal_crypto.h" + #include "hal_hkdf.h" +#endif + +#ifdef WOLF_CRYPTO_CB + +/* The HUK-derived working key is always a 256-bit key. */ +#define WC_AMEBAPRO2_KEYLEN 32 + +/* The HAL crypto engine DMAs its key/iv/aad/tag buffers on 32-byte (cache line) + * boundaries; unaligned caller buffers are bounced through aligned temporaries + * so callers need not align. A heap bounce is over-allocated by 31 bytes and the + * usable pointer rounded up to a 32-byte boundary (XMALLOC does not guarantee + * that alignment); the raw allocation pointer is kept for XFREE. */ +#define WC_AMEBAPRO2_IS_ALIGNED32(p) ((((wc_ptr_t)(p)) & 31u) == 0) +#define WC_AMEBAPRO2_ALIGN_UP32(p) \ + ((byte*)((((wc_ptr_t)(p)) + 31u) & ~(wc_ptr_t)31u)) + +static int AmebaPro2Huk_Init(void* ctx) +{ + (void)ctx; + /* One-time crypto engine bring-up. Idempotent on the HAL side. */ + if (hal_crypto_engine_init() != 0) { + return WC_HW_E; + } + return 0; +} + +/* Run the HUK key-ladder on the per-operation seed (the 32-byte HKDF input the + * Aes carries in devKey): HUK (secure key slot) -> HKDF-Extract(secure) -> PRK + * slot -> HKDF-Expand(secure) -> device-bound working key in the derived slot. + * The working key never enters software; on return it resides in + * WC_AMEBAPRO2_DERIVED_WB_IDX, ready for an AES *_sk_init that references that + * slot. The seed is passed by argument (not held in a global), so concurrent + * Aes objects never race; the caller holds the crypto mutex across derive + op. + * + * The HUK is the built-in secure key at slot WC_AMEBAPRO2_HUK_SK_IDX (HUK1); the + * engine reads it internally. We deliberately do NOT lock the derived slot: each + * operation re-derives the working key into it, and a locked key-storage slot + * silently rejects that re-derivation (it would keep a stale key, so a different + * seed would yield the wrong result). The slot is overwritten on the next + * derive; nothing reads it back into software. */ +static int AmebaPro2Huk_DeriveSlotKey(const byte* seed) +{ + XALIGNED(32) byte seedA[WC_AMEBAPRO2_KEYLEN]; + + if (seed == NULL) { + return BAD_FUNC_ARG; + } + /* HKDF reads the seed via DMA -- pass it a 32-byte-aligned copy. */ + XMEMCPY(seedA, seed, WC_AMEBAPRO2_KEYLEN); + + /* Init the secure HKDF HMAC-SHA256 engine (sets isHWCrypto_Init); required + * before any *_secure_all call or extract returns HW_NOT_INIT. */ + if (hal_hkdf_hmac_sha256_secure_init((u8)WC_AMEBAPRO2_HKDF_CRYPTO_SEL) + != HAL_OK) { + return WC_HW_E; + } + /* HKDF-Extract: PRK = HMAC(HUK, seed), into the PRK slot. */ + if (hal_hkdf_extract_secure_all((u8)WC_AMEBAPRO2_HUK_SK_IDX, + (u8)WC_AMEBAPRO2_HKDF_PRK_IDX, seedA) != HAL_OK) { + return WC_HW_E; + } + /* HKDF-Expand: OKM = working key, into the derived working-key slot. */ + if (hal_hkdf_expand_secure_all((u8)WC_AMEBAPRO2_HKDF_PRK_IDX, + (u8)WC_AMEBAPRO2_DERIVED_WB_IDX, seedA) != HAL_OK) { + return WC_HW_E; + } + return 0; +} + +#ifndef NO_AES + +#ifdef HAVE_AESGCM +/* Full AES-GCM (encrypt or decrypt-verify) under a HUK-derived slot key. + * The HAL GCM path assumes a 96-bit (12-byte) IV (standard J0). For a HUK key we + * must not fall back to software GCM (the software path would key off the seed, + * not the device-bound key), so an unsupported IV length returns BAD_FUNC_ARG -- + * a hard error. (NOT_COMPILED_IN must NOT be used here: the crypto-callback layer + * rewrites it to CRYPTOCB_UNAVAILABLE, which would trigger exactly that unwanted + * software fallback.) */ +static int AmebaPro2Huk_Gcm(int enc, const byte* seed, const byte* in, + word32 sz, byte* out, const byte* iv, word32 ivSz, const byte* aad, + word32 aadSz, byte* tag, word32 tagSz) +{ + int ret; + /* 16-byte aligned IV block: the HAL reads a full block, so the 4 bytes past + * the 12-byte nonce must be zero and stable across calls. */ + XALIGNED(32) byte ivA[WC_AES_BLOCK_SIZE] = { 0 }; + XALIGNED(32) byte hwTag[WC_AES_BLOCK_SIZE] = { 0 }; + const byte* inA = in; /* aligned views; bounced below if needed */ + const byte* aadA = aad; + byte* outA = out; + byte* inBounce = NULL; + byte* outBounce = NULL; + byte* aadBounce = NULL; + + /* Validate args before any copy/bounce/dereference: the crypto-callback + * wrapper does not, so a bad caller would otherwise crash inside the HAL. */ + if (seed == NULL || iv == NULL) { + return BAD_FUNC_ARG; + } + if (sz > 0 && (in == NULL || out == NULL)) { + return BAD_FUNC_ARG; + } + if (aadSz > 0 && aad == NULL) { + return BAD_FUNC_ARG; + } + if (ivSz != GCM_NONCE_MID_SZ) { + /* Hard error -- see header comment; must not be NOT_COMPILED_IN. */ + return BAD_FUNC_ARG; /* only 12-byte GCM IV supported by the HAL */ + } + if (tag == NULL || tagSz == 0 || tagSz > WC_AES_BLOCK_SIZE) { + return BAD_FUNC_ARG; + } + + /* Bounce any unaligned DMA buffer through a 32-byte-aligned temporary. iv + * and tag are small and always staged on aligned stack buffers; in/out/aad + * may be large, so are only copied when actually unaligned. */ + XMEMCPY(ivA, iv, GCM_NONCE_MID_SZ); + if (aadSz > 0 && !WC_AMEBAPRO2_IS_ALIGNED32(aad)) { + aadBounce = (byte*)XMALLOC(aadSz + 31, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (aadBounce == NULL) { + return MEMORY_E; + } + aadA = WC_AMEBAPRO2_ALIGN_UP32(aadBounce); + XMEMCPY((byte*)aadA, aad, aadSz); + } + if (sz > 0 && !WC_AMEBAPRO2_IS_ALIGNED32(in)) { + inBounce = (byte*)XMALLOC(sz + 31, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (inBounce == NULL) { + ret = MEMORY_E; + goto cleanup; + } + inA = WC_AMEBAPRO2_ALIGN_UP32(inBounce); + XMEMCPY((byte*)inA, in, sz); + } + if (sz > 0 && !WC_AMEBAPRO2_IS_ALIGNED32(out)) { + outBounce = (byte*)XMALLOC(sz + 31, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (outBounce == NULL) { + ret = MEMORY_E; + goto cleanup; + } + outA = WC_AMEBAPRO2_ALIGN_UP32(outBounce); + } + if (sz == 0) { + /* GMAC (empty payload): the caller's in/out may be NULL. Point the HAL at + * a valid aligned buffer -- zero data bytes are processed, only the tag is + * produced over the AAD. */ + inA = ivA; + outA = ivA; + } + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + goto cleanup; + } + ret = AmebaPro2Huk_DeriveSlotKey(seed); + if (ret != 0) { + goto unlock; + } + if (hal_crypto_aes_gcm_sk_init((byte)WC_AMEBAPRO2_DERIVED_WB_IDX, + WC_AMEBAPRO2_KEYLEN) != 0) { + ret = WC_HW_E; + goto unlock; + } + XMEMSET(hwTag, 0, sizeof(hwTag)); + if (enc) { + if (hal_crypto_aes_gcm_encrypt(inA, sz, ivA, aadA, aadSz, outA, hwTag) + != 0) { + ret = WC_HW_E; + goto unlock; + } + XMEMCPY(tag, hwTag, tagSz); + ret = 0; + } + else { + if (hal_crypto_aes_gcm_decrypt(inA, sz, ivA, aadA, aadSz, outA, hwTag) + != 0) { + ret = WC_HW_E; + goto unlock; + } + if (ConstantCompare(hwTag, tag, (int)tagSz) != 0) { + if (outA != NULL && sz != 0) { + ForceZero(outA, sz); + } + ret = AES_GCM_AUTH_E; + } + else { + ret = 0; + } + } + if (ret == 0 && outBounce != NULL) { + XMEMCPY(out, outA, sz); + } + +unlock: + ForceZero(hwTag, sizeof(hwTag)); + wolfSSL_CryptHwMutexUnLock(); +cleanup: + if (inBounce != NULL) { + XFREE(inBounce, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + if (outBounce != NULL) { + ForceZero(outA, sz); /* scrub the aligned plaintext view */ + XFREE(outBounce, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + if (aadBounce != NULL) { + XFREE(aadBounce, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + return ret; +} +#endif /* HAVE_AESGCM */ + +/* AES-ECB under a HUK-derived slot key. sz must be a multiple of the block. */ +static int AmebaPro2Huk_Ecb(int enc, const byte* seed, const byte* in, + word32 sz, byte* out) +{ + int ret; + + if (seed == NULL || in == NULL || out == NULL || + sz == 0 || (sz % WC_AES_BLOCK_SIZE) != 0) { + return BAD_FUNC_ARG; + } + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + ret = AmebaPro2Huk_DeriveSlotKey(seed); + if (ret != 0) { + goto out; + } + if (hal_crypto_aes_ecb_sk_init((byte)WC_AMEBAPRO2_DERIVED_WB_IDX, + WC_AMEBAPRO2_KEYLEN) != 0) { + ret = WC_HW_E; + goto out; + } + if (enc) { + ret = hal_crypto_aes_ecb_encrypt(in, sz, NULL, 0, out); + } + else { + ret = hal_crypto_aes_ecb_decrypt(in, sz, NULL, 0, out); + } + if (ret != 0) { + ret = WC_HW_E; + } + +out: + wolfSSL_CryptHwMutexUnLock(); + return ret; +} + +#ifdef HAVE_AES_CBC +/* AES-CBC under a HUK-derived slot key. The HAL has no CBC secure-key variant + * (only ECB/GCM expose *_sk_init), so chain in software over single-block + * ECB-sk operations -- the key still never leaves hardware. iv is the 16-byte + * chaining block (aes->reg); on success it is advanced to the last ciphertext + * block for the next call. Handles in == out (in-place) for both directions. */ +static int AmebaPro2Huk_Cbc(int enc, const byte* seed, const byte* in, + word32 sz, byte* out, byte* iv) +{ + int ret; + word32 off; + XALIGNED(32) byte prev[WC_AES_BLOCK_SIZE]; + XALIGNED(32) byte blk[WC_AES_BLOCK_SIZE]; + XALIGNED(32) byte cur[WC_AES_BLOCK_SIZE]; + + if (seed == NULL || in == NULL || out == NULL || iv == NULL || + sz == 0 || (sz % WC_AES_BLOCK_SIZE) != 0) { + return BAD_FUNC_ARG; + } + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + ret = AmebaPro2Huk_DeriveSlotKey(seed); + if (ret != 0) { + goto out; + } + if (hal_crypto_aes_ecb_sk_init((byte)WC_AMEBAPRO2_DERIVED_WB_IDX, + WC_AMEBAPRO2_KEYLEN) != 0) { + ret = WC_HW_E; + goto out; + } + + XMEMCPY(prev, iv, WC_AES_BLOCK_SIZE); + for (off = 0; off < sz; off += WC_AES_BLOCK_SIZE) { + if (enc) { + /* C_i = ECB_enc(P_i XOR C_{i-1}). Reads in+off once, before writing + * out+off, so in-place (out == in) is safe. */ + xorbufout(blk, in + off, prev, WC_AES_BLOCK_SIZE); + ret = hal_crypto_aes_ecb_encrypt(blk, WC_AES_BLOCK_SIZE, NULL, 0, + out + off); + if (ret != 0) { + ret = WC_HW_E; + goto out; + } + XMEMCPY(prev, out + off, WC_AES_BLOCK_SIZE); + } + else { + /* P_i = ECB_dec(C_i) XOR C_{i-1}. Save C_i first: writing out+off + * below would clobber it when out == in, and it is the next call's + * chaining value. */ + XMEMCPY(cur, in + off, WC_AES_BLOCK_SIZE); + ret = hal_crypto_aes_ecb_decrypt(cur, WC_AES_BLOCK_SIZE, NULL, 0, + blk); + if (ret != 0) { + ret = WC_HW_E; + goto out; + } + xorbufout(out + off, blk, prev, WC_AES_BLOCK_SIZE); + XMEMCPY(prev, cur, WC_AES_BLOCK_SIZE); + } + } + /* Advance the chaining IV to the last ciphertext block (prev holds it for + * both directions); only on full success. */ + XMEMCPY(iv, prev, WC_AES_BLOCK_SIZE); + ret = 0; + +out: + ForceZero(prev, sizeof(prev)); + ForceZero(blk, sizeof(blk)); + ForceZero(cur, sizeof(cur)); + wolfSSL_CryptHwMutexUnLock(); + return ret; +} +#endif /* HAVE_AES_CBC */ + +#ifdef WOLFSSL_AES_COUNTER +/* Increment a 16-byte big-endian (network order) counter in place. */ +static void AmebaPro2Huk_IncCtr(byte* ctr) +{ + int i; + for (i = WC_AES_BLOCK_SIZE - 1; i >= 0; i--) { + if (++ctr[i] != 0) { + break; + } + } +} + +/* AES-CTR under a HUK-derived slot key. The HAL has no CTR secure-key variant, + * so generate the keystream by ECB-sk encrypting the counter and XOR it with the + * data -- the key never leaves hardware. Maintains the wolfCrypt CTR state: + * aes->reg (counter), aes->tmp (current keystream block) and aes->left (unused + * keystream bytes at the tail of aes->tmp) so partial blocks continue across + * calls exactly as the software path does. The counter is staged on an aligned + * stack buffer, so caller in/out alignment does not matter (only XORed here). */ +static int AmebaPro2Huk_Ctr(Aes* aes, const byte* seed, const byte* in, + word32 sz, byte* out) +{ + int ret; + word32 processed; + XALIGNED(32) byte ctr[WC_AES_BLOCK_SIZE] = { 0 }; + XALIGNED(32) byte ks[WC_AES_BLOCK_SIZE] = { 0 }; + + if (aes == NULL || (sz != 0 && (in == NULL || out == NULL))) { + return BAD_FUNC_ARG; + } + + /* If the whole request is covered by leftover keystream, no HW is needed: + * consume it and return without touching the lock. */ + if (aes->left >= sz) { + if (sz > 0) { + xorbufout(out, in, + (byte*)aes->tmp + WC_AES_BLOCK_SIZE - aes->left, sz); + aes->left -= sz; + } + return 0; + } + + /* HW is needed -- take the lock before mutating any state, so a lock failure + * leaves the CTR state and output untouched. */ + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + + /* Derive/init the HW key first: a failure here must leave the output and CTR + * state untouched, so the leftover-keystream consumption (below) only runs + * once the hardware is ready. */ + ret = AmebaPro2Huk_DeriveSlotKey(seed); + if (ret != 0) { + goto out; + } + if (hal_crypto_aes_ecb_sk_init((byte)WC_AMEBAPRO2_DERIVED_WB_IDX, + WC_AMEBAPRO2_KEYLEN) != 0) { + ret = WC_HW_E; + goto out; + } + + /* Now consume any leftover keystream (all of it, since left < sz here). */ + processed = aes->left; + if (processed > 0) { + xorbufout(out, in, + (byte*)aes->tmp + WC_AES_BLOCK_SIZE - aes->left, processed); + out += processed; + in += processed; + aes->left = 0; + sz -= processed; + } + + XMEMCPY(ctr, aes->reg, WC_AES_BLOCK_SIZE); + while (sz >= WC_AES_BLOCK_SIZE) { + ret = hal_crypto_aes_ecb_encrypt(ctr, WC_AES_BLOCK_SIZE, NULL, 0, ks); + if (ret != 0) { + ret = WC_HW_E; + goto out; + } + xorbufout(out, in, ks, WC_AES_BLOCK_SIZE); + AmebaPro2Huk_IncCtr(ctr); + out += WC_AES_BLOCK_SIZE; + in += WC_AES_BLOCK_SIZE; + sz -= WC_AES_BLOCK_SIZE; + } + if (sz > 0) { + /* Final partial block: keep the unused keystream for the next call. */ + ret = hal_crypto_aes_ecb_encrypt(ctr, WC_AES_BLOCK_SIZE, NULL, 0, ks); + if (ret != 0) { + ret = WC_HW_E; + goto out; + } + XMEMCPY(aes->tmp, ks, WC_AES_BLOCK_SIZE); + xorbufout(out, in, ks, sz); + AmebaPro2Huk_IncCtr(ctr); + aes->left = WC_AES_BLOCK_SIZE - sz; + } + XMEMCPY(aes->reg, ctr, WC_AES_BLOCK_SIZE); + ret = 0; + +out: + ForceZero(ks, sizeof(ks)); + ForceZero(ctr, sizeof(ctr)); + wolfSSL_CryptHwMutexUnLock(); + return ret; +} +#endif /* WOLFSSL_AES_COUNTER */ + +/* The 256-bit seed an Aes carries in devKey (set via the normal key API) is the + * per-operation HKDF input. Point *seed at it, or return CRYPTOCB_UNAVAILABLE if + * this is not a 256-bit seed key (so non-HUK keys fall back to software). */ +static int AmebaPro2Huk_AesSeed(Aes* aes, const byte** seed) +{ + if (aes == NULL || aes->keylen != WC_AMEBAPRO2_KEYLEN) { + return CRYPTOCB_UNAVAILABLE; + } + *seed = (const byte*)aes->devKey; + return 0; +} + +/* Route a cipher (AES ECB/CBC/CTR, AES-GCM) request to the HUK backend. */ +static int AmebaPro2Huk_Cipher(struct wc_CryptoInfo* info) +{ + int ret; + const byte* seed = NULL; + + switch (info->cipher.type) { +#if defined(HAVE_AES_ECB) || defined(WOLFSSL_AES_DIRECT) || \ + defined(WOLF_CRYPTO_CB_ONLY_AES) + case WC_CIPHER_AES_ECB: + ret = AmebaPro2Huk_AesSeed(info->cipher.aesecb.aes, &seed); + if (ret != 0) { + return ret; + } + return AmebaPro2Huk_Ecb(info->cipher.enc, seed, info->cipher.aesecb.in, + info->cipher.aesecb.sz, info->cipher.aesecb.out); +#endif +#if defined(HAVE_AES_CBC) + case WC_CIPHER_AES_CBC: + ret = AmebaPro2Huk_AesSeed(info->cipher.aescbc.aes, &seed); + if (ret != 0) { + return ret; + } + /* AmebaPro2Huk_Cbc advances aes->reg (the chaining IV) itself, correctly + * for in-place and both directions. */ + return AmebaPro2Huk_Cbc(info->cipher.enc, seed, info->cipher.aescbc.in, + info->cipher.aescbc.sz, info->cipher.aescbc.out, + (byte*)info->cipher.aescbc.aes->reg); +#endif +#ifdef WOLFSSL_AES_COUNTER + case WC_CIPHER_AES_CTR: + ret = AmebaPro2Huk_AesSeed(info->cipher.aesctr.aes, &seed); + if (ret != 0) { + return ret; + } + return AmebaPro2Huk_Ctr(info->cipher.aesctr.aes, seed, + info->cipher.aesctr.in, info->cipher.aesctr.sz, + info->cipher.aesctr.out); +#endif +#ifdef HAVE_AESGCM + case WC_CIPHER_AES_GCM: + if (info->cipher.enc) { + ret = AmebaPro2Huk_AesSeed(info->cipher.aesgcm_enc.aes, &seed); + if (ret != 0) { + return ret; + } + return AmebaPro2Huk_Gcm(1, seed, + info->cipher.aesgcm_enc.in, + info->cipher.aesgcm_enc.sz, + info->cipher.aesgcm_enc.out, + info->cipher.aesgcm_enc.iv, + info->cipher.aesgcm_enc.ivSz, + info->cipher.aesgcm_enc.authIn, + info->cipher.aesgcm_enc.authInSz, + info->cipher.aesgcm_enc.authTag, + info->cipher.aesgcm_enc.authTagSz); + } + else { + ret = AmebaPro2Huk_AesSeed(info->cipher.aesgcm_dec.aes, &seed); + if (ret != 0) { + return ret; + } + return AmebaPro2Huk_Gcm(0, seed, + info->cipher.aesgcm_dec.in, + info->cipher.aesgcm_dec.sz, + info->cipher.aesgcm_dec.out, + info->cipher.aesgcm_dec.iv, + info->cipher.aesgcm_dec.ivSz, + info->cipher.aesgcm_dec.authIn, + info->cipher.aesgcm_dec.authInSz, + (byte*)info->cipher.aesgcm_dec.authTag, + info->cipher.aesgcm_dec.authTagSz); + } +#endif + default: + return CRYPTOCB_UNAVAILABLE; + } +} +#endif /* !NO_AES */ + +#if defined(HAVE_ECC) && defined(HAVE_ECC_SIGN) +/* Route an ECDSA sign request to the HUK backend (wrapped-scalar model). + * + * The caller attaches a wc_AmebaPro2_EccKey via the standard crypto-callback + * context pointer key->devCtx (see amebapro2.h): the private scalar AES-wrapped + * under a HUK-derived key, plus the 32-byte seed. To sign we derive the same slot + * key from the seed, ECB-unwrap the scalar into a short-lived buffer, sign with + * it, then scrub. The wrapped blob is device-bound: it only unwraps on the + * silicon whose HUK produced the slot key, so it is not portable. + * + * The scalar is briefly in software during the sign -- RealTek has no STM32-style + * SAES->PKA blob coupling that would keep it hardware-only. An OTP-resident model + * (hal_ecdsa_select_prk, scalar never in software) is a planned follow-on, as is + * routing the sign itself through the HW ECDSA engine (hal_ecdsa). */ +static int AmebaPro2Huk_PkSign(struct wc_CryptoInfo* info) +{ + ecc_key* key = info->pk.eccsign.key; + const wc_AmebaPro2_EccKey* hk; + ecc_key* tmp = NULL; + int ret; + int curveId; + word32 scalarSz; + XALIGNED(32) byte wrapped[WC_AMEBAPRO2_MAX_WRAPPED]; + byte scalar[MAX_ECC_BYTES]; + + if (key == NULL || key->devCtx == NULL) { + return CRYPTOCB_UNAVAILABLE; /* not a HUK-bound ECC key */ + } + hk = (const wc_AmebaPro2_EccKey*)key->devCtx; + if (hk->seed == NULL || hk->seedSz != WC_AMEBAPRO2_KEYLEN || + hk->wrapped == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + if (hk->wrappedLen == 0 || + (hk->wrappedLen % WC_AES_BLOCK_SIZE) != 0 || /* ECB unwrap needs whole blocks */ + hk->wrappedLen > sizeof(wrapped) || + hk->plainLen == 0 || + hk->plainLen > hk->wrappedLen || /* scalar fits in the blob */ + hk->plainLen > (word32)sizeof(scalar)) { + return BAD_FUNC_ARG; + } + curveId = (key->dp != NULL) ? key->dp->id : ECC_SECP256R1; + scalarSz = hk->plainLen; + + /* Unwrap: derive the HUK slot key from the seed, ECB-sk decrypt in place. */ + XMEMCPY(wrapped, hk->wrapped, hk->wrappedLen); + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + ForceZero(wrapped, sizeof(wrapped)); + return ret; + } + ret = AmebaPro2Huk_DeriveSlotKey(hk->seed); + if (ret == 0 && hal_crypto_aes_ecb_sk_init( + (byte)WC_AMEBAPRO2_DERIVED_WB_IDX, WC_AMEBAPRO2_KEYLEN) != 0) { + ret = WC_HW_E; + } + if (ret == 0 && hal_crypto_aes_ecb_decrypt( + wrapped, hk->wrappedLen, NULL, 0, wrapped) != 0) { + ret = WC_HW_E; + } + wolfSSL_CryptHwMutexUnLock(); + if (ret != 0) { + ForceZero(wrapped, sizeof(wrapped)); + return ret; + } + XMEMCPY(scalar, wrapped, scalarSz); + ForceZero(wrapped, sizeof(wrapped)); + + /* Sign with the unwrapped scalar via the software ECC path. The temporary + * key is forced to INVALID_DEVID so the inner sign does not re-enter this + * crypto callback. */ + tmp = (ecc_key*)XMALLOC(sizeof(ecc_key), NULL, DYNAMIC_TYPE_ECC); + if (tmp == NULL) { + ForceZero(scalar, sizeof(scalar)); + return MEMORY_E; + } + ret = wc_ecc_init_ex(tmp, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_ecc_import_private_key_ex(scalar, scalarSz, NULL, 0, tmp, + curveId); + if (ret == 0) { + ret = wc_ecc_sign_hash(info->pk.eccsign.in, info->pk.eccsign.inlen, + info->pk.eccsign.out, info->pk.eccsign.outlen, + info->pk.eccsign.rng, tmp); + } + wc_ecc_free(tmp); + } + ForceZero(scalar, sizeof(scalar)); + XFREE(tmp, NULL, DYNAMIC_TYPE_ECC); + return ret; +} +#endif /* HAVE_ECC && HAVE_ECC_SIGN */ + +/* The crypto-callback device entry point (registered by + * wc_AmebaPro2_HukRegister). Returns CRYPTOCB_UNAVAILABLE for anything it does + * not handle so the caller falls back to software. */ +static int AmebaPro2_CryptoDevCb(int devId, struct wc_CryptoInfo* info, + void* ctx) +{ + (void)devId; + (void)ctx; + if (info == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + switch (info->algo_type) { +#ifndef NO_AES + case WC_ALGO_TYPE_CIPHER: + return AmebaPro2Huk_Cipher(info); +#endif +#if defined(HAVE_ECC) && defined(HAVE_ECC_SIGN) + case WC_ALGO_TYPE_PK: + if (info->pk.type == WC_PK_TYPE_ECDSA_SIGN) { + return AmebaPro2Huk_PkSign(info); + } + return CRYPTOCB_UNAVAILABLE; +#endif + default: + return CRYPTOCB_UNAVAILABLE; + } +} + +/* Register the AmebaPro2 HUK device at devId (e.g. WC_HUK_DEVID). After this, + * objects whose devId is set to it at init route transparently to the HUK + * crypto engine. */ +int wc_AmebaPro2_HukRegister(int devId) +{ + int ret = AmebaPro2Huk_Init(NULL); + if (ret != 0) { + return ret; + } + return wc_CryptoCb_RegisterDevice(devId, AmebaPro2_CryptoDevCb, NULL); +} + +void wc_AmebaPro2_HukUnRegister(int devId) +{ + wc_CryptoCb_UnRegisterDevice(devId); + /* No port-global secret to scrub: each op derives from the Aes' own devKey + * seed under the crypto mutex; the working key lives only in the HW slot. */ +} + +#endif /* WOLF_CRYPTO_CB */ + +#endif /* WOLFSSL_REALTEK_HUK */ diff --git a/wolfcrypt/src/port/realtek/amebapro2_shim.h b/wolfcrypt/src/port/realtek/amebapro2_shim.h new file mode 100644 index 00000000000..f8ab9f022e8 --- /dev/null +++ b/wolfcrypt/src/port/realtek/amebapro2_shim.h @@ -0,0 +1,89 @@ +/* amebapro2_shim.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 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* Host compile-test stand-in for the slice of the RealTek AmebaPro2 HAL that + * wolfcrypt/src/port/realtek/amebapro2.c references. Compiled ONLY under + * WOLFSSL_AMEBAPRO2_HOST_TEST (set by --enable-amebapro2). It lets the + * crypto-callback dispatch, field access, and compile-time guards be exercised + * on a host without the customer SDK. Every stub returns a success sentinel; it + * performs NO real crypto. On target this header is NOT used -- the real HAL + * headers (hal_crypto.h, hal_hkdf.h) are included instead, supplied via the + * application/board include path. + * + * The prototypes here intentionally mirror the real HAL signatures from + * nuwa_hal_realtek (rtl8735b branch), + * ameba/amebapro2/source/fwlib/rtl8735b/include/. Keep this in sync with the + * HAL calls in amebapro2.c (add a stub here when the port starts calling a new + * HAL function under host test). + */ + +#ifndef _WOLFPORT_AMEBAPRO2_SHIM_H_ +#define _WOLFPORT_AMEBAPRO2_SHIM_H_ + +#ifdef WOLFSSL_AMEBAPRO2_HOST_TEST + +/* HAL scalar types (the real HAL pulls these from its basic_types header). */ +#ifndef _RTL8735B_TYPES_SHIMMED_ + #define _RTL8735B_TYPES_SHIMMED_ + typedef unsigned char u8; + typedef unsigned int u32; +#endif + +/* hal_status_t / success sentinel. */ +typedef int hal_status_t; +#ifndef HAL_OK + #define HAL_OK 0 +#endif + +/* ---- Engine + AES secure-key ops (hal_crypto.h) ---- */ +static inline int hal_crypto_engine_init(void) { return 0; } +static inline int hal_crypto_aes_gcm_sk_init(u8 key_num, const u32 keylen) + { (void)key_num; (void)keylen; return 0; } +static inline int hal_crypto_aes_gcm_encrypt(const u8* msg, const u32 msglen, + const u8* iv, const u8* aad, const u32 aadlen, u8* pResult, u8* pTag) + { (void)msg; (void)msglen; (void)iv; (void)aad; (void)aadlen; + (void)pResult; (void)pTag; return 0; } +static inline int hal_crypto_aes_gcm_decrypt(const u8* msg, const u32 msglen, + const u8* iv, const u8* aad, const u32 aadlen, u8* pResult, u8* pTag) + { (void)msg; (void)msglen; (void)iv; (void)aad; (void)aadlen; + (void)pResult; (void)pTag; return 0; } +static inline int hal_crypto_aes_ecb_sk_init(u8 key_num, const u32 keylen) + { (void)key_num; (void)keylen; return 0; } +static inline int hal_crypto_aes_ecb_encrypt(const u8* msg, const u32 msglen, + const u8* iv, const u32 ivlen, u8* pResult) + { (void)msg; (void)msglen; (void)iv; (void)ivlen; (void)pResult; return 0; } +static inline int hal_crypto_aes_ecb_decrypt(const u8* msg, const u32 msglen, + const u8* iv, const u32 ivlen, u8* pResult) + { (void)msg; (void)msglen; (void)iv; (void)ivlen; (void)pResult; return 0; } + +/* ---- HKDF secure key-ladder (hal_hkdf.h) ---- */ +static inline hal_status_t hal_hkdf_hmac_sha256_secure_init(const u8 crypto_sel) + { (void)crypto_sel; return HAL_OK; } +static inline hal_status_t hal_hkdf_extract_secure_all(const u8 sk_idx, + const u8 wb_idx, const u8* msg_buf) + { (void)sk_idx; (void)wb_idx; (void)msg_buf; return HAL_OK; } +static inline hal_status_t hal_hkdf_expand_secure_all(const u8 sk_idx, + const u8 wb_idx, const u8* nonce) + { (void)sk_idx; (void)wb_idx; (void)nonce; return HAL_OK; } + +#endif /* WOLFSSL_AMEBAPRO2_HOST_TEST */ + +#endif /* _WOLFPORT_AMEBAPRO2_SHIM_H_ */ diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index 9635e1a6cfd..844bfd6f6a1 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -110,6 +110,7 @@ noinst_HEADERS+= \ wolfssl/wolfcrypt/port/silabs/silabs_random.h \ wolfssl/wolfcrypt/port/st/stm32.h \ wolfssl/wolfcrypt/port/st/stsafe.h \ + wolfssl/wolfcrypt/port/realtek/amebapro2.h \ wolfssl/wolfcrypt/port/tropicsquare/tropic01.h \ wolfssl/wolfcrypt/port/Espressif/esp-sdk-lib.h \ wolfssl/wolfcrypt/port/Espressif/esp32-crypt.h \ diff --git a/wolfssl/wolfcrypt/port/realtek/amebapro2.h b/wolfssl/wolfcrypt/port/realtek/amebapro2.h new file mode 100644 index 00000000000..e0dd8740576 --- /dev/null +++ b/wolfssl/wolfcrypt/port/realtek/amebapro2.h @@ -0,0 +1,117 @@ +/* amebapro2.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 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* RealTek AmebaPro2 (RTL8735B) HUK (Hardware Unique Key) crypto-callback port. + * + * Binds keys to the silicon HUK via the AmebaPro2 HAL crypto engine: a 256-bit + * "seed" is run through the HAL HKDF key-ladder against the HUK to land a + * device-bound working key in a secure key-storage slot; AES (GCM/ECB/CBC/CTR) + * then runs from that slot without the key ever entering software. ECDSA sign + * binds a HUK-wrapped private scalar. The port is a pure crypto-callback device: + * it adds no wolfSSL core API or struct fields -- AES reads its seed from the + * standard aes->devKey, and ECDSA reads a wc_AmebaPro2_EccKey (below) the caller + * attaches via the standard ecc_key->devCtx. + */ + +#ifndef _WOLFPORT_AMEBAPRO2_H_ +#define _WOLFPORT_AMEBAPRO2_H_ + +#include + +#ifdef WOLFSSL_REALTEK_HUK + +/* Transparent HUK crypto flows through the crypto-callback framework. */ +#if !defined(WOLF_CRYPTO_CB) + #error "WOLFSSL_REALTEK_HUK requires WOLF_CRYPTO_CB (crypto callback dispatch)" +#endif + +/* Crypto-callback device id for transparent HUK crypto. Distinct from the + * STM32 DHUK device (808). Override before include if it collides. */ +#ifndef WC_HUK_DEVID + #define WC_HUK_DEVID 809 +#endif + +/* Secure key-storage slot numbers used by the key ladder (HKDF_KEY_STG_IDX_* + * in the HAL). The HUK source slot is HUK1 (==1); HUK2 is 2; slots 3..8 are + * general write-back slots. The PRK lands in one slot, the derived working key + * in another -- the working-key slot is the one AES *_sk_init references. All + * overridable from user_settings. */ +#ifndef WC_AMEBAPRO2_HUK_SK_IDX + #define WC_AMEBAPRO2_HUK_SK_IDX 1 /* HKDF_KEY_STG_IDX_HUK1 */ +#endif +#ifndef WC_AMEBAPRO2_HKDF_PRK_IDX + #define WC_AMEBAPRO2_HKDF_PRK_IDX 3 /* HKDF_KEY_STG_IDX_3 */ +#endif +#ifndef WC_AMEBAPRO2_DERIVED_WB_IDX + #define WC_AMEBAPRO2_DERIVED_WB_IDX 4 /* HKDF_KEY_STG_IDX_4 */ +#endif + +/* crypto_sel for hal_hkdf_hmac_sha256_secure_init: HKDF_CRYPTO_HW_SEL_EN. */ +#ifndef WC_AMEBAPRO2_HKDF_CRYPTO_SEL + #define WC_AMEBAPRO2_HKDF_CRYPTO_SEL 0 +#endif + +/* Max wrapped-scalar blob the HUK ECDSA sign path will unwrap (a multiple of 16 + * covering up to P-521: 66 padded to 80, plus headroom). */ +#ifndef WC_AMEBAPRO2_MAX_WRAPPED + #define WC_AMEBAPRO2_MAX_WRAPPED 96 +#endif + +/* HUK-bound ECC private key context for the ECDSA sign path. Instead of a new + * wolfSSL core API, the caller attaches one of these to a WC_HUK_DEVID ecc_key + * via the standard crypto-callback context pointer (key->devCtx) before signing: + * + * wc_AmebaPro2_EccKey hk = { seed, 32, wrapped, wrappedLen, plainLen }; + * wc_ecc_init_ex(&key, NULL, WC_HUK_DEVID); + * wc_ecc_set_curve(&key, plainLen, curveId); + * key.devCtx = &hk; + * wc_ecc_sign_hash(...); (unwraps + signs under the HUK) + * + * The pointed-at buffers must stay valid for the key's lifetime (borrowed, not + * copied). seed is the 256-bit HKDF input; wrapped is the private scalar + * AES-wrapped under the HUK-derived key (length a multiple of 16, <= + * WC_AMEBAPRO2_MAX_WRAPPED); plainLen is the real scalar size (e.g. 32 P-256). */ +typedef struct wc_AmebaPro2_EccKey { + const byte* seed; + word32 seedSz; + const byte* wrapped; + word32 wrappedLen; + word32 plainLen; +} wc_AmebaPro2_EccKey; + +#ifdef __cplusplus + extern "C" { +#endif + +/* Register / unregister the AmebaPro2 HUK device. After registering at + * WC_HUK_DEVID, set an object's devId to it at init (e.g. + * wc_AesInit(&aes, NULL, WC_HUK_DEVID)) to route transparently to the HUK + * crypto engine. Returns 0 on success. */ +WOLFSSL_API int wc_AmebaPro2_HukRegister(int devId); +WOLFSSL_API void wc_AmebaPro2_HukUnRegister(int devId); + +#ifdef __cplusplus + } +#endif + +#endif /* WOLFSSL_REALTEK_HUK */ + +#endif /* _WOLFPORT_AMEBAPRO2_H_ */ diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index b4603df6058..a69a8e37bba 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -131,6 +131,7 @@ if(CONFIG_WOLFSSL) zephyr_library_sources(${ZEPHYR_CURRENT_MODULE_DIR}/wolfcrypt/src/port/psa/psa_hash.c) zephyr_library_sources(${ZEPHYR_CURRENT_MODULE_DIR}/wolfcrypt/src/port/psa/psa_pkcbs.c) zephyr_library_sources(${ZEPHYR_CURRENT_MODULE_DIR}/wolfcrypt/src/port/st/stm32.c) + zephyr_library_sources(${ZEPHYR_CURRENT_MODULE_DIR}/wolfcrypt/src/port/realtek/amebapro2.c) if(CONFIG_WOLFCRYPT_ARMASM) # tested with board: "qemu_kvm_arm64"