diff --git a/tests/api/test_eccsi.c b/tests/api/test_eccsi.c index df09a80e2b2..1a2b3ff48da 100644 --- a/tests/api/test_eccsi.c +++ b/tests/api/test_eccsi.c @@ -860,3 +860,192 @@ int test_wc_Eccsi_FeatureCoverage(void) #endif return EXPECT_RESULT(); } + +/* wc_FreeEccsiKey() must wipe the SSK before releasing it. Only the heap math + * backend keeps mp_int digits in a separate allocation, so only there can a + * free hook see what is handed back to the allocator. WOLFSSL_CHECK_MEM_ZERO + * and WOLFSSL_MEMORY_LOG put a header in front of each allocation, so the free + * hook would never be handed key.ssk.dp itself. Platforms whose XFREE skips + * the hooks entirely are detected at run time by eccsi_zero_probe(). */ +#if defined(WOLFCRYPT_HAVE_ECCSI) && defined(WOLFCRYPT_ECCSI_CLIENT) && \ + defined(USE_INTEGER_HEAP_MATH) && defined(USE_WOLFSSL_MEMORY) && \ + !defined(WOLFSSL_NO_MALLOC) && !defined(WOLFSSL_STATIC_MEMORY) && \ + !defined(WOLFSSL_CHECK_MEM_ZERO) && !defined(WOLFSSL_MEMORY_LOG) +#define TEST_ECCSI_FREE_ZEROIZE + +/* These callback types take (func, line) too under WOLFSSL_DEBUG_MEMORY. */ +#ifdef WOLFSSL_DEBUG_MEMORY + #define ECCSI_ZERO_CB_TAIL , const char* func, unsigned int line + #define ECCSI_ZERO_CB_ARGS , func, line +#else + #define ECCSI_ZERO_CB_TAIL + #define ECCSI_ZERO_CB_ARGS +#endif + +/* The hooks delegate to the allocators that were installed before the test + * (e.g. WOLFSSL_TRACK_MEMORY's), so blocks that outlive the test, such as + * cached ECC curve values, can still be freed by them later. */ +static wolfSSL_Malloc_cb eccsi_zero_prev_mc; +static wolfSSL_Free_cb eccsi_zero_prev_fc; +static wolfSSL_Realloc_cb eccsi_zero_prev_rc; + +/* State for eccsi_zero_probe(). */ +static int eccsi_zero_probing; +static const void* eccsi_zero_probe_alloc; +static int eccsi_zero_probe_freed; + +static const void* eccsi_zero_watch; +static size_t eccsi_zero_watchSz; +static int eccsi_zero_seen; +static int eccsi_zero_clean; + +static void* eccsi_zero_malloc_cb(size_t size ECCSI_ZERO_CB_TAIL) +{ + void* p; + + if (eccsi_zero_prev_mc != NULL) { + p = eccsi_zero_prev_mc(size ECCSI_ZERO_CB_ARGS); + } + else { + p = malloc(size); + } + if (eccsi_zero_probing && (eccsi_zero_probe_alloc == NULL)) { + eccsi_zero_probe_alloc = p; + } + return p; +} + +static void eccsi_zero_free_cb(void* ptr ECCSI_ZERO_CB_TAIL) +{ + if (eccsi_zero_probing && (ptr != NULL) && + (ptr == eccsi_zero_probe_alloc)) { + eccsi_zero_probe_freed = 1; + } + if ((ptr != NULL) && (ptr == eccsi_zero_watch)) { + const byte* p = (const byte*)ptr; + size_t i; + byte acc = 0; + + for (i = 0; i < eccsi_zero_watchSz; i++) { + acc |= p[i]; + } + eccsi_zero_seen = 1; + eccsi_zero_clean = (acc == 0); + } + if (eccsi_zero_prev_fc != NULL) { + eccsi_zero_prev_fc(ptr ECCSI_ZERO_CB_ARGS); + } + else { + free(ptr); + } +} + +static void* eccsi_zero_realloc_cb(void* ptr, size_t size ECCSI_ZERO_CB_TAIL) +{ + if (eccsi_zero_prev_rc != NULL) { + return eccsi_zero_prev_rc(ptr, size ECCSI_ZERO_CB_ARGS); + } + return realloc(ptr, size); +} + +/* Checks that XMALLOC()/XFREE() reach the hooks with the pointer unchanged. + * Returns 1 when they do and 0 when both bypass the hooks, as on platforms + * that map them to an OS or pool allocator (see types.h); the wipe cannot be + * observed there. Returns -1 when the hooks are reached but see a different + * pointer, or only one of the pair reaches them: that is a failure. */ +static int eccsi_zero_probe(void) +{ + void* probe; + int ret; + + eccsi_zero_probe_alloc = NULL; + eccsi_zero_probe_freed = 0; + eccsi_zero_probing = 1; + probe = XMALLOC(8, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (probe == NULL) { + ret = -1; + } + else { + ret = (eccsi_zero_probe_alloc == probe) ? 1 : + (eccsi_zero_probe_alloc == NULL) ? 0 : -1; + XFREE(probe, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (eccsi_zero_probe_freed != ret) { + ret = -1; + } + } + eccsi_zero_probing = 0; + return ret; +} +#endif + +int test_wc_Eccsi_FreeZeroizesSsk(void) +{ + EXPECT_DECLS; +#if defined(TEST_ECCSI_FREE_ZEROIZE) && defined(USE_WOLFSSL_MEMORY) + EccsiKey key; + WC_RNG rng; + mp_int ssk; + ecc_point* pvt = NULL; + char mail[] = "test@wolfssl.com"; + int hooked = 0; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&ssk, 0, sizeof(ssk)); + eccsi_zero_watch = NULL; + eccsi_zero_watchSz = 0; + eccsi_zero_seen = 0; + eccsi_zero_clean = 0; + eccsi_zero_prev_mc = NULL; + eccsi_zero_prev_fc = NULL; + eccsi_zero_prev_rc = NULL; + + ExpectIntEQ(wolfSSL_GetAllocators(&eccsi_zero_prev_mc, + &eccsi_zero_prev_fc, &eccsi_zero_prev_rc), 0); + ExpectIntEQ(wolfSSL_SetAllocators(eccsi_zero_malloc_cb, + eccsi_zero_free_cb, eccsi_zero_realloc_cb), 0); + if (EXPECT_SUCCESS()) { + hooked = eccsi_zero_probe(); + ExpectIntGE(hooked, 0); + } + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectNotNull(pvt = wc_ecc_new_point()); + ExpectIntEQ(mp_init(&ssk), MP_OKAY); + ExpectIntEQ(wc_InitEccsiKey(&key, HEAP_HINT, INVALID_DEVID), 0); + ExpectIntEQ(wc_MakeEccsiKey(&key, &rng), 0); + ExpectIntEQ(wc_MakeEccsiPair(&key, &rng, WC_HASH_TYPE_SHA256, + (byte*)mail, (word32)XSTRLEN(mail), &ssk, pvt), 0); + ExpectIntEQ(wc_SetEccsiPair(&key, &ssk, pvt), 0); + + /* The key's copy of the SSK lives in its own digit allocation. */ + ExpectFalse(mp_iszero(&key.ssk)); + ExpectNotNull(key.ssk.dp); + if (EXPECT_SUCCESS()) { + eccsi_zero_watch = key.ssk.dp; + eccsi_zero_watchSz = (size_t)key.ssk.used * sizeof(mp_digit); + } + + wc_FreeEccsiKey(&key); + eccsi_zero_watch = NULL; + + if (hooked == 1) { + ExpectIntEQ(eccsi_zero_seen, 1); + ExpectIntEQ(eccsi_zero_clean, 1); + } + + if (pvt != NULL) { + wc_ecc_del_point(pvt); + } + mp_forcezero(&ssk); + wc_FreeRng(&rng); + (void)wolfSSL_SetAllocators(eccsi_zero_prev_mc, eccsi_zero_prev_fc, + eccsi_zero_prev_rc); + /* Report the test as skipped, not passed, where XMALLOC() and XFREE() + * bypass the hooks. */ + if ((hooked == 0) && EXPECT_SUCCESS()) { + return TEST_SKIPPED; + } +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_eccsi.h b/tests/api/test_eccsi.h index 80ec3257944..a4976fbeb85 100644 --- a/tests/api/test_eccsi.h +++ b/tests/api/test_eccsi.h @@ -26,9 +26,11 @@ int test_wc_Eccsi_DecisionCoverage(void); int test_wc_Eccsi_FeatureCoverage(void); +int test_wc_Eccsi_FreeZeroizesSsk(void); #define TEST_ECCSI_DECLS \ TEST_DECL_GROUP("eccsi", test_wc_Eccsi_DecisionCoverage), \ - TEST_DECL_GROUP("eccsi", test_wc_Eccsi_FeatureCoverage) + TEST_DECL_GROUP("eccsi", test_wc_Eccsi_FeatureCoverage), \ + TEST_DECL_GROUP("eccsi", test_wc_Eccsi_FreeZeroizesSsk) #endif /* WOLFCRYPT_TEST_ECCSI_H */ diff --git a/wolfcrypt/src/eccsi.c b/wolfcrypt/src/eccsi.c index 95cab2236cf..e62445e926a 100644 --- a/wolfcrypt/src/eccsi.c +++ b/wolfcrypt/src/eccsi.c @@ -116,6 +116,11 @@ int wc_InitEccsiKey_ex(EccsiKey* key, int keySz, int curveId, void* heap, #endif ); } +#if defined(WOLFCRYPT_ECCSI_CLIENT) && defined(WOLFSSL_CHECK_MEM_ZERO) + if (err == 0) { + mp_memzero_add("ECCSI ssk", &key->ssk); + } +#endif if (err == 0) { err = wc_ecc_set_curve(&key->ecc, keySz, curveId); } @@ -164,7 +169,10 @@ void wc_FreeEccsiKey(EccsiKey* key) wc_ecc_del_point_h(params->base, key->heap); #ifdef WOLFCRYPT_ECCSI_CLIENT - mp_free(&key->ssk); + mp_forcezero(&key->ssk); + #ifdef WOLFSSL_CHECK_MEM_ZERO + mp_memzero_check(&key->ssk); + #endif mp_free(&key->tmp); mp_free(¶ms->prime); mp_free(¶ms->b);