Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion .github/workflows/build-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ on:
required: false
type: string
default: 'make check'
wolfssl_lms:
description: 'wolfSSL LMS configure flag (small|verify-only|<empty for full>)'
required: false
type: string
default: 'small'
wolfssl_lms_levels:
description: 'WOLFSSL_LMS_MAX_LEVELS to compile wolfSSL with'
required: false
type: string
default: '2'
wolfssl_lms_height:
description: 'WOLFSSL_LMS_MAX_HEIGHT to compile wolfSSL with'
required: false
type: string
default: '10'

jobs:
build:
Expand All @@ -34,8 +49,13 @@ jobs:
- name: wolfssl configure
working-directory: ./wolfssl
run: |
LMS_FLAG="--enable-lms"
if [ -n "${{ inputs.wolfssl_lms }}" ]; then
LMS_FLAG="--enable-lms=${{ inputs.wolfssl_lms }}"
fi
./configure --enable-cryptocb --enable-aescfb --enable-rsapss --enable-keygen --enable-pwdbased --enable-scrypt --enable-md5 \
--enable-mldsa C_EXTRA_FLAGS="-DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT -DHAVE_AES_ECB -DHAVE_AES_KEYWRAP"
--enable-mldsa $LMS_FLAG \
C_EXTRA_FLAGS="-DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT -DHAVE_AES_ECB -DHAVE_AES_KEYWRAP -DWOLFSSL_LMS_MAX_LEVELS=${{ inputs.wolfssl_lms_levels }} -DWOLFSSL_LMS_MAX_HEIGHT=${{ inputs.wolfssl_lms_height }}"
- name: wolfssl make install
working-directory: ./wolfssl
run: make
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,30 @@ jobs:
uses: ./.github/workflows/build-workflow.yml
with:
config: --enable-mlkem
lms:
uses: ./.github/workflows/build-workflow.yml
with:
config: --enable-lms
lms_private:
uses: ./.github/workflows/build-workflow.yml
with:
config: --enable-lms-private
# lms_state_persistence_test is in check_PROGRAMS — `make check` already
# runs it. No explicit second invocation; the original duplicate ran
# the test twice in the same token directory which masked some bugs.
check: make check
# Full (non-`small`) wolfSSL LMS build with larger MAX_LEVELS/MAX_HEIGHT
# to exercise multi-level HSS and the H10 path. The `small` build that the
# other LMS jobs use caps levels and height at 2/10, hiding bugs in the
# multi-level / H>5 code paths.
lms_private_full:
uses: ./.github/workflows/build-workflow.yml
with:
config: --enable-lms-private
check: make check
wolfssl_lms: ''
wolfssl_lms_levels: '4'
wolfssl_lms_height: '15'
debug:
uses: ./.github/workflows/build-workflow.yml
with:
Expand Down
35 changes: 35 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,41 @@ if(WOLFPKCS11_MLKEM)
endif()


# LMS/HSS verify-only support (RFC 8554)
add_option("WOLFPKCS11_LMS"
"Enable wolfPKCS11 LMS/HSS verification (default: disabled)"
"no" "yes;no"
)

# LMS/HSS keygen + signing (stateful, EXPERIMENTAL); implies WOLFPKCS11_LMS
add_option("WOLFPKCS11_LMS_PRIVATE"
"Enable wolfPKCS11 LMS/HSS keygen + signing (EXPERIMENTAL, default: disabled)"
"no" "yes;no"
)

if(WOLFPKCS11_LMS_PRIVATE AND NOT WOLFPKCS11_LMS)
message(STATUS "WOLFPKCS11_LMS_PRIVATE implies WOLFPKCS11_LMS; enabling automatically")
override_cache(WOLFPKCS11_LMS "yes")
endif()

if(WOLFPKCS11_LMS)
if(NOT WOLFPKCS11_PKCS11_V3_2)
message(STATUS "LMS/HSS requires PKCS#11 v3.2 support — enabling WOLFPKCS11_PKCS11_V3_2 automatically")
override_cache(WOLFPKCS11_PKCS11_V3_2 "yes")
if(NOT WOLFPKCS11_PKCS11_V3_0)
override_cache(WOLFPKCS11_PKCS11_V3_0 "yes")
list(APPEND WOLFPKCS11_DEFINITIONS "-DWOLFPKCS11_PKCS11_V3_0")
endif()
list(APPEND WOLFPKCS11_DEFINITIONS "-DWOLFPKCS11_PKCS11_V3_2")
endif()
list(APPEND WOLFPKCS11_DEFINITIONS "-DWOLFPKCS11_LMS")
endif()

if(WOLFPKCS11_LMS_PRIVATE)
list(APPEND WOLFPKCS11_DEFINITIONS "-DWOLFPKCS11_LMS_PRIVATE")
endif()


# If wolfpkcs11/options.h exists, delete it to avoid
# a mixup with build/wolfpkcs11/options.h.
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/wolfpkcs11/options.h")
Expand Down
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,71 @@ As ML-KEM is a feature of PKCS#11 version 3.2, support for that is required,
too. Hence, to enable all in wolfPKCS11, add `--enable-pkcs11v32 --enable-mlkem`
during the configure step.

### Optional: LMS / HSS hash-based signatures (RFC 8554)

LMS (Leighton-Micali) and its hierarchical extension HSS are hash-based
one-time signature schemes specified in
[RFC 8554](https://www.rfc-editor.org/rfc/rfc8554) and standardized in
NIST SP 800-208. The PKCS#11 v3.2 surface for HSS uses `CKK_HSS`,
`CKM_HSS_KEY_PAIR_GEN`, and `CKM_HSS`. Note that `C_Sign` and `C_Verify`
operate on the **whole message** (not a digest).

Two separate compile flags split this feature into a safe verifier-only
build and a stateful signer build:

| Flag | What it enables | Risk |
|---|---|---|
| `--enable-lms` (CMake `WOLFPKCS11_LMS=yes`) | Verification + public-key import only. | None — verify is stateless. |
| `--enable-lms-private` (CMake `WOLFPKCS11_LMS_PRIVATE=yes`, implies `--enable-lms`) | Adds key generation and signing. **EXPERIMENTAL.** | Stateful private keys. Mismanagement causes one-time-key reuse and complete forgery. |

Build wolfSSL with `--enable-lms` (or `--enable-lms=small` for a smaller
footprint) and then build wolfPKCS11 with one of the two flags above.
For `--enable-lms-private` wolfSSL must NOT be built with
`--enable-lms=verify-only` (the build will fail at the prerequisite check).

#### Operational responsibilities for `--enable-lms-private`

LMS/HSS are stateful: every signature consumes a one-time key, and re-using
a leaf index breaks security catastrophically. wolfPKCS11 implements the
following safeguards on top of the wolfSSL state callbacks:

* The per-signature state file is encrypted with the token master key and
written via an `mkstemp` + `fsync(file)` + `rename` + `fsync(parent dir)`
sequence, so a returned signature always corresponds to a state index that
is durably on disk. The on-disk header (parameters, version) is bound into
the AES-GCM tag so any tampering is detected at decrypt time.
* On any state-write failure the in-memory state is *poisoned* — subsequent
sign attempts return `CKR_DEVICE_ERROR` until the key is reloaded from
durable storage.
* **Private key import** (`C_CreateObject` with `CKO_PRIVATE_KEY` +
`CKK_HSS`) is rejected unconditionally. There is no way to install an
HSS private key with a caller-controlled state index.
* **Private key export** (`C_GetAttributeValue` for `CKA_VALUE` on a private
HSS key) returns `CK_UNAVAILABLE_INFORMATION` regardless of
`CKA_EXTRACTABLE` / `CKA_SENSITIVE`.
* **Copying** an HSS private key (`C_CopyObject`) is rejected.

The operator is responsible for:

* **Never copying or snapshotting the token directory** while a signing key
is present. A restored copy would re-issue already-consumed indices.
* Treating HSS private keys as bound to the device they were generated on.
`rsync`, `cp`, filesystem snapshots and backup tools will silently break
security if used on a token with active HSS keys.
* Storing the token directory on a journaled filesystem (durability
assumptions rely on `fsync` semantics).

The default parameter set when none is supplied is `levels = 1`,
`H = 10`, `W = 8` (1024 lifetime signatures, ~3 KiB signature). Mixed
`(H, W)` across HSS levels is rejected with `CKR_MECHANISM_PARAM_INVALID`
because the underlying wolfSSL API requires uniform parameters.

For non-production rigs (e.g., tmpfs-backed test harnesses) the env var
`WOLFPKCS11_STATEFUL_RELAX_FSYNC=1` skips the per-signature `fsync` calls.
The variable applies to all stateful hash-based signature schemes
(LMS/HSS today; XMSS in the future). **Never set this in production.** A
power loss or kernel panic can then expose a one-time-key reuse window.

### Build options and defines

#### Define WOLFPKCS11_TPM_STORE
Expand Down Expand Up @@ -219,6 +284,8 @@ cmake -DCMAKE_PREFIX_PATH=/path/to/wolfssl/install ..
| `WOLFPKCS11_PKCS11_V3_2` | `no` | PKCS#11 v3.2 support |
| `WOLFPKCS11_MLDSA` | `no` | ML-DSA support |
| `WOLFPKCS11_MLKEM` | `no` | ML-KEM support |
| `WOLFPKCS11_LMS` | `no` | LMS/HSS verification (RFC 8554) |
| `WOLFPKCS11_LMS_PRIVATE` | `no` | LMS/HSS keygen + signing (EXPERIMENTAL) |
| `WOLFPKCS11_EXAMPLES` | `yes` | Build examples |
| `WOLFPKCS11_TESTS` | `yes` | Build and register tests |
| `WOLFPKCS11_COVERAGE` | `no` | Code coverage support |
Expand Down Expand Up @@ -246,6 +313,14 @@ POSIX or `%APPDIR%\wolfPKCS11` on Windows), and finally the optional

Set to any value to stop storage of token data.

### WOLFPKCS11_STATEFUL_RELAX_FSYNC

When set to `1`, skips the per-signature `fsync` calls used by stateful
hash-based signature schemes (LMS/HSS today; XMSS in the future). **Never
set this in production.** A power loss or kernel panic with this enabled
can roll back the leaf-index advance and expose a one-time-key reuse
window. See the LMS/HSS build section for details.


## Release Notes

Expand Down
4 changes: 4 additions & 0 deletions cmake/options.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ extern "C" {
#cmakedefine WOLFPKCS11_MLDSA
#undef WOLFPKCS11_MLKEM
#cmakedefine WOLFPKCS11_MLKEM
#undef WOLFPKCS11_LMS
#cmakedefine WOLFPKCS11_LMS
#undef WOLFPKCS11_LMS_PRIVATE
#cmakedefine WOLFPKCS11_LMS_PRIVATE
#undef WOLFPKCS11_TPM
#cmakedefine WOLFPKCS11_TPM
#undef WOLFPKCS11_NSS
Expand Down
40 changes: 40 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,44 @@ then
AM_CFLAGS="$AM_CFLAGS -DWOLFPKCS11_MLKEM"
fi

# LMS/HSS verify-only support (RFC 8554)
AC_ARG_ENABLE([lms],
[AS_HELP_STRING([--enable-lms],[Enable LMS/HSS verification and public-key import (default: disabled)])],
[ ENABLED_LMS=$enableval ],
[ ENABLED_LMS=no ]
)

# LMS/HSS keygen + signing (stateful, EXPERIMENTAL); implies --enable-lms
AC_ARG_ENABLE([lms-private],
[AS_HELP_STRING([--enable-lms-private],[Enable LMS/HSS keygen and signing (EXPERIMENTAL, stateful private keys, default: disabled)])],
[ ENABLED_LMS_PRIVATE=$enableval ],
[ ENABLED_LMS_PRIVATE=no ]
)

if test "$ENABLED_LMS_PRIVATE" = "yes" && test "$ENABLED_LMS" = "no"
then
AC_MSG_NOTICE([--enable-lms-private implies --enable-lms; enabling])
ENABLED_LMS=yes
fi

if test "$ENABLED_LMS" = "yes"
then
if test "$ENABLED_PKCS11V3_2" = "no"; then
ENABLED_PKCS11V3_2=yes
AM_CFLAGS="$AM_CFLAGS -DWOLFPKCS11_PKCS11_V3_2"
if test "$ENABLED_PKCS11V3_0" = "no"; then
ENABLED_PKCS11V3_0=yes
AM_CFLAGS="$AM_CFLAGS -DWOLFPKCS11_PKCS11_V3_0"
fi
fi
AM_CFLAGS="$AM_CFLAGS -DWOLFPKCS11_LMS"
fi

if test "$ENABLED_LMS_PRIVATE" = "yes"
then
AM_CFLAGS="$AM_CFLAGS -DWOLFPKCS11_LMS_PRIVATE"
fi


AM_CONDITIONAL([BUILD_STATIC],[test "x$enable_shared" = "xno"])

Expand Down Expand Up @@ -755,6 +793,8 @@ echo " * ECC: $ENABLED_ECC"
echo " * HKDF: $ENABLED_HKDF"
echo " * ML-DSA: $ENABLED_MLDSA"
echo " * ML-KEM: $ENABLED_MLKEM"
echo " * LMS/HSS verify: $ENABLED_LMS"
echo " * LMS/HSS sign+keygen (EXP): $ENABLED_LMS_PRIVATE"
echo " * NSS modifications: $ENABLED_NSS"
echo " * Default token path: $WOLFPKCS11_DEFAULT_TOKEN_PATH"
echo " * PKCS#11 Version 3.0: $ENABLED_PKCS11V3_0"
Expand Down
Loading
Loading