From 4e4f518a906ece90baf2cd9de4576941a47fcdec Mon Sep 17 00:00:00 2001 From: Takashi Kojo Date: Tue, 3 Nov 2026 06:56:40 +0900 Subject: [PATCH 1/2] Wipe and free previous EVP_PKEY DER cache on reassignment (F-8331) --- tests/api/test_evp_pkey.c | 114 ++++++++++++++++++++++++++++++++++++++ tests/api/test_evp_pkey.h | 2 + wolfcrypt/src/evp.c | 37 +++++++++++++ 3 files changed, 153 insertions(+) diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index 21be3ac7898..ce0cc39f460 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -1011,6 +1011,120 @@ int test_wolfSSL_EVP_PKEY_set1_get1_DH(void) return EXPECT_RESULT(); } /* END test_EVP_PKEY_set1_get1_DH */ +/* Reassigning a key must wipe and free the previous DER cache. */ +int test_wolfSSL_EVP_PKEY_reassign_der_cache(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_ALL) && !defined(NO_FILESYSTEM) && \ + !defined(NO_RSA) && defined(WOLFSSL_KEY_TO_DER) && \ + !defined(NO_DSA) && !defined(HAVE_SELFTEST) && \ + !defined(WC_FIPS_186_5_PLUS) && defined(WOLFSSL_KEY_GEN) + EVP_PKEY* pkey = NULL; + EVP_PKEY* dsaPkey = NULL; + DSA* dsa = NULL; + RSA* rsa = NULL; + RSA* rsaEmpty = NULL; + XFILE f = XBADFILE; + byte dsaDer[TWOK_BUF]; + byte rsaDer[TWOK_BUF]; + const unsigned char* p = NULL; + long dsaDerSz = 0; + long rsaDerSz = 0; + int dsaPkeySz = 0; +#if !defined(NO_DH) && defined(WOLFSSL_DH_EXTRA) && \ + (!defined(HAVE_FIPS) || (defined(HAVE_FIPS_VERSION) && \ + (HAVE_FIPS_VERSION > 2))) + DH* dh = NULL; + byte dhDer[TWOK_BUF]; + long dhDerSz = 0; + int dhPkeySz = 0; +#endif + + ExpectTrue((f = XFOPEN("./certs/dsa2048.der", "rb")) != XBADFILE); + ExpectIntGT(dsaDerSz = (long)XFREAD(dsaDer, 1, sizeof(dsaDer), f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + ExpectTrue((f = XFOPEN("./certs/client-key.der", "rb")) != XBADFILE); + ExpectIntGT(rsaDerSz = (long)XFREAD(rsaDer, 1, sizeof(rsaDer), f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + + p = dsaDer; + ExpectNotNull(d2i_PrivateKey(EVP_PKEY_DSA, &dsaPkey, &p, dsaDerSz)); + ExpectNotNull(dsa = EVP_PKEY_get1_DSA(dsaPkey)); + p = rsaDer; + ExpectNotNull(rsa = d2i_RSAPrivateKey(NULL, &p, rsaDerSz)); + ExpectNotNull(rsaEmpty = RSA_new()); + ExpectNotNull(pkey = EVP_PKEY_new()); + + /* DSA, then the same DSA again. */ + ExpectIntEQ(EVP_PKEY_set1_DSA(pkey, dsa), WOLFSSL_SUCCESS); + ExpectNotNull(pkey == NULL ? NULL : pkey->pkey.ptr); + ExpectIntGT(dsaPkeySz = (pkey == NULL ? 0 : pkey->pkey_sz), 0); + ExpectIntEQ(EVP_PKEY_set1_DSA(pkey, dsa), WOLFSSL_SUCCESS); + ExpectIntEQ(pkey == NULL ? 0 : pkey->pkey_sz, dsaPkeySz); + + /* Unencodable RSA key: old DSA DER must not remain. */ + ExpectIntEQ(EVP_PKEY_assign_RSA(pkey, rsaEmpty), WOLFSSL_SUCCESS); + if (EXPECT_FAIL()) { + RSA_free(rsaEmpty); + } + rsaEmpty = NULL; + ExpectNull(pkey == NULL ? (char*)1 : pkey->pkey.ptr); + ExpectIntEQ(pkey == NULL ? -1 : pkey->pkey_sz, 0); + + /* Private RSA key: cache holds its DER. */ + ExpectIntEQ(EVP_PKEY_assign_RSA(pkey, rsa), WOLFSSL_SUCCESS); + if (EXPECT_FAIL()) { + RSA_free(rsa); + } + rsa = NULL; + ExpectIntEQ(pkey == NULL ? 0 : pkey->pkey_sz, (int)rsaDerSz); + ExpectIntEQ(pkey == NULL || pkey->pkey.ptr == NULL ? -1 : + XMEMCMP(pkey->pkey.ptr, rsaDer, (size_t)rsaDerSz), 0); + +#if !defined(NO_DH) && defined(WOLFSSL_DH_EXTRA) && \ + (!defined(HAVE_FIPS) || (defined(HAVE_FIPS_VERSION) && \ + (HAVE_FIPS_VERSION > 2))) + ExpectTrue((f = XFOPEN("./certs/dh2048.der", "rb")) != XBADFILE); + ExpectIntGT(dhDerSz = (long)XFREAD(dhDer, 1, sizeof(dhDer), f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + p = dhDer; + ExpectNotNull(dh = wolfSSL_d2i_DHparams(NULL, &p, dhDerSz)); + + /* RSA -> DH -> DH. */ + ExpectIntEQ(EVP_PKEY_set1_DH(pkey, dh), WOLFSSL_SUCCESS); + ExpectIntGT(dhPkeySz = (pkey == NULL ? 0 : pkey->pkey_sz), 0); + ExpectIntEQ(EVP_PKEY_set1_DH(pkey, dh), WOLFSSL_SUCCESS); + ExpectIntEQ(pkey == NULL ? 0 : pkey->pkey_sz, dhPkeySz); + ExpectIntEQ(pkey == NULL ? -1 : pkey->pkcs8HeaderSz, 0); +#endif + + /* Back to DSA. */ + ExpectIntEQ(EVP_PKEY_set1_DSA(pkey, dsa), WOLFSSL_SUCCESS); + ExpectIntEQ(pkey == NULL ? 0 : pkey->pkey_sz, dsaPkeySz); + + EVP_PKEY_free(pkey); +#if !defined(NO_DH) && defined(WOLFSSL_DH_EXTRA) && \ + (!defined(HAVE_FIPS) || (defined(HAVE_FIPS_VERSION) && \ + (HAVE_FIPS_VERSION > 2))) + DH_free(dh); +#endif + RSA_free(rsaEmpty); + RSA_free(rsa); + DSA_free(dsa); + EVP_PKEY_free(dsaPkey); +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_EVP_PKEY_assign(void) { EXPECT_DECLS; diff --git a/tests/api/test_evp_pkey.h b/tests/api/test_evp_pkey.h index 874391edb21..083778a516f 100644 --- a/tests/api/test_evp_pkey.h +++ b/tests/api/test_evp_pkey.h @@ -41,6 +41,7 @@ int test_wolfSSL_EVP_PKEY_set1_get1_DSA(void); int test_wolfSSL_EVP_PKEY_set1_get1_EC_KEY (void); int test_wolfSSL_EVP_PKEY_get0_EC_KEY(void); int test_wolfSSL_EVP_PKEY_set1_get1_DH (void); +int test_wolfSSL_EVP_PKEY_reassign_der_cache(void); int test_wolfSSL_EVP_PKEY_assign(void); int test_wolfSSL_EVP_PKEY_assign_DH(void); int test_EVP_PKEY_rsa(void); @@ -102,6 +103,7 @@ int test_wolfSSL_CTX_use_PrivateKey_pkcs8_repopulate(void); TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_set1_get1_EC_KEY), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_get0_EC_KEY), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_set1_get1_DH), \ + TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_reassign_der_cache), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_assign), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_assign_DH), \ TEST_DECL_GROUP("evp_pkey", test_EVP_PKEY_rsa), \ diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index 1828f4d94b6..883069e16f4 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -9671,6 +9671,16 @@ int wolfSSL_EVP_PKEY_set1_DSA(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DSA *key) if((pkey == NULL) || (key == NULL))return WOLFSSL_FAILURE; clearEVPPkeyKeys(pkey); + /* Wipe and free the previous key's DER. */ + if (pkey->pkey.ptr != NULL) { + if (pkey->pkey_sz > 0) { + ForceZero(pkey->pkey.ptr, (word32)pkey->pkey_sz); + } + XFREE(pkey->pkey.ptr, pkey->heap, DYNAMIC_TYPE_PUBLIC_KEY); + pkey->pkey.ptr = NULL; + } + pkey->pkey_sz = 0; + pkey->pkcs8HeaderSz = 0; pkey->dsa = key; pkey->ownDsa = 0; /* pkey does not own DSA */ pkey->type = WC_EVP_PKEY_DSA; @@ -9708,6 +9718,7 @@ int wolfSSL_EVP_PKEY_set1_DSA(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DSA *key) else { WOLFSSL_MSG("wc_DsaKeyToPublicDer failed"); } + ForceZero(derBuf, (word32)derMax); XFREE(derBuf, pkey->heap, DYNAMIC_TYPE_TMP_BUFFER); return WOLFSSL_FAILURE; } @@ -9716,11 +9727,13 @@ int wolfSSL_EVP_PKEY_set1_DSA(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DSA *key) DYNAMIC_TYPE_DER); if (pkey->pkey.ptr == NULL) { WOLFSSL_MSG("key malloc failed"); + ForceZero(derBuf, (word32)derSz); XFREE(derBuf, pkey->heap, DYNAMIC_TYPE_TMP_BUFFER); return WOLFSSL_FAILURE; } pkey->pkey_sz = derSz; XMEMCPY(pkey->pkey.ptr, derBuf, (size_t)derSz); + ForceZero(derBuf, (word32)derSz); XFREE(derBuf, pkey->heap, DYNAMIC_TYPE_TMP_BUFFER); return WOLFSSL_SUCCESS; @@ -9851,6 +9864,7 @@ int wolfSSL_EVP_PKEY_set1_DH(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DH *key) byte havePublic = 0, havePrivate = 0; int ret; word32 derSz = 0; + word32 derAllocSz = 0; byte* derBuf = NULL; DhKey* dhkey = NULL; @@ -9860,6 +9874,16 @@ int wolfSSL_EVP_PKEY_set1_DH(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DH *key) return WOLFSSL_FAILURE; clearEVPPkeyKeys(pkey); + /* Wipe and free the previous key's DER. */ + if (pkey->pkey.ptr != NULL) { + if (pkey->pkey_sz > 0) { + ForceZero(pkey->pkey.ptr, (word32)pkey->pkey_sz); + } + XFREE(pkey->pkey.ptr, pkey->heap, DYNAMIC_TYPE_PUBLIC_KEY); + pkey->pkey.ptr = NULL; + } + pkey->pkey_sz = 0; + pkey->pkcs8HeaderSz = 0; if (wolfSSL_DH_up_ref(key) != WOLFSSL_SUCCESS) { WOLFSSL_MSG("Failed to increase dh key ref count"); @@ -9900,6 +9924,7 @@ int wolfSSL_EVP_PKEY_set1_DH(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DH *key) WOLFSSL_MSG("malloc failed"); return WOLFSSL_FAILURE; } + derAllocSz = derSz; /* Fill DER buffer */ if (havePublic && !havePrivate) { @@ -9912,6 +9937,7 @@ int wolfSSL_EVP_PKEY_set1_DH(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DH *key) if (ret <= 0) { WOLFSSL_MSG("Failed to export DH Key"); + ForceZero(derBuf, derAllocSz); XFREE(derBuf, pkey->heap, DYNAMIC_TYPE_TMP_BUFFER); return WOLFSSL_FAILURE; } @@ -11010,6 +11036,16 @@ int wolfSSL_EVP_PKEY_assign_RSA(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_RSA* key) return WOLFSSL_FAILURE; clearEVPPkeyKeys(pkey); + /* Wipe and free the previous key's DER. */ + if (pkey->pkey.ptr != NULL) { + if (pkey->pkey_sz > 0) { + ForceZero(pkey->pkey.ptr, (word32)pkey->pkey_sz); + } + XFREE(pkey->pkey.ptr, pkey->heap, DYNAMIC_TYPE_PUBLIC_KEY); + pkey->pkey.ptr = NULL; + } + pkey->pkey_sz = 0; + pkey->pkcs8HeaderSz = 0; pkey->type = WC_EVP_PKEY_RSA; pkey->rsa = key; pkey->ownRsa = 1; @@ -11029,6 +11065,7 @@ int wolfSSL_EVP_PKEY_assign_RSA(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_RSA* key) pkey->pkey.ptr = (char*)derBuf; } else { /* failure - okay to ignore */ + ForceZero(derBuf, derSz); XFREE(derBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); derBuf = NULL; } From 3f9e86bbd1a1a3b673444db4b71c32e37cfc4b46 Mon Sep 17 00:00:00 2001 From: Takashi Kojo Date: Tue, 3 Nov 2026 12:57:23 +0900 Subject: [PATCH 2/2] Test that EVP_PKEY reassignment wipes and frees the previous DER cache --- tests/api/test_evp_pkey.c | 98 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index ce0cc39f460..e099031f1e2 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -1011,6 +1011,92 @@ int test_wolfSSL_EVP_PKEY_set1_get1_DH(void) return EXPECT_RESULT(); } /* END test_EVP_PKEY_set1_get1_DH */ +#if defined(OPENSSL_ALL) && !defined(NO_FILESYSTEM) && \ + !defined(NO_RSA) && defined(WOLFSSL_KEY_TO_DER) && \ + !defined(NO_DSA) && !defined(HAVE_SELFTEST) && \ + !defined(WC_FIPS_186_5_PLUS) && defined(WOLFSSL_KEY_GEN) && \ + defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_DEBUG_MEMORY) && \ + !defined(WOLFSSL_CHECK_MEM_ZERO) + /* CHECK_MEM_ZERO passes free_function a pointer offset by a header. */ + #define TEST_DER_WATCH_FREE_HOOK +/* Free hook watching one buffer: counts its frees and checks it is zeroed. */ +static wolfSSL_Malloc_cb der_watch_prev_malloc = NULL; +static wolfSSL_Free_cb der_watch_prev_free = NULL; +static wolfSSL_Realloc_cb der_watch_prev_realloc = NULL; +static int der_watch_installed = 0; +static void* der_watch_ptr = NULL; +static int der_watch_sz = 0; +static int der_watch_frees = 0; +static int der_watch_zeroed = 0; + +static void der_watch_free_cb(void* ptr) +{ + if (ptr != NULL && ptr == der_watch_ptr) { + int i; + der_watch_zeroed = 1; + for (i = 0; i < der_watch_sz; i++) { + if (((byte*)ptr)[i] != 0) { + der_watch_zeroed = 0; + break; + } + } + der_watch_frees++; + der_watch_ptr = NULL; /* address may be reused */ + } + if (der_watch_prev_free != NULL) + der_watch_prev_free(ptr); + else + free(ptr); +} + +static void der_watch_begin(const WOLFSSL_EVP_PKEY* pkey) +{ + der_watch_ptr = (pkey == NULL) ? NULL : pkey->pkey.ptr; + der_watch_sz = (pkey == NULL) ? 0 : pkey->pkey_sz; + der_watch_frees = 0; + der_watch_zeroed = 0; +} +static int der_watch_install(void) +{ + if (wolfSSL_GetAllocators(&der_watch_prev_malloc, &der_watch_prev_free, + &der_watch_prev_realloc) != 0) { + return -1; + } + if (wolfSSL_SetAllocators(der_watch_prev_malloc, der_watch_free_cb, + der_watch_prev_realloc) != 0) { + return -1; + } + der_watch_installed = 1; + return 0; +} + +static void der_watch_uninstall(void) +{ + if (der_watch_installed) { + (void)wolfSSL_SetAllocators(der_watch_prev_malloc, + der_watch_prev_free, der_watch_prev_realloc); + der_watch_installed = 0; + } + der_watch_prev_free = NULL; +} +#define DER_WATCH_INSTALL() ExpectIntEQ(der_watch_install(), 0) +#define DER_WATCH_UNINSTALL() der_watch_uninstall() +#define DER_WATCH_BEGIN(pkey) der_watch_begin(pkey) +/* The previous DER must be freed exactly once, zeroed beforehand. */ +#define DER_WATCH_END() \ + do { \ + ExpectIntEQ(der_watch_frees, 1); \ + ExpectIntEQ(der_watch_zeroed, 1); \ + der_watch_ptr = NULL; \ + } while (0) +#else +#define DER_WATCH_INSTALL() WC_DO_NOTHING +#define DER_WATCH_UNINSTALL() WC_DO_NOTHING +#define DER_WATCH_BEGIN(pkey) WC_DO_NOTHING +#define DER_WATCH_END() WC_DO_NOTHING +#endif + /* Reassigning a key must wipe and free the previous DER cache. */ int test_wolfSSL_EVP_PKEY_reassign_der_cache(void) { @@ -1060,16 +1146,21 @@ int test_wolfSSL_EVP_PKEY_reassign_der_cache(void) ExpectNotNull(rsa = d2i_RSAPrivateKey(NULL, &p, rsaDerSz)); ExpectNotNull(rsaEmpty = RSA_new()); ExpectNotNull(pkey = EVP_PKEY_new()); + DER_WATCH_INSTALL(); /* DSA, then the same DSA again. */ ExpectIntEQ(EVP_PKEY_set1_DSA(pkey, dsa), WOLFSSL_SUCCESS); ExpectNotNull(pkey == NULL ? NULL : pkey->pkey.ptr); ExpectIntGT(dsaPkeySz = (pkey == NULL ? 0 : pkey->pkey_sz), 0); + DER_WATCH_BEGIN(pkey); ExpectIntEQ(EVP_PKEY_set1_DSA(pkey, dsa), WOLFSSL_SUCCESS); + DER_WATCH_END(); ExpectIntEQ(pkey == NULL ? 0 : pkey->pkey_sz, dsaPkeySz); /* Unencodable RSA key: old DSA DER must not remain. */ + DER_WATCH_BEGIN(pkey); ExpectIntEQ(EVP_PKEY_assign_RSA(pkey, rsaEmpty), WOLFSSL_SUCCESS); + DER_WATCH_END(); if (EXPECT_FAIL()) { RSA_free(rsaEmpty); } @@ -1100,17 +1191,24 @@ int test_wolfSSL_EVP_PKEY_reassign_der_cache(void) ExpectNotNull(dh = wolfSSL_d2i_DHparams(NULL, &p, dhDerSz)); /* RSA -> DH -> DH. */ + DER_WATCH_BEGIN(pkey); ExpectIntEQ(EVP_PKEY_set1_DH(pkey, dh), WOLFSSL_SUCCESS); + DER_WATCH_END(); ExpectIntGT(dhPkeySz = (pkey == NULL ? 0 : pkey->pkey_sz), 0); + DER_WATCH_BEGIN(pkey); ExpectIntEQ(EVP_PKEY_set1_DH(pkey, dh), WOLFSSL_SUCCESS); + DER_WATCH_END(); ExpectIntEQ(pkey == NULL ? 0 : pkey->pkey_sz, dhPkeySz); ExpectIntEQ(pkey == NULL ? -1 : pkey->pkcs8HeaderSz, 0); #endif /* Back to DSA. */ + DER_WATCH_BEGIN(pkey); ExpectIntEQ(EVP_PKEY_set1_DSA(pkey, dsa), WOLFSSL_SUCCESS); + DER_WATCH_END(); ExpectIntEQ(pkey == NULL ? 0 : pkey->pkey_sz, dsaPkeySz); + DER_WATCH_UNINSTALL(); EVP_PKEY_free(pkey); #if !defined(NO_DH) && defined(WOLFSSL_DH_EXTRA) && \ (!defined(HAVE_FIPS) || (defined(HAVE_FIPS_VERSION) && \