From 103b75cc76ed9311a7d54174f91f51adb6cc3405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:03 +0200 Subject: [PATCH 01/16] nxp: add the EdgeLock crypto callback port with SHA-256 offload Routes wolfCrypt through the on-chip EdgeLock subsystem (ELS) on RW612 via the crypto callback interface, with SHA-256 as the first offloaded primitive. Anything the hardware does not implement declines to software, so enabling the port never removes functionality. Three properties of the hardware shape the design. ELS is one peripheral with global busy state: every operation is an _Async call followed by a wait, and nothing may start between the two, so one lock is held across each pair. ELS answers a rejected request by signalling the tamper controller, which resets the SoC, rather than by returning an error. Validation therefore happens in software before the call: getting it wrong reboots the device. mcuxClEls_WaitForOperation() busy-spins with no timeout, and would do so holding that lock. Completion is taken from the ELS interrupt instead, wired with IRQ_CONNECT because the IRQ has no devicetree node, with polling as the fallback for ISR context and for anything before the IRQ is armed. A late interrupt degrades to the synchronous wait rather than cancelling: measured, MCUXCLELS_RESET_CANCEL on an in-flight operation reboots the SoC. For SHA-256 the engine round-trips its intermediate state (hashoe writes it, hashld reloads it), so the state lives in the caller's wc_Sha256 and the lock covers one call. Holding it from update to final deadlocks TLS 1.3, which keeps several transcript hashes alive at once. ELS never pads, so the port carries the residual block and appends the padding itself. Registration happens inside wolfCrypt_Init(), which is also what zeroes the callback device table. Zephyr gains CONFIG_WOLFSSL_SYS_INIT to run that at boot from POST_KERNEL rather than leaving it to whichever library call happens to be first, which starts to matter once initialization brings up a peripheral. It defaults on when the port is enabled and stays available on its own, since nothing about it is specific to this hardware. The hook calls wolfSSL_Init() so the TLS layer is initialized too, and wolfCrypt_Init() in a WOLFCRYPT_ONLY build, where wolfSSL_Init() is not compiled. The port's Kconfig symbol is restricted to SOC_SERIES_RW6XX, the only series for which els_pkc exports src/platforms/, where mcux_els.h lives. --- ChangeLog.md | 2 + wolfcrypt/src/include.am | 1 + wolfcrypt/src/port/nxp/README.md | 81 +++ wolfcrypt/src/port/nxp/els_pkc_port.c | 732 ++++++++++++++++++++++ wolfcrypt/src/wc_port.c | 19 + wolfssl/wolfcrypt/include.am | 1 + wolfssl/wolfcrypt/port/nxp/els_pkc_port.h | 69 ++ wolfssl/wolfcrypt/settings.h | 21 + zephyr/CMakeLists.txt | 10 + zephyr/Kconfig | 38 ++ zephyr/user_settings.h | 11 + zephyr/zephyr_init.c | 55 +- 12 files changed, 1034 insertions(+), 6 deletions(-) create mode 100644 wolfcrypt/src/port/nxp/els_pkc_port.c create mode 100644 wolfssl/wolfcrypt/port/nxp/els_pkc_port.h diff --git a/ChangeLog.md b/ChangeLog.md index f3107ffb0bf..4555d213a0f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -191,6 +191,8 @@ * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev +* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. + ## Fixes * **Fix (sniffer could not decrypt Encrypt-Then-MAC or X25519 sessions)**: the diff --git a/wolfcrypt/src/include.am b/wolfcrypt/src/include.am index 53fa2ca0c45..0586b41d998 100644 --- a/wolfcrypt/src/include.am +++ b/wolfcrypt/src/include.am @@ -90,6 +90,7 @@ EXTRA_DIST += wolfcrypt/src/port/ti/ti-aes.c \ wolfcrypt/src/port/nxp/ksdk_port.c \ wolfcrypt/src/port/nxp/dcp_port.c \ wolfcrypt/src/port/nxp/se050_port.c \ + wolfcrypt/src/port/nxp/els_pkc_port.c \ wolfcrypt/src/port/nxp/README.md \ wolfcrypt/src/port/nxp/casper_port.c \ wolfcrypt/src/port/nxp/hashcrypt_port.c \ diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 55f7351a643..8936237b65f 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -48,6 +48,87 @@ NOTE: Both can be defined with no problem. For details on wolfSSL integration with NXP SE050, see [README_SE050.md](./README_SE050.md). +## NXP EdgeLock (ELS) + +`els_pkc_port.c` offloads wolfCrypt to the EdgeLock subsystem found on the +RW612 and related parts, through the crypto callback interface. The ELS +peripheral serves SHA-256. + +Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` +and completed in software, so an unsupported algorithm or key size costs +performance, never correctness. A hardware failure is deliberately not +treated as a decline: it is reported, so a caller that ignores the return +cannot walk away with a confident bad result. + +`wolfCrypt_Init()` brings the subsystem up and registers the callback, and +`wc_CryptoCb_DefaultDevID()` answers `WOLFSSL_ELS_PKC_DEVID`, so a context +created without an explicit device still reaches the hardware. +`WC_NO_DEFAULT_DEVID` turns that off and leaves routing to the caller. + +### Hardware behaviour worth knowing + +**A rejected request resets the SoC, by default.** ELS answers an invalid key +permission or a failed unwrap by signalling the Intrusion and Tamper Response +Controller, which on its reset-on-tamper default drives a chip reset rather +than returning an error. The port validates its arguments in software before +issuing a command, and does not cancel an operation already in flight, +because `mcuxClEls_Reset_Async(MCUXCLELS_RESET_CANCEL)` is itself a tamper +event. An integration that has retargeted the ITRC can define +`WOLFSSL_ELS_PKC_ALLOW_CANCEL`, and a late completion interrupt then cancels +and fails the operation instead of degrading to the vendor's synchronous +wait. Do not probe the hardware with deliberately malformed references. + +**ELS is a system-wide peripheral, and the port's lock covers only wolfSSL.** +Every operation is an `_Async` call followed by +`mcuxClEls_WaitForOperation()`, serialized under one mutex. An application +calling `mcuxClEls` directly, or a second OS task, can drive the peripheral +behind wolfSSL's back, and an integration that mixes the two has to arbitrate +above both. Reaching the hardware through wolfPSA does not raise this: that +provider is itself a wolfCrypt caller, so it arrives through the same lock. + +**Completion is interrupt-driven where a kernel is available**, with the +vendor busy-spin as the fallback for ISR context and pre-IRQ bring-up. Tune +it with `WOLFSSL_ELS_PKC_TIMEOUT_MS`, `WOLFSSL_ELS_PKC_IRQ_PRIO`, and +`WOLFSSL_ELS_PKC_SPIN_US` - the last is how long a completion is spun for +before the thread sleeps, and zero never spins. + +**Offload state lives in the caller's object.** A hash keeps its ELS state in +the same `wc_Sha256` fields the software implementation would have used, so +nothing is allocated, a struct copy duplicates a context correctly, and the +port needs neither the copy nor the free crypto-callback hook. + +### Vendor library + +The port depends on NXP's CLNS library, `els_pkc`, which ships as an +MCUXpresso SDK component and is published at +. wolfSSL neither builds nor vendors it: the +port calls into CLNS and the application links it, the same arrangement the +SE050 port uses. A library built with this port therefore carries unresolved +`mcuxCl*` references by design. + +`els_pkc` is under NXP's proprietary `LA_OPT_Online Code Hosting` license, +not an open source license, and its clause 3.7 forbids subjecting it to a +license requiring source disclosure. Fetch it at build time; do not copy it +into a wolfSSL tree. + +### Building + +- **Zephyr.** The wolfSSL Zephyr module compiles the port like every other + wolfCrypt port and turns it on with `CONFIG_WOLFSSL_ELS_PKC=y`, alongside + `CONFIG_MCUX_ELS_PKC=y` for the vendor headers. When a settings file is + supplied through `CONFIG_WOLFSSL_SETTINGS_FILE`, the feature Kconfigs do + not apply and `WOLFSSL_ELS_PKC` has to be defined in that file instead. + +Everything in the port is gated on `WOLFSSL_ELS_PKC`. Without that macro the +file compiles to an empty translation unit and reaches no vendor header, +which is why it can sit in the build unconditionally. It also means a +mis-wired build succeeds with the port silently absent, so verify by symbol +rather than by object file: + +```sh +nm src/.libs/libwolfssl.a | grep wc_ElsPkc +``` + ## Support For questions please email support@wolfssl.com diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c new file mode 100644 index 00000000000..9cc0494ebdb --- /dev/null +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -0,0 +1,732 @@ +/* els_pkc_port.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* wolfCrypt crypto-callback port for the NXP EdgeLock secure subsystem (ELS). + * ELS is one peripheral with global busy state, so every _Async call and its + * WaitForOperation run under the lock below. That lock serializes wolfSSL's + * own callers only; any other ELS user has to be arbitrated above both. */ + +#include + +#ifdef WOLFSSL_ELS_PKC + +/* On Zephyr the vendor headers arrive with the els_pkc module, which is a + * separate choice from asking for the port. Name the option that was left out + * rather than fail on a missing mcuxClEls.h. */ +#if defined(__ZEPHYR__) && !defined(CONFIG_MCUX_ELS_PKC) + #error "WOLFSSL_ELS_PKC requires the NXP els_pkc module (CONFIG_MCUX_ELS_PKC=y)" +#endif + +#include +#include +#include + +#include +#include + +/* per-SoC bring-up helper; els_pkc provides one for each supported platform */ +#include + +#ifdef WOLFSSL_ZEPHYR + #include + #include +#endif + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +/* --------------------------------------------------------------------------- + * Peripheral serialization + * ------------------------------------------------------------------------ */ + +static wolfSSL_Mutex elsLock; +/* Read from the crypto-callback path, written by init/cleanup. Volatile so a + * compiler cannot cache the flag across the mutex operations that order them. */ +static volatile int elsLockInit = 0; +static volatile int elsRegistered = 0; +/* Set only once the peripheral is actually up. */ +static volatile int elsReady = 0; + +static int ElsLock(void) +{ + /* elsReady as well as elsLockInit: the mutex is created before the + * peripheral is enabled, so a failed wc_ElsPkc_Init() would otherwise let + * the direct entry points issue commands to a block still in reset. */ + if (!elsLockInit || !elsReady) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + if (wc_LockMutex(&elsLock) != 0) { + return WC_NO_ERR_TRACE(BAD_MUTEX_E); + } + return 0; +} + +static void ElsUnlock(void) +{ + if (elsLockInit) { + (void)wc_UnLockMutex(&elsLock); + } +} + +/* mcuxClEls_WaitForOperation() busy-spins with no timeout, and does so while + * this port holds the ELS lock. Where a kernel is available the thread sleeps + * on the completion interrupt instead and the vendor call only harvests the + * status. The spin stays the fallback for ISR context and pre-IRQ bring-up. + * + * A late interrupt degrades to that synchronous wait, because cancelling is a + * tamper event on the default ITRC configuration. An integration that has + * retargeted the ITRC can define WOLFSSL_ELS_PKC_ALLOW_CANCEL to cancel and + * fail the operation instead. */ + +#ifndef WOLFSSL_ELS_PKC_TIMEOUT_MS + #define WOLFSSL_ELS_PKC_TIMEOUT_MS 1000 +#endif + +/* A short symmetric operation finishes in less time than the pair of thread + * switches a blocking wait costs, and the scheduler latency makes the result + * vary run to run besides. Spin this long first; zero never spins. */ +#ifndef WOLFSSL_ELS_PKC_SPIN_US + #define WOLFSSL_ELS_PKC_SPIN_US 128 +#endif + +#ifdef WOLFSSL_ZEPHYR + +static struct k_sem elsDone; +static volatile int elsIrqReady = 0; + +static void ElsIsr(const void* arg) +{ + mcuxClEls_InterruptOptionRst_t rst; + + (void)arg; + + /* acknowledge at the peripheral before releasing the waiter, so a fast + * follow-up operation cannot see a stale flag */ + rst.word.value = 0u; + rst.bits.elsint = MCUXCLELS_ELS_RESET_CLEAR; + (void)mcuxClEls_ResetIntFlags(rst); + + k_sem_give(&elsDone); +} + +/* Arm the completion interrupt. Called once from wc_ElsPkc_Init(). */ +static int ElsIrqInit(void) +{ + mcuxClEls_InterruptOptionEn_t en; + + if (elsIrqReady) { + return 0; + } + + k_sem_init(&elsDone, 0, 1); + + IRQ_CONNECT(ELS_IRQn, WOLFSSL_ELS_PKC_IRQ_PRIO, ElsIsr, NULL, 0); + irq_enable(ELS_IRQn); + + en.word.value = 0u; + en.bits.elsint = MCUXCLELS_ELS_INTERRUPT_ENABLE; + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_SetIntEnableFlags(en)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_SetIntEnableFlags) != t) || + (MCUXCLELS_STATUS_OK != r)) { + irq_disable(ELS_IRQn); + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + elsIrqReady = 1; + + return 0; +} + +/* Can this context afford to sleep? */ +static int ElsCanSleep(void) +{ + return elsIrqReady && !k_is_in_isr(); +} + +/* Take the completion semaphore without blocking, for as long as the spin + * budget allows. Returns whether it was taken. */ +static int ElsSpinForDone(void) +{ +#if WOLFSSL_ELS_PKC_SPIN_US > 0 + uint32_t start = k_cycle_get_32(); + uint32_t budget = k_us_to_cyc_ceil32(WOLFSSL_ELS_PKC_SPIN_US); + + do { + if (k_sem_take(&elsDone, K_NO_WAIT) == 0) { + return 1; + } + } while ((k_cycle_get_32() - start) < budget); +#endif + + return 0; +} + +#endif /* WOLFSSL_ZEPHYR */ + +#if defined(WOLFSSL_ELS_PKC_ALLOW_CANCEL) && defined(WOLFSSL_ZEPHYR) +/* Abandon the operation the caller is waiting on. + * + * Out of reach unless the integration has retargeted the Intrusion and Tamper + * Response Controller: on the reset-on-tamper default this call is itself a + * tamper event and reboots the SoC rather than returning. */ +static void ElsCancel(void) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, + mcuxClEls_Reset_Async(MCUXCLELS_RESET_CANCEL)); + (void)r; + (void)t; + MCUX_CSSL_FP_FUNCTION_CALL_END(); +} +#endif + +/* Must be called with the lock held, and always paired with the _Async that + * preceded it. */ +static int ElsWait(void) +{ + int ret; +#ifdef WOLFSSL_ZEPHYR + int timedOut = 0; +#ifdef WOLFSSL_ELS_PKC_ALLOW_CANCEL + int cancelled = 0; +#endif + + if (ElsCanSleep()) { + if (!ElsSpinForDone() && + k_sem_take(&elsDone, K_MSEC(WOLFSSL_ELS_PKC_TIMEOUT_MS)) != 0) { + timedOut = 1; + WOLFSSL_MSG("els_pkc: completion interrupt late"); +#ifdef WOLFSSL_ELS_PKC_ALLOW_CANCEL + /* Cancel, then fall through to the vendor wait below: the reset + * is itself an async command, and returning while it is still in + * flight would fail the next caller's operation with + * CANNOT_INTERRUPT. The wait also reaches the drain below. */ + ElsCancel(); + cancelled = 1; +#endif + } + } +#endif + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(res, tok, + mcuxClEls_WaitForOperation(MCUXCLELS_ERROR_FLAGS_CLEAR)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_WaitForOperation) != tok) || + (MCUXCLELS_STATUS_OK != res)) { + ret = WC_NO_ERR_TRACE(WC_HW_E); + } + else { + ret = 0; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + +#ifdef WOLFSSL_ZEPHYR + /* The interrupt for this operation lands eventually. Drop it, or it would + * satisfy the next wait before that operation had finished. */ + if (timedOut) { + k_sem_reset(&elsDone); + } +#ifdef WOLFSSL_ELS_PKC_ALLOW_CANCEL + /* A cancelled operation produced no result, whatever the wait reported. */ + if (cancelled) { + ret = WC_NO_ERR_TRACE(WC_HW_E); + } +#endif +#endif + + return ret; +} + +/* els_pkc ships this helper for every platform it supports, so the port stays + * SoC-agnostic. */ +static int ElsEnable(void) +{ + if (ELS_PowerDownWakeupInit(ELS) != 0) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + + return 0; +} + +/* --------------------------------------------------------------------------- + * SHA-256 / SHA-384 / SHA-512 + * ------------------------------------------------------------------------ */ + +/* The engine round-trips its intermediate state (hashoe writes it, hashld + * reloads it), so the state lives in the caller's object and the lock is held + * for one call - holding it from update to final deadlocks TLS 1.3, which + * keeps several transcript hashes alive at once. ELS never pads. */ + +#ifndef NO_SHA256 + +#define ELS_SHA256_BLOCK MCUXCLELS_HASH_BLOCK_SIZE_SHA_256 +#define ELS_SHA256_STATE MCUXCLELS_HASH_STATE_SIZE_SHA_256 + +#define ELS_HASH_MAX_BLOCK ELS_SHA256_BLOCK + +/* The offload keeps its state in the caller's hash object, in the very fields + * the software implementation would have used: wolfCrypt sizes digest[] and + * buffer[] exactly as ELS sizes its intermediate state and its block. Nothing + * is allocated, a struct copy duplicates a hash correctly, and there is no + * context to reclaim - which is why this port needs neither the copy nor the + * free crypto-callback hook. + * + * The invariant that makes it safe: the port takes an object over at its first + * update, before any byte has been absorbed, and never hands one back. A + * declined hash is therefore always one whose software state is untouched. */ + +/* Equalities, not bounds: a mismatch would overrun the caller's object. */ +#ifndef NO_SHA256 +wc_static_assert(WC_SHA256_DIGEST_SIZE == ELS_SHA256_STATE); +wc_static_assert(WC_SHA256_BLOCK_SIZE == ELS_SHA256_BLOCK); +#endif + +/* The fields above exist only in the software arm of the #ifdef chain in + * sha256.h; another SHA-2 port replaces them with its own. */ +#if defined(FREESCALE_LTC_SHA) || defined(STM32_HASH_SHA2) || \ + defined(WOLFSSL_SILABS_SE_ACCEL) || defined(WOLFSSL_IMXRT_DCP) || \ + defined(PSOC6_HASH_SHA2) || \ + (defined(WOLFSSL_SE050) && defined(WOLFSSL_SE050_HASH)) || \ + (defined(WOLFSSL_HAVE_PSA) && !defined(WOLFSSL_PSA_NO_HASH)) || \ + defined(WOLFSSL_TI_HASH) || defined(WOLFSSL_AFALG_HASH) || \ + (defined(WOLFSSL_IMX6_CAAM) && !defined(WOLFSSL_QNX_CAAM)) || \ + ((defined(WOLFSSL_RENESAS_TSIP_TLS) || \ + defined(WOLFSSL_RENESAS_TSIP_CRYPTONLY)) && \ + !defined(NO_WOLFSSL_RENESAS_TSIP_CRYPT_HASH)) || \ + ((defined(WOLFSSL_RENESAS_SCEPROTECT) || defined(WOLFSSL_RENESAS_RSIP)) && \ + !defined(NO_WOLFSSL_RENESAS_FSPSM_HASH)) || \ + defined(WOLFSSL_RENESAS_RX64_HASH) + #error "WOLFSSL_ELS_PKC hash offload conflicts with another SHA-2 port" +#endif + +/* Parked in devCtx as a tagged scalar rather than a pointer, so that a struct + * copy carries it and a free has nothing to release. */ +#define ELS_HASH_OWNED ((wc_ptr_t)0x1) +#define ELS_HASH_STARTED ((wc_ptr_t)0x2) /* the engine has absorbed a block */ +#define ELS_HASH_FAILED ((wc_ptr_t)0x4) /* a block never reached it */ + +/* FIPS 180-4 initial hash values, in the same host-order word form wolfCrypt + * keeps digest[] in. ElsHashFinal() restores them so the object is left as a + * software Final would leave it - see there. */ +#ifndef NO_SHA256 +static const word32 elsSha256Iv[8] = { + 0x6a09e667U, 0xbb67ae85U, 0x3c6ef372U, 0xa54ff53aU, + 0x510e527fU, 0x9b05688cU, 0x1f83d9abU, 0x5be0cd19U +}; +#endif + +/* The caller's object, bound at the dispatch site so the offload below does + * not care which of the three hashes it is driving. */ +typedef struct ElsHashObj { + void** devCtx; + byte* state; /* the object's digest[] */ + byte* buf; /* the object's buffer[] */ + word32* buffered; /* the object's buffLen */ + void* lo; /* the object's loLen */ + void* hi; /* the object's hiLen */ + const void* iv; /* the software initial state, restored at Final */ + word32 stateSz; + word32 blockSz; + word32 lenSz; /* width of the length field the padding ends with */ + byte mode; /* MCUXCLELS_HASH_MODE_* */ + byte wide; /* loLen/hiLen are word64, not word32 */ +} ElsHashObj; + +static word64 ElsHashTotal(const ElsHashObj* o) +{ + if (o->wide) { + return *(const word64*)o->lo; + } + + return ((word64)*(const word32*)o->hi << 32) | + (word64)*(const word32*)o->lo; +} + +static void ElsHashTotalSet(const ElsHashObj* o, word64 total) +{ + if (o->wide) { + *(word64*)o->lo = total; + } + else { + *(word32*)o->lo = (word32)total; + *(word32*)o->hi = (word32)(total >> 32); + } +} + +#ifndef NO_SHA256 +static void ElsHashBindSha256(ElsHashObj* o, wc_Sha256* sha) +{ + o->devCtx = &sha->devCtx; + o->state = (byte*)sha->digest; + o->buf = (byte*)sha->buffer; + o->buffered = &sha->buffLen; + o->lo = &sha->loLen; + o->hi = &sha->hiLen; + o->iv = elsSha256Iv; + o->stateSz = (word32)ELS_SHA256_STATE; + o->blockSz = (word32)ELS_SHA256_BLOCK; + o->lenSz = WC_SHA256_BLOCK_SIZE - WC_SHA256_PAD_SIZE; + o->mode = MCUXCLELS_HASH_MODE_SHA_256; + o->wide = 0; +} +#endif + +/* Feed whole blocks to the engine, carrying the running state in and out. + * Caller must hold the lock. len must be a multiple of the block size. */ +static int ElsHashBlocks(const ElsHashObj* o, const byte* in, word32 len) +{ + mcuxClEls_HashOption_t opt; + wc_ptr_t st = (wc_ptr_t)*o->devCtx; + int ret; + + if (len == 0) { + return 0; + } + + opt.word.value = 0u; + opt.bits.hashmd = o->mode; + opt.bits.hashoe = MCUXCLELS_HASH_OUTPUT_ENABLE; + if (st & ELS_HASH_STARTED) { + opt.bits.hashini = MCUXCLELS_HASH_INIT_DISABLE; + opt.bits.hashld = MCUXCLELS_HASH_LOAD_ENABLE; + } + else { + opt.bits.hashini = MCUXCLELS_HASH_INIT_ENABLE; + opt.bits.hashld = MCUXCLELS_HASH_LOAD_DISABLE; + } + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, + mcuxClEls_Hash_Async(opt, in, len, o->state)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_Hash_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + *o->devCtx = (void*)(st | ELS_HASH_STARTED); + + ret = ElsWait(); + + return ret; +} + +/* Absorb into the caller's object, taking it over on the first call. */ +static int ElsHashUpdate(ElsHashObj* o, const byte* in, word32 inSz) +{ + word32 take, whole; + int ret; + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + if (*o->devCtx == NULL) { + /* The engine compresses the first block from the standard IV, so only + * a pristine object can be taken over: SHA-512/224 and SHA-512/256 + * update as plain WC_HASH_TYPE_SHA512, and software may have begun. */ + if (*o->buffered != 0 || ElsHashTotal(o) != 0 || + XMEMCMP(o->state, o->iv, o->stateSz) != 0) { + ElsUnlock(); + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + *o->devCtx = (void*)ELS_HASH_OWNED; + } + else if ((wc_ptr_t)*o->devCtx & ELS_HASH_FAILED) { + ElsUnlock(); + return WC_NO_ERR_TRACE(WC_HW_E); + } + + ElsHashTotalSet(o, ElsHashTotal(o) + inSz); + + /* Hold the last block back so Final can absorb it together with the + * padding: a command costs far more than the bytes in it, and a message + * ending on a block boundary would otherwise need one all to itself. */ + if (*o->buffered > 0) { + take = o->blockSz - *o->buffered; + if (take > inSz) { + take = inSz; + } + XMEMCPY(o->buf + *o->buffered, in, take); + *o->buffered += take; + in += take; + inSz -= take; + + if (*o->buffered == o->blockSz && inSz > 0) { + ret = ElsHashBlocks(o, o->buf, o->blockSz); + if (ret != 0) { + goto out; + } + *o->buffered = 0; + } + } + + /* whole blocks straight from the caller's buffer, less the last one */ + if (inSz > o->blockSz) { + whole = ((inSz - 1u) / o->blockSz) * o->blockSz; + ret = ElsHashBlocks(o, in, whole); + if (ret != 0) { + goto out; + } + in += whole; + inSz -= whole; + } + + /* keep whatever is left for next time */ + if (inSz > 0) { + XMEMCPY(o->buf + *o->buffered, in, inSz); + *o->buffered += inSz; + } + +out: + if (ret != 0) { + /* the length now counts bytes the engine never saw, so anything this + * object produces from here is wrong */ + *o->devCtx = (void*)((wc_ptr_t)*o->devCtx | ELS_HASH_FAILED); + } + ElsUnlock(); + + return ret; +} + +/* Pad, absorb the tail, and take the digest from the running state. */ +static int ElsHashFinal(ElsHashObj* o, byte* digest, word32 digestSz) +{ + ALIGN32 byte tail[2u * ELS_HASH_MAX_BLOCK]; + word32 buffered, tailSz; + word64 bitLen; + int ret; + int i; + + if (*o->devCtx == NULL) { + /* never taken over, so software holds the whole message */ + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + if ((wc_ptr_t)*o->devCtx & ELS_HASH_FAILED) { + ElsUnlock(); + return WC_NO_ERR_TRACE(WC_HW_E); + } + + /* residual, then 0x80, zeros, and a big-endian bit count of lenSz bytes. A + * second block is needed when the remainder leaves no room for it. */ + buffered = *o->buffered; + XMEMSET(tail, 0, sizeof(tail)); + if (buffered > 0) { + XMEMCPY(tail, o->buf, buffered); + } + tail[buffered] = 0x80; + tailSz = (buffered + 1u + o->lenSz > o->blockSz) + ? (2u * o->blockSz) : o->blockSz; + + bitLen = ElsHashTotal(o) * 8u; + for (i = 0; i < 8; i++) { + tail[tailSz - 1u - (word32)i] = (byte)(bitLen >> (8 * i)); + } + + ret = ElsHashBlocks(o, tail, tailSz); + if (ret == 0) { + /* after the padded tail the running state IS the digest, truncated + * for the modes whose output is shorter than the state */ + XMEMCPY(digest, o->state, digestSz); + } + + /* Software's Final ends with a re-init; the callback arm returns before + * it. Do it here, or an object reused after the device is unregistered + * would resume in software from this finished state and hand back a wrong + * digest with a success code. */ + XMEMCPY(o->state, o->iv, o->stateSz); + *o->devCtx = NULL; + *o->buffered = 0; + ElsHashTotalSet(o, 0); + + ElsUnlock(); + ForceZero(tail, sizeof(tail)); + + return ret; +} + +#endif /* !NO_SHA256 */ + +/* --------------------------------------------------------------------------- + * Dispatch + * ------------------------------------------------------------------------ */ + +int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +#ifndef NO_SHA256 + ElsHashObj hobj; +#endif + + (void)devId; + (void)ctx; + + if (info == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + switch (info->algo_type) { + + +#ifndef NO_SHA256 + case WC_ALGO_TYPE_HASH: + /* update passes (data, len, NULL) and final passes (NULL, 0, + * digest), never both. */ + switch (info->hash.type) { + #if !defined(NO_SHA256) + case WC_HASH_TYPE_SHA256: + if (info->hash.sha256 == NULL) { + break; + } + ElsHashBindSha256(&hobj, info->hash.sha256); + if (info->hash.digest != NULL) { + ret = ElsHashFinal(&hobj, info->hash.digest, + WC_SHA256_DIGEST_SIZE); + } + else if (info->hash.in != NULL) { + ret = ElsHashUpdate(&hobj, info->hash.in, + info->hash.inSz); + } + else { + /* update of zero bytes with no buffer */ + ret = 0; + } + break; + #endif + default: + break; + } + break; +#endif + + default: + /* Anything not claimed above falls back to software. */ + break; + } + + return ret; +} + +/* --------------------------------------------------------------------------- + * Lifecycle + * ------------------------------------------------------------------------ */ + +int wc_ElsPkc_Init(void) +{ + int ret; + + /* Init is expected from a single boot-time context (a Zephyr SYS_INIT + * hook, or the application before it starts threads). It is deliberately + * not safe to call concurrently with itself: creating the mutex is the + * very thing that would have to be protected. */ + if (!elsLockInit) { + if (wc_InitMutex(&elsLock) != 0) { + return WC_NO_ERR_TRACE(BAD_MUTEX_E); + } + elsLockInit = 1; + } + + /* Bring the subsystem out of reset and enable it. Without this the first + * offload would drive a disabled peripheral. Safe to repeat. */ + ret = ElsEnable(); + if (ret != 0) { + WOLFSSL_MSG("els_pkc: enable failed"); + return ret; + } + elsReady = 1; + +#ifdef WOLFSSL_ZEPHYR + /* after ElsEnable(): the peripheral must be clocked and out of reset + * before its interrupt configuration will stick */ + ret = ElsIrqInit(); + if (ret != 0) { + WOLFSSL_MSG("els_pkc: interrupt setup failed, falling back to polling"); + /* not fatal - ElsWait() polls when the IRQ is not armed */ + } +#endif + + /* Always attempt registration rather than trusting a cached flag: a + * wolfCrypt_Cleanup() elsewhere clears the device table, and a flag saying + * "already registered" would then be a lie. ALREADY_E just means the entry + * survived, which is the outcome we want either way. */ + ret = wc_CryptoCb_RegisterDevice(WOLFSSL_ELS_PKC_DEVID, + wc_ElsPkc_CryptoCb, NULL); + if (ret != 0 && ret != WC_NO_ERR_TRACE(ALREADY_E)) { + WOLFSSL_MSG("els_pkc: RegisterDevice failed"); + /* ElsLock() keys off this: a failed init must leave the port shut, or + * the direct entry points would drive a device that never came up. */ + elsReady = 0; + return ret; + } + + elsRegistered = 1; + + return 0; +} + +int wc_ElsPkc_Cleanup(void) +{ + /* Unregister first, so no new operation can enter the callback, and take + * the lock before tearing it down so an in-flight operation on another + * thread has finished. Freeing the mutex under a running ELS call would + * leave that call unserialized against whatever ran next. */ + if (elsRegistered) { + wc_CryptoCb_UnRegisterDevice(WOLFSSL_ELS_PKC_DEVID); + elsRegistered = 0; + } + + if (elsLockInit) { + /* Close the gate from inside the lock, so a caller that has already + * passed ElsLock()'s test either completed before this point or is + * blocked on the mutex we hold. The mutex outlives cleanup: that + * blocked caller still has to unlock it, and freeing it here would + * destroy a locked mutex and strand the caller's ElsUnlock(). */ + if (wc_LockMutex(&elsLock) == 0) { + elsReady = 0; + (void)wc_UnLockMutex(&elsLock); + } + else { + elsReady = 0; + } + } + +#ifdef WOLFSSL_ZEPHYR + /* Disarm the interrupt: the ISR gives a semaphore this port now considers + * dead, and nothing else owns the ELS IRQ. */ + if (elsIrqReady) { + irq_disable(ELS_IRQn); + elsIrqReady = 0; + } +#endif + + return 0; +} + +#endif /* WOLFSSL_ELS_PKC */ diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index 4c6fbe7bdf6..11ea3bd1a81 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -210,6 +210,10 @@ Threading/Mutex options: #include #endif +#ifdef WOLFSSL_ELS_PKC +#include +#endif + #ifdef WOLFSSL_SCE #include "hal_data.h" #endif @@ -653,6 +657,14 @@ int wolfCrypt_Init(void) } #endif + #ifdef WOLFSSL_ELS_PKC + ret = wc_ElsPkc_Init(); + if (ret != 0) { + WOLFSSL_MSG("EdgeLock (ELS/PKC) init failed"); + WOLFCRYPT_INIT_RAISE_BAD_STATE(); + } + #endif + #ifdef WOLFSSL_ARMASM WOLFSSL_MSG("Using ARM hardware acceleration"); #endif @@ -865,6 +877,13 @@ int wolfCrypt_Cleanup(void) WOLFSSL_SCE_GSCE_HANDLE.p_api->close(WOLFSSL_SCE_GSCE_HANDLE.p_ctrl); #endif + #ifdef WOLFSSL_ELS_PKC + { + int ret2 = wc_ElsPkc_Cleanup(); + if (ret == 0) + ret = ret2; + } + #endif #if defined(WOLFSSL_CAAM) wc_caamFree(); #endif diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index 777a9619377..1ea69657e49 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -106,6 +106,7 @@ noinst_HEADERS+= \ wolfssl/wolfcrypt/port/nxp/dcp_port.h \ wolfssl/wolfcrypt/port/nxp/casper_port.h \ wolfssl/wolfcrypt/port/nxp/hashcrypt_port.h \ + wolfssl/wolfcrypt/port/nxp/els_pkc_port.h \ wolfssl/wolfcrypt/port/xilinx/xil-sha3.h \ wolfssl/wolfcrypt/port/xilinx/xil-versal-glue.h \ wolfssl/wolfcrypt/port/xilinx/xil-versal-trng.h \ diff --git a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h new file mode 100644 index 00000000000..d68bdc42930 --- /dev/null +++ b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h @@ -0,0 +1,69 @@ +/* els_pkc_port.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 + */ + +#ifndef WOLF_CRYPT_ELS_PKC_PORT_H +#define WOLF_CRYPT_ELS_PKC_PORT_H + +#include + +#ifdef WOLFSSL_ELS_PKC + +/* The port IS a crypto callback: it hands back a wc_CryptoInfo* and registers + * against the callback device table, neither of which exists without this. */ +#ifndef WOLF_CRYPTO_CB + #error "WOLFSSL_ELS_PKC requires WOLF_CRYPTO_CB" +#endif + +#include +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/* WOLFSSL_ELS_PKC_DEVID comes from settings.h, which the test and benchmark + * reach without the vendor headers this file needs. An application may still + * register the callback under a different id and bind keys to that instead. */ + +#ifndef WOLFSSL_ELS_PKC_IRQ_PRIO + #define WOLFSSL_ELS_PKC_IRQ_PRIO 2 +#endif + +/* Bring the EdgeLock subsystem up and register the crypto callback. + * wolfCrypt_Init() already does this; call it directly only to re-register + * after a wolfCrypt_Cleanup(). Safe to call more than once. */ +WOLFSSL_API int wc_ElsPkc_Init(void); + +/* Unregister the callback and release the lock. */ +WOLFSSL_API int wc_ElsPkc_Cleanup(void); + +/* The crypto callback itself, exposed so an application can register it under + * a different device id. Registering routes nothing by itself: a context opts + * in by being initialised with this devId, e.g. wc_AesInit(&aes, NULL, + * WOLFSSL_ELS_PKC_DEVID). */ +WOLFSSL_API int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx); + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFSSL_ELS_PKC */ +#endif /* WOLF_CRYPT_ELS_PKC_PORT_H */ diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 310f28639c3..1e9bd2cfd5b 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -455,6 +455,27 @@ #include #endif +/* EdgeLock (ELS + PKC) port: map WC_USE_DEVID before the unmodified test and + * benchmark read it. els_pkc_port.h cannot, as it pulls in vendor headers that + * sit on the wolfCrypt library's include path alone. */ +#ifdef WOLFSSL_ELS_PKC + #ifndef WOLFSSL_ELS_PKC_DEVID + #define WOLFSSL_ELS_PKC_DEVID 0x454C /* 'EL' - an id, not an address */ + #endif + #if !defined(WC_USE_DEVID) && !defined(WC_NO_DEFAULT_DEVID) + #define WC_USE_DEVID WOLFSSL_ELS_PKC_DEVID + #endif + /* The offload leaves the engine's running state in digest[], which is a + * block behind and in the wrong byte order for the raw accessors, and + * those read the object without calling the callback at all. Only when a + * hash arm is actually compiled - the same condition the port uses. */ + #if !defined(NO_SHA256) || defined(WOLFSSL_SHA384) || \ + defined(WOLFSSL_SHA512) + #undef WOLFSSL_NO_HASH_RAW + #define WOLFSSL_NO_HASH_RAW + #endif +#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 diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 0d27dbf5cf3..81acaaf1e1f 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -136,6 +136,16 @@ if(CONFIG_WOLFSSL) 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/rtl8735b.c) + zephyr_library_sources(${ZEPHYR_CURRENT_MODULE_DIR}/wolfcrypt/src/port/nxp/els_pkc_port.c) + + # Vendor headers for the EdgeLock port. Not for TF-M: els_pkc defaults + # MCUX_ELS_PKC on there but only creates McuxElsPkc when TF-M is off, + # and naming a target that does not exist is a generate-time error. + if(CONFIG_MCUX_ELS_PKC AND NOT CONFIG_BUILD_WITH_TFM) + zephyr_library_include_directories( + $ + ) + endif() if(CONFIG_WOLFCRYPT_ASM) set(WOLF_ARM_ASM_DIR ${ZEPHYR_CURRENT_MODULE_DIR}/wolfcrypt/src/port/arm) diff --git a/zephyr/Kconfig b/zephyr/Kconfig index a8259fef9db..dacbde9e1ef 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -117,6 +117,44 @@ config WOLFCRYPT_FIPS_READY endchoice +config WOLFSSL_SYS_INIT + bool "Initialize wolfSSL from a SYS_INIT hook" + help + Call wolfSSL_Init() at boot, from POST_KERNEL, instead of leaving it + to whichever library call happens to run first. A wolfCrypt-only + build calls wolfCrypt_Init() instead, since wolfSSL_Init() is not + compiled without the TLS layer. + + wolfCrypt initializes and seeds its DRBG lazily either way, so this + matters only when initialization also brings up a peripheral. A port + that does selects this itself; a settings-file build gets no such + select and has to set it by hand. + +config WOLFSSL_ELS_PKC + bool "NXP EdgeLock (ELS) hardware offload" + depends on WOLFSSL_BUILTIN && !WOLFSSL_HAS_SETTINGS_FILE + depends on !BUILD_WITH_TFM + depends on SOC_SERIES_RW6XX + select MCUX_ELS_PKC + select WOLFSSL_SYS_INIT + help + Route wolfCrypt operations to the on-chip EdgeLock secure subsystem + through the crypto callback interface. Operations the hardware does + not implement fall back to software. + + Selects MCUX_ELS_PKC, which is what puts the vendor headers on the + include path, and WOLFSSL_SYS_INIT, which brings the subsystem up at + boot. A settings-file build turns the port on by defining + WOLFSSL_ELS_PKC itself, since these options only write the module's + user_settings.h, and has to set CONFIG_MCUX_ELS_PKC=y and + CONFIG_WOLFSSL_SYS_INIT=y by hand. + + Not selectable in a TF-M build, where els_pkc does not create its + McuxElsPkc CMake target. + + Restricted to RW6XX because els_pkc exports src/platforms/, where + mcux_els.h lives, only under CONFIG_SOC_SERIES_RW6XX. + config WOLFSSL_CRYPTO_ONLY bool "Build wolfCrypt only (no TLS layer)" depends on !WOLFSSL_HAS_SETTINGS_FILE diff --git a/zephyr/user_settings.h b/zephyr/user_settings.h index 2dc565335fc..bed4a38b255 100644 --- a/zephyr/user_settings.h +++ b/zephyr/user_settings.h @@ -277,6 +277,17 @@ extern "C" { #define WOLFSSL_AES_CFB #endif +/* NXP EdgeLock (ELS) offload. WOLF_CRYPTO_CB_FIND is deliberately not set, + * unlike the MCUXpresso settings file: Zephyr's entropy driver supplies + * wc_GenerateSeed(), so an RNG left on INVALID_DEVID staying in software is + * the only consequence. */ +#ifdef CONFIG_WOLFSSL_ELS_PKC + #define WOLFSSL_ELS_PKC + #ifndef WOLF_CRYPTO_CB + #define WOLF_CRYPTO_CB + #endif +#endif + /* ------------------------------------------------------------------------- */ /* Algorithms */ /* ------------------------------------------------------------------------- */ diff --git a/zephyr/zephyr_init.c b/zephyr/zephyr_init.c index 033f436d41f..24801cbb4e7 100644 --- a/zephyr/zephyr_init.c +++ b/zephyr/zephyr_init.c @@ -19,9 +19,52 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ -/* The wolfSSL Zephyr module needs no boot-time SYS_INIT hook. wolfCrypt's - * Hash-DRBG is seeded on demand by wc_GenerateSeed() -- which on Zephyr draws - * from the hardware entropy driver when one is present (see - * wolfcrypt/src/random.c) -- and wolfCrypt_Init()/wolfSSL_Init() run lazily - * from the first library call. This translation unit is kept (it is referenced - * by the module CMakeLists) as the place for any future module init. */ +/* The module needs no boot-time SYS_INIT hook of its own: wolfSSL_Init() and + * the DRBG seed both happen lazily. A port that brings up a peripheral is the + * exception, since that is worth doing at boot rather than inside whichever + * call happens to be first. */ + +#ifdef CONFIG_WOLFSSL_SYS_INIT + +#include + +#include +#include + +#include +#ifdef WOLFCRYPT_ONLY + #include +#else + #include +#endif + +LOG_MODULE_REGISTER(wolfssl, CONFIG_WOLFSSL_LOG_LEVEL); + +static int wolfssl_module_init(void) +{ + int ret; + + /* Zephyr hands a non-device SYS_INIT return only to a trace hook, so the + * failure would otherwise leave no evidence. */ +#ifdef WOLFCRYPT_ONLY + ret = wolfCrypt_Init(); + if (ret != 0) { + LOG_ERR("wolfCrypt_Init failed: %d", ret); + return -EIO; + } +#else + ret = wolfSSL_Init(); + if (ret != WOLFSSL_SUCCESS) { + LOG_ERR("wolfSSL_Init failed: %d", ret); + return -EIO; + } +#endif + + return 0; +} + +/* POST_KERNEL because initialization takes mutexes and may allocate, late + * within it because a port needs the SoC and clock init that runs there. */ +SYS_INIT(wolfssl_module_init, POST_KERNEL, 99); + +#endif /* CONFIG_WOLFSSL_SYS_INIT */ From 730f8c6cb5777935c74d364dc461193f7c208839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:07 +0200 Subject: [PATCH 02/16] nxp: offload SHA-384 and SHA-512 to the EdgeLock ELS serves SHA-2 in all four widths. SHA-384 and SHA-512 share the engine's 512-bit state and 1024-bit block, so they differ from SHA-256 only in the mode selector, a 128-bit padding length field, and SHA-384 taking the leading 48 bytes of the final state. HMAC benefits without an arm of its own: HmacKeyInitHash passes the Hmac's devId to the inner hash, so HMAC-SHA384, HMAC-SHA512, HKDF and the SHA-384 TLS PRF all compress on the engine. The ELS HMAC command stays unused. It is one shot, with no equivalent of the hash command's state load, while the wolfCrypt callback is incremental, so serving it would mean buffering whole messages on parts routinely built without an allocator. --- ChangeLog.md | 2 +- wolfcrypt/src/port/nxp/README.md | 9 ++- wolfcrypt/src/port/nxp/els_pkc_port.c | 104 ++++++++++++++++++++++++-- 3 files changed, 104 insertions(+), 11 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 4555d213a0f..0004f40509a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -191,7 +191,7 @@ * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev -* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. +* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. ## Fixes diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 8936237b65f..196881d6bc7 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -52,7 +52,7 @@ see [README_SE050.md](./README_SE050.md). `els_pkc_port.c` offloads wolfCrypt to the EdgeLock subsystem found on the RW612 and related parts, through the crypto callback interface. The ELS -peripheral serves SHA-256. +peripheral serves SHA-256, SHA-384 and SHA-512. Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size costs @@ -93,9 +93,10 @@ it with `WOLFSSL_ELS_PKC_TIMEOUT_MS`, `WOLFSSL_ELS_PKC_IRQ_PRIO`, and before the thread sleeps, and zero never spins. **Offload state lives in the caller's object.** A hash keeps its ELS state in -the same `wc_Sha256` fields the software implementation would have used, so -nothing is allocated, a struct copy duplicates a context correctly, and the -port needs neither the copy nor the free crypto-callback hook. +the same `wc_Sha256`/`wc_Sha512` fields the software implementation would +have used, so nothing is allocated, a struct copy duplicates a context +correctly, and the port needs neither the copy nor the free crypto-callback +hook. ### Vendor library diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index 9cc0494ebdb..6bc35950b7e 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -278,12 +278,19 @@ static int ElsEnable(void) * for one call - holding it from update to final deadlocks TLS 1.3, which * keeps several transcript hashes alive at once. ELS never pads. */ -#ifndef NO_SHA256 +#if !defined(NO_SHA256) || defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512) #define ELS_SHA256_BLOCK MCUXCLELS_HASH_BLOCK_SIZE_SHA_256 #define ELS_SHA256_STATE MCUXCLELS_HASH_STATE_SIZE_SHA_256 -#define ELS_HASH_MAX_BLOCK ELS_SHA256_BLOCK +/* SHA-384 and SHA-512 share the engine's block and state size, differing in + * the mode selector, the digest truncation and a 128-bit length field. */ +#if defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512) + #define ELS_HASH_SHA512 + #define ELS_HASH_MAX_BLOCK MCUXCLELS_HASH_BLOCK_SIZE_SHA_512 +#else + #define ELS_HASH_MAX_BLOCK ELS_SHA256_BLOCK +#endif /* The offload keeps its state in the caller's hash object, in the very fields * the software implementation would have used: wolfCrypt sizes digest[] and @@ -301,9 +308,13 @@ static int ElsEnable(void) wc_static_assert(WC_SHA256_DIGEST_SIZE == ELS_SHA256_STATE); wc_static_assert(WC_SHA256_BLOCK_SIZE == ELS_SHA256_BLOCK); #endif +#ifdef ELS_HASH_SHA512 +wc_static_assert(WC_SHA512_DIGEST_SIZE == MCUXCLELS_HASH_STATE_SIZE_SHA_512); +wc_static_assert(WC_SHA512_BLOCK_SIZE == MCUXCLELS_HASH_BLOCK_SIZE_SHA_512); +#endif /* The fields above exist only in the software arm of the #ifdef chain in - * sha256.h; another SHA-2 port replaces them with its own. */ + * sha256.h and sha512.h; another SHA-2 port replaces them with its own. */ #if defined(FREESCALE_LTC_SHA) || defined(STM32_HASH_SHA2) || \ defined(WOLFSSL_SILABS_SE_ACCEL) || defined(WOLFSSL_IMXRT_DCP) || \ defined(PSOC6_HASH_SHA2) || \ @@ -335,6 +346,22 @@ static const word32 elsSha256Iv[8] = { 0x510e527fU, 0x9b05688cU, 0x1f83d9abU, 0x5be0cd19U }; #endif +#ifdef WOLFSSL_SHA384 +static const word64 elsSha384Iv[8] = { + W64LIT(0xcbbb9d5dc1059ed8), W64LIT(0x629a292a367cd507), + W64LIT(0x9159015a3070dd17), W64LIT(0x152fecd8f70e5939), + W64LIT(0x67332667ffc00b31), W64LIT(0x8eb44a8768581511), + W64LIT(0xdb0c2e0d64f98fa7), W64LIT(0x47b5481dbefa4fa4) +}; +#endif +#ifdef WOLFSSL_SHA512 +static const word64 elsSha512Iv[8] = { + W64LIT(0x6a09e667f3bcc908), W64LIT(0xbb67ae8584caa73b), + W64LIT(0x3c6ef372fe94f82b), W64LIT(0xa54ff53a5f1d36f1), + W64LIT(0x510e527fade682d1), W64LIT(0x9b05688c2b3e6c1f), + W64LIT(0x1f83d9abfb41bd6b), W64LIT(0x5be0cd19137e2179) +}; +#endif /* The caller's object, bound at the dispatch site so the offload below does * not care which of the three hashes it is driving. */ @@ -392,6 +419,31 @@ static void ElsHashBindSha256(ElsHashObj* o, wc_Sha256* sha) } #endif +#ifdef ELS_HASH_SHA512 +static void ElsHashBindSha512(ElsHashObj* o, wc_Sha512* sha, byte mode) +{ + o->devCtx = &sha->devCtx; + o->state = (byte*)sha->digest; + o->buf = (byte*)sha->buffer; + o->buffered = &sha->buffLen; + o->lo = &sha->loLen; + o->hi = &sha->hiLen; +#if defined(WOLFSSL_SHA384) && defined(WOLFSSL_SHA512) + o->iv = (mode == MCUXCLELS_HASH_MODE_SHA_384) ? (const void*)elsSha384Iv + : (const void*)elsSha512Iv; +#elif defined(WOLFSSL_SHA384) + o->iv = elsSha384Iv; +#else + o->iv = elsSha512Iv; +#endif + o->stateSz = (word32)MCUXCLELS_HASH_STATE_SIZE_SHA_512; + o->blockSz = (word32)MCUXCLELS_HASH_BLOCK_SIZE_SHA_512; + o->lenSz = WC_SHA512_BLOCK_SIZE - WC_SHA512_PAD_SIZE; + o->mode = mode; + o->wide = 1; +} +#endif + /* Feed whole blocks to the engine, carrying the running state in and out. * Caller must hold the lock. len must be a multiple of the block size. */ static int ElsHashBlocks(const ElsHashObj* o, const byte* in, word32 len) @@ -572,7 +624,7 @@ static int ElsHashFinal(ElsHashObj* o, byte* digest, word32 digestSz) return ret; } -#endif /* !NO_SHA256 */ +#endif /* !NO_SHA256 || WOLFSSL_SHA384 || WOLFSSL_SHA512 */ /* --------------------------------------------------------------------------- * Dispatch @@ -581,7 +633,7 @@ static int ElsHashFinal(ElsHashObj* o, byte* digest, word32 digestSz) int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) { int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); -#ifndef NO_SHA256 +#if !defined(NO_SHA256) || defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512) ElsHashObj hobj; #endif @@ -595,7 +647,7 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) switch (info->algo_type) { -#ifndef NO_SHA256 +#if !defined(NO_SHA256) || defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512) case WC_ALGO_TYPE_HASH: /* update passes (data, len, NULL) and final passes (NULL, 0, * digest), never both. */ @@ -619,6 +671,46 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) ret = 0; } break; + #endif + #ifdef WOLFSSL_SHA384 + case WC_HASH_TYPE_SHA384: + if (info->hash.sha384 == NULL) { + break; + } + ElsHashBindSha512(&hobj, info->hash.sha384, + MCUXCLELS_HASH_MODE_SHA_384); + if (info->hash.digest != NULL) { + ret = ElsHashFinal(&hobj, info->hash.digest, + WC_SHA384_DIGEST_SIZE); + } + else if (info->hash.in != NULL) { + ret = ElsHashUpdate(&hobj, info->hash.in, + info->hash.inSz); + } + else { + ret = 0; + } + break; + #endif + #ifdef WOLFSSL_SHA512 + case WC_HASH_TYPE_SHA512: + if (info->hash.sha512 == NULL) { + break; + } + ElsHashBindSha512(&hobj, info->hash.sha512, + MCUXCLELS_HASH_MODE_SHA_512); + if (info->hash.digest != NULL) { + ret = ElsHashFinal(&hobj, info->hash.digest, + WC_SHA512_DIGEST_SIZE); + } + else if (info->hash.in != NULL) { + ret = ElsHashUpdate(&hobj, info->hash.in, + info->hash.inSz); + } + else { + ret = 0; + } + break; #endif default: break; From 34aa0550344198718e7288c89f9a93ee07f075cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:11 +0200 Subject: [PATCH 03/16] nxp: offload AES-ECB, CBC and CTR to the EdgeLock Adds the AES cipher modes, and with them the slot reference that lets a wolfCrypt key name a key living inside the ELS key store. A callback sees only key->id[], its length and the devId, so the reference is self-describing and travels as the key's id blob: sixteen bytes of magic, version, key class, slot, flags and eight bytes binding it to a public point. wolfPSA stores the same bytes for a vendor-location key. Fields are never redefined; the version bumps and the format appends. Each key class maps onto exactly one ELS permission bit and one entry point, and every entry point reads the slot's properties back and checks that permission before issuing a command, because a permission violation resets the part instead of returning an error. AES-192 and any trailing partial block decline to software: ELS knows only 128- and 256-bit keys and only whole blocks. CBC chaining is maintained here. The documentation says ELS "will always read and write" pIV, but it only reads it, so aes->reg still held the original IV after a one-block encrypt: correct for a single call and wrong from the second onward. The next IV is the last ciphertext block either way, saved before an in-place decrypt overwrites it. cphsie/cphsoe are set only for CTR; for CBC they are documented as ignored, but setting them made ELS treat pIV as an internal state blob and produce output that was self-consistent yet disagreed with software. --- ChangeLog.md | 2 +- wolfcrypt/src/port/nxp/README.md | 19 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 366 ++++++++++++++++++++++ wolfssl/wolfcrypt/port/nxp/els_pkc_port.h | 84 +++++ 4 files changed, 465 insertions(+), 6 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 0004f40509a..cfaede9038b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -191,7 +191,7 @@ * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev -* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. +* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512 and AES (ECB/CBC/CTR). Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. ## Fixes diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 196881d6bc7..51949f225ab 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -52,7 +52,7 @@ see [README_SE050.md](./README_SE050.md). `els_pkc_port.c` offloads wolfCrypt to the EdgeLock subsystem found on the RW612 and related parts, through the crypto callback interface. The ELS -peripheral serves SHA-256, SHA-384 and SHA-512. +peripheral serves SHA-256, SHA-384, SHA-512 and AES (ECB/CBC/CTR). Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size costs @@ -65,15 +65,21 @@ cannot walk away with a confident bad result. created without an explicit device still reaches the hardware. `WC_NO_DEFAULT_DEVID` turns that off and leaves routing to the caller. +Keys that live in the ELS key store are referenced rather than exported. +`wc_ElsPkc_AesUseSlot()` attaches a slot reference to an AES key, so the key +material never leaves the hardware. It is the documented entry point: +`wc_ElsPkc_MakeKeyRef()` is the lower-level primitive it builds on. + ### Hardware behaviour worth knowing **A rejected request resets the SoC, by default.** ELS answers an invalid key permission or a failed unwrap by signalling the Intrusion and Tamper Response Controller, which on its reset-on-tamper default drives a chip reset rather -than returning an error. The port validates its arguments in software before -issuing a command, and does not cancel an operation already in flight, -because `mcuxClEls_Reset_Async(MCUXCLELS_RESET_CANCEL)` is itself a tamper -event. An integration that has retargeted the ITRC can define +than returning an error. The port validates every slot reference against the +hardware in software before issuing a command, and does not cancel an +operation already in flight, because +`mcuxClEls_Reset_Async(MCUXCLELS_RESET_CANCEL)` is itself a tamper event. An +integration that has retargeted the ITRC can define `WOLFSSL_ELS_PKC_ALLOW_CANCEL`, and a late completion interrupt then cancels and fails the operation instead of degrading to the vendor's synchronous wait. Do not probe the hardware with deliberately malformed references. @@ -98,6 +104,9 @@ have used, so nothing is allocated, a struct copy duplicates a context correctly, and the port needs neither the copy nor the free crypto-callback hook. +**What the hardware declines**, so it runs in software instead: AES-192 (no +ELS key size), and any trailing partial block. + ### Vendor library The port depends on NXP's CLNS library, `els_pkc`, which ships as an diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index 6bc35950b7e..dd2bdf4996d 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -57,6 +57,20 @@ #include #endif +/* The AES modes ELS drives with its block-cipher command. GCM and CMAC are + * separate commands, so they are not in here. */ +#if !defined(NO_AES) && (defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || \ + defined(HAVE_AES_ECB) || defined(WOLFSSL_AES_DIRECT)) + #define ELS_HAVE_AES_BLOCK_CIPHER +#endif + +/* Everything that can name a key slot. Each validates a reference against the + * hardware before issuing a command, because a permission violation resets the + * SoC, so they share the helpers in the next section. */ +#ifdef ELS_HAVE_AES_BLOCK_CIPHER + #define ELS_HAVE_SLOT_KEYS +#endif + /* --------------------------------------------------------------------------- * Peripheral serialization * ------------------------------------------------------------------------ */ @@ -269,6 +283,143 @@ static int ElsEnable(void) return 0; } +/* --------------------------------------------------------------------------- + * Slot references and permission validation + * ------------------------------------------------------------------------ */ + +/* Public bound, so a caller can range-check a slot without the NXP headers. */ +#if (WC_ELSPKC_MAX_SLOT + 1) != MCUXCLELS_KEY_SLOTS + #error WC_ELSPKC_MAX_SLOT is out of step with MCUXCLELS_KEY_SLOTS +#endif + +int wc_ElsPkc_MakeKeyRef(const wc_ElsPkc_KeyRef* ref, byte* out, word32* outSz) +{ + if (ref == NULL || outSz == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + if (out == NULL) { + *outSz = WC_ELSPKC_KEYREF_SZ; + return WC_NO_ERR_TRACE(LENGTH_ONLY_E); + } + if (*outSz < WC_ELSPKC_KEYREF_SZ) { + return WC_NO_ERR_TRACE(BUFFER_E); + } + if (ref->keyClass == WC_ELSPKC_KEY_NONE || + ref->keyClass > WC_ELSPKC_KEY_HKDF || + ref->slot > WC_ELSPKC_MAX_SLOT) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + XMEMSET(out, 0, WC_ELSPKC_KEYREF_SZ); + out[0] = WC_ELSPKC_KEYREF_MAGIC_0; + out[1] = WC_ELSPKC_KEYREF_MAGIC_1; + out[2] = WC_ELSPKC_KEYREF_VER; + out[3] = ref->keyClass; + out[4] = ref->slot; + out[5] = ref->flags; + /* out[6..7] stay zero - reserved */ + if (ref->flags & WC_ELSPKC_REF_FLAG_BIND) { + XMEMCPY(out + 8, ref->bind, WC_ELSPKC_BIND_SZ); + } + + *outSz = WC_ELSPKC_KEYREF_SZ; + + return 0; +} + +int wc_ElsPkc_ParseKeyRef(const byte* in, word32 inSz, wc_ElsPkc_KeyRef* ref) +{ + if (in == NULL || ref == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + /* wolfPSA stores the reference followed by the public point, so a longer + * blob is expected - only the prefix belongs to us. */ + if (inSz < WC_ELSPKC_KEYREF_SZ) { + return WC_NO_ERR_TRACE(BUFFER_E); + } + if (in[0] != WC_ELSPKC_KEYREF_MAGIC_0 || + in[1] != WC_ELSPKC_KEYREF_MAGIC_1) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + if (in[2] != WC_ELSPKC_KEYREF_VER) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + if (in[3] == WC_ELSPKC_KEY_NONE || in[3] > WC_ELSPKC_KEY_HKDF) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + if (in[4] > WC_ELSPKC_MAX_SLOT) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + + XMEMSET(ref, 0, sizeof(*ref)); + ref->keyClass = in[3]; + ref->slot = in[4]; + ref->flags = in[5]; + if (ref->flags & WC_ELSPKC_REF_FLAG_BIND) { + XMEMCPY(ref->bind, in + 8, WC_ELSPKC_BIND_SZ); + } + + return 0; +} + +/* Slot validation, reached from every path that can name a slot. */ +#ifdef ELS_HAVE_SLOT_KEYS + +/* The ELS permission bit each class stands for. */ +static word32 ElsClassUsageBit(byte keyClass) +{ + switch (keyClass) { + case WC_ELSPKC_KEY_ECC_SIGN: return MCUXCLELS_KEYPROPERTY_VALUE_ECSGN; + case WC_ELSPKC_KEY_ECC_DH: return MCUXCLELS_KEYPROPERTY_VALUE_ECDH; + case WC_ELSPKC_KEY_AES: return MCUXCLELS_KEYPROPERTY_VALUE_AES; + case WC_ELSPKC_KEY_HMAC: return MCUXCLELS_KEYPROPERTY_VALUE_HMAC; + case WC_ELSPKC_KEY_CMAC: return MCUXCLELS_KEYPROPERTY_VALUE_CMAC; + case WC_ELSPKC_KEY_KWK: return MCUXCLELS_KEYPROPERTY_VALUE_KWK; + case WC_ELSPKC_KEY_CKDF: return MCUXCLELS_KEYPROPERTY_VALUE_CKDF; + case WC_ELSPKC_KEY_HKDF: return MCUXCLELS_KEYPROPERTY_VALUE_HKDF; + default: return 0; + } +} + +/* Confirm the slot is occupied and carries the permission the class needs. + * The bit must be present, never exclusive - one slot legitimately carries + * several usage bits. ELS answers a permission violation by signalling the + * tamper controller, which resets the SoC. Caller must hold the lock. */ +static int ElsCheckSlot(const wc_ElsPkc_KeyRef* ref, byte expectClass) +{ + mcuxClEls_KeyProp_t prop; + word32 need; + + if (ref->keyClass != expectClass) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + need = ElsClassUsageBit(ref->keyClass); + if (need == 0) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, + mcuxClEls_GetKeyProperties((mcuxClEls_KeyIndex_t)ref->slot, &prop)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_GetKeyProperties) != t) || + (MCUXCLELS_STATUS_OK != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + /* An empty slot reads as all-zero properties, which fails the usage test + * below anyway - but check it explicitly so the error says what is wrong. */ + if ((prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE) == 0) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + if ((prop.word.value & need) == 0) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + + return 0; +} + +#endif /* ELS_HAVE_SLOT_KEYS */ + /* --------------------------------------------------------------------------- * SHA-256 / SHA-384 / SHA-512 * ------------------------------------------------------------------------ */ @@ -626,6 +777,185 @@ static int ElsHashFinal(ElsHashObj* o, byte* digest, word32 digestSz) #endif /* !NO_SHA256 || WOLFSSL_SHA384 || WOLFSSL_SHA512 */ +/* --------------------------------------------------------------------------- + * AES-ECB / CBC / CTR + * ------------------------------------------------------------------------ */ + +/* ELS knows only 128- and 256-bit keys and only whole blocks. AES-192 and any + * trailing partial block are declined so software handles them. */ + +#ifndef NO_AES + +int wc_ElsPkc_AesUseSlot(Aes* aes, const wc_ElsPkc_KeyRef* ref, + void* heap, int devId) +{ + byte blob[WC_ELSPKC_KEYREF_SZ]; + word32 blobSz = sizeof(blob); + int ret; + + if (aes == NULL || ref == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + /* Only the classes an Aes could drive. A CMAC key gets its own reference + * on the Cmac object, which has its own id[]. */ + if (ref->keyClass != WC_ELSPKC_KEY_AES && + ref->keyClass != WC_ELSPKC_KEY_KWK) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + ret = wc_ElsPkc_MakeKeyRef(ref, blob, &blobSz); + if (ret == 0) { + /* keyInstalled stays clear: the key is in the slot, not in a software + * schedule, so an unwired operation fails instead of running on + * zeros. */ + ret = wc_AesInit_Id(aes, blob, (int)blobSz, heap, devId); + } + + ForceZero(blob, sizeof(blob)); + + return ret; +} + +static int ElsAesKeyOk(const Aes* aes) +{ + return (aes->keylen == 16 || aes->keylen == 32); +} + +/* Read the slot reference an Aes carries, if any. CRYPTOCB_UNAVAILABLE means + * no reference; a parse failure is a hard error, because an Aes bound to a + * slot has no key material for software to fall back to. */ +static int ElsAesRef(const Aes* aes, wc_ElsPkc_KeyRef* ref) +{ + if (aes == NULL || aes->idLen <= 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + return wc_ElsPkc_ParseKeyRef(aes->id, (word32)aes->idLen, ref); +} + +/* Issue one cipher command. Caller holds the lock. keyIdx is read only when + * opt says the key is internal, in which case the memory pair is ignored - + * pass nothing rather than a stale devKey. */ +#ifdef ELS_HAVE_AES_BLOCK_CIPHER + +static int ElsCipherRun(mcuxClEls_CipherOption_t opt, + mcuxClEls_KeyIndex_t keyIdx, Aes* aes, + const byte* in, word32 sz, byte* out, int useIv) +{ + int extKey = (opt.bits.extkey == MCUXCLELS_CIPHER_EXTERNAL_KEY); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_Cipher_Async( + opt, keyIdx, + extKey ? (const uint8_t*)aes->devKey : NULL, + extKey ? (size_t)aes->keylen : 0u, + in, sz, useIv ? (uint8_t*)aes->reg : NULL, out)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_Cipher_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return ElsWait(); +} + +static int ElsAesCipher(Aes* aes, byte* out, const byte* in, word32 sz, + int mode, int encrypt, int useIv) +{ + mcuxClEls_CipherOption_t opt; + wc_ElsPkc_KeyRef ref; + byte lastCipher[MCUXCLELS_CIPHER_BLOCK_SIZE_AES]; + int slotted = 0; + int ret; + + if (aes == NULL || out == NULL || in == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + XMEMSET(&ref, 0, sizeof(ref)); + ret = ElsAesRef(aes, &ref); + if (ret == 0) { + slotted = 1; + } + else if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + return ret; + } + + /* Whole blocks only. keylen matters only for a key held in memory: a slot + * key carries its size in its property word and aes->keylen is 0. */ + if (sz == 0 || (sz % MCUXCLELS_CIPHER_BLOCK_SIZE_AES) != 0 || + (!slotted && !ElsAesKeyOk(aes))) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } +#ifdef WOLFSSL_AES_COUNTER + /* CTR keeps an unconsumed keystream remainder in aes->tmp and has already + * advanced aes->reg past it, and this callback runs before that remainder + * is used - so starting from aes->reg would desynchronise the stream. */ + if (mode == MCUXCLELS_CIPHERPARAM_ALGORITHM_AES_CTR && aes->left != 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } +#endif + + opt.word.value = 0u; + opt.bits.cphmde = (uint32_t)mode; + opt.bits.dcrpt = encrypt ? MCUXCLELS_CIPHER_ENCRYPT + : MCUXCLELS_CIPHER_DECRYPT; + opt.bits.extkey = slotted ? MCUXCLELS_CIPHER_INTERNAL_KEY + : MCUXCLELS_CIPHER_EXTERNAL_KEY; + if (useIv && mode == MCUXCLELS_CIPHERPARAM_ALGORITHM_AES_CTR) { + /* cphsoe makes ELS write the updated counter back. NOT set for CBC: + * the documentation says it is ignored there, but setting it made ELS + * take pIV as an internal state blob and disagree with software. */ + opt.bits.cphsie = MCUXCLELS_CIPHER_STATE_IN_ENABLE; + opt.bits.cphsoe = MCUXCLELS_CIPHER_STATE_OUT_ENABLE; + } + + /* CBC decrypt chains on the last ciphertext block, which is the input - + * save it now because an in-place call is about to overwrite it. */ + if (mode == MCUXCLELS_CIPHERPARAM_ALGORITHM_AES_CBC && !encrypt) { + XMEMCPY(lastCipher, in + sz - MCUXCLELS_CIPHER_BLOCK_SIZE_AES, + MCUXCLELS_CIPHER_BLOCK_SIZE_AES); + } + + ret = ElsLock(); + if (ret == 0) { + /* Prove the slot holds an active AES key: the class check alone would + * pass a hand-built reference against, say, a wrapping key. */ + if (slotted) { + ret = ElsCheckSlot(&ref, WC_ELSPKC_KEY_AES); + } + if (ret == 0) { + ret = ElsCipherRun(opt, (mcuxClEls_KeyIndex_t)ref.slot, aes, + in, sz, out, useIv); + } + ElsUnlock(); + } + + if (ret == 0) { + /* Despite the documentation, this part does not write pIV back for + * CBC, so a chained call would restart from the original IV. The next + * IV is the last ciphertext block: the output when encrypting, the + * saved input when decrypting. */ + if (mode == MCUXCLELS_CIPHERPARAM_ALGORITHM_AES_CBC) { + if (encrypt) { + XMEMCPY(aes->reg, out + sz - MCUXCLELS_CIPHER_BLOCK_SIZE_AES, + MCUXCLELS_CIPHER_BLOCK_SIZE_AES); + } + else { + XMEMCPY(aes->reg, lastCipher, + MCUXCLELS_CIPHER_BLOCK_SIZE_AES); + } + } + } + + ForceZero(lastCipher, sizeof(lastCipher)); + + return ret; +} + +#endif /* ELS_HAVE_AES_BLOCK_CIPHER */ + +#endif /* !NO_AES */ + /* --------------------------------------------------------------------------- * Dispatch * ------------------------------------------------------------------------ */ @@ -646,6 +976,42 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) switch (info->algo_type) { +#ifndef NO_AES + case WC_ALGO_TYPE_CIPHER: + switch (info->cipher.type) { + #ifdef HAVE_AES_CBC + case WC_CIPHER_AES_CBC: + ret = ElsAesCipher(info->cipher.aescbc.aes, + info->cipher.aescbc.out, info->cipher.aescbc.in, + info->cipher.aescbc.sz, + MCUXCLELS_CIPHERPARAM_ALGORITHM_AES_CBC, + info->cipher.enc, 1); + break; + #endif + #ifdef WOLFSSL_AES_COUNTER + case WC_CIPHER_AES_CTR: + /* CTR keystream is symmetric, so the hardware always runs + * the "encrypt" direction regardless of the caller's. */ + ret = ElsAesCipher(info->cipher.aesctr.aes, + info->cipher.aesctr.out, info->cipher.aesctr.in, + info->cipher.aesctr.sz, + MCUXCLELS_CIPHERPARAM_ALGORITHM_AES_CTR, 1, 1); + break; + #endif + #if defined(HAVE_AES_ECB) || defined(WOLFSSL_AES_DIRECT) + case WC_CIPHER_AES_ECB: + ret = ElsAesCipher(info->cipher.aesecb.aes, + info->cipher.aesecb.out, info->cipher.aesecb.in, + info->cipher.aesecb.sz, + MCUXCLELS_CIPHERPARAM_ALGORITHM_AES_ECB, + info->cipher.enc, 0); + break; + #endif + default: + break; + } + break; +#endif /* !NO_AES */ #if !defined(NO_SHA256) || defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512) case WC_ALGO_TYPE_HASH: diff --git a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h index d68bdc42930..a59f0a006b2 100644 --- a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h +++ b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h @@ -32,8 +32,17 @@ #error "WOLFSSL_ELS_PKC requires WOLF_CRYPTO_CB" #endif +/* The slot reference travels as a key's id blob, which wc_ecc_init_id() and + * wc_AesInit_Id() set. NO_WOLF_PRIVATE_KEY_ID removes both. */ +#ifndef WOLF_PRIVATE_KEY_ID + #error "WOLFSSL_ELS_PKC key slot references require WOLF_PRIVATE_KEY_ID" +#endif + #include #include +#ifndef NO_AES + #include +#endif #ifdef __cplusplus extern "C" { @@ -47,6 +56,81 @@ #define WOLFSSL_ELS_PKC_IRQ_PRIO 2 #endif +/* --------------------------------------------------------------------------- + * Slot references + * + * A reference to an ELS key slot travels as the key's id blob, since a + * callback sees only key->id[], its length and the devId. wolfPSA stores the + * same 16 bytes followed by the public point. Never redefine a field; bump ver + * and append. + * + * off size field + * 0 2 magic 'E','L' + * 2 1 ver + * 3 1 keyClass + * 4 1 slot + * 5 1 flags + * 6 2 reserved, zero + * 8 8 bind - first 8 bytes of SHA-256 over the X9.62 public point, + * zero for symmetric keys + * ------------------------------------------------------------------------ */ + +/* Highest slot index a reference may name, mirroring the vendor header's + * MCUXCLELS_KEY_SLOTS so a caller need not include the NXP SDK. */ +#define WC_ELSPKC_MAX_SLOT 19 + +#define WC_ELSPKC_KEYREF_SZ 16 +#define WC_ELSPKC_KEYREF_MAGIC_0 0x45 /* 'E' */ +#define WC_ELSPKC_KEYREF_MAGIC_1 0x4C /* 'L' */ +#define WC_ELSPKC_KEYREF_VER 1 +#define WC_ELSPKC_BIND_SZ 8 + +/* Each class maps 1:1 onto one ELS permission bit and one ELS entry point. + * RSA and the Ed curves have no key property bit, so they cannot be vaulted; + * they run on the PKC tier with ordinary key material and no reference. */ +enum wc_ElsPkc_KeyClass { + WC_ELSPKC_KEY_NONE = 0, + WC_ELSPKC_KEY_ECC_SIGN = 1, /* ELS uecsg */ + WC_ELSPKC_KEY_ECC_DH = 2, /* ELS uecdh */ + WC_ELSPKC_KEY_AES = 3, /* ELS uaes */ + WC_ELSPKC_KEY_HMAC = 4, /* ELS uhmac */ + WC_ELSPKC_KEY_CMAC = 5, /* ELS ucmac */ + WC_ELSPKC_KEY_KWK = 6, /* ELS ukwk / ukuok */ + WC_ELSPKC_KEY_CKDF = 7, /* ELS uckdf */ + WC_ELSPKC_KEY_HKDF = 8 /* ELS uhkdf */ +}; + +/* bit0 is set when bind[] carries a real value; bits 1-2 are creation-time + * attributes, meaningful only on a key generation. */ +#define WC_ELSPKC_REF_FLAG_BIND 0x01 +#define WC_ELSPKC_REF_FLAG_EXPORTABLE 0x02 /* ELS wrpok */ +#define WC_ELSPKC_REF_FLAG_PERSISTENT 0x04 /* ELS frtn */ + +typedef struct wc_ElsPkc_KeyRef { + byte keyClass; + byte slot; + byte flags; + byte bind[WC_ELSPKC_BIND_SZ]; +} wc_ElsPkc_KeyRef; + +/* Serialise into out, which must hold WC_ELSPKC_KEYREF_SZ bytes. outSz is + * in/out; out == NULL is a size query returning LENGTH_ONLY_E. Parse returns + * BAD_STATE_E for a bad magic, version, class or slot, and accepts a longer + * blob, reading only the prefix. Parsing touches no hardware. */ +WOLFSSL_API int wc_ElsPkc_MakeKeyRef(const wc_ElsPkc_KeyRef* ref, byte* out, + word32* outSz); +WOLFSSL_API int wc_ElsPkc_ParseKeyRef(const byte* in, word32 inSz, + wc_ElsPkc_KeyRef* ref); + +#ifndef NO_AES +/* Same, for an Aes that names a slot. aes->keylen stays 0, because + * wc_AesInit_Id() runs no key schedule and the engine takes the size from the + * slot's own property word. WC_ELSPKC_KEY_KWK is accepted so a wrapping key + * can be named the same way, but only WC_ELSPKC_KEY_AES drives a cipher. */ +WOLFSSL_API int wc_ElsPkc_AesUseSlot(Aes* aes, const wc_ElsPkc_KeyRef* ref, + void* heap, int devId); +#endif + /* Bring the EdgeLock subsystem up and register the crypto callback. * wolfCrypt_Init() already does this; call it directly only to re-register * after a wolfCrypt_Cleanup(). Safe to call more than once. */ From 1db2a52977e3ab646d67b8163178579a232741ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:16 +0200 Subject: [PATCH 04/16] nxp: offload AES-GCM to the EdgeLock The whole Init/UpdateAad/UpdateData/Finalize sequence runs under one lock, which it can because the callback hands over the entire message at once. Every stage consumes whole blocks: the final partial data block is zero-padded with msgendw carrying its real byte count, and the AAD length reaches the hardware only through Finalize. An exact block multiple needs no padding and so comes out correct either way, which means a test exercising only 16, 32 and 64 bytes passes against a port that is wrong for every other length. J0 is IV || 0x00000001, the single-block form Aead_Init takes; other IV lengths need the GHASH-based derivation through Aead_PartialInit and are declined. Finalize outputs the expected tag on decrypt rather than checking it, so the comparison is done here in constant time, and a mismatch clears the plaintext so a caller that ignores the return value is not handed unauthenticated data. --- ChangeLog.md | 2 +- wolfcrypt/src/port/nxp/README.md | 5 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 291 +++++++++++++++++++++++++- 3 files changed, 294 insertions(+), 4 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index cfaede9038b..ad09645c683 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -191,7 +191,7 @@ * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev -* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512 and AES (ECB/CBC/CTR). Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. +* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR) and AES-GCM. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. ## Fixes diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 51949f225ab..5baf1a02d48 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -52,7 +52,7 @@ see [README_SE050.md](./README_SE050.md). `els_pkc_port.c` offloads wolfCrypt to the EdgeLock subsystem found on the RW612 and related parts, through the crypto callback interface. The ELS -peripheral serves SHA-256, SHA-384, SHA-512 and AES (ECB/CBC/CTR). +peripheral serves SHA-256, SHA-384, SHA-512, AES (ECB/CBC/CTR) and AES-GCM. Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size costs @@ -105,7 +105,8 @@ correctly, and the port needs neither the copy nor the free crypto-callback hook. **What the hardware declines**, so it runs in software instead: AES-192 (no -ELS key size), and any trailing partial block. +ELS key size), any trailing partial block, and an AES-GCM IV other than 12 +bytes. ### Vendor library diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index dd2bdf4996d..e3c103af400 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -67,7 +67,8 @@ /* Everything that can name a key slot. Each validates a reference against the * hardware before issuing a command, because a permission violation resets the * SoC, so they share the helpers in the next section. */ -#ifdef ELS_HAVE_AES_BLOCK_CIPHER +#if defined(ELS_HAVE_AES_BLOCK_CIPHER) || \ + (!defined(NO_AES) && defined(HAVE_AESGCM)) #define ELS_HAVE_SLOT_KEYS #endif @@ -954,6 +955,264 @@ static int ElsAesCipher(Aes* aes, byte* out, const byte* in, word32 sz, #endif /* ELS_HAVE_AES_BLOCK_CIPHER */ +/* --------------------------------------------------------------------------- + * AES-GCM + * ------------------------------------------------------------------------ */ + +/* Init / UpdateAad / UpdateData / Finalize, threaded through an 80-byte + * context. Every stage consumes whole blocks, and the true AAD and data + * lengths reach the hardware only through Finalize; getting either wrong + * yields a clean-looking wrong tag. */ + +#ifdef HAVE_AESGCM + +#define ELS_GCM_BLOCK MCUXCLELS_AEAD_IV_BLOCK_SIZE + +/* An internal key is named by keyIdx and the memory pair is then ignored. */ +static const uint8_t* ElsGcmKeyPtr(mcuxClEls_AeadOption_t opt, const Aes* aes) +{ + return (opt.bits.extkey == MCUXCLELS_AEAD_EXTERN_KEY) + ? (const uint8_t*)aes->devKey : NULL; +} + +static size_t ElsGcmKeyLen(mcuxClEls_AeadOption_t opt, const Aes* aes) +{ + return (opt.bits.extkey == MCUXCLELS_AEAD_EXTERN_KEY) + ? (size_t)aes->keylen : 0u; +} + +/* Each stage below is its own function so the flow-protection macro pair stays + * within one scope, matching the rest of this port. Caller holds the lock. */ + +static int ElsGcmInit(mcuxClEls_AeadOption_t opt, mcuxClEls_KeyIndex_t keyIdx, + const Aes* aes, const byte* j0, byte* aeadCtx) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_Aead_Init_Async( + opt, keyIdx, ElsGcmKeyPtr(opt, aes), ElsGcmKeyLen(opt, aes), + j0, (size_t)ELS_GCM_BLOCK, aeadCtx)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_Aead_Init_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return ElsWait(); +} + +static int ElsGcmAadChunk(mcuxClEls_AeadOption_t opt, + mcuxClEls_KeyIndex_t keyIdx, const Aes* aes, + const byte* aad, word32 len, byte* aeadCtx) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_Aead_UpdateAad_Async( + opt, keyIdx, ElsGcmKeyPtr(opt, aes), ElsGcmKeyLen(opt, aes), + aad, (size_t)len, aeadCtx)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_Aead_UpdateAad_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return ElsWait(); +} + +static int ElsGcmDataChunk(mcuxClEls_AeadOption_t opt, + mcuxClEls_KeyIndex_t keyIdx, const Aes* aes, + const byte* in, word32 len, byte* out, + byte* aeadCtx) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_Aead_UpdateData_Async( + opt, keyIdx, ElsGcmKeyPtr(opt, aes), ElsGcmKeyLen(opt, aes), + in, (size_t)len, out, aeadCtx)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_Aead_UpdateData_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return ElsWait(); +} + +static int ElsGcmFinal(mcuxClEls_AeadOption_t opt, mcuxClEls_KeyIndex_t keyIdx, + const Aes* aes, word32 aadSz, word32 dataSz, + byte* tag, byte* aeadCtx) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_Aead_Finalize_Async( + opt, keyIdx, ElsGcmKeyPtr(opt, aes), ElsGcmKeyLen(opt, aes), + (size_t)aadSz, (size_t)dataSz, tag, aeadCtx)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_Aead_Finalize_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return ElsWait(); +} + +/* AAD is fed as whole blocks; Finalize's aadLength says how much of the + * zero-padded last block was real. */ +static int ElsGcmAad(mcuxClEls_AeadOption_t opt, mcuxClEls_KeyIndex_t keyIdx, + const Aes* aes, const byte* aad, word32 aadSz, + byte* aeadCtx) +{ + ALIGN32 byte block[ELS_GCM_BLOCK]; + word32 full = aadSz & ~(word32)(ELS_GCM_BLOCK - 1u); + word32 tail = aadSz - full; + int ret = 0; + + if (full > 0u) { + ret = ElsGcmAadChunk(opt, keyIdx, aes, aad, full, aeadCtx); + } + + if (ret == 0 && tail > 0u) { + XMEMSET(block, 0, sizeof(block)); + XMEMCPY(block, aad + full, tail); + ret = ElsGcmAadChunk(opt, keyIdx, aes, block, ELS_GCM_BLOCK, aeadCtx); + ForceZero(block, sizeof(block)); + } + + return ret; +} + +static int ElsAesGcm(Aes* aes, byte* out, const byte* in, word32 sz, + const byte* iv, word32 ivSz, + byte* authTag, word32 authTagSz, + const byte* authIn, word32 authInSz, int encrypt) +{ + mcuxClEls_AeadOption_t opt; + mcuxClEls_AeadOption_t stageOpt; + wc_ElsPkc_KeyRef ref; + ALIGN32 byte aeadCtx[MCUXCLELS_AEAD_CONTEXT_SIZE]; + ALIGN32 byte j0[ELS_GCM_BLOCK]; + ALIGN32 byte inBlock[ELS_GCM_BLOCK]; + ALIGN32 byte outBlock[ELS_GCM_BLOCK]; + byte tag[MCUXCLELS_AEAD_TAG_SIZE]; + word32 full, tail; + int slotted = 0; + int ret; + + if (aes == NULL || iv == NULL || authTag == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + if ((sz > 0 && (in == NULL || out == NULL)) || + (authInSz > 0 && authIn == NULL)) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + XMEMSET(&ref, 0, sizeof(ref)); + ret = ElsAesRef(aes, &ref); + if (ret == 0) { + slotted = 1; + } + else if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + return ret; + } + /* Only a 12-byte IV maps onto the single-block J0 Aead_Init takes. A + * shorter tag is the leftmost bytes of the 16-byte one, so those are + * served. AES-192 has no ELS key size, which only an in-memory key can + * claim. */ + if (ivSz != GCM_NONCE_MID_SZ || authTagSz == 0 || + authTagSz > MCUXCLELS_AEAD_TAG_SIZE || + (!slotted && !ElsAesKeyOk(aes))) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + /* For a 96-bit IV, J0 is IV || 0x00000001 (SP 800-38D, 7.1). */ + XMEMSET(j0, 0, sizeof(j0)); + XMEMCPY(j0, iv, ivSz); + j0[ELS_GCM_BLOCK - 1] = 0x01; + + opt.word.value = 0u; + opt.bits.dcrpt = encrypt ? MCUXCLELS_AEAD_ENCRYPT + : MCUXCLELS_AEAD_DECRYPT; + opt.bits.extkey = slotted ? MCUXCLELS_AEAD_INTERN_KEY + : MCUXCLELS_AEAD_EXTERN_KEY; + opt.bits.acpsie = MCUXCLELS_AEAD_STATE_IN_ENABLE; + opt.bits.lastinit = MCUXCLELS_AEAD_LASTINIT_FALSE; + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + /* Prove the slot before the four-stage sequence starts, so a bad reference + * fails before any context is built. */ + if (slotted) { + ret = ElsCheckSlot(&ref, WC_ELSPKC_KEY_AES); + if (ret != 0) { + ElsUnlock(); + return ret; + } + } + + /* Init builds the context rather than continuing one, so it must not be + * told to read a state in - aeadCtx holds nothing yet. */ + stageOpt = opt; + stageOpt.bits.acpsie = MCUXCLELS_AEAD_STATE_IN_DISABLE; + ret = ElsGcmInit(stageOpt, (mcuxClEls_KeyIndex_t)ref.slot, aes, j0, + aeadCtx); + + if (ret == 0 && authInSz > 0) { + ret = ElsGcmAad(opt, (mcuxClEls_KeyIndex_t)ref.slot, aes, authIn, + authInSz, aeadCtx); + } + + if (ret == 0 && sz > 0) { + full = sz & ~(word32)(ELS_GCM_BLOCK - 1u); + tail = sz - full; + + if (full > 0) { + ret = ElsGcmDataChunk(opt, (mcuxClEls_KeyIndex_t)ref.slot, aes, + in, full, out, aeadCtx); + } + if (ret == 0 && tail > 0) { + /* msgendw carries the real byte count of the final block while + * the block itself still arrives zero-padded. */ + stageOpt = opt; + stageOpt.bits.msgendw = (uint32_t)tail; + + XMEMSET(inBlock, 0, sizeof(inBlock)); + XMEMCPY(inBlock, in + full, tail); + ret = ElsGcmDataChunk(stageOpt, (mcuxClEls_KeyIndex_t)ref.slot, + aes, inBlock, ELS_GCM_BLOCK, outBlock, + aeadCtx); + if (ret == 0) { + XMEMCPY(out + full, outBlock, tail); + } + } + } + + if (ret == 0) { + ret = ElsGcmFinal(opt, (mcuxClEls_KeyIndex_t)ref.slot, aes, authInSz, + sz, tag, aeadCtx); + } + + ElsUnlock(); + + if (ret == 0) { + if (encrypt) { + XMEMCPY(authTag, tag, authTagSz); + } + else if (wc_ConstantCompare(tag, authTag, (int)authTagSz) != 0) { + /* Match the software path: a failed tag check leaves no plaintext + * behind for a caller that ignores the return value. */ + if (sz > 0) { + ForceZero(out, sz); + } + ret = WC_NO_ERR_TRACE(AES_GCM_AUTH_E); + } + } + + ForceZero(tag, sizeof(tag)); + ForceZero(j0, sizeof(j0)); + ForceZero(inBlock, sizeof(inBlock)); + ForceZero(outBlock, sizeof(outBlock)); + ForceZero(aeadCtx, sizeof(aeadCtx)); + + return ret; +} + +#endif /* HAVE_AESGCM */ + #endif /* !NO_AES */ /* --------------------------------------------------------------------------- @@ -979,6 +1238,36 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) #ifndef NO_AES case WC_ALGO_TYPE_CIPHER: switch (info->cipher.type) { + #ifdef HAVE_AESGCM + case WC_CIPHER_AES_GCM: + if (info->cipher.enc) { + ret = ElsAesGcm(info->cipher.aesgcm_enc.aes, + info->cipher.aesgcm_enc.out, + info->cipher.aesgcm_enc.in, + info->cipher.aesgcm_enc.sz, + info->cipher.aesgcm_enc.iv, + info->cipher.aesgcm_enc.ivSz, + info->cipher.aesgcm_enc.authTag, + info->cipher.aesgcm_enc.authTagSz, + info->cipher.aesgcm_enc.authIn, + info->cipher.aesgcm_enc.authInSz, 1); + } + else { + /* the decrypt struct keeps authTag const; the port + * only ever compares against it */ + ret = ElsAesGcm(info->cipher.aesgcm_dec.aes, + info->cipher.aesgcm_dec.out, + info->cipher.aesgcm_dec.in, + info->cipher.aesgcm_dec.sz, + info->cipher.aesgcm_dec.iv, + info->cipher.aesgcm_dec.ivSz, + (byte*)info->cipher.aesgcm_dec.authTag, + info->cipher.aesgcm_dec.authTagSz, + info->cipher.aesgcm_dec.authIn, + info->cipher.aesgcm_dec.authInSz, 0); + } + break; + #endif #ifdef HAVE_AES_CBC case WC_CIPHER_AES_CBC: ret = ElsAesCipher(info->cipher.aescbc.aes, From f61ebc9a1fc9867d428756d30294c5652691025b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:20 +0200 Subject: [PATCH 05/16] nxp: offload AES-CMAC to the EdgeLock The input must arrive already padded to a whole block per SP 800-38B, 0x80 then zeros, while inputLength stays the true pre-padding count, because that count is what selects subkey K1 or K2. Only the last block needs holding back, since only it takes the other subkey, so everything before it goes in a single command. Every ELS command costs a fixed ~8us for AES, measured by sweeping buffer size on frdm_rw612, so a command per 16-byte block is what throughput is actually made of: AES-128-CMAC over 1KB runs at 7.2 MiB/s this way against 775 KiB/s one block at a time. pMac is [in, out] and carries the intermediate state, so it lives in the caller's Cmac rather than in a pool. --- ChangeLog.md | 2 +- wolfcrypt/src/port/nxp/README.md | 16 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 346 +++++++++++++++++++++- wolfssl/wolfcrypt/port/nxp/els_pkc_port.h | 10 + 4 files changed, 365 insertions(+), 9 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index ad09645c683..805de1d46ef 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -191,7 +191,7 @@ * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev -* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR) and AES-GCM. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. +* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM and CMAC. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. ## Fixes diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 5baf1a02d48..b0369419008 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -52,7 +52,8 @@ see [README_SE050.md](./README_SE050.md). `els_pkc_port.c` offloads wolfCrypt to the EdgeLock subsystem found on the RW612 and related parts, through the crypto callback interface. The ELS -peripheral serves SHA-256, SHA-384, SHA-512, AES (ECB/CBC/CTR) and AES-GCM. +peripheral serves SHA-256, SHA-384, SHA-512, AES (ECB/CBC/CTR), AES-GCM and +CMAC. Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size costs @@ -66,9 +67,10 @@ created without an explicit device still reaches the hardware. `WC_NO_DEFAULT_DEVID` turns that off and leaves routing to the caller. Keys that live in the ELS key store are referenced rather than exported. -`wc_ElsPkc_AesUseSlot()` attaches a slot reference to an AES key, so the key -material never leaves the hardware. It is the documented entry point: -`wc_ElsPkc_MakeKeyRef()` is the lower-level primitive it builds on. +`wc_ElsPkc_AesUseSlot()` and `wc_ElsPkc_CmacUseSlot()` attach a slot +reference to a key, so the key material never leaves the hardware. They are +the documented entry point: `wc_ElsPkc_MakeKeyRef()` is the lower-level +primitive they build on. ### Hardware behaviour worth knowing @@ -100,9 +102,9 @@ before the thread sleeps, and zero never spins. **Offload state lives in the caller's object.** A hash keeps its ELS state in the same `wc_Sha256`/`wc_Sha512` fields the software implementation would -have used, so nothing is allocated, a struct copy duplicates a context -correctly, and the port needs neither the copy nor the free crypto-callback -hook. +have used, and a CMAC in the `Cmac` ones, so nothing is allocated, a struct +copy duplicates a context correctly, and the port needs neither the copy nor +the free crypto-callback hook. **What the hardware declines**, so it runs in software instead: AES-192 (no ELS key size), any trailing partial block, and an AES-GCM IV other than 12 diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index e3c103af400..9bed96e3cf4 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -68,7 +68,7 @@ * hardware before issuing a command, because a permission violation resets the * SoC, so they share the helpers in the next section. */ #if defined(ELS_HAVE_AES_BLOCK_CIPHER) || \ - (!defined(NO_AES) && defined(HAVE_AESGCM)) + (!defined(NO_AES) && (defined(HAVE_AESGCM) || defined(WOLFSSL_CMAC))) #define ELS_HAVE_SLOT_KEYS #endif @@ -1215,6 +1215,319 @@ static int ElsAesGcm(Aes* aes, byte* out, const byte* in, word32 sz, #endif /* !NO_AES */ +/* --------------------------------------------------------------------------- + * AES-CMAC + * ------------------------------------------------------------------------ */ + +/* pMac is [in, out] and carries the intermediate state, so it lives in the + * caller's Cmac and the lock is held per call. */ + +#if defined(WOLFSSL_CMAC) && !defined(NO_AES) + +#define ELS_CMAC_BLOCK MCUXCLELS_CIPHER_BLOCK_SIZE_AES +#define ELS_CMAC_STATE MCUXCLELS_CMAC_OUT_SIZE + +/* As with the hash, the offload lives in the caller's object: digest[] is the + * running state, buffer[] the residual block, and the software-only subkeys + * k1||k2 are exactly an AES-256 key's worth of room for a plaintext key. They + * are free because wc_InitCmac() derives them only on the path this callback + * displaces, and wc_CmacFree() zeroes the struct either way. */ +wc_static_assert(ELS_CMAC_STATE == WC_AES_BLOCK_SIZE); +wc_static_assert(ELS_CMAC_BLOCK == WC_AES_BLOCK_SIZE); + +/* Parked in devCtx, tagged like the hash's. The slot index needs a byte; the + * rest are flags. */ +#define ELS_CMAC_OWNED ((wc_ptr_t)0x01) +#define ELS_CMAC_STARTED ((wc_ptr_t)0x02) +#define ELS_CMAC_SLOTTED ((wc_ptr_t)0x04) +#define ELS_CMAC_KEY256 ((wc_ptr_t)0x08) +#define ELS_CMAC_FAILED ((wc_ptr_t)0x10) /* a block never reached it */ +#define ELS_CMAC_SLOT_SHIFT 8 +#define ELS_CMAC_SLOT(st) ((byte)(((st) >> ELS_CMAC_SLOT_SHIFT) & 0xFFu)) + +/* Feed one chunk. Caller holds the lock. */ +static int ElsCmacChunk(Cmac* cmac, const byte* in, word32 len, int final) +{ + mcuxClEls_CmacOption_t opt; + wc_ptr_t st = (wc_ptr_t)cmac->devCtx; + byte key[2 * WC_AES_BLOCK_SIZE]; + size_t keySz = 0; + int ret; + + opt.word.value = 0u; + opt.bits.extkey = (st & ELS_CMAC_SLOTTED) + ? MCUXCLELS_CMAC_EXTERNAL_KEY_DISABLE + : MCUXCLELS_CMAC_EXTERNAL_KEY_ENABLE; + opt.bits.initialize = (st & ELS_CMAC_STARTED) + ? MCUXCLELS_CMAC_INITIALIZE_DISABLE + : MCUXCLELS_CMAC_INITIALIZE_ENABLE; + opt.bits.finalize = final ? MCUXCLELS_CMAC_FINALIZE_ENABLE + : MCUXCLELS_CMAC_FINALIZE_DISABLE; + + if (!(st & ELS_CMAC_SLOTTED)) { + /* k1 and k2 are separate members, so the engine's contiguous key is + * assembled here rather than assumed of the struct layout. */ + XMEMCPY(key, cmac->k1, WC_AES_BLOCK_SIZE); + if (st & ELS_CMAC_KEY256) { + XMEMCPY(key + WC_AES_BLOCK_SIZE, cmac->k2, WC_AES_BLOCK_SIZE); + keySz = sizeof(key); + } + else { + keySz = WC_AES_BLOCK_SIZE; + } + } + + /* As in the cipher path, the unused half of the key pair is passed as + * nothing rather than as an empty key[]. */ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_Cmac_Async( + opt, (mcuxClEls_KeyIndex_t)ELS_CMAC_SLOT(st), + (st & ELS_CMAC_SLOTTED) ? NULL : key, keySz, + in, (size_t)len, cmac->digest)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_Cmac_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + ForceZero(key, sizeof(key)); + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + cmac->devCtx = (void*)(st | ELS_CMAC_STARTED); + + ret = ElsWait(); + ForceZero(key, sizeof(key)); + + return ret; +} + +/* Read the slot reference a Cmac carries, if it carries one at all. */ +static int ElsCmacRef(const Cmac* cmac, wc_ElsPkc_KeyRef* ref) +{ + if (cmac == NULL || cmac->idLen <= 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + return wc_ElsPkc_ParseKeyRef(cmac->id, (word32)cmac->idLen, ref); +} + +/* key == NULL means the key is in the store and the Cmac names the slot. */ +static int ElsCmacInit(Cmac* cmac, const byte* key, word32 keySz) +{ + wc_ElsPkc_KeyRef ref; + wc_ptr_t st = ELS_CMAC_OWNED; + int ret; + + if (cmac == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + XMEMSET(&ref, 0, sizeof(ref)); + if (key == NULL) { + ret = ElsCmacRef(cmac, &ref); + if (ret != 0) { + return ret; + } + st |= ELS_CMAC_SLOTTED | + ((wc_ptr_t)ref.slot << ELS_CMAC_SLOT_SHIFT); + } + else if (keySz != WC_AES_BLOCK_SIZE && keySz != 2 * WC_AES_BLOCK_SIZE) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + else if (keySz == 2 * WC_AES_BLOCK_SIZE) { + st |= ELS_CMAC_KEY256; + } + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + /* Validate the slot before claiming the object, so a bad reference leaves + * the Cmac to software untouched. */ + if (st & ELS_CMAC_SLOTTED) { + ret = ElsCheckSlot(&ref, WC_ELSPKC_KEY_CMAC); + if (ret != 0) { + ElsUnlock(); + return ret; + } + } + else { + XMEMCPY(cmac->k1, key, WC_AES_BLOCK_SIZE); + if (keySz == 2 * WC_AES_BLOCK_SIZE) { + XMEMCPY(cmac->k2, key + WC_AES_BLOCK_SIZE, WC_AES_BLOCK_SIZE); + } + } + + cmac->bufferSz = 0; + cmac->devCtx = (void*)st; + /* _InitCmac_common() returns before wc_AesInit() once the callback takes + * the init, so wc_CmacFree() would otherwise free an all-zero Aes. */ + cmac->aes.devId = INVALID_DEVID; + + ElsUnlock(); + + return 0; +} + +int wc_ElsPkc_CmacUseSlot(Cmac* cmac, const wc_ElsPkc_KeyRef* ref, + void* heap, int devId) +{ + byte blob[WC_ELSPKC_KEYREF_SZ]; + word32 blobSz = sizeof(blob); + int ret; + + if (cmac == NULL || ref == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + /* ucmac is its own permission; a uaes slot is not automatically usable + * here, and a reference for one is not a reference for the other. */ + if (ref->keyClass != WC_ELSPKC_KEY_CMAC) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + ret = wc_ElsPkc_MakeKeyRef(ref, blob, &blobSz); + if (ret == 0) { + /* No key and no size: both live in the slot, and passing NULL is what + * tells the callback this is a key store init. */ + ret = wc_InitCmac_Id(cmac, NULL, 0, WC_CMAC_AES, NULL, + blob, (int)blobSz, heap, devId); + } + + ForceZero(blob, sizeof(blob)); + + return ret; +} + +static int ElsCmacUpdate(Cmac* cmac, const byte* in, word32 inSz) +{ + word32 take, whole; + int ret; + + if (cmac == NULL || cmac->devCtx == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + /* A zero-length update is legal and absorbs nothing; in may be NULL. */ + if (inSz == 0) { + return 0; + } + if (in == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + if ((wc_ptr_t)cmac->devCtx & ELS_CMAC_FAILED) { + ElsUnlock(); + return WC_NO_ERR_TRACE(WC_HW_E); + } + + /* CMAC uses a different subkey for the final chunk and only the final call + * knows which that is, so the last block is always held back. Everything + * before it goes to the engine in one command. */ + if (cmac->bufferSz > 0) { + take = ELS_CMAC_BLOCK - cmac->bufferSz; + if (take > inSz) { + take = inSz; + } + XMEMCPY(cmac->buffer + cmac->bufferSz, in, take); + cmac->bufferSz += take; + in += take; + inSz -= take; + + if (cmac->bufferSz == ELS_CMAC_BLOCK && inSz > 0) { + ret = ElsCmacChunk(cmac, cmac->buffer, ELS_CMAC_BLOCK, 0); + if (ret != 0) { + goto out; + } + cmac->bufferSz = 0; + } + } + + if (inSz > ELS_CMAC_BLOCK) { + whole = ((inSz - 1u) / ELS_CMAC_BLOCK) * ELS_CMAC_BLOCK; + ret = ElsCmacChunk(cmac, in, whole, 0); + if (ret != 0) { + goto out; + } + in += whole; + inSz -= whole; + } + + if (inSz > 0) { + XMEMCPY(cmac->buffer + cmac->bufferSz, in, inSz); + cmac->bufferSz += inSz; + } + +out: + if (ret != 0) { + /* buffer[] and digest[] now describe a message the engine only partly + * absorbed, so nothing this object produces afterwards is a MAC */ + cmac->devCtx = (void*)((wc_ptr_t)cmac->devCtx | ELS_CMAC_FAILED); + } + ElsUnlock(); + + return ret; +} + +static int ElsCmacFinal(Cmac* cmac, byte* out, word32* outSz) +{ + word32 buffered; + int ret; + + if (cmac == NULL || cmac->devCtx == NULL || out == NULL || outSz == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + /* Truncation to any length in [WC_CMAC_TAG_MIN_SZ, WC_CMAC_TAG_MAX_SZ] is + * what wc_AesCmacVerify_ex() asks for. Refusing it is not a fallback: the + * message is already absorbed and no software state remains. */ + if (*outSz < WC_CMAC_TAG_MIN_SZ || *outSz > ELS_CMAC_STATE) { + return WC_NO_ERR_TRACE(BUFFER_E); + } + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + if ((wc_ptr_t)cmac->devCtx & ELS_CMAC_FAILED) { + ElsUnlock(); + return WC_NO_ERR_TRACE(WC_HW_E); + } + + /* ELS takes inputLength as the length *before* padding but expects the + * block already padded per SP 800-38B. Getting this wrong is quiet: exact + * block multiples come out correct and every other length is wrong. The + * zero-length case, where the engine is handed a padded block and a length + * of 0, is RFC 4493 example 1 and is checked on hardware by cmac_test(). */ + buffered = cmac->bufferSz; + if (buffered < ELS_CMAC_BLOCK) { + cmac->buffer[buffered] = 0x80; + if (buffered + 1u < ELS_CMAC_BLOCK) { + XMEMSET(cmac->buffer + buffered + 1u, 0, + ELS_CMAC_BLOCK - buffered - 1u); + } + } + + ret = ElsCmacChunk(cmac, cmac->buffer, buffered, 1); + if (ret == 0) { + XMEMCPY(out, cmac->digest, *outSz); + } + + /* The key is not needed again, and wc_CmacFinalNoFree() leaves the object + * to the caller. */ + ForceZero(cmac->k1, WC_AES_BLOCK_SIZE); + ForceZero(cmac->k2, WC_AES_BLOCK_SIZE); + cmac->bufferSz = 0; + cmac->devCtx = NULL; + + ElsUnlock(); + + return ret; +} + +#endif /* WOLFSSL_CMAC && !NO_AES */ + /* --------------------------------------------------------------------------- * Dispatch * ------------------------------------------------------------------------ */ @@ -1234,6 +1547,37 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) } switch (info->algo_type) { +#if defined(WOLFSSL_CMAC) && !defined(NO_AES) + case WC_ALGO_TYPE_CMAC: + if (info->cmac.type != WC_CMAC_AES || info->cmac.cmac == NULL) { + break; + } + /* cmac.c distinguishes init / update / final by which pointers + * are present. A key store init carries no key material, which + * looks like a zero-length update; devCtx separates them. */ + if (info->cmac.in == NULL && info->cmac.out == NULL && + info->cmac.cmac->bufferSz == 0 && + (info->cmac.key != NULL || info->cmac.cmac->devCtx == NULL)) { + ret = ElsCmacInit(info->cmac.cmac, info->cmac.key, + info->cmac.keySz); + } + else if (info->cmac.out == NULL && + info->cmac.cmac->devCtx != NULL) { + /* in == NULL with a zero length is a legal update, and for an + * object this port owns software has no key schedule to fall + * back on - init returned through the device. */ + ret = ElsCmacUpdate(info->cmac.cmac, info->cmac.in, + info->cmac.inSz); + } + else if (info->cmac.out != NULL && info->cmac.key == NULL && + info->cmac.in == NULL) { + ret = ElsCmacFinal(info->cmac.cmac, info->cmac.out, + info->cmac.outSz); + } + /* the one-shot form (key + in + out together) is left to software: + * it would need init/update/final stitched here for no gain */ + break; +#endif #ifndef NO_AES case WC_ALGO_TYPE_CIPHER: diff --git a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h index a59f0a006b2..4aa7b6a9c74 100644 --- a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h +++ b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h @@ -43,6 +43,9 @@ #ifndef NO_AES #include #endif +#if defined(WOLFSSL_CMAC) && !defined(NO_AES) + #include +#endif #ifdef __cplusplus extern "C" { @@ -131,6 +134,13 @@ WOLFSSL_API int wc_ElsPkc_AesUseSlot(Aes* aes, const wc_ElsPkc_KeyRef* ref, void* heap, int devId); #endif +#if defined(WOLFSSL_CMAC) && !defined(NO_AES) +/* Same, for a Cmac. The slot must carry ucmac, which is a separate permission + * from uaes, so a slot holding both needs one reference per class. */ +WOLFSSL_API int wc_ElsPkc_CmacUseSlot(Cmac* cmac, const wc_ElsPkc_KeyRef* ref, + void* heap, int devId); +#endif + /* Bring the EdgeLock subsystem up and register the crypto callback. * wolfCrypt_Init() already does this; call it directly only to re-register * after a wolfCrypt_Cleanup(). Safe to call more than once. */ From 6f97d3bf3f6e2dfa8884741e7be8c87f41205687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:24 +0200 Subject: [PATCH 06/16] nxp: offload the DRBG to the EdgeLock Serves WC_ALGO_TYPE_SEED as well as WC_ALGO_TYPE_RNG, so wolfCrypt's own Hash-DRBG is seeded from the hardware rather than only the direct generate path being accelerated. The engine takes at least four bytes and only whole words. With the driver's parameter checks compiled out, a sub-word length would program the DMA past the end of the caller's buffer, and odd sizes are ordinary (wc_RNG_GenerateByte), so the tail comes from a word-sized scratch. --- ChangeLog.md | 2 +- wolfcrypt/src/port/nxp/README.md | 4 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 86 +++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 805de1d46ef..8891510f602 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -191,7 +191,7 @@ * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev -* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM and CMAC. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. +* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC and the DRBG. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. ## Fixes diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index b0369419008..0ae159481f7 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -52,8 +52,8 @@ see [README_SE050.md](./README_SE050.md). `els_pkc_port.c` offloads wolfCrypt to the EdgeLock subsystem found on the RW612 and related parts, through the crypto callback interface. The ELS -peripheral serves SHA-256, SHA-384, SHA-512, AES (ECB/CBC/CTR), AES-GCM and -CMAC. +peripheral serves SHA-256, SHA-384, SHA-512, AES (ECB/CBC/CTR), AES-GCM, CMAC +and the DRBG. Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size costs diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index 9bed96e3cf4..8066bb75436 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -1215,6 +1215,82 @@ static int ElsAesGcm(Aes* aes, byte* out, const byte* in, word32 sz, #endif /* !NO_AES */ +/* --------------------------------------------------------------------------- + * Random + * ------------------------------------------------------------------------ */ + +/* Serving WC_ALGO_TYPE_SEED as well as _RNG means wolfCrypt's own Hash-DRBG is + * seeded from the hardware, not just the direct generate path. */ + +#ifndef WC_NO_RNG + +/* Issue one DRBG request. Caller holds the lock, and len must already satisfy + * the engine's contract. */ +static int ElsRandomRun(byte* out, word32 len) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, + mcuxClEls_Rng_DrbgRequest_Async(out, (size_t)len)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_Rng_DrbgRequest_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return ElsWait(); +} + +static int ElsRandom(byte* out, word32 sz) +{ + /* The DRBG takes at least four bytes and only whole words; with the + * driver's parameter checks compiled out a sub-word length would program + * the DMA past the end of the caller's buffer. Odd sizes are ordinary + * (wc_RNG_GenerateByte), so the tail comes from a word-sized scratch. */ + ALIGN32 byte tail[MCUXCLELS_RNG_DRBG_TEST_EXTRACT_OUTPUT_MIN_SIZE]; + word32 whole; + word32 rest; + int ret; + + if (out == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + if (sz == 0) { + return 0; + } + + whole = sz & ~(word32)(sizeof(tail) - 1u); + rest = sz - whole; + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + /* One request is capped; the caller's size is not. */ + while (ret == 0 && whole > 0) { + word32 chunk = whole; + if (chunk > MCUXCLELS_RNG_DRBG_TEST_EXTRACT_OUTPUT_MAX_SIZE) { + chunk = MCUXCLELS_RNG_DRBG_TEST_EXTRACT_OUTPUT_MAX_SIZE; + } + ret = ElsRandomRun(out, chunk); + out += chunk; + whole -= chunk; + } + if (ret == 0 && rest > 0) { + ret = ElsRandomRun(tail, (word32)sizeof(tail)); + if (ret == 0) { + XMEMCPY(out, tail, rest); + } + } + + ElsUnlock(); + + ForceZero(tail, sizeof(tail)); + + return ret; +} + +#endif /* !WC_NO_RNG */ + /* --------------------------------------------------------------------------- * AES-CMAC * ------------------------------------------------------------------------ */ @@ -1579,6 +1655,16 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) break; #endif +#ifndef WC_NO_RNG + case WC_ALGO_TYPE_RNG: + ret = ElsRandom(info->rng.out, info->rng.sz); + break; + + case WC_ALGO_TYPE_SEED: + ret = ElsRandom(info->seed.seed, info->seed.sz); + break; +#endif + #ifndef NO_AES case WC_ALGO_TYPE_CIPHER: switch (info->cipher.type) { From 8c915d040886d9bdd2d2748226f03c9c31a22721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:28 +0200 Subject: [PATCH 07/16] nxp: offload P-256 to EdgeLock key slots The three P-256 operations that can use a slot reference: in-slot generation, signing and verification. Key generation asks for as little as it can. The usage bit, the key size and the slot kind are set by the engine from kgtypedh, and a property word ELS disagrees with is a tamper event rather than an error return, so only the two access-protection bits are passed, plus wrpok when the reference asks for an exportable key. The properties are read back before first use. Verification carries the sharpest trap in this port: ELS does not report a bad signature through its return status. It recomputes R and hands it back, and the signature is valid only if that equals the R the caller supplied. A port that trusted the status would pass every positive test and accept every forgery, so the comparison is done here, in constant time. An all-zero digest is declined. wolfCrypt rejects one too, but below the callback, so an offload would otherwise accept what software refuses. ECDH is absent by design. mcuxClEls_EccKeyExchange_Async deposits the shared secret in a slot, which cannot be read back in the clear, while the ecdh callback must hand a buffer back. An in-slot agreement is reachable through the keystore derive path. ELS speaks raw X9.62 throughout, points as X||Y and signatures as R||S, while the callback boundary is DER, so the conversion happens here. --- ChangeLog.md | 2 +- wolfcrypt/src/port/nxp/README.md | 21 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 443 +++++++++++++++++++++- wolfssl/wolfcrypt/port/nxp/els_pkc_port.h | 18 + 4 files changed, 476 insertions(+), 8 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 8891510f602..1c7b540dd40 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -191,7 +191,7 @@ * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev -* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC and the DRBG. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. +* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG and ECDSA on P-256 from a key slot. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. ## Fixes diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 0ae159481f7..dda4b2113b1 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -52,8 +52,8 @@ see [README_SE050.md](./README_SE050.md). `els_pkc_port.c` offloads wolfCrypt to the EdgeLock subsystem found on the RW612 and related parts, through the crypto callback interface. The ELS -peripheral serves SHA-256, SHA-384, SHA-512, AES (ECB/CBC/CTR), AES-GCM, CMAC -and the DRBG. +peripheral serves SHA-256, SHA-384, SHA-512, AES (ECB/CBC/CTR), AES-GCM, +CMAC, the DRBG, and ECDSA on P-256 for a key that names a slot. Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size costs @@ -67,10 +67,11 @@ created without an explicit device still reaches the hardware. `WC_NO_DEFAULT_DEVID` turns that off and leaves routing to the caller. Keys that live in the ELS key store are referenced rather than exported. -`wc_ElsPkc_AesUseSlot()` and `wc_ElsPkc_CmacUseSlot()` attach a slot -reference to a key, so the key material never leaves the hardware. They are -the documented entry point: `wc_ElsPkc_MakeKeyRef()` is the lower-level -primitive they build on. +`wc_ElsPkc_EccUseSlot()`, `wc_ElsPkc_AesUseSlot()` and +`wc_ElsPkc_CmacUseSlot()` attach a slot reference to a key, so the private +key never leaves the hardware. They are the documented entry point: +`wc_ElsPkc_MakeKeyRef()` is the lower-level primitive they build on, and for +ECC the helper additionally pins the curve, which the offload requires. ### Hardware behaviour worth knowing @@ -106,10 +107,18 @@ have used, and a CMAC in the `Cmac` ones, so nothing is allocated, a struct copy duplicates a context correctly, and the port needs neither the copy nor the free crypto-callback hook. +ELS declines an ECDSA digest that is not a full 32 bytes - reachable, since +`wc_ecc_sign_hash()` takes a digest of any length and a SHA-1 hash under +P-256 is 20 bytes - so that signature is made in software. + **What the hardware declines**, so it runs in software instead: AES-192 (no ELS key size), any trailing partial block, and an AES-GCM IV other than 12 bytes. +**ECDH is not offloaded at all.** ELS deposits the agreed secret in a key +slot, which cannot be read back, while the wolfCrypt ECDH callback has to +hand a buffer to its caller - so the callback declines and software answers. + ### Vendor library The port depends on NXP's CLNS library, `els_pkc`, which ships as an diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index 8066bb75436..4fe6d879083 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -38,6 +38,9 @@ #include #include #include +#ifdef HAVE_ECC + #include +#endif #include #include @@ -68,7 +71,8 @@ * hardware before issuing a command, because a permission violation resets the * SoC, so they share the helpers in the next section. */ #if defined(ELS_HAVE_AES_BLOCK_CIPHER) || \ - (!defined(NO_AES) && (defined(HAVE_AESGCM) || defined(WOLFSSL_CMAC))) + (!defined(NO_AES) && (defined(HAVE_AESGCM) || defined(WOLFSSL_CMAC))) || \ + defined(HAVE_ECC) #define ELS_HAVE_SLOT_KEYS #endif @@ -366,6 +370,22 @@ int wc_ElsPkc_ParseKeyRef(const byte* in, word32 inSz, wc_ElsPkc_KeyRef* ref) /* Slot validation, reached from every path that can name a slot. */ #ifdef ELS_HAVE_SLOT_KEYS +#ifdef HAVE_ECC +/* Read a slot's properties. Caller holds the lock. */ +static int ElsKsProps(byte slot, mcuxClEls_KeyProp_t* prop) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, + mcuxClEls_GetKeyProperties((mcuxClEls_KeyIndex_t)slot, prop)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_GetKeyProperties) != t) || + (MCUXCLELS_STATUS_OK != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return 0; +} +#endif + /* The ELS permission bit each class stands for. */ static word32 ElsClassUsageBit(byte keyClass) { @@ -1215,6 +1235,375 @@ static int ElsAesGcm(Aes* aes, byte* out, const byte* in, word32 sz, #endif /* !NO_AES */ +/* --------------------------------------------------------------------------- + * ECC over slot keys + * ------------------------------------------------------------------------ */ + +/* P-256 only: the key property word has no bit for RSA or the Ed curves. ELS + * speaks raw X9.62 (X||Y, R||S) where the callback boundary is DER. */ + +#ifdef HAVE_ECC + +#define ELS_ECC_COORD_SZ 32 /* P-256 */ +#define ELS_ECC_PUB_SZ MCUXCLELS_ECC_PUBLICKEY_SIZE /* X||Y */ +#define ELS_ECC_SIG_SZ MCUXCLELS_ECC_SIGNATURE_SIZE /* R||S */ + +#if (defined(HAVE_ECC_SIGN) || defined(HAVE_ECC_VERIFY)) && \ + !defined(WC_ALLOW_ECC_ZERO_HASH) +/* wolfCrypt rejects an all-zero digest, but only below the callback, so an + * offload would accept what software refuses. Declining keeps one contract + * and one copy of the rule. */ +static int ElsEccDigestIsZero(const byte* d, word32 len) +{ + byte acc = 0; + word32 i; + + for (i = 0; i < len; i++) { + acc |= d[i]; + } + + return acc == 0; +} +#endif + +#ifdef HAVE_ECC_VERIFY +/* DecodeECC_DSA_Sig_Bin() reports each INTEGER's own length, not the field + * width, so a component with leading zero bytes comes back short - about one + * verify in 128 for P-256, and almost every one for P-521, whose order is 521 + * bits. The engine wants fixed-width big-endian, so right-align rather than + * decline. */ +static int ElsPadSigComponent(byte* buf, word32 len, word32 width) +{ + if (len > width) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (len < width) { + XMEMMOVE(buf + (width - len), buf, len); + XMEMSET(buf, 0, width - len); + } + + return 0; +} +#endif /* HAVE_ECC_VERIFY */ + +/* Read the slot reference an ecc_key carries, if it carries one at all. */ +static int ElsEccRef(const ecc_key* key, wc_ElsPkc_KeyRef* ref) +{ + if (key == NULL || key->idLen <= 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + return wc_ElsPkc_ParseKeyRef(key->id, (word32)key->idLen, ref); +} + +/* P-256 is the only curve with a slot to reference. */ +static int ElsEccCurveOk(const ecc_key* key) +{ + return (key != NULL && key->dp != NULL && + key->dp->id == ECC_SECP256R1); +} + +int wc_ElsPkc_EccUseSlot(ecc_key* key, const wc_ElsPkc_KeyRef* ref, + void* heap, int devId) +{ + byte blob[WC_ELSPKC_KEYREF_SZ]; + word32 blobSz = sizeof(blob); + int ret; + + if (key == NULL || ref == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + if (ref->keyClass != WC_ELSPKC_KEY_ECC_SIGN && + ref->keyClass != WC_ELSPKC_KEY_ECC_DH) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + ret = wc_ElsPkc_MakeKeyRef(ref, blob, &blobSz); + if (ret == 0) { + ret = wc_ecc_init_id(key, blob, (int)blobSz, heap, devId); + } + if (ret == 0) { + /* Pin the curve now. Without it the first operation would have to + * infer P-256 from a key whose private part it can never see. */ + ret = wc_ecc_set_curve(key, ELS_ECC_COORD_SZ, ECC_SECP256R1); + if (ret != 0) { + wc_ecc_free(key); + } + } + + ForceZero(blob, sizeof(blob)); + + return ret; +} + +/* Generate a key inside the slot the reference names. The reference is a + * request, not a lookup: the slot is empty and the flags say what the key may + * do. This is also the only moment the public point is available. */ +#if defined(HAVE_ECC_KEY_IMPORT) && defined(HAVE_ECC_DHE) +static int ElsEccKeyGen(ecc_key* key) +{ + mcuxClEls_EccKeyGenOption_t opt; + mcuxClEls_KeyProp_t prop; + mcuxClEls_KeyProp_t cur; + wc_ElsPkc_KeyRef ref; + ALIGN32 byte pub[ELS_ECC_PUB_SZ]; + int ret; + + ret = ElsEccRef(key, &ref); + if (ret != 0) { + return ret; + } + if (!ElsEccCurveOk(key)) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (ref.keyClass != WC_ELSPKC_KEY_ECC_SIGN && + ref.keyClass != WC_ELSPKC_KEY_ECC_DH) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + /* Ask for as little as possible. The usage bit, key size and slot kind are + * set by the hardware from kgtypedh, and a property word ELS disagrees + * with is a tamper event, not an error return. ElsCheckSlot() reads the + * bits back before first use. */ + prop.word.value = 0u; + prop.bits.upprot_priv = MCUXCLELS_KEYPROPERTY_PRIVILEGED_FALSE; + prop.bits.upprot_sec = MCUXCLELS_KEYPROPERTY_SECURE_FALSE; + if (ref.flags & WC_ELSPKC_REF_FLAG_EXPORTABLE) { + prop.bits.wrpok = MCUXCLELS_KEYPROPERTY_WRAP_TRUE; + } + /* WC_ELSPKC_REF_FLAG_PERSISTENT is parsed but not applied: retention is a + * slot attribute, not something a generation requests. */ + + opt.word.value = 0u; + opt.bits.kgsrc = MCUXCLELS_ECC_OUTPUTKEY_RANDOM; + opt.bits.kgtypedh = (ref.keyClass == WC_ELSPKC_KEY_ECC_DH) + ? MCUXCLELS_ECC_OUTPUTKEY_KEYEXCHANGE + : MCUXCLELS_ECC_OUTPUTKEY_SIGN; + opt.bits.kgsign = MCUXCLELS_ECC_PUBLICKEY_SIGN_DISABLE; + opt.bits.kgsign_rnd = MCUXCLELS_ECC_NO_RANDOM_DATA; + opt.bits.skip_pbk = MCUXCLELS_ECC_GEN_PUBLIC_KEY; + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + /* Validate the target even though it should still be empty, and refuse an + * occupied slot: overwriting a key nobody asked to replace is not this + * function's decision. */ + ret = ElsKsProps(ref.slot, &cur); + if (ret == 0 && (cur.word.value & MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE)) { + ret = WC_NO_ERR_TRACE(BAD_STATE_E); + } + if (ret != 0) { + ElsUnlock(); + return ret; + } + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_EccKeyGen_Async( + opt, 0u, (mcuxClEls_KeyIndex_t)ref.slot, prop, NULL, pub)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_EccKeyGen_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + ElsUnlock(); + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + ret = ElsWait(); + + ElsUnlock(); + + if (ret == 0) { + /* Keep the id[] reference: wc_ecc_import_unsigned would otherwise be + * the last word on this key and the slot would be forgotten. */ + byte savedId[ECC_MAX_ID_LEN]; + int savedLen = key->idLen; + + XMEMCPY(savedId, key->id, sizeof(savedId)); + + ret = wc_ecc_import_unsigned(key, pub, pub + ELS_ECC_COORD_SZ, + NULL, ECC_SECP256R1); + /* Restored even on failure: the key now exists in the slot, and the + * reference is the only way left to name or delete it - the hardware + * will not hand the public point back a second time. */ + XMEMCPY(key->id, savedId, sizeof(savedId)); + key->idLen = savedLen; + if (ret == 0) { + /* The import saw no d and typed the key public; the private half + * is in the slot, and export and wc_ecc_check_key() test for it. */ + key->type = ECC_PRIVATEKEY; + } + ForceZero(savedId, sizeof(savedId)); + } + + ForceZero(pub, sizeof(pub)); + + return ret; +} + +#endif /* HAVE_ECC_KEY_IMPORT && HAVE_ECC_DHE */ + +#ifdef HAVE_ECC_SIGN +/* Issue the sign command. Caller holds the lock and has validated the slot. + * Split out so the flow-protection macro pair stays within one scope. */ +static int ElsEccSignRun(mcuxClEls_EccSignOption_t opt, byte slot, + const byte* hash, byte* sig) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_EccSign_Async( + opt, (mcuxClEls_KeyIndex_t)slot, hash, NULL, 0u, sig)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_EccSign_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return ElsWait(); +} + +static int ElsEccSign(const byte* in, word32 inlen, byte* out, word32* outlen, + ecc_key* key) +{ + mcuxClEls_EccSignOption_t opt; + wc_ElsPkc_KeyRef ref; + ALIGN32 byte hash[ELS_ECC_COORD_SZ]; + ALIGN32 byte sig[ELS_ECC_SIG_SZ]; + int ret; + + if (in == NULL || out == NULL || outlen == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + ret = ElsEccRef(key, &ref); + if (ret != 0) { + return ret; + } + if (!ElsEccCurveOk(key) || ref.keyClass != WC_ELSPKC_KEY_ECC_SIGN) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + /* ELS consumes a full 32-byte digest. Rather than guess whether a caller + * wants left-padding or truncation, decline. */ + if (inlen != ELS_ECC_COORD_SZ) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + XMEMCPY(hash, in, ELS_ECC_COORD_SZ); + + opt.word.value = 0u; + opt.bits.echashchl = MCUXCLELS_ECC_HASHED; + opt.bits.signrtf = MCUXCLELS_ECC_NO_RTF; + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + ret = ElsCheckSlot(&ref, WC_ELSPKC_KEY_ECC_SIGN); + if (ret == 0) { + ret = ElsEccSignRun(opt, ref.slot, hash, sig); + } + + ElsUnlock(); + + if (ret == 0) { + /* ELS returns raw R||S; the callback boundary is DER. */ + ret = StoreECC_DSA_Sig_Bin(out, outlen, + sig, ELS_ECC_COORD_SZ, + sig + ELS_ECC_COORD_SZ, ELS_ECC_COORD_SZ); + } + + ForceZero(hash, sizeof(hash)); + ForceZero(sig, sizeof(sig)); + + return ret; +} + +#endif /* HAVE_ECC_SIGN */ + +#ifdef HAVE_ECC_VERIFY +/* Verification needs no slot - the public key is ordinary wolfCrypt material, + * so this accelerates any P-256 verify, vaulted key or not. */ +static int ElsEccVerify(const byte* sig, word32 siglen, const byte* hashIn, + word32 hashlen, int* res, ecc_key* key) +{ + mcuxClEls_EccVerifyOption_t opt; + ALIGN32 byte sigAndPub[ELS_ECC_SIG_SZ + ELS_ECC_PUB_SZ]; + ALIGN32 byte hash[ELS_ECC_COORD_SZ]; + ALIGN32 byte rCalc[ELS_ECC_COORD_SZ]; + byte x963[1 + ELS_ECC_PUB_SZ]; + word32 x963Sz = sizeof(x963); + word32 rLen = ELS_ECC_COORD_SZ, sLen = ELS_ECC_COORD_SZ; + int ret; + + if (sig == NULL || hashIn == NULL || res == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + if (!ElsEccCurveOk(key) || hashlen != ELS_ECC_COORD_SZ) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + *res = 0; + + /* DER in, raw R||S out, both fixed 32-byte components. */ + ret = DecodeECC_DSA_Sig_Bin(sig, siglen, sigAndPub, &rLen, + sigAndPub + ELS_ECC_COORD_SZ, &sLen); + if (ret != 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + ret = ElsPadSigComponent(sigAndPub, rLen, ELS_ECC_COORD_SZ); + if (ret == 0) { + ret = ElsPadSigComponent(sigAndPub + ELS_ECC_COORD_SZ, sLen, + ELS_ECC_COORD_SZ); + } + if (ret != 0) { + return ret; + } + + /* X9.62 uncompressed is 0x04 || X || Y; ELS wants the point without the + * leading tag byte. */ + ret = wc_ecc_export_x963_ex(key, x963, &x963Sz, 0); + if (ret != 0 || x963Sz != sizeof(x963) || x963[0] != 0x04) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + XMEMCPY(sigAndPub + ELS_ECC_SIG_SZ, x963 + 1, ELS_ECC_PUB_SZ); + XMEMCPY(hash, hashIn, ELS_ECC_COORD_SZ); + + opt.word.value = 0u; + opt.bits.echashchl = MCUXCLELS_ECC_HASHED; + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_EccVerify_Async( + opt, hash, NULL, 0u, sigAndPub, rCalc)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_EccVerify_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + ElsUnlock(); + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + ret = ElsWait(); + + ElsUnlock(); + + if (ret == 0) { + /* THE RESULT IS THE COMPARISON, NOT THE STATUS. ELS does not report a + * bad signature through the return code: it recomputes R, and the + * verification succeeds only if that equals the caller's R. */ + *res = (wc_ConstantCompare(rCalc, sigAndPub, ELS_ECC_COORD_SZ) == 0); + } + + ForceZero(hash, sizeof(hash)); + ForceZero(rCalc, sizeof(rCalc)); + + return ret; +} + +#endif /* HAVE_ECC_VERIFY */ + +#endif /* HAVE_ECC */ + /* --------------------------------------------------------------------------- * Random * ------------------------------------------------------------------------ */ @@ -1665,6 +2054,58 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) break; #endif +#ifdef HAVE_ECC + case WC_ALGO_TYPE_PK: + switch (info->pk.type) { + /* info->pk.eckg itself is declared under HAVE_ECC_DHE. */ + #if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_IMPORT) && \ + defined(HAVE_ECC_DHE) + case WC_PK_TYPE_EC_KEYGEN: + ret = ElsEccKeyGen(info->pk.eckg.key); + break; + #endif + #if defined(HAVE_ECC) && defined(HAVE_ECC_SIGN) + case WC_PK_TYPE_ECDSA_SIGN: + #ifndef WC_ALLOW_ECC_ZERO_HASH + if (ElsEccDigestIsZero(info->pk.eccsign.in, + info->pk.eccsign.inlen)) { + break; + } + #endif + /* ELS first: it signs only for a P-256 key that names a + * slot, and declines everything else, which the PKC + * then covers. */ + ret = ElsEccSign(info->pk.eccsign.in, + info->pk.eccsign.inlen, info->pk.eccsign.out, + info->pk.eccsign.outlen, info->pk.eccsign.key); + break; + #endif + #if defined(HAVE_ECC) && defined(HAVE_ECC_VERIFY) + case WC_PK_TYPE_ECDSA_VERIFY: + #ifndef WC_ALLOW_ECC_ZERO_HASH + if (ElsEccDigestIsZero(info->pk.eccverify.hash, + info->pk.eccverify.hashlen)) { + break; + } + #endif + ret = ElsEccVerify(info->pk.eccverify.sig, + info->pk.eccverify.siglen, + info->pk.eccverify.hash, + info->pk.eccverify.hashlen, + info->pk.eccverify.res, + info->pk.eccverify.key); + break; + #endif + /* WC_PK_TYPE_ECDH is deliberately absent: ELS deposits the + * shared secret in a slot, which cannot be read back, while + * the ecdh callback must hand a buffer back. The keystore + * derive path covers an in-slot agreement. */ + default: + break; + } + break; +#endif /* HAVE_ECC */ + #ifndef NO_AES case WC_ALGO_TYPE_CIPHER: switch (info->cipher.type) { diff --git a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h index 4aa7b6a9c74..4a67a873a29 100644 --- a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h +++ b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h @@ -38,8 +38,18 @@ #error "WOLFSSL_ELS_PKC key slot references require WOLF_PRIVATE_KEY_ID" #endif +/* The ECDSA arms exchange DER with ecc.c through StoreECC_DSA_Sig_Bin() and + * DecodeECC_DSA_Sig_Bin(); NO_ASN removes both and switches ecc.c to raw + * R||S, so the offload would be wrong even if it linked. */ +#if defined(HAVE_ECC) && defined(NO_ASN) + #error "WOLFSSL_ELS_PKC ECC support requires ASN.1" +#endif + #include #include +#ifdef HAVE_ECC + #include +#endif #ifndef NO_AES #include #endif @@ -125,6 +135,14 @@ WOLFSSL_API int wc_ElsPkc_MakeKeyRef(const wc_ElsPkc_KeyRef* ref, byte* out, WOLFSSL_API int wc_ElsPkc_ParseKeyRef(const byte* in, word32 inSz, wc_ElsPkc_KeyRef* ref); +#ifdef HAVE_ECC +/* Initialise an ecc_key that names an ELS slot instead of holding a private + * key, and bind it to the port's devId and to P-256. For a key generation the + * reference is a request: the key does not exist until wc_ecc_make_key(). */ +WOLFSSL_API int wc_ElsPkc_EccUseSlot(ecc_key* key, const wc_ElsPkc_KeyRef* ref, + void* heap, int devId); +#endif + #ifndef NO_AES /* Same, for an Aes that names a slot. aes->keylen stays 0, because * wc_AesInit_Id() runs no key schedule and the engine takes the size from the From b764c08c071494fc851453198acabba357f5fca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:32 +0200 Subject: [PATCH 08/16] nxp: open a PKC session and offload RSA Adds the second engine on this part. Where ELS is a fixed-function block driven by a single async command, the PKC is a general modular-arithmetic coprocessor reached through a session owning two workareas. Only the CPU half is the caller's to allocate. The PKC half is a fixed hardware region that mcuxClSession_init() takes as a pointer, which makes it look like memory the caller chose. That shared region is also why PKC work runs under the same lock as ELS: two concurrent operations would corrupt each other's intermediates rather than merely race. The session instantiates mcuxClRandomModes_Mode_ELS_Drbg, which carries 128-bit strength and is instantiated in microseconds, and takes the full SP800-90A CTR_DRBG only above RSA-4096 and P-384, which is the split the vendor's own PSA driver makes. DRG.3 unconditionally costs about 90ms, enough to make RSA public ten times slower than software. RSA is claimed at the raw primitive, since the callback sits in wc_RsaFunction_ex beneath all padding: NoVerify is RSAVP1 and NoEncode is RSASP1. That is also the well-formed corner of the vendor API, as every encrypt and decrypt workarea macro on this part still carries an unsubstituted template placeholder. Private operations use CRT when the key carries its factors. The raw public operation reports VERIFYPRIMITIVE_OK, not VERIFY_OK; the latter belongs to the padded modes. Accepting only VERIFY_OK rejects every successful RSAVP1. Nothing here is vaulted: the PKC tier accelerates keys in ordinary memory, which is precisely what the ELS key store cannot hold. --- ChangeLog.md | 2 +- wolfcrypt/src/port/nxp/README.md | 11 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 392 +++++++++++++++++++++++++- 3 files changed, 400 insertions(+), 5 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 1c7b540dd40..e66f81f7b7d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -191,7 +191,7 @@ * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev -* Added an NXP EdgeLock (ELS) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG and ECDSA on P-256 from a key slot. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. +* Added an NXP EdgeLock (ELS + PKC) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG and ECDSA on P-256 from a key slot; the PKC coprocessor serves RSA. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. ## Fixes diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index dda4b2113b1..10d68be51b2 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -48,12 +48,13 @@ NOTE: Both can be defined with no problem. For details on wolfSSL integration with NXP SE050, see [README_SE050.md](./README_SE050.md). -## NXP EdgeLock (ELS) +## NXP EdgeLock (ELS + PKC) `els_pkc_port.c` offloads wolfCrypt to the EdgeLock subsystem found on the RW612 and related parts, through the crypto callback interface. The ELS peripheral serves SHA-256, SHA-384, SHA-512, AES (ECB/CBC/CTR), AES-GCM, -CMAC, the DRBG, and ECDSA on P-256 for a key that names a slot. +CMAC, the DRBG, and ECDSA on P-256 for a key that names a slot; the PKC +coprocessor serves RSA. Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size costs @@ -119,6 +120,12 @@ bytes. slot, which cannot be read back, while the wolfCrypt ECDH callback has to hand a buffer to its caller - so the callback declines and software answers. +**The PKC uses a CTR_DRBG, not the ELS DRBG.** The ELS DRBG's security +strength is 128 bits on this part, which the PKC's own key operations +outgrow. A plain `wc_GenerateRandom()` is a different path and does reach the +ELS DRBG directly. The PKC workarea is a fixed hardware region shared by +every PKC consumer, which is why PKC work runs under the same lock as ELS. + ### Vendor library The port depends on NXP's CLNS library, `els_pkc`, which ships as an diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index 4fe6d879083..94d3ef59c68 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -1604,6 +1604,383 @@ static int ElsEccVerify(const byte* sig, word32 siglen, const byte* hashIn, #endif /* HAVE_ECC */ +/* --------------------------------------------------------------------------- + * PKC coprocessor + * ------------------------------------------------------------------------ */ + +/* The PKC workarea is a FIXED HARDWARE REGION at PKC_RAM_ADDR, not memory the + * caller chose, even though mcuxClSession_init() takes it as a pointer. That + * shared region is why PKC work runs under the same lock as ELS. Nothing here + * is vaulted: the PKC accelerates ordinary in-memory key material. */ + +/* Shared by every PKC consumer - RSA, ECDSA and X25519 - so guarded on the + * union of them, not on RSA alone. */ +#if !defined(NO_RSA) || defined(HAVE_CURVE25519) || \ + (defined(HAVE_ECC) && \ + (defined(HAVE_ECC_SIGN) || defined(HAVE_ECC_VERIFY))) +#define ELS_PKC_HAVE_SESSION + +#include +#include /* MCUXCLPKC_PACKARGS4, used by the ECC + * domain-parameter packing macro */ +#include +#include +#if defined(HAVE_ECC) || defined(HAVE_CURVE25519) + /* Shared by the ECDSA and Montgomery-curve paths, either of which + * can be built without the other. */ + #include +#endif +#ifndef NO_RSA + #include +#endif + +/* Fixed PKC RAM window on rw61x, mirrored from the vendor platform header so a + * build that does not export ip_platform.h still gets the right region. */ +#ifndef WOLFSSL_ELS_PKC_RAM_ADDR + #define WOLFSSL_ELS_PKC_RAM_ADDR 0x5015A000u +#endif +#ifndef WOLFSSL_ELS_PKC_RAM_SIZE + #define WOLFSSL_ELS_PKC_RAM_SIZE 0x2000u +#endif + +/* mcuxClSession takes two work areas: this one in ordinary RAM, for the + * library's own bookkeeping, and separately the fixed PKC window above. Large + * enough for every operation this port claims: RSA sign/verify tops out at 536 + * bytes (4096-bit, NoEncode) and ECC at 504. */ +#ifndef WOLFSSL_ELS_PKC_CPU_WA_SZ + #define WOLFSSL_ELS_PKC_CPU_WA_SZ 1024 +#endif + +static uint32_t elsPkcCpuWa[(WOLFSSL_ELS_PKC_CPU_WA_SZ + 3u) / 4u]; +static uint32_t elsPkcRngCtx[64]; +/* The vendor publishes the context size per mode; a bigger SDK context would + * otherwise overflow this .bss object silently. */ +wc_static_assert(sizeof(elsPkcRngCtx) >= + MCUXCLRANDOMMODES_CTR_DRBG_AES256_CONTEXT_SIZE); + +/* The DRBG the PKC operations draw from. Not the ELS one: its security + * strength is 128 bits on this part, so mcuxClEcc_Sign returns RNG_ERROR for + * P-384 and P-521 - a failure that names the random source, not the curve. The + * CTR_DRBG at 256 bits covers every selectable curve. Caller holds the lock. */ +/* DRG.3 is a full SP800-90A CTR_DRBG instantiation and reseeds from the + * entropy source, which costs about 90ms - more than any operation here. The + * ELS DRBG is instantiated in microseconds and carries 128-bit strength, so + * take DRG.3 only where the operation genuinely needs 256. The thresholds are + * the ones the vendor's own PSA driver uses: RSA-4096, and orders over 32 + * bytes. */ +static int ElsPkcRandomInit(mcuxClSession_Descriptor_t* sess, int need256) +{ + mcuxClRandom_Mode_t mode = need256 + ? mcuxClRandomModes_Mode_CtrDrbg_AES256_DRG3 + : mcuxClRandomModes_Mode_ELS_Drbg; + + XMEMSET(elsPkcRngCtx, 0, sizeof(elsPkcRngCtx)); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(rr, rt, mcuxClRandom_init( + sess, (mcuxClRandom_Context_t)elsPkcRngCtx, mode)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClRandom_init) != rt) || + (MCUXCLRANDOM_STATUS_OK != rr)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return 0; +} + +static int ElsPkcRandomNcInit(mcuxClSession_Descriptor_t* sess) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(pr, pt, mcuxClRandom_ncInit(sess)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClRandom_ncInit) != pt) || + (MCUXCLRANDOM_STATUS_OK != pr)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return 0; +} + +static int ElsPkcSessionInit(mcuxClSession_Descriptor_t* sess) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(sr, st, mcuxClSession_init( + sess, elsPkcCpuWa, WOLFSSL_ELS_PKC_CPU_WA_SZ, + (uint32_t*)WOLFSSL_ELS_PKC_RAM_ADDR, WOLFSSL_ELS_PKC_RAM_SIZE)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClSession_init) != st) || + (MCUXCLSESSION_STATUS_OK != sr)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return 0; +} + +/* Bring up a session over the shared workareas. Caller holds the lock. */ +static int ElsPkcSessionOpen(mcuxClSession_Descriptor_t* sess, int need256) +{ + int ret = ElsPkcSessionInit(sess); + + if (ret == 0) { + ret = ElsPkcRandomInit(sess, need256); + } + if (ret == 0) { + ret = ElsPkcRandomNcInit(sess); + } + + return ret; +} + +/* Pair every Open. CLNS wipes the work areas here, and the PKC half is the + * fixed hardware window shared by RSA, ECDSA and X25519 - without this a + * private-key operation leaves its primes, nonce or scalar resident there + * until something else happens to overwrite them. Best effort: the operation's + * own result is what the caller is told about. */ +/* Wipe what a session leaves behind. Called from wc_ElsPkc_Cleanup() under the + * ELS lock and only while the peripheral is still up, because the PKC workarea + * is a live hardware window rather than memory this port owns: writing it with + * the block unclocked faults the bus. */ +static void ElsCleanupSession(void) +{ + ForceZero(elsPkcRngCtx, sizeof(elsPkcRngCtx)); + ForceZero(elsPkcCpuWa, sizeof(elsPkcCpuWa)); + ForceZero((void*)(wc_ptr_t)WOLFSSL_ELS_PKC_RAM_ADDR, + WOLFSSL_ELS_PKC_RAM_SIZE); +} + +static void ElsPkcSessionClose(mcuxClSession_Descriptor_t* sess) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(ur, ut, mcuxClRandom_uninit(sess)); + (void)ur; + (void)ut; + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(cr, ct, mcuxClSession_cleanup(sess)); + (void)cr; + (void)ct; + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(dr, dt, mcuxClSession_destroy(sess)); + (void)dr; + (void)dt; + MCUX_CSSL_FP_FUNCTION_CALL_END(); +} + +#ifndef NO_RSA + +/* Export an mp_int as a fixed-width big-endian string, which is the only form + * the vendor key entries accept. */ +static int ElsPkcMpToBin(mp_int* a, byte* out, word32 len) +{ + if (mp_unsigned_bin_size(a) > (int)len) { + return WC_NO_ERR_TRACE(BUFFER_E); + } + + return mp_to_unsigned_bin_len(a, out, (int)len); +} + +/* RSAVP1: the raw public operation, out = in^e mod n. */ +static int ElsPkcRsaPublic(mcuxClSession_Descriptor_t* sess, + mcuxClRsa_Key* k, const byte* in, word32 modLen, + byte* out) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClRsa_verify( + sess, k, NULL, 0u, (mcuxCl_Buffer_t)(uintptr_t)in, + (mcuxClRsa_SignVerifyMode)&mcuxClRsa_Mode_Verify_NoVerify, + 0u, 0u, out)); + /* RSAVP1 reports VERIFYPRIMITIVE_OK, not VERIFY_OK - the latter belongs to + * the padded modes. */ + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClRsa_verify) != t) || + ((MCUXCLRSA_STATUS_VERIFYPRIMITIVE_OK != r) && + (MCUXCLRSA_STATUS_VERIFY_OK != r))) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + (void)modLen; + + return 0; +} + +/* RSASP1: the raw private operation, out = in^d mod n (by CRT when possible). */ +static int ElsPkcRsaPrivate(mcuxClSession_Descriptor_t* sess, + mcuxClRsa_Key* k, const byte* in, word32 modLen, + byte* out) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClRsa_sign( + sess, k, in, modLen, + (mcuxClRsa_SignVerifyMode)&mcuxClRsa_Mode_Sign_NoEncode, + 0u, 0u, out)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClRsa_sign) != t) || + (MCUXCLRSA_STATUS_SIGN_OK != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return 0; +} + +/* RSA at the raw primitive. wolfCrypt's callback sits in wc_RsaFunction_ex, + * beneath all padding, so the matching vendor modes are NoVerify (RSAVP1) and + * NoEncode (RSASP1) - also the only corner of this API that compiles, since + * the rw61x encrypt/decrypt workarea macros carry unsubstituted placeholders. */ +/* rsa.h declares p/q only without WOLFSSL_RSA_PUBLIC_ONLY, and dP/dQ/u only + * outside RSA_LOW_MEM (unless key gen or OpenSSL compat is on). Mirror those + * conditions rather than assuming the members exist. */ +#if !defined(WOLFSSL_RSA_PUBLIC_ONLY) && \ + (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA) || \ + !defined(RSA_LOW_MEM)) + #define ELS_PKC_RSA_HAVE_CRT +#endif + +/* Does this key carry a usable CRT set? RsaKey declares p/q only without + * WOLFSSL_RSA_PUBLIC_ONLY, and dP/dQ/u only outside RSA_LOW_MEM, so the whole + * question compiles away where the members do not exist. */ +#ifdef ELS_PKC_RSA_HAVE_CRT +static int ElsPkcRsaHaveCrt(const RsaKey* key) +{ + return mp_unsigned_bin_size((mp_int*)&key->p) > 0 && + mp_unsigned_bin_size((mp_int*)&key->q) > 0 && + mp_unsigned_bin_size((mp_int*)&key->dP) > 0 && + mp_unsigned_bin_size((mp_int*)&key->dQ) > 0 && + mp_unsigned_bin_size((mp_int*)&key->u) > 0; +} +#endif + +static int ElsPkcRsaFunction(const byte* in, word32 inLen, byte* out, + word32* outLen, int type, RsaKey* key) +{ + mcuxClSession_Descriptor_t sess; + mcuxClRsa_Key rsaKey; + mcuxClRsa_KeyEntry_t e1, e4; +#ifdef ELS_PKC_RSA_HAVE_CRT + mcuxClRsa_KeyEntry_t e2, e3, e5; /* the CRT-only entries */ +#endif + byte* buf = NULL; + word32 modLen; + int isPrivate; + int ret; + + if (in == NULL || out == NULL || outLen == NULL || key == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + modLen = (word32)mp_unsigned_bin_size(&key->n); + /* The engine covers 512..4096-bit moduli in multiples of 8 bits. Anything + * else, including a modulus wolfCrypt would accept, goes to software. */ + if (modLen < 64 || modLen > 512 || inLen != modLen) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (*outLen < modLen) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + isPrivate = (type == RSA_PRIVATE_DECRYPT || type == RSA_PRIVATE_ENCRYPT); +#ifdef WOLFSSL_RSA_PUBLIC_ONLY + /* RsaKey carries no private members at all in this build. */ + if (isPrivate) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } +#endif + + /* One scratch block holds every key entry, sized for the CRT case: five + * entries of at most half a modulus each, plus the modulus itself. */ + buf = (byte*)XMALLOC(modLen * 4u, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (buf == NULL) { + return WC_NO_ERR_TRACE(MEMORY_E); + } + XMEMSET(buf, 0, modLen * 4u); + XMEMSET(&rsaKey, 0, sizeof(rsaKey)); + + if (!isPrivate) { + /* public: N and E */ + e1.pKeyEntryData = buf; + e1.keyEntryLength = modLen; + ret = ElsPkcMpToBin(&key->n, e1.pKeyEntryData, modLen); + + if (ret == 0) { + word32 eLen = (word32)mp_unsigned_bin_size(&key->e); + e4.pKeyEntryData = buf + modLen; + e4.keyEntryLength = eLen; + ret = ElsPkcMpToBin(&key->e, e4.pKeyEntryData, eLen); + } + rsaKey.keytype = MCUXCLRSA_KEY_PUBLIC; + rsaKey.pMod1 = &e1; + rsaKey.pExp1 = &e4; + } +#ifndef WOLFSSL_RSA_PUBLIC_ONLY + else { + /* CRT is the whole point of offloading a private operation, so only + * fall back when the key genuinely lacks the factors. Every one of the + * five is required: ElsPkcMpToBin() writes an empty mp_int as zeros + * rather than failing, so a key carrying only p and q would otherwise + * be signed with dP = dQ = qInv = 0 and reported as success. This is + * the same set wolfCrypt's own CRT path tests for. */ + word32 half = (modLen + 1u) / 2u; + +#ifdef ELS_PKC_RSA_HAVE_CRT + if (ElsPkcRsaHaveCrt(key)) { + e1.pKeyEntryData = buf; e1.keyEntryLength = half; + e2.pKeyEntryData = buf + half; e2.keyEntryLength = half; + e3.pKeyEntryData = buf + 2u * half; e3.keyEntryLength = half; + e4.pKeyEntryData = buf + 3u * half; e4.keyEntryLength = half; + e5.pKeyEntryData = buf + 4u * half; e5.keyEntryLength = half; + + ret = ElsPkcMpToBin(&key->p, e1.pKeyEntryData, half); + if (ret == 0) ret = ElsPkcMpToBin(&key->q, e2.pKeyEntryData, half); + if (ret == 0) ret = ElsPkcMpToBin(&key->u, e3.pKeyEntryData, half); + if (ret == 0) ret = ElsPkcMpToBin(&key->dP, e4.pKeyEntryData, half); + if (ret == 0) ret = ElsPkcMpToBin(&key->dQ, e5.pKeyEntryData, half); + + rsaKey.keytype = MCUXCLRSA_KEY_PRIVATECRT; + rsaKey.pMod1 = &e1; + rsaKey.pMod2 = &e2; + rsaKey.pQInv = &e3; + rsaKey.pExp1 = &e4; + rsaKey.pExp2 = &e5; + } + else +#endif /* ELS_PKC_RSA_HAVE_CRT */ + { + e1.pKeyEntryData = buf; e1.keyEntryLength = modLen; + e4.pKeyEntryData = buf + modLen; e4.keyEntryLength = modLen; + + ret = ElsPkcMpToBin(&key->n, e1.pKeyEntryData, modLen); + if (ret == 0) ret = ElsPkcMpToBin(&key->d, e4.pKeyEntryData, modLen); + + rsaKey.keytype = MCUXCLRSA_KEY_PRIVATEPLAIN; + rsaKey.pMod1 = &e1; + rsaKey.pExp1 = &e4; + } + } +#endif /* !WOLFSSL_RSA_PUBLIC_ONLY */ + + if (ret == 0) { + ret = ElsLock(); + } + if (ret == 0) { + /* Only the private operation blinds from the DRBG. */ + ret = ElsPkcSessionOpen(&sess, isPrivate && modLen >= 512u); + if (ret == 0) { + ret = isPrivate + ? ElsPkcRsaPrivate(&sess, &rsaKey, in, modLen, out) + : ElsPkcRsaPublic(&sess, &rsaKey, in, modLen, out); + ElsPkcSessionClose(&sess); + } + ElsUnlock(); + } + + if (ret == 0) { + *outLen = modLen; + } + + ForceZero(buf, modLen * 4u); + XFREE(buf, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +#endif /* !NO_RSA */ + +#endif /* ELS_PKC_HAVE_SESSION */ + /* --------------------------------------------------------------------------- * Random * ------------------------------------------------------------------------ */ @@ -2054,9 +2431,17 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) break; #endif -#ifdef HAVE_ECC +#if defined(HAVE_ECC) || !defined(NO_RSA) case WC_ALGO_TYPE_PK: switch (info->pk.type) { + #ifndef NO_RSA + case WC_PK_TYPE_RSA: + ret = ElsPkcRsaFunction(info->pk.rsa.in, + info->pk.rsa.inLen, info->pk.rsa.out, + info->pk.rsa.outLen, info->pk.rsa.type, + info->pk.rsa.key); + break; + #endif /* info->pk.eckg itself is declared under HAVE_ECC_DHE. */ #if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_IMPORT) && \ defined(HAVE_ECC_DHE) @@ -2104,7 +2489,7 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) break; } break; -#endif /* HAVE_ECC */ +#endif /* HAVE_ECC || !NO_RSA */ #ifndef NO_AES case WC_ALGO_TYPE_CIPHER: @@ -2327,6 +2712,9 @@ int wc_ElsPkc_Cleanup(void) * blocked caller still has to unlock it, and freeing it here would * destroy a locked mutex and strand the caller's ElsUnlock(). */ if (wc_LockMutex(&elsLock) == 0) { +#ifdef ELS_PKC_HAVE_SESSION + ElsCleanupSession(); +#endif elsReady = 0; (void)wc_UnLockMutex(&elsLock); } From 47bd6ecca8f4bdaff091bd4ae69d36db3bf54113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:37 +0200 Subject: [PATCH 09/16] nxp: offload X25519 to the PKC The agreement only. Key generation stays in software so the private key keeps whatever provenance wolfCrypt gave it, and the agreement is where the scalar multiplication, and so all of the time, actually is. A big-endian request is declined rather than byte-swapped behind the caller. EdDSA is not claimed because it cannot be: the Ed25519 entry points are present as symbols but their Twisted-Edwards internals are compiled out for this part, so a build that calls them fails to link, and Ed448 has no domain parameters at all. Symbol presence is not capability here; only a link test settles it. --- ChangeLog.md | 2 +- wolfcrypt/src/port/nxp/README.md | 2 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 167 +++++++++++++++++++++++++- 3 files changed, 167 insertions(+), 4 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index e66f81f7b7d..44086e3d830 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -191,7 +191,7 @@ * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev -* Added an NXP EdgeLock (ELS + PKC) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG and ECDSA on P-256 from a key slot; the PKC coprocessor serves RSA. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. +* Added an NXP EdgeLock (ELS + PKC) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG and ECDSA on P-256 from a key slot; the PKC coprocessor serves RSA and X25519. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. ## Fixes diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 10d68be51b2..05e611d413e 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -54,7 +54,7 @@ see [README_SE050.md](./README_SE050.md). RW612 and related parts, through the crypto callback interface. The ELS peripheral serves SHA-256, SHA-384, SHA-512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG, and ECDSA on P-256 for a key that names a slot; the PKC -coprocessor serves RSA. +coprocessor serves RSA and X25519. Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size costs diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index 94d3ef59c68..c23c57c10b3 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -1981,6 +1981,160 @@ static int ElsPkcRsaFunction(const byte* in, word32 inLen, byte* out, #endif /* ELS_PKC_HAVE_SESSION */ +/* --------------------------------------------------------------------------- + * X25519 + * ------------------------------------------------------------------------ */ + +/* Only the shared secret is claimed; key generation stays in software. */ + +#ifdef HAVE_CURVE25519 + +#include + +#define ELS_X25519_KEY_SZ MCUXCLECC_MONTDH_CURVE25519_SIZE_PRIVATEKEY + +/* Wrap a raw key buffer in a vendor key descriptor. Caller holds the lock. */ +static int ElsPkcKeyInit(mcuxClSession_Descriptor_t* sess, + mcuxClKey_Handle_t h, mcuxClKey_Type_t type, + byte* data) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClKey_init( + sess, h, type, data, ELS_X25519_KEY_SZ)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClKey_init) != t) || + (MCUXCLKEY_STATUS_OK != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return 0; +} + +/* The agreement itself, kept in its own scope so the flow-protection macro + * pair - which expands to a do/while - stays balanced. */ +static int ElsPkcMontDhRun(mcuxClSession_Descriptor_t* sess, + mcuxClKey_Handle_t priv, mcuxClKey_Handle_t pub, + byte* ss, uint32_t* len) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEcc_MontDH_KeyAgreement( + sess, priv, pub, ss, len)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEcc_MontDH_KeyAgreement) != t) || + (MCUXCLECC_STATUS_OK != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return 0; +} + +static int ElsPkcMontDh(mcuxClSession_Descriptor_t* sess, + const byte* priv, const byte* pub, byte* out, + word32* outLen) +{ + uint32_t privDesc[MCUXCLKEY_DESCRIPTOR_SIZE_IN_WORDS]; + uint32_t pubDesc[MCUXCLKEY_DESCRIPTOR_SIZE_IN_WORDS]; + ALIGN32 byte privBuf[ELS_X25519_KEY_SZ]; + ALIGN32 byte pubBuf[ELS_X25519_KEY_SZ]; + ALIGN32 byte ss[ELS_X25519_KEY_SZ]; + uint32_t len = 0u; + int ret = 0; + + XMEMCPY(privBuf, priv, ELS_X25519_KEY_SZ); + XMEMCPY(pubBuf, pub, ELS_X25519_KEY_SZ); + + ret = ElsPkcKeyInit(sess, (mcuxClKey_Handle_t)privDesc, + mcuxClKey_Type_Ecc_MontDH_Curve25519_PrivateKey, + privBuf); + if (ret == 0) { + ret = ElsPkcKeyInit(sess, (mcuxClKey_Handle_t)pubDesc, + mcuxClKey_Type_Ecc_MontDH_Curve25519_PublicKey, + pubBuf); + } + + if (ret == 0) { + ret = ElsPkcMontDhRun(sess, (mcuxClKey_Handle_t)privDesc, + (mcuxClKey_Handle_t)pubDesc, ss, &len); + } + + if (ret == 0) { + if (len != ELS_X25519_KEY_SZ || *outLen < len) { + ret = WC_NO_ERR_TRACE(BUFFER_E); + } + else { + XMEMCPY(out, ss, len); + *outLen = len; + } + } + + ForceZero(privBuf, sizeof(privBuf)); + ForceZero(ss, sizeof(ss)); + + return ret; +} + +static int ElsPkcX25519(curve25519_key* privKey, curve25519_key* pubKey, + byte* out, word32* outLen, int endian) +{ + mcuxClSession_Descriptor_t sess; + byte priv[ELS_X25519_KEY_SZ]; + byte pub[ELS_X25519_KEY_SZ]; + word32 privSz = sizeof(priv); + word32 pubSz = sizeof(pub); + int ret; + + if (privKey == NULL || pubKey == NULL || out == NULL || outLen == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + /* The engine works little-endian, which is X25519's own wire order. A + * big-endian request is wolfCrypt's legacy convention; decline it rather + * than byte-swapping a shared secret behind the caller's back. */ + if (endian != EC25519_LITTLE_ENDIAN) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = wc_curve25519_export_private_raw_ex(privKey, priv, &privSz, + EC25519_LITTLE_ENDIAN); + if (ret == 0) { + ret = wc_curve25519_export_public_ex(pubKey, pub, &pubSz, + EC25519_LITTLE_ENDIAN); + } + if (ret != 0 || privSz != ELS_X25519_KEY_SZ || + pubSz != ELS_X25519_KEY_SZ) { + ForceZero(priv, sizeof(priv)); + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = ElsLock(); + if (ret == 0) { + ret = ElsPkcSessionOpen(&sess, 0); + if (ret == 0) { + ret = ElsPkcMontDh(&sess, priv, pub, out, outLen); + ElsPkcSessionClose(&sess); +#ifndef WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK + /* RFC 7748 contributory behaviour, which the software path + * enforces in wc_curve25519_shared_secret_ex(). The offload + * replaces that path, so it has to carry the check itself. */ + if (ret == 0) { + word32 i; + byte acc = 0; + for (i = 0; i < *outLen; i++) { + acc |= out[i]; + } + if (acc == 0) { + ret = WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E); + } + } +#endif + } + ElsUnlock(); + } + + ForceZero(priv, sizeof(priv)); + + return ret; +} + +#endif /* HAVE_CURVE25519 */ + /* --------------------------------------------------------------------------- * Random * ------------------------------------------------------------------------ */ @@ -2431,9 +2585,18 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) break; #endif -#if defined(HAVE_ECC) || !defined(NO_RSA) +#if defined(HAVE_ECC) || !defined(NO_RSA) || defined(HAVE_CURVE25519) case WC_ALGO_TYPE_PK: switch (info->pk.type) { + #ifdef HAVE_CURVE25519 + case WC_PK_TYPE_CURVE25519: + ret = ElsPkcX25519(info->pk.curve25519.private_key, + info->pk.curve25519.public_key, + info->pk.curve25519.out, + info->pk.curve25519.outlen, + info->pk.curve25519.endian); + break; + #endif #ifndef NO_RSA case WC_PK_TYPE_RSA: ret = ElsPkcRsaFunction(info->pk.rsa.in, @@ -2489,7 +2652,7 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) break; } break; -#endif /* HAVE_ECC || !NO_RSA */ +#endif /* HAVE_ECC || !NO_RSA || HAVE_CURVE25519 */ #ifndef NO_AES case WC_ALGO_TYPE_CIPHER: From 112044fa12274ae855baa613617221216dc27e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:41 +0200 Subject: [PATCH 10/16] nxp: offload ECDSA on the PKC for curves beyond P-256 The complement to the ELS tier, which signs only with a P-256 key that lives in a slot. The two do not overlap, so the dispatch tries ELS first and falls through on CRYPTOCB_UNAVAILABLE, which is exactly the set ELS declines. Unlike ELS, the PKC has no built-in notion of a curve: a, b, p, G and n arrive as octet strings on every call. wolfCrypt keeps those as hex text, so they are converted into scratch held under the existing lock rather than onto the stack, which would otherwise cost around half a kilobyte per signature. --- ChangeLog.md | 2 +- wolfcrypt/src/port/nxp/README.md | 12 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 771 ++++++++++++++++++++++++++ 3 files changed, 779 insertions(+), 6 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 44086e3d830..366bf9f8aa4 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -191,7 +191,7 @@ * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev -* Added an NXP EdgeLock (ELS + PKC) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG and ECDSA on P-256 from a key slot; the PKC coprocessor serves RSA and X25519. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. +* Added an NXP EdgeLock (ELS + PKC) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG and ECDSA on P-256 from a key slot; the PKC coprocessor serves RSA, X25519 and ECDSA on every curve. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. ## Fixes diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 05e611d413e..9768b580e9a 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -54,7 +54,8 @@ see [README_SE050.md](./README_SE050.md). RW612 and related parts, through the crypto callback interface. The ELS peripheral serves SHA-256, SHA-384, SHA-512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG, and ECDSA on P-256 for a key that names a slot; the PKC -coprocessor serves RSA and X25519. +coprocessor serves RSA, X25519, and ECDSA on every curve, P-256 included when +the key holds ordinary material. Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size costs @@ -110,7 +111,7 @@ the free crypto-callback hook. ELS declines an ECDSA digest that is not a full 32 bytes - reachable, since `wc_ecc_sign_hash()` takes a digest of any length and a SHA-1 hash under -P-256 is 20 bytes - so that signature is made in software. +P-256 is 20 bytes - but the PKC then serves it, so it stays in hardware. **What the hardware declines**, so it runs in software instead: AES-192 (no ELS key size), any trailing partial block, and an AES-GCM IV other than 12 @@ -121,9 +122,10 @@ slot, which cannot be read back, while the wolfCrypt ECDH callback has to hand a buffer to its caller - so the callback declines and software answers. **The PKC uses a CTR_DRBG, not the ELS DRBG.** The ELS DRBG's security -strength is 128 bits on this part, which the PKC's own key operations -outgrow. A plain `wc_GenerateRandom()` is a different path and does reach the -ELS DRBG directly. The PKC workarea is a fixed hardware region shared by +strength is 128 bits on this part, so `mcuxClEcc_Sign` refuses P-384 and +P-521 with `RNG_ERROR` - a failure that names the random source rather than +the curve. A plain `wc_GenerateRandom()` is a different path and does reach +the ELS DRBG directly. The PKC workarea is a fixed hardware region shared by every PKC consumer, which is why PKC work runs under the same lock as ELS. ### Vendor library diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index c23c57c10b3..00a9d453976 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -2135,6 +2135,764 @@ static int ElsPkcX25519(curve25519_key* privKey, curve25519_key* pubKey, #endif /* HAVE_CURVE25519 */ +/* --------------------------------------------------------------------------- + * ECDSA on the PKC + * ------------------------------------------------------------------------ */ + +/* Serves every curve, including P-256 for a key that names no slot: ELS signs + * only from a slot, while an ELS verify needs none and answers first. The PKC + * has no built-in curves, so a, b, p, G and n are converted from wolfCrypt's + * hex into shared scratch under the lock, keeping ~half a kilobyte off the + * stack. */ + +#if defined(HAVE_ECC) && \ + (defined(HAVE_ECC_SIGN) || defined(HAVE_ECC_VERIFY)) + +#define ELS_PKC_ECC_MAX_P MCUXCLECC_WEIERECC_MAX_SIZE_PRIMEP +#define ELS_PKC_ECC_MAX_N MCUXCLECC_WEIERECC_MAX_SIZE_BASEPOINTORDER + +/* Curve constants for the operation in flight. Caller holds the lock. */ +static struct { + ALIGN32 byte a[ELS_PKC_ECC_MAX_P]; + ALIGN32 byte b[ELS_PKC_ECC_MAX_P]; + ALIGN32 byte p[ELS_PKC_ECC_MAX_P]; + ALIGN32 byte g[2 * ELS_PKC_ECC_MAX_P]; + ALIGN32 byte n[ELS_PKC_ECC_MAX_N]; +} elsPkcCurve; + +/* wolfCrypt stores curve constants as hex text; the engine wants fixed-width + * big-endian octets with leading zeros. */ +static int ElsPkcHexToBin(const char* hex, byte* out, word32 len) +{ + mp_int t; + int ret; + + if (hex == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + ret = mp_init(&t); + if (ret != 0) { + return ret; + } + ret = mp_read_radix(&t, hex, MP_RADIX_HEX); + if (ret == 0) { + ret = mp_to_unsigned_bin_len(&t, out, (int)len); + } + mp_clear(&t); + + return ret; +} + +/* Marshal a wolfCrypt curve into the engine's parameter block. */ +static int ElsPkcLoadCurve(const ecc_key* key, mcuxClEcc_DomainParam_t* dp, + word32* lenPOut) +{ + const ecc_set_type* set; + word32 lenP; + int ret; + + if (key == NULL || key->dp == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + set = key->dp; + lenP = (word32)set->size; + /* lenP sizes writes into buffers dimensioned by both constants - the order + * is carried at the prime's width - so it has to respect the smaller. */ + if (lenP == 0 || lenP > ELS_PKC_ECC_MAX_P || lenP > ELS_PKC_ECC_MAX_N) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = ElsPkcHexToBin(set->Af, elsPkcCurve.a, lenP); + if (ret == 0) ret = ElsPkcHexToBin(set->Bf, elsPkcCurve.b, lenP); + if (ret == 0) ret = ElsPkcHexToBin(set->prime, elsPkcCurve.p, lenP); + if (ret == 0) ret = ElsPkcHexToBin(set->Gx, elsPkcCurve.g, lenP); + if (ret == 0) ret = ElsPkcHexToBin(set->Gy, elsPkcCurve.g + lenP, lenP); + /* The order is carried at the same width as the prime, which holds for + * every selectable curve; one needing more bytes would truncate here. */ + if (ret == 0) ret = ElsPkcHexToBin(set->order, elsPkcCurve.n, lenP); + if (ret != 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + /* Both leading bytes must be nonzero per the engine's contract. */ + if (elsPkcCurve.p[0] == 0 || elsPkcCurve.n[0] == 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + dp->pA = elsPkcCurve.a; + dp->pB = elsPkcCurve.b; + dp->pP = elsPkcCurve.p; + dp->pG = elsPkcCurve.g; + dp->pN = elsPkcCurve.n; + dp->misc = mcuxClEcc_DomainParam_misc_Pack(lenP, lenP); + + *lenPOut = lenP; + + return 0; +} + +#ifdef HAVE_ECC_SIGN + +static int ElsPkcEccSignRun(mcuxClSession_Descriptor_t* sess, + mcuxClEcc_Sign_Param_t* prm) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEcc_Sign(sess, prm)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEcc_Sign) != t) || + (MCUXCLECC_STATUS_OK != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return 0; +} + +static int ElsPkcEccSign(const byte* in, word32 inlen, byte* out, + word32* outlen, ecc_key* key) +{ + mcuxClSession_Descriptor_t sess; + mcuxClEcc_Sign_Param_t prm; + ALIGN32 byte priv[ELS_PKC_ECC_MAX_N]; + ALIGN32 byte sig[2 * ELS_PKC_ECC_MAX_N]; + ALIGN32 byte hash[ELS_PKC_ECC_MAX_N]; + word32 lenP = 0, privSz; + int ret; + + if (in == NULL || out == NULL || outlen == NULL || key == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + /* The digest is copied into a fixed buffer, which bounds this more tightly + * than the eight-bit byteLenHash field does. */ + if (inlen == 0 || inlen > 255 || inlen > sizeof(hash)) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + ret = ElsPkcLoadCurve(key, &prm.curveParam, &lenP); + + if (ret == 0) { + privSz = lenP; + if (wc_ecc_export_private_only(key, priv, &privSz) != 0 || + privSz != lenP) { + ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + } + + if (ret == 0) { + XMEMCPY(hash, in, inlen); + + prm.pHash = hash; + prm.pPrivateKey = priv; + prm.pSignature = sig; + prm.optLen = mcuxClEcc_Sign_Param_optLen_Pack(inlen); + prm.pMode = &mcuxClEcc_ECDSA_ProtocolDescriptor; + + ret = ElsPkcSessionOpen(&sess, lenP > 32u); + if (ret == 0) { + ret = ElsPkcEccSignRun(&sess, &prm); + ElsPkcSessionClose(&sess); + } + } + + ElsUnlock(); + + if (ret == 0) { + /* raw R||S out of the engine, DER at the callback boundary */ + ret = StoreECC_DSA_Sig_Bin(out, outlen, sig, lenP, sig + lenP, lenP); + } + + ForceZero(priv, sizeof(priv)); + ForceZero(hash, sizeof(hash)); + + return ret; +} + +#endif /* HAVE_ECC_SIGN */ + +#ifdef HAVE_ECC_VERIFY + +#ifdef WOLFSSL_CUSTOM_CURVES +static int ElsPkcPointMultRun(mcuxClSession_Descriptor_t* sess, + mcuxClEcc_PointMult_Param_t* prm) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEcc_PointMult(sess, prm)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEcc_PointMult) != t) || + (MCUXCLECC_STATUS_OK != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return 0; +} + +/* One entry, for a curve the table below does not carry. Keyed on the + * generator rather than the curve id: every curve parsed from explicit domain + * parameters reports ECC_CURVE_CUSTOM, so two different custom curves of the + * same field size would otherwise share the entry and the second verify would + * run against the first curve's point - a wrong verdict, not a decline. */ +static struct { + ALIGN32 byte precG[2 * ELS_PKC_ECC_MAX_P]; + byte g[2 * ELS_PKC_ECC_MAX_P]; + word32 lenP; + byte valid; +} elsPkcPrecG; +#endif /* WOLFSSL_CUSTOM_CURVES */ + +/* PrecG = (2^(byteLenN*4))*G, which an input verification needs. CLNS keeps + * its own copies, but they are static to mcuxClEcc_Constants.c and reachable + * only through its curve descriptors, which this port does not use since it + * builds domain parameters from wolfCrypt's table. Precomputed here rather + * than derived: + * each costs 2*byteLenP of flash against one point multiplication per curve + * change at run time, and each carries the same guard its curve does in + * ecc.c, so a build pays only for the curves it selected. A curve absent from + * the table - a custom one - still has its value computed. */ +typedef struct ElsPkcPrecGEntry { + int curveId; + word32 lenP; + const byte* precG; +} ElsPkcPrecGEntry; + +/* ecc.c gates its curve table on these, but derives them in the .c file. + * Mirrored here so this table cannot drift out of step with that one. */ +#if (defined(HAVE_ECC112) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 112 + #define ELS_PKC_ECC112 +#endif +#if (defined(HAVE_ECC128) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 128 + #define ELS_PKC_ECC128 +#endif +#if (defined(HAVE_ECC160) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 160 + #define ELS_PKC_ECC160 +#endif +#if (defined(HAVE_ECC192) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 192 + #define ELS_PKC_ECC192 +#endif +#if (defined(HAVE_ECC224) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 224 + #define ELS_PKC_ECC224 +#endif +#if (defined(HAVE_ECC239) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 239 + #define ELS_PKC_ECC239 +#endif +#if (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 + #define ELS_PKC_ECC256 +#endif +#if (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 + #define ELS_PKC_ECC384 +#endif +#if (defined(HAVE_ECC512) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 512 + #define ELS_PKC_ECC512 +#endif +#if (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 521 + #define ELS_PKC_ECC521 +#endif + +#if defined(ELS_PKC_ECC112) && \ + !defined(NO_ECC_SECP) +static const byte elsPkcPrecGSecp112r1[2 * 14] = { /* SECP112R1 */ + 0xAB, 0x76, 0x6C, 0xBE, 0x5F, 0x14, 0xE7, 0xC7, 0xB4, 0x67, 0x81, 0x12, + 0x7E, 0x03, 0x84, 0xE6, 0xB1, 0xDB, 0x60, 0x93, 0x50, 0x82, 0xDD, 0xAA, + 0x2F, 0x64, 0x1B, 0x51 +}; +#endif + +#if defined(ELS_PKC_ECC128) && \ + !defined(NO_ECC_SECP) +static const byte elsPkcPrecGSecp128r1[2 * 16] = { /* SECP128R1 */ + 0x8A, 0xB9, 0x90, 0x7E, 0x91, 0x28, 0x33, 0xFC, 0x5B, 0x10, 0xF2, 0x01, + 0x40, 0xA6, 0x3E, 0xF0, 0xFA, 0x2F, 0xC2, 0x33, 0x76, 0x21, 0xCE, 0x72, + 0x40, 0x13, 0xDF, 0x3B, 0x6F, 0x3D, 0xCF, 0x65 +}; +#endif + +#if defined(ELS_PKC_ECC160) && \ + !defined(FP_ECC) && \ + !defined(NO_ECC_SECP) +static const byte elsPkcPrecGSecp160r1[2 * 20] = { /* SECP160R1 */ + 0xF2, 0xE0, 0xA3, 0x2F, 0xBE, 0x4A, 0xD4, 0xC7, 0xA2, 0x96, 0x1A, 0x17, + 0xB2, 0x18, 0x44, 0xA4, 0x89, 0x66, 0x53, 0x47, 0x46, 0xB7, 0x03, 0x2F, + 0xCA, 0xA2, 0xF3, 0x78, 0x92, 0xDE, 0xA6, 0x40, 0x65, 0x19, 0xE3, 0xDE, + 0x31, 0xB9, 0x80, 0xCA +}; +#endif + +#if defined(ELS_PKC_ECC160) && \ + !defined(FP_ECC) && \ + defined(HAVE_ECC_SECPR2) +static const byte elsPkcPrecGSecp160r2[2 * 20] = { /* SECP160R2 */ + 0xAF, 0x60, 0x92, 0xBE, 0x02, 0x9E, 0x4D, 0x9B, 0x06, 0xA1, 0x96, 0x1F, + 0x2D, 0xD8, 0xDA, 0x5D, 0x9F, 0xDB, 0xE7, 0xCC, 0x3E, 0x92, 0x21, 0xCD, + 0xDC, 0xD3, 0xFB, 0x29, 0x95, 0x2F, 0x7D, 0x35, 0x56, 0xD0, 0xEE, 0x47, + 0x31, 0xA7, 0xC9, 0x0D +}; +#endif + +#if defined(ELS_PKC_ECC160) && \ + defined(HAVE_ECC_BRAINPOOL) +static const byte elsPkcPrecGBrainpoolp160r1[2 * 20] = { /* BRAINPOOLP160R1 */ + 0x8E, 0x63, 0xBD, 0x39, 0x39, 0xFC, 0x8E, 0x94, 0xBB, 0x51, 0x87, 0x25, + 0xD3, 0xB8, 0x21, 0x0C, 0xB1, 0x9B, 0xD5, 0xA1, 0x4D, 0x1E, 0x97, 0x7B, + 0x17, 0xA6, 0x83, 0x90, 0x8E, 0x13, 0x88, 0x36, 0xAF, 0x10, 0x15, 0xA3, + 0x68, 0xDE, 0x44, 0x48 +}; +#endif + +#if defined(ELS_PKC_ECC192) && \ + !defined(NO_ECC_SECP) +static const byte elsPkcPrecGSecp192r1[2 * 24] = { /* SECP192R1 */ + 0x51, 0xA5, 0x81, 0xD9, 0x18, 0x4A, 0xC7, 0x37, 0x47, 0x30, 0xD4, 0xF4, + 0x80, 0xD1, 0x09, 0x0B, 0xB1, 0x99, 0x63, 0xD8, 0xC0, 0xA1, 0xE3, 0x40, + 0x5B, 0xD8, 0x1E, 0xE2, 0xE0, 0xBB, 0x9F, 0x6E, 0x7C, 0xDF, 0xCE, 0xA0, + 0x2F, 0x68, 0x3F, 0x16, 0xEC, 0xC5, 0x67, 0x31, 0xE6, 0x99, 0x12, 0xA5 +}; +#endif + +#if defined(ELS_PKC_ECC192) && \ + defined(HAVE_ECC_SECPR2) +static const byte elsPkcPrecGPrime192v2[2 * 24] = { /* PRIME192V2 */ + 0x0F, 0x81, 0x09, 0x2A, 0xE6, 0x45, 0x5D, 0xB7, 0x6E, 0xC5, 0x18, 0xDC, + 0x04, 0xA6, 0x25, 0x99, 0x88, 0xF9, 0xAE, 0x41, 0xB7, 0xCF, 0x44, 0xC4, + 0xEC, 0xDA, 0xA4, 0x7C, 0x25, 0x27, 0x26, 0x27, 0x1C, 0x40, 0x4C, 0x1B, + 0x55, 0x9C, 0x79, 0x71, 0x13, 0x73, 0xED, 0x13, 0x86, 0xA2, 0x33, 0xD7 +}; +#endif + +#if defined(ELS_PKC_ECC192) && \ + defined(HAVE_ECC_SECPR3) +static const byte elsPkcPrecGPrime192v3[2 * 24] = { /* PRIME192V3 */ + 0x58, 0x27, 0x5C, 0x4B, 0xC2, 0x74, 0xE9, 0x83, 0x6A, 0xCE, 0x2E, 0xDD, + 0xE4, 0x4B, 0xF4, 0x94, 0xBE, 0x8F, 0x86, 0x61, 0x7E, 0x22, 0x02, 0x71, + 0xA0, 0xD8, 0x9C, 0x98, 0x1D, 0x5C, 0x7F, 0xE2, 0xA4, 0x37, 0xAD, 0xB4, + 0xCA, 0x6A, 0x8A, 0x43, 0x42, 0xCC, 0x4D, 0x4C, 0x07, 0x9C, 0xBD, 0x07 +}; +#endif + +#if defined(ELS_PKC_ECC192) && \ + defined(HAVE_ECC_BRAINPOOL) +static const byte elsPkcPrecGBrainpoolp192r1[2 * 24] = { /* BRAINPOOLP192R1 */ + 0x77, 0x3D, 0x02, 0x97, 0x1E, 0x97, 0x2E, 0x92, 0x08, 0x6B, 0x6F, 0x1D, + 0xB5, 0xB5, 0x55, 0x27, 0xB4, 0x00, 0x6C, 0xD5, 0x6E, 0x55, 0x38, 0x4C, + 0x80, 0x7F, 0x61, 0x5C, 0x01, 0xCE, 0x04, 0x88, 0xA3, 0xD9, 0x34, 0x6E, + 0x66, 0x34, 0xC2, 0xCE, 0x32, 0x82, 0x8E, 0x3D, 0x30, 0x14, 0x23, 0x4C +}; +#endif + +#if defined(ELS_PKC_ECC224) && \ + !defined(NO_ECC_SECP) +static const byte elsPkcPrecGSecp224r1[2 * 28] = { /* SECP224R1 */ + 0x04, 0x99, 0xAA, 0x8A, 0x5F, 0x8E, 0xBE, 0xEF, 0xEC, 0x27, 0xA4, 0xE1, + 0x3A, 0x0B, 0x91, 0xFB, 0x29, 0x91, 0xFA, 0xB0, 0xA0, 0x06, 0x41, 0x96, + 0x6C, 0xAB, 0x26, 0xE3, 0x69, 0x16, 0xF6, 0xD4, 0x33, 0x8C, 0x5B, 0x81, + 0xD7, 0x7A, 0xAE, 0x82, 0xF7, 0x06, 0x84, 0xD9, 0x29, 0x61, 0x0D, 0x54, + 0x50, 0x75, 0x10, 0x40, 0x77, 0x66, 0xAF, 0x5D +}; +#endif + +#if defined(ELS_PKC_ECC224) && \ + defined(HAVE_ECC_BRAINPOOL) +static const byte elsPkcPrecGBrainpoolp224r1[2 * 28] = { /* BRAINPOOLP224R1 */ + 0xB3, 0xCD, 0xA5, 0xBC, 0x06, 0x99, 0xB4, 0xA5, 0xD7, 0xC8, 0x4B, 0x0E, + 0xEE, 0x59, 0x10, 0x79, 0x61, 0x9F, 0x1D, 0x35, 0x79, 0x4A, 0xDD, 0x48, + 0x18, 0x67, 0x1C, 0xAC, 0xBC, 0x2D, 0xC0, 0x34, 0xFE, 0xF3, 0x2E, 0x40, + 0xD6, 0xD7, 0x50, 0xA1, 0x3B, 0x07, 0xA6, 0x8C, 0x4D, 0x88, 0xB7, 0x67, + 0x6E, 0x60, 0x80, 0xC1, 0xBB, 0x4A, 0x54, 0x47 +}; +#endif + +#if defined(ELS_PKC_ECC239) && \ + !defined(NO_ECC_SECP) +static const byte elsPkcPrecGPrime239v1[2 * 30] = { /* PRIME239V1 */ + 0x35, 0xFE, 0x64, 0x2B, 0x09, 0x5F, 0x1E, 0x6F, 0x16, 0x04, 0xB1, 0x6F, + 0x77, 0x69, 0x34, 0x3A, 0xEC, 0x0F, 0xEE, 0x90, 0x4B, 0xCB, 0xE7, 0x45, + 0xC8, 0xDE, 0x0B, 0xDF, 0xE2, 0x69, 0x02, 0xA1, 0x73, 0x21, 0xBF, 0x3F, + 0x1A, 0xA0, 0xE4, 0xF1, 0x9B, 0xBA, 0x3B, 0xE4, 0xB2, 0xC6, 0xE0, 0x69, + 0xF5, 0x47, 0x9F, 0x56, 0xA9, 0xE7, 0x67, 0x9F, 0xD8, 0xC7, 0xE7, 0x08 +}; +#endif + +#if defined(ELS_PKC_ECC239) && \ + defined(HAVE_ECC_SECPR2) +static const byte elsPkcPrecGPrime239v2[2 * 30] = { /* PRIME239V2 */ + 0x2C, 0xCE, 0xC8, 0x07, 0x91, 0xF3, 0x58, 0x99, 0x5E, 0x55, 0x43, 0x3E, + 0xCC, 0x2F, 0x82, 0xDF, 0x31, 0x9C, 0x29, 0x01, 0x4A, 0x0C, 0x27, 0x27, + 0x74, 0x9F, 0xB5, 0xB4, 0xB0, 0x5C, 0x34, 0x95, 0xBC, 0x6F, 0xA7, 0x7E, + 0xBC, 0x6A, 0x5F, 0x16, 0x02, 0x27, 0xB5, 0xD2, 0x35, 0xFB, 0x51, 0x68, + 0x1A, 0x74, 0x1D, 0xBF, 0xBE, 0xC2, 0x37, 0x43, 0xBE, 0x34, 0x3B, 0x70 +}; +#endif + +#if defined(ELS_PKC_ECC239) && \ + defined(HAVE_ECC_SECPR3) +static const byte elsPkcPrecGPrime239v3[2 * 30] = { /* PRIME239V3 */ + 0x09, 0x69, 0x0E, 0xBA, 0xDD, 0xAE, 0xC9, 0x1E, 0xF0, 0x96, 0xA4, 0x9C, + 0x6D, 0x77, 0xD3, 0x2D, 0x5C, 0x81, 0x03, 0x5A, 0xC2, 0x8D, 0xEB, 0x75, + 0xD1, 0x22, 0xB3, 0x09, 0x86, 0x7E, 0x11, 0x6C, 0xDA, 0x12, 0xDE, 0x47, + 0xAA, 0x2C, 0x94, 0x5E, 0x29, 0xB2, 0x3B, 0x05, 0xCF, 0x19, 0xAC, 0xC2, + 0x3D, 0x82, 0xBF, 0xAC, 0x51, 0x06, 0x9D, 0x97, 0xB7, 0x0F, 0xDD, 0xF4 +}; +#endif + +#if defined(ELS_PKC_ECC256) && \ + !defined(NO_ECC_SECP) +static const byte elsPkcPrecGSecp256r1[2 * 32] = { /* SECP256R1 */ + 0x44, 0x7D, 0x73, 0x9B, 0xEE, 0xDB, 0x5E, 0x67, 0xFB, 0x98, 0x2F, 0xD5, + 0x88, 0xC6, 0x76, 0x6E, 0xFC, 0x35, 0xFF, 0x7D, 0xC2, 0x97, 0xEA, 0xC3, + 0x57, 0xC8, 0x4F, 0xC9, 0xD7, 0x89, 0xBD, 0x85, 0x2D, 0x48, 0x25, 0xAB, + 0x83, 0x41, 0x31, 0xEE, 0xE1, 0x2E, 0x9D, 0x95, 0x3A, 0x4A, 0xAF, 0xF7, + 0x3D, 0x34, 0x9B, 0x95, 0xA7, 0xFA, 0xE5, 0x00, 0x0C, 0x7E, 0x33, 0xC9, + 0x72, 0xE2, 0x5B, 0x32 +}; +#endif + +#if defined(ELS_PKC_ECC256) && \ + defined(HAVE_ECC_BRAINPOOL) +static const byte elsPkcPrecGBrainpoolp256r1[2 * 32] = { /* BRAINPOOLP256R1 */ + 0x4A, 0x14, 0xC0, 0x30, 0x3B, 0x85, 0x6C, 0x94, 0xB4, 0x43, 0x85, 0x11, + 0x7F, 0x87, 0xED, 0x9D, 0x12, 0x00, 0xCA, 0x9B, 0x11, 0x00, 0x65, 0x90, + 0xEB, 0x6B, 0x65, 0x1C, 0xF5, 0x84, 0x72, 0xC9, 0x7B, 0x81, 0xE4, 0x70, + 0xDA, 0xE2, 0xD5, 0xEF, 0xE6, 0x38, 0x73, 0x49, 0x8B, 0x47, 0xCC, 0x5E, + 0xD5, 0x44, 0xA0, 0x68, 0xCD, 0x73, 0x21, 0x17, 0x52, 0x9C, 0x5C, 0xD6, + 0x28, 0xF8, 0x52, 0xD1 +}; +#endif + +#if (defined(WOLFSSL_SM2)) +static const byte elsPkcPrecGSm2p256v1[2 * 32] = { /* SM2P256V1 */ + 0xB6, 0x92, 0xE5, 0xB5, 0x74, 0xD5, 0x5D, 0xA9, 0x3D, 0xB7, 0xB2, 0x48, + 0x88, 0xC2, 0x1F, 0x3A, 0x2B, 0x23, 0x08, 0xF6, 0x48, 0x4E, 0x1B, 0x38, + 0xEA, 0xE3, 0xD9, 0xA9, 0xD1, 0x3A, 0x42, 0xED, 0xA1, 0x75, 0x05, 0x1B, + 0x0F, 0x3F, 0xB6, 0x13, 0x5A, 0x92, 0x4F, 0x85, 0x54, 0x49, 0x26, 0xF9, + 0xDB, 0x61, 0xAC, 0x17, 0x73, 0x43, 0x8E, 0x6D, 0xD1, 0x86, 0x46, 0x9D, + 0xE2, 0x95, 0xE5, 0xAB +}; +#endif + +#if defined(ELS_PKC_ECC384) && \ + !defined(NO_ECC_SECP) +static const byte elsPkcPrecGSecp384r1[2 * 48] = { /* SECP384R1 */ + 0xC1, 0x9E, 0x0B, 0x4C, 0x80, 0x01, 0x19, 0xC4, 0x40, 0xF7, 0xF9, 0xE7, + 0x06, 0x42, 0x12, 0x79, 0xB4, 0x2A, 0x31, 0xAF, 0x8A, 0x3E, 0x29, 0x7D, + 0xDB, 0x29, 0x87, 0x89, 0x4D, 0x10, 0xDD, 0xEA, 0xBA, 0x06, 0x54, 0x58, + 0xA4, 0xF5, 0x2D, 0x78, 0xA6, 0x28, 0xB0, 0x9A, 0xAA, 0x03, 0xBD, 0x53, + 0x16, 0xF3, 0xFD, 0xBF, 0x03, 0x56, 0xB3, 0x01, 0xE5, 0xA0, 0x19, 0x1D, + 0x1F, 0x5B, 0x77, 0xF6, 0x57, 0x7A, 0x30, 0xEA, 0xE3, 0x56, 0x7A, 0xF9, + 0xC1, 0xC7, 0xCA, 0xD1, 0x35, 0xF6, 0xEB, 0xF2, 0xAF, 0x68, 0xAA, 0x6D, + 0xE6, 0x39, 0xD8, 0x58, 0x82, 0x2D, 0x0F, 0xC5, 0xE6, 0xC8, 0x8C, 0x41 +}; +#endif + +#if defined(ELS_PKC_ECC384) && \ + defined(HAVE_ECC_BRAINPOOL) +static const byte elsPkcPrecGBrainpoolp384r1[2 * 48] = { /* BRAINPOOLP384R1 */ + 0x23, 0x69, 0xDB, 0xB6, 0x39, 0x7C, 0x99, 0xF1, 0x89, 0x74, 0xB5, 0x68, + 0x8E, 0x81, 0xAF, 0x98, 0xDF, 0x4D, 0xDA, 0xCC, 0xBB, 0x1F, 0xDC, 0x04, + 0xE1, 0x8A, 0x5D, 0x2D, 0x02, 0xC7, 0xF7, 0x27, 0x02, 0xA3, 0x53, 0xBC, + 0x53, 0x45, 0xA9, 0x46, 0x6B, 0xF5, 0x50, 0xB0, 0x4D, 0x99, 0x4B, 0x04, + 0x6F, 0x47, 0xB1, 0x1D, 0xAA, 0x3B, 0x12, 0x4F, 0x93, 0xB0, 0x87, 0x75, + 0x92, 0x5F, 0xF0, 0xA8, 0x12, 0x73, 0x68, 0xF1, 0x07, 0x42, 0xFA, 0x8F, + 0xCD, 0x41, 0xCA, 0xE9, 0x33, 0x34, 0xCE, 0x66, 0x43, 0xF1, 0x43, 0xE6, + 0x50, 0x0C, 0xB2, 0xC1, 0x0E, 0xE1, 0x88, 0xBB, 0x14, 0x50, 0x4C, 0x85 +}; +#endif + +#if defined(ELS_PKC_ECC512) && \ + defined(HAVE_ECC_BRAINPOOL) +static const byte elsPkcPrecGBrainpoolp512r1[2 * 64] = { /* BRAINPOOLP512R1 */ + 0x7B, 0x59, 0x13, 0xF7, 0x66, 0xC4, 0xED, 0x95, 0xD5, 0x26, 0x2C, 0xE1, + 0xB8, 0xF1, 0xB2, 0xAF, 0xC0, 0x56, 0xFD, 0x65, 0x8F, 0x28, 0x48, 0x70, + 0x79, 0xA8, 0x3D, 0x9B, 0x94, 0x5E, 0x84, 0x60, 0x1B, 0xAE, 0x0F, 0x47, + 0xAC, 0xA3, 0xB9, 0x4E, 0x97, 0x50, 0x2F, 0x33, 0x73, 0x0A, 0x37, 0x21, + 0x9D, 0x18, 0x9C, 0x54, 0x08, 0xAC, 0x7D, 0xA5, 0xCA, 0x44, 0x81, 0x27, + 0xEF, 0x66, 0xEF, 0xB5, 0x3B, 0x4D, 0x8E, 0x45, 0x4F, 0xFC, 0x59, 0x3A, + 0x37, 0x27, 0xEA, 0x1E, 0xEA, 0x20, 0xB7, 0x08, 0xC2, 0x1F, 0xCA, 0x91, + 0x61, 0x30, 0x6A, 0xF0, 0xE4, 0x66, 0x56, 0xA7, 0x5F, 0xC4, 0x50, 0xA8, + 0xFB, 0x4E, 0xDB, 0x0D, 0xBC, 0x57, 0x88, 0x77, 0x24, 0xEC, 0x6F, 0x84, + 0x4E, 0xB4, 0x96, 0xC3, 0x3E, 0x94, 0x70, 0xF8, 0xDA, 0x00, 0x92, 0xC9, + 0xD6, 0x10, 0x45, 0x3C, 0xC2, 0xF4, 0x58, 0x72 +}; +#endif + +#if defined(ELS_PKC_ECC521) && \ + !defined(NO_ECC_SECP) +static const byte elsPkcPrecGSecp521r1[2 * 66] = { /* SECP521R1 */ + 0x00, 0x8E, 0x81, 0x8D, 0x28, 0xF3, 0x81, 0xD8, 0xB2, 0x05, 0xED, 0xFF, + 0x69, 0x61, 0x3B, 0x96, 0x2E, 0x0C, 0x77, 0xD2, 0x23, 0xEF, 0x25, 0xCC, + 0x1C, 0x99, 0xD9, 0xA6, 0x2E, 0x4F, 0x25, 0x72, 0xC1, 0x61, 0x7A, 0xD0, + 0xF5, 0xE9, 0xA8, 0x6B, 0x71, 0x04, 0xE8, 0x97, 0x00, 0xD4, 0xDA, 0x71, + 0x3C, 0xB4, 0x08, 0xF3, 0xDE, 0x44, 0x65, 0xF8, 0x6A, 0xC4, 0xEE, 0x31, + 0xE7, 0x1A, 0x28, 0x64, 0x92, 0xAD, 0x01, 0x3E, 0xFD, 0xBC, 0x85, 0x6E, + 0x8F, 0x68, 0xBF, 0x44, 0xD4, 0xE1, 0x9F, 0xC7, 0xC3, 0x26, 0xFE, 0x48, + 0xA1, 0x6F, 0x78, 0x55, 0xC8, 0x08, 0x66, 0x23, 0x71, 0x96, 0xBF, 0x8F, + 0x72, 0xEA, 0x0D, 0xCB, 0x42, 0x22, 0x85, 0xDC, 0x03, 0x70, 0x68, 0x9F, + 0xBE, 0x72, 0x6F, 0x8C, 0xE0, 0x45, 0xA4, 0x03, 0x8B, 0x64, 0x0F, 0x2B, + 0x67, 0x17, 0x76, 0x0F, 0x72, 0x12, 0x31, 0xCF, 0x8C, 0xDF, 0x1F, 0x60 +}; +#endif + +static const ElsPkcPrecGEntry elsPkcPrecGTable[] = { +#if defined(ELS_PKC_ECC112) && \ + !defined(NO_ECC_SECP) + { ECC_SECP112R1, 14, elsPkcPrecGSecp112r1 }, +#endif +#if defined(ELS_PKC_ECC128) && \ + !defined(NO_ECC_SECP) + { ECC_SECP128R1, 16, elsPkcPrecGSecp128r1 }, +#endif +#if defined(ELS_PKC_ECC160) && \ + !defined(FP_ECC) && \ + !defined(NO_ECC_SECP) + { ECC_SECP160R1, 20, elsPkcPrecGSecp160r1 }, +#endif +#if defined(ELS_PKC_ECC160) && \ + !defined(FP_ECC) && \ + defined(HAVE_ECC_SECPR2) + { ECC_SECP160R2, 20, elsPkcPrecGSecp160r2 }, +#endif +#if defined(ELS_PKC_ECC160) && \ + defined(HAVE_ECC_BRAINPOOL) + { ECC_BRAINPOOLP160R1, 20, elsPkcPrecGBrainpoolp160r1 }, +#endif +#if defined(ELS_PKC_ECC192) && \ + !defined(NO_ECC_SECP) + { ECC_SECP192R1, 24, elsPkcPrecGSecp192r1 }, +#endif +#if defined(ELS_PKC_ECC192) && \ + defined(HAVE_ECC_SECPR2) + { ECC_PRIME192V2, 24, elsPkcPrecGPrime192v2 }, +#endif +#if defined(ELS_PKC_ECC192) && \ + defined(HAVE_ECC_SECPR3) + { ECC_PRIME192V3, 24, elsPkcPrecGPrime192v3 }, +#endif +#if defined(ELS_PKC_ECC192) && \ + defined(HAVE_ECC_BRAINPOOL) + { ECC_BRAINPOOLP192R1, 24, elsPkcPrecGBrainpoolp192r1 }, +#endif +#if defined(ELS_PKC_ECC224) && \ + !defined(NO_ECC_SECP) + { ECC_SECP224R1, 28, elsPkcPrecGSecp224r1 }, +#endif +#if defined(ELS_PKC_ECC224) && \ + defined(HAVE_ECC_BRAINPOOL) + { ECC_BRAINPOOLP224R1, 28, elsPkcPrecGBrainpoolp224r1 }, +#endif +#if defined(ELS_PKC_ECC239) && \ + !defined(NO_ECC_SECP) + { ECC_PRIME239V1, 30, elsPkcPrecGPrime239v1 }, +#endif +#if defined(ELS_PKC_ECC239) && \ + defined(HAVE_ECC_SECPR2) + { ECC_PRIME239V2, 30, elsPkcPrecGPrime239v2 }, +#endif +#if defined(ELS_PKC_ECC239) && \ + defined(HAVE_ECC_SECPR3) + { ECC_PRIME239V3, 30, elsPkcPrecGPrime239v3 }, +#endif +#if defined(ELS_PKC_ECC256) && \ + !defined(NO_ECC_SECP) + { ECC_SECP256R1, 32, elsPkcPrecGSecp256r1 }, +#endif +#if defined(ELS_PKC_ECC256) && \ + defined(HAVE_ECC_BRAINPOOL) + { ECC_BRAINPOOLP256R1, 32, elsPkcPrecGBrainpoolp256r1 }, +#endif +#if (defined(WOLFSSL_SM2)) + { ECC_SM2P256V1, 32, elsPkcPrecGSm2p256v1 }, +#endif +#if defined(ELS_PKC_ECC384) && \ + !defined(NO_ECC_SECP) + { ECC_SECP384R1, 48, elsPkcPrecGSecp384r1 }, +#endif +#if defined(ELS_PKC_ECC384) && \ + defined(HAVE_ECC_BRAINPOOL) + { ECC_BRAINPOOLP384R1, 48, elsPkcPrecGBrainpoolp384r1 }, +#endif +#if defined(ELS_PKC_ECC512) && \ + defined(HAVE_ECC_BRAINPOOL) + { ECC_BRAINPOOLP512R1, 64, elsPkcPrecGBrainpoolp512r1 }, +#endif +#if defined(ELS_PKC_ECC521) && \ + !defined(NO_ECC_SECP) + { ECC_SECP521R1, 66, elsPkcPrecGSecp521r1 }, +#endif + { ECC_CURVE_INVALID, 0, NULL } /* terminator, and keeps it non-empty */ +}; + +/* Point at the value for this curve, deriving it only for a curve the table + * does not carry. Caller holds the lock and has loaded the curve. */ +static int ElsPkcPrecGet(mcuxClSession_Descriptor_t* sess, + const mcuxClEcc_DomainParam_t* dp, int curveId, + word32 lenP, const byte** precG) +{ + int i; +#ifdef WOLFSSL_CUSTOM_CURVES + mcuxClEcc_PointMult_Param_t prm; + ALIGN32 byte scalar[ELS_PKC_ECC_MAX_N]; + int ret; +#endif + + for (i = 0; elsPkcPrecGTable[i].precG != NULL; i++) { + if (elsPkcPrecGTable[i].curveId == curveId && + elsPkcPrecGTable[i].lenP == lenP) { + *precG = elsPkcPrecGTable[i].precG; + return 0; + } + } + +#ifdef WOLFSSL_CUSTOM_CURVES + if (elsPkcPrecG.valid && elsPkcPrecG.lenP == lenP && + XMEMCMP(elsPkcPrecG.g, dp->pG, 2u * lenP) == 0) { + *precG = elsPkcPrecG.precG; + return 0; + } + elsPkcPrecG.valid = 0; + /* The exponent byteLenN*4 is half the order's bit width, so the single set + * bit lands halfway through the scalar. Even byte lengths only. */ + if ((lenP & 1u) != 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + XMEMSET(scalar, 0, sizeof(scalar)); + scalar[lenP / 2u - 1u] = 0x01; + + prm.curveParam = *dp; + prm.pScalar = scalar; + prm.pPoint = dp->pG; + prm.pResult = elsPkcPrecG.precG; + prm.optLen = 0u; + + ret = ElsPkcPointMultRun(sess, &prm); + if (ret == 0) { + XMEMCPY(elsPkcPrecG.g, dp->pG, 2u * lenP); + elsPkcPrecG.lenP = lenP; + elsPkcPrecG.valid = 1; + *precG = elsPkcPrecG.precG; + } + + return ret; +#else + (void)sess; + (void)dp; + + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +#endif +} + +static int ElsPkcEccVerifyRun(mcuxClSession_Descriptor_t* sess, + mcuxClEcc_Verify_Param_t* prm, int* res) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEcc_Verify(sess, prm)); + if (MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEcc_Verify) != t) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + /* Unlike ELS, the PKC has a dedicated INVALID_SIGNATURE status, so here + * the status is the answer and a wrong signature is not an error. */ + if (MCUXCLECC_STATUS_OK == r) { + *res = 1; + } + else if (MCUXCLECC_STATUS_INVALID_SIGNATURE == r) { + *res = 0; + } + else { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return 0; +} + +static int ElsPkcEccVerify(const byte* sigDer, word32 sigLen, + const byte* hashIn, word32 hashLen, int* res, + ecc_key* key) +{ + mcuxClSession_Descriptor_t sess; + mcuxClEcc_Verify_Param_t prm; + ALIGN32 byte sig[2 * ELS_PKC_ECC_MAX_N]; + ALIGN32 byte hash[ELS_PKC_ECC_MAX_N]; + ALIGN32 byte pub[2 * ELS_PKC_ECC_MAX_P]; + ALIGN32 byte rOut[ELS_PKC_ECC_MAX_N]; + byte x963[1 + 2 * ELS_PKC_ECC_MAX_P]; + const byte* precG = NULL; + word32 x963Sz = sizeof(x963); + word32 lenP = 0, rLen, sLen; + int ret; + + if (sigDer == NULL || hashIn == NULL || res == NULL || key == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + /* As in the signer: the fixed buffer bounds this more tightly than the + * eight-bit byteLenHash field does. */ + if (hashLen == 0 || hashLen > 255 || hashLen > sizeof(hash)) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + *res = 0; + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + ret = ElsPkcLoadCurve(key, &prm.curveParam, &lenP); + + if (ret == 0) { + rLen = lenP; + sLen = lenP; + if (DecodeECC_DSA_Sig_Bin(sigDer, sigLen, sig, &rLen, + sig + lenP, &sLen) != 0) { + ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (ret == 0) { + ret = ElsPadSigComponent(sig, rLen, lenP); + } + if (ret == 0) { + ret = ElsPadSigComponent(sig + lenP, sLen, lenP); + } + } + + if (ret == 0) { + if (wc_ecc_export_x963_ex(key, x963, &x963Sz, 0) != 0 || + x963Sz != (1u + 2u * lenP) || x963[0] != 0x04) { + ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + else { + XMEMCPY(pub, x963 + 1, 2u * lenP); + } + } + + if (ret == 0) { + XMEMCPY(hash, hashIn, hashLen); + /* Verify consumes no nonce, so it never needs the DRG.3 instantiation + * the signer's k does - and that costs more than the operation. */ + ret = ElsPkcSessionOpen(&sess, 0); + if (ret == 0) { + ret = ElsPkcPrecGet(&sess, &prm.curveParam, key->dp->id, lenP, + &precG); + if (ret == 0) { + prm.pPrecG = precG; + prm.pHash = hash; + prm.pSignature = sig; + prm.pPublicKey = pub; + prm.pOutputR = rOut; + prm.optLen = mcuxClEcc_Verify_Param_optLen_Pack(hashLen); + + ret = ElsPkcEccVerifyRun(&sess, &prm, res); + } + ElsPkcSessionClose(&sess); + } + } + + ElsUnlock(); + + ForceZero(hash, sizeof(hash)); + ForceZero(rOut, sizeof(rOut)); + + return ret; +} + +#endif /* HAVE_ECC_VERIFY */ +#endif /* HAVE_ECC && (HAVE_ECC_SIGN || HAVE_ECC_VERIFY) */ + /* --------------------------------------------------------------------------- * Random * ------------------------------------------------------------------------ */ @@ -2626,6 +3384,11 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) ret = ElsEccSign(info->pk.eccsign.in, info->pk.eccsign.inlen, info->pk.eccsign.out, info->pk.eccsign.outlen, info->pk.eccsign.key); + if (ret == WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + ret = ElsPkcEccSign(info->pk.eccsign.in, + info->pk.eccsign.inlen, info->pk.eccsign.out, + info->pk.eccsign.outlen, info->pk.eccsign.key); + } break; #endif #if defined(HAVE_ECC) && defined(HAVE_ECC_VERIFY) @@ -2642,6 +3405,14 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) info->pk.eccverify.hashlen, info->pk.eccverify.res, info->pk.eccverify.key); + if (ret == WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + ret = ElsPkcEccVerify(info->pk.eccverify.sig, + info->pk.eccverify.siglen, + info->pk.eccverify.hash, + info->pk.eccverify.hashlen, + info->pk.eccverify.res, + info->pk.eccverify.key); + } break; #endif /* WC_PK_TYPE_ECDH is deliberately absent: ELS deposits the From ebd2fc4171c030e76dddbae8a1742e72b8d81d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 23:37:47 +0200 Subject: [PATCH 11/16] cryptocb: let wc_KeyStore_Derive() request the key size Derive is the only key store operation that creates a key without any way to say how big it should be. Every other one has an answer: ImportPlain has the plaintext's length, ImportWrapped reads it from the blob, the exports and GetInfo report a property of a key that already exists. Derive has keyType, which is a purpose rather than a length - AES is 128 or 256 either way - derivSz, which measures the input rather than the output, and attrs, which is three flag bits. So the device picks, the caller cannot ask, and GetInfo then reports a size the API gave no way to request. Add keySz, in bytes, beside the keyType it qualifies. Zero keeps the current behaviour and leaves the choice to the device, which is the right answer for a key store whose derivation has only one output size. PKCS#11 carries this in C_DeriveKey's template as CKA_VALUE_LEN and PSA in the attributes passed to psa_key_derivation_output_key(); this is the same parameter under a name that matches wc_KeyStore_ImportPlain(). The key store API reached master without a ChangeLog entry. Add one here, since this is the last change to that API and the entry can describe it as it ships. --- ChangeLog.md | 2 ++ doc/dox_comments/header_files/wc_keystore.h | 7 +++++-- tests/api/test_keystore.c | 13 ++++++++----- wolfcrypt/src/cryptocb.c | 3 ++- wolfcrypt/src/wc_keystore.c | 4 ++-- wolfcrypt/test/test.c | 6 ++++-- wolfssl/wolfcrypt/cryptocb.h | 3 ++- wolfssl/wolfcrypt/wc_keystore.h | 5 +++-- 8 files changed, 28 insertions(+), 15 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 366bf9f8aa4..964a7376654 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -189,6 +189,8 @@ ## New Features +* Added `WC_ALGO_TYPE_KEYSTORE`, a crypto callback algorithm type for lifetime operations on keys held in a hardware key store, with the public API in `wolfssl/wolfcrypt/wc_keystore.h` behind `--enable-cryptocbutils=keystore`. Seven operations - plaintext and wrapped import/export, derive, delete and get-info - address keys by an opaque device-defined reference that wolfCrypt copies through and never interprets, the same way it treats a key object's `id[]` blob. This lets a device create, wrap, derive and destroy keys that never appear in memory, which `WOLF_CRYPTO_CB_SETKEY` and `WOLF_CRYPTO_CB_EXPORT_KEY` cannot express because both are bound to a wolfCrypt key object holding material for its own use. + * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev * Added an NXP EdgeLock (ELS + PKC) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG and ECDSA on P-256 from a key slot; the PKC coprocessor serves RSA, X25519 and ECDSA on every curve. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. diff --git a/doc/dox_comments/header_files/wc_keystore.h b/doc/dox_comments/header_files/wc_keystore.h index 5d280e4159a..a036c0f1160 100644 --- a/doc/dox_comments/header_files/wc_keystore.h +++ b/doc/dox_comments/header_files/wc_keystore.h @@ -253,6 +253,9 @@ int wc_KeyStore_ExportWrapped(int devId, \param keyRefSz length of keyRef in bytes \param keyType what the derived key is for, from enum wc_KeyStoreKeyType, or WC_KEYSTORE_KEY_NONE to leave it to the device + \param keySz size in bytes of the key to create, or 0 to leave it to the + device. Unlike the other operations there is nothing else to infer it + from: the derivation data length is the input's, not the output's \param srcKeyRef opaque reference naming the derivation key \param srcKeyRefSz length of srcKeyRef in bytes \param kdfType derivation function, from enum wc_KdfType. @@ -266,7 +269,7 @@ int wc_KeyStore_ExportWrapped(int devId, _Example_ \code ret = wc_KeyStore_Derive(devId, keyRef, sizeof(keyRef), - WC_KEYSTORE_KEY_AES, + WC_KEYSTORE_KEY_AES, AES_256_KEY_SIZE, parentRef, sizeof(parentRef), WC_KDF_TYPE_NONE, deriv, sizeof(deriv), WC_KEYSTORE_ATTR_EXPORTABLE, NULL); @@ -275,7 +278,7 @@ int wc_KeyStore_ExportWrapped(int devId, \sa wc_KeyStore_GetInfo */ int wc_KeyStore_Derive(int devId, - const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* keyRef, word32 keyRefSz, word32 keyType, word32 keySz, const byte* srcKeyRef, word32 srcKeyRefSz, word32 kdfType, const byte* deriv, word32 derivSz, word32 attrs, const void* ctx); diff --git a/tests/api/test_keystore.c b/tests/api/test_keystore.c index 0bdf1d920d3..687cb646f2a 100644 --- a/tests/api/test_keystore.c +++ b/tests/api/test_keystore.c @@ -120,6 +120,7 @@ static int KsCb(int devIdArg, wc_CryptoInfo* info, void* ctx) seen->otherRef = info->keystore.op.derive.srcKeyRef; seen->otherRefSz = info->keystore.op.derive.srcKeyRefSz; seen->keyType = info->keystore.op.derive.keyType; + seen->keySz = info->keystore.op.derive.keySz; seen->attrs = info->keystore.op.derive.attrs; seen->kdfType = info->keystore.op.derive.kdfType; seen->deriv = info->keystore.op.derive.deriv; @@ -386,11 +387,12 @@ int test_wc_KeyStore_Derive(void) * kdfType and attrs numerically apart or a transposition reads as equal. */ ExpectIntEQ(wc_KeyStore_Derive(KS_TEST_DEVID, ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, - ksOtherRef, (word32)sizeof(ksOtherRef), + AES_256_KEY_SIZE, ksOtherRef, (word32)sizeof(ksOtherRef), WC_KDF_TYPE_HKDF, ksDeriv, (word32)sizeof(ksDeriv), WC_KEYSTORE_ATTR_PERSISTENT, ksCallerCtx), 0); ExpectIntEQ(seen.op, WC_KEYSTORE_DERIVE); ExpectIntEQ(seen.keyType, WC_KEYSTORE_KEY_AES); + ExpectIntEQ(seen.keySz, AES_256_KEY_SIZE); ExpectPtrEq(seen.keyRef, ksKeyRef); ExpectIntEQ(seen.keyRefSz, (word32)sizeof(ksKeyRef)); ExpectPtrEq(seen.otherRef, ksOtherRef); @@ -403,21 +405,22 @@ int test_wc_KeyStore_Derive(void) /* WC_KDF_TYPE_NONE asks for the device's own derivation */ ExpectIntEQ(wc_KeyStore_Derive(KS_TEST_DEVID, - ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_NONE, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_NONE, 0, ksOtherRef, (word32)sizeof(ksOtherRef), WC_KDF_TYPE_NONE, ksDeriv, (word32)sizeof(ksDeriv), WC_KEYSTORE_ATTR_EXPORTABLE, NULL), 0); ExpectIntEQ(seen.kdfType, WC_KDF_TYPE_NONE); + ExpectIntEQ(seen.keySz, 0); /* 0 leaves the size to the device */ ExpectIntEQ(seen.attrs, WC_KEYSTORE_ATTR_EXPORTABLE); seen.calls = 0; ExpectIntEQ(wc_KeyStore_Derive(KS_TEST_DEVID, - ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, NULL, 0, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, 0, NULL, 0, WC_KDF_TYPE_NONE, ksDeriv, (word32)sizeof(ksDeriv), 0, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); /* No derivation data means no length beside it. */ ExpectIntEQ(wc_KeyStore_Derive(KS_TEST_DEVID, - ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, 0, ksOtherRef, (word32)sizeof(ksOtherRef), WC_KDF_TYPE_NONE, NULL, (word32)sizeof(ksDeriv), 0, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); @@ -532,7 +535,7 @@ int test_wc_KeyStore_NoDevice(void) ExpectIntEQ(keySz, 0); ExpectIntEQ(attrs, 0); ExpectIntEQ(wc_KeyStore_Derive(KS_TEST_DEVID, - ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, + ksKeyRef, (word32)sizeof(ksKeyRef), WC_KEYSTORE_KEY_AES, 0, ksOtherRef, (word32)sizeof(ksOtherRef), WC_KDF_TYPE_NONE, ksDeriv, (word32)sizeof(ksDeriv), 0, NULL), WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)); diff --git a/wolfcrypt/src/cryptocb.c b/wolfcrypt/src/cryptocb.c index 8783a1684fb..866970e9d85 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -3987,7 +3987,7 @@ int wc_CryptoCb_KeyStoreExportWrapped(int devId, } int wc_CryptoCb_KeyStoreDerive(int devId, - const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* keyRef, word32 keyRefSz, word32 keyType, word32 keySz, const byte* srcKeyRef, word32 srcKeyRefSz, word32 kdfType, const byte* deriv, word32 derivSz, word32 attrs, const void* ctx) @@ -4013,6 +4013,7 @@ int wc_CryptoCb_KeyStoreDerive(int devId, cryptoInfo.keystore.op.derive.keyRef = keyRef; cryptoInfo.keystore.op.derive.keyRefSz = keyRefSz; cryptoInfo.keystore.op.derive.keyType = keyType; + cryptoInfo.keystore.op.derive.keySz = keySz; cryptoInfo.keystore.op.derive.srcKeyRef = srcKeyRef; cryptoInfo.keystore.op.derive.srcKeyRefSz = srcKeyRefSz; cryptoInfo.keystore.op.derive.deriv = deriv; diff --git a/wolfcrypt/src/wc_keystore.c b/wolfcrypt/src/wc_keystore.c index 93055d57b20..621ef4bf6a5 100644 --- a/wolfcrypt/src/wc_keystore.c +++ b/wolfcrypt/src/wc_keystore.c @@ -67,12 +67,12 @@ int wc_KeyStore_ExportWrapped(int devId, } int wc_KeyStore_Derive(int devId, - const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* keyRef, word32 keyRefSz, word32 keyType, word32 keySz, const byte* srcKeyRef, word32 srcKeyRefSz, word32 kdfType, const byte* deriv, word32 derivSz, word32 attrs, const void* ctx) { - return wc_CryptoCb_KeyStoreDerive(devId, keyRef, keyRefSz, keyType, + return wc_CryptoCb_KeyStoreDerive(devId, keyRef, keyRefSz, keyType, keySz, srcKeyRef, srcKeyRefSz, kdfType, deriv, derivSz, attrs, ctx); } diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 33a25729dcc..a3a29da3784 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -69943,6 +69943,7 @@ static int keystoreTestCb(int cbDevId, wc_CryptoInfo* info, void* ctx) c->lastSrcKeyRef = info->keystore.op.derive.srcKeyRef; c->lastOtherRefSz = info->keystore.op.derive.srcKeyRefSz; c->lastKeyType = info->keystore.op.derive.keyType; + c->lastKeySz = info->keystore.op.derive.keySz; c->lastAttrs = info->keystore.op.derive.attrs; c->lastKdfType = info->keystore.op.derive.kdfType; c->lastDeriv = info->keystore.op.derive.deriv; @@ -70051,14 +70052,15 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t keystore_cb_test(void) } ret = wc_KeyStore_Derive(ksDevId, kref, (word32)sizeof(kref), - WC_KEYSTORE_KEY_CMAC, sref, (word32)sizeof(sref), - WC_KDF_TYPE_HKDF, dd, (word32)sizeof(dd), + WC_KEYSTORE_KEY_CMAC, WC_AES_BLOCK_SIZE, sref, + (word32)sizeof(sref), WC_KDF_TYPE_HKDF, dd, (word32)sizeof(dd), WC_KEYSTORE_ATTR_PERSISTENT, NULL); if (ret != 0 || ctx.lastOp != WC_KEYSTORE_DERIVE || ctx.lastKeyRef != kref || ctx.lastKeyRefSz != (word32)sizeof(kref) || ctx.lastSrcKeyRef != sref || ctx.lastOtherRefSz != (word32)sizeof(sref) || ctx.lastKeyType != WC_KEYSTORE_KEY_CMAC || + ctx.lastKeySz != WC_AES_BLOCK_SIZE || ctx.lastAttrs != WC_KEYSTORE_ATTR_PERSISTENT || ctx.lastKdfType != WC_KDF_TYPE_HKDF || ctx.lastDeriv != dd || ctx.lastDerivSz != (word32)sizeof(dd)) { diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index 9c32ecdf230..f2c1a31ba77 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -910,6 +910,7 @@ typedef struct wc_CryptoInfo { const byte* keyRef; /* opaque: where it should land */ word32 keyRefSz; word32 keyType; /* enum wc_KeyStoreKeyType */ + word32 keySz; /* bytes; 0 = device's own default */ word32 attrs; /* WC_KEYSTORE_ATTR_* */ const byte* srcKeyRef; /* opaque: the derivation key */ word32 srcKeyRefSz; @@ -1418,7 +1419,7 @@ WOLFSSL_LOCAL int wc_CryptoCb_KeyStoreExportWrapped(int devId, const byte* wrapKeyRef, word32 wrapKeyRefSz, word32 format, byte* blob, word32* blobSz, const void* ctx); WOLFSSL_LOCAL int wc_CryptoCb_KeyStoreDerive(int devId, - const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* keyRef, word32 keyRefSz, word32 keyType, word32 keySz, const byte* srcKeyRef, word32 srcKeyRefSz, word32 kdfType, const byte* deriv, word32 derivSz, word32 attrs, const void* ctx); diff --git a/wolfssl/wolfcrypt/wc_keystore.h b/wolfssl/wolfcrypt/wc_keystore.h index 1e268778448..79426a3d3b0 100644 --- a/wolfssl/wolfcrypt/wc_keystore.h +++ b/wolfssl/wolfcrypt/wc_keystore.h @@ -77,9 +77,10 @@ WOLFSSL_API int wc_KeyStore_ExportWrapped(int devId, word32 format, byte* blob, word32* blobSz, const void* ctx); /* Derive a new stored key from an existing one without either touching RAM. - * derivSz is algorithm-specific and often fixed by the hardware. */ + * keySz is the size in bytes of the key to create, or 0 to leave it to the + * device; derivSz is algorithm-specific and often fixed by the hardware. */ WOLFSSL_API int wc_KeyStore_Derive(int devId, - const byte* keyRef, word32 keyRefSz, word32 keyType, + const byte* keyRef, word32 keyRefSz, word32 keyType, word32 keySz, const byte* srcKeyRef, word32 srcKeyRefSz, word32 kdfType, const byte* deriv, word32 derivSz, word32 attrs, const void* ctx); From e077d8ae642a62a43f010e2313c7ef6f1561f2f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:46 +0200 Subject: [PATCH 12/16] nxp: implement the EdgeLock key store operations Wires WC_ALGO_TYPE_KEYSTORE to EdgeLock: import a wrapped blob straight into a slot, wrap a stored key back out, derive one slot from another, delete, and report what a slot holds. None of these can be expressed by an algorithm callback, since import and export carry a container that never becomes plaintext on this side and derive moves a key slot to slot without it entering memory. Validation matters more here than anywhere else in the port: these calls name two slots rather than one, and a permission mistake resets the SoC. Delete checks the slot is occupied first for the same reason. The wrapping key's two permissions are not interchangeable. ukwk both wraps and unwraps while ukuok only unwraps, so an export demands more than an import does, which is the entire point of an unwrap-only key. A derived wrapping key is given ukwk and nothing else. Exportability is decided at creation and cannot be added afterwards, so a key made without wrpok is refused an export rather than attempted. Only the vendor container is accepted: a bare RFC 3394 blob has no property word for ELS to set the imported key's permissions from. Slots 0 and 1 are retention and slot 2 is hardware-out, holding the die key and the flash encryption key, so an ordinary key is required to name a general purpose slot. --- ChangeLog.md | 2 +- wolfcrypt/src/port/nxp/README.md | 19 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 619 +++++++++++++++++++++- wolfssl/wolfcrypt/port/nxp/els_pkc_port.h | 11 + zephyr/user_settings.h | 3 + 5 files changed, 650 insertions(+), 4 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 964a7376654..4efaf8a0d25 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -193,7 +193,7 @@ * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev -* Added an NXP EdgeLock (ELS + PKC) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG and ECDSA on P-256 from a key slot; the PKC coprocessor serves RSA, X25519 and ECDSA on every curve. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. +* Added an NXP EdgeLock (ELS + PKC) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG and ECDSA on P-256 from a key slot; the PKC coprocessor serves RSA, X25519 and ECDSA on every curve. Keys resident in the ELS key store are referenced rather than exported, through the new `WC_ALGO_TYPE_KEYSTORE` operations. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. ## Fixes diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 9768b580e9a..2d30c647f61 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -55,7 +55,10 @@ RW612 and related parts, through the crypto callback interface. The ELS peripheral serves SHA-256, SHA-384, SHA-512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG, and ECDSA on P-256 for a key that names a slot; the PKC coprocessor serves RSA, X25519, and ECDSA on every curve, P-256 included when -the key holds ordinary material. +the key holds ordinary material. Five key store operations cover importing +and exporting wrapped keys, deriving one key from another, deleting a key, +and reading back what a slot holds. Generating happens through +`wc_ecc_make_key()` instead. Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size costs @@ -120,6 +123,14 @@ bytes. **ECDH is not offloaded at all.** ELS deposits the agreed secret in a key slot, which cannot be read back, while the wolfCrypt ECDH callback has to hand a buffer to its caller - so the callback declines and software answers. +An in-slot agreement would need `mcuxClEls_EccKeyExchange_Async` exposed +through a key store operation, which this port does not yet do: +`wc_KeyStore_Derive()` drives the SP800-108 CMAC KDF only, from a +`WC_ELSPKC_KEY_CKDF` source, in either of the two sizes an ELS slot key can +have - pass 16 or 32 bytes as `keySz`, or 0 for the 128-bit default. A +256-bit key spans the slot pair, so the reference must name the first of two +free slots. A slot generated as `WC_ELSPKC_KEY_ECC_DH` therefore has no +consumer here yet. **The PKC uses a CTR_DRBG, not the ELS DRBG.** The ELS DRBG's security strength is 128 bits on this part, so `mcuxClEcc_Sign` refuses P-384 and @@ -128,6 +139,12 @@ the curve. A plain `wc_GenerateRandom()` is a different path and does reach the ELS DRBG directly. The PKC workarea is a fixed hardware region shared by every PKC consumer, which is why PKC work runs under the same lock as ELS. +**Key store permissions are per-purpose.** `uaes` and `ucmac` are separate +permission bits, so a slot holding both needs one reference per class, and +`wrpok` is set at creation and cannot be added later - a key not made +exportable can never leave. A generated key's public point is available only +at the moment of generation; the hardware will not hand it back afterwards. + ### Vendor library The port depends on NXP's CLNS library, `els_pkc`, which ships as an diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index 00a9d453976..a9c000151cc 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -72,7 +72,7 @@ * SoC, so they share the helpers in the next section. */ #if defined(ELS_HAVE_AES_BLOCK_CIPHER) || \ (!defined(NO_AES) && (defined(HAVE_AESGCM) || defined(WOLFSSL_CMAC))) || \ - defined(HAVE_ECC) + defined(HAVE_ECC) || defined(WOLF_CRYPTO_CB_KEYSTORE) #define ELS_HAVE_SLOT_KEYS #endif @@ -370,7 +370,7 @@ int wc_ElsPkc_ParseKeyRef(const byte* in, word32 inSz, wc_ElsPkc_KeyRef* ref) /* Slot validation, reached from every path that can name a slot. */ #ifdef ELS_HAVE_SLOT_KEYS -#ifdef HAVE_ECC +#if defined(HAVE_ECC) || defined(WOLF_CRYPTO_CB_KEYSTORE) /* Read a slot's properties. Caller holds the lock. */ static int ElsKsProps(byte slot, mcuxClEls_KeyProp_t* prop) { @@ -2893,6 +2893,592 @@ static int ElsPkcEccVerify(const byte* sigDer, word32 sigLen, #endif /* HAVE_ECC_VERIFY */ #endif /* HAVE_ECC && (HAVE_ECC_SIGN || HAVE_ECC_VERIFY) */ +/* --------------------------------------------------------------------------- + * Key store + * ------------------------------------------------------------------------ */ + +/* These calls name two slots rather than one, and a permission mistake resets + * the SoC, so both references are validated against the hardware first. */ + +#ifdef WOLF_CRYPTO_CB_KEYSTORE + +/* The container is the key plus an 8-byte property/padding prefix, all inside + * an RFC 3394 wrap that adds its own 8-byte integrity block. */ +#define ELS_KS_BLOB_SZ(keySz) ((word32)(keySz) + 16u) + +/* ELS derives with a fixed-width label/context block. */ +#define ELS_KS_DERIV_SZ MCUXCLELS_CKDF_DERIVATIONDATA_SIZE + +/* A slot holds 128 bits; a 256-bit key spans the pair. */ +#define ELS_KS_KEY_128_SZ 16u +#define ELS_KS_KEY_256_SZ 32u + +/* Map a stored key's ELS properties onto the facility's vocabulary. */ +static word32 ElsKsTypeFromProp(const mcuxClEls_KeyProp_t* prop) +{ + if (prop->word.value & MCUXCLELS_KEYPROPERTY_VALUE_ECSGN) { + return WC_KEYSTORE_KEY_ECC_SIGN; + } + if (prop->word.value & MCUXCLELS_KEYPROPERTY_VALUE_ECDH) { + return WC_KEYSTORE_KEY_ECC_DH; + } + if (prop->word.value & MCUXCLELS_KEYPROPERTY_VALUE_HMAC) { + return WC_KEYSTORE_KEY_HMAC; + } + if (prop->word.value & MCUXCLELS_KEYPROPERTY_VALUE_CMAC) { + return WC_KEYSTORE_KEY_CMAC; + } + if (prop->word.value & MCUXCLELS_KEYPROPERTY_VALUE_AES) { + return WC_KEYSTORE_KEY_AES; + } + /* Reported after the algorithm usages on purpose: a slot carrying both is + * more usefully described by what it computes than by what it wraps. */ + if (prop->word.value & (MCUXCLELS_KEYPROPERTY_VALUE_KWK | + MCUXCLELS_KEYPROPERTY_VALUE_KUOK)) { + return WC_KEYSTORE_KEY_WRAP; + } + if (prop->word.value & (MCUXCLELS_KEYPROPERTY_VALUE_CKDF | + MCUXCLELS_KEYPROPERTY_VALUE_HKDF)) { + return WC_KEYSTORE_KEY_DERIVE; + } + + return WC_KEYSTORE_KEY_NONE; +} + +/* The facility's key type for a reference's class. */ +static word32 ElsKsTypeFromClass(byte keyClass) +{ + switch (keyClass) { + case WC_ELSPKC_KEY_ECC_SIGN: return WC_KEYSTORE_KEY_ECC_SIGN; + case WC_ELSPKC_KEY_ECC_DH: return WC_KEYSTORE_KEY_ECC_DH; + case WC_ELSPKC_KEY_AES: return WC_KEYSTORE_KEY_AES; + case WC_ELSPKC_KEY_HMAC: return WC_KEYSTORE_KEY_HMAC; + case WC_ELSPKC_KEY_CMAC: return WC_KEYSTORE_KEY_CMAC; + case WC_ELSPKC_KEY_KWK: return WC_KEYSTORE_KEY_WRAP; + case WC_ELSPKC_KEY_CKDF: /* fall through */ + case WC_ELSPKC_KEY_HKDF: return WC_KEYSTORE_KEY_DERIVE; + default: return WC_KEYSTORE_KEY_NONE; + } +} + +/* Check the caller's stated keyType against what the target reference names. + * WC_KEYSTORE_KEY_NONE is unspecified, not a claim to disagree with. */ +static int ElsKsCheckType(word32 keyType, byte keyClass) +{ + if (keyType == WC_KEYSTORE_KEY_NONE) { + return 0; + } + if (keyType != ElsKsTypeFromClass(keyClass)) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + return 0; +} + +/* Key size in bits, from the two-bit size field. */ +static word32 ElsKsBitsFromProp(const mcuxClEls_KeyProp_t* prop) +{ + /* The 512-bit encoding only exists on parts with an internal public-key + * store; elsewhere the field holds 128 or 256 and nothing else. */ + switch (prop->word.value & 0x3u) { + case MCUXCLELS_KEYPROPERTY_VALUE_KEY_SIZE_128: + return 128; + case MCUXCLELS_KEYPROPERTY_VALUE_KEY_SIZE_256: + return 256; +#ifdef MCUXCL_FEATURE_ELS_PUK_INTERNAL + case MCUXCLELS_KEYPROPERTY_VALUE_KEY_SIZE_512: + return 512; +#endif + default: + return 0; + } +} + +/* Find room for a key that does not exist yet, since PSA's generate has no way + * for a caller to name a slot. The store is not empty on a fresh part, so the + * only safe test is the hardware's active bit, and a 256-bit key needs a + * consecutive pair. Advisory: reserve and generate together. */ +int wc_ElsPkc_ReserveSlot(byte keyClass, wc_ElsPkc_KeyRef* ref) +{ + mcuxClEls_KeyProp_t prop; + int need; + int run = 0; + int slot; + int ret; + + if (ref == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + if (ElsClassUsageBit(keyClass) == 0) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + need = (keyClass == WC_ELSPKC_KEY_ECC_SIGN || + keyClass == WC_ELSPKC_KEY_ECC_DH) ? 2 : 1; + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + for (slot = 0; slot <= WC_ELSPKC_MAX_SLOT; slot++) { + ret = ElsKsProps((byte)slot, &prop); + if (ret != 0) { + break; + } + if (prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE) { + run = 0; + continue; + } + /* Free is not the same as usable: slots 0 and 1 are retention and + * slot 2 is hardware-out on this part. Only general purpose slots. */ + if ((prop.word.value & + MCUXCLELS_KEYPROPERTY_VALUE_GENERAL_PURPOSE_SLOT) == 0) { + run = 0; + continue; + } + if (++run == need) { + XMEMSET(ref, 0, sizeof(*ref)); + ref->keyClass = keyClass; + ref->slot = (byte)(slot - (need - 1)); + ElsUnlock(); + return 0; + } + } + + ElsUnlock(); + + return (ret == 0) ? WC_NO_ERR_TRACE(MEMORY_E) : ret; +} + +/* Parse a reference and, when it must already exist, confirm the slot carries + * the permission its class claims. */ +static int ElsKsRef(const byte* ref, word32 refSz, byte expectClass, + int mustExist, wc_ElsPkc_KeyRef* out) +{ + int ret = wc_ElsPkc_ParseKeyRef(ref, refSz, out); + + if (ret != 0) { + return ret; + } + if (out->keyClass != expectClass) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + + return mustExist ? ElsCheckSlot(out, expectClass) : 0; +} + +/* ukwk both wraps and unwraps, ukuok only unwraps, so an export needs more + * than an import does - the point of an unwrap-only key. */ +static int ElsKsWrapRef(const byte* ref, word32 refSz, int needWrap, + wc_ElsPkc_KeyRef* out) +{ + mcuxClEls_KeyProp_t prop; + int ret = wc_ElsPkc_ParseKeyRef(ref, refSz, out); + + if (ret != 0) { + return ret; + } + if (out->keyClass != WC_ELSPKC_KEY_KWK) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + ret = ElsKsProps(out->slot, &prop); + if (ret != 0) { + return ret; + } + if ((prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE) == 0) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + if (needWrap) { + if ((prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_KWK) == 0) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + } + else if ((prop.word.value & (MCUXCLELS_KEYPROPERTY_VALUE_KWK | + MCUXCLELS_KEYPROPERTY_VALUE_KUOK)) == 0) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + + return 0; +} + +/* A creation path must not destroy a key nobody asked to replace. Key + * generation already refuses an occupied target; import and derive use this to + * apply the same rule. Caller holds the lock. */ +static int ElsKsTargetFree(byte slot) +{ + mcuxClEls_KeyProp_t cur; + int ret; + + ret = ElsKsProps(slot, &cur); + if (ret == 0 && (cur.word.value & MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE)) { + ret = WC_NO_ERR_TRACE(BAD_STATE_E); + } + + return ret; +} + +static int ElsKsImportRun(const byte* blob, word32 blobSz, byte wrapSlot, + byte targetSlot) +{ + mcuxClEls_KeyImportOption_t opt; + + opt.word.value = 0u; + opt.bits.kfmt = MCUXCLELS_KEYIMPORT_KFMT_RFC3394; + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_KeyImport_Async( + opt, blob, (size_t)blobSz, (mcuxClEls_KeyIndex_t)wrapSlot, + (mcuxClEls_KeyIndex_t)targetSlot)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_KeyImport_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return ElsWait(); +} + +static int ElsKsExportRun(byte wrapSlot, byte keySlot, byte* out) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_KeyExport_Async( + (mcuxClEls_KeyIndex_t)wrapSlot, (mcuxClEls_KeyIndex_t)keySlot, out)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_KeyExport_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return ElsWait(); +} + +static int ElsKsDeriveRun(byte srcSlot, byte targetSlot, + mcuxClEls_KeyProp_t prop, const byte* deriv) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_Ckdf_Sp800108_Async( + (mcuxClEls_KeyIndex_t)srcSlot, (mcuxClEls_KeyIndex_t)targetSlot, + prop, deriv)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_Ckdf_Sp800108_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return ElsWait(); +} + +static int ElsKsDeleteRun(byte slot) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, + mcuxClEls_KeyDelete_Async((mcuxClEls_KeyIndex_t)slot)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_KeyDelete_Async) != t) || + (MCUXCLELS_STATUS_OK_WAIT != r)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return ElsWait(); +} + +static int ElsKsImport(wc_CryptoInfo* info) +{ + wc_ElsPkc_KeyRef target, wrap; + int ret; + + if (info->keystore.op.importWrapped.blob == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + /* Only this container shape. A bare RFC 3394 blob has no property word, + * so ELS would have nothing to set the imported key's permissions from. */ + if (info->keystore.op.importWrapped.format != WC_KEYWRAP_FORMAT_VENDOR) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (info->keystore.op.importWrapped.blobSz != ELS_KS_BLOB_SZ(16) && + info->keystore.op.importWrapped.blobSz != ELS_KS_BLOB_SZ(32)) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + /* The target need not exist yet - that is the point of an import - so its + * reference is parsed but not checked against the hardware. */ + ret = wc_ElsPkc_ParseKeyRef(info->keystore.op.importWrapped.keyRef, + info->keystore.op.importWrapped.keyRefSz, + &target); + if (ret == 0) { + /* A cross-check, not a source: the container's property word decides. + * Catches the right blob aimed at the wrong slot, before the unwrap. */ + ret = ElsKsCheckType(info->keystore.op.importWrapped.keyType, + target.keyClass); + } + /* attrs is not consulted: the container's property word decides, and it is + * inside the wrap, so there is nothing to compare against yet. */ + if (ret == 0) { + ret = ElsKsWrapRef(info->keystore.op.importWrapped.wrapKeyRef, + info->keystore.op.importWrapped.wrapKeyRefSz, + 0, &wrap); + } + if (ret == 0) { + ret = ElsKsTargetFree(target.slot); + } + if (ret == 0) { + ret = ElsKsImportRun(info->keystore.op.importWrapped.blob, + info->keystore.op.importWrapped.blobSz, + wrap.slot, target.slot); + } + + ElsUnlock(); + + return ret; +} + +static int ElsKsExport(wc_CryptoInfo* info) +{ + wc_ElsPkc_KeyRef key, wrap; + mcuxClEls_KeyProp_t prop; + word32 need = 0; + int ret; + + if (info->keystore.op.exportWrapped.blobSz == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + if (info->keystore.op.exportWrapped.format != WC_KEYWRAP_FORMAT_VENDOR) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + ret = wc_ElsPkc_ParseKeyRef(info->keystore.op.exportWrapped.keyRef, + info->keystore.op.exportWrapped.keyRefSz, + &key); + if (ret == 0) { + ret = ElsKsProps(key.slot, &prop); + } + if (ret == 0) { + word32 bits = ElsKsBitsFromProp(&prop); + + if ((prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE) == 0) { + ret = WC_NO_ERR_TRACE(BAD_STATE_E); + } + /* wrpok is set at creation and cannot be added later, so a key that + * was not made exportable simply cannot leave. */ + else if ((prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_WRPOK) == 0) { + ret = WC_NO_ERR_TRACE(BAD_STATE_E); + } + else if (bits != 128 && bits != 256) { + ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + else { + need = ELS_KS_BLOB_SZ(bits / 8u); + + /* Size query: the caller asks how big the container will be. + * wc_KeyStore_ExportWrapped() defines this form as succeeding, so + * it returns 0 - LENGTH_ONLY_E is this port's own convention on + * wc_ElsPkc_MakeKeyRef() and does not belong at this boundary. */ + if (info->keystore.op.exportWrapped.blob == NULL) { + *info->keystore.op.exportWrapped.blobSz = need; + ElsUnlock(); + return 0; + } + if (*info->keystore.op.exportWrapped.blobSz < need) { + ret = WC_NO_ERR_TRACE(BUFFER_E); + } + } + } + + if (ret == 0) { + ret = ElsKsWrapRef(info->keystore.op.exportWrapped.wrapKeyRef, + info->keystore.op.exportWrapped.wrapKeyRefSz, + 1, &wrap); + } + if (ret == 0) { + ret = ElsKsExportRun(wrap.slot, key.slot, + info->keystore.op.exportWrapped.blob); + } + + ElsUnlock(); + + if (ret == 0) { + *info->keystore.op.exportWrapped.blobSz = need; + } + + return ret; +} + +static int ElsKsDerive(wc_CryptoInfo* info) +{ + wc_ElsPkc_KeyRef target, src; + mcuxClEls_KeyProp_t prop; + word32 keySz; + int ret; + + if (info->keystore.op.derive.deriv == NULL || + info->keystore.op.derive.derivSz != ELS_KS_DERIV_SZ) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + /* The engine has one KDF. Substituting it for the one the caller asked + * for would leave two peers deriving different keys with no diagnostic. */ + if (info->keystore.op.derive.kdfType != WC_KDF_TYPE_NONE && + info->keystore.op.derive.kdfType != WC_KDF_TYPE_TWOSTEP_CMAC) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + /* The only two sizes an ELS slot key can have. Zero takes the smaller, + * which is what the engine's zero-valued property word already encodes. */ + keySz = info->keystore.op.derive.keySz; + if (keySz == 0) { + keySz = ELS_KS_KEY_128_SZ; + } + if (keySz != ELS_KS_KEY_128_SZ && keySz != ELS_KS_KEY_256_SZ) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + + /* The source must already be a derivation key; the target is where the + * result lands and does not exist yet. */ + ret = ElsKsRef(info->keystore.op.derive.srcKeyRef, + info->keystore.op.derive.srcKeyRefSz, + WC_ELSPKC_KEY_CKDF, 1, &src); + if (ret == 0) { + ret = wc_ElsPkc_ParseKeyRef(info->keystore.op.derive.keyRef, + info->keystore.op.derive.keyRefSz, + &target); + } + if (ret == 0) { + ret = ElsKsCheckType(info->keystore.op.derive.keyType, + target.keyClass); + } + if (ret == 0) { + ret = ElsKsTargetFree(target.slot); + } + if (ret == 0 && keySz == ELS_KS_KEY_256_SZ) { + /* A 256-bit key occupies the slot pair, so the second one has to be + * free too - and in range, since the reference names only the first. */ + if (target.slot >= WC_ELSPKC_MAX_SLOT) { + ret = WC_NO_ERR_TRACE(BAD_STATE_E); + } + else { + ret = ElsKsTargetFree((byte)(target.slot + 1u)); + } + } + if (ret == 0) { + /* The derived key's permissions come from the target reference's + * class, which keyType has just been checked against. */ + prop.word.value = 0u; + if (keySz == ELS_KS_KEY_256_SZ) { + prop.bits.ksize = MCUXCLELS_KEYPROPERTY_KEY_SIZE_256; + } + prop.bits.upprot_priv = MCUXCLELS_KEYPROPERTY_PRIVILEGED_FALSE; + prop.bits.upprot_sec = MCUXCLELS_KEYPROPERTY_SECURE_FALSE; + prop.word.value |= ElsClassUsageBit(target.keyClass); + if (info->keystore.op.derive.attrs & WC_KEYSTORE_ATTR_EXPORTABLE) { + prop.bits.wrpok = MCUXCLELS_KEYPROPERTY_WRAP_TRUE; + } + if (target.keyClass == WC_ELSPKC_KEY_KWK && + (info->keystore.op.derive.attrs & WC_KEYSTORE_ATTR_UNWRAP_ONLY)) { + /* An unwrap-only wrapping key: it can bring keys in and never + * take them out. */ + prop.word.value &= ~MCUXCLELS_KEYPROPERTY_VALUE_KWK; + prop.word.value |= MCUXCLELS_KEYPROPERTY_VALUE_KUOK; + } + + ret = ElsKsDeriveRun(src.slot, target.slot, prop, + info->keystore.op.derive.deriv); + } + + ElsUnlock(); + + return ret; +} + +static int ElsKsDelete(wc_CryptoInfo* info) +{ + wc_ElsPkc_KeyRef key; + int ret = ElsLock(); + + if (ret != 0) { + return ret; + } + + ret = wc_ElsPkc_ParseKeyRef(info->keystore.op.deleteKey.keyRef, + info->keystore.op.deleteKey.keyRefSz, &key); + if (ret == 0) { + mcuxClEls_KeyProp_t prop; + + ret = ElsKsProps(key.slot, &prop); + if (ret == 0) { + /* Deleting an empty slot is a caller mistake, and this part does + * not return errors for those. Nothing is destroyed either way. */ + if ((prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE) == 0) { + ElsUnlock(); + return 0; + } + ret = ElsKsDeleteRun(key.slot); + } + } + + ElsUnlock(); + + return ret; +} + +static int ElsKsGetInfo(wc_CryptoInfo* info) +{ + wc_ElsPkc_KeyRef key; + mcuxClEls_KeyProp_t prop; + int ret = ElsLock(); + + if (ret != 0) { + return ret; + } + + ret = wc_ElsPkc_ParseKeyRef(info->keystore.op.getInfo.keyRef, + info->keystore.op.getInfo.keyRefSz, &key); + if (ret == 0) { + ret = ElsKsProps(key.slot, &prop); + } + + ElsUnlock(); + + if (ret == 0) { + if ((prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE) == 0) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + if (info->keystore.op.getInfo.keyType != NULL) { + *info->keystore.op.getInfo.keyType = ElsKsTypeFromProp(&prop); + } + if (info->keystore.op.getInfo.keyBits != NULL) { + *info->keystore.op.getInfo.keyBits = ElsKsBitsFromProp(&prop); + } + if (info->keystore.op.getInfo.attrs != NULL) { + word32 a = 0; + + if (prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_WRPOK) { + a |= WC_KEYSTORE_ATTR_EXPORTABLE; + } + if ((prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_KUOK) && + !(prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_KWK)) { + a |= WC_KEYSTORE_ATTR_UNWRAP_ONLY; + } + if (prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_RETENTION_SLOT) { + a |= WC_KEYSTORE_ATTR_PERSISTENT; + } + *info->keystore.op.getInfo.attrs = a; + } + } + + return ret; +} + +#endif /* WOLF_CRYPTO_CB_KEYSTORE */ + /* --------------------------------------------------------------------------- * Random * ------------------------------------------------------------------------ */ @@ -3425,6 +4011,35 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) break; #endif /* HAVE_ECC || !NO_RSA || HAVE_CURVE25519 */ +#ifdef WOLF_CRYPTO_CB_KEYSTORE + case WC_ALGO_TYPE_KEYSTORE: + switch (info->keystore.type) { + /* No plaintext path in either direction: a key enters only as + * an unwrapped blob or a derivation, and leaves only wrapped. */ + case WC_KEYSTORE_IMPORT_PLAIN: + case WC_KEYSTORE_EXPORT_PLAIN: + break; + case WC_KEYSTORE_IMPORT_WRAPPED: + ret = ElsKsImport(info); + break; + case WC_KEYSTORE_EXPORT_WRAPPED: + ret = ElsKsExport(info); + break; + case WC_KEYSTORE_DERIVE: + ret = ElsKsDerive(info); + break; + case WC_KEYSTORE_DELETE: + ret = ElsKsDelete(info); + break; + case WC_KEYSTORE_GET_INFO: + ret = ElsKsGetInfo(info); + break; + default: + break; + } + break; +#endif /* WOLF_CRYPTO_CB_KEYSTORE */ + #ifndef NO_AES case WC_ALGO_TYPE_CIPHER: switch (info->cipher.type) { diff --git a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h index 4a67a873a29..05db6937ac6 100644 --- a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h +++ b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h @@ -47,6 +47,9 @@ #include #include +#ifdef WOLF_CRYPTO_CB_KEYSTORE + #include +#endif #ifdef HAVE_ECC #include #endif @@ -135,6 +138,14 @@ WOLFSSL_API int wc_ElsPkc_MakeKeyRef(const wc_ElsPkc_KeyRef* ref, byte* out, WOLFSSL_API int wc_ElsPkc_ParseKeyRef(const byte* in, word32 inSz, wc_ElsPkc_KeyRef* ref); +#ifdef WOLF_CRYPTO_CB_KEYSTORE +/* Choose a free slot for a key that does not exist yet and fill in a reference + * naming it. Returns MEMORY_E when the store has no room. Advisory: nothing is + * marked taken until a key is written, so reserve and generate together. */ +WOLFSSL_API int wc_ElsPkc_ReserveSlot(byte keyClass, wc_ElsPkc_KeyRef* ref); + +#endif + #ifdef HAVE_ECC /* Initialise an ecc_key that names an ELS slot instead of holding a private * key, and bind it to the port's devId and to P-256. For a key generation the diff --git a/zephyr/user_settings.h b/zephyr/user_settings.h index bed4a38b255..593ceb6aa40 100644 --- a/zephyr/user_settings.h +++ b/zephyr/user_settings.h @@ -286,6 +286,9 @@ extern "C" { #ifndef WOLF_CRYPTO_CB #define WOLF_CRYPTO_CB #endif + #ifndef WOLF_CRYPTO_CB_KEYSTORE + #define WOLF_CRYPTO_CB_KEYSTORE + #endif #endif /* ------------------------------------------------------------------------- */ From 1844c9c68972f89891385783c2375a3b945247a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:50 +0200 Subject: [PATCH 13/16] nxp: derive NXP_DIE_KEK_SK instead of requiring a provisioned one Importing an RFC 3394 key blob needs a wrapping key the application had to arrange for itself, which is awkward: the blob is wrapped at provisioning time under NXP_DIE_KEK_SK, and the boot ROM deletes its own copy when SB file loading finishes, so nothing is resident by the time the application runs. It does not have to be. NXP_DIE_KEK_SK is a CKDF child of the die master key and NXP publishes both the parent and the twelve byte derivation constant, so wc_ElsPkc_DeriveDieKek() derives a fresh copy into a free slot pair and hands back a reference. Import against that reference and delete the slot afterwards. What is not verified is that this is the same key the provisioning tooling used, which would need a blob programmed into OTP on a provisioned part. The header says so rather than implying more than was tested. --- wolfcrypt/src/port/nxp/els_pkc_port.c | 71 +++++++++++++++++++++++ wolfssl/wolfcrypt/port/nxp/els_pkc_port.h | 4 ++ 2 files changed, 75 insertions(+) diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index a9c000151cc..c71e68a56db 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -2913,6 +2913,14 @@ static int ElsPkcEccVerify(const byte* sigDer, word32 sigLen, #define ELS_KS_KEY_128_SZ 16u #define ELS_KS_KEY_256_SZ 32u +/* The die master key every ROM-generated key descends from. */ +#define ELS_DIE_MK_SLOT 0 + +/* SP800-108 derivation data for NXP_DIE_KEK_SK, published by NXP. */ +static const byte elsDieKekDeriv[ELS_KS_DERIV_SZ] = { + 0x94, 0xbe, 0x03, 0xac, 0x8b, 0x59, 0x32, 0x45, 0x11, 0x7f, 0xf8, 0x3f +}; + /* Map a stored key's ELS properties onto the facility's vocabulary. */ static word32 ElsKsTypeFromProp(const mcuxClEls_KeyProp_t* prop) { @@ -3398,6 +3406,69 @@ static int ElsKsDerive(wc_CryptoInfo* info) return ret; } +int wc_ElsPkc_DeriveDieKek(wc_ElsPkc_KeyRef* ref) +{ + mcuxClEls_KeyProp_t prop; + int ret; + + if (ref == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + /* 256 bits, so two adjacent slots. ReserveSlot counts by class, and only + * the ECC classes are two-slot, so ask for the pair directly. */ + ret = wc_ElsPkc_ReserveSlot(WC_ELSPKC_KEY_KWK, ref); + if (ret == 0 && ref->slot >= WC_ELSPKC_MAX_SLOT) { + ret = WC_NO_ERR_TRACE(MEMORY_E); + } + + if (ret == 0) { + ret = ElsLock(); + } + if (ret != 0) { + /* ReserveSlot has already filled ref, so scrub it rather than return + * a well-formed reference to a slot nothing was derived into. */ + XMEMSET(ref, 0, sizeof(*ref)); + return ret; + } + + /* The second slot of the pair has to be free, and usable: free is not the + * same as usable, which is why ReserveSlot tests the general purpose flag + * on the first. An ELS permission mistake is a tamper event, not an error + * return, so the pair is checked to the same standard. */ + ret = ElsKsProps((byte)(ref->slot + 1u), &prop); + if (ret == 0 && + ((prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE) || + (prop.word.value & + MCUXCLELS_KEYPROPERTY_VALUE_GENERAL_PURPOSE_SLOT) == 0)) { + ret = WC_NO_ERR_TRACE(MEMORY_E); + } + + if (ret == 0) { + /* Reproduce the ROM's own recipe. Nothing else is requested, because + * the engine refuses a derivation that asks for properties the parent + * cannot confer. */ + prop.word.value = 0u; + prop.bits.upprot_priv = MCUXCLELS_KEYPROPERTY_PRIVILEGED_FALSE; + prop.bits.upprot_sec = MCUXCLELS_KEYPROPERTY_SECURE_FALSE; + prop.bits.ksize = MCUXCLELS_KEYPROPERTY_KEY_SIZE_256; + prop.word.value |= MCUXCLELS_KEYPROPERTY_VALUE_KWK; + + ret = ElsKsDeriveRun(ELS_DIE_MK_SLOT, ref->slot, prop, + elsDieKekDeriv); + } + + ElsUnlock(); + + if (ret == 0) { + } + else { + XMEMSET(ref, 0, sizeof(*ref)); + } + + return ret; +} + static int ElsKsDelete(wc_CryptoInfo* info) { wc_ElsPkc_KeyRef key; diff --git a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h index 05db6937ac6..849fca4daa9 100644 --- a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h +++ b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h @@ -144,6 +144,10 @@ WOLFSSL_API int wc_ElsPkc_ParseKeyRef(const byte* in, word32 inSz, * marked taken until a key is written, so reserve and generate together. */ WOLFSSL_API int wc_ElsPkc_ReserveSlot(byte keyClass, wc_ElsPkc_KeyRef* ref); +/* Derive NXP_DIE_KEK_SK, the key encryption key the boot ROM uses to unwrap + * RFC 3394 blobs, into a free pair of slots; ref receives a KWK reference + * naming it. Delete it with wc_KeyStore_Delete() when done. */ +WOLFSSL_API int wc_ElsPkc_DeriveDieKek(wc_ElsPkc_KeyRef* ref); #endif #ifdef HAVE_ECC From c4d8dd34581e789d8de0cb0621387886ce0dad46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:55 +0200 Subject: [PATCH 14/16] nxp: build the EdgeLock port from autoconf The port was reachable only through the Zephyr module, on the assumption that NXP's els_pkc is a Zephyr artifact. It is not: els_pkc is NXP's CLNS middleware, shipped as an MCUXpresso SDK component that additionally carries a Zephyr module, so a project using the SDK directly should be able to build the port too. Add --with-els-pkc=DIR alongside --with-mcux-sdk=DIR for the SoC device headers its platform layer includes, and --with-els-pkc-platform=SOC defaulting to rw61x. The component include directories are globbed rather than enumerated: the list runs to roughly fifty entries and moves with each vendor release. The port states its prerequisites as #error in els_pkc_port.h, so promote WOLF_CRYPTO_CB_COPY and WOLF_CRYPTO_CB_FREE here the way --enable-rtl8735b does. A wrong or missing path is diagnosed at configure time, naming the option, rather than several hundred lines into the build with a missing mcuxClEls.h. The vendor library stays link-only, so the archive carries unresolved mcuxCl* references by design, as the SE050 port does. --- ChangeLog.md | 2 +- configure.ac | 125 ++++++++++++++++++++++++++++++- wolfcrypt/src/include.am | 4 + wolfcrypt/src/port/nxp/README.md | 2 + wolfssl/wolfcrypt/include.am | 4 + 5 files changed, 135 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 4efaf8a0d25..52a0db1b2b4 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -193,7 +193,7 @@ * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev -* Added an NXP EdgeLock (ELS + PKC) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG and ECDSA on P-256 from a key slot; the PKC coprocessor serves RSA, X25519 and ECDSA on every curve. Keys resident in the ELS key store are referenced rather than exported, through the new `WC_ALGO_TYPE_KEYSTORE` operations. Under Zephyr, turn it on with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. +* Added an NXP EdgeLock (ELS + PKC) crypto callback port for the RW612 and related parts, in `wolfcrypt/src/port/nxp/els_pkc_port.c` behind `WOLFSSL_ELS_PKC`. The ELS peripheral serves SHA-256/384/512, AES (ECB/CBC/CTR), AES-GCM, CMAC, the DRBG and ECDSA on P-256 from a key slot; the PKC coprocessor serves RSA, X25519 and ECDSA on every curve. Keys resident in the ELS key store are referenced rather than exported, through the new `WC_ALGO_TYPE_KEYSTORE` operations. Configure with `--with-els-pkc=PATH --with-mcux-sdk=PATH`, or under Zephyr with `CONFIG_WOLFSSL_ELS_PKC=y`. NXP's CLNS library is linked by the application and is neither built nor vendored here. ## Fixes diff --git a/configure.ac b/configure.ac index c1b4212bc24..5026b09c7ab 100644 --- a/configure.ac +++ b/configure.ac @@ -3830,6 +3830,127 @@ then fi +# NXP EdgeLock (ELS + PKC) crypto-callback port. +# +# els_pkc is NXP's CLNS middleware, delivered as an MCUXpresso SDK component +# (github.com/NXP/els_pkc) that additionally carries a Zephyr module. Under +# Zephyr that module hands the port its include path and these options are +# unused; they are the MCUXpresso SDK path, where the checkout is named here. +# +# The vendor library is link-only and is NOT built here. The application links +# CLNS - either its own build of els_pkc/src/comps, or the prebuilt +# els_pkc/static_library//libclns.a - exactly as the SE050 port does, so +# the archive produced here carries unresolved mcuxCl* references by design. +# +# Example: +# ./configure --host=arm-none-eabi --enable-cryptonly --enable-cryptocb \ +# --with-els-pkc=/path/to/els_pkc --with-mcux-sdk=/path/to/mcux-sdk-ng \ +# CFLAGS="-mcpu=cortex-m33+nodsp -mthumb" +ENABLED_ELS_PKC="no" +els_pkc_dir="" +mcux_sdk_dir="" +els_pkc_platform="rw61x" +els_pkc_opt_given="no" + +# Both of these are read by the --with-els-pkc block below, so they are taken +# first: AC_ARG_WITH runs its action where it appears in the script. +AC_ARG_WITH([mcux-sdk], + [AS_HELP_STRING([--with-mcux-sdk=PATH], + [PATH to an MCUXpresso SDK checkout (mcux-sdk-ng), for the SoC device headers the els_pkc platform layer includes])], + [ AS_CASE([$withval], + [no], [ mcux_sdk_dir="" ], + [yes], [ AC_MSG_ERROR([--with-mcux-sdk needs the path to an MCUXpresso SDK checkout, e.g. --with-mcux-sdk=/path/to/mcux-sdk-ng]) ], + [ mcux_sdk_dir=$withval ]) + els_pkc_opt_given=yes ]) + +AC_ARG_WITH([els-pkc-platform], + [AS_HELP_STRING([--with-els-pkc-platform=SOC], + [els_pkc platform layer to build against (default: rw61x)])], + [ AS_CASE([$withval], + [no|yes], [ AC_MSG_ERROR([--with-els-pkc-platform needs a platform name, e.g. --with-els-pkc-platform=rw61x]) ], + [ els_pkc_platform=$withval ]) + els_pkc_opt_given=yes ]) + +AC_ARG_WITH([els-pkc], + [AS_HELP_STRING([--with-els-pkc=PATH], + [Enable the NXP EdgeLock (ELS/PKC) crypto callback port, built against the els_pkc checkout at PATH])], + [ AS_CASE([$withval], + [no], [ ENABLED_ELS_PKC=no ], + [yes], [ AC_MSG_ERROR([--with-els-pkc needs the path to an els_pkc checkout, e.g. --with-els-pkc=/path/to/els_pkc]) ], + [ ENABLED_ELS_PKC=yes; els_pkc_dir=$withval ]) ]) + +if test "x$ENABLED_ELS_PKC" = "xyes" +then + # Diagnose a wrong path here, naming the option, rather than several + # hundred lines into the build with a missing mcuxClEls.h. + AC_MSG_CHECKING([for els_pkc in $els_pkc_dir]) + if test ! -f "$els_pkc_dir/src/comps/mcuxClEls/inc/mcuxClEls.h"; then + AC_MSG_RESULT([no]) + AC_MSG_ERROR([mcuxClEls.h not found under --with-els-pkc=$els_pkc_dir]) + fi + AC_MSG_RESULT([yes]) + + if test "x$mcux_sdk_dir" = "x"; then + AC_MSG_ERROR([--with-els-pkc also needs --with-mcux-sdk=PATH: the els_pkc platform layer includes the SoC device headers (fsl_common.h, fsl_device_registers.h) from an MCUXpresso SDK checkout]) + fi + + if test ! -d "$els_pkc_dir/src/platforms/$els_pkc_platform"; then + AC_MSG_ERROR([els_pkc has no platform layer "$els_pkc_platform"; select one with --with-els-pkc-platform=SOC]) + fi + + # Every component's public headers, plus the internal ones the components + # include among themselves. Globbed rather than enumerated: the list runs + # to roughly fifty directories and moves with the vendor release. + for els_pkc_inc in "$els_pkc_dir"/src/comps/*/inc \ + "$els_pkc_dir"/src/comps/*/inc/internal; do + if test -d "$els_pkc_inc"; then + CPPFLAGS="$CPPFLAGS -I$els_pkc_inc" + fi + done + + CPPFLAGS="$CPPFLAGS -I$els_pkc_dir/src/inc -I$els_pkc_dir/src/compiler" + CPPFLAGS="$CPPFLAGS -I$els_pkc_dir/src/platforms/$els_pkc_platform" + CPPFLAGS="$CPPFLAGS -I$els_pkc_dir/src/platforms/$els_pkc_platform/inc" + CPPFLAGS="$CPPFLAGS -I$els_pkc_dir/includes/platform/$els_pkc_platform" + + # els_pkc carries its own CMSIS, so no separate CMSIS checkout is needed. + # The directory is version-stamped (cmsis_cortex_m_5_5_1), hence the glob. + for els_pkc_inc in "$els_pkc_dir"/src/platforms/"$els_pkc_platform"/cmsis_*/inc; do + if test -d "$els_pkc_inc"; then + CPPFLAGS="$CPPFLAGS -I$els_pkc_inc" + fi + done + + # The SoC header layout is per-device and only the platforms this port has + # been brought up on can be named here. Anything else still configures - + # the paths and part number just have to arrive through CPPFLAGS/CFLAGS. + AS_CASE([$els_pkc_platform], + [rw61x], [ + CPPFLAGS="$CPPFLAGS -I$mcux_sdk_dir/devices/Wireless/RW/RW612" + CPPFLAGS="$CPPFLAGS -I$mcux_sdk_dir/devices/Wireless/RW/RW612/drivers" + CPPFLAGS="$CPPFLAGS -I$mcux_sdk_dir/devices/Wireless/RW/periph" + # fsl_device_registers.h picks the register map off the part + # number, and only the A2 parts appear in its selector. A different + # package overrides this through CPPFLAGS. + AM_CFLAGS="$AM_CFLAGS -DCPU_RW612ETA2I" + ], + [ AC_MSG_WARN([no known MCUXpresso device header layout for els_pkc platform "$els_pkc_platform"; pass the SoC include paths in CPPFLAGS and the part number in CFLAGS]) ]) + + CPPFLAGS="$CPPFLAGS -I$mcux_sdk_dir/drivers/common" + + # WOLF_CRYPTO_CB is promoted in the crypto callback aggregation further + # down, and it is what brings in the WOLF_PRIVATE_KEY_ID the key slot + # references need. The key store ops are promoted here rather than left to + # --enable-cryptocbutils=keystore, so that a plain --with-els-pkc does not + # silently build the port with its key store arm compiled out - both + # user_settings.h paths turn it on automatically. + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_ELS_PKC -DWOLF_CRYPTO_CB_KEYSTORE" +elif test "x$els_pkc_opt_given" = "xyes" +then + AC_MSG_WARN([--with-mcux-sdk and --with-els-pkc-platform do nothing without --with-els-pkc=PATH; the EdgeLock port is not being built]) +fi + + # NXP SE050 # Example: "./configure --with-se050=/home/pi/simw_top" ENABLED_SE050="no" @@ -11731,7 +11852,7 @@ then fi fi -if test "x$ENABLED_PKCS11" = "xyes" || test "x$ENABLED_WOLFTPM" = "xyes" || test "$ENABLED_CAAM" != "no" || test "x$ENABLED_RTL8735B" != "xno" || test "x$ENABLED_VAULTIC" = "xyes" +if test "x$ENABLED_PKCS11" = "xyes" || test "x$ENABLED_WOLFTPM" = "xyes" || test "$ENABLED_CAAM" != "no" || test "x$ENABLED_RTL8735B" != "xno" || test "x$ENABLED_VAULTIC" = "xyes" || test "x$ENABLED_ELS_PKC" = "xyes" then ENABLED_CRYPTOCB=yes fi @@ -13644,6 +13765,7 @@ AM_CONDITIONAL([BUILD_IOTSAFE],[test "x$ENABLED_IOTSAFE" = "xyes"]) AM_CONDITIONAL([BUILD_IOTSAFE_HWRNG],[test "x$ENABLED_IOTSAFE_HWRNG" = "xyes"]) AM_CONDITIONAL([BUILD_VAULTIC],[test "x$ENABLED_VAULTIC" = "xyes"]) AM_CONDITIONAL([BUILD_SE050],[test "x$ENABLED_SE050" = "xyes"]) +AM_CONDITIONAL([BUILD_ELS_PKC],[test "x$ENABLED_ELS_PKC" = "xyes"]) AM_CONDITIONAL([BUILD_STSAFE],[test "x$ENABLED_STSAFE" != "xno"]) AM_CONDITIONAL([BUILD_RTL8735B],[test "x$ENABLED_RTL8735B" != "xno"]) AM_CONDITIONAL([BUILD_TROPIC01],[test "x$ENABLED_TROPIC01" = "xyes"]) @@ -14261,6 +14383,7 @@ echo " * IoT-Safe HWRNG: $ENABLED_IOTSAFE_HWRNG" echo " * NXP SE050: $ENABLED_SE050" echo " * STMicro STSAFE: $ENABLED_STSAFE" echo " * RealTek RTL8735B HUK: $ENABLED_RTL8735B" +echo " * NXP EdgeLock ELS/PKC: $ENABLED_ELS_PKC" 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 0586b41d998..c28d65599d7 100644 --- a/wolfcrypt/src/include.am +++ b/wolfcrypt/src/include.am @@ -277,6 +277,10 @@ if BUILD_SE050 src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/nxp/se050_port.c endif +if BUILD_ELS_PKC +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/nxp/els_pkc_port.c +endif + if BUILD_STSAFE src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/st/stsafe.c endif diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 2d30c647f61..45cd4ab57e1 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -166,6 +166,8 @@ into a wolfSSL tree. `CONFIG_MCUX_ELS_PKC=y` for the vendor headers. When a settings file is supplied through `CONFIG_WOLFSSL_SETTINGS_FILE`, the feature Kconfigs do not apply and `WOLFSSL_ELS_PKC` has to be defined in that file instead. +- **MCUXpresso SDK / autoconf.** `--with-els-pkc=PATH` and + `--with-mcux-sdk=PATH`. Everything in the port is gated on `WOLFSSL_ELS_PKC`. Without that macro the file compiles to an empty translation unit and reaches no vendor header, diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index 1ea69657e49..a5d094a0b3b 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -256,6 +256,10 @@ if BUILD_SE050 nobase_include_HEADERS+= wolfssl/wolfcrypt/port/nxp/se050_port.h endif +if BUILD_ELS_PKC +nobase_include_HEADERS+= wolfssl/wolfcrypt/port/nxp/els_pkc_port.h +endif + if BUILD_TROPIC01 nobase_include_HEADERS+= wolfssl/wolfcrypt/port/tropicsquare/tropic01.h endif From 255dae07db0acc45f64d149beaeeae4cc30c205e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:16:59 +0200 Subject: [PATCH 15/16] nxp: document the MCUXpresso build for the EdgeLock port An IDE/MCUEXPRESSO/RW612 directory with a known-good user_settings.h and the steps to build the port against the SDK rather than Zephyr, plus the port README's pointer to it. The README itself grew alongside the offloads it documents. --- IDE/MCUEXPRESSO/RW612/README.md | 104 ++++++++++++++++++++ IDE/MCUEXPRESSO/RW612/user_settings.h | 134 ++++++++++++++++++++++++++ wolfcrypt/src/port/nxp/README.md | 5 +- 3 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 IDE/MCUEXPRESSO/RW612/README.md create mode 100644 IDE/MCUEXPRESSO/RW612/user_settings.h diff --git a/IDE/MCUEXPRESSO/RW612/README.md b/IDE/MCUEXPRESSO/RW612/README.md new file mode 100644 index 00000000000..03f02bf737e --- /dev/null +++ b/IDE/MCUEXPRESSO/RW612/README.md @@ -0,0 +1,104 @@ +# wolfSSL on NXP RW612 with EdgeLock (ELS + PKC) + +This directory configures wolfSSL for the NXP RW612 with the EdgeLock crypto callback port, `wolfcrypt/src/port/nxp/els_pkc_port.c`, built from an MCUXpresso SDK project. It offloads SHA-256, AES, AES-GCM, CMAC and the DRBG to the ELS peripheral, and RSA, X25519 and ECDSA to the PKC coprocessor. + +Zephyr users do not need any of this: the wolfSSL Zephyr module supplies the same port through `CONFIG_WOLFSSL_ELS_PKC=y`, and the `els_pkc` Zephyr module supplies the vendor headers. This is the path for a project that uses the MCUXpresso SDK directly. + +## What els_pkc is + +`els_pkc` is NXP's CLNS (Crypto Library NXP Secure) middleware. It ships as an MCUXpresso SDK component under `components/els_pkc/` and is also published at . The Zephyr module it carries is an add-on; the SDK component is the primary form. + +wolfSSL never builds or vendors CLNS. The port calls into it and the application links it, exactly as the SE050 port does. Take either route: + +- build `els_pkc/src/comps` as part of your project, or +- link the prebuilt `els_pkc/static_library/rw61x/libclns.a`. + +**Licensing.** els_pkc is distributed under NXP's proprietary `LA_OPT_Online Code Hosting` license, not an open source license. Clause 3.7 forbids subjecting it to a license that would require source disclosure. Fetching it at build time is fine and is what both this setup and the Zephyr module do; copying it into a wolfSSL tree is not. + +## MCUXpresso IDE setup + +1. Create a project for your RW612 board (for example `frdm_rw612`). +2. In the SDK component selector, add **els_pkc**. That brings in `src/comps`, the `rw61x` platform layer, and the bundled CMSIS. +3. Add the wolfSSL sources to the project. `wolfcrypt/src/port/nxp/els_pkc_port.c` is compiled like every other wolfCrypt port and gates itself on `WOLFSSL_ELS_PKC`, so it costs nothing when the port is off. +4. Copy `user_settings.h` from this directory into your project and define `WOLFSSL_USER_SETTINGS` for the build. +5. Add these include paths, all inside the els_pkc component: `src/inc`, `src/compiler`, `src/comps/*/inc`, `src/comps/*/inc/internal`, `src/platforms/rw61x`, `src/platforms/rw61x/inc`, `src/platforms/rw61x/cmsis_cortex_m_5_5_1/inc`, `includes/platform/rw61x`. + +### Compiler flags that matter + +| Flag | Why | +| --- | --- | +| `-mcpu=cortex-m33+nodsp` | Without `+nodsp` the vendor CMSIS `core_cm33.h` aborts the build with "Compiler generates DSP (SIMD) instructions for a device without DSP extensions". `-D__DSP_PRESENT=1` does **not** work around it. | +| `-DCPU_RW612ETA2I` | `fsl_device_registers.h` selects the register map from the part number. Use the macro matching your part; only the A2 parts appear in its selector. | +| `-mcmse` | Matches how NXP builds CLNS. | +| `-DNO_THREAD_LS` | Required on bare metal. See below. | +| `-DWOLFSSL_NO_GETPID` | Required with newlib. See below. | + +### wolfCrypt_Init() returning -1000 + +`wolfCrypt_Init()` guards against recursive entry with a `__thread` variable whenever the toolchain advertises thread-local storage. On bare metal nothing sets the thread pointer, so that read returns garbage, the guard fires, and the call returns `DEADLOCK_AVERTED_E` (-1000) before initialising anything. Define `NO_THREAD_LS` unless your runtime really does set up TLS. + +### random.c and an implicit declaration of getpid() + +`--specs=nosys.specs` gives the linker a stub for every syscall, which is what +lets autoconf's link probes succeed without a board support package. It also +makes the probe for `getpid()` succeed, so `HAVE_GETPID` ends up defined even +though newlib's headers never declare the function, and `random.c` then fails +to compile on an implicit declaration. Define `WOLFSSL_NO_GETPID`; a bare metal +target has no processes to identify. + +Toolchains shipping picolibc rather than newlib, such as the Zephyr SDK's +`arm-zephyr-eabi`, declare `getpid()` and do not need this. + +## Building with autoconf instead + +The same result from a source tarball or checkout. Note that no CI job builds this port today - it needs the vendor library and the SoC headers, neither of which the CI images carry - so a green run says nothing about it: + +```sh +./autogen.sh +./configure --host=arm-none-eabi --disable-shared --enable-static \ + --enable-cryptonly --enable-cryptocb \ + --enable-cryptocbutils=keystore \ + --enable-ecc --enable-aesgcm --enable-cmac --enable-curve25519 --enable-rsa \ + --with-els-pkc=/path/to/els_pkc \ + --with-mcux-sdk=/path/to/mcux-sdk-ng \ + --disable-filesystem --enable-singlethreaded \ + CFLAGS="-mcpu=cortex-m33+nodsp -mthumb -Os --specs=nosys.specs" \ + CPPFLAGS="-DWOLFSSL_NO_SOCK -DWOLFSSL_USER_IO -DNO_WRITEV -DNO_DEV_RANDOM \ + -DNO_THREAD_LS -DWOLFSSL_NO_GETPID" +make +``` + +`--with-mcux-sdk` wants a tree holding `devices/Wireless/RW/RW612` and `drivers/common`: your MCUXpresso SDK install, or a checkout of NXP's `mcux-sdk-ng`. The els_pkc platform layer includes `fsl_common.h`, `fsl_device_registers.h` and `fsl_ocotp.h` from it. + +The resulting `libwolfssl.a` carries unresolved `mcuxCl*` references. That is correct: the application links CLNS. + +To confirm the port really made it into the archive, check for its symbols rather than for the object file. The port compiles to an empty translation unit when `WOLFSSL_ELS_PKC` is missing, so a build can succeed with the port silently absent: + +```sh +arm-none-eabi-nm src/.libs/libwolfssl.a | grep wc_ElsPkc +``` + +## Using it + +`wolfCrypt_Init()` brings the port up and registers its crypto callback, so there is nothing to call by hand, and `wc_CryptoCb_DefaultDevID()` answers `WOLFSSL_ELS_PKC_DEVID`, so a context created without an explicit device is offloaded too: + +```c +#include + +wolfCrypt_Init(); + +wc_Sha256 sha; +wc_InitSha256(&sha); /* offloaded */ +wc_InitSha256_ex(&sha, NULL, WOLFSSL_ELS_PKC_DEVID); /* the same, explicitly */ +wc_InitSha256_ex(&sha, NULL, INVALID_DEVID); /* kept in software */ +``` + +Define `WC_NO_DEFAULT_DEVID` to switch the automatic routing off and require every context to name the device. Note it only reaches code that asks `wc_CryptoCb_DefaultDevID()` for a device: `wc_AesInit()` takes a `devId` argument directly, so pass `WOLFSSL_ELS_PKC_DEVID` there yourself. + +Anything the hardware does not serve is *declined* with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size is a performance question, never a failure. + +A hardware *failure* is treated differently on purpose: the port reports the error rather than quietly finishing in software, because a caller that ignores the return would otherwise receive a confident bad result. Declining and failing are not the same thing. + +`wolfCrypt_Cleanup()` unregisters the callback and releases the lock; `wc_ElsPkc_Init()` re-registers it if you need the port back afterwards. + +Keys resident in the ELS key store are referenced rather than exported: attach a reference to a key with `wc_ElsPkc_EccUseSlot()`, `wc_ElsPkc_AesUseSlot()` or `wc_ElsPkc_CmacUseSlot()`. Those are the entry point - `wc_ElsPkc_MakeKeyRef()` is the primitive underneath them, and the ECC helper also pins the curve, without which the offload declines. See the port header for the key store operations and the reference format. diff --git a/IDE/MCUEXPRESSO/RW612/user_settings.h b/IDE/MCUEXPRESSO/RW612/user_settings.h new file mode 100644 index 00000000000..baa98069d6d --- /dev/null +++ b/IDE/MCUEXPRESSO/RW612/user_settings.h @@ -0,0 +1,134 @@ +/* user_settings.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* wolfSSL configuration for the NXP RW612 (frdm_rw612) with the EdgeLock + * ELS + PKC crypto callback port, built from an MCUXpresso SDK project. See + * README.md for the SDK components and compiler flags. Under Zephyr none of + * this applies: the module's Kconfig generates the equivalent settings. */ + +#ifndef USER_SETTINGS_H +#define USER_SETTINGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* --------------------------------------------------------------------------- + * Platform + * ------------------------------------------------------------------------ */ + +#define WOLFSSL_GENERAL_ALIGNMENT 4 +#define SINGLE_THREADED + +/* wolfCrypt_Init() uses a __thread recursion guard when the toolchain reports + * thread-local storage. Bare-metal startup never sets the thread pointer, so + * the guard reads garbage and the call fails with DEADLOCK_AVERTED_E (-1000) + * before doing any work. */ +#define NO_THREAD_LS + +/* newlib does not declare getpid(), but --specs=nosys.specs makes configure's + * link probe for it succeed, so random.c would compile against an implicit + * declaration. There are no processes here to identify. */ +#define WOLFSSL_NO_GETPID +#define NO_FILESYSTEM +#define NO_WRITEV +#define WOLFSSL_NO_SOCK +#define WOLFSSL_USER_IO +#define NO_DEV_RANDOM /* entropy comes from the ELS DRBG, below */ +#define WOLFCRYPT_ONLY /* drop for TLS; then supply IO and time */ + +/* No RTC by default on this board. Supply a real time source and remove this + * if certificate validity has to be checked. */ +#define NO_ASN_TIME + +/* --------------------------------------------------------------------------- + * EdgeLock (ELS + PKC) offload + * ------------------------------------------------------------------------ */ + +/* The single switch for the port. */ +#define WOLFSSL_ELS_PKC + +/* The port is a crypto callback; KEYSTORE adds the ELS key store operations. + * + * FIND matters more than it looks: with NO_DEV_RANDOM below there is no + * software seed, and wc_GenerateSeed() dispatches to a device only when the + * RNG carries a devId - which wc_InitRng() does not set. Without FIND, + * wc_InitRng() fails with WC_HW_E and nothing falls back. */ +#define WOLF_CRYPTO_CB +#define WOLF_CRYPTO_CB_FIND +#define WOLF_CRYPTO_CB_KEYSTORE + +/* Seed wolfCrypt's DRBG from the ELS hardware DRBG. */ +#define HAVE_HASHDRBG + +/* --------------------------------------------------------------------------- + * Algorithms + * ------------------------------------------------------------------------ */ + +/* Anything not listed here falls back to software through the normal + * CRYPTOCB_UNAVAILABLE path. */ + +#define WOLFSSL_SHA256 +#define WOLFSSL_SHA384 +#define WOLFSSL_SHA512 + +#define HAVE_AESGCM +#define WOLFSSL_AES_DIRECT +#define HAVE_AES_ECB +#define WOLFSSL_AES_COUNTER +#define WOLFSSL_CMAC + +#define HAVE_ECC +#define ECC_USER_CURVES +#define HAVE_ECC256 +#define HAVE_ECC384 +#define HAVE_CURVE25519 +/* Ed25519 is deliberately absent: the PKC does not serve EdDSA, so it would be + * pure software here and imply an offload that does not exist. */ + +/* SP math with the curves this build uses. SP_MATH is per-curve: a curve the + * SP backend was not built for fails at run time with WC_KEY_SIZE_E rather + * than at compile time, so keep this in step with the curve list above. */ +#define WOLFSSL_SP_MATH_ALL +#define WOLFSSL_HAVE_SP_ECC +#define WOLFSSL_SP_384 +#define WOLFSSL_HAVE_SP_RSA + +#define WC_RSA_BLINDING +#define WOLFSSL_KEY_GEN + +/* --------------------------------------------------------------------------- + * Size + * ------------------------------------------------------------------------ */ + +#define NO_DSA +#define NO_RC4 +#define NO_MD4 +#define NO_DES3 +#define NO_PSK +#define NO_OLD_TLS +#define WOLFSSL_SMALL_STACK + +#ifdef __cplusplus +} +#endif + +#endif /* USER_SETTINGS_H */ diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 45cd4ab57e1..c66c2abbcd4 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -167,7 +167,10 @@ into a wolfSSL tree. supplied through `CONFIG_WOLFSSL_SETTINGS_FILE`, the feature Kconfigs do not apply and `WOLFSSL_ELS_PKC` has to be defined in that file instead. - **MCUXpresso SDK / autoconf.** `--with-els-pkc=PATH` and - `--with-mcux-sdk=PATH`. + `--with-mcux-sdk=PATH`. See + [IDE/MCUEXPRESSO/RW612](../../../IDE/MCUEXPRESSO/RW612) for the worked + setup, the compiler flags that matter, a known-good `user_settings.h`, and + the run-time API. Everything in the port is gated on `WOLFSSL_ELS_PKC`. Without that macro the file compiles to an empty translation unit and reaches no vendor header, From 9e3c5c2918c9cb26739b2406a4283df04574a929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 9 Sep 2026 20:17:04 +0200 Subject: [PATCH 16/16] nxp: run the Zephyr test and benchmark against the EdgeLock Nothing committed exercised the port through wolfCrypt's own suites. Give both samples an frdm_rw612 board overlay that turns the offload on; settings.h already maps WC_USE_DEVID at WOLFSSL_ELS_PKC_DEVID, which is what the unmodified test and benchmark read, and defining it also defines BENCH_DEVID, so the benchmark measures every algorithm twice and labels the rows HW and SW. Both suites pass on frdm_rw612. --- zephyr/samples/wolfssl_benchmark/sample.yaml | 26 ++++++++++++++++++ zephyr/samples/wolfssl_test/sample.yaml | 28 ++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/zephyr/samples/wolfssl_benchmark/sample.yaml b/zephyr/samples/wolfssl_benchmark/sample.yaml index 4c5d1fcea80..2009c6c1f36 100644 --- a/zephyr/samples/wolfssl_benchmark/sample.yaml +++ b/zephyr/samples/wolfssl_benchmark/sample.yaml @@ -13,3 +13,29 @@ tests: platform_allow: qemu_x86 integration_platforms: - qemu_x86 + sample.crypto.wolfssl_benchmark_els_pkc: + # The EdgeLock offload also defines BENCH_DEVID, so every algorithm is + # measured in software and on the hardware in the same run. Carried here + # rather than in boards/frdm_rw612.conf, which Zephyr would merge into + # every other build of this sample for the board, CI's included. + timeout: 1200 + platform_allow: frdm_rw612 + extra_configs: + - CONFIG_WOLFSSL_ELS_PKC=y + # wc_port.c calls clock_gettime(). POSIX_TIMERS is gated behind + # POSIX_SYSTEM_INTERFACES and silently does nothing without it. + - CONFIG_POSIX_SYSTEM_INTERFACES=y + - CONFIG_POSIX_TIMERS=y + # The board has a real entropy source, so drop prj.conf's placeholder. + - CONFIG_TEST_RANDOM_GENERATOR=n + - CONFIG_ENTROPY_GENERATOR=y + # The curves the PKC offloads beyond the module's default P-256. + - CONFIG_WOLFSSL_ECC_384=y + - CONFIG_WOLFSSL_ECC_521=y + - CONFIG_WOLFSSL_CURVE25519=y + # The arena carries the HAVE_STACK_SIZE benchmark thread stack on top of + # the benchmark's own allocations. + - CONFIG_MAIN_STACK_SIZE=32768 + - CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=131072 + integration_platforms: + - frdm_rw612 diff --git a/zephyr/samples/wolfssl_test/sample.yaml b/zephyr/samples/wolfssl_test/sample.yaml index 64119403d74..7ee43f06b6c 100644 --- a/zephyr/samples/wolfssl_test/sample.yaml +++ b/zephyr/samples/wolfssl_test/sample.yaml @@ -84,3 +84,31 @@ tests: - CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=65536 integration_platforms: - mps2/an521/cpu0 + sample.crypto.wolfssl_test_els_pkc: + # The whole suite against the EdgeLock offload. No software platform can + # stand in for it. Carried here rather than in boards/frdm_rw612.conf, + # which Zephyr would merge into every other build of this sample for the + # board, this scenario's and CI's alike. + timeout: 600 + platform_allow: frdm_rw612 + extra_configs: + # Registers the crypto callback and selects MCUX_ELS_PKC for the vendor + # headers; settings.h then maps WC_USE_DEVID at it, so every context the + # suite creates reaches the hardware. + - CONFIG_WOLFSSL_ELS_PKC=y + # wc_port.c calls clock_gettime(). POSIX_TIMERS is gated behind + # POSIX_SYSTEM_INTERFACES and silently does nothing without it. + - CONFIG_POSIX_SYSTEM_INTERFACES=y + - CONFIG_POSIX_TIMERS=y + # The board has a real entropy source, so drop prj.conf's placeholder. + - CONFIG_TEST_RANDOM_GENERATOR=n + - CONFIG_ENTROPY_GENERATOR=y + # The curves the PKC offloads beyond the module's default P-256. + - CONFIG_WOLFSSL_ECC_384=y + - CONFIG_WOLFSSL_ECC_521=y + - CONFIG_WOLFSSL_CURVE25519=y + # P-521 in software is the stack peak, PKCS12 the heap peak. + - CONFIG_MAIN_STACK_SIZE=65536 + - CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=65536 + integration_platforms: + - frdm_rw612