Skip to content

Add verify-only LMS/HSS and XMSS/XMSS^MT support (RFC 8554 / RFC 8391) — Step 1#3

Open
Frauschi wants to merge 5 commits into
masterfrom
claude/add-hbs-verify
Open

Add verify-only LMS/HSS and XMSS/XMSS^MT support (RFC 8554 / RFC 8391) — Step 1#3
Frauschi wants to merge 5 commits into
masterfrom
claude/add-hbs-verify

Conversation

@Frauschi

@Frauschi Frauschi commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Overview

This is Step 1 of adding stateful hash-based signature (HBS) support to wolfPKCS11, split into phases for easier review:

  • Step 1 (this PR): verify-only support for LMS/HSS (RFC 8554) and XMSS/XMSS^MT (RFC 8391) — PKCS#11 header constants, raw public-key import, signature verification, and token-object persistence of public keys. No keygen, no signing, no private-key objects, no durable state-file framework.
  • Step 2 (future): private-key support for LMS (keygen + signing), introducing the durable state-file framework.
  • Step 3 (future): private-key support for XMSS, reusing the Step 2 framework.

Keeping Step 1 to the verify path makes it small and self-contained: a token can verify RFC 8554 / RFC 8391 signatures against imported public keys, with no changes to the storage backend beyond serializing raw public keys.

What's included

PKCS#11 API (wolfpkcs11/pkcs11.h)

  • Key types CKK_HSS, CKK_XMSS, CKK_XMSSMT; mechanisms CKM_HSS, CKM_XMSS, CKM_XMSSMT (keygen mechanisms defined for header completeness but not wired until Step 2/3).
  • LMS parameter types/constants (CK_HSS_LEVELS, CK_LMS_TYPE, CK_LMOTS_TYPE, CKL_LMS_*, CKL_LMOTS_*, CK_HSS_PARAMS). Constants are always defined; the library rejects at runtime when built without support.

Build system

  • --enable-lms-DWOLFPKCS11_LMS and --enable-xmss-DWOLFPKCS11_XMSS (autotools + CMake), each pulling in PKCS#11 v3.2.
  • Both self-disable when SHA-256 is unavailable (both schemes require it), mirroring the ML-DSA/ML-KEM pattern.

Verify implementation (src/internal.c, src/crypto.c, src/slot.c)

  • LMS: wc_LmsKey_InitSetParametersImportPubRawVerify. Parameters are self-derived from the raw public key's type fields (L / LMS_type / LMOTS_type); CKA_PARAMETER_SET is accepted as an optional override.
  • XMSS/XMSS^MT: wc_XmssKey_ImportPubRaw_ex auto-derives the parameter set from the embedded OID; is_xmssmt is taken from the key type (CKK_XMSS vs CKK_XMSSMT). One XmssKey type serves both.
  • Import accepts the bare wc_*_ImportPubRaw byte format (60 bytes for HSS, 68 for XMSS with SHA-256).
  • Verify maps only a genuine mismatch (SIG_VERIFY_E) to CKR_SIGNATURE_INVALID; argument/format errors surface as function failures. Wrong-length or malformed public keys on import are rejected with CKR_ATTRIBUTE_VALUE_INVALID. Private-key import and keygen are rejected.
  • Mechanisms advertised with CKF_VERIFY only.

Token-object persistence

  • HBS public keys imported as token objects (CKA_TOKEN=CK_TRUE) are serialized to and reloaded from the token store — just the raw public-key bytes, no state-file machinery.

Testing

  • KAT import + verify (good signature accepted, tampered rejected) for HSS, XMSS and XMSS^MT.
  • Attribute round-trip (CKA_VALUE, CKA_HSS_LEVELS/LMS_TYPE/LMOTS_TYPE), C_GetMechanismInfo (CKF_VERIFY set, no CKF_SIGN), mechanism/key-type mismatch, empty-template and truncated-key rejection.
  • Standalone tests/hbs_persistence_test.c performing a real disk round-trip: create token public key → C_Finalize/C_Initialize → find by CKA_ID → re-verify.
  • Private-key-import-rejected and keygen-rejected tests.

Verified across four build configurations — disabled, --enable-lms, --enable-xmss, and both — each building clean with -Werror and passing make check (48 test programs) with no regressions. Header ABI (pkcs11.h) compiles standalone without the feature macros.

🤖 Generated with Claude Code


Generated by Claude Code

claude added 3 commits July 9, 2026 19:03
Step 1 of a phased addition of stateful hash-based signatures to
wolfPKCS11: public-key import and signature verification only. No
key generation, signing, or private-key state persistence — those
follow in later steps.

Two independent build options gate the work, each pulling in PKCS#11
v3.2 and requiring the matching wolfSSL capability:
  --enable-lms   -> -DWOLFPKCS11_LMS   (requires WOLFSSL_HAVE_LMS)
  --enable-xmss  -> -DWOLFPKCS11_XMSS  (requires WOLFSSL_HAVE_XMSS)
CMake gains the equivalent WOLFPKCS11_LMS / WOLFPKCS11_XMSS options.

Public API (wolfpkcs11/pkcs11.h), exposed unconditionally so downstream
consumers compile regardless of build flags (the library rejects an
unbuilt mechanism at runtime with CKR_MECHANISM_INVALID):
  - key types  CKK_HSS, CKK_XMSS, CKK_XMSSMT
  - mechanisms CKM_HSS, CKM_XMSS, CKM_XMSSMT (+ the *_KEY_PAIR_GEN
    identifiers, defined for completeness but not implemented here)
  - CKA_HSS_* attributes and the CK_HSS_PARAMS / CKL_* helpers

Design: public keys are self-describing, so no parameter attribute is
required on import. LMS derives (levels, height, winternitz) from the
first 12 bytes of the raw RFC 8554 key; XMSS uses
wc_XmssKey_ImportPubRaw_ex with the OID embedded in the key, and the
PKCS#11 key type (CKK_XMSS vs CKK_XMSSMT) supplies the is_xmssmt flag
that disambiguates the overlapping OID namespaces. CKA_PARAMETER_SET is
accepted but ignored.

Implementation mirrors the existing ML-DSA/ML-KEM structure with
parallel per-scheme code (WP11_Hss_Verify / WP11_Xmss_Verify, separate
SetKey and GetAttr), a thin dispatch in crypto.c (C_CreateObject import,
C_VerifyInit / C_Verify), and mechanism advertising in slot.c
(CKF_VERIFY, 0..0 key-size envelope). Public keys persist as their raw
byte form under new WOLFPKCS11_STORE_{HSS,XMSS}KEY_PUB storage types, so
both session and token objects work. Private-key import and keygen are
rejected (CKR_ATTRIBUTE_VALUE_INVALID / CKR_MECHANISM_INVALID); object
copy of HBS keys is rejected as unsupported.

Tests (tests/pkcs11v3test.c): known-answer import+verify for HSS
(L1/H5/W4), XMSS (SHA2_10_256) and XMSS^MT (SHA2_20/2_256) — import the
raw public key, verify a valid signature, then flip a byte and require
CKR_SIGNATURE_INVALID — plus private-import-rejected and
keygen-rejected negative tests per scheme.

Verified across four configurations (disabled / LMS / XMSS / both) with
-Werror: builds clean, make check passes, and pkcs11.h compiles
standalone without the feature macros.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GtRoh5TVMmmfX81LLRbroa
Fixes from code review of the initial verify-only HBS commit:

- internal.c: drop the erroneous wc_LmsKey_Free/wc_XmssKey_Free on import
  failure. The object still owns the key with encoded == 0, so
  WP11_Object_Free performs the single free; freeing on the import error
  path double-freed.
- internal.c: reject a public key created without CKA_VALUE
  (BAD_FUNC_ARG -> CKR_ATTRIBUTE_VALUE_INVALID) instead of silently
  creating an unusable object; there is no keygen path in a verify-only
  build that would populate the value later.
- internal.c: narrow the verify error mapping to SIG_VERIFY_E only.
  Argument/format errors (BAD_FUNC_ARG, BUFFER_E) are not signature
  forgeries and must surface as a function failure rather than being
  reported as an invalid signature.
- crypto.c: map BUFFER_E (wrong-length raw public key) on import to
  CKR_ATTRIBUTE_VALUE_INVALID, matching BAD_FUNC_ARG.
- crypto.c: register the CKA_HSS_* attribute types in attrType[] so
  IsKnownAttrType accepts them; without this, C_GetAttributeValue rejected
  the HSS parameter queries with CKR_ATTRIBUTE_TYPE_INVALID before ever
  dispatching to HssObject_GetAttr.
- configure.ac, CMakeLists.txt: self-disable LMS/XMSS when SHA-256 is not
  available (both schemes require SHA-256), mirroring the ML-DSA/ML-KEM
  self-disable pattern.
- tests: add coverage for CKA_VALUE round-trip, HSS parameter attribute
  queries, C_GetMechanismInfo (CKF_VERIFY set, no CKF_SIGN), empty-template
  rejection, truncated-public-key rejection, and mechanism/key-type
  mismatch. Change the KAT tampered-signature buffer from static to a local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GtRoh5TVMmmfX81LLRbroa
Verify-only LMS/HSS and XMSS/XMSS^MT public keys imported as token objects
(CKA_TOKEN=CK_TRUE) are already persisted to and reloaded from the token
store by the existing encode/decode/load/store paths. Add an explicit
regression test that exercises a real disk round-trip: create the token
public key, C_Finalize + C_Initialize to force a reload from disk, find the
key by CKA_ID, and confirm it still verifies a known-good signature (and
rejects a tampered one). Covers HSS, XMSS and XMSS^MT.

To avoid duplicating the ~650 lines of KAT vectors, factor the shared
public keys, signatures and message out of pkcs11v3test.c into a new
tests/hbs_kat.h included by both test programs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GtRoh5TVMmmfX81LLRbroa
@Frauschi Frauschi force-pushed the claude/add-hbs-verify branch from 6df7544 to e8402ca Compare July 9, 2026 17:03
Frauschi added 2 commits July 9, 2026 20:14
Address review findings on the verify-only HBS support:

- Remove the unused WP11_Hss_SigLen, WP11_Hss_PubLen,
  WP11_Hss_GetParameters, WP11_Xmss_SigLen and WP11_Xmss_PubLen
  helpers and their prototypes. They had no callers and implied a
  signature-length check that verify does not perform; wolfSSL's
  verify validates the length internally.
- Set encoded to 0 on all paths in the HSS and XMSS decode helpers so
  WP11_Object_Free performs the single key free even when the import
  fails. The import helpers never free, so the previous
  encoded = (ret != 0) leaked the key on a decode failure.
- Correct the C_CopyObject comments to say public-key copy is
  intentionally not implemented rather than citing statefulness.
- Drop the redundant CKO_PRIVATE_KEY guard in WP11_Object_SetHssKey
  and WP11_Object_SetXmssKey; it is subsumed by the following
  CKO_PUBLIC_KEY check.
- Move the declarations in the HSS type-array attribute case to the
  top of the block to match the file's C89 style.
- Allocate the tamper-copy signature buffers in the HBS tests with
  XMALLOC instead of an 8 KB stack array.
- Return NOT_AVAILABLE_E for CKA_HSS_KEYS_REMAINING and the
  non-public CKA_VALUE branch so the result matches the switch
  default arm.
Address a second round of review findings on the verify-only HBS
support:

- Report a wrong-length or malformed HBS signature as
  CKR_SIGNATURE_INVALID instead of CKR_FUNCTION_FAILED. WP11_Hss_Verify
  and WP11_Xmss_Verify now compare the supplied length against the
  key's parameter-set signature length before calling wolfSSL and map a
  mismatch to stat = 0.
- Register WP11_INIT_HSS_VERIFY and WP11_INIT_XMSS_VERIFY in
  wp11_init_get_op_category so an in-progress HBS verify is recognized
  as a verify operation. A second C_VerifyInit with no intervening
  C_Verify now returns CKR_OPERATION_ACTIVE instead of silently
  resetting the operation.
- Drop the cached serialization in WP11_Object_SetHssKey and
  WP11_Object_SetXmssKey after a successful import so a re-import via
  C_SetAttributeValue does not persist stale public-key bytes on the
  next store.
- Remove the redundant ulValueLen write in the CKA_HSS_KEYS_REMAINING
  attribute case; C_GetAttributeValue already sets it on NOT_AVAILABLE_E.
- Document that HBS verification only reads the key, so the shared RO
  lock is safe for concurrent verifies.
- Extend the HBS known-answer tests to cover a wrong-length signature,
  C_CopyObject rejection, and the double-C_VerifyInit
  CKR_OPERATION_ACTIVE guard.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants