From a1b8d7f3fa9c2f5b6686ec172c1022237a466b92 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 1/9] 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 would need a key store operation, which this port does not provide. A generated key stays typed ECC_PUBLICKEY, as wc_ecc_import_unsigned() leaves it: the private half is in the slot, so export and software ECDH refuse the key instead of using the zero scalar its ecc_key holds. For the same reason key generation does not decline once a slot reference parses: a wrong class or curve is BAD_STATE_E. wolfCrypt already refuses a software fallback for a key with an id, but not under WOLF_CRYPTO_CB_KEYGEN_SW_FALLBACK, where a decline would let software fill the key while its id still names the slot. Generation also needs a free pair of general purpose slots. Slots 0 and 1 are retention and slot 2 is hardware-out, and the key store refuses them as targets for the same reason. 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. A P-256 key can also arrive as a key generation seed, which is how EdgeLock 2GO delivers one. The seed is the private scalar, and ELS will not sign with it until a deterministic key generation has turned it into a key pair in the same slots. wc_ElsPkc_EccKeyGenFromSeed() takes a reference to the seed, runs that generation, binds the ecc_key to the result and loads the public point, since that is again the only moment the hardware gives it out. It refuses a slot that does not hold a whole 256-bit seed before issuing anything. The engine sets the generated key's properties itself, and marks it wrap-exportable whatever the seed carried. The reference format reserves bytes 8 to 15 for a binding to the public point, but nothing computes or checks one, and ELS cannot hand out a stored key's public point to check it against. Until that exists, wc_ElsPkc_MakeKeyRef() and wc_ElsPkc_ParseKeyRef() refuse the BIND flag, so the field stays zero instead of carrying a value nobody verifies. wc_ecc_check_key() is what shows that a slot still holds the key a reference was made for. Every slot path requires the reference to name the first slot of a key over 128 bits, and every ECC key is one; the engine marks that slot with its base-slot property bit, so a reference to the second slot of a pair is refused before any command is issued. Verification range-checks r and s itself, since wolfCrypt does so only on its software path, and fails with the MP_ZERO_E or MP_VAL software would give. ELS answers an off-curve public key with a mismatched R, so that fails as it does in software. Verification declines a DER signature with a negative r or s, or with bytes after its SEQUENCE, both of which software rejects (the latter unless NO_STRICT_ECDSA_LEN), and reads the public point straight from the key, so it needs no key export support. If the public point cannot be loaded after an in-slot generation, the pair is deleted again so a retry finds it free, and a build without key import refuses a slot key's generation with NOT_COMPILED_IN. --- wolfcrypt/src/port/nxp/README.md | 24 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 659 ++++++++++++++++++++-- wolfssl/wolfcrypt/port/nxp/els_pkc_port.h | 29 +- 3 files changed, 663 insertions(+), 49 deletions(-) diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index c1353827ccf..38f7947853e 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -629,9 +629,10 @@ algorithms, so ML-KEM and ML-DSA stay on the CPU. `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. NXP rates that DRBG at 128 bits of security strength, so where it -seeds wolfCrypt's Hash-DRBG, that DRBG is capped at 128 bits too. +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. +NXP rates the ELS DRBG at 128 bits of security strength, which caps +wolfCrypt's Hash-DRBG at 128 bits where it supplies the seed. Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size costs @@ -645,10 +646,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 @@ -689,11 +691,19 @@ finished before the last `wolfCrypt_Cleanup()`: with the device gone, wolfCrypt carries on in software from the engine's state, and a hash returns a wrong digest. +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. A key in a slot has no software copy, so with one a partial block fails with `BAD_LENGTH_E` and such an IV with `BAD_FUNC_ARG`. +**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 4324ff8c54f..5f853a1dbdb 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -34,6 +34,9 @@ #include #include #include +#ifdef HAVE_ECC + #include +#endif #include #include @@ -63,7 +66,8 @@ /* Each of these checks a slot reference first, because a permission * violation resets the SoC. */ #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 @@ -302,8 +306,7 @@ static int ElsEnable(void) /* Public bound, so a caller can range-check a slot without the NXP headers. */ wc_static_assert((WC_ELSPKC_MAX_SLOT + 1) == MCUXCLELS_KEY_SLOTS); -#define ELS_REF_FLAGS_KNOWN (WC_ELSPKC_REF_FLAG_BIND | \ - WC_ELSPKC_REF_FLAG_EXPORTABLE | \ +#define ELS_REF_FLAGS_KNOWN (WC_ELSPKC_REF_FLAG_EXPORTABLE | \ WC_ELSPKC_REF_FLAG_PERSISTENT) int wc_ElsPkc_MakeKeyRef(const wc_ElsPkc_KeyRef* ref, byte* out, word32* outSz) @@ -332,10 +335,7 @@ int wc_ElsPkc_MakeKeyRef(const wc_ElsPkc_KeyRef* ref, byte* out, word32* outSz) 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); - } + /* out[6..15] stay zero - reserved */ *outSz = WC_ELSPKC_KEYREF_SZ; @@ -371,11 +371,9 @@ int wc_ElsPkc_ParseKeyRef(const byte* in, word32 inSz, wc_ElsPkc_KeyRef* ref) return WC_NO_ERR_TRACE(BAD_STATE_E); } - if (!(in[5] & WC_ELSPKC_REF_FLAG_BIND)) { - for (i = 0; i < WC_ELSPKC_BIND_SZ; i++) { - if (in[8 + i] != 0) { - return WC_NO_ERR_TRACE(BAD_STATE_E); - } + for (i = 0; i < WC_ELSPKC_BIND_SZ; i++) { + if (in[8 + i] != 0) { + return WC_NO_ERR_TRACE(BAD_STATE_E); } } @@ -383,9 +381,6 @@ int wc_ElsPkc_ParseKeyRef(const byte* in, word32 inSz, wc_ElsPkc_KeyRef* 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; } @@ -393,6 +388,80 @@ 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 +/* 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; +} + +/* A key over 128 bits, which every ECC class is, is named by its first slot. */ +static int ElsSlotBaseOk(const mcuxClEls_KeyProp_t* prop, byte keyClass) +{ + if (keyClass == WC_ELSPKC_KEY_ECC_SIGN || + keyClass == WC_ELSPKC_KEY_ECC_DH || + keyClass == WC_ELSPKC_KEY_ECC_SEED || + (prop->word.value & 0x3u) != MCUXCLELS_KEYPROPERTY_VALUE_KEY_SIZE_128) { + return (prop->word.value & MCUXCLELS_KEYPROPERTY_VALUE_BASE_SLOT) != 0; + } + + return 1; +} + +#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_IMPORT) +/* A new key needs a free general purpose slot. Caller holds the lock. */ +static int ElsSlotFree(byte slot) +{ + mcuxClEls_KeyProp_t cur; + int ret = ElsKsProps(slot, &cur); + + if (ret == 0 && + ((cur.word.value & MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE) || + (cur.word.value & + MCUXCLELS_KEYPROPERTY_VALUE_GENERAL_PURPOSE_SLOT) == 0)) { + ret = WC_NO_ERR_TRACE(BAD_STATE_E); + } + + return ret; +} + +/* Both slots of a 256-bit key. Caller holds the lock. */ +static int ElsSlotPairFree(byte slot) +{ + int ret; + + if (slot >= WC_ELSPKC_MAX_SLOT) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + ret = ElsSlotFree(slot); + if (ret == 0) { + ret = ElsSlotFree((byte)(slot + 1u)); + } + + return ret; +} + +static int ElsSlotDeleteRun(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(); +} +#endif + /* The ELS permission bit each class stands for. */ static word32 ElsClassUsageBit(byte keyClass) { @@ -410,39 +479,42 @@ static word32 ElsClassUsageBit(byte keyClass) } } -/* Check the slot is occupied and carries the usage bit; a permission - * violation resets the SoC. Caller holds the lock. */ +/* Whether a slot carries the permission a reference's class stands for. */ +static int ElsSlotHasClass(const mcuxClEls_KeyProp_t* prop, byte keyClass) +{ + word32 need = ElsClassUsageBit(keyClass); + + if (keyClass == WC_ELSPKC_KEY_KWK) { + need |= MCUXCLELS_KEYPROPERTY_VALUE_KUOK; + } + + return need != 0 && (prop->word.value & need) != 0; +} + +/* An active key of the class, named by its first slot. */ +static int ElsSlotHolds(const mcuxClEls_KeyProp_t* prop, byte keyClass) +{ + return (prop->word.value & MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE) != 0 && + ElsSlotHasClass(prop, keyClass) && ElsSlotBaseOk(prop, keyClass); +} + +/* Check the slot holds a key of the class; a permission violation resets the + * SoC. Caller holds the lock. */ static int ElsCheckSlot(const wc_ElsPkc_KeyRef* ref, byte expectClass) { mcuxClEls_KeyProp_t prop; - word32 need; + int ret; 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); + ret = ElsKsProps(ref->slot, &prop); + if (ret == 0 && !ElsSlotHolds(&prop, ref->keyClass)) { + ret = WC_NO_ERR_TRACE(BAD_STATE_E); } - return 0; + return ret; } #endif /* ELS_HAVE_SLOT_KEYS */ @@ -1235,6 +1307,471 @@ static int ElsAesGcm(Aes* aes, byte* out, const byte* in, word32 sz, #endif /* !NO_AES */ +/* 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) +/* Software rejects an all-zero digest below the callback; decline it. */ +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() drops leading zero bytes; the engine wants + * fixed-width big-endian. */ +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; +} + +/* Software takes r and s as positive INTEGERs in a SEQUENCE that fills the + * buffer; DecodeECC_DSA_Sig_Bin() checks neither. */ +static int ElsEccSigDerOk(const byte* sig, word32 sigLen) +{ + word32 idx = 0; + int len; + int i; + byte tag; + + if (GetSequence(sig, &idx, &len, sigLen) < 0) { + return 0; + } +#ifndef NO_STRICT_ECDSA_LEN + if (idx + (word32)len != sigLen) { + return 0; + } +#endif + for (i = 0; i < 2; i++) { + if (GetASNTag(sig, &idx, &tag, sigLen) < 0 || tag != ASN_INTEGER || + GetLength(sig, &idx, &len, sigLen) <= 0 || + (sig[idx] & 0x80) != 0) { + return 0; + } + idx += (word32)len; + } + + return 1; +} + +static const byte elsP256Order[ELS_ECC_COORD_SZ] = { + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84, + 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51 +}; + +/* Range-check a padded R||S with the codes wc_ecc_check_r_s_range uses. */ +static int ElsEccSigRange(const byte* sig, const byte* order, word32 len) +{ + byte rAcc = 0, sAcc = 0; + word32 i; + + for (i = 0; i < len; i++) { + rAcc |= sig[i]; + sAcc |= sig[len + i]; + } + if (rAcc == 0 || sAcc == 0) { + return WC_NO_ERR_TRACE(MP_ZERO_E); + } + if (XMEMCMP(sig, order, len) >= 0 || + XMEMCMP(sig + len, order, len) >= 0) { + return WC_NO_ERR_TRACE(MP_VAL); + } + + 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 in the empty slot the reference names, or from the ukgsrc + * seed already there. Only now is the public point available. */ +#ifdef HAVE_ECC_KEY_IMPORT +static int ElsEccKeyGenRun(ecc_key* key, int seeded) +{ + 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; + } + /* Under WOLF_CRYPTO_CB_KEYGEN_SW_FALLBACK a decline would have software + * fill a slot key. */ + if (!ElsEccCurveOk(key) || + (ref.keyClass != WC_ELSPKC_KEY_ECC_SIGN && + ref.keyClass != WC_ELSPKC_KEY_ECC_DH)) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + + /* kgtypedh sets the rest; a property word ELS disagrees with is a tamper + * event. */ + 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 ignored; retention is per slot. */ + + opt.word.value = 0u; + opt.bits.kgsrc = seeded ? MCUXCLELS_ECC_OUTPUTKEY_DETERMINISTIC + : 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; + } + + /* A seed becomes the scalar, so must be 256 bits. */ + if (seeded) { + ret = ElsKsProps(ref.slot, &cur); + if (ret == 0 && + ((cur.word.value & MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE) == 0 || + (cur.word.value & MCUXCLELS_KEYPROPERTY_VALUE_KGSRC) == 0 || + (cur.word.value & 0x3u) != + MCUXCLELS_KEYPROPERTY_VALUE_KEY_SIZE_256 || + !ElsSlotBaseOk(&cur, WC_ELSPKC_KEY_ECC_SEED))) { + ret = WC_NO_ERR_TRACE(BAD_STATE_E); + } + } + else { + ret = ElsSlotPairFree(ref.slot); + } + if (ret != 0) { + ElsUnlock(); + return ret; + } + if (seeded) { + prop.bits.upprot_priv = cur.bits.upprot_priv; + prop.bits.upprot_sec = cur.bits.upprot_sec; + } + + 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) { + ret = wc_ecc_import_unsigned(key, pub, pub + ELS_ECC_COORD_SZ, + NULL, ECC_SECP256R1); + /* Give the pair back, or a retry would find it occupied. */ + if (ret != 0 && !seeded && ElsLock() == 0) { + (void)ElsSlotDeleteRun(ref.slot); + ElsUnlock(); + } + } + + ForceZero(pub, sizeof(pub)); + + return ret; +} + +#ifdef HAVE_ECC_DHE +static int ElsEccKeyGen(ecc_key* key) +{ + return ElsEccKeyGenRun(key, 0); +} +#endif + +int wc_ElsPkc_EccKeyGenFromSeed(ecc_key* key, const wc_ElsPkc_KeyRef* seed, + byte keyClass, void* heap, int devId) +{ + wc_ElsPkc_KeyRef ref; + int ret; + + if (key == NULL || seed == NULL || + seed->keyClass != WC_ELSPKC_KEY_ECC_SEED || + (keyClass != WC_ELSPKC_KEY_ECC_SIGN && + keyClass != WC_ELSPKC_KEY_ECC_DH)) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + /* The same slots, named by what they will hold once this returns. The + * engine sets a seeded key's properties itself, wrpok included. */ + ref = *seed; + ref.keyClass = keyClass; + ref.flags &= (byte)~WC_ELSPKC_REF_FLAG_EXPORTABLE; + ret = wc_ElsPkc_EccUseSlot(key, &ref, heap, devId); + if (ret == 0) { + ret = ElsEccKeyGenRun(key, 1); + if (ret == WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + if (ret != 0) { + wc_ecc_free(key); + } + } + ForceZero(&ref, sizeof(ref)); + + return ret; +} +#elif defined(HAVE_ECC_DHE) +/* Without the import the public point cannot be loaded into the key. */ +static int ElsEccKeyGen(ecc_key* key) +{ + wc_ElsPkc_KeyRef ref; + int ret = ElsEccRef(key, &ref); + + return (ret == 0) ? WC_NO_ERR_TRACE(NOT_COMPILED_IN) : ret; +} +#endif /* HAVE_ECC_KEY_IMPORT */ + +#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* digest, + 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]; + word32 rLen = ELS_ECC_COORD_SZ, sLen = ELS_ECC_COORD_SZ; + int ret; + + if (sig == NULL || digest == NULL || res == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + if (!ElsEccCurveOk(key) || hashlen != ELS_ECC_COORD_SZ || + !ElsEccSigDerOk(sig, siglen) || + (key->type != ECC_PUBLICKEY && key->type != ECC_PRIVATEKEY)) { + 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; + } + /* wolfCrypt range-checks r and s only on its software path. */ + ret = ElsEccSigRange(sigAndPub, elsP256Order, ELS_ECC_COORD_SZ); + if (ret != 0) { + return ret; + } + + /* ELS takes the point as X||Y. */ + if (mp_to_unsigned_bin_len(key->pubkey.x, sigAndPub + ELS_ECC_SIG_SZ, + ELS_ECC_COORD_SZ) != MP_OKAY || + mp_to_unsigned_bin_len(key->pubkey.y, + sigAndPub + ELS_ECC_SIG_SZ + ELS_ECC_COORD_SZ, + ELS_ECC_COORD_SZ) != MP_OKAY) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + XMEMCPY(hash, digest, 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) { + /* ELS reports a bad signature only through the R comparison. */ + *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 */ + /* 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. */ @@ -1688,6 +2225,52 @@ 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_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 declines anything but a P-256 slot key. */ + 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 + /* No WC_PK_TYPE_ECDH: ELS leaves the agreed secret in a slot. */ + 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 0c3aaa6b822..af470218b6a 100644 --- a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h +++ b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h @@ -36,8 +36,16 @@ #error "WOLFSSL_ELS_PKC key slot references require WOLF_PRIVATE_KEY_ID" #endif +/* NO_ASN switches ecc.c to raw R||S, which the ECDSA arms do not handle. */ +#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 @@ -65,8 +73,7 @@ * 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 + * 8 8 bind, reserved and zero; nothing computes or checks it yet */ /* Highest slot index a reference may name, mirroring the vendor header's @@ -95,8 +102,8 @@ enum wc_ElsPkc_KeyClass { WC_ELSPKC_KEY_MAX = WC_ELSPKC_KEY_HKDF }; -/* bit0 is set when bind[] carries a real value; bits 1-2 are creation-time - * attributes, meaningful only on a key generation. */ +/* bit0 is reserved and refused; 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 */ @@ -115,6 +122,20 @@ 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 +/* Bind key to the slot ref names, the port's devId and P-256. For key + * generation the slot is empty until wc_ecc_make_key(). */ +WOLFSSL_API int wc_ElsPkc_EccUseSlot(ecc_key* key, const wc_ElsPkc_KeyRef* ref, + void* heap, int devId); + +#ifdef HAVE_ECC_KEY_IMPORT +/* Turn the ECC_SEED key seed names into a P-256 pair of keyClass in the + * same slots; key then holds the public point. */ +WOLFSSL_API int wc_ElsPkc_EccKeyGenFromSeed(ecc_key* key, + const wc_ElsPkc_KeyRef* seed, byte keyClass, void* heap, int devId); +#endif +#endif + #ifndef NO_AES /* Same for an Aes; keylen stays 0 and the engine takes the size from the * slot. Only WC_ELSPKC_KEY_AES drives a cipher. */ From ce29df1bba36c4c32d7c3db48f17984d08788363 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 2/9] 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 uses mcuxClRandomModes_Mode_ELS_Drbg, which carries 128-bit strength and is instantiated in microseconds, and the full SP800-90A CTR_DRBG only for RSA-4096 and curves over 256 bits, which is the split the vendor's own PSA driver makes. DRG.3 costs about 90ms to instantiate, so one session serves every PKC operation: it is opened on first use, the CTR_DRBG is instantiated the first time it is needed and then kept, and mcuxClSession_setRandom() picks the DRBG per operation. Each operation ends with mcuxClSession_cleanup(), which wipes the work areas. Some CLNS error exits keep their work area claimed (the MontDH small-subgroup exit, for one), so the session is torn down after any operation that leaves more claimed than it found, and after an engine failure; wc_ElsPkc_Cleanup() closes it, and init forgets any session a failed cleanup left behind, since the PKC reset invalidates it. Tearing a session down also resets the PKC, because the same error exits leave it mid-operation, and init skips that reset while the port is already up, where it would wedge an operation in flight. 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. CLNS takes a CRT key's modulus length as bits(p) + bits(q), and its raw private operation assumes a modulus of whole bytes. A modulus that is not a multiple of 8 bits is therefore declined; on the board an RSA-2047 key gave a wrong private result. A private operation uses CRT only when every CRT member fits in half the modulus and bits(p) + bits(q) equals bits(n), and any other key takes the plain form. A shorter p is fine: the engine reads it correctly from its zero-padded entry, as a 2040-bit key built from 1016-bit and 1024-bit primes shows. 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. wolfCrypt bounds a decrypt input to 1 < c < n - 1 only on its software path, and there only for its own padded callers, which the callback cannot tell apart from a raw wc_RsaFunction() call. The port therefore runs RsaFunctionCheckIn() and declines an input that fails it, so software applies its own rule: RSA_OUT_OF_RANGE_E for a padded caller, a result for a raw call. An unknown operation type gives RSA_WRONG_TYPE_E, as in software, instead of running as a public operation. ELS_PowerDownWakeupInit() leaves the PKC alone, so init also runs PKC_PowerDownWakeupInit(), which clocks and resets the PKC and clears its RAM. Without it the port relies on the boot ROM having left the PKC clocked, which does not hold after a power-down. A session whose DRBG fails to come up is torn down without mcuxClRandom_uninit(), which would read the mode that the failed init never set. Nothing here is vaulted: the PKC tier accelerates keys in ordinary memory, which is precisely what the ELS key store cannot hold. The public path copies the exponent into the room the scratch block leaves after the modulus, so an exponent longer than the modulus is declined instead of overrunning it. Where WOLF_CRYPTO_CB_ONLY_RSA or WOLFSSL_NO_RSA_SW compiles RsaFunctionCheckIn() out, the port applies the same 1 < c < n - 1 bound itself, since no software path would. An even modulus is declined, so software rejects it with MP_VAL as it does on its own path. --- wolfcrypt/src/port/nxp/README.md | 11 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 556 +++++++++++++++++++++++++- 2 files changed, 563 insertions(+), 4 deletions(-) diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 38f7947853e..306dc787bdb 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -625,12 +625,13 @@ 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) +## 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. NXP rates the ELS DRBG at 128 bits of security strength, which caps wolfCrypt's Hash-DRBG at 128 bits where it supplies the seed. @@ -704,6 +705,12 @@ fails with `BAD_LENGTH_E` and such an IV with `BAD_FUNC_ARG`. 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 5f853a1dbdb..3d039334763 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -1772,6 +1772,538 @@ static int ElsEccVerify(const byte* sig, word32 siglen, const byte* digest, #endif /* HAVE_ECC */ +/* The PKC workarea is a fixed hardware region at PKC_RAM_ADDR shared with + * ELS, so PKC work runs under the ELS lock. */ + +#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 +#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 + +/* CLNS CPU workarea, sized for RSA-4096. */ +#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 128-bit ELS DRBG is too weak for RSA-4096 and orders over 32 bytes, + * which take the 256-bit DRG.3 instead. Caller holds the lock. */ +static int ElsPkcRandomInit(mcuxClSession_Descriptor_t* sess, int need256) +{ + mcuxClRandom_Mode_t mode = need256 + ? mcuxClRandomModes_Mode_CtrDrbg_AES256_DRG3 + : mcuxClRandomModes_Mode_ELS_Drbg; + + if (need256) { + XMEMSET(elsPkcRngCtx, 0, sizeof(elsPkcRngCtx)); + } + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(rr, rt, mcuxClRandom_init(sess, + need256 ? (mcuxClRandom_Context_t)elsPkcRngCtx : NULL, 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; +} + +/* Point the session at a DRBG that ElsPkcRandomInit() has set up. */ +static int ElsPkcRandomUse(mcuxClSession_Descriptor_t* sess, int need256) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(sr, st, mcuxClSession_setRandom(sess, + need256 ? mcuxClRandomModes_Mode_CtrDrbg_AES256_DRG3 + : mcuxClRandomModes_Mode_ELS_Drbg, + need256 ? (mcuxClRandom_Context_t)elsPkcRngCtx : NULL)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClSession_setRandom) != st) || + (MCUXCLSESSION_STATUS_OK != sr)) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return 0; +} + +static void ElsPkcRandomUninit(mcuxClSession_Descriptor_t* sess, int need256) +{ + if (ElsPkcRandomUse(sess, need256) != 0) { + return; + } + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(ur, ut, mcuxClRandom_uninit(sess)); + (void)ur; + (void)ut; + MCUX_CSSL_FP_FUNCTION_CALL_END(); +} + +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; +} + +/* Writing the PKC window faults the bus unless the PKC is clocked. Caller + * holds the lock. */ +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); +} + +/* CLNS wipes the work areas, including key material left in the shared PKC + * window. Best effort. */ +static void ElsPkcSessionWipe(mcuxClSession_Descriptor_t* sess) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(cr, ct, mcuxClSession_cleanup(sess)); + (void)cr; + (void)ct; + MCUX_CSSL_FP_FUNCTION_CALL_END(); +} + +static void ElsPkcSessionEnd(mcuxClSession_Descriptor_t* sess) +{ + ElsPkcSessionWipe(sess); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(dr, dt, mcuxClSession_destroy(sess)); + (void)dr; + (void)dt; + MCUX_CSSL_FP_FUNCTION_CALL_END(); +} + +/* One session serves every PKC operation, so the 256-bit DRBG is instantiated + * once. Caller holds the lock. */ +static mcuxClSession_Descriptor_t elsPkcSess; +static byte elsPkcSessUp; +static byte elsPkcRng256Up; +static uint32_t elsPkcCpuUsed; +static uint32_t elsPkcPkcUsed; + +static void ElsPkcSessionDown(void) +{ + if (!elsPkcSessUp) { + return; + } + if (elsPkcRng256Up) { + ElsPkcRandomUninit(&elsPkcSess, 1); + } + ElsPkcRandomUninit(&elsPkcSess, 0); + ElsPkcSessionEnd(&elsPkcSess); + ForceZero(elsPkcRngCtx, sizeof(elsPkcRngCtx)); + elsPkcSessUp = 0; + elsPkcRng256Up = 0; + /* The error exits that keep work area claimed also leave the PKC set up. */ + (void)PKC_PowerDownWakeupInit(PKC); +} + +static int ElsPkcSessionOpen(mcuxClSession_Descriptor_t** sess, int need256) +{ + int ret; + + if (!elsPkcSessUp) { + ret = ElsPkcSessionInit(&elsPkcSess); + if (ret != 0) { + return ret; + } + /* mcuxClRandom_uninit() reads the mode that a failed init never set. */ + ret = ElsPkcRandomInit(&elsPkcSess, 0); + if (ret != 0) { + ElsPkcSessionEnd(&elsPkcSess); + return ret; + } + elsPkcSessUp = 1; + ret = ElsPkcRandomNcInit(&elsPkcSess); + if (ret != 0) { + ElsPkcSessionDown(); + return ret; + } + } + if (need256 && !elsPkcRng256Up) { + ret = ElsPkcRandomInit(&elsPkcSess, 1); + if (ret != 0) { + ElsPkcSessionDown(); + return ret; + } + elsPkcRng256Up = 1; + } + ret = ElsPkcRandomUse(&elsPkcSess, need256); + if (ret != 0) { + ElsPkcSessionDown(); + return ret; + } + + elsPkcCpuUsed = elsPkcSess.cpuWa.used; + elsPkcPkcUsed = elsPkcSess.pkcWa.used; + *sess = &elsPkcSess; + + return 0; +} + +/* Some CLNS error exits keep their work area claimed, so the session starts + * over after any operation that leaves more claimed than it found. */ +static void ElsPkcSessionClose(mcuxClSession_Descriptor_t* sess, int failed) +{ + if (failed || sess->cpuWa.used != elsPkcCpuUsed || + sess->pkcWa.used != elsPkcPkcUsed) { + ElsPkcSessionDown(); + } + else { + ElsPkcSessionWipe(sess); + } +} + +/* ELS_PowerDownWakeupInit() leaves the PKC alone. */ +static int ElsPkcEnable(void) +{ + if (PKC_PowerDownWakeupInit(PKC) != kStatus_Success) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + /* A session that a failed cleanup left open would not survive the reset. */ + ForceZero(&elsPkcSess, sizeof(elsPkcSess)); + ForceZero(elsPkcRngCtx, sizeof(elsPkcRngCtx)); + elsPkcSessUp = 0; + elsPkcRng256Up = 0; + + return 0; +} + +#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; the padded modes report VERIFY_OK. */ + 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; +} + +#if !defined(NO_RSA_BOUNDS_CHECK) && \ + (defined(WOLF_CRYPTO_CB_ONLY_RSA) || defined(WOLFSSL_NO_RSA_SW)) +/* RsaFunctionCheckIn() is compiled out in this build: 1 < c < n - 1. */ +static int ElsPkcRsaCheckIn(const byte* in, word32 len, RsaKey* key) +{ + byte* nm1; + word32 i; + int ret; + + nm1 = (byte*)XMALLOC(len, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (nm1 == NULL) { + return WC_NO_ERR_TRACE(MEMORY_E); + } + ret = ElsPkcMpToBin(&key->n, nm1, len); + if (ret == 0) { + for (i = len; i > 0 && nm1[i - 1] == 0; i--) { + nm1[i - 1] = 0xff; + } + if (i == 0) { + ret = WC_NO_ERR_TRACE(RSA_OUT_OF_RANGE_E); + } + else { + nm1[i - 1]--; + } + } + if (ret == 0 && XMEMCMP(in, nm1, len) >= 0) { + ret = WC_NO_ERR_TRACE(RSA_OUT_OF_RANGE_E); + } + if (ret == 0) { + for (i = 0; i < len - 1 && in[i] == 0; i++) { + } + if (i == len - 1 && in[i] <= 1) { + ret = WC_NO_ERR_TRACE(RSA_OUT_OF_RANGE_E); + } + } + + XFREE(nm1, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif + +/* RSA at the raw primitive, beneath padding: NoVerify and NoEncode. */ +/* Mirror rsa.h's conditions for the p/q and dP/dQ/u members. */ +#if !defined(WOLFSSL_RSA_PUBLIC_ONLY) && \ + (defined(WOLFSSL_KEY_GEN) || defined(OPENSSL_EXTRA) || \ + !defined(RSA_LOW_MEM)) + #define ELS_PKC_RSA_HAVE_CRT +#endif + +#ifdef ELS_PKC_RSA_HAVE_CRT +static int ElsPkcRsaCrtFits(const mp_int* a, word32 half) +{ + int sz = mp_unsigned_bin_size((mp_int*)a); + + return sz > 0 && sz <= (int)half; +} + +/* CLNS takes a CRT key's modulus length as bits(p) + bits(q). */ +static int ElsPkcRsaHaveCrt(const RsaKey* key, word32 half) +{ + return ElsPkcRsaCrtFits(&key->p, half) && + ElsPkcRsaCrtFits(&key->q, half) && + ElsPkcRsaCrtFits(&key->dP, half) && + ElsPkcRsaCrtFits(&key->dQ, half) && + ElsPkcRsaCrtFits(&key->u, half) && + mp_count_bits((mp_int*)&key->p) + mp_count_bits((mp_int*)&key->q) == + mp_count_bits((mp_int*)&key->n); +} +#endif + +static int ElsPkcRsaFunction(const byte* in, word32 inLen, byte* out, + word32* outLen, int type, RsaKey* key) +{ + mcuxClSession_Descriptor_t* sess = NULL; + 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); + } + if (type != RSA_PUBLIC_ENCRYPT && type != RSA_PUBLIC_DECRYPT && + type != RSA_PRIVATE_ENCRYPT && type != RSA_PRIVATE_DECRYPT) { + return WC_NO_ERR_TRACE(RSA_WRONG_TYPE_E); + } + + 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 || + (word32)mp_count_bits(&key->n) != modLen * 8u || mp_iseven(&key->n)) { + 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); + } +#else + if (isPrivate && (key->type != RSA_PRIVATE || mp_iszero(&key->d))) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } +#endif +#ifndef NO_RSA_BOUNDS_CHECK + if (type == RSA_PRIVATE_DECRYPT || type == RSA_PUBLIC_DECRYPT) { + #if !defined(WOLF_CRYPTO_CB_ONLY_RSA) && !defined(WOLFSSL_NO_RSA_SW) + /* Software bounds the input only for its own padded callers. */ + if (RsaFunctionCheckIn(in, inLen, key, 1) != 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + #else + ret = ElsPkcRsaCheckIn(in, modLen, key); + if (ret != 0) { + return ret; + } + #endif + } +#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); + /* The scratch block leaves one modulus length for the exponent. */ + if (eLen == 0 || eLen > modLen) { + ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + else { + 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 { +#ifdef ELS_PKC_RSA_HAVE_CRT + word32 half = (modLen + 1u) / 2u; + + if (ElsPkcRsaHaveCrt(key, half)) { + 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, ret == WC_NO_ERR_TRACE(WC_HW_E)); + } + 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 */ + /* 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. */ @@ -2225,9 +2757,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_DHE) case WC_PK_TYPE_EC_KEYGEN: @@ -2269,7 +2809,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: @@ -2432,6 +2972,12 @@ int wc_ElsPkc_Init(void) /* Bring the subsystem out of reset and enable it. Without this the first * offload would drive a disabled peripheral. Safe to repeat. */ ret = ElsEnable(); +#ifdef ELS_PKC_HAVE_SESSION + /* A PKC reset under a live port would wedge an operation in flight. */ + if (ret == 0 && !elsReady) { + ret = ElsPkcEnable(); + } +#endif if (ret != 0) { WOLFSSL_MSG("els_pkc: enable failed"); return ret; @@ -2481,6 +3027,12 @@ int wc_ElsPkc_Cleanup(void) /* Close the gate under the lock. The mutex outlives cleanup, since a * blocked caller still has to unlock it. */ if (wc_LockMutex(&elsLock) == 0) { +#ifdef ELS_PKC_HAVE_SESSION + if (elsReady) { + ElsPkcSessionDown(); + ElsCleanupSession(); + } +#endif elsReady = 0; ElsIrqDisarm(); (void)wc_UnLockMutex(&elsLock); From 9eeed400ec132be97e959448f88374b93a1f837e 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 3/9] 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. The engine performs the RFC 7748 all-zero check itself and reports a small-order peer key as ERROR_SMALL_SUBGROUP, which maps to ECC_OUT_OF_RANGE_E, the error software returns for it. CLNS reports a fault it detects during the multiplication through the same status, so such a fault surfaces as ECC_OUT_OF_RANGE_E as well. 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. --- wolfcrypt/src/port/nxp/README.md | 2 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 157 +++++++++++++++++++++++++- 2 files changed, 156 insertions(+), 3 deletions(-) diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 306dc787bdb..f46a5af361c 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -631,7 +631,7 @@ algorithms, so ML-KEM and ML-DSA stay on the CPU. 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. NXP rates the ELS DRBG at 128 bits of security strength, which caps wolfCrypt's Hash-DRBG at 128 bits where it supplies the seed. diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index 3d039334763..1160dec1f08 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -2304,6 +2304,150 @@ static int ElsPkcRsaFunction(const byte* in, word32 inLen, byte* out, #endif /* ELS_PKC_HAVE_SESSION */ +/* 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) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + /* The engine's RFC 7748 all-zero check. CLNS reports a detected fault the + * same way, so one also lands here. */ + if (MCUXCLECC_STATUS_ERROR_SMALL_SUBGROUP == r) { + return WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E); + } + if (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 = NULL; + 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 is little-endian; decline the legacy big-endian request. */ + if (endian != EC25519_LITTLE_ENDIAN) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + +#ifdef HAVE_CURVE25519_KEY_EXPORT + 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); + } +#else + ret = WC_NO_ERR_TRACE(NOT_COMPILED_IN); +#endif + 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, ret == WC_NO_ERR_TRACE(WC_HW_E)); + } + ElsUnlock(); + } + + ForceZero(priv, sizeof(priv)); + + return ret; +} + +#endif /* HAVE_CURVE25519 */ + /* 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. */ @@ -2757,9 +2901,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, @@ -2809,7 +2962,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 83f2e5d604095ddf1f6056ee7fdd6cbcbeac61e7 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 4/9] nxp: offload ECDSA on the PKC for curves beyond P-256 The complement to the ELS tier, which verifies only on P-256 and 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. The PKC draws its own nonce, so an RFC 6979 or wc_ecc_sign_set_k() signature is declined and stays in software. secp160r1 and secp160r2 also stay in software: their order is a byte longer than the prime, and the domain parameters are carried at the prime's width. The PKC verify path applies the same DER checks to the signature as the ELS one. The converted constants of a curve from wolfCrypt's own table are kept for the next operation on it, so the hex parsing runs once per curve change rather than once per signature, inside the lock. A caller-supplied curve set may live anywhere and is converted every time, and the precomputed point derived for a custom curve is keyed on p, a, b and G together, since every explicit-parameter curve reports ECC_CURVE_CUSTOM. An off-curve public key makes mcuxClEcc_Verify() return INVALID_PARAMS, which is declined so software gives its own answer, instead of being reported as a hardware failure, and the same status from the generator precomputation is declined too. As on ELS, r and s are range-checked against the curve order with software's MP_ZERO_E and MP_VAL. --- wolfcrypt/src/port/nxp/README.md | 20 +- wolfcrypt/src/port/nxp/els_pkc_port.c | 790 ++++++++++++++++++++++++++ 2 files changed, 803 insertions(+), 7 deletions(-) diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index f46a5af361c..93d54099db8 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -631,7 +631,8 @@ algorithms, so ML-KEM and ML-DSA stay on the CPU. 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. NXP rates the ELS DRBG at 128 bits of security strength, which caps wolfCrypt's Hash-DRBG at 128 bits where it supplies the seed. @@ -694,7 +695,7 @@ digest. 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 @@ -705,11 +706,16 @@ fails with `BAD_LENGTH_E` and such an IV with `BAD_FUNC_ARG`. 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. +**The PKC uses the ELS DRBG where 128 bits are enough.** That is the ELS +DRBG's security strength on this part, and `mcuxClEcc_Sign` refuses P-384 and +P-521 with `RNG_ERROR` when given it - a failure that names the random source +rather than the curve. Signatures on curves over 256 bits and RSA-4096 private +operations therefore take a 256-bit CTR_DRBG, instantiated once and then kept. +Neither engine accepts a nonce from the caller, so an RFC 6979 or +`wc_ecc_sign_set_k()` signature stays in software. 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 1160dec1f08..5f359721ce5 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -1416,6 +1416,33 @@ static int ElsEccRef(const ecc_key* key, wc_ElsPkc_KeyRef* ref) return wc_ElsPkc_ParseKeyRef(key->id, (word32)key->idLen, ref); } +#ifdef HAVE_ECC_SIGN +/* 1 when the caller asked for RFC 6979 or a set k; neither engine takes one. */ +static int ElsEccCustomNonce(const ecc_key* key) +{ +#if defined(WOLFSSL_ECDSA_DETERMINISTIC_K) || \ + defined(WOLFSSL_ECDSA_DETERMINISTIC_K_VARIANT) + if (key->deterministic) { + return 1; + } +#endif +#if defined(WOLFSSL_ECDSA_SET_K) || defined(WOLFSSL_ECDSA_SET_K_ONE_LOOP) + #ifndef WOLFSSL_NO_MALLOC + if (key->sign_k != NULL) { + return 1; + } + #else + if (key->sign_k_set) { + return 1; + } + #endif +#endif + (void)key; + + return 0; +} +#endif /* HAVE_ECC_SIGN */ + /* P-256 is the only curve with a slot to reference. */ static int ElsEccCurveOk(const ecc_key* key) { @@ -2448,6 +2475,756 @@ static int ElsPkcX25519(curve25519_key* privKey, curve25519_key* pubKey, #endif /* HAVE_CURVE25519 */ +/* Every curve, and P-256 for a key without a slot. Domain parameters are + * converted into shared scratch under the lock. */ + +#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, kept for the next one on the + * same built-in curve. 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]; + const ecc_set_type* set; +} 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); + } + + if (elsPkcCurve.set == set) { + goto loaded; + } + elsPkcCurve.set = NULL; + + 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 prime's width; secp160r1/r2, whose order is + * a byte longer, fail here and stay in software. */ + 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); + } + /* Only wolfCrypt's own table entries outlive the call. */ + elsPkcCurve.set = (wc_ecc_get_curve_params(key->idx) == set) ? set : NULL; + +loaded: + 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 = NULL; + 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) || + ElsEccCustomNonce(key)) { + 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; +#ifdef HAVE_ECC_KEY_EXPORT + ret = wc_ecc_export_private_only(key, priv, &privSz); +#else + ret = WC_NO_ERR_TRACE(NOT_COMPILED_IN); +#endif + if (ret != 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, ret == WC_NO_ERR_TRACE(WC_HW_E)); + } + } + + 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)); + ForceZero(sig, sizeof(sig)); + + 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) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + if (MCUXCLECC_STATUS_INVALID_PARAMS == r) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (MCUXCLECC_STATUS_OK != r) { + return WC_NO_ERR_TRACE(WC_HW_E); + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return 0; +} + +/* Keyed on the whole domain, since every explicit-parameter curve reports + * ECC_CURVE_CUSTOM. */ +static struct { + ALIGN32 byte precG[2 * ELS_PKC_ECC_MAX_P]; + byte dom[5 * ELS_PKC_ECC_MAX_P]; /* p || a || b || G */ + word32 lenP; + byte valid; +} elsPkcPrecG; + +static void ElsPkcPrecGDomain(const mcuxClEcc_DomainParam_t* dp, word32 lenP, + byte* dom) +{ + XMEMCPY(dom, dp->pP, lenP); + XMEMCPY(dom + lenP, dp->pA, lenP); + XMEMCPY(dom + 2u * lenP, dp->pB, lenP); + XMEMCPY(dom + 3u * lenP, dp->pG, 2u * lenP); +} +#endif /* WOLFSSL_CUSTOM_CURVES */ + +/* PrecG = (2^(byteLenN*4))*G for input verification, under the same guards + * as ecc.c. A custom curve's is computed. */ +typedef struct ElsPkcPrecGEntry { + int curveId; + word32 lenP; + const byte* precG; +} ElsPkcPrecGEntry; + +/* The curve gates of ecc.c's table, which ecc.c keeps private. */ +#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(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(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]; + byte dom[5 * ELS_PKC_ECC_MAX_P]; + 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 + ElsPkcPrecGDomain(dp, lenP, dom); + if (elsPkcPrecG.valid && elsPkcPrecG.lenP == lenP && + XMEMCMP(elsPkcPrecG.dom, dom, 5u * 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.dom, dom, 5u * 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); + } + /* The PKC reports a bad signature through INVALID_SIGNATURE. */ + if (MCUXCLECC_STATUS_OK == r) { + *res = 1; + } + else if (MCUXCLECC_STATUS_INVALID_SIGNATURE == r) { + *res = 0; + } + /* An off-curve public key; software gives its own answer for one. */ + else if (MCUXCLECC_STATUS_INVALID_PARAMS == r) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + 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* digest, word32 hashLen, int* res, + ecc_key* key) +{ + mcuxClSession_Descriptor_t* sess = NULL; + 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 || digest == 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) || + !ElsEccSigDerOk(sigDer, sigLen)) { + 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) { + ret = ElsEccSigRange(sig, elsPkcCurve.n, lenP); + } + } + + if (ret == 0) { +#ifdef HAVE_ECC_KEY_EXPORT + ret = wc_ecc_export_x963_ex(key, x963, &x963Sz, 0); +#else + ret = WC_NO_ERR_TRACE(NOT_COMPILED_IN); +#endif + if (ret != 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, digest, hashLen); + /* Verify consumes no nonce, so it never needs the 256-bit DRBG. */ + 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, ret == WC_NO_ERR_TRACE(WC_HW_E)); + } + } + + ElsUnlock(); + + ForceZero(hash, sizeof(hash)); + ForceZero(rOut, sizeof(rOut)); + + return ret; +} + +#endif /* HAVE_ECC_VERIFY */ +#endif /* HAVE_ECC && (HAVE_ECC_SIGN || HAVE_ECC_VERIFY) */ + /* 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. */ @@ -2939,6 +3716,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) @@ -2955,6 +3737,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 /* No WC_PK_TYPE_ECDH: ELS leaves the agreed secret in a slot. */ From 65aaf5364de227c91f8e975240a22dfb8181653d 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 5/9] 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, both for reserve and delete and as an import or derive target. An ECC key or seed always takes a slot pair. An imported key's permissions come from the property word inside the container, which is only readable after the unwrap. Import therefore checks the new key against the target reference's class afterwards and deletes it again on a mismatch. Derive refuses an ECC key pair class as its target, since those only come out of key generation, refuses a seed under 256 bits, and accepts only WC_KDF_TYPE_NONE: the engine runs the SP800-108 CMAC KDF, and WC_KDF_TYPE_TWOSTEP_CMAC names the different SP 800-56C two-step construction. ELS feeds the requested property word into every derivation, so the same parent and derivation data give a different key when the protection bits or wrpok differ. The derive path asks for the same bits NXP's own key chains do, which is what lets an application reproduce keys like the EdgeLock 2GO import keys. A wrap-capable key may only be derived from the die unique key. From any derived parent the engine trips the tamper controller, measured on hardware, so derive refuses that case up front. An unwrap-only key derives from any source. The README records both rules and what an EdgeLock 2GO import needs from the port. Export, delete, GetInfo and the wrapping key also require a base slot, and delete, export and GetInfo also require the slot to carry the permission of the reference's class, so a stale reference cannot destroy or export a key that has since reused its slot. An ECC import needs a 256-bit container, and a seed target derives at 256 bits when keySz is 0. A 256-bit target that would run past the last slot gives BAD_FUNC_ARG, the same as key generation, and so does an export whose wrapping key is the key being exported. --- .wolfssl_known_macro_extras | 1 + configure.ac | 3 +- wolfcrypt/src/port/nxp/README.md | 66 ++- wolfcrypt/src/port/nxp/els_pkc_port.c | 644 +++++++++++++++++++++- wolfssl/wolfcrypt/port/nxp/els_pkc_port.h | 11 + zephyr/Kconfig | 8 +- zephyr/user_settings.h | 8 +- 7 files changed, 724 insertions(+), 17 deletions(-) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 899a2f9cf5c..27741af7054 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -455,6 +455,7 @@ MAX_WOLFSSL_FILE_SIZE MCDC_FH_NO_SHA256 MCDC_FH_NO_SHA512 MCDC_FM_WITH_INIT +MCUXCL_FEATURE_ELS_PUK_INTERNAL MDK_CONF_BARE_METAL MDK_CONF_FS MDK_CONF_RTX_TCP_FS diff --git a/configure.ac b/configure.ac index 4f95dcd6ce3..3347d926cae 100644 --- a/configure.ac +++ b/configure.ac @@ -4355,7 +4355,8 @@ then CPPFLAGS="$CPPFLAGS -I$mcux_sdk_dir/drivers/common" # WOLF_CRYPTO_CB, and with it WOLF_PRIVATE_KEY_ID, is promoted further down. - AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_ELS_PKC" + # The key store ops are promoted here so they cannot be left out. + 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]) diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 93d54099db8..4f9b50a2e6d 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -629,12 +629,15 @@ algorithms, so ML-KEM and ML-DSA stay on the CPU. `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; the PKC -coprocessor serves RSA, X25519, and ECDSA on every curve, P-256 included when -the key holds ordinary material. -NXP rates the ELS DRBG at 128 bits of security strength, which caps -wolfCrypt's Hash-DRBG at 128 bits where it supplies the seed. +peripheral serves SHA-256, SHA-384, SHA-512, AES (ECB/CBC/CTR), AES-GCM, CMAC, +the DRBG, every P-256 ECDSA verify, and P-256 signing with a key that names a +slot; the PKC coprocessor serves RSA, X25519, and the rest of ECDSA, P-256 +signing with ordinary key material included. 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. NXP rates the ELS DRBG at 128 bits of security +strength, which caps wolfCrypt's Hash-DRBG at 128 bits where it supplies the +seed. Anything the hardware does not serve is declined with `CRYPTOCB_UNAVAILABLE` and completed in software, so an unsupported algorithm or key size costs @@ -703,8 +706,19 @@ bytes. A key in a slot has no software copy, so with one a partial block fails with `BAD_LENGTH_E` and such an IV with `BAD_FUNC_ARG`. **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. +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. A slot +key fails there with `ECC_BAD_ARG_E`, since its `ecc_key` holds only the +public point. An in-slot agreement would need `mcuxClEls_EccKeyExchange_Async` +exposed through a key store operation, which this port does not yet do, so a +slot generated as `WC_ELSPKC_KEY_ECC_DH` has no consumer here yet. + +`wc_KeyStore_Derive()` drives the SP800-108 CMAC KDF only, as +`WC_KDF_TYPE_NONE`, 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, and a `WC_ELSPKC_KEY_ECC_SEED` target must be 256 +bits, which 0 selects for it. **The PKC uses the ELS DRBG where 128 bits are enough.** That is the ELS DRBG's security strength on this part, and `mcuxClEcc_Sign` refuses P-384 and @@ -717,6 +731,42 @@ Neither engine accepts a nonce from the caller, so an RFC 6979 or 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. +An imported key whose container lacks the permission of the reference's +class is deleted again, and the import fails with `BAD_STATE_E`. Delete and +export refuse a reference whose class the slot's key does not carry, so a +stale reference cannot destroy a key that has since reused its slot. + +**Two derivation rules the hardware enforces.** The ELS mixes the requested +property word into every CKDF, so the same parent and derivation data give a +different key when the protection bits or `wrpok` differ (`kactv` makes no +difference). A derived key therefore takes its privileged and secure bits from +its parent, as NXP's own recipes do, and a seeded key generation keeps the +seed's. And only the die unique key in slot 0 may parent a wrap-capable KWK: +deriving one from any other key trips the tamper controller, so +`wc_KeyStore_Derive()` refuses it with `BAD_STATE_E`. An unwrap-only KWK +(`WC_KEYSTORE_ATTR_UNWRAP_ONLY`) derives from any `WC_ELSPKC_KEY_CKDF` source. + +**EdgeLock 2GO.** Keys provisioned through NXP's EdgeLock 2GO service descend +from `NXP_DIE_EL2GOOEM_MK_SK`, which the boot ROM leaves in slots 4 and 5 only +when secure boot is enabled (RW61x user manual UM11865, section 12.11, table +99). The port carries no EdgeLock 2GO code. An application derives the import +keys from that slot with `wc_KeyStore_Derive()`, checks each secure object's +CMAC with a slot bound through `wc_ElsPkc_CmacUseSlot()`, and unwraps the key +container with `wc_KeyStore_ImportWrapped()`; since the port copies the +parent's protection bits, the keys come out identical to those of NXP's RW61x +driver. A P-256 key arrives as a `WC_ELSPKC_KEY_ECC_SEED` holding the private +scalar, and `wc_ElsPkc_EccKeyGenFromSeed()` turns it into a key pair in the +same slots and returns the public point; the ELS marks the result +wrap-exportable by itself. If loading that point fails, the seed is already +consumed and the pair stays in the slots without it. The derivation data and +the secure object format are in NXP's EdgeLock 2GO agent, +https://github.com/NXP/el2go-agent. + ### 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 5f359721ce5..77462255ede 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -67,7 +67,7 @@ * violation resets the SoC. */ #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 @@ -415,7 +415,8 @@ static int ElsSlotBaseOk(const mcuxClEls_KeyProp_t* prop, byte keyClass) return 1; } -#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_IMPORT) +#if (defined(HAVE_ECC) && defined(HAVE_ECC_KEY_IMPORT)) || \ + defined(WOLF_CRYPTO_CB_KEYSTORE) /* A new key needs a free general purpose slot. Caller holds the lock. */ static int ElsSlotFree(byte slot) { @@ -3225,6 +3226,616 @@ static int ElsPkcEccVerify(const byte* sigDer, word32 sigLen, #endif /* HAVE_ECC_VERIFY */ #endif /* HAVE_ECC && (HAVE_ECC_SIGN || HAVE_ECC_VERIFY) */ +/* 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; + } + /* A slot that also wraps is reported by what it computes. */ + 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 | + MCUXCLELS_KEYPROPERTY_VALUE_KGSRC)) { + 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: /* fall through */ + case WC_ELSPKC_KEY_ECC_SEED: 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 new key: only the active bit is a safe test, and a + * 256-bit key needs a pair. Advisory. */ +int wc_ElsPkc_ReserveSlot(byte keyClass, word32 keySz, wc_ElsPkc_KeyRef* ref) +{ + mcuxClEls_KeyProp_t prop; + int isEcc; + 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); + } + isEcc = (keyClass == WC_ELSPKC_KEY_ECC_SIGN || + keyClass == WC_ELSPKC_KEY_ECC_DH || + keyClass == WC_ELSPKC_KEY_ECC_SEED); + if ((keySz != 0 && keySz != ELS_KS_KEY_128_SZ && + keySz != ELS_KS_KEY_256_SZ) || + (isEcc && keySz == ELS_KS_KEY_128_SZ)) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + /* ECC keys and seeds are 256 bits, so always a pair; every other class + * follows the size, which is why the caller has to state it. */ + need = (isEcc || keySz == ELS_KS_KEY_256_SZ) ? 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; + } + /* Slots 0 and 1 are retention and slot 2 is hardware-out. */ + 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 && out->keyClass != WC_ELSPKC_KEY_KWK) { + ret = WC_NO_ERR_TRACE(BAD_STATE_E); + } + if (ret == 0) { + ret = ElsKsProps(out->slot, &prop); + } + if (ret == 0 && (!ElsSlotHolds(&prop, WC_ELSPKC_KEY_KWK) || + (needWrap && + (prop.word.value & MCUXCLELS_KEYPROPERTY_VALUE_KWK) == 0))) { + 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(); +} + +/* Check the imported key carries the target class's permission. Caller holds + * the lock. */ +static int ElsKsImportCheck(const wc_ElsPkc_KeyRef* target) +{ + mcuxClEls_KeyProp_t prop; + int ret; + + ret = ElsKsProps(target->slot, &prop); + if (ret == 0 && !ElsSlotHolds(&prop, target->keyClass)) { + ret = WC_NO_ERR_TRACE(BAD_STATE_E); + } + + return ret; +} + +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) { + /* The container's property word sets the type; this catches a blob + * aimed at a reference of another class before the unwrap. */ + ret = ElsKsCheckType(info->keystore.op.importWrapped.keyType, + target.keyClass); + } + /* Every ECC key is 256 bits. */ + if (ret == 0 && (target.keyClass == WC_ELSPKC_KEY_ECC_SIGN || + target.keyClass == WC_ELSPKC_KEY_ECC_DH || + target.keyClass == WC_ELSPKC_KEY_ECC_SEED) && + info->keystore.op.importWrapped.blobSz != + ELS_KS_BLOB_SZ(ELS_KS_KEY_256_SZ)) { + ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + /* attrs is unused; the property word inside the container decides. */ + if (ret == 0) { + ret = ElsKsWrapRef(info->keystore.op.importWrapped.wrapKeyRef, + info->keystore.op.importWrapped.wrapKeyRefSz, + 0, &wrap); + } + /* The container size gives the slot count. */ + if (ret == 0) { + ret = (info->keystore.op.importWrapped.blobSz == + ELS_KS_BLOB_SZ(ELS_KS_KEY_256_SZ)) + ? ElsSlotPairFree(target.slot) : ElsSlotFree(target.slot); + } + if (ret == 0) { + ret = ElsKsImportRun(info->keystore.op.importWrapped.blob, + info->keystore.op.importWrapped.blobSz, + wrap.slot, target.slot); + } + /* The container set the permissions; remove a key that disagrees. */ + if (ret == 0) { + ret = ElsKsImportCheck(&target); + if (ret != 0) { + (void)ElsSlotDeleteRun(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 (!ElsSlotHolds(&prop, key.keyClass)) { + 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: wc_KeyStore_ExportWrapped() defines this as + * success. */ + 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 && wrap.slot == key.slot) { + ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + 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, srcProp; + 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 runs the SP800-108 CMAC KDF and nothing else. */ + if (info->keystore.op.derive.kdfType != WC_KDF_TYPE_NONE) { + 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 && info->keystore.op.derive.keySz == 0 && + target.keyClass == WC_ELSPKC_KEY_ECC_SEED) { + keySz = ELS_KS_KEY_256_SZ; + } + /* An ECC key pair only comes out of key generation, and a seed is 256 + * bits. */ + if (ret == 0 && (target.keyClass == WC_ELSPKC_KEY_ECC_SIGN || + target.keyClass == WC_ELSPKC_KEY_ECC_DH || + (target.keyClass == WC_ELSPKC_KEY_ECC_SEED && + keySz != ELS_KS_KEY_256_SZ))) { + ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + if (ret == 0) { + ret = (keySz == ELS_KS_KEY_256_SZ) ? ElsSlotPairFree(target.slot) + : ElsSlotFree(target.slot); + } + if (ret == 0) { + ret = ElsKsProps(src.slot, &srcProp); + } + if (ret == 0 && target.keyClass == WC_ELSPKC_KEY_KWK && + !(info->keystore.op.derive.attrs & WC_KEYSTORE_ATTR_UNWRAP_ONLY) && + (srcProp.word.value & MCUXCLELS_KEYPROPERTY_VALUE_DUK) == 0) { + /* A key that can wrap may only descend from the die unique key; from + * any other parent the engine trips the tamper controller. */ + ret = WC_NO_ERR_TRACE(BAD_STATE_E); + } + 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; + } + /* The protection bits feed the derivation, and NXP's recipes (EdgeLock + * 2GO included) copy them from the parent. */ + prop.bits.upprot_priv = srcProp.bits.upprot_priv; + prop.bits.upprot_sec = srcProp.bits.upprot_sec; + 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); + /* Retention and hardware-out slots hold the ROM's keys. */ + if (ret == 0 && (prop.word.value & + MCUXCLELS_KEYPROPERTY_VALUE_GENERAL_PURPOSE_SLOT) == 0) { + ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + 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; + } + if (!ElsSlotBaseOk(&prop, key.keyClass)) { + ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + /* A stale reference must not delete a key that reused its slot. */ + else if (!ElsSlotHasClass(&prop, key.keyClass)) { + ret = WC_NO_ERR_TRACE(BAD_STATE_E); + } + else { + ret = ElsSlotDeleteRun(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 (!ElsSlotHolds(&prop, key.keyClass)) { + 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 */ + /* 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. */ @@ -3754,6 +4365,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 af470218b6a..1e85d13e9a6 100644 --- a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h +++ b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h @@ -43,6 +43,9 @@ #include #include +#ifdef WOLF_CRYPTO_CB_KEYSTORE + #include +#endif #ifdef HAVE_ECC #include #endif @@ -122,6 +125,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 +/* Pick a free slot, a pair for a 256-bit or ECC key; keySz 0 lets the device + * decide. MEMORY_E when full. Advisory: reserve and generate together. */ +WOLFSSL_API int wc_ElsPkc_ReserveSlot(byte keyClass, word32 keySz, + wc_ElsPkc_KeyRef* ref); + +#endif + #ifdef HAVE_ECC /* Bind key to the slot ref names, the port's devId and P-256. For key * generation the slot is empty until wc_ecc_make_key(). */ diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 18e24f6cd5c..94a5be79f12 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -158,10 +158,10 @@ config WOLFSSL_ELS_PKC 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. Neither is expressible in a settings file, so this option stays - available there; what it will not do is write WOLFSSL_ELS_PKC and - WOLF_CRYPTO_CB into a build whose settings file is authoritative. - Both go in the settings file, and user_settings.h fails the build if - either is missing. + available there; what it will not do is write WOLFSSL_ELS_PKC, + WOLF_CRYPTO_CB and WOLF_CRYPTO_CB_KEYSTORE into a build whose settings + file is authoritative. Those three go in the settings file, and + user_settings.h fails the build if they are missing. Not selectable in a TF-M build, where els_pkc does not create its McuxElsPkc CMake target. diff --git a/zephyr/user_settings.h b/zephyr/user_settings.h index 3c58119e2e1..7f133469663 100644 --- a/zephyr/user_settings.h +++ b/zephyr/user_settings.h @@ -292,6 +292,9 @@ extern "C" { #ifndef WOLF_CRYPTO_CB #define WOLF_CRYPTO_CB #endif + #ifndef WOLF_CRYPTO_CB_KEYSTORE + #define WOLF_CRYPTO_CB_KEYSTORE + #endif #endif /* ------------------------------------------------------------------------- */ @@ -804,8 +807,9 @@ extern "C" { /* The port is silent when its define is missing: els_pkc_port.c compiles to * nothing and no crypto callback is ever registered. */ #if defined(CONFIG_WOLFSSL_ELS_PKC) && \ - (!defined(WOLFSSL_ELS_PKC) || !defined(WOLF_CRYPTO_CB)) -#error "CONFIG_WOLFSSL_ELS_PKC=y needs the settings file to define WOLFSSL_ELS_PKC and WOLF_CRYPTO_CB" + (!defined(WOLFSSL_ELS_PKC) || !defined(WOLF_CRYPTO_CB) || \ + !defined(WOLF_CRYPTO_CB_KEYSTORE)) +#error "CONFIG_WOLFSSL_ELS_PKC=y needs the settings file to define WOLFSSL_ELS_PKC, WOLF_CRYPTO_CB and WOLF_CRYPTO_CB_KEYSTORE" #endif #endif /* CONFIG_WOLFSSL */ From 1dae4b99936d9f54956221998f61cf65bd129734 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 6/9] 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. Slot 0 has to hold an active key with CKDF and DUK before anything is derived from it, since deriving from an unfit parent resets the SoC instead of failing. 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. Slot 0 holds NXP_DIE_INT_MK_SK, and the macro naming it now says so. The header points at the README for the EdgeLock 2GO import keys, which descend from a different root and are not this key. Now that slot 0 has a name, derive's rule that only the die unique key may parent a wrap-capable KWK checks the slot as well as the DUK property bit, so another ROM key carrying DUK cannot reach the derivation that would trip the tamper controller. --- wolfcrypt/src/port/nxp/els_pkc_port.c | 68 ++++++++++++++++++++++- wolfssl/wolfcrypt/port/nxp/els_pkc_port.h | 3 + 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index 77462255ede..b5399ff7f60 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -3242,6 +3242,17 @@ static int ElsPkcEccVerify(const byte* sigDer, word32 sigLen, #define ELS_KS_KEY_128_SZ 16u #define ELS_KS_KEY_256_SZ 32u +/* NXP_DIE_INT_MK_SK, the die unique key the ROM leaves in slots 0 and 1. */ +#define ELS_DIE_INT_MK_SLOT 0 +#define ELS_DIE_INT_MK_PROPS (MCUXCLELS_KEYPROPERTY_VALUE_ACTIVE | \ + MCUXCLELS_KEYPROPERTY_VALUE_CKDF | \ + MCUXCLELS_KEYPROPERTY_VALUE_DUK) + +/* 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) { @@ -3704,7 +3715,8 @@ static int ElsKsDerive(wc_CryptoInfo* info) } if (ret == 0 && target.keyClass == WC_ELSPKC_KEY_KWK && !(info->keystore.op.derive.attrs & WC_KEYSTORE_ATTR_UNWRAP_ONLY) && - (srcProp.word.value & MCUXCLELS_KEYPROPERTY_VALUE_DUK) == 0) { + (src.slot != ELS_DIE_INT_MK_SLOT || + (srcProp.word.value & MCUXCLELS_KEYPROPERTY_VALUE_DUK) == 0)) { /* A key that can wrap may only descend from the die unique key; from * any other parent the engine trips the tamper controller. */ ret = WC_NO_ERR_TRACE(BAD_STATE_E); @@ -3741,6 +3753,60 @@ static int ElsKsDerive(wc_CryptoInfo* info) return ret; } +int wc_ElsPkc_DeriveDieKek(wc_ElsPkc_KeyRef* ref) +{ + mcuxClEls_KeyProp_t prop, src; + int ret; + + if (ref == NULL) { + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); + } + + ret = wc_ElsPkc_ReserveSlot(WC_ELSPKC_KEY_KWK, ELS_KS_KEY_256_SZ, ref); + + 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; + } + + /* ReserveSlot dropped the lock, so the pair may have been taken since. */ + ret = ElsSlotPairFree(ref->slot); + + /* The ROM's recipe exactly; the engine refuses properties the parent + * cannot confer. */ + if (ret == 0) { + ret = ElsKsProps(ELS_DIE_INT_MK_SLOT, &src); + } + /* Deriving from an unfit parent resets the SoC instead of failing. */ + if (ret == 0 && + (src.word.value & ELS_DIE_INT_MK_PROPS) != ELS_DIE_INT_MK_PROPS) { + ret = WC_NO_ERR_TRACE(BAD_STATE_E); + } + if (ret == 0) { + prop.word.value = 0u; + prop.bits.upprot_priv = src.bits.upprot_priv; + prop.bits.upprot_sec = src.bits.upprot_sec; + prop.bits.ksize = MCUXCLELS_KEYPROPERTY_KEY_SIZE_256; + prop.word.value |= MCUXCLELS_KEYPROPERTY_VALUE_KWK; + + ret = ElsKsDeriveRun(ELS_DIE_INT_MK_SLOT, ref->slot, prop, + elsDieKekDeriv); + } + + ElsUnlock(); + + if (ret != 0) { + 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 1e85d13e9a6..cc84f8581fc 100644 --- a/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h +++ b/wolfssl/wolfcrypt/port/nxp/els_pkc_port.h @@ -131,6 +131,9 @@ WOLFSSL_API int wc_ElsPkc_ParseKeyRef(const byte* in, word32 inSz, WOLFSSL_API int wc_ElsPkc_ReserveSlot(byte keyClass, word32 keySz, wc_ElsPkc_KeyRef* ref); +/* Derive NXP_DIE_KEK_SK into a free slot pair; ref receives a KWK + * reference. Delete it with wc_KeyStore_Delete(). */ +WOLFSSL_API int wc_ElsPkc_DeriveDieKek(wc_ElsPkc_KeyRef* ref); #endif #ifdef HAVE_ECC From 5232c1046c4fec6244778bba8f3d4ddae17b2e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 11 Sep 2026 10:52:28 +0200 Subject: [PATCH 7/9] nxp: take a bare key id and any digest length in the EdgeLock ECDSA path An ecc_key built by wc_ecc_init_id() carries a key id and nothing else: no curve, because an id says nothing about one. ElsEccCurveOk() required key->dp->id == ECC_SECP256R1, so the port declined every signature from such a key, the operation fell through to the PKC path that has no private scalar, and the handshake failed with ECC_BAD_ARG_E. That is the path wolfSSL_CTX_use_PrivateKey_Id() takes, so an application could reference a slot key from TLS but never sign with it. ElsEccPinCurve() now pins P-256 when the key has no curve, which the key reference already implies, the same way wolfPKCS11's Pkcs11GetEccParams() fills the curve in from the token. Once the key reference parses, nothing declines: the PKC and software paths would sign with the zero scalar the ecc_key holds, so a wrong key class, a curve other than P-256, a curve-table miss or an RFC 6979 or set-k request, which ELS cannot honour, is returned as an error. ElsEccSign() and ElsEccVerify() also rejected any digest that was not exactly 32 bytes, on the grounds that padding or truncating would be guesswork. It is not: FIPS 186-4 6.4 and RFC 6979 bits2int take the leftmost min(N, outlen) bits, which over a 256-bit order is a plain byte truncation with no bit shift, and ecc.c does exactly that at its own truncation sites. ElsEccDigest() now truncates a longer digest and zero-extends a shorter one, so SHA-384 and SHA-512 ECDSA reach the hardware instead of being declined. Both functions gain a single exit, so the error returns after the digest buffer is populated reach the ForceZero the success path already performed. --- wolfcrypt/src/port/nxp/README.md | 10 +++-- wolfcrypt/src/port/nxp/els_pkc_port.c | 59 +++++++++++++++++++++------ 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 4f9b50a2e6d..66895ce91e7 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -655,7 +655,7 @@ Keys that live in the ELS key store are referenced rather than exported. `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. +ECC the helper additionally pins the curve to P-256. ### Hardware behaviour worth knowing @@ -696,9 +696,11 @@ finished before the last `wolfCrypt_Cleanup()`: with the device gone, wolfCrypt carries on in software from the engine's state, and a hash returns a wrong digest. -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 - but the PKC then serves it, so it stays in hardware. +ELS takes a 32-byte ECDSA digest, so a longer one is truncated and a shorter +one zero-extended, as FIPS 186-4 6.4 does for a 256-bit order. A SHA-1 or +SHA-384 signature with a slot key therefore stays on ELS. ELS draws its own +nonce, so an RFC 6979 or set-k signature with a slot key fails with +`BAD_STATE_E`. **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 b5399ff7f60..d859f2dd834 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -1451,6 +1451,34 @@ static int ElsEccCurveOk(const ecc_key* key) key->dp->id == ECC_SECP256R1); } +#if defined(HAVE_ECC_SIGN) || defined(HAVE_ECC_VERIFY) +/* ELS takes a fixed 32-byte digest; a 256-bit order makes the ECDSA + * conversion a plain truncate-or-zero-extend (FIPS 186-4 6.4). */ +static void ElsEccDigest(const byte* in, word32 inlen, byte* out) +{ + if (inlen >= ELS_ECC_COORD_SZ) { + XMEMCPY(out, in, ELS_ECC_COORD_SZ); + } + else { + XMEMSET(out, 0, ELS_ECC_COORD_SZ - inlen); + XMEMCPY(out + ELS_ECC_COORD_SZ - inlen, in, inlen); + } +} +#endif /* HAVE_ECC_SIGN || HAVE_ECC_VERIFY */ + +#ifdef HAVE_ECC_SIGN +/* A key that arrived as a bare id has no curve, and an ELS ECC reference names + * a slot the hardware only ever fills with P-256. */ +static int ElsEccPinCurve(ecc_key* key) +{ + if (key->dp != NULL) { + return 0; + } + + return wc_ecc_set_curve(key, ELS_ECC_COORD_SZ, ECC_SECP256R1); +} +#endif /* HAVE_ECC_SIGN */ + int wc_ElsPkc_EccUseSlot(ecc_key* key, const wc_ElsPkc_KeyRef* ref, void* heap, int devId) { @@ -1668,16 +1696,19 @@ static int ElsEccSign(const byte* in, word32 inlen, byte* out, word32* outlen, if (ret != 0) { return ret; } - if (!ElsEccCurveOk(key) || ref.keyClass != WC_ELSPKC_KEY_ECC_SIGN) { - return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + /* Past this point a decline would sign with ecc_key's zero scalar. */ + if (ref.keyClass != WC_ELSPKC_KEY_ECC_SIGN) { + return WC_NO_ERR_TRACE(BAD_STATE_E); } - /* 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); + ret = ElsEccPinCurve(key); + if (ret != 0) { + return ret; + } + if (!ElsEccCurveOk(key) || ElsEccCustomNonce(key)) { + return WC_NO_ERR_TRACE(BAD_STATE_E); } - XMEMCPY(hash, in, ELS_ECC_COORD_SZ); + ElsEccDigest(in, inlen, hash); opt.word.value = 0u; opt.bits.echashchl = MCUXCLELS_ECC_HASHED; @@ -1685,7 +1716,7 @@ static int ElsEccSign(const byte* in, word32 inlen, byte* out, word32* outlen, ret = ElsLock(); if (ret != 0) { - return ret; + goto out; } ret = ElsCheckSlot(&ref, WC_ELSPKC_KEY_ECC_SIGN); @@ -1702,6 +1733,7 @@ static int ElsEccSign(const byte* in, word32 inlen, byte* out, word32* outlen, sig + ELS_ECC_COORD_SZ, ELS_ECC_COORD_SZ); } +out: ForceZero(hash, sizeof(hash)); ForceZero(sig, sizeof(sig)); @@ -1726,8 +1758,7 @@ static int ElsEccVerify(const byte* sig, word32 siglen, const byte* digest, if (sig == NULL || digest == NULL || res == NULL) { return WC_NO_ERR_TRACE(BAD_FUNC_ARG); } - if (!ElsEccCurveOk(key) || hashlen != ELS_ECC_COORD_SZ || - !ElsEccSigDerOk(sig, siglen) || + if (!ElsEccCurveOk(key) || !ElsEccSigDerOk(sig, siglen) || (key->type != ECC_PUBLICKEY && key->type != ECC_PRIVATEKEY)) { return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); } @@ -1762,14 +1793,14 @@ static int ElsEccVerify(const byte* sig, word32 siglen, const byte* digest, ELS_ECC_COORD_SZ) != MP_OKAY) { return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); } - XMEMCPY(hash, digest, ELS_ECC_COORD_SZ); + ElsEccDigest(digest, hashlen, hash); opt.word.value = 0u; opt.bits.echashchl = MCUXCLELS_ECC_HASHED; ret = ElsLock(); if (ret != 0) { - return ret; + goto out; } MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(r, t, mcuxClEls_EccVerify_Async( @@ -1777,7 +1808,8 @@ static int ElsEccVerify(const byte* sig, word32 siglen, const byte* digest, if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_EccVerify_Async) != t) || (MCUXCLELS_STATUS_OK_WAIT != r)) { ElsUnlock(); - return WC_NO_ERR_TRACE(WC_HW_E); + ret = WC_NO_ERR_TRACE(WC_HW_E); + goto out; } MCUX_CSSL_FP_FUNCTION_CALL_END(); @@ -1790,6 +1822,7 @@ static int ElsEccVerify(const byte* sig, word32 siglen, const byte* digest, *res = (wc_ConstantCompare(rCalc, sigAndPub, ELS_ECC_COORD_SZ) == 0); } +out: ForceZero(hash, sizeof(hash)); ForceZero(rCalc, sizeof(rCalc)); From 99ae282d2dae5ede40a4b4ee00c69e58aaf80efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 7 Oct 2026 17:02:56 +0200 Subject: [PATCH 8/9] nxp: cover the PKC and key store in the MCUXpresso and Zephyr setups The MCUXpresso user_settings.h and README now turn on ECC, X25519 and RSA with the SP math they need and ECC timing resistance, and the key store operations, and the autoconf example passes the matching options. The frdm_rw612 test and benchmark scenarios add P-384, P-521 and X25519, the curves the PKC offloads beyond the module's default P-256. --- IDE/MCUEXPRESSO/RW612/README.md | 8 ++++---- IDE/MCUEXPRESSO/RW612/user_settings.h | 18 ++++++++++++++++++ zephyr/samples/wolfssl_benchmark/sample.yaml | 4 ++++ zephyr/samples/wolfssl_test/sample.yaml | 5 +++++ 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/IDE/MCUEXPRESSO/RW612/README.md b/IDE/MCUEXPRESSO/RW612/README.md index 4c62ed2a468..280fb96cfe9 100644 --- a/IDE/MCUEXPRESSO/RW612/README.md +++ b/IDE/MCUEXPRESSO/RW612/README.md @@ -1,6 +1,6 @@ -# wolfSSL on NXP RW612 with EdgeLock (ELS) +# 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. +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. @@ -57,7 +57,7 @@ The same result from a source tarball or checkout. Note that no CI job builds th ./autogen.sh ./configure --host=arm-none-eabi --disable-shared --enable-static \ --enable-cryptonly --enable-cryptocb \ - --enable-aesgcm --enable-cmac \ + --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 \ @@ -102,4 +102,4 @@ A hardware *failure* is treated differently on purpose: the port reports the err `wolfCrypt_Cleanup()` unregisters the callback and closes the port, keeping its mutex; `wc_ElsPkc_Init()` re-registers it if you need the port back afterwards. Finish or free every hash, CMAC and AES context the port has taken over before the last `wolfCrypt_Cleanup()`: with the device gone, wolfCrypt continues it in software from the engine's state, and a hash returns a wrong digest. -Keys resident in the ELS key store are referenced rather than exported: attach a reference to a key with `wc_ElsPkc_AesUseSlot()` or `wc_ElsPkc_CmacUseSlot()`. Those are the entry point - `wc_ElsPkc_MakeKeyRef()` is the primitive underneath them. See the port header for the reference format. +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 to P-256. 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 index 5a51dc34cfd..d32b43c0936 100644 --- a/IDE/MCUEXPRESSO/RW612/user_settings.h +++ b/IDE/MCUEXPRESSO/RW612/user_settings.h @@ -51,6 +51,7 @@ extern "C" { #define WOLFSSL_ELS_PKC #define WOLF_CRYPTO_CB +#define WOLF_CRYPTO_CB_KEYSTORE /* Seed wolfCrypt's DRBG from the ELS hardware DRBG. */ #define HAVE_HASHDRBG @@ -68,6 +69,23 @@ extern "C" { #define WOLFSSL_AES_COUNTER #define WOLFSSL_CMAC +#define HAVE_ECC +#define ECC_TIMING_RESISTANT +#define ECC_USER_CURVES +#define HAVE_ECC256 +#define HAVE_ECC384 +#define HAVE_CURVE25519 +/* No Ed25519: the PKC does not serve EdDSA. */ + +/* SP math is per curve: 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 + #define NO_DSA #define NO_RC4 #define NO_MD4 diff --git a/zephyr/samples/wolfssl_benchmark/sample.yaml b/zephyr/samples/wolfssl_benchmark/sample.yaml index 525dfb2069a..e480745ca8e 100644 --- a/zephyr/samples/wolfssl_benchmark/sample.yaml +++ b/zephyr/samples/wolfssl_benchmark/sample.yaml @@ -27,6 +27,10 @@ tests: # 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 diff --git a/zephyr/samples/wolfssl_test/sample.yaml b/zephyr/samples/wolfssl_test/sample.yaml index 3698d56b184..6a3cb039f41 100644 --- a/zephyr/samples/wolfssl_test/sample.yaml +++ b/zephyr/samples/wolfssl_test/sample.yaml @@ -157,6 +157,11 @@ tests: # 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: From 54fd40563c7ba8b99ddbed30e76075f829a9c68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 26 Oct 2026 10:49:27 +0100 Subject: [PATCH 9/9] nxp: validate EdgeLock slot keys in wc_ecc_check_key A P-256 key generated in or referenced from an ELS slot holds no scalar in its ecc_key, so the software check says nothing about the slot, and a key bound with wc_ElsPkc_EccUseSlot() has no public point either and fails it outright. The port now answers WC_PK_TYPE_EC_CHECK_PUB_KEY for keys that carry a slot reference. A public point, when the key has one, goes through wc_ecc_point_is_on_curve(), which also bounds both coordinates below p; P-256 has cofactor 1, so that covers the order check as well. With checkPriv, the slot has to be occupied with the usage bit its class claims, and a signing slot with a public point also signs a fixed digest and verifies the result against the public point. That is the only way to show the slot and the point belong together, since ELS cannot recompute the public key of a stored key. A key agreement slot cannot sign and gets the slot check alone, at the cost of one ELS sign and one verify for a signing slot. The self-test signs whatever deterministic or set-k state the key is in, since that state concerns the caller's signatures, not the test's. With no public point and no checkPriv the slot has nothing to answer, so that case goes back to software. If the self-test cannot run at all, the check fails with ECC_PRIV_KEY_E instead of declining, since a decline would let wc_ecc_check_key() fall back to a software check that cannot see the slot. Keys without a slot reference still go to software. --- wolfcrypt/src/port/nxp/README.md | 6 ++ wolfcrypt/src/port/nxp/els_pkc_port.c | 93 ++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 66895ce91e7..78dcab2bb00 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -702,6 +702,12 @@ SHA-384 signature with a slot key therefore stays on ELS. ELS draws its own nonce, so an RFC 6979 or set-k signature with a slot key fails with `BAD_STATE_E`. +`wc_ecc_check_key()` on a slot key checks its public point, if it has one, in +software and that the slot holds a key of the referenced class. A signing slot +with a public point also signs and verifies a fixed digest, which shows the +two belong together; a key agreement slot cannot sign and gets the slot check +only. + **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. A key in a slot has no software copy, so with one a partial block diff --git a/wolfcrypt/src/port/nxp/els_pkc_port.c b/wolfcrypt/src/port/nxp/els_pkc_port.c index d859f2dd834..5198fe25401 100644 --- a/wolfcrypt/src/port/nxp/els_pkc_port.c +++ b/wolfcrypt/src/port/nxp/els_pkc_port.c @@ -1680,8 +1680,9 @@ static int ElsEccSignRun(mcuxClEls_EccSignOption_t opt, byte slot, return ElsWait(); } +/* The check-key self-test passes anyNonce to ignore the key's nonce mode. */ static int ElsEccSign(const byte* in, word32 inlen, byte* out, word32* outlen, - ecc_key* key) + ecc_key* key, int anyNonce) { mcuxClEls_EccSignOption_t opt; wc_ElsPkc_KeyRef ref; @@ -1704,7 +1705,7 @@ static int ElsEccSign(const byte* in, word32 inlen, byte* out, word32* outlen, if (ret != 0) { return ret; } - if (!ElsEccCurveOk(key) || ElsEccCustomNonce(key)) { + if (!ElsEccCurveOk(key) || (!anyNonce && ElsEccCustomNonce(key))) { return WC_NO_ERR_TRACE(BAD_STATE_E); } @@ -1831,6 +1832,85 @@ static int ElsEccVerify(const byte* sig, word32 siglen, const byte* digest, #endif /* HAVE_ECC_VERIFY */ +#ifdef HAVE_ECC_CHECK_KEY +#if defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY) +static const byte elsEccPctDigest[ELS_ECC_COORD_SZ] = { + 0x45, 0x4c, 0x53, 0x2d, 0x50, 0x43, 0x54, 0x2d, + 0x45, 0x4c, 0x53, 0x2d, 0x50, 0x43, 0x54, 0x2d, + 0x45, 0x4c, 0x53, 0x2d, 0x50, 0x43, 0x54, 0x2d, + 0x45, 0x4c, 0x53, 0x2d, 0x50, 0x43, 0x54, 0x2d +}; + +/* Sign in the slot and verify against key->pubkey. */ +static int ElsEccPairwiseTest(ecc_key* key) +{ + byte der[ECC_MAX_SIG_SIZE]; + word32 derSz = sizeof(der); + int res = 0; + int ret; + + ret = ElsEccSign(elsEccPctDigest, sizeof(elsEccPctDigest), der, &derSz, + key, 1); + if (ret == 0) { + ret = ElsEccVerify(der, derSz, elsEccPctDigest, + sizeof(elsEccPctDigest), &res, key); + } + /* A decline would let wc_ecc_check_key() skip the pairing in software. */ + if ((ret == 0 && res != 1) || + ret == WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + ret = WC_NO_ERR_TRACE(ECC_PRIV_KEY_E); + } + + return ret; +} +#endif + +/* wc_ecc_check_key() for a slot key, whose ecc_key holds no scalar. */ +static int ElsEccCheckKey(ecc_key* key, int checkPriv) +{ + wc_ElsPkc_KeyRef ref; + int havePub; + int ret; + + ret = ElsEccRef(key, &ref); + if (ret != 0) { + return ret; + } + if ((ref.keyClass != WC_ELSPKC_KEY_ECC_SIGN && + ref.keyClass != WC_ELSPKC_KEY_ECC_DH) || !ElsEccCurveOk(key)) { + return WC_NO_ERR_TRACE(BAD_STATE_E); + } + + /* P-256 has cofactor 1, so a point on the curve also has order n. */ + havePub = (key->type == ECC_PUBLICKEY || key->type == ECC_PRIVATEKEY); + if (!havePub && !checkPriv) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (havePub) { + ret = wc_ecc_point_is_on_curve(&key->pubkey, key->idx); + } + if (ret != 0 || !checkPriv) { + return ret; + } + + ret = ElsLock(); + if (ret != 0) { + return ret; + } + ret = ElsCheckSlot(&ref, ref.keyClass); + ElsUnlock(); + +#if defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY) + /* A key agreement slot cannot sign, so only the slot check covers it. */ + if (ret == 0 && havePub && ref.keyClass == WC_ELSPKC_KEY_ECC_SIGN) { + ret = ElsEccPairwiseTest(key); + } +#endif + + return ret; +} +#endif /* HAVE_ECC_CHECK_KEY */ + #endif /* HAVE_ECC */ /* The PKC workarea is a fixed hardware region at PKC_RAM_ADDR shared with @@ -4425,7 +4505,8 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) /* ELS first; it declines anything but a P-256 slot key. */ ret = ElsEccSign(info->pk.eccsign.in, info->pk.eccsign.inlen, info->pk.eccsign.out, - info->pk.eccsign.outlen, info->pk.eccsign.key); + info->pk.eccsign.outlen, info->pk.eccsign.key, + 0); if (ret == WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { ret = ElsPkcEccSign(info->pk.eccsign.in, info->pk.eccsign.inlen, info->pk.eccsign.out, @@ -4456,6 +4537,12 @@ int wc_ElsPkc_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) info->pk.eccverify.key); } break; + #endif + #if defined(HAVE_ECC) && defined(HAVE_ECC_CHECK_KEY) + case WC_PK_TYPE_EC_CHECK_PUB_KEY: + ret = ElsEccCheckKey(info->pk.ecc_check_pub.key, + info->pk.ecc_check_pub.checkPriv); + break; #endif /* No WC_PK_TYPE_ECDH: ELS leaves the agreed secret in a slot. */ default: