diff --git a/tests/api/test_falcon.c b/tests/api/test_falcon.c index 28327e1549..5c526e2685 100644 --- a/tests/api/test_falcon.c +++ b/tests/api/test_falcon.c @@ -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(); +} diff --git a/tests/api/test_falcon.h b/tests/api/test_falcon.h index fba3924895..ed4a66e2de 100644 --- a/tests/api/test_falcon.h +++ b/tests/api/test_falcon.h @@ -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 */ diff --git a/tests/unit-mcdc/test_cryptocb_whitebox.c b/tests/unit-mcdc/test_cryptocb_whitebox.c index 3399b464c4..fd424a148e 100644 --- a/tests/unit-mcdc/test_cryptocb_whitebox.c +++ b/tests/unit-mcdc/test_cryptocb_whitebox.c @@ -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 "