From b52132cb91ee70f9f64dbf91efb369b9560d4697 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 4 Aug 2026 16:27:34 -0700 Subject: [PATCH 1/2] Add OpenSSL-compatible LMS verification --- .github/workflows/nginx-pqc.yml | 14 +- .github/workflows/wolfssl-pqc-kat.yml | 52 +- .github/workflows/wolfssl-versions-pqc.yml | 46 +- README.md | 21 + configure.ac | 13 + docs/INTEGRATION_GUIDE.md | 28 +- include/wolfprovider/alg_funcs.h | 7 +- include/wolfprovider/settings.h | 17 +- scripts/build-wolfprovider.sh | 34 +- scripts/test-pqc-kat.sh | 55 ++- scripts/utils-openssl.sh | 19 +- scripts/utils-wolfprovider.sh | 8 +- scripts/utils-wolfssl.sh | 41 +- src/include.am | 2 +- src/wp_lms.c | 530 +++++++++++++++++++++ src/wp_wolfprov.c | 10 + test/include.am | 1 + test/test_lms.c | 380 +++++++++++++++ test/unit.c | 6 + test/unit.h | 6 + 20 files changed, 1196 insertions(+), 94 deletions(-) create mode 100644 src/wp_lms.c create mode 100644 test/test_lms.c diff --git a/.github/workflows/nginx-pqc.yml b/.github/workflows/nginx-pqc.yml index e75cf558..27ed5e2a 100644 --- a/.github/workflows/nginx-pqc.yml +++ b/.github/workflows/nginx-pqc.yml @@ -63,22 +63,20 @@ jobs: else REFS=$(printf '%s\n%s\n' "master" "$LATEST") fi - # PQC needs the wc_MlDsaKey_* seed/message API that lands after - # v5.9.1-stable, so the floor is v5.9.2-stable. master is always - # eligible; a -stable ref only runs once it is past the floor. A + # v5.9.2-stable is the first release with the complete PQC interfaces + # required by wolfProvider. master is always eligible. A # pre-floor ref (e.g. v5.8.4-stable from Wave 2) is dropped -- it # would just fail the --enable-pqc gate. - PQC_FLOOR="v5.9.1-stable" + PQC_FLOOR="v5.9.2-stable" ELIGIBLE=() for ref in $REFS; do if [ "$ref" = "master" ]; then ELIGIBLE+=("$ref") - elif [ "$ref" != "$PQC_FLOOR" ] && \ - [ "$(printf '%s\n%s\n' "$PQC_FLOOR" "$ref" \ - | sort -V | tail -n1)" = "$ref" ]; then + elif [ "$(printf '%s\n%s\n' "$PQC_FLOOR" "$ref" \ + | sort -V | head -n1)" = "$PQC_FLOOR" ]; then ELIGIBLE+=("$ref") else - echo "::notice::Skipping pre-PQC-floor wolfSSL ref $ref (floor past $PQC_FLOOR)" + echo "::notice::Skipping pre-PQC-floor wolfSSL ref $ref (floor $PQC_FLOOR)" fi done if [ ${#ELIGIBLE[@]} -eq 0 ]; then diff --git a/.github/workflows/wolfssl-pqc-kat.yml b/.github/workflows/wolfssl-pqc-kat.yml index f2f24f0a..62a64746 100644 --- a/.github/workflows/wolfssl-pqc-kat.yml +++ b/.github/workflows/wolfssl-pqc-kat.yml @@ -1,7 +1,7 @@ name: wolfSSL PQC KAT (OpenSSL vectors) -# Runs OpenSSL's own ML-KEM (FIPS 203) and ML-DSA (FIPS 204) EVP KAT vectors -# (NIST ACVP + Wycheproof, 2602 sub-tests) through wolfProvider using +# Runs OpenSSL's own ML-KEM (FIPS 203), ML-DSA (FIPS 204), and LMS +# (RFC 8554 / SP 800-208) EVP KAT vectors through wolfProvider using # OpenSSL's own evp_test harness, unmodified. Closely mirrors the version # matrix of wolfssl-versions-pqc.yml: a discover job resolves the latest # wolfSSL -stable tag and the latest OpenSSL release, then the test job runs @@ -62,9 +62,9 @@ jobs: echo "Latest stable wolfSSL: $LATEST" echo "Latest OpenSSL: $OSSL" echo "openssl-tag=$OSSL" >> "$GITHUB_OUTPUT" - PQC_FLOOR="v5.9.1-stable" + PQC_FLOOR="v5.9.2-stable" if [ "$(printf '%s\n%s\n' "$PQC_FLOOR" "$LATEST" \ - | sort -V | tail -n1)" != "$PQC_FLOOR" ]; then + | sort -V | head -n1)" = "$PQC_FLOOR" ]; then LATEST_PQC=true else LATEST_PQC=false @@ -85,17 +85,22 @@ jobs: "sfx":" [non-replace] [force-fail]"}, {"replace":false,"ff":"","sfx":" [non-replace]"} ] | map({"name":($lbl+.sfx), - "wolfssl-ref":$ref,"pqc":true,"replace":.replace, + "wolfssl-ref":$ref,"pqc":true,"lms":true, + "replace":.replace, "force_fail":.ff}) else [ {"name":($lbl+" [build-only]"), - "wolfssl-ref":$ref,"pqc":false,"replace":false, + "wolfssl-ref":$ref,"pqc":false,"lms":false, + "replace":false, "force_fail":""} ] end; { include: ( rows("v5.8.0-stable"; false; "pre-PQC v5.8.0-stable") + rows($latest; $latest_pqc; ("latest stable " + $latest)) - + rows("master"; true; "master") ) + + rows("master"; true; "master") + + [ {"name":"master [LMS only]", + "wolfssl-ref":"master","pqc":false,"lms":true, + "replace":false,"force_fail":""} ] ) }') echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" @@ -125,25 +130,29 @@ jobs: id: deps uses: ./.github/actions/oras-build-deps with: - variant: pqckat${{ matrix.replace == true && '-rd' || '' }} + variant: pqckat${{ matrix.replace == true && '-rd' || '' }}${{ matrix.lms == true && '-lms' || '' }} openssl_ref: ${{ needs.discover-versions.outputs.openssl-tag }} wolfssl_ref: ${{ matrix.wolfssl-ref }} - extra_key: pqc${{ matrix.pqc }} - cache_openssl_source: ${{ matrix.pqc }} + extra_key: pqc${{ matrix.pqc }}-lms${{ matrix.lms }} + cache_openssl_source: ${{ matrix.pqc == true || matrix.lms == true }} github_token: ${{ secrets.GITHUB_TOKEN }} - - name: Build wolfProvider (PQC=${{ matrix.pqc }}, replace=${{ matrix.replace }}) + - name: Build wolfProvider (PQC=${{ matrix.pqc }}, LMS=${{ matrix.lms }}, replace=${{ matrix.replace }}) run: | - ARGS="--enable-pqc" + ARGS="" + if [ "${{ matrix.pqc }}" = "true" ]; then + ARGS="$ARGS --enable-pqc" + fi + if [ "${{ matrix.lms }}" = "true" ]; then + ARGS="$ARGS --enable-lms" + fi if [ "${{ matrix.replace }}" = "true" ]; then ARGS="$ARGS --replace-default" fi - if [ "${{ matrix.pqc }}" != "true" ]; then - ARGS="" - fi # The KAT runs OpenSSL's evp_test; replace-default builds omit it # ('no-tests') unless we ask for it here. - if [ "${{ matrix.pqc }}" = "true" ]; then + if [ "${{ matrix.pqc }}" = "true" ] || \ + [ "${{ matrix.lms }}" = "true" ]; then ARGS="$ARGS --enable-openssl-test" fi OPENSSL_TAG=${{ needs.discover-versions.outputs.openssl-tag }} \ @@ -162,18 +171,19 @@ jobs: openssl_source_hit: ${{ steps.deps.outputs.openssl_source_hit }} cache_openssl_source: ${{ steps.deps.outputs.cache_openssl_source }} - # Runs all 2602 OpenSSL ML-KEM/ML-DSA vectors. In normal mode every file + # Runs all OpenSSL ML-KEM/ML-DSA/LMS vectors. In normal mode every file # must pass and the full count must run; in force-fail mode the run fails # and check-workflow-result.sh inverts that to a pass, proving wolfProvider # genuinely served the crypto with no silent OpenSSL fallback. - - name: PQC KAT (all 2602 OpenSSL vectors) - if: matrix.pqc == true + - name: PQC KAT (ML-KEM, ML-DSA, and LMS OpenSSL vectors) + if: matrix.pqc == true || matrix.lms == true shell: bash run: | set +e - # The build step already built the provider with --enable-pqc; the KAT - # only runs against it (no rebuild). Force-fail is a runtime env var. + # The KAT only runs against the provider built in the prior step. export ${{ matrix.force_fail }} + export WOLFPROV_PQC=${{ matrix.pqc == true && '1' || '0' }} + export WOLFPROV_LMS=${{ matrix.lms == true && '1' || '0' }} ./scripts/test-pqc-kat.sh TEST_RESULT=$? $GITHUB_WORKSPACE/.github/scripts/check-workflow-result.sh \ diff --git a/.github/workflows/wolfssl-versions-pqc.yml b/.github/workflows/wolfssl-versions-pqc.yml index ca75360f..80d5dfd5 100644 --- a/.github/workflows/wolfssl-versions-pqc.yml +++ b/.github/workflows/wolfssl-versions-pqc.yml @@ -6,9 +6,8 @@ name: wolfSSL Versions (PQC) # floor, then the build job runs three rows: pre-PQC floor, dynamically # resolved latest -stable, and master. # -# PQC is opt-in (--enable-pqc). PQC_FLOOR is v5.9.1-stable: the wc_MlDsaKey_* -# seed/message API wolfProvider's PQC code depends on lands post-v5.9.1-stable -# (wolfSSL PR #10436), so v5.9.2-stable+ is the first PQC-eligible release. +# PQC is opt-in (--enable-pqc). PQC_FLOOR is v5.9.2-stable, the first release +# with the complete ML-KEM and ML-DSA interfaces required by wolfProvider. # PQC rows build with --enable-pqc against the latest OpenSSL (>= 3.6 required); # older/no-flag rows build without it and verify PQC is absent (opt-in). @@ -60,21 +59,19 @@ jobs: echo "Latest OpenSSL: $OSSL" echo "latest-stable=$LATEST" >> "$GITHUB_OUTPUT" echo "openssl-tag=$OSSL" >> "$GITHUB_OUTPUT" - # Enable PQC when $LATEST is strictly newer than v5.9.1-stable - # (i.e. v5.9.2-stable, v5.10+, v6+, ...). Anything at or before - # the floor lacks the wc_MlDsaKey_* / wc_dilithium_sign_ctx_msg - # API and stays on the no-symbol path. - PQC_FLOOR="v5.9.1-stable" + # Enable PQC at v5.9.2-stable and later. Older releases stay on the + # no-symbol path. + PQC_FLOOR="v5.9.2-stable" if [ "$(printf '%s\n%s\n' "$PQC_FLOOR" "$LATEST" \ - | sort -V | tail -n1)" != "$PQC_FLOOR" ]; then + | sort -V | head -n1)" = "$PQC_FLOOR" ]; then LATEST_PQC_ELIGIBLE=true else LATEST_PQC_ELIGIBLE=false fi echo "latest-stable PQC eligible: $LATEST_PQC_ELIGIBLE" # Each row carries the build flag (enable) and which PQC test families - # must result (expect: both | mlkem | mldsa | none). This exercises the - # combined, per-algorithm, and opt-in-absent paths. + # must result (expect: both | mlkem | mldsa | lms | none). This + # exercises the combined, per-algorithm, and opt-in-absent paths. MATRIX=$(jq -nc \ --arg latest "$LATEST" \ --argjson latest_pqc "$LATEST_PQC_ELIGIBLE" '{ @@ -91,7 +88,9 @@ jobs: {"name":"master (--enable-mlkem only)", "wolfssl-ref":"master","enable":"--enable-mlkem","expect":"mlkem"}, {"name":"master (--enable-mldsa only)", - "wolfssl-ref":"master","enable":"--enable-mldsa","expect":"mldsa"} + "wolfssl-ref":"master","enable":"--enable-mldsa","expect":"mldsa"}, + {"name":"master (--enable-lms only)", + "wolfssl-ref":"master","enable":"--enable-lms","expect":"lms"} ] }') echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" @@ -121,7 +120,7 @@ jobs: id: deps uses: ./.github/actions/oras-build-deps with: - variant: pqc + variant: ${{ matrix.expect == 'lms' && 'pqc-lms' || 'pqc' }} openssl_ref: ${{ needs.discover-versions.outputs.openssl-tag }} wolfssl_ref: ${{ matrix.wolfssl-ref }} extra_key: ${{ matrix.enable }} @@ -143,24 +142,27 @@ jobs: wolfssl_hit: ${{ steps.deps.outputs.wolfssl_hit }} # Opt-in is per-algorithm: assert exactly the expected PQC test families - # are present (both / mlkem / mldsa / none). This catches a leaked + # are present (both / mlkem / mldsa / lms / none). This catches a leaked # algorithm, a missing one, or PQC dragged in without a flag. - name: Verify PQC test presence matches opt-in (${{ matrix.expect }}) run: | + export LD_LIBRARY_PATH="$(pwd)/wolfssl-install/lib:$(pwd)/openssl-install/lib:$(pwd)/openssl-install/lib64" tests=$(./test/unit.test --list) || exit 1 - kem=0; dsa=0 + kem=0; dsa=0; lms=0 printf '%s\n' "$tests" | grep -q 'test_mlkem_keygen' && kem=1 printf '%s\n' "$tests" | grep -q 'test_mldsa_sign_verify' && dsa=1 - echo "expect=${{ matrix.expect }} mlkem=$kem mldsa=$dsa" + printf '%s\n' "$tests" | grep -q 'test_lms_import_export' && lms=1 + echo "expect=${{ matrix.expect }} mlkem=$kem mldsa=$dsa lms=$lms" case "${{ matrix.expect }}" in - both) [ "$kem" = 1 ] && [ "$dsa" = 1 ] ;; - mlkem) [ "$kem" = 1 ] && [ "$dsa" = 0 ] ;; - mldsa) [ "$kem" = 0 ] && [ "$dsa" = 1 ] ;; - none) [ "$kem" = 0 ] && [ "$dsa" = 0 ] ;; + both) [ "$kem" = 1 ] && [ "$dsa" = 1 ] && [ "$lms" = 0 ] ;; + mlkem) [ "$kem" = 1 ] && [ "$dsa" = 0 ] && [ "$lms" = 0 ] ;; + mldsa) [ "$kem" = 0 ] && [ "$dsa" = 1 ] && [ "$lms" = 0 ] ;; + lms) [ "$kem" = 0 ] && [ "$dsa" = 0 ] && [ "$lms" = 1 ] ;; + none) [ "$kem" = 0 ] && [ "$dsa" = 0 ] && [ "$lms" = 0 ] ;; *) false ;; esac || { echo "ERROR: PQC test families do not match expect=${{ matrix.expect }}"; exit 1; } - # Run the ML-KEM / ML-DSA / hybrid unit tests: keygen, sign/verify, + # Run the ML-KEM / ML-DSA / LMS / hybrid unit tests: keygen, sign/verify, # encap/decap, PEM encoder/decoder round-trip, X.509 signing, and the # hybrid-group KEM. Selected by index from --list so it tracks however # many PQC tests are registered. LD_LIBRARY_PATH carries libwolfprov's @@ -169,7 +171,7 @@ jobs: if: matrix.expect != 'none' run: | export LD_LIBRARY_PATH="$(pwd)/wolfssl-install/lib:$(pwd)/openssl-install/lib:$(pwd)/openssl-install/lib64" - idxs=$(./test/unit.test --list | grep -iE 'mlkem|mldsa|mlx' \ + idxs=$(./test/unit.test --list | grep -iE 'mlkem|mldsa|mlx|lms' \ | grep -oE '^[0-9]+') if [ -z "$idxs" ]; then echo "::error::No PQC unit tests found" diff --git a/README.md b/README.md index ea423d25..61ae7d28 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,27 @@ Without an enable flag no PQC code is compiled, regardless of what wolfSSL enabl * ML-KEM (FIPS 203): ML-KEM-512, ML-KEM-768, ML-KEM-1024 (key encapsulation) * ML-DSA (FIPS 204): ML-DSA-44, ML-DSA-65, ML-DSA-87 (signatures, pure mode with empty context per FIPS 204 sec 5.2) +### LMS (RFC 8554 / NIST SP 800-208) +LMS verification is independently opt-in and requires wolfSSL 5.9.2-stable or +newer and OpenSSL 3.6 or newer: + +```bash +./scripts/build-wolfprovider.sh --enable-lms +``` + +wolfProvider follows OpenSSL's LMS provider contract: it imports and exports +raw XDR public keys and supports one-shot verification through +`EVP_PKEY_verify_message_init()` followed by `EVP_PKEY_verify()`. The OpenSSL +wire format omits the single-level HSS header; wolfProvider adds that wrapper +only at the wolfCrypt boundary. LMS signing, key generation, private-key +import, and streaming operations are not exposed because OpenSSL's LMS +provider is verification-only for SP 800-208 software modules. + +The PQC KAT workflow runs OpenSSL's 320 LMS verification vectors, including +valid and corrupted messages, signatures, and key encodings. Focused unit +tests cover public-key import/export, XDR decoding, selection handling, and +the unsupported stateful operations. + ## Support diff --git a/configure.ac b/configure.ac index 38d00698..1a8d9c1c 100644 --- a/configure.ac +++ b/configure.ac @@ -181,6 +181,14 @@ AC_ARG_ENABLE([mldsa], [ ENABLED_MLDSA=$enableval ], [ ENABLED_MLDSA= ] ) +AC_ARG_ENABLE([lms], + [AS_HELP_STRING([--enable-lms],[Enable LMS verification (default: disabled).])], + [ ENABLED_LMS=$enableval ], + [ ENABLED_LMS=no ] + ) +if test "x$ENABLED_LMS" != "xyes" && test "x$ENABLED_LMS" != "xno"; then + AC_MSG_ERROR([--enable-lms accepts only yes or no]) +fi # --enable-pqc is shorthand for both ML-KEM and ML-DSA, unless one was # explicitly disabled (--enable-pqc --disable-mldsa keeps ML-DSA off). @@ -194,6 +202,9 @@ fi if test "x$ENABLED_MLDSA" = "xyes"; then AM_CFLAGS="$AM_CFLAGS -DWOLFPROV_HAVE_MLDSA" fi +if test "x$ENABLED_LMS" = "xyes"; then + AM_CFLAGS="$AM_CFLAGS -DWOLFPROV_HAVE_LMS" +fi # Set OpenSSL lib directory for installing libdefault.so if test "x$ENABLED_REPLACE_DEFAULT" = "xyes"; then @@ -251,11 +262,13 @@ echo " * Debug silent mode: $ENABLED_DEBUG_SILENT" echo test "x$ENABLED_MLKEM" = "xyes" || ENABLED_MLKEM=no test "x$ENABLED_MLDSA" = "xyes" || ENABLED_MLDSA=no +test "x$ENABLED_LMS" = "xyes" || ENABLED_LMS=no echo " Features " echo " * User settings: $ENABLED_USERSETTINGS" echo " * Dynamic provider: $ENABLED_DYNAMIC_PROVIDER" echo " * Replace default: $ENABLED_REPLACE_DEFAULT" echo " * ML-KEM (FIPS 203): $ENABLED_MLKEM" echo " * ML-DSA (FIPS 204): $ENABLED_MLDSA" +echo " * LMS verification: $ENABLED_LMS" echo "" echo "---" diff --git a/docs/INTEGRATION_GUIDE.md b/docs/INTEGRATION_GUIDE.md index 074bdaf8..f3416cc6 100644 --- a/docs/INTEGRATION_GUIDE.md +++ b/docs/INTEGRATION_GUIDE.md @@ -30,7 +30,8 @@ This retrieves dependencies (OpenSSL and wolfSSL) and compiles them as necessary | `--openssl-dir=/path` | Use existing OpenSSL installation | | `--replace-default` | Make wolfProvider the default provider | | `--enable-replace-default-testing` | Enable unit testing with replace-default | -| `--enable-pqc` | Enable ML-KEM and ML-DSA post-quantum algorithms (adds `--enable-mlkem --enable-mldsa` to wolfSSL). Requires wolfSSL post-v5.9.1-stable. | +| `--enable-pqc` | Enable ML-KEM and ML-DSA (adds `--enable-mlkem --enable-mldsa` to wolfSSL). Requires wolfSSL v5.9.2-stable or later and OpenSSL 3.6 or later. | +| `--enable-lms` | Enable verification-only LMS support. Requires wolfSSL v5.9.2-stable or later and OpenSSL 3.6 or later. | **Examples:** @@ -83,7 +84,8 @@ sudo make install | `--enable-pwdbased` | PKCS#12 support | | `--enable-hmac-copy` | Faster repeated HMAC with same key (wolfSSL 5.7.8+) | | `--enable-sp=yes,asm --enable-sp-math-all` | SP Integer maths | -| `--enable-mlkem --enable-mldsa` | ML-KEM and ML-DSA post-quantum algorithms (wolfSSL post-v5.9.1-stable). The `build-wolfprovider.sh --enable-pqc` flag sets these automatically. | +| `--enable-mlkem --enable-mldsa` | ML-KEM and ML-DSA post-quantum algorithms (wolfSSL v5.9.2-stable or later). The `build-wolfprovider.sh --enable-pqc` flag sets these automatically. | +| `--enable-lms=sha256-192,shake256` | LMS verification with the parameter families used by the OpenSSL vectors. The `build-wolfprovider.sh --enable-lms` flag sets this automatically. | **Optional CPPFLAGS:** @@ -153,7 +155,7 @@ This makes replace default mode useful for testing scenarios where you want to e --- -## Post-Quantum Cryptography (ML-KEM and ML-DSA) +## Post-Quantum Cryptography (ML-KEM, ML-DSA, and LMS) wolfProvider supports NIST's post-quantum algorithms via the wolfSSL backend: @@ -161,13 +163,14 @@ wolfProvider supports NIST's post-quantum algorithms via the wolfSSL backend: |-----------|----------|----------------| | ML-KEM (key encapsulation) | FIPS 203 | ML-KEM-512, ML-KEM-768, ML-KEM-1024 | | ML-DSA (digital signature) | FIPS 204 | ML-DSA-44, ML-DSA-65, ML-DSA-87 | +| LMS (digital signature verification) | RFC 8554 / SP 800-208 | OpenSSL-compatible XDR public keys | ML-DSA uses pure mode with an empty context string (FIPS 204 sec 5.2, Algorithm 22), interoperable with OpenSSL 3.5+'s native ML-DSA. ### Requirements -- **wolfSSL**: post-v5.9.1-stable (i.e. v5.9.2-stable or master). v5.9.1-stable defines `HAVE_DILITHIUM` and exposes `wc_dilithium_sign_ctx_msg` (the older name for the FIPS 204 pure-mode signer) but does not yet ship the canonical `WOLFSSL_HAVE_MLDSA` macro, `` header, or `wc_MlDsaKey_SignCtx` alias that wolfProvider gates on. -- **OpenSSL**: any 3.x. OpenSSL 3.5+ is required only for cross-provider interop against its native ML-KEM/ML-DSA implementations. +- **wolfSSL**: v5.9.2-stable or later. v5.9.1-stable does not provide the complete public interfaces required by wolfProvider. +- **OpenSSL**: 3.6 or later when these features are enabled. ### Building with PQC @@ -177,6 +180,14 @@ ML-DSA uses pure mode with an empty context string (FIPS 204 sec 5.2, Algorithm This adds `--enable-mlkem --enable-mldsa` to the wolfSSL configure step. wolfProvider auto-detects the resulting `WOLFSSL_HAVE_MLKEM` / `WOLFSSL_HAVE_MLDSA` macros via `include/wolfprovider/settings.h` (gated on `__has_include` of `` / ``) and registers the six PQC algorithms. +LMS is enabled separately because it is a stateful signature scheme and is +verification-only in OpenSSL 3.6. Build it with `--enable-lms`; wolfProvider +uses wolfSSL's `wc_LmsKey_ImportPubRaw()` and `wc_LmsKey_Verify()` APIs and +accepts OpenSSL's raw XDR public-key representation. OpenSSL removes the +single-level HSS header from LMS public keys and signatures, so wolfProvider +restores that wrapper only while calling wolfCrypt. The provider does not +generate, import, or sign with LMS private keys. + ### Usage Example ```bash @@ -203,6 +214,13 @@ A standalone three-way interop validator (`test/pqc_interop.test`) cross-checks This proves wolfProvider's raw-key, ciphertext, and signature bytes are FIPS 203 / 204 standards-compliant. The CI workflow `.github/workflows/wolfssl-versions-pqc.yml` runs this validator on every PR, plus a backward-compatibility build against pre-PQC wolfSSL to verify the no-symbol path still builds cleanly. +The `.github/workflows/wolfssl-pqc-kat.yml` workflow runs OpenSSL's unmodified +ML-KEM, ML-DSA, and LMS EVP vectors. LMS contributes 320 verification cases +covering valid and corrupted messages, signatures, and public keys. The matrix +tests combined PQC builds across the latest stable wolfSSL and master in +replace-default, standard-provider, and force-fail configurations, and also +contains an LMS-only build and KAT run. + --- ## Testing diff --git a/include/wolfprovider/alg_funcs.h b/include/wolfprovider/alg_funcs.h index 3d35d115..2f9fc6de 100644 --- a/include/wolfprovider/alg_funcs.h +++ b/include/wolfprovider/alg_funcs.h @@ -185,6 +185,9 @@ typedef void (*DFUNC)(void); #define WP_NAMES_ML_DSA_65 "ML-DSA-65:MLDSA65:2.16.840.1.101.3.4.3.18:id-ml-dsa-65" #define WP_NAMES_ML_DSA_87 "ML-DSA-87:MLDSA87:2.16.840.1.101.3.4.3.19:id-ml-dsa-87" +/* LMS name (NIST SP 800-208 and RFC 8554). */ +#define WP_NAMES_LMS "LMS" + /* DRBG names. */ #define WP_NAMES_SEED_SRC "SEED-SRC" #define WP_NAMES_CTR_DRBG "CTR-DRBG" @@ -406,6 +409,7 @@ extern const OSSL_DISPATCH wp_ed448_signature_functions[]; extern const OSSL_DISPATCH wp_hmac_signature_functions[]; extern const OSSL_DISPATCH wp_cmac_signature_functions[]; extern const OSSL_DISPATCH wp_mldsa_signature_functions[]; +extern const OSSL_DISPATCH wp_lms_signature_functions[]; /* Asymmetric cipher implementations. */ extern const OSSL_DISPATCH wp_rsa_asym_cipher_functions[]; @@ -428,6 +432,8 @@ extern const OSSL_DISPATCH wp_hmac_keymgmt_functions[]; extern const OSSL_DISPATCH wp_cmac_keymgmt_functions[]; extern const OSSL_DISPATCH wp_kdf_keymgmt_functions[]; extern const OSSL_DISPATCH wp_mlkem512_keymgmt_functions[]; +extern const OSSL_DISPATCH wp_lms_keymgmt_functions[]; +extern const OSSL_DISPATCH wp_lms_xdr_decoder_functions[]; extern const OSSL_DISPATCH wp_mlkem768_keymgmt_functions[]; extern const OSSL_DISPATCH wp_mlkem1024_keymgmt_functions[]; extern const OSSL_DISPATCH wp_mlx_x25519_keymgmt_functions[]; @@ -560,4 +566,3 @@ extern const OSSL_DISPATCH wp_mldsa87_epki_pem_encoder_functions[]; extern const OSSL_DISPATCH wp_file_store_functions[]; #endif /* WP_ALG_FUNCS_H */ - diff --git a/include/wolfprovider/settings.h b/include/wolfprovider/settings.h index d5325492..3ab5f21d 100644 --- a/include/wolfprovider/settings.h +++ b/include/wolfprovider/settings.h @@ -243,6 +243,22 @@ #if defined(WOLFPROV_HAVE_MLDSA) && !defined(WP_HAVE_MLDSA) #error "ML-DSA requested but unavailable: needs OpenSSL >= 3.6 and wolfSSL master or v5.9.2-stable+ with ML-DSA." #endif +#if !defined(__has_include) + #define WP_LMS_HEADER +#elif __has_include() + #define WP_LMS_HEADER +#endif +#if defined(WOLFPROV_HAVE_LMS) && defined(HAVE_FIPS) + #error "LMS is unavailable in FIPS builds until CAST integration is added." +#endif +#if defined(WOLFPROV_HAVE_LMS) && defined(WOLFSSL_HAVE_LMS) && \ + defined(WP_LMS_HEADER) && defined(WP_WOLFSSL_PQC_CAPABLE) && \ + (OPENSSL_VERSION_NUMBER >= 0x30600000L) + #define WP_HAVE_LMS +#endif +#if defined(WOLFPROV_HAVE_LMS) && !defined(WP_HAVE_LMS) + #error "LMS requested but unavailable: needs OpenSSL >= 3.6 and wolfSSL master or v5.9.2-stable+ with LMS." +#endif #if !defined(NO_AES_CBC) && (defined(WP_HAVE_HMAC) || defined(WP_HAVE_CMAC)) #define WP_HAVE_KBKDF #endif @@ -251,4 +267,3 @@ #endif #endif /* WOLFPROV_SETTINGS_H */ - diff --git a/scripts/build-wolfprovider.sh b/scripts/build-wolfprovider.sh index 41236bc9..17191554 100755 --- a/scripts/build-wolfprovider.sh +++ b/scripts/build-wolfprovider.sh @@ -35,6 +35,7 @@ show_help() { echo " --enable-pqc Enable both ML-KEM and ML-DSA (requires wolfSSL master/v5.9.2+ and OpenSSL 3.6+)." echo " --enable-mlkem Enable ML-KEM only." echo " --enable-mldsa Enable ML-DSA only." + echo " --enable-lms Enable LMS verification only (requires OpenSSL 3.6+)." echo " --enable-openssl-test Build OpenSSL with its test suite (e.g. evp_test). For CI that runs OpenSSL's own tests." echo "" echo "Environment Variables:" @@ -58,6 +59,7 @@ show_help() { echo " WOLFPROV_PQC If set to 1, enables both ML-KEM and ML-DSA (requires wolfSSL master/v5.9.2+ and OpenSSL 3.6+)" echo " WOLFPROV_MLKEM If set to 1, enables ML-KEM only" echo " WOLFPROV_MLDSA If set to 1, enables ML-DSA only" + echo " WOLFPROV_LMS If set to 1, enables LMS verification only" echo "" } @@ -163,6 +165,9 @@ for arg in "$@"; do --enable-mldsa) WOLFPROV_MLDSA=1 ;; + --enable-lms) + WOLFPROV_LMS=1 + ;; --enable-openssl-test) WOLFPROV_OPENSSL_TEST=1 ;; @@ -221,14 +226,21 @@ if [ "$WOLFPROV_MLKEM" = "1" ] || [ "$WOLFPROV_MLDSA" = "1" ]; then WOLFPROV_PQC=1 fi +if { [ "$WOLFSSL_ISFIPS" = "1" ] || [ -n "$WOLFSSL_FIPS_BUNDLE" ]; } && + { [ "$WOLFPROV_PQC" = "1" ] || [ "$WOLFPROV_LMS" = "1" ]; }; then + echo "ERROR: PQC is not supported by the selected validated FIPS module." + echo " Enable it only after the module-specific CAST integration is available." + exit 1 +fi + # The Debian package path builds against the distribution OpenSSL (bookworm # ships 3.0.x), which has no ML-KEM/ML-DSA and is far below the 3.6 PQC floor, # so a PQC package cannot compile. Reject it up front rather than producing a # broken build. Once Debian ships OpenSSL 3.6+, PQC support here is a small # addition: forward the per-algorithm flags through install-wolfprov.sh and # debian/rules (mirroring the --debug/--fips flags). -if [ -n "$build_debian" ] && [ "$WOLFPROV_PQC" = "1" ]; then - echo "ERROR: PQC (--enable-pqc/--enable-mlkem/--enable-mldsa) is not supported with --debian; the distro OpenSSL is older than the required 3.6+." +if [ -n "$build_debian" ] && { [ "$WOLFPROV_PQC" = "1" ] || [ "$WOLFPROV_LMS" = "1" ]; }; then + echo "ERROR: PQC (--enable-pqc/--enable-mlkem/--enable-mldsa/--enable-lms) is not supported with --debian; the distro OpenSSL is older than the required 3.6+." exit 1 fi @@ -300,7 +312,7 @@ fi # PQC needs newer wolfSSL/OpenSSL than the repo defaults, so when PQC is # requested and the user has not pinned a version, default to PQC-capable ones # (the version gate below still enforces the floors for explicit pins). -if [ "$WOLFPROV_PQC" = "1" ]; then +if [ "$WOLFPROV_PQC" = "1" ] || [ "$WOLFPROV_LMS" = "1" ]; then if [ -z "$WOLFSSL_TAG" ]; then WOLFSSL_TAG=master echo "PQC: defaulting WOLFSSL_TAG=master" @@ -317,22 +329,22 @@ source ${SCRIPT_DIR}/utils-wolfprovider.sh echo "Using openssl: $OPENSSL_TAG, wolfssl: $WOLFSSL_TAG" -# ML-KEM / ML-DSA need the wolfSSL FIPS 203/204 seed and message APIs that land -# after v5.9.1-stable, and the matching OpenSSL provider params that arrive in -# 3.6. Refuse PQC on older releases so the failure is an explicit message, not -# an opaque missing-symbol build error. master and non -stable wolfSSL refs +# ML-KEM / ML-DSA / LMS need wolfSSL APIs and the matching OpenSSL provider +# interfaces that are available with wolfSSL v5.9.2+ (or master) and OpenSSL +# 3.6+. Refuse PQC on older releases so the failure is an explicit message, +# not an opaque missing-symbol build error. Non-stable wolfSSL refs # (branches/commits) are assumed new enough. # 'sort -V' is GNU-only; on a host without it skip the gate (a compile-time # guard in settings.h still rejects too-old versions) rather than misfiring. -if [ "$WOLFPROV_PQC" = "1" ] && ! printf '1\n2\n' | sort -V >/dev/null 2>&1; then +if { [ "$WOLFPROV_PQC" = "1" ] || [ "$WOLFPROV_LMS" = "1" ]; } && ! printf '1\n2\n' | sort -V >/dev/null 2>&1; then echo "WARNING: 'sort -V' unavailable; skipping PQC version check (compile-time guard still applies)." -elif [ "$WOLFPROV_PQC" = "1" ]; then +elif [ "$WOLFPROV_PQC" = "1" ] || [ "$WOLFPROV_LMS" = "1" ]; then PQC_MIN_WOLFSSL="v5.9.2-stable" case "$WOLFSSL_TAG" in v*-stable) if [ "$(printf '%s\n%s\n' "$PQC_MIN_WOLFSSL" "$WOLFSSL_TAG" \ | sort -V | head -n1)" != "$PQC_MIN_WOLFSSL" ]; then - echo "ERROR: ML-KEM/ML-DSA require wolfSSL master or ${PQC_MIN_WOLFSSL} or higher (got ${WOLFSSL_TAG})." + echo "ERROR: PQC algorithms require wolfSSL master or ${PQC_MIN_WOLFSSL} or higher (got ${WOLFSSL_TAG})." exit 1 fi ;; @@ -342,7 +354,7 @@ elif [ "$WOLFPROV_PQC" = "1" ]; then openssl-3.*) if [ "$(printf '%s\n%s\n' "$PQC_MIN_OPENSSL" "$OPENSSL_TAG" \ | sort -V | head -n1)" != "$PQC_MIN_OPENSSL" ]; then - echo "ERROR: ML-KEM/ML-DSA require ${PQC_MIN_OPENSSL} or higher (got ${OPENSSL_TAG})." + echo "ERROR: PQC algorithms require ${PQC_MIN_OPENSSL} or higher (got ${OPENSSL_TAG})." exit 1 fi ;; diff --git a/scripts/test-pqc-kat.sh b/scripts/test-pqc-kat.sh index f0383dd3..514d3f17 100755 --- a/scripts/test-pqc-kat.sh +++ b/scripts/test-pqc-kat.sh @@ -17,27 +17,24 @@ # You should have received a copy of the GNU General Public License # along with wolfProvider. If not, see . -# Run OpenSSL's own ML-KEM/ML-DSA EVP KAT vectors (NIST ACVP + Wycheproof, -# 2602 sub-tests) through wolfProvider using OpenSSL's own evp_test harness. -# This proves wolfcrypt serves the FIPS 203 / FIPS 204 reference vectors -# unmodified via the OpenSSL provider interface. +# Run OpenSSL's ML-KEM, ML-DSA, and LMS EVP KAT vectors through wolfProvider +# using OpenSSL's evp_test harness. # # The script reports a raw result: exit 0 only when every vector file passes -# and all 2602 sub-tests ran. The caller owns force-fail interpretation: under +# and the exact selected sub-test count ran. The caller owns force-fail +# interpretation: under # WOLFPROV_FORCE_FAIL=1 every operation fails, so this exits non-zero, and the # CI job inverts that via check-workflow-result.sh. wolfProvider (replace-default # or not) must already be built by a prior build-wolfprovider.sh step; this -# script does not build it, only runs the KAT against it. +# script does not build it, only runs the KAT against it. WOLFPROV_PQC and +# WOLFPROV_LMS select families; when both are unset, ML-KEM/ML-DSA is the default. SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source ${SCRIPT_DIR}/utils-wolfprovider.sh -# OpenSSL's own KAT data files, run unmodified: the wolfcrypt FIPS 203/204 -# decode fix makes every ML-KEM/ML-DSA vector pass as-is, so nothing is -# staged or edited here. +# OpenSSL's KAT data files are run unmodified. VECTOR_DIR=${OPENSSL_SOURCE_DIR}/test/recipes/30-test_evp_data EVP_TEST=${OPENSSL_TEST}/evp_test -EXPECTED_TESTS=2602 require_evp_test() { if [ -x "${EVP_TEST}" ]; then @@ -47,7 +44,7 @@ require_evp_test() { # OpenSSL's test suite ('no-tests') unless --enable-openssl-test was passed; # non-replace builds include it by default. printf "ERROR: evp_test not found at %s\n" "${EVP_TEST}" - printf " Build with: build-wolfprovider.sh --enable-pqc --enable-openssl-test\n" + printf " Build with --enable-openssl-test and the selected PQC flags\n" return 1 } @@ -64,13 +61,36 @@ run_pqc_kat() { local bad=0 local files=0 local total=0 + local expected=0 local out line n + local vectors="" + + if [ -z "${WOLFPROV_PQC+x}" ] && [ -z "${WOLFPROV_LMS+x}" ]; then + WOLFPROV_PQC=1 + fi + + if [ "${WOLFPROV_PQC:-0}" = "1" ]; then + vectors="${VECTOR_DIR}/evppkey_ml_kem_*.txt ${VECTOR_DIR}/evppkey_ml_dsa_*.txt" + expected=$((expected + 2602)) + fi + if [ "${WOLFPROV_LMS:-0}" = "1" ]; then + if [ ! -f "${VECTOR_DIR}/evppkey_lms_sigver.txt" ]; then + printf "ERROR: LMS vector file is missing: %s\n" \ + "${VECTOR_DIR}/evppkey_lms_sigver.txt" + return 1 + fi + vectors="${vectors} ${VECTOR_DIR}/evppkey_lms_sigver.txt" + expected=$((expected + 320)) + fi + if [ -z "${vectors}" ]; then + printf "ERROR: no PQC KAT family selected; set WOLFPROV_PQC=1 and/or WOLFPROV_LMS=1\n" + return 1 + fi printf "PQC KAT: %d sub-tests expected across all files\n" \ - "${EXPECTED_TESTS}" + "${expected}" - for f in ${VECTOR_DIR}/evppkey_ml_kem_*.txt \ - ${VECTOR_DIR}/evppkey_ml_dsa_*.txt; do + for f in ${vectors}; do files=$((files + 1)) printf "\t%-42s ... " "$(basename ${f})" out=$(${EVP_TEST} -config ${WOLFPROV_CONFIG} "${f}" 2>&1) @@ -82,6 +102,7 @@ run_pqc_kat() { printf "PASS (%s)\n" "${n:-0}" else printf "FAIL\n" + printf "%s\n" "${out}" bad=$((bad + 1)) fi done @@ -91,9 +112,9 @@ run_pqc_kat() { if [ ${bad} -ne 0 ]; then return 1 fi - if [ ${total} -ne ${EXPECTED_TESTS} ]; then + if [ ${total} -ne ${expected} ]; then printf "ERROR: expected %d sub-tests, ran %d\n" \ - "${EXPECTED_TESTS}" "${total}" + "${expected}" "${total}" return 1 fi return 0 @@ -107,7 +128,7 @@ if [ -z "${NUMCPU}" ]; then fi fi -# wolfProvider must already be built (e.g. build-wolfprovider.sh --enable-pqc). +# wolfProvider must already be built with the selected PQC flags. # This script only runs the KAT against that build; it does not rebuild, so it # cannot drop the opt-in PQC flags. WOLFPROV_FORCE_FAIL is honored at runtime. set_lib_env diff --git a/scripts/utils-openssl.sh b/scripts/utils-openssl.sh index 9a36b908..47be083c 100755 --- a/scripts/utils-openssl.sh +++ b/scripts/utils-openssl.sh @@ -400,6 +400,20 @@ check_openssl_fips_baseline_mismatch() { fi } +check_openssl_lms_mismatch() { + local config="${OPENSSL_INSTALL_DIR}/include/openssl/configuration.h" + + if [ "${WOLFPROV_LMS}" = "1" ] && [ -d "${OPENSSL_INSTALL_DIR}" ] && + { [ ! -f "${config}" ] || grep -Eq \ + '^[[:space:]]*#[[:space:]]*define[[:space:]]+OPENSSL_NO_LMS' \ + "${config}"; }; + then + printf "ERROR: existing OpenSSL install was built without LMS.\n" + printf "Fix: ./scripts/build-wolfprovider.sh --distclean\n" + exit 1 + fi +} + install_openssl() { printf "\nInstalling OpenSSL ${OPENSSL_TAG} ...\n" clone_openssl @@ -407,6 +421,7 @@ install_openssl() { check_openssl_replace_default_mismatch check_openssl_fips_baseline_mismatch check_replace_default_testing_mismatch + check_openssl_lms_mismatch pushd ${OPENSSL_SOURCE_DIR} &> /dev/null @@ -418,6 +433,9 @@ install_openssl() { if [ "$WOLFPROV_DEBUG" = "1" ]; then CONFIG_CMD+=" enable-trace --debug" fi + if [ "${WOLFPROV_LMS}" = "1" ]; then + CONFIG_CMD+=" enable-lms" + fi # Replace-default builds skip the OpenSSL test suite for speed, unless # --enable-openssl-test asks for it (e.g. CI that runs evp_test). if [ "$WOLFPROV_REPLACE_DEFAULT" = "1" ] && \ @@ -481,4 +499,3 @@ init_openssl() { export LD_LIBRARY_PATH=${OPENSSL_LIB_DIRS}:$LD_LIBRARY_PATH fi } - diff --git a/scripts/utils-wolfprovider.sh b/scripts/utils-wolfprovider.sh index 92661f00..0987db00 100644 --- a/scripts/utils-wolfprovider.sh +++ b/scripts/utils-wolfprovider.sh @@ -29,7 +29,9 @@ WOLFPROV_WITH_WOLFSSL=--with-wolfssl=${WOLFSSL_INSTALL_DIR} WOLFPROV_WITH_OPENSSL=--with-openssl=${OPENSSL_INSTALL_DIR} # Check if using system wolfSSL installation -if [ ! -d "$WOLFSSL_INSTALL_DIR" ] && command -v dpkg >/dev/null 2>&1; then +if [ "$WOLFPROV_MLKEM" != "1" ] && [ "$WOLFPROV_MLDSA" != "1" ] && + [ "$WOLFPROV_LMS" != "1" ] && [ ! -d "$WOLFSSL_INSTALL_DIR" ] && + command -v dpkg >/dev/null 2>&1; then if dpkg -l | grep -q "^ii.*libwolfssl[[:space:]]" && dpkg -l | grep -q "^ii.*libwolfssl-dev[[:space:]]"; then WOLFPROV_WITH_WOLFSSL= fi @@ -115,6 +117,9 @@ install_wolfprov() { if [ "$WOLFPROV_MLDSA" = "1" ]; then WOLFPROV_CONFIG_OPTS+=" --enable-mldsa" fi + if [ "$WOLFPROV_LMS" = "1" ]; then + WOLFPROV_CONFIG_OPTS+=" --enable-lms" + fi if [ "$WOLFPROV_SEED_SRC" = "1" ]; then WOLFPROV_CONFIG_OPTS+=" --enable-seed-src" @@ -230,4 +235,3 @@ init_wolfprov() { printf "\twolfProvider installed in: ${WOLFPROV_INSTALL_DIR}\n" fi } - diff --git a/scripts/utils-wolfssl.sh b/scripts/utils-wolfssl.sh index 48b5c204..4f7827de 100644 --- a/scripts/utils-wolfssl.sh +++ b/scripts/utils-wolfssl.sh @@ -46,6 +46,7 @@ WOLFSSL_FIPS_CONFIG_OPTS=${WOLFSSL_CONFIG_OPTS:-'--enable-opensslcoexist '} WOLFSSL_FIPS_CONFIG_CFLAGS=${WOLFSSL_CONFIG_CFLAGS:-"-I${OPENSSL_INSTALL_DIR}/include -DWOLFSSL_OLD_OID_SUM -DWOLFSSL_DH_EXTRA"} WOLFSSL_CONFIG_OPTS=${WOLFSSL_CONFIG_OPTS:-'--enable-all-crypto --with-eccminsz=192 --with-max-ecc-bits=1024 --enable-opensslcoexist --enable-sha'} WOLFSSL_CONFIG_CFLAGS=${WOLFSSL_CONFIG_CFLAGS:-"-I${OPENSSL_INSTALL_DIR}/include -DWC_RSA_NO_PADDING -DWOLFSSL_PUBLIC_MP -DHAVE_PUBLIC_FFDHE -DHAVE_FFDHE_6144 -DHAVE_FFDHE_8192 -DWOLFSSL_PSS_LONG_SALT -DWOLFSSL_PSS_SALT_LEN_DISCOVER -DRSA_MIN_SIZE=1024 -DWOLFSSL_OLD_OID_SUM "} +WOLFSSL_PQC_CONFIG_OPTS="" # Add WC_RNG_SEED_CB when SEED-SRC is enabled (allows custom seed callback for fork safety) if [ "$WOLFPROV_SEED_SRC" = "1" ]; then @@ -56,11 +57,15 @@ fi # Enable ML-KEM and ML-DSA in wolfSSL when --enable-pqc is requested. # Use the canonical FIPS 203 / FIPS 204 flag names, per requested algorithm. if [ "$WOLFPROV_MLKEM" = "1" ]; then - WOLFSSL_CONFIG_OPTS="${WOLFSSL_CONFIG_OPTS} --enable-mlkem" + WOLFSSL_PQC_CONFIG_OPTS="${WOLFSSL_PQC_CONFIG_OPTS} --enable-mlkem" fi if [ "$WOLFPROV_MLDSA" = "1" ]; then - WOLFSSL_CONFIG_OPTS="${WOLFSSL_CONFIG_OPTS} --enable-mldsa" + WOLFSSL_PQC_CONFIG_OPTS="${WOLFSSL_PQC_CONFIG_OPTS} --enable-mldsa" fi +if [ "$WOLFPROV_LMS" = "1" ]; then + WOLFSSL_PQC_CONFIG_OPTS="${WOLFSSL_PQC_CONFIG_OPTS} --enable-lms=sha256-192,shake256" +fi +WOLFSSL_CONFIG_OPTS="${WOLFSSL_CONFIG_OPTS}${WOLFSSL_PQC_CONFIG_OPTS}" WOLFSSL_DEBUG_ASN_TEMPLATE=${DWOLFSSL_DEBUG_ASN_TEMPLATE:-0} WOLFPROV_DISABLE_ERR_TRACE=${WOLFPROV_DISABLE_ERR_TRACE:-0} @@ -124,11 +129,39 @@ clone_wolfssl() { fi } +check_wolfssl_feature() { + local requested="$1" + local macro="$2" + local name="$3" + local options="${WOLFSSL_INSTALL_DIR}/include/wolfssl/options.h" + + if [ "${requested}" = "1" ] && [ -d "${WOLFSSL_INSTALL_DIR}" ] && + ! grep -Eq "^[[:space:]]*#define[[:space:]]+${macro}([[:space:]]|$)" \ + "${options}" 2>/dev/null; then + printf "ERROR: existing wolfSSL install was built without %s.\n" \ + "${name}" + printf "Fix: ./scripts/build-wolfprovider.sh --distclean\n" + exit 1 + fi +} + +check_wolfssl_feature_mismatch() { + check_wolfssl_feature "${WOLFPROV_MLKEM}" WOLFSSL_HAVE_MLKEM ML-KEM + check_wolfssl_feature "${WOLFPROV_MLDSA}" WOLFSSL_HAVE_MLDSA ML-DSA + check_wolfssl_feature "${WOLFPROV_LMS}" WOLFSSL_HAVE_LMS LMS + check_wolfssl_feature "${WOLFPROV_LMS}" WOLFSSL_LMS_SHA256_192 \ + "LMS SHA-256/192 parameter sets" + check_wolfssl_feature "${WOLFPROV_LMS}" WOLFSSL_LMS_SHAKE256 \ + "LMS SHAKE256 parameter sets" +} + install_wolfssl() { # Check if libwolfssl and libwolfssl-dev packages are already installed # This is allowed only for wolfSSL, but not for OpenSSL because we want to # use the custom OpenSSL built with wolfProvider. - if command -v dpkg >/dev/null 2>&1; then + if [ "$WOLFPROV_MLKEM" != "1" ] && [ "$WOLFPROV_MLDSA" != "1" ] && + [ "$WOLFPROV_LMS" != "1" ] && + command -v dpkg >/dev/null 2>&1; then if dpkg -l | grep -q "^ii.*libwolfssl[[:space:]]" && dpkg -l | grep -q "^ii.*libwolfssl-dev[[:space:]]"; then # Check if there is a FIPS mismatch # If the system wolfSSL is FIPS, we need to be doing a FIPS build @@ -153,6 +186,7 @@ install_wolfssl() { printf "\nInstalling wolfSSL ${WOLFSSL_TAG} ...\n" clone_wolfssl + check_wolfssl_feature_mismatch cd ${WOLFSSL_SOURCE_DIR} if [ ! -d ${WOLFSSL_INSTALL_DIR} ]; then @@ -371,4 +405,3 @@ init_wolfssl() { export LD_LIBRARY_PATH="$WOLFSSL_INSTALL_DIR/lib:$LD_LIBRARY_PATH" fi } - diff --git a/src/include.am b/src/include.am index 156e11fc..d36d3705 100644 --- a/src/include.am +++ b/src/include.am @@ -42,6 +42,7 @@ libwolfprov_la_SOURCES += src/wp_mlx_kmgmt.c libwolfprov_la_SOURCES += src/wp_mlx_kem.c libwolfprov_la_SOURCES += src/wp_mldsa_kmgmt.c libwolfprov_la_SOURCES += src/wp_mldsa_sig.c +libwolfprov_la_SOURCES += src/wp_lms.c libwolfprov_la_SOURCES += src/wp_drbg.c libwolfprov_la_SOURCES += src/wp_seed_src.c libwolfprov_la_SOURCES += src/wp_dec_pem2der.c @@ -51,4 +52,3 @@ libwolfprov_la_SOURCES += src/wp_internal.c libwolfprov_la_SOURCES += src/wp_params.c libwolfprov_la_SOURCES += src/wp_logging.c libwolfprov_la_SOURCES += src/wp_fips.c - diff --git a/src/wp_lms.c b/src/wp_lms.c new file mode 100644 index 00000000..c1f074ac --- /dev/null +++ b/src/wp_lms.c @@ -0,0 +1,530 @@ +/* wp_lms.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfProvider. + * + * wolfProvider is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfProvider is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfProvider. If not, see . + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef WP_HAVE_LMS + +#include + +#define WP_LMS_LEVELS_SZ 4 +#define WP_LMS_TYPE_SZ 4 +#define WP_LMS_XDR_MIN_SZ 48 +#define WP_LMS_XDR_MAX_SZ 56 + +typedef struct wp_Lms { + LmsKey key; + unsigned char pub[WP_LMS_XDR_MAX_SZ]; + size_t pubLen; + WOLFPROV_CTX* provCtx; +#ifndef WP_SINGLE_THREADED + wolfSSL_Mutex mutex; +#endif + int refCnt; +} wp_Lms; + +typedef struct wp_LmsSigCtx { + WOLFPROV_CTX* provCtx; + wp_Lms* lms; +} wp_LmsSigCtx; + +static wp_Lms* wp_lms_new(WOLFPROV_CTX* provCtx) +{ + wp_Lms* lms = NULL; + + if (wolfssl_prov_is_running()) { + lms = OPENSSL_zalloc(sizeof(*lms)); + } + if (lms != NULL) { + if (wc_LmsKey_Init(&lms->key, NULL, INVALID_DEVID) != 0) { + OPENSSL_free(lms); + lms = NULL; + } + else { +#ifndef WP_SINGLE_THREADED + if (wc_InitMutex(&lms->mutex) != 0) { + wc_LmsKey_Free(&lms->key); + OPENSSL_free(lms); + lms = NULL; + } +#endif + } + if (lms != NULL) { + lms->provCtx = provCtx; + lms->refCnt = 1; + } + } + + return lms; +} + +static void wp_lms_free(wp_Lms* lms) +{ + if (lms != NULL) { + int cnt; +#ifndef WP_SINGLE_THREADED + if (wc_LockMutex(&lms->mutex) != 0) { + return; + } + cnt = --lms->refCnt; + wc_UnLockMutex(&lms->mutex); +#else + cnt = --lms->refCnt; +#endif + if (cnt == 0) { +#ifndef WP_SINGLE_THREADED + wc_FreeMutex(&lms->mutex); +#endif + wc_LmsKey_Free(&lms->key); + OPENSSL_clear_free(lms, sizeof(*lms)); + } + } +} + +static int wp_lms_up_ref(wp_Lms* lms) +{ + if (lms == NULL) { + return 0; + } +#ifndef WP_SINGLE_THREADED + if (wc_LockMutex(&lms->mutex) != 0) { + return 0; + } +#endif + lms->refCnt++; +#ifndef WP_SINGLE_THREADED + wc_UnLockMutex(&lms->mutex); +#endif + return 1; +} + +static int wp_lms_has(const wp_Lms* lms, int selection) +{ + int ok = wolfssl_prov_is_running() && (lms != NULL); + + if (ok && ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0) && + ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) == 0)) { + ok = 0; + } + if (ok && ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0)) { + ok = lms->pubLen != 0; + } + + return ok; +} + +static int wp_lms_match(const wp_Lms* lms1, const wp_Lms* lms2, + int selection) +{ + int ok = wolfssl_prov_is_running() && (lms1 != NULL) && (lms2 != NULL); + + if (ok && ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0) && + ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) == 0)) { + ok = 0; + } + if (ok && ((lms1->pubLen == 0) || (lms2->pubLen == 0))) { + ok = 0; + } + if (ok) { + ok = (lms1->pubLen >= (2 * WP_LMS_TYPE_SZ)) && + (lms2->pubLen >= (2 * WP_LMS_TYPE_SZ)) && + (XMEMCMP(lms1->pub, lms2->pub, 2 * WP_LMS_TYPE_SZ) == 0); + } + if (ok && ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0)) { + ok = (lms1->pubLen != 0) && (lms1->pubLen == lms2->pubLen) && + (XMEMCMP(lms1->pub, lms2->pub, lms1->pubLen) == 0); + } + + return ok; +} + +static int wp_lms_validate(const wp_Lms* lms, int selection, int checkType) +{ + (void)checkType; + + return wp_lms_has(lms, selection); +} + +static int wp_lms_import(wp_Lms* lms, int selection, + const OSSL_PARAM params[]) +{ + int ok = wolfssl_prov_is_running() && (lms != NULL) && (params != NULL); + const OSSL_PARAM* p = NULL; + const void* pub = NULL; + size_t pubLen = 0; + unsigned char raw[WP_LMS_LEVELS_SZ + WP_LMS_XDR_MAX_SZ]; + + if (ok && ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) == 0)) { + ok = 0; + } + if (ok) { + p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PUB_KEY); + ok = (p != NULL) && + OSSL_PARAM_get_octet_string_ptr(p, &pub, &pubLen) && + (pub != NULL); + } + if (ok && ((pubLen < WP_LMS_XDR_MIN_SZ) || + (pubLen > WP_LMS_XDR_MAX_SZ))) { + ok = 0; + } + if (ok) { + raw[0] = 0; + raw[1] = 0; + raw[2] = 0; + raw[3] = 1; + XMEMCPY(raw + WP_LMS_LEVELS_SZ, pub, pubLen); + ok = wc_LmsKey_ImportPubRaw(&lms->key, raw, + (word32)(pubLen + WP_LMS_LEVELS_SZ)) == 0; + } + if (ok) { + XMEMCPY(lms->pub, pub, pubLen); + lms->pubLen = pubLen; + } + + return ok; +} + +static const OSSL_PARAM wp_lms_imexport_types[] = { + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0), + OSSL_PARAM_END +}; + +static const OSSL_PARAM* wp_lms_import_types(int selection) +{ + return (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0 ? + wp_lms_imexport_types : NULL; +} + +static const OSSL_PARAM* wp_lms_export_types(int selection) +{ + return wp_lms_import_types(selection); +} + +static int wp_lms_export(wp_Lms* lms, int selection, OSSL_CALLBACK* paramCb, + void* cbArg) +{ + int ok = wolfssl_prov_is_running() && (lms != NULL) && + (paramCb != NULL) && + ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0) && + (lms->pubLen != 0); + OSSL_PARAM params[2]; + + if (ok) { + params[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, + lms->pub, lms->pubLen); + params[1] = OSSL_PARAM_construct_end(); + ok = paramCb(params, cbArg); + } + + return ok; +} + +static wp_Lms* wp_lms_load(const void* reference, size_t referenceSz) +{ + wp_Lms* lms = NULL; + + if (wolfssl_prov_is_running() && (reference != NULL) && + (referenceSz == sizeof(lms))) { + lms = *(wp_Lms**)reference; + *(wp_Lms**)reference = NULL; + } + + return lms; +} + +static const char* wp_lms_query_operation_name(int operationId) +{ + return operationId == OSSL_OP_SIGNATURE ? "LMS" : NULL; +} + +const OSSL_DISPATCH wp_lms_keymgmt_functions[] = { + { OSSL_FUNC_KEYMGMT_NEW, (DFUNC)wp_lms_new }, + { OSSL_FUNC_KEYMGMT_FREE, (DFUNC)wp_lms_free }, + { OSSL_FUNC_KEYMGMT_HAS, (DFUNC)wp_lms_has }, + { OSSL_FUNC_KEYMGMT_MATCH, (DFUNC)wp_lms_match }, + { OSSL_FUNC_KEYMGMT_VALIDATE, (DFUNC)wp_lms_validate }, + { OSSL_FUNC_KEYMGMT_IMPORT, (DFUNC)wp_lms_import }, + { OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (DFUNC)wp_lms_import_types }, + { OSSL_FUNC_KEYMGMT_EXPORT, (DFUNC)wp_lms_export }, + { OSSL_FUNC_KEYMGMT_EXPORT_TYPES, (DFUNC)wp_lms_export_types }, + { OSSL_FUNC_KEYMGMT_LOAD, (DFUNC)wp_lms_load }, + { OSSL_FUNC_KEYMGMT_QUERY_OPERATION_NAME, + (DFUNC)wp_lms_query_operation_name }, + { 0, NULL } +}; + +static wp_LmsSigCtx* wp_lms_sig_newctx(WOLFPROV_CTX* provCtx, + const char* propq) +{ + wp_LmsSigCtx* ctx = NULL; + + (void)propq; + + if (wolfssl_prov_is_running()) { + ctx = OPENSSL_zalloc(sizeof(*ctx)); + } + if (ctx != NULL) { + ctx->provCtx = provCtx; + } + + return ctx; +} + +static void wp_lms_sig_freectx(wp_LmsSigCtx* ctx) +{ + if (ctx != NULL) { + wp_lms_free(ctx->lms); + OPENSSL_free(ctx); + } +} + +static int wp_lms_verify_message_init(wp_LmsSigCtx* ctx, wp_Lms* lms, + const OSSL_PARAM params[]) +{ + int ok = wolfssl_prov_is_running() && (ctx != NULL); + + (void)params; + + if (ok && (lms == NULL) && (ctx->lms == NULL)) { + ok = 0; + } + if (ok && (lms != NULL)) { + if (!wp_lms_up_ref(lms)) { + return 0; + } + wp_lms_free(ctx->lms); + ctx->lms = lms; + } + + return ok; +} + +static int wp_lms_verify(wp_LmsSigCtx* ctx, const unsigned char* sig, + size_t sigLen, const unsigned char* msg, size_t msgLen) +{ + int ok = wolfssl_prov_is_running() && (ctx != NULL) && + (ctx->lms != NULL) && (sig != NULL) && + ((msg != NULL) || (msgLen == 0)); + unsigned char* rawSig = NULL; + unsigned char empty = 0; + word32 expectedRawSigLen = 0; + size_t rawSigLen = 0; + + if (ok && ((wc_LmsKey_GetSigLen(&ctx->lms->key, + &expectedRawSigLen) != 0) || + (expectedRawSigLen < WP_LMS_LEVELS_SZ) || + (sigLen != (size_t)expectedRawSigLen - WP_LMS_LEVELS_SZ) || + (msgLen > INT_MAX))) { + ok = 0; + } + if (ok) { + rawSigLen = expectedRawSigLen; + rawSig = OPENSSL_malloc(rawSigLen); + ok = rawSig != NULL; + } + if (ok) { + XMEMSET(rawSig, 0, WP_LMS_LEVELS_SZ); + XMEMCPY(rawSig + WP_LMS_LEVELS_SZ, sig, sigLen); + if (msg == NULL) { + msg = ∅ + } + ok = wc_LmsKey_Verify(&ctx->lms->key, rawSig, + (word32)rawSigLen, msg, (int)msgLen) == 0; + } + + OPENSSL_clear_free(rawSig, rawSigLen); + return ok; +} + +const OSSL_DISPATCH wp_lms_signature_functions[] = { + { OSSL_FUNC_SIGNATURE_NEWCTX, (DFUNC)wp_lms_sig_newctx }, + { OSSL_FUNC_SIGNATURE_FREECTX, (DFUNC)wp_lms_sig_freectx }, + { OSSL_FUNC_SIGNATURE_VERIFY_MESSAGE_INIT, + (DFUNC)wp_lms_verify_message_init }, + { OSSL_FUNC_SIGNATURE_VERIFY, (DFUNC)wp_lms_verify }, + { 0, NULL } +}; + +typedef struct wp_LmsDecCtx { + WOLFPROV_CTX* provCtx; + int selection; +} wp_LmsDecCtx; + +static wp_LmsDecCtx* wp_lms_dec_new(WOLFPROV_CTX* provCtx) +{ + wp_LmsDecCtx* ctx = NULL; + + if (wolfssl_prov_is_running()) { + ctx = OPENSSL_zalloc(sizeof(*ctx)); + } + if (ctx != NULL) { + ctx->provCtx = provCtx; + } + return ctx; +} + +static void wp_lms_dec_free(wp_LmsDecCtx* ctx) +{ + OPENSSL_free(ctx); +} + +static int wp_lms_dec_selection(const wp_LmsDecCtx* ctx, int selection) +{ + (void)ctx; + return selection == 0 || + (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0; +} + +static int wp_lms_dec_export(wp_LmsDecCtx* ctx, const void* reference, + size_t referenceSz, OSSL_CALLBACK* exportCb, void* exportCbArg) +{ + wp_Lms* lms = NULL; + int selection; + + if ((ctx == NULL) || (reference == NULL) || + (referenceSz != sizeof(lms)) || (exportCb == NULL)) { + return 0; + } + lms = *(wp_Lms* const*)reference; + selection = ctx->selection; + if (selection == 0) { + selection = OSSL_KEYMGMT_SELECT_PUBLIC_KEY; + } + return wp_lms_export(lms, selection, exportCb, exportCbArg); +} + +static size_t wp_lms_xdr_pub_len(const unsigned char* header) +{ + word32 lmsType = ((word32)header[0] << 24) | + ((word32)header[1] << 16) | ((word32)header[2] << 8) | header[3]; + size_t len = 0; + + if (((lmsType >= 5) && (lmsType <= 9)) || + ((lmsType >= 15) && (lmsType <= 19))) { + len = WP_LMS_XDR_MAX_SZ; + } + else if (((lmsType >= 10) && (lmsType <= 14)) || + ((lmsType >= 20) && (lmsType <= 24))) { + len = WP_LMS_XDR_MIN_SZ; + } + return len; +} + +static int wp_lms_bio_read(BIO* bio, unsigned char* data, size_t len) +{ + size_t offset = 0; + + while (offset < len) { + int readLen = BIO_read(bio, data + offset, (int)(len - offset)); + + if (readLen <= 0) { + return 0; + } + offset += (size_t)readLen; + } + return 1; +} + +static int wp_lms_dec_decode(wp_LmsDecCtx* ctx, OSSL_CORE_BIO* cBio, + int selection, OSSL_CALLBACK* dataCb, void* dataCbArg, + OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg) +{ + BIO* bio = NULL; + wp_Lms* lms = NULL; + unsigned char pub[WP_LMS_XDR_MAX_SZ]; + size_t pubLen = 0; + int ok = 0; + OSSL_PARAM params[4]; + int objectType = OSSL_OBJECT_PKEY; + + (void)selection; + (void)pwCb; + (void)pwCbArg; + if (!wolfssl_prov_is_running() || (ctx == NULL) || + (dataCb == NULL) || !wp_lms_dec_selection(ctx, selection)) { + goto done; + } + bio = wp_corebio_get_bio(ctx->provCtx, cBio); + if (bio == NULL) { + goto done; + } + ctx->selection = selection; + if (!wp_lms_bio_read(bio, pub, WP_LMS_LEVELS_SZ)) { + ok = 1; + goto done; + } + pubLen = wp_lms_xdr_pub_len(pub); + if (pubLen == 0) { + ok = 1; + goto done; + } + if (!wp_lms_bio_read(bio, pub + WP_LMS_LEVELS_SZ, + pubLen - WP_LMS_LEVELS_SZ)) { + ok = 1; + goto done; + } + lms = wp_lms_new(ctx->provCtx); + if (lms == NULL) { + goto done; + } + if (!wp_lms_import(lms, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, + (OSSL_PARAM[]){ OSSL_PARAM_construct_octet_string( + OSSL_PKEY_PARAM_PUB_KEY, pub, pubLen), + OSSL_PARAM_construct_end() })) { + wp_lms_free(lms); + lms = NULL; + ok = 1; + goto done; + } + params[0] = OSSL_PARAM_construct_int(OSSL_OBJECT_PARAM_TYPE, + &objectType); + params[1] = OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA_TYPE, + (char*)WP_NAMES_LMS, 0); + params[2] = OSSL_PARAM_construct_octet_string(OSSL_OBJECT_PARAM_REFERENCE, + &lms, sizeof(lms)); + params[3] = OSSL_PARAM_construct_end(); + ok = dataCb(params, dataCbArg); + wp_lms_free(lms); + +done: + BIO_free(bio); + return ok; +} + +const OSSL_DISPATCH wp_lms_xdr_decoder_functions[] = { + { OSSL_FUNC_DECODER_NEWCTX, (DFUNC)wp_lms_dec_new }, + { OSSL_FUNC_DECODER_FREECTX, (DFUNC)wp_lms_dec_free }, + { OSSL_FUNC_DECODER_DOES_SELECTION, (DFUNC)wp_lms_dec_selection }, + { OSSL_FUNC_DECODER_DECODE, (DFUNC)wp_lms_dec_decode }, + { OSSL_FUNC_DECODER_EXPORT_OBJECT, (DFUNC)wp_lms_dec_export }, + { 0, NULL } +}; + +#endif /* WP_HAVE_LMS */ diff --git a/src/wp_wolfprov.c b/src/wp_wolfprov.c index f09c5607..bd0f392d 100644 --- a/src/wp_wolfprov.c +++ b/src/wp_wolfprov.c @@ -701,6 +701,9 @@ static const OSSL_ALGORITHM wolfprov_keymgmt[] = { { WP_NAMES_ML_DSA_87, WOLFPROV_PROPERTIES, wp_mldsa87_keymgmt_functions, "" }, #endif +#ifdef WP_HAVE_LMS + { WP_NAMES_LMS, WOLFPROV_PROPERTIES, wp_lms_keymgmt_functions, "" }, +#endif { NULL, NULL, NULL, NULL } }; @@ -769,6 +772,9 @@ static const OSSL_ALGORITHM wolfprov_signature[] = { { WP_NAMES_ML_DSA_87, WOLFPROV_PROPERTIES, wp_mldsa_signature_functions, "" }, #endif +#ifdef WP_HAVE_LMS + { WP_NAMES_LMS, WOLFPROV_PROPERTIES, wp_lms_signature_functions, "" }, +#endif { NULL, NULL, NULL, NULL } }; @@ -1244,6 +1250,10 @@ static const OSSL_ALGORITHM wolfprov_decoder[] = { wp_mldsa87_pki_decoder_functions, "" }, #endif +#ifdef WP_HAVE_LMS + { WP_NAMES_LMS, WOLFPROV_PROPERTIES ",input=xdr,structure=lms", + wp_lms_xdr_decoder_functions, "" }, +#endif /* Dummy decoder added to match PKI bit not match EPKI from context. * Flag set to say context type checked even though it didn't match and diff --git a/test/include.am b/test/include.am index 8a0e9528..c0a72a2e 100644 --- a/test/include.am +++ b/test/include.am @@ -28,6 +28,7 @@ test_unit_test_SOURCES = \ test/test_hmac.c \ test/test_kbkdf.c \ test/test_logging.c \ + test/test_lms.c \ test/test_pbe.c \ test/test_pbkdf2.c \ test/test_pkey.c \ diff --git a/test/test_lms.c b/test/test_lms.c new file mode 100644 index 00000000..a32008ec --- /dev/null +++ b/test/test_lms.c @@ -0,0 +1,380 @@ +/* test_lms.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfProvider. + * + * wolfProvider is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfProvider is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfProvider. If not, see . + */ + +#include "unit.h" + +#include +#include + +#ifdef WP_HAVE_LMS + +/* OpenSSL LMS XDR public keys exclude the single-level HSS header. */ +static const unsigned char lmsPub1[] = { + 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x08, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x2c, 0x57, 0x14, 0x50, 0xae, 0xd9, 0x9c, 0xfb, + 0x4f, 0x4a, 0xc2, 0x85, 0xda, 0x14, 0x88, 0x27, + 0x96, 0x61, 0x83, 0x14, 0x50, 0x8b, 0x12, 0xd2 +}; + +static const unsigned char lmsPub2[] = { + 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x10, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0xdb, 0x54, 0xa4, 0x50, 0x99, 0x01, 0x05, 0x1c, + 0x01, 0xe2, 0x6d, 0x99, 0x90, 0xe5, 0x50, 0x34, + 0x79, 0x86, 0xda, 0x87, 0x92, 0x4f, 0xf0, 0xb1 +}; + +static const unsigned char lmsPub3[] = { + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, + 0xc3, 0x4b, 0xae, 0x13, 0x90, 0xdd, 0xdb, 0x18, + 0x2e, 0x0e, 0xd8, 0x97, 0x27, 0xcb, 0x17, 0xe6, + 0x50, 0xdb, 0x2d, 0xad, 0x1f, 0xd2, 0xfa, 0x75, + 0x19, 0x2b, 0x92, 0x3c, 0x5b, 0x6a, 0xf9, 0xa2, + 0x70, 0xc8, 0x46, 0xd9, 0xfa, 0xfb, 0x22, 0xb0, + 0x45, 0x1f, 0x2c, 0x28, 0xd4, 0x8a, 0x29, 0x67 +}; + +static int lms_from_data(const unsigned char* pub, size_t pubLen, + int selection, EVP_PKEY** pkey) +{ + int err = 0; + EVP_PKEY_CTX* ctx = NULL; + OSSL_PARAM params[2]; + + params[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, + (unsigned char*)pub, pubLen); + params[1] = OSSL_PARAM_construct_end(); + ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "LMS", NULL); + err = ctx == NULL; + if (err == 0) { + err = EVP_PKEY_fromdata_init(ctx) != 1; + } + if (err == 0) { + err = EVP_PKEY_fromdata(ctx, pkey, selection, params) != 1; + } + EVP_PKEY_CTX_free(ctx); + return err; +} + +static int lms_decode_data(const unsigned char* data, size_t dataLen, + int selection, EVP_PKEY** pkey, size_t* remaining) +{ + int err = 0; + OSSL_DECODER_CTX* ctx = NULL; + const unsigned char* p = data; + + ctx = OSSL_DECODER_CTX_new_for_pkey(pkey, "XDR", NULL, "LMS", + selection, wpLibCtx, NULL); + err = ctx == NULL; + if (err == 0) { + err = OSSL_DECODER_from_data(ctx, &p, &dataLen) != 1; + } + if (remaining != NULL) { + *remaining = dataLen; + } + OSSL_DECODER_CTX_free(ctx); + return err; +} + +int test_lms_import_export(void* data) +{ + int err = 0; + EVP_PKEY* key1 = NULL; + EVP_PKEY* key1Copy = NULL; + EVP_PKEY* key2 = NULL; + EVP_PKEY* badKey = NULL; + EVP_PKEY* rawKey = NULL; + EVP_PKEY_CTX* checkCtx = NULL; + OSSL_PARAM* exportedParams = NULL; + const OSSL_PARAM* p = NULL; + const void* exported = NULL; + unsigned char badPub[sizeof(lmsPub1)]; + unsigned char badPubLen[sizeof(lmsPub3)] = { 0 }; + size_t exportedLen = 0; + + (void)data; + XMEMCPY(badPub, lmsPub1, sizeof(badPub)); + badPub[3] = 0xaa; + XMEMCPY(badPubLen, lmsPub1, sizeof(lmsPub1)); + + err = lms_from_data(lmsPub1, sizeof(lmsPub1), EVP_PKEY_PUBLIC_KEY, + &key1); + if (err != 0) { + PRINT_ERR_MSG("LMS public key import failed"); + } + if (err == 0) { + err = lms_from_data(lmsPub1, sizeof(lmsPub1), EVP_PKEY_PUBLIC_KEY, + &key1Copy); + if (err != 0) { + PRINT_ERR_MSG("LMS duplicate public key import failed"); + } + } + if (err == 0) { + err = lms_from_data(lmsPub2, sizeof(lmsPub2), EVP_PKEY_PUBLIC_KEY, + &key2); + if (err != 0) { + PRINT_ERR_MSG("LMS alternate public key import failed"); + } + } + if (err == 0) { + err = EVP_PKEY_todata(key1, EVP_PKEY_PUBLIC_KEY, + &exportedParams) != 1; + if (err == 0) { + p = OSSL_PARAM_locate_const(exportedParams, + OSSL_PKEY_PARAM_PUB_KEY); + err = (p == NULL) || !OSSL_PARAM_get_octet_string_ptr(p, + &exported, &exportedLen); + } + if (err != 0) { + PRINT_ERR_MSG("LMS public key export failed"); + } + } + if (err == 0) { + err = (exportedLen != sizeof(lmsPub1)) || (exported == NULL) || + (XMEMCMP(exported, lmsPub1, sizeof(lmsPub1)) != 0); + if (err != 0) { + PRINT_ERR_MSG("LMS public key export did not round trip"); + } + } + if (err == 0) { + err = (EVP_PKEY_eq(key1, key1Copy) != 1) || + (EVP_PKEY_eq(key1, key2) == 1); + if (err != 0) { + PRINT_ERR_MSG("LMS public key equality check failed"); + } + } + if (err == 0) { + err = (EVP_PKEY_parameters_eq(key1, key1Copy) != 1) || + (EVP_PKEY_parameters_eq(key1, key2) == 1); + if (err != 0) { + PRINT_ERR_MSG("LMS parameter equality check failed"); + } + } + if (err == 0) { + checkCtx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, key1, NULL); + err = (checkCtx == NULL) || (EVP_PKEY_check(checkCtx) != 1) || + (EVP_PKEY_public_check(checkCtx) != 1) || + (EVP_PKEY_private_check(checkCtx) == 1); + if (err != 0) { + PRINT_ERR_MSG("LMS public/private selection check failed"); + } + } + if (err == 0) { + err = lms_from_data(lmsPub1, sizeof(lmsPub1) - 1, + EVP_PKEY_PUBLIC_KEY, &badKey) == 0; + if (err != 0) { + PRINT_ERR_MSG("LMS short public key was accepted"); + } + } + if (err == 0) { + err = lms_from_data(NULL, sizeof(lmsPub1), EVP_PKEY_PUBLIC_KEY, + &badKey) == 0; + if (err != 0) { + PRINT_ERR_MSG("LMS NULL public key was accepted"); + } + } + if (err == 0) { + err = lms_from_data(badPubLen, sizeof(badPubLen), + EVP_PKEY_PUBLIC_KEY, &badKey) == 0; + if (err != 0) { + PRINT_ERR_MSG("LMS public key with a mismatched length was accepted"); + } + } + if (err == 0) { + err = lms_from_data(badPub, sizeof(badPub), EVP_PKEY_PUBLIC_KEY, + &badKey) == 0; + if (err != 0) { + PRINT_ERR_MSG("LMS public key with an unknown type was accepted"); + } + } + if (err == 0) { + rawKey = EVP_PKEY_new_raw_public_key_ex(wpLibCtx, "LMS", NULL, + lmsPub1, sizeof(lmsPub1)); + err = rawKey == NULL; + if (err != 0) { + PRINT_ERR_MSG("OpenSSL raw LMS public key import failed"); + } + } + if (err == 0) { + EVP_PKEY_CTX_free(checkCtx); + checkCtx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, rawKey, NULL); + err = (checkCtx == NULL) || (EVP_PKEY_private_check(checkCtx) == 1); + if (err != 0) { + PRINT_ERR_MSG("Raw LMS public key reported a private component"); + } + } + if (err != 0) { + PRINT_ERR_MSG("LMS public key import/export validation failed"); + } + ERR_clear_error(); + OSSL_PARAM_free(exportedParams); + EVP_PKEY_CTX_free(checkCtx); + EVP_PKEY_free(rawKey); + EVP_PKEY_free(badKey); + EVP_PKEY_free(key2); + EVP_PKEY_free(key1Copy); + EVP_PKEY_free(key1); + return err; +} + +int test_lms_decode(void* data) +{ + int err = 0; + EVP_PKEY* key = NULL; + EVP_PKEY_CTX* checkCtx = NULL; + unsigned char bad[sizeof(lmsPub1) + 1]; + size_t remaining = 0; + + (void)data; + XMEMCPY(bad, lmsPub1, sizeof(lmsPub1)); + bad[sizeof(lmsPub1)] = 0; + + err = lms_decode_data(lmsPub1, sizeof(lmsPub1), EVP_PKEY_PUBLIC_KEY, + &key, NULL); + if (err != 0) { + PRINT_ERR_MSG("LMS XDR public key decode failed"); + } + EVP_PKEY_free(key); + key = NULL; + if (err == 0) { + err = lms_decode_data(lmsPub3, sizeof(lmsPub3), + EVP_PKEY_PUBLIC_KEY, &key, NULL); + if (err != 0) { + PRINT_ERR_MSG("LMS 32-byte XDR public key decode failed"); + } + } + EVP_PKEY_free(key); + key = NULL; + if (err == 0) { + err = lms_decode_data(lmsPub1, sizeof(lmsPub1) - 1, + EVP_PKEY_PUBLIC_KEY, &key, NULL) == 0; + if (err != 0) { + PRINT_ERR_MSG("LMS short XDR public key was accepted"); + } + } + if (err == 0) { + err = lms_decode_data(bad, sizeof(bad), EVP_PKEY_PUBLIC_KEY, + &key, &remaining); + if (err != 0) { + PRINT_ERR_MSG("LMS XDR public key with trailing data failed"); + } + } + if (err == 0) { + err = remaining != 1; + if (err != 0) { + PRINT_ERR_MSG("LMS XDR decoder consumed trailing data"); + } + } + EVP_PKEY_free(key); + key = NULL; + if (err == 0) { + bad[3] = 0xaa; + err = lms_decode_data(bad, sizeof(lmsPub1), EVP_PKEY_PUBLIC_KEY, + &key, NULL) == 0; + if (err != 0) { + PRINT_ERR_MSG("LMS XDR public key with an unknown type was accepted"); + } + } + if (err == 0) { + err = lms_decode_data(lmsPub1, sizeof(lmsPub1), EVP_PKEY_KEYPAIR, + &key, NULL); + if (err != 0) { + PRINT_ERR_MSG("LMS XDR keypair selection failed"); + } + } + if (err == 0) { + checkCtx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, key, NULL); + err = (checkCtx == NULL) || (EVP_PKEY_private_check(checkCtx) == 1); + if (err != 0) { + PRINT_ERR_MSG("Decoded LMS public key reported a private component"); + } + } + if (err != 0) { + PRINT_ERR_MSG("LMS XDR decoder validation failed"); + } + ERR_clear_error(); + EVP_PKEY_CTX_free(checkCtx); + EVP_PKEY_free(key); + return err; +} + +int test_lms_unsupported_operations(void* data) +{ + int err = 0; + EVP_PKEY* key = NULL; + EVP_PKEY_CTX* ctx = NULL; + EVP_PKEY_CTX* genCtx = NULL; + EVP_SIGNATURE* signature = NULL; + EVP_MD_CTX* mdCtx = NULL; + const unsigned char msg[] = "LMS unsupported operation test"; + const unsigned char badSig[] = { 0 }; + + (void)data; + err = lms_from_data(lmsPub1, sizeof(lmsPub1), EVP_PKEY_PUBLIC_KEY, &key); + if (err == 0) { + signature = EVP_SIGNATURE_fetch(wpLibCtx, "LMS", NULL); + ctx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, key, NULL); + err = (signature == NULL) || (ctx == NULL); + } + if (err == 0) { + err = EVP_PKEY_verify_message_init(ctx, signature, NULL) != 1; + } + if (err == 0) { + err = EVP_PKEY_verify(ctx, badSig, sizeof(badSig), msg, + sizeof(msg) - 1) == 1; + } + if (err == 0) { + err = EVP_PKEY_verify_init(ctx) != -2; + } + if (err == 0) { + err = EVP_PKEY_sign_message_init(ctx, signature, NULL) != -2; + } + if (err == 0) { + mdCtx = EVP_MD_CTX_new(); + err = (mdCtx == NULL) || (EVP_DigestVerifyInit_ex(mdCtx, NULL, NULL, + wpLibCtx, NULL, key, NULL) != 0); + } + if (err == 0) { + EVP_MD_CTX_free(mdCtx); + mdCtx = EVP_MD_CTX_new(); + err = (mdCtx == NULL) || (EVP_DigestSignInit_ex(mdCtx, NULL, NULL, + wpLibCtx, NULL, key, NULL) != 0); + } + if (err == 0) { + genCtx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "LMS", NULL); + err = (genCtx == NULL) || (EVP_PKEY_keygen_init(genCtx) != -2) || + (EVP_PKEY_paramgen_init(genCtx) != -2); + } + if (err != 0) { + PRINT_ERR_MSG("LMS unsupported operation validation failed"); + } + ERR_clear_error(); + EVP_MD_CTX_free(mdCtx); + EVP_SIGNATURE_free(signature); + EVP_PKEY_CTX_free(genCtx); + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(key); + return err; +} + +#endif /* WP_HAVE_LMS */ diff --git a/test/unit.c b/test/unit.c index ead233d2..3780dedc 100644 --- a/test/unit.c +++ b/test/unit.c @@ -554,6 +554,12 @@ TEST_CASE test_case[] = { #endif #endif +#ifdef WP_HAVE_LMS + TEST_DECL(test_lms_import_export, NULL), + TEST_DECL(test_lms_decode, NULL), + TEST_DECL(test_lms_unsupported_operations, NULL), +#endif + #ifdef WP_HAVE_MLKEM TEST_DECL(test_mlkem_keygen, NULL), TEST_DECL(test_mlkem_import_export_roundtrip, NULL), diff --git a/test/unit.h b/test/unit.h index 96e373d3..45ba1b10 100644 --- a/test/unit.h +++ b/test/unit.h @@ -572,6 +572,12 @@ int test_des3_tls_cbc_dec(void *data); #endif #endif +#ifdef WP_HAVE_LMS +int test_lms_import_export(void *data); +int test_lms_decode(void *data); +int test_lms_unsupported_operations(void *data); +#endif + #ifdef WP_HAVE_MLKEM int test_mlkem_keygen(void *data); int test_mlkem_import_export_roundtrip(void *data); From cb8cab330b2159508cc83b6cb22a75b989d60617 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 16:07:48 -0700 Subject: [PATCH 2/2] Add LMS verification support and coverage --- .github/workflows/wolfssl-pqc-kat.yml | 3 + .github/workflows/wolfssl-versions-pqc.yml | 4 +- ChangeLog.md | 1 + configure.ac | 2 +- docs/INTEGRATION_GUIDE.md | 2 +- scripts/test-pqc-kat.sh | 12 +- scripts/utils-openssl.sh | 16 +- scripts/utils-wolfprovider.sh | 3 +- scripts/utils-wolfssl.sh | 7 +- src/wp_lms.c | 142 +++++-- test/test_lms.c | 410 ++++++++++++++++++++- test/unit.c | 1 + test/unit.h | 1 + 13 files changed, 556 insertions(+), 48 deletions(-) diff --git a/.github/workflows/wolfssl-pqc-kat.yml b/.github/workflows/wolfssl-pqc-kat.yml index 62a64746..7d91fcd5 100644 --- a/.github/workflows/wolfssl-pqc-kat.yml +++ b/.github/workflows/wolfssl-pqc-kat.yml @@ -100,6 +100,9 @@ jobs: + rows("master"; true; "master") + [ {"name":"master [LMS only]", "wolfssl-ref":"master","pqc":false,"lms":true, + "replace":false,"force_fail":""}, + {"name":"v5.9.2-stable [LMS only]", + "wolfssl-ref":"v5.9.2-stable","pqc":false,"lms":true, "replace":false,"force_fail":""} ] ) }') echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/wolfssl-versions-pqc.yml b/.github/workflows/wolfssl-versions-pqc.yml index 80d5dfd5..2cfdc2f7 100644 --- a/.github/workflows/wolfssl-versions-pqc.yml +++ b/.github/workflows/wolfssl-versions-pqc.yml @@ -90,7 +90,9 @@ jobs: {"name":"master (--enable-mldsa only)", "wolfssl-ref":"master","enable":"--enable-mldsa","expect":"mldsa"}, {"name":"master (--enable-lms only)", - "wolfssl-ref":"master","enable":"--enable-lms","expect":"lms"} + "wolfssl-ref":"master","enable":"--enable-lms","expect":"lms"}, + {"name":"v5.9.2-stable (--enable-lms only)", + "wolfssl-ref":"v5.9.2-stable","enable":"--enable-lms","expect":"lms"} ] }') echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" diff --git a/ChangeLog.md b/ChangeLog.md index 8a86862f..e5b9224e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -8,6 +8,7 @@ number where the code change was added. ## New Feature Additions * Add ML-KEM (FIPS 203) and ML-DSA (FIPS 204) post-quantum support via `--enable-pqc` (PR 399) +* Add LMS signature verification support (PR 459) ## Enhancements and Optimizations * Route KDF key-exchange and MAC-signature operations through wolfProvider directly (PR 429) diff --git a/configure.ac b/configure.ac index 1a8d9c1c..7a45bbd8 100644 --- a/configure.ac +++ b/configure.ac @@ -269,6 +269,6 @@ echo " * Dynamic provider: $ENABLED_DYNAMIC_PROVIDER" echo " * Replace default: $ENABLED_REPLACE_DEFAULT" echo " * ML-KEM (FIPS 203): $ENABLED_MLKEM" echo " * ML-DSA (FIPS 204): $ENABLED_MLDSA" -echo " * LMS verification: $ENABLED_LMS" +echo " * LMS verification: $ENABLED_LMS" echo "" echo "---" diff --git a/docs/INTEGRATION_GUIDE.md b/docs/INTEGRATION_GUIDE.md index f3416cc6..7920afad 100644 --- a/docs/INTEGRATION_GUIDE.md +++ b/docs/INTEGRATION_GUIDE.md @@ -85,7 +85,7 @@ sudo make install | `--enable-hmac-copy` | Faster repeated HMAC with same key (wolfSSL 5.7.8+) | | `--enable-sp=yes,asm --enable-sp-math-all` | SP Integer maths | | `--enable-mlkem --enable-mldsa` | ML-KEM and ML-DSA post-quantum algorithms (wolfSSL v5.9.2-stable or later). The `build-wolfprovider.sh --enable-pqc` flag sets these automatically. | -| `--enable-lms=sha256-192,shake256` | LMS verification with the parameter families used by the OpenSSL vectors. The `build-wolfprovider.sh --enable-lms` flag sets this automatically. | +| `--enable-lms=sha256-192` | LMS verification with the SHA-256/192 parameter family used by the OpenSSL vectors. The `build-wolfprovider.sh --enable-lms` flag sets this automatically. | **Optional CPPFLAGS:** diff --git a/scripts/test-pqc-kat.sh b/scripts/test-pqc-kat.sh index 514d3f17..9117be55 100755 --- a/scripts/test-pqc-kat.sh +++ b/scripts/test-pqc-kat.sh @@ -32,6 +32,10 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source ${SCRIPT_DIR}/utils-wolfprovider.sh +# Sub-test counts from OpenSSL's vector files. Update when vectors change. +EXPECTED_MLKEM_MLDSA_TESTS=2602 +EXPECTED_LMS_TESTS=320 + # OpenSSL's KAT data files are run unmodified. VECTOR_DIR=${OPENSSL_SOURCE_DIR}/test/recipes/30-test_evp_data EVP_TEST=${OPENSSL_TEST}/evp_test @@ -71,7 +75,7 @@ run_pqc_kat() { if [ "${WOLFPROV_PQC:-0}" = "1" ]; then vectors="${VECTOR_DIR}/evppkey_ml_kem_*.txt ${VECTOR_DIR}/evppkey_ml_dsa_*.txt" - expected=$((expected + 2602)) + expected=$((expected + EXPECTED_MLKEM_MLDSA_TESTS)) fi if [ "${WOLFPROV_LMS:-0}" = "1" ]; then if [ ! -f "${VECTOR_DIR}/evppkey_lms_sigver.txt" ]; then @@ -80,7 +84,7 @@ run_pqc_kat() { return 1 fi vectors="${vectors} ${VECTOR_DIR}/evppkey_lms_sigver.txt" - expected=$((expected + 320)) + expected=$((expected + EXPECTED_LMS_TESTS)) fi if [ -z "${vectors}" ]; then printf "ERROR: no PQC KAT family selected; set WOLFPROV_PQC=1 and/or WOLFPROV_LMS=1\n" @@ -102,7 +106,9 @@ run_pqc_kat() { printf "PASS (%s)\n" "${n:-0}" else printf "FAIL\n" - printf "%s\n" "${out}" + if [ "${WOLFPROV_FORCE_FAIL}" != "1" ]; then + printf "%s\n" "${out}" + fi bad=$((bad + 1)) fi done diff --git a/scripts/utils-openssl.sh b/scripts/utils-openssl.sh index 47be083c..69b2633b 100755 --- a/scripts/utils-openssl.sh +++ b/scripts/utils-openssl.sh @@ -403,11 +403,17 @@ check_openssl_fips_baseline_mismatch() { check_openssl_lms_mismatch() { local config="${OPENSSL_INSTALL_DIR}/include/openssl/configuration.h" - if [ "${WOLFPROV_LMS}" = "1" ] && [ -d "${OPENSSL_INSTALL_DIR}" ] && - { [ ! -f "${config}" ] || grep -Eq \ - '^[[:space:]]*#[[:space:]]*define[[:space:]]+OPENSSL_NO_LMS' \ - "${config}"; }; - then + if [ "${WOLFPROV_LMS}" != "1" ] || [ ! -d "${OPENSSL_INSTALL_DIR}" ]; then + return 0 + fi + if [ ! -f "${config}" ]; then + printf "ERROR: existing OpenSSL install is incomplete (%s missing).\n" \ + "${config}" + printf "Fix: ./scripts/build-wolfprovider.sh --distclean\n" + exit 1 + fi + if grep -Eq '^[[:space:]]*#[[:space:]]*define[[:space:]]+OPENSSL_NO_LMS' \ + "${config}"; then printf "ERROR: existing OpenSSL install was built without LMS.\n" printf "Fix: ./scripts/build-wolfprovider.sh --distclean\n" exit 1 diff --git a/scripts/utils-wolfprovider.sh b/scripts/utils-wolfprovider.sh index 0987db00..c0a8d8f4 100644 --- a/scripts/utils-wolfprovider.sh +++ b/scripts/utils-wolfprovider.sh @@ -29,7 +29,8 @@ WOLFPROV_WITH_WOLFSSL=--with-wolfssl=${WOLFSSL_INSTALL_DIR} WOLFPROV_WITH_OPENSSL=--with-openssl=${OPENSSL_INSTALL_DIR} # Check if using system wolfSSL installation -if [ "$WOLFPROV_MLKEM" != "1" ] && [ "$WOLFPROV_MLDSA" != "1" ] && +if [ "$WOLFPROV_PQC" != "1" ] && [ "$WOLFPROV_MLKEM" != "1" ] && + [ "$WOLFPROV_MLDSA" != "1" ] && [ "$WOLFPROV_LMS" != "1" ] && [ ! -d "$WOLFSSL_INSTALL_DIR" ] && command -v dpkg >/dev/null 2>&1; then if dpkg -l | grep -q "^ii.*libwolfssl[[:space:]]" && dpkg -l | grep -q "^ii.*libwolfssl-dev[[:space:]]"; then diff --git a/scripts/utils-wolfssl.sh b/scripts/utils-wolfssl.sh index 4f7827de..b17c466b 100644 --- a/scripts/utils-wolfssl.sh +++ b/scripts/utils-wolfssl.sh @@ -63,7 +63,7 @@ if [ "$WOLFPROV_MLDSA" = "1" ]; then WOLFSSL_PQC_CONFIG_OPTS="${WOLFSSL_PQC_CONFIG_OPTS} --enable-mldsa" fi if [ "$WOLFPROV_LMS" = "1" ]; then - WOLFSSL_PQC_CONFIG_OPTS="${WOLFSSL_PQC_CONFIG_OPTS} --enable-lms=sha256-192,shake256" + WOLFSSL_PQC_CONFIG_OPTS="${WOLFSSL_PQC_CONFIG_OPTS} --enable-lms=sha256-192" fi WOLFSSL_CONFIG_OPTS="${WOLFSSL_CONFIG_OPTS}${WOLFSSL_PQC_CONFIG_OPTS}" @@ -151,15 +151,14 @@ check_wolfssl_feature_mismatch() { check_wolfssl_feature "${WOLFPROV_LMS}" WOLFSSL_HAVE_LMS LMS check_wolfssl_feature "${WOLFPROV_LMS}" WOLFSSL_LMS_SHA256_192 \ "LMS SHA-256/192 parameter sets" - check_wolfssl_feature "${WOLFPROV_LMS}" WOLFSSL_LMS_SHAKE256 \ - "LMS SHAKE256 parameter sets" } install_wolfssl() { # Check if libwolfssl and libwolfssl-dev packages are already installed # This is allowed only for wolfSSL, but not for OpenSSL because we want to # use the custom OpenSSL built with wolfProvider. - if [ "$WOLFPROV_MLKEM" != "1" ] && [ "$WOLFPROV_MLDSA" != "1" ] && + if [ "$WOLFPROV_PQC" != "1" ] && [ "$WOLFPROV_MLKEM" != "1" ] && + [ "$WOLFPROV_MLDSA" != "1" ] && [ "$WOLFPROV_LMS" != "1" ] && command -v dpkg >/dev/null 2>&1; then if dpkg -l | grep -q "^ii.*libwolfssl[[:space:]]" && dpkg -l | grep -q "^ii.*libwolfssl-dev[[:space:]]"; then diff --git a/src/wp_lms.c b/src/wp_lms.c index c1f074ac..006bdc48 100644 --- a/src/wp_lms.c +++ b/src/wp_lms.c @@ -55,6 +55,8 @@ typedef struct wp_LmsSigCtx { wp_Lms* lms; } wp_LmsSigCtx; +static size_t wp_lms_xdr_pub_len(const unsigned char* header); + static wp_Lms* wp_lms_new(WOLFPROV_CTX* provCtx) { wp_Lms* lms = NULL; @@ -90,11 +92,14 @@ static void wp_lms_free(wp_Lms* lms) if (lms != NULL) { int cnt; #ifndef WP_SINGLE_THREADED - if (wc_LockMutex(&lms->mutex) != 0) { - return; + if (wc_LockMutex(&lms->mutex) == 0) { + cnt = --lms->refCnt; + wc_UnLockMutex(&lms->mutex); + } + else { + /* Cannot safely decrement without the lock; keep the object. */ + cnt = lms->refCnt; } - cnt = --lms->refCnt; - wc_UnLockMutex(&lms->mutex); #else cnt = --lms->refCnt; #endif @@ -129,8 +134,7 @@ static int wp_lms_has(const wp_Lms* lms, int selection) { int ok = wolfssl_prov_is_running() && (lms != NULL); - if (ok && ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0) && - ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) == 0)) { + if (ok && ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0)) { ok = 0; } if (ok && ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0)) { @@ -145,20 +149,17 @@ static int wp_lms_match(const wp_Lms* lms1, const wp_Lms* lms2, { int ok = wolfssl_prov_is_running() && (lms1 != NULL) && (lms2 != NULL); - if (ok && ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0) && - ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) == 0)) { + if (ok && ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0)) { ok = 0; } if (ok && ((lms1->pubLen == 0) || (lms2->pubLen == 0))) { ok = 0; } if (ok) { - ok = (lms1->pubLen >= (2 * WP_LMS_TYPE_SZ)) && - (lms2->pubLen >= (2 * WP_LMS_TYPE_SZ)) && - (XMEMCMP(lms1->pub, lms2->pub, 2 * WP_LMS_TYPE_SZ) == 0); + ok = XMEMCMP(lms1->pub, lms2->pub, 2 * WP_LMS_TYPE_SZ) == 0; } if (ok && ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0)) { - ok = (lms1->pubLen != 0) && (lms1->pubLen == lms2->pubLen) && + ok = (lms1->pubLen == lms2->pubLen) && (XMEMCMP(lms1->pub, lms2->pub, lms1->pubLen) == 0); } @@ -194,6 +195,9 @@ static int wp_lms_import(wp_Lms* lms, int selection, (pubLen > WP_LMS_XDR_MAX_SZ))) { ok = 0; } + if (ok && (pubLen != wp_lms_xdr_pub_len((const unsigned char*)pub))) { + ok = 0; + } if (ok) { raw[0] = 0; raw[1] = 0; @@ -227,6 +231,70 @@ static const OSSL_PARAM* wp_lms_export_types(int selection) return wp_lms_import_types(selection); } +static const OSSL_PARAM* wp_lms_gettable_params(WOLFPROV_CTX* provCtx) +{ + static const OSSL_PARAM params[] = { + OSSL_PARAM_int(OSSL_PKEY_PARAM_BITS, NULL), + OSSL_PARAM_int(OSSL_PKEY_PARAM_SECURITY_BITS, NULL), + OSSL_PARAM_int(OSSL_PKEY_PARAM_MAX_SIZE, NULL), + OSSL_PARAM_END + }; + + (void)provCtx; + return params; +} + +static int wp_lms_get_params(wp_Lms* lms, OSSL_PARAM params[]) +{ + int ok = (lms != NULL) && (lms->pubLen != 0); + OSSL_PARAM* p; + word32 sigLen = 0; + int bits; + + if (ok) { + bits = (lms->pubLen == WP_LMS_XDR_MIN_SZ) ? 192 : 256; + p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_BITS); + if ((p != NULL) && !OSSL_PARAM_set_int(p, bits)) { + ok = 0; + } + p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_SECURITY_BITS); + if ((p != NULL) && !OSSL_PARAM_set_int(p, bits / 2)) { + ok = 0; + } + p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_MAX_SIZE); + if (p != NULL) { + if ((wc_LmsKey_GetSigLen(&lms->key, &sigLen) != 0) || + (sigLen < WP_LMS_LEVELS_SZ) || + !OSSL_PARAM_set_int(p, + (int)sigLen - WP_LMS_LEVELS_SZ)) { + ok = 0; + } + } + } + return ok; +} + +static wp_Lms* wp_lms_dup(const wp_Lms* src, int selection) +{ + wp_Lms* dst = NULL; + OSSL_PARAM params[2]; + + if ((src != NULL) && + ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0)) { + dst = wp_lms_new(src->provCtx); + if (dst != NULL) { + params[0] = OSSL_PARAM_construct_octet_string( + OSSL_PKEY_PARAM_PUB_KEY, (void*)src->pub, src->pubLen); + params[1] = OSSL_PARAM_construct_end(); + if (!wp_lms_import(dst, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params)) { + wp_lms_free(dst); + dst = NULL; + } + } + } + return dst; +} + static int wp_lms_export(wp_Lms* lms, int selection, OSSL_CALLBACK* paramCb, void* cbArg) { @@ -267,6 +335,9 @@ static const char* wp_lms_query_operation_name(int operationId) const OSSL_DISPATCH wp_lms_keymgmt_functions[] = { { OSSL_FUNC_KEYMGMT_NEW, (DFUNC)wp_lms_new }, { OSSL_FUNC_KEYMGMT_FREE, (DFUNC)wp_lms_free }, + { OSSL_FUNC_KEYMGMT_DUP, (DFUNC)wp_lms_dup }, + { OSSL_FUNC_KEYMGMT_GET_PARAMS, (DFUNC)wp_lms_get_params }, + { OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (DFUNC)wp_lms_gettable_params }, { OSSL_FUNC_KEYMGMT_HAS, (DFUNC)wp_lms_has }, { OSSL_FUNC_KEYMGMT_MATCH, (DFUNC)wp_lms_match }, { OSSL_FUNC_KEYMGMT_VALIDATE, (DFUNC)wp_lms_validate }, @@ -305,6 +376,26 @@ static void wp_lms_sig_freectx(wp_LmsSigCtx* ctx) } } +static wp_LmsSigCtx* wp_lms_sig_dupctx(const wp_LmsSigCtx* src) +{ + wp_LmsSigCtx* dst = NULL; + + if (src != NULL) { + dst = OPENSSL_zalloc(sizeof(*dst)); + if (dst != NULL) { + dst->provCtx = src->provCtx; + if ((src->lms != NULL) && !wp_lms_up_ref(src->lms)) { + OPENSSL_free(dst); + dst = NULL; + } + else { + dst->lms = src->lms; + } + } + } + return dst; +} + static int wp_lms_verify_message_init(wp_LmsSigCtx* ctx, wp_Lms* lms, const OSSL_PARAM params[]) { @@ -366,6 +457,7 @@ static int wp_lms_verify(wp_LmsSigCtx* ctx, const unsigned char* sig, const OSSL_DISPATCH wp_lms_signature_functions[] = { { OSSL_FUNC_SIGNATURE_NEWCTX, (DFUNC)wp_lms_sig_newctx }, { OSSL_FUNC_SIGNATURE_FREECTX, (DFUNC)wp_lms_sig_freectx }, + { OSSL_FUNC_SIGNATURE_DUPCTX, (DFUNC)wp_lms_sig_dupctx }, { OSSL_FUNC_SIGNATURE_VERIFY_MESSAGE_INIT, (DFUNC)wp_lms_verify_message_init }, { OSSL_FUNC_SIGNATURE_VERIFY, (DFUNC)wp_lms_verify }, @@ -395,11 +487,17 @@ static void wp_lms_dec_free(wp_LmsDecCtx* ctx) OPENSSL_free(ctx); } -static int wp_lms_dec_selection(const wp_LmsDecCtx* ctx, int selection) +static int wp_lms_selection_ok(int selection) +{ + return (selection == 0) || + (((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) == 0) && + ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0)); +} + +static int wp_lms_dec_selection(WOLFPROV_CTX* provCtx, int selection) { - (void)ctx; - return selection == 0 || - (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0; + (void)provCtx; + return wp_lms_selection_ok(selection); } static int wp_lms_dec_export(wp_LmsDecCtx* ctx, const void* reference, @@ -462,13 +560,13 @@ static int wp_lms_dec_decode(wp_LmsDecCtx* ctx, OSSL_CORE_BIO* cBio, size_t pubLen = 0; int ok = 0; OSSL_PARAM params[4]; + OSSL_PARAM importParams[2]; int objectType = OSSL_OBJECT_PKEY; - (void)selection; (void)pwCb; (void)pwCbArg; if (!wolfssl_prov_is_running() || (ctx == NULL) || - (dataCb == NULL) || !wp_lms_dec_selection(ctx, selection)) { + (dataCb == NULL) || !wp_lms_selection_ok(selection)) { goto done; } bio = wp_corebio_get_bio(ctx->provCtx, cBio); @@ -494,10 +592,10 @@ static int wp_lms_dec_decode(wp_LmsDecCtx* ctx, OSSL_CORE_BIO* cBio, if (lms == NULL) { goto done; } - if (!wp_lms_import(lms, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, - (OSSL_PARAM[]){ OSSL_PARAM_construct_octet_string( - OSSL_PKEY_PARAM_PUB_KEY, pub, pubLen), - OSSL_PARAM_construct_end() })) { + importParams[0] = OSSL_PARAM_construct_octet_string( + OSSL_PKEY_PARAM_PUB_KEY, pub, pubLen); + importParams[1] = OSSL_PARAM_construct_end(); + if (!wp_lms_import(lms, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, importParams)) { wp_lms_free(lms); lms = NULL; ok = 1; diff --git a/test/test_lms.c b/test/test_lms.c index a32008ec..ab7db21c 100644 --- a/test/test_lms.c +++ b/test/test_lms.c @@ -101,6 +101,7 @@ int test_lms_import_export(void* data) int err = 0; EVP_PKEY* key1 = NULL; EVP_PKEY* key1Copy = NULL; + EVP_PKEY* keyDup = NULL; EVP_PKEY* key2 = NULL; EVP_PKEY* badKey = NULL; EVP_PKEY* rawKey = NULL; @@ -163,6 +164,16 @@ int test_lms_import_export(void* data) PRINT_ERR_MSG("LMS public key equality check failed"); } } + if (err == 0) { + keyDup = EVP_PKEY_dup(key1); + err = (keyDup == NULL) || (EVP_PKEY_eq(key1, keyDup) != 1) || + (EVP_PKEY_get_bits(key1) == 0) || + (EVP_PKEY_get_security_bits(key1) == 0) || + (EVP_PKEY_get_size(key1) == 0); + if (err != 0) { + PRINT_ERR_MSG("LMS key duplication or metadata failed"); + } + } if (err == 0) { err = (EVP_PKEY_parameters_eq(key1, key1Copy) != 1) || (EVP_PKEY_parameters_eq(key1, key2) == 1); @@ -172,7 +183,7 @@ int test_lms_import_export(void* data) } if (err == 0) { checkCtx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, key1, NULL); - err = (checkCtx == NULL) || (EVP_PKEY_check(checkCtx) != 1) || + err = (checkCtx == NULL) || (EVP_PKEY_check(checkCtx) == 1) || (EVP_PKEY_public_check(checkCtx) != 1) || (EVP_PKEY_private_check(checkCtx) == 1); if (err != 0) { @@ -232,6 +243,7 @@ int test_lms_import_export(void* data) EVP_PKEY_free(rawKey); EVP_PKEY_free(badKey); EVP_PKEY_free(key2); + EVP_PKEY_free(keyDup); EVP_PKEY_free(key1Copy); EVP_PKEY_free(key1); return err; @@ -254,6 +266,15 @@ int test_lms_decode(void* data) if (err != 0) { PRINT_ERR_MSG("LMS XDR public key decode failed"); } + if (err == 0) { + checkCtx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, key, NULL); + err = (checkCtx == NULL) || (EVP_PKEY_private_check(checkCtx) == 1); + if (err != 0) { + PRINT_ERR_MSG("Decoded LMS public key reported a private component"); + } + EVP_PKEY_CTX_free(checkCtx); + checkCtx = NULL; + } EVP_PKEY_free(key); key = NULL; if (err == 0) { @@ -297,16 +318,9 @@ int test_lms_decode(void* data) } if (err == 0) { err = lms_decode_data(lmsPub1, sizeof(lmsPub1), EVP_PKEY_KEYPAIR, - &key, NULL); - if (err != 0) { - PRINT_ERR_MSG("LMS XDR keypair selection failed"); - } - } - if (err == 0) { - checkCtx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, key, NULL); - err = (checkCtx == NULL) || (EVP_PKEY_private_check(checkCtx) == 1); + &key, NULL) == 0; if (err != 0) { - PRINT_ERR_MSG("Decoded LMS public key reported a private component"); + PRINT_ERR_MSG("LMS XDR decoder accepted keypair selection"); } } if (err != 0) { @@ -377,4 +391,380 @@ int test_lms_unsupported_operations(void* data) return err; } + +/* OpenSSL LMS known-answer verification and rejection checks. */ +static const unsigned char lmsVerifyPub[] = { + 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x05, 0x00, 0x92, 0x40, 0xbf, 0x00, 0xa8, 0xfc, 0x86, + 0x91, 0x1c, 0x70, 0x8e, 0xb1, 0x67, 0x6d, 0x8a, 0x97, 0x28, 0xb2, 0x66, 0xe3, 0xdd, 0x7c, 0xf4, + 0xb6, 0x3b, 0x21, 0x03, 0x8e, 0xd8, 0xf9, 0xf4, 0xed, 0x10, 0x41, 0xed, 0x7d, 0xc9, 0xc0, 0x10 +}; + +static const unsigned char lmsVerifyMsg[] = { + 0xad, 0xc9, 0x32, 0xe7, 0xe9, 0x35, 0xe1, 0x85, 0x51, 0x86, 0x23, 0x58, 0xe2, 0x9d, 0xe1, 0x61, + 0xe3, 0xf7, 0x48, 0x74, 0x90, 0xf0, 0xe6, 0x37, 0x83, 0xc0, 0x7d, 0x03, 0xab, 0xb8, 0x72, 0xeb, + 0x84, 0xe2, 0xb5, 0x85, 0x14, 0xda, 0x15, 0xfe, 0xcb, 0xa1, 0xd9, 0x04, 0x99, 0x0e, 0xab, 0x8e, + 0xcc, 0x5f, 0xc5, 0x6f, 0x4f, 0x89, 0x03, 0x63, 0x4e, 0x46, 0xc2, 0xfe, 0x33, 0xad, 0x27, 0x2b, + 0x8b, 0xbd, 0x67, 0x8d, 0x2b, 0xad, 0xc0, 0xa9, 0x66, 0x97, 0xfe, 0x93, 0x54, 0x35, 0x7e, 0x46, + 0xbe, 0xb1, 0xb0, 0xd2, 0xe4, 0xfd, 0xa8, 0xeb, 0x6f, 0xe1, 0x32, 0xd4, 0x84, 0x7c, 0x95, 0xca, + 0x35, 0x2e, 0x3a, 0x17, 0x54, 0x6a, 0x3f, 0x7c, 0x31, 0xaa, 0x6d, 0xf8, 0x75, 0x73, 0x44, 0x81, + 0x1b, 0x46, 0x7a, 0xc9, 0x78, 0x5c, 0x5a, 0xc5, 0xd4, 0xbb, 0xf7, 0x6b, 0x2a, 0xf2, 0x54, 0xd3 +}; + +static const unsigned char lmsVerifySig[] = { + 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x05, 0xf7, 0x48, 0xd8, 0xa3, 0x56, 0x7e, 0x7e, 0x9c, + 0x32, 0x28, 0x86, 0xff, 0x78, 0x89, 0xa4, 0x07, 0xf7, 0xa6, 0x7e, 0x64, 0x0b, 0x37, 0x65, 0x25, + 0x80, 0xe1, 0x0e, 0x78, 0x94, 0x8d, 0xcd, 0x00, 0xa0, 0x52, 0xb0, 0x50, 0xb9, 0x63, 0xb0, 0x59, + 0x80, 0xe5, 0x58, 0x3f, 0xfb, 0xda, 0x57, 0xc3, 0xc7, 0xd2, 0x9b, 0xcd, 0xbc, 0x8d, 0x8f, 0xa6, + 0xb2, 0xcc, 0xff, 0x0b, 0x81, 0x3c, 0x2c, 0x6f, 0x3d, 0xc9, 0x9a, 0xc1, 0x92, 0x72, 0x84, 0x1f, + 0xc9, 0x73, 0xc6, 0x07, 0x92, 0xfe, 0xcb, 0x60, 0xf1, 0x47, 0x35, 0xdc, 0xc6, 0xee, 0x35, 0x69, + 0x66, 0x8a, 0x65, 0x66, 0x94, 0x2b, 0xeb, 0xfa, 0xf6, 0x8a, 0xd0, 0x56, 0x12, 0x1c, 0x28, 0x88, + 0x9d, 0x3c, 0x5c, 0x44, 0xfd, 0x0c, 0xb2, 0x93, 0x7c, 0xc4, 0x86, 0x55, 0x99, 0x97, 0xa0, 0xcb, + 0x39, 0x8b, 0x5d, 0x7a, 0xb8, 0xaf, 0xdb, 0xc5, 0x90, 0x5b, 0xd6, 0x1d, 0x1a, 0x86, 0xaa, 0xaa, + 0xea, 0x1e, 0x61, 0x77, 0x67, 0xc7, 0x15, 0x39, 0x4c, 0x13, 0x1a, 0x87, 0x41, 0x22, 0x19, 0xfe, + 0x7a, 0xeb, 0x4e, 0xba, 0x09, 0xfc, 0x06, 0x82, 0xd6, 0x18, 0xa1, 0xac, 0xcb, 0x87, 0xfb, 0xd2, + 0xb7, 0x0c, 0xae, 0xee, 0x6c, 0x12, 0x65, 0x23, 0x10, 0x4c, 0x9e, 0x86, 0xa2, 0xc9, 0x36, 0x0d, + 0x52, 0x41, 0xf4, 0x57, 0x9c, 0xb1, 0x67, 0x9e, 0x39, 0x03, 0x71, 0x54, 0x6b, 0xb3, 0x94, 0x1b, + 0x6f, 0x4e, 0xc3, 0x98, 0xdf, 0x04, 0xf1, 0xf0, 0x08, 0xc1, 0x92, 0x99, 0x2d, 0x4c, 0x7f, 0xd5, + 0x3d, 0x0e, 0x0f, 0x81, 0x16, 0xb3, 0xcc, 0x97, 0x74, 0x60, 0x55, 0xab, 0x0b, 0x3e, 0x32, 0x3a, + 0x73, 0x32, 0xcd, 0xd9, 0xf0, 0x73, 0xfa, 0xe3, 0x76, 0xda, 0x5e, 0x44, 0x63, 0x7a, 0x49, 0x73, + 0x50, 0x47, 0x9a, 0x10, 0x19, 0x5a, 0x6c, 0xcc, 0x44, 0x4b, 0x63, 0x08, 0x15, 0xfa, 0x2a, 0x50, + 0x4b, 0x47, 0x11, 0x4a, 0x6e, 0x0b, 0x60, 0xaa, 0x33, 0xbf, 0x2a, 0x9d, 0x0b, 0xcc, 0x75, 0x85, + 0xd1, 0xb6, 0x60, 0x82, 0xc0, 0x9e, 0x7b, 0xfd, 0xa3, 0x9a, 0xbf, 0x35, 0x60, 0x01, 0xdf, 0x58, + 0x26, 0x75, 0xae, 0x00, 0xe8, 0x05, 0x62, 0x15, 0xef, 0x08, 0xbe, 0xc5, 0x94, 0x03, 0x18, 0x37, + 0x4e, 0x55, 0x23, 0x23, 0xf6, 0xe7, 0x45, 0xd4, 0x4c, 0xb2, 0x65, 0xd4, 0xdf, 0x45, 0x46, 0xe6, + 0x00, 0x04, 0x03, 0x4e, 0x2d, 0x8b, 0xe0, 0xaf, 0xde, 0xad, 0x23, 0x6f, 0xc0, 0x17, 0x9a, 0x63, + 0xf2, 0x39, 0x44, 0xe5, 0x17, 0x04, 0xd4, 0xf6, 0xf5, 0x82, 0xae, 0xd6, 0x0b, 0xad, 0x41, 0xe0, + 0x36, 0xd9, 0x09, 0xdf, 0x3e, 0x1f, 0x7a, 0x7f, 0x96, 0xa0, 0xfc, 0x50, 0x7d, 0x46, 0x7d, 0x37, + 0xf3, 0x74, 0x7a, 0xe2, 0x9d, 0x17, 0x1d, 0xad, 0x54, 0x9a, 0x3e, 0xf1, 0x74, 0x07, 0x57, 0x1a, + 0x79, 0xd6, 0x47, 0xe3, 0x5a, 0x82, 0x9c, 0x25, 0x31, 0xa0, 0x0c, 0xc6, 0x93, 0xfa, 0xbe, 0x44, + 0xaa, 0xf4, 0x9e, 0x23, 0xfd, 0xe1, 0x65, 0x78, 0x68, 0x2d, 0x81, 0x03, 0x9f, 0x00, 0x09, 0xc0, + 0x26, 0x28, 0x20, 0x2b, 0xba, 0xc0, 0xef, 0x6f, 0x28, 0xb8, 0x64, 0x23, 0xf9, 0xab, 0x53, 0xf8, + 0x1a, 0xa8, 0x59, 0x5b, 0xf9, 0xc3, 0x0e, 0xc3, 0x3f, 0xad, 0x6e, 0x12, 0x16, 0xfd, 0x65, 0x23, + 0x12, 0xe0, 0x55, 0xc1, 0xa5, 0xce, 0x27, 0xfd, 0x19, 0xf0, 0x07, 0x75, 0x68, 0x61, 0x50, 0x42, + 0x81, 0xb1, 0x50, 0x38, 0xaa, 0x60, 0x2e, 0x90, 0x16, 0x38, 0xce, 0xd9, 0x87, 0xd2, 0x50, 0xcb, + 0xe0, 0x5c, 0x8e, 0x79, 0xcb, 0xe6, 0x87, 0xf0, 0xce, 0xd8, 0x54, 0x02, 0x31, 0x76, 0x0b, 0xfc, + 0x27, 0x80, 0x50, 0x59, 0x55, 0xbd, 0x5d, 0xef, 0x86, 0xd8, 0xc4, 0x19, 0xcd, 0xe3, 0x2d, 0x62, + 0x8e, 0xae, 0x95, 0x0c, 0x54, 0xe2, 0xd5, 0x92, 0x81, 0xbc, 0x95, 0x7a, 0x6a, 0x88, 0xe1, 0x2a, + 0xf9, 0xee, 0xc0, 0x9d, 0xfc, 0x82, 0x76, 0xe0, 0x9e, 0x33, 0x99, 0xf5, 0xeb, 0x6c, 0x08, 0x5b, + 0xc1, 0x1f, 0xbc, 0x3a, 0xa1, 0x47, 0x70, 0x33, 0xbe, 0xc4, 0x8d, 0xd0, 0xe8, 0xa2, 0x4e, 0xea, + 0xc6, 0x67, 0xa5, 0x43, 0x7a, 0xe0, 0x4f, 0xa3, 0x12, 0x55, 0x37, 0x7e, 0xee, 0x1d, 0x3a, 0xd4, + 0x17, 0x71, 0x6f, 0x1b, 0x58, 0x7e, 0xfa, 0xd7, 0x49, 0x80, 0x7e, 0x66, 0xf8, 0x5c, 0xce, 0x16, + 0xbc, 0xa1, 0xba, 0x3b, 0xfc, 0x32, 0xc9, 0xed, 0x61, 0xa3, 0x99, 0x0c, 0x5f, 0x9c, 0x0a, 0xb4, + 0xcd, 0x17, 0x7c, 0x57, 0x38, 0x24, 0x07, 0x35, 0xc5, 0x1c, 0x93, 0x40, 0x54, 0x97, 0x52, 0x18, + 0x24, 0x52, 0x5f, 0x3a, 0x82, 0x41, 0x0c, 0x5e, 0x58, 0x55, 0x2c, 0x42, 0xe0, 0x41, 0x3f, 0x2c, + 0x0e, 0xde, 0xa7, 0x99, 0x45, 0x88, 0x26, 0x6b, 0xb2, 0x0f, 0x67, 0x3d, 0xed, 0xc4, 0x26, 0x82, + 0x67, 0x91, 0xe7, 0xb0, 0x42, 0xd0, 0xa8, 0xe5, 0x30, 0x06, 0x18, 0xd0, 0x27, 0x50, 0xe9, 0x14, + 0x96, 0x43, 0xa8, 0xff, 0x69, 0x2d, 0x21, 0x3f, 0xe2, 0x3f, 0xed, 0xc5, 0x01, 0xa4, 0x77, 0xab, + 0x0b, 0x87, 0xbf, 0xf9, 0x6d, 0x60, 0x29, 0x7e, 0x3b, 0xaa, 0x24, 0xc4, 0xee, 0x08, 0xf9, 0xb2, + 0x0f, 0x36, 0x69, 0x3d, 0xae, 0x3e, 0x22, 0xd8, 0x78, 0x6b, 0x03, 0x97, 0xd3, 0xb8, 0x47, 0xb5, + 0x0a, 0xb0, 0x89, 0x8e, 0x78, 0x70, 0x4b, 0x9a, 0x90, 0x4c, 0x16, 0x8b, 0x68, 0x3b, 0x71, 0xa4, + 0x7d, 0xbb, 0xe2, 0x1c, 0xa3, 0xe9, 0x2a, 0x45, 0x8d, 0xf0, 0xdd, 0x55, 0x23, 0xd0, 0x88, 0x6e, + 0x3d, 0xed, 0x75, 0xb8, 0xc0, 0x39, 0xe0, 0x60, 0x46, 0x78, 0x5a, 0x26, 0x16, 0x96, 0xba, 0xd8, + 0x3c, 0x78, 0xc5, 0xbf, 0x1e, 0x4f, 0x31, 0x9c, 0xcb, 0x8d, 0x88, 0xce, 0x1b, 0xdc, 0xd6, 0x92, + 0xee, 0xdf, 0xe1, 0x42, 0xa7, 0x8e, 0x62, 0x9a, 0x3b, 0xfc, 0x64, 0xcb, 0x63, 0x4b, 0xad, 0x50, + 0x8c, 0x55, 0x48, 0x66, 0xaf, 0x38, 0x98, 0x49, 0x73, 0xe9, 0x9e, 0x30, 0x67, 0xf5, 0x95, 0x58, + 0x21, 0x4c, 0x2c, 0x1f, 0xf7, 0xf6, 0x90, 0x62, 0x52, 0x91, 0x20, 0x8a, 0xbe, 0x8f, 0x98, 0xb0, + 0x9c, 0x60, 0xac, 0xca, 0x66, 0xdf, 0xab, 0x90, 0x86, 0x3c, 0xff, 0xc3, 0x4a, 0x8f, 0x83, 0x2a, + 0x80, 0xb3, 0x82, 0xd9, 0x50, 0xc5, 0xea, 0x59, 0xb8, 0x32, 0x18, 0x2a, 0x2a, 0xbd, 0xb2, 0x58, + 0x94, 0x3a, 0xb5, 0xf7, 0x6e, 0x65, 0x8f, 0x38, 0x0c, 0x61, 0xaa, 0x4b, 0xec, 0x52, 0xbe, 0x98, + 0x21, 0x1d, 0xa0, 0x1b, 0xc6, 0xfb, 0xc8, 0x7b, 0xee, 0xc3, 0x4a, 0x49, 0x20, 0x3b, 0x43, 0xa3, + 0x64, 0x49, 0x92, 0x94, 0x8e, 0x28, 0x8a, 0x81, 0x5d, 0x1e, 0x99, 0xc0, 0xd6, 0x52, 0x0c, 0x50, + 0x38, 0x68, 0x0d, 0x79, 0xa8, 0xca, 0x3a, 0x15, 0xaf, 0x6d, 0xc3, 0xc0, 0x02, 0x86, 0x31, 0x7b, + 0x70, 0xc3, 0xea, 0x86, 0xf5, 0xb2, 0x1d, 0xa0, 0xe2, 0x2a, 0x16, 0x72, 0x1b, 0x16, 0x3b, 0x9f, + 0xdc, 0xb0, 0x38, 0xe3, 0xcd, 0x78, 0xc5, 0xf5, 0x88, 0x49, 0xb4, 0xe9, 0x4f, 0x05, 0xa6, 0x98, + 0xb4, 0x0c, 0x84, 0x6e, 0xd8, 0x26, 0xbb, 0xe2, 0x73, 0x2c, 0xb6, 0xbd, 0xc1, 0x94, 0x2f, 0xa4, + 0x51, 0x40, 0x13, 0x29, 0xd9, 0xf3, 0x63, 0x51, 0x9d, 0x4c, 0x02, 0xfe, 0x54, 0x47, 0xe0, 0xf2, + 0x80, 0x16, 0x37, 0x66, 0x10, 0xda, 0x4e, 0x5e, 0x91, 0x07, 0x9b, 0xc0, 0xa9, 0x06, 0xb4, 0x2c, + 0x5c, 0xa9, 0x23, 0x95, 0xc0, 0xed, 0x23, 0xc4, 0x15, 0xbc, 0xf9, 0x62, 0xf2, 0x60, 0x98, 0x2e, + 0x61, 0x9b, 0x47, 0xe7, 0x66, 0x1a, 0x6f, 0x67, 0x6d, 0x58, 0x39, 0x39, 0x09, 0x9e, 0x0c, 0x3f, + 0x25, 0x2b, 0xbd, 0xd4, 0xcc, 0x05, 0x1a, 0x2b, 0x0a, 0x22, 0x26, 0x5b, 0xfe, 0x13, 0xe5, 0x4c, + 0x08, 0xe9, 0x63, 0x47, 0x31, 0xa1, 0x6e, 0xf5, 0xf0, 0x1e, 0xdb, 0xf2, 0x12, 0xa9, 0x84, 0x74, + 0xea, 0x13, 0x09, 0xb1, 0x80, 0xe9, 0xba, 0x26, 0xc4, 0x57, 0x3d, 0x40, 0xf2, 0x2d, 0x67, 0x88, + 0xae, 0xd9, 0x27, 0x5c, 0xc2, 0xdd, 0xa5, 0x0b, 0x7e, 0x26, 0xed, 0x6e, 0xf6, 0x05, 0xe8, 0xc5, + 0xe4, 0xce, 0x77, 0xc1, 0x3e, 0x1c, 0xd5, 0x10, 0xb5, 0xeb, 0x01, 0x3d, 0x7c, 0x5a, 0x1d, 0x38, + 0x4d, 0xec, 0x59, 0xaa, 0x34, 0x6c, 0xab, 0x0b, 0xc4, 0xfa, 0xac, 0x43, 0x71, 0x15, 0x36, 0x97, + 0xcf, 0x1c, 0xdd, 0x4f, 0x99, 0xbc, 0xd7, 0x16, 0x3d, 0x40, 0xed, 0xed, 0xef, 0x04, 0x9b, 0xba, + 0x31, 0x4b, 0xca, 0x31, 0xfc, 0xcc, 0xa9, 0x04, 0xbc, 0x32, 0xbb, 0xbb, 0xaa, 0x94, 0x2a, 0x53, + 0xd4, 0x2a, 0xba, 0xab, 0xe6, 0xdf, 0x44, 0x01, 0xfe, 0xae, 0xba, 0x05, 0xc0, 0x8b, 0x7c, 0x8e, + 0x36, 0x68, 0xaf, 0xbc, 0x8d, 0x7f, 0x86, 0xef, 0x78, 0xeb, 0x90, 0x5a, 0x0d, 0x5d, 0xa6, 0xc2, + 0x9f, 0xda, 0xbf, 0x98, 0xfb, 0xe7, 0xab, 0x76, 0xa9, 0xea, 0xe7, 0x58, 0x03, 0x78, 0x92, 0xb3, + 0xd8, 0xc1, 0x84, 0xec, 0x5d, 0x8a, 0x13, 0x4c, 0xe5, 0xbe, 0x0b, 0x4e, 0xe1, 0x3f, 0x82, 0x15, + 0x61, 0x16, 0x1c, 0x7f, 0x31, 0xfb, 0xc8, 0x06, 0xb8, 0xed, 0x43, 0xc0, 0x99, 0x84, 0x1c, 0xd4, + 0x88, 0xf9, 0x51, 0x31, 0x78, 0xca, 0x99, 0x8f, 0x34, 0x27, 0xf1, 0x30, 0x8f, 0x75, 0x0c, 0xac, + 0xb4, 0x5f, 0xaa, 0xf4, 0xe4, 0x3a, 0x8e, 0xf9, 0x41, 0xc0, 0x81, 0xa4, 0xcc, 0x68, 0xb8, 0x23, + 0x6d, 0x3a, 0x35, 0x85, 0x97, 0x18, 0xc7, 0x88, 0x53, 0xee, 0x8c, 0x85, 0x7f, 0x50, 0x56, 0x8a, + 0x61, 0x6c, 0xf0, 0xaf, 0xcb, 0xb2, 0x88, 0x8a, 0xbe, 0xf9, 0x7a, 0x8b, 0x8b, 0x7a, 0xdc, 0x6b, + 0xdc, 0xb1, 0x4c, 0x8c, 0x63, 0xa7, 0x26, 0xa1, 0x07, 0xa7, 0x2c, 0x75, 0x20, 0x1f, 0x91, 0x67, + 0x16, 0x69, 0xb3, 0xd0, 0x88, 0xac, 0x3e, 0x4b, 0x7d, 0x97, 0x10, 0xa2, 0x39, 0x1f, 0xfe, 0x4f, + 0x5b, 0xd9, 0xee, 0x14, 0xf1, 0xe0, 0x84, 0x36, 0x2b, 0x54, 0xdd, 0xe3, 0x59, 0xc4, 0x28, 0x40, + 0xf7, 0xf2, 0x21, 0xce, 0x97, 0xf2, 0xc4, 0x69, 0x99, 0x4e, 0xdf, 0x8e, 0xb9, 0xd4, 0xfd, 0x6a, + 0x2d, 0xff, 0xae, 0x75, 0xaf, 0xf9, 0x48, 0x5d, 0x7a, 0x89, 0x69, 0x77, 0x97, 0x07, 0xdc, 0x1f, + 0x27, 0xbc, 0x50, 0x6b, 0x37, 0xf6, 0x41, 0x75, 0xbf, 0xe5, 0xb9, 0xcd, 0x9f, 0x5a, 0x66, 0x06, + 0xf4, 0xa4, 0xe6, 0x90, 0x42, 0xbc, 0xd7, 0x5f, 0x53, 0xb9, 0x6b, 0x79, 0xfc, 0xaf, 0x01, 0xa4, + 0x5e, 0x37, 0xfe, 0xb4, 0xb3, 0x83, 0x57, 0xa6, 0x17, 0xd4, 0xe7, 0xd6, 0x2a, 0x30, 0xae, 0xfa, + 0xf8, 0x35, 0xc8, 0x1a, 0x2a, 0xc5, 0xe4, 0x66, 0x2a, 0x81, 0xeb, 0xc5, 0x54, 0x77, 0x9a, 0x6e, + 0x1d, 0x09, 0x50, 0x7e, 0x98, 0xf8, 0x1b, 0x3e, 0x4e, 0xd6, 0xd6, 0x81, 0x88, 0x4a, 0x74, 0x12, + 0xb8, 0x3f, 0x02, 0xfe, 0xf0, 0xb2, 0x1d, 0xf7, 0x26, 0x80, 0xf7, 0x24, 0x79, 0x99, 0x4a, 0x77, + 0xc5, 0xae, 0xf1, 0x02, 0x12, 0x34, 0x48, 0x77, 0x4b, 0x03, 0xd6, 0xfe, 0xa3, 0xf6, 0xe6, 0x72, + 0x1f, 0xff, 0x68, 0xe9, 0x47, 0x19, 0xe4, 0x7c, 0x09, 0x34, 0x89, 0x20, 0x74, 0x2b, 0x81, 0xf8, + 0xa1, 0x74, 0x57, 0xd9, 0x9c, 0x9a, 0x4d, 0xa8, 0x36, 0x5e, 0x04, 0xa9, 0x5e, 0xbc, 0x92, 0x20, + 0x94, 0xdc, 0x4f, 0x46, 0xe9, 0xa6, 0x3b, 0x4b, 0x52, 0xfd, 0xf7, 0x2c, 0xb0, 0x4c, 0x4a, 0x56, + 0xa6, 0x62, 0x75, 0x0d, 0x4c, 0x81, 0xb0, 0x3b, 0x7e, 0x08, 0x3c, 0x80, 0xd7, 0xe8, 0x50, 0x03, + 0x31, 0x9b, 0x7d, 0x10, 0xb6, 0x71, 0xd0, 0x49, 0x4f, 0x59, 0xd4, 0x8a, 0xcd, 0x63, 0x4c, 0x09, + 0x7a, 0xd2, 0x95, 0xf9, 0x42, 0xa8, 0xf1, 0x92, 0x5c, 0xb9, 0xea, 0x71, 0xe3, 0x00, 0x37, 0x62, + 0x7b, 0x81, 0x78, 0x29, 0xc3, 0x2d, 0x95, 0xcb, 0x19, 0x46, 0x8b, 0x5a, 0x13, 0xe6, 0x6a, 0x20, + 0x33, 0x23, 0xaf, 0x20, 0xf6, 0x4e, 0x11, 0x29, 0x28, 0x28, 0xea, 0xe9, 0xaf, 0x4d, 0x2a, 0xe8, + 0x9a, 0x2f, 0xaa, 0xee, 0x8a, 0xde, 0x69, 0x9b, 0x5e, 0x33, 0xdb, 0x78, 0xbc, 0x00, 0xb6, 0xae, + 0x03, 0x78, 0x13, 0x18, 0x1b, 0x11, 0xf5, 0x38, 0x70, 0x07, 0xc4, 0x0d, 0xd9, 0x45, 0x18, 0xcd, + 0x56, 0xa4, 0x90, 0x9c, 0x0e, 0xa7, 0x0b, 0xc9, 0x8f, 0x0b, 0xb5, 0x71, 0x87, 0x82, 0x8a, 0xf4, + 0x17, 0xba, 0x66, 0x91, 0x1a, 0x5a, 0x55, 0x87, 0xf6, 0xfb, 0x67, 0xff, 0xd4, 0x6e, 0xf9, 0x8c, + 0x87, 0x4a, 0x15, 0x45, 0xaa, 0xcb, 0x8f, 0xb7, 0xfd, 0x81, 0xfa, 0x78, 0x42, 0xc6, 0xfa, 0x55, + 0xc5, 0x17, 0x16, 0x16, 0xd6, 0x17, 0x98, 0xd5, 0x8d, 0x45, 0xe3, 0x23, 0x57, 0xad, 0x6a, 0x77, + 0xfc, 0x11, 0xe2, 0x04, 0xa0, 0xd3, 0x30, 0x39, 0x9d, 0x98, 0xa8, 0xb2, 0xd1, 0x10, 0xaa, 0x64, + 0xa6, 0x08, 0x35, 0x22, 0x25, 0xde, 0xff, 0xdd, 0x57, 0x0a, 0x83, 0xc6, 0xf3, 0x43, 0x1a, 0x60, + 0x6c, 0xfb, 0x30, 0xe4, 0x37, 0xc7, 0xaa, 0x8e, 0xd6, 0x82, 0x4a, 0xf8, 0xac, 0xfc, 0x5c, 0xdc, + 0x20, 0xb1, 0x61, 0xd8, 0xbb, 0x4e, 0xec, 0x56, 0x97, 0xbc, 0x75, 0x5d, 0x15, 0x6a, 0x86, 0xe3, + 0x63, 0xd0, 0x17, 0xb0, 0xa4, 0xd7, 0x65, 0x61, 0x66, 0xb9, 0xf7, 0xbf, 0xdc, 0x8b, 0xba, 0x11, + 0xbd, 0xe7, 0xaa, 0xa3, 0xe7, 0x5f, 0x51, 0x6b, 0x74, 0x9d, 0x78, 0x81, 0x01, 0x57, 0xf9, 0x59, + 0x9c, 0x05, 0xe7, 0x40, 0xe8, 0xaf, 0x6a, 0x46, 0x1e, 0xa4, 0xf4, 0x0a, 0x24, 0x68, 0xd6, 0x47, + 0x66, 0xd3, 0xd1, 0xda, 0x85, 0x6a, 0xe3, 0x1e, 0xc5, 0x90, 0x62, 0xf8, 0xe8, 0x83, 0x98, 0x59, + 0xed, 0x7d, 0x80, 0x1e, 0x5e, 0x49, 0x47, 0xd4, 0x6b, 0x99, 0xf2, 0xe2, 0xa2, 0x9e, 0xf2, 0x23, + 0x3e, 0x46, 0x22, 0x64, 0xab, 0x75, 0xf4, 0xd2, 0xc6, 0x8e, 0x8b, 0xac, 0xe0, 0xa8, 0xda, 0x42, + 0xaf, 0x0f, 0xba, 0x14, 0x34, 0xa5, 0x5f, 0xcb, 0x2d, 0x7d, 0x30, 0x79, 0x37, 0x73, 0xcf, 0x96, + 0xe5, 0xe4, 0xe1, 0x17, 0xfa, 0xda, 0x72, 0x23, 0x77, 0x6d, 0x1b, 0x73, 0x31, 0x91, 0xb3, 0x52, + 0xe3, 0x9c, 0x40, 0x89, 0xce, 0xdd, 0xab, 0xb5, 0x6d, 0xdf, 0xd9, 0xc3, 0xc0, 0x81, 0xf8, 0x83, + 0x46, 0x55, 0xea, 0xe4, 0x51, 0x65, 0x62, 0x15, 0xb7, 0xd6, 0x39, 0x62, 0x73, 0x5f, 0x5a, 0x60, + 0x5b, 0x41, 0xce, 0xfb, 0x77, 0xcf, 0xae, 0x4d, 0x15, 0x11, 0x63, 0xa4, 0x77, 0x03, 0xe6, 0x77, + 0xf6, 0x62, 0x99, 0x4a, 0x65, 0xaa, 0x0b, 0x3f, 0xec, 0x94, 0x3f, 0xe4, 0xe4, 0x6b, 0xae, 0x53, + 0x43, 0xd1, 0xc2, 0x00, 0x3d, 0x16, 0x37, 0x4a, 0x03, 0xfb, 0x46, 0x14, 0x86, 0xa4, 0xa6, 0xad, + 0x83, 0x03, 0x41, 0x52, 0xd9, 0x19, 0x3d, 0xb4, 0x8f, 0x25, 0xbe, 0xc0, 0xea, 0xff, 0xae, 0xeb, + 0xef, 0x99, 0x7f, 0xfd, 0xc0, 0x1d, 0xf5, 0xa1, 0x95, 0xa9, 0xea, 0x31, 0x2e, 0x2c, 0xcd, 0x82, + 0xf8, 0x17, 0xe0, 0x14, 0x18, 0xe6, 0x0f, 0x81, 0x3f, 0xa0, 0x90, 0xca, 0xb2, 0xcb, 0xa1, 0x49, + 0x97, 0x24, 0xd1, 0x37, 0x75, 0x56, 0x46, 0x8f, 0x6c, 0xb8, 0xad, 0x58, 0x5a, 0x46, 0x51, 0x40, + 0xb2, 0x07, 0xb7, 0xb3, 0xdc, 0xec, 0x10, 0x47, 0x08, 0x2a, 0xc0, 0x07, 0x16, 0xc7, 0xb7, 0x34, + 0x8c, 0xb7, 0xb2, 0xd8, 0x44, 0x24, 0x20, 0x59, 0xa4, 0xff, 0xc9, 0x16, 0x22, 0x5a, 0xeb, 0x8f, + 0x92, 0x29, 0xd2, 0x65, 0x4a, 0xcc, 0x3b, 0x10, 0x31, 0x5b, 0x2a, 0x1a, 0xe5, 0x26, 0xba, 0xfe, + 0xd2, 0x56, 0xf0, 0x3f, 0x81, 0x2d, 0x03, 0xc7, 0xd1, 0xb7, 0x0b, 0xcb, 0x94, 0x09, 0x3d, 0x27, + 0xed, 0x3d, 0x92, 0x4f, 0xe9, 0xa5, 0x65, 0xfc, 0x25, 0x28, 0x61, 0xab, 0xc9, 0x10, 0x73, 0xb7, + 0xd3, 0x55, 0x92, 0x11, 0xd9, 0x6b, 0x63, 0xf2, 0xf8, 0x0b, 0x74, 0x13, 0x04, 0xd4, 0xe8, 0x92, + 0x17, 0x65, 0x6e, 0x58, 0x76, 0x95, 0x80, 0xa2, 0x2d, 0x66, 0xbb, 0xcd, 0xa9, 0x21, 0xc8, 0x06, + 0xe5, 0x9b, 0x85, 0xe3, 0xed, 0x13, 0xd7, 0x3d, 0x23, 0x8e, 0x66, 0x84, 0xcf, 0xe4, 0xa7, 0xa6, + 0xc7, 0x1c, 0x67, 0x13, 0xfe, 0x8f, 0x9d, 0x7b, 0xea, 0x4d, 0xec, 0x91, 0x7b, 0x2e, 0xe0, 0x40, + 0x7c, 0xf4, 0x80, 0xd8, 0x71, 0x65, 0x83, 0xff, 0x54, 0x48, 0x82, 0x24, 0x21, 0xcb, 0x56, 0xd7, + 0x09, 0xc3, 0xdd, 0x97, 0x3b, 0x87, 0x66, 0x4d, 0xbb, 0x61, 0x80, 0x11, 0x9e, 0x17, 0x02, 0xcd, + 0x7a, 0xf7, 0xd5, 0x45, 0xe0, 0x6c, 0xd3, 0xa2, 0xea, 0xe8, 0x14, 0x4f, 0xc0, 0xd4, 0xe5, 0x1a, + 0xa3, 0xae, 0x43, 0x8d, 0x99, 0x59, 0x64, 0x63, 0xe7, 0x0d, 0x5c, 0x38, 0x1d, 0x83, 0x35, 0x7e, + 0xf4, 0x9f, 0x4a, 0xe0, 0x12, 0x8d, 0xb1, 0xc8, 0x3e, 0x54, 0x2f, 0xfb, 0x1a, 0xe9, 0x11, 0xdc, + 0xe9, 0xf0, 0x72, 0x09, 0x5c, 0xd7, 0xae, 0xd8, 0xd5, 0xf1, 0xe9, 0x6a, 0xe3, 0x58, 0xda, 0x93, + 0x31, 0x97, 0x69, 0xe7, 0x27, 0xe0, 0xd8, 0x76, 0x8d, 0x26, 0x64, 0x1a, 0x00, 0x11, 0x92, 0x0e, + 0xb3, 0x83, 0xc1, 0x58, 0x23, 0x17, 0x2a, 0xde, 0x4d, 0x85, 0xed, 0x3b, 0xbf, 0xf9, 0xaa, 0xeb, + 0xc2, 0x50, 0xf1, 0x03, 0x35, 0xfd, 0x9a, 0x6d, 0xcf, 0xed, 0x39, 0x0f, 0xfc, 0x0b, 0xf1, 0x89, + 0x2d, 0x23, 0xe9, 0x79, 0x82, 0x63, 0x18, 0xfa, 0x66, 0x0f, 0xf5, 0x5e, 0x63, 0x41, 0x34, 0xbb, + 0x26, 0xf7, 0x56, 0xad, 0xbc, 0x63, 0x9c, 0x0a, 0x2f, 0x58, 0xb5, 0xb3, 0x51, 0xfd, 0xa6, 0x3d, + 0x2b, 0x10, 0x3c, 0xe2, 0xb0, 0x65, 0xc3, 0xf8, 0x6e, 0xc5, 0xf8, 0xdb, 0xb6, 0xb4, 0x8d, 0x24, + 0x95, 0xfd, 0xa9, 0x85, 0xc1, 0xd7, 0xc6, 0xa0, 0x06, 0x5a, 0x19, 0x15, 0x5d, 0x58, 0xc2, 0x00, + 0x85, 0x6e, 0x28, 0x81, 0xdc, 0x90, 0x70, 0xcc, 0x27, 0x22, 0x42, 0xc2, 0x1e, 0xac, 0x23, 0x59, + 0x03, 0x78, 0x6d, 0xac, 0xd1, 0x75, 0xee, 0x29, 0x89, 0x33, 0x8c, 0x60, 0x4e, 0x16, 0x11, 0x2f, + 0x82, 0x1c, 0x0e, 0xc4, 0xda, 0x9b, 0xcc, 0x54, 0xb7, 0x60, 0x6c, 0x13, 0xda, 0xb5, 0x89, 0x0f, + 0x95, 0xf7, 0x7a, 0x05, 0xfc, 0xef, 0x2a, 0x07, 0xe8, 0xd0, 0x50, 0x43, 0xb5, 0x06, 0xf6, 0x24, + 0x27, 0x53, 0x83, 0x4e, 0xc3, 0x66, 0xe9, 0x72, 0xf6, 0x89, 0x59, 0x30, 0x46, 0x88, 0x6a, 0xcd, + 0x5e, 0x86, 0x43, 0x59, 0xab, 0xe1, 0xa9, 0x31, 0x37, 0x4c, 0x96, 0xab, 0xbe, 0xe7, 0x83, 0x70, + 0xfd, 0xa9, 0x61, 0x55, 0xc2, 0x61, 0x1a, 0xbd, 0xac, 0x2e, 0x10, 0x04, 0x59, 0xd8, 0x87, 0x87, + 0xc7, 0xa9, 0x44, 0x54, 0xd8, 0xcc, 0xee, 0xc8, 0x31, 0x64, 0xc8, 0x20, 0x4a, 0xbb, 0x24, 0xd4, + 0xc7, 0x86, 0x2f, 0x69, 0xb6, 0x18, 0x10, 0x83, 0xeb, 0x41, 0x1e, 0x55, 0x7e, 0xcc, 0x95, 0x14, + 0x6b, 0xe7, 0xb9, 0xb6, 0xfc, 0x6f, 0xf8, 0x66, 0x70, 0x55, 0xc8, 0x22, 0x2d, 0xe1, 0x23, 0xf9, + 0x9d, 0x58, 0xd4, 0x92, 0xc7, 0xf8, 0x64, 0x4b, 0x8c, 0x8b, 0xe0, 0xef, 0xef, 0xfe, 0xd5, 0x74, + 0x73, 0x53, 0x9c, 0x4c, 0x83, 0xee, 0xea, 0x42, 0x33, 0x9c, 0x0f, 0x26, 0x0e, 0xc7, 0x24, 0x14, + 0x22, 0xed, 0x19, 0x40, 0x3d, 0x1f, 0x47, 0x17, 0x94, 0xc8, 0x15, 0x4c, 0x96, 0xc3, 0x77, 0x14, + 0x84, 0x7b, 0x35, 0xb7, 0x67, 0xb9, 0xb9, 0x56, 0x42, 0x68, 0x9f, 0xc6, 0x42, 0x43, 0xcd, 0xf8, + 0x77, 0x54, 0x33, 0xbc, 0xa8, 0x25, 0x4e, 0xd7, 0x7b, 0x00, 0x40, 0x8c, 0xa1, 0x17, 0x40, 0x20, + 0x17, 0xe2, 0x31, 0x3e, 0x99, 0x4c, 0x7c, 0x23, 0xa8, 0x71, 0x2f, 0xb8, 0xba, 0xc1, 0x9b, 0x5c, + 0x3d, 0x47, 0x83, 0x4b, 0x04, 0x02, 0xdc, 0x2b, 0xa3, 0xd6, 0x8d, 0x6b, 0x00, 0x73, 0x53, 0x54, + 0xae, 0x42, 0xb4, 0x3e, 0x3f, 0xf8, 0xd8, 0x5b, 0xff, 0x59, 0x2a, 0xa1, 0x4d, 0x2f, 0x77, 0x56, + 0xd2, 0xe1, 0xe0, 0x83, 0x5f, 0x6c, 0xaa, 0x0f, 0xcd, 0x38, 0xf2, 0xf4, 0x24, 0xbe, 0xe9, 0x37, + 0xcb, 0x3c, 0xff, 0x98, 0x93, 0xef, 0xc9, 0x80, 0x8a, 0x7d, 0x55, 0xac, 0xf8, 0x30, 0xab, 0x6d, + 0x67, 0x8f, 0x1f, 0xa4, 0x2a, 0xdd, 0x47, 0x18, 0x2a, 0xda, 0x24, 0x26, 0x2d, 0x98, 0x76, 0x0d, + 0x2d, 0xbe, 0xe8, 0x52, 0x47, 0x73, 0x25, 0x09, 0x60, 0xda, 0xce, 0x20, 0x4a, 0x12, 0xb5, 0x3d, + 0xc9, 0x90, 0xaf, 0x33, 0x6e, 0x51, 0x77, 0x2e, 0x7a, 0x3d, 0x38, 0x7c, 0x60, 0x11, 0x4c, 0x3c, + 0xf7, 0xe2, 0xca, 0x76, 0xd4, 0x15, 0xef, 0xc3, 0xa7, 0x24, 0x8f, 0x35, 0x1c, 0x33, 0x64, 0x09, + 0x10, 0x2b, 0x9d, 0x9a, 0x57, 0x16, 0xf6, 0x98, 0x84, 0x80, 0x2d, 0xed, 0xee, 0xa4, 0xf2, 0x85, + 0x86, 0xbe, 0x14, 0x45, 0xc9, 0xb7, 0x85, 0xaf, 0xec, 0x07, 0x24, 0xb1, 0xfc, 0xce, 0x49, 0x1d, + 0xc3, 0x58, 0x25, 0x8f, 0x33, 0x38, 0x0b, 0x2e, 0x9a, 0x3e, 0x32, 0x05, 0x8d, 0x69, 0x2b, 0x43, + 0xdc, 0x99, 0xb6, 0x13, 0x1c, 0x2d, 0x09, 0xaf, 0xbd, 0x62, 0x65, 0x46, 0x51, 0x37, 0x74, 0xe9, + 0xbd, 0x18, 0x52, 0x9f, 0x92, 0x0c, 0xfb, 0x37, 0x9f, 0x91, 0xe2, 0x1c, 0xe2, 0xc2, 0x79, 0xa2, + 0x62, 0x8b, 0x84, 0xeb, 0x35, 0xc0, 0xa8, 0x29, 0x44, 0xdc, 0x7a, 0xc6, 0x70, 0xd3, 0xf3, 0xf7, + 0xcc, 0xf6, 0x22, 0xf8, 0x7e, 0x08, 0x9d, 0x96, 0xfd, 0xba, 0xa4, 0xfd, 0xe9, 0x54, 0x17, 0x9d, + 0x5a, 0xf9, 0xd7, 0x94, 0x5c, 0x91, 0xfd, 0x34, 0xc4, 0x9f, 0x31, 0x21, 0x7e, 0x78, 0x89, 0x40, + 0xaa, 0x9b, 0xab, 0x44, 0x18, 0x82, 0x14, 0xd0, 0x63, 0xfb, 0xbc, 0x59, 0xa8, 0x77, 0xfa, 0x9c, + 0xaa, 0xae, 0xa9, 0x94, 0x0f, 0xfd, 0x42, 0x0e, 0x01, 0xc9, 0x04, 0xf7, 0xd9, 0x99, 0x4d, 0x15, + 0xed, 0x7b, 0x0c, 0x70, 0xfc, 0x1a, 0xfa, 0x5f, 0x7e, 0xfa, 0xfe, 0x37, 0xba, 0x3a, 0x5f, 0x97, + 0x7a, 0xcd, 0xd8, 0xc1, 0x7c, 0x33, 0x31, 0x32, 0x19, 0x16, 0xa2, 0x32, 0x2f, 0xcb, 0xfb, 0x2d, + 0xc4, 0x73, 0xa1, 0xe7, 0xa9, 0x98, 0xfa, 0x63, 0x85, 0x55, 0x73, 0x58, 0x7c, 0x13, 0xa0, 0xe8, + 0xe3, 0x02, 0x00, 0x88, 0x99, 0xcd, 0x08, 0x6c, 0x88, 0x10, 0xbf, 0xcc, 0x6e, 0xea, 0xd7, 0x5f, + 0xa0, 0x5f, 0xd3, 0x75, 0xc3, 0xee, 0x58, 0x0c, 0x46, 0x89, 0xd4, 0x9e, 0x51, 0x49, 0x9b, 0xdf, + 0xc3, 0xd6, 0x1b, 0xda, 0x0a, 0x9d, 0x3b, 0x0a, 0x38, 0x99, 0x21, 0x9e, 0x54, 0x4b, 0x2f, 0x1d, + 0xb8, 0xa9, 0x96, 0x75, 0x76, 0x20, 0xb0, 0x3f, 0x89, 0x39, 0xa8, 0xda, 0x9c, 0x95, 0x8e, 0x74, + 0x71, 0x2c, 0x77, 0xe0, 0x97, 0xc6, 0x01, 0x9c, 0x18, 0x5f, 0x9d, 0x5e, 0x06, 0x9b, 0x71, 0x00, + 0x05, 0x44, 0x44, 0xfa, 0x21, 0x5a, 0x7e, 0xcc, 0x12, 0x1c, 0xe3, 0x72, 0x58, 0x48, 0x59, 0xec, + 0xe3, 0xd4, 0x5d, 0x7f, 0xea, 0x12, 0x68, 0xce, 0x29, 0x13, 0x03, 0x82, 0xbb, 0xaf, 0x11, 0x11, + 0x4b, 0x53, 0xff, 0x52, 0x7e, 0x4c, 0x5a, 0x32, 0x07, 0x01, 0xb6, 0xc1, 0x64, 0x81, 0x1a, 0x3d, + 0x27, 0x6e, 0x64, 0xf0, 0x17, 0x01, 0x13, 0xfc, 0x91, 0xb2, 0xc7, 0x45, 0x15, 0xe2, 0xb2, 0xe6, + 0x89, 0xdc, 0x74, 0xca, 0x35, 0xb8, 0x9d, 0x0d, 0x77, 0xae, 0x89, 0x4f, 0xac, 0xbe, 0xcb, 0xee, + 0xac, 0x30, 0x2c, 0xa3, 0x8d, 0x6f, 0xaa, 0x84, 0x58, 0x9b, 0x14, 0x22, 0x38, 0xba, 0x8c, 0x79, + 0x57, 0x29, 0xc7, 0x9c, 0x8d, 0xa1, 0xcd, 0xc0, 0x8a, 0xc1, 0x1a, 0x92, 0x47, 0xf3, 0xd8, 0xe6, + 0x2e, 0xe7, 0xb0, 0xbd, 0x0f, 0x4f, 0xe0, 0xe5, 0x2e, 0xeb, 0x1b, 0x70, 0x50, 0xc8, 0x18, 0x4d, + 0x57, 0x96, 0x53, 0x0b, 0x31, 0x1c, 0xc8, 0x06, 0x70, 0x90, 0xf9, 0xe8, 0x6b, 0x76, 0x9f, 0xc0, + 0x40, 0x36, 0xd1, 0x9c, 0x21, 0x26, 0x51, 0xea, 0x7a, 0x66, 0x65, 0xf5, 0x45, 0x52, 0x1d, 0xa3, + 0xfc, 0x45, 0xd4, 0xc0, 0x95, 0xa0, 0x71, 0x0a, 0x41, 0x69, 0xfb, 0xd3, 0xe1, 0xf1, 0x33, 0x71, + 0x94, 0x64, 0xe9, 0x4e, 0x3f, 0xf0, 0x7f, 0x52, 0x32, 0xd2, 0x49, 0x5a, 0x89, 0xa7, 0xc4, 0xe7, + 0x5a, 0x44, 0x55, 0xbf, 0xc9, 0x0c, 0xd8, 0x47, 0x8b, 0x55, 0xc2, 0x01, 0xb4, 0xdf, 0x6b, 0xb9, + 0x53, 0x3b, 0xf8, 0x94, 0xc7, 0xe0, 0xcb, 0xcc, 0xda, 0xf6, 0x72, 0x7b, 0x44, 0x9a, 0xb7, 0x48, + 0x46, 0xe5, 0x3e, 0x4f, 0x1c, 0xf3, 0xb1, 0xf1, 0x13, 0x86, 0x51, 0x26, 0xa0, 0x08, 0xc9, 0xaf, + 0x0e, 0xa8, 0x6e, 0x17, 0x00, 0x8a, 0x9f, 0xf0, 0x75, 0xe3, 0xd4, 0x30, 0xc9, 0x9d, 0x49, 0x6b, + 0xf1, 0x08, 0xb0, 0x8f, 0x5a, 0xb7, 0x4f, 0xc9, 0x67, 0x05, 0x6a, 0x8f, 0x09, 0x6c, 0xad, 0xe9, + 0xb0, 0x3d, 0x78, 0x21, 0x67, 0x82, 0x26, 0xdb, 0xa4, 0xce, 0x9c, 0x4c, 0x34, 0xf9, 0x29, 0x2f, + 0x4a, 0x7a, 0x0c, 0xa5, 0xd2, 0xfc, 0xe2, 0x98, 0x6f, 0xe9, 0xea, 0xcb, 0x67, 0x1d, 0x47, 0xfc, + 0xd0, 0x39, 0xa7, 0x1a, 0xf5, 0xae, 0xa6, 0x8e, 0x53, 0x8e, 0xc8, 0x5c, 0x91, 0xaf, 0x0e, 0x62, + 0x3a, 0xdb, 0x30, 0x5c, 0x80, 0x2c, 0xb7, 0x9d, 0x7d, 0xa2, 0x3f, 0xf9, 0x67, 0x06, 0x1a, 0xae, + 0x3d, 0x94, 0x24, 0x88, 0x93, 0x87, 0x6f, 0x60, 0x67, 0x60, 0x15, 0x41, 0x5f, 0x1a, 0x22, 0x25, + 0x9c, 0x59, 0x80, 0x0c, 0x66, 0x2e, 0x0f, 0x58, 0xbc, 0x47, 0x85, 0x4d, 0xb6, 0x3d, 0x80, 0x90, + 0xd8, 0x4e, 0x73, 0x09, 0x97, 0x3b, 0xd9, 0x40, 0xd3, 0x77, 0xaf, 0x5c, 0x81, 0x91, 0xd7, 0xaf, + 0xba, 0x71, 0x68, 0x81, 0x64, 0x70, 0xcd, 0x3f, 0x55, 0xff, 0xdd, 0xd9, 0xda, 0x14, 0x92, 0xda, + 0xea, 0xdf, 0xd6, 0x27, 0xd6, 0xf7, 0x37, 0x9c, 0x6c, 0xc4, 0xf6, 0xfc, 0x87, 0x25, 0x6f, 0x9d, + 0xae, 0xe4, 0xe9, 0x13, 0xd3, 0xf1, 0xc9, 0x5b, 0x87, 0x74, 0x38, 0x54, 0x4d, 0xa4, 0x7b, 0x15, + 0xa1, 0x85, 0x03, 0xba, 0xa9, 0x3d, 0xeb, 0xd0, 0x27, 0x33, 0xa4, 0xb3, 0x8d, 0x7b, 0xd1, 0x0b, + 0xb7, 0x2c, 0xed, 0x92, 0x61, 0x1e, 0x6b, 0x6f, 0x40, 0x96, 0x1d, 0xaa, 0x63, 0x5f, 0x72, 0xe7, + 0xec, 0xba, 0x0e, 0x23, 0xa0, 0x51, 0x92, 0x99, 0x0e, 0x2a, 0xe0, 0x2c, 0x5f, 0xe8, 0x87, 0xf7, + 0x25, 0xb7, 0xb2, 0xed, 0xa1, 0xf7, 0x43, 0x2c, 0xac, 0xa3, 0xbd, 0x62, 0x5c, 0xf5, 0x1f, 0xa6, + 0x53, 0x64, 0xaa, 0x99, 0x25, 0xb0, 0xbb, 0xc0, 0xf6, 0x42, 0x58, 0xc9, 0xc6, 0x61, 0xbe, 0xff, + 0x4e, 0x81, 0x8d, 0x1c, 0xfe, 0x25, 0x79, 0x1e, 0xc5, 0x22, 0xaa, 0x2b, 0x56, 0x29, 0xa4, 0x2a, + 0xcf, 0x86, 0xa4, 0x4d, 0x37, 0x23, 0xc2, 0x6b, 0x13, 0x8b, 0xfd, 0x3b, 0x90, 0xe6, 0xc3, 0x7f, + 0xa6, 0xfc, 0xfc, 0xaa, 0xb9, 0x69, 0xbc, 0x95, 0xa2, 0x8a, 0xbf, 0x72, 0xac, 0x2c, 0x1c, 0x10, + 0x1f, 0x63, 0x2c, 0xc2, 0x29, 0x3d, 0x14, 0x4e, 0x57, 0xf4, 0xc5, 0xdd, 0xc9, 0xfa, 0x61, 0x9a, + 0x7a, 0xe1, 0xd2, 0xfe, 0xd8, 0xb8, 0xa3, 0xa1, 0x93, 0x06, 0x2a, 0x88, 0x20, 0x72, 0x67, 0x2b, + 0xd2, 0x67, 0x9f, 0x83, 0x8c, 0xbd, 0x19, 0xa8, 0x11, 0x8a, 0xad, 0x01, 0x2f, 0x06, 0x3b, 0x2f, + 0x2a, 0xca, 0x46, 0x5d, 0xbd, 0x7f, 0xd4, 0xca, 0x95, 0x3d, 0x79, 0xc2, 0xf4, 0xba, 0x27, 0xd7, + 0x13, 0xb5, 0x9c, 0xaa, 0xe1, 0xc5, 0xce, 0x83, 0x3f, 0x6a, 0x72, 0xdb, 0x89, 0xd6, 0x0f, 0x59, + 0xf2, 0xde, 0x57, 0x47, 0x4c, 0xef, 0xf5, 0x6f, 0x7a, 0x09, 0xb2, 0x7f, 0x88, 0x04, 0x10, 0xfa, + 0x44, 0x95, 0xef, 0x39, 0x3c, 0x8b, 0x47, 0x3f, 0x0d, 0x17, 0x93, 0x4f, 0xc9, 0xe7, 0xa1, 0x10, + 0x94, 0x05, 0xea, 0x3a, 0x6b, 0x0c, 0xa3, 0x72, 0x6a, 0xc6, 0x54, 0x97, 0xff, 0x35, 0x44, 0xfd, + 0x30, 0x79, 0x2a, 0x62, 0x12, 0x84, 0x27, 0x78, 0x5d, 0x83, 0x7f, 0xf3, 0xe2, 0xb1, 0xcf, 0x4a, + 0x08, 0x0e, 0x6c, 0x64, 0x5e, 0x04, 0x33, 0xac, 0x24, 0x8a, 0x2e, 0xb1, 0x9b, 0x83, 0x60, 0xc3, + 0x74, 0x37, 0x6c, 0x34, 0xbb, 0xb0, 0x51, 0xbd, 0x4e, 0xa4, 0x9d, 0xa7, 0x81, 0xb4, 0x29, 0xc8, + 0xd6, 0x08, 0xb2, 0xe2, 0xca, 0xed, 0xc2, 0x72, 0x7b, 0x2a, 0x2c, 0x45, 0xce, 0xb1, 0xb1, 0x87, + 0x4b, 0x43, 0xd2, 0xe2, 0x95, 0xd7, 0xee, 0x16, 0x08, 0xaa, 0x0c, 0x01, 0x05, 0x6c, 0xb7, 0xba, + 0x06, 0x55, 0x32, 0x20, 0xb9, 0xed, 0xc8, 0xb2, 0xbd, 0x60, 0x80, 0x20, 0xcc, 0xda, 0xce, 0xee, + 0x72, 0x2a, 0xa0, 0x5e, 0x86, 0x37, 0x12, 0xc9, 0xef, 0xee, 0x9a, 0x2a, 0x3f, 0x9f, 0x7d, 0x8d, + 0x5c, 0xd8, 0x59, 0x3c, 0x6b, 0x95, 0x8a, 0x79, 0xc1, 0x34, 0xd3, 0x4d, 0x9e, 0xb7, 0xfb, 0x59, + 0x7d, 0x91, 0x4e, 0x24, 0xa3, 0x44, 0x3d, 0x1f, 0xd3, 0xd6, 0x9e, 0x41, 0x02, 0x8a, 0x84, 0x36, + 0x61, 0xab, 0x5f, 0x77, 0x5a, 0x11, 0x4b, 0x87, 0x82, 0x07, 0x33, 0x5e, 0x53, 0x96, 0xd1, 0x2b, + 0x4f, 0xbe, 0x3c, 0x9b, 0x37, 0x44, 0x29, 0xed, 0x61, 0x5a, 0xda, 0x19, 0xb7, 0x41, 0x06, 0xeb, + 0x75, 0x82, 0x6d, 0x0a, 0xb5, 0xe8, 0x40, 0xdb, 0x3e, 0xe2, 0xd4, 0xd8, 0x4e, 0xe0, 0x8c, 0xb6, + 0xce, 0x32, 0x7f, 0xe7, 0xd9, 0x3e, 0x54, 0x87, 0xa1, 0x5a, 0xc6, 0x40, 0x11, 0xb1, 0x09, 0xc9, + 0x7d, 0x54, 0x1c, 0xb8, 0xca, 0x6b, 0xd2, 0xa1, 0x97, 0xa6, 0x40, 0x20, 0x9d, 0x7c, 0xf0, 0xec, + 0x55, 0xda, 0x63, 0xd0, 0x9c, 0x80, 0xc1, 0xfa, 0x18, 0x10, 0x75, 0x52, 0x0c, 0x3c, 0x6b, 0x5d, + 0x7c, 0xe0, 0x23, 0x90, 0x8e, 0x18, 0x2c, 0xff, 0x06, 0x5c, 0x18, 0x19, 0x46, 0xd2, 0x49, 0x60, + 0x4e, 0x7d, 0xf8, 0xb2, 0xc4, 0x82, 0x20, 0x90, 0x27, 0xbb, 0x98, 0xfe, 0x08, 0x41, 0xb1, 0x1e, + 0x18, 0xd2, 0xe9, 0x7f, 0x92, 0x76, 0x8d, 0xd2, 0x48, 0xb9, 0x35, 0xa7, 0x3d, 0x18, 0x59, 0x4e, + 0x35, 0x9f, 0x09, 0x4f, 0x5d, 0x64, 0x96, 0x2c, 0x01, 0x4a, 0x21, 0x03, 0x18, 0xd2, 0x32, 0xa4, + 0x21, 0xf7, 0xeb, 0x63, 0x95, 0x5d, 0xd2, 0x52, 0xb8, 0xa7, 0xfd, 0x9b, 0x9d, 0x4c, 0x13, 0x5b, + 0x66, 0x8b, 0x38, 0x39, 0x73, 0x82, 0x79, 0x24, 0x30, 0x8c, 0x91, 0xcd, 0xcc, 0xc0, 0x97, 0xf5, + 0xc7, 0xa9, 0x29, 0x96, 0x89, 0x3c, 0xa3, 0x6c, 0x1e, 0xc8, 0x83, 0x2f, 0xd8, 0x2a, 0x99, 0x03, + 0x2b, 0xb3, 0x5a, 0x14, 0x60, 0x71, 0x5d, 0x4a, 0x72, 0x47, 0xb6, 0x27, 0xa7, 0x9d, 0x6f, 0x44, + 0x0c, 0xfd, 0x60, 0xb4, 0x32, 0xd5, 0x21, 0x01, 0xe8, 0x0e, 0x07, 0x39, 0xe5, 0x67, 0x21, 0x6f, + 0x1c, 0x1a, 0xa0, 0x96, 0xd8, 0x1c, 0xe4, 0x2a, 0xec, 0x67, 0xf5, 0xe2, 0x95, 0x09, 0xa2, 0x8c, + 0xb8, 0x54, 0x86, 0x27, 0x7c, 0x0f, 0x3c, 0xfd, 0x69, 0x7c, 0xbc, 0x06, 0x7c, 0x76, 0xfe, 0xdb, + 0x90, 0x1a, 0x5c, 0x9d, 0xec, 0x0d, 0xdd, 0x9a, 0xc0, 0x01, 0x51, 0x03, 0xa2, 0x7f, 0x87, 0x40, + 0xcb, 0xbc, 0xfc, 0xad, 0xfe, 0x6d, 0x7a, 0x17, 0x1b, 0x1f, 0x74, 0xe9, 0x71, 0x3f, 0xbd, 0x69, + 0xd9, 0x76, 0xa9, 0x94, 0x40, 0x10, 0x01, 0x53, 0x5e, 0xfc, 0x52, 0xa4, 0x0c, 0x09, 0xa9, 0x06, + 0xce, 0xfb, 0x0b, 0xfa, 0xc4, 0xa3, 0x2b, 0x1b, 0xf0, 0x7d, 0xeb, 0x46, 0x1d, 0x90, 0x3d, 0x42, + 0x7c, 0xa9, 0xa3, 0x94, 0x4b, 0x5e, 0xb1, 0x3b, 0x5a, 0x23, 0xed, 0x7a, 0x7b, 0xe3, 0xdb, 0x90, + 0xa9, 0xde, 0xbd, 0x8c, 0x86, 0x81, 0xfd, 0xed, 0x81, 0x6e, 0x7c, 0xcf, 0x89, 0x38, 0x80, 0x79, + 0xba, 0x05, 0x2d, 0xd3, 0x59, 0x6c, 0xe6, 0x23, 0x28, 0x1c, 0xbf, 0xe2, 0xbb, 0x56, 0xa5, 0x76, + 0xf5, 0xae, 0xeb, 0xe0, 0x64, 0x7b, 0xc4, 0xf2, 0x21, 0x7e, 0x5f, 0xde, 0xf6, 0x2d, 0xfd, 0x7d, + 0x1f, 0x26, 0x08, 0x57, 0x9f, 0x9d, 0x16, 0x76, 0xcf, 0x93, 0xa6, 0x81, 0x5c, 0x43, 0x24, 0x74, + 0x86, 0x90, 0x00, 0x6a, 0x18, 0x35, 0x9e, 0x72, 0x7c, 0x69, 0xf2, 0xd2, 0x08, 0xda, 0x61, 0x86, + 0x01, 0xf9, 0x3c, 0xfc, 0xc6, 0x53, 0x73, 0xd6, 0x9c, 0xe7, 0xfa, 0x91, 0xc8, 0x96, 0x93, 0x2c, + 0xd1, 0x8f, 0xcf, 0x83, 0xd4, 0x70, 0xf8, 0xa8, 0x0c, 0x1d, 0xb6, 0x7d, 0x36, 0xed, 0xc0, 0x19, + 0x57, 0xf3, 0x8f, 0x5c, 0xbb, 0x25, 0xb4, 0x13, 0xae, 0x4c, 0x7a, 0x65, 0x68, 0xfd, 0x2c, 0x4d, + 0x2b, 0x37, 0xde, 0x0c, 0xab, 0xfd, 0x84, 0x12, 0xd5, 0x13, 0x38, 0xdc, 0x8d, 0xbc, 0x65, 0xed, + 0x54, 0x4d, 0xc2, 0x98, 0x2c, 0x25, 0x36, 0xc0, 0x35, 0x33, 0x33, 0x43, 0x80, 0xc9, 0x90, 0x4a, + 0xc4, 0xfa, 0xb5, 0x92, 0x0c, 0x69, 0xc8, 0xc4, 0xbc, 0x1a, 0xcc, 0x17, 0x65, 0xab, 0x04, 0x35, + 0x34, 0xb4, 0xdf, 0x7e, 0x74, 0x5c, 0xab, 0x58, 0x0a, 0xee, 0xed, 0x58, 0xc2, 0xc5, 0x7b, 0x92, + 0x10, 0x7b, 0x31, 0x65, 0x02, 0xbd, 0x49, 0x71, 0x22, 0x0d, 0xb6, 0x6c, 0xca, 0x60, 0x45, 0x89, + 0xc3, 0xf3, 0x81, 0x75, 0x49, 0xe7, 0xee, 0x39, 0x8a, 0x1f, 0x95, 0xe6, 0xe9, 0xbd, 0xe9, 0x41, + 0x25, 0xef, 0xc2, 0xcc, 0xe4, 0x09, 0x99, 0xb7, 0x91, 0xad, 0x5e, 0x44, 0xf0, 0x37, 0x74, 0x1a, + 0x2b, 0x61, 0x04, 0xaf, 0x43, 0xbd, 0x0a, 0x32, 0xb5, 0x06, 0xe2, 0x34, 0xb4, 0xb0, 0x6e, 0x1b, + 0xe8, 0x70, 0x3f, 0x20, 0xee, 0xbc, 0x96, 0xea, 0x37, 0x65, 0x1f, 0xce, 0x9f, 0xa4, 0x6b, 0x37, + 0xee, 0x79, 0x1e, 0x50, 0xae, 0xd5, 0x64, 0xa7, 0xc6, 0xbb, 0x0c, 0xe8, 0x0d, 0x88, 0xb9, 0xf4, + 0x33, 0x1d, 0x79, 0xb3, 0x41, 0xa4, 0x79, 0x1e, 0xf4, 0xc5, 0x6a, 0x26, 0xef, 0x07, 0xbe, 0xf8, + 0x0f, 0x42, 0xa9, 0x12, 0xb3, 0xe3, 0x38, 0xeb, 0xd9, 0x48, 0x9b, 0x95, 0x96, 0x39, 0x07, 0x3f, + 0xec, 0xaf, 0x7e, 0x46, 0xa9, 0x3a, 0x63, 0xe4, 0x1f, 0x9b, 0x8a, 0x1c, 0x0d, 0x0a, 0x26, 0x67, + 0x88, 0xd6, 0x6d, 0x40, 0x9b, 0x67, 0x5f, 0xc1, 0x12, 0x3b, 0x36, 0x1d, 0x85, 0x98, 0x14, 0x15, + 0x2b, 0xdf, 0x69, 0x8d, 0xfb, 0x63, 0xb7, 0xf3, 0xe4, 0x46, 0x8b, 0xe8, 0x2d, 0x8b, 0xbc, 0x29, + 0x36, 0xb0, 0xb8, 0x1a, 0x60, 0x77, 0xee, 0x1c, 0xbf, 0xbe, 0x82, 0xa8, 0xc4, 0x5e, 0xe5, 0x3f, + 0x87, 0xa2, 0x4c, 0x2b, 0x36, 0xeb, 0x59, 0x62, 0x2e, 0xcf, 0x91, 0xa0, 0xb3, 0x24, 0x2b, 0xd2, + 0xde, 0x99, 0xef, 0xaf, 0x42, 0x6a, 0xf7, 0xc3, 0x1e, 0xbb, 0xa5, 0x78, 0x77, 0x45, 0x79, 0xba, + 0xf2, 0x7c, 0xd0, 0x90, 0x2c, 0x1e, 0x91, 0x83, 0x84, 0xb1, 0x4b, 0xaf, 0xfa, 0xe7, 0x3d, 0xb1, + 0xe3, 0x76, 0xb3, 0xee, 0x11, 0x2b, 0xb1, 0xd6, 0x6c, 0x26, 0xe1, 0x1f, 0xdd, 0x3a, 0x9b, 0xa6, + 0xda, 0x9d, 0x68, 0x30, 0x72, 0x64, 0xf0, 0x5e, 0x4b, 0x09, 0x18, 0xf5, 0x80, 0xd2, 0xc3, 0x28, + 0x05, 0x36, 0xd0, 0x1d, 0x95, 0xb9, 0xaa, 0xc4, 0x77, 0xac, 0x70, 0x51, 0xbd, 0x69, 0x24, 0x04, + 0x90, 0x47, 0x3a, 0x5e, 0xbe, 0x05, 0xbe, 0x1a, 0x9c, 0xc1, 0x43, 0xe2, 0x2a, 0xab, 0x84, 0x18, + 0x0c, 0x29, 0x96, 0xf9, 0x07, 0x7b, 0x69, 0x0d, 0x2e, 0x8a, 0x71, 0x7e, 0xf1, 0x21, 0xa5, 0x4b, + 0x42, 0xcc, 0x8d, 0xd7, 0xa4, 0x88, 0xa7, 0x45, 0xa1, 0xe1, 0x78, 0x37, 0xc4, 0xaa, 0x49, 0x45, + 0x4e, 0x91, 0xce, 0x22, 0x51, 0x46, 0xe4, 0x4d, 0xdd, 0x3f, 0xff, 0x8d, 0x18, 0x02, 0xb2, 0xad, + 0x00, 0x00, 0x00, 0x0a, 0x08, 0x48, 0xd1, 0x57, 0x67, 0x64, 0xfc, 0xe0, 0x89, 0x98, 0x09, 0x8a, + 0x86, 0x50, 0x36, 0x7a, 0x3e, 0x65, 0x02, 0x1e, 0xe1, 0x1c, 0xfa, 0xaa, 0xf3, 0x87, 0x27, 0x25, + 0x57, 0x05, 0x48, 0xa2, 0x0d, 0x6a, 0xa3, 0x46, 0xc6, 0x42, 0x17, 0x4a, 0xea, 0xde, 0x49, 0xe2, + 0x51, 0x99, 0x72, 0x68, 0xe0, 0xfa, 0xe5, 0x90, 0xe6, 0x70, 0x7a, 0x21, 0x10, 0x35, 0x73, 0xf8, + 0xa9, 0x97, 0x5c, 0x15, 0xad, 0x66, 0x4a, 0xb2, 0x9c, 0x7b, 0x57, 0x93, 0x0d, 0xf8, 0x25, 0xe3, + 0x4c, 0x8b, 0x25, 0xbe, 0x70, 0xcf, 0xf8, 0xb7, 0xaa, 0x7f, 0x1b, 0xa3, 0x19, 0x5b, 0x65, 0x74, + 0x3d, 0x35, 0xea, 0x35, 0x71, 0xc5, 0x40, 0xf0, 0x19, 0x8d, 0x5f, 0x51, 0xfb, 0x9c, 0x40, 0xf1, + 0x12, 0xc3, 0x97, 0xfe, 0x2a, 0xb7, 0x30, 0xa5, 0xe8, 0x74, 0x45, 0x95 +}; + +int test_lms_verify(void* data) +{ + int err = 0; + EVP_PKEY* key = NULL; + EVP_PKEY_CTX* ctx = NULL; + EVP_SIGNATURE* signature = NULL; + unsigned char badSig[sizeof(lmsVerifySig)]; + unsigned char badMsg[sizeof(lmsVerifyMsg)]; + + (void)data; + XMEMCPY(badSig, lmsVerifySig, sizeof(badSig)); + XMEMCPY(badMsg, lmsVerifyMsg, sizeof(badMsg)); + badSig[sizeof(badSig) / 2] ^= 1; + badMsg[sizeof(badMsg) / 2] ^= 1; + err = lms_from_data(lmsVerifyPub, sizeof(lmsVerifyPub), + EVP_PKEY_PUBLIC_KEY, &key); + if (err == 0) { + signature = EVP_SIGNATURE_fetch(wpLibCtx, "LMS", NULL); + ctx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, key, NULL); + err = (signature == NULL) || (ctx == NULL) || + (EVP_PKEY_verify_message_init(ctx, signature, NULL) != 1); + } + if (err == 0) { + err = EVP_PKEY_verify(ctx, lmsVerifySig, sizeof(lmsVerifySig), + lmsVerifyMsg, sizeof(lmsVerifyMsg)) != 1; + if (err != 0) PRINT_ERR_MSG("valid LMS signature did not verify"); + } + if (err == 0) { + err = EVP_PKEY_verify(ctx, badSig, sizeof(badSig), + lmsVerifyMsg, sizeof(lmsVerifyMsg)) == 1; + if (err != 0) PRINT_ERR_MSG("tampered LMS signature was accepted"); + } + if (err == 0) { + err = EVP_PKEY_verify(ctx, lmsVerifySig, sizeof(lmsVerifySig), + badMsg, sizeof(badMsg)) == 1; + if (err != 0) PRINT_ERR_MSG("tampered LMS message was accepted"); + } + ERR_clear_error(); + EVP_SIGNATURE_free(signature); + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(key); + return err; +} + #endif /* WP_HAVE_LMS */ diff --git a/test/unit.c b/test/unit.c index 3780dedc..16f4670c 100644 --- a/test/unit.c +++ b/test/unit.c @@ -555,6 +555,7 @@ TEST_CASE test_case[] = { #endif #ifdef WP_HAVE_LMS + TEST_DECL(test_lms_verify, NULL), TEST_DECL(test_lms_import_export, NULL), TEST_DECL(test_lms_decode, NULL), TEST_DECL(test_lms_unsupported_operations, NULL), diff --git a/test/unit.h b/test/unit.h index 45ba1b10..6a3d14ce 100644 --- a/test/unit.h +++ b/test/unit.h @@ -574,6 +574,7 @@ int test_des3_tls_cbc_dec(void *data); #ifdef WP_HAVE_LMS int test_lms_import_export(void *data); +int test_lms_verify(void *data); int test_lms_decode(void *data); int test_lms_unsupported_operations(void *data); #endif