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

PR feedback: block GetKid when privKey is not set, add test case and entry for CI

This commit is contained in:
Paul Adelsbach
2026-10-08 20:02:46 -07:00
parent aaf8741097
commit 122d7ecc5d
9 changed files with 110 additions and 28 deletions

View File

@@ -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"]},

View File

@@ -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.

View File

@@ -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.

View File

@@ -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);

View File

@@ -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");

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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