1
0
mirror of https://github.com/wolfSSL/wolfssl.git synced 2026-10-24 04:09:07 +03:00

Add tests for PQC verify clearing *res on error

Falcon: device success, silent device, device error, decline and bad arg.
Whitebox: drive the dispatchers' *res clear with NULL res and ret == 0.
This commit is contained in:
Emma Stensland
2026-09-30 10:00:30 -06:00
parent e65feb618d
commit 209ae2b2fd
3 changed files with 215 additions and 7 deletions

View File

@@ -1865,3 +1865,106 @@ int test_falcon_cb_free(void)
#endif
return EXPECT_RESULT();
}
#if defined(HAVE_FALCON) && defined(WOLF_CRYPTO_CB)
#define TEST_FALCON_VERIFY_CB_RES
#define TEST_FALCON_VERIFY_CB_RES_DEVID 0x46414C56
#endif
#ifdef TEST_FALCON_VERIFY_CB_RES
typedef struct FalconVerifyResCtx {
int ret; /* Value the device returns. */
int writeRes; /* Whether the device writes *res = 1 first. */
} FalconVerifyResCtx;
/* A device that may claim the signature is valid and then returns ctx->ret,
* which may be 0, an error or CRYPTOCB_UNAVAILABLE. */
static int falcon_verify_res_cb(int devIdArg, wc_CryptoInfo* info, void* ctx)
{
FalconVerifyResCtx* cbCtx = (FalconVerifyResCtx*)ctx;
(void)devIdArg;
if ((info != NULL) && (info->algo_type == WC_ALGO_TYPE_PK) &&
(info->pk.type == WC_PK_TYPE_PQC_SIG_VERIFY) &&
(info->pk.pqc_verify.type == WC_PQC_SIG_TYPE_FALCON)) {
if (cbCtx->writeRes) {
*info->pk.pqc_verify.res = 1;
}
return cbCtx->ret;
}
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
#endif /* TEST_FALCON_VERIFY_CB_RES */
/* *res must only be 1 when verify returns 0 and the signature was found
* valid. Cases expecting 0 start res at 1 to catch a stale value. */
int test_falcon_verify_cb_res(void)
{
EXPECT_DECLS;
#ifdef TEST_FALCON_VERIFY_CB_RES
falcon_key key;
byte sig[16];
byte msg[16];
FalconVerifyResCtx cbCtx;
int res;
#ifdef WOLF_CRYPTO_CB_ONLY_FALCON
const int declineRet = WC_NO_ERR_TRACE(NO_VALID_DEVID);
#else
/* The software fallback rejects a key with no public key set. */
const int declineRet = WC_NO_ERR_TRACE(BAD_FUNC_ARG);
#endif
XMEMSET(&key, 0, sizeof(key));
XMEMSET(&cbCtx, 0, sizeof(cbCtx));
XMEMSET(sig, 0, sizeof(sig));
XMEMSET(msg, 0x5a, sizeof(msg));
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_FALCON_VERIFY_CB_RES_DEVID,
falcon_verify_res_cb, &cbCtx), 0);
ExpectIntEQ(wc_falcon_init_ex(&key, NULL,
TEST_FALCON_VERIFY_CB_RES_DEVID), 0);
ExpectIntEQ(wc_falcon_set_level(&key, falcon_levels[0]), 0);
/* The device verifies the signature. */
cbCtx.ret = 0;
cbCtx.writeRes = 1;
res = 0;
ExpectIntEQ(wc_falcon_verify_msg(sig, (word32)sizeof(sig), msg,
(word32)sizeof(msg), &res, &key), 0);
ExpectIntEQ(res, 1);
/* The device completes without writing *res. */
cbCtx.writeRes = 0;
res = 1;
ExpectIntEQ(wc_falcon_verify_msg(sig, (word32)sizeof(sig), msg,
(word32)sizeof(msg), &res, &key), 0);
ExpectIntEQ(res, 0);
/* The device fails. */
cbCtx.ret = WC_NO_ERR_TRACE(WC_HW_E);
cbCtx.writeRes = 1;
res = 1;
ExpectIntEQ(wc_falcon_verify_msg(sig, (word32)sizeof(sig), msg,
(word32)sizeof(msg), &res, &key), WC_NO_ERR_TRACE(WC_HW_E));
ExpectIntEQ(res, 0);
/* The device declines, then the fallback rejects the call. */
cbCtx.ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
cbCtx.writeRes = 1;
res = 1;
ExpectIntEQ(wc_falcon_verify_msg(sig, (word32)sizeof(sig), msg,
(word32)sizeof(msg), &res, &key), declineRet);
ExpectIntEQ(res, 0);
/* A bad argument is rejected before the device is reached. */
res = 1;
ExpectIntEQ(wc_falcon_verify_msg(NULL, (word32)sizeof(sig), msg,
(word32)sizeof(msg), &res, &key), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
ExpectIntEQ(res, 0);
wc_falcon_free(&key);
wc_CryptoCb_UnRegisterDevice(TEST_FALCON_VERIFY_CB_RES_DEVID);
#endif
return EXPECT_RESULT();
}

View File

@@ -38,6 +38,7 @@ int test_wc_falcon_set_level_oom_callers(void);
int test_wc_falcon_verify_oom(void);
int test_wc_FalconDecisionCoverage(void);
int test_falcon_cb_free(void);
int test_falcon_verify_cb_res(void);
#define TEST_FALCON_DECLS \
TEST_DECL_GROUP("falcon", test_wc_falcon_sizes), \
@@ -53,6 +54,7 @@ int test_falcon_cb_free(void);
TEST_DECL_GROUP("falcon", test_wc_falcon_set_level_oom_callers), \
TEST_DECL_GROUP("falcon", test_wc_falcon_verify_oom), \
TEST_DECL_GROUP("falcon", test_wc_FalconDecisionCoverage), \
TEST_DECL_GROUP("falcon", test_falcon_cb_free)
TEST_DECL_GROUP("falcon", test_falcon_cb_free), \
TEST_DECL_GROUP("falcon", test_falcon_verify_cb_res)
#endif /* WOLFCRYPT_TEST_FALCON_H */

View File

@@ -76,10 +76,12 @@
* HMAC, RNG (RandomBlock/RandomSeed), GetCert, CMAC,
* HKDF (extract/expand/two-step-CMAC), the generic Copy/Free/SetKey/
* ExportKey callbacks, and the ML-KEM / ML-DSA PQC dispatch functions.
* wc_SHE (WOLFSSL_SHE) and the LMS/XMSS/FALCON/SLHDSA/FRODOKEM PQC families
* are left as residuals - they need family-specific key setup (wc_SHE,
* LmsKey, XmssKey, falcon_key, SlhDsaKey) that is out of scope for this
* pass; see the WB_NOTE calls below for exactly which are skipped.
* LMS PqcStatefulSigVerify is also driven, from a zeroed LmsKey.
* wc_SHE (WOLFSSL_SHE), the rest of the LMS/XMSS stateful family and the
* FALCON/SLHDSA/FRODOKEM PQC families are left as residuals - they need
* family-specific key setup (wc_SHE, LmsKey, XmssKey, falcon_key, SlhDsaKey)
* that is out of scope for this pass; see the WB_NOTE calls below for
* exactly which are skipped.
*
* Crash-safety: wb_cb() always returns CRYPTOCB_UNAVAILABLE before touching
* `info`, so every `if (dev && dev->cb)` body returns immediately after the
@@ -217,6 +219,32 @@ static int wb_cb_hash_fallback_ok(int devId, wc_CryptoInfo* info, void* ctx)
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
#if defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_LMS)
/* A device that reports a PQC verify as completed without touching info,
* so the post-dev->cb `ret != 0` decision that clears *res in
* wc_CryptoCb_PqcVerify{,Msg} and wc_CryptoCb_PqcStatefulSigVerify can be
* shown FALSE. Registered only around those calls. */
#define WB_DEVID_VERIFY_OK 4
static int wb_cb_pqc_verify_ok(int devId, wc_CryptoInfo* info, void* ctx)
{
(void)devId;
(void)ctx;
if (info == NULL || info->algo_type != WC_ALGO_TYPE_PK)
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
#ifdef WOLFSSL_HAVE_MLDSA
if (info->pk.type == WC_PK_TYPE_PQC_SIG_VERIFY ||
info->pk.type == WC_PK_TYPE_PQC_SIG_VERIFY_MSG)
return 0;
#endif
#ifdef WOLFSSL_HAVE_LMS
if (info->pk.type == WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY)
return 0;
#endif
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
#endif /* WOLFSSL_HAVE_MLDSA || WOLFSSL_HAVE_LMS */
#ifdef WOLF_CRYPTO_CB_FIND
/* Find callback used ONLY by the :1159/:1189 (F,F) vectors. It rewrites the
* INVALID_DEVID lookup that wc_CryptoCb_Curve25519MakePub/Generic issue into
@@ -1019,6 +1047,40 @@ int main(void)
WB_DRIVE3(mldsa.devId, wc_CryptoCb_PqcVerifyMsg(out, sizeof(out), in,
sizeof(in), &res, WC_PQC_SIG_TYPE_MLDSA, &mldsa));
/* `ret != 0 && res != NULL` after dev->cb: (T,T) a declining
* device clears *res, (T,F) res is NULL, (F,-) a device that
* completes leaves *res as it wrote it. */
if (wc_CryptoCb_RegisterDevice(WB_DEVID_VERIFY_OK,
wb_cb_pqc_verify_ok, NULL) != 0)
wb_fail = 1;
mldsa.devId = WB_DEVID;
res = 1;
(void)wc_CryptoCb_PqcVerify(out, sizeof(out), in, sizeof(in), NULL,
0, 0, &res, WC_PQC_SIG_TYPE_MLDSA, &mldsa);
if (res != 0)
wb_bad = 1;
(void)wc_CryptoCb_PqcVerify(out, sizeof(out), in, sizeof(in), NULL,
0, 0, NULL, WC_PQC_SIG_TYPE_MLDSA, &mldsa);
res = 1;
(void)wc_CryptoCb_PqcVerifyMsg(out, sizeof(out), in, sizeof(in),
&res, WC_PQC_SIG_TYPE_MLDSA, &mldsa);
if (res != 0)
wb_bad = 1;
(void)wc_CryptoCb_PqcVerifyMsg(out, sizeof(out), in, sizeof(in),
NULL, WC_PQC_SIG_TYPE_MLDSA, &mldsa);
mldsa.devId = WB_DEVID_VERIFY_OK;
res = 1;
(void)wc_CryptoCb_PqcVerify(out, sizeof(out), in, sizeof(in), NULL,
0, 0, &res, WC_PQC_SIG_TYPE_MLDSA, &mldsa);
if (res != 1)
wb_bad = 1;
res = 1;
(void)wc_CryptoCb_PqcVerifyMsg(out, sizeof(out), in, sizeof(in),
&res, WC_PQC_SIG_TYPE_MLDSA, &mldsa);
if (res != 1)
wb_bad = 1;
wc_CryptoCb_UnRegisterDevice(WB_DEVID_VERIFY_OK);
/* bonus: an unrecognized algorithm type leaves GetDevId at
* INVALID_DEVID, which then finds no device */
mldsa.devId = WB_DEVID;
@@ -1068,11 +1130,52 @@ int main(void)
"defined; PQC signature dispatch skipped");
#endif
/* ---- PQC stateful: PqcStatefulSigVerify ----
* Only key->devId is read before dev->cb, so a zeroed LmsKey is enough
* for the dev&&dev->cb guard and the *res clear after it. */
#ifdef WOLFSSL_HAVE_LMS
{
LmsKey lms;
XMEMSET(&lms, 0, sizeof(lms));
WB_DRIVE3(lms.devId, wc_CryptoCb_PqcStatefulSigVerify(out,
sizeof(out), in, sizeof(in), &res, WC_PQC_STATEFUL_SIG_TYPE_LMS,
&lms));
/* *res clear: same three vectors as the ML-DSA block above. */
if (wc_CryptoCb_RegisterDevice(WB_DEVID_VERIFY_OK,
wb_cb_pqc_verify_ok, NULL) != 0)
wb_fail = 1;
lms.devId = WB_DEVID;
res = 1;
(void)wc_CryptoCb_PqcStatefulSigVerify(out, sizeof(out), in,
sizeof(in), &res, WC_PQC_STATEFUL_SIG_TYPE_LMS, &lms);
if (res != 0)
wb_bad = 1;
(void)wc_CryptoCb_PqcStatefulSigVerify(out, sizeof(out), in,
sizeof(in), NULL, WC_PQC_STATEFUL_SIG_TYPE_LMS, &lms);
lms.devId = WB_DEVID_VERIFY_OK;
res = 1;
(void)wc_CryptoCb_PqcStatefulSigVerify(out, sizeof(out), in,
sizeof(in), &res, WC_PQC_STATEFUL_SIG_TYPE_LMS, &lms);
if (res != 1)
wb_bad = 1;
wc_CryptoCb_UnRegisterDevice(WB_DEVID_VERIFY_OK);
WB_NOTE("PQC LMS: PqcStatefulSigVerify dev&&dev->cb and "
"ret!=0&&res!=NULL driven");
}
#endif
/* ---- residuals: not driven in this pass ---- */
#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)
WB_NOTE("RESIDUAL: PqcStatefulSig{KeyGen,Sign,Verify,SigsLeft} "
#ifdef WOLFSSL_HAVE_LMS
WB_NOTE("RESIDUAL: PqcStatefulSig{KeyGen,Sign,SigsLeft} "
"(LMS/XMSS) not driven - needs LmsKey/XmssKey-specific setup, "
"out of scope for this pass");
#elif defined(WOLFSSL_HAVE_XMSS)
WB_NOTE("RESIDUAL: PqcStatefulSig{KeyGen,Sign,Verify,SigsLeft} "
"(XMSS) not driven - needs XmssKey-specific setup, "
"out of scope for this pass");
#endif
#ifdef WOLFSSL_SHE
WB_NOTE("RESIDUAL: wc_CryptoCb_She* family not driven - needs wc_SHE "