diff --git a/doc/LMS_XMSS_CryptoCb.md b/doc/LMS_XMSS_CryptoCb.md new file mode 100644 index 0000000000..90fa8c9d7a --- /dev/null +++ b/doc/LMS_XMSS_CryptoCb.md @@ -0,0 +1,179 @@ +# LMS / XMSS Crypto Callback support + +This document describes the wolfSSL-side groundwork that lets LMS / HSS +(RFC 8554) and XMSS / XMSS^MT (RFC 8391, both profiled in NIST SP 800-208) +participate in the `WOLF_CRYPTO_CB` framework. With this layer in place, the +wolfSSL PKCS#11 provider and the wolfHSM client can host stateful +hash-based keys on a device without the wolfSSL public API changing. + +No HSM-side or PKCS#11-provider code lives in this layer. It only adds the +dispatcher surface, the per-key device binding, and the helpers a backend +needs to answer the request. + +## Why route stateful hash-based keys through a device + +LMS and XMSS are one-time-signature trees: the private key holds a counter +that must be incremented on every signature, and signing the same index twice +breaks the security proof. Moving that counter to a hardware module is the +clean way to make the scheme operationally safe — the HSM is the natural +owner of the index, and an attacker who steals a host snapshot cannot replay +old indices. + +## PKCS#11 mapping + +PKCS#11 v3.1 standardised HSS and v3.2 added XMSS / XMSS^MT. The CryptoCb +surface mirrors what those mechanisms expose: + +| wolfSSL API | PKCS#11 analog | CryptoCb dispatcher | +|-----------------------|------------------------------------------------|----------------------------------------------| +| `wc_LmsKey_MakeKey` | `CKM_HSS_KEY_PAIR_GEN` | `wc_CryptoCb_PqcStatefulSigKeyGen` | +| `wc_LmsKey_Sign` | `CKM_HSS` (sign) | `wc_CryptoCb_PqcStatefulSigSign` | +| `wc_LmsKey_Verify` | `CKM_HSS` (verify) | `wc_CryptoCb_PqcStatefulSigVerify` | +| `wc_LmsKey_SigsLeft` | `CKA_HSS_KEYS_REMAINING` attribute | `wc_CryptoCb_PqcStatefulSigSigsLeft` | +| `wc_XmssKey_MakeKey` | `CKM_XMSS_KEY_PAIR_GEN` / `CKM_XMSSMT_KEY_PAIR_GEN` | `wc_CryptoCb_PqcStatefulSigKeyGen` | +| `wc_XmssKey_Sign` | `CKM_XMSS` / `CKM_XMSSMT` (sign) | `wc_CryptoCb_PqcStatefulSigSign` | +| `wc_XmssKey_Verify` | `CKM_XMSS` / `CKM_XMSSMT` (verify) | `wc_CryptoCb_PqcStatefulSigVerify` | +| `wc_XmssKey_SigsLeft` | XMSS remaining-sigs attribute | `wc_CryptoCb_PqcStatefulSigSigsLeft` | + +The four dispatchers are shared between LMS and XMSS, following the +`wc_CryptoCb_PqcSign*` family used for Dilithium and Falcon. A new +discriminator enum `wc_PqcStatefulSignatureType` (`WC_PQC_STATEFUL_SIG_TYPE_LMS`, +`WC_PQC_STATEFUL_SIG_TYPE_XMSS`) tells the callback which of `LmsKey*` or +`XmssKey*` the `void* key` field is. XMSS vs XMSS^MT is decided inside the +callback via the existing `XmssKey::is_xmssmt` field. + +`Reload`, `GetKid`, and `ExportPub` are not routed through CryptoCb, but each +is aware of HSM-backed keys: `Reload` short-circuits because state lives in +the device, `GetKid` logs a warning since `priv_raw` may be uninitialised, +and `ExportPub` preserves the source key's `devId` so the verify-only copy +keeps dispatching through the same device. The external-backend variants +(`ext_lms.c` / `ext_xmss.c`, selected by `--with-liblms` / `--with-libxmss`) +are intentionally outside the scope of this layer and execute purely in +software. + +## Per-key device binding + +Each key carries the device-binding fields that other key types +(`RsaKey`, `ecc_key`, `dilithium_key`) already expose: + +```c +struct LmsKey { + /* ... existing fields ... */ +#ifdef WOLF_CRYPTO_CB + int devId; /* device identifier */ + void* devCtx; /* opaque per-device state, owned by the callback */ +#endif +#ifdef WOLF_PRIVATE_KEY_ID + byte id[LMS_MAX_ID_LEN]; /* device-side key identifier */ + int idLen; + char label[LMS_MAX_LABEL_LEN]; /* device-side key label */ + int labelLen; +#endif +}; +``` + +`XmssKey` carries the equivalent set under the same macro guards, with +`XMSS_MAX_ID_LEN` / `XMSS_MAX_LABEL_LEN`. The `*_MAX_ID_LEN` and +`*_MAX_LABEL_LEN` constants default to 32 and can be overridden by +predefining the macros. + +`devCtx`, `id`, and `label` are storage only — wolfSSL never reads or writes +them internally. Backends populate `devCtx` from the callback (typically the +first time they touch the key) and consume `id` / `label` to resolve the +on-device handle. + +## Public API additions + +```c +/* Bind a key to a device-side identifier or label. */ +#ifdef WOLF_PRIVATE_KEY_ID +WOLFSSL_API int wc_LmsKey_InitId (LmsKey * key, const unsigned char * id, + int len, void * heap, int devId); +WOLFSSL_API int wc_LmsKey_InitLabel(LmsKey * key, const char * label, + void * heap, int devId); +WOLFSSL_API int wc_XmssKey_InitId (XmssKey* key, const unsigned char* id, + int len, void* heap, int devId); +WOLFSSL_API int wc_XmssKey_InitLabel(XmssKey* key, const char* label, + void* heap, int devId); +#endif + +/* Compute the digest of a message with the hash function dictated by + * the parameter set. Useful for backends that follow the PKCS#11 v3.2 + * CKM_HSS / CKM_XMSS / CKM_XMSSMT convention of operating on a + * pre-computed digest (see "Sign / verify input format" below). */ +WOLFSSL_API int wc_LmsKey_HashMsg (const LmsKey * key, const byte * msg, + word32 msgSz, byte * hash, + word32 * hashSz); +WOLFSSL_API int wc_XmssKey_HashMsg(const XmssKey* key, const byte* msg, + word32 msgSz, byte* hash, + word32* hashSz); +``` + +The `Init*` helpers follow the `wc_InitRsaKey_Id` / `wc_InitRsaKey_Label` +shape: they validate length bounds, delegate the rest of init to +`wc_LmsKey_Init` / `wc_XmssKey_Init`, then copy id / label onto the key. + +The `HashMsg` helpers honour the parameter set: + +| Algorithm | Hash families covered | +|-----------|-----------------------------------------------------------------| +| LMS / HSS | SHA-256 (32 bytes), SHA-256/192 (24 bytes), SHAKE256 (32 / 24) | +| XMSS / MT | SHA-256, SHA-512, SHAKE128, SHAKE256 (per `params->hash`) | + +`*hashSz` is in / out: callers pass the buffer size and receive the digest +length on success. + +## Sign / verify input format + +The CryptoCb dispatcher forwards the raw message to the callback. PKCS#11 +v3.2 section 6.66.8 ("XMSS and XMSSMT without hashing") and the analogous +text for HSS specify that those mechanisms take a pre-computed digest +rather than the message. Backends that need that behaviour — typically +PKCS#11 providers — call `wc_LmsKey_HashMsg` or `wc_XmssKey_HashMsg` from +inside the callback to produce the algorithm-dictated digest. Backends +that take the full message (typically wolfHSM) consume `msg` / `msgSz` +directly. Picking one or the other is a callback decision; the dispatcher +is agnostic. + +## Build configuration + +| `./configure` flag(s) | Effect | +|--------------------------------------------------------|-------------------------------------------------------| +| `--enable-lms --enable-xmss --enable-cryptocb` | Primary target. Full dispatcher and round-trip tests. | +| `--enable-lms --enable-xmss` | New dispatcher code is fully `#ifdef`-elided. | +| `--enable-cryptocb` | LMS / XMSS-less build; nothing CryptoCb-side breaks. | +| `CPPFLAGS=-DWOLF_PRIVATE_KEY_ID …` | Adds `id` / `label` fields and the `Init*` helpers. | + +## Verification + +`./wolfcrypt/test/testwolfcrypt` exercises the full dispatcher round trip: +inside `cryptocb_test`, `lms_test` and `xmss_test` run with the harness's +registered `myCryptoDevCb`, which clears the key's `devId`, invokes the +software API recursively, then restores `devId`. Sign and verify both go +through the dispatcher, so the produced signatures self-verify within the +harness. With no device registered, `lms_test` and `xmss_test` remain on the +software path and produce bit-identical KAT output. + +## Design notes + +- **Shared dispatcher, separate type tag.** The eight LMS / XMSS operations + collapse to four shared dispatchers (`KeyGen`, `Sign`, `Verify`, + `SigsLeft`) keyed on `wc_PqcStatefulSignatureType`. The pattern matches + the `PqcSign` family used for Dilithium / Falcon and reduces the surface + area a backend has to implement. +- **Verify carries `int* res`.** Following the Ed25519 / ECC / PqcVerify + convention, the verify dispatcher reports validity through a separate + `*res` flag, so a backend can distinguish a transport error from a + forged signature. The wrapping wolfSSL function still translates + `res != 1` to `SIG_VERIFY_E` for callers that do not see `res`. +- **`SigsLeft` carries `word32* sigsLeft`.** PKCS#11 defines + `CKA_HSS_KEYS_REMAINING` as a `CK_ULONG`-sized attribute; the callback + uses `word32*` so an HSS key at its 2^32 limit can be expressed + unambiguously. The wolfSSL public API still returns `int` and clamps at + `0x7FFFFFFF`. +- **HSM-backed keys skip the software write / read callbacks.** + `wc_LmsKey_MakeKey` / `_Sign` and the XMSS equivalents dispatch through + CryptoCb *before* validating `write_private_key` / `read_private_key` / + `context`. A device-backed key does not need dummy software callbacks. + On `CRYPTOCB_UNAVAILABLE` fall-through the software validations are + re-applied as normal. diff --git a/wolfcrypt/src/cryptocb.c b/wolfcrypt/src/cryptocb.c index dcdb5bf7b0..0036556ba9 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -1017,6 +1017,154 @@ int wc_CryptoCb_Ed25519Verify(const byte* sig, word32 sigLen, } #endif /* HAVE_ED25519 */ +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) +int wc_CryptoCb_PqcStatefulSigGetDevId(int type, void* key) +{ + int devId = INVALID_DEVID; + + if (key == NULL) + return devId; + +#if defined(WOLFSSL_HAVE_LMS) + if (type == WC_PQC_STATEFUL_SIG_TYPE_LMS) { + devId = ((LmsKey*)key)->devId; + } +#endif +#if defined(WOLFSSL_HAVE_XMSS) + if (type == WC_PQC_STATEFUL_SIG_TYPE_XMSS) { + devId = ((XmssKey*)key)->devId; + } +#endif + + return devId; +} + +int wc_CryptoCb_PqcStatefulSigKeyGen(int type, void* key, WC_RNG* rng) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + int devId = INVALID_DEVID; + CryptoCb* dev; + + if (key == NULL) + return ret; + + devId = wc_CryptoCb_PqcStatefulSigGetDevId(type, key); + if (devId == INVALID_DEVID) + return ret; + + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_PK); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_PK; + cryptoInfo.pk.type = WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN; + cryptoInfo.pk.pqc_stateful_sig_kg.rng = rng; + cryptoInfo.pk.pqc_stateful_sig_kg.key = key; + cryptoInfo.pk.pqc_stateful_sig_kg.type = type; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} + +int wc_CryptoCb_PqcStatefulSigSign(const byte* msg, word32 msgSz, byte* out, + word32* outSz, int type, void* key) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + int devId = INVALID_DEVID; + CryptoCb* dev; + + if (key == NULL) + return ret; + + devId = wc_CryptoCb_PqcStatefulSigGetDevId(type, key); + if (devId == INVALID_DEVID) + return ret; + + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_PK); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_PK; + cryptoInfo.pk.type = WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN; + cryptoInfo.pk.pqc_stateful_sig_sign.msg = msg; + cryptoInfo.pk.pqc_stateful_sig_sign.msgSz = msgSz; + cryptoInfo.pk.pqc_stateful_sig_sign.out = out; + cryptoInfo.pk.pqc_stateful_sig_sign.outSz = outSz; + cryptoInfo.pk.pqc_stateful_sig_sign.key = key; + cryptoInfo.pk.pqc_stateful_sig_sign.type = type; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} + +int wc_CryptoCb_PqcStatefulSigVerify(const byte* sig, word32 sigSz, + const byte* msg, word32 msgSz, int* res, int type, void* key) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + int devId = INVALID_DEVID; + CryptoCb* dev; + + if (key == NULL) + return ret; + + devId = wc_CryptoCb_PqcStatefulSigGetDevId(type, key); + if (devId == INVALID_DEVID) + return ret; + + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_PK); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_PK; + cryptoInfo.pk.type = WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY; + cryptoInfo.pk.pqc_stateful_sig_verify.sig = sig; + cryptoInfo.pk.pqc_stateful_sig_verify.sigSz = sigSz; + cryptoInfo.pk.pqc_stateful_sig_verify.msg = msg; + cryptoInfo.pk.pqc_stateful_sig_verify.msgSz = msgSz; + cryptoInfo.pk.pqc_stateful_sig_verify.res = res; + cryptoInfo.pk.pqc_stateful_sig_verify.key = key; + cryptoInfo.pk.pqc_stateful_sig_verify.type = type; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} + +int wc_CryptoCb_PqcStatefulSigSigsLeft(int type, void* key, word32* sigsLeft) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + int devId = INVALID_DEVID; + CryptoCb* dev; + + if (key == NULL) + return ret; + + devId = wc_CryptoCb_PqcStatefulSigGetDevId(type, key); + if (devId == INVALID_DEVID) + return ret; + + dev = wc_CryptoCb_FindDevice(devId, WC_ALGO_TYPE_PK); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_PK; + cryptoInfo.pk.type = WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT; + cryptoInfo.pk.pqc_stateful_sig_sigs_left.key = key; + cryptoInfo.pk.pqc_stateful_sig_sigs_left.sigsLeft = sigsLeft; + cryptoInfo.pk.pqc_stateful_sig_sigs_left.type = type; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ + #if defined(WOLFSSL_HAVE_MLKEM) int wc_CryptoCb_PqcKemGetDevId(int type, void* key) { diff --git a/wolfcrypt/src/wc_lms.c b/wolfcrypt/src/wc_lms.c index 6192dcdba7..3c527749f1 100644 --- a/wolfcrypt/src/wc_lms.c +++ b/wolfcrypt/src/wc_lms.c @@ -36,6 +36,77 @@ #include #endif +#ifdef WOLF_CRYPTO_CB + #include +#endif + +/* Compute the digest of msg using the hash function dictated by the LMS + * parameter set. Crypto-callback / HSM backends that follow PKCS#11 v3.2 + * CKM_HSS semantics (pre-computed digest input) can call this from within + * their callback; backends that take the raw message (e.g. wolfHSM) can + * ignore it. *hashSz is in/out: it must be at least params->hash_len on + * entry and is set to the actual digest length on success. + * + * @param [in] key LMS key (must have a parameter set bound). + * @param [in] msg Message to hash. + * @param [in] msgSz Length of msg in bytes. + * @param [out] hash Buffer receiving the digest. + * @param [in,out] hashSz On entry, size of hash buffer. On success, + * the digest length. + * @return 0 on success. + * @return BAD_FUNC_ARG when an argument is NULL or the buffer is too + * small for the digest. + * @return NOT_COMPILED_IN when the param set's hash family is disabled. + */ +int wc_LmsKey_HashMsg(const LmsKey* key, const byte* msg, word32 msgSz, + byte* hash, word32* hashSz) +{ + int ret = 0; + word32 needSz; + + if ((key == NULL) || (msg == NULL) || (hash == NULL) || (hashSz == NULL)) + return BAD_FUNC_ARG; + if (key->params == NULL) + return BAD_FUNC_ARG; + needSz = (word32)key->params->hash_len; + if (*hashSz < needSz) + return BAD_FUNC_ARG; + + switch (key->params->lmsType & 0xF000) { + case LMS_SHA256: /* 32-byte SHA-256 */ + case LMS_SHA256_192: /* SHA-256 truncated to 24 bytes */ { + byte full[WC_SHA256_DIGEST_SIZE]; + ret = wc_Sha256Hash(msg, msgSz, full); + if (ret == 0) + XMEMCPY(hash, full, needSz); + break; + } + #ifdef WOLFSSL_LMS_SHAKE256 + case LMS_SHAKE256: /* SHAKE256 with 32-byte output */ + case LMS_SHAKE256_192: /* SHAKE256 with 24-byte output */ { + wc_Shake shake; + ret = wc_InitShake256(&shake, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_Shake256_Update(&shake, msg, msgSz); + if (ret == 0) + ret = wc_Shake256_Final(&shake, hash, needSz); + wc_Shake256_Free(&shake); + } + break; + } + #endif + default: + WOLFSSL_MSG("LMS: unsupported hash family for HashMsg"); + ret = NOT_COMPILED_IN; + break; + } + + if (ret == 0) + *hashSz = needSz; + + return ret; +} + /* Calculate u. Appendix B. Works for w of 1, 2, 4, or 8. * @@ -644,6 +715,72 @@ int wc_LmsKey_Init(LmsKey* key, void* heap, int devId) return ret; } +#ifdef WOLF_PRIVATE_KEY_ID +/* Initialize an LmsKey and bind it to a device-side key identifier. + * + * @param [in,out] key LmsKey to initialize. + * @param [in] id Identifier bytes (may be NULL when len is 0). + * @param [in] len Length of id; must be in [0, LMS_MAX_ID_LEN]. + * @param [in] heap Heap hint forwarded to wc_LmsKey_Init. + * @param [in] devId Device identifier. + * + * @return 0 on success. + * @return BAD_FUNC_ARG when key is NULL. + * @return BUFFER_E when len is negative or exceeds LMS_MAX_ID_LEN. + */ +int wc_LmsKey_InitId(LmsKey* key, const unsigned char* id, int len, void* heap, + int devId) +{ + int ret = 0; + + if (key == NULL) + ret = BAD_FUNC_ARG; + if (ret == 0 && (len < 0 || len > LMS_MAX_ID_LEN)) + ret = BUFFER_E; + if (ret == 0) + ret = wc_LmsKey_Init(key, heap, devId); + if (ret == 0 && id != NULL && len != 0) { + XMEMCPY(key->id, id, (size_t)len); + key->idLen = len; + } + + return ret; +} + +/* Initialize an LmsKey and bind it to a device-side key label. + * + * @param [in,out] key LmsKey to initialize. + * @param [in] label NUL-terminated label string (must be non-empty). + * @param [in] heap Heap hint forwarded to wc_LmsKey_Init. + * @param [in] devId Device identifier. + * + * @return 0 on success. + * @return BAD_FUNC_ARG when key or label is NULL. + * @return BUFFER_E when label is empty or longer than LMS_MAX_LABEL_LEN. + */ +int wc_LmsKey_InitLabel(LmsKey* key, const char* label, void* heap, int devId) +{ + int ret = 0; + int labelLen = 0; + + if (key == NULL || label == NULL) + ret = BAD_FUNC_ARG; + if (ret == 0) { + labelLen = (int)XSTRLEN(label); + if (labelLen == 0 || labelLen > LMS_MAX_LABEL_LEN) + ret = BUFFER_E; + } + if (ret == 0) + ret = wc_LmsKey_Init(key, heap, devId); + if (ret == 0) { + XMEMCPY(key->label, label, (size_t)labelLen); + key->labelLen = labelLen; + } + + return ret; +} +#endif /* WOLF_PRIVATE_KEY_ID */ + /* Get the string representation of the LMS parameter set. * * @param [in] lmsParm LMS parameter set identifier. @@ -971,6 +1108,20 @@ int wc_LmsKey_MakeKey(LmsKey* key, WC_RNG* rng) WOLFSSL_MSG("error: LmsKey not ready for generation"); ret = BAD_STATE_E; } + +#ifdef WOLF_CRYPTO_CB + /* HSM-backed keys skip the software write/context callbacks because the + * device owns the private state. On CRYPTOCB_UNAVAILABLE fall-through the + * software checks below still run. */ + if ((ret == 0) && (key->devId != INVALID_DEVID)) { + ret = wc_CryptoCb_PqcStatefulSigKeyGen(WC_PQC_STATEFUL_SIG_TYPE_LMS, + key, rng); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) + return ret; + ret = 0; /* fall through to software path */ + } +#endif + /* Check write callback set. */ if ((ret == 0) && (key->write_private_key == NULL)) { WOLFSSL_MSG("error: LmsKey write callback is not set"); @@ -1079,6 +1230,16 @@ int wc_LmsKey_Reload(LmsKey* key) WOLFSSL_MSG("error: LmsKey not ready for reload"); ret = BAD_STATE_E; } + +#ifdef WOLF_CRYPTO_CB + /* State for HSM-backed keys lives in the device; no software reload. */ + if ((ret == 0) && (key->devId != INVALID_DEVID)) { + WOLFSSL_MSG("wc_LmsKey_Reload is a no-op for HSM-backed keys"); + key->state = WC_LMS_STATE_OK; + return 0; + } +#endif + /* Check read callback present. */ if ((ret == 0) && (key->read_private_key == NULL)) { WOLFSSL_MSG("error: LmsKey read callback is not set"); @@ -1238,6 +1399,20 @@ int wc_LmsKey_Sign(LmsKey* key, byte* sig, word32* sigSz, const byte* msg, WOLFSSL_MSG("error: LMS sig buffer too small"); ret = BUFFER_E; } + +#ifdef WOLF_CRYPTO_CB + /* HSM-backed keys skip the software write/context callbacks because the + * device owns the private state. On CRYPTOCB_UNAVAILABLE fall-through the + * software checks below still run. */ + if ((ret == 0) && (key->devId != INVALID_DEVID)) { + ret = wc_CryptoCb_PqcStatefulSigSign(msg, (word32)msgSz, sig, sigSz, + WC_PQC_STATEFUL_SIG_TYPE_LMS, key); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) + return ret; + ret = 0; /* fall through to software path */ + } +#endif + /* Check read and write callbacks available. */ if ((ret == 0) && (key->write_private_key == NULL)) { WOLFSSL_MSG("error: LmsKey write/read callbacks are not set"); @@ -1315,6 +1490,27 @@ int wc_LmsKey_SigsLeft(LmsKey* key) /* NULL keys have no signatures remaining. */ if (key != NULL) { +#ifdef WOLF_CRYPTO_CB + if (key->devId != INVALID_DEVID) { + word32 sigsLeft = 0; + int cbRet = wc_CryptoCb_PqcStatefulSigSigsLeft( + WC_PQC_STATEFUL_SIG_TYPE_LMS, key, &sigsLeft); + if (cbRet == 0) { + /* Clamp to int range; callers treat 0 as "exhausted". */ + return (sigsLeft > (word32)0x7FFFFFFF) + ? 0x7FFFFFFF : (int)sigsLeft; + } + /* The device owns the private state; no safe software fallback + * exists because key->priv_raw does not reflect HSM state. */ + if (cbRet != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + WOLFSSL_MSG("PqcStatefulSigSigsLeft returned an error"); + } + else { + WOLFSSL_MSG("LMS SigsLeft not supported by device"); + } + return 0; + } +#endif ret = wc_hss_sigsleft(key->params, key->priv_raw); } @@ -1373,6 +1569,12 @@ int wc_LmsKey_ExportPub(LmsKey* keyDst, const LmsKey* keySrc) keyDst->params = keySrc->params; XMEMCPY(keyDst->pub, keySrc->pub, sizeof(keySrc->pub)); + #ifdef WOLF_CRYPTO_CB + /* Preserve the source key's device binding so the verify-only + * copy dispatches through the same crypto callback. */ + keyDst->devId = keySrc->devId; + #endif + /* Mark this key as verify only, to prevent misuse. */ keyDst->state = WC_LMS_STATE_VERIFYONLY; } @@ -1507,6 +1709,9 @@ int wc_LmsKey_Verify(LmsKey* key, const byte* sig, word32 sigSz, if ((key == NULL) || (sig == NULL) || (msg == NULL)) { ret = BAD_FUNC_ARG; } + if ((ret == 0) && (msgSz <= 0)) { + ret = BAD_FUNC_ARG; + } /* Check state. */ if ((ret == 0) && (key->state != WC_LMS_STATE_OK) && (key->state != WC_LMS_STATE_VERIFYONLY)) { @@ -1520,6 +1725,20 @@ int wc_LmsKey_Verify(LmsKey* key, const byte* sig, word32 sigSz, ret = BUFFER_E; } +#ifdef WOLF_CRYPTO_CB + if ((ret == 0) && (key->devId != INVALID_DEVID)) { + int res = 0; + ret = wc_CryptoCb_PqcStatefulSigVerify(sig, sigSz, msg, (word32)msgSz, + &res, WC_PQC_STATEFUL_SIG_TYPE_LMS, key); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + if (ret == 0 && res != 1) + ret = SIG_VERIFY_E; + return ret; + } + ret = 0; /* fall through to software path */ + } +#endif + if (ret == 0) { WC_DECLARE_VAR(state, LmsState, 1, 0); @@ -1564,6 +1783,16 @@ int wc_LmsKey_GetKid(LmsKey * key, const byte ** kid, word32* kidSz) return BAD_FUNC_ARG; } +#ifdef WOLF_CRYPTO_CB + /* priv_raw is not populated for HSM-backed keys where the device owns + * the private state; the returned KID will be zero bytes. Extend the + * CryptoCb surface if device-side KID retrieval becomes a requirement. */ + if (key->devId != INVALID_DEVID) { + WOLFSSL_MSG( + "wc_LmsKey_GetKid: priv_raw may be uninitialised for HSM keys"); + } +#endif + /* SEED length is hash length. */ offset = HSS_Q_LEN + HSS_PRIV_KEY_PARAM_SET_LEN + key->params->hash_len; *kid = key->priv_raw + offset; diff --git a/wolfcrypt/src/wc_xmss.c b/wolfcrypt/src/wc_xmss.c index dfc6375e4e..a909ebb655 100644 --- a/wolfcrypt/src/wc_xmss.c +++ b/wolfcrypt/src/wc_xmss.c @@ -36,6 +36,92 @@ #include #endif +#ifdef WOLF_CRYPTO_CB + #include +#endif + +/* Compute the digest of msg using the hash function dictated by the XMSS + * parameter set. Crypto-callback / HSM backends that follow PKCS#11 v3.2 + * CKM_XMSS / CKM_XMSSMT semantics (pre-computed digest input, see section + * 6.66.8 "XMSS and XMSSMT without hashing") can call this from within + * their callback; backends that take the raw message (e.g. wolfHSM) can + * ignore it. *hashSz is in/out: it must be at least params->n on entry + * and is set to the actual digest length on success. + * + * @param [in] key XMSS key (must have a parameter set bound). + * @param [in] msg Message to hash. + * @param [in] msgSz Length of msg in bytes. + * @param [out] hash Buffer receiving the digest. + * @param [in,out] hashSz On entry, size of hash buffer. On success, + * the digest length. + * @return 0 on success. + * @return BAD_FUNC_ARG when an argument is NULL or the buffer is too + * small for the digest. + * @return NOT_COMPILED_IN when the param set's hash family is disabled. + */ +int wc_XmssKey_HashMsg(const XmssKey* key, const byte* msg, word32 msgSz, + byte* hash, word32* hashSz) +{ + int ret = 0; + word32 needSz; + + if ((key == NULL) || (msg == NULL) || (hash == NULL) || (hashSz == NULL)) + return BAD_FUNC_ARG; + if (key->params == NULL) + return BAD_FUNC_ARG; + needSz = (word32)key->params->n; + if (*hashSz < needSz) + return BAD_FUNC_ARG; + + switch (key->params->hash) { + #ifdef WC_XMSS_SHA256 + case WC_HASH_TYPE_SHA256: + ret = wc_Hash(WC_HASH_TYPE_SHA256, msg, msgSz, hash, needSz); + break; + #endif + #ifdef WC_XMSS_SHA512 + case WC_HASH_TYPE_SHA512: + ret = wc_Hash(WC_HASH_TYPE_SHA512, msg, msgSz, hash, needSz); + break; + #endif + #ifdef WC_XMSS_SHAKE128 + case WC_HASH_TYPE_SHAKE128: { + wc_Shake shake; + ret = wc_InitShake128(&shake, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_Shake128_Update(&shake, msg, msgSz); + if (ret == 0) + ret = wc_Shake128_Final(&shake, hash, needSz); + wc_Shake128_Free(&shake); + } + break; + } + #endif + #ifdef WC_XMSS_SHAKE256 + case WC_HASH_TYPE_SHAKE256: { + wc_Shake shake; + ret = wc_InitShake256(&shake, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_Shake256_Update(&shake, msg, msgSz); + if (ret == 0) + ret = wc_Shake256_Final(&shake, hash, needSz); + wc_Shake256_Free(&shake); + } + break; + } + #endif + default: + WOLFSSL_MSG("XMSS: unsupported hash for HashMsg"); + ret = NOT_COMPILED_IN; + break; + } + + if (ret == 0) + *hashSz = needSz; + + return ret; +} + /*************************** * DIGEST init and free. @@ -810,7 +896,7 @@ static WC_INLINE int wc_xmsskey_signupdate(XmssKey* key, byte* sig, * * @param [in] key The XMSS key to init. * @param [in] heap Unused. - * @param [in] devId Unused. + * @param [in] devId Device identifier (used with WOLF_CRYPTO_CB). * * @return 0 on success. * @return BAD_FUNC_ARG when a parameter is NULL. @@ -820,7 +906,9 @@ int wc_XmssKey_Init(XmssKey* key, void* heap, int devId) int ret = 0; (void) heap; +#ifndef WOLF_CRYPTO_CB (void) devId; +#endif /* Validate parameters. */ if (key == NULL) { @@ -830,12 +918,82 @@ int wc_XmssKey_Init(XmssKey* key, void* heap, int devId) if (ret == 0) { /* Zeroize key and set state to initialized. */ ForceZero(key, sizeof(XmssKey)); + #ifdef WOLF_CRYPTO_CB + key->devId = devId; + #endif key->state = WC_XMSS_STATE_INITED; } return ret; } +#ifdef WOLF_PRIVATE_KEY_ID +/* Initialize an XmssKey and bind it to a device-side key identifier. + * + * @param [in,out] key XmssKey to initialize. + * @param [in] id Identifier bytes (may be NULL when len is 0). + * @param [in] len Length of id; must be in [0, XMSS_MAX_ID_LEN]. + * @param [in] heap Heap hint forwarded to wc_XmssKey_Init. + * @param [in] devId Device identifier. + * + * @return 0 on success. + * @return BAD_FUNC_ARG when key is NULL. + * @return BUFFER_E when len is negative or exceeds XMSS_MAX_ID_LEN. + */ +int wc_XmssKey_InitId(XmssKey* key, const unsigned char* id, int len, + void* heap, int devId) +{ + int ret = 0; + + if (key == NULL) + ret = BAD_FUNC_ARG; + if (ret == 0 && (len < 0 || len > XMSS_MAX_ID_LEN)) + ret = BUFFER_E; + if (ret == 0) + ret = wc_XmssKey_Init(key, heap, devId); + if (ret == 0 && id != NULL && len != 0) { + XMEMCPY(key->id, id, (size_t)len); + key->idLen = len; + } + + return ret; +} + +/* Initialize an XmssKey and bind it to a device-side key label. + * + * @param [in,out] key XmssKey to initialize. + * @param [in] label NUL-terminated label string (must be non-empty). + * @param [in] heap Heap hint forwarded to wc_XmssKey_Init. + * @param [in] devId Device identifier. + * + * @return 0 on success. + * @return BAD_FUNC_ARG when key or label is NULL. + * @return BUFFER_E when label is empty or longer than XMSS_MAX_LABEL_LEN. + */ +int wc_XmssKey_InitLabel(XmssKey* key, const char* label, void* heap, + int devId) +{ + int ret = 0; + int labelLen = 0; + + if (key == NULL || label == NULL) + ret = BAD_FUNC_ARG; + if (ret == 0) { + labelLen = (int)XSTRLEN(label); + if (labelLen == 0 || labelLen > XMSS_MAX_LABEL_LEN) + ret = BUFFER_E; + } + if (ret == 0) + ret = wc_XmssKey_Init(key, heap, devId); + if (ret == 0) { + XMEMCPY(key->label, label, (size_t)labelLen); + key->labelLen = labelLen; + } + + return ret; +} +#endif /* WOLF_PRIVATE_KEY_ID */ + /* Set the XMSS key parameter string. * * The input string must be one of the supported parm set names in @@ -1066,6 +1224,19 @@ int wc_XmssKey_MakeKey(XmssKey* key, WC_RNG* rng) WOLFSSL_MSG("error: XmssKey not ready for generation"); ret = BAD_STATE_E; } +#ifdef WOLF_CRYPTO_CB + /* HSM-backed keys skip the software write/context callbacks because the + * device owns the private state. On CRYPTOCB_UNAVAILABLE fall-through the + * software checks below still run. */ + if ((ret == 0) && (key->devId != INVALID_DEVID)) { + ret = wc_CryptoCb_PqcStatefulSigKeyGen(WC_PQC_STATEFUL_SIG_TYPE_XMSS, + key, rng); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) + return ret; + ret = 0; /* fall through to software path */ + } +#endif + /* Ensure write callback available. */ if ((ret == 0) && (key->write_private_key == NULL)) { WOLFSSL_MSG("error: XmssKey write callback is not set"); @@ -1191,6 +1362,16 @@ int wc_XmssKey_Reload(XmssKey* key) WOLFSSL_MSG("error: XmssKey not ready for reload"); ret = BAD_STATE_E; } + +#ifdef WOLF_CRYPTO_CB + /* State for HSM-backed keys lives in the device; no software reload. */ + if ((ret == 0) && (key->devId != INVALID_DEVID)) { + WOLFSSL_MSG("wc_XmssKey_Reload is a no-op for HSM-backed keys"); + key->state = WC_XMSS_STATE_OK; + return 0; + } +#endif + /* Ensure read and write callbacks are available. */ if ((ret == 0) && ((key->write_private_key == NULL) || (key->read_private_key == NULL))) { @@ -1313,6 +1494,20 @@ int wc_XmssKey_Sign(XmssKey* key, byte* sig, word32* sigLen, const byte* msg, WOLFSSL_MSG("error: XMSS sig buffer too small"); ret = BUFFER_E; } + +#ifdef WOLF_CRYPTO_CB + /* HSM-backed keys skip the software write/context callbacks because the + * device owns the private state. On CRYPTOCB_UNAVAILABLE fall-through the + * software checks below still run. */ + if ((ret == 0) && (key->devId != INVALID_DEVID)) { + ret = wc_CryptoCb_PqcStatefulSigSign(msg, (word32)msgLen, sig, sigLen, + WC_PQC_STATEFUL_SIG_TYPE_XMSS, key); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) + return ret; + ret = 0; /* fall through to software path */ + } +#endif + /* Check read and write callbacks available. */ if ((ret == 0) && ((key->write_private_key == NULL) || (key->read_private_key == NULL))) { @@ -1342,14 +1537,36 @@ int wc_XmssKey_Sign(XmssKey* key, byte* sig, word32* sigLen, const byte* msg, */ int wc_XmssKey_SigsLeft(XmssKey* key) { - int ret; + int ret = 0; /* Validate parameter. */ - if (key == NULL) { - ret = 0; + if (key == NULL) + return 0; + +#ifdef WOLF_CRYPTO_CB + if (key->devId != INVALID_DEVID) { + word32 sigsLeft = 0; + int cbRet = wc_CryptoCb_PqcStatefulSigSigsLeft( + WC_PQC_STATEFUL_SIG_TYPE_XMSS, key, &sigsLeft); + if (cbRet == 0) { + /* Clamp to int range; callers treat 0 as "exhausted". */ + return (sigsLeft > (word32)0x7FFFFFFF) + ? 0x7FFFFFFF : (int)sigsLeft; + } + /* The device owns the private state; no safe software fallback + * exists because key->sk does not reflect HSM state. */ + if (cbRet != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + WOLFSSL_MSG("PqcStatefulSigSigsLeft returned an error"); + } + else { + WOLFSSL_MSG("XMSS SigsLeft not supported by device"); + } + return 0; } +#endif + /* Validate state. */ - else if (key->state == WC_XMSS_STATE_NOSIGS) { + if (key->state == WC_XMSS_STATE_NOSIGS) { WOLFSSL_MSG("error: XMSS signatures exhausted"); ret = 0; } @@ -1430,6 +1647,12 @@ int wc_XmssKey_ExportPub(XmssKey* keyDst, const XmssKey* keySrc) keyDst->oid = keySrc->oid; keyDst->is_xmssmt = keySrc->is_xmssmt; keyDst->params = keySrc->params; + + #ifdef WOLF_CRYPTO_CB + /* Preserve the source key's device binding so the verify-only + * copy dispatches through the same crypto callback. */ + keyDst->devId = keySrc->devId; + #endif } if (ret == 0) { /* Mark keyDst as verify only, to prevent misuse. */ @@ -1606,6 +1829,9 @@ int wc_XmssKey_Verify(XmssKey* key, const byte* sig, word32 sigLen, if ((key == NULL) || (sig == NULL) || (m == NULL)) { ret = BAD_FUNC_ARG; } + if ((ret == 0) && (mLen <= 0)) { + ret = BAD_FUNC_ARG; + } /* Validate state. */ if ((ret == 0) && (key->state != WC_XMSS_STATE_OK) && (key->state != WC_XMSS_STATE_VERIFYONLY)) { @@ -1620,6 +1846,20 @@ int wc_XmssKey_Verify(XmssKey* key, const byte* sig, word32 sigLen, ret = BUFFER_E; } +#ifdef WOLF_CRYPTO_CB + if ((ret == 0) && (key->devId != INVALID_DEVID)) { + int res = 0; + ret = wc_CryptoCb_PqcStatefulSigVerify(sig, sigLen, m, (word32)mLen, + &res, WC_PQC_STATEFUL_SIG_TYPE_XMSS, key); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + if (ret == 0 && res != 1) + ret = SIG_VERIFY_E; + return ret; + } + ret = 0; /* fall through to software path */ + } +#endif + if (ret == 0) { WC_DECLARE_VAR(state, XmssState, 1, 0); diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 914f9ab22b..db5caadbf1 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -68384,6 +68384,114 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx) } #endif #endif /* HAVE_ED25519 */ + #if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) + if (info->pk.type == WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN) { + int pqcType = info->pk.pqc_stateful_sig_kg.type; + #if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + if (pqcType == WC_PQC_STATEFUL_SIG_TYPE_LMS) { + LmsKey* lk = (LmsKey*)info->pk.pqc_stateful_sig_kg.key; + lk->devId = INVALID_DEVID; + ret = wc_LmsKey_MakeKey(lk, info->pk.pqc_stateful_sig_kg.rng); + lk->devId = devIdArg; + } + #endif + #if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + if (pqcType == WC_PQC_STATEFUL_SIG_TYPE_XMSS) { + XmssKey* xk = (XmssKey*)info->pk.pqc_stateful_sig_kg.key; + xk->devId = INVALID_DEVID; + ret = wc_XmssKey_MakeKey(xk, info->pk.pqc_stateful_sig_kg.rng); + xk->devId = devIdArg; + } + #endif + } + else if (info->pk.type == WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN) { + int pqcType = info->pk.pqc_stateful_sig_sign.type; + word32 sigSz = *info->pk.pqc_stateful_sig_sign.outSz; + #if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + if (pqcType == WC_PQC_STATEFUL_SIG_TYPE_LMS) { + LmsKey* lk = (LmsKey*)info->pk.pqc_stateful_sig_sign.key; + lk->devId = INVALID_DEVID; + ret = wc_LmsKey_Sign(lk, + info->pk.pqc_stateful_sig_sign.out, &sigSz, + info->pk.pqc_stateful_sig_sign.msg, + (int)info->pk.pqc_stateful_sig_sign.msgSz); + lk->devId = devIdArg; + } + #endif + #if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + if (pqcType == WC_PQC_STATEFUL_SIG_TYPE_XMSS) { + XmssKey* xk = (XmssKey*)info->pk.pqc_stateful_sig_sign.key; + xk->devId = INVALID_DEVID; + ret = wc_XmssKey_Sign(xk, + info->pk.pqc_stateful_sig_sign.out, &sigSz, + info->pk.pqc_stateful_sig_sign.msg, + (int)info->pk.pqc_stateful_sig_sign.msgSz); + xk->devId = devIdArg; + } + #endif + *info->pk.pqc_stateful_sig_sign.outSz = sigSz; + } + else if (info->pk.type == WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY) { + int pqcType = info->pk.pqc_stateful_sig_verify.type; + int verifyRet = WC_NO_ERR_TRACE(NOT_COMPILED_IN); + #if defined(WOLFSSL_HAVE_LMS) + if (pqcType == WC_PQC_STATEFUL_SIG_TYPE_LMS) { + LmsKey* lk = (LmsKey*)info->pk.pqc_stateful_sig_verify.key; + lk->devId = INVALID_DEVID; + verifyRet = wc_LmsKey_Verify(lk, + info->pk.pqc_stateful_sig_verify.sig, + info->pk.pqc_stateful_sig_verify.sigSz, + info->pk.pqc_stateful_sig_verify.msg, + (int)info->pk.pqc_stateful_sig_verify.msgSz); + lk->devId = devIdArg; + } + #endif + #if defined(WOLFSSL_HAVE_XMSS) + if (pqcType == WC_PQC_STATEFUL_SIG_TYPE_XMSS) { + XmssKey* xk = (XmssKey*)info->pk.pqc_stateful_sig_verify.key; + xk->devId = INVALID_DEVID; + verifyRet = wc_XmssKey_Verify(xk, + info->pk.pqc_stateful_sig_verify.sig, + info->pk.pqc_stateful_sig_verify.sigSz, + info->pk.pqc_stateful_sig_verify.msg, + (int)info->pk.pqc_stateful_sig_verify.msgSz); + xk->devId = devIdArg; + } + #endif + if (info->pk.pqc_stateful_sig_verify.res != NULL) { + *info->pk.pqc_stateful_sig_verify.res = + (verifyRet == 0) ? 1 : 0; + } + /* SIG_VERIFY_E is a validity signal, not a crypto error, so + * translate it back to success for the dispatcher. */ + if (verifyRet == WC_NO_ERR_TRACE(SIG_VERIFY_E)) + verifyRet = 0; + ret = verifyRet; + } + else if (info->pk.type == WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT) { + int pqcType = info->pk.pqc_stateful_sig_sigs_left.type; + int count = 0; + #if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + if (pqcType == WC_PQC_STATEFUL_SIG_TYPE_LMS) { + LmsKey* lk = (LmsKey*)info->pk.pqc_stateful_sig_sigs_left.key; + lk->devId = INVALID_DEVID; + count = wc_LmsKey_SigsLeft(lk); + lk->devId = devIdArg; + } + #endif + #if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + if (pqcType == WC_PQC_STATEFUL_SIG_TYPE_XMSS) { + XmssKey* xk = (XmssKey*)info->pk.pqc_stateful_sig_sigs_left.key; + xk->devId = INVALID_DEVID; + count = wc_XmssKey_SigsLeft(xk); + xk->devId = devIdArg; + } + #endif + if (info->pk.pqc_stateful_sig_sigs_left.sigsLeft != NULL) + *info->pk.pqc_stateful_sig_sigs_left.sigsLeft = (word32)count; + ret = 0; + } + #endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ #ifdef WOLFSSL_HAVE_MLKEM if (info->pk.type == WC_PK_TYPE_PQC_KEM_KEYGEN) { if ((info->pk.pqc_kem_kg.type == WC_PQC_KEM_TYPE_KYBER) && @@ -69837,6 +69945,14 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) if (ret == 0) ret = dilithium_test(); #endif +#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) + if (ret == 0) + ret = xmss_test(); +#endif +#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) + if (ret == 0) + ret = lms_test(); +#endif #ifdef HAVE_ED25519 PRIVATE_KEY_UNLOCK(); if (ret == 0) diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index 710b522b9e..0075d823ea 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -91,6 +91,12 @@ #if defined(HAVE_FALCON) #include #endif +#if defined(WOLFSSL_HAVE_LMS) + #include +#endif +#if defined(WOLFSSL_HAVE_XMSS) + #include +#endif #ifdef WOLF_CRYPTO_CB_CMD @@ -349,6 +355,41 @@ typedef struct wc_CryptoInfo { int type; /* enum wc_PqcSignatureType */ } pqc_sig_check; #endif + #if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) + struct { + WC_RNG* rng; + void* key; + int type; /* enum wc_PqcStatefulSignatureType */ + } pqc_stateful_sig_kg; + struct { + /* Raw message. Backends following the PKCS#11 v3.2 + * CKM_HSS / CKM_XMSS convention of operating on a + * pre-computed digest can call wc_LmsKey_HashMsg / + * wc_XmssKey_HashMsg from inside the callback to obtain + * the algorithm-dictated digest of msg. */ + const byte* msg; + word32 msgSz; + byte* out; + word32* outSz; + void* key; + int type; /* enum wc_PqcStatefulSignatureType */ + } pqc_stateful_sig_sign; + struct { + const byte* sig; + word32 sigSz; + /* Raw message. See sign note. */ + const byte* msg; + word32 msgSz; + int* res; + void* key; + int type; /* enum wc_PqcStatefulSignatureType */ + } pqc_stateful_sig_verify; + struct { + void* key; + word32* sigsLeft; + int type; /* enum wc_PqcStatefulSignatureType */ + } pqc_stateful_sig_sigs_left; + #endif #ifdef HAVE_ANONYMOUS_INLINE_AGGREGATES }; #endif @@ -716,6 +757,23 @@ WOLFSSL_LOCAL int wc_CryptoCb_Ed25519Verify(const byte* sig, word32 sigLen, const byte* context, byte contextLen); #endif /* HAVE_ED25519 */ +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) +WOLFSSL_LOCAL int wc_CryptoCb_PqcStatefulSigGetDevId(int type, void* key); + +WOLFSSL_LOCAL int wc_CryptoCb_PqcStatefulSigKeyGen(int type, void* key, + WC_RNG* rng); +/* The raw message is forwarded to the callback. Backends that follow the + * PKCS#11 v3.2 CKM_HSS / CKM_XMSS convention (digest input) can call + * wc_LmsKey_HashMsg / wc_XmssKey_HashMsg from inside the callback. */ +WOLFSSL_LOCAL int wc_CryptoCb_PqcStatefulSigSign(const byte* msg, + word32 msgSz, byte* out, word32* outSz, int type, void* key); +WOLFSSL_LOCAL int wc_CryptoCb_PqcStatefulSigVerify(const byte* sig, + word32 sigSz, const byte* msg, word32 msgSz, int* res, int type, + void* key); +WOLFSSL_LOCAL int wc_CryptoCb_PqcStatefulSigSigsLeft(int type, void* key, + word32* sigsLeft); +#endif /* WOLFSSL_HAVE_LMS || WOLFSSL_HAVE_XMSS */ + #if defined(WOLFSSL_HAVE_MLKEM) WOLFSSL_LOCAL int wc_CryptoCb_PqcKemGetDevId(int type, void* key); diff --git a/wolfssl/wolfcrypt/lms.h b/wolfssl/wolfcrypt/lms.h index 19a7a0543c..f982f02930 100644 --- a/wolfssl/wolfcrypt/lms.h +++ b/wolfssl/wolfcrypt/lms.h @@ -223,6 +223,12 @@ enum wc_LmsState { extern "C" { #endif WOLFSSL_API int wc_LmsKey_Init(LmsKey * key, void * heap, int devId); +#ifdef WOLF_PRIVATE_KEY_ID +WOLFSSL_API int wc_LmsKey_InitId(LmsKey * key, const unsigned char * id, + int len, void * heap, int devId); +WOLFSSL_API int wc_LmsKey_InitLabel(LmsKey * key, const char * label, + void * heap, int devId); +#endif WOLFSSL_API int wc_LmsKey_SetLmsParm(LmsKey * key, enum wc_LmsParm lmsParm); WOLFSSL_API int wc_LmsKey_SetParameters(LmsKey * key, int levels, int height, int winternitz); @@ -251,6 +257,11 @@ WOLFSSL_API int wc_LmsKey_ImportPubRaw(LmsKey * key, const byte * in, word32 inLen); WOLFSSL_API int wc_LmsKey_Verify(LmsKey * key, const byte * sig, word32 sigSz, const byte * msg, int msgSz); +/* Compute the digest of a message with the hash function dictated by the + * LMS parameter set. Useful for crypto-callback / HSM backends that follow + * the PKCS#11 v3.2 CKM_HSS convention of taking a pre-computed digest. */ +WOLFSSL_API int wc_LmsKey_HashMsg(const LmsKey * key, const byte * msg, + word32 msgSz, byte * hash, word32 * hashSz); WOLFSSL_API const char * wc_LmsKey_ParmToStr(enum wc_LmsParm lmsParm); WOLFSSL_API const char * wc_LmsKey_RcToStr(enum wc_LmsRc lmsRc); diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index e8c24855be..200ea198a6 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -1573,6 +1573,14 @@ enum wc_PkType { WC_PK_TYPE_EC_GET_SIG_SIZE = 29, #undef _WC_PK_TYPE_MAX #define _WC_PK_TYPE_MAX WC_PK_TYPE_EC_GET_SIG_SIZE +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) + WC_PK_TYPE_PQC_STATEFUL_SIG_KEYGEN = 30, + WC_PK_TYPE_PQC_STATEFUL_SIG_SIGN = 31, + WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY = 32, + WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT = 33, + #undef _WC_PK_TYPE_MAX + #define _WC_PK_TYPE_MAX WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT +#endif WC_PK_TYPE_MAX = _WC_PK_TYPE_MAX }; @@ -1609,6 +1617,25 @@ enum wc_PkType { }; #endif +#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS) + /* Post quantum stateful hash-based signature algorithms. */ + enum wc_PqcStatefulSignatureType { + WC_PQC_STATEFUL_SIG_TYPE_NONE = 0, + #define _WC_PQC_STATEFUL_SIG_TYPE_MAX WC_PQC_STATEFUL_SIG_TYPE_NONE + #if defined(WOLFSSL_HAVE_LMS) + WC_PQC_STATEFUL_SIG_TYPE_LMS = 1, + #undef _WC_PQC_STATEFUL_SIG_TYPE_MAX + #define _WC_PQC_STATEFUL_SIG_TYPE_MAX WC_PQC_STATEFUL_SIG_TYPE_LMS + #endif + #if defined(WOLFSSL_HAVE_XMSS) + WC_PQC_STATEFUL_SIG_TYPE_XMSS = 2, + #undef _WC_PQC_STATEFUL_SIG_TYPE_MAX + #define _WC_PQC_STATEFUL_SIG_TYPE_MAX WC_PQC_STATEFUL_SIG_TYPE_XMSS + #endif + WC_PQC_STATEFUL_SIG_TYPE_MAX = _WC_PQC_STATEFUL_SIG_TYPE_MAX + }; +#endif + /* settings detection for compile vs runtime math incompatibilities */ enum { diff --git a/wolfssl/wolfcrypt/wc_lms.h b/wolfssl/wolfcrypt/wc_lms.h index 34f77279a0..a5307b076e 100644 --- a/wolfssl/wolfcrypt/wc_lms.h +++ b/wolfssl/wolfcrypt/wc_lms.h @@ -546,6 +546,13 @@ typedef struct HssPrivKey { #endif } HssPrivKey; +#ifndef LMS_MAX_ID_LEN +#define LMS_MAX_ID_LEN 32 +#endif +#ifndef LMS_MAX_LABEL_LEN +#define LMS_MAX_LABEL_LEN 32 +#endif + struct LmsKey { /* Public key. */ ALIGN16 byte pub[HSS_PUBLIC_KEY_LEN(LMS_MAX_NODE_LEN)]; @@ -574,6 +581,16 @@ struct LmsKey { #ifdef WOLF_CRYPTO_CB /* Device Identifier. */ int devId; + /* Per-device opaque context, populated by the callback. */ + void* devCtx; +#endif +#ifdef WOLF_PRIVATE_KEY_ID + /* Optional device-side key identifier. */ + byte id[LMS_MAX_ID_LEN]; + int idLen; + /* Optional device-side key label. */ + char label[LMS_MAX_LABEL_LEN]; + int labelLen; #endif }; diff --git a/wolfssl/wolfcrypt/wc_xmss.h b/wolfssl/wolfcrypt/wc_xmss.h index 200cd4322e..7c68c7d24c 100644 --- a/wolfssl/wolfcrypt/wc_xmss.h +++ b/wolfssl/wolfcrypt/wc_xmss.h @@ -205,6 +205,13 @@ typedef struct XmssParams { word8 bds_k; } XmssParams; +#ifndef XMSS_MAX_ID_LEN +#define XMSS_MAX_ID_LEN 32 +#endif +#ifndef XMSS_MAX_LABEL_LEN +#define XMSS_MAX_LABEL_LEN 32 +#endif + struct XmssKey { /* Public key. */ unsigned char pk[2 * WC_XMSS_MAX_N]; @@ -228,6 +235,20 @@ struct XmssKey { #endif /* ifndef WOLFSSL_XMSS_VERIFY_ONLY */ /* State of key. */ enum wc_XmssState state; +#ifdef WOLF_CRYPTO_CB + /* Device Identifier. */ + int devId; + /* Per-device opaque context, populated by the callback. */ + void* devCtx; +#endif +#ifdef WOLF_PRIVATE_KEY_ID + /* Optional device-side key identifier. */ + byte id[XMSS_MAX_ID_LEN]; + int idLen; + /* Optional device-side key label. */ + char label[XMSS_MAX_LABEL_LEN]; + int labelLen; +#endif }; typedef struct XmssState { diff --git a/wolfssl/wolfcrypt/xmss.h b/wolfssl/wolfcrypt/xmss.h index 4fd4da1cca..551185b848 100644 --- a/wolfssl/wolfcrypt/xmss.h +++ b/wolfssl/wolfcrypt/xmss.h @@ -170,6 +170,12 @@ typedef enum wc_XmssRc (*wc_xmss_read_private_key_cb)(byte* priv, word32 privSz, #endif WOLFSSL_API int wc_XmssKey_Init(XmssKey* key, void* heap, int devId); +#ifdef WOLF_PRIVATE_KEY_ID +WOLFSSL_API int wc_XmssKey_InitId(XmssKey* key, const unsigned char* id, + int len, void* heap, int devId); +WOLFSSL_API int wc_XmssKey_InitLabel(XmssKey* key, const char* label, + void* heap, int devId); +#endif WOLFSSL_API int wc_XmssKey_SetParamStr(XmssKey* key, const char* str); #ifndef WOLFSSL_XMSS_VERIFY_ONLY WOLFSSL_API int wc_XmssKey_SetWriteCb(XmssKey* key, @@ -194,6 +200,12 @@ WOLFSSL_API int wc_XmssKey_ImportPubRaw(XmssKey* key, const byte* in, word32 inLen); WOLFSSL_API int wc_XmssKey_Verify(XmssKey* key, const byte* sig, word32 sigSz, const byte* msg, int msgSz); +/* Compute the digest of a message with the hash function dictated by the + * XMSS parameter set. Useful for crypto-callback / HSM backends that follow + * the PKCS#11 v3.2 CKM_XMSS / CKM_XMSSMT convention of taking a + * pre-computed digest. */ +WOLFSSL_API int wc_XmssKey_HashMsg(const XmssKey* key, const byte* msg, + word32 msgSz, byte* hash, word32* hashSz); #ifdef __cplusplus } /* extern "C" */