1
0
mirror of https://github.com/wolfSSL/wolfssl.git synced 2026-10-24 04:09:07 +03:00

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/<soc>, where mcux_els.h lives.
This commit is contained in:
Tobias Frauenschläger
2026-09-09 20:16:03 +02:00
parent 06d4408d87
commit 536421bb92
12 changed files with 1005 additions and 6 deletions

View File

@@ -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

View File

@@ -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 \

View File

@@ -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
<https://github.com/NXP/els_pkc>. 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

View File

@@ -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 <wolfssl/wolfcrypt/libwolfssl_sources.h>
#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 <wolfssl/wolfcrypt/port/nxp/els_pkc_port.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/logging.h>
#include <mcuxClEls.h>
#include <mcuxCsslFlowProtection.h>
/* per-SoC bring-up helper; els_pkc provides one for each supported platform */
#include <mcux_els.h>
#ifdef WOLFSSL_ZEPHYR
#include <zephyr/kernel.h>
#include <zephyr/irq.h>
#endif
#ifdef NO_INLINE
#include <wolfssl/wolfcrypt/misc.h>
#else
#define WOLFSSL_MISC_INCLUDED
#include <wolfcrypt/src/misc.c>
#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 */

View File

@@ -235,6 +235,10 @@ Threading/Mutex options:
#include <wolfssl/wolfcrypt/port/nxp/se050_port.h>
#endif
#ifdef WOLFSSL_ELS_PKC
#include <wolfssl/wolfcrypt/port/nxp/els_pkc_port.h>
#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

View File

@@ -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 \

View File

@@ -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 <wolfssl/wolfcrypt/settings.h>
#ifdef WOLFSSL_ELS_PKC
#ifndef WOLF_CRYPTO_CB
#error "WOLFSSL_ELS_PKC requires WOLF_CRYPTO_CB"
#endif
#include <wolfssl/wolfcrypt/types.h>
#include <wolfssl/wolfcrypt/cryptocb.h>
#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 */

View File

@@ -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

View File

@@ -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(
$<TARGET_PROPERTY:McuxElsPkc,INTERFACE_INCLUDE_DIRECTORIES>
)
endif()
if(CONFIG_WOLFCRYPT_ASM)
if(CONFIG_WOLFCRYPT_ARM_SYMMETRIC_ASM)

View File

@@ -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/<soc>, 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

View File

@@ -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 */
/* ------------------------------------------------------------------------- */

View File

@@ -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 <errno.h>
#include <zephyr/init.h>
#include <zephyr/logging/log.h>
#include <wolfssl/wolfcrypt/settings.h>
#ifdef WOLFCRYPT_ONLY
#include <wolfssl/wolfcrypt/wc_port.h>
#else
#include <wolfssl/ssl.h>
#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 */