Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/configs/pq-all.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@
"--enable-dilithium=yes,no-ctx", "--enable-dual-alg-certs",
"--disable-qt",
"CPPFLAGS=-pedantic -Wdeclaration-after-statement -Wnull-dereference -DWOLFCRYPT_TEST_LINT -DNO_WOLFSSL_CIPHER_SUITE_TEST -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE"]},
{"name": "pq-cryptocbutils", "minutes": 3,
"comment": "The only config pairing --enable-cryptocbutils with the PQC algorithms. --enable-all already turns cryptocb on, but the utility callbacks (copy, free, setkey, export) default to off, so without this entry WOLF_CRYPTO_CB_FREE is never compiled alongside a PQC algorithm and the free-callback tests for ML-DSA, SLH-DSA and ML-KEM all skip. Falcon and FrodoKEM are named explicitly: experimental only auto-enables them under --enable-all-quantum-crypto, which nothing here sets.",
"configure": ["--enable-intelasm", "--enable-sp-asm",
"--enable-all", "--enable-testcert", "--enable-experimental",
"--enable-mlkem=yes,kyber,ml-kem", "--enable-slhdsa",
"--enable-dilithium", "--enable-falcon", "--enable-frodokem",
"--enable-cryptocb", "--enable-cryptocbutils",
"--disable-qt",
"CPPFLAGS=-pedantic -Wdeclaration-after-statement -Wnull-dereference -DWOLFCRYPT_TEST_LINT -DNO_WOLFSSL_CIPHER_SUITE_TEST -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE"]},
{"name": "pq-asynccrypt-dual-alg", "minutes": 2.5,
"comment": "Async crypto with dual-algorithm certs and PQC signatures. WOLFSSL_ASYNC_CRYPT turns off the streaming CertificateVerify path, so this is the only config that compiles the in-place fragmented send and the async args holder that Scv13Args must fit.",
"configure": ["--enable-asynccrypt-sw", "--enable-dual-alg-certs",
Expand Down
100 changes: 100 additions & 0 deletions tests/api/test_falcon.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <wolfssl/wolfcrypt/random.h>
#include <wolfssl/wolfcrypt/asn.h>
#include <wolfssl/wolfcrypt/asn_public.h>
#include <wolfssl/wolfcrypt/cryptocb.h>
#include <tests/api/api.h>
#include <tests/api/test_falcon.h>

Expand Down Expand Up @@ -1035,3 +1036,102 @@ int test_wc_FalconDecisionCoverage(void)
#endif /* HAVE_FALCON */
return EXPECT_RESULT();
}

#if defined(HAVE_FALCON) && defined(WOLF_CRYPTO_CB) && \
defined(WOLF_CRYPTO_CB_FREE)
#define TEST_FALCON_CB_FREE
#define TEST_FALCON_CB_FREE_DEVID 0x46414C43
#endif

#ifdef TEST_FALCON_CB_FREE
/* What the free callback saw, so the test can check the contract rather than
* just that something fired. */
typedef struct {
int frees; /* matching free callbacks seen */
int badObj; /* callback was handed the wrong object */
int wiped; /* callback saw a key already cleaned up */
int ret; /* what the callback returns */
const void* obj; /* object the free is expected to name */
} FalconCbFreeCtx;

/* Stands in for a device holding state for the key. Counting the call proves
* wc_falcon_free told the device rather than only cleaning up in software,
* which would leave the device side of the key behind. */
static int falcon_cb_free_cb(int devIdArg, wc_CryptoInfo* info, void* ctx)
{
FalconCbFreeCtx* seen = (FalconCbFreeCtx*)ctx;

(void)devIdArg;

if ((seen != NULL) && (info != NULL) &&
(info->algo_type == WC_ALGO_TYPE_FREE) &&
(info->free.algo == WC_ALGO_TYPE_PK) &&
(info->free.type == WC_PK_TYPE_PQC_SIG_KEYGEN) &&
(info->free.subType == WC_PQC_SIG_TYPE_FALCON)) {
const falcon_key* fk = (const falcon_key*)info->free.obj;

seen->frees++;
if ((fk == NULL) || ((const void*)fk != seen->obj)) {
seen->badObj++;
}
/* The device gets the key while it is still whole: it may need to
* read it to release the right resource, so the software wipe has
* to come after this call, not before. */
else if (fk->devId != TEST_FALCON_CB_FREE_DEVID) {
seen->wiped++;
}
return seen->ret;
}

return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
#endif /* TEST_FALCON_CB_FREE */

/* Freeing a key that names a device has to tell that device, so it can
* release what it holds. A key with no device must not, and neither must a
* second free of a key already freed: a freed key names no device. A device
* that reports an error does not stop the software cleanup. */
int test_falcon_cb_free(void)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 [Low] New cb-free tests skip the NULL-key branch of the free functions the PR changed · Missing edge-case coverage on a function the PR also changed

wc_falcon_free(NULL) and wc_MlKemKey_Free(NULL) appear nowhere in the tree, so the NULL guard of the two free functions this PR modifies stays unexercised, while the sibling tests already cover it for ML-DSA (test_mldsa.c:575) and SLH-DSA (test_slhdsa.c:149).

Fix: Add wc_falcon_free(NULL); to this test and ExpectIntEQ(wc_MlKemKey_Free(NULL), 0); to test_wc_mlkem_cb_free.

{
EXPECT_DECLS;
#ifdef TEST_FALCON_CB_FREE
falcon_key key;
FalconCbFreeCtx seen;

XMEMSET(&key, 0, sizeof(key));
XMEMSET(&seen, 0, sizeof(seen));
seen.obj = &key;

ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_FALCON_CB_FREE_DEVID,
falcon_cb_free_cb, &seen), 0);

ExpectIntEQ(wc_falcon_init_ex(&key, NULL, TEST_FALCON_CB_FREE_DEVID), 0);
wc_falcon_free(&key);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ [Info] New Falcon free-callback test never exercises the NULL-key guard · Missing edge-case coverage on a function the PR also changed

wc_falcon_free gained a key->devId read behind its if (key != NULL) guard, but wc_falcon_free(NULL) is not called by the new test nor anywhere else in the repository, leaving that guard uncovered.

Fix: Add a wc_falcon_free(NULL); call at the start of test_falcon_cb_free.

ExpectIntEQ(seen.frees, 1);
ExpectIntEQ(seen.badObj, 0);
ExpectIntEQ(seen.wiped, 0);
ExpectIntEQ(key.devId, INVALID_DEVID);

wc_falcon_free(&key);
ExpectIntEQ(seen.frees, 1);

/* A device that fails still leaves the key cleaned up locally. */
seen.ret = WC_NO_ERR_TRACE(WC_HW_E);
XMEMSET(&key, 0, sizeof(key));
ExpectIntEQ(wc_falcon_init_ex(&key, NULL, TEST_FALCON_CB_FREE_DEVID), 0);
ExpectIntEQ(wc_falcon_set_level(&key, 1), 0);
wc_falcon_free(&key);
ExpectIntEQ(seen.frees, 2);
ExpectIntEQ(key.devId, INVALID_DEVID);
ExpectIntEQ(key.level, 0);
seen.ret = 0;

XMEMSET(&key, 0, sizeof(key));
ExpectIntEQ(wc_falcon_init_ex(&key, NULL, INVALID_DEVID), 0);
wc_falcon_free(&key);
ExpectIntEQ(seen.frees, 2);

wc_CryptoCb_UnRegisterDevice(TEST_FALCON_CB_FREE_DEVID);
#endif
return EXPECT_RESULT();
}
4 changes: 3 additions & 1 deletion tests/api/test_falcon.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ int test_wc_falcon_check_key(void);
int test_wc_falcon_der(void);
int test_wc_falcon_error_paths(void);
int test_wc_FalconDecisionCoverage(void);
int test_falcon_cb_free(void);

#define TEST_FALCON_DECLS \
TEST_DECL_GROUP("falcon", test_wc_falcon_sizes), \
Expand All @@ -41,6 +42,7 @@ int test_wc_FalconDecisionCoverage(void);
TEST_DECL_GROUP("falcon", test_wc_falcon_check_key), \
TEST_DECL_GROUP("falcon", test_wc_falcon_der), \
TEST_DECL_GROUP("falcon", test_wc_falcon_error_paths), \
TEST_DECL_GROUP("falcon", test_wc_FalconDecisionCoverage)
TEST_DECL_GROUP("falcon", test_wc_FalconDecisionCoverage), \
TEST_DECL_GROUP("falcon", test_falcon_cb_free)

#endif /* WOLFCRYPT_TEST_FALCON_H */
102 changes: 102 additions & 0 deletions tests/api/test_mldsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#endif

#include <wolfssl/wolfcrypt/asn_public.h>
#include <wolfssl/wolfcrypt/cryptocb.h>
#ifdef WOLFSSL_HAVE_MLDSA
#include <wolfssl/wolfcrypt/wc_mldsa.h>
#endif
Expand Down Expand Up @@ -31401,3 +31402,104 @@ int test_wc_MldsaDerDecisionCoverage(void)
#endif /* WOLFSSL_HAVE_MLDSA && WOLFSSL_MLDSA_NO_ASN1 && ... */
return EXPECT_RESULT();
}

#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLF_CRYPTO_CB) && \
defined(WOLF_CRYPTO_CB_FREE)
#define TEST_MLDSA_CB_FREE
#define TEST_MLDSA_CB_FREE_DEVID 0x4D4C4453
#endif

#ifdef TEST_MLDSA_CB_FREE
/* What the free callback saw, so the test can check the contract rather than
* just that something fired. */
typedef struct {
int frees; /* matching free callbacks seen */
int badObj; /* callback was handed the wrong object */
int wiped; /* callback saw a key already cleaned up */
int ret; /* what the callback returns */
const void* obj; /* object the free is expected to name */
} MlDsaCbFreeCtx;

/* Stands in for a device holding state for the key. Counting the call proves
* wc_MlDsaKey_Free told the device rather than only cleaning up in software,
* which would leave the device side of the key behind. */
static int mldsa_cb_free_cb(int devIdArg, wc_CryptoInfo* info, void* ctx)
{
MlDsaCbFreeCtx* seen = (MlDsaCbFreeCtx*)ctx;

(void)devIdArg;

if ((seen != NULL) && (info != NULL) &&
(info->algo_type == WC_ALGO_TYPE_FREE) &&
(info->free.algo == WC_ALGO_TYPE_PK) &&
(info->free.type == WC_PK_TYPE_PQC_SIG_KEYGEN) &&
(info->free.subType == WC_PQC_SIG_TYPE_MLDSA)) {
const wc_MlDsaKey* dil = (const wc_MlDsaKey*)info->free.obj;

seen->frees++;
if ((dil == NULL) || ((const void*)dil != seen->obj)) {
seen->badObj++;
}
/* The device gets the key while it is still whole: it may need to
* read it to release the right resource, so the software wipe has
* to come after this call, not before. */
else if (dil->devId != TEST_MLDSA_CB_FREE_DEVID) {
seen->wiped++;
}
return seen->ret;
}

return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
#endif /* TEST_MLDSA_CB_FREE */

/* Freeing a key that names a device has to tell that device, so it can
* release what it holds. A key with no device must not, and neither must a
* second free of a key already freed: a freed key names no device. A device
* that reports an error does not stop the software cleanup. */
int test_mldsa_cb_free(void)
{
EXPECT_DECLS;
#ifdef TEST_MLDSA_CB_FREE
wc_MlDsaKey* key = NULL;
MlDsaCbFreeCtx seen;

XMEMSET(&seen, 0, sizeof(seen));

ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_MLDSA_CB_FREE_DEVID,
mldsa_cb_free_cb, &seen), 0);

ExpectNotNull(key = (wc_MlDsaKey*)XMALLOC(sizeof(wc_MlDsaKey), NULL,
DYNAMIC_TYPE_TMP_BUFFER));
seen.obj = key;
ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, TEST_MLDSA_CB_FREE_DEVID), 0);
wc_MlDsaKey_Free(key);
ExpectIntEQ(seen.frees, 1);
ExpectIntEQ(seen.badObj, 0);
ExpectIntEQ(seen.wiped, 0);
if (key != NULL) {
ExpectIntEQ(key->devId, INVALID_DEVID);
}

wc_MlDsaKey_Free(key);
ExpectIntEQ(seen.frees, 1);

/* A device that fails still leaves the key cleaned up locally. */
seen.ret = WC_NO_ERR_TRACE(WC_HW_E);
ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, TEST_MLDSA_CB_FREE_DEVID), 0);
wc_MlDsaKey_Free(key);
ExpectIntEQ(seen.frees, 2);
if (key != NULL) {
ExpectIntEQ(key->devId, INVALID_DEVID);
}
seen.ret = 0;

ExpectIntEQ(wc_MlDsaKey_Init(key, NULL, INVALID_DEVID), 0);
wc_MlDsaKey_Free(key);
ExpectIntEQ(seen.frees, 2);

XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER);
wc_CryptoCb_UnRegisterDevice(TEST_MLDSA_CB_FREE_DEVID);
#endif
return EXPECT_RESULT();
}
4 changes: 3 additions & 1 deletion tests/api/test_mldsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ int test_wc_MldsaFeatureCoverage(void);
int test_mldsa_legacy_shim(void);
int test_wc_MldsaDecisionCoverage2(void);
int test_wc_MldsaDerDecisionCoverage(void);
int test_mldsa_cb_free(void);

#define TEST_MLDSA_DECLS \
TEST_DECL_GROUP("mldsa", test_mldsa), \
Expand Down Expand Up @@ -99,6 +100,7 @@ int test_wc_MldsaDerDecisionCoverage(void);
TEST_DECL_GROUP("mldsa", test_wc_MldsaFeatureCoverage), \
TEST_DECL_GROUP("mldsa", test_mldsa_legacy_shim), \
TEST_DECL_GROUP("mldsa", test_wc_MldsaDecisionCoverage2), \
TEST_DECL_GROUP("mldsa", test_wc_MldsaDerDecisionCoverage)
TEST_DECL_GROUP("mldsa", test_wc_MldsaDerDecisionCoverage), \
TEST_DECL_GROUP("mldsa", test_mldsa_cb_free)

#endif /* WOLFCRYPT_TEST_MLDSA_H */
Loading
Loading