From 7906e67c1405db0687fa16c73c1ae828ccb6560b Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Tue, 5 May 2026 13:03:56 -0700 Subject: [PATCH] Address PR comments --- doc/LMS_XMSS_CryptoCb.md | 186 --------------------------------------- wolfcrypt/src/wc_lms.c | 140 +++++++++++++---------------- wolfcrypt/src/wc_xmss.c | 173 ++++++++++++++++-------------------- 3 files changed, 135 insertions(+), 364 deletions(-) delete mode 100644 doc/LMS_XMSS_CryptoCb.md diff --git a/doc/LMS_XMSS_CryptoCb.md b/doc/LMS_XMSS_CryptoCb.md deleted file mode 100644 index bc104b1341..0000000000 --- a/doc/LMS_XMSS_CryptoCb.md +++ /dev/null @@ -1,186 +0,0 @@ -# LMS / XMSS Crypto Callback support - -This document describes the wolfSSL-side groundwork that lets LMS / HSS -(RFC 8554) and XMSS / XMSS^MT (RFC 8391, both profiled in NIST SP 800-208) -participate in the `WOLF_CRYPTO_CB` framework. With this layer in place, the -wolfSSL PKCS#11 provider and the wolfHSM client can host stateful -hash-based keys on a device without the wolfSSL public API changing. - -No HSM-side or PKCS#11-provider code lives in this layer. It only adds the -dispatcher surface, the per-key device binding, and the helpers a backend -needs to answer the request. - -## Why route stateful hash-based keys through a device - -LMS and XMSS are one-time-signature trees: the private key holds a counter -that must be incremented on every signature, and signing the same index twice -breaks the security proof. Moving that counter to a hardware module is the -clean way to make the scheme operationally safe — the HSM is the natural -owner of the index, and an attacker who steals a host snapshot cannot replay -old indices. - -## PKCS#11 mapping - -PKCS#11 v3.1 standardised HSS and v3.2 added XMSS / XMSS^MT. The CryptoCb -surface mirrors what those mechanisms expose: - -| wolfSSL API | PKCS#11 analog | CryptoCb dispatcher | -|-----------------------|------------------------------------------------|----------------------------------------------| -| `wc_LmsKey_MakeKey` | `CKM_HSS_KEY_PAIR_GEN` | `wc_CryptoCb_PqcStatefulSigKeyGen` | -| `wc_LmsKey_Sign` | `CKM_HSS` (sign) | `wc_CryptoCb_PqcStatefulSigSign` | -| `wc_LmsKey_Verify` | `CKM_HSS` (verify) | `wc_CryptoCb_PqcStatefulSigVerify` | -| `wc_LmsKey_SigsLeft` | `CKA_HSS_KEYS_REMAINING` attribute | `wc_CryptoCb_PqcStatefulSigSigsLeft` | -| `wc_XmssKey_MakeKey` | `CKM_XMSS_KEY_PAIR_GEN` / `CKM_XMSSMT_KEY_PAIR_GEN` | `wc_CryptoCb_PqcStatefulSigKeyGen` | -| `wc_XmssKey_Sign` | `CKM_XMSS` / `CKM_XMSSMT` (sign) | `wc_CryptoCb_PqcStatefulSigSign` | -| `wc_XmssKey_Verify` | `CKM_XMSS` / `CKM_XMSSMT` (verify) | `wc_CryptoCb_PqcStatefulSigVerify` | -| `wc_XmssKey_SigsLeft` | XMSS remaining-sigs attribute | `wc_CryptoCb_PqcStatefulSigSigsLeft` | - -The four dispatchers are shared between LMS and XMSS, following the -`wc_CryptoCb_PqcSign*` family used for Dilithium and Falcon. A new -discriminator enum `wc_PqcStatefulSignatureType` (`WC_PQC_STATEFUL_SIG_TYPE_LMS`, -`WC_PQC_STATEFUL_SIG_TYPE_XMSS`) tells the callback which of `LmsKey*` or -`XmssKey*` the `void* key` field is. XMSS vs XMSS^MT is decided inside the -callback via the existing `XmssKey::is_xmssmt` field. - -`Reload`, `GetKid`, and `ExportPub` are not routed through CryptoCb, but each -is aware of HSM-backed keys: `Reload` short-circuits because state lives in -the device, `GetKid` logs a warning since `priv_raw` may be uninitialised, -and a new `ExportPub_ex` variant lets the caller bind the verify-only copy -to a `heap` / `devId` of their choosing so it dispatches through the same -device as the signing key. The external-backend variants (`ext_lms.c` / -`ext_xmss.c`, selected by `--with-liblms` / `--with-libxmss`) are -intentionally outside the scope of this layer and execute purely in -software. - -## Per-key device binding - -Each key carries the device-binding fields that other key types -(`RsaKey`, `ecc_key`, `dilithium_key`) already expose: - -```c -struct LmsKey { - /* ... existing fields ... */ -#ifdef WOLF_CRYPTO_CB - int devId; /* device identifier */ - void* devCtx; /* opaque per-device state, owned by the callback */ -#endif -#ifdef WOLF_PRIVATE_KEY_ID - byte id[LMS_MAX_ID_LEN]; /* device-side key identifier */ - int idLen; - char label[LMS_MAX_LABEL_LEN]; /* device-side key label */ - int labelLen; -#endif -}; -``` - -`XmssKey` carries the equivalent set under the same macro guards, with -`XMSS_MAX_ID_LEN` / `XMSS_MAX_LABEL_LEN`. The `*_MAX_ID_LEN` and -`*_MAX_LABEL_LEN` constants default to 32 and can be overridden by -predefining the macros. - -`devCtx`, `id`, and `label` are storage only — wolfSSL never reads or writes -them internally. Backends populate `devCtx` from the callback (typically the -first time they touch the key) and consume `id` / `label` to resolve the -on-device handle. - -## Public API additions - -```c -/* Bind a key to a device-side identifier or label. */ -#ifdef WOLF_PRIVATE_KEY_ID -WOLFSSL_API int wc_LmsKey_InitId (LmsKey * key, const unsigned char * id, - int len, void * heap, int devId); -WOLFSSL_API int wc_LmsKey_InitLabel(LmsKey * key, const char * label, - void * heap, int devId); -WOLFSSL_API int wc_XmssKey_InitId (XmssKey* key, const unsigned char* id, - int len, void* heap, int devId); -WOLFSSL_API int wc_XmssKey_InitLabel(XmssKey* key, const char* label, - void* heap, int devId); -#endif - -/* Hash a message according to the given algorithm. */ -WOLFSSL_API int wc_LmsKey_HashMsg (const LmsKey * key, const byte * msg, - word32 msgSz, byte * hash, - word32 * hashSz); -WOLFSSL_API int wc_XmssKey_HashMsg(const XmssKey* key, const byte* msg, - word32 msgSz, byte* hash, - word32* hashSz); - -/* Export a public key into a verify-only key with explicit heap and - * device bindings. */ -WOLFSSL_API int wc_LmsKey_ExportPub_ex (LmsKey * keyDst, - const LmsKey * keySrc, - void * heap, int devId); -WOLFSSL_API int wc_XmssKey_ExportPub_ex(XmssKey* keyDst, - const XmssKey* keySrc, - void* heap, int devId); -``` - -The `Init*` helpers follow the `wc_InitRsaKey_Id` / `wc_InitRsaKey_Label` -shape: they validate length bounds, delegate the rest of init to -`wc_LmsKey_Init` / `wc_XmssKey_Init`, then copy id / label onto the key. - -The `HashMsg` helpers honour the parameter set: - -| Algorithm | Hash families covered | -|-----------|-----------------------------------------------------------------| -| LMS / HSS | SHA-256 (32 bytes), SHA-256/192 (24 bytes), SHAKE256 (32 / 24) | -| XMSS / MT | SHA-256, SHA-512, SHAKE128, SHAKE256 (per `params->hash`) | - -`*hashSz` is in / out: callers pass the buffer size and receive the digest -length on success. - -## Sign / verify input format - -The CryptoCb dispatcher forwards the raw message to the callback. PKCS#11 -v3.2 section 6.66.8 ("XMSS and XMSSMT without hashing") and the analogous -text for HSS specify that those mechanisms take a pre-computed digest -rather than the message. Backends that need that behaviour — typically -PKCS#11 providers — call `wc_LmsKey_HashMsg` or `wc_XmssKey_HashMsg` from -inside the callback to produce the algorithm-dictated digest. Backends -that take the full message (typically wolfHSM) consume `msg` / `msgSz` -directly. Picking one or the other is a callback decision; the dispatcher -is agnostic. - -## Build configuration - -| `./configure` flag(s) | Effect | -|--------------------------------------------------------|-------------------------------------------------------| -| `--enable-lms --enable-xmss --enable-cryptocb` | Primary target. Full dispatcher and round-trip tests. | -| `--enable-lms --enable-xmss` | New dispatcher code is fully `#ifdef`-elided. | -| `--enable-cryptocb` | LMS / XMSS-less build; nothing CryptoCb-side breaks. | -| `CPPFLAGS=-DWOLF_PRIVATE_KEY_ID …` | Adds `id` / `label` fields and the `Init*` helpers. | - -## Verification - -`./wolfcrypt/test/testwolfcrypt` exercises the full dispatcher round trip: -inside `cryptocb_test`, `lms_test` and `xmss_test` run with the harness's -registered `myCryptoDevCb`, which clears the key's `devId`, invokes the -software API recursively, then restores `devId`. Sign and verify both go -through the dispatcher, so the produced signatures self-verify within the -harness. With no device registered, `lms_test` and `xmss_test` remain on the -software path and produce bit-identical KAT output. - -## Design notes - -- **Shared dispatcher, separate type tag.** The eight LMS / XMSS operations - collapse to four shared dispatchers (`KeyGen`, `Sign`, `Verify`, - `SigsLeft`) keyed on `wc_PqcStatefulSignatureType`. The pattern matches - the `PqcSign` family used for Dilithium / Falcon and reduces the surface - area a backend has to implement. -- **Verify carries `int* res`.** Following the Ed25519 / ECC / PqcVerify - convention, the verify dispatcher reports validity through a separate - `*res` flag, so a backend can distinguish a transport error from a - forged signature. The wrapping wolfSSL function still translates - `res != 1` to `SIG_VERIFY_E` for callers that do not see `res`. -- **`SigsLeft` carries `word32* sigsLeft`.** PKCS#11 defines - `CKA_HSS_KEYS_REMAINING` as a `CK_ULONG`-sized attribute; the callback - uses `word32*` so an HSS key at its 2^32 limit can be expressed - unambiguously. The wolfSSL public API still returns `int` and clamps at - `0x7FFFFFFF`. -- **HSM-backed keys skip the software write / read callbacks.** - `wc_LmsKey_MakeKey` / `_Sign` and the XMSS equivalents dispatch through - CryptoCb *before* validating `write_private_key` / `read_private_key` / - `context`. A device-backed key does not need dummy software callbacks. - On `CRYPTOCB_UNAVAILABLE` fall-through the software validations are - re-applied as normal. diff --git a/wolfcrypt/src/wc_lms.c b/wolfcrypt/src/wc_lms.c index 2c4649f9bc..29267a9cf8 100644 --- a/wolfcrypt/src/wc_lms.c +++ b/wolfcrypt/src/wc_lms.c @@ -41,73 +41,6 @@ #include #endif -/* Compute the digest of msg using the hash function dictated by the LMS - * parameter set. Crypto-callback / HSM backends that follow PKCS#11 v3.2 - * CKM_HSS semantics (pre-computed digest input) can call this from within - * their callback; backends that take the raw message (e.g. wolfHSM) can - * ignore it. *hashSz is in/out: it must be at least params->hash_len on - * entry and is set to the actual digest length on success. - * - * @param [in] key LMS key (must have a parameter set bound). - * @param [in] msg Message to hash. - * @param [in] msgSz Length of msg in bytes. - * @param [out] hash Buffer receiving the digest. - * @param [in,out] hashSz On entry, size of hash buffer. On success, - * the digest length. - * @return 0 on success. - * @return BAD_FUNC_ARG when an argument is NULL or the buffer is too - * small for the digest. - * @return NOT_COMPILED_IN when the param set's hash family is disabled. - */ -int wc_LmsKey_HashMsg(const LmsKey* key, const byte* msg, word32 msgSz, - byte* hash, word32* hashSz) -{ - int ret = 0; - word32 needSz; - - if ((key == NULL) || (msg == NULL) || (hash == NULL) || (hashSz == NULL)) - return BAD_FUNC_ARG; - if (key->params == NULL) - return BAD_FUNC_ARG; - needSz = (word32)key->params->hash_len; - if (*hashSz < needSz) - return BAD_FUNC_ARG; - - switch (key->params->lmsType & 0xF000) { - case LMS_SHA256: /* 32-byte SHA-256 */ - case LMS_SHA256_192: /* SHA-256 truncated to 24 bytes */ { - byte full[WC_SHA256_DIGEST_SIZE]; - ret = wc_Sha256Hash(msg, msgSz, full); - if (ret == 0) - XMEMCPY(hash, full, needSz); - break; - } - #ifdef WOLFSSL_LMS_SHAKE256 - case LMS_SHAKE256: /* SHAKE256 with 32-byte output */ - case LMS_SHAKE256_192: /* SHAKE256 with 24-byte output */ { - wc_Shake shake; - ret = wc_InitShake256(&shake, NULL, INVALID_DEVID); - if (ret == 0) { - ret = wc_Shake256_Update(&shake, msg, msgSz); - if (ret == 0) - ret = wc_Shake256_Final(&shake, hash, needSz); - wc_Shake256_Free(&shake); - } - break; - } - #endif - default: - WOLFSSL_MSG("LMS: unsupported hash family for HashMsg"); - ret = NOT_COMPILED_IN; - break; - } - - if (ret == 0) - *hashSz = needSz; - - return ret; -} - /* Calculate u. Appendix B. Works for w of 1, 2, 4, or 8. * @@ -1134,10 +1067,6 @@ int wc_LmsKey_MakeKey(LmsKey* key, WC_RNG* rng) WOLFSSL_MSG("error: LmsKey write callback is not set"); ret = BAD_FUNC_ARG; } - /* Callback context is opaque to wolfCrypt and may legitimately be NULL - * (e.g. callbacks that read/write a static buffer or HSM-backed keys - * with stub callbacks); no check needed here. */ - if (ret == 0) { const LmsParams* params = key->params; priv_data_len = LMS_PRIV_DATA_LEN(params->levels, params->height, @@ -1250,7 +1179,6 @@ int wc_LmsKey_Reload(LmsKey* key) WOLFSSL_MSG("error: LmsKey read callback is not set"); ret = BAD_FUNC_ARG; } - /* Callback context is opaque; NULL is allowed. */ if (ret == 0) { const LmsParams* params = key->params; @@ -1354,6 +1282,66 @@ int wc_LmsKey_GetPrivLen(const LmsKey* key, word32* len) return ret; } +/* Compute the digest of msg using the hash function dictated by the LMS + * parameter set. Crypto-callback / HSM backends that follow PKCS#11 v3.2 + * CKM_HSS semantics (pre-computed digest input) can call this from within + * their callback; backends that take the raw message (e.g. wolfHSM) can + * ignore it. *hashSz is in/out: it must be at least params->hash_len on + * entry and is set to the actual digest length on success. + * + * @param [in] key LMS key (must have a parameter set bound). + * @param [in] msg Message to hash. + * @param [in] msgSz Length of msg in bytes. + * @param [out] hash Buffer receiving the digest. + * @param [in,out] hashSz On entry, size of hash buffer. On success, + * the digest length. + * @return 0 on success. + * @return BAD_FUNC_ARG when an argument is NULL or the buffer is too + * small for the digest. + * @return NOT_COMPILED_IN when the param set's hash family is disabled. + */ +int wc_LmsKey_HashMsg(const LmsKey* key, const byte* msg, word32 msgSz, + byte* hash, word32* hashSz) +{ + int ret = 0; + word32 needSz; + + if ((key == NULL) || (msg == NULL) || (hash == NULL) || (hashSz == NULL)) + return BAD_FUNC_ARG; + if (key->params == NULL) + return BAD_FUNC_ARG; + needSz = (word32)key->params->hash_len; + if (*hashSz < needSz) + return BAD_FUNC_ARG; + + switch (key->params->lmsType & LMS_HASH_MASK) { + case LMS_SHA256: /* 32-byte SHA-256 */ + case LMS_SHA256_192: /* SHA-256 truncated to 24 bytes */ { + byte full[WC_SHA256_DIGEST_SIZE]; + ret = wc_Sha256Hash(msg, msgSz, full); + if (ret == 0) + XMEMCPY(hash, full, needSz); + break; + } + #ifdef WOLFSSL_LMS_SHAKE256 + case LMS_SHAKE256: /* SHAKE256 with 32-byte output */ + case LMS_SHAKE256_192: /* SHAKE256 with 24-byte output */ { + ret = wc_Shake256Hash(msg, msgSz, hash, needSz); + break; + } + #endif + default: + WOLFSSL_MSG("LMS: unsupported hash family for HashMsg"); + ret = NOT_COMPILED_IN; + break; + } + + if (ret == 0) + *hashSz = needSz; + + return ret; +} + /* Sign a message. * * @param [in, out] key LMS key to sign with. @@ -1419,7 +1407,6 @@ int wc_LmsKey_Sign(LmsKey* key, byte* sig, word32* sigSz, const byte* msg, WOLFSSL_MSG("error: LmsKey write/read callbacks are not set"); ret = BAD_FUNC_ARG; } - /* Callback context is opaque; NULL is allowed. */ if (ret == 0) { WC_DECLARE_VAR(state, LmsState, 1, 0); @@ -1493,9 +1480,7 @@ int wc_LmsKey_SigsLeft(LmsKey* key) int cbRet = wc_CryptoCb_PqcStatefulSigSigsLeft( WC_PQC_STATEFUL_SIG_TYPE_LMS, key, &sigsLeft); if (cbRet == 0) { - /* Clamp to int range; callers treat 0 as "exhausted". */ - return (sigsLeft > (word32)0x7FFFFFFF) - ? 0x7FFFFFFF : (int)sigsLeft; + return (sigsLeft != 0) ? 1 : 0; } /* The device owns the private state; no safe software fallback * exists because key->priv_raw does not reflect HSM state. */ @@ -1734,9 +1719,6 @@ int wc_LmsKey_Verify(LmsKey* key, const byte* sig, word32 sigSz, if ((key == NULL) || (sig == NULL) || (msg == NULL)) { ret = BAD_FUNC_ARG; } - if ((ret == 0) && (msgSz <= 0)) { - ret = BAD_FUNC_ARG; - } /* Check state. */ if ((ret == 0) && (key->state != WC_LMS_STATE_OK) && (key->state != WC_LMS_STATE_VERIFYONLY)) { diff --git a/wolfcrypt/src/wc_xmss.c b/wolfcrypt/src/wc_xmss.c index 2672638b73..f739f5ee08 100644 --- a/wolfcrypt/src/wc_xmss.c +++ b/wolfcrypt/src/wc_xmss.c @@ -41,96 +41,6 @@ #include #endif -/* Compute the digest of msg using the hash function dictated by the XMSS - * parameter set. Crypto-callback / HSM backends that follow PKCS#11 v3.2 - * CKM_XMSS / CKM_XMSSMT semantics (pre-computed digest input, see section - * 6.66.8 "XMSS and XMSSMT without hashing") can call this from within - * their callback; backends that take the raw message (e.g. wolfHSM) can - * ignore it. *hashSz is in/out: it must be at least params->n on entry - * and is set to the actual digest length on success. - * - * @param [in] key XMSS key (must have a parameter set bound). - * @param [in] msg Message to hash. - * @param [in] msgSz Length of msg in bytes. - * @param [out] hash Buffer receiving the digest. - * @param [in,out] hashSz On entry, size of hash buffer. On success, - * the digest length. - * @return 0 on success. - * @return BAD_FUNC_ARG when an argument is NULL or the buffer is too - * small for the digest. - * @return NOT_COMPILED_IN when the param set's hash family is disabled. - */ -int wc_XmssKey_HashMsg(const XmssKey* key, const byte* msg, word32 msgSz, - byte* hash, word32* hashSz) -{ - int ret = 0; - word32 needSz; - - if ((key == NULL) || (msg == NULL) || (hash == NULL) || (hashSz == NULL)) - return BAD_FUNC_ARG; - if (key->params == NULL) - return BAD_FUNC_ARG; - needSz = (word32)key->params->n; - if (*hashSz < needSz) - return BAD_FUNC_ARG; - - switch (key->params->hash) { - #ifdef WC_XMSS_SHA256 - case WC_HASH_TYPE_SHA256: { - /* SHA2_*_192 variants set n=24, but wc_Hash rejects an output - * smaller than WC_SHA256_DIGEST_SIZE. Hash to a full buffer and - * copy the requested prefix, mirroring LMS_SHA256_192. */ - byte full[WC_SHA256_DIGEST_SIZE]; - ret = wc_Sha256Hash(msg, msgSz, full); - if (ret == 0) - XMEMCPY(hash, full, needSz); - break; - } - #endif - #ifdef WC_XMSS_SHA512 - case WC_HASH_TYPE_SHA512: - ret = wc_Hash(WC_HASH_TYPE_SHA512, msg, msgSz, hash, needSz); - break; - #endif - #ifdef WC_XMSS_SHAKE128 - case WC_HASH_TYPE_SHAKE128: { - wc_Shake shake; - ret = wc_InitShake128(&shake, NULL, INVALID_DEVID); - if (ret == 0) { - ret = wc_Shake128_Update(&shake, msg, msgSz); - if (ret == 0) - ret = wc_Shake128_Final(&shake, hash, needSz); - wc_Shake128_Free(&shake); - } - break; - } - #endif - #ifdef WC_XMSS_SHAKE256 - case WC_HASH_TYPE_SHAKE256: { - wc_Shake shake; - ret = wc_InitShake256(&shake, NULL, INVALID_DEVID); - if (ret == 0) { - ret = wc_Shake256_Update(&shake, msg, msgSz); - if (ret == 0) - ret = wc_Shake256_Final(&shake, hash, needSz); - wc_Shake256_Free(&shake); - } - break; - } - #endif - default: - WOLFSSL_MSG("XMSS: unsupported hash for HashMsg"); - ret = NOT_COMPILED_IN; - break; - } - - if (ret == 0) - *hashSz = needSz; - - return ret; -} - - /*************************** * DIGEST init and free. ***************************/ @@ -1316,10 +1226,6 @@ int wc_XmssKey_MakeKey(XmssKey* key, WC_RNG* rng) WOLFSSL_MSG("error: XmssKey write callback is not set"); ret = BAD_FUNC_ARG; } - /* Callback context is opaque to wolfCrypt and may legitimately be NULL - * (e.g. callbacks that read/write a static buffer or HSM-backed keys - * with stub callbacks); no check needed here. */ - if (ret == 0) { /* Allocate sk array. */ ret = wc_xmsskey_alloc_sk(key); @@ -1450,7 +1356,6 @@ int wc_XmssKey_Reload(XmssKey* key) WOLFSSL_MSG("error: XmssKey write/read callbacks are not set"); ret = BAD_FUNC_ARG; } - /* Callback context is opaque; NULL is allowed. */ if (ret == 0) { /* Allocate sk array. */ @@ -1516,6 +1421,79 @@ int wc_XmssKey_GetPrivLen(const XmssKey* key, word32* len) return ret; } +/* Compute the digest of msg using the hash function dictated by the XMSS + * parameter set. Crypto-callback / HSM backends that follow PKCS#11 v3.2 + * CKM_XMSS / CKM_XMSSMT semantics (pre-computed digest input, see section + * 6.66.8 "XMSS and XMSSMT without hashing") can call this from within + * their callback; backends that take the raw message (e.g. wolfHSM) can + * ignore it. *hashSz is in/out: it must be at least params->n on entry + * and is set to the actual digest length on success. + * + * @param [in] key XMSS key (must have a parameter set bound). + * @param [in] msg Message to hash. + * @param [in] msgSz Length of msg in bytes. + * @param [out] hash Buffer receiving the digest. + * @param [in,out] hashSz On entry, size of hash buffer. On success, + * the digest length. + * @return 0 on success. + * @return BAD_FUNC_ARG when an argument is NULL or the buffer is too + * small for the digest. + * @return NOT_COMPILED_IN when the param set's hash family is disabled. + */ +int wc_XmssKey_HashMsg(const XmssKey* key, const byte* msg, word32 msgSz, + byte* hash, word32* hashSz) +{ + int ret = 0; + word32 needSz; + + if ((key == NULL) || (msg == NULL) || (hash == NULL) || (hashSz == NULL)) + return BAD_FUNC_ARG; + if (key->params == NULL) + return BAD_FUNC_ARG; + needSz = (word32)key->params->n; + if (*hashSz < needSz) + return BAD_FUNC_ARG; + + switch (key->params->hash) { + #ifdef WC_XMSS_SHA256 + case WC_HASH_TYPE_SHA256: { + /* SHA2_*_192 variants set n=24, but wc_Hash rejects an output + * smaller than WC_SHA256_DIGEST_SIZE. Hash to a full buffer and + * copy the requested prefix. */ + byte full[WC_SHA256_DIGEST_SIZE]; + ret = wc_Sha256Hash(msg, msgSz, full); + if (ret == 0) + XMEMCPY(hash, full, needSz); + break; + } + #endif + #ifdef WC_XMSS_SHA512 + case WC_HASH_TYPE_SHA512: + ret = wc_Hash(WC_HASH_TYPE_SHA512, msg, msgSz, hash, needSz); + break; + #endif + #ifdef WC_XMSS_SHAKE128 + case WC_HASH_TYPE_SHAKE128: + ret = wc_Shake128Hash(msg, msgSz, hash, needSz); + break; + #endif + #ifdef WC_XMSS_SHAKE256 + case WC_HASH_TYPE_SHAKE256: + ret = wc_Shake256Hash(msg, msgSz, hash, needSz); + break; + #endif + default: + WOLFSSL_MSG("XMSS: unsupported hash for HashMsg"); + ret = NOT_COMPILED_IN; + break; + } + + if (ret == 0) + *hashSz = needSz; + + return ret; +} + /* Sign the message using the XMSS secret key. * * @param [in] key XMSS key to use to sign. @@ -1582,7 +1560,6 @@ int wc_XmssKey_Sign(XmssKey* key, byte* sig, word32* sigLen, const byte* msg, WOLFSSL_MSG("error: XmssKey write/read callbacks are not set"); ret = BAD_FUNC_ARG; } - /* Callback context is opaque; NULL is allowed. */ if (ret == 0) { *sigLen = key->params->sig_len; @@ -1613,9 +1590,7 @@ int wc_XmssKey_SigsLeft(XmssKey* key) int cbRet = wc_CryptoCb_PqcStatefulSigSigsLeft( WC_PQC_STATEFUL_SIG_TYPE_XMSS, key, &sigsLeft); if (cbRet == 0) { - /* Clamp to int range; callers treat 0 as "exhausted". */ - return (sigsLeft > (word32)0x7FFFFFFF) - ? 0x7FFFFFFF : (int)sigsLeft; + return (sigsLeft != 0) ? 1 : 0; } /* The device owns the private state; no safe software fallback * exists because key->sk does not reflect HSM state. */