From ccd8f555dba6c8255414358b9c08fb003f4b6cd9 Mon Sep 17 00:00:00 2001 From: night1rider Date: Fri, 21 Aug 2026 14:07:45 -0600 Subject: [PATCH 1/4] ECIES: support X25519 and X448 keys Select the key type with wc_ecc_ctx_set_curve_id(), then use the new wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2(); the ecc_key entry points keep their ABI. The ephemeral key is a raw little-endian u-coordinate (RFC 7748). --- doc/dox_comments/header_files/ecc.h | 170 +++++++ tests/api/test_ecc.c | 254 +++++++++- tests/api/test_ecc.h | 6 + tests/unit-mcdc/test_ecc_whitebox.c | 33 +- wolfcrypt/src/ecc.c | 705 +++++++++++++++++++++++----- wolfcrypt/test/test.c | 661 ++++++++++++++++++++++++++ wolfssl/wolfcrypt/cryptocb.h | 5 + wolfssl/wolfcrypt/ecc.h | 46 ++ 8 files changed, 1745 insertions(+), 135 deletions(-) diff --git a/doc/dox_comments/header_files/ecc.h b/doc/dox_comments/header_files/ecc.h index a0210c23ae..586d0602cb 100644 --- a/doc/dox_comments/header_files/ecc.h +++ b/doc/dox_comments/header_files/ecc.h @@ -1888,6 +1888,69 @@ 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 selects the type of key an ecEncCtx object operates + on. It can optionally be called after wc_ecc_ctx_new. + + ECC_CURVE_DEF, the default, means the key pointers handed to the ECIES + functions are ecc_key pointers. ECC_X25519 and ECC_X448 mean they are + curve25519_key and curve448_key pointers respectively, in which case they + must be passed to wc_ecc_encrypt_ex2 and wc_ecc_decrypt_ex2 - the + ecc_key-typed wc_ecc_encrypt, wc_ecc_encrypt_ex and wc_ecc_decrypt reject a + context configured for a Montgomery curve. + + Individual ECC curves are not selectable here: for an ecc_key the curve is + carried by the key itself. This is a key type selector only. + + \return 0 Returned upon successfully setting the key type. + \return NOT_COMPILED_IN Returned if the curve is compiled in but its ECIES + support is not. See WOLFSSL_ECIES_X25519 and WOLFSSL_ECIES_X448. + \return BAD_FUNC_ARG Returned if the given ecEncCtx object is NULL or + curveId is not one of the three values above. + + \param ctx pointer to the ecEncCtx for which to set the key type + \param curveId ECC_CURVE_DEF, ECC_X25519 or ECC_X448 + + \note The setting survives wc_ecc_ctx_reset, so a context can be reused + across the REQ/RESP rounds without reconfiguring it. + + _Example_ + \code + ecEncCtx* ctx; + // initialize ctx + if (wc_ecc_ctx_set_curve_id(ctx, ECC_X25519) != 0) { + // error setting the key type + } + \endcode + + \sa wc_ecc_ctx_new + \sa wc_ecc_ctx_get_curve_id + \sa wc_ecc_encrypt_ex2 + \sa wc_ecc_decrypt_ex2 +*/ + +int wc_ecc_ctx_set_curve_id(ecEncCtx* ctx, int curveId); + +/*! + \ingroup ECC + + \brief This function reads back the key type configured on an ecEncCtx + object with wc_ecc_ctx_set_curve_id. + + \return 0 Returned upon successfully reading the key type. + \return BAD_FUNC_ARG Returned if either argument is NULL. + + \param ctx pointer to the ecEncCtx to read + \param curveId pointer to an int that receives ECC_CURVE_DEF, ECC_X25519 + or ECC_X448 + + \sa wc_ecc_ctx_set_curve_id +*/ + +int wc_ecc_ctx_get_curve_id(ecEncCtx* ctx, int* curveId); + /*! \ingroup ECC @@ -2243,6 +2306,113 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx); +/*! + \ingroup ECC + + \brief This function encrypts the given message using ECIES, as + wc_ecc_encrypt_ex does, but takes the keys as void pointers so that a + Montgomery-curve key can be used. The type the pointers actually have is + whatever wc_ecc_ctx_set_curve_id selected on ctx: ecc_key for + ECC_CURVE_DEF, curve25519_key for ECC_X25519, curve448_key for ECC_X448. + + For the Montgomery curves the ephemeral public key is placed at the front + of the message as a raw little-endian u-coordinate - 32 bytes for X25519, + 56 for X448 - with no format byte and no compression. This is the RFC 7748 + encoding used by the TLS key_share extension and by HPKE. + + \return 0 Returned upon successfully encrypting the message. + \return BAD_FUNC_ARG Returned if privKey, pubKey, msg, out or outSz is + NULL, or if compressed is set for a Montgomery curve. + \return BUFFER_E Returned if the supplied output buffer is too small. + \return NOT_COMPILED_IN Returned if the selected DEM or curve support is + not built in. + + \param privKey pointer to the sender's private key, of the type ctx + selects. This is normally an ephemeral key, fresh for each message. + \param pubKey pointer to the peer's public key, of the type ctx selects + \param msg pointer to the buffer holding the message to encrypt + \param msgSz size of the buffer to encrypt + \param out pointer to the buffer in which to store the ciphertext + \param outSz pointer to a word32 holding the available size in out; on + success, holds the number of bytes written + \param ctx pointer to an ecEncCtx object. Mandatory for the Montgomery + curves: the key type cannot be recovered from a NULL context, and passing + NULL there would make this function read a curve25519_key or curve448_key + as though it were an ecc_key. + \param compressed whether to export the ephemeral public key as a + compressed point. ECC only; must be 0 for a Montgomery curve. + + \note The crypto callback (WOLF_CRYPTO_CB) ECIES hooks take ecc_key + pointers and are therefore bypassed for the Montgomery curves. + + _Example_ + \code + byte msg[32]; // padded to the DEM block size + byte out[CURVE25519_PUB_KEY_SIZE + sizeof(msg) + WC_SHA256_DIGEST_SIZE]; + word32 outSz = sizeof(out); + curve25519_key eph, peer; + ecEncCtx* ctx; + // initialize eph with a fresh key pair and peer with the peer public key + + ctx = wc_ecc_ctx_new(0, &rng); + if (wc_ecc_ctx_set_curve_id(ctx, ECC_X25519) != 0) { + // error selecting the key type + } + if (wc_ecc_encrypt_ex2(&eph, &peer, msg, sizeof(msg), out, &outSz, ctx, 0) + != 0) { + // error encrypting message + } + \endcode + + \sa wc_ecc_decrypt_ex2 + \sa wc_ecc_ctx_set_curve_id + \sa wc_ecc_encrypt_ex +*/ + +int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, + word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, int compressed); + +/*! + \ingroup ECC + + \brief This function decrypts a message produced by wc_ecc_encrypt_ex2. + The keys are void pointers whose actual type is whatever + wc_ecc_ctx_set_curve_id selected on ctx. + + For the Montgomery curves the peer's ephemeral public key is read from the + front of the message and validated - wc_curve25519_check_public or + wc_curve448_check_public - before the shared secret is derived. That check + requires a canonical encoding with the high bit clear, which is what + wc_ecc_encrypt_ex2 produces. + + \return 0 Returned upon successfully decrypting the message. + \return BAD_FUNC_ARG Returned if privKey, msg, out or outSz is NULL, or + the input is too short to hold a message. + \return BUFFER_E Returned if the supplied output buffer is too small. + \return HASH_TYPE_E Returned if the message MAC does not verify. + \return AES_GCM_AUTH_E Returned if an AES-GCM DEM fails to authenticate. + + \param privKey pointer to the recipient's private key, of the type ctx + selects + \param pubKey optional pointer to storage of the type ctx selects, which + receives the peer's ephemeral public key. May be NULL, in which case + temporary storage is used. + \param msg pointer to the ciphertext to decrypt + \param msgSz size of the ciphertext + \param out pointer to the buffer in which to store the plaintext + \param outSz pointer to a word32 holding the available size in out; on + success, holds the number of bytes written + \param ctx pointer to an ecEncCtx object. Mandatory for the Montgomery + curves, for the same reason as in wc_ecc_encrypt_ex2. + + \sa wc_ecc_encrypt_ex2 + \sa wc_ecc_ctx_set_curve_id + \sa wc_ecc_decrypt +*/ + +int wc_ecc_decrypt_ex2(void* privKey, void* pubKey, const byte* msg, + word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx); + /*! \ingroup ECC diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 0550d9adc2..c559aa02fe 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -31,6 +31,12 @@ #include #include #include +#ifdef HAVE_CURVE25519 + #include +#endif +#ifdef HAVE_CURVE448 + #include +#endif #include #include @@ -1566,9 +1572,15 @@ int test_wc_ecc_ctx_new(void) cli = NULL; wc_ecc_ctx_free(srv); + /* No protocol is valid: there is no REQ/RESP salt exchange, but the + * context still carries the algorithms and the key type. It matches the + * default context the encrypt/decrypt paths build internally. */ + ExpectNotNull(cli = wc_ecc_ctx_new(0, &rng)); + wc_ecc_ctx_free(cli); + cli = NULL; + /* Test bad args. */ /* wc_ecc_ctx_new_ex() will free if returned NULL. */ - ExpectNull(cli = wc_ecc_ctx_new(0, &rng)); ExpectNull(cli = wc_ecc_ctx_new(REQ_RESP_CLIENT, NULL)); DoExpectIntEQ(wc_FreeRng(&rng), 0); @@ -1868,6 +1880,246 @@ int test_wc_ecc_encryptDecrypt(void) return EXPECT_RESULT(); } /* END test_wc_ecc_encryptDecrypt */ +/* + * Testing wc_ecc_ctx_set_curve_id()/wc_ecc_ctx_get_curve_id(): bad arguments, + * curves that are not compiled in, and that the setting survives the context + * reset that the REQ/RESP flow performs between rounds. + */ +int test_wc_ecc_ctx_set_curve_id(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_NO_MALLOC) + WC_RNG rng; + ecEncCtx* ctx = NULL; + int curveId = ECC_CURVE_INVALID; + + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng(&rng), 0); + + ExpectIntEQ(wc_ecc_ctx_set_curve_id(NULL, ECC_CURVE_DEF), BAD_FUNC_ARG); + ExpectIntEQ(wc_ecc_ctx_get_curve_id(NULL, &curveId), BAD_FUNC_ARG); + + ExpectNotNull(ctx = wc_ecc_ctx_new(0, &rng)); + ExpectIntEQ(wc_ecc_ctx_get_curve_id(ctx, NULL), BAD_FUNC_ARG); + + /* Defaults to the ecc_key path. */ + ExpectIntEQ(wc_ecc_ctx_get_curve_id(ctx, &curveId), 0); + ExpectIntEQ(curveId, ECC_CURVE_DEF); + + /* A named ECC curve is not a key type - the curve comes from the key. */ + ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_SECP256R1), BAD_FUNC_ARG); + ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, -2), BAD_FUNC_ARG); + +#ifdef HAVE_CURVE25519 + #ifdef WOLFSSL_ECIES_X25519 + ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_X25519), 0); + ExpectIntEQ(wc_ecc_ctx_get_curve_id(ctx, &curveId), 0); + ExpectIntEQ(curveId, ECC_X25519); + + /* The key type has to survive a reset; the REQ/RESP flow resets between + * rounds and reverting to ECC_CURVE_DEF would be a type confusion. */ + ExpectIntEQ(wc_ecc_ctx_reset(ctx, &rng), 0); + ExpectIntEQ(wc_ecc_ctx_get_curve_id(ctx, &curveId), 0); + ExpectIntEQ(curveId, ECC_X25519); + + /* The typed entry points stay ECC-only. */ + ExpectIntEQ(wc_ecc_encrypt(NULL, NULL, NULL, 0, NULL, NULL, ctx), + BAD_FUNC_ARG); + ExpectIntEQ(wc_ecc_decrypt(NULL, NULL, NULL, 0, NULL, NULL, ctx), + BAD_FUNC_ARG); + #else + ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_X25519), NOT_COMPILED_IN); + #endif +#endif +#ifdef HAVE_CURVE448 + #ifdef WOLFSSL_ECIES_X448 + ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_X448), 0); + ExpectIntEQ(wc_ecc_ctx_get_curve_id(ctx, &curveId), 0); + ExpectIntEQ(curveId, ECC_X448); + #else + ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_X448), NOT_COMPILED_IN); + #endif +#endif + + /* Back to the default. */ + ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_CURVE_DEF), 0); + ExpectIntEQ(wc_ecc_ctx_get_curve_id(ctx, &curveId), 0); + ExpectIntEQ(curveId, ECC_CURVE_DEF); + + wc_ecc_ctx_free(ctx); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ctx_set_curve_id */ + +/* + * Testing ECIES with an X25519 key: round trip, the on-the-wire encoding of + * the ephemeral public key, rejection of a tampered message, and rejection of + * point compression (meaningless on a Montgomery curve). + */ +int test_wc_ecc_ecies_x25519(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && \ + defined(WOLFSSL_ECIES_X25519) && !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_ECIES_OLD) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_AES_128) && defined(HAVE_HKDF) && \ + !defined(WOLFSSL_NO_MALLOC) + WC_RNG rng; + curve25519_key ephKey; + curve25519_key srvKey; + ecEncCtx* ctx = NULL; + const char* msg = "EccBlock Size 16"; + word32 msgSz = (word32)XSTRLEN("EccBlock Size 16"); + /* ephemeral public key | [IV, GEN_IV only] | ciphertext | HMAC. + * The DEM here is AES-128-CBC, so the IV is one AES block. */ + byte out[CURVE25519_PUB_KEY_SIZE + AES_BLOCK_SIZE + + AES_BLOCK_SIZE + WC_SHA256_DIGEST_SIZE]; + word32 outSz = (word32)sizeof(out); + byte plain[sizeof("EccBlock Size 16") + AES_BLOCK_SIZE]; + word32 plainSz = (word32)sizeof(plain); + byte wire[CURVE25519_PUB_KEY_SIZE]; + word32 wireSz = (word32)sizeof(wire); + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&ephKey, 0, sizeof(ephKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + XMEMSET(out, 0, sizeof(out)); + XMEMSET(plain, 0, sizeof(plain)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_curve25519_init(&ephKey), 0); + ExpectIntEQ(wc_curve25519_init(&srvKey), 0); + ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, &ephKey), 0); + ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, &srvKey), 0); + + ExpectNotNull(ctx = wc_ecc_ctx_new(0, &rng)); + ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_X25519), 0); + + ExpectIntEQ(wc_ecc_encrypt_ex2(&ephKey, &srvKey, (const byte*)msg, msgSz, + out, &outSz, ctx, 0), 0); +#ifdef WOLFSSL_ECIES_GEN_IV + ExpectIntEQ(outSz, CURVE25519_PUB_KEY_SIZE + AES_BLOCK_SIZE + msgSz + + WC_SHA256_DIGEST_SIZE); +#else + ExpectIntEQ(outSz, + CURVE25519_PUB_KEY_SIZE + msgSz + WC_SHA256_DIGEST_SIZE); +#endif + + /* The message opens with the ephemeral public key as a raw little-endian + * u-coordinate (RFC 7748), with no format byte. */ + ExpectIntEQ(wc_curve25519_export_public_ex(&ephKey, wire, &wireSz, + EC25519_LITTLE_ENDIAN), 0); + ExpectIntEQ(XMEMCMP(wire, out, CURVE25519_PUB_KEY_SIZE), 0); + + /* Point compression does not apply to a Montgomery curve. */ + { + byte tmp[sizeof(out)]; + word32 tmpSz = (word32)sizeof(tmp); + ExpectIntEQ(wc_ecc_encrypt_ex2(&ephKey, &srvKey, (const byte*)msg, + msgSz, tmp, &tmpSz, ctx, 1), BAD_FUNC_ARG); + } + + ExpectIntEQ(wc_ecc_decrypt_ex2(&srvKey, NULL, out, outSz, plain, &plainSz, + ctx), 0); + ExpectIntEQ(plainSz, msgSz); + ExpectIntEQ(XMEMCMP(msg, plain, msgSz), 0); + + /* Corrupting the ciphertext must fail the MAC. */ + out[outSz - 1] ^= 0x01; + plainSz = (word32)sizeof(plain); + ExpectIntNE(wc_ecc_decrypt_ex2(&srvKey, NULL, out, outSz, plain, &plainSz, + ctx), 0); + out[outSz - 1] ^= 0x01; + + /* So must corrupting the ephemeral public key. */ + out[0] ^= 0x01; + plainSz = (word32)sizeof(plain); + ExpectIntNE(wc_ecc_decrypt_ex2(&srvKey, NULL, out, outSz, plain, &plainSz, + ctx), 0); + + wc_ecc_ctx_free(ctx); + wc_curve25519_free(&srvKey); + wc_curve25519_free(&ephKey); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ecies_x25519 */ + +/* + * Testing ECIES with an X448 key: round trip and the on-the-wire encoding of + * the ephemeral public key. + */ +int test_wc_ecc_ecies_x448(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && \ + defined(WOLFSSL_ECIES_X448) && !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_ECIES_OLD) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_AES_128) && defined(HAVE_HKDF) && \ + !defined(WOLFSSL_NO_MALLOC) + WC_RNG rng; + curve448_key ephKey; + curve448_key srvKey; + ecEncCtx* ctx = NULL; + const char* msg = "EccBlock Size 16"; + word32 msgSz = (word32)XSTRLEN("EccBlock Size 16"); + /* ephemeral public key | [IV, GEN_IV only] | ciphertext | HMAC. */ + byte out[CURVE448_PUB_KEY_SIZE + AES_BLOCK_SIZE + + AES_BLOCK_SIZE + WC_SHA256_DIGEST_SIZE]; + word32 outSz = (word32)sizeof(out); + byte plain[sizeof("EccBlock Size 16") + AES_BLOCK_SIZE]; + word32 plainSz = (word32)sizeof(plain); + byte wire[CURVE448_PUB_KEY_SIZE]; + word32 wireSz = (word32)sizeof(wire); + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&ephKey, 0, sizeof(ephKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + XMEMSET(out, 0, sizeof(out)); + XMEMSET(plain, 0, sizeof(plain)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_curve448_init(&ephKey), 0); + ExpectIntEQ(wc_curve448_init(&srvKey), 0); + ExpectIntEQ(wc_curve448_make_key(&rng, CURVE448_KEY_SIZE, &ephKey), 0); + ExpectIntEQ(wc_curve448_make_key(&rng, CURVE448_KEY_SIZE, &srvKey), 0); + + ExpectNotNull(ctx = wc_ecc_ctx_new(0, &rng)); + ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_X448), 0); + + ExpectIntEQ(wc_ecc_encrypt_ex2(&ephKey, &srvKey, (const byte*)msg, msgSz, + out, &outSz, ctx, 0), 0); +#ifdef WOLFSSL_ECIES_GEN_IV + ExpectIntEQ(outSz, CURVE448_PUB_KEY_SIZE + AES_BLOCK_SIZE + msgSz + + WC_SHA256_DIGEST_SIZE); +#else + ExpectIntEQ(outSz, CURVE448_PUB_KEY_SIZE + msgSz + WC_SHA256_DIGEST_SIZE); +#endif + + ExpectIntEQ(wc_curve448_export_public_ex(&ephKey, wire, &wireSz, + EC448_LITTLE_ENDIAN), 0); + ExpectIntEQ(XMEMCMP(wire, out, CURVE448_PUB_KEY_SIZE), 0); + + ExpectIntEQ(wc_ecc_decrypt_ex2(&srvKey, NULL, out, outSz, plain, &plainSz, + ctx), 0); + ExpectIntEQ(plainSz, msgSz); + ExpectIntEQ(XMEMCMP(msg, plain, msgSz), 0); + + out[outSz - 1] ^= 0x01; + plainSz = (word32)sizeof(plain); + ExpectIntNE(wc_ecc_decrypt_ex2(&srvKey, NULL, out, outSz, plain, &plainSz, + ctx), 0); + + wc_ecc_ctx_free(ctx); + wc_curve448_free(&srvKey); + wc_curve448_free(&ephKey); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ecies_x448 */ + /* * Testing ECIES with the AES-256-GCM DEM. Exercises, each with its own * single-use client/server ctx pair: diff --git a/tests/api/test_ecc.h b/tests/api/test_ecc.h index 61fd06dd00..29e7a367a6 100644 --- a/tests/api/test_ecc.h +++ b/tests/api/test_ecc.h @@ -55,7 +55,10 @@ int test_wc_ecc_ctx_reset(void); int test_wc_ecc_ctx_set_peer_salt(void); int test_wc_ecc_ctx_set_info(void); int test_wc_ecc_ctx_getters(void); +int test_wc_ecc_ctx_set_curve_id(void); int test_wc_ecc_encryptDecrypt(void); +int test_wc_ecc_ecies_x25519(void); +int test_wc_ecc_ecies_x448(void); int test_wc_ecc_ecies_gcm(void); int test_wc_ecc_ecies_gcm_no_rng(void); int test_wc_ecc_ecies_cryptocb(void); @@ -105,7 +108,10 @@ int test_wc_EccDecisionCoverage4(void); TEST_DECL_GROUP("ecc", test_wc_ecc_ctx_set_peer_salt), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ctx_set_info), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ctx_getters), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_ctx_set_curve_id), \ TEST_DECL_GROUP("ecc", test_wc_ecc_encryptDecrypt), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_x25519), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_x448), \ 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), \ diff --git a/tests/unit-mcdc/test_ecc_whitebox.c b/tests/unit-mcdc/test_ecc_whitebox.c index 8f6e9d6466..11d809afd9 100644 --- a/tests/unit-mcdc/test_ecc_whitebox.c +++ b/tests/unit-mcdc/test_ecc_whitebox.c @@ -196,17 +196,19 @@ static void wb_import_private_key_ex(void) * * Both public callers (wc_ecc_ctx_set_own_salt via REQ_RESP_CLIENT/SERVER, * ecc_ctx_init) always pass a live ctx and a hard-coded nonzero flags - * (REQ_RESP_CLIENT/REQ_RESP_SERVER), so flags==0 can never be observed from - * the API; ctx==NULL is likewise never forwarded by any caller (they all - * either early-return on their own NULL check or pass &localCtx). + * (REQ_RESP_CLIENT/REQ_RESP_SERVER); wc_ecc_ctx_reset() skips the call + * entirely when protocol==0, so flags==0 can never be observed from the API. + * ctx==NULL is likewise never forwarded by any caller (they all either + * early-return on their own NULL check or pass &localCtx). * * Classes 4-6: ecEncCtx.protocol == 0 halves of the get_own_salt / * set_peer_salt / set_own_salt guards (lines ~14506, ~14554, ~14646). The - * only public constructor, wc_ecc_ctx_new()/wc_ecc_ctx_new_ex(), always sets - * ctx->protocol to REQ_RESP_CLIENT or REQ_RESP_SERVER (or fails and frees the - * ctx), so a live ctx with protocol==0 does not exist on any API path. Build - * one directly here since ecEncCtx's full definition is only visible inside - * this TU (it is an opaque forward-declared type in ecc.h). + * public constructor wc_ecc_ctx_new()/wc_ecc_ctx_new_ex() does accept + * flags==0 - such a context carries the algorithms and the key type but takes + * no part in the REQ/RESP salt exchange - and these three functions are the + * ones that reject it. Build the ctx directly here since ecEncCtx's full + * definition is only visible inside this TU (it is an opaque + * forward-declared type in ecc.h). * ------------------------------------------------------------------------- */ static void wb_ctx_set_salt(void) { @@ -1645,16 +1647,23 @@ static void wb_arg_guards(void) } } - /* ecc_public_key_size: key == NULL || key->dp == NULL */ + /* ecies_pub_key_size: the ECC branch rejects a NULL key and a key with no + * domain parameters, both of which wc_ecc_size() reports as size 0. (This + * guard used to live in ecc_public_key_size(), which the key-type dispatch + * for ECIES replaced.) */ +#if defined(HAVE_ECC_ENCRYPT) && !defined(WOLFSSL_ECIES_OLD) { const ecc_set_type* savedDp = key.dp; + word32 pubSz = 0; - (void)ecc_public_key_size(NULL, &sz); + (void)ecies_pub_key_size(NULL, NULL, 0, &pubSz); key.dp = NULL; - (void)ecc_public_key_size(&key, &sz); + (void)ecies_pub_key_size(NULL, &key, 0, &pubSz); key.dp = savedDp; - (void)ecc_public_key_size(&key, &sz); + (void)ecies_pub_key_size(NULL, &key, 0, &pubSz); + (void)ecies_pub_key_size(NULL, &key, 1, &pubSz); } +#endif /* The accepting vectors below need the real curve constants: a zero * modulus/order would make wc_ecc_gen_deterministic_k's RFC 6979 retry diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 5497cdb312..f99c59678f 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -236,6 +236,13 @@ ECC Curve Sizes: #include #include + + #ifdef HAVE_CURVE25519 + #include + #endif + #ifdef HAVE_CURVE448 + #include + #endif #endif #ifdef WOLF_CRYPTO_CB @@ -13003,20 +13010,6 @@ int wc_ecc_import_raw(ecc_key* key, const char* qx, const char* qy, } #endif /* HAVE_ECC_KEY_IMPORT */ -#if defined(HAVE_ECC_ENCRYPT) && !defined(WOLFSSL_ECIES_OLD) -/* public key size in octets */ -static int ecc_public_key_size(ecc_key* key, word32* sz) -{ - if (key == NULL || key->dp == NULL) - return BAD_FUNC_ARG; - - /* 'Uncompressed' | x | y */ - *sz = 1 + 2 * (word32)key->dp->size; - - return 0; -} -#endif - /* key size in octets */ WOLFSSL_ABI int wc_ecc_size(ecc_key* key) @@ -15013,6 +15006,10 @@ struct ecEncCtx { byte protocol; /* are we REQ_RESP client or server ? */ byte cliSt; /* protocol state, for sanity checks */ byte srvSt; /* protocol state, for sanity checks */ + int curveId; /* key type the key pointers refer to: + * ECC_CURVE_DEF -> ecc_key (default), + * ECC_X25519 -> curve25519_key, + * ECC_X448 -> curve448_key */ WC_RNG* rng; }; @@ -15029,6 +15026,65 @@ int wc_ecc_ctx_set_algo(ecEncCtx* ctx, byte encAlgo, byte kdfAlgo, byte macAlgo) return 0; } +/* Select the type of key the context operates on. + * + * ECC_CURVE_DEF (the default) means the key pointers handed to the ECIES + * functions are ecc_key*. ECC_X25519 and ECC_X448 mean they are + * curve25519_key* and curve448_key* respectively, in which case they have to + * be passed through wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2() - the typed + * wc_ecc_encrypt()/wc_ecc_decrypt() entry points stay ECC-only. + * + * Individual ECC curves are not selectable here: for an ecc_key the curve is + * carried by the key itself. This is a key-type selector only. + * + * Returns 0 on success, NOT_COMPILED_IN when the curve is built but its ECIES + * support is not, and BAD_FUNC_ARG otherwise. */ +int wc_ecc_ctx_set_curve_id(ecEncCtx* ctx, int curveId) +{ + int ret = 0; + + if (ctx == NULL) + return BAD_FUNC_ARG; + + switch (curveId) { + case ECC_CURVE_DEF: + break; + #ifdef HAVE_CURVE25519 + case ECC_X25519: + #ifndef WOLFSSL_ECIES_X25519 + ret = NOT_COMPILED_IN; + #endif + break; + #endif + #ifdef HAVE_CURVE448 + case ECC_X448: + #ifndef WOLFSSL_ECIES_X448 + ret = NOT_COMPILED_IN; + #endif + break; + #endif + default: + ret = BAD_FUNC_ARG; + break; + } + + if (ret == 0) + ctx->curveId = curveId; + + return ret; +} + +/* Read back the key type configured with wc_ecc_ctx_set_curve_id(). */ +int wc_ecc_ctx_get_curve_id(ecEncCtx* ctx, int* curveId) +{ + if (ctx == NULL || curveId == NULL) + return BAD_FUNC_ARG; + + *curveId = ctx->curveId; + + return 0; +} + #ifdef WOLF_CRYPTO_CB /* Read back the parameters a caller configured on the context. Intended for * crypto-callback backends (e.g. a hardware ECIES engine) that must reproduce @@ -15294,15 +15350,28 @@ WOLFSSL_ABI int wc_ecc_ctx_reset(ecEncCtx* ctx, WC_RNG* rng) { void* heap; + int curveId; 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. + * The key type has to survive too: reset exists so a context can be reused + * without a free/new cycle, and silently reverting to ECC_CURVE_DEF would + * make the next call reinterpret a Montgomery key as an ecc_key. */ heap = ctx->heap; + curveId = ctx->curveId; ecc_ctx_init(ctx, ctx->protocol, rng); ctx->heap = heap; + ctx->curveId = curveId; + + /* The exchange salts only exist for the REQ/RESP protocol. A context with + * no protocol is still useful - it is how non-default algorithms and the + * key type are carried - and matches the default context the encrypt and + * decrypt paths build internally when none is supplied. */ + if (ctx->protocol == 0) + return 0; return ecc_ctx_set_salt(ctx, ctx->protocol); } @@ -15317,6 +15386,9 @@ ecEncCtx* wc_ecc_ctx_new_ex(int flags, WC_RNG* rng, void* heap) if (ctx) { ctx->protocol = (byte)flags; ctx->heap = heap; + /* wc_ecc_ctx_reset() preserves curveId, so it must not be XMALLOC + * garbage by the time it is called. */ + ctx->curveId = ECC_CURVE_DEF; } ret = wc_ecc_ctx_reset(ctx, rng); @@ -15504,14 +15576,361 @@ static int ecc_ctx_decrypt_advance(ecEncCtx* ctx) } +/* Key-type dispatch for ECIES. + * + * Everything from the shared secret onwards - the KDF, the DEM and the MAC - + * is key-type agnostic. Only the handful of operations below actually care + * whether the caller handed us an ecc_key, a curve25519_key or a curve448_key, + * so they are funnelled through these helpers. ctx->curveId, set with + * wc_ecc_ctx_set_curve_id(), says which it is; ECC_CURVE_DEF (the default) + * means ecc_key and behaves exactly as it always has. */ + +/* Are the key pointers Montgomery-curve keys rather than ecc_key? */ +static int ecies_is_mont(ecEncCtx* ctx) +{ + int isMont = 0; + + (void)ctx; +#ifdef WOLFSSL_ECIES_X25519 + if (ctx != NULL && ctx->curveId == ECC_X25519) + isMont = 1; +#endif +#ifdef WOLFSSL_ECIES_X448 + if (ctx != NULL && ctx->curveId == ECC_X448) + isMont = 1; +#endif + + return isMont; +} + +/* Heap hint to give the AES/HMAC primitives and the peer key. */ +static void* ecies_heap(ecEncCtx* ctx, void* privKey) +{ + if (ecies_is_mont(ctx)) { + /* wc_curve25519_init_ex() discards the heap hint it is given and + * curve448_key has no heap field at all, so neither key can supply + * one. The context's hint is what the surrounding allocations in + * this file already use. */ + return ctx->heap; + } + + return ((ecc_key*)privKey)->heap; +} + +/* devId to hand the DEM AES/HMAC primitives. */ +static int ecies_devid(ecEncCtx* ctx, void* privKey) +{ + int devId = INVALID_DEVID; + + (void)ctx; + (void)privKey; + +#ifdef WOLFSSL_ECIES_X25519 + if (ctx != NULL && ctx->curveId == ECC_X25519) { + #ifdef WOLF_CRYPTO_CB + /* curve25519_key only carries a devId with WOLF_CRYPTO_CB. */ + devId = ((curve25519_key*)privKey)->devId; + #endif + } + else +#endif +#ifdef WOLFSSL_ECIES_X448 + if (ctx != NULL && ctx->curveId == ECC_X448) { + /* curve448_key has no devId field. */ + } + else +#endif + { + #if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) + /* ecc_key only carries a devId field with these. */ + devId = ((ecc_key*)privKey)->devId; + #endif + } + + return devId; +} + +/* Give the private key an RNG for blinding / timing resistance, where the key + * type has somewhere to keep one. */ +static int ecies_key_set_rng(ecEncCtx* ctx, void* privKey) +{ + int ret = 0; + + (void)ctx; + (void)privKey; + +#ifdef WOLFSSL_ECIES_X25519 + if (ctx != NULL && ctx->curveId == ECC_X25519) { + #ifdef WOLFSSL_CURVE25519_BLINDING + if (ctx->rng != NULL) + ret = wc_curve25519_set_rng((curve25519_key*)privKey, ctx->rng); + #endif + } + else +#endif +#ifdef WOLFSSL_ECIES_X448 + if (ctx != NULL && ctx->curveId == ECC_X448) { + /* curve448 has no blinding RNG. */ + } + else +#endif + { + #ifdef ECC_TIMING_RESISTANT + if (ctx != NULL && ctx->rng != NULL && + ((ecc_key*)privKey)->rng == NULL) { + ((ecc_key*)privKey)->rng = ctx->rng; + } + #endif + } + + return ret; +} + +#if !defined(WOLFSSL_ECIES_OLD) && defined(WOLFSSL_ECIES_GEN_IV) +/* RNG to generate the message IV/nonce with. */ +static WC_RNG* ecies_rng(ecEncCtx* ctx, void* privKey) +{ + (void)privKey; + +#ifdef ECC_TIMING_RESISTANT + if (!ecies_is_mont(ctx) && ((ecc_key*)privKey)->rng != NULL) + return ((ecc_key*)privKey)->rng; +#endif + + return (ctx != NULL) ? ctx->rng : NULL; +} +#endif /* !WOLFSSL_ECIES_OLD && WOLFSSL_ECIES_GEN_IV */ + +#ifndef WOLFSSL_ECIES_OLD +/* Storage for a peer's ephemeral public key, big enough for whichever key + * type ctx->curveId selects. */ +typedef union { + ecc_key ecc; +#ifdef WOLFSSL_ECIES_X25519 + curve25519_key x25519; +#endif +#ifdef WOLFSSL_ECIES_X448 + curve448_key x448; +#endif +} ecies_peer_key; + +/* Size of the ephemeral public key as it appears in the message. + * + * For an ecc_key this is the X9.63 point encoding, so it depends on whether + * the point is compressed. Montgomery keys are a bare u-coordinate of fixed + * size with no leading format byte, so 'compressed' does not apply. */ +static int ecies_pub_key_size(ecEncCtx* ctx, void* privKey, int compressed, + word32* pubKeySz) +{ + (void)ctx; + (void)compressed; + +#ifdef WOLFSSL_ECIES_X25519 + if (ctx != NULL && ctx->curveId == ECC_X25519) { + *pubKeySz = CURVE25519_PUB_KEY_SIZE; + return 0; + } +#endif +#ifdef WOLFSSL_ECIES_X448 + if (ctx != NULL && ctx->curveId == ECC_X448) { + *pubKeySz = CURVE448_PUB_KEY_SIZE; + return 0; + } +#endif + + { + /* wc_ecc_size() rather than dp->size directly: with a crypto callback + * that implements key export, the size comes from the device. */ + int sz = wc_ecc_size((ecc_key*)privKey); + + if (sz <= 0) + return BAD_FUNC_ARG; + + /* 'Uncompressed' | x | y, or 'Compressed' | x */ + *pubKeySz = 1 + (word32)sz * (compressed ? 1U : 2U); + } + + return 0; +} + +/* Write the ephemeral public key to the front of the message. */ +static int ecies_export_pub(ecEncCtx* ctx, void* privKey, byte* out, + word32* pubKeySz, int compressed) +{ + int ret; + + (void)ctx; + (void)compressed; + +#ifdef WOLFSSL_ECIES_X25519 + if (ctx != NULL && ctx->curveId == ECC_X25519) { + curve25519_key* key = (curve25519_key*)privKey; + + /* wc_curve25519_export_public_ex() derives the public point from the + * private scalar when it is missing, but does not check that there is + * a private scalar to derive it from - an init-only key would quietly + * export base * 0. */ + if (!key->pubSet && !key->privSet) + return ECC_BAD_ARG_E; + #ifdef WC_X25519_NONBLOCK + /* Non-blocking curve25519 returns FP_WOULDBLOCK, which the ECIES + * flow has no way to resume from. */ + if (key->nb_ctx != NULL) + return NOT_COMPILED_IN; + #endif + return wc_curve25519_export_public_ex(key, out, pubKeySz, + EC25519_LITTLE_ENDIAN); + } +#endif +#ifdef WOLFSSL_ECIES_X448 + if (ctx != NULL && ctx->curveId == ECC_X448) { + return wc_curve448_export_public_ex((curve448_key*)privKey, out, + pubKeySz, EC448_LITTLE_ENDIAN); + } +#endif + + if (((ecc_key*)privKey)->type == ECC_PRIVATEKEY_ONLY) { + #ifdef ECC_TIMING_RESISTANT + ret = wc_ecc_make_pub_ex((ecc_key*)privKey, NULL, + ((ecc_key*)privKey)->rng); + #else + ret = wc_ecc_make_pub_ex((ecc_key*)privKey, NULL, NULL); + #endif + if (ret != 0) + return ret; + } + + return wc_ecc_export_x963_ex((ecc_key*)privKey, out, pubKeySz, compressed); +} + +/* Validate and import the peer's ephemeral public key out of the message. + * + * pubKey is uninitialised storage of the type ctx->curveId selects. */ +static int ecies_peer_import(ecEncCtx* ctx, void* privKey, void* pubKey, + const byte* msg, word32 pubKeySz) +{ + int ret; + +#ifdef WOLFSSL_ECIES_X25519 + if (ctx != NULL && ctx->curveId == ECC_X25519) { + /* wc_curve25519_import_public_ex() does no validation of its own; + * this is the counterpart of the on-curve check that + * wc_ecc_import_x963_ex() performs. */ + ret = wc_curve25519_check_public(msg, pubKeySz, + EC25519_LITTLE_ENDIAN); + if (ret != 0) + return ret; + ret = wc_curve25519_init_ex((curve25519_key*)pubKey, + ecies_heap(ctx, privKey), INVALID_DEVID); + if (ret != 0) + return ret; + return wc_curve25519_import_public_ex(msg, pubKeySz, + (curve25519_key*)pubKey, + EC25519_LITTLE_ENDIAN); + } +#endif +#ifdef WOLFSSL_ECIES_X448 + if (ctx != NULL && ctx->curveId == ECC_X448) { + ret = wc_curve448_check_public(msg, pubKeySz, EC448_LITTLE_ENDIAN); + if (ret != 0) + return ret; + ret = wc_curve448_init((curve448_key*)pubKey); + if (ret != 0) + return ret; + return wc_curve448_import_public_ex(msg, pubKeySz, + (curve448_key*)pubKey, + EC448_LITTLE_ENDIAN); + } +#endif + + ret = wc_ecc_init_ex((ecc_key*)pubKey, ecies_heap(ctx, privKey), + INVALID_DEVID); + if (ret != 0) + return ret; + + return wc_ecc_import_x963_ex(msg, pubKeySz, (ecc_key*)pubKey, + ((ecc_key*)privKey)->dp->id); +} + +/* Free a key of whichever type the context selects. NULL tolerant. */ +static void ecies_key_free(ecEncCtx* ctx, void* key) +{ + (void)ctx; + + if (key == NULL) + return; + +#ifdef WOLFSSL_ECIES_X25519 + if (ctx != NULL && ctx->curveId == ECC_X25519) { + wc_curve25519_free((curve25519_key*)key); + return; + } +#endif +#ifdef WOLFSSL_ECIES_X448 + if (ctx != NULL && ctx->curveId == ECC_X448) { + wc_curve448_free((curve448_key*)key); + return; + } +#endif + + wc_ecc_free((ecc_key*)key); +} +#endif /* !WOLFSSL_ECIES_OLD */ + +/* Derive the ECIES shared secret. */ +static int ecies_shared_secret(ecEncCtx* ctx, void* privKey, void* pubKey, + byte* out, word32* outSz) +{ + int ret; + + (void)ctx; + +#ifdef WOLFSSL_ECIES_X25519 + if (ctx != NULL && ctx->curveId == ECC_X25519) { + #ifdef WC_X25519_NONBLOCK + if (((curve25519_key*)privKey)->nb_ctx != NULL) + return NOT_COMPILED_IN; + #endif + return wc_curve25519_shared_secret_ex((curve25519_key*)privKey, + (curve25519_key*)pubKey, + out, outSz, + EC25519_LITTLE_ENDIAN); + } +#endif +#ifdef WOLFSSL_ECIES_X448 + if (ctx != NULL && ctx->curveId == ECC_X448) { + return wc_curve448_shared_secret_ex((curve448_key*)privKey, + (curve448_key*)pubKey, + out, outSz, EC448_LITTLE_ENDIAN); + } +#endif + + ret = 0; + do { + #if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_ECC) + ret = wc_AsyncWait(ret, &((ecc_key*)privKey)->asyncDev, + WC_ASYNC_FLAG_CALL_AGAIN); + if (ret != 0) + break; + #endif + ret = wc_ecc_shared_secret((ecc_key*)privKey, (ecc_key*)pubKey, out, + outSz); + } + while (ret == WC_NO_ERR_TRACE(WC_PENDING_E)); + + return ret; +} + + /* ecc encrypt with shared secret run through kdf ctx holds non default algos and inputs msgSz should be the right size for encAlgo, i.e., already padded return 0 on success */ -int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, +int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, int compressed) { int ret = 0; + int isMont; word32 blockSz = 0; #ifndef WOLFSSL_ECIES_OLD #ifndef WOLFSSL_ECIES_GEN_IV @@ -15546,21 +15965,35 @@ 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 to hand the DEM AES/HMAC primitives; not every key type carries + * one, so default to INVALID. */ 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 + /* Determine the key type before anything dereferences the key pointers, + * and before ctx is defaulted below - the crypto callback has to keep + * seeing the caller's own ctx pointer. */ + isMont = ecies_is_mont(ctx); + + /* The X9.63 point encoding, and therefore point compression, has no + * meaning for a Montgomery curve. Reject rather than silently ignore an + * explicit request. */ + if (isMont && compressed) + return BAD_FUNC_ARG; + + eciesDevId = ecies_devid(ctx, privKey); #ifdef WOLF_CRYPTO_CB - #ifndef WOLF_CRYPTO_CB_FIND - if (privKey->devId != INVALID_DEVID) + /* wc_CryptoInfo.pk.eciesencrypt is typed ecc_key*, so the callback is + * ECC-only. This test has to stay ahead of the block because + * WOLF_CRYPTO_CB_FIND drops the devId guard entirely. */ + #ifdef WOLF_CRYPTO_CB_FIND + if (!isMont) + #else + if (!isMont && ((ecc_key*)privKey)->devId != INVALID_DEVID) #endif { /* Snapshot single-use state so we can tell whether the callback handled @@ -15568,8 +16001,9 @@ 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((ecc_key*)privKey, (ecc_key*)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 @@ -15603,12 +16037,9 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #endif #ifndef WOLFSSL_ECIES_OLD - if (!compressed) { - pubKeySz = 1 + (word32)wc_ecc_size(privKey) * 2; - } - else { - pubKeySz = 1 + (word32)wc_ecc_size(privKey); - } + ret = ecies_pub_key_size(ctx, privKey, compressed, &pubKeySz); + if (ret != 0) + return ret; #else (void) compressed; /* avoid unused parameter if WOLFSSL_ECIES_OLD is defined */ #endif @@ -15635,22 +16066,12 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, if (*outSz < ecc_ecies_total_size(pubKeySz, ivSz, msgSz, digestSz)) return BUFFER_E; -#ifdef ECC_TIMING_RESISTANT - if (ctx->rng != NULL && privKey->rng == NULL) - privKey->rng = ctx->rng; -#endif + ret = ecies_key_set_rng(ctx, privKey); + if (ret != 0) + return ret; #ifndef WOLFSSL_ECIES_OLD - if (privKey->type == ECC_PRIVATEKEY_ONLY) { -#ifdef ECC_TIMING_RESISTANT - ret = wc_ecc_make_pub_ex(privKey, NULL, privKey->rng); -#else - ret = wc_ecc_make_pub_ex(privKey, NULL, NULL); -#endif - if (ret != 0) - return ret; - } - ret = wc_ecc_export_x963_ex(privKey, out, &pubKeySz, compressed); + ret = ecies_export_pub(ctx, privKey, out, &pubKeySz, compressed); if (ret != 0) return ret; out += pubKeySz; @@ -15673,20 +16094,12 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, sharedSz -= pubKeySz; #endif - do { - #if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_ECC) - ret = wc_AsyncWait(ret, &privKey->asyncDev, WC_ASYNC_FLAG_CALL_AGAIN); - if (ret != 0) - break; - #endif - #ifndef WOLFSSL_ECIES_ISO18033 - ret = wc_ecc_shared_secret(privKey, pubKey, sharedSecret, &sharedSz); - #else - ret = wc_ecc_shared_secret(privKey, pubKey, sharedSecret + pubKeySz, - &sharedSz); - #endif - } - while (ret == WC_NO_ERR_TRACE(WC_PENDING_E)); +#ifndef WOLFSSL_ECIES_ISO18033 + ret = ecies_shared_secret(ctx, privKey, pubKey, sharedSecret, &sharedSz); +#else + ret = ecies_shared_secret(ctx, privKey, pubKey, sharedSecret + pubKeySz, + &sharedSz); +#endif if (ret == 0) { #ifdef WOLFSSL_ECIES_ISO18033 @@ -15741,7 +16154,7 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, encIv = encKey + encKeySz; #elif defined(WOLFSSL_ECIES_GEN_IV) { - WC_RNG* rng = (privKey->rng != NULL) ? privKey->rng : ctx->rng; + WC_RNG* rng = ecies_rng(ctx, privKey); encIv = out; out += ivSz; if (rng == NULL) @@ -15760,11 +16173,17 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, encIv = encKey + encKeySz; macKey = encKey + encKeySz + ivSz; #elif defined(WOLFSSL_ECIES_GEN_IV) - encKey = keys + offset; - encIv = out; - out += ivSz; - macKey = encKey + encKeySz; - ret = wc_RNG_GenerateBlock(privKey->rng, encIv, ivSz); + { + WC_RNG* rng = ecies_rng(ctx, privKey); + encKey = keys + offset; + encIv = out; + out += ivSz; + macKey = encKey + encKeySz; + if (rng == NULL) + ret = MISSING_RNG_E; + else + ret = wc_RNG_GenerateBlock(rng, encIv, (word32)ivSz); + } #else XMEMSET(iv, 0, (size_t)ivSz); encKey = keys + offset; @@ -15790,7 +16209,8 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else Aes aes[1]; #endif - ret = wc_AesInit(aes, privKey->heap, eciesDevId); + ret = wc_AesInit(aes, ecies_heap(ctx, privKey), + eciesDevId); if (ret == 0) { ret = wc_AesSetKey(aes, encKey, (word32)encKeySz, encIv, AES_ENCRYPTION); @@ -15831,7 +16251,8 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, XMEMSET(ctr_iv + WOLFSSL_ECIES_GEN_IV_SIZE, 0, WC_AES_BLOCK_SIZE - WOLFSSL_ECIES_GEN_IV_SIZE); - ret = wc_AesInit(aes, privKey->heap, eciesDevId); + ret = wc_AesInit(aes, ecies_heap(ctx, privKey), + eciesDevId); if (ret == 0) { ret = wc_AesSetKey(aes, encKey, (word32)encKeySz, ctr_iv, AES_ENCRYPTION); @@ -15865,7 +16286,8 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else Aes aes[1]; #endif - ret = wc_AesInit(aes, privKey->heap, eciesDevId); + ret = wc_AesInit(aes, ecies_heap(ctx, privKey), + eciesDevId); if (ret == 0) { ret = wc_AesGcmSetKey(aes, encKey, (word32)encKeySz); if (ret == 0) { @@ -15908,7 +16330,8 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else Hmac hmac[1]; #endif - ret = wc_HmacInit(hmac, privKey->heap, eciesDevId); + ret = wc_HmacInit(hmac, ecies_heap(ctx, privKey), + eciesDevId); if (ret == 0) { ret = wc_HmacSetKey(hmac, WC_SHA256, macKey, WC_SHA256_DIGEST_SIZE); @@ -15951,6 +16374,18 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, ctx holds non default algos and inputs msgSz should be the right size for encAlgo, i.e., already padded return 0 on success */ +int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, + word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, int compressed) +{ + /* This entry point is typed for ecc_key. A context configured for a + * Montgomery curve has to go through wc_ecc_encrypt_ex2(). */ + if (ctx != NULL && ctx->curveId != ECC_CURVE_DEF) + return BAD_FUNC_ARG; + + return wc_ecc_encrypt_ex2(privKey, pubKey, msg, msgSz, out, outSz, ctx, + compressed); +} + WOLFSSL_ABI int wc_ecc_encrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx) @@ -15961,18 +16396,21 @@ int wc_ecc_encrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, /* ecc decrypt with shared secret run through kdf ctx holds non default algos and inputs return 0 on success */ -WOLFSSL_ABI -int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, +int wc_ecc_decrypt_ex2(void* privKey, void* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx) { int ret = 0; + int isMont; word32 blockSz = 0; #ifndef WOLFSSL_ECIES_OLD #ifndef WOLFSSL_ECIES_GEN_IV byte iv[ECC_MAX_IV_SIZE]; #endif word32 pubKeySz = 0; - WC_DECLARE_VAR(peerKey, ecc_key, 1, 0); + /* Storage for the peer's ephemeral key when the caller does not supply + * somewhere to put it. ecc_key is by far the largest member, so this does + * not grow the frame over what it already was. */ + WC_DECLARE_VAR(peerKey, ecies_peer_key, 1, 0); #endif word32 digestSz = 0; ecEncCtx localCtx; @@ -15999,8 +16437,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 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 to hand the DEM AES/HMAC primitives; not every key type carries + * one, so default to INVALID. */ int eciesDevId = INVALID_DEVID; @@ -16011,13 +16449,22 @@ 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 + /* Determine the key type before anything dereferences the key pointers, + * and before ctx is defaulted below - the crypto callback has to keep + * seeing the caller's own ctx pointer. */ + isMont = ecies_is_mont(ctx); + (void)isMont; /* only read by the callback and compressed-point guards */ + + eciesDevId = ecies_devid(ctx, privKey); #ifdef WOLF_CRYPTO_CB - #ifndef WOLF_CRYPTO_CB_FIND - if (privKey->devId != INVALID_DEVID) + /* wc_CryptoInfo.pk.eciesdecrypt is typed ecc_key*, so the callback is + * ECC-only. This test has to stay ahead of the block because + * WOLF_CRYPTO_CB_FIND drops the devId guard entirely. */ + #ifdef WOLF_CRYPTO_CB_FIND + if (!isMont) + #else + if (!isMont && ((ecc_key*)privKey)->devId != INVALID_DEVID) #endif { /* Snapshot single-use state so we can tell whether the callback handled @@ -16025,8 +16472,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((ecc_key*)privKey, (ecc_key*)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. */ @@ -16059,14 +16506,21 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #endif #ifndef WOLFSSL_ECIES_OLD - ret = ecc_public_key_size(privKey, &pubKeySz); - if (ret != 0) - return ret; + { + int compressed = 0; #ifdef HAVE_COMP_KEY - if ((msgSz > 1) && ((msg[0] == 0x02) || (msg[0] == 0x03))) { - pubKeySz = (pubKeySz / 2) + 1; - } + /* A Montgomery public key is a bare u-coordinate with no format byte, + * so 0x02/0x03 is just data there - roughly 1 key in 128 would be + * mistaken for a compressed point. */ + if (!isMont && (msgSz > 1) && + ((msg[0] == 0x02) || (msg[0] == 0x03))) { + compressed = 1; + } #endif /* HAVE_COMP_KEY */ + ret = ecies_pub_key_size(ctx, privKey, compressed, &pubKeySz); + if (ret != 0) + return ret; + } #endif /* WOLFSSL_ECIES_OLD */ if (ctx->protocol == REQ_RESP_CLIENT) { @@ -16129,17 +16583,16 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #endif } -#ifdef ECC_TIMING_RESISTANT - if (ctx->rng != NULL && privKey->rng == NULL) - privKey->rng = ctx->rng; -#endif + ret = ecies_key_set_rng(ctx, privKey); + if (ret != 0) + return ret; #ifdef WOLFSSL_SMALL_STACK sharedSecret = (byte*)XMALLOC(sharedSz, ctx->heap, DYNAMIC_TYPE_ECC_BUFFER); if (sharedSecret == NULL) { #ifndef WOLFSSL_ECIES_OLD - if (pubKey == peerKey) - wc_ecc_free(peerKey); + if (pubKey == (void*)peerKey) + ecies_key_free(ctx, peerKey); #endif return MEMORY_E; } @@ -16148,8 +16601,8 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, if (keys == NULL) { XFREE(sharedSecret, ctx->heap, DYNAMIC_TYPE_ECC_BUFFER); #ifndef WOLFSSL_ECIES_OLD - if (pubKey == peerKey) - wc_ecc_free(peerKey); + if (pubKey == (void*)peerKey) + ecies_key_free(ctx, peerKey); #endif return MEMORY_E; } @@ -16157,20 +16610,17 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #ifndef WOLFSSL_ECIES_OLD if (pubKey == NULL) { - WC_ALLOC_VAR_EX(peerKey, ecc_key, 1, ctx->heap, + WC_ALLOC_VAR_EX(peerKey, ecies_peer_key, 1, ctx->heap, DYNAMIC_TYPE_ECC_BUFFER, ret=MEMORY_E); pubKey = peerKey; } else { /* if a public key was passed in we should free it here before init * and import */ - wc_ecc_free(pubKey); + ecies_key_free(ctx, pubKey); } if (ret == 0) { - ret = wc_ecc_init_ex(pubKey, privKey->heap, INVALID_DEVID); - } - if (ret == 0) { - ret = wc_ecc_import_x963_ex(msg, pubKeySz, pubKey, privKey->dp->id); + ret = ecies_peer_import(ctx, privKey, pubKey, msg, pubKeySz); } if (ret == 0) { /* Point is not MACed. */ @@ -16185,21 +16635,13 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, sharedSz -= pubKeySz; #endif - do { - #if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_ECC) - ret = wc_AsyncWait(ret, &privKey->asyncDev, - WC_ASYNC_FLAG_CALL_AGAIN); - if (ret != 0) - break; - #endif - #ifndef WOLFSSL_ECIES_ISO18033 - ret = wc_ecc_shared_secret(privKey, pubKey, sharedSecret, - &sharedSz); - #else - ret = wc_ecc_shared_secret(privKey, pubKey, sharedSecret + - pubKeySz, &sharedSz); - #endif - } while (ret == WC_NO_ERR_TRACE(WC_PENDING_E)); + #ifndef WOLFSSL_ECIES_ISO18033 + ret = ecies_shared_secret(ctx, privKey, pubKey, sharedSecret, + &sharedSz); + #else + ret = ecies_shared_secret(ctx, privKey, pubKey, + sharedSecret + pubKeySz, &sharedSz); + #endif } if (ret == 0) { #ifdef WOLFSSL_ECIES_ISO18033 @@ -16290,7 +16732,8 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else Hmac hmac[1]; #endif - ret = wc_HmacInit(hmac, privKey->heap, eciesDevId); + ret = wc_HmacInit(hmac, ecies_heap(ctx, privKey), + eciesDevId); if (ret == 0) { ret = wc_HmacSetKey(hmac, WC_SHA256, macKey, WC_SHA256_DIGEST_SIZE); @@ -16340,7 +16783,8 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else Aes aes[1]; #endif - ret = wc_AesInit(aes, privKey->heap, eciesDevId); + ret = wc_AesInit(aes, ecies_heap(ctx, privKey), + eciesDevId); if (ret == 0) { ret = wc_AesSetKey(aes, encKey, (word32)encKeySz, encIv, AES_DECRYPTION); @@ -16372,7 +16816,8 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else Aes aes[1]; #endif - ret = wc_AesInit(aes, privKey->heap, eciesDevId); + ret = wc_AesInit(aes, ecies_heap(ctx, privKey), + eciesDevId); if (ret == 0) { byte ctr_iv[WC_AES_BLOCK_SIZE]; /* Make a 16 byte IV from the bytes passed in. */ @@ -16409,7 +16854,8 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else Aes aes[1]; #endif - ret = wc_AesInit(aes, privKey->heap, eciesDevId); + ret = wc_AesInit(aes, ecies_heap(ctx, privKey), + eciesDevId); if (ret == 0) { ret = wc_AesGcmSetKey(aes, encKey, (word32)encKeySz); if (ret == 0) { @@ -16441,8 +16887,8 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, *outSz = msgSz - digestSz; #ifndef WOLFSSL_ECIES_OLD - if (pubKey == peerKey) - wc_ecc_free(peerKey); + if (pubKey == (void*)peerKey) + ecies_key_free(ctx, peerKey); #endif ForceZero(sharedSecret, sharedSz); ForceZero(keys, (word32)keysLen); @@ -16457,6 +16903,21 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, return ret; } +/* ecc decrypt with shared secret run through kdf + ctx holds non default algos and inputs + return 0 on success */ +WOLFSSL_ABI +int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, + word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx) +{ + /* This entry point is typed for ecc_key. A context configured for a + * Montgomery curve has to go through wc_ecc_decrypt_ex2(). */ + if (ctx != NULL && ctx->curveId != ECC_CURVE_DEF) + return BAD_FUNC_ARG; + + return wc_ecc_decrypt_ex2(privKey, pubKey, msg, msgSz, out, outSz, ctx); +} + #endif /* HAVE_ECC_ENCRYPT */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 0df3ec3a1e..238effd517 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -47696,6 +47696,640 @@ static wc_test_ret_t ecc_encrypt_cryptocb_test(WC_RNG* rng) #endif /* WOLF_CRYPTO_CB && !WOLFSSL_NO_MALLOC */ #endif /* !HAVE_FIPS || FIPS_VERSION_GE(5,3) */ +#ifdef WOLFSSL_ECIES_MONTGOMERY +/* ECIES over a Montgomery curve (X25519 / X448). + * + * The key type is carried on the context, and the keys are handed to the + * generic wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2() entry points. */ +static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, + void* srvKey, word32 pubKeySz) +{ + wc_test_ret_t ret = 0; + ecEncCtx* ctx = NULL; + byte enc[MAX_ECIES_TEST_SZ]; + byte plain[MAX_ECIES_TEST_SZ]; + word32 encSz = (word32)sizeof(enc); + word32 plainSz = (word32)sizeof(plain); + word32 expectSz; + /* The ephemeral public key is only carried in the message outside of OLD + * mode, and GEN_IV additionally embeds the IV. The DEM here is + * AES-128-CBC, so that IV is one AES block. */ +#ifdef WOLFSSL_ECIES_OLD + void* decPubKey = ephKey; +#else + void* decPubKey = NULL; +#endif + /* Multiple of the AES block size: the DEM does no padding of its own. */ + static const byte msg[] = "ECIES on a Montgomery curve"; + const word32 msgSz = 16 * 2; + + ctx = wc_ecc_ctx_new(0, rng); + if (ctx == NULL) + return WC_TEST_RET_ENC_NC; + + ret = wc_ecc_ctx_set_curve_id(ctx, curveId); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + XMEMSET(enc, 0, sizeof(enc)); + XMEMSET(plain, 0, sizeof(plain)); + XMEMCPY(plain, msg, XSTRLEN((const char*)msg) + 1); + + ret = wc_ecc_encrypt_ex2(ephKey, srvKey, plain, msgSz, enc, &encSz, ctx, 0); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + /* [ephemeral public key] [IV] ciphertext | HMAC */ +#ifdef WOLFSSL_ECIES_OLD + expectSz = msgSz + WC_SHA256_DIGEST_SIZE; + (void)pubKeySz; +#elif defined(WOLFSSL_ECIES_GEN_IV) + expectSz = pubKeySz + WC_AES_BLOCK_SIZE + msgSz + WC_SHA256_DIGEST_SIZE; +#else + expectSz = pubKeySz + msgSz + WC_SHA256_DIGEST_SIZE; +#endif + if (encSz != expectSz) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + + /* Point compression is meaningless for a Montgomery curve. */ + { + word32 tmpSz = (word32)sizeof(enc); + byte tmp[MAX_ECIES_TEST_SZ]; + if (wc_ecc_encrypt_ex2(ephKey, srvKey, plain, msgSz, tmp, &tmpSz, ctx, + 1) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + } + + /* The typed entry points stay ECC-only. */ + if (wc_ecc_encrypt(NULL, NULL, plain, msgSz, enc, &encSz, ctx) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + if (wc_ecc_decrypt(NULL, NULL, enc, encSz, plain, &plainSz, ctx) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + + /* The key type has to survive a context reset. */ + ret = wc_ecc_ctx_reset(ctx, rng); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + { + int gotId = ECC_CURVE_INVALID; + ret = wc_ecc_ctx_get_curve_id(ctx, &gotId); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + if (gotId != curveId) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + } + + XMEMSET(plain, 0, sizeof(plain)); + plainSz = (word32)sizeof(plain); + ret = wc_ecc_decrypt_ex2(srvKey, decPubKey, enc, encSz, plain, &plainSz, + ctx); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + if (plainSz != msgSz) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + if (XMEMCMP(plain, msg, XSTRLEN((const char*)msg)) != 0) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + + /* A modified ciphertext must not authenticate. */ + enc[encSz - 1] ^= 0x01; + ret = wc_ecc_ctx_reset(ctx, rng); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + plainSz = (word32)sizeof(plain); + if (wc_ecc_decrypt_ex2(srvKey, decPubKey, enc, encSz, plain, &plainSz, ctx) + == 0) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + enc[encSz - 1] ^= 0x01; + +#ifndef WOLFSSL_ECIES_OLD + /* So must a modified ephemeral public key. */ + enc[0] ^= 0x01; + ret = wc_ecc_ctx_reset(ctx, rng); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + plainSz = (word32)sizeof(plain); + if (wc_ecc_decrypt_ex2(srvKey, decPubKey, enc, encSz, plain, &plainSz, ctx) + == 0) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } +#endif + + ret = 0; + +done: + wc_ecc_ctx_free(ctx); + + return ret; +} +#endif /* WOLFSSL_ECIES_MONTGOMERY */ + +#if defined(WOLFSSL_ECIES_X25519) && !defined(WOLFSSL_ECIES_OLD) && \ + !defined(WOLFSSL_ECIES_GEN_IV) && !defined(WOLFSSL_ECIES_ISO18033) +/* Known answer test for ECIES over X25519. + * + * The keys are the Alice/Bob pair from RFC 7748 section 6.1. The test first + * checks the raw shared secret against the RFC's value, which pins the byte + * order the ECIES code derives it in - a round trip cannot, because both ends + * agree whichever order is used. The ciphertext is then checked against a + * fixed vector; with no IV and a fixed ephemeral key the default mode is fully + * deterministic. */ +static const byte ecies_x25519_kat_alice_priv[] = { + 0x77,0x07,0x6d,0x0a,0x73,0x18,0xa5,0x7d,0x3c,0x16,0xc1,0x72, + 0x51,0xb2,0x66,0x45,0xdf,0x4c,0x2f,0x87,0xeb,0xc0,0x99,0x2a, + 0xb1,0x77,0xfb,0xa5,0x1d,0xb9,0x2c,0x2a +}; +static const byte ecies_x25519_kat_alice_pub[] = { + 0x85,0x20,0xf0,0x09,0x89,0x30,0xa7,0x54,0x74,0x8b,0x7d,0xdc, + 0xb4,0x3e,0xf7,0x5a,0x0d,0xbf,0x3a,0x0d,0x26,0x38,0x1a,0xf4, + 0xeb,0xa4,0xa9,0x8e,0xaa,0x9b,0x4e,0x6a +}; +static const byte ecies_x25519_kat_bob_priv[] = { + 0x5d,0xab,0x08,0x7e,0x62,0x4a,0x8a,0x4b,0x79,0xe1,0x7f,0x8b, + 0x83,0x80,0x0e,0xe6,0x6f,0x3b,0xb1,0x29,0x26,0x18,0xb6,0xfd, + 0x1c,0x2f,0x8b,0x27,0xff,0x88,0xe0,0xeb +}; +static const byte ecies_x25519_kat_bob_pub[] = { + 0xde,0x9e,0xdb,0x7d,0x7b,0x7d,0xc1,0xb4,0xd3,0x5b,0x61,0xc2, + 0xec,0xe4,0x35,0x37,0x3f,0x83,0x43,0xc8,0x5b,0x78,0x67,0x4d, + 0xad,0xfc,0x7e,0x14,0x6f,0x88,0x2b,0x4f +}; +/* RFC 7748 section 6.1 shared secret K. */ +static const byte ecies_x25519_kat_shared[] = { + 0x4a,0x5d,0x9d,0x5b,0xa4,0xce,0x2d,0xe1,0x72,0x8e,0x3b,0xf4, + 0x80,0x35,0x0f,0x25,0xe0,0x7e,0x21,0xc9,0x47,0xd1,0x9e,0x33, + 0x76,0xf0,0x9b,0x3c,0x1e,0x16,0x17,0x42 +}; +static const byte ecies_x25519_kat_msg[] = + "ECIES X25519 known answer test!!"; /* 32 bytes, no NUL used */ +/* Alice's public key, then AES-128-CBC ciphertext, then HMAC-SHA256. */ +static const byte ecies_x25519_kat_out[] = { + 0x85,0x20,0xf0,0x09,0x89,0x30,0xa7,0x54,0x74,0x8b,0x7d,0xdc, + 0xb4,0x3e,0xf7,0x5a,0x0d,0xbf,0x3a,0x0d,0x26,0x38,0x1a,0xf4, + 0xeb,0xa4,0xa9,0x8e,0xaa,0x9b,0x4e,0x6a,0x41,0x0b,0x09,0x51, + 0x98,0x12,0x81,0xd3,0x0c,0xe6,0x9e,0x60,0xd3,0xe7,0x21,0xcf, + 0x18,0x33,0xae,0xbd,0x43,0x63,0x04,0x6c,0x6b,0x45,0x57,0xdb, + 0xc9,0xb8,0xab,0x8a,0xb2,0x67,0xc0,0xa3,0xa8,0x38,0x2f,0x5b, + 0x15,0x0f,0x71,0x8d,0x06,0x32,0x91,0x84,0xa9,0xe8,0x82,0x32, + 0xc5,0x41,0xdb,0xa1,0xab,0x5e,0x28,0xa2,0x19,0x25,0x85,0x4c +}; + +static wc_test_ret_t ecies_x25519_kat(WC_RNG* rng) +{ + wc_test_ret_t ret; + curve25519_key alice; + curve25519_key bob; + ecEncCtx* ctx = NULL; + byte out[sizeof(ecies_x25519_kat_out)]; + byte plain[sizeof(ecies_x25519_kat_msg)]; + byte shared[CURVE25519_KEYSIZE]; + word32 outSz = (word32)sizeof(out); + word32 plainSz = (word32)sizeof(plain); + word32 sharedSz = (word32)sizeof(shared); + const word32 msgSz = (word32)sizeof(ecies_x25519_kat_msg) - 1; + + XMEMSET(&alice, 0, sizeof(alice)); + XMEMSET(&bob, 0, sizeof(bob)); + XMEMSET(out, 0, sizeof(out)); + + ret = wc_curve25519_init_ex(&alice, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_curve25519_init_ex(&bob, HEAP_HINT, INVALID_DEVID); + if (ret != 0) { + wc_curve25519_free(&alice); + return WC_TEST_RET_ENC_EC(ret); + } + + ret = wc_curve25519_import_private_raw_ex(ecies_x25519_kat_alice_priv, + (word32)sizeof(ecies_x25519_kat_alice_priv), + ecies_x25519_kat_alice_pub, + (word32)sizeof(ecies_x25519_kat_alice_pub), + &alice, EC25519_LITTLE_ENDIAN); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + ret = wc_curve25519_import_private_raw_ex( + ecies_x25519_kat_bob_priv, (word32)sizeof(ecies_x25519_kat_bob_priv), + ecies_x25519_kat_bob_pub, (word32)sizeof(ecies_x25519_kat_bob_pub), + &bob, EC25519_LITTLE_ENDIAN); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + +#ifdef WOLFSSL_CURVE25519_BLINDING + ret = wc_curve25519_set_rng(&alice, rng); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + ret = wc_curve25519_set_rng(&bob, rng); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } +#endif + + /* The little-endian shared secret is the one RFC 7748 specifies. */ + ret = wc_curve25519_shared_secret_ex(&alice, &bob, shared, &sharedSz, + EC25519_LITTLE_ENDIAN); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + if (sharedSz != (word32)sizeof(ecies_x25519_kat_shared) || + XMEMCMP(shared, ecies_x25519_kat_shared, sharedSz) != 0) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + + ctx = wc_ecc_ctx_new(0, rng); + if (ctx == NULL) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + ret = wc_ecc_ctx_set_curve_id(ctx, ECC_X25519); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + ret = wc_ecc_encrypt_ex2(&alice, &bob, ecies_x25519_kat_msg, msgSz, out, + &outSz, ctx, 0); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + if (outSz != (word32)sizeof(ecies_x25519_kat_out) || + XMEMCMP(out, ecies_x25519_kat_out, outSz) != 0) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + + /* And it decrypts back. */ + ret = wc_ecc_ctx_reset(ctx, rng); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + ret = wc_ecc_decrypt_ex2(&bob, NULL, ecies_x25519_kat_out, + (word32)sizeof(ecies_x25519_kat_out), plain, + &plainSz, ctx); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + if (plainSz != msgSz || + XMEMCMP(plain, ecies_x25519_kat_msg, msgSz) != 0) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + + ret = 0; + +done: + wc_ecc_ctx_free(ctx); + wc_curve25519_free(&bob); + wc_curve25519_free(&alice); + + return ret; +} +#endif /* WOLFSSL_ECIES_X25519 && default IV mode */ + +#if defined(WOLFSSL_ECIES_X25519) && !defined(WOLFSSL_ECIES_OLD) +/* ECIES over X25519 driven through the REQ/RESP salt exchange, which is the + * flow that resets a context between rounds, and with a non-default DEM. */ +static wc_test_ret_t ecies_x25519_req_resp_test(WC_RNG* rng, byte encAlgo) +{ + wc_test_ret_t ret; + curve25519_key cliKey; + curve25519_key srvKey; + ecEncCtx* cliCtx = NULL; + ecEncCtx* srvCtx = NULL; + const byte* tmpSalt; + byte cliSalt[EXCHANGE_SALT_SZ]; + byte srvSalt[EXCHANGE_SALT_SZ]; + byte out[MAX_ECIES_TEST_SZ]; + byte plain[MAX_ECIES_TEST_SZ]; + word32 outSz = (word32)sizeof(out); + word32 plainSz = (word32)sizeof(plain); + static const byte msg[] = "ECIES X25519 request/response!!!"; + const word32 msgSz = (word32)sizeof(msg) - 1; + + XMEMSET(&cliKey, 0, sizeof(cliKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + + ret = wc_curve25519_init_ex(&cliKey, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_curve25519_init_ex(&srvKey, HEAP_HINT, INVALID_DEVID); + if (ret != 0) { + wc_curve25519_free(&cliKey); + return WC_TEST_RET_ENC_EC(ret); + } + + ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, &cliKey); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, &srvKey); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + cliCtx = wc_ecc_ctx_new(REQ_RESP_CLIENT, rng); + srvCtx = wc_ecc_ctx_new(REQ_RESP_SERVER, rng); + if (cliCtx == NULL || srvCtx == NULL) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + + ret = wc_ecc_ctx_set_curve_id(cliCtx, ECC_X25519); + if (ret == 0) + ret = wc_ecc_ctx_set_curve_id(srvCtx, ECC_X25519); + if (ret == 0) + ret = wc_ecc_ctx_set_algo(cliCtx, encAlgo, ecHKDF_SHA256, + ecHMAC_SHA256); + if (ret == 0) + ret = wc_ecc_ctx_set_algo(srvCtx, encAlgo, ecHKDF_SHA256, + ecHMAC_SHA256); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + /* Exchange salts, as the peers would over the transport. */ + tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx); + if (tmpSalt == NULL) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); + tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx); + if (tmpSalt == NULL) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); + + ret = wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt); + if (ret == 0) + ret = wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + /* Client request. */ + ret = wc_ecc_encrypt_ex2(&cliKey, &srvKey, msg, msgSz, out, &outSz, + cliCtx, 0); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + /* Server decrypts it, using the doubled key material of the REQ/RESP + * protocol. */ + ret = wc_ecc_decrypt_ex2(&srvKey, NULL, out, outSz, plain, &plainSz, + srvCtx); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + if (plainSz != msgSz || XMEMCMP(plain, msg, msgSz) != 0) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + + /* The contexts are single use; the key type has to survive the reset. */ + ret = wc_ecc_ctx_reset(cliCtx, rng); + if (ret == 0) + ret = wc_ecc_ctx_reset(srvCtx, rng); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + { + int cliId = ECC_CURVE_INVALID; + int srvId = ECC_CURVE_INVALID; + ret = wc_ecc_ctx_get_curve_id(cliCtx, &cliId); + if (ret == 0) + ret = wc_ecc_ctx_get_curve_id(srvCtx, &srvId); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + if (cliId != ECC_X25519 || srvId != ECC_X25519) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + } + + ret = 0; + +done: + wc_ecc_ctx_free(srvCtx); + wc_ecc_ctx_free(cliCtx); + wc_curve25519_free(&srvKey); + wc_curve25519_free(&cliKey); + + return ret; +} +#endif /* WOLFSSL_ECIES_X25519 && !WOLFSSL_ECIES_OLD */ + +#ifdef WOLFSSL_ECIES_X25519 +static wc_test_ret_t ecies_x25519_test(WC_RNG* rng) +{ + wc_test_ret_t ret; + curve25519_key ephKey; + curve25519_key srvKey; + byte wire[CURVE25519_PUB_KEY_SIZE]; + word32 wireSz = (word32)sizeof(wire); + byte enc[MAX_ECIES_TEST_SZ]; + word32 encSz = (word32)sizeof(enc); + byte msg[32]; + ecEncCtx* ctx; + + XMEMSET(&ephKey, 0, sizeof(ephKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + + ret = wc_curve25519_init_ex(&ephKey, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_curve25519_init_ex(&srvKey, HEAP_HINT, INVALID_DEVID); + if (ret != 0) { + wc_curve25519_free(&ephKey); + return WC_TEST_RET_ENC_EC(ret); + } + + ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, &ephKey); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, &srvKey); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + ret = ecies_mont_test(rng, ECC_X25519, &ephKey, &srvKey, + CURVE25519_PUB_KEY_SIZE); + if (ret != 0) + goto done; + +#ifndef WOLFSSL_ECIES_OLD + /* The ephemeral key goes on the wire as a raw little-endian + * u-coordinate - RFC 7748, as used by TLS key_share and HPKE. */ + XMEMSET(msg, 0, sizeof(msg)); + ctx = wc_ecc_ctx_new(0, rng); + if (ctx == NULL) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + ret = wc_ecc_ctx_set_curve_id(ctx, ECC_X25519); + if (ret == 0) { + ret = wc_ecc_encrypt_ex2(&ephKey, &srvKey, msg, (word32)sizeof(msg), + enc, &encSz, ctx, 0); + } + if (ret == 0) { + ret = wc_curve25519_export_public_ex(&ephKey, wire, &wireSz, + EC25519_LITTLE_ENDIAN); + } + wc_ecc_ctx_free(ctx); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + if (XMEMCMP(wire, enc, CURVE25519_PUB_KEY_SIZE) != 0) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } +#else + (void)wire; (void)wireSz; (void)enc; (void)encSz; (void)msg; (void)ctx; +#endif /* !WOLFSSL_ECIES_OLD */ + +done: + wc_curve25519_free(&srvKey); + wc_curve25519_free(&ephKey); + + return ret; +} +#endif /* WOLFSSL_ECIES_X25519 */ + +#ifdef WOLFSSL_ECIES_X448 +static wc_test_ret_t ecies_x448_test(WC_RNG* rng) +{ + wc_test_ret_t ret; + curve448_key ephKey; + curve448_key srvKey; + byte wire[CURVE448_PUB_KEY_SIZE]; + word32 wireSz = (word32)sizeof(wire); + byte enc[MAX_ECIES_TEST_SZ]; + word32 encSz = (word32)sizeof(enc); + byte msg[32]; + ecEncCtx* ctx; + + XMEMSET(&ephKey, 0, sizeof(ephKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + + ret = wc_curve448_init(&ephKey); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_curve448_init(&srvKey); + if (ret != 0) { + wc_curve448_free(&ephKey); + return WC_TEST_RET_ENC_EC(ret); + } + + ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, &ephKey); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, &srvKey); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + ret = ecies_mont_test(rng, ECC_X448, &ephKey, &srvKey, + CURVE448_PUB_KEY_SIZE); + if (ret != 0) + goto done; + +#ifndef WOLFSSL_ECIES_OLD + XMEMSET(msg, 0, sizeof(msg)); + ctx = wc_ecc_ctx_new(0, rng); + if (ctx == NULL) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + ret = wc_ecc_ctx_set_curve_id(ctx, ECC_X448); + if (ret == 0) { + ret = wc_ecc_encrypt_ex2(&ephKey, &srvKey, msg, (word32)sizeof(msg), + enc, &encSz, ctx, 0); + } + if (ret == 0) { + ret = wc_curve448_export_public_ex(&ephKey, wire, &wireSz, + EC448_LITTLE_ENDIAN); + } + wc_ecc_ctx_free(ctx); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + if (XMEMCMP(wire, enc, CURVE448_PUB_KEY_SIZE) != 0) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } +#else + (void)wire; (void)wireSz; (void)enc; (void)encSz; (void)msg; (void)ctx; +#endif /* !WOLFSSL_ECIES_OLD */ + +done: + wc_curve448_free(&srvKey); + wc_curve448_free(&ephKey); + + return ret; +} +#endif /* WOLFSSL_ECIES_X448 */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecc_encrypt_test(void) { WC_RNG rng; @@ -47780,6 +48414,33 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecc_encrypt_test(void) if (ret == 0) ret = ecc_encrypt_cryptocb_test(&rng); #endif +#ifdef WOLFSSL_ECIES_X25519 + if (ret == 0) + ret = ecies_x25519_test(&rng); +#ifndef WOLFSSL_ECIES_OLD + if (ret == 0) + ret = ecies_x25519_req_resp_test(&rng, ecAES_128_CBC); +#if !defined(NO_AES) && defined(WOLFSSL_AES_COUNTER) && defined(WOLFSSL_AES_256) + /* A different DEM: AES-256-CTR does no block padding. */ + if (ret == 0) + ret = ecies_x25519_req_resp_test(&rng, ecAES_256_CTR); +#endif +#if !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AES_256) && \ + (defined(WOLFSSL_ECIES_GEN_IV) || defined(WOLFSSL_ECIES_STATIC_GCM_NONCE)) + if (ret == 0) + ret = ecies_x25519_req_resp_test(&rng, ecAES_256_GCM); +#endif +#endif /* !WOLFSSL_ECIES_OLD */ +#if !defined(WOLFSSL_ECIES_OLD) && !defined(WOLFSSL_ECIES_GEN_IV) && \ + !defined(WOLFSSL_ECIES_ISO18033) + if (ret == 0) + ret = ecies_x25519_kat(&rng); +#endif +#endif +#ifdef WOLFSSL_ECIES_X448 + if (ret == 0) + ret = ecies_x448_test(&rng); +#endif #endif /* !HAVE_FIPS || FIPS_VERSION_GE(5,3) */ done: diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index d3028e91e9..30641272b2 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -290,6 +290,11 @@ typedef struct wc_CryptoInfo { } ecc_check_pub; /* distinct from ecc_check (priv-key cmp) */ #endif #ifdef HAVE_ECC_ENCRYPT + /* ECC keys only. ECIES over a Montgomery curve (ECC_X25519 / + * ECC_X448, selected with wc_ecc_ctx_set_curve_id) bypasses these + * callbacks entirely, since the key pointers below are typed for + * ecc_key. The curve25519 callback still fires for the shared + * secret itself. */ struct { ecc_key* privKey; ecc_key* pubKey; diff --git a/wolfssl/wolfcrypt/ecc.h b/wolfssl/wolfcrypt/ecc.h index effe7f72ad..1c5f549ae3 100644 --- a/wolfssl/wolfcrypt/ecc.h +++ b/wolfssl/wolfcrypt/ecc.h @@ -1059,6 +1059,32 @@ int wc_ecc_get_oid(word32 oidSum, const byte** oid, word32* oidSz); #ifdef HAVE_ECC_ENCRYPT /* ecc encrypt */ +/* ECIES can use a Montgomery-curve (X25519/X448) key in place of an ecc_key. + * The curve is selected on the context with wc_ecc_ctx_set_curve_id() and the + * keys are then handed to wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2(). + * + * The shared secret is always needed. The ephemeral public key is only + * exported into (and imported out of) the message when not in + * WOLFSSL_ECIES_OLD mode, so the import/export sub-guards only matter there. + * Note that the curve25519.h/curve448.h prototypes are not guarded even though + * the definitions are, so getting this wrong is a link error rather than a + * compile error. */ +#if defined(HAVE_CURVE25519) && defined(HAVE_CURVE25519_SHARED_SECRET) && \ + (defined(WOLFSSL_ECIES_OLD) || \ + (defined(HAVE_CURVE25519_KEY_EXPORT) && \ + defined(HAVE_CURVE25519_KEY_IMPORT))) + #define WOLFSSL_ECIES_X25519 +#endif +#if defined(HAVE_CURVE448) && defined(HAVE_CURVE448_SHARED_SECRET) && \ + (defined(WOLFSSL_ECIES_OLD) || \ + (defined(HAVE_CURVE448_KEY_EXPORT) && \ + defined(HAVE_CURVE448_KEY_IMPORT))) + #define WOLFSSL_ECIES_X448 +#endif +#if defined(WOLFSSL_ECIES_X25519) || defined(WOLFSSL_ECIES_X448) + #define WOLFSSL_ECIES_MONTGOMERY +#endif + enum ecEncAlgo { ecAES_128_CBC = 1, /* default */ ecAES_256_CBC = 2, @@ -1116,6 +1142,14 @@ int wc_ecc_ctx_reset(ecEncCtx* ctx, WC_RNG* rng); /* reset for use again w/o al WOLFSSL_API int wc_ecc_ctx_set_algo(ecEncCtx* ctx, byte encAlgo, byte kdfAlgo, byte macAlgo); +/* Select the type of key the context operates on: ECC_CURVE_DEF (default, + * ecc_key), ECC_X25519 (curve25519_key) or ECC_X448 (curve448_key). A + * Montgomery key type has to be used with wc_ecc_encrypt_ex2()/ + * wc_ecc_decrypt_ex2(); wc_ecc_encrypt()/wc_ecc_decrypt() stay ECC-only. */ +WOLFSSL_API +int wc_ecc_ctx_set_curve_id(ecEncCtx* ctx, int curveId); +WOLFSSL_API +int wc_ecc_ctx_get_curve_id(ecEncCtx* ctx, int* curveId); #ifdef WOLF_CRYPTO_CB /* Accessors for crypto-callback backends; only built with WOLF_CRYPTO_CB. */ WOLFSSL_API @@ -1147,6 +1181,18 @@ WOLFSSL_ABI WOLFSSL_API int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx); +/* Generic entry points. The type of privKey/pubKey is whatever + * wc_ecc_ctx_set_curve_id() selected on the context: ecc_key* for + * ECC_CURVE_DEF, curve25519_key* for ECC_X25519, curve448_key* for ECC_X448. + * ctx is mandatory for the Montgomery curves - there is no way to recover the + * key type from a NULL context. */ +WOLFSSL_API +int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, + word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, int compressed); +WOLFSSL_API +int wc_ecc_decrypt_ex2(void* privKey, void* pubKey, const byte* msg, + word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx); + #endif /* HAVE_ECC_ENCRYPT */ #ifdef HAVE_X963_KDF From 38f32ae3ece1798682d68c58240e3d1f0076ab76 Mon Sep 17 00:00:00 2001 From: night1rider Date: Sun, 23 Aug 2026 19:44:06 -0600 Subject: [PATCH 2/4] ECIES: harden Montgomery tests and docs from review findings --- doc/dox_comments/header_files/ecc.h | 60 ++- tests/api/test_ecc.c | 265 ++++++++++-- tests/unit-mcdc/test_ecc_whitebox.c | 13 +- wolfcrypt/src/ecc.c | 23 +- wolfcrypt/test/test.c | 636 +++++++++++++++++++++++----- 5 files changed, 823 insertions(+), 174 deletions(-) diff --git a/doc/dox_comments/header_files/ecc.h b/doc/dox_comments/header_files/ecc.h index 586d0602cb..d4144a06f9 100644 --- a/doc/dox_comments/header_files/ecc.h +++ b/doc/dox_comments/header_files/ecc.h @@ -1778,7 +1778,10 @@ int wc_ecc_sig_size(const ecc_key* key); ecEncCtx object \param flags indicate whether this is a server or client context - Options are: REQ_RESP_CLIENT, and REQ_RESP_SERVER + Options are: REQ_RESP_CLIENT, REQ_RESP_SERVER, and 0. With 0 the context + takes no part in the REQ/RESP salt exchange; it still carries the + algorithm selection (wc_ecc_ctx_set_algo) and the key type + (wc_ecc_ctx_set_curve_id) into the encrypt/decrypt calls \param rng pointer to a RNG object with which to generate a salt _Example_ @@ -1839,6 +1842,10 @@ void wc_ecc_ctx_free(ecEncCtx* ctx); \param ctx pointer to the ecEncCtx object to reset \param rng pointer to an RNG object with which to generate a new salt + \note A context created with flags 0 generates no salt, so the reset + skips the salt regeneration. The key type selected with + wc_ecc_ctx_set_curve_id survives the reset in every mode. + _Example_ \code ecEncCtx* ctx; @@ -1959,10 +1966,11 @@ int wc_ecc_ctx_get_curve_id(ecEncCtx* ctx, int* curveId); ecSRV_INIT or ecCLI_INIT. \return Success On success, returns the ecEncCtx salt - \return NULL Returned if the ecEncCtx object is NULL, or the ecEncCtx's - state is not ecSRV_INIT or ecCLI_INIT. In the latter two cases, this - function also sets the ecEncCtx's state to ecSRV_BAD_STATE or - ecCLI_BAD_STATE, respectively + \return NULL Returned if the ecEncCtx object is NULL, was created with + flags 0 (no REQ/RESP protocol, so it takes no part in the salt + exchange), or the ecEncCtx's state is not ecSRV_INIT or ecCLI_INIT. + In the latter case, this function also sets the ecEncCtx's state to + ecSRV_BAD_STATE or ecCLI_BAD_STATE, respectively \param ctx pointer to the ecEncCtx object from which to get the salt @@ -1994,7 +2002,8 @@ const byte* wc_ecc_ctx_get_own_salt(ecEncCtx* ctx); \return 0 Returned upon successfully setting the peer salt for the ecEncCtx object. \return BAD_FUNC_ARG Returned if the given ecEncCtx object is NULL - or has an invalid protocol, or if the given salt is NULL + or has no REQ/RESP protocol (created with flags 0, so it takes no part + in the salt exchange), or if the given salt is NULL \return BAD_ENC_STATE_E Returned if the ecEncCtx's state is ecSRV_SALT_GET or ecCLI_SALT_GET. In the latter two cases, this function also sets the ecEncCtx's state to ecSRV_BAD_STATE or @@ -2033,8 +2042,10 @@ int wc_ecc_ctx_set_peer_salt(ecEncCtx* ctx, const byte* salt); \return 0 Returned upon successfully setting the salt for the ecEncCtx object. - \return BAD_FUNC_ARG Returned if the given ecEncCtx object is NULL - or if the given salt is NULL and length is not NULL. + \return BAD_FUNC_ARG Returned if the given ecEncCtx object is NULL, + was created with flags 0 (no REQ/RESP protocol, so there is no salt + storage to borrow for the custom KDF salt), or if the given salt is + NULL and length is not NULL. \param ctx pointer to the ecEncCtx for which to set the salt \param salt pointer to salt buffer @@ -2104,8 +2115,10 @@ int wc_ecc_ctx_set_info(ecEncCtx* ctx, const byte* info, int sz); \return 0 Returned upon successfully encrypting the input message \return BAD_FUNC_ARG Returned if privKey, pubKey, msg, msgSz, out, - or outSz are NULL, or the ctx object specifies an unsupported - encryption type + or outSz are NULL, the ctx object specifies an unsupported + encryption type, or the ctx object is configured for a Montgomery + curve (ECC_X25519 / ECC_X448) - such a context must be used with + wc_ecc_encrypt_ex2 \return BAD_ENC_STATE_E Returned if the ctx object given is in a state that is not appropriate for encryption \return BUFFER_E Returned if the supplied output buffer is too @@ -2171,8 +2184,10 @@ int wc_ecc_encrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, \return 0 Returned upon successfully encrypting the input message \return BAD_FUNC_ARG Returned if privKey, pubKey, msg, msgSz, out, - or outSz are NULL, or the ctx object specifies an unsupported - encryption type + or outSz are NULL, the ctx object specifies an unsupported + encryption type, or the ctx object is configured for a Montgomery + curve (ECC_X25519 / ECC_X448) - such a context must be used with + wc_ecc_encrypt_ex2 \return BAD_ENC_STATE_E Returned if the ctx object given is in a state that is not appropriate for encryption \return BUFFER_E Returned if the supplied output buffer is too @@ -2248,8 +2263,10 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, \return 0 Returned upon successfully decrypting the input message \return BAD_FUNC_ARG Returned if privKey, pubKey, msg, msgSz, out, - or outSz are NULL, or the ctx object specifies an unsupported - encryption type + or outSz are NULL, the ctx object specifies an unsupported + encryption type, or the ctx object is configured for a Montgomery + curve (ECC_X25519 / ECC_X448) - such a context must be used with + wc_ecc_decrypt_ex2 \return BAD_ENC_STATE_E Returned if the ctx object given is in a state that is not appropriate for decryption \return BUFFER_E Returned if the supplied output buffer is too @@ -2381,9 +2398,11 @@ int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, For the Montgomery curves the peer's ephemeral public key is read from the front of the message and validated - wc_curve25519_check_public or - wc_curve448_check_public - before the shared secret is derived. That check - requires a canonical encoding with the high bit clear, which is what - wc_ecc_encrypt_ex2 produces. + wc_curve448_check_public - before the shared secret is derived. Both + checks reject the low-order points; the X25519 check additionally requires + a canonical encoding with the high bit clear, which is what + wc_ecc_encrypt_ex2 produces. (An X448 u-coordinate is a 448-bit field + element that fills its last byte, so there is no high bit to check.) \return 0 Returned upon successfully decrypting the message. \return BAD_FUNC_ARG Returned if privKey, msg, out or outSz is NULL, or @@ -2396,7 +2415,9 @@ int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, selects \param pubKey optional pointer to storage of the type ctx selects, which receives the peer's ephemeral public key. May be NULL, in which case - temporary storage is used. + temporary storage is used. Under WOLFSSL_ECIES_OLD the ephemeral key is + not carried in the message, so there pubKey must supply the peer's public + key and NULL is rejected with BAD_FUNC_ARG. \param msg pointer to the ciphertext to decrypt \param msgSz size of the ciphertext \param out pointer to the buffer in which to store the plaintext @@ -3546,6 +3567,9 @@ const byte* wc_ecc_ctx_get_own_salt(ecEncCtx* ctx); \brief Sets own salt in context. \return 0 on success + \return BAD_FUNC_ARG when ctx is NULL, ctx was created with flags 0 + (no REQ/RESP protocol, so it takes no part in the salt exchange), or + salt is NULL \return negative on error \param ctx ECC encryption context diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index c559aa02fe..6edba20945 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -1648,6 +1648,24 @@ int test_wc_ecc_ctx_set_peer_salt(void) ExpectIntEQ(wc_ecc_ctx_set_peer_salt(cliCtx, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* A no-protocol context (wc_ecc_ctx_new(0, ...)) takes no part in the + * REQ/RESP salt exchange, and the salt accessors reject it. */ + { + ecEncCtx* plainCtx = NULL; + + ExpectNotNull(plainCtx = wc_ecc_ctx_new(0, &rng)); + ExpectNull(wc_ecc_ctx_get_own_salt(plainCtx)); + ExpectIntEQ(wc_ecc_ctx_set_peer_salt(plainCtx, servSalt), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_ctx_set_own_salt(plainCtx, servSalt, + EXCHANGE_SALT_SZ), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* The custom KDF salt borrows the REQ/RESP salt storage, which a + * no-protocol context does not have. */ + ExpectIntEQ(wc_ecc_ctx_set_kdf_salt(plainCtx, servSalt, + EXCHANGE_SALT_SZ), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_ecc_ctx_free(plainCtx); + } + wc_ecc_ctx_free(cliCtx); wc_ecc_ctx_free(servCtx); DoExpectIntEQ(wc_FreeRng(&rng), 0); @@ -1923,11 +1941,29 @@ int test_wc_ecc_ctx_set_curve_id(void) ExpectIntEQ(wc_ecc_ctx_get_curve_id(ctx, &curveId), 0); ExpectIntEQ(curveId, ECC_X25519); - /* The typed entry points stay ECC-only. */ - ExpectIntEQ(wc_ecc_encrypt(NULL, NULL, NULL, 0, NULL, NULL, ctx), - BAD_FUNC_ARG); - ExpectIntEQ(wc_ecc_decrypt(NULL, NULL, NULL, 0, NULL, NULL, ctx), - BAD_FUNC_ARG); + /* The typed entry points stay ECC-only. Use a live key and buffers so + * the rejection can only come from the Montgomery-context guard, not + * from the generic NULL-argument checks. */ + { + curve25519_key mkey; + byte mbuf[16]; + byte mout[CURVE25519_PUB_KEY_SIZE + 16 + WC_SHA256_DIGEST_SIZE]; + word32 moutSz = (word32)sizeof(mout); + word32 mplainSz = (word32)sizeof(mbuf); + + XMEMSET(&mkey, 0, sizeof(mkey)); + XMEMSET(mbuf, 0, sizeof(mbuf)); + ExpectIntEQ(wc_curve25519_init(&mkey), 0); + ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, &mkey), + 0); + ExpectIntEQ(wc_ecc_encrypt((ecc_key*)&mkey, (ecc_key*)&mkey, mbuf, + (word32)sizeof(mbuf), mout, &moutSz, ctx), BAD_FUNC_ARG); + ExpectIntEQ(wc_ecc_encrypt_ex((ecc_key*)&mkey, (ecc_key*)&mkey, mbuf, + (word32)sizeof(mbuf), mout, &moutSz, ctx, 0), BAD_FUNC_ARG); + ExpectIntEQ(wc_ecc_decrypt((ecc_key*)&mkey, (ecc_key*)&mkey, mout, + moutSz, mbuf, &mplainSz, ctx), BAD_FUNC_ARG); + wc_curve25519_free(&mkey); + } #else ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_X25519), NOT_COMPILED_IN); #endif @@ -1967,8 +2003,8 @@ int test_wc_ecc_ecies_x25519(void) defined(WOLFSSL_AES_128) && defined(HAVE_HKDF) && \ !defined(WOLFSSL_NO_MALLOC) WC_RNG rng; - curve25519_key ephKey; - curve25519_key srvKey; + WC_DECLARE_VAR(ephKey, curve25519_key, 1, HEAP_HINT); + WC_DECLARE_VAR(srvKey, curve25519_key, 1, HEAP_HINT); ecEncCtx* ctx = NULL; const char* msg = "EccBlock Size 16"; word32 msgSz = (word32)XSTRLEN("EccBlock Size 16"); @@ -1983,21 +2019,32 @@ int test_wc_ecc_ecies_x25519(void) word32 wireSz = (word32)sizeof(wire); XMEMSET(&rng, 0, sizeof(rng)); - XMEMSET(&ephKey, 0, sizeof(ephKey)); - XMEMSET(&srvKey, 0, sizeof(srvKey)); XMEMSET(out, 0, sizeof(out)); XMEMSET(plain, 0, sizeof(plain)); + WC_ALLOC_VAR(ephKey, curve25519_key, 1, HEAP_HINT); + WC_ALLOC_VAR(srvKey, curve25519_key, 1, HEAP_HINT); +#ifdef WC_DECLARE_VAR_IS_HEAP_ALLOC + ExpectNotNull(ephKey); + ExpectNotNull(srvKey); +#endif + if (WC_VAR_OK(ephKey)) { + XMEMSET(ephKey, 0, sizeof(*ephKey)); + } + if (WC_VAR_OK(srvKey)) { + XMEMSET(srvKey, 0, sizeof(*srvKey)); + } + ExpectIntEQ(wc_InitRng(&rng), 0); - ExpectIntEQ(wc_curve25519_init(&ephKey), 0); - ExpectIntEQ(wc_curve25519_init(&srvKey), 0); - ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, &ephKey), 0); - ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, &srvKey), 0); + ExpectIntEQ(wc_curve25519_init(ephKey), 0); + ExpectIntEQ(wc_curve25519_init(srvKey), 0); + ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, ephKey), 0); + ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, srvKey), 0); ExpectNotNull(ctx = wc_ecc_ctx_new(0, &rng)); ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_X25519), 0); - ExpectIntEQ(wc_ecc_encrypt_ex2(&ephKey, &srvKey, (const byte*)msg, msgSz, + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, (const byte*)msg, msgSz, out, &outSz, ctx, 0), 0); #ifdef WOLFSSL_ECIES_GEN_IV ExpectIntEQ(outSz, CURVE25519_PUB_KEY_SIZE + AES_BLOCK_SIZE + msgSz + @@ -2009,7 +2056,7 @@ int test_wc_ecc_ecies_x25519(void) /* The message opens with the ephemeral public key as a raw little-endian * u-coordinate (RFC 7748), with no format byte. */ - ExpectIntEQ(wc_curve25519_export_public_ex(&ephKey, wire, &wireSz, + ExpectIntEQ(wc_curve25519_export_public_ex(ephKey, wire, &wireSz, EC25519_LITTLE_ENDIAN), 0); ExpectIntEQ(XMEMCMP(wire, out, CURVE25519_PUB_KEY_SIZE), 0); @@ -2017,31 +2064,107 @@ int test_wc_ecc_ecies_x25519(void) { byte tmp[sizeof(out)]; word32 tmpSz = (word32)sizeof(tmp); - ExpectIntEQ(wc_ecc_encrypt_ex2(&ephKey, &srvKey, (const byte*)msg, + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, (const byte*)msg, msgSz, tmp, &tmpSz, ctx, 1), BAD_FUNC_ARG); } - ExpectIntEQ(wc_ecc_decrypt_ex2(&srvKey, NULL, out, outSz, plain, &plainSz, + /* An init-only ephemeral key - no key material generated or imported - + * must be rejected rather than quietly exporting base * 0. */ + { + curve25519_key emptyKey; + byte tmp[sizeof(out)]; + word32 tmpSz = (word32)sizeof(tmp); + + XMEMSET(&emptyKey, 0, sizeof(emptyKey)); + ExpectIntEQ(wc_curve25519_init(&emptyKey), 0); + ExpectIntEQ(wc_ecc_encrypt_ex2(&emptyKey, srvKey, (const byte*)msg, + msgSz, tmp, &tmpSz, ctx, 0), WC_NO_ERR_TRACE(ECC_BAD_ARG_E)); + wc_curve25519_free(&emptyKey); + } + + /* The documented error contract of the generic entry points, driven + * directly rather than through the typed wrappers. */ + { + byte tmp[sizeof(out)]; + word32 tmpSz = (word32)sizeof(tmp); + word32 smallSz = 8; + + ExpectIntEQ(wc_ecc_encrypt_ex2(NULL, srvKey, (const byte*)msg, msgSz, + tmp, &tmpSz, ctx, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, NULL, (const byte*)msg, msgSz, + tmp, &tmpSz, ctx, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, NULL, msgSz, tmp, + &tmpSz, ctx, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, (const byte*)msg, + msgSz, NULL, &tmpSz, ctx, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, (const byte*)msg, + msgSz, tmp, NULL, ctx, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* The Montgomery total size from ecies_pub_key_size() feeds the + * output-size check. */ + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, (const byte*)msg, + msgSz, tmp, &smallSz, ctx, 0), WC_NO_ERR_TRACE(BUFFER_E)); + + tmpSz = (word32)sizeof(tmp); + ExpectIntEQ(wc_ecc_decrypt_ex2(NULL, NULL, out, outSz, tmp, &tmpSz, + ctx), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, NULL, NULL, outSz, tmp, + &tmpSz, ctx), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, NULL, out, outSz, NULL, + &tmpSz, ctx), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, NULL, out, outSz, tmp, NULL, + ctx), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* Too short to hold the ephemeral key, a cipher block and the MAC. + * A block-multiple length is used so the padding check up front + * stays quiet and the length check itself is what rejects it. */ + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, NULL, out, + CURVE25519_PUB_KEY_SIZE, tmp, &tmpSz, ctx), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + } + + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, NULL, out, outSz, plain, &plainSz, ctx), 0); ExpectIntEQ(plainSz, msgSz); ExpectIntEQ(XMEMCMP(msg, plain, msgSz), 0); + /* Caller-supplied storage for the peer's ephemeral public key: decrypt + * must succeed and leave the key from the front of the message imported + * into it. */ + { + curve25519_key peerEph; + byte got[CURVE25519_PUB_KEY_SIZE]; + word32 gotSz = (word32)sizeof(got); + + XMEMSET(&peerEph, 0, sizeof(peerEph)); + XMEMSET(plain, 0, sizeof(plain)); + plainSz = (word32)sizeof(plain); + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, &peerEph, out, outSz, plain, + &plainSz, ctx), 0); + ExpectIntEQ(plainSz, msgSz); + ExpectIntEQ(XMEMCMP(msg, plain, msgSz), 0); + ExpectIntEQ(wc_curve25519_export_public_ex(&peerEph, got, &gotSz, + EC25519_LITTLE_ENDIAN), 0); + ExpectIntEQ(XMEMCMP(got, out, CURVE25519_PUB_KEY_SIZE), 0); + wc_curve25519_free(&peerEph); + } + /* Corrupting the ciphertext must fail the MAC. */ out[outSz - 1] ^= 0x01; plainSz = (word32)sizeof(plain); - ExpectIntNE(wc_ecc_decrypt_ex2(&srvKey, NULL, out, outSz, plain, &plainSz, + ExpectIntNE(wc_ecc_decrypt_ex2(srvKey, NULL, out, outSz, plain, &plainSz, ctx), 0); out[outSz - 1] ^= 0x01; /* So must corrupting the ephemeral public key. */ out[0] ^= 0x01; plainSz = (word32)sizeof(plain); - ExpectIntNE(wc_ecc_decrypt_ex2(&srvKey, NULL, out, outSz, plain, &plainSz, + ExpectIntNE(wc_ecc_decrypt_ex2(srvKey, NULL, out, outSz, plain, &plainSz, ctx), 0); wc_ecc_ctx_free(ctx); - wc_curve25519_free(&srvKey); - wc_curve25519_free(&ephKey); + wc_curve25519_free(srvKey); + wc_curve25519_free(ephKey); + WC_FREE_VAR(srvKey, HEAP_HINT); + WC_FREE_VAR(ephKey, HEAP_HINT); DoExpectIntEQ(wc_FreeRng(&rng), 0); #endif return EXPECT_RESULT(); @@ -2060,8 +2183,8 @@ int test_wc_ecc_ecies_x448(void) defined(WOLFSSL_AES_128) && defined(HAVE_HKDF) && \ !defined(WOLFSSL_NO_MALLOC) WC_RNG rng; - curve448_key ephKey; - curve448_key srvKey; + WC_DECLARE_VAR(ephKey, curve448_key, 1, HEAP_HINT); + WC_DECLARE_VAR(srvKey, curve448_key, 1, HEAP_HINT); ecEncCtx* ctx = NULL; const char* msg = "EccBlock Size 16"; word32 msgSz = (word32)XSTRLEN("EccBlock Size 16"); @@ -2075,21 +2198,32 @@ int test_wc_ecc_ecies_x448(void) word32 wireSz = (word32)sizeof(wire); XMEMSET(&rng, 0, sizeof(rng)); - XMEMSET(&ephKey, 0, sizeof(ephKey)); - XMEMSET(&srvKey, 0, sizeof(srvKey)); XMEMSET(out, 0, sizeof(out)); XMEMSET(plain, 0, sizeof(plain)); + WC_ALLOC_VAR(ephKey, curve448_key, 1, HEAP_HINT); + WC_ALLOC_VAR(srvKey, curve448_key, 1, HEAP_HINT); +#ifdef WC_DECLARE_VAR_IS_HEAP_ALLOC + ExpectNotNull(ephKey); + ExpectNotNull(srvKey); +#endif + if (WC_VAR_OK(ephKey)) { + XMEMSET(ephKey, 0, sizeof(*ephKey)); + } + if (WC_VAR_OK(srvKey)) { + XMEMSET(srvKey, 0, sizeof(*srvKey)); + } + ExpectIntEQ(wc_InitRng(&rng), 0); - ExpectIntEQ(wc_curve448_init(&ephKey), 0); - ExpectIntEQ(wc_curve448_init(&srvKey), 0); - ExpectIntEQ(wc_curve448_make_key(&rng, CURVE448_KEY_SIZE, &ephKey), 0); - ExpectIntEQ(wc_curve448_make_key(&rng, CURVE448_KEY_SIZE, &srvKey), 0); + ExpectIntEQ(wc_curve448_init(ephKey), 0); + ExpectIntEQ(wc_curve448_init(srvKey), 0); + ExpectIntEQ(wc_curve448_make_key(&rng, CURVE448_KEY_SIZE, ephKey), 0); + ExpectIntEQ(wc_curve448_make_key(&rng, CURVE448_KEY_SIZE, srvKey), 0); ExpectNotNull(ctx = wc_ecc_ctx_new(0, &rng)); ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_X448), 0); - ExpectIntEQ(wc_ecc_encrypt_ex2(&ephKey, &srvKey, (const byte*)msg, msgSz, + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, (const byte*)msg, msgSz, out, &outSz, ctx, 0), 0); #ifdef WOLFSSL_ECIES_GEN_IV ExpectIntEQ(outSz, CURVE448_PUB_KEY_SIZE + AES_BLOCK_SIZE + msgSz + @@ -2098,23 +2232,84 @@ int test_wc_ecc_ecies_x448(void) ExpectIntEQ(outSz, CURVE448_PUB_KEY_SIZE + msgSz + WC_SHA256_DIGEST_SIZE); #endif - ExpectIntEQ(wc_curve448_export_public_ex(&ephKey, wire, &wireSz, + ExpectIntEQ(wc_curve448_export_public_ex(ephKey, wire, &wireSz, EC448_LITTLE_ENDIAN), 0); ExpectIntEQ(XMEMCMP(wire, out, CURVE448_PUB_KEY_SIZE), 0); - ExpectIntEQ(wc_ecc_decrypt_ex2(&srvKey, NULL, out, outSz, plain, &plainSz, + /* Point compression does not apply to a Montgomery curve. */ + { + byte tmp[sizeof(out)]; + word32 tmpSz = (word32)sizeof(tmp); + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, (const byte*)msg, + msgSz, tmp, &tmpSz, ctx, 1), BAD_FUNC_ARG); + } + + /* An init-only ephemeral key - no key material generated or imported - + * must be rejected rather than quietly exporting base * 0. */ + { + curve448_key emptyKey; + byte tmp[sizeof(out)]; + word32 tmpSz = (word32)sizeof(tmp); + + XMEMSET(&emptyKey, 0, sizeof(emptyKey)); + ExpectIntEQ(wc_curve448_init(&emptyKey), 0); + ExpectIntEQ(wc_ecc_encrypt_ex2(&emptyKey, srvKey, (const byte*)msg, + msgSz, tmp, &tmpSz, ctx, 0), WC_NO_ERR_TRACE(ECC_BAD_ARG_E)); + wc_curve448_free(&emptyKey); + } + + /* The X448 total size from ecies_pub_key_size() feeds the output-size + * check, and a message too short for the ephemeral key and MAC is + * rejected outright. */ + { + byte tmp[sizeof(out)]; + word32 tmpSz = (word32)sizeof(tmp); + word32 smallSz = 8; + + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, (const byte*)msg, + msgSz, tmp, &smallSz, ctx, 0), WC_NO_ERR_TRACE(BUFFER_E)); + /* Block-multiple length, so the length check is what rejects it. */ + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, NULL, out, + CURVE448_PUB_KEY_SIZE, tmp, &tmpSz, ctx), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + } + + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, NULL, out, outSz, plain, &plainSz, ctx), 0); ExpectIntEQ(plainSz, msgSz); ExpectIntEQ(XMEMCMP(msg, plain, msgSz), 0); + /* Caller-supplied storage for the peer's ephemeral public key: decrypt + * must succeed and leave the key from the front of the message imported + * into it. */ + { + curve448_key peerEph; + byte got[CURVE448_PUB_KEY_SIZE]; + word32 gotSz = (word32)sizeof(got); + + XMEMSET(&peerEph, 0, sizeof(peerEph)); + XMEMSET(plain, 0, sizeof(plain)); + plainSz = (word32)sizeof(plain); + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, &peerEph, out, outSz, plain, + &plainSz, ctx), 0); + ExpectIntEQ(plainSz, msgSz); + ExpectIntEQ(XMEMCMP(msg, plain, msgSz), 0); + ExpectIntEQ(wc_curve448_export_public_ex(&peerEph, got, &gotSz, + EC448_LITTLE_ENDIAN), 0); + ExpectIntEQ(XMEMCMP(got, out, CURVE448_PUB_KEY_SIZE), 0); + wc_curve448_free(&peerEph); + } + out[outSz - 1] ^= 0x01; plainSz = (word32)sizeof(plain); - ExpectIntNE(wc_ecc_decrypt_ex2(&srvKey, NULL, out, outSz, plain, &plainSz, + ExpectIntNE(wc_ecc_decrypt_ex2(srvKey, NULL, out, outSz, plain, &plainSz, ctx), 0); wc_ecc_ctx_free(ctx); - wc_curve448_free(&srvKey); - wc_curve448_free(&ephKey); + wc_curve448_free(srvKey); + wc_curve448_free(ephKey); + WC_FREE_VAR(srvKey, HEAP_HINT); + WC_FREE_VAR(ephKey, HEAP_HINT); DoExpectIntEQ(wc_FreeRng(&rng), 0); #endif return EXPECT_RESULT(); diff --git a/tests/unit-mcdc/test_ecc_whitebox.c b/tests/unit-mcdc/test_ecc_whitebox.c index 11d809afd9..fca6063e4f 100644 --- a/tests/unit-mcdc/test_ecc_whitebox.c +++ b/tests/unit-mcdc/test_ecc_whitebox.c @@ -32,11 +32,14 @@ * Class 4 wc_ecc_ctx_get_own_salt() ctx->protocol==0 half ....... 1 condition * Class 5 wc_ecc_ctx_set_peer_salt() ctx->protocol==0 half ...... 1 condition * Class 6 wc_ecc_ctx_set_own_salt() ctx->protocol==0 half ....... 1 condition - * These are the only ecc.c gaps confirmed structurally unreachable through - * any public wrapper (every wrapper either hard-codes the "safe" side of the - * static helper's own re-check, or -- for the ecEncCtx cases -- there is no - * public constructor that leaves ctx->protocol == 0 on a live, non-NULL - * context). See RESIDUALS.md for everything else. + * Classes 1-3 are confirmed structurally unreachable through any public + * wrapper (every wrapper hard-codes the "safe" side of the static helper's + * own re-check). Classes 4-6 became API-reachable when + * wc_ecc_ctx_new()/wc_ecc_ctx_new_ex() started accepting flags==0 - + * tests/api/test_ecc.c (test_wc_ecc_ctx_set_peer_salt) now drives them + * through the public API - and are kept here so this binary still completes + * both halves of each pair with a directly-built ctx (see the note above on + * per-binary MC/DC independence). See RESIDUALS.md for everything else. */ /* ecc.c refuses the AES-GCM ECIES DEM in the default IV mode unless one of diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index f99c59678f..da0a939337 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -15233,7 +15233,11 @@ int wc_ecc_ctx_set_peer_salt(ecEncCtx* ctx, const byte* salt) */ int wc_ecc_ctx_set_kdf_salt(ecEncCtx* ctx, const byte* salt, word32 sz) { - if (ctx == NULL || (salt == NULL && sz != 0)) + /* The custom KDF salt borrows the REQ/RESP clientSalt/serverSalt + * storage, so a no-protocol context (wc_ecc_ctx_new(0, ...)) has nowhere + * to keep it - kdfSalt would stay NULL and the copy below would write + * through it. */ + if (ctx == NULL || ctx->protocol == 0 || (salt == NULL && sz != 0)) return BAD_FUNC_ARG; /* truncate salt if exceeds max */ @@ -15813,6 +15817,12 @@ static int ecies_peer_import(ecEncCtx* ctx, void* privKey, void* pubKey, #ifdef WOLFSSL_ECIES_X25519 if (ctx != NULL && ctx->curveId == ECC_X25519) { + /* Init before any validation, so the caller's cleanup path always + * frees an initialized key even when the encoding is rejected. */ + ret = wc_curve25519_init_ex((curve25519_key*)pubKey, + ecies_heap(ctx, privKey), INVALID_DEVID); + if (ret != 0) + return ret; /* wc_curve25519_import_public_ex() does no validation of its own; * this is the counterpart of the on-curve check that * wc_ecc_import_x963_ex() performs. */ @@ -15820,10 +15830,6 @@ static int ecies_peer_import(ecEncCtx* ctx, void* privKey, void* pubKey, EC25519_LITTLE_ENDIAN); if (ret != 0) return ret; - ret = wc_curve25519_init_ex((curve25519_key*)pubKey, - ecies_heap(ctx, privKey), INVALID_DEVID); - if (ret != 0) - return ret; return wc_curve25519_import_public_ex(msg, pubKeySz, (curve25519_key*)pubKey, EC25519_LITTLE_ENDIAN); @@ -15831,10 +15837,10 @@ static int ecies_peer_import(ecEncCtx* ctx, void* privKey, void* pubKey, #endif #ifdef WOLFSSL_ECIES_X448 if (ctx != NULL && ctx->curveId == ECC_X448) { - ret = wc_curve448_check_public(msg, pubKeySz, EC448_LITTLE_ENDIAN); + ret = wc_curve448_init((curve448_key*)pubKey); if (ret != 0) return ret; - ret = wc_curve448_init((curve448_key*)pubKey); + ret = wc_curve448_check_public(msg, pubKeySz, EC448_LITTLE_ENDIAN); if (ret != 0) return ret; return wc_curve448_import_public_ex(msg, pubKeySz, @@ -15905,7 +15911,10 @@ static int ecies_shared_secret(ecEncCtx* ctx, void* privKey, void* pubKey, } #endif +#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_ECC) + /* Only the first wc_AsyncWait() reads this value. */ ret = 0; +#endif do { #if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_ECC) ret = wc_AsyncWait(ret, &((ecc_key*)privKey)->asyncDev, diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 238effd517..405907cdf8 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -47696,20 +47696,31 @@ static wc_test_ret_t ecc_encrypt_cryptocb_test(WC_RNG* rng) #endif /* WOLF_CRYPTO_CB && !WOLFSSL_NO_MALLOC */ #endif /* !HAVE_FIPS || FIPS_VERSION_GE(5,3) */ -#ifdef WOLFSSL_ECIES_MONTGOMERY +/* Like the rest of the ECIES tests, the Montgomery tests need malloc (the + * ecEncCtx construction) and, under FIPS, a 5.3+ module. MAX_ECIES_TEST_SZ + * is only defined under these same guards. */ +#if !defined(WOLFSSL_NO_MALLOC) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GE(5,3)) + +#if defined(WOLFSSL_ECIES_MONTGOMERY) && !defined(NO_AES) && \ + defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) /* ECIES over a Montgomery curve (X25519 / X448). * * The key type is carried on the context, and the keys are handed to the - * generic wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2() entry points. */ + * generic wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2() entry points. + * + * The DEM is pinned to AES-128-CBC + HMAC-SHA256 below - the size checks + * assume it, and the build's default algorithm tracks the configuration. */ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, void* srvKey, word32 pubKeySz) { wc_test_ret_t ret = 0; ecEncCtx* ctx = NULL; - byte enc[MAX_ECIES_TEST_SZ]; - byte plain[MAX_ECIES_TEST_SZ]; - word32 encSz = (word32)sizeof(enc); - word32 plainSz = (word32)sizeof(plain); + WC_DECLARE_VAR(enc, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); + WC_DECLARE_VAR(plain, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); + WC_DECLARE_VAR(tmp, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); + word32 encSz = MAX_ECIES_TEST_SZ; + word32 plainSz = MAX_ECIES_TEST_SZ; word32 expectSz; /* The ephemeral public key is only carried in the message outside of OLD * mode, and GEN_IV additionally embeds the IV. The DEM here is @@ -47723,9 +47734,19 @@ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, static const byte msg[] = "ECIES on a Montgomery curve"; const word32 msgSz = 16 * 2; + WC_ALLOC_VAR(enc, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); + WC_ALLOC_VAR(plain, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); + WC_ALLOC_VAR(tmp, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); + if (!WC_VAR_OK(enc) || !WC_VAR_OK(plain) || !WC_VAR_OK(tmp)) { + ret = WC_TEST_RET_ENC_EC(MEMORY_E); + goto done; + } + ctx = wc_ecc_ctx_new(0, rng); - if (ctx == NULL) - return WC_TEST_RET_ENC_NC; + if (ctx == NULL) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } ret = wc_ecc_ctx_set_curve_id(ctx, curveId); if (ret != 0) { @@ -47733,8 +47754,15 @@ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, goto done; } - XMEMSET(enc, 0, sizeof(enc)); - XMEMSET(plain, 0, sizeof(plain)); + ret = wc_ecc_ctx_set_algo(ctx, ecAES_128_CBC, ecHKDF_SHA256, + ecHMAC_SHA256); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + XMEMSET(enc, 0, MAX_ECIES_TEST_SZ); + XMEMSET(plain, 0, MAX_ECIES_TEST_SZ); XMEMCPY(plain, msg, XSTRLEN((const char*)msg) + 1); ret = wc_ecc_encrypt_ex2(ephKey, srvKey, plain, msgSz, enc, &encSz, ctx, 0); @@ -47758,8 +47786,7 @@ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, /* Point compression is meaningless for a Montgomery curve. */ { - word32 tmpSz = (word32)sizeof(enc); - byte tmp[MAX_ECIES_TEST_SZ]; + word32 tmpSz = MAX_ECIES_TEST_SZ; if (wc_ecc_encrypt_ex2(ephKey, srvKey, plain, msgSz, tmp, &tmpSz, ctx, 1) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { ret = WC_TEST_RET_ENC_NC; @@ -47767,14 +47794,21 @@ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, } } - /* The typed entry points stay ECC-only. */ - if (wc_ecc_encrypt(NULL, NULL, plain, msgSz, enc, &encSz, ctx) != - WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + /* The typed entry points stay ECC-only. Pass the live keys and buffers + * so the rejection can only come from the Montgomery-context guard, not + * from the generic NULL-argument checks. */ + if (wc_ecc_encrypt((ecc_key*)ephKey, (ecc_key*)srvKey, plain, msgSz, enc, + &encSz, ctx) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + if (wc_ecc_encrypt_ex((ecc_key*)ephKey, (ecc_key*)srvKey, plain, msgSz, + enc, &encSz, ctx, 0) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { ret = WC_TEST_RET_ENC_NC; goto done; } - if (wc_ecc_decrypt(NULL, NULL, enc, encSz, plain, &plainSz, ctx) != - WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + if (wc_ecc_decrypt((ecc_key*)srvKey, (ecc_key*)ephKey, enc, encSz, plain, + &plainSz, ctx) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { ret = WC_TEST_RET_ENC_NC; goto done; } @@ -47798,8 +47832,8 @@ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, } } - XMEMSET(plain, 0, sizeof(plain)); - plainSz = (word32)sizeof(plain); + XMEMSET(plain, 0, MAX_ECIES_TEST_SZ); + plainSz = MAX_ECIES_TEST_SZ; ret = wc_ecc_decrypt_ex2(srvKey, decPubKey, enc, encSz, plain, &plainSz, ctx); if (ret != 0) { @@ -47822,7 +47856,7 @@ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, ret = WC_TEST_RET_ENC_EC(ret); goto done; } - plainSz = (word32)sizeof(plain); + plainSz = MAX_ECIES_TEST_SZ; if (wc_ecc_decrypt_ex2(srvKey, decPubKey, enc, encSz, plain, &plainSz, ctx) == 0) { ret = WC_TEST_RET_ENC_NC; @@ -47838,25 +47872,58 @@ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, ret = WC_TEST_RET_ENC_EC(ret); goto done; } - plainSz = (word32)sizeof(plain); + plainSz = MAX_ECIES_TEST_SZ; if (wc_ecc_decrypt_ex2(srvKey, decPubKey, enc, encSz, plain, &plainSz, ctx) == 0) { ret = WC_TEST_RET_ENC_NC; goto done; } + enc[0] ^= 0x01; + + /* An invalid ephemeral key must be rejected by the public-key validation + * itself - the check_public error, not a MAC failure. A set high bit is + * non-canonical for X25519; wc_curve448_check_public() accepts the high + * bit (a 448-bit coordinate fills its last byte), so X448 uses the + * all-zero low-order point instead. */ + { + int expErr; + if (curveId == ECC_X25519) { + enc[pubKeySz - 1] |= 0x80; + expErr = WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E); + } + else { + XMEMSET(enc, 0, pubKeySz); + expErr = WC_NO_ERR_TRACE(ECC_BAD_ARG_E); + } + ret = wc_ecc_ctx_reset(ctx, rng); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + plainSz = MAX_ECIES_TEST_SZ; + if (wc_ecc_decrypt_ex2(srvKey, decPubKey, enc, encSz, plain, &plainSz, + ctx) != expErr) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + } #endif ret = 0; done: wc_ecc_ctx_free(ctx); + WC_FREE_VAR(tmp, HEAP_HINT); + WC_FREE_VAR(plain, HEAP_HINT); + WC_FREE_VAR(enc, HEAP_HINT); return ret; } -#endif /* WOLFSSL_ECIES_MONTGOMERY */ +#endif /* WOLFSSL_ECIES_MONTGOMERY && AES-128-CBC */ #if defined(WOLFSSL_ECIES_X25519) && !defined(WOLFSSL_ECIES_OLD) && \ - !defined(WOLFSSL_ECIES_GEN_IV) && !defined(WOLFSSL_ECIES_ISO18033) + !defined(WOLFSSL_ECIES_GEN_IV) && !defined(WOLFSSL_ECIES_ISO18033) && \ + !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) /* Known answer test for ECIES over X25519. * * The keys are the Alice/Bob pair from RFC 7748 section 6.1. The test first @@ -47908,8 +47975,8 @@ static const byte ecies_x25519_kat_out[] = { static wc_test_ret_t ecies_x25519_kat(WC_RNG* rng) { wc_test_ret_t ret; - curve25519_key alice; - curve25519_key bob; + WC_DECLARE_VAR(alice, curve25519_key, 1, HEAP_HINT); + WC_DECLARE_VAR(bob, curve25519_key, 1, HEAP_HINT); ecEncCtx* ctx = NULL; byte out[sizeof(ecies_x25519_kat_out)]; byte plain[sizeof(ecies_x25519_kat_msg)]; @@ -47919,16 +47986,29 @@ static wc_test_ret_t ecies_x25519_kat(WC_RNG* rng) word32 sharedSz = (word32)sizeof(shared); const word32 msgSz = (word32)sizeof(ecies_x25519_kat_msg) - 1; - XMEMSET(&alice, 0, sizeof(alice)); - XMEMSET(&bob, 0, sizeof(bob)); + WC_ALLOC_VAR(alice, curve25519_key, 1, HEAP_HINT); + WC_ALLOC_VAR(bob, curve25519_key, 1, HEAP_HINT); + if (!WC_VAR_OK(alice) || !WC_VAR_OK(bob)) { + WC_FREE_VAR(bob, HEAP_HINT); + WC_FREE_VAR(alice, HEAP_HINT); + return WC_TEST_RET_ENC_EC(MEMORY_E); + } + + XMEMSET(alice, 0, sizeof(*alice)); + XMEMSET(bob, 0, sizeof(*bob)); XMEMSET(out, 0, sizeof(out)); - ret = wc_curve25519_init_ex(&alice, HEAP_HINT, INVALID_DEVID); - if (ret != 0) + ret = wc_curve25519_init_ex(alice, HEAP_HINT, INVALID_DEVID); + if (ret != 0) { + WC_FREE_VAR(bob, HEAP_HINT); + WC_FREE_VAR(alice, HEAP_HINT); return WC_TEST_RET_ENC_EC(ret); - ret = wc_curve25519_init_ex(&bob, HEAP_HINT, INVALID_DEVID); + } + ret = wc_curve25519_init_ex(bob, HEAP_HINT, INVALID_DEVID); if (ret != 0) { - wc_curve25519_free(&alice); + wc_curve25519_free(alice); + WC_FREE_VAR(bob, HEAP_HINT); + WC_FREE_VAR(alice, HEAP_HINT); return WC_TEST_RET_ENC_EC(ret); } @@ -47936,7 +48016,7 @@ static wc_test_ret_t ecies_x25519_kat(WC_RNG* rng) (word32)sizeof(ecies_x25519_kat_alice_priv), ecies_x25519_kat_alice_pub, (word32)sizeof(ecies_x25519_kat_alice_pub), - &alice, EC25519_LITTLE_ENDIAN); + alice, EC25519_LITTLE_ENDIAN); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto done; @@ -47944,19 +48024,19 @@ static wc_test_ret_t ecies_x25519_kat(WC_RNG* rng) ret = wc_curve25519_import_private_raw_ex( ecies_x25519_kat_bob_priv, (word32)sizeof(ecies_x25519_kat_bob_priv), ecies_x25519_kat_bob_pub, (word32)sizeof(ecies_x25519_kat_bob_pub), - &bob, EC25519_LITTLE_ENDIAN); + bob, EC25519_LITTLE_ENDIAN); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto done; } #ifdef WOLFSSL_CURVE25519_BLINDING - ret = wc_curve25519_set_rng(&alice, rng); + ret = wc_curve25519_set_rng(alice, rng); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto done; } - ret = wc_curve25519_set_rng(&bob, rng); + ret = wc_curve25519_set_rng(bob, rng); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto done; @@ -47964,7 +48044,7 @@ static wc_test_ret_t ecies_x25519_kat(WC_RNG* rng) #endif /* The little-endian shared secret is the one RFC 7748 specifies. */ - ret = wc_curve25519_shared_secret_ex(&alice, &bob, shared, &sharedSz, + ret = wc_curve25519_shared_secret_ex(alice, bob, shared, &sharedSz, EC25519_LITTLE_ENDIAN); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); @@ -47987,7 +48067,16 @@ static wc_test_ret_t ecies_x25519_kat(WC_RNG* rng) goto done; } - ret = wc_ecc_encrypt_ex2(&alice, &bob, ecies_x25519_kat_msg, msgSz, out, + /* The fixed vector is only valid for HKDF-SHA256 + AES-128-CBC + + * HMAC-SHA256, so pin the DEM rather than trusting the default. */ + ret = wc_ecc_ctx_set_algo(ctx, ecAES_128_CBC, ecHKDF_SHA256, + ecHMAC_SHA256); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + ret = wc_ecc_encrypt_ex2(alice, bob, ecies_x25519_kat_msg, msgSz, out, &outSz, ctx, 0); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); @@ -48005,7 +48094,7 @@ static wc_test_ret_t ecies_x25519_kat(WC_RNG* rng) ret = WC_TEST_RET_ENC_EC(ret); goto done; } - ret = wc_ecc_decrypt_ex2(&bob, NULL, ecies_x25519_kat_out, + ret = wc_ecc_decrypt_ex2(bob, NULL, ecies_x25519_kat_out, (word32)sizeof(ecies_x25519_kat_out), plain, &plainSz, ctx); if (ret != 0) { @@ -48022,55 +48111,236 @@ static wc_test_ret_t ecies_x25519_kat(WC_RNG* rng) done: wc_ecc_ctx_free(ctx); - wc_curve25519_free(&bob); - wc_curve25519_free(&alice); + wc_curve25519_free(bob); + wc_curve25519_free(alice); + WC_FREE_VAR(bob, HEAP_HINT); + WC_FREE_VAR(alice, HEAP_HINT); return ret; } #endif /* WOLFSSL_ECIES_X25519 && default IV mode */ -#if defined(WOLFSSL_ECIES_X25519) && !defined(WOLFSSL_ECIES_OLD) -/* ECIES over X25519 driven through the REQ/RESP salt exchange, which is the - * flow that resets a context between rounds, and with a non-default DEM. */ -static wc_test_ret_t ecies_x25519_req_resp_test(WC_RNG* rng, byte encAlgo) +#if defined(WOLFSSL_ECIES_X448) && !defined(WOLFSSL_ECIES_OLD) && \ + !defined(WOLFSSL_ECIES_GEN_IV) && !defined(WOLFSSL_ECIES_ISO18033) && \ + !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) +/* Known answer test for ECIES over X448, the sibling of ecies_x25519_kat(). + * + * The keys are the Alice/Bob pair from RFC 7748 section 6.2, and the raw + * shared secret is checked against the RFC's value to pin the byte order + * wc_curve448_shared_secret_ex() is asked to derive it in. */ +static const byte ecies_x448_kat_alice_priv[] = { + 0x9a,0x8f,0x49,0x25,0xd1,0x51,0x9f,0x57,0x75,0xcf,0x46,0xb0, + 0x4b,0x58,0x00,0xd4,0xee,0x9e,0xe8,0xba,0xe8,0xbc,0x55,0x65, + 0xd4,0x98,0xc2,0x8d,0xd9,0xc9,0xba,0xf5,0x74,0xa9,0x41,0x97, + 0x44,0x89,0x73,0x91,0x00,0x63,0x82,0xa6,0xf1,0x27,0xab,0x1d, + 0x9a,0xc2,0xd8,0xc0,0xa5,0x98,0x72,0x6b +}; +static const byte ecies_x448_kat_alice_pub[] = { + 0x9b,0x08,0xf7,0xcc,0x31,0xb7,0xe3,0xe6,0x7d,0x22,0xd5,0xae, + 0xa1,0x21,0x07,0x4a,0x27,0x3b,0xd2,0xb8,0x3d,0xe0,0x9c,0x63, + 0xfa,0xa7,0x3d,0x2c,0x22,0xc5,0xd9,0xbb,0xc8,0x36,0x64,0x72, + 0x41,0xd9,0x53,0xd4,0x0c,0x5b,0x12,0xda,0x88,0x12,0x0d,0x53, + 0x17,0x7f,0x80,0xe5,0x32,0xc4,0x1f,0xa0 +}; +static const byte ecies_x448_kat_bob_priv[] = { + 0x1c,0x30,0x6a,0x7a,0xc2,0xa0,0xe2,0xe0,0x99,0x0b,0x29,0x44, + 0x70,0xcb,0xa3,0x39,0xe6,0x45,0x37,0x72,0xb0,0x75,0x81,0x1d, + 0x8f,0xad,0x0d,0x1d,0x69,0x27,0xc1,0x20,0xbb,0x5e,0xe8,0x97, + 0x2b,0x0d,0x3e,0x21,0x37,0x4c,0x9c,0x92,0x1b,0x09,0xd1,0xb0, + 0x36,0x6f,0x10,0xb6,0x51,0x73,0x99,0x2d +}; +static const byte ecies_x448_kat_bob_pub[] = { + 0x3e,0xb7,0xa8,0x29,0xb0,0xcd,0x20,0xf5,0xbc,0xfc,0x0b,0x59, + 0x9b,0x6f,0xec,0xcf,0x6d,0xa4,0x62,0x71,0x07,0xbd,0xb0,0xd4, + 0xf3,0x45,0xb4,0x30,0x27,0xd8,0xb9,0x72,0xfc,0x3e,0x34,0xfb, + 0x42,0x32,0xa1,0x3c,0xa7,0x06,0xdc,0xb5,0x7a,0xec,0x3d,0xae, + 0x07,0xbd,0xc1,0xc6,0x7b,0xf3,0x36,0x09 +}; +/* RFC 7748 section 6.2 shared secret K. */ +static const byte ecies_x448_kat_shared[] = { + 0x07,0xff,0xf4,0x18,0x1a,0xc6,0xcc,0x95,0xec,0x1c,0x16,0xa9, + 0x4a,0x0f,0x74,0xd1,0x2d,0xa2,0x32,0xce,0x40,0xa7,0x75,0x52, + 0x28,0x1d,0x28,0x2b,0xb6,0x0c,0x0b,0x56,0xfd,0x24,0x64,0xc3, + 0x35,0x54,0x39,0x36,0x52,0x1c,0x24,0x40,0x30,0x85,0xd5,0x9a, + 0x44,0x9a,0x50,0x37,0x51,0x4a,0x87,0x9d +}; +static const byte ecies_x448_kat_msg[] = + "ECIES X448 known answer test!!!!"; /* 32 bytes, no NUL used */ +/* Alice's public key, then AES-128-CBC ciphertext, then HMAC-SHA256. */ +static const byte ecies_x448_kat_out[] = { + 0x9b,0x08,0xf7,0xcc,0x31,0xb7,0xe3,0xe6,0x7d,0x22,0xd5,0xae, + 0xa1,0x21,0x07,0x4a,0x27,0x3b,0xd2,0xb8,0x3d,0xe0,0x9c,0x63, + 0xfa,0xa7,0x3d,0x2c,0x22,0xc5,0xd9,0xbb,0xc8,0x36,0x64,0x72, + 0x41,0xd9,0x53,0xd4,0x0c,0x5b,0x12,0xda,0x88,0x12,0x0d,0x53, + 0x17,0x7f,0x80,0xe5,0x32,0xc4,0x1f,0xa0,0x6e,0x25,0x43,0x12, + 0x87,0x47,0x3a,0xbe,0xec,0x5f,0x16,0xca,0x11,0xa2,0x24,0x9b, + 0x6c,0x4b,0xd2,0x86,0x88,0xce,0x9b,0xef,0x09,0x64,0xf8,0x1d, + 0xaf,0xa7,0xce,0x01,0x13,0x76,0x38,0xdf,0xc0,0xa7,0x00,0x51, + 0x9e,0x6c,0x52,0x8e,0xfd,0x6c,0x01,0x9e,0xe0,0x22,0x6b,0xae, + 0xe3,0xac,0xfa,0xc8,0x1f,0x37,0x71,0x79,0x50,0xbe,0x9d,0x22 +}; + +static wc_test_ret_t ecies_x448_kat(WC_RNG* rng) { wc_test_ret_t ret; - curve25519_key cliKey; - curve25519_key srvKey; - ecEncCtx* cliCtx = NULL; - ecEncCtx* srvCtx = NULL; - const byte* tmpSalt; - byte cliSalt[EXCHANGE_SALT_SZ]; - byte srvSalt[EXCHANGE_SALT_SZ]; - byte out[MAX_ECIES_TEST_SZ]; - byte plain[MAX_ECIES_TEST_SZ]; + WC_DECLARE_VAR(alice, curve448_key, 1, HEAP_HINT); + WC_DECLARE_VAR(bob, curve448_key, 1, HEAP_HINT); + ecEncCtx* ctx = NULL; + byte out[sizeof(ecies_x448_kat_out)]; + byte plain[sizeof(ecies_x448_kat_msg)]; + byte shared[CURVE448_KEY_SIZE]; word32 outSz = (word32)sizeof(out); word32 plainSz = (word32)sizeof(plain); - static const byte msg[] = "ECIES X25519 request/response!!!"; - const word32 msgSz = (word32)sizeof(msg) - 1; + word32 sharedSz = (word32)sizeof(shared); + const word32 msgSz = (word32)sizeof(ecies_x448_kat_msg) - 1; - XMEMSET(&cliKey, 0, sizeof(cliKey)); - XMEMSET(&srvKey, 0, sizeof(srvKey)); + WC_ALLOC_VAR(alice, curve448_key, 1, HEAP_HINT); + WC_ALLOC_VAR(bob, curve448_key, 1, HEAP_HINT); + if (!WC_VAR_OK(alice) || !WC_VAR_OK(bob)) { + WC_FREE_VAR(bob, HEAP_HINT); + WC_FREE_VAR(alice, HEAP_HINT); + return WC_TEST_RET_ENC_EC(MEMORY_E); + } - ret = wc_curve25519_init_ex(&cliKey, HEAP_HINT, INVALID_DEVID); - if (ret != 0) + XMEMSET(alice, 0, sizeof(*alice)); + XMEMSET(bob, 0, sizeof(*bob)); + XMEMSET(out, 0, sizeof(out)); + + ret = wc_curve448_init(alice); + if (ret != 0) { + WC_FREE_VAR(bob, HEAP_HINT); + WC_FREE_VAR(alice, HEAP_HINT); return WC_TEST_RET_ENC_EC(ret); - ret = wc_curve25519_init_ex(&srvKey, HEAP_HINT, INVALID_DEVID); + } + ret = wc_curve448_init(bob); if (ret != 0) { - wc_curve25519_free(&cliKey); + wc_curve448_free(alice); + WC_FREE_VAR(bob, HEAP_HINT); + WC_FREE_VAR(alice, HEAP_HINT); return WC_TEST_RET_ENC_EC(ret); } - ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, &cliKey); + ret = wc_curve448_import_private_raw_ex(ecies_x448_kat_alice_priv, + (word32)sizeof(ecies_x448_kat_alice_priv), + ecies_x448_kat_alice_pub, + (word32)sizeof(ecies_x448_kat_alice_pub), + alice, EC448_LITTLE_ENDIAN); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto done; } - ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, &srvKey); + ret = wc_curve448_import_private_raw_ex( + ecies_x448_kat_bob_priv, (word32)sizeof(ecies_x448_kat_bob_priv), + ecies_x448_kat_bob_pub, (word32)sizeof(ecies_x448_kat_bob_pub), + bob, EC448_LITTLE_ENDIAN); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + /* The little-endian shared secret is the one RFC 7748 specifies. */ + ret = wc_curve448_shared_secret_ex(alice, bob, shared, &sharedSz, + EC448_LITTLE_ENDIAN); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto done; } + if (sharedSz != (word32)sizeof(ecies_x448_kat_shared) || + XMEMCMP(shared, ecies_x448_kat_shared, sharedSz) != 0) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + + ctx = wc_ecc_ctx_new(0, rng); + if (ctx == NULL) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + ret = wc_ecc_ctx_set_curve_id(ctx, ECC_X448); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + /* The fixed vector is only valid for HKDF-SHA256 + AES-128-CBC + + * HMAC-SHA256, so pin the DEM rather than trusting the default. */ + ret = wc_ecc_ctx_set_algo(ctx, ecAES_128_CBC, ecHKDF_SHA256, + ecHMAC_SHA256); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + + ret = wc_ecc_encrypt_ex2(alice, bob, ecies_x448_kat_msg, msgSz, out, + &outSz, ctx, 0); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + if (outSz != (word32)sizeof(ecies_x448_kat_out) || + XMEMCMP(out, ecies_x448_kat_out, outSz) != 0) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + + /* And it decrypts back. */ + ret = wc_ecc_ctx_reset(ctx, rng); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + ret = wc_ecc_decrypt_ex2(bob, NULL, ecies_x448_kat_out, + (word32)sizeof(ecies_x448_kat_out), plain, + &plainSz, ctx); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } + if (plainSz != msgSz || + XMEMCMP(plain, ecies_x448_kat_msg, msgSz) != 0) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } + + ret = 0; + +done: + wc_ecc_ctx_free(ctx); + wc_curve448_free(bob); + wc_curve448_free(alice); + WC_FREE_VAR(bob, HEAP_HINT); + WC_FREE_VAR(alice, HEAP_HINT); + + return ret; +} +#endif /* WOLFSSL_ECIES_X448 && default IV mode */ + +#if defined(WOLFSSL_ECIES_MONTGOMERY) && !defined(WOLFSSL_ECIES_OLD) +/* ECIES over a Montgomery curve driven through the REQ/RESP salt exchange, + * which is the flow that resets a context between rounds, and with a + * caller-selected DEM. The keys are made by the per-curve wrappers below. */ +static wc_test_ret_t ecies_mont_req_resp_test(WC_RNG* rng, byte encAlgo, + int curveId, void* cliKey, void* srvKey) +{ + wc_test_ret_t ret; + WC_DECLARE_VAR(out, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); + WC_DECLARE_VAR(plain, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); + ecEncCtx* cliCtx = NULL; + ecEncCtx* srvCtx = NULL; + const byte* tmpSalt; + byte cliSalt[EXCHANGE_SALT_SZ]; + byte srvSalt[EXCHANGE_SALT_SZ]; + word32 outSz = MAX_ECIES_TEST_SZ; + word32 plainSz = MAX_ECIES_TEST_SZ; + static const byte msg[] = "ECIES Montgomery req/response!!!"; + const word32 msgSz = (word32)sizeof(msg) - 1; + + WC_ALLOC_VAR(out, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); + WC_ALLOC_VAR(plain, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); + if (!WC_VAR_OK(out) || !WC_VAR_OK(plain)) { + ret = WC_TEST_RET_ENC_EC(MEMORY_E); + goto done; + } cliCtx = wc_ecc_ctx_new(REQ_RESP_CLIENT, rng); srvCtx = wc_ecc_ctx_new(REQ_RESP_SERVER, rng); @@ -48079,9 +48349,9 @@ static wc_test_ret_t ecies_x25519_req_resp_test(WC_RNG* rng, byte encAlgo) goto done; } - ret = wc_ecc_ctx_set_curve_id(cliCtx, ECC_X25519); + ret = wc_ecc_ctx_set_curve_id(cliCtx, curveId); if (ret == 0) - ret = wc_ecc_ctx_set_curve_id(srvCtx, ECC_X25519); + ret = wc_ecc_ctx_set_curve_id(srvCtx, curveId); if (ret == 0) ret = wc_ecc_ctx_set_algo(cliCtx, encAlgo, ecHKDF_SHA256, ecHMAC_SHA256); @@ -48116,7 +48386,7 @@ static wc_test_ret_t ecies_x25519_req_resp_test(WC_RNG* rng, byte encAlgo) } /* Client request. */ - ret = wc_ecc_encrypt_ex2(&cliKey, &srvKey, msg, msgSz, out, &outSz, + ret = wc_ecc_encrypt_ex2(cliKey, srvKey, msg, msgSz, out, &outSz, cliCtx, 0); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); @@ -48125,7 +48395,7 @@ static wc_test_ret_t ecies_x25519_req_resp_test(WC_RNG* rng, byte encAlgo) /* Server decrypts it, using the doubled key material of the REQ/RESP * protocol. */ - ret = wc_ecc_decrypt_ex2(&srvKey, NULL, out, outSz, plain, &plainSz, + ret = wc_ecc_decrypt_ex2(srvKey, NULL, out, outSz, plain, &plainSz, srvCtx); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); @@ -48154,7 +48424,7 @@ static wc_test_ret_t ecies_x25519_req_resp_test(WC_RNG* rng, byte encAlgo) ret = WC_TEST_RET_ENC_EC(ret); goto done; } - if (cliId != ECC_X25519 || srvId != ECC_X25519) { + if (cliId != curveId || srvId != curveId) { ret = WC_TEST_RET_ENC_NC; goto done; } @@ -48165,53 +48435,162 @@ static wc_test_ret_t ecies_x25519_req_resp_test(WC_RNG* rng, byte encAlgo) done: wc_ecc_ctx_free(srvCtx); wc_ecc_ctx_free(cliCtx); - wc_curve25519_free(&srvKey); - wc_curve25519_free(&cliKey); + WC_FREE_VAR(plain, HEAP_HINT); + WC_FREE_VAR(out, HEAP_HINT); + + return ret; +} +#endif /* WOLFSSL_ECIES_MONTGOMERY && !WOLFSSL_ECIES_OLD */ + +#if defined(WOLFSSL_ECIES_X25519) && !defined(WOLFSSL_ECIES_OLD) +static wc_test_ret_t ecies_x25519_req_resp_test(WC_RNG* rng, byte encAlgo) +{ + wc_test_ret_t ret; + WC_DECLARE_VAR(cliKey, curve25519_key, 1, HEAP_HINT); + WC_DECLARE_VAR(srvKey, curve25519_key, 1, HEAP_HINT); + + WC_ALLOC_VAR(cliKey, curve25519_key, 1, HEAP_HINT); + WC_ALLOC_VAR(srvKey, curve25519_key, 1, HEAP_HINT); + if (!WC_VAR_OK(cliKey) || !WC_VAR_OK(srvKey)) { + ret = WC_TEST_RET_ENC_EC(MEMORY_E); + goto done_free; + } + + XMEMSET(cliKey, 0, sizeof(*cliKey)); + XMEMSET(srvKey, 0, sizeof(*srvKey)); + + ret = wc_curve25519_init_ex(cliKey, HEAP_HINT, INVALID_DEVID); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done_free; + } + ret = wc_curve25519_init_ex(srvKey, HEAP_HINT, INVALID_DEVID); + if (ret != 0) { + wc_curve25519_free(cliKey); + ret = WC_TEST_RET_ENC_EC(ret); + goto done_free; + } + + ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, cliKey); + if (ret == 0) + ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, srvKey); + if (ret != 0) + ret = WC_TEST_RET_ENC_EC(ret); + if (ret == 0) + ret = ecies_mont_req_resp_test(rng, encAlgo, ECC_X25519, cliKey, + srvKey); + + wc_curve25519_free(srvKey); + wc_curve25519_free(cliKey); +done_free: + WC_FREE_VAR(srvKey, HEAP_HINT); + WC_FREE_VAR(cliKey, HEAP_HINT); return ret; } #endif /* WOLFSSL_ECIES_X25519 && !WOLFSSL_ECIES_OLD */ +#if defined(WOLFSSL_ECIES_X448) && !defined(WOLFSSL_ECIES_OLD) +static wc_test_ret_t ecies_x448_req_resp_test(WC_RNG* rng, byte encAlgo) +{ + wc_test_ret_t ret; + WC_DECLARE_VAR(cliKey, curve448_key, 1, HEAP_HINT); + WC_DECLARE_VAR(srvKey, curve448_key, 1, HEAP_HINT); + + WC_ALLOC_VAR(cliKey, curve448_key, 1, HEAP_HINT); + WC_ALLOC_VAR(srvKey, curve448_key, 1, HEAP_HINT); + if (!WC_VAR_OK(cliKey) || !WC_VAR_OK(srvKey)) { + ret = WC_TEST_RET_ENC_EC(MEMORY_E); + goto done_free; + } + + XMEMSET(cliKey, 0, sizeof(*cliKey)); + XMEMSET(srvKey, 0, sizeof(*srvKey)); + + ret = wc_curve448_init(cliKey); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done_free; + } + ret = wc_curve448_init(srvKey); + if (ret != 0) { + wc_curve448_free(cliKey); + ret = WC_TEST_RET_ENC_EC(ret); + goto done_free; + } + + ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, cliKey); + if (ret == 0) + ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, srvKey); + if (ret != 0) + ret = WC_TEST_RET_ENC_EC(ret); + if (ret == 0) + ret = ecies_mont_req_resp_test(rng, encAlgo, ECC_X448, cliKey, + srvKey); + + wc_curve448_free(srvKey); + wc_curve448_free(cliKey); +done_free: + WC_FREE_VAR(srvKey, HEAP_HINT); + WC_FREE_VAR(cliKey, HEAP_HINT); + + return ret; +} +#endif /* WOLFSSL_ECIES_X448 && !WOLFSSL_ECIES_OLD */ + #ifdef WOLFSSL_ECIES_X25519 static wc_test_ret_t ecies_x25519_test(WC_RNG* rng) { wc_test_ret_t ret; - curve25519_key ephKey; - curve25519_key srvKey; + WC_DECLARE_VAR(ephKey, curve25519_key, 1, HEAP_HINT); + WC_DECLARE_VAR(srvKey, curve25519_key, 1, HEAP_HINT); + WC_DECLARE_VAR(enc, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); byte wire[CURVE25519_PUB_KEY_SIZE]; word32 wireSz = (word32)sizeof(wire); - byte enc[MAX_ECIES_TEST_SZ]; - word32 encSz = (word32)sizeof(enc); + word32 encSz = MAX_ECIES_TEST_SZ; byte msg[32]; ecEncCtx* ctx; - XMEMSET(&ephKey, 0, sizeof(ephKey)); - XMEMSET(&srvKey, 0, sizeof(srvKey)); + WC_ALLOC_VAR(ephKey, curve25519_key, 1, HEAP_HINT); + WC_ALLOC_VAR(srvKey, curve25519_key, 1, HEAP_HINT); + WC_ALLOC_VAR(enc, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); + if (!WC_VAR_OK(ephKey) || !WC_VAR_OK(srvKey) || !WC_VAR_OK(enc)) { + ret = WC_TEST_RET_ENC_EC(MEMORY_E); + goto done_free; + } - ret = wc_curve25519_init_ex(&ephKey, HEAP_HINT, INVALID_DEVID); - if (ret != 0) - return WC_TEST_RET_ENC_EC(ret); - ret = wc_curve25519_init_ex(&srvKey, HEAP_HINT, INVALID_DEVID); + XMEMSET(ephKey, 0, sizeof(*ephKey)); + XMEMSET(srvKey, 0, sizeof(*srvKey)); + + ret = wc_curve25519_init_ex(ephKey, HEAP_HINT, INVALID_DEVID); if (ret != 0) { - wc_curve25519_free(&ephKey); - return WC_TEST_RET_ENC_EC(ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto done_free; + } + ret = wc_curve25519_init_ex(srvKey, HEAP_HINT, INVALID_DEVID); + if (ret != 0) { + wc_curve25519_free(ephKey); + ret = WC_TEST_RET_ENC_EC(ret); + goto done_free; } - ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, &ephKey); + ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, ephKey); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto done; } - ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, &srvKey); + ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, srvKey); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto done; } - ret = ecies_mont_test(rng, ECC_X25519, &ephKey, &srvKey, +#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) + ret = ecies_mont_test(rng, ECC_X25519, ephKey, srvKey, CURVE25519_PUB_KEY_SIZE); if (ret != 0) goto done; +#endif #ifndef WOLFSSL_ECIES_OLD /* The ephemeral key goes on the wire as a raw little-endian @@ -48224,11 +48603,11 @@ static wc_test_ret_t ecies_x25519_test(WC_RNG* rng) } ret = wc_ecc_ctx_set_curve_id(ctx, ECC_X25519); if (ret == 0) { - ret = wc_ecc_encrypt_ex2(&ephKey, &srvKey, msg, (word32)sizeof(msg), + ret = wc_ecc_encrypt_ex2(ephKey, srvKey, msg, (word32)sizeof(msg), enc, &encSz, ctx, 0); } if (ret == 0) { - ret = wc_curve25519_export_public_ex(&ephKey, wire, &wireSz, + ret = wc_curve25519_export_public_ex(ephKey, wire, &wireSz, EC25519_LITTLE_ENDIAN); } wc_ecc_ctx_free(ctx); @@ -48245,8 +48624,12 @@ static wc_test_ret_t ecies_x25519_test(WC_RNG* rng) #endif /* !WOLFSSL_ECIES_OLD */ done: - wc_curve25519_free(&srvKey); - wc_curve25519_free(&ephKey); + wc_curve25519_free(srvKey); + wc_curve25519_free(ephKey); +done_free: + WC_FREE_VAR(enc, HEAP_HINT); + WC_FREE_VAR(srvKey, HEAP_HINT); + WC_FREE_VAR(ephKey, HEAP_HINT); return ret; } @@ -48256,42 +48639,55 @@ static wc_test_ret_t ecies_x25519_test(WC_RNG* rng) static wc_test_ret_t ecies_x448_test(WC_RNG* rng) { wc_test_ret_t ret; - curve448_key ephKey; - curve448_key srvKey; + WC_DECLARE_VAR(ephKey, curve448_key, 1, HEAP_HINT); + WC_DECLARE_VAR(srvKey, curve448_key, 1, HEAP_HINT); + WC_DECLARE_VAR(enc, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); byte wire[CURVE448_PUB_KEY_SIZE]; word32 wireSz = (word32)sizeof(wire); - byte enc[MAX_ECIES_TEST_SZ]; - word32 encSz = (word32)sizeof(enc); + word32 encSz = MAX_ECIES_TEST_SZ; byte msg[32]; ecEncCtx* ctx; - XMEMSET(&ephKey, 0, sizeof(ephKey)); - XMEMSET(&srvKey, 0, sizeof(srvKey)); + WC_ALLOC_VAR(ephKey, curve448_key, 1, HEAP_HINT); + WC_ALLOC_VAR(srvKey, curve448_key, 1, HEAP_HINT); + WC_ALLOC_VAR(enc, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); + if (!WC_VAR_OK(ephKey) || !WC_VAR_OK(srvKey) || !WC_VAR_OK(enc)) { + ret = WC_TEST_RET_ENC_EC(MEMORY_E); + goto done_free; + } - ret = wc_curve448_init(&ephKey); - if (ret != 0) - return WC_TEST_RET_ENC_EC(ret); - ret = wc_curve448_init(&srvKey); + XMEMSET(ephKey, 0, sizeof(*ephKey)); + XMEMSET(srvKey, 0, sizeof(*srvKey)); + + ret = wc_curve448_init(ephKey); if (ret != 0) { - wc_curve448_free(&ephKey); - return WC_TEST_RET_ENC_EC(ret); + ret = WC_TEST_RET_ENC_EC(ret); + goto done_free; + } + ret = wc_curve448_init(srvKey); + if (ret != 0) { + wc_curve448_free(ephKey); + ret = WC_TEST_RET_ENC_EC(ret); + goto done_free; } - ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, &ephKey); + ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, ephKey); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto done; } - ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, &srvKey); + ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, srvKey); if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto done; } - ret = ecies_mont_test(rng, ECC_X448, &ephKey, &srvKey, +#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) + ret = ecies_mont_test(rng, ECC_X448, ephKey, srvKey, CURVE448_PUB_KEY_SIZE); if (ret != 0) goto done; +#endif #ifndef WOLFSSL_ECIES_OLD XMEMSET(msg, 0, sizeof(msg)); @@ -48302,11 +48698,11 @@ static wc_test_ret_t ecies_x448_test(WC_RNG* rng) } ret = wc_ecc_ctx_set_curve_id(ctx, ECC_X448); if (ret == 0) { - ret = wc_ecc_encrypt_ex2(&ephKey, &srvKey, msg, (word32)sizeof(msg), + ret = wc_ecc_encrypt_ex2(ephKey, srvKey, msg, (word32)sizeof(msg), enc, &encSz, ctx, 0); } if (ret == 0) { - ret = wc_curve448_export_public_ex(&ephKey, wire, &wireSz, + ret = wc_curve448_export_public_ex(ephKey, wire, &wireSz, EC448_LITTLE_ENDIAN); } wc_ecc_ctx_free(ctx); @@ -48323,13 +48719,19 @@ static wc_test_ret_t ecies_x448_test(WC_RNG* rng) #endif /* !WOLFSSL_ECIES_OLD */ done: - wc_curve448_free(&srvKey); - wc_curve448_free(&ephKey); + wc_curve448_free(srvKey); + wc_curve448_free(ephKey); +done_free: + WC_FREE_VAR(enc, HEAP_HINT); + WC_FREE_VAR(srvKey, HEAP_HINT); + WC_FREE_VAR(ephKey, HEAP_HINT); return ret; } #endif /* WOLFSSL_ECIES_X448 */ +#endif /* !WOLFSSL_NO_MALLOC && (!HAVE_FIPS || FIPS_VERSION_GE(5,3)) */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecc_encrypt_test(void) { WC_RNG rng; @@ -48414,12 +48816,15 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecc_encrypt_test(void) if (ret == 0) ret = ecc_encrypt_cryptocb_test(&rng); #endif +#ifndef WOLFSSL_NO_MALLOC #ifdef WOLFSSL_ECIES_X25519 if (ret == 0) ret = ecies_x25519_test(&rng); #ifndef WOLFSSL_ECIES_OLD +#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) if (ret == 0) ret = ecies_x25519_req_resp_test(&rng, ecAES_128_CBC); +#endif #if !defined(NO_AES) && defined(WOLFSSL_AES_COUNTER) && defined(WOLFSSL_AES_256) /* A different DEM: AES-256-CTR does no block padding. */ if (ret == 0) @@ -48432,7 +48837,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecc_encrypt_test(void) #endif #endif /* !WOLFSSL_ECIES_OLD */ #if !defined(WOLFSSL_ECIES_OLD) && !defined(WOLFSSL_ECIES_GEN_IV) && \ - !defined(WOLFSSL_ECIES_ISO18033) + !defined(WOLFSSL_ECIES_ISO18033) && \ + !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) if (ret == 0) ret = ecies_x25519_kat(&rng); #endif @@ -48440,7 +48846,19 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecc_encrypt_test(void) #ifdef WOLFSSL_ECIES_X448 if (ret == 0) ret = ecies_x448_test(&rng); +#if !defined(WOLFSSL_ECIES_OLD) && \ + !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) + if (ret == 0) + ret = ecies_x448_req_resp_test(&rng, ecAES_128_CBC); #endif +#if !defined(WOLFSSL_ECIES_OLD) && !defined(WOLFSSL_ECIES_GEN_IV) && \ + !defined(WOLFSSL_ECIES_ISO18033) && \ + !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) + if (ret == 0) + ret = ecies_x448_kat(&rng); +#endif +#endif +#endif /* !WOLFSSL_NO_MALLOC */ #endif /* !HAVE_FIPS || FIPS_VERSION_GE(5,3) */ done: From c83b3e77a78797dc1bba0987f2763cabcd454b0d Mon Sep 17 00:00:00 2001 From: night1rider Date: Sun, 23 Aug 2026 23:46:00 -0600 Subject: [PATCH 3/4] ECIES: dispatch crypto callbacks generically for Montgomery curves --- doc/dox_comments/header_files/ecc.h | 126 +++++- tests/api/test_ecc.c | 521 ++++++++++++++++++++- tests/api/test_ecc.h | 6 + tests/unit-mcdc/test_ecc_whitebox.c | 6 +- wolfcrypt/benchmark/benchmark.c | 7 + wolfcrypt/src/cryptocb.c | 130 +++++- wolfcrypt/src/ecc.c | 320 +++++++------ wolfcrypt/test/test.c | 677 ++++++++++++++++++---------- wolfssl/wolfcrypt/cryptocb.h | 40 +- wolfssl/wolfcrypt/ecc.h | 41 +- wolfssl/wolfcrypt/types.h | 8 + 11 files changed, 1453 insertions(+), 429 deletions(-) diff --git a/doc/dox_comments/header_files/ecc.h b/doc/dox_comments/header_files/ecc.h index d4144a06f9..fc0f402882 100644 --- a/doc/dox_comments/header_files/ecc.h +++ b/doc/dox_comments/header_files/ecc.h @@ -1958,6 +1958,100 @@ int wc_ecc_ctx_set_curve_id(ecEncCtx* ctx, int curveId); int wc_ecc_ctx_get_curve_id(ecEncCtx* ctx, int* curveId); +/*! + \ingroup ECC + + \brief This function selects the device the whole-operation ECIES crypto + callback (WOLF_CRYPTO_CB) dispatches to for operations using this + context. It routes only that callback: the DEM cipher, KDF and MAC + primitives of the software path route by wc_ecc_ctx_set_algo_dev_ids, + and the ECDH shared secret by the keys' own devIds. Only an ecc_key's + own devId participates in the resolution: it is adopted onto the context + only while the context devId is INVALID_DEVID (the key is never + modified). Once the context devId is set it always wins, even when the + key names a different device. Like the key type set with + wc_ecc_ctx_set_curve_id, the devId survives wc_ecc_ctx_reset. Only + built when crypto callbacks are enabled. + + \return 0 Returned upon successfully setting the device id. + \return BAD_FUNC_ARG Returned if ctx is NULL. + + \param ctx pointer to the ecEncCtx to configure + \param devId the device id registered with wc_CryptoCb_RegisterDevice, + or INVALID_DEVID to clear it + + _Example_ + \code + ecEncCtx* ctx = wc_ecc_ctx_new(0, &rng); + if (wc_ecc_ctx_set_curve_id(ctx, ECC_X25519) != 0 || + wc_ecc_ctx_set_dev_id(ctx, myDevId) != 0) { + // error configuring the context + } + \endcode + + \sa wc_ecc_ctx_set_curve_id + \sa wc_ecc_encrypt_ex2 + \sa wc_ecc_decrypt_ex2 +*/ + +int wc_ecc_ctx_set_dev_id(ecEncCtx* ctx, int devId); + +/*! + \ingroup ECC + + \brief This function reads back the devId configured on (or adopted by) + an ecEncCtx object. Only built when crypto callbacks are enabled. + + \return 0 Returned upon successfully reading the devId. + \return BAD_FUNC_ARG Returned if either argument is NULL. + + \param ctx pointer to the ecEncCtx to read + \param devId pointer to an int that receives the devId, or INVALID_DEVID + when none is set + + \sa wc_ecc_ctx_set_dev_id +*/ + +int wc_ecc_ctx_get_dev_id(ecEncCtx* ctx, int* devId); + +/*! + \ingroup ECC + + \brief This function declares, in a single call, the devIds the software + ECIES path hands the underlying primitives, mirroring the + wc_ecc_ctx_set_algo trio: encDevId routes the DEM cipher (AES), kdfDevId + the KDF (HKDF) and macDevId the MAC (HMAC/SHA). Pass INVALID_DEVID for + any primitive that should stay in software; that is also the default when + this function is never called. The ECIES devId + (wc_ecc_ctx_set_dev_id) routes only the whole-operation crypto callback + and is never inherited by the primitives. The values survive + wc_ecc_ctx_reset. Only built when crypto callbacks are enabled. + + \return 0 Returned upon successfully setting the devIds. + \return BAD_FUNC_ARG Returned if ctx is NULL. + + \param ctx pointer to the ecEncCtx to configure + \param encDevId device for the DEM cipher, or INVALID_DEVID for software + \param kdfDevId device for the KDF, or INVALID_DEVID for software + \param macDevId device for the MAC, or INVALID_DEVID for software + + _Example_ + \code + ecEncCtx* ctx = wc_ecc_ctx_new(0, &rng); + // AES and HMAC on the accelerator, HKDF in software + if (wc_ecc_ctx_set_algo_dev_ids(ctx, myDevId, INVALID_DEVID, + myDevId) != 0) { + // error configuring the context + } + \endcode + + \sa wc_ecc_ctx_set_algo + \sa wc_ecc_ctx_set_dev_id +*/ + +int wc_ecc_ctx_set_algo_dev_ids(ecEncCtx* ctx, int encDevId, int kdfDevId, + int macDevId); + /*! \ingroup ECC @@ -2145,6 +2239,14 @@ int wc_ecc_ctx_set_info(ecEncCtx* ctx, const byte* info, int sz); otherwise this function returns NOT_COMPILED_IN. See wc_ecc_encrypt_ex for the full rationale. + \note With WOLF_CRYPTO_CB, the device the ECIES crypto callback dispatches + to is the context's devId (wc_ecc_ctx_set_dev_id). A devId given to + wc_ecc_init_ex() on privKey is adopted onto the context only while the + context devId is INVALID_DEVID (the key is never modified); a set + context devId always wins. New code should prefer the context setter: + one setting covers both directions of the exchange, and it is the only + route for the Montgomery key types. + _Example_ \code byte msg[] = { initialize with msg to encrypt. Ensure padded to block size }; @@ -2223,6 +2325,14 @@ int wc_ecc_encrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, algorithm. The macro is not needed with WOLFSSL_ECIES_GEN_IV (random per-message nonce) or WOLFSSL_ECIES_OLD (nonce derived from the KDF output). + \note With WOLF_CRYPTO_CB, the device the ECIES crypto callback dispatches + to is the context's devId (wc_ecc_ctx_set_dev_id). A devId given to + wc_ecc_init_ex() on privKey is adopted onto the context only while the + context devId is INVALID_DEVID (the key is never modified); a set + context devId always wins. New code should prefer the context setter: + one setting covers both directions of the exchange, and it is the only + route for the Montgomery key types. + _Example_ \code byte msg[] = { initialize with msg to encrypt. Ensure padded to block size }; @@ -2295,6 +2405,14 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, otherwise this function returns NOT_COMPILED_IN. See wc_ecc_encrypt_ex for the full rationale. + \note With WOLF_CRYPTO_CB, the device the ECIES crypto callback dispatches + to is the context's devId (wc_ecc_ctx_set_dev_id). A devId given to + wc_ecc_init_ex() on privKey is adopted onto the context only while the + context devId is INVALID_DEVID (the key is never modified); a set + context devId always wins. New code should prefer the context setter: + one setting covers both directions of the exchange, and it is the only + route for the Montgomery key types. + _Example_ \code byte cipher[] = { initialize with @@ -2359,8 +2477,12 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, \param compressed whether to export the ephemeral public key as a compressed point. ECC only; must be 0 for a Montgomery curve. - \note The crypto callback (WOLF_CRYPTO_CB) ECIES hooks take ecc_key - pointers and are therefore bypassed for the Montgomery curves. + \note The crypto callback (WOLF_CRYPTO_CB) ECIES hooks fire for the + Montgomery curves too: the operation arrives as + WC_PK_TYPE_ECIES_ENCRYPT_MONT/DECRYPT_MONT with the key pointers typed by + a curveId discriminator. The dispatch device comes from + wc_ecc_ctx_set_dev_id() (for ecc_key, the key's own devId is the + fallback). _Example_ \code diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 6edba20945..72a640cad5 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -1898,11 +1898,8 @@ int test_wc_ecc_encryptDecrypt(void) return EXPECT_RESULT(); } /* END test_wc_ecc_encryptDecrypt */ -/* - * Testing wc_ecc_ctx_set_curve_id()/wc_ecc_ctx_get_curve_id(): bad arguments, - * curves that are not compiled in, and that the setting survives the context - * reset that the REQ/RESP flow performs between rounds. - */ +/* wc_ecc_ctx_set_curve_id()/wc_ecc_ctx_get_curve_id(): bad arguments, + * curves not compiled in, and the setting surviving a REQ/RESP reset. */ int test_wc_ecc_ctx_set_curve_id(void) { EXPECT_DECLS; @@ -1989,11 +1986,8 @@ int test_wc_ecc_ctx_set_curve_id(void) return EXPECT_RESULT(); } /* END test_wc_ecc_ctx_set_curve_id */ -/* - * Testing ECIES with an X25519 key: round trip, the on-the-wire encoding of - * the ephemeral public key, rejection of a tampered message, and rejection of - * point compression (meaningless on a Montgomery curve). - */ +/* ECIES with an X25519 key: round trip, on-the-wire ephemeral key + * encoding, tamper rejection, and rejection of point compression. */ int test_wc_ecc_ecies_x25519(void) { EXPECT_DECLS; @@ -2170,10 +2164,8 @@ int test_wc_ecc_ecies_x25519(void) return EXPECT_RESULT(); } /* END test_wc_ecc_ecies_x25519 */ -/* - * Testing ECIES with an X448 key: round trip and the on-the-wire encoding of - * the ephemeral public key. - */ +/* ECIES with an X448 key: round trip and the on-the-wire encoding of the + * ephemeral public key. */ int test_wc_ecc_ecies_x448(void) { EXPECT_DECLS; @@ -2462,23 +2454,37 @@ static int myEciesApiCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx) if (info->pk.type == WC_PK_TYPE_ECIES_ENCRYPT) { if (invoked != NULL) *invoked = 1; + /* Clear every devId that routed here - the key's, and the ctx's + * when one was supplied - so the forward runs in software. */ info->pk.eciesencrypt.privKey->devId = INVALID_DEVID; + if (info->pk.eciesencrypt.ctx != NULL) + (void)wc_ecc_ctx_set_dev_id(info->pk.eciesencrypt.ctx, + 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 (info->pk.eciesencrypt.ctx != NULL) + (void)wc_ecc_ctx_set_dev_id(info->pk.eciesencrypt.ctx, + devIdArg); } else if (info->pk.type == WC_PK_TYPE_ECIES_DECRYPT) { if (invoked != NULL) *invoked = 1; info->pk.eciesdecrypt.privKey->devId = INVALID_DEVID; + if (info->pk.eciesdecrypt.ctx != NULL) + (void)wc_ecc_ctx_set_dev_id(info->pk.eciesdecrypt.ctx, + 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 (info->pk.eciesdecrypt.ctx != NULL) + (void)wc_ecc_ctx_set_dev_id(info->pk.eciesdecrypt.ctx, + devIdArg); } } return ret; @@ -2556,6 +2562,42 @@ int test_wc_ecc_ecies_cryptocb(void) ExpectIntEQ(plainSz, sizeof(msg)); ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); + /* devId resolution against a context. A context with no devId adopts + * the key's (the key is never modified); once the ctx devId is set it + * always wins, even when the key names a different device. */ + { + ecEncCtx* ctx0 = NULL; + int ctxDev = INVALID_DEVID; + ExpectNotNull(ctx0 = wc_ecc_ctx_new(0, &rng)); + /* Adoption: nothing set on the ctx, the key's devId routes it. */ + cbInvoked = 0; + outSz = (word32)sizeof(out); + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, + &outSz, ctx0), 0); + ExpectIntEQ(cbInvoked, 1); + /* A different ctx devId wins over the key's: the dispatch goes to + * the unregistered ctx device, so software runs and the key's + * device is never consulted. The set ctx devId is not replaced. */ + ExpectIntEQ(wc_ecc_ctx_set_dev_id(ctx0, cbDevId + 1), 0); + cbInvoked = 0; + outSz = (word32)sizeof(out); + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, + &outSz, ctx0), 0); + ExpectIntEQ(cbInvoked, 0); + ExpectIntEQ(wc_ecc_ctx_get_dev_id(ctx0, &ctxDev), 0); + ExpectIntEQ(ctxDev, cbDevId + 1); + /* The same device on both sides is fine. */ + ExpectIntEQ(wc_ecc_ctx_set_dev_id(ctx0, cbDevId), 0); + cbInvoked = 0; + outSz = (word32)sizeof(out); + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, + &outSz, ctx0), 0); + ExpectIntEQ(cbInvoked, 1); + /* Adoption went key -> ctx only: the key still has its own devId. */ + ExpectIntEQ(cliKey.devId, cbDevId); + wc_ecc_ctx_free(ctx0); + } + cliKey.devId = INVALID_DEVID; srvKey.devId = INVALID_DEVID; wc_ecc_free(&srvKey); @@ -2567,6 +2609,457 @@ int test_wc_ecc_ecies_cryptocb(void) return EXPECT_RESULT(); } /* END test_wc_ecc_ecies_cryptocb */ +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) && \ + defined(WOLFSSL_ECIES_MONTGOMERY) && !defined(WOLFSSL_ECIES_OLD) && \ + !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) && \ + defined(HAVE_HKDF) +/* CryptoCb servicing the Montgomery ECIES ops by forwarding to software. + * The devId rides on the ecEncCtx, so the re-entrancy dance clears the ctx + * devId. The registered int* ctx records the curveId the op carried. */ +static int myEciesApiMontCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + int* sawCurveId = (int*)ctx; + if (info->algo_type == WC_ALGO_TYPE_PK) { + if (info->pk.type == WC_PK_TYPE_ECIES_ENCRYPT_MONT) { + if (sawCurveId != NULL) + *sawCurveId = info->pk.eciesencrypt_mont.curveId; + /* The ctx devId is the only Montgomery routing, so clearing it + * is all it takes for the forward to run in software. */ + ret = wc_ecc_ctx_set_dev_id(info->pk.eciesencrypt_mont.ctx, + INVALID_DEVID); + if (ret != 0) + return ret; + ret = wc_ecc_encrypt_ex2(info->pk.eciesencrypt_mont.privKey, + info->pk.eciesencrypt_mont.pubKey, + info->pk.eciesencrypt_mont.msg, + info->pk.eciesencrypt_mont.msgSz, + info->pk.eciesencrypt_mont.out, + info->pk.eciesencrypt_mont.outSz, + info->pk.eciesencrypt_mont.ctx, 0); + (void)wc_ecc_ctx_set_dev_id(info->pk.eciesencrypt_mont.ctx, + devIdArg); + } + else if (info->pk.type == WC_PK_TYPE_ECIES_DECRYPT_MONT) { + if (sawCurveId != NULL) + *sawCurveId = info->pk.eciesdecrypt_mont.curveId; + ret = wc_ecc_ctx_set_dev_id(info->pk.eciesdecrypt_mont.ctx, + INVALID_DEVID); + if (ret != 0) + return ret; + ret = wc_ecc_decrypt_ex2(info->pk.eciesdecrypt_mont.privKey, + info->pk.eciesdecrypt_mont.pubKey, + info->pk.eciesdecrypt_mont.msg, + info->pk.eciesdecrypt_mont.msgSz, + info->pk.eciesdecrypt_mont.out, + info->pk.eciesdecrypt_mont.outSz, + info->pk.eciesdecrypt_mont.ctx); + (void)wc_ecc_ctx_set_dev_id(info->pk.eciesdecrypt_mont.ctx, + devIdArg); + } + } + return ret; +} +#endif + +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) && \ + !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) && \ + defined(HAVE_HKDF) +/* Records that any operation reached the device it is registered under, and + * declines it so software still runs. The registered ctx is an int* flag. */ +static int myEciesPrimCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx) +{ + int* seen = (int*)ctx; + (void)devIdArg; + (void)info; + if (seen != NULL) + *seen = 1; + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} + +/* Records the whole-operation ECIES offer and the ECDH offer separately, + * declining both so the software path composes the primitives. */ +typedef struct EciesKeyCbSeen { + int ecies; + int ecdh; +} EciesKeyCbSeen; + +static int myEciesKeyPrimCryptoCb(int devIdArg, wc_CryptoInfo* info, + void* ctx) +{ + EciesKeyCbSeen* seen = (EciesKeyCbSeen*)ctx; + (void)devIdArg; + if (seen != NULL && info->algo_type == WC_ALGO_TYPE_PK) { + if (info->pk.type == WC_PK_TYPE_ECIES_ENCRYPT || + info->pk.type == WC_PK_TYPE_ECIES_DECRYPT) { + seen->ecies = 1; + } + else if (info->pk.type == WC_PK_TYPE_ECDH) { + seen->ecdh = 1; + } + } + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} +#endif + +/* wc_ecc_ctx_set_algo_dev_ids(): routing the software ECIES primitives + * (DEM/KDF/MAC) to devices, composed with the ECDH routed by the private + * key's own devId; INVALID_DEVID keeps a primitive in software. */ +int test_wc_ecc_ctx_algo_dev_ids(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) && \ + !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) && \ + defined(HAVE_HKDF) + const int encDev = 0x45504130; /* 'EPA0' */ + const int kdfDev = 0x45504B30; /* 'EPK0' */ + const int macDev = 0x45504D30; /* 'EPM0' */ + const int keyDev = 0x45504430; /* 'EPD0' */ + ecc_key cliKey; + ecc_key srvKey; + ecc_key* decPub = NULL; + WC_RNG rng; + ecEncCtx* ctx = NULL; + byte msg[32]; + byte out[256]; + byte plain[64]; + word32 outSz = (word32)sizeof(out); + word32 plainSz = (word32)sizeof(plain); + int i; + int encSeen = 0, kdfSeen = 0, macSeen = 0; + int regEnc = 0, regKdf = 0, regMac = 0; + int regKey = 0; + int gotDev = INVALID_DEVID; + EciesKeyCbSeen keySeen; + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&cliKey, 0, sizeof(cliKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + XMEMSET(&keySeen, 0, sizeof(keySeen)); + for (i = 0; i < (int)sizeof(msg); i++) + msg[i] = (byte)i; + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(encDev, myEciesPrimCryptoCb, + &encSeen), 0); + if (EXPECT_SUCCESS()) + regEnc = 1; + ExpectIntEQ(wc_CryptoCb_RegisterDevice(kdfDev, myEciesPrimCryptoCb, + &kdfSeen), 0); + if (EXPECT_SUCCESS()) + regKdf = 1; + ExpectIntEQ(wc_CryptoCb_RegisterDevice(macDev, myEciesPrimCryptoCb, + &macSeen), 0); + if (EXPECT_SUCCESS()) + regMac = 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 +#ifdef WOLFSSL_ECIES_OLD + decPub = &cliKey; +#endif + + ExpectNotNull(ctx = wc_ecc_ctx_new(0, &rng)); + ExpectIntEQ(wc_ecc_ctx_set_algo(ctx, ecAES_128_CBC, ecHKDF_SHA256, + ecHMAC_SHA256), 0); + + ExpectIntEQ(wc_ecc_ctx_set_algo_dev_ids(NULL, encDev, kdfDev, macDev), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_ctx_set_algo_dev_ids(ctx, encDev, kdfDev, macDev), 0); + /* The routing survives a reset, like the algorithms and the key type. */ + ExpectIntEQ(wc_ecc_ctx_reset(ctx, &rng), 0); + ExpectIntEQ(wc_ecc_ctx_set_algo(ctx, ecAES_128_CBC, ecHKDF_SHA256, + ecHMAC_SHA256), 0); + + /* Each primitive must reach its own device on encrypt and decrypt (the + * recording callback declines, so software still produces the data). */ + encSeen = kdfSeen = macSeen = 0; + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, + &outSz, ctx), 0); + ExpectIntEQ(encSeen, 1); + ExpectIntEQ(kdfSeen, 1); + ExpectIntEQ(macSeen, 1); + + encSeen = kdfSeen = macSeen = 0; + ExpectIntEQ(wc_ecc_decrypt(&srvKey, decPub, out, outSz, plain, &plainSz, + ctx), 0); + ExpectIntEQ(encSeen, 1); + ExpectIntEQ(kdfSeen, 1); + ExpectIntEQ(macSeen, 1); + ExpectIntEQ(plainSz, sizeof(msg)); + ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); + + /* INVALID_DEVID keeps that primitive in software: the KDF device must + * not be consulted while the cipher and MAC still are. */ + ExpectIntEQ(wc_ecc_ctx_set_algo_dev_ids(ctx, encDev, INVALID_DEVID, + macDev), 0); + encSeen = kdfSeen = macSeen = 0; + outSz = (word32)sizeof(out); + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, + &outSz, ctx), 0); + ExpectIntEQ(encSeen, 1); + ExpectIntEQ(kdfSeen, 0); + ExpectIntEQ(macSeen, 1); + + /* Fully composed use: no whole-operation ECIES service (the key's + * device declines the offer), the ECDH routed by the private key's own + * devId and the DEM/KDF/MAC by their per-algorithm devIds. */ + ExpectIntEQ(wc_CryptoCb_RegisterDevice(keyDev, myEciesKeyPrimCryptoCb, + &keySeen), 0); + if (EXPECT_SUCCESS()) + regKey = 1; + ExpectIntEQ(wc_ecc_ctx_set_algo_dev_ids(ctx, encDev, kdfDev, macDev), 0); + cliKey.devId = keyDev; + srvKey.devId = keyDev; + + encSeen = kdfSeen = macSeen = 0; + keySeen.ecies = keySeen.ecdh = 0; + outSz = (word32)sizeof(out); + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, + &outSz, ctx), 0); + ExpectIntEQ(keySeen.ecies, 1); + ExpectIntEQ(keySeen.ecdh, 1); + ExpectIntEQ(encSeen, 1); + ExpectIntEQ(kdfSeen, 1); + ExpectIntEQ(macSeen, 1); + /* The dispatch adopted the private key's devId onto the ctx. */ + ExpectIntEQ(wc_ecc_ctx_get_dev_id(ctx, &gotDev), 0); + ExpectIntEQ(gotDev, keyDev); + + encSeen = kdfSeen = macSeen = 0; + keySeen.ecies = keySeen.ecdh = 0; + plainSz = (word32)sizeof(plain); + ExpectIntEQ(wc_ecc_decrypt(&srvKey, decPub, out, outSz, plain, &plainSz, + ctx), 0); + ExpectIntEQ(keySeen.ecies, 1); + ExpectIntEQ(keySeen.ecdh, 1); + ExpectIntEQ(encSeen, 1); + ExpectIntEQ(kdfSeen, 1); + ExpectIntEQ(macSeen, 1); + ExpectIntEQ(plainSz, sizeof(msg)); + ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); + + cliKey.devId = INVALID_DEVID; + srvKey.devId = INVALID_DEVID; + wc_ecc_ctx_free(ctx); + wc_ecc_free(&srvKey); + wc_ecc_free(&cliKey); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + if (regEnc) + wc_CryptoCb_UnRegisterDevice(encDev); + if (regKdf) + wc_CryptoCb_UnRegisterDevice(kdfDev); + if (regMac) + wc_CryptoCb_UnRegisterDevice(macDev); + if (regKey) + wc_CryptoCb_UnRegisterDevice(keyDev); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ctx_algo_dev_ids */ + +/* ECIES X25519 CryptoCb dispatch: the devId rides on the ecEncCtx + * (wc_ecc_ctx_set_dev_id), survives a reset, and the callback receives the + * key type via curveId. */ +int test_wc_ecc_ecies_cryptocb_x25519(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) && \ + defined(WOLFSSL_ECIES_X25519) && !defined(WOLFSSL_ECIES_OLD) && \ + !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) && \ + defined(HAVE_HKDF) + const int cbDevId = 0x45434231; /* 'ECB1' */ + WC_RNG rng; + WC_DECLARE_VAR(ephKey, curve25519_key, 1, HEAP_HINT); + WC_DECLARE_VAR(srvKey, curve25519_key, 1, HEAP_HINT); + ecEncCtx* ctx = NULL; + const char* msg = "EccBlock Size 16"; + word32 msgSz = (word32)XSTRLEN("EccBlock Size 16"); + byte out[CURVE25519_PUB_KEY_SIZE + AES_BLOCK_SIZE + AES_BLOCK_SIZE + + WC_SHA256_DIGEST_SIZE]; + word32 outSz = (word32)sizeof(out); + byte plain[sizeof("EccBlock Size 16") + AES_BLOCK_SIZE]; + word32 plainSz = (word32)sizeof(plain); + int registered = 0; + int sawCurveId = ECC_CURVE_INVALID; + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(out, 0, sizeof(out)); + XMEMSET(plain, 0, sizeof(plain)); + + WC_ALLOC_VAR(ephKey, curve25519_key, 1, HEAP_HINT); + WC_ALLOC_VAR(srvKey, curve25519_key, 1, HEAP_HINT); +#ifdef WC_DECLARE_VAR_IS_HEAP_ALLOC + ExpectNotNull(ephKey); + ExpectNotNull(srvKey); +#endif + if (WC_VAR_OK(ephKey)) { + XMEMSET(ephKey, 0, sizeof(*ephKey)); + } + if (WC_VAR_OK(srvKey)) { + XMEMSET(srvKey, 0, sizeof(*srvKey)); + } + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(cbDevId, myEciesApiMontCryptoCb, + &sawCurveId), 0); + if (EXPECT_SUCCESS()) + registered = 1; + + ExpectIntEQ(wc_InitRng(&rng), 0); + /* keys stay at INVALID_DEVID; the ctx routes the dispatch */ + ExpectIntEQ(wc_curve25519_init_ex(ephKey, HEAP_HINT, INVALID_DEVID), 0); + ExpectIntEQ(wc_curve25519_init_ex(srvKey, HEAP_HINT, INVALID_DEVID), 0); + ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, ephKey), 0); + ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, srvKey), 0); + + ExpectNotNull(ctx = wc_ecc_ctx_new(0, &rng)); + ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_X25519), 0); + ExpectIntEQ(wc_ecc_ctx_set_algo(ctx, ecAES_128_CBC, ecHKDF_SHA256, + ecHMAC_SHA256), 0); + + /* Bad args, then set the devId and prove it survives a reset by the + * dispatch below actually firing. */ + ExpectIntEQ(wc_ecc_ctx_set_dev_id(NULL, cbDevId), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_ctx_set_dev_id(ctx, cbDevId), 0); + ExpectIntEQ(wc_ecc_ctx_reset(ctx, &rng), 0); + ExpectIntEQ(wc_ecc_ctx_set_algo(ctx, ecAES_128_CBC, ecHKDF_SHA256, + ecHMAC_SHA256), 0); + + sawCurveId = ECC_CURVE_INVALID; + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, (const byte*)msg, msgSz, + out, &outSz, ctx, 0), 0); + /* callback must have serviced the encrypt with the right key type */ + ExpectIntEQ(sawCurveId, ECC_X25519); + + sawCurveId = ECC_CURVE_INVALID; + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, NULL, out, outSz, plain, &plainSz, + ctx), 0); + ExpectIntEQ(sawCurveId, ECC_X25519); + ExpectIntEQ(plainSz, msgSz); + ExpectIntEQ(XMEMCMP(msg, plain, msgSz), 0); + + /* A Montgomery key's own devId takes no part in the ECIES routing: the + * ctx devId is the only source, so a stray devId on the key neither + * conflicts nor reroutes. */ + if (WC_VAR_OK(srvKey)) { + srvKey->devId = cbDevId + 1; + } + sawCurveId = ECC_CURVE_INVALID; + plainSz = (word32)sizeof(plain); + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, NULL, out, outSz, plain, &plainSz, + ctx), 0); + ExpectIntEQ(sawCurveId, ECC_X25519); + if (WC_VAR_OK(srvKey)) { + srvKey->devId = INVALID_DEVID; + } + + wc_ecc_ctx_free(ctx); + wc_curve25519_free(srvKey); + wc_curve25519_free(ephKey); + WC_FREE_VAR(srvKey, HEAP_HINT); + WC_FREE_VAR(ephKey, HEAP_HINT); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + if (registered) + wc_CryptoCb_UnRegisterDevice(cbDevId); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ecies_cryptocb_x25519 */ + +/* The X448 sibling: curve448_key has no devId member at all, so the + * ctx-carried devId is the only dispatch route. */ +int test_wc_ecc_ecies_cryptocb_x448(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) && \ + defined(WOLFSSL_ECIES_X448) && !defined(WOLFSSL_ECIES_OLD) && \ + !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) && \ + defined(HAVE_HKDF) + const int cbDevId = 0x45434232; /* 'ECB2' */ + WC_RNG rng; + WC_DECLARE_VAR(ephKey, curve448_key, 1, HEAP_HINT); + WC_DECLARE_VAR(srvKey, curve448_key, 1, HEAP_HINT); + ecEncCtx* ctx = NULL; + const char* msg = "EccBlock Size 16"; + word32 msgSz = (word32)XSTRLEN("EccBlock Size 16"); + byte out[CURVE448_PUB_KEY_SIZE + AES_BLOCK_SIZE + AES_BLOCK_SIZE + + WC_SHA256_DIGEST_SIZE]; + word32 outSz = (word32)sizeof(out); + byte plain[sizeof("EccBlock Size 16") + AES_BLOCK_SIZE]; + word32 plainSz = (word32)sizeof(plain); + int registered = 0; + int sawCurveId = ECC_CURVE_INVALID; + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(out, 0, sizeof(out)); + XMEMSET(plain, 0, sizeof(plain)); + + WC_ALLOC_VAR(ephKey, curve448_key, 1, HEAP_HINT); + WC_ALLOC_VAR(srvKey, curve448_key, 1, HEAP_HINT); +#ifdef WC_DECLARE_VAR_IS_HEAP_ALLOC + ExpectNotNull(ephKey); + ExpectNotNull(srvKey); +#endif + if (WC_VAR_OK(ephKey)) { + XMEMSET(ephKey, 0, sizeof(*ephKey)); + } + if (WC_VAR_OK(srvKey)) { + XMEMSET(srvKey, 0, sizeof(*srvKey)); + } + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(cbDevId, myEciesApiMontCryptoCb, + &sawCurveId), 0); + if (EXPECT_SUCCESS()) + registered = 1; + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_curve448_init(ephKey), 0); + ExpectIntEQ(wc_curve448_init(srvKey), 0); + ExpectIntEQ(wc_curve448_make_key(&rng, CURVE448_KEY_SIZE, ephKey), 0); + ExpectIntEQ(wc_curve448_make_key(&rng, CURVE448_KEY_SIZE, srvKey), 0); + + ExpectNotNull(ctx = wc_ecc_ctx_new(0, &rng)); + ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_X448), 0); + ExpectIntEQ(wc_ecc_ctx_set_algo(ctx, ecAES_128_CBC, ecHKDF_SHA256, + ecHMAC_SHA256), 0); + ExpectIntEQ(wc_ecc_ctx_set_dev_id(ctx, cbDevId), 0); + + sawCurveId = ECC_CURVE_INVALID; + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, (const byte*)msg, msgSz, + out, &outSz, ctx, 0), 0); + ExpectIntEQ(sawCurveId, ECC_X448); + + sawCurveId = ECC_CURVE_INVALID; + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, NULL, out, outSz, plain, &plainSz, + ctx), 0); + ExpectIntEQ(sawCurveId, ECC_X448); + ExpectIntEQ(plainSz, msgSz); + ExpectIntEQ(XMEMCMP(msg, plain, msgSz), 0); + + wc_ecc_ctx_free(ctx); + wc_curve448_free(srvKey); + wc_curve448_free(ephKey); + WC_FREE_VAR(srvKey, HEAP_HINT); + WC_FREE_VAR(ephKey, HEAP_HINT); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + if (registered) + wc_CryptoCb_UnRegisterDevice(cbDevId); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ecies_cryptocb_x448 */ + /* * 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 29e7a367a6..8389463505 100644 --- a/tests/api/test_ecc.h +++ b/tests/api/test_ecc.h @@ -62,6 +62,9 @@ int test_wc_ecc_ecies_x448(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_ctx_algo_dev_ids(void); +int test_wc_ecc_ecies_cryptocb_x25519(void); +int test_wc_ecc_ecies_cryptocb_x448(void); int test_wc_ecc_del_point(void); int test_wc_ecc_pointFns(void); int test_wc_ecc_shared_secret_ssh(void); @@ -115,6 +118,9 @@ 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_ctx_algo_dev_ids), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_cryptocb_x25519), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_cryptocb_x448), \ 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_ecc_whitebox.c b/tests/unit-mcdc/test_ecc_whitebox.c index fca6063e4f..d3b684e515 100644 --- a/tests/unit-mcdc/test_ecc_whitebox.c +++ b/tests/unit-mcdc/test_ecc_whitebox.c @@ -1650,10 +1650,8 @@ static void wb_arg_guards(void) } } - /* ecies_pub_key_size: the ECC branch rejects a NULL key and a key with no - * domain parameters, both of which wc_ecc_size() reports as size 0. (This - * guard used to live in ecc_public_key_size(), which the key-type dispatch - * for ECIES replaced.) */ + /* ecies_pub_key_size: the ECC branch rejects a NULL key and a key with + * no domain parameters, both reported by wc_ecc_size() as size 0. */ #if defined(HAVE_ECC_ENCRYPT) && !defined(WOLFSSL_ECIES_OLD) { const ecc_set_type* savedDp = key.dp; diff --git a/wolfcrypt/benchmark/benchmark.c b/wolfcrypt/benchmark/benchmark.c index 9d96beb57c..998f80106c 100644 --- a/wolfcrypt/benchmark/benchmark.c +++ b/wolfcrypt/benchmark/benchmark.c @@ -14597,6 +14597,13 @@ void bench_eccEncrypt(int curveId) goto exit; } + #ifdef WOLF_CRYPTO_CB + /* Route the whole-op ECIES callback by the benchmark's devId, like + * every other benchmarked operation. */ + (void)wc_ecc_ctx_set_dev_id(cliCtx, devId); + (void)wc_ecc_ctx_set_dev_id(srvCtx, devId); + #endif + for (c = 0; eciesCiphers[c].label != NULL; c++) { byte algo = eciesCiphers[c].algo; diff --git a/wolfcrypt/src/cryptocb.c b/wolfcrypt/src/cryptocb.c index ba4f53fcb0..1f7b299cec 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -1129,31 +1129,81 @@ 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, +/* Resolve the ECIES dispatch devId - the one place the adopt logic lives. + * The ctx devId is only set while INVALID_DEVID, adopting an ecc_key's own + * devId (the key is never modified); otherwise the ctx devId wins. */ +static int wc_CryptoCb_EciesDevId(void* privKey, ecEncCtx* ctx, int curveId, + int* devId) +{ + int keyDevId = INVALID_DEVID; + int ctxDevId = INVALID_DEVID; + + /* Only an ecc_key's own devId participates in the resolution; the + * Montgomery key types route exclusively by the context devId. */ + if (curveId == ECC_CURVE_DEF) { + keyDevId = ((ecc_key*)privKey)->devId; + } + + if (ctx == NULL) { + *devId = keyDevId; + return 0; + } + + if (wc_ecc_ctx_get_dev_id(ctx, &ctxDevId) != 0) + return BAD_FUNC_ARG; + + if (ctxDevId == INVALID_DEVID && keyDevId != INVALID_DEVID) { + (void)wc_ecc_ctx_set_dev_id(ctx, keyDevId); + ctxDevId = keyDevId; + } + + *devId = ctxDevId; + return 0; +} + +int wc_CryptoCb_EciesEncrypt_ex(void* privKey, void* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, - int compressed) + int compressed, int curveId) { int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); CryptoCb* dev; + int devId = INVALID_DEVID; if (privKey == NULL) return ret; + if (wc_CryptoCb_EciesDevId(privKey, ctx, curveId, &devId) != 0) + return BAD_FUNC_ARG; + /* locate registered callback */ - dev = wc_CryptoCb_FindDevice(privKey->devId, WC_ALGO_TYPE_PK); + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_PK); if (dev && dev->cb) { wc_CryptoInfo cryptoInfo; XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); cryptoInfo.algo_type = WC_ALGO_TYPE_PK; - cryptoInfo.pk.type = WC_PK_TYPE_ECIES_ENCRYPT; - cryptoInfo.pk.eciesencrypt.privKey = privKey; - cryptoInfo.pk.eciesencrypt.pubKey = pubKey; - cryptoInfo.pk.eciesencrypt.msg = msg; - cryptoInfo.pk.eciesencrypt.msgSz = msgSz; - cryptoInfo.pk.eciesencrypt.out = out; - cryptoInfo.pk.eciesencrypt.outSz = outSz; - cryptoInfo.pk.eciesencrypt.ctx = ctx; - cryptoInfo.pk.eciesencrypt.compressed = compressed; + if (curveId == ECC_CURVE_DEF) { + cryptoInfo.pk.type = WC_PK_TYPE_ECIES_ENCRYPT; + cryptoInfo.pk.eciesencrypt.privKey = (ecc_key*)privKey; + cryptoInfo.pk.eciesencrypt.pubKey = (ecc_key*)pubKey; + cryptoInfo.pk.eciesencrypt.msg = msg; + cryptoInfo.pk.eciesencrypt.msgSz = msgSz; + cryptoInfo.pk.eciesencrypt.out = out; + cryptoInfo.pk.eciesencrypt.outSz = outSz; + cryptoInfo.pk.eciesencrypt.ctx = ctx; + cryptoInfo.pk.eciesencrypt.compressed = compressed; + } + else { + /* Montgomery key type; the void pointers are typed by curveId. */ + cryptoInfo.pk.type = WC_PK_TYPE_ECIES_ENCRYPT_MONT; + cryptoInfo.pk.eciesencrypt_mont.privKey = privKey; + cryptoInfo.pk.eciesencrypt_mont.pubKey = pubKey; + cryptoInfo.pk.eciesencrypt_mont.msg = msg; + cryptoInfo.pk.eciesencrypt_mont.msgSz = msgSz; + cryptoInfo.pk.eciesencrypt_mont.out = out; + cryptoInfo.pk.eciesencrypt_mont.outSz = outSz; + cryptoInfo.pk.eciesencrypt_mont.ctx = ctx; + cryptoInfo.pk.eciesencrypt_mont.curveId = curveId; + } ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); } @@ -1161,35 +1211,69 @@ 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, - const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx) +int wc_CryptoCb_EciesEncrypt(ecc_key* privKey, ecc_key* pubKey, + const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, + int compressed) +{ + return wc_CryptoCb_EciesEncrypt_ex(privKey, pubKey, msg, msgSz, out, + outSz, ctx, compressed, ECC_CURVE_DEF); +} + +int wc_CryptoCb_EciesDecrypt_ex(void* privKey, void* pubKey, + const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, + int curveId) { int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); CryptoCb* dev; + int devId = INVALID_DEVID; if (privKey == NULL) return ret; + if (wc_CryptoCb_EciesDevId(privKey, ctx, curveId, &devId) != 0) + return BAD_FUNC_ARG; + /* locate registered callback */ - dev = wc_CryptoCb_FindDevice(privKey->devId, WC_ALGO_TYPE_PK); + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_PK); if (dev && dev->cb) { wc_CryptoInfo cryptoInfo; XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); cryptoInfo.algo_type = WC_ALGO_TYPE_PK; - cryptoInfo.pk.type = WC_PK_TYPE_ECIES_DECRYPT; - cryptoInfo.pk.eciesdecrypt.privKey = privKey; - cryptoInfo.pk.eciesdecrypt.pubKey = pubKey; - cryptoInfo.pk.eciesdecrypt.msg = msg; - cryptoInfo.pk.eciesdecrypt.msgSz = msgSz; - cryptoInfo.pk.eciesdecrypt.out = out; - cryptoInfo.pk.eciesdecrypt.outSz = outSz; - cryptoInfo.pk.eciesdecrypt.ctx = ctx; + if (curveId == ECC_CURVE_DEF) { + cryptoInfo.pk.type = WC_PK_TYPE_ECIES_DECRYPT; + cryptoInfo.pk.eciesdecrypt.privKey = (ecc_key*)privKey; + cryptoInfo.pk.eciesdecrypt.pubKey = (ecc_key*)pubKey; + cryptoInfo.pk.eciesdecrypt.msg = msg; + cryptoInfo.pk.eciesdecrypt.msgSz = msgSz; + cryptoInfo.pk.eciesdecrypt.out = out; + cryptoInfo.pk.eciesdecrypt.outSz = outSz; + cryptoInfo.pk.eciesdecrypt.ctx = ctx; + } + else { + /* Montgomery key type; the void pointers are typed by curveId. */ + cryptoInfo.pk.type = WC_PK_TYPE_ECIES_DECRYPT_MONT; + cryptoInfo.pk.eciesdecrypt_mont.privKey = privKey; + cryptoInfo.pk.eciesdecrypt_mont.pubKey = pubKey; + cryptoInfo.pk.eciesdecrypt_mont.msg = msg; + cryptoInfo.pk.eciesdecrypt_mont.msgSz = msgSz; + cryptoInfo.pk.eciesdecrypt_mont.out = out; + cryptoInfo.pk.eciesdecrypt_mont.outSz = outSz; + cryptoInfo.pk.eciesdecrypt_mont.ctx = ctx; + cryptoInfo.pk.eciesdecrypt_mont.curveId = curveId; + } ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); } return wc_CryptoCb_TranslateErrorCode(ret); } + +int wc_CryptoCb_EciesDecrypt(ecc_key* privKey, ecc_key* pubKey, + const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx) +{ + return wc_CryptoCb_EciesDecrypt_ex(privKey, pubKey, msg, msgSz, out, + outSz, ctx, ECC_CURVE_DEF); +} #endif /* HAVE_ECC_ENCRYPT */ #endif /* HAVE_ECC */ diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index da0a939337..623c0f0edf 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -15010,6 +15010,15 @@ struct ecEncCtx { * ECC_CURVE_DEF -> ecc_key (default), * ECC_X25519 -> curve25519_key, * ECC_X448 -> curve448_key */ +#ifdef WOLF_CRYPTO_CB + int devId; /* device for the ECIES crypto callback; an + * ecc_key's own devId is adopted as fallback */ + int encDevId; /* per-algorithm devIds for the software path + * (wc_ecc_ctx_set_algo_dev_ids); INVALID_DEVID + * keeps that primitive in software */ + int kdfDevId; + int macDevId; +#endif WC_RNG* rng; }; @@ -15026,19 +15035,9 @@ int wc_ecc_ctx_set_algo(ecEncCtx* ctx, byte encAlgo, byte kdfAlgo, byte macAlgo) return 0; } -/* Select the type of key the context operates on. - * - * ECC_CURVE_DEF (the default) means the key pointers handed to the ECIES - * functions are ecc_key*. ECC_X25519 and ECC_X448 mean they are - * curve25519_key* and curve448_key* respectively, in which case they have to - * be passed through wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2() - the typed - * wc_ecc_encrypt()/wc_ecc_decrypt() entry points stay ECC-only. - * - * Individual ECC curves are not selectable here: for an ecc_key the curve is - * carried by the key itself. This is a key-type selector only. - * - * Returns 0 on success, NOT_COMPILED_IN when the curve is built but its ECIES - * support is not, and BAD_FUNC_ARG otherwise. */ +/* Select the key type the context operates on: ECC_CURVE_DEF (default) + * means ecc_key, ECC_X25519/ECC_X448 mean curve25519_key/curve448_key used + * via wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2(). Not a curve selector. */ int wc_ecc_ctx_set_curve_id(ecEncCtx* ctx, int curveId) { int ret = 0; @@ -15085,6 +15084,48 @@ int wc_ecc_ctx_get_curve_id(ecEncCtx* ctx, int* curveId) return 0; } +#ifdef WOLF_CRYPTO_CB +/* Device for the whole-operation ECIES crypto callback only; an ecc_key + * devId is adopted while the ctx devId is unset, inside + * wc_CryptoCb_EciesEncrypt_ex/Decrypt_ex. Survives 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; +} + +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; +} + +/* Per-algorithm devIds mirroring the wc_ecc_ctx_set_algo() trio: DEM + * cipher, KDF, MAC. One call declares all three; INVALID_DEVID keeps a + * primitive in software. The values survive wc_ecc_ctx_reset(). */ +int wc_ecc_ctx_set_algo_dev_ids(ecEncCtx* ctx, int encDevId, int kdfDevId, + int macDevId) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + + ctx->encDevId = encDevId; + ctx->kdfDevId = kdfDevId; + ctx->macDevId = macDevId; + + return 0; +} +#endif /* WOLF_CRYPTO_CB */ + + #ifdef WOLF_CRYPTO_CB /* Read back the parameters a caller configured on the context. Intended for * crypto-callback backends (e.g. a hardware ECIES engine) that must reproduce @@ -15233,10 +15274,9 @@ int wc_ecc_ctx_set_peer_salt(ecEncCtx* ctx, const byte* salt) */ int wc_ecc_ctx_set_kdf_salt(ecEncCtx* ctx, const byte* salt, word32 sz) { - /* The custom KDF salt borrows the REQ/RESP clientSalt/serverSalt - * storage, so a no-protocol context (wc_ecc_ctx_new(0, ...)) has nowhere - * to keep it - kdfSalt would stay NULL and the copy below would write - * through it. */ + /* The custom KDF salt borrows the REQ/RESP salt storage, which a + * no-protocol context does not have - kdfSalt would stay NULL and the + * copy below would write through it. */ if (ctx == NULL || ctx->protocol == 0 || (salt == NULL && sz != 0)) return BAD_FUNC_ARG; @@ -15340,6 +15380,13 @@ static void ecc_ctx_init(ecEncCtx* ctx, int flags, WC_RNG* rng) ctx->macAlgo = ecHMAC_SHA256; ctx->protocol = (byte)flags; ctx->rng = rng; + /* 0 is a valid devId, so the XMEMSET default is not "no device". */ +#ifdef WOLF_CRYPTO_CB + ctx->devId = INVALID_DEVID; + ctx->encDevId = INVALID_DEVID; + ctx->kdfDevId = INVALID_DEVID; + ctx->macDevId = INVALID_DEVID; +#endif if (flags == REQ_RESP_CLIENT) ctx->cliSt = ecCLI_INIT; @@ -15366,14 +15413,27 @@ int wc_ecc_ctx_reset(ecEncCtx* ctx, WC_RNG* rng) * make the next call reinterpret a Montgomery key as an ecc_key. */ heap = ctx->heap; curveId = ctx->curveId; - ecc_ctx_init(ctx, ctx->protocol, rng); + { +#ifdef WOLF_CRYPTO_CB + int devId = ctx->devId; + int encDevId = ctx->encDevId; + int kdfDevId = ctx->kdfDevId; + int macDevId = ctx->macDevId; +#endif + ecc_ctx_init(ctx, ctx->protocol, rng); +#ifdef WOLF_CRYPTO_CB + ctx->devId = devId; + ctx->encDevId = encDevId; + ctx->kdfDevId = kdfDevId; + ctx->macDevId = macDevId; +#endif + } ctx->heap = heap; ctx->curveId = curveId; - /* The exchange salts only exist for the REQ/RESP protocol. A context with - * no protocol is still useful - it is how non-default algorithms and the - * key type are carried - and matches the default context the encrypt and - * decrypt paths build internally when none is supplied. */ + /* Exchange salts only exist for the REQ/RESP protocol. A no-protocol + * context still carries non-default algorithms and the key type, like + * the default context the encrypt/decrypt paths build internally. */ if (ctx->protocol == 0) return 0; @@ -15390,9 +15450,15 @@ ecEncCtx* wc_ecc_ctx_new_ex(int flags, WC_RNG* rng, void* heap) if (ctx) { ctx->protocol = (byte)flags; ctx->heap = heap; - /* wc_ecc_ctx_reset() preserves curveId, so it must not be XMALLOC - * garbage by the time it is called. */ + /* wc_ecc_ctx_reset() preserves curveId and the devIds, so they must + * not be XMALLOC garbage by the time it is called. */ ctx->curveId = ECC_CURVE_DEF; +#ifdef WOLF_CRYPTO_CB + ctx->devId = INVALID_DEVID; + ctx->encDevId = INVALID_DEVID; + ctx->kdfDevId = INVALID_DEVID; + ctx->macDevId = INVALID_DEVID; +#endif } ret = wc_ecc_ctx_reset(ctx, rng); @@ -15580,14 +15646,9 @@ static int ecc_ctx_decrypt_advance(ecEncCtx* ctx) } -/* Key-type dispatch for ECIES. - * - * Everything from the shared secret onwards - the KDF, the DEM and the MAC - - * is key-type agnostic. Only the handful of operations below actually care - * whether the caller handed us an ecc_key, a curve25519_key or a curve448_key, - * so they are funnelled through these helpers. ctx->curveId, set with - * wc_ecc_ctx_set_curve_id(), says which it is; ECC_CURVE_DEF (the default) - * means ecc_key and behaves exactly as it always has. */ +/* Key-type dispatch for ECIES. Only the helpers below care whether the + * caller passed an ecc_key, curve25519_key or curve448_key; ctx->curveId + * says which, with ECC_CURVE_DEF (the default) meaning ecc_key. */ /* Are the key pointers Montgomery-curve keys rather than ecc_key? */ static int ecies_is_mont(ecEncCtx* ctx) @@ -15611,47 +15672,49 @@ static int ecies_is_mont(ecEncCtx* ctx) static void* ecies_heap(ecEncCtx* ctx, void* privKey) { if (ecies_is_mont(ctx)) { - /* wc_curve25519_init_ex() discards the heap hint it is given and - * curve448_key has no heap field at all, so neither key can supply - * one. The context's hint is what the surrounding allocations in - * this file already use. */ + /* wc_curve25519_init_ex() discards its heap hint and curve448_key + * has no heap field, so neither key can supply one; the context's + * hint matches the surrounding allocations in this file. */ return ctx->heap; } return ((ecc_key*)privKey)->heap; } -/* devId to hand the DEM AES/HMAC primitives. */ -static int ecies_devid(ecEncCtx* ctx, void* privKey) +/* Per-algorithm devIds exactly as declared with + * wc_ecc_ctx_set_algo_dev_ids(); INVALID_DEVID (the default, and the only + * value without WOLF_CRYPTO_CB) keeps that primitive in software. */ +static int ecies_enc_devid(ecEncCtx* ctx) { - int devId = INVALID_DEVID; - +#ifdef WOLF_CRYPTO_CB + if (ctx != NULL) + return ctx->encDevId; +#else (void)ctx; - (void)privKey; - -#ifdef WOLFSSL_ECIES_X25519 - if (ctx != NULL && ctx->curveId == ECC_X25519) { - #ifdef WOLF_CRYPTO_CB - /* curve25519_key only carries a devId with WOLF_CRYPTO_CB. */ - devId = ((curve25519_key*)privKey)->devId; - #endif - } - else #endif -#ifdef WOLFSSL_ECIES_X448 - if (ctx != NULL && ctx->curveId == ECC_X448) { - /* curve448_key has no devId field. */ - } - else + return INVALID_DEVID; +} + +static int ecies_kdf_devid(ecEncCtx* ctx) +{ +#ifdef WOLF_CRYPTO_CB + if (ctx != NULL) + return ctx->kdfDevId; +#else + (void)ctx; #endif - { - #if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) - /* ecc_key only carries a devId field with these. */ - devId = ((ecc_key*)privKey)->devId; - #endif - } + return INVALID_DEVID; +} - return devId; +static int ecies_mac_devid(ecEncCtx* ctx) +{ +#ifdef WOLF_CRYPTO_CB + if (ctx != NULL) + return ctx->macDevId; +#else + (void)ctx; +#endif + return INVALID_DEVID; } /* Give the private key an RNG for blinding / timing resistance, where the key @@ -15701,7 +15764,9 @@ static WC_RNG* ecies_rng(ecEncCtx* ctx, void* privKey) return ((ecc_key*)privKey)->rng; #endif - return (ctx != NULL) ? ctx->rng : NULL; + if (ctx != NULL) + return ctx->rng; + return NULL; } #endif /* !WOLFSSL_ECIES_OLD && WOLFSSL_ECIES_GEN_IV */ @@ -15718,11 +15783,9 @@ typedef union { #endif } ecies_peer_key; -/* Size of the ephemeral public key as it appears in the message. - * - * For an ecc_key this is the X9.63 point encoding, so it depends on whether - * the point is compressed. Montgomery keys are a bare u-coordinate of fixed - * size with no leading format byte, so 'compressed' does not apply. */ +/* Size of the ephemeral public key in the message: X9.63 point encoding + * for an ecc_key (compression matters), a fixed-size bare u-coordinate for + * Montgomery keys ('compressed' does not apply). */ static int ecies_pub_key_size(ecEncCtx* ctx, void* privKey, int compressed, word32* pubKeySz) { @@ -15770,10 +15833,9 @@ static int ecies_export_pub(ecEncCtx* ctx, void* privKey, byte* out, if (ctx != NULL && ctx->curveId == ECC_X25519) { curve25519_key* key = (curve25519_key*)privKey; - /* wc_curve25519_export_public_ex() derives the public point from the - * private scalar when it is missing, but does not check that there is - * a private scalar to derive it from - an init-only key would quietly - * export base * 0. */ + /* wc_curve25519_export_public_ex() derives a missing public point + * without checking a private scalar exists - an init-only key would + * quietly export base * 0. */ if (!key->pubSet && !key->privSet) return ECC_BAD_ARG_E; #ifdef WC_X25519_NONBLOCK @@ -15884,6 +15946,9 @@ static void ecies_key_free(ecEncCtx* ctx, void* key) #endif /* !WOLFSSL_ECIES_OLD */ /* Derive the ECIES shared secret. */ +/* Derive the ECIES shared secret. The exchange routes by the keys' own + * devIds - the per-primitive ECDH crypto callbacks read them directly - so + * the ECIES and per-algorithm devIds on the context take no part here. */ static int ecies_shared_secret(ecEncCtx* ctx, void* privKey, void* pubKey, byte* out, word32* outSz) { @@ -15974,9 +16039,6 @@ int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, byte* encKey = NULL; byte* encIv = NULL; byte* macKey = NULL; - /* devId to hand the DEM AES/HMAC primitives; not every key type carries - * one, so default to INVALID. */ - int eciesDevId = INVALID_DEVID; if (privKey == NULL || pubKey == NULL || msg == NULL || out == NULL || outSz == NULL) @@ -15993,26 +16055,25 @@ int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, if (isMont && compressed) return BAD_FUNC_ARG; - eciesDevId = ecies_devid(ctx, privKey); - #ifdef WOLF_CRYPTO_CB - /* wc_CryptoInfo.pk.eciesencrypt is typed ecc_key*, so the callback is - * ECC-only. This test has to stay ahead of the block because - * WOLF_CRYPTO_CB_FIND drops the devId guard entirely. */ - #ifdef WOLF_CRYPTO_CB_FIND - if (!isMont) - #else - if (!isMont && ((ecc_key*)privKey)->devId != INVALID_DEVID) - #endif + /* Dispatch is generic over the key type (curveId). The devId + * adoption resolution lives inside the callback entry point, and + * nowhere else. */ { /* Snapshot single-use state so we can tell whether the callback handled * it purely in hardware (state untouched) versus re-entered software * (which advances the state itself, below). */ - byte cliStBefore = (ctx != NULL) ? ctx->cliSt : 0; - byte srvStBefore = (ctx != NULL) ? ctx->srvSt : 0; - ret = wc_CryptoCb_EciesEncrypt((ecc_key*)privKey, (ecc_key*)pubKey, + byte cliStBefore = 0; + byte srvStBefore = 0; + int dispCurveId = ECC_CURVE_DEF; + if (ctx != NULL) { + cliStBefore = ctx->cliSt; + srvStBefore = ctx->srvSt; + dispCurveId = ctx->curveId; + } + ret = wc_CryptoCb_EciesEncrypt_ex(privKey, pubKey, msg, msgSz, out, outSz, ctx, - compressed); + compressed, dispCurveId); 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 @@ -16117,14 +16178,18 @@ int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, #endif switch (ctx->kdfAlgo) { 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, + ecies_heap(ctx, privKey), + ecies_kdf_devid(ctx)); break; case ecHKDF_SHA1 : - ret = wc_HKDF(WC_SHA, sharedSecret, sharedSz, ctx->kdfSalt, - ctx->kdfSaltSz, ctx->kdfInfo, ctx->kdfInfoSz, - keys, (word32)keysLen); + ret = wc_HKDF_ex(WC_SHA, sharedSecret, sharedSz, + ctx->kdfSalt, ctx->kdfSaltSz, ctx->kdfInfo, + ctx->kdfInfoSz, keys, (word32)keysLen, + ecies_heap(ctx, privKey), + ecies_kdf_devid(ctx)); break; #if defined(HAVE_X963_KDF) && !defined(NO_HASH_WRAPPER) case ecKDF_X963_SHA1 : @@ -16219,7 +16284,7 @@ int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, Aes aes[1]; #endif ret = wc_AesInit(aes, ecies_heap(ctx, privKey), - eciesDevId); + ecies_enc_devid(ctx)); if (ret == 0) { ret = wc_AesSetKey(aes, encKey, (word32)encKeySz, encIv, AES_ENCRYPTION); @@ -16261,7 +16326,7 @@ int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, WC_AES_BLOCK_SIZE - WOLFSSL_ECIES_GEN_IV_SIZE); ret = wc_AesInit(aes, ecies_heap(ctx, privKey), - eciesDevId); + ecies_enc_devid(ctx)); if (ret == 0) { ret = wc_AesSetKey(aes, encKey, (word32)encKeySz, ctr_iv, AES_ENCRYPTION); @@ -16296,7 +16361,7 @@ int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, Aes aes[1]; #endif ret = wc_AesInit(aes, ecies_heap(ctx, privKey), - eciesDevId); + ecies_enc_devid(ctx)); if (ret == 0) { ret = wc_AesGcmSetKey(aes, encKey, (word32)encKeySz); if (ret == 0) { @@ -16340,7 +16405,7 @@ int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, Hmac hmac[1]; #endif ret = wc_HmacInit(hmac, ecies_heap(ctx, privKey), - eciesDevId); + ecies_mac_devid(ctx)); if (ret == 0) { ret = wc_HmacSetKey(hmac, WC_SHA256, macKey, WC_SHA256_DIGEST_SIZE); @@ -16446,9 +16511,6 @@ int wc_ecc_decrypt_ex2(void* privKey, void* pubKey, const byte* msg, byte* encKey = NULL; const byte* encIv = NULL; byte* macKey = NULL; - /* devId to hand the DEM AES/HMAC primitives; not every key type carries - * one, so default to INVALID. */ - int eciesDevId = INVALID_DEVID; if (privKey == NULL || msg == NULL || out == NULL || outSz == NULL) @@ -16462,27 +16524,27 @@ int wc_ecc_decrypt_ex2(void* privKey, void* pubKey, const byte* msg, * and before ctx is defaulted below - the crypto callback has to keep * seeing the caller's own ctx pointer. */ isMont = ecies_is_mont(ctx); - (void)isMont; /* only read by the callback and compressed-point guards */ - - eciesDevId = ecies_devid(ctx, privKey); + (void)isMont; /* only read by the compressed-point guard (HAVE_COMP_KEY) */ #ifdef WOLF_CRYPTO_CB - /* wc_CryptoInfo.pk.eciesdecrypt is typed ecc_key*, so the callback is - * ECC-only. This test has to stay ahead of the block because - * WOLF_CRYPTO_CB_FIND drops the devId guard entirely. */ - #ifdef WOLF_CRYPTO_CB_FIND - if (!isMont) - #else - if (!isMont && ((ecc_key*)privKey)->devId != INVALID_DEVID) - #endif + /* Dispatch is generic over the key type (curveId). The devId + * adoption resolution lives inside the callback entry point, and + * nowhere else. */ { /* Snapshot single-use state so we can tell whether the callback handled * it purely in hardware (state untouched) versus re-entered software * (which advances the state itself, below). */ - byte cliStBefore = (ctx != NULL) ? ctx->cliSt : 0; - byte srvStBefore = (ctx != NULL) ? ctx->srvSt : 0; - ret = wc_CryptoCb_EciesDecrypt((ecc_key*)privKey, (ecc_key*)pubKey, - msg, msgSz, out, outSz, ctx); + byte cliStBefore = 0; + byte srvStBefore = 0; + int dispCurveId = ECC_CURVE_DEF; + if (ctx != NULL) { + cliStBefore = ctx->cliSt; + srvStBefore = ctx->srvSt; + dispCurveId = ctx->curveId; + } + ret = wc_CryptoCb_EciesDecrypt_ex(privKey, pubKey, + msg, msgSz, out, outSz, ctx, + dispCurveId); 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. */ @@ -16659,14 +16721,18 @@ int wc_ecc_decrypt_ex2(void* privKey, void* pubKey, const byte* msg, #endif switch (ctx->kdfAlgo) { 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, + ecies_heap(ctx, privKey), + ecies_kdf_devid(ctx)); break; case ecHKDF_SHA1 : - ret = wc_HKDF(WC_SHA, sharedSecret, sharedSz, ctx->kdfSalt, - ctx->kdfSaltSz, ctx->kdfInfo, ctx->kdfInfoSz, - keys, (word32)keysLen); + ret = wc_HKDF_ex(WC_SHA, sharedSecret, sharedSz, + ctx->kdfSalt, ctx->kdfSaltSz, ctx->kdfInfo, + ctx->kdfInfoSz, keys, (word32)keysLen, + ecies_heap(ctx, privKey), + ecies_kdf_devid(ctx)); break; #if defined(HAVE_X963_KDF) && !defined(NO_HASH_WRAPPER) case ecKDF_X963_SHA1 : @@ -16742,7 +16808,7 @@ int wc_ecc_decrypt_ex2(void* privKey, void* pubKey, const byte* msg, Hmac hmac[1]; #endif ret = wc_HmacInit(hmac, ecies_heap(ctx, privKey), - eciesDevId); + ecies_mac_devid(ctx)); if (ret == 0) { ret = wc_HmacSetKey(hmac, WC_SHA256, macKey, WC_SHA256_DIGEST_SIZE); @@ -16793,7 +16859,7 @@ int wc_ecc_decrypt_ex2(void* privKey, void* pubKey, const byte* msg, Aes aes[1]; #endif ret = wc_AesInit(aes, ecies_heap(ctx, privKey), - eciesDevId); + ecies_enc_devid(ctx)); if (ret == 0) { ret = wc_AesSetKey(aes, encKey, (word32)encKeySz, encIv, AES_DECRYPTION); @@ -16826,7 +16892,7 @@ int wc_ecc_decrypt_ex2(void* privKey, void* pubKey, const byte* msg, Aes aes[1]; #endif ret = wc_AesInit(aes, ecies_heap(ctx, privKey), - eciesDevId); + ecies_enc_devid(ctx)); if (ret == 0) { byte ctr_iv[WC_AES_BLOCK_SIZE]; /* Make a 16 byte IV from the bytes passed in. */ @@ -16864,7 +16930,7 @@ int wc_ecc_decrypt_ex2(void* privKey, void* pubKey, const byte* msg, Aes aes[1]; #endif ret = wc_AesInit(aes, ecies_heap(ctx, privKey), - eciesDevId); + ecies_enc_devid(ctx)); if (ret == 0) { ret = wc_AesGcmSetKey(aes, encKey, (word32)encKeySz); if (ret == 0) { diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 405907cdf8..6d3f37a8c6 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -46368,6 +46368,104 @@ 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)) +#if !defined(WOLFSSL_NO_MALLOC) && \ + ((! defined(HAVE_FIPS)) || FIPS_VERSION_GE(5,3)) +/* REQ/RESP salt exchange between a client and a server context, as the two + * peers would do it over the transport. */ +static wc_test_ret_t ecies_test_exchange_salts(ecEncCtx* cliCtx, + ecEncCtx* srvCtx) +{ + byte cliSalt[EXCHANGE_SALT_SZ]; + byte srvSalt[EXCHANGE_SALT_SZ]; + const byte* tmpSalt; + int ret; + + tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx); + if (tmpSalt == NULL) + return WC_TEST_RET_ENC_NC; + XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); + tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx); + if (tmpSalt == NULL) + return WC_TEST_RET_ENC_NC; + XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); + + /* in actual use, we'd get the peer's salt over the transport */ + ret = wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt); + if (ret == 0) + ret = wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + return 0; +} + +#ifdef WOLFSSL_ECIES_X25519 +/* Fresh X25519 key pair for an ECIES test. The keys are caller-allocated + * storage (WC_DECLARE_VAR/WC_ALLOC_VAR); on failure nothing is left + * initialized, so the caller skips straight to freeing the storage. */ +static wc_test_ret_t ecies_make_x25519_pair(WC_RNG* rng, curve25519_key* keyA, + curve25519_key* keyB) +{ + int ret; + + XMEMSET(keyA, 0, sizeof(*keyA)); + XMEMSET(keyB, 0, sizeof(*keyB)); + + ret = wc_curve25519_init_ex(keyA, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_curve25519_init_ex(keyB, HEAP_HINT, INVALID_DEVID); + if (ret != 0) { + wc_curve25519_free(keyA); + return WC_TEST_RET_ENC_EC(ret); + } + + ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, keyA); + if (ret == 0) + ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, keyB); + if (ret != 0) { + wc_curve25519_free(keyB); + wc_curve25519_free(keyA); + return WC_TEST_RET_ENC_EC(ret); + } + + return 0; +} +#endif /* WOLFSSL_ECIES_X25519 */ + +#ifdef WOLFSSL_ECIES_X448 +/* The X448 sibling of ecies_make_x25519_pair(). */ +static wc_test_ret_t ecies_make_x448_pair(WC_RNG* rng, curve448_key* keyA, + curve448_key* keyB) +{ + int ret; + + XMEMSET(keyA, 0, sizeof(*keyA)); + XMEMSET(keyB, 0, sizeof(*keyB)); + + ret = wc_curve448_init(keyA); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_curve448_init(keyB); + if (ret != 0) { + wc_curve448_free(keyA); + return WC_TEST_RET_ENC_EC(ret); + } + + ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, keyA); + if (ret == 0) + ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, keyB); + if (ret != 0) { + wc_curve448_free(keyB); + wc_curve448_free(keyA); + return WC_TEST_RET_ENC_EC(ret); + } + + return 0; +} +#endif /* WOLFSSL_ECIES_X448 */ +#endif /* !WOLFSSL_NO_MALLOC && (!HAVE_FIPS || FIPS_VERSION_GE(5,3)) */ + #if !defined(WOLFSSL_NO_MALLOC) #if ((! defined(HAVE_FIPS)) || FIPS_VERSION_GE(5,3)) @@ -46768,9 +46866,6 @@ static wc_test_ret_t ecc_encrypt_e2e_test(WC_RNG* rng, ecc_key* userA, ecc_key* int i; ecEncCtx* cliCtx = NULL; ecEncCtx* srvCtx = NULL; - byte cliSalt[EXCHANGE_SALT_SZ]; - byte srvSalt[EXCHANGE_SALT_SZ]; - const byte* tmpSalt; byte msg2[48]; byte plain2[48]; #ifdef WOLFSSL_ECIES_OLD @@ -46852,24 +46947,8 @@ static wc_test_ret_t ecc_encrypt_e2e_test(WC_RNG* rng, ecc_key* userA, ecc_key* if (ret != 0) goto done; - /* get salt to send to peer */ - tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx); - if (tmpSalt == NULL) { - ret = WC_TEST_RET_ENC_NC; goto done; - } - XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); - - tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx); - if (tmpSalt == NULL) { - ret = WC_TEST_RET_ENC_NC; goto done; - } - XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); - - /* in actual use, we'd get the peer's salt over the transport */ - ret = wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt); - if (ret != 0) - goto done; - ret = wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt); + /* exchange salts, as the peers would over the transport */ + ret = ecies_test_exchange_salts(cliCtx, srvCtx); if (ret != 0) goto done; @@ -46948,24 +47027,8 @@ static wc_test_ret_t ecc_encrypt_e2e_test(WC_RNG* rng, ecc_key* userA, ecc_key* if (ret != 0) goto done; - /* get salt to send to peer */ - tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx); - if (tmpSalt == NULL) { - ret = WC_TEST_RET_ENC_NC; goto done; - } - XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); - - tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx); - if (tmpSalt == NULL) { - ret = WC_TEST_RET_ENC_NC; goto done; - } - XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); - - /* in actual use, we'd get the peer's salt over the transport */ - ret = wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt); - if (ret != 0) - goto done; - ret = wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt); + /* exchange salts, as the peers would over the transport */ + ret = ecies_test_exchange_salts(cliCtx, srvCtx); if (ret != 0) goto done; @@ -47382,6 +47445,8 @@ typedef struct EciesCbCtx { int mode; /* 0 = force fallback, 1 = handle in callback */ int encryptInvoked; /* set when the callback services an ECIES encrypt */ int decryptInvoked; /* set when the callback services an ECIES decrypt */ + int sawCurveId; /* key type the last serviced op carried; proves the + * curveId discriminator reaches the callback */ } EciesCbCtx; /* Arbitrary per-message overhead the simulated (mode==2) device adds beyond @@ -47399,6 +47464,7 @@ static int myEciesCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx) if (cbCtx->mode == 0) return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); cbCtx->encryptInvoked = 1; + cbCtx->sawCurveId = ECC_CURVE_DEF; if (cbCtx->mode == 2) { /* Simulate a device that produces output without re-entering * software, leaving the ecEncCtx state untouched. Used to @@ -47412,25 +47478,93 @@ static int myEciesCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx) *info->pk.eciesencrypt.outSz = needed; return 0; } + /* Clear every devId that can route back here: the key's, and + * the context's, onto which the key's devId is adopted. */ info->pk.eciesencrypt.privKey->devId = INVALID_DEVID; + if (info->pk.eciesencrypt.ctx != NULL) + (void)wc_ecc_ctx_set_dev_id(info->pk.eciesencrypt.ctx, + 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 (info->pk.eciesencrypt.ctx != NULL) + (void)wc_ecc_ctx_set_dev_id(info->pk.eciesencrypt.ctx, + devIdArg); } else if (info->pk.type == WC_PK_TYPE_ECIES_DECRYPT) { if (cbCtx->mode == 0) return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); cbCtx->decryptInvoked = 1; + cbCtx->sawCurveId = ECC_CURVE_DEF; info->pk.eciesdecrypt.privKey->devId = INVALID_DEVID; + if (info->pk.eciesdecrypt.ctx != NULL) + (void)wc_ecc_ctx_set_dev_id(info->pk.eciesdecrypt.ctx, + 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 (info->pk.eciesdecrypt.ctx != NULL) + (void)wc_ecc_ctx_set_dev_id(info->pk.eciesdecrypt.ctx, + devIdArg); } +#ifdef WOLFSSL_ECIES_MONTGOMERY + else if (info->pk.type == WC_PK_TYPE_ECIES_ENCRYPT_MONT) { + if (cbCtx->mode == 0) + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + cbCtx->encryptInvoked = 1; + cbCtx->sawCurveId = info->pk.eciesencrypt_mont.curveId; + if (cbCtx->mode == 2) { + word32 needed = info->pk.eciesencrypt_mont.msgSz + + ECIES_CB_FAKE_OVERHEAD; + if (info->pk.eciesencrypt_mont.out == NULL || + *info->pk.eciesencrypt_mont.outSz < needed) + return WC_NO_ERR_TRACE(BUFFER_E); + XMEMSET(info->pk.eciesencrypt_mont.out, 0, needed); + *info->pk.eciesencrypt_mont.outSz = needed; + return 0; + } + /* Forward to software. The devId rides on the ecEncCtx for the + * Montgomery key types (curve448_key has no devId member), so + * the re-entrancy dance clears the ctx devId, not the key's. */ + ret = wc_ecc_ctx_set_dev_id(info->pk.eciesencrypt_mont.ctx, + INVALID_DEVID); + if (ret != 0) + return ret; + ret = wc_ecc_encrypt_ex2(info->pk.eciesencrypt_mont.privKey, + info->pk.eciesencrypt_mont.pubKey, + info->pk.eciesencrypt_mont.msg, + info->pk.eciesencrypt_mont.msgSz, + info->pk.eciesencrypt_mont.out, + info->pk.eciesencrypt_mont.outSz, + info->pk.eciesencrypt_mont.ctx, 0); + (void)wc_ecc_ctx_set_dev_id(info->pk.eciesencrypt_mont.ctx, + devIdArg); + } + else if (info->pk.type == WC_PK_TYPE_ECIES_DECRYPT_MONT) { + if (cbCtx->mode == 0) + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + cbCtx->decryptInvoked = 1; + cbCtx->sawCurveId = info->pk.eciesdecrypt_mont.curveId; + ret = wc_ecc_ctx_set_dev_id(info->pk.eciesdecrypt_mont.ctx, + INVALID_DEVID); + if (ret != 0) + return ret; + ret = wc_ecc_decrypt_ex2(info->pk.eciesdecrypt_mont.privKey, + info->pk.eciesdecrypt_mont.pubKey, + info->pk.eciesdecrypt_mont.msg, + info->pk.eciesdecrypt_mont.msgSz, + info->pk.eciesdecrypt_mont.out, + info->pk.eciesdecrypt_mont.outSz, + info->pk.eciesdecrypt_mont.ctx); + (void)wc_ecc_ctx_set_dev_id(info->pk.eciesdecrypt_mont.ctx, + devIdArg); + } +#endif /* WOLFSSL_ECIES_MONTGOMERY */ } return ret; @@ -47472,9 +47606,13 @@ static const byte eciesCbModes[] = { /* One callback round-trip for a specific DEM cipher. handleInCb selects whether * the callback services the op (mode 1) or declines so software runs (mode 0). - * Verifies the plaintext round-trips and that the callback was/ wasn't used. */ + * Verifies the plaintext round-trips and that the callback was/ wasn't used. + * The keys are of the type curveId selects (ecc_key for ECC_CURVE_DEF, + * curve25519_key/curve448_key for the Montgomery types); decPub is the + * sender's key for WOLFSSL_ECIES_OLD, NULL otherwise. */ static wc_test_ret_t ecies_cryptocb_roundtrip(WC_RNG* rng, EciesCbCtx* cbCtx, - ecc_key* userA, ecc_key* userB, byte encAlgo, int handleInCb) + void* keyA, void* keyB, void* decPub, int curveId, byte encAlgo, + int handleInCb) { wc_test_ret_t ret; ecEncCtx* cliCtx = NULL; @@ -47484,46 +47622,63 @@ static wc_test_ret_t ecies_cryptocb_roundtrip(WC_RNG* rng, EciesCbCtx* cbCtx, byte plain[64]; word32 outSz = (word32)sizeof(out); word32 plainSz = (word32)sizeof(plain); - byte cliSalt[EXCHANGE_SALT_SZ]; - byte srvSalt[EXCHANGE_SALT_SZ]; - const byte* tmpSalt; - ecc_key* decPub = NULL; int i; -#ifdef WOLFSSL_ECIES_OLD - decPub = userA; /* OLD needs the sender's public key to decrypt */ -#endif for (i = 0; i < (int)sizeof(msg); i++) msg[i] = (byte)i; cliCtx = wc_ecc_ctx_new(REQ_RESP_CLIENT, rng); srvCtx = wc_ecc_ctx_new(REQ_RESP_SERVER, rng); - if (cliCtx == NULL || srvCtx == NULL) { ret = WC_TEST_RET_ENC_ERRNO; goto rt_done; } + if (cliCtx == NULL || srvCtx == NULL) { + ret = WC_TEST_RET_ENC_ERRNO; + goto rt_done; + } ret = wc_ecc_ctx_set_algo(cliCtx, encAlgo, ecHKDF_SHA256, ecHMAC_SHA256); if (ret == 0) 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; } + 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); - tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx); - if (tmpSalt == NULL) { ret = WC_TEST_RET_ENC_NC; goto rt_done; } - XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); - ret = wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt); - if (ret == 0) - ret = wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt); - if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto rt_done; } + if (curveId != ECC_CURVE_DEF) { + /* Montgomery keys carry no usable devId, so the context routes the + * dispatch to the test device. */ + ret = wc_ecc_ctx_set_curve_id(cliCtx, curveId); + if (ret == 0) + ret = wc_ecc_ctx_set_curve_id(srvCtx, curveId); + if (ret == 0) + 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; + } + } + + ret = ecies_test_exchange_salts(cliCtx, srvCtx); + if (ret != 0) + goto rt_done; cbCtx->mode = handleInCb; cbCtx->encryptInvoked = 0; cbCtx->decryptInvoked = 0; + cbCtx->sawCurveId = ECC_CURVE_INVALID; - ret = wc_ecc_encrypt(userA, userB, msg, sizeof(msg), out, &outSz, cliCtx); - if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto rt_done; } - ret = wc_ecc_decrypt(userB, decPub, out, outSz, plain, &plainSz, srvCtx); - if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto rt_done; } + ret = wc_ecc_encrypt_ex2(keyA, keyB, msg, sizeof(msg), out, &outSz, + cliCtx, 0); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto rt_done; + } + ret = wc_ecc_decrypt_ex2(keyB, decPub, out, outSz, plain, &plainSz, + srvCtx); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto rt_done; + } if (plainSz != sizeof(msg) || XMEMCMP(plain, msg, sizeof(msg)) != 0) { ret = WC_TEST_RET_ENC_NC; goto rt_done; @@ -47534,6 +47689,9 @@ static wc_test_ret_t ecies_cryptocb_roundtrip(WC_RNG* rng, EciesCbCtx* cbCtx, if (cbCtx->encryptInvoked != (handleInCb ? 1 : 0) || cbCtx->decryptInvoked != (handleInCb ? 1 : 0)) ret = WC_TEST_RET_ENC_NC; + /* And a serviced op must have carried the right key type. */ + if (ret == 0 && handleInCb && cbCtx->sawCurveId != curveId) + ret = WC_TEST_RET_ENC_NC; rt_done: wc_ecc_ctx_free(cliCtx); @@ -47546,7 +47704,7 @@ static wc_test_ret_t ecies_cryptocb_roundtrip(WC_RNG* rng, EciesCbCtx* cbCtx, * the same ctx could be reused, which is nonce reuse for the static-nonce GCM * DEM. Verify a second encrypt on the same ctx is rejected with BAD_STATE_E. */ static wc_test_ret_t ecies_cryptocb_state_test(WC_RNG* rng, EciesCbCtx* cbCtx, - ecc_key* userA, ecc_key* userB) + void* keyA, void* keyB, int curveId) { wc_test_ret_t ret = 0; ecEncCtx* cliCtx = NULL; @@ -47554,9 +47712,6 @@ static wc_test_ret_t ecies_cryptocb_state_test(WC_RNG* rng, EciesCbCtx* cbCtx, byte out[256]; word32 outSz = sizeof(out); byte msg[16]; - byte cliSalt[EXCHANGE_SALT_SZ]; - byte srvSalt[EXCHANGE_SALT_SZ]; - const byte* tmpSalt; int i; for (i = 0; i < (int)sizeof(msg); i++) @@ -47568,30 +47723,45 @@ static wc_test_ret_t ecies_cryptocb_state_test(WC_RNG* rng, EciesCbCtx* cbCtx, ret = WC_TEST_RET_ENC_NC; goto st_done; } + if (curveId != ECC_CURVE_DEF) { + ret = wc_ecc_ctx_set_curve_id(cliCtx, curveId); + if (ret == 0) + ret = wc_ecc_ctx_set_curve_id(srvCtx, curveId); + if (ret == 0) + 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; } - XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); - tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx); - if (tmpSalt == NULL) { ret = WC_TEST_RET_ENC_NC; goto st_done; } - XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); - ret = wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt); - if (ret == 0) - ret = wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt); - if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto st_done; } + ret = ecies_test_exchange_salts(cliCtx, srvCtx); + if (ret != 0) + goto st_done; cbCtx->mode = 2; /* pure hardware: succeed without touching ctx state */ cbCtx->encryptInvoked = 0; /* First encrypt: serviced "in hardware". */ - ret = wc_ecc_encrypt(userA, userB, msg, sizeof(msg), out, &outSz, cliCtx); - if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto st_done; } - if (cbCtx->encryptInvoked != 1) { ret = WC_TEST_RET_ENC_NC; goto st_done; } + ret = wc_ecc_encrypt_ex2(keyA, keyB, msg, sizeof(msg), out, &outSz, + cliCtx, 0); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto st_done; + } + if (cbCtx->encryptInvoked != 1) { + ret = WC_TEST_RET_ENC_NC; + goto st_done; + } /* Second encrypt on the same ctx must be rejected: the hardware path must * have advanced the single-use state. */ outSz = sizeof(out); - ret = wc_ecc_encrypt(userA, userB, msg, sizeof(msg), out, &outSz, cliCtx); + ret = wc_ecc_encrypt_ex2(keyA, keyB, msg, sizeof(msg), out, &outSz, + cliCtx, 0); 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; @@ -47604,6 +47774,109 @@ static wc_test_ret_t ecies_cryptocb_state_test(WC_RNG* rng, EciesCbCtx* cbCtx, return ret; } +/* The full CryptoCb matrix for one key type: every DEM cipher in the build + * x {callback services the op, callback declines and software runs}, then + * single-use enforcement on the pure-hardware path. */ +static wc_test_ret_t ecies_cryptocb_matrix(WC_RNG* rng, EciesCbCtx* cbCtx, + void* keyA, void* keyB, void* decPub, int curveId) +{ + wc_test_ret_t ret = 0; + int m, pass; + + for (m = 0; + m < (int)(sizeof(eciesCbModes) / sizeof(eciesCbModes[0])) && ret == 0; + m++) { + if (eciesCbModes[m] == 0) /* sentinel entry */ + continue; + for (pass = 1; pass >= 0 && ret == 0; pass--) { + ret = ecies_cryptocb_roundtrip(rng, cbCtx, keyA, keyB, decPub, + curveId, eciesCbModes[m], pass); + } + } + + /* Single-use state must be enforced even for a pure-hardware callback. */ + if (ret == 0) + ret = ecies_cryptocb_state_test(rng, cbCtx, keyA, keyB, curveId); + + return ret; +} + +#ifdef WOLFSSL_ECIES_X25519 +/* Full ECIES CryptoCb matrix over X25519. The devId rides on the ecEncCtx + * (set inside the parameterized helpers) - the keys stay at INVALID_DEVID on + * purpose. */ +static wc_test_ret_t ecies_x25519_cryptocb_test(WC_RNG* rng, EciesCbCtx* cbCtx) +{ + wc_test_ret_t ret; + WC_DECLARE_VAR(keyA, curve25519_key, 1, HEAP_HINT); + WC_DECLARE_VAR(keyB, curve25519_key, 1, HEAP_HINT); + void* decPub = NULL; + + WC_ALLOC_VAR(keyA, curve25519_key, 1, HEAP_HINT); + WC_ALLOC_VAR(keyB, curve25519_key, 1, HEAP_HINT); + if (!WC_VAR_OK(keyA) || !WC_VAR_OK(keyB)) { + ret = WC_TEST_RET_ENC_EC(MEMORY_E); + goto mx_done_free; + } + + ret = ecies_make_x25519_pair(rng, keyA, keyB); + if (ret != 0) + goto mx_done_free; + +#ifdef WOLFSSL_ECIES_OLD + decPub = keyA; /* OLD needs the sender's public key to decrypt */ +#endif + + ret = ecies_cryptocb_matrix(rng, cbCtx, keyA, keyB, decPub, ECC_X25519); + + wc_curve25519_free(keyB); + wc_curve25519_free(keyA); +mx_done_free: + WC_FREE_VAR(keyB, HEAP_HINT); + WC_FREE_VAR(keyA, HEAP_HINT); + + return ret; +} +#endif /* WOLFSSL_ECIES_X25519 */ + +#ifdef WOLFSSL_ECIES_X448 +/* The X448 sibling of ecies_x25519_cryptocb_test(). curve448_key has no + * devId member at all, which is exactly what the ctx-carried devId exists + * for. */ +static wc_test_ret_t ecies_x448_cryptocb_test(WC_RNG* rng, EciesCbCtx* cbCtx) +{ + wc_test_ret_t ret; + WC_DECLARE_VAR(keyA, curve448_key, 1, HEAP_HINT); + WC_DECLARE_VAR(keyB, curve448_key, 1, HEAP_HINT); + void* decPub = NULL; + + WC_ALLOC_VAR(keyA, curve448_key, 1, HEAP_HINT); + WC_ALLOC_VAR(keyB, curve448_key, 1, HEAP_HINT); + if (!WC_VAR_OK(keyA) || !WC_VAR_OK(keyB)) { + ret = WC_TEST_RET_ENC_EC(MEMORY_E); + goto m4_done_free; + } + + ret = ecies_make_x448_pair(rng, keyA, keyB); + if (ret != 0) + goto m4_done_free; + +#ifdef WOLFSSL_ECIES_OLD + decPub = keyA; /* OLD needs the sender's public key to decrypt */ +#endif + + ret = ecies_cryptocb_matrix(rng, cbCtx, keyA, keyB, decPub, ECC_X448); + + wc_curve448_free(keyB); + wc_curve448_free(keyA); +m4_done_free: + WC_FREE_VAR(keyB, HEAP_HINT); + WC_FREE_VAR(keyA, HEAP_HINT); + + return ret; +} +#endif /* WOLFSSL_ECIES_X448 */ + /* Exercises the ECIES CryptoCb dispatch for every DEM cipher in the build, and * for each: pass with the callback servicing the op, and a pass verifying * software fallback on CRYPTOCB_UNAVAILABLE. */ @@ -47614,7 +47887,6 @@ static wc_test_ret_t ecc_encrypt_cryptocb_test(WC_RNG* rng) ecc_key* userB = NULL; int registered = 0; int userAInit = 0, userBInit = 0; - int m, pass; EciesCbCtx cbCtx; XMEMSET(&cbCtx, 0, sizeof(cbCtx)); @@ -47664,22 +47936,26 @@ static wc_test_ret_t ecc_encrypt_cryptocb_test(WC_RNG* rng) userA->devId = ECIES_CB_TEST_DEVID; userB->devId = ECIES_CB_TEST_DEVID; - /* For each DEM cipher in the build: pass 1 = callback services the op, - * pass 0 = callback declines and software fallback runs. */ - for (m = 0; - m < (int)(sizeof(eciesCbModes) / sizeof(eciesCbModes[0])) && ret == 0; - m++) { - if (eciesCbModes[m] == 0) /* sentinel entry */ - continue; - for (pass = 1; pass >= 0 && ret == 0; pass--) { - ret = ecies_cryptocb_roundtrip(rng, &cbCtx, userA, userB, - eciesCbModes[m], pass); - } - } + /* Every DEM x {serviced, declined} plus the pure-hardware single-use + * check, all through the shared matrix. */ +#ifdef WOLFSSL_ECIES_OLD + ret = ecies_cryptocb_matrix(rng, &cbCtx, userA, userB, userA, + ECC_CURVE_DEF); +#else + ret = ecies_cryptocb_matrix(rng, &cbCtx, userA, userB, NULL, + ECC_CURVE_DEF); +#endif - /* Single-use state must be enforced even for a pure-hardware callback. */ + /* The same matrix over the Montgomery key types, routed by the devId on + * the ecEncCtx rather than the keys. */ +#ifdef WOLFSSL_ECIES_X25519 if (ret == 0) - ret = ecies_cryptocb_state_test(rng, &cbCtx, userA, userB); + ret = ecies_x25519_cryptocb_test(rng, &cbCtx); +#endif +#ifdef WOLFSSL_ECIES_X448 + if (ret == 0) + ret = ecies_x448_cryptocb_test(rng, &cbCtx); +#endif cb_done: if (userAInit) @@ -47704,13 +47980,9 @@ static wc_test_ret_t ecc_encrypt_cryptocb_test(WC_RNG* rng) #if defined(WOLFSSL_ECIES_MONTGOMERY) && !defined(NO_AES) && \ defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) -/* ECIES over a Montgomery curve (X25519 / X448). - * - * The key type is carried on the context, and the keys are handed to the - * generic wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2() entry points. - * - * The DEM is pinned to AES-128-CBC + HMAC-SHA256 below - the size checks - * assume it, and the build's default algorithm tracks the configuration. */ +/* ECIES over a Montgomery curve (X25519 / X448) through the generic + * wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2() entry points. The DEM is + * pinned to AES-128-CBC + HMAC-SHA256 - the size checks assume it. */ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, void* srvKey, word32 pubKeySz) { @@ -47761,6 +48033,16 @@ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, goto done; } +#ifdef WOLF_CRYPTO_CB + /* Route the whole-op ECIES callback by the rig-selected global devId, + * like the rest of the wolfcrypt tests. */ + ret = wc_ecc_ctx_set_dev_id(ctx, devId); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } +#endif + XMEMSET(enc, 0, MAX_ECIES_TEST_SZ); XMEMSET(plain, 0, MAX_ECIES_TEST_SZ); XMEMCPY(plain, msg, XSTRLEN((const char*)msg) + 1); @@ -47880,18 +48162,20 @@ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, } enc[0] ^= 0x01; - /* An invalid ephemeral key must be rejected by the public-key validation - * itself - the check_public error, not a MAC failure. A set high bit is - * non-canonical for X25519; wc_curve448_check_public() accepts the high - * bit (a 448-bit coordinate fills its last byte), so X448 uses the - * all-zero low-order point instead. */ + /* An invalid ephemeral key must fail public-key validation, not the + * MAC. X25519 uses a set high bit (non-canonical); X448 accepts the + * high bit, so it uses the all-zero low-order point instead. */ { int expErr; +#ifdef WOLFSSL_ECIES_X25519 + /* ECC_X25519 only exists in the enum when curve25519 is built. */ if (curveId == ECC_X25519) { enc[pubKeySz - 1] |= 0x80; expErr = WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E); } - else { + else +#endif + { XMEMSET(enc, 0, pubKeySz); expErr = WC_NO_ERR_TRACE(ECC_BAD_ARG_E); } @@ -47924,14 +48208,9 @@ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, #if defined(WOLFSSL_ECIES_X25519) && !defined(WOLFSSL_ECIES_OLD) && \ !defined(WOLFSSL_ECIES_GEN_IV) && !defined(WOLFSSL_ECIES_ISO18033) && \ !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) -/* Known answer test for ECIES over X25519. - * - * The keys are the Alice/Bob pair from RFC 7748 section 6.1. The test first - * checks the raw shared secret against the RFC's value, which pins the byte - * order the ECIES code derives it in - a round trip cannot, because both ends - * agree whichever order is used. The ciphertext is then checked against a - * fixed vector; with no IV and a fixed ephemeral key the default mode is fully - * deterministic. */ +/* ECIES X25519 KAT with the RFC 7748 section 6.1 Alice/Bob keys. The raw + * shared secret is checked first to pin the byte order (a round trip can + * not); the ciphertext is then checked against a fixed vector. */ static const byte ecies_x25519_kat_alice_priv[] = { 0x77,0x07,0x6d,0x0a,0x73,0x18,0xa5,0x7d,0x3c,0x16,0xc1,0x72, 0x51,0xb2,0x66,0x45,0xdf,0x4c,0x2f,0x87,0xeb,0xc0,0x99,0x2a, @@ -48076,6 +48355,14 @@ static wc_test_ret_t ecies_x25519_kat(WC_RNG* rng) goto done; } +#ifdef WOLF_CRYPTO_CB + ret = wc_ecc_ctx_set_dev_id(ctx, devId); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } +#endif + ret = wc_ecc_encrypt_ex2(alice, bob, ecies_x25519_kat_msg, msgSz, out, &outSz, ctx, 0); if (ret != 0) { @@ -48123,11 +48410,9 @@ static wc_test_ret_t ecies_x25519_kat(WC_RNG* rng) #if defined(WOLFSSL_ECIES_X448) && !defined(WOLFSSL_ECIES_OLD) && \ !defined(WOLFSSL_ECIES_GEN_IV) && !defined(WOLFSSL_ECIES_ISO18033) && \ !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) -/* Known answer test for ECIES over X448, the sibling of ecies_x25519_kat(). - * - * The keys are the Alice/Bob pair from RFC 7748 section 6.2, and the raw - * shared secret is checked against the RFC's value to pin the byte order - * wc_curve448_shared_secret_ex() is asked to derive it in. */ +/* ECIES X448 KAT, the sibling of ecies_x25519_kat(): RFC 7748 section 6.2 + * Alice/Bob keys, with the raw shared secret checked against the RFC to + * pin the byte order it is derived in. */ static const byte ecies_x448_kat_alice_priv[] = { 0x9a,0x8f,0x49,0x25,0xd1,0x51,0x9f,0x57,0x75,0xcf,0x46,0xb0, 0x4b,0x58,0x00,0xd4,0xee,0x9e,0xe8,0xba,0xe8,0xbc,0x55,0x65, @@ -48271,6 +48556,14 @@ static wc_test_ret_t ecies_x448_kat(WC_RNG* rng) goto done; } +#ifdef WOLF_CRYPTO_CB + ret = wc_ecc_ctx_set_dev_id(ctx, devId); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto done; + } +#endif + ret = wc_ecc_encrypt_ex2(alice, bob, ecies_x448_kat_msg, msgSz, out, &outSz, ctx, 0); if (ret != 0) { @@ -48327,9 +48620,6 @@ static wc_test_ret_t ecies_mont_req_resp_test(WC_RNG* rng, byte encAlgo, WC_DECLARE_VAR(plain, byte, MAX_ECIES_TEST_SZ, HEAP_HINT); ecEncCtx* cliCtx = NULL; ecEncCtx* srvCtx = NULL; - const byte* tmpSalt; - byte cliSalt[EXCHANGE_SALT_SZ]; - byte srvSalt[EXCHANGE_SALT_SZ]; word32 outSz = MAX_ECIES_TEST_SZ; word32 plainSz = MAX_ECIES_TEST_SZ; static const byte msg[] = "ECIES Montgomery req/response!!!"; @@ -48352,6 +48642,12 @@ static wc_test_ret_t ecies_mont_req_resp_test(WC_RNG* rng, byte encAlgo, ret = wc_ecc_ctx_set_curve_id(cliCtx, curveId); if (ret == 0) ret = wc_ecc_ctx_set_curve_id(srvCtx, curveId); +#ifdef WOLF_CRYPTO_CB + if (ret == 0) + ret = wc_ecc_ctx_set_dev_id(cliCtx, devId); + if (ret == 0) + ret = wc_ecc_ctx_set_dev_id(srvCtx, devId); +#endif if (ret == 0) ret = wc_ecc_ctx_set_algo(cliCtx, encAlgo, ecHKDF_SHA256, ecHMAC_SHA256); @@ -48364,26 +48660,9 @@ static wc_test_ret_t ecies_mont_req_resp_test(WC_RNG* rng, byte encAlgo, } /* Exchange salts, as the peers would over the transport. */ - tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx); - if (tmpSalt == NULL) { - ret = WC_TEST_RET_ENC_NC; - goto done; - } - XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); - tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx); - if (tmpSalt == NULL) { - ret = WC_TEST_RET_ENC_NC; - goto done; - } - XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); - - ret = wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt); - if (ret == 0) - ret = wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt); - if (ret != 0) { - ret = WC_TEST_RET_ENC_EC(ret); + ret = ecies_test_exchange_salts(cliCtx, srvCtx); + if (ret != 0) goto done; - } /* Client request. */ ret = wc_ecc_encrypt_ex2(cliKey, srvKey, msg, msgSz, out, &outSz, @@ -48456,29 +48735,11 @@ static wc_test_ret_t ecies_x25519_req_resp_test(WC_RNG* rng, byte encAlgo) goto done_free; } - XMEMSET(cliKey, 0, sizeof(*cliKey)); - XMEMSET(srvKey, 0, sizeof(*srvKey)); - - ret = wc_curve25519_init_ex(cliKey, HEAP_HINT, INVALID_DEVID); - if (ret != 0) { - ret = WC_TEST_RET_ENC_EC(ret); - goto done_free; - } - ret = wc_curve25519_init_ex(srvKey, HEAP_HINT, INVALID_DEVID); - if (ret != 0) { - wc_curve25519_free(cliKey); - ret = WC_TEST_RET_ENC_EC(ret); + ret = ecies_make_x25519_pair(rng, cliKey, srvKey); + if (ret != 0) goto done_free; - } - ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, cliKey); - if (ret == 0) - ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, srvKey); - if (ret != 0) - ret = WC_TEST_RET_ENC_EC(ret); - if (ret == 0) - ret = ecies_mont_req_resp_test(rng, encAlgo, ECC_X25519, cliKey, - srvKey); + ret = ecies_mont_req_resp_test(rng, encAlgo, ECC_X25519, cliKey, srvKey); wc_curve25519_free(srvKey); wc_curve25519_free(cliKey); @@ -48504,29 +48765,11 @@ static wc_test_ret_t ecies_x448_req_resp_test(WC_RNG* rng, byte encAlgo) goto done_free; } - XMEMSET(cliKey, 0, sizeof(*cliKey)); - XMEMSET(srvKey, 0, sizeof(*srvKey)); - - ret = wc_curve448_init(cliKey); - if (ret != 0) { - ret = WC_TEST_RET_ENC_EC(ret); - goto done_free; - } - ret = wc_curve448_init(srvKey); - if (ret != 0) { - wc_curve448_free(cliKey); - ret = WC_TEST_RET_ENC_EC(ret); + ret = ecies_make_x448_pair(rng, cliKey, srvKey); + if (ret != 0) goto done_free; - } - ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, cliKey); - if (ret == 0) - ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, srvKey); - if (ret != 0) - ret = WC_TEST_RET_ENC_EC(ret); - if (ret == 0) - ret = ecies_mont_req_resp_test(rng, encAlgo, ECC_X448, cliKey, - srvKey); + ret = ecies_mont_req_resp_test(rng, encAlgo, ECC_X448, cliKey, srvKey); wc_curve448_free(srvKey); wc_curve448_free(cliKey); @@ -48559,31 +48802,9 @@ static wc_test_ret_t ecies_x25519_test(WC_RNG* rng) goto done_free; } - XMEMSET(ephKey, 0, sizeof(*ephKey)); - XMEMSET(srvKey, 0, sizeof(*srvKey)); - - ret = wc_curve25519_init_ex(ephKey, HEAP_HINT, INVALID_DEVID); - if (ret != 0) { - ret = WC_TEST_RET_ENC_EC(ret); - goto done_free; - } - ret = wc_curve25519_init_ex(srvKey, HEAP_HINT, INVALID_DEVID); - if (ret != 0) { - wc_curve25519_free(ephKey); - ret = WC_TEST_RET_ENC_EC(ret); + ret = ecies_make_x25519_pair(rng, ephKey, srvKey); + if (ret != 0) goto done_free; - } - - ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, ephKey); - if (ret != 0) { - ret = WC_TEST_RET_ENC_EC(ret); - goto done; - } - ret = wc_curve25519_make_key(rng, CURVE25519_KEYSIZE, srvKey); - if (ret != 0) { - ret = WC_TEST_RET_ENC_EC(ret); - goto done; - } #if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) ret = ecies_mont_test(rng, ECC_X25519, ephKey, srvKey, @@ -48602,6 +48823,10 @@ static wc_test_ret_t ecies_x25519_test(WC_RNG* rng) goto done; } ret = wc_ecc_ctx_set_curve_id(ctx, ECC_X25519); +#ifdef WOLF_CRYPTO_CB + if (ret == 0) + ret = wc_ecc_ctx_set_dev_id(ctx, devId); +#endif if (ret == 0) { ret = wc_ecc_encrypt_ex2(ephKey, srvKey, msg, (word32)sizeof(msg), enc, &encSz, ctx, 0); @@ -48656,31 +48881,9 @@ static wc_test_ret_t ecies_x448_test(WC_RNG* rng) goto done_free; } - XMEMSET(ephKey, 0, sizeof(*ephKey)); - XMEMSET(srvKey, 0, sizeof(*srvKey)); - - ret = wc_curve448_init(ephKey); - if (ret != 0) { - ret = WC_TEST_RET_ENC_EC(ret); - goto done_free; - } - ret = wc_curve448_init(srvKey); - if (ret != 0) { - wc_curve448_free(ephKey); - ret = WC_TEST_RET_ENC_EC(ret); + ret = ecies_make_x448_pair(rng, ephKey, srvKey); + if (ret != 0) goto done_free; - } - - ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, ephKey); - if (ret != 0) { - ret = WC_TEST_RET_ENC_EC(ret); - goto done; - } - ret = wc_curve448_make_key(rng, CURVE448_KEY_SIZE, srvKey); - if (ret != 0) { - ret = WC_TEST_RET_ENC_EC(ret); - goto done; - } #if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) ret = ecies_mont_test(rng, ECC_X448, ephKey, srvKey, @@ -48697,6 +48900,10 @@ static wc_test_ret_t ecies_x448_test(WC_RNG* rng) goto done; } ret = wc_ecc_ctx_set_curve_id(ctx, ECC_X448); +#ifdef WOLF_CRYPTO_CB + if (ret == 0) + ret = wc_ecc_ctx_set_dev_id(ctx, devId); +#endif if (ret == 0) { ret = wc_ecc_encrypt_ex2(ephKey, srvKey, msg, (word32)sizeof(msg), enc, &encSz, ctx, 0); diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index 30641272b2..7ecebe1b24 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -290,11 +290,9 @@ typedef struct wc_CryptoInfo { } ecc_check_pub; /* distinct from ecc_check (priv-key cmp) */ #endif #ifdef HAVE_ECC_ENCRYPT - /* ECC keys only. ECIES over a Montgomery curve (ECC_X25519 / - * ECC_X448, selected with wc_ecc_ctx_set_curve_id) bypasses these - * callbacks entirely, since the key pointers below are typed for - * ecc_key. The curve25519 callback still fires for the shared - * secret itself. */ + /* WC_PK_TYPE_ECIES_ENCRYPT/DECRYPT: ECC keys. ECIES over a + * Montgomery curve arrives as the _MONT types with the + * eciesencrypt_mont/eciesdecrypt_mont members below instead. */ struct { ecc_key* privKey; ecc_key* pubKey; @@ -314,6 +312,29 @@ typedef struct wc_CryptoInfo { word32* outSz; ecEncCtx* ctx; } eciesdecrypt; + /* _MONT types: key pointers typed by curveId (curve25519_key* + * for ECC_X25519, curve448_key* for ECC_X448); ctx is never + * NULL. No compressed field on a Montgomery curve. */ + struct { + void* privKey; + void* pubKey; + const byte* msg; + word32 msgSz; + byte* out; + word32* outSz; + ecEncCtx* ctx; + int curveId; + } eciesencrypt_mont; + struct { + void* privKey; + void* pubKey; + const byte* msg; + word32 msgSz; + byte* out; + word32* outSz; + ecEncCtx* ctx; + int curveId; + } eciesdecrypt_mont; #endif /* HAVE_ECC_ENCRYPT */ #endif /* HAVE_ECC */ #ifdef HAVE_CURVE25519 @@ -920,6 +941,15 @@ WOLFSSL_LOCAL int wc_CryptoCb_EccCheckPubKey(ecc_key* key, int checkOrder, int checkPriv); #endif #ifdef HAVE_ECC_ENCRYPT +/* curveId types the void key pointers (ECC_CURVE_DEF/ECC_X25519/ECC_X448). + * The dispatch devId is resolved here and only here: ctx devId first, with + * with an ecc_key devId adopted only while the ctx devId is unset. */ +WOLFSSL_LOCAL int wc_CryptoCb_EciesEncrypt_ex(void* privKey, void* pubKey, + const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, + int compressed, int curveId); +WOLFSSL_LOCAL int wc_CryptoCb_EciesDecrypt_ex(void* privKey, void* pubKey, + const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, + int curveId); 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); diff --git a/wolfssl/wolfcrypt/ecc.h b/wolfssl/wolfcrypt/ecc.h index 1c5f549ae3..3a1e03a640 100644 --- a/wolfssl/wolfcrypt/ecc.h +++ b/wolfssl/wolfcrypt/ecc.h @@ -1059,16 +1059,9 @@ int wc_ecc_get_oid(word32 oidSum, const byte** oid, word32* oidSz); #ifdef HAVE_ECC_ENCRYPT /* ecc encrypt */ -/* ECIES can use a Montgomery-curve (X25519/X448) key in place of an ecc_key. - * The curve is selected on the context with wc_ecc_ctx_set_curve_id() and the - * keys are then handed to wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2(). - * - * The shared secret is always needed. The ephemeral public key is only - * exported into (and imported out of) the message when not in - * WOLFSSL_ECIES_OLD mode, so the import/export sub-guards only matter there. - * Note that the curve25519.h/curve448.h prototypes are not guarded even though - * the definitions are, so getting this wrong is a link error rather than a - * compile error. */ +/* ECIES with a Montgomery-curve key: select the type with + * wc_ecc_ctx_set_curve_id(), use wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2(). + * The import/export sub-guards only matter outside WOLFSSL_ECIES_OLD. */ #if defined(HAVE_CURVE25519) && defined(HAVE_CURVE25519_SHARED_SECRET) && \ (defined(WOLFSSL_ECIES_OLD) || \ (defined(HAVE_CURVE25519_KEY_EXPORT) && \ @@ -1142,15 +1135,27 @@ int wc_ecc_ctx_reset(ecEncCtx* ctx, WC_RNG* rng); /* reset for use again w/o al WOLFSSL_API int wc_ecc_ctx_set_algo(ecEncCtx* ctx, byte encAlgo, byte kdfAlgo, byte macAlgo); -/* Select the type of key the context operates on: ECC_CURVE_DEF (default, - * ecc_key), ECC_X25519 (curve25519_key) or ECC_X448 (curve448_key). A - * Montgomery key type has to be used with wc_ecc_encrypt_ex2()/ - * wc_ecc_decrypt_ex2(); wc_ecc_encrypt()/wc_ecc_decrypt() stay ECC-only. */ +/* Key type the context operates on: ECC_CURVE_DEF (ecc_key), ECC_X25519 + * (curve25519_key) or ECC_X448 (curve448_key). Montgomery types must use + * wc_ecc_encrypt_ex2()/wc_ecc_decrypt_ex2(); the typed calls stay ECC. */ WOLFSSL_API int wc_ecc_ctx_set_curve_id(ecEncCtx* ctx, int curveId); WOLFSSL_API int wc_ecc_ctx_get_curve_id(ecEncCtx* ctx, int* curveId); #ifdef WOLF_CRYPTO_CB +/* Device for the whole-operation ECIES crypto callback only (primitives + * route by wc_ecc_ctx_set_algo_dev_ids(), ECDH by the keys' own devIds). + * An ecc_key devId is adopted only while unset. Survives 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); +/* Per-algorithm devIds mirroring the wc_ecc_ctx_set_algo() trio (DEM + * cipher / KDF / MAC); INVALID_DEVID keeps that primitive in software. + * Survives wc_ecc_ctx_reset(). */ +WOLFSSL_API +int wc_ecc_ctx_set_algo_dev_ids(ecEncCtx* ctx, int encDevId, int kdfDevId, + int macDevId); /* Accessors for crypto-callback backends; only built with WOLF_CRYPTO_CB. */ WOLFSSL_API int wc_ecc_ctx_get_algo(ecEncCtx* ctx, byte* encAlgo, byte* kdfAlgo, @@ -1181,11 +1186,9 @@ WOLFSSL_ABI WOLFSSL_API int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx); -/* Generic entry points. The type of privKey/pubKey is whatever - * wc_ecc_ctx_set_curve_id() selected on the context: ecc_key* for - * ECC_CURVE_DEF, curve25519_key* for ECC_X25519, curve448_key* for ECC_X448. - * ctx is mandatory for the Montgomery curves - there is no way to recover the - * key type from a NULL context. */ +/* Generic entry points: privKey/pubKey are typed by the ctx's curve id + * (ecc_key*, curve25519_key* or curve448_key*). ctx is mandatory for the + * Montgomery curves - the key type is unrecoverable from NULL. */ WOLFSSL_API int wc_ecc_encrypt_ex2(void* privKey, void* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, int compressed); diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 3cce9b4c70..18840b28c2 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -1705,6 +1705,14 @@ enum wc_PkType { WC_PK_TYPE_ED448_VERIFY = 43, #undef _WC_PK_TYPE_MAX #define _WC_PK_TYPE_MAX WC_PK_TYPE_ED448_VERIFY +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) + /* ECIES with a Montgomery key type (curve25519_key / curve448_key), + * selected on the ecEncCtx with wc_ecc_ctx_set_curve_id(). */ + WC_PK_TYPE_ECIES_ENCRYPT_MONT = 44, + WC_PK_TYPE_ECIES_DECRYPT_MONT = 45, + #undef _WC_PK_TYPE_MAX + #define _WC_PK_TYPE_MAX WC_PK_TYPE_ECIES_DECRYPT_MONT +#endif WC_PK_TYPE_MAX = _WC_PK_TYPE_MAX }; From 1ea121e41505083984bc025d4e79296a42c0e379 Mon Sep 17 00:00:00 2001 From: night1rider Date: Mon, 24 Aug 2026 01:15:09 -0600 Subject: [PATCH 4/4] ECIES: address review findings in docs and tests --- doc/dox_comments/header_files/ecc.h | 28 +++++- tests/api/test_ecc.c | 150 ++++++++++++++++++++++++++++ tests/api/test_ecc.h | 4 + wolfcrypt/src/ecc.c | 1 - wolfcrypt/test/test.c | 10 ++ wolfssl/wolfcrypt/cryptocb.h | 3 + 6 files changed, 191 insertions(+), 5 deletions(-) diff --git a/doc/dox_comments/header_files/ecc.h b/doc/dox_comments/header_files/ecc.h index fc0f402882..74fbfbd2d1 100644 --- a/doc/dox_comments/header_files/ecc.h +++ b/doc/dox_comments/header_files/ecc.h @@ -1913,7 +1913,9 @@ int wc_ecc_ctx_set_algo(ecEncCtx* ctx, byte encAlgo, byte kdfAlgo, \return 0 Returned upon successfully setting the key type. \return NOT_COMPILED_IN Returned if the curve is compiled in but its ECIES - support is not. See WOLFSSL_ECIES_X25519 and WOLFSSL_ECIES_X448. + support is not. See WOLFSSL_ECIES_X25519 and WOLFSSL_ECIES_X448; the + umbrella macro WOLFSSL_ECIES_MONTGOMERY is defined whenever either + variant is enabled. \return BAD_FUNC_ARG Returned if the given ecEncCtx object is NULL or curveId is not one of the three values above. @@ -1973,6 +1975,15 @@ int wc_ecc_ctx_get_curve_id(ecEncCtx* ctx, int* curveId); wc_ecc_ctx_set_curve_id, the devId survives wc_ecc_ctx_reset. Only built when crypto callbacks are enabled. + A registered callback that services the whole operation by re-entering + the software path (wc_ecc_encrypt_ex2 / wc_ecc_decrypt_ex2) must first + clear this devId with wc_ecc_ctx_set_dev_id(ctx, INVALID_DEVID) - and + clear the ecc_key's own devId as well, or it is adopted right back - + then restore both afterwards. Otherwise the dispatch fires again + inside the forward and recurses until the stack overflows. Backends + written before the context devId existed cleared only the key's devId; + update them before their callers start passing a context. + \return 0 Returned upon successfully setting the device id. \return BAD_FUNC_ARG Returned if ctx is NULL. @@ -2026,6 +2037,9 @@ int wc_ecc_ctx_get_dev_id(ecEncCtx* ctx, int* devId); (wc_ecc_ctx_set_dev_id) routes only the whole-operation crypto callback and is never inherited by the primitives. The values survive wc_ecc_ctx_reset. Only built when crypto callbacks are enabled. + Earlier releases initialized the DEM cipher and MAC with the private + key's devId; that implicit routing is gone, so callers that relied on + it should pass the key's devId here explicitly. \return 0 Returned upon successfully setting the devIds. \return BAD_FUNC_ARG Returned if ctx is NULL. @@ -2245,7 +2259,9 @@ int wc_ecc_ctx_set_info(ecEncCtx* ctx, const byte* info, int sz); context devId is INVALID_DEVID (the key is never modified); a set context devId always wins. New code should prefer the context setter: one setting covers both directions of the exchange, and it is the only - route for the Montgomery key types. + route for the Montgomery key types. A callback that forwards to + software must clear the context devId (and the key's) around the + forward, then restore them - see wc_ecc_ctx_set_dev_id. _Example_ \code @@ -2331,7 +2347,9 @@ int wc_ecc_encrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, context devId is INVALID_DEVID (the key is never modified); a set context devId always wins. New code should prefer the context setter: one setting covers both directions of the exchange, and it is the only - route for the Montgomery key types. + route for the Montgomery key types. A callback that forwards to + software must clear the context devId (and the key's) around the + forward, then restore them - see wc_ecc_ctx_set_dev_id. _Example_ \code @@ -2411,7 +2429,9 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, context devId is INVALID_DEVID (the key is never modified); a set context devId always wins. New code should prefer the context setter: one setting covers both directions of the exchange, and it is the only - route for the Montgomery key types. + route for the Montgomery key types. A callback that forwards to + software must clear the context devId (and the key's) around the + forward, then restore them - see wc_ecc_ctx_set_dev_id. _Example_ \code diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 72a640cad5..2b0849a3b4 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -2515,6 +2515,8 @@ int test_wc_ecc_ecies_cryptocb(void) int i; int registered = 0; int cbInvoked = 0; + int reg0 = 0; + int cb0Invoked = 0; XMEMSET(&rng, 0, sizeof(rng)); XMEMSET(&cliKey, 0, sizeof(cliKey)); @@ -2569,6 +2571,10 @@ int test_wc_ecc_ecies_cryptocb(void) ecEncCtx* ctx0 = NULL; int ctxDev = INVALID_DEVID; ExpectNotNull(ctx0 = wc_ecc_ctx_new(0, &rng)); + ExpectIntEQ(wc_ecc_ctx_get_dev_id(NULL, &ctxDev), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_ctx_get_dev_id(ctx0, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); /* Adoption: nothing set on the ctx, the key's devId routes it. */ cbInvoked = 0; outSz = (word32)sizeof(out); @@ -2595,6 +2601,25 @@ int test_wc_ecc_ecies_cryptocb(void) ExpectIntEQ(cbInvoked, 1); /* Adoption went key -> ctx only: the key still has its own devId. */ ExpectIntEQ(cliKey.devId, cbDevId); + /* Device id 0 is a valid, SET devId: it survives a reset, is not + * replaced by key-devId adoption, and dispatches to device 0. */ + ExpectIntEQ(wc_CryptoCb_RegisterDevice(0, myEciesApiCryptoCb, + &cb0Invoked), 0); + if (EXPECT_SUCCESS()) + reg0 = 1; + ExpectIntEQ(wc_ecc_ctx_set_dev_id(ctx0, 0), 0); + ExpectIntEQ(wc_ecc_ctx_reset(ctx0, &rng), 0); + ExpectIntEQ(wc_ecc_ctx_get_dev_id(ctx0, &ctxDev), 0); + ExpectIntEQ(ctxDev, 0); + cbInvoked = 0; + cb0Invoked = 0; + outSz = (word32)sizeof(out); + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, + &outSz, ctx0), 0); + ExpectIntEQ(cb0Invoked, 1); + ExpectIntEQ(cbInvoked, 0); + ExpectIntEQ(wc_ecc_ctx_get_dev_id(ctx0, &ctxDev), 0); + ExpectIntEQ(ctxDev, 0); wc_ecc_ctx_free(ctx0); } @@ -2605,6 +2630,8 @@ int test_wc_ecc_ecies_cryptocb(void) DoExpectIntEQ(wc_FreeRng(&rng), 0); if (registered) wc_CryptoCb_UnRegisterDevice(cbDevId); + if (reg0) + wc_CryptoCb_UnRegisterDevice(0); #endif return EXPECT_RESULT(); } /* END test_wc_ecc_ecies_cryptocb */ @@ -3108,6 +3135,129 @@ int test_wc_ecc_ecies_gcm_no_rng(void) return EXPECT_RESULT(); } /* END test_wc_ecc_ecies_gcm_no_rng */ +/* The AES-CBC DEM sibling of test_wc_ecc_ecies_gcm_no_rng: in GEN_IV mode + * the per-message IV needs an RNG, so a NULL context and a key with no RNG + * set must fail with MISSING_RNG_E. */ +int test_wc_ecc_ecies_cbc_no_rng(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_ECIES_GEN_IV) && !defined(WOLFSSL_NO_MALLOC) && \ + defined(WOLFSSL_AES_128) + WC_RNG rng; + ecc_key cliKey; + ecc_key srvKey; + byte msg[32]; + byte out[256]; + word32 outSz = (word32)sizeof(out); + int i; + + 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_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); + + /* Deliberately do NOT call wc_ecc_set_rng() on cliKey, and pass a NULL + * context so no RNG is available for the CBC IV. */ + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, &outSz, + NULL), WC_NO_ERR_TRACE(MISSING_RNG_E)); + + wc_ecc_free(&srvKey); + wc_ecc_free(&cliKey); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ecies_cbc_no_rng */ + +/* The ECIES flow cannot resume FP_WOULDBLOCK, so a non-blocking X25519 key + * is rejected with NOT_COMPILED_IN on both the encrypt and decrypt paths. */ +int test_wc_ecc_ecies_x25519_nonblock(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && \ + defined(WOLFSSL_ECIES_X25519) && !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_ECIES_OLD) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_AES_128) && defined(HAVE_HKDF) && \ + !defined(WOLFSSL_NO_MALLOC) && defined(CURVE25519_SMALL) && \ + defined(WC_X25519_NONBLOCK) + WC_RNG rng; + WC_DECLARE_VAR(ephKey, curve25519_key, 1, HEAP_HINT); + WC_DECLARE_VAR(srvKey, curve25519_key, 1, HEAP_HINT); + x25519_nb_ctx_t nbEph; + x25519_nb_ctx_t nbSrv; + ecEncCtx* ctx = NULL; + const char* msg = "EccBlock Size 16"; + word32 msgSz = (word32)XSTRLEN("EccBlock Size 16"); + byte out[CURVE25519_PUB_KEY_SIZE + AES_BLOCK_SIZE + + AES_BLOCK_SIZE + WC_SHA256_DIGEST_SIZE]; + word32 outSz = (word32)sizeof(out); + byte out2[CURVE25519_PUB_KEY_SIZE + AES_BLOCK_SIZE + + AES_BLOCK_SIZE + WC_SHA256_DIGEST_SIZE]; + word32 out2Sz = (word32)sizeof(out2); + byte plain[sizeof("EccBlock Size 16") + AES_BLOCK_SIZE]; + word32 plainSz = (word32)sizeof(plain); + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&nbEph, 0, sizeof(nbEph)); + XMEMSET(&nbSrv, 0, sizeof(nbSrv)); + XMEMSET(out, 0, sizeof(out)); + + WC_ALLOC_VAR(ephKey, curve25519_key, 1, HEAP_HINT); + WC_ALLOC_VAR(srvKey, curve25519_key, 1, HEAP_HINT); +#ifdef WC_DECLARE_VAR_IS_HEAP_ALLOC + ExpectNotNull(ephKey); + ExpectNotNull(srvKey); +#endif + if (WC_VAR_OK(ephKey)) { + XMEMSET(ephKey, 0, sizeof(*ephKey)); + } + if (WC_VAR_OK(srvKey)) { + XMEMSET(srvKey, 0, sizeof(*srvKey)); + } + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_curve25519_init(ephKey), 0); + ExpectIntEQ(wc_curve25519_init(srvKey), 0); + ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, ephKey), 0); + ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, srvKey), 0); + + ExpectNotNull(ctx = wc_ecc_ctx_new(0, &rng)); + ExpectIntEQ(wc_ecc_ctx_set_curve_id(ctx, ECC_X25519), 0); + + /* A good message first, for the decrypt-side check below. */ + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, (const byte*)msg, msgSz, + out, &outSz, ctx, 0), 0); + + /* Encrypt: the non-blocking ephemeral key is rejected. */ + ExpectIntEQ(wc_curve25519_set_nonblock(ephKey, &nbEph), 0); + ExpectIntEQ(wc_ecc_ctx_reset(ctx, &rng), 0); + ExpectIntEQ(wc_ecc_encrypt_ex2(ephKey, srvKey, (const byte*)msg, msgSz, + out2, &out2Sz, ctx, 0), WC_NO_ERR_TRACE(NOT_COMPILED_IN)); + + /* Decrypt: the non-blocking recipient key is rejected the same way. */ + ExpectIntEQ(wc_curve25519_set_nonblock(srvKey, &nbSrv), 0); + ExpectIntEQ(wc_ecc_ctx_reset(ctx, &rng), 0); + ExpectIntEQ(wc_ecc_decrypt_ex2(srvKey, NULL, out, outSz, plain, &plainSz, + ctx), WC_NO_ERR_TRACE(NOT_COMPILED_IN)); + + wc_ecc_ctx_free(ctx); + wc_curve25519_free(srvKey); + wc_curve25519_free(ephKey); + WC_FREE_VAR(srvKey, HEAP_HINT); + WC_FREE_VAR(ephKey, HEAP_HINT); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ecies_x25519_nonblock */ + /* * Testing wc_ecc_del_point() and wc_ecc_new_point() */ diff --git a/tests/api/test_ecc.h b/tests/api/test_ecc.h index 8389463505..011575ecbc 100644 --- a/tests/api/test_ecc.h +++ b/tests/api/test_ecc.h @@ -61,6 +61,8 @@ int test_wc_ecc_ecies_x25519(void); int test_wc_ecc_ecies_x448(void); int test_wc_ecc_ecies_gcm(void); int test_wc_ecc_ecies_gcm_no_rng(void); +int test_wc_ecc_ecies_cbc_no_rng(void); +int test_wc_ecc_ecies_x25519_nonblock(void); int test_wc_ecc_ecies_cryptocb(void); int test_wc_ecc_ctx_algo_dev_ids(void); int test_wc_ecc_ecies_cryptocb_x25519(void); @@ -117,6 +119,8 @@ int test_wc_EccDecisionCoverage4(void); TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_x448), \ 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_cbc_no_rng), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_x25519_nonblock), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_cryptocb), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ctx_algo_dev_ids), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_cryptocb_x25519), \ diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 623c0f0edf..95232062be 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -15945,7 +15945,6 @@ static void ecies_key_free(ecEncCtx* ctx, void* key) } #endif /* !WOLFSSL_ECIES_OLD */ -/* Derive the ECIES shared secret. */ /* Derive the ECIES shared secret. The exchange routes by the keys' own * devIds - the per-primitive ECDH crypto callbacks read them directly - so * the ECIES and per-algorithm devIds on the context take no part here. */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 6d3f37a8c6..766dd18172 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -48114,6 +48114,16 @@ static wc_test_ret_t ecies_mont_test(WC_RNG* rng, int curveId, void* ephKey, } } +#ifdef WOLFSSL_ECIES_OLD + /* OLD mode carries no ephemeral key in the message, so a NULL pubKey + * must be rejected before anything else runs. */ + if (wc_ecc_decrypt_ex2(srvKey, NULL, enc, encSz, plain, &plainSz, + ctx) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = WC_TEST_RET_ENC_NC; + goto done; + } +#endif + XMEMSET(plain, 0, MAX_ECIES_TEST_SZ); plainSz = MAX_ECIES_TEST_SZ; ret = wc_ecc_decrypt_ex2(srvKey, decPubKey, enc, encSz, plain, &plainSz, diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index 7ecebe1b24..d94906f668 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -944,6 +944,9 @@ WOLFSSL_LOCAL int wc_CryptoCb_EccCheckPubKey(ecc_key* key, int checkOrder, /* curveId types the void key pointers (ECC_CURVE_DEF/ECC_X25519/ECC_X448). * The dispatch devId is resolved here and only here: ctx devId first, with * with an ecc_key devId adopted only while the ctx devId is unset. */ +/* A callback that services the operation by re-entering the software path + * must clear the ctx devId (and an ecc_key's own devId, or it is adopted + * right back) around the forward, then restore both - else it recurses. */ WOLFSSL_LOCAL int wc_CryptoCb_EciesEncrypt_ex(void* privKey, void* pubKey, const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, int compressed, int curveId);