From 6e8bb19238c9d40ec9926cb768f529acd8ea98d0 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Mon, 6 Apr 2026 14:51:20 +0000 Subject: [PATCH 1/7] Chunk large inputs to prevent word32 truncation --- src/wp_aes_block.c | 84 ++++++++++++++++++++++++++++----------------- src/wp_aes_stream.c | 48 +++++++++++++++++--------- src/wp_cmac.c | 17 +++++---- src/wp_des.c | 21 ++++++++---- src/wp_digests.c | 13 ++++--- src/wp_hmac.c | 15 +++++--- 6 files changed, 128 insertions(+), 70 deletions(-) diff --git a/src/wp_aes_block.c b/src/wp_aes_block.c index 46a27242..378bd909 100644 --- a/src/wp_aes_block.c +++ b/src/wp_aes_block.c @@ -431,45 +431,65 @@ static int wp_aes_block_dinit(wp_AesBlockCtx *ctx, const unsigned char *key, static int wp_aes_block_doit(wp_AesBlockCtx *ctx, unsigned char *out, const unsigned char *in, size_t inLen) { - int rc; - -#ifdef WP_HAVE_AESCBC - if (ctx->mode == EVP_CIPH_CBC_MODE) { - if (ctx->enc) { - rc = wc_AesCbcEncrypt(&ctx->aes, out, in, (word32)inLen); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesCbcEncrypt", rc); + int rc = 0; + + while ((rc == 0) && (inLen > 0)) { + /* Chunk must be block-aligned (AES block size = 16). */ + word32 chunk = (inLen > 0xFFFFFFF0U) ? 0xFFFFFFF0U : (word32)inLen; + + #ifdef WP_HAVE_AESCBC + if (ctx->mode == EVP_CIPH_CBC_MODE) { + if (ctx->enc) { + rc = wc_AesCbcEncrypt(&ctx->aes, out, in, chunk); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, + "wc_AesCbcEncrypt", rc); + } } - } - else { - rc = wc_AesCbcDecrypt(&ctx->aes, out, in, (word32)inLen); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesCbcDecrypt", rc); + else { + rc = wc_AesCbcDecrypt(&ctx->aes, out, in, chunk); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, + "wc_AesCbcDecrypt", rc); + } } } - XMEMCPY(ctx->iv, ctx->aes.reg, ctx->ivLen); - } - else -#endif -#ifdef WP_HAVE_AESECB - if (ctx->mode == EVP_CIPH_ECB_MODE) { - if (ctx->enc) { - rc = wc_AesEcbEncrypt(&ctx->aes, out, in, (word32)inLen); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesEcbEncrypt", rc); + else + #endif + #ifdef WP_HAVE_AESECB + if (ctx->mode == EVP_CIPH_ECB_MODE) { + if (ctx->enc) { + rc = wc_AesEcbEncrypt(&ctx->aes, out, in, chunk); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, + "wc_AesEcbEncrypt", rc); + } } - } - else { - rc = wc_AesEcbDecrypt(&ctx->aes, out, in, (word32)inLen); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesEcbDecrypt", rc); + else { + rc = wc_AesEcbDecrypt(&ctx->aes, out, in, chunk); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, + "wc_AesEcbDecrypt", rc); + } } } + else + #endif + { + rc = -1; + } + + in += chunk; + out += chunk; + inLen -= chunk; } - else -#endif - { - rc = -1; + + if (rc == 0) { + #ifdef WP_HAVE_AESCBC + if (ctx->mode == EVP_CIPH_CBC_MODE) { + XMEMCPY(ctx->iv, ctx->aes.reg, ctx->ivLen); + } + #endif } return rc == 0; diff --git a/src/wp_aes_stream.c b/src/wp_aes_stream.c index a2190a21..4728df66 100644 --- a/src/wp_aes_stream.c +++ b/src/wp_aes_stream.c @@ -532,13 +532,18 @@ static int wp_aes_stream_doit(wp_AesStreamCtx *ctx, unsigned char *out, #ifdef WP_HAVE_AESCTR if (ctx->mode == EVP_CIPH_CTR_MODE) { - int rc; - XMEMCPY(&ctx->aes.reg, ctx->iv, ctx->ivLen); - rc = wc_AesCtrEncrypt(&ctx->aes, out, in, (word32)inLen); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesCtrEncrypt", rc); - ok = 0; + while (ok && (inLen > 0)) { + word32 chunk = (inLen > 0xFFFFFFFFU) ? 0xFFFFFFFFU : (word32)inLen; + int rc = wc_AesCtrEncrypt(&ctx->aes, out, in, chunk); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, + "wc_AesCtrEncrypt", rc); + ok = 0; + } + in += chunk; + out += chunk; + inLen -= chunk; } if (ok) { XMEMCPY(ctx->iv, ctx->aes.reg, ctx->ivLen); @@ -548,17 +553,24 @@ static int wp_aes_stream_doit(wp_AesStreamCtx *ctx, unsigned char *out, #endif #ifdef WP_HAVE_AESCFB if (ctx->mode == EVP_CIPH_CFB_MODE) { - int rc; - XMEMCPY(&ctx->aes.reg, ctx->iv, ctx->ivLen); - if (ctx->enc) { - rc = wc_AesCfbEncrypt(&ctx->aes, out, in, (word32)inLen); - }else { - rc = wc_AesCfbDecrypt(&ctx->aes, out, in, (word32)inLen); - } - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesCfbEncrypt/wc_AesCfbDecrypt", rc); - ok = 0; + while (ok && (inLen > 0)) { + word32 chunk = (inLen > 0xFFFFFFFFU) ? 0xFFFFFFFFU : (word32)inLen; + int rc; + if (ctx->enc) { + rc = wc_AesCfbEncrypt(&ctx->aes, out, in, chunk); + } + else { + rc = wc_AesCfbDecrypt(&ctx->aes, out, in, chunk); + } + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, + "wc_AesCfbEncrypt/wc_AesCfbDecrypt", rc); + ok = 0; + } + in += chunk; + out += chunk; + inLen -= chunk; } if (ok) { XMEMCPY(ctx->iv, ctx->aes.reg, ctx->ivLen); @@ -682,7 +694,9 @@ static int wp_aes_stream_cipher(wp_AesStreamCtx* ctx, unsigned char* out, ok = 0; } - *outLen = inLen; + if (ok) { + *outLen = inLen; + } WOLFPROV_LEAVE(WP_LOG_COMP_AES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } diff --git a/src/wp_cmac.c b/src/wp_cmac.c index f8a01c47..afa81360 100644 --- a/src/wp_cmac.c +++ b/src/wp_cmac.c @@ -91,7 +91,7 @@ static void wp_cmac_free(wp_CmacCtx* macCtx) { if (macCtx != NULL) { OPENSSL_cleanse(macCtx->key, macCtx->keyLen); - OPENSSL_free(macCtx); + OPENSSL_clear_free(macCtx, sizeof(*macCtx)); } } @@ -222,14 +222,19 @@ static int wp_cmac_update(wp_CmacCtx* macCtx, const unsigned char* data, size_t dataLen) { int ok = 1; - int rc; WOLFPROV_ENTER(WP_LOG_COMP_MAC, "wp_cmac_update"); - rc = wc_CmacUpdate(&macCtx->cmac, data, (word32)dataLen); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_CmacUpdate", rc); - ok = 0; + while (ok && (dataLen > 0)) { + word32 chunk = (dataLen > 0xFFFFFFFFU) ? 0xFFFFFFFFU : (word32)dataLen; + int rc = wc_CmacUpdate(&macCtx->cmac, data, chunk); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_CmacUpdate", + rc); + ok = 0; + } + data += chunk; + dataLen -= chunk; } WOLFPROV_LEAVE(WP_LOG_COMP_MAC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); diff --git a/src/wp_des.c b/src/wp_des.c index 561f1dea..51e0e987 100644 --- a/src/wp_des.c +++ b/src/wp_des.c @@ -374,16 +374,25 @@ static int wp_des3_block_dinit(wp_Des3BlockCtx *ctx, const unsigned char *key, static int wp_des3_block_doit(wp_Des3BlockCtx *ctx, unsigned char *out, const unsigned char *in, size_t inLen) { - int rc; + int rc = 0; if (ctx->mode == EVP_CIPH_CBC_MODE) { - if (ctx->enc) { - rc = wc_Des3_CbcEncrypt(&ctx->des3, out, in, (word32)inLen); + while ((rc == 0) && (inLen > 0)) { + /* Chunk must be block-aligned (DES3 block size = 8). */ + word32 chunk = (inLen > 0xFFFFFFF8U) ? 0xFFFFFFF8U : (word32)inLen; + if (ctx->enc) { + rc = wc_Des3_CbcEncrypt(&ctx->des3, out, in, chunk); + } + else { + rc = wc_Des3_CbcDecrypt(&ctx->des3, out, in, chunk); + } + in += chunk; + out += chunk; + inLen -= chunk; } - else { - rc = wc_Des3_CbcDecrypt(&ctx->des3, out, in, (word32)inLen); + if (rc == 0) { + XMEMCPY(ctx->iv, ctx->des3.reg, ctx->ivLen); } - XMEMCPY(ctx->iv, ctx->des3.reg, ctx->ivLen); } else { diff --git a/src/wp_digests.c b/src/wp_digests.c index 2f182b16..0c777eeb 100644 --- a/src/wp_digests.c +++ b/src/wp_digests.c @@ -153,10 +153,15 @@ static int name##_update(void* ctx, const unsigned char* in, size_t inLen) \ { \ int ok = 1; \ WOLFPROV_ENTER(WP_LOG_COMP_DIGEST, #name "_update"); \ - int rc = upd(ctx, in, (word32)inLen); \ - if (rc != 0) { \ - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, #upd, rc); \ - ok = 0; \ + while (ok && (inLen > 0)) { \ + word32 chunk = (inLen > 0xFFFFFFFFU) ? 0xFFFFFFFFU : (word32)inLen; \ + int rc = upd(ctx, in, chunk); \ + if (rc != 0) { \ + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, #upd, rc); \ + ok = 0; \ + } \ + in += chunk; \ + inLen -= chunk; \ } \ WOLFPROV_LEAVE(WP_LOG_COMP_DIGEST, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);\ return ok; \ diff --git a/src/wp_hmac.c b/src/wp_hmac.c index 5ff78ae9..bc1d27ca 100644 --- a/src/wp_hmac.c +++ b/src/wp_hmac.c @@ -248,14 +248,19 @@ static int wp_hmac_update(wp_HmacCtx* macCtx, const unsigned char* data, size_t dataLen) { int ok = 1; - int rc; WOLFPROV_ENTER(WP_LOG_COMP_MAC, "wp_hmac_update"); - rc = wc_HmacUpdate(&macCtx->hmac, data, (word32)dataLen); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_HmacUpdate", rc); - ok = 0; + while (ok && (dataLen > 0)) { + word32 chunk = (dataLen > 0xFFFFFFFFU) ? 0xFFFFFFFFU : (word32)dataLen; + int rc = wc_HmacUpdate(&macCtx->hmac, data, chunk); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_HmacUpdate", + rc); + ok = 0; + } + data += chunk; + dataLen -= chunk; } WOLFPROV_LEAVE(WP_LOG_COMP_MAC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); From a0fedc38190a235d63afa51c6665f00567979ad5 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Mon, 6 Apr 2026 14:51:24 +0000 Subject: [PATCH 2/7] Fix mutex guards to use WP_SINGLE_THREADED --- src/wp_dh_kmgmt.c | 2 +- src/wp_ecc_kmgmt.c | 2 +- src/wp_ecx_kmgmt.c | 8 +++++--- src/wp_kdf_kmgmt.c | 2 +- src/wp_mac_kmgmt.c | 2 +- src/wp_rsa_kmgmt.c | 6 +++--- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/wp_dh_kmgmt.c b/src/wp_dh_kmgmt.c index e5a21cbc..6631c107 100644 --- a/src/wp_dh_kmgmt.c +++ b/src/wp_dh_kmgmt.c @@ -401,7 +401,7 @@ static wp_Dh* wp_dh_new(WOLFPROV_CTX *provCtx) WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_InitDhKey_ex", rc); ok = 0; } - #ifndef SINGLE_THREADED + #ifndef WP_SINGLE_THREADED if (ok) { rc = wc_InitMutex(&dh->mutex); if (rc != 0) { diff --git a/src/wp_ecc_kmgmt.c b/src/wp_ecc_kmgmt.c index c3caf6d2..cd8fe9c4 100644 --- a/src/wp_ecc_kmgmt.c +++ b/src/wp_ecc_kmgmt.c @@ -350,7 +350,7 @@ static wp_Ecc* wp_ecc_new(WOLFPROV_CTX *provCtx) } } - #ifndef SINGLE_THREADED + #ifndef WP_SINGLE_THREADED if (ok) { rc = wc_InitMutex(&ecc->mutex); if (rc != 0) { diff --git a/src/wp_ecx_kmgmt.c b/src/wp_ecx_kmgmt.c index 2cbaa839..d103c280 100644 --- a/src/wp_ecx_kmgmt.c +++ b/src/wp_ecx_kmgmt.c @@ -283,7 +283,7 @@ static wp_Ecx* wp_ecx_new(WOLFPROV_CTX* provCtx, const wp_EcxData* data) ok = 0; } - #ifndef SINGLE_THREADED + #ifndef WP_SINGLE_THREADED if (ok) { rc = wc_InitMutex(&ecx->mutex); if (rc != 0) { @@ -698,6 +698,9 @@ static int wp_ecx_match_priv_key(const wp_Ecx* ecx1, const wp_Ecx* ecx2) ok = 0; } + OPENSSL_cleanse(key1, sizeof(key1)); + OPENSSL_cleanse(key2, sizeof(key2)); + WOLFPROV_LEAVE(WP_LOG_COMP_KE, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } @@ -1273,8 +1276,7 @@ static int wp_ecx_gen_set_params(wp_EcxGenCtx* ctx, const OSSL_PARAM params[]) &name)) { ok = 0; } - if (ok && (name != NULL) && (XSTRNCMP(name, ctx->name, - XSTRLEN(name)) != 0)) { + if (ok && (name != NULL) && (XSTRCMP(name, ctx->name) != 0)) { ok = 0; } diff --git a/src/wp_kdf_kmgmt.c b/src/wp_kdf_kmgmt.c index c774b8fd..69a2420a 100644 --- a/src/wp_kdf_kmgmt.c +++ b/src/wp_kdf_kmgmt.c @@ -96,7 +96,7 @@ static wp_Kdf* wp_kdf_new(WOLFPROV_CTX *provCtx) kdf = (wp_Kdf*)OPENSSL_zalloc(sizeof(*kdf)); } if (kdf != NULL) { - #ifndef SINGLE_THREADED + #ifndef WP_SINGLE_THREADED int rc = wc_InitMutex(&kdf->mutex); if (rc != 0) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_InitMutex", rc); diff --git a/src/wp_mac_kmgmt.c b/src/wp_mac_kmgmt.c index e942d1b5..743db5d2 100644 --- a/src/wp_mac_kmgmt.c +++ b/src/wp_mac_kmgmt.c @@ -190,7 +190,7 @@ static wp_Mac* wp_mac_new(WOLFPROV_CTX *provCtx, int type) mac = (wp_Mac*)OPENSSL_zalloc(sizeof(*mac)); } if (mac != NULL) { - #ifndef SINGLE_THREADED + #ifndef WP_SINGLE_THREADED int rc = wc_InitMutex(&mac->mutex); if (rc != 0) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_InitMutex", rc); diff --git a/src/wp_rsa_kmgmt.c b/src/wp_rsa_kmgmt.c index b4de0013..3ac30bcb 100644 --- a/src/wp_rsa_kmgmt.c +++ b/src/wp_rsa_kmgmt.c @@ -470,7 +470,7 @@ static wp_Rsa* wp_rsa_base_new(WOLFPROV_CTX* provCtx, int type) ok = 0; } - #ifndef SINGLE_THREADED + #ifndef WP_SINGLE_THREADED if (ok) { rc = wc_InitMutex(&rsa->mutex); if (rc != 0) { @@ -600,7 +600,7 @@ static int wp_rsa_pss_params_set_pss_defaults(wp_RsaPssParams* pss) pss->hashType = WP_RSA_PSS_DIGEST_DEF; pss->mgf = WP_RSA_PSS_MGF_DEF; XSTRNCPY(pss->mdName, "SHA-1", sizeof(pss->mdName)); - XSTRNCPY(pss->mgfMdName, "SHA-1", sizeof(pss->mdName)); + XSTRNCPY(pss->mgfMdName, "SHA-1", sizeof(pss->mgfMdName)); pss->saltLen = WP_RSA_DEFAULT_SALT_LEN; pss->derTrailer = 1; /* Default: RFC8017 A.2.3 */ @@ -1229,7 +1229,7 @@ static int wp_rsa_import_key_data(wp_Rsa* rsa, const OSSL_PARAM params[], p = ¶ms[i]; index = -1; for (j = 0; j < (int)ARRAY_SIZE(wp_rsa_param_key); j++) { - if (XSTRNCMP(p->key, wp_rsa_param_key[j], XSTRLEN(p->key)) == 0) { + if (XSTRCMP(p->key, wp_rsa_param_key[j]) == 0) { index = j; break; } From d3d620dd7ccad747c621965795d053429763dc29 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Mon, 6 Apr 2026 14:51:29 +0000 Subject: [PATCH 3/7] Fix prefix matches, sizeof errors in string ops --- src/wp_internal.c | 8 +++---- src/wp_rsa_asym.c | 58 ++++++++++++++++++++++++++++++----------------- src/wp_rsa_kem.c | 3 +-- src/wp_rsa_sig.c | 4 ++-- 4 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/wp_internal.c b/src/wp_internal.c index f8f5609e..9d25f5f9 100644 --- a/src/wp_internal.c +++ b/src/wp_internal.c @@ -793,7 +793,7 @@ static int wp_EncryptedInfoGet(wp_EncryptedInfo* info, const char* cipherInfo) /* determine cipher information */ #if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) - if (XSTRNCMP(cipherInfo, kEncTypeAesCbc128, XSTRLEN(kEncTypeAesCbc128)) == 0) { + if (XSTRCMP(cipherInfo, kEncTypeAesCbc128) == 0) { info->cipherType = WC_CIPHER_AES_CBC; info->keySz = AES_128_KEY_SIZE; if (info->ivSz == 0) info->ivSz = AES_IV_SIZE; @@ -801,7 +801,7 @@ static int wp_EncryptedInfoGet(wp_EncryptedInfo* info, const char* cipherInfo) else #endif #if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_192) - if (XSTRNCMP(cipherInfo, kEncTypeAesCbc192, XSTRLEN(kEncTypeAesCbc192)) == 0) { + if (XSTRCMP(cipherInfo, kEncTypeAesCbc192) == 0) { info->cipherType = WC_CIPHER_AES_CBC; info->keySz = AES_192_KEY_SIZE; if (info->ivSz == 0) info->ivSz = AES_IV_SIZE; @@ -809,7 +809,7 @@ static int wp_EncryptedInfoGet(wp_EncryptedInfo* info, const char* cipherInfo) else #endif #if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_256) - if (XSTRNCMP(cipherInfo, kEncTypeAesCbc256, XSTRLEN(kEncTypeAesCbc256)) == 0) { + if (XSTRCMP(cipherInfo, kEncTypeAesCbc256) == 0) { info->cipherType = WC_CIPHER_AES_CBC; info->keySz = AES_256_KEY_SIZE; if (info->ivSz == 0) info->ivSz = AES_IV_SIZE; @@ -1113,7 +1113,7 @@ int wp_read_pem_bio(WOLFPROV_CTX *provctx, OSSL_CORE_BIO *coreBio, *len += readLen; } /* Last line should have footer. */ - if (XMEMCMP(buf, "-----END ", 8) == 0) { + if (XMEMCMP(buf, "-----END ", 9) == 0) { break; } } diff --git a/src/wp_rsa_asym.c b/src/wp_rsa_asym.c index 66b20012..8eebf5db 100644 --- a/src/wp_rsa_asym.c +++ b/src/wp_rsa_asym.c @@ -466,28 +466,44 @@ static int wp_rsaa_decrypt(wp_RsaAsymCtx* ctx, unsigned char* out, if (ok) { byte mask; byte negMask; - - XMEMSET(out, 0, outSize); - PRIVATE_KEY_UNLOCK(); - rc = wc_RsaPrivateDecrypt(in, (word32)inLen, out, - (word32)outSize, wp_rsa_get_key(ctx->rsa)); - PRIVATE_KEY_LOCK(); - - /* Constant time checking of master secret. */ - mask = wp_ct_byte_mask_eq(out[0], ctx->clientVersion >> 8); - mask &= wp_ct_byte_mask_eq(out[1], ctx->clientVersion); - if (ctx->negVersion > 0) { - /* Check for negotiated version as well. */ - negMask = wp_ct_byte_mask_eq(out[0], ctx->negVersion >> 8); - negMask &= wp_ct_byte_mask_eq(out[1], ctx->negVersion); - mask |= negMask; - } - rc &= (int)(char)mask; - - if (rc <= 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_RsaPrivateDecrypt TLS padding", rc); + byte rand[WOLFSSL_MAX_MASTER_KEY_LENGTH]; + int i; + + /* Implicit rejection: always generate random fallback + * to prevent Bleichenbacher-style oracle attacks. */ + rc = wc_RNG_GenerateBlock(&ctx->rng, rand, + WOLFSSL_MAX_MASTER_KEY_LENGTH); + if (rc != 0) { ok = 0; } + if (ok) { + XMEMSET(out, 0, outSize); + PRIVATE_KEY_UNLOCK(); + rc = wc_RsaPrivateDecrypt(in, (word32)inLen, out, + (word32)outSize, wp_rsa_get_key(ctx->rsa)); + PRIVATE_KEY_LOCK(); + + /* Constant time checking of master secret. */ + mask = wp_ct_byte_mask_eq(out[0], + ctx->clientVersion >> 8); + mask &= wp_ct_byte_mask_eq(out[1], ctx->clientVersion); + if (ctx->negVersion > 0) { + negMask = wp_ct_byte_mask_eq(out[0], + ctx->negVersion >> 8); + negMask &= wp_ct_byte_mask_eq(out[1], + ctx->negVersion); + mask |= negMask; + } + /* Combine decrypt success with version check. */ + mask &= wp_ct_int_mask_gte(rc, 1); + + /* Constant-time select: real result or random fallback. */ + for (i = 0; i < WOLFSSL_MAX_MASTER_KEY_LENGTH; i++) { + out[i] = wp_ct_byte_mask_sel(mask, out[i], rand[i]); + } + OPENSSL_cleanse(rand, sizeof(rand)); + rc = WOLFSSL_MAX_MASTER_KEY_LENGTH; + } } } else if (ctx->padMode == RSA_NO_PADDING) { @@ -551,7 +567,7 @@ static int wp_rsaa_setup_md(wp_RsaAsymCtx* ctx, const char* mdName, } else { OPENSSL_strlcpy(ctx->mgf1MdName, mdName, - sizeof(ctx->oaepMdName)); + sizeof(ctx->mgf1MdName)); } } } diff --git a/src/wp_rsa_kem.c b/src/wp_rsa_kem.c index 424503a2..cfe73c6e 100644 --- a/src/wp_rsa_kem.c +++ b/src/wp_rsa_kem.c @@ -539,8 +539,7 @@ static int wp_rsakem_set_ctx_params(wp_RsaKemCtx* ctx, ok = 0; } if (ok && (op != NULL)) { - if (XSTRNCMP(OSSL_KEM_PARAM_OPERATION_RSASVE, op, - sizeof(OSSL_KEM_PARAM_OPERATION_RSASVE) - 1) == 0) { + if (XSTRCMP(OSSL_KEM_PARAM_OPERATION_RSASVE, op) == 0) { ctx->op = WP_RSA_KEM_OP_RSASVE; } else { diff --git a/src/wp_rsa_sig.c b/src/wp_rsa_sig.c index 25c2094a..962c50ac 100644 --- a/src/wp_rsa_sig.c +++ b/src/wp_rsa_sig.c @@ -130,7 +130,7 @@ static int wp_rsa_setup_md(wp_RsaSigCtx* ctx, const char* mdName, if (ctx->padMode == RSA_PKCS1_PSS_PADDING && ctx->minSaltLen != -1) { wp_rsa_get_pss_mds(ctx->rsa, &localMdName, NULL); if (mdName != NULL && - XSTRNCASECMP(localMdName, mdName, XSTRLEN(localMdName)) != 0) { + XSTRCASECMP(localMdName, mdName) != 0) { ok = 0; } } @@ -197,7 +197,7 @@ static int wp_rsa_setup_md(wp_RsaSigCtx* ctx, const char* mdName, } } else { - OPENSSL_strlcpy(ctx->mgf1MdName, mdName, sizeof(ctx->mdName)); + OPENSSL_strlcpy(ctx->mgf1MdName, mdName, sizeof(ctx->mgf1MdName)); } } } From eba09b8621f3add7b3d1304446df3402c145ebe8 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Mon, 6 Apr 2026 14:51:37 +0000 Subject: [PATCH 4/7] Secure-clear key material on free and stack --- src/wp_dec_epki2pki.c | 2 +- src/wp_ecx_exch.c | 35 +++++++++++++++++++++++------------ src/wp_gmac.c | 3 ++- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/wp_dec_epki2pki.c b/src/wp_dec_epki2pki.c index 3b3071b8..7671cdc6 100644 --- a/src/wp_dec_epki2pki.c +++ b/src/wp_dec_epki2pki.c @@ -261,7 +261,7 @@ static int wp_epki2pki_decode(wp_Epki2Pki* ctx, OSSL_CORE_BIO* coreBio, } /* Dispose of the EPKI data buffer. */ - OPENSSL_free(data); + OPENSSL_clear_free(data, len); OPENSSL_cleanse(password, sizeof(password)); diff --git a/src/wp_ecx_exch.c b/src/wp_ecx_exch.c index 1a3dae8f..c900fe42 100644 --- a/src/wp_ecx_exch.c +++ b/src/wp_ecx_exch.c @@ -250,21 +250,32 @@ static int wp_x25519_derive(wp_EcxCtx* ctx, unsigned char* secret, ok = 0; } if (ok) { + /* Constant-time: always subtract, then select based on + * whether secret >= order. */ + unsigned char reduced[CURVE25519_KEYSIZE]; + int16_t carry = 0; + byte gt = 0; + byte eq = 0xFF; + + for (i = CURVE25519_KEYSIZE - 1; i >= 0; i--) { + carry += secret[i]; + carry -= wp_curve25519_order[i]; + reduced[i] = (unsigned char)carry; + carry >>= 8; + } + /* Determine if secret >= order in constant time. */ for (i = 0; i < CURVE25519_KEYSIZE; i++) { - if (secret[i] != wp_curve25519_order[i]) { - break; - } + gt |= eq & wp_ct_int_mask_gte(secret[i], + wp_curve25519_order[i] + 1); + eq &= wp_ct_byte_mask_eq(secret[i], + wp_curve25519_order[i]); } - if ((i < CURVE25519_KEYSIZE) && - (secret[i] > wp_curve25519_order[i])) { - int16_t carry = 0; - for (i = CURVE25519_KEYSIZE - 1; i >= 0; i--) { - carry += secret[i]; - carry -= wp_curve25519_order[i]; - secret[i] = (unsigned char)carry; - carry >>= 8; - } + /* Select reduced if secret >= order. */ + for (i = 0; i < CURVE25519_KEYSIZE; i++) { + secret[i] = wp_ct_byte_mask_sel(gt | eq, reduced[i], + secret[i]); } + OPENSSL_cleanse(reduced, sizeof(reduced)); } if (ok) { *secLen = len; diff --git a/src/wp_gmac.c b/src/wp_gmac.c index d303e560..50bff4d1 100644 --- a/src/wp_gmac.c +++ b/src/wp_gmac.c @@ -93,10 +93,11 @@ static wp_GmacCtx* wp_gmac_new(WOLFPROV_CTX* provCtx) static void wp_gmac_free(wp_GmacCtx* macCtx) { if (macCtx != NULL) { + wc_AesFree(&macCtx->gmac.aes); OPENSSL_cleanse(macCtx->key, macCtx->keyLen); OPENSSL_cleanse(macCtx->iv, macCtx->ivLen); OPENSSL_clear_free(macCtx->data, macCtx->dataLen); - OPENSSL_free(macCtx); + OPENSSL_clear_free(macCtx, sizeof(*macCtx)); } } From 1ebe98a16102bf7cbd820f902d3b8ee4fb845297 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Mon, 6 Apr 2026 14:51:41 +0000 Subject: [PATCH 5/7] Fix error handling, NULL checks, and TLS padding --- src/wp_aes_aead.c | 6 ++++-- src/wp_kbkdf.c | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/wp_aes_aead.c b/src/wp_aes_aead.c index 7cb6d9f1..02ad5e22 100644 --- a/src/wp_aes_aead.c +++ b/src/wp_aes_aead.c @@ -488,7 +488,9 @@ static int wp_aead_set_param_tag(wp_AeadCtx* ctx, if (ok && ((sz == 0) || ((p->data != NULL) && ctx->enc))) { ok = 0; } - ctx->tagLen = sz; + if (ok) { + ctx->tagLen = sz; + } WOLFPROV_LEAVE(WP_LOG_COMP_AES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; @@ -1122,7 +1124,7 @@ static int wp_aesgcm_dinit(wp_AeadCtx *ctx, const unsigned char *key, ok = 0; } } - if (ok) { + if (ok && (iv != NULL)) { XMEMCPY(ctx->iv, iv, ivLen); ctx->ivState = IV_STATE_BUFFERED; ctx->ivSet = 0; diff --git a/src/wp_kbkdf.c b/src/wp_kbkdf.c index 0abee09b..3b65b915 100644 --- a/src/wp_kbkdf.c +++ b/src/wp_kbkdf.c @@ -623,7 +623,9 @@ static int wp_kdf_kbkdf_derive(wp_KbkdfCtx* ctx, unsigned char* key, ok = 0; } else { - XMEMCPY(k_i, ctx->iv, ctx->ivLen); + if (ctx->ivLen > 0) { + XMEMCPY(k_i, ctx->iv, ctx->ivLen); + } k_i_len = ctx->ivLen; /* Prep [L]2 */ wp_c32toa((word32)(keyLen * 8), (byte *)&beL); From 4aeb35eaf647d125eada30bcb3c28d6a778d8432 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Mon, 6 Apr 2026 14:51:45 +0000 Subject: [PATCH 6/7] Fix PEM parsing and hex dump off-by-one --- src/wp_dec_pem2der.c | 2 +- src/wp_logging.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp_dec_pem2der.c b/src/wp_dec_pem2der.c index 2000fb74..55a84846 100644 --- a/src/wp_dec_pem2der.c +++ b/src/wp_dec_pem2der.c @@ -158,7 +158,7 @@ static int wp_pem2der_convert(const char* data, word32 len, DerBuffer** pDer, /* Skip '-----BEGIN -----\n'. */ base64Data = data + 16 + nameLen + 1; - base64Len = len - 16 + nameLen + 1; + base64Len = len - (16 + nameLen + 1); footer = XSTRSTR(base64Data, "-----END "); if (footer == NULL) { info->consumed = len; diff --git a/src/wp_logging.c b/src/wp_logging.c index 44e1cce5..51887c84 100644 --- a/src/wp_logging.c +++ b/src/wp_logging.c @@ -444,7 +444,7 @@ void WOLFPROV_BUFFER(int component, const unsigned char* buffer, } XSNPRINTF(&line[bufidx], sizeof(line)-bufidx, "| "); - bufidx++; + bufidx += 2; for (i = 0; i < WOLFPROV_LINE_LEN; i++) { if (i < buflen) { From 1d0fb43448849cdc4fde985a55f47492a681df83 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Mon, 6 Apr 2026 22:14:52 +0000 Subject: [PATCH 7/7] Add unit tests for Fenrir fixes --- test/test_aestag.c | 111 ++++++++++++++++++++++++++++++++++++++ test/test_cipher.c | 103 +++++++++++++++++++++++++++++++++++ test/test_cmac.c | 101 ++++++++++++++++++++++++++++++++++ test/test_digest.c | 80 +++++++++++++++++++++++++++ test/test_ecx.c | 1 + test/test_hmac.c | 96 +++++++++++++++++++++++++++++++++ test/test_rsa.c | 132 +++++++++++++++++++++++++++++++++++++++++++++ test/unit.c | 7 +++ test/unit.h | 10 +++- 9 files changed, 640 insertions(+), 1 deletion(-) diff --git a/test/test_aestag.c b/test/test_aestag.c index bc21c4e1..0dbb4b6f 100644 --- a/test/test_aestag.c +++ b/test/test_aestag.c @@ -1196,6 +1196,117 @@ int test_aes128_gcm_set_iv_inv(void *data) EVP_GCM_TLS_FIXED_IV_LEN, 12); } +/******************************************************************************/ + +/** + * Test GCM decrypt init with key only (NULL IV), then set IV via params. + * Without the F-175 fix, this would crash with a NULL pointer dereference + * under WOLFSSL_AESGCM_STREAM. + */ +static int test_gcm_key_then_iv_helper(OSSL_LIB_CTX *libCtx) +{ + int err; + EVP_CIPHER_CTX *encCtx = NULL; + EVP_CIPHER_CTX *decCtx = NULL; + EVP_CIPHER *cipher = NULL; + unsigned char key[16]; + unsigned char iv[12]; + unsigned char msg[] = "GCM key-then-iv test"; + unsigned char aad[] = "additional data"; + unsigned char enc[sizeof(msg) + 16]; + unsigned char dec[sizeof(msg) + 16]; + unsigned char tag[16]; + int encLen = 0; + int decLen = 0; + int fLen = 0; + + RAND_bytes(key, sizeof(key)); + RAND_bytes(iv, sizeof(iv)); + + err = (cipher = EVP_CIPHER_fetch(libCtx, "AES-128-GCM", "")) == NULL; + + /* Encrypt normally to produce ciphertext + tag */ + if (err == 0) { + err = (encCtx = EVP_CIPHER_CTX_new()) == NULL; + } + if (err == 0) { + err = EVP_EncryptInit(encCtx, cipher, key, iv) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(encCtx, NULL, &encLen, aad, + sizeof(aad)) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(encCtx, enc, &encLen, msg, + sizeof(msg)) != 1; + } + if (err == 0) { + err = EVP_EncryptFinal_ex(encCtx, enc + encLen, &fLen) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(encCtx, EVP_CTRL_AEAD_GET_TAG, sizeof(tag), + tag) != 1; + } + EVP_CIPHER_CTX_free(encCtx); + + /* Decrypt with key-only init, then set IV separately */ + if (err == 0) { + err = (decCtx = EVP_CIPHER_CTX_new()) == NULL; + } + if (err == 0) { + /* Init with key but NULL IV */ + err = EVP_DecryptInit_ex(decCtx, cipher, NULL, key, NULL) != 1; + } + if (err == 0) { + /* Set IV via ctrl */ + err = EVP_CIPHER_CTX_ctrl(decCtx, EVP_CTRL_AEAD_SET_IVLEN, + sizeof(iv), NULL) != 1; + } + if (err == 0) { + err = EVP_DecryptInit_ex(decCtx, NULL, NULL, NULL, iv) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(decCtx, EVP_CTRL_AEAD_SET_TAG, sizeof(tag), + tag) != 1; + } + if (err == 0) { + err = EVP_DecryptUpdate(decCtx, NULL, &decLen, aad, + sizeof(aad)) != 1; + } + if (err == 0) { + err = EVP_DecryptUpdate(decCtx, dec, &decLen, enc, encLen) != 1; + } + if (err == 0) { + err = EVP_DecryptFinal_ex(decCtx, dec + decLen, &fLen) != 1; + } + if (err == 0) { + if (decLen != (int)sizeof(msg) || + memcmp(dec, msg, sizeof(msg)) != 0) { + PRINT_ERR_MSG("GCM key-then-iv decrypt mismatch"); + err = 1; + } + } + + EVP_CIPHER_CTX_free(decCtx); + EVP_CIPHER_free(cipher); + return err; +} + +int test_aes128_gcm_key_then_iv(void *data) +{ + int err; + + (void)data; + + PRINT_MSG("GCM key-then-iv with OpenSSL"); + err = test_gcm_key_then_iv_helper(osslLibCtx); + if (err == 0) { + PRINT_MSG("GCM key-then-iv with wolfProvider"); + err = test_gcm_key_then_iv_helper(wpLibCtx); + } + return err; +} + #endif /* WP_HAVE_AESGCM */ /******************************************************************************/ diff --git a/test/test_cipher.c b/test/test_cipher.c index 2a95155a..143984fe 100644 --- a/test/test_cipher.c +++ b/test/test_cipher.c @@ -1488,4 +1488,107 @@ int test_aes256_cbc_bad_pad(void *data) return err; } +/******************************************************************************/ + +/** + * Test AES-CBC encrypt/decrypt roundtrip with a large buffer processed in + * multiple update calls. Validates the chunked loop path in + * wp_aes_block_doit (F-1641). + */ +static int test_aes_cbc_large_update_helper(OSSL_LIB_CTX *libCtx) +{ + int err; + EVP_CIPHER_CTX *ctx = NULL; + EVP_CIPHER *cipher = NULL; + unsigned char key[32]; + unsigned char iv[16]; + unsigned char plain[8192]; + unsigned char enc[8192 + 16]; + unsigned char dec[8192 + 16]; + int outLen; + int fLen; + int totalEnc = 0; + int totalDec = 0; + size_t i; + + RAND_bytes(key, sizeof(key)); + RAND_bytes(iv, sizeof(iv)); + RAND_bytes(plain, sizeof(plain)); + + err = (cipher = EVP_CIPHER_fetch(libCtx, "AES-256-CBC", "")) == NULL; + + /* Encrypt in 1024-byte chunks */ + if (err == 0) { + err = (ctx = EVP_CIPHER_CTX_new()) == NULL; + } + if (err == 0) { + err = EVP_EncryptInit(ctx, cipher, key, iv) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_set_padding(ctx, 0) != 1; + } + for (i = 0; err == 0 && i < sizeof(plain); i += 1024) { + err = EVP_EncryptUpdate(ctx, enc + totalEnc, &outLen, + plain + i, 1024) != 1; + if (err == 0) { + totalEnc += outLen; + } + } + if (err == 0) { + err = EVP_EncryptFinal_ex(ctx, enc + totalEnc, &fLen) != 1; + totalEnc += fLen; + } + EVP_CIPHER_CTX_free(ctx); + ctx = NULL; + + /* Decrypt in 1024-byte chunks */ + if (err == 0) { + err = (ctx = EVP_CIPHER_CTX_new()) == NULL; + } + if (err == 0) { + err = EVP_DecryptInit(ctx, cipher, key, iv) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_set_padding(ctx, 0) != 1; + } + for (i = 0; err == 0 && (int)i < totalEnc; i += 1024) { + int chunk = (totalEnc - (int)i < 1024) ? totalEnc - (int)i : 1024; + err = EVP_DecryptUpdate(ctx, dec + totalDec, &outLen, + enc + i, chunk) != 1; + if (err == 0) { + totalDec += outLen; + } + } + if (err == 0) { + err = EVP_DecryptFinal_ex(ctx, dec + totalDec, &fLen) != 1; + totalDec += fLen; + } + if (err == 0) { + if (totalDec != (int)sizeof(plain) || + memcmp(dec, plain, sizeof(plain)) != 0) { + PRINT_ERR_MSG("AES-CBC large update decrypt mismatch"); + err = 1; + } + } + + EVP_CIPHER_CTX_free(ctx); + EVP_CIPHER_free(cipher); + return err; +} + +int test_aes_cbc_large_update(void *data) +{ + int err; + + (void)data; + + PRINT_MSG("AES-CBC large update with OpenSSL"); + err = test_aes_cbc_large_update_helper(osslLibCtx); + if (err == 0) { + PRINT_MSG("AES-CBC large update with wolfProvider"); + err = test_aes_cbc_large_update_helper(wpLibCtx); + } + return err; +} + #endif /* WP_HAVE_AESCBC */ diff --git a/test/test_cmac.c b/test/test_cmac.c index f4ab49ed..1493d2a7 100644 --- a/test/test_cmac.c +++ b/test/test_cmac.c @@ -257,5 +257,106 @@ int test_cmac_create(void *data) return ret; } +/******************************************************************************/ + +/** + * Test that CMAC produces consistent results when data is fed in many small + * updates vs. a single large update. Exercises the chunked update path + * (F-1640). + */ +static int test_cmac_multi_update_helper(OSSL_LIB_CTX *libCtx) +{ + int err; + EVP_MAC *emac = NULL; + EVP_MAC_CTX *ctx = NULL; + OSSL_PARAM params[3]; + char cipher[] = "AES-256-CBC"; + unsigned char key[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + }; + unsigned char data[2048]; + unsigned char macOne[16]; + unsigned char macMulti[16]; + size_t macOneSz = sizeof(macOne); + size_t macMultiSz = sizeof(macMulti); + size_t i; + + RAND_bytes(data, sizeof(data)); + + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_CIPHER, + cipher, 0); + params[1] = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY, + (void *)key, sizeof(key)); + params[2] = OSSL_PARAM_construct_end(); + + err = (emac = EVP_MAC_fetch(libCtx, "CMAC", NULL)) == NULL; + + /* Single update */ + if (err == 0) { + err = (ctx = EVP_MAC_CTX_new(emac)) == NULL; + } + if (err == 0) { + err = EVP_MAC_CTX_set_params(ctx, params) != 1; + } + if (err == 0) { + err = EVP_MAC_init(ctx, NULL, 0, NULL) != 1; + } + if (err == 0) { + err = EVP_MAC_update(ctx, data, sizeof(data)) != 1; + } + if (err == 0) { + err = EVP_MAC_final(ctx, macOne, &macOneSz, sizeof(macOne)) != 1; + } + EVP_MAC_CTX_free(ctx); + ctx = NULL; + + /* Many small updates (16 bytes each — one AES block) */ + if (err == 0) { + err = (ctx = EVP_MAC_CTX_new(emac)) == NULL; + } + if (err == 0) { + err = EVP_MAC_CTX_set_params(ctx, params) != 1; + } + if (err == 0) { + err = EVP_MAC_init(ctx, NULL, 0, NULL) != 1; + } + for (i = 0; err == 0 && i < sizeof(data); i += 16) { + err = EVP_MAC_update(ctx, data + i, 16) != 1; + } + if (err == 0) { + err = EVP_MAC_final(ctx, macMulti, &macMultiSz, + sizeof(macMulti)) != 1; + } + if (err == 0) { + if (macOneSz != macMultiSz || + memcmp(macOne, macMulti, macOneSz) != 0) { + PRINT_ERR_MSG("Multi-update CMAC doesn't match single update"); + err = 1; + } + } + + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(emac); + return err; +} + +int test_cmac_multi_update(void *data) +{ + int err; + + (void)data; + + PRINT_MSG("CMAC multi-update with OpenSSL"); + err = test_cmac_multi_update_helper(osslLibCtx); + if (err == 0) { + PRINT_MSG("CMAC multi-update with wolfProvider"); + err = test_cmac_multi_update_helper(wpLibCtx); + } + return err; +} + #endif /* WP_HAVE_CMAC */ diff --git a/test/test_digest.c b/test/test_digest.c index 51136311..5b30132c 100644 --- a/test/test_digest.c +++ b/test/test_digest.c @@ -283,4 +283,84 @@ int test_shake_256(void *data) /******************************************************************************/ +/** + * Test that digest produces consistent results when data is fed in many small + * updates vs. a single large update. Exercises the chunked update path + * (F-1635). + */ +static int test_digest_multi_update_helper(OSSL_LIB_CTX *libCtx) +{ + int err; + EVP_MD_CTX *ctx = NULL; + EVP_MD *md = NULL; + unsigned char data[8192]; + unsigned char digestOne[64]; + unsigned char digestMulti[64]; + unsigned int dLenOne = sizeof(digestOne); + unsigned int dLenMulti = sizeof(digestMulti); + size_t i; + + RAND_bytes(data, sizeof(data)); + + err = (md = EVP_MD_fetch(libCtx, "SHA-256", "")) == NULL; + + /* Single update */ + if (err == 0) { + err = (ctx = EVP_MD_CTX_new()) == NULL; + } + if (err == 0) { + err = EVP_DigestInit(ctx, md) != 1; + } + if (err == 0) { + err = EVP_DigestUpdate(ctx, data, sizeof(data)) != 1; + } + if (err == 0) { + err = EVP_DigestFinal_ex(ctx, digestOne, &dLenOne) != 1; + } + EVP_MD_CTX_free(ctx); + ctx = NULL; + + /* Many small updates (64 bytes each) */ + if (err == 0) { + err = (ctx = EVP_MD_CTX_new()) == NULL; + } + if (err == 0) { + err = EVP_DigestInit(ctx, md) != 1; + } + for (i = 0; err == 0 && i < sizeof(data); i += 64) { + err = EVP_DigestUpdate(ctx, data + i, 64) != 1; + } + if (err == 0) { + err = EVP_DigestFinal_ex(ctx, digestMulti, &dLenMulti) != 1; + } + if (err == 0) { + if (dLenOne != dLenMulti || + memcmp(digestOne, digestMulti, dLenOne) != 0) { + PRINT_ERR_MSG("Multi-update digest doesn't match single update"); + err = 1; + } + } + + EVP_MD_CTX_free(ctx); + EVP_MD_free(md); + return err; +} + +int test_digest_multi_update(void *data) +{ + int err; + + (void)data; + + PRINT_MSG("Digest multi-update with OpenSSL"); + err = test_digest_multi_update_helper(osslLibCtx); + if (err == 0) { + PRINT_MSG("Digest multi-update with wolfProvider"); + err = test_digest_multi_update_helper(wpLibCtx); + } + return err; +} + +/******************************************************************************/ + #endif /* WP_HAVE_DIGEST */ diff --git a/test/test_ecx.c b/test/test_ecx.c index 99304fcf..ed17ea1a 100644 --- a/test/test_ecx.c +++ b/test/test_ecx.c @@ -694,3 +694,4 @@ int test_ecx_null_init(void* data) } #endif /* defined(WP_HAVE_ED25519) || defined(WP_HAVE_ECD444) */ + diff --git a/test/test_hmac.c b/test/test_hmac.c index 2d5a8aca..41b1a651 100644 --- a/test/test_hmac.c +++ b/test/test_hmac.c @@ -295,6 +295,102 @@ int test_hmac_create(void *data) return ret; } +/******************************************************************************/ + +/** + * Test that HMAC produces consistent results when data is fed in many small + * updates vs. a single large update. Exercises the chunked update path + * (F-1639). + */ +static int test_hmac_multi_update_helper(OSSL_LIB_CTX *libCtx) +{ + int err; + EVP_MAC *emac = NULL; + EVP_MAC_CTX *ctx = NULL; + OSSL_PARAM params[3]; + char digest[] = "SHA-256"; + unsigned char key[] = "test-hmac-multi-update-key"; + unsigned char data[4096]; + unsigned char macOne[32]; + unsigned char macMulti[32]; + size_t macOneSz = sizeof(macOne); + size_t macMultiSz = sizeof(macMulti); + size_t i; + + RAND_bytes(data, sizeof(data)); + + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, + digest, 0); + params[1] = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY, + (void *)key, sizeof(key)); + params[2] = OSSL_PARAM_construct_end(); + + err = (emac = EVP_MAC_fetch(libCtx, "HMAC", NULL)) == NULL; + + /* Single update */ + if (err == 0) { + err = (ctx = EVP_MAC_CTX_new(emac)) == NULL; + } + if (err == 0) { + err = EVP_MAC_CTX_set_params(ctx, params) != 1; + } + if (err == 0) { + err = EVP_MAC_init(ctx, NULL, 0, NULL) != 1; + } + if (err == 0) { + err = EVP_MAC_update(ctx, data, sizeof(data)) != 1; + } + if (err == 0) { + err = EVP_MAC_final(ctx, macOne, &macOneSz, sizeof(macOne)) != 1; + } + EVP_MAC_CTX_free(ctx); + ctx = NULL; + + /* Many small updates (128 bytes each) */ + if (err == 0) { + err = (ctx = EVP_MAC_CTX_new(emac)) == NULL; + } + if (err == 0) { + err = EVP_MAC_CTX_set_params(ctx, params) != 1; + } + if (err == 0) { + err = EVP_MAC_init(ctx, NULL, 0, NULL) != 1; + } + for (i = 0; err == 0 && i < sizeof(data); i += 128) { + err = EVP_MAC_update(ctx, data + i, 128) != 1; + } + if (err == 0) { + err = EVP_MAC_final(ctx, macMulti, &macMultiSz, + sizeof(macMulti)) != 1; + } + if (err == 0) { + if (macOneSz != macMultiSz || + memcmp(macOne, macMulti, macOneSz) != 0) { + PRINT_ERR_MSG("Multi-update HMAC doesn't match single update"); + err = 1; + } + } + + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(emac); + return err; +} + +int test_hmac_multi_update(void *data) +{ + int err; + + (void)data; + + PRINT_MSG("HMAC multi-update with OpenSSL"); + err = test_hmac_multi_update_helper(osslLibCtx); + if (err == 0) { + PRINT_MSG("HMAC multi-update with wolfProvider"); + err = test_hmac_multi_update_helper(wpLibCtx); + } + return err; +} + #endif /* WP_HAVE_HMAC */ diff --git a/test/test_rsa.c b/test/test_rsa.c index 5cf07b35..df1607cc 100644 --- a/test/test_rsa.c +++ b/test/test_rsa.c @@ -2134,4 +2134,136 @@ int test_rsa_null_init(void* data) return err; } +/******************************************************************************/ + +/** + * Test that RSA param import rejects prefix-matching param names (F-505). + * The old code used XSTRNCMP with strlen(p->key) which let "rsa-factor" + * (without the "1" suffix) incorrectly match "rsa-factor1" in the param table. + */ +int test_rsa_param_prefix_match(void* data) +{ + int err = 0; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + BIGNUM *factor = NULL; + unsigned long rsa_n = 0xbc747fc5; + unsigned long rsa_e = 0x10001; + unsigned long rsa_d = 0x7b133399; + unsigned long bogus = 0xdeadbeef; + + (void)data; + + PRINT_MSG("Test RSA param prefix match (wolfProvider)"); + + ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "RSA", NULL); + if (ctx == NULL) { + err = 1; + } + if (err == 0) { + err = EVP_PKEY_fromdata_init(ctx) != 1; + } + if (err == 0) { + /* Import with "rsa-factor" — a prefix of "rsa-factor1". + * On unfixed code, this prefix-matches "rsa-factor1" and loads + * the bogus value into the p factor slot. On fixed code, the + * param is ignored (unknown name). */ + OSSL_PARAM params[] = { + OSSL_PARAM_ulong("n", &rsa_n), + OSSL_PARAM_ulong("e", &rsa_e), + OSSL_PARAM_ulong("d", &rsa_d), + OSSL_PARAM_ulong(OSSL_PKEY_PARAM_RSA_FACTOR, &bogus), + OSSL_PARAM_END + }; + + PRINT_MSG("Import with prefix param '" OSSL_PKEY_PARAM_RSA_FACTOR "'"); + err = EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) != 1; + } + if (err == 0) { + /* Read back rsa-factor1. On fixed code, it should not be set. */ + PRINT_MSG("Read back rsa-factor1 to check for prefix contamination"); + if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, + &factor) == 1 && factor != NULL) { + if (BN_get_word(factor) == bogus) { + PRINT_ERR_MSG("Prefix param incorrectly matched rsa-factor1"); + err = 1; + } + } + } + + BN_free(factor); + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + return err; +} + +/******************************************************************************/ + +/** + * Test that RSA KEM rejects prefix-matching operation names (F-834). + * The old code used XSTRNCMP with sizeof-1 which let "RSASVE_extra" + * incorrectly match "RSASVE". + */ +int test_rsa_kem_prefix_match(void* data) +{ + int err = 0; + EVP_PKEY_CTX *genCtx = NULL; + EVP_PKEY_CTX *kemCtx = NULL; + EVP_PKEY *pkey = NULL; + int rc; + + (void)data; + + PRINT_MSG("Test RSA KEM operation prefix match (wolfProvider)"); + + /* Generate RSA key */ + PRINT_MSG("Generate RSA 2048 key for KEM prefix test"); + genCtx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "RSA", NULL); + if (genCtx == NULL) { + err = 1; + } + if (err == 0) { + err = EVP_PKEY_keygen_init(genCtx) != 1; + } + if (err == 0) { + err = EVP_PKEY_CTX_set_rsa_keygen_bits(genCtx, 2048) != 1; + } + if (err == 0) { + err = EVP_PKEY_keygen(genCtx, &pkey) != 1; + } + + /* Try KEM encapsulate init with wrong operation name */ + if (err == 0) { + kemCtx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, pkey, NULL); + if (kemCtx == NULL) { + err = 1; + } + } + if (err == 0) { + err = EVP_PKEY_encapsulate_init(kemCtx, NULL) != 1; + } + if (err == 0) { + /* Set a bogus operation that is a prefix extension of RSASVE. + * On unfixed code, this prefix-matches and is accepted. + * On fixed code, it is rejected. */ + OSSL_PARAM params[] = { + OSSL_PARAM_utf8_string(OSSL_KEM_PARAM_OPERATION, + (char *)"RSASVE_extra", 0), + OSSL_PARAM_END + }; + + PRINT_MSG("Set KEM operation to 'RSASVE_extra' (should fail)"); + rc = EVP_PKEY_CTX_set_params(kemCtx, params); + if (rc == 1) { + PRINT_ERR_MSG("Prefix operation name incorrectly accepted"); + err = 1; + } + } + + EVP_PKEY_CTX_free(kemCtx); + EVP_PKEY_CTX_free(genCtx); + EVP_PKEY_free(pkey); + return err; +} + #endif /* WP_HAVE_RSA */ diff --git a/test/unit.c b/test/unit.c index 564b05ae..2844c3fb 100644 --- a/test/unit.c +++ b/test/unit.c @@ -202,11 +202,14 @@ TEST_CASE test_case[] = { #ifdef WP_HAVE_SHAKE_256 TEST_DECL(test_shake_256, NULL), #endif + TEST_DECL(test_digest_multi_update, NULL), #ifdef WP_HAVE_HMAC TEST_DECL(test_hmac_create, NULL), + TEST_DECL(test_hmac_multi_update, NULL), #endif #ifdef WP_HAVE_CMAC TEST_DECL(test_cmac_create, &flags), + TEST_DECL(test_cmac_multi_update, &flags), #endif #ifdef WP_HAVE_GMAC TEST_DECL(test_gmac_create, &flags), @@ -248,6 +251,7 @@ TEST_CASE test_case[] = { TEST_DECL(test_aes256_cbc_stream, NULL), TEST_DECL(test_aes256_cbc_multiple, NULL), TEST_DECL(test_aes256_cbc_bad_pad, NULL), + TEST_DECL(test_aes_cbc_large_update, NULL), #endif #ifdef WP_HAVE_AESCTR TEST_DECL(test_aes128_ctr_stream, NULL), @@ -271,6 +275,7 @@ TEST_CASE test_case[] = { TEST_DECL(test_aes128_gcm_fixed, NULL), TEST_DECL(test_aes128_gcm_tls, NULL), TEST_DECL(test_aes128_gcm_set_iv_inv, NULL), + TEST_DECL(test_aes128_gcm_key_then_iv, NULL), #endif #ifdef WP_HAVE_AESCCM TEST_DECL(test_aes128_ccm, NULL), @@ -317,6 +322,8 @@ TEST_CASE test_case[] = { TEST_DECL(test_rsa_decode_pkcs8, NULL), TEST_DECL(test_rsa_encode_pkcs8, NULL), TEST_DECL(test_rsa_null_init, NULL), + TEST_DECL(test_rsa_param_prefix_match, NULL), + TEST_DECL(test_rsa_kem_prefix_match, NULL), #endif /* WP_HAVE_RSA */ #ifdef WP_HAVE_EC_P192 #ifdef WP_HAVE_ECKEYGEN diff --git a/test/unit.h b/test/unit.h index 41557ebb..285df88f 100644 --- a/test/unit.h +++ b/test/unit.h @@ -117,13 +117,17 @@ int test_shake_256(void *data); #endif /* WP_HAVE_DIGEST */ +int test_digest_multi_update(void *data); + #ifdef WP_HAVE_HMAC int test_hmac_create(void *data); +int test_hmac_multi_update(void *data); #endif /* WP_HAVE_HMAC */ #ifdef WP_HAVE_CMAC int test_cmac_create(void *data); -#endif /* WP_HAVE_HMAC */ +int test_cmac_multi_update(void *data); +#endif /* WP_HAVE_CMAC */ #ifdef WP_HAVE_GMAC int test_gmac_create(void *data); @@ -172,6 +176,7 @@ int test_aes192_cbc_stream(void *data); int test_aes256_cbc_stream(void *data); int test_aes256_cbc_multiple(void *data); int test_aes256_cbc_bad_pad(void *data); +int test_aes_cbc_large_update(void *data); #endif @@ -201,6 +206,7 @@ int test_aes256_gcm(void *data); int test_aes128_gcm_fixed(void *data); int test_aes128_gcm_tls(void *data); int test_aes128_gcm_set_iv_inv(void *data); +int test_aes128_gcm_key_then_iv(void *data); #endif /* WP_HAVE_AESGCM */ @@ -283,6 +289,8 @@ int test_rsa_fromdata(void* data); int test_rsa_decode_pkcs8(void* data); int test_rsa_encode_pkcs8(void* data); int test_rsa_null_init(void* data); +int test_rsa_param_prefix_match(void* data); +int test_rsa_kem_prefix_match(void* data); #endif /* WP_HAVE_RSA */ #ifdef WP_HAVE_DH