From 3d10969c450562c3c945929a837aae1656f459c9 Mon Sep 17 00:00:00 2001 From: night1rider Date: Fri, 4 Sep 2026 15:56:22 -0600 Subject: [PATCH 1/9] ECIES: give the encrypt context its own device id Add wc_ecc_ctx_set_dev_id() and _get_dev_id(); the key devId is unused. --- ChangeLog.md | 14 ++++++ wolfcrypt/src/cryptocb.c | 14 +++--- wolfcrypt/src/ecc.c | 96 +++++++++++++++++++++++++++++------- wolfssl/wolfcrypt/cryptocb.h | 14 ++++-- wolfssl/wolfcrypt/ecc.h | 7 +++ 5 files changed, 115 insertions(+), 30 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index f3107ffb0bf..44595c1b2f9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,16 @@ ## Behavioral Changes +* **Behavioral change (ECIES no longer takes its device from the key)**: + `wc_ecc_encrypt()`, `wc_ecc_encrypt_ex()` and `wc_ecc_decrypt()` used to read + the crypto callback device from `privKey->devId`. They now read it only from + the `ecEncCtx`, set with the new `wc_ecc_ctx_set_dev_id()`. A context that + was never given a device, or a NULL context, runs ECIES in software, even + when the key was opened with a device. This covers the whole-operation + callback and the KDF, cipher and MAC steps of the software path. ECDH is + not affected and still uses the key's device. Callers that relied on the + key's device for ECIES must call `wc_ecc_ctx_set_dev_id()` on each context. + * **Behavioral change (`wc_PufReadSram` health tests the raw SRAM readout)**: the raw readout is now health tested before the context accepts it, and a readout that cannot be SRAM power-on noise is rejected with `PUF_READ_E` @@ -189,6 +199,10 @@ ## New Features +* Added `wc_ecc_ctx_set_dev_id()` and `wc_ecc_ctx_get_dev_id()` (WOLF_CRYPTO_CB + builds) to pick the crypto callback device an ECIES context runs on. The + value is kept across `wc_ecc_ctx_reset()`. + * Added Argon2 (RFC 9106) password hashing with all three variants - Argon2d, Argon2i and Argon2id - via `--enable-argon2`. Only version 0x13 is implemented. Provides the one-shot `wc_Argon2()`/`wc_Argon2_ex()` and a reusable context API (`wc_Argon2Init`/`wc_Argon2SetParams`/`wc_Argon2DeriveTag`/`wc_Argon2Free`, plus `wc_Argon2New`/`wc_Argon2Delete` unless `WC_NO_CONSTRUCTORS`) that allocates the memory block array once for applications deriving many tags. `--enable-argon2-threads` fills the segments of a slice in parallel, which does not change the derived tag: the one-shot functions use a thread per lane, and the context API takes a count from `wc_Argon2SetThreads()`. by @SparkiDev ## Fixes diff --git a/wolfcrypt/src/cryptocb.c b/wolfcrypt/src/cryptocb.c index 3811c85be20..62a37e0afbf 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -1142,7 +1142,7 @@ int wc_CryptoCb_EccCheckPubKey(ecc_key* key, int checkOrder, int checkPriv) #endif /* HAVE_ECC_CHECK_KEY */ #ifdef HAVE_ECC_ENCRYPT -int wc_CryptoCb_EciesEncrypt(ecc_key* privKey, ecc_key* pubKey, +int wc_CryptoCb_EciesEncrypt(int devId, ecc_key* privKey, ecc_key* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, int compressed) { @@ -1152,8 +1152,9 @@ int wc_CryptoCb_EciesEncrypt(ecc_key* privKey, ecc_key* pubKey, if (privKey == NULL) return ret; - /* locate registered callback */ - dev = wc_CryptoCb_FindDevice(privKey->devId, WC_ALGO_TYPE_PK); + /* find the registered callback. The device comes from the ECIES + * context, not from privKey->devId. */ + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_PK); if (dev && dev->cb) { wc_CryptoInfo cryptoInfo; XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); @@ -1174,7 +1175,7 @@ int wc_CryptoCb_EciesEncrypt(ecc_key* privKey, ecc_key* pubKey, return wc_CryptoCb_TranslateErrorCode(ret); } -int wc_CryptoCb_EciesDecrypt(ecc_key* privKey, ecc_key* pubKey, +int wc_CryptoCb_EciesDecrypt(int devId, ecc_key* privKey, ecc_key* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx) { int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); @@ -1183,8 +1184,9 @@ int wc_CryptoCb_EciesDecrypt(ecc_key* privKey, ecc_key* pubKey, if (privKey == NULL) return ret; - /* locate registered callback */ - dev = wc_CryptoCb_FindDevice(privKey->devId, WC_ALGO_TYPE_PK); + /* find the registered callback. The device comes from the ECIES + * context, not from privKey->devId. */ + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_PK); if (dev && dev->cb) { wc_CryptoInfo cryptoInfo; XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 37b64a89efe..d92357d9271 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -15097,6 +15097,13 @@ struct ecEncCtx { word32 kdfSaltSz; /* size of kdfSalt */ word32 kdfInfoSz; /* size of kdfInfo */ word32 macSaltSz; /* size of macSalt */ +#ifdef WOLF_CRYPTO_CB + /* Device that ECIES runs on for this context. It is NOT copied from the + * ECC key. Until wc_ecc_ctx_set_dev_id() is called it stays at + * INVALID_DEVID and ECIES runs in software, even if the key has a + * device. */ + int devId; +#endif void* heap; /* heap hint for memory used */ byte clientSalt[EXCHANGE_SALT_SZ]; /* for msg exchange */ byte serverSalt[EXCHANGE_SALT_SZ]; /* for msg exchange */ @@ -15199,6 +15206,32 @@ int wc_ecc_ctx_get_rng(ecEncCtx* ctx, WC_RNG** rng) return 0; } + +/* Pick the device that ECIES uses for this context. The context does not + * copy the ECC key's devId, so until this is called ECIES runs in software, + * even for a key that has a device. Pass INVALID_DEVID to go back to + * software. The value is kept across wc_ecc_ctx_reset(). */ +int wc_ecc_ctx_set_dev_id(ecEncCtx* ctx, int devId) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + + ctx->devId = devId; + + return 0; +} + +/* Read back the device set above. Callback code can use this to learn + * which device it was called for. */ +int wc_ecc_ctx_get_dev_id(ecEncCtx* ctx, int* devId) +{ + if (ctx == NULL || devId == NULL) + return BAD_FUNC_ARG; + + *devId = ctx->devId; + + return 0; +} #endif /* WOLF_CRYPTO_CB */ @@ -15408,6 +15441,12 @@ static void ecc_ctx_init(ecEncCtx* ctx, int flags, WC_RNG* rng) ctx->macAlgo = ecHMAC_SHA256; ctx->protocol = (byte)flags; ctx->rng = rng; + #ifdef WOLF_CRYPTO_CB + /* The XMEMSET above leaves this at 0, and 0 is a real devId. Start + * in software; the caller picks a device with + * wc_ecc_ctx_set_dev_id(). */ + ctx->devId = INVALID_DEVID; + #endif if (flags == REQ_RESP_CLIENT) ctx->cliSt = ecCLI_INIT; @@ -15422,15 +15461,25 @@ WOLFSSL_ABI int wc_ecc_ctx_reset(ecEncCtx* ctx, WC_RNG* rng) { void* heap; +#ifdef WOLF_CRYPTO_CB + int devId; +#endif if (ctx == NULL || rng == NULL) return BAD_FUNC_ARG; /* ecc_ctx_init clears the whole context, so carry the heap hint over it. - * The context has to be freed to the heap it was allocated from. */ + * The context has to be freed to the heap it was allocated from. Keep + * the device too: reset means "reuse this context", so it must stay. */ heap = ctx->heap; +#ifdef WOLF_CRYPTO_CB + devId = ctx->devId; +#endif ecc_ctx_init(ctx, ctx->protocol, rng); ctx->heap = heap; +#ifdef WOLF_CRYPTO_CB + ctx->devId = devId; +#endif return ecc_ctx_set_salt(ctx, ctx->protocol); } @@ -15445,6 +15494,11 @@ ecEncCtx* wc_ecc_ctx_new_ex(int flags, WC_RNG* rng, void* heap) if (ctx) { ctx->protocol = (byte)flags; ctx->heap = heap; + #ifdef WOLF_CRYPTO_CB + /* wc_ecc_ctx_reset() below keeps devId across ecc_ctx_init(), so it + * needs a real value first. This memory starts out uninitialized. */ + ctx->devId = INVALID_DEVID; + #endif } ret = wc_ecc_ctx_reset(ctx, rng); @@ -15674,21 +15728,23 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, byte* encKey = NULL; byte* encIv = NULL; byte* macKey = NULL; - /* devId to hand the DEM AES/HMAC primitives; ecc_key only carries a devId - * field with PLUTON_CRYPTO_ECC or WOLF_CRYPTO_CB, so default to INVALID. */ + /* devId for the ECIES crypto callback and for the AES/HMAC steps. It + * comes only from the context, never from privKey->devId. No context, or + * no wc_ecc_ctx_set_dev_id() call, means software. */ int eciesDevId = INVALID_DEVID; if (privKey == NULL || pubKey == NULL || msg == NULL || out == NULL || outSz == NULL) return BAD_FUNC_ARG; -#if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) - eciesDevId = privKey->devId; -#endif - #ifdef WOLF_CRYPTO_CB + /* Read this before ctx is swapped for the local default below. A NULL + * context has no device and stays INVALID_DEVID. */ + if (ctx != NULL) + eciesDevId = ctx->devId; + #ifndef WOLF_CRYPTO_CB_FIND - if (privKey->devId != INVALID_DEVID) + if (eciesDevId != INVALID_DEVID) #endif { /* Snapshot single-use state so we can tell whether the callback handled @@ -15696,8 +15752,8 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, * (which advances the state itself, below). */ byte cliStBefore = (ctx != NULL) ? ctx->cliSt : 0; byte srvStBefore = (ctx != NULL) ? ctx->srvSt : 0; - ret = wc_CryptoCb_EciesEncrypt(privKey, pubKey, msg, msgSz, out, outSz, - ctx, compressed); + ret = wc_CryptoCb_EciesEncrypt(eciesDevId, privKey, pubKey, msg, msgSz, + out, outSz, ctx, compressed); if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { /* Pure-hardware service left the state alone; enforce single-use * here so the ctx can't be reused (nonce reuse for static-nonce @@ -16127,8 +16183,9 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, byte* encKey = NULL; const byte* encIv = NULL; byte* macKey = NULL; - /* devId to hand the DEM AES/HMAC primitives; ecc_key only carries a devId - * field with PLUTON_CRYPTO_ECC or WOLF_CRYPTO_CB, so default to INVALID. */ + /* devId for the ECIES crypto callback and for the AES/HMAC steps. It + * comes only from the context, never from privKey->devId. No context, or + * no wc_ecc_ctx_set_dev_id() call, means software. */ int eciesDevId = INVALID_DEVID; @@ -16139,13 +16196,14 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, return BAD_FUNC_ARG; #endif -#if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) - eciesDevId = privKey->devId; -#endif - #ifdef WOLF_CRYPTO_CB + /* Read this before ctx is swapped for the local default below. A NULL + * context has no device and stays INVALID_DEVID. */ + if (ctx != NULL) + eciesDevId = ctx->devId; + #ifndef WOLF_CRYPTO_CB_FIND - if (privKey->devId != INVALID_DEVID) + if (eciesDevId != INVALID_DEVID) #endif { /* Snapshot single-use state so we can tell whether the callback handled @@ -16153,8 +16211,8 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, * (which advances the state itself, below). */ byte cliStBefore = (ctx != NULL) ? ctx->cliSt : 0; byte srvStBefore = (ctx != NULL) ? ctx->srvSt : 0; - ret = wc_CryptoCb_EciesDecrypt(privKey, pubKey, msg, msgSz, out, outSz, - ctx); + ret = wc_CryptoCb_EciesDecrypt(eciesDevId, privKey, pubKey, msg, msgSz, + out, outSz, ctx); if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { /* Pure-hardware service left the state alone; enforce single-use * here. A re-entrant software callback already advanced it. */ diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index 775084f8d7f..fadd26825f6 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -1064,11 +1064,15 @@ WOLFSSL_LOCAL int wc_CryptoCb_EccCheckPubKey(ecc_key* key, int checkOrder, int checkPriv); #endif #ifdef HAVE_ECC_ENCRYPT -WOLFSSL_LOCAL int wc_CryptoCb_EciesEncrypt(ecc_key* privKey, ecc_key* pubKey, - const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, - int compressed); -WOLFSSL_LOCAL int wc_CryptoCb_EciesDecrypt(ecc_key* privKey, ecc_key* pubKey, - const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx); +/* devId is the ECIES context's device (see wc_ecc_ctx_set_dev_id), not + * privKey->devId. A key with a device does not by itself send ECIES to + * that device. INVALID_DEVID means software. */ +WOLFSSL_LOCAL int wc_CryptoCb_EciesEncrypt(int devId, ecc_key* privKey, + ecc_key* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, + ecEncCtx* ctx, int compressed); +WOLFSSL_LOCAL int wc_CryptoCb_EciesDecrypt(int devId, ecc_key* privKey, + ecc_key* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, + ecEncCtx* ctx); #endif #endif /* HAVE_ECC */ diff --git a/wolfssl/wolfcrypt/ecc.h b/wolfssl/wolfcrypt/ecc.h index d3785827f40..6664c34d334 100644 --- a/wolfssl/wolfcrypt/ecc.h +++ b/wolfssl/wolfcrypt/ecc.h @@ -1139,6 +1139,13 @@ WOLFSSL_API int wc_ecc_ctx_get_protocol(ecEncCtx* ctx, int* protocol); WOLFSSL_API int wc_ecc_ctx_get_rng(ecEncCtx* ctx, WC_RNG** rng); +/* Device that ECIES runs on. The context never copies the ECC key's devId, + * so without wc_ecc_ctx_set_dev_id() ECIES runs in software. The value is + * kept across wc_ecc_ctx_reset(). */ +WOLFSSL_API +int wc_ecc_ctx_set_dev_id(ecEncCtx* ctx, int devId); +WOLFSSL_API +int wc_ecc_ctx_get_dev_id(ecEncCtx* ctx, int* devId); #endif /* WOLF_CRYPTO_CB */ WOLFSSL_API const byte* wc_ecc_ctx_get_own_salt(ecEncCtx* ctx); From 38deb60b79ba37c3dde75e8a81d2ee26c1f5bdf7 Mon Sep 17 00:00:00 2001 From: night1rider Date: Fri, 4 Sep 2026 16:07:11 -0600 Subject: [PATCH 2/9] ECIES: set the context device id in tests, benchmark and docs Also add a test that checks the key's devId does not pick the device. --- doc/dox_comments/header_files/ecc.h | 81 ++++ tests/api/test_ecc.c | 414 +++++++++++++++++- tests/api/test_ecc.h | 4 + tests/unit-mcdc/test_cryptocb_whitebox.c | 24 +- wolfcrypt/benchmark/benchmark.c | 16 + .../src/port/xilinx/versal_gen2_asu/README.md | 11 +- wolfcrypt/test/test.c | 120 ++++- 7 files changed, 635 insertions(+), 35 deletions(-) diff --git a/doc/dox_comments/header_files/ecc.h b/doc/dox_comments/header_files/ecc.h index a0210c23ae0..62ba3c46ad1 100644 --- a/doc/dox_comments/header_files/ecc.h +++ b/doc/dox_comments/header_files/ecc.h @@ -1851,7 +1851,11 @@ void wc_ecc_ctx_free(ecEncCtx* ctx); // do more secure communication \endcode + \note The device id set with wc_ecc_ctx_set_dev_id() (WOLF_CRYPTO_CB + builds) is kept across the reset, like the heap hint. + \sa wc_ecc_ctx_new + \sa wc_ecc_ctx_set_dev_id */ int wc_ecc_ctx_reset(ecEncCtx* ctx, WC_RNG* rng); /* reset for use again w/o alloc/free */ @@ -1888,6 +1892,68 @@ int wc_ecc_ctx_reset(ecEncCtx* ctx, WC_RNG* rng); /* reset for use again w/o al int wc_ecc_ctx_set_algo(ecEncCtx* ctx, byte encAlgo, byte kdfAlgo, byte macAlgo); +/*! + \ingroup ECC + + \brief This function picks the device that ECIES operations using this + context run on. Only available when WOLF_CRYPTO_CB is defined. A context + starts at INVALID_DEVID, meaning software: ECIES does not copy the + device from the private key, so this must be called for a crypto + callback to be reached. The value is used both for the whole-operation + ECIES callback and for the AES/HMAC steps of the software path. + Passing a NULL context to wc_ecc_encrypt() or wc_ecc_decrypt() always + means software. The setting is kept across wc_ecc_ctx_reset(). + + \return 0 Returned upon successfully setting the device id. + \return BAD_FUNC_ARG Returned if the given context is NULL. + + \param ctx pointer to the ecEncCtx for which to set the device id + \param devId device id to use, or INVALID_DEVID for software + + _Example_ + \code + ecEncCtx* ctx = wc_ecc_ctx_new(REQ_RESP_CLIENT, &rng); + if (wc_ecc_ctx_set_dev_id(ctx, myDevId) != 0) { + // error setting device id + } + \endcode + + \sa wc_ecc_ctx_get_dev_id + \sa wc_ecc_ctx_new + \sa wc_ecc_ctx_reset +*/ + +int wc_ecc_ctx_set_dev_id(ecEncCtx* ctx, int devId); + +/*! + \ingroup ECC + + \brief This function reads back the device id set with + wc_ecc_ctx_set_dev_id(). Crypto callback code can use it to learn which + device it was called for. Only available when WOLF_CRYPTO_CB is defined. + A context that was never given a device reads back INVALID_DEVID. + + \return 0 Returned upon successfully reading the device id. + \return BAD_FUNC_ARG Returned if the given context or output pointer + is NULL. + + \param ctx pointer to the ecEncCtx to read the device id from + \param devId pointer that receives the device id + + _Example_ + \code + int devId; + if (wc_ecc_ctx_get_dev_id(ctx, &devId) != 0) { + // error reading device id + } + \endcode + + \sa wc_ecc_ctx_set_dev_id + \sa wc_ecc_ctx_new +*/ + +int wc_ecc_ctx_get_dev_id(ecEncCtx* ctx, int* devId); + /*! \ingroup ECC @@ -2088,8 +2154,13 @@ int wc_ecc_ctx_set_info(ecEncCtx* ctx, const byte* info, int sz); } \endcode + \note The device this runs on comes from the context + (wc_ecc_ctx_set_dev_id), not from privKey->devId. A NULL context, or one + that was never given a device, runs in software. + \sa wc_ecc_encrypt_ex \sa wc_ecc_decrypt + \sa wc_ecc_ctx_set_dev_id */ int wc_ecc_encrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, @@ -2165,8 +2236,13 @@ int wc_ecc_encrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, } \endcode + \note The device this runs on comes from the context + (wc_ecc_ctx_set_dev_id), not from privKey->devId. A NULL context, or one + that was never given a device, runs in software. + \sa wc_ecc_encrypt \sa wc_ecc_decrypt + \sa wc_ecc_ctx_set_dev_id */ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, @@ -2236,8 +2312,13 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, } \endcode + \note The device this runs on comes from the context + (wc_ecc_ctx_set_dev_id), not from privKey->devId. A NULL context, or one + that was never given a device, runs in software. + \sa wc_ecc_encrypt \sa wc_ecc_encrypt_ex + \sa wc_ecc_ctx_set_dev_id */ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 856ab70ed94..cdb200db49c 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -1680,7 +1680,8 @@ int test_wc_ecc_ctx_set_info(void) /* * Testing the crypto-callback context accessors wc_ecc_ctx_get_algo, * wc_ecc_ctx_get_kdf_salt, wc_ecc_ctx_get_info, wc_ecc_ctx_get_mac_salt, - * wc_ecc_ctx_get_protocol and wc_ecc_ctx_get_rng (built only when + * wc_ecc_ctx_get_protocol, wc_ecc_ctx_get_rng and the + * wc_ecc_ctx_set_dev_id / wc_ecc_ctx_get_dev_id pair (built only when * WOLF_CRYPTO_CB is enabled). */ int test_wc_ecc_ctx_getters(void) @@ -1841,6 +1842,51 @@ int test_wc_ecc_ctx_getters(void) WC_NO_ERR_TRACE(BAD_FUNC_ARG)); } + /* devId: the ECIES crypto callback and the AES/HMAC steps both use this, + * so a wrong value here sends the whole operation somewhere else. */ + { + int gotDevId = 0; + + /* A fresh context is software. ecc_ctx_init() zeroes the struct and + * devId 0 is a real device, so INVALID_DEVID has to be written on + * purpose. A 0 here means it was not. */ + gotDevId = 0x5a5a; + ExpectIntEQ(wc_ecc_ctx_get_dev_id(ctx, &gotDevId), 0); + ExpectIntEQ(gotDevId, INVALID_DEVID); + + ExpectIntEQ(wc_ecc_ctx_set_dev_id(ctx, 0x1234), 0); + gotDevId = 0; + ExpectIntEQ(wc_ecc_ctx_get_dev_id(ctx, &gotDevId), 0); + ExpectIntEQ(gotDevId, 0x1234); + + /* devId 0 is a legal device and must not read back as "unset" */ + ExpectIntEQ(wc_ecc_ctx_set_dev_id(ctx, 0), 0); + gotDevId = 0x5a5a; + ExpectIntEQ(wc_ecc_ctx_get_dev_id(ctx, &gotDevId), 0); + ExpectIntEQ(gotDevId, 0); + + /* the device is the caller's setting and is kept across a reset */ + ExpectIntEQ(wc_ecc_ctx_set_dev_id(ctx, 0x4d43), 0); + ExpectIntEQ(wc_ecc_ctx_reset(ctx, &rng), 0); + gotDevId = 0; + ExpectIntEQ(wc_ecc_ctx_get_dev_id(ctx, &gotDevId), 0); + ExpectIntEQ(gotDevId, 0x4d43); + + /* and can be set back to software */ + ExpectIntEQ(wc_ecc_ctx_set_dev_id(ctx, INVALID_DEVID), 0); + gotDevId = 0; + ExpectIntEQ(wc_ecc_ctx_get_dev_id(ctx, &gotDevId), 0); + ExpectIntEQ(gotDevId, INVALID_DEVID); + + /* bad args: NULL ctx / NULL out-parameter */ + ExpectIntEQ(wc_ecc_ctx_set_dev_id(NULL, 0x1234), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_ctx_get_dev_id(NULL, &gotDevId), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_ctx_get_dev_id(ctx, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + } + wc_ecc_ctx_free(ctx); DoExpectIntEQ(wc_FreeRng(&rng), 0); #endif @@ -2105,27 +2151,47 @@ static int myEciesApiCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx) { int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); int* invoked = (int*)ctx; + + (void)devIdArg; + if (info->algo_type == WC_ALGO_TYPE_PK) { if (info->pk.type == WC_PK_TYPE_ECIES_ENCRYPT) { + ecEncCtx* eCtx = info->pk.eciesencrypt.ctx; + int savedDevId = INVALID_DEVID; + if (invoked != NULL) *invoked = 1; - info->pk.eciesencrypt.privKey->devId = INVALID_DEVID; + /* ECIES picks its device from the context devId, so clear that, + * not the caller's key, so the call back into wolfSSL stays in + * software. A NULL context is already software-only. */ + if (eCtx != NULL) { + (void)wc_ecc_ctx_get_dev_id(eCtx, &savedDevId); + (void)wc_ecc_ctx_set_dev_id(eCtx, INVALID_DEVID); + } ret = wc_ecc_encrypt_ex(info->pk.eciesencrypt.privKey, info->pk.eciesencrypt.pubKey, info->pk.eciesencrypt.msg, info->pk.eciesencrypt.msgSz, info->pk.eciesencrypt.out, info->pk.eciesencrypt.outSz, info->pk.eciesencrypt.ctx, info->pk.eciesencrypt.compressed); - info->pk.eciesencrypt.privKey->devId = devIdArg; + if (eCtx != NULL) + (void)wc_ecc_ctx_set_dev_id(eCtx, savedDevId); } else if (info->pk.type == WC_PK_TYPE_ECIES_DECRYPT) { + ecEncCtx* eCtx = info->pk.eciesdecrypt.ctx; + int savedDevId = INVALID_DEVID; + if (invoked != NULL) *invoked = 1; - info->pk.eciesdecrypt.privKey->devId = INVALID_DEVID; + if (eCtx != NULL) { + (void)wc_ecc_ctx_get_dev_id(eCtx, &savedDevId); + (void)wc_ecc_ctx_set_dev_id(eCtx, INVALID_DEVID); + } ret = wc_ecc_decrypt(info->pk.eciesdecrypt.privKey, info->pk.eciesdecrypt.pubKey, info->pk.eciesdecrypt.msg, info->pk.eciesdecrypt.msgSz, info->pk.eciesdecrypt.out, info->pk.eciesdecrypt.outSz, info->pk.eciesdecrypt.ctx); - info->pk.eciesdecrypt.privKey->devId = devIdArg; + if (eCtx != NULL) + (void)wc_ecc_ctx_set_dev_id(eCtx, savedDevId); } } return ret; @@ -2148,6 +2214,11 @@ int test_wc_ecc_ecies_cryptocb(void) ecc_key cliKey; ecc_key srvKey; WC_RNG rng; + ecEncCtx* cliCtx = NULL; + ecEncCtx* srvCtx = NULL; + byte cliSalt[EXCHANGE_SALT_SZ]; + byte srvSalt[EXCHANGE_SALT_SZ]; + const byte* tmpSalt = NULL; byte msg[32]; byte out[256]; byte plain[64]; @@ -2180,12 +2251,28 @@ int test_wc_ecc_ecies_cryptocb(void) ExpectIntEQ(wc_ecc_set_rng(&cliKey, &rng), 0); ExpectIntEQ(wc_ecc_set_rng(&srvKey, &rng), 0); #endif + /* The keys name the device too, but that no longer picks where ECIES + * runs. The contexts below are what reach the callback. Leaving these + * set shows the two are independent. */ cliKey.devId = cbDevId; srvKey.devId = cbDevId; + ExpectNotNull(cliCtx = wc_ecc_ctx_new(REQ_RESP_CLIENT, &rng)); + ExpectNotNull(srvCtx = wc_ecc_ctx_new(REQ_RESP_SERVER, &rng)); + ExpectIntEQ(wc_ecc_ctx_set_dev_id(cliCtx, cbDevId), 0); + ExpectIntEQ(wc_ecc_ctx_set_dev_id(srvCtx, cbDevId), 0); + ExpectNotNull(tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx)); + if (tmpSalt != NULL) + XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); + ExpectNotNull(tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx)); + if (tmpSalt != NULL) + XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); + ExpectIntEQ(wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt), 0); + ExpectIntEQ(wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt), 0); + cbInvoked = 0; ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, &outSz, - NULL), 0); + cliCtx), 0); /* callback must have serviced the encrypt */ ExpectIntEQ(cbInvoked, 1); @@ -2194,15 +2281,17 @@ int test_wc_ecc_ecies_cryptocb(void) * NULL and read the ephemeral key from the message. */ #ifdef WOLFSSL_ECIES_OLD ExpectIntEQ(wc_ecc_decrypt(&srvKey, &cliKey, out, outSz, plain, &plainSz, - NULL), 0); + srvCtx), 0); #else ExpectIntEQ(wc_ecc_decrypt(&srvKey, NULL, out, outSz, plain, &plainSz, - NULL), 0); + srvCtx), 0); #endif ExpectIntEQ(cbInvoked, 1); ExpectIntEQ(plainSz, sizeof(msg)); ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); + wc_ecc_ctx_free(srvCtx); + wc_ecc_ctx_free(cliCtx); cliKey.devId = INVALID_DEVID; srvKey.devId = INVALID_DEVID; wc_ecc_free(&srvKey); @@ -2214,6 +2303,315 @@ int test_wc_ecc_ecies_cryptocb(void) return EXPECT_RESULT(); } /* END test_wc_ecc_ecies_cryptocb */ +/* + * ECIES used to take its device from privKey->devId. It now takes it from the + * context only. Both checks below fail silently if this breaks: the call + * still succeeds, it just runs somewhere else. + * 1. a key with a device, and a context with none, runs in software; + * 2. a NULL context is software-only no matter what the key says. + * ECDH is unchanged and still uses the key's device; that is not tested here. + */ +int test_wc_ecc_ecies_devid_not_inherited(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) && \ + (defined(HAVE_AES_CBC) || \ + (defined(HAVE_AESGCM) && (defined(WOLFSSL_ECIES_GEN_IV) || \ + defined(WOLFSSL_ECIES_OLD) || \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE)))) && defined(WOLFSSL_AES_128) + const int cbDevId = 0x45434231; /* 'ECB1' */ + ecc_key cliKey; + ecc_key srvKey; + WC_RNG rng; + ecEncCtx* cliCtx = NULL; + ecEncCtx* srvCtx = NULL; + byte cliSalt[EXCHANGE_SALT_SZ]; + byte srvSalt[EXCHANGE_SALT_SZ]; + const byte* tmpSalt = NULL; + byte msg[32]; + byte out[256]; + byte plain[64]; + word32 outSz = (word32)sizeof(out); + word32 plainSz = (word32)sizeof(plain); + int i; + int registered = 0; + int cbInvoked = 0; + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&cliKey, 0, sizeof(cliKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + for (i = 0; i < (int)sizeof(msg); i++) + msg[i] = (byte)i; + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(cbDevId, myEciesApiCryptoCb, + &cbInvoked), 0); + if (EXPECT_SUCCESS()) + registered = 1; + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_ecc_init(&cliKey), 0); + ExpectIntEQ(wc_ecc_init(&srvKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, KEY32, &cliKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, KEY32, &srvKey), 0); +#if defined(ECC_TIMING_RESISTANT) && (!defined(HAVE_FIPS) || \ + (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION != 2))) && \ + !defined(HAVE_SELFTEST) + ExpectIntEQ(wc_ecc_set_rng(&cliKey, &rng), 0); + ExpectIntEQ(wc_ecc_set_rng(&srvKey, &rng), 0); +#endif + /* Both keys are bound to the device for the whole test. */ + cliKey.devId = cbDevId; + srvKey.devId = cbDevId; + + /* (1) contexts given, but no devId set: software, callback never called. */ + ExpectNotNull(cliCtx = wc_ecc_ctx_new(REQ_RESP_CLIENT, &rng)); + ExpectNotNull(srvCtx = wc_ecc_ctx_new(REQ_RESP_SERVER, &rng)); + ExpectNotNull(tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx)); + if (tmpSalt != NULL) + XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); + ExpectNotNull(tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx)); + if (tmpSalt != NULL) + XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); + ExpectIntEQ(wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt), 0); + ExpectIntEQ(wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt), 0); + + cbInvoked = 0; + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, &outSz, + cliCtx), 0); + ExpectIntEQ(cbInvoked, 0); +#ifdef WOLFSSL_ECIES_OLD + ExpectIntEQ(wc_ecc_decrypt(&srvKey, &cliKey, out, outSz, plain, &plainSz, + srvCtx), 0); +#else + ExpectIntEQ(wc_ecc_decrypt(&srvKey, NULL, out, outSz, plain, &plainSz, + srvCtx), 0); +#endif + ExpectIntEQ(cbInvoked, 0); + ExpectIntEQ(plainSz, sizeof(msg)); + ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); + + /* (2) no context at all: still software. */ + cbInvoked = 0; + outSz = (word32)sizeof(out); + plainSz = (word32)sizeof(plain); + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, &outSz, + NULL), 0); + ExpectIntEQ(cbInvoked, 0); +#ifdef WOLFSSL_ECIES_OLD + ExpectIntEQ(wc_ecc_decrypt(&srvKey, &cliKey, out, outSz, plain, &plainSz, + NULL), 0); +#else + ExpectIntEQ(wc_ecc_decrypt(&srvKey, NULL, out, outSz, plain, &plainSz, + NULL), 0); +#endif + ExpectIntEQ(cbInvoked, 0); + ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); + + /* (3) Check: same keys, device now set on the contexts. Without this + * step, (1) and (2) would also pass if the callback were never + * registered at all. */ + ExpectIntEQ(wc_ecc_ctx_reset(cliCtx, &rng), 0); + ExpectIntEQ(wc_ecc_ctx_reset(srvCtx, &rng), 0); + ExpectIntEQ(wc_ecc_ctx_set_dev_id(cliCtx, cbDevId), 0); + ExpectIntEQ(wc_ecc_ctx_set_dev_id(srvCtx, cbDevId), 0); + ExpectNotNull(tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx)); + if (tmpSalt != NULL) + XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); + ExpectNotNull(tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx)); + if (tmpSalt != NULL) + XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); + ExpectIntEQ(wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt), 0); + ExpectIntEQ(wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt), 0); + + cbInvoked = 0; + outSz = (word32)sizeof(out); + plainSz = (word32)sizeof(plain); + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, &outSz, + cliCtx), 0); + ExpectIntEQ(cbInvoked, 1); + ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); + + wc_ecc_ctx_free(srvCtx); + wc_ecc_ctx_free(cliCtx); + cliKey.devId = INVALID_DEVID; + srvKey.devId = INVALID_DEVID; + wc_ecc_free(&srvKey); + wc_ecc_free(&cliKey); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + if (registered) + wc_CryptoCb_UnRegisterDevice(cbDevId); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ecies_devid_not_inherited */ + +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) && \ + (defined(HAVE_AES_CBC) || \ + (defined(HAVE_AESGCM) && (defined(WOLFSSL_ECIES_GEN_IV) || \ + defined(WOLFSSL_ECIES_OLD) || \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE)))) && defined(WOLFSSL_AES_128) +/* Counts how often a device is asked to do a KDF, cipher or HMAC step. It + * always says no, so each step then runs in software. */ +typedef struct EciesStepCount { + int kdf; + int cipher; + int hmac; +} EciesStepCount; + +static int myEciesStepCountCb(int devIdArg, wc_CryptoInfo* info, void* ctx) +{ + EciesStepCount* cnt = (EciesStepCount*)ctx; + + (void)devIdArg; + + if (cnt != NULL) { + if (info->algo_type == WC_ALGO_TYPE_KDF) + cnt->kdf++; + else if (info->algo_type == WC_ALGO_TYPE_CIPHER) + cnt->cipher++; + else if (info->algo_type == WC_ALGO_TYPE_HMAC) + cnt->hmac++; + } + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} +#endif + +/* + * The software ECIES path hands the context devId to its KDF, cipher and MAC + * steps. The other ECIES tests never see this: their callbacks take the whole + * job and clear the devId. Here the callback turns down the whole job but + * counts the KDF, cipher and HMAC steps. With a device on the context the + * counts must go up; without one they must stay at zero. Both HKDF hashes run, + * which also covers the ecHKDF_SHA1 case. + */ +int test_wc_ecc_ecies_ctx_devid_steps(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) && \ + (defined(HAVE_AES_CBC) || \ + (defined(HAVE_AESGCM) && (defined(WOLFSSL_ECIES_GEN_IV) || \ + defined(WOLFSSL_ECIES_OLD) || \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE)))) && defined(WOLFSSL_AES_128) + const int cbDevId = 0x45434232; /* 'ECB2' */ + const byte kdfAlgos[] = { + ecHKDF_SHA256, + #ifndef NO_SHA + ecHKDF_SHA1, + #endif + }; + EciesStepCount cnt; + ecc_key cliKey; + ecc_key srvKey; + WC_RNG rng; + ecEncCtx* cliCtx = NULL; + ecEncCtx* srvCtx = NULL; + byte cliSalt[EXCHANGE_SALT_SZ]; + byte srvSalt[EXCHANGE_SALT_SZ]; + const byte* tmpSalt = NULL; + byte encAlgo = 0; + byte macAlgo = 0; + byte msg[32]; + byte out[256]; + byte plain[64]; + word32 outSz; + word32 plainSz; + int i; + int k; + int useDev; + int registered = 0; + + XMEMSET(&cnt, 0, sizeof(cnt)); + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&cliKey, 0, sizeof(cliKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + for (i = 0; i < (int)sizeof(msg); i++) + msg[i] = (byte)i; + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(cbDevId, myEciesStepCountCb, + &cnt), 0); + if (EXPECT_SUCCESS()) + registered = 1; + + ExpectIntEQ(wc_InitRng(&rng), 0); + /* Keys stay in software so only the context can reach the device. */ + ExpectIntEQ(wc_ecc_init(&cliKey), 0); + ExpectIntEQ(wc_ecc_init(&srvKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, KEY32, &cliKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, KEY32, &srvKey), 0); +#if defined(ECC_TIMING_RESISTANT) && (!defined(HAVE_FIPS) || \ + (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION != 2))) && \ + !defined(HAVE_SELFTEST) + ExpectIntEQ(wc_ecc_set_rng(&cliKey, &rng), 0); + ExpectIntEQ(wc_ecc_set_rng(&srvKey, &rng), 0); +#endif + ExpectNotNull(cliCtx = wc_ecc_ctx_new(REQ_RESP_CLIENT, &rng)); + ExpectNotNull(srvCtx = wc_ecc_ctx_new(REQ_RESP_SERVER, &rng)); + /* keep the build's default cipher and MAC, only the KDF changes below */ + ExpectIntEQ(wc_ecc_ctx_get_algo(cliCtx, &encAlgo, NULL, &macAlgo), 0); + + for (k = 0; k < (int)sizeof(kdfAlgos) && EXPECT_SUCCESS(); k++) { + for (useDev = 1; useDev >= 0; useDev--) { + /* a context is single use, so start each message fresh */ + ExpectIntEQ(wc_ecc_ctx_reset(cliCtx, &rng), 0); + ExpectIntEQ(wc_ecc_ctx_reset(srvCtx, &rng), 0); + ExpectIntEQ(wc_ecc_ctx_set_dev_id(cliCtx, + useDev ? cbDevId : INVALID_DEVID), 0); + ExpectIntEQ(wc_ecc_ctx_set_dev_id(srvCtx, + useDev ? cbDevId : INVALID_DEVID), 0); + ExpectIntEQ(wc_ecc_ctx_set_algo(cliCtx, encAlgo, kdfAlgos[k], + macAlgo), 0); + ExpectIntEQ(wc_ecc_ctx_set_algo(srvCtx, encAlgo, kdfAlgos[k], + macAlgo), 0); + ExpectNotNull(tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx)); + if (tmpSalt != NULL) + XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); + ExpectNotNull(tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx)); + if (tmpSalt != NULL) + XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); + ExpectIntEQ(wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt), 0); + ExpectIntEQ(wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt), 0); + + XMEMSET(&cnt, 0, sizeof(cnt)); + XMEMSET(plain, 0, sizeof(plain)); + outSz = (word32)sizeof(out); + plainSz = (word32)sizeof(plain); + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), + out, &outSz, cliCtx), 0); + #ifdef WOLFSSL_ECIES_OLD + ExpectIntEQ(wc_ecc_decrypt(&srvKey, &cliKey, out, outSz, plain, + &plainSz, srvCtx), 0); + #else + ExpectIntEQ(wc_ecc_decrypt(&srvKey, NULL, out, outSz, plain, + &plainSz, srvCtx), 0); + #endif + ExpectIntEQ(plainSz, sizeof(msg)); + ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); + if (useDev) { + ExpectIntGT(cnt.kdf, 0); + ExpectIntGT(cnt.cipher, 0); + ExpectIntGT(cnt.hmac, 0); + } + else { + ExpectIntEQ(cnt.kdf, 0); + ExpectIntEQ(cnt.cipher, 0); + ExpectIntEQ(cnt.hmac, 0); + } + } + } + + wc_ecc_ctx_free(srvCtx); + wc_ecc_ctx_free(cliCtx); + wc_ecc_free(&srvKey); + wc_ecc_free(&cliKey); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + if (registered) + wc_CryptoCb_UnRegisterDevice(cbDevId); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ecies_ctx_devid_steps */ + /* * The ECIES AES-GCM DEM needs an RNG only in GEN_IV mode, where it generates a * random per-message nonce (default mode uses a fixed nonce and OLD derives it diff --git a/tests/api/test_ecc.h b/tests/api/test_ecc.h index 61fd06dd002..03a2eb9a89e 100644 --- a/tests/api/test_ecc.h +++ b/tests/api/test_ecc.h @@ -59,6 +59,8 @@ int test_wc_ecc_encryptDecrypt(void); int test_wc_ecc_ecies_gcm(void); int test_wc_ecc_ecies_gcm_no_rng(void); int test_wc_ecc_ecies_cryptocb(void); +int test_wc_ecc_ecies_devid_not_inherited(void); +int test_wc_ecc_ecies_ctx_devid_steps(void); int test_wc_ecc_del_point(void); int test_wc_ecc_pointFns(void); int test_wc_ecc_shared_secret_ssh(void); @@ -109,6 +111,8 @@ int test_wc_EccDecisionCoverage4(void); TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_gcm), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_gcm_no_rng), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_cryptocb), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_devid_not_inherited), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_ctx_devid_steps), \ TEST_DECL_GROUP("ecc", test_wc_ecc_del_point), \ TEST_DECL_GROUP("ecc", test_wc_ecc_pointFns), \ TEST_DECL_GROUP("ecc", test_wc_ecc_shared_secret_ssh), \ diff --git a/tests/unit-mcdc/test_cryptocb_whitebox.c b/tests/unit-mcdc/test_cryptocb_whitebox.c index 7d7f054e72a..9a2d8113ee6 100644 --- a/tests/unit-mcdc/test_cryptocb_whitebox.c +++ b/tests/unit-mcdc/test_cryptocb_whitebox.c @@ -1014,38 +1014,40 @@ int main(void) #endif /* ---- ECIES encrypt/decrypt dispatch (HAVE_ECC_ENCRYPT) ---- - * Both bodies resolve their device from privKey->devId and then take the + * Both bodies get their device from the devId parameter (the caller reads + * it off the ECIES context, not off privKey->devId) and then take the * usual `if (dev && dev->cb)` guard, so the standard three-vector sweep - * applies. Nothing but privKey->devId is read before the guard, and the - * registered callback (wb_cb) ignores the wc_CryptoInfo it is handed and - * reports CRYPTOCB_UNAVAILABLE, so a zeroed ecc_key with no key material - * is sufficient and safe here -- no curve arithmetic runs. */ + * works on a plain local devId. Nothing but that parameter is read before + * the guard, and the registered callback (wb_cb) ignores the wc_CryptoInfo + * it is handed and reports CRYPTOCB_UNAVAILABLE, so a zeroed ecc_key with + * no key material is enough and safe here -- no curve math runs. */ #ifdef HAVE_ECC_ENCRYPT { ecc_key ecPriv; byte eciesMsg[16]; byte eciesOut[128]; word32 eciesOutSz; + int eciesDevId = INVALID_DEVID; XMEMSET(&ecPriv, 0, sizeof(ecPriv)); XMEMSET(eciesMsg, 0x5e, sizeof(eciesMsg)); XMEMSET(eciesOut, 0, sizeof(eciesOut)); eciesOutSz = (word32)sizeof(eciesOut); - WB_DRIVE3(ecPriv.devId, - wc_CryptoCb_EciesEncrypt(&ecPriv, NULL, eciesMsg, + WB_DRIVE3(eciesDevId, + wc_CryptoCb_EciesEncrypt(eciesDevId, &ecPriv, NULL, eciesMsg, (word32)sizeof(eciesMsg), eciesOut, &eciesOutSz, NULL, 0)); eciesOutSz = (word32)sizeof(eciesOut); - WB_DRIVE3(ecPriv.devId, - wc_CryptoCb_EciesDecrypt(&ecPriv, NULL, eciesMsg, + WB_DRIVE3(eciesDevId, + wc_CryptoCb_EciesDecrypt(eciesDevId, &ecPriv, NULL, eciesMsg, (word32)sizeof(eciesMsg), eciesOut, &eciesOutSz, NULL)); /* privKey == NULL early return (both entry points). */ eciesOutSz = (word32)sizeof(eciesOut); - (void)wc_CryptoCb_EciesEncrypt(NULL, NULL, eciesMsg, + (void)wc_CryptoCb_EciesEncrypt(INVALID_DEVID, NULL, NULL, eciesMsg, (word32)sizeof(eciesMsg), eciesOut, &eciesOutSz, NULL, 0); - (void)wc_CryptoCb_EciesDecrypt(NULL, NULL, eciesMsg, + (void)wc_CryptoCb_EciesDecrypt(INVALID_DEVID, NULL, NULL, eciesMsg, (word32)sizeof(eciesMsg), eciesOut, &eciesOutSz, NULL); WB_NOTE("ECIES Encrypt/Decrypt dev&&dev->cb three-vector driven"); diff --git a/wolfcrypt/benchmark/benchmark.c b/wolfcrypt/benchmark/benchmark.c index fb5c372ef7d..1a734226298 100644 --- a/wolfcrypt/benchmark/benchmark.c +++ b/wolfcrypt/benchmark/benchmark.c @@ -15091,6 +15091,22 @@ static void bench_eccEncryptEx(int useDeviceID, int curveId, int ctxMode) goto exit; } + #ifdef WOLF_CRYPTO_CB + /* ECIES picks its device from the context, not the keys. Without + * this the -dev rows would time software but be labeled as device + * rows. bench_ecies_prep() resets the contexts each round, and a + * reset keeps the devId. */ + if (useDeviceID) { + if (wc_ecc_ctx_set_dev_id(cliCtx, devId) != 0 || + wc_ecc_ctx_set_dev_id(srvCtx, devId) != 0) { + printf("bench_eccEncrypt ctx set dev id failed\n"); + wc_ecc_ctx_free(cliCtx); + wc_ecc_ctx_free(srvCtx); + goto exit; + } + } + #endif + for (c = 0; eciesCiphers[c].label != NULL; c++) { byte algo = eciesCiphers[c].algo; /* Tag the KDF rows so they do not read as the default ones. */ diff --git a/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md b/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md index 39c7fc67aed..ed4b9036248 100644 --- a/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md +++ b/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md @@ -146,6 +146,9 @@ Use `wc_ecc_ctx_set_kdf_salt`, not `wc_ecc_ctx_set_peer_salt`: ```c ecEncCtx* ctx = wc_ecc_ctx_new(REQ_RESP_CLIENT, &rng); +/* Required: ECIES routes on the context's device, not the key's. */ +wc_ecc_ctx_set_dev_id(ctx, WOLFSSL_VERSAL_GEN2_ASU_DEVID); + wc_ecc_ctx_set_algo(ctx, ecAES_256_GCM, ecHKDF_SHA256, ecHMAC_SHA256); wc_ecc_ctx_set_kdf_salt(ctx, salt, saltSz); wc_ecc_ctx_set_info(ctx, info, infoSz); @@ -159,12 +162,14 @@ context bytes, then calls `wc_ecc_decrypt`. The wolfCrypt benchmark keys ECIES the other way by default, so its ECIES rows run in software. Build the benchmark with `WC_BENCH_ECIES_KDF` to add a second set of rows, tagged `-kdf`, that use the context shown above and reach the ASU. +The benchmark sets the context device id itself on its `-dev` rows. What the offload requires: | Setting | Value | | --- | --- | | Build | `WOLFSSL_ECIES_GEN_IV`, with neither `WOLFSSL_ECIES_OLD` nor `WOLFSSL_ECIES_ISO18033` | +| Device | `wc_ecc_ctx_set_dev_id` on the context; a devId on the key does not route ECIES | | RNG | one on the key or on the context, see below | | Scheme | `ecAES_128_GCM` or `ecAES_256_GCM` with `ecHKDF_SHA256` | | KDF salt | `wc_ecc_ctx_set_kdf_salt`, passed through as given | @@ -179,8 +184,10 @@ authenticated data, which the ASU cannot accept, so the port declines and wolfSSL runs ECIES in software. There is no way around this from the port. Declining is not the same as running with no hardware. The software ECIES path -still passes the device id to the AES and HMAC underneath, so those operations -go to the ASU one at a time. Only the single-command ECIES is lost. +still passes the context's device id to the AES and HMAC underneath, so those +operations go to the ASU one at a time. Only the single-command ECIES is lost. +This too depends on `wc_ecc_ctx_set_dev_id`: a context without a device runs +entirely in software, AES and HMAC included, whatever the key says. **The private key passed to encrypt is not used.** `wc_ecc_encrypt` takes a private key, and software derives the shared secret from it and puts its public diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 68e63525e5c..5ccb510793b 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -46939,6 +46939,22 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecc_test(void) #if defined(HAVE_ECC_ENCRYPT) && defined(HAVE_AES_CBC) && \ (defined(WOLFSSL_AES_128) || defined(WOLFSSL_AES_256)) +/* ECIES takes its device from the context, not from the keys, so each context + * has to be told which device to use. These tests build their keys with the + * global devId, which is a real device on ports that set WC_USE_DEVID or + * WOLFSSL_CAAM_DEVID. Without this the tests below would quietly run in + * software there and never touch the hardware path. Defined outside the + * guards below so every ECIES test in this file can use it. */ +#ifdef WOLF_CRYPTO_CB +static wc_test_ret_t ecc_ctx_apply_devid(ecEncCtx* ctx) +{ + int ret = wc_ecc_ctx_set_dev_id(ctx, devId); + return (ret == 0) ? 0 : WC_TEST_RET_ENC_EC(ret); +} +#else +#define ecc_ctx_apply_devid(ctx) (0) +#endif + #if !defined(WOLFSSL_NO_MALLOC) #if ((! defined(HAVE_FIPS)) || FIPS_VERSION_GE(5,3)) @@ -47017,6 +47033,11 @@ static wc_test_ret_t ecc_ctx_kdf_salt_test(WC_RNG* rng, ecc_key* a, ecc_key* b) ret = WC_TEST_RET_ENC_NC; } + if (ret == 0) + ret = ecc_ctx_apply_devid(aCtx); + if (ret == 0) + ret = ecc_ctx_apply_devid(bCtx); + /* set salt */ if (ret == 0) { ret = wc_ecc_ctx_set_kdf_salt(aCtx, salt, sizeof(salt)); @@ -47373,7 +47394,10 @@ static wc_test_ret_t ecc_encrypt_e2e_test(WC_RNG* rng, ecc_key* userA, ecc_key* for (i = 0; i < (int)sizeof(msg); i++) msg[i] = i; - /* encrypt msg to B */ + /* encrypt msg to B. The NULL-context calls here and below run in software + * on purpose: ECIES takes its device from the context, so with no context + * there is nowhere to name one. The context-based exchange further down + * covers the device path. */ ret = wc_ecc_encrypt(userA, userB, msg, sizeof(msg), out, &outSz, NULL); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto done; @@ -47416,6 +47440,12 @@ static wc_test_ret_t ecc_encrypt_e2e_test(WC_RNG* rng, ecc_key* userA, ecc_key* ret = WC_TEST_RET_ENC_ERRNO; goto done; } + ret = ecc_ctx_apply_devid(cliCtx); + if (ret == 0) + ret = ecc_ctx_apply_devid(srvCtx); + if (ret != 0) + goto done; + ret = wc_ecc_ctx_set_algo(cliCtx, encAlgo, kdfAlgo, macAlgo); if (ret != 0) goto done; @@ -47512,6 +47542,12 @@ static wc_test_ret_t ecc_encrypt_e2e_test(WC_RNG* rng, ecc_key* userA, ecc_key* ret = WC_TEST_RET_ENC_ERRNO; goto done; } + ret = ecc_ctx_apply_devid(cliCtx); + if (ret == 0) + ret = ecc_ctx_apply_devid(srvCtx); + if (ret != 0) + goto done; + ret = wc_ecc_ctx_set_algo(cliCtx, encAlgo, kdfAlgo, macAlgo); if (ret != 0) goto done; @@ -47791,6 +47827,8 @@ static wc_test_ret_t ecc_encrypt_gcm_kat_vec(WC_RNG* rng, byte encAlgo, srvCtx = wc_ecc_ctx_new(REQ_RESP_SERVER, rng); if (srvCtx == NULL) { ret = WC_TEST_RET_ENC_ERRNO; break; } + ret = ecc_ctx_apply_devid(srvCtx); + if (ret != 0) break; ret = wc_ecc_ctx_set_algo(srvCtx, encAlgo, kdfAlgo, ecHMAC_SHA256); if (ret == 0) { /* force our fixed own salt, then set the peer's fixed salt */ @@ -47946,9 +47984,10 @@ static wc_test_ret_t ecc_encrypt_gcm_kat(WC_RNG* rng) #endif /* GCM KAT guards */ #if defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) -/* Minimal ECIES CryptoCb: with mode==1 it services the operation (forwarding to - * software after clearing devId) and records that it was invoked; with mode==0 - * it returns CRYPTOCB_UNAVAILABLE so ECIES falls back to software. */ +/* Minimal ECIES CryptoCb: with mode==1 it handles the operation (by calling + * software after clearing the context devId) and records that it was called; + * with mode==0 it returns CRYPTOCB_UNAVAILABLE so ECIES falls back to + * software. */ typedef struct EciesCbCtx { int mode; /* 0 = force fallback, 1 = handle in callback */ int encryptInvoked; /* set when the callback services an ECIES encrypt */ @@ -47965,8 +48004,13 @@ static int myEciesCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx) int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); EciesCbCtx* cbCtx = (EciesCbCtx*)ctx; + (void)devIdArg; + if (info->algo_type == WC_ALGO_TYPE_PK) { if (info->pk.type == WC_PK_TYPE_ECIES_ENCRYPT) { + ecEncCtx* eCtx = info->pk.eciesencrypt.ctx; + int savedDevId = INVALID_DEVID; + if (cbCtx->mode == 0) return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); cbCtx->encryptInvoked = 1; @@ -47983,24 +48027,38 @@ static int myEciesCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx) *info->pk.eciesencrypt.outSz = needed; return 0; } - info->pk.eciesencrypt.privKey->devId = INVALID_DEVID; + /* ECIES picks its device from the context devId, so clear that, + * not the caller's key, so the call back into wolfSSL stays in + * software. A NULL context is already software-only. */ + if (eCtx != NULL) { + (void)wc_ecc_ctx_get_dev_id(eCtx, &savedDevId); + (void)wc_ecc_ctx_set_dev_id(eCtx, INVALID_DEVID); + } ret = wc_ecc_encrypt_ex(info->pk.eciesencrypt.privKey, info->pk.eciesencrypt.pubKey, info->pk.eciesencrypt.msg, info->pk.eciesencrypt.msgSz, info->pk.eciesencrypt.out, info->pk.eciesencrypt.outSz, info->pk.eciesencrypt.ctx, info->pk.eciesencrypt.compressed); - info->pk.eciesencrypt.privKey->devId = devIdArg; + if (eCtx != NULL) + (void)wc_ecc_ctx_set_dev_id(eCtx, savedDevId); } else if (info->pk.type == WC_PK_TYPE_ECIES_DECRYPT) { + ecEncCtx* eCtx = info->pk.eciesdecrypt.ctx; + int savedDevId = INVALID_DEVID; + if (cbCtx->mode == 0) return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); cbCtx->decryptInvoked = 1; - info->pk.eciesdecrypt.privKey->devId = INVALID_DEVID; + if (eCtx != NULL) { + (void)wc_ecc_ctx_get_dev_id(eCtx, &savedDevId); + (void)wc_ecc_ctx_set_dev_id(eCtx, INVALID_DEVID); + } ret = wc_ecc_decrypt(info->pk.eciesdecrypt.privKey, info->pk.eciesdecrypt.pubKey, info->pk.eciesdecrypt.msg, info->pk.eciesdecrypt.msgSz, info->pk.eciesdecrypt.out, info->pk.eciesdecrypt.outSz, info->pk.eciesdecrypt.ctx); - info->pk.eciesdecrypt.privKey->devId = devIdArg; + if (eCtx != NULL) + (void)wc_ecc_ctx_set_dev_id(eCtx, savedDevId); } } @@ -48076,6 +48134,13 @@ static wc_test_ret_t ecies_cryptocb_roundtrip(WC_RNG* rng, EciesCbCtx* cbCtx, ret = wc_ecc_ctx_set_algo(srvCtx, encAlgo, ecHKDF_SHA256, ecHMAC_SHA256); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto rt_done; } + /* ECIES picks its device from the context devId, not the key's, so the + * device has to be set here or the callback is never reached. */ + ret = wc_ecc_ctx_set_dev_id(cliCtx, ECIES_CB_TEST_DEVID); + if (ret == 0) + ret = wc_ecc_ctx_set_dev_id(srvCtx, ECIES_CB_TEST_DEVID); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto rt_done; } + tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx); if (tmpSalt == NULL) { ret = WC_TEST_RET_ENC_NC; goto rt_done; } XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); @@ -48139,6 +48204,12 @@ static wc_test_ret_t ecies_cryptocb_state_test(WC_RNG* rng, EciesCbCtx* cbCtx, ret = WC_TEST_RET_ENC_NC; goto st_done; } + /* ECIES picks its device from the context devId, not the key's. */ + ret = wc_ecc_ctx_set_dev_id(cliCtx, ECIES_CB_TEST_DEVID); + if (ret == 0) + ret = wc_ecc_ctx_set_dev_id(srvCtx, ECIES_CB_TEST_DEVID); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto st_done; } + /* Salt exchange brings the client ctx to ecCLI_SALT_SET (encrypt-ready). */ tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx); if (tmpSalt == NULL) { ret = WC_TEST_RET_ENC_NC; goto st_done; } @@ -48162,11 +48233,16 @@ static wc_test_ret_t ecies_cryptocb_state_test(WC_RNG* rng, EciesCbCtx* cbCtx, /* Second encrypt on the same ctx must be rejected: the hardware path must * have advanced the single-use state. */ outSz = sizeof(out); + cbCtx->encryptInvoked = 0; ret = wc_ecc_encrypt(userA, userB, msg, sizeof(msg), out, &outSz, cliCtx); if (ret != WC_NO_ERR_TRACE(BAD_STATE_E)) { ret = (ret == 0) ? WC_TEST_RET_ENC_NC : WC_TEST_RET_ENC_EC(ret); goto st_done; } + /* The reject has to come from the single-use check after the hardware + * handled the call, not from the callback being skipped and software + * rejecting it. Otherwise this passes for the wrong reason. */ + if (cbCtx->encryptInvoked != 1) { ret = WC_TEST_RET_ENC_NC; goto st_done; } ret = 0; st_done: @@ -82200,23 +82276,39 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx) } #ifdef HAVE_ECC_ENCRYPT else if (info->pk.type == WC_PK_TYPE_ECIES_ENCRYPT) { - /* set devId to invalid so the software path runs */ - info->pk.eciesencrypt.privKey->devId = INVALID_DEVID; + /* ECIES picks its device from the context devId, so clear that, + * not the caller's key, so the software path runs instead of + * calling straight back into this callback. */ + ecEncCtx* eCtx = info->pk.eciesencrypt.ctx; + int savedDevId = INVALID_DEVID; + + if (eCtx != NULL) { + (void)wc_ecc_ctx_get_dev_id(eCtx, &savedDevId); + (void)wc_ecc_ctx_set_dev_id(eCtx, INVALID_DEVID); + } ret = wc_ecc_encrypt_ex(info->pk.eciesencrypt.privKey, info->pk.eciesencrypt.pubKey, info->pk.eciesencrypt.msg, info->pk.eciesencrypt.msgSz, info->pk.eciesencrypt.out, info->pk.eciesencrypt.outSz, info->pk.eciesencrypt.ctx, info->pk.eciesencrypt.compressed); - /* reset devId */ - info->pk.eciesencrypt.privKey->devId = devIdArg; + /* put back the caller's device */ + if (eCtx != NULL) + (void)wc_ecc_ctx_set_dev_id(eCtx, savedDevId); } else if (info->pk.type == WC_PK_TYPE_ECIES_DECRYPT) { - info->pk.eciesdecrypt.privKey->devId = INVALID_DEVID; + ecEncCtx* eCtx = info->pk.eciesdecrypt.ctx; + int savedDevId = INVALID_DEVID; + + if (eCtx != NULL) { + (void)wc_ecc_ctx_get_dev_id(eCtx, &savedDevId); + (void)wc_ecc_ctx_set_dev_id(eCtx, INVALID_DEVID); + } ret = wc_ecc_decrypt(info->pk.eciesdecrypt.privKey, info->pk.eciesdecrypt.pubKey, info->pk.eciesdecrypt.msg, info->pk.eciesdecrypt.msgSz, info->pk.eciesdecrypt.out, info->pk.eciesdecrypt.outSz, info->pk.eciesdecrypt.ctx); - info->pk.eciesdecrypt.privKey->devId = devIdArg; + if (eCtx != NULL) + (void)wc_ecc_ctx_set_dev_id(eCtx, savedDevId); } #endif /* HAVE_ECC_ENCRYPT */ else if (info->pk.type == WC_PK_TYPE_EC_GET_SIZE) { From f8d0ef5e328c78ab8bf08ca4fec1d522365b9b17 Mon Sep 17 00:00:00 2001 From: night1rider Date: Fri, 4 Sep 2026 16:29:53 -0600 Subject: [PATCH 3/9] ASU Versal Gen2: explain the ECIES context device id Update the README and turn on WC_BENCH_ECIES_KDF in the Vitis template. --- .../wolfssl/src/user_settings_template.h | 6 +++ .../src/port/xilinx/versal_gen2_asu/README.md | 51 +++++++++++++++---- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/IDE/XilinxSDK/vitis_sdt/lib/sw_services/wolfssl/src/user_settings_template.h b/IDE/XilinxSDK/vitis_sdt/lib/sw_services/wolfssl/src/user_settings_template.h index 3fbf9ae6b29..9b62d064d9a 100644 --- a/IDE/XilinxSDK/vitis_sdt/lib/sw_services/wolfssl/src/user_settings_template.h +++ b/IDE/XilinxSDK/vitis_sdt/lib/sw_services/wolfssl/src/user_settings_template.h @@ -104,6 +104,12 @@ #define USE_CERT_BUFFERS_256 #define BENCH_EMBEDDED +/* Adds a second set of ECIES benchmark rows, tagged -kdf, set up the way the + * ASU needs (KDF salt and info, no MAC salt). Without it the only ECIES rows + * are the salt-exchange ones, which the port always turns down, so the + * benchmark would show no ECIES number that reaches the hardware. */ +#define WC_BENCH_ECIES_KDF + /* Uncomment for a build with only wolfCrypt (no TLS layer). */ /* #define WOLFCRYPT_ONLY */ diff --git a/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md b/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md index ed4b9036248..9ddcbf82aee 100644 --- a/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md +++ b/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md @@ -13,13 +13,17 @@ Define this in `user_settings.h`: #define WOLFSSL_VERSAL_GEN2_ASU ``` -That is the whole setup. `wolfCrypt_Init()` registers the device and brings the -ASU client up, so an application needs no ASU calls of its own: +That is the whole setup for everything except ECIES. `wolfCrypt_Init()` +registers the device and brings the ASU client up, so an application needs no +ASU calls of its own: ```c ret = wolfCrypt_Init(); /* opens the mailbox, calls XAsu_ClientInit */ ``` +ECIES is the one exception: it routes on its own context's device id, which the +application has to set. See "What ECIES needs to reach the ASU" below. + The BSP must have the `xilasu` and `xilmailbox` libraries enabled. ## What runs on hardware @@ -35,7 +39,7 @@ The BSP must have the `xilasu` and `xilmailbox` libraries enabled. | EdDSA | plain Ed25519 and Ed448 sign and verify | | ECDH | the same curves as ECDSA | | X25519 / X448 | key agreement, Vitis 2026.1 and later | -| ECIES | AES-GCM with HKDF-SHA256 | +| ECIES | AES-GCM with HKDF-SHA256, and only with a context device id, see below | | TRNG | seed and random block | Anything outside this list is declined and wolfSSL runs it in software. That @@ -67,7 +71,7 @@ Other switches: | Macro | Effect | | --- | --- | -| `WOLFSSL_VERSAL_GEN2_ASU_DEVID` | device id for the callback, default `0x4153` | +| `WOLFSSL_VERSAL_GEN2_ASU_DEVID` | device id for the callback, default `0x4153`; also the value to hand `wc_ecc_ctx_set_dev_id` for ECIES | | `WOLFSSL_VERSAL_GEN2_ASU_IPI_BASEADDR` | IPI channel, default `XPAR_XIPIPSU_0_BASEADDR` | | `WOLFSSL_VERSAL_GEN2_ASU_NO_CLIENT_INIT` | the application calls `XAsu_ClientInit` itself | | `WOLFSSL_VERSAL_GEN2_ASU_NO_RSA_PAD` | RSA on, padding in software | @@ -81,7 +85,9 @@ Other switches: The port sets `WOLF_CRYPTO_CB`, `WOLF_CRYPTO_CB_CMD`, `WOLF_CRYPTO_CB_COPY` and `WOLF_CRYPTO_CB_FREE` for you, and points `WC_USE_DEVID` at the ASU device so -the unmodified wolfCrypt test and benchmark route through it. +the unmodified wolfCrypt test and benchmark route through it. `WC_USE_DEVID` +lands on keys and crypto objects at init; ECIES does not read it from there, so +the test and benchmark hand it to each ECIES context themselves. ## Which Vitis release @@ -130,6 +136,10 @@ declines to software however long the message is. Build the benchmark with `AES_AUTH_ADD_SZ` set to 16 to keep those rows on hardware; wolfSSL already does that for the first-generation Versal port for the same reason. +**ECIES needs its own device id on the context.** A device id on the ECC key +does not route ECIES; without `wc_ecc_ctx_set_dev_id` it runs in software and +says nothing. See below. + **ECIES needs the KDF context path.** See below. **ECIES needs `WOLFSSL_ECIES_GEN_IV`, and only offloads one direction.** See @@ -184,10 +194,25 @@ authenticated data, which the ASU cannot accept, so the port declines and wolfSSL runs ECIES in software. There is no way around this from the port. Declining is not the same as running with no hardware. The software ECIES path -still passes the context's device id to the AES and HMAC underneath, so those -operations go to the ASU one at a time. Only the single-command ECIES is lost. -This too depends on `wc_ecc_ctx_set_dev_id`: a context without a device runs -entirely in software, AES and HMAC included, whatever the key says. +still passes the context's device id to the AES and to the MAC HMAC underneath, +so those operations go to the ASU one at a time. Only the single-command ECIES +is lost. + +Which pieces reach the ASU on that path is worth being exact about, because +they answer to two different device ids: + +| Stage | Routes on | On the ASU? | +| --- | --- | --- | +| ECDH shared secret | the key's devId (`wc_ecc_init_ex`) | yes, via `asu_ecdh.c` | +| HKDF-SHA256 | nothing | no, always software | +| AES-GCM / AES-CBC DEM | the context devId | yes, with `wc_ecc_ctx_set_dev_id` | +| MAC HMAC | the context devId | yes, with `wc_ecc_ctx_set_dev_id` | + +So a context with no device id does not mean "no hardware": the ECDH inside the +software path still lands on the ASU whenever the key carries the device id. It +does mean the AES and the MAC run in software. The KDF is a separate matter -- +`wc_HKDF()` takes no device id at all, so HKDF-SHA256 stays in software however +both ids are set. **The private key passed to encrypt is not used.** `wc_ecc_encrypt` takes a private key, and software derives the shared secret from it and puts its public @@ -200,7 +225,13 @@ supply. ## What ECIES needs to reach the ASU -Two things on top of the table above. +Three things on top of the table above. + +**The context must carry the device id.** `wc_ecc_ctx_set_dev_id(ctx, +WOLFSSL_VERSAL_GEN2_ASU_DEVID)`, on every context, in both directions. ECIES +takes its device from the context and never from the ECC key, so a key opened +with `wc_ecc_init_ex(&key, heap, WOLFSSL_VERSAL_GEN2_ASU_DEVID)` is not enough +on its own. Miss it and ECIES still works, in software, without a diagnostic. **The build must use `WOLFSSL_ECIES_GEN_IV`.** The ASU puts the GCM nonce in the message, which is what that mode does. `WOLFSSL_ECIES_OLD` and From aa1fdb3f1d83e0d7ace9d284aaae7cae266b02d0 Mon Sep 17 00:00:00 2001 From: night1rider Date: Fri, 4 Sep 2026 16:40:08 -0600 Subject: [PATCH 4/9] ECIES: run the HKDF step on the context's device too Use wc_HKDF_ex() with the same heap and devId as the AES and HMAC steps. --- wolfcrypt/src/ecc.c | 28 +++++++----- .../src/port/xilinx/versal_gen2_asu/README.md | 43 ++++++++++--------- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index d92357d9271..fdb5d7d7736 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -15878,15 +15878,19 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, sharedSz += pubKeySz; #endif switch (ctx->kdfAlgo) { + /* Use the _ex form so the KDF runs on the context's device, like + * the cipher and MAC do. wc_HKDF() would always use software. + * wc_X963_KDF() below takes no device, so it stays in software. */ case ecHKDF_SHA256 : - ret = wc_HKDF(WC_SHA256, sharedSecret, sharedSz, ctx->kdfSalt, - ctx->kdfSaltSz, ctx->kdfInfo, ctx->kdfInfoSz, - keys, (word32)keysLen); + ret = wc_HKDF_ex(WC_SHA256, sharedSecret, sharedSz, + ctx->kdfSalt, ctx->kdfSaltSz, ctx->kdfInfo, + ctx->kdfInfoSz, keys, (word32)keysLen, + privKey->heap, eciesDevId); break; case ecHKDF_SHA1 : - ret = wc_HKDF(WC_SHA, sharedSecret, sharedSz, ctx->kdfSalt, + ret = wc_HKDF_ex(WC_SHA, sharedSecret, sharedSz, ctx->kdfSalt, ctx->kdfSaltSz, ctx->kdfInfo, ctx->kdfInfoSz, - keys, (word32)keysLen); + keys, (word32)keysLen, privKey->heap, eciesDevId); break; #if defined(HAVE_X963_KDF) && !defined(NO_HASH_WRAPPER) case ecKDF_X963_SHA1 : @@ -16393,15 +16397,19 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, sharedSz += pubKeySz; #endif switch (ctx->kdfAlgo) { + /* Use the _ex form so the KDF runs on the context's device, like + * the cipher and MAC do. wc_HKDF() would always use software. + * wc_X963_KDF() below takes no device, so it stays in software. */ case ecHKDF_SHA256 : - ret = wc_HKDF(WC_SHA256, sharedSecret, sharedSz, ctx->kdfSalt, - ctx->kdfSaltSz, ctx->kdfInfo, ctx->kdfInfoSz, - keys, (word32)keysLen); + ret = wc_HKDF_ex(WC_SHA256, sharedSecret, sharedSz, + ctx->kdfSalt, ctx->kdfSaltSz, ctx->kdfInfo, + ctx->kdfInfoSz, keys, (word32)keysLen, + privKey->heap, eciesDevId); break; case ecHKDF_SHA1 : - ret = wc_HKDF(WC_SHA, sharedSecret, sharedSz, ctx->kdfSalt, + ret = wc_HKDF_ex(WC_SHA, sharedSecret, sharedSz, ctx->kdfSalt, ctx->kdfSaltSz, ctx->kdfInfo, ctx->kdfInfoSz, - keys, (word32)keysLen); + keys, (word32)keysLen, privKey->heap, eciesDevId); break; #if defined(HAVE_X963_KDF) && !defined(NO_HASH_WRAPPER) case ecKDF_X963_SHA1 : diff --git a/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md b/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md index 9ddcbf82aee..ff473175cbf 100644 --- a/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md +++ b/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md @@ -21,7 +21,7 @@ ASU calls of its own: ret = wolfCrypt_Init(); /* opens the mailbox, calls XAsu_ClientInit */ ``` -ECIES is the one exception: it routes on its own context's device id, which the +ECIES is the one exception: it uses its own context's device id, which the application has to set. See "What ECIES needs to reach the ASU" below. The BSP must have the `xilasu` and `xilmailbox` libraries enabled. @@ -39,7 +39,7 @@ The BSP must have the `xilasu` and `xilmailbox` libraries enabled. | EdDSA | plain Ed25519 and Ed448 sign and verify | | ECDH | the same curves as ECDSA | | X25519 / X448 | key agreement, Vitis 2026.1 and later | -| ECIES | AES-GCM with HKDF-SHA256, and only with a context device id, see below | +| ECIES | AES-GCM with HKDF-SHA256; needs a context device id, see below | | TRNG | seed and random block | Anything outside this list is declined and wolfSSL runs it in software. That @@ -71,7 +71,7 @@ Other switches: | Macro | Effect | | --- | --- | -| `WOLFSSL_VERSAL_GEN2_ASU_DEVID` | device id for the callback, default `0x4153`; also the value to hand `wc_ecc_ctx_set_dev_id` for ECIES | +| `WOLFSSL_VERSAL_GEN2_ASU_DEVID` | device id for the callback, default `0x4153` | | `WOLFSSL_VERSAL_GEN2_ASU_IPI_BASEADDR` | IPI channel, default `XPAR_XIPIPSU_0_BASEADDR` | | `WOLFSSL_VERSAL_GEN2_ASU_NO_CLIENT_INIT` | the application calls `XAsu_ClientInit` itself | | `WOLFSSL_VERSAL_GEN2_ASU_NO_RSA_PAD` | RSA on, padding in software | @@ -137,8 +137,8 @@ declines to software however long the message is. Build the benchmark with does that for the first-generation Versal port for the same reason. **ECIES needs its own device id on the context.** A device id on the ECC key -does not route ECIES; without `wc_ecc_ctx_set_dev_id` it runs in software and -says nothing. See below. +does not count for ECIES; without `wc_ecc_ctx_set_dev_id` it runs in software +and gives no warning. See below. **ECIES needs the KDF context path.** See below. @@ -156,7 +156,7 @@ Use `wc_ecc_ctx_set_kdf_salt`, not `wc_ecc_ctx_set_peer_salt`: ```c ecEncCtx* ctx = wc_ecc_ctx_new(REQ_RESP_CLIENT, &rng); -/* Required: ECIES routes on the context's device, not the key's. */ +/* Required: ECIES uses the context's device, not the key's. */ wc_ecc_ctx_set_dev_id(ctx, WOLFSSL_VERSAL_GEN2_ASU_DEVID); wc_ecc_ctx_set_algo(ctx, ecAES_256_GCM, ecHKDF_SHA256, ecHMAC_SHA256); @@ -179,7 +179,7 @@ What the offload requires: | Setting | Value | | --- | --- | | Build | `WOLFSSL_ECIES_GEN_IV`, with neither `WOLFSSL_ECIES_OLD` nor `WOLFSSL_ECIES_ISO18033` | -| Device | `wc_ecc_ctx_set_dev_id` on the context; a devId on the key does not route ECIES | +| Device | `wc_ecc_ctx_set_dev_id` on the context; the key's devId is not used | | RNG | one on the key or on the context, see below | | Scheme | `ecAES_128_GCM` or `ecAES_256_GCM` with `ecHKDF_SHA256` | | KDF salt | `wc_ecc_ctx_set_kdf_salt`, passed through as given | @@ -198,21 +198,24 @@ still passes the context's device id to the AES and to the MAC HMAC underneath, so those operations go to the ASU one at a time. Only the single-command ECIES is lost. -Which pieces reach the ASU on that path is worth being exact about, because -they answer to two different device ids: +It helps to be exact about which pieces reach the ASU on that path, because +they use two different device ids: -| Stage | Routes on | On the ASU? | +| Stage | Uses | On the ASU? | | --- | --- | --- | -| ECDH shared secret | the key's devId (`wc_ecc_init_ex`) | yes, via `asu_ecdh.c` | -| HKDF-SHA256 | nothing | no, always software | -| AES-GCM / AES-CBC DEM | the context devId | yes, with `wc_ecc_ctx_set_dev_id` | -| MAC HMAC | the context devId | yes, with `wc_ecc_ctx_set_dev_id` | +| ECDH shared secret | the key's devId | yes, via `asu_ecdh.c` | +| HKDF-SHA256 KDF | the context devId | yes, via `asu_hmac.c` | +| AES-GCM / AES-CBC DEM | the context devId | yes, via `asu_cipher.c` | +| MAC HMAC | the context devId | yes, via `asu_hmac.c` | -So a context with no device id does not mean "no hardware": the ECDH inside the -software path still lands on the ASU whenever the key carries the device id. It -does mean the AES and the MAC run in software. The KDF is a separate matter -- -`wc_HKDF()` takes no device id at all, so HKDF-SHA256 stays in software however -both ids are set. +All four need their device id set to reach the ASU, and the first one uses a +different id from the other three. So a context with no device id does not +mean "no hardware": the ECDH still lands on the ASU whenever the key carries +the device id, while the KDF, cipher and MAC fall back to software. + +The X9.63 and plain-hash KDFs are the exception -- `wc_X963_KDF()` takes no +device id, so `ecKDF_X963_*` and `ecKDF_*` stay in software whatever is set. +The ASU path uses HKDF-SHA256, so this does not affect it. **The private key passed to encrypt is not used.** `wc_ecc_encrypt` takes a private key, and software derives the shared secret from it and puts its public @@ -231,7 +234,7 @@ Three things on top of the table above. WOLFSSL_VERSAL_GEN2_ASU_DEVID)`, on every context, in both directions. ECIES takes its device from the context and never from the ECC key, so a key opened with `wc_ecc_init_ex(&key, heap, WOLFSSL_VERSAL_GEN2_ASU_DEVID)` is not enough -on its own. Miss it and ECIES still works, in software, without a diagnostic. +on its own. Miss it and ECIES still works, in software, with no warning. **The build must use `WOLFSSL_ECIES_GEN_IV`.** The ASU puts the GCM nonce in the message, which is what that mode does. `WOLFSSL_ECIES_OLD` and From f4395f0076ea8202f40902e62bea9ccefa06ffe7 Mon Sep 17 00:00:00 2001 From: night1rider Date: Tue, 8 Sep 2026 10:28:24 -0600 Subject: [PATCH 5/9] ECIES: decrypt in the devId test before checking the plaintext The last step compared old plaintext without decrypting first. --- tests/api/test_ecc.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index cdb200db49c..39b19421ac7 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -2430,6 +2430,17 @@ int test_wc_ecc_ecies_devid_not_inherited(void) ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, &outSz, cliCtx), 0); ExpectIntEQ(cbInvoked, 1); + cbInvoked = 0; + XMEMSET(plain, 0, sizeof(plain)); +#ifdef WOLFSSL_ECIES_OLD + ExpectIntEQ(wc_ecc_decrypt(&srvKey, &cliKey, out, outSz, plain, &plainSz, + srvCtx), 0); +#else + ExpectIntEQ(wc_ecc_decrypt(&srvKey, NULL, out, outSz, plain, &plainSz, + srvCtx), 0); +#endif + ExpectIntEQ(cbInvoked, 1); + ExpectIntEQ(plainSz, sizeof(msg)); ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); wc_ecc_ctx_free(srvCtx); From f29cfab16c726e1a038126121498daf577986a49 Mon Sep 17 00:00:00 2001 From: night1rider Date: Wed, 9 Sep 2026 16:36:03 -0600 Subject: [PATCH 6/9] ECIES test: clear the plaintext buffer before the no-context decrypt Zero the buffer and check the output size so a decrypt that writes nothing cannot pass on the previous stage's plaintext. --- tests/api/test_ecc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 39b19421ac7..09ca19284a4 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -2393,6 +2393,7 @@ int test_wc_ecc_ecies_devid_not_inherited(void) /* (2) no context at all: still software. */ cbInvoked = 0; + XMEMSET(plain, 0, sizeof(plain)); outSz = (word32)sizeof(out); plainSz = (word32)sizeof(plain); ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, &outSz, @@ -2406,6 +2407,7 @@ int test_wc_ecc_ecies_devid_not_inherited(void) NULL), 0); #endif ExpectIntEQ(cbInvoked, 0); + ExpectIntEQ(plainSz, sizeof(msg)); ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); /* (3) Check: same keys, device now set on the contexts. Without this From 175cb9d1f8fc3769d64b7809bd94a6c99746c2fa Mon Sep 17 00:00:00 2001 From: night1rider Date: Wed, 9 Sep 2026 16:36:03 -0600 Subject: [PATCH 7/9] ECIES test: check the device step counts for encrypt and decrypt separately Reset the counters between the two calls so each direction has to route its KDF, cipher and MAC through the device on its own. --- tests/api/test_ecc.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 09ca19284a4..81474ff90f8 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -2586,12 +2586,26 @@ int test_wc_ecc_ecies_ctx_devid_steps(void) ExpectIntEQ(wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt), 0); ExpectIntEQ(wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt), 0); - XMEMSET(&cnt, 0, sizeof(cnt)); XMEMSET(plain, 0, sizeof(plain)); outSz = (word32)sizeof(out); plainSz = (word32)sizeof(plain); + + /* Count each direction on its own so neither can hide the other. */ + XMEMSET(&cnt, 0, sizeof(cnt)); ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, &outSz, cliCtx), 0); + if (useDev) { + ExpectIntGT(cnt.kdf, 0); + ExpectIntGT(cnt.cipher, 0); + ExpectIntGT(cnt.hmac, 0); + } + else { + ExpectIntEQ(cnt.kdf, 0); + ExpectIntEQ(cnt.cipher, 0); + ExpectIntEQ(cnt.hmac, 0); + } + + XMEMSET(&cnt, 0, sizeof(cnt)); #ifdef WOLFSSL_ECIES_OLD ExpectIntEQ(wc_ecc_decrypt(&srvKey, &cliKey, out, outSz, plain, &plainSz, srvCtx), 0); From ae9623c1c7977166104381c54162713ecfee836b Mon Sep 17 00:00:00 2001 From: night1rider Date: Wed, 9 Sep 2026 16:56:49 -0600 Subject: [PATCH 8/9] ECIES: note that the WOLF_CRYPTO_CB_FIND finder still applies to an unset device id The context never takes the device from the ECC key, but with the finder built in an unset id is still routed the same way as any other wolfCrypt call. --- doc/dox_comments/header_files/ecc.h | 4 +++- wolfcrypt/src/ecc.c | 22 +++++++------------ .../src/port/xilinx/versal_gen2_asu/README.md | 6 +++-- wolfssl/wolfcrypt/ecc.h | 5 ++--- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/doc/dox_comments/header_files/ecc.h b/doc/dox_comments/header_files/ecc.h index 62ba3c46ad1..3885413b0ef 100644 --- a/doc/dox_comments/header_files/ecc.h +++ b/doc/dox_comments/header_files/ecc.h @@ -1902,7 +1902,9 @@ int wc_ecc_ctx_set_algo(ecEncCtx* ctx, byte encAlgo, byte kdfAlgo, callback to be reached. The value is used both for the whole-operation ECIES callback and for the AES/HMAC steps of the software path. Passing a NULL context to wc_ecc_encrypt() or wc_ecc_decrypt() always - means software. The setting is kept across wc_ecc_ctx_reset(). + means software. When WOLF_CRYPTO_CB_FIND is defined, an unset device id + still goes through the registered finder, as it does for every other + wolfCrypt operation. The setting is kept across wc_ecc_ctx_reset(). \return 0 Returned upon successfully setting the device id. \return BAD_FUNC_ARG Returned if the given context is NULL. diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index fdb5d7d7736..cf9f60e8f6f 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -15098,10 +15098,8 @@ struct ecEncCtx { word32 kdfInfoSz; /* size of kdfInfo */ word32 macSaltSz; /* size of macSalt */ #ifdef WOLF_CRYPTO_CB - /* Device that ECIES runs on for this context. It is NOT copied from the - * ECC key. Until wc_ecc_ctx_set_dev_id() is called it stays at - * INVALID_DEVID and ECIES runs in software, even if the key has a - * device. */ + /* Device for ECIES. Not copied from the ECC key: unset means software, + * or the WOLF_CRYPTO_CB_FIND finder, even if the key has a device. */ int devId; #endif void* heap; /* heap hint for memory used */ @@ -15207,10 +15205,8 @@ int wc_ecc_ctx_get_rng(ecEncCtx* ctx, WC_RNG** rng) return 0; } -/* Pick the device that ECIES uses for this context. The context does not - * copy the ECC key's devId, so until this is called ECIES runs in software, - * even for a key that has a device. Pass INVALID_DEVID to go back to - * software. The value is kept across wc_ecc_ctx_reset(). */ +/* Pick the device that ECIES uses; it is never copied from the ECC key. Unset + * means software, or the WOLF_CRYPTO_CB_FIND finder. Kept across ctx reset. */ int wc_ecc_ctx_set_dev_id(ecEncCtx* ctx, int devId) { if (ctx == NULL) @@ -15728,9 +15724,8 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, byte* encKey = NULL; byte* encIv = NULL; byte* macKey = NULL; - /* devId for the ECIES crypto callback and for the AES/HMAC steps. It - * comes only from the context, never from privKey->devId. No context, or - * no wc_ecc_ctx_set_dev_id() call, means software. */ + /* Device for the ECIES callback and the KDF/AES/HMAC steps. It comes only + * from the context; unset means software, or the finder with CB_FIND. */ int eciesDevId = INVALID_DEVID; if (privKey == NULL || pubKey == NULL || msg == NULL || out == NULL || @@ -16187,9 +16182,8 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, byte* encKey = NULL; const byte* encIv = NULL; byte* macKey = NULL; - /* devId for the ECIES crypto callback and for the AES/HMAC steps. It - * comes only from the context, never from privKey->devId. No context, or - * no wc_ecc_ctx_set_dev_id() call, means software. */ + /* Device for the ECIES callback and the KDF/AES/HMAC steps. It comes only + * from the context; unset means software, or the finder with CB_FIND. */ int eciesDevId = INVALID_DEVID; diff --git a/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md b/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md index ff473175cbf..4baf3081ccf 100644 --- a/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md +++ b/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md @@ -138,7 +138,8 @@ does that for the first-generation Versal port for the same reason. **ECIES needs its own device id on the context.** A device id on the ECC key does not count for ECIES; without `wc_ecc_ctx_set_dev_id` it runs in software -and gives no warning. See below. +and gives no warning (with `WOLF_CRYPTO_CB_FIND` the finder still applies). See +below. **ECIES needs the KDF context path.** See below. @@ -234,7 +235,8 @@ Three things on top of the table above. WOLFSSL_VERSAL_GEN2_ASU_DEVID)`, on every context, in both directions. ECIES takes its device from the context and never from the ECC key, so a key opened with `wc_ecc_init_ex(&key, heap, WOLFSSL_VERSAL_GEN2_ASU_DEVID)` is not enough -on its own. Miss it and ECIES still works, in software, with no warning. +on its own. Miss it and ECIES still works, in software, with no warning, +unless a `WOLF_CRYPTO_CB_FIND` finder routes it to a device. **The build must use `WOLFSSL_ECIES_GEN_IV`.** The ASU puts the GCM nonce in the message, which is what that mode does. `WOLFSSL_ECIES_OLD` and diff --git a/wolfssl/wolfcrypt/ecc.h b/wolfssl/wolfcrypt/ecc.h index 6664c34d334..efa57e4290f 100644 --- a/wolfssl/wolfcrypt/ecc.h +++ b/wolfssl/wolfcrypt/ecc.h @@ -1139,9 +1139,8 @@ WOLFSSL_API int wc_ecc_ctx_get_protocol(ecEncCtx* ctx, int* protocol); WOLFSSL_API int wc_ecc_ctx_get_rng(ecEncCtx* ctx, WC_RNG** rng); -/* Device that ECIES runs on. The context never copies the ECC key's devId, - * so without wc_ecc_ctx_set_dev_id() ECIES runs in software. The value is - * kept across wc_ecc_ctx_reset(). */ +/* Device that ECIES runs on; never copied from the ECC key. Unset means + * software, or the WOLF_CRYPTO_CB_FIND finder. Kept across ctx reset. */ WOLFSSL_API int wc_ecc_ctx_set_dev_id(ecEncCtx* ctx, int devId); WOLFSSL_API From a35a633f8d9d6368b4023559d096cff05cb9c359 Mon Sep 17 00:00:00 2001 From: night1rider Date: Wed, 9 Sep 2026 19:30:15 -0600 Subject: [PATCH 9/9] ECIES: keep the KDF step in software The KDF is the only step that hands ECIES temporaries to a device and ECIES cannot wait for a pending result, so it now always runs in software while the cipher and MAC still use the context device. --- doc/dox_comments/header_files/ecc.h | 3 ++- tests/api/test_ecc.c | 18 +++++++-------- wolfcrypt/src/ecc.c | 22 +++++++++---------- .../src/port/xilinx/versal_gen2_asu/README.md | 16 +++++++------- 4 files changed, 28 insertions(+), 31 deletions(-) diff --git a/doc/dox_comments/header_files/ecc.h b/doc/dox_comments/header_files/ecc.h index 3885413b0ef..2df1a2fefda 100644 --- a/doc/dox_comments/header_files/ecc.h +++ b/doc/dox_comments/header_files/ecc.h @@ -1900,7 +1900,8 @@ int wc_ecc_ctx_set_algo(ecEncCtx* ctx, byte encAlgo, byte kdfAlgo, starts at INVALID_DEVID, meaning software: ECIES does not copy the device from the private key, so this must be called for a crypto callback to be reached. The value is used both for the whole-operation - ECIES callback and for the AES/HMAC steps of the software path. + ECIES callback and for the AES/HMAC steps of the software path. The KDF + step always runs in software. Passing a NULL context to wc_ecc_encrypt() or wc_ecc_decrypt() always means software. When WOLF_CRYPTO_CB_FIND is defined, an unset device id still goes through the registered finder, as it does for every other diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 81474ff90f8..deccdf5f57d 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -2491,12 +2491,12 @@ static int myEciesStepCountCb(int devIdArg, wc_CryptoInfo* info, void* ctx) #endif /* - * The software ECIES path hands the context devId to its KDF, cipher and MAC - * steps. The other ECIES tests never see this: their callbacks take the whole - * job and clear the devId. Here the callback turns down the whole job but - * counts the KDF, cipher and HMAC steps. With a device on the context the - * counts must go up; without one they must stay at zero. Both HKDF hashes run, - * which also covers the ecHKDF_SHA1 case. + * The software ECIES path hands the context devId to its cipher and MAC + * steps, never to its KDF. The other ECIES tests never see this: their + * callbacks take the whole job and clear the devId. Here the callback turns + * down the whole job but counts the KDF, cipher and HMAC steps. With a device + * on the context the cipher and HMAC counts must go up and the KDF count must + * stay at zero; without one they must all stay at zero. Both HKDF hashes run. */ int test_wc_ecc_ecies_ctx_devid_steps(void) { @@ -2594,13 +2594,12 @@ int test_wc_ecc_ecies_ctx_devid_steps(void) XMEMSET(&cnt, 0, sizeof(cnt)); ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, &outSz, cliCtx), 0); + ExpectIntEQ(cnt.kdf, 0); if (useDev) { - ExpectIntGT(cnt.kdf, 0); ExpectIntGT(cnt.cipher, 0); ExpectIntGT(cnt.hmac, 0); } else { - ExpectIntEQ(cnt.kdf, 0); ExpectIntEQ(cnt.cipher, 0); ExpectIntEQ(cnt.hmac, 0); } @@ -2615,13 +2614,12 @@ int test_wc_ecc_ecies_ctx_devid_steps(void) #endif ExpectIntEQ(plainSz, sizeof(msg)); ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); + ExpectIntEQ(cnt.kdf, 0); if (useDev) { - ExpectIntGT(cnt.kdf, 0); ExpectIntGT(cnt.cipher, 0); ExpectIntGT(cnt.hmac, 0); } else { - ExpectIntEQ(cnt.kdf, 0); ExpectIntEQ(cnt.cipher, 0); ExpectIntEQ(cnt.hmac, 0); } diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index cf9f60e8f6f..a66d3920178 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -15724,7 +15724,7 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, byte* encKey = NULL; byte* encIv = NULL; byte* macKey = NULL; - /* Device for the ECIES callback and the KDF/AES/HMAC steps. It comes only + /* Device for the ECIES callback and the AES/HMAC steps. It comes only * from the context; unset means software, or the finder with CB_FIND. */ int eciesDevId = INVALID_DEVID; @@ -15873,19 +15873,18 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, sharedSz += pubKeySz; #endif switch (ctx->kdfAlgo) { - /* Use the _ex form so the KDF runs on the context's device, like - * the cipher and MAC do. wc_HKDF() would always use software. - * wc_X963_KDF() below takes no device, so it stays in software. */ + /* The KDF hands ECIES temporaries to a callback and cannot wait + * for a pending result, so it always runs in software. */ case ecHKDF_SHA256 : ret = wc_HKDF_ex(WC_SHA256, sharedSecret, sharedSz, ctx->kdfSalt, ctx->kdfSaltSz, ctx->kdfInfo, ctx->kdfInfoSz, keys, (word32)keysLen, - privKey->heap, eciesDevId); + privKey->heap, INVALID_DEVID); break; case ecHKDF_SHA1 : ret = wc_HKDF_ex(WC_SHA, sharedSecret, sharedSz, ctx->kdfSalt, ctx->kdfSaltSz, ctx->kdfInfo, ctx->kdfInfoSz, - keys, (word32)keysLen, privKey->heap, eciesDevId); + keys, (word32)keysLen, privKey->heap, INVALID_DEVID); break; #if defined(HAVE_X963_KDF) && !defined(NO_HASH_WRAPPER) case ecKDF_X963_SHA1 : @@ -16182,7 +16181,7 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, byte* encKey = NULL; const byte* encIv = NULL; byte* macKey = NULL; - /* Device for the ECIES callback and the KDF/AES/HMAC steps. It comes only + /* Device for the ECIES callback and the AES/HMAC steps. It comes only * from the context; unset means software, or the finder with CB_FIND. */ int eciesDevId = INVALID_DEVID; @@ -16391,19 +16390,18 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, sharedSz += pubKeySz; #endif switch (ctx->kdfAlgo) { - /* Use the _ex form so the KDF runs on the context's device, like - * the cipher and MAC do. wc_HKDF() would always use software. - * wc_X963_KDF() below takes no device, so it stays in software. */ + /* The KDF hands ECIES temporaries to a callback and cannot wait + * for a pending result, so it always runs in software. */ case ecHKDF_SHA256 : ret = wc_HKDF_ex(WC_SHA256, sharedSecret, sharedSz, ctx->kdfSalt, ctx->kdfSaltSz, ctx->kdfInfo, ctx->kdfInfoSz, keys, (word32)keysLen, - privKey->heap, eciesDevId); + privKey->heap, INVALID_DEVID); break; case ecHKDF_SHA1 : ret = wc_HKDF_ex(WC_SHA, sharedSecret, sharedSz, ctx->kdfSalt, ctx->kdfSaltSz, ctx->kdfInfo, ctx->kdfInfoSz, - keys, (word32)keysLen, privKey->heap, eciesDevId); + keys, (word32)keysLen, privKey->heap, INVALID_DEVID); break; #if defined(HAVE_X963_KDF) && !defined(NO_HASH_WRAPPER) case ecKDF_X963_SHA1 : diff --git a/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md b/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md index 4baf3081ccf..d8005263e88 100644 --- a/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md +++ b/wolfcrypt/src/port/xilinx/versal_gen2_asu/README.md @@ -205,18 +205,18 @@ they use two different device ids: | Stage | Uses | On the ASU? | | --- | --- | --- | | ECDH shared secret | the key's devId | yes, via `asu_ecdh.c` | -| HKDF-SHA256 KDF | the context devId | yes, via `asu_hmac.c` | +| HKDF-SHA256 KDF | none, always software | no | | AES-GCM / AES-CBC DEM | the context devId | yes, via `asu_cipher.c` | | MAC HMAC | the context devId | yes, via `asu_hmac.c` | -All four need their device id set to reach the ASU, and the first one uses a -different id from the other three. So a context with no device id does not -mean "no hardware": the ECDH still lands on the ASU whenever the key carries -the device id, while the KDF, cipher and MAC fall back to software. +The ECDH, cipher and MAC need their device id set to reach the ASU, and the +ECDH uses a different id from the other two. So a context with no device id +does not mean "no hardware": the ECDH still lands on the ASU whenever the key +carries the device id, while the cipher and MAC fall back to software. -The X9.63 and plain-hash KDFs are the exception -- `wc_X963_KDF()` takes no -device id, so `ecKDF_X963_*` and `ecKDF_*` stay in software whatever is set. -The ASU path uses HKDF-SHA256, so this does not affect it. +The KDF step always runs in software whatever is set. It hands ECIES +temporaries to the callback and ECIES cannot wait for a pending result, so it +is never offloaded on this path. **The private key passed to encrypt is not used.** `wc_ecc_encrypt` takes a private key, and software derives the shared secret from it and puts its public