From 8d3368cbfa90ddb8f45816bfb51f1f470bec7820 Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 17:28:35 -0700 Subject: [PATCH 01/12] Refactor wolfSSL_EVP_DigestUpdate into a chunked function so sizes above word32 max are handled correctly. (F-7103) --- wolfcrypt/src/evp.c | 76 +++++++++++++++++++++++++++++-------------- wolfssl/openssl/evp.h | 2 +- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index a85d7603d79..dee6ba62744 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -4895,22 +4895,6 @@ static int wolfSSL_evp_digest_pk_init(WOLFSSL_EVP_MD_CTX *ctx, return WOLFSSL_SUCCESS; } -/* Update an EVP_DigestSign/Verify operation. - * Update a digest for RSA and ECC keys, or HMAC for HMAC key. - */ -static int wolfssl_evp_digest_pk_update(WOLFSSL_EVP_MD_CTX *ctx, - const void *d, unsigned int cnt) -{ - if (ctx->isHMAC) { - if (wc_HmacUpdate(&ctx->hash.hmac, (const byte *)d, cnt) != 0) - return WOLFSSL_FAILURE; - - return WOLFSSL_SUCCESS; - } - else - return wolfSSL_EVP_DigestUpdate(ctx, d, cnt); -} - /* Finalize an EVP_DigestSign/Verify operation - common part only. * Finalize a digest for RSA and ECC keys, or HMAC for HMAC key. * Copies the digest so that you can keep updating. @@ -5053,14 +5037,14 @@ int wolfSSL_EVP_DigestSignInit(WOLFSSL_EVP_MD_CTX *ctx, int wolfSSL_EVP_DigestSignUpdate(WOLFSSL_EVP_MD_CTX *ctx, const void *d, - unsigned int cnt) + size_t cnt) { WOLFSSL_ENTER("EVP_DigestSignUpdate"); if (ctx == NULL || d == NULL) return WOLFSSL_FAILURE; - return wolfssl_evp_digest_pk_update(ctx, d, cnt); + return wolfSSL_EVP_DigestUpdate(ctx, d, cnt); } int wolfSSL_EVP_DigestSignFinal(WOLFSSL_EVP_MD_CTX *ctx, unsigned char *sig, @@ -5179,7 +5163,7 @@ int wolfSSL_EVP_DigestSign(WOLFSSL_EVP_MD_CTX *ctx, unsigned char *sigret, if (sigret != NULL) { if (tbs == NULL) return WOLFSSL_FAILURE; - if (wolfSSL_EVP_DigestSignUpdate(ctx, tbs, (unsigned int)tbslen) + if (wolfSSL_EVP_DigestSignUpdate(ctx, tbs, tbslen) != WOLFSSL_SUCCESS) return WOLFSSL_FAILURE; } @@ -5209,7 +5193,7 @@ int wolfSSL_EVP_DigestVerifyUpdate(WOLFSSL_EVP_MD_CTX *ctx, const void *d, if (ctx == NULL || d == NULL) return WOLFSSL_FAILURE; - return wolfssl_evp_digest_pk_update(ctx, d, (unsigned int)cnt); + return wolfSSL_EVP_DigestUpdate(ctx, d, cnt); } @@ -11710,15 +11694,14 @@ int wolfSSL_EVP_MD_type(const WOLFSSL_EVP_MD* type) return ret; } - /* WOLFSSL_SUCCESS on ok, WOLFSSL_FAILURE on failure */ - int wolfSSL_EVP_DigestUpdate(WOLFSSL_EVP_MD_CTX* ctx, const void* data, - size_t sz) + /* Update the digest with at most a word32 of data. + * WOLFSSL_SUCCESS on ok, WOLFSSL_FAILURE on failure */ + static int wolfssl_evp_digest_update_chunk(WOLFSSL_EVP_MD_CTX* ctx, + const void* data, word32 sz) { int ret = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); enum wc_HashType macType; - WOLFSSL_ENTER("EVP_DigestUpdate"); - macType = EvpMd2MacType(wolfSSL_EVP_MD_CTX_md(ctx)); switch (macType) { case WC_HASH_TYPE_MD4: @@ -11877,6 +11860,49 @@ int wolfSSL_EVP_MD_type(const WOLFSSL_EVP_MD* type) return ret; } + /* WOLFSSL_SUCCESS on ok, WOLFSSL_FAILURE on failure */ + int wolfSSL_EVP_DigestUpdate(WOLFSSL_EVP_MD_CTX* ctx, const void* data, + size_t sz) + { + int ret; + + WOLFSSL_ENTER("EVP_DigestUpdate"); + + if (ctx == NULL) + return WOLFSSL_FAILURE; + + /* The underlying update functions take a word32 length. Feed the data + * in chunks so the whole of sz is hashed instead of sz mod 2^32. + * Detect the narrowing by round-tripping rather than comparing against + * a constant, so this holds for any width of size_t. */ + do { + word32 chunk = (word32)sz; + if ((size_t)chunk != sz) + chunk = WC_MAX_UINT_OF(word32); + + #ifndef NO_HMAC + if (ctx->isHMAC) { + if (wc_HmacUpdate(&ctx->hash.hmac, (const byte*)data, + chunk) != 0) { + return WOLFSSL_FAILURE; + } + } + else + #endif + { + /* pass the sub-call's code through, e.g. NOT_COMPILED_IN */ + ret = wolfssl_evp_digest_update_chunk(ctx, data, chunk); + if (ret != WOLFSSL_SUCCESS) + return ret; + } + + data = (const byte*)data + chunk; + sz -= chunk; + } while (sz > 0); + + return WOLFSSL_SUCCESS; + } + /* WOLFSSL_SUCCESS on ok */ static int wolfSSL_EVP_DigestFinal_Common(WOLFSSL_EVP_MD_CTX* ctx, unsigned char* md, unsigned int* s, enum wc_HashType macType) diff --git a/wolfssl/openssl/evp.h b/wolfssl/openssl/evp.h index 74e8a8ba126..97026369dea 100644 --- a/wolfssl/openssl/evp.h +++ b/wolfssl/openssl/evp.h @@ -865,7 +865,7 @@ WOLFSSL_API int wolfSSL_EVP_DigestFinal_ex(WOLFSSL_EVP_MD_CTX* ctx, WOLFSSL_API int wolfSSL_EVP_DigestFinalXOF(WOLFSSL_EVP_MD_CTX* ctx, unsigned char* md, size_t sz); WOLFSSL_API int wolfSSL_EVP_DigestSignUpdate(WOLFSSL_EVP_MD_CTX *ctx, - const void *d, unsigned int cnt); + const void *d, size_t cnt); WOLFSSL_API int wolfSSL_EVP_DigestSignFinal(WOLFSSL_EVP_MD_CTX *ctx, unsigned char *sig, size_t *siglen); WOLFSSL_API int wolfSSL_EVP_DigestSign(WOLFSSL_EVP_MD_CTX *ctx, From 7b330ea05b3830a2de512abf5e1a71cf6a9c7f48 Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 17:32:07 -0700 Subject: [PATCH 02/12] Always use new key in wc_ecc_decrypt to avoid freeing passed in key. (F-7105) --- doc/dox_comments/header_files/ecc.h | 13 +++-- tests/api/test_ecc.c | 73 +++++++++++++++++++++++++++++ tests/api/test_ecc.h | 2 + wolfcrypt/src/ecc.c | 15 ++---- 4 files changed, 88 insertions(+), 15 deletions(-) diff --git a/doc/dox_comments/header_files/ecc.h b/doc/dox_comments/header_files/ecc.h index 74c700321d5..f46299c3445 100644 --- a/doc/dox_comments/header_files/ecc.h +++ b/doc/dox_comments/header_files/ecc.h @@ -2151,9 +2151,9 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, the encryption type specified by ctx. \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 + \return BAD_FUNC_ARG Returned if privKey, msg, msgSz, out, or outSz + are NULL (or pubKey is NULL when built with WOLFSSL_ECIES_OLD), or + the ctx object specifies an unsupported encryption type \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 @@ -2166,8 +2166,11 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, \param privKey pointer to the ecc_key object containing the private key to use for decryption - \param pubKey pointer to the ecc_key object containing the public - key of the peer with whom one wishes to communicate + \param pubKey only used when built with WOLFSSL_ECIES_OLD: pointer to + the ecc_key object containing the public key of the peer with whom one + wishes to communicate. In the default message format the sender's + ephemeral public key is read from the start of msg instead and pubKey + is ignored (it may be NULL and is left unmodified) \param msg pointer to the buffer holding the ciphertext to decrypt \param msgSz size of the buffer to decrypt \param out pointer to the buffer in which to store the decrypted plaintext diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 95ed354da47..7a9d9dabc23 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -1756,6 +1756,79 @@ int test_wc_ecc_encryptDecrypt(void) return EXPECT_RESULT(); } /* END test_wc_ecc_encryptDecrypt */ +/* + * In the default ECIES message format the sender's ephemeral public key is + * carried in the message, so wc_ecc_decrypt() must not free or overwrite a + * caller-supplied pubKey object. Confirm the object is byte-for-byte preserved + * across a decrypt. + */ +int test_wc_ecc_decrypt_pubkey_preserved(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_ECIES_OLD) && defined(HAVE_ECC_KEY_EXPORT) && \ + defined(HAVE_ECC_KEY_IMPORT) && \ + (defined(HAVE_AES_CBC) || \ + (defined(HAVE_AESGCM) && (defined(WOLFSSL_ECIES_GEN_IV) || \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE)))) && defined(WOLFSSL_AES_128) + ecc_key cliKey; + ecc_key srvKey; + ecc_key pubKey; + WC_RNG rng; + const char* msg = "EccBlock Size 16"; + word32 msgSz = (word32)XSTRLEN("EccBlock Size 16"); + byte out[KEY20 * 2 + 1 + (sizeof("EccBlock Size 16") - 1) + + WC_SHA256_DIGEST_SIZE]; + word32 outSz = (word32)sizeof(out); + byte plain[sizeof("EccBlock Size 16")]; + word32 plainSz = (word32)sizeof(plain); + byte before[ECC_BUFSIZE]; + byte after[ECC_BUFSIZE]; + word32 beforeSz = (word32)sizeof(before); + word32 afterSz = (word32)sizeof(after); + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&cliKey, 0, sizeof(cliKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + XMEMSET(&pubKey, 0, sizeof(pubKey)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_ecc_init(&cliKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, KEY20, &cliKey), 0); + ExpectIntEQ(wc_ecc_init(&srvKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, KEY20, &srvKey), 0); + ExpectIntEQ(wc_ecc_init(&pubKey), 0); + /* Load a public key distinct from the sender's ephemeral (embedded in the + * message) so that overwriting pubKey would be detectable. */ + ExpectIntEQ(wc_ecc_export_x963(&srvKey, before, &beforeSz), 0); + ExpectIntEQ(wc_ecc_import_x963(before, beforeSz, &pubKey), 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(&srvKey, &rng), 0); + ExpectIntEQ(wc_ecc_set_rng(&cliKey, &rng), 0); +#endif + + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, (byte*)msg, msgSz, out, + &outSz, NULL), 0); + ExpectIntEQ(wc_ecc_decrypt(&srvKey, &pubKey, out, outSz, plain, &plainSz, + NULL), 0); + ExpectIntEQ(XMEMCMP(msg, plain, msgSz), 0); + + /* the caller's pubKey object must be unchanged after the decrypt */ + ExpectIntEQ(wc_ecc_export_x963(&pubKey, after, &afterSz), 0); + ExpectIntEQ(afterSz, beforeSz); + ExpectIntEQ(XMEMCMP(before, after, beforeSz), 0); + + wc_ecc_free(&pubKey); + wc_ecc_free(&srvKey); + wc_ecc_free(&cliKey); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_decrypt_pubkey_preserved */ + /* * 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 31d475f160d..cb3889ea594 100644 --- a/tests/api/test_ecc.h +++ b/tests/api/test_ecc.h @@ -55,6 +55,7 @@ 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_encryptDecrypt(void); +int test_wc_ecc_decrypt_pubkey_preserved(void); int test_wc_ecc_ecies_gcm(void); int test_wc_ecc_ecies_gcm_no_rng(void); int test_wc_ecc_ecies_cryptocb(void); @@ -104,6 +105,7 @@ int test_wc_EccDecisionCoverage4(void); 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_encryptDecrypt), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_decrypt_pubkey_preserved), \ 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/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 3a56e8159e6..29c01e3ffa6 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -16092,17 +16092,12 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #endif #ifndef WOLFSSL_ECIES_OLD - if (pubKey == NULL) { - WC_ALLOC_VAR_EX(peerKey, ecc_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); - } + /* The ephemeral public key comes from the message; parse it into the + * local key object so a caller-supplied pubKey is left untouched. */ + WC_ALLOC_VAR_EX(peerKey, ecc_key, 1, ctx->heap, + DYNAMIC_TYPE_ECC_BUFFER, ret=MEMORY_E); if (ret == 0) { + pubKey = peerKey; ret = wc_ecc_init_ex(pubKey, privKey->heap, INVALID_DEVID); } if (ret == 0) { From b4ee21158aefe5cf4c71562fd7fba3fc84ea9b0e Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 17:34:35 -0700 Subject: [PATCH 03/12] Zero out the digest in SHA256/512 generation functions. (F-7135) --- wolfcrypt/src/random.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index a600356d9e4..21ac9c83c2d 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -795,6 +795,7 @@ static int Hash_gen(DRBG_internal* drbg, byte* out, word32 outSz, const byte* V) XMEMCPY(data, V, DRBG_SEED_LEN); #ifdef WOLFSSL_CHECK_MEM_ZERO wc_MemZero_Add("Hash_gen data", data, DRBG_SEED_LEN); + wc_MemZero_Add("Hash_gen digest", digest, WC_SHA256_DIGEST_SIZE); #endif for (i = 0; i < len; i++) { #ifndef WOLFSSL_SMALL_STACK_CACHE @@ -832,9 +833,11 @@ static int Hash_gen(DRBG_internal* drbg, byte* out, word32 outSz, const byte* V) } } ForceZero(data, DRBG_SEED_LEN); + ForceZero(digest, WC_SHA256_DIGEST_SIZE); #if (!defined(WOLFSSL_SMALL_STACK) || defined(WOLFSSL_SMALL_STACK_CACHE)) && \ defined(WOLFSSL_CHECK_MEM_ZERO) wc_MemZero_Check(data, DRBG_SEED_LEN); + wc_MemZero_Check(digest, WC_SHA256_DIGEST_SIZE); #endif #ifndef WOLFSSL_SMALL_STACK_CACHE @@ -1395,6 +1398,7 @@ static int Hash512_gen(DRBG_SHA512_internal* drbg, byte* out, word32 outSz, XMEMCPY(data, V, DRBG_SHA512_SEED_LEN); #ifdef WOLFSSL_CHECK_MEM_ZERO wc_MemZero_Add("Hash512_gen data", data, DRBG_SHA512_SEED_LEN); + wc_MemZero_Add("Hash512_gen digest", digest, WC_SHA512_DIGEST_SIZE); #endif for (i = 0; i < len; i++) { #ifndef WOLFSSL_SMALL_STACK_CACHE @@ -1431,9 +1435,11 @@ static int Hash512_gen(DRBG_SHA512_internal* drbg, byte* out, word32 outSz, } } ForceZero(data, DRBG_SHA512_SEED_LEN); + ForceZero(digest, WC_SHA512_DIGEST_SIZE); #if (!defined(WOLFSSL_SMALL_STACK) || defined(WOLFSSL_SMALL_STACK_CACHE)) && \ defined(WOLFSSL_CHECK_MEM_ZERO) wc_MemZero_Check(data, DRBG_SHA512_SEED_LEN); + wc_MemZero_Check(digest, WC_SHA512_DIGEST_SIZE); #endif #ifndef WOLFSSL_SMALL_STACK_CACHE From b88319c0adba6c7099409ce7fc3040240d9ec584 Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 17:36:02 -0700 Subject: [PATCH 04/12] Zero out SRP user and key when overwriting them. (F-7400) --- wolfcrypt/src/srp.c | 14 ++++++++++++++ wolfcrypt/test/test.c | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/wolfcrypt/src/srp.c b/wolfcrypt/src/srp.c index 44c48d45ef0..13d70e545ea 100644 --- a/wolfcrypt/src/srp.c +++ b/wolfcrypt/src/srp.c @@ -326,6 +326,13 @@ int wc_SrpSetUsername(Srp* srp, const byte* username, word32 size) if (!srp || !username) return BAD_FUNC_ARG; + if (srp->user) { + ForceZero(srp->user, srp->userSz); + XFREE(srp->user, srp->heap, DYNAMIC_TYPE_SRP); + srp->user = NULL; + srp->userSz = 0; + } + /* +1 for NULL char */ srp->user = (byte*)XMALLOC(size + 1, srp->heap, DYNAMIC_TYPE_SRP); if (srp->user == NULL) @@ -676,6 +683,13 @@ static int wc_SrpSetKey(Srp* srp, byte* secret, word32 size) XMEMSET(digest, 0, SRP_MAX_DIGEST_SIZE); + if (srp->key) { + ForceZero(srp->key, srp->keySz); + XFREE(srp->key, srp->heap, DYNAMIC_TYPE_SRP); + srp->key = NULL; + srp->keySz = 0; + } + srp->key = (byte*)XMALLOC(2 * (word32)digestSz, srp->heap, DYNAMIC_TYPE_SRP); if (srp->key == NULL) return MEMORY_E; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index e2fa72b69ec..2cfa04733b7 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -34067,6 +34067,14 @@ static wc_test_ret_t srp_test_digest(SrpType dgstType) if (!r) r = wc_SrpVerifyPeersProof(cli, serverProof, serverProofSz); + /* Regression: a second wc_SrpSetUsername()/wc_SrpComputeKey() must release + * (and, for the key, zeroise) the buffer from the first call rather than + * leaking it. The exchange above is already verified; these repeat calls + * exercise the overwrite path so ASan flags a leak if it regresses. */ + if (!r) r = wc_SrpSetUsername(cli, username, usernameSz); + if (!r) r = wc_SrpComputeKey(cli, clientPubKey, clientPubKeySz, + serverPubKey, serverPubKeySz); + /* Negative test: corrupted proof must be rejected with SRP_VERIFY_E. */ if (!r) { int rNeg; From 7ab3105e36f895077a1e1a1435596dc13c75c136 Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 17:38:09 -0700 Subject: [PATCH 05/12] Avoid suppressing errors from wc_RNG_GenerateBlock in wc_rng_bank_reseed. (F-7414) --- wolfcrypt/src/rng_bank.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/wolfcrypt/src/rng_bank.c b/wolfcrypt/src/rng_bank.c index c17e30eda11..92a380b7bf9 100644 --- a/wolfcrypt/src/rng_bank.c +++ b/wolfcrypt/src/rng_bank.c @@ -997,11 +997,8 @@ WOLFSSL_API int wc_rng_bank_reseed(struct wc_rng_bank *bank, "for DRBG #%d returned %d.", n, ret); #endif (void)wc_rng_bank_checkin(bank, &drbg); - if ((ret == WC_NO_ERR_TRACE(WC_TIMEOUT_E)) || - (ret == WC_NO_ERR_TRACE(INTERRUPTED_E))) - { + if (ret != 0) return ret; - } ret = WC_CHECK_FOR_INTR_SIGNALS(); if (ret == WC_NO_ERR_TRACE(INTERRUPTED_E)) return ret; From 0687e2ae5b24e2a69a92b4ab96382608b81fa6fd Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 17:39:37 -0700 Subject: [PATCH 06/12] Use subtraction-based comparison in EVP fillBuff. (F-7446) --- wolfcrypt/src/evp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index dee6ba62744..daf0989c3bf 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -594,7 +594,7 @@ static int fillBuff(WOLFSSL_EVP_CIPHER_CTX *ctx, const unsigned char *in, int sz if (sz > 0) { int fill; - if ((sz+ctx->bufUsed) > ctx->block_size) { + if (sz > ctx->block_size - ctx->bufUsed) { fill = ctx->block_size - ctx->bufUsed; } else { fill = sz; From 66563d3934f62e1f186d4733dcba0f86465d9169 Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 17:40:53 -0700 Subject: [PATCH 07/12] Correct decOidSz in wc_ecc_get_curve_id_from_oid. (F-7623) --- tests/api/test_ecc.c | 19 +++++++++++++++++++ wolfcrypt/src/ecc.c | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 7a9d9dabc23..07f6c1987fb 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -2550,6 +2550,25 @@ int test_wc_ecc_get_curve_id_from_oid(void) ExpectIntEQ(wc_ecc_get_curve_id_from_oid(oid, 0), ECC_CURVE_INVALID); /* Good Case */ ExpectIntEQ(wc_ecc_get_curve_id_from_oid(oid, len), ECC_SECP256R1); + +#ifdef HAVE_OID_DECODING + { + /* An OID with more sub-identifiers than the internal decode array can + * hold must be rejected, not decoded past the end of that array. The + * first byte decodes to two arcs and every following byte to one, so + * MAX_OID_SZ bytes yield well over the MAX_OID_SZ/2 element capacity. + * Run under ASan to catch a regression. */ + byte longOid[MAX_OID_SZ]; + word32 i; + + longOid[0] = 0x2A; + for (i = 1; i < (word32)sizeof(longOid); i++) + longOid[i] = 0x01; + + ExpectIntEQ(wc_ecc_get_curve_id_from_oid(longOid, sizeof(longOid)), + WC_NO_ERR_TRACE(BUFFER_E)); + } +#endif #endif return EXPECT_RESULT(); } /* END test_wc_ecc_get_curve_id_from_oid */ diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 29c01e3ffa6..81c8c54afc0 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -4642,7 +4642,8 @@ int wc_ecc_get_curve_id_from_oid(const byte* oid, word32 len) return BAD_FUNC_ARG; #ifdef HAVE_OID_DECODING - decOidSz = (word32)sizeof(decOid); + /* in elements, not bytes */ + decOidSz = (word32)(sizeof(decOid) / sizeof(decOid[0])); ret = DecodeObjectId(oid, len, decOid, &decOidSz); if (ret != 0) { return ret; From 4148b3300b853faaeb5a555e765ae5f697c6a829 Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 17:44:47 -0700 Subject: [PATCH 08/12] Avoid clamping in EncodeAttributes when attribute size exceeds capacity. (F-7624) --- wolfcrypt/src/pkcs7.c | 8 ++- wolfcrypt/test/test.c | 124 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 3 deletions(-) diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 9ef0130ebf7..62b89a2c837 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -1795,16 +1795,18 @@ static int EncodeAttributes(EncodedAttrib* ea, int eaSz, PKCS7Attrib* attribs, int attribsSz) { int i; - int maxSz; word32 allAttribsSz = 0; if (eaSz < 0 || attribsSz < 0) { return BAD_FUNC_ARG; } - maxSz = (int)min((word32)eaSz, (word32)attribsSz); + /* every attribute must fit in the output array; do not silently drop */ + if (attribsSz > eaSz) { + return BUFFER_E; + } - for (i = 0; i < maxSz; i++) + for (i = 0; i < attribsSz; i++) { word32 attribSz = 0; word32 boundSz = 0; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 2cfa04733b7..8aaae1578d9 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -70152,6 +70152,123 @@ static wc_test_ret_t pkcs7authenveloped_run_vectors(byte* rsaCert, word32 rsaCer return ret; } +#if !defined(NO_RSA) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(HAVE_AES_KEYWRAP) && defined(WOLFSSL_AES_128) +/* Boundary test for the fixed-size auth/unauth attribute arrays in + * wc_PKCS7_EncodeAuthEnvelopedData(): filling them to capacity must encode, + * while requesting one attribute more than fits must fail cleanly instead of + * writing past the arrays. Run under ASan to catch a regression. */ +static wc_test_ret_t pkcs7_authenv_attribs_boundary_test(byte* rsaCert, + word32 rsaCertSz, byte* rsaPrivKey, word32 rsaPrivKeySz) +{ + wc_test_ret_t ret = 0; + wc_PKCS7* pkcs7 = NULL; + byte* enveloped = NULL; + int envSz; + byte content[] = "authenv attribs boundary test"; + + /* eight distinct, well-formed attribute TLVs (OID + PrintableString) */ + static const byte oid0[] = { 0x06,0x03, 0x55,0x04,0x03 }; + static const byte oid1[] = { 0x06,0x03, 0x55,0x04,0x04 }; + static const byte oid2[] = { 0x06,0x03, 0x55,0x04,0x05 }; + static const byte oid3[] = { 0x06,0x03, 0x55,0x04,0x06 }; + static const byte oid4[] = { 0x06,0x03, 0x55,0x04,0x07 }; + static const byte oid5[] = { 0x06,0x03, 0x55,0x04,0x08 }; + static const byte oid6[] = { 0x06,0x03, 0x55,0x04,0x09 }; + static const byte oid7[] = { 0x06,0x03, 0x55,0x04,0x0a }; + static const byte val[] = { 0x13,0x01, 0x30 }; + PKCS7Attrib attribs[8] = { + { oid0, sizeof(oid0), val, sizeof(val) }, + { oid1, sizeof(oid1), val, sizeof(val) }, + { oid2, sizeof(oid2), val, sizeof(val) }, + { oid3, sizeof(oid3), val, sizeof(val) }, + { oid4, sizeof(oid4), val, sizeof(val) }, + { oid5, sizeof(oid5), val, sizeof(val) }, + { oid6, sizeof(oid6), val, sizeof(val) }, + { oid7, sizeof(oid7), val, sizeof(val) } + }; + + enveloped = (byte*)XMALLOC(PKCS7_BUF_SIZE, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (enveloped == NULL) + return WC_TEST_RET_ENC_ERRNO; + + /* contentOID == DATA so no contentType attribute is auto-added; all + * MAX_AUTH_ATTRIBS_SZ slots are available to the user attributes. */ + + /* exactly MAX_AUTH_ATTRIBS_SZ authenticated attributes: must encode */ + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + ret = wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + pkcs7->content = content; + pkcs7->contentSz = (word32)XSTRLEN((char*)content); + pkcs7->contentOID = DATA; + pkcs7->encryptOID = AES128GCMb; + pkcs7->privateKey = rsaPrivKey; + pkcs7->privateKeySz = rsaPrivKeySz; + pkcs7->authAttribs = attribs; + pkcs7->authAttribsSz = MAX_AUTH_ATTRIBS_SZ; + envSz = wc_PKCS7_EncodeAuthEnvelopedData(pkcs7, enveloped, PKCS7_BUF_SIZE); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + if (envSz <= 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(envSz), out); + + /* one more authenticated attribute than fits: must fail, not overrun */ + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + ret = wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + pkcs7->content = content; + pkcs7->contentSz = (word32)XSTRLEN((char*)content); + pkcs7->contentOID = DATA; + pkcs7->encryptOID = AES128GCMb; + pkcs7->privateKey = rsaPrivKey; + pkcs7->privateKeySz = rsaPrivKeySz; + pkcs7->authAttribs = attribs; + pkcs7->authAttribsSz = MAX_AUTH_ATTRIBS_SZ + 1; + envSz = wc_PKCS7_EncodeAuthEnvelopedData(pkcs7, enveloped, PKCS7_BUF_SIZE); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + if (envSz >= 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* one more unauthenticated attribute than fits: must fail, not overrun */ + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + ret = wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + pkcs7->content = content; + pkcs7->contentSz = (word32)XSTRLEN((char*)content); + pkcs7->contentOID = DATA; + pkcs7->encryptOID = AES128GCMb; + pkcs7->privateKey = rsaPrivKey; + pkcs7->privateKeySz = rsaPrivKeySz; + pkcs7->unauthAttribs = attribs; + pkcs7->unauthAttribsSz = MAX_UNAUTH_ATTRIBS_SZ + 1; + envSz = wc_PKCS7_EncodeAuthEnvelopedData(pkcs7, enveloped, PKCS7_BUF_SIZE); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + if (envSz >= 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + ret = 0; + +out: + if (pkcs7 != NULL) + wc_PKCS7_Free(pkcs7); + XFREE(enveloped, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif /* RSA + AESGCM + keywrap + AES128 */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pkcs7authenveloped_test(void) { wc_test_ret_t ret = 0; @@ -70229,6 +70346,13 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pkcs7authenveloped_test(void) eccCert, (word32)eccCertSz, eccPrivKey, (word32)eccPrivKeySz); +#if !defined(NO_RSA) && !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(HAVE_AES_KEYWRAP) && defined(WOLFSSL_AES_128) + if (ret == 0) + ret = pkcs7_authenv_attribs_boundary_test(rsaCert, (word32)rsaCertSz, + rsaPrivKey, (word32)rsaPrivKeySz); +#endif + #ifndef NO_RSA XFREE(rsaCert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); XFREE(rsaPrivKey, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); From 809dcfb3bf1e90ef149a99711086d3af9d240e2e Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 17:52:44 -0700 Subject: [PATCH 09/12] Ensure wc_PKCS7_DecodeEnvelopedData outputSz matches what was actually written and does not exceed the given size. (F-7631) --- tests/api/test_pkcs7.c | 87 ++++++++++++++++++++++++++++++++++++++++++ wolfcrypt/src/pkcs7.c | 40 +++++++++++++++---- 2 files changed, 120 insertions(+), 7 deletions(-) diff --git a/tests/api/test_pkcs7.c b/tests/api/test_pkcs7.c index e0b1fd85c80..71bba806cf9 100644 --- a/tests/api/test_pkcs7.c +++ b/tests/api/test_pkcs7.c @@ -4498,6 +4498,93 @@ int test_wc_PKCS7_EncodeDecodeEnvelopedData(void) } #endif /* !NO_AES && HAVE_AES_CBC && WOLFSSL_AES_256 && HAVE_AES_KEYWRAP */ +#if !defined(NO_RSA) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_AES_256) && defined(ASN_BER_TO_DER) && \ + !defined(NO_PKCS7_STREAM) + /* A BER EnvelopedData whose encryptedContent is a multi-segment + * indefinite-length OCTET STRING must never report more plaintext than it + * placed in the caller's buffer. Encode >1 segment (content > the 4096-byte + * streaming chunk), then decode with a full and an undersized output buffer. + * Run under ASan. */ + { + /* 6000 spans two 4096-byte streaming segments (4096 + 1904) without + * being an exact multiple of the chunk size. */ + const word32 bigSz = 6000; + const word32 halfSz = 4096; /* one segment: smaller than the total */ + byte* bigContent = NULL; + byte* berOut = NULL; + byte* plainFull = NULL; + byte* plainSmall = NULL; + int berSz = 0; + int dSz; + word32 j; + + bigContent = (byte*)XMALLOC(bigSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + berOut = (byte*)XMALLOC(bigSz + FOURK_BUF, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + plainFull = (byte*)XMALLOC(bigSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + /* exact-size small buffer so any over-write faults under ASan */ + plainSmall = (byte*)XMALLOC(halfSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(bigContent); + ExpectNotNull(berOut); + ExpectNotNull(plainFull); + ExpectNotNull(plainSmall); + if (bigContent != NULL) { + for (j = 0; j < bigSz; j++) + bigContent[j] = (byte)j; + } + + /* encode as BER (streaming) so encryptedContent is fragmented */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0); + if (pkcs7 != NULL) { + pkcs7->content = bigContent; + pkcs7->contentSz = bigSz; + pkcs7->contentOID = DATA; + pkcs7->encryptOID = AES256CBCb; + pkcs7->privateKey = rsaPrivKey; + pkcs7->privateKeySz = rsaPrivKeySz; + } + ExpectIntEQ(wc_PKCS7_SetStreamMode(pkcs7, 1, NULL, NULL, NULL), 0); + ExpectIntGT((berSz = wc_PKCS7_EncodeEnvelopedData(pkcs7, berOut, + bigSz + FOURK_BUF)), 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* full-size output buffer: all segments returned and content matches */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0); + if (pkcs7 != NULL) { + pkcs7->privateKey = rsaPrivKey; + pkcs7->privateKeySz = rsaPrivKeySz; + } + dSz = wc_PKCS7_DecodeEnvelopedData(pkcs7, berOut, (word32)berSz, + plainFull, bigSz); + ExpectIntEQ(dSz, (int)bigSz); + ExpectIntEQ(XMEMCMP(plainFull, bigContent, bigSz), 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* undersized output buffer: must fail, not report a length past it */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0); + if (pkcs7 != NULL) { + pkcs7->privateKey = rsaPrivKey; + pkcs7->privateKeySz = rsaPrivKeySz; + } + dSz = wc_PKCS7_DecodeEnvelopedData(pkcs7, berOut, (word32)berSz, + plainSmall, halfSz); + ExpectIntLT(dSz, 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + XFREE(bigContent, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(berOut, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(plainFull, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(plainSmall, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } +#endif /* multi-segment BER bounds regression */ + #ifndef NO_RSA XFREE(rsaCert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); XFREE(rsaPrivKey, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 62b89a2c837..9ed934640bc 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -14838,8 +14838,12 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in, localIdx += (word32)encryptedContentSz; /* keep track of total encrypted content size */ - pkcs7->totalEncryptedContentSz += - (word32)encryptedContentSz; + if (!WC_SAFE_SUM_WORD32(pkcs7->totalEncryptedContentSz, + (word32)encryptedContentSz, + pkcs7->totalEncryptedContentSz)) { + ret = BUFFER_E; + break; + } if (localIdx + ASN_INDEF_END_SZ <= pkiMsgSz) { if (pkiMsg[localIdx] == ASN_EOC && @@ -14869,17 +14873,34 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in, } #endif - /* save last decrypted string to handle padding (this output - * flush happens outside of the while loop in the case that - * the indef end was found) */ + /* flush this decrypted segment (the last segment is + * flushed outside of the while loop, once the indef end + * has been found and padding stripped) */ if (ret == 0) { #ifdef ASN_BER_TO_DER if (pkcs7->streamOutCb) { ret = pkcs7->streamOutCb(pkcs7, pkcs7->cachedEncryptedContent, (word32)encryptedContentSz, pkcs7->streamCtx); + if (ret != 0) + break; } + else #endif /* ASN_BER_TO_DER */ + { + /* copy segment to output; the return value counts + * every segment so each one must be written out */ + word32 outIdx = pkcs7->totalEncryptedContentSz - + (word32)encryptedContentSz; + if (output == NULL || + pkcs7->totalEncryptedContentSz > outputSz) { + ret = BUFFER_E; + break; + } + XMEMCPY(output + outIdx, + pkcs7->cachedEncryptedContent, + (word32)encryptedContentSz); + } } idx = localIdx; @@ -14966,12 +14987,17 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in, else #endif /* ASN_BER_TO_DER */ { - if (output == NULL || (word32)(encryptedContentSz - padLen) > + /* the return value counts every segment minus padding, so it + * must be bounded by outputSz */ + word32 outIdx = pkcs7->totalEncryptedContentSz - + (word32)encryptedContentSz; + if (output == NULL || + (pkcs7->totalEncryptedContentSz - (word32)padLen) > outputSz) { ret = BUFFER_E; break; } - XMEMCPY(output, encryptedContent, + XMEMCPY(output + outIdx, encryptedContent, (word32)encryptedContentSz - padLen); } From 0a6e94b8992dfbe6e234a5653b89a03154ab076b Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 17:53:59 -0700 Subject: [PATCH 10/12] Clear hash in EVP_CIPHER_MD_CTX_copy_ex before all possible returns. (F-8166) --- wolfcrypt/src/evp.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index daf0989c3bf..9f6a8f8cbf7 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -6155,14 +6155,15 @@ void wolfSSL_EVP_init(void) WOLFSSL_ENTER("EVP_CIPHER_MD_CTX_copy_ex"); wolfSSL_EVP_MD_CTX_cleanup(out); XMEMCPY(out, in, sizeof(WOLFSSL_EVP_MD_CTX)); + /* Zero hash context after shallow copy to prevent shared sub-pointers + * with src, even if the pctx allocation below fails. The hash Copy + * function will perform the proper deep copy. */ + XMEMSET(&out->hash, 0, sizeof(out->hash)); if (in->pctx != NULL) { out->pctx = wolfSSL_EVP_PKEY_CTX_new(in->pctx->pkey, NULL); if (out->pctx == NULL) return WOLFSSL_FAILURE; } - /* Zero hash context after shallow copy to prevent shared sub-pointers - * with src. The hash Copy function will perform the proper deep copy. */ - XMEMSET(&out->hash, 0, sizeof(out->hash)); return wolfSSL_EVP_MD_Copy_Hasher(out, (WOLFSSL_EVP_MD_CTX*)in); } #ifndef NO_AES From cc954630a0a1b0bd7c5a930722acc9e71911f93e Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 17:54:54 -0700 Subject: [PATCH 11/12] Correct size documentation for wc_AesCfb1Encrypt and wc_AesCfb1Decrypt (bits not bytes). (F-9329) --- wolfcrypt/src/aes.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index e96e4b1842f..e51aeff2e19 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -17069,7 +17069,8 @@ static WARN_UNUSED_RESULT int wc_AesFeedbackCFB1( * out buffer to hold result of encryption (must be at least as large as input * buffer) * in buffer to encrypt (packed to left, i.e. 101 is 0x90) - * sz size of input buffer in bits (0x1 would be size of 1 and 0xFF size of 8) + * sz number of bits to process, e.g. 1 processes one bit and 8 one byte; + * in and out must hold at least (sz + 7) / 8 bytes * * returns 0 on success and negative values on failure */ @@ -17100,8 +17101,9 @@ int wc_AesCfb8Encrypt(Aes* aes, byte* out, const byte* in, word32 sz) * aes structure holding key to use for encryption * out buffer to hold result of encryption (must be at least as large as input * buffer) - * in buffer to encrypt - * sz size of input buffer in bits (0x1 would be size of 1 and 0xFF size of 8) + * in buffer to decrypt (packed to left, i.e. 101 is 0x90) + * sz number of bits to process, e.g. 1 processes one bit and 8 one byte; + * in and out must hold at least (sz + 7) / 8 bytes * * returns 0 on success and negative values on failure */ From 3b56bf157c19f9639c2fa10d5120a396bc8e655e Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 17:58:35 -0700 Subject: [PATCH 12/12] Correct encContentOut size in wc_PKCS7_EncodeContentStream. --- tests/api/test_pkcs7.c | 57 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_pkcs7.h | 3 +++ wolfcrypt/src/pkcs7.c | 4 ++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/tests/api/test_pkcs7.c b/tests/api/test_pkcs7.c index 71bba806cf9..f40dfa45922 100644 --- a/tests/api/test_pkcs7.c +++ b/tests/api/test_pkcs7.c @@ -4637,6 +4637,63 @@ int test_wc_PKCS7_EncodeDecodeEnvelopedData(void) return EXPECT_RESULT(); } /* END test_wc_PKCS7_EncodeDecodeEnvelopedData() */ +/* + * The BER streaming encoder encrypts the content one 4096-byte octet chunk at a + * time into a working buffer, and the final chunk additionally carries the + * block cipher pad. Content that is an exact multiple of the chunk length makes + * that last chunk the largest one the buffer has to hold. Encode such sizes and + * confirm they succeed; run under ASan. + */ +int test_wc_PKCS7_stream_encode_chunk_boundary(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS7) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_AES_256) && defined(HAVE_AES_KEYWRAP) && \ + defined(ASN_BER_TO_DER) && !defined(NO_PKCS7_STREAM) + /* multiples of the encoder's private BER_OCTET_LENGTH (4096) */ + static const word32 contentSizes[] = { 4096, 8192 }; + static const byte keyId[] = { 0x00 }; + word32 i; + + for (i = 0; i < (word32)XELEM_CNT(contentSizes); i++) { + PKCS7* pkcs7 = NULL; + byte* content = NULL; + byte* ber = NULL; + word32 contentSz = contentSizes[i]; + word32 berSz = contentSz + FOURK_BUF; + word32 j; + + ExpectNotNull(content = (byte*)XMALLOC(contentSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(ber = (byte*)XMALLOC(berSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER)); + if (content != NULL) { + for (j = 0; j < contentSz; j++) + content[j] = (byte)j; + } + + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + if (pkcs7 != NULL) { + pkcs7->content = content; + pkcs7->contentSz = contentSz; + pkcs7->contentOID = DATA; + pkcs7->encryptOID = AES256CBCb; + } + ExpectIntGT(wc_PKCS7_AddRecipient_KEKRI(pkcs7, AES256_WRAP, + (byte*)defKey, sizeof(defKey), (byte*)keyId, sizeof(keyId), + NULL, NULL, 0, NULL, 0, 0), 0); + ExpectIntEQ(wc_PKCS7_SetSignerIdentifierType(pkcs7, CMS_SKID), 0); + ExpectIntEQ(wc_PKCS7_SetStreamMode(pkcs7, 1, NULL, NULL, NULL), 0); + ExpectIntGT(wc_PKCS7_EncodeEnvelopedData(pkcs7, ber, berSz), 0); + + wc_PKCS7_Free(pkcs7); + XFREE(content, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(ber, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } +#endif + return EXPECT_RESULT(); +} /* END test_wc_PKCS7_stream_encode_chunk_boundary() */ + #if defined(HAVE_PKCS7) && defined(HAVE_ECC) && defined(HAVE_X963_KDF) && \ !defined(NO_SHA256) && defined(WOLFSSL_AES_256) diff --git a/tests/api/test_pkcs7.h b/tests/api/test_pkcs7.h index bf66118e08d..73fe981ca5f 100644 --- a/tests/api/test_pkcs7.h +++ b/tests/api/test_pkcs7.h @@ -58,6 +58,7 @@ int test_wc_PKCS7_VerifySignedData_ECC(void); int test_wc_PKCS7_VerifySignedData_ECC_TamperedAttribs(void); int test_wc_PKCS7_DecodeEnvelopedData_stream(void); int test_wc_PKCS7_EncodeDecodeEnvelopedData(void); +int test_wc_PKCS7_stream_encode_chunk_boundary(void); int test_wc_PKCS7_SetAESKeyWrapUnwrapCb(void); int test_wc_PKCS7_GetEnvelopedDataKariRid(void); int test_wc_PKCS7_EncodeEncryptedData(void); @@ -155,6 +156,8 @@ int test_wc_PKCS7_VerifySignedData_NoDigestParams(void); #define TEST_PKCS7_ENCRYPTED_DATA_DECLS \ TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_stream), \ TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_EncodeDecodeEnvelopedData), \ + TEST_DECL_GROUP("pkcs7_ed", \ + test_wc_PKCS7_stream_encode_chunk_boundary), \ TEST_PKCS7_RSA_PSS_ED_DECL \ TEST_PKCS7_KTRI_BADRSAPAD_DECL \ TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_SetAESKeyWrapUnwrapCb), \ diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 9ed934640bc..e66b652356c 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -3318,7 +3318,9 @@ static int wc_PKCS7_EncodeContentStream(wc_PKCS7* pkcs7, ESD* esd, void* aes, } } - encContentOut = (byte *)XMALLOC(BER_OCTET_LENGTH + MAX_OCTET_STR_SZ, + /* the final chunk carries the trailing pad, so both buffers hold a + * full octet chunk plus padSz */ + encContentOut = (byte *)XMALLOC(BER_OCTET_LENGTH + padSz, heap, DYNAMIC_TYPE_PKCS7); contentData = (byte *)XMALLOC(BER_OCTET_LENGTH + padSz, heap, DYNAMIC_TYPE_PKCS7);