Skip to content

Commit 7fe4069

Browse files
committed
Add crypto callback mode for SLH-DSA
1 parent dd3ff88 commit 7fe4069

26 files changed

Lines changed: 5741 additions & 789 deletions

.github/workflows/build-and-run-examples.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,30 @@ jobs:
9191
done
9292
done
9393
done
94+
95+
# The client and the server are separate binaries with separate
96+
# user_settings.h, so the client can be built with no software SLH-DSA at
97+
# all while the server keeps its own. That is the only way to prove the
98+
# callback-only path really reaches the server: with the software stripped
99+
# the client has nothing to fall back to, so a passing demo means every
100+
# SLH-DSA operation was served over the wire. Only one transport needs to
101+
# run it; the ONLY macro is orthogonal to the transport.
102+
- name: Build and run the callback-only SLH-DSA client
103+
if: ${{ matrix.transport == 'tcp' }}
104+
run: |
105+
WS="$(pwd)"
106+
SERVER_DIR="$WS/examples/posix/wh_posix_server"
107+
CLIENT_DIR="$WS/examples/posix/wh_posix_client"
108+
make -C "$SERVER_DIR" clean
109+
make -C "$CLIENT_DIR" clean
110+
DEMO_KEK=1 make -C "$SERVER_DIR" -j WOLFSSL_DIR=../../../wolfssl
111+
SLHDSA_CB_ONLY=1 make -C "$CLIENT_DIR" -j WOLFSSL_DIR=../../../wolfssl
112+
rm -f "$SERVER_DIR"/*.bin
113+
cd "$SERVER_DIR"
114+
./Build/wh_posix_server.elf --type tcp &
115+
SERVER_PID=$!
116+
sleep 1
117+
cd "$CLIENT_DIR"
118+
./Build/wh_posix_client.elf --type tcp --test
119+
kill $SERVER_PID 2>/dev/null || true
120+
wait $SERVER_PID 2>/dev/null || true

.github/workflows/build-and-test-refactor.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ jobs:
9999
if: matrix.group == 'pq-dma'
100100
run: cd test-refactor/posix && make clean && make -j DMA=1 ASAN=1 MLDSA_VERIFY_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run
101101

102+
# Verify-only drops the signing APIs, so this catches code that assumes them.
103+
- name: Build and test refactor DMA ASAN SLHDSA verify-only
104+
if: matrix.group == 'pq-dma'
105+
run: cd test-refactor/posix && make clean && make -j DMA=1 ASAN=1 SLHDSA_VERIFY_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run
106+
102107
# Build and test ASAN build, with wolfCrypt tests enabled.
103108
- name: Build and test refactor ASAN TESTWOLFCRYPT
104109
if: matrix.group == 'wolfcrypt'

examples/demo/client/wh_demo_client_all.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,5 +201,12 @@ int wh_DemoClient_All(whClientContext* clientContext)
201201

202202
#endif /* WOLFSSL_CMAC */
203203

204+
#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY)
205+
rc = wh_DemoClient_CryptoSlhDsa(clientContext);
206+
if (rc != 0) {
207+
return rc;
208+
}
209+
#endif /* WOLFSSL_HAVE_SLHDSA && !WOLFSSL_SLHDSA_VERIFY_ONLY */
210+
204211
return rc;
205212
}

examples/demo/client/wh_demo_client_crypto.c

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
#include "wolfssl/wolfcrypt/kdf.h"
4545
#endif
4646

47+
#ifdef WOLFSSL_HAVE_SLHDSA
48+
#include "wolfssl/wolfcrypt/wc_slhdsa.h"
49+
#endif
50+
4751
#include "wh_demo_client_crypto.h"
4852

4953
#if !defined(NO_RSA)
@@ -1692,4 +1696,91 @@ int wh_DemoClient_CryptoCmacKdfCacheInputs(whClientContext* clientContext)
16921696

16931697
#endif /* HAVE_CMAC_KDF && WOLFSSL_CMAC */
16941698

1699+
#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY)
1700+
1701+
/* Generate an SLH-DSA key that stays on the server and use it purely by key
1702+
* id. Only the smallest parameter set produces a signature that fits the comm
1703+
* buffer, so that is what this demo asks for. */
1704+
int wh_DemoClient_CryptoSlhDsa(whClientContext* clientContext)
1705+
{
1706+
int ret;
1707+
int devId = WH_CLIENT_DEVID(clientContext);
1708+
whKeyId keyId = WH_KEYID_ERASED;
1709+
SlhDsaKey pub[1];
1710+
SlhDsaKey handle[1];
1711+
uint8_t label[] = "slhdsa-demo";
1712+
byte message[] = "wolfHSM SLH-DSA demo message";
1713+
byte signature[WC_SLHDSA_SHAKE128S_SIG_LEN];
1714+
word32 sigLen = sizeof(signature);
1715+
1716+
ret = wc_SlhDsaKey_Init(pub, SLHDSA_SHAKE128S, NULL, devId);
1717+
if (ret != 0) {
1718+
WOLFHSM_CFG_PRINTF("Failed to wc_SlhDsaKey_Init %d\n", ret);
1719+
return ret;
1720+
}
1721+
1722+
ret = wc_SlhDsaKey_Init(handle, SLHDSA_SHAKE128S, NULL, devId);
1723+
if (ret != 0) {
1724+
WOLFHSM_CFG_PRINTF("Failed to wc_SlhDsaKey_Init %d\n", ret);
1725+
wc_SlhDsaKey_Free(pub);
1726+
return ret;
1727+
}
1728+
1729+
/* The private key is generated on and never leaves the HSM; only the
1730+
* public key comes back. */
1731+
ret = wh_Client_SlhDsaMakeCacheKeyAndExportPublic(
1732+
clientContext, SLHDSA_SHAKE128S, &keyId,
1733+
WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY, sizeof(label),
1734+
label, pub);
1735+
if (ret != 0) {
1736+
WOLFHSM_CFG_PRINTF("Failed to generate SLH-DSA key %d\n", ret);
1737+
goto exit;
1738+
}
1739+
1740+
/* handle holds no key material at all, just the server key id */
1741+
ret = wh_Client_SlhDsaSetKeyId(handle, keyId);
1742+
if (ret != 0) {
1743+
WOLFHSM_CFG_PRINTF("Failed to wh_Client_SlhDsaSetKeyId %d\n", ret);
1744+
goto exit;
1745+
}
1746+
1747+
ret = wc_SlhDsaKey_SignDeterministic(handle, NULL, 0, message,
1748+
sizeof(message), signature, &sigLen);
1749+
if (ret != 0) {
1750+
WOLFHSM_CFG_PRINTF("Failed to wc_SlhDsaKey_SignDeterministic %d\n",
1751+
ret);
1752+
goto exit;
1753+
}
1754+
1755+
ret = wc_SlhDsaKey_Verify(pub, NULL, 0, message, sizeof(message),
1756+
signature, sigLen);
1757+
if (ret != 0) {
1758+
WOLFHSM_CFG_PRINTF("Failed to wc_SlhDsaKey_Verify %d\n", ret);
1759+
goto exit;
1760+
}
1761+
1762+
/* A tampered signature must not verify */
1763+
signature[0] ^= 0xFF;
1764+
if (wc_SlhDsaKey_Verify(pub, NULL, 0, message, sizeof(message), signature,
1765+
sigLen) == 0) {
1766+
WOLFHSM_CFG_PRINTF("SLH-DSA verified a tampered signature\n");
1767+
ret = -1;
1768+
goto exit;
1769+
}
1770+
1771+
WOLFHSM_CFG_PRINTF("SLH-DSA sign/verify with a server-resident key: "
1772+
"SUCCESS\n");
1773+
ret = 0;
1774+
1775+
exit:
1776+
if (!WH_KEYID_ISERASED(keyId)) {
1777+
(void)wh_Client_KeyEvict(clientContext, keyId);
1778+
}
1779+
wc_SlhDsaKey_Free(handle);
1780+
wc_SlhDsaKey_Free(pub);
1781+
return ret;
1782+
}
1783+
1784+
#endif /* WOLFSSL_HAVE_SLHDSA && !WOLFSSL_SLHDSA_VERIFY_ONLY */
1785+
16951786
#endif /* WOLFHSM_CFG_NO_CRYPTO */

examples/demo/client/wh_demo_client_crypto.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,6 @@ int wh_DemoClient_CryptoCmacKdfExport(whClientContext* clientContext);
3030
int wh_DemoClient_CryptoCmacKdfCache(whClientContext* clientContext);
3131
int wh_DemoClient_CryptoCmacKdfCacheInputs(whClientContext* clientContext);
3232

33+
int wh_DemoClient_CryptoSlhDsa(whClientContext* clientContext);
34+
3335
#endif /* !DEMO_CLIENT_CRYPTO_H_ */

examples/posix/wh_posix_client/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,13 @@ ifeq ($(AUTH),1)
143143
DEF += -DWOLFHSM_CFG_ENABLE_AUTHENTICATION
144144
endif
145145

146+
# Strip the software SLH-DSA from the client so every operation has to reach
147+
# the server. The server is a separate binary with its own settings, so it
148+
# keeps its software implementation.
149+
ifeq ($(SLHDSA_CB_ONLY),1)
150+
DEF += -DWH_CFG_SLHDSA_CB_ONLY
151+
endif
152+
146153
else
147154
DEF += -DWOLFHSM_CFG_NO_CRYPTO
148155
endif

examples/posix/wh_posix_client/user_settings.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@
4444
#define WOLFSSL_CMAC
4545
#define HAVE_HKDF
4646

47+
/* SLH-DSA. Only the smallest parameter set is built: its 7856-byte signature
48+
* is the only one that fits WOLFHSM_CFG_COMM_DATA_LEN. */
49+
#define WOLFSSL_HAVE_SLHDSA
50+
#define WOLFSSL_SHA3
51+
#define WOLFSSL_SHAKE128
52+
#define WOLFSSL_SHAKE256
53+
#define WOLFSSL_SLHDSA_PARAM_NO_128F
54+
#define WOLFSSL_SLHDSA_PARAM_NO_192
55+
#define WOLFSSL_SLHDSA_PARAM_NO_256
56+
57+
/* Build the client with no software SLH-DSA at all, so every operation must
58+
* reach the server or fail closed. Set -DWH_CFG_SLHDSA_CB_ONLY to select it;
59+
* the server keeps its software implementation either way. */
60+
#ifdef WH_CFG_SLHDSA_CB_ONLY
61+
#define WOLF_CRYPTO_CB_ONLY_SLHDSA
62+
#endif
63+
4764
/* wolfCrypt benchmark settings */
4865
#define NO_MAIN_DRIVER
4966
#define BENCH_EMBEDDED

examples/posix/wh_posix_server/user_settings.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2024 wolfSSL Inc.
2+
* Copyright (C) 2026 wolfSSL Inc.
33
*
44
* This file is part of wolfHSM.
55
*
@@ -150,6 +150,13 @@ extern "C" {
150150
#define WOLFSSL_MLDSA_NO_MAKE_KEY
151151
#endif
152152

153+
/* SLH-DSA Options. Only the smallest parameter set is built: its 7856-byte
154+
* signature is the only one that fits WOLFHSM_CFG_COMM_DATA_LEN. */
155+
#define WOLFSSL_HAVE_SLHDSA
156+
#define WOLFSSL_SLHDSA_PARAM_NO_128F
157+
#define WOLFSSL_SLHDSA_PARAM_NO_192
158+
#define WOLFSSL_SLHDSA_PARAM_NO_256
159+
153160
/* ML-KEM Options */
154161
#define WOLFSSL_HAVE_MLKEM
155162

0 commit comments

Comments
 (0)