diff --git a/.github/workflows/cryptocb-only.yml b/.github/workflows/cryptocb-only.yml index 5ef73d2317..6614eb281d 100644 --- a/.github/workflows/cryptocb-only.yml +++ b/.github/workflows/cryptocb-only.yml @@ -178,6 +178,12 @@ jobs: {"name": "lms-xmss-onlycb-no-swdev", "minutes": 4.0, "comment": "Both stripped without swdev, so nothing serves a key that has no device or whose device declines: test_wc_LmsKey_cb_only_no_device and test_wc_XmssKey_cb_only_no_device check that those operations, and Reload, fail with NO_VALID_DEVID. The tests that need a software LMS/XMSS are skipped. cryptocbutils=free builds the device check before the free dispatch, which swdev's WOLF_CRYPTO_CB_FIND compiles out.", "configure": ["--disable-swdev", "--enable-lms", "--enable-xmss", "--enable-cryptocbutils=free", "CPPFLAGS=-DWOLF_CRYPTO_CB_ONLY_LMS -DWOLF_CRYPTO_CB_ONLY_XMSS"]}, + {"name": "lms-xmss-swdev", "minutes": 4.0, + "comment": "LMS and XMSS with the software cores kept. swdev's WOLF_CRYPTO_CB_FIND still sends a key bound to no device to swdev, which keeps the private key, so lms_test checks that wc_LmsKey_GetKid fails rather than return a key ID that is not in memory.", + "configure": ["--enable-lms", "--enable-xmss"]}, + {"name": "lms-xmss-only-pk", "minutes": 2.0, + "comment": "LMS and XMSS stripped as the only public key algorithms, so they alone turn on swdev's public key dispatch. Every other entry also has RSA or ECC, which would hide a missing LMS/XMSS term in that guard: the build only warns and LMS/XMSS operations fail with NO_VALID_DEVID. cryptonly because TLS needs another public key algorithm; the base's ASN, PKCS#7, OCSP and CRL options need RSA or ECC, so they are off too.", + "configure": ["--enable-cryptonly", "--disable-rsa", "--disable-ecc", "--disable-dh", "--disable-curve25519", "--disable-ed25519", "--disable-curve448", "--disable-ed448", "--disable-mlkem", "--disable-dilithium", "--disable-dtls", "--disable-dtls13", "--disable-asn", "--disable-pkcs7", "--disable-ocsp", "--disable-ocspstapling", "--disable-ocspstapling2", "--disable-crl", "--enable-lms", "--enable-xmss", "CPPFLAGS=-DWOLF_CRYPTO_CB_ONLY_LMS -DWOLF_CRYPTO_CB_ONLY_XMSS"]}, {"name": "shake-xof", "minutes": 4.0, "comment": "WOLF_CRYPTO_CB_SHAKE_XOF: swdev handles SHAKE absorb and squeeze. No ONLY_* strip exists for SHAKE, so software SHAKE stays in. ML-KEM and ML-DSA reach swdev_shake through WOLF_CRYPTO_CB_FIND, and cryptocb_test runs shake_cb_xof_test.", "configure": ["CPPFLAGS=-DWOLF_CRYPTO_CB_SHAKE_XOF"]}, diff --git a/doc/dox_comments/header_files/wc_lms.h b/doc/dox_comments/header_files/wc_lms.h index da2fb26484..1ef77a3751 100644 --- a/doc/dox_comments/header_files/wc_lms.h +++ b/doc/dox_comments/header_files/wc_lms.h @@ -363,6 +363,10 @@ int wc_LmsKey_MakeKey(LmsKey* key, WC_RNG* rng); taken to be device-backed and Reload is a no-op. With a read callback the software reload runs as usual. + WOLF_CRYPTO_CB_ONLY_LMS removes the software reload, so the read + callback is never called and only the device-backed no-op + succeeds. + A reloaded key holds no public key: neither arm populates it. Call wc_LmsKey_ImportPubRaw() on a separate key to verify, or keep the public key exported at generation time. @@ -371,6 +375,8 @@ int wc_LmsKey_MakeKey(LmsKey* key, WC_RNG* rng); \return BAD_FUNC_ARG if any required pointer is NULL. \return WC_LMS_RC_* mapped error if the read callback fails. \return IO_FAILED_E if the private key could not be read. + \return NO_VALID_DEVID with WOLF_CRYPTO_CB_ONLY_LMS when the key is + not device-backed. \param [in,out] key Pointer to an LmsKey with parameters and read callback set. @@ -627,6 +633,8 @@ const char* wc_LmsKey_ParmToStr(enum wc_LmsParm lmsParm); \return 0 on success. \return BAD_FUNC_ARG if any required pointer is NULL. + \return BAD_STATE_E if the private key is not in memory, such as + when a device keeps it. \param [in,out] key Pointer to an LmsKey with a private key. \param [out] kid Receives a pointer to the I bytes. diff --git a/doc/dox_comments/header_files/wc_xmss.h b/doc/dox_comments/header_files/wc_xmss.h index 6568bd8d78..a35144508f 100644 --- a/doc/dox_comments/header_files/wc_xmss.h +++ b/doc/dox_comments/header_files/wc_xmss.h @@ -275,6 +275,10 @@ int wc_XmssKey_MakeKey(XmssKey* key, WC_RNG* rng); callback the software reload runs as usual, which needs the write callback set too. + WOLF_CRYPTO_CB_ONLY_XMSS removes the software reload, so the read + callback is never called and only the device-backed no-op + succeeds. + A reloaded key holds no public key: neither arm populates it. Call wc_XmssKey_ImportPubRaw() on a separate key to verify, or keep the public key exported at generation time. @@ -283,6 +287,8 @@ int wc_XmssKey_MakeKey(XmssKey* key, WC_RNG* rng); \return BAD_FUNC_ARG if any required pointer is NULL. \return WC_XMSS_RC_* mapped error if the read callback fails. \return IO_FAILED_E if the private key could not be read. + \return NO_VALID_DEVID with WOLF_CRYPTO_CB_ONLY_XMSS when the key is + not device-backed. \param [in,out] key Pointer to an XmssKey with parameters and read callback set. diff --git a/tests/api/test_lms_xmss.c b/tests/api/test_lms_xmss.c index 77de5a552e..352b0e2ae0 100644 --- a/tests/api/test_lms_xmss.c +++ b/tests/api/test_lms_xmss.c @@ -444,6 +444,8 @@ int test_wc_LmsKey_reload_devid(void) word32 sigSz; byte pub[64]; word32 pubSz = sizeof(pub); + const byte* kid = NULL; + word32 kidSz = 0; /* Zero so cleanup is safe if an early alloc failure skips init. */ XMEMSET(&key, 0, sizeof(key)); @@ -468,6 +470,8 @@ int test_wc_LmsKey_reload_devid(void) ExpectIntEQ(wc_LmsKey_Reload(&key), 0); /* The reload must have expanded the private key, not been skipped. */ ExpectNotNull(key.priv_data); + ExpectIntEQ(wc_LmsKey_GetKid(&key, &kid, &kidSz), 0); + ExpectIntEQ(kidSz, WC_LMS_I_LEN); /* Sign with the reloaded key and verify with a software-only key. */ sigSz = sizeof(sig); @@ -488,6 +492,8 @@ int test_wc_LmsKey_reload_devid(void) ExpectIntEQ(test_lms_set_params(&hsmKey), 0); ExpectIntEQ(wc_LmsKey_Reload(&hsmKey), 0); ExpectNull(hsmKey.priv_data); + ExpectIntEQ(wc_LmsKey_GetKid(&hsmKey, &kid, &kidSz), + WC_NO_ERR_TRACE(BAD_STATE_E)); wc_LmsKey_Free(&hsmKey); wc_LmsKey_Free(&vkey); @@ -642,6 +648,8 @@ int test_wc_LmsKey_keygen_no_sigsleft(void) defined(WOLF_CRYPTO_CB) LmsKey key; WC_RNG rng; + const byte* kid = NULL; + word32 kidSz = 0; XMEMSET(&key, 0, sizeof(key)); XMEMSET(&rng, 0, sizeof(rng)); @@ -654,6 +662,9 @@ int test_wc_LmsKey_keygen_no_sigsleft(void) ExpectIntEQ(test_lms_set_params(&key), 0); ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0); ExpectIntEQ(key.state, WC_LMS_STATE_OK); + /* The device keeps the private key, which contains the key ID. */ + ExpectIntEQ(wc_LmsKey_GetKid(&key, &kid, &kidSz), + WC_NO_ERR_TRACE(BAD_STATE_E)); wc_LmsKey_Free(&key); wc_FreeRng(&rng); @@ -662,6 +673,17 @@ int test_wc_LmsKey_keygen_no_sigsleft(void) return EXPECT_RESULT(); } +#if defined(WOLF_CRYPTO_CB_ONLY_LMS) && !defined(WOLFSSL_SWDEV) && \ + !defined(WOLFSSL_LMS_VERIFY_ONLY) +static int test_lms_read_fail(byte* priv, word32 privSz, void* context) +{ + (void)priv; + (void)privSz; + (void)context; + return WC_LMS_RC_READ_FAIL; +} +#endif + /* Callback-only LMS returns NO_VALID_DEVID when no device does the work. */ int test_wc_LmsKey_cb_only_no_device(void) { @@ -692,6 +714,14 @@ int test_wc_LmsKey_cb_only_no_device(void) ExpectIntEQ(wc_LmsKey_Reload(&key), WC_NO_ERR_TRACE(NO_VALID_DEVID)); wc_LmsKey_Free(&key); + /* A read callback skips the device no-op, and no software reload + * exists to call it. */ + ExpectIntEQ(wc_LmsKey_Init(&key, NULL, TEST_LMS_XMSS_CRYPTOCB_DEVID), 0); + ExpectIntEQ(test_lms_set_params(&key), 0); + ExpectIntEQ(wc_LmsKey_SetReadCb(&key, test_lms_read_fail), 0); + ExpectIntEQ(wc_LmsKey_Reload(&key), WC_NO_ERR_TRACE(NO_VALID_DEVID)); + wc_LmsKey_Free(&key); + /* The device makes the key, then declines sign, verify and sigs left. */ ExpectIntEQ(wc_LmsKey_Init(&key, NULL, TEST_LMS_XMSS_CRYPTOCB_DEVID), 0); ExpectIntEQ(test_lms_set_params(&key), 0); @@ -957,6 +987,18 @@ int test_wc_XmssKey_reload_devid_verify(void) return EXPECT_RESULT(); } +#if defined(WOLF_CRYPTO_CB_ONLY_XMSS) && !defined(WOLFSSL_SWDEV) && \ + !defined(WOLFSSL_XMSS_VERIFY_ONLY) && defined(TEST_XMSS_H10_AVAILABLE) +static enum wc_XmssRc test_xmss_read_fail(byte* priv, word32 privSz, + void* context) +{ + (void)priv; + (void)privSz; + (void)context; + return WC_XMSS_RC_READ_FAIL; +} +#endif + /* Same as test_wc_LmsKey_cb_only_no_device, for XMSS. */ int test_wc_XmssKey_cb_only_no_device(void) { @@ -987,6 +1029,14 @@ int test_wc_XmssKey_cb_only_no_device(void) ExpectIntEQ(wc_XmssKey_Reload(&key), WC_NO_ERR_TRACE(NO_VALID_DEVID)); wc_XmssKey_Free(&key); + /* A read callback skips the device no-op, and no software reload + * exists to call it. */ + ExpectIntEQ(wc_XmssKey_Init(&key, NULL, TEST_LMS_XMSS_CRYPTOCB_DEVID), 0); + ExpectIntEQ(wc_XmssKey_SetParamStr(&key, "XMSS-SHA2_10_256"), 0); + ExpectIntEQ(wc_XmssKey_SetReadCb(&key, test_xmss_read_fail), 0); + ExpectIntEQ(wc_XmssKey_Reload(&key), WC_NO_ERR_TRACE(NO_VALID_DEVID)); + wc_XmssKey_Free(&key); + /* The device makes the key, then declines sign, verify and sigs left. */ ExpectIntEQ(wc_XmssKey_Init(&key, NULL, TEST_LMS_XMSS_CRYPTOCB_DEVID), 0); ExpectIntEQ(wc_XmssKey_SetParamStr(&key, "XMSS-SHA2_10_256"), 0); diff --git a/tests/unit-mcdc/test_lms_fault_whitebox.c b/tests/unit-mcdc/test_lms_fault_whitebox.c index 0d7c34f621..6d6d66378d 100644 --- a/tests/unit-mcdc/test_lms_fault_whitebox.c +++ b/tests/unit-mcdc/test_lms_fault_whitebox.c @@ -941,8 +941,8 @@ static void wb_getkid_from_privraw(void) * its independence pair still needs the all-valid baseline row alongside it * (masking MC/DC on an OR chain: the operand's pair is baseline-all-false * vs only-that-operand-true, not two different-failure rows). No real - * keygen needed -- GetKid does not check state, only key/params/kid/kidSz, - * and reads a zeroed priv_raw harmlessly. + * keygen needed -- forcing privSet stands in for it, and GetKid reads a + * zeroed priv_raw harmlessly. ******************************************************************/ static void wb_getkid(void) { @@ -953,7 +953,13 @@ static void wb_getkid(void) XMEMSET(&key, 0, sizeof(key)); wc_LmsKey_Init(&key, NULL, INVALID_DEVID); - wc_LmsKey_SetParameters(&key, 1, 5, 8); /* baseline: all operands false */ + wc_LmsKey_SetParameters(&key, 1, 5, 8); + ret = wc_LmsKey_GetKid(&key, &kid, &kidSz); + if (ret != WC_NO_ERR_TRACE(BAD_STATE_E)) { + WB_NOTE("GetKid(privSet==0) did not fail"); + wb_fail = 1; + } + key.privSet = 1; /* baseline: all operands false */ ret = wc_LmsKey_GetKid(&key, &kid, &kidSz); if (ret != 0) { WB_NOTE("GetKid baseline failed"); diff --git a/wolfcrypt/src/wc_lms.c b/wolfcrypt/src/wc_lms.c index c15b8f2005..bb5660ea93 100644 --- a/wolfcrypt/src/wc_lms.c +++ b/wolfcrypt/src/wc_lms.c @@ -1043,6 +1043,7 @@ void wc_LmsKey_Free(LmsKey* key) } ForceZero(key->priv_raw, HSS_MAX_PRIVATE_KEY_LEN); + key->privSet = 0; ForceZero(&key->priv, sizeof(HssPrivKey)); key->write_private_key = NULL; @@ -1316,6 +1317,7 @@ int wc_LmsKey_MakeKey(LmsKey* key, WC_RNG* rng) /* Update state. */ key->state = WC_LMS_STATE_OK; key->pubSet = 1; + key->privSet = 1; } #endif /* WOLF_CRYPTO_CB_ONLY_LMS */ @@ -1332,13 +1334,17 @@ int wc_LmsKey_MakeKey(LmsKey* key, WC_RNG* rng) * * With a crypto callback device, the read callback and not the devId decides * whether the software reload runs. See wc_LmsKey_Reload below. + * WOLF_CRYPTO_CB_ONLY_LMS removes the software reload, so the read callback + * is never called. * * Neither arm populates key->pub, so the reloaded key can sign but cannot * export a public key or verify. * * @param [in, out] key LMS key. * - * Returns 0 on success. */ + * Returns 0 on success. + * Returns NO_VALID_DEVID with WOLF_CRYPTO_CB_ONLY_LMS unless the key is + * device-backed. */ int wc_LmsKey_Reload(LmsKey* key) { int ret = 0; @@ -1370,7 +1376,7 @@ int wc_LmsKey_Reload(LmsKey* key) #endif #ifdef WOLF_CRYPTO_CB_ONLY_LMS - /* The device owns the state, so there is nothing to reload. */ + /* No software reload exists to use the read callback. */ if (ret == 0) { ret = NO_VALID_DEVID; } @@ -1458,6 +1464,7 @@ int wc_LmsKey_Reload(LmsKey* key) if (ret == 0) { /* Update state. */ key->state = WC_LMS_STATE_OK; + key->privSet = 1; } #endif /* WOLF_CRYPTO_CB_ONLY_LMS */ @@ -2119,7 +2126,8 @@ int wc_LmsKey_Verify(LmsKey* key, const byte* sig, word32 sigSz, * @param [out] kidSz Size of key ID. * @return 0 on success. * @return BAD_FUNC_ARG when a key, kid or kidSz is NULL. - * @return NOT_COMPILED_IN when key is on a device. + * @return BAD_STATE_E when the private key is not in memory, such as when a + * device keeps it. */ int wc_LmsKey_GetKid(LmsKey* key, const byte** kid, word32* kidSz) { @@ -2130,31 +2138,17 @@ int wc_LmsKey_GetKid(LmsKey* key, const byte** kid, word32* kidSz) (kidSz == NULL)) { ret = BAD_FUNC_ARG; } + if ((ret == 0) && (!key->privSet)) { + WOLFSSL_MSG("error: LMS key contains no private key"); + ret = BAD_STATE_E; + } -#ifdef WOLF_CRYPTO_CB_ONLY_LMS - /* The device owns the private key, which contains the key ID. */ - if (ret == 0) { - ret = NOT_COMPILED_IN; - } - (void)offset; -#else -#ifdef WOLF_CRYPTO_CB - /* priv_raw is not populated for HSM-backed keys where the device owns - * the private state. Extend the CryptoCb surface if device-side KID - * retrieval becomes a requirement. - */ - if ((ret == 0) && (key->devId != INVALID_DEVID)) { - WOLFSSL_MSG( - "wc_LmsKey_GetKid: priv_raw may be uninitialised for HSM keys"); - } -#endif if (ret == 0) { /* SEED length is hash length. */ offset = HSS_Q_LEN + HSS_PRIV_KEY_PARAM_SET_LEN + key->params->hash_len; *kid = key->priv_raw + offset; *kidSz = HSS_PRIVATE_KEY_LEN(key->params->hash_len) - offset; } -#endif /* WOLF_CRYPTO_CB_ONLY_LMS */ return ret; } diff --git a/wolfcrypt/src/wc_xmss.c b/wolfcrypt/src/wc_xmss.c index 6bdd0999e4..906ba03dec 100644 --- a/wolfcrypt/src/wc_xmss.c +++ b/wolfcrypt/src/wc_xmss.c @@ -1382,6 +1382,8 @@ int wc_XmssKey_MakeKey(XmssKey* key, WC_RNG* rng) * * With a crypto callback device, the read callback and not the devId decides * whether the software reload runs. See wc_XmssKey_Reload below. + * WOLF_CRYPTO_CB_ONLY_XMSS removes the software reload, so the read callback + * is never called. * * Neither arm populates key->pk, so the reloaded key can sign but cannot * export a public key or verify. @@ -1396,6 +1398,8 @@ int wc_XmssKey_MakeKey(XmssKey* key, WC_RNG* rng) * @return MEMORY_E when allocating dynamic memory fails. * @return BAD_STATE_E when wrong state for operation. * @return IO_FAILED_E when reading private key failed. + * @return NO_VALID_DEVID with WOLF_CRYPTO_CB_ONLY_XMSS unless the key is + * device-backed. */ int wc_XmssKey_Reload(XmssKey* key) { @@ -1428,7 +1432,7 @@ int wc_XmssKey_Reload(XmssKey* key) #endif #ifdef WOLF_CRYPTO_CB_ONLY_XMSS - /* The device owns the state, so there is nothing to reload. */ + /* No software reload exists to use the read callback. */ if (ret == 0) { ret = NO_VALID_DEVID; } diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index a626236268..0d7db84008 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -71998,15 +71998,20 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t lms_test(void) if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); ret = wc_LmsKey_GetKid(&signingKey, &kid, &kidSz); -#ifdef WOLF_CRYPTO_CB_ONLY_LMS - /* The key ID stays on the device. */ - if (ret != WC_NO_ERR_TRACE(NOT_COMPILED_IN)) +#if defined(WOLFSSL_SWDEV) && defined(WOLF_CRYPTO_CB_FIND) + /* swdev keeps the private key, which contains the key ID. */ + if (ret != WC_NO_ERR_TRACE(BAD_STATE_E)) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); #else if (ret != 0) { ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); } if (kidSz != WC_LMS_I_LEN) { ERROR_OUT(WC_TEST_RET_ENC_I(kidSz), out); } + /* The key ID follows the L, LMS and LM-OTS types in the public key. */ + if (XMEMCMP(kid, signingKey.pub + LMS_L_LEN + 2 * LMS_TYPE_LEN, + kidSz) != 0) { + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } #endif ret = wc_LmsKey_ExportPub_ex(&verifyKey, &signingKey, NULL, devId); diff --git a/wolfssl/wolfcrypt/wc_lms.h b/wolfssl/wolfcrypt/wc_lms.h index 8743d249a0..76446453e2 100644 --- a/wolfssl/wolfcrypt/wc_lms.h +++ b/wolfssl/wolfcrypt/wc_lms.h @@ -881,6 +881,9 @@ struct LmsKey { int labelLen; #endif WC_BITFIELD pubSet:1; /* pub holds a public key */ +#ifndef WOLFSSL_LMS_VERIFY_ONLY + WC_BITFIELD privSet:1; /* priv_raw contains the private key */ +#endif }; #ifndef WC_LMSKEY_TYPE_DEFINED