From adcb417af79f280aef2b294465932d647b799031 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 14 Aug 2026 12:53:58 -0700 Subject: [PATCH] Add crypto callback mode for SLH-DSA --- .github/workflows/build-and-run-examples.yml | 27 + examples/demo/client/wh_demo_client_all.c | 7 + examples/demo/client/wh_demo_client_crypto.c | 91 + examples/demo/client/wh_demo_client_crypto.h | 2 + examples/posix/wh_posix_client/Makefile | 7 + .../posix/wh_posix_client/user_settings.h | 17 + .../posix/wh_posix_server/user_settings.h | 7 + src/wh_client_crypto.c | 1355 +++++++++ src/wh_client_cryptocb.c | 162 +- src/wh_crypto.c | 61 + src/wh_message_crypto.c | 280 ++ src/wh_server_crypto.c | 2676 ++++++++++++----- src/wh_server_keystore.c | 41 + .../client-server/wh_test_crypto_slhdsa.c | 1002 ++++++ .../misc/wh_test_check_struct_padding.c | 14 + test-refactor/wh_test_list.c | 2 + test/config/user_settings.h | 6 + test/wh_test_check_struct_padding.c | 14 + wolfhsm/wh_client_crypto.h | 380 +++ wolfhsm/wh_common.h | 1 + wolfhsm/wh_crypto.h | 15 + wolfhsm/wh_message_crypto.h | 226 ++ wolfhsm/wh_server_crypto.h | 10 + wolfhsm/wh_settings.h | 8 + 24 files changed, 5632 insertions(+), 779 deletions(-) create mode 100644 test-refactor/client-server/wh_test_crypto_slhdsa.c diff --git a/.github/workflows/build-and-run-examples.yml b/.github/workflows/build-and-run-examples.yml index 0ba90e663..d047ea8bc 100644 --- a/.github/workflows/build-and-run-examples.yml +++ b/.github/workflows/build-and-run-examples.yml @@ -91,3 +91,30 @@ jobs: done done done + + # The client and the server are separate binaries with separate + # user_settings.h, so the client can be built with no software SLH-DSA at + # all while the server keeps its own. That is the only way to prove the + # callback-only path really reaches the server: with the software stripped + # the client has nothing to fall back to, so a passing demo means every + # SLH-DSA operation was served over the wire. Only one transport needs to + # run it; the ONLY macro is orthogonal to the transport. + - name: Build and run the callback-only SLH-DSA client + if: ${{ matrix.transport == 'tcp' }} + run: | + WS="$(pwd)" + SERVER_DIR="$WS/examples/posix/wh_posix_server" + CLIENT_DIR="$WS/examples/posix/wh_posix_client" + make -C "$SERVER_DIR" clean + make -C "$CLIENT_DIR" clean + DEMO_KEK=1 make -C "$SERVER_DIR" -j WOLFSSL_DIR=../../../wolfssl + SLHDSA_CB_ONLY=1 make -C "$CLIENT_DIR" -j WOLFSSL_DIR=../../../wolfssl + rm -f "$SERVER_DIR"/*.bin + cd "$SERVER_DIR" + ./Build/wh_posix_server.elf --type tcp & + SERVER_PID=$! + sleep 1 + cd "$CLIENT_DIR" + ./Build/wh_posix_client.elf --type tcp --test + kill $SERVER_PID 2>/dev/null || true + wait $SERVER_PID 2>/dev/null || true diff --git a/examples/demo/client/wh_demo_client_all.c b/examples/demo/client/wh_demo_client_all.c index 2b1169dde..225e203c9 100644 --- a/examples/demo/client/wh_demo_client_all.c +++ b/examples/demo/client/wh_demo_client_all.c @@ -201,5 +201,12 @@ int wh_DemoClient_All(whClientContext* clientContext) #endif /* WOLFSSL_CMAC */ +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) + rc = wh_DemoClient_CryptoSlhDsa(clientContext); + if (rc != 0) { + return rc; + } +#endif /* WOLFSSL_HAVE_SLHDSA && !WOLFSSL_SLHDSA_VERIFY_ONLY */ + return rc; } diff --git a/examples/demo/client/wh_demo_client_crypto.c b/examples/demo/client/wh_demo_client_crypto.c index a7c3dc91d..3cc0c6b25 100644 --- a/examples/demo/client/wh_demo_client_crypto.c +++ b/examples/demo/client/wh_demo_client_crypto.c @@ -44,6 +44,10 @@ #include "wolfssl/wolfcrypt/kdf.h" #endif +#ifdef WOLFSSL_HAVE_SLHDSA +#include "wolfssl/wolfcrypt/wc_slhdsa.h" +#endif + #include "wh_demo_client_crypto.h" #if !defined(NO_RSA) @@ -1692,4 +1696,91 @@ int wh_DemoClient_CryptoCmacKdfCacheInputs(whClientContext* clientContext) #endif /* HAVE_CMAC_KDF && WOLFSSL_CMAC */ +#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) + +/* Generate an SLH-DSA key that stays on the server and use it purely by key + * id. Only the smallest parameter set produces a signature that fits the comm + * buffer, so that is what this demo asks for. */ +int wh_DemoClient_CryptoSlhDsa(whClientContext* clientContext) +{ + int ret; + int devId = WH_CLIENT_DEVID(clientContext); + whKeyId keyId = WH_KEYID_ERASED; + SlhDsaKey pub[1]; + SlhDsaKey handle[1]; + uint8_t label[] = "slhdsa-demo"; + byte message[] = "wolfHSM SLH-DSA demo message"; + byte signature[WC_SLHDSA_SHAKE128S_SIG_LEN]; + word32 sigLen = sizeof(signature); + + ret = wc_SlhDsaKey_Init(pub, SLHDSA_SHAKE128S, NULL, devId); + if (ret != 0) { + WOLFHSM_CFG_PRINTF("Failed to wc_SlhDsaKey_Init %d\n", ret); + return ret; + } + + ret = wc_SlhDsaKey_Init(handle, SLHDSA_SHAKE128S, NULL, devId); + if (ret != 0) { + WOLFHSM_CFG_PRINTF("Failed to wc_SlhDsaKey_Init %d\n", ret); + wc_SlhDsaKey_Free(pub); + return ret; + } + + /* The private key is generated on and never leaves the HSM; only the + * public key comes back. */ + ret = wh_Client_SlhDsaMakeCacheKeyAndExportPublic( + clientContext, SLHDSA_SHAKE128S, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, sizeof(label), + label, pub); + if (ret != 0) { + WOLFHSM_CFG_PRINTF("Failed to generate SLH-DSA key %d\n", ret); + goto exit; + } + + /* handle holds no key material at all, just the server key id */ + ret = wh_Client_SlhDsaSetKeyId(handle, keyId); + if (ret != 0) { + WOLFHSM_CFG_PRINTF("Failed to wh_Client_SlhDsaSetKeyId %d\n", ret); + goto exit; + } + + ret = wc_SlhDsaKey_SignDeterministic(handle, NULL, 0, message, + sizeof(message), signature, &sigLen); + if (ret != 0) { + WOLFHSM_CFG_PRINTF("Failed to wc_SlhDsaKey_SignDeterministic %d\n", + ret); + goto exit; + } + + ret = wc_SlhDsaKey_Verify(pub, NULL, 0, message, sizeof(message), + signature, sigLen); + if (ret != 0) { + WOLFHSM_CFG_PRINTF("Failed to wc_SlhDsaKey_Verify %d\n", ret); + goto exit; + } + + /* A tampered signature must not verify */ + signature[0] ^= 0xFF; + if (wc_SlhDsaKey_Verify(pub, NULL, 0, message, sizeof(message), signature, + sigLen) == 0) { + WOLFHSM_CFG_PRINTF("SLH-DSA verified a tampered signature\n"); + ret = -1; + goto exit; + } + + WOLFHSM_CFG_PRINTF("SLH-DSA sign/verify with a server-resident key: " + "SUCCESS\n"); + ret = 0; + +exit: + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(clientContext, keyId); + } + wc_SlhDsaKey_Free(handle); + wc_SlhDsaKey_Free(pub); + return ret; +} + +#endif /* WOLFSSL_HAVE_SLHDSA && !WOLFSSL_SLHDSA_VERIFY_ONLY */ + #endif /* WOLFHSM_CFG_NO_CRYPTO */ diff --git a/examples/demo/client/wh_demo_client_crypto.h b/examples/demo/client/wh_demo_client_crypto.h index 72fbb187d..f7c14417c 100644 --- a/examples/demo/client/wh_demo_client_crypto.h +++ b/examples/demo/client/wh_demo_client_crypto.h @@ -30,4 +30,6 @@ int wh_DemoClient_CryptoCmacKdfExport(whClientContext* clientContext); int wh_DemoClient_CryptoCmacKdfCache(whClientContext* clientContext); int wh_DemoClient_CryptoCmacKdfCacheInputs(whClientContext* clientContext); +int wh_DemoClient_CryptoSlhDsa(whClientContext* clientContext); + #endif /* !DEMO_CLIENT_CRYPTO_H_ */ diff --git a/examples/posix/wh_posix_client/Makefile b/examples/posix/wh_posix_client/Makefile index dbd1c39ed..b7d10552f 100644 --- a/examples/posix/wh_posix_client/Makefile +++ b/examples/posix/wh_posix_client/Makefile @@ -143,6 +143,13 @@ ifeq ($(AUTH),1) DEF += -DWOLFHSM_CFG_ENABLE_AUTHENTICATION endif +# Strip the software SLH-DSA from the client so every operation has to reach +# the server. The server is a separate binary with its own settings, so it +# keeps its software implementation. +ifeq ($(SLHDSA_CB_ONLY),1) +DEF += -DWH_CFG_SLHDSA_CB_ONLY +endif + else DEF += -DWOLFHSM_CFG_NO_CRYPTO endif diff --git a/examples/posix/wh_posix_client/user_settings.h b/examples/posix/wh_posix_client/user_settings.h index d25542bc9..ce8b2baf4 100644 --- a/examples/posix/wh_posix_client/user_settings.h +++ b/examples/posix/wh_posix_client/user_settings.h @@ -44,6 +44,23 @@ #define WOLFSSL_CMAC #define HAVE_HKDF +/* SLH-DSA. Only the smallest parameter set is built: its 7856-byte signature + * is the only one that fits WOLFHSM_CFG_COMM_DATA_LEN. */ +#define WOLFSSL_HAVE_SLHDSA +#define WOLFSSL_SHA3 +#define WOLFSSL_SHAKE128 +#define WOLFSSL_SHAKE256 +#define WOLFSSL_SLHDSA_PARAM_NO_128F +#define WOLFSSL_SLHDSA_PARAM_NO_192 +#define WOLFSSL_SLHDSA_PARAM_NO_256 + +/* Build the client with no software SLH-DSA at all, so every operation must + * reach the server or fail closed. Set -DWH_CFG_SLHDSA_CB_ONLY to select it; + * the server keeps its software implementation either way. */ +#ifdef WH_CFG_SLHDSA_CB_ONLY +#define WOLF_CRYPTO_CB_ONLY_SLHDSA +#endif + /* wolfCrypt benchmark settings */ #define NO_MAIN_DRIVER #define BENCH_EMBEDDED diff --git a/examples/posix/wh_posix_server/user_settings.h b/examples/posix/wh_posix_server/user_settings.h index efb06ce4b..4c4708075 100644 --- a/examples/posix/wh_posix_server/user_settings.h +++ b/examples/posix/wh_posix_server/user_settings.h @@ -150,6 +150,13 @@ extern "C" { #define WOLFSSL_MLDSA_NO_MAKE_KEY #endif +/* SLH-DSA Options. Only the smallest parameter set is built: its 7856-byte + * signature is the only one that fits WOLFHSM_CFG_COMM_DATA_LEN. */ +#define WOLFSSL_HAVE_SLHDSA +#define WOLFSSL_SLHDSA_PARAM_NO_128F +#define WOLFSSL_SLHDSA_PARAM_NO_192 +#define WOLFSSL_SLHDSA_PARAM_NO_256 + /* ML-KEM Options */ #define WOLFSSL_HAVE_MLKEM diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index f7ecc05da..b14c2958a 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -11152,6 +11152,1361 @@ int wh_Client_MlDsaCheckPrivKeyDma(whClientContext* ctx, wc_MlDsaKey* key, #endif /* WOLFHSM_CFG_DMA */ #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + +/* Parameter set the caller's key was initialized with. The server needs it to + * rebuild the key, and it is not recoverable from the key id alone. */ +static int _SlhDsaKeyParam(const SlhDsaKey* key) +{ + if ((key == NULL) || (key->params == NULL)) { + return WC_SLHDSA_DEFAULT_PARAM; + } + return (int)key->params->param; +} + +int wh_Client_SlhDsaSetKeyId(SlhDsaKey* key, whKeyId keyId) +{ + if (key == NULL) { + return WH_ERROR_BADARGS; + } + + key->devCtx = WH_KEYID_TO_DEVCTX(keyId); + + return WH_ERROR_OK; +} + +int wh_Client_SlhDsaGetKeyId(SlhDsaKey* key, whKeyId* outId) +{ + if ((key == NULL) || (outId == NULL)) { + return WH_ERROR_BADARGS; + } + + *outId = WH_DEVCTX_TO_KEYID(key->devCtx); + + return WH_ERROR_OK; +} + +int wh_Client_SlhDsaImportKey(whClientContext* ctx, SlhDsaKey* key, + whKeyId* inout_keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + int ret = WH_ERROR_OK; + whKeyId key_id = WH_KEYID_ERASED; + byte buffer[WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE]; + uint16_t buffer_len = 0; + + if ((ctx == NULL) || (key == NULL) || + ((label_len != 0) && (label == NULL))) { + return WH_ERROR_BADARGS; + } + + if (inout_keyId != NULL) { + key_id = *inout_keyId; + } + + ret = wh_Crypto_SlhDsaSerializeKeyDer(key, sizeof(buffer), buffer, + &buffer_len); + if (ret == WH_ERROR_OK) { + /* Cache the key and get the keyID */ + ret = wh_Client_KeyCache(ctx, flags, label, label_len, buffer, + buffer_len, &key_id); + if ((ret == WH_ERROR_OK) && (inout_keyId != NULL)) { + *inout_keyId = key_id; + } + } + + WH_DEBUG_CLIENT_VERBOSE("label:%.*s ret:%d keyid:%u\n", label_len, label, + ret, key_id); + return ret; +} + +int wh_Client_SlhDsaExportKey(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label) +{ + int ret = WH_ERROR_OK; + byte buffer[WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE]; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = + wh_Client_KeyExport(ctx, keyId, label, label_len, buffer, &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaDeserializeKeyDer(buffer, buffer_len, key); + } + + WH_DEBUG_CLIENT_VERBOSE("keyid:%x key:%p ret:%d label:%.*s\n", keyId, key, + ret, (int)label_len, label); + return ret; +} + +int wh_Client_SlhDsaExportPublicKey(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label) +{ + int ret; + byte buffer[WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE] = {0}; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_KeyExportPublic(ctx, keyId, WH_KEY_ALGO_SLHDSA, label, + label_len, buffer, &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaDeserializeKeyDer(buffer, buffer_len, key); + } + return ret; +} + +static int _SlhDsaMakeKey(whClientContext* ctx, int param, const byte* seed, + word32 seedSz, whKeyId* inout_key_id, + whNvmFlags flags, uint16_t label_len, + const uint8_t* label, SlhDsaKey* key) +{ + int ret = WH_ERROR_OK; + whKeyId key_id = WH_KEYID_ERASED; + uint8_t* dataPtr = NULL; + whMessageCrypto_SlhDsaKeyGenRequest* req = NULL; + whMessageCrypto_SlhDsaKeyGenResponse* res = NULL; + uint16_t pkType; + + if ((ctx == NULL) || ((seed == NULL) && (seedSz > 0))) { + return WH_ERROR_BADARGS; + } + + /* Get data pointer from the context to use as request/response storage */ + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + /* A seeded generation is a distinct operation from a random one */ + pkType = (seed != NULL) ? WC_PK_TYPE_PQC_SIG_KEYGEN_SEEDED + : WC_PK_TYPE_PQC_SIG_KEYGEN; + + /* Setup generic header and get pointer to request data */ + req = (whMessageCrypto_SlhDsaKeyGenRequest*)_createCryptoRequestWithSubtype( + dataPtr, pkType, WC_PQC_SIG_TYPE_SLHDSA, ctx->cryptoAffinity); + + /* Use the supplied key id if provided */ + if (inout_key_id != NULL) { + key_id = *inout_key_id; + } + + { + /* Request Message */ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WC_ALGO_TYPE_PK; + + uint32_t total_len = sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + seedSz; + + if (total_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + uint16_t req_len = (uint16_t)total_len; + + memset(req, 0, sizeof(*req)); + req->param = param; + req->sz = 0; + req->flags = flags; + req->keyId = key_id; + req->seedSz = seedSz; + if (seedSz > 0) { + memcpy((uint8_t*)(req + 1), seed, seedSz); + } + if ((label != NULL) && (label_len > 0)) { + if (label_len > WH_NVM_LABEL_LEN) { + label_len = WH_NVM_LABEL_LEN; + } + memcpy(req->label, label, label_len); + } + + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + if (ret == WH_ERROR_OK) { + uint16_t res_len = 0; + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + + if (ret == WH_ERROR_OK) { + /* Get response structure pointer, validates generic header + * rc */ + ret = _getCryptoResponse(dataPtr, pkType, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some + * scenarios */ + if (ret >= 0) { + /* Key is cached on server or is ephemeral */ + key_id = (whKeyId)(res->keyId); + + /* Update output variable if requested */ + if (inout_key_id != NULL) { + *inout_key_id = key_id; + } + + /* Update the context if provided */ + if (key != NULL) { + uint16_t der_size = (uint16_t)(res->len); + const size_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + /* Set the key_id. ERASED for EPHEMERAL, cached id + * otherwise. */ + wh_Client_SlhDsaSetKeyId(key, key_id); + + /* Response carries the exported key (EPHEMERAL) or + * the public key (cached keygen). An empty body + * means the caller requested key material the + * server did not return; also reject a length that + * does not fit the received frame before + * deserializing. */ + if (der_size == 0) { + ret = WH_ERROR_ABORTED; + } + else if ((res_len < hdr_sz) || + (res->len > (res_len - hdr_sz))) { + ret = WH_ERROR_ABORTED; + } + else { + uint8_t* key_der = (uint8_t*)(res + 1); + ret = wh_Crypto_SlhDsaDeserializeKeyDer( + key_der, der_size, key); + } + } + } + } + } + } + else { + ret = WH_ERROR_BADARGS; + } + } + return ret; +} + +int wh_Client_SlhDsaMakeCacheKey(whClientContext* ctx, int param, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + if (inout_key_id == NULL) { + return WH_ERROR_BADARGS; + } + + return _SlhDsaMakeKey(ctx, param, NULL, 0, inout_key_id, flags, label_len, + label, NULL); +} + +int wh_Client_SlhDsaMakeCacheKeyAndExportPublic( + whClientContext* ctx, int param, whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, SlhDsaKey* pub) +{ + int ret; + whKeyId in_keyId; + + if ((ctx == NULL) || (inout_key_id == NULL) || (pub == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Ephemeral keygen belongs to the export path, not the cache path. */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + in_keyId = *inout_key_id; + ret = _SlhDsaMakeKey(ctx, param, NULL, 0, inout_key_id, flags, label_len, + label, pub); + if (ret >= 0) { + /* Stamp the cached keyId and the client's HSM devId so pub is + * immediately usable as a handle to the cached private key. The keyId + * is set here as well because the public-key deserialize re-inits + * pub and clears it. */ + wh_Client_SlhDsaSetKeyId(pub, *inout_key_id); + pub->devId = WH_CLIENT_DEVID(ctx); + } + else if (!WH_KEYID_ISERASED(*inout_key_id) && + (WH_KEYID_ISERASED(in_keyId) || (ret == WH_ERROR_ABORTED))) { + /* The server committed a key but the best-effort export returned no + * public key. Roll back so the operation is atomic and no cache slot + * is orphaned. */ + (void)wh_Client_KeyEvict(ctx, *inout_key_id); + *inout_key_id = WH_KEYID_ERASED; + } + return ret; +} + +int wh_Client_SlhDsaMakeExportKey(whClientContext* ctx, int param, + SlhDsaKey* key) +{ + if (key == NULL) { + return WH_ERROR_BADARGS; + } + + return _SlhDsaMakeKey(ctx, param, NULL, 0, NULL, WH_NVM_FLAGS_EPHEMERAL, 0, + NULL, key); +} + +int wh_Client_SlhDsaMakeExportKeyFromSeed(whClientContext* ctx, int param, + const byte* seed, word32 seedSz, + SlhDsaKey* key) +{ + if ((key == NULL) || (seed == NULL) || (seedSz == 0)) { + return WH_ERROR_BADARGS; + } + + return _SlhDsaMakeKey(ctx, param, seed, seedSz, NULL, + WH_NVM_FLAGS_EPHEMERAL, 0, NULL, key); +} + +int wh_Client_SlhDsaMakeCacheKeyFromSeed(whClientContext* ctx, int param, + const byte* seed, word32 seedSz, + whKeyId* inout_key_id, + whNvmFlags flags, uint16_t label_len, + uint8_t* label) +{ + if ((inout_key_id == NULL) || (seed == NULL) || (seedSz == 0)) { + return WH_ERROR_BADARGS; + } + + return _SlhDsaMakeKey(ctx, param, seed, seedSz, inout_key_id, flags, + label_len, label, NULL); +} + +int wh_Client_SlhDsaSign(whClientContext* ctx, const byte* in, word32 in_len, + byte* out, word32* inout_len, SlhDsaKey* key, + const byte* context, byte contextLen, + word32 preHashType, const byte* addRnd, byte addRndSz, + int randomized, int isMPrime) +{ + int ret = WH_ERROR_OK; + whMessageCrypto_SlhDsaSignRequest* req = NULL; + whMessageCrypto_SlhDsaSignResponse* res = NULL; + uint8_t* dataPtr = NULL; + uint16_t pkType; + + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + if ((ctx == NULL) || (key == NULL) || ((in == NULL) && (in_len > 0)) || + (out == NULL) || (inout_len == NULL) || + ((addRnd == NULL) && (addRndSz > 0))) { + return WH_ERROR_BADARGS; + } + + pkType = (isMPrime != 0) ? WC_PK_TYPE_PQC_SIG_SIGN_MSG + : WC_PK_TYPE_PQC_SIG_SIGN; + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + + /* Import key if necessary */ + if (WH_KEYID_ISERASED(key_id)) { + /* Must import the key to the server and evict it afterwards */ + uint8_t keyLabel[] = "TempSlhDsaSign"; + whNvmFlags flags = WH_NVM_FLAGS_USAGE_SIGN; + + ret = wh_Client_SlhDsaImportKey(ctx, key, &key_id, flags, + sizeof(keyLabel), keyLabel); + if (ret == WH_ERROR_OK) { + evict = 1; + } + } + + if (ret == WH_ERROR_OK) { + /* Request Message */ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WC_ALGO_TYPE_PK; + + uint32_t total_len = sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + in_len + contextLen + addRndSz; + uint32_t options = 0; + + /* Get data pointer from the context to use as request/response storage + */ + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + /* Setup generic header and get pointer to request data */ + req = + (whMessageCrypto_SlhDsaSignRequest*)_createCryptoRequestWithSubtype( + dataPtr, pkType, WC_PQC_SIG_TYPE_SLHDSA, ctx->cryptoAffinity); + + if (total_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + uint16_t req_len = (uint16_t)total_len; + uint8_t* req_data = (uint8_t*)(req + 1); + if (evict != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_EVICT; + } + if (isMPrime != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_MPRIME; + } + if (randomized != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_RANDOMIZED; + } + + memset(req, 0, sizeof(*req)); + req->options = options; + req->param = _SlhDsaKeyParam(key); + req->keyId = key_id; + req->sz = in_len; + req->contextSz = contextLen; + req->preHashType = preHashType; + req->addRndSz = addRndSz; + if ((in != NULL) && (in_len > 0)) { + memcpy(req_data, in, in_len); + } + if ((context != NULL) && (contextLen > 0)) { + memcpy(req_data + in_len, context, contextLen); + } + if (addRndSz > 0) { + memcpy(req_data + in_len + contextLen, addRnd, addRndSz); + } + + /* Send Request */ + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + if (ret == WH_ERROR_OK) { + /* Server will evict at this point. Reset evict */ + uint16_t res_len = 0; + evict = 0; + + /* Recv Response */ + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + + if (ret == WH_ERROR_OK) { + /* Get response structure pointer, validates generic header + * rc */ + ret = _getCryptoResponse(dataPtr, pkType, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some + * scenarios */ + if (ret >= 0) { + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + if ((res_len < hdr_sz) || + (res->sz > (res_len - hdr_sz))) { + ret = WH_ERROR_ABORTED; + } + else { + uint8_t* res_sig = (uint8_t*)(res + 1); + if (res->sz > *inout_len) { + ret = WH_ERROR_BUFFER_SIZE; + } + else { + memcpy(out, res_sig, res->sz); + } + *inout_len = res->sz; + } + } + } + } + } + else { + /* Request length is too long */ + ret = WH_ERROR_BADARGS; + } + } + /* Evict the key manually on error */ + if (evict != 0) { + (void)wh_Client_KeyEvict(ctx, key_id); + } + WH_DEBUG_CLIENT_VERBOSE("ret:%d\n", ret); + return ret; +} + +int wh_Client_SlhDsaVerify(whClientContext* ctx, const byte* sig, + word32 sig_len, const byte* msg, word32 msg_len, + int* out_res, SlhDsaKey* key, const byte* context, + byte contextLen, word32 preHashType, int isMPrime) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr = NULL; + whMessageCrypto_SlhDsaVerifyRequest* req = NULL; + whMessageCrypto_SlhDsaVerifyResponse* res = NULL; + uint16_t pkType; + + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + if ((ctx == NULL) || (key == NULL) || ((sig == NULL) && (sig_len > 0)) || + (out_res == NULL) || ((msg == NULL) && (msg_len > 0))) { + return WH_ERROR_BADARGS; + } + + pkType = (isMPrime != 0) ? WC_PK_TYPE_PQC_SIG_VERIFY_MSG + : WC_PK_TYPE_PQC_SIG_VERIFY; + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + + /* Import key if necessary */ + if (WH_KEYID_ISERASED(key_id)) { + /* Must import the key to the server and evict it afterwards */ + uint8_t keyLabel[] = "TempSlhDsaVerify"; + whNvmFlags flags = WH_NVM_FLAGS_USAGE_VERIFY; + + ret = wh_Client_SlhDsaImportKey(ctx, key, &key_id, flags, + sizeof(keyLabel), keyLabel); + if (ret == WH_ERROR_OK) { + evict = 1; + } + } + + if (ret == WH_ERROR_OK) { + /* Request Message */ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WC_ALGO_TYPE_PK; + uint32_t options = 0; + + uint32_t total_len = sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + sig_len + msg_len + contextLen; + + /* Get data pointer from the context to use as request/response storage + */ + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + /* Setup generic header and get pointer to request data */ + req = (whMessageCrypto_SlhDsaVerifyRequest*) + _createCryptoRequestWithSubtype(dataPtr, pkType, + WC_PQC_SIG_TYPE_SLHDSA, + ctx->cryptoAffinity); + + if (total_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + uint16_t req_len = (uint16_t)total_len; + uint8_t* req_sig = (uint8_t*)(req + 1); + uint8_t* req_hash = req_sig + sig_len; + + /* Set request packet members */ + if (evict != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_EVICT; + } + if (isMPrime != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_MPRIME; + } + + memset(req, 0, sizeof(*req)); + req->options = options; + req->param = _SlhDsaKeyParam(key); + req->keyId = key_id; + req->sigSz = sig_len; + if ((sig != NULL) && (sig_len > 0)) { + memcpy(req_sig, sig, sig_len); + } + req->hashSz = msg_len; + if ((msg != NULL) && (msg_len > 0)) { + memcpy(req_hash, msg, msg_len); + } + req->contextSz = contextLen; + req->preHashType = preHashType; + if ((context != NULL) && (contextLen > 0)) { + memcpy(req_hash + msg_len, context, contextLen); + } + + /* write request */ + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + + if (ret == WH_ERROR_OK) { + /* Server will evict at this point. Reset evict */ + uint16_t res_len = 0; + evict = 0; + + /* Recv Response */ + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + if (ret == WH_ERROR_OK) { + /* Get response structure pointer, validates generic header + * rc */ + ret = _getCryptoResponse(dataPtr, pkType, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some + * scenarios */ + if (ret >= 0) { + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + /* Note whMessageCrypto_SlhDsaVerifyResponse has no + * size field */ + if (res_len < hdr_sz) { + ret = WH_ERROR_ABORTED; + } + else { + *out_res = res->res; + } + } + } + } + } + else { + /* Request length is too long */ + ret = WH_ERROR_BADARGS; + } + } + /* Evict the key manually on error */ + if (evict != 0) { + (void)wh_Client_KeyEvict(ctx, key_id); + } + WH_DEBUG_CLIENT_VERBOSE("ret:%d\n", ret); + return ret; +} + +int wh_Client_SlhDsaCheckPrivKey(whClientContext* ctx, SlhDsaKey* key, + const byte* pubKey, word32 pubKeySz) +{ + int ret = WH_ERROR_OK; + uint8_t* dataPtr = NULL; + whMessageCrypto_SlhDsaCheckPrivKeyRequest* req = NULL; + whMessageCrypto_SlhDsaCheckPrivKeyResponse* res = NULL; + + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + /* A NULL public key asks the server to check its own copy of the private + * key for consistency, with nothing to compare it against. */ + if ((ctx == NULL) || (key == NULL) || + ((pubKey == NULL) != (pubKeySz == 0))) { + return WH_ERROR_BADARGS; + } + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + + /* Import key if necessary */ + if (WH_KEYID_ISERASED(key_id)) { + uint8_t keyLabel[] = "TempSlhDsaCheck"; + whNvmFlags flags = WH_NVM_FLAGS_USAGE_SIGN; + + ret = wh_Client_SlhDsaImportKey(ctx, key, &key_id, flags, + sizeof(keyLabel), keyLabel); + if (ret == WH_ERROR_OK) { + evict = 1; + } + } + + if (ret == WH_ERROR_OK) { + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WC_ALGO_TYPE_PK; + uint32_t options = 0; + + uint32_t total_len = sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + pubKeySz; + + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_SlhDsaCheckPrivKeyRequest*) + _createCryptoRequestWithSubtype( + dataPtr, WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY, + WC_PQC_SIG_TYPE_SLHDSA, ctx->cryptoAffinity); + + if (total_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + uint16_t req_len = (uint16_t)total_len; + + if (evict != 0) { + options |= + WH_MESSAGE_CRYPTO_SLHDSA_CHECKPRIVKEY_OPTIONS_EVICT; + } + + memset(req, 0, sizeof(*req)); + req->options = options; + req->param = _SlhDsaKeyParam(key); + req->keyId = key_id; + req->pubSz = pubKeySz; + if (pubKeySz > 0) { + memcpy((uint8_t*)(req + 1), pubKey, pubKeySz); + } + + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + if (ret == WH_ERROR_OK) { + uint16_t res_len = 0; + evict = 0; + + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + + if (ret == WH_ERROR_OK) { + ret = _getCryptoResponse( + dataPtr, WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY, + (uint8_t**)&res); + if (ret >= 0) { + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + if (res_len < hdr_sz) { + ret = WH_ERROR_ABORTED; + } + else { + ret = (int)res->res; + } + } + } + } + } + else { + ret = WH_ERROR_BADARGS; + } + } + if (evict != 0) { + (void)wh_Client_KeyEvict(ctx, key_id); + } + return ret; +} + +#ifdef WOLFHSM_CFG_DMA + +int wh_Client_SlhDsaImportKeyDma(whClientContext* ctx, SlhDsaKey* key, + whKeyId* inout_keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + int ret = WH_ERROR_OK; + whKeyId key_id = WH_KEYID_ERASED; + byte buffer[WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE]; + uint16_t buffer_len = 0; + + if ((ctx == NULL) || (key == NULL) || + ((label_len != 0) && (label == NULL))) { + return WH_ERROR_BADARGS; + } + + if (inout_keyId != NULL) { + key_id = *inout_keyId; + } + + /* Serialize the key to a temporary buffer first */ + ret = wh_Crypto_SlhDsaSerializeKeyDer(key, sizeof(buffer), buffer, + &buffer_len); + if (ret == WH_ERROR_OK) { + /* Cache the key using DMA and get the keyID */ + ret = wh_Client_KeyCacheDma(ctx, flags, label, label_len, buffer, + buffer_len, &key_id); + if ((ret == WH_ERROR_OK) && (inout_keyId != NULL)) { + *inout_keyId = key_id; + } + } + + return ret; +} + +int wh_Client_SlhDsaExportKeyDma(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label) +{ + int ret = WH_ERROR_OK; + byte buffer[WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE] = {0}; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Export the key from server using DMA */ + ret = wh_Client_KeyExportDma(ctx, keyId, buffer, buffer_len, label, + label_len, &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaDeserializeKeyDer(buffer, buffer_len, key); + } + + return ret; +} + +int wh_Client_SlhDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label) +{ + int ret; + byte buffer[WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE] = {0}; + uint16_t buffer_len = sizeof(buffer); + + if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_KeyExportPublicDma(ctx, keyId, WH_KEY_ALGO_SLHDSA, buffer, + buffer_len, label, label_len, + &buffer_len); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaDeserializeKeyDer(buffer, buffer_len, key); + } + return ret; +} + +static int _SlhDsaMakeKeyDma(whClientContext* ctx, int param, const byte* seed, + word32 seedSz, whKeyId* inout_key_id, + whNvmFlags flags, uint16_t label_len, + const uint8_t* label, SlhDsaKey* key) +{ + int ret = WH_ERROR_OK; + whKeyId key_id = WH_KEYID_ERASED; + byte buffer[WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE]; + uint8_t* dataPtr = NULL; + whMessageCrypto_SlhDsaKeyGenDmaRequest* req = NULL; + whMessageCrypto_SlhDsaKeyGenDmaResponse* res = NULL; + uintptr_t keyAddr = 0; + uintptr_t seedAddr = 0; + uint64_t keyAddrSz = 0; + uint16_t pkType; + uint16_t req_len; + uint16_t res_len = 0; + uint16_t group; + uint16_t action; + + if ((ctx == NULL) || ((seed == NULL) && (seedSz > 0))) { + return WH_ERROR_BADARGS; + } + + /* Get data pointer from the context to use as request/response storage */ + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + pkType = (seed != NULL) ? WC_PK_TYPE_PQC_SIG_KEYGEN_SEEDED + : WC_PK_TYPE_PQC_SIG_KEYGEN; + + /* Setup generic header and get pointer to request data */ + req = + (whMessageCrypto_SlhDsaKeyGenDmaRequest*) + _createCryptoRequestWithSubtype(dataPtr, pkType, + WC_PQC_SIG_TYPE_SLHDSA, + ctx->cryptoAffinity); + + /* Use the supplied key id if provided */ + if (inout_key_id != NULL) { + key_id = *inout_key_id; + } + + group = WH_MESSAGE_GROUP_CRYPTO_DMA; + action = WC_ALGO_TYPE_PK; + + req_len = sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + + if (req_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + memset(req, 0, sizeof(*req)); + req->param = param; + req->flags = flags; + req->keyId = key_id; + req->key.sz = keyAddrSz = sizeof(buffer); + + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)buffer, (void**)&keyAddr, keyAddrSz, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + req->key.addr = (uint64_t)(uintptr_t)keyAddr; + } + + if ((ret == WH_ERROR_OK) && (seedSz > 0)) { + req->seed.sz = seedSz; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)seed, (void**)&seedAddr, seedSz, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + req->seed.addr = (uint64_t)(uintptr_t)seedAddr; + } + } + + if ((label != NULL) && (label_len > 0)) { + if (label_len > WH_NVM_LABEL_LEN) { + label_len = WH_NVM_LABEL_LEN; + } + memcpy(req->label, label, label_len); + req->labelSize = label_len; + } + + if (ret == WH_ERROR_OK) { + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + } + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + } + + if (seedSz > 0) { + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)seed, (void**)&seedAddr, seedSz, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + } + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)buffer, (void**)&keyAddr, keyAddrSz, + WH_DMA_OPER_CLIENT_WRITE_POST, (whDmaFlags){0}); + + if (ret == WH_ERROR_OK) { + /* Get response structure pointer, validates generic header rc */ + ret = _getCryptoResponse(dataPtr, pkType, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some + * scenarios */ + if (ret >= 0) { + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + /* The response has no trailing payload; keySize bounds the + * DMA buffer write */ + if (res_len < hdr_sz) { + ret = WH_ERROR_ABORTED; + } + } + if (ret >= 0) { + /* Key is cached on server or is ephemeral */ + key_id = (whKeyId)(res->keyId); + + /* Update output variable if requested */ + if (inout_key_id != NULL) { + *inout_key_id = key_id; + } + + /* Update the context if provided */ + if (key != NULL) { + /* Set the key_id. ERASED for EPHEMERAL, cached id + * otherwise. */ + wh_Client_SlhDsaSetKeyId(key, key_id); + + if (res->keySize == 0) { + ret = WH_ERROR_ABORTED; + } + /* Bound the server-reported key size to the DMA buffer + * capacity before deserializing */ + else if (res->keySize > sizeof(buffer)) { + ret = WH_ERROR_ABORTED; + } + else { + ret = wh_Crypto_SlhDsaDeserializeKeyDer( + buffer, (uint16_t)res->keySize, key); + } + } + } + } + } + else { + ret = WH_ERROR_BADARGS; + } + return ret; +} + +int wh_Client_SlhDsaMakeExportKeyDma(whClientContext* ctx, int param, + SlhDsaKey* key) +{ + if (key == NULL) { + return WH_ERROR_BADARGS; + } + + return _SlhDsaMakeKeyDma(ctx, param, NULL, 0, NULL, WH_NVM_FLAGS_EPHEMERAL, + 0, NULL, key); +} + +int wh_Client_SlhDsaMakeExportKeyFromSeedDma(whClientContext* ctx, int param, + const byte* seed, word32 seedSz, + SlhDsaKey* key) +{ + if ((key == NULL) || (seed == NULL) || (seedSz == 0)) { + return WH_ERROR_BADARGS; + } + + return _SlhDsaMakeKeyDma(ctx, param, seed, seedSz, NULL, + WH_NVM_FLAGS_EPHEMERAL, 0, NULL, key); +} + +int wh_Client_SlhDsaMakeCacheKeyDma(whClientContext* ctx, int param, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, + SlhDsaKey* pub) +{ + int ret; + whKeyId in_keyId; + + if ((ctx == NULL) || (inout_key_id == NULL) || (pub == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Ephemeral keygen belongs to the export path, not the cache path. */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + in_keyId = *inout_key_id; + ret = _SlhDsaMakeKeyDma(ctx, param, NULL, 0, inout_key_id, flags, label_len, + label, pub); + if (ret >= 0) { + wh_Client_SlhDsaSetKeyId(pub, *inout_key_id); + pub->devId = WH_CLIENT_DEVID(ctx); + } + else if (WH_KEYID_ISERASED(in_keyId) && !WH_KEYID_ISERASED(*inout_key_id)) { + /* The server auto-assigned and committed a key but the export failed. + * Roll back so the operation is atomic and no cache slot is + * orphaned. */ + (void)wh_Client_KeyEvict(ctx, *inout_key_id); + *inout_key_id = WH_KEYID_ERASED; + } + return ret; +} + +int wh_Client_SlhDsaSignDma(whClientContext* ctx, const byte* in, + word32 in_len, byte* out, word32* out_len, + SlhDsaKey* key, const byte* context, + byte contextLen, word32 preHashType, + const byte* addRnd, byte addRndSz, int randomized, + int isMPrime) +{ + int ret = WH_ERROR_OK; + whMessageCrypto_SlhDsaSignDmaRequest* req = NULL; + whMessageCrypto_SlhDsaSignDmaResponse* res = NULL; + uint8_t* dataPtr = NULL; + uintptr_t inAddr = 0; + uintptr_t outAddr = 0; + word32 sigCap = 0; + uint16_t pkType; + + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + if ((ctx == NULL) || (key == NULL) || ((in == NULL) && (in_len > 0)) || + (out == NULL) || (out_len == NULL) || + ((addRnd == NULL) && (addRndSz > 0))) { + return WH_ERROR_BADARGS; + } + + /* Caller's signature buffer capacity, before the response overwrites it */ + sigCap = *out_len; + + pkType = (isMPrime != 0) ? WC_PK_TYPE_PQC_SIG_SIGN_MSG + : WC_PK_TYPE_PQC_SIG_SIGN; + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + + /* Import key if necessary */ + if (WH_KEYID_ISERASED(key_id)) { + uint8_t keyLabel[] = "TempSlhDsaSign"; + whNvmFlags flags = WH_NVM_FLAGS_USAGE_SIGN; + + ret = wh_Client_SlhDsaImportKeyDma(ctx, key, &key_id, flags, + sizeof(keyLabel), keyLabel); + if (ret == WH_ERROR_OK) { + evict = 1; + } + } + + if (ret == WH_ERROR_OK) { + /* Request Message */ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO_DMA; + uint16_t action = WC_ALGO_TYPE_PK; + + uint16_t req_len = sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + contextLen + addRndSz; + uint32_t options = 0; + + /* Get data pointer from the context to use as request/response storage + */ + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + /* Setup generic header and get pointer to request data */ + req = (whMessageCrypto_SlhDsaSignDmaRequest*) + _createCryptoRequestWithSubtype(dataPtr, pkType, + WC_PQC_SIG_TYPE_SLHDSA, + ctx->cryptoAffinity); + + if (req_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + uint8_t* req_data = (uint8_t*)(req + 1); + + if (evict != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_EVICT; + } + if (isMPrime != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_MPRIME; + } + if (randomized != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_RANDOMIZED; + } + + memset(req, 0, sizeof(*req)); + req->options = options; + req->param = _SlhDsaKeyParam(key); + req->keyId = key_id; + req->contextSz = contextLen; + req->preHashType = preHashType; + req->addRndSz = addRndSz; + if ((context != NULL) && (contextLen > 0)) { + memcpy(req_data, context, contextLen); + } + if (addRndSz > 0) { + memcpy(req_data + contextLen, addRnd, addRndSz); + } + + /* Set up DMA buffers */ + req->msg.sz = in_len; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)in, (void**)&inAddr, req->msg.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + req->msg.addr = inAddr; + } + + if (ret == WH_ERROR_OK) { + req->sig.sz = sigCap; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)out, (void**)&outAddr, req->sig.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + req->sig.addr = outAddr; + } + } + + /* Send Request */ + if (ret == WH_ERROR_OK) { + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + } + if (ret == WH_ERROR_OK) { + /* Server will evict at this point if requested */ + uint16_t res_len = 0; + evict = 0; + + /* Recv Response */ + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + + if (ret == WH_ERROR_OK) { + /* Get response structure pointer, validates generic header + * rc */ + ret = _getCryptoResponse(dataPtr, pkType, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some + * scenarios */ + if (ret >= 0) { + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + if (res_len < hdr_sz) { + ret = WH_ERROR_ABORTED; + } + else if (res->sigLen > sigCap) { + ret = WH_ERROR_BADARGS; + } + else { + /* Update signature length */ + *out_len = res->sigLen; + } + } + } + } + + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)out, (void**)&outAddr, sigCap, + WH_DMA_OPER_CLIENT_WRITE_POST, (whDmaFlags){0}); + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)in, (void**)&inAddr, in_len, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + } + else { + ret = WH_ERROR_BADARGS; + } + } + /* Evict the key manually on error if needed */ + if (evict != 0) { + (void)wh_Client_KeyEvict(ctx, key_id); + } + + return ret; +} + +int wh_Client_SlhDsaVerifyDma(whClientContext* ctx, const byte* sig, + word32 sig_len, const byte* msg, word32 msg_len, + int* out_res, SlhDsaKey* key, const byte* context, + byte contextLen, word32 preHashType, + int isMPrime) +{ + int ret = WH_ERROR_OK; + whMessageCrypto_SlhDsaVerifyDmaRequest* req = NULL; + whMessageCrypto_SlhDsaVerifyDmaResponse* res = NULL; + uint8_t* dataPtr = NULL; + uint16_t pkType; + + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + if ((ctx == NULL) || (key == NULL) || ((sig == NULL) && (sig_len > 0)) || + ((msg == NULL) && (msg_len > 0)) || (out_res == NULL)) { + return WH_ERROR_BADARGS; + } + + pkType = (isMPrime != 0) ? WC_PK_TYPE_PQC_SIG_VERIFY_MSG + : WC_PK_TYPE_PQC_SIG_VERIFY; + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + + /* Import key if necessary */ + if (WH_KEYID_ISERASED(key_id)) { + uint8_t keyLabel[] = "TempSlhDsaVerify"; + whNvmFlags flags = WH_NVM_FLAGS_USAGE_VERIFY; + + ret = wh_Client_SlhDsaImportKeyDma(ctx, key, &key_id, flags, + sizeof(keyLabel), keyLabel); + if (ret == WH_ERROR_OK) { + evict = 1; + } + } + + if (ret == WH_ERROR_OK) { + /* Request Message */ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO_DMA; + uint16_t action = WC_ALGO_TYPE_PK; + uint32_t options = 0; + uintptr_t sigAddr = 0; + uintptr_t msgAddr = 0; + + uint16_t req_len = sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(*req) + contextLen; + + /* Get data pointer from the context to use as request/response storage + */ + dataPtr = (uint8_t*)wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + /* Setup generic header and get pointer to request data */ + req = (whMessageCrypto_SlhDsaVerifyDmaRequest*) + _createCryptoRequestWithSubtype(dataPtr, pkType, + WC_PQC_SIG_TYPE_SLHDSA, + ctx->cryptoAffinity); + + if (req_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + if (evict != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_EVICT; + } + if (isMPrime != 0) { + options |= WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_MPRIME; + } + + memset(req, 0, sizeof(*req)); + req->options = options; + req->param = _SlhDsaKeyParam(key); + req->keyId = key_id; + req->contextSz = contextLen; + req->preHashType = preHashType; + if ((context != NULL) && (contextLen > 0)) { + memcpy((uint8_t*)(req + 1), context, contextLen); + } + + /* Set up DMA buffers */ + req->sig.sz = sig_len; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)sig, (void**)&sigAddr, sig_len, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + req->sig.addr = sigAddr; + } + if (ret == WH_ERROR_OK) { + req->msg.sz = msg_len; + ret = wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)msg, (void**)&msgAddr, msg_len, + WH_DMA_OPER_CLIENT_READ_PRE, (whDmaFlags){0}); + if (ret == WH_ERROR_OK) { + req->msg.addr = msgAddr; + } + } + + /* Send Request */ + if (ret == WH_ERROR_OK) { + ret = wh_Client_SendRequest(ctx, group, action, req_len, + (uint8_t*)dataPtr); + } + if (ret == WH_ERROR_OK) { + /* Server will evict at this point if requested */ + uint16_t res_len = 0; + evict = 0; + + /* Recv Response */ + do { + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, + (uint8_t*)dataPtr); + } while (ret == WH_ERROR_NOTREADY); + + if (ret == WH_ERROR_OK) { + /* Get response structure pointer, validates generic header + * rc */ + ret = _getCryptoResponse(dataPtr, pkType, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some + * scenarios */ + if (ret >= 0) { + const uint32_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + + sizeof(*res); + /* Note whMessageCrypto_SlhDsaVerifyDmaResponse has no + * size field */ + if (res_len < hdr_sz) { + ret = WH_ERROR_ABORTED; + } + else { + /* Set verification result */ + *out_res = res->verifyResult; + } + } + } + } + + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)msg, (void**)&msgAddr, msg_len, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + (void)wh_Client_DmaProcessClientAddress( + ctx, (uintptr_t)sig, (void**)&sigAddr, sig_len, + WH_DMA_OPER_CLIENT_READ_POST, (whDmaFlags){0}); + } + else { + ret = WH_ERROR_BADARGS; + } + } + + /* Evict the key manually on error if needed */ + if (evict != 0) { + (void)wh_Client_KeyEvict(ctx, key_id); + } + + return ret; +} + +int wh_Client_SlhDsaCheckPrivKeyDma(whClientContext* ctx, SlhDsaKey* key, + const byte* pubKey, word32 pubKeySz) +{ + /* The public key is 2n bytes, so the non-DMA request always fits. There is + * nothing for DMA to carry, so reuse the comm-buffer path. */ + return wh_Client_SlhDsaCheckPrivKey(ctx, key, pubKey, pubKeySz); +} + +#endif /* WOLFHSM_CFG_DMA */ +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef WOLFSSL_HAVE_MLKEM int wh_Client_MlKemSetKeyId(MlKemKey* key, whKeyId keyId) diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c index 8ad889399..5d5d13b61 100644 --- a/src/wh_client_cryptocb.c +++ b/src/wh_client_cryptocb.c @@ -81,7 +81,8 @@ static int _handlePqcStatefulSigSigsLeft(whClientContext* ctx, #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) static int _handlePqcSigKeyGen(whClientContext* ctx, wc_CryptoInfo* info, int useDma); static int _handlePqcSign(whClientContext* ctx, wc_CryptoInfo* info, @@ -90,7 +91,7 @@ static int _handlePqcVerify(whClientContext* ctx, wc_CryptoInfo* info, int useDma); static int _handlePqcSigCheckPrivKey(whClientContext* ctx, wc_CryptoInfo* info, int useDma); -#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON */ +#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON || WOLFSSL_HAVE_SLHDSA */ int wh_Client_CryptoCb(int devId, wc_CryptoInfo* info, void* inCtx) { @@ -564,7 +565,8 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx) #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) case WC_PK_TYPE_PQC_SIG_KEYGEN: ret = _handlePqcSigKeyGen(ctx, info, 0); break; @@ -581,7 +583,23 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx) ret = _handlePqcSigCheckPrivKey(ctx, info, 0); break; -#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON */ +#ifdef WOLFSSL_HAVE_SLHDSA + /* Only SLH-DSA reaches these: a seeded key generation and the + * FIPS 205 internal interface, where the caller builds M' itself. */ + case WC_PK_TYPE_PQC_SIG_KEYGEN_SEEDED: + ret = _handlePqcSigKeyGen(ctx, info, 0); + break; + + case WC_PK_TYPE_PQC_SIG_SIGN_MSG: + ret = _handlePqcSign(ctx, info, 0); + break; + + case WC_PK_TYPE_PQC_SIG_VERIFY_MSG: + ret = _handlePqcVerify(ctx, info, 0); + break; +#endif /* WOLFSSL_HAVE_SLHDSA */ + +#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON || WOLFSSL_HAVE_SLHDSA */ case WC_PK_TYPE_NONE: default: @@ -1229,7 +1247,8 @@ static int _handlePqcStatefulSigSigsLeft(whClientContext* ctx, } #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ -#if defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) +#if defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \ + defined(WOLFSSL_HAVE_SLHDSA) static int _handlePqcSigKeyGen(whClientContext* ctx, wc_CryptoInfo* info, int useDma) { @@ -1263,6 +1282,35 @@ static int _handlePqcSigKeyGen(whClientContext* ctx, wc_CryptoInfo* info, } break; #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PQC_SIG_TYPE_SLHDSA: { + /* size carries the SlhDsaParam enum, not a byte count */ + const byte* seed = info->pk.pqc_sig_kg.seed; + word32 seedSz = info->pk.pqc_sig_kg.seedSz; +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + if (seed != NULL) { + ret = wh_Client_SlhDsaMakeExportKeyFromSeedDma( + ctx, size, seed, seedSz, key); + } + else { + ret = wh_Client_SlhDsaMakeExportKeyDma(ctx, size, key); + } + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + if (seed != NULL) { + ret = wh_Client_SlhDsaMakeExportKeyFromSeed( + ctx, size, seed, seedSz, key); + } + else { + ret = wh_Client_SlhDsaMakeExportKey(ctx, size, key); + } + } + } break; +#endif /* WOLFSSL_HAVE_SLHDSA */ + /* Support for additional PQC algorithms should be added here */ default: @@ -1273,6 +1321,19 @@ static int _handlePqcSigKeyGen(whClientContext* ctx, wc_CryptoInfo* info, return ret; } +#ifdef WOLFSSL_HAVE_SLHDSA +/* Whether the caller's key carries actual key bytes rather than being a bare + * handle to a server-resident key. Several wolfCrypt SLH-DSA entry points read + * PK.seed or PK.root straight out of the key struct, and for a bare handle + * those are zeroes that the server must supply from its own copy instead. */ +static int _SlhDsaKeyHasMaterial(const SlhDsaKey* key) +{ + return (key != NULL) && + ((key->flags & + (WC_SLHDSA_FLAG_PRIVATE | WC_SLHDSA_FLAG_PUBLIC)) != 0); +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + static int _handlePqcSign(whClientContext* ctx, wc_CryptoInfo* info, int useDma) { int ret = CRYPTOCB_UNAVAILABLE; @@ -1313,6 +1374,34 @@ static int _handlePqcSign(whClientContext* ctx, wc_CryptoInfo* info, int useDma) break; #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PQC_SIG_TYPE_SLHDSA: { + const byte* addRnd = info->pk.pqc_sign.addRnd; + byte addRndSz = info->pk.pqc_sign.addRndSz; + int randomized = (info->pk.pqc_sign.rng != NULL); + int isMPrime = + (info->pk.type == WC_PK_TYPE_PQC_SIG_SIGN_MSG); + + if (!_SlhDsaKeyHasMaterial((const SlhDsaKey*)key)) { + addRnd = NULL; + addRndSz = 0; + } +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_SlhDsaSignDma( + ctx, in, in_len, out, out_len, key, context, contextLen, + preHashType, addRnd, addRndSz, randomized, isMPrime); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = wh_Client_SlhDsaSign( + ctx, in, in_len, out, out_len, key, context, contextLen, + preHashType, addRnd, addRndSz, randomized, isMPrime); + } + } break; +#endif /* WOLFSSL_HAVE_SLHDSA */ + /* Support for additional PQC algorithms should be added here */ default: @@ -1366,6 +1455,25 @@ static int _handlePqcVerify(whClientContext* ctx, wc_CryptoInfo* info, break; #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PQC_SIG_TYPE_SLHDSA: { + int isMPrime = (info->pk.type == WC_PK_TYPE_PQC_SIG_VERIFY_MSG); +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_SlhDsaVerifyDma(ctx, sig, sig_len, msg, msg_len, + res, key, context, contextLen, + preHashType, isMPrime); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = wh_Client_SlhDsaVerify(ctx, sig, sig_len, msg, msg_len, + res, key, context, contextLen, + preHashType, isMPrime); + } + } break; +#endif /* WOLFSSL_HAVE_SLHDSA */ + /* Support for additional PQC algorithms should be added here */ default: @@ -1410,6 +1518,32 @@ static int _handlePqcSigCheckPrivKey(whClientContext* ctx, wc_CryptoInfo* info, break; #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PQC_SIG_TYPE_SLHDSA: { + /* wc_SlhDsaKey_CheckKey takes the expected public key out of the + * key struct. A bare handle holds none, so drop it and let the + * server check its own copy for consistency instead of comparing + * against zeroes. */ + const byte* slhPub = pubKey; + word32 slhPubSz = pubKeySz; + + if (!_SlhDsaKeyHasMaterial((const SlhDsaKey*)key)) { + slhPub = NULL; + slhPubSz = 0; + } +#ifdef WOLFHSM_CFG_DMA + if (useDma) { + ret = wh_Client_SlhDsaCheckPrivKeyDma(ctx, key, slhPub, + slhPubSz); + } + else +#endif /* WOLFHSM_CFG_DMA */ + { + ret = wh_Client_SlhDsaCheckPrivKey(ctx, key, slhPub, slhPubSz); + } + } break; +#endif /* WOLFSSL_HAVE_SLHDSA */ + /* Support for additional PQC algorithms should be added here */ default: @@ -1419,7 +1553,7 @@ static int _handlePqcSigCheckPrivKey(whClientContext* ctx, wc_CryptoInfo* info, return ret; } -#endif /* HAVE_FALCON || WOLFSSL_HAVE_MLDSA */ +#endif /* HAVE_FALCON || WOLFSSL_HAVE_MLDSA || WOLFSSL_HAVE_SLHDSA */ #ifdef WOLFHSM_CFG_DMA @@ -1563,7 +1697,8 @@ int wh_Client_CryptoCbDma(int devId, wc_CryptoInfo* info, void* inCtx) ret = _handlePqcStatefulSigSigsLeft(ctx, info, 1); break; #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) case WC_PK_TYPE_PQC_SIG_KEYGEN: ret = _handlePqcSigKeyGen(ctx, info, 1); break; @@ -1576,7 +1711,18 @@ int wh_Client_CryptoCbDma(int devId, wc_CryptoInfo* info, void* inCtx) case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: ret = _handlePqcSigCheckPrivKey(ctx, info, 1); break; -#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PK_TYPE_PQC_SIG_KEYGEN_SEEDED: + ret = _handlePqcSigKeyGen(ctx, info, 1); + break; + case WC_PK_TYPE_PQC_SIG_SIGN_MSG: + ret = _handlePqcSign(ctx, info, 1); + break; + case WC_PK_TYPE_PQC_SIG_VERIFY_MSG: + ret = _handlePqcVerify(ctx, info, 1); + break; +#endif /* WOLFSSL_HAVE_SLHDSA */ +#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON || WOLFSSL_HAVE_SLHDSA */ #ifdef HAVE_ED25519 case WC_PK_TYPE_ED25519_KEYGEN: { ed25519_key* key = info->pk.ed25519kg.key; diff --git a/src/wh_crypto.c b/src/wh_crypto.c index 3c786c7ae..b8c83ca79 100644 --- a/src/wh_crypto.c +++ b/src/wh_crypto.c @@ -387,6 +387,67 @@ int wh_Crypto_MlDsaDeserializeKeyDer(const uint8_t* buffer, uint16_t size, } #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA +int wh_Crypto_SlhDsaSerializeKeyDer(SlhDsaKey* key, uint16_t max_size, + uint8_t* buffer, uint16_t* out_size) +{ + int ret = 0; + + if ((key == NULL) || (buffer == NULL) || (out_size == NULL)) { + return WH_ERROR_BADARGS; + } + + /* Choose appropriate serialization based on key flags */ + if (key->flags & WC_SLHDSA_FLAG_PRIVATE) { +#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY + /* RFC 9909 always carries the public key alongside the private one */ + ret = wc_SlhDsaKey_KeyToDer(key, buffer, max_size); +#else + ret = WH_ERROR_BADARGS; +#endif + } + else if (key->flags & WC_SLHDSA_FLAG_PUBLIC) { + /* Public key only - use SPKI format */ + ret = wc_SlhDsaKey_PublicKeyToDer(key, buffer, max_size, 1); + } + else { + /* No key data set */ + return WH_ERROR_BADARGS; + } + + /* ASN.1 functions return the size of the DER encoded key on success */ + if (ret > 0) { + *out_size = ret; + ret = WH_ERROR_OK; + } + return ret; +} + +int wh_Crypto_SlhDsaDeserializeKeyDer(const uint8_t* buffer, uint16_t size, + SlhDsaKey* key) +{ + word32 idx = 0; + int ret; + + if ((buffer == NULL) || (key == NULL)) { + return WH_ERROR_BADARGS; + } + +#ifndef WOLFSSL_SLHDSA_VERIFY_ONLY + /* Try private key first, if that fails try public key */ + ret = wc_SlhDsaKey_PrivateKeyDecode(buffer, &idx, key, size); + if (ret != 0) { + /* Reset index before trying public key */ + idx = 0; + ret = wc_SlhDsaKey_PublicKeyDecode(buffer, &idx, key, size); + } +#else + ret = wc_SlhDsaKey_PublicKeyDecode(buffer, &idx, key, size); +#endif + return ret; +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef WOLFSSL_HAVE_MLKEM int wh_Crypto_MlKemSerializeKey(MlKemKey* key, uint16_t max_size, uint8_t* buffer, uint16_t* out_size) diff --git a/src/wh_message_crypto.c b/src/wh_message_crypto.c index 45579b7be..22a763176 100644 --- a/src/wh_message_crypto.c +++ b/src/wh_message_crypto.c @@ -923,6 +923,127 @@ int wh_MessageCrypto_TranslateMlDsaVerifyResponse( return 0; } +/* SLH-DSA Key Generation Request translation */ +int wh_MessageCrypto_TranslateSlhDsaKeyGenRequest( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenRequest* src, + whMessageCrypto_SlhDsaKeyGenRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, sz); + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, flags); + WH_T32(magic, dest, src, access); + WH_T32(magic, dest, src, seedSz); + /* Label is just a byte array, no translation needed */ + if (src != dest) { + memcpy(dest->label, src->label, sizeof(src->label)); + } + return 0; +} + +/* SLH-DSA Key Generation Response translation */ +int wh_MessageCrypto_TranslateSlhDsaKeyGenResponse( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenResponse* src, + whMessageCrypto_SlhDsaKeyGenResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, len); + return 0; +} + +/* SLH-DSA Sign Request translation */ +int wh_MessageCrypto_TranslateSlhDsaSignRequest( + uint16_t magic, const whMessageCrypto_SlhDsaSignRequest* src, + whMessageCrypto_SlhDsaSignRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, sz); + WH_T32(magic, dest, src, contextSz); + WH_T32(magic, dest, src, preHashType); + WH_T32(magic, dest, src, addRndSz); + return 0; +} + +/* SLH-DSA Sign Response translation */ +int wh_MessageCrypto_TranslateSlhDsaSignResponse( + uint16_t magic, const whMessageCrypto_SlhDsaSignResponse* src, + whMessageCrypto_SlhDsaSignResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, sz); + return 0; +} + +/* SLH-DSA Verify Request translation */ +int wh_MessageCrypto_TranslateSlhDsaVerifyRequest( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyRequest* src, + whMessageCrypto_SlhDsaVerifyRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, sigSz); + WH_T32(magic, dest, src, hashSz); + WH_T32(magic, dest, src, contextSz); + WH_T32(magic, dest, src, preHashType); + return 0; +} + +/* SLH-DSA Verify Response translation */ +int wh_MessageCrypto_TranslateSlhDsaVerifyResponse( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyResponse* src, + whMessageCrypto_SlhDsaVerifyResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, res); + return 0; +} + +/* SLH-DSA Check Private Key Request translation */ +int wh_MessageCrypto_TranslateSlhDsaCheckPrivKeyRequest( + uint16_t magic, const whMessageCrypto_SlhDsaCheckPrivKeyRequest* src, + whMessageCrypto_SlhDsaCheckPrivKeyRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, pubSz); + return 0; +} + +/* SLH-DSA Check Private Key Response translation */ +int wh_MessageCrypto_TranslateSlhDsaCheckPrivKeyResponse( + uint16_t magic, const whMessageCrypto_SlhDsaCheckPrivKeyResponse* src, + whMessageCrypto_SlhDsaCheckPrivKeyResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, res); + return 0; +} + /* ML-KEM Key Generation Request translation */ int wh_MessageCrypto_TranslateMlKemKeyGenRequest( uint16_t magic, const whMessageCrypto_MlKemKeyGenRequest* src, @@ -1363,6 +1484,165 @@ int wh_MessageCrypto_TranslateMlDsaVerifyDmaResponse( return 0; } +/* SLH-DSA DMA Key Generation Request translation */ +int wh_MessageCrypto_TranslateSlhDsaKeyGenDmaRequest( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenDmaRequest* src, + whMessageCrypto_SlhDsaKeyGenDmaRequest* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->key, &dest->key); + if (ret != 0) { + return ret; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->seed, &dest->seed); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, flags); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, access); + WH_T32(magic, dest, src, labelSize); + /* Label is just a byte array, no translation needed */ + if (src != dest) { + memcpy(dest->label, src->label, sizeof(src->label)); + } + + return 0; +} + +/* SLH-DSA DMA Key Generation Response translation */ +int wh_MessageCrypto_TranslateSlhDsaKeyGenDmaResponse( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenDmaResponse* src, + whMessageCrypto_SlhDsaKeyGenDmaResponse* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaAddrStatus(magic, &src->dmaAddrStatus, + &dest->dmaAddrStatus); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, keySize); + return 0; +} + +/* SLH-DSA DMA Sign Request translation */ +int wh_MessageCrypto_TranslateSlhDsaSignDmaRequest( + uint16_t magic, const whMessageCrypto_SlhDsaSignDmaRequest* src, + whMessageCrypto_SlhDsaSignDmaRequest* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->msg, &dest->msg); + if (ret != 0) { + return ret; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->sig, &dest->sig); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, contextSz); + WH_T32(magic, dest, src, preHashType); + WH_T32(magic, dest, src, addRndSz); + + return 0; +} + +/* SLH-DSA DMA Sign Response translation */ +int wh_MessageCrypto_TranslateSlhDsaSignDmaResponse( + uint16_t magic, const whMessageCrypto_SlhDsaSignDmaResponse* src, + whMessageCrypto_SlhDsaSignDmaResponse* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaAddrStatus(magic, &src->dmaAddrStatus, + &dest->dmaAddrStatus); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, sigLen); + return 0; +} + +/* SLH-DSA DMA Verify Request translation */ +int wh_MessageCrypto_TranslateSlhDsaVerifyDmaRequest( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyDmaRequest* src, + whMessageCrypto_SlhDsaVerifyDmaRequest* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->sig, &dest->sig); + if (ret != 0) { + return ret; + } + + ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->msg, &dest->msg); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, param); + WH_T32(magic, dest, src, keyId); + WH_T32(magic, dest, src, contextSz); + WH_T32(magic, dest, src, preHashType); + + return 0; +} + +/* SLH-DSA DMA Verify Response translation */ +int wh_MessageCrypto_TranslateSlhDsaVerifyDmaResponse( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyDmaResponse* src, + whMessageCrypto_SlhDsaVerifyDmaResponse* dest) +{ + int ret; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateDmaAddrStatus(magic, &src->dmaAddrStatus, + &dest->dmaAddrStatus); + if (ret != 0) { + return ret; + } + + WH_T32(magic, dest, src, verifyResult); + return 0; +} + /* ML-KEM DMA Key Generation Request translation */ int wh_MessageCrypto_TranslateMlKemKeyGenDmaRequest( uint16_t magic, const whMessageCrypto_MlKemKeyGenDmaRequest* src, diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 23e3b703c..04034410f 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -1030,6 +1030,103 @@ static int _MlDsaKeyCacheExportEnforce(whServerContext* ctx, whKeyId keyId, } #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA +#define WH_SERVER_SLHDSA_MAX_CACHE_DER_SIZE WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE + +WH_UTILS_STATIC_ASSERT(WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE >= + WH_SERVER_SLHDSA_MAX_CACHE_DER_SIZE, + "WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE too small for " + "SLH-DSA key DER"); + +int wh_Server_SlhDsaKeyCacheImport(whServerContext* ctx, SlhDsaKey* key, + whKeyId keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label) +{ + int ret = WH_ERROR_OK; + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + uint16_t der_size; + + if ((ctx == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId)) || + ((label != NULL) && (label_len > sizeof(cacheMeta->label)))) { + return WH_ERROR_BADARGS; + } + + ret = wh_Server_KeystoreGetCacheSlotChecked( + ctx, keyId, WH_SERVER_SLHDSA_MAX_CACHE_DER_SIZE, &cacheBuf, &cacheMeta); + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaSerializeKeyDer( + key, WH_SERVER_SLHDSA_MAX_CACHE_DER_SIZE, cacheBuf, &der_size); + WH_DEBUG_SERVER_VERBOSE("keyId:%u, ret:%d\n", keyId, ret); + } + + if (ret == WH_ERROR_OK) { + cacheMeta->id = keyId; + cacheMeta->len = der_size; + /* clients can't set server-only flags (e.g. trusted KEK) */ + cacheMeta->flags = flags & ~WH_NVM_FLAGS_SERVER_ONLY; + cacheMeta->access = WH_NVM_ACCESS_ANY; + + if ((label != NULL) && (label_len > 0)) { + memcpy(cacheMeta->label, label, label_len); + } + } + + return ret; +} + +int wh_Server_SlhDsaKeyCacheExport(whServerContext* ctx, whKeyId keyId, + SlhDsaKey* key) +{ + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + int ret = WH_ERROR_OK; + + if ((ctx == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId))) { + return WH_ERROR_BADARGS; + } + + ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta); + + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaDeserializeKeyDer(cacheBuf, cacheMeta->len, key); + WH_DEBUG_SERVER_VERBOSE("keyId:%u, ret:%d\n", keyId, ret); + } + return ret; +} + +static int _SlhDsaKeyCacheExportEnforce(whServerContext* ctx, whKeyId keyId, + whNvmFlags requiredUsage, + SlhDsaKey* key) +{ + uint8_t* cacheBuf; + whNvmMetadata* cacheMeta; + int ret; + + if ((ctx == NULL) || (key == NULL) || (WH_KEYID_ISERASED(keyId))) { + return WH_ERROR_BADARGS; + } + + /* Freshen, check usage and deserialize under one hold of the NVM lock so + * the policy verdict, the metadata length and the key bytes all come from + * the same snapshot of the shared cache slot. */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeystoreFreshenKey(ctx, keyId, &cacheBuf, &cacheMeta); + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeystoreEnforceKeyUsage(cacheMeta, requiredUsage); + } + if (ret == WH_ERROR_OK) { + ret = wh_Crypto_SlhDsaDeserializeKeyDer(cacheBuf, cacheMeta->len, + key); + WH_DEBUG_SERVER_VERBOSE("keyId:%u, ret:%d\n", keyId, ret); + } + (void)WH_SERVER_NVM_UNLOCK(ctx); + } /* WH_SERVER_NVM_LOCK() */ + return ret; +} +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef WOLFSSL_HAVE_MLKEM /* The cache import below always requests a max-size slot, so a build whose big * cache buffer cannot hold one has no working ML-KEM cache keygen or import. */ @@ -5538,40 +5635,141 @@ static int _HandleMlDsaCheckPrivKey(whServerContext* ctx, uint16_t magic, } #endif /* WOLFSSL_HAVE_MLDSA */ -#ifdef WOLFSSL_HAVE_MLKEM -static int _IsMlKemLevelSupported(int level) +#ifdef WOLFSSL_HAVE_SLHDSA +static int _IsSlhDsaParamSupported(int param) { - int ret = 0; + /* A parameter set with no table row cannot be initialized, so ask the + * library rather than tracking the build guards here. */ + return (wc_SlhDsaKey_SigSizeFromParam((enum SlhDsaParam)param) > 0); +} - switch (level) { -#ifndef WOLFSSL_NO_ML_KEM_512 - case WC_ML_KEM_512: - ret = 1; - break; -#endif -#ifndef WOLFSSL_NO_ML_KEM_768 - case WC_ML_KEM_768: - ret = 1; - break; -#endif -#ifndef WOLFSSL_NO_ML_KEM_1024 - case WC_ML_KEM_1024: - ret = 1; - break; -#endif - default: - ret = 0; - break; +/* Initialize a key to load a cached one into. req.param is the parameter set + * the client believes the key has; it is only a starting point, because the + * DER the key was cached as carries the real one and the decoder switches to + * it. It is deliberately not enforced: a caller holding nothing but a key id + * legitimately initializes its handle with a placeholder parameter set, so a + * mismatch here is not an error. */ +static int _SlhDsaInitForCachedKey(SlhDsaKey* key, uint32_t param, int devId) +{ + enum SlhDsaParam hint = (enum SlhDsaParam)param; + + if (0 == _IsSlhDsaParamSupported((int)param)) { + hint = WC_SLHDSA_DEFAULT_PARAM; + } + return wc_SlhDsaKey_Init(key, hint, NULL, devId); +} + +/* Load the signing or verifying key named by the request. */ +static int _SlhDsaLoadKey(whServerContext* ctx, whKeyId key_id, + whNvmFlags requiredUsage, SlhDsaKey* key) +{ + return _SlhDsaKeyCacheExportEnforce(ctx, key_id, requiredUsage, key); +} + +/* Sign with whichever FIPS 205 entry point the request selected. */ +static int _SlhDsaSignDispatch(whServerContext* ctx, SlhDsaKey* key, + uint32_t options, const byte* in, word32 in_len, + const byte* context, uint32_t contextSz, + uint32_t preHashType, const byte* addRnd, + uint32_t addRndSz, byte* sig, word32* sigLen) +{ + int ret; + int mprime = !!(options & WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_MPRIME); + int randomized = !!(options & + WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_RANDOMIZED); + + if (mprime) { + if (addRndSz > 0) { + ret = wc_SlhDsaKey_SignMsgWithRandom(key, in, in_len, sig, sigLen, + addRnd); + } + else { + /* The randomizer is the key's own PK.seed, which only the server + * copy of the key has. */ + ret = wc_SlhDsaKey_SignMsgDeterministic(key, in, in_len, sig, + sigLen); + } + } + else if (preHashType != WC_HASH_TYPE_NONE) { + if (addRndSz > 0) { + ret = wc_SlhDsaKey_SignHashWithRandom( + key, context, (byte)contextSz, in, in_len, + (enum wc_HashType)preHashType, sig, sigLen, addRnd); + } + else if (randomized) { + ret = wc_SlhDsaKey_SignHash(key, context, (byte)contextSz, in, + in_len, (enum wc_HashType)preHashType, + sig, sigLen, ctx->crypto->rng); + } + else { + ret = wc_SlhDsaKey_SignHashDeterministic( + key, context, (byte)contextSz, in, in_len, + (enum wc_HashType)preHashType, sig, sigLen); + } + } + else { + if (addRndSz > 0) { + ret = wc_SlhDsaKey_SignWithRandom(key, context, (byte)contextSz, in, + in_len, sig, sigLen, addRnd); + } + else if (randomized) { + ret = wc_SlhDsaKey_Sign(key, context, (byte)contextSz, in, in_len, + sig, sigLen, ctx->crypto->rng); + } + else { + ret = wc_SlhDsaKey_SignDeterministic(key, context, (byte)contextSz, + in, in_len, sig, sigLen); + } } return ret; } -static int _HandleMlKemKeyGen(whServerContext* ctx, uint16_t magic, int devId, - const void* cryptoDataIn, uint16_t inSize, - void* cryptoDataOut, uint16_t* outSize) +/* Verify with whichever FIPS 205 entry point the request selected. wolfCrypt + * reports a bad signature as an error code, but the client interface wants a + * boolean, so translate here. */ +static int _SlhDsaVerifyDispatch(SlhDsaKey* key, uint32_t options, + const byte* sig, word32 sig_len, + const byte* msg, word32 msg_len, + const byte* context, uint32_t contextSz, + uint32_t preHashType, int* out_result) { -#ifdef WOLFSSL_MLKEM_NO_MAKE_KEY + int ret; + int mprime = !!(options & WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_MPRIME); + + if (mprime) { + ret = wc_SlhDsaKey_VerifyMsg(key, msg, msg_len, sig, sig_len); + } + else if (preHashType != WC_HASH_TYPE_NONE) { + ret = wc_SlhDsaKey_VerifyHash(key, context, (byte)contextSz, msg, + msg_len, (enum wc_HashType)preHashType, + sig, sig_len); + } + else { + ret = wc_SlhDsaKey_Verify(key, context, (byte)contextSz, msg, msg_len, + sig, sig_len); + } + + if (ret == 0) { + *out_result = 1; + } + else if (ret == WC_NO_ERR_TRACE(SIG_VERIFY_E)) { + /* A signature that does not verify is a result, not a failure. Every + * other code, BAD_LENGTH_E in particular, describes a malformed + * request and is propagated so the caller can tell the two apart the + * same way a software-only build would. */ + *out_result = 0; + ret = 0; + } + + return ret; +} + +static int _HandleSlhDsaKeyGen(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ +#ifdef WOLFSSL_SLHDSA_VERIFY_ONLY (void)ctx; (void)magic; (void)devId; @@ -5581,46 +5779,84 @@ static int _HandleMlKemKeyGen(whServerContext* ctx, uint16_t magic, int devId, (void)outSize; return WH_ERROR_NOHANDLER; #else - int ret = WH_ERROR_OK; - MlKemKey key[1]; - whMessageCrypto_MlKemKeyGenRequest req; - whMessageCrypto_MlKemKeyGenResponse res; - uint16_t res_size = 0; - uint8_t* res_out; - uint16_t max_size; - whKeyId key_id; - uint16_t label_size = WH_NVM_LABEL_LEN; + int ret = WH_ERROR_OK; + SlhDsaKey key[1]; + whMessageCrypto_SlhDsaKeyGenRequest req; + whMessageCrypto_SlhDsaKeyGenResponse res; + whKeyId key_id; + whNvmFlags flags; + uint8_t* label; + const byte* seed; + uint32_t seedSz; + uint8_t* res_out; + uint16_t max_size; + uint16_t res_size = 0; + int param; - if (inSize < sizeof(whMessageCrypto_MlKemKeyGenRequest)) { + if (inSize < sizeof(whMessageCrypto_SlhDsaKeyGenRequest)) { return WH_ERROR_BADARGS; } - ret = wh_MessageCrypto_TranslateMlKemKeyGenRequest( - magic, (whMessageCrypto_MlKemKeyGenRequest*)cryptoDataIn, &req); + /* Translate the request */ + ret = wh_MessageCrypto_TranslateSlhDsaKeyGenRequest( + magic, (whMessageCrypto_SlhDsaKeyGenRequest*)cryptoDataIn, &req); if (ret != 0) { return ret; } - key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, - req.keyId); - res_out = (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_MlKemKeyGenResponse); + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + param = (int)req.param; + flags = req.flags; + label = req.label; + seedSz = req.seedSz; + seed = (const byte*)cryptoDataIn + + sizeof(whMessageCrypto_SlhDsaKeyGenRequest); + + if (seedSz > (uint32_t)(inSize - + sizeof(whMessageCrypto_SlhDsaKeyGenRequest))) { + return WH_ERROR_BADARGS; + } + + /* Response message. cryptoDataOut already points past the generic + * response header, so that header comes out of the budget too. */ + res_out = + (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_SlhDsaKeyGenResponse); max_size = (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN - - (res_out - (uint8_t*)cryptoDataOut)); + sizeof(whMessageCrypto_GenericResponseHeader) - + sizeof(whMessageCrypto_SlhDsaKeyGenResponse)); - if (!_IsMlKemLevelSupported((int)req.level)) { + if (0 == _IsSlhDsaParamSupported(param)) { return WH_ERROR_BADARGS; } - ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId); + ret = wc_SlhDsaKey_Init(key, (enum SlhDsaParam)param, NULL, devId); if (ret == 0) { - ret = wc_MlKemKey_MakeKey(key, ctx->crypto->rng); + if (seedSz > 0) { + /* The seed is the contiguous SK.seed || SK.prf || PK.seed */ + word32 n = seedSz / 3; + if ((seedSz % 3) != 0) { + ret = WH_ERROR_BADARGS; + } + else { + ret = wc_SlhDsaKey_MakeKeyWithRandom(key, seed, n, seed + n, n, + seed + 2 * n, n); + } + } + else { + ret = wc_SlhDsaKey_MakeKey(key, ctx->crypto->rng); + } + if (ret == 0) { - if ((req.flags & WH_NVM_FLAGS_EPHEMERAL) != 0) { + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + /* Must serialize the key into the response message. */ key_id = WH_KEYID_ERASED; - ret = wh_Crypto_MlKemSerializeKey(key, max_size, res_out, - &res_size); + ret = wh_Crypto_SlhDsaSerializeKeyDer(key, max_size, res_out, + &res_size); } else { + /* Must import the key into the cache and return keyid */ + res_size = 0; /* Hold the NVM lock so id allocation and cache import are * atomic with respect to other server contexts under * THREADSAFE. */ @@ -5630,53 +5866,42 @@ static int _HandleMlKemKeyGen(whServerContext* ctx, uint16_t magic, int devId, ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id); } if (ret == WH_ERROR_OK) { - ret = wh_Server_MlKemKeyCacheImport( - ctx, key, key_id, req.flags, label_size, req.label); + ret = wh_Server_SlhDsaKeyCacheImport( + ctx, key, key_id, flags, WH_NVM_LABEL_LEN, label); } (void)WH_SERVER_NVM_UNLOCK(ctx); } /* WH_SERVER_NVM_LOCK() */ - if (ret == WH_ERROR_OK) { - /* Best-effort public key export: when the serialized - * public key fits in the response body, return it so the - * client can skip a separate ExportPublicKey call. When it - * does not fit (small comm buffer or a large key), leave the - * body empty and keep the cached key. Plain MakeCacheKey - * callers ignore the body and see no regression; - * MakeCacheKeyAndExportPublic callers detect the empty body - * and evict the key themselves. */ - word32 pubSize = 0; - if ((wc_MlKemKey_PublicKeySize(key, &pubSize) == 0) && - ((uint32_t)pubSize <= (uint32_t)max_size) && - (wc_MlKemKey_EncodePublicKey(key, res_out, pubSize) == - 0)) { - res_size = (uint16_t)pubSize; - } - else { - res_size = 0; - } + if (ret == 0) { + /* Best-effort public key export so the client can skip a + * separate ExportPublicKey call. An empty body is not an + * error; MakeCacheKeyAndExportPublic callers detect it. */ + int pub_ret = + wc_SlhDsaKey_PublicKeyToDer(key, res_out, max_size, 1); + res_size = (pub_ret > 0) ? (uint16_t)pub_ret : 0; } } } - wc_MlKemKey_Free(key); + wc_SlhDsaKey_Free(key); } if (ret == WH_ERROR_OK) { res.keyId = wh_KeyId_TranslateToClient(key_id); res.len = res_size; - (void)wh_MessageCrypto_TranslateMlKemKeyGenResponse( - magic, &res, (whMessageCrypto_MlKemKeyGenResponse*)cryptoDataOut); - *outSize = sizeof(whMessageCrypto_MlKemKeyGenResponse) + res_size; - } + wh_MessageCrypto_TranslateSlhDsaKeyGenResponse(magic, &res, + cryptoDataOut); + + *outSize = sizeof(whMessageCrypto_SlhDsaKeyGenResponse) + res_size; + } return ret; -#endif /* WOLFSSL_MLKEM_NO_MAKE_KEY */ +#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */ } -static int _HandleMlKemEncaps(whServerContext* ctx, uint16_t magic, int devId, - const void* cryptoDataIn, uint16_t inSize, - void* cryptoDataOut, uint16_t* outSize) +static int _HandleSlhDsaSign(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) { -#ifdef WOLFSSL_MLKEM_NO_ENCAPSULATE +#ifdef WOLFSSL_SLHDSA_VERIFY_ONLY (void)ctx; (void)magic; (void)devId; @@ -5686,100 +5911,202 @@ static int _HandleMlKemEncaps(whServerContext* ctx, uint16_t magic, int devId, (void)outSize; return WH_ERROR_NOHANDLER; #else - int ret = WH_ERROR_OK; - MlKemKey key[1]; - whMessageCrypto_MlKemEncapsRequest req; - whMessageCrypto_MlKemEncapsResponse res; - whKeyId key_id; - uint8_t* res_ct; - uint8_t* res_ss; - word32 ct_len; - word32 ss_len; - word32 max_out; - int evict = 0; - int keyInited = 0; - - if (inSize < sizeof(whMessageCrypto_MlKemEncapsRequest)) { + int ret; + SlhDsaKey key[1]; + whMessageCrypto_SlhDsaSignRequest req; + whMessageCrypto_SlhDsaSignResponse res; + byte* in; + byte* req_context; + byte* req_addRnd; + byte* res_out; + whKeyId key_id; + word32 in_len; + word32 available; + word32 max_len; + word32 res_len; + uint32_t contextSz; + uint32_t preHashType; + uint32_t addRndSz; + uint32_t options; + int evict; + int sigLen; + + if (inSize < sizeof(whMessageCrypto_SlhDsaSignRequest)) { return WH_ERROR_BADARGS; } - ret = wh_MessageCrypto_TranslateMlKemEncapsRequest( - magic, (whMessageCrypto_MlKemEncapsRequest*)cryptoDataIn, &req); + /* Translate the request */ + ret = wh_MessageCrypto_TranslateSlhDsaSignRequest( + magic, (whMessageCrypto_SlhDsaSignRequest*)cryptoDataIn, &req); if (ret != 0) { return ret; } - key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, - req.keyId); - evict = !!(req.options & WH_MESSAGE_CRYPTO_MLKEM_ENCAPS_OPTIONS_EVICT); + in = (uint8_t*)(cryptoDataIn) + sizeof(whMessageCrypto_SlhDsaSignRequest); + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + in_len = req.sz; + contextSz = req.contextSz; + preHashType = req.preHashType; + addRndSz = req.addRndSz; + options = req.options; + evict = !!(options & WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_EVICT); - if (!_IsMlKemLevelSupported((int)req.level)) { - ret = WH_ERROR_BADARGS; - goto cleanup; + /* Validate the declared lengths against the remaining payload */ + available = inSize - sizeof(whMessageCrypto_SlhDsaSignRequest); + if (in_len > available) { + return WH_ERROR_BADARGS; } - - ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId); - if (ret == 0) { - keyInited = 1; - /* Export the key, enforcing the derive usage policy against the same - * locked snapshot that is exported */ - ret = _MlKemKeyCacheExportEnforce(ctx, key_id, - WH_NVM_FLAGS_USAGE_DERIVE, key); + if (contextSz > (available - in_len)) { + return WH_ERROR_BADARGS; } - - /* Verify the exported key matches the requested level */ - if (ret == WH_ERROR_OK && key->type != (int)req.level) { - ret = WH_ERROR_BADARGS; + if (addRndSz > (available - in_len - contextSz)) { + return WH_ERROR_BADARGS; + } + if (contextSz > WH_CRYPTO_SLHDSA_MAX_CTX_LEN) { + return WH_ERROR_BADARGS; } + req_context = (contextSz > 0) ? (in + in_len) : NULL; + req_addRnd = (addRndSz > 0) ? (in + in_len + contextSz) : NULL; - if (ret == WH_ERROR_OK) { - ret = wc_MlKemKey_CipherTextSize(key, &ct_len); + /* Response message. cryptoDataOut already points past the generic + * response header, so that header comes out of the budget too. */ + res_out = + (uint8_t*)(cryptoDataOut) + sizeof(whMessageCrypto_SlhDsaSignResponse); + max_len = (word32)(WOLFHSM_CFG_COMM_DATA_LEN - + sizeof(whMessageCrypto_GenericResponseHeader) - + sizeof(whMessageCrypto_SlhDsaSignResponse)); + res_len = max_len; + + ret = _SlhDsaInitForCachedKey(key, req.param, devId); + if (ret == 0) { + ret = _SlhDsaLoadKey(ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, key); if (ret == WH_ERROR_OK) { - ret = wc_MlKemKey_SharedSecretSize(key, &ss_len); + /* SLH-DSA signatures run from 7856 to 49856 bytes, so most + * parameter sets cannot be returned through the comm buffer at + * all. Report that up front rather than as a wolfCrypt length + * error from inside the sign call. */ + sigLen = wc_SlhDsaKey_SigSize(key); + if (sigLen <= 0) { + ret = WH_ERROR_ABORTED; + } + else if ((word32)sigLen > max_len) { + ret = WH_ERROR_BUFFER_SIZE; + } + } + if (ret == WH_ERROR_OK) { + ret = _SlhDsaSignDispatch(ctx, key, options, in, in_len, + req_context, contextSz, preHashType, + req_addRnd, addRndSz, res_out, &res_len); } + wc_SlhDsaKey_Free(key); + } + if (evict != 0) { + /* User requested to evict from cache, even if the call failed */ + _CryptoEvictKeyLocked(ctx, key_id); } + if (ret == 0) { + res.sz = res_len; - if (ret == WH_ERROR_OK) { - res_ct = (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_MlKemEncapsResponse); - res_ss = res_ct + ct_len; - max_out = (word32)(WOLFHSM_CFG_COMM_DATA_LEN - - ((uint8_t*)res_ct - (uint8_t*)cryptoDataOut)); - if (ct_len + ss_len > max_out) { - ret = WH_ERROR_BADARGS; - } + wh_MessageCrypto_TranslateSlhDsaSignResponse( + magic, &res, (whMessageCrypto_SlhDsaSignResponse*)cryptoDataOut); + + *outSize = sizeof(whMessageCrypto_SlhDsaSignResponse) + res_len; } + return ret; +#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */ +} - if (ret == WH_ERROR_OK) { - ret = wc_MlKemKey_Encapsulate(key, res_ct, res_ss, ctx->crypto->rng); - if (ret == WH_ERROR_OK) { - res.ctSz = ct_len; - res.ssSz = ss_len; - (void)wh_MessageCrypto_TranslateMlKemEncapsResponse( - magic, &res, (whMessageCrypto_MlKemEncapsResponse*)cryptoDataOut); - *outSize = sizeof(whMessageCrypto_MlKemEncapsResponse) + ct_len + ss_len; - } - else { - /* Zero sensitive data on failure */ - wc_ForceZero(res_ss, ss_len); - } +static int _HandleSlhDsaVerify(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ + int ret; + SlhDsaKey key[1]; + whMessageCrypto_SlhDsaVerifyRequest req; + whMessageCrypto_SlhDsaVerifyResponse res; + byte* req_sig; + byte* req_hash; + byte* req_context; + whKeyId key_id; + uint32_t options; + uint32_t hash_len; + uint32_t sig_len; + uint32_t contextSz; + uint32_t preHashType; + uint32_t available; + int evict; + int result = 0; + + if (inSize < sizeof(whMessageCrypto_SlhDsaVerifyRequest)) { + return WH_ERROR_BADARGS; } - if (keyInited) { - wc_MlKemKey_Free(key); + /* Translate the request */ + ret = wh_MessageCrypto_TranslateSlhDsaVerifyRequest( + magic, (whMessageCrypto_SlhDsaVerifyRequest*)cryptoDataIn, &req); + if (ret != 0) { + return ret; + } + + options = req.options; + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + hash_len = req.hashSz; + sig_len = req.sigSz; + contextSz = req.contextSz; + preHashType = req.preHashType; + req_sig = + (uint8_t*)(cryptoDataIn) + sizeof(whMessageCrypto_SlhDsaVerifyRequest); + evict = !!(options & WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_EVICT); + + /* Validate lengths against available payload (overflow-safe) */ + available = inSize - sizeof(whMessageCrypto_SlhDsaVerifyRequest); + if ((sig_len > available) || (hash_len > available) || + (sig_len > (available - hash_len))) { + return WH_ERROR_BADARGS; + } + if (contextSz > (available - sig_len - hash_len)) { + return WH_ERROR_BADARGS; + } + if (contextSz > WH_CRYPTO_SLHDSA_MAX_CTX_LEN) { + return WH_ERROR_BADARGS; + } + + req_hash = req_sig + sig_len; + req_context = (contextSz > 0) ? (req_hash + hash_len) : NULL; + + ret = _SlhDsaInitForCachedKey(key, req.param, devId); + if (ret == 0) { + ret = _SlhDsaLoadKey(ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, key); + if (ret == WH_ERROR_OK) { + ret = _SlhDsaVerifyDispatch(key, options, req_sig, sig_len, + req_hash, hash_len, req_context, + contextSz, preHashType, &result); + } + wc_SlhDsaKey_Free(key); } -cleanup: if (evict != 0) { + /* User requested to evict from cache, even if the call failed */ _CryptoEvictKeyLocked(ctx, key_id); } + if (ret == 0) { + res.res = result; + + wh_MessageCrypto_TranslateSlhDsaVerifyResponse( + magic, &res, (whMessageCrypto_SlhDsaVerifyResponse*)cryptoDataOut); + + *outSize = sizeof(whMessageCrypto_SlhDsaVerifyResponse); + } return ret; -#endif /* WOLFSSL_MLKEM_NO_ENCAPSULATE */ } -static int _HandleMlKemDecaps(whServerContext* ctx, uint16_t magic, int devId, - const void* cryptoDataIn, uint16_t inSize, - void* cryptoDataOut, uint16_t* outSize) +static int _HandleSlhDsaCheckPrivKey(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) { -#ifdef WOLFSSL_MLKEM_NO_DECAPSULATE +#ifdef WOLFSSL_SLHDSA_VERIFY_ONLY (void)ctx; (void)magic; (void)devId; @@ -5789,162 +6116,523 @@ static int _HandleMlKemDecaps(whServerContext* ctx, uint16_t magic, int devId, (void)outSize; return WH_ERROR_NOHANDLER; #else - int ret = WH_ERROR_OK; - MlKemKey key[1]; - whMessageCrypto_MlKemDecapsRequest req; - whMessageCrypto_MlKemDecapsResponse res; - whKeyId key_id; - byte* req_ct; - byte* res_ss; - uint32_t available; - word32 ss_len; - word32 max_out; - int evict = 0; - int keyInited = 0; + int ret; + SlhDsaKey key[1]; + whMessageCrypto_SlhDsaCheckPrivKeyRequest req; + whMessageCrypto_SlhDsaCheckPrivKeyResponse res; + byte* req_pub; + whKeyId key_id; + uint32_t pubSz; + uint32_t available; + int evict; + int result = 0; - if (inSize < sizeof(whMessageCrypto_MlKemDecapsRequest)) { + if (inSize < sizeof(whMessageCrypto_SlhDsaCheckPrivKeyRequest)) { return WH_ERROR_BADARGS; } - ret = wh_MessageCrypto_TranslateMlKemDecapsRequest( - magic, (whMessageCrypto_MlKemDecapsRequest*)cryptoDataIn, &req); + ret = wh_MessageCrypto_TranslateSlhDsaCheckPrivKeyRequest( + magic, (whMessageCrypto_SlhDsaCheckPrivKeyRequest*)cryptoDataIn, &req); if (ret != 0) { return ret; } - key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, - req.keyId); - evict = !!(req.options & WH_MESSAGE_CRYPTO_MLKEM_DECAPS_OPTIONS_EVICT); - - if (!_IsMlKemLevelSupported((int)req.level)) { - ret = WH_ERROR_BADARGS; - goto cleanup; - } - - available = inSize - sizeof(whMessageCrypto_MlKemDecapsRequest); - if (req.ctSz > available) { - ret = WH_ERROR_BADARGS; - goto cleanup; - } - req_ct = (byte*)cryptoDataIn + sizeof(whMessageCrypto_MlKemDecapsRequest); - - ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId); - if (ret == WH_ERROR_OK) { - keyInited = 1; - /* Export the key, enforcing the derive usage policy against the same - * locked snapshot that is exported */ - ret = _MlKemKeyCacheExportEnforce(ctx, key_id, - WH_NVM_FLAGS_USAGE_DERIVE, key); - } - - /* Verify the exported key matches the requested level */ - if (ret == WH_ERROR_OK && key->type != (int)req.level) { - ret = WH_ERROR_BADARGS; - } - - if (ret == WH_ERROR_OK) { - ret = wc_MlKemKey_SharedSecretSize(key, &ss_len); - } + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + pubSz = req.pubSz; + evict = !!(req.options & + WH_MESSAGE_CRYPTO_SLHDSA_CHECKPRIVKEY_OPTIONS_EVICT); + req_pub = (uint8_t*)(cryptoDataIn) + + sizeof(whMessageCrypto_SlhDsaCheckPrivKeyRequest); - if (ret == WH_ERROR_OK) { - res_ss = (byte*)cryptoDataOut + sizeof(whMessageCrypto_MlKemDecapsResponse); - max_out = (word32)(WOLFHSM_CFG_COMM_DATA_LEN - - ((uint8_t*)res_ss - (uint8_t*)cryptoDataOut)); - if (ss_len > max_out) { - ret = WH_ERROR_BADARGS; - } + available = inSize - sizeof(whMessageCrypto_SlhDsaCheckPrivKeyRequest); + if (pubSz > available) { + return WH_ERROR_BADARGS; } - if (ret == WH_ERROR_OK) { - ret = wc_MlKemKey_Decapsulate(key, res_ss, req_ct, req.ctSz); + ret = _SlhDsaInitForCachedKey(key, req.param, devId); + if (ret == 0) { + ret = _SlhDsaLoadKey(ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, key); if (ret == WH_ERROR_OK) { - res.ssSz = ss_len; - (void)wh_MessageCrypto_TranslateMlKemDecapsResponse( - magic, &res, (whMessageCrypto_MlKemDecapsResponse*)cryptoDataOut); - *outSize = sizeof(whMessageCrypto_MlKemDecapsResponse) + ss_len; + /* Recompute the public root from the private seeds */ + ret = wc_SlhDsaKey_CheckKey(key); } - else { - /* Zero sensitive data on failure */ - wc_ForceZero(res_ss, ss_len); + if (ret == WH_ERROR_OK) { + /* PK.seed || PK.root sits at the end of the key data. An absent + * public key means the caller had none to offer, so the + * consistency check above is the whole answer. */ + uint32_t expected = 2U * (uint32_t)key->params->n; + if (pubSz == 0) { + result = 1; + } + else if (pubSz != expected) { + result = 0; + } + else { + result = (memcmp(key->sk + 2 * key->params->n, req_pub, + pubSz) == 0); + } } + wc_SlhDsaKey_Free(key); } - - if (keyInited) { - wc_MlKemKey_Free(key); - } -cleanup: if (evict != 0) { _CryptoEvictKeyLocked(ctx, key_id); } + if (ret == 0) { + /* Mirror wc_SlhDsaKey_CheckKey: 0 on match, WC_KEY_MISMATCH_E on not */ + res.res = (result != 0) ? 0 : WC_KEY_MISMATCH_E; + + wh_MessageCrypto_TranslateSlhDsaCheckPrivKeyResponse( + magic, &res, + (whMessageCrypto_SlhDsaCheckPrivKeyResponse*)cryptoDataOut); + + *outSize = sizeof(whMessageCrypto_SlhDsaCheckPrivKeyResponse); + } return ret; -#endif /* WOLFSSL_MLKEM_NO_DECAPSULATE */ +#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */ } -#endif /* WOLFSSL_HAVE_MLKEM */ +#endif /* WOLFSSL_HAVE_SLHDSA */ -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) -static int _HandlePqcSigAlgorithm(whServerContext* ctx, uint16_t magic, - int devId, const void* cryptoDataIn, - uint16_t cryptoInSize, void* cryptoDataOut, - uint16_t* cryptoOutSize, uint32_t pkAlgoType, - uint32_t pqAlgoType) +#ifdef WOLFSSL_HAVE_MLKEM +static int _IsMlKemLevelSupported(int level) { - int ret = WH_ERROR_NOHANDLER; + int ret = 0; - /* Dispatch the appropriate algorithm handler based on the requested PK type - * and the algorithm type. */ - switch (pqAlgoType) { -#ifdef WOLFSSL_HAVE_MLDSA - case WC_PQC_SIG_TYPE_MLDSA: { - switch (pkAlgoType) { - case WC_PK_TYPE_PQC_SIG_KEYGEN: - ret = _HandleMlDsaKeyGen(ctx, magic, devId, cryptoDataIn, - cryptoInSize, cryptoDataOut, - cryptoOutSize); - break; - case WC_PK_TYPE_PQC_SIG_SIGN: - ret = _HandleMlDsaSign(ctx, magic, devId, cryptoDataIn, - cryptoInSize, cryptoDataOut, - cryptoOutSize); - break; - case WC_PK_TYPE_PQC_SIG_VERIFY: - ret = _HandleMlDsaVerify(ctx, magic, devId, cryptoDataIn, - cryptoInSize, cryptoDataOut, - cryptoOutSize); - break; - case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: - ret = _HandleMlDsaCheckPrivKey( - ctx, magic, devId, cryptoDataIn, cryptoInSize, - cryptoDataOut, cryptoOutSize); - break; - default: - ret = WH_ERROR_NOHANDLER; - break; - } - } break; -#endif /* WOLFSSL_HAVE_MLDSA */ + switch (level) { +#ifndef WOLFSSL_NO_ML_KEM_512 + case WC_ML_KEM_512: + ret = 1; + break; +#endif +#ifndef WOLFSSL_NO_ML_KEM_768 + case WC_ML_KEM_768: + ret = 1; + break; +#endif +#ifndef WOLFSSL_NO_ML_KEM_1024 + case WC_ML_KEM_1024: + ret = 1; + break; +#endif default: - ret = WH_ERROR_NOHANDLER; + ret = 0; break; } return ret; } -#endif -#if defined(WOLFSSL_HAVE_MLKEM) -static int _HandlePqcKemAlgorithm(whServerContext* ctx, uint16_t magic, - int devId, const void* cryptoDataIn, - uint16_t cryptoInSize, void* cryptoDataOut, - uint16_t* cryptoOutSize, uint32_t pkAlgoType, - uint32_t pqAlgoType) +static int _HandleMlKemKeyGen(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) { - int ret = WH_ERROR_NOHANDLER; - - switch (pqAlgoType) { - case WC_PQC_KEM_TYPE_KYBER: { - switch (pkAlgoType) { - case WC_PK_TYPE_PQC_KEM_KEYGEN: - ret = _HandleMlKemKeyGen(ctx, magic, devId, cryptoDataIn, +#ifdef WOLFSSL_MLKEM_NO_MAKE_KEY + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret = WH_ERROR_OK; + MlKemKey key[1]; + whMessageCrypto_MlKemKeyGenRequest req; + whMessageCrypto_MlKemKeyGenResponse res; + uint16_t res_size = 0; + uint8_t* res_out; + uint16_t max_size; + whKeyId key_id; + uint16_t label_size = WH_NVM_LABEL_LEN; + + if (inSize < sizeof(whMessageCrypto_MlKemKeyGenRequest)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateMlKemKeyGenRequest( + magic, (whMessageCrypto_MlKemKeyGenRequest*)cryptoDataIn, &req); + if (ret != 0) { + return ret; + } + + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, + req.keyId); + res_out = (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_MlKemKeyGenResponse); + max_size = (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN - + (res_out - (uint8_t*)cryptoDataOut)); + + if (!_IsMlKemLevelSupported((int)req.level)) { + return WH_ERROR_BADARGS; + } + + ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId); + if (ret == 0) { + ret = wc_MlKemKey_MakeKey(key, ctx->crypto->rng); + if (ret == 0) { + if ((req.flags & WH_NVM_FLAGS_EPHEMERAL) != 0) { + key_id = WH_KEYID_ERASED; + ret = wh_Crypto_MlKemSerializeKey(key, max_size, res_out, + &res_size); + } + else { + /* Hold the NVM lock so id allocation and cache import are + * atomic with respect to other server contexts under + * THREADSAFE. */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + if (WH_KEYID_ISERASED(key_id)) { + ret = wh_Server_KeystoreGetUniqueId(ctx, &key_id); + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_MlKemKeyCacheImport( + ctx, key, key_id, req.flags, label_size, req.label); + } + (void)WH_SERVER_NVM_UNLOCK(ctx); + } /* WH_SERVER_NVM_LOCK() */ + if (ret == WH_ERROR_OK) { + /* Best-effort public key export: when the serialized + * public key fits in the response body, return it so the + * client can skip a separate ExportPublicKey call. When it + * does not fit (small comm buffer or a large key), leave the + * body empty and keep the cached key. Plain MakeCacheKey + * callers ignore the body and see no regression; + * MakeCacheKeyAndExportPublic callers detect the empty body + * and evict the key themselves. */ + word32 pubSize = 0; + if ((wc_MlKemKey_PublicKeySize(key, &pubSize) == 0) && + ((uint32_t)pubSize <= (uint32_t)max_size) && + (wc_MlKemKey_EncodePublicKey(key, res_out, pubSize) == + 0)) { + res_size = (uint16_t)pubSize; + } + else { + res_size = 0; + } + } + } + } + wc_MlKemKey_Free(key); + } + + if (ret == WH_ERROR_OK) { + res.keyId = wh_KeyId_TranslateToClient(key_id); + res.len = res_size; + (void)wh_MessageCrypto_TranslateMlKemKeyGenResponse( + magic, &res, (whMessageCrypto_MlKemKeyGenResponse*)cryptoDataOut); + *outSize = sizeof(whMessageCrypto_MlKemKeyGenResponse) + res_size; + } + + return ret; +#endif /* WOLFSSL_MLKEM_NO_MAKE_KEY */ +} + +static int _HandleMlKemEncaps(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ +#ifdef WOLFSSL_MLKEM_NO_ENCAPSULATE + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret = WH_ERROR_OK; + MlKemKey key[1]; + whMessageCrypto_MlKemEncapsRequest req; + whMessageCrypto_MlKemEncapsResponse res; + whKeyId key_id; + uint8_t* res_ct; + uint8_t* res_ss; + word32 ct_len; + word32 ss_len; + word32 max_out; + int evict = 0; + int keyInited = 0; + + if (inSize < sizeof(whMessageCrypto_MlKemEncapsRequest)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateMlKemEncapsRequest( + magic, (whMessageCrypto_MlKemEncapsRequest*)cryptoDataIn, &req); + if (ret != 0) { + return ret; + } + + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, + req.keyId); + evict = !!(req.options & WH_MESSAGE_CRYPTO_MLKEM_ENCAPS_OPTIONS_EVICT); + + if (!_IsMlKemLevelSupported((int)req.level)) { + ret = WH_ERROR_BADARGS; + goto cleanup; + } + + ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId); + if (ret == 0) { + keyInited = 1; + /* Export the key, enforcing the derive usage policy against the same + * locked snapshot that is exported */ + ret = _MlKemKeyCacheExportEnforce(ctx, key_id, + WH_NVM_FLAGS_USAGE_DERIVE, key); + } + + /* Verify the exported key matches the requested level */ + if (ret == WH_ERROR_OK && key->type != (int)req.level) { + ret = WH_ERROR_BADARGS; + } + + if (ret == WH_ERROR_OK) { + ret = wc_MlKemKey_CipherTextSize(key, &ct_len); + if (ret == WH_ERROR_OK) { + ret = wc_MlKemKey_SharedSecretSize(key, &ss_len); + } + } + + if (ret == WH_ERROR_OK) { + res_ct = (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_MlKemEncapsResponse); + res_ss = res_ct + ct_len; + max_out = (word32)(WOLFHSM_CFG_COMM_DATA_LEN - + ((uint8_t*)res_ct - (uint8_t*)cryptoDataOut)); + if (ct_len + ss_len > max_out) { + ret = WH_ERROR_BADARGS; + } + } + + if (ret == WH_ERROR_OK) { + ret = wc_MlKemKey_Encapsulate(key, res_ct, res_ss, ctx->crypto->rng); + if (ret == WH_ERROR_OK) { + res.ctSz = ct_len; + res.ssSz = ss_len; + (void)wh_MessageCrypto_TranslateMlKemEncapsResponse( + magic, &res, (whMessageCrypto_MlKemEncapsResponse*)cryptoDataOut); + *outSize = sizeof(whMessageCrypto_MlKemEncapsResponse) + ct_len + ss_len; + } + else { + /* Zero sensitive data on failure */ + wc_ForceZero(res_ss, ss_len); + } + } + + if (keyInited) { + wc_MlKemKey_Free(key); + } +cleanup: + if (evict != 0) { + _CryptoEvictKeyLocked(ctx, key_id); + } + return ret; +#endif /* WOLFSSL_MLKEM_NO_ENCAPSULATE */ +} + +static int _HandleMlKemDecaps(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ +#ifdef WOLFSSL_MLKEM_NO_DECAPSULATE + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret = WH_ERROR_OK; + MlKemKey key[1]; + whMessageCrypto_MlKemDecapsRequest req; + whMessageCrypto_MlKemDecapsResponse res; + whKeyId key_id; + byte* req_ct; + byte* res_ss; + uint32_t available; + word32 ss_len; + word32 max_out; + int evict = 0; + int keyInited = 0; + + if (inSize < sizeof(whMessageCrypto_MlKemDecapsRequest)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateMlKemDecapsRequest( + magic, (whMessageCrypto_MlKemDecapsRequest*)cryptoDataIn, &req); + if (ret != 0) { + return ret; + } + + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, + req.keyId); + evict = !!(req.options & WH_MESSAGE_CRYPTO_MLKEM_DECAPS_OPTIONS_EVICT); + + if (!_IsMlKemLevelSupported((int)req.level)) { + ret = WH_ERROR_BADARGS; + goto cleanup; + } + + available = inSize - sizeof(whMessageCrypto_MlKemDecapsRequest); + if (req.ctSz > available) { + ret = WH_ERROR_BADARGS; + goto cleanup; + } + req_ct = (byte*)cryptoDataIn + sizeof(whMessageCrypto_MlKemDecapsRequest); + + ret = wc_MlKemKey_Init(key, (int)req.level, NULL, devId); + if (ret == WH_ERROR_OK) { + keyInited = 1; + /* Export the key, enforcing the derive usage policy against the same + * locked snapshot that is exported */ + ret = _MlKemKeyCacheExportEnforce(ctx, key_id, + WH_NVM_FLAGS_USAGE_DERIVE, key); + } + + /* Verify the exported key matches the requested level */ + if (ret == WH_ERROR_OK && key->type != (int)req.level) { + ret = WH_ERROR_BADARGS; + } + + if (ret == WH_ERROR_OK) { + ret = wc_MlKemKey_SharedSecretSize(key, &ss_len); + } + + if (ret == WH_ERROR_OK) { + res_ss = (byte*)cryptoDataOut + sizeof(whMessageCrypto_MlKemDecapsResponse); + max_out = (word32)(WOLFHSM_CFG_COMM_DATA_LEN - + ((uint8_t*)res_ss - (uint8_t*)cryptoDataOut)); + if (ss_len > max_out) { + ret = WH_ERROR_BADARGS; + } + } + + if (ret == WH_ERROR_OK) { + ret = wc_MlKemKey_Decapsulate(key, res_ss, req_ct, req.ctSz); + if (ret == WH_ERROR_OK) { + res.ssSz = ss_len; + (void)wh_MessageCrypto_TranslateMlKemDecapsResponse( + magic, &res, (whMessageCrypto_MlKemDecapsResponse*)cryptoDataOut); + *outSize = sizeof(whMessageCrypto_MlKemDecapsResponse) + ss_len; + } + else { + /* Zero sensitive data on failure */ + wc_ForceZero(res_ss, ss_len); + } + } + + if (keyInited) { + wc_MlKemKey_Free(key); + } +cleanup: + if (evict != 0) { + _CryptoEvictKeyLocked(ctx, key_id); + } + return ret; +#endif /* WOLFSSL_MLKEM_NO_DECAPSULATE */ +} +#endif /* WOLFSSL_HAVE_MLKEM */ + +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) +static int _HandlePqcSigAlgorithm(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t cryptoInSize, void* cryptoDataOut, + uint16_t* cryptoOutSize, uint32_t pkAlgoType, + uint32_t pqAlgoType) +{ + int ret = WH_ERROR_NOHANDLER; + + /* Dispatch the appropriate algorithm handler based on the requested PK type + * and the algorithm type. */ + switch (pqAlgoType) { +#ifdef WOLFSSL_HAVE_MLDSA + case WC_PQC_SIG_TYPE_MLDSA: { + switch (pkAlgoType) { + case WC_PK_TYPE_PQC_SIG_KEYGEN: + ret = _HandleMlDsaKeyGen(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_SIGN: + ret = _HandleMlDsaSign(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_VERIFY: + ret = _HandleMlDsaVerify(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: + ret = _HandleMlDsaCheckPrivKey( + ctx, magic, devId, cryptoDataIn, cryptoInSize, + cryptoDataOut, cryptoOutSize); + break; + default: + ret = WH_ERROR_NOHANDLER; + break; + } + } break; +#endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PQC_SIG_TYPE_SLHDSA: { + switch (pkAlgoType) { + case WC_PK_TYPE_PQC_SIG_KEYGEN: + case WC_PK_TYPE_PQC_SIG_KEYGEN_SEEDED: + ret = _HandleSlhDsaKeyGen(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_SIGN: + case WC_PK_TYPE_PQC_SIG_SIGN_MSG: + ret = _HandleSlhDsaSign(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_VERIFY: + case WC_PK_TYPE_PQC_SIG_VERIFY_MSG: + ret = _HandleSlhDsaVerify(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: + ret = _HandleSlhDsaCheckPrivKey( + ctx, magic, devId, cryptoDataIn, cryptoInSize, + cryptoDataOut, cryptoOutSize); + break; + default: + ret = WH_ERROR_NOHANDLER; + break; + } + } break; +#endif /* WOLFSSL_HAVE_SLHDSA */ + default: + ret = WH_ERROR_NOHANDLER; + break; + } + + return ret; +} +#endif + +#if defined(WOLFSSL_HAVE_MLKEM) +static int _HandlePqcKemAlgorithm(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t cryptoInSize, void* cryptoDataOut, + uint16_t* cryptoOutSize, uint32_t pkAlgoType, + uint32_t pqAlgoType) +{ + int ret = WH_ERROR_NOHANDLER; + + switch (pqAlgoType) { + case WC_PQC_KEM_TYPE_KYBER: { + switch (pkAlgoType) { + case WC_PK_TYPE_PQC_KEM_KEYGEN: + ret = _HandleMlKemKeyGen(ctx, magic, devId, cryptoDataIn, cryptoInSize, cryptoDataOut, cryptoOutSize); break; @@ -6156,11 +6844,17 @@ int wh_Server_HandleCryptoRequest(whServerContext* ctx, uint16_t magic, break; #endif /* HAVE_ED25519 */ -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) case WC_PK_TYPE_PQC_SIG_KEYGEN: case WC_PK_TYPE_PQC_SIG_SIGN: case WC_PK_TYPE_PQC_SIG_VERIFY: case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PK_TYPE_PQC_SIG_KEYGEN_SEEDED: + case WC_PK_TYPE_PQC_SIG_SIGN_MSG: + case WC_PK_TYPE_PQC_SIG_VERIFY_MSG: +#endif ret = _HandlePqcSigAlgorithm( ctx, magic, devId, cryptoDataIn, cryptoInSize, cryptoDataOut, &cryptoOutSize, rqstHeader.algoType, @@ -6552,49 +7246,289 @@ static int _HandleSha384Dma(whServerContext* ctx, uint16_t magic, int devId, const uint8_t* inlineData; void* inAddr = NULL; - res.hashType = WC_HASH_TYPE_SHA384; + res.hashType = WC_HASH_TYPE_SHA384; + + if (inSize < sizeof(whMessageCrypto_Sha512DmaRequest)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateSha512DmaRequest( + magic, (const whMessageCrypto_Sha512DmaRequest*)cryptoDataIn, &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + if ((uint32_t)req.inSz > + (uint32_t)(inSize - sizeof(whMessageCrypto_Sha512DmaRequest))) { + return WH_ERROR_BADARGS; + } + /* Non-final: inline and DMA input must be multiples of block size */ + if (!req.isLastBlock && ((req.inSz % WC_SHA384_BLOCK_SIZE) != 0 || + (req.input.sz % WC_SHA384_BLOCK_SIZE) != 0)) { + return WH_ERROR_BADARGS; + } + /* Final: inline data must be less than one block, no DMA input */ + if (req.isLastBlock && + (req.inSz >= WC_SHA384_BLOCK_SIZE || req.input.sz != 0)) { + return WH_ERROR_BADARGS; + } + + inlineData = + (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha512DmaRequest); + + ret = wc_InitSha384_ex(sha384, NULL, devId); + if (ret != 0) { + return ret; + } + + /* SHA384 shares SHA512's internal 64-byte digest state */ + memcpy(sha384->digest, req.resumeState.hash, WC_SHA512_DIGEST_SIZE); + sha384->loLen = req.resumeState.loLen; + sha384->hiLen = req.resumeState.hiLen; + sha384->buffLen = 0; + + if (ret == 0 && req.inSz > 0) { + ret = wc_Sha384Update(sha384, inlineData, req.inSz); + } + + if (ret == 0 && req.input.sz > 0) { + ret = wh_Server_DmaProcessClientAddress( + ctx, req.input.addr, &inAddr, req.input.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret == WH_ERROR_OK) { + preOk = 1; + ret = wc_Sha384Update(sha384, inAddr, req.input.sz); + } + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.input; + } + } + /* Pair every successful PRE with a POST so DMA callbacks can release any + * resources they acquired, even if the Update failed. */ + if (preOk) { + (void)wh_Server_DmaProcessClientAddress( + ctx, req.input.addr, &inAddr, req.input.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + + if (ret == 0) { + if (req.isLastBlock) { + ret = wc_Sha384Final(sha384, res.hash); + } + else { + if (sha384->buffLen != 0) { + ret = WH_ERROR_ABORTED; + } + else { + memcpy(res.hash, sha384->digest, WC_SHA512_DIGEST_SIZE); + res.loLen = sha384->loLen; + res.hiLen = sha384->hiLen; + } + } + } + + (void)wh_MessageCrypto_TranslateSha2DmaResponse( + magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + + return ret; +} +#endif /* WOLFSSL_SHA384 */ + +#ifdef WOLFSSL_SHA512 +static int _HandleSha512Dma(whServerContext* ctx, uint16_t magic, int devId, + uint16_t seq, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ + (void)seq; + int ret = 0; + int preOk = 0; + whMessageCrypto_Sha512DmaRequest req; + whMessageCrypto_Sha2DmaResponse res = {0}; + wc_Sha512 sha512[1]; + const uint8_t* inlineData; + void* inAddr = NULL; + int hashType; + + if (inSize < sizeof(whMessageCrypto_Sha512DmaRequest)) { + return WH_ERROR_BADARGS; + } + + ret = wh_MessageCrypto_TranslateSha512DmaRequest( + magic, (const whMessageCrypto_Sha512DmaRequest*)cryptoDataIn, &req); + if (ret != WH_ERROR_OK) { + return ret; + } + + if ((uint32_t)req.inSz > + (uint32_t)(inSize - sizeof(whMessageCrypto_Sha512DmaRequest))) { + return WH_ERROR_BADARGS; + } + /* Non-final: inline and DMA input must be multiples of block size */ + if (!req.isLastBlock && ((req.inSz % WC_SHA512_BLOCK_SIZE) != 0 || + (req.input.sz % WC_SHA512_BLOCK_SIZE) != 0)) { + return WH_ERROR_BADARGS; + } + /* Final: inline data must be less than one block, no DMA input */ + if (req.isLastBlock && + (req.inSz >= WC_SHA512_BLOCK_SIZE || req.input.sz != 0)) { + return WH_ERROR_BADARGS; + } + + inlineData = + (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha512DmaRequest); + hashType = req.resumeState.hashType; + + /* If the client requested a variant the server does not have compiled in, + * normalize hashType to plain SHA512 so the response reflects what was + * actually executed; the client detects the mismatch against its own + * hashType and returns an error. */ + switch (hashType) { +#ifndef WOLFSSL_NOSHA512_224 + case WC_HASH_TYPE_SHA512_224: + ret = wc_InitSha512_224_ex(sha512, NULL, devId); + break; +#endif +#ifndef WOLFSSL_NOSHA512_256 + case WC_HASH_TYPE_SHA512_256: + ret = wc_InitSha512_256_ex(sha512, NULL, devId); + break; +#endif + default: + ret = wc_InitSha512_ex(sha512, NULL, devId); + hashType = WC_HASH_TYPE_SHA512; + break; + } + if (ret != 0) { + return ret; + } + + res.hashType = hashType; + + memcpy(sha512->digest, req.resumeState.hash, WC_SHA512_DIGEST_SIZE); + sha512->loLen = req.resumeState.loLen; + sha512->hiLen = req.resumeState.hiLen; + sha512->buffLen = 0; + + if (ret == 0 && req.inSz > 0) { + ret = wc_Sha512Update(sha512, inlineData, req.inSz); + } + + if (ret == 0 && req.input.sz > 0) { + ret = wh_Server_DmaProcessClientAddress( + ctx, req.input.addr, &inAddr, req.input.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret == WH_ERROR_OK) { + preOk = 1; + ret = wc_Sha512Update(sha512, inAddr, req.input.sz); + } + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.input; + } + } + /* Pair every successful PRE with a POST so DMA callbacks can release any + * resources they acquired, even if the Update failed. */ + if (preOk) { + (void)wh_Server_DmaProcessClientAddress( + ctx, req.input.addr, &inAddr, req.input.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + + if (ret == 0) { + if (req.isLastBlock) { + switch (hashType) { +#ifndef WOLFSSL_NOSHA512_224 + case WC_HASH_TYPE_SHA512_224: + ret = wc_Sha512_224Final(sha512, res.hash); + break; +#endif +#ifndef WOLFSSL_NOSHA512_256 + case WC_HASH_TYPE_SHA512_256: + ret = wc_Sha512_256Final(sha512, res.hash); + break; +#endif + default: + ret = wc_Sha512Final(sha512, res.hash); + break; + } + } + else { + if (sha512->buffLen != 0) { + ret = WH_ERROR_ABORTED; + } + else { + memcpy(res.hash, sha512->digest, WC_SHA512_DIGEST_SIZE); + res.loLen = sha512->loLen; + res.hiLen = sha512->hiLen; + } + } + } + + (void)wh_MessageCrypto_TranslateSha2DmaResponse( + magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + + return ret; +} +#endif /* WOLFSSL_SHA512 */ + +#if defined(WOLFSSL_SHA3) +static int _HandleSha3Dma(whServerContext* ctx, int hashType, uint16_t magic, + int devId, uint16_t seq, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ + (void)seq; + int ret = 0; + int preOk = 0; + whMessageCrypto_Sha3DmaRequest req; + whMessageCrypto_Sha3DmaResponse res = {0}; + wc_Sha3 sha3[1]; + const uint8_t* inlineData; + void* inAddr = NULL; + _Sha3VariantOps ops; + + ret = _Sha3LookupOps(hashType, &ops); + if (ret != 0) { + return ret; + } - if (inSize < sizeof(whMessageCrypto_Sha512DmaRequest)) { + if (inSize < sizeof(whMessageCrypto_Sha3DmaRequest)) { return WH_ERROR_BADARGS; } - ret = wh_MessageCrypto_TranslateSha512DmaRequest( - magic, (const whMessageCrypto_Sha512DmaRequest*)cryptoDataIn, &req); + ret = wh_MessageCrypto_TranslateSha3DmaRequest( + magic, (const whMessageCrypto_Sha3DmaRequest*)cryptoDataIn, &req); if (ret != WH_ERROR_OK) { return ret; } if ((uint32_t)req.inSz > - (uint32_t)(inSize - sizeof(whMessageCrypto_Sha512DmaRequest))) { + (uint32_t)(inSize - sizeof(whMessageCrypto_Sha3DmaRequest))) { return WH_ERROR_BADARGS; } - /* Non-final: inline and DMA input must be multiples of block size */ - if (!req.isLastBlock && ((req.inSz % WC_SHA384_BLOCK_SIZE) != 0 || - (req.input.sz % WC_SHA384_BLOCK_SIZE) != 0)) { + if (!req.isLastBlock && ((req.inSz % ops.blockSize) != 0 || + (req.input.sz % ops.blockSize) != 0)) { return WH_ERROR_BADARGS; } - /* Final: inline data must be less than one block, no DMA input */ - if (req.isLastBlock && - (req.inSz >= WC_SHA384_BLOCK_SIZE || req.input.sz != 0)) { + if (req.isLastBlock && (req.inSz >= ops.blockSize || req.input.sz != 0)) { return WH_ERROR_BADARGS; } inlineData = - (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha512DmaRequest); + (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha3DmaRequest); - ret = wc_InitSha384_ex(sha384, NULL, devId); + ret = ops.initFn(sha3, NULL, devId); if (ret != 0) { return ret; } - /* SHA384 shares SHA512's internal 64-byte digest state */ - memcpy(sha384->digest, req.resumeState.hash, WC_SHA512_DIGEST_SIZE); - sha384->loLen = req.resumeState.loLen; - sha384->hiLen = req.resumeState.hiLen; - sha384->buffLen = 0; + /* Restore Keccak state from client. initFn already zeroed t[] and i. */ + memcpy(sha3->s, req.resumeState.s, sizeof(sha3->s)); if (ret == 0 && req.inSz > 0) { - ret = wc_Sha384Update(sha384, inlineData, req.inSz); + ret = ops.updateFn(sha3, inlineData, req.inSz); } if (ret == 0 && req.input.sz > 0) { @@ -6603,14 +7537,12 @@ static int _HandleSha384Dma(whServerContext* ctx, uint16_t magic, int devId, WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); if (ret == WH_ERROR_OK) { preOk = 1; - ret = wc_Sha384Update(sha384, inAddr, req.input.sz); + ret = ops.updateFn(sha3, inAddr, req.input.sz); } if (ret == WH_ERROR_ACCESS) { res.dmaAddrStatus.badAddr = req.input; } } - /* Pair every successful PRE with a POST so DMA callbacks can release any - * resources they acquired, even if the Update failed. */ if (preOk) { (void)wh_Server_DmaProcessClientAddress( ctx, req.input.addr, &inAddr, req.input.sz, @@ -6619,272 +7551,487 @@ static int _HandleSha384Dma(whServerContext* ctx, uint16_t magic, int devId, if (ret == 0) { if (req.isLastBlock) { - ret = wc_Sha384Final(sha384, res.hash); + ret = ops.finalFn(sha3, res.hash); } else { - if (sha384->buffLen != 0) { + if (sha3->i != 0) { ret = WH_ERROR_ABORTED; } else { - memcpy(res.hash, sha384->digest, WC_SHA512_DIGEST_SIZE); - res.loLen = sha384->loLen; - res.hiLen = sha384->hiLen; + memcpy(res.resumeState.s, sha3->s, sizeof(res.resumeState.s)); } } } - (void)wh_MessageCrypto_TranslateSha2DmaResponse( - magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut); + (void)wh_MessageCrypto_TranslateSha3DmaResponse( + magic, &res, (whMessageCrypto_Sha3DmaResponse*)cryptoDataOut); *outSize = sizeof(res); return ret; } -#endif /* WOLFSSL_SHA384 */ +#endif /* WOLFSSL_SHA3 */ -#ifdef WOLFSSL_SHA512 -static int _HandleSha512Dma(whServerContext* ctx, uint16_t magic, int devId, - uint16_t seq, const void* cryptoDataIn, - uint16_t inSize, void* cryptoDataOut, - uint16_t* outSize) +#if defined(WOLFSSL_HAVE_MLDSA) + +static int _HandleMlDsaKeyGenDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) { - (void)seq; - int ret = 0; - int preOk = 0; - whMessageCrypto_Sha512DmaRequest req; - whMessageCrypto_Sha2DmaResponse res = {0}; - wc_Sha512 sha512[1]; - const uint8_t* inlineData; - void* inAddr = NULL; - int hashType; +#ifdef WOLFSSL_MLDSA_NO_MAKE_KEY + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret = WH_ERROR_OK; + wc_MlDsaKey key[1]; + void* clientOutAddr = NULL; + uint16_t keySize = 0; - if (inSize < sizeof(whMessageCrypto_Sha512DmaRequest)) { + whMessageCrypto_MlDsaKeyGenDmaRequest req; + whMessageCrypto_MlDsaKeyGenDmaResponse res; + + memset(&res, 0, sizeof(res)); + + if (inSize < sizeof(whMessageCrypto_MlDsaKeyGenDmaRequest)) { return WH_ERROR_BADARGS; } - ret = wh_MessageCrypto_TranslateSha512DmaRequest( - magic, (const whMessageCrypto_Sha512DmaRequest*)cryptoDataIn, &req); + /* Translate the request */ + ret = wh_MessageCrypto_TranslateMlDsaKeyGenDmaRequest( + magic, (whMessageCrypto_MlDsaKeyGenDmaRequest*)cryptoDataIn, &req); if (ret != WH_ERROR_OK) { return ret; } - if ((uint32_t)req.inSz > - (uint32_t)(inSize - sizeof(whMessageCrypto_Sha512DmaRequest))) { - return WH_ERROR_BADARGS; + /* Check the ML-DSA security level is valid and supported */ + if (0 == _IsMlDsaLevelSupported(req.level)) { + ret = WH_ERROR_BADARGS; } - /* Non-final: inline and DMA input must be multiples of block size */ - if (!req.isLastBlock && ((req.inSz % WC_SHA512_BLOCK_SIZE) != 0 || - (req.input.sz % WC_SHA512_BLOCK_SIZE) != 0)) { - return WH_ERROR_BADARGS; + else { + /* init mldsa key */ + ret = wc_MlDsaKey_Init(key, NULL, devId); + if (ret == 0) { + /* Set the ML-DSA security level */ + ret = wc_MlDsaKey_SetParams(key, req.level); + if (ret == 0) { + /* generate the key */ + ret = wc_MlDsaKey_MakeKey(key, ctx->crypto->rng); + if (ret == 0) { + /* Check incoming flags */ + if (req.flags & WH_NVM_FLAGS_EPHEMERAL) { + /* Must serialize the key into client memory */ + ret = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, req.key.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, + (whServerDmaFlags){0}); + + if (ret == 0) { + ret = wh_Crypto_MlDsaSerializeKeyDer( + key, req.key.sz, clientOutAddr, &keySize); + if (ret == 0) { + res.keyId = WH_KEYID_ERASED; + res.keySize = keySize; + } + } + + if (ret == 0) { + ret = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, keySize, + WH_DMA_OPER_CLIENT_WRITE_POST, + (whServerDmaFlags){0}); + } + } + else { + /* Must import the key into the cache and return keyid + */ + whKeyId keyId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); + + /* Hold the NVM lock so id allocation and cache import + * are atomic with respect to other server contexts + * under THREADSAFE. */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + if (WH_KEYID_ISERASED(keyId)) { + /* Generate a new id */ + ret = + wh_Server_KeystoreGetUniqueId(ctx, &keyId); + WH_DEBUG_SERVER("UniqueId: keyId:%u, ret:%d\n", + keyId, ret); + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_MlDsaKeyCacheImport( + ctx, key, keyId, req.flags, req.labelSize, + req.label); + WH_DEBUG_SERVER( + "CacheImport: keyId:%u, ret:%d\n", keyId, + ret); + } + (void)WH_SERVER_NVM_UNLOCK(ctx); + } /* WH_SERVER_NVM_LOCK() */ +#ifdef WOLFSSL_MLDSA_PUBLIC_KEY + /* Stream the public key back through the client's DMA + * buffer so it gets the pubkey without a separate + * ExportPublicKey call. A freshly generated key must + * serialize, so treat a failure as fatal: evict the + * just-committed key and propagate the error rather + * than returning a keyId with no public key. */ + if (ret == 0) { + int rc = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, req.key.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, + (whServerDmaFlags){0}); + if (rc == 0) { + int pub_ret = wc_MlDsaKey_PublicKeyToDer( + key, (byte*)clientOutAddr, + (word32)req.key.sz, 1); + if (pub_ret > 0) { + keySize = (uint16_t)pub_ret; + } + else { + ret = (pub_ret < 0) ? pub_ret + : WH_ERROR_ABORTED; + } + (void)wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, keySize, + WH_DMA_OPER_CLIENT_WRITE_POST, + (whServerDmaFlags){0}); + } + else { + ret = rc; + } + if (ret != 0) { + _CryptoEvictKeyLocked(ctx, keyId); + } + } +#endif /* WOLFSSL_MLDSA_PUBLIC_KEY */ + if (ret == 0) { + res.keyId = wh_KeyId_TranslateToClient(keyId); + res.keySize = keySize; + } + } + } + } + wc_MlDsaKey_Free(key); + } } - /* Final: inline data must be less than one block, no DMA input */ - if (req.isLastBlock && - (req.inSz >= WC_SHA512_BLOCK_SIZE || req.input.sz != 0)) { - return WH_ERROR_BADARGS; + + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.key; } - inlineData = - (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha512DmaRequest); - hashType = req.resumeState.hashType; + /* Translate the response */ + (void)wh_MessageCrypto_TranslateMlDsaKeyGenDmaResponse( + magic, &res, (whMessageCrypto_MlDsaKeyGenDmaResponse*)cryptoDataOut); + + *outSize = sizeof(res); + + return ret; +#endif /* WOLFSSL_MLDSA_NO_MAKE_KEY */ +} + +static int _HandleMlDsaSignDma(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ +#ifdef WOLFSSL_MLDSA_NO_SIGN + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret = 0; + wc_MlDsaKey key[1]; + void* msgAddr = NULL; + void* sigAddr = NULL; + word32 sigLen = 0; + + whMessageCrypto_MlDsaSignDmaRequest req; + whMessageCrypto_MlDsaSignDmaResponse res; - /* If the client requested a variant the server does not have compiled in, - * normalize hashType to plain SHA512 so the response reflects what was - * actually executed; the client detects the mismatch against its own - * hashType and returns an error. */ - switch (hashType) { -#ifndef WOLFSSL_NOSHA512_224 - case WC_HASH_TYPE_SHA512_224: - ret = wc_InitSha512_224_ex(sha512, NULL, devId); - break; -#endif -#ifndef WOLFSSL_NOSHA512_256 - case WC_HASH_TYPE_SHA512_256: - ret = wc_InitSha512_256_ex(sha512, NULL, devId); - break; -#endif - default: - ret = wc_InitSha512_ex(sha512, NULL, devId); - hashType = WC_HASH_TYPE_SHA512; - break; + if (inSize < sizeof(whMessageCrypto_MlDsaSignDmaRequest)) { + return WH_ERROR_BADARGS; } - if (ret != 0) { + + /* Translate the request */ + ret = wh_MessageCrypto_TranslateMlDsaSignDmaRequest( + magic, (whMessageCrypto_MlDsaSignDmaRequest*)cryptoDataIn, &req); + if (ret != WH_ERROR_OK) { return ret; } - res.hashType = hashType; + /* Transaction state */ + whKeyId key_id; + int evict = 0; - memcpy(sha512->digest, req.resumeState.hash, WC_SHA512_DIGEST_SIZE); - sha512->loLen = req.resumeState.loLen; - sha512->hiLen = req.resumeState.hiLen; - sha512->buffLen = 0; - if (ret == 0 && req.inSz > 0) { - ret = wc_Sha512Update(sha512, inlineData, req.inSz); - } + /* Get key ID and evict flag */ + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + evict = !!(req.options & WH_MESSAGE_CRYPTO_MLDSA_SIGN_OPTIONS_EVICT); - if (ret == 0 && req.input.sz > 0) { - ret = wh_Server_DmaProcessClientAddress( - ctx, req.input.addr, &inAddr, req.input.sz, - WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); - if (ret == WH_ERROR_OK) { - preOk = 1; - ret = wc_Sha512Update(sha512, inAddr, req.input.sz); - } - if (ret == WH_ERROR_ACCESS) { - res.dmaAddrStatus.badAddr = req.input; - } + /* Extract context from inline data after the struct */ + uint32_t contextSz = req.contextSz; + uint32_t preHashType = req.preHashType; + byte* req_context = NULL; + if (contextSz > WH_CRYPTO_MLDSA_MAX_CTX_LEN) { + return WH_ERROR_BADARGS; } - /* Pair every successful PRE with a POST so DMA callbacks can release any - * resources they acquired, even if the Update failed. */ - if (preOk) { - (void)wh_Server_DmaProcessClientAddress( - ctx, req.input.addr, &inAddr, req.input.sz, - WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + if (contextSz > 0) { + if (inSize < sizeof(whMessageCrypto_MlDsaSignDmaRequest) + contextSz) { + return WH_ERROR_BADARGS; + } + req_context = (uint8_t*)(cryptoDataIn) + + sizeof(whMessageCrypto_MlDsaSignDmaRequest); } + /* Initialize key */ + ret = wc_MlDsaKey_Init(key, NULL, devId); if (ret == 0) { - if (req.isLastBlock) { - switch (hashType) { -#ifndef WOLFSSL_NOSHA512_224 - case WC_HASH_TYPE_SHA512_224: - ret = wc_Sha512_224Final(sha512, res.hash); - break; -#endif -#ifndef WOLFSSL_NOSHA512_256 - case WC_HASH_TYPE_SHA512_256: - ret = wc_Sha512_256Final(sha512, res.hash); - break; -#endif - default: - ret = wc_Sha512Final(sha512, res.hash); - break; - } - } - else { - if (sha512->buffLen != 0) { - ret = WH_ERROR_ABORTED; - } - else { - memcpy(res.hash, sha512->digest, WC_SHA512_DIGEST_SIZE); - res.loLen = sha512->loLen; - res.hiLen = sha512->hiLen; + /* Export key from cache */ + /* TODO: sanity check security level against key pulled from cache? */ + /* Export the key, enforcing the sign usage policy against the same + * locked snapshot that is exported. The non-DMA sign handler enforces + * the same policy. */ + ret = _MlDsaKeyCacheExportEnforce(ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, + key); + if (ret == 0) { + /* Process client message buffer address */ + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + + if (ret == 0) { + /* Process client signature buffer address */ + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); + + if (ret == 0) { + /* Sign the message using appropriate FIPS 204 API */ + sigLen = req.sig.sz; + if (preHashType != WC_HASH_TYPE_NONE) { + ret = wc_MlDsaKey_SignCtxHash( + key, req_context, (byte)contextSz, + sigAddr, &sigLen, msgAddr, req.msg.sz, + preHashType, ctx->crypto->rng); + } + else { + ret = wc_MlDsaKey_SignCtx( + key, req_context, (byte)contextSz, + sigAddr, &sigLen, msgAddr, req.msg.sz, + ctx->crypto->rng); + } + } + + if (sigAddr != NULL) { + /* Post-write processing of signature buffer */ + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, sigLen, + WH_DMA_OPER_CLIENT_WRITE_POST, + (whServerDmaFlags){0}); + } + if (msgAddr != NULL) { + /* Post-read processing of message buffer */ + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, + req.msg.sz, WH_DMA_OPER_CLIENT_READ_POST, + (whServerDmaFlags){0}); + } } } + wc_MlDsaKey_Free(key); } - (void)wh_MessageCrypto_TranslateSha2DmaResponse( - magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut); - *outSize = sizeof(res); + /* Evict key if requested */ + if (evict) { + /* User requested to evict from cache, even if the call failed */ + _CryptoEvictKeyLocked(ctx, key_id); + } + + if (ret == 0) { + /* Set response signature length */ + res.sigLen = sigLen; + *outSize = sizeof(res); + + /* Translate the response */ + (void)wh_MessageCrypto_TranslateMlDsaSignDmaResponse( + magic, &res, (whMessageCrypto_MlDsaSignDmaResponse*)cryptoDataOut); + } return ret; +#endif /* WOLFSSL_MLDSA_NO_SIGN */ } -#endif /* WOLFSSL_SHA512 */ -#if defined(WOLFSSL_SHA3) -static int _HandleSha3Dma(whServerContext* ctx, int hashType, uint16_t magic, - int devId, uint16_t seq, const void* cryptoDataIn, - uint16_t inSize, void* cryptoDataOut, - uint16_t* outSize) +static int _HandleMlDsaVerifyDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) { - (void)seq; - int ret = 0; - int preOk = 0; - whMessageCrypto_Sha3DmaRequest req; - whMessageCrypto_Sha3DmaResponse res = {0}; - wc_Sha3 sha3[1]; - const uint8_t* inlineData; - void* inAddr = NULL; - _Sha3VariantOps ops; +#ifdef WOLFSSL_MLDSA_NO_VERIFY + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +#else + int ret = 0; + wc_MlDsaKey key[1]; + void* msgAddr = NULL; + void* sigAddr = NULL; + int verified = 0; - ret = _Sha3LookupOps(hashType, &ops); - if (ret != 0) { - return ret; - } + whMessageCrypto_MlDsaVerifyDmaRequest req; + whMessageCrypto_MlDsaVerifyDmaResponse res; - if (inSize < sizeof(whMessageCrypto_Sha3DmaRequest)) { + if (inSize < sizeof(whMessageCrypto_MlDsaVerifyDmaRequest)) { return WH_ERROR_BADARGS; } - ret = wh_MessageCrypto_TranslateSha3DmaRequest( - magic, (const whMessageCrypto_Sha3DmaRequest*)cryptoDataIn, &req); + /* Translate the request */ + ret = wh_MessageCrypto_TranslateMlDsaVerifyDmaRequest( + magic, (whMessageCrypto_MlDsaVerifyDmaRequest*)cryptoDataIn, &req); if (ret != WH_ERROR_OK) { return ret; } - if ((uint32_t)req.inSz > - (uint32_t)(inSize - sizeof(whMessageCrypto_Sha3DmaRequest))) { - return WH_ERROR_BADARGS; - } - if (!req.isLastBlock && ((req.inSz % ops.blockSize) != 0 || - (req.input.sz % ops.blockSize) != 0)) { - return WH_ERROR_BADARGS; - } - if (req.isLastBlock && (req.inSz >= ops.blockSize || req.input.sz != 0)) { + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + /* Get key ID and evict flag */ + key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, + ctx->comm->client_id, req.keyId); + evict = !!(req.options & WH_MESSAGE_CRYPTO_MLDSA_VERIFY_OPTIONS_EVICT); + + /* Extract context from inline data after the struct */ + uint32_t contextSz = req.contextSz; + uint32_t preHashType = req.preHashType; + byte* req_context = NULL; + if (contextSz > WH_CRYPTO_MLDSA_MAX_CTX_LEN) { return WH_ERROR_BADARGS; } + if (contextSz > 0) { + if (inSize < sizeof(whMessageCrypto_MlDsaVerifyDmaRequest) + contextSz) { + return WH_ERROR_BADARGS; + } + req_context = (uint8_t*)(cryptoDataIn) + + sizeof(whMessageCrypto_MlDsaVerifyDmaRequest); + } - inlineData = - (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_Sha3DmaRequest); - - ret = ops.initFn(sha3, NULL, devId); + /* Initialize key */ + ret = wc_MlDsaKey_Init(key, NULL, devId); if (ret != 0) { return ret; } - /* Restore Keccak state from client. initFn already zeroed t[] and i. */ - memcpy(sha3->s, req.resumeState.s, sizeof(sha3->s)); - - if (ret == 0 && req.inSz > 0) { - ret = ops.updateFn(sha3, inlineData, req.inSz); - } - - if (ret == 0 && req.input.sz > 0) { + /* Export the key, enforcing the verify usage policy against the same + * locked snapshot that is exported. The non-DMA verify handler enforces + * the same policy. */ + ret = _MlDsaKeyCacheExportEnforce(ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, + key); + if (ret == 0) { + /* Process client signature buffer address */ ret = wh_Server_DmaProcessClientAddress( - ctx, req.input.addr, &inAddr, req.input.sz, + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); - if (ret == WH_ERROR_OK) { - preOk = 1; - ret = ops.updateFn(sha3, inAddr, req.input.sz); - } - if (ret == WH_ERROR_ACCESS) { - res.dmaAddrStatus.badAddr = req.input; - } - } - if (preOk) { - (void)wh_Server_DmaProcessClientAddress( - ctx, req.input.addr, &inAddr, req.input.sz, - WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); - } - if (ret == 0) { - if (req.isLastBlock) { - ret = ops.finalFn(sha3, res.hash); - } - else { - if (sha3->i != 0) { - ret = WH_ERROR_ABORTED; + if (ret == 0) { + /* Process client message buffer address */ + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + + if (ret == 0) { + /* Verify the signature using appropriate FIPS 204 API */ + if (preHashType != WC_HASH_TYPE_NONE) { + ret = wc_MlDsaKey_VerifyCtxHash( + key, sigAddr, req.sig.sz, req_context, (byte)contextSz, + msgAddr, req.msg.sz, preHashType, &verified); + } + else { + ret = wc_MlDsaKey_VerifyCtx( + key, sigAddr, req.sig.sz, req_context, (byte)contextSz, + msgAddr, req.msg.sz, &verified); + } } - else { - memcpy(res.resumeState.s, sha3->s, sizeof(res.resumeState.s)); + + if (sigAddr != NULL) { + /* Post-read processing of signature buffer */ + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + + if (msgAddr != NULL) { + /* Post-read processing of message buffer */ + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, + req.msg.sz, WH_DMA_OPER_CLIENT_READ_POST, + (whServerDmaFlags){0}); } } } - (void)wh_MessageCrypto_TranslateSha3DmaResponse( - magic, &res, (whMessageCrypto_Sha3DmaResponse*)cryptoDataOut); - *outSize = sizeof(res); + /* Evict key if requested */ + if (evict) { + /* User requested to evict from cache, even if the call failed */ + _CryptoEvictKeyLocked(ctx, key_id); + } + + if (ret == 0) { + /* Set verification result */ + res.verifyResult = verified; + + /* Translate the response */ + (void)wh_MessageCrypto_TranslateMlDsaVerifyDmaResponse( + magic, &res, + (whMessageCrypto_MlDsaVerifyDmaResponse*)cryptoDataOut); + *outSize = sizeof(res); + } + + wc_MlDsaKey_Free(key); return ret; +#endif /* WOLFSSL_MLDSA_NO_VERIFY */ } -#endif /* WOLFSSL_SHA3 */ -#if defined(WOLFSSL_HAVE_MLDSA) +static int _HandleMlDsaCheckPrivKeyDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ + (void)ctx; + (void)magic; + (void)devId; + (void)cryptoDataIn; + (void)inSize; + (void)cryptoDataOut; + (void)outSize; + return WH_ERROR_NOHANDLER; +} +#endif /* WOLFSSL_HAVE_MLDSA */ -static int _HandleMlDsaKeyGenDma(whServerContext* ctx, uint16_t magic, - int devId, const void* cryptoDataIn, - uint16_t inSize, void* cryptoDataOut, - uint16_t* outSize) +#ifdef WOLFSSL_HAVE_SLHDSA +static int _HandleSlhDsaKeyGenDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) { -#ifdef WOLFSSL_MLDSA_NO_MAKE_KEY +#ifdef WOLFSSL_SLHDSA_VERIFY_ONLY (void)ctx; (void)magic; (void)devId; @@ -6894,159 +8041,158 @@ static int _HandleMlDsaKeyGenDma(whServerContext* ctx, uint16_t magic, (void)outSize; return WH_ERROR_NOHANDLER; #else - int ret = WH_ERROR_OK; - wc_MlDsaKey key[1]; - void* clientOutAddr = NULL; - uint16_t keySize = 0; + int ret = WH_ERROR_OK; + SlhDsaKey key[1]; + void* clientOutAddr = NULL; + void* clientSeedAddr = NULL; + uint16_t keySize = 0; - whMessageCrypto_MlDsaKeyGenDmaRequest req; - whMessageCrypto_MlDsaKeyGenDmaResponse res; + whMessageCrypto_SlhDsaKeyGenDmaRequest req; + whMessageCrypto_SlhDsaKeyGenDmaResponse res; memset(&res, 0, sizeof(res)); - if (inSize < sizeof(whMessageCrypto_MlDsaKeyGenDmaRequest)) { + if (inSize < sizeof(whMessageCrypto_SlhDsaKeyGenDmaRequest)) { return WH_ERROR_BADARGS; } - /* Translate the request */ - ret = wh_MessageCrypto_TranslateMlDsaKeyGenDmaRequest( - magic, (whMessageCrypto_MlDsaKeyGenDmaRequest*)cryptoDataIn, &req); + ret = wh_MessageCrypto_TranslateSlhDsaKeyGenDmaRequest( + magic, (whMessageCrypto_SlhDsaKeyGenDmaRequest*)cryptoDataIn, &req); if (ret != WH_ERROR_OK) { return ret; } - /* Check the ML-DSA security level is valid and supported */ - if (0 == _IsMlDsaLevelSupported(req.level)) { - ret = WH_ERROR_BADARGS; + if (0 == _IsSlhDsaParamSupported((int)req.param)) { + return WH_ERROR_BADARGS; } - else { - /* init mldsa key */ - ret = wc_MlDsaKey_Init(key, NULL, devId); - if (ret == 0) { - /* Set the ML-DSA security level */ - ret = wc_MlDsaKey_SetParams(key, req.level); + + ret = wc_SlhDsaKey_Init(key, (enum SlhDsaParam)req.param, NULL, devId); + if (ret == 0) { + if (req.seed.sz > 0) { + /* The seed is the contiguous SK.seed || SK.prf || PK.seed */ + ret = wh_Server_DmaProcessClientAddress( + ctx, req.seed.addr, &clientSeedAddr, req.seed.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); if (ret == 0) { - /* generate the key */ - ret = wc_MlDsaKey_MakeKey(key, ctx->crypto->rng); - if (ret == 0) { - /* Check incoming flags */ - if (req.flags & WH_NVM_FLAGS_EPHEMERAL) { - /* Must serialize the key into client memory */ - ret = wh_Server_DmaProcessClientAddress( - ctx, req.key.addr, &clientOutAddr, req.key.sz, - WH_DMA_OPER_CLIENT_WRITE_PRE, - (whServerDmaFlags){0}); + word32 n = (word32)(req.seed.sz / 3); + if ((req.seed.sz % 3) != 0) { + ret = WH_ERROR_BADARGS; + } + else { + const byte* seed = (const byte*)clientSeedAddr; + ret = wc_SlhDsaKey_MakeKeyWithRandom( + key, seed, n, seed + n, n, seed + 2 * n, n); + } + (void)wh_Server_DmaProcessClientAddress( + ctx, req.seed.addr, &clientSeedAddr, req.seed.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + } + else { + ret = wc_SlhDsaKey_MakeKey(key, ctx->crypto->rng); + } - if (ret == 0) { - ret = wh_Crypto_MlDsaSerializeKeyDer( - key, req.key.sz, clientOutAddr, &keySize); - if (ret == 0) { - res.keyId = WH_KEYID_ERASED; - res.keySize = keySize; - } - } + if (ret == 0) { + if (req.flags & WH_NVM_FLAGS_EPHEMERAL) { + /* Must serialize the key into client memory */ + ret = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, req.key.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); - if (ret == 0) { - ret = wh_Server_DmaProcessClientAddress( - ctx, req.key.addr, &clientOutAddr, keySize, - WH_DMA_OPER_CLIENT_WRITE_POST, - (whServerDmaFlags){0}); - } + if (ret == 0) { + ret = wh_Crypto_SlhDsaSerializeKeyDer( + key, (uint16_t)req.key.sz, clientOutAddr, &keySize); + if (ret == 0) { + res.keyId = WH_KEYID_ERASED; + res.keySize = keySize; } - else { - /* Must import the key into the cache and return keyid - */ - whKeyId keyId = wh_KeyId_TranslateFromClient( - WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); + } - /* Hold the NVM lock so id allocation and cache import - * are atomic with respect to other server contexts - * under THREADSAFE. */ - ret = WH_SERVER_NVM_LOCK(ctx); - if (ret == WH_ERROR_OK) { - if (WH_KEYID_ISERASED(keyId)) { - /* Generate a new id */ - ret = - wh_Server_KeystoreGetUniqueId(ctx, &keyId); - WH_DEBUG_SERVER("UniqueId: keyId:%u, ret:%d\n", - keyId, ret); - } - if (ret == WH_ERROR_OK) { - ret = wh_Server_MlDsaKeyCacheImport( - ctx, key, keyId, req.flags, req.labelSize, - req.label); - WH_DEBUG_SERVER( - "CacheImport: keyId:%u, ret:%d\n", keyId, - ret); - } - (void)WH_SERVER_NVM_UNLOCK(ctx); - } /* WH_SERVER_NVM_LOCK() */ -#ifdef WOLFSSL_MLDSA_PUBLIC_KEY - /* Stream the public key back through the client's DMA - * buffer so it gets the pubkey without a separate - * ExportPublicKey call. A freshly generated key must - * serialize, so treat a failure as fatal: evict the - * just-committed key and propagate the error rather - * than returning a keyId with no public key. */ - if (ret == 0) { - int rc = wh_Server_DmaProcessClientAddress( - ctx, req.key.addr, &clientOutAddr, req.key.sz, - WH_DMA_OPER_CLIENT_WRITE_PRE, - (whServerDmaFlags){0}); - if (rc == 0) { - int pub_ret = wc_MlDsaKey_PublicKeyToDer( - key, (byte*)clientOutAddr, - (word32)req.key.sz, 1); - if (pub_ret > 0) { - keySize = (uint16_t)pub_ret; - } - else { - ret = (pub_ret < 0) ? pub_ret - : WH_ERROR_ABORTED; - } - (void)wh_Server_DmaProcessClientAddress( - ctx, req.key.addr, &clientOutAddr, keySize, - WH_DMA_OPER_CLIENT_WRITE_POST, - (whServerDmaFlags){0}); - } - else { - ret = rc; - } - if (ret != 0) { - _CryptoEvictKeyLocked(ctx, keyId); - } + if (ret == 0) { + ret = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, keySize, + WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0}); + } + } + else { + /* Must import the key into the cache and return keyid */ + whKeyId keyId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); + + /* Hold the NVM lock so id allocation and cache import are + * atomic with respect to other server contexts under + * THREADSAFE. */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + if (WH_KEYID_ISERASED(keyId)) { + ret = wh_Server_KeystoreGetUniqueId(ctx, &keyId); + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_SlhDsaKeyCacheImport( + ctx, key, keyId, req.flags, req.labelSize, + req.label); + } + (void)WH_SERVER_NVM_UNLOCK(ctx); + } /* WH_SERVER_NVM_LOCK() */ + + /* Stream the public key back through the client's DMA buffer + * so it gets the pubkey without a separate ExportPublicKey + * call. A freshly generated key must serialize, so treat a + * failure as fatal: evict the just-committed key and propagate + * the error rather than returning a keyId with no public + * key. */ + if (ret == 0) { + int rc = wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, req.key.sz, + WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); + if (rc == 0) { + int pub_ret = wc_SlhDsaKey_PublicKeyToDer( + key, (byte*)clientOutAddr, (word32)req.key.sz, 1); + if (pub_ret > 0) { + keySize = (uint16_t)pub_ret; } -#endif /* WOLFSSL_MLDSA_PUBLIC_KEY */ - if (ret == 0) { - res.keyId = wh_KeyId_TranslateToClient(keyId); - res.keySize = keySize; + else { + ret = (pub_ret < 0) ? pub_ret : WH_ERROR_ABORTED; } + (void)wh_Server_DmaProcessClientAddress( + ctx, req.key.addr, &clientOutAddr, keySize, + WH_DMA_OPER_CLIENT_WRITE_POST, + (whServerDmaFlags){0}); + } + else { + ret = rc; + } + if (ret != 0) { + _CryptoEvictKeyLocked(ctx, keyId); } } + if (ret == 0) { + res.keyId = wh_KeyId_TranslateToClient(keyId); + res.keySize = keySize; + } } - wc_MlDsaKey_Free(key); } + wc_SlhDsaKey_Free(key); } if (ret == WH_ERROR_ACCESS) { res.dmaAddrStatus.badAddr = req.key; } - /* Translate the response */ - (void)wh_MessageCrypto_TranslateMlDsaKeyGenDmaResponse( - magic, &res, (whMessageCrypto_MlDsaKeyGenDmaResponse*)cryptoDataOut); + (void)wh_MessageCrypto_TranslateSlhDsaKeyGenDmaResponse( + magic, &res, (whMessageCrypto_SlhDsaKeyGenDmaResponse*)cryptoDataOut); *outSize = sizeof(res); return ret; -#endif /* WOLFSSL_MLDSA_NO_MAKE_KEY */ +#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */ } -static int _HandleMlDsaSignDma(whServerContext* ctx, uint16_t magic, int devId, - const void* cryptoDataIn, uint16_t inSize, - void* cryptoDataOut, uint16_t* outSize) +static int _HandleSlhDsaSignDma(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) { -#ifdef WOLFSSL_MLDSA_NO_SIGN +#ifdef WOLFSSL_SLHDSA_VERIFY_ONLY (void)ctx; (void)magic; (void)devId; @@ -7056,282 +8202,230 @@ static int _HandleMlDsaSignDma(whServerContext* ctx, uint16_t magic, int devId, (void)outSize; return WH_ERROR_NOHANDLER; #else - int ret = 0; - wc_MlDsaKey key[1]; - void* msgAddr = NULL; - void* sigAddr = NULL; - word32 sigLen = 0; + int ret = 0; + SlhDsaKey key[1]; + void* msgAddr = NULL; + void* sigAddr = NULL; + word32 sigLen = 0; + whKeyId key_id; + int evict; + uint32_t contextSz; + uint32_t preHashType; + uint32_t addRndSz; + byte* req_context = NULL; + byte* req_addRnd = NULL; + uint32_t inline_len; + + whMessageCrypto_SlhDsaSignDmaRequest req; + whMessageCrypto_SlhDsaSignDmaResponse res; - whMessageCrypto_MlDsaSignDmaRequest req; - whMessageCrypto_MlDsaSignDmaResponse res; + memset(&res, 0, sizeof(res)); - if (inSize < sizeof(whMessageCrypto_MlDsaSignDmaRequest)) { + if (inSize < sizeof(whMessageCrypto_SlhDsaSignDmaRequest)) { return WH_ERROR_BADARGS; } - /* Translate the request */ - ret = wh_MessageCrypto_TranslateMlDsaSignDmaRequest( - magic, (whMessageCrypto_MlDsaSignDmaRequest*)cryptoDataIn, &req); + ret = wh_MessageCrypto_TranslateSlhDsaSignDmaRequest( + magic, (whMessageCrypto_SlhDsaSignDmaRequest*)cryptoDataIn, &req); if (ret != WH_ERROR_OK) { return ret; } - /* Transaction state */ - whKeyId key_id; - int evict = 0; - - - /* Get key ID and evict flag */ key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); - evict = !!(req.options & WH_MESSAGE_CRYPTO_MLDSA_SIGN_OPTIONS_EVICT); + evict = !!(req.options & WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_EVICT); - /* Extract context from inline data after the struct */ - uint32_t contextSz = req.contextSz; - uint32_t preHashType = req.preHashType; - byte* req_context = NULL; - if (contextSz > WH_CRYPTO_MLDSA_MAX_CTX_LEN) { + contextSz = req.contextSz; + preHashType = req.preHashType; + addRndSz = req.addRndSz; + if (contextSz > WH_CRYPTO_SLHDSA_MAX_CTX_LEN) { + return WH_ERROR_BADARGS; + } + inline_len = inSize - sizeof(whMessageCrypto_SlhDsaSignDmaRequest); + if ((contextSz > inline_len) || (addRndSz > (inline_len - contextSz))) { return WH_ERROR_BADARGS; } if (contextSz > 0) { - if (inSize < sizeof(whMessageCrypto_MlDsaSignDmaRequest) + contextSz) { - return WH_ERROR_BADARGS; - } req_context = (uint8_t*)(cryptoDataIn) + - sizeof(whMessageCrypto_MlDsaSignDmaRequest); + sizeof(whMessageCrypto_SlhDsaSignDmaRequest); + } + if (addRndSz > 0) { + req_addRnd = (uint8_t*)(cryptoDataIn) + + sizeof(whMessageCrypto_SlhDsaSignDmaRequest) + contextSz; } - /* Initialize key */ - ret = wc_MlDsaKey_Init(key, NULL, devId); + ret = _SlhDsaInitForCachedKey(key, req.param, devId); if (ret == 0) { - /* Export key from cache */ - /* TODO: sanity check security level against key pulled from cache? */ - /* Export the key, enforcing the sign usage policy against the same - * locked snapshot that is exported. The non-DMA sign handler enforces - * the same policy. */ - ret = _MlDsaKeyCacheExportEnforce(ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, - key); + ret = _SlhDsaLoadKey(ctx, key_id, WH_NVM_FLAGS_USAGE_SIGN, key); if (ret == 0) { - /* Process client message buffer address */ ret = wh_Server_DmaProcessClientAddress( ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.msg; + } if (ret == 0) { - /* Process client signature buffer address */ ret = wh_Server_DmaProcessClientAddress( ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0}); + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.sig; + } if (ret == 0) { - /* Sign the message using appropriate FIPS 204 API */ - sigLen = req.sig.sz; - if (preHashType != WC_HASH_TYPE_NONE) { - ret = wc_MlDsaKey_SignCtxHash( - key, req_context, (byte)contextSz, - sigAddr, &sigLen, msgAddr, req.msg.sz, - preHashType, ctx->crypto->rng); - } - else { - ret = wc_MlDsaKey_SignCtx( - key, req_context, (byte)contextSz, - sigAddr, &sigLen, msgAddr, req.msg.sz, - ctx->crypto->rng); - } + sigLen = (word32)req.sig.sz; + ret = _SlhDsaSignDispatch( + ctx, key, req.options, (const byte*)msgAddr, + (word32)req.msg.sz, req_context, contextSz, + preHashType, req_addRnd, addRndSz, (byte*)sigAddr, + &sigLen); } if (sigAddr != NULL) { - /* Post-write processing of signature buffer */ (void)wh_Server_DmaProcessClientAddress( ctx, (uintptr_t)req.sig.addr, &sigAddr, sigLen, - WH_DMA_OPER_CLIENT_WRITE_POST, - (whServerDmaFlags){0}); + WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0}); } if (msgAddr != NULL) { - /* Post-read processing of message buffer */ (void)wh_Server_DmaProcessClientAddress( - ctx, (uintptr_t)req.msg.addr, &msgAddr, - req.msg.sz, WH_DMA_OPER_CLIENT_READ_POST, - (whServerDmaFlags){0}); + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); } } } - wc_MlDsaKey_Free(key); + wc_SlhDsaKey_Free(key); } - /* Evict key if requested */ if (evict) { /* User requested to evict from cache, even if the call failed */ _CryptoEvictKeyLocked(ctx, key_id); } if (ret == 0) { - /* Set response signature length */ res.sigLen = sigLen; - *outSize = sizeof(res); - - /* Translate the response */ - (void)wh_MessageCrypto_TranslateMlDsaSignDmaResponse( - magic, &res, (whMessageCrypto_MlDsaSignDmaResponse*)cryptoDataOut); } + (void)wh_MessageCrypto_TranslateSlhDsaSignDmaResponse( + magic, &res, (whMessageCrypto_SlhDsaSignDmaResponse*)cryptoDataOut); + + *outSize = sizeof(res); + return ret; -#endif /* WOLFSSL_MLDSA_NO_SIGN */ +#endif /* WOLFSSL_SLHDSA_VERIFY_ONLY */ } -static int _HandleMlDsaVerifyDma(whServerContext* ctx, uint16_t magic, - int devId, const void* cryptoDataIn, - uint16_t inSize, void* cryptoDataOut, - uint16_t* outSize) +static int _HandleSlhDsaVerifyDma(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) { -#ifdef WOLFSSL_MLDSA_NO_VERIFY - (void)ctx; - (void)magic; - (void)devId; - (void)cryptoDataIn; - (void)inSize; - (void)cryptoDataOut; - (void)outSize; - return WH_ERROR_NOHANDLER; -#else - int ret = 0; - wc_MlDsaKey key[1]; - void* msgAddr = NULL; - void* sigAddr = NULL; - int verified = 0; + int ret = 0; + SlhDsaKey key[1]; + void* msgAddr = NULL; + void* sigAddr = NULL; + whKeyId key_id; + int evict; + int result = 0; + uint32_t contextSz; + uint32_t preHashType; + byte* req_context = NULL; + + whMessageCrypto_SlhDsaVerifyDmaRequest req; + whMessageCrypto_SlhDsaVerifyDmaResponse res; - whMessageCrypto_MlDsaVerifyDmaRequest req; - whMessageCrypto_MlDsaVerifyDmaResponse res; + memset(&res, 0, sizeof(res)); - if (inSize < sizeof(whMessageCrypto_MlDsaVerifyDmaRequest)) { + if (inSize < sizeof(whMessageCrypto_SlhDsaVerifyDmaRequest)) { return WH_ERROR_BADARGS; } - /* Translate the request */ - ret = wh_MessageCrypto_TranslateMlDsaVerifyDmaRequest( - magic, (whMessageCrypto_MlDsaVerifyDmaRequest*)cryptoDataIn, &req); + ret = wh_MessageCrypto_TranslateSlhDsaVerifyDmaRequest( + magic, (whMessageCrypto_SlhDsaVerifyDmaRequest*)cryptoDataIn, &req); if (ret != WH_ERROR_OK) { return ret; } - /* Transaction state */ - whKeyId key_id; - int evict = 0; - - /* Get key ID and evict flag */ key_id = wh_KeyId_TranslateFromClient(WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); - evict = !!(req.options & WH_MESSAGE_CRYPTO_MLDSA_VERIFY_OPTIONS_EVICT); + evict = !!(req.options & WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_EVICT); - /* Extract context from inline data after the struct */ - uint32_t contextSz = req.contextSz; - uint32_t preHashType = req.preHashType; - byte* req_context = NULL; - if (contextSz > WH_CRYPTO_MLDSA_MAX_CTX_LEN) { + contextSz = req.contextSz; + preHashType = req.preHashType; + if (contextSz > WH_CRYPTO_SLHDSA_MAX_CTX_LEN) { return WH_ERROR_BADARGS; } if (contextSz > 0) { - if (inSize < sizeof(whMessageCrypto_MlDsaVerifyDmaRequest) + contextSz) { + if (inSize < + sizeof(whMessageCrypto_SlhDsaVerifyDmaRequest) + contextSz) { return WH_ERROR_BADARGS; } req_context = (uint8_t*)(cryptoDataIn) + - sizeof(whMessageCrypto_MlDsaVerifyDmaRequest); - } - - /* Initialize key */ - ret = wc_MlDsaKey_Init(key, NULL, devId); - if (ret != 0) { - return ret; + sizeof(whMessageCrypto_SlhDsaVerifyDmaRequest); } - /* Export the key, enforcing the verify usage policy against the same - * locked snapshot that is exported. The non-DMA verify handler enforces - * the same policy. */ - ret = _MlDsaKeyCacheExportEnforce(ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, - key); + ret = _SlhDsaInitForCachedKey(key, req.param, devId); if (ret == 0) { - /* Process client signature buffer address */ - ret = wh_Server_DmaProcessClientAddress( - ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, - WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); - + ret = _SlhDsaLoadKey(ctx, key_id, WH_NVM_FLAGS_USAGE_VERIFY, key); if (ret == 0) { - /* Process client message buffer address */ ret = wh_Server_DmaProcessClientAddress( - ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.sig; + } if (ret == 0) { - /* Verify the signature using appropriate FIPS 204 API */ - if (preHashType != WC_HASH_TYPE_NONE) { - ret = wc_MlDsaKey_VerifyCtxHash( - key, sigAddr, req.sig.sz, req_context, (byte)contextSz, - msgAddr, req.msg.sz, preHashType, &verified); + ret = wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (ret == WH_ERROR_ACCESS) { + res.dmaAddrStatus.badAddr = req.msg; } - else { - ret = wc_MlDsaKey_VerifyCtx( - key, sigAddr, req.sig.sz, req_context, (byte)contextSz, - msgAddr, req.msg.sz, &verified); + + if (ret == 0) { + ret = _SlhDsaVerifyDispatch( + key, req.options, (const byte*)sigAddr, + (word32)req.sig.sz, (const byte*)msgAddr, + (word32)req.msg.sz, req_context, contextSz, + preHashType, &result); } - } + if (msgAddr != NULL) { + (void)wh_Server_DmaProcessClientAddress( + ctx, (uintptr_t)req.msg.addr, &msgAddr, req.msg.sz, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + } if (sigAddr != NULL) { - /* Post-read processing of signature buffer */ (void)wh_Server_DmaProcessClientAddress( ctx, (uintptr_t)req.sig.addr, &sigAddr, req.sig.sz, WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); } - - if (msgAddr != NULL) { - /* Post-read processing of message buffer */ - (void)wh_Server_DmaProcessClientAddress( - ctx, (uintptr_t)req.msg.addr, &msgAddr, - req.msg.sz, WH_DMA_OPER_CLIENT_READ_POST, - (whServerDmaFlags){0}); - } } + wc_SlhDsaKey_Free(key); } - /* Evict key if requested */ if (evict) { /* User requested to evict from cache, even if the call failed */ _CryptoEvictKeyLocked(ctx, key_id); } if (ret == 0) { - /* Set verification result */ - res.verifyResult = verified; + res.verifyResult = result; + } - /* Translate the response */ - (void)wh_MessageCrypto_TranslateMlDsaVerifyDmaResponse( - magic, &res, - (whMessageCrypto_MlDsaVerifyDmaResponse*)cryptoDataOut); + (void)wh_MessageCrypto_TranslateSlhDsaVerifyDmaResponse( + magic, &res, (whMessageCrypto_SlhDsaVerifyDmaResponse*)cryptoDataOut); - *outSize = sizeof(res); - } + *outSize = sizeof(res); - wc_MlDsaKey_Free(key); return ret; -#endif /* WOLFSSL_MLDSA_NO_VERIFY */ -} - -static int _HandleMlDsaCheckPrivKeyDma(whServerContext* ctx, uint16_t magic, - int devId, const void* cryptoDataIn, - uint16_t inSize, void* cryptoDataOut, - uint16_t* outSize) -{ - (void)ctx; - (void)magic; - (void)devId; - (void)cryptoDataIn; - (void)inSize; - (void)cryptoDataOut; - (void)outSize; - return WH_ERROR_NOHANDLER; } -#endif /* WOLFSSL_HAVE_MLDSA */ +#endif /* WOLFSSL_HAVE_SLHDSA */ -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) static int _HandlePqcSigAlgorithmDma(whServerContext* ctx, uint16_t magic, int devId, const void* cryptoDataIn, uint16_t cryptoInSize, void* cryptoDataOut, @@ -7372,6 +8466,40 @@ static int _HandlePqcSigAlgorithmDma(whServerContext* ctx, uint16_t magic, } } break; #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PQC_SIG_TYPE_SLHDSA: { + switch (pkAlgoType) { + case WC_PK_TYPE_PQC_SIG_KEYGEN: + case WC_PK_TYPE_PQC_SIG_KEYGEN_SEEDED: + ret = _HandleSlhDsaKeyGenDma(ctx, magic, devId, + cryptoDataIn, cryptoInSize, + cryptoDataOut, cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_SIGN: + case WC_PK_TYPE_PQC_SIG_SIGN_MSG: + ret = _HandleSlhDsaSignDma(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_VERIFY: + case WC_PK_TYPE_PQC_SIG_VERIFY_MSG: + ret = _HandleSlhDsaVerifyDma(ctx, magic, devId, + cryptoDataIn, cryptoInSize, + cryptoDataOut, cryptoOutSize); + break; + case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: + /* The public key is 2n bytes, so the comm-buffer handler + * carries it and there is nothing for DMA to move. */ + ret = _HandleSlhDsaCheckPrivKey( + ctx, magic, devId, cryptoDataIn, cryptoInSize, + cryptoDataOut, cryptoOutSize); + break; + default: + ret = WH_ERROR_NOHANDLER; + break; + } + } break; +#endif /* WOLFSSL_HAVE_SLHDSA */ default: ret = WH_ERROR_NOHANDLER; break; @@ -9344,17 +10472,23 @@ int wh_Server_HandleCryptoDmaRequest(whServerContext* ctx, uint16_t magic, case WC_ALGO_TYPE_PK: switch (rqstHeader.algoType) { -#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) +#if defined(WOLFSSL_HAVE_MLDSA) || defined(HAVE_FALCON) || \ + defined(WOLFSSL_HAVE_SLHDSA) case WC_PK_TYPE_PQC_SIG_KEYGEN: case WC_PK_TYPE_PQC_SIG_SIGN: case WC_PK_TYPE_PQC_SIG_VERIFY: case WC_PK_TYPE_PQC_SIG_CHECK_PRIV_KEY: +#ifdef WOLFSSL_HAVE_SLHDSA + case WC_PK_TYPE_PQC_SIG_KEYGEN_SEEDED: + case WC_PK_TYPE_PQC_SIG_SIGN_MSG: + case WC_PK_TYPE_PQC_SIG_VERIFY_MSG: +#endif ret = _HandlePqcSigAlgorithmDma( ctx, magic, devId, cryptoDataIn, cryptoInSize, cryptoDataOut, &cryptoOutSize, rqstHeader.algoType, rqstHeader.algoSubType); break; -#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON */ +#endif /* WOLFSSL_HAVE_MLDSA || HAVE_FALCON || WOLFSSL_HAVE_SLHDSA */ #if defined(WOLFSSL_HAVE_MLKEM) case WC_PK_TYPE_PQC_KEM_KEYGEN: case WC_PK_TYPE_PQC_KEM_ENCAPS: diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index 94512ef1f..9139efc7a 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -581,6 +581,35 @@ static int _ExportMldsaPublicKey(whServerContext* server, whKeyId keyId, } #endif +#ifdef WOLFSSL_HAVE_SLHDSA +static int _ExportSlhDsaPublicKey(whServerContext* server, whKeyId keyId, + uint8_t* out, uint16_t* outSz) +{ + int ret = WH_ERROR_OK; + SlhDsaKey key[1]; + int pub_ret; + int devId = (server->crypto != NULL) ? server->devId + : INVALID_DEVID; + + /* The decoder detects the real parameter set from the key OID */ + ret = wc_SlhDsaKey_Init(key, WC_SLHDSA_DEFAULT_PARAM, NULL, devId); + if (ret == 0) { + ret = wh_Server_SlhDsaKeyCacheExport(server, keyId, key); + if (ret == 0) { + pub_ret = wc_SlhDsaKey_PublicKeyToDer(key, out, (word32)*outSz, 1); + if (pub_ret > 0) { + *outSz = (uint16_t)pub_ret; + } + else { + ret = (pub_ret == 0) ? WH_ERROR_ABORTED : pub_ret; + } + } + wc_SlhDsaKey_Free(key); + } + return ret; +} +#endif + #ifdef HAVE_CURVE25519 static int _ExportCurve25519PublicKey(whServerContext* server, whKeyId keyId, uint8_t* out, uint16_t* outSz) @@ -2917,6 +2946,12 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, stage, &stageMax); break; #endif /* WOLFSSL_HAVE_MLDSA && WOLFSSL_MLDSA_PUBLIC_KEY */ + #ifdef WOLFSSL_HAVE_SLHDSA + case WH_KEY_ALGO_SLHDSA: + ret = _ExportSlhDsaPublicKey(server, serverKeyId, + stage, &stageMax); + break; + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef HAVE_CURVE25519 case WH_KEY_ALGO_CURVE25519: ret = _ExportCurve25519PublicKey(server, serverKeyId, @@ -3132,6 +3167,12 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, out, &max_der); break; #endif /* WOLFSSL_HAVE_MLDSA && WOLFSSL_MLDSA_PUBLIC_KEY */ + #ifdef WOLFSSL_HAVE_SLHDSA + case WH_KEY_ALGO_SLHDSA: + ret = _ExportSlhDsaPublicKey(server, serverKeyId, + out, &max_der); + break; + #endif /* WOLFSSL_HAVE_SLHDSA */ #ifdef HAVE_CURVE25519 case WH_KEY_ALGO_CURVE25519: ret = _ExportCurve25519PublicKey(server, diff --git a/test-refactor/client-server/wh_test_crypto_slhdsa.c b/test-refactor/client-server/wh_test_crypto_slhdsa.c new file mode 100644 index 000000000..e6308d93d --- /dev/null +++ b/test-refactor/client-server/wh_test_crypto_slhdsa.c @@ -0,0 +1,1002 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/client-server/wh_test_crypto_slhdsa.c + * + * SLH-DSA tests routed through the server. + * + * Parameter sets are chosen around the comm buffer: an SLH-DSA signature is + * 7856 bytes at the smallest parameter set and 49856 at the largest, so the + * comm-buffer path only works for 128s and everything else needs DMA. The + * 'f' variant signs roughly twenty times faster, so the DMA tests use it. + */ + +#include "wolfhsm/wh_settings.h" + +#if !defined(WOLFHSM_CFG_NO_CRYPTO) + +#include +#include + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/random.h" +#include "wolfssl/wolfcrypt/wc_slhdsa.h" +#include "wolfssl/wolfcrypt/error-crypt.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_client_crypto.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#ifdef WOLFSSL_HAVE_SLHDSA + +#if defined(WOLFSSL_SLHDSA_PARAM_128S) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) +#define WH_TEST_SLHDSA_COMM_PARAM SLHDSA_SHAKE128S +#define WH_TEST_SLHDSA_COMM_SIG_LEN WC_SLHDSA_SHAKE128S_SIG_LEN +#endif + +#if defined(WOLFSSL_SLHDSA_PARAM_128F) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY) +#define WH_TEST_SLHDSA_FAST_PARAM SLHDSA_SHAKE128F +#define WH_TEST_SLHDSA_FAST_SIG_LEN WC_SLHDSA_SHAKE128F_SIG_LEN +#endif + +#ifdef WH_TEST_SLHDSA_COMM_PARAM + +/* Drives the crypto callback through the plain wolfCrypt API, which is how an + * application reaches the HSM. The key is ephemeral so the whole generate, + * sign and verify chain crosses the wire. */ +static int _whTest_SlhDsaWolfCryptImpl(whClientContext* ctx, int devId) +{ + int ret = 0; + int verified = 0; + SlhDsaKey key[1]; + WC_RNG rng[1]; + byte msg[] = "Test message for SLH-DSA signing"; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigSz = sizeof(sig); + + ret = wc_InitRng_ex(rng, NULL, WH_CLIENT_DEVID(ctx)); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + return ret; + } + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize SLH-DSA key: %d\n", ret); + (void)wc_FreeRng(rng); + return ret; + } + + if (ret == 0) { + ret = wc_SlhDsaKey_MakeKey(key, rng); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate SLH-DSA key: %d\n", ret); + } + } + if (ret == 0) { + ret = wc_SlhDsaKey_Sign(key, NULL, 0, msg, sizeof(msg), sig, &sigSz, + rng); + if (ret != 0) { + WH_ERROR_PRINT("Failed to sign with SLH-DSA: %d\n", ret); + } + } + if (ret == 0) { + ret = wc_SlhDsaKey_Verify(key, NULL, 0, msg, sizeof(msg), sig, sigSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to verify SLH-DSA signature: %d\n", ret); + } + else { + verified = 1; + } + } + /* Tamper check: a corrupted signature must not verify. */ + if ((ret == 0) && verified) { + sig[0] ^= 1; + ret = wc_SlhDsaKey_Verify(key, NULL, 0, msg, sizeof(msg), sig, sigSz); + if (ret == 0) { + WH_ERROR_PRINT("SLH-DSA verified a tampered signature\n"); + ret = -1; + } + else { + ret = 0; + } + } + + if (ret == 0) { + WH_TEST_PRINT("SLH-DSA WOLFCRYPT DEVID=0x%X SUCCESS\n", devId); + } + + wc_SlhDsaKey_Free(key); + (void)wc_FreeRng(rng); + return ret; +} + +/* Ephemeral generate plus the pure, context and pre-hash signing shapes over + * the comm buffer. */ +static int _whTest_CryptoSlhDsaClient(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize SLH-DSA key: %d\n", ret); + return ret; + } + + ret = wh_Client_SlhDsaMakeExportKey(ctx, WH_TEST_SLHDSA_COMM_PARAM, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate SLH-DSA key: %d\n", ret); + goto done; + } + + { + byte msg[] = "Test message for non-DMA SLH-DSA"; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigLen = sizeof(sig); + int verified = 0; + + ret = wh_Client_SlhDsaSign(ctx, msg, sizeof(msg), sig, &sigLen, key, + NULL, 0, WC_HASH_TYPE_NONE, NULL, 0, 1, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to sign using SLH-DSA non-DMA: %d\n", ret); + goto done; + } + if (sigLen != WH_TEST_SLHDSA_COMM_SIG_LEN) { + WH_ERROR_PRINT("SLH-DSA signature length %u, expected %u\n", + (unsigned)sigLen, + (unsigned)WH_TEST_SLHDSA_COMM_SIG_LEN); + ret = WH_TEST_FAIL; + goto done; + } + + ret = wh_Client_SlhDsaVerify(ctx, sig, sigLen, msg, sizeof(msg), + &verified, key, NULL, 0, + WC_HASH_TYPE_NONE, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to verify SLH-DSA non-DMA: %d\n", ret); + goto done; + } + if (!verified) { + WH_ERROR_PRINT("SLH-DSA non-DMA verification failed\n"); + ret = WH_TEST_FAIL; + goto done; + } + + /* A tampered signature must come back as a result, not an error */ + sig[0] ^= 0xFF; + ret = wh_Client_SlhDsaVerify(ctx, sig, sigLen, msg, sizeof(msg), + &verified, key, NULL, 0, + WC_HASH_TYPE_NONE, 0); + if (ret != 0) { + WH_ERROR_PRINT("Verify with modified sig returned %d\n", ret); + goto done; + } + if (verified) { + WH_ERROR_PRINT("SLH-DSA non-DMA verified a bad signature\n"); + ret = WH_TEST_FAIL; + goto done; + } + } + + /* FIPS 205 context string */ + { + byte msg[] = "Context test message non-DMA"; + const byte context[] = {'w', 'o', 'l', 'f', 'H', 'S', 'M'}; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigLen = sizeof(sig); + int verified = 0; + + ret = wh_Client_SlhDsaSign(ctx, msg, sizeof(msg), sig, &sigLen, key, + context, (byte)sizeof(context), + WC_HASH_TYPE_NONE, NULL, 0, 1, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to sign with context: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaVerify(ctx, sig, sigLen, msg, sizeof(msg), + &verified, key, context, + (byte)sizeof(context), WC_HASH_TYPE_NONE, + 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to verify with context: %d\n", ret); + goto done; + } + if (!verified) { + WH_ERROR_PRINT("SLH-DSA context verification failed\n"); + ret = WH_TEST_FAIL; + goto done; + } + + /* The context is bound into the signature, so a different one fails */ + ret = wh_Client_SlhDsaVerify(ctx, sig, sigLen, msg, sizeof(msg), + &verified, key, NULL, 0, + WC_HASH_TYPE_NONE, 0); + if (ret != 0) { + WH_ERROR_PRINT("Verify with dropped context returned %d\n", ret); + goto done; + } + if (verified) { + WH_ERROR_PRINT("SLH-DSA verified across differing contexts\n"); + ret = WH_TEST_FAIL; + goto done; + } + } + +#ifndef NO_SHA256 + /* HashSLH-DSA over a caller-supplied digest */ + { + byte digest[WC_SHA256_DIGEST_SIZE]; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigLen = sizeof(sig); + int verified = 0; + + memset(digest, 0x5A, sizeof(digest)); + + ret = wh_Client_SlhDsaSign(ctx, digest, sizeof(digest), sig, &sigLen, + key, NULL, 0, WC_HASH_TYPE_SHA256, NULL, 0, + 1, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to pre-hash sign: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaVerify(ctx, sig, sigLen, digest, sizeof(digest), + &verified, key, NULL, 0, + WC_HASH_TYPE_SHA256, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to pre-hash verify: %d\n", ret); + goto done; + } + if (!verified) { + WH_ERROR_PRINT("SLH-DSA pre-hash verification failed\n"); + ret = WH_TEST_FAIL; + goto done; + } + } +#endif /* !NO_SHA256 */ + + WH_TEST_PRINT("SLH-DSA NON-DMA DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} + +/* Generate a key that stays on the server and use it purely by key ID. This is + * the case that has no local key material at all, so it also covers the + * deterministic signing path where the randomizer has to be derived from the + * server's copy of the key. */ +static int _whTest_CryptoSlhDsaCachedKey(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + whKeyId keyId = WH_KEYID_ERASED; + SlhDsaKey pub[1]; + SlhDsaKey handle[1]; + int pubInit = 0; + int handleInit = 0; + uint8_t label[] = "SlhDsaCached"; + + ret = wc_SlhDsaKey_Init(pub, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to init SLH-DSA pub key: %d\n", ret); + return ret; + } + pubInit = 1; + + ret = wh_Client_SlhDsaMakeCacheKeyAndExportPublic( + ctx, WH_TEST_SLHDSA_COMM_PARAM, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, sizeof(label), + label, pub); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache SLH-DSA key: %d\n", ret); + goto done; + } + + /* A bare handle: no key material, only the server key ID */ + ret = wc_SlhDsaKey_Init(handle, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to init SLH-DSA handle: %d\n", ret); + goto done; + } + handleInit = 1; + + ret = wh_Client_SlhDsaSetKeyId(handle, keyId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to set SLH-DSA key id: %d\n", ret); + goto done; + } + + { + byte msg[] = "Signed by a key that never left the HSM"; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigLen = sizeof(sig); + + /* Deterministic: wolfCrypt derives the randomizer from the key's own + * PK.seed, which only the server has. */ + ret = wc_SlhDsaKey_SignDeterministic(handle, NULL, 0, msg, sizeof(msg), + sig, &sigLen); + if (ret != 0) { + WH_ERROR_PRINT("Failed to sign with a cached SLH-DSA key: %d\n", + ret); + goto done; + } + + /* The exported public key verifies it, through the server */ + ret = wc_SlhDsaKey_Verify(pub, NULL, 0, msg, sizeof(msg), sig, sigLen); + if (ret != 0) { + WH_ERROR_PRINT("Cached-key signature did not verify: %d\n", ret); + goto done; + } + + /* Signing the same message twice deterministically must repeat */ + { + byte sig2[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sig2Len = sizeof(sig2); + + ret = wc_SlhDsaKey_SignDeterministic(handle, NULL, 0, msg, + sizeof(msg), sig2, &sig2Len); + if (ret != 0) { + WH_ERROR_PRINT("Second deterministic sign failed: %d\n", ret); + goto done; + } + if ((sig2Len != sigLen) || (memcmp(sig, sig2, sigLen) != 0)) { + WH_ERROR_PRINT("Deterministic SLH-DSA signatures differ\n"); + ret = WH_TEST_FAIL; + goto done; + } + } + } + + WH_TEST_PRINT("SLH-DSA CACHED KEY DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + if (handleInit) { + wc_SlhDsaKey_Free(handle); + } + if (pubInit) { + wc_SlhDsaKey_Free(pub); + } + return ret; +} + +/* FIPS 205 internal interface: the caller builds M' and the server signs it + * directly. */ +static int _whTest_CryptoSlhDsaMPrime(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + /* M' for a pure signature with an empty context: 0x00 || ctxSz || msg */ + byte mprime[] = {0x00, 0x00, 'm', 'p', 'r', 'i', 'm', 'e'}; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigLen = sizeof(sig); + byte addRnd[WC_SLHDSA_MAX_SEED]; + + memset(addRnd, 0x42, sizeof(addRnd)); + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to initialize SLH-DSA key: %d\n", ret); + return ret; + } + + ret = wh_Client_SlhDsaMakeExportKey(ctx, WH_TEST_SLHDSA_COMM_PARAM, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate SLH-DSA key: %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_SignMsgWithRandom(key, mprime, sizeof(mprime), sig, + &sigLen, addRnd); + if (ret != 0) { + WH_ERROR_PRINT("Failed to sign M': %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_VerifyMsg(key, mprime, sizeof(mprime), sig, sigLen); + if (ret != 0) { + WH_ERROR_PRINT("Failed to verify M' signature: %d\n", ret); + goto done; + } + + /* The same signature must not verify against a different M' */ + mprime[2] ^= 0xFF; + ret = wc_SlhDsaKey_VerifyMsg(key, mprime, sizeof(mprime), sig, sigLen); + if (ret == 0) { + WH_ERROR_PRINT("M' verification accepted the wrong message\n"); + ret = WH_TEST_FAIL; + goto done; + } + + WH_TEST_PRINT("SLH-DSA MPRIME DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} + +/* wc_SlhDsaKey_CheckKey against a server-resident private key. */ +static int _whTest_CryptoSlhDsaCheckPrivKey(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + whKeyId keyId = WH_KEYID_ERASED; + SlhDsaKey pub[1]; + SlhDsaKey handle[1]; + int pubInit = 0; + int handleInit = 0; + uint8_t label[] = "SlhDsaCheck"; + + ret = wc_SlhDsaKey_Init(pub, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + pubInit = 1; + + ret = wh_Client_SlhDsaMakeCacheKeyAndExportPublic( + ctx, WH_TEST_SLHDSA_COMM_PARAM, &keyId, WH_NVM_FLAGS_USAGE_SIGN, + sizeof(label), label, pub); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache SLH-DSA key: %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_Init(handle, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + goto done; + } + handleInit = 1; + (void)wh_Client_SlhDsaSetKeyId(handle, keyId); + + { + byte expected[WC_SLHDSA_MAX_PUB_LEN]; + int pubSz; + + pubSz = wc_SlhDsaKey_PublicSize(pub); + if ((pubSz <= 0) || ((word32)pubSz > sizeof(expected))) { + WH_ERROR_PRINT("Bad SLH-DSA public size %d\n", pubSz); + ret = WH_TEST_FAIL; + goto done; + } + ret = wc_SlhDsaKey_ExportPublic(pub, expected, (word32*)&pubSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to export SLH-DSA public key: %d\n", ret); + goto done; + } + + /* wc_SlhDsaKey_CheckKey takes the public key from the key struct, so + * it exercises the callback with the handle's own (server-held) + * material. */ + ret = wc_SlhDsaKey_CheckKey(handle); + if (ret != 0) { + WH_ERROR_PRINT("CheckKey rejected the cached key: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaCheckPrivKey(ctx, handle, expected, + (word32)pubSz); + if (ret != 0) { + WH_ERROR_PRINT("CheckPrivKey rejected the matching key: %d\n", ret); + goto done; + } + + /* A public key that does not belong to the private key must be + * rejected rather than silently accepted. */ + expected[0] ^= 0xFF; + ret = wh_Client_SlhDsaCheckPrivKey(ctx, handle, expected, + (word32)pubSz); + if (ret != WC_KEY_MISMATCH_E) { + WH_ERROR_PRINT("CheckPrivKey accepted a mismatched key: %d\n", ret); + ret = WH_TEST_FAIL; + goto done; + } + } + + WH_TEST_PRINT("SLH-DSA CHECKPRIVKEY DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + if (handleInit) { + wc_SlhDsaKey_Free(handle); + } + if (pubInit) { + wc_SlhDsaKey_Free(pub); + } + return ret; +} + +#ifndef NO_SHA256 +/* A digest whose length does not match the declared pre-hash algorithm is a + * malformed request, not a signature that failed to verify. The two must stay + * distinguishable: reporting res=0 would tell the caller the signature is bad + * when the real problem is their own argument. */ +static int _whTest_CryptoSlhDsaBadDigestLen(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + byte digest[WC_SHA256_DIGEST_SIZE]; + byte sig[WH_TEST_SLHDSA_COMM_SIG_LEN]; + word32 sigLen = sizeof(sig); + int verified = 1; + + memset(digest, 0x3C, sizeof(digest)); + memset(sig, 0, sizeof(sig)); + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + + ret = wh_Client_SlhDsaMakeExportKey(ctx, WH_TEST_SLHDSA_COMM_PARAM, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate SLH-DSA key: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaSign(ctx, digest, sizeof(digest), sig, &sigLen, key, + NULL, 0, WC_HASH_TYPE_SHA256, NULL, 0, 1, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to pre-hash sign: %d\n", ret); + goto done; + } + + /* Same signature, same hash type, digest one byte short */ + ret = wh_Client_SlhDsaVerify(ctx, sig, sigLen, digest, + (word32)sizeof(digest) - 1, &verified, key, + NULL, 0, WC_HASH_TYPE_SHA256, 0); + if (ret == 0) { + WH_ERROR_PRINT("Short digest reported as a verify result (res=%d) " + "instead of an error\n", + verified); + ret = WH_TEST_FAIL; + goto done; + } + + WH_TEST_PRINT("SLH-DSA BAD DIGEST LEN DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} +#endif /* !NO_SHA256 */ + +/* A caller buffer smaller than the signature must report WH_ERROR_BUFFER_SIZE + * and the length that would have been needed, without writing past the end. */ +static int _whTest_CryptoSlhDsaBufferTooSmall(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + const byte msg[] = "slh-dsa buf size test"; + uint8_t small_sig[16] = {0}; + word32 small_buf_sz = (word32)sizeof(small_sig); + word32 sig_len; + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_COMM_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + + ret = wh_Client_SlhDsaMakeExportKey(ctx, WH_TEST_SLHDSA_COMM_PARAM, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate SLH-DSA key: %d\n", ret); + goto done; + } + + sig_len = small_buf_sz; + ret = wh_Client_SlhDsaSign(ctx, msg, (word32)sizeof(msg), small_sig, + &sig_len, key, NULL, 0, WC_HASH_TYPE_NONE, NULL, + 0, 1, 0); + if (ret != WH_ERROR_BUFFER_SIZE) { + WH_ERROR_PRINT("SlhDsaSign small buf expected WH_ERROR_BUFFER_SIZE, " + "got %d\n", + ret); + ret = WH_TEST_FAIL; + goto done; + } + if (sig_len <= small_buf_sz) { + WH_ERROR_PRINT("SlhDsaSign small buf reported size %u not greater " + "than %u\n", + (unsigned)sig_len, (unsigned)small_buf_sz); + ret = WH_TEST_FAIL; + goto done; + } + + WH_TEST_PRINT("SLH-DSA BUFFER SIZE DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} + +#endif /* WH_TEST_SLHDSA_COMM_PARAM */ + +#ifdef WH_TEST_SLHDSA_FAST_PARAM +/* NIST CAVP SLH-DSA-SHAKE-128f keyGen vector (tgId=4, tcId=31). The seeded + * generation path is what makes a known-answer test possible at all: a random + * key generation has no expected output to compare against. */ +static const byte whTestSlhDsaKatSeed[] = { + /* SK.seed */ + 0x39, 0x56, 0xAB, 0x39, 0x1B, 0x4D, 0x22, 0xFC, + 0x90, 0x7A, 0xF0, 0x74, 0x03, 0x26, 0xD0, 0x61, + /* SK.prf */ + 0xAB, 0x0E, 0xB2, 0x06, 0x43, 0x6F, 0x2B, 0x86, + 0xEB, 0xE0, 0x86, 0xD7, 0x77, 0x39, 0xB3, 0xE4, + /* PK.seed */ + 0x56, 0x50, 0x5C, 0x22, 0x9F, 0x4E, 0x7F, 0xA6, + 0xB2, 0x01, 0x71, 0x4C, 0x7D, 0xCC, 0x9D, 0xA3 +}; + +static const byte whTestSlhDsaKatPub[] = { + /* PK.seed */ + 0x56, 0x50, 0x5C, 0x22, 0x9F, 0x4E, 0x7F, 0xA6, + 0xB2, 0x01, 0x71, 0x4C, 0x7D, 0xCC, 0x9D, 0xA3, + /* PK.root */ + 0x66, 0x57, 0x8F, 0x1F, 0x24, 0xC3, 0xFE, 0x37, + 0x1C, 0x97, 0xC1, 0x4C, 0xE0, 0xE7, 0x9C, 0xDC +}; + +/* Seeded generation over the comm buffer. Only the tiny key material crosses + * the wire here, so the fast parameter set is usable even without DMA. */ +static int _whTest_CryptoSlhDsaSeededKat(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + byte pub[WC_SLHDSA_MAX_PUB_LEN]; + word32 pubSz = sizeof(pub); + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_FAST_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + + /* Through wc_SlhDsaKey_MakeKeyWithRandom so the seeded key generation + * takes the callback path an application would. */ + { + word32 n = (word32)(sizeof(whTestSlhDsaKatSeed) / 3); + + ret = wc_SlhDsaKey_MakeKeyWithRandom( + key, whTestSlhDsaKatSeed, n, whTestSlhDsaKatSeed + n, n, + whTestSlhDsaKatSeed + 2 * n, n); + } + if (ret != 0) { + WH_ERROR_PRINT("Failed seeded SLH-DSA keygen: %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_ExportPublic(key, pub, &pubSz); + if (ret != 0) { + WH_ERROR_PRINT("Failed to export seeded public key: %d\n", ret); + goto done; + } + + if ((pubSz != sizeof(whTestSlhDsaKatPub)) || + (memcmp(pub, whTestSlhDsaKatPub, pubSz) != 0)) { + WH_ERROR_PRINT("Seeded SLH-DSA public key does not match the KAT\n"); + ret = WH_TEST_FAIL; + goto done; + } + + WH_TEST_PRINT("SLH-DSA SEEDED KAT DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} + +#ifdef WOLFHSM_CFG_DMA +/* The fast parameter set signs a 17088-byte signature, which no reasonable + * comm buffer holds, so this is the DMA path end to end. */ +static int _whTest_CryptoSlhDsaDmaClient(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + byte msg[] = "Test message for DMA SLH-DSA"; + byte sig[WH_TEST_SLHDSA_FAST_SIG_LEN]; + word32 sigLen = sizeof(sig); + int verified = 0; + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_FAST_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + + ret = wh_Client_SlhDsaMakeExportKeyDma(ctx, WH_TEST_SLHDSA_FAST_PARAM, key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate SLH-DSA key over DMA: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaSignDma(ctx, msg, sizeof(msg), sig, &sigLen, key, + NULL, 0, WC_HASH_TYPE_NONE, NULL, 0, 1, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to sign over DMA: %d\n", ret); + goto done; + } + if (sigLen != WH_TEST_SLHDSA_FAST_SIG_LEN) { + WH_ERROR_PRINT("DMA signature length %u, expected %u\n", + (unsigned)sigLen, + (unsigned)WH_TEST_SLHDSA_FAST_SIG_LEN); + ret = WH_TEST_FAIL; + goto done; + } + + ret = wh_Client_SlhDsaVerifyDma(ctx, sig, sigLen, msg, sizeof(msg), + &verified, key, NULL, 0, WC_HASH_TYPE_NONE, + 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to verify over DMA: %d\n", ret); + goto done; + } + if (!verified) { + WH_ERROR_PRINT("SLH-DSA DMA verification failed\n"); + ret = WH_TEST_FAIL; + goto done; + } + + sig[0] ^= 0xFF; + ret = wh_Client_SlhDsaVerifyDma(ctx, sig, sigLen, msg, sizeof(msg), + &verified, key, NULL, 0, WC_HASH_TYPE_NONE, + 0); + if (ret != 0) { + WH_ERROR_PRINT("DMA verify with modified sig returned %d\n", ret); + goto done; + } + if (verified) { + WH_ERROR_PRINT("SLH-DSA DMA verified a bad signature\n"); + ret = WH_TEST_FAIL; + goto done; + } + + WH_TEST_PRINT("SLH-DSA DMA DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} + +/* A cached key driven over DMA, plus a round trip of the key material through + * the DMA import and export calls. */ +static int _whTest_CryptoSlhDsaDmaCachedKey(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + whKeyId keyId = WH_KEYID_ERASED; + SlhDsaKey pub[1]; + SlhDsaKey handle[1]; + int pubInit = 0; + int handleInit = 0; + uint8_t label[] = "SlhDsaDmaCached"; + + ret = wc_SlhDsaKey_Init(pub, WH_TEST_SLHDSA_FAST_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + pubInit = 1; + + ret = wh_Client_SlhDsaMakeCacheKeyDma( + ctx, WH_TEST_SLHDSA_FAST_PARAM, &keyId, + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, sizeof(label), + label, pub); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache SLH-DSA key over DMA: %d\n", ret); + goto done; + } + + ret = wc_SlhDsaKey_Init(handle, WH_TEST_SLHDSA_FAST_PARAM, NULL, devId); + if (ret != 0) { + goto done; + } + handleInit = 1; + (void)wh_Client_SlhDsaSetKeyId(handle, keyId); + + { + byte msg[] = "DMA signed by a key that never left the HSM"; + byte sig[WH_TEST_SLHDSA_FAST_SIG_LEN]; + word32 sigLen = sizeof(sig); + int verified = 0; + + ret = wh_Client_SlhDsaSignDma(ctx, msg, sizeof(msg), sig, &sigLen, + handle, NULL, 0, WC_HASH_TYPE_NONE, NULL, + 0, 1, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to DMA sign with a cached key: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaVerifyDma(ctx, sig, sigLen, msg, sizeof(msg), + &verified, pub, NULL, 0, + WC_HASH_TYPE_NONE, 0); + if (ret != 0) { + WH_ERROR_PRINT("Failed to DMA verify a cached-key sig: %d\n", ret); + goto done; + } + if (!verified) { + WH_ERROR_PRINT("Cached-key DMA signature did not verify\n"); + ret = WH_TEST_FAIL; + goto done; + } + } + + /* Export the public key on its own and check it matches what keygen + * already handed back. */ + { + SlhDsaKey exported[1]; + byte a[WC_SLHDSA_MAX_PUB_LEN]; + byte b[WC_SLHDSA_MAX_PUB_LEN]; + word32 aSz = sizeof(a); + word32 bSz = sizeof(b); + + ret = wc_SlhDsaKey_Init(exported, WH_TEST_SLHDSA_FAST_PARAM, NULL, + devId); + if (ret != 0) { + goto done; + } + ret = wh_Client_SlhDsaExportPublicKeyDma(ctx, keyId, exported, 0, NULL); + if (ret == 0) { + ret = wc_SlhDsaKey_ExportPublic(pub, a, &aSz); + } + if (ret == 0) { + ret = wc_SlhDsaKey_ExportPublic(exported, b, &bSz); + } + if ((ret == 0) && ((aSz != bSz) || (memcmp(a, b, aSz) != 0))) { + WH_ERROR_PRINT("Exported SLH-DSA public key does not match\n"); + ret = WH_TEST_FAIL; + } + wc_SlhDsaKey_Free(exported); + if (ret != 0) { + WH_ERROR_PRINT("SLH-DSA DMA public key export failed: %d\n", ret); + goto done; + } + } + + WH_TEST_PRINT("SLH-DSA DMA CACHED KEY DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + if (handleInit) { + wc_SlhDsaKey_Free(handle); + } + if (pubInit) { + wc_SlhDsaKey_Free(pub); + } + return ret; +} +#endif /* WOLFHSM_CFG_DMA */ +#endif /* WH_TEST_SLHDSA_FAST_PARAM */ + +#ifdef WH_TEST_SLHDSA_COMM_PARAM +/* The comm buffer cannot carry the larger parameter sets, so the server must + * say so rather than truncating the signature. */ +#if defined(WOLFSSL_SLHDSA_PARAM_192S) || defined(WOLFSSL_SLHDSA_PARAM_256S) +#if defined(WOLFSSL_SLHDSA_PARAM_192S) +#define WH_TEST_SLHDSA_OVERSIZE_PARAM SLHDSA_SHAKE192S +#else +#define WH_TEST_SLHDSA_OVERSIZE_PARAM SLHDSA_SHAKE256S +#endif +static int _whTest_CryptoSlhDsaCommBufferLimit(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + SlhDsaKey key[1]; + byte msg[] = "too big for the comm buffer"; + byte sig[WC_SLHDSA_MAX_SIG_LEN]; + word32 sigLen = sizeof(sig); + + ret = wc_SlhDsaKey_Init(key, WH_TEST_SLHDSA_OVERSIZE_PARAM, NULL, devId); + if (ret != 0) { + return ret; + } + + ret = wh_Client_SlhDsaMakeExportKey(ctx, WH_TEST_SLHDSA_OVERSIZE_PARAM, + key); + if (ret != 0) { + WH_ERROR_PRINT("Failed to generate oversize SLH-DSA key: %d\n", ret); + goto done; + } + + ret = wh_Client_SlhDsaSign(ctx, msg, sizeof(msg), sig, &sigLen, key, NULL, + 0, WC_HASH_TYPE_NONE, NULL, 0, 1, 0); + if (ret != WH_ERROR_BUFFER_SIZE) { + WH_ERROR_PRINT("Oversize sign expected WH_ERROR_BUFFER_SIZE, got %d\n", + ret); + ret = WH_TEST_FAIL; + goto done; + } + + WH_TEST_PRINT("SLH-DSA COMM LIMIT DEVID=0x%X SUCCESS\n", devId); + ret = 0; + +done: + wc_SlhDsaKey_Free(key); + return ret; +} +#endif /* 192S || 256S */ +#endif /* WH_TEST_SLHDSA_COMM_PARAM */ + +int whTest_Crypto_SlhDsa(whClientContext* ctx) +{ +#ifdef WH_TEST_SLHDSA_COMM_PARAM + /* The wolfCrypt-API driver is the path an application actually takes, so + * run it in each dispatch mode the build offers. */ + int i; + + for (i = 0; i < WH_TEST_DMA_MODE_CNT; i++) { + (void)wh_Client_SetDmaMode(ctx, i); + WH_TEST_RETURN_ON_FAIL( + _whTest_SlhDsaWolfCryptImpl(ctx, WH_CLIENT_DEVID(ctx))); + } + (void)wh_Client_SetDmaMode(ctx, 0); + + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaClient(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaCachedKey(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaMPrime(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaCheckPrivKey(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaBufferTooSmall(ctx)); +#ifndef NO_SHA256 + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaBadDigestLen(ctx)); +#endif +#ifdef WH_TEST_SLHDSA_OVERSIZE_PARAM + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaCommBufferLimit(ctx)); +#endif +#endif /* WH_TEST_SLHDSA_COMM_PARAM */ + +#ifdef WH_TEST_SLHDSA_FAST_PARAM + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaSeededKat(ctx)); +#ifdef WOLFHSM_CFG_DMA + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaDmaClient(ctx)); + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoSlhDsaDmaCachedKey(ctx)); +#endif +#endif /* WH_TEST_SLHDSA_FAST_PARAM */ + + (void)ctx; + return 0; +} + +#endif /* WOLFSSL_HAVE_SLHDSA */ + +#endif /* !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test-refactor/misc/wh_test_check_struct_padding.c b/test-refactor/misc/wh_test_check_struct_padding.c index 403c93d1d..134e123aa 100644 --- a/test-refactor/misc/wh_test_check_struct_padding.c +++ b/test-refactor/misc/wh_test_check_struct_padding.c @@ -141,6 +141,14 @@ whMessageCrypto_MlDsaSignRequest pkMldsaSignReq; whMessageCrypto_MlDsaSignResponse pkMldsaSignRes; whMessageCrypto_MlDsaVerifyRequest pkMldsaVerifyReq; whMessageCrypto_MlDsaVerifyResponse pkMldsaVerifyRes; +whMessageCrypto_SlhDsaKeyGenRequest pkSlhdsaKeygenReq; +whMessageCrypto_SlhDsaKeyGenResponse pkSlhdsaKeygenRes; +whMessageCrypto_SlhDsaSignRequest pkSlhdsaSignReq; +whMessageCrypto_SlhDsaSignResponse pkSlhdsaSignRes; +whMessageCrypto_SlhDsaVerifyRequest pkSlhdsaVerifyReq; +whMessageCrypto_SlhDsaVerifyResponse pkSlhdsaVerifyRes; +whMessageCrypto_SlhDsaCheckPrivKeyRequest pkSlhdsaCheckReq; +whMessageCrypto_SlhDsaCheckPrivKeyResponse pkSlhdsaCheckRes; whMessageCrypto_MlKemKeyGenRequest pkMlkemKeygenReq; whMessageCrypto_MlKemKeyGenResponse pkMlkemKeygenRes; whMessageCrypto_MlKemEncapsRequest pkMlkemEncapsReq; @@ -163,6 +171,12 @@ whMessageCrypto_MlDsaSignDmaRequest pqMldsaSignDmaReq; whMessageCrypto_MlDsaSignDmaResponse pqMldsaSignDmaRes; whMessageCrypto_MlDsaVerifyDmaRequest pqMldsaVerifyDmaReq; whMessageCrypto_MlDsaVerifyDmaResponse pqMldsaVerifyDmaRes; +whMessageCrypto_SlhDsaKeyGenDmaRequest pqSlhdsaKeygenDmaReq; +whMessageCrypto_SlhDsaKeyGenDmaResponse pqSlhdsaKeygenDmaRes; +whMessageCrypto_SlhDsaSignDmaRequest pqSlhdsaSignDmaReq; +whMessageCrypto_SlhDsaSignDmaResponse pqSlhdsaSignDmaRes; +whMessageCrypto_SlhDsaVerifyDmaRequest pqSlhdsaVerifyDmaReq; +whMessageCrypto_SlhDsaVerifyDmaResponse pqSlhdsaVerifyDmaRes; whMessageCrypto_CmacAesDmaRequest cmacDmaReq; whMessageCrypto_CmacAesDmaResponse cmacDmaRes; whMessageCrypto_MlKemKeyGenDmaRequest pkMlkemKeygenDmaReq; diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index 02e121ebe..57a144c67 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -75,6 +75,7 @@ WH_TEST_DECL(whTest_Crypto_MlDsa); WH_TEST_DECL(whTest_Crypto_Rng); WH_TEST_DECL(whTest_Crypto_Rsa); WH_TEST_DECL(whTest_Crypto_Sha); +WH_TEST_DECL(whTest_Crypto_SlhDsa); WH_TEST_DECL(whTest_Crypto_Sha3); WH_TEST_DECL(whTest_Crypto_Xmss); WH_TEST_DECL(whTest_CryptoEcc256); @@ -153,6 +154,7 @@ const whTestCase whTestsClient[] = { {"whTest_Crypto_Rng", whTest_Crypto_Rng}, {"whTest_Crypto_Rsa", whTest_Crypto_Rsa}, {"whTest_Crypto_Sha", whTest_Crypto_Sha}, + {"whTest_Crypto_SlhDsa", whTest_Crypto_SlhDsa}, {"whTest_Crypto_Sha3", whTest_Crypto_Sha3}, {"whTest_Crypto_Xmss", whTest_Crypto_Xmss}, {"whTest_CryptoEcc256", whTest_CryptoEcc256}, diff --git a/test/config/user_settings.h b/test/config/user_settings.h index f237ccaa4..6a00ed9a9 100644 --- a/test/config/user_settings.h +++ b/test/config/user_settings.h @@ -141,6 +141,12 @@ * compiled and exercised by the test suite. */ #define WOLFSSL_HASH_FLAGS +/* SLH-DSA Options. Only the 128-bit category is built: 128s is the one + * signature that fits the comm buffer and 128f keeps the DMA tests quick. */ +#define WOLFSSL_HAVE_SLHDSA +#define WOLFSSL_SLHDSA_PARAM_NO_192 +#define WOLFSSL_SLHDSA_PARAM_NO_256 + /* ML-KEM Options */ #define WOLFSSL_HAVE_MLKEM /* LMS / HSS Options (RFC 8554, NIST SP 800-208) */ diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index bf29822a6..556fa471b 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -139,6 +139,14 @@ whMessageCrypto_MlDsaSignRequest pkMldsaSignReq; whMessageCrypto_MlDsaSignResponse pkMldsaSignRes; whMessageCrypto_MlDsaVerifyRequest pkMldsaVerifyReq; whMessageCrypto_MlDsaVerifyResponse pkMldsaVerifyRes; +whMessageCrypto_SlhDsaKeyGenRequest pkSlhdsaKeygenReq; +whMessageCrypto_SlhDsaKeyGenResponse pkSlhdsaKeygenRes; +whMessageCrypto_SlhDsaSignRequest pkSlhdsaSignReq; +whMessageCrypto_SlhDsaSignResponse pkSlhdsaSignRes; +whMessageCrypto_SlhDsaVerifyRequest pkSlhdsaVerifyReq; +whMessageCrypto_SlhDsaVerifyResponse pkSlhdsaVerifyRes; +whMessageCrypto_SlhDsaCheckPrivKeyRequest pkSlhdsaCheckReq; +whMessageCrypto_SlhDsaCheckPrivKeyResponse pkSlhdsaCheckRes; whMessageCrypto_MlKemKeyGenRequest pkMlkemKeygenReq; whMessageCrypto_MlKemKeyGenResponse pkMlkemKeygenRes; whMessageCrypto_MlKemEncapsRequest pkMlkemEncapsReq; @@ -161,6 +169,12 @@ whMessageCrypto_MlDsaSignDmaRequest pqMldsaSignDmaReq; whMessageCrypto_MlDsaSignDmaResponse pqMldsaSignDmaRes; whMessageCrypto_MlDsaVerifyDmaRequest pqMldsaVerifyDmaReq; whMessageCrypto_MlDsaVerifyDmaResponse pqMldsaVerifyDmaRes; +whMessageCrypto_SlhDsaKeyGenDmaRequest pqSlhdsaKeygenDmaReq; +whMessageCrypto_SlhDsaKeyGenDmaResponse pqSlhdsaKeygenDmaRes; +whMessageCrypto_SlhDsaSignDmaRequest pqSlhdsaSignDmaReq; +whMessageCrypto_SlhDsaSignDmaResponse pqSlhdsaSignDmaRes; +whMessageCrypto_SlhDsaVerifyDmaRequest pqSlhdsaVerifyDmaReq; +whMessageCrypto_SlhDsaVerifyDmaResponse pqSlhdsaVerifyDmaRes; whMessageCrypto_CmacAesDmaRequest cmacDmaReq; whMessageCrypto_CmacAesDmaResponse cmacDmaRes; whMessageCrypto_MlKemKeyGenDmaRequest pkMlkemKeygenDmaReq; diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index 9a0e921c6..cf11eef13 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -52,6 +52,7 @@ #include "wolfssl/wolfcrypt/ed25519.h" #include "wolfssl/wolfcrypt/wc_mldsa.h" #include "wolfssl/wolfcrypt/wc_mlkem.h" +#include "wolfssl/wolfcrypt/wc_slhdsa.h" #include "wolfssl/wolfcrypt/hmac.h" #ifdef WOLFSSL_SHA3 #include "wolfssl/wolfcrypt/sha3.h" @@ -3374,6 +3375,385 @@ int wh_Client_MlDsaCheckPrivKeyDma(whClientContext* ctx, wc_MlDsaKey* key, #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA + +/** + * @brief Associates an SLH-DSA key with a specific key ID. + * + * This function sets the device context of an SLH-DSA key to the specified + * key ID. On the server side, this key ID is used to reference the key stored + * in the HSM. + * + * @param[in] key Pointer to the SLH-DSA key structure. + * @param[in] keyId Key ID to be associated with the SLH-DSA key. + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaSetKeyId(SlhDsaKey* key, whKeyId keyId); + +/** + * @brief Gets the wolfHSM keyId being used by the wolfCrypt struct. + * + * @param[in] key Pointer to the SLH-DSA key structure. + * @param[out] outId Pointer to the key ID to return. + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaGetKeyId(SlhDsaKey* key, whKeyId* outId); + +/** + * @brief Import an SLH-DSA key to the server key cache. + * + * @param[in] ctx Pointer to the client context + * @param[in] key Pointer to the key to import + * @param[in,out] inout_keyId Pointer to key ID to use/receive + * @param[in] flags Flags to control key persistence + * @param[in] label_len Length of optional label + * @param[in] label Optional label to associate with key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaImportKey(whClientContext* ctx, SlhDsaKey* key, + whKeyId* inout_keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label); + +/** + * @brief Export an SLH-DSA key from the server. + * + * @param[in] ctx Pointer to the client context + * @param[in] keyId ID of key to export + * @param[out] key Pointer to receive exported key + * @param[in] label_len Length of optional label buffer + * @param[in] label Optional buffer to receive key label + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaExportKey(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label); + +/** + * @brief Exports only the public part of a cached SLH-DSA key. + * + * The private key stays inside the HSM. The caller is responsible for + * initializing key with wc_SlhDsaKey_Init; the parameter set is taken from + * the key OID in the exported DER, so the placeholder set the caller used + * does not have to match. + * + * @param[in] ctx Pointer to the client context + * @param[in] keyId Server key ID whose public key should be exported + * @param[out] key Pointer to receive the exported public key + * @param[in] label_len Length of optional label buffer + * @param[in] label Optional buffer to receive key label + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaExportPublicKey(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label); + +/** + * @brief Generate an SLH-DSA key on the server and leave it cached there. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[in,out] inout_key_id Pointer to key ID to use/receive + * @param[in] flags Flags to control key persistence + * @param[in] label_len Length of optional label + * @param[in] label Optional label to associate with key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeCacheKey(whClientContext* ctx, int param, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, uint8_t* label); + +/** + * @brief Generate a cached SLH-DSA key and export its public part. + * + * On success pub is a usable handle to the cached private key: its key ID and + * the client's HSM devId are stamped into it. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[in,out] inout_key_id Pointer to key ID to use/receive + * @param[in] flags Flags to control key persistence, must not be EPHEMERAL + * @param[in] label_len Length of optional label + * @param[in] label Optional label to associate with key + * @param[out] pub Pointer to receive the public key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeCacheKeyAndExportPublic( + whClientContext* ctx, int param, whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, SlhDsaKey* pub); + +/** + * @brief Generate an ephemeral SLH-DSA key and export it to the caller. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[out] key Pointer to receive the generated key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeExportKey(whClientContext* ctx, int param, + SlhDsaKey* key); + +/** + * @brief Generate an ephemeral SLH-DSA key from a caller-supplied seed. + * + * The seed is the contiguous SK.seed || SK.prf || PK.seed, 3n bytes for the + * requested parameter set. Deterministic generation makes known-answer tests + * and reproducible provisioning possible. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[in] seed Pointer to the 3n seed bytes + * @param[in] seedSz Length of seed in bytes + * @param[out] key Pointer to receive the generated key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeExportKeyFromSeed(whClientContext* ctx, int param, + const byte* seed, word32 seedSz, + SlhDsaKey* key); + +/** + * @brief Generate a cached SLH-DSA key from a caller-supplied seed. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[in] seed Pointer to the 3n seed bytes + * @param[in] seedSz Length of seed in bytes + * @param[in,out] inout_key_id Pointer to key ID to use/receive + * @param[in] flags Flags to control key persistence + * @param[in] label_len Length of optional label + * @param[in] label Optional label to associate with key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeCacheKeyFromSeed(whClientContext* ctx, int param, + const byte* seed, word32 seedSz, + whKeyId* inout_key_id, + whNvmFlags flags, uint16_t label_len, + uint8_t* label); + +/** + * @brief Sign a message or digest with an SLH-DSA key held by the server. + * + * Covers the whole FIPS 205 signing surface. preHashType selects pure + * SLH-DSA (WC_HASH_TYPE_NONE) or HashSLH-DSA. isMPrime signs a caller-built + * M' directly, in which case context and preHashType are ignored. A non-empty + * addRnd supplies the randomizer explicitly; otherwise randomized selects + * between a server-generated randomizer and deterministic signing. + * + * @param[in] ctx Pointer to the client context + * @param[in] in Message, digest, or M' to sign + * @param[in] in_len Length of in in bytes + * @param[out] out Buffer to receive the signature + * @param[in,out] inout_len Capacity of out on entry, signature length on exit + * @param[in] key Key handle, either server-resident or holding key material + * @param[in] context FIPS 205 context string, may be NULL + * @param[in] contextLen Length of context, 0 to 255 + * @param[in] preHashType Pre-hash algorithm (enum wc_HashType) + * @param[in] addRnd Explicit randomizer, may be NULL + * @param[in] addRndSz Length of addRnd in bytes, 0 if none + * @param[in] randomized Non-zero to have the server pick the randomizer + * @param[in] isMPrime Non-zero when in is a caller-built M' + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaSign(whClientContext* ctx, const byte* in, word32 in_len, + byte* out, word32* inout_len, SlhDsaKey* key, + const byte* context, byte contextLen, + word32 preHashType, const byte* addRnd, byte addRndSz, + int randomized, int isMPrime); + +/** + * @brief Verify an SLH-DSA signature with a key held by the server. + * + * @param[in] ctx Pointer to the client context + * @param[in] sig Signature to verify + * @param[in] sig_len Length of sig in bytes + * @param[in] msg Message, digest, or M' that was signed + * @param[in] msg_len Length of msg in bytes + * @param[out] out_res Set to 1 when the signature verifies, 0 otherwise + * @param[in] key Key handle, either server-resident or holding key material + * @param[in] context FIPS 205 context string, may be NULL + * @param[in] contextLen Length of context, 0 to 255 + * @param[in] preHashType Pre-hash algorithm (enum wc_HashType) + * @param[in] isMPrime Non-zero when msg is a caller-built M' + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaVerify(whClientContext* ctx, const byte* sig, + word32 sig_len, const byte* msg, word32 msg_len, + int* out_res, SlhDsaKey* key, const byte* context, + byte contextLen, word32 preHashType, int isMPrime); + +/** + * @brief Check that a server-held SLH-DSA private key matches a public key. + * + * Passing NULL for pubKey (with pubKeySz 0) asks the server to check its copy + * of the private key for internal consistency, with nothing to compare it + * against. That is what a caller holding only a key ID can ask for. + * + * @param[in] ctx Pointer to the client context + * @param[in] key Key handle for the private key + * @param[in] pubKey Public key bytes, PK.seed || PK.root, or NULL + * @param[in] pubKeySz Length of pubKey in bytes, 2n, or 0 when pubKey is NULL + * @return int Returns 0 when they match, WC_KEY_MISMATCH_E when they do not, + * or a negative error code on failure. + */ +int wh_Client_SlhDsaCheckPrivKey(whClientContext* ctx, SlhDsaKey* key, + const byte* pubKey, word32 pubKeySz); + +#ifdef WOLFHSM_CFG_DMA + +/** + * @brief Import an SLH-DSA key to the server key cache using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] key Pointer to the key to import + * @param[in,out] inout_keyId Pointer to key ID to use/receive + * @param[in] flags Flags to control key persistence + * @param[in] label_len Length of optional label + * @param[in] label Optional label to associate with key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaImportKeyDma(whClientContext* ctx, SlhDsaKey* key, + whKeyId* inout_keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label); + +/** + * @brief Export an SLH-DSA key from the server using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] keyId ID of key to export + * @param[out] key Pointer to receive exported key + * @param[in] label_len Length of optional label buffer + * @param[in] label Optional buffer to receive key label + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaExportKeyDma(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label); + +/** + * @brief Export only the public part of a cached SLH-DSA key using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] keyId Server key ID whose public key should be exported + * @param[out] key Pointer to receive the exported public key + * @param[in] label_len Length of optional label buffer + * @param[in] label Optional buffer to receive key label + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, + SlhDsaKey* key, uint16_t label_len, + uint8_t* label); + +/** + * @brief Generate an ephemeral SLH-DSA key and export it using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[out] key Pointer to receive the generated key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeExportKeyDma(whClientContext* ctx, int param, + SlhDsaKey* key); + +/** + * @brief Generate an ephemeral SLH-DSA key from a seed using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[in] seed Pointer to the 3n seed bytes + * @param[in] seedSz Length of seed in bytes + * @param[out] key Pointer to receive the generated key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeExportKeyFromSeedDma(whClientContext* ctx, int param, + const byte* seed, word32 seedSz, + SlhDsaKey* key); + +/** + * @brief Generate a cached SLH-DSA key and export its public part using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] param Parameter set to generate (enum SlhDsaParam) + * @param[in,out] inout_key_id Pointer to key ID to use/receive + * @param[in] flags Flags to control key persistence, must not be EPHEMERAL + * @param[in] label_len Length of optional label + * @param[in] label Optional label to associate with key + * @param[out] pub Pointer to receive the public key + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaMakeCacheKeyDma(whClientContext* ctx, int param, + whKeyId* inout_key_id, whNvmFlags flags, + uint16_t label_len, const uint8_t* label, + SlhDsaKey* pub); + +/** + * @brief Sign with an SLH-DSA key held by the server using DMA. + * + * Arguments match wh_Client_SlhDsaSign; the message and signature travel by + * DMA rather than through the comm buffer, which is what makes the larger + * parameter sets usable. + * + * @param[in] ctx Pointer to the client context + * @param[in] in Message, digest, or M' to sign + * @param[in] in_len Length of in in bytes + * @param[out] out Buffer to receive the signature + * @param[in,out] out_len Capacity of out on entry, signature length on exit + * @param[in] key Key handle, either server-resident or holding key material + * @param[in] context FIPS 205 context string, may be NULL + * @param[in] contextLen Length of context, 0 to 255 + * @param[in] preHashType Pre-hash algorithm (enum wc_HashType) + * @param[in] addRnd Explicit randomizer, may be NULL + * @param[in] addRndSz Length of addRnd in bytes, 0 if none + * @param[in] randomized Non-zero to have the server pick the randomizer + * @param[in] isMPrime Non-zero when in is a caller-built M' + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaSignDma(whClientContext* ctx, const byte* in, + word32 in_len, byte* out, word32* out_len, + SlhDsaKey* key, const byte* context, + byte contextLen, word32 preHashType, + const byte* addRnd, byte addRndSz, int randomized, + int isMPrime); + +/** + * @brief Verify an SLH-DSA signature using DMA. + * + * @param[in] ctx Pointer to the client context + * @param[in] sig Signature to verify + * @param[in] sig_len Length of sig in bytes + * @param[in] msg Message, digest, or M' that was signed + * @param[in] msg_len Length of msg in bytes + * @param[out] out_res Set to 1 when the signature verifies, 0 otherwise + * @param[in] key Key handle, either server-resident or holding key material + * @param[in] context FIPS 205 context string, may be NULL + * @param[in] contextLen Length of context, 0 to 255 + * @param[in] preHashType Pre-hash algorithm (enum wc_HashType) + * @param[in] isMPrime Non-zero when msg is a caller-built M' + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_SlhDsaVerifyDma(whClientContext* ctx, const byte* sig, + word32 sig_len, const byte* msg, word32 msg_len, + int* out_res, SlhDsaKey* key, + const byte* context, byte contextLen, + word32 preHashType, int isMPrime); + +/** + * @brief Check a server-held SLH-DSA private key against a public key. + * + * The public key is only 2n bytes, so this forwards to the comm-buffer path. + * + * @param[in] ctx Pointer to the client context + * @param[in] key Key handle for the private key + * @param[in] pubKey Public key bytes, PK.seed || PK.root + * @param[in] pubKeySz Length of pubKey in bytes, 2n + * @return int Returns 0 when they match, WC_KEY_MISMATCH_E when they do not, + * or a negative error code on failure. + */ +int wh_Client_SlhDsaCheckPrivKeyDma(whClientContext* ctx, SlhDsaKey* key, + const byte* pubKey, word32 pubKeySz); + +#endif /* WOLFHSM_CFG_DMA */ + +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef WOLFSSL_HAVE_MLKEM /** diff --git a/wolfhsm/wh_common.h b/wolfhsm/wh_common.h index e8289111f..95d4ecff7 100644 --- a/wolfhsm/wh_common.h +++ b/wolfhsm/wh_common.h @@ -177,6 +177,7 @@ enum WH_KEY_ALGO_ENUM { WH_KEY_ALGO_MLKEM = 6, WH_KEY_ALGO_LMS = 7, WH_KEY_ALGO_XMSS = 8, + WH_KEY_ALGO_SLHDSA = 9, }; #endif /* !WOLFHSM_WH_COMMON_H_ */ diff --git a/wolfhsm/wh_crypto.h b/wolfhsm/wh_crypto.h index c1a97ae51..b15eee2fa 100644 --- a/wolfhsm/wh_crypto.h +++ b/wolfhsm/wh_crypto.h @@ -44,6 +44,7 @@ #include "wolfssl/wolfcrypt/ed25519.h" #include "wolfssl/wolfcrypt/wc_mldsa.h" #include "wolfssl/wolfcrypt/wc_mlkem.h" +#include "wolfssl/wolfcrypt/wc_slhdsa.h" #include "wolfhsm/wh_message_crypto.h" @@ -119,6 +120,20 @@ int wh_Crypto_MlDsaDeserializeKeyDer(const uint8_t* buffer, uint16_t size, wc_MlDsaKey* key); #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA +#define WH_CRYPTO_SLHDSA_MAX_CTX_LEN (255U) +/* RFC 9909 wraps the raw 4n private and 2n public key in a OneAsymmetricKey. + * The slack covers the algorithm identifier and the ASN.1 headers. */ +#define WH_CRYPTO_SLHDSA_MAX_KEY_DER_SIZE \ + (WC_SLHDSA_MAX_PRIV_LEN + WC_SLHDSA_MAX_PUB_LEN + 128U) +/* Store a SlhDsaKey to a byte sequence */ +int wh_Crypto_SlhDsaSerializeKeyDer(SlhDsaKey* key, uint16_t max_size, + uint8_t* buffer, uint16_t* out_size); +/* Restore a SlhDsaKey from a byte sequence */ +int wh_Crypto_SlhDsaDeserializeKeyDer(const uint8_t* buffer, uint16_t size, + SlhDsaKey* key); +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef WOLFSSL_HAVE_MLKEM /* Store a MlKemKey to a byte sequence */ int wh_Crypto_MlKemSerializeKey(MlKemKey* key, uint16_t max_size, diff --git a/wolfhsm/wh_message_crypto.h b/wolfhsm/wh_message_crypto.h index 809513bc9..4d9353450 100644 --- a/wolfhsm/wh_message_crypto.h +++ b/wolfhsm/wh_message_crypto.h @@ -1193,6 +1193,142 @@ int wh_MessageCrypto_TranslateMlDsaVerifyResponse( uint16_t magic, const whMessageCrypto_MlDsaVerifyResponse* src, whMessageCrypto_MlDsaVerifyResponse* dest); + +/* + * SLH-DSA + */ + +/* SLH-DSA Key Generation Request */ +typedef struct { + uint32_t sz; + uint32_t param; /* enum SlhDsaParam parameter set */ + uint32_t keyId; + uint32_t flags; + uint32_t access; + uint32_t seedSz; /* 0 for random keygen, else SK.seed||SK.prf||PK.seed */ + uint8_t label[WH_NVM_LABEL_LEN]; + /* Data follows: + * uint8_t seed[seedSz]; + */ +} whMessageCrypto_SlhDsaKeyGenRequest; + +/* SLH-DSA Key Generation Response */ +typedef struct { + uint32_t keyId; + uint32_t len; + /* Data follows: + * uint8_t out[len]; + */ +} whMessageCrypto_SlhDsaKeyGenResponse; + +int wh_MessageCrypto_TranslateSlhDsaKeyGenRequest( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenRequest* src, + whMessageCrypto_SlhDsaKeyGenRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaKeyGenResponse( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenResponse* src, + whMessageCrypto_SlhDsaKeyGenResponse* dest); + +/* SLH-DSA Sign Request */ +typedef struct { + uint32_t options; +#define WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_EVICT (1 << 0) +/* Input is a caller-built M', so context and pre-hash do not apply */ +#define WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_MPRIME (1 << 1) +/* Caller asked for a hedged signature; the server supplies the randomizer */ +#define WH_MESSAGE_CRYPTO_SLHDSA_SIGN_OPTIONS_RANDOMIZED (1 << 2) + uint32_t param; + uint32_t keyId; + uint32_t sz; + uint32_t contextSz; /* FIPS 205 context length (0-255) */ + uint32_t preHashType; /* enum wc_HashType, 0 for pure SLH-DSA */ + uint32_t addRndSz; /* Caller-supplied randomizer length, 0 if none */ + uint8_t WH_PAD[4]; + /* Data follows: + * uint8_t in[sz]; + * uint8_t context[contextSz]; + * uint8_t addRnd[addRndSz]; + */ +} whMessageCrypto_SlhDsaSignRequest; + +/* SLH-DSA Sign Response */ +typedef struct { + uint32_t sz; + uint8_t WH_PAD[4]; + /* Data follows: + * uint8_t out[sz]; + */ +} whMessageCrypto_SlhDsaSignResponse; + +int wh_MessageCrypto_TranslateSlhDsaSignRequest( + uint16_t magic, const whMessageCrypto_SlhDsaSignRequest* src, + whMessageCrypto_SlhDsaSignRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaSignResponse( + uint16_t magic, const whMessageCrypto_SlhDsaSignResponse* src, + whMessageCrypto_SlhDsaSignResponse* dest); + +/* SLH-DSA Verify Request */ +typedef struct { + uint32_t options; +#define WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_EVICT (1 << 0) +#define WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_EXPORTPUB (1 << 1) +/* Message is a caller-built M', so context and pre-hash do not apply */ +#define WH_MESSAGE_CRYPTO_SLHDSA_VERIFY_OPTIONS_MPRIME (1 << 2) + uint32_t param; + uint32_t keyId; + uint32_t sigSz; + uint32_t hashSz; + uint32_t contextSz; /* FIPS 205 context length (0-255) */ + uint32_t preHashType; /* enum wc_HashType, 0 for pure SLH-DSA */ + uint8_t WH_PAD[4]; + /* Data follows: + * uint8_t sig[sigSz]; + * uint8_t hash[hashSz]; + * uint8_t context[contextSz]; + */ +} whMessageCrypto_SlhDsaVerifyRequest; + +/* SLH-DSA Verify Response */ +typedef struct { + uint32_t res; + uint8_t WH_PAD[4]; +} whMessageCrypto_SlhDsaVerifyResponse; + +int wh_MessageCrypto_TranslateSlhDsaVerifyRequest( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyRequest* src, + whMessageCrypto_SlhDsaVerifyRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaVerifyResponse( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyResponse* src, + whMessageCrypto_SlhDsaVerifyResponse* dest); + +/* SLH-DSA Check Private Key Request */ +typedef struct { + uint32_t options; +#define WH_MESSAGE_CRYPTO_SLHDSA_CHECKPRIVKEY_OPTIONS_EVICT (1 << 0) + uint32_t param; + uint32_t keyId; + uint32_t pubSz; /* PK.seed||PK.root, 2n bytes */ + /* Data follows: + * uint8_t pub[pubSz]; + */ +} whMessageCrypto_SlhDsaCheckPrivKeyRequest; + +/* SLH-DSA Check Private Key Response */ +typedef struct { + uint32_t res; + uint8_t WH_PAD[4]; +} whMessageCrypto_SlhDsaCheckPrivKeyResponse; + +int wh_MessageCrypto_TranslateSlhDsaCheckPrivKeyRequest( + uint16_t magic, const whMessageCrypto_SlhDsaCheckPrivKeyRequest* src, + whMessageCrypto_SlhDsaCheckPrivKeyRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaCheckPrivKeyResponse( + uint16_t magic, const whMessageCrypto_SlhDsaCheckPrivKeyResponse* src, + whMessageCrypto_SlhDsaCheckPrivKeyResponse* dest); + /* * ML-KEM */ @@ -1690,6 +1826,96 @@ int wh_MessageCrypto_TranslateMlDsaVerifyDmaResponse( uint16_t magic, const whMessageCrypto_MlDsaVerifyDmaResponse* src, whMessageCrypto_MlDsaVerifyDmaResponse* dest); +/* SLH-DSA DMA Key Generation Request */ +typedef struct { + whMessageCrypto_DmaBuffer key; + whMessageCrypto_DmaBuffer seed; /* sz 0 for random keygen */ + uint32_t param; + uint32_t flags; + uint32_t keyId; + uint32_t access; /* Key access permissions */ + uint32_t labelSize; + uint8_t label[WH_NVM_LABEL_LEN]; + uint8_t WH_PAD[4]; +} whMessageCrypto_SlhDsaKeyGenDmaRequest; + +/* SLH-DSA DMA Key Generation Response */ +typedef struct { + whMessageCrypto_DmaAddrStatus dmaAddrStatus; + uint32_t keyId; /* Assigned key ID */ + uint32_t keySize; /* Actual size of generated key */ +} whMessageCrypto_SlhDsaKeyGenDmaResponse; + +/* SLH-DSA DMA Sign Request */ +typedef struct { + whMessageCrypto_DmaBuffer msg; /* Message buffer */ + whMessageCrypto_DmaBuffer sig; /* Signature buffer */ + uint32_t options; /* Same options as non-DMA version */ + uint32_t param; /* enum SlhDsaParam parameter set */ + uint32_t keyId; /* Key ID to use for signing */ + uint32_t contextSz; /* FIPS 205 context length (0-255) */ + uint32_t preHashType; /* enum wc_HashType */ + uint32_t addRndSz; /* Randomizer length, 0 if none */ + /* Data follows: + * uint8_t context[contextSz]; + * uint8_t addRnd[addRndSz]; + */ +} whMessageCrypto_SlhDsaSignDmaRequest; + +/* SLH-DSA DMA Sign Response */ +typedef struct { + whMessageCrypto_DmaAddrStatus dmaAddrStatus; + uint32_t sigLen; /* Actual signature length */ + uint8_t WH_PAD[4]; /* Pad to 8-byte alignment */ +} whMessageCrypto_SlhDsaSignDmaResponse; + +/* SLH-DSA DMA Verify Request */ +typedef struct { + whMessageCrypto_DmaBuffer sig; /* Signature buffer */ + whMessageCrypto_DmaBuffer msg; /* Message buffer */ + uint32_t options; /* Same options as non-DMA version */ + uint32_t param; /* enum SlhDsaParam parameter set */ + uint32_t keyId; /* Key ID to use for verification */ + uint32_t contextSz; /* FIPS 205 context length (0-255) */ + uint32_t preHashType; /* enum wc_HashType */ + uint8_t WH_PAD[4]; /* Pad to 8-byte alignment */ + /* Data follows: + * uint8_t context[contextSz]; + */ +} whMessageCrypto_SlhDsaVerifyDmaRequest; + +/* SLH-DSA DMA Verify Response */ +typedef struct { + whMessageCrypto_DmaAddrStatus dmaAddrStatus; + int32_t verifyResult; /* Result of verification */ + uint8_t WH_PAD[4]; /* Pad to 8-byte alignment */ +} whMessageCrypto_SlhDsaVerifyDmaResponse; + +/* SLH-DSA DMA translation functions */ +int wh_MessageCrypto_TranslateSlhDsaKeyGenDmaRequest( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenDmaRequest* src, + whMessageCrypto_SlhDsaKeyGenDmaRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaKeyGenDmaResponse( + uint16_t magic, const whMessageCrypto_SlhDsaKeyGenDmaResponse* src, + whMessageCrypto_SlhDsaKeyGenDmaResponse* dest); + +int wh_MessageCrypto_TranslateSlhDsaSignDmaRequest( + uint16_t magic, const whMessageCrypto_SlhDsaSignDmaRequest* src, + whMessageCrypto_SlhDsaSignDmaRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaSignDmaResponse( + uint16_t magic, const whMessageCrypto_SlhDsaSignDmaResponse* src, + whMessageCrypto_SlhDsaSignDmaResponse* dest); + +int wh_MessageCrypto_TranslateSlhDsaVerifyDmaRequest( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyDmaRequest* src, + whMessageCrypto_SlhDsaVerifyDmaRequest* dest); + +int wh_MessageCrypto_TranslateSlhDsaVerifyDmaResponse( + uint16_t magic, const whMessageCrypto_SlhDsaVerifyDmaResponse* src, + whMessageCrypto_SlhDsaVerifyDmaResponse* dest); + /* ML-KEM DMA Key Generation Request */ typedef struct { whMessageCrypto_DmaBuffer key; diff --git a/wolfhsm/wh_server_crypto.h b/wolfhsm/wh_server_crypto.h index 739172d37..8698d4b07 100644 --- a/wolfhsm/wh_server_crypto.h +++ b/wolfhsm/wh_server_crypto.h @@ -104,6 +104,16 @@ int wh_Server_MlDsaKeyCacheExport(whServerContext* ctx, whKeyId keyId, wc_MlDsaKey* key); #endif /* WOLFSSL_HAVE_MLDSA */ +#ifdef WOLFSSL_HAVE_SLHDSA +/* Store a SlhDsaKey into a server key cache with optional metadata */ +int wh_Server_SlhDsaKeyCacheImport(whServerContext* ctx, SlhDsaKey* key, + whKeyId keyId, whNvmFlags flags, + uint16_t label_len, uint8_t* label); +/* Restore a SlhDsaKey from a server key cache */ +int wh_Server_SlhDsaKeyCacheExport(whServerContext* ctx, whKeyId keyId, + SlhDsaKey* key); +#endif /* WOLFSSL_HAVE_SLHDSA */ + #ifdef WOLFSSL_HAVE_MLKEM /* Store a MlKemKey into a server key cache with optional metadata */ int wh_Server_MlKemKeyCacheImport(whServerContext* ctx, MlKemKey* key, diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h index f9974d905..66fb61219 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -136,6 +136,14 @@ * operation in DMA requests. * Default: Not defined * + * SLH-DSA signatures range from 7856 bytes (128s) to 49856 bytes (256f), so + * the non-DMA sign and verify paths only work for a parameter set whose + * signature fits WOLFHSM_CFG_COMM_DATA_LEN alongside the message headers. + * With the default buffer no parameter set fits; at 8192 only 128s does, and + * a maximum-length context with a 64-byte pre-hash still overflows it. The + * server reports WH_ERROR_BUFFER_SIZE rather than truncating. Use the DMA + * sign and verify calls for the other parameter sets. + * * WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS - Maximum number of trusted root NVM IDs * accepted in a single wh_Server_CertVerifyMultiRoot request. Bounded so the * non-DMA wire request fits within WOLFHSM_CFG_COMM_DATA_LEN alongside the