From 50ce5d3fabccb1bdd5f714b9381a808660a0d20f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 7 Apr 2026 13:03:56 -0700 Subject: [PATCH 1/8] F-2222 - https://fenrir.wolfssl.com/finding/2222 - Add X25519 ECDH known-answer test (RFC 7748 Sec 6.1) --- test/test_ecc.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++ test/unit.c | 1 + test/unit.h | 4 +++ 3 files changed, 77 insertions(+) diff --git a/test/test_ecc.c b/test/test_ecc.c index 3d235470..d90e5854 100644 --- a/test/test_ecc.c +++ b/test/test_ecc.c @@ -970,6 +970,78 @@ int test_ecdh_p521(void *data) } #endif /* WP_HAVE_EC_P521 */ +#ifdef WP_HAVE_X25519 +/* RFC 7748 Section 6.1 X25519 test vectors. */ +static const unsigned char x25519_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 unsigned char x25519_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 unsigned char x25519_shared_secret[] = { + 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 +}; + +/* + * X25519 known-answer ECDH test using RFC 7748 Section 6.1 vectors. + * Validates order-reduction and endian-swap logic in wp_x25519_derive by + * comparing the derived shared secret against the expected RFC output. + */ +int test_ecdh_x25519_vector(void *data) +{ + int err = 0; + EVP_PKEY *keyA = NULL; + EVP_PKEY *keyB = NULL; + unsigned char *secret = NULL; + + (void)data; + + PRINT_MSG("X25519 ECDH known-answer test (RFC 7748 Sec 6.1)"); + + keyA = EVP_PKEY_new_raw_private_key_ex(wpLibCtx, "X25519", NULL, + x25519_alice_priv, sizeof(x25519_alice_priv)); + if (keyA == NULL) { + PRINT_ERR_MSG("Failed to import X25519 Alice private key"); + err = 1; + } + if (err == 0) { + keyB = EVP_PKEY_new_raw_private_key_ex(wpLibCtx, "X25519", NULL, + x25519_bob_priv, sizeof(x25519_bob_priv)); + if (keyB == NULL) { + PRINT_ERR_MSG("Failed to import X25519 Bob private key"); + err = 1; + } + } + if (err == 0) { + err = test_ecdh_derive(keyA, keyB, &secret, 32); + } + if (err == 0) { + if (memcmp(secret, x25519_shared_secret, 32) != 0) { + PRINT_ERR_MSG("X25519 shared secret does not match expected"); + PRINT_BUFFER("Got", secret, 32); + PRINT_BUFFER("Expected", + (unsigned char *)x25519_shared_secret, 32); + err = 1; + } + } + + OPENSSL_free(secret); + EVP_PKEY_free(keyB); + EVP_PKEY_free(keyA); + + return err; +} +#endif /* WP_HAVE_X25519 */ + #endif /* WP_HAVE_ECDH */ #ifdef WP_HAVE_ECDSA diff --git a/test/unit.c b/test/unit.c index 564b05ae..042a46f4 100644 --- a/test/unit.c +++ b/test/unit.c @@ -407,6 +407,7 @@ TEST_CASE test_case[] = { #ifdef WP_HAVE_ECKEYGEN TEST_DECL(test_ecdh_x25519_keygen, NULL), #endif + TEST_DECL(test_ecdh_x25519_vector, NULL), #endif #endif #ifdef WP_HAVE_X448 diff --git a/test/unit.h b/test/unit.h index 41557ebb..696346a4 100644 --- a/test/unit.h +++ b/test/unit.h @@ -358,6 +358,10 @@ int test_ecdh_x448_keygen(void *data); #endif /* WP_HAVE_ECKEYGEN */ +#ifdef WP_HAVE_X25519 +int test_ecdh_x25519_vector(void *data); +#endif /* WP_HAVE_X25519 */ + #ifdef WP_HAVE_EC_P192 int test_ecdh_p192(void *data); #endif /* WP_HAVE_EC_P192 */ From d8a89b43ace79040417ef61bb2a7e8c5a3eb481f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 7 Apr 2026 13:32:29 -0700 Subject: [PATCH 2/8] F-2223 - https://fenrir.wolfssl.com/finding/2223 - Add X448 ECDH known-answer test (RFC 7748 Sec 6.2) --- test/test_ecc.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ test/unit.c | 1 + test/unit.h | 3 ++ 3 files changed, 85 insertions(+) diff --git a/test/test_ecc.c b/test/test_ecc.c index d90e5854..a3e3fdc4 100644 --- a/test/test_ecc.c +++ b/test/test_ecc.c @@ -1042,6 +1042,87 @@ int test_ecdh_x25519_vector(void *data) } #endif /* WP_HAVE_X25519 */ +#ifdef WP_HAVE_X448 +/* RFC 7748 Section 6.2 X448 test vectors (from OpenSSL evppkey_ecx.txt). */ +static const unsigned char x448_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 unsigned char x448_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 unsigned char x448_shared_secret[] = { + 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 +}; + +/* + * X448 known-answer ECDH test using RFC 7748 Section 6.2 vectors. + * Validates endian-swap logic in wp_x448_derive by comparing the + * derived shared secret against the expected RFC output. + */ +int test_ecdh_x448_vector(void *data) +{ + int err = 0; + EVP_PKEY *keyA = NULL; + EVP_PKEY *keyB = NULL; + unsigned char *secret = NULL; + + (void)data; + + PRINT_MSG("X448 ECDH known-answer test (RFC 7748 Sec 6.2)"); + + keyA = EVP_PKEY_new_raw_private_key_ex(wpLibCtx, "X448", NULL, + x448_alice_priv, sizeof(x448_alice_priv)); + if (keyA == NULL) { + PRINT_ERR_MSG("Failed to import X448 Alice private key"); + err = 1; + } + if (err == 0) { + keyB = EVP_PKEY_new_raw_private_key_ex(wpLibCtx, "X448", NULL, + x448_bob_priv, sizeof(x448_bob_priv)); + if (keyB == NULL) { + PRINT_ERR_MSG("Failed to import X448 Bob private key"); + err = 1; + } + } + if (err == 0) { + err = test_ecdh_derive(keyA, keyB, &secret, 56); + } + if (err == 0) { + if (memcmp(secret, x448_shared_secret, 56) != 0) { + PRINT_ERR_MSG("X448 shared secret does not match expected"); + PRINT_BUFFER("Got", secret, 56); + PRINT_BUFFER("Expected", + (unsigned char *)x448_shared_secret, 56); + err = 1; + } + } + + OPENSSL_free(secret); + EVP_PKEY_free(keyB); + EVP_PKEY_free(keyA); + + return err; +} +#endif /* WP_HAVE_X448 */ + #endif /* WP_HAVE_ECDH */ #ifdef WP_HAVE_ECDSA diff --git a/test/unit.c b/test/unit.c index 042a46f4..bcf66a7f 100644 --- a/test/unit.c +++ b/test/unit.c @@ -418,6 +418,7 @@ TEST_CASE test_case[] = { #ifdef WP_HAVE_ECKEYGEN TEST_DECL(test_ecdh_x448_keygen, NULL), #endif + TEST_DECL(test_ecdh_x448_vector, NULL), #endif #endif #ifdef WP_HAVE_ECKEYGEN diff --git a/test/unit.h b/test/unit.h index 696346a4..579d4c10 100644 --- a/test/unit.h +++ b/test/unit.h @@ -361,6 +361,9 @@ int test_ecdh_x448_keygen(void *data); #ifdef WP_HAVE_X25519 int test_ecdh_x25519_vector(void *data); #endif /* WP_HAVE_X25519 */ +#ifdef WP_HAVE_X448 +int test_ecdh_x448_vector(void *data); +#endif /* WP_HAVE_X448 */ #ifdef WP_HAVE_EC_P192 int test_ecdh_p192(void *data); From ed31f71d6908b843a6679b9f908354e7332f9f98 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 7 Apr 2026 13:40:05 -0700 Subject: [PATCH 3/8] F-2041 - https://fenrir.wolfssl.com/finding/2041 - Fix clear_seed incorrectly setting SEED-SRC state to UNINITIALISED --- src/wp_seed_src.c | 4 +- test/test_rand_seed.c | 107 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/src/wp_seed_src.c b/src/wp_seed_src.c index fd4b3dad..09186099 100644 --- a/src/wp_seed_src.c +++ b/src/wp_seed_src.c @@ -689,10 +689,8 @@ static size_t wp_seed_src_get_seed(wp_SeedSrcCtx* ctx, unsigned char** pSeed, static void wp_seed_src_clear_seed(wp_SeedSrcCtx* ctx, unsigned char* seed, size_t seedLen) { + (void)ctx; OPENSSL_secure_clear_free(seed, seedLen); - if (ctx != NULL) { - ctx->state = EVP_RAND_STATE_UNINITIALISED; - } } /** diff --git a/test/test_rand_seed.c b/test/test_rand_seed.c index 1692c897..4d08aa29 100644 --- a/test/test_rand_seed.c +++ b/test/test_rand_seed.c @@ -421,6 +421,110 @@ static int test_seed_src_three_level(OSSL_LIB_CTX *libCtx, const char *propq) return err; } +/** + * Test that SEED-SRC remains usable after a child DRBG instantiates from it. + * Instantiates two child DRBGs from the same SEED-SRC parent and verifies + * both can generate random bytes. + */ +static int test_seed_src_multi_child(OSSL_LIB_CTX *libCtx, const char *propq) +{ + int err = 0; + EVP_RAND *seed_src = NULL; + EVP_RAND *ctr_drbg = NULL; + EVP_RAND_CTX *seed_ctx = NULL; + EVP_RAND_CTX *child1_ctx = NULL; + EVP_RAND_CTX *child2_ctx = NULL; + unsigned char buf[32]; + OSSL_PARAM params[2]; + + PRINT_MSG("Testing SEED-SRC survives multiple child DRBG instantiations"); + + seed_src = EVP_RAND_fetch(libCtx, "SEED-SRC", propq); + ctr_drbg = EVP_RAND_fetch(libCtx, "CTR-DRBG", propq); + if (seed_src == NULL || ctr_drbg == NULL) { + PRINT_ERR_MSG("Failed to fetch RAND algorithms"); + err = 1; + goto cleanup; + } + + seed_ctx = EVP_RAND_CTX_new(seed_src, NULL); + if (seed_ctx == NULL) { + PRINT_ERR_MSG("Failed to create SEED-SRC context"); + err = 1; + goto cleanup; + } + if (EVP_RAND_instantiate(seed_ctx, 0, 0, NULL, 0, NULL) != 1) { + PRINT_ERR_MSG("Failed to instantiate SEED-SRC"); + err = 1; + goto cleanup; + } + + /* First child DRBG: instantiate from SEED-SRC (calls get_seed + clear_seed) */ + child1_ctx = EVP_RAND_CTX_new(ctr_drbg, seed_ctx); + if (child1_ctx == NULL) { + PRINT_ERR_MSG("Failed to create first child DRBG"); + err = 1; + goto cleanup; + } + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_DRBG_PARAM_CIPHER, + (char*)"AES-256-CTR", 0); + params[1] = OSSL_PARAM_construct_end(); + if (EVP_RAND_CTX_set_params(child1_ctx, params) != 1) { + PRINT_ERR_MSG("Failed to set first child DRBG params"); + err = 1; + goto cleanup; + } + if (EVP_RAND_instantiate(child1_ctx, 256, 0, NULL, 0, NULL) != 1) { + PRINT_ERR_MSG("Failed to instantiate first child DRBG"); + err = 1; + goto cleanup; + } + PRINT_MSG("First child DRBG instantiated OK"); + + /* Second child DRBG: instantiate from same SEED-SRC. + * Before the fix, this fails because clear_seed set SEED-SRC to + * UNINITIALISED and get_seed returns 0. */ + child2_ctx = EVP_RAND_CTX_new(ctr_drbg, seed_ctx); + if (child2_ctx == NULL) { + PRINT_ERR_MSG("Failed to create second child DRBG"); + err = 1; + goto cleanup; + } + if (EVP_RAND_CTX_set_params(child2_ctx, params) != 1) { + PRINT_ERR_MSG("Failed to set second child DRBG params"); + err = 1; + goto cleanup; + } + if (EVP_RAND_instantiate(child2_ctx, 256, 0, NULL, 0, NULL) != 1) { + PRINT_ERR_MSG("Failed to instantiate second child DRBG from same " + "SEED-SRC"); + err = 1; + goto cleanup; + } + PRINT_MSG("Second child DRBG instantiated OK"); + + /* Verify both children can generate */ + if (EVP_RAND_generate(child1_ctx, buf, sizeof(buf), 256, 0, NULL, 0) != 1) { + PRINT_ERR_MSG("Failed to generate from first child DRBG"); + err = 1; + goto cleanup; + } + if (EVP_RAND_generate(child2_ctx, buf, sizeof(buf), 256, 0, NULL, 0) != 1) { + PRINT_ERR_MSG("Failed to generate from second child DRBG"); + err = 1; + goto cleanup; + } + PRINT_MSG("Both child DRBGs generate OK after shared SEED-SRC parent"); + +cleanup: + EVP_RAND_CTX_free(child2_ctx); + EVP_RAND_CTX_free(child1_ctx); + EVP_RAND_CTX_free(seed_ctx); + EVP_RAND_free(ctr_drbg); + EVP_RAND_free(seed_src); + return err; +} + /** * Main test entry point - runs tests with OpenSSL default provider and wolfProvider. */ @@ -462,6 +566,9 @@ int test_rand_seed(void *data) if (err == 0) { err = test_seed_src_three_level(wpLibCtx, NULL); } + if (err == 0) { + err = test_seed_src_multi_child(wpLibCtx, NULL); + } if (err == 0) { PRINT_MSG("=== All DRBG SEED-SRC hierarchy tests passed ==="); From 446aad8d89ccfec99fcdc917b2be17f3f601f946 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 7 Apr 2026 13:43:15 -0700 Subject: [PATCH 4/8] F-2224 - https://fenrir.wolfssl.com/finding/2224 - Add GCM streaming decryption test with tampered authentication tag --- test/test_aestag.c | 110 +++++++++++++++++++++++++++++++++++++++++++++ test/unit.c | 1 + test/unit.h | 1 + 3 files changed, 112 insertions(+) diff --git a/test/test_aestag.c b/test/test_aestag.c index bc21c4e1..e72538c6 100644 --- a/test/test_aestag.c +++ b/test/test_aestag.c @@ -1196,6 +1196,116 @@ int test_aes128_gcm_set_iv_inv(void *data) EVP_GCM_TLS_FIXED_IV_LEN, 12); } +/******************************************************************************/ + +/* + * GCM streaming decryption with a tampered authentication tag. + * Verifies that DecryptFinal correctly rejects a forged tag. + */ +static int test_aes_gcm_bad_tag_helper(OSSL_LIB_CTX *libCtx, + const char *cipherName, int keyLen) +{ + int err = 0; + EVP_CIPHER *cipher = NULL; + EVP_CIPHER_CTX *ctx = NULL; + unsigned char key[32]; + unsigned char iv[12]; + unsigned char aad[] = "additional data"; + unsigned char pt[] = "GCM plaintext for tag test"; + unsigned char ct[64]; + unsigned char tag[16]; + unsigned char dec[64]; + int outLen = 0, fLen = 0; + + memset(key, 0xAA, keyLen); + memset(iv, 0xBB, sizeof(iv)); + + cipher = EVP_CIPHER_fetch(libCtx, cipherName, ""); + if (cipher == NULL) { + err = 1; + } + + /* Encrypt */ + if (err == 0) { + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + err = 1; + } + if (err == 0) { + err = EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, NULL, &outLen, aad, + (int)sizeof(aad)) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, ct, &outLen, pt, + (int)sizeof(pt)) != 1; + } + if (err == 0) { + err = EVP_EncryptFinal_ex(ctx, ct + outLen, &fLen) != 1; + outLen += fLen; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, tag) != 1; + } + EVP_CIPHER_CTX_free(ctx); + ctx = NULL; + + /* Tamper with the tag */ + if (err == 0) { + tag[0] ^= 0x01; + } + + /* Decrypt with tampered tag -- must fail at DecryptFinal */ + if (err == 0) { + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + err = 1; + } + if (err == 0) { + err = EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv) != 1; + } + if (err == 0) { + err = EVP_DecryptUpdate(ctx, NULL, &fLen, aad, + (int)sizeof(aad)) != 1; + } + if (err == 0) { + err = EVP_DecryptUpdate(ctx, dec, &fLen, ct, outLen) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, tag) != 1; + } + if (err == 0) { + int ret = EVP_DecryptFinal_ex(ctx, dec + fLen, &fLen); + if (ret == 1) { + PRINT_ERR_MSG("%s bad-tag: DecryptFinal should have failed", + cipherName); + err = 1; + } + } + + EVP_CIPHER_CTX_free(ctx); + EVP_CIPHER_free(cipher); + return err; +} + +int test_aes_gcm_bad_tag(void *data) +{ + int err = 0; + + (void)data; + + PRINT_MSG("AES-128-GCM streaming decryption with tampered tag"); + err = test_aes_gcm_bad_tag_helper(wpLibCtx, "AES-128-GCM", 16); + if (err == 0) { + PRINT_MSG("AES-256-GCM streaming decryption with tampered tag"); + err = test_aes_gcm_bad_tag_helper(wpLibCtx, "AES-256-GCM", 32); + } + + return err; +} + #endif /* WP_HAVE_AESGCM */ /******************************************************************************/ diff --git a/test/unit.c b/test/unit.c index bcf66a7f..a70202f6 100644 --- a/test/unit.c +++ b/test/unit.c @@ -271,6 +271,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_aes_gcm_bad_tag, NULL), #endif #ifdef WP_HAVE_AESCCM TEST_DECL(test_aes128_ccm, NULL), diff --git a/test/unit.h b/test/unit.h index 579d4c10..9b0e4939 100644 --- a/test/unit.h +++ b/test/unit.h @@ -201,6 +201,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_aes_gcm_bad_tag(void *data); #endif /* WP_HAVE_AESGCM */ From c4fd09956a4da3229450b57627063801204b9fb8 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 7 Apr 2026 13:45:21 -0700 Subject: [PATCH 5/8] F-2225 - https://fenrir.wolfssl.com/finding/2225 - Add CCM streaming decryption test with tampered authentication tag --- test/test_aestag.c | 136 +++++++++++++++++++++++++++++++++++++++++++++ test/unit.c | 1 + test/unit.h | 1 + 3 files changed, 138 insertions(+) diff --git a/test/test_aestag.c b/test/test_aestag.c index e72538c6..9c303a71 100644 --- a/test/test_aestag.c +++ b/test/test_aestag.c @@ -1355,5 +1355,141 @@ int test_aes128_ccm_tls(void *data) EVP_CCM_TLS_FIXED_IV_LEN, 1); } +/******************************************************************************/ + +/* + * CCM streaming decryption with a tampered authentication tag. + * Verifies that DecryptFinal correctly rejects a forged tag. + */ +static int test_aes_ccm_bad_tag_helper(OSSL_LIB_CTX *libCtx, + const char *cipherName, int keyLen) +{ + int err = 0; + EVP_CIPHER *cipher = NULL; + EVP_CIPHER_CTX *ctx = NULL; + unsigned char key[32]; + unsigned char iv[13]; + unsigned char aad[] = "additional data"; + unsigned char pt[] = "CCM plaintext for tag test"; + int ptLen = (int)sizeof(pt); + unsigned char ct[64]; + unsigned char tag[16]; + unsigned char dec[64]; + int outLen = 0, fLen = 0; + + memset(key, 0xAA, keyLen); + memset(iv, 0xBB, sizeof(iv)); + + cipher = EVP_CIPHER_fetch(libCtx, cipherName, ""); + if (cipher == NULL) { + err = 1; + } + + /* Encrypt */ + if (err == 0) { + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + err = 1; + } + if (err == 0) { + err = EVP_EncryptInit(ctx, cipher, NULL, NULL) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, + (int)sizeof(iv), NULL) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, NULL) != 1; + } + if (err == 0) { + err = EVP_EncryptInit(ctx, NULL, key, iv) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, NULL, &outLen, NULL, ptLen) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, NULL, &outLen, aad, + (int)sizeof(aad)) != 1; + } + if (err == 0) { + err = EVP_EncryptUpdate(ctx, ct, &outLen, pt, ptLen) != 1; + } + if (err == 0) { + err = EVP_EncryptFinal_ex(ctx, ct + outLen, &fLen) != 1; + outLen += fLen; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, tag) != 1; + } + EVP_CIPHER_CTX_free(ctx); + ctx = NULL; + + /* Tamper with the tag */ + if (err == 0) { + tag[0] ^= 0x01; + } + + /* Decrypt with tampered tag -- must fail */ + if (err == 0) { + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + err = 1; + } + if (err == 0) { + err = EVP_DecryptInit(ctx, cipher, NULL, NULL) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, + (int)sizeof(iv), NULL) != 1; + } + if (err == 0) { + err = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, tag) != 1; + } + if (err == 0) { + err = EVP_DecryptInit(ctx, NULL, key, iv) != 1; + } + if (err == 0) { + err = EVP_DecryptUpdate(ctx, NULL, &fLen, NULL, outLen) != 1; + } + if (err == 0) { + err = EVP_DecryptUpdate(ctx, NULL, &fLen, aad, + (int)sizeof(aad)) != 1; + } + if (err == 0) { + /* CCM DecryptUpdate should fail with bad tag */ + int ret = EVP_DecryptUpdate(ctx, dec, &fLen, ct, outLen); + if (ret == 1) { + /* If Update succeeded, Final must fail */ + ret = EVP_DecryptFinal_ex(ctx, dec + fLen, &fLen); + if (ret == 1) { + PRINT_ERR_MSG("%s bad-tag: decryption should have failed", + cipherName); + err = 1; + } + } + /* else: Update failed, which is also correct for CCM bad tag */ + } + + EVP_CIPHER_CTX_free(ctx); + EVP_CIPHER_free(cipher); + return err; +} + +int test_aes_ccm_bad_tag(void *data) +{ + int err = 0; + + (void)data; + + PRINT_MSG("AES-128-CCM streaming decryption with tampered tag"); + err = test_aes_ccm_bad_tag_helper(wpLibCtx, "AES-128-CCM", 16); + if (err == 0) { + PRINT_MSG("AES-256-CCM streaming decryption with tampered tag"); + err = test_aes_ccm_bad_tag_helper(wpLibCtx, "AES-256-CCM", 32); + } + + return err; +} + #endif /* WP_HAVE_AESCCM */ diff --git a/test/unit.c b/test/unit.c index a70202f6..5c8f24f4 100644 --- a/test/unit.c +++ b/test/unit.c @@ -279,6 +279,7 @@ TEST_CASE test_case[] = { TEST_DECL(test_aes256_ccm, NULL), #if OPENSSL_VERSION_NUMBER >= 0x10100000L TEST_DECL(test_aes128_ccm_tls, NULL), + TEST_DECL(test_aes_ccm_bad_tag, NULL), #endif #endif #ifdef WP_HAVE_RANDOM diff --git a/test/unit.h b/test/unit.h index 9b0e4939..61d0b8ad 100644 --- a/test/unit.h +++ b/test/unit.h @@ -211,6 +211,7 @@ int test_aes128_ccm(void *data); int test_aes192_ccm(void *data); int test_aes256_ccm(void *data); int test_aes128_ccm_tls(void *data); +int test_aes_ccm_bad_tag(void *data); #endif /* WP_HAVE_AESCCM */ From bbffef27c6497ec0dcd3276bdd285650481daa5f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 7 Apr 2026 13:49:42 -0700 Subject: [PATCH 6/8] F-2226 - https://fenrir.wolfssl.com/finding/2226 - Add HKDF extract-only output-length validation test --- test/test_hkdf.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/test_hkdf.c b/test/test_hkdf.c index 9a09814a..a0d40c3f 100644 --- a/test/test_hkdf.c +++ b/test/test_hkdf.c @@ -535,6 +535,77 @@ static int test_hkdf_fail(void) return err; } +/* + * Test that HKDF Extract-Only mode rejects output lengths that do not match + * the hash digest size. + */ +static int test_hkdf_extract_only_bad_len(OSSL_LIB_CTX *libCtx) +{ + int err = 0; + EVP_PKEY_CTX *ctx = NULL; + unsigned char inKey[32] = { 0, }; + unsigned char salt[32] = { 0, }; + unsigned char out[64]; + size_t len; + int mdSize; + + PRINT_MSG("HKDF Extract-Only with wrong output length"); + + mdSize = EVP_MD_get_size(EVP_sha256()); + + ctx = EVP_PKEY_CTX_new_from_name(libCtx, "HKDF", NULL); + if (ctx == NULL) { + err = 1; + } + if (err == 0) { + err = EVP_PKEY_derive_init(ctx) != 1; + } + if (err == 0) { + err = EVP_PKEY_CTX_hkdf_mode(ctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != 1; + } + if (err == 0) { + err = EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256()) != 1; + } + if (err == 0) { + err = EVP_PKEY_CTX_set1_hkdf_key(ctx, inKey, sizeof(inKey)) != 1; + } + if (err == 0) { + err = EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt, sizeof(salt)) != 1; + } + + /* Request wrong length (too short) -- must fail */ + if (err == 0) { + len = (size_t)(mdSize - 1); + if (EVP_PKEY_derive(ctx, out, &len) == 1) { + PRINT_ERR_MSG("Extract-Only should reject len %zu (md=%d)", + len, mdSize); + err = 1; + } + } + + /* Request wrong length (too long) -- must fail */ + if (err == 0) { + len = (size_t)(mdSize + 1); + if (EVP_PKEY_derive(ctx, out, &len) == 1) { + PRINT_ERR_MSG("Extract-Only should reject len %zu (md=%d)", + len, mdSize); + err = 1; + } + } + + /* Request correct length -- must succeed */ + if (err == 0) { + len = (size_t)mdSize; + if (EVP_PKEY_derive(ctx, out, &len) != 1) { + PRINT_ERR_MSG("Extract-Only should accept len %zu", len); + err = 1; + } + } + + EVP_PKEY_CTX_free(ctx); + return err; +} + #define NUM_MODES 3 int test_hkdf(void *data) @@ -574,6 +645,9 @@ int test_hkdf(void *data) if (err == 0) { err = test_hkdf_fail(); } + if (err == 0) { + err = test_hkdf_extract_only_bad_len(wpLibCtx); + } return err; } From 7e9ea088a8e833408eecdc3cf5bb0eac270be3cb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 7 Apr 2026 13:58:24 -0700 Subject: [PATCH 7/8] F-2235 - https://fenrir.wolfssl.com/finding/2235 - Fix ECX get_params to restore unclamped private key bytes --- src/wp_ecx_kmgmt.c | 4 +++ test/test_ecx.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++ test/unit.c | 3 +++ test/unit.h | 3 +++ 4 files changed, 77 insertions(+) diff --git a/src/wp_ecx_kmgmt.c b/src/wp_ecx_kmgmt.c index 2cbaa839..ed7a25d6 100644 --- a/src/wp_ecx_kmgmt.c +++ b/src/wp_ecx_kmgmt.c @@ -560,6 +560,10 @@ static int wp_ecx_get_params_priv_key(wp_Ecx* ecx, OSSL_PARAM params[]) WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "exportPriv", rc); ok = 0; } + if (ok && ecx->clamped) { + ((unsigned char*)p->data)[0 ] = ecx->unclamped[0]; + ((unsigned char*)p->data)[outLen - 1] = ecx->unclamped[1]; + } } p->return_size = outLen; } diff --git a/test/test_ecx.c b/test/test_ecx.c index 99304fcf..325ed335 100644 --- a/test/test_ecx.c +++ b/test/test_ecx.c @@ -693,4 +693,71 @@ int test_ecx_null_init(void* data) return err; } +#ifdef WP_HAVE_X25519 +/* + * Test that importing an X25519 private key and reading it back via + * EVP_PKEY_get_params returns the original unclamped bytes. + * X25519 clamping modifies the first and last byte internally, but the + * get_params readback must return the original imported bytes. + */ +int test_ecx_x25519_raw_priv_roundtrip(void *data) +{ + int err = 0; + EVP_PKEY *pkey = NULL; + /* RFC 7748 Section 6.1 Alice private key -- first byte 0x77 clamps to + * 0x70, last byte 0x2a clamps to 0x6a. */ + static const unsigned char privKey[] = { + 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 + }; + unsigned char readback[32]; + OSSL_PARAM params[2]; + + (void)data; + + PRINT_MSG("X25519 raw private key get_params roundtrip"); + + pkey = EVP_PKEY_new_raw_private_key_ex(wpLibCtx, "X25519", NULL, + privKey, sizeof(privKey)); + if (pkey == NULL) { + PRINT_ERR_MSG("Failed to import X25519 private key"); + err = 1; + } + + /* Use EVP_PKEY_get_params to exercise the get_params code path + * (EVP_PKEY_get_raw_private_key uses the export path instead). */ + if (err == 0) { + params[0] = OSSL_PARAM_construct_octet_string( + OSSL_PKEY_PARAM_PRIV_KEY, readback, sizeof(readback)); + params[1] = OSSL_PARAM_construct_end(); + if (EVP_PKEY_get_params(pkey, params) != 1) { + PRINT_ERR_MSG("EVP_PKEY_get_params for priv key failed"); + err = 1; + } + } + if (err == 0) { + if (params[0].return_size != sizeof(privKey)) { + PRINT_ERR_MSG("Readback length mismatch: %zu vs %zu", + params[0].return_size, sizeof(privKey)); + err = 1; + } + } + if (err == 0) { + if (memcmp(readback, privKey, sizeof(privKey)) != 0) { + PRINT_ERR_MSG("X25519 private key get_params does not match " + "original (unclamped bytes not restored)"); + PRINT_BUFFER("Got", readback, (int)sizeof(privKey)); + PRINT_BUFFER("Expected", (unsigned char *)privKey, + (int)sizeof(privKey)); + err = 1; + } + } + + EVP_PKEY_free(pkey); + return err; +} +#endif /* WP_HAVE_X25519 */ + #endif /* defined(WP_HAVE_ED25519) || defined(WP_HAVE_ECD444) */ diff --git a/test/unit.c b/test/unit.c index 5c8f24f4..6fd2940e 100644 --- a/test/unit.c +++ b/test/unit.c @@ -444,6 +444,9 @@ TEST_CASE test_case[] = { TEST_DECL(test_ecx_sign_verify_raw_pub, NULL), TEST_DECL(test_ecx_misc, NULL), TEST_DECL(test_ecx_null_init, NULL), +#ifdef WP_HAVE_X25519 + TEST_DECL(test_ecx_x25519_raw_priv_roundtrip, NULL), +#endif #endif TEST_DECL(test_pkcs7_x509_sign_verify, NULL), diff --git a/test/unit.h b/test/unit.h index 61d0b8ad..499284f0 100644 --- a/test/unit.h +++ b/test/unit.h @@ -440,6 +440,9 @@ int test_ecx_sign_verify_raw_priv(void *data); int test_ecx_sign_verify_raw_pub(void *data); int test_ecx_misc(void *data); int test_ecx_null_init(void *data); +#ifdef WP_HAVE_X25519 +int test_ecx_x25519_raw_priv_roundtrip(void *data); +#endif #endif int test_pkcs7_x509_sign_verify(void *data); From ec10a34000223c12fd2f787b1ef8a3011f08adbc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 7 Apr 2026 15:07:01 -0700 Subject: [PATCH 8/8] =?UTF-8?q?Fix=20copilot=20review=20feedback=20-=20wp?= =?UTF-8?q?=5Fecx=5Fkmgmt.c=20=E2=80=94=20Added=20bounds=20guard=20(outLen?= =?UTF-8?q?=20<=202=20/=20p->data=5Fsize=20<=20outLen)=20before=20unclampe?= =?UTF-8?q?d=20byte=20restoration=20-=20test=5Fhkdf.c=20=E2=80=94=20Added?= =?UTF-8?q?=20mdSize=20<=3D=200=20early-return=20check=20after=20EVP=5FMD?= =?UTF-8?q?=5Fget=5Fsize=20-=20test=5Faestag.c=20=E2=80=94=20Changed=20siz?= =?UTF-8?q?eof(aad)=20/=20sizeof(pt)=20to=20exclude=20trailing=20NUL=20in?= =?UTF-8?q?=20both=20GCM=20and=20CCM=20helpers=20(encrypt=20+=20decrypt=20?= =?UTF-8?q?AAD,=20and=20ptLen)=20-=20unit.h=20=E2=80=94=20Added=20/*=20WP?= =?UTF-8?q?=5FHAVE=5FX25519=20*/=20comment=20to=20#endif=20-=20test=5Frand?= =?UTF-8?q?=5Fseed.c=20const=20cast=20=E2=80=94=20Skipped;=20(char*)=20is?= =?UTF-8?q?=20the=20established=20pattern=20in=20this=20file=20(4=20pre-ex?= =?UTF-8?q?isting=20instances)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wp_ecx_kmgmt.c | 9 +++++++-- test/test_aestag.c | 14 +++++++------- test/test_hkdf.c | 11 ++++++++--- test/unit.h | 2 +- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/wp_ecx_kmgmt.c b/src/wp_ecx_kmgmt.c index ed7a25d6..5c7504eb 100644 --- a/src/wp_ecx_kmgmt.c +++ b/src/wp_ecx_kmgmt.c @@ -561,8 +561,13 @@ static int wp_ecx_get_params_priv_key(wp_Ecx* ecx, OSSL_PARAM params[]) ok = 0; } if (ok && ecx->clamped) { - ((unsigned char*)p->data)[0 ] = ecx->unclamped[0]; - ((unsigned char*)p->data)[outLen - 1] = ecx->unclamped[1]; + if ((outLen < 2) || (p->data_size < outLen)) { + ok = 0; + } + else { + ((unsigned char*)p->data)[0 ] = ecx->unclamped[0]; + ((unsigned char*)p->data)[outLen - 1] = ecx->unclamped[1]; + } } } p->return_size = outLen; diff --git a/test/test_aestag.c b/test/test_aestag.c index 9c303a71..1280c1f0 100644 --- a/test/test_aestag.c +++ b/test/test_aestag.c @@ -1212,6 +1212,7 @@ static int test_aes_gcm_bad_tag_helper(OSSL_LIB_CTX *libCtx, unsigned char iv[12]; unsigned char aad[] = "additional data"; unsigned char pt[] = "GCM plaintext for tag test"; + int ptLen = (int)(sizeof(pt) - 1); unsigned char ct[64]; unsigned char tag[16]; unsigned char dec[64]; @@ -1236,11 +1237,10 @@ static int test_aes_gcm_bad_tag_helper(OSSL_LIB_CTX *libCtx, } if (err == 0) { err = EVP_EncryptUpdate(ctx, NULL, &outLen, aad, - (int)sizeof(aad)) != 1; + (int)(sizeof(aad) - 1)) != 1; } if (err == 0) { - err = EVP_EncryptUpdate(ctx, ct, &outLen, pt, - (int)sizeof(pt)) != 1; + err = EVP_EncryptUpdate(ctx, ct, &outLen, pt, ptLen) != 1; } if (err == 0) { err = EVP_EncryptFinal_ex(ctx, ct + outLen, &fLen) != 1; @@ -1268,7 +1268,7 @@ static int test_aes_gcm_bad_tag_helper(OSSL_LIB_CTX *libCtx, } if (err == 0) { err = EVP_DecryptUpdate(ctx, NULL, &fLen, aad, - (int)sizeof(aad)) != 1; + (int)(sizeof(aad) - 1)) != 1; } if (err == 0) { err = EVP_DecryptUpdate(ctx, dec, &fLen, ct, outLen) != 1; @@ -1371,7 +1371,7 @@ static int test_aes_ccm_bad_tag_helper(OSSL_LIB_CTX *libCtx, unsigned char iv[13]; unsigned char aad[] = "additional data"; unsigned char pt[] = "CCM plaintext for tag test"; - int ptLen = (int)sizeof(pt); + int ptLen = (int)(sizeof(pt) - 1); unsigned char ct[64]; unsigned char tag[16]; unsigned char dec[64]; @@ -1409,7 +1409,7 @@ static int test_aes_ccm_bad_tag_helper(OSSL_LIB_CTX *libCtx, } if (err == 0) { err = EVP_EncryptUpdate(ctx, NULL, &outLen, aad, - (int)sizeof(aad)) != 1; + (int)(sizeof(aad) - 1)) != 1; } if (err == 0) { err = EVP_EncryptUpdate(ctx, ct, &outLen, pt, ptLen) != 1; @@ -1453,7 +1453,7 @@ static int test_aes_ccm_bad_tag_helper(OSSL_LIB_CTX *libCtx, } if (err == 0) { err = EVP_DecryptUpdate(ctx, NULL, &fLen, aad, - (int)sizeof(aad)) != 1; + (int)(sizeof(aad) - 1)) != 1; } if (err == 0) { /* CCM DecryptUpdate should fail with bad tag */ diff --git a/test/test_hkdf.c b/test/test_hkdf.c index a0d40c3f..d8d940ff 100644 --- a/test/test_hkdf.c +++ b/test/test_hkdf.c @@ -552,11 +552,16 @@ static int test_hkdf_extract_only_bad_len(OSSL_LIB_CTX *libCtx) PRINT_MSG("HKDF Extract-Only with wrong output length"); mdSize = EVP_MD_get_size(EVP_sha256()); - - ctx = EVP_PKEY_CTX_new_from_name(libCtx, "HKDF", NULL); - if (ctx == NULL) { + if (mdSize <= 0) { + PRINT_ERR_MSG("EVP_MD_get_size(EVP_sha256()) failed: %d", mdSize); err = 1; } + if (err == 0) { + ctx = EVP_PKEY_CTX_new_from_name(libCtx, "HKDF", NULL); + if (ctx == NULL) { + err = 1; + } + } if (err == 0) { err = EVP_PKEY_derive_init(ctx) != 1; } diff --git a/test/unit.h b/test/unit.h index 499284f0..3278e42b 100644 --- a/test/unit.h +++ b/test/unit.h @@ -442,7 +442,7 @@ int test_ecx_misc(void *data); int test_ecx_null_init(void *data); #ifdef WP_HAVE_X25519 int test_ecx_x25519_raw_priv_roundtrip(void *data); -#endif +#endif /* WP_HAVE_X25519 */ #endif int test_pkcs7_x509_sign_verify(void *data);