Skip to content

Commit 9da629a

Browse files
committed
Add crypto callbacks for LMS and XMSS
1 parent 7b53303 commit 9da629a

9 files changed

Lines changed: 1150 additions & 49 deletions

File tree

doc/LMS_XMSS_CryptoCb.md

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# LMS / XMSS Crypto Callback support
2+
3+
This document describes the wolfSSL-side groundwork that lets LMS / HSS
4+
(RFC 8554) and XMSS / XMSS^MT (RFC 8391, both profiled in NIST SP 800-208)
5+
participate in the `WOLF_CRYPTO_CB` framework. With this layer in place, the
6+
wolfSSL PKCS#11 provider and the wolfHSM client can host stateful
7+
hash-based keys on a device without the wolfSSL public API changing.
8+
9+
No HSM-side or PKCS#11-provider code lives in this layer. It only adds the
10+
dispatcher surface, the per-key device binding, and the helpers a backend
11+
needs to answer the request.
12+
13+
## Why route stateful hash-based keys through a device
14+
15+
LMS and XMSS are one-time-signature trees: the private key holds a counter
16+
that must be incremented on every signature, and signing the same index twice
17+
breaks the security proof. Moving that counter to a hardware module is the
18+
clean way to make the scheme operationally safe — the HSM is the natural
19+
owner of the index, and an attacker who steals a host snapshot cannot replay
20+
old indices.
21+
22+
## PKCS#11 mapping
23+
24+
PKCS#11 v3.1 standardised HSS and v3.2 added XMSS / XMSS^MT. The CryptoCb
25+
surface mirrors what those mechanisms expose:
26+
27+
| wolfSSL API | PKCS#11 analog | CryptoCb dispatcher |
28+
|-----------------------|------------------------------------------------|----------------------------------------------|
29+
| `wc_LmsKey_MakeKey` | `CKM_HSS_KEY_PAIR_GEN` | `wc_CryptoCb_PqcStatefulSigKeyGen` |
30+
| `wc_LmsKey_Sign` | `CKM_HSS` (sign) | `wc_CryptoCb_PqcStatefulSigSign` |
31+
| `wc_LmsKey_Verify` | `CKM_HSS` (verify) | `wc_CryptoCb_PqcStatefulSigVerify` |
32+
| `wc_LmsKey_SigsLeft` | `CKA_HSS_KEYS_REMAINING` attribute | `wc_CryptoCb_PqcStatefulSigSigsLeft` |
33+
| `wc_XmssKey_MakeKey` | `CKM_XMSS_KEY_PAIR_GEN` / `CKM_XMSSMT_KEY_PAIR_GEN` | `wc_CryptoCb_PqcStatefulSigKeyGen` |
34+
| `wc_XmssKey_Sign` | `CKM_XMSS` / `CKM_XMSSMT` (sign) | `wc_CryptoCb_PqcStatefulSigSign` |
35+
| `wc_XmssKey_Verify` | `CKM_XMSS` / `CKM_XMSSMT` (verify) | `wc_CryptoCb_PqcStatefulSigVerify` |
36+
| `wc_XmssKey_SigsLeft` | XMSS remaining-sigs attribute | `wc_CryptoCb_PqcStatefulSigSigsLeft` |
37+
38+
The four dispatchers are shared between LMS and XMSS, following the
39+
`wc_CryptoCb_PqcSign*` family used for Dilithium and Falcon. A new
40+
discriminator enum `wc_PqcStatefulSignatureType` (`WC_PQC_STATEFUL_SIG_TYPE_LMS`,
41+
`WC_PQC_STATEFUL_SIG_TYPE_XMSS`) tells the callback which of `LmsKey*` or
42+
`XmssKey*` the `void* key` field is. XMSS vs XMSS^MT is decided inside the
43+
callback via the existing `XmssKey::is_xmssmt` field.
44+
45+
`Reload`, `GetKid`, and `ExportPub` are not routed through CryptoCb, but each
46+
is aware of HSM-backed keys: `Reload` short-circuits because state lives in
47+
the device, `GetKid` logs a warning since `priv_raw` may be uninitialised,
48+
and `ExportPub` preserves the source key's `devId` so the verify-only copy
49+
keeps dispatching through the same device. The external-backend variants
50+
(`ext_lms.c` / `ext_xmss.c`, selected by `--with-liblms` / `--with-libxmss`)
51+
are intentionally outside the scope of this layer and execute purely in
52+
software.
53+
54+
## Per-key device binding
55+
56+
Each key carries the device-binding fields that other key types
57+
(`RsaKey`, `ecc_key`, `dilithium_key`) already expose:
58+
59+
```c
60+
struct LmsKey {
61+
/* ... existing fields ... */
62+
#ifdef WOLF_CRYPTO_CB
63+
int devId; /* device identifier */
64+
void* devCtx; /* opaque per-device state, owned by the callback */
65+
#endif
66+
#ifdef WOLF_PRIVATE_KEY_ID
67+
byte id[LMS_MAX_ID_LEN]; /* device-side key identifier */
68+
int idLen;
69+
char label[LMS_MAX_LABEL_LEN]; /* device-side key label */
70+
int labelLen;
71+
#endif
72+
};
73+
```
74+
75+
`XmssKey` carries the equivalent set under the same macro guards, with
76+
`XMSS_MAX_ID_LEN` / `XMSS_MAX_LABEL_LEN`. The `*_MAX_ID_LEN` and
77+
`*_MAX_LABEL_LEN` constants default to 32 and can be overridden by
78+
predefining the macros.
79+
80+
`devCtx`, `id`, and `label` are storage only — wolfSSL never reads or writes
81+
them internally. Backends populate `devCtx` from the callback (typically the
82+
first time they touch the key) and consume `id` / `label` to resolve the
83+
on-device handle.
84+
85+
## Public API additions
86+
87+
```c
88+
/* Bind a key to a device-side identifier or label. */
89+
#ifdef WOLF_PRIVATE_KEY_ID
90+
WOLFSSL_API int wc_LmsKey_InitId (LmsKey * key, const unsigned char * id,
91+
int len, void * heap, int devId);
92+
WOLFSSL_API int wc_LmsKey_InitLabel(LmsKey * key, const char * label,
93+
void * heap, int devId);
94+
WOLFSSL_API int wc_XmssKey_InitId (XmssKey* key, const unsigned char* id,
95+
int len, void* heap, int devId);
96+
WOLFSSL_API int wc_XmssKey_InitLabel(XmssKey* key, const char* label,
97+
void* heap, int devId);
98+
#endif
99+
100+
/* Compute the digest of a message with the hash function dictated by
101+
* the parameter set. Useful for backends that follow the PKCS#11 v3.2
102+
* CKM_HSS / CKM_XMSS / CKM_XMSSMT convention of operating on a
103+
* pre-computed digest (see "Sign / verify input format" below). */
104+
WOLFSSL_API int wc_LmsKey_HashMsg (const LmsKey * key, const byte * msg,
105+
word32 msgSz, byte * hash,
106+
word32 * hashSz);
107+
WOLFSSL_API int wc_XmssKey_HashMsg(const XmssKey* key, const byte* msg,
108+
word32 msgSz, byte* hash,
109+
word32* hashSz);
110+
```
111+
112+
The `Init*` helpers follow the `wc_InitRsaKey_Id` / `wc_InitRsaKey_Label`
113+
shape: they validate length bounds, delegate the rest of init to
114+
`wc_LmsKey_Init` / `wc_XmssKey_Init`, then copy id / label onto the key.
115+
116+
The `HashMsg` helpers honour the parameter set:
117+
118+
| Algorithm | Hash families covered |
119+
|-----------|-----------------------------------------------------------------|
120+
| LMS / HSS | SHA-256 (32 bytes), SHA-256/192 (24 bytes), SHAKE256 (32 / 24) |
121+
| XMSS / MT | SHA-256, SHA-512, SHAKE128, SHAKE256 (per `params->hash`) |
122+
123+
`*hashSz` is in / out: callers pass the buffer size and receive the digest
124+
length on success.
125+
126+
## Sign / verify input format
127+
128+
The CryptoCb dispatcher forwards the raw message to the callback. PKCS#11
129+
v3.2 section 6.66.8 ("XMSS and XMSSMT without hashing") and the analogous
130+
text for HSS specify that those mechanisms take a pre-computed digest
131+
rather than the message. Backends that need that behaviour — typically
132+
PKCS#11 providers — call `wc_LmsKey_HashMsg` or `wc_XmssKey_HashMsg` from
133+
inside the callback to produce the algorithm-dictated digest. Backends
134+
that take the full message (typically wolfHSM) consume `msg` / `msgSz`
135+
directly. Picking one or the other is a callback decision; the dispatcher
136+
is agnostic.
137+
138+
## Build configuration
139+
140+
| `./configure` flag(s) | Effect |
141+
|--------------------------------------------------------|-------------------------------------------------------|
142+
| `--enable-lms --enable-xmss --enable-cryptocb` | Primary target. Full dispatcher and round-trip tests. |
143+
| `--enable-lms --enable-xmss` | New dispatcher code is fully `#ifdef`-elided. |
144+
| `--enable-cryptocb` | LMS / XMSS-less build; nothing CryptoCb-side breaks. |
145+
| `CPPFLAGS=-DWOLF_PRIVATE_KEY_ID …` | Adds `id` / `label` fields and the `Init*` helpers. |
146+
147+
## Verification
148+
149+
`./wolfcrypt/test/testwolfcrypt` exercises the full dispatcher round trip:
150+
inside `cryptocb_test`, `lms_test` and `xmss_test` run with the harness's
151+
registered `myCryptoDevCb`, which clears the key's `devId`, invokes the
152+
software API recursively, then restores `devId`. Sign and verify both go
153+
through the dispatcher, so the produced signatures self-verify within the
154+
harness. With no device registered, `lms_test` and `xmss_test` remain on the
155+
software path and produce bit-identical KAT output.
156+
157+
## Design notes
158+
159+
- **Shared dispatcher, separate type tag.** The eight LMS / XMSS operations
160+
collapse to four shared dispatchers (`KeyGen`, `Sign`, `Verify`,
161+
`SigsLeft`) keyed on `wc_PqcStatefulSignatureType`. The pattern matches
162+
the `PqcSign` family used for Dilithium / Falcon and reduces the surface
163+
area a backend has to implement.
164+
- **Verify carries `int* res`.** Following the Ed25519 / ECC / PqcVerify
165+
convention, the verify dispatcher reports validity through a separate
166+
`*res` flag, so a backend can distinguish a transport error from a
167+
forged signature. The wrapping wolfSSL function still translates
168+
`res != 1` to `SIG_VERIFY_E` for callers that do not see `res`.
169+
- **`SigsLeft` carries `word32* sigsLeft`.** PKCS#11 defines
170+
`CKA_HSS_KEYS_REMAINING` as a `CK_ULONG`-sized attribute; the callback
171+
uses `word32*` so an HSS key at its 2^32 limit can be expressed
172+
unambiguously. The wolfSSL public API still returns `int` and clamps at
173+
`0x7FFFFFFF`.
174+
- **HSM-backed keys skip the software write / read callbacks.**
175+
`wc_LmsKey_MakeKey` / `_Sign` and the XMSS equivalents dispatch through
176+
CryptoCb *before* validating `write_private_key` / `read_private_key` /
177+
`context`. A device-backed key does not need dummy software callbacks.
178+
On `CRYPTOCB_UNAVAILABLE` fall-through the software validations are
179+
re-applied as normal.

wolfcrypt/src/cryptocb.c

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,154 @@ int wc_CryptoCb_Ed25519Verify(const byte* sig, word32 sigLen,
10171017
}
10181018
#endif /* HAVE_ED25519 */
10191019

1020+
#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)
1021+
int wc_CryptoCb_PqcStatefulSigGetDevId(int type, void* key)
1022+
{
1023+
int devId = INVALID_DEVID;
1024+
1025+
if (key == NULL)
1026+
return devId;
1027+
1028+
#if defined(WOLFSSL_HAVE_LMS)
1029+
if (type == WC_PQC_STATEFUL_SIG_TYPE_LMS) {
1030+
devId = ((LmsKey*)key)->devId;
1031+
}
1032+
#endif
1033+
#if defined(WOLFSSL_HAVE_XMSS)
1034+
if (type == WC_PQC_STATEFUL_SIG_TYPE_XMSS) {
1035+
devId = ((XmssKey*)key)->devId;
1036+
}
1037+
#endif
1038+
1039+
return devId;
1040+
}
1041+
1042+
int wc_CryptoCb_PqcStatefulSigKeyGen(int type, void* key, WC_RNG* rng)
1043+
{
1044+
int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
1045+
int devId = INVALID_DEVID;
1046+
CryptoCb* dev;
1047+
1048+
if (key == NULL)
1049+
return ret;
1050+
1051+
devId = wc_CryptoCb_PqcStatefulSigGetDevId(type, key);
1052+
if (devId == INVALID_DEVID)
1053+
return ret;
1054+
1055+
dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_PK);
1056+
if (dev && dev->cb) {
1057+
wc_CryptoInfo cryptoInfo;
1058+
XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo));
1059+
cryptoInfo.algo_type = WC_ALGO_TYPE_PK;
1060+
cryptoInfo.pk.type = WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN;
1061+
cryptoInfo.pk.pqc_stateful_sig_kg.rng = rng;
1062+
cryptoInfo.pk.pqc_stateful_sig_kg.key = key;
1063+
cryptoInfo.pk.pqc_stateful_sig_kg.type = type;
1064+
1065+
ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx);
1066+
}
1067+
1068+
return wc_CryptoCb_TranslateErrorCode(ret);
1069+
}
1070+
1071+
int wc_CryptoCb_PqcStatefulSigSign(const byte* msg, word32 msgSz, byte* out,
1072+
word32* outSz, int type, void* key)
1073+
{
1074+
int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
1075+
int devId = INVALID_DEVID;
1076+
CryptoCb* dev;
1077+
1078+
if (key == NULL)
1079+
return ret;
1080+
1081+
devId = wc_CryptoCb_PqcStatefulSigGetDevId(type, key);
1082+
if (devId == INVALID_DEVID)
1083+
return ret;
1084+
1085+
dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_PK);
1086+
if (dev && dev->cb) {
1087+
wc_CryptoInfo cryptoInfo;
1088+
XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo));
1089+
cryptoInfo.algo_type = WC_ALGO_TYPE_PK;
1090+
cryptoInfo.pk.type = WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN;
1091+
cryptoInfo.pk.pqc_stateful_sig_sign.msg = msg;
1092+
cryptoInfo.pk.pqc_stateful_sig_sign.msgSz = msgSz;
1093+
cryptoInfo.pk.pqc_stateful_sig_sign.out = out;
1094+
cryptoInfo.pk.pqc_stateful_sig_sign.outSz = outSz;
1095+
cryptoInfo.pk.pqc_stateful_sig_sign.key = key;
1096+
cryptoInfo.pk.pqc_stateful_sig_sign.type = type;
1097+
1098+
ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx);
1099+
}
1100+
1101+
return wc_CryptoCb_TranslateErrorCode(ret);
1102+
}
1103+
1104+
int wc_CryptoCb_PqcStatefulSigVerify(const byte* sig, word32 sigSz,
1105+
const byte* msg, word32 msgSz, int* res, int type, void* key)
1106+
{
1107+
int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
1108+
int devId = INVALID_DEVID;
1109+
CryptoCb* dev;
1110+
1111+
if (key == NULL)
1112+
return ret;
1113+
1114+
devId = wc_CryptoCb_PqcStatefulSigGetDevId(type, key);
1115+
if (devId == INVALID_DEVID)
1116+
return ret;
1117+
1118+
dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_PK);
1119+
if (dev && dev->cb) {
1120+
wc_CryptoInfo cryptoInfo;
1121+
XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo));
1122+
cryptoInfo.algo_type = WC_ALGO_TYPE_PK;
1123+
cryptoInfo.pk.type = WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY;
1124+
cryptoInfo.pk.pqc_stateful_sig_verify.sig = sig;
1125+
cryptoInfo.pk.pqc_stateful_sig_verify.sigSz = sigSz;
1126+
cryptoInfo.pk.pqc_stateful_sig_verify.msg = msg;
1127+
cryptoInfo.pk.pqc_stateful_sig_verify.msgSz = msgSz;
1128+
cryptoInfo.pk.pqc_stateful_sig_verify.res = res;
1129+
cryptoInfo.pk.pqc_stateful_sig_verify.key = key;
1130+
cryptoInfo.pk.pqc_stateful_sig_verify.type = type;
1131+
1132+
ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx);
1133+
}
1134+
1135+
return wc_CryptoCb_TranslateErrorCode(ret);
1136+
}
1137+
1138+
int wc_CryptoCb_PqcStatefulSigSigsLeft(int type, void* key, word32* sigsLeft)
1139+
{
1140+
int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
1141+
int devId = INVALID_DEVID;
1142+
CryptoCb* dev;
1143+
1144+
if (key == NULL)
1145+
return ret;
1146+
1147+
devId = wc_CryptoCb_PqcStatefulSigGetDevId(type, key);
1148+
if (devId == INVALID_DEVID)
1149+
return ret;
1150+
1151+
dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_PK);
1152+
if (dev && dev->cb) {
1153+
wc_CryptoInfo cryptoInfo;
1154+
XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo));
1155+
cryptoInfo.algo_type = WC_ALGO_TYPE_PK;
1156+
cryptoInfo.pk.type = WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT;
1157+
cryptoInfo.pk.pqc_stateful_sig_sigs_left.key = key;
1158+
cryptoInfo.pk.pqc_stateful_sig_sigs_left.sigsLeft = sigsLeft;
1159+
cryptoInfo.pk.pqc_stateful_sig_sigs_left.type = type;
1160+
1161+
ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx);
1162+
}
1163+
1164+
return wc_CryptoCb_TranslateErrorCode(ret);
1165+
}
1166+
#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */
1167+
10201168
#if defined(WOLFSSL_HAVE_MLKEM)
10211169
int wc_CryptoCb_PqcKemGetDevId(int type, void* key)
10221170
{

0 commit comments

Comments
 (0)