From 536421bb92b2093442045b0e5f20d6fb2499d326 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] 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. --- .wolfssl_known_macro_extras | 4 + wolfcrypt/src/include.am | 1 + wolfcrypt/src/port/nxp/README.md | 83 +++ wolfcrypt/src/port/nxp/els_pkc_port.c | 716 ++++++++++++++++++++++ wolfcrypt/src/wc_port.c | 19 + wolfssl/wolfcrypt/include.am | 1 + wolfssl/wolfcrypt/port/nxp/els_pkc_port.h | 63 ++ wolfssl/wolfcrypt/settings.h | 17 + zephyr/CMakeLists.txt | 8 + zephyr/Kconfig | 39 ++ zephyr/user_settings.h | 8 + zephyr/zephyr_init.c | 52 +- 12 files changed, 1005 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/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index ae4e3d1697..7a0e8d365f 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -155,6 +155,7 @@ CONFIG_KPROBES CONFIG_MAIN_TASK_STACK_SIZE CONFIG_MBEDTLS_CERTIFICATE_BUNDLE CONFIG_MBEDTLS_PSA_CRYPTO_C +CONFIG_MCUX_ELS_PKC CONFIG_MIPS CONFIG_MODULE_SIG CONFIG_MODULE_SIG_SHA1 @@ -219,6 +220,7 @@ CONFIG_WOLFSSL_ECC_ALLOW_ZERO_HASH CONFIG_WOLFSSL_ECC_BRAINPOOL CONFIG_WOLFSSL_ED25519 CONFIG_WOLFSSL_ED448 +CONFIG_WOLFSSL_ELS_PKC CONFIG_WOLFSSL_ENABLE_KYBER CONFIG_WOLFSSL_EXAMPLE_NAME_ESP32_SSH_SERVER CONFIG_WOLFSSL_EXAMPLE_NAME_ESP8266_SSH_SERVER @@ -258,6 +260,7 @@ CONFIG_WOLFSSL_SESSION_TICKET CONFIG_WOLFSSL_SETTINGS_FILE CONFIG_WOLFSSL_SINGLE_THREADED CONFIG_WOLFSSL_SNI +CONFIG_WOLFSSL_SYS_INIT CONFIG_WOLFSSL_TARGET_HOST CONFIG_WOLFSSL_TARGET_PORT CONFIG_WOLFSSL_TLS_VERSION_1_2 @@ -982,6 +985,7 @@ WOLFSSL_ECDSA_SET_K_ONE_LOOP WOLFSSL_EC_POINT_CMP_JACOBIAN WOLFSSL_ED448_NO_LARGE_CODE WOLFSSL_EDDSA_CHECK_PRIV_ON_SIGN +WOLFSSL_ELS_PKC_ALLOW_CANCEL WOLFSSL_ESPWROOM32 WOLFSSL_EVP_PRINT WOLFSSL_EXPORT_INT diff --git a/wolfcrypt/src/include.am b/wolfcrypt/src/include.am index 2d9f51d290..381c36248b 100644 --- a/wolfcrypt/src/include.am +++ b/wolfcrypt/src/include.am @@ -93,6 +93,7 @@ EXTRA_DIST += wolfcrypt/src/port/ti/ti-aes.c \ wolfcrypt/src/port/nxp/se050_port.c \ wolfcrypt/src/port/nxp/ele_rng.c \ wolfcrypt/src/port/nxp/ele_cryptocb.c \ + wolfcrypt/src/port/nxp/els_pkc_port.c \ wolfcrypt/src/port/nxp/README.md \ wolfcrypt/src/port/nxp/sec_qoriq.c \ wolfcrypt/src/port/nxp/sec_qoriq_cb.c \ diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index c4fb2dda8d..4206a6e4ef 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -625,6 +625,89 @@ kernel carrying NXP's downstream `ele_mu` driver; the upstream `fsl-se` driver rejects userspace SAB writes with `EINVAL`. The enclave exposes no post-quantum algorithms, so ML-KEM and ML-DSA stay on the CPU. +## 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 before the interrupt is armed. Crypto calls +from an ISR work only in a single-threaded build, since the port's mutex +cannot be taken there otherwise. 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 0000000000..b4d38adcb5 --- /dev/null +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -0,0 +1,716 @@ +/* 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 subsystem. ELS has + * global busy state, so every command and its wait run under one lock. */ + +#include + +#ifdef WOLFSSL_ELS_PKC + +/* Name the missing Kconfig option rather than fail on 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 + +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; + +#ifdef WOLFSSL_ZEPHYR +static void ElsDropStaleDone(void); +#else + #define ElsDropStaleDone() WC_DO_NOTHING +#endif + +static int ElsLock(void) +{ + /* The mutex exists before the peripheral is enabled, so test elsReady + * too. */ + if (!elsLockInit || !elsReady) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + if (wc_LockMutex(&elsLock) != 0) { + return WC_NO_ERR_TRACE(BAD_MUTEX_E); + } + /* Cleanup may have run while this caller waited for the mutex. */ + if (!elsReady) { + (void)wc_UnLockMutex(&elsLock); + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + ElsDropStaleDone(); + return 0; +} + +static void ElsUnlock(void) +{ + if (elsLockInit) { + (void)wc_UnLockMutex(&elsLock); + } +} + +/* mcuxClEls_WaitForOperation() spins with no timeout, so sleep on the + * completion interrupt where a kernel is available. */ + +#ifndef WOLFSSL_ELS_PKC_TIMEOUT_MS + #define WOLFSSL_ELS_PKC_TIMEOUT_MS 1000 +#endif + +/* Spin this long before blocking, since a short operation finishes sooner + * than a thread switch. 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; + mcuxClEls_InterruptOptionRst_t rst; + + 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); + + /* The enable at bring-up was polled, so its completion is still latched. */ + rst.word.value = 0u; + rst.bits.elsint = MCUXCLELS_ELS_RESET_CLEAR; + (void)mcuxClEls_ResetIntFlags(rst); + + 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; +} + +/* Sleeping needs the armed interrupt and a thread to put 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; +} + +/* A completion nobody waited for, from bring-up or a polled wait, must not end + * the next command's wait early. Caller holds the lock. */ +static void ElsDropStaleDone(void) +{ + if (elsIrqReady) { + k_sem_reset(&elsDone); + } +} + +/* Under the lock, so no caller is left asleep on a disabled interrupt. */ +static void ElsIrqDisarm(void) +{ + if (elsIrqReady) { + irq_disable(ELS_IRQn); + elsIrqReady = 0; + } +} + +#else + #define ElsIrqDisarm() WC_DO_NOTHING +#endif /* WOLFSSL_ZEPHYR */ + +#if defined(WOLFSSL_ELS_PKC_ALLOW_CANCEL) && defined(WOLFSSL_ZEPHYR) +/* Abandon the pending operation. A tamper event unless the ITRC has been + * retargeted; see WOLFSSL_ELS_PKC_ALLOW_CANCEL. */ +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 + /* The reset is itself async; wait for it so the next caller does + * not get CANNOT_INTERRUPT. */ + 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; +} + +/* State lives in the caller's object and the lock is held per call, since + * TLS 1.3 keeps several transcript hashes open 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 + +/* State uses the object's own digest[] and buffer[], so a struct copy + * duplicates it and nothing needs freeing. */ + +/* 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 values, in the host-order form of digest[]. */ +#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) { + /* Only a pristine object: the engine starts from the standard IV, and + * SHA-512/224 and SHA-512/256 arrive as WC_HASH_TYPE_SHA512. */ + 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 absorbs it with the padding. */ + 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. */ +/* Drop the message state of an object that will produce no digest. */ +static void ElsHashAbandon(ElsHashObj* o) +{ + ForceZero(o->state, o->stateSz); + ForceZero(o->buf, o->blockSz); + *o->buffered = 0; + ElsHashTotalSet(o, 0); + *o->devCtx = (void*)((wc_ptr_t)*o->devCtx | ELS_HASH_FAILED); +} + +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) { + ElsHashAbandon(o); + return ret; + } + + if ((wc_ptr_t)*o->devCtx & ELS_HASH_FAILED) { + ElsUnlock(); + ElsHashAbandon(o); + 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); + } + + /* Re-init as software's Final does, which the callback return skips. */ + 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 */ + +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; +} + +int wc_ElsPkc_Init(void) +{ + int ret; + + /* Not safe against itself; call from one boot-time context. */ + 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 register: wolfCrypt_Cleanup() clears the device table. ALREADY_E + * is ours only when this port registered and has not been cleaned up. */ + ret = wc_CryptoCb_RegisterDevice(WOLFSSL_ELS_PKC_DEVID, + wc_ElsPkc_CryptoCb, NULL); + if (ret == WC_NO_ERR_TRACE(ALREADY_E) && elsRegistered) { + ret = 0; + } + if (ret != 0) { + 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, then take the lock so an in-flight operation finishes + * first. */ + if (elsRegistered) { + wc_CryptoCb_UnRegisterDevice(WOLFSSL_ELS_PKC_DEVID); + elsRegistered = 0; + } + + if (elsLockInit) { + /* Close the gate under the lock. The mutex outlives cleanup, since a + * blocked caller still has to unlock it. */ + if (wc_LockMutex(&elsLock) == 0) { + elsReady = 0; + ElsIrqDisarm(); + (void)wc_UnLockMutex(&elsLock); + } + else { + elsReady = 0; + ElsIrqDisarm(); + } + } + + return 0; +} + +#endif /* WOLFSSL_ELS_PKC */ diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index 01aab63443..b741909145 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -235,6 +235,10 @@ Threading/Mutex options: #include #endif +#ifdef WOLFSSL_ELS_PKC +#include +#endif + #ifdef WOLFSSL_SCE #include "hal_data.h" #endif @@ -1038,6 +1042,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 @@ -1276,6 +1288,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 20e8b75ccb..f592918429 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -109,6 +109,7 @@ noinst_HEADERS+= \ wolfssl/wolfcrypt/port/nxp/casper_port.h \ wolfssl/wolfcrypt/port/nxp/hashcrypt_port.h \ wolfssl/wolfcrypt/port/nxp/hwpuf_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 0000000000..bbfe58e70a --- /dev/null +++ b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h @@ -0,0 +1,63 @@ +/* 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 + +#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. */ + +#ifndef WOLFSSL_ELS_PKC_IRQ_PRIO + #define WOLFSSL_ELS_PKC_IRQ_PRIO 2 +#endif + +/* wolfCrypt_Init() already calls this; call it again only after + * wolfCrypt_Cleanup(). */ +WOLFSSL_API int wc_ElsPkc_Init(void); + +/* Unregister the callback and close the port; the mutex is kept for reuse. + * Finish every context the port holds state for first. */ +WOLFSSL_API int wc_ElsPkc_Cleanup(void); + +/* The callback, for registering under another devId. A context opts in by + * being initialised with that 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 f5aed0c992..16d561f8b2 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -534,6 +534,23 @@ #endif #endif +/* Map WC_USE_DEVID for the test and benchmark, which cannot include + * els_pkc_port.h. */ +#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 engine state in digest[] is unusable by the raw accessors. */ + #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 a0224fa181..ec2def9ac3 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -181,6 +181,14 @@ 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) + + # Not under TF-M, where els_pkc does not create McuxElsPkc. + if(CONFIG_MCUX_ELS_PKC AND NOT CONFIG_BUILD_WITH_TFM) + zephyr_library_include_directories( + $ + ) + endif() if(CONFIG_WOLFCRYPT_ASM) if(CONFIG_WOLFCRYPT_ARM_SYMMETRIC_ASM) diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 1e196591e0..0d8ae3b51c 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -129,6 +129,45 @@ config WOLFSSL_CRYPTO_CB (WOLF_PRIVATE_KEY_ID), which is what lets a TLS key live on that device instead of in the application's memory. +config WOLFSSL_SYS_INIT + bool "Initialize wolfSSL from a SYS_INIT hook" + depends on WOLFSSL_BUILTIN + 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 9699e5f229..bde7e3a875 100644 --- a/zephyr/user_settings.h +++ b/zephyr/user_settings.h @@ -291,6 +291,14 @@ extern "C" { #define WOLFSSL_AES_CFB #endif +/* No WOLF_CRYPTO_CB_FIND: the Zephyr entropy driver supplies the seed. */ +#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 033f436d41..77a8205806 100644 --- a/zephyr/zephyr_init.c +++ b/zephyr/zephyr_init.c @@ -19,9 +19,49 @@ * 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. */ +/* Only a port that brings up a peripheral needs a boot-time hook. */ + +#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 */