diff --git a/doc/dox_comments/header_files/wc_slhdsa.h b/doc/dox_comments/header_files/wc_slhdsa.h index dec6bae790..5cc843c3b2 100644 --- a/doc/dox_comments/header_files/wc_slhdsa.h +++ b/doc/dox_comments/header_files/wc_slhdsa.h @@ -42,6 +42,103 @@ int wc_SlhDsaKey_Init(SlhDsaKey* key, enum SlhDsaParam param, void* heap, int devId); +/*! + \ingroup SLH_DSA + + \brief Initializes an SLH-DSA key with a device-side key identifier (id). + Equivalent to wc_SlhDsaKey_Init() but also stashes a binary id that a + crypto callback can use to look up the underlying key material on the + device. Only available when wolfSSL is built with WOLF_PRIVATE_KEY_ID. + + The id is copied into the key object; the caller may free its buffer + immediately after this call returns. + + \return 0 on success. + \return BAD_FUNC_ARG if key is NULL, or if id is NULL while len > 0. + \return BUFFER_E if len is negative or greater than SLHDSA_MAX_ID_LEN. + + \param [in,out] key Pointer to the SlhDsaKey to initialize. + \param [in] param Parameter set to use (see wc_SlhDsaKey_Init). + \param [in] id Pointer to the device-side key identifier bytes. May be + NULL if len is 0. + \param [in] len Number of bytes in id. Must be in [0, SLHDSA_MAX_ID_LEN]. + \param [in] heap Pointer to heap hint for dynamic memory allocation. + May be NULL. + \param [in] devId Device identifier for the crypto callback. Should be + a registered cb devId, not INVALID_DEVID, for the id to be meaningful. + + _Example_ + \code + SlhDsaKey key; + unsigned char id[8] = { 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08 }; + int ret; + + ret = wc_SlhDsaKey_Init_id(&key, SLHDSA_SHAKE128F, id, sizeof(id), + NULL, devId); + if (ret != 0) { + // error initializing key with id + } + // ... use key, the cb resolves id -> device key ... + wc_SlhDsaKey_Free(&key); + \endcode + + \sa wc_SlhDsaKey_Init + \sa wc_SlhDsaKey_Init_label + \sa wc_SlhDsaKey_Free +*/ +int wc_SlhDsaKey_Init_id(SlhDsaKey* key, enum SlhDsaParam param, + const unsigned char* id, int len, void* heap, int devId); + +/*! + \ingroup SLH_DSA + + \brief Initializes an SLH-DSA key with a device-side key label. + Equivalent to wc_SlhDsaKey_Init() but also stashes a label string that + a crypto callback can use to look up the underlying key material on + the device. Only available when wolfSSL is built with + WOLF_PRIVATE_KEY_ID. + + The label length is taken via XSTRLEN, so embedded NUL bytes terminate + the label. The copy stored in key->label is NOT guaranteed to be + NUL-terminated (when the input is exactly SLHDSA_MAX_LABEL_LEN bytes + the entire array is consumed). Consumers must read at most + key->labelLen bytes — do not pass key->label to C-string APIs. + + \return 0 on success. + \return BAD_FUNC_ARG if key or label is NULL. + \return BUFFER_E if label is empty or longer than SLHDSA_MAX_LABEL_LEN. + + \param [in,out] key Pointer to the SlhDsaKey to initialize. + \param [in] param Parameter set to use (see wc_SlhDsaKey_Init). + \param [in] label NUL-terminated device-side key label string. + \param [in] heap Pointer to heap hint for dynamic memory allocation. + May be NULL. + \param [in] devId Device identifier for the crypto callback. Should be + a registered cb devId, not INVALID_DEVID, for the label to be + meaningful. + + _Example_ + \code + SlhDsaKey key; + int ret; + + ret = wc_SlhDsaKey_Init_label(&key, SLHDSA_SHAKE128F, + "device-key-1", NULL, devId); + if (ret != 0) { + // error initializing key with label + } + // ... use key, the cb resolves label -> device key ... + wc_SlhDsaKey_Free(&key); + \endcode + + \sa wc_SlhDsaKey_Init + \sa wc_SlhDsaKey_Init_id + \sa wc_SlhDsaKey_Free +*/ +int wc_SlhDsaKey_Init_label(SlhDsaKey* key, enum SlhDsaParam param, + const char* label, void* heap, int devId); + /*! \ingroup SLH_DSA diff --git a/wolfcrypt/src/cryptocb.c b/wolfcrypt/src/cryptocb.c index 0036556ba9..5e2312c497 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -1286,7 +1286,8 @@ int wc_CryptoCb_PqcDecapsulate(const byte* ciphertext, word32 ciphertextLen, } #endif /* WOLFSSL_HAVE_MLKEM */ -#if defined(HAVE_FALCON) || defined(HAVE_DILITHIUM) +#if defined(HAVE_FALCON) || defined(HAVE_DILITHIUM) || \ + defined(WOLFSSL_HAVE_SLHDSA) int wc_CryptoCb_PqcSigGetDevId(int type, void* key) { int devId = INVALID_DEVID; @@ -1305,6 +1306,11 @@ int wc_CryptoCb_PqcSigGetDevId(int type, void* key) devId = ((falcon_key*) key)->devId; } #endif +#if defined(WOLFSSL_HAVE_SLHDSA) + if (type == WC_PQC_SIG_TYPE_SLHDSA) { + devId = ((SlhDsaKey*) key)->devId; + } +#endif return devId; } @@ -1454,7 +1460,7 @@ int wc_CryptoCb_PqcSignatureCheckPrivKey(void* key, int type, return wc_CryptoCb_TranslateErrorCode(ret); } -#endif /* HAVE_FALCON || HAVE_DILITHIUM */ +#endif /* HAVE_FALCON || HAVE_DILITHIUM || WOLFSSL_HAVE_SLHDSA */ #ifndef NO_AES #ifdef HAVE_AESGCM diff --git a/wolfcrypt/src/wc_slhdsa.c b/wolfcrypt/src/wc_slhdsa.c index eb4a022113..d96ed528ee 100644 --- a/wolfcrypt/src/wc_slhdsa.c +++ b/wolfcrypt/src/wc_slhdsa.c @@ -6557,9 +6557,14 @@ int wc_SlhDsaKey_Init(SlhDsaKey* key, enum SlhDsaParam param, void* heap, /* Set heap hint to use with all allocations. */ key->heap = heap; #ifdef WOLF_CRYPTO_CB - /* Set device id. */ + /* Set device context and id. */ + key->devCtx = NULL; key->devId = devId; #endif + #ifdef WOLF_PRIVATE_KEY_ID + key->idLen = 0; + key->labelLen = 0; + #endif #ifdef WOLFSSL_SLHDSA_SHA2 if (SLHDSA_IS_SHA2(param)) { @@ -6595,14 +6600,106 @@ int wc_SlhDsaKey_Init(SlhDsaKey* key, enum SlhDsaParam param, void* heap, return ret; } +#ifdef WOLF_PRIVATE_KEY_ID +/* Initialize an SLH-DSA key with a device key id. + * + * @param [in] key SLH-DSA key. + * @param [in] param SLH-DSA parameter set to use. + * @param [in] id Device-side key handle bytes. + * @param [in] len Length of id in bytes. + * @param [in] heap Dynamic memory allocation hint. + * @param [in] devId Device Id. + * @return 0 on success. + * @return BAD_FUNC_ARG when key is NULL or when id is NULL with len > 0. + * @return BUFFER_E when len is negative or larger than SLHDSA_MAX_ID_LEN. + */ +int wc_SlhDsaKey_Init_id(SlhDsaKey* key, enum SlhDsaParam param, + const unsigned char* id, int len, void* heap, int devId) +{ + int ret = 0; + + if (key == NULL) { + ret = BAD_FUNC_ARG; + } + /* Reject id == NULL with len > 0. */ + if ((ret == 0) && (id == NULL) && (len > 0)) { + ret = BAD_FUNC_ARG; + } + if ((ret == 0) && ((len < 0) || (len > SLHDSA_MAX_ID_LEN))) { + ret = BUFFER_E; + } + + if (ret == 0) { + ret = wc_SlhDsaKey_Init(key, param, heap, devId); + } + if ((ret == 0) && (id != NULL) && (len > 0)) { + XMEMCPY(key->id, id, (size_t)len); + key->idLen = len; + } + + return ret; +} + +/* Initialize an SLH-DSA key with a device key label. + * + * Label length is taken via XSTRLEN; embedded NULs terminate the label. + * + * @param [in] key SLH-DSA key. + * @param [in] param SLH-DSA parameter set to use. + * @param [in] label NUL-terminated device-side key label. + * @param [in] heap Dynamic memory allocation hint. + * @param [in] devId Device Id. + * @return 0 on success. + * @return BAD_FUNC_ARG when key or label is NULL. + * @return BUFFER_E when label is empty or longer than SLHDSA_MAX_LABEL_LEN. + */ +int wc_SlhDsaKey_Init_label(SlhDsaKey* key, enum SlhDsaParam param, + const char* label, void* heap, int devId) +{ + int ret = 0; + int labelLen = 0; + + if ((key == NULL) || (label == NULL)) { + ret = BAD_FUNC_ARG; + } + if (ret == 0) { + labelLen = (int)XSTRLEN(label); + if ((labelLen == 0) || (labelLen > SLHDSA_MAX_LABEL_LEN)) { + ret = BUFFER_E; + } + } + + if (ret == 0) { + ret = wc_SlhDsaKey_Init(key, param, heap, devId); + } + if (ret == 0) { + XMEMCPY(key->label, label, (size_t)labelLen); + key->labelLen = labelLen; + } + + return ret; +} +#endif /* WOLF_PRIVATE_KEY_ID */ + /* Free the SLH-DSA key. * * @param [in] key SLH-DSA key. Cannot be used after this call. */ void wc_SlhDsaKey_Free(SlhDsaKey* key) { - /* Check we have a valid key to free. */ - if ((key != NULL) && (key->params != NULL)) { + if (key == NULL) + return; + +#if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_FREE) + if ((key->params != NULL) && (key->devId != INVALID_DEVID)) { + (void)wc_CryptoCb_Free(key->devId, WC_ALGO_TYPE_PK, + WC_PK_TYPE_PQC_SIG_KEYGEN, + WC_PQC_SIG_TYPE_SLHDSA, + (void*)key); + } +#endif + + if (key->params != NULL) { /* Ensure the private key data is zeroized. */ ForceZero(key->sk, (size_t)key->params->n * 2); #ifdef WOLFSSL_SLHDSA_SHA2 @@ -6641,6 +6738,17 @@ void wc_SlhDsaKey_Free(SlhDsaKey* key) wc_Shake256_Free(&key->hash.shk.shake); } } + +#ifdef WOLF_PRIVATE_KEY_ID + key->idLen = 0; + key->labelLen = 0; +#endif +#ifdef WOLF_CRYPTO_CB + key->devCtx = NULL; + key->devId = INVALID_DEVID; +#endif + /* Marks the key freed; subsequent Frees become no-ops. */ + key->params = NULL; } /* Set the HashAddress based on message digest data. @@ -6752,6 +6860,24 @@ int wc_SlhDsaKey_MakeKey(SlhDsaKey* key, WC_RNG* rng) if ((key == NULL) || (key->params == NULL) || (rng == NULL)) { ret = BAD_FUNC_ARG; } + +#ifdef WOLF_CRYPTO_CB + if (ret == 0) { + #ifndef WOLF_CRYPTO_CB_FIND + if (key->devId != INVALID_DEVID) + #endif + { + /* size is the SlhDsaParam enum (S/F variants are distinct). */ + ret = wc_CryptoCb_MakePqcSignatureKey(rng, + WC_PQC_SIG_TYPE_SLHDSA, (int)key->params->param, key); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) + return ret; + /* fall-through when unavailable */ + ret = 0; + } + } +#endif + if (ret == 0) { /* Steps 1-5: Generate the 3 random hashes. */ ret = wc_RNG_GenerateBlock(rng, key->sk, 3U * key->params->n); @@ -7251,6 +7377,23 @@ int wc_SlhDsaKey_Sign(SlhDsaKey* key, const byte* ctx, byte ctxSz, else if ((key->flags & WC_SLHDSA_FLAG_PRIVATE) == 0) { ret = MISSING_KEY; } + +#ifdef WOLF_CRYPTO_CB + if (ret == 0) { + #ifndef WOLF_CRYPTO_CB_FIND + if (key->devId != INVALID_DEVID) + #endif + { + ret = wc_CryptoCb_PqcSign(msg, msgSz, sig, sigSz, ctx, ctxSz, + WC_HASH_TYPE_NONE, rng, WC_PQC_SIG_TYPE_SLHDSA, key); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) + return ret; + /* fall-through when unavailable */ + ret = 0; + } + } +#endif + if (ret == 0) { /* Generate n bytes of random. */ ret = wc_RNG_GenerateBlock(rng, addRnd, key->params->n); @@ -7459,6 +7602,27 @@ int wc_SlhDsaKey_Verify(SlhDsaKey* key, const byte* ctx, byte ctxSz, else if ((key->flags & WC_SLHDSA_FLAG_PUBLIC) == 0) { ret = MISSING_KEY; } + +#ifdef WOLF_CRYPTO_CB + if (ret == 0) { + #ifndef WOLF_CRYPTO_CB_FIND + if (key->devId != INVALID_DEVID) + #endif + { + int res = 0; + ret = wc_CryptoCb_PqcVerify(sig, sigSz, msg, msgSz, ctx, ctxSz, + WC_HASH_TYPE_NONE, &res, WC_PQC_SIG_TYPE_SLHDSA, key); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + if (ret != 0) + return ret; + return (res == 1) ? 0 : SIG_VERIFY_E; + } + /* fall-through when unavailable */ + ret = 0; + } + } +#endif + if (ret == 0) { byte md[SLHDSA_MAX_MD]; byte n = key->params->n; @@ -8119,6 +8283,30 @@ int wc_SlhDsaKey_SignHash(SlhDsaKey* key, const byte* ctx, byte ctxSz, else if ((key->flags & WC_SLHDSA_FLAG_PRIVATE) == 0) { ret = MISSING_KEY; } + /* The cryptocb path below casts hashType to word32 to fit the + * wc_CryptoInfo.preHashType field. Reject negative enum values here so + * the cast can't smuggle a huge unsigned value past the callback (the + * downstream prehash validator only inspects values it knows about). */ + else if ((int)hashType < 0) { + ret = BAD_FUNC_ARG; + } + +#ifdef WOLF_CRYPTO_CB + if (ret == 0) { + #ifndef WOLF_CRYPTO_CB_FIND + if (key->devId != INVALID_DEVID) + #endif + { + ret = wc_CryptoCb_PqcSign(hash, hashSz, sig, sigSz, ctx, ctxSz, + (word32)hashType, rng, WC_PQC_SIG_TYPE_SLHDSA, key); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) + return ret; + /* fall-through when unavailable */ + ret = 0; + } + } +#endif + if (ret == 0) { /* Generate n bytes of random. */ ret = wc_RNG_GenerateBlock(rng, addRnd, key->params->n); @@ -8221,6 +8409,34 @@ int wc_SlhDsaKey_VerifyHash(SlhDsaKey* key, const byte* ctx, byte ctxSz, else if ((key->flags & WC_SLHDSA_FLAG_PUBLIC) == 0) { ret = MISSING_KEY; } + /* The cryptocb path below casts hashType to word32 to fit the + * wc_CryptoInfo.preHashType field. Reject negative enum values here so + * the cast can't smuggle a huge unsigned value past the callback (the + * downstream prehash validator only inspects values it knows about). */ + else if ((int)hashType < 0) { + ret = BAD_FUNC_ARG; + } + +#ifdef WOLF_CRYPTO_CB + if (ret == 0) { + #ifndef WOLF_CRYPTO_CB_FIND + if (key->devId != INVALID_DEVID) + #endif + { + int res = 0; + ret = wc_CryptoCb_PqcVerify(sig, sigSz, hash, hashSz, ctx, ctxSz, + (word32)hashType, &res, WC_PQC_SIG_TYPE_SLHDSA, key); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + if (ret != 0) + return ret; + return (res == 1) ? 0 : SIG_VERIFY_E; + } + /* fall-through when unavailable */ + ret = 0; + } + } +#endif + if (ret == 0) { /* Alg 24, Steps 4-19: Validate caller-supplied pre-hashed digest length * and select OID for the chosen hash algorithm. */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index ed33d6ffb4..0660728c21 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -54684,7 +54684,11 @@ static wc_test_ret_t slhdsa_test_param(enum SlhDsaParam param) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); } - ret = wc_SlhDsaKey_Init(key, param, NULL, INVALID_DEVID); + /* Use the module-global devId so that when cryptocb_test() runs this + * test with a registered callback, MakeKey/Sign/Verify route through + * the cryptocb dispatcher. In standalone runs devId == INVALID_DEVID + * and the calls go straight to the SW implementation. */ + ret = wc_SlhDsaKey_Init(key, param, NULL, devId); if (ret != 0) { ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); } @@ -54707,7 +54711,7 @@ static wc_test_ret_t slhdsa_test_param(enum SlhDsaParam param) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); } - ret = wc_SlhDsaKey_Init(key_vfy, param, NULL, INVALID_DEVID); + ret = wc_SlhDsaKey_Init(key_vfy, param, NULL, devId); if (ret != 0) { ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); } @@ -54848,6 +54852,163 @@ static wc_test_ret_t slhdsa_test_param(enum SlhDsaParam param) #define SLHDSA_TEST_HAVE_ANY_PARAM #endif +#if defined(WOLF_PRIVATE_KEY_ID) && \ + (defined(WOLFSSL_SLHDSA_PARAM_128S) || defined(WOLFSSL_SLHDSA_PARAM_128F) || \ + defined(WOLFSSL_SLHDSA_PARAM_SHA2_128S) || \ + defined(WOLFSSL_SLHDSA_PARAM_SHA2_128F)) +/* Exercise wc_SlhDsaKey_Init_id / _Init_label argument validation and + * id/label storage round-trip. Independent of any cryptocb device. */ +static wc_test_ret_t slhdsa_id_label_test(void) +{ + wc_test_ret_t ret; + SlhDsaKey key; + static const unsigned char id[] = { + 0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6, 0x07, 0x18, + 0x29, 0x3a, 0x4b, 0x5c, 0x6d, 0x7e, 0x8f, 0x90 + }; + static const char label[] = "slh-dsa-test-label"; + enum SlhDsaParam param = +#ifdef WOLFSSL_SLHDSA_PARAM_128S + SLHDSA_SHAKE128S; +#elif defined(WOLFSSL_SLHDSA_PARAM_128F) + SLHDSA_SHAKE128F; +#elif defined(WOLFSSL_SLHDSA_PARAM_SHA2_128S) + SLHDSA_SHA2_128S; +#else + SLHDSA_SHA2_128F; +#endif + + /* Zero the stack key so rejection-path tests below don't read + * uninitialized fields if a future Init refactor inspects key state + * before zeroizing it. */ + XMEMSET(&key, 0, sizeof(key)); + + /* NULL key rejected. */ + ret = wc_SlhDsaKey_Init_id(NULL, param, id, (int)sizeof(id), HEAP_HINT, + INVALID_DEVID); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_EC(ret); + + /* (id == NULL, len > 0) is the silent-contradiction case the original + * review flagged; must be rejected. */ + ret = wc_SlhDsaKey_Init_id(&key, param, NULL, 8, HEAP_HINT, INVALID_DEVID); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_EC(ret); + + /* Length over the cap rejected with BUFFER_E. */ + ret = wc_SlhDsaKey_Init_id(&key, param, id, SLHDSA_MAX_ID_LEN + 1, + HEAP_HINT, INVALID_DEVID); + if (ret != WC_NO_ERR_TRACE(BUFFER_E)) + return WC_TEST_RET_ENC_EC(ret); + + /* Negative length rejected. */ + ret = wc_SlhDsaKey_Init_id(&key, param, id, -1, HEAP_HINT, INVALID_DEVID); + if (ret != WC_NO_ERR_TRACE(BUFFER_E)) + return WC_TEST_RET_ENC_EC(ret); + + /* Successful init copies the id and stores its length. */ + ret = wc_SlhDsaKey_Init_id(&key, param, id, (int)sizeof(id), HEAP_HINT, + INVALID_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (key.idLen != (int)sizeof(id)) + ret = WC_TEST_RET_ENC_NC; + if ((ret == 0) && (XMEMCMP(key.id, id, sizeof(id)) != 0)) + ret = WC_TEST_RET_ENC_NC; + wc_SlhDsaKey_Free(&key); + if (ret != 0) + return ret; + XMEMSET(&key, 0, sizeof(key)); + + /* (id != NULL, len == 0) is accepted as a no-op. */ + ret = wc_SlhDsaKey_Init_id(&key, param, id, 0, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (key.idLen != 0) + ret = WC_TEST_RET_ENC_NC; + wc_SlhDsaKey_Free(&key); + if (ret != 0) + return ret; + XMEMSET(&key, 0, sizeof(key)); + + /* Boundary: exactly SLHDSA_MAX_ID_LEN bytes must be accepted and round + * trip byte-for-byte. */ + { + unsigned char id_max[SLHDSA_MAX_ID_LEN]; + int i; + for (i = 0; i < SLHDSA_MAX_ID_LEN; i++) + id_max[i] = (unsigned char)(0x40 + i); + ret = wc_SlhDsaKey_Init_id(&key, param, id_max, SLHDSA_MAX_ID_LEN, + HEAP_HINT, INVALID_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (key.idLen != SLHDSA_MAX_ID_LEN) + ret = WC_TEST_RET_ENC_NC; + if ((ret == 0) && + (XMEMCMP(key.id, id_max, SLHDSA_MAX_ID_LEN) != 0)) + ret = WC_TEST_RET_ENC_NC; + wc_SlhDsaKey_Free(&key); + if (ret != 0) + return ret; + XMEMSET(&key, 0, sizeof(key)); + } + + /* Init_label: NULL label / NULL key rejected. */ + ret = wc_SlhDsaKey_Init_label(NULL, param, label, HEAP_HINT, + INVALID_DEVID); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_SlhDsaKey_Init_label(&key, param, NULL, HEAP_HINT, + INVALID_DEVID); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_EC(ret); + + /* Empty label is rejected. */ + ret = wc_SlhDsaKey_Init_label(&key, param, "", HEAP_HINT, INVALID_DEVID); + if (ret != WC_NO_ERR_TRACE(BUFFER_E)) + return WC_TEST_RET_ENC_EC(ret); + + /* Successful init copies the label and stores its length. */ + ret = wc_SlhDsaKey_Init_label(&key, param, label, HEAP_HINT, + INVALID_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (key.labelLen != (int)XSTRLEN(label)) + ret = WC_TEST_RET_ENC_NC; + if ((ret == 0) && + (XMEMCMP(key.label, label, (size_t)key.labelLen) != 0)) + ret = WC_TEST_RET_ENC_NC; + wc_SlhDsaKey_Free(&key); + if (ret != 0) + return ret; + XMEMSET(&key, 0, sizeof(key)); + + /* Boundary: a SLHDSA_MAX_LABEL_LEN-char label (33-byte buffer with the + * trailing NUL) must be accepted. The stored copy fills the whole + * key->label array and is NOT NUL-terminated; callers must use + * key->labelLen. */ + { + char label_max[SLHDSA_MAX_LABEL_LEN + 1]; + int i; + for (i = 0; i < SLHDSA_MAX_LABEL_LEN; i++) + label_max[i] = 'L'; + label_max[SLHDSA_MAX_LABEL_LEN] = '\0'; + ret = wc_SlhDsaKey_Init_label(&key, param, label_max, HEAP_HINT, + INVALID_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (key.labelLen != SLHDSA_MAX_LABEL_LEN) + ret = WC_TEST_RET_ENC_NC; + if ((ret == 0) && + (XMEMCMP(key.label, label_max, SLHDSA_MAX_LABEL_LEN) != 0)) + ret = WC_TEST_RET_ENC_NC; + wc_SlhDsaKey_Free(&key); + } + + return ret; +} +#endif /* WOLF_PRIVATE_KEY_ID && (SHAKE128S|F || SHA2_128S|F) */ + wc_test_ret_t slhdsa_test(void) { int ret = 0; @@ -55940,7 +56101,10 @@ wc_test_ret_t slhdsa_test(void) } #endif - ret = wc_SlhDsaKey_Init(key_vfy, SLHDSA_SHAKE128S, NULL, INVALID_DEVID); + /* Use module-global devId so this verify routes through the cryptocb + * when registered. In VERIFY_ONLY builds this is the only path through + * slhdsa_test() that exercises the cb. */ + ret = wc_SlhDsaKey_Init(key_vfy, SLHDSA_SHAKE128S, NULL, devId); if (ret != 0) { ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); } @@ -56659,6 +56823,22 @@ wc_test_ret_t slhdsa_test(void) #endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */ +#if defined(WOLF_PRIVATE_KEY_ID) && \ + (defined(WOLFSSL_SLHDSA_PARAM_128S) || defined(WOLFSSL_SLHDSA_PARAM_128F) || \ + defined(WOLFSSL_SLHDSA_PARAM_SHA2_128S) || \ + defined(WOLFSSL_SLHDSA_PARAM_SHA2_128F)) + /* Init_id/Init_label/Free are available in VERIFY_ONLY builds, so this + * runs regardless of VERIFY_ONLY. Fall through to cleanup on failure + * (no `goto out;` because `out:` is not defined in 128F-only + + * VERIFY_ONLY builds, and no allocations precede this point that + * would need an early bail-out). */ + if (ret == 0) { + ret = slhdsa_id_label_test(); + if (ret != 0) + wc_test_render_error_message("SLHDSA_ID_LABEL", ret); + } +#endif + #ifdef SLHDSA_TEST_HAVE_ANY_PARAM out: #endif @@ -69168,6 +69348,95 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx) ret = 0; } #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ + #if defined(WOLFSSL_HAVE_SLHDSA) + #ifndef WOLFSSL_SLHDSA_VERIFY_ONLY + if (info->pk.type == WC_PK_TYPE_PQC_SIG_KEYGEN) { + int pqcType = info->pk.pqc_sig_kg.type; + (void)pqcType; + if (pqcType == WC_PQC_SIG_TYPE_SLHDSA) { + SlhDsaKey* sk = (SlhDsaKey*)info->pk.pqc_sig_kg.key; + sk->devId = INVALID_DEVID; + ret = wc_SlhDsaKey_MakeKey(sk, info->pk.pqc_sig_kg.rng); + sk->devId = devIdArg; + myCtx->exampleVar++; + } + } + else if (info->pk.type == WC_PK_TYPE_PQC_SIG_SIGN) { + int pqcType = info->pk.pqc_sign.type; + (void)pqcType; + if (pqcType == WC_PQC_SIG_TYPE_SLHDSA) { + SlhDsaKey* sk = (SlhDsaKey*)info->pk.pqc_sign.key; + enum wc_HashType phType = + (enum wc_HashType)info->pk.pqc_sign.preHashType; + sk->devId = INVALID_DEVID; + if (phType == WC_HASH_TYPE_NONE) { + ret = wc_SlhDsaKey_Sign(sk, + info->pk.pqc_sign.context, + info->pk.pqc_sign.contextLen, + info->pk.pqc_sign.in, + info->pk.pqc_sign.inlen, + info->pk.pqc_sign.out, + info->pk.pqc_sign.outlen, + info->pk.pqc_sign.rng); + } + else { + ret = wc_SlhDsaKey_SignHash(sk, + info->pk.pqc_sign.context, + info->pk.pqc_sign.contextLen, + info->pk.pqc_sign.in, + info->pk.pqc_sign.inlen, + phType, + info->pk.pqc_sign.out, + info->pk.pqc_sign.outlen, + info->pk.pqc_sign.rng); + } + sk->devId = devIdArg; + myCtx->exampleVar++; + } + } + else + #endif /* !WOLFSSL_SLHDSA_VERIFY_ONLY */ + if (info->pk.type == WC_PK_TYPE_PQC_SIG_VERIFY) { + int pqcType = info->pk.pqc_verify.type; + (void)pqcType; + if (pqcType == WC_PQC_SIG_TYPE_SLHDSA) { + SlhDsaKey* sk = (SlhDsaKey*)info->pk.pqc_verify.key; + enum wc_HashType phType = + (enum wc_HashType)info->pk.pqc_verify.preHashType; + int verifyRet = WC_NO_ERR_TRACE(NOT_COMPILED_IN); + sk->devId = INVALID_DEVID; + if (phType == WC_HASH_TYPE_NONE) { + verifyRet = wc_SlhDsaKey_Verify(sk, + info->pk.pqc_verify.context, + info->pk.pqc_verify.contextLen, + info->pk.pqc_verify.msg, + info->pk.pqc_verify.msglen, + info->pk.pqc_verify.sig, + info->pk.pqc_verify.siglen); + } + else { + verifyRet = wc_SlhDsaKey_VerifyHash(sk, + info->pk.pqc_verify.context, + info->pk.pqc_verify.contextLen, + info->pk.pqc_verify.msg, + info->pk.pqc_verify.msglen, + phType, + info->pk.pqc_verify.sig, + info->pk.pqc_verify.siglen); + } + sk->devId = devIdArg; + if (info->pk.pqc_verify.res != NULL) { + *info->pk.pqc_verify.res = (verifyRet == 0) ? 1 : 0; + } + /* SIG_VERIFY_E is a validity signal, not a crypto error, so + * translate it back to success for the dispatcher. */ + if (verifyRet == WC_NO_ERR_TRACE(SIG_VERIFY_E)) + verifyRet = 0; + ret = verifyRet; + myCtx->exampleVar++; + } + } + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef WOLFSSL_HAVE_MLKEM if (info->pk.type == WC_PK_TYPE_PQC_KEM_KEYGEN) { if ((info->pk.pqc_kem_kg.type == WC_PQC_KEM_TYPE_KYBER) && @@ -69938,15 +70207,25 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx) break; } #endif -#ifdef HAVE_DILITHIUM +#if defined(HAVE_DILITHIUM) || defined(WOLFSSL_HAVE_SLHDSA) case WC_PK_TYPE_PQC_SIG_KEYGEN: { + #ifdef HAVE_DILITHIUM if (info->free.subType == WC_PQC_SIG_TYPE_DILITHIUM) { dilithium_key* dil = (dilithium_key*)info->free.obj; dil->devId = INVALID_DEVID; wc_dilithium_free(dil); ret = 0; } + #endif + #ifdef WOLFSSL_HAVE_SLHDSA + if (info->free.subType == WC_PQC_SIG_TYPE_SLHDSA) { + SlhDsaKey* slh = (SlhDsaKey*)info->free.obj; + slh->devId = INVALID_DEVID; + wc_SlhDsaKey_Free(slh); + ret = 0; + } + #endif break; } #endif @@ -70601,6 +70880,27 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) if (ret == 0) ret = dilithium_test(); #endif +#ifdef WOLFSSL_HAVE_SLHDSA + if (ret == 0) { + /* Reuse exampleVar as a hit counter for the SLH-DSA cb branches. + * baseline holds the value seen by every other cb at this point; + * we restore it after, so subsequent tests are unaffected. + * Confirms the SLH-DSA cb path was actually exercised; a silent + * SW fallback would otherwise mask a regression in the dispatch. + * + * Only enforce when slhdsa_test() actually runs a cb-routed op: + * !VERIFY_ONLY runs slhdsa_test_param (uses devId), or + * PARAM_128S enables the in-tree KAT verify (also uses devId). */ + int baseline = myCtx.exampleVar; + ret = slhdsa_test(); + #if !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) || \ + defined(WOLFSSL_SLHDSA_PARAM_128S) + if ((ret == 0) && (myCtx.exampleVar == baseline)) + ret = WC_TEST_RET_ENC_NC; + #endif + myCtx.exampleVar = baseline; + } +#endif #if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) if (ret == 0) ret = xmss_test(); diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index eee9e23182..c50fb823f4 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -86,6 +86,9 @@ #if defined(HAVE_FALCON) #include #endif +#if defined(WOLFSSL_HAVE_SLHDSA) + #include +#endif #if defined(WOLFSSL_HAVE_LMS) #include #endif @@ -312,7 +315,8 @@ typedef struct wc_CryptoInfo { int type; /* enum wc_PqcKemType */ } pqc_decaps; #endif - #if defined(HAVE_FALCON) || defined(HAVE_DILITHIUM) + #if defined(HAVE_FALCON) || defined(HAVE_DILITHIUM) || \ + defined(WOLFSSL_HAVE_SLHDSA) struct { WC_RNG* rng; int size; @@ -775,7 +779,8 @@ WOLFSSL_LOCAL int wc_CryptoCb_PqcDecapsulate(const byte* ciphertext, int type, void* key); #endif /* WOLFSSL_HAVE_MLKEM */ -#if defined(HAVE_FALCON) || defined(HAVE_DILITHIUM) +#if defined(HAVE_FALCON) || defined(HAVE_DILITHIUM) || \ + defined(WOLFSSL_HAVE_SLHDSA) WOLFSSL_LOCAL int wc_CryptoCb_PqcSigGetDevId(int type, void* key); WOLFSSL_LOCAL int wc_CryptoCb_MakePqcSignatureKey(WC_RNG* rng, int type, @@ -791,7 +796,7 @@ WOLFSSL_LOCAL int wc_CryptoCb_PqcVerify(const byte* sig, word32 siglen, WOLFSSL_LOCAL int wc_CryptoCb_PqcSignatureCheckPrivKey(void* key, int type, const byte* pubKey, word32 pubKeySz); -#endif /* HAVE_FALCON || HAVE_DILITHIUM */ +#endif /* HAVE_FALCON || HAVE_DILITHIUM || WOLFSSL_HAVE_SLHDSA */ #ifndef NO_AES #ifdef HAVE_AESGCM diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index da5a680dbc..1b787ccf73 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -1559,7 +1559,8 @@ enum wc_PkType { #undef _WC_PK_TYPE_MAX #define _WC_PK_TYPE_MAX WC_PK_TYPE_PQC_KEM_DECAPS #endif -#if defined(HAVE_DILITHIUM) || defined(HAVE_FALCON) +#if defined(HAVE_DILITHIUM) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) WC_PK_TYPE_PQC_SIG_KEYGEN = 21, WC_PK_TYPE_PQC_SIG_SIGN = 22, WC_PK_TYPE_PQC_SIG_VERIFY = 23, @@ -1597,7 +1598,8 @@ enum wc_PkType { }; #endif -#if defined(HAVE_DILITHIUM) || defined(HAVE_FALCON) +#if defined(HAVE_DILITHIUM) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) /* Post quantum signature algorithms */ enum wc_PqcSignatureType { WC_PQC_SIG_TYPE_NONE = 0, @@ -1611,6 +1613,11 @@ enum wc_PkType { WC_PQC_SIG_TYPE_FALCON = 2, #undef _WC_PQC_SIG_TYPE_MAX #define _WC_PQC_SIG_TYPE_MAX WC_PQC_SIG_TYPE_FALCON + #endif + #if defined(WOLFSSL_HAVE_SLHDSA) + WC_PQC_SIG_TYPE_SLHDSA = 3, + #undef _WC_PQC_SIG_TYPE_MAX + #define _WC_PQC_SIG_TYPE_MAX WC_PQC_SIG_TYPE_SLHDSA #endif WC_PQC_SIG_TYPE_MAX = _WC_PQC_SIG_TYPE_MAX }; diff --git a/wolfssl/wolfcrypt/wc_slhdsa.h b/wolfssl/wolfcrypt/wc_slhdsa.h index 323731334b..5e3e9eb11b 100644 --- a/wolfssl/wolfcrypt/wc_slhdsa.h +++ b/wolfssl/wolfcrypt/wc_slhdsa.h @@ -24,6 +24,10 @@ #include +#ifdef WOLF_CRYPTO_CB + #include +#endif + #if FIPS_VERSION3_GE(7,0,0) #include #endif @@ -591,6 +595,11 @@ typedef struct SlhDsaParameters { #define WC_SLHDSA_FLAG_BOTH_KEYS (WC_SLHDSA_FLAG_PRIVATE | \ WC_SLHDSA_FLAG_PUBLIC) +#ifdef WOLF_PRIVATE_KEY_ID +#define SLHDSA_MAX_ID_LEN 32 +#define SLHDSA_MAX_LABEL_LEN 32 +#endif + /* SLH-DSA key data and state. */ typedef struct SlhDsaKey { /* Parameters. */ @@ -600,9 +609,17 @@ typedef struct SlhDsaKey { /* Dynamic memory hint. */ void* heap; #ifdef WOLF_CRYPTO_CB + /* Device context (opaque, owned by the registered callback). */ + void* devCtx; /* Device Identifier. */ int devId; #endif +#ifdef WOLF_PRIVATE_KEY_ID + byte id[SLHDSA_MAX_ID_LEN]; + int idLen; + char label[SLHDSA_MAX_LABEL_LEN]; + int labelLen; +#endif /* sk_seed | sk_prf | pk_seed, pk_root */ byte sk[32 * 4]; @@ -641,6 +658,12 @@ typedef struct SlhDsaKey { WOLFSSL_API int wc_SlhDsaKey_Init(SlhDsaKey* key, enum SlhDsaParam param, void* heap, int devId); +#ifdef WOLF_PRIVATE_KEY_ID +WOLFSSL_API int wc_SlhDsaKey_Init_id(SlhDsaKey* key, enum SlhDsaParam param, + const unsigned char* id, int len, void* heap, int devId); +WOLFSSL_API int wc_SlhDsaKey_Init_label(SlhDsaKey* key, enum SlhDsaParam param, + const char* label, void* heap, int devId); +#endif WOLFSSL_API void wc_SlhDsaKey_Free(SlhDsaKey* key); #ifndef WOLFSSL_SLHDSA_VERIFY_ONLY